spec-ctx 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +298 -2
- package/action.yml +33 -0
- package/dist/adopt/config-write.js +59 -0
- package/dist/adopt/plan.js +276 -0
- package/dist/adopt/write.js +126 -0
- package/dist/assets.generated.js +14 -0
- package/dist/baseline/baseline.js +65 -0
- package/dist/check/check.js +126 -0
- package/dist/check/evidence.js +70 -0
- package/dist/check/hints.js +26 -0
- package/dist/cli/args.js +90 -0
- package/dist/cli/color.js +23 -0
- package/dist/cli/commands.js +278 -0
- package/dist/cli/errors.js +3 -0
- package/dist/cli/gate.js +187 -0
- package/dist/cli/help.js +230 -0
- package/dist/cli/root.js +32 -0
- package/dist/cli/status.js +116 -0
- package/dist/cli/workflow.js +285 -0
- package/dist/cli.js +14 -0
- package/dist/config.js +119 -0
- package/dist/graph/bundle.js +94 -0
- package/dist/graph/query.js +31 -0
- package/dist/graph/related.js +76 -0
- package/dist/handoff/handoff.js +148 -0
- package/dist/ids/ids.js +115 -0
- package/dist/index/build.js +57 -0
- package/dist/index/canonical.js +44 -0
- package/dist/index/scan.js +357 -0
- package/dist/index/types.js +2 -0
- package/dist/init/init.js +169 -0
- package/dist/lint/cli.js +253 -0
- package/dist/lint/py.js +152 -0
- package/dist/lint/selftest.js +246 -0
- package/dist/lint/ste.js +395 -0
- package/dist/lint/tells.js +116 -0
- package/dist/main.js +74 -0
- package/dist/stats/stats.js +147 -0
- package/dist/stats/write.js +26 -0
- package/dist/tasks/evidence.js +102 -0
- package/dist/tasks/tasks.js +105 -0
- package/dist/util/glob.js +43 -0
- package/dist/util/hash.js +14 -0
- package/dist/util/json.js +16 -0
- package/dist/util/pystr.js +67 -0
- package/dist/util/time.js +11 -0
- package/dist/verify/parse.js +58 -0
- package/dist/verify/run.js +42 -0
- package/dist/version.js +2 -0
- package/package.json +50 -4
- package/schemas/spec-driven-ids/README.md +31 -0
- package/schemas/spec-driven-ids/config-fragment.yaml +25 -0
- package/schemas/spec-driven-ids/schema.yaml +301 -0
- package/schemas/spec-driven-ids/templates/design.md +22 -0
- package/schemas/spec-driven-ids/templates/proposal.md +34 -0
- package/schemas/spec-driven-ids/templates/spec.md +15 -0
- package/schemas/spec-driven-ids/templates/tasks.md +10 -0
- package/skills/specctx-bootstrap/SKILL.md +27 -0
- package/skills/specctx-done/SKILL.md +25 -0
- package/skills/specctx-next/SKILL.md +23 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Quach Hoanh Son
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,299 @@
|
|
|
1
|
-
#
|
|
1
|
+
# spec-ctx
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Stable IDs for OpenSpec. Your coding agent loads only the spec one task needs, and knows what to do next.
|
|
4
|
+
|
|
5
|
+
For teams that run Claude Code, Codex, or Cursor on an OpenSpec project.
|
|
6
|
+
|
|
7
|
+
## What OpenSpec is
|
|
8
|
+
|
|
9
|
+
[OpenSpec](https://github.com/Fission-AI/OpenSpec) is a folder named `openspec/` in your repo. It holds specs, the rules your software must follow, and changes. A change holds a proposal, a design, and a task list. A coding agent reads them and works down the list.
|
|
10
|
+
|
|
11
|
+
## The problem
|
|
12
|
+
|
|
13
|
+
Three things go wrong.
|
|
14
|
+
|
|
15
|
+
- The agent reads the whole `openspec/` tree for every task. Most of it is not about the task. Tokens go to text the agent will not use.
|
|
16
|
+
- OpenSpec links a change to a spec by header text. A reword breaks the link, and the agent loses the thread between requirement, code, and test.
|
|
17
|
+
- A new session starts cold. The agent does not know which task is next, what it already did, or which questions are open.
|
|
18
|
+
|
|
19
|
+
## What spec-ctx adds
|
|
20
|
+
|
|
21
|
+
| Without | With spec-ctx |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| The agent reads the whole `openspec/` tree. | The agent gets the bundle of one task: the requirements it cites, their scenarios, decisions, and neighbors. |
|
|
24
|
+
| Links go by header text. | Every requirement, scenario, decision, and task has an ID. Code and tests carry tags that name it. |
|
|
25
|
+
| Each session starts cold. | `status`, `task next`, and the generated handoff say where the work stands. |
|
|
26
|
+
| The agent ticks `[x]` by hand. | `[x]` appears only after the Verify command of the task exits 0. The run leaves an evidence file. |
|
|
27
|
+
|
|
28
|
+
A later edit can break a task that passed. `check --strict` names each task whose files changed after its Verify passed.
|
|
29
|
+
|
|
30
|
+
No LLM call. No daemon. The same files give the same output.
|
|
31
|
+
|
|
32
|
+
## See it work
|
|
33
|
+
|
|
34
|
+
Six commands on a small project. Each clip is one command.
|
|
35
|
+
|
|
36
|
+
### 1. Give every requirement an ID
|
|
37
|
+
|
|
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
|
+
|
|
40
|
+

|
|
41
|
+
|
|
42
|
+
You see: the dry run prints each header and the ID it gets. Then `adopt` writes them.
|
|
43
|
+
|
|
44
|
+
### 2. Where are we
|
|
45
|
+
|
|
46
|
+
`status` shows the open change, the task counts, and the next command to run.
|
|
47
|
+
|
|
48
|
+

|
|
49
|
+
|
|
50
|
+
You see: one change, two tasks done, one open, and the hint.
|
|
51
|
+
|
|
52
|
+
### 3. What is next
|
|
53
|
+
|
|
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
|
+
|
|
56
|
+

|
|
57
|
+
|
|
58
|
+
You see: task `add-mfa/1.3`, its requirement, and `grep -q totp src/login.ts`.
|
|
59
|
+
|
|
60
|
+
### 4. Only the spec this task needs
|
|
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.
|
|
63
|
+
|
|
64
|
+

|
|
65
|
+
|
|
66
|
+
You see: 711 characters, 3.6 percent of the tree. The agent reads this instead of the tree.
|
|
67
|
+
|
|
68
|
+
### 5. Done means Verify passed
|
|
69
|
+
|
|
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
|
+
|
|
72
|
+

|
|
73
|
+
|
|
74
|
+
You see: a red refusal, the edit, then a green done line.
|
|
75
|
+
|
|
76
|
+
### 6. Hand off to the next session
|
|
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.
|
|
79
|
+
|
|
80
|
+

|
|
81
|
+
|
|
82
|
+
You see: the head of `.ctx/handoff.md`.
|
|
83
|
+
|
|
84
|
+
The transcript of all six clips is in `docs/demo/loop.txt`. The clips run on a copy of `tests/fixtures/no-id-repo` plus the six capabilities in `docs/demo/specs/`. `bash docs/demo/record.sh` records them again.
|
|
85
|
+
|
|
86
|
+
## Quickstart
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npx -y spec-ctx init --all # config, schema, and three agent skills
|
|
90
|
+
npx -y spec-ctx task next # the next task and the command that proves it
|
|
91
|
+
npx -y spec-ctx task start <task> # only the spec that this task needs
|
|
92
|
+
npx -y spec-ctx task done <task> # runs that command. A pass ticks the box.
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
A project with specs but no IDs needs `specctx adopt` first. See [Two paths](#two-paths).
|
|
96
|
+
|
|
97
|
+
## How it finds the context
|
|
98
|
+
|
|
99
|
+
```mermaid
|
|
100
|
+
graph LR
|
|
101
|
+
T["task add-mfa/1.3"] -->|cites| R["REQ-UA-004"]
|
|
102
|
+
R -->|scenario| S1["SCN-UA-004-1"]
|
|
103
|
+
R -->|scenario| S2["SCN-UA-004-2"]
|
|
104
|
+
R -->|"// REQ tag"| C["src/login.ts"]
|
|
105
|
+
R -->|"Covers:"| X["tests/login.test.ts"]
|
|
106
|
+
```
|
|
107
|
+
|
|
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
|
+
|
|
110
|
+
`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
|
+
|
|
112
|
+
```text
|
|
113
|
+
$ specctx related REQ-UA-004
|
|
114
|
+
scn SCN-UA-004-1 active openspec/changes/add-mfa/specs/user-auth/spec.md:8 scenario of REQ-UA-004
|
|
115
|
+
scn SCN-UA-004-2 active openspec/changes/add-mfa/specs/user-auth/spec.md:12 scenario of REQ-UA-004
|
|
116
|
+
task add-mfa/1.3 done openspec/changes/add-mfa/tasks.md:7 task cites REQ-UA-004
|
|
117
|
+
code src/login.ts:1 active src/login.ts:1 implements REQ-UA-004
|
|
118
|
+
test tests/login.test.ts:1 active tests/login.test.ts:1 covers REQ-UA-004
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## The life cycle for agents
|
|
122
|
+
|
|
123
|
+
`init --all` installs three skills. Each one fires at a fixed point of the session.
|
|
124
|
+
|
|
125
|
+
- `specctx-bootstrap` fires at the start of a session. It reads the status and the handoff and starts the task.
|
|
126
|
+
- `specctx-next` fires after a task closes. It picks the next task and loads its bundle.
|
|
127
|
+
- `specctx-done` fires when the work is complete. It runs Verify, closes the task, and writes the handoff.
|
|
128
|
+
|
|
129
|
+
The loop that the skills teach:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
specctx init --all # config, schema, and the three skills
|
|
133
|
+
specctx status # active changes, current task, open questions
|
|
134
|
+
specctx task next # the next open task, its REQ IDs, and its Verify command
|
|
135
|
+
specctx task start <task> # the bundle of that task and its size
|
|
136
|
+
specctx task done <task> # run Verify, write evidence, set the checkbox
|
|
137
|
+
specctx check --strict # IDs, links, coverage, and evidence
|
|
138
|
+
specctx handoff write # .ctx/handoff.md for the next session
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The rule: the agent never opens `openspec/specs/` by hand. It uses `task start` or `bundle`. The skills are in [`skills/`](skills/). Install them alone with `npx skills add sonqh/spec-ctx` or `specctx init --skills`.
|
|
142
|
+
|
|
143
|
+
## Install
|
|
144
|
+
|
|
145
|
+
spec-ctx needs Node 22. Run it in a project after `openspec init`.
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
npx -y spec-ctx init --all # from npm
|
|
149
|
+
npm install -g spec-ctx # puts specctx on your path
|
|
150
|
+
npx -y github:sonqh/spec-ctx#<sha> init --all # from git, for an unreleased commit
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The git form builds the package on install with Node only. Pin a commit SHA. A branch name such as `main` also works, but it moves.
|
|
154
|
+
|
|
155
|
+
The command writes `spec-ctx.toml`, installs the `spec-driven-ids` schema into `openspec/schemas/`, adds its rules to `openspec/config.yaml`, and installs three skills into `.claude/skills/` and `.agents/skills/`.
|
|
156
|
+
|
|
157
|
+
The examples below use `specctx`. With `npx`, write `npx -y spec-ctx` in its place. Each GitHub release also carries a single binary for Linux and macOS.
|
|
158
|
+
|
|
159
|
+
## Two paths
|
|
160
|
+
|
|
161
|
+
### Fresh project
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
npx -y @fission-ai/openspec@latest init
|
|
165
|
+
npx spec-ctx init --all
|
|
166
|
+
specctx gate --strict
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Existing OpenSpec project
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
npx spec-ctx init --all
|
|
173
|
+
specctx adopt --dry-run # the ID that each header gets. Writes nothing.
|
|
174
|
+
specctx adopt # writes the IDs into main specs and active deltas
|
|
175
|
+
specctx task backfill --manual "adopted"
|
|
176
|
+
specctx baseline write
|
|
177
|
+
specctx gate --strict
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
- `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`.
|
|
181
|
+
- `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.
|
|
182
|
+
- `adopt` runs `check` before and after the write. A finding that the write added undoes every file. A second run prints `nothing to adopt`.
|
|
183
|
+
- `task backfill` writes manual evidence for every ticked task that has none.
|
|
184
|
+
- `baseline write` records the debt of the day in `.ctx/baseline.json`: untested requirements and scenarios, tasks without evidence, and legacy tasks without a citation or a Verify code span. `check` hides that debt and prints `baselined: <n>`. New debt still shows.
|
|
185
|
+
- Legacy tasks stay in the baseline until an agent adds `[REQ-...]` citations and a `Verify:` code span. `specctx baseline prune` then drops them. No command adds a single entry.
|
|
186
|
+
|
|
187
|
+
Both paths have an end to end test: `tests/paths/fresh.sh` and `tests/paths/existing.sh`.
|
|
188
|
+
|
|
189
|
+
## Commands
|
|
190
|
+
|
|
191
|
+
| Command | Use |
|
|
192
|
+
| --- | --- |
|
|
193
|
+
| `build` | Write `.ctx/index.json`. Every other command rebuilds a stale index first. |
|
|
194
|
+
| `status` | Active changes with task counts, the current task, and open questions. |
|
|
195
|
+
| `query <term...>` | Entries that hold every term. Flags: `--kind`, `--change`, `--limit`, `--all`. |
|
|
196
|
+
| `related <ID>` | Linked entries with the reason for each link. Flags: `--depth`, `--all`. |
|
|
197
|
+
| `bundle <ID...>` | Spec text of the IDs, their scenarios, decisions, and neighbors. Flags: `--budget`, `--depth`, `--all`. |
|
|
198
|
+
| `check` | ID and link integrity. `--strict` adds test coverage and evidence. |
|
|
199
|
+
| `id new`, `id retire` | Allocate and retire IDs. |
|
|
200
|
+
| `task next`, `start`, `verify`, `done`, `reopen` | The task state machine. |
|
|
201
|
+
| `handoff write`, `ask`, `resolve` | The session handoff and its open questions. |
|
|
202
|
+
| `init` | Config file, schema bundle, and skills. Flags: `--schema`, `--skills`, `--all`, `--force`. |
|
|
203
|
+
| `adopt` | IDs for a project without them. `--dry-run` writes nothing. |
|
|
204
|
+
| `baseline write`, `prune` | The debt ratchet in `.ctx/baseline.json`. |
|
|
205
|
+
| `task backfill` | Manual evidence for ticked tasks without it. |
|
|
206
|
+
| `gate` | `check`, both markdown linters, `openspec validate`, and the 500 character length check on active deltas. |
|
|
207
|
+
| `lint tells`, `lint ste` | The two markdown linters. Flags: `--baseline`, `--disable`, `--json`, `--selftest`. |
|
|
208
|
+
| `help <command>` | The page of one command. `specctx <command> --help` prints the same page. |
|
|
209
|
+
|
|
210
|
+
Every command takes `--root <path>` and `--json`. A task ID takes the change key, such as `008/1.1` or `add-mfa/1.1`, or the full folder name. Outside an OpenSpec project every command but `init` exits 2. Exit codes: 0 for success, 1 for findings or a failed Verify command, 2 for a usage error, 3 for an internal error.
|
|
211
|
+
|
|
212
|
+
## Config reference
|
|
213
|
+
|
|
214
|
+
`spec-ctx.toml` sits at the project root. Every key is optional.
|
|
215
|
+
|
|
216
|
+
```toml
|
|
217
|
+
[scan]
|
|
218
|
+
specs = ["openspec/specs/**/*.md"] # main specs
|
|
219
|
+
changes = ["openspec/changes/**/*.md"] # active and archived changes
|
|
220
|
+
decisions = ["decisions/*.md"] # decision records
|
|
221
|
+
code = ["src/**"] # files with code tags
|
|
222
|
+
tests = ["tests/**"] # files with Covers tags and scenario IDs
|
|
223
|
+
ignore = [] # globs that the scan skips
|
|
224
|
+
|
|
225
|
+
[ids]
|
|
226
|
+
caps = [] # capability codes. Empty means any code is valid.
|
|
227
|
+
task_pattern = '\d+\.\d+' # the task number in a tasks.md checkbox
|
|
228
|
+
|
|
229
|
+
[ids.paths]
|
|
230
|
+
UA = "user-auth" # capability path of each code. adopt writes the derived ones.
|
|
231
|
+
|
|
232
|
+
[bundle]
|
|
233
|
+
budget = 8000 # characters
|
|
234
|
+
depth = 1
|
|
235
|
+
|
|
236
|
+
[verify]
|
|
237
|
+
timeout_s = 600
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
One environment variable changes the engine:
|
|
241
|
+
|
|
242
|
+
| Variable | Effect |
|
|
243
|
+
| --- | --- |
|
|
244
|
+
| `SPECCTX_OPENSPEC` | The command that `gate` runs for `openspec validate`. The default is `npx -y @fission-ai/openspec@latest`. Tests and offline machines set it. |
|
|
245
|
+
|
|
246
|
+
## Markdown linters
|
|
247
|
+
|
|
248
|
+
`specctx lint tells` and `specctx lint ste` replace `scripts/tells-lint.py` and `scripts/ste-lint.py` from the ste-humanize skill. They take the same flags and give the same output and exit codes. They need Node only.
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
# before
|
|
252
|
+
python3 scripts/tells-lint.py --baseline 0 README.md
|
|
253
|
+
python3 scripts/ste-lint.py --baseline 0 --disable synonym-rotation README.md
|
|
254
|
+
# after
|
|
255
|
+
npx spec-ctx lint tells --baseline 0 README.md
|
|
256
|
+
npx spec-ctx lint ste --baseline 0 --disable synonym-rotation README.md
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## GitHub Action
|
|
260
|
+
|
|
261
|
+
```yaml
|
|
262
|
+
- uses: sonqh/spec-ctx@v0
|
|
263
|
+
with:
|
|
264
|
+
version: latest # npm version, or a git spec such as github:sonqh/spec-ctx#main
|
|
265
|
+
strict: true
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
The action runs `specctx gate`: `check --strict`, both markdown linters, `openspec validate --all --strict`, and the length check.
|
|
269
|
+
|
|
270
|
+
## Status
|
|
271
|
+
|
|
272
|
+
- Published: `spec-ctx@0.1.1` on npm, with tags `v0.1.1` and `v0` and binaries on the GitHub release.
|
|
273
|
+
- Archived: 001 to 013. No change is open. See `CHANGELOG.md` for each release.
|
|
274
|
+
- A version bump on `main` publishes the next release. See `docs/CONTRIBUTING.md`.
|
|
275
|
+
- 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.
|
|
276
|
+
|
|
277
|
+
## Measured
|
|
278
|
+
|
|
279
|
+
`specctx stats --write` measures this repository after each change. The newest file is `docs/metrics/2026-10-26.md`. Its total row:
|
|
280
|
+
|
|
281
|
+
| change | context_ratio | evidence_ratio | stale_catches | cycle_time_median_s | debt |
|
|
282
|
+
| --- | --- | --- | --- | --- | --- |
|
|
283
|
+
| total | 0.0469 (n=41) | 0.85 (88/104) | 36 (of 62 reopens) | 60 (n=41) | 0 (0 baselined + 0 new) |
|
|
284
|
+
|
|
285
|
+
- `context_ratio`: the size of one task bundle divided by the size of all spec text. Lower is better.
|
|
286
|
+
- `evidence_ratio`: done tasks with a passed Verify command, divided by all done tasks. The rest have a manual note. Higher is better.
|
|
287
|
+
- `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.
|
|
288
|
+
- `cycle_time_median_s`: the median number of seconds from `task start` to `task done`. Lower is faster.
|
|
289
|
+
- `debt`: findings that the baseline hides plus new findings. Zero is the goal.
|
|
290
|
+
|
|
291
|
+
The value in brackets is the count of inputs. A value without inputs reads n/a.
|
|
292
|
+
|
|
293
|
+
## Contribute
|
|
294
|
+
|
|
295
|
+
`docs/CONTRIBUTING.md` holds the build steps, the layout, the ID scheme, and the gate steps.
|
|
296
|
+
|
|
297
|
+
## License
|
|
298
|
+
|
|
299
|
+
MIT.
|
package/action.yml
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: spec-ctx check
|
|
2
|
+
description: Run specctx check, the markdown linters, and openspec validate on an OpenSpec project.
|
|
3
|
+
branding:
|
|
4
|
+
icon: check-square
|
|
5
|
+
color: blue
|
|
6
|
+
inputs:
|
|
7
|
+
version:
|
|
8
|
+
description: Version of the spec-ctx npm package, or a git spec such as github:sonqh/spec-ctx#main.
|
|
9
|
+
default: latest
|
|
10
|
+
strict:
|
|
11
|
+
description: Run check and validate in strict mode.
|
|
12
|
+
default: "true"
|
|
13
|
+
markdown:
|
|
14
|
+
description: Globs of the markdown files that the linters read, separated by spaces.
|
|
15
|
+
default: "openspec/**/*.md decisions/*.md *.md"
|
|
16
|
+
specctx:
|
|
17
|
+
description: Command that runs specctx. Empty means npx with the version input.
|
|
18
|
+
default: ""
|
|
19
|
+
runs:
|
|
20
|
+
using: composite
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/setup-node@v4
|
|
23
|
+
with:
|
|
24
|
+
node-version: 22
|
|
25
|
+
- name: spec-ctx gates
|
|
26
|
+
shell: bash
|
|
27
|
+
run: bash "$GITHUB_ACTION_PATH/action/run.sh"
|
|
28
|
+
env:
|
|
29
|
+
INPUT_VERSION: ${{ inputs.version }}
|
|
30
|
+
INPUT_STRICT: ${{ inputs.strict }}
|
|
31
|
+
INPUT_MARKDOWN: ${{ inputs.markdown }}
|
|
32
|
+
INPUT_SPECCTX: ${{ inputs.specctx }}
|
|
33
|
+
SPECCTX_ACTION_PATH: ${{ github.action_path }}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// REQ-ADOPT-004 write derived codes into spec-ctx.toml under [ids] caps and [ids.paths]. Comments stay.
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { parse } from "smol-toml";
|
|
5
|
+
import { CONFIG_FILE, INIT_TEXT } from "../config.js";
|
|
6
|
+
/** The new text of spec-ctx.toml with the codes added. Codes already listed stay as they are. */
|
|
7
|
+
export function withCaps(text, caps) {
|
|
8
|
+
const cur = parse(text);
|
|
9
|
+
const have = new Set(cur.ids?.caps ?? []);
|
|
10
|
+
const paths = cur.ids?.paths ?? {};
|
|
11
|
+
const addCaps = caps.filter((c) => !have.has(c.code)).map((c) => c.code);
|
|
12
|
+
const addPaths = caps.filter((c) => paths[c.code] === undefined);
|
|
13
|
+
let lines = text.split("\n");
|
|
14
|
+
const sectionAt = (name) => lines.findIndex((l) => l.trim() === `[${name}]`);
|
|
15
|
+
if (addCaps.length) {
|
|
16
|
+
let ids = sectionAt("ids");
|
|
17
|
+
if (ids < 0) {
|
|
18
|
+
lines.push("", "[ids]");
|
|
19
|
+
ids = lines.length - 1;
|
|
20
|
+
}
|
|
21
|
+
let end = lines.findIndex((l, i) => i > ids && /^\s*\[/.test(l));
|
|
22
|
+
if (end < 0)
|
|
23
|
+
end = lines.length;
|
|
24
|
+
const at = lines.findIndex((l, i) => i > ids && i < end && /^caps\s*=/.test(l));
|
|
25
|
+
const all = [...(cur.ids?.caps ?? []), ...addCaps];
|
|
26
|
+
const line = `caps = [${all.map((c) => JSON.stringify(c)).join(", ")}]`;
|
|
27
|
+
if (at >= 0 && /\]\s*(#.*)?$/.test(lines[at]))
|
|
28
|
+
lines[at] = line;
|
|
29
|
+
else if (at >= 0) {
|
|
30
|
+
// a caps list over several lines: replace it up to the closing bracket
|
|
31
|
+
let close = at;
|
|
32
|
+
while (close < end && !/\]/.test(lines[close]))
|
|
33
|
+
close++;
|
|
34
|
+
lines.splice(at, close - at + 1, line);
|
|
35
|
+
}
|
|
36
|
+
else
|
|
37
|
+
lines.splice(ids + 1, 0, "# Capability codes. adopt added the derived ones.", line);
|
|
38
|
+
}
|
|
39
|
+
if (addPaths.length) {
|
|
40
|
+
let sec = sectionAt("ids.paths");
|
|
41
|
+
if (sec < 0) {
|
|
42
|
+
lines = [...lines.join("\n").replace(/\n*$/, "").split("\n"), "", "[ids.paths]", "# Capability path of each code. adopt wrote the derived ones. Rename a code here before the IDs spread."];
|
|
43
|
+
sec = lines.length - 1;
|
|
44
|
+
}
|
|
45
|
+
let end = lines.findIndex((l, i) => i > sec && /^\s*\[/.test(l));
|
|
46
|
+
if (end < 0)
|
|
47
|
+
end = lines.length;
|
|
48
|
+
while (end > sec + 1 && lines[end - 1].trim() === "")
|
|
49
|
+
end--;
|
|
50
|
+
lines.splice(end, 0, ...addPaths.map((c) => `${c.code} = ${JSON.stringify(c.path)}`));
|
|
51
|
+
}
|
|
52
|
+
const out = lines.join("\n").replace(/\n*$/, "\n");
|
|
53
|
+
parse(out);
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
export function configText(root) {
|
|
57
|
+
const p = join(root, CONFIG_FILE);
|
|
58
|
+
return existsSync(p) ? readFileSync(p, "utf8") : INIT_TEXT;
|
|
59
|
+
}
|