@descent-vtt/spec-harness 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +20 -0
- package/dist/cli.js +21 -15
- package/dist/cli.js.map +1 -1
- package/dist/formats.js +3 -1
- package/dist/formats.js.map +1 -1
- package/dist/junit.js +13 -2
- package/dist/junit.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spec-harness",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Rounds of agent work under a brief: context, a guard on every write, escalations a person signs, an audit at the end. Needs Claude Code 2.1.139 or later, and @descent-vtt/spec-harness and @descent-vtt/spec-brief 0.2.0 or later installed in the project.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "DescentVTT",
|
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,26 @@ only fixes, reports less or documents, so `^0.7.0` takes only releases that
|
|
|
9
9
|
cannot turn a run red. The family's policy is
|
|
10
10
|
[spec-core's ADR-0009](https://github.com/DescentVTT/spec-core/blob/main/docs/adr/0009-versions-before-1-0.md).
|
|
11
11
|
|
|
12
|
+
## 0.9.1
|
|
13
|
+
|
|
14
|
+
A command or a JUnit entity named `constructor`, `toString` or `__proto__`
|
|
15
|
+
is the unknown name it is, and the suite now runs against the siblings' newest
|
|
16
|
+
releases and, in CI, their minimums.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- A command named as something every JavaScript object answers to -
|
|
21
|
+
`spec-harness constructor`, `toString`, `__proto__` - is the unknown
|
|
22
|
+
command it is: `unknown command "constructor"; see spec-harness --help`
|
|
23
|
+
with exit 2, where it ended on a stack trace with exit 1.
|
|
24
|
+
- `probe` leaves an entity of such a name in a JUnit report as written, as
|
|
25
|
+
it leaves any entity XML does not name: `&constructor;` or `&toString;` in
|
|
26
|
+
a test's name, its class or a failure's message was replaced with the
|
|
27
|
+
source of a function, so a probe's `test:` could not name that test.
|
|
28
|
+
- `formatSarif`, called with a finding whose rule is so named, describes the
|
|
29
|
+
rule by its name, as it does any rule the audit does not have; the
|
|
30
|
+
description had no text. No command makes such a finding.
|
|
31
|
+
|
|
12
32
|
## 0.9.0
|
|
13
33
|
|
|
14
34
|
spec-harness refuses sibling JSON of a shape it does not read, with exit 2,
|
package/dist/cli.js
CHANGED
|
@@ -269,20 +269,26 @@ async function doctorCommand(options, io) {
|
|
|
269
269
|
return outdated || !usable || claude === 'both' || tooOld || untold ? EXIT_FAILED : EXIT_OK;
|
|
270
270
|
}
|
|
271
271
|
/* ---------------------------------------------------------------- dispatch */
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
272
|
+
/**
|
|
273
|
+
* A map, because the command is the first word a person types and an object
|
|
274
|
+
* answers to more names than it was given: `spec-harness constructor` ran
|
|
275
|
+
* `Object` as a command and ended on a stack trace, where `spec-harness
|
|
276
|
+
* construct` is an unknown command with exit 2.
|
|
277
|
+
*/
|
|
278
|
+
const COMMANDS = new Map([
|
|
279
|
+
['guard', guardCommand],
|
|
280
|
+
['hook', hookCommand],
|
|
281
|
+
['context', contextCommand],
|
|
282
|
+
['audit', auditCommand],
|
|
283
|
+
['escalate', escalateCommand],
|
|
284
|
+
['rule', ruleCommand],
|
|
285
|
+
['rulings', rulingsCommand],
|
|
286
|
+
['probe', probeCommand],
|
|
287
|
+
['premises', premisesCommand],
|
|
288
|
+
['init', initCommand],
|
|
289
|
+
['mcp', mcpCommand],
|
|
290
|
+
['doctor', doctorCommand],
|
|
291
|
+
]);
|
|
286
292
|
export async function run(argv, io) {
|
|
287
293
|
let options;
|
|
288
294
|
try {
|
|
@@ -300,7 +306,7 @@ export async function run(argv, io) {
|
|
|
300
306
|
io.stdout.write(HELP);
|
|
301
307
|
return options.help ? EXIT_OK : EXIT_ERROR;
|
|
302
308
|
}
|
|
303
|
-
const command = COMMANDS
|
|
309
|
+
const command = COMMANDS.get(options.command);
|
|
304
310
|
if (command === undefined) {
|
|
305
311
|
io.stderr.write(`spec-harness: unknown command "${options.command}"; see spec-harness --help\n`);
|
|
306
312
|
return EXIT_ERROR;
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The command line: arguments, dispatch, output and exit codes.\n *\n * Exit 0: done, nothing refused. Exit 1: something refused or found. Exit 2:\n * the answer cannot be trusted - a bad flag, a configuration that does not\n * load, a sibling that is missing or printed something unreadable. A run that\n * could not look must never exit as though it looked and found nothing\n * (spec-core ADR-0005).\n */\n\nimport { SiblingOutputError } from './briefs.js';\nimport { auditCommand, contextCommand, escalateCommand, probeCommand, ruleCommand, rulingsCommand } from './commands.js';\nimport { ConfigError, SIBLINGS } from './config.js';\nimport {\n describeBase,\n describeClaudeCode,\n describeClaudeRelease,\n describeGitHook,\n describePlugin,\n describeSignerKeys,\n describeSignerProblem,\n describeSigners,\n wiringState,\n} from './configure.js';\nimport { show, stagedChanges } from './git.js';\nimport type { Decision } from './guard.js';\nimport { claudeResponse, gitResponse, parseClaudeHook } from './hooks.js';\nimport { claudeVersion } from './host.js';\nimport { premisesCommand } from './premises.js';\nimport { createReader } from './reader.js';\nimport { checkPaths, claudeCodeWiring, resolveBase, specBriefPlugin } from './round.js';\nimport { mcpCommand } from './server.js';\nimport { gitHook, initCommand } from './setup.js';\nimport { notFido2, readAllowedSigners } from './signers.js';\nimport { SiblingError } from './siblings.js';\nimport { checkClaudeCode, CLAUDE_CODE_MINIMUM, MINIMUM_VERSIONS } from './versions.js';\nimport {\n activeBrief,\n describeActive,\n EXIT_ERROR,\n EXIT_FAILED,\n EXIT_OK,\n json,\n namedId,\n openWorkspace,\n parseOptions,\n UsageError,\n version,\n type CliIO,\n type Options,\n type Workspace,\n} from './workspace.js';\n\nexport { EXIT_ERROR, EXIT_FAILED, EXIT_OK, UsageError, version, type CliIO } from './workspace.js';\n\nexport const HELP = `spec-harness - context, guards, audits and rulings for rounds of agent work\n\nUsage:\n spec-harness <command> [options]\n\nCommands:\n context [brief] What an agent needs to start a round: the brief, its scope,\n rulings, dependencies, the rules in force, the cited documents.\n guard <path...> May the round write these paths? Exit 1 if one is refused.\n hook claude Answer a Claude Code PreToolUse or PostToolUse hook (stdin).\n hook git Answer git's pre-commit hook for the staged files.\n escalate Ask a person to rule: --path <file> (repeatable) --reason <why>\n [--option \"<label>: <cost>\"]... [--recommend <text>].\n --list shows what waits; --show <id> prints one memo.\n rule <id> Record a person's ruling in the brief: --allow or --deny, with\n --note. It counts once committed signed.\n rulings [brief] The brief's rulings, and whether each one's signature verifies.\n audit [brief] Did the round stay inside the lines: what the archive would say,\n the brief's assertions, unverified rulings, new dependencies.\n probe [brief] Run the brief's probes: red at the base, green at the head.\n --at base|head|both, --id <probe>.\n premises Do the live briefs' premises still hold? For CI: exit 1 when one\n is stale, or, with --strict, when spec-guard cannot read one.\n init Configure the spec-* tools to agree. Prints the plan; --write\n applies it; --git-hook adds a pre-commit hook.\n mcp Serve start_round, check_path, request_escalation, audit_round,\n list_rounds and the workflow prompts over MCP on stdio.\n doctor Which siblings are installed, at which versions, which brief is\n named, how Claude Code runs the guard, and git's hook. Exit 1\n when a sibling is older than this release needs, the guard is\n installed twice, or Claude Code is too old to run its hooks.\n\nOptions:\n --brief <id> The brief the round works on. Otherwise SPEC_BRIEF, then the branch.\n --base <ref> What the round is measured from. Otherwise \"base\" in\n .spec-harness.json, then the remote's default branch.\n --root <dir> Run from another directory.\n --format <fmt> pretty or json; audit and premises also gitlab, sarif or github.\n --strict Warnings fail the run.\n --help, --version\n\nExit codes: 0 clean, 1 refused or found something, 2 the answer cannot be trusted.\n`;\n\n\n/* ------------------------------------------------------------------- guard */\n\nconst reader = createReader();\n\nasync function decisionsFor(\n workspace: Workspace,\n options: Options,\n io: CliIO,\n given: readonly string[],\n cwd: string,\n): Promise<{ readonly decisions: Decision[] } | { readonly problem: string }> {\n const active = await activeBrief(workspace, options, io.env);\n const { brief, note, problem } = describeActive(active);\n if (problem !== null) return { problem };\n return { decisions: await checkPaths(workspace, brief, note, given, cwd, reader, options.base) };\n}\n\nconst SYMBOL: Record<Decision['verdict'], string> = { allow: 'ok ', warn: 'warning', ask: 'ask ', deny: 'refused' };\n\nasync function guardCommand(options: Options, io: CliIO): Promise<number> {\n if (options.positionals.length === 0) throw new UsageError('guard needs at least one path');\n const workspace = await openWorkspace(options, io);\n const decided = await decisionsFor(workspace, options, io, options.positionals, io.cwd);\n if ('problem' in decided) {\n io.stderr.write(`spec-harness: ${decided.problem}\\n`);\n return EXIT_ERROR;\n }\n const { decisions } = decided;\n if (options.format === 'json') {\n io.stdout.write(json('guard', { decisions }));\n } else {\n for (const decision of decisions) {\n io.stdout.write(`${SYMBOL[decision.verdict]} ${decision.message}\\n`);\n if (decision.verdict !== 'allow') io.stdout.write(` ${decision.hint}\\n`);\n }\n }\n const refused = decisions.some((d) => d.verdict === 'deny' || d.verdict === 'ask');\n const warned = decisions.some((d) => d.verdict === 'warn');\n return refused || (options.strict && warned) ? EXIT_FAILED : EXIT_OK;\n}\n\n/* -------------------------------------------------------------------- hook */\n\nasync function readStdin(io: CliIO): Promise<string> {\n // Only the built command line reads process.stdin, and the suite spawns it\n // from dist/, outside the sweep (tests/integration/claude-code.test.ts): the\n // lines below survive it untested in-process.\n if (io.stdin !== undefined) return io.stdin();\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) chunks.push(chunk as Buffer);\n return Buffer.concat(chunks).toString('utf8');\n}\n\nasync function hookCommand(options: Options, io: CliIO): Promise<number> {\n const kind = options.positionals[0];\n if (kind === 'claude') {\n const request = parseClaudeHook(await readStdin(io));\n if ('error' in request) {\n // A hook that cannot read its question refuses nothing it cannot see,\n // but says so where the person reads it: a non-blocking error.\n io.stderr.write(`spec-harness: ${request.error}\\n`);\n return EXIT_FAILED;\n }\n if (request.paths.length === 0) return EXIT_OK;\n const cwd = request.cwd ?? io.cwd;\n let workspace: Workspace;\n try {\n workspace = await openWorkspace({ ...options, root: options.root ?? io.env['CLAUDE_PROJECT_DIR'] ?? cwd }, io);\n } catch (error) {\n if (error instanceof UsageError) return EXIT_OK;\n throw error;\n }\n try {\n const decided = await decisionsFor(workspace, options, io, request.paths, cwd);\n if ('problem' in decided) {\n // Exit 2 blocks the tool call, and its stderr reaches the model.\n io.stderr.write(`spec-harness: ${decided.problem}. Fix the brief or the branch before writing.\\n`);\n return EXIT_ERROR;\n }\n const response = claudeResponse(request.event, decided.decisions);\n io.stdout.write(response.stdout);\n return response.exitCode;\n } catch (error) {\n if (request.event !== 'PreToolUse') throw error;\n // A round is named and its briefs cannot be read: the guard cannot\n // tell a protected file from any other, so the write waits.\n io.stderr.write(`spec-harness: cannot check this write: ${(error as Error).message}\\n`);\n return EXIT_ERROR;\n }\n }\n if (kind === 'git') {\n const workspace = await openWorkspace(options, io);\n const staged = await stagedChanges(workspace.root);\n const paths = staged.flatMap((change) => (change.from === undefined ? [change.path] : [change.from, change.path]));\n if (paths.length === 0) return EXIT_OK;\n const decided = await decisionsFor(workspace, options, io, paths, workspace.root);\n if ('problem' in decided) {\n io.stderr.write(`spec-harness: ${decided.problem}\\n`);\n return EXIT_ERROR;\n }\n const response = gitResponse(decided.decisions);\n io.stderr.write(response.text);\n return response.exitCode;\n }\n throw new UsageError('hook takes \"claude\" or \"git\"');\n}\n\n/* ------------------------------------------------------------------ doctor */\n\nasync function doctorCommand(options: Options, io: CliIO): Promise<number> {\n const workspace = await openWorkspace(options, io);\n const rows = SIBLINGS.map((name) => {\n const sibling = workspace.siblings.locate(name);\n const row = { tool: name, state: sibling.kind, minimum: MINIMUM_VERSIONS[name] };\n if (sibling.kind === 'absent') return { ...row, version: null, detail: sibling.reason };\n if (sibling.kind === 'outdated') return { ...row, version: sibling.version, detail: sibling.reason };\n const command = sibling.command.join(' ');\n const detail = sibling.version === null ? `${command} (named in .spec-harness.json; its version is not checked)` : `${command} (${sibling.version})`;\n return { ...row, version: sibling.version, detail };\n });\n const named = namedId(workspace, options, io.env);\n // A ruling counts only by a signature checked against the allowed signers\n // on the base, and only once spec-brief's archive asks the plugin about it.\n const base = await resolveBase(workspace, options.base);\n const signersFile = workspace.config.rulings.allowedSigners;\n // An unresolved base has no commit: git shows nothing for one, so asking\n // anyway reads as no file, and that mutant is equivalent.\n const signersText = base.kind === 'resolved' ? await show(base.sha, signersFile, workspace.root) : null;\n const onBase = base.kind === 'resolved' ? signersText !== null : null;\n const signers = describeSigners(signersFile, base.kind === 'resolved' ? base.ref : null, onBase === true);\n // Which keys sign, as notes: a key that is not FIDO2 can still be one the\n // agent cannot use, and a PIV key reads as a plain one (ADR-0006).\n const read = readAllowedSigners(signersText ?? '');\n const plain = notFido2(read.signers);\n const signerNotes = [describeSignerKeys(plain), ...read.problems.map(describeSignerProblem)].filter((note): note is string => note !== null);\n const plugin = await specBriefPlugin(workspace.root);\n // The plugin and init's entries both on: Claude Code runs every guard twice\n // and registers the server twice (ADR-0012).\n const wiring = await claudeCodeWiring(workspace.root, io.env);\n const claude = wiringState(wiring);\n // Only a Claude Code that runs the guard is asked its release: one older\n // than the hooks need lets every write through unguarded.\n const release = claude === 'none' ? null : checkClaudeCode(await claudeVersion(io.env));\n const { hook } = await gitHook(workspace.root);\n const branch = workspace.branch === null ? '(detached)' : `${workspace.branch}${workspace.branchSource === undefined ? '' : ` (detached; ${workspace.branchSource} names it)`}`;\n if (options.format === 'json') {\n io.stdout.write(\n json('doctor', {\n root: workspace.root,\n branch: workspace.branch,\n branchSource: workspace.branchSource ?? null,\n brief: named,\n base: base.kind === 'resolved' ? { ref: base.ref, source: base.source, mergeBase: base.mergeBase } : { unresolved: base.reason },\n allowedSigners: {\n file: signersFile,\n onBase,\n detail: signers,\n notFido2: plain.map(({ line, principals, keyType }) => ({ line, principals, keyType })),\n problems: read.problems,\n },\n plugin: { state: plugin.kind, file: 'file' in plugin ? plugin.file : null, detail: describePlugin(plugin) },\n claudeCode: {\n state: claude,\n ...wiring,\n detail: describeClaudeCode(wiring),\n release:\n release === null\n ? { state: 'unchecked', version: null, minimum: CLAUDE_CODE_MINIMUM, detail: 'not asked: nothing wires Claude Code to the guard here' }\n : { state: release.state, version: 'version' in release ? release.version : null, minimum: CLAUDE_CODE_MINIMUM, detail: describeClaudeRelease(release) },\n },\n gitHook: { state: hook.state, file: 'file' in hook ? hook.file : null, detail: describeGitHook(hook) },\n siblings: rows,\n }),\n );\n } else {\n const more = (notes: readonly string[]): string[] => notes.map((note) => ` ${note}`);\n io.stdout.write(\n [\n `root ${workspace.root}`,\n `branch ${branch}`,\n `brief ${named ?? '(none named)'}`,\n `base ${describeBase(base)}`,\n `signers ${signers}`,\n ...more(signerNotes),\n `plugin ${describePlugin(plugin)}`,\n `claude ${describeClaudeCode(wiring)}`,\n ...more(release === null ? [] : [describeClaudeRelease(release)]),\n `git ${describeGitHook(hook)}`,\n '',\n '',\n ].join('\\n'),\n );\n for (const row of rows) io.stdout.write(`${row.state.padEnd(8)} ${row.tool.padEnd(10)} ${row.detail}\\n`);\n }\n // An outdated sibling is a problem to fix, where a missing optional one is\n // a choice; with none installed, the harness can check nothing. A double\n // install is a problem too, where no Claude Code wiring at all is a choice,\n // and so is a Claude Code too old to run the hooks. One whose release\n // cannot be told is not fine either: --strict fails it.\n const outdated = rows.some((row) => row.state === 'outdated');\n const usable = rows.some((row) => row.state === 'found');\n const tooOld = release?.state === 'outdated';\n const untold = options.strict && release?.state === 'unknown';\n return outdated || !usable || claude === 'both' || tooOld || untold ? EXIT_FAILED : EXIT_OK;\n}\n\n/* ---------------------------------------------------------------- dispatch */\n\nconst COMMANDS: Readonly<Record<string, (options: Options, io: CliIO) => Promise<number>>> = {\n guard: guardCommand,\n hook: hookCommand,\n context: contextCommand,\n audit: auditCommand,\n escalate: escalateCommand,\n rule: ruleCommand,\n rulings: rulingsCommand,\n probe: probeCommand,\n premises: premisesCommand,\n init: initCommand,\n mcp: mcpCommand,\n doctor: doctorCommand,\n};\n\nexport async function run(argv: readonly string[], io: CliIO): Promise<number> {\n let options: Options;\n try {\n options = parseOptions(argv);\n } catch (error) {\n io.stderr.write(`spec-harness: ${(error as Error).message}\\n`);\n return EXIT_ERROR;\n }\n if (options.version) {\n io.stdout.write(`${version()}\\n`);\n return EXIT_OK;\n }\n if (options.help || options.command === undefined) {\n io.stdout.write(HELP);\n return options.help ? EXIT_OK : EXIT_ERROR;\n }\n const command = COMMANDS[options.command];\n if (command === undefined) {\n io.stderr.write(`spec-harness: unknown command \"${options.command}\"; see spec-harness --help\\n`);\n return EXIT_ERROR;\n }\n try {\n return await command(options, io);\n } catch (error) {\n // A sibling's document of a shape the harness does not read is a sibling\n // that printed something it cannot read (answers.ts): exit 2.\n if (error instanceof UsageError || error instanceof ConfigError || error instanceof SiblingError || error instanceof SiblingOutputError) {\n io.stderr.write(`spec-harness: ${error.message}\\n`);\n return EXIT_ERROR;\n }\n throw error;\n }\n}\n\n// What bin/spec-harness.js runs; the suite spawns the built one, from dist/,\n// outside the sweep (tests/integration/cli.test.ts), so it survives untested\n// in-process.\nexport async function main(argv: readonly string[] = process.argv.slice(2)): Promise<number> {\n return run(argv, { stdout: process.stdout, stderr: process.stderr, cwd: process.cwd(), env: process.env });\n}\n\n"]}
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The command line: arguments, dispatch, output and exit codes.\n *\n * Exit 0: done, nothing refused. Exit 1: something refused or found. Exit 2:\n * the answer cannot be trusted - a bad flag, a configuration that does not\n * load, a sibling that is missing or printed something unreadable. A run that\n * could not look must never exit as though it looked and found nothing\n * (spec-core ADR-0005).\n */\n\nimport { SiblingOutputError } from './briefs.js';\nimport { auditCommand, contextCommand, escalateCommand, probeCommand, ruleCommand, rulingsCommand } from './commands.js';\nimport { ConfigError, SIBLINGS } from './config.js';\nimport {\n describeBase,\n describeClaudeCode,\n describeClaudeRelease,\n describeGitHook,\n describePlugin,\n describeSignerKeys,\n describeSignerProblem,\n describeSigners,\n wiringState,\n} from './configure.js';\nimport { show, stagedChanges } from './git.js';\nimport type { Decision } from './guard.js';\nimport { claudeResponse, gitResponse, parseClaudeHook } from './hooks.js';\nimport { claudeVersion } from './host.js';\nimport { premisesCommand } from './premises.js';\nimport { createReader } from './reader.js';\nimport { checkPaths, claudeCodeWiring, resolveBase, specBriefPlugin } from './round.js';\nimport { mcpCommand } from './server.js';\nimport { gitHook, initCommand } from './setup.js';\nimport { notFido2, readAllowedSigners } from './signers.js';\nimport { SiblingError } from './siblings.js';\nimport { checkClaudeCode, CLAUDE_CODE_MINIMUM, MINIMUM_VERSIONS } from './versions.js';\nimport {\n activeBrief,\n describeActive,\n EXIT_ERROR,\n EXIT_FAILED,\n EXIT_OK,\n json,\n namedId,\n openWorkspace,\n parseOptions,\n UsageError,\n version,\n type CliIO,\n type Options,\n type Workspace,\n} from './workspace.js';\n\nexport { EXIT_ERROR, EXIT_FAILED, EXIT_OK, UsageError, version, type CliIO } from './workspace.js';\n\nexport const HELP = `spec-harness - context, guards, audits and rulings for rounds of agent work\n\nUsage:\n spec-harness <command> [options]\n\nCommands:\n context [brief] What an agent needs to start a round: the brief, its scope,\n rulings, dependencies, the rules in force, the cited documents.\n guard <path...> May the round write these paths? Exit 1 if one is refused.\n hook claude Answer a Claude Code PreToolUse or PostToolUse hook (stdin).\n hook git Answer git's pre-commit hook for the staged files.\n escalate Ask a person to rule: --path <file> (repeatable) --reason <why>\n [--option \"<label>: <cost>\"]... [--recommend <text>].\n --list shows what waits; --show <id> prints one memo.\n rule <id> Record a person's ruling in the brief: --allow or --deny, with\n --note. It counts once committed signed.\n rulings [brief] The brief's rulings, and whether each one's signature verifies.\n audit [brief] Did the round stay inside the lines: what the archive would say,\n the brief's assertions, unverified rulings, new dependencies.\n probe [brief] Run the brief's probes: red at the base, green at the head.\n --at base|head|both, --id <probe>.\n premises Do the live briefs' premises still hold? For CI: exit 1 when one\n is stale, or, with --strict, when spec-guard cannot read one.\n init Configure the spec-* tools to agree. Prints the plan; --write\n applies it; --git-hook adds a pre-commit hook.\n mcp Serve start_round, check_path, request_escalation, audit_round,\n list_rounds and the workflow prompts over MCP on stdio.\n doctor Which siblings are installed, at which versions, which brief is\n named, how Claude Code runs the guard, and git's hook. Exit 1\n when a sibling is older than this release needs, the guard is\n installed twice, or Claude Code is too old to run its hooks.\n\nOptions:\n --brief <id> The brief the round works on. Otherwise SPEC_BRIEF, then the branch.\n --base <ref> What the round is measured from. Otherwise \"base\" in\n .spec-harness.json, then the remote's default branch.\n --root <dir> Run from another directory.\n --format <fmt> pretty or json; audit and premises also gitlab, sarif or github.\n --strict Warnings fail the run.\n --help, --version\n\nExit codes: 0 clean, 1 refused or found something, 2 the answer cannot be trusted.\n`;\n\n\n/* ------------------------------------------------------------------- guard */\n\nconst reader = createReader();\n\nasync function decisionsFor(\n workspace: Workspace,\n options: Options,\n io: CliIO,\n given: readonly string[],\n cwd: string,\n): Promise<{ readonly decisions: Decision[] } | { readonly problem: string }> {\n const active = await activeBrief(workspace, options, io.env);\n const { brief, note, problem } = describeActive(active);\n if (problem !== null) return { problem };\n return { decisions: await checkPaths(workspace, brief, note, given, cwd, reader, options.base) };\n}\n\nconst SYMBOL: Record<Decision['verdict'], string> = { allow: 'ok ', warn: 'warning', ask: 'ask ', deny: 'refused' };\n\nasync function guardCommand(options: Options, io: CliIO): Promise<number> {\n if (options.positionals.length === 0) throw new UsageError('guard needs at least one path');\n const workspace = await openWorkspace(options, io);\n const decided = await decisionsFor(workspace, options, io, options.positionals, io.cwd);\n if ('problem' in decided) {\n io.stderr.write(`spec-harness: ${decided.problem}\\n`);\n return EXIT_ERROR;\n }\n const { decisions } = decided;\n if (options.format === 'json') {\n io.stdout.write(json('guard', { decisions }));\n } else {\n for (const decision of decisions) {\n io.stdout.write(`${SYMBOL[decision.verdict]} ${decision.message}\\n`);\n if (decision.verdict !== 'allow') io.stdout.write(` ${decision.hint}\\n`);\n }\n }\n const refused = decisions.some((d) => d.verdict === 'deny' || d.verdict === 'ask');\n const warned = decisions.some((d) => d.verdict === 'warn');\n return refused || (options.strict && warned) ? EXIT_FAILED : EXIT_OK;\n}\n\n/* -------------------------------------------------------------------- hook */\n\nasync function readStdin(io: CliIO): Promise<string> {\n // Only the built command line reads process.stdin, and the suite spawns it\n // from dist/, outside the sweep (tests/integration/claude-code.test.ts): the\n // lines below survive it untested in-process.\n if (io.stdin !== undefined) return io.stdin();\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) chunks.push(chunk as Buffer);\n return Buffer.concat(chunks).toString('utf8');\n}\n\nasync function hookCommand(options: Options, io: CliIO): Promise<number> {\n const kind = options.positionals[0];\n if (kind === 'claude') {\n const request = parseClaudeHook(await readStdin(io));\n if ('error' in request) {\n // A hook that cannot read its question refuses nothing it cannot see,\n // but says so where the person reads it: a non-blocking error.\n io.stderr.write(`spec-harness: ${request.error}\\n`);\n return EXIT_FAILED;\n }\n if (request.paths.length === 0) return EXIT_OK;\n const cwd = request.cwd ?? io.cwd;\n let workspace: Workspace;\n try {\n workspace = await openWorkspace({ ...options, root: options.root ?? io.env['CLAUDE_PROJECT_DIR'] ?? cwd }, io);\n } catch (error) {\n if (error instanceof UsageError) return EXIT_OK;\n throw error;\n }\n try {\n const decided = await decisionsFor(workspace, options, io, request.paths, cwd);\n if ('problem' in decided) {\n // Exit 2 blocks the tool call, and its stderr reaches the model.\n io.stderr.write(`spec-harness: ${decided.problem}. Fix the brief or the branch before writing.\\n`);\n return EXIT_ERROR;\n }\n const response = claudeResponse(request.event, decided.decisions);\n io.stdout.write(response.stdout);\n return response.exitCode;\n } catch (error) {\n if (request.event !== 'PreToolUse') throw error;\n // A round is named and its briefs cannot be read: the guard cannot\n // tell a protected file from any other, so the write waits.\n io.stderr.write(`spec-harness: cannot check this write: ${(error as Error).message}\\n`);\n return EXIT_ERROR;\n }\n }\n if (kind === 'git') {\n const workspace = await openWorkspace(options, io);\n const staged = await stagedChanges(workspace.root);\n const paths = staged.flatMap((change) => (change.from === undefined ? [change.path] : [change.from, change.path]));\n if (paths.length === 0) return EXIT_OK;\n const decided = await decisionsFor(workspace, options, io, paths, workspace.root);\n if ('problem' in decided) {\n io.stderr.write(`spec-harness: ${decided.problem}\\n`);\n return EXIT_ERROR;\n }\n const response = gitResponse(decided.decisions);\n io.stderr.write(response.text);\n return response.exitCode;\n }\n throw new UsageError('hook takes \"claude\" or \"git\"');\n}\n\n/* ------------------------------------------------------------------ doctor */\n\nasync function doctorCommand(options: Options, io: CliIO): Promise<number> {\n const workspace = await openWorkspace(options, io);\n const rows = SIBLINGS.map((name) => {\n const sibling = workspace.siblings.locate(name);\n const row = { tool: name, state: sibling.kind, minimum: MINIMUM_VERSIONS[name] };\n if (sibling.kind === 'absent') return { ...row, version: null, detail: sibling.reason };\n if (sibling.kind === 'outdated') return { ...row, version: sibling.version, detail: sibling.reason };\n const command = sibling.command.join(' ');\n const detail = sibling.version === null ? `${command} (named in .spec-harness.json; its version is not checked)` : `${command} (${sibling.version})`;\n return { ...row, version: sibling.version, detail };\n });\n const named = namedId(workspace, options, io.env);\n // A ruling counts only by a signature checked against the allowed signers\n // on the base, and only once spec-brief's archive asks the plugin about it.\n const base = await resolveBase(workspace, options.base);\n const signersFile = workspace.config.rulings.allowedSigners;\n // An unresolved base has no commit: git shows nothing for one, so asking\n // anyway reads as no file, and that mutant is equivalent.\n const signersText = base.kind === 'resolved' ? await show(base.sha, signersFile, workspace.root) : null;\n const onBase = base.kind === 'resolved' ? signersText !== null : null;\n const signers = describeSigners(signersFile, base.kind === 'resolved' ? base.ref : null, onBase === true);\n // Which keys sign, as notes: a key that is not FIDO2 can still be one the\n // agent cannot use, and a PIV key reads as a plain one (ADR-0006).\n const read = readAllowedSigners(signersText ?? '');\n const plain = notFido2(read.signers);\n const signerNotes = [describeSignerKeys(plain), ...read.problems.map(describeSignerProblem)].filter((note): note is string => note !== null);\n const plugin = await specBriefPlugin(workspace.root);\n // The plugin and init's entries both on: Claude Code runs every guard twice\n // and registers the server twice (ADR-0012).\n const wiring = await claudeCodeWiring(workspace.root, io.env);\n const claude = wiringState(wiring);\n // Only a Claude Code that runs the guard is asked its release: one older\n // than the hooks need lets every write through unguarded.\n const release = claude === 'none' ? null : checkClaudeCode(await claudeVersion(io.env));\n const { hook } = await gitHook(workspace.root);\n const branch = workspace.branch === null ? '(detached)' : `${workspace.branch}${workspace.branchSource === undefined ? '' : ` (detached; ${workspace.branchSource} names it)`}`;\n if (options.format === 'json') {\n io.stdout.write(\n json('doctor', {\n root: workspace.root,\n branch: workspace.branch,\n branchSource: workspace.branchSource ?? null,\n brief: named,\n base: base.kind === 'resolved' ? { ref: base.ref, source: base.source, mergeBase: base.mergeBase } : { unresolved: base.reason },\n allowedSigners: {\n file: signersFile,\n onBase,\n detail: signers,\n notFido2: plain.map(({ line, principals, keyType }) => ({ line, principals, keyType })),\n problems: read.problems,\n },\n plugin: { state: plugin.kind, file: 'file' in plugin ? plugin.file : null, detail: describePlugin(plugin) },\n claudeCode: {\n state: claude,\n ...wiring,\n detail: describeClaudeCode(wiring),\n release:\n release === null\n ? { state: 'unchecked', version: null, minimum: CLAUDE_CODE_MINIMUM, detail: 'not asked: nothing wires Claude Code to the guard here' }\n : { state: release.state, version: 'version' in release ? release.version : null, minimum: CLAUDE_CODE_MINIMUM, detail: describeClaudeRelease(release) },\n },\n gitHook: { state: hook.state, file: 'file' in hook ? hook.file : null, detail: describeGitHook(hook) },\n siblings: rows,\n }),\n );\n } else {\n const more = (notes: readonly string[]): string[] => notes.map((note) => ` ${note}`);\n io.stdout.write(\n [\n `root ${workspace.root}`,\n `branch ${branch}`,\n `brief ${named ?? '(none named)'}`,\n `base ${describeBase(base)}`,\n `signers ${signers}`,\n ...more(signerNotes),\n `plugin ${describePlugin(plugin)}`,\n `claude ${describeClaudeCode(wiring)}`,\n ...more(release === null ? [] : [describeClaudeRelease(release)]),\n `git ${describeGitHook(hook)}`,\n '',\n '',\n ].join('\\n'),\n );\n for (const row of rows) io.stdout.write(`${row.state.padEnd(8)} ${row.tool.padEnd(10)} ${row.detail}\\n`);\n }\n // An outdated sibling is a problem to fix, where a missing optional one is\n // a choice; with none installed, the harness can check nothing. A double\n // install is a problem too, where no Claude Code wiring at all is a choice,\n // and so is a Claude Code too old to run the hooks. One whose release\n // cannot be told is not fine either: --strict fails it.\n const outdated = rows.some((row) => row.state === 'outdated');\n const usable = rows.some((row) => row.state === 'found');\n const tooOld = release?.state === 'outdated';\n const untold = options.strict && release?.state === 'unknown';\n return outdated || !usable || claude === 'both' || tooOld || untold ? EXIT_FAILED : EXIT_OK;\n}\n\n/* ---------------------------------------------------------------- dispatch */\n\n/**\n * A map, because the command is the first word a person types and an object\n * answers to more names than it was given: `spec-harness constructor` ran\n * `Object` as a command and ended on a stack trace, where `spec-harness\n * construct` is an unknown command with exit 2.\n */\nconst COMMANDS: ReadonlyMap<string, (options: Options, io: CliIO) => Promise<number>> = new Map([\n ['guard', guardCommand],\n ['hook', hookCommand],\n ['context', contextCommand],\n ['audit', auditCommand],\n ['escalate', escalateCommand],\n ['rule', ruleCommand],\n ['rulings', rulingsCommand],\n ['probe', probeCommand],\n ['premises', premisesCommand],\n ['init', initCommand],\n ['mcp', mcpCommand],\n ['doctor', doctorCommand],\n]);\n\nexport async function run(argv: readonly string[], io: CliIO): Promise<number> {\n let options: Options;\n try {\n options = parseOptions(argv);\n } catch (error) {\n io.stderr.write(`spec-harness: ${(error as Error).message}\\n`);\n return EXIT_ERROR;\n }\n if (options.version) {\n io.stdout.write(`${version()}\\n`);\n return EXIT_OK;\n }\n if (options.help || options.command === undefined) {\n io.stdout.write(HELP);\n return options.help ? EXIT_OK : EXIT_ERROR;\n }\n const command = COMMANDS.get(options.command);\n if (command === undefined) {\n io.stderr.write(`spec-harness: unknown command \"${options.command}\"; see spec-harness --help\\n`);\n return EXIT_ERROR;\n }\n try {\n return await command(options, io);\n } catch (error) {\n // A sibling's document of a shape the harness does not read is a sibling\n // that printed something it cannot read (answers.ts): exit 2.\n if (error instanceof UsageError || error instanceof ConfigError || error instanceof SiblingError || error instanceof SiblingOutputError) {\n io.stderr.write(`spec-harness: ${error.message}\\n`);\n return EXIT_ERROR;\n }\n throw error;\n }\n}\n\n// What bin/spec-harness.js runs; the suite spawns the built one, from dist/,\n// outside the sweep (tests/integration/cli.test.ts), so it survives untested\n// in-process.\nexport async function main(argv: readonly string[] = process.argv.slice(2)): Promise<number> {\n return run(argv, { stdout: process.stdout, stderr: process.stderr, cwd: process.cwd(), env: process.env });\n}\n\n"]}
|
package/dist/formats.js
CHANGED
|
@@ -59,7 +59,9 @@ export function formatGitlab(findings, options) {
|
|
|
59
59
|
function describeRule(id) {
|
|
60
60
|
if (id.startsWith('archive/'))
|
|
61
61
|
return `A reason spec-brief's archive gives: ${id.slice('archive/'.length)}.`;
|
|
62
|
-
|
|
62
|
+
// Its own rules only: a finding a caller made may name any rule, and every
|
|
63
|
+
// object answers to `constructor`, which is no sentence.
|
|
64
|
+
return Object.hasOwn(RULES, id) ? RULES[id] : id;
|
|
63
65
|
}
|
|
64
66
|
/**
|
|
65
67
|
* SARIF 2.1.0, for code scanning. What the run measured is a note in the
|
package/dist/formats.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formats.js","sourceRoot":"","sources":["../src/formats.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Findings in the formats a forge reads beside a change: GitLab Code Quality,\n * SARIF 2.1.0 and GitHub workflow commands (spec-core ADR-0005).\n *\n * The shapes are the siblings': spec-brief's severities and its hint in the\n * text, spec-guard's fingerprint of a finding's identity. Pure: the command\n * prints what these return.\n */\n\nimport { createHash } from 'node:crypto';\n\nimport { RULES } from './audit.js';\nimport type { Finding, Severity } from './types.js';\n\n/** The formats that place findings, which `audit` and `premises` print. */\nexport type FindingFormat = 'gitlab' | 'sarif' | 'github';\n\nexport const FINDING_FORMATS: readonly FindingFormat[] = ['gitlab', 'sarif', 'github'];\n\nexport interface FormatOptions {\n /** Where a finding with no file of its own is placed: the brief the command is about. */\n readonly file: string;\n /** This tool's version, which SARIF names. */\n readonly version: string;\n /** What the run measured, which SARIF carries as a note beside its results. */\n readonly summary?: string | undefined;\n}\n\n/**\n * GitLab's severities. An error fails the run, but it is not the crash or the\n * security hole `critical` and `blocker` say in GitLab's own reports, so the\n * scale tops out at `major`, as spec-brief's does.\n */\nconst GITLAB_SEVERITY: Readonly<Record<Severity, string>> = { error: 'major', warning: 'minor', note: 'info' };\nconst SARIF_LEVEL: Readonly<Record<Severity, string>> = { error: 'error', warning: 'warning', note: 'note' };\nconst GITHUB_COMMAND: Readonly<Record<Severity, string>> = { error: 'error', warning: 'warning', note: 'notice' };\n\ninterface Placed {\n readonly finding: Finding;\n readonly file: string;\n readonly line: number;\n readonly fingerprint: string;\n}\n\n/**\n * Each finding with its place and its fingerprint. A finding about a whole\n * brief is placed on the brief's first line. The fingerprint is the SHA-256\n * of the finding's identity - its rule, its file and its subject - and never\n * of its message, hint or line: GitLab tells a new finding from one it has\n * seen by it, so a reworded message or a line added above must not read as\n * one problem fixed and another found. Two findings with one identity are\n * told apart by the order they come in.\n */\nfunction place(findings: readonly Finding[], fallback: string): Placed[] {\n const seen = new Map<string, number>();\n return findings.map((finding) => {\n const file = finding.file ?? fallback;\n const identity = [finding.rule, file, finding.subject ?? ''].join('\\u0000');\n const repeat = seen.get(identity) ?? 0;\n seen.set(identity, repeat + 1);\n const fingerprint = createHash('sha256').update(repeat === 0 ? identity : `${identity}\\u0000${repeat}`).digest('hex');\n return { finding, file, line: Math.max(finding.line ?? 1, 1), fingerprint };\n });\n}\n\n/**\n * The message and the next action, which every format carries (spec-core\n * ADR-0005). A message a sibling ended with a full stop keeps one.\n */\nfunction text(finding: Finding): string {\n return `${finding.message.replace(/\\.$/, '')}. ${finding.hint}`;\n}\n\n/** GitLab Code Quality: a JSON array of issues, which a merge request shows beside the lines they are on. */\nexport function formatGitlab(findings: readonly Finding[], options: FormatOptions): string {\n const issues = place(findings, options.file).map(({ finding, file, line, fingerprint }) => ({\n description: text(finding),\n check_name: finding.rule,\n fingerprint,\n severity: GITLAB_SEVERITY[finding.severity],\n location: { path: file, lines: { begin: line } },\n }));\n return `${JSON.stringify(issues, null, 2)}\\n`;\n}\n\n/** How SARIF describes a rule: the audit's own words, or the archive rule spec-brief gave. */\nfunction describeRule(id: string): string {\n if (id.startsWith('archive/')) return `A reason spec-brief's archive gives: ${id.slice('archive/'.length)}.`;\n return RULES[id] ?? id;\n}\n\n/**\n * SARIF 2.1.0, for code scanning. What the run measured is a note in the\n * invocation, SARIF's place for what happened in a run that is not a finding,\n * so a page with no result still says what was checked.\n */\nexport function formatSarif(findings: readonly Finding[], options: FormatOptions): string {\n const placed = place(findings, options.file);\n const ids = [...new Set(findings.map((finding) => finding.rule))].sort();\n const document = {\n $schema: 'https://json.schemastore.org/sarif-2.1.0.json',\n version: '2.1.0',\n runs: [\n {\n ...(options.summary === undefined\n ? {}\n : { invocations: [{ executionSuccessful: true, toolExecutionNotifications: [{ level: 'note', message: { text: options.summary } }] }] }),\n tool: {\n driver: {\n name: 'spec-harness',\n version: options.version,\n informationUri: 'https://github.com/DescentVTT/spec-harness',\n rules: ids.map((id) => ({ id, shortDescription: { text: describeRule(id) } })),\n },\n },\n results: placed.map(({ finding, file, line, fingerprint }) => ({\n ruleId: finding.rule,\n level: SARIF_LEVEL[finding.severity],\n message: { text: text(finding) },\n locations: [{ physicalLocation: { artifactLocation: { uri: file, uriBaseId: '%SRCROOT%' }, region: { startLine: line } } }],\n partialFingerprints: { specHarnessFinding: fingerprint },\n })),\n },\n ],\n };\n return `${JSON.stringify(document, null, 2)}\\n`;\n}\n\n/** A workflow command's message: `%`, and the line breaks that would end the command. */\nfunction escapeData(value: string): string {\n return value.replace(/%/g, '%25').replace(/\\r/g, '%0D').replace(/\\n/g, '%0A');\n}\n\n/** A workflow command's property: as a message, and the `:` and `,` that would end the property. */\nfunction escapeProperty(value: string): string {\n return escapeData(value).replace(/:/g, '%3A').replace(/,/g, '%2C');\n}\n\n/** GitHub Actions workflow commands, one line each, which a job's log turns into annotations on the diff. */\nexport function formatGithub(findings: readonly Finding[], options: FormatOptions): string {\n return place(findings, options.file)\n .map(({ finding, file, line }) => {\n const properties = `file=${escapeProperty(file)},line=${line},title=${escapeProperty(`spec-harness ${finding.rule}`)}`;\n return `::${GITHUB_COMMAND[finding.severity]} ${properties}::${escapeData(text(finding))}\\n`;\n })\n .join('');\n}\n\nexport function formatFindings(format: FindingFormat, findings: readonly Finding[], options: FormatOptions): string {\n switch (format) {\n case 'gitlab':\n return formatGitlab(findings, options);\n case 'sarif':\n return formatSarif(findings, options);\n case 'github':\n return formatGithub(findings, options);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"formats.js","sourceRoot":"","sources":["../src/formats.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Findings in the formats a forge reads beside a change: GitLab Code Quality,\n * SARIF 2.1.0 and GitHub workflow commands (spec-core ADR-0005).\n *\n * The shapes are the siblings': spec-brief's severities and its hint in the\n * text, spec-guard's fingerprint of a finding's identity. Pure: the command\n * prints what these return.\n */\n\nimport { createHash } from 'node:crypto';\n\nimport { RULES } from './audit.js';\nimport type { Finding, Severity } from './types.js';\n\n/** The formats that place findings, which `audit` and `premises` print. */\nexport type FindingFormat = 'gitlab' | 'sarif' | 'github';\n\nexport const FINDING_FORMATS: readonly FindingFormat[] = ['gitlab', 'sarif', 'github'];\n\nexport interface FormatOptions {\n /** Where a finding with no file of its own is placed: the brief the command is about. */\n readonly file: string;\n /** This tool's version, which SARIF names. */\n readonly version: string;\n /** What the run measured, which SARIF carries as a note beside its results. */\n readonly summary?: string | undefined;\n}\n\n/**\n * GitLab's severities. An error fails the run, but it is not the crash or the\n * security hole `critical` and `blocker` say in GitLab's own reports, so the\n * scale tops out at `major`, as spec-brief's does.\n */\nconst GITLAB_SEVERITY: Readonly<Record<Severity, string>> = { error: 'major', warning: 'minor', note: 'info' };\nconst SARIF_LEVEL: Readonly<Record<Severity, string>> = { error: 'error', warning: 'warning', note: 'note' };\nconst GITHUB_COMMAND: Readonly<Record<Severity, string>> = { error: 'error', warning: 'warning', note: 'notice' };\n\ninterface Placed {\n readonly finding: Finding;\n readonly file: string;\n readonly line: number;\n readonly fingerprint: string;\n}\n\n/**\n * Each finding with its place and its fingerprint. A finding about a whole\n * brief is placed on the brief's first line. The fingerprint is the SHA-256\n * of the finding's identity - its rule, its file and its subject - and never\n * of its message, hint or line: GitLab tells a new finding from one it has\n * seen by it, so a reworded message or a line added above must not read as\n * one problem fixed and another found. Two findings with one identity are\n * told apart by the order they come in.\n */\nfunction place(findings: readonly Finding[], fallback: string): Placed[] {\n const seen = new Map<string, number>();\n return findings.map((finding) => {\n const file = finding.file ?? fallback;\n const identity = [finding.rule, file, finding.subject ?? ''].join('\\u0000');\n const repeat = seen.get(identity) ?? 0;\n seen.set(identity, repeat + 1);\n const fingerprint = createHash('sha256').update(repeat === 0 ? identity : `${identity}\\u0000${repeat}`).digest('hex');\n return { finding, file, line: Math.max(finding.line ?? 1, 1), fingerprint };\n });\n}\n\n/**\n * The message and the next action, which every format carries (spec-core\n * ADR-0005). A message a sibling ended with a full stop keeps one.\n */\nfunction text(finding: Finding): string {\n return `${finding.message.replace(/\\.$/, '')}. ${finding.hint}`;\n}\n\n/** GitLab Code Quality: a JSON array of issues, which a merge request shows beside the lines they are on. */\nexport function formatGitlab(findings: readonly Finding[], options: FormatOptions): string {\n const issues = place(findings, options.file).map(({ finding, file, line, fingerprint }) => ({\n description: text(finding),\n check_name: finding.rule,\n fingerprint,\n severity: GITLAB_SEVERITY[finding.severity],\n location: { path: file, lines: { begin: line } },\n }));\n return `${JSON.stringify(issues, null, 2)}\\n`;\n}\n\n/** How SARIF describes a rule: the audit's own words, or the archive rule spec-brief gave. */\nfunction describeRule(id: string): string {\n if (id.startsWith('archive/')) return `A reason spec-brief's archive gives: ${id.slice('archive/'.length)}.`;\n // Its own rules only: a finding a caller made may name any rule, and every\n // object answers to `constructor`, which is no sentence.\n return Object.hasOwn(RULES, id) ? (RULES[id] as string) : id;\n}\n\n/**\n * SARIF 2.1.0, for code scanning. What the run measured is a note in the\n * invocation, SARIF's place for what happened in a run that is not a finding,\n * so a page with no result still says what was checked.\n */\nexport function formatSarif(findings: readonly Finding[], options: FormatOptions): string {\n const placed = place(findings, options.file);\n const ids = [...new Set(findings.map((finding) => finding.rule))].sort();\n const document = {\n $schema: 'https://json.schemastore.org/sarif-2.1.0.json',\n version: '2.1.0',\n runs: [\n {\n ...(options.summary === undefined\n ? {}\n : { invocations: [{ executionSuccessful: true, toolExecutionNotifications: [{ level: 'note', message: { text: options.summary } }] }] }),\n tool: {\n driver: {\n name: 'spec-harness',\n version: options.version,\n informationUri: 'https://github.com/DescentVTT/spec-harness',\n rules: ids.map((id) => ({ id, shortDescription: { text: describeRule(id) } })),\n },\n },\n results: placed.map(({ finding, file, line, fingerprint }) => ({\n ruleId: finding.rule,\n level: SARIF_LEVEL[finding.severity],\n message: { text: text(finding) },\n locations: [{ physicalLocation: { artifactLocation: { uri: file, uriBaseId: '%SRCROOT%' }, region: { startLine: line } } }],\n partialFingerprints: { specHarnessFinding: fingerprint },\n })),\n },\n ],\n };\n return `${JSON.stringify(document, null, 2)}\\n`;\n}\n\n/** A workflow command's message: `%`, and the line breaks that would end the command. */\nfunction escapeData(value: string): string {\n return value.replace(/%/g, '%25').replace(/\\r/g, '%0D').replace(/\\n/g, '%0A');\n}\n\n/** A workflow command's property: as a message, and the `:` and `,` that would end the property. */\nfunction escapeProperty(value: string): string {\n return escapeData(value).replace(/:/g, '%3A').replace(/,/g, '%2C');\n}\n\n/** GitHub Actions workflow commands, one line each, which a job's log turns into annotations on the diff. */\nexport function formatGithub(findings: readonly Finding[], options: FormatOptions): string {\n return place(findings, options.file)\n .map(({ finding, file, line }) => {\n const properties = `file=${escapeProperty(file)},line=${line},title=${escapeProperty(`spec-harness ${finding.rule}`)}`;\n return `::${GITHUB_COMMAND[finding.severity]} ${properties}::${escapeData(text(finding))}\\n`;\n })\n .join('');\n}\n\nexport function formatFindings(format: FindingFormat, findings: readonly Finding[], options: FormatOptions): string {\n switch (format) {\n case 'gitlab':\n return formatGitlab(findings, options);\n case 'sarif':\n return formatSarif(findings, options);\n case 'github':\n return formatGithub(findings, options);\n }\n}\n"]}
|
package/dist/junit.js
CHANGED
|
@@ -10,7 +10,18 @@
|
|
|
10
10
|
* attributes, entity-aware, that skips everything else, and it refuses text
|
|
11
11
|
* it cannot read rather than guessing at it.
|
|
12
12
|
*/
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* A map, because the name comes from a report and an object answers to more
|
|
15
|
+
* names than it was given: `&constructor;` in a test's name was replaced with
|
|
16
|
+
* the source of a function.
|
|
17
|
+
*/
|
|
18
|
+
const ENTITIES = new Map([
|
|
19
|
+
['amp', '&'],
|
|
20
|
+
['lt', '<'],
|
|
21
|
+
['gt', '>'],
|
|
22
|
+
['quot', '"'],
|
|
23
|
+
['apos', "'"],
|
|
24
|
+
]);
|
|
14
25
|
/** Replaces the five named entities and numeric references. An unknown entity stays as written. */
|
|
15
26
|
export function decodeEntities(text) {
|
|
16
27
|
return text.replace(/&(#x[0-9a-fA-F]+|#[0-9]+|[a-zA-Z]+);/g, (whole, body) => {
|
|
@@ -22,7 +33,7 @@ export function decodeEntities(text) {
|
|
|
22
33
|
const point = Number.parseInt(body.slice(1), 10);
|
|
23
34
|
return point <= 0x10ffff ? String.fromCodePoint(point) : whole;
|
|
24
35
|
}
|
|
25
|
-
return ENTITIES
|
|
36
|
+
return ENTITIES.get(body) ?? whole;
|
|
26
37
|
});
|
|
27
38
|
}
|
|
28
39
|
function attributes(text) {
|
package/dist/junit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"junit.js","sourceRoot":"","sources":["../src/junit.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The failures a JUnit XML report records.\n *\n * JUnit XML is the one result format nearly every test runner writes, natively\n * or through a reporter - Vitest, Jest, pytest, `go test` through\n * go-junit-report, `cargo nextest`, `dotnet test` through its logger - so a\n * probe that reads it reads them all (ADR-0007). This reads the part of the\n * format a probe needs: which test cases failed or errored, and with what\n * message. It is not an XML parser; it is a tokenizer for elements and\n * attributes, entity-aware, that skips everything else, and it refuses text\n * it cannot read rather than guessing at it.\n */\n\nexport interface JUnitCase {\n readonly name: string;\n readonly classname: string;\n readonly outcome: 'passed' | 'failed' | 'errored' | 'skipped';\n /** The failure's or error's message and body, joined. */\n readonly message: string;\n}\n\nexport type JUnitRead = { readonly ok: true; readonly cases: readonly JUnitCase[] } | { readonly ok: false; readonly error: string };\n\nconst ENTITIES: Readonly<Record<string, string>> = { amp: '&', lt: '<', gt: '>', quot: '\"', apos: \"'\" };\n\n/** Replaces the five named entities and numeric references. An unknown entity stays as written. */\nexport function decodeEntities(text: string): string {\n return text.replace(/&(#x[0-9a-fA-F]+|#[0-9]+|[a-zA-Z]+);/g, (whole, body: string) => {\n if (body.startsWith('#x')) {\n const point = Number.parseInt(body.slice(2), 16);\n return point <= 0x10ffff ? String.fromCodePoint(point) : whole;\n }\n if (body.startsWith('#')) {\n const point = Number.parseInt(body.slice(1), 10);\n return point <= 0x10ffff ? String.fromCodePoint(point) : whole;\n }\n return ENTITIES[body] ?? whole;\n });\n}\n\nfunction attributes(text: string): Record<string, string> {\n const out: Record<string, string> = {};\n const pattern = /([A-Za-z_:][\\w:.-]*)\\s*=\\s*(\"([^\"]*)\"|'([^']*)')/g;\n for (let match = pattern.exec(text); match !== null; match = pattern.exec(text)) {\n out[match[1] as string] = decodeEntities(match[3] ?? match[4] ?? '');\n }\n return out;\n}\n\ninterface Tag {\n readonly kind: 'open' | 'close' | 'self';\n readonly name: string;\n readonly attributes: Record<string, string>;\n readonly start: number;\n readonly end: number;\n}\n\n/** The tags of a document, comments, CDATA and declarations skipped, in one pass. */\nfunction tags(xml: string): Tag[] | string {\n const out: Tag[] = [];\n let i = 0;\n while (i < xml.length) {\n const open = xml.indexOf('<', i);\n if (open < 0) break;\n if (xml.startsWith('<!--', open)) {\n const close = xml.indexOf('-->', open + 4);\n if (close < 0) return 'a comment is never closed';\n i = close + 3;\n continue;\n }\n if (xml.startsWith('<![CDATA[', open)) {\n const close = xml.indexOf(']]>', open + 9);\n if (close < 0) return 'a CDATA section is never closed';\n i = close + 3;\n continue;\n }\n if (xml.startsWith('<?', open) || xml.startsWith('<!', open)) {\n const close = xml.indexOf('>', open);\n if (close < 0) return 'a declaration is never closed';\n i = close + 1;\n continue;\n }\n let close = open + 1;\n let quote: string | null = null;\n for (; close < xml.length; close += 1) {\n const ch = xml.charAt(close);\n if (quote !== null) {\n if (ch === quote) quote = null;\n } else if (ch === '\"' || ch === \"'\") quote = ch;\n else if (ch === '>') break;\n }\n if (close >= xml.length) return 'a tag is never closed';\n const body = xml.slice(open + 1, close);\n const closing = body.startsWith('/');\n const self = body.endsWith('/');\n const name = /^\\/?\\s*([A-Za-z_:][\\w:.-]*)/.exec(body)?.[1];\n if (name === undefined) return `\"<${body.slice(0, 20)}\" is not a tag`;\n out.push({\n kind: closing ? 'close' : self ? 'self' : 'open',\n name,\n attributes: closing ? {} : attributes(body.slice(name.length + 1)),\n start: open,\n end: close + 1,\n });\n i = close + 1;\n }\n return out;\n}\n\n/**\n * The text between two offsets, tags dropped and entities decoded outside\n * CDATA, and CDATA kept as written: a stack trace's `Object.<anonymous>` or\n * `a < b` inside CDATA is text, not a tag.\n */\nfunction innerText(xml: string, from: number, to: number): string {\n const slice = xml.slice(from, to);\n const markup = (text: string): string => decodeEntities(text.replace(/<[^>]*>/g, ''));\n let out = '';\n let last = 0;\n for (const match of slice.matchAll(/<!\\[CDATA\\[([\\s\\S]*?)\\]\\]>/g)) {\n out += markup(slice.slice(last, match.index)) + (match[1] as string);\n last = match.index + match[0].length;\n }\n return (out + markup(slice.slice(last))).trim();\n}\n\n/** Reads the test cases of a JUnit XML report, nested suites included. */\nexport function readJUnit(xml: string): JUnitRead {\n const list = tags(xml);\n if (typeof list === 'string') return { ok: false, error: list };\n if (!list.some((tag) => tag.name === 'testsuite' || tag.name === 'testsuites')) {\n return { ok: false, error: 'no <testsuite> element: this is not a JUnit report' };\n }\n const cases: JUnitCase[] = [];\n for (let i = 0; i < list.length; i += 1) {\n const tag = list[i] as Tag;\n if (tag.name !== 'testcase' || tag.kind === 'close') continue;\n const name = tag.attributes['name'] ?? '';\n const classname = tag.attributes['classname'] ?? '';\n if (tag.kind === 'self') {\n cases.push({ name, classname, outcome: 'passed', message: '' });\n continue;\n }\n let outcome: JUnitCase['outcome'] = 'passed';\n const messages: string[] = [];\n let j = i + 1;\n for (; j < list.length; j += 1) {\n const inner = list[j] as Tag;\n if (inner.name === 'testcase' && inner.kind === 'close') break;\n if (inner.kind === 'close') continue;\n if (inner.name === 'failure' || inner.name === 'error') {\n outcome = inner.name === 'failure' ? 'failed' : outcome === 'failed' ? 'failed' : 'errored';\n const message = inner.attributes['message'];\n if (message !== undefined && message !== '') messages.push(message);\n if (inner.kind === 'open') {\n const end = list.findIndex((candidate, k) => k > j && candidate.name === inner.name && candidate.kind === 'close');\n if (end > j) {\n const body = innerText(xml, inner.end, (list[end] as Tag).start);\n if (body !== '') messages.push(body);\n }\n }\n } else if (inner.name === 'skipped' && outcome === 'passed') {\n outcome = 'skipped';\n }\n }\n if (j >= list.length) return { ok: false, error: `test case \"${name}\" is never closed` };\n cases.push({ name, classname, outcome, message: messages.join('\\n') });\n i = j;\n }\n return { ok: true, cases };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"junit.js","sourceRoot":"","sources":["../src/junit.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The failures a JUnit XML report records.\n *\n * JUnit XML is the one result format nearly every test runner writes, natively\n * or through a reporter - Vitest, Jest, pytest, `go test` through\n * go-junit-report, `cargo nextest`, `dotnet test` through its logger - so a\n * probe that reads it reads them all (ADR-0007). This reads the part of the\n * format a probe needs: which test cases failed or errored, and with what\n * message. It is not an XML parser; it is a tokenizer for elements and\n * attributes, entity-aware, that skips everything else, and it refuses text\n * it cannot read rather than guessing at it.\n */\n\nexport interface JUnitCase {\n readonly name: string;\n readonly classname: string;\n readonly outcome: 'passed' | 'failed' | 'errored' | 'skipped';\n /** The failure's or error's message and body, joined. */\n readonly message: string;\n}\n\nexport type JUnitRead = { readonly ok: true; readonly cases: readonly JUnitCase[] } | { readonly ok: false; readonly error: string };\n\n/**\n * A map, because the name comes from a report and an object answers to more\n * names than it was given: `&constructor;` in a test's name was replaced with\n * the source of a function.\n */\nconst ENTITIES: ReadonlyMap<string, string> = new Map([\n ['amp', '&'],\n ['lt', '<'],\n ['gt', '>'],\n ['quot', '\"'],\n ['apos', \"'\"],\n]);\n\n/** Replaces the five named entities and numeric references. An unknown entity stays as written. */\nexport function decodeEntities(text: string): string {\n return text.replace(/&(#x[0-9a-fA-F]+|#[0-9]+|[a-zA-Z]+);/g, (whole, body: string) => {\n if (body.startsWith('#x')) {\n const point = Number.parseInt(body.slice(2), 16);\n return point <= 0x10ffff ? String.fromCodePoint(point) : whole;\n }\n if (body.startsWith('#')) {\n const point = Number.parseInt(body.slice(1), 10);\n return point <= 0x10ffff ? String.fromCodePoint(point) : whole;\n }\n return ENTITIES.get(body) ?? whole;\n });\n}\n\nfunction attributes(text: string): Record<string, string> {\n const out: Record<string, string> = {};\n const pattern = /([A-Za-z_:][\\w:.-]*)\\s*=\\s*(\"([^\"]*)\"|'([^']*)')/g;\n for (let match = pattern.exec(text); match !== null; match = pattern.exec(text)) {\n out[match[1] as string] = decodeEntities(match[3] ?? match[4] ?? '');\n }\n return out;\n}\n\ninterface Tag {\n readonly kind: 'open' | 'close' | 'self';\n readonly name: string;\n readonly attributes: Record<string, string>;\n readonly start: number;\n readonly end: number;\n}\n\n/** The tags of a document, comments, CDATA and declarations skipped, in one pass. */\nfunction tags(xml: string): Tag[] | string {\n const out: Tag[] = [];\n let i = 0;\n while (i < xml.length) {\n const open = xml.indexOf('<', i);\n if (open < 0) break;\n if (xml.startsWith('<!--', open)) {\n const close = xml.indexOf('-->', open + 4);\n if (close < 0) return 'a comment is never closed';\n i = close + 3;\n continue;\n }\n if (xml.startsWith('<![CDATA[', open)) {\n const close = xml.indexOf(']]>', open + 9);\n if (close < 0) return 'a CDATA section is never closed';\n i = close + 3;\n continue;\n }\n if (xml.startsWith('<?', open) || xml.startsWith('<!', open)) {\n const close = xml.indexOf('>', open);\n if (close < 0) return 'a declaration is never closed';\n i = close + 1;\n continue;\n }\n let close = open + 1;\n let quote: string | null = null;\n for (; close < xml.length; close += 1) {\n const ch = xml.charAt(close);\n if (quote !== null) {\n if (ch === quote) quote = null;\n } else if (ch === '\"' || ch === \"'\") quote = ch;\n else if (ch === '>') break;\n }\n if (close >= xml.length) return 'a tag is never closed';\n const body = xml.slice(open + 1, close);\n const closing = body.startsWith('/');\n const self = body.endsWith('/');\n const name = /^\\/?\\s*([A-Za-z_:][\\w:.-]*)/.exec(body)?.[1];\n if (name === undefined) return `\"<${body.slice(0, 20)}\" is not a tag`;\n out.push({\n kind: closing ? 'close' : self ? 'self' : 'open',\n name,\n attributes: closing ? {} : attributes(body.slice(name.length + 1)),\n start: open,\n end: close + 1,\n });\n i = close + 1;\n }\n return out;\n}\n\n/**\n * The text between two offsets, tags dropped and entities decoded outside\n * CDATA, and CDATA kept as written: a stack trace's `Object.<anonymous>` or\n * `a < b` inside CDATA is text, not a tag.\n */\nfunction innerText(xml: string, from: number, to: number): string {\n const slice = xml.slice(from, to);\n const markup = (text: string): string => decodeEntities(text.replace(/<[^>]*>/g, ''));\n let out = '';\n let last = 0;\n for (const match of slice.matchAll(/<!\\[CDATA\\[([\\s\\S]*?)\\]\\]>/g)) {\n out += markup(slice.slice(last, match.index)) + (match[1] as string);\n last = match.index + match[0].length;\n }\n return (out + markup(slice.slice(last))).trim();\n}\n\n/** Reads the test cases of a JUnit XML report, nested suites included. */\nexport function readJUnit(xml: string): JUnitRead {\n const list = tags(xml);\n if (typeof list === 'string') return { ok: false, error: list };\n if (!list.some((tag) => tag.name === 'testsuite' || tag.name === 'testsuites')) {\n return { ok: false, error: 'no <testsuite> element: this is not a JUnit report' };\n }\n const cases: JUnitCase[] = [];\n for (let i = 0; i < list.length; i += 1) {\n const tag = list[i] as Tag;\n if (tag.name !== 'testcase' || tag.kind === 'close') continue;\n const name = tag.attributes['name'] ?? '';\n const classname = tag.attributes['classname'] ?? '';\n if (tag.kind === 'self') {\n cases.push({ name, classname, outcome: 'passed', message: '' });\n continue;\n }\n let outcome: JUnitCase['outcome'] = 'passed';\n const messages: string[] = [];\n let j = i + 1;\n for (; j < list.length; j += 1) {\n const inner = list[j] as Tag;\n if (inner.name === 'testcase' && inner.kind === 'close') break;\n if (inner.kind === 'close') continue;\n if (inner.name === 'failure' || inner.name === 'error') {\n outcome = inner.name === 'failure' ? 'failed' : outcome === 'failed' ? 'failed' : 'errored';\n const message = inner.attributes['message'];\n if (message !== undefined && message !== '') messages.push(message);\n if (inner.kind === 'open') {\n const end = list.findIndex((candidate, k) => k > j && candidate.name === inner.name && candidate.kind === 'close');\n if (end > j) {\n const body = innerText(xml, inner.end, (list[end] as Tag).start);\n if (body !== '') messages.push(body);\n }\n }\n } else if (inner.name === 'skipped' && outcome === 'passed') {\n outcome = 'skipped';\n }\n }\n if (j >= list.length) return { ok: false, error: `test case \"${name}\" is never closed` };\n cases.push({ name, classname, outcome, message: messages.join('\\n') });\n i = j;\n }\n return { ok: true, cases };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@descent-vtt/spec-harness",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "The agent-facing member of the spec-* tools: context for a round, a guard on every write, an audit when it ends, probes that prove a defect before it is filed, and rulings a person signs.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -73,8 +73,8 @@
|
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@descent-vtt/spec-brief": "^0.
|
|
77
|
-
"@descent-vtt/spec-guard": "^0.
|
|
76
|
+
"@descent-vtt/spec-brief": "^0.4.1",
|
|
77
|
+
"@descent-vtt/spec-guard": "^0.18.1",
|
|
78
78
|
"@stryker-mutator/core": "^10.0.0",
|
|
79
79
|
"@stryker-mutator/vitest-runner": "^10.0.0",
|
|
80
80
|
"@types/node": "^22.20.4",
|