spec-ctx 0.4.0 → 0.5.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/README.md +121 -199
- package/dist/adopt/write.js +6 -2
- package/dist/assets.generated.js +1 -1
- package/dist/check/check.js +63 -23
- package/dist/check/hints.js +10 -9
- package/dist/cli/args.js +3 -1
- package/dist/cli/commands.js +96 -23
- package/dist/cli/help.js +23 -6
- package/dist/cli/workflow.js +31 -12
- package/dist/cli.js +45 -6
- package/dist/config.js +8 -0
- package/dist/graph/bundle.js +155 -27
- package/dist/graph/files.js +31 -0
- package/dist/graph/input.js +37 -0
- package/dist/graph/links.js +96 -0
- package/dist/graph/path.js +54 -0
- package/dist/graph/query.js +3 -0
- package/dist/graph/related.js +58 -11
- package/dist/handoff/handoff.js +6 -4
- package/dist/handoff/questions.js +9 -1
- package/dist/index/build.js +33 -4
- package/dist/index/clock.js +55 -0
- package/dist/index/scan.js +36 -9
- package/dist/index/structure.js +97 -0
- package/dist/init/init.js +15 -4
- package/dist/journal/journal.js +65 -0
- package/dist/journal/note.js +54 -0
- package/dist/journal/report.js +115 -0
- package/dist/journal/signals.js +26 -0
- package/dist/loop/fails.js +71 -12
- package/dist/loop/next.js +38 -17
- package/dist/main.js +16 -1
- package/dist/runcmd.js +5 -0
- package/dist/tasks/evidence.js +3 -3
- package/dist/util/redact.js +57 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/specctx-run/SKILL.md +3 -3
package/README.md
CHANGED
|
@@ -25,19 +25,84 @@ Three things go wrong.
|
|
|
25
25
|
| Each session starts cold. | `status`, `task next`, and the generated handoff say where the work stands. |
|
|
26
26
|
| The agent ticks `[x]` by hand. | `[x]` appears only after the Verify command of the task exits 0. The run leaves an evidence file. |
|
|
27
27
|
|
|
28
|
-
A later edit can break a task that passed. `check --strict` names each task whose files changed after its Verify passed.
|
|
28
|
+
A later edit can break a task that passed. `check --strict` names each task whose files changed after its Verify passed. While a change has an open task or no task, it prints a `deferred:` line for the delta items it did not test yet.
|
|
29
29
|
|
|
30
30
|
No LLM call. No daemon. The same files give the same output.
|
|
31
31
|
|
|
32
|
+
## Try it in two minutes
|
|
33
|
+
|
|
34
|
+
Paste this block in a shell. It builds a toy project, then runs the loop on one task. It needs Node 22, `git`, and `npx`.
|
|
35
|
+
|
|
36
|
+
<!-- try:begin -->
|
|
37
|
+
```bash
|
|
38
|
+
set -e
|
|
39
|
+
mkdir -p spec-ctx-try && cd spec-ctx-try && git init -q
|
|
40
|
+
mkdir -p openspec/specs/user-auth openspec/changes/add-mfa/specs/user-auth src tests
|
|
41
|
+
cat > openspec/specs/user-auth/spec.md <<'MD'
|
|
42
|
+
# user-auth
|
|
43
|
+
|
|
44
|
+
## Requirements
|
|
45
|
+
|
|
46
|
+
### Requirement: Login with a password
|
|
47
|
+
The system SHALL let a user log in with a username and a password.
|
|
48
|
+
|
|
49
|
+
#### Scenario: Correct password
|
|
50
|
+
- **WHEN** a user enters a correct password
|
|
51
|
+
- **THEN** the user is logged in
|
|
52
|
+
MD
|
|
53
|
+
cat > openspec/changes/add-mfa/proposal.md <<'MD'
|
|
54
|
+
## Why
|
|
55
|
+
Passwords alone are weak.
|
|
56
|
+
|
|
57
|
+
## What Changes
|
|
58
|
+
- Ask for a TOTP code after the password.
|
|
59
|
+
MD
|
|
60
|
+
cat > openspec/changes/add-mfa/specs/user-auth/spec.md <<'MD'
|
|
61
|
+
## ADDED Requirements
|
|
62
|
+
|
|
63
|
+
### Requirement: TOTP second factor
|
|
64
|
+
The system SHALL ask for a TOTP code after a correct password.
|
|
65
|
+
|
|
66
|
+
#### Scenario: Correct code
|
|
67
|
+
- **WHEN** a user enters a correct TOTP code
|
|
68
|
+
- **THEN** the user is logged in
|
|
69
|
+
MD
|
|
70
|
+
cat > openspec/changes/add-mfa/tasks.md <<'MD'
|
|
71
|
+
## 1. Login
|
|
72
|
+
|
|
73
|
+
- [ ] 1.1 Add the TOTP prompt to the login form
|
|
74
|
+
MD
|
|
75
|
+
printf '## Context\nThe login form has one step.\n' > openspec/changes/add-mfa/design.md
|
|
76
|
+
printf 'export const prompt = "password";\n' > src/login.ts
|
|
77
|
+
npx -y spec-ctx init --all
|
|
78
|
+
npx -y spec-ctx adopt
|
|
79
|
+
sed -i.bak 's/^- \[ \] 1.1 Add the TOTP prompt to the login form$/- [ ] 1.1 Add the TOTP prompt to the login form [REQ-UA-002] Verify: `grep -q totp src\/login.ts`/' openspec/changes/add-mfa/tasks.md && rm openspec/changes/add-mfa/tasks.md.bak
|
|
80
|
+
printf '// Covers: REQ-UA-002\n' > tests/login.test.ts
|
|
81
|
+
npx -y spec-ctx status
|
|
82
|
+
npx -y spec-ctx task next
|
|
83
|
+
npx -y spec-ctx task start add-mfa/1.1
|
|
84
|
+
npx -y spec-ctx task done add-mfa/1.1 || echo "refused, as expected"
|
|
85
|
+
printf '// REQ-UA-002\nexport const prompt = "password, then totp";\n' > src/login.ts
|
|
86
|
+
npx -y spec-ctx task done add-mfa/1.1
|
|
87
|
+
npx -y spec-ctx context add-mfa/1.1
|
|
88
|
+
npx -y spec-ctx context src/login.ts
|
|
89
|
+
npx -y spec-ctx handoff write
|
|
90
|
+
```
|
|
91
|
+
<!-- try:end -->
|
|
92
|
+
|
|
93
|
+
On this toy project the bundle footer shows more than 100 percent of the spec text, because the tree is tiny. On a real repo the share is small. Step 4 of the next section shows 3.6 percent.
|
|
94
|
+
|
|
95
|
+
`status` prints a `next:` line that asks for plan approval with `handoff ask --plan`. The `specctx-run` skill handles that step. This demo skips it.
|
|
96
|
+
|
|
32
97
|
## See it work
|
|
33
98
|
|
|
34
99
|
Six commands on a small project. Each clip is one command.
|
|
35
100
|
|
|
36
101
|
### 1. Give every requirement an ID
|
|
37
102
|
|
|
38
|
-
OpenSpec names a requirement by its header text. A reword breaks every link to it. `adopt` gives each requirement and scenario an ID that stays.
|
|
103
|
+
OpenSpec names a requirement by its header text. A reword breaks every link to it. `adopt` gives each requirement and scenario an ID that stays.
|
|
39
104
|
|
|
40
|
-

|
|
105
|
+

|
|
41
106
|
|
|
42
107
|
You see: the dry run prints each header and the ID it gets. Then `adopt` writes them.
|
|
43
108
|
|
|
@@ -45,54 +110,60 @@ You see: the dry run prints each header and the ID it gets. Then `adopt` writes
|
|
|
45
110
|
|
|
46
111
|
`status` shows the open change, the task counts, and the next command to run.
|
|
47
112
|
|
|
48
|
-

|
|
113
|
+

|
|
49
114
|
|
|
50
115
|
You see: one change, two tasks done, one open, and the hint.
|
|
51
116
|
|
|
52
117
|
### 3. What is next
|
|
53
118
|
|
|
54
|
-
`task next` picks the first open task. It prints the
|
|
119
|
+
`task next` picks the first open task. It prints the requirements the task cites and its Verify command, the shell command that proves the task is done.
|
|
55
120
|
|
|
56
|
-

|
|
121
|
+

|
|
57
122
|
|
|
58
123
|
You see: task `add-mfa/1.3`, its requirement, and `grep -q totp src/login.ts`.
|
|
59
124
|
|
|
60
125
|
### 4. Only the spec this task needs
|
|
61
126
|
|
|
62
|
-
`task start` prints the
|
|
127
|
+
`task start` prints the context of the task. It holds the task line, the requirements it cites with their scenarios, the code and test lines that tag them, and the decisions. The blocks fit a character budget. The last line compares that size with all spec text.
|
|
63
128
|
|
|
64
|
-

|
|
129
|
+

|
|
65
130
|
|
|
66
131
|
You see: 711 characters, 3.6 percent of the tree. The agent reads this instead of the tree.
|
|
67
132
|
|
|
68
133
|
### 5. Done means Verify passed
|
|
69
134
|
|
|
70
|
-
`task done` runs the Verify command. On exit 1 the box stays open. On exit 0 the box flips to `[x]` and an evidence file records the files that were checked.
|
|
135
|
+
`task done` runs the Verify command. On exit 1 the box stays open. On exit 0 the box flips to `[x]` and an evidence file records the files that were checked.
|
|
71
136
|
|
|
72
|
-

|
|
137
|
+

|
|
73
138
|
|
|
74
139
|
You see: a red refusal, the edit, then a green done line.
|
|
75
140
|
|
|
76
141
|
### 6. Hand off to the next session
|
|
77
142
|
|
|
78
|
-
`handoff write` puts the state in one file: the change, the done tasks, the current task, and the open questions. The next session starts from it.
|
|
143
|
+
`handoff write` puts the state in one file: the change, the done tasks, the current task, and the open questions. The next session starts from it.
|
|
79
144
|
|
|
80
|
-

|
|
145
|
+

|
|
81
146
|
|
|
82
147
|
You see: the head of `.ctx/handoff.md`.
|
|
83
148
|
|
|
84
|
-
The transcript of all six clips is in `docs/demo/loop.txt`.
|
|
149
|
+
The transcript of all six clips is in `docs/demo/loop.txt`. `bash docs/demo/record.sh` records them again.
|
|
85
150
|
|
|
86
151
|
## Quickstart
|
|
87
152
|
|
|
153
|
+
spec-ctx needs Node 22. Run it in a project after `openspec init`.
|
|
154
|
+
|
|
88
155
|
```bash
|
|
89
156
|
npx -y spec-ctx init --all # config, ID templates, rules, and the agent skill
|
|
157
|
+
npx -y spec-ctx adopt # only when your specs have no IDs yet
|
|
90
158
|
npx -y spec-ctx task next # the next task and the command that proves it
|
|
91
159
|
npx -y spec-ctx task start <task> # only the spec that this task needs
|
|
92
160
|
npx -y spec-ctx task done <task> # runs that command. A pass ticks the box.
|
|
93
161
|
```
|
|
94
162
|
|
|
95
|
-
|
|
163
|
+
- `adopt --dry-run` shows the ID that each header gets and writes nothing.
|
|
164
|
+
- `adopt --dry-run` exits 1 when headers need IDs. A script with `set -e` must allow that code.
|
|
165
|
+
- [`docs/getting-started.md`](https://github.com/sonqh/spec-ctx/blob/main/docs/getting-started.md) walks the fresh path with real output.
|
|
166
|
+
- [`docs/reference.md`](https://github.com/sonqh/spec-ctx/blob/main/docs/reference.md) holds the install options, the path for an existing project, every command, and the config file.
|
|
96
167
|
|
|
97
168
|
## How it finds the context
|
|
98
169
|
|
|
@@ -105,9 +176,9 @@ graph LR
|
|
|
105
176
|
R -->|"Covers:"| X["tests/login.test.ts"]
|
|
106
177
|
```
|
|
107
178
|
|
|
108
|
-
The ID is the stable key. A reword of a header does not change it. The index is the graph of IDs: tasks, requirements, scenarios, decisions, code tags, and test tags.
|
|
179
|
+
The ID is the stable key. A reword of a header does not change it. The index is the graph of IDs: tasks, requirements, scenarios, decisions, code tags, and test tags. A decision record joins the graph through its `Refs:` line.
|
|
109
180
|
|
|
110
|
-
`specctx
|
|
181
|
+
`specctx context <task|ID|path|change>` is the one command to run before you edit. It walks the edges inside a character budget and prints one provenance line for each block. When a block does not fit, a `truncated:` line names it. `related` prints each neighbor and the reason for the link:
|
|
111
182
|
|
|
112
183
|
```text
|
|
113
184
|
$ specctx related REQ-UA-004
|
|
@@ -118,160 +189,17 @@ code src/login.ts:1 active src/login.ts:1 implements REQ-UA-004
|
|
|
118
189
|
test tests/login.test.ts:1 active tests/login.test.ts:1 covers REQ-UA-004
|
|
119
190
|
```
|
|
120
191
|
|
|
192
|
+
The reference has the budget rules and the lookups by file, by change, and by task.
|
|
193
|
+
|
|
121
194
|
## The life cycle for agents
|
|
122
195
|
|
|
123
196
|
`init --all` installs one skill, `specctx-run`. The user types one prompt, such as "build this" or "continue". The skill loops until the change reaches the archive or a human must decide.
|
|
124
197
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
- `specctx-bootstrap`, `specctx-next`, and `specctx-done` stay for one release as aliases. Each one says: run `specctx-run`.
|
|
129
|
-
|
|
130
|
-
The commands the skill runs for one task:
|
|
131
|
-
|
|
132
|
-
```bash
|
|
133
|
-
specctx init --all # config, ID templates, rules, and the skill
|
|
134
|
-
specctx next --json # the next action, its task, and its command
|
|
135
|
-
specctx task start <task> # the bundle of that task and its size
|
|
136
|
-
specctx task done <task> # run Verify, write evidence, set the checkbox
|
|
137
|
-
specctx gate --strict # check, baseline, linters, openspec validate, length
|
|
138
|
-
specctx handoff write # .ctx/handoff.md for the next session
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
The rule: the agent never opens `openspec/specs/` by hand. It uses `task start` or `bundle`. It never ticks a checkbox and never runs `/opsx:apply`. The skill is in [`skills/`](skills/). Install it alone with `npx skills add sonqh/spec-ctx` or `specctx init --skills`.
|
|
142
|
-
|
|
143
|
-
## Install
|
|
144
|
-
|
|
145
|
-
spec-ctx needs Node 22. Run it in a project after `openspec init`.
|
|
146
|
-
|
|
147
|
-
```bash
|
|
148
|
-
npx -y spec-ctx init --all # from npm
|
|
149
|
-
npm install -g spec-ctx # puts specctx on your path
|
|
150
|
-
npx -y github:sonqh/spec-ctx#<sha> init --all # from git, for an unreleased commit
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
The git form builds the package on install with Node only. Pin a commit SHA. A branch name such as `main` also works, but it moves.
|
|
154
|
-
|
|
155
|
-
The command writes `spec-ctx.toml`, copies the templates with ID placeholders to `openspec/templates/spec-driven-ids/`, adds the spec-ctx `rules` and `operations` to `openspec/config.yaml`, and installs the `specctx-run` skill and its three aliases into `.claude/skills/` and `.agents/skills/`. The project keeps the stock `schema: spec-driven`. Upstream OpenSpec injects the rules into every artifact instruction. It pins the OpenSpec version it finds in `[gate] openspec`. It adds four lines to `.gitignore`: `.ctx/index.json`, `adopt-report.md`, `.ctx/current.json`, and `.ctx/gate-log.jsonl`. The rest of `.ctx/` is meant for git, `questions.json` included.
|
|
156
|
-
|
|
157
|
-
The examples below use `specctx`. With `npx`, write `npx -y spec-ctx` in its place. Each GitHub release also carries a single binary for Linux and macOS.
|
|
158
|
-
|
|
159
|
-
## Two paths
|
|
160
|
-
|
|
161
|
-
[`docs/getting-started.md`](docs/getting-started.md) walks the fresh path on upstream OpenSpec with real output, for a developer who knows OpenSpec.
|
|
162
|
-
|
|
163
|
-
### Fresh project
|
|
164
|
-
|
|
165
|
-
```bash
|
|
166
|
-
npx -y @fission-ai/openspec@latest init --tools <tool>
|
|
167
|
-
npx spec-ctx init --all
|
|
168
|
-
specctx gate --strict
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
`--tools <tool>` names your agent, such as `claude`, and skips the interactive picker. `--no-animation` keeps logs clean. `init --all` merges the ID rules into an existing `rules` key of `openspec/config.yaml` item by item and prints `merged openspec/config.yaml rules (<n> added)`.
|
|
172
|
-
|
|
173
|
-
### Existing OpenSpec project
|
|
174
|
-
|
|
175
|
-
```bash
|
|
176
|
-
npx spec-ctx init --all
|
|
177
|
-
specctx adopt --dry-run # the ID that each header gets. Writes nothing.
|
|
178
|
-
specctx adopt # writes the IDs into main specs and active deltas
|
|
179
|
-
specctx baseline write
|
|
180
|
-
specctx gate --strict
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
- `adopt` gives an ID to every requirement and scenario header without one, in `openspec/specs/` and in the deltas of active changes. A delta header gets the main ID by its text or through its RENAMED line. A delta header that matches neither keeps its text and goes into `adopt-report.md`.
|
|
184
|
-
- `adopt` derives a capability code from each folder name, for example `UA` for `user-auth`. It writes the codes into `spec-ctx.toml`. Rename a code there before the IDs spread.
|
|
185
|
-
- `adopt` runs `check` before and after the write. A finding that the write added undoes every file. A second run prints `nothing to adopt`.
|
|
186
|
-
- `baseline write` records the debt of the day in `.ctx/baseline.json`. The debt has five kinds. Ticked tasks without evidence. Legacy tasks without a citation or a Verify code span. Delta headers that match no main requirement. Hard lint findings per file and rule on the gate globs. `check` hides that debt and prints `baselined: <n>`. `gate` fails the lint step only when a count rises. New debt still shows.
|
|
187
|
-
- `gate` compares `.ctx/baseline.json` with the copy at the merge base of `HEAD` and `[gate] base`, default `origin/main`. A new entry fails the `baseline` step. Commit a grown baseline with a subject that starts with `baseline:` to pass once. In CI, check out with `fetch-depth: 0`, so the merge base exists. Without git or a merge base the step prints `skipped`.
|
|
188
|
-
- `[gate] markdown` in `spec-ctx.toml` names the markdown globs that `gate` lints. `init` writes `[]`, so the gate lints nothing until you opt in. The comment in the file names the globs this repository uses.
|
|
189
|
-
- Legacy tasks stay in the baseline until an agent adds `[REQ-...]` citations and a `Verify:` code span. `specctx baseline prune` then drops them. No command adds a single entry.
|
|
190
|
-
|
|
191
|
-
Both paths have an end to end test on upstream OpenSpec: `tests/paths/fresh.sh` and `tests/paths/existing.sh`. `tests/paths/rules-only.sh` shows that the merged `rules` reach the stock `spec-driven` schema.
|
|
192
|
-
|
|
193
|
-
## Commands
|
|
194
|
-
|
|
195
|
-
| Command | Use |
|
|
196
|
-
| --- | --- |
|
|
197
|
-
| `build` | Write `.ctx/index.json`. Every other command rebuilds a stale index first. |
|
|
198
|
-
| `status` | Active changes with task counts, the current task, and open questions. |
|
|
199
|
-
| `next` | The next action of the loop from the files on disk: `plan`, `cite`, `start`, `work`, `fix`, `repair`, `gate`, `archive`, `ask`, or `stop`. `--json` prints one object. Flag: `--change`. |
|
|
200
|
-
| `query <term...>` | Entries that hold every term. Flags: `--kind`, `--change`, `--limit`, `--all`. |
|
|
201
|
-
| `related <ID>` | Linked entries with the reason for each link. Flags: `--depth`, `--all`. |
|
|
202
|
-
| `bundle <ID...>` | Spec text of the IDs, their scenarios, decisions, and neighbors. Flags: `--budget`, `--depth`, `--all`. |
|
|
203
|
-
| `check` | ID and link integrity. `--strict` adds test coverage and evidence. `retitle-needs-renamed` names a retitled delta header that needs a RENAMED pair before `openspec archive`. |
|
|
204
|
-
| `id new`, `id retire` | Allocate and retire IDs. |
|
|
205
|
-
| `task next`, `start`, `verify`, `done`, `reopen` | The task state machine. |
|
|
206
|
-
| `handoff write`, `ask`, `resolve` | The session handoff and its open questions in `.ctx/questions.json`. |
|
|
207
|
-
| `init` | Config file, ID templates and rules, and skills. Flags: `--schema`, `--skills`, `--all`, `--force`. |
|
|
208
|
-
| `adopt` | IDs for a project without them. `--dry-run` writes nothing. |
|
|
209
|
-
| `baseline write`, `prune` | The debt ratchet in `.ctx/baseline.json`. |
|
|
210
|
-
| `gate` | `check`, the baseline against the merge base, both markdown linters, `openspec validate`, and the 500 character length check on active deltas. |
|
|
211
|
-
| `lint tells`, `lint ste` | The two markdown linters. Flags: `--baseline`, `--disable`, `--json`, `--selftest`. |
|
|
212
|
-
| `help <command>` | The page of one command. `specctx <command> --help` prints the same page. |
|
|
213
|
-
|
|
214
|
-
Every command takes `--root <path>` and `--json`. A task ID takes the change key, such as `008/1.1` or `add-mfa/1.1`, or the full folder name. Outside an OpenSpec project every command but `init` exits 2. Exit codes: 0 for success, 1 for findings or a failed Verify command, 2 for a usage error, 3 for an internal error.
|
|
215
|
-
|
|
216
|
-
## Config reference
|
|
217
|
-
|
|
218
|
-
`spec-ctx.toml` sits at the project root. Every key is optional.
|
|
219
|
-
|
|
220
|
-
```toml
|
|
221
|
-
[scan]
|
|
222
|
-
specs = ["openspec/specs/**/*.md"] # main specs
|
|
223
|
-
changes = ["openspec/changes/**/*.md"] # active and archived changes
|
|
224
|
-
decisions = ["decisions/*.md"] # decision records
|
|
225
|
-
code = ["src/**"] # files with code tags
|
|
226
|
-
tests = ["tests/**"] # files with Covers tags and scenario IDs
|
|
227
|
-
ignore = [] # globs that the scan skips
|
|
228
|
-
|
|
229
|
-
[ids]
|
|
230
|
-
caps = [] # capability codes. Empty means any code is valid.
|
|
231
|
-
task_pattern = '\d+\.\d+' # the task number in a tasks.md checkbox
|
|
232
|
-
brackets = false # true: adopt, id new, and init --schema write [REQ-UA-001] in headers. The scan reads both forms.
|
|
233
|
-
|
|
234
|
-
[ids.paths]
|
|
235
|
-
UA = "user-auth" # capability path of each code. adopt writes the derived ones.
|
|
236
|
-
|
|
237
|
-
[bundle]
|
|
238
|
-
budget = 8000 # characters
|
|
239
|
-
depth = 1
|
|
240
|
-
|
|
241
|
-
[verify]
|
|
242
|
-
timeout_s = 600
|
|
243
|
-
|
|
244
|
-
[gate]
|
|
245
|
-
markdown = [] # globs that gate lints. Empty means no lint. This repo opts in.
|
|
246
|
-
openspec = "npx -y @fission-ai/openspec@1.15.0" # the command that runs OpenSpec. init pins the version it found.
|
|
247
|
-
base = "origin/main" # the ref whose merge base holds the baseline that gate compares with
|
|
248
|
-
|
|
249
|
-
[loop]
|
|
250
|
-
max_attempts = 3 # Verify failures on one task before task done writes a question
|
|
251
|
-
archive = "ask" # "ask" stops before archive for a human. "auto" lets the loop archive.
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
With `brackets = true`, `init --schema` installs templates with `[REQ-<CAP>-NNN]` and `[SCN-<CAP>-NNN-N]` in the headers. The merged rules show the same form. Task citations, `Refs:` lines, and tags stay bare. After you change the value, run `specctx init --schema` again. It switches each template and rule that you did not edit. It keeps the ones you edited, unless you add `--force`.
|
|
255
|
-
|
|
256
|
-
Two environment variables change the engine:
|
|
257
|
-
|
|
258
|
-
| Variable | Effect |
|
|
259
|
-
| --- | --- |
|
|
260
|
-
| `SPECCTX_OPENSPEC` | The command that `gate` runs for `openspec validate` and `next` runs for `openspec status`. It overrides `[gate] openspec` for one run. At `init` time it is written into the file as it is, and no version detection runs. Tests and offline machines set it. |
|
|
261
|
-
| `SPECCTX_BASELINE_SUBJECT` | The commit subject that the `baseline` step of `gate` tests for `baseline:`. It replaces the subject of `HEAD`. Tests set it. |
|
|
262
|
-
|
|
263
|
-
## Markdown linters
|
|
264
|
-
|
|
265
|
-
`specctx lint tells` and `specctx lint ste` replace `scripts/tells-lint.py` and `scripts/ste-lint.py` from the ste-humanize skill. They take the same flags and give the same output and exit codes. They need Node only.
|
|
198
|
+
1. It runs `specctx next --json`. The engine reads the files on disk and prints one action, such as `start`, `fix`, or `archive`.
|
|
199
|
+
2. It runs the exact commands of that action.
|
|
200
|
+
3. It runs `specctx handoff write` and repeats.
|
|
266
201
|
|
|
267
|
-
|
|
268
|
-
# before
|
|
269
|
-
python3 scripts/tells-lint.py --baseline 0 README.md
|
|
270
|
-
python3 scripts/ste-lint.py --baseline 0 --disable synonym-rotation README.md
|
|
271
|
-
# after
|
|
272
|
-
npx spec-ctx lint tells --baseline 0 README.md
|
|
273
|
-
npx spec-ctx lint ste --baseline 0 --disable synonym-rotation README.md
|
|
274
|
-
```
|
|
202
|
+
It stops for a human at the plan and before archive. A Verify command that fails three times writes a question and stops too. The agent never opens `openspec/specs/` by hand, never ticks a checkbox, and never runs `/opsx:apply`. `specctx-bootstrap`, `specctx-next`, and `specctx-done` stay for one release as aliases of `specctx-run`. The skill is in [`skills/`](https://github.com/sonqh/spec-ctx/blob/main/skills/).
|
|
275
203
|
|
|
276
204
|
## GitHub Action
|
|
277
205
|
|
|
@@ -282,29 +210,7 @@ npx spec-ctx lint ste --baseline 0 --disable synonym-rotation README.md
|
|
|
282
210
|
strict: true
|
|
283
211
|
```
|
|
284
212
|
|
|
285
|
-
The action runs `specctx gate`: `check --strict`, both markdown linters, `openspec validate --all --strict`, and the length check.
|
|
286
|
-
|
|
287
|
-
### Rerun
|
|
288
|
-
|
|
289
|
-
```yaml
|
|
290
|
-
on: pull_request
|
|
291
|
-
permissions:
|
|
292
|
-
contents: read
|
|
293
|
-
jobs:
|
|
294
|
-
rerun:
|
|
295
|
-
runs-on: ubuntu-latest
|
|
296
|
-
steps:
|
|
297
|
-
- uses: actions/checkout@v4
|
|
298
|
-
with: { fetch-depth: 0 }
|
|
299
|
-
- uses: sonqh/spec-ctx@v0
|
|
300
|
-
with:
|
|
301
|
-
rerun: true
|
|
302
|
-
base: origin/${{ github.base_ref }}
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
With `rerun: true` the action runs `specctx gate --rerun` and no other step. It runs the Verify command of each task that the pull request closed and compares the exit code with the evidence. `base` is the ref for the merge base. Without `rerun`, the action ignores `base`.
|
|
306
|
-
|
|
307
|
-
On `push`, the default base gives a merge base equal to HEAD, so the rerun compares nothing. It prints one line and passes. Pass `base: ${{ github.event.before }}` on push. A push that creates a branch gives forty zeros. The rerun prints `rerun: base is the zero commit (a new branch), nothing to compare` and passes. After a force push the old commit may be missing from the clone. The rerun then prints `rerun: base <ref> is not in this clone. Fetch it or use fetch-depth: 0` and exits 1.
|
|
213
|
+
The action runs `specctx gate`: `check --strict`, both markdown linters, `openspec validate --all --strict`, and the length check. With `rerun: true` it runs the Verify command of each task that the pull request closed. The reference has the full workflow.
|
|
308
214
|
|
|
309
215
|
A Verify command is code from the pull request. The boundary:
|
|
310
216
|
|
|
@@ -315,28 +221,44 @@ A Verify command is code from the pull request. The boundary:
|
|
|
315
221
|
- For fork pull requests, turn on the repository setting that asks for approval before a workflow runs.
|
|
316
222
|
- Check out with `fetch-depth: 0`. The merge base needs full history.
|
|
317
223
|
|
|
224
|
+
Tests and offline machines set `SPECCTX_OPENSPEC` to the command that runs OpenSpec.
|
|
225
|
+
|
|
318
226
|
## Status
|
|
319
227
|
|
|
320
|
-
- Published: `spec-ctx@0.
|
|
321
|
-
- Archived: 001 to 025, 027 to 034, and
|
|
322
|
-
-
|
|
323
|
-
- Upstream: the ID scheme is proposed as a comment on [OpenSpec#1967](https://github.com/Fission-AI/OpenSpec/issues/1967#issuecomment-6414314730). The schema bundle is submitted to the community table in [OpenSpec#2157](https://github.com/Fission-AI/OpenSpec/issues/2157). No pull request until a maintainer asks.
|
|
228
|
+
- Published: `spec-ctx@0.5.0` on npm, with binaries on the GitHub release. See `CHANGELOG.md` for each release.
|
|
229
|
+
- Archived: 001 to 025, 027 to 034, 036 to 053, and 055 to 057. No change is open.
|
|
230
|
+
- Upstream: the ID scheme is proposed in [OpenSpec#1967](https://github.com/Fission-AI/OpenSpec/issues/1967#issuecomment-6414314730). The schema bundle is in the community table of [OpenSpec#2157](https://github.com/Fission-AI/OpenSpec/issues/2157).
|
|
324
231
|
|
|
325
232
|
## Measured
|
|
326
233
|
|
|
327
|
-
`specctx stats --write` measures this repository after each change. The newest file is `docs/metrics/2026-
|
|
234
|
+
`specctx stats --write` measures this repository after each change. The newest file is `docs/metrics/2026-11-05.md`. Its total row:
|
|
328
235
|
|
|
329
236
|
| change | context_ratio | evidence_ratio | stale_catches | cycle_time_median_s | debt |
|
|
330
237
|
| --- | --- | --- | --- | --- | --- |
|
|
331
|
-
| total | 0.
|
|
238
|
+
| total | 0.0164 (n=154) | 0.94 (257/273) | 65 (of 122 reopens) | 61 (n=154) | 0 (0 baselined + 0 new) |
|
|
332
239
|
|
|
333
|
-
- `context_ratio`: the size of one task bundle divided by the size of all spec text. Lower is better.
|
|
334
|
-
- `evidence_ratio`: done tasks with a passed Verify command, divided by all done tasks.
|
|
335
|
-
- `stale_catches`: reopened tasks that `check --strict` flagged as stale, out of all reopened tasks.
|
|
336
|
-
- `cycle_time_median_s`: the median
|
|
240
|
+
- `context_ratio`: the size of one task bundle divided by the size of all spec text. Lower is better. Change 047 on 2026-10-30 made the bundle hold more, so older values do not compare.
|
|
241
|
+
- `evidence_ratio`: done tasks with a passed Verify command, divided by all done tasks. Higher is better.
|
|
242
|
+
- `stale_catches`: reopened tasks that `check --strict` flagged as stale, out of all reopened tasks.
|
|
243
|
+
- `cycle_time_median_s`: the median seconds from `task start` to `task done`.
|
|
337
244
|
- `debt`: findings that the baseline hides plus new findings. Zero is the goal.
|
|
338
245
|
|
|
339
|
-
The value in brackets is the count of inputs.
|
|
246
|
+
The value in brackets is the count of inputs.
|
|
247
|
+
|
|
248
|
+
## Report a bug
|
|
249
|
+
|
|
250
|
+
spec-ctx keeps a small run journal on your machine. It helps the team see where first runs fail. Nothing leaves your machine unless you file a report.
|
|
251
|
+
|
|
252
|
+
- What it holds: one JSON line per command in `.ctx/local/runs.jsonl`. Each line has the time, the version, the command name, the exit code, the run time, and signals such as `init-ok` or `empty-context`.
|
|
253
|
+
- What it never holds: flag values, paths, IDs, change names, spec text, or error messages.
|
|
254
|
+
- Where it is: `.ctx/local/` in the project root. A `.gitignore` of `*` in that folder keeps it out of git. The file rotates at 256 KB.
|
|
255
|
+
- How to turn it off: set `SPECCTX_JOURNAL=0`.
|
|
256
|
+
- How to file a bug:
|
|
257
|
+
1. Run `specctx report > report.md`.
|
|
258
|
+
2. Read `report.md`. Remove what you do not want to share.
|
|
259
|
+
3. Run `gh issue create -R sonqh/spec-ctx --body-file report.md`.
|
|
260
|
+
|
|
261
|
+
`specctx report` only prints. It never runs `gh` and never uses the network.
|
|
340
262
|
|
|
341
263
|
## Contribute
|
|
342
264
|
|
package/dist/adopt/write.js
CHANGED
|
@@ -4,6 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
import { UsageExit } from "../cli/errors.js";
|
|
5
5
|
import { CONFIG_FILE } from "../config.js";
|
|
6
6
|
import { loadConfigOrExit } from "../cli/config.js";
|
|
7
|
+
import { runCommand } from "../runcmd.js";
|
|
7
8
|
import { dumps } from "../util/json.js";
|
|
8
9
|
import { configText, withCaps } from "./config-write.js";
|
|
9
10
|
export const REPORT_FILE = "adopt-report.md";
|
|
@@ -57,7 +58,7 @@ export function reportText(plan) {
|
|
|
57
58
|
}
|
|
58
59
|
return out.join("\n") + "\n";
|
|
59
60
|
}
|
|
60
|
-
export async function applyAdopt(root,
|
|
61
|
+
export async function applyAdopt(root, cfg, plan, io, json) {
|
|
61
62
|
const cfgPath = join(root, CONFIG_FILE);
|
|
62
63
|
const cfgBefore = existsSync(cfgPath) ? readFileSync(cfgPath, "utf8") : null;
|
|
63
64
|
const cfgAfter = withCaps(configText(root), plan.caps);
|
|
@@ -120,8 +121,11 @@ export async function applyAdopt(root, _cfg, plan, io, json) {
|
|
|
120
121
|
// REQ-CLI-007 only the codes in spec-ctx.toml changed: no header, no report
|
|
121
122
|
if (!report)
|
|
122
123
|
io.out(`wrote the codes to ${CONFIG_FILE}\nnothing to adopt\n`);
|
|
123
|
-
else
|
|
124
|
+
else {
|
|
124
125
|
io.out(`adopted ${plan.edits.length} header(s) in ${files} file(s). Report: ${REPORT_FILE}\n`);
|
|
126
|
+
// REQ-ADOPT-002 adopt writes no baseline. The next step is a separate commit (044/D4, ADR-006 item 6).
|
|
127
|
+
io.out(`Commit the baseline with a subject that starts with baseline:.\nnext: ${runCommand(cfg.cli.run, "baseline write")}\n`);
|
|
128
|
+
}
|
|
125
129
|
}
|
|
126
130
|
return 0;
|
|
127
131
|
}
|
package/dist/assets.generated.js
CHANGED
|
@@ -10,5 +10,5 @@ export const ASSETS = {
|
|
|
10
10
|
"skills/specctx-bootstrap/SKILL.md": "LS0tCm5hbWU6IHNwZWNjdHgtYm9vdHN0cmFwCmRlc2NyaXB0aW9uOiBTdGFydCBhIHNlc3Npb24gaW4gYW4gT3BlblNwZWMgcHJvamVjdCB0aGF0IHVzZXMgc3BlYy1jdHguIFVzZSBhdCB0aGUgc3RhcnQgb2YgZXZlcnkgc2Vzc2lvbiwgYW5kIG9uICJzdGFydCIsICJib290c3RyYXAiLCBvciAid2hhdCBzaG91bGQgSSB3b3JrIG9uIi4gTG9hZHMgb25seSB0aGUgc3BlYyB0ZXh0IHRoYXQgdGhlIG5leHQgdGFzayBuZWVkcy4KLS0tCgpUaGlzIHNraWxsIGlzIG5vdyBgc3BlY2N0eC1ydW5gLiBSdW4gaXQuCg==",
|
|
11
11
|
"skills/specctx-done/SKILL.md": "LS0tCm5hbWU6IHNwZWNjdHgtZG9uZQpkZXNjcmlwdGlvbjogQ2xvc2UgYSB0YXNrIGluIGFuIE9wZW5TcGVjIHByb2plY3QgdGhhdCB1c2VzIHNwZWMtY3R4LiBSdW5zIHRoZSBWZXJpZnkgY29tbWFuZCwgd3JpdGVzIGV2aWRlbmNlLCBzZXRzIHRoZSBjaGVja2JveCwgd3JpdGVzIHRoZSBoYW5kb2ZmLCBhbmQgc2hvd3MgdGhlIG1ldHJpY3MuIFVzZSB3aGVuIHRoZSB3b3JrIG9mIGEgdGFzayBpcyBjb21wbGV0ZSwgb3Igb24gImRvbmUiLCAiY2xvc2UgdGhlIHRhc2siLCBhbmQgImVuZCB0aGUgc2Vzc2lvbiIuCi0tLQoKVGhpcyBza2lsbCBpcyBub3cgYHNwZWNjdHgtcnVuYC4gUnVuIGl0Lgo=",
|
|
12
12
|
"skills/specctx-next/SKILL.md": "LS0tCm5hbWU6IHNwZWNjdHgtbmV4dApkZXNjcmlwdGlvbjogUGljayBhbmQgc3RhcnQgdGhlIG5leHQgdGFzayBpbiB0aGUgbWlkZGxlIG9mIGEgc2Vzc2lvbiBpbiBhbiBPcGVuU3BlYyBwcm9qZWN0IHRoYXQgdXNlcyBzcGVjLWN0eC4gVXNlIGFmdGVyIHlvdSBjbG9zZSBhIHRhc2ssIG9yIG9uICJuZXh0IHRhc2siIGFuZCAid2hhdCBpcyBuZXh0Ii4KLS0tCgpUaGlzIHNraWxsIGlzIG5vdyBgc3BlY2N0eC1ydW5gLiBSdW4gaXQuCg==",
|
|
13
|
-
"skills/specctx-run/SKILL.md": "LS0tCm5hbWU6IHNwZWNjdHgtcnVuCmRlc2NyaXB0aW9uOiBSdW4gdGhlIHNwZWMtY3R4IGxvb3AgaW4gYW4gT3BlblNwZWMgcHJvamVjdC4gVXNlIG9uICJydW4iLCAiY29udGludWUiLCAibmV4dCB0YXNrIiwgImJ1aWxkIHRoaXMiLCAiY2Fycnkgb24iLCBhbmQgb24gYSBmaXJzdCBtZXNzYWdlIHRoYXQgZGVzY3JpYmVzIGEgY2hhbmdlIHRvIGJ1aWxkLiBUaGUgZW5naW5lIHBpY2tzIHRoZSBuZXh0IGFjdGlvbi4gVGhlIHNraWxsIGFjdHMgb24gaXQgdW50aWwgdGhlIGNoYW5nZSByZWFjaGVzIHRoZSBhcmNoaXZlIG9yIGEgaHVtYW4gbXVzdCBkZWNpZGUuCi0tLQoKIyBzcGVjY3R4LXJ1bgoKUnVuIGBzcGVjY3R4YCBhcyBgbnB4IHNwZWMtY3R4YCB3aGVuIGl0IGlzIG5vdCBpbnN0YWxsZWQuIGBvcGVuc3BlY2AgaXMgdGhlIGNvbW1hbmQgaW4gYFtnYXRlXSBvcGVuc3BlY2Agb2YgYHNwZWMtY3R4LnRvbWxgLiBXaGVuIHRoYXQga2V5IGlzIGFic2VudCwgYG9wZW5zcGVjYCBpcyBgbnB4IC15IEBmaXNzaW9uLWFpL29wZW5zcGVjQGxhdGVzdGAuCgojIyBUaGUgbG9vcAoKMS4gUnVuIGBzcGVjY3R4IG5leHQgLS1qc29uYC4gUmVhZCBgYWN0aW9uYCwgYGNoYW5nZWAgKGA8Yz5gKSwgYHRhc2tgIChgPHQ+YCksIGBjb21tYW5kYCwgYHJlYXNvbmAsIGFuZCBgaHVtYW5gLgoyLiBBY3Qgb24gYGFjdGlvbmAgYXMgdGhlIHRhYmxlIGJlbG93IHNheXMuCjMuIFJ1biBgc3BlY2N0eCBoYW5kb2ZmIHdyaXRlYC4KNC4gUmVwZWF0IGZyb20gc3RlcCAxIHVudGlsIGBhY3Rpb25gIGlzIGBzdG9wYCBvciBgaHVtYW5gIGlzIHRydWUuCgojIyBBY3Rpb25zCgotIGBwbGFuYDogdGFrZSB0aGUgY2hhbmdlIG5hbWUgZnJvbSB0aGUgcmVxdWVzdCBvZiB0aGUgdXNlciBpbiBrZWJhYiBjYXNlLiBUaGF0IG5hbWUgaXMgYDxjPmAuIEZvbGxvdyB0aGUgdXBzdHJlYW0gYG9wZW5zcGVjLXByb3Bvc2VgIHNraWxsIGZvciBgPGM+YC4gUmVhZCBleGlzdGluZyByZXF1aXJlbWVudHMgd2l0aCBgc3BlY2N0eCBidW5kbGUgPGlkPmAgb3IgYHNwZWNjdHggcXVlcnlgLCBuZXZlciBgb3BlbnNwZWMgc2hvd2AuIFRha2UgZXZlcnkgSUQgZnJvbSBgc3BlY2N0eCBpZCBuZXcgUkVRIDxDQVA+
|
|
13
|
+
"skills/specctx-run/SKILL.md": "LS0tCm5hbWU6IHNwZWNjdHgtcnVuCmRlc2NyaXB0aW9uOiBSdW4gdGhlIHNwZWMtY3R4IGxvb3AgaW4gYW4gT3BlblNwZWMgcHJvamVjdC4gVXNlIG9uICJydW4iLCAiY29udGludWUiLCAibmV4dCB0YXNrIiwgImJ1aWxkIHRoaXMiLCAiY2Fycnkgb24iLCBhbmQgb24gYSBmaXJzdCBtZXNzYWdlIHRoYXQgZGVzY3JpYmVzIGEgY2hhbmdlIHRvIGJ1aWxkLiBUaGUgZW5naW5lIHBpY2tzIHRoZSBuZXh0IGFjdGlvbi4gVGhlIHNraWxsIGFjdHMgb24gaXQgdW50aWwgdGhlIGNoYW5nZSByZWFjaGVzIHRoZSBhcmNoaXZlIG9yIGEgaHVtYW4gbXVzdCBkZWNpZGUuCi0tLQoKIyBzcGVjY3R4LXJ1bgoKUnVuIGBzcGVjY3R4YCBhcyBgbnB4IHNwZWMtY3R4YCB3aGVuIGl0IGlzIG5vdCBpbnN0YWxsZWQuIGBvcGVuc3BlY2AgaXMgdGhlIGNvbW1hbmQgaW4gYFtnYXRlXSBvcGVuc3BlY2Agb2YgYHNwZWMtY3R4LnRvbWxgLiBXaGVuIHRoYXQga2V5IGlzIGFic2VudCwgYG9wZW5zcGVjYCBpcyBgbnB4IC15IEBmaXNzaW9uLWFpL29wZW5zcGVjQGxhdGVzdGAuCgojIyBUaGUgbG9vcAoKMS4gUnVuIGBzcGVjY3R4IG5leHQgLS1qc29uYC4gUmVhZCBgYWN0aW9uYCwgYGNoYW5nZWAgKGA8Yz5gKSwgYHRhc2tgIChgPHQ+YCksIGBjb21tYW5kYCwgYHJlYXNvbmAsIGFuZCBgaHVtYW5gLgoyLiBBY3Qgb24gYGFjdGlvbmAgYXMgdGhlIHRhYmxlIGJlbG93IHNheXMuCjMuIFJ1biBgc3BlY2N0eCBoYW5kb2ZmIHdyaXRlYC4KNC4gUmVwZWF0IGZyb20gc3RlcCAxIHVudGlsIGBhY3Rpb25gIGlzIGBzdG9wYCBvciBgaHVtYW5gIGlzIHRydWUuCgojIyBBY3Rpb25zCgotIGBwbGFuYDogdGFrZSB0aGUgY2hhbmdlIG5hbWUgZnJvbSB0aGUgcmVxdWVzdCBvZiB0aGUgdXNlciBpbiBrZWJhYiBjYXNlLiBUaGF0IG5hbWUgaXMgYDxjPmAuIEZvbGxvdyB0aGUgdXBzdHJlYW0gYG9wZW5zcGVjLXByb3Bvc2VgIHNraWxsIGZvciBgPGM+YC4gUmVhZCBleGlzdGluZyByZXF1aXJlbWVudHMgd2l0aCBgc3BlY2N0eCBidW5kbGUgPGlkPmAgb3IgYHNwZWNjdHggcXVlcnlgLCBuZXZlciBgb3BlbnNwZWMgc2hvd2AuIFRha2UgZXZlcnkgSUQgZnJvbSBgc3BlY2N0eCBpZCBuZXcgUkVRIDxDQVA+YCBhbmQgYHNwZWNjdHggaWQgbmV3IFNDTiA8Q0FQPiAtLWZvciA8UkVRPmAuIFdoZW4gdGhlIGNoYW5nZSBuZWVkcyBzZXZlcmFsIElEcyBvZiBvbmUga2luZCwgcnVuIGBzcGVjY3R4IGlkIG5ldyAtLWNvdW50IDxuPmAgb25jZS4gR2l2ZSBldmVyeSB0YXNrIGEgYFtSRVEtLi4uXWAgY2l0YXRpb24gYW5kIGEgYFZlcmlmeTpgIGNvZGUgc3Bhbi4gVGhlbiBydW4gYHNwZWNjdHggY2hlY2sgLS1zdHJpY3RgIGFuZCBgc3BlY2N0eCBnYXRlIC0tc3RyaWN0YC4gVGhlIGdhdGUgcnVucyB0aGUgT3BlblNwZWMgdmFsaWRhdGUgYW5kIHRoZSBzcGVjLWxlbmd0aCBjaGVjay4gRml4IGV2ZXJ5IGZpbmRpbmcuIFRoZW4gcnVuIGBzcGVjY3R4IG5leHQgLS1qc29uYCBhbmQgcnVuIHRoZSBjb21tYW5kIGl0IHByaW50cy4gVGhpcyBpcyB0aGUgcGxhbiBzdG9wLiBgbmV4dGAgcmV0dXJucyBgYXNrYCB3aXRoIGBodW1hbjogdHJ1ZWAgdW50aWwgdGhlIGh1bWFuIGFwcHJvdmVzIHRoZSBxdWVzdGlvbi4gWW91IG5ldmVyIHJlc29sdmUgYSBwbGFuIHF1ZXN0aW9uIHlvdXJzZWxmLgotIGBjaXRlYDogcnVuIGBzcGVjY3R4IHF1ZXJ5IC0tY2hhbmdlIDxjPiAtLWtpbmQgcmVxYC4gQWRkIHRoZSBgW1JFUS0uLi5dYCBjaXRhdGlvbiBhbmQgYSBgVmVyaWZ5OmAgY29kZSBzcGFuIHRvIHRoZSB0YXNrIGxpbmUgbmFtZWQgaW4gYHRhc2tgLiBSdW4gYHNwZWNjdHggY2hlY2tgLiBDb250aW51ZS4KLSBgc3RhcnRgOiBydW4gYHNwZWNjdHggdGFzayBzdGFydCA8dD5gLiBSZWFkIHRoZSBidW5kbGUgaXQgcHJpbnRzLiBDb250aW51ZS4KLSBgd29ya2A6IGltcGxlbWVudCB0aGUgdGFzay4gVGFnIGNvZGUgd2l0aCBgLy8gUkVRLS4uLmAuIFRhZyB0ZXN0cyB3aXRoIGAvLyBDb3ZlcnM6IFJFUS0uLi5gLiBUaGVuIHJ1biBgc3BlY2N0eCB0YXNrIGRvbmUgPHQ+YC4KLSBgZml4YDogcmVhZCBgcmVhc29uYC4gSXQgaG9sZHMgdGhlIHRhaWwgb2YgdGhlIGZhaWxlZCBWZXJpZnkgb3V0cHV0LiBGaXggdGhlIGNhdXNlLiBSdW4gYHNwZWNjdHggdGFzayBkb25lIDx0PmAgYWdhaW4uCi0gYHJlcGFpcmA6IHJ1biB0aGUgY29tbWFuZCBpbiB0aGUgYGNvbW1hbmRgIGZpZWxkLiBJdCBydW5zIFZlcmlmeSBhZ2FpbiBmb3IgYSB0aWNrZWQgdGFzayB3aG9zZSBldmlkZW5jZSBpcyBhYnNlbnQgb3Igc3RhbGUuCi0gYGdhdGVgOiBydW4gYHNwZWNjdHggZ2F0ZSAtLXN0cmljdGAuIEZpeCBldmVyeSBmaW5kaW5nLiBGb3IgYSBgc3RhbGUtZXZpZGVuY2VgIGZpbmRpbmcsIGdvIGJhY2sgdG8gc3RlcCAxLiBgbmV4dGAgcmV0dXJucyBgcmVwYWlyYCBhbmQgaXRzIGNvbW1hbmQgcmVwYWlycyBldmVyeSBzdGFsZSB0YXNrLiBSdW4gdGhlIGdhdGUgYWdhaW4gdW50aWwgaXQgZXhpdHMgMC4KLSBgYXJjaGl2ZWA6IHdoZW4gYGh1bWFuYCBpcyB0cnVlLCBydW4gYHNwZWNjdHggaGFuZG9mZiBhc2sgLS1jaGFuZ2UgPGM+ICJhcmNoaXZlIDxjPj8iYCBhbmQgc3RvcC4gVGhpcyBpcyB0aGUgYXJjaGl2ZSBzdG9wLiBgbmV4dGAgcmV0dXJucyBgYXNrYCB1bnRpbCB0aGUgaHVtYW4gYXBwcm92ZXMgdGhlIHF1ZXN0aW9uLiBZb3UgbmV2ZXIgcmVzb2x2ZSBhbiBhcmNoaXZlIHF1ZXN0aW9uIHlvdXJzZWxmLiBBZnRlciBhIHJlamVjdGlvbiwgYG5leHRgIHJldHVybnMgYGFyY2hpdmVgIHdpdGggYGh1bWFuOiB0cnVlYCBhZ2Fpbi4gV2hlbiBgaHVtYW5gIGlzIGZhbHNlLCB0aGUgdXNlciBhcHByb3ZlZCBvciBgW2xvb3BdIGFyY2hpdmVgIGlzIGBhdXRvYC4gVGhlbiBydW4gZWFjaCBzdGVwIHVuZGVyIGBvcGVyYXRpb25zLmFyY2hpdmUuZ3VpZGFuY2VgIGluIGBvcGVuc3BlYy9jb25maWcueWFtbGAuIFRoZW4gcnVuIHRoZSBjb21tYW5kIGluIHRoZSBgY29tbWFuZGAgZmllbGQuIFRoZW4gcnVuIGBvcGVuc3BlYyB2YWxpZGF0ZSAtLWFsbCAtLXN0cmljdGAsIGBzcGVjY3R4IGNoZWNrIC0tc3RyaWN0YCwgYW5kIGBzcGVjY3R4IGhhbmRvZmYgd3JpdGVgLgotIGBhc2tgOiBwcmludCBgcmVhc29uYC4gSXQgaXMgdGhlIG9wZW4gcXVlc3Rpb24uIFN0b3AuIFlvdSBuZXZlciByZXNvbHZlIGEgcGxhbiBvciBhcmNoaXZlIHF1ZXN0aW9uIHlvdXJzZWxmLiBUaGUgaHVtYW4gZG9lcywgd2l0aCBgc3BlY2N0eCBoYW5kb2ZmIHJlc29sdmUgPG4+IC0tYXBwcm92ZSAiPGFuc3dlcj4iYCBvciBgLS1yZWplY3QgIjxhbnN3ZXI+ImAuIEEgcmVqZWN0aW9uIGtlZXBzIHRoZSBzdG9wLiBBbnkgb3RoZXIgcXVlc3Rpb24gdGFrZXMgYHNwZWNjdHggaGFuZG9mZiByZXNvbHZlIDxuPiAiPGFuc3dlcj4iYC4KLSBgc3RvcGA6IHByaW50IGByZWFzb25gLiBTdG9wLgoKIyMgUnVsZXMKCi0gVGhpcyBza2lsbCBvdmVycmlkZXMgdHdvIHN0ZXBzIG9mIHRoZSB1cHN0cmVhbSBgb3BlbnNwZWMtcHJvcG9zZWAgc2tpbGwuIEl0IHJlYWRzIHNwZWNzIHdpdGggYHNwZWNjdHggYnVuZGxlYCBvciBgc3BlY2N0eCBxdWVyeWAsIG5vdCBgb3BlbnNwZWMgc2hvd2AuIEl0IG5ldmVyIHJ1bnMgdGhlIGNsb3NpbmcgYC9vcHN4OmFwcGx5YC4KLSBOZXZlciB0aWNrIGEgY2hlY2tib3ggYnkgaGFuZC4gYHNwZWNjdHggdGFzayBkb25lYCBpcyB0aGUgb25seSB3YXkgYSB0YXNrIGNsb3Nlcy4KLSBOZXZlciB1c2UgYC0tbWFudWFsYCBvbiBhIHRhc2sgd2l0aCBhIFZlcmlmeSBjb21tYW5kLgotIE5ldmVyIGNhbGwgYC9vcHN4OmFwcGx5YC4gVGhpcyBza2lsbCByZXBsYWNlcyBpdC4KLSBUbyB3cml0ZSBhIE1PRElGSUVEIGJsb2NrLCBydW4gYHNwZWNjdHggYnVuZGxlIDxSRVE+IC0tYmxvY2tgLiBJdCBwcmludHMgdGhlIGJsb2NrIGFzIGl0IHNpdHMgaW4gdGhlIHNwZWMgZmlsZS4gQSBNT0RJRklFRCBibG9jayBrZWVwcyBldmVyeSBzY2VuYXJpbyB0aXRsZSB1bmNoYW5nZWQuCi0gYGdhdGVgIGNvdW50cyB0aGUgUmVmcyBsaW5lIGluIHRoZSA1MDAgY2hhcmFjdGVycyBvZiBhIHJlcXVpcmVtZW50LgotIFRoZSBDSEFOR0VMT0cgZW50cnkgZ29lcyB1bmRlciBgIyMgVW5yZWxlYXNlZGAsIG5ldmVyIHVuZGVyIGEgcmVsZWFzZWQgdmVyc2lvbi4KLSBBZnRlciBhbiBlZGl0IHVuZGVyIGBza2lsbHMvYCwgcnVuIGBidW4gcnVuIGJ1aWxkICYmIG5vZGUgZGlzdC9jbGkuanMgaW5pdCAtLXNraWxscyAtLWZvcmNlYCB0byByZWZyZXNoIHRoZSBjb3BpZXMuCi0gTmV2ZXIgcmVhZCBgb3BlbnNwZWMvc3BlY3MvYCBvciBgb3BlbnNwZWMvY2hhbmdlcy8qL3NwZWNzL2AgZGlyZWN0bHkuIFVzZSB0aGUgYnVuZGxlIGZyb20gYHRhc2sgc3RhcnRgIG9yIGBzcGVjY3R4IGJ1bmRsZSA8aWQ+YC4KLSBTdG9wIG9uIGFueSBgYWN0aW9uYCBub3QgaW4gdGhlIGxpc3QgYWJvdmUuIFByaW50IHRoZSBvYmplY3QgYW5kIGFzayB0aGUgdXNlci4KLSBLZWVwIG5vIHN0YXRlIGluIHlvdXIgY29udGV4dC4gQSBuZXcgc2Vzc2lvbiBzdGFydHMgdGhlIHNhbWUgbG9vcCBhZ2FpbiB3aXRoIHN0ZXAgMS4K",
|
|
14
14
|
};
|
package/dist/check/check.js
CHANGED
|
@@ -122,6 +122,65 @@ export function weakVerifyFindings(entries) {
|
|
|
122
122
|
export function weakVerifyWarnings(entries) {
|
|
123
123
|
return weakVerifyFindings(entries).map((f) => `weak-verify ${f.id} ${f.file}:${f.line}`);
|
|
124
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* REQ-CHK-010 REQ-CHK-008 REQ-CHK-009 one predicate for the delta test rule and the deferred notice (052/D1).
|
|
127
|
+
* A gap is a delta requirement or scenario of an active change that no test names. The rule reports the gaps of
|
|
128
|
+
* a change that has a task and no open task. The notice lists the gaps that an open task or a missing task hides.
|
|
129
|
+
*/
|
|
130
|
+
export function deltaGaps(entries, data) {
|
|
131
|
+
const covered = new Set(entries.filter((e) => e.kind === "test").flatMap((e) => e.refs));
|
|
132
|
+
const named = new Set(data.scn_named ?? []);
|
|
133
|
+
// an ID that a REMOVED block of an active change names waits for archive, so it needs no test
|
|
134
|
+
const pending = new Set();
|
|
135
|
+
for (const e of entries) {
|
|
136
|
+
if (e.status !== "removed")
|
|
137
|
+
continue;
|
|
138
|
+
pending.add(e.id);
|
|
139
|
+
for (const sc of e.scenarios ?? [])
|
|
140
|
+
pending.add(sc);
|
|
141
|
+
}
|
|
142
|
+
const out = new Map();
|
|
143
|
+
const slot = (change) => {
|
|
144
|
+
let g = out.get(change);
|
|
145
|
+
if (!g)
|
|
146
|
+
out.set(change, (g = { open: 0, all: 0, gaps: [] }));
|
|
147
|
+
return g;
|
|
148
|
+
};
|
|
149
|
+
for (const e of entries) {
|
|
150
|
+
if (e.kind !== "task" || e.change === "main" || e.file.includes("/archive/"))
|
|
151
|
+
continue;
|
|
152
|
+
const g = slot(e.change);
|
|
153
|
+
g.all++;
|
|
154
|
+
if (e.status !== "done")
|
|
155
|
+
g.open++;
|
|
156
|
+
}
|
|
157
|
+
const seen = new Set();
|
|
158
|
+
for (const e of entries) {
|
|
159
|
+
if ((e.kind !== "scn" && e.kind !== "req") || e.change === "main" || (e.status !== "active" && e.status !== "modified"))
|
|
160
|
+
continue;
|
|
161
|
+
if (pending.has(e.id))
|
|
162
|
+
continue;
|
|
163
|
+
const rule = e.kind === "req" ? (covered.has(e.id) ? null : "uncovered") : named.has(e.id) ? null : "scenario-untested";
|
|
164
|
+
if (!rule)
|
|
165
|
+
continue;
|
|
166
|
+
const gap = [e.file, e.line, rule, e.id];
|
|
167
|
+
const key = e.change + JSON.stringify(gap);
|
|
168
|
+
if (seen.has(key))
|
|
169
|
+
continue;
|
|
170
|
+
seen.add(key);
|
|
171
|
+
slot(e.change).gaps.push(gap);
|
|
172
|
+
}
|
|
173
|
+
return out;
|
|
174
|
+
}
|
|
175
|
+
export function deferredOf(entries, data) {
|
|
176
|
+
const out = [];
|
|
177
|
+
for (const [change, g] of deltaGaps(entries, data)) {
|
|
178
|
+
// 052/D1 to include a change that has no task, the condition is `g.all === 0 || g.open > 0`
|
|
179
|
+
if ((g.all === 0 || g.open > 0) && g.gaps.length)
|
|
180
|
+
out.push({ change, untested: g.gaps.length, open: g.open });
|
|
181
|
+
}
|
|
182
|
+
return out.sort((x, y) => cmpTuple([x.change], [y.change]));
|
|
183
|
+
}
|
|
125
184
|
/** Sorted, unique findings. The scan never reads .ctx/, so evidence adds no index entry. */
|
|
126
185
|
export function check(root, cfg, data, strict) {
|
|
127
186
|
const entries = data.entries;
|
|
@@ -190,29 +249,10 @@ export function check(root, cfg, data, strict) {
|
|
|
190
249
|
if (e.kind === "scn" && !named.has(e.id))
|
|
191
250
|
findings.push([e.file, e.line, "scenario-untested", e.id]);
|
|
192
251
|
}
|
|
193
|
-
// REQ-CHK-008 a delta
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
continue;
|
|
198
|
-
const t = tasksOf.get(e.change) ?? { open: 0, all: 0 };
|
|
199
|
-
t.all++;
|
|
200
|
-
if (e.status !== "done")
|
|
201
|
-
t.open++;
|
|
202
|
-
tasksOf.set(e.change, t);
|
|
203
|
-
}
|
|
204
|
-
for (const e of entries) {
|
|
205
|
-
if ((e.kind !== "scn" && e.kind !== "req") || e.change === "main" || (e.status !== "active" && e.status !== "modified"))
|
|
206
|
-
continue;
|
|
207
|
-
const t = tasksOf.get(e.change);
|
|
208
|
-
if (!t || t.open > 0 || pending.has(e.id))
|
|
209
|
-
continue;
|
|
210
|
-
// REQ-CHK-009 a delta requirement needs a Covers tag on the same terms (039/D2)
|
|
211
|
-
if (e.kind === "req" && !covered.has(e.id))
|
|
212
|
-
findings.push([e.file, e.line, "uncovered", e.id]);
|
|
213
|
-
if (e.kind === "scn" && !named.has(e.id))
|
|
214
|
-
findings.push([e.file, e.line, "scenario-untested", e.id]);
|
|
215
|
-
}
|
|
252
|
+
// REQ-CHK-008 REQ-CHK-009 a delta item needs a test once its change has a task and no open task (038/D1, 038/D2, 039/D2)
|
|
253
|
+
for (const g of deltaGaps(entries, data).values())
|
|
254
|
+
if (g.open === 0 && g.all > 0)
|
|
255
|
+
findings.push(...g.gaps);
|
|
216
256
|
findings.push(...evidenceFindings(root, entries, listEvidence(root)));
|
|
217
257
|
}
|
|
218
258
|
const uniq = new Map();
|