@descent-vtt/spec-harness 0.6.0 → 0.7.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +85 -0
- package/README.md +273 -24
- package/dist/audit.d.ts +71 -1
- package/dist/audit.js +140 -17
- package/dist/audit.js.map +1 -1
- package/dist/branch.d.ts +13 -0
- package/dist/branch.js +19 -0
- package/dist/branch.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +51 -13
- package/dist/cli.js.map +1 -1
- package/dist/commands.js +11 -4
- package/dist/commands.js.map +1 -1
- package/dist/config.js +3 -1
- package/dist/config.js.map +1 -1
- package/dist/configure.d.ts +50 -3
- package/dist/configure.js +78 -5
- package/dist/configure.js.map +1 -1
- package/dist/context.d.ts +10 -5
- package/dist/context.js +54 -19
- package/dist/context.js.map +1 -1
- package/dist/document.d.ts +2 -1
- package/dist/document.js.map +1 -1
- package/dist/formats.d.ts +31 -0
- package/dist/formats.js +127 -0
- package/dist/formats.js.map +1 -0
- package/dist/git.d.ts +6 -0
- package/dist/git.js +8 -0
- package/dist/git.js.map +1 -1
- package/dist/guard.d.ts +8 -0
- package/dist/guard.js +19 -1
- package/dist/guard.js.map +1 -1
- package/dist/host.d.ts +14 -0
- package/dist/host.js +50 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/manifests.d.ts +8 -1
- package/dist/manifests.js +16 -6
- package/dist/manifests.js.map +1 -1
- package/dist/premises.d.ts +3 -0
- package/dist/premises.js +43 -13
- package/dist/premises.js.map +1 -1
- package/dist/reader.js +10 -2
- package/dist/reader.js.map +1 -1
- package/dist/round.d.ts +8 -0
- package/dist/round.js +73 -22
- package/dist/round.js.map +1 -1
- package/dist/server.js +9 -3
- package/dist/server.js.map +1 -1
- package/dist/setup.d.ts +10 -0
- package/dist/setup.js +68 -32
- package/dist/setup.js.map +1 -1
- package/dist/signers.d.ts +43 -0
- package/dist/signers.js +105 -0
- package/dist/signers.js.map +1 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.js.map +1 -1
- package/dist/vendor/spec-core/markdown/frontmatter.d.ts +11 -1
- package/dist/vendor/spec-core/markdown/frontmatter.js +20 -3
- package/dist/vendor/spec-core/markdown/frontmatter.js.map +1 -1
- package/dist/vendor/spec-core/pattern/glob.d.ts +85 -0
- package/dist/vendor/spec-core/pattern/glob.js +207 -17
- package/dist/vendor/spec-core/pattern/glob.js.map +1 -1
- package/dist/vendor/spec-core/pattern/index.d.ts +2 -2
- package/dist/vendor/spec-core/pattern/index.js +1 -1
- package/dist/vendor/spec-core/pattern/index.js.map +1 -1
- package/dist/vendor/spec-core/text/index.d.ts +1 -0
- package/dist/vendor/spec-core/text/index.js +1 -0
- package/dist/vendor/spec-core/text/index.js.map +1 -1
- package/dist/vendor/spec-core/text/width.d.ts +30 -0
- package/dist/vendor/spec-core/text/width.js +113 -0
- package/dist/vendor/spec-core/text/width.js.map +1 -0
- package/dist/versions.d.ts +27 -1
- package/dist/versions.js +23 -1
- package/dist/versions.js.map +1 -1
- package/dist/workspace.d.ts +6 -1
- package/dist/workspace.js +17 -6
- package/dist/workspace.js.map +1 -1
- package/package.json +1 -1
- package/skills/close-round/SKILL.md +4 -1
package/dist/audit.d.ts
CHANGED
|
@@ -17,7 +17,10 @@
|
|
|
17
17
|
* - And the one question nobody else asks: which dependencies the round
|
|
18
18
|
* added. A dependency is code no reviewer read.
|
|
19
19
|
*
|
|
20
|
-
* A part that could not be measured is a finding, never a silence
|
|
20
|
+
* A part that could not be measured is a finding, never a silence, and what
|
|
21
|
+
* was measured is reported beside what was found: an audit that finds nothing
|
|
22
|
+
* because it checked nothing must not read as one that checked and found
|
|
23
|
+
* nothing (spec-core ADR-0005).
|
|
21
24
|
*/
|
|
22
25
|
import { type VerifiedRuling } from './guard.js';
|
|
23
26
|
import type { DependencyChange } from './manifests.js';
|
|
@@ -32,6 +35,13 @@ export interface AssertionOutcome {
|
|
|
32
35
|
/** Every section it sits in, outermost first; `[section]` when not given. */
|
|
33
36
|
readonly enclosing?: readonly string[] | undefined;
|
|
34
37
|
}
|
|
38
|
+
/** An assertion spec-guard could not read, as it reports it: nothing it states was run. */
|
|
39
|
+
export interface UnreadableAssertion {
|
|
40
|
+
readonly message: string;
|
|
41
|
+
readonly line: number;
|
|
42
|
+
/** The directive as written. */
|
|
43
|
+
readonly raw: string;
|
|
44
|
+
}
|
|
35
45
|
/** A reason spec-brief's archive gives; a `protected-file` refusal names its `path`. */
|
|
36
46
|
export interface ArchiveReason extends Finding {
|
|
37
47
|
readonly path?: string | undefined;
|
|
@@ -48,6 +58,11 @@ export interface AuditInput {
|
|
|
48
58
|
readonly name: string;
|
|
49
59
|
readonly reason: string;
|
|
50
60
|
}[] | undefined;
|
|
61
|
+
/** Names in `dependencies.manifests` a leading `/` roots, and whether every alternative of each is rooted; none when not given. */
|
|
62
|
+
readonly rootedNames?: readonly {
|
|
63
|
+
readonly name: string;
|
|
64
|
+
readonly whole: boolean;
|
|
65
|
+
}[] | undefined;
|
|
51
66
|
};
|
|
52
67
|
readonly archive: {
|
|
53
68
|
readonly blocking: readonly ArchiveReason[];
|
|
@@ -58,6 +73,8 @@ export interface AuditInput {
|
|
|
58
73
|
readonly assertions: readonly AssertionOutcome[] | {
|
|
59
74
|
readonly unavailable: string;
|
|
60
75
|
};
|
|
76
|
+
/** The assertions in the brief spec-guard could not read; none when not given. */
|
|
77
|
+
readonly unreadableAssertions?: readonly UnreadableAssertion[] | undefined;
|
|
61
78
|
/** Section names whose assertions are premises. Compared without case, emphasis, a leading number or a trailing colon. */
|
|
62
79
|
readonly premiseSections: readonly string[];
|
|
63
80
|
readonly unverifiedRulings: readonly {
|
|
@@ -69,10 +86,49 @@ export interface AuditInput {
|
|
|
69
86
|
/** Whether spec-brief's configuration loads this package's plugin, through which its archive learns of them. */
|
|
70
87
|
readonly pluginLoaded: boolean;
|
|
71
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* What the audit measured, beside what it found. A script tells "nothing
|
|
91
|
+
* found" from "nothing checked" here: `assertions: "unavailable"` with no
|
|
92
|
+
* goal failed is an audit that ran no goal.
|
|
93
|
+
*/
|
|
94
|
+
export interface Measured {
|
|
95
|
+
/** Whether the round's changes were measured from a base; the dependencies are read from them. */
|
|
96
|
+
readonly changes: 'measured' | 'unmeasured';
|
|
97
|
+
/** Whether spec-brief's archive answered. */
|
|
98
|
+
readonly archive: 'asked' | 'unavailable';
|
|
99
|
+
/** Whether spec-guard ran the brief's assertions. */
|
|
100
|
+
readonly assertions: 'run' | 'unavailable';
|
|
101
|
+
readonly goals: {
|
|
102
|
+
readonly held: number;
|
|
103
|
+
readonly failed: number;
|
|
104
|
+
};
|
|
105
|
+
readonly premises: {
|
|
106
|
+
readonly retired: number;
|
|
107
|
+
readonly holding: number;
|
|
108
|
+
};
|
|
109
|
+
/** Assertions spec-guard could not read, so ran none of. */
|
|
110
|
+
readonly unreadableAssertions: number;
|
|
111
|
+
/** The rulings that allow something, by whether their signatures verify. */
|
|
112
|
+
readonly rulings: {
|
|
113
|
+
readonly verified: number;
|
|
114
|
+
readonly unverified: number;
|
|
115
|
+
};
|
|
116
|
+
/** Dependencies the round added, removed or moved, and manifests it changed that could not be read. */
|
|
117
|
+
readonly dependencies: {
|
|
118
|
+
readonly changed: number;
|
|
119
|
+
readonly unread: number;
|
|
120
|
+
};
|
|
121
|
+
}
|
|
72
122
|
export interface AuditReport {
|
|
73
123
|
readonly findings: readonly Finding[];
|
|
74
124
|
readonly counts: Readonly<Record<Severity, number>>;
|
|
125
|
+
readonly measured: Measured;
|
|
75
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Each rule the audit and `premises` report, as a reader of SARIF sees it
|
|
129
|
+
* described. A reason spec-brief's archive gives is `archive/<its rule>`.
|
|
130
|
+
*/
|
|
131
|
+
export declare const RULES: Readonly<Record<string, string>>;
|
|
76
132
|
/**
|
|
77
133
|
* Whether an assertion's section is a premise section. Compared as the
|
|
78
134
|
* rulings section is, and as spec-brief compares section names: a heading
|
|
@@ -92,4 +148,18 @@ export declare function premiseFinding(brief: Pick<BriefRow, 'id' | 'file'>, out
|
|
|
92
148
|
readonly message: string;
|
|
93
149
|
readonly line: number;
|
|
94
150
|
}, active: boolean): Finding;
|
|
151
|
+
/**
|
|
152
|
+
* A premise spec-guard cannot read, as `premises` reports it: nothing checks
|
|
153
|
+
* whether it still holds, so a stale brief would pass for a live one. A
|
|
154
|
+
* warning, as the audit's is, which fails the run under `--strict`.
|
|
155
|
+
*/
|
|
156
|
+
export declare function unreadablePremiseFinding(brief: Pick<BriefRow, 'id' | 'file'>, assertion: UnreadableAssertion): Finding;
|
|
95
157
|
export declare function audit(input: AuditInput): AuditReport;
|
|
158
|
+
/** How many findings of each severity. */
|
|
159
|
+
export declare function tally(findings: readonly Finding[]): Record<Severity, number>;
|
|
160
|
+
/**
|
|
161
|
+
* What the audit measured, in one line a person reads above the counts. A
|
|
162
|
+
* brief that declares no assertion says so, and draws no warning: a brief
|
|
163
|
+
* without assertions is a brief, and the archive still measured its round.
|
|
164
|
+
*/
|
|
165
|
+
export declare function describeMeasured(measured: Measured): string;
|
package/dist/audit.js
CHANGED
|
@@ -17,14 +17,49 @@
|
|
|
17
17
|
* - And the one question nobody else asks: which dependencies the round
|
|
18
18
|
* added. A dependency is code no reviewer read.
|
|
19
19
|
*
|
|
20
|
-
* A part that could not be measured is a finding, never a silence
|
|
20
|
+
* A part that could not be measured is a finding, never a silence, and what
|
|
21
|
+
* was measured is reported beside what was found: an audit that finds nothing
|
|
22
|
+
* because it checked nothing must not read as one that checked and found
|
|
23
|
+
* nothing (spec-core ADR-0005).
|
|
21
24
|
*/
|
|
22
25
|
import { CONFIG_FILE } from './config.js';
|
|
23
26
|
import { PLUGIN } from './configure.js';
|
|
24
|
-
import { rulingFor } from './guard.js';
|
|
27
|
+
import { rooted, rulingFor } from './guard.js';
|
|
25
28
|
import { sameSection } from './reader.js';
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Each rule the audit and `premises` report, as a reader of SARIF sees it
|
|
31
|
+
* described. A reason spec-brief's archive gives is `archive/<its rule>`.
|
|
32
|
+
*/
|
|
33
|
+
export const RULES = {
|
|
34
|
+
unmeasured: "The round's changes could not be measured from a base.",
|
|
35
|
+
'archive-unchecked': "spec-brief's archive could not be asked what it would refuse.",
|
|
36
|
+
'assertions-unchecked': "The brief's assertions could not be run.",
|
|
37
|
+
'assertion-unreadable': 'An assertion in the brief spec-guard cannot read, so nothing it states was run.',
|
|
38
|
+
'goal-failed': 'A goal the brief asserts does not hold.',
|
|
39
|
+
'premise-holds': 'A premise still holds after the round meant to change it.',
|
|
40
|
+
'premise-retired': 'A premise no longer holds, as the round on the brief intends.',
|
|
41
|
+
'stale-premise': "A live brief's premise no longer holds: what it was written against has changed.",
|
|
42
|
+
'ruling-unverified': 'A ruling whose signature does not verify, which allows nothing.',
|
|
43
|
+
'ruling-unreadable': 'A row of the rulings table that cannot be read.',
|
|
44
|
+
'protection-rooted': "A protection rooted at the filesystem's root, which protects no path of the repository.",
|
|
45
|
+
'ruling-path-rooted': "A signed ruling's path rooted at the filesystem's root, which allows nothing.",
|
|
46
|
+
'manifest-name-unread': 'A name in dependencies.manifests that cannot be read, which names no manifest.',
|
|
47
|
+
'manifest-name-rooted': "A name in dependencies.manifests rooted at the filesystem's root, where no file of the repository is.",
|
|
48
|
+
'manifest-unread': 'A manifest the round changed that cannot be read for dependencies.',
|
|
49
|
+
'new-dependency': 'A dependency the round added: code no reviewer read.',
|
|
50
|
+
'dependency-removed': 'A dependency the round removed.',
|
|
51
|
+
'dependency-changed': 'A dependency the round moved to another version.',
|
|
52
|
+
};
|
|
53
|
+
function finding(rule, severity, message, hint, file, line, subject) {
|
|
54
|
+
return {
|
|
55
|
+
rule,
|
|
56
|
+
severity,
|
|
57
|
+
message,
|
|
58
|
+
hint,
|
|
59
|
+
...(file === undefined ? {} : { file }),
|
|
60
|
+
...(line === undefined ? {} : { line }),
|
|
61
|
+
...(subject === undefined ? {} : { subject }),
|
|
62
|
+
};
|
|
28
63
|
}
|
|
29
64
|
/**
|
|
30
65
|
* Whether an assertion's section is a premise section. Compared as the
|
|
@@ -47,9 +82,17 @@ export function isPremise(sections, premises) {
|
|
|
47
82
|
*/
|
|
48
83
|
export function premiseFinding(brief, outcome, active) {
|
|
49
84
|
if (active) {
|
|
50
|
-
return finding('premise-retired', 'note', `brief ${brief.id}'s premise no longer holds, as the round on it intends: ${outcome.description}`, 'nothing to do: this is the round that changes it, and audit measures it', brief.file, outcome.line);
|
|
85
|
+
return finding('premise-retired', 'note', `brief ${brief.id}'s premise no longer holds, as the round on it intends: ${outcome.description}`, 'nothing to do: this is the round that changes it, and audit measures it', brief.file, outcome.line, outcome.description);
|
|
51
86
|
}
|
|
52
|
-
return finding('stale-premise', 'error', `brief ${brief.id}'s premise no longer holds: ${outcome.description}: ${outcome.message}`, 'what the brief was written against has changed; archive the brief if its work is done, or rewrite its premise before a round is run on it', brief.file, outcome.line);
|
|
87
|
+
return finding('stale-premise', 'error', `brief ${brief.id}'s premise no longer holds: ${outcome.description}: ${outcome.message}`, 'what the brief was written against has changed; archive the brief if its work is done, or rewrite its premise before a round is run on it', brief.file, outcome.line, outcome.description);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* A premise spec-guard cannot read, as `premises` reports it: nothing checks
|
|
91
|
+
* whether it still holds, so a stale brief would pass for a live one. A
|
|
92
|
+
* warning, as the audit's is, which fails the run under `--strict`.
|
|
93
|
+
*/
|
|
94
|
+
export function unreadablePremiseFinding(brief, assertion) {
|
|
95
|
+
return finding('assertion-unreadable', 'warning', `spec-guard cannot read a premise of brief ${brief.id}, so whether it still holds is not checked: ${assertion.message}`, `fix the directive in ${brief.file}; until spec-guard can read it, nothing checks what it states`, brief.file, assertion.line, assertion.raw.trim());
|
|
53
96
|
}
|
|
54
97
|
/**
|
|
55
98
|
* The next step for a reason the archive gives. A protected file a verified
|
|
@@ -68,6 +111,8 @@ function archiveHint(reason, input) {
|
|
|
68
111
|
export function audit(input) {
|
|
69
112
|
const { brief } = input;
|
|
70
113
|
const out = [];
|
|
114
|
+
const goals = { held: 0, failed: 0 };
|
|
115
|
+
const premises = { retired: 0, holding: 0 };
|
|
71
116
|
if (input.unmeasured !== null) {
|
|
72
117
|
out.push(finding('unmeasured', 'warning', `the round's changes were not measured: ${input.unmeasured}`, 'pass --base <ref>, set "base" in .spec-harness.json, or audit on the round\'s branch before it merges', brief.file));
|
|
73
118
|
}
|
|
@@ -76,7 +121,7 @@ export function audit(input) {
|
|
|
76
121
|
}
|
|
77
122
|
else {
|
|
78
123
|
for (const reason of [...input.archive.blocking, ...input.archive.warnings]) {
|
|
79
|
-
out.push(finding(`archive/${reason.rule}`, reason.severity, reason.message, archiveHint(reason, input), reason.file, reason.line));
|
|
124
|
+
out.push(finding(`archive/${reason.rule}`, reason.severity, reason.message, archiveHint(reason, input), reason.file, reason.line, reason.path));
|
|
80
125
|
}
|
|
81
126
|
}
|
|
82
127
|
if (!Array.isArray(input.assertions)) {
|
|
@@ -87,40 +132,118 @@ export function audit(input) {
|
|
|
87
132
|
for (const outcome of input.assertions) {
|
|
88
133
|
const premise = isPremise(outcome.enclosing ?? outcome.section, input.premiseSections);
|
|
89
134
|
if (premise && outcome.ok) {
|
|
90
|
-
|
|
135
|
+
premises.holding += 1;
|
|
136
|
+
out.push(finding('premise-holds', 'warning', `a premise still holds after the round: ${outcome.description}`, 'the round set out to change what this premise states; check that it did, or move the assertion out of the premises', brief.file, outcome.line, outcome.description));
|
|
91
137
|
}
|
|
92
138
|
else if (premise) {
|
|
93
|
-
|
|
139
|
+
premises.retired += 1;
|
|
140
|
+
out.push(finding('premise-retired', 'note', `a premise no longer holds, as the round intended: ${outcome.description}`, 'nothing to do', brief.file, outcome.line, outcome.description));
|
|
94
141
|
}
|
|
95
142
|
else if (!outcome.ok) {
|
|
96
|
-
|
|
143
|
+
goals.failed += 1;
|
|
144
|
+
out.push(finding('goal-failed', 'error', `${outcome.description}: ${outcome.message}`, 'the round is not done until this holds', brief.file, outcome.line, outcome.description));
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
goals.held += 1;
|
|
97
148
|
}
|
|
98
149
|
}
|
|
99
150
|
}
|
|
151
|
+
// Whether a goal or a premise, an assertion spec-guard cannot read was not
|
|
152
|
+
// run, and dropping it would let the audit pass on what it never checked.
|
|
153
|
+
const unreadable = input.unreadableAssertions ?? [];
|
|
154
|
+
for (const assertion of unreadable) {
|
|
155
|
+
out.push(finding('assertion-unreadable', 'warning', `spec-guard cannot read an assertion in the brief, so nothing it states was run: ${assertion.message}`, `fix the directive in ${brief.file}; until spec-guard can read it, the audit measures nothing it states`, brief.file, assertion.line, assertion.raw.trim()));
|
|
156
|
+
}
|
|
100
157
|
for (const ruling of input.unverifiedRulings) {
|
|
101
|
-
out.push(finding('ruling-unverified', 'warning', `ruling ${ruling.id} allows nothing: ${ruling.reason}`, 'a ruling counts when the commit that last changed its row is signed by a key in the base branch\'s allowed signers', brief.file));
|
|
158
|
+
out.push(finding('ruling-unverified', 'warning', `ruling ${ruling.id} allows nothing: ${ruling.reason}`, 'a ruling counts when the commit that last changed its row is signed by a key in the base branch\'s allowed signers', brief.file, undefined, ruling.id));
|
|
159
|
+
}
|
|
160
|
+
// A protection or a ruling's path a leading `/` roots can be read, and
|
|
161
|
+
// names no path the guard or the archive decides, all repository-relative:
|
|
162
|
+
// the file it was meant to protect was the round's to write, and the file
|
|
163
|
+
// a ruling was meant to allow is still refused.
|
|
164
|
+
for (const pattern of brief.protectedFiles) {
|
|
165
|
+
const root = rooted(pattern);
|
|
166
|
+
if (root === null)
|
|
167
|
+
continue;
|
|
168
|
+
const what = root === 'whole' ? 'which a leading "/" roots at the filesystem\'s root, so it protects no path' : 'an alternative of which a leading "/" roots at the filesystem\'s root, so that alternative protects no path';
|
|
169
|
+
out.push(finding('protection-rooted', 'warning', `brief ${brief.id} protects "${pattern}", ${what}`, `write it without the leading "/" in ${brief.file}, since a protection is read from the repository's root, and check what the round changed there`, brief.file, undefined, pattern));
|
|
170
|
+
}
|
|
171
|
+
for (const ruling of input.verifiedRulings) {
|
|
172
|
+
for (const pattern of ruling.paths) {
|
|
173
|
+
const root = rooted(pattern);
|
|
174
|
+
if (root === null)
|
|
175
|
+
continue;
|
|
176
|
+
const what = root === 'whole' ? 'which a leading "/" roots at the filesystem\'s root, so it allows nothing' : 'an alternative of which a leading "/" roots at the filesystem\'s root, so that alternative allows nothing';
|
|
177
|
+
out.push(finding('ruling-path-rooted', 'warning', `ruling ${ruling.id} allows "${pattern}", ${what}`, 'a ruling\'s path is read from the repository\'s root: escalate again, and have the person rule on the path without the leading "/"', brief.file, undefined, `${ruling.id} ${pattern}`));
|
|
178
|
+
}
|
|
102
179
|
}
|
|
103
180
|
for (const { name, reason } of input.dependencies.unreadNames ?? []) {
|
|
104
|
-
out.push(finding('manifest-name-unread', 'warning', `"dependencies.manifests" names "${name}", which cannot be read: ${reason}; no manifest it names was read`, `fix or remove the name in ${CONFIG_FILE}; the other names were read`, CONFIG_FILE));
|
|
181
|
+
out.push(finding('manifest-name-unread', 'warning', `"dependencies.manifests" names "${name}", which cannot be read: ${reason}; no manifest it names was read`, `fix or remove the name in ${CONFIG_FILE}; the other names were read`, CONFIG_FILE, undefined, name));
|
|
182
|
+
}
|
|
183
|
+
for (const { name, whole } of input.dependencies.rootedNames ?? []) {
|
|
184
|
+
// Every path the audit reads is repository-relative, so a rooted name, or
|
|
185
|
+
// a rooted alternative of one, names no manifest, as silently as a name
|
|
186
|
+
// that cannot be read.
|
|
187
|
+
const what = whole
|
|
188
|
+
? 'a leading "/" roots it at the filesystem\'s root, where no file of the repository is, so no manifest it names was read'
|
|
189
|
+
: 'a leading "/" roots an alternative of it at the filesystem\'s root, where no file of the repository is, so that alternative names no manifest';
|
|
190
|
+
out.push(finding('manifest-name-rooted', 'warning', `"dependencies.manifests" names "${name}": ${what}`, `write it without the leading "/" in ${CONFIG_FILE}, since a name is matched at any depth; the other names were read`, CONFIG_FILE, undefined, name));
|
|
105
191
|
}
|
|
106
192
|
for (const file of input.dependencies.unread) {
|
|
107
193
|
out.push(finding('manifest-unread', 'warning', `${file} changed and could not be read for dependencies`, 'check its dependencies by hand; the audit cannot', file));
|
|
108
194
|
}
|
|
109
195
|
for (const change of input.dependencies.changes) {
|
|
110
196
|
const where = `${change.file} (${change.section})`;
|
|
197
|
+
const subject = `${change.name} (${change.section})`;
|
|
111
198
|
if (change.before === null) {
|
|
112
|
-
out.push(finding('new-dependency', 'warning', `the round added ${change.ecosystem} dependency "${change.name}"${change.after === '' ? '' : ` ${change.after}`} in ${where}`, 'say in the brief why it is needed, or remove it; a new dependency is code no reviewer read', change.file));
|
|
199
|
+
out.push(finding('new-dependency', 'warning', `the round added ${change.ecosystem} dependency "${change.name}"${change.after === '' ? '' : ` ${change.after}`} in ${where}`, 'say in the brief why it is needed, or remove it; a new dependency is code no reviewer read', change.file, undefined, subject));
|
|
113
200
|
}
|
|
114
201
|
else if (change.after === null) {
|
|
115
|
-
out.push(finding('dependency-removed', 'note', `the round removed "${change.name}" from ${where}`, 'nothing to do', change.file));
|
|
202
|
+
out.push(finding('dependency-removed', 'note', `the round removed "${change.name}" from ${where}`, 'nothing to do', change.file, undefined, subject));
|
|
116
203
|
}
|
|
117
204
|
else {
|
|
118
|
-
out.push(finding('dependency-changed', 'note', `the round moved "${change.name}" from ${change.before} to ${change.after} in ${where}`, 'nothing to do, if the brief meant it', change.file));
|
|
205
|
+
out.push(finding('dependency-changed', 'note', `the round moved "${change.name}" from ${change.before} to ${change.after} in ${where}`, 'nothing to do, if the brief meant it', change.file, undefined, subject));
|
|
119
206
|
}
|
|
120
207
|
}
|
|
208
|
+
const measured = {
|
|
209
|
+
changes: input.unmeasured === null ? 'measured' : 'unmeasured',
|
|
210
|
+
archive: 'unavailable' in input.archive ? 'unavailable' : 'asked',
|
|
211
|
+
assertions: Array.isArray(input.assertions) ? 'run' : 'unavailable',
|
|
212
|
+
goals,
|
|
213
|
+
premises,
|
|
214
|
+
unreadableAssertions: unreadable.length,
|
|
215
|
+
rulings: { verified: input.verifiedRulings.length, unverified: input.unverifiedRulings.length },
|
|
216
|
+
dependencies: { changed: input.dependencies.changes.length, unread: input.dependencies.unread.length },
|
|
217
|
+
};
|
|
218
|
+
return { findings: out, counts: tally(out), measured };
|
|
219
|
+
}
|
|
220
|
+
/** How many findings of each severity. */
|
|
221
|
+
export function tally(findings) {
|
|
121
222
|
const counts = { error: 0, warning: 0, note: 0 };
|
|
122
|
-
for (const item of
|
|
223
|
+
for (const item of findings)
|
|
123
224
|
counts[item.severity] += 1;
|
|
124
|
-
return
|
|
225
|
+
return counts;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* What the audit measured, in one line a person reads above the counts. A
|
|
229
|
+
* brief that declares no assertion says so, and draws no warning: a brief
|
|
230
|
+
* without assertions is a brief, and the archive still measured its round.
|
|
231
|
+
*/
|
|
232
|
+
export function describeMeasured(measured) {
|
|
233
|
+
const { goals, premises, rulings, dependencies } = measured;
|
|
234
|
+
const parts = [];
|
|
235
|
+
if (measured.assertions === 'unavailable') {
|
|
236
|
+
parts.push('assertions: not run');
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
parts.push(goals.held + goals.failed === 0 ? 'goals: none declared' : `goals: ${goals.held} held, ${goals.failed} failed`);
|
|
240
|
+
parts.push(premises.retired + premises.holding === 0 ? 'premises: none declared' : `premises: ${premises.retired} retired, ${premises.holding} holding`);
|
|
241
|
+
}
|
|
242
|
+
if (measured.unreadableAssertions > 0)
|
|
243
|
+
parts.push(`unreadable assertions: ${measured.unreadableAssertions}`);
|
|
244
|
+
parts.push(measured.archive === 'asked' ? 'archive: asked' : 'archive: not asked');
|
|
245
|
+
parts.push(rulings.verified + rulings.unverified === 0 ? 'rulings: none' : `rulings: ${rulings.verified} verified, ${rulings.unverified} unverified`);
|
|
246
|
+
parts.push(measured.changes === 'unmeasured' ? 'dependencies: not measured' : `dependencies: ${dependencies.changed} changed, ${dependencies.unread} unread`);
|
|
247
|
+
return `measured: ${parts.join(' · ')}`;
|
|
125
248
|
}
|
|
126
249
|
//# sourceMappingURL=audit.js.map
|
package/dist/audit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Did the round stay inside the lines?\n *\n * One report, from three sources and one question of its own:\n *\n * - spec-brief's archive, asked with `--dry-run`: open boxes, protected files\n * changed, files outside the scope, a tree that was not committed. The\n * archive is spec-brief's decision, so its reasons are carried as they are\n * - one defect, one finding - and the harness does not measure the scope a\n * second time.\n * - spec-guard, over the brief's own assertions: the goals must hold now. A\n * premise - an assertion in a section that states what was true before the\n * round, such as \"The Defect, Measured\" - is expected to stop holding, and\n * one that still holds says the defect may still be there.\n * - The rulings: each one whose signature does not verify is reported, since\n * an unverified ruling allows nothing.\n * - And the one question nobody else asks: which dependencies the round\n * added. A dependency is code no reviewer read.\n *\n * A part that could not be measured is a finding, never a silence.\n */\n\nimport { CONFIG_FILE } from './config.js';\nimport { PLUGIN } from './configure.js';\nimport { rulingFor, type VerifiedRuling } from './guard.js';\nimport type { DependencyChange } from './manifests.js';\nimport { sameSection } from './reader.js';\nimport type { BriefRow, Finding, Severity } from './types.js';\n\nexport interface AssertionOutcome {\n readonly ok: boolean;\n readonly description: string;\n readonly message: string;\n readonly line: number;\n /** The brief section the directive sits in, or `null`. */\n readonly section: string | null;\n /** Every section it sits in, outermost first; `[section]` when not given. */\n readonly enclosing?: readonly string[] | undefined;\n}\n\n/** A reason spec-brief's archive gives; a `protected-file` refusal names its `path`. */\nexport interface ArchiveReason extends Finding {\n readonly path?: string | undefined;\n}\n\nexport interface AuditInput {\n readonly brief: BriefRow;\n /** Why the round's changes could not be measured, when they could not. */\n readonly unmeasured: string | null;\n readonly dependencies: {\n readonly changes: readonly DependencyChange[];\n readonly unread: readonly string[];\n /** Names in `dependencies.manifests` that could not be read, with spec-core's reason; none when not given. */\n readonly unreadNames?: readonly { readonly name: string; readonly reason: string }[] | undefined;\n };\n readonly archive: { readonly blocking: readonly ArchiveReason[]; readonly warnings: readonly ArchiveReason[] } | { readonly unavailable: string };\n readonly assertions: readonly AssertionOutcome[] | { readonly unavailable: string };\n /** Section names whose assertions are premises. Compared without case, emphasis, a leading number or a trailing colon. */\n readonly premiseSections: readonly string[];\n readonly unverifiedRulings: readonly { readonly id: string; readonly reason: string }[];\n /** The rulings whose signatures verify, which a protected file the archive refuses may be covered by. */\n readonly verifiedRulings: readonly VerifiedRuling[];\n /** Whether spec-brief's configuration loads this package's plugin, through which its archive learns of them. */\n readonly pluginLoaded: boolean;\n}\n\nexport interface AuditReport {\n readonly findings: readonly Finding[];\n readonly counts: Readonly<Record<Severity, number>>;\n}\n\nfunction finding(rule: string, severity: Severity, message: string, hint: string, file?: string, line?: number): Finding {\n return { rule, severity, message, hint, ...(file === undefined ? {} : { file }), ...(line === undefined ? {} : { line }) };\n}\n\n/**\n * Whether an assertion's section is a premise section. Compared as the\n * rulings section is, and as spec-brief compares section names: a heading\n * written `## 3. **The Defect, Measured:**` is still the premise section, and\n * an assertion under it read as a goal would fail the round for retiring it.\n */\nexport function isPremise(sections: string | null | readonly string[], premises: readonly string[]): boolean {\n const list = sections === null ? [] : typeof sections === 'string' ? [sections] : sections;\n // A premise section's subsections are still premises: `### Before` under\n // `## The Defect, Measured` states what was true before the round.\n return list.some((section) => premises.some((premise) => sameSection(section, premise)));\n}\n\n/**\n * A live brief's premise that no longer holds, as `premises` reports it. On\n * the brief a round is working on - the one the flag, SPEC_BRIEF or the\n * branch names - that is what the round set out to do, and it is reported as\n * the audit reports it; on any other, what the brief was written against has\n * changed under it.\n */\nexport function premiseFinding(\n brief: Pick<BriefRow, 'id' | 'file'>,\n outcome: { readonly description: string; readonly message: string; readonly line: number },\n active: boolean,\n): Finding {\n if (active) {\n return finding(\n 'premise-retired',\n 'note',\n `brief ${brief.id}'s premise no longer holds, as the round on it intends: ${outcome.description}`,\n 'nothing to do: this is the round that changes it, and audit measures it',\n brief.file,\n outcome.line,\n );\n }\n return finding(\n 'stale-premise',\n 'error',\n `brief ${brief.id}'s premise no longer holds: ${outcome.description}: ${outcome.message}`,\n 'what the brief was written against has changed; archive the brief if its work is done, or rewrite its premise before a round is run on it',\n brief.file,\n outcome.line,\n );\n}\n\n/**\n * The next step for a reason the archive gives. A protected file a verified\n * ruling covers is refused only because the archive did not learn of the\n * ruling, and recording a departure in the brief would not fix that.\n */\nfunction archiveHint(reason: ArchiveReason, input: AuditInput): string {\n const ruling = reason.rule === 'protected-file' && reason.path !== undefined ? rulingFor(input.verifiedRulings, reason.path) : undefined;\n if (ruling === undefined) return reason.hint;\n const allowed = `ruling ${ruling.id}, signed by ${ruling.signer}, allows it`;\n return input.pluginLoaded\n ? `${allowed}, and the archive still refused it: check that spec-brief loads the spec-harness installed here and measures from the same base (spec-harness doctor)`\n : `${allowed}, but spec-brief does not load spec-harness's plugin, which is how its archive learns of signed rulings: add \"${PLUGIN}\" to \"plugins\" in its configuration, or run spec-harness init --write`;\n}\n\nexport function audit(input: AuditInput): AuditReport {\n const { brief } = input;\n const out: Finding[] = [];\n\n if (input.unmeasured !== null) {\n out.push(\n finding(\n 'unmeasured',\n 'warning',\n `the round's changes were not measured: ${input.unmeasured}`,\n 'pass --base <ref>, set \"base\" in .spec-harness.json, or audit on the round\\'s branch before it merges',\n brief.file,\n ),\n );\n }\n\n if ('unavailable' in input.archive) {\n out.push(finding('archive-unchecked', 'warning', `what the archive would say is unknown: ${input.archive.unavailable}`, 'install spec-brief, or fix what stopped it', brief.file));\n } else {\n for (const reason of [...input.archive.blocking, ...input.archive.warnings]) {\n out.push(finding(`archive/${reason.rule}`, reason.severity, reason.message, archiveHint(reason, input), reason.file, reason.line));\n }\n }\n\n if (!Array.isArray(input.assertions)) {\n const unavailable = (input.assertions as { unavailable: string }).unavailable;\n out.push(finding('assertions-unchecked', 'warning', `the brief's assertions were not run: ${unavailable}`, 'install spec-guard, or fix what stopped it', brief.file));\n } else {\n for (const outcome of input.assertions) {\n const premise = isPremise(outcome.enclosing ?? outcome.section, input.premiseSections);\n if (premise && outcome.ok) {\n out.push(\n finding(\n 'premise-holds',\n 'warning',\n `a premise still holds after the round: ${outcome.description}`,\n 'the round set out to change what this premise states; check that it did, or move the assertion out of the premises',\n brief.file,\n outcome.line,\n ),\n );\n } else if (premise) {\n out.push(finding('premise-retired', 'note', `a premise no longer holds, as the round intended: ${outcome.description}`, 'nothing to do', brief.file, outcome.line));\n } else if (!outcome.ok) {\n out.push(finding('goal-failed', 'error', `${outcome.description}: ${outcome.message}`, 'the round is not done until this holds', brief.file, outcome.line));\n }\n }\n }\n\n for (const ruling of input.unverifiedRulings) {\n out.push(\n finding(\n 'ruling-unverified',\n 'warning',\n `ruling ${ruling.id} allows nothing: ${ruling.reason}`,\n 'a ruling counts when the commit that last changed its row is signed by a key in the base branch\\'s allowed signers',\n brief.file,\n ),\n );\n }\n\n for (const { name, reason } of input.dependencies.unreadNames ?? []) {\n out.push(\n finding(\n 'manifest-name-unread',\n 'warning',\n `\"dependencies.manifests\" names \"${name}\", which cannot be read: ${reason}; no manifest it names was read`,\n `fix or remove the name in ${CONFIG_FILE}; the other names were read`,\n CONFIG_FILE,\n ),\n );\n }\n for (const file of input.dependencies.unread) {\n out.push(finding('manifest-unread', 'warning', `${file} changed and could not be read for dependencies`, 'check its dependencies by hand; the audit cannot', file));\n }\n for (const change of input.dependencies.changes) {\n const where = `${change.file} (${change.section})`;\n if (change.before === null) {\n out.push(\n finding(\n 'new-dependency',\n 'warning',\n `the round added ${change.ecosystem} dependency \"${change.name}\"${change.after === '' ? '' : ` ${change.after}`} in ${where}`,\n 'say in the brief why it is needed, or remove it; a new dependency is code no reviewer read',\n change.file,\n ),\n );\n } else if (change.after === null) {\n out.push(finding('dependency-removed', 'note', `the round removed \"${change.name}\" from ${where}`, 'nothing to do', change.file));\n } else {\n out.push(finding('dependency-changed', 'note', `the round moved \"${change.name}\" from ${change.before} to ${change.after} in ${where}`, 'nothing to do, if the brief meant it', change.file));\n }\n }\n\n const counts: Record<Severity, number> = { error: 0, warning: 0, note: 0 };\n for (const item of out) counts[item.severity] += 1;\n return { findings: out, counts };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Did the round stay inside the lines?\n *\n * One report, from three sources and one question of its own:\n *\n * - spec-brief's archive, asked with `--dry-run`: open boxes, protected files\n * changed, files outside the scope, a tree that was not committed. The\n * archive is spec-brief's decision, so its reasons are carried as they are\n * - one defect, one finding - and the harness does not measure the scope a\n * second time.\n * - spec-guard, over the brief's own assertions: the goals must hold now. A\n * premise - an assertion in a section that states what was true before the\n * round, such as \"The Defect, Measured\" - is expected to stop holding, and\n * one that still holds says the defect may still be there.\n * - The rulings: each one whose signature does not verify is reported, since\n * an unverified ruling allows nothing.\n * - And the one question nobody else asks: which dependencies the round\n * added. A dependency is code no reviewer read.\n *\n * A part that could not be measured is a finding, never a silence, and what\n * was measured is reported beside what was found: an audit that finds nothing\n * because it checked nothing must not read as one that checked and found\n * nothing (spec-core ADR-0005).\n */\n\nimport { CONFIG_FILE } from './config.js';\nimport { PLUGIN } from './configure.js';\nimport { rooted, rulingFor, type VerifiedRuling } from './guard.js';\nimport type { DependencyChange } from './manifests.js';\nimport { sameSection } from './reader.js';\nimport type { BriefRow, Finding, Severity } from './types.js';\n\nexport interface AssertionOutcome {\n readonly ok: boolean;\n readonly description: string;\n readonly message: string;\n readonly line: number;\n /** The brief section the directive sits in, or `null`. */\n readonly section: string | null;\n /** Every section it sits in, outermost first; `[section]` when not given. */\n readonly enclosing?: readonly string[] | undefined;\n}\n\n/** An assertion spec-guard could not read, as it reports it: nothing it states was run. */\nexport interface UnreadableAssertion {\n readonly message: string;\n readonly line: number;\n /** The directive as written. */\n readonly raw: string;\n}\n\n/** A reason spec-brief's archive gives; a `protected-file` refusal names its `path`. */\nexport interface ArchiveReason extends Finding {\n readonly path?: string | undefined;\n}\n\nexport interface AuditInput {\n readonly brief: BriefRow;\n /** Why the round's changes could not be measured, when they could not. */\n readonly unmeasured: string | null;\n readonly dependencies: {\n readonly changes: readonly DependencyChange[];\n readonly unread: readonly string[];\n /** Names in `dependencies.manifests` that could not be read, with spec-core's reason; none when not given. */\n readonly unreadNames?: readonly { readonly name: string; readonly reason: string }[] | undefined;\n /** Names in `dependencies.manifests` a leading `/` roots, and whether every alternative of each is rooted; none when not given. */\n readonly rootedNames?: readonly { readonly name: string; readonly whole: boolean }[] | undefined;\n };\n readonly archive: { readonly blocking: readonly ArchiveReason[]; readonly warnings: readonly ArchiveReason[] } | { readonly unavailable: string };\n readonly assertions: readonly AssertionOutcome[] | { readonly unavailable: string };\n /** The assertions in the brief spec-guard could not read; none when not given. */\n readonly unreadableAssertions?: readonly UnreadableAssertion[] | undefined;\n /** Section names whose assertions are premises. Compared without case, emphasis, a leading number or a trailing colon. */\n readonly premiseSections: readonly string[];\n readonly unverifiedRulings: readonly { readonly id: string; readonly reason: string }[];\n /** The rulings whose signatures verify, which a protected file the archive refuses may be covered by. */\n readonly verifiedRulings: readonly VerifiedRuling[];\n /** Whether spec-brief's configuration loads this package's plugin, through which its archive learns of them. */\n readonly pluginLoaded: boolean;\n}\n\n/**\n * What the audit measured, beside what it found. A script tells \"nothing\n * found\" from \"nothing checked\" here: `assertions: \"unavailable\"` with no\n * goal failed is an audit that ran no goal.\n */\nexport interface Measured {\n /** Whether the round's changes were measured from a base; the dependencies are read from them. */\n readonly changes: 'measured' | 'unmeasured';\n /** Whether spec-brief's archive answered. */\n readonly archive: 'asked' | 'unavailable';\n /** Whether spec-guard ran the brief's assertions. */\n readonly assertions: 'run' | 'unavailable';\n readonly goals: { readonly held: number; readonly failed: number };\n readonly premises: { readonly retired: number; readonly holding: number };\n /** Assertions spec-guard could not read, so ran none of. */\n readonly unreadableAssertions: number;\n /** The rulings that allow something, by whether their signatures verify. */\n readonly rulings: { readonly verified: number; readonly unverified: number };\n /** Dependencies the round added, removed or moved, and manifests it changed that could not be read. */\n readonly dependencies: { readonly changed: number; readonly unread: number };\n}\n\nexport interface AuditReport {\n readonly findings: readonly Finding[];\n readonly counts: Readonly<Record<Severity, number>>;\n readonly measured: Measured;\n}\n\n/**\n * Each rule the audit and `premises` report, as a reader of SARIF sees it\n * described. A reason spec-brief's archive gives is `archive/<its rule>`.\n */\nexport const RULES: Readonly<Record<string, string>> = {\n unmeasured: \"The round's changes could not be measured from a base.\",\n 'archive-unchecked': \"spec-brief's archive could not be asked what it would refuse.\",\n 'assertions-unchecked': \"The brief's assertions could not be run.\",\n 'assertion-unreadable': 'An assertion in the brief spec-guard cannot read, so nothing it states was run.',\n 'goal-failed': 'A goal the brief asserts does not hold.',\n 'premise-holds': 'A premise still holds after the round meant to change it.',\n 'premise-retired': 'A premise no longer holds, as the round on the brief intends.',\n 'stale-premise': \"A live brief's premise no longer holds: what it was written against has changed.\",\n 'ruling-unverified': 'A ruling whose signature does not verify, which allows nothing.',\n 'ruling-unreadable': 'A row of the rulings table that cannot be read.',\n 'protection-rooted': \"A protection rooted at the filesystem's root, which protects no path of the repository.\",\n 'ruling-path-rooted': \"A signed ruling's path rooted at the filesystem's root, which allows nothing.\",\n 'manifest-name-unread': 'A name in dependencies.manifests that cannot be read, which names no manifest.',\n 'manifest-name-rooted': \"A name in dependencies.manifests rooted at the filesystem's root, where no file of the repository is.\",\n 'manifest-unread': 'A manifest the round changed that cannot be read for dependencies.',\n 'new-dependency': 'A dependency the round added: code no reviewer read.',\n 'dependency-removed': 'A dependency the round removed.',\n 'dependency-changed': 'A dependency the round moved to another version.',\n};\n\nfunction finding(rule: string, severity: Severity, message: string, hint: string, file?: string, line?: number, subject?: string): Finding {\n return {\n rule,\n severity,\n message,\n hint,\n ...(file === undefined ? {} : { file }),\n ...(line === undefined ? {} : { line }),\n ...(subject === undefined ? {} : { subject }),\n };\n}\n\n/**\n * Whether an assertion's section is a premise section. Compared as the\n * rulings section is, and as spec-brief compares section names: a heading\n * written `## 3. **The Defect, Measured:**` is still the premise section, and\n * an assertion under it read as a goal would fail the round for retiring it.\n */\nexport function isPremise(sections: string | null | readonly string[], premises: readonly string[]): boolean {\n const list = sections === null ? [] : typeof sections === 'string' ? [sections] : sections;\n // A premise section's subsections are still premises: `### Before` under\n // `## The Defect, Measured` states what was true before the round.\n return list.some((section) => premises.some((premise) => sameSection(section, premise)));\n}\n\n/**\n * A live brief's premise that no longer holds, as `premises` reports it. On\n * the brief a round is working on - the one the flag, SPEC_BRIEF or the\n * branch names - that is what the round set out to do, and it is reported as\n * the audit reports it; on any other, what the brief was written against has\n * changed under it.\n */\nexport function premiseFinding(\n brief: Pick<BriefRow, 'id' | 'file'>,\n outcome: { readonly description: string; readonly message: string; readonly line: number },\n active: boolean,\n): Finding {\n if (active) {\n return finding(\n 'premise-retired',\n 'note',\n `brief ${brief.id}'s premise no longer holds, as the round on it intends: ${outcome.description}`,\n 'nothing to do: this is the round that changes it, and audit measures it',\n brief.file,\n outcome.line,\n outcome.description,\n );\n }\n return finding(\n 'stale-premise',\n 'error',\n `brief ${brief.id}'s premise no longer holds: ${outcome.description}: ${outcome.message}`,\n 'what the brief was written against has changed; archive the brief if its work is done, or rewrite its premise before a round is run on it',\n brief.file,\n outcome.line,\n outcome.description,\n );\n}\n\n/**\n * A premise spec-guard cannot read, as `premises` reports it: nothing checks\n * whether it still holds, so a stale brief would pass for a live one. A\n * warning, as the audit's is, which fails the run under `--strict`.\n */\nexport function unreadablePremiseFinding(brief: Pick<BriefRow, 'id' | 'file'>, assertion: UnreadableAssertion): Finding {\n return finding(\n 'assertion-unreadable',\n 'warning',\n `spec-guard cannot read a premise of brief ${brief.id}, so whether it still holds is not checked: ${assertion.message}`,\n `fix the directive in ${brief.file}; until spec-guard can read it, nothing checks what it states`,\n brief.file,\n assertion.line,\n assertion.raw.trim(),\n );\n}\n\n/**\n * The next step for a reason the archive gives. A protected file a verified\n * ruling covers is refused only because the archive did not learn of the\n * ruling, and recording a departure in the brief would not fix that.\n */\nfunction archiveHint(reason: ArchiveReason, input: AuditInput): string {\n const ruling = reason.rule === 'protected-file' && reason.path !== undefined ? rulingFor(input.verifiedRulings, reason.path) : undefined;\n if (ruling === undefined) return reason.hint;\n const allowed = `ruling ${ruling.id}, signed by ${ruling.signer}, allows it`;\n return input.pluginLoaded\n ? `${allowed}, and the archive still refused it: check that spec-brief loads the spec-harness installed here and measures from the same base (spec-harness doctor)`\n : `${allowed}, but spec-brief does not load spec-harness's plugin, which is how its archive learns of signed rulings: add \"${PLUGIN}\" to \"plugins\" in its configuration, or run spec-harness init --write`;\n}\n\nexport function audit(input: AuditInput): AuditReport {\n const { brief } = input;\n const out: Finding[] = [];\n const goals = { held: 0, failed: 0 };\n const premises = { retired: 0, holding: 0 };\n\n if (input.unmeasured !== null) {\n out.push(\n finding(\n 'unmeasured',\n 'warning',\n `the round's changes were not measured: ${input.unmeasured}`,\n 'pass --base <ref>, set \"base\" in .spec-harness.json, or audit on the round\\'s branch before it merges',\n brief.file,\n ),\n );\n }\n\n if ('unavailable' in input.archive) {\n out.push(finding('archive-unchecked', 'warning', `what the archive would say is unknown: ${input.archive.unavailable}`, 'install spec-brief, or fix what stopped it', brief.file));\n } else {\n for (const reason of [...input.archive.blocking, ...input.archive.warnings]) {\n out.push(finding(`archive/${reason.rule}`, reason.severity, reason.message, archiveHint(reason, input), reason.file, reason.line, reason.path));\n }\n }\n\n if (!Array.isArray(input.assertions)) {\n const unavailable = (input.assertions as { unavailable: string }).unavailable;\n out.push(finding('assertions-unchecked', 'warning', `the brief's assertions were not run: ${unavailable}`, 'install spec-guard, or fix what stopped it', brief.file));\n } else {\n for (const outcome of input.assertions) {\n const premise = isPremise(outcome.enclosing ?? outcome.section, input.premiseSections);\n if (premise && outcome.ok) {\n premises.holding += 1;\n out.push(\n finding(\n 'premise-holds',\n 'warning',\n `a premise still holds after the round: ${outcome.description}`,\n 'the round set out to change what this premise states; check that it did, or move the assertion out of the premises',\n brief.file,\n outcome.line,\n outcome.description,\n ),\n );\n } else if (premise) {\n premises.retired += 1;\n out.push(finding('premise-retired', 'note', `a premise no longer holds, as the round intended: ${outcome.description}`, 'nothing to do', brief.file, outcome.line, outcome.description));\n } else if (!outcome.ok) {\n goals.failed += 1;\n out.push(finding('goal-failed', 'error', `${outcome.description}: ${outcome.message}`, 'the round is not done until this holds', brief.file, outcome.line, outcome.description));\n } else {\n goals.held += 1;\n }\n }\n }\n // Whether a goal or a premise, an assertion spec-guard cannot read was not\n // run, and dropping it would let the audit pass on what it never checked.\n const unreadable = input.unreadableAssertions ?? [];\n for (const assertion of unreadable) {\n out.push(\n finding(\n 'assertion-unreadable',\n 'warning',\n `spec-guard cannot read an assertion in the brief, so nothing it states was run: ${assertion.message}`,\n `fix the directive in ${brief.file}; until spec-guard can read it, the audit measures nothing it states`,\n brief.file,\n assertion.line,\n assertion.raw.trim(),\n ),\n );\n }\n\n for (const ruling of input.unverifiedRulings) {\n out.push(\n finding(\n 'ruling-unverified',\n 'warning',\n `ruling ${ruling.id} allows nothing: ${ruling.reason}`,\n 'a ruling counts when the commit that last changed its row is signed by a key in the base branch\\'s allowed signers',\n brief.file,\n undefined,\n ruling.id,\n ),\n );\n }\n\n // A protection or a ruling's path a leading `/` roots can be read, and\n // names no path the guard or the archive decides, all repository-relative:\n // the file it was meant to protect was the round's to write, and the file\n // a ruling was meant to allow is still refused.\n for (const pattern of brief.protectedFiles) {\n const root = rooted(pattern);\n if (root === null) continue;\n const what = root === 'whole' ? 'which a leading \"/\" roots at the filesystem\\'s root, so it protects no path' : 'an alternative of which a leading \"/\" roots at the filesystem\\'s root, so that alternative protects no path';\n out.push(\n finding(\n 'protection-rooted',\n 'warning',\n `brief ${brief.id} protects \"${pattern}\", ${what}`,\n `write it without the leading \"/\" in ${brief.file}, since a protection is read from the repository's root, and check what the round changed there`,\n brief.file,\n undefined,\n pattern,\n ),\n );\n }\n for (const ruling of input.verifiedRulings) {\n for (const pattern of ruling.paths) {\n const root = rooted(pattern);\n if (root === null) continue;\n const what = root === 'whole' ? 'which a leading \"/\" roots at the filesystem\\'s root, so it allows nothing' : 'an alternative of which a leading \"/\" roots at the filesystem\\'s root, so that alternative allows nothing';\n out.push(\n finding(\n 'ruling-path-rooted',\n 'warning',\n `ruling ${ruling.id} allows \"${pattern}\", ${what}`,\n 'a ruling\\'s path is read from the repository\\'s root: escalate again, and have the person rule on the path without the leading \"/\"',\n brief.file,\n undefined,\n `${ruling.id} ${pattern}`,\n ),\n );\n }\n }\n\n for (const { name, reason } of input.dependencies.unreadNames ?? []) {\n out.push(\n finding(\n 'manifest-name-unread',\n 'warning',\n `\"dependencies.manifests\" names \"${name}\", which cannot be read: ${reason}; no manifest it names was read`,\n `fix or remove the name in ${CONFIG_FILE}; the other names were read`,\n CONFIG_FILE,\n undefined,\n name,\n ),\n );\n }\n for (const { name, whole } of input.dependencies.rootedNames ?? []) {\n // Every path the audit reads is repository-relative, so a rooted name, or\n // a rooted alternative of one, names no manifest, as silently as a name\n // that cannot be read.\n const what = whole\n ? 'a leading \"/\" roots it at the filesystem\\'s root, where no file of the repository is, so no manifest it names was read'\n : 'a leading \"/\" roots an alternative of it at the filesystem\\'s root, where no file of the repository is, so that alternative names no manifest';\n out.push(\n finding(\n 'manifest-name-rooted',\n 'warning',\n `\"dependencies.manifests\" names \"${name}\": ${what}`,\n `write it without the leading \"/\" in ${CONFIG_FILE}, since a name is matched at any depth; the other names were read`,\n CONFIG_FILE,\n undefined,\n name,\n ),\n );\n }\n for (const file of input.dependencies.unread) {\n out.push(finding('manifest-unread', 'warning', `${file} changed and could not be read for dependencies`, 'check its dependencies by hand; the audit cannot', file));\n }\n for (const change of input.dependencies.changes) {\n const where = `${change.file} (${change.section})`;\n const subject = `${change.name} (${change.section})`;\n if (change.before === null) {\n out.push(\n finding(\n 'new-dependency',\n 'warning',\n `the round added ${change.ecosystem} dependency \"${change.name}\"${change.after === '' ? '' : ` ${change.after}`} in ${where}`,\n 'say in the brief why it is needed, or remove it; a new dependency is code no reviewer read',\n change.file,\n undefined,\n subject,\n ),\n );\n } else if (change.after === null) {\n out.push(finding('dependency-removed', 'note', `the round removed \"${change.name}\" from ${where}`, 'nothing to do', change.file, undefined, subject));\n } else {\n out.push(finding('dependency-changed', 'note', `the round moved \"${change.name}\" from ${change.before} to ${change.after} in ${where}`, 'nothing to do, if the brief meant it', change.file, undefined, subject));\n }\n }\n\n const measured: Measured = {\n changes: input.unmeasured === null ? 'measured' : 'unmeasured',\n archive: 'unavailable' in input.archive ? 'unavailable' : 'asked',\n assertions: Array.isArray(input.assertions) ? 'run' : 'unavailable',\n goals,\n premises,\n unreadableAssertions: unreadable.length,\n rulings: { verified: input.verifiedRulings.length, unverified: input.unverifiedRulings.length },\n dependencies: { changed: input.dependencies.changes.length, unread: input.dependencies.unread.length },\n };\n return { findings: out, counts: tally(out), measured };\n}\n\n/** How many findings of each severity. */\nexport function tally(findings: readonly Finding[]): Record<Severity, number> {\n const counts: Record<Severity, number> = { error: 0, warning: 0, note: 0 };\n for (const item of findings) counts[item.severity] += 1;\n return counts;\n}\n\n/**\n * What the audit measured, in one line a person reads above the counts. A\n * brief that declares no assertion says so, and draws no warning: a brief\n * without assertions is a brief, and the archive still measured its round.\n */\nexport function describeMeasured(measured: Measured): string {\n const { goals, premises, rulings, dependencies } = measured;\n const parts: string[] = [];\n if (measured.assertions === 'unavailable') {\n parts.push('assertions: not run');\n } else {\n parts.push(goals.held + goals.failed === 0 ? 'goals: none declared' : `goals: ${goals.held} held, ${goals.failed} failed`);\n parts.push(premises.retired + premises.holding === 0 ? 'premises: none declared' : `premises: ${premises.retired} retired, ${premises.holding} holding`);\n }\n if (measured.unreadableAssertions > 0) parts.push(`unreadable assertions: ${measured.unreadableAssertions}`);\n parts.push(measured.archive === 'asked' ? 'archive: asked' : 'archive: not asked');\n parts.push(rulings.verified + rulings.unverified === 0 ? 'rulings: none' : `rulings: ${rulings.verified} verified, ${rulings.unverified} unverified`);\n parts.push(measured.changes === 'unmeasured' ? 'dependencies: not measured' : `dependencies: ${dependencies.changed} changed, ${dependencies.unread} unread`);\n return `measured: ${parts.join(' · ')}`;\n}\n"]}
|
package/dist/branch.d.ts
CHANGED
|
@@ -16,6 +16,19 @@
|
|
|
16
16
|
export declare function templateError(template: string): string | null;
|
|
17
17
|
/** The brief id a branch name carries under one template, or `null`. */
|
|
18
18
|
export declare function idFromBranch(template: string, name: string): string | null;
|
|
19
|
+
/**
|
|
20
|
+
* The branch a CI run is building, as the forge names it, for a checkout on
|
|
21
|
+
* no branch: CI checks out a commit, and without the branch a round's own
|
|
22
|
+
* run would name no brief. In order: a GitHub pull request's head branch; the
|
|
23
|
+
* branch a GitHub push built; a GitLab merge request's source branch; the
|
|
24
|
+
* branch a GitLab pipeline built. Each is set only where it names a branch -
|
|
25
|
+
* GitHub's `GITHUB_REF_NAME` names a tag too, so only when `GITHUB_REF_TYPE`
|
|
26
|
+
* says `branch` - and an empty one is not set. `null` outside CI.
|
|
27
|
+
*/
|
|
28
|
+
export declare function ciBranch(env: Readonly<Record<string, string | undefined>>): {
|
|
29
|
+
readonly branch: string;
|
|
30
|
+
readonly source: string;
|
|
31
|
+
} | null;
|
|
19
32
|
/** The first template that yields an id, in order. */
|
|
20
33
|
export declare function briefIdFromBranch(templates: readonly string[], name: string): string | null;
|
|
21
34
|
/**
|
package/dist/branch.js
CHANGED
|
@@ -78,6 +78,25 @@ export function idFromBranch(template, name) {
|
|
|
78
78
|
}
|
|
79
79
|
return null;
|
|
80
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* The branch a CI run is building, as the forge names it, for a checkout on
|
|
83
|
+
* no branch: CI checks out a commit, and without the branch a round's own
|
|
84
|
+
* run would name no brief. In order: a GitHub pull request's head branch; the
|
|
85
|
+
* branch a GitHub push built; a GitLab merge request's source branch; the
|
|
86
|
+
* branch a GitLab pipeline built. Each is set only where it names a branch -
|
|
87
|
+
* GitHub's `GITHUB_REF_NAME` names a tag too, so only when `GITHUB_REF_TYPE`
|
|
88
|
+
* says `branch` - and an empty one is not set. `null` outside CI.
|
|
89
|
+
*/
|
|
90
|
+
export function ciBranch(env) {
|
|
91
|
+
const named = (name) => {
|
|
92
|
+
const value = env[name]?.trim() ?? '';
|
|
93
|
+
return value === '' ? null : { branch: value, source: name };
|
|
94
|
+
};
|
|
95
|
+
return (named('GITHUB_HEAD_REF') ??
|
|
96
|
+
(env['GITHUB_REF_TYPE'] === 'branch' ? named('GITHUB_REF_NAME') : null) ??
|
|
97
|
+
named('CI_MERGE_REQUEST_SOURCE_BRANCH_NAME') ??
|
|
98
|
+
named('CI_COMMIT_BRANCH'));
|
|
99
|
+
}
|
|
81
100
|
/** The first template that yields an id, in order. */
|
|
82
101
|
export function briefIdFromBranch(templates, name) {
|
|
83
102
|
for (const template of templates) {
|
package/dist/branch.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"branch.js","sourceRoot":"","sources":["../src/branch.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Which brief a branch is working on, read from its name.\n *\n * A round runs on a branch, and naming the branch after the brief is the one\n * convention every repository that runs agents on briefs already has. Reading\n * it from there keeps the harness stateless: no file says which brief is\n * active, so no file can say it wrongly (ADR-0004).\n *\n * A template is a branch name with `{id}` where the id goes and `*` for any\n * run of characters other than `/`: `brief/{id}`, `*\\/brief-{id}`. The id is\n * the longest run of letters and digits at that point, and must be followed\n * by the end of the name or a separator - so `brief/012-rotate-tokens` names\n * 012, and `brief/012x` names 012x, never 012.\n */\n\nconst SEPARATORS = new Set(['-', '_', '/', '.']);\n\nfunction isIdChar(ch: string): boolean {\n return /^[A-Za-z0-9]$/.test(ch);\n}\n\n/**\n * The offsets in `name` at which a template prefix can end, by dynamic\n * programming over prefix and name, so a template of many stars costs their\n * product and never an exponent.\n */\nfunction prefixEnds(prefix: string, name: string): number[] {\n // reach[j]: the prefix read so far can end at name offset j.\n let reach: boolean[] = new Array<boolean>(name.length + 1).fill(false);\n reach[0] = true;\n for (const token of prefix) {\n const next = new Array<boolean>(name.length + 1).fill(false);\n if (token === '*') {\n let open = false;\n for (let j = 0; j <= name.length; j += 1) {\n if (reach[j]) open = true;\n if (open) next[j] = true;\n if (name.charAt(j) === '/') open = false;\n }\n } else {\n for (let j = 0; j < name.length; j += 1) if (reach[j] && name.charAt(j) === token) next[j + 1] = true;\n }\n reach = next;\n }\n const ends: number[] = [];\n reach.forEach((ok, j) => {\n if (ok) ends.push(j);\n });\n return ends;\n}\n\n/** Why a template cannot be used, or `null`. */\nexport function templateError(template: string): string | null {\n const count = template.split('{id}').length - 1;\n if (count !== 1) return `\"${template}\" must contain {id} exactly once`;\n if (!template.endsWith('{id}')) return `\"${template}\" must end with {id}; whatever follows the id is the branch's own`;\n return null;\n}\n\n/** The brief id a branch name carries under one template, or `null`. */\nexport function idFromBranch(template: string, name: string): string | null {\n if (templateError(template) !== null) return null;\n const prefix = template.slice(0, -'{id}'.length);\n for (const start of prefixEnds(prefix, name)) {\n let end = start;\n while (end < name.length && isIdChar(name.charAt(end))) end += 1;\n if (end === start) continue;\n if (end < name.length && !SEPARATORS.has(name.charAt(end))) continue;\n return name.slice(start, end);\n }\n return null;\n}\n\n/** The first template that yields an id, in order. */\nexport function briefIdFromBranch(templates: readonly string[], name: string): string | null {\n for (const template of templates) {\n const id = idFromBranch(template, name);\n if (id !== null) return id;\n }\n return null;\n}\n\n/**\n * Whether two ids name the same brief: equal as written, or equal as numbers\n * when both are all digits, since a branch says `brief/12` of a brief whose\n * file says `012`.\n */\nexport function sameId(a: string, b: string): boolean {\n if (a === b) return true;\n return /^\\d+$/.test(a) && /^\\d+$/.test(b) && BigInt(a) === BigInt(b);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"branch.js","sourceRoot":"","sources":["../src/branch.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Which brief a branch is working on, read from its name.\n *\n * A round runs on a branch, and naming the branch after the brief is the one\n * convention every repository that runs agents on briefs already has. Reading\n * it from there keeps the harness stateless: no file says which brief is\n * active, so no file can say it wrongly (ADR-0004).\n *\n * A template is a branch name with `{id}` where the id goes and `*` for any\n * run of characters other than `/`: `brief/{id}`, `*\\/brief-{id}`. The id is\n * the longest run of letters and digits at that point, and must be followed\n * by the end of the name or a separator - so `brief/012-rotate-tokens` names\n * 012, and `brief/012x` names 012x, never 012.\n */\n\nconst SEPARATORS = new Set(['-', '_', '/', '.']);\n\nfunction isIdChar(ch: string): boolean {\n return /^[A-Za-z0-9]$/.test(ch);\n}\n\n/**\n * The offsets in `name` at which a template prefix can end, by dynamic\n * programming over prefix and name, so a template of many stars costs their\n * product and never an exponent.\n */\nfunction prefixEnds(prefix: string, name: string): number[] {\n // reach[j]: the prefix read so far can end at name offset j.\n let reach: boolean[] = new Array<boolean>(name.length + 1).fill(false);\n reach[0] = true;\n for (const token of prefix) {\n const next = new Array<boolean>(name.length + 1).fill(false);\n if (token === '*') {\n let open = false;\n for (let j = 0; j <= name.length; j += 1) {\n if (reach[j]) open = true;\n if (open) next[j] = true;\n if (name.charAt(j) === '/') open = false;\n }\n } else {\n for (let j = 0; j < name.length; j += 1) if (reach[j] && name.charAt(j) === token) next[j + 1] = true;\n }\n reach = next;\n }\n const ends: number[] = [];\n reach.forEach((ok, j) => {\n if (ok) ends.push(j);\n });\n return ends;\n}\n\n/** Why a template cannot be used, or `null`. */\nexport function templateError(template: string): string | null {\n const count = template.split('{id}').length - 1;\n if (count !== 1) return `\"${template}\" must contain {id} exactly once`;\n if (!template.endsWith('{id}')) return `\"${template}\" must end with {id}; whatever follows the id is the branch's own`;\n return null;\n}\n\n/** The brief id a branch name carries under one template, or `null`. */\nexport function idFromBranch(template: string, name: string): string | null {\n if (templateError(template) !== null) return null;\n const prefix = template.slice(0, -'{id}'.length);\n for (const start of prefixEnds(prefix, name)) {\n let end = start;\n while (end < name.length && isIdChar(name.charAt(end))) end += 1;\n if (end === start) continue;\n if (end < name.length && !SEPARATORS.has(name.charAt(end))) continue;\n return name.slice(start, end);\n }\n return null;\n}\n\n/**\n * The branch a CI run is building, as the forge names it, for a checkout on\n * no branch: CI checks out a commit, and without the branch a round's own\n * run would name no brief. In order: a GitHub pull request's head branch; the\n * branch a GitHub push built; a GitLab merge request's source branch; the\n * branch a GitLab pipeline built. Each is set only where it names a branch -\n * GitHub's `GITHUB_REF_NAME` names a tag too, so only when `GITHUB_REF_TYPE`\n * says `branch` - and an empty one is not set. `null` outside CI.\n */\nexport function ciBranch(env: Readonly<Record<string, string | undefined>>): { readonly branch: string; readonly source: string } | null {\n const named = (name: string): { branch: string; source: string } | null => {\n const value = env[name]?.trim() ?? '';\n return value === '' ? null : { branch: value, source: name };\n };\n return (\n named('GITHUB_HEAD_REF') ??\n (env['GITHUB_REF_TYPE'] === 'branch' ? named('GITHUB_REF_NAME') : null) ??\n named('CI_MERGE_REQUEST_SOURCE_BRANCH_NAME') ??\n named('CI_COMMIT_BRANCH')\n );\n}\n\n/** The first template that yields an id, in order. */\nexport function briefIdFromBranch(templates: readonly string[], name: string): string | null {\n for (const template of templates) {\n const id = idFromBranch(template, name);\n if (id !== null) return id;\n }\n return null;\n}\n\n/**\n * Whether two ids name the same brief: equal as written, or equal as numbers\n * when both are all digits, since a branch says `brief/12` of a brief whose\n * file says `012`.\n */\nexport function sameId(a: string, b: string): boolean {\n if (a === b) return true;\n return /^\\d+$/.test(a) && /^\\d+$/.test(b) && BigInt(a) === BigInt(b);\n}\n"]}
|
package/dist/cli.d.ts
CHANGED
|
@@ -9,6 +9,6 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { type CliIO } from './workspace.js';
|
|
11
11
|
export { EXIT_ERROR, EXIT_FAILED, EXIT_OK, UsageError, version, type CliIO } from './workspace.js';
|
|
12
|
-
export declare 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
|
|
12
|
+
export declare 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";
|
|
13
13
|
export declare function run(argv: readonly string[], io: CliIO): Promise<number>;
|
|
14
14
|
export declare function main(argv?: readonly string[]): Promise<number>;
|