@descent-vtt/spec-harness 0.5.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 +148 -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 +13 -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 +21 -7
- package/dist/context.js +104 -31
- 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 +5 -3
- 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 +11 -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 +88 -2
- package/dist/vendor/spec-core/pattern/glob.js +234 -32
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spec-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
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
|
@@ -1,5 +1,153 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
spec-harness is before 1.0, and its version says what an upgrade can do to a
|
|
4
|
+
run: a minor release (0.6.x to 0.7.0) may turn a passing run red or change
|
|
5
|
+
what a script reads - a finding on by default that was not, a changed exit
|
|
6
|
+
code, a flag, key or field renamed or removed, each deprecated with a warning
|
|
7
|
+
for a minor release first - and brings new features, while a patch release
|
|
8
|
+
only fixes, reports less or documents, so `^0.7.0` takes only releases that
|
|
9
|
+
cannot turn a run red. The family's policy is
|
|
10
|
+
[spec-core's ADR-0009](https://github.com/DescentVTT/spec-core/blob/main/docs/adr/0009-versions-before-1-0.md).
|
|
11
|
+
|
|
12
|
+
## 0.7.0
|
|
13
|
+
|
|
14
|
+
The audit and `premises` no longer pass on what they could not read, the
|
|
15
|
+
audit says what it measured, both write GitLab, SARIF and GitHub reports,
|
|
16
|
+
`doctor` checks the Claude Code that runs the guard, and a rooted pattern is
|
|
17
|
+
marked wherever it protects, allows or scopes nothing. spec-core is at
|
|
18
|
+
5666c96.
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- `audit` and `premises` take `--format gitlab`, `sarif` and `github`. A
|
|
23
|
+
fingerprint is the rule, the file and the finding's subject, never its
|
|
24
|
+
message or line ([ADR-0005](https://github.com/DescentVTT/spec-harness/blob/main/docs/adr/0005-a-guard-is-a-guardrail.md)).
|
|
25
|
+
- `audit` prints what it measured above the counts line, which stays last,
|
|
26
|
+
and `--format json` and `audit_round` gain `measured`. A brief with no
|
|
27
|
+
assertion says `goals: none declared`.
|
|
28
|
+
- `--format json` findings carry a `subject`; `doctor --format json` gains
|
|
29
|
+
`branchSource`, `allowedSigners.notFido2`, `allowedSigners.problems`,
|
|
30
|
+
`claudeCode.release` and `gitHook`. No `schemaVersion` moves.
|
|
31
|
+
- `doctor` notes signers whose key is not a FIDO2 key (a note, never a
|
|
32
|
+
failure), and says whether git's pre-commit hook runs spec-harness; `init`
|
|
33
|
+
advises that hook when `--git-hook` is not given.
|
|
34
|
+
- The README covers non-Node repositories, merge settings that keep signed
|
|
35
|
+
rulings valid on GitHub and GitLab, a marketplace from a GitLab mirror, and
|
|
36
|
+
the git 2.31 the harness has always needed.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- spec-core at 5666c96: `.//docs` is `docs`, as POSIX reads it, in every
|
|
41
|
+
scope, protection, ruling path and manifest name, where it was rooted.
|
|
42
|
+
Upgrading: check any pattern written `.//x`; `//x` is still rooted.
|
|
43
|
+
- `audit` reports an assertion spec-guard cannot read as
|
|
44
|
+
`assertion-unreadable`, where it dropped it and passed; it fails
|
|
45
|
+
`--strict`. Upgrading: fix the directive spec-guard names.
|
|
46
|
+
- `premises` reports an unreadable premise the same way, counts it, and
|
|
47
|
+
`premises --strict` fails on it. Upgrading: fix the directive, or run
|
|
48
|
+
without `--strict`.
|
|
49
|
+
- A leading `/` roots a pattern, so a rooted protection or ruling path
|
|
50
|
+
protects or allows nothing and a rooted manifest name names nothing: the
|
|
51
|
+
packet marks them, and `audit` warns `protection-rooted`,
|
|
52
|
+
`ruling-path-rooted` or `manifest-name-rooted`, failing `--strict`.
|
|
53
|
+
Upgrading: write the pattern without the leading slash.
|
|
54
|
+
- `doctor` exits 1 when the Claude Code wired to the guard is older than
|
|
55
|
+
2.1.139, which runs every write unguarded; one it cannot find or run is
|
|
56
|
+
unknown, never fine, and fails `--strict`
|
|
57
|
+
([ADR-0012](https://github.com/DescentVTT/spec-harness/blob/main/docs/adr/0012-one-way-into-claude-code.md)).
|
|
58
|
+
Upgrading: `claude update`.
|
|
59
|
+
- `context` shows a cited document's status under `狀態` or `状态` when it
|
|
60
|
+
has no `status` key. Upgrading: nothing; write the key with an ASCII colon.
|
|
61
|
+
|
|
62
|
+
### Fixed
|
|
63
|
+
|
|
64
|
+
- On a detached HEAD, as in CI, the brief is found from the forge's branch
|
|
65
|
+
variable (`GITHUB_HEAD_REF`, `GITHUB_REF_NAME` on a branch push,
|
|
66
|
+
`CI_MERGE_REQUEST_SOURCE_BRANCH_NAME`, `CI_COMMIT_BRANCH`), so `premises`
|
|
67
|
+
no longer fails a round's own CI run
|
|
68
|
+
([ADR-0004](https://github.com/DescentVTT/spec-harness/blob/main/docs/adr/0004-the-active-brief-is-named-not-guessed.md)).
|
|
69
|
+
- A rooted pattern in `affectedFiles` no longer makes spec-guard refuse the
|
|
70
|
+
whole scope; the rules over the other patterns come back.
|
|
71
|
+
- `init --git-hook` writes the hook where git runs it, for an absolute or
|
|
72
|
+
`~` `core.hooksPath` too.
|
|
73
|
+
- `init` names the configured allowed-signers file, suggests a FIDO2 key,
|
|
74
|
+
and says how GitHub, GitLab Premium and GitLab Free protect the rule files.
|
|
75
|
+
- A spec-brief plugin loaded by path is recognised, so `init` no longer adds
|
|
76
|
+
it twice.
|
|
77
|
+
- The `tools` error suggests a `node` command, which Windows can start
|
|
78
|
+
without a shell, where it suggested `npx`.
|
|
79
|
+
- A brief's id is dropped from its title before a full-width colon or an
|
|
80
|
+
unspaced dash too, as spec-brief does.
|
|
81
|
+
|
|
82
|
+
### Documentation
|
|
83
|
+
|
|
84
|
+
- The README is reorganised into lists and tables, and names the spec-brief
|
|
85
|
+
plugin apart from the Claude Code plugin; SECURITY.md says how to report a
|
|
86
|
+
vulnerability privately.
|
|
87
|
+
|
|
88
|
+
## 0.6.0
|
|
89
|
+
|
|
90
|
+
The context packet names a protection or a ruling's path the guard cannot
|
|
91
|
+
read, with what that costs - every write refused, or nothing allowed - and
|
|
92
|
+
spec-core is at 7e41240, which reads a leading `/` on a brace alternative as
|
|
93
|
+
it reads one on a pattern alone. `context --format json` and `start_round`
|
|
94
|
+
gain `unreadableProtections` and `unreadableRulingPaths`.
|
|
95
|
+
|
|
96
|
+
### Changed
|
|
97
|
+
|
|
98
|
+
- spec-core at 7e41240. Its glob reads a leading `/` on a brace
|
|
99
|
+
alternative as it reads one on a pattern written alone, where the slash
|
|
100
|
+
was dropped: `{/docs,src/**}` is `/docs` or `src/**`, where it was `docs`
|
|
101
|
+
or `src/**`. `/docs` is rooted at the filesystem's root, and every path
|
|
102
|
+
the guard decides is repository-relative, so it matches none of them:
|
|
103
|
+
- In a brief's `affectedFiles`, `{/docs,src/**}` no longer puts
|
|
104
|
+
`docs/a.md` in the scope: the guard answers `out-of-scope` where it
|
|
105
|
+
answered `in-scope`, as it does for `/docs` written alone. `src/a.ts`
|
|
106
|
+
is in the scope as before.
|
|
107
|
+
- In `protectedFiles`, `{/docs,migrations/**}` no longer protects
|
|
108
|
+
`docs/a.md`, and in a ruling's paths `{/src/db/schema.ts,x}` no longer
|
|
109
|
+
allows `src/db/schema.ts`, as `/docs` and `/src/db/schema.ts` alone
|
|
110
|
+
protect and allow nothing. The guard can read each, so the packet lists
|
|
111
|
+
it as written, as it lists `/docs`, with no note.
|
|
112
|
+
- `context` and `start_round` ask spec-guard about `/docs`, or about `/`
|
|
113
|
+
when the repository holds no `docs`, where they asked about `docs`, as
|
|
114
|
+
they do for `/docs` written alone. spec-guard refuses a path outside the
|
|
115
|
+
repository, so the packet says the rules could not be read, with what
|
|
116
|
+
spec-guard said, and to treat every ADR as binding.
|
|
117
|
+
- In `dependencies.manifests`, `{/package.json,Gemfile}` no longer names
|
|
118
|
+
`package.json` at any depth, and names no file the round changed, as
|
|
119
|
+
`/package.json` alone names none. `Gemfile` is read as before, and no
|
|
120
|
+
warning names the name, since it can be read.
|
|
121
|
+
- In spec-graph's `patterns`, `{/briefs/**/*.md,docs/**/*.md}` no longer
|
|
122
|
+
reaches `briefs/`, and `init` says spec-graph does not read the briefs
|
|
123
|
+
there, as it says for `/briefs/**/*.md`.
|
|
124
|
+
- A slash after a segment, or before the braces, reads as before:
|
|
125
|
+
`a/{/b,c}` is `a/b` or `a/c`, `/{docs,src}` roots both, and
|
|
126
|
+
`{docs,src}` is `docs` or `src`.
|
|
127
|
+
|
|
128
|
+
### Fixed
|
|
129
|
+
|
|
130
|
+
- `context` and `start_round` name a pattern in `protectedFiles` the guard
|
|
131
|
+
cannot read - malformed, too large to compile, or naming no path, such as
|
|
132
|
+
`{./,src}` - where the protections are listed: it is marked with
|
|
133
|
+
spec-core's reason, the one the guard gives in `because` when it refuses a
|
|
134
|
+
write for it, and with what it does: until it is fixed, the guard refuses
|
|
135
|
+
every write but to the brief, as `unreadable-protection`, which no ruling
|
|
136
|
+
waives. It was listed as written, so the agent learned why every write was
|
|
137
|
+
refused only from the first refusal.
|
|
138
|
+
- A path of a signed ruling the guard cannot read is marked the same way
|
|
139
|
+
where the ruling is listed, as allowing nothing: the guard passes over it.
|
|
140
|
+
It was listed among the paths the ruling allows. The ruling's paths the
|
|
141
|
+
guard reads are listed as before, as is a readable protection.
|
|
142
|
+
- `context --format json` and `start_round`'s structured result list such
|
|
143
|
+
patterns as `unreadableProtections`, each with its `pattern` and
|
|
144
|
+
`reason`, and `unreadableRulingPaths`, each with its `ruling` as well,
|
|
145
|
+
fields added beside `unreadableScope`, so no `schemaVersion` moves; in the
|
|
146
|
+
programmatic API a context packet carries both, each entry of the second
|
|
147
|
+
an `UnreadableRulingPath`. The reasoning is in
|
|
148
|
+
[ADR-0001](https://github.com/DescentVTT/spec-harness/blob/main/docs/adr/0001-the-harness-decides-the-agent-writes.md)'s
|
|
149
|
+
third amendment of 2026-09-29.
|
|
150
|
+
|
|
3
151
|
## 0.5.0
|
|
4
152
|
|
|
5
153
|
The context packet names a scope pattern the guard cannot read, with
|
package/README.md
CHANGED
|
@@ -4,15 +4,22 @@
|
|
|
4
4
|
|
|
5
5
|
spec-harness is the agent-facing member of the [spec-\* tools](https://github.com/DescentVTT/spec-core#the-family). It calls no model and writes no content - the agent is the model. It gives the agent the contract and the facts, and answers, the same way every time, whether the agent stayed inside the lines. No runtime dependencies, no account, no network.
|
|
6
6
|
|
|
7
|
+
The family's words - brief, round, wave, ruling, premise, the two kinds of plugin - are defined in [concepts](https://github.com/DescentVTT/spec-core/blob/main/docs/concepts.md), and the [tutorial](https://github.com/DescentVTT/spec-core/blob/main/docs/tutorial.md) takes one round from the brief to the archive in ten steps.
|
|
8
|
+
|
|
7
9
|
```bash
|
|
8
10
|
npm install --save-dev @descent-vtt/spec-harness @descent-vtt/spec-brief
|
|
9
11
|
npx spec-harness init # the plan: what it would configure, and why
|
|
10
12
|
npx spec-harness init --write # apply it
|
|
11
13
|
```
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
## Requirements
|
|
16
|
+
|
|
17
|
+
- **spec-brief 0.2.0 or later**, required: the brief is the contract, spec-brief is its reader, and 0.2.0 is the first whose archive asks this package's spec-brief plugin about signed rulings.
|
|
18
|
+
- **spec-guard 0.12.0 and spec-graph 0.9.0 or later**, used when they are installed. Their absence is reported, never assumed clean.
|
|
19
|
+
- **Claude Code 2.1.139 or later** runs the guard hooks and the Claude Code plugin's server: the hooks pass the project's path in a hook's `args`, which Claude Code reads from that release on, and the plugin's server names `${CLAUDE_PROJECT_DIR}`, which a Claude Code plugin may from the same release ([ADR-0012](https://github.com/DescentVTT/spec-harness/blob/main/docs/adr/0012-one-way-into-claude-code.md)). An older Claude Code runs every write unguarded, so `doctor` checks the one on `PATH`.
|
|
20
|
+
- **git 2.31 or later**: spec-harness asks git where its shared directory and its hooks are with `rev-parse --path-format`, which that release added (2021).
|
|
14
21
|
|
|
15
|
-
|
|
22
|
+
A sibling installed below its minimum is never run: `doctor` and every command that needed it name the minimum ([ADR-0011](https://github.com/DescentVTT/spec-harness/blob/main/docs/adr/0011-releases-are-staged-by-ci.md)).
|
|
16
23
|
|
|
17
24
|
## A round
|
|
18
25
|
|
|
@@ -30,17 +37,81 @@ person approves brief 012 ─▶ agent on branch brief/012-rotate-tokens
|
|
|
30
37
|
spec-harness audit ─▶ spec-brief archive --dry-run ─▶ person archives
|
|
31
38
|
```
|
|
32
39
|
|
|
40
|
+
### Which brief
|
|
41
|
+
|
|
42
|
+
The first of these names the round's brief; it is never guessed ([ADR-0004](https://github.com/DescentVTT/spec-harness/blob/main/docs/adr/0004-the-active-brief-is-named-not-guessed.md)):
|
|
43
|
+
|
|
44
|
+
1. `--brief <id>`;
|
|
45
|
+
2. `SPEC_BRIEF`;
|
|
46
|
+
3. the branch name: `brief/{id}`, `brief-{id}`, `*/brief/{id}`, `*/brief-{id}`.
|
|
47
|
+
|
|
48
|
+
CI checks out a commit on no branch, so on a detached HEAD the branch is the one the forge's CI names, the first of:
|
|
49
|
+
|
|
50
|
+
| Variable | Set by |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| `GITHUB_HEAD_REF` | a GitHub pull request |
|
|
53
|
+
| `GITHUB_REF_NAME`, when `GITHUB_REF_TYPE` is `branch` | a GitHub push |
|
|
54
|
+
| `CI_MERGE_REQUEST_SOURCE_BRANCH_NAME` | a GitLab merge request pipeline |
|
|
55
|
+
| `CI_COMMIT_BRANCH` | a GitLab branch pipeline |
|
|
56
|
+
|
|
57
|
+
A branch checked out always wins, and `doctor` says which variable named the branch. Without them, a round's own CI run would name no brief, and `premises` would report the premise the round retires as stale.
|
|
58
|
+
|
|
59
|
+
## Commands
|
|
60
|
+
|
|
61
|
+
In the order a round meets them; `init` and `doctor` set a repository up and check it.
|
|
62
|
+
|
|
33
63
|
### `context [brief]`
|
|
34
64
|
|
|
35
|
-
The
|
|
65
|
+
The packet an agent starts a round with: the brief in full - it is the contract - then what it implies:
|
|
66
|
+
|
|
67
|
+
- the files the round may write and must not change, as the guard reads them;
|
|
68
|
+
- the rulings already signed;
|
|
69
|
+
- the briefs it depends on, and whether they are done;
|
|
70
|
+
- the architecture rules spec-guard holds that code to;
|
|
71
|
+
- the documents the brief links to, whole, until the packet reaches its budget (`context.budget`, 60,000 characters). Documents left out are named by path, not dropped. Each is headed by its first level-one heading and the status its front matter gives under `status`, or, without that key, under `狀態` or `状态`, written with YAML's ASCII colon.
|
|
72
|
+
|
|
73
|
+
`--base` names the base the rulings are verified against.
|
|
74
|
+
|
|
75
|
+
**The rules over the scope.** spec-guard is asked about where `affectedFiles` can reach, not the whole repository ([ADR-0001](https://github.com/DescentVTT/spec-harness/blob/main/docs/adr/0001-the-harness-decides-the-agent-writes.md)):
|
|
76
|
+
|
|
77
|
+
- for a pattern, the directory it reaches: `src` for `src/**` or `src/`;
|
|
78
|
+
- for a name with no glob syntax that exists, `src` or `src/a.ts`, that name;
|
|
79
|
+
- for a name the round has yet to create, the directory that will hold it, the root for a name at the top, since it may become a directory as well as a file.
|
|
80
|
+
|
|
81
|
+
Rules spec-guard could not read are named as unread, with what it said, never as none; a repository where no spec file matches spec-guard's patterns has none.
|
|
82
|
+
|
|
83
|
+
**What it could not read is named, never left out.** A cited document whose front matter opens on its first line and is never closed is one spec-core's scanner reads as having no front matter: its status was not read, and the packet says so, with the fix - close the block with `---` on a line of its own - rather than showing a document without one. The note fails nothing; `context` exits 0.
|
|
84
|
+
|
|
85
|
+
A pattern the guard cannot read - malformed, too large to compile, or naming no path, such as `{./,src}` - is named as unread where it is listed, with spec-core's reason, the one the guard gives when it passes over it:
|
|
86
|
+
|
|
87
|
+
| Unreadable in | What the packet says of it | In `context --format json` and `start_round` |
|
|
88
|
+
| --- | --- | --- |
|
|
89
|
+
| `affectedFiles` | It puts no path in the scope, and spec-guard is not asked about it. When no pattern in `affectedFiles` can be read, the packet says the scope could not be read, never that spec-guard holds no rule over it. | `unreadableScope`, each with its `pattern` and `reason` |
|
|
90
|
+
| `protectedFiles` | Until it is fixed, the guard refuses every write but to the brief, and no ruling waives the refusal. | `unreadableProtections`, each with its `pattern` and `reason` |
|
|
91
|
+
| a signed ruling's paths | The guard passes over it, so it allows nothing. | `unreadableRulingPaths`, each with its `pattern`, `reason` and `ruling` |
|
|
92
|
+
|
|
93
|
+
A pattern a leading `/` roots at the filesystem's root, alone or on a brace alternative, such as `/docs` or `{/docs,src/**}`, can be read, and names no path the guard decides, all of which are repository-relative. Where it is listed, it is marked:
|
|
94
|
+
|
|
95
|
+
- in the scope, as putting no path in it: spec-guard is not asked about it, so the rules over the rest of the scope stand;
|
|
96
|
+
- as a protection, as protecting no path;
|
|
97
|
+
- as a ruling's path, as allowing nothing.
|
|
36
98
|
|
|
37
99
|
### `guard <path...>` and the hooks
|
|
38
100
|
|
|
39
|
-
May the round write this?
|
|
101
|
+
May the round write this?
|
|
102
|
+
|
|
103
|
+
| The path | The answer |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| one the brief protects | **Refused**, unless a signed ruling allows it. |
|
|
106
|
+
| one in `affectedFiles` | Allowed. |
|
|
107
|
+
| anything else | **Outside the scope**: a warning by default, or a question to the person, or a refusal (`outOfScope`: `warn`, `ask`, `deny`). |
|
|
40
108
|
|
|
41
|
-
|
|
109
|
+
Paths are compared as the filesystem spells them, links resolved and case corrected, so `SRC/db/schema.ts` does not walk past a protection on `src/db/schema.ts` on Windows.
|
|
42
110
|
|
|
43
|
-
|
|
111
|
+
- `spec-harness hook claude` answers Claude Code's PreToolUse and PostToolUse hooks: a refusal before the write, with the reason and the next step; a warning after a write outside the scope. It never answers `allow`, which would skip the person's own permission prompt.
|
|
112
|
+
- `spec-harness hook git` is a pre-commit hook for any agent or none.
|
|
113
|
+
|
|
114
|
+
A guard is a guardrail - an agent that writes through a shell passes it - so the audit and spec-brief's archive are the gates ([ADR-0005](https://github.com/DescentVTT/spec-harness/blob/main/docs/adr/0005-a-guard-is-a-guardrail.md)).
|
|
44
115
|
|
|
45
116
|
### `escalate`, `rule`, `rulings`
|
|
46
117
|
|
|
@@ -57,13 +128,96 @@ npx spec-harness rule E-012-1 --allow --note "Add rotated_at only."
|
|
|
57
128
|
git commit -S -m "ruling R-012-1: allow" -- briefs/012_rotate-tokens.md
|
|
58
129
|
```
|
|
59
130
|
|
|
60
|
-
A ruling is a row in the brief's `## Rulings` table. It **counts** when the commit that last changed the row is signed by a key the **base branch's** `.github/allowed_signers` lists.
|
|
131
|
+
A ruling is a row in the brief's `## Rulings` table. It **counts** when the commit that last changed the row is signed by a key the **base branch's** `.github/allowed_signers` lists. The file is `rulings.allowedSigners` in `.spec-harness.json`, `.github/allowed_signers` by default.
|
|
132
|
+
|
|
133
|
+
- An agent can write a row and compute any hash; it cannot produce the person's signature, and an edit to the row moves it to a commit that must be signed again ([ADR-0006](https://github.com/DescentVTT/spec-harness/blob/main/docs/adr/0006-a-ruling-is-a-signed-commit.md)).
|
|
134
|
+
- Use a FIDO2 key (`ssh-keygen -t ed25519-sk`) where the agent runs as you: its signature needs a touch no process can supply.
|
|
135
|
+
|
|
136
|
+
**The spec-brief plugin.** spec-brief's archive refuses a round that changed a protected file, and learns that a signed ruling allows it only from this package's spec-brief plugin: `init` adds `"plugins": ["@descent-vtt/spec-harness/spec-brief-plugin"]` to spec-brief's configuration.
|
|
137
|
+
|
|
138
|
+
- Without it, the archive refuses the file whatever was signed.
|
|
139
|
+
- `doctor` says whether spec-brief loads the spec-brief plugin, and `audit` names it as the reason for such a refusal.
|
|
140
|
+
- spec-brief loads a plugin by a path as well, one that starts with `.` or is absolute: a path to this package's spec-brief plugin file counts as loading it.
|
|
61
141
|
|
|
62
|
-
|
|
142
|
+
#### Merge a round with a merge commit
|
|
143
|
+
|
|
144
|
+
A squash or a rebase writes new commits: the forge signs them with its own key or not at all, so on the base branch a ruling's row blames to a commit no allowed signer signed, and it no longer counts. Rulings are verified on the round's branch, before the merge; keep them verifiable after it:
|
|
145
|
+
|
|
146
|
+
- GitHub: allow merge commits for the repository, and turn off *Allow squash merging* and *Allow rebase merging* in its settings, or in the rules for the branch rounds merge into.
|
|
147
|
+
- GitLab: in *Settings > Merge requests*, set *Merge method* to *Merge commit* and *Squash commits when merging* to *Do not allow*. *Merge commit with semi-linear history* and *Fast-forward merge* need the branch rebased, which the *Rebase* button does on the server, writing new commits.
|
|
63
148
|
|
|
64
149
|
### `audit [brief]`
|
|
65
150
|
|
|
66
|
-
One report
|
|
151
|
+
One report on a round, in four parts:
|
|
152
|
+
|
|
153
|
+
| Part | What it reports |
|
|
154
|
+
| --- | --- |
|
|
155
|
+
| The archive | What spec-brief's archive would refuse or warn about, run with `--dry-run`. |
|
|
156
|
+
| Assertions | The brief's own assertions through spec-guard: a goal that fails, or a premise (under a heading such as *The Defect, Measured*) that still holds after the round meant to change it. |
|
|
157
|
+
| Rulings | Rulings whose signatures do not verify. |
|
|
158
|
+
| Dependencies | Every dependency the round added to `package.json`, `Cargo.toml`, `go.mod`, `pyproject.toml`, `requirements*.txt`, NuGet project files or a `Gemfile`. |
|
|
159
|
+
|
|
160
|
+
#### What it could not measure
|
|
161
|
+
|
|
162
|
+
A part that could not be measured is a finding, never a silence. Each of these is a warning, which fails the audit only under `--strict`:
|
|
163
|
+
|
|
164
|
+
- `assertion-unreadable`: an assertion in the brief spec-guard cannot read, on its line, with spec-guard's reason. Nothing it states was run.
|
|
165
|
+
- `manifest-name-unread`: a name in `dependencies.manifests` that spec-core's glob cannot read, malformed or too large to compile, with the reason. The audit reads the manifests the other names name.
|
|
166
|
+
- `protection-rooted` and `ruling-path-rooted`: a pattern in `protectedFiles`, or a path of a signed ruling, that a leading `/` roots, alone or on a brace alternative, such as `/src/db/schema.ts`. It protects no path, so the round could change the file it meant to protect, or allows nothing, so the file is still refused. Write the protection without the slash; a ruling's path is fixed by a new ruling.
|
|
167
|
+
- `manifest-name-rooted`: a name a leading `/` roots at the filesystem's root, alone or on a brace alternative, such as `/package.json` or `{/Gemfile,Cargo.toml}`. Every path the audit reads is repository-relative, so the rooted part names no manifest; write the name without the slash, since a name is matched at any depth.
|
|
168
|
+
|
|
169
|
+
#### What it measured
|
|
170
|
+
|
|
171
|
+
What was measured is said beside what was found, so an audit that found nothing can be told from one that checked nothing ([spec-core's ADR-0005](https://github.com/DescentVTT/spec-core/blob/main/docs/adr/0005-the-family-contract.md)). It is the line above the counts, which stay the last line:
|
|
172
|
+
|
|
173
|
+
```text
|
|
174
|
+
measured: goals: 2 held, 0 failed · premises: 1 retired, 0 holding · archive: asked · rulings: none · dependencies: 3 changed, 0 unread
|
|
175
|
+
0 error(s), 0 warning(s), 1 note(s)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
A brief that declares no assertion says `goals: none declared`, and draws no warning: a brief without assertions is a brief, and the archive still measures its round.
|
|
179
|
+
|
|
180
|
+
`--format json` has the same as `measured`, beside `counts`, and `audit_round` says it too:
|
|
181
|
+
|
|
182
|
+
| Field | Holds |
|
|
183
|
+
| --- | --- |
|
|
184
|
+
| `changes` | `measured` or `unmeasured` |
|
|
185
|
+
| `archive` | `asked` or `unavailable` |
|
|
186
|
+
| `assertions` | `run` or `unavailable` |
|
|
187
|
+
| `goals` | counts: `held`, `failed` |
|
|
188
|
+
| `premises` | counts: `retired`, `holding` |
|
|
189
|
+
| `unreadableAssertions` | a count |
|
|
190
|
+
| `rulings` | counts: `verified`, `unverified` |
|
|
191
|
+
| `dependencies` | counts: `changed`, `unread` |
|
|
192
|
+
|
|
193
|
+
#### For a forge
|
|
194
|
+
|
|
195
|
+
`--format gitlab`, `sarif` or `github` prints the findings for a forge:
|
|
196
|
+
|
|
197
|
+
| Format | Prints | An error, a warning, a note |
|
|
198
|
+
| --- | --- | --- |
|
|
199
|
+
| `gitlab` | a GitLab Code Quality report | `major`, `minor`, `info` |
|
|
200
|
+
| `sarif` | SARIF 2.1.0 | levels `error`, `warning`, `note` |
|
|
201
|
+
| `github` | GitHub workflow commands | `error`, `warning`, `notice` |
|
|
202
|
+
|
|
203
|
+
- Each carries the finding's hint after its message.
|
|
204
|
+
- A finding with no file of its own is placed on the brief, and one with no line on line 1.
|
|
205
|
+
- The fingerprint is the SHA-256 of the rule, the file and the finding's `subject` - the assertion, ruling, dependency or name it is about - never of its message or line, so a reworded message or a line added above does not read as one problem fixed and another found.
|
|
206
|
+
- SARIF carries the fingerprint as `partialFingerprints.specHarnessFinding`, with what was measured as a note.
|
|
207
|
+
|
|
208
|
+
In GitLab CI:
|
|
209
|
+
|
|
210
|
+
```yaml
|
|
211
|
+
spec-harness:
|
|
212
|
+
image: node:22
|
|
213
|
+
script:
|
|
214
|
+
- npm ci
|
|
215
|
+
- npx spec-harness audit --format gitlab > gl-spec-harness.json
|
|
216
|
+
artifacts:
|
|
217
|
+
when: always
|
|
218
|
+
reports:
|
|
219
|
+
codequality: gl-spec-harness.json
|
|
220
|
+
```
|
|
67
221
|
|
|
68
222
|
### `probe [brief]`
|
|
69
223
|
|
|
@@ -82,11 +236,20 @@ signature: expected 401, got 200
|
|
|
82
236
|
```
|
|
83
237
|
````
|
|
84
238
|
|
|
85
|
-
`probe --at base` runs it in a temporary worktree at the base commit: every run must fail **for the declared reason** - the `signature` in the output, or a JUnit `test` failing - or the verdict is `vacuous` (no defect), `flaky` or `invalid`.
|
|
239
|
+
- `probe --at base` runs it in a temporary worktree at the base commit: every run must fail **for the declared reason** - the `signature` in the output, or a JUnit `test` failing - or the verdict is `vacuous` (no defect), `flaky` or `invalid`.
|
|
240
|
+
- `probe --at head` must be green: `fixed`.
|
|
241
|
+
- The evidence table it prints names the hash of the probe it measured with ([ADR-0007](https://github.com/DescentVTT/spec-harness/blob/main/docs/adr/0007-probes-declare-their-failure.md)).
|
|
242
|
+
- Interrupted, it stops every command it started, with everything those started, before it removes the worktree ([ADR-0003](https://github.com/DescentVTT/spec-harness/blob/main/docs/adr/0003-state-outside-the-work-tree.md)).
|
|
86
243
|
|
|
87
244
|
### `premises`
|
|
88
245
|
|
|
89
|
-
Is every live brief still about something true? It runs spec-guard over the live briefs' assertions and reports each premise - an assertion under a section in `assertions.premises` - that no longer holds as `stale-premise`: the defect was fixed another way, or the code the brief describes is gone, and an agent sent to fix it would fix nothing.
|
|
246
|
+
Is every live brief still about something true? It runs spec-guard over the live briefs' assertions and reports each premise - an assertion under a section in `assertions.premises` - that no longer holds as `stale-premise`: the defect was fixed another way, or the code the brief describes is gone, and an agent sent to fix it would fix nothing.
|
|
247
|
+
|
|
248
|
+
- Goals are left to `audit`, since a goal fails until its round is done.
|
|
249
|
+
- For the same reason, the premise of the brief a round is working on - the one `--brief`, `SPEC_BRIEF` or the branch names - is reported as `audit` reports it, `premise-retired`, a note that fails nothing: on the round's branch, a premise that no longer holds is the work being done.
|
|
250
|
+
- A premise spec-guard cannot read is `assertion-unreadable`, a warning, as in the audit: nothing checks it, so `--strict` fails the run on it, and the summary counts it apart from the premises that were checked.
|
|
251
|
+
|
|
252
|
+
Run it in CI: exit 1 when a premise is stale, exit 2 when spec-guard is not there to ask. `--format gitlab`, `sarif` or `github` prints its findings for a forge, as `audit` does.
|
|
90
253
|
|
|
91
254
|
### `mcp`
|
|
92
255
|
|
|
@@ -94,28 +257,103 @@ Is every live brief still about something true? It runs spec-guard over the live
|
|
|
94
257
|
|
|
95
258
|
### `init`
|
|
96
259
|
|
|
97
|
-
Configures the family to agree. It prints the plan, merges into files that exist, keeping what is there, and changes nothing without `--write
|
|
98
|
-
|
|
99
|
-
- **spec-brief**: `spec-brief init` when there is no configuration, then
|
|
100
|
-
-
|
|
101
|
-
-
|
|
102
|
-
- **
|
|
103
|
-
-
|
|
260
|
+
Configures the family to agree. It prints the plan, merges into files that exist, keeping what is there, and changes nothing without `--write`. Each step names its file and what `init` does there: `skip` is a step it does not take and `advise` one it leaves to you, each with the reason.
|
|
261
|
+
|
|
262
|
+
- **spec-brief**: `spec-brief init` when there is no configuration, then:
|
|
263
|
+
- this package's spec-brief plugin in its `plugins`, `"@descent-vtt/spec-harness/spec-brief-plugin"`. spec-brief's archive asks the plugin whether a signed ruling allows a protected file, and refuses the file without it;
|
|
264
|
+
- the base below as its `archiving.base`, unless one is set. Without a base, the archive checks no protected file at all, warning only that the scope went unmeasured, so a plain `spec-brief archive` is a gate only once one is named.
|
|
265
|
+
- **spec-graph**, when it is installed: the archive in its `historyPatterns`, so an archived brief is a record whatever its status says, and a live brief that depends on one is not a stale premise.
|
|
266
|
+
- That holds only where spec-graph reads the briefs, and its default patterns (`docs/`, `doc/`, `adr/`, `rfcs/`, `specs/` and Markdown at the root) do not reach `briefs/`. `init` says whether they do, and leaves adding the briefs to `patterns` to you, since that changes what spec-graph checks, and its `patterns` replace its defaults.
|
|
267
|
+
- It merges into the configuration spec-graph reads, and leaves a `"spec-graph"` key in `package.json` to you.
|
|
268
|
+
- **`.spec-harness.json`**, naming the base rounds are measured from and the allowed signers are read on:
|
|
269
|
+
- the remote's default branch where git recorded one, as a clone does and git 2.48 or later does on a fetch;
|
|
270
|
+
- otherwise the branch `init` runs on when it is `main` or `master`, or the only branch, since a repository made with `git init` and pushed to a remote often records no default;
|
|
271
|
+
- when it cannot tell, it says so and writes none: set `"base"` yourself.
|
|
272
|
+
- **Claude Code**: the guard hooks in `.claude/settings.json` and the MCP server in `.mcp.json`, each run with `node` from the project's `node_modules`. The `npx` entries 0.1 wrote are replaced.
|
|
273
|
+
- These are the ones the [Claude Code plugin](#as-a-claude-code-plugin) brings, so while that plugin is on - `"spec-harness@<marketplace>": true` under `enabledPlugins` in your user, project or local Claude Code settings - `init` writes neither and says why (`skip`).
|
|
274
|
+
- To have its entries instead, which every clone reads, turn the Claude Code plugin off for the project, `claude plugin disable spec-harness@spec-tools --scope local`, and run `init` again.
|
|
275
|
+
- It removes nothing: where the Claude Code plugin is on and a file already holds its entry, it says so (`advise`) and leaves the file alone.
|
|
276
|
+
- **git's pre-commit hook**, where git runs it from: `core.hooksPath`, relative, absolute or under `~`, and the shared hooks of a linked worktree.
|
|
277
|
+
- It refuses a commit that changes what the active brief protects, for any agent or none, a shell's writes included.
|
|
278
|
+
- With `--git-hook` it is written; without, the plan advises it (`advise`).
|
|
279
|
+
- A hook that exists is never replaced: `init` says to add the line to it.
|
|
280
|
+
- **The allowed-signers file** `rulings.allowedSigners` names, `.github/allowed_signers` by default: one line per person, and a FIDO2 key, `ssh-keygen -t ed25519-sk`, for anyone whose agent runs as them (`advise`).
|
|
281
|
+
- With it, how the forge keeps it, the ADRs, the tool configurations and the CI configuration from changing unread, as [spec-core's ADR-0005](https://github.com/DescentVTT/spec-core/blob/main/docs/adr/0005-the-family-contract.md) asks; [spec-core's adopting guide](https://github.com/DescentVTT/spec-core/blob/main/docs/adopting.md) has the settings:
|
|
282
|
+
- GitHub: CODEOWNERS and a protected branch;
|
|
283
|
+
- GitLab Premium: Code Owners;
|
|
284
|
+
- GitLab Free, which has no Code Owners approval: a protected branch no one pushes to, merged by Maintainers, with agents as Developers and pipelines that must succeed.
|
|
104
285
|
|
|
105
286
|
### `doctor`
|
|
106
287
|
|
|
107
|
-
|
|
288
|
+
The first thing to run when a hook refuses something unexpectedly. It reports:
|
|
289
|
+
|
|
290
|
+
- **The siblings**: which are installed, at which versions, and how each is run.
|
|
291
|
+
- A sibling older than this release needs is `outdated`, with the minimum and the command that installs a newer one, and exit 1.
|
|
292
|
+
- A command named under `tools` is run as named, and its version is not checked.
|
|
293
|
+
- **The repository**: its root, the branch, and the brief the flag, `SPEC_BRIEF` or the branch names.
|
|
294
|
+
- **What a signed ruling needs to count**:
|
|
295
|
+
- the base, and whether `--base`, `.spec-harness.json` or the remote named it;
|
|
296
|
+
- whether the allowed-signers file is on that base;
|
|
297
|
+
- whether spec-brief loads this package's spec-brief plugin, by name or by a path to its file.
|
|
298
|
+
- **The signers' keys**: a note naming each signer in the allowed-signers file whose key is not a FIDO2 key (`sk-ssh-ed25519@openssh.com` or `sk-ecdsa-sha2-nistp256@openssh.com`).
|
|
299
|
+
- A note, never a failure: [ADR-0006](https://github.com/DescentVTT/spec-harness/blob/main/docs/adr/0006-a-ruling-is-a-signed-commit.md) also accepts a key the agent's account cannot read, and a PIV or PKCS#11 hardware key reads as a plain `ssh-rsa` or `ecdsa` line.
|
|
300
|
+
- A `cert-authority` line is a certificate authority's key, not a person's, and is left out.
|
|
301
|
+
- **How Claude Code runs the guard**: the Claude Code plugin, with the settings file that turns it on; `init`'s hooks and server; both, which guards every write twice and exits 1, with how to keep one; or neither.
|
|
302
|
+
- **Which Claude Code**, when Claude Code runs the guard: `claude --version`, found on `PATH` and run without a shell.
|
|
303
|
+
- Older than 2.1.139 exits 1. An older release ignores a hook's `args` and runs a bare `node`, which reads the hook's input as a script and fails, and a PreToolUse hook that fails with anything but exit 2 blocks nothing: every write passes unguarded.
|
|
304
|
+
- A `claude` that is not found, a Windows `claude.cmd` shim, which cannot be run without a shell, and a version that cannot be read are *cannot tell*, never fine; `--strict` fails them.
|
|
305
|
+
- The Claude Code in an editor or the desktop app may be another release than the one on `PATH`: check it there with `claude --version` or `/status`.
|
|
306
|
+
- **git's pre-commit hook**: whether it runs spec-harness, where git runs it from: installed, missing, a hook of the repository's own without the line, or, outside Windows, one git skips because it is not executable.
|
|
108
307
|
|
|
109
308
|
## As a Claude Code plugin
|
|
110
309
|
|
|
111
|
-
|
|
310
|
+
spec-harness ships two different things called a plugin. This section is the **Claude Code plugin**, installed into Claude Code. The **spec-brief plugin**, `@descent-vtt/spec-harness/spec-brief-plugin`, is a module spec-brief's archive loads to learn of signed rulings; `init` configures it ([above](#escalate-rule-rulings)).
|
|
311
|
+
|
|
312
|
+
The repository is a Claude Code plugin and a one-plugin marketplace: the four skills (`draft-brief`, `split-goal`, `run-round`, `close-round`), the hooks and the MCP server. The hooks and the server run the `spec-harness` installed in your project, `node ${CLAUDE_PROJECT_DIR}/node_modules/@descent-vtt/spec-harness/bin/spec-harness.js`, so install it there first; the server is told the project with `--root`, since Claude Code starts a plugin's server in the plugin's own directory. It needs Claude Code 2.1.139 or later.
|
|
112
313
|
|
|
113
314
|
```text
|
|
114
315
|
/plugin marketplace add DescentVTT/spec-harness
|
|
115
316
|
/plugin install spec-harness@spec-tools
|
|
116
317
|
```
|
|
117
318
|
|
|
118
|
-
|
|
319
|
+
### From a mirror
|
|
320
|
+
|
|
321
|
+
`DescentVTT/spec-harness` is GitHub shorthand, fetched from github.com. Where GitHub cannot be reached, as on a company network, mirror the repository and add the mirror by its full git URL; the plugin is `spec-harness@spec-tools` either way:
|
|
322
|
+
|
|
323
|
+
```text
|
|
324
|
+
/plugin marketplace add https://gitlab.example.com/tools/spec-harness.git
|
|
325
|
+
/plugin install spec-harness@spec-tools
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### The plugin or `init`, not both
|
|
329
|
+
|
|
330
|
+
The Claude Code plugin's hooks and server are the ones `init` writes into `.claude/settings.json` and `.mcp.json`: use one or the other. With both, every write is guarded twice and the server is registered twice.
|
|
331
|
+
|
|
332
|
+
- `init` run while the plugin is on writes neither.
|
|
333
|
+
- The other order, `init` first and the plugin after, nothing prevents: Claude Code runs a plugin's hooks as the plugin ships them.
|
|
334
|
+
- A plugin hook that stood down because it believed `init`'s ran could leave no guard at all: Claude Code reads no hooks from `.claude/settings.json` in a session that spans several repositories, and managed settings can turn off the settings' hooks while those of a plugin they force on keep running ([ADR-0012](https://github.com/DescentVTT/spec-harness/blob/main/docs/adr/0012-one-way-into-claude-code.md)).
|
|
335
|
+
|
|
336
|
+
So after installing the plugin where `init` has run:
|
|
337
|
+
|
|
338
|
+
1. Run `npx spec-harness doctor`: a double install exits 1.
|
|
339
|
+
2. Take the `spec-harness` entries out of those two files, or turn the plugin off for the project.
|
|
340
|
+
|
|
341
|
+
## In a repository that is not Node
|
|
342
|
+
|
|
343
|
+
The hooks, the server and git's hook run the spec-harness installed in the project, so a .NET, Java or Python repository installs the tools as a Node project does, and its build never sees them:
|
|
344
|
+
|
|
345
|
+
1. A `package.json` at the root holding `{ "private": true }`. `private` keeps npm from ever publishing it.
|
|
346
|
+
2. `npm install --save-dev @descent-vtt/spec-harness @descent-vtt/spec-brief`, and `@descent-vtt/spec-guard` for the rules and the assertions. npm writes the tools as `devDependencies`, and `package-lock.json` pins them.
|
|
347
|
+
3. `node_modules/` in `.gitignore`.
|
|
348
|
+
4. `npm ci` after a clone and in CI, which installs what the lock pins.
|
|
349
|
+
|
|
350
|
+
The hooks name `${CLAUDE_PROJECT_DIR}/node_modules/@descent-vtt/spec-harness/bin/spec-harness.js`, not `npx` or a global install:
|
|
351
|
+
|
|
352
|
+
- Claude Code starts a hook wherever the session stands and a plugin's server in the plugin's directory, so neither can rely on `npx` finding the project's install.
|
|
353
|
+
- On Windows `npx` is a shim that cannot start without a shell.
|
|
354
|
+
- The copy in the project is the version the project pinned, the one `doctor` checks the siblings against.
|
|
355
|
+
|
|
356
|
+
A person who runs the tools by hand runs `npx spec-harness`, from the root.
|
|
119
357
|
|
|
120
358
|
## Configuration
|
|
121
359
|
|
|
@@ -128,17 +366,28 @@ The plugin's hooks and server are the ones `init` writes into `.claude/settings.
|
|
|
128
366
|
| `base` | the remote's default branch | What rounds are measured from, and where allowed signers are read. `init` names it, since a repository git did not clone may have no default branch recorded. |
|
|
129
367
|
| `rulings.section` | `"Rulings"` | The brief section holding the rulings table. |
|
|
130
368
|
| `rulings.allowedSigners` | `".github/allowed_signers"` | The allowed-signers file, read from the base branch. |
|
|
131
|
-
| `dependencies.manifests` | the list
|
|
369
|
+
| `dependencies.manifests` | the list under [`audit`](#audit-brief) | Manifest names whose added dependencies the audit reports. A name that cannot be read is a warning in the audit. |
|
|
132
370
|
| `context.budget` | `60000` | Characters a context packet may hold; cited documents fill what the rest leaves, and those that do not fit are named by path. |
|
|
133
371
|
| `assertions.premises` | `The Defect, Measured`, `Premises`, `Preconditions` | Sections whose assertions state what was true before the round. |
|
|
134
372
|
| `probes.runs`, `probes.timeout` | `2`, `600` | Runs per probe, and seconds per run. |
|
|
135
373
|
| `tools` | found in `node_modules` | A sibling's command by name: `{ "spec-brief": ["node", "path/to/spec-brief.js"] }`. |
|
|
136
374
|
|
|
137
|
-
**Exit codes:**
|
|
375
|
+
**Exit codes:**
|
|
376
|
+
|
|
377
|
+
| Code | Means |
|
|
378
|
+
| --- | --- |
|
|
379
|
+
| `0` | Clean. |
|
|
380
|
+
| `1` | Refused, found something, or waiting on a person. |
|
|
381
|
+
| `2` | The answer cannot be trusted. |
|
|
382
|
+
| `130` | A `probe` interrupted by SIGINT (Ctrl+C) while it runs. |
|
|
383
|
+
| `143` | A `probe` interrupted by SIGTERM. |
|
|
384
|
+
| `129` | A `probe` interrupted by SIGHUP. |
|
|
385
|
+
|
|
386
|
+
An interrupted `probe` exits as a shell reports a process the signal ended.
|
|
138
387
|
|
|
139
388
|
## Design
|
|
140
389
|
|
|
141
|
-
The decisions and what they cost are in [`docs/adr/`](https://github.com/DescentVTT/spec-harness/blob/main/docs/adr/README.md); what the whole family shares is [spec-core's ADR-0005](https://github.com/DescentVTT/spec-core/blob/main/docs/adr/0005-the-family-contract.md).
|
|
390
|
+
The decisions and what they cost are in [`docs/adr/`](https://github.com/DescentVTT/spec-harness/blob/main/docs/adr/README.md); what the whole family shares is [spec-core's ADR-0005](https://github.com/DescentVTT/spec-core/blob/main/docs/adr/0005-the-family-contract.md). Working on spec-harness itself: [CONTRIBUTING.md](https://github.com/DescentVTT/spec-harness/blob/main/CONTRIBUTING.md); reporting a vulnerability: [SECURITY.md](https://github.com/DescentVTT/spec-harness/blob/main/SECURITY.md).
|
|
142
391
|
|
|
143
392
|
## License
|
|
144
393
|
|