mason-context 0.21.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/README.md +10 -5
- package/dist/mason-audit.js +159 -95
- package/dist/mason-audit.js.map +1 -1
- package/dist/mason-drift.js +90 -53
- package/dist/mason-drift.js.map +1 -1
- package/dist/mason-hook.js +97 -45
- package/dist/mason-hook.js.map +1 -1
- package/dist/mason-mcp.js +3425 -5255
- package/dist/mason-mcp.js.map +1 -1
- package/dist/mason-review.js +180 -125
- package/dist/mason-review.js.map +1 -1
- package/dist/mason-runtime.js +13 -6
- package/dist/mason-runtime.js.map +1 -1
- package/dist/mason.js +1808 -1882
- package/dist/mason.js.map +1 -1
- package/package.json +1 -4
package/dist/mason-hook.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/audit/types.ts","../src/hook/hook.ts","../src/utils/validation.ts","../src/audit/inspection.ts","../src/utils/git-read.ts","../src/utils/profile.ts","../src/utils/files.ts","../src/utils/paths.ts","../src/decisions/anchors.ts","../src/utils/storage.ts","../src/decisions/decisions.ts","../src/snapshot/snapshot.ts","../src/utils/store-lock.ts","../src/test-map.ts","../src/context/lexical.ts","../src/decisions/provenance.ts","../src/context/trust.ts","../src/decisions/drift.ts","../src/drift/drift.ts","../src/hook/cli.ts","../bin/mason-hook.ts"],"sourcesContent":["import type { decisionProvenance } from \"../decisions/provenance.js\";\n\nexport type IssueType = \"deleted-reference\" | \"new-module\" | \"stale-count\" | \"dead-command\";\n\nexport type AdvisoryType = \"deps-changed\" | \"decision-anchor-drift\" | IssueType;\n\nexport type CheckName = IssueType | AdvisoryType;\n\nexport const ALL_CHECKS: CheckName[] = [\n \"deleted-reference\",\n \"new-module\",\n \"stale-count\",\n \"dead-command\",\n \"deps-changed\",\n \"decision-anchor-drift\",\n];\n\n/**\n * \"certain\" — the claim is provably false (a tracked path is gone, a computed\n * count differs, a script is absent from a resolved manifest). \"likely\" is\n * retained for older reports. Inferred scope and speculative omissions are\n * advisory; even a certain mismatch needs semantic review before repair.\n */\nexport type Confidence = \"certain\" | \"likely\";\n\nexport interface CommitRef {\n hash: string;\n date: string;\n subject: string;\n}\n\nexport interface DocAnchor {\n /** Repo-relative doc path, e.g. \"CLAUDE.md\" or \".claude/CLAUDE.md\". */\n doc: string;\n /** 1-based line of the claim; null for doc-level issues (new-module). */\n line: number | null;\n /** The claim exactly as written, e.g. \"src/utils/logger.ts\". */\n excerpt: string | null;\n}\n\nexport interface ClaimScope {\n basis: \"repository\" | \"document\" | \"document-link\" | \"explicit\";\n directory: string;\n /** Exact repository-relative paths/manifests considered by the check. */\n candidates: string[];\n}\n\nexport type Evidence =\n | {\n kind: \"missing-path\";\n claimed: string;\n renamedTo: string | null;\n deletedInCommit: CommitRef | null;\n everTracked: boolean;\n parentDirExists: boolean;\n resolvedPath?: string;\n scope?: ClaimScope;\n }\n | {\n kind: \"unmentioned-dir\";\n dir: string;\n sourceFileCount: number;\n firstCommit: CommitRef | null;\n checkedDocs: string[];\n }\n | {\n kind: \"count-mismatch\";\n claimed: number;\n actual: number;\n unit: string;\n /** Where the actual count came from, e.g. \"package.json workspaces\". */\n countedFrom: string;\n members: string[];\n }\n | {\n kind: \"missing-script\";\n scriptName: string;\n invocation: string;\n manifestsChecked: string[];\n availableScripts: string[];\n scope?: ClaimScope;\n }\n | {\n kind: \"doc-behind-manifests\";\n docLastCommit: CommitRef;\n manifestCommits: Array<CommitRef & { files: string[] }>;\n totalCommits: number;\n matches?: import(\"./dependency-claims.js\").DependencyMatch[];\n matchingIncomplete?: boolean;\n }\n | {\n kind: \"decision-anchor\";\n provenance?: ReturnType<typeof decisionProvenance>;\n decisionId: string;\n title: string;\n changedFiles: string[];\n refreshedHash: string;\n };\n\nexport interface AuditIssue {\n type: IssueType;\n message: string;\n anchor: DocAnchor;\n confidence: Confidence;\n evidence: Evidence;\n}\n\n/**\n * Advisories require judgment and never drive the ordinary audit exit code.\n * Historical advisories require a separate review. Explicitly recheckable\n * candidates can cease to be detected without asserting semantic approval.\n */\nexport interface AuditAdvisory {\n type: AdvisoryType;\n message: string;\n anchor: DocAnchor;\n evidence: Evidence;\n resolution?: \"recheck\" | \"informational\";\n}\n\nexport interface AuditDocInfo {\n path: string;\n scope?: string;\n kind?: \"instructions\" | \"readme\" | \"guide\";\n lastCommit: CommitRef | null;\n /** Uncommitted edits present — deps-changed is suppressed for dirty docs. */\n dirty: boolean;\n lineCount: number;\n}\n\nexport interface AuditReport {\n /** Additive-only schema — this output is a CI contract. */\n version: 1;\n root: string;\n gitAvailable: boolean;\n /** Commit and exact check scope used by this run. */\n headHash?: string;\n checksRun?: CheckName[];\n docs: AuditDocInfo[];\n /** Whether .mason/decisions/ existed and was checked. */\n decisionsChecked: boolean;\n /** Drive exit code 1. */\n issues: AuditIssue[];\n /** Never drive the exit code. */\n advisories: AuditAdvisory[];\n /** Original committed evidence retained while local doc edits suppress reporting. */\n suppressedAdvisories?: AuditAdvisory[];\n skippedChecks: Array<{ check: string; reason: string; doc?: string }>;\n clean: boolean;\n excludedChecks?: Array<{ check: CheckName; doc: string }>;\n /** Additive assessment status; original advisory evidence remains intact. */\n advisoryReviews?: import(\"./advisory-review.js\").AdvisoryReviewSummary[];\n}\n\nexport interface PathClaim {\n path: string;\n line: number;\n excerpt: string;\n /** Markdown links resolve against the physical document directory. */\n relativeTo?: \"document\";\n}\n\nexport interface CountClaim {\n count: number;\n unit: string;\n line: number;\n excerpt: string;\n}\n\nexport interface CommandClaim {\n scriptName: string;\n invocation: string;\n line: number;\n excerpt: string;\n /** A literal cd/directory flag, relative to the document's assumed cwd. */\n directory?: string;\n scopeUnknown?: string;\n}\n\nexport interface DocClaims {\n paths: PathClaim[];\n counts: CountClaim[];\n commands: CommandClaim[];\n}\n","import { z } from \"zod\";\nimport { isStringArray } from \"../utils/validation.js\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport { loadDecisionStore } from \"../decisions/decisions.js\";\nimport { decisionAnchors, decisionKnowledge, DECISION_GUIDANCE } from \"../decisions/provenance.js\";\nimport { anchorMatches } from \"../utils/paths.js\";\nimport { createHash } from \"node:crypto\";\nimport type { Freshness } from \"../context/trust.js\";\nimport type { DecisionRecord } from \"../decisions/decisions.js\";\nimport { computeDecisionDrift, type DecisionDriftReport } from \"../decisions/drift.js\";\n\n/** More than a few constraints per fire is noise, not context. */\nconst MAX_INJECTED_DECISIONS = 3;\n/** Walk-up bound when locating the Mason root from an edited file. */\nconst MAX_WALK_UP = 30;\n\nconst SUPPORTED_TOOLS = new Set([\"Read\", \"Edit\", \"Write\"]);\n\nexport interface HookStdin {\n session_id?: string;\n agent_id?: string;\n cwd?: string;\n hook_event_name?: string;\n tool_name?: string;\n tool_input?: { file_path?: string };\n}\n\nconst hookStdinSchema = z.object({\n session_id: z.string().optional(),\n agent_id: z.string().optional(),\n cwd: z.string().optional(),\n hook_event_name: z.string().optional(),\n tool_name: z.string().optional(),\n tool_input: z.object({ file_path: z.string().optional() }).optional(),\n});\n\nexport interface HookEnv {\n /** Override for tests; defaults to os.tmpdir(). */\n stateDir?: string;\n}\n\nasync function exists(p: string): Promise<boolean> {\n try {\n await fs.access(p);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Locate the nearest ancestor holding a .mason/decisions store. Stops at the\n * first .git boundary — a repo without Mason must stay silent, not borrow a\n * parent directory's decisions.\n */\nasync function findMasonRoot(startDir: string): Promise<string | null> {\n let dir = startDir;\n for (let i = 0; i < MAX_WALK_UP; i++) {\n if (await exists(path.join(dir, \".mason\", \"decisions\"))) return dir;\n if (await exists(path.join(dir, \".git\"))) return null;\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n return null;\n}\n\nfunction anchorsCover(record: DecisionRecord, relPath: string): boolean {\n return decisionAnchors(record).some((anchor) => anchorMatches(anchor, relPath));\n}\n\nfunction exactAnchor(record: DecisionRecord, relPath: string): boolean {\n return decisionAnchors(record).some((a) => a.replace(/\\/+$/, \"\") === relPath);\n}\n\nfunction stateKey(input: HookStdin): string {\n const raw = `${input.session_id ?? \"nosession\"}${input.agent_id ? `-${input.agent_id}` : \"\"}`;\n return raw.replace(/[^A-Za-z0-9_-]/g, \"\").slice(0, 120) || \"nosession\";\n}\n\nasync function loadInjected(stateFile: string): Promise<Set<string>> {\n try {\n const parsed: unknown = JSON.parse(await fs.readFile(stateFile, \"utf-8\"));\n return new Set(isStringArray(parsed) ? parsed : []);\n } catch {\n return new Set();\n }\n}\n\nfunction formatContext(\n relPath: string,\n records: DecisionRecord[],\n drift: DecisionDriftReport,\n): string {\n const lines: string[] = [];\n lines.push(\n `Mason: decision knowledge relevant to ${relPath} or updated since this session saw it. This replaces earlier guidance for the same decision id. ${DECISION_GUIDANCE} Retired or superseded records are no longer active. Use Mason tools to record decision changes and source inspections; do not edit the JSON in .mason/decisions/ directly.`,\n );\n const append = (\n id: string,\n knowledge: ReturnType<typeof decisionKnowledge>,\n label: string,\n freshness: Freshness,\n ) => {\n const stale =\n freshness === \"current\"\n ? \"\"\n : freshness === \"changed\"\n ? \" [recorded against changed files – verify against current code before relying on it]\"\n : \" [freshness unknown – verify against current code before relying on it]\";\n lines.push(\n `- [${label}] [${knowledge.category}] ${knowledge.title}: ${knowledge.body} (id: ${id}; revision: ${knowledge.revision}; anchors: ${knowledge.files.join(\", \")}; owner: ${knowledge.owner ?? \"unknown\"}; sources: ${\n knowledge.sources\n .slice(0, 2)\n .map((s) => s.reference)\n .join(\", \") || \"unrecorded\"\n })${stale}`,\n );\n };\n for (const record of records) {\n const knowledge = decisionKnowledge(\n record,\n drift.freshness?.[record.id] ?? \"unknown\",\n drift.pendingProposals?.[record.id]?.freshness ?? \"unknown\",\n );\n append(\n record.id,\n knowledge,\n record.status === \"active\" ? knowledge.approval : record.status,\n knowledge.trust.freshness,\n );\n if (knowledge.pendingProposal)\n append(\n record.id,\n knowledge.pendingProposal,\n \"proposed\",\n knowledge.pendingProposal.trust.freshness,\n );\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * The push half of Mason's memory: when an agent touches a file that a\n * decision record anchors, the record is injected into the session via the\n * PostToolUse hook contract — deterministically, without relying on the\n * model deciding to ask. Returns the hook's stdout JSON, or null for\n * \"stay silent\" (no store, no match, already injected, malformed input —\n * a hook must never disrupt the session).\n */\nexport async function runHook(stdinText: string, env: HookEnv = {}): Promise<string | null> {\n let input: HookStdin;\n try {\n input = hookStdinSchema.parse(JSON.parse(stdinText));\n } catch {\n return null;\n }\n\n if (!input || typeof input !== \"object\") return null;\n if (input.tool_name && !SUPPORTED_TOOLS.has(input.tool_name)) return null;\n const filePath = input.tool_input?.file_path;\n if (!filePath || typeof filePath !== \"string\") return null;\n\n const absPath = path.isAbsolute(filePath)\n ? filePath\n : path.resolve(input.cwd ?? process.cwd(), filePath);\n const root = await findMasonRoot(path.dirname(absPath));\n if (!root) return null;\n const relPath = path.relative(root, absPath).split(path.sep).join(\"/\");\n if (relPath.startsWith(\"..\")) return null;\n\n const { records } = await loadDecisionStore(root);\n\n const stateDir = env.stateDir ?? os.tmpdir();\n const stateFile = path.join(\n stateDir,\n `mason-hook-${createHash(\"sha256\").update(root).digest(\"hex\").slice(0, 12)}-${stateKey(input)}.json`,\n );\n const injected = await loadInjected(stateFile);\n const recordKey = (record: DecisionRecord) =>\n `${record.id}:${createHash(\"sha256\").update(JSON.stringify(record)).digest(\"hex\")}`;\n const previouslyInjected = (record: DecisionRecord) =>\n [...injected].some((key) => key === record.id || key.startsWith(`${record.id}:`));\n // Re-inject revised/accepted records and withdraw previously injected records\n // that were retired during this session. Untouched archived records stay dark.\n // Also update previously seen records after their anchors move. Otherwise an\n // acceptance or retirement could leave obsolete guidance in the session.\n const fresh = records.filter(\n (r) =>\n !injected.has(recordKey(r)) &&\n (previouslyInjected(r) || (r.status === \"active\" && anchorsCover(r, relPath))),\n );\n if (fresh.length === 0) return null;\n\n // Exact-file anchors outrank directory-prefix ones; newest knowledge wins ties.\n fresh.sort((a, b) => {\n const withdrawn = Number(b.status !== \"active\") - Number(a.status !== \"active\");\n if (withdrawn) return withdrawn;\n const exactDiff = Number(exactAnchor(b, relPath)) - Number(exactAnchor(a, relPath));\n if (exactDiff !== 0) return exactDiff;\n return b.updatedAt.localeCompare(a.updatedAt);\n });\n const selected = fresh.slice(0, MAX_INJECTED_DECISIONS);\n\n const drift = await computeDecisionDrift(root, selected);\n\n for (const record of selected) injected.add(recordKey(record));\n try {\n await fs.writeFile(stateFile, JSON.stringify([...injected]), \"utf-8\");\n } catch {\n // Dedupe state is a convenience; losing it must not block injection.\n }\n\n return JSON.stringify({\n hookSpecificOutput: {\n hookEventName: \"PostToolUse\",\n additionalContext: formatContext(relPath, selected, drift),\n },\n });\n}\n","/** Narrow external objects without allowing arrays or null to masquerade as records. */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nexport function isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item: unknown) => typeof item === \"string\");\n}\n\nexport function hasErrorCode(error: unknown, code: string): boolean {\n return isRecord(error) && error.code === code;\n}\n","import fg from \"fast-glob\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport path from \"node:path\";\nimport { execGit, type GitReadOptions } from \"../utils/git-read.js\";\nimport { loadProjectConfig, SOURCE_EXTENSIONS } from \"../utils/files.js\";\n\ninterface Inspection {\n root: string;\n reads: Map<string, Promise<unknown>>;\n}\nconst inspections = new AsyncLocalStorage<Inspection>();\n\n/** Always start fresh. In particular, final validation must not reuse initial reads. */\nexport function withRepositoryInspection<T>(root: string, run: () => Promise<T>): Promise<T> {\n return inspections.run({ root: path.resolve(root), reads: new Map() }, run);\n}\n\n/** Read observations are shared only inside an explicit inspection, never across hooks. */\nexport function inspectionRead<T>(root: string, key: string, read: () => Promise<T>): Promise<T> {\n const inspection = inspections.getStore();\n if (!inspection || inspection.root !== path.resolve(root)) return read();\n if (!inspection.reads.has(key)) {\n const pending = read();\n inspection.reads.set(key, pending);\n // A failed query must be retried, not turned into a reusable unavailable result.\n void pending.catch(() => {\n if (inspection.reads.get(key) === pending) inspection.reads.delete(key);\n });\n }\n return inspection.reads.get(key)! as Promise<T>;\n}\n\n/** Tool writes invalidate their cached observations within the active inspection. */\nexport function invalidateInspectionReads(root: string, prefix: string): void {\n const inspection = inspections.getStore();\n if (!inspection || inspection.root !== path.resolve(root)) return;\n for (const key of inspection.reads.keys())\n if (key.startsWith(prefix)) inspection.reads.delete(key);\n}\n\n/** Exact arguments and limits preserve each history query's semantics. */\nexport function inspectionGit(args: string[], options: GitReadOptions) {\n return inspectionRead(options.cwd, \"git:\" + JSON.stringify([args, options]), () =>\n execGit(args, options),\n );\n}\n\nconst sourceSpecs = SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`);\nconst docSpecs = [\n \":(icase,glob)**/readme.md\",\n \":(icase,glob)**/agents.md\",\n \":(icase,glob)**/claude.md\",\n];\nconst sourceSuffixes = new Set(SOURCE_EXTENSIONS.map((extension) => \".\" + extension));\n\n/** Git still prunes ignored trees; unrelated generated files are never inventoried. */\nexport async function auditGitPaths(\n root: string,\n kind: \"documents\" | \"sources\",\n): Promise<string[]> {\n const custom = (await loadProjectConfig(root)).audit?.include ?? [];\n const shared = inspections.getStore()?.root === path.resolve(root) && !custom.length;\n const expanded = fg.generateTasks(custom).flatMap((task) => task.positive);\n if (expanded.length > 1000)\n throw new Error(\"Audit include patterns expand to more than 1,000 globs\");\n const documentSpecs = [...docSpecs, ...expanded.map((pattern) => `:(glob)${pattern}`)];\n const specs = shared\n ? [...documentSpecs, ...sourceSpecs]\n : kind === \"documents\"\n ? documentSpecs\n : sourceSpecs;\n const limit = kind === \"documents\" ? 16 * 1024 * 1024 : 32 * 1024 * 1024;\n const { stdout } = await inspectionGit(\n [\"ls-files\", \"-z\", \"--cached\", \"--others\", \"--exclude-standard\", \"--\", ...specs],\n { cwd: root, maxBuffer: shared ? 48 * 1024 * 1024 : limit, timeout: 10000 },\n );\n const paths = [...new Set(stdout.split(\"\\0\").filter(Boolean))].filter((file) =>\n kind === \"documents\"\n ? custom.length > 0 || /^(readme|agents|claude)\\.md$/i.test(path.posix.basename(file))\n : // Git's *.ts also matches a file named .ts, unlike path.extname().\n sourceSuffixes.has(file.slice(file.lastIndexOf(\".\"))),\n );\n if (Buffer.byteLength(paths.join(\"\\0\")) > limit)\n throw new Error(\"Git \" + kind + \" inventory exceeds its bounded read limit.\");\n return paths.sort();\n}\n","import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { profilePhase } from \"./profile.js\";\n\nconst execute = promisify(execFile);\nconst commands = new Set([\n \"rev-parse\",\n \"symbolic-ref\",\n \"for-each-ref\",\n \"ls-files\",\n \"check-ignore\",\n \"status\",\n \"log\",\n \"diff\",\n \"ls-tree\",\n \"show\",\n \"merge-base\",\n]);\nexport interface GitReadOptions {\n cwd: string;\n maxBuffer?: number;\n timeout?: number;\n input?: string;\n}\n\n/** Fixed command labels only: profiling never includes arguments or repository paths. */\nexport function execGit(args: string[], options: GitReadOptions) {\n const command = args.find((arg) => !arg.startsWith(\"-\")) ?? \"other\";\n return profilePhase(\"git.\" + (commands.has(command) ? command : \"other\"), async () => {\n const { input, ...processOptions } = options;\n const operation = execute(\"git\", args, processOptions);\n if (input !== undefined) {\n operation.child.stdin!.on(\"error\", () => {\n /* The child exit reports a failed query. */\n });\n operation.child.stdin!.end(input);\n }\n return operation;\n });\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\";\n\ntype Timing = { calls: number; totalMs: number };\nconst profiles = new AsyncLocalStorage<Record<string, Timing>>();\n\n/** Opt-in, invocation-local timings. Labels are fixed in source; no repository data. */\nexport async function profilePhase<T>(label: string, run: () => Promise<T>): Promise<T> {\n const phases = profiles.getStore();\n if (!phases) return run();\n const start = performance.now();\n try {\n return await run();\n } finally {\n const timing = (phases[label] ??= { calls: 0, totalMs: 0 });\n timing.calls++;\n timing.totalMs += performance.now() - start;\n }\n}\n\nexport async function withProfile<T>(\n run: () => Promise<T>,\n emit: (json: string) => void,\n): Promise<T> {\n const phases: Record<string, Timing> = {};\n const start = performance.now();\n return profiles.run(phases, async () => {\n try {\n return await run();\n } finally {\n emit(\n JSON.stringify({\n kind: \"mason-profile\",\n version: 1,\n totalMs: performance.now() - start,\n phases,\n }),\n );\n }\n });\n}\n","import { isRecord, isStringArray } from \"./validation.js\";\nimport { execGit } from \"./git-read.js\";\nimport fs from \"node:fs/promises\";\nimport { constants } from \"node:fs\";\nimport path from \"node:path\";\nimport fg from \"fast-glob\";\nimport { isWithinRoot, normalizeRepoPath } from \"./paths.js\";\n\nexport const SOURCE_EXTENSIONS = [\n \"ts\",\n \"tsx\",\n \"js\",\n \"jsx\",\n \"mts\",\n \"cts\",\n \"mjs\",\n \"cjs\",\n \"vue\",\n \"svelte\",\n \"kt\",\n \"kts\",\n \"java\",\n \"py\",\n \"go\",\n \"rs\",\n \"swift\",\n \"rb\",\n \"cs\",\n \"cpp\",\n \"c\",\n \"h\",\n \"hpp\",\n \"dart\",\n \"php\",\n];\nexport const SOURCE_GLOB = `**/*.{${SOURCE_EXTENSIONS.join(\",\")}}`;\nexport const SOURCE_IGNORE = [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/build/**\",\n \"**/.gradle/**\",\n \"**/target/**\",\n \"**/.git/**\",\n \"**/.mason/**\",\n \"**/vendor/**\",\n \"**/__pycache__/**\",\n \"**/venv/**\",\n \"**/.venv/**\",\n \"**/*.min.*\",\n \"**/*.map\",\n \"**/*.lock\",\n \"**/generated/**\",\n \"**/*.generated.*\",\n \"**/R.java\",\n \"**/BuildConfig.java\",\n \"**/package-lock.json\",\n \"**/yarn.lock\",\n \"**/pnpm-lock.yaml\",\n];\nexport const MAX_SOURCE_BYTES = 1024 * 1024;\nexport interface ProjectConfig {\n patterns?: string[];\n alwaysInclude?: string[];\n ignore?: string[];\n audit?: {\n include?: string[];\n exclude?: Partial<Record<import(\"../audit/types.js\").CheckName, string[]>>;\n };\n}\nexport interface SourceFile {\n path: string;\n content: string;\n totalLines: number;\n}\n\nexport function isSensitiveFile(file: string): boolean {\n return file\n .split(/[\\\\/]/)\n .some((part) =>\n /^(?:\\.env(?:\\..*)?|id_rsa.*|id_ed25519.*)$|\\.(?:pem|key|p12|pfx|jks|keystore)$|credentials\\.|secret|^local\\.properties$/i.test(\n part,\n ),\n );\n}\n\n/** Bound reads even if a file grows after stat. Only read regular files. */\nexport async function readBoundedFile(file: string, maxBytes: number): Promise<string | null> {\n // Do not block on a FIFO or follow a symlink substituted after resolution.\n const handle = await fs.open(\n file,\n constants.O_RDONLY | constants.O_NONBLOCK | constants.O_NOFOLLOW,\n );\n try {\n const stat = await handle.stat();\n if (!stat.isFile() || stat.size > maxBytes) return null;\n const buffer = Buffer.alloc(Math.min(maxBytes + 1, stat.size + 1));\n let bytes = 0;\n while (bytes < buffer.length) {\n const result = await handle.read(buffer, bytes, buffer.length - bytes, null);\n if (result.bytesRead === 0) break;\n bytes += result.bytesRead;\n }\n return bytes === buffer.length ? null : buffer.subarray(0, bytes).toString(\"utf8\");\n } finally {\n await handle.close();\n }\n}\n\nexport async function loadProjectConfig(root: string): Promise<ProjectConfig> {\n try {\n const canonicalRoot = await fs.realpath(root);\n const configPath = await fs.realpath(path.join(root, \".mason/config.json\"));\n if (!isWithinRoot(canonicalRoot, configPath))\n throw new Error(\"Project configuration resolves outside the repository\");\n const raw = await readBoundedFile(configPath, 64 * 1024);\n if (raw === null)\n throw new Error(\"Project configuration is not a regular file or exceeds 64 KiB\");\n const value: unknown = JSON.parse(raw);\n if (!isRecord(value)) throw new Error(\"Expected a configuration object\");\n const config: ProjectConfig = {};\n for (const key of [\"patterns\", \"alwaysInclude\", \"ignore\"] as const) {\n if (value[key] === undefined) continue;\n if (!isStringArray(value[key])) {\n throw new Error(`Configuration ${key} must be an array of strings`);\n }\n config[key] = value[key];\n }\n if (value.audit !== undefined) {\n const audit = value.audit;\n if (!isRecord(audit)) throw new Error(\"Configuration audit must be an object\");\n const patterns = (raw: unknown): string[] => {\n if (\n !isStringArray(raw) ||\n raw.length > 100 ||\n !raw.every(\n (s) =>\n typeof s === \"string\" &&\n normalizeRepoPath(s) === s &&\n !s.startsWith(\"!\") &&\n !/[\\x00-\\x1f]/.test(s),\n )\n ) {\n throw new Error(\n \"Audit patterns must be at most 100 repository-relative globs without traversal or negation\",\n );\n }\n return raw;\n };\n config.audit = {};\n if (audit.include !== undefined) config.audit.include = patterns(audit.include);\n if (audit.exclude !== undefined) {\n if (!isRecord(audit.exclude))\n throw new Error(\"audit.exclude must map check names to patterns\");\n const { ALL_CHECKS } = await import(\"../audit/types.js\");\n config.audit.exclude = {};\n for (const [check, globs] of Object.entries(audit.exclude)) {\n if (!ALL_CHECKS.includes(check as (typeof ALL_CHECKS)[number]))\n throw new Error(\"Unknown audit check: \" + check);\n config.audit.exclude[check as (typeof ALL_CHECKS)[number]] = patterns(globs);\n }\n }\n }\n return config;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return {};\n throw new Error(\n `Cannot apply project file policy: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n}\n\n/** Scoped to one operation, so a later tool call sees newly edited files/ignores. */\nexport async function createFileAccess(rootDir: string) {\n const root = path.resolve(rootDir);\n const canonicalRoot = await fs.realpath(root).catch(() => root);\n const config = await loadProjectConfig(root);\n const ignore = [...SOURCE_IGNORE, ...(config.ignore ?? [])];\n let gitFiles: Set<string> | null = null;\n try {\n const { stdout } = await execGit(\n [\"ls-files\", \"-z\", \"--cached\", \"--others\", \"--exclude-standard\"],\n { cwd: root, maxBuffer: 50 * 1024 * 1024 },\n );\n gitFiles = new Set(stdout.split(\"\\0\").filter(Boolean));\n } catch {\n // File-system projects are supported. Fail closed if this IS a Git repo.\n let inGit = false;\n try {\n await execGit([\"rev-parse\", \"--git-dir\"], { cwd: root });\n inGit = true;\n } catch {\n /* no Git */\n }\n if (inGit) throw new Error(\"Cannot enumerate Git files safely\");\n }\n\n async function resolve(file: string): Promise<string | null> {\n const relative = normalizeRepoPath(file);\n if (!relative || isSensitiveFile(relative) || (gitFiles && !gitFiles.has(relative)))\n return null;\n const candidate = path.join(root, relative);\n try {\n const real = await fs.realpath(candidate);\n if (!isWithinRoot(canonicalRoot, real) || isSensitiveFile(path.relative(canonicalRoot, real)))\n return null;\n const stat = await fs.stat(real);\n if (!stat.isFile() || stat.size > MAX_SOURCE_BYTES) return null;\n // A symlink must not bypass the target's ignore policy either.\n if (gitFiles && !gitFiles.has(path.relative(canonicalRoot, real).split(path.sep).join(\"/\")))\n return null;\n return real;\n } catch {\n return null;\n }\n }\n\n async function list(\n patterns: string | string[] = SOURCE_GLOB,\n options: { deep?: number; dot?: boolean } = {},\n ): Promise<string[]> {\n const found = await fg(patterns, { cwd: root, ignore, followSymbolicLinks: false, ...options });\n const safe = await Promise.all(found.map(async (f) => ((await resolve(f)) ? f : null)));\n return safe.filter((f): f is string => f !== null).sort();\n }\n\n async function read(file: string): Promise<SourceFile | null> {\n const relative = normalizeRepoPath(file);\n if (!relative) return null;\n const real = await resolve(relative);\n if (!real) return null;\n // Apply the same glob exclusions to explicit reads and symlink targets.\n for (const rel of new Set([\n relative,\n path.relative(canonicalRoot, real).split(path.sep).join(\"/\"),\n ])) {\n if (!(await fg(fg.escapePath(rel), { cwd: root, ignore, dot: true })).length) return null;\n }\n try {\n const content = await readBoundedFile(real, MAX_SOURCE_BYTES);\n return content === null\n ? null\n : { path: relative, content, totalLines: content.split(\"\\n\").length };\n } catch {\n return null;\n }\n }\n return { root, config, list, read, inventory: () => (gitFiles ? [...gitFiles].sort() : null) };\n}\n","import path from \"node:path\";\n\n/** One canonical representation for stored paths and decision anchors. */\nexport function normalizeRepoPath(value: string): string | null {\n const slash = value.replace(/\\\\/g, \"/\");\n if (!slash || slash.includes(\"\\0\") || path.posix.isAbsolute(slash) || /^[A-Za-z]:/.test(slash))\n return null;\n if (slash.split(\"/\").includes(\"..\")) return null;\n const normalized = path.posix.normalize(slash).replace(/\\/$/, \"\");\n return normalized === \".\" ? null : normalized;\n}\n\nexport function sanitizeRepoPaths(files: string[]): string[] {\n return [...new Set(files.map(normalizeRepoPath).filter((p): p is string => p !== null))];\n}\n\nexport function isWithinRoot(root: string, candidate: string): boolean {\n const relative = path.relative(root, candidate);\n return relative !== \"..\" && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);\n}\n\nexport function anchorMatches(anchor: string, file: string): boolean {\n const a = normalizeRepoPath(anchor);\n const f = normalizeRepoPath(file);\n return a !== null && f !== null && (a === f || f.startsWith(`${a}/`));\n}\n\nexport function matchingPaths(anchors: string[], files: Iterable<string>): string[] {\n return [...new Set(files)].filter((file) =>\n anchors.some((anchor) => anchorMatches(anchor, file)),\n );\n}\n\n/** Match an inventory to every scope in one pass through its path components. */\nexport function indexMatchingPaths(\n scopes: readonly (readonly string[])[],\n files: Iterable<string>,\n): string[][] {\n interface Node {\n children: Map<string, Node>;\n scopes: Set<number>;\n }\n const node = (): Node => ({ children: new Map(), scopes: new Set() });\n const root = node();\n const matches: string[][] = scopes.map(() => []);\n scopes.forEach((anchors, index) => {\n for (const anchor of anchors) {\n const normalized = normalizeRepoPath(anchor);\n if (!normalized) continue;\n let current = root;\n for (const part of normalized.split(\"/\")) {\n if (!current.children.has(part)) current.children.set(part, node());\n current = current.children.get(part)!;\n }\n current.scopes.add(index);\n }\n });\n for (const file of new Set(files)) {\n const normalized = normalizeRepoPath(file);\n if (!normalized) continue;\n const selected = new Set<number>();\n let current: Node | undefined = root;\n for (const part of normalized.split(\"/\")) {\n current = current.children.get(part);\n if (!current) break;\n for (const index of current.scopes) selected.add(index);\n }\n for (const index of selected) matches[index].push(file);\n }\n return matches;\n}\n","import { createHash } from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport { constants } from \"node:fs\";\nimport { z } from \"zod\";\nimport fg from \"fast-glob\";\nimport { inspectionGit, inspectionRead } from \"../audit/inspection.js\";\nimport { createFileAccess, MAX_SOURCE_BYTES } from \"../utils/files.js\";\nimport { indexMatchingPaths, matchingPaths, normalizeRepoPath } from \"../utils/paths.js\";\nimport { storePath } from \"../utils/storage.js\";\n\nconst repoPath = z.string().refine((p) => normalizeRepoPath(p) === p);\nexport const anchorCaptureSchema = z\n .object({\n anchors: z.array(repoPath),\n files: z.record(repoPath, z.string().regex(/^[a-f0-9]{64}$/)),\n complete: z.boolean(),\n })\n .refine(\n (capture) =>\n JSON.stringify(capture.anchors) === JSON.stringify([...new Set(capture.anchors)].sort()) &&\n matchingPaths(capture.anchors, Object.keys(capture.files)).length ===\n Object.keys(capture.files).length,\n \"Capture paths must belong to its normalized, sorted anchor scope\",\n );\nexport type AnchorCapture = z.infer<typeof anchorCaptureSchema>;\n\n/** Content evidence, not approval. Unreadable/excluded scopes never become current.\n * Batch independent scopes and share observations only inside one repository inspection.\n * Final validation creates a fresh inspection; standalone callers always read again.\n */\nexport async function captureAnchorScopes(\n root: string,\n scopes: string[][],\n): Promise<AnchorCapture[]> {\n const union = [...new Set(scopes.flat())].sort();\n let context: ReturnType<typeof prepareContext> | undefined;\n const prepareContext = async () => {\n const access = await inspectionRead(root, \"decision-anchor-access\", () =>\n createFileAccess(root),\n );\n const inventory = access.inventory();\n if (!inventory) throw new Error(\"Git anchor inventory unavailable\");\n const ignored = await inspectionGit(\n [\n \"ls-files\",\n \"-z\",\n \"--others\",\n \"--ignored\",\n \"--exclude-standard\",\n \"--\",\n ...union.map((anchor) => `:(literal)${anchor}`),\n ],\n { cwd: root, maxBuffer: 8 * 1024 * 1024 },\n );\n const files = [\n ...new Set([...inventory, ...ignored.stdout.split(\"\\0\").filter(Boolean)]),\n ].sort();\n // Apply file policy in one glob walk rather than one walk per anchor file.\n // storePath still rejects symlinked parents at each bounded byte read.\n const patterns = await Promise.all(\n union.map(async (anchor) => {\n const literal = fg.escapePath(anchor);\n try {\n return (await fs.lstat(await storePath(root, anchor))).isDirectory()\n ? `${literal}/**/*`\n : literal;\n } catch {\n return literal;\n }\n }),\n );\n const eligible = new Set(await access.list(patterns, { dot: true }));\n const indexed = indexMatchingPaths(scopes, files);\n const filesByScope = new Map(\n scopes.map((scope, index) => [JSON.stringify([...new Set(scope)].sort()), indexed[index]]),\n );\n return { filesByScope, eligible };\n };\n return Promise.all(\n scopes.map((scope) => {\n const anchors = [...new Set(scope)].sort();\n return inspectionRead(\n root,\n \"decision-anchor-capture:\" + JSON.stringify(anchors),\n async () => {\n const capture: AnchorCapture = { anchors, files: {}, complete: true };\n if (!anchors.length) return capture;\n try {\n const { eligible, filesByScope } = await (context ??= prepareContext());\n const files = filesByScope.get(JSON.stringify(anchors))!;\n if (files.length > 2000) return { ...capture, complete: false };\n for (const file of files) {\n const evidence = await inspectionRead(\n root,\n \"decision-anchor-file:\" + file,\n async () => {\n try {\n const absolute = await storePath(root, file);\n await fs.lstat(absolute); // Missing indexed files are deletions, not unreadable evidence.\n if (!eligible.has(file)) return { complete: false };\n const handle = await fs.open(\n absolute,\n constants.O_RDONLY | constants.O_NONBLOCK | constants.O_NOFOLLOW,\n );\n try {\n const stat = await handle.stat();\n if (!stat.isFile() || stat.size > MAX_SOURCE_BYTES)\n return { complete: false };\n const bytes = Buffer.alloc(Math.min(stat.size + 1, MAX_SOURCE_BYTES + 1));\n let size = 0;\n while (size < bytes.length) {\n const read = await handle.read(bytes, size, bytes.length - size, null);\n if (!read.bytesRead) break;\n size += read.bytesRead;\n }\n if (size === bytes.length) return { complete: false }; // Grew during capture.\n return {\n complete: true,\n hash: createHash(\"sha256\").update(bytes.subarray(0, size)).digest(\"hex\"),\n };\n } finally {\n await handle.close();\n }\n } catch (error) {\n return { complete: (error as NodeJS.ErrnoException).code === \"ENOENT\" };\n }\n },\n );\n if (!evidence.complete) capture.complete = false;\n if (evidence.hash) capture.files[file] = evidence.hash;\n }\n } catch {\n capture.complete = false;\n }\n return capture;\n },\n );\n }),\n );\n}\n\nexport async function captureAnchors(root: string, anchors: string[]): Promise<AnchorCapture> {\n return (await captureAnchorScopes(root, [anchors]))[0];\n}\n\nexport function changedCaptureFiles(before: AnchorCapture, after: AnchorCapture): string[] {\n return [...new Set([...Object.keys(before.files), ...Object.keys(after.files)])]\n .filter((file) => before.files[file] !== after.files[file])\n .sort();\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\nimport { normalizeRepoPath } from \"./paths.js\";\nimport { readBoundedFile } from \"./files.js\";\n\nexport interface StoreDiagnostic {\n path: string;\n message: string;\n}\n\n/** Metadata paths may not contain symlinks, including their parent directories. */\nexport async function storePath(\n root: string,\n relative: string,\n createParents = false,\n): Promise<string> {\n const normalized = normalizeRepoPath(relative);\n if (!normalized) throw new Error(`Invalid store path: ${relative}`);\n let current = await fs.realpath(root);\n const parts = normalized.split(\"/\");\n for (let i = 0; i < parts.length; i++) {\n current = path.join(current, parts[i]);\n let stat;\n try {\n stat = await fs.lstat(current);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n if (createParents && i < parts.length - 1) {\n try {\n await fs.mkdir(current);\n } catch (mkdirError) {\n if ((mkdirError as NodeJS.ErrnoException).code !== \"EEXIST\") throw mkdirError;\n }\n stat = await fs.lstat(current);\n }\n }\n if (stat?.isSymbolicLink()) throw new Error(`Symlink in store path: ${relative}`);\n }\n return current;\n}\n\nexport async function readStoreJson(root: string, relative: string): Promise<unknown | null> {\n try {\n const file = await storePath(root, relative);\n const raw = await readBoundedFile(file, 10 * 1024 * 1024);\n if (raw === null) throw new Error(\"file is not regular or exceeds 10 MiB\");\n const parsed: unknown = JSON.parse(raw);\n if (parsed === null) throw new Error(\"expected a JSON object, received null\");\n return parsed;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw new Error(\n `Invalid Mason store ${relative}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n}\n\nexport async function writeStoreJson(\n root: string,\n relative: string,\n value: unknown,\n): Promise<void> {\n const payload = JSON.stringify(value, null, 2) + \"\\n\";\n if (Buffer.byteLength(payload) > 10 * 1024 * 1024) {\n throw new Error(`Mason store ${relative} exceeds 10 MiB`);\n }\n const file = await storePath(root, relative, true);\n const temporary = path.join(path.dirname(file), `.${path.basename(file)}.${randomUUID()}.tmp`);\n try {\n const handle = await fs.open(temporary, \"wx\", 0o600);\n try {\n await handle.writeFile(payload, \"utf8\");\n await handle.sync();\n } finally {\n await handle.close();\n }\n await fs.rename(temporary, file);\n } finally {\n await fs.rm(temporary, { force: true });\n }\n}\n","import { inspectionRead, invalidateInspectionReads } from \"../audit/inspection.js\";\nimport { readBoundedFile } from \"../utils/files.js\";\nimport { captureAnchors } from \"./anchors.js\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { createHash } from \"node:crypto\";\nimport { z } from \"zod\";\nimport { writeStoreJson, storePath, type StoreDiagnostic } from \"../utils/storage.js\";\nimport { sanitizeRepoPaths } from \"../utils/paths.js\";\nimport { getCurrentGitHash } from \"../snapshot/snapshot.js\";\nimport { jaccard, tokenSet } from \"../context/lexical.js\";\nimport {\n attributionSchema,\n decisionSchema,\n decisionContent,\n decisionApproval,\n effectiveDecision,\n importLegacy,\n type DecisionSource,\n type DecisionRecord,\n type ReviewedDecisionRecord,\n} from \"./provenance.js\";\nexport type { DecisionRecord } from \"./provenance.js\";\n\nexport type DecisionCategory = \"decision\" | \"gotcha\" | \"deprecation\" | \"convention\";\nexport type DecisionStatus = \"active\" | \"superseded\" | \"retired\";\n\nexport const TITLE_MAX_CHARS = 80;\nexport const BODY_RECOMMENDED_CHARS = 1500;\nexport const BODY_MAX_CHARS = 2500;\nexport const MAX_ACTIVE_DECISIONS = 150;\n\n// Share validation with MCP so rejected tool calls include the actual length.\nconst boundedText = (field: string, max: number, hint: string) =>\n z\n .string({\n errorMap: (issue, ctx) => ({\n message:\n issue.code === \"too_big\" && typeof ctx.data === \"string\"\n ? `${field} has ${ctx.data.length} characters; maximum is ${max} (${ctx.data.length - max} over). ${hint}`\n : ctx.defaultError,\n }),\n })\n .trim()\n .min(1, `${field} must be non-empty`)\n .max(max);\n\nexport const decisionTitleSchema = boundedText(\n \"title\",\n TITLE_MAX_CHARS,\n \"Tighten it to a specific headline.\",\n);\nexport const decisionBodySchema = boundedText(\n \"body\",\n BODY_MAX_CHARS,\n `Aim for ${BODY_RECOMMENDED_CHARS} characters or fewer. Preserve the rule, reason, and exceptions; put supporting references in sources.`,\n);\n\nconst DUPLICATE_JACCARD = 0.5;\nconst DUPLICATE_JACCARD_WITH_SHARED_FILE = 0.35;\n\n/** Raw bytes also feed automation fingerprints, including malformed records. */\nexport async function readDecisionInputs(\n rootDir: string,\n): Promise<{ inputs: Array<[string, string | null]>; diagnostics: StoreDiagnostic[] }> {\n return structuredClone(\n await inspectionRead(rootDir, \"decision-store:inputs\", async () => {\n const inputs: Array<[string, string | null]> = [];\n const diagnostics: StoreDiagnostic[] = [];\n let entries: string[];\n try {\n entries = await fs.readdir(await storePath(rootDir, \".mason/decisions\"));\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\")\n diagnostics.push({ path: \".mason/decisions\", message: String(error) });\n return { inputs, diagnostics };\n }\n for (const entry of entries.sort()) {\n if (!entry.endsWith(\".json\")) continue;\n const relative = `.mason/decisions/${entry}`;\n try {\n const raw = await readBoundedFile(await storePath(rootDir, relative), 10 * 1024 * 1024);\n if (raw === null) throw new Error(\"file is not regular or exceeds 10 MiB\");\n inputs.push([relative, raw]);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") inputs.push([relative, null]);\n else\n diagnostics.push({\n path: relative,\n message: error instanceof Error ? error.message : String(error),\n });\n }\n }\n return { inputs, diagnostics };\n }),\n );\n}\n\nexport async function loadDecisionStore(\n rootDir: string,\n): Promise<{ records: DecisionRecord[]; diagnostics: StoreDiagnostic[] }> {\n // Callers may edit their returned records while preparing a write. Never expose\n // mutable cached records, and never share this cache with final validation.\n return structuredClone(\n await inspectionRead(rootDir, \"decision-store:parsed\", async () => {\n const raw = await readDecisionInputs(rootDir);\n const records: DecisionRecord[] = [];\n const diagnostics = [...raw.diagnostics];\n for (const [relative, content] of raw.inputs) {\n try {\n const record = decisionSchema.parse(content === null ? null : JSON.parse(content));\n if (relative !== `.mason/decisions/${record.id}.json`)\n throw new Error(\"Record id does not match its filename\");\n records.push(record);\n } catch (error) {\n diagnostics.push({\n path: relative,\n message: error instanceof Error ? error.message : String(error),\n });\n }\n }\n return { records, diagnostics };\n }),\n );\n}\n\nexport async function loadDecisions(rootDir: string): Promise<DecisionRecord[]> {\n return (await loadDecisionStore(rootDir)).records;\n}\n\nexport async function saveDecisionRecord(rootDir: string, record: DecisionRecord): Promise<void> {\n const validated = decisionSchema.parse(record);\n await writeStoreJson(rootDir, `.mason/decisions/${validated.id}.json`, validated);\n invalidateInspectionReads(rootDir, \"decision-store:\");\n}\n\n/**\n * Deterministic, human-readable id: base slug ≤60 chars, ending at a word\n * boundary when possible. A single longer word is cut to fit.\n * A slug collision with a DIFFERENT record appends a 6-hex content suffix.\n */\nexport function decisionIdFor(title: string, body: string, existingIds: Set<string>): string {\n const fullSlug = title\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n let slug = fullSlug.slice(0, 60);\n if (fullSlug.length > 60 && fullSlug[60] !== \"-\") {\n const boundary = slug.lastIndexOf(\"-\");\n if (boundary > 0) slug = slug.slice(0, boundary);\n }\n slug = slug.replace(/-+$/, \"\") || \"decision\";\n if (!existingIds.has(slug)) return slug;\n const suffix = createHash(\"sha1\")\n .update(title + body)\n .digest(\"hex\")\n .slice(0, 6);\n return `${slug}-${suffix}`;\n}\n\nexport function findNearDuplicate(\n candidate: { title: string; body: string; files: string[] },\n existing: DecisionRecord[],\n): { record: DecisionRecord; similarity: number } | null {\n const candidateTokens = tokenSet(`${candidate.title} ${candidate.body}`);\n const candidateFiles = new Set(candidate.files);\n let best: { record: DecisionRecord; similarity: number } | null = null;\n\n for (const record of existing) {\n if (record.status !== \"active\") continue;\n const similarity = jaccard(candidateTokens, tokenSet(`${record.title} ${record.body}`));\n const sharesFile = record.files.some((f) => candidateFiles.has(f));\n const threshold = sharesFile ? DUPLICATE_JACCARD_WITH_SHARED_FILE : DUPLICATE_JACCARD;\n if (similarity >= threshold && (!best || similarity > best.similarity)) {\n best = { record, similarity };\n }\n }\n return best;\n}\n\nexport interface UpsertDecisionInput {\n title: string;\n body: string;\n category: DecisionCategory;\n files?: string[];\n owner?: string | null;\n sources?: DecisionSource[];\n /** Only supply a known identity; never infer authorship from Git configuration. */\n actor?: string;\n /** Existing id to revise. Unchanged content is a no-op; use review_decision to reaffirm. */\n id?: string;\n /** Id of a decision this one replaces; the old record is kept, marked superseded. */\n supersedes?: string;\n /** Save even when a near-duplicate was detected. */\n force?: boolean;\n}\n\nexport type UpsertDecisionResult =\n | {\n status: \"created\" | \"updated\" | \"unchanged\" | \"superseded_and_created\";\n id: string;\n totalActive: number;\n warnings: string[];\n approval?: \"unreviewed\" | \"proposed\" | \"accepted\";\n hint?: string;\n pruneCandidates?: string[];\n }\n | { status: \"duplicate_suspected\"; existing: DecisionRecord; hint: string }\n | { status: \"error\"; error: string };\n\n/** Serialize tool writes so prepared reviews cannot overwrite another decision edit. */\nexport async function withDecisionWrite<T>(\n root: string,\n operation: () => Promise<T>,\n): Promise<T | { status: \"error\"; error: string }> {\n const lockPath = await storePath(root, \".mason/decisions/.write-lock\", true);\n let lock;\n try {\n lock = await fs.open(lockPath, \"wx\", 0o600);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"EEXIST\")\n return {\n status: \"error\",\n error:\n \"Decision store is locked by another write. Retry after it finishes; an abandoned .mason/decisions/.write-lock must be removed only after confirming no writer is running.\",\n };\n throw error;\n }\n try {\n return await operation();\n } finally {\n await lock.close();\n await fs.unlink(lockPath);\n }\n}\n\nexport async function upsertDecision(\n rootDir: string,\n input: UpsertDecisionInput,\n): Promise<UpsertDecisionResult> {\n const parsedTitle = decisionTitleSchema.safeParse(input.title),\n parsedBody = decisionBodySchema.safeParse(input.body);\n if (!parsedTitle.success) return { status: \"error\", error: parsedTitle.error.issues[0].message };\n if (!parsedBody.success) return { status: \"error\", error: parsedBody.error.issues[0].message };\n const title = parsedTitle.data,\n body = parsedBody.data;\n const attribution = attributionSchema.safeParse(input);\n if (!attribution.success) return { status: \"error\", error: attribution.error.message };\n if (input.id && input.supersedes)\n return {\n status: \"error\",\n error: \"Use either id to revise or supersedes to replace a record, not both.\",\n };\n return withDecisionWrite(rootDir, async () => {\n const store = await loadDecisionStore(rootDir);\n if (store.diagnostics.length)\n return {\n status: \"error\",\n error:\n \"Repair malformed decision records before saving: \" +\n store.diagnostics.map((d) => d.path).join(\", \"),\n };\n const existing = store.records,\n byId = new Map(existing.map((r) => [r.id, r]));\n const now = new Date().toISOString(),\n head = await getCurrentGitHash(rootDir);\n const warnings: string[] = [];\n if (body.length > BODY_RECOMMENDED_CHARS)\n warnings.push(\n `body has ${body.length} characters, above the recommended ${BODY_RECOMMENDED_CHARS}; matching context and hooks include it in full. Keep the rule, reason, and exceptions concise; use sources for supporting references.`,\n );\n const files = sanitizeRepoPaths(input.files ?? []);\n if (input.files && files.length < input.files.length)\n warnings.push(\"some anchor paths were outside the repo or duplicated and were dropped\");\n for (const file of files) {\n try {\n await fs.access(path.join(rootDir, file));\n } catch {\n warnings.push(`anchor file does not exist on disk: ${file}`);\n }\n }\n const hint =\n \"Saved locally for review and commit. Proposals are not accepted constraints; an existing accepted revision remains operative while its replacement is proposed. Use review_decision to prepare evidence and record a separate inspection without human approval, or an authorized acceptance or reaffirmation.\";\n if (input.id) {\n const original = byId.get(input.id);\n if (!original) return { status: \"error\", error: `no decision with id \"${input.id}\"` };\n if (original.status !== \"active\")\n return {\n status: \"error\",\n error: \"Archived records cannot be revised; create a new proposal.\",\n };\n const record = importLegacy(original, now);\n const content = decisionContent({\n ...record,\n title,\n body,\n category: input.category,\n files: input.files !== undefined ? files : record.files,\n owner:\n attribution.data.owner === undefined\n ? record.owner\n : (attribution.data.owner ?? undefined),\n sources: attribution.data.sources ?? record.sources,\n });\n if (JSON.stringify(content) === JSON.stringify(decisionContent(record))) {\n return {\n status: \"unchanged\",\n id: record.id,\n totalActive: existing.filter((r) => r.status === \"active\").length,\n approval: decisionApproval(original),\n warnings,\n hint: \"Unchanged content; no review or freshness stamp was written. Use review_decision for explicit re-verification.\",\n };\n }\n const revision = record.revision + 1;\n const capture = await captureAnchors(rootDir, content.files);\n const updated: ReviewedDecisionRecord = {\n ...record,\n ...content,\n owner: content.owner,\n updatedAt: now,\n revision,\n approval: \"proposed\",\n capture,\n history: [\n ...record.history,\n {\n kind: \"revised\",\n at: now,\n actor: attribution.data.actor,\n revision,\n content,\n approval: \"proposed\",\n status: \"active\",\n refreshedHash: record.refreshedHash,\n capture,\n },\n ],\n };\n await saveDecisionRecord(rootDir, updated);\n return {\n status: \"updated\",\n id: record.id,\n totalActive: existing.filter((r) => r.status === \"active\").length,\n approval: \"proposed\",\n warnings,\n hint,\n };\n }\n const old = input.supersedes ? byId.get(input.supersedes) : undefined;\n if (input.supersedes && !old)\n return { status: \"error\", error: `no decision with id \"${input.supersedes}\" to supersede` };\n if (\n old &&\n (old.status !== \"active\" || decisionApproval(effectiveDecision(old)) === \"accepted\")\n ) {\n return {\n status: \"error\",\n error:\n \"A proposal cannot supersede an accepted or archived record. Create and review the replacement separately, then explicitly retire the old decision with review_decision.\",\n };\n }\n if (!input.force) {\n const duplicate = findNearDuplicate({ title, body, files }, existing);\n if (duplicate)\n return {\n status: \"duplicate_suspected\",\n existing: duplicate.record,\n hint: `A similar decision exists (\"${duplicate.record.title}\"). Call save_decision with id=\"${duplicate.record.id}\" to revise it, or force:true if distinct.`,\n };\n }\n const id = decisionIdFor(title, body, new Set(byId.keys()));\n if (byId.has(id))\n return {\n status: \"error\",\n error: `Decision id collision: ${id}. Choose a distinct title or revise the existing record.`,\n };\n const content = decisionContent({\n title,\n body,\n category: input.category,\n files,\n owner: attribution.data.owner ?? undefined,\n sources: attribution.data.sources ?? [],\n });\n const capture = await captureAnchors(rootDir, content.files);\n const record: ReviewedDecisionRecord = {\n ...content,\n capture,\n version: 2,\n id,\n createdAt: now,\n updatedAt: now,\n refreshedHash: head,\n status: \"active\",\n approval: \"proposed\",\n revision: 1,\n history: [\n {\n kind: \"created\",\n at: now,\n actor: attribution.data.actor,\n revision: 1,\n content,\n approval: \"proposed\",\n status: \"active\",\n refreshedHash: head,\n capture,\n },\n ],\n };\n // Write the replacement first: a failed second write leaves both records\n // available instead of removing the original before its replacement exists.\n await saveDecisionRecord(rootDir, record);\n if (old) {\n const imported = importLegacy(old, now);\n await saveDecisionRecord(rootDir, {\n ...imported,\n status: \"superseded\",\n supersededBy: id,\n updatedAt: now,\n history: [\n ...imported.history,\n {\n kind: \"superseded\",\n at: now,\n actor: attribution.data.actor,\n note: `Replaced by proposal ${id}`,\n revision: imported.revision,\n content: decisionContent(imported),\n approval: imported.approval,\n status: \"superseded\",\n refreshedHash: imported.refreshedHash,\n capture: imported.capture,\n },\n ],\n });\n }\n const totalActive = existing.filter((r) => r.status === \"active\").length + (old ? 0 : 1);\n const result: UpsertDecisionResult = {\n status: old ? \"superseded_and_created\" : \"created\",\n id,\n totalActive,\n approval: \"proposed\",\n warnings,\n hint,\n };\n if (totalActive > MAX_ACTIVE_DECISIONS) {\n result.pruneCandidates = existing\n .filter((r) => r.status !== \"active\")\n .map((r) => r.id)\n .slice(0, 10);\n warnings.push(\n `${totalActive} active decisions exceeds the soft cap of ${MAX_ACTIVE_DECISIONS} — consider a cleanup PR (archived records first)`,\n );\n }\n return result;\n });\n}\n","import { execGit } from \"../utils/git-read.js\";\nimport path from \"node:path\";\nimport { createFileAccess } from \"../utils/files.js\";\nexport { SOURCE_GLOB, SOURCE_IGNORE } from \"../utils/files.js\";\nimport { readStoreJson, writeStoreJson, type StoreDiagnostic } from \"../utils/storage.js\";\nimport { z } from \"zod\";\nimport { withSnapshotWrite } from \"./lock.js\";\nimport { normalizeRepoPath } from \"../utils/paths.js\";\nimport { buildTestMap } from \"../test-map.js\";\n\nexport interface FeatureEntry {\n description: string;\n files: string[];\n tests?: string[];\n /**\n * Commit this entry was last verified against. Entries updated by an\n * incremental save carry HEAD here; untouched entries keep the hash the\n * map had before the save, so drift stays visible per entry. Absent means\n * \"as of the snapshot's top-level gitHash\".\n */\n refreshedHash?: string;\n /**\n * Whether this is a user-facing capability or internal infrastructure\n * (DI wiring, config loading, logging, provider/transport plumbing).\n * Capabilities are published to product-facing docs (Confluence);\n * infrastructure stays in the AI concept map only. Defaults to \"capability\"\n * when absent (older snapshots) — see normalizeFeatureType.\n */\n type?: \"capability\" | \"infrastructure\";\n /**\n * When an assistant last confirmed this entry's files actually implement\n * the claimed feature (verify_snapshot flow). Absent on older snapshots\n * and never-verified entries. Drift checks freshness against git; this\n * checks the map was CORRECT in the first place.\n */\n verifiedAt?: string;\n verifiedHash?: string;\n /** Digest of the entry and bounded source evidence reviewed for this verdict. */\n verificationToken?: string;\n /** Set when verification judged the entry wrong — re-map it. */\n verificationFailed?: boolean;\n verificationNote?: string;\n}\n\nexport type FeatureType = \"capability\" | \"infrastructure\";\n\n/**\n * Coerce an arbitrary type value to a known classification. Anything that\n * isn't explicitly \"infrastructure\" defaults to \"capability\" — so older\n * snapshots and unclassified entries are treated as user-facing (published),\n * never silently hidden.\n */\nexport function normalizeFeatureType(value: unknown): FeatureType {\n return value === \"infrastructure\" ? \"infrastructure\" : \"capability\";\n}\n\nexport interface FlowEntry {\n description: string;\n chain: string[];\n /** See FeatureEntry.refreshedHash. */\n refreshedHash?: string;\n /** See FeatureEntry.verifiedAt / verificationFailed. */\n verifiedAt?: string;\n verifiedHash?: string;\n /** Digest of the entry and bounded source evidence reviewed for this verdict. */\n verificationToken?: string;\n verificationFailed?: boolean;\n verificationNote?: string;\n}\n\nexport interface Snapshot {\n version: 2;\n createdAt: string;\n updatedAt: string;\n gitHash: string;\n features: Record<string, FeatureEntry>;\n flows: Record<string, FlowEntry>;\n}\n\nconst repoPath = z\n .string()\n .refine((value) => normalizeRepoPath(value) !== null, \"Expected a relative repository path\");\nconst verificationFields = {\n refreshedHash: z.string().optional(),\n verifiedAt: z.string().optional(),\n verifiedHash: z.string().optional(),\n verificationToken: z.string().optional(),\n verificationFailed: z.boolean().optional(),\n verificationNote: z.string().optional(),\n};\nexport const featureSchema = z\n .object({\n description: z.string(),\n files: z.array(repoPath),\n tests: z.array(repoPath).optional(),\n type: z.enum([\"capability\", \"infrastructure\"]).optional(),\n ...verificationFields,\n })\n .passthrough();\nexport const flowSchema = z\n .object({ description: z.string(), chain: z.array(repoPath), ...verificationFields })\n .passthrough();\nconst snapshotSchema = z\n .object({\n version: z.literal(2),\n createdAt: z.string(),\n updatedAt: z.string(),\n gitHash: z.string(),\n features: z.record(featureSchema),\n flows: z.record(flowSchema),\n })\n .passthrough();\n\nexport async function loadSnapshot(rootDir: string): Promise<Snapshot | null> {\n const parsed = await readStoreJson(rootDir, \".mason/snapshot.json\");\n if (parsed === null || (parsed as { version?: number }).version === 1) return null;\n const result = snapshotSchema.safeParse(parsed);\n if (!result.success) throw new Error(`Invalid Mason snapshot: ${result.error.message}`);\n return result.data;\n}\n\n/** Context and onboarding can use decisions even when the optional map is broken. */\nexport async function inspectSnapshot(rootDir: string): Promise<{\n status: \"available\" | \"missing\" | \"invalid\";\n snapshot: Snapshot | null;\n diagnostics: StoreDiagnostic[];\n}> {\n try {\n const raw = await readStoreJson(rootDir, \".mason/snapshot.json\");\n const snapshot = raw === null ? null : snapshotSchema.parse(raw);\n return { status: snapshot ? \"available\" : \"missing\", snapshot, diagnostics: [] };\n } catch (error) {\n return {\n status: \"invalid\",\n snapshot: null,\n diagnostics: [\n {\n path: \".mason/snapshot.json\",\n message: error instanceof Error ? error.message : String(error),\n },\n ],\n };\n }\n}\n\n/** Read and commit under one cross-process lock; a null snapshot means no write. */\nexport async function updateSnapshot<T>(\n rootDir: string,\n update: (current: Snapshot | null) => Promise<{\n snapshot: Snapshot | null;\n result: T;\n afterSave?: () => Promise<void>;\n }>,\n): Promise<T> {\n return withSnapshotWrite(rootDir, async () => {\n const change = await update(await loadSnapshot(rootDir));\n if (change.snapshot) {\n await writeStoreJson(rootDir, \".mason/snapshot.json\", snapshotSchema.parse(change.snapshot));\n await change.afterSave?.();\n }\n return change.result;\n });\n}\n\nexport async function getCurrentGitHash(rootDir: string): Promise<string> {\n try {\n const { stdout } = await execGit([\"rev-parse\", \"HEAD\"], {\n cwd: rootDir,\n });\n return stdout.trim();\n } catch {\n return \"unknown\";\n }\n}\n\nexport const DEFAULT_BATCH_SIZE = 50;\nconst SKELETON_CHARS = 500;\nconst DEEP_SAMPLE_CHARS = 1500;\nconst DEEP_SAMPLES_PER_BATCH = 3;\n\nexport interface SnapshotBatch {\n offset: number;\n batchSize: number;\n nextOffset: number | null;\n totalFiles: number;\n skeletons: Array<{ path: string; content: string }>;\n samples: Array<{ path: string; content: string }>;\n testPairs: Array<{ test: string; source: string; confidence: string }>;\n}\n\nexport async function listSourceFiles(resolvedRoot: string): Promise<string[]> {\n return (await createFileAccess(resolvedRoot)).list();\n}\n\nexport async function prepareSnapshotBatch(\n rootDir: string,\n offset: number,\n batchSize: number = DEFAULT_BATCH_SIZE,\n scopeFiles?: string[],\n): Promise<SnapshotBatch> {\n const resolvedRoot = path.resolve(rootDir);\n const access = await createFileAccess(resolvedRoot);\n let allFiles = await access.list();\n if (scopeFiles) {\n // Intersect with the real source list: keeps ignore rules and path safety,\n // and silently drops scope entries that no longer exist on disk. An empty\n // scope stays empty — it must not fall back to walking the whole project.\n const scopeSet = new Set(scopeFiles);\n allFiles = allFiles.filter((f) => scopeSet.has(f));\n }\n const totalFiles = allFiles.length;\n const safeOffset = Math.max(0, Math.min(offset, totalFiles));\n const batchPaths = allFiles.slice(safeOffset, safeOffset + batchSize);\n\n const skeletons: Array<{ path: string; content: string }> = [];\n for (const filePath of batchPaths) {\n const full = await access.read(filePath);\n if (full) {\n skeletons.push({\n path: full.path,\n content: full.content.slice(0, SKELETON_CHARS),\n });\n }\n }\n\n // Pick a few files from this batch to read deeply for grounding. Spread\n // evenly across the batch so the deep samples represent the batch's range.\n const samples: Array<{ path: string; content: string }> = [];\n if (skeletons.length > 0) {\n const step = Math.max(1, Math.floor(skeletons.length / DEEP_SAMPLES_PER_BATCH));\n for (let i = 0; i < skeletons.length && samples.length < DEEP_SAMPLES_PER_BATCH; i += step) {\n const full = await access.read(skeletons[i].path);\n if (full) {\n samples.push({\n path: full.path,\n content: full.content.slice(0, DEEP_SAMPLE_CHARS),\n });\n }\n }\n }\n\n // Only include test pairs that involve files in this batch — keeps the\n // appendix relevant and small.\n const batchPathSet = new Set(batchPaths);\n const allTestPairs = (await buildTestMap(resolvedRoot)).paired;\n const testPairs = allTestPairs.filter(\n (p) => batchPathSet.has(p.test) || batchPathSet.has(p.source),\n );\n\n const nextOffset = safeOffset + batchSize >= totalFiles ? null : safeOffset + batchSize;\n\n return {\n offset: safeOffset,\n batchSize,\n nextOffset,\n totalFiles,\n skeletons,\n samples,\n testPairs,\n };\n}\n","import { z } from \"zod\";\nimport fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport { storePath } from \"./storage.js\";\n\nconst lockOwnerSchema = z.object({ host: z.string(), pid: z.number().int().positive() });\n\nasync function lockDiagnostic(file: string): Promise<string> {\n let reason: string;\n try {\n const owner = lockOwnerSchema.safeParse(JSON.parse(await fs.readFile(file, \"utf8\")));\n if (!owner.success) {\n reason = \"Lock owner data is incomplete or malformed.\";\n } else if (owner.data.host !== os.hostname()) {\n reason = \"The lock belongs to another host; its process cannot be checked locally.\";\n } else {\n try {\n process.kill(owner.data.pid, 0);\n reason = `Local owner PID ${owner.data.pid} is still running.`;\n } catch (error) {\n reason =\n (error as NodeJS.ErrnoException).code === \"ESRCH\"\n ? `Local owner PID ${owner.data.pid} has exited, but lock recovery has not completed.`\n : `Local owner PID ${owner.data.pid} could not be checked.`;\n }\n }\n } catch (error) {\n reason =\n error instanceof SyntaxError\n ? \"Lock owner data is incomplete or malformed.\"\n : \"Lock owner data could not be read; the lock may have changed.\";\n }\n if (\n await fs.lstat(file + \".reclaim\").then(\n () => true,\n () => false,\n )\n ) {\n reason += \" A recovery guard also needs inspection: \" + file + \".reclaim.\";\n }\n return (\n reason +\n \" Retry if a writer is active. For manual recovery, stop all Mason writers for this checkout, confirm they have exited (including other hosts), then remove only confirmed abandoned lock and lock.reclaim files. See docs/reference.md#snapshot-repairs-and-verification.\"\n );\n}\n\n/** Cross-process lock: a killed writer's lock is reclaimed only after its local PID is gone. */\nexport async function withStoreLock<T>(\n root: string,\n directory: string,\n run: () => Promise<T>,\n waitMs = 5000,\n label = \"Mason store\",\n): Promise<T> {\n const file = await storePath(root, directory + \"/lock\", true);\n const deadline = Date.now() + waitMs;\n let handle;\n while (!handle) {\n try {\n handle = await fs.open(file, \"wx\", 0o600);\n } catch (error) {\n // Windows can deny opening a lock while another process finishes deleting it.\n // Retry acquisition only, retaining the original error if access stays denied.\n if (process.platform === \"win32\" && (error as NodeJS.ErrnoException).code === \"EPERM\") {\n if (Date.now() >= deadline) throw error;\n await new Promise((resolve) => setTimeout(resolve, 40));\n continue;\n }\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\") throw error;\n // Do not steal malformed, remote, or live locks on a time-based guess.\n try {\n const owner = lockOwnerSchema.parse(JSON.parse(await fs.readFile(file, \"utf8\")));\n if (owner.host === os.hostname() && Number.isInteger(owner.pid) && owner.pid > 0) {\n try {\n process.kill(owner.pid, 0);\n } catch (probe) {\n if ((probe as NodeJS.ErrnoException).code === \"ESRCH\") {\n // A separate guard arbitrates reclamation; recheck the owner before unlinking.\n const reclaim = file + \".reclaim\";\n let guard;\n try {\n guard = await fs.open(reclaim, \"wx\", 0o600);\n const current = lockOwnerSchema.parse(JSON.parse(await fs.readFile(file, \"utf8\")));\n if (current.pid === owner.pid && current.host === owner.host) await fs.unlink(file);\n } finally {\n if (guard) {\n await guard.close();\n await fs.rm(reclaim, { force: true });\n }\n }\n }\n }\n }\n } catch {\n /* Another writer may be creating/releasing/reclaiming it. */\n }\n if (Date.now() >= deadline)\n throw new Error(\n label +\n \" is busy or its lock needs inspection: \" +\n file +\n \". \" +\n (await lockDiagnostic(file)),\n { cause: error },\n );\n await new Promise((resolve) => setTimeout(resolve, 40));\n }\n if (handle) {\n try {\n await handle.writeFile(JSON.stringify({ pid: process.pid, host: os.hostname() }));\n } catch (error) {\n await handle.close().catch(() => {});\n await fs.rm(file, { force: true }).catch(() => {});\n throw error;\n }\n }\n }\n try {\n return await run();\n } finally {\n await handle.close();\n await fs.unlink(file);\n }\n}\n","import path from \"node:path\";\nimport { createFileAccess } from \"./utils/files.js\";\n\nexport interface TestPair {\n test: string;\n source: string;\n confidence: string;\n}\n\nexport interface TestMapResult {\n totalTestFiles: number;\n paired: TestPair[];\n unmatched: string[];\n}\n\nexport async function buildTestMap(dir: string): Promise<TestMapResult> {\n const rootDir = path.resolve(dir);\n const access = await createFileAccess(rootDir);\n\n // Find all test files\n const testPatterns = [\n \"**/*.test.*\",\n \"**/*.spec.*\",\n \"**/*Test.kt\",\n \"**/*Test.java\",\n \"**/*Tests.kt\",\n \"**/*Tests.java\",\n \"**/test_*.py\",\n \"**/*_test.py\",\n \"**/*_test.go\",\n \"**/*Tests.swift\",\n \"**/*Test.swift\",\n \"**/*_test.rs\",\n ];\n const testFiles = await access.list(testPatterns);\n\n // Find all source files\n const sourceFiles = await access.list();\n\n // Build source file index by base name (without extension)\n const sourceByBaseName = new Map<string, string[]>();\n for (const file of sourceFiles) {\n if (testFiles.includes(file)) continue; // Skip test files\n const baseName = path.basename(file).replace(/\\.[^.]+$/, \"\");\n const existing = sourceByBaseName.get(baseName) ?? [];\n existing.push(file);\n sourceByBaseName.set(baseName, existing);\n }\n\n // Match test files to source files by name\n const paired: TestPair[] = [];\n const unmatched: string[] = [];\n\n for (const testFile of testFiles) {\n const testBaseName = path.basename(testFile).replace(/\\.[^.]+$/, \"\");\n\n // Strip test suffixes/prefixes to get the source name\n const sourceName = testBaseName\n .replace(/Test$|Tests$|Spec$|\\.test$|\\.spec$/, \"\")\n .replace(/^test_|_test$/, \"\");\n\n if (!sourceName) {\n unmatched.push(testFile);\n continue;\n }\n\n const candidates = sourceByBaseName.get(sourceName);\n if (candidates && candidates.length > 0) {\n // If multiple candidates, prefer one in a similar directory path\n const testDir = path.dirname(testFile);\n const bestMatch = candidates.reduce((best, candidate) => {\n const candidateDir = path.dirname(candidate);\n const bestDir = path.dirname(best);\n const candidateOverlap = commonSegments(testDir, candidateDir);\n const bestOverlap = commonSegments(testDir, bestDir);\n return candidateOverlap > bestOverlap ? candidate : best;\n });\n\n paired.push({\n test: testFile,\n source: bestMatch,\n confidence: candidates.length === 1 ? \"exact\" : \"best-guess\",\n });\n } else {\n unmatched.push(testFile);\n }\n }\n\n return { totalTestFiles: testFiles.length, paired, unmatched };\n}\n\nfunction commonSegments(pathA: string, pathB: string): number {\n const segsA = pathA.split(\"/\");\n const segsB = pathB.split(\"/\");\n let count = 0;\n for (let i = 0; i < Math.min(segsA.length, segsB.length); i++) {\n if (segsA[i] === segsB[i]) count++;\n else break;\n }\n return count;\n}\n","import path from \"node:path\";\n\n// Question/filler words that carry no signal about which entry a task\n// touches. Domain words (\"auth\", \"drift\") are never in this list.\nconst STOPWORDS = new Set([\n \"the\",\n \"a\",\n \"an\",\n \"and\",\n \"or\",\n \"of\",\n \"to\",\n \"in\",\n \"on\",\n \"for\",\n \"with\",\n \"how\",\n \"does\",\n \"do\",\n \"is\",\n \"are\",\n \"was\",\n \"what\",\n \"where\",\n \"which\",\n \"why\",\n \"when\",\n \"who\",\n \"i\",\n \"we\",\n \"my\",\n \"our\",\n \"you\",\n \"your\",\n \"it\",\n \"its\",\n \"this\",\n \"that\",\n \"these\",\n \"those\",\n \"can\",\n \"could\",\n \"should\",\n \"would\",\n \"will\",\n \"want\",\n \"need\",\n \"please\",\n \"about\",\n \"into\",\n \"from\",\n \"when\",\n \"there\",\n \"any\",\n \"all\",\n \"some\",\n \"not\",\n \"but\",\n \"also\",\n \"just\",\n \"like\",\n \"get\",\n \"make\",\n \"use\",\n \"new\",\n \"work\",\n \"works\",\n \"working\",\n \"implement\",\n \"implemented\",\n \"change\",\n \"changed\",\n \"file\",\n \"files\",\n \"code\",\n]);\n\n/** Split camelCase/PascalCase/kebab/snake/path into lowercase word tokens. */\nexport function tokenize(text: string): string[] {\n return text\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .filter((t) => t.length > 2 && !STOPWORDS.has(t));\n}\n\n/** Crude singular/plural folding so \"flows\" matches \"flow\" etc. */\nexport function stem(token: string): string {\n return token.length > 3 && token.endsWith(\"s\") ? token.slice(0, -1) : token;\n}\n\nexport function tokenSet(text: string): Set<string> {\n return new Set(tokenize(text).map(stem));\n}\n\nexport interface Scorable {\n name: string;\n description: string;\n files: string[];\n}\n\n/**\n * Lexical relevance of one entry to the task. Name hits are the strongest\n * signal, then description, then file-path words. Each distinct task token\n * counts once at its best weight, so a token appearing everywhere doesn't\n * triple-count.\n */\nexport function scoreEntry(taskTokens: Set<string>, entry: Scorable): number {\n const nameTokens = tokenSet(entry.name);\n const descTokens = tokenSet(entry.description);\n const fileTokens = tokenSet(entry.files.map((f) => path.basename(f)).join(\" \"));\n\n let score = 0;\n for (const token of taskTokens) {\n if (nameTokens.has(token)) score += 3;\n else if (descTokens.has(token)) score += 1;\n else if (fileTokens.has(token)) score += 1;\n }\n return score;\n}\n\n/** Jaccard similarity of two token sets: |∩| / |∪|, 0 when both empty. */\nexport function jaccard(a: Set<string>, b: Set<string>): number {\n if (a.size === 0 && b.size === 0) return 0;\n let intersection = 0;\n for (const token of a) if (b.has(token)) intersection++;\n return intersection / (a.size + b.size - intersection);\n}\n","import { z } from \"zod\";\nimport { createHash } from \"node:crypto\";\nimport { anchorCaptureSchema } from \"./anchors.js\";\nimport { normalizeRepoPath } from \"../utils/paths.js\";\nimport { assessTrust, type Freshness } from \"../context/trust.js\";\n\nconst text = (max: number) => z.string().trim().min(1).max(max);\nexport const decisionSourceSchema = z\n .object({\n kind: z.enum([\"pull_request\", \"issue\", \"incident\", \"discussion\", \"document\", \"other\"]),\n reference: text(1000),\n note: text(500).optional(),\n })\n .strict();\nexport type DecisionSource = z.infer<typeof decisionSourceSchema>;\nexport const attributionSchema = z.object({\n owner: text(200).nullable().optional(),\n sources: z.array(decisionSourceSchema).max(20).optional(),\n actor: text(200).optional(),\n});\nconst contentSchema = z.object({\n title: z.string().min(1),\n body: z.string().min(1),\n category: z.enum([\"decision\", \"gotcha\", \"deprecation\", \"convention\"]),\n files: z.array(z.string().refine((f) => normalizeRepoPath(f) !== null)),\n owner: text(200).optional(),\n sources: z.array(decisionSourceSchema).max(20),\n});\nconst approvalSchema = z.enum([\"unreviewed\", \"proposed\", \"accepted\"]);\nconst statusSchema = z.enum([\"active\", \"superseded\", \"retired\"]);\nexport const reviewEvidenceSchema = z.object({\n baseHash: z.string(),\n headHash: z.string(),\n historyAvailable: z.boolean(),\n changedFiles: z.array(z.string()),\n localChanges: z.array(z.string()),\n});\nconst eventSchema = z.object({\n kind: z.enum([\n \"imported\",\n \"created\",\n \"revised\",\n \"accepted\",\n \"reaffirmed\",\n \"retired\",\n \"superseded\",\n ]),\n at: z.string().datetime(),\n actor: text(200).optional(),\n note: text(1500).optional(),\n revision: z.number().int().positive(),\n content: contentSchema,\n approval: approvalSchema,\n status: statusSchema,\n refreshedHash: z.string(),\n evidence: reviewEvidenceSchema.optional(),\n capture: anchorCaptureSchema.optional(),\n});\nexport type DecisionEvent = z.infer<typeof eventSchema>;\nexport type DecisionApproval = z.infer<typeof approvalSchema>;\nexport type DecisionContent = z.infer<typeof contentSchema>;\n\nconst legacySchema = z\n .object({\n version: z.literal(1),\n id: z.string().regex(/^[a-zA-Z0-9_-]+$/),\n title: z.string().min(1),\n body: z.string().min(1),\n category: contentSchema.shape.category,\n files: contentSchema.shape.files,\n createdAt: z.string(),\n updatedAt: z.string(),\n refreshedHash: z.string(),\n status: z.enum([\"active\", \"superseded\"]),\n supersededBy: z.string().optional(),\n })\n .passthrough();\nconst currentSchema = legacySchema\n .extend({\n version: z.literal(2),\n status: statusSchema,\n approval: approvalSchema,\n revision: z.number().int().positive(),\n owner: text(200).optional(),\n sources: z.array(decisionSourceSchema).max(20),\n history: z.array(eventSchema).min(1),\n capture: anchorCaptureSchema.optional(),\n inspections: z\n .array(\n z.object({\n at: z.string().datetime(),\n inspector: text(200),\n note: text(1500),\n outcome: z.literal(\"no_contradiction_found\"),\n decisionDigest: z.string().regex(/^[a-f0-9]{64}$/),\n headHash: z.string(),\n capture: anchorCaptureSchema,\n }),\n )\n .max(200)\n .optional(),\n })\n .superRefine((record, ctx) => {\n const invalid = (message: string) => ctx.addIssue({ code: \"custom\", message });\n const same = (a: unknown, b: unknown) => JSON.stringify(a) === JSON.stringify(b);\n let previous: DecisionEvent | undefined;\n for (const event of record.history) {\n if (!previous) {\n if (![\"created\", \"imported\"].includes(event.kind) || event.revision !== 1)\n invalid(\"History must begin with creation or legacy import at revision 1\");\n if (event.approval !== (event.kind === \"created\" ? \"proposed\" : \"unreviewed\"))\n invalid(\"Initial records cannot claim acceptance\");\n } else {\n if ([\"created\", \"imported\"].includes(event.kind)) invalid(\"History cannot restart\");\n if (previous.status !== \"active\") invalid(\"Archived decisions cannot be changed\");\n if (event.revision !== previous.revision + (event.kind === \"revised\" ? 1 : 0))\n invalid(\"Invalid revision sequence\");\n if (event.kind !== \"revised\" && !same(event.content, previous.content))\n invalid(\"A review cannot silently revise decision content\");\n if (event.kind === \"reaffirmed\" && previous.approval !== \"accepted\")\n invalid(\"Only accepted decisions can be reaffirmed\");\n if (event.kind === \"accepted\" && previous.approval === \"accepted\")\n invalid(\"Use reaffirmation for an accepted decision\");\n const approval =\n event.kind === \"revised\"\n ? \"proposed\"\n : [\"accepted\", \"reaffirmed\"].includes(event.kind)\n ? \"accepted\"\n : previous.approval;\n if (event.approval !== approval) invalid(\"Approval disagrees with review history\");\n if (\n ![\"accepted\", \"reaffirmed\"].includes(event.kind) &&\n event.refreshedHash !== previous.refreshedHash\n )\n invalid(\"Only a review can refresh the evidence baseline\");\n }\n if (\n event.kind !== \"imported\" &&\n event.status !==\n (event.kind === \"retired\"\n ? \"retired\"\n : event.kind === \"superseded\"\n ? \"superseded\"\n : \"active\")\n )\n invalid(\"Lifecycle disagrees with history\");\n if (\n [\"accepted\", \"reaffirmed\", \"retired\"].includes(event.kind) &&\n (!event.actor || !event.note || !event.evidence)\n )\n invalid(\"Reviews require a named reviewer, reason, and code evidence\");\n if ([\"accepted\", \"reaffirmed\"].includes(event.kind)) {\n if (!event.content.owner || !event.content.sources.length)\n invalid(\"Accepted decisions require an owner and source\");\n if (\n !event.evidence ||\n !/^[a-f0-9]{40,64}$/.test(event.evidence.headHash) ||\n event.refreshedHash !== event.evidence.headHash ||\n event.evidence.localChanges.length\n )\n invalid(\"Acceptance requires a committed evidence baseline\");\n }\n if (event.capture && !same([...event.content.files].sort(), event.capture.anchors))\n invalid(\"Capture scope disagrees with decision anchors\");\n previous = event;\n }\n if (previous && !same(previous.capture, record.capture))\n invalid(\"Capture does not match the final history event\");\n if (\n !previous ||\n !same(previous.content, decisionContent(record)) ||\n previous.approval !== record.approval ||\n previous.status !== record.status ||\n previous.revision !== record.revision ||\n previous.refreshedHash !== record.refreshedHash\n )\n invalid(\"Decision does not match the final history event\");\n });\n\nexport const decisionSchema = z.union([legacySchema, currentSchema]);\nexport type DecisionRecord = z.infer<typeof decisionSchema>;\nexport type ReviewedDecisionRecord = z.infer<typeof currentSchema>;\n\nexport function decisionContent(\n record: Pick<DecisionRecord, \"title\" | \"body\" | \"category\" | \"files\"> & {\n owner?: unknown;\n sources?: unknown;\n },\n): DecisionContent {\n return {\n title: record.title,\n body: record.body,\n category: record.category,\n files: record.files,\n ...(typeof record.owner === \"string\" ? { owner: record.owner } : {}),\n sources: Array.isArray(record.sources) ? (record.sources as DecisionSource[]) : [],\n };\n}\n\n/** Reading legacy records never upgrades their approval or rewrites their files. */\nexport function decisionApproval(record: DecisionRecord): DecisionApproval {\n return record.version === 1 ? \"unreviewed\" : record.approval;\n}\n\n/** The last accepted revision remains operative while a replacement is drafted.\n * This is a read-only projection; writes and review tokens use the complete record.\n * Archived records never regain authority from their history.\n */\nexport function effectiveDecision(record: DecisionRecord): DecisionRecord {\n if (record.version !== 2 || record.status !== \"active\" || record.approval !== \"proposed\")\n return record;\n let index = record.history.length - 1;\n while (index >= 0 && ![\"accepted\", \"reaffirmed\"].includes(record.history[index].kind)) index--;\n if (index < 0) return record;\n const event = record.history[index];\n return {\n ...record,\n ...event.content,\n owner: event.content.owner,\n approval: \"accepted\",\n revision: event.revision,\n refreshedHash: event.refreshedHash,\n capture: event.capture,\n updatedAt: event.at,\n history: record.history.slice(0, index + 1),\n };\n}\n\n/** Anchors relevant to either the operative knowledge or its pending proposal. */\nexport function decisionAnchors(record: DecisionRecord): string[] {\n return [...new Set([...effectiveDecision(record).files, ...record.files])];\n}\n\nexport function importLegacy(record: DecisionRecord, now: string): ReviewedDecisionRecord {\n if (record.version === 2) return record;\n // Ignore unrecognized legacy fields: they are not evidence of authorship or approval.\n const content = decisionContent({\n title: record.title,\n body: record.body,\n category: record.category,\n files: record.files,\n });\n return {\n id: record.id,\n createdAt: record.createdAt,\n updatedAt: record.updatedAt,\n status: record.status,\n refreshedHash: record.refreshedHash,\n supersededBy: record.supersededBy,\n ...content,\n version: 2,\n approval: \"unreviewed\",\n revision: 1,\n history: [\n {\n kind: \"imported\",\n at: now,\n revision: 1,\n content,\n approval: \"unreviewed\",\n status: record.status,\n refreshedHash: record.refreshedHash,\n note: \"Imported a legacy record. Prior authorship and review history are unknown.\",\n },\n ],\n };\n}\n\nexport function decisionProvenance(record: DecisionRecord, freshness: Freshness = \"unknown\") {\n const approval = decisionApproval(record);\n const review =\n record.version === 2\n ? [...record.history]\n .reverse()\n .find(\n (e) => [\"accepted\", \"reaffirmed\"].includes(e.kind) && e.revision === record.revision,\n )\n : undefined;\n return {\n approval,\n revision: record.version === 2 ? record.revision : 0,\n owner: record.version === 2 ? (record.owner ?? null) : null,\n sources: record.version === 2 ? record.sources : [],\n guidance:\n record.status !== \"active\"\n ? \"historical\"\n : approval === \"accepted\"\n ? \"constraint\"\n : approval === \"proposed\"\n ? \"proposal\"\n : \"unreviewed\",\n reviewRequired:\n record.status === \"active\" && (approval !== \"accepted\" || freshness !== \"current\"),\n lastReview: review\n ? {\n reviewer: review.actor!,\n at: review.at,\n note: review.note!,\n gitHash: review.refreshedHash,\n }\n : null,\n };\n}\n\nexport function decisionTrust(record: DecisionRecord, freshness: Freshness) {\n const review = decisionProvenance(record, freshness).lastReview;\n return assessTrust(\n review ? { verifiedAt: review.at, verifiedHash: review.gitHash } : {},\n freshness,\n );\n}\n\n/** Inspection identity covers both the proposal and the operative accepted revision. */\nexport function decisionInspectionDigest(record: DecisionRecord): string {\n const effective = effectiveDecision(record);\n return createHash(\"sha256\")\n .update(\n JSON.stringify([\n record.status,\n decisionApproval(record),\n record.version === 2 ? record.revision : 0,\n decisionContent(record),\n decisionContent(effective),\n effective.refreshedHash,\n ]),\n )\n .digest(\"hex\");\n}\n\nexport function latestDecisionInspection(record: DecisionRecord) {\n return record.version === 2\n ? record.inspections\n ?.filter((i) => i.decisionDigest === decisionInspectionDigest(record))\n .at(-1)\n : undefined;\n}\n\nfunction revisionKnowledge(record: DecisionRecord, freshness: Freshness) {\n return {\n ...decisionContent(record),\n ...decisionProvenance(record, freshness),\n trust: decisionTrust(record, freshness),\n };\n}\n\n/** Readers show accepted content first and label the unaccepted draft separately. */\nexport function decisionKnowledge(\n record: DecisionRecord,\n freshness: Freshness = \"unknown\",\n proposalFreshness: Freshness = \"unknown\",\n) {\n const effective = effectiveDecision(record);\n const inspection = latestDecisionInspection(record);\n return {\n ...revisionKnowledge(effective, freshness),\n ...(inspection\n ? {\n inspection: {\n freshness,\n at: inspection.at,\n inspector: inspection.inspector,\n note: inspection.note,\n outcome: inspection.outcome,\n headHash: inspection.headHash,\n },\n }\n : {}),\n ...(effective !== record\n ? { pendingProposal: revisionKnowledge(record, proposalFreshness) }\n : {}),\n };\n}\n\nexport function compactDecisionKnowledge(...args: Parameters<typeof decisionKnowledge>) {\n const { body, pendingProposal, ...summary } = decisionKnowledge(...args);\n if (!pendingProposal) return summary;\n const { body: proposalBody, ...proposal } = pendingProposal;\n return { ...summary, pendingProposal: proposal };\n}\n\nexport const DECISION_GUIDANCE =\n \"Accepted decisions are recorded team constraints, subject to freshness checks. A pendingProposal is an unaccepted replacement; the accepted revision remains operative until explicit acceptance or retirement. Proposals are suggestions; legacy unreviewed records need confirmation. Use review_decision to prepare evidence and record a separate source inspection without human approval, or an authorized human review; identities and sources are recorded assertions, not authenticated proof.\";\n","export type Freshness = \"current\" | \"changed\" | \"unknown\";\nexport interface TrustState {\n freshness: Freshness;\n verification: \"unverified\" | \"passed\" | \"failed\" | \"stale\" | \"unknown\";\n /** Historical map verdict, retained even when its evidence no longer applies. */\n recordedVerdict?: \"passed\" | \"failed\";\n verifiedAt?: string;\n verifiedHash?: string;\n reasons: string[];\n}\n\nexport function assessTrust(\n entry: {\n verifiedAt?: string;\n verifiedHash?: string;\n verificationFailed?: boolean;\n verificationNote?: string;\n },\n freshness: Freshness,\n): TrustState {\n const verification = entry.verificationFailed\n ? \"failed\"\n : entry.verifiedAt\n ? \"passed\"\n : \"unverified\";\n const reasons: string[] = [];\n if (freshness === \"unknown\")\n reasons.push(\n \"Anchors, history, or working-tree evidence are unavailable; verify before relying on this entry.\",\n );\n if (freshness === \"changed\")\n reasons.push(\n \"Anchored files changed; verify against current code before relying on this entry.\",\n );\n if (verification === \"failed\")\n reasons.push(\n `Verification failed: ${entry.verificationNote ?? \"re-map this entry before relying on it\"}`,\n );\n if (verification === \"unverified\") reasons.push(\"No correctness verification has been recorded.\");\n return {\n freshness,\n verification,\n verifiedAt: entry.verifiedAt,\n verifiedHash: entry.verifiedHash,\n reasons,\n };\n}\n\nexport function trustHint(states: TrustState[]): string {\n const parts: string[] = [];\n if (states.some((s) => s.verification === \"failed\" || s.recordedVerdict === \"failed\"))\n parts.push(\n \"Verification failed for returned entries; do not rely on those descriptions until corrected.\",\n );\n if (states.some((s) => s.verification === \"stale\"))\n parts.push(\n \"Recorded verification evidence changed; use verify_snapshot and review the current evidence before reusing those verdicts.\",\n );\n if (states.some((s) => s.verification === \"unknown\"))\n parts.push(\n \"Recorded verification evidence cannot be confirmed; inspect the files and obtain a fresh review before relying on those verdicts.\",\n );\n if (states.some((s) => s.freshness === \"unknown\"))\n parts.push(\n \"Freshness is unknown for some returned entries; inspect their files before relying on them.\",\n );\n if (states.some((s) => s.freshness === \"changed\"))\n parts.push(\n \"Some returned entries have changed files, including possible local edits; verify against the current code.\",\n );\n if (!parts.length)\n parts.push(\n \"No changes detected in the returned anchors. This does not prove the descriptions are correct.\",\n );\n if (states.some((s) => s.verification === \"unverified\"))\n parts.push(\n \"Some entries have never been verified; read their evidence or use verify_snapshot for map descriptions.\",\n );\n return parts.join(\" \");\n}\n","import { captureAnchorScopes, changedCaptureFiles, type AnchorCapture } from \"./anchors.js\";\nimport path from \"node:path\";\nimport { getChangesWithStatus, getWorkingTree, touchedPaths } from \"../drift/drift.js\";\nimport { matchingPaths } from \"../utils/paths.js\";\nimport type { Freshness } from \"../context/trust.js\";\nimport type { StoreDiagnostic } from \"../utils/storage.js\";\nimport { getCurrentGitHash } from \"../snapshot/snapshot.js\";\nimport { loadDecisionStore } from \"./decisions.js\";\nimport type { DecisionRecord } from \"./decisions.js\";\nimport { effectiveDecision, latestDecisionInspection } from \"./provenance.js\";\n\n/**\n * Deliberately separate from DriftReport: mason-drift's exit codes and\n * --json shape are a CI contract, and `stale` there means MAP staleness.\n * Decision staleness is additive on top.\n */\nexport interface DecisionDriftReport {\n historyAvailable: boolean;\n freshness?: Record<string, Freshness>;\n diagnostics?: StoreDiagnostic[];\n totalDecisions: number;\n /** Decision id → committed and local anchor files changed since the evidence baseline. */\n staleDecisions: Record<string, string[]>;\n /** Draft anchors have their own freshness; they cannot replace accepted anchors. */\n pendingProposals?: Record<string, { freshness: Freshness; changedFiles: string[] }>;\n}\n\n/**\n * Flag active decisions whose anchor files changed since the record was\n * last verified. Anchorless decisions have unknown freshness and do not\n * contribute to committed drift.\n * Deterministic — git only, no LLM.\n */\nexport async function computeDecisionDrift(\n rootDir: string,\n decisions?: DecisionRecord[],\n): Promise<DecisionDriftReport> {\n const resolvedRoot = path.resolve(rootDir);\n const store = decisions\n ? { records: decisions, diagnostics: [] }\n : await loadDecisionStore(resolvedRoot);\n const report: DecisionDriftReport = {\n historyAvailable: true,\n totalDecisions: store.records.length,\n staleDecisions: {},\n freshness: {},\n diagnostics: store.diagnostics,\n };\n const [head, workingTree] = await Promise.all([\n getCurrentGitHash(resolvedRoot),\n getWorkingTree(resolvedRoot),\n ]);\n const changesByHash = new Map<string, string[] | null>();\n const baselines = store.records\n .filter((record) => record.status === \"active\")\n .flatMap((record) => {\n const inspection = latestDecisionInspection(record);\n if (inspection) return [inspection.capture];\n return [...new Set([effectiveDecision(record), record])].flatMap((candidate) =>\n candidate.version === 2 && candidate.capture ? [candidate.capture] : [],\n );\n });\n const captures = await captureAnchorScopes(\n resolvedRoot,\n baselines.map((baseline) => baseline.anchors),\n );\n const currentCaptures = new Map(\n baselines.map((baseline, index) => [JSON.stringify(baseline.anchors), captures[index]]),\n );\n const inspect = async (\n record: DecisionRecord,\n inspectionCapture?: AnchorCapture,\n ): Promise<{ freshness: Freshness; changedFiles: string[] }> => {\n if (record.files.length === 0) return { freshness: \"unknown\", changedFiles: [] };\n const baseline = inspectionCapture ?? (record.version === 2 ? record.capture : undefined);\n if (baseline) {\n const current = currentCaptures.get(JSON.stringify(baseline.anchors))!;\n const hits = changedCaptureFiles(baseline, current).filter(\n (file) => matchingPaths(record.files, [file]).length,\n );\n return {\n freshness:\n !baseline.complete || !current.complete ? \"unknown\" : hits.length ? \"changed\" : \"current\",\n changedFiles: hits,\n };\n }\n let touched = changesByHash.get(record.refreshedHash);\n if (touched === undefined) {\n const changes =\n record.refreshedHash === head && head !== \"unknown\"\n ? []\n : await getChangesWithStatus(resolvedRoot, record.refreshedHash);\n touched = changes === null ? null : touchedPaths(changes);\n changesByHash.set(record.refreshedHash, touched);\n }\n if (touched === null) report.historyAvailable = false;\n const hits = touched ? matchingPaths(record.files, touched) : [];\n const localHits = matchingPaths(record.files, workingTree.changedFiles);\n return {\n freshness:\n touched === null || !workingTree.available\n ? \"unknown\"\n : hits.length || localHits.length\n ? \"changed\"\n : \"current\",\n changedFiles: [...new Set([...hits, ...localHits])].sort(),\n };\n };\n for (const record of store.records) {\n if (record.status !== \"active\") continue;\n const effective = effectiveDecision(record);\n const inspection = latestDecisionInspection(record);\n const state = await inspect(effective, inspection?.capture);\n report.freshness![record.id] = state.freshness;\n if (state.changedFiles.length) report.staleDecisions[record.id] = state.changedFiles;\n if (effective !== record)\n (report.pendingProposals ??= {})[record.id] = await inspect(record, inspection?.capture);\n }\n return report;\n}\n","import { execGit } from \"../utils/git-read.js\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { loadSnapshot, getCurrentGitHash, listSourceFiles } from \"../snapshot/snapshot.js\";\nimport type { Snapshot } from \"../snapshot/snapshot.js\";\nimport type { Freshness } from \"../context/trust.js\";\nimport { matchingPaths } from \"../utils/paths.js\";\n\n// Incremental refresh stops paying off once a large share of the map is\n// touched — but small absolute counts are always cheap to refresh in place,\n// so both thresholds must be exceeded before recommending a full rebuild.\nconst FULL_REBUILD_FRACTION = 0.4;\nconst FULL_REBUILD_MIN_CHANGED_MAPPED_FILES = 10;\n\nexport type ChangeStatus = \"added\" | \"modified\" | \"deleted\" | \"renamed\";\n\nexport interface FileChange {\n status: ChangeStatus;\n /** Current path (the new path for renames). */\n path: string;\n /** Pre-rename path, only present for renames. */\n previousPath?: string;\n}\n\nexport type DriftRecommendation = \"up-to-date\" | \"incremental\" | \"full-rebuild\";\n\nexport interface WorkingTreeReport {\n available: boolean;\n changedFiles: string[];\n untrackedFiles: string[];\n}\n\nexport interface DriftReport {\n /** Live entry state; committed drift alone continues to drive CLI exit codes. */\n featureFreshness?: Record<string, Freshness>;\n flowFreshness?: Record<string, Freshness>;\n workingTree?: WorkingTreeReport;\n verification?: { neverVerified: number; failed: string[] };\n stale: boolean;\n snapshotHash: string;\n headHash: string;\n /** Commits between the snapshot and HEAD; null when history is unavailable. */\n commitsBehind: number | null;\n /**\n * False when the snapshot commit is unreachable (shallow clone, rewritten\n * history) — staleFeatures/unmappedFiles/renames cannot be computed then.\n */\n historyAvailable: boolean;\n /** Current paths of every file changed since the snapshot. */\n changedFiles: string[];\n /** Stale feature name → the mapped files that changed under it. */\n staleFeatures: Record<string, string[]>;\n /** Stale flow name → the chain files that changed under it. */\n staleFlows: Record<string, string[]>;\n totalFeatures: number;\n totalFlows: number;\n /** New source files not referenced by any feature or flow. */\n unmappedFiles: string[];\n /** Files referenced by the map that no longer exist on disk. */\n ghostFiles: string[];\n renames: Array<{ from: string; to: string }>;\n recommendation: DriftRecommendation;\n}\n\nfunction parseChanges(output: string): FileChange[] {\n const fields = output.split(\"\\0\");\n const changes: FileChange[] = [];\n for (let i = 0; i < fields.length && fields[i];) {\n const code = fields[i++];\n const first = fields[i++];\n if (!first) break;\n const second = /^[RC]/.test(code) ? fields[i++] : undefined;\n const change: FileChange = second\n ? code.startsWith(\"R\")\n ? { status: \"renamed\", path: second, previousPath: first }\n : { status: \"added\", path: second }\n : { status: code === \"A\" ? \"added\" : code === \"D\" ? \"deleted\" : \"modified\", path: first };\n if (\n change.path.startsWith(\".mason/\") &&\n (!change.previousPath || change.previousPath.startsWith(\".mason/\"))\n )\n continue;\n changes.push(change);\n }\n return changes;\n}\n\nexport function touchedPaths(changes: FileChange[]): string[] {\n return [\n ...new Set(changes.flatMap((c) => (c.previousPath ? [c.previousPath, c.path] : [c.path]))),\n ].sort();\n}\n\nexport async function getChangesWithStatus(\n resolvedRoot: string,\n fromHash: string,\n toHash = \"HEAD\",\n): Promise<FileChange[] | null> {\n if (\n !fromHash ||\n fromHash === \"unknown\" ||\n fromHash.startsWith(\"-\") ||\n !toHash ||\n toHash === \"unknown\" ||\n toHash.startsWith(\"-\")\n )\n return null;\n try {\n const { stdout } = await execGit(\n [\"diff\", \"--name-status\", \"-z\", \"-M\", fromHash, toHash, \"--\"],\n { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 },\n );\n return parseChanges(stdout);\n } catch {\n return null;\n }\n}\n\nexport async function getWorkingTree(resolvedRoot: string): Promise<WorkingTreeReport> {\n try {\n const [diff, untracked] = await Promise.all([\n execGit([\"diff\", \"--name-status\", \"-z\", \"-M\", \"HEAD\", \"--\"], {\n cwd: resolvedRoot,\n maxBuffer: 10 * 1024 * 1024,\n }),\n execGit([\"ls-files\", \"-z\", \"--others\", \"--exclude-standard\"], {\n cwd: resolvedRoot,\n maxBuffer: 10 * 1024 * 1024,\n }),\n ]);\n const untrackedFiles = untracked.stdout\n .split(\"\\0\")\n .filter((f) => f && !f.startsWith(\".mason/\"));\n return {\n available: true,\n changedFiles: [\n ...new Set([...touchedPaths(parseChanges(diff.stdout)), ...untrackedFiles]),\n ].sort(),\n untrackedFiles,\n };\n } catch {\n return { available: false, changedFiles: [], untrackedFiles: [] };\n }\n}\n\nasync function countCommitsBehind(resolvedRoot: string, fromHash: string): Promise<number | null> {\n if (!fromHash || fromHash === \"unknown\" || fromHash.startsWith(\"-\")) return null;\n try {\n const { stdout } = await execGit([\"rev-list\", \"--count\", `${fromHash}..HEAD`], {\n cwd: resolvedRoot,\n });\n const count = Number.parseInt(stdout.trim(), 10);\n return Number.isNaN(count) ? null : count;\n } catch {\n return null;\n }\n}\n\nfunction collectMappedFiles(snapshot: Snapshot): Set<string> {\n const mappedFiles = new Set<string>();\n for (const feature of Object.values(snapshot.features)) {\n for (const f of feature.files) mappedFiles.add(f);\n for (const t of feature.tests ?? []) mappedFiles.add(t);\n }\n for (const flow of Object.values(snapshot.flows)) {\n for (const f of flow.chain) mappedFiles.add(f);\n }\n return mappedFiles;\n}\n\nasync function findGhostFiles(resolvedRoot: string, mappedFiles: Set<string>): Promise<string[]> {\n const ghosts: string[] = [];\n for (const file of mappedFiles) {\n try {\n await fs.access(path.join(resolvedRoot, file));\n } catch {\n ghosts.push(file);\n }\n }\n return ghosts.sort();\n}\n\n/**\n * Compare the concept map against HEAD and report feature-level drift.\n * Fully deterministic — git + filesystem only, no LLM involved.\n * Returns null when no snapshot exists.\n */\nexport async function computeDrift(rootDir: string): Promise<DriftReport | null> {\n const root = path.resolve(rootDir);\n const snapshot = await loadSnapshot(root);\n if (!snapshot) return null;\n const [headHash, workingTree] = await Promise.all([\n getCurrentGitHash(root),\n getWorkingTree(root),\n ]);\n const hashFor = (entry: { refreshedHash?: string }) => entry.refreshedHash ?? snapshot.gitHash;\n const entries = [...Object.values(snapshot.features), ...Object.values(snapshot.flows)];\n const hashes = new Set([snapshot.gitHash, ...entries.map(hashFor)]);\n const changesByHash = new Map<string, FileChange[] | null>();\n await Promise.all(\n [...hashes].map(async (hash) => {\n changesByHash.set(\n hash,\n hash === headHash && headHash !== \"unknown\" ? [] : await getChangesWithStatus(root, hash),\n );\n }),\n );\n const historyAvailable =\n headHash !== \"unknown\" && [...changesByHash.values()].every((changes) => changes !== null);\n const mappedFiles = collectMappedFiles(snapshot);\n const report: DriftReport = {\n stale: !historyAvailable,\n snapshotHash: snapshot.gitHash,\n headHash,\n commitsBehind: 0,\n historyAvailable,\n changedFiles: [],\n staleFeatures: {},\n staleFlows: {},\n totalFeatures: Object.keys(snapshot.features).length,\n totalFlows: Object.keys(snapshot.flows).length,\n unmappedFiles: [],\n ghostFiles: await findGhostFiles(root, mappedFiles),\n renames: [],\n recommendation: historyAvailable ? \"up-to-date\" : \"full-rebuild\",\n featureFreshness: {},\n flowFreshness: {},\n workingTree,\n verification: {\n neverVerified: entries.filter((e) => !e.verifiedAt).length,\n failed: [...Object.entries(snapshot.features), ...Object.entries(snapshot.flows)]\n .filter(([, e]) => e.verificationFailed)\n .map(([name]) => name),\n },\n };\n const counts = await Promise.all(\n [...hashes].map(async (hash) => (hash === headHash ? 0 : countCommitsBehind(root, hash))),\n );\n const knownCounts = counts.filter((n): n is number => n !== null);\n report.commitsBehind = knownCounts.length ? Math.max(...knownCounts) : null;\n\n const check = (\n name: string,\n files: string[],\n hash: string,\n staleEntries: Record<string, string[]>,\n freshness: Record<string, Freshness>,\n ) => {\n const changes = changesByHash.get(hash);\n const committedHits = changes ? matchingPaths(files, touchedPaths(changes)) : [];\n if (committedHits.length) staleEntries[name] = committedHits;\n const localHits = matchingPaths(files, workingTree.changedFiles);\n freshness[name] =\n files.length === 0 || changes === null || changes === undefined || !workingTree.available\n ? \"unknown\"\n : committedHits.length ||\n localHits.length ||\n files.some((f) => report.ghostFiles.includes(f))\n ? \"changed\"\n : \"current\";\n };\n for (const [name, feature] of Object.entries(snapshot.features)) {\n check(\n name,\n [...feature.files, ...(feature.tests ?? [])],\n hashFor(feature),\n report.staleFeatures,\n report.featureFreshness!,\n );\n }\n for (const [name, flow] of Object.entries(snapshot.flows)) {\n check(name, flow.chain, hashFor(flow), report.staleFlows, report.flowFreshness!);\n }\n\n const allChanges = [...changesByHash.values()].flatMap((changes) => changes ?? []);\n report.changedFiles = [...new Set(allChanges.map((c) => c.path))].sort();\n // Complete coverage, including omissions from a map saved at HEAD. Untracked\n // files remain in workingTree and never change the committed-drift exit code.\n const sourceFiles = new Set(await listSourceFiles(root));\n let committedFiles: Set<string> = new Set();\n try {\n const { stdout } = await execGit([\"ls-tree\", \"-r\", \"--name-only\", \"-z\", \"HEAD\"], {\n cwd: root,\n maxBuffer: 50 * 1024 * 1024,\n });\n committedFiles = new Set(stdout.split(\"\\0\").filter(Boolean));\n } catch {\n report.historyAvailable = false;\n report.stale = true;\n }\n report.unmappedFiles = [...sourceFiles]\n .filter((f) => committedFiles.has(f) && !mappedFiles.has(f))\n .sort();\n const renames = new Map<string, { from: string; to: string }>();\n for (const change of allChanges) {\n if (change.status === \"renamed\" && change.previousPath)\n renames.set(`${change.previousPath}\\0${change.path}`, {\n from: change.previousPath,\n to: change.path,\n });\n }\n report.renames = [...renames.values()];\n const changedMapped = new Set([\n ...Object.values(report.staleFeatures).flat(),\n ...Object.values(report.staleFlows).flat(),\n ]);\n // A locally deleted file is a live-edit warning, not committed map drift.\n const committedGhosts = report.ghostFiles.filter((f) => !workingTree.changedFiles.includes(f));\n report.stale ||=\n changedMapped.size > 0 || report.unmappedFiles.length > 0 || committedGhosts.length > 0;\n if (!report.historyAvailable) report.recommendation = \"full-rebuild\";\n else if (!report.stale) report.recommendation = \"up-to-date\";\n else\n report.recommendation =\n changedMapped.size >= FULL_REBUILD_MIN_CHANGED_MAPPED_FILES &&\n changedMapped.size / Math.max(1, mappedFiles.size) > FULL_REBUILD_FRACTION\n ? \"full-rebuild\"\n : \"incremental\";\n return report;\n}\n","import { runHook } from \"./hook.js\";\nimport type { HookEnv } from \"./hook.js\";\n\nexport const USAGE = `Usage: mason-hook [--print-config | --help]\n\nClaude Code PostToolUse hook: when the session reads or edits a file that a\nMason decision record anchors, the record is injected into the model's\ncontext. Deterministic lookup, no LLM call; silent when nothing matches.\n\nReads the hook JSON on stdin and prints the hook output JSON on stdout.\nRegister it via .claude/settings.json (committed to the repo, so the whole\nteam gets the same rail):\n\n mason-hook --print-config Print the settings.json hooks block\n\nRepeat injections are deduped per session; state lives in the OS temp dir.`;\n\nexport const SETTINGS_CONFIG = {\n hooks: {\n PostToolUse: [\n {\n matcher: \"Read|Edit|Write\",\n hooks: [\n {\n type: \"command\",\n command: \"npx -y -p mason-context mason-hook\",\n timeout: 10,\n },\n ],\n },\n ],\n },\n};\n\nexport interface HookCliIo {\n out: (line: string) => void;\n err: (line: string) => void;\n}\n\nexport async function runHookCli(\n argv: string[],\n stdinText: string,\n io: HookCliIo = {\n out: (line) => process.stdout.write(`${line}\\n`),\n err: (line) => process.stderr.write(`${line}\\n`),\n },\n env: HookEnv = {},\n): Promise<number> {\n if (argv.includes(\"--help\") || argv.includes(\"-h\")) {\n io.out(USAGE);\n return 0;\n }\n if (argv.includes(\"--print-config\")) {\n io.out(JSON.stringify(SETTINGS_CONFIG, null, 2));\n return 0;\n }\n\n // A hook must never disrupt the session: any failure path is a silent\n // success with no output.\n try {\n const output = await runHook(stdinText, env);\n if (output !== null) io.out(output);\n } catch {\n // Silent by design.\n }\n return 0;\n}\n","import { runHookCli } from \"../src/hook/cli.js\";\n\nasync function readStdin(): Promise<string> {\n if (process.stdin.isTTY) return \"\";\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n const value: unknown = chunk;\n if (typeof value === \"string\" || value instanceof Uint8Array) chunks.push(Buffer.from(value));\n else throw new Error(\"Unsupported stdin chunk.\");\n }\n return Buffer.concat(chunks).toString(\"utf-8\");\n}\n\nconst argv = process.argv.slice(2);\nconst informational = argv.some((arg) => [\"--help\", \"-h\", \"--print-config\"].includes(arg));\n\n(informational ? Promise.resolve(\"\") : readStdin())\n .then((stdinText) => runHookCli(argv, stdinText))\n .then((code) => process.exit(code))\n .catch(() => process.exit(0));\n"],"mappings":"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","names":["z","fs","path","os","fg","AsyncLocalStorage","path","path","path","raw","ALL_CHECKS","AsyncLocalStorage","path","fs","constants","fg","fs","path","fs","path","fg","fs","constants","fs","path","createHash","z","path","z","z","fs","z","path","repoPath","z","path","z","createHash","z","createHash","z","fs","createHash","path","fs","path","path","hits","z","fs","path","os","createHash","argv"]}
|
|
1
|
+
{"version":3,"sources":["../src/audit/types.ts","../src/hook/hook.ts","../src/utils/validation.ts","../src/audit/inspection.ts","../src/utils/git-read.ts","../src/utils/profile.ts","../src/utils/files.ts","../src/utils/paths.ts","../src/decisions/anchors.ts","../src/utils/storage.ts","../src/decisions/decisions.ts","../src/snapshot/snapshot.ts","../src/utils/store-lock.ts","../src/context/lexical.ts","../src/decisions/provenance.ts","../src/context/trust.ts","../src/decisions/drift.ts","../src/drift/drift.ts","../src/decisions/excerpts.ts","../src/hook/cli.ts","../bin/mason-hook.ts"],"sourcesContent":["import type { decisionProvenance } from \"../decisions/provenance.js\";\n\nexport type IssueType = \"deleted-reference\" | \"new-module\" | \"stale-count\" | \"dead-command\";\n\nexport type AdvisoryType = \"deps-changed\" | \"decision-anchor-drift\" | IssueType;\n\nexport type CheckName = IssueType | AdvisoryType;\n\nexport const ALL_CHECKS: CheckName[] = [\n \"deleted-reference\",\n \"new-module\",\n \"stale-count\",\n \"dead-command\",\n \"deps-changed\",\n \"decision-anchor-drift\",\n];\n\n/**\n * \"certain\" — the claim is provably false (a tracked path is gone, a computed\n * count differs, a script is absent from a resolved manifest). \"likely\" is\n * retained for older reports. Inferred scope and speculative omissions are\n * advisory; even a certain mismatch needs semantic review before repair.\n */\nexport type Confidence = \"certain\" | \"likely\";\n\nexport interface CommitRef {\n hash: string;\n date: string;\n subject: string;\n}\n\nexport interface DocAnchor {\n /** Repo-relative doc path, e.g. \"CLAUDE.md\" or \".claude/CLAUDE.md\". */\n doc: string;\n /** 1-based line of the claim; null for doc-level issues (new-module). */\n line: number | null;\n /** The claim exactly as written, e.g. \"src/utils/logger.ts\". */\n excerpt: string | null;\n}\n\nexport interface ClaimScope {\n basis: \"repository\" | \"document\" | \"document-link\" | \"explicit\";\n directory: string;\n /** Exact repository-relative paths/manifests considered by the check. */\n candidates: string[];\n}\n\nexport type Evidence =\n | {\n kind: \"missing-path\";\n claimed: string;\n renamedTo: string | null;\n deletedInCommit: CommitRef | null;\n everTracked: boolean;\n parentDirExists: boolean;\n resolvedPath?: string;\n scope?: ClaimScope;\n }\n | {\n kind: \"unmentioned-dir\";\n dir: string;\n sourceFileCount: number;\n firstCommit: CommitRef | null;\n checkedDocs: string[];\n }\n | {\n kind: \"count-mismatch\";\n claimed: number;\n actual: number;\n unit: string;\n /** Where the actual count came from, e.g. \"package.json workspaces\". */\n countedFrom: string;\n members: string[];\n }\n | {\n kind: \"missing-script\";\n scriptName: string;\n invocation: string;\n manifestsChecked: string[];\n availableScripts: string[];\n scope?: ClaimScope;\n }\n | {\n kind: \"doc-behind-manifests\";\n docLastCommit: CommitRef;\n manifestCommits: Array<CommitRef & { files: string[] }>;\n totalCommits: number;\n matches?: import(\"./dependency-claims.js\").DependencyMatch[];\n matchingIncomplete?: boolean;\n }\n | {\n kind: \"decision-anchor\";\n provenance?: ReturnType<typeof decisionProvenance>;\n decisionId: string;\n title: string;\n changedFiles: string[];\n refreshedHash: string;\n };\n\nexport interface AuditIssue {\n type: IssueType;\n message: string;\n anchor: DocAnchor;\n confidence: Confidence;\n evidence: Evidence;\n}\n\n/**\n * Advisories require judgment and never drive the ordinary audit exit code.\n * Historical advisories require a separate review. Explicitly recheckable\n * candidates can cease to be detected without asserting semantic approval.\n */\nexport interface AuditAdvisory {\n type: AdvisoryType;\n message: string;\n anchor: DocAnchor;\n evidence: Evidence;\n resolution?: \"recheck\" | \"informational\";\n}\n\nexport interface AuditDocInfo {\n path: string;\n scope?: string;\n kind?: \"instructions\" | \"readme\" | \"guide\";\n lastCommit: CommitRef | null;\n /** Uncommitted edits present — deps-changed is suppressed for dirty docs. */\n dirty: boolean;\n lineCount: number;\n}\n\nexport interface AuditReport {\n /** Additive-only schema — this output is a CI contract. */\n version: 1;\n root: string;\n gitAvailable: boolean;\n /** Commit and exact check scope used by this run. */\n headHash?: string;\n checksRun?: CheckName[];\n docs: AuditDocInfo[];\n /** Whether .mason/decisions/ existed and was checked. */\n decisionsChecked: boolean;\n /** Drive exit code 1. */\n issues: AuditIssue[];\n /** Never drive the exit code. */\n advisories: AuditAdvisory[];\n /** Original committed evidence retained while local doc edits suppress reporting. */\n suppressedAdvisories?: AuditAdvisory[];\n skippedChecks: Array<{ check: string; reason: string; doc?: string }>;\n clean: boolean;\n excludedChecks?: Array<{ check: CheckName; doc: string }>;\n /** Additive assessment status; original advisory evidence remains intact. */\n advisoryReviews?: import(\"./advisory-review.js\").AdvisoryReviewSummary[];\n}\n\nexport interface PathClaim {\n path: string;\n line: number;\n excerpt: string;\n /** Markdown links resolve against the physical document directory. */\n relativeTo?: \"document\";\n}\n\nexport interface CountClaim {\n count: number;\n unit: string;\n line: number;\n excerpt: string;\n}\n\nexport interface CommandClaim {\n scriptName: string;\n invocation: string;\n line: number;\n excerpt: string;\n /** A literal cd/directory flag, relative to the document's assumed cwd. */\n directory?: string;\n scopeUnknown?: string;\n}\n\nexport interface DocClaims {\n paths: PathClaim[];\n counts: CountClaim[];\n commands: CommandClaim[];\n}\n","import { z } from \"zod\";\nimport { isStringArray } from \"../utils/validation.js\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport { loadDecisionStore } from \"../decisions/decisions.js\";\nimport { decisionAnchors, decisionKnowledge, DECISION_GUIDANCE } from \"../decisions/provenance.js\";\nimport { anchorMatches } from \"../utils/paths.js\";\nimport { createHash } from \"node:crypto\";\nimport type { Freshness } from \"../context/trust.js\";\nimport type { DecisionRecord } from \"../decisions/decisions.js\";\nimport { computeDecisionDrift, type DecisionDriftReport } from \"../decisions/drift.js\";\n\n/** More than a few constraints per fire is noise, not context. */\nconst MAX_INJECTED_DECISIONS = 3;\n/** Walk-up bound when locating the Mason root from an edited file. */\nconst MAX_WALK_UP = 30;\n\nconst SUPPORTED_TOOLS = new Set([\"Read\", \"Edit\", \"Write\"]);\n\nexport interface HookStdin {\n session_id?: string;\n agent_id?: string;\n cwd?: string;\n hook_event_name?: string;\n tool_name?: string;\n tool_input?: { file_path?: string };\n}\n\nconst hookStdinSchema = z.object({\n session_id: z.string().optional(),\n agent_id: z.string().optional(),\n cwd: z.string().optional(),\n hook_event_name: z.string().optional(),\n tool_name: z.string().optional(),\n tool_input: z.object({ file_path: z.string().optional() }).optional(),\n});\n\nexport interface HookEnv {\n /** Override for tests; defaults to os.tmpdir(). */\n stateDir?: string;\n /** Standard lifecycle integration confines delivery to its active checkout. */\n rootDir?: string;\n /** Standalone hooks retain PostToolUse; the lifecycle adapter can deliver before edits. */\n hookEventName?: \"PreToolUse\" | \"PostToolUse\";\n}\n\nasync function exists(p: string): Promise<boolean> {\n try {\n await fs.access(p);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Locate the nearest ancestor holding a .mason/decisions store. Stops at the\n * first .git boundary — a repo without Mason must stay silent, not borrow a\n * parent directory's decisions.\n */\nasync function findMasonRoot(startDir: string): Promise<string | null> {\n let dir = startDir;\n for (let i = 0; i < MAX_WALK_UP; i++) {\n if (await exists(path.join(dir, \".mason\", \"decisions\"))) return dir;\n if (await exists(path.join(dir, \".git\"))) return null;\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n return null;\n}\n\nfunction anchorsCover(record: DecisionRecord, relPath: string): boolean {\n return decisionAnchors(record).some((anchor) => anchorMatches(anchor, relPath));\n}\n\nfunction exactAnchor(record: DecisionRecord, relPath: string): boolean {\n return decisionAnchors(record).some((a) => a.replace(/\\/+$/, \"\") === relPath);\n}\n\nfunction stateKey(input: HookStdin): string {\n const raw = `${input.session_id ?? \"nosession\"}${input.agent_id ? `-${input.agent_id}` : \"\"}`;\n return raw.replace(/[^A-Za-z0-9_-]/g, \"\").slice(0, 120) || \"nosession\";\n}\n\nasync function loadInjected(stateFile: string): Promise<Set<string>> {\n try {\n const parsed: unknown = JSON.parse(await fs.readFile(stateFile, \"utf-8\"));\n return new Set(isStringArray(parsed) ? parsed : []);\n } catch {\n return new Set();\n }\n}\n\nfunction formatContext(\n relPath: string,\n records: DecisionRecord[],\n drift: DecisionDriftReport,\n): string {\n const lines: string[] = [];\n lines.push(\n `Mason: decision knowledge relevant to ${relPath} or updated since this session saw it. This replaces earlier guidance for the same decision id. ${DECISION_GUIDANCE} Retired or superseded records are no longer active. Use Mason tools to record decision changes and source inspections; do not edit the JSON in .mason/decisions/ directly.`,\n );\n const append = (\n id: string,\n knowledge: ReturnType<typeof decisionKnowledge>,\n label: string,\n freshness: Freshness,\n ) => {\n const stale =\n freshness === \"current\"\n ? \"\"\n : freshness === \"changed\"\n ? \" [recorded against changed files – verify against current code before relying on it]\"\n : \" [freshness unknown – verify against current code before relying on it]\";\n lines.push(\n `- [${label}] [${knowledge.category}] ${knowledge.title}: ${knowledge.body} (id: ${id}; revision: ${knowledge.revision}; anchors: ${knowledge.files.join(\", \")}; owner: ${knowledge.owner ?? \"unknown\"}; sources: ${\n knowledge.sources\n .slice(0, 2)\n .map((s) => s.reference)\n .join(\", \") || \"unrecorded\"\n })${stale}`,\n );\n };\n for (const record of records) {\n const knowledge = decisionKnowledge(\n record,\n drift.freshness?.[record.id] ?? \"unknown\",\n drift.pendingProposals?.[record.id]?.freshness ?? \"unknown\",\n );\n append(\n record.id,\n knowledge,\n record.status === \"active\" ? knowledge.approval : record.status,\n knowledge.trust.freshness,\n );\n if (knowledge.pendingProposal)\n append(\n record.id,\n knowledge.pendingProposal,\n \"proposed\",\n knowledge.pendingProposal.trust.freshness,\n );\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * The push half of Mason's memory: when an agent touches a file that a\n * decision record anchors, the record is injected into the session via the\n * PostToolUse hook contract — deterministically, without relying on the\n * model deciding to ask. Returns the hook's stdout JSON, or null for\n * \"stay silent\" (no store, no match, already injected, malformed input —\n * a hook must never disrupt the session).\n */\nexport async function runHook(stdinText: string, env: HookEnv = {}): Promise<string | null> {\n let input: HookStdin;\n try {\n input = hookStdinSchema.parse(JSON.parse(stdinText));\n } catch {\n return null;\n }\n\n if (!input || typeof input !== \"object\") return null;\n if (input.tool_name && !SUPPORTED_TOOLS.has(input.tool_name)) return null;\n const filePath = input.tool_input?.file_path;\n if (!filePath || typeof filePath !== \"string\") return null;\n\n const absPath = path.isAbsolute(filePath)\n ? filePath\n : path.resolve(input.cwd ?? process.cwd(), filePath);\n const root = await findMasonRoot(path.dirname(absPath));\n if (!root) return null;\n if (env.rootDir && (await fs.realpath(root)) !== (await fs.realpath(env.rootDir))) return null;\n const relPath = path.relative(root, absPath).split(path.sep).join(\"/\");\n if (relPath.startsWith(\"..\")) return null;\n\n const { records } = await loadDecisionStore(root);\n\n const stateDir = env.stateDir ?? os.tmpdir();\n const stateFile = path.join(\n stateDir,\n `mason-hook-${createHash(\"sha256\").update(root).digest(\"hex\").slice(0, 12)}-${stateKey(input)}.json`,\n );\n const injected = await loadInjected(stateFile);\n const recordKey = (record: DecisionRecord) =>\n `${record.id}:${createHash(\"sha256\").update(JSON.stringify(record)).digest(\"hex\")}`;\n const previouslyInjected = (record: DecisionRecord) =>\n [...injected].some((key) => key === record.id || key.startsWith(`${record.id}:`));\n // Re-inject revised/accepted records and withdraw previously injected records\n // that were retired during this session. Untouched archived records stay dark.\n // Also update previously seen records after their anchors move. Otherwise an\n // acceptance or retirement could leave obsolete guidance in the session.\n const fresh = records.filter(\n (r) =>\n !injected.has(recordKey(r)) &&\n (previouslyInjected(r) || (r.status === \"active\" && anchorsCover(r, relPath))),\n );\n if (fresh.length === 0) return null;\n\n // Exact-file anchors outrank directory-prefix ones; newest knowledge wins ties.\n fresh.sort((a, b) => {\n const withdrawn = Number(b.status !== \"active\") - Number(a.status !== \"active\");\n if (withdrawn) return withdrawn;\n const exactDiff = Number(exactAnchor(b, relPath)) - Number(exactAnchor(a, relPath));\n if (exactDiff !== 0) return exactDiff;\n return b.updatedAt.localeCompare(a.updatedAt);\n });\n const selected = fresh.slice(0, MAX_INJECTED_DECISIONS);\n\n const drift = await computeDecisionDrift(root, selected);\n\n for (const record of selected) injected.add(recordKey(record));\n try {\n await fs.writeFile(stateFile, JSON.stringify([...injected]), \"utf-8\");\n } catch {\n // Dedupe state is a convenience; losing it must not block injection.\n }\n\n return JSON.stringify({\n hookSpecificOutput: {\n hookEventName: env.hookEventName ?? \"PostToolUse\",\n additionalContext: formatContext(relPath, selected, drift),\n },\n });\n}\n","/** Narrow external objects without allowing arrays or null to masquerade as records. */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nexport function isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item: unknown) => typeof item === \"string\");\n}\n\nexport function hasErrorCode(error: unknown, code: string): boolean {\n return isRecord(error) && error.code === code;\n}\n","import fg from \"fast-glob\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport path from \"node:path\";\nimport { execGit, type GitReadOptions } from \"../utils/git-read.js\";\nimport { loadProjectConfig, SOURCE_EXTENSIONS } from \"../utils/files.js\";\n\ninterface Inspection {\n root: string;\n reads: Map<string, Promise<unknown>>;\n}\nconst inspections = new AsyncLocalStorage<Inspection>();\n\n/** Always start fresh. In particular, final validation must not reuse initial reads. */\nexport function withRepositoryInspection<T>(root: string, run: () => Promise<T>): Promise<T> {\n return inspections.run({ root: path.resolve(root), reads: new Map() }, run);\n}\n\n/** Read observations are shared only inside an explicit inspection, never across hooks. */\nexport function inspectionRead<T>(root: string, key: string, read: () => Promise<T>): Promise<T> {\n const inspection = inspections.getStore();\n if (!inspection || inspection.root !== path.resolve(root)) return read();\n if (!inspection.reads.has(key)) {\n const pending = read();\n inspection.reads.set(key, pending);\n // A failed query must be retried, not turned into a reusable unavailable result.\n void pending.catch(() => {\n if (inspection.reads.get(key) === pending) inspection.reads.delete(key);\n });\n }\n return inspection.reads.get(key)! as Promise<T>;\n}\n\n/** Tool writes invalidate their cached observations within the active inspection. */\nexport function invalidateInspectionReads(root: string, prefix: string): void {\n const inspection = inspections.getStore();\n if (!inspection || inspection.root !== path.resolve(root)) return;\n for (const key of inspection.reads.keys())\n if (key.startsWith(prefix)) inspection.reads.delete(key);\n}\n\n/** Exact arguments and limits preserve each history query's semantics. */\nexport function inspectionGit(args: string[], options: GitReadOptions) {\n return inspectionRead(options.cwd, \"git:\" + JSON.stringify([args, options]), () =>\n execGit(args, options),\n );\n}\n\nconst sourceSpecs = SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`);\nconst docSpecs = [\n \":(icase,glob)**/readme.md\",\n \":(icase,glob)**/agents.md\",\n \":(icase,glob)**/claude.md\",\n];\nconst sourceSuffixes = new Set(SOURCE_EXTENSIONS.map((extension) => \".\" + extension));\n\n/** Git still prunes ignored trees; unrelated generated files are never inventoried. */\nexport async function auditGitPaths(\n root: string,\n kind: \"documents\" | \"sources\",\n): Promise<string[]> {\n const custom = (await loadProjectConfig(root)).audit?.include ?? [];\n const shared = inspections.getStore()?.root === path.resolve(root) && !custom.length;\n const expanded = fg.generateTasks(custom).flatMap((task) => task.positive);\n if (expanded.length > 1000)\n throw new Error(\"Audit include patterns expand to more than 1,000 globs\");\n const documentSpecs = [...docSpecs, ...expanded.map((pattern) => `:(glob)${pattern}`)];\n const specs = shared\n ? [...documentSpecs, ...sourceSpecs]\n : kind === \"documents\"\n ? documentSpecs\n : sourceSpecs;\n const limit = kind === \"documents\" ? 16 * 1024 * 1024 : 32 * 1024 * 1024;\n const { stdout } = await inspectionGit(\n [\"ls-files\", \"-z\", \"--cached\", \"--others\", \"--exclude-standard\", \"--\", ...specs],\n { cwd: root, maxBuffer: shared ? 48 * 1024 * 1024 : limit, timeout: 10000 },\n );\n const paths = [...new Set(stdout.split(\"\\0\").filter(Boolean))].filter((file) =>\n kind === \"documents\"\n ? custom.length > 0 || /^(readme|agents|claude)\\.md$/i.test(path.posix.basename(file))\n : // Git's *.ts also matches a file named .ts, unlike path.extname().\n sourceSuffixes.has(file.slice(file.lastIndexOf(\".\"))),\n );\n if (Buffer.byteLength(paths.join(\"\\0\")) > limit)\n throw new Error(\"Git \" + kind + \" inventory exceeds its bounded read limit.\");\n return paths.sort();\n}\n","import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { profilePhase } from \"./profile.js\";\n\nconst execute = promisify(execFile);\nconst commands = new Set([\n \"rev-parse\",\n \"symbolic-ref\",\n \"for-each-ref\",\n \"ls-files\",\n \"check-ignore\",\n \"status\",\n \"log\",\n \"diff\",\n \"ls-tree\",\n \"show\",\n \"merge-base\",\n]);\nexport interface GitReadOptions {\n cwd: string;\n maxBuffer?: number;\n timeout?: number;\n input?: string;\n}\n\n/** Fixed command labels only: profiling never includes arguments or repository paths. */\nexport function execGit(args: string[], options: GitReadOptions) {\n const command = args.find((arg) => !arg.startsWith(\"-\")) ?? \"other\";\n return profilePhase(\"git.\" + (commands.has(command) ? command : \"other\"), async () => {\n const { input, ...processOptions } = options;\n const operation = execute(\"git\", args, processOptions);\n if (input !== undefined) {\n operation.child.stdin!.on(\"error\", () => {\n /* The child exit reports a failed query. */\n });\n operation.child.stdin!.end(input);\n }\n return operation;\n });\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\";\n\ntype Timing = { calls: number; totalMs: number };\nconst profiles = new AsyncLocalStorage<Record<string, Timing>>();\n\n/** Opt-in, invocation-local timings. Labels are fixed in source; no repository data. */\nexport async function profilePhase<T>(label: string, run: () => Promise<T>): Promise<T> {\n const phases = profiles.getStore();\n if (!phases) return run();\n const start = performance.now();\n try {\n return await run();\n } finally {\n const timing = (phases[label] ??= { calls: 0, totalMs: 0 });\n timing.calls++;\n timing.totalMs += performance.now() - start;\n }\n}\n\nexport async function withProfile<T>(\n run: () => Promise<T>,\n emit: (json: string) => void,\n): Promise<T> {\n const phases: Record<string, Timing> = {};\n const start = performance.now();\n return profiles.run(phases, async () => {\n try {\n return await run();\n } finally {\n emit(\n JSON.stringify({\n kind: \"mason-profile\",\n version: 1,\n totalMs: performance.now() - start,\n phases,\n }),\n );\n }\n });\n}\n","import { isRecord, isStringArray } from \"./validation.js\";\nimport { execGit } from \"./git-read.js\";\nimport fs from \"node:fs/promises\";\nimport { constants } from \"node:fs\";\nimport path from \"node:path\";\nimport fg from \"fast-glob\";\nimport { isWithinRoot, normalizeRepoPath } from \"./paths.js\";\n\nexport const SOURCE_EXTENSIONS = [\n \"ts\",\n \"tsx\",\n \"js\",\n \"jsx\",\n \"mts\",\n \"cts\",\n \"mjs\",\n \"cjs\",\n \"vue\",\n \"svelte\",\n \"kt\",\n \"kts\",\n \"java\",\n \"py\",\n \"go\",\n \"rs\",\n \"swift\",\n \"rb\",\n \"cs\",\n \"cpp\",\n \"c\",\n \"h\",\n \"hpp\",\n \"dart\",\n \"php\",\n];\nexport const SOURCE_GLOB = `**/*.{${SOURCE_EXTENSIONS.join(\",\")}}`;\nexport const SOURCE_IGNORE = [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/build/**\",\n \"**/.gradle/**\",\n \"**/target/**\",\n \"**/.git/**\",\n \"**/.mason/**\",\n \"**/vendor/**\",\n \"**/__pycache__/**\",\n \"**/venv/**\",\n \"**/.venv/**\",\n \"**/*.min.*\",\n \"**/*.map\",\n \"**/*.lock\",\n \"**/generated/**\",\n \"**/*.generated.*\",\n \"**/R.java\",\n \"**/BuildConfig.java\",\n \"**/package-lock.json\",\n \"**/yarn.lock\",\n \"**/pnpm-lock.yaml\",\n];\nexport const MAX_SOURCE_BYTES = 1024 * 1024;\nexport interface ProjectConfig {\n patterns?: string[];\n alwaysInclude?: string[];\n ignore?: string[];\n audit?: {\n include?: string[];\n exclude?: Partial<Record<import(\"../audit/types.js\").CheckName, string[]>>;\n };\n}\nexport interface SourceFile {\n path: string;\n content: string;\n totalLines: number;\n}\n\nexport function isSensitiveFile(file: string): boolean {\n return file\n .split(/[\\\\/]/)\n .some((part) =>\n /^(?:\\.env(?:\\..*)?|id_rsa.*|id_ed25519.*)$|\\.(?:pem|key|p12|pfx|jks|keystore)$|credentials\\.|secret|^local\\.properties$/i.test(\n part,\n ),\n );\n}\n\n/** Bound reads even if a file grows after stat. Only read regular files. */\nexport async function readBoundedFile(file: string, maxBytes: number): Promise<string | null> {\n // Do not block on a FIFO or follow a symlink substituted after resolution.\n const handle = await fs.open(\n file,\n constants.O_RDONLY | constants.O_NONBLOCK | constants.O_NOFOLLOW,\n );\n try {\n const stat = await handle.stat();\n if (!stat.isFile() || stat.size > maxBytes) return null;\n const buffer = Buffer.alloc(Math.min(maxBytes + 1, stat.size + 1));\n let bytes = 0;\n while (bytes < buffer.length) {\n const result = await handle.read(buffer, bytes, buffer.length - bytes, null);\n if (result.bytesRead === 0) break;\n bytes += result.bytesRead;\n }\n return bytes === buffer.length ? null : buffer.subarray(0, bytes).toString(\"utf8\");\n } finally {\n await handle.close();\n }\n}\n\nexport async function loadProjectConfig(root: string): Promise<ProjectConfig> {\n try {\n const canonicalRoot = await fs.realpath(root);\n const configPath = await fs.realpath(path.join(root, \".mason/config.json\"));\n if (!isWithinRoot(canonicalRoot, configPath))\n throw new Error(\"Project configuration resolves outside the repository\");\n const raw = await readBoundedFile(configPath, 64 * 1024);\n if (raw === null)\n throw new Error(\"Project configuration is not a regular file or exceeds 64 KiB\");\n const value: unknown = JSON.parse(raw);\n if (!isRecord(value)) throw new Error(\"Expected a configuration object\");\n const config: ProjectConfig = {};\n for (const key of [\"patterns\", \"alwaysInclude\", \"ignore\"] as const) {\n if (value[key] === undefined) continue;\n if (!isStringArray(value[key])) {\n throw new Error(`Configuration ${key} must be an array of strings`);\n }\n config[key] = value[key];\n }\n if (value.audit !== undefined) {\n const audit = value.audit;\n if (!isRecord(audit)) throw new Error(\"Configuration audit must be an object\");\n const patterns = (raw: unknown): string[] => {\n if (\n !isStringArray(raw) ||\n raw.length > 100 ||\n !raw.every(\n (s) =>\n typeof s === \"string\" &&\n normalizeRepoPath(s) === s &&\n !s.startsWith(\"!\") &&\n !/[\\x00-\\x1f]/.test(s),\n )\n ) {\n throw new Error(\n \"Audit patterns must be at most 100 repository-relative globs without traversal or negation\",\n );\n }\n return raw;\n };\n config.audit = {};\n if (audit.include !== undefined) config.audit.include = patterns(audit.include);\n if (audit.exclude !== undefined) {\n if (!isRecord(audit.exclude))\n throw new Error(\"audit.exclude must map check names to patterns\");\n const { ALL_CHECKS } = await import(\"../audit/types.js\");\n config.audit.exclude = {};\n for (const [check, globs] of Object.entries(audit.exclude)) {\n if (!ALL_CHECKS.includes(check as (typeof ALL_CHECKS)[number]))\n throw new Error(\"Unknown audit check: \" + check);\n config.audit.exclude[check as (typeof ALL_CHECKS)[number]] = patterns(globs);\n }\n }\n }\n return config;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return {};\n throw new Error(\n `Cannot apply project file policy: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n}\n\n/** Scoped to one operation, so a later tool call sees newly edited files/ignores. */\nexport async function createFileAccess(rootDir: string) {\n const root = path.resolve(rootDir);\n const canonicalRoot = await fs.realpath(root).catch(() => root);\n const config = await loadProjectConfig(root);\n const ignore = [...SOURCE_IGNORE, ...(config.ignore ?? [])];\n let gitFiles: Set<string> | null = null;\n try {\n const { stdout } = await execGit(\n [\"ls-files\", \"-z\", \"--cached\", \"--others\", \"--exclude-standard\"],\n { cwd: root, maxBuffer: 50 * 1024 * 1024 },\n );\n gitFiles = new Set(stdout.split(\"\\0\").filter(Boolean));\n } catch {\n // File-system projects are supported. Fail closed if this IS a Git repo.\n let inGit = false;\n try {\n await execGit([\"rev-parse\", \"--git-dir\"], { cwd: root });\n inGit = true;\n } catch {\n /* no Git */\n }\n if (inGit) throw new Error(\"Cannot enumerate Git files safely\");\n }\n\n async function resolve(file: string): Promise<string | null> {\n const relative = normalizeRepoPath(file);\n if (!relative || isSensitiveFile(relative) || (gitFiles && !gitFiles.has(relative)))\n return null;\n const candidate = path.join(root, relative);\n try {\n const real = await fs.realpath(candidate);\n if (!isWithinRoot(canonicalRoot, real) || isSensitiveFile(path.relative(canonicalRoot, real)))\n return null;\n const stat = await fs.stat(real);\n if (!stat.isFile() || stat.size > MAX_SOURCE_BYTES) return null;\n // A symlink must not bypass the target's ignore policy either.\n if (gitFiles && !gitFiles.has(path.relative(canonicalRoot, real).split(path.sep).join(\"/\")))\n return null;\n return real;\n } catch {\n return null;\n }\n }\n\n async function list(\n patterns: string | string[] = SOURCE_GLOB,\n options: { deep?: number; dot?: boolean } = {},\n ): Promise<string[]> {\n const found = await fg(patterns, { cwd: root, ignore, followSymbolicLinks: false, ...options });\n const safe = await Promise.all(found.map(async (f) => ((await resolve(f)) ? f : null)));\n return safe.filter((f): f is string => f !== null).sort();\n }\n\n async function read(file: string): Promise<SourceFile | null> {\n const relative = normalizeRepoPath(file);\n if (!relative) return null;\n const real = await resolve(relative);\n if (!real) return null;\n // Apply the same glob exclusions to explicit reads and symlink targets.\n for (const rel of new Set([\n relative,\n path.relative(canonicalRoot, real).split(path.sep).join(\"/\"),\n ])) {\n if (!(await fg(fg.escapePath(rel), { cwd: root, ignore, dot: true })).length) return null;\n }\n try {\n const content = await readBoundedFile(real, MAX_SOURCE_BYTES);\n return content === null\n ? null\n : { path: relative, content, totalLines: content.split(\"\\n\").length };\n } catch {\n return null;\n }\n }\n return { root, config, list, read, inventory: () => (gitFiles ? [...gitFiles].sort() : null) };\n}\n","import path from \"node:path\";\n\n/** One canonical representation for stored paths and decision anchors. */\nexport function normalizeRepoPath(value: string): string | null {\n const slash = value.replace(/\\\\/g, \"/\");\n if (!slash || slash.includes(\"\\0\") || path.posix.isAbsolute(slash) || /^[A-Za-z]:/.test(slash))\n return null;\n if (slash.split(\"/\").includes(\"..\")) return null;\n const normalized = path.posix.normalize(slash).replace(/\\/$/, \"\");\n return normalized === \".\" ? null : normalized;\n}\n\nexport function sanitizeRepoPaths(files: string[]): string[] {\n return [...new Set(files.map(normalizeRepoPath).filter((p): p is string => p !== null))];\n}\n\nexport function isWithinRoot(root: string, candidate: string): boolean {\n const relative = path.relative(root, candidate);\n return relative !== \"..\" && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);\n}\n\nexport function anchorMatches(anchor: string, file: string): boolean {\n const a = normalizeRepoPath(anchor);\n const f = normalizeRepoPath(file);\n return a !== null && f !== null && (a === f || f.startsWith(`${a}/`));\n}\n\nexport function matchingPaths(anchors: string[], files: Iterable<string>): string[] {\n return [...new Set(files)].filter((file) =>\n anchors.some((anchor) => anchorMatches(anchor, file)),\n );\n}\n\n/** Match an inventory to every scope in one pass through its path components. */\nexport function indexMatchingPaths(\n scopes: readonly (readonly string[])[],\n files: Iterable<string>,\n): string[][] {\n interface Node {\n children: Map<string, Node>;\n scopes: Set<number>;\n }\n const node = (): Node => ({ children: new Map(), scopes: new Set() });\n const root = node();\n const matches: string[][] = scopes.map(() => []);\n scopes.forEach((anchors, index) => {\n for (const anchor of anchors) {\n const normalized = normalizeRepoPath(anchor);\n if (!normalized) continue;\n let current = root;\n for (const part of normalized.split(\"/\")) {\n if (!current.children.has(part)) current.children.set(part, node());\n current = current.children.get(part)!;\n }\n current.scopes.add(index);\n }\n });\n for (const file of new Set(files)) {\n const normalized = normalizeRepoPath(file);\n if (!normalized) continue;\n const selected = new Set<number>();\n let current: Node | undefined = root;\n for (const part of normalized.split(\"/\")) {\n current = current.children.get(part);\n if (!current) break;\n for (const index of current.scopes) selected.add(index);\n }\n for (const index of selected) matches[index].push(file);\n }\n return matches;\n}\n","import { createHash } from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport { constants } from \"node:fs\";\nimport { z } from \"zod\";\nimport fg from \"fast-glob\";\nimport { inspectionGit, inspectionRead } from \"../audit/inspection.js\";\nimport { createFileAccess, MAX_SOURCE_BYTES } from \"../utils/files.js\";\nimport {\n anchorMatches,\n indexMatchingPaths,\n matchingPaths,\n normalizeRepoPath,\n} from \"../utils/paths.js\";\nimport { storePath } from \"../utils/storage.js\";\n\nconst repoPath = z.string().refine((p) => normalizeRepoPath(p) === p);\nexport const anchorCaptureSchema = z\n .object({\n anchors: z.array(repoPath),\n files: z.record(repoPath, z.string().regex(/^[a-f0-9]{64}$/)),\n complete: z.boolean(),\n })\n .refine(\n (capture) =>\n JSON.stringify(capture.anchors) === JSON.stringify([...new Set(capture.anchors)].sort()) &&\n matchingPaths(capture.anchors, Object.keys(capture.files)).length ===\n Object.keys(capture.files).length,\n \"Capture paths must belong to its normalized, sorted anchor scope\",\n );\nexport type AnchorCapture = z.infer<typeof anchorCaptureSchema>;\n\n/** Content evidence, not approval. Unreadable/excluded scopes never become current.\n * Batch independent scopes and share observations only inside one repository inspection.\n * Final validation creates a fresh inspection; standalone callers always read again.\n */\nexport async function captureAnchorScopes(\n root: string,\n scopes: string[][],\n): Promise<AnchorCapture[]> {\n const union = [...new Set(scopes.flat())].sort();\n let context: ReturnType<typeof prepareContext> | undefined;\n const prepareContext = async () => {\n const access = await inspectionRead(root, \"decision-anchor-access\", () =>\n createFileAccess(root),\n );\n const inventory = access.inventory();\n if (!inventory) throw new Error(\"Git anchor inventory unavailable\");\n // Ignored files under an anchor with Git-visible content sit outside its evidence, as\n // in Git. An anchor naming only ignored paths (.env, an ignored directory) stays\n // incomplete rather than capturing nothing.\n const visible = indexMatchingPaths(\n union.map((anchor) => [anchor]),\n inventory,\n );\n const hidden = new Set(union.filter((_, index) => !visible[index].length));\n const ignored = hidden.size\n ? (\n await inspectionGit(\n [\n \"ls-files\",\n \"-z\",\n \"--others\",\n \"--ignored\",\n \"--exclude-standard\",\n \"--\",\n ...[...hidden].map((anchor) => `:(literal)${anchor}`),\n ],\n { cwd: root, maxBuffer: 8 * 1024 * 1024 },\n )\n ).stdout\n .split(\"\\0\")\n .filter(Boolean)\n : [];\n const files = [...new Set([...inventory, ...ignored])].sort();\n // Apply file policy in one glob walk rather than one walk per anchor file.\n // storePath still rejects symlinked parents at each bounded byte read.\n const patterns = await Promise.all(\n union.map(async (anchor) => {\n const literal = fg.escapePath(anchor);\n try {\n return (await fs.lstat(await storePath(root, anchor))).isDirectory()\n ? `${literal}/**/*`\n : literal;\n } catch {\n return literal;\n }\n }),\n );\n const eligible = new Set(await access.list(patterns, { dot: true }));\n const indexed = indexMatchingPaths(scopes, files);\n const filesByScope = new Map(\n scopes.map((scope, index) => [JSON.stringify([...new Set(scope)].sort()), indexed[index]]),\n );\n return { filesByScope, eligible, hidden, ignored: new Set(ignored) };\n };\n return Promise.all(\n scopes.map((scope) => {\n const anchors = [...new Set(scope)].sort();\n return inspectionRead(\n root,\n \"decision-anchor-capture:\" + JSON.stringify(anchors),\n async () => {\n const capture: AnchorCapture = { anchors, files: {}, complete: true };\n if (!anchors.length) return capture;\n try {\n const { eligible, filesByScope, hidden, ignored } = await (context ??=\n prepareContext());\n // Another scope's hidden anchor can list ignored files under this scope's anchors.\n const files = filesByScope\n .get(JSON.stringify(anchors))!\n .filter(\n (file) =>\n !ignored.has(file) ||\n anchors.some((anchor) => hidden.has(anchor) && anchorMatches(anchor, file)),\n );\n if (files.length > 2000) return { ...capture, complete: false };\n for (const file of files) {\n const evidence = await inspectionRead(\n root,\n \"decision-anchor-file:\" + file,\n async () => {\n try {\n const absolute = await storePath(root, file);\n await fs.lstat(absolute); // Missing indexed files are deletions, not unreadable evidence.\n if (!eligible.has(file)) return { complete: false };\n const handle = await fs.open(\n absolute,\n constants.O_RDONLY | constants.O_NONBLOCK | constants.O_NOFOLLOW,\n );\n try {\n const stat = await handle.stat();\n if (!stat.isFile() || stat.size > MAX_SOURCE_BYTES)\n return { complete: false };\n const bytes = Buffer.alloc(Math.min(stat.size + 1, MAX_SOURCE_BYTES + 1));\n let size = 0;\n while (size < bytes.length) {\n const read = await handle.read(bytes, size, bytes.length - size, null);\n if (!read.bytesRead) break;\n size += read.bytesRead;\n }\n if (size === bytes.length) return { complete: false }; // Grew during capture.\n return {\n complete: true,\n hash: createHash(\"sha256\").update(bytes.subarray(0, size)).digest(\"hex\"),\n };\n } finally {\n await handle.close();\n }\n } catch (error) {\n return { complete: (error as NodeJS.ErrnoException).code === \"ENOENT\" };\n }\n },\n );\n if (!evidence.complete) capture.complete = false;\n if (evidence.hash) capture.files[file] = evidence.hash;\n }\n } catch {\n capture.complete = false;\n }\n return capture;\n },\n );\n }),\n );\n}\n\nexport async function captureAnchors(root: string, anchors: string[]): Promise<AnchorCapture> {\n return (await captureAnchorScopes(root, [anchors]))[0];\n}\n\nexport function changedCaptureFiles(before: AnchorCapture, after: AnchorCapture): string[] {\n return [...new Set([...Object.keys(before.files), ...Object.keys(after.files)])]\n .filter((file) => before.files[file] !== after.files[file])\n .sort();\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\nimport { normalizeRepoPath } from \"./paths.js\";\nimport { readBoundedFile } from \"./files.js\";\n\nexport interface StoreDiagnostic {\n path: string;\n message: string;\n}\n\n/** Metadata paths may not contain symlinks, including their parent directories. */\nexport async function storePath(\n root: string,\n relative: string,\n createParents = false,\n): Promise<string> {\n const normalized = normalizeRepoPath(relative);\n if (!normalized) throw new Error(`Invalid store path: ${relative}`);\n let current = await fs.realpath(root);\n const parts = normalized.split(\"/\");\n for (let i = 0; i < parts.length; i++) {\n current = path.join(current, parts[i]);\n let stat;\n try {\n stat = await fs.lstat(current);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n if (createParents && i < parts.length - 1) {\n try {\n await fs.mkdir(current);\n } catch (mkdirError) {\n if ((mkdirError as NodeJS.ErrnoException).code !== \"EEXIST\") throw mkdirError;\n }\n stat = await fs.lstat(current);\n }\n }\n if (stat?.isSymbolicLink()) throw new Error(`Symlink in store path: ${relative}`);\n }\n return current;\n}\n\nexport const STORE_MAX_BYTES = 10 * 1024 * 1024;\n\n/** A larger `maxBytes` lets a caller read an oversized store it can shrink. */\nexport async function readStoreJson(\n root: string,\n relative: string,\n maxBytes = STORE_MAX_BYTES,\n): Promise<unknown | null> {\n try {\n const file = await storePath(root, relative);\n const raw = await readBoundedFile(file, maxBytes);\n if (raw === null)\n throw new Error(`file is not regular or exceeds ${maxBytes / 1024 / 1024} MiB`);\n const parsed: unknown = JSON.parse(raw);\n if (parsed === null) throw new Error(\"expected a JSON object, received null\");\n return parsed;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw new Error(\n `Invalid Mason store ${relative}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n}\n\nexport async function writeStoreJson(\n root: string,\n relative: string,\n value: unknown,\n options: { compact?: boolean } = {},\n): Promise<void> {\n const payload = JSON.stringify(value, null, options.compact ? undefined : 2) + \"\\n\";\n if (Buffer.byteLength(payload) > STORE_MAX_BYTES) {\n throw new Error(`Mason store ${relative} exceeds 10 MiB`);\n }\n const file = await storePath(root, relative, true);\n const temporary = path.join(path.dirname(file), `.${path.basename(file)}.${randomUUID()}.tmp`);\n try {\n const handle = await fs.open(temporary, \"wx\", 0o600);\n try {\n await handle.writeFile(payload, \"utf8\");\n await handle.sync();\n } finally {\n await handle.close();\n }\n await fs.rename(temporary, file);\n } finally {\n await fs.rm(temporary, { force: true });\n }\n}\n","import { inspectionRead, invalidateInspectionReads } from \"../audit/inspection.js\";\nimport { readBoundedFile } from \"../utils/files.js\";\nimport { captureAnchors } from \"./anchors.js\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { createHash } from \"node:crypto\";\nimport { z } from \"zod\";\nimport { writeStoreJson, storePath, type StoreDiagnostic } from \"../utils/storage.js\";\nimport { sanitizeRepoPaths } from \"../utils/paths.js\";\nimport { getCurrentGitHash } from \"../snapshot/snapshot.js\";\nimport { jaccard, tokenSet } from \"../context/lexical.js\";\nimport {\n attributionSchema,\n decisionSchema,\n decisionContent,\n decisionApproval,\n effectiveDecision,\n importLegacy,\n type DecisionSource,\n type DecisionRecord,\n type ReviewedDecisionRecord,\n} from \"./provenance.js\";\nexport type { DecisionRecord } from \"./provenance.js\";\n\nexport type DecisionCategory = \"decision\" | \"gotcha\" | \"deprecation\" | \"convention\";\nexport type DecisionStatus = \"active\" | \"superseded\" | \"retired\";\n\nexport const TITLE_MAX_CHARS = 80;\nexport const BODY_RECOMMENDED_CHARS = 1500;\nexport const BODY_MAX_CHARS = 2500;\nexport const MAX_ACTIVE_DECISIONS = 150;\n\n// Share validation with MCP so rejected tool calls include the actual length.\nconst boundedText = (field: string, max: number, hint: string) =>\n z\n .string({\n errorMap: (issue, ctx) => ({\n message:\n issue.code === \"too_big\" && typeof ctx.data === \"string\"\n ? `${field} has ${ctx.data.length} characters; maximum is ${max} (${ctx.data.length - max} over). ${hint}`\n : ctx.defaultError,\n }),\n })\n .trim()\n .min(1, `${field} must be non-empty`)\n .max(max);\n\nexport const decisionTitleSchema = boundedText(\n \"title\",\n TITLE_MAX_CHARS,\n \"Tighten it to a specific headline.\",\n);\nexport const decisionBodySchema = boundedText(\n \"body\",\n BODY_MAX_CHARS,\n `Aim for ${BODY_RECOMMENDED_CHARS} characters or fewer. Preserve the rule, reason, and exceptions; put supporting references in sources.`,\n);\n\nconst DUPLICATE_JACCARD = 0.5;\nconst DUPLICATE_JACCARD_WITH_SHARED_FILE = 0.35;\n\n/** Raw bytes also feed automation fingerprints, including malformed records. */\nexport async function readDecisionInputs(\n rootDir: string,\n): Promise<{ inputs: Array<[string, string | null]>; diagnostics: StoreDiagnostic[] }> {\n return structuredClone(\n await inspectionRead(rootDir, \"decision-store:inputs\", async () => {\n const inputs: Array<[string, string | null]> = [];\n const diagnostics: StoreDiagnostic[] = [];\n let entries: string[];\n try {\n entries = await fs.readdir(await storePath(rootDir, \".mason/decisions\"));\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\")\n diagnostics.push({ path: \".mason/decisions\", message: String(error) });\n return { inputs, diagnostics };\n }\n for (const entry of entries.sort()) {\n if (!entry.endsWith(\".json\")) continue;\n const relative = `.mason/decisions/${entry}`;\n try {\n const raw = await readBoundedFile(await storePath(rootDir, relative), 10 * 1024 * 1024);\n if (raw === null) throw new Error(\"file is not regular or exceeds 10 MiB\");\n inputs.push([relative, raw]);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") inputs.push([relative, null]);\n else\n diagnostics.push({\n path: relative,\n message: error instanceof Error ? error.message : String(error),\n });\n }\n }\n return { inputs, diagnostics };\n }),\n );\n}\n\nexport async function loadDecisionStore(\n rootDir: string,\n): Promise<{ records: DecisionRecord[]; diagnostics: StoreDiagnostic[] }> {\n // Callers may edit their returned records while preparing a write. Never expose\n // mutable cached records, and never share this cache with final validation.\n return structuredClone(\n await inspectionRead(rootDir, \"decision-store:parsed\", async () => {\n const raw = await readDecisionInputs(rootDir);\n const records: DecisionRecord[] = [];\n const diagnostics = [...raw.diagnostics];\n for (const [relative, content] of raw.inputs) {\n try {\n const record = decisionSchema.parse(content === null ? null : JSON.parse(content));\n if (relative !== `.mason/decisions/${record.id}.json`)\n throw new Error(\"Record id does not match its filename\");\n records.push(record);\n } catch (error) {\n diagnostics.push({\n path: relative,\n message: error instanceof Error ? error.message : String(error),\n });\n }\n }\n return { records, diagnostics };\n }),\n );\n}\n\nexport async function loadDecisions(rootDir: string): Promise<DecisionRecord[]> {\n return (await loadDecisionStore(rootDir)).records;\n}\n\nexport async function saveDecisionRecord(rootDir: string, record: DecisionRecord): Promise<void> {\n const validated = decisionSchema.parse(record);\n await writeStoreJson(rootDir, `.mason/decisions/${validated.id}.json`, validated);\n invalidateInspectionReads(rootDir, \"decision-store:\");\n}\n\n/**\n * Deterministic, human-readable id: base slug ≤60 chars, ending at a word\n * boundary when possible. A single longer word is cut to fit.\n * A slug collision with a DIFFERENT record appends a 6-hex content suffix.\n */\nexport function decisionIdFor(title: string, body: string, existingIds: Set<string>): string {\n const fullSlug = title\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n let slug = fullSlug.slice(0, 60);\n if (fullSlug.length > 60 && fullSlug[60] !== \"-\") {\n const boundary = slug.lastIndexOf(\"-\");\n if (boundary > 0) slug = slug.slice(0, boundary);\n }\n slug = slug.replace(/-+$/, \"\") || \"decision\";\n if (!existingIds.has(slug)) return slug;\n const suffix = createHash(\"sha1\")\n .update(title + body)\n .digest(\"hex\")\n .slice(0, 6);\n return `${slug}-${suffix}`;\n}\n\nexport function findNearDuplicate(\n candidate: { title: string; body: string; files: string[] },\n existing: DecisionRecord[],\n): { record: DecisionRecord; similarity: number } | null {\n const candidateTokens = tokenSet(`${candidate.title} ${candidate.body}`);\n const candidateFiles = new Set(candidate.files);\n let best: { record: DecisionRecord; similarity: number } | null = null;\n\n for (const record of existing) {\n if (record.status !== \"active\") continue;\n const similarity = jaccard(candidateTokens, tokenSet(`${record.title} ${record.body}`));\n const sharesFile = record.files.some((f) => candidateFiles.has(f));\n const threshold = sharesFile ? DUPLICATE_JACCARD_WITH_SHARED_FILE : DUPLICATE_JACCARD;\n if (similarity >= threshold && (!best || similarity > best.similarity)) {\n best = { record, similarity };\n }\n }\n return best;\n}\n\nexport interface UpsertDecisionInput {\n title: string;\n body: string;\n category: DecisionCategory;\n files?: string[];\n owner?: string | null;\n sources?: DecisionSource[];\n /** Only supply a known identity; never infer authorship from Git configuration. */\n actor?: string;\n /** Existing id to revise. Unchanged content is a no-op; use review_decision to reaffirm. */\n id?: string;\n /** Id of a decision this one replaces; the old record is kept, marked superseded. */\n supersedes?: string;\n /** Save even when a near-duplicate was detected. */\n force?: boolean;\n}\n\nexport type UpsertDecisionResult =\n | {\n status: \"created\" | \"updated\" | \"unchanged\" | \"superseded_and_created\";\n id: string;\n totalActive: number;\n warnings: string[];\n approval?: \"unreviewed\" | \"proposed\" | \"accepted\";\n hint?: string;\n pruneCandidates?: string[];\n }\n | { status: \"duplicate_suspected\"; existing: DecisionRecord; hint: string }\n | { status: \"error\"; error: string };\n\n/** Serialize tool writes so prepared reviews cannot overwrite another decision edit. */\nexport async function withDecisionWrite<T>(\n root: string,\n operation: () => Promise<T>,\n): Promise<T | { status: \"error\"; error: string }> {\n const lockPath = await storePath(root, \".mason/decisions/.write-lock\", true);\n let lock;\n try {\n lock = await fs.open(lockPath, \"wx\", 0o600);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"EEXIST\")\n return {\n status: \"error\",\n error:\n \"Decision store is locked by another write. Retry after it finishes; an abandoned .mason/decisions/.write-lock must be removed only after confirming no writer is running.\",\n };\n throw error;\n }\n try {\n return await operation();\n } finally {\n await lock.close();\n await fs.unlink(lockPath);\n }\n}\n\nexport async function upsertDecision(\n rootDir: string,\n input: UpsertDecisionInput,\n): Promise<UpsertDecisionResult> {\n const parsedTitle = decisionTitleSchema.safeParse(input.title),\n parsedBody = decisionBodySchema.safeParse(input.body);\n if (!parsedTitle.success) return { status: \"error\", error: parsedTitle.error.issues[0].message };\n if (!parsedBody.success) return { status: \"error\", error: parsedBody.error.issues[0].message };\n const title = parsedTitle.data,\n body = parsedBody.data;\n const attribution = attributionSchema.safeParse(input);\n if (!attribution.success) return { status: \"error\", error: attribution.error.message };\n if (input.id && input.supersedes)\n return {\n status: \"error\",\n error: \"Use either id to revise or supersedes to replace a record, not both.\",\n };\n return withDecisionWrite(rootDir, async () => {\n const store = await loadDecisionStore(rootDir);\n if (store.diagnostics.length)\n return {\n status: \"error\",\n error:\n \"Repair malformed decision records before saving: \" +\n store.diagnostics.map((d) => d.path).join(\", \"),\n };\n const existing = store.records,\n byId = new Map(existing.map((r) => [r.id, r]));\n const now = new Date().toISOString(),\n head = await getCurrentGitHash(rootDir);\n const warnings: string[] = [];\n if (body.length > BODY_RECOMMENDED_CHARS)\n warnings.push(\n `body has ${body.length} characters, above the recommended ${BODY_RECOMMENDED_CHARS}; matching context and hooks include it in full. Keep the rule, reason, and exceptions concise; use sources for supporting references.`,\n );\n const files = sanitizeRepoPaths(input.files ?? []);\n if (input.files && files.length < input.files.length)\n warnings.push(\"some anchor paths were outside the repo or duplicated and were dropped\");\n for (const file of files) {\n try {\n await fs.access(path.join(rootDir, file));\n } catch {\n warnings.push(`anchor file does not exist on disk: ${file}`);\n }\n }\n const hint =\n \"Saved locally for review and commit. Proposals are not accepted constraints; an existing accepted revision remains operative while its replacement is proposed. Use review_decision to prepare evidence and record a separate inspection without human approval, or an authorized acceptance or reaffirmation.\";\n if (input.id) {\n const original = byId.get(input.id);\n if (!original) return { status: \"error\", error: `no decision with id \"${input.id}\"` };\n if (original.status !== \"active\")\n return {\n status: \"error\",\n error: \"Archived records cannot be revised; create a new proposal.\",\n };\n const record = importLegacy(original, now);\n const content = decisionContent({\n ...record,\n title,\n body,\n category: input.category,\n files: input.files !== undefined ? files : record.files,\n owner:\n attribution.data.owner === undefined\n ? record.owner\n : (attribution.data.owner ?? undefined),\n sources: attribution.data.sources ?? record.sources,\n });\n if (JSON.stringify(content) === JSON.stringify(decisionContent(record))) {\n return {\n status: \"unchanged\",\n id: record.id,\n totalActive: existing.filter((r) => r.status === \"active\").length,\n approval: decisionApproval(original),\n warnings,\n hint: \"Unchanged content; no review or freshness stamp was written. Use review_decision for explicit re-verification.\",\n };\n }\n const revision = record.revision + 1;\n const capture = await captureAnchors(rootDir, content.files);\n const updated: ReviewedDecisionRecord = {\n ...record,\n ...content,\n owner: content.owner,\n updatedAt: now,\n revision,\n approval: \"proposed\",\n capture,\n history: [\n ...record.history,\n {\n kind: \"revised\",\n at: now,\n actor: attribution.data.actor,\n revision,\n content,\n approval: \"proposed\",\n status: \"active\",\n refreshedHash: record.refreshedHash,\n capture,\n },\n ],\n };\n await saveDecisionRecord(rootDir, updated);\n return {\n status: \"updated\",\n id: record.id,\n totalActive: existing.filter((r) => r.status === \"active\").length,\n approval: \"proposed\",\n warnings,\n hint,\n };\n }\n const old = input.supersedes ? byId.get(input.supersedes) : undefined;\n if (input.supersedes && !old)\n return { status: \"error\", error: `no decision with id \"${input.supersedes}\" to supersede` };\n if (\n old &&\n (old.status !== \"active\" || decisionApproval(effectiveDecision(old)) === \"accepted\")\n ) {\n return {\n status: \"error\",\n error:\n \"A proposal cannot supersede an accepted or archived record. Create and review the replacement separately, then explicitly retire the old decision with review_decision.\",\n };\n }\n if (!input.force) {\n const duplicate = findNearDuplicate({ title, body, files }, existing);\n if (duplicate)\n return {\n status: \"duplicate_suspected\",\n existing: duplicate.record,\n hint: `A similar decision exists (\"${duplicate.record.title}\"). Call save_decision with id=\"${duplicate.record.id}\" to revise it, or force:true if distinct.`,\n };\n }\n const id = decisionIdFor(title, body, new Set(byId.keys()));\n if (byId.has(id))\n return {\n status: \"error\",\n error: `Decision id collision: ${id}. Choose a distinct title or revise the existing record.`,\n };\n const content = decisionContent({\n title,\n body,\n category: input.category,\n files,\n owner: attribution.data.owner ?? undefined,\n sources: attribution.data.sources ?? [],\n });\n const capture = await captureAnchors(rootDir, content.files);\n const record: ReviewedDecisionRecord = {\n ...content,\n capture,\n version: 2,\n id,\n createdAt: now,\n updatedAt: now,\n refreshedHash: head,\n status: \"active\",\n approval: \"proposed\",\n revision: 1,\n history: [\n {\n kind: \"created\",\n at: now,\n actor: attribution.data.actor,\n revision: 1,\n content,\n approval: \"proposed\",\n status: \"active\",\n refreshedHash: head,\n capture,\n },\n ],\n };\n // Write the replacement first: a failed second write leaves both records\n // available instead of removing the original before its replacement exists.\n await saveDecisionRecord(rootDir, record);\n if (old) {\n const imported = importLegacy(old, now);\n await saveDecisionRecord(rootDir, {\n ...imported,\n status: \"superseded\",\n supersededBy: id,\n updatedAt: now,\n history: [\n ...imported.history,\n {\n kind: \"superseded\",\n at: now,\n actor: attribution.data.actor,\n note: `Replaced by proposal ${id}`,\n revision: imported.revision,\n content: decisionContent(imported),\n approval: imported.approval,\n status: \"superseded\",\n refreshedHash: imported.refreshedHash,\n capture: imported.capture,\n },\n ],\n });\n }\n const totalActive = existing.filter((r) => r.status === \"active\").length + (old ? 0 : 1);\n const result: UpsertDecisionResult = {\n status: old ? \"superseded_and_created\" : \"created\",\n id,\n totalActive,\n approval: \"proposed\",\n warnings,\n hint,\n };\n if (totalActive > MAX_ACTIVE_DECISIONS) {\n result.pruneCandidates = existing\n .filter((r) => r.status !== \"active\")\n .map((r) => r.id)\n .slice(0, 10);\n warnings.push(\n `${totalActive} active decisions exceeds the soft cap of ${MAX_ACTIVE_DECISIONS} — consider a cleanup PR (archived records first)`,\n );\n }\n return result;\n });\n}\n","import { execGit } from \"../utils/git-read.js\";\nimport { createFileAccess } from \"../utils/files.js\";\nimport { readStoreJson, writeStoreJson, type StoreDiagnostic } from \"../utils/storage.js\";\nimport { z } from \"zod\";\nimport { withSnapshotWrite } from \"./lock.js\";\nimport { normalizeRepoPath } from \"../utils/paths.js\";\n\nexport interface FeatureEntry {\n description: string;\n files: string[];\n tests?: string[];\n /**\n * Commit this entry was last verified against. Entries updated by an\n * incremental save carry HEAD here; untouched entries keep the hash the\n * map had before the save, so drift stays visible per entry. Absent means\n * \"as of the snapshot's top-level gitHash\".\n */\n refreshedHash?: string;\n /**\n * Whether this is a user-facing capability or internal infrastructure\n * (DI wiring, config loading, logging, provider/transport plumbing).\n * Capabilities describe user-facing functionality;\n * infrastructure stays in the AI concept map only. Defaults to \"capability\"\n * when absent (older snapshots) — see normalizeFeatureType.\n */\n type?: \"capability\" | \"infrastructure\";\n /**\n * When an assistant last confirmed this entry's files actually implement\n * the claimed feature (verify_snapshot flow). Absent on older snapshots\n * and never-verified entries. Drift checks freshness against git; this\n * checks the map was CORRECT in the first place.\n */\n verifiedAt?: string;\n verifiedHash?: string;\n /** Digest of the entry and bounded source evidence reviewed for this verdict. */\n verificationToken?: string;\n /** Set when verification judged the entry wrong — re-map it. */\n verificationFailed?: boolean;\n verificationNote?: string;\n}\n\nexport type FeatureType = \"capability\" | \"infrastructure\";\n\n/**\n * Coerce an arbitrary type value to a known classification. Anything that\n * isn't explicitly \"infrastructure\" defaults to \"capability\" — so older\n * snapshots and unclassified entries are treated as user-facing (published),\n * never silently hidden.\n */\nexport function normalizeFeatureType(value: unknown): FeatureType {\n return value === \"infrastructure\" ? \"infrastructure\" : \"capability\";\n}\n\nexport interface FlowEntry {\n description: string;\n chain: string[];\n /** See FeatureEntry.refreshedHash. */\n refreshedHash?: string;\n /** See FeatureEntry.verifiedAt / verificationFailed. */\n verifiedAt?: string;\n verifiedHash?: string;\n /** Digest of the entry and bounded source evidence reviewed for this verdict. */\n verificationToken?: string;\n verificationFailed?: boolean;\n verificationNote?: string;\n}\n\nexport interface Snapshot {\n version: 2;\n createdAt: string;\n updatedAt: string;\n gitHash: string;\n features: Record<string, FeatureEntry>;\n flows: Record<string, FlowEntry>;\n}\n\nconst repoPath = z\n .string()\n .refine((value) => normalizeRepoPath(value) !== null, \"Expected a relative repository path\");\nconst verificationFields = {\n refreshedHash: z.string().optional(),\n verifiedAt: z.string().optional(),\n verifiedHash: z.string().optional(),\n verificationToken: z.string().optional(),\n verificationFailed: z.boolean().optional(),\n verificationNote: z.string().optional(),\n};\nexport const featureSchema = z\n .object({\n description: z.string(),\n files: z.array(repoPath),\n tests: z.array(repoPath).optional(),\n type: z.enum([\"capability\", \"infrastructure\"]).optional(),\n ...verificationFields,\n })\n .passthrough();\nexport const flowSchema = z\n .object({ description: z.string(), chain: z.array(repoPath), ...verificationFields })\n .passthrough();\nconst snapshotSchema = z\n .object({\n version: z.literal(2),\n createdAt: z.string(),\n updatedAt: z.string(),\n gitHash: z.string(),\n features: z.record(featureSchema),\n flows: z.record(flowSchema),\n })\n .passthrough();\n\nexport async function loadSnapshot(rootDir: string): Promise<Snapshot | null> {\n const parsed = await readStoreJson(rootDir, \".mason/snapshot.json\");\n if (parsed === null || (parsed as { version?: number }).version === 1) return null;\n const result = snapshotSchema.safeParse(parsed);\n if (!result.success) throw new Error(`Invalid Mason snapshot: ${result.error.message}`);\n return result.data;\n}\n\n/** Context and onboarding can use decisions even when the optional map is broken. */\nexport async function inspectSnapshot(rootDir: string): Promise<{\n status: \"available\" | \"missing\" | \"invalid\";\n snapshot: Snapshot | null;\n diagnostics: StoreDiagnostic[];\n}> {\n try {\n const raw = await readStoreJson(rootDir, \".mason/snapshot.json\");\n const snapshot = raw === null ? null : snapshotSchema.parse(raw);\n return { status: snapshot ? \"available\" : \"missing\", snapshot, diagnostics: [] };\n } catch (error) {\n return {\n status: \"invalid\",\n snapshot: null,\n diagnostics: [\n {\n path: \".mason/snapshot.json\",\n message: error instanceof Error ? error.message : String(error),\n },\n ],\n };\n }\n}\n\n/** Read and commit under one cross-process lock; a null snapshot means no write. */\nexport async function updateSnapshot<T>(\n rootDir: string,\n update: (current: Snapshot | null) => Promise<{\n snapshot: Snapshot | null;\n result: T;\n }>,\n): Promise<T> {\n return withSnapshotWrite(rootDir, async () => {\n const change = await update(await loadSnapshot(rootDir));\n if (change.snapshot) {\n await writeStoreJson(rootDir, \".mason/snapshot.json\", snapshotSchema.parse(change.snapshot));\n }\n return change.result;\n });\n}\n\nexport async function getCurrentGitHash(rootDir: string): Promise<string> {\n try {\n const { stdout } = await execGit([\"rev-parse\", \"HEAD\"], {\n cwd: rootDir,\n });\n return stdout.trim();\n } catch {\n return \"unknown\";\n }\n}\n\nexport async function listSourceFiles(resolvedRoot: string): Promise<string[]> {\n return (await createFileAccess(resolvedRoot)).list();\n}\n","import { z } from \"zod\";\nimport fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport { storePath } from \"./storage.js\";\n\nconst lockOwnerSchema = z.object({ host: z.string(), pid: z.number().int().positive() });\n\nasync function lockDiagnostic(file: string): Promise<string> {\n let reason: string;\n try {\n const owner = lockOwnerSchema.safeParse(JSON.parse(await fs.readFile(file, \"utf8\")));\n if (!owner.success) {\n reason = \"Lock owner data is incomplete or malformed.\";\n } else if (owner.data.host !== os.hostname()) {\n reason = \"The lock belongs to another host; its process cannot be checked locally.\";\n } else {\n try {\n process.kill(owner.data.pid, 0);\n reason = `Local owner PID ${owner.data.pid} is still running.`;\n } catch (error) {\n reason =\n (error as NodeJS.ErrnoException).code === \"ESRCH\"\n ? `Local owner PID ${owner.data.pid} has exited, but lock recovery has not completed.`\n : `Local owner PID ${owner.data.pid} could not be checked.`;\n }\n }\n } catch (error) {\n reason =\n error instanceof SyntaxError\n ? \"Lock owner data is incomplete or malformed.\"\n : \"Lock owner data could not be read; the lock may have changed.\";\n }\n if (\n await fs.lstat(file + \".reclaim\").then(\n () => true,\n () => false,\n )\n ) {\n reason += \" A recovery guard also needs inspection: \" + file + \".reclaim.\";\n }\n return (\n reason +\n \" Retry if a writer is active. For manual recovery, stop all Mason writers for this checkout, confirm they have exited (including other hosts), then remove only confirmed abandoned lock and lock.reclaim files. See docs/reference.md#snapshot-repairs-and-verification.\"\n );\n}\n\n/** Cross-process lock: a killed writer's lock is reclaimed only after its local PID is gone. */\nexport async function withStoreLock<T>(\n root: string,\n directory: string,\n run: () => Promise<T>,\n waitMs = 5000,\n label = \"Mason store\",\n): Promise<T> {\n const file = await storePath(root, directory + \"/lock\", true);\n const deadline = Date.now() + waitMs;\n let handle;\n while (!handle) {\n try {\n handle = await fs.open(file, \"wx\", 0o600);\n } catch (error) {\n // Windows can deny opening a lock while another process finishes deleting it.\n // Retry acquisition only, retaining the original error if access stays denied.\n if (process.platform === \"win32\" && (error as NodeJS.ErrnoException).code === \"EPERM\") {\n if (Date.now() >= deadline) throw error;\n await new Promise((resolve) => setTimeout(resolve, 40));\n continue;\n }\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\") throw error;\n // Do not steal malformed, remote, or live locks on a time-based guess.\n try {\n const owner = lockOwnerSchema.parse(JSON.parse(await fs.readFile(file, \"utf8\")));\n if (owner.host === os.hostname() && Number.isInteger(owner.pid) && owner.pid > 0) {\n try {\n process.kill(owner.pid, 0);\n } catch (probe) {\n if ((probe as NodeJS.ErrnoException).code === \"ESRCH\") {\n // A separate guard arbitrates reclamation; recheck the owner before unlinking.\n const reclaim = file + \".reclaim\";\n let guard;\n try {\n guard = await fs.open(reclaim, \"wx\", 0o600);\n const current = lockOwnerSchema.parse(JSON.parse(await fs.readFile(file, \"utf8\")));\n if (current.pid === owner.pid && current.host === owner.host) await fs.unlink(file);\n } finally {\n if (guard) {\n await guard.close();\n await fs.rm(reclaim, { force: true });\n }\n }\n }\n }\n }\n } catch {\n /* Another writer may be creating/releasing/reclaiming it. */\n }\n if (Date.now() >= deadline)\n throw new Error(\n label +\n \" is busy or its lock needs inspection: \" +\n file +\n \". \" +\n (await lockDiagnostic(file)),\n { cause: error },\n );\n await new Promise((resolve) => setTimeout(resolve, 40));\n }\n if (handle) {\n try {\n await handle.writeFile(JSON.stringify({ pid: process.pid, host: os.hostname() }));\n } catch (error) {\n await handle.close().catch(() => {});\n await fs.rm(file, { force: true }).catch(() => {});\n throw error;\n }\n }\n }\n try {\n return await run();\n } finally {\n await handle.close();\n await fs.unlink(file);\n }\n}\n","import path from \"node:path\";\n\n// Question/filler words that carry no signal about which entry a task\n// touches. Domain words (\"auth\", \"drift\") are never in this list.\nconst STOPWORDS = new Set([\n \"the\",\n \"a\",\n \"an\",\n \"and\",\n \"or\",\n \"of\",\n \"to\",\n \"in\",\n \"on\",\n \"for\",\n \"with\",\n \"how\",\n \"does\",\n \"do\",\n \"is\",\n \"are\",\n \"was\",\n \"what\",\n \"where\",\n \"which\",\n \"why\",\n \"when\",\n \"who\",\n \"i\",\n \"we\",\n \"my\",\n \"our\",\n \"you\",\n \"your\",\n \"it\",\n \"its\",\n \"this\",\n \"that\",\n \"these\",\n \"those\",\n \"can\",\n \"could\",\n \"should\",\n \"would\",\n \"will\",\n \"want\",\n \"need\",\n \"please\",\n \"about\",\n \"into\",\n \"from\",\n \"when\",\n \"there\",\n \"any\",\n \"all\",\n \"some\",\n \"not\",\n \"but\",\n \"also\",\n \"just\",\n \"like\",\n \"get\",\n \"make\",\n \"use\",\n \"new\",\n \"work\",\n \"works\",\n \"working\",\n \"implement\",\n \"implemented\",\n \"change\",\n \"changed\",\n \"file\",\n \"files\",\n \"code\",\n]);\n\n/** Split camelCase/PascalCase/kebab/snake/path into lowercase word tokens. */\nexport function tokenize(text: string): string[] {\n return text\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .filter((t) => t.length > 2 && !STOPWORDS.has(t));\n}\n\n/** Crude singular/plural folding so \"flows\" matches \"flow\" etc. */\nexport function stem(token: string): string {\n return token.length > 3 && token.endsWith(\"s\") ? token.slice(0, -1) : token;\n}\n\nexport function tokenSet(text: string): Set<string> {\n return new Set(tokenize(text).map(stem));\n}\n\nexport interface Scorable {\n name: string;\n description: string;\n files: string[];\n}\n\n/**\n * Lexical relevance of one entry to the task. Name hits are the strongest\n * signal, then description, then file-path words. Each distinct task token\n * counts once at its best weight, so a token appearing everywhere doesn't\n * triple-count.\n */\nexport function scoreEntry(taskTokens: Set<string>, entry: Scorable): number {\n const nameTokens = tokenSet(entry.name);\n const descTokens = tokenSet(entry.description);\n const fileTokens = tokenSet(entry.files.map((f) => path.basename(f)).join(\" \"));\n\n let score = 0;\n for (const token of taskTokens) {\n if (nameTokens.has(token)) score += 3;\n else if (descTokens.has(token)) score += 1;\n else if (fileTokens.has(token)) score += 1;\n }\n return score;\n}\n\n/** Jaccard similarity of two token sets: |∩| / |∪|, 0 when both empty. */\nexport function jaccard(a: Set<string>, b: Set<string>): number {\n if (a.size === 0 && b.size === 0) return 0;\n let intersection = 0;\n for (const token of a) if (b.has(token)) intersection++;\n return intersection / (a.size + b.size - intersection);\n}\n","import { z } from \"zod\";\nimport { createHash } from \"node:crypto\";\nimport { anchorCaptureSchema } from \"./anchors.js\";\nimport { normalizeRepoPath } from \"../utils/paths.js\";\nimport { assessTrust, type Freshness } from \"../context/trust.js\";\n\nconst text = (max: number) => z.string().trim().min(1).max(max);\nexport const decisionSourceSchema = z\n .object({\n kind: z.enum([\"pull_request\", \"issue\", \"incident\", \"discussion\", \"document\", \"other\"]),\n reference: text(1000),\n note: text(500).optional(),\n })\n .strict();\nexport type DecisionSource = z.infer<typeof decisionSourceSchema>;\nexport const attributionSchema = z.object({\n owner: text(200).nullable().optional(),\n sources: z.array(decisionSourceSchema).max(20).optional(),\n actor: text(200).optional(),\n});\nconst contentSchema = z.object({\n title: z.string().min(1),\n body: z.string().min(1),\n category: z.enum([\"decision\", \"gotcha\", \"deprecation\", \"convention\"]),\n files: z.array(z.string().refine((f) => normalizeRepoPath(f) !== null)),\n owner: text(200).optional(),\n sources: z.array(decisionSourceSchema).max(20),\n});\nconst approvalSchema = z.enum([\"unreviewed\", \"proposed\", \"accepted\"]);\nconst statusSchema = z.enum([\"active\", \"superseded\", \"retired\"]);\nexport const reviewEvidenceSchema = z.object({\n baseHash: z.string(),\n headHash: z.string(),\n historyAvailable: z.boolean(),\n changedFiles: z.array(z.string()),\n localChanges: z.array(z.string()),\n});\nconst eventSchema = z.object({\n kind: z.enum([\n \"imported\",\n \"created\",\n \"revised\",\n \"accepted\",\n \"reaffirmed\",\n \"retired\",\n \"superseded\",\n ]),\n at: z.string().datetime(),\n actor: text(200).optional(),\n note: text(1500).optional(),\n revision: z.number().int().positive(),\n content: contentSchema,\n approval: approvalSchema,\n status: statusSchema,\n refreshedHash: z.string(),\n evidence: reviewEvidenceSchema.optional(),\n capture: anchorCaptureSchema.optional(),\n});\nexport type DecisionEvent = z.infer<typeof eventSchema>;\nexport type DecisionApproval = z.infer<typeof approvalSchema>;\nexport type DecisionContent = z.infer<typeof contentSchema>;\n\nconst legacySchema = z\n .object({\n version: z.literal(1),\n id: z.string().regex(/^[a-zA-Z0-9_-]+$/),\n title: z.string().min(1),\n body: z.string().min(1),\n category: contentSchema.shape.category,\n files: contentSchema.shape.files,\n createdAt: z.string(),\n updatedAt: z.string(),\n refreshedHash: z.string(),\n status: z.enum([\"active\", \"superseded\"]),\n supersededBy: z.string().optional(),\n })\n .passthrough();\nconst currentSchema = legacySchema\n .extend({\n version: z.literal(2),\n status: statusSchema,\n approval: approvalSchema,\n revision: z.number().int().positive(),\n owner: text(200).optional(),\n sources: z.array(decisionSourceSchema).max(20),\n history: z.array(eventSchema).min(1),\n capture: anchorCaptureSchema.optional(),\n inspections: z\n .array(\n z.object({\n at: z.string().datetime(),\n inspector: text(200),\n note: text(1500),\n outcome: z.literal(\"no_contradiction_found\"),\n scope: z.literal(\"operative\").optional(),\n decisionDigest: z.string().regex(/^[a-f0-9]{64}$/),\n headHash: z.string(),\n capture: anchorCaptureSchema,\n }),\n )\n .max(200)\n .optional(),\n })\n .superRefine((record, ctx) => {\n const invalid = (message: string) => ctx.addIssue({ code: \"custom\", message });\n const same = (a: unknown, b: unknown) => JSON.stringify(a) === JSON.stringify(b);\n let previous: DecisionEvent | undefined;\n for (const event of record.history) {\n if (!previous) {\n if (![\"created\", \"imported\"].includes(event.kind) || event.revision !== 1)\n invalid(\"History must begin with creation or legacy import at revision 1\");\n if (event.approval !== (event.kind === \"created\" ? \"proposed\" : \"unreviewed\"))\n invalid(\"Initial records cannot claim acceptance\");\n } else {\n if ([\"created\", \"imported\"].includes(event.kind)) invalid(\"History cannot restart\");\n if (previous.status !== \"active\") invalid(\"Archived decisions cannot be changed\");\n if (event.revision !== previous.revision + (event.kind === \"revised\" ? 1 : 0))\n invalid(\"Invalid revision sequence\");\n if (event.kind !== \"revised\" && !same(event.content, previous.content))\n invalid(\"A review cannot silently revise decision content\");\n if (event.kind === \"reaffirmed\" && previous.approval !== \"accepted\")\n invalid(\"Only accepted decisions can be reaffirmed\");\n if (event.kind === \"accepted\" && previous.approval === \"accepted\")\n invalid(\"Use reaffirmation for an accepted decision\");\n const approval =\n event.kind === \"revised\"\n ? \"proposed\"\n : [\"accepted\", \"reaffirmed\"].includes(event.kind)\n ? \"accepted\"\n : previous.approval;\n if (event.approval !== approval) invalid(\"Approval disagrees with review history\");\n if (\n ![\"accepted\", \"reaffirmed\"].includes(event.kind) &&\n event.refreshedHash !== previous.refreshedHash\n )\n invalid(\"Only a review can refresh the evidence baseline\");\n }\n if (\n event.kind !== \"imported\" &&\n event.status !==\n (event.kind === \"retired\"\n ? \"retired\"\n : event.kind === \"superseded\"\n ? \"superseded\"\n : \"active\")\n )\n invalid(\"Lifecycle disagrees with history\");\n if (\n [\"accepted\", \"reaffirmed\", \"retired\"].includes(event.kind) &&\n (!event.actor || !event.note || !event.evidence)\n )\n invalid(\"Reviews require a named reviewer, reason, and code evidence\");\n if ([\"accepted\", \"reaffirmed\"].includes(event.kind)) {\n if (!event.content.owner || !event.content.sources.length)\n invalid(\"Accepted decisions require an owner and source\");\n if (\n !event.evidence ||\n !/^[a-f0-9]{40,64}$/.test(event.evidence.headHash) ||\n event.refreshedHash !== event.evidence.headHash ||\n event.evidence.localChanges.length\n )\n invalid(\"Acceptance requires a committed evidence baseline\");\n }\n if (event.capture && !same([...event.content.files].sort(), event.capture.anchors))\n invalid(\"Capture scope disagrees with decision anchors\");\n previous = event;\n }\n if (previous && !same(previous.capture, record.capture))\n invalid(\"Capture does not match the final history event\");\n if (\n !previous ||\n !same(previous.content, decisionContent(record)) ||\n previous.approval !== record.approval ||\n previous.status !== record.status ||\n previous.revision !== record.revision ||\n previous.refreshedHash !== record.refreshedHash\n )\n invalid(\"Decision does not match the final history event\");\n });\n\nexport const decisionSchema = z.union([legacySchema, currentSchema]);\nexport type DecisionRecord = z.infer<typeof decisionSchema>;\nexport type ReviewedDecisionRecord = z.infer<typeof currentSchema>;\n\nexport function decisionContent(\n record: Pick<DecisionRecord, \"title\" | \"body\" | \"category\" | \"files\"> & {\n owner?: unknown;\n sources?: unknown;\n },\n): DecisionContent {\n return {\n title: record.title,\n body: record.body,\n category: record.category,\n files: record.files,\n ...(typeof record.owner === \"string\" ? { owner: record.owner } : {}),\n sources: Array.isArray(record.sources) ? (record.sources as DecisionSource[]) : [],\n };\n}\n\n/** Reading legacy records never upgrades their approval or rewrites their files. */\nexport function decisionApproval(record: DecisionRecord): DecisionApproval {\n return record.version === 1 ? \"unreviewed\" : record.approval;\n}\n\n/** The last accepted revision remains operative while a replacement is drafted.\n * This is a read-only projection; writes and review tokens use the complete record.\n * Archived records never regain authority from their history.\n */\nexport function effectiveDecision(record: DecisionRecord): DecisionRecord {\n if (record.version !== 2 || record.status !== \"active\" || record.approval !== \"proposed\")\n return record;\n let index = record.history.length - 1;\n while (index >= 0 && ![\"accepted\", \"reaffirmed\"].includes(record.history[index].kind)) index--;\n if (index < 0) return record;\n const event = record.history[index];\n return {\n ...record,\n ...event.content,\n owner: event.content.owner,\n approval: \"accepted\",\n revision: event.revision,\n refreshedHash: event.refreshedHash,\n capture: event.capture,\n updatedAt: event.at,\n history: record.history.slice(0, index + 1),\n };\n}\n\n/** Anchors relevant to either the operative knowledge or its pending proposal. */\nexport function decisionAnchors(record: DecisionRecord): string[] {\n return [...new Set([...effectiveDecision(record).files, ...record.files])];\n}\n\nexport function importLegacy(record: DecisionRecord, now: string): ReviewedDecisionRecord {\n if (record.version === 2) return record;\n // Ignore unrecognized legacy fields: they are not evidence of authorship or approval.\n const content = decisionContent({\n title: record.title,\n body: record.body,\n category: record.category,\n files: record.files,\n });\n return {\n id: record.id,\n createdAt: record.createdAt,\n updatedAt: record.updatedAt,\n status: record.status,\n refreshedHash: record.refreshedHash,\n supersededBy: record.supersededBy,\n ...content,\n version: 2,\n approval: \"unreviewed\",\n revision: 1,\n history: [\n {\n kind: \"imported\",\n at: now,\n revision: 1,\n content,\n approval: \"unreviewed\",\n status: record.status,\n refreshedHash: record.refreshedHash,\n note: \"Imported a legacy record. Prior authorship and review history are unknown.\",\n },\n ],\n };\n}\n\nexport function decisionProvenance(record: DecisionRecord, freshness: Freshness = \"unknown\") {\n const approval = decisionApproval(record);\n const review =\n record.version === 2\n ? [...record.history]\n .reverse()\n .find(\n (e) => [\"accepted\", \"reaffirmed\"].includes(e.kind) && e.revision === record.revision,\n )\n : undefined;\n return {\n approval,\n revision: record.version === 2 ? record.revision : 0,\n owner: record.version === 2 ? (record.owner ?? null) : null,\n sources: record.version === 2 ? record.sources : [],\n guidance:\n record.status !== \"active\"\n ? \"historical\"\n : approval === \"accepted\"\n ? \"constraint\"\n : approval === \"proposed\"\n ? \"proposal\"\n : \"unreviewed\",\n reviewRequired:\n record.status === \"active\" && (approval !== \"accepted\" || freshness !== \"current\"),\n lastReview: review\n ? {\n reviewer: review.actor!,\n at: review.at,\n note: review.note!,\n gitHash: review.refreshedHash,\n }\n : null,\n };\n}\n\nexport function decisionTrust(record: DecisionRecord, freshness: Freshness) {\n const review = decisionProvenance(record, freshness).lastReview;\n return assessTrust(\n review ? { verifiedAt: review.at, verifiedHash: review.gitHash } : {},\n freshness,\n );\n}\n\n/** Inspection identity covers both the proposal and the operative accepted revision. */\nexport type InspectionScope = \"all\" | \"operative\";\n\nexport function decisionInspectionDigest(\n record: DecisionRecord,\n scope: InspectionScope = \"all\",\n): string {\n const effective = effectiveDecision(record);\n if (scope === \"operative\")\n return createHash(\"sha256\")\n .update(\n JSON.stringify([\n \"operative\",\n record.status,\n decisionApproval(effective),\n effective.version === 2 ? effective.revision : 0,\n decisionContent(effective),\n effective.refreshedHash,\n ]),\n )\n .digest(\"hex\");\n return createHash(\"sha256\")\n .update(\n JSON.stringify([\n record.status,\n decisionApproval(record),\n record.version === 2 ? record.revision : 0,\n decisionContent(record),\n decisionContent(effective),\n effective.refreshedHash,\n ]),\n )\n .digest(\"hex\");\n}\n\nexport function latestDecisionInspection(record: DecisionRecord, scope: InspectionScope = \"all\") {\n return record.version === 2\n ? record.inspections\n ?.filter(\n (i) =>\n (scope === \"operative\" || i.scope !== \"operative\") &&\n i.decisionDigest === decisionInspectionDigest(record, i.scope ?? \"all\"),\n )\n .at(-1)\n : undefined;\n}\n\nfunction revisionKnowledge(record: DecisionRecord, freshness: Freshness) {\n return {\n ...decisionContent(record),\n ...decisionProvenance(record, freshness),\n trust: decisionTrust(record, freshness),\n };\n}\n\n/** Readers show accepted content first and label the unaccepted draft separately. */\nexport function decisionKnowledge(\n record: DecisionRecord,\n freshness: Freshness = \"unknown\",\n proposalFreshness: Freshness = \"unknown\",\n) {\n const effective = effectiveDecision(record);\n const inspection = latestDecisionInspection(\n record,\n decisionApproval(effective) === \"accepted\" ? \"operative\" : \"all\",\n );\n return {\n ...revisionKnowledge(effective, freshness),\n ...(inspection\n ? {\n inspection: {\n freshness,\n at: inspection.at,\n inspector: inspection.inspector,\n note: inspection.note,\n outcome: inspection.outcome,\n scope: inspection.scope ?? \"all\",\n headHash: inspection.headHash,\n },\n }\n : {}),\n ...(effective !== record\n ? {\n pendingProposal: {\n ...revisionKnowledge(record, proposalFreshness),\n assessment: latestDecisionInspection(record)\n ? \"no_contradiction_found\"\n : \"not_assessed\",\n },\n }\n : {}),\n };\n}\n\nexport function compactDecisionKnowledge(...args: Parameters<typeof decisionKnowledge>) {\n const { body, pendingProposal, ...summary } = decisionKnowledge(...args);\n if (!pendingProposal) return summary;\n const { body: proposalBody, ...proposal } = pendingProposal;\n return { ...summary, pendingProposal: proposal };\n}\n\nexport const DECISION_GUIDANCE =\n \"Accepted decisions are recorded team constraints, subject to freshness checks. A pendingProposal is an unaccepted replacement; the accepted revision remains operative until explicit acceptance or retirement. Proposals are suggestions; legacy unreviewed records need confirmation. Use review_decision to prepare evidence and record a separate source inspection without human approval, or an authorized human review; identities and sources are recorded assertions, not authenticated proof.\";\n","export type Freshness = \"current\" | \"changed\" | \"unknown\";\nexport interface TrustState {\n freshness: Freshness;\n verification: \"unverified\" | \"passed\" | \"failed\" | \"stale\" | \"unknown\";\n /** Historical map verdict, retained even when its evidence no longer applies. */\n recordedVerdict?: \"passed\" | \"failed\";\n verifiedAt?: string;\n verifiedHash?: string;\n reasons: string[];\n}\n\nexport function assessTrust(\n entry: {\n verifiedAt?: string;\n verifiedHash?: string;\n verificationFailed?: boolean;\n verificationNote?: string;\n },\n freshness: Freshness,\n): TrustState {\n const verification = entry.verificationFailed\n ? \"failed\"\n : entry.verifiedAt\n ? \"passed\"\n : \"unverified\";\n const reasons: string[] = [];\n if (freshness === \"unknown\")\n reasons.push(\n \"Anchors, history, or working-tree evidence are unavailable; verify before relying on this entry.\",\n );\n if (freshness === \"changed\")\n reasons.push(\n \"Anchored files changed; verify against current code before relying on this entry.\",\n );\n if (verification === \"failed\")\n reasons.push(\n `Verification failed: ${entry.verificationNote ?? \"re-map this entry before relying on it\"}`,\n );\n if (verification === \"unverified\") reasons.push(\"No correctness verification has been recorded.\");\n return {\n freshness,\n verification,\n verifiedAt: entry.verifiedAt,\n verifiedHash: entry.verifiedHash,\n reasons,\n };\n}\n\nexport function trustHint(states: TrustState[]): string {\n const parts: string[] = [];\n if (states.some((s) => s.verification === \"failed\" || s.recordedVerdict === \"failed\"))\n parts.push(\n \"Verification failed for returned entries; do not rely on those descriptions until corrected.\",\n );\n if (states.some((s) => s.verification === \"stale\"))\n parts.push(\n \"Recorded verification evidence changed; use verify_snapshot and review the current evidence before reusing those verdicts.\",\n );\n if (states.some((s) => s.verification === \"unknown\"))\n parts.push(\n \"Recorded verification evidence cannot be confirmed; inspect the files and obtain a fresh review before relying on those verdicts.\",\n );\n if (states.some((s) => s.freshness === \"unknown\"))\n parts.push(\n \"Freshness is unknown for some returned entries; inspect their files before relying on them.\",\n );\n if (states.some((s) => s.freshness === \"changed\"))\n parts.push(\n \"Some returned entries have changed files, including possible local edits; verify against the current code.\",\n );\n if (!parts.length)\n parts.push(\n \"No changes detected in the returned anchors. This does not prove the descriptions are correct.\",\n );\n if (states.some((s) => s.verification === \"unverified\"))\n parts.push(\n \"Some entries have never been verified; read their evidence or use verify_snapshot for map descriptions.\",\n );\n return parts.join(\" \");\n}\n","import { captureAnchorScopes, changedCaptureFiles, type AnchorCapture } from \"./anchors.js\";\nimport path from \"node:path\";\nimport { getChangesWithStatus, getWorkingTree, touchedPaths } from \"../drift/drift.js\";\nimport { matchingPaths } from \"../utils/paths.js\";\nimport type { Freshness } from \"../context/trust.js\";\nimport type { StoreDiagnostic } from \"../utils/storage.js\";\nimport { getCurrentGitHash } from \"../snapshot/snapshot.js\";\nimport { loadDecisionStore } from \"./decisions.js\";\nimport { driftBaseline } from \"./excerpts.js\";\nimport type { DecisionRecord } from \"./decisions.js\";\nimport { decisionApproval, effectiveDecision, latestDecisionInspection } from \"./provenance.js\";\n\n/**\n * Deliberately separate from DriftReport: mason-drift's exit codes and\n * --json shape are a CI contract, and `stale` there means MAP staleness.\n * Decision staleness is additive on top.\n */\nexport interface DecisionDriftReport {\n historyAvailable: boolean;\n freshness?: Record<string, Freshness>;\n diagnostics?: StoreDiagnostic[];\n totalDecisions: number;\n /** Decision id → committed and local anchor files changed since the evidence baseline. */\n staleDecisions: Record<string, string[]>;\n /** Draft anchors have their own freshness; they cannot replace accepted anchors. */\n pendingProposals?: Record<string, { freshness: Freshness; changedFiles: string[] }>;\n}\n\n/**\n * Flag active decisions whose anchor files changed since the record was\n * last verified. Anchorless decisions have unknown freshness and do not\n * contribute to committed drift.\n * Deterministic — git only, no LLM.\n */\nexport async function computeDecisionDrift(\n rootDir: string,\n decisions?: DecisionRecord[],\n): Promise<DecisionDriftReport> {\n const resolvedRoot = path.resolve(rootDir);\n const store = decisions\n ? { records: decisions, diagnostics: [] }\n : await loadDecisionStore(resolvedRoot);\n const report: DecisionDriftReport = {\n historyAvailable: true,\n totalDecisions: store.records.length,\n staleDecisions: {},\n freshness: {},\n diagnostics: store.diagnostics,\n };\n const [head, workingTree] = await Promise.all([\n getCurrentGitHash(resolvedRoot),\n getWorkingTree(resolvedRoot),\n ]);\n const changesByHash = new Map<string, string[] | null>();\n const baselines = store.records\n .filter((record) => record.status === \"active\")\n .flatMap((record) => {\n const effective = effectiveDecision(record);\n return [...new Set([effective, record])].flatMap((candidate) => {\n const inspection = latestDecisionInspection(\n record,\n candidate === effective && decisionApproval(effective) === \"accepted\"\n ? \"operative\"\n : \"all\",\n );\n const capture =\n inspection?.capture ?? (candidate.version === 2 ? candidate.capture : undefined);\n return capture ? [capture] : [];\n });\n });\n const captures = await captureAnchorScopes(\n resolvedRoot,\n baselines.map((baseline) => baseline.anchors),\n );\n const currentCaptures = new Map(\n baselines.map((baseline, index) => [JSON.stringify(baseline.anchors), captures[index]]),\n );\n const inspect = async (\n record: DecisionRecord,\n inspectionCapture?: AnchorCapture,\n ): Promise<{ freshness: Freshness; changedFiles: string[] }> => {\n if (record.files.length === 0) return { freshness: \"unknown\", changedFiles: [] };\n const baseline = inspectionCapture ?? (record.version === 2 ? record.capture : undefined);\n if (baseline) {\n const current = currentCaptures.get(JSON.stringify(baseline.anchors))!;\n const hits = changedCaptureFiles(baseline, current).filter(\n (file) => matchingPaths(record.files, [file]).length,\n );\n return {\n freshness:\n !baseline.complete || !current.complete ? \"unknown\" : hits.length ? \"changed\" : \"current\",\n changedFiles: hits,\n };\n }\n let touched = changesByHash.get(record.refreshedHash);\n if (touched === undefined) {\n const changes =\n record.refreshedHash === head && head !== \"unknown\"\n ? []\n : await getChangesWithStatus(resolvedRoot, record.refreshedHash);\n touched = changes === null ? null : touchedPaths(changes);\n changesByHash.set(record.refreshedHash, touched);\n }\n if (touched === null) report.historyAvailable = false;\n const hits = touched ? matchingPaths(record.files, touched) : [];\n const localHits = matchingPaths(record.files, workingTree.changedFiles);\n return {\n freshness:\n touched === null || !workingTree.available\n ? \"unknown\"\n : hits.length || localHits.length\n ? \"changed\"\n : \"current\",\n changedFiles: [...new Set([...hits, ...localHits])].sort(),\n };\n };\n for (const record of store.records) {\n if (record.status !== \"active\") continue;\n // Shared with drift excerpts so hunks are shown against the same captured baseline.\n const baseline = driftBaseline(record);\n const state = await inspect(baseline.record, baseline.capture);\n report.freshness![record.id] = state.freshness;\n if (state.changedFiles.length) report.staleDecisions[record.id] = state.changedFiles;\n if (baseline.record !== record)\n (report.pendingProposals ??= {})[record.id] = await inspect(\n record,\n latestDecisionInspection(record)?.capture,\n );\n }\n return report;\n}\n","import { execGit } from \"../utils/git-read.js\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { loadSnapshot, getCurrentGitHash, listSourceFiles } from \"../snapshot/snapshot.js\";\nimport type { Snapshot } from \"../snapshot/snapshot.js\";\nimport type { Freshness } from \"../context/trust.js\";\nimport { matchingPaths } from \"../utils/paths.js\";\n\n// Incremental refresh stops paying off once a large share of the map is\n// touched — but small absolute counts are always cheap to refresh in place,\n// so both thresholds must be exceeded before recommending a full rebuild.\nconst FULL_REBUILD_FRACTION = 0.4;\nconst FULL_REBUILD_MIN_CHANGED_MAPPED_FILES = 10;\n\nexport type ChangeStatus = \"added\" | \"modified\" | \"deleted\" | \"renamed\";\n\nexport interface FileChange {\n status: ChangeStatus;\n /** Current path (the new path for renames). */\n path: string;\n /** Pre-rename path, only present for renames. */\n previousPath?: string;\n}\n\nexport type DriftRecommendation = \"up-to-date\" | \"incremental\" | \"full-rebuild\";\n\nexport interface WorkingTreeReport {\n available: boolean;\n changedFiles: string[];\n untrackedFiles: string[];\n}\n\nexport interface DriftReport {\n /** Live entry state; committed drift alone continues to drive CLI exit codes. */\n featureFreshness?: Record<string, Freshness>;\n flowFreshness?: Record<string, Freshness>;\n workingTree?: WorkingTreeReport;\n verification?: { neverVerified: number; failed: string[] };\n stale: boolean;\n snapshotHash: string;\n headHash: string;\n /** Commits between the snapshot and HEAD; null when history is unavailable. */\n commitsBehind: number | null;\n /**\n * False when the snapshot commit is unreachable (shallow clone, rewritten\n * history) — staleFeatures/unmappedFiles/renames cannot be computed then.\n */\n historyAvailable: boolean;\n /** Current paths of every file changed since the snapshot. */\n changedFiles: string[];\n /** Stale feature name → the mapped files that changed under it. */\n staleFeatures: Record<string, string[]>;\n /** Stale flow name → the chain files that changed under it. */\n staleFlows: Record<string, string[]>;\n totalFeatures: number;\n totalFlows: number;\n /** New source files not referenced by any feature or flow. */\n unmappedFiles: string[];\n /** Files referenced by the map that no longer exist on disk. */\n ghostFiles: string[];\n renames: Array<{ from: string; to: string }>;\n recommendation: DriftRecommendation;\n}\n\nfunction parseChanges(output: string): FileChange[] {\n const fields = output.split(\"\\0\");\n const changes: FileChange[] = [];\n for (let i = 0; i < fields.length && fields[i];) {\n const code = fields[i++];\n const first = fields[i++];\n if (!first) break;\n const second = /^[RC]/.test(code) ? fields[i++] : undefined;\n const change: FileChange = second\n ? code.startsWith(\"R\")\n ? { status: \"renamed\", path: second, previousPath: first }\n : { status: \"added\", path: second }\n : { status: code === \"A\" ? \"added\" : code === \"D\" ? \"deleted\" : \"modified\", path: first };\n if (\n change.path.startsWith(\".mason/\") &&\n (!change.previousPath || change.previousPath.startsWith(\".mason/\"))\n )\n continue;\n changes.push(change);\n }\n return changes;\n}\n\nexport function touchedPaths(changes: FileChange[]): string[] {\n return [\n ...new Set(changes.flatMap((c) => (c.previousPath ? [c.previousPath, c.path] : [c.path]))),\n ].sort();\n}\n\nexport async function getChangesWithStatus(\n resolvedRoot: string,\n fromHash: string,\n toHash = \"HEAD\",\n): Promise<FileChange[] | null> {\n if (\n !fromHash ||\n fromHash === \"unknown\" ||\n fromHash.startsWith(\"-\") ||\n !toHash ||\n toHash === \"unknown\" ||\n toHash.startsWith(\"-\")\n )\n return null;\n try {\n const { stdout } = await execGit(\n [\"diff\", \"--name-status\", \"-z\", \"-M\", fromHash, toHash, \"--\"],\n { cwd: resolvedRoot, maxBuffer: 10 * 1024 * 1024 },\n );\n return parseChanges(stdout);\n } catch {\n return null;\n }\n}\n\nexport async function getWorkingTree(resolvedRoot: string): Promise<WorkingTreeReport> {\n try {\n const [diff, untracked] = await Promise.all([\n execGit([\"diff\", \"--name-status\", \"-z\", \"-M\", \"HEAD\", \"--\"], {\n cwd: resolvedRoot,\n maxBuffer: 10 * 1024 * 1024,\n }),\n execGit([\"ls-files\", \"-z\", \"--others\", \"--exclude-standard\"], {\n cwd: resolvedRoot,\n maxBuffer: 10 * 1024 * 1024,\n }),\n ]);\n const untrackedFiles = untracked.stdout\n .split(\"\\0\")\n .filter((f) => f && !f.startsWith(\".mason/\"));\n return {\n available: true,\n changedFiles: [\n ...new Set([...touchedPaths(parseChanges(diff.stdout)), ...untrackedFiles]),\n ].sort(),\n untrackedFiles,\n };\n } catch {\n return { available: false, changedFiles: [], untrackedFiles: [] };\n }\n}\n\nasync function countCommitsBehind(resolvedRoot: string, fromHash: string): Promise<number | null> {\n if (!fromHash || fromHash === \"unknown\" || fromHash.startsWith(\"-\")) return null;\n try {\n const { stdout } = await execGit([\"rev-list\", \"--count\", `${fromHash}..HEAD`], {\n cwd: resolvedRoot,\n });\n const count = Number.parseInt(stdout.trim(), 10);\n return Number.isNaN(count) ? null : count;\n } catch {\n return null;\n }\n}\n\nfunction collectMappedFiles(snapshot: Snapshot): Set<string> {\n const mappedFiles = new Set<string>();\n for (const feature of Object.values(snapshot.features)) {\n for (const f of feature.files) mappedFiles.add(f);\n for (const t of feature.tests ?? []) mappedFiles.add(t);\n }\n for (const flow of Object.values(snapshot.flows)) {\n for (const f of flow.chain) mappedFiles.add(f);\n }\n return mappedFiles;\n}\n\nasync function findGhostFiles(resolvedRoot: string, mappedFiles: Set<string>): Promise<string[]> {\n const ghosts: string[] = [];\n for (const file of mappedFiles) {\n try {\n await fs.access(path.join(resolvedRoot, file));\n } catch {\n ghosts.push(file);\n }\n }\n return ghosts.sort();\n}\n\n/**\n * Compare the concept map against HEAD and report feature-level drift.\n * Fully deterministic — git + filesystem only, no LLM involved.\n * Returns null when no snapshot exists.\n */\nexport async function computeDrift(rootDir: string): Promise<DriftReport | null> {\n const root = path.resolve(rootDir);\n const snapshot = await loadSnapshot(root);\n if (!snapshot) return null;\n const [headHash, workingTree] = await Promise.all([\n getCurrentGitHash(root),\n getWorkingTree(root),\n ]);\n const hashFor = (entry: { refreshedHash?: string }) => entry.refreshedHash ?? snapshot.gitHash;\n const entries = [...Object.values(snapshot.features), ...Object.values(snapshot.flows)];\n const hashes = new Set([snapshot.gitHash, ...entries.map(hashFor)]);\n const changesByHash = new Map<string, FileChange[] | null>();\n await Promise.all(\n [...hashes].map(async (hash) => {\n changesByHash.set(\n hash,\n hash === headHash && headHash !== \"unknown\" ? [] : await getChangesWithStatus(root, hash),\n );\n }),\n );\n const historyAvailable =\n headHash !== \"unknown\" && [...changesByHash.values()].every((changes) => changes !== null);\n const mappedFiles = collectMappedFiles(snapshot);\n const report: DriftReport = {\n stale: !historyAvailable,\n snapshotHash: snapshot.gitHash,\n headHash,\n commitsBehind: 0,\n historyAvailable,\n changedFiles: [],\n staleFeatures: {},\n staleFlows: {},\n totalFeatures: Object.keys(snapshot.features).length,\n totalFlows: Object.keys(snapshot.flows).length,\n unmappedFiles: [],\n ghostFiles: await findGhostFiles(root, mappedFiles),\n renames: [],\n recommendation: historyAvailable ? \"up-to-date\" : \"full-rebuild\",\n featureFreshness: {},\n flowFreshness: {},\n workingTree,\n verification: {\n neverVerified: entries.filter((e) => !e.verifiedAt).length,\n failed: [...Object.entries(snapshot.features), ...Object.entries(snapshot.flows)]\n .filter(([, e]) => e.verificationFailed)\n .map(([name]) => name),\n },\n };\n const counts = await Promise.all(\n [...hashes].map(async (hash) => (hash === headHash ? 0 : countCommitsBehind(root, hash))),\n );\n const knownCounts = counts.filter((n): n is number => n !== null);\n report.commitsBehind = knownCounts.length ? Math.max(...knownCounts) : null;\n\n const check = (\n name: string,\n files: string[],\n hash: string,\n staleEntries: Record<string, string[]>,\n freshness: Record<string, Freshness>,\n ) => {\n const changes = changesByHash.get(hash);\n const committedHits = changes ? matchingPaths(files, touchedPaths(changes)) : [];\n if (committedHits.length) staleEntries[name] = committedHits;\n const localHits = matchingPaths(files, workingTree.changedFiles);\n freshness[name] =\n files.length === 0 || changes === null || changes === undefined || !workingTree.available\n ? \"unknown\"\n : committedHits.length ||\n localHits.length ||\n files.some((f) => report.ghostFiles.includes(f))\n ? \"changed\"\n : \"current\";\n };\n for (const [name, feature] of Object.entries(snapshot.features)) {\n check(\n name,\n [...feature.files, ...(feature.tests ?? [])],\n hashFor(feature),\n report.staleFeatures,\n report.featureFreshness!,\n );\n }\n for (const [name, flow] of Object.entries(snapshot.flows)) {\n check(name, flow.chain, hashFor(flow), report.staleFlows, report.flowFreshness!);\n }\n\n const allChanges = [...changesByHash.values()].flatMap((changes) => changes ?? []);\n report.changedFiles = [...new Set(allChanges.map((c) => c.path))].sort();\n // Complete coverage, including omissions from a map saved at HEAD. Untracked\n // files remain in workingTree and never change the committed-drift exit code.\n const sourceFiles = new Set(await listSourceFiles(root));\n let committedFiles: Set<string> = new Set();\n try {\n const { stdout } = await execGit([\"ls-tree\", \"-r\", \"--name-only\", \"-z\", \"HEAD\"], {\n cwd: root,\n maxBuffer: 50 * 1024 * 1024,\n });\n committedFiles = new Set(stdout.split(\"\\0\").filter(Boolean));\n } catch {\n report.historyAvailable = false;\n report.stale = true;\n }\n report.unmappedFiles = [...sourceFiles]\n .filter((f) => committedFiles.has(f) && !mappedFiles.has(f))\n .sort();\n const renames = new Map<string, { from: string; to: string }>();\n for (const change of allChanges) {\n if (change.status === \"renamed\" && change.previousPath)\n renames.set(`${change.previousPath}\\0${change.path}`, {\n from: change.previousPath,\n to: change.path,\n });\n }\n report.renames = [...renames.values()];\n const changedMapped = new Set([\n ...Object.values(report.staleFeatures).flat(),\n ...Object.values(report.staleFlows).flat(),\n ]);\n // A locally deleted file is a live-edit warning, not committed map drift.\n const committedGhosts = report.ghostFiles.filter((f) => !workingTree.changedFiles.includes(f));\n report.stale ||=\n changedMapped.size > 0 || report.unmappedFiles.length > 0 || committedGhosts.length > 0;\n if (!report.historyAvailable) report.recommendation = \"full-rebuild\";\n else if (!report.stale) report.recommendation = \"up-to-date\";\n else\n report.recommendation =\n changedMapped.size >= FULL_REBUILD_MIN_CHANGED_MAPPED_FILES &&\n changedMapped.size / Math.max(1, mappedFiles.size) > FULL_REBUILD_FRACTION\n ? \"full-rebuild\"\n : \"incremental\";\n return report;\n}\n","import { createHash } from \"node:crypto\";\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { createFileAccess } from \"../utils/files.js\";\nimport { anchorMatches } from \"../utils/paths.js\";\nimport {\n decisionApproval,\n effectiveDecision,\n latestDecisionInspection,\n type DecisionRecord,\n} from \"./provenance.js\";\n\nconst exec = promisify(execFile);\nconst MAX_DIFFED_FILES = 8;\nconst MAX_SHOWN_FILES = 3;\nconst MAX_SHOWN_HUNKS = 2;\nconst MAX_HUNK_LINES = 8;\nconst HISTORY_DEPTH = 20;\n\n/** The capture that drift compares against: the latest valid inspection, else the save capture. */\nexport function driftBaseline(record: DecisionRecord) {\n const effective = effectiveDecision(record);\n const inspection = latestDecisionInspection(\n record,\n decisionApproval(effective) === \"accepted\" ? \"operative\" : \"all\",\n );\n return {\n record: effective,\n capture: inspection?.capture ?? (effective.version === 2 ? effective.capture : undefined),\n commits: [inspection?.headHash, effective.refreshedHash].filter(\n (hash): hash is string => !!hash && /^[a-f0-9]{40,64}$/.test(hash),\n ),\n };\n}\n\ninterface Hunk {\n header: string;\n lines: string[];\n}\nexport interface DriftExcerpt {\n /** Identifiers named by the decision that appear in changed lines or enclosing declarations. */\n mentions: string[];\n /** `low` only when the decision names identifiers and every changed file was diffed\n * against its exact captured content without mentioning them. */\n relevance: \"mentioned\" | \"low\" | \"unknown\";\n unknownReason?: \"no-identifiers\" | \"incomplete\";\n files: Array<{ path: string; hunks: Hunk[]; omittedHunks: number; unavailable?: string }>;\n omittedFiles: string[];\n}\n\n/** Find a commit whose copy of the file has exactly the captured bytes, in one Git process. */\nasync function capturedCommit(root: string, file: string, hash: string, preferred: string[]) {\n if (/[\\n\\r]/.test(file)) return null;\n const { stdout } = await exec(\n \"git\",\n [\"--literal-pathspecs\", \"log\", \"-n\", String(HISTORY_DEPTH), \"--format=%H\", \"--\", file],\n { cwd: root, maxBuffer: 1024 * 1024 },\n );\n const commits = [...new Set([...preferred, ...stdout.split(\"\\n\").filter(Boolean)])];\n const batch = exec(\"git\", [\"cat-file\", \"--batch\"], {\n cwd: root,\n encoding: \"buffer\",\n maxBuffer: 64 * 1024 * 1024,\n });\n batch.child.stdin!.end(commits.map((commit) => `${commit}:${file}\\n`).join(\"\"));\n const output = (await batch).stdout;\n let position = 0;\n for (const commit of commits) {\n const end = output.indexOf(10, position);\n if (end < 0) break;\n const header = output.subarray(position, end).toString(\"utf8\").split(\" \");\n position = end + 1;\n if (header[1] !== \"blob\") continue;\n const size = Number(header[2]);\n const bytes = output.subarray(position, position + size);\n position += size + 1;\n if (createHash(\"sha256\").update(bytes).digest(\"hex\") === hash) return commit;\n }\n return null;\n}\n\n/** Code identifiers a decision names: backticked spans, calls, camelCase, snake_case and\n * backticked kebab-case. Ordinary prose is excluded because it overlaps with nearly any change. */\nexport function namedIdentifiers(text: string): string[] {\n const names = new Set<string>();\n const add = (name: string) => {\n if (name.length >= 3) names.add(name);\n };\n for (const [, span] of text.matchAll(/`([^`]+)`/g))\n for (const [name] of span.matchAll(/[A-Za-z_$][\\w$]*(?:-[A-Za-z0-9_$]+)*/g)) add(name);\n for (const [, name] of text.matchAll(/([A-Za-z_$][\\w$]*)\\(/g)) add(name);\n // Hyphenated prose (\"opt-in\", \"draft-only\") is not code; kebab-case names count in backticks.\n for (const [name] of text.matchAll(/[A-Za-z_$][\\w$]*/g))\n if (/[a-z][A-Z]|[_$]/.test(name)) add(name);\n return [...names];\n}\n\n/** Lowercase identifiers in code, with their camelCase and snake_case parts. */\nfunction codeTokens(code: string): Set<string> {\n const tokens = new Set<string>();\n for (const [name] of code.matchAll(/[A-Za-z_$][\\w$]*/g)) {\n tokens.add(name.toLowerCase());\n for (const part of name.split(/_+|(?<=[a-z0-9])(?=[A-Z])/))\n if (part.length >= 3) tokens.add(part.toLowerCase());\n }\n return tokens;\n}\n\nfunction parseHunks(diff: string): Hunk[] {\n const hunks: Hunk[] = [];\n for (const line of diff.split(\"\\n\")) {\n if (line.startsWith(\"@@\")) hunks.push({ header: line, lines: [] });\n else if (hunks.length && /^[+-]/.test(line)) hunks.at(-1)!.lines.push(line);\n }\n return hunks;\n}\n\n/** Bounded changed hunks for the drifted anchor files, against the exact captured content.\n * Relevance matches identifiers the decision names against changed code; it never establishes\n * consistency and never replaces source inspection. */\nexport async function driftExcerpt(\n root: string,\n record: DecisionRecord,\n changedFiles: string[],\n): Promise<DriftExcerpt> {\n const baseline = driftBaseline(record);\n const excerpt: DriftExcerpt = { mentions: [], relevance: \"unknown\", files: [], omittedFiles: [] };\n const named = namedIdentifiers(`${baseline.record.title}\\n${baseline.record.body}`);\n const found = new Set<string>();\n let complete = !!baseline.capture && changedFiles.length <= MAX_DIFFED_FILES;\n const access = await createFileAccess(root);\n for (const [index, file] of changedFiles.slice(0, MAX_DIFFED_FILES).entries()) {\n let hunks: Hunk[] | null = null;\n let unavailable: string | undefined;\n const captured = baseline.capture?.files[file];\n try {\n if (!baseline.capture?.anchors.some((anchor) => anchorMatches(anchor, file))) {\n unavailable = \"not in the captured anchor scope\";\n } else if (captured === undefined) {\n // Added since capture: every current line is new.\n const source = await access.read(file);\n if (source)\n hunks = [\n {\n header: \"(added since capture)\",\n lines: source.content\n .replace(/\\r?\\n$/, \"\")\n .split(\"\\n\")\n .map((line) => \"+\" + line),\n },\n ];\n else unavailable = \"added since capture but unreadable\";\n } else {\n const commit = await capturedCommit(root, file, captured, baseline.commits);\n if (!commit) unavailable = \"captured content is not in recent Git history\";\n else {\n const { stdout } = await exec(\n \"git\",\n [\n \"--literal-pathspecs\",\n \"diff\",\n \"--no-ext-diff\",\n \"--no-textconv\",\n \"--no-color\",\n \"--no-renames\",\n \"-U0\",\n commit,\n \"--\",\n file,\n ],\n { cwd: root, maxBuffer: 1024 * 1024 },\n );\n hunks = parseHunks(stdout);\n }\n }\n } catch {\n unavailable = \"diff could not be read within its bounds\";\n }\n // Binary and mode-only changes have no hunks to compare, so they cannot be low relevance.\n if (!hunks?.length) complete = false;\n const code = (hunks ?? [])\n .map((hunk) => [hunk.header, ...hunk.lines.map((line) => line.slice(1))].join(\"\\n\"))\n .join(\"\\n\");\n const tokens = codeTokens(code);\n for (const name of named)\n if (\n name.includes(\"-\")\n ? code.toLowerCase().includes(name.toLowerCase())\n : tokens.has(name.toLowerCase())\n )\n found.add(name);\n if (index >= MAX_SHOWN_FILES) excerpt.omittedFiles.push(file);\n else\n excerpt.files.push({\n path: file,\n hunks: (hunks ?? [])\n .slice(0, MAX_SHOWN_HUNKS)\n .map((hunk) => ({ ...hunk, lines: hunk.lines.slice(0, MAX_HUNK_LINES) })),\n omittedHunks: Math.max(0, (hunks?.length ?? 0) - MAX_SHOWN_HUNKS),\n ...(unavailable ? { unavailable } : {}),\n });\n }\n excerpt.omittedFiles.push(...changedFiles.slice(MAX_DIFFED_FILES));\n excerpt.mentions = [...found].sort();\n excerpt.relevance = found.size ? \"mentioned\" : complete && named.length ? \"low\" : \"unknown\";\n if (excerpt.relevance === \"unknown\")\n excerpt.unknownReason = named.length ? \"incomplete\" : \"no-identifiers\";\n return excerpt;\n}\n\nexport function formatDriftExcerpt(title: string, id: string, excerpt: DriftExcerpt): string {\n const safe = (value: string, max = 160) =>\n value.replace(/[\\u0000-\\u001f\\u007f-\\u009f]/g, \" \").slice(0, max);\n const lines = [`Changed lines for decision ${JSON.stringify(safe(title, 250))} (${safe(id)}):`];\n lines.push(\n excerpt.relevance === \"mentioned\"\n ? ` Changed lines mention identifiers named by the decision: ${excerpt.mentions.map((name) => safe(name, 60)).join(\", \")}.`\n : excerpt.relevance === \"low\"\n ? \" Low relevance: no changed line or enclosing declaration mentions an identifier named by the decision. This is a name match, not evidence of consistency; inspect before recording.\"\n : excerpt.unknownReason === \"no-identifiers\"\n ? \" Relevance not assessed: the decision names no code identifiers (such as `refresh()` or onRefresh) to match against the changes.\"\n : \" Relevance unknown: some changed content could not be compared with its captured version.\",\n );\n for (const file of excerpt.files) {\n if (file.unavailable) {\n lines.push(` ${safe(file.path)}: ${file.unavailable}.`);\n continue;\n }\n for (const hunk of file.hunks)\n lines.push(\n ` ${safe(file.path)} ${safe(hunk.header, 120)}`,\n ...hunk.lines.map((line) => \" \" + safe(line)),\n );\n if (!file.hunks.length) lines.push(` ${safe(file.path)}: changed without text hunks.`);\n if (file.omittedHunks) lines.push(` ${safe(file.path)}: ${file.omittedHunks} more hunk(s).`);\n }\n if (excerpt.omittedFiles.length)\n lines.push(` ${excerpt.omittedFiles.length} more changed file(s) not shown.`);\n return lines.join(\"\\n\");\n}\n","import { runHook } from \"./hook.js\";\nimport type { HookEnv } from \"./hook.js\";\n\nexport const USAGE = `Usage: mason-hook [--print-config | --help]\n\nClaude Code PostToolUse hook: when the session reads or edits a file that a\nMason decision record anchors, the record is injected into the model's\ncontext. Deterministic lookup, no LLM call; silent when nothing matches.\n\nReads the hook JSON on stdin and prints the hook output JSON on stdout.\nRegister it via .claude/settings.json (committed to the repo, so the whole\nteam gets the same rail):\n\n mason-hook --print-config Print the settings.json hooks block\n\nRepeat injections are deduped per session; state lives in the OS temp dir.`;\n\nexport const SETTINGS_CONFIG = {\n hooks: {\n PostToolUse: [\n {\n matcher: \"Read|Edit|Write\",\n hooks: [\n {\n type: \"command\",\n command: \"npx -y -p mason-context mason-hook\",\n timeout: 10,\n },\n ],\n },\n ],\n },\n};\n\nexport interface HookCliIo {\n out: (line: string) => void;\n err: (line: string) => void;\n}\n\nexport async function runHookCli(\n argv: string[],\n stdinText: string,\n io: HookCliIo = {\n out: (line) => process.stdout.write(`${line}\\n`),\n err: (line) => process.stderr.write(`${line}\\n`),\n },\n env: HookEnv = {},\n): Promise<number> {\n if (argv.includes(\"--help\") || argv.includes(\"-h\")) {\n io.out(USAGE);\n return 0;\n }\n if (argv.includes(\"--print-config\")) {\n io.out(JSON.stringify(SETTINGS_CONFIG, null, 2));\n return 0;\n }\n\n // A hook must never disrupt the session: any failure path is a silent\n // success with no output.\n try {\n const output = await runHook(stdinText, env);\n if (output !== null) io.out(output);\n } catch {\n // Silent by design.\n }\n return 0;\n}\n","import { runHookCli } from \"../src/hook/cli.js\";\n\nasync function readStdin(): Promise<string> {\n if (process.stdin.isTTY) return \"\";\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n const value: unknown = chunk;\n if (typeof value === \"string\" || value instanceof Uint8Array) chunks.push(Buffer.from(value));\n else throw new Error(\"Unsupported stdin chunk.\");\n }\n return Buffer.concat(chunks).toString(\"utf-8\");\n}\n\nconst argv = process.argv.slice(2);\nconst informational = argv.some((arg) => [\"--help\", \"-h\", \"--print-config\"].includes(arg));\n\n(informational ? Promise.resolve(\"\") : readStdin())\n .then((stdinText) => runHookCli(argv, stdinText))\n .then((code) => process.exit(code))\n .catch(() => process.exit(0));\n"],"mappings":"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","names":["z","fs","path","os","fg","AsyncLocalStorage","path","path","path","raw","ALL_CHECKS","AsyncLocalStorage","path","fs","constants","fg","fs","path","fs","path","fg","fs","constants","fs","path","createHash","z","z","z","fs","z","repoPath","z","path","z","createHash","z","createHash","z","fs","createHash","path","fs","path","createHash","execFile","promisify","promisify","execFile","path","hits","z","fs","path","os","createHash","argv"]}
|