@descent-vtt/spec-harness 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/commands.js CHANGED
@@ -21,13 +21,13 @@ const reader = createReader();
21
21
  /** 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. */
22
22
  export async function targetBrief(workspace, options, io, positional) {
23
23
  const briefs = await workspace.siblings.briefs();
24
- const fromBranch = workspace.branch === null ? null : briefIdFromBranch(workspace.config.branches, workspace.branch);
24
+ const fromBranch = briefIdFromBranch(workspace.config.branches, workspace.branch);
25
25
  const active = findActive(briefs, { flag: positional ?? options.brief, environment: io.env['SPEC_BRIEF'], branch: fromBranch });
26
26
  const { brief, note, problem } = describeActive(active);
27
27
  if (problem !== null)
28
28
  throw new UsageError(problem);
29
29
  if (brief === null)
30
- throw new UsageError(note ?? 'no brief is named');
30
+ throw new UsageError(note);
31
31
  return { brief, briefs };
32
32
  }
33
33
  const MARK = { error: 'error ', warning: 'warning', note: 'note ' };
@@ -161,6 +161,8 @@ export async function rulingsCommand(options, io) {
161
161
  for (const row of check.rows) {
162
162
  const verified = check.verified.find((ruling) => ruling.id === row.id);
163
163
  const unverified = check.unverified.find((ruling) => ruling.id === row.id);
164
+ // An allow row that does not verify is among the unverified, with its
165
+ // reason, so the fallback for none is equivalent to its mutants.
164
166
  const state = row.decision === 'deny' ? 'refused' : verified !== undefined ? `signed by ${verified.signer}` : `not verified: ${unverified?.reason ?? ''}`;
165
167
  io.stdout.write(`${row.id} ${row.decision} ${row.paths.join(', ')} ${state}\n`);
166
168
  }
@@ -176,6 +178,8 @@ async function runProbe(directory, probe, runs, timeout) {
176
178
  if (report !== null)
177
179
  await rm(report, { force: true });
178
180
  const run = await runCommand(probe.run, directory, timeout);
181
+ // existsSync answers false for null, so the null check is there for the
182
+ // type, and its mutant is equivalent.
179
183
  const junit = report === null || !existsSync(report) ? null : readJUnit(await readFile(report, 'utf8'));
180
184
  classified.push(classify(probe, { exitCode: run.exitCode, output: run.output, junit }));
181
185
  }
@@ -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 { describeMeasured } from './audit.js';\nimport { briefIdFromBranch } from './branch.js';\nimport { findActive } from './briefs.js';\nimport { formatFindings } from './formats.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 version,\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(\n json('context', {\n brief: brief.id,\n markdown: packet.markdown,\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 } 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, measured } = 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 measured,\n findings,\n dependencies: result.dependencies,\n }),\n );\n } else if (options.format === 'pretty') {\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 // The counts stay the last line, where a script reads them.\n io.stdout.write(`\\n${describeMeasured(measured)}\\n${counts.error} error(s), ${counts.warning} warning(s), ${counts.note} note(s)\\n`);\n } else {\n io.stdout.write(formatFindings(options.format, findings, { file: brief.file, version: version(), summary: describeMeasured(measured) }));\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 { describeMeasured } from './audit.js';\nimport { briefIdFromBranch } from './branch.js';\nimport { findActive } from './briefs.js';\nimport { formatFindings } from './formats.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 version,\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 = 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);\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(\n json('context', {\n brief: brief.id,\n markdown: packet.markdown,\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 } 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, measured } = 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 measured,\n findings,\n dependencies: result.dependencies,\n }),\n );\n } else if (options.format === 'pretty') {\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 // The counts stay the last line, where a script reads them.\n io.stdout.write(`\\n${describeMeasured(measured)}\\n${counts.error} error(s), ${counts.warning} warning(s), ${counts.note} note(s)\\n`);\n } else {\n io.stdout.write(formatFindings(options.format, findings, { file: brief.file, version: version(), summary: describeMeasured(measured) }));\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 // An allow row that does not verify is among the unverified, with its\n // reason, so the fallback for none is equivalent to its mutants.\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 // existsSync answers false for null, so the null check is there for the\n // type, and its mutant is equivalent.\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"]}
package/dist/context.d.ts CHANGED
@@ -52,6 +52,7 @@ export type Rules = readonly RuleInForce[] | {
52
52
  * where it does not look - and when it could not read the specs it has; only
53
53
  * the first prints a document, one with no spec file in it, so that is how
54
54
  * the two are told apart. What it said on stderr is the reason for the second.
55
+ * A document of another shape is refused (answers.ts).
55
56
  */
56
57
  export declare function readRules(answer: {
57
58
  readonly code: number;
package/dist/context.js CHANGED
@@ -16,6 +16,7 @@
16
16
  * as one it can; and one a leading `/` roots, which names no path the guard
17
17
  * decides.
18
18
  */
19
+ import { readQueryRules } from './answers.js';
19
20
  import { sameId } from './branch.js';
20
21
  import { rooted, whyUnreadable } from './guard.js';
21
22
  /**
@@ -24,6 +25,7 @@ import { rooted, whyUnreadable } from './guard.js';
24
25
  * where it does not look - and when it could not read the specs it has; only
25
26
  * the first prints a document, one with no spec file in it, so that is how
26
27
  * the two are told apart. What it said on stderr is the reason for the second.
28
+ * A document of another shape is refused (answers.ts).
27
29
  */
28
30
  export function readRules(answer) {
29
31
  const document = (answer.document ?? {});
@@ -36,13 +38,13 @@ export function readRules(answer) {
36
38
  }
37
39
  const seen = new Set();
38
40
  const out = [];
39
- for (const result of document.results ?? []) {
40
- for (const rule of result.rules ?? []) {
41
+ for (const rules of readQueryRules(answer.document)) {
42
+ for (const rule of rules) {
41
43
  const key = `${rule.document}:${rule.line}`;
42
- if (rule.inForce === false || seen.has(key))
44
+ if (!rule.inForce || seen.has(key))
43
45
  continue;
44
46
  seen.add(key);
45
- out.push({ document: rule.document, line: rule.line, kind: rule.kind, description: rule.description, reason: rule.reason ?? null });
47
+ out.push({ document: rule.document, line: rule.line, kind: rule.kind, description: rule.description, reason: rule.reason });
46
48
  }
47
49
  }
48
50
  return out;
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What an agent needs to start a round, and nothing it does not.\n *\n * The brief in full - it is the contract, and a summary of a contract is a\n * different contract. Then what the brief implies but does not say: the scope\n * as the guard will read it, the rulings already signed, the briefs it waits\n * on, the rules spec-guard holds the scope's code to, and the documents the\n * brief cites. The cited documents are the one part that can be large, so\n * they fill what the budget leaves, in the order the brief cites them, and\n * the rest are named rather than dropped silently. So is a document whose\n * front matter is never closed, whose status cannot be read, rather than\n * shown as one without a status, and a line of front matter that is not\n * `key: value`, passed over with any status written on it; and a pattern in\n * the scope, a protection or a ruling's path the guard cannot read, with\n * spec-core's reason and what the guard does without it, rather than listed\n * as one it can; and one a leading `/` roots, which names no path the guard\n * decides.\n */\n\nimport { sameId } from './branch.js';\nimport type { UnreadableFrontMatterLine } from './document.js';\nimport { rooted, whyUnreadable } from './guard.js';\nimport type { BriefRow } from './types.js';\n\nexport interface CitedDocument {\n readonly path: string;\n readonly title: string | null;\n readonly status: string | null;\n /** `null` when the link does not resolve to a readable file. */\n readonly text: string | null;\n /** YAML front matter opened on line 1 and never closed, so no status was read from it. */\n readonly unclosedFrontMatter?: boolean | undefined;\n /** Lines of its front matter that are not `key: value`, passed over with any status written on them. */\n readonly unreadableFrontMatter?: readonly UnreadableFrontMatterLine[] | undefined;\n}\n\nexport interface RuleInForce {\n readonly document: string;\n readonly line: number;\n readonly kind: string;\n readonly description: string;\n readonly reason: string | null;\n}\n\n/**\n * The rules in force for the scope; `none` when spec-guard reads no\n * specification here, so none can be in force; `unavailable` when the rules\n * could not be read.\n */\nexport type Rules = readonly RuleInForce[] | { readonly none: string } | { readonly unavailable: string };\n\ninterface QueryDocument {\n readonly specFiles?: unknown;\n readonly results?: readonly { readonly rules?: readonly (RuleInForce & { readonly reason?: string | null; readonly inForce?: boolean })[] }[];\n}\n\n/**\n * spec-guard's answer to `query --json`, read. It exits 2 both when no spec\n * file matched its patterns - a repository that keeps none, or keeps them\n * where it does not look - and when it could not read the specs it has; only\n * the first prints a document, one with no spec file in it, so that is how\n * the two are told apart. What it said on stderr is the reason for the second.\n */\nexport function readRules(answer: { readonly code: number; readonly document: unknown; readonly stderr: string }): Rules {\n const document = (answer.document ?? {}) as QueryDocument;\n if (answer.code !== 0) {\n if (Array.isArray(document.specFiles) && document.specFiles.length === 0) {\n return { none: 'no spec file matched its patterns (\"specs\" in its configuration, docs/**/*.md by default)' };\n }\n const said = (answer.stderr.trim().split('\\n')[0] ?? '').replace(/^spec-guard:\\s*/, '');\n return { unavailable: said === '' ? `spec-guard exited ${answer.code}` : `spec-guard exited ${answer.code}: ${said}` };\n }\n const seen = new Set<string>();\n const out: RuleInForce[] = [];\n for (const result of document.results ?? []) {\n for (const rule of result.rules ?? []) {\n const key = `${rule.document}:${rule.line}`;\n if (rule.inForce === false || seen.has(key)) continue;\n seen.add(key);\n out.push({ document: rule.document, line: rule.line, kind: rule.kind, description: rule.description, reason: rule.reason ?? null });\n }\n }\n return out;\n}\n\nexport interface RulingInForce {\n readonly id: string;\n readonly paths: readonly string[];\n readonly signer: string;\n}\n\nexport interface ContextInput {\n readonly brief: BriefRow;\n readonly briefText: string;\n readonly dependencies: readonly BriefRow[];\n readonly cited: readonly CitedDocument[];\n /** The rules in force for the scope, none because spec-guard reads no specification, or why they could not be read. */\n readonly rules: Rules;\n readonly rulings: readonly RulingInForce[];\n readonly branch: string | null;\n readonly base: string | null;\n readonly budget: number;\n}\n\n/** A pattern in the brief's scope or protections that the guard cannot read, with spec-core's reason. */\nexport interface UnreadablePattern {\n readonly pattern: string;\n readonly reason: string;\n}\n\n/** A line of a cited document's front matter that is not `key: value`, with spec-core's reason. */\nexport interface UnreadableFrontMatter extends UnreadableFrontMatterLine {\n readonly path: string;\n}\n\n/** A path of a ruling in force that the guard cannot read, with the ruling's id and spec-core's reason. */\nexport interface UnreadableRulingPath extends UnreadablePattern {\n readonly ruling: string;\n}\n\nexport interface ContextPacket {\n readonly markdown: string;\n /** Cited documents included whole. */\n readonly included: readonly string[];\n /** Cited documents named but left out for the budget. */\n readonly omitted: readonly string[];\n /** Links in the brief that resolve to nothing. */\n readonly unresolved: readonly string[];\n /** Cited documents whose front matter opens on line 1 and is never closed, so no status was read from them. */\n readonly unclosedFrontMatter: readonly string[];\n /** Lines of cited documents' front matter that are not `key: value`, in the order the brief cites them: a status written on one was not read. */\n readonly unreadableFrontMatter: readonly UnreadableFrontMatter[];\n /** Patterns in `affectedFiles` the guard cannot read, in the brief's order: each puts no path in the scope. */\n readonly unreadableScope: readonly UnreadablePattern[];\n /** Patterns in `protectedFiles` the guard cannot read, in the brief's order: while one stands, the guard refuses every write but to the brief. */\n readonly unreadableProtections: readonly UnreadablePattern[];\n /** Paths of the rulings in force the guard cannot read, in the order the rulings are listed: each allows nothing. */\n readonly unreadableRulingPaths: readonly UnreadableRulingPath[];\n}\n\nfunction list(items: readonly string[]): string {\n return items.map((item) => `- \\`${item}\\``).join('\\n');\n}\n\n/** An unread line of front matter: where it is, as written, and spec-core's reason. */\nfunction unreadLine(found: UnreadableFrontMatter): string {\n return `- \\`${found.path}\\`, line ${found.line}, \\`${found.text}\\`: ${found.reason}`;\n}\n\n/**\n * Each pattern a line, as the guard reads it: as written, or with spec-core's\n * reason the guard cannot read it and what the guard does without it. The\n * guard's own compile decides, so the packet and the guard cannot disagree.\n * A pattern a leading `/` roots, or one of whose alternatives it roots, is\n * marked with `rootedNote`, and counted when every alternative is rooted.\n */\nfunction patternLines(\n patterns: readonly string[],\n consequence: string,\n rootedNote: { readonly whole: string; readonly part: string },\n): { lines: string[]; unreadable: UnreadablePattern[]; rootedWhole: number } {\n const unreadable: UnreadablePattern[] = [];\n let rootedWhole = 0;\n const lines = patterns.map((pattern) => {\n const reason = whyUnreadable(pattern);\n if (reason === null) {\n const root = rooted(pattern);\n if (root === null) return `- \\`${pattern}\\``;\n if (root === 'whole') rootedWhole += 1;\n return `- \\`${pattern}\\`, ${rootedNote[root]}`;\n }\n unreadable.push({ pattern, reason });\n return `- \\`${pattern}\\`, which the guard cannot read: ${reason}; ${consequence}`;\n });\n return { lines, unreadable, rootedWhole };\n}\n\nfunction rulesSection(rules: Rules, scopeUnread: boolean, scopeRooted: boolean): string {\n if ('none' in rules) return `spec-guard holds no rule over this scope: ${rules.none}.`;\n if ('unavailable' in rules) {\n return `The rules spec-guard holds this code to could not be read: ${rules.unavailable}. Treat every ADR as binding until they can.`;\n }\n if (rules.length === 0) {\n // With no pattern of the scope readable, spec-guard was asked about no\n // path, so an empty answer is no answer: \"no rule\" would tell the agent\n // the code it writes is unconstrained.\n if (scopeUnread) {\n return 'The scope could not be read: no pattern in `affectedFiles` can be read, so spec-guard was not asked for the rules over it. Treat every ADR as binding until the scope is fixed.';\n }\n return scopeRooted\n ? \"No pattern in `affectedFiles` puts a path in the scope: each is rooted at the filesystem's root or cannot be read, so spec-guard was not asked for the rules over it. Treat every ADR as binding until the scope is fixed.\"\n : 'spec-guard holds no rule over this scope.';\n }\n const byDocument = new Map<string, RuleInForce[]>();\n for (const rule of rules) byDocument.set(rule.document, [...(byDocument.get(rule.document) ?? []), rule]);\n const parts: string[] = [];\n for (const [document, found] of byDocument) {\n parts.push(`### ${document}`, '');\n for (const rule of found) parts.push(`- line ${rule.line}: ${rule.description}${rule.reason === null ? '' : ` - ${rule.reason}`}`);\n parts.push('');\n }\n return parts.join('\\n').trimEnd();\n}\n\n/**\n * A brief's title without the id it repeats, by the rules spec-brief reads\n * it with: the brief's own id, then an em dash, which `spec-brief new`\n * writes, an en dash, a full-width colon or a colon, spaced or not, or a\n * hyphen with a space on each side; `012 - Rotate tokens`, `012\\u2014Rotate\n * tokens` and `012\\uFF1ARotate tokens` read as `Rotate tokens`. A bare hyphen\n * is part of a word or a number, as in `001-2 migration`, and only the\n * brief's own id is taken off: `Fix - the login bug` is a title, and so is\n * `0010 \\u2014 x` on brief 001.\n */\nexport function titleOf(brief: Pick<BriefRow, 'id' | 'title'>): string | null {\n if (brief.title === null) return null;\n return brief.title.replace(/^\\s*([^\\s:\\uFF1A\\u2013\\u2014]+)(?:\\s*[:\\uFF1A\\u2013\\u2014]\\s*|\\s+-\\s+)(?=\\S)/, (whole, first: string) =>\n sameId(first, brief.id) ? '' : whole,\n );\n}\n\n/** Renders the packet, filling the budget with cited documents in the order the brief cites them. */\nexport function renderContext(input: ContextInput): ContextPacket {\n const { brief } = input;\n const title = titleOf(brief);\n const header = [\n `# Round ${brief.id}${title === null ? '' : `: ${title}`}`,\n '',\n [\n `Status ${brief.status ?? 'unknown'}`,\n brief.wave === null ? null : `wave ${brief.wave}`,\n input.branch === null ? null : `branch \\`${input.branch}\\``,\n input.base === null ? null : `measured from \\`${input.base}\\``,\n ]\n .filter((part) => part !== null)\n .join(' · '),\n '',\n ].join('\\n');\n\n const contract = ['## The contract', '', `\\`${brief.file}\\`, in full:`, '', '````markdown', input.briefText.trimEnd(), '````', ''].join('\\n');\n\n // The guard passes over a ruling's path it cannot read, so the path allows\n // nothing. The paths share a line, so the note is in parentheses, closed\n // before the next path.\n const unreadableRulingPaths: UnreadableRulingPath[] = [];\n const rulings =\n input.rulings.length === 0\n ? '- none'\n : input.rulings\n .map((ruling) => {\n const paths = ruling.paths.map((pattern) => {\n const reason = whyUnreadable(pattern);\n if (reason === null) {\n // A rooted path can be read, and names no path the guard decides.\n const root = rooted(pattern);\n if (root === 'whole') return `\\`${pattern}\\` (which a leading \\`/\\` roots at the filesystem's root; it allows nothing)`;\n if (root === 'part') return `\\`${pattern}\\` (an alternative of which a leading \\`/\\` roots at the filesystem's root; that alternative allows nothing)`;\n return `\\`${pattern}\\``;\n }\n unreadableRulingPaths.push({ ruling: ruling.id, pattern, reason });\n return `\\`${pattern}\\` (which the guard cannot read: ${reason}; it allows nothing)`;\n });\n return `- ${ruling.id}, signed by ${ruling.signer}: ${paths.join(', ')}`;\n })\n .join('\\n');\n // A rooted pattern can be read and puts no path in the scope: every path\n // the guard decides is repository-relative. spec-guard is not asked about it.\n const mayWrite = patternLines(brief.affectedFiles, 'it puts no path in the scope', {\n whole: \"which a leading `/` roots at the filesystem's root: it puts no path in the scope, and spec-guard is not asked about it\",\n part: \"an alternative of which a leading `/` roots at the filesystem's root: that alternative puts no path in the scope, and spec-guard is not asked about it\",\n });\n const unreadableScope = mayWrite.unreadable;\n // While a protection the guard cannot read stands, the guard refuses every\n // write, and no ruling waives it; a write to the brief is let through,\n // since that is where it is fixed. A rooted protection can be read, and\n // protects nothing: the file it was meant to protect is the round's to write.\n const mustNot = patternLines(brief.protectedFiles, 'until it is fixed, the guard refuses every write but to the brief', {\n whole: \"which a leading `/` roots at the filesystem's root: it protects no path\",\n part: \"an alternative of which a leading `/` roots at the filesystem's root: that alternative protects no path\",\n });\n const unreadableProtections = mustNot.unreadable;\n const scope = [\n '## Scope, as the guard reads it',\n '',\n 'May write:',\n mayWrite.lines.length === 0 ? '- nothing declared: every write is outside the scope' : mayWrite.lines.join('\\n'),\n '',\n 'Must not change without a ruling:',\n mustNot.lines.length === 0 ? '- nothing declared' : mustNot.lines.join('\\n'),\n '',\n 'Rulings in force:',\n rulings,\n '',\n ].join('\\n');\n\n const waiting = input.dependencies.map((dependency) => {\n const state = dependency.phase === 'archived' ? 'archived, done' : `still ${dependency.status ?? 'live'}`;\n const named = titleOf(dependency);\n return `- ${dependency.id}${named === null ? '' : ` ${named}`}: ${state}`;\n });\n const dependencies = ['## Depends on', '', waiting.length === 0 ? '- nothing' : waiting.join('\\n'), ''].join('\\n');\n\n const scopeUnread = mayWrite.lines.length > 0 && unreadableScope.length === mayWrite.lines.length;\n const scopeRooted = mayWrite.rootedWhole > 0 && unreadableScope.length + mayWrite.rootedWhole === mayWrite.lines.length;\n const rules = ['## Rules in force for this scope', '', rulesSection(input.rules, scopeUnread, scopeRooted), ''].join('\\n');\n\n const howTo = [\n '## How this round works',\n '',\n '- Before writing a file, ask whether the round may: `spec-harness guard <path>`, or the `check_path` tool.',\n '- A protected file is changed only under a ruling a person signs. If the round cannot be done without one, stop and ask: `spec-harness escalate --path <file> --reason <why>`, or the `request_escalation` tool. Do not work around it.',\n '- Every box in the brief ends ticked, or with a note under it that starts with a disposition (`**Delegated`, `**Accepted debt`, `**Rejected`, or what the repository configures).',\n '- When the work is done, run `spec-harness audit` and fix what it reports. The archive is a person\\'s decision, not the round\\'s.',\n '',\n ].join('\\n');\n\n const fixed = [header, contract, scope, dependencies, rules, howTo].join('\\n');\n const included: string[] = [];\n const omitted: string[] = [];\n const unresolved: string[] = [];\n const unclosed: string[] = [];\n const unreadableFrontMatter: UnreadableFrontMatter[] = [];\n const documents: string[] = [];\n let used = fixed.length;\n for (const cited of input.cited) {\n if (cited.text === null) {\n unresolved.push(cited.path);\n continue;\n }\n // Named whether or not the budget leaves room for it: the status is\n // unread either way, and the fix is the same.\n if (cited.unclosedFrontMatter === true) unclosed.push(cited.path);\n for (const line of cited.unreadableFrontMatter ?? []) unreadableFrontMatter.push({ path: cited.path, ...line });\n const heading = `### \\`${cited.path}\\`${cited.title === null ? '' : ` - ${cited.title}`}${cited.status === null ? '' : ` (${cited.status})`}`;\n const block = [heading, '', '````markdown', cited.text.trimEnd(), '````', ''].join('\\n');\n if (used + block.length > input.budget) {\n omitted.push(cited.path);\n continue;\n }\n used += block.length;\n included.push(cited.path);\n documents.push(block);\n }\n const citedSection = ['## Documents the brief cites', ''];\n if (documents.length === 0 && omitted.length === 0 && unresolved.length === 0) citedSection.push('None.', '');\n citedSection.push(...documents);\n if (omitted.length > 0) {\n citedSection.push(`Left out to stay within ${input.budget} characters; read them when the work reaches them:`, list(omitted), '');\n }\n if (unresolved.length > 0) citedSection.push('Cited but not found in the repository:', list(unresolved), '');\n if (unclosed.length > 0) {\n citedSection.push('Front matter opened on line 1 and never closed, so the status was not read; close the block with `---` on a line of its own:', list(unclosed), '');\n }\n // The last note: the section is trimmed at its end, so no blank line follows it.\n if (unreadableFrontMatter.length > 0) {\n citedSection.push(\n 'Front matter lines that are not `key: value` were not read, so a status written on one, if any, was not read either:',\n unreadableFrontMatter.map(unreadLine).join('\\n'),\n );\n }\n\n return {\n markdown: `${fixed}\\n${citedSection.join('\\n').trimEnd()}\\n`,\n included,\n omitted,\n unresolved,\n unclosedFrontMatter: unclosed,\n unreadableFrontMatter,\n unreadableScope,\n unreadableProtections,\n unreadableRulingPaths,\n };\n}\n"]}
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What an agent needs to start a round, and nothing it does not.\n *\n * The brief in full - it is the contract, and a summary of a contract is a\n * different contract. Then what the brief implies but does not say: the scope\n * as the guard will read it, the rulings already signed, the briefs it waits\n * on, the rules spec-guard holds the scope's code to, and the documents the\n * brief cites. The cited documents are the one part that can be large, so\n * they fill what the budget leaves, in the order the brief cites them, and\n * the rest are named rather than dropped silently. So is a document whose\n * front matter is never closed, whose status cannot be read, rather than\n * shown as one without a status, and a line of front matter that is not\n * `key: value`, passed over with any status written on it; and a pattern in\n * the scope, a protection or a ruling's path the guard cannot read, with\n * spec-core's reason and what the guard does without it, rather than listed\n * as one it can; and one a leading `/` roots, which names no path the guard\n * decides.\n */\n\nimport { readQueryRules } from './answers.js';\nimport { sameId } from './branch.js';\nimport type { UnreadableFrontMatterLine } from './document.js';\nimport { rooted, whyUnreadable } from './guard.js';\nimport type { BriefRow } from './types.js';\n\nexport interface CitedDocument {\n readonly path: string;\n readonly title: string | null;\n readonly status: string | null;\n /** `null` when the link does not resolve to a readable file. */\n readonly text: string | null;\n /** YAML front matter opened on line 1 and never closed, so no status was read from it. */\n readonly unclosedFrontMatter?: boolean | undefined;\n /** Lines of its front matter that are not `key: value`, passed over with any status written on them. */\n readonly unreadableFrontMatter?: readonly UnreadableFrontMatterLine[] | undefined;\n}\n\nexport interface RuleInForce {\n readonly document: string;\n readonly line: number;\n readonly kind: string;\n readonly description: string;\n readonly reason: string | null;\n}\n\n/**\n * The rules in force for the scope; `none` when spec-guard reads no\n * specification here, so none can be in force; `unavailable` when the rules\n * could not be read.\n */\nexport type Rules = readonly RuleInForce[] | { readonly none: string } | { readonly unavailable: string };\n\ninterface QueryDocument {\n readonly specFiles?: unknown;\n}\n\n/**\n * spec-guard's answer to `query --json`, read. It exits 2 both when no spec\n * file matched its patterns - a repository that keeps none, or keeps them\n * where it does not look - and when it could not read the specs it has; only\n * the first prints a document, one with no spec file in it, so that is how\n * the two are told apart. What it said on stderr is the reason for the second.\n * A document of another shape is refused (answers.ts).\n */\nexport function readRules(answer: { readonly code: number; readonly document: unknown; readonly stderr: string }): Rules {\n const document = (answer.document ?? {}) as QueryDocument;\n if (answer.code !== 0) {\n if (Array.isArray(document.specFiles) && document.specFiles.length === 0) {\n return { none: 'no spec file matched its patterns (\"specs\" in its configuration, docs/**/*.md by default)' };\n }\n const said = (answer.stderr.trim().split('\\n')[0] ?? '').replace(/^spec-guard:\\s*/, '');\n return { unavailable: said === '' ? `spec-guard exited ${answer.code}` : `spec-guard exited ${answer.code}: ${said}` };\n }\n const seen = new Set<string>();\n const out: RuleInForce[] = [];\n for (const rules of readQueryRules(answer.document)) {\n for (const rule of rules) {\n const key = `${rule.document}:${rule.line}`;\n if (!rule.inForce || seen.has(key)) continue;\n seen.add(key);\n out.push({ document: rule.document, line: rule.line, kind: rule.kind, description: rule.description, reason: rule.reason });\n }\n }\n return out;\n}\n\nexport interface RulingInForce {\n readonly id: string;\n readonly paths: readonly string[];\n readonly signer: string;\n}\n\nexport interface ContextInput {\n readonly brief: BriefRow;\n readonly briefText: string;\n readonly dependencies: readonly BriefRow[];\n readonly cited: readonly CitedDocument[];\n /** The rules in force for the scope, none because spec-guard reads no specification, or why they could not be read. */\n readonly rules: Rules;\n readonly rulings: readonly RulingInForce[];\n readonly branch: string | null;\n readonly base: string | null;\n readonly budget: number;\n}\n\n/** A pattern in the brief's scope or protections that the guard cannot read, with spec-core's reason. */\nexport interface UnreadablePattern {\n readonly pattern: string;\n readonly reason: string;\n}\n\n/** A line of a cited document's front matter that is not `key: value`, with spec-core's reason. */\nexport interface UnreadableFrontMatter extends UnreadableFrontMatterLine {\n readonly path: string;\n}\n\n/** A path of a ruling in force that the guard cannot read, with the ruling's id and spec-core's reason. */\nexport interface UnreadableRulingPath extends UnreadablePattern {\n readonly ruling: string;\n}\n\nexport interface ContextPacket {\n readonly markdown: string;\n /** Cited documents included whole. */\n readonly included: readonly string[];\n /** Cited documents named but left out for the budget. */\n readonly omitted: readonly string[];\n /** Links in the brief that resolve to nothing. */\n readonly unresolved: readonly string[];\n /** Cited documents whose front matter opens on line 1 and is never closed, so no status was read from them. */\n readonly unclosedFrontMatter: readonly string[];\n /** Lines of cited documents' front matter that are not `key: value`, in the order the brief cites them: a status written on one was not read. */\n readonly unreadableFrontMatter: readonly UnreadableFrontMatter[];\n /** Patterns in `affectedFiles` the guard cannot read, in the brief's order: each puts no path in the scope. */\n readonly unreadableScope: readonly UnreadablePattern[];\n /** Patterns in `protectedFiles` the guard cannot read, in the brief's order: while one stands, the guard refuses every write but to the brief. */\n readonly unreadableProtections: readonly UnreadablePattern[];\n /** Paths of the rulings in force the guard cannot read, in the order the rulings are listed: each allows nothing. */\n readonly unreadableRulingPaths: readonly UnreadableRulingPath[];\n}\n\nfunction list(items: readonly string[]): string {\n return items.map((item) => `- \\`${item}\\``).join('\\n');\n}\n\n/** An unread line of front matter: where it is, as written, and spec-core's reason. */\nfunction unreadLine(found: UnreadableFrontMatter): string {\n return `- \\`${found.path}\\`, line ${found.line}, \\`${found.text}\\`: ${found.reason}`;\n}\n\n/**\n * Each pattern a line, as the guard reads it: as written, or with spec-core's\n * reason the guard cannot read it and what the guard does without it. The\n * guard's own compile decides, so the packet and the guard cannot disagree.\n * A pattern a leading `/` roots, or one of whose alternatives it roots, is\n * marked with `rootedNote`, and counted when every alternative is rooted.\n */\nfunction patternLines(\n patterns: readonly string[],\n consequence: string,\n rootedNote: { readonly whole: string; readonly part: string },\n): { lines: string[]; unreadable: UnreadablePattern[]; rootedWhole: number } {\n const unreadable: UnreadablePattern[] = [];\n let rootedWhole = 0;\n const lines = patterns.map((pattern) => {\n const reason = whyUnreadable(pattern);\n if (reason === null) {\n const root = rooted(pattern);\n if (root === null) return `- \\`${pattern}\\``;\n if (root === 'whole') rootedWhole += 1;\n return `- \\`${pattern}\\`, ${rootedNote[root]}`;\n }\n unreadable.push({ pattern, reason });\n return `- \\`${pattern}\\`, which the guard cannot read: ${reason}; ${consequence}`;\n });\n return { lines, unreadable, rootedWhole };\n}\n\nfunction rulesSection(rules: Rules, scopeUnread: boolean, scopeRooted: boolean): string {\n if ('none' in rules) return `spec-guard holds no rule over this scope: ${rules.none}.`;\n if ('unavailable' in rules) {\n return `The rules spec-guard holds this code to could not be read: ${rules.unavailable}. Treat every ADR as binding until they can.`;\n }\n if (rules.length === 0) {\n // With no pattern of the scope readable, spec-guard was asked about no\n // path, so an empty answer is no answer: \"no rule\" would tell the agent\n // the code it writes is unconstrained.\n if (scopeUnread) {\n return 'The scope could not be read: no pattern in `affectedFiles` can be read, so spec-guard was not asked for the rules over it. Treat every ADR as binding until the scope is fixed.';\n }\n return scopeRooted\n ? \"No pattern in `affectedFiles` puts a path in the scope: each is rooted at the filesystem's root or cannot be read, so spec-guard was not asked for the rules over it. Treat every ADR as binding until the scope is fixed.\"\n : 'spec-guard holds no rule over this scope.';\n }\n const byDocument = new Map<string, RuleInForce[]>();\n for (const rule of rules) byDocument.set(rule.document, [...(byDocument.get(rule.document) ?? []), rule]);\n const parts: string[] = [];\n for (const [document, found] of byDocument) {\n parts.push(`### ${document}`, '');\n for (const rule of found) parts.push(`- line ${rule.line}: ${rule.description}${rule.reason === null ? '' : ` - ${rule.reason}`}`);\n parts.push('');\n }\n return parts.join('\\n').trimEnd();\n}\n\n/**\n * A brief's title without the id it repeats, by the rules spec-brief reads\n * it with: the brief's own id, then an em dash, which `spec-brief new`\n * writes, an en dash, a full-width colon or a colon, spaced or not, or a\n * hyphen with a space on each side; `012 - Rotate tokens`, `012\\u2014Rotate\n * tokens` and `012\\uFF1ARotate tokens` read as `Rotate tokens`. A bare hyphen\n * is part of a word or a number, as in `001-2 migration`, and only the\n * brief's own id is taken off: `Fix - the login bug` is a title, and so is\n * `0010 \\u2014 x` on brief 001.\n */\nexport function titleOf(brief: Pick<BriefRow, 'id' | 'title'>): string | null {\n if (brief.title === null) return null;\n return brief.title.replace(/^\\s*([^\\s:\\uFF1A\\u2013\\u2014]+)(?:\\s*[:\\uFF1A\\u2013\\u2014]\\s*|\\s+-\\s+)(?=\\S)/, (whole, first: string) =>\n sameId(first, brief.id) ? '' : whole,\n );\n}\n\n/** Renders the packet, filling the budget with cited documents in the order the brief cites them. */\nexport function renderContext(input: ContextInput): ContextPacket {\n const { brief } = input;\n const title = titleOf(brief);\n const header = [\n `# Round ${brief.id}${title === null ? '' : `: ${title}`}`,\n '',\n [\n `Status ${brief.status ?? 'unknown'}`,\n brief.wave === null ? null : `wave ${brief.wave}`,\n input.branch === null ? null : `branch \\`${input.branch}\\``,\n input.base === null ? null : `measured from \\`${input.base}\\``,\n ]\n .filter((part) => part !== null)\n .join(' · '),\n '',\n ].join('\\n');\n\n const contract = ['## The contract', '', `\\`${brief.file}\\`, in full:`, '', '````markdown', input.briefText.trimEnd(), '````', ''].join('\\n');\n\n // The guard passes over a ruling's path it cannot read, so the path allows\n // nothing. The paths share a line, so the note is in parentheses, closed\n // before the next path.\n const unreadableRulingPaths: UnreadableRulingPath[] = [];\n const rulings =\n input.rulings.length === 0\n ? '- none'\n : input.rulings\n .map((ruling) => {\n const paths = ruling.paths.map((pattern) => {\n const reason = whyUnreadable(pattern);\n if (reason === null) {\n // A rooted path can be read, and names no path the guard decides.\n const root = rooted(pattern);\n if (root === 'whole') return `\\`${pattern}\\` (which a leading \\`/\\` roots at the filesystem's root; it allows nothing)`;\n if (root === 'part') return `\\`${pattern}\\` (an alternative of which a leading \\`/\\` roots at the filesystem's root; that alternative allows nothing)`;\n return `\\`${pattern}\\``;\n }\n unreadableRulingPaths.push({ ruling: ruling.id, pattern, reason });\n return `\\`${pattern}\\` (which the guard cannot read: ${reason}; it allows nothing)`;\n });\n return `- ${ruling.id}, signed by ${ruling.signer}: ${paths.join(', ')}`;\n })\n .join('\\n');\n // A rooted pattern can be read and puts no path in the scope: every path\n // the guard decides is repository-relative. spec-guard is not asked about it.\n const mayWrite = patternLines(brief.affectedFiles, 'it puts no path in the scope', {\n whole: \"which a leading `/` roots at the filesystem's root: it puts no path in the scope, and spec-guard is not asked about it\",\n part: \"an alternative of which a leading `/` roots at the filesystem's root: that alternative puts no path in the scope, and spec-guard is not asked about it\",\n });\n const unreadableScope = mayWrite.unreadable;\n // While a protection the guard cannot read stands, the guard refuses every\n // write, and no ruling waives it; a write to the brief is let through,\n // since that is where it is fixed. A rooted protection can be read, and\n // protects nothing: the file it was meant to protect is the round's to write.\n const mustNot = patternLines(brief.protectedFiles, 'until it is fixed, the guard refuses every write but to the brief', {\n whole: \"which a leading `/` roots at the filesystem's root: it protects no path\",\n part: \"an alternative of which a leading `/` roots at the filesystem's root: that alternative protects no path\",\n });\n const unreadableProtections = mustNot.unreadable;\n const scope = [\n '## Scope, as the guard reads it',\n '',\n 'May write:',\n mayWrite.lines.length === 0 ? '- nothing declared: every write is outside the scope' : mayWrite.lines.join('\\n'),\n '',\n 'Must not change without a ruling:',\n mustNot.lines.length === 0 ? '- nothing declared' : mustNot.lines.join('\\n'),\n '',\n 'Rulings in force:',\n rulings,\n '',\n ].join('\\n');\n\n const waiting = input.dependencies.map((dependency) => {\n const state = dependency.phase === 'archived' ? 'archived, done' : `still ${dependency.status ?? 'live'}`;\n const named = titleOf(dependency);\n return `- ${dependency.id}${named === null ? '' : ` ${named}`}: ${state}`;\n });\n const dependencies = ['## Depends on', '', waiting.length === 0 ? '- nothing' : waiting.join('\\n'), ''].join('\\n');\n\n const scopeUnread = mayWrite.lines.length > 0 && unreadableScope.length === mayWrite.lines.length;\n const scopeRooted = mayWrite.rootedWhole > 0 && unreadableScope.length + mayWrite.rootedWhole === mayWrite.lines.length;\n const rules = ['## Rules in force for this scope', '', rulesSection(input.rules, scopeUnread, scopeRooted), ''].join('\\n');\n\n const howTo = [\n '## How this round works',\n '',\n '- Before writing a file, ask whether the round may: `spec-harness guard <path>`, or the `check_path` tool.',\n '- A protected file is changed only under a ruling a person signs. If the round cannot be done without one, stop and ask: `spec-harness escalate --path <file> --reason <why>`, or the `request_escalation` tool. Do not work around it.',\n '- Every box in the brief ends ticked, or with a note under it that starts with a disposition (`**Delegated`, `**Accepted debt`, `**Rejected`, or what the repository configures).',\n '- When the work is done, run `spec-harness audit` and fix what it reports. The archive is a person\\'s decision, not the round\\'s.',\n '',\n ].join('\\n');\n\n const fixed = [header, contract, scope, dependencies, rules, howTo].join('\\n');\n const included: string[] = [];\n const omitted: string[] = [];\n const unresolved: string[] = [];\n const unclosed: string[] = [];\n const unreadableFrontMatter: UnreadableFrontMatter[] = [];\n const documents: string[] = [];\n let used = fixed.length;\n for (const cited of input.cited) {\n if (cited.text === null) {\n unresolved.push(cited.path);\n continue;\n }\n // Named whether or not the budget leaves room for it: the status is\n // unread either way, and the fix is the same.\n if (cited.unclosedFrontMatter === true) unclosed.push(cited.path);\n for (const line of cited.unreadableFrontMatter ?? []) unreadableFrontMatter.push({ path: cited.path, ...line });\n const heading = `### \\`${cited.path}\\`${cited.title === null ? '' : ` - ${cited.title}`}${cited.status === null ? '' : ` (${cited.status})`}`;\n const block = [heading, '', '````markdown', cited.text.trimEnd(), '````', ''].join('\\n');\n if (used + block.length > input.budget) {\n omitted.push(cited.path);\n continue;\n }\n used += block.length;\n included.push(cited.path);\n documents.push(block);\n }\n const citedSection = ['## Documents the brief cites', ''];\n if (documents.length === 0 && omitted.length === 0 && unresolved.length === 0) citedSection.push('None.', '');\n citedSection.push(...documents);\n if (omitted.length > 0) {\n citedSection.push(`Left out to stay within ${input.budget} characters; read them when the work reaches them:`, list(omitted), '');\n }\n if (unresolved.length > 0) citedSection.push('Cited but not found in the repository:', list(unresolved), '');\n if (unclosed.length > 0) {\n citedSection.push('Front matter opened on line 1 and never closed, so the status was not read; close the block with `---` on a line of its own:', list(unclosed), '');\n }\n // The last note: the section is trimmed at its end, so no blank line follows it.\n if (unreadableFrontMatter.length > 0) {\n citedSection.push(\n 'Front matter lines that are not `key: value` were not read, so a status written on one, if any, was not read either:',\n unreadableFrontMatter.map(unreadLine).join('\\n'),\n );\n }\n\n return {\n markdown: `${fixed}\\n${citedSection.join('\\n').trimEnd()}\\n`,\n included,\n omitted,\n unresolved,\n unclosedFrontMatter: unclosed,\n unreadableFrontMatter,\n unreadableScope,\n unreadableProtections,\n unreadableRulingPaths,\n };\n}\n"]}
package/dist/fs.js CHANGED
@@ -13,6 +13,7 @@ export async function loadConfig(root) {
13
13
  return { config: DEFAULT_CONFIG, file: null };
14
14
  let raw;
15
15
  try {
16
+ // A Buffer parses as its text, so the encoding is equivalent to its mutant.
16
17
  raw = JSON.parse(await readFile(file, 'utf8'));
17
18
  }
18
19
  catch (error) {
@@ -33,14 +34,17 @@ export function realSpelling(path) {
33
34
  let current = resolve(path);
34
35
  for (;;) {
35
36
  try {
36
- const real = realpathSync.native(current);
37
- return missing.length === 0 ? real : join(real, ...missing.reverse());
37
+ // join leaves a real path as it is, and reads a segment's leading
38
+ // separator as the one between it and the last.
39
+ return join(realpathSync.native(current), ...missing.reverse());
38
40
  }
39
41
  catch {
40
42
  const parent = dirname(current);
43
+ // Only a root that does not exist fails at the top, a drive Windows
44
+ // does not have; everywhere else the loop ends in the try.
41
45
  if (parent === current)
42
46
  return resolve(path);
43
- missing.push(current.slice(parent.length).replace(/^[\\/]+/, ''));
47
+ missing.push(current.slice(parent.length));
44
48
  current = parent;
45
49
  }
46
50
  }
@@ -54,8 +58,6 @@ export function repositoryPath(given, root, cwd) {
54
58
  const real = realSpelling(absolute);
55
59
  const realRoot = realSpelling(root);
56
60
  const rel = relative(realRoot, real);
57
- if (rel === '')
58
- return '';
59
61
  // `..env` at the root is inside it; only a `..` segment climbs out.
60
62
  if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel))
61
63
  return null;
@@ -71,6 +73,7 @@ export async function stateDirectory(commonDir, ...parts) {
71
73
  export async function writeAtomic(file, content) {
72
74
  await mkdir(dirname(file), { recursive: true });
73
75
  const temporary = `${file}.${process.pid}.tmp`;
76
+ // utf8 is writeFile's own default, so naming it is equivalent to its mutant.
74
77
  await writeFile(temporary, content, 'utf8');
75
78
  await rename(temporary, file);
76
79
  }
package/dist/fs.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"fs.js","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAE9E,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,EAAsB,MAAM,aAAa,CAAC;AAExG,kFAAkF;AAClF,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY;IAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IACrC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACrE,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IACjD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,WAAW,CAAC,GAAG,WAAW,uBAAwB,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;IACzF,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC;AAC5C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,SAAS,CAAC;QACR,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC1C,OAAO,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;QACxE,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;YAChC,IAAI,MAAM,KAAK,OAAO;gBAAE,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC;YAC7C,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC;YAClE,OAAO,GAAG,MAAM,CAAC;QACnB,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa,EAAE,IAAY,EAAE,GAAW;IACrE,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACjE,MAAM,IAAI,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACpC,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IACpC,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACrC,IAAI,GAAG,KAAK,EAAE;QAAE,OAAO,EAAE,CAAC;IAC1B,oEAAoE;IACpE,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC,KAAK,GAAG,EAAE,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/E,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClC,CAAC;AAED,gGAAgG;AAChG,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,SAAiB,EAAE,GAAG,KAAe;IACxE,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,cAAc,EAAE,GAAG,KAAK,CAAC,CAAC;IAC5D,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,mFAAmF;AACnF,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAY,EAAE,OAAe;IAC7D,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,MAAM,SAAS,GAAG,GAAG,IAAI,IAAI,OAAO,CAAC,GAAG,MAAM,CAAC;IAC/C,MAAM,SAAS,CAAC,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAC5C,MAAM,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;AAChC,CAAC;AAED,2GAA2G;AAC3G,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAY;IAC/C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAY,CAAC;QAClE,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,KAAiC,CAAC,CAAC,CAAC,YAAY,CAAC;IAClI,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,YAAY,CAAC;IACtB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAY;IACzC,IAAI,CAAC;QACH,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC","sourcesContent":["/**\n * The edge where the harness meets the disk: the configuration, the state\n * directory outside the work tree, and paths as the filesystem spells them.\n */\n\nimport { existsSync, realpathSync } from 'node:fs';\nimport { mkdir, readFile, rename, writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';\n\nimport { ConfigError, CONFIG_FILE, DEFAULT_CONFIG, parseConfig, type HarnessConfig } from './config.js';\n\n/** Reads `.spec-harness.json` at the root, or the defaults when there is none. */\nexport async function loadConfig(root: string): Promise<{ config: HarnessConfig; file: string | null }> {\n const file = join(root, CONFIG_FILE);\n if (!existsSync(file)) return { config: DEFAULT_CONFIG, file: null };\n let raw: unknown;\n try {\n raw = JSON.parse(await readFile(file, 'utf8'));\n } catch (error) {\n throw new ConfigError(`${CONFIG_FILE} is not valid JSON: ${(error as Error).message}`);\n }\n return { config: parseConfig(raw), file };\n}\n\n/**\n * The real spelling of a path: links resolved and, on a filesystem that\n * ignores case, the case the directory entries have. A path that does not\n * exist yet is resolved through its nearest existing ancestor.\n *\n * This is what stops `SRC/db/schema.ts` from walking past a protection on\n * `src/db/schema.ts` on Windows or macOS, where both name one file.\n */\nexport function realSpelling(path: string): string {\n const missing: string[] = [];\n let current = resolve(path);\n for (;;) {\n try {\n const real = realpathSync.native(current);\n return missing.length === 0 ? real : join(real, ...missing.reverse());\n } catch {\n const parent = dirname(current);\n if (parent === current) return resolve(path);\n missing.push(current.slice(parent.length).replace(/^[\\\\/]+/, ''));\n current = parent;\n }\n }\n}\n\n/**\n * A path as the repository names it - relative to the root, POSIX-separated,\n * real spelling - or `null` when it lies outside the root.\n */\nexport function repositoryPath(given: string, root: string, cwd: string): string | null {\n const absolute = isAbsolute(given) ? given : resolve(cwd, given);\n const real = realSpelling(absolute);\n const realRoot = realSpelling(root);\n const rel = relative(realRoot, real);\n if (rel === '') return '';\n // `..env` at the root is inside it; only a `..` segment climbs out.\n if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return null;\n return rel.split(sep).join('/');\n}\n\n/** The harness's own directory under the git common directory, created on demand (ADR-0003). */\nexport async function stateDirectory(commonDir: string, ...parts: string[]): Promise<string> {\n const directory = join(commonDir, 'spec-harness', ...parts);\n await mkdir(directory, { recursive: true });\n return directory;\n}\n\n/** Writes a file whole or not at all: a temporary file renamed over the target. */\nexport async function writeAtomic(file: string, content: string): Promise<void> {\n await mkdir(dirname(file), { recursive: true });\n const temporary = `${file}.${process.pid}.tmp`;\n await writeFile(temporary, content, 'utf8');\n await rename(temporary, file);\n}\n\n/** A JSON object from a file: `null` when there is no file, `'unreadable'` when it holds anything else. */\nexport async function readJsonObject(file: string): Promise<Record<string, unknown> | null | 'unreadable'> {\n if (!existsSync(file)) return null;\n try {\n const value = JSON.parse(await readFile(file, 'utf8')) as unknown;\n return typeof value === 'object' && value !== null && !Array.isArray(value) ? (value as Record<string, unknown>) : 'unreadable';\n } catch {\n return 'unreadable';\n }\n}\n\nexport async function readText(file: string): Promise<string | null> {\n try {\n return await readFile(file, 'utf8');\n } catch {\n return null;\n }\n}\n"]}
1
+ {"version":3,"file":"fs.js","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The edge where the harness meets the disk: the configuration, the state\n * directory outside the work tree, and paths as the filesystem spells them.\n */\n\nimport { existsSync, realpathSync } from 'node:fs';\nimport { mkdir, readFile, rename, writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';\n\nimport { ConfigError, CONFIG_FILE, DEFAULT_CONFIG, parseConfig, type HarnessConfig } from './config.js';\n\n/** Reads `.spec-harness.json` at the root, or the defaults when there is none. */\nexport async function loadConfig(root: string): Promise<{ config: HarnessConfig; file: string | null }> {\n const file = join(root, CONFIG_FILE);\n if (!existsSync(file)) return { config: DEFAULT_CONFIG, file: null };\n let raw: unknown;\n try {\n // A Buffer parses as its text, so the encoding is equivalent to its mutant.\n raw = JSON.parse(await readFile(file, 'utf8'));\n } catch (error) {\n throw new ConfigError(`${CONFIG_FILE} is not valid JSON: ${(error as Error).message}`);\n }\n return { config: parseConfig(raw), file };\n}\n\n/**\n * The real spelling of a path: links resolved and, on a filesystem that\n * ignores case, the case the directory entries have. A path that does not\n * exist yet is resolved through its nearest existing ancestor.\n *\n * This is what stops `SRC/db/schema.ts` from walking past a protection on\n * `src/db/schema.ts` on Windows or macOS, where both name one file.\n */\nexport function realSpelling(path: string): string {\n const missing: string[] = [];\n let current = resolve(path);\n for (;;) {\n try {\n // join leaves a real path as it is, and reads a segment's leading\n // separator as the one between it and the last.\n return join(realpathSync.native(current), ...missing.reverse());\n } catch {\n const parent = dirname(current);\n // Only a root that does not exist fails at the top, a drive Windows\n // does not have; everywhere else the loop ends in the try.\n if (parent === current) return resolve(path);\n missing.push(current.slice(parent.length));\n current = parent;\n }\n }\n}\n\n/**\n * A path as the repository names it - relative to the root, POSIX-separated,\n * real spelling - or `null` when it lies outside the root.\n */\nexport function repositoryPath(given: string, root: string, cwd: string): string | null {\n const absolute = isAbsolute(given) ? given : resolve(cwd, given);\n const real = realSpelling(absolute);\n const realRoot = realSpelling(root);\n const rel = relative(realRoot, real);\n // `..env` at the root is inside it; only a `..` segment climbs out.\n if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return null;\n return rel.split(sep).join('/');\n}\n\n/** The harness's own directory under the git common directory, created on demand (ADR-0003). */\nexport async function stateDirectory(commonDir: string, ...parts: string[]): Promise<string> {\n const directory = join(commonDir, 'spec-harness', ...parts);\n await mkdir(directory, { recursive: true });\n return directory;\n}\n\n/** Writes a file whole or not at all: a temporary file renamed over the target. */\nexport async function writeAtomic(file: string, content: string): Promise<void> {\n await mkdir(dirname(file), { recursive: true });\n const temporary = `${file}.${process.pid}.tmp`;\n // utf8 is writeFile's own default, so naming it is equivalent to its mutant.\n await writeFile(temporary, content, 'utf8');\n await rename(temporary, file);\n}\n\n/** A JSON object from a file: `null` when there is no file, `'unreadable'` when it holds anything else. */\nexport async function readJsonObject(file: string): Promise<Record<string, unknown> | null | 'unreadable'> {\n if (!existsSync(file)) return null;\n try {\n const value = JSON.parse(await readFile(file, 'utf8')) as unknown;\n return typeof value === 'object' && value !== null && !Array.isArray(value) ? (value as Record<string, unknown>) : 'unreadable';\n } catch {\n return 'unreadable';\n }\n}\n\nexport async function readText(file: string): Promise<string | null> {\n try {\n return await readFile(file, 'utf8');\n } catch {\n return null;\n }\n}\n"]}
package/dist/git.d.ts CHANGED
@@ -13,7 +13,7 @@ export interface Run {
13
13
  readonly stderr: string;
14
14
  }
15
15
  /** Runs git and answers whatever it answered; a missing git is a code of -1. */
16
- export declare function git(args: readonly string[], cwd: string, input?: string): Promise<Run>;
16
+ export declare function git(args: readonly string[], cwd: string): Promise<Run>;
17
17
  export declare class GitError extends Error {
18
18
  constructor(message: string);
19
19
  }
@@ -43,18 +43,24 @@ export declare function changes(from: string, to: string, cwd: string): Promise<
43
43
  export declare function stagedChanges(cwd: string): Promise<FileChange[]>;
44
44
  /** A file's content at a revision, or `null` when it does not exist there. */
45
45
  export declare function show(ref: string, path: string, cwd: string): Promise<string | null>;
46
+ /** Whether a commit id from {@link blameLine} is the all-zero one of a line nobody committed. */
47
+ export declare function isUncommitted(sha: string): boolean;
46
48
  /**
47
49
  * The commit that last changed one line of a file, as of a revision, or of
48
50
  * the working tree when `ref` is `null` - where a line nobody committed yet
49
51
  * belongs to the all-zero commit.
50
52
  */
51
53
  export declare function blameLine(ref: string | null, path: string, line: number, cwd: string): Promise<string | null>;
52
- export interface Signature {
53
- readonly good: boolean;
54
- /** The allowed signer's principal, for a good signature. */
55
- readonly principal: string | null;
54
+ /** A good signature names its allowed signer's principal; any other names none. */
55
+ export type Signature = {
56
+ readonly good: true;
57
+ readonly principal: string;
56
58
  readonly detail: string;
57
- }
59
+ } | {
60
+ readonly good: false;
61
+ readonly principal: null;
62
+ readonly detail: string;
63
+ };
58
64
  /**
59
65
  * Whether a commit carries a good SSH signature by a principal in the given
60
66
  * allowed-signers file. The file is passed in, never read from the commit's
package/dist/git.js CHANGED
@@ -8,14 +8,16 @@
8
8
  */
9
9
  import { execFile } from 'node:child_process';
10
10
  /** Runs git and answers whatever it answered; a missing git is a code of -1. */
11
- export function git(args, cwd, input) {
11
+ export function git(args, cwd) {
12
12
  return new Promise((resolve) => {
13
- const child = execFile('git', [...args], { cwd, maxBuffer: 256 * 1024 * 1024, encoding: 'utf8', windowsHide: true }, (error, stdout, stderr) => {
14
- const code = error === null ? 0 : typeof error.code === 'number' ? (error.code) : -1;
13
+ // windowsHide only keeps a console window from flashing up on Windows,
14
+ // and the sweep runs on Linux, where its mutant is equivalent.
15
+ execFile('git', [...args], { cwd, maxBuffer: 256 * 1024 * 1024, encoding: 'utf8', windowsHide: true }, (error, stdout, stderr) => {
16
+ // Every caller tells 0 from any other code, never one failure from
17
+ // another, so which code a failure gets is equivalent to its mutants.
18
+ const code = error === null ? 0 : typeof error.code === 'number' ? error.code : -1;
15
19
  resolve({ code, stdout, stderr });
16
20
  });
17
- if (input !== undefined)
18
- child.stdin?.end(input);
19
21
  });
20
22
  }
21
23
  async function value(args, cwd) {
@@ -55,7 +57,9 @@ export async function remoteDefault(cwd) {
55
57
  /** Every local branch, by its short name. */
56
58
  export async function localBranches(cwd) {
57
59
  const listed = await value(['for-each-ref', '--format=%(refname:short)', 'refs/heads/'], cwd);
58
- return listed === null || listed === '' ? [] : listed.split('\n').map((name) => name.trim());
60
+ // for-each-ref fails only where git itself does, outside a repository,
61
+ // where init never runs: the null check is there for the type.
62
+ return listed === null || listed === '' ? [] : listed.split('\n');
59
63
  }
60
64
  export async function revision(ref, cwd) {
61
65
  return value(['rev-parse', '--verify', '--quiet', `${ref}^{commit}`], cwd);
@@ -68,7 +72,7 @@ export function parseNameStatus(output) {
68
72
  const fields = output.split('\0');
69
73
  const out = [];
70
74
  for (let i = 0; i < fields.length;) {
71
- const code = fields[i] ?? '';
75
+ const code = fields[i];
72
76
  if (code === '') {
73
77
  i += 1;
74
78
  continue;
@@ -104,6 +108,10 @@ export async function show(ref, path, cwd) {
104
108
  const run = await git(['show', `${ref}:${path}`], cwd);
105
109
  return run.code === 0 ? run.stdout : null;
106
110
  }
111
+ /** Whether a commit id from {@link blameLine} is the all-zero one of a line nobody committed. */
112
+ export function isUncommitted(sha) {
113
+ return /^0+$/.test(sha);
114
+ }
107
115
  /**
108
116
  * The commit that last changed one line of a file, as of a revision, or of
109
117
  * the working tree when `ref` is `null` - where a line nobody committed yet
@@ -111,8 +119,8 @@ export async function show(ref, path, cwd) {
111
119
  */
112
120
  export async function blameLine(ref, path, line, cwd) {
113
121
  const run = await git(['blame', '--porcelain', '-L', `${line},${line}`, ...(ref === null ? [] : [ref]), '--', path], cwd);
114
- if (run.code !== 0)
115
- return null;
122
+ // A blame that fails prints nothing on stdout, which names no commit. Its
123
+ // output opens with the commit, so the anchor is equivalent to its mutant.
116
124
  const sha = /^([0-9a-f]{40,64}) /.exec(run.stdout)?.[1];
117
125
  return sha ?? null;
118
126
  }
@@ -124,8 +132,14 @@ export async function blameLine(ref, path, line, cwd) {
124
132
  export async function verifyCommit(sha, allowedSignersFile, cwd) {
125
133
  const run = await git(['-c', `gpg.ssh.allowedSignersFile=${allowedSignersFile}`, '-c', 'gpg.format=ssh', 'verify-commit', '--raw', sha], cwd);
126
134
  const text = `${run.stderr}\n${run.stdout}`;
127
- const principal = /Good "git" signature for (\S+)/.exec(text)?.[1] ?? null;
128
- return { good: run.code === 0 && principal !== null, principal, detail: text.trim().split('\n')[0] ?? '' };
135
+ const principal = /Good "git" signature for (\S+)/.exec(text)?.[1];
136
+ // git writes the check to stderr, so the trim changes the first line only
137
+ // for output on stdout alone, which verify-commit does not print; and split
138
+ // always answers a first part.
139
+ const detail = text.trim().split('\n')[0];
140
+ // git exits 0 only for a signature by a listed principal, which it names:
141
+ // either condition alone is equivalent to both, and both trust neither.
142
+ return run.code === 0 && principal !== undefined ? { good: true, principal, detail } : { good: false, principal: null, detail };
129
143
  }
130
144
  export async function addWorktree(path, rev, cwd) {
131
145
  const run = await git(['worktree', 'add', '--detach', '--force', path, rev], cwd);