{
	"$schema": "https://json-schema.org/draft/2020-12/schema",
	"$id": "https://docs.torcato.dev/repo-tooling/schemas/lockfile.json",
	"title": "Lockfile",
	"description": ".repo-tooling.json — two documents sharing one file: `record` is written by @rtorcato/repo-tooling (`setup` and `fix`) and stamped with provenance; `rules` is written by humans, reviewed in PRs, and never stamped. Both are read by `doctor`.",
	"type": "object",
	"additionalProperties": false,
	"required": [
		"version",
		"record"
	],
	"properties": {
		"$schema": {
			"type": "string",
			"description": "URL of this schema; stamped on every write so editors validate the file."
		},
		"version": {
			"type": "integer",
			"description": "Lockfile format version (current: 4). v2 added config.language, v3 added assets, v4 split the file into record/rules subtrees; older files are migrated on read."
		},
		"record": {
			"type": "object",
			"additionalProperties": false,
			"required": [
				"config",
				"writtenBy",
				"writtenAt"
			],
			"description": "The tool-written record of what setup/fix last did. Only the tool writes here — the writtenBy/writtenAt stamps are provenance claims about exactly this subtree.",
			"properties": {
				"config": {
					"title": "ProjectConfig",
					"description": "The resolved setup configuration this repo was scaffolded or audited with.",
					"type": "object",
					"additionalProperties": false,
					"required": [
						"projectName",
						"projectType",
						"typescript",
						"linting",
						"formatting",
						"testing",
						"gitHooks",
						"commitLint",
						"semanticRelease",
						"securityAutomation",
						"bundler"
					],
					"properties": {
						"projectName": {
							"type": "string",
							"minLength": 1
						},
						"language": {
							"type": "string",
							"enum": [
								"js",
								"swift",
								"perl",
								"python"
							]
						},
						"projectType": {
							"type": "string",
							"enum": [
								"library",
								"web-app",
								"node-api",
								"nextjs-app",
								"react-app"
							]
						},
						"typescript": {
							"type": "object",
							"additionalProperties": false,
							"required": [
								"enabled",
								"config"
							],
							"properties": {
								"enabled": {
									"type": "boolean"
								},
								"config": {
									"type": "string",
									"enum": [
										"base",
										"react",
										"vite-app",
										"next",
										"node",
										"express"
									]
								}
							}
						},
						"linting": {
							"type": "object",
							"additionalProperties": false,
							"required": [
								"tool"
							],
							"properties": {
								"tool": {
									"type": "string",
									"enum": [
										"biome",
										"eslint",
										"both",
										"none"
									]
								},
								"eslintConfig": {
									"type": "string",
									"enum": [
										"base",
										"nextjs"
									]
								}
							}
						},
						"formatting": {
							"type": "object",
							"additionalProperties": false,
							"required": [
								"tool"
							],
							"properties": {
								"tool": {
									"type": "string",
									"enum": [
										"biome",
										"prettier",
										"none"
									]
								}
							}
						},
						"testing": {
							"type": "object",
							"additionalProperties": false,
							"required": [
								"framework"
							],
							"properties": {
								"framework": {
									"type": "string",
									"enum": [
										"vitest",
										"jest",
										"playwright",
										"cypress",
										"none"
									]
								},
								"environment": {
									"type": "string",
									"enum": [
										"node",
										"browser",
										"both"
									]
								}
							}
						},
						"gitHooks": {
							"type": "boolean"
						},
						"commitLint": {
							"type": "boolean"
						},
						"semanticRelease": {
							"type": "boolean"
						},
						"changesets": {
							"type": "boolean"
						},
						"releasePlease": {
							"type": "boolean"
						},
						"oxlint": {
							"type": "boolean"
						},
						"securityAutomation": {
							"type": "boolean"
						},
						"bundler": {
							"type": "string",
							"enum": [
								"tsup",
								"esbuild",
								"rollup",
								"rolldown",
								"vite",
								"none"
							]
						},
						"treeshakeCheck": {
							"type": "boolean"
						},
						"publint": {
							"type": "boolean"
						},
						"badges": {
							"type": "boolean"
						},
						"aiSetup": {
							"type": "boolean"
						},
						"turborepo": {
							"type": "boolean"
						},
						"nx": {
							"type": "boolean"
						},
						"tailwind": {
							"type": "boolean"
						},
						"docsSite": {
							"type": "boolean",
							"deprecated": true
						},
						"brand": {
							"type": "boolean",
							"deprecated": true
						},
						"bun": {
							"type": "boolean"
						},
						"docs": {
							"type": "object",
							"additionalProperties": false,
							"properties": {
								"url": {
									"type": "string",
									"format": "uri",
									"pattern": "^https?://"
								},
								"deploy": {
									"type": "string",
									"enum": [
										"github",
										"cloudflare",
										"none"
									]
								}
							}
						}
					}
				},
				"assets": {
					"type": "object",
					"additionalProperties": {
						"type": "string"
					},
					"description": "Preset name → sha256 of the asset's pristine content at copy time. Lets doctor tell a deliberate local fork (file differs from this hash) from a copy the package has since moved past (file still matches, shipped asset doesn't). A preset with no entry is untracked, never drifted."
				},
				"writtenBy": {
					"type": "string",
					"description": "Package name and version that last wrote the record subtree."
				},
				"writtenAt": {
					"type": "string",
					"format": "date-time",
					"description": "ISO 8601 timestamp of the last record write."
				}
			}
		},
		"rules": {
			"type": "object",
			"additionalProperties": false,
			"description": "The human-written ruleset: the repo's stated intent, edited by hand and reviewed in PRs. The tool carries it forward verbatim on every write and never stamps it.",
			"properties": {
				"aiLoop": {
					"type": "object",
					"additionalProperties": false,
					"deprecated": true,
					"description": "Deprecated: moved to `.repo-ai.json`, owned by @rtorcato/repo-ai. Still accepted so existing files validate; removed in the next major. `npx @rtorcato/repo-ai fix config` moves it across.",
					"properties": {
						"agentUser": {
							"type": "string",
							"description": "Login that in-flight work is assigned to, so `assignee` says whose turn it is. Must be an assignable collaborator; the skills verify that at runtime."
						}
					}
				},
				"dependabot": {
					"type": "object",
					"additionalProperties": false,
					"description": "What the generated dependabot-automerge workflow does with a patch/minor PR it would otherwise auto-merge. `doctor` reports drift against it and `fix dependabot` regenerates from it.",
					"properties": {
						"onPr": {
							"type": "string",
							"enum": [
								"automerge",
								"label"
							],
							"default": "automerge",
							"description": "`automerge` (default) merges on green. `label` adds `label` instead and never merges. Majors and consumer-facing bumps always go to a human either way."
						},
						"label": {
							"type": "string",
							"minLength": 1,
							"description": "The label `onPr: \"label\"` adds. Defaults to `needs-review` when omitted."
						}
					}
				},
				"requiredSkills": {
					"type": "array",
					"items": {
						"type": "string"
					},
					"deprecated": true,
					"description": "Deprecated: moved to `.repo-ai.json`, owned by @rtorcato/repo-ai. Still accepted so existing files validate; removed in the next major. `npx @rtorcato/repo-ai fix config` moves it across."
				},
				"mcp": {
					"type": "object",
					"additionalProperties": false,
					"description": "Advisory MCP metadata: names, importance and reasons only, never an install directive. Executable server config belongs in the native .mcp.json, which carries Claude Code's own first-use consent prompt.",
					"properties": {
						"recommended": {
							"type": "array",
							"description": "MCP servers this repo's workflow assumes. doctor reports which of them .mcp.json does not declare, informationally — it never installs or enables one.",
							"items": {
								"type": "object",
								"additionalProperties": false,
								"required": [
									"name",
									"importance",
									"why"
								],
								"properties": {
									"name": {
										"type": "string",
										"description": "The server name as it would appear in .mcp.json."
									},
									"importance": {
										"type": "string",
										"enum": [
											"nice-to-have",
											"important",
											"critical"
										],
										"description": "How much of the repo's workflow assumes the server."
									},
									"why": {
										"type": "string",
										"description": "One line on what the server is for — the thing .mcp.json structurally cannot say."
									}
								}
							}
						}
					}
				},
				"exceptions": {
					"type": "object",
					"additionalProperties": {
						"type": "string",
						"minLength": 1
					},
					"description": "Declared exceptions: doctor check name → the reason this repo deliberately deviates. The reason is mandatory and non-empty — doctor shows the check as `declared` with it (never hidden) and stops failing the run for it. An entry naming a check doctor does not run is itself reported as drift."
				},
				"brand": {
					"type": "object",
					"additionalProperties": false,
					"deprecated": true,
					"description": "Deprecated: the brand scaffold moved to @rtorcato/shared-docs. Still accepted so existing files validate; removed in the next major.",
					"properties": {
						"tagline": {
							"type": "string",
							"description": "Short line for the banners and social card, used in place of the package.json description. Keep it to two lines of about 42 characters."
						}
					}
				},
				"docs": {
					"type": "object",
					"additionalProperties": false,
					"deprecated": true,
					"description": "Deprecated: the docs-site scaffold moved to @rtorcato/shared-docs, which has the sibling list natively. Still accepted so existing files validate; removed in the next major.",
					"properties": {
						"siblings": {
							"type": "array",
							"description": "Sibling projects to link from the scaffolded docs site (navbar, footer, intro page). Opt-in: when absent the scaffold emits no such links.",
							"items": {
								"type": "object",
								"additionalProperties": false,
								"required": [
									"label",
									"href"
								],
								"properties": {
									"label": {
										"type": "string",
										"minLength": 1
									},
									"href": {
										"type": "string",
										"minLength": 1
									}
								}
							}
						}
					}
				}
			}
		}
	}
}
