@descent-vtt/spec-harness 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.mcp.json +5 -4
- package/CHANGELOG.md +69 -1
- package/README.md +22 -12
- package/dist/audit.d.ts +23 -2
- package/dist/audit.js +30 -1
- package/dist/audit.js.map +1 -1
- package/dist/cli.js +30 -5
- package/dist/cli.js.map +1 -1
- package/dist/commands.js +1 -1
- package/dist/commands.js.map +1 -1
- package/dist/configure.d.ts +105 -0
- package/dist/configure.js +210 -0
- package/dist/configure.js.map +1 -0
- package/dist/context.d.ts +31 -4
- package/dist/context.js +40 -7
- package/dist/context.js.map +1 -1
- package/dist/fs.d.ts +2 -0
- package/dist/fs.js +12 -0
- package/dist/fs.js.map +1 -1
- package/dist/git.d.ts +2 -0
- package/dist/git.js +5 -0
- package/dist/git.js.map +1 -1
- package/dist/guard.d.ts +2 -0
- package/dist/guard.js +5 -1
- package/dist/guard.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/plugin.d.ts +2 -1
- package/dist/plugin.js +4 -9
- package/dist/plugin.js.map +1 -1
- package/dist/premises.d.ts +4 -0
- package/dist/premises.js +20 -14
- package/dist/premises.js.map +1 -1
- package/dist/round.d.ts +7 -3
- package/dist/round.js +35 -26
- package/dist/round.js.map +1 -1
- package/dist/server.js +37 -13
- package/dist/server.js.map +1 -1
- package/dist/setup.d.ts +0 -9
- package/dist/setup.js +85 -68
- package/dist/setup.js.map +1 -1
- package/dist/siblings.d.ts +2 -1
- package/dist/siblings.js +1 -1
- package/dist/siblings.js.map +1 -1
- package/dist/versions.js +2 -2
- package/dist/versions.js.map +1 -1
- package/hooks/hooks.json +12 -2
- package/package.json +4 -5
- package/skills/close-round/SKILL.md +4 -0
package/dist/commands.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The commands that read a round: context, audit, escalate, rule, rulings\n * and probe. Each resolves the brief, asks `round.ts` for the facts and\n * prints them; exit codes follow the family contract.\n */\n\nimport { existsSync } from 'node:fs';\nimport { mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\n\nimport { briefIdFromBranch } from './branch.js';\nimport { findActive } from './briefs.js';\nimport { readJUnit } from './junit.js';\nimport { classify, readProbes, renderEvidence, verdictOf, type ProbeResult, type ProbeSpec } from './probe.js';\nimport { createReader } from './reader.js';\nimport {\n buildContext,\n checkRulings,\n briefText,\n listEscalations,\n raiseEscalation,\n recordRuling,\n resolveBase,\n runAudit,\n} from './round.js';\nimport { renderMemo, type EscalationOption } from './rulings.js';\nimport { runCommand, withWorktree } from './sandbox.js';\nimport type { BriefRow, Finding } from './types.js';\nimport {\n describeActive,\n EXIT_ERROR,\n EXIT_FAILED,\n EXIT_OK,\n json,\n UsageError,\n type CliIO,\n type Options,\n type Workspace,\n openWorkspace,\n} from './workspace.js';\n\nconst reader = createReader();\n\n/** The brief a command acts on: a positional id, else the active one. A command that needs one and has none is a usage error. */\nexport async function targetBrief(workspace: Workspace, options: Options, io: CliIO, positional: string | undefined): Promise<{ brief: BriefRow; briefs: BriefRow[] }> {\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: positional ?? options.brief, environment: io.env['SPEC_BRIEF'], branch: fromBranch });\n const { brief, note, problem } = describeActive(active);\n if (problem !== null) throw new UsageError(problem);\n if (brief === null) throw new UsageError(note ?? 'no brief is named');\n return { brief, briefs };\n}\n\nconst MARK: Record<Finding['severity'], string> = { error: 'error ', warning: 'warning', note: 'note ' };\n\nfunction printFindings(io: CliIO, findings: readonly Finding[]): void {\n for (const finding of findings) {\n const where = finding.file === undefined ? '' : `${finding.file}${finding.line === undefined ? '' : `:${finding.line}`} `;\n io.stdout.write(`${MARK[finding.severity]} ${where}${finding.message} ${finding.rule}\\n ${finding.hint}\\n`);\n }\n}\n\n/* ----------------------------------------------------------------- context */\n\nexport async function contextCommand(options: Options, io: CliIO): Promise<number> {\n const workspace = await openWorkspace(options, io);\n const { brief, briefs } = await targetBrief(workspace, options, io, options.positionals[0]);\n const packet = await buildContext(workspace, brief, briefs, reader);\n if (options.format === 'json') {\n io.stdout.write(json('context', { brief: brief.id, markdown: packet.markdown, included: packet.included, omitted: packet.omitted, unresolved: packet.unresolved }));\n } else {\n io.stdout.write(packet.markdown);\n }\n return EXIT_OK;\n}\n\n/* ------------------------------------------------------------------- audit */\n\nexport async function auditCommand(options: Options, io: CliIO): Promise<number> {\n const workspace = await openWorkspace(options, io);\n const { brief } = await targetBrief(workspace, options, io, options.positionals[0]);\n const result = await runAudit(workspace, brief, reader, options.base);\n const { counts, findings } = result.report;\n if (options.format === 'json') {\n io.stdout.write(\n json('audit', {\n ok: counts.error === 0 && (!options.strict || counts.warning === 0),\n brief: brief.id,\n base: result.base.kind === 'resolved' ? { ref: result.base.ref, mergeBase: result.base.mergeBase, head: result.base.head } : null,\n counts,\n findings,\n dependencies: result.dependencies,\n }),\n );\n } else {\n io.stdout.write(`audit of brief ${brief.id}${result.base.kind === 'resolved' ? ` from ${result.base.ref} (${result.base.mergeBase.slice(0, 12)})` : ''}\\n\\n`);\n printFindings(io, findings);\n io.stdout.write(`\\n${counts.error} error(s), ${counts.warning} warning(s), ${counts.note} note(s)\\n`);\n }\n return counts.error > 0 || (options.strict && counts.warning > 0) ? EXIT_FAILED : EXIT_OK;\n}\n\n/* -------------------------------------------------------------- escalation */\n\nfunction parseOption(raw: string): EscalationOption {\n const colon = raw.indexOf(':');\n return colon < 0 ? { label: raw.trim(), consequence: '' } : { label: raw.slice(0, colon).trim(), consequence: raw.slice(colon + 1).trim() };\n}\n\nexport async function escalateCommand(options: Options, io: CliIO): Promise<number> {\n const workspace = await openWorkspace(options, io);\n if (options.list) {\n const waiting = await listEscalations(workspace);\n if (options.format === 'json') io.stdout.write(json('escalate', { waiting }));\n else if (waiting.length === 0) io.stdout.write('no escalation is waiting\\n');\n else for (const request of waiting) io.stdout.write(`${request.id} brief ${request.brief} ${request.paths.join(', ')} ${request.reason.split('\\n')[0]}\\n`);\n return EXIT_OK;\n }\n if (options.show !== undefined) {\n const request = (await listEscalations(workspace)).find((candidate) => candidate.id === options.show);\n if (request === undefined) throw new UsageError(`no escalation \"${options.show}\" is waiting`);\n io.stdout.write(options.format === 'json' ? json('escalate', { request }) : renderMemo(request));\n return EXIT_OK;\n }\n if (options.paths.length === 0 || options.reason === undefined) {\n throw new UsageError('escalate needs --path <file> (repeatable) and --reason <why>; or --list, or --show <id>');\n }\n const { brief } = await targetBrief(workspace, options, io, undefined);\n const { request, memo } = await raiseEscalation(workspace, brief, {\n paths: options.paths,\n reason: options.reason,\n options: options.options.map(parseOption),\n recommendation: options.recommend ?? null,\n });\n io.stdout.write(options.format === 'json' ? json('escalate', { request, memo }) : memo);\n // Exit 1: the round is waiting on a person, which a caller must not read as done.\n return EXIT_FAILED;\n}\n\nexport async function ruleCommand(options: Options, io: CliIO): Promise<number> {\n const id = options.positionals[0];\n if (id === undefined) throw new UsageError('rule needs the escalation id, such as E-012-1');\n if (options.allow === options.deny) throw new UsageError('rule needs exactly one of --allow or --deny');\n if (options.note === undefined || options.note.trim() === '') throw new UsageError('rule needs --note: what exactly is allowed, or why not');\n const workspace = await openWorkspace(options, io);\n const recorded = await recordRuling(workspace, id, options.allow ? 'allow' : 'deny', options.note, reader);\n const command = `git commit -S -m \"ruling ${recorded.id}: ${options.allow ? 'allow' : 'deny'}\" -- ${recorded.file}`;\n if (options.format === 'json') {\n io.stdout.write(json('rule', { ruling: recorded.id, file: recorded.file, row: recorded.row, commit: command }));\n } else {\n io.stdout.write(`${recorded.file} now holds ruling ${recorded.id}:\\n\\n ${recorded.row}\\n\\nIt counts once you commit it signed, with a key the base branch's allowed signers list:\\n\\n ${command}\\n`);\n }\n return EXIT_OK;\n}\n\nexport async function rulingsCommand(options: Options, io: CliIO): Promise<number> {\n const workspace = await openWorkspace(options, io);\n const { brief } = await targetBrief(workspace, options, io, options.positionals[0]);\n const base = await resolveBase(workspace, options.base);\n const check = await checkRulings(workspace, brief, await briefText(workspace, brief), reader, base);\n if (options.format === 'json') {\n io.stdout.write(json('rulings', { brief: brief.id, rows: check.rows, verified: check.verified, unverified: check.unverified, problems: check.problems }));\n } else {\n if (check.rows.length === 0) io.stdout.write(`brief ${brief.id} holds no ruling\\n`);\n for (const row of check.rows) {\n const verified = check.verified.find((ruling) => ruling.id === row.id);\n const unverified = check.unverified.find((ruling) => ruling.id === row.id);\n const state = row.decision === 'deny' ? 'refused' : verified !== undefined ? `signed by ${verified.signer}` : `not verified: ${unverified?.reason ?? ''}`;\n io.stdout.write(`${row.id} ${row.decision} ${row.paths.join(', ')} ${state}\\n`);\n }\n printFindings(io, check.problems);\n }\n return check.unverified.length > 0 || check.problems.length > 0 ? EXIT_FAILED : EXIT_OK;\n}\n\n/* ------------------------------------------------------------------- probe */\n\nasync function runProbe(directory: string, probe: ProbeSpec, runs: number, timeout: number): Promise<ProbeResult['runs']> {\n const classified = [];\n for (let i = 0; i < runs; i += 1) {\n const report = probe.junit === null ? null : join(directory, probe.junit);\n if (report !== null) await rm(report, { force: true });\n const run = await runCommand(probe.run, directory, timeout);\n const junit = report === null || !existsSync(report) ? null : readJUnit(await readFile(report, 'utf8'));\n classified.push(classify(probe, { exitCode: run.exitCode, output: run.output, junit }));\n }\n return classified;\n}\n\nexport async function probeCommand(options: Options, io: CliIO): Promise<number> {\n const workspace = await openWorkspace(options, io);\n const { brief } = await targetBrief(workspace, options, io, options.positionals[0]);\n const text = await briefText(workspace, brief);\n const set = readProbes(reader.codeBlocks(text));\n if (set.problems.length > 0) {\n for (const problem of set.problems) io.stderr.write(`spec-harness: ${brief.file}:${problem.line}: ${problem.message}\\n`);\n return EXIT_ERROR;\n }\n const probes = set.probes.filter((probe) => options.id === undefined || probe.id === options.id);\n if (probes.length === 0) {\n io.stdout.write(`brief ${brief.id} declares no probe${options.id === undefined ? '' : ` named ${options.id}`}\\n`);\n return options.id === undefined ? EXIT_OK : EXIT_ERROR;\n }\n const base = await resolveBase(workspace, options.base);\n if (base.kind === 'unresolved') throw new UsageError(base.reason);\n const at = options.at ?? (base.mergeBase === base.head ? 'base' : 'both');\n const results: ProbeResult[] = [];\n const measure = async (label: 'base' | 'head', commit: string, expected: 'red' | 'green'): Promise<void> => {\n await withWorktree(workspace.root, commit, async (directory) => {\n for (const file of set.files) {\n await mkdir(dirname(join(directory, file.path)), { recursive: true });\n await writeFile(join(directory, file.path), file.content.endsWith('\\n') ? file.content : `${file.content}\\n`);\n }\n const setups = [...new Set(probes.map((probe) => probe.setup).filter((setup): setup is string => setup !== null))];\n for (const setup of setups) {\n const run = await runCommand(setup, directory, workspace.config.probes.timeout);\n if (run.exitCode !== 0) throw new UsageError(`the probe setup \"${setup}\" failed at ${label}:\\n${run.output.slice(-2000)}`);\n }\n for (const probe of probes) {\n const runs = await runProbe(directory, probe, probe.runs ?? workspace.config.probes.runs, probe.timeout ?? workspace.config.probes.timeout);\n results.push({ probe, at: label, commit, expected, runs, verdict: verdictOf(expected, runs) });\n }\n });\n };\n if (at === 'base' || at === 'both') await measure('base', base.mergeBase, 'red');\n if (at === 'head' || at === 'both') await measure('head', base.head, 'green');\n const evidence = renderEvidence(results, set.hash, new Date().toISOString().slice(0, 10));\n const failed = results.filter((result) => result.verdict !== (result.expected === 'red' ? 'measured' : 'fixed'));\n if (options.format === 'json') {\n io.stdout.write(\n json('probe', {\n ok: failed.length === 0,\n brief: brief.id,\n hash: set.hash,\n results: results.map((result) => ({ id: result.probe.id, at: result.at, commit: result.commit, expected: result.expected, verdict: result.verdict, runs: result.runs })),\n evidence,\n }),\n );\n } else {\n io.stdout.write(`${evidence}\\n`);\n }\n return failed.length === 0 ? EXIT_OK : EXIT_FAILED;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The commands that read a round: context, audit, escalate, rule, rulings\n * and probe. Each resolves the brief, asks `round.ts` for the facts and\n * prints them; exit codes follow the family contract.\n */\n\nimport { existsSync } from 'node:fs';\nimport { mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\n\nimport { briefIdFromBranch } from './branch.js';\nimport { findActive } from './briefs.js';\nimport { readJUnit } from './junit.js';\nimport { classify, readProbes, renderEvidence, verdictOf, type ProbeResult, type ProbeSpec } from './probe.js';\nimport { createReader } from './reader.js';\nimport {\n buildContext,\n checkRulings,\n briefText,\n listEscalations,\n raiseEscalation,\n recordRuling,\n resolveBase,\n runAudit,\n} from './round.js';\nimport { renderMemo, type EscalationOption } from './rulings.js';\nimport { runCommand, withWorktree } from './sandbox.js';\nimport type { BriefRow, Finding } from './types.js';\nimport {\n describeActive,\n EXIT_ERROR,\n EXIT_FAILED,\n EXIT_OK,\n json,\n UsageError,\n type CliIO,\n type Options,\n type Workspace,\n openWorkspace,\n} from './workspace.js';\n\nconst reader = createReader();\n\n/** The brief a command acts on: a positional id, else the active one. A command that needs one and has none is a usage error. */\nexport async function targetBrief(workspace: Workspace, options: Options, io: CliIO, positional: string | undefined): Promise<{ brief: BriefRow; briefs: BriefRow[] }> {\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: positional ?? options.brief, environment: io.env['SPEC_BRIEF'], branch: fromBranch });\n const { brief, note, problem } = describeActive(active);\n if (problem !== null) throw new UsageError(problem);\n if (brief === null) throw new UsageError(note ?? 'no brief is named');\n return { brief, briefs };\n}\n\nconst MARK: Record<Finding['severity'], string> = { error: 'error ', warning: 'warning', note: 'note ' };\n\nfunction printFindings(io: CliIO, findings: readonly Finding[]): void {\n for (const finding of findings) {\n const where = finding.file === undefined ? '' : `${finding.file}${finding.line === undefined ? '' : `:${finding.line}`} `;\n io.stdout.write(`${MARK[finding.severity]} ${where}${finding.message} ${finding.rule}\\n ${finding.hint}\\n`);\n }\n}\n\n/* ----------------------------------------------------------------- context */\n\nexport async function contextCommand(options: Options, io: CliIO): Promise<number> {\n const workspace = await openWorkspace(options, io);\n const { brief, briefs } = await targetBrief(workspace, options, io, options.positionals[0]);\n const packet = await buildContext(workspace, brief, briefs, reader, options.base);\n if (options.format === 'json') {\n io.stdout.write(json('context', { brief: brief.id, markdown: packet.markdown, included: packet.included, omitted: packet.omitted, unresolved: packet.unresolved }));\n } else {\n io.stdout.write(packet.markdown);\n }\n return EXIT_OK;\n}\n\n/* ------------------------------------------------------------------- audit */\n\nexport async function auditCommand(options: Options, io: CliIO): Promise<number> {\n const workspace = await openWorkspace(options, io);\n const { brief } = await targetBrief(workspace, options, io, options.positionals[0]);\n const result = await runAudit(workspace, brief, reader, options.base);\n const { counts, findings } = result.report;\n if (options.format === 'json') {\n io.stdout.write(\n json('audit', {\n ok: counts.error === 0 && (!options.strict || counts.warning === 0),\n brief: brief.id,\n base: result.base.kind === 'resolved' ? { ref: result.base.ref, mergeBase: result.base.mergeBase, head: result.base.head } : null,\n counts,\n findings,\n dependencies: result.dependencies,\n }),\n );\n } else {\n io.stdout.write(`audit of brief ${brief.id}${result.base.kind === 'resolved' ? ` from ${result.base.ref} (${result.base.mergeBase.slice(0, 12)})` : ''}\\n\\n`);\n printFindings(io, findings);\n io.stdout.write(`\\n${counts.error} error(s), ${counts.warning} warning(s), ${counts.note} note(s)\\n`);\n }\n return counts.error > 0 || (options.strict && counts.warning > 0) ? EXIT_FAILED : EXIT_OK;\n}\n\n/* -------------------------------------------------------------- escalation */\n\nfunction parseOption(raw: string): EscalationOption {\n const colon = raw.indexOf(':');\n return colon < 0 ? { label: raw.trim(), consequence: '' } : { label: raw.slice(0, colon).trim(), consequence: raw.slice(colon + 1).trim() };\n}\n\nexport async function escalateCommand(options: Options, io: CliIO): Promise<number> {\n const workspace = await openWorkspace(options, io);\n if (options.list) {\n const waiting = await listEscalations(workspace);\n if (options.format === 'json') io.stdout.write(json('escalate', { waiting }));\n else if (waiting.length === 0) io.stdout.write('no escalation is waiting\\n');\n else for (const request of waiting) io.stdout.write(`${request.id} brief ${request.brief} ${request.paths.join(', ')} ${request.reason.split('\\n')[0]}\\n`);\n return EXIT_OK;\n }\n if (options.show !== undefined) {\n const request = (await listEscalations(workspace)).find((candidate) => candidate.id === options.show);\n if (request === undefined) throw new UsageError(`no escalation \"${options.show}\" is waiting`);\n io.stdout.write(options.format === 'json' ? json('escalate', { request }) : renderMemo(request));\n return EXIT_OK;\n }\n if (options.paths.length === 0 || options.reason === undefined) {\n throw new UsageError('escalate needs --path <file> (repeatable) and --reason <why>; or --list, or --show <id>');\n }\n const { brief } = await targetBrief(workspace, options, io, undefined);\n const { request, memo } = await raiseEscalation(workspace, brief, {\n paths: options.paths,\n reason: options.reason,\n options: options.options.map(parseOption),\n recommendation: options.recommend ?? null,\n });\n io.stdout.write(options.format === 'json' ? json('escalate', { request, memo }) : memo);\n // Exit 1: the round is waiting on a person, which a caller must not read as done.\n return EXIT_FAILED;\n}\n\nexport async function ruleCommand(options: Options, io: CliIO): Promise<number> {\n const id = options.positionals[0];\n if (id === undefined) throw new UsageError('rule needs the escalation id, such as E-012-1');\n if (options.allow === options.deny) throw new UsageError('rule needs exactly one of --allow or --deny');\n if (options.note === undefined || options.note.trim() === '') throw new UsageError('rule needs --note: what exactly is allowed, or why not');\n const workspace = await openWorkspace(options, io);\n const recorded = await recordRuling(workspace, id, options.allow ? 'allow' : 'deny', options.note, reader);\n const command = `git commit -S -m \"ruling ${recorded.id}: ${options.allow ? 'allow' : 'deny'}\" -- ${recorded.file}`;\n if (options.format === 'json') {\n io.stdout.write(json('rule', { ruling: recorded.id, file: recorded.file, row: recorded.row, commit: command }));\n } else {\n io.stdout.write(`${recorded.file} now holds ruling ${recorded.id}:\\n\\n ${recorded.row}\\n\\nIt counts once you commit it signed, with a key the base branch's allowed signers list:\\n\\n ${command}\\n`);\n }\n return EXIT_OK;\n}\n\nexport async function rulingsCommand(options: Options, io: CliIO): Promise<number> {\n const workspace = await openWorkspace(options, io);\n const { brief } = await targetBrief(workspace, options, io, options.positionals[0]);\n const base = await resolveBase(workspace, options.base);\n const check = await checkRulings(workspace, brief, await briefText(workspace, brief), reader, base);\n if (options.format === 'json') {\n io.stdout.write(json('rulings', { brief: brief.id, rows: check.rows, verified: check.verified, unverified: check.unverified, problems: check.problems }));\n } else {\n if (check.rows.length === 0) io.stdout.write(`brief ${brief.id} holds no ruling\\n`);\n for (const row of check.rows) {\n const verified = check.verified.find((ruling) => ruling.id === row.id);\n const unverified = check.unverified.find((ruling) => ruling.id === row.id);\n const state = row.decision === 'deny' ? 'refused' : verified !== undefined ? `signed by ${verified.signer}` : `not verified: ${unverified?.reason ?? ''}`;\n io.stdout.write(`${row.id} ${row.decision} ${row.paths.join(', ')} ${state}\\n`);\n }\n printFindings(io, check.problems);\n }\n return check.unverified.length > 0 || check.problems.length > 0 ? EXIT_FAILED : EXIT_OK;\n}\n\n/* ------------------------------------------------------------------- probe */\n\nasync function runProbe(directory: string, probe: ProbeSpec, runs: number, timeout: number): Promise<ProbeResult['runs']> {\n const classified = [];\n for (let i = 0; i < runs; i += 1) {\n const report = probe.junit === null ? null : join(directory, probe.junit);\n if (report !== null) await rm(report, { force: true });\n const run = await runCommand(probe.run, directory, timeout);\n const junit = report === null || !existsSync(report) ? null : readJUnit(await readFile(report, 'utf8'));\n classified.push(classify(probe, { exitCode: run.exitCode, output: run.output, junit }));\n }\n return classified;\n}\n\nexport async function probeCommand(options: Options, io: CliIO): Promise<number> {\n const workspace = await openWorkspace(options, io);\n const { brief } = await targetBrief(workspace, options, io, options.positionals[0]);\n const text = await briefText(workspace, brief);\n const set = readProbes(reader.codeBlocks(text));\n if (set.problems.length > 0) {\n for (const problem of set.problems) io.stderr.write(`spec-harness: ${brief.file}:${problem.line}: ${problem.message}\\n`);\n return EXIT_ERROR;\n }\n const probes = set.probes.filter((probe) => options.id === undefined || probe.id === options.id);\n if (probes.length === 0) {\n io.stdout.write(`brief ${brief.id} declares no probe${options.id === undefined ? '' : ` named ${options.id}`}\\n`);\n return options.id === undefined ? EXIT_OK : EXIT_ERROR;\n }\n const base = await resolveBase(workspace, options.base);\n if (base.kind === 'unresolved') throw new UsageError(base.reason);\n const at = options.at ?? (base.mergeBase === base.head ? 'base' : 'both');\n const results: ProbeResult[] = [];\n const measure = async (label: 'base' | 'head', commit: string, expected: 'red' | 'green'): Promise<void> => {\n await withWorktree(workspace.root, commit, async (directory) => {\n for (const file of set.files) {\n await mkdir(dirname(join(directory, file.path)), { recursive: true });\n await writeFile(join(directory, file.path), file.content.endsWith('\\n') ? file.content : `${file.content}\\n`);\n }\n const setups = [...new Set(probes.map((probe) => probe.setup).filter((setup): setup is string => setup !== null))];\n for (const setup of setups) {\n const run = await runCommand(setup, directory, workspace.config.probes.timeout);\n if (run.exitCode !== 0) throw new UsageError(`the probe setup \"${setup}\" failed at ${label}:\\n${run.output.slice(-2000)}`);\n }\n for (const probe of probes) {\n const runs = await runProbe(directory, probe, probe.runs ?? workspace.config.probes.runs, probe.timeout ?? workspace.config.probes.timeout);\n results.push({ probe, at: label, commit, expected, runs, verdict: verdictOf(expected, runs) });\n }\n });\n };\n if (at === 'base' || at === 'both') await measure('base', base.mergeBase, 'red');\n if (at === 'head' || at === 'both') await measure('head', base.head, 'green');\n const evidence = renderEvidence(results, set.hash, new Date().toISOString().slice(0, 10));\n const failed = results.filter((result) => result.verdict !== (result.expected === 'red' ? 'measured' : 'fixed'));\n if (options.format === 'json') {\n io.stdout.write(\n json('probe', {\n ok: failed.length === 0,\n brief: brief.id,\n hash: set.hash,\n results: results.map((result) => ({ id: result.probe.id, at: result.at, commit: result.commit, expected: result.expected, verdict: result.verdict, runs: result.runs })),\n evidence,\n }),\n );\n } else {\n io.stdout.write(`${evidence}\\n`);\n }\n return failed.length === 0 ? EXIT_OK : EXIT_FAILED;\n}\n"]}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `init` writes into each tool's configuration, and what `doctor` says
|
|
3
|
+
* of the result.
|
|
4
|
+
*
|
|
5
|
+
* Pure: `setup.ts` and the command line read the files and ask git, and
|
|
6
|
+
* write what these return, so the core sweep holds these to the unit suite.
|
|
7
|
+
* Each merge keeps what a person wrote and returns `null` when there is
|
|
8
|
+
* nothing to add.
|
|
9
|
+
*/
|
|
10
|
+
type Json = Record<string, unknown>;
|
|
11
|
+
/** The project root, as Claude Code substitutes it in a hook's arguments and in a plugin server's. */
|
|
12
|
+
export declare const PROJECT_DIR = "${CLAUDE_PROJECT_DIR}";
|
|
13
|
+
/**
|
|
14
|
+
* The project root in a project's own `.mcp.json`. Claude Code expands it
|
|
15
|
+
* there from its own environment, which does not hold it, so it needs a
|
|
16
|
+
* default; `.` is right, since a project's server starts in the project.
|
|
17
|
+
*/
|
|
18
|
+
export declare const PROJECT_DIR_OR_HERE = "${CLAUDE_PROJECT_DIR:-.}";
|
|
19
|
+
/**
|
|
20
|
+
* The guard as a Claude Code hook: exec form, `args` given, so the project's
|
|
21
|
+
* path is one argument whatever it holds, and no shell is involved.
|
|
22
|
+
*/
|
|
23
|
+
export declare const GUARD_HOOK: Readonly<Json>;
|
|
24
|
+
/** `.claude/settings.json`, or the plugin's hooks, with the guard hooks merged in; `null` when they are already there. */
|
|
25
|
+
export declare function mergeClaudeSettings(current: Json): Json | null;
|
|
26
|
+
/** The server's entry, reading the project at `projectDir`: {@link PROJECT_DIR} for the plugin, {@link PROJECT_DIR_OR_HERE} for a project. */
|
|
27
|
+
export declare function mcpServer(projectDir: string): Json;
|
|
28
|
+
/**
|
|
29
|
+
* A project's `.mcp.json` with the harness's server merged in, or `null` when
|
|
30
|
+
* one is registered. A server registered under the name some other way is
|
|
31
|
+
* the person's, and stays; 0.1's npx entry is replaced, keeping its other
|
|
32
|
+
* fields.
|
|
33
|
+
*/
|
|
34
|
+
export declare function mergeMcp(current: Json): Json | null;
|
|
35
|
+
/** The spec-brief plugin this package ships, as spec-brief's `plugins` names it. */
|
|
36
|
+
export declare const PLUGIN = "@descent-vtt/spec-harness/spec-brief-plugin";
|
|
37
|
+
/** spec-brief's configuration files, in the order spec-brief looks for them in a directory. */
|
|
38
|
+
export declare const SPEC_BRIEF_CONFIGS: readonly string[];
|
|
39
|
+
/** Whether spec-brief's configuration loads the plugin, by name or as `{ module, options }`. */
|
|
40
|
+
export declare function loadsPlugin(config: Json): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* spec-brief's configuration with the plugin loaded, or `null` when it is.
|
|
43
|
+
* spec-brief's archive refuses a round that changed a protected file, and
|
|
44
|
+
* learns that a signed ruling allows it only by asking the plugin (ADR-0006).
|
|
45
|
+
*/
|
|
46
|
+
export declare function mergeSpecBrief(current: Json): Json | null;
|
|
47
|
+
/** Whether spec-brief loads the plugin, as its configuration at the root says. */
|
|
48
|
+
export type PluginState = {
|
|
49
|
+
readonly kind: 'loaded' | 'not-loaded' | 'unreadable';
|
|
50
|
+
readonly file: string;
|
|
51
|
+
} | {
|
|
52
|
+
readonly kind: 'unconfigured';
|
|
53
|
+
};
|
|
54
|
+
/** What a person reads about the plugin, and what to do when it is not loaded. */
|
|
55
|
+
export declare function describePlugin(state: PluginState): string;
|
|
56
|
+
/** What git says that tells a base apart. */
|
|
57
|
+
export interface BaseFacts {
|
|
58
|
+
/** The remote's default branch as a local ref, such as `origin/main`, or `null`. */
|
|
59
|
+
readonly remoteDefault: string | null;
|
|
60
|
+
/** The branch checked out, or `null` on a detached head. */
|
|
61
|
+
readonly branch: string | null;
|
|
62
|
+
/** Every local branch. */
|
|
63
|
+
readonly branches: readonly string[];
|
|
64
|
+
}
|
|
65
|
+
/** The base `init` names, and why; `base` is `null` when none can be told. */
|
|
66
|
+
export interface BaseChoice {
|
|
67
|
+
readonly base: string | null;
|
|
68
|
+
readonly detail: string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The base `init` writes into `.spec-harness.json`. Every ruling is verified
|
|
72
|
+
* against the allowed signers on it, so it is named rather than left to be
|
|
73
|
+
* found. The remote's default branch comes first, but git records it when
|
|
74
|
+
* it clones, and before 2.48 not on a fetch: a repository made with `git
|
|
75
|
+
* init` and pushed to a remote often has none. There the branch init runs on
|
|
76
|
+
* is the base when it is `main` or `master`, or the only branch; otherwise
|
|
77
|
+
* the person names it.
|
|
78
|
+
*/
|
|
79
|
+
export declare function chooseBase(facts: BaseFacts): BaseChoice;
|
|
80
|
+
/** Where a base came from: `--base`, the configuration, or the remote. */
|
|
81
|
+
export type BaseSource = 'flag' | 'config' | 'remote';
|
|
82
|
+
/** The base a command resolved, and how; or why there is none. */
|
|
83
|
+
export declare function describeBase(base: {
|
|
84
|
+
readonly ref: string;
|
|
85
|
+
readonly source: BaseSource;
|
|
86
|
+
readonly mergeBase: string;
|
|
87
|
+
} | {
|
|
88
|
+
readonly reason: string;
|
|
89
|
+
}): string;
|
|
90
|
+
/** Whether the allowed-signers file is on the base, where every ruling's signature is checked against it. */
|
|
91
|
+
export declare function describeSigners(file: string, base: string | null, onBase: boolean): string;
|
|
92
|
+
/** spec-graph's configuration files, in the order spec-graph reads them; then a "spec-graph" key in package.json. */
|
|
93
|
+
export declare const SPEC_GRAPH_CONFIGS: readonly string[];
|
|
94
|
+
/** Whether spec-graph, configured as `config` says, reads the briefs in the directory `briefs`. */
|
|
95
|
+
export declare function graphReadsBriefs(config: Json, briefs: string): boolean;
|
|
96
|
+
/**
|
|
97
|
+
* What the history entry does. spec-graph reads a brief whose status says
|
|
98
|
+
* `archived` as a record already; the entry makes one a record whatever word
|
|
99
|
+
* its status uses, or none - and it does anything only where spec-graph
|
|
100
|
+
* reads the briefs, which its defaults do not in `briefs/`.
|
|
101
|
+
*/
|
|
102
|
+
export declare function describeGraph(archiveGlob: string, briefs: string, reads: boolean, added: boolean): string;
|
|
103
|
+
/** spec-graph's configuration with the archive read as history, or `null` when it already is. */
|
|
104
|
+
export declare function mergeSpecGraph(current: Json, archiveGlob: string): Json | null;
|
|
105
|
+
export {};
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `init` writes into each tool's configuration, and what `doctor` says
|
|
3
|
+
* of the result.
|
|
4
|
+
*
|
|
5
|
+
* Pure: `setup.ts` and the command line read the files and ask git, and
|
|
6
|
+
* write what these return, so the core sweep holds these to the unit suite.
|
|
7
|
+
* Each merge keeps what a person wrote and returns `null` when there is
|
|
8
|
+
* nothing to add.
|
|
9
|
+
*/
|
|
10
|
+
import { parseGlobList } from './vendor/spec-core/pattern/index.js';
|
|
11
|
+
function isObject(value) {
|
|
12
|
+
return typeof value === 'object' && value !== null;
|
|
13
|
+
}
|
|
14
|
+
function sameStrings(value, expected) {
|
|
15
|
+
return Array.isArray(value) && value.length === expected.length && value.every((item, index) => item === expected[index]);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The command line as the project installed it. Claude Code starts a plugin's
|
|
19
|
+
* server in the plugin's own directory and a hook wherever the session
|
|
20
|
+
* stands, so neither can rely on `npx` finding the project's install; and on
|
|
21
|
+
* Windows a server's command is started without a shell, where `npx` is a
|
|
22
|
+
* shim that cannot start at all. `node` and the script's path work
|
|
23
|
+
* everywhere.
|
|
24
|
+
*/
|
|
25
|
+
const BIN = 'node_modules/@descent-vtt/spec-harness/bin/spec-harness.js';
|
|
26
|
+
/** The project root, as Claude Code substitutes it in a hook's arguments and in a plugin server's. */
|
|
27
|
+
export const PROJECT_DIR = '${CLAUDE_PROJECT_DIR}';
|
|
28
|
+
/**
|
|
29
|
+
* The project root in a project's own `.mcp.json`. Claude Code expands it
|
|
30
|
+
* there from its own environment, which does not hold it, so it needs a
|
|
31
|
+
* default; `.` is right, since a project's server starts in the project.
|
|
32
|
+
*/
|
|
33
|
+
export const PROJECT_DIR_OR_HERE = '${CLAUDE_PROJECT_DIR:-.}';
|
|
34
|
+
const HOOK_MATCHER = 'Edit|Write|MultiEdit|NotebookEdit';
|
|
35
|
+
const GUARDED_EVENTS = ['PreToolUse', 'PostToolUse'];
|
|
36
|
+
const GUARD_ARGS = Object.freeze([`${PROJECT_DIR}/${BIN}`, 'hook', 'claude']);
|
|
37
|
+
/**
|
|
38
|
+
* The guard as a Claude Code hook: exec form, `args` given, so the project's
|
|
39
|
+
* path is one argument whatever it holds, and no shell is involved.
|
|
40
|
+
*/
|
|
41
|
+
export const GUARD_HOOK = Object.freeze({ type: 'command', command: 'node', args: GUARD_ARGS, timeout: 60 });
|
|
42
|
+
/** What 0.1 installed: an npx command, found from wherever the session stood. */
|
|
43
|
+
const LEGACY_HOOK = 'npx --no-install spec-harness hook claude';
|
|
44
|
+
const LEGACY_SERVER_ARGS = ['--no-install', 'spec-harness', 'mcp'];
|
|
45
|
+
function isGuard(hook) {
|
|
46
|
+
return isObject(hook) && hook['command'] === 'node' && sameStrings(hook['args'], GUARD_ARGS);
|
|
47
|
+
}
|
|
48
|
+
function isLegacyGuard(hook) {
|
|
49
|
+
return isObject(hook) && hook['command'] === LEGACY_HOOK;
|
|
50
|
+
}
|
|
51
|
+
function hooksOf(group) {
|
|
52
|
+
return isObject(group) && Array.isArray(group['hooks']) ? group['hooks'] : [];
|
|
53
|
+
}
|
|
54
|
+
/** A group with 0.1's guard replaced where it stands, keeping its other fields; the group itself when it has none. */
|
|
55
|
+
function upgraded(group) {
|
|
56
|
+
const hooks = hooksOf(group);
|
|
57
|
+
if (!hooks.some(isLegacyGuard))
|
|
58
|
+
return group;
|
|
59
|
+
return { ...group, hooks: hooks.map((hook) => (isLegacyGuard(hook) ? { ...hook, command: 'node', args: [...GUARD_ARGS] } : hook)) };
|
|
60
|
+
}
|
|
61
|
+
/** `.claude/settings.json`, or the plugin's hooks, with the guard hooks merged in; `null` when they are already there. */
|
|
62
|
+
export function mergeClaudeSettings(current) {
|
|
63
|
+
const hooks = isObject(current['hooks']) ? current['hooks'] : {};
|
|
64
|
+
let changed = false;
|
|
65
|
+
const next = { ...hooks };
|
|
66
|
+
for (const event of GUARDED_EVENTS) {
|
|
67
|
+
const listed = hooks[event];
|
|
68
|
+
const before = Array.isArray(listed) ? listed : [];
|
|
69
|
+
const groups = before.map(upgraded);
|
|
70
|
+
const replaced = groups.some((group, index) => group !== before[index]);
|
|
71
|
+
const present = groups.some((group) => hooksOf(group).some(isGuard));
|
|
72
|
+
if (!present)
|
|
73
|
+
groups.push({ matcher: HOOK_MATCHER, hooks: [{ ...GUARD_HOOK, args: [...GUARD_ARGS] }] });
|
|
74
|
+
if (replaced || !present) {
|
|
75
|
+
next[event] = groups;
|
|
76
|
+
changed = true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return changed ? { ...current, hooks: next } : null;
|
|
80
|
+
}
|
|
81
|
+
/** The server's entry, reading the project at `projectDir`: {@link PROJECT_DIR} for the plugin, {@link PROJECT_DIR_OR_HERE} for a project. */
|
|
82
|
+
export function mcpServer(projectDir) {
|
|
83
|
+
return { command: 'node', args: [`${projectDir}/${BIN}`, 'mcp', '--root', projectDir] };
|
|
84
|
+
}
|
|
85
|
+
function isLegacyServer(server) {
|
|
86
|
+
return isObject(server) && server['command'] === 'npx' && sameStrings(server['args'], LEGACY_SERVER_ARGS);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* A project's `.mcp.json` with the harness's server merged in, or `null` when
|
|
90
|
+
* one is registered. A server registered under the name some other way is
|
|
91
|
+
* the person's, and stays; 0.1's npx entry is replaced, keeping its other
|
|
92
|
+
* fields.
|
|
93
|
+
*/
|
|
94
|
+
export function mergeMcp(current) {
|
|
95
|
+
const servers = isObject(current['mcpServers']) ? current['mcpServers'] : {};
|
|
96
|
+
const existing = servers['spec-harness'];
|
|
97
|
+
if ('spec-harness' in servers && !isLegacyServer(existing))
|
|
98
|
+
return null;
|
|
99
|
+
return { ...current, mcpServers: { ...servers, 'spec-harness': { ...existing, ...mcpServer(PROJECT_DIR_OR_HERE) } } };
|
|
100
|
+
}
|
|
101
|
+
/** The spec-brief plugin this package ships, as spec-brief's `plugins` names it. */
|
|
102
|
+
export const PLUGIN = '@descent-vtt/spec-harness/spec-brief-plugin';
|
|
103
|
+
/** spec-brief's configuration files, in the order spec-brief looks for them in a directory. */
|
|
104
|
+
export const SPEC_BRIEF_CONFIGS = ['.spec-brief.json', 'spec-brief.json'];
|
|
105
|
+
/** Whether spec-brief's configuration loads the plugin, by name or as `{ module, options }`. */
|
|
106
|
+
export function loadsPlugin(config) {
|
|
107
|
+
const plugins = config['plugins'];
|
|
108
|
+
return Array.isArray(plugins) && plugins.some((entry) => entry === PLUGIN || (isObject(entry) && entry['module'] === PLUGIN));
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* spec-brief's configuration with the plugin loaded, or `null` when it is.
|
|
112
|
+
* spec-brief's archive refuses a round that changed a protected file, and
|
|
113
|
+
* learns that a signed ruling allows it only by asking the plugin (ADR-0006).
|
|
114
|
+
*/
|
|
115
|
+
export function mergeSpecBrief(current) {
|
|
116
|
+
if (loadsPlugin(current))
|
|
117
|
+
return null;
|
|
118
|
+
const plugins = Array.isArray(current['plugins']) ? current['plugins'] : [];
|
|
119
|
+
return { ...current, plugins: [...plugins, PLUGIN] };
|
|
120
|
+
}
|
|
121
|
+
/** What a person reads about the plugin, and what to do when it is not loaded. */
|
|
122
|
+
export function describePlugin(state) {
|
|
123
|
+
switch (state.kind) {
|
|
124
|
+
case 'loaded':
|
|
125
|
+
return `spec-brief loads spec-harness's plugin (${state.file}): its archive accepts a protected file a signed ruling allows`;
|
|
126
|
+
case 'not-loaded':
|
|
127
|
+
return `spec-brief does not load spec-harness's plugin, so its archive refuses a protected file whatever ruling is signed: add "${PLUGIN}" to "plugins" in ${state.file}, or run spec-harness init --write`;
|
|
128
|
+
case 'unreadable':
|
|
129
|
+
return `${state.file} cannot be read as JSON, so whether spec-brief loads spec-harness's plugin is unknown`;
|
|
130
|
+
case 'unconfigured':
|
|
131
|
+
return "spec-brief has no configuration at the root, so it loads no plugin: spec-harness init --write writes one that loads spec-harness's";
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* The base `init` writes into `.spec-harness.json`. Every ruling is verified
|
|
136
|
+
* against the allowed signers on it, so it is named rather than left to be
|
|
137
|
+
* found. The remote's default branch comes first, but git records it when
|
|
138
|
+
* it clones, and before 2.48 not on a fetch: a repository made with `git
|
|
139
|
+
* init` and pushed to a remote often has none. There the branch init runs on
|
|
140
|
+
* is the base when it is `main` or `master`, or the only branch; otherwise
|
|
141
|
+
* the person names it.
|
|
142
|
+
*/
|
|
143
|
+
export function chooseBase(facts) {
|
|
144
|
+
const { remoteDefault, branch } = facts;
|
|
145
|
+
if (remoteDefault !== null)
|
|
146
|
+
return { base: remoteDefault, detail: `rounds are measured from ${remoteDefault}, the remote's default branch` };
|
|
147
|
+
const unrecorded = 'no remote records a default branch (refs/remotes/origin/HEAD)';
|
|
148
|
+
if (branch !== null) {
|
|
149
|
+
const conventional = branch === 'main' || branch === 'master';
|
|
150
|
+
if (conventional || facts.branches.every((name) => name === branch)) {
|
|
151
|
+
const which = conventional ? 'the branch init runs on' : 'the only branch';
|
|
152
|
+
return { base: branch, detail: `rounds are measured from ${branch}, ${which}: ${unrecorded}; change "base" if rounds merge into another` };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const where = branch === null ? 'HEAD is detached' : `${branch} is not main, master or the only branch`;
|
|
156
|
+
return {
|
|
157
|
+
base: null,
|
|
158
|
+
detail: `no base can be told: ${unrecorded}, and ${where}; set "base" to the branch rounds merge into, or run git remote set-head origin --auto and init again`,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const SOURCES = { flag: '--base', config: '.spec-harness.json', remote: "the remote's default branch" };
|
|
162
|
+
/** The base a command resolved, and how; or why there is none. */
|
|
163
|
+
export function describeBase(base) {
|
|
164
|
+
if ('reason' in base)
|
|
165
|
+
return `none: ${base.reason}`;
|
|
166
|
+
return `${base.ref} (${SOURCES[base.source]}), merge base ${base.mergeBase.slice(0, 12)}`;
|
|
167
|
+
}
|
|
168
|
+
/** Whether the allowed-signers file is on the base, where every ruling's signature is checked against it. */
|
|
169
|
+
export function describeSigners(file, base, onBase) {
|
|
170
|
+
if (base === null)
|
|
171
|
+
return `${file}, read from the base, which could not be resolved: no ruling can count`;
|
|
172
|
+
if (onBase)
|
|
173
|
+
return `${file} is on ${base}`;
|
|
174
|
+
return `${file} is not on ${base}, so no ruling can count: commit it there, one line per person, "<email> namespaces="git" <public key>"`;
|
|
175
|
+
}
|
|
176
|
+
/** spec-graph's configuration files, in the order spec-graph reads them; then a "spec-graph" key in package.json. */
|
|
177
|
+
export const SPEC_GRAPH_CONFIGS = ['.spec-graph.json', 'spec-graph.config.json'];
|
|
178
|
+
/**
|
|
179
|
+
* spec-graph's own patterns when its configuration names none, its
|
|
180
|
+
* DEFAULT_PATTERNS as of 0.9.0. They decide only what init says, never what
|
|
181
|
+
* it writes: `patterns` in the configuration replaces them, so writing the
|
|
182
|
+
* briefs into it would freeze spec-graph's defaults into the file.
|
|
183
|
+
*/
|
|
184
|
+
const SPEC_GRAPH_DEFAULTS = ['docs/**/*.md', 'doc/**/*.md', 'adr/**/*.md', 'rfcs/**/*.md', 'specs/**/*.md', '*.md'];
|
|
185
|
+
/** Whether spec-graph, configured as `config` says, reads the briefs in the directory `briefs`. */
|
|
186
|
+
export function graphReadsBriefs(config, briefs) {
|
|
187
|
+
const configured = Array.isArray(config['patterns']) ? config['patterns'].filter((pattern) => typeof pattern === 'string') : [];
|
|
188
|
+
const list = parseGlobList(configured.length > 0 ? configured : SPEC_GRAPH_DEFAULTS, { dialect: 'path', caseSensitive: true, backslash: 'separator' });
|
|
189
|
+
return list.ok && list.list.match(`${briefs}/001_brief.md`);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* What the history entry does. spec-graph reads a brief whose status says
|
|
193
|
+
* `archived` as a record already; the entry makes one a record whatever word
|
|
194
|
+
* its status uses, or none - and it does anything only where spec-graph
|
|
195
|
+
* reads the briefs, which its defaults do not in `briefs/`.
|
|
196
|
+
*/
|
|
197
|
+
export function describeGraph(archiveGlob, briefs, reads, added) {
|
|
198
|
+
const history = added ? `read ${archiveGlob} as history` : `${archiveGlob} is history`;
|
|
199
|
+
if (reads)
|
|
200
|
+
return `${history}: an archived brief is a record whatever its status says, so a live brief that depends on one is not a stale premise`;
|
|
201
|
+
return `${history}, for when spec-graph reads the briefs; its patterns do not reach ${briefs}/, so it checks none of them now: add "${briefs}/**/*.md" to its "patterns" if it should`;
|
|
202
|
+
}
|
|
203
|
+
/** spec-graph's configuration with the archive read as history, or `null` when it already is. */
|
|
204
|
+
export function mergeSpecGraph(current, archiveGlob) {
|
|
205
|
+
const history = Array.isArray(current['historyPatterns']) ? current['historyPatterns'].filter((p) => typeof p === 'string') : [];
|
|
206
|
+
if (history.includes(archiveGlob))
|
|
207
|
+
return null;
|
|
208
|
+
return { ...current, historyPatterns: [...history, archiveGlob] };
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=configure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configure.js","sourceRoot":"","sources":["../src/configure.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What `init` writes into each tool's configuration, and what `doctor` says\n * of the result.\n *\n * Pure: `setup.ts` and the command line read the files and ask git, and\n * write what these return, so the core sweep holds these to the unit suite.\n * Each merge keeps what a person wrote and returns `null` when there is\n * nothing to add.\n */\n\nimport { parseGlobList } from './vendor/spec-core/pattern/index.js';\n\ntype Json = Record<string, unknown>;\n\nfunction isObject(value: unknown): value is Json {\n return typeof value === 'object' && value !== null;\n}\n\nfunction sameStrings(value: unknown, expected: readonly string[]): boolean {\n return Array.isArray(value) && value.length === expected.length && value.every((item, index) => item === expected[index]);\n}\n\n/**\n * The command line as the project installed it. Claude Code starts a plugin's\n * server in the plugin's own directory and a hook wherever the session\n * stands, so neither can rely on `npx` finding the project's install; and on\n * Windows a server's command is started without a shell, where `npx` is a\n * shim that cannot start at all. `node` and the script's path work\n * everywhere.\n */\nconst BIN = 'node_modules/@descent-vtt/spec-harness/bin/spec-harness.js';\n\n/** The project root, as Claude Code substitutes it in a hook's arguments and in a plugin server's. */\nexport const PROJECT_DIR = '${CLAUDE_PROJECT_DIR}';\n\n/**\n * The project root in a project's own `.mcp.json`. Claude Code expands it\n * there from its own environment, which does not hold it, so it needs a\n * default; `.` is right, since a project's server starts in the project.\n */\nexport const PROJECT_DIR_OR_HERE = '${CLAUDE_PROJECT_DIR:-.}';\n\nconst HOOK_MATCHER = 'Edit|Write|MultiEdit|NotebookEdit';\nconst GUARDED_EVENTS = ['PreToolUse', 'PostToolUse'];\nconst GUARD_ARGS: readonly string[] = Object.freeze([`${PROJECT_DIR}/${BIN}`, 'hook', 'claude']);\n\n/**\n * The guard as a Claude Code hook: exec form, `args` given, so the project's\n * path is one argument whatever it holds, and no shell is involved.\n */\nexport const GUARD_HOOK: Readonly<Json> = Object.freeze({ type: 'command', command: 'node', args: GUARD_ARGS, timeout: 60 });\n\n/** What 0.1 installed: an npx command, found from wherever the session stood. */\nconst LEGACY_HOOK = 'npx --no-install spec-harness hook claude';\nconst LEGACY_SERVER_ARGS = ['--no-install', 'spec-harness', 'mcp'];\n\nfunction isGuard(hook: unknown): boolean {\n return isObject(hook) && hook['command'] === 'node' && sameStrings(hook['args'], GUARD_ARGS);\n}\n\nfunction isLegacyGuard(hook: unknown): boolean {\n return isObject(hook) && hook['command'] === LEGACY_HOOK;\n}\n\nfunction hooksOf(group: unknown): unknown[] {\n return isObject(group) && Array.isArray(group['hooks']) ? group['hooks'] : [];\n}\n\n/** A group with 0.1's guard replaced where it stands, keeping its other fields; the group itself when it has none. */\nfunction upgraded(group: unknown): unknown {\n const hooks = hooksOf(group);\n if (!hooks.some(isLegacyGuard)) return group;\n return { ...(group as Json), hooks: hooks.map((hook) => (isLegacyGuard(hook) ? { ...(hook as Json), command: 'node', args: [...GUARD_ARGS] } : hook)) };\n}\n\n/** `.claude/settings.json`, or the plugin's hooks, with the guard hooks merged in; `null` when they are already there. */\nexport function mergeClaudeSettings(current: Json): Json | null {\n const hooks = isObject(current['hooks']) ? current['hooks'] : {};\n let changed = false;\n const next: Json = { ...hooks };\n for (const event of GUARDED_EVENTS) {\n const listed = hooks[event];\n const before: unknown[] = Array.isArray(listed) ? listed : [];\n const groups = before.map(upgraded);\n const replaced = groups.some((group, index) => group !== before[index]);\n const present = groups.some((group) => hooksOf(group).some(isGuard));\n if (!present) groups.push({ matcher: HOOK_MATCHER, hooks: [{ ...GUARD_HOOK, args: [...GUARD_ARGS] }] });\n if (replaced || !present) {\n next[event] = groups;\n changed = true;\n }\n }\n return changed ? { ...current, hooks: next } : null;\n}\n\n/** The server's entry, reading the project at `projectDir`: {@link PROJECT_DIR} for the plugin, {@link PROJECT_DIR_OR_HERE} for a project. */\nexport function mcpServer(projectDir: string): Json {\n return { command: 'node', args: [`${projectDir}/${BIN}`, 'mcp', '--root', projectDir] };\n}\n\nfunction isLegacyServer(server: unknown): boolean {\n return isObject(server) && server['command'] === 'npx' && sameStrings(server['args'], LEGACY_SERVER_ARGS);\n}\n\n/**\n * A project's `.mcp.json` with the harness's server merged in, or `null` when\n * one is registered. A server registered under the name some other way is\n * the person's, and stays; 0.1's npx entry is replaced, keeping its other\n * fields.\n */\nexport function mergeMcp(current: Json): Json | null {\n const servers = isObject(current['mcpServers']) ? current['mcpServers'] : {};\n const existing = servers['spec-harness'];\n if ('spec-harness' in servers && !isLegacyServer(existing)) return null;\n return { ...current, mcpServers: { ...servers, 'spec-harness': { ...(existing as Json | undefined), ...mcpServer(PROJECT_DIR_OR_HERE) } } };\n}\n\n/** The spec-brief plugin this package ships, as spec-brief's `plugins` names it. */\nexport const PLUGIN = '@descent-vtt/spec-harness/spec-brief-plugin';\n\n/** spec-brief's configuration files, in the order spec-brief looks for them in a directory. */\nexport const SPEC_BRIEF_CONFIGS: readonly string[] = ['.spec-brief.json', 'spec-brief.json'];\n\n/** Whether spec-brief's configuration loads the plugin, by name or as `{ module, options }`. */\nexport function loadsPlugin(config: Json): boolean {\n const plugins = config['plugins'];\n return Array.isArray(plugins) && plugins.some((entry) => entry === PLUGIN || (isObject(entry) && entry['module'] === PLUGIN));\n}\n\n/**\n * spec-brief's configuration with the plugin loaded, or `null` when it is.\n * spec-brief's archive refuses a round that changed a protected file, and\n * learns that a signed ruling allows it only by asking the plugin (ADR-0006).\n */\nexport function mergeSpecBrief(current: Json): Json | null {\n if (loadsPlugin(current)) return null;\n const plugins: unknown[] = Array.isArray(current['plugins']) ? current['plugins'] : [];\n return { ...current, plugins: [...plugins, PLUGIN] };\n}\n\n/** Whether spec-brief loads the plugin, as its configuration at the root says. */\nexport type PluginState =\n | { readonly kind: 'loaded' | 'not-loaded' | 'unreadable'; readonly file: string }\n | { readonly kind: 'unconfigured' };\n\n/** What a person reads about the plugin, and what to do when it is not loaded. */\nexport function describePlugin(state: PluginState): string {\n switch (state.kind) {\n case 'loaded':\n return `spec-brief loads spec-harness's plugin (${state.file}): its archive accepts a protected file a signed ruling allows`;\n case 'not-loaded':\n return `spec-brief does not load spec-harness's plugin, so its archive refuses a protected file whatever ruling is signed: add \"${PLUGIN}\" to \"plugins\" in ${state.file}, or run spec-harness init --write`;\n case 'unreadable':\n return `${state.file} cannot be read as JSON, so whether spec-brief loads spec-harness's plugin is unknown`;\n case 'unconfigured':\n return \"spec-brief has no configuration at the root, so it loads no plugin: spec-harness init --write writes one that loads spec-harness's\";\n }\n}\n\n/** What git says that tells a base apart. */\nexport interface BaseFacts {\n /** The remote's default branch as a local ref, such as `origin/main`, or `null`. */\n readonly remoteDefault: string | null;\n /** The branch checked out, or `null` on a detached head. */\n readonly branch: string | null;\n /** Every local branch. */\n readonly branches: readonly string[];\n}\n\n/** The base `init` names, and why; `base` is `null` when none can be told. */\nexport interface BaseChoice {\n readonly base: string | null;\n readonly detail: string;\n}\n\n/**\n * The base `init` writes into `.spec-harness.json`. Every ruling is verified\n * against the allowed signers on it, so it is named rather than left to be\n * found. The remote's default branch comes first, but git records it when\n * it clones, and before 2.48 not on a fetch: a repository made with `git\n * init` and pushed to a remote often has none. There the branch init runs on\n * is the base when it is `main` or `master`, or the only branch; otherwise\n * the person names it.\n */\nexport function chooseBase(facts: BaseFacts): BaseChoice {\n const { remoteDefault, branch } = facts;\n if (remoteDefault !== null) return { base: remoteDefault, detail: `rounds are measured from ${remoteDefault}, the remote's default branch` };\n const unrecorded = 'no remote records a default branch (refs/remotes/origin/HEAD)';\n if (branch !== null) {\n const conventional = branch === 'main' || branch === 'master';\n if (conventional || facts.branches.every((name) => name === branch)) {\n const which = conventional ? 'the branch init runs on' : 'the only branch';\n return { base: branch, detail: `rounds are measured from ${branch}, ${which}: ${unrecorded}; change \"base\" if rounds merge into another` };\n }\n }\n const where = branch === null ? 'HEAD is detached' : `${branch} is not main, master or the only branch`;\n return {\n base: null,\n detail: `no base can be told: ${unrecorded}, and ${where}; set \"base\" to the branch rounds merge into, or run git remote set-head origin --auto and init again`,\n };\n}\n\n/** Where a base came from: `--base`, the configuration, or the remote. */\nexport type BaseSource = 'flag' | 'config' | 'remote';\n\nconst SOURCES: Readonly<Record<BaseSource, string>> = { flag: '--base', config: '.spec-harness.json', remote: \"the remote's default branch\" };\n\n/** The base a command resolved, and how; or why there is none. */\nexport function describeBase(base: { readonly ref: string; readonly source: BaseSource; readonly mergeBase: string } | { readonly reason: string }): string {\n if ('reason' in base) return `none: ${base.reason}`;\n return `${base.ref} (${SOURCES[base.source]}), merge base ${base.mergeBase.slice(0, 12)}`;\n}\n\n/** Whether the allowed-signers file is on the base, where every ruling's signature is checked against it. */\nexport function describeSigners(file: string, base: string | null, onBase: boolean): string {\n if (base === null) return `${file}, read from the base, which could not be resolved: no ruling can count`;\n if (onBase) return `${file} is on ${base}`;\n return `${file} is not on ${base}, so no ruling can count: commit it there, one line per person, \"<email> namespaces=\"git\" <public key>\"`;\n}\n\n/** spec-graph's configuration files, in the order spec-graph reads them; then a \"spec-graph\" key in package.json. */\nexport const SPEC_GRAPH_CONFIGS: readonly string[] = ['.spec-graph.json', 'spec-graph.config.json'];\n\n/**\n * spec-graph's own patterns when its configuration names none, its\n * DEFAULT_PATTERNS as of 0.9.0. They decide only what init says, never what\n * it writes: `patterns` in the configuration replaces them, so writing the\n * briefs into it would freeze spec-graph's defaults into the file.\n */\nconst SPEC_GRAPH_DEFAULTS: readonly string[] = ['docs/**/*.md', 'doc/**/*.md', 'adr/**/*.md', 'rfcs/**/*.md', 'specs/**/*.md', '*.md'];\n\n/** Whether spec-graph, configured as `config` says, reads the briefs in the directory `briefs`. */\nexport function graphReadsBriefs(config: Json, briefs: string): boolean {\n const configured = Array.isArray(config['patterns']) ? config['patterns'].filter((pattern): pattern is string => typeof pattern === 'string') : [];\n const list = parseGlobList(configured.length > 0 ? configured : SPEC_GRAPH_DEFAULTS, { dialect: 'path', caseSensitive: true, backslash: 'separator' });\n return list.ok && list.list.match(`${briefs}/001_brief.md`);\n}\n\n/**\n * What the history entry does. spec-graph reads a brief whose status says\n * `archived` as a record already; the entry makes one a record whatever word\n * its status uses, or none - and it does anything only where spec-graph\n * reads the briefs, which its defaults do not in `briefs/`.\n */\nexport function describeGraph(archiveGlob: string, briefs: string, reads: boolean, added: boolean): string {\n const history = added ? `read ${archiveGlob} as history` : `${archiveGlob} is history`;\n if (reads) return `${history}: an archived brief is a record whatever its status says, so a live brief that depends on one is not a stale premise`;\n return `${history}, for when spec-graph reads the briefs; its patterns do not reach ${briefs}/, so it checks none of them now: add \"${briefs}/**/*.md\" to its \"patterns\" if it should`;\n}\n\n/** spec-graph's configuration with the archive read as history, or `null` when it already is. */\nexport function mergeSpecGraph(current: Json, archiveGlob: string): Json | null {\n const history = Array.isArray(current['historyPatterns']) ? (current['historyPatterns'] as unknown[]).filter((p): p is string => typeof p === 'string') : [];\n if (history.includes(archiveGlob)) return null;\n return { ...current, historyPatterns: [...history, archiveGlob] };\n}\n"]}
|
package/dist/context.d.ts
CHANGED
|
@@ -24,6 +24,28 @@ export interface RuleInForce {
|
|
|
24
24
|
readonly description: string;
|
|
25
25
|
readonly reason: string | null;
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* The rules in force for the scope; `none` when spec-guard reads no
|
|
29
|
+
* specification here, so none can be in force; `unavailable` when the rules
|
|
30
|
+
* could not be read.
|
|
31
|
+
*/
|
|
32
|
+
export type Rules = readonly RuleInForce[] | {
|
|
33
|
+
readonly none: string;
|
|
34
|
+
} | {
|
|
35
|
+
readonly unavailable: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* spec-guard's answer to `query --json`, read. It exits 2 both when no spec
|
|
39
|
+
* file matched its patterns - a repository that keeps none, or keeps them
|
|
40
|
+
* where it does not look - and when it could not read the specs it has; only
|
|
41
|
+
* the first prints a document, one with no spec file in it, so that is how
|
|
42
|
+
* the two are told apart. What it said on stderr is the reason for the second.
|
|
43
|
+
*/
|
|
44
|
+
export declare function readRules(answer: {
|
|
45
|
+
readonly code: number;
|
|
46
|
+
readonly document: unknown;
|
|
47
|
+
readonly stderr: string;
|
|
48
|
+
}): Rules;
|
|
27
49
|
export interface RulingInForce {
|
|
28
50
|
readonly id: string;
|
|
29
51
|
readonly paths: readonly string[];
|
|
@@ -34,10 +56,8 @@ export interface ContextInput {
|
|
|
34
56
|
readonly briefText: string;
|
|
35
57
|
readonly dependencies: readonly BriefRow[];
|
|
36
58
|
readonly cited: readonly CitedDocument[];
|
|
37
|
-
/** The rules in force for the scope, or why they could not be read. */
|
|
38
|
-
readonly rules:
|
|
39
|
-
readonly unavailable: string;
|
|
40
|
-
};
|
|
59
|
+
/** The rules in force for the scope, none because spec-guard reads no specification, or why they could not be read. */
|
|
60
|
+
readonly rules: Rules;
|
|
41
61
|
readonly rulings: readonly RulingInForce[];
|
|
42
62
|
readonly branch: string | null;
|
|
43
63
|
readonly base: string | null;
|
|
@@ -52,5 +72,12 @@ export interface ContextPacket {
|
|
|
52
72
|
/** Links in the brief that resolve to nothing. */
|
|
53
73
|
readonly unresolved: readonly string[];
|
|
54
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* A brief's title without the id it repeats: `012 - Rotate tokens` read as
|
|
77
|
+
* `Rotate tokens`, and the same with an en or an em dash, which `spec-brief
|
|
78
|
+
* new` writes, or with a colon after the id. Only the brief's own id is taken
|
|
79
|
+
* off: `Fix - the login bug` is a title, not an id and a title.
|
|
80
|
+
*/
|
|
81
|
+
export declare function titleOf(brief: Pick<BriefRow, 'id' | 'title'>): string | null;
|
|
55
82
|
/** Renders the packet, filling the budget with cited documents in the order the brief cites them. */
|
|
56
83
|
export declare function renderContext(input: ContextInput): ContextPacket;
|
package/dist/context.js
CHANGED
|
@@ -10,11 +10,42 @@
|
|
|
10
10
|
* the rest are named rather than dropped silently.
|
|
11
11
|
*/
|
|
12
12
|
import { sameId } from './branch.js';
|
|
13
|
+
/**
|
|
14
|
+
* spec-guard's answer to `query --json`, read. It exits 2 both when no spec
|
|
15
|
+
* file matched its patterns - a repository that keeps none, or keeps them
|
|
16
|
+
* where it does not look - and when it could not read the specs it has; only
|
|
17
|
+
* the first prints a document, one with no spec file in it, so that is how
|
|
18
|
+
* the two are told apart. What it said on stderr is the reason for the second.
|
|
19
|
+
*/
|
|
20
|
+
export function readRules(answer) {
|
|
21
|
+
const document = (answer.document ?? {});
|
|
22
|
+
if (answer.code !== 0) {
|
|
23
|
+
if (Array.isArray(document.specFiles) && document.specFiles.length === 0) {
|
|
24
|
+
return { none: 'no spec file matched its patterns ("specs" in its configuration, docs/**/*.md by default)' };
|
|
25
|
+
}
|
|
26
|
+
const said = (answer.stderr.trim().split('\n')[0] ?? '').replace(/^spec-guard:\s*/, '');
|
|
27
|
+
return { unavailable: said === '' ? `spec-guard exited ${answer.code}` : `spec-guard exited ${answer.code}: ${said}` };
|
|
28
|
+
}
|
|
29
|
+
const seen = new Set();
|
|
30
|
+
const out = [];
|
|
31
|
+
for (const result of document.results ?? []) {
|
|
32
|
+
for (const rule of result.rules ?? []) {
|
|
33
|
+
const key = `${rule.document}:${rule.line}`;
|
|
34
|
+
if (rule.inForce === false || seen.has(key))
|
|
35
|
+
continue;
|
|
36
|
+
seen.add(key);
|
|
37
|
+
out.push({ document: rule.document, line: rule.line, kind: rule.kind, description: rule.description, reason: rule.reason ?? null });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
13
42
|
function list(items, empty) {
|
|
14
43
|
return items.length === 0 ? `- ${empty}` : items.map((item) => `- \`${item}\``).join('\n');
|
|
15
44
|
}
|
|
16
45
|
function rulesSection(rules) {
|
|
17
|
-
if (
|
|
46
|
+
if ('none' in rules)
|
|
47
|
+
return `spec-guard holds no rule over this scope: ${rules.none}.`;
|
|
48
|
+
if ('unavailable' in rules) {
|
|
18
49
|
return `The rules spec-guard holds this code to could not be read: ${rules.unavailable}. Treat every ADR as binding until they can.`;
|
|
19
50
|
}
|
|
20
51
|
if (rules.length === 0)
|
|
@@ -32,14 +63,15 @@ function rulesSection(rules) {
|
|
|
32
63
|
return parts.join('\n').trimEnd();
|
|
33
64
|
}
|
|
34
65
|
/**
|
|
35
|
-
* A brief's title without the id it repeats
|
|
36
|
-
* `Rotate tokens
|
|
37
|
-
*
|
|
66
|
+
* A brief's title without the id it repeats: `012 - Rotate tokens` read as
|
|
67
|
+
* `Rotate tokens`, and the same with an en or an em dash, which `spec-brief
|
|
68
|
+
* new` writes, or with a colon after the id. Only the brief's own id is taken
|
|
69
|
+
* off: `Fix - the login bug` is a title, not an id and a title.
|
|
38
70
|
*/
|
|
39
|
-
function titleOf(brief) {
|
|
71
|
+
export function titleOf(brief) {
|
|
40
72
|
if (brief.title === null)
|
|
41
73
|
return null;
|
|
42
|
-
return brief.title.replace(/^\s*(
|
|
74
|
+
return brief.title.replace(/^\s*([^\s:]+)(?:\s+[-\u2013\u2014]|:)\s+/, (whole, first) => (sameId(first, brief.id) ? '' : whole));
|
|
43
75
|
}
|
|
44
76
|
/** Renders the packet, filling the budget with cited documents in the order the brief cites them. */
|
|
45
77
|
export function renderContext(input) {
|
|
@@ -77,7 +109,8 @@ export function renderContext(input) {
|
|
|
77
109
|
].join('\n');
|
|
78
110
|
const waiting = input.dependencies.map((dependency) => {
|
|
79
111
|
const state = dependency.phase === 'archived' ? 'archived, done' : `still ${dependency.status ?? 'live'}`;
|
|
80
|
-
|
|
112
|
+
const named = titleOf(dependency);
|
|
113
|
+
return `- ${dependency.id}${named === null ? '' : ` ${named}`}: ${state}`;
|
|
81
114
|
});
|
|
82
115
|
const dependencies = ['## Depends on', '', waiting.length === 0 ? '- nothing' : waiting.join('\n'), ''].join('\n');
|
|
83
116
|
const rules = ['## Rules in force for this scope', '', rulesSection(input.rules), ''].join('\n');
|