spec-ctx 0.4.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`.
@@ -317,8 +389,8 @@ A Verify command is code from the pull request. The boundary:
317
389
 
318
390
  ## Status
319
391
 
320
- - Published: `spec-ctx@0.4.0` on npm, with tags `v0.4.0` and `v0` and binaries on the GitHub release.
321
- - Archived: 001 to 025, 027 to 034, and 036 to 042. 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.
322
394
  - A version bump on `main` publishes the next release. See `docs/CONTRIBUTING.md`.
323
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.
324
396
 
@@ -328,9 +400,9 @@ A Verify command is code from the pull request. The boundary:
328
400
 
329
401
  | change | context_ratio | evidence_ratio | stale_catches | cycle_time_median_s | debt |
330
402
  | --- | --- | --- | --- | --- | --- |
331
- | total | 0.0228 (n=116) | 0.93 (204/220) | 56 (of 106 reopens) | 61 (n=116) | 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) |
332
404
 
333
- - `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.
334
406
  - `evidence_ratio`: done tasks with a passed Verify command, divided by all done tasks. The rest have a manual note. Higher is better.
335
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.
336
408
  - `cycle_time_median_s`: the median number of seconds from `task start` to `task done`. Lower is faster.
@@ -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, _cfg, plan, io, json) {
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
  }
@@ -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+YCBhbmQgYHNwZWNjdHggaWQgbmV3IFNDTiA8Q0FQPiAtLWZvciA8UkVRPmAuIFdoZW4gdGhlIGNoYW5nZSBuZWVkcyBzZXZlcmFsIElEcyBvZiBvbmUga2luZCwgcnVuIGBzcGVjY3R4IGlkIG5ldyAtLWNvdW50IDxuPmAgb25jZS4gR2l2ZSBldmVyeSB0YXNrIGEgYFtSRVEtLi4uXWAgY2l0YXRpb24gYW5kIGEgYFZlcmlmeTpgIGNvZGUgc3Bhbi4gVGhlbiBydW4gYHNwZWNjdHggY2hlY2sgLS1zdHJpY3RgIGFuZCBgc3BlY2N0eCBnYXRlIC0tc3RyaWN0YC4gVGhlIGdhdGUgcnVucyB0aGUgT3BlblNwZWMgdmFsaWRhdGUgYW5kIHRoZSBzcGVjLWxlbmd0aCBjaGVjay4gRml4IGV2ZXJ5IGZpbmRpbmcuIFRoZW4gcnVuIGBzcGVjY3R4IG5leHQgLS1qc29uYCBhbmQgcnVuIHRoZSBjb21tYW5kIGl0IHByaW50cy4gVGhpcyBpcyB0aGUgcGxhbiBzdG9wLiBgbmV4dGAgcmV0dXJucyBgYXNrYCB3aXRoIGBodW1hbjogdHJ1ZWAgdW50aWwgdGhlIHVzZXIgcmVzb2x2ZXMgdGhlIHF1ZXN0aW9uLgotIGBjaXRlYDogcnVuIGBzcGVjY3R4IHF1ZXJ5IC0tY2hhbmdlIDxjPiAtLWtpbmQgcmVxYC4gQWRkIHRoZSBgW1JFUS0uLi5dYCBjaXRhdGlvbiBhbmQgYSBgVmVyaWZ5OmAgY29kZSBzcGFuIHRvIHRoZSB0YXNrIGxpbmUgbmFtZWQgaW4gYHRhc2tgLiBSdW4gYHNwZWNjdHggY2hlY2tgLiBDb250aW51ZS4KLSBgc3RhcnRgOiBydW4gYHNwZWNjdHggdGFzayBzdGFydCA8dD5gLiBSZWFkIHRoZSBidW5kbGUgaXQgcHJpbnRzLiBDb250aW51ZS4KLSBgd29ya2A6IGltcGxlbWVudCB0aGUgdGFzay4gVGFnIGNvZGUgd2l0aCBgLy8gUkVRLS4uLmAuIFRhZyB0ZXN0cyB3aXRoIGAvLyBDb3ZlcnM6IFJFUS0uLi5gLiBUaGVuIHJ1biBgc3BlY2N0eCB0YXNrIGRvbmUgPHQ+YC4KLSBgZml4YDogcmVhZCBgcmVhc29uYC4gSXQgaG9sZHMgdGhlIHRhaWwgb2YgdGhlIGZhaWxlZCBWZXJpZnkgb3V0cHV0LiBGaXggdGhlIGNhdXNlLiBSdW4gYHNwZWNjdHggdGFzayBkb25lIDx0PmAgYWdhaW4uCi0gYHJlcGFpcmA6IHJ1biB0aGUgY29tbWFuZCBpbiB0aGUgYGNvbW1hbmRgIGZpZWxkLiBJdCBydW5zIFZlcmlmeSBhZ2FpbiBmb3IgYSB0aWNrZWQgdGFzayB3aG9zZSBldmlkZW5jZSBpcyBhYnNlbnQgb3Igc3RhbGUuCi0gYGdhdGVgOiBydW4gYHNwZWNjdHggZ2F0ZSAtLXN0cmljdGAuIEZpeCBldmVyeSBmaW5kaW5nLiBGb3IgYSBgc3RhbGUtZXZpZGVuY2VgIGZpbmRpbmcsIGdvIGJhY2sgdG8gc3RlcCAxLiBgbmV4dGAgcmV0dXJucyBgcmVwYWlyYCBhbmQgaXRzIGNvbW1hbmQgcmVwYWlycyBldmVyeSBzdGFsZSB0YXNrLiBSdW4gdGhlIGdhdGUgYWdhaW4gdW50aWwgaXQgZXhpdHMgMC4KLSBgYXJjaGl2ZWA6IHdoZW4gYGh1bWFuYCBpcyB0cnVlLCBydW4gYHNwZWNjdHggaGFuZG9mZiBhc2sgLS1jaGFuZ2UgPGM+ICJhcmNoaXZlIDxjPj8iYCBhbmQgc3RvcC4gVGhpcyBpcyB0aGUgYXJjaGl2ZSBzdG9wLiBgbmV4dGAgcmV0dXJucyBgYXNrYCB1bnRpbCB0aGUgdXNlciByZXNvbHZlcyB0aGUgcXVlc3Rpb24uIFdoZW4gYGh1bWFuYCBpcyBmYWxzZSwgdGhlIHVzZXIgYXBwcm92ZWQgb3IgYFtsb29wXSBhcmNoaXZlYCBpcyBgYXV0b2AuIFRoZW4gcnVuIGVhY2ggc3RlcCB1bmRlciBgb3BlcmF0aW9ucy5hcmNoaXZlLmd1aWRhbmNlYCBpbiBgb3BlbnNwZWMvY29uZmlnLnlhbWxgLiBUaGVuIHJ1biB0aGUgY29tbWFuZCBpbiB0aGUgYGNvbW1hbmRgIGZpZWxkLiBUaGVuIHJ1biBgb3BlbnNwZWMgdmFsaWRhdGUgLS1hbGwgLS1zdHJpY3RgLCBgc3BlY2N0eCBjaGVjayAtLXN0cmljdGAsIGFuZCBgc3BlY2N0eCBoYW5kb2ZmIHdyaXRlYC4KLSBgYXNrYDogcHJpbnQgYHJlYXNvbmAuIEl0IGlzIHRoZSBvcGVuIHF1ZXN0aW9uLiBTdG9wLiBUaGUgdXNlciBhbnN3ZXJzIHdpdGggYHNwZWNjdHggaGFuZG9mZiByZXNvbHZlIDxuPiAiPGFuc3dlcj4iYC4KLSBgc3RvcGA6IHByaW50IGByZWFzb25gLiBTdG9wLgoKIyMgUnVsZXMKCi0gVGhpcyBza2lsbCBvdmVycmlkZXMgdHdvIHN0ZXBzIG9mIHRoZSB1cHN0cmVhbSBgb3BlbnNwZWMtcHJvcG9zZWAgc2tpbGwuIEl0IHJlYWRzIHNwZWNzIHdpdGggYHNwZWNjdHggYnVuZGxlYCBvciBgc3BlY2N0eCBxdWVyeWAsIG5vdCBgb3BlbnNwZWMgc2hvd2AuIEl0IG5ldmVyIHJ1bnMgdGhlIGNsb3NpbmcgYC9vcHN4OmFwcGx5YC4KLSBOZXZlciB0aWNrIGEgY2hlY2tib3ggYnkgaGFuZC4gYHNwZWNjdHggdGFzayBkb25lYCBpcyB0aGUgb25seSB3YXkgYSB0YXNrIGNsb3Nlcy4KLSBOZXZlciB1c2UgYC0tbWFudWFsYCBvbiBhIHRhc2sgd2l0aCBhIFZlcmlmeSBjb21tYW5kLgotIE5ldmVyIGNhbGwgYC9vcHN4OmFwcGx5YC4gVGhpcyBza2lsbCByZXBsYWNlcyBpdC4KLSBUbyB3cml0ZSBhIE1PRElGSUVEIGJsb2NrLCBydW4gYHNwZWNjdHggYnVuZGxlIDxSRVE+IC0tYmxvY2tgLiBJdCBwcmludHMgdGhlIGJsb2NrIGFzIGl0IHNpdHMgaW4gdGhlIHNwZWMgZmlsZS4gQSBNT0RJRklFRCBibG9jayBrZWVwcyBldmVyeSBzY2VuYXJpbyB0aXRsZSB1bmNoYW5nZWQuCi0gYGdhdGVgIGNvdW50cyB0aGUgUmVmcyBsaW5lIGluIHRoZSA1MDAgY2hhcmFjdGVycyBvZiBhIHJlcXVpcmVtZW50LgotIFRoZSBDSEFOR0VMT0cgZW50cnkgZ29lcyB1bmRlciBgIyMgVW5yZWxlYXNlZGAsIG5ldmVyIHVuZGVyIGEgcmVsZWFzZWQgdmVyc2lvbi4KLSBBZnRlciBhbiBlZGl0IHVuZGVyIGBza2lsbHMvYCwgcnVuIGBidW4gcnVuIGJ1aWxkICYmIG5vZGUgZGlzdC9jbGkuanMgaW5pdCAtLXNraWxscyAtLWZvcmNlYCB0byByZWZyZXNoIHRoZSBjb3BpZXMuCi0gTmV2ZXIgcmVhZCBgb3BlbnNwZWMvc3BlY3MvYCBvciBgb3BlbnNwZWMvY2hhbmdlcy8qL3NwZWNzL2AgZGlyZWN0bHkuIFVzZSB0aGUgYnVuZGxlIGZyb20gYHRhc2sgc3RhcnRgIG9yIGBzcGVjY3R4IGJ1bmRsZSA8aWQ+YC4KLSBTdG9wIG9uIGFueSBgYWN0aW9uYCBub3QgaW4gdGhlIGxpc3QgYWJvdmUuIFByaW50IHRoZSBvYmplY3QgYW5kIGFzayB0aGUgdXNlci4KLSBLZWVwIG5vIHN0YXRlIGluIHlvdXIgY29udGV4dC4gQSBuZXcgc2Vzc2lvbiBzdGFydHMgdGhlIHNhbWUgbG9vcCBhZ2FpbiB3aXRoIHN0ZXAgMS4K",
13
+ "skills/specctx-run/SKILL.md": "LS0tCm5hbWU6IHNwZWNjdHgtcnVuCmRlc2NyaXB0aW9uOiBSdW4gdGhlIHNwZWMtY3R4IGxvb3AgaW4gYW4gT3BlblNwZWMgcHJvamVjdC4gVXNlIG9uICJydW4iLCAiY29udGludWUiLCAibmV4dCB0YXNrIiwgImJ1aWxkIHRoaXMiLCAiY2Fycnkgb24iLCBhbmQgb24gYSBmaXJzdCBtZXNzYWdlIHRoYXQgZGVzY3JpYmVzIGEgY2hhbmdlIHRvIGJ1aWxkLiBUaGUgZW5naW5lIHBpY2tzIHRoZSBuZXh0IGFjdGlvbi4gVGhlIHNraWxsIGFjdHMgb24gaXQgdW50aWwgdGhlIGNoYW5nZSByZWFjaGVzIHRoZSBhcmNoaXZlIG9yIGEgaHVtYW4gbXVzdCBkZWNpZGUuCi0tLQoKIyBzcGVjY3R4LXJ1bgoKUnVuIGBzcGVjY3R4YCBhcyBgbnB4IHNwZWMtY3R4YCB3aGVuIGl0IGlzIG5vdCBpbnN0YWxsZWQuIGBvcGVuc3BlY2AgaXMgdGhlIGNvbW1hbmQgaW4gYFtnYXRlXSBvcGVuc3BlY2Agb2YgYHNwZWMtY3R4LnRvbWxgLiBXaGVuIHRoYXQga2V5IGlzIGFic2VudCwgYG9wZW5zcGVjYCBpcyBgbnB4IC15IEBmaXNzaW9uLWFpL29wZW5zcGVjQGxhdGVzdGAuCgojIyBUaGUgbG9vcAoKMS4gUnVuIGBzcGVjY3R4IG5leHQgLS1qc29uYC4gUmVhZCBgYWN0aW9uYCwgYGNoYW5nZWAgKGA8Yz5gKSwgYHRhc2tgIChgPHQ+YCksIGBjb21tYW5kYCwgYHJlYXNvbmAsIGFuZCBgaHVtYW5gLgoyLiBBY3Qgb24gYGFjdGlvbmAgYXMgdGhlIHRhYmxlIGJlbG93IHNheXMuCjMuIFJ1biBgc3BlY2N0eCBoYW5kb2ZmIHdyaXRlYC4KNC4gUmVwZWF0IGZyb20gc3RlcCAxIHVudGlsIGBhY3Rpb25gIGlzIGBzdG9wYCBvciBgaHVtYW5gIGlzIHRydWUuCgojIyBBY3Rpb25zCgotIGBwbGFuYDogdGFrZSB0aGUgY2hhbmdlIG5hbWUgZnJvbSB0aGUgcmVxdWVzdCBvZiB0aGUgdXNlciBpbiBrZWJhYiBjYXNlLiBUaGF0IG5hbWUgaXMgYDxjPmAuIEZvbGxvdyB0aGUgdXBzdHJlYW0gYG9wZW5zcGVjLXByb3Bvc2VgIHNraWxsIGZvciBgPGM+YC4gUmVhZCBleGlzdGluZyByZXF1aXJlbWVudHMgd2l0aCBgc3BlY2N0eCBidW5kbGUgPGlkPmAgb3IgYHNwZWNjdHggcXVlcnlgLCBuZXZlciBgb3BlbnNwZWMgc2hvd2AuIFRha2UgZXZlcnkgSUQgZnJvbSBgc3BlY2N0eCBpZCBuZXcgUkVRIDxDQVA+YCBhbmQgYHNwZWNjdHggaWQgbmV3IFNDTiA8Q0FQPiAtLWZvciA8UkVRPmAuIFdoZW4gdGhlIGNoYW5nZSBuZWVkcyBzZXZlcmFsIElEcyBvZiBvbmUga2luZCwgcnVuIGBzcGVjY3R4IGlkIG5ldyAtLWNvdW50IDxuPmAgb25jZS4gR2l2ZSBldmVyeSB0YXNrIGEgYFtSRVEtLi4uXWAgY2l0YXRpb24gYW5kIGEgYFZlcmlmeTpgIGNvZGUgc3Bhbi4gVGhlbiBydW4gYHNwZWNjdHggY2hlY2sgLS1zdHJpY3RgIGFuZCBgc3BlY2N0eCBnYXRlIC0tc3RyaWN0YC4gVGhlIGdhdGUgcnVucyB0aGUgT3BlblNwZWMgdmFsaWRhdGUgYW5kIHRoZSBzcGVjLWxlbmd0aCBjaGVjay4gRml4IGV2ZXJ5IGZpbmRpbmcuIFRoZW4gcnVuIGBzcGVjY3R4IG5leHQgLS1qc29uYCBhbmQgcnVuIHRoZSBjb21tYW5kIGl0IHByaW50cy4gVGhpcyBpcyB0aGUgcGxhbiBzdG9wLiBgbmV4dGAgcmV0dXJucyBgYXNrYCB3aXRoIGBodW1hbjogdHJ1ZWAgdW50aWwgdGhlIGh1bWFuIGFwcHJvdmVzIHRoZSBxdWVzdGlvbi4gWW91IG5ldmVyIHJlc29sdmUgYSBwbGFuIHF1ZXN0aW9uIHlvdXJzZWxmLgotIGBjaXRlYDogcnVuIGBzcGVjY3R4IHF1ZXJ5IC0tY2hhbmdlIDxjPiAtLWtpbmQgcmVxYC4gQWRkIHRoZSBgW1JFUS0uLi5dYCBjaXRhdGlvbiBhbmQgYSBgVmVyaWZ5OmAgY29kZSBzcGFuIHRvIHRoZSB0YXNrIGxpbmUgbmFtZWQgaW4gYHRhc2tgLiBSdW4gYHNwZWNjdHggY2hlY2tgLiBDb250aW51ZS4KLSBgc3RhcnRgOiBydW4gYHNwZWNjdHggdGFzayBzdGFydCA8dD5gLiBSZWFkIHRoZSBidW5kbGUgaXQgcHJpbnRzLiBDb250aW51ZS4KLSBgd29ya2A6IGltcGxlbWVudCB0aGUgdGFzay4gVGFnIGNvZGUgd2l0aCBgLy8gUkVRLS4uLmAuIFRhZyB0ZXN0cyB3aXRoIGAvLyBDb3ZlcnM6IFJFUS0uLi5gLiBUaGVuIHJ1biBgc3BlY2N0eCB0YXNrIGRvbmUgPHQ+YC4KLSBgZml4YDogcmVhZCBgcmVhc29uYC4gSXQgaG9sZHMgdGhlIHRhaWwgb2YgdGhlIGZhaWxlZCBWZXJpZnkgb3V0cHV0LiBGaXggdGhlIGNhdXNlLiBSdW4gYHNwZWNjdHggdGFzayBkb25lIDx0PmAgYWdhaW4uCi0gYHJlcGFpcmA6IHJ1biB0aGUgY29tbWFuZCBpbiB0aGUgYGNvbW1hbmRgIGZpZWxkLiBJdCBydW5zIFZlcmlmeSBhZ2FpbiBmb3IgYSB0aWNrZWQgdGFzayB3aG9zZSBldmlkZW5jZSBpcyBhYnNlbnQgb3Igc3RhbGUuCi0gYGdhdGVgOiBydW4gYHNwZWNjdHggZ2F0ZSAtLXN0cmljdGAuIEZpeCBldmVyeSBmaW5kaW5nLiBGb3IgYSBgc3RhbGUtZXZpZGVuY2VgIGZpbmRpbmcsIGdvIGJhY2sgdG8gc3RlcCAxLiBgbmV4dGAgcmV0dXJucyBgcmVwYWlyYCBhbmQgaXRzIGNvbW1hbmQgcmVwYWlycyBldmVyeSBzdGFsZSB0YXNrLiBSdW4gdGhlIGdhdGUgYWdhaW4gdW50aWwgaXQgZXhpdHMgMC4KLSBgYXJjaGl2ZWA6IHdoZW4gYGh1bWFuYCBpcyB0cnVlLCBydW4gYHNwZWNjdHggaGFuZG9mZiBhc2sgLS1jaGFuZ2UgPGM+ICJhcmNoaXZlIDxjPj8iYCBhbmQgc3RvcC4gVGhpcyBpcyB0aGUgYXJjaGl2ZSBzdG9wLiBgbmV4dGAgcmV0dXJucyBgYXNrYCB1bnRpbCB0aGUgaHVtYW4gYXBwcm92ZXMgdGhlIHF1ZXN0aW9uLiBZb3UgbmV2ZXIgcmVzb2x2ZSBhbiBhcmNoaXZlIHF1ZXN0aW9uIHlvdXJzZWxmLiBBZnRlciBhIHJlamVjdGlvbiwgYG5leHRgIHJldHVybnMgYGFyY2hpdmVgIHdpdGggYGh1bWFuOiB0cnVlYCBhZ2Fpbi4gV2hlbiBgaHVtYW5gIGlzIGZhbHNlLCB0aGUgdXNlciBhcHByb3ZlZCBvciBgW2xvb3BdIGFyY2hpdmVgIGlzIGBhdXRvYC4gVGhlbiBydW4gZWFjaCBzdGVwIHVuZGVyIGBvcGVyYXRpb25zLmFyY2hpdmUuZ3VpZGFuY2VgIGluIGBvcGVuc3BlYy9jb25maWcueWFtbGAuIFRoZW4gcnVuIHRoZSBjb21tYW5kIGluIHRoZSBgY29tbWFuZGAgZmllbGQuIFRoZW4gcnVuIGBvcGVuc3BlYyB2YWxpZGF0ZSAtLWFsbCAtLXN0cmljdGAsIGBzcGVjY3R4IGNoZWNrIC0tc3RyaWN0YCwgYW5kIGBzcGVjY3R4IGhhbmRvZmYgd3JpdGVgLgotIGBhc2tgOiBwcmludCBgcmVhc29uYC4gSXQgaXMgdGhlIG9wZW4gcXVlc3Rpb24uIFN0b3AuIFlvdSBuZXZlciByZXNvbHZlIGEgcGxhbiBvciBhcmNoaXZlIHF1ZXN0aW9uIHlvdXJzZWxmLiBUaGUgaHVtYW4gZG9lcywgd2l0aCBgc3BlY2N0eCBoYW5kb2ZmIHJlc29sdmUgPG4+IC0tYXBwcm92ZSAiPGFuc3dlcj4iYCBvciBgLS1yZWplY3QgIjxhbnN3ZXI+ImAuIEEgcmVqZWN0aW9uIGtlZXBzIHRoZSBzdG9wLiBBbnkgb3RoZXIgcXVlc3Rpb24gdGFrZXMgYHNwZWNjdHggaGFuZG9mZiByZXNvbHZlIDxuPiAiPGFuc3dlcj4iYC4KLSBgc3RvcGA6IHByaW50IGByZWFzb25gLiBTdG9wLgoKIyMgUnVsZXMKCi0gVGhpcyBza2lsbCBvdmVycmlkZXMgdHdvIHN0ZXBzIG9mIHRoZSB1cHN0cmVhbSBgb3BlbnNwZWMtcHJvcG9zZWAgc2tpbGwuIEl0IHJlYWRzIHNwZWNzIHdpdGggYHNwZWNjdHggYnVuZGxlYCBvciBgc3BlY2N0eCBxdWVyeWAsIG5vdCBgb3BlbnNwZWMgc2hvd2AuIEl0IG5ldmVyIHJ1bnMgdGhlIGNsb3NpbmcgYC9vcHN4OmFwcGx5YC4KLSBOZXZlciB0aWNrIGEgY2hlY2tib3ggYnkgaGFuZC4gYHNwZWNjdHggdGFzayBkb25lYCBpcyB0aGUgb25seSB3YXkgYSB0YXNrIGNsb3Nlcy4KLSBOZXZlciB1c2UgYC0tbWFudWFsYCBvbiBhIHRhc2sgd2l0aCBhIFZlcmlmeSBjb21tYW5kLgotIE5ldmVyIGNhbGwgYC9vcHN4OmFwcGx5YC4gVGhpcyBza2lsbCByZXBsYWNlcyBpdC4KLSBUbyB3cml0ZSBhIE1PRElGSUVEIGJsb2NrLCBydW4gYHNwZWNjdHggYnVuZGxlIDxSRVE+IC0tYmxvY2tgLiBJdCBwcmludHMgdGhlIGJsb2NrIGFzIGl0IHNpdHMgaW4gdGhlIHNwZWMgZmlsZS4gQSBNT0RJRklFRCBibG9jayBrZWVwcyBldmVyeSBzY2VuYXJpbyB0aXRsZSB1bmNoYW5nZWQuCi0gYGdhdGVgIGNvdW50cyB0aGUgUmVmcyBsaW5lIGluIHRoZSA1MDAgY2hhcmFjdGVycyBvZiBhIHJlcXVpcmVtZW50LgotIFRoZSBDSEFOR0VMT0cgZW50cnkgZ29lcyB1bmRlciBgIyMgVW5yZWxlYXNlZGAsIG5ldmVyIHVuZGVyIGEgcmVsZWFzZWQgdmVyc2lvbi4KLSBBZnRlciBhbiBlZGl0IHVuZGVyIGBza2lsbHMvYCwgcnVuIGBidW4gcnVuIGJ1aWxkICYmIG5vZGUgZGlzdC9jbGkuanMgaW5pdCAtLXNraWxscyAtLWZvcmNlYCB0byByZWZyZXNoIHRoZSBjb3BpZXMuCi0gTmV2ZXIgcmVhZCBgb3BlbnNwZWMvc3BlY3MvYCBvciBgb3BlbnNwZWMvY2hhbmdlcy8qL3NwZWNzL2AgZGlyZWN0bHkuIFVzZSB0aGUgYnVuZGxlIGZyb20gYHRhc2sgc3RhcnRgIG9yIGBzcGVjY3R4IGJ1bmRsZSA8aWQ+YC4KLSBTdG9wIG9uIGFueSBgYWN0aW9uYCBub3QgaW4gdGhlIGxpc3QgYWJvdmUuIFByaW50IHRoZSBvYmplY3QgYW5kIGFzayB0aGUgdXNlci4KLSBLZWVwIG5vIHN0YXRlIGluIHlvdXIgY29udGV4dC4gQSBuZXcgc2Vzc2lvbiBzdGFydHMgdGhlIHNhbWUgbG9vcCBhZ2FpbiB3aXRoIHN0ZXAgMS4K",
14
14
  };
@@ -1,14 +1,14 @@
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." },
11
+ "reused-id": { level: "hard", hint: "The ID is retired. Get a new ID from {run} id new." },
12
12
  // REQ-CHK-007 the hint names the string literal cause and the fix (033/D3)
13
13
  "dangling-ref": {
14
14
  level: "hard",
@@ -20,13 +20,13 @@ export const RULES = {
20
20
  "unknown-cap": { level: "hard", hint: "Add the code to caps under [ids] in spec-ctx.toml." },
21
21
  uncovered: { level: "hard", hint: "Add a test with a Covers tag for the requirement." },
22
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 specctx task done. It runs the Verify command and writes the evidence." },
23
+ "done-without-evidence": { level: "hard", hint: "Run {run} task done. It runs the Verify command and writes the evidence." },
24
24
  // REQ-CHK-006 the hint names the why argument of task reopen (031/D8)
25
- "stale-evidence": { level: "hard", hint: "Run specctx task reopen <task> <why>, then specctx task done <task>." },
26
- "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." },
27
27
  // REQ-CHK-005 advisory in plain check. check --strict reports it as a hard finding (028/D5).
28
28
  "weak-verify": { level: "advisory", hint: "Name a test file or a command whose exit code proves the task." },
29
- "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." },
30
30
  // REQ-CHK-001 the hint is the block to paste. The finding line carries the ID and both titles (021/D3).
31
31
  // REQ-ADOPT-010 FROM copies the main header and TO copies the delta header, each in its own form (027/D4).
32
32
  "retitle-needs-renamed": {
@@ -34,6 +34,7 @@ export const RULES = {
34
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>`",
35
35
  },
36
36
  };
37
- export function hintOf(rule) {
38
- 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);
39
40
  }
package/dist/cli/args.js CHANGED
@@ -8,6 +8,7 @@ export const COMMAND_FLAGS = {
8
8
  query: { kind: "string", change: "string", all: "bool", limit: "int" },
9
9
  related: { depth: "int", all: "bool" },
10
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
14
  gate: { strict: "bool", markdown: "string", rerun: "bool", base: "string" },
@@ -20,7 +21,7 @@ export const COMMAND_FLAGS = {
20
21
  "task reopen": {},
21
22
  "handoff write": {},
22
23
  "handoff ask": { change: "string", plan: "bool" },
23
- "handoff resolve": {},
24
+ "handoff resolve": { approve: "bool", reject: "bool" },
24
25
  adopt: { "dry-run": "bool" },
25
26
  "baseline write": {},
26
27
  "baseline prune": {},
@@ -2,8 +2,10 @@
2
2
  import { resolve } from "node:path";
3
3
  import { UsageExit } from "./errors.js";
4
4
  import { findRoot, requireOpenSpec } from "./root.js";
5
+ import { defaults } from "../config.js";
5
6
  import { hintOf } from "../check/hints.js";
6
7
  import { paint } from "./color.js";
8
+ import { runCommand } from "../runcmd.js";
7
9
  /** check with warnings and the baseline filter. Strict mode adds the strict rules and the warnings. */
8
10
  async function runCheck(root, cfg, data, strict) {
9
11
  const { check } = await import("../check/check.js");
@@ -26,7 +28,7 @@ async function runCheck(root, cfg, data, strict) {
26
28
  * REQ-CHK-004 the findings, then a blank line, the summary line, and one line per rule with its hint.
27
29
  * A warning counts as advisory. The `baselined` line stays last (REQ-CHK-002).
28
30
  */
29
- export function checkText(r, tty) {
31
+ export function checkText(r, tty, run = "specctx") {
30
32
  // REQ-CLI-008 every finding of check is hard, so every finding line is red. stale-evidence is one of them.
31
33
  // REQ-CHK-001 a finding with a detail, such as a retitle, carries it after the ID (021/D2)
32
34
  const body = r.findings.map((x) => paint("fail", `${x.file}:${x.line} ${x.rule} ${x.id}${x.detail ? ` ${x.detail}` : ""}\n`, tty)).join("")
@@ -62,7 +64,7 @@ export function checkText(r, tty) {
62
64
  const rules = [...count.keys()].sort();
63
65
  return body
64
66
  + "\n" + paint("dim", `${n} finding${n === 1 ? "" : "s"} in ${files.size} file${files.size === 1 ? "" : "s"} (${r.findings.length} hard, ${r.warnings.length} advisory)\n`, tty)
65
- + rules.map((rule) => `${rule} ${count.get(rule)} ${own.get(rule)?.join("\n") ?? hintOf(rule)}\n`).join("")
67
+ + rules.map((rule) => `${rule} ${count.get(rule)} ${own.get(rule)?.join("\n") ?? hintOf(rule, run)}\n`).join("")
66
68
  + base;
67
69
  }
68
70
  export async function run(p, io) {
@@ -103,7 +105,7 @@ export async function run(p, io) {
103
105
  io.out(plan.unmatched.length ? planLines(plan).join("\n") + "\nnothing to adopt\n" : "nothing to adopt\n");
104
106
  // REQ-CLI-007 the dry run ends with the next step. REQ-ADOPT-001 the exit code stays 1 and the last line says why (017/D5).
105
107
  else
106
- io.out(planLines(plan).join("\n") + `\nexit 1: ${plan.edits.length} headers need an ID. Next: specctx adopt\n`);
108
+ io.out(planLines(plan).join("\n") + `\nexit 1: ${plan.edits.length} headers need an ID. Next: ${runCommand(cfg.cli.run, "adopt")}\n`);
107
109
  return plan.edits.length ? 1 : 0;
108
110
  }
109
111
  if (cmd === "baseline write" || cmd === "baseline prune") {
@@ -163,7 +165,7 @@ export async function run(p, io) {
163
165
  void check;
164
166
  const data = buildIndex(root, cfg);
165
167
  const r = await runCheck(root, cfg, data, strict);
166
- return { code: r.findings.length ? 1 : 0, out: checkText(r, io.tty) };
168
+ return { code: r.findings.length ? 1 : 0, out: checkText(r, io.tty, cfg.cli.run) };
167
169
  },
168
170
  }, io);
169
171
  }
@@ -176,12 +178,17 @@ export async function run(p, io) {
176
178
  if (cmd === "query" && !f.change)
177
179
  need(1, "at least one term");
178
180
  if (cmd === "related") {
179
- need(1, "one ID");
181
+ need(1, "one ID, path, or change");
180
182
  if (p.positional.length > 1)
181
- throw new UsageExit("related takes one ID");
183
+ throw new UsageExit("related takes one ID, path, or change");
182
184
  }
183
185
  if (cmd === "bundle")
184
- need(1, "at least one ID");
186
+ need(1, "at least one ID, path, or change");
187
+ if (cmd === "context") {
188
+ need(1, "one task, ID, path, or change");
189
+ if (p.positional.length > 1)
190
+ throw new UsageExit("context takes one task, ID, path, or change");
191
+ }
185
192
  if ((cmd === "status" || cmd === "check" || cmd === "next") && p.positional.length)
186
193
  throw new UsageExit(`${cmd} takes no arguments`);
187
194
  const { data, rebuilt } = loadIndex(root, cfg);
@@ -231,15 +238,35 @@ export async function run(p, io) {
231
238
  }
232
239
  case "related": {
233
240
  const { canon } = await import("../index/canonical.js");
234
- const { related } = await import("../graph/related.js");
235
- const id = p.positional[0];
241
+ const { related, mainVersion } = await import("../graph/related.js");
236
242
  const ids = canon(entries);
243
+ let id = p.positional[0];
244
+ let isPath = false;
237
245
  if (!ids.has(id) && !(all && entries.some((e) => e.id === id))) {
238
- io.err(`unknown id ${id}\n`);
239
- return 2;
246
+ // REQ-QRY-011 a change key or folder name is a change. REQ-QRY-005 any other input is a path in the repository.
247
+ const { resolveFileInput, changeInput } = await import("../graph/path.js");
248
+ const ch = changeInput(entries, id);
249
+ if (ch)
250
+ id = ch;
251
+ else {
252
+ const r = resolveFileInput(root, io.cwd, id);
253
+ if ("error" in r) {
254
+ io.err(`${r.error}\n`);
255
+ return 2;
256
+ }
257
+ id = r.rel;
258
+ isPath = true;
259
+ }
240
260
  }
241
261
  const rows = [];
242
262
  for (const [i, reason] of related(entries, id, f.depth ?? 1, all)) {
263
+ // REQ-QRY-010 the pair of a main version stands for the non-canonical copy in the main spec
264
+ if (!all && reason === `main version of ${i}`) {
265
+ const m = mainVersion(entries, i);
266
+ if (m)
267
+ rows.push([m, reason]);
268
+ continue;
269
+ }
243
270
  const copies = all ? entries.filter((e) => e.id === i) : ids.has(i) ? [ids.get(i)] : [];
244
271
  // canonical copy first, the rest in index order
245
272
  copies.sort((a, b) => Number(!a.canonical) - Number(!b.canonical));
@@ -248,22 +275,27 @@ export async function run(p, io) {
248
275
  }
249
276
  if (json)
250
277
  print(rows.map(([e, reason]) => ({ ...e, reason })));
278
+ else if (isPath && rows.length === 0)
279
+ io.out(`no links: ${id}\n`);
251
280
  else
252
281
  io.out(rows.map(([e, r]) => `${e.kind} ${e.id} ${e.status} ${e.file}:${e.line} ${r}\n`).join(""));
253
282
  return 0;
254
283
  }
284
+ case "context":
255
285
  case "bundle": {
256
286
  const { canon } = await import("../index/canonical.js");
257
287
  const { bundle } = await import("../graph/bundle.js");
288
+ const { resolveInputs } = await import("../graph/input.js");
258
289
  const ids = canon(entries);
259
- const unknown = p.positional.filter((i) => !ids.has(i));
260
- if (unknown.length) {
261
- io.err(`unknown id(s): ${unknown.join(", ")}\n`);
290
+ // REQ-QRY-005 an input that is no known ID is a path in the repository
291
+ const { wanted, paths, errors, taskId } = resolveInputs(root, io.cwd, entries, p.positional);
292
+ if (errors.length) {
293
+ io.err(errors.map((l) => l + "\n").join(""));
262
294
  return 2;
263
295
  }
264
296
  // REQ-QRY-004 --block prints the requirement blocks verbatim and nothing else (034/D2)
265
297
  if (f.block) {
266
- const notReq = p.positional.filter((i) => ids.get(i).kind !== "req");
298
+ const notReq = p.positional.filter((i) => ids.get(i)?.kind !== "req");
267
299
  if (notReq.length) {
268
300
  io.err(`not a requirement: ${notReq.join(", ")}\n`);
269
301
  return 2;
@@ -276,9 +308,13 @@ export async function run(p, io) {
276
308
  io.out(text);
277
309
  return 0;
278
310
  }
279
- const b = bundle(root, entries, p.positional, f.budget ?? cfg.bundle.budget, f.depth ?? cfg.bundle.depth, all);
311
+ // 047/D5 a task input passes the entry that findTask picks, so an active task wins over an archived one
312
+ const task = taskId === undefined ? undefined : (await import("../tasks/tasks.js")).findTask(root, entries, taskId).entry;
313
+ const b = bundle(root, entries, wanted, f.budget ?? cfg.bundle.budget, f.depth ?? cfg.bundle.depth, { all, task });
280
314
  if (json)
281
315
  print(b);
316
+ else if (b.text === "" && wanted.every((w) => paths.has(w)))
317
+ io.out(wanted.map((w) => `no links: ${w}\n`).join(""));
282
318
  else
283
319
  io.out(b.text);
284
320
  return 0;
@@ -310,7 +346,7 @@ export async function run(p, io) {
310
346
  if (json)
311
347
  print({ baselined: r.baselined, count: findings.length, findings, warnings: r.warnings });
312
348
  else
313
- io.out(checkText(r, io.tty));
349
+ io.out(checkText(r, io.tty, cfg.cli.run));
314
350
  return r.findings.length ? 1 : 0;
315
351
  }
316
352
  }
@@ -323,18 +359,28 @@ async function init(p, io) {
323
359
  const root = resolve(io.cwd, p.flags.root ?? ".");
324
360
  const all = !!p.flags.all;
325
361
  const r = runInit(root, { schema: all || !!p.flags.schema, skills: all || !!p.flags.skills, force: !!p.flags.force });
362
+ // REQ-CLI-010 init prints its next step with the run prefix of the config it wrote or kept (043/D1)
363
+ const { loadConfigOrExit } = await import("./config.js");
364
+ const run = (() => {
365
+ try {
366
+ return loadConfigOrExit(root).cli.run;
367
+ }
368
+ catch {
369
+ return defaults().cli.run;
370
+ }
371
+ })();
326
372
  if (p.flags.json)
327
373
  io.out(JSON.stringify(r, null, 2) + "\n");
328
374
  else if (r.nothing)
329
- io.out("nothing to do\n" + r.notes.map((n) => `${n}\n`).join("") + paint("dim", "next: specctx status\n", io.tty));
375
+ io.out("nothing to do\n" + r.notes.map((n) => `${n}\n`).join("") + paint("dim", `next: ${runCommand(run, "status")}\n`, io.tty));
330
376
  else {
331
377
  io.out(r.written.map((w) => `wrote ${w}\n`).join(""));
332
378
  io.out(r.merged.map((m) => `merged ${m}\n`).join(""));
333
379
  io.out(r.kept.map((k) => `kept ${k}\n`).join(""));
334
380
  io.out(r.notes.map((n) => `${n}\n`).join(""));
335
381
  // REQ-CLI-007 init ends with the next step. REQ-DIST-002 a kept changed file points to --force (014/D2).
336
- const again = ["specctx init", all ? "--all" : [p.flags.schema ? "--schema" : "", p.flags.skills ? "--skills" : ""].filter(Boolean).join(" "), "--force"].filter(Boolean).join(" ");
337
- io.out(paint("dim", `next: ${r.changed.length ? again : "specctx status"}\n`, io.tty));
382
+ const again = [runCommand(run, "init"), all ? "--all" : [p.flags.schema ? "--schema" : "", p.flags.skills ? "--skills" : ""].filter(Boolean).join(" "), "--force"].filter(Boolean).join(" ");
383
+ io.out(paint("dim", `next: ${r.changed.length ? again : runCommand(run, "status")}\n`, io.tty));
338
384
  }
339
385
  return 0;
340
386
  }
package/dist/cli/help.js CHANGED
@@ -38,19 +38,27 @@ export const TABLE = [
38
38
  exit: PLAIN, example: "specctx query lockfile --kind req",
39
39
  },
40
40
  {
41
- name: "related", group: "Read", args: "<ID>", summary: "Show the entries that link to an ID.",
41
+ name: "context", group: "Read", args: "<task|ID|path|change>", summary: "Print what an agent needs before it edits a task, an ID, a file, or a change.",
42
+ flags: [
43
+ ["--budget <n>", "Stop at n characters. The default is 8000."],
44
+ ["--depth <n>", "Follow links n steps. The default is 1."],
45
+ ],
46
+ exit: [OK, ["2", "A usage error, an unknown ID, or a bad path. It takes one input. Use `bundle` for several inputs or for `--block`."], INTERNAL], example: "specctx context 045/2.1",
47
+ },
48
+ {
49
+ name: "related", group: "Read", args: "<ID|path|change>", summary: "Show the entries that link to an ID, a file, or a change.",
42
50
  flags: [["--depth <n>", "Follow links n steps. The default is 1."], ["--all", "Show all copies of an ID."]],
43
- exit: [OK, ["2", "A usage error or an unknown ID."], INTERNAL], example: "specctx related REQ-IDX-001",
51
+ exit: [OK, ["2", "A usage error, an unknown ID, or a path that is not a file in the repository."], INTERNAL], example: "specctx related src/graph/bundle.ts",
44
52
  },
45
53
  {
46
- name: "bundle", group: "Read", args: "<ID...>", summary: "Print the spec text for the IDs and their links.",
54
+ name: "bundle", group: "Read", args: "<ID|path|change...>", summary: "Print the spec text for the IDs, files, or changes and their links.",
47
55
  flags: [
48
56
  ["--budget <n>", "Stop at n characters. The default is 8000."],
49
57
  ["--depth <n>", "Follow links n steps. The default is 1."],
50
58
  ["--all", "Show all copies of an ID."],
51
59
  ["--block", "Print only the requirement block, as it sits in the spec file. Each ID must be a requirement."],
52
60
  ],
53
- exit: [OK, ["2", "A usage error, an unknown ID, or an ID that is not a requirement."], INTERNAL], example: "specctx bundle REQ-IDX-001 --budget 3000",
61
+ exit: [OK, ["2", "A usage error, an unknown ID, a bad path, or an ID that is not a requirement."], INTERNAL], example: "specctx bundle REQ-IDX-001 --budget 3000",
54
62
  },
55
63
  {
56
64
  name: "check", group: "Read", args: "", summary: "Find ID and link errors.",
@@ -125,7 +133,11 @@ export const TABLE = [
125
133
  },
126
134
  {
127
135
  name: "handoff resolve", group: "Write", args: "<n> <answer>", summary: "Move open question n to the resolved list.",
128
- flags: [], exit: PLAIN, example: "specctx handoff resolve 1 \"yes, for one release\"",
136
+ flags: [
137
+ ["--approve", "Approve a plan or archive question. A plan or archive question needs this or --reject."],
138
+ ["--reject", "Reject a plan or archive question. The stop stays."],
139
+ ],
140
+ exit: PLAIN, example: "specctx handoff resolve 1 --approve \"yes, for one release\"",
129
141
  },
130
142
  // Setup
131
143
  {