spec-ctx 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -37,7 +37,7 @@ Six commands on a small project. Each clip is one command.
37
37
 
38
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. Run it once on a project that has specs. A new project gets IDs from `init --all`.
39
39
 
40
- ![The adopt dry run lists each header and its new ID](docs/demo/clips/1-adopt.svg)
40
+ ![The adopt dry run lists each header and its new ID](https://raw.githubusercontent.com/sonqh/spec-ctx/main/docs/demo/clips/1-adopt.svg)
41
41
 
42
42
  You see: the dry run prints each header and the ID it gets. Then `adopt` writes them.
43
43
 
@@ -45,7 +45,7 @@ You see: the dry run prints each header and the ID it gets. Then `adopt` writes
45
45
 
46
46
  `status` shows the open change, the task counts, and the next command to run.
47
47
 
48
- ![One change with two tasks done and one open](docs/demo/clips/2-status.svg)
48
+ ![One change with two tasks done and one open](https://raw.githubusercontent.com/sonqh/spec-ctx/main/docs/demo/clips/2-status.svg)
49
49
 
50
50
  You see: one change, two tasks done, one open, and the hint.
51
51
 
@@ -53,15 +53,15 @@ You see: one change, two tasks done, one open, and the hint.
53
53
 
54
54
  `task next` picks the first open task. It prints the requirement IDs the task cites and the Verify command, the shell command that proves the task is done.
55
55
 
56
- ![Task add-mfa/1.3 with its requirement and Verify command](docs/demo/clips/3-next.svg)
56
+ ![Task add-mfa/1.3 with its requirement and Verify command](https://raw.githubusercontent.com/sonqh/spec-ctx/main/docs/demo/clips/3-next.svg)
57
57
 
58
58
  You see: task `add-mfa/1.3`, its requirement, and `grep -q totp src/login.ts`.
59
59
 
60
60
  ### 4. Only the spec this task needs
61
61
 
62
- `task start` prints the requirement, its scenarios, the task line, and the test that covers it. Nothing else. The last line is the size of that bundle next to the size of all spec text.
62
+ `task start` prints the `context` answer for the task. It holds the task line and the requirements it cites with their scenarios. It holds the code and test lines that tag them, the decisions, and the deltas and designs of its change. All of it fits a character budget. The last line is the size of that text next to the size of all spec text.
63
63
 
64
- ![The bundle of one task and its size footer](docs/demo/clips/4-start.svg)
64
+ ![The bundle of one task and its size footer](https://raw.githubusercontent.com/sonqh/spec-ctx/main/docs/demo/clips/4-start.svg)
65
65
 
66
66
  You see: 711 characters, 3.6 percent of the tree. The agent reads this instead of the tree.
67
67
 
@@ -69,15 +69,15 @@ You see: 711 characters, 3.6 percent of the tree. The agent reads this instead o
69
69
 
70
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. The agent cannot tick the box by hand.
71
71
 
72
- ![A refused done, the edit, and a passing done](docs/demo/clips/5-done.svg)
72
+ ![A refused done, the edit, and a passing done](https://raw.githubusercontent.com/sonqh/spec-ctx/main/docs/demo/clips/5-done.svg)
73
73
 
74
74
  You see: a red refusal, the edit, then a green done line.
75
75
 
76
76
  ### 6. Hand off to the next session
77
77
 
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. The questions live in `.ctx/questions.json`. `handoff ask` adds one and `handoff resolve <n>` answers it.
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. The questions live in `.ctx/questions.json`. `handoff ask` adds one and `handoff resolve <n>` answers it. A plan or archive question needs `--approve` or `--reject`, and a rejection keeps the stop.
79
79
 
80
- ![The head of the handoff file](docs/demo/clips/6-handoff.svg)
80
+ ![The head of the handoff file](https://raw.githubusercontent.com/sonqh/spec-ctx/main/docs/demo/clips/6-handoff.svg)
81
81
 
82
82
  You see: the head of `.ctx/handoff.md`.
83
83
 
@@ -107,6 +107,26 @@ graph LR
107
107
 
108
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. `bundle` walks the edges to the configured depth inside a character budget. A decision record joins the graph through its `Refs:` line. The demo fixture has none, so the graph above shows no decision.
109
109
 
110
+ ### Ask for the context
111
+
112
+ `specctx context <task|ID|path|change>` is the one command to run before you edit. It takes one input and prints the spec text that relates to it, with one provenance line for each block. A task gets the requirements it cites and the code and test lines that tag them. It also gets the decisions that name them, and the deltas and designs of its change. A file gets the requirements it tags and the code and test lines of those requirements, so its own tests appear. `task start` prints this same answer. This is the output for task `045/2.1` of this repository, with the text of each block left out:
113
+
114
+ ```text
115
+ $ specctx context 045/2.1
116
+ -- 045/2.1 | openspec/changes/archive/2026-10-30-045-query-by-file/tasks.md:11 | done | requested
117
+ -- 045/D2 | openspec/changes/archive/2026-10-30-045-query-by-file/design.md:22 | archived | design named in 045/2.1
118
+ -- 045/D3 | openspec/changes/archive/2026-10-30-045-query-by-file/design.md:23 | archived | design named in 045/2.1
119
+ -- REQ-IDX-008 | openspec/specs/index/spec.md:133 | active | delta in change of 045/2.1
120
+ -- REQ-IDX-009 | openspec/specs/index/spec.md:146 | active | delta in change of 045/2.1
121
+ -- REQ-QRY-005 | openspec/specs/query/spec.md:76 | active | delta in change of 045/2.1
122
+ -- REQ-QRY-006 | openspec/specs/query/spec.md:93 | active | ref from 045/2.1
123
+ -- REQ-QRY-008 | openspec/specs/query/spec.md:106 | active | delta in change of 045/2.1
124
+ -- src/graph/files.ts:1 | src/graph/files.ts:1 | active | implements REQ-QRY-006
125
+ -- tests/query/file-edges.test.ts:1 | tests/query/file-edges.test.ts:1 | active | covers REQ-QRY-006
126
+ ```
127
+
128
+ `bundle` gives the same answer for one input. It also takes several inputs, `--block`, and `--all`. When the text passes the budget, blocks drop in this order. First go the archived decisions and the entries two steps away. Then go the specs. Then go the code and test lines. The input and the requirements it cites stay.
129
+
110
130
  `specctx related` prints each neighbor of an ID and the reason for the link. This is the output for the demo project after its last step:
111
131
 
112
132
  ```text
@@ -118,6 +138,53 @@ code src/login.ts:1 active src/login.ts:1 implements REQ-UA-004
118
138
  test tests/login.test.ts:1 active tests/login.test.ts:1 covers REQ-UA-004
119
139
  ```
120
140
 
141
+ ### Ask about a file
142
+
143
+ `related`, `context`, and `bundle` take a file path where they take an ID. Run it before you edit a file. The reason on each line tells you how strong the link is. `implements` comes from a code tag. `covers` comes from a `Covers:` tag. `mentions` comes from a path in backticks in a task line of an active change. This is the output for one file of this repository:
144
+
145
+ ```text
146
+ $ specctx related src/util/hash.ts
147
+ adr ADR-004 active decisions/ADR-004-tasks-md-is-the-view-evidence-is-the-record.md:1 ref from REQ-TASK-004
148
+ req REQ-CHK-003 active openspec/specs/check/spec.md:64 ref to REQ-TASK-004
149
+ req REQ-STAT-002 active openspec/specs/metrics/spec.md:19 ref to REQ-TASK-004
150
+ req REQ-TASK-004 active openspec/specs/task-workflow/spec.md:89 implements, tag at src/util/hash.ts:1
151
+ req REQ-VER-004 active openspec/specs/verify-runner/spec.md:37 ref to REQ-TASK-004
152
+ scn SCN-TASK-004-1 active openspec/specs/task-workflow/spec.md:94 scenario of REQ-TASK-004
153
+ task 002/3.3 done openspec/changes/archive/2026-10-24-002-ids-and-tasks/tasks.md:17 task cites REQ-TASK-004
154
+ ```
155
+
156
+ A path that is not a file in the repository exits 2. A file with no links prints `no links: <path>` and exits 0.
157
+
158
+ ### Ask about a change or a task
159
+
160
+ `related`, `context`, and `bundle` take a change key or a folder name where they take an ID. A change reaches the requirements and scenarios of its delta specs and the capabilities they touch. This is the output for an archived change of this repository:
161
+
162
+ ```text
163
+ $ specctx related 045
164
+ req REQ-IDX-008 active openspec/specs/index/spec.md:133 delta in change/045
165
+ req REQ-IDX-009 active openspec/specs/index/spec.md:146 delta in change/045
166
+ req REQ-QRY-005 active openspec/specs/query/spec.md:76 delta in change/045
167
+ req REQ-QRY-006 active openspec/specs/query/spec.md:93 delta in change/045
168
+ req REQ-QRY-008 active openspec/specs/query/spec.md:106 delta in change/045
169
+ scn SCN-IDX-008-1 active openspec/specs/index/spec.md:138 delta in change/045
170
+ scn SCN-IDX-008-2 active openspec/specs/index/spec.md:142 delta in change/045
171
+ scn SCN-IDX-009-1 active openspec/specs/index/spec.md:151 delta in change/045
172
+ scn SCN-IDX-009-2 active openspec/specs/index/spec.md:155 delta in change/045
173
+ scn SCN-QRY-005-1 active openspec/specs/query/spec.md:81 delta in change/045
174
+ scn SCN-QRY-005-2 active openspec/specs/query/spec.md:85 delta in change/045
175
+ scn SCN-QRY-005-3 active openspec/specs/query/spec.md:89 delta in change/045
176
+ scn SCN-QRY-006-1 active openspec/specs/query/spec.md:98 delta in change/045
177
+ scn SCN-QRY-006-3 active openspec/specs/query/spec.md:102 delta in change/045
178
+ scn SCN-QRY-008-1 active openspec/specs/query/spec.md:111 delta in change/045
179
+ scn SCN-QRY-008-2 active openspec/specs/query/spec.md:115 delta in change/045
180
+ scn SCN-QRY-008-3 active openspec/specs/query/spec.md:119 delta in change/045
181
+ scn SCN-QRY-008-4 active openspec/specs/query/spec.md:123 delta in change/045
182
+ cap cap/index active openspec/specs/index/spec.md:1 capability touched by change/045
183
+ cap cap/query active openspec/specs/query/spec.md:1 capability touched by change/045
184
+ ```
185
+
186
+ A task reaches the same delta entries and the design decisions that its line names, for example `specctx related 045/2.1`. A requirement reaches its capability, and `related cap/query` lists the requirements of one capability. A modified requirement shows its main version beside the delta. The index stores only the `change` and `cap` entries and the design links of tasks. The other edges come from the folder structure when you ask. `query` hides the `change` and `cap` entries unless you pass `--kind change` or `--kind cap`.
187
+
121
188
  ## The life cycle for agents
122
189
 
123
190
  `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.
@@ -138,7 +205,7 @@ specctx gate --strict # check, baseline, linters, openspec validate, le
138
205
  specctx handoff write # .ctx/handoff.md for the next session
139
206
  ```
140
207
 
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`.
208
+ The rule: the agent never opens `openspec/specs/` by hand. It uses `task start` or `context`. It never ticks a checkbox and never runs `/opsx:apply`. The skill is in [`skills/`](https://github.com/sonqh/spec-ctx/blob/main/skills/). Install it alone with `npx skills add sonqh/spec-ctx` or `specctx init --skills`.
142
209
 
143
210
  ## Install
144
211
 
@@ -158,7 +225,7 @@ The examples below use `specctx`. With `npx`, write `npx -y spec-ctx` in its pla
158
225
 
159
226
  ## Two paths
160
227
 
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.
228
+ [`docs/getting-started.md`](https://github.com/sonqh/spec-ctx/blob/main/docs/getting-started.md) walks the fresh path on upstream OpenSpec with real output, for a developer who knows OpenSpec.
162
229
 
163
230
  ### Fresh project
164
231
 
@@ -180,6 +247,7 @@ specctx baseline write
180
247
  specctx gate --strict
181
248
  ```
182
249
 
250
+ - `adopt --dry-run` exits 1 when headers need IDs. A script with `set -e` must allow that code.
183
251
  - `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
252
  - `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
253
  - `adopt` runs `check` before and after the write. A finding that the write added undoes every file. A second run prints `nothing to adopt`.
@@ -198,8 +266,9 @@ Both paths have an end to end test on upstream OpenSpec: `tests/paths/fresh.sh`
198
266
  | `status` | Active changes with task counts, the current task, and open questions. |
199
267
  | `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
268
  | `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`. |
269
+ | `context <task\|ID\|path\|change>` | The spec text, code lines, and test lines that relate to one input, in a budget. `task start` prints the same answer. Flags: `--budget`, `--depth`. |
270
+ | `related <ID\|path\|change>` | Linked entries with the reason for each link. A file path or a change works too. Flags: `--depth`, `--all`. |
271
+ | `bundle <ID\|path\|change...>` | The answer of `context` for several inputs. Flags: `--budget`, `--depth`, `--all`, `--block`. |
203
272
  | `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
273
  | `id new`, `id retire` | Allocate and retire IDs. |
205
274
  | `task next`, `start`, `verify`, `done`, `reopen` | The task state machine. |
@@ -249,6 +318,9 @@ base = "origin/main" # the ref whose merge base holds the baseline that gate co
249
318
  [loop]
250
319
  max_attempts = 3 # Verify failures on one task before task done writes a question
251
320
  archive = "ask" # "ask" stops before archive for a human. "auto" lets the loop archive.
321
+
322
+ [cli]
323
+ run = "specctx" # the prefix of every command that spec-ctx prints. init writes npx -y spec-ctx@<version> under npx.
252
324
  ```
253
325
 
254
326
  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`.
@@ -284,10 +356,41 @@ npx spec-ctx lint ste --baseline 0 --disable synonym-rotation README.md
284
356
 
285
357
  The action runs `specctx gate`: `check --strict`, both markdown linters, `openspec validate --all --strict`, and the length check.
286
358
 
359
+ ### Rerun
360
+
361
+ ```yaml
362
+ on: pull_request
363
+ permissions:
364
+ contents: read
365
+ jobs:
366
+ rerun:
367
+ runs-on: ubuntu-latest
368
+ steps:
369
+ - uses: actions/checkout@v4
370
+ with: { fetch-depth: 0 }
371
+ - uses: sonqh/spec-ctx@v0
372
+ with:
373
+ rerun: true
374
+ base: origin/${{ github.base_ref }}
375
+ ```
376
+
377
+ 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`.
378
+
379
+ 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.
380
+
381
+ A Verify command is code from the pull request. The boundary:
382
+
383
+ - Use the `pull_request` and `push` events only. The action exits 1 on any other event.
384
+ - Never use `pull_request_target` or `workflow_run` with code from a pull request. The action exits 1 on `pull_request_target`.
385
+ - Set `permissions: contents: read`.
386
+ - Give the job no secrets.
387
+ - For fork pull requests, turn on the repository setting that asks for approval before a workflow runs.
388
+ - Check out with `fetch-depth: 0`. The merge base needs full history.
389
+
287
390
  ## Status
288
391
 
289
- - Published: `spec-ctx@0.3.0` on npm, with tags `v0.3.0` and `v0` and binaries on the GitHub release.
290
- - Archived: 001 to 025 and 027 to 032. No change is open. See `CHANGELOG.md` for each release.
392
+ - Published: `spec-ctx@0.4.1` on npm, with tags `v0.4.1` and `v0` and binaries on the GitHub release.
393
+ - Archived: 001 to 025, 027 to 034, and 036 to 049. No change is open. See `CHANGELOG.md` for each release.
291
394
  - A version bump on `main` publishes the next release. See `docs/CONTRIBUTING.md`.
292
395
  - 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.
293
396
 
@@ -297,9 +400,9 @@ The action runs `specctx gate`: `check --strict`, both markdown linters, `opensp
297
400
 
298
401
  | change | context_ratio | evidence_ratio | stale_catches | cycle_time_median_s | debt |
299
402
  | --- | --- | --- | --- | --- | --- |
300
- | total | 0.0281 (n=93) | 0.91 (166/182) | 43 (of 90 reopens) | 76 (n=93) | 0 (0 baselined + 0 new) |
403
+ | total | 0.0189 (n=135) | 0.94 (236/252) | 65 (of 118 reopens) | 60 (n=135) | 0 (0 baselined + 0 new) |
301
404
 
302
- - `context_ratio`: the size of one task bundle divided by the size of all spec text. Lower is better.
405
+ - `context_ratio`: the size of one task bundle divided by the size of all spec text. Lower is better. The meaning changed on 2026-10-30 with change 047-context. Before, `task start` printed the cited requirements. Now it prints the `context` answer, which holds more. `bundle_chars` in the evidence of older tasks keeps the old size, so the two do not compare.
303
406
  - `evidence_ratio`: done tasks with a passed Verify command, divided by all done tasks. The rest have a manual note. Higher is better.
304
407
  - `stale_catches`: reopened tasks that `check --strict` flagged as stale, out of all reopened tasks. A higher share means the check found the drift before a person did.
305
408
  - `cycle_time_median_s`: the median number of seconds from `task start` to `task done`. Lower is faster.
package/action.yml CHANGED
@@ -16,6 +16,12 @@ inputs:
16
16
  specctx:
17
17
  description: Command that runs specctx. Empty means npx with the version input.
18
18
  default: ""
19
+ rerun:
20
+ description: Run only gate --rerun. It reruns the Verify command of each task that the pull request closed. Use it on pull_request or push only. On push, pass the commit before the push (github.event.before) as base.
21
+ default: "false"
22
+ base:
23
+ description: With rerun, the ref for the merge base, such as origin/main. On push, use the commit before the push (github.event.before). Empty uses the default of spec-ctx.toml. Without rerun, the action ignores it.
24
+ default: ""
19
25
  runs:
20
26
  using: composite
21
27
  steps:
@@ -30,4 +36,6 @@ runs:
30
36
  INPUT_STRICT: ${{ inputs.strict }}
31
37
  INPUT_MARKDOWN: ${{ inputs.markdown }}
32
38
  INPUT_SPECCTX: ${{ inputs.specctx }}
39
+ INPUT_RERUN: ${{ inputs.rerun }}
40
+ INPUT_BASE: ${{ inputs.base }}
33
41
  SPECCTX_ACTION_PATH: ${{ github.action_path }}
@@ -2,14 +2,16 @@
2
2
  import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { UsageExit } from "../cli/errors.js";
5
- import { CONFIG_FILE, loadConfig } from "../config.js";
5
+ import { CONFIG_FILE } from "../config.js";
6
+ import { loadConfigOrExit } from "../cli/config.js";
7
+ import { runCommand } from "../runcmd.js";
6
8
  import { dumps } from "../util/json.js";
7
9
  import { configText, withCaps } from "./config-write.js";
8
10
  export const REPORT_FILE = "adopt-report.md";
9
11
  async function findings(root) {
10
12
  const { buildIndex } = await import("../index/build.js");
11
13
  const { check } = await import("../check/check.js");
12
- const cfg = loadConfig(root);
14
+ const cfg = loadConfigOrExit(root);
13
15
  const data = buildIndex(root, cfg);
14
16
  return new Set(check(root, cfg, data, false).map((f) => `${f.file}:${f.line} ${f.rule} ${f.id}`));
15
17
  }
@@ -56,7 +58,7 @@ export function reportText(plan) {
56
58
  }
57
59
  return out.join("\n") + "\n";
58
60
  }
59
- export async function applyAdopt(root, _cfg, plan, io, json) {
61
+ export async function applyAdopt(root, cfg, plan, io, json) {
60
62
  const cfgPath = join(root, CONFIG_FILE);
61
63
  const cfgBefore = existsSync(cfgPath) ? readFileSync(cfgPath, "utf8") : null;
62
64
  const cfgAfter = withCaps(configText(root), plan.caps);
@@ -119,8 +121,11 @@ export async function applyAdopt(root, _cfg, plan, io, json) {
119
121
  // REQ-CLI-007 only the codes in spec-ctx.toml changed: no header, no report
120
122
  if (!report)
121
123
  io.out(`wrote the codes to ${CONFIG_FILE}\nnothing to adopt\n`);
122
- else
124
+ else {
123
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
+ }
124
129
  }
125
130
  return 0;
126
131
  }
@@ -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+YCBhbmQgYHNwZWNjdHggaWQgbmV3IFNDTiA8Q0FQPiAtLWZvciA8UkVRPmAuIFdoZW4gdGhlIGNoYW5nZSBuZWVkcyBzZXZlcmFsIElEcyBvZiBvbmUga2luZCwgcnVuIGBzcGVjY3R4IGlkIG5ldyAtLWNvdW50IDxuPmAgb25jZS4gR2l2ZSBldmVyeSB0YXNrIGEgYFtSRVEtLi4uXWAgY2l0YXRpb24gYW5kIGEgYFZlcmlmeTpgIGNvZGUgc3Bhbi4gVGhlbiBydW4gYHNwZWNjdHggY2hlY2sgLS1zdHJpY3RgIGFuZCBgc3BlY2N0eCBnYXRlIC0tc3RyaWN0YC4gVGhlIGdhdGUgcnVucyB0aGUgT3BlblNwZWMgdmFsaWRhdGUgYW5kIHRoZSBzcGVjLWxlbmd0aCBjaGVjay4gRml4IGV2ZXJ5IGZpbmRpbmcuIFRoZW4gcnVuIGBzcGVjY3R4IGhhbmRvZmYgYXNrIC0tY2hhbmdlIDxjPiAicGxhbiByZWFkeSBmb3IgPGM+LiBBcHByb3ZlIHRvIHN0YXJ0IHRoZSB0YXNrcz8iYC4gVGhpcyBpcyB0aGUgcGxhbiBzdG9wLiBgbmV4dGAgcmV0dXJucyBgYXNrYCB1bnRpbCB0aGUgdXNlciByZXNvbHZlcyB0aGUgcXVlc3Rpb24uCi0gYGNpdGVgOiBydW4gYHNwZWNjdHggcXVlcnkgLS1jaGFuZ2UgPGM+IC0ta2luZCByZXFgLiBBZGQgdGhlIGBbUkVRLS4uLl1gIGNpdGF0aW9uIGFuZCBhIGBWZXJpZnk6YCBjb2RlIHNwYW4gdG8gdGhlIHRhc2sgbGluZSBuYW1lZCBpbiBgdGFza2AuIFJ1biBgc3BlY2N0eCBjaGVja2AuIENvbnRpbnVlLgotIGBzdGFydGA6IHJ1biBgc3BlY2N0eCB0YXNrIHN0YXJ0IDx0PmAuIFJlYWQgdGhlIGJ1bmRsZSBpdCBwcmludHMuIENvbnRpbnVlLgotIGB3b3JrYDogaW1wbGVtZW50IHRoZSB0YXNrLiBUYWcgY29kZSB3aXRoIGAvLyBSRVEtLi4uYC4gVGFnIHRlc3RzIHdpdGggYC8vIENvdmVyczogUkVRLS4uLmAuIFRoZW4gcnVuIGBzcGVjY3R4IHRhc2sgZG9uZSA8dD5gLgotIGBmaXhgOiByZWFkIGByZWFzb25gLiBJdCBob2xkcyB0aGUgdGFpbCBvZiB0aGUgZmFpbGVkIFZlcmlmeSBvdXRwdXQuIEZpeCB0aGUgY2F1c2UuIFJ1biBgc3BlY2N0eCB0YXNrIGRvbmUgPHQ+YCBhZ2Fpbi4KLSBgcmVwYWlyYDogcnVuIHRoZSBjb21tYW5kIGluIHRoZSBgY29tbWFuZGAgZmllbGQuIEl0IHJ1bnMgVmVyaWZ5IGFnYWluIGZvciBhIHRpY2tlZCB0YXNrIHdob3NlIGV2aWRlbmNlIGlzIGFic2VudCBvciBzdGFsZS4KLSBgZ2F0ZWA6IHJ1biBgc3BlY2N0eCBnYXRlIC0tc3RyaWN0YC4gRml4IGV2ZXJ5IGZpbmRpbmcuIFJ1biBpdCBhZ2FpbiB1bnRpbCBpdCBleGl0cyAwLgotIGBhcmNoaXZlYDogd2hlbiBgaHVtYW5gIGlzIHRydWUsIHJ1biBgc3BlY2N0eCBoYW5kb2ZmIGFzayAtLWNoYW5nZSA8Yz4gImFyY2hpdmUgPGM+PyJgIGFuZCBzdG9wLiBUaGlzIGlzIHRoZSBhcmNoaXZlIHN0b3AuIGBuZXh0YCByZXR1cm5zIGBhc2tgIHVudGlsIHRoZSB1c2VyIHJlc29sdmVzIHRoZSBxdWVzdGlvbi4gV2hlbiBgaHVtYW5gIGlzIGZhbHNlLCB0aGUgdXNlciBhcHByb3ZlZCBvciBgW2xvb3BdIGFyY2hpdmVgIGlzIGBhdXRvYC4gVGhlbiBydW4gZWFjaCBzdGVwIHVuZGVyIGBvcGVyYXRpb25zLmFyY2hpdmUuZ3VpZGFuY2VgIGluIGBvcGVuc3BlYy9jb25maWcueWFtbGAuIFRoZW4gcnVuIHRoZSBjb21tYW5kIGluIHRoZSBgY29tbWFuZGAgZmllbGQuIFRoZW4gcnVuIGBvcGVuc3BlYyB2YWxpZGF0ZSAtLWFsbCAtLXN0cmljdGAsIGBzcGVjY3R4IGNoZWNrIC0tc3RyaWN0YCwgYW5kIGBzcGVjY3R4IGhhbmRvZmYgd3JpdGVgLgotIGBhc2tgOiBwcmludCBgcmVhc29uYC4gSXQgaXMgdGhlIG9wZW4gcXVlc3Rpb24uIFN0b3AuIFRoZSB1c2VyIGFuc3dlcnMgd2l0aCBgc3BlY2N0eCBoYW5kb2ZmIHJlc29sdmUgPG4+ICI8YW5zd2VyPiJgLgotIGBzdG9wYDogcHJpbnQgYHJlYXNvbmAuIFN0b3AuCgojIyBSdWxlcwoKLSBOZXZlciB0aWNrIGEgY2hlY2tib3ggYnkgaGFuZC4gYHNwZWNjdHggdGFzayBkb25lYCBpcyB0aGUgb25seSB3YXkgYSB0YXNrIGNsb3Nlcy4KLSBOZXZlciB1c2UgYC0tbWFudWFsYCBvbiBhIHRhc2sgd2l0aCBhIFZlcmlmeSBjb21tYW5kLgotIE5ldmVyIGNhbGwgYC9vcHN4OmFwcGx5YC4gVGhpcyBza2lsbCByZXBsYWNlcyBpdC4KLSBOZXZlciByZWFkIGBvcGVuc3BlYy9zcGVjcy9gIG9yIGBvcGVuc3BlYy9jaGFuZ2VzLyovc3BlY3MvYCBkaXJlY3RseS4gVXNlIHRoZSBidW5kbGUgZnJvbSBgdGFzayBzdGFydGAgb3IgYHNwZWNjdHggYnVuZGxlIDxpZD5gLgotIFN0b3Agb24gYW55IGBhY3Rpb25gIG5vdCBpbiB0aGUgbGlzdCBhYm92ZS4gUHJpbnQgdGhlIG9iamVjdCBhbmQgYXNrIHRoZSB1c2VyLgotIEtlZXAgbm8gc3RhdGUgaW4geW91ciBjb250ZXh0LiBBIG5ldyBzZXNzaW9uIHN0YXJ0cyB0aGUgc2FtZSBsb29wIGFnYWluIHdpdGggc3RlcCAxLgo=",
13
+ "skills/specctx-run/SKILL.md": "LS0tCm5hbWU6IHNwZWNjdHgtcnVuCmRlc2NyaXB0aW9uOiBSdW4gdGhlIHNwZWMtY3R4IGxvb3AgaW4gYW4gT3BlblNwZWMgcHJvamVjdC4gVXNlIG9uICJydW4iLCAiY29udGludWUiLCAibmV4dCB0YXNrIiwgImJ1aWxkIHRoaXMiLCAiY2Fycnkgb24iLCBhbmQgb24gYSBmaXJzdCBtZXNzYWdlIHRoYXQgZGVzY3JpYmVzIGEgY2hhbmdlIHRvIGJ1aWxkLiBUaGUgZW5naW5lIHBpY2tzIHRoZSBuZXh0IGFjdGlvbi4gVGhlIHNraWxsIGFjdHMgb24gaXQgdW50aWwgdGhlIGNoYW5nZSByZWFjaGVzIHRoZSBhcmNoaXZlIG9yIGEgaHVtYW4gbXVzdCBkZWNpZGUuCi0tLQoKIyBzcGVjY3R4LXJ1bgoKUnVuIGBzcGVjY3R4YCBhcyBgbnB4IHNwZWMtY3R4YCB3aGVuIGl0IGlzIG5vdCBpbnN0YWxsZWQuIGBvcGVuc3BlY2AgaXMgdGhlIGNvbW1hbmQgaW4gYFtnYXRlXSBvcGVuc3BlY2Agb2YgYHNwZWMtY3R4LnRvbWxgLiBXaGVuIHRoYXQga2V5IGlzIGFic2VudCwgYG9wZW5zcGVjYCBpcyBgbnB4IC15IEBmaXNzaW9uLWFpL29wZW5zcGVjQGxhdGVzdGAuCgojIyBUaGUgbG9vcAoKMS4gUnVuIGBzcGVjY3R4IG5leHQgLS1qc29uYC4gUmVhZCBgYWN0aW9uYCwgYGNoYW5nZWAgKGA8Yz5gKSwgYHRhc2tgIChgPHQ+YCksIGBjb21tYW5kYCwgYHJlYXNvbmAsIGFuZCBgaHVtYW5gLgoyLiBBY3Qgb24gYGFjdGlvbmAgYXMgdGhlIHRhYmxlIGJlbG93IHNheXMuCjMuIFJ1biBgc3BlY2N0eCBoYW5kb2ZmIHdyaXRlYC4KNC4gUmVwZWF0IGZyb20gc3RlcCAxIHVudGlsIGBhY3Rpb25gIGlzIGBzdG9wYCBvciBgaHVtYW5gIGlzIHRydWUuCgojIyBBY3Rpb25zCgotIGBwbGFuYDogdGFrZSB0aGUgY2hhbmdlIG5hbWUgZnJvbSB0aGUgcmVxdWVzdCBvZiB0aGUgdXNlciBpbiBrZWJhYiBjYXNlLiBUaGF0IG5hbWUgaXMgYDxjPmAuIEZvbGxvdyB0aGUgdXBzdHJlYW0gYG9wZW5zcGVjLXByb3Bvc2VgIHNraWxsIGZvciBgPGM+YC4gUmVhZCBleGlzdGluZyByZXF1aXJlbWVudHMgd2l0aCBgc3BlY2N0eCBidW5kbGUgPGlkPmAgb3IgYHNwZWNjdHggcXVlcnlgLCBuZXZlciBgb3BlbnNwZWMgc2hvd2AuIFRha2UgZXZlcnkgSUQgZnJvbSBgc3BlY2N0eCBpZCBuZXcgUkVRIDxDQVA+YCBhbmQgYHNwZWNjdHggaWQgbmV3IFNDTiA8Q0FQPiAtLWZvciA8UkVRPmAuIFdoZW4gdGhlIGNoYW5nZSBuZWVkcyBzZXZlcmFsIElEcyBvZiBvbmUga2luZCwgcnVuIGBzcGVjY3R4IGlkIG5ldyAtLWNvdW50IDxuPmAgb25jZS4gR2l2ZSBldmVyeSB0YXNrIGEgYFtSRVEtLi4uXWAgY2l0YXRpb24gYW5kIGEgYFZlcmlmeTpgIGNvZGUgc3Bhbi4gVGhlbiBydW4gYHNwZWNjdHggY2hlY2sgLS1zdHJpY3RgIGFuZCBgc3BlY2N0eCBnYXRlIC0tc3RyaWN0YC4gVGhlIGdhdGUgcnVucyB0aGUgT3BlblNwZWMgdmFsaWRhdGUgYW5kIHRoZSBzcGVjLWxlbmd0aCBjaGVjay4gRml4IGV2ZXJ5IGZpbmRpbmcuIFRoZW4gcnVuIGBzcGVjY3R4IG5leHQgLS1qc29uYCBhbmQgcnVuIHRoZSBjb21tYW5kIGl0IHByaW50cy4gVGhpcyBpcyB0aGUgcGxhbiBzdG9wLiBgbmV4dGAgcmV0dXJucyBgYXNrYCB3aXRoIGBodW1hbjogdHJ1ZWAgdW50aWwgdGhlIGh1bWFuIGFwcHJvdmVzIHRoZSBxdWVzdGlvbi4gWW91IG5ldmVyIHJlc29sdmUgYSBwbGFuIHF1ZXN0aW9uIHlvdXJzZWxmLgotIGBjaXRlYDogcnVuIGBzcGVjY3R4IHF1ZXJ5IC0tY2hhbmdlIDxjPiAtLWtpbmQgcmVxYC4gQWRkIHRoZSBgW1JFUS0uLi5dYCBjaXRhdGlvbiBhbmQgYSBgVmVyaWZ5OmAgY29kZSBzcGFuIHRvIHRoZSB0YXNrIGxpbmUgbmFtZWQgaW4gYHRhc2tgLiBSdW4gYHNwZWNjdHggY2hlY2tgLiBDb250aW51ZS4KLSBgc3RhcnRgOiBydW4gYHNwZWNjdHggdGFzayBzdGFydCA8dD5gLiBSZWFkIHRoZSBidW5kbGUgaXQgcHJpbnRzLiBDb250aW51ZS4KLSBgd29ya2A6IGltcGxlbWVudCB0aGUgdGFzay4gVGFnIGNvZGUgd2l0aCBgLy8gUkVRLS4uLmAuIFRhZyB0ZXN0cyB3aXRoIGAvLyBDb3ZlcnM6IFJFUS0uLi5gLiBUaGVuIHJ1biBgc3BlY2N0eCB0YXNrIGRvbmUgPHQ+YC4KLSBgZml4YDogcmVhZCBgcmVhc29uYC4gSXQgaG9sZHMgdGhlIHRhaWwgb2YgdGhlIGZhaWxlZCBWZXJpZnkgb3V0cHV0LiBGaXggdGhlIGNhdXNlLiBSdW4gYHNwZWNjdHggdGFzayBkb25lIDx0PmAgYWdhaW4uCi0gYHJlcGFpcmA6IHJ1biB0aGUgY29tbWFuZCBpbiB0aGUgYGNvbW1hbmRgIGZpZWxkLiBJdCBydW5zIFZlcmlmeSBhZ2FpbiBmb3IgYSB0aWNrZWQgdGFzayB3aG9zZSBldmlkZW5jZSBpcyBhYnNlbnQgb3Igc3RhbGUuCi0gYGdhdGVgOiBydW4gYHNwZWNjdHggZ2F0ZSAtLXN0cmljdGAuIEZpeCBldmVyeSBmaW5kaW5nLiBGb3IgYSBgc3RhbGUtZXZpZGVuY2VgIGZpbmRpbmcsIGdvIGJhY2sgdG8gc3RlcCAxLiBgbmV4dGAgcmV0dXJucyBgcmVwYWlyYCBhbmQgaXRzIGNvbW1hbmQgcmVwYWlycyBldmVyeSBzdGFsZSB0YXNrLiBSdW4gdGhlIGdhdGUgYWdhaW4gdW50aWwgaXQgZXhpdHMgMC4KLSBgYXJjaGl2ZWA6IHdoZW4gYGh1bWFuYCBpcyB0cnVlLCBydW4gYHNwZWNjdHggaGFuZG9mZiBhc2sgLS1jaGFuZ2UgPGM+ICJhcmNoaXZlIDxjPj8iYCBhbmQgc3RvcC4gVGhpcyBpcyB0aGUgYXJjaGl2ZSBzdG9wLiBgbmV4dGAgcmV0dXJucyBgYXNrYCB1bnRpbCB0aGUgaHVtYW4gYXBwcm92ZXMgdGhlIHF1ZXN0aW9uLiBZb3UgbmV2ZXIgcmVzb2x2ZSBhbiBhcmNoaXZlIHF1ZXN0aW9uIHlvdXJzZWxmLiBBZnRlciBhIHJlamVjdGlvbiwgYG5leHRgIHJldHVybnMgYGFyY2hpdmVgIHdpdGggYGh1bWFuOiB0cnVlYCBhZ2Fpbi4gV2hlbiBgaHVtYW5gIGlzIGZhbHNlLCB0aGUgdXNlciBhcHByb3ZlZCBvciBgW2xvb3BdIGFyY2hpdmVgIGlzIGBhdXRvYC4gVGhlbiBydW4gZWFjaCBzdGVwIHVuZGVyIGBvcGVyYXRpb25zLmFyY2hpdmUuZ3VpZGFuY2VgIGluIGBvcGVuc3BlYy9jb25maWcueWFtbGAuIFRoZW4gcnVuIHRoZSBjb21tYW5kIGluIHRoZSBgY29tbWFuZGAgZmllbGQuIFRoZW4gcnVuIGBvcGVuc3BlYyB2YWxpZGF0ZSAtLWFsbCAtLXN0cmljdGAsIGBzcGVjY3R4IGNoZWNrIC0tc3RyaWN0YCwgYW5kIGBzcGVjY3R4IGhhbmRvZmYgd3JpdGVgLgotIGBhc2tgOiBwcmludCBgcmVhc29uYC4gSXQgaXMgdGhlIG9wZW4gcXVlc3Rpb24uIFN0b3AuIFlvdSBuZXZlciByZXNvbHZlIGEgcGxhbiBvciBhcmNoaXZlIHF1ZXN0aW9uIHlvdXJzZWxmLiBUaGUgaHVtYW4gZG9lcywgd2l0aCBgc3BlY2N0eCBoYW5kb2ZmIHJlc29sdmUgPG4+IC0tYXBwcm92ZSAiPGFuc3dlcj4iYCBvciBgLS1yZWplY3QgIjxhbnN3ZXI+ImAuIEEgcmVqZWN0aW9uIGtlZXBzIHRoZSBzdG9wLiBBbnkgb3RoZXIgcXVlc3Rpb24gdGFrZXMgYHNwZWNjdHggaGFuZG9mZiByZXNvbHZlIDxuPiAiPGFuc3dlcj4iYC4KLSBgc3RvcGA6IHByaW50IGByZWFzb25gLiBTdG9wLgoKIyMgUnVsZXMKCi0gVGhpcyBza2lsbCBvdmVycmlkZXMgdHdvIHN0ZXBzIG9mIHRoZSB1cHN0cmVhbSBgb3BlbnNwZWMtcHJvcG9zZWAgc2tpbGwuIEl0IHJlYWRzIHNwZWNzIHdpdGggYHNwZWNjdHggYnVuZGxlYCBvciBgc3BlY2N0eCBxdWVyeWAsIG5vdCBgb3BlbnNwZWMgc2hvd2AuIEl0IG5ldmVyIHJ1bnMgdGhlIGNsb3NpbmcgYC9vcHN4OmFwcGx5YC4KLSBOZXZlciB0aWNrIGEgY2hlY2tib3ggYnkgaGFuZC4gYHNwZWNjdHggdGFzayBkb25lYCBpcyB0aGUgb25seSB3YXkgYSB0YXNrIGNsb3Nlcy4KLSBOZXZlciB1c2UgYC0tbWFudWFsYCBvbiBhIHRhc2sgd2l0aCBhIFZlcmlmeSBjb21tYW5kLgotIE5ldmVyIGNhbGwgYC9vcHN4OmFwcGx5YC4gVGhpcyBza2lsbCByZXBsYWNlcyBpdC4KLSBUbyB3cml0ZSBhIE1PRElGSUVEIGJsb2NrLCBydW4gYHNwZWNjdHggYnVuZGxlIDxSRVE+IC0tYmxvY2tgLiBJdCBwcmludHMgdGhlIGJsb2NrIGFzIGl0IHNpdHMgaW4gdGhlIHNwZWMgZmlsZS4gQSBNT0RJRklFRCBibG9jayBrZWVwcyBldmVyeSBzY2VuYXJpbyB0aXRsZSB1bmNoYW5nZWQuCi0gYGdhdGVgIGNvdW50cyB0aGUgUmVmcyBsaW5lIGluIHRoZSA1MDAgY2hhcmFjdGVycyBvZiBhIHJlcXVpcmVtZW50LgotIFRoZSBDSEFOR0VMT0cgZW50cnkgZ29lcyB1bmRlciBgIyMgVW5yZWxlYXNlZGAsIG5ldmVyIHVuZGVyIGEgcmVsZWFzZWQgdmVyc2lvbi4KLSBBZnRlciBhbiBlZGl0IHVuZGVyIGBza2lsbHMvYCwgcnVuIGBidW4gcnVuIGJ1aWxkICYmIG5vZGUgZGlzdC9jbGkuanMgaW5pdCAtLXNraWxscyAtLWZvcmNlYCB0byByZWZyZXNoIHRoZSBjb3BpZXMuCi0gTmV2ZXIgcmVhZCBgb3BlbnNwZWMvc3BlY3MvYCBvciBgb3BlbnNwZWMvY2hhbmdlcy8qL3NwZWNzL2AgZGlyZWN0bHkuIFVzZSB0aGUgYnVuZGxlIGZyb20gYHRhc2sgc3RhcnRgIG9yIGBzcGVjY3R4IGJ1bmRsZSA8aWQ+YC4KLSBTdG9wIG9uIGFueSBgYWN0aW9uYCBub3QgaW4gdGhlIGxpc3QgYWJvdmUuIFByaW50IHRoZSBvYmplY3QgYW5kIGFzayB0aGUgdXNlci4KLSBLZWVwIG5vIHN0YXRlIGluIHlvdXIgY29udGV4dC4gQSBuZXcgc2Vzc2lvbiBzdGFydHMgdGhlIHNhbWUgbG9vcCBhZ2FpbiB3aXRoIHN0ZXAgMS4K",
14
14
  };
@@ -190,6 +190,29 @@ export function check(root, cfg, data, strict) {
190
190
  if (e.kind === "scn" && !named.has(e.id))
191
191
  findings.push([e.file, e.line, "scenario-untested", e.id]);
192
192
  }
193
+ // REQ-CHK-008 a delta scenario needs a test once its change has no open task (038/D1, D2)
194
+ const tasksOf = new Map();
195
+ for (const e of entries) {
196
+ if (e.kind !== "task" || e.change === "main" || e.file.includes("/archive/"))
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
+ }
193
216
  findings.push(...evidenceFindings(root, entries, listEvidence(root)));
194
217
  }
195
218
  const uniq = new Map();
@@ -1,28 +1,32 @@
1
1
  // REQ-CHK-004 the rules of check and one fix hint per rule. The check summary prints the hint of each rule it found.
2
2
  /** `hard` rules set exit 1. `advisory` rules print a warning and keep exit 0. */
3
3
  export const RULES = {
4
- "missing-id": { level: "hard", hint: "Add an ID to the header. Run specctx adopt --dry-run to see the plan." },
4
+ "missing-id": { level: "hard", hint: "Add an ID to the header. Run {run} adopt --dry-run to see the plan." },
5
5
  "scenario-id-mismatch": { level: "hard", hint: "Give the scenario the number of its requirement." },
6
6
  "bad-ref": { level: "hard", hint: "Write only IDs on the Refs line, separated by commas." },
7
7
  "task-without-req": { level: "hard", hint: "Cite a requirement in brackets on the task line, for example [REQ-ABC-001]." },
8
8
  "task-without-verify": { level: "hard", hint: "Add Verify: and a command in backticks to the task line." },
9
- "duplicate-id": { level: "hard", hint: "Give one of the two entries a new ID from specctx id new." },
9
+ "duplicate-id": { level: "hard", hint: "Give one of the two entries a new ID from {run} id new." },
10
10
  "duplicate-change-key": { level: "hard", hint: "Rename one change folder, so that each key names one change." },
11
- "reused-id": { level: "hard", hint: "The ID is retired. Get a new ID from specctx id new." },
12
- "dangling-ref": { level: "hard", hint: "Fix the ID or remove the link. No entry has this ID." },
11
+ "reused-id": { level: "hard", hint: "The ID is retired. Get a new ID from {run} id new." },
12
+ // REQ-CHK-007 the hint names the string literal cause and the fix (033/D3)
13
+ "dangling-ref": {
14
+ level: "hard",
15
+ hint: "No entry has this ID. Fix the ID or remove the link. A tag inside a string literal or fixture text also reads as a link. Build that text at run time, or move the file under a path in [scan] ignore.",
16
+ },
13
17
  "unknown-task-req": { level: "hard", hint: "The task cites an ID that the index does not hold. Fix the ID on the task line." },
14
18
  "req-without-scn": { level: "hard", hint: "Add a scenario under the requirement." },
15
19
  "code-tag-no-canonical": { level: "hard", hint: "The tag names an archived requirement. Tag the current requirement." },
16
20
  "unknown-cap": { level: "hard", hint: "Add the code to caps under [ids] in spec-ctx.toml." },
17
21
  uncovered: { level: "hard", hint: "Add a test with a Covers tag for the requirement." },
18
- "scenario-untested": { level: "hard", hint: "Name the scenario ID in a test." },
19
- "done-without-evidence": { level: "hard", hint: "Run specctx task done. It runs the Verify command and writes the evidence." },
22
+ "scenario-untested": { level: "hard", hint: "Name the scenario ID in a test with a // Covers: <SCN> tag." },
23
+ "done-without-evidence": { level: "hard", hint: "Run {run} task done. It runs the Verify command and writes the evidence." },
20
24
  // REQ-CHK-006 the hint names the why argument of task reopen (031/D8)
21
- "stale-evidence": { level: "hard", hint: "Run specctx task reopen <task> <why>, then specctx task done <task>." },
22
- "evidence-without-done": { level: "hard", hint: "Run specctx task done, or remove the evidence file." },
25
+ "stale-evidence": { level: "hard", hint: "Run {run} task reopen <task> <why>, then {run} task done <task>." },
26
+ "evidence-without-done": { level: "hard", hint: "Run {run} task done, or remove the evidence file." },
23
27
  // REQ-CHK-005 advisory in plain check. check --strict reports it as a hard finding (028/D5).
24
28
  "weak-verify": { level: "advisory", hint: "Name a test file or a command whose exit code proves the task." },
25
- "manual-evidence": { level: "advisory", hint: "Give the task a Verify command and run specctx task done again." },
29
+ "manual-evidence": { level: "advisory", hint: "Give the task a Verify command and run {run} task done again." },
26
30
  // REQ-CHK-001 the hint is the block to paste. The finding line carries the ID and both titles (021/D3).
27
31
  // REQ-ADOPT-010 FROM copies the main header and TO copies the delta header, each in its own form (027/D4).
28
32
  "retitle-needs-renamed": {
@@ -30,6 +34,7 @@ export const RULES = {
30
34
  hint: "Add to the delta before the MODIFIED block. Write each ID as its header writes it, bare or in brackets:\n## RENAMED Requirements\n- FROM: `### Requirement: <ID> <old title>`\n- TO: `### Requirement: <ID> <new title>`",
31
35
  },
32
36
  };
33
- export function hintOf(rule) {
34
- return RULES[rule]?.hint ?? "Read the check rules in the check spec.";
37
+ /** REQ-CLI-010 `{run}` in a hint stands for the run prefix of `[cli] run` (043/D1). */
38
+ export function hintOf(rule, run = "specctx") {
39
+ return (RULES[rule]?.hint ?? "Read the check rules in the check spec.").replaceAll("{run}", run);
35
40
  }
package/dist/cli/args.js CHANGED
@@ -7,10 +7,11 @@ export const COMMAND_FLAGS = {
7
7
  next: { change: "string" },
8
8
  query: { kind: "string", change: "string", all: "bool", limit: "int" },
9
9
  related: { depth: "int", all: "bool" },
10
- bundle: { budget: "int", depth: "int", all: "bool" },
10
+ bundle: { budget: "int", depth: "int", all: "bool", block: "bool" },
11
+ context: { budget: "int", depth: "int" },
11
12
  check: { strict: "bool" },
12
13
  stats: { change: "string", write: "bool" },
13
- gate: { strict: "bool", markdown: "string" },
14
+ gate: { strict: "bool", markdown: "string", rerun: "bool", base: "string" },
14
15
  "id new": { for: "string", count: "int" },
15
16
  "id retire": {},
16
17
  "task next": { change: "string" },
@@ -19,8 +20,8 @@ export const COMMAND_FLAGS = {
19
20
  "task done": { manual: "string" },
20
21
  "task reopen": {},
21
22
  "handoff write": {},
22
- "handoff ask": { change: "string" },
23
- "handoff resolve": {},
23
+ "handoff ask": { change: "string", plan: "bool" },
24
+ "handoff resolve": { approve: "bool", reject: "bool" },
24
25
  adopt: { "dry-run": "bool" },
25
26
  "baseline write": {},
26
27
  "baseline prune": {},