@descent-vtt/spec-harness 0.8.0 → 0.9.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `spec-harness mcp`: the round's questions, served over the Model Context\n * Protocol on stdio (ADR-0008). The protocol is spec-core's `jsonrpc` module;\n * this file is the five tools, the four prompts, and nothing else. Every\n * request reads the repository afresh: a brief edited a minute ago is the\n * brief the next answer uses.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\n\nimport { describeMeasured } from './audit.js';\nimport { findActive } from './briefs.js';\nimport { briefIdFromBranch } from './branch.js';\nimport { titleOf } from './context.js';\nimport { createReader } from './reader.js';\nimport { buildContext, checkPaths, raiseEscalation, runAudit } from './round.js';\nimport type { BriefRow } from './types.js';\nimport {\n createMcpServer,\n serveLines,\n toolError,\n unknownArguments,\n type JsonObject,\n type PromptDefinition,\n type ToolDefinition,\n type ToolOutcome,\n} from './vendor/spec-core/jsonrpc/index.js';\nimport { describeActive, openWorkspace, version, type CliIO, type Options, type Workspace } from './workspace.js';\n\nconst reader = createReader();\n\nexport const INSTRUCTIONS =\n 'spec-harness keeps a round of work inside the brief a person approved. ' +\n 'Call start_round before working: it returns the brief, the scope you may write, what you must not touch, the rules in force and the documents the brief cites. ' +\n 'Call check_path before writing a file you are unsure of. A protected file needs a ruling: call request_escalation and stop until a person rules. ' +\n 'Call audit_round when the work is done, and fix what it reports; archiving is a person\\'s decision. ' +\n 'For the architecture rules themselves, spec-guard\\'s own server answers get_architectural_rules.';\n\nconst briefProperty = { type: 'string', description: 'The brief id. Omit to use the brief the branch or SPEC_BRIEF names.' };\nconst baseProperty = {\n type: 'string',\n description: 'The branch the round is measured from, whose allowed signers a ruling is verified against. Omit to use the configured base.',\n};\n\n/** The `base` argument, or the tool error for one that is not a string. */\nfunction baseOf(args: JsonObject): { base: string | undefined } | ToolOutcome {\n const base = args['base'];\n if (base !== undefined && typeof base !== 'string') return toolError('\"base\" must be a string.');\n return { base };\n}\n\nasync function round(workspace: Workspace, env: CliIO['env'], id: unknown): Promise<{ brief: BriefRow; briefs: BriefRow[] } | ToolOutcome> {\n if (id !== undefined && typeof id !== 'string') return toolError('\"brief\" must be a string.');\n const briefs = await workspace.siblings.briefs();\n const fromBranch = workspace.branch === null ? null : briefIdFromBranch(workspace.config.branches, workspace.branch);\n const active = findActive(briefs, { flag: id, environment: env['SPEC_BRIEF'], branch: fromBranch });\n const { brief, note, problem } = describeActive(active);\n if (problem !== null) return toolError(problem);\n if (brief === null) return toolError(note ?? 'no brief is named');\n return { brief, briefs };\n}\n\nexport function tools(workspace: Workspace, env: CliIO['env']): ToolDefinition[] {\n const readOnly = { readOnlyHint: true, idempotentHint: true, openWorldHint: false };\n return [\n {\n descriptor: {\n name: 'start_round',\n title: 'Start a round',\n description:\n 'Everything needed to start the round: the brief in full, the files it may write and must not change, signed rulings, the briefs it depends on, the architecture rules in force for its scope, and the documents it cites.',\n inputSchema: { type: 'object', properties: { brief: briefProperty, base: baseProperty }, additionalProperties: false },\n annotations: readOnly,\n },\n async call(args: JsonObject): Promise<ToolOutcome> {\n const unknown = unknownArguments(args, ['brief', 'base']);\n if (unknown) return unknown;\n const base = baseOf(args);\n if ('text' in base) return base;\n const found = await round(workspace, env, args['brief']);\n if ('text' in found) return found;\n const packet = await buildContext(workspace, found.brief, found.briefs, reader, base.base);\n return {\n text: packet.markdown,\n structured: {\n brief: found.brief.id,\n included: [...packet.included],\n omitted: [...packet.omitted],\n unresolved: [...packet.unresolved],\n unclosedFrontMatter: [...packet.unclosedFrontMatter],\n unreadableFrontMatter: [...packet.unreadableFrontMatter],\n unreadableScope: [...packet.unreadableScope],\n unreadableProtections: [...packet.unreadableProtections],\n unreadableRulingPaths: [...packet.unreadableRulingPaths],\n },\n };\n },\n },\n {\n descriptor: {\n name: 'check_path',\n title: 'May the round write these files',\n description:\n 'For each path: allowed (in scope, or covered by a signed ruling), outside the scope, or refused (protected by the brief). Works for files that do not exist yet.',\n inputSchema: {\n type: 'object',\n properties: {\n paths: { type: 'array', items: { type: 'string' }, description: 'Paths relative to the project root, or absolute inside it.' },\n brief: briefProperty,\n base: baseProperty,\n },\n required: ['paths'],\n additionalProperties: false,\n },\n annotations: readOnly,\n },\n async call(args: JsonObject): Promise<ToolOutcome> {\n const unknown = unknownArguments(args, ['paths', 'brief', 'base']);\n if (unknown) return unknown;\n const paths = args['paths'];\n if (!Array.isArray(paths) || !paths.every((p) => typeof p === 'string') || paths.length === 0) {\n return toolError('\"paths\" must be a non-empty array of strings.');\n }\n const base = baseOf(args);\n if ('text' in base) return base;\n const found = await round(workspace, env, args['brief']);\n if ('text' in found) return found;\n const decisions = await checkPaths(workspace, found.brief, undefined, paths as string[], workspace.root, reader, base.base);\n const text = decisions.map((d) => `${d.verdict}: ${d.message}${d.verdict === 'allow' ? '' : `. Next: ${d.hint}`}`).join('\\n');\n return { text, structured: { decisions: decisions.map((d) => ({ ...d })) } };\n },\n },\n {\n descriptor: {\n name: 'request_escalation',\n title: 'Ask a person to rule',\n description:\n 'When the round cannot be finished without changing something the brief protects, record the request and stop. A person reads the memo and rules; only a ruling they sign lets the change through.',\n inputSchema: {\n type: 'object',\n properties: {\n paths: { type: 'array', items: { type: 'string' }, description: 'The protected files the round needs to change.' },\n reason: { type: 'string', description: 'Why the round cannot be done without them.' },\n options: {\n type: 'array',\n items: { type: 'object', properties: { label: { type: 'string' }, consequence: { type: 'string' } }, required: ['label', 'consequence'] },\n description: 'The choices the person has, each with what it costs.',\n },\n recommendation: { type: 'string', description: 'Which option you recommend, and why.' },\n brief: briefProperty,\n },\n required: ['paths', 'reason'],\n additionalProperties: false,\n },\n annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: false },\n },\n async call(args: JsonObject): Promise<ToolOutcome> {\n const unknown = unknownArguments(args, ['paths', 'reason', 'options', 'recommendation', 'brief']);\n if (unknown) return unknown;\n const paths = args['paths'];\n const reason = args['reason'];\n if (!Array.isArray(paths) || !paths.every((p) => typeof p === 'string') || paths.length === 0) return toolError('\"paths\" must be a non-empty array of strings.');\n if (typeof reason !== 'string' || reason.trim() === '') return toolError('\"reason\" is required.');\n const rawOptions = args['options'] ?? [];\n if (!Array.isArray(rawOptions)) return toolError('\"options\" must be an array.');\n const options = rawOptions.map((o) => ({ label: String((o as JsonObject)['label'] ?? ''), consequence: String((o as JsonObject)['consequence'] ?? '') }));\n const recommendation = args['recommendation'];\n if (recommendation !== undefined && typeof recommendation !== 'string') return toolError('\"recommendation\" must be a string.');\n const found = await round(workspace, env, args['brief']);\n if ('text' in found) return found;\n const { request, memo } = await raiseEscalation(workspace, found.brief, { paths: paths as string[], reason, options, recommendation: recommendation ?? null });\n return { text: `${memo}\\nStop here until a person rules on ${request.id}.`, structured: { id: request.id } };\n },\n },\n {\n descriptor: {\n name: 'audit_round',\n title: 'Audit the round',\n description:\n 'Did the round stay inside the lines: what the archive would refuse (open boxes, protected files, files outside the scope), the brief\\'s own assertions, unverified rulings, and every dependency added.',\n inputSchema: { type: 'object', properties: { brief: briefProperty, base: baseProperty }, additionalProperties: false },\n annotations: readOnly,\n },\n async call(args: JsonObject): Promise<ToolOutcome> {\n const unknown = unknownArguments(args, ['brief', 'base']);\n if (unknown) return unknown;\n const base = baseOf(args);\n if ('text' in base) return base;\n const found = await round(workspace, env, args['brief']);\n if ('text' in found) return found;\n const result = await runAudit(workspace, found.brief, reader, base.base);\n const { counts, findings, measured } = result.report;\n // What was measured beside what was found: \"nothing found\" from an\n // audit that ran no goal is not a round that is done.\n const text =\n findings.length === 0\n ? 'The audit found nothing in what it measured.'\n : findings.map((f) => `${f.severity} ${f.rule}: ${f.message}. Next: ${f.hint}`).join('\\n');\n return {\n text: `${text}\\n\\n${describeMeasured(measured)}\\n\\n${counts.error} error(s), ${counts.warning} warning(s), ${counts.note} note(s)`,\n structured: { counts, measured: { ...measured }, findings: findings.map((f) => ({ ...f })) },\n };\n },\n },\n {\n descriptor: {\n name: 'list_rounds',\n title: 'List the rounds',\n description: 'The live briefs: status, wave, what each waits on, and which are ready to start.',\n inputSchema: { type: 'object', properties: {}, additionalProperties: false },\n annotations: readOnly,\n },\n async call(args: JsonObject): Promise<ToolOutcome> {\n const unknown = unknownArguments(args, []);\n if (unknown) return unknown;\n const live = (await workspace.siblings.briefs()).filter((brief) => brief.phase === 'live');\n const text = live\n .map((b) => `${[b.id, titleOf(b)].filter((part) => part !== null).join(' ')} - ${b.status ?? 'unknown'}, wave ${b.wave ?? '-'}, ${b.ready ? 'ready' : `waits on ${b.waitingOn.join(', ')}`}`)\n .join('\\n');\n return { text: text === '' ? 'No live brief.' : text, structured: { briefs: live.map((b) => ({ ...b, title: titleOf(b) })) } };\n },\n },\n ];\n}\n\nconst SKILLS: readonly { name: string; title: string; argument: string; description: string }[] = [\n { name: 'draft-brief', title: 'Draft a brief', argument: 'request', description: 'What the round should achieve, in the person\\'s words.' },\n { name: 'split-goal', title: 'Split a goal into rounds', argument: 'goal', description: 'The goal to split.' },\n { name: 'run-round', title: 'Run a round', argument: 'brief', description: 'The brief id.' },\n { name: 'close-round', title: 'Close a round', argument: 'brief', description: 'The brief id.' },\n];\n\nfunction skillText(name: string): string {\n const file = fileURLToPath(new URL(`../skills/${name}/SKILL.md`, import.meta.url));\n const text = existsSync(file) ? readFileSync(file, 'utf8') : '';\n return text.replace(/^---\\r?\\n[\\s\\S]*?\\r?\\n---\\r?\\n/, '').trim();\n}\n\nexport function prompts(): PromptDefinition[] {\n return SKILLS.map((skill) => ({\n descriptor: {\n name: skill.name,\n title: skill.title,\n arguments: [{ name: skill.argument, description: skill.description, required: false }],\n },\n async get(args: Readonly<Record<string, string>>): Promise<JsonObject> {\n const given = args[skill.argument];\n const text = `${skillText(skill.name)}${given === undefined || given === '' ? '' : `\\n\\n${skill.argument}: ${given}`}`;\n return { description: skill.title, messages: [{ role: 'user', content: { type: 'text', text } }] };\n },\n }));\n}\n\nexport async function mcpCommand(options: Options, io: CliIO): Promise<number> {\n // Claude Code starts a plugin's server in the plugin's directory, and names\n // the project in CLAUDE_PROJECT_DIR; the server answers for the project.\n const workspace = await openWorkspace({ ...options, root: options.root ?? (io.env['CLAUDE_PROJECT_DIR'] || undefined) }, io);\n const handle = createMcpServer({ name: 'spec-harness', version: version(), instructions: INSTRUCTIONS, tools: tools(workspace, io.env), prompts: prompts() });\n await serveLines(process.stdin, (line) => process.stdout.write(`${line}\\n`), handle);\n return 0;\n}\n"]}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `spec-harness mcp`: the round's questions, served over the Model Context\n * Protocol on stdio (ADR-0008). The protocol is spec-core's `jsonrpc` module;\n * this file is the five tools, the four prompts, and nothing else. Every\n * request reads the repository afresh: a brief edited a minute ago is the\n * brief the next answer uses.\n */\n\nimport { readFileSync } from 'node:fs';\nimport { Readable } from 'node:stream';\nimport { fileURLToPath } from 'node:url';\n\nimport { describeMeasured } from './audit.js';\nimport { findActive } from './briefs.js';\nimport { briefIdFromBranch } from './branch.js';\nimport { titleOf } from './context.js';\nimport { createReader } from './reader.js';\nimport { buildContext, checkPaths, raiseEscalation, runAudit } from './round.js';\nimport type { BriefRow } from './types.js';\nimport {\n createMcpServer,\n serveLines,\n toolError,\n unknownArguments,\n type JsonObject,\n type PromptDefinition,\n type ToolDefinition,\n type ToolOutcome,\n} from './vendor/spec-core/jsonrpc/index.js';\nimport { describeActive, openWorkspace, version, type CliIO, type Options, type Workspace } from './workspace.js';\n\nconst reader = createReader();\n\nexport const INSTRUCTIONS =\n 'spec-harness keeps a round of work inside the brief a person approved. ' +\n 'Call start_round before working: it returns the brief, the scope you may write, what you must not touch, the rules in force and the documents the brief cites. ' +\n 'Call check_path before writing a file you are unsure of. A protected file needs a ruling: call request_escalation and stop until a person rules. ' +\n 'Call audit_round when the work is done, and fix what it reports; archiving is a person\\'s decision. ' +\n 'For the architecture rules themselves, spec-guard\\'s own server answers get_architectural_rules.';\n\nconst briefProperty = { type: 'string', description: 'The brief id. Omit to use the brief the branch or SPEC_BRIEF names.' };\nconst baseProperty = {\n type: 'string',\n description: 'The branch the round is measured from, whose allowed signers a ruling is verified against. Omit to use the configured base.',\n};\n\n/** The `base` argument, or the tool error for one that is not a string. */\nfunction baseOf(args: JsonObject): { base: string | undefined } | ToolOutcome {\n const base = args['base'];\n if (base !== undefined && typeof base !== 'string') return toolError('\"base\" must be a string.');\n return { base };\n}\n\nasync function round(workspace: Workspace, env: CliIO['env'], id: unknown): Promise<{ brief: BriefRow; briefs: BriefRow[] } | ToolOutcome> {\n if (id !== undefined && typeof id !== 'string') return toolError('\"brief\" must be a string.');\n const briefs = await workspace.siblings.briefs();\n const fromBranch = briefIdFromBranch(workspace.config.branches, workspace.branch);\n const active = findActive(briefs, { flag: id, environment: env['SPEC_BRIEF'], branch: fromBranch });\n const { brief, note, problem } = describeActive(active);\n if (problem !== null) return toolError(problem);\n if (brief === null) return toolError(note);\n return { brief, briefs };\n}\n\nexport function tools(workspace: Workspace, env: CliIO['env']): ToolDefinition[] {\n const readOnly = { readOnlyHint: true, idempotentHint: true, openWorldHint: false };\n return [\n {\n descriptor: {\n name: 'start_round',\n title: 'Start a round',\n description:\n 'Everything needed to start the round: the brief in full, the files it may write and must not change, signed rulings, the briefs it depends on, the architecture rules in force for its scope, and the documents it cites.',\n inputSchema: { type: 'object', properties: { brief: briefProperty, base: baseProperty }, additionalProperties: false },\n annotations: readOnly,\n },\n async call(args: JsonObject): Promise<ToolOutcome> {\n const unknown = unknownArguments(args, ['brief', 'base']);\n if (unknown) return unknown;\n const base = baseOf(args);\n if ('text' in base) return base;\n const found = await round(workspace, env, args['brief']);\n if ('text' in found) return found;\n const packet = await buildContext(workspace, found.brief, found.briefs, reader, base.base);\n return {\n text: packet.markdown,\n structured: {\n brief: found.brief.id,\n included: [...packet.included],\n omitted: [...packet.omitted],\n unresolved: [...packet.unresolved],\n unclosedFrontMatter: [...packet.unclosedFrontMatter],\n unreadableFrontMatter: [...packet.unreadableFrontMatter],\n unreadableScope: [...packet.unreadableScope],\n unreadableProtections: [...packet.unreadableProtections],\n unreadableRulingPaths: [...packet.unreadableRulingPaths],\n },\n };\n },\n },\n {\n descriptor: {\n name: 'check_path',\n title: 'May the round write these files',\n description:\n 'For each path: allowed (in scope, or covered by a signed ruling), outside the scope, or refused (protected by the brief). Works for files that do not exist yet.',\n inputSchema: {\n type: 'object',\n properties: {\n paths: { type: 'array', items: { type: 'string' }, description: 'Paths relative to the project root, or absolute inside it.' },\n brief: briefProperty,\n base: baseProperty,\n },\n required: ['paths'],\n additionalProperties: false,\n },\n annotations: readOnly,\n },\n async call(args: JsonObject): Promise<ToolOutcome> {\n const unknown = unknownArguments(args, ['paths', 'brief', 'base']);\n if (unknown) return unknown;\n const paths = args['paths'];\n if (!Array.isArray(paths) || !paths.every((p) => typeof p === 'string') || paths.length === 0) {\n return toolError('\"paths\" must be a non-empty array of strings.');\n }\n const base = baseOf(args);\n if ('text' in base) return base;\n const found = await round(workspace, env, args['brief']);\n if ('text' in found) return found;\n const decisions = await checkPaths(workspace, found.brief, undefined, paths as string[], workspace.root, reader, base.base);\n const text = decisions.map((d) => `${d.verdict}: ${d.message}${d.verdict === 'allow' ? '' : `. Next: ${d.hint}`}`).join('\\n');\n return { text, structured: { decisions: decisions.map((d) => ({ ...d })) } };\n },\n },\n {\n descriptor: {\n name: 'request_escalation',\n title: 'Ask a person to rule',\n description:\n 'When the round cannot be finished without changing something the brief protects, record the request and stop. A person reads the memo and rules; only a ruling they sign lets the change through.',\n inputSchema: {\n type: 'object',\n properties: {\n paths: { type: 'array', items: { type: 'string' }, description: 'The protected files the round needs to change.' },\n reason: { type: 'string', description: 'Why the round cannot be done without them.' },\n options: {\n type: 'array',\n items: { type: 'object', properties: { label: { type: 'string' }, consequence: { type: 'string' } }, required: ['label', 'consequence'] },\n description: 'The choices the person has, each with what it costs.',\n },\n recommendation: { type: 'string', description: 'Which option you recommend, and why.' },\n brief: briefProperty,\n },\n required: ['paths', 'reason'],\n additionalProperties: false,\n },\n annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: false },\n },\n async call(args: JsonObject): Promise<ToolOutcome> {\n const unknown = unknownArguments(args, ['paths', 'reason', 'options', 'recommendation', 'brief']);\n if (unknown) return unknown;\n const paths = args['paths'];\n const reason = args['reason'];\n if (!Array.isArray(paths) || !paths.every((p) => typeof p === 'string') || paths.length === 0) return toolError('\"paths\" must be a non-empty array of strings.');\n if (typeof reason !== 'string' || reason.trim() === '') return toolError('\"reason\" is required.');\n const rawOptions = args['options'] ?? [];\n if (!Array.isArray(rawOptions)) return toolError('\"options\" must be an array.');\n const options = rawOptions.map((o) => ({ label: String((o as JsonObject)['label'] ?? ''), consequence: String((o as JsonObject)['consequence'] ?? '') }));\n const recommendation = args['recommendation'];\n if (recommendation !== undefined && typeof recommendation !== 'string') return toolError('\"recommendation\" must be a string.');\n const found = await round(workspace, env, args['brief']);\n if ('text' in found) return found;\n const { request, memo } = await raiseEscalation(workspace, found.brief, { paths: paths as string[], reason, options, recommendation: recommendation ?? null });\n return { text: `${memo}\\nStop here until a person rules on ${request.id}.`, structured: { id: request.id } };\n },\n },\n {\n descriptor: {\n name: 'audit_round',\n title: 'Audit the round',\n description:\n 'Did the round stay inside the lines: what the archive would refuse (open boxes, protected files, files outside the scope), the brief\\'s own assertions, unverified rulings, and every dependency added.',\n inputSchema: { type: 'object', properties: { brief: briefProperty, base: baseProperty }, additionalProperties: false },\n annotations: readOnly,\n },\n async call(args: JsonObject): Promise<ToolOutcome> {\n const unknown = unknownArguments(args, ['brief', 'base']);\n if (unknown) return unknown;\n const base = baseOf(args);\n if ('text' in base) return base;\n const found = await round(workspace, env, args['brief']);\n if ('text' in found) return found;\n const result = await runAudit(workspace, found.brief, reader, base.base);\n const { counts, findings, measured } = result.report;\n // What was measured beside what was found: \"nothing found\" from an\n // audit that ran no goal is not a round that is done.\n const text =\n findings.length === 0\n ? 'The audit found nothing in what it measured.'\n : findings.map((f) => `${f.severity} ${f.rule}: ${f.message}. Next: ${f.hint}`).join('\\n');\n return {\n text: `${text}\\n\\n${describeMeasured(measured)}\\n\\n${counts.error} error(s), ${counts.warning} warning(s), ${counts.note} note(s)`,\n structured: { counts, measured: { ...measured }, findings: findings.map((f) => ({ ...f })) },\n };\n },\n },\n {\n descriptor: {\n name: 'list_rounds',\n title: 'List the rounds',\n description: 'The live briefs: status, wave, what each waits on, and which are ready to start.',\n inputSchema: { type: 'object', properties: {}, additionalProperties: false },\n annotations: readOnly,\n },\n async call(args: JsonObject): Promise<ToolOutcome> {\n const unknown = unknownArguments(args, []);\n if (unknown) return unknown;\n const live = (await workspace.siblings.briefs()).filter((brief) => brief.phase === 'live');\n const text = live\n .map((b) => `${[b.id, titleOf(b)].filter((part) => part !== null).join(' ')} - ${b.status ?? 'unknown'}, wave ${b.wave ?? '-'}, ${b.ready ? 'ready' : `waits on ${b.waitingOn.join(', ')}`}`)\n .join('\\n');\n return { text: text === '' ? 'No live brief.' : text, structured: { briefs: live.map((b) => ({ ...b, title: titleOf(b) })) } };\n },\n },\n ];\n}\n\nconst SKILLS: readonly { name: string; title: string; argument: string; description: string }[] = [\n { name: 'draft-brief', title: 'Draft a brief', argument: 'request', description: 'What the round should achieve, in the person\\'s words.' },\n { name: 'split-goal', title: 'Split a goal into rounds', argument: 'goal', description: 'The goal to split.' },\n { name: 'run-round', title: 'Run a round', argument: 'brief', description: 'The brief id.' },\n { name: 'close-round', title: 'Close a round', argument: 'brief', description: 'The brief id.' },\n];\n\nfunction skillText(name: string): string {\n // The package ships the skills beside dist/ (package.json's files), so one\n // that cannot be read is a broken install, and the request fails saying so\n // rather than serving an empty prompt.\n const text = readFileSync(fileURLToPath(new URL(`../skills/${name}/SKILL.md`, import.meta.url)), 'utf8');\n // Every skill opens with its front matter (tests/source.test.ts), so the\n // anchor is equivalent to its mutant here.\n return text.replace(/^---\\r?\\n[\\s\\S]*?\\r?\\n---\\r?\\n/, '').trim();\n}\n\nexport function prompts(): PromptDefinition[] {\n return SKILLS.map((skill) => ({\n descriptor: {\n name: skill.name,\n title: skill.title,\n arguments: [{ name: skill.argument, description: skill.description, required: false }],\n },\n async get(args: Readonly<Record<string, string>>): Promise<JsonObject> {\n const given = args[skill.argument];\n const text = `${skillText(skill.name)}${given === undefined || given === '' ? '' : `\\n\\n${skill.argument}: ${given}`}`;\n return { description: skill.title, messages: [{ role: 'user', content: { type: 'text', text } }] };\n },\n }));\n}\n\nexport async function mcpCommand(options: Options, io: CliIO): Promise<number> {\n // Claude Code starts a plugin's server in the plugin's directory, and names\n // the project in CLAUDE_PROJECT_DIR; the server answers for the project.\n const workspace = await openWorkspace({ ...options, root: options.root ?? (io.env['CLAUDE_PROJECT_DIR'] || undefined) }, io);\n const handle = createMcpServer({ name: 'spec-harness', version: version(), instructions: INSTRUCTIONS, tools: tools(workspace, io.env), prompts: prompts() });\n // A session handed over whole, as a test hands it, is served as a client\n // would stream it on stdin. Only the built command line serves\n // process.stdin, and the suite spawns it from dist/, outside the sweep\n // (tests/integration/mcp.test.ts), so a mutant that ignores it survives.\n const input = io.stdin === undefined ? process.stdin : Readable.from([Buffer.from(await io.stdin())]);\n await serveLines(input, (line) => io.stdout.write(`${line}\\n`), handle);\n return 0;\n}\n"]}
package/dist/setup.js CHANGED
@@ -68,13 +68,13 @@ export async function plan(workspace, options, env) {
68
68
  // The base rounds merge into, decided once: spec-harness verifies rulings
69
69
  // against the allowed signers on it, and spec-brief's archive measures the
70
70
  // round from it. A base a person wrote, in either file, is kept.
71
+ // openWorkspace loaded this file and refused one that is not a JSON object,
72
+ // so here it is one, or there is none.
71
73
  const harnessFile = join(root, CONFIG_FILE);
72
- const harness = await readJson(harnessFile);
73
- const namedBase = harness !== null && harness !== 'unreadable' && typeof harness['base'] === 'string' ? harness['base'] : null;
74
- const choice = harness === 'unreadable' || namedBase !== null
75
- ? null
76
- : chooseBase({ remoteDefault: await remoteDefault(root), branch: workspace.branch, branches: await localBranches(root) });
77
- const base = namedBase ?? choice?.base ?? null;
74
+ const harness = (await readJson(harnessFile));
75
+ const namedBase = harness !== null && typeof harness['base'] === 'string' ? harness['base'] : null;
76
+ const choice = namedBase === null ? chooseBase({ remoteDefault: await remoteDefault(root), branch: workspace.branch, branches: await localBranches(root) }) : null;
77
+ const base = choice === null ? namedBase : choice.base;
78
78
  // spec-brief first: every other setting is derived from its directories.
79
79
  const briefConfig = SPEC_BRIEF_CONFIGS.find((name) => existsSync(join(root, name)));
80
80
  const brief = workspace.siblings.locate('spec-brief');
@@ -115,6 +115,8 @@ export async function plan(workspace, options, env) {
115
115
  else {
116
116
  const file = join(root, briefConfig);
117
117
  const config = await readJson(file);
118
+ // The file was found a moment ago, so null is one removed in between; and
119
+ // a string has neither key, so these checks are there for the type.
118
120
  if (config !== null && config !== 'unreadable') {
119
121
  if (typeof config['briefs'] === 'string')
120
122
  briefs = config['briefs'];
@@ -171,18 +173,16 @@ export async function plan(workspace, options, env) {
171
173
  }
172
174
  }
173
175
  // spec-harness's own file, naming the base the rounds merge into: every
174
- // ruling is verified against the allowed signers on it.
175
- if (harness === 'unreadable') {
176
- steps.push({ file: CONFIG_FILE, action: 'advise', detail: 'cannot be read as JSON; name the base rounds merge into as "base" by hand' });
177
- }
178
- else if (namedBase !== null) {
176
+ // ruling is verified against the allowed signers on it. No choice was made
177
+ // only where the file names a base.
178
+ if (choice === null) {
179
179
  steps.push({ file: CONFIG_FILE, action: 'keep', detail: `rounds are measured from ${namedBase}` });
180
180
  }
181
- else if (base === null || choice === null) {
182
- steps.push({ file: CONFIG_FILE, action: 'advise', detail: choice?.detail ?? 'name the base rounds merge into as "base"' });
181
+ else if (choice.base === null) {
182
+ steps.push({ file: CONFIG_FILE, action: 'advise', detail: choice.detail });
183
183
  }
184
184
  else {
185
- const content = { ...harness, base };
185
+ const content = { ...harness, base: choice.base };
186
186
  steps.push({ file: CONFIG_FILE, action: harness === null ? 'create' : 'update', detail: choice.detail, apply: () => writeAtomic(harnessFile, stringify(content)) });
187
187
  }
188
188
  // The agent's hooks and server, unless the plugin brings them: Claude Code
package/dist/setup.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAErE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EACL,UAAU,EACV,eAAe,EACf,aAAa,EACb,eAAe,EACf,aAAa,EACb,aAAa,EAEb,gBAAgB,EAChB,UAAU,EACV,UAAU,EACV,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,QAAQ,EACR,cAAc,EACd,cAAc,EACd,cAAc,EACd,MAAM,EACN,kBAAkB,EAClB,eAAe,EACf,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,cAAc,IAAI,QAAQ,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAA4C,MAAM,gBAAgB,CAAC;AAcpH,SAAS,SAAS,CAAC,KAAc;IAC/B,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAC/C,CAAC;AAED,MAAM,aAAa,GAAG,gCAAgC,MAAM,kHAAkH,CAAC;AAE/K,4EAA4E;AAC5E,SAAS,eAAe,CAAC,MAAe,EAAE,IAAmB;IAC3D,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5C,IAAI,IAAI,KAAK,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,4BAA4B,IAAI,gIAAgI,CAAC,CAAC;IAChM,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,GAAG;;;CAGlB,CAAC;AAEF,gHAAgH;AAChH,SAAS,KAAK,CAAC,IAAY,EAAE,IAAY;IACvC,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACpC,OAAO,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC5F,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAY;IACxC,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;IAChD,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,uEAAuE,EAAE,EAAE,IAAI,EAAE,CAAC;IAChJ,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC/B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC;IACxE,iFAAiF;IACjF,IAAI,CAAC,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,cAAc,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC;IAC9G,0EAA0E;IAC1E,sCAAsC;IACtC,8FAA8F;IAC9F,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;IAClF,OAAO,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC;AACnE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,SAAoB,EAAE,OAAgB,EAAE,GAAiB;IAClF,MAAM,EAAE,IAAI,EAAE,GAAG,SAAS,CAAC;IAC3B,MAAM,KAAK,GAAW,EAAE,CAAC;IAEzB,0EAA0E;IAC1E,2EAA2E;IAC3E,iEAAiE;IACjE,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC5C,MAAM,SAAS,GAAG,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,YAAY,IAAI,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/H,MAAM,MAAM,GACV,OAAO,KAAK,YAAY,IAAI,SAAS,KAAK,IAAI;QAC5C,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,UAAU,CAAC,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC9H,MAAM,IAAI,GAAG,SAAS,IAAI,MAAM,EAAE,IAAI,IAAI,IAAI,CAAC;IAE/C,yEAAyE;IACzE,MAAM,WAAW,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACpF,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IACtD,IAAI,MAAM,GAAG,QAAQ,CAAC;IACtB,IAAI,OAAO,GAAG,gBAAgB,CAAC;IAC/B,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QAC9B,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC3B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;YAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC;YAC/C,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,kBAAkB;gBACxB,MAAM,EAAE,KAAK;gBACb,MAAM,EAAE,+EAA+E;gBACvF,KAAK,EAAE,KAAK,IAAI,EAAE;oBAChB,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC;oBACtD,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC;wBAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBACtF,CAAC;aACF,CAAC,CAAC;YACH,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,kBAAkB;gBACxB,MAAM,EAAE,QAAQ;gBAChB,MAAM,EAAE,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC;gBACnC,KAAK,EAAE,KAAK,IAAI,EAAE;oBAChB,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC;oBACxC,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,YAAY;wBAAE,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;oBAC1H,MAAM,MAAM,GAAG,cAAc,CAAC,OAAO,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;oBAC/D,IAAI,MAAM,KAAK,IAAI;wBAAE,MAAM,WAAW,CAAC,OAAO,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;gBACrE,CAAC;aACF,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QACnF,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;QACrC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,YAAY,EAAE,CAAC;YAC/C,IAAI,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,QAAQ;gBAAE,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;YACpE,OAAO,GAAG,OAAO,MAAM,CAAC,SAAS,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,UAAU,CAAC;QAC5F,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,MAAM,qBAAqB,OAAO,GAAG,EAAE,CAAC,CAAC;QAC9G,2EAA2E;QAC3E,8CAA8C;QAC9C,IAAI,MAAM,KAAK,YAAY,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YAC/C,MAAM,OAAO,GAAG,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,iCAAiC,IAAI,KAAK,CAAC;YAChF,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,4CAA4C,MAAM,KAAK,OAAO,UAAU,EAAE,CAAC,CAAC;QACxI,CAAC;aAAM,CAAC;YACN,MAAM,YAAY,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;YACtC,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC;YAC1D,MAAM,QAAQ,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;YACzC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;gBACpB,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,sCAAsC,MAAM,CAAE,MAAM,CAAC,WAAW,CAAU,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACnH,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,kCAAkC,IAAI,EAAE,EAAE,CAAC,CAAC;YACtG,CAAC;iBAAM,CAAC;gBACN,MAAM,MAAM,GAAG,eAAe,CAAC,CAAC,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;gBAC3F,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC;YACjH,CAAC;QACH,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,yEAAyE;IACzE,gDAAgD;IAChD,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IACtD,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU;QAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IAChH,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC3B,MAAM,WAAW,GAAG,GAAG,OAAO,KAAK,CAAC;QACpC,MAAM,SAAS,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QAClF,MAAM,QAAQ,GAAG,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC7F,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,KAAK,YAAY,IAAI,YAAY,IAAI,QAAQ,EAAE,CAAC;YAC/E,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,qGAAqG,WAAW,oCAAoC,EAAE,CAAC,CAAC;QACvN,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,GAAG,SAAS,IAAI,kBAAkB,CAAC;YAC7C,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;YACjD,IAAI,OAAO,KAAK,YAAY,EAAE,CAAC;gBAC7B,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,oDAAoD,WAAW,YAAY,EAAE,CAAC,CAAC;YAC9H,CAAC;iBAAM,CAAC;gBACN,MAAM,MAAM,GAAG,cAAc,CAAC,OAAO,IAAI,EAAE,EAAE,WAAW,CAAC,CAAC;gBAC1D,MAAM,MAAM,GAAG,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,gBAAgB,CAAC,OAAO,IAAI,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC,CAAC;gBAC5G,IAAI,MAAM,KAAK,IAAI;oBAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;;oBAC7D,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC;YACnJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,wEAAwE;IACxE,wDAAwD;IACxD,IAAI,OAAO,KAAK,YAAY,EAAE,CAAC;QAC7B,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,2EAA2E,EAAE,CAAC,CAAC;IAC3I,CAAC;SAAM,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;QAC9B,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,4BAA4B,SAAS,EAAE,EAAE,CAAC,CAAC;IACrG,CAAC;SAAM,IAAI,IAAI,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QAC5C,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,IAAI,2CAA2C,EAAE,CAAC,CAAC;IAC7H,CAAC;SAAM,CAAC;QACN,MAAM,OAAO,GAAS,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,CAAC;QAC3C,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,WAAW,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IACtK,CAAC;IAED,2EAA2E;IAC3E,uEAAuE;IACvE,sCAAsC;IACtC,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;IAC9D,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;IAC5D,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,YAAY,CAAC,CAAC;IAC9C,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,OAAO,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;QACrC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,uBAAuB,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,eAAe,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IACxI,CAAC;SAAM,IAAI,QAAQ,KAAK,YAAY,EAAE,CAAC;QACrC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,uBAAuB,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,yFAAyF,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC;IACjM,CAAC;SAAM,CAAC;QACN,MAAM,MAAM,GAAG,mBAAmB,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;QACnD,IAAI,MAAM,KAAK,IAAI;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,uBAAuB,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,+BAA+B,EAAE,CAAC,CAAC;;YAE1H,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,uBAAuB;gBAC7B,MAAM,EAAE,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ;gBAC/C,MAAM,EAAE,0KAA0K;gBAClL,KAAK,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,YAAY,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;aAC1D,CAAC,CAAC;IACP,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC;IACpC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,OAAO,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;QACrC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,eAAe,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IAC7H,CAAC;SAAM,IAAI,GAAG,KAAK,YAAY,EAAE,CAAC;QAChC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,6DAA6D,EAAE,CAAC,CAAC;IAC7H,CAAC;SAAM,CAAC;QACN,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;QACnC,IAAI,MAAM,KAAK,IAAI;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,uCAAuC,EAAE,CAAC,CAAC;;YAEtH,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,WAAW;gBACjB,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ;gBAC1C,MAAM,EAAE,+IAA+I;gBACvJ,KAAK,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,OAAO,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;aACrD,CAAC,CAAC;IACP,CAAC;IAED,0EAA0E;IAC1E,4EAA4E;IAC5E,wEAAwE;IACxE,yEAAyE;IACzE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,uEAAuE;IACvE,yEAAyE;IACzE,2EAA2E;IAC3E,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QAClD,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACtF,CAAC;SAAM,IAAI,IAAI,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC;QACjC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,2BAA2B,EAAE,CAAC,CAAC;IACvF,CAAC;SAAM,IAAI,IAAI,CAAC,KAAK,KAAK,OAAO,EAAE,CAAC;QAClC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACnF,CAAC;SAAM,IAAI,IAAI,CAAC,KAAK,KAAK,OAAO,EAAE,CAAC;QAClC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,2CAA2C,aAAa,SAAS,EAAE,CAAC,CAAC;IAC/H,CAAC;SAAM,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,6DAA6D;YACrE,KAAK,EAAE,KAAK,IAAI,EAAE;gBAChB,MAAM,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBACpD,MAAM,WAAW,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;gBACxC,MAAM,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;YAC/B,CAAC;SACF,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAClF,CAAC;IAED,0EAA0E;IAC1E,yCAAyC;IACzC,KAAK,CAAC,IAAI,CAAC;QACT,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,cAAc;QAC7C,MAAM,EAAE,QAAQ;QAChB,MAAM,EAAE,8QAA8Q,kBAAkB,EAAE;KAC3S,CAAC,CAAC;IACH,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAgB,EAAE,EAAS;IAC3D,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;IACrD,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBAC7B,IAAI,CAAC;oBACH,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;gBACrB,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,IAAI,CAAC,IAAI,KAAM,KAAe,CAAC,OAAO,IAAI,CAAC,CAAC;oBAC7E,OAAO,UAAU,CAAC;gBACpB,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;QAC9B,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACxI,CAAC;SAAM,CAAC;QACN,KAAK,MAAM,IAAI,IAAI,KAAK;YAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,IAAI,aAAa,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;QAC9G,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC;YAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,oEAAoE,CAAC,CAAC;IAC9J,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC","sourcesContent":["/**\n * `spec-harness init`: the family configured to agree, from the first day.\n *\n * The integration faults found when the tools were first run together were all\n * configuration: spec-graph read an archived brief as a retired decision and\n * reported every brief that depended on one; spec-guard executed an archived\n * brief's assertions. Each tool can be told the truth; `init` tells each of\n * them the same truth, derived from spec-brief's own settings.\n *\n * It prints a plan and changes nothing until `--write`. It merges into files\n * that exist and never replaces what a person wrote; a git hook the\n * repository already has is left alone, with the line to add printed instead.\n */\n\nimport { existsSync, statSync } from 'node:fs';\nimport { chmod, mkdir, readFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, join, relative, sep } from 'node:path';\n\nimport { CONFIG_FILE } from './config.js';\nimport {\n chooseBase,\n describeGitHook,\n describeGraph,\n describeSkipped,\n enabledPlugin,\n GIT_HOOK_LINE,\n type GitHookState,\n graphReadsBriefs,\n GUARD_HOOK,\n holdsGuard,\n loadsPlugin,\n measuresArchive,\n mergeClaudeSettings,\n mergeMcp,\n mergeSpecBrief,\n mergeSpecGraph,\n missingGitHook,\n PLUGIN,\n PROTECT_RULE_FILES,\n registersServer,\n SPEC_BRIEF_CONFIGS,\n SPEC_GRAPH_CONFIGS,\n} from './configure.js';\nimport { readJsonObject as readJson, writeAtomic } from './fs.js';\nimport { hookPath, localBranches, remoteDefault } from './git.js';\nimport { claudeSettings, pluginFile } from './round.js';\nimport { runSibling } from './siblings.js';\nimport { EXIT_ERROR, EXIT_OK, json, openWorkspace, type CliIO, type Options, type Workspace } from './workspace.js';\n\n/** `skip`: a file init would write, left alone because Claude Code's plugin brings what init would add. */\nexport type Action = 'create' | 'update' | 'keep' | 'skip' | 'run' | 'advise';\n\nexport interface Step {\n readonly file: string;\n readonly action: Action;\n readonly detail: string;\n apply?: () => Promise<void>;\n}\n\ntype Json = Record<string, unknown>;\n\nfunction stringify(value: unknown): string {\n return `${JSON.stringify(value, null, 2)}\\n`;\n}\n\nconst PLUGIN_DETAIL = `load spec-harness's plugin, \"${PLUGIN}\": spec-brief's archive asks it whether a signed ruling allows a protected file, and refuses the file without it`;\n\n/** What init adds to spec-brief's configuration, said once for the plan. */\nfunction specBriefDetail(plugin: boolean, base: string | null): string {\n const parts = plugin ? [PLUGIN_DETAIL] : [];\n if (base !== null) parts.push(`measure the archive from ${base}, as spec-harness does: without a base, spec-brief's archive warns that the scope went unmeasured and checks no protected file`);\n return parts.join('; ');\n}\n\nconst PRE_COMMIT = `#!/bin/sh\n# spec-harness: refuse a commit that changes what the active brief protects.\nexec npx --no-install spec-harness hook git\n`;\n\n/** A path as a person finds it: from the root when it is inside it, POSIX-separated, and as it is otherwise. */\nfunction shown(root: string, path: string): string {\n const inside = relative(root, path);\n return inside.startsWith('..') || isAbsolute(inside) ? path : inside.split(sep).join('/');\n}\n\n/**\n * git's pre-commit hook, where git runs it from, and what is there: init and\n * doctor both read it. `path` is `null` when git cannot say where its hooks\n * are.\n */\nexport async function gitHook(root: string): Promise<{ readonly hook: GitHookState; readonly path: string | null }> {\n const path = await hookPath('pre-commit', root);\n if (path === null) return { hook: { state: 'unknown', reason: 'git does not say where they are; spec-harness needs git 2.31 or later' }, path };\n const file = shown(root, path);\n if (!existsSync(path)) return { hook: { state: 'absent', file }, path };\n // A Buffer finds the text as a string does, so an encoding mutant is equivalent.\n if (!(await readFile(path, 'utf8')).includes('spec-harness')) return { hook: { state: 'other', file }, path };\n // git skips a hook it cannot execute, and says so only in a hint; Windows\n // has no executable bit, and runs it.\n // The Windows job holds the platform check; the sweep runs on Linux, where it is always true.\n const inert = process.platform !== 'win32' && (statSync(path).mode & 0o111) === 0;\n return { hook: { state: inert ? 'inert' : 'runs', file }, path };\n}\n\nexport async function plan(workspace: Workspace, options: Options, env: CliIO['env']): Promise<Step[]> {\n const { root } = workspace;\n const steps: Step[] = [];\n\n // The base rounds merge into, decided once: spec-harness verifies rulings\n // against the allowed signers on it, and spec-brief's archive measures the\n // round from it. A base a person wrote, in either file, is kept.\n const harnessFile = join(root, CONFIG_FILE);\n const harness = await readJson(harnessFile);\n const namedBase = harness !== null && harness !== 'unreadable' && typeof harness['base'] === 'string' ? harness['base'] : null;\n const choice =\n harness === 'unreadable' || namedBase !== null\n ? null\n : chooseBase({ remoteDefault: await remoteDefault(root), branch: workspace.branch, branches: await localBranches(root) });\n const base = namedBase ?? choice?.base ?? null;\n\n // spec-brief first: every other setting is derived from its directories.\n const briefConfig = SPEC_BRIEF_CONFIGS.find((name) => existsSync(join(root, name)));\n const brief = workspace.siblings.locate('spec-brief');\n let briefs = 'briefs';\n let archive = 'briefs/archive';\n if (briefConfig === undefined) {\n if (brief.kind === 'found') {\n const command = brief.command;\n const created = join(root, '.spec-brief.json');\n steps.push({\n file: '.spec-brief.json',\n action: 'run',\n detail: 'spec-brief init: the brief and archive directories, every default spelled out',\n apply: async () => {\n const run = await runSibling(command, ['init'], root);\n if (run.code !== 0) throw new Error(`spec-brief init failed: ${run.stderr.trim()}`);\n },\n });\n steps.push({\n file: '.spec-brief.json',\n action: 'update',\n detail: specBriefDetail(true, base),\n apply: async () => {\n const written = await readJson(created);\n if (written === null || written === 'unreadable') throw new Error('spec-brief init wrote no configuration JSON can read');\n const merged = mergeSpecBrief(written, base, pluginFile(root));\n if (merged !== null) await writeAtomic(created, stringify(merged));\n },\n });\n } else {\n steps.push({ file: '.spec-brief.json', action: 'advise', detail: brief.reason });\n }\n } else {\n const file = join(root, briefConfig);\n const config = await readJson(file);\n if (config !== null && config !== 'unreadable') {\n if (typeof config['briefs'] === 'string') briefs = config['briefs'];\n archive = typeof config['archive'] === 'string' ? config['archive'] : `${briefs}/archive`;\n }\n steps.push({ file: briefConfig, action: 'keep', detail: `briefs in ${briefs}/, the archive in ${archive}/` });\n // spec-brief's archive learns that a signed ruling allows a protected file\n // only from this package's plugin (ADR-0006).\n if (config === 'unreadable' || config === null) {\n const measure = base === null ? '' : `, and \"archiving\": { \"base\": \"${base}\" }`;\n steps.push({ file: briefConfig, action: 'advise', detail: `cannot be read as JSON; add \"plugins\": [\"${PLUGIN}\"]${measure} by hand` });\n } else {\n const isPluginFile = pluginFile(root);\n const merged = mergeSpecBrief(config, base, isPluginFile);\n const measured = measuresArchive(config);\n if (merged === null) {\n const from = measured ? `, and the archive is measured from ${String((config['archiving'] as Json)['base'])}` : '';\n steps.push({ file: briefConfig, action: 'keep', detail: `spec-harness's plugin is loaded${from}` });\n } else {\n const detail = specBriefDetail(!loadsPlugin(config, isPluginFile), measured ? null : base);\n steps.push({ file: briefConfig, action: 'update', detail, apply: () => writeAtomic(file, stringify(merged)) });\n }\n }\n }\n\n // spec-graph: an archived brief is a record, not a retired decision. Its\n // configuration is the first of its files, then a key in package.json; a\n // file written beside that key would shadow it.\n const graph = workspace.siblings.locate('spec-graph');\n if (graph.kind === 'outdated') steps.push({ file: '.spec-graph.json', action: 'advise', detail: graph.reason });\n if (graph.kind === 'found') {\n const archiveGlob = `${archive}/**`;\n const graphName = SPEC_GRAPH_CONFIGS.find((name) => existsSync(join(root, name)));\n const manifest = graphName === undefined ? await readJson(join(root, 'package.json')) : null;\n if (manifest !== null && manifest !== 'unreadable' && 'spec-graph' in manifest) {\n steps.push({ file: 'package.json', action: 'advise', detail: `spec-graph reads its configuration from the \"spec-graph\" key here, which init does not edit: add \"${archiveGlob}\" to its \"historyPatterns\" by hand` });\n } else {\n const file = graphName ?? '.spec-graph.json';\n const current = await readJson(join(root, file));\n if (current === 'unreadable') {\n steps.push({ file, action: 'advise', detail: `cannot be read as JSON; add \"historyPatterns\": [\"${archiveGlob}\"] by hand` });\n } else {\n const merged = mergeSpecGraph(current ?? {}, archiveGlob);\n const detail = describeGraph(archiveGlob, briefs, graphReadsBriefs(current ?? {}, briefs), merged !== null);\n if (merged === null) steps.push({ file, action: 'keep', detail });\n else steps.push({ file, action: current === null ? 'create' : 'update', detail, apply: () => writeAtomic(join(root, file), stringify(merged)) });\n }\n }\n }\n\n // spec-harness's own file, naming the base the rounds merge into: every\n // ruling is verified against the allowed signers on it.\n if (harness === 'unreadable') {\n steps.push({ file: CONFIG_FILE, action: 'advise', detail: 'cannot be read as JSON; name the base rounds merge into as \"base\" by hand' });\n } else if (namedBase !== null) {\n steps.push({ file: CONFIG_FILE, action: 'keep', detail: `rounds are measured from ${namedBase}` });\n } else if (base === null || choice === null) {\n steps.push({ file: CONFIG_FILE, action: 'advise', detail: choice?.detail ?? 'name the base rounds merge into as \"base\"' });\n } else {\n const content: Json = { ...harness, base };\n steps.push({ file: CONFIG_FILE, action: harness === null ? 'create' : 'update', detail: choice.detail, apply: () => writeAtomic(harnessFile, stringify(content)) });\n }\n\n // The agent's hooks and server, unless the plugin brings them: Claude Code\n // runs a plugin's hook beside the same hook in settings, so both would\n // guard every write twice (ADR-0012).\n const plugin = enabledPlugin(await claudeSettings(root, env));\n const settingsFile = join(root, '.claude', 'settings.json');\n const settings = await readJson(settingsFile);\n if (plugin !== null) {\n const present = holdsGuard(settings);\n steps.push({ file: '.claude/settings.json', action: present ? 'advise' : 'skip', detail: describeSkipped(plugin, 'hooks', present) });\n } else if (settings === 'unreadable') {\n steps.push({ file: '.claude/settings.json', action: 'advise', detail: `cannot be read as JSON; add the guard as a PreToolUse and a PostToolUse hook by hand: ${JSON.stringify(GUARD_HOOK)}` });\n } else {\n const merged = mergeClaudeSettings(settings ?? {});\n if (merged === null) steps.push({ file: '.claude/settings.json', action: 'keep', detail: 'the guard hooks are installed' });\n else\n steps.push({\n file: '.claude/settings.json',\n action: settings === null ? 'create' : 'update',\n detail: 'the guard hooks, run with node from the project\\'s install: a PreToolUse hook refuses writes to protected files; a PostToolUse hook warns about writes outside the scope',\n apply: () => writeAtomic(settingsFile, stringify(merged)),\n });\n }\n const mcpFile = join(root, '.mcp.json');\n const mcp = await readJson(mcpFile);\n if (plugin !== null) {\n const present = registersServer(mcp);\n steps.push({ file: '.mcp.json', action: present ? 'advise' : 'skip', detail: describeSkipped(plugin, 'server', present) });\n } else if (mcp === 'unreadable') {\n steps.push({ file: '.mcp.json', action: 'advise', detail: 'cannot be read as JSON; add the spec-harness server by hand' });\n } else {\n const merged = mergeMcp(mcp ?? {});\n if (merged === null) steps.push({ file: '.mcp.json', action: 'keep', detail: 'the spec-harness server is registered' });\n else\n steps.push({\n file: '.mcp.json',\n action: mcp === null ? 'create' : 'update',\n detail: 'the spec-harness MCP server, run with node from the project\\'s install: start_round, check_path, request_escalation, audit_round, list_rounds',\n apply: () => writeAtomic(mcpFile, stringify(merged)),\n });\n }\n\n // git's hook, where git runs it from: core.hooksPath as git reads it, and\n // a linked worktree's shared hooks. It is the guard for a write the agent's\n // hooks never see, one through a shell, so it is advised when not asked\n // for; it is configuration outside the tree, so it is written only then.\n const { hook, path: hookFile } = await gitHook(root);\n // Only a git older than 2.31, which the README states as the harness's\n // floor, cannot name its hooks, and init runs only in a work tree, where\n // any later git names them: no test reaches this advice, by that decision.\n if (hook.state === 'unknown' || hookFile === null) {\n steps.push({ file: 'pre-commit', action: 'advise', detail: describeGitHook(hook) });\n } else if (hook.state === 'runs') {\n steps.push({ file: hook.file, action: 'keep', detail: 'already runs spec-harness' });\n } else if (hook.state === 'inert') {\n steps.push({ file: hook.file, action: 'advise', detail: describeGitHook(hook) });\n } else if (hook.state === 'other') {\n steps.push({ file: hook.file, action: 'advise', detail: `a pre-commit hook exists; add the line \"${GIT_HOOK_LINE}\" to it` });\n } else if (options.gitHook) {\n steps.push({\n file: hook.file,\n action: 'create',\n detail: 'refuse a commit that changes what the active brief protects',\n apply: async () => {\n await mkdir(dirname(hookFile), { recursive: true });\n await writeAtomic(hookFile, PRE_COMMIT);\n await chmod(hookFile, 0o755);\n },\n });\n } else {\n steps.push({ file: hook.file, action: 'advise', detail: missingGitHook(null) });\n }\n\n // The file the configuration names, which a ruling's signature is checked\n // against on the base branch (ADR-0006).\n steps.push({\n file: workspace.config.rulings.allowedSigners,\n action: 'advise',\n detail: `rulings count only when signed by a key listed here on the base branch: one line per person, <email> namespaces=\"git\" <public key>. Where an agent runs as the person, make it a FIDO2 key, ssh-keygen -t ed25519-sk, whose signature needs a touch no process can supply. ${PROTECT_RULE_FILES}`,\n });\n return steps;\n}\n\nexport async function initCommand(options: Options, io: CliIO): Promise<number> {\n const workspace = await openWorkspace(options, io);\n const steps = await plan(workspace, options, io.env);\n if (options.write) {\n for (const step of steps) {\n if (step.apply !== undefined) {\n try {\n await step.apply();\n } catch (error) {\n io.stderr.write(`spec-harness: ${step.file}: ${(error as Error).message}\\n`);\n return EXIT_ERROR;\n }\n }\n }\n }\n if (options.format === 'json') {\n io.stdout.write(json('init', { written: options.write, steps: steps.map(({ file, action, detail }) => ({ file, action, detail })) }));\n } else {\n for (const step of steps) io.stdout.write(`${step.action.padEnd(6)} ${step.file}\\n ${step.detail}\\n`);\n if (!options.write && steps.some((step) => step.apply !== undefined)) io.stdout.write('\\nNothing was changed. Run again with --write to apply the plan.\\n');\n }\n return EXIT_OK;\n}\n"]}
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `spec-harness init`: the family configured to agree, from the first day.\n *\n * The integration faults found when the tools were first run together were all\n * configuration: spec-graph read an archived brief as a retired decision and\n * reported every brief that depended on one; spec-guard executed an archived\n * brief's assertions. Each tool can be told the truth; `init` tells each of\n * them the same truth, derived from spec-brief's own settings.\n *\n * It prints a plan and changes nothing until `--write`. It merges into files\n * that exist and never replaces what a person wrote; a git hook the\n * repository already has is left alone, with the line to add printed instead.\n */\n\nimport { existsSync, statSync } from 'node:fs';\nimport { chmod, mkdir, readFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, join, relative, sep } from 'node:path';\n\nimport { CONFIG_FILE } from './config.js';\nimport {\n chooseBase,\n describeGitHook,\n describeGraph,\n describeSkipped,\n enabledPlugin,\n GIT_HOOK_LINE,\n type GitHookState,\n graphReadsBriefs,\n GUARD_HOOK,\n holdsGuard,\n loadsPlugin,\n measuresArchive,\n mergeClaudeSettings,\n mergeMcp,\n mergeSpecBrief,\n mergeSpecGraph,\n missingGitHook,\n PLUGIN,\n PROTECT_RULE_FILES,\n registersServer,\n SPEC_BRIEF_CONFIGS,\n SPEC_GRAPH_CONFIGS,\n} from './configure.js';\nimport { readJsonObject as readJson, writeAtomic } from './fs.js';\nimport { hookPath, localBranches, remoteDefault } from './git.js';\nimport { claudeSettings, pluginFile } from './round.js';\nimport { runSibling } from './siblings.js';\nimport { EXIT_ERROR, EXIT_OK, json, openWorkspace, type CliIO, type Options, type Workspace } from './workspace.js';\n\n/** `skip`: a file init would write, left alone because Claude Code's plugin brings what init would add. */\nexport type Action = 'create' | 'update' | 'keep' | 'skip' | 'run' | 'advise';\n\nexport interface Step {\n readonly file: string;\n readonly action: Action;\n readonly detail: string;\n apply?: () => Promise<void>;\n}\n\ntype Json = Record<string, unknown>;\n\nfunction stringify(value: unknown): string {\n return `${JSON.stringify(value, null, 2)}\\n`;\n}\n\nconst PLUGIN_DETAIL = `load spec-harness's plugin, \"${PLUGIN}\": spec-brief's archive asks it whether a signed ruling allows a protected file, and refuses the file without it`;\n\n/** What init adds to spec-brief's configuration, said once for the plan. */\nfunction specBriefDetail(plugin: boolean, base: string | null): string {\n const parts = plugin ? [PLUGIN_DETAIL] : [];\n if (base !== null) parts.push(`measure the archive from ${base}, as spec-harness does: without a base, spec-brief's archive warns that the scope went unmeasured and checks no protected file`);\n return parts.join('; ');\n}\n\nconst PRE_COMMIT = `#!/bin/sh\n# spec-harness: refuse a commit that changes what the active brief protects.\nexec npx --no-install spec-harness hook git\n`;\n\n/** A path as a person finds it: from the root when it is inside it, POSIX-separated, and as it is otherwise. */\nfunction shown(root: string, path: string): string {\n const inside = relative(root, path);\n return inside.startsWith('..') || isAbsolute(inside) ? path : inside.split(sep).join('/');\n}\n\n/**\n * git's pre-commit hook, where git runs it from, and what is there: init and\n * doctor both read it. `path` is `null` when git cannot say where its hooks\n * are.\n */\nexport async function gitHook(root: string): Promise<{ readonly hook: GitHookState; readonly path: string | null }> {\n const path = await hookPath('pre-commit', root);\n if (path === null) return { hook: { state: 'unknown', reason: 'git does not say where they are; spec-harness needs git 2.31 or later' }, path };\n const file = shown(root, path);\n if (!existsSync(path)) return { hook: { state: 'absent', file }, path };\n // A Buffer finds the text as a string does, so an encoding mutant is equivalent.\n if (!(await readFile(path, 'utf8')).includes('spec-harness')) return { hook: { state: 'other', file }, path };\n // git skips a hook it cannot execute, and says so only in a hint; Windows\n // has no executable bit, and runs it.\n // The Windows job holds the platform check; the sweep runs on Linux, where it is always true.\n const inert = process.platform !== 'win32' && (statSync(path).mode & 0o111) === 0;\n return { hook: { state: inert ? 'inert' : 'runs', file }, path };\n}\n\nexport async function plan(workspace: Workspace, options: Options, env: CliIO['env']): Promise<Step[]> {\n const { root } = workspace;\n const steps: Step[] = [];\n\n // The base rounds merge into, decided once: spec-harness verifies rulings\n // against the allowed signers on it, and spec-brief's archive measures the\n // round from it. A base a person wrote, in either file, is kept.\n // openWorkspace loaded this file and refused one that is not a JSON object,\n // so here it is one, or there is none.\n const harnessFile = join(root, CONFIG_FILE);\n const harness = (await readJson(harnessFile)) as Json | null;\n const namedBase = harness !== null && typeof harness['base'] === 'string' ? harness['base'] : null;\n const choice = namedBase === null ? chooseBase({ remoteDefault: await remoteDefault(root), branch: workspace.branch, branches: await localBranches(root) }) : null;\n const base = choice === null ? namedBase : choice.base;\n\n // spec-brief first: every other setting is derived from its directories.\n const briefConfig = SPEC_BRIEF_CONFIGS.find((name) => existsSync(join(root, name)));\n const brief = workspace.siblings.locate('spec-brief');\n let briefs = 'briefs';\n let archive = 'briefs/archive';\n if (briefConfig === undefined) {\n if (brief.kind === 'found') {\n const command = brief.command;\n const created = join(root, '.spec-brief.json');\n steps.push({\n file: '.spec-brief.json',\n action: 'run',\n detail: 'spec-brief init: the brief and archive directories, every default spelled out',\n apply: async () => {\n const run = await runSibling(command, ['init'], root);\n if (run.code !== 0) throw new Error(`spec-brief init failed: ${run.stderr.trim()}`);\n },\n });\n steps.push({\n file: '.spec-brief.json',\n action: 'update',\n detail: specBriefDetail(true, base),\n apply: async () => {\n const written = await readJson(created);\n if (written === null || written === 'unreadable') throw new Error('spec-brief init wrote no configuration JSON can read');\n const merged = mergeSpecBrief(written, base, pluginFile(root));\n if (merged !== null) await writeAtomic(created, stringify(merged));\n },\n });\n } else {\n steps.push({ file: '.spec-brief.json', action: 'advise', detail: brief.reason });\n }\n } else {\n const file = join(root, briefConfig);\n const config = await readJson(file);\n // The file was found a moment ago, so null is one removed in between; and\n // a string has neither key, so these checks are there for the type.\n if (config !== null && config !== 'unreadable') {\n if (typeof config['briefs'] === 'string') briefs = config['briefs'];\n archive = typeof config['archive'] === 'string' ? config['archive'] : `${briefs}/archive`;\n }\n steps.push({ file: briefConfig, action: 'keep', detail: `briefs in ${briefs}/, the archive in ${archive}/` });\n // spec-brief's archive learns that a signed ruling allows a protected file\n // only from this package's plugin (ADR-0006).\n if (config === 'unreadable' || config === null) {\n const measure = base === null ? '' : `, and \"archiving\": { \"base\": \"${base}\" }`;\n steps.push({ file: briefConfig, action: 'advise', detail: `cannot be read as JSON; add \"plugins\": [\"${PLUGIN}\"]${measure} by hand` });\n } else {\n const isPluginFile = pluginFile(root);\n const merged = mergeSpecBrief(config, base, isPluginFile);\n const measured = measuresArchive(config);\n if (merged === null) {\n const from = measured ? `, and the archive is measured from ${String((config['archiving'] as Json)['base'])}` : '';\n steps.push({ file: briefConfig, action: 'keep', detail: `spec-harness's plugin is loaded${from}` });\n } else {\n const detail = specBriefDetail(!loadsPlugin(config, isPluginFile), measured ? null : base);\n steps.push({ file: briefConfig, action: 'update', detail, apply: () => writeAtomic(file, stringify(merged)) });\n }\n }\n }\n\n // spec-graph: an archived brief is a record, not a retired decision. Its\n // configuration is the first of its files, then a key in package.json; a\n // file written beside that key would shadow it.\n const graph = workspace.siblings.locate('spec-graph');\n if (graph.kind === 'outdated') steps.push({ file: '.spec-graph.json', action: 'advise', detail: graph.reason });\n if (graph.kind === 'found') {\n const archiveGlob = `${archive}/**`;\n const graphName = SPEC_GRAPH_CONFIGS.find((name) => existsSync(join(root, name)));\n const manifest = graphName === undefined ? await readJson(join(root, 'package.json')) : null;\n if (manifest !== null && manifest !== 'unreadable' && 'spec-graph' in manifest) {\n steps.push({ file: 'package.json', action: 'advise', detail: `spec-graph reads its configuration from the \"spec-graph\" key here, which init does not edit: add \"${archiveGlob}\" to its \"historyPatterns\" by hand` });\n } else {\n const file = graphName ?? '.spec-graph.json';\n const current = await readJson(join(root, file));\n if (current === 'unreadable') {\n steps.push({ file, action: 'advise', detail: `cannot be read as JSON; add \"historyPatterns\": [\"${archiveGlob}\"] by hand` });\n } else {\n const merged = mergeSpecGraph(current ?? {}, archiveGlob);\n const detail = describeGraph(archiveGlob, briefs, graphReadsBriefs(current ?? {}, briefs), merged !== null);\n if (merged === null) steps.push({ file, action: 'keep', detail });\n else steps.push({ file, action: current === null ? 'create' : 'update', detail, apply: () => writeAtomic(join(root, file), stringify(merged)) });\n }\n }\n }\n\n // spec-harness's own file, naming the base the rounds merge into: every\n // ruling is verified against the allowed signers on it. No choice was made\n // only where the file names a base.\n if (choice === null) {\n steps.push({ file: CONFIG_FILE, action: 'keep', detail: `rounds are measured from ${namedBase}` });\n } else if (choice.base === null) {\n steps.push({ file: CONFIG_FILE, action: 'advise', detail: choice.detail });\n } else {\n const content: Json = { ...harness, base: choice.base };\n steps.push({ file: CONFIG_FILE, action: harness === null ? 'create' : 'update', detail: choice.detail, apply: () => writeAtomic(harnessFile, stringify(content)) });\n }\n\n // The agent's hooks and server, unless the plugin brings them: Claude Code\n // runs a plugin's hook beside the same hook in settings, so both would\n // guard every write twice (ADR-0012).\n const plugin = enabledPlugin(await claudeSettings(root, env));\n const settingsFile = join(root, '.claude', 'settings.json');\n const settings = await readJson(settingsFile);\n if (plugin !== null) {\n const present = holdsGuard(settings);\n steps.push({ file: '.claude/settings.json', action: present ? 'advise' : 'skip', detail: describeSkipped(plugin, 'hooks', present) });\n } else if (settings === 'unreadable') {\n steps.push({ file: '.claude/settings.json', action: 'advise', detail: `cannot be read as JSON; add the guard as a PreToolUse and a PostToolUse hook by hand: ${JSON.stringify(GUARD_HOOK)}` });\n } else {\n const merged = mergeClaudeSettings(settings ?? {});\n if (merged === null) steps.push({ file: '.claude/settings.json', action: 'keep', detail: 'the guard hooks are installed' });\n else\n steps.push({\n file: '.claude/settings.json',\n action: settings === null ? 'create' : 'update',\n detail: 'the guard hooks, run with node from the project\\'s install: a PreToolUse hook refuses writes to protected files; a PostToolUse hook warns about writes outside the scope',\n apply: () => writeAtomic(settingsFile, stringify(merged)),\n });\n }\n const mcpFile = join(root, '.mcp.json');\n const mcp = await readJson(mcpFile);\n if (plugin !== null) {\n const present = registersServer(mcp);\n steps.push({ file: '.mcp.json', action: present ? 'advise' : 'skip', detail: describeSkipped(plugin, 'server', present) });\n } else if (mcp === 'unreadable') {\n steps.push({ file: '.mcp.json', action: 'advise', detail: 'cannot be read as JSON; add the spec-harness server by hand' });\n } else {\n const merged = mergeMcp(mcp ?? {});\n if (merged === null) steps.push({ file: '.mcp.json', action: 'keep', detail: 'the spec-harness server is registered' });\n else\n steps.push({\n file: '.mcp.json',\n action: mcp === null ? 'create' : 'update',\n detail: 'the spec-harness MCP server, run with node from the project\\'s install: start_round, check_path, request_escalation, audit_round, list_rounds',\n apply: () => writeAtomic(mcpFile, stringify(merged)),\n });\n }\n\n // git's hook, where git runs it from: core.hooksPath as git reads it, and\n // a linked worktree's shared hooks. It is the guard for a write the agent's\n // hooks never see, one through a shell, so it is advised when not asked\n // for; it is configuration outside the tree, so it is written only then.\n const { hook, path: hookFile } = await gitHook(root);\n // Only a git older than 2.31, which the README states as the harness's\n // floor, cannot name its hooks, and init runs only in a work tree, where\n // any later git names them: no test reaches this advice, by that decision.\n if (hook.state === 'unknown' || hookFile === null) {\n steps.push({ file: 'pre-commit', action: 'advise', detail: describeGitHook(hook) });\n } else if (hook.state === 'runs') {\n steps.push({ file: hook.file, action: 'keep', detail: 'already runs spec-harness' });\n } else if (hook.state === 'inert') {\n steps.push({ file: hook.file, action: 'advise', detail: describeGitHook(hook) });\n } else if (hook.state === 'other') {\n steps.push({ file: hook.file, action: 'advise', detail: `a pre-commit hook exists; add the line \"${GIT_HOOK_LINE}\" to it` });\n } else if (options.gitHook) {\n steps.push({\n file: hook.file,\n action: 'create',\n detail: 'refuse a commit that changes what the active brief protects',\n apply: async () => {\n await mkdir(dirname(hookFile), { recursive: true });\n await writeAtomic(hookFile, PRE_COMMIT);\n await chmod(hookFile, 0o755);\n },\n });\n } else {\n steps.push({ file: hook.file, action: 'advise', detail: missingGitHook(null) });\n }\n\n // The file the configuration names, which a ruling's signature is checked\n // against on the base branch (ADR-0006).\n steps.push({\n file: workspace.config.rulings.allowedSigners,\n action: 'advise',\n detail: `rulings count only when signed by a key listed here on the base branch: one line per person, <email> namespaces=\"git\" <public key>. Where an agent runs as the person, make it a FIDO2 key, ssh-keygen -t ed25519-sk, whose signature needs a touch no process can supply. ${PROTECT_RULE_FILES}`,\n });\n return steps;\n}\n\nexport async function initCommand(options: Options, io: CliIO): Promise<number> {\n const workspace = await openWorkspace(options, io);\n const steps = await plan(workspace, options, io.env);\n if (options.write) {\n for (const step of steps) {\n if (step.apply !== undefined) {\n try {\n await step.apply();\n } catch (error) {\n io.stderr.write(`spec-harness: ${step.file}: ${(error as Error).message}\\n`);\n return EXIT_ERROR;\n }\n }\n }\n }\n if (options.format === 'json') {\n io.stdout.write(json('init', { written: options.write, steps: steps.map(({ file, action, detail }) => ({ file, action, detail })) }));\n } else {\n for (const step of steps) io.stdout.write(`${step.action.padEnd(6)} ${step.file}\\n ${step.detail}\\n`);\n if (!options.write && steps.some((step) => step.apply !== undefined)) io.stdout.write('\\nNothing was changed. Run again with --write to apply the plan.\\n');\n }\n return EXIT_OK;\n}\n"]}
package/dist/siblings.js CHANGED
@@ -39,9 +39,12 @@ export function locate(name, root, config) {
39
39
  }
40
40
  /** The `version` a package.json declares, or `undefined` when there is no such file or it is not JSON. */
41
41
  export function declaredVersion(file) {
42
+ // A manifest that holds null throws on the read, and any other value that
43
+ // is not an object has no version, so the catch answers for both. Falling
44
+ // out of it answers undefined as well, and a Buffer parses as its text:
45
+ // the empty catch and the encoding are equivalent to their mutants.
42
46
  try {
43
- const manifest = JSON.parse(readFileSync(file, 'utf8'));
44
- return typeof manifest === 'object' && manifest !== null ? manifest.version : undefined;
47
+ return JSON.parse(readFileSync(file, 'utf8')).version;
45
48
  }
46
49
  catch {
47
50
  return undefined;
@@ -64,6 +67,8 @@ export function runSibling(command, args, root, timeoutMs = 600_000) {
64
67
  cwd: root,
65
68
  maxBuffer: 256 * 1024 * 1024,
66
69
  encoding: 'utf8',
70
+ // Keeps a console window from opening on Windows; the sweep runs on
71
+ // Linux, where the mutant is equivalent.
67
72
  windowsHide: true,
68
73
  timeout: timeoutMs,
69
74
  shell: false,
@@ -111,12 +116,15 @@ export function createSiblings(root, config) {
111
116
  async briefs() {
112
117
  const run = await runSibling(found('spec-brief'), ['list', '--archived', '--format', 'json', '--no-color'], root);
113
118
  if (run.code === 2) {
114
- throw new SiblingError(`spec-brief could not list the briefs: ${run.stderr.trim().split('\n')[0] ?? 'exit 2'}`);
119
+ throw new SiblingError(`spec-brief could not list the briefs: ${run.stderr.trim().split('\n')[0] || 'exit 2'}`);
115
120
  }
116
121
  try {
117
122
  return parseBriefList(parseJson('spec-brief', run));
118
123
  }
119
124
  catch (error) {
125
+ // parseJson throws a SiblingError, which this would copy, and
126
+ // parseBriefList nothing but its own: the check is equivalent to its
127
+ // mutant, and keeps any other error the stack trace it needs.
120
128
  if (error instanceof SiblingOutputError)
121
129
  throw new SiblingError(error.message);
122
130
  throw error;
@@ -1 +1 @@
1
- {"version":3,"file":"siblings.js","sourceRoot":"","sources":["../src/siblings.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The edge where the harness meets spec-brief, spec-graph and spec-guard.\n *\n * Through their command lines and their versioned JSON, never by importing\n * their packages: each is released, pinned and dropped on its own, and none\n * becomes a dependency of this one (spec-core ADR-0005, ADR-0002 here).\n *\n * A sibling is found as the repository installed it -\n * `node_modules/@descent-vtt/<name>/bin/<name>.js`, run with this Node - or\n * as `.spec-harness.json` names it. One that is not there is `absent`, and\n * what needed it says so; nothing it would have checked is reported clean.\n * One installed below this release's minimum is `outdated`, and is reported\n * the same way rather than run (versions.ts).\n */\n\nimport { execFile } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { parseBriefList, SiblingOutputError } from './briefs.js';\nimport type { HarnessConfig, SiblingName } from './config.js';\nimport type { BriefRow } from './types.js';\nimport { checkVersion } from './versions.js';\n\nexport interface SiblingRun {\n readonly code: number;\n readonly stdout: string;\n readonly stderr: string;\n}\n\nexport type Sibling =\n /** `version` is `null` for a command `.spec-harness.json` names, whose version is not checked. */\n | { readonly kind: 'found'; readonly command: readonly string[]; readonly version: string | null }\n | { readonly kind: 'absent'; readonly reason: string }\n /** Installed, at a version below the minimum or one that cannot be read, which is `null`. */\n | { readonly kind: 'outdated'; readonly reason: string; readonly version: string | null };\n\n/** Where a sibling is, or why it cannot be run. */\nexport function locate(name: SiblingName, root: string, config: HarnessConfig): Sibling {\n // A command the configuration names is run as named: which package it runs,\n // and so which version, is the configuration's to say.\n const configured = config.tools[name];\n if (configured !== null) return { kind: 'found', command: configured, version: null };\n const directory = join(root, 'node_modules', '@descent-vtt', name);\n const bin = join(directory, 'bin', `${name}.js`);\n if (!existsSync(bin)) {\n return {\n kind: 'absent',\n reason: `${name} is not installed here: npm install --save-dev @descent-vtt/${name}, or name its command under \"tools\" in .spec-harness.json`,\n };\n }\n const check = checkVersion(name, declaredVersion(join(directory, 'package.json')));\n if (!check.ok) return { kind: 'outdated', reason: check.reason, version: check.version };\n return { kind: 'found', command: [process.execPath, bin], version: check.version };\n}\n\n/** The `version` a package.json declares, or `undefined` when there is no such file or it is not JSON. */\nexport function declaredVersion(file: string): unknown {\n try {\n const manifest: unknown = JSON.parse(readFileSync(file, 'utf8'));\n return typeof manifest === 'object' && manifest !== null ? (manifest as { version?: unknown }).version : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Runs a sibling with arguments, in the repository root.\n *\n * Never through a shell: an argument is a path or a brief id, and a shell\n * would read it as syntax. `node` as a command's first word is this Node, so\n * `[\"node\", \"path/to/bin.js\"]` works on every host; a Windows `.cmd` shim such\n * as `npx` cannot be started without a shell, and the error says to name the\n * script instead.\n */\nexport function runSibling(command: readonly string[], args: readonly string[], root: string, timeoutMs = 600_000): Promise<SiblingRun> {\n const [first, ...rest] = command;\n const program = first === 'node' ? process.execPath : (first as string);\n return new Promise((resolve) => {\n execFile(\n program,\n [...rest, ...args],\n {\n cwd: root,\n maxBuffer: 256 * 1024 * 1024,\n encoding: 'utf8',\n windowsHide: true,\n timeout: timeoutMs,\n shell: false,\n env: { ...process.env, NO_COLOR: '1', FORCE_COLOR: '0' },\n },\n (error, stdout, stderr) => {\n const failure = error as (Error & { code?: unknown }) | null;\n if (failure !== null && failure.code === 'ENOENT') {\n resolve({\n code: -1,\n stdout: '',\n stderr: `cannot start \"${first}\": name the tool's script, such as [\"node\", \"node_modules/@descent-vtt/<tool>/bin/<tool>.js\"]`,\n });\n return;\n }\n const code = failure === null ? 0 : typeof failure.code === 'number' ? failure.code : -1;\n resolve({ code, stdout, stderr });\n },\n );\n });\n}\n\nexport class SiblingError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'SiblingError';\n }\n}\n\nfunction parseJson(name: string, run: SiblingRun): unknown {\n try {\n return JSON.parse(run.stdout);\n } catch {\n const said = (run.stderr.trim() || run.stdout.trim()).split('\\n').slice(0, 3).join(' ');\n throw new SiblingError(`${name} exited ${run.code} without JSON${said ? `: ${said}` : ''}`);\n }\n}\n\nexport interface Siblings {\n readonly root: string;\n locate(name: SiblingName): Sibling;\n /** Every brief, live and archived. Exit 2 from spec-brief is an error here too. */\n briefs(): Promise<BriefRow[]>;\n /** A sibling's JSON output for a command line, with its exit code and what it said on stderr; `absent` says why it cannot be run, missing or outdated. */\n json(name: SiblingName, args: readonly string[]): Promise<{ readonly code: number; readonly document: unknown; readonly stderr: string } | { readonly absent: string }>;\n}\n\nexport function createSiblings(root: string, config: HarnessConfig): Siblings {\n const found = (name: SiblingName): readonly string[] => {\n const sibling = locate(name, root, config);\n if (sibling.kind !== 'found') throw new SiblingError(sibling.reason);\n return sibling.command;\n };\n return {\n root,\n locate: (name) => locate(name, root, config),\n async briefs() {\n const run = await runSibling(found('spec-brief'), ['list', '--archived', '--format', 'json', '--no-color'], root);\n if (run.code === 2) {\n throw new SiblingError(`spec-brief could not list the briefs: ${run.stderr.trim().split('\\n')[0] ?? 'exit 2'}`);\n }\n try {\n return parseBriefList(parseJson('spec-brief', run));\n } catch (error) {\n if (error instanceof SiblingOutputError) throw new SiblingError(error.message);\n throw error;\n }\n },\n async json(name, args) {\n const sibling = locate(name, root, config);\n if (sibling.kind !== 'found') return { absent: sibling.reason };\n const run = await runSibling(sibling.command, args, root);\n return { code: run.code, document: parseJson(name, run), stderr: run.stderr };\n },\n };\n}\n"]}
1
+ {"version":3,"file":"siblings.js","sourceRoot":"","sources":["../src/siblings.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The edge where the harness meets spec-brief, spec-graph and spec-guard.\n *\n * Through their command lines and their versioned JSON, never by importing\n * their packages: each is released, pinned and dropped on its own, and none\n * becomes a dependency of this one (spec-core ADR-0005, ADR-0002 here).\n *\n * A sibling is found as the repository installed it -\n * `node_modules/@descent-vtt/<name>/bin/<name>.js`, run with this Node - or\n * as `.spec-harness.json` names it. One that is not there is `absent`, and\n * what needed it says so; nothing it would have checked is reported clean.\n * One installed below this release's minimum is `outdated`, and is reported\n * the same way rather than run (versions.ts).\n */\n\nimport { execFile } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { parseBriefList, SiblingOutputError } from './briefs.js';\nimport type { HarnessConfig, SiblingName } from './config.js';\nimport type { BriefRow } from './types.js';\nimport { checkVersion } from './versions.js';\n\nexport interface SiblingRun {\n readonly code: number;\n readonly stdout: string;\n readonly stderr: string;\n}\n\nexport type Sibling =\n /** `version` is `null` for a command `.spec-harness.json` names, whose version is not checked. */\n | { readonly kind: 'found'; readonly command: readonly string[]; readonly version: string | null }\n | { readonly kind: 'absent'; readonly reason: string }\n /** Installed, at a version below the minimum or one that cannot be read, which is `null`. */\n | { readonly kind: 'outdated'; readonly reason: string; readonly version: string | null };\n\n/** Where a sibling is, or why it cannot be run. */\nexport function locate(name: SiblingName, root: string, config: HarnessConfig): Sibling {\n // A command the configuration names is run as named: which package it runs,\n // and so which version, is the configuration's to say.\n const configured = config.tools[name];\n if (configured !== null) return { kind: 'found', command: configured, version: null };\n const directory = join(root, 'node_modules', '@descent-vtt', name);\n const bin = join(directory, 'bin', `${name}.js`);\n if (!existsSync(bin)) {\n return {\n kind: 'absent',\n reason: `${name} is not installed here: npm install --save-dev @descent-vtt/${name}, or name its command under \"tools\" in .spec-harness.json`,\n };\n }\n const check = checkVersion(name, declaredVersion(join(directory, 'package.json')));\n if (!check.ok) return { kind: 'outdated', reason: check.reason, version: check.version };\n return { kind: 'found', command: [process.execPath, bin], version: check.version };\n}\n\n/** The `version` a package.json declares, or `undefined` when there is no such file or it is not JSON. */\nexport function declaredVersion(file: string): unknown {\n // A manifest that holds null throws on the read, and any other value that\n // is not an object has no version, so the catch answers for both. Falling\n // out of it answers undefined as well, and a Buffer parses as its text:\n // the empty catch and the encoding are equivalent to their mutants.\n try {\n return (JSON.parse(readFileSync(file, 'utf8')) as { version?: unknown }).version;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Runs a sibling with arguments, in the repository root.\n *\n * Never through a shell: an argument is a path or a brief id, and a shell\n * would read it as syntax. `node` as a command's first word is this Node, so\n * `[\"node\", \"path/to/bin.js\"]` works on every host; a Windows `.cmd` shim such\n * as `npx` cannot be started without a shell, and the error says to name the\n * script instead.\n */\nexport function runSibling(command: readonly string[], args: readonly string[], root: string, timeoutMs = 600_000): Promise<SiblingRun> {\n const [first, ...rest] = command;\n const program = first === 'node' ? process.execPath : (first as string);\n return new Promise((resolve) => {\n execFile(\n program,\n [...rest, ...args],\n {\n cwd: root,\n maxBuffer: 256 * 1024 * 1024,\n encoding: 'utf8',\n // Keeps a console window from opening on Windows; the sweep runs on\n // Linux, where the mutant is equivalent.\n windowsHide: true,\n timeout: timeoutMs,\n shell: false,\n env: { ...process.env, NO_COLOR: '1', FORCE_COLOR: '0' },\n },\n (error, stdout, stderr) => {\n const failure = error as (Error & { code?: unknown }) | null;\n if (failure !== null && failure.code === 'ENOENT') {\n resolve({\n code: -1,\n stdout: '',\n stderr: `cannot start \"${first}\": name the tool's script, such as [\"node\", \"node_modules/@descent-vtt/<tool>/bin/<tool>.js\"]`,\n });\n return;\n }\n const code = failure === null ? 0 : typeof failure.code === 'number' ? failure.code : -1;\n resolve({ code, stdout, stderr });\n },\n );\n });\n}\n\nexport class SiblingError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'SiblingError';\n }\n}\n\nfunction parseJson(name: string, run: SiblingRun): unknown {\n try {\n return JSON.parse(run.stdout);\n } catch {\n const said = (run.stderr.trim() || run.stdout.trim()).split('\\n').slice(0, 3).join(' ');\n throw new SiblingError(`${name} exited ${run.code} without JSON${said ? `: ${said}` : ''}`);\n }\n}\n\nexport interface Siblings {\n readonly root: string;\n locate(name: SiblingName): Sibling;\n /** Every brief, live and archived. Exit 2 from spec-brief is an error here too. */\n briefs(): Promise<BriefRow[]>;\n /** A sibling's JSON output for a command line, with its exit code and what it said on stderr; `absent` says why it cannot be run, missing or outdated. */\n json(name: SiblingName, args: readonly string[]): Promise<{ readonly code: number; readonly document: unknown; readonly stderr: string } | { readonly absent: string }>;\n}\n\nexport function createSiblings(root: string, config: HarnessConfig): Siblings {\n const found = (name: SiblingName): readonly string[] => {\n const sibling = locate(name, root, config);\n if (sibling.kind !== 'found') throw new SiblingError(sibling.reason);\n return sibling.command;\n };\n return {\n root,\n locate: (name) => locate(name, root, config),\n async briefs() {\n const run = await runSibling(found('spec-brief'), ['list', '--archived', '--format', 'json', '--no-color'], root);\n if (run.code === 2) {\n throw new SiblingError(`spec-brief could not list the briefs: ${run.stderr.trim().split('\\n')[0] || 'exit 2'}`);\n }\n try {\n return parseBriefList(parseJson('spec-brief', run));\n } catch (error) {\n // parseJson throws a SiblingError, which this would copy, and\n // parseBriefList nothing but its own: the check is equivalent to its\n // mutant, and keeps any other error the stack trace it needs.\n if (error instanceof SiblingOutputError) throw new SiblingError(error.message);\n throw error;\n }\n },\n async json(name, args) {\n const sibling = locate(name, root, config);\n if (sibling.kind !== 'found') return { absent: sibling.reason };\n const run = await runSibling(sibling.command, args, root);\n return { code: run.code, document: parseJson(name, run), stderr: run.stderr };\n },\n };\n}\n"]}
@@ -74,9 +74,22 @@ export declare function openWorkspace(options: Options, io: CliIO): Promise<Work
74
74
  /** The id the flag, the environment or the branch names, without asking spec-brief anything. */
75
75
  export declare function namedId(workspace: Workspace, options: Options, env: CliIO['env']): string | null;
76
76
  export declare function activeBrief(workspace: Workspace, options: Options, env: CliIO['env']): Promise<ActiveBrief>;
77
+ /**
78
+ * The brief a command acts on, or why there is none: a note when nothing names
79
+ * one, which a command that needs a brief refuses with, or a problem when what
80
+ * names one is wrong.
81
+ */
77
82
  export declare function describeActive(active: ActiveBrief): {
78
- brief: BriefRow | null;
79
- note: string | undefined;
80
- problem: string | null;
83
+ brief: BriefRow;
84
+ note: undefined;
85
+ problem: null;
86
+ } | {
87
+ brief: null;
88
+ note: string;
89
+ problem: null;
90
+ } | {
91
+ brief: null;
92
+ note: undefined;
93
+ problem: string;
81
94
  };
82
95
  export declare function json(command: string, body: Record<string, unknown>): string;
package/dist/workspace.js CHANGED
@@ -101,6 +101,8 @@ export async function openWorkspace(options, io) {
101
101
  const root = await workTreeRoot(cwd);
102
102
  if (root === null)
103
103
  throw new UsageError(`${cwd} is not inside a git work tree; spec-harness measures rounds by their commits`);
104
+ // Inside a work tree git always names the common directory; the fallback
105
+ // is there for the type.
104
106
  const commonDir = (await commonDirectory(root)) ?? `${root}/.git`;
105
107
  const { config } = await loadConfig(root);
106
108
  // A branch checked out is the branch; only a head on none, as CI checks
@@ -117,16 +119,24 @@ export function namedId(workspace, options, env) {
117
119
  const environment = env['SPEC_BRIEF'];
118
120
  if (environment !== undefined && environment.trim() !== '')
119
121
  return environment.trim();
120
- return workspace.branch === null ? null : briefIdFromBranch(workspace.config.branches, workspace.branch);
122
+ return briefIdFromBranch(workspace.config.branches, workspace.branch);
121
123
  }
122
124
  export async function activeBrief(workspace, options, env) {
125
+ // Nothing names a brief, so spec-brief is not asked (ADR-0004): the answer
126
+ // is none whatever the briefs are, and so the empty list is equivalent to
127
+ // its mutant.
123
128
  if (namedId(workspace, options, env) === null) {
124
129
  return findActive([], {});
125
130
  }
126
131
  const briefs = await workspace.siblings.briefs();
127
- const fromBranch = workspace.branch === null ? null : briefIdFromBranch(workspace.config.branches, workspace.branch);
132
+ const fromBranch = briefIdFromBranch(workspace.config.branches, workspace.branch);
128
133
  return findActive(briefs, { flag: options.brief, environment: env['SPEC_BRIEF'], branch: fromBranch });
129
134
  }
135
+ /**
136
+ * The brief a command acts on, or why there is none: a note when nothing names
137
+ * one, which a command that needs a brief refuses with, or a problem when what
138
+ * names one is wrong.
139
+ */
130
140
  export function describeActive(active) {
131
141
  switch (active.kind) {
132
142
  case 'found':
@@ -1 +1 @@
1
- {"version":3,"file":"workspace.js","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What every command shares: exit codes, options, the repository a command\n * runs in, and which brief the round is working on.\n */\n\nimport { readFileSync } from 'node:fs';\nimport { parseArgs } from 'node:util';\n\nimport { findActive, type ActiveBrief } from './briefs.js';\nimport { briefIdFromBranch, ciBranch } from './branch.js';\nimport type { HarnessConfig } from './config.js';\nimport { loadConfig } from './fs.js';\nimport { commonDirectory, currentBranch, workTreeRoot } from './git.js';\nimport type { FindingFormat } from './formats.js';\nimport { createSiblings, type Siblings } from './siblings.js';\nimport type { BriefRow } from './types.js';\n\nexport const EXIT_OK = 0;\nexport const EXIT_FAILED = 1;\nexport const EXIT_ERROR = 2;\n\n/** The version of the JSON documents this tool prints. Bumped when a field changes meaning. */\nexport const JSON_SCHEMA_VERSION = 1;\n\nexport class UsageError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'UsageError';\n }\n}\n\nexport interface CliIO {\n readonly stdout: { write(text: string): unknown };\n readonly stderr: { write(text: string): unknown };\n readonly stdin?: () => Promise<string>;\n readonly cwd: string;\n readonly env: Readonly<Record<string, string | undefined>>;\n}\n\nexport function version(): string {\n const manifest = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as { version: string };\n return manifest.version;\n}\n\nexport interface Options {\n readonly command: string | undefined;\n readonly positionals: readonly string[];\n readonly brief: string | undefined;\n readonly base: string | undefined;\n readonly root: string | undefined;\n /** `gitlab`, `sarif` and `github` only for `audit` and `premises`, whose findings have places. */\n readonly format: 'pretty' | 'json' | FindingFormat;\n readonly strict: boolean;\n readonly help: boolean;\n readonly version: boolean;\n /** init: apply the plan instead of printing it. */\n readonly write: boolean;\n /** init: also install git's pre-commit hook. */\n readonly gitHook: boolean;\n /** escalate: the files the round needs, the reason, the options and the recommendation. */\n readonly paths: readonly string[];\n readonly reason: string | undefined;\n readonly options: readonly string[];\n readonly recommend: string | undefined;\n readonly list: boolean;\n readonly show: string | undefined;\n /** rule: the decision and what it allows. */\n readonly allow: boolean;\n readonly deny: boolean;\n readonly note: string | undefined;\n /** probe: which probe, and at which commit. */\n readonly id: string | undefined;\n readonly at: 'base' | 'head' | 'both' | undefined;\n}\n\nexport function parseOptions(argv: readonly string[]): Options {\n let parsed;\n try {\n parsed = parseArgs({\n args: [...argv],\n allowPositionals: true,\n strict: true,\n options: {\n brief: { type: 'string' },\n base: { type: 'string' },\n root: { type: 'string' },\n format: { type: 'string' },\n strict: { type: 'boolean' },\n write: { type: 'boolean' },\n 'git-hook': { type: 'boolean' },\n path: { type: 'string', multiple: true },\n reason: { type: 'string' },\n option: { type: 'string', multiple: true },\n recommend: { type: 'string' },\n list: { type: 'boolean' },\n show: { type: 'string' },\n allow: { type: 'boolean' },\n deny: { type: 'boolean' },\n note: { type: 'string' },\n id: { type: 'string' },\n at: { type: 'string' },\n help: { type: 'boolean', short: 'h' },\n version: { type: 'boolean', short: 'v' },\n },\n });\n } catch (error) {\n throw new UsageError((error as Error).message);\n }\n const { values, positionals } = parsed;\n const format = (values.format ?? 'pretty') as Options['format'];\n const formats: readonly Options['format'][] = ['pretty', 'json', 'gitlab', 'sarif', 'github'];\n if (!formats.includes(format)) throw new UsageError(`--format must be pretty, json, gitlab, sarif or github, not \"${format}\"`);\n const command = positionals[0];\n // Only the commands whose findings have places print them for a forge.\n if (command !== undefined && command !== 'audit' && command !== 'premises' && format !== 'pretty' && format !== 'json') {\n throw new UsageError(`--format ${format} is for audit and premises; ${command} prints pretty or json`);\n }\n const at = values.at;\n if (at !== undefined && at !== 'base' && at !== 'head' && at !== 'both') throw new UsageError(`--at must be base, head or both, not \"${at}\"`);\n return {\n command,\n positionals: positionals.slice(1),\n brief: values.brief,\n base: values.base,\n root: values.root,\n format,\n strict: values.strict === true,\n help: values.help === true,\n version: values.version === true,\n write: values.write === true,\n gitHook: values['git-hook'] === true,\n paths: values.path ?? [],\n reason: values.reason,\n options: values.option ?? [],\n recommend: values.recommend,\n list: values.list === true,\n show: values.show,\n allow: values.allow === true,\n deny: values.deny === true,\n note: values.note,\n id: values.id,\n at,\n };\n}\n\n/** Everything a command needs about the repository it runs in. */\nexport interface Workspace {\n readonly root: string;\n readonly commonDir: string;\n readonly config: HarnessConfig;\n readonly siblings: Siblings;\n /** The branch checked out, or on a detached head the one the forge's CI names; `null` when neither names one. */\n readonly branch: string | null;\n /** The CI variable that named the branch, on a detached head; absent when git named it. */\n readonly branchSource?: string | undefined;\n readonly cwd: string;\n}\n\nexport async function openWorkspace(options: Options, io: CliIO): Promise<Workspace> {\n const cwd = options.root ?? io.cwd;\n const root = await workTreeRoot(cwd);\n if (root === null) throw new UsageError(`${cwd} is not inside a git work tree; spec-harness measures rounds by their commits`);\n const commonDir = (await commonDirectory(root)) ?? `${root}/.git`;\n const { config } = await loadConfig(root);\n // A branch checked out is the branch; only a head on none, as CI checks\n // out, asks the forge which branch the run builds.\n const checkedOut = await currentBranch(root);\n const ci = checkedOut === null ? ciBranch(io.env) : null;\n const named = ci === null ? { branch: checkedOut } : { branch: ci.branch, branchSource: ci.source };\n return { root, commonDir, config, siblings: createSiblings(root, config), ...named, cwd: io.cwd };\n}\n\n/** The id the flag, the environment or the branch names, without asking spec-brief anything. */\nexport function namedId(workspace: Workspace, options: Options, env: CliIO['env']): string | null {\n if (options.brief !== undefined && options.brief.trim() !== '') return options.brief.trim();\n const environment = env['SPEC_BRIEF'];\n if (environment !== undefined && environment.trim() !== '') return environment.trim();\n return workspace.branch === null ? null : briefIdFromBranch(workspace.config.branches, workspace.branch);\n}\n\nexport async function activeBrief(workspace: Workspace, options: Options, env: CliIO['env']): Promise<ActiveBrief> {\n if (namedId(workspace, options, env) === null) {\n return findActive([], {});\n }\n const briefs = await workspace.siblings.briefs();\n const fromBranch = workspace.branch === null ? null : briefIdFromBranch(workspace.config.branches, workspace.branch);\n return findActive(briefs, { flag: options.brief, environment: env['SPEC_BRIEF'], branch: fromBranch });\n}\n\nexport function describeActive(active: ActiveBrief): { brief: BriefRow | null; note: string | undefined; problem: string | null } {\n switch (active.kind) {\n case 'found':\n return { brief: active.brief, note: undefined, problem: null };\n case 'none':\n return { brief: null, note: active.reason, problem: null };\n case 'unknown':\n return { brief: null, note: undefined, problem: `the ${active.source} names brief ${active.id}, and spec-brief knows no such brief` };\n case 'archived':\n return {\n brief: null,\n note: undefined,\n problem: `the ${active.source} names brief ${active.brief.id}, which is archived; a closed round writes nothing`,\n };\n }\n}\n\nexport function json(command: string, body: Record<string, unknown>): string {\n return `${JSON.stringify({ tool: 'spec-harness', version: version(), schemaVersion: JSON_SCHEMA_VERSION, command, ...body }, null, 2)}\\n`;\n}\n\n"]}
1
+ {"version":3,"file":"workspace.js","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What every command shares: exit codes, options, the repository a command\n * runs in, and which brief the round is working on.\n */\n\nimport { readFileSync } from 'node:fs';\nimport { parseArgs } from 'node:util';\n\nimport { findActive, type ActiveBrief } from './briefs.js';\nimport { briefIdFromBranch, ciBranch } from './branch.js';\nimport type { HarnessConfig } from './config.js';\nimport { loadConfig } from './fs.js';\nimport { commonDirectory, currentBranch, workTreeRoot } from './git.js';\nimport type { FindingFormat } from './formats.js';\nimport { createSiblings, type Siblings } from './siblings.js';\nimport type { BriefRow } from './types.js';\n\nexport const EXIT_OK = 0;\nexport const EXIT_FAILED = 1;\nexport const EXIT_ERROR = 2;\n\n/** The version of the JSON documents this tool prints. Bumped when a field changes meaning. */\nexport const JSON_SCHEMA_VERSION = 1;\n\nexport class UsageError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'UsageError';\n }\n}\n\nexport interface CliIO {\n readonly stdout: { write(text: string): unknown };\n readonly stderr: { write(text: string): unknown };\n readonly stdin?: () => Promise<string>;\n readonly cwd: string;\n readonly env: Readonly<Record<string, string | undefined>>;\n}\n\nexport function version(): string {\n const manifest = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as { version: string };\n return manifest.version;\n}\n\nexport interface Options {\n readonly command: string | undefined;\n readonly positionals: readonly string[];\n readonly brief: string | undefined;\n readonly base: string | undefined;\n readonly root: string | undefined;\n /** `gitlab`, `sarif` and `github` only for `audit` and `premises`, whose findings have places. */\n readonly format: 'pretty' | 'json' | FindingFormat;\n readonly strict: boolean;\n readonly help: boolean;\n readonly version: boolean;\n /** init: apply the plan instead of printing it. */\n readonly write: boolean;\n /** init: also install git's pre-commit hook. */\n readonly gitHook: boolean;\n /** escalate: the files the round needs, the reason, the options and the recommendation. */\n readonly paths: readonly string[];\n readonly reason: string | undefined;\n readonly options: readonly string[];\n readonly recommend: string | undefined;\n readonly list: boolean;\n readonly show: string | undefined;\n /** rule: the decision and what it allows. */\n readonly allow: boolean;\n readonly deny: boolean;\n readonly note: string | undefined;\n /** probe: which probe, and at which commit. */\n readonly id: string | undefined;\n readonly at: 'base' | 'head' | 'both' | undefined;\n}\n\nexport function parseOptions(argv: readonly string[]): Options {\n let parsed;\n try {\n parsed = parseArgs({\n args: [...argv],\n allowPositionals: true,\n strict: true,\n options: {\n brief: { type: 'string' },\n base: { type: 'string' },\n root: { type: 'string' },\n format: { type: 'string' },\n strict: { type: 'boolean' },\n write: { type: 'boolean' },\n 'git-hook': { type: 'boolean' },\n path: { type: 'string', multiple: true },\n reason: { type: 'string' },\n option: { type: 'string', multiple: true },\n recommend: { type: 'string' },\n list: { type: 'boolean' },\n show: { type: 'string' },\n allow: { type: 'boolean' },\n deny: { type: 'boolean' },\n note: { type: 'string' },\n id: { type: 'string' },\n at: { type: 'string' },\n help: { type: 'boolean', short: 'h' },\n version: { type: 'boolean', short: 'v' },\n },\n });\n } catch (error) {\n throw new UsageError((error as Error).message);\n }\n const { values, positionals } = parsed;\n const format = (values.format ?? 'pretty') as Options['format'];\n const formats: readonly Options['format'][] = ['pretty', 'json', 'gitlab', 'sarif', 'github'];\n if (!formats.includes(format)) throw new UsageError(`--format must be pretty, json, gitlab, sarif or github, not \"${format}\"`);\n const command = positionals[0];\n // Only the commands whose findings have places print them for a forge.\n if (command !== undefined && command !== 'audit' && command !== 'premises' && format !== 'pretty' && format !== 'json') {\n throw new UsageError(`--format ${format} is for audit and premises; ${command} prints pretty or json`);\n }\n const at = values.at;\n if (at !== undefined && at !== 'base' && at !== 'head' && at !== 'both') throw new UsageError(`--at must be base, head or both, not \"${at}\"`);\n return {\n command,\n positionals: positionals.slice(1),\n brief: values.brief,\n base: values.base,\n root: values.root,\n format,\n strict: values.strict === true,\n help: values.help === true,\n version: values.version === true,\n write: values.write === true,\n gitHook: values['git-hook'] === true,\n paths: values.path ?? [],\n reason: values.reason,\n options: values.option ?? [],\n recommend: values.recommend,\n list: values.list === true,\n show: values.show,\n allow: values.allow === true,\n deny: values.deny === true,\n note: values.note,\n id: values.id,\n at,\n };\n}\n\n/** Everything a command needs about the repository it runs in. */\nexport interface Workspace {\n readonly root: string;\n readonly commonDir: string;\n readonly config: HarnessConfig;\n readonly siblings: Siblings;\n /** The branch checked out, or on a detached head the one the forge's CI names; `null` when neither names one. */\n readonly branch: string | null;\n /** The CI variable that named the branch, on a detached head; absent when git named it. */\n readonly branchSource?: string | undefined;\n readonly cwd: string;\n}\n\nexport async function openWorkspace(options: Options, io: CliIO): Promise<Workspace> {\n const cwd = options.root ?? io.cwd;\n const root = await workTreeRoot(cwd);\n if (root === null) throw new UsageError(`${cwd} is not inside a git work tree; spec-harness measures rounds by their commits`);\n // Inside a work tree git always names the common directory; the fallback\n // is there for the type.\n const commonDir = (await commonDirectory(root)) ?? `${root}/.git`;\n const { config } = await loadConfig(root);\n // A branch checked out is the branch; only a head on none, as CI checks\n // out, asks the forge which branch the run builds.\n const checkedOut = await currentBranch(root);\n const ci = checkedOut === null ? ciBranch(io.env) : null;\n const named = ci === null ? { branch: checkedOut } : { branch: ci.branch, branchSource: ci.source };\n return { root, commonDir, config, siblings: createSiblings(root, config), ...named, cwd: io.cwd };\n}\n\n/** The id the flag, the environment or the branch names, without asking spec-brief anything. */\nexport function namedId(workspace: Workspace, options: Options, env: CliIO['env']): string | null {\n if (options.brief !== undefined && options.brief.trim() !== '') return options.brief.trim();\n const environment = env['SPEC_BRIEF'];\n if (environment !== undefined && environment.trim() !== '') return environment.trim();\n return briefIdFromBranch(workspace.config.branches, workspace.branch);\n}\n\nexport async function activeBrief(workspace: Workspace, options: Options, env: CliIO['env']): Promise<ActiveBrief> {\n // Nothing names a brief, so spec-brief is not asked (ADR-0004): the answer\n // is none whatever the briefs are, and so the empty list is equivalent to\n // its mutant.\n if (namedId(workspace, options, env) === null) {\n return findActive([], {});\n }\n const briefs = await workspace.siblings.briefs();\n const fromBranch = briefIdFromBranch(workspace.config.branches, workspace.branch);\n return findActive(briefs, { flag: options.brief, environment: env['SPEC_BRIEF'], branch: fromBranch });\n}\n\n/**\n * The brief a command acts on, or why there is none: a note when nothing names\n * one, which a command that needs a brief refuses with, or a problem when what\n * names one is wrong.\n */\nexport function describeActive(\n active: ActiveBrief,\n): { brief: BriefRow; note: undefined; problem: null } | { brief: null; note: string; problem: null } | { brief: null; note: undefined; problem: string } {\n switch (active.kind) {\n case 'found':\n return { brief: active.brief, note: undefined, problem: null };\n case 'none':\n return { brief: null, note: active.reason, problem: null };\n case 'unknown':\n return { brief: null, note: undefined, problem: `the ${active.source} names brief ${active.id}, and spec-brief knows no such brief` };\n case 'archived':\n return {\n brief: null,\n note: undefined,\n problem: `the ${active.source} names brief ${active.brief.id}, which is archived; a closed round writes nothing`,\n };\n }\n}\n\nexport function json(command: string, body: Record<string, unknown>): string {\n return `${JSON.stringify({ tool: 'spec-harness', version: version(), schemaVersion: JSON_SCHEMA_VERSION, command, ...body }, null, 2)}\\n`;\n}\n\n"]}