spec-ctx 0.1.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -30
- package/dist/adopt/plan.js +26 -14
- package/dist/assets.generated.js +7 -7
- package/dist/baseline/baseline.js +6 -3
- package/dist/baseline/ratchet.js +81 -0
- package/dist/check/check.js +78 -2
- package/dist/check/evidence.js +26 -14
- package/dist/check/hints.js +11 -2
- package/dist/cli/args.js +2 -2
- package/dist/cli/commands.js +50 -9
- package/dist/cli/gate.js +33 -8
- package/dist/cli/help.js +10 -9
- package/dist/cli/status.js +11 -36
- package/dist/cli/workflow.js +47 -41
- package/dist/config.js +55 -4
- package/dist/handoff/handoff.js +57 -24
- package/dist/handoff/questions.js +101 -0
- package/dist/ids/ids.js +11 -6
- package/dist/index/scan.js +8 -6
- package/dist/init/init.js +298 -36
- package/dist/loop/fails.js +102 -0
- package/dist/loop/next.js +193 -0
- package/dist/loop/openspec.js +41 -0
- package/dist/stats/stats.js +9 -4
- package/dist/tasks/evidence.js +5 -2
- package/dist/verify/parse.js +6 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/schemas/spec-driven-ids/README.md +12 -12
- package/schemas/spec-driven-ids/config-fragment.yaml +3 -2
- package/skills/specctx-bootstrap/SKILL.md +1 -22
- package/skills/specctx-done/SKILL.md +1 -20
- package/skills/specctx-next/SKILL.md +1 -18
- package/skills/specctx-run/SKILL.md +37 -0
- package/schemas/spec-driven-ids/schema.yaml +0 -301
package/README.md
CHANGED
|
@@ -75,7 +75,7 @@ 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.
|
|
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.
|
|
79
79
|
|
|
80
80
|

|
|
81
81
|
|
|
@@ -86,7 +86,7 @@ The transcript of all six clips is in `docs/demo/loop.txt`. The clips run on a c
|
|
|
86
86
|
## Quickstart
|
|
87
87
|
|
|
88
88
|
```bash
|
|
89
|
-
npx -y spec-ctx init --all # config,
|
|
89
|
+
npx -y spec-ctx init --all # config, ID templates, rules, and the agent skill
|
|
90
90
|
npx -y spec-ctx task next # the next task and the command that proves it
|
|
91
91
|
npx -y spec-ctx task start <task> # only the spec that this task needs
|
|
92
92
|
npx -y spec-ctx task done <task> # runs that command. A pass ticks the box.
|
|
@@ -120,25 +120,25 @@ test tests/login.test.ts:1 active tests/login.test.ts:1 covers REQ-UA-004
|
|
|
120
120
|
|
|
121
121
|
## The life cycle for agents
|
|
122
122
|
|
|
123
|
-
`init --all` installs
|
|
123
|
+
`init --all` installs one skill, `specctx-run`. The user types one prompt, such as "build this" or "continue". The skill loops until the change reaches the archive or a human must decide.
|
|
124
124
|
|
|
125
|
-
- `specctx
|
|
126
|
-
-
|
|
127
|
-
-
|
|
125
|
+
- It runs `specctx next --json`. The engine reads the files on disk and prints one action: `plan`, `cite`, `start`, `work`, `fix`, `repair`, `gate`, `archive`, `ask`, or `stop`.
|
|
126
|
+
- It acts on the action with the exact commands, runs `specctx handoff write`, and repeats.
|
|
127
|
+
- It stops twice for a human by default: at the plan, and before archive. A Verify command that fails three times writes a question and stops too.
|
|
128
|
+
- `specctx-bootstrap`, `specctx-next`, and `specctx-done` stay for one release as aliases. Each one says: run `specctx-run`.
|
|
128
129
|
|
|
129
|
-
The
|
|
130
|
+
The commands the skill runs for one task:
|
|
130
131
|
|
|
131
132
|
```bash
|
|
132
|
-
specctx init --all # config,
|
|
133
|
-
specctx
|
|
134
|
-
specctx task next # the next open task, its REQ IDs, and its Verify command
|
|
133
|
+
specctx init --all # config, ID templates, rules, and the skill
|
|
134
|
+
specctx next --json # the next action, its task, and its command
|
|
135
135
|
specctx task start <task> # the bundle of that task and its size
|
|
136
136
|
specctx task done <task> # run Verify, write evidence, set the checkbox
|
|
137
|
-
specctx
|
|
137
|
+
specctx gate --strict # check, baseline, linters, openspec validate, length
|
|
138
138
|
specctx handoff write # .ctx/handoff.md for the next session
|
|
139
139
|
```
|
|
140
140
|
|
|
141
|
-
The rule: the agent never opens `openspec/specs/` by hand. It uses `task start` or `bundle`. The
|
|
141
|
+
The rule: the agent never opens `openspec/specs/` by hand. It uses `task start` or `bundle`. It never ticks a checkbox and never runs `/opsx:apply`. The skill is in [`skills/`](skills/). Install it alone with `npx skills add sonqh/spec-ctx` or `specctx init --skills`.
|
|
142
142
|
|
|
143
143
|
## Install
|
|
144
144
|
|
|
@@ -152,27 +152,30 @@ npx -y github:sonqh/spec-ctx#<sha> init --all # from git, for an unreleas
|
|
|
152
152
|
|
|
153
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
154
|
|
|
155
|
-
The command writes `spec-ctx.toml`,
|
|
155
|
+
The command writes `spec-ctx.toml`, copies the templates with ID placeholders to `openspec/templates/spec-driven-ids/`, adds the spec-ctx `rules` and `operations` to `openspec/config.yaml`, and installs the `specctx-run` skill and its three aliases into `.claude/skills/` and `.agents/skills/`. The project keeps the stock `schema: spec-driven`. Upstream OpenSpec injects the rules into every artifact instruction. It pins the OpenSpec version it finds in `[gate] openspec`. It adds four lines to `.gitignore`: `.ctx/index.json`, `adopt-report.md`, `.ctx/current.json`, and `.ctx/gate-log.jsonl`. The rest of `.ctx/` is meant for git, `questions.json` included.
|
|
156
156
|
|
|
157
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
158
|
|
|
159
159
|
## Two paths
|
|
160
160
|
|
|
161
|
+
[`docs/getting-started.md`](docs/getting-started.md) walks the fresh path on upstream OpenSpec with real output, for a developer who knows OpenSpec.
|
|
162
|
+
|
|
161
163
|
### Fresh project
|
|
162
164
|
|
|
163
165
|
```bash
|
|
164
|
-
npx -y @fission-ai/openspec@latest init
|
|
166
|
+
npx -y @fission-ai/openspec@latest init --tools <tool>
|
|
165
167
|
npx spec-ctx init --all
|
|
166
168
|
specctx gate --strict
|
|
167
169
|
```
|
|
168
170
|
|
|
171
|
+
`--tools <tool>` names your agent, such as `claude`, and skips the interactive picker. `--no-animation` keeps logs clean. `init --all` merges the ID rules into an existing `rules` key of `openspec/config.yaml` item by item and prints `merged openspec/config.yaml rules (<n> added)`.
|
|
172
|
+
|
|
169
173
|
### Existing OpenSpec project
|
|
170
174
|
|
|
171
175
|
```bash
|
|
172
176
|
npx spec-ctx init --all
|
|
173
177
|
specctx adopt --dry-run # the ID that each header gets. Writes nothing.
|
|
174
178
|
specctx adopt # writes the IDs into main specs and active deltas
|
|
175
|
-
specctx task backfill --manual "adopted"
|
|
176
179
|
specctx baseline write
|
|
177
180
|
specctx gate --strict
|
|
178
181
|
```
|
|
@@ -180,12 +183,12 @@ specctx gate --strict
|
|
|
180
183
|
- `adopt` gives an ID to every requirement and scenario header without one, in `openspec/specs/` and in the deltas of active changes. A delta header gets the main ID by its text or through its RENAMED line. A delta header that matches neither keeps its text and goes into `adopt-report.md`.
|
|
181
184
|
- `adopt` derives a capability code from each folder name, for example `UA` for `user-auth`. It writes the codes into `spec-ctx.toml`. Rename a code there before the IDs spread.
|
|
182
185
|
- `adopt` runs `check` before and after the write. A finding that the write added undoes every file. A second run prints `nothing to adopt`.
|
|
183
|
-
- `
|
|
184
|
-
- `
|
|
185
|
-
- `[gate] markdown` in `spec-ctx.toml` names the markdown globs that `gate` lints.
|
|
186
|
+
- `baseline write` records the debt of the day in `.ctx/baseline.json`. The debt has five kinds. Ticked tasks without evidence. Legacy tasks without a citation or a Verify code span. Delta headers that match no main requirement. Hard lint findings per file and rule on the gate globs. `check` hides that debt and prints `baselined: <n>`. `gate` fails the lint step only when a count rises. New debt still shows.
|
|
187
|
+
- `gate` compares `.ctx/baseline.json` with the copy at the merge base of `HEAD` and `[gate] base`, default `origin/main`. A new entry fails the `baseline` step. Commit a grown baseline with a subject that starts with `baseline:` to pass once. In CI, check out with `fetch-depth: 0`, so the merge base exists. Without git or a merge base the step prints `skipped`.
|
|
188
|
+
- `[gate] markdown` in `spec-ctx.toml` names the markdown globs that `gate` lints. `init` writes `[]`, so the gate lints nothing until you opt in. The comment in the file names the globs this repository uses.
|
|
186
189
|
- Legacy tasks stay in the baseline until an agent adds `[REQ-...]` citations and a `Verify:` code span. `specctx baseline prune` then drops them. No command adds a single entry.
|
|
187
190
|
|
|
188
|
-
Both paths have an end to end test: `tests/paths/fresh.sh` and `tests/paths/existing.sh`.
|
|
191
|
+
Both paths have an end to end test on upstream OpenSpec: `tests/paths/fresh.sh` and `tests/paths/existing.sh`. `tests/paths/rules-only.sh` shows that the merged `rules` reach the stock `spec-driven` schema.
|
|
189
192
|
|
|
190
193
|
## Commands
|
|
191
194
|
|
|
@@ -193,18 +196,18 @@ Both paths have an end to end test: `tests/paths/fresh.sh` and `tests/paths/exis
|
|
|
193
196
|
| --- | --- |
|
|
194
197
|
| `build` | Write `.ctx/index.json`. Every other command rebuilds a stale index first. |
|
|
195
198
|
| `status` | Active changes with task counts, the current task, and open questions. |
|
|
199
|
+
| `next` | The next action of the loop from the files on disk: `plan`, `cite`, `start`, `work`, `fix`, `repair`, `gate`, `archive`, `ask`, or `stop`. `--json` prints one object. Flag: `--change`. |
|
|
196
200
|
| `query <term...>` | Entries that hold every term. Flags: `--kind`, `--change`, `--limit`, `--all`. |
|
|
197
201
|
| `related <ID>` | Linked entries with the reason for each link. Flags: `--depth`, `--all`. |
|
|
198
202
|
| `bundle <ID...>` | Spec text of the IDs, their scenarios, decisions, and neighbors. Flags: `--budget`, `--depth`, `--all`. |
|
|
199
|
-
| `check` | ID and link integrity. `--strict` adds test coverage and evidence. |
|
|
203
|
+
| `check` | ID and link integrity. `--strict` adds test coverage and evidence. `retitle-needs-renamed` names a retitled delta header that needs a RENAMED pair before `openspec archive`. |
|
|
200
204
|
| `id new`, `id retire` | Allocate and retire IDs. |
|
|
201
205
|
| `task next`, `start`, `verify`, `done`, `reopen` | The task state machine. |
|
|
202
|
-
| `handoff write`, `ask`, `resolve` | The session handoff and its open questions. |
|
|
203
|
-
| `init` | Config file,
|
|
206
|
+
| `handoff write`, `ask`, `resolve` | The session handoff and its open questions in `.ctx/questions.json`. |
|
|
207
|
+
| `init` | Config file, ID templates and rules, and skills. Flags: `--schema`, `--skills`, `--all`, `--force`. |
|
|
204
208
|
| `adopt` | IDs for a project without them. `--dry-run` writes nothing. |
|
|
205
209
|
| `baseline write`, `prune` | The debt ratchet in `.ctx/baseline.json`. |
|
|
206
|
-
| `
|
|
207
|
-
| `gate` | `check`, both markdown linters, `openspec validate`, and the 500 character length check on active deltas. |
|
|
210
|
+
| `gate` | `check`, the baseline against the merge base, both markdown linters, `openspec validate`, and the 500 character length check on active deltas. |
|
|
208
211
|
| `lint tells`, `lint ste` | The two markdown linters. Flags: `--baseline`, `--disable`, `--json`, `--selftest`. |
|
|
209
212
|
| `help <command>` | The page of one command. `specctx <command> --help` prints the same page. |
|
|
210
213
|
|
|
@@ -226,6 +229,7 @@ ignore = [] # globs that the scan skips
|
|
|
226
229
|
[ids]
|
|
227
230
|
caps = [] # capability codes. Empty means any code is valid.
|
|
228
231
|
task_pattern = '\d+\.\d+' # the task number in a tasks.md checkbox
|
|
232
|
+
brackets = false # true: adopt, id new, and init --schema write [REQ-UA-001] in headers. The scan reads both forms.
|
|
229
233
|
|
|
230
234
|
[ids.paths]
|
|
231
235
|
UA = "user-auth" # capability path of each code. adopt writes the derived ones.
|
|
@@ -236,13 +240,25 @@ depth = 1
|
|
|
236
240
|
|
|
237
241
|
[verify]
|
|
238
242
|
timeout_s = 600
|
|
243
|
+
|
|
244
|
+
[gate]
|
|
245
|
+
markdown = [] # globs that gate lints. Empty means no lint. This repo opts in.
|
|
246
|
+
openspec = "npx -y @fission-ai/openspec@1.15.0" # the command that runs OpenSpec. init pins the version it found.
|
|
247
|
+
base = "origin/main" # the ref whose merge base holds the baseline that gate compares with
|
|
248
|
+
|
|
249
|
+
[loop]
|
|
250
|
+
max_attempts = 3 # Verify failures on one task before task done writes a question
|
|
251
|
+
archive = "ask" # "ask" stops before archive for a human. "auto" lets the loop archive.
|
|
239
252
|
```
|
|
240
253
|
|
|
241
|
-
|
|
254
|
+
With `brackets = true`, `init --schema` installs templates with `[REQ-<CAP>-NNN]` and `[SCN-<CAP>-NNN-N]` in the headers. The merged rules show the same form. Task citations, `Refs:` lines, and tags stay bare. After you change the value, run `specctx init --schema` again. It switches each template and rule that you did not edit. It keeps the ones you edited, unless you add `--force`.
|
|
255
|
+
|
|
256
|
+
Two environment variables change the engine:
|
|
242
257
|
|
|
243
258
|
| Variable | Effect |
|
|
244
259
|
| --- | --- |
|
|
245
|
-
| `SPECCTX_OPENSPEC` | The command that `gate` runs for `openspec validate
|
|
260
|
+
| `SPECCTX_OPENSPEC` | The command that `gate` runs for `openspec validate` and `next` runs for `openspec status`. It overrides `[gate] openspec` for one run. At `init` time it is written into the file as it is, and no version detection runs. Tests and offline machines set it. |
|
|
261
|
+
| `SPECCTX_BASELINE_SUBJECT` | The commit subject that the `baseline` step of `gate` tests for `baseline:`. It replaces the subject of `HEAD`. Tests set it. |
|
|
246
262
|
|
|
247
263
|
## Markdown linters
|
|
248
264
|
|
|
@@ -270,18 +286,18 @@ The action runs `specctx gate`: `check --strict`, both markdown linters, `opensp
|
|
|
270
286
|
|
|
271
287
|
## Status
|
|
272
288
|
|
|
273
|
-
- Published: `spec-ctx@0.
|
|
274
|
-
- Archived: 001 to
|
|
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.
|
|
275
291
|
- A version bump on `main` publishes the next release. See `docs/CONTRIBUTING.md`.
|
|
276
292
|
- 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.
|
|
277
293
|
|
|
278
294
|
## Measured
|
|
279
295
|
|
|
280
|
-
`specctx stats --write` measures this repository after each change. The newest file is `docs/metrics/2026-10-
|
|
296
|
+
`specctx stats --write` measures this repository after each change. The newest file is `docs/metrics/2026-10-30.md`. Its total row:
|
|
281
297
|
|
|
282
298
|
| change | context_ratio | evidence_ratio | stale_catches | cycle_time_median_s | debt |
|
|
283
299
|
| --- | --- | --- | --- | --- | --- |
|
|
284
|
-
| total | 0.
|
|
300
|
+
| total | 0.0281 (n=93) | 0.91 (166/182) | 43 (of 90 reopens) | 76 (n=93) | 0 (0 baselined + 0 new) |
|
|
285
301
|
|
|
286
302
|
- `context_ratio`: the size of one task bundle divided by the size of all spec text. Lower is better.
|
|
287
303
|
- `evidence_ratio`: done tasks with a passed Verify command, divided by all done tasks. The rest have a manual note. Higher is better.
|
package/dist/adopt/plan.js
CHANGED
|
@@ -8,6 +8,9 @@ const REQ_HDR = /^### Requirement:[ \t]*(.*)$/s;
|
|
|
8
8
|
const SCN_HDR = /^#### Scenario:[ \t]*(.*)$/s;
|
|
9
9
|
const HAS_REQ = /^REQ-([A-Z]{2,6})-(\d{3})(?:[ \t]|$)/;
|
|
10
10
|
const HAS_SCN = /^SCN-([A-Z]{2,6})-(\d{3})-(\d+)(?:[ \t]|$)/;
|
|
11
|
+
// REQ-ADOPT-010 an ID in square brackets, the form of OpenSpec PR #1979 (027/D3)
|
|
12
|
+
const BR_REQ = /^\[(REQ-[A-Z]{2,6}-\d{3})\](?:[ \t]|$)/;
|
|
13
|
+
const BR_SCN = /^\[(SCN-[A-Z]{2,6}-\d{3}-\d+)\](?:[ \t]|$)/;
|
|
11
14
|
const RENAME = /^- (FROM|TO):[ \t]*`### Requirement:[ \t]*(.*)`[ \t]*$/s;
|
|
12
15
|
function listSpecs(root, base) {
|
|
13
16
|
const out = [];
|
|
@@ -26,12 +29,15 @@ function listSpecs(root, base) {
|
|
|
26
29
|
walk(join(root, base), base);
|
|
27
30
|
return out;
|
|
28
31
|
}
|
|
29
|
-
|
|
32
|
+
/** REQ-ADOPT-010 the ID at the start of a header text, bare or in square brackets, and the name after it. */
|
|
33
|
+
function splitId(text, re, br) {
|
|
30
34
|
const m = re.exec(text);
|
|
31
|
-
if (
|
|
32
|
-
return { id:
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
+
if (m)
|
|
36
|
+
return { id: m[0].trim(), name: text.slice(m[0].length).trim(), bracketed: false };
|
|
37
|
+
const b = br.exec(text);
|
|
38
|
+
if (b)
|
|
39
|
+
return { id: b[1], name: text.slice(b[0].length).trim(), bracketed: true };
|
|
40
|
+
return { id: null, name: text.trim(), bracketed: false };
|
|
35
41
|
}
|
|
36
42
|
function parse(root, rel, cap) {
|
|
37
43
|
const lines = splitLines(readFileSync(join(root, rel)).toString("utf8"));
|
|
@@ -53,21 +59,21 @@ function parse(root, rel, cap) {
|
|
|
53
59
|
}
|
|
54
60
|
let m = REQ_HDR.exec(line);
|
|
55
61
|
if (m) {
|
|
56
|
-
const { id, name } = splitId(m[1], HAS_REQ);
|
|
57
|
-
req = { kind: "req", line: i + 1, raw: line, name, id, section };
|
|
62
|
+
const { id, name, bracketed } = splitId(m[1], HAS_REQ, BR_REQ);
|
|
63
|
+
req = { kind: "req", line: i + 1, raw: line, name, id, bracketed, section };
|
|
58
64
|
blocks.push(req);
|
|
59
65
|
return;
|
|
60
66
|
}
|
|
61
67
|
m = SCN_HDR.exec(line);
|
|
62
68
|
if (m) {
|
|
63
|
-
const { id, name } = splitId(m[1], HAS_SCN);
|
|
64
|
-
blocks.push({ kind: "scn", line: i + 1, raw: line, name, id, req, section });
|
|
69
|
+
const { id, name, bracketed } = splitId(m[1], HAS_SCN, BR_SCN);
|
|
70
|
+
blocks.push({ kind: "scn", line: i + 1, raw: line, name, id, bracketed, req, section });
|
|
65
71
|
return;
|
|
66
72
|
}
|
|
67
73
|
m = RENAME.exec(line);
|
|
68
74
|
if (m && section === "RENAMED") {
|
|
69
|
-
const { id, name } = splitId(m[2], HAS_REQ);
|
|
70
|
-
const item = { line: i + 1, name, id, raw: line };
|
|
75
|
+
const { id, name, bracketed } = splitId(m[2], HAS_REQ, BR_REQ);
|
|
76
|
+
const item = { line: i + 1, name, id, bracketed, raw: line };
|
|
71
77
|
if (m[1] === "FROM")
|
|
72
78
|
from = item;
|
|
73
79
|
else if (from) {
|
|
@@ -189,11 +195,15 @@ export function planAdopt(root, cfg) {
|
|
|
189
195
|
};
|
|
190
196
|
const edits = [];
|
|
191
197
|
const unmatched = [];
|
|
192
|
-
|
|
193
|
-
const
|
|
198
|
+
// REQ-ADOPT-010 the configured form of a new ID: bare, or in square brackets (027/D3)
|
|
199
|
+
const form = (id, br = cfg.ids.brackets) => (br ? `[${id}]` : id);
|
|
200
|
+
const reqHeader = (id, name, br) => `### Requirement: ${form(id, br)} ${name}`;
|
|
201
|
+
const scnHeader = (id, name) => `#### Scenario: ${form(id)} ${name}`;
|
|
194
202
|
// main spec: requirement id by name, and scenario id by requirement and name
|
|
195
203
|
const mainReq = new Map();
|
|
196
204
|
const mainScn = new Map();
|
|
205
|
+
// the form of each main header after adopt: its own form when it had an ID, else the configured form
|
|
206
|
+
const mainBr = new Map();
|
|
197
207
|
for (const f of mainFiles) {
|
|
198
208
|
const code = codeFor.get(f.cap);
|
|
199
209
|
const seen = new Set();
|
|
@@ -210,6 +220,7 @@ export function planAdopt(root, cfg) {
|
|
|
210
220
|
if (!b.id)
|
|
211
221
|
edits.push({ file: f.rel, line: b.line, old: b.raw, new: reqHeader(id, b.name) });
|
|
212
222
|
mainReq.set(`${f.cap}\u0000${b.name}`, id);
|
|
223
|
+
mainBr.set(`${f.cap}\u0000${b.name}`, b.id ? b.bracketed : cfg.ids.brackets);
|
|
213
224
|
b.id = id;
|
|
214
225
|
}
|
|
215
226
|
else if (b.req?.id) {
|
|
@@ -232,8 +243,9 @@ export function planAdopt(root, cfg) {
|
|
|
232
243
|
continue;
|
|
233
244
|
}
|
|
234
245
|
renamedTo.set(r.to.name, id);
|
|
246
|
+
// REQ-ADOPT-010 the FROM line names the main header, so it takes the form of that header (027/D3)
|
|
235
247
|
if (!r.from.id)
|
|
236
|
-
edits.push({ file: f.rel, line: r.from.line, old: r.from.raw, new: `- FROM: \`${reqHeader(id, r.from.name)}\`` });
|
|
248
|
+
edits.push({ file: f.rel, line: r.from.line, old: r.from.raw, new: `- FROM: \`${reqHeader(id, r.from.name, mainBr.get(`${f.cap}\u0000${r.from.name}`))}\`` });
|
|
237
249
|
if (!r.to.id)
|
|
238
250
|
edits.push({ file: f.rel, line: r.to.line, old: r.to.raw, new: `- TO: \`${reqHeader(id, r.to.name)}\`` });
|
|
239
251
|
}
|
package/dist/assets.generated.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// Generated by scripts/gen-assets.mjs from schemas/spec-driven-ids/ and skills/. Do not edit.
|
|
2
|
-
// REQ-SCH-
|
|
2
|
+
// REQ-SCH-003 REQ-SKL-002
|
|
3
3
|
export const ASSETS = {
|
|
4
|
-
"schemas/spec-driven-ids/README.md": "
|
|
5
|
-
"schemas/spec-driven-ids/config-fragment.yaml": "
|
|
6
|
-
"schemas/spec-driven-ids/schema.yaml": "bmFtZTogc3BlYy1kcml2ZW4taWRzCnZlcnNpb246IDEKZGVzY3JpcHRpb246IE9wZW5TcGVjIHNwZWMtZHJpdmVuIHdvcmtmbG93IHdpdGggc3RhYmxlIElEcyBmcm9tIHNwZWMtY3R4IC0gcHJvcG9zYWwg4oaSIHNwZWNzIOKGkiBkZXNpZ24g4oaSIHRhc2tzCmFydGlmYWN0czoKICAtIGlkOiBwcm9wb3NhbAogICAgZ2VuZXJhdGVzOiBwcm9wb3NhbC5tZAogICAgZGVzY3JpcHRpb246IEluaXRpYWwgcHJvcG9zYWwgZG9jdW1lbnQgb3V0bGluaW5nIHRoZSBjaGFuZ2UKICAgIHRlbXBsYXRlOiBwcm9wb3NhbC5tZAogICAgaW5zdHJ1Y3Rpb246IHwKICAgICAgQ3JlYXRlIHRoZSBwcm9wb3NhbCBkb2N1bWVudCB0aGF0IGVzdGFibGlzaGVzIFdIWSB0aGlzIGNoYW5nZSBpcyBuZWVkZWQuCgogICAgICBTZWN0aW9uczoKICAgICAgLSAqKldoeSoqOiAxLTIgc2VudGVuY2VzIG9uIHRoZSBwcm9ibGVtIG9yIG9wcG9ydHVuaXR5LiBXaGF0IHByb2JsZW0gZG9lcyB0aGlzIHNvbHZlPyBXaHkgbm93PwogICAgICAtICoqV2hhdCBDaGFuZ2VzKio6IEJ1bGxldCBsaXN0IG9mIGNoYW5nZXMuIEJlIHNwZWNpZmljIGFib3V0IG5ldyBjYXBhYmlsaXRpZXMsIG1vZGlmaWNhdGlvbnMsIG9yIHJlbW92YWxzLiBNYXJrIGJyZWFraW5nIGNoYW5nZXMgd2l0aCAqKkJSRUFLSU5HKiouCiAgICAgIC0gKipDYXBhYmlsaXRpZXMqKjogSWRlbnRpZnkgd2hpY2ggc3BlY3Mgd2lsbCBiZSBjcmVhdGVkIG9yIG1vZGlmaWVkOgogICAgICAgIC0gKipOZXcgQ2FwYWJpbGl0aWVzKio6IExpc3QgY2FwYWJpbGl0aWVzIGJlaW5nIGludHJvZHVjZWQuIEVhY2ggYmVjb21lcyBhIG5ldyBgc3BlY3MvPGNhcGFiaWxpdHktcGF0aD4vc3BlYy5tZGAuIE5hbWUgZWFjaCBjYXBhYmlsaXR5IGZvciBhIGR1cmFibGUgc3lzdGVtIGJlaGF2aW9yIChmb3IgZXhhbXBsZSwgYHVzZXItYXV0aGApLCBub3QgdGhlIHdvcmsgaW4gdGhpcyBjaGFuZ2UgKGZvciBleGFtcGxlLCBgYWRkLWxvZ2luLWVuZHBvaW50YCkuIENob29zZSBhIGNvaGVzaXZlIGJvdW5kYXJ5IHRoYXQgY2FuIG93biByZWxhdGVkIHJlcXVpcmVtZW50cyBhcyB0aGUgc3lzdGVtIGV2b2x2ZXM7IGF2b2lkIGJyb2FkIGNhdGNoLWFsbCBjYXBhYmlsaXRpZXMuIFVzZSBrZWJhYi1jYXNlIGZvciBwYXRoIHNlZ21lbnRzIHlvdSBpbnRyb2R1Y2UgKGUuZy4sIGB1c2VyLWF1dGhgIG9yIGBpZGVudGl0eS91c2VyLWF1dGhgKSBhbmQgZm9sbG93IHRoZSBwcm9qZWN0J3MgZXhpc3Rpbmcgc3BlYyBvcmdhbml6YXRpb24uCiAgICAgICAgLSAqKk1vZGlmaWVkIENhcGFiaWxpdGllcyoqOiBMaXN0IGV4aXN0aW5nIGNhcGFiaWxpdGllcyB3aG9zZSBSRVFVSVJFTUVOVFMgYXJlIGNoYW5naW5nLiBPbmx5IGluY2x1ZGUgaWYgc3BlYy1sZXZlbCBiZWhhdmlvciBjaGFuZ2VzIChub3QganVzdCBpbXBsZW1lbnRhdGlvbiBkZXRhaWxzKS4gRWFjaCBuZWVkcyBhIGRlbHRhIHNwZWMgZmlsZS4gVXNlIHRoZSBleGFjdCBleGlzdGluZyBwYXRoIHVuZGVyIGBvcGVuc3BlYy9zcGVjcy9gLiBMZWF2ZSBlbXB0eSBpZiBubyByZXF1aXJlbWVudCBjaGFuZ2VzLgogICAgICAtICoqSW1wYWN0Kio6IEFmZmVjdGVkIGNvZGUsIEFQSXMsIGRlcGVuZGVuY2llcywgb3Igc3lzdGVtcy4KCiAgICAgIElNUE9SVEFOVDogVGhlIENhcGFiaWxpdGllcyBzZWN0aW9uIGlzIGNyaXRpY2FsLiBJdCBjcmVhdGVzIHRoZSBjb250cmFjdCBiZXR3ZWVuCiAgICAgIHByb3Bvc2FsIGFuZCBzcGVjcyBwaGFzZXMuIFJlc2VhcmNoIGV4aXN0aW5nIHNwZWNzIGJlZm9yZSBmaWxsaW5nIHRoaXMgaW46CiAgICAgIHJ1biBgb3BlbnNwZWMgbGlzdCAtLXNwZWNzYCBmb3IgdGhlIHByb2plY3QncyBjYXBhYmlsaXR5IGludmVudG9yeSwgdGhlbgogICAgICBgb3BlbnNwZWMgc2hvdyAiPHNwZWMtaWQ+IiAtLXR5cGUgc3BlYyAtLWpzb24gLS1uby1zY2VuYXJpb3NgIGZvciBhbnkgdGhhdAogICAgICBsb29rIHJlbGF0ZWQgLSB0aGF0IHJldHVybnMgYSBjYXBhYmlsaXR5J3MgcHVycG9zZSBhbmQgcmVxdWlyZW1lbnQgdGV4dHMKICAgICAgd2l0aG91dCBwdWxsaW5nIHdob2xlIHNwZWMgZmlsZXMgaW50byBjb250ZXh0LiBBcHBlbmQgYC0tc3RvcmUgIjxpZD4iYCB0bwogICAgICBib3RoIGNvbW1hbmRzIG9ubHkgZm9yIGEgcmVnaXN0ZXJlZCBzdGFuZGFsb25lIHN0b3JlLCBhbmQga2VlcCBgLS10eXBlCiAgICAgIHNwZWNgOiBhIGNoYW5nZSBhbmQgYSBzcGVjIHNoYXJpbmcgYSBuYW1lIGlzIG90aGVyd2lzZSBhbiBhbWJpZ3VvdXMtaXRlbQogICAgICBlcnJvci4gYG9wZW5zcGVjIGxpc3RgIHdpdGhvdXQgYC0tc3BlY3NgIGxpc3RzIGluLWZsaWdodCBjaGFuZ2VzLCBub3QKICAgICAgc3BlY3MgLSBpdCBuZXZlciBzaG93cyB3aGF0IHRoZSBwcm9qZWN0IGFscmVhZHkgY292ZXJzLiBSZXVzZSBhbiBleGlzdGluZwogICAgICBjYXBhYmlsaXR5J3MgZXhhY3QgcGF0aCBpbnN0ZWFkIG9mIGludHJvZHVjaW5nIGEgbmVhci1kdXBsaWNhdGUgbmFtZS4KICAgICAgVGhlIGZpbHRlcmVkIHJlYWQgaXMgb25seSBhbiBvdmVydmlldy4gQmVmb3JlIGRlY2lkaW5nIHdoYXQgaXMgYWxyZWFkeQogICAgICBjb3ZlcmVkIG9yIHdoYXQgc2hvdWxkIGNoYW5nZSwgcmVhZCBlYWNoIHJlbGV2YW50IHNwZWMgaW4gZnVsbCwgaW5jbHVkaW5nCiAgICAgIHNjZW5hcmlvcywgd2l0aCBgb3BlbnNwZWMgc2hvdyAiPHNwZWMtaWQ+IiAtLXR5cGUgc3BlY2AgKHNhbWUgYC0tc3RvcmVgIHJ1bGUpLgogICAgICBFYWNoIGNhcGFiaWxpdHkgbGlzdGVkIGhlcmUgd2lsbCBuZWVkIGEgY29ycmVzcG9uZGluZyBzcGVjIGZpbGUuCgogICAgICBFdmVyeSBjaGFuZ2UgbXVzdCBlaXRoZXIgZGVjbGFyZSBhdCBsZWFzdCBvbmUgY2FwYWJpbGl0eSAobmV3IG9yCiAgICAgIG1vZGlmaWVkKSBvciBleHBsaWNpdGx5IG9wdCBvdXQgb2Ygc3BlY3M6IGBvcGVuc3BlYyB2YWxpZGF0ZWAgcmVqZWN0cyBhCiAgICAgIGNoYW5nZSB3aXRoIHplcm8gZGVsdGFzIHVubGVzcyB0aGUgY2hhbmdlJ3MgYC5vcGVuc3BlYy55YW1sYCBzZXRzCiAgICAgIGBza2lwX3NwZWNzOiB0cnVlYC4gVXNlIGBza2lwX3NwZWNzOiB0cnVlYCBvbmx5IHdoZW4gbm8gc3BlYy1sZXZlbAogICAgICBiZWhhdmlvciBjaGFuZ2VzIChwdXJlIHJlZmFjdG9yLCB0b29saW5nLCBkb2NzKSAtIHNwZWNzIGRlc2NyaWJlCiAgICAgIGJlaGF2aW9yLCBzbyBpZiBiZWhhdmlvciBkb2VzIG5vdCBjaGFuZ2UsIG5vIHNwZWMgc2hvdWxkIGNoYW5nZSBlaXRoZXIuCiAgICAgIERvIG5vdCBpbnZlbnQgYSByZXF1aXJlbWVudCBqdXN0IHRvIHNhdGlzZnkgdmFsaWRhdGlvbi4KCiAgICAgIEtlZXAgaXQgY29uY2lzZSAoMS0yIHBhZ2VzKS4gRm9jdXMgb24gdGhlICJ3aHkiIG5vdCB0aGUgImhvdyIgLQogICAgICBpbXBsZW1lbnRhdGlvbiBkZXRhaWxzIGJlbG9uZyBpbiBkZXNpZ24ubWQuCgogICAgICBUaGlzIGlzIHRoZSBmb3VuZGF0aW9uIC0gc3BlY3MsIGRlc2lnbiwgYW5kIHRhc2tzIGFsbCBidWlsZCBvbiB0aGlzLgogICAgcmVxdWlyZXM6IFtdCgogIC0gaWQ6IHNwZWNzCiAgICBnZW5lcmF0ZXM6ICJzcGVjcy8qKi8qLm1kIgogICAgZGVzY3JpcHRpb246IERldGFpbGVkIHNwZWNpZmljYXRpb25zIGZvciB0aGUgY2hhbmdlCiAgICB0ZW1wbGF0ZTogc3BlYy5tZAogICAgaW5zdHJ1Y3Rpb246IHwKICAgICAgQ3JlYXRlIHNwZWNpZmljYXRpb24gZmlsZXMgdGhhdCBkZWZpbmUgV0hBVCB0aGUgc3lzdGVtIHNob3VsZCBkby4KCiAgICAgIEEgc3BlYyBpcyBhIGJlaGF2aW9yIGNvbnRyYWN0LCBub3QgYW4gaW1wbGVtZW50YXRpb24gcGxhbi4KCiAgICAgIEdvb2Qgc3BlYyBjb250ZW50OgogICAgICAtIE9ic2VydmFibGUgYmVoYXZpb3IgdXNlcnMgb3IgZG93bnN0cmVhbSBzeXN0ZW1zIHJlbHkgb24KICAgICAgLSBJbnB1dHMsIG91dHB1dHMsIGFuZCBlcnJvciBjb25kaXRpb25zCiAgICAgIC0gRXh0ZXJuYWwgY29uc3RyYWludHMgKHNlY3VyaXR5LCBwcml2YWN5LCByZWxpYWJpbGl0eSwgY29tcGF0aWJpbGl0eSkKICAgICAgLSBTY2VuYXJpb3MgdGhhdCBjYW4gYmUgdGVzdGVkIG9yIGV4cGxpY2l0bHkgdmFsaWRhdGVkCgogICAgICBBdm9pZCBpbiBzcGVjczoKICAgICAgLSBJbnRlcm5hbCBjbGFzcy9mdW5jdGlvbiBuYW1lcwogICAgICAtIExpYnJhcnkgb3IgZnJhbWV3b3JrIGNob2ljZXMKICAgICAgLSBTdGVwLWJ5LXN0ZXAgaW1wbGVtZW50YXRpb24gZGV0YWlscwogICAgICAtIERldGFpbGVkIGV4ZWN1dGlvbiBwbGFucyAodGhvc2UgYmVsb25nIGluIGRlc2lnbi5tZCBvciB0YXNrcy5tZCkKCiAgICAgIFF1aWNrIHRlc3Q6IGlmIHRoZSBpbXBsZW1lbnRhdGlvbiBjYW4gY2hhbmdlIHdpdGhvdXQgY2hhbmdpbmcgZXh0ZXJuYWxseQogICAgICB2aXNpYmxlIGJlaGF2aW9yLCBpdCBsaWtlbHkgZG9lcyBub3QgYmVsb25nIGluIHRoZSBzcGVjLgoKICAgICAgQ3JlYXRlIG9uZSBzcGVjIGZpbGUgcGVyIGNhcGFiaWxpdHkgbGlzdGVkIGluIHRoZSBwcm9wb3NhbCdzIENhcGFiaWxpdGllcyBzZWN0aW9uLgogICAgICBgPGNhcGFiaWxpdHktcGF0aD5gIGlzIHRoZSBzcGVjIGRpcmVjdG9yeSByZWxhdGl2ZSB0byBgc3BlY3MvYCAoZm9yIGV4YW1wbGUsCiAgICAgIGB1c2VyLWF1dGhgIG9yIGBpZGVudGl0eS91c2VyLWF1dGhgKS4gUHJlc2VydmUgdGhlIGZ1bGwgcGF0aDoKICAgICAgLSBOZXcgY2FwYWJpbGl0aWVzOiB1c2UgdGhlIGV4YWN0IHBhdGggZnJvbSB0aGUgcHJvcG9zYWwgYXQgYHNwZWNzLzxjYXBhYmlsaXR5LXBhdGg+L3NwZWMubWRgLiBBbnkgcGF0aCBzZWdtZW50IG5ld2x5IGludHJvZHVjZWQgaW4gdGhlIHByb3Bvc2FsIG11c3QgYmUga2ViYWItY2FzZS4gRm9sbG93IHRoZSBwcm9qZWN0J3MgZXhpc3Rpbmcgb3JnYW5pemF0aW9uOyBkbyBub3QgYWRkIGEgbmV3IGRvbWFpbiBsZXZlbCB3aGVuIHRoZSBwcm9qZWN0IHVzZXMgYSBmbGF0IGxheW91dC4KICAgICAgLSBNb2RpZmllZCBjYXBhYmlsaXRpZXM6IHVzZSB0aGUgZXhhY3QgZXhpc3RpbmcgcGF0aCBmcm9tIGBvcGVuc3BlYy9zcGVjcy88Y2FwYWJpbGl0eS1wYXRoPi9gIHdoZW4gY3JlYXRpbmcgdGhlIGRlbHRhIGF0IGBzcGVjcy88Y2FwYWJpbGl0eS1wYXRoPi9zcGVjLm1kYC4gUnVuIGBvcGVuc3BlYyBsaXN0IC0tc3BlY3NgIHRvIGNvbmZpcm0gdGhhdCBwYXRoIGJlZm9yZSB3cml0aW5nIHRoZSBkZWx0YSwgYXBwZW5kaW5nIGAtLXN0b3JlICI8aWQ+ImAgb25seSBmb3IgYSByZWdpc3RlcmVkIHN0YW5kYWxvbmUgc3RvcmUgLSBhIG1pc3R5cGVkIG9yIGludmVudGVkIHBhdGggdGFyZ2V0cyBhIGNhcGFiaWxpdHkgdGhhdCBkb2VzIG5vdCBleGlzdCByYXRoZXIgdGhhbiB0aGUgb25lIHlvdSBtZWFudC4gRG8gbm90IG1vdmUgb3IgcmVuYW1lIHRoZSBjYXBhYmlsaXR5LgoKICAgICAgV3JpdGUgdGhlIHNwZWMgZmlsZXMgb25lIGF0IGEgdGltZSwgc28gdGhlIHVzZXIgc2VlcyBwcm9ncmVzcyBhbmQgYW4KICAgICAgaW50ZXJydXB0ZWQgcnVuIGtlZXBzIHRoZSBmaWxlcyBpdCBhbHJlYWR5IHdyb3RlOgogICAgICAtIEJlZm9yZSB3cml0aW5nIGFueXRoaW5nLCBjb21wYXJlIHRoZSBjYXBhYmlsaXRpZXMgdGhlIHByb3Bvc2FsIGxpc3RzCiAgICAgICAgd2l0aCB0aGUgYHNwZWNzLzxjYXBhYmlsaXR5LXBhdGg+L3NwZWMubWRgIGZpbGVzIHRoYXQgYWxyZWFkeSBleGlzdCwKICAgICAgICBhbmQgc3RhcnQgd2l0aCB0aGUgZmlyc3QgY2FwYWJpbGl0eSB0aGF0IGhhcyBubyBmaWxlLiBEbyBub3QgcmV3cml0ZQogICAgICAgIGFuIGV4aXN0aW5nIGZpbGUgdW5sZXNzIHRoZSB1c2VyIGFza3MgeW91IHRvLgogICAgICAtIEJlZm9yZSBlYWNoIGZpbGUsIHNheSBpbiBvbmUgbGluZSB3aGljaCBjYXBhYmlsaXR5IHlvdSBhcmUgd3JpdGluZy4KICAgICAgLSBXcml0ZSBlYWNoIGZpbGUgYXMgc29vbiBhcyB5b3UgaGF2ZSBkcmFmdGVkIGl0LiBEbyBub3QgcGxhbiBldmVyeSBmaWxlCiAgICAgICAgYmVmb3JlIHdyaXRpbmcgdGhlIGZpcnN0LgogICAgICAtIFN0b3Agd2hlbiBldmVyeSBjYXBhYmlsaXR5IHRoZSBwcm9wb3NhbCBsaXN0cyBoYXMgaXRzIGZpbGUuCgogICAgICBUaGVyZSBtdXN0IGJlIGF0IGxlYXN0IG9uZSBzcGVjIGZpbGUgdW5sZXNzIHRoZSBjaGFuZ2UncyBgLm9wZW5zcGVjLnlhbWxgCiAgICAgIHNldHMgYHNraXBfc3BlY3M6IHRydWVgIChubyBzcGVjLWxldmVsIGJlaGF2aW9yIGNoYW5nZSkgLSBgb3BlbnNwZWMgdmFsaWRhdGVgCiAgICAgIHJlamVjdHMgYSB6ZXJvLWRlbHRhIGNoYW5nZSB3aXRob3V0IHRoYXQgbWFya2VyLiBJZiB0aGUgcHJvcG9zYWwgbGlzdHMgbm8KICAgICAgY2FwYWJpbGl0aWVzIGFuZCBgc2tpcF9zcGVjc2AgaXMgbm90IHNldCwgcmV2aXNpdCB0aGUgcHJvcG9zYWwgZmlyc3QuCgogICAgICBEZWx0YSBvcGVyYXRpb25zICh1c2UgIyMgaGVhZGVycyk6CiAgICAgIC0gKipBRERFRCBSZXF1aXJlbWVudHMqKjogTmV3IGNhcGFiaWxpdGllcwogICAgICAtICoqTU9ESUZJRUQgUmVxdWlyZW1lbnRzKio6IENoYW5nZWQgYmVoYXZpb3IgLSBNVVNUIGluY2x1ZGUgZnVsbCB1cGRhdGVkIGNvbnRlbnQKICAgICAgLSAqKlJFTU9WRUQgUmVxdWlyZW1lbnRzKio6IERlcHJlY2F0ZWQgZmVhdHVyZXMgLSBNVVNUIGluY2x1ZGUgKipSZWFzb24qKiBhbmQgKipNaWdyYXRpb24qKgogICAgICAtICoqUkVOQU1FRCBSZXF1aXJlbWVudHMqKjogTmFtZSBjaGFuZ2VzIG9ubHkgLSB1c2UgRlJPTTovVE86IGZvcm1hdAoKICAgICAgRm9ybWF0IHJlcXVpcmVtZW50czoKICAgICAgLSBFYWNoIHJlcXVpcmVtZW50OiBgIyMjIFJlcXVpcmVtZW50OiA8bmFtZT5gIGZvbGxvd2VkIGJ5IGRlc2NyaXB0aW9uCiAgICAgIC0gVXNlIFNIQUxML01VU1QgZm9yIG5vcm1hdGl2ZSByZXF1aXJlbWVudHMgKGF2b2lkIHNob3VsZC9tYXkpCiAgICAgIC0gRWFjaCBzY2VuYXJpbzogYCMjIyMgU2NlbmFyaW86IDxuYW1lPmAgd2l0aCBXSEVOL1RIRU4gZm9ybWF0CiAgICAgIC0gKipDUklUSUNBTCoqOiBTY2VuYXJpb3MgTVVTVCB1c2UgZXhhY3RseSA0IGhhc2h0YWdzIChgIyMjI2ApLiBVc2luZyAzIGhhc2h0YWdzIG9yIGJ1bGxldHMgd2lsbCBmYWlsIHNpbGVudGx5LgogICAgICAtIEV2ZXJ5IHJlcXVpcmVtZW50IE1VU1QgaGF2ZSBhdCBsZWFzdCBvbmUgc2NlbmFyaW8uCiAgICAgIC0gS2VlcCBlYWNoIHJlcXVpcmVtZW50J3MgZGVzY3JpcHRpb24gKHRoZSB0ZXh0IGJldHdlZW4gYCMjIyBSZXF1aXJlbWVudDpgIGFuZCBpdHMgZmlyc3Qgc2NlbmFyaW8pIHRvIDUwMCBjaGFyYWN0ZXJzIG9yIGZld2VyLiBgb3BlbnNwZWMgdmFsaWRhdGVgIGZsYWdzIGxvbmdlciBkZXNjcmlwdGlvbnMgaW4gQURERUQgcmVxdWlyZW1lbnRzIGFuZCBpbiB0aGUgbWFpbiBzcGVjLiBUaGlzIGlzIGEgd2FybmluZzogbm9ybWFsIHZhbGlkYXRpb24gc3RpbGwgcGFzc2VzLCBidXQgYG9wZW5zcGVjIHZhbGlkYXRlIC0tc3RyaWN0YCBmYWlscyBvbiBpdC4gV2hlbiB3cml0aW5nIGEgbmV3IHJlcXVpcmVtZW50LCBzdGF0ZSBvbmUgYmVoYXZpb3IgcGVyIHJlcXVpcmVtZW50OiBtb3ZlIGV4YW1wbGVzIGFuZCBlZGdlIGNhc2VzIGludG8gc2NlbmFyaW9zLCBhbmQgc3BsaXQgYSByZXF1aXJlbWVudCB0aGF0IGNvdmVycyBzZXZlcmFsIGJlaGF2aW9ycyBpbnRvIHNlcGFyYXRlIGAjIyMgUmVxdWlyZW1lbnQ6YCBibG9ja3MsIGVhY2ggd2l0aCBpdHMgb3duIHNjZW5hcmlvcy4gVW5kZXIgTU9ESUZJRUQsIGtlZXAgdGhlIGV4aXN0aW5nIHJlcXVpcmVtZW50IGJsb2NrIHdob2xlOyBuZXZlciBzcGxpdCwgdHJpbSBvciByZXdyaXRlIGV4aXN0aW5nIHRleHQganVzdCB0byBtZWV0IHRoZSBsZW5ndGguIFNwbGl0IGFuIGV4aXN0aW5nIGxvbmcgcmVxdWlyZW1lbnQgb25seSB3aGVuIHRoZSB1c2VyIGFza3MgZm9yIGl0LCBpbiBhIGNoYW5nZSBtYWRlIGZvciB0aGF0IHB1cnBvc2U6IHVuZGVyIE1PRElGSUVELCBrZWVwIGl0cyBoZWFkZXIgYW5kIGV2ZXJ5IHNjZW5hcmlvIGFuZCBjdXQgaXRzIGRlc2NyaXB0aW9uIGRvd24gdG8gb25lIGJlaGF2aW9yIHdpdGhvdXQgY2hhbmdpbmcgaXRzIG1lYW5pbmcsIHRoZW4gYWRkIGVhY2ggYmVoYXZpb3IgeW91IHJlbW92ZWQgYXMgaXRzIG93biBBRERFRCByZXF1aXJlbWVudCB3aXRoIGl0cyBvd24gc2NlbmFyaW9zLgoKICAgICAgTmV3IGNhcGFiaWxpdGllcyBvbmx5OiB0aGUgZGVsdGEgc3BlYydzIGZpcnN0IHNlY3Rpb24gaXMgYCMjIFB1cnBvc2VgIC0KICAgICAgb25lIG9yIHR3byBzZW50ZW5jZXMgKDUwKyBjaGFyYWN0ZXJzLCBvciBgb3BlbnNwZWMgdmFsaWRhdGUgLS1zdHJpY3RgCiAgICAgIHJlcG9ydHMgaXQgYXMgdG9vIGJyaWVmKSBkZXNjcmliaW5nIHdoYXQgdGhlIGNhcGFiaWxpdHkgaXMgZm9yLiBBcmNoaXZlCiAgICAgIGNvcGllcyBpdCBpbnRvIHRoZSBtYWluIHNwZWMgaXQgY3JlYXRlczsgd2l0aG91dCBpdCB0aGUgbmV3IG1haW4gc3BlYyBpcwogICAgICBsZWZ0IHdpdGggYSBgVEJEIC4uLiBVcGRhdGUgUHVycG9zZSBhZnRlciBhcmNoaXZlYCBwbGFjZWhvbGRlciB0byBmaWxsIGluCiAgICAgIGJ5IGhhbmQuIERvIE5PVCBhZGQgYCMjIFB1cnBvc2VgIHRvIGEgZGVsdGEgZm9yIGFuIGV4aXN0aW5nIGNhcGFiaWxpdHkgLQogICAgICB0aGF0IHNwZWMgYWxyZWFkeSBoYXMgb25lIGFuZCB0aGUgZGVsdGEncyBpcyBpZ25vcmVkLiBUbyBjaGFuZ2UgYW4KICAgICAgZXhpc3RpbmcgY2FwYWJpbGl0eSdzIFB1cnBvc2UgLSBpbmNsdWRpbmcgYSBsZWZ0b3ZlciBgVEJEYCBwbGFjZWhvbGRlciAtCiAgICAgIGVkaXQgYDxwbGFubmluZ0hvbWUucm9vdD4vb3BlbnNwZWMvc3BlY3MvPGNhcGFiaWxpdHktcGF0aD4vc3BlYy5tZGAKICAgICAgZGlyZWN0bHkuIGBwbGFubmluZ0hvbWUucm9vdGAgY29tZXMgZnJvbSB0aGUgYG9wZW5zcGVjIGluc3RydWN0aW9ucyAuLi4KICAgICAgLS1qc29uYCByZXNwb25zZS4gQWx3YXlzIHVzZSBpdCByYXRoZXIgdGhhbiBhIHJlcG8tcmVsYXRpdmUgcGF0aDogaXQKICAgICAgcmVzb2x2ZXMgdG8gdGhlIHN0b3JlIHdoZW5ldmVyIHRoZSBjaGFuZ2UgbGl2ZXMgaW4gb25lIC0gd2hldGhlciB0aGF0CiAgICAgIGNhbWUgZnJvbSBgLS1zdG9yZWAsIGEgcHJvamVjdCBgc3RvcmU6YCBwb2ludGVyLCBvciBhIGdsb2JhbCBkZWZhdWx0CiAgICAgIHN0b3JlIC0gYW5kIHRvIHRoZSBjdXJyZW50IHJlcG9zaXRvcnkgb3RoZXJ3aXNlLiBEbyBub3QgdHJ5IHRvIHdvcmsgb3V0CiAgICAgIHdoaWNoIGNhc2UgYXBwbGllczsgdGhlIGZpZWxkIGFscmVhZHkgaGFzLgoKICAgICAgTU9ESUZJRUQgcmVxdWlyZW1lbnRzIHdvcmtmbG93OgogICAgICAxLiBMb2NhdGUgdGhlIGV4aXN0aW5nIHJlcXVpcmVtZW50IGluIGA8cGxhbm5pbmdIb21lLnJvb3Q+L29wZW5zcGVjL3NwZWNzLzxjYXBhYmlsaXR5LXBhdGg+L3NwZWMubWRgICh0aGUgc2FtZSBzdG9yZS1hd2FyZSByb290IGFzIGFib3ZlKQogICAgICAyLiBDb3B5IHRoZSBFTlRJUkUgcmVxdWlyZW1lbnQgYmxvY2sgKGZyb20gYCMjIyBSZXF1aXJlbWVudDpgIHRocm91Z2ggYWxsIHNjZW5hcmlvcykKICAgICAgMy4gUGFzdGUgdW5kZXIgYCMjIE1PRElGSUVEIFJlcXVpcmVtZW50c2AgYW5kIGVkaXQgdG8gcmVmbGVjdCBuZXcgYmVoYXZpb3IKICAgICAgNC4gRW5zdXJlIGhlYWRlciB0ZXh0IG1hdGNoZXMgZXhhY3RseSAod2hpdGVzcGFjZS1pbnNlbnNpdGl2ZSkKCiAgICAgIENvbW1vbiBwaXRmYWxsOiBVc2luZyBNT0RJRklFRCB3aXRoIHBhcnRpYWwgY29udGVudCBsb3NlcyBkZXRhaWwgYXQgYXJjaGl2ZSB0aW1lLgogICAgICBJZiBhZGRpbmcgbmV3IGNvbmNlcm5zIHdpdGhvdXQgY2hhbmdpbmcgZXhpc3RpbmcgYmVoYXZpb3IsIHVzZSBBRERFRCBpbnN0ZWFkLgoKICAgICAgRXhhbXBsZSAoYSBuZXcgY2FwYWJpbGl0eSwgc28gaXRzIGZpcnN0IHNlY3Rpb24gaXMgYCMjIFB1cnBvc2VgKToKICAgICAgYGBgCiAgICAgICMgU3BlYyBEZWx0YQoKICAgICAgIyMgUHVycG9zZQoKICAgICAgTGV0cyB1c2VycyB0YWtlIHRoZWlyIGRhdGEgb3V0IG9mIHRoZSBwcm9kdWN0IGluIGEgcG9ydGFibGUgZm9ybWF0LgoKICAgICAgIyMgQURERUQgUmVxdWlyZW1lbnRzCgogICAgICAjIyMgUmVxdWlyZW1lbnQ6IFVzZXIgY2FuIGV4cG9ydCBkYXRhCiAgICAgIFRoZSBzeXN0ZW0gU0hBTEwgYWxsb3cgdXNlcnMgdG8gZXhwb3J0IHRoZWlyIGRhdGEgaW4gQ1NWIGZvcm1hdC4KCiAgICAgICMjIyMgU2NlbmFyaW86IFN1Y2Nlc3NmdWwgZXhwb3J0CiAgICAgIC0gKipXSEVOKiogdXNlciBjbGlja3MgIkV4cG9ydCIgYnV0dG9uCiAgICAgIC0gKipUSEVOKiogc3lzdGVtIGRvd25sb2FkcyBhIENTViBmaWxlIHdpdGggYWxsIHVzZXIgZGF0YQoKICAgICAgIyMgUkVNT1ZFRCBSZXF1aXJlbWVudHMKCiAgICAgICMjIyBSZXF1aXJlbWVudDogTGVnYWN5IGV4cG9ydAogICAgICAqKlJlYXNvbioqOiBSZXBsYWNlZCBieSBuZXcgZXhwb3J0IHN5c3RlbQogICAgICAqKk1pZ3JhdGlvbioqOiBVc2UgbmV3IGV4cG9ydCBlbmRwb2ludCBhdCAvYXBpL3YyL2V4cG9ydAogICAgICBgYGAKCgogICAgICBTdGFibGUgSURzIChzcGVjLWN0eCk6CiAgICAgIC0gRXZlcnkgcmVxdWlyZW1lbnQgaGVhZGVyIHN0YXJ0cyB3aXRoIGFuIElEOiBgIyMjIFJlcXVpcmVtZW50OiBSRVEtPENBUD4tTk5OIDxuYW1lPmAuCiAgICAgICAgR2V0IGEgbmV3IElEIGZyb20gYHNwZWNjdHggaWQgbmV3IFJFUSA8Q0FQPmAuIE5ldmVyIGNvdW50IG51bWJlcnMgYnkgaGFuZC4KICAgICAgICBgPENBUD5gIGlzIGEgY2FwYWJpbGl0eSBjb2RlIGZyb20gYFtpZHNdIGNhcHNgIGluIGBzcGVjLWN0eC50b21sYC4KICAgICAgLSBFdmVyeSBzY2VuYXJpbyBoZWFkZXIgc3RhcnRzIHdpdGggYW4gSUQ6IGAjIyMjIFNjZW5hcmlvOiBTQ04tPENBUD4tTk5OLTxuPiA8bmFtZT5gLgogICAgICAgIEdldCBpdCBmcm9tIGBzcGVjY3R4IGlkIG5ldyBTQ04gPENBUD4gLS1mb3IgUkVRLTxDQVA+LU5OTmAuCiAgICAgIC0gVW5kZXIgTU9ESUZJRUQsIFJFTU9WRUQsIG9yIFJFTkFNRUQsIGtlZXAgdGhlIGV4aXN0aW5nIElELiBBbiBJRCBuZXZlciBjaGFuZ2VzLgogICAgICAtIExpbmsgcmVsYXRlZCBJRHMgd2l0aCBvbmUgbGluZSBgUmVmczogSUQsIElEYCBhZnRlciB0aGUgcmVxdWlyZW1lbnQgdGV4dC4KICAgICAgLSBSZXBsYWNlIGV2ZXJ5IHBsYWNlaG9sZGVyIG9mIHRoZSB0ZW1wbGF0ZS4gYHNwZWNjdHggY2hlY2tgIHJlcG9ydHMgYSBoZWFkZXIgd2l0aAogICAgICAgIG5vIElEIGFzIGBtaXNzaW5nLWlkYC4KCiAgICAgIFNwZWNzIHNob3VsZCBiZSB0ZXN0YWJsZSAtIGVhY2ggc2NlbmFyaW8gaXMgYSBwb3RlbnRpYWwgdGVzdCBjYXNlLgogICAgcmVxdWlyZXM6CiAgICAgIC0gcHJvcG9zYWwKCiAgLSBpZDogZGVzaWduCiAgICBnZW5lcmF0ZXM6IGRlc2lnbi5tZAogICAgZGVzY3JpcHRpb246IFRlY2huaWNhbCBkZXNpZ24gZG9jdW1lbnQgd2l0aCBpbXBsZW1lbnRhdGlvbiBkZXRhaWxzCiAgICB0ZW1wbGF0ZTogZGVzaWduLm1kCiAgICBpbnN0cnVjdGlvbjogfAogICAgICBDcmVhdGUgdGhlIGRlc2lnbiBkb2N1bWVudCB0aGF0IGV4cGxhaW5zIEhPVyB0byBpbXBsZW1lbnQgdGhlIGNoYW5nZS4KCiAgICAgIFdoZW4gdG8gaW5jbHVkZSBkZXNpZ24ubWQgKGNyZWF0ZSBvbmx5IGlmIGFueSBhcHBseSk6CiAgICAgIC0gQ3Jvc3MtY3V0dGluZyBjaGFuZ2UgKG11bHRpcGxlIHNlcnZpY2VzL21vZHVsZXMpIG9yIG5ldyBhcmNoaXRlY3R1cmFsIHBhdHRlcm4KICAgICAgLSBOZXcgZXh0ZXJuYWwgZGVwZW5kZW5jeSBvciBzaWduaWZpY2FudCBkYXRhIG1vZGVsIGNoYW5nZXMKICAgICAgLSBTZWN1cml0eSwgcGVyZm9ybWFuY2UsIG9yIG1pZ3JhdGlvbiBjb21wbGV4aXR5CiAgICAgIC0gQW1iaWd1aXR5IHRoYXQgYmVuZWZpdHMgZnJvbSB0ZWNobmljYWwgZGVjaXNpb25zIGJlZm9yZSBjb2RpbmcKCiAgICAgIFNlY3Rpb25zOgogICAgICAtICoqQ29udGV4dCoqOiBPbmx5IHRoZSBjdXJyZW50IHN0YXRlIGFuZCBjb25zdHJhaW50cyBuZWVkZWQgdG8gZXhwbGFpbiB0aGUgYXBwcm9hY2guIFJlZmVyZW5jZSB0aGUgcHJvcG9zYWwgZm9yIG1vdGl2YXRpb24gaW5zdGVhZCBvZiByZXN0YXRpbmcgaXQgKGUuZy4sICJTZWUgcHJvcG9zYWwubWQgLSBXaHkiKS4KICAgICAgLSAqKkdvYWxzIC8gTm9uLUdvYWxzKio6IFdoYXQgdGhpcyBkZXNpZ24gYWNoaWV2ZXMgYW5kIGV4cGxpY2l0bHkgZXhjbHVkZXMuIERvbid0IHJlc3RhdGUgdGhlIHByb3Bvc2FsJ3Mgc2NvcGUgLSBhZGQgb25seSBkZXNpZ24tbGV2ZWwgYm91bmRhcmllcy4KICAgICAgLSAqKkRlY2lzaW9ucyoqOiBLZXkgdGVjaG5pY2FsIGNob2ljZXMgd2l0aCByYXRpb25hbGUgKHdoeSBYIG92ZXIgWT8pLiBJbmNsdWRlIGFsdGVybmF0aXZlcyBjb25zaWRlcmVkIGZvciBlYWNoIGRlY2lzaW9uLgogICAgICAtICoqUmlza3MgLyBUcmFkZS1vZmZzKio6IEtub3duIGxpbWl0YXRpb25zLCB0aGluZ3MgdGhhdCBjb3VsZCBnbyB3cm9uZy4gRm9ybWF0OiBbUmlza10g4oaSIE1pdGlnYXRpb24KICAgICAgLSAqKk1pZ3JhdGlvbiBQbGFuKio6IFN0ZXBzIHRvIGRlcGxveSwgcm9sbGJhY2sgc3RyYXRlZ3kgKGlmIGFwcGxpY2FibGUpCiAgICAgIC0gKipPcGVuIFF1ZXN0aW9ucyoqOiBVbmtub3ducyB0aGF0IGNhbiBzYWZlbHkgYmUgYW5zd2VyZWQgbGF0ZXIgd2l0aG91dAogICAgICAgIGNoYW5naW5nIHRoZSBzcGVjcywgdGhlIGFwcHJvYWNoLCBvciB0aGUgdGFzayBicmVha2Rvd24uIE9taXQgaWYgbm9uZS4KCiAgICAgIE9wZW4gcXVlc3Rpb25zIGFyZSBmb3IgZ2VudWluZWx5IGRlZmVycmFibGUgdW5rbm93bnMsIG5vdCBkZWNpc2lvbnMgeW91CiAgICAgIHNraXBwZWQuIElmIGEgcXVlc3Rpb24gd291bGQgY2hhbmdlIHRoZSBzcGVjcywgdGhlIGNob3NlbiBhcHByb2FjaCwgb3IKICAgICAgdGhlIHRhc2sgYnJlYWtkb3duLCByZXNvbHZlIGl0IG5vdyAtIGFzayB0aGUgdXNlciBpbnN0ZWFkIG9mIGd1ZXNzaW5nLgoKICAgICAgRm9jdXMgb24gYXJjaGl0ZWN0dXJlIGFuZCBhcHByb2FjaCwgbm90IGxpbmUtYnktbGluZSBpbXBsZW1lbnRhdGlvbi4KICAgICAgVGhlIHByb3Bvc2FsIGNvdmVycyB3aHkgYW5kIHdoYXQ7IGRlc2lnbiBjb3ZlcnMgaG93LiBSZWZlcmVuY2UgdGhlCiAgICAgIHByb3Bvc2FsIGZvciBtb3RpdmF0aW9uIGFuZCwgb25jZSB3cml0dGVuLCB0aGUgc3BlY3MgZm9yIHJlcXVpcmVtZW50cyAtCiAgICAgIGlmIGEgc2VjdGlvbiB3b3VsZCBvbmx5IHJlc3RhdGUgdGhlbSwgcG9pbnQgdG8gdGhlbSBpbnN0ZWFkLgoKCiAgICAgIFN0YWJsZSBJRHMgKHNwZWMtY3R4KToKICAgICAgLSBFYWNoIGJ1bGxldCB1bmRlciBgIyMgRGVjaXNpb25zYCBpcyBvbmUgZGVjaXNpb24uIEl0cyBJRCBpcyBgPGNoYW5nZSBudW1iZXI+L0Q8bj5gCiAgICAgICAgYnkgcG9zaXRpb24sIHNvIGFkZCBuZXcgZGVjaXNpb25zIGF0IHRoZSBlbmQgYW5kIG5ldmVyIHJlb3JkZXIgdGhlbS4KICAgICAgLSBFbmQgYSBkZWNpc2lvbiB3aXRoIGBSZWZzOiBgIGFuZCB0aGUgUkVRIG9yIEFEUiBJRHMgaXQgc2VydmVzLiBUaGVuIGBzcGVjY3R4IGJ1bmRsZWAKICAgICAgICBwcmludHMgdGhlIGRlY2lzaW9uIHdpdGggdGhvc2UgcmVxdWlyZW1lbnRzLgoKICAgICAgR29vZCBkZXNpZ24gZG9jcyBleHBsYWluIHRoZSAid2h5IiBiZWhpbmQgdGVjaG5pY2FsIGRlY2lzaW9ucy4KICAgIHJlcXVpcmVzOgogICAgICAtIHByb3Bvc2FsCgogIC0gaWQ6IHRhc2tzCiAgICBnZW5lcmF0ZXM6IHRhc2tzLm1kCiAgICBkZXNjcmlwdGlvbjogSW1wbGVtZW50YXRpb24gY2hlY2tsaXN0IHdpdGggdHJhY2thYmxlIHRhc2tzCiAgICB0ZW1wbGF0ZTogdGFza3MubWQKICAgIGluc3RydWN0aW9uOiB8CiAgICAgIENyZWF0ZSB0aGUgdGFzayBsaXN0IHRoYXQgYnJlYWtzIGRvd24gdGhlIGltcGxlbWVudGF0aW9uIHdvcmsuCgogICAgICBCZWZvcmUgd3JpdGluZyB0YXNrcywgY2hlY2sgZGVzaWduLm1kIGZvciBPcGVuIFF1ZXN0aW9ucy4gSWYgYW55IG9mIHRoZW0KICAgICAgd291bGQgY2hhbmdlIHdoYXQgZ2V0cyBidWlsdCwgcmVzb2x2ZSB0aGVtIHdpdGggdGhlIHVzZXIgZmlyc3QgLSBkbyBub3QKICAgICAgYmFrZSBhbiB1bnN0YXRlZCBhc3N1bXB0aW9uIGludG8gdGhlIHRhc2sgbGlzdC4KCiAgICAgICoqSU1QT1JUQU5UOiBGb2xsb3cgdGhlIHRlbXBsYXRlIGJlbG93IGZvciB0cmFja2VkIHRhc2tzLioqIFRoZSBhcHBseSBwaGFzZSBwYXJzZXMKICAgICAgY2hlY2tib3ggZm9ybWF0IHRvIHRyYWNrIHByb2dyZXNzLiBBIGJveCBob2xkaW5nIG9ubHkgYHhgIGNvdW50cyBhcyBkb25lLAogICAgICB1cHBlciBvciBsb3dlciBjYXNlIGFuZCB3aXRoIGFueSBzcGFjaW5nLCBzbyBgLSBbIHhdYCBpcyBkb25lIHRvby4gRXZlcnkKICAgICAgb3RoZXIgbWFya2VyLCBpbmNsdWRpbmcgYC0gW35dYCwgYC0gWy1dYCBhbmQgYW4gZW1wdHkgYC0gW11gLCByZWFkcyBhcwogICAgICB1bmZpbmlzaGVkLiBBIGxpbmUgd2l0aCBubyBjaGVja2JveCBpcyBub3QgdHJhY2tlZCBhdCBhbGwuCgogICAgICBHdWlkZWxpbmVzOgogICAgICAtIEdyb3VwIHJlbGF0ZWQgdHJhY2tlZCB0YXNrcyB1bmRlciAjIyBudW1iZXJlZCBoZWFkaW5ncwogICAgICAtIEVhY2ggdHJhY2tlZCB0YXNrIE1VU1QgYmUgYSBjaGVja2JveDogYC0gWyBdIFguWSBUYXNrIGRlc2NyaXB0aW9uYAogICAgICAtIFRhc2tzIHNob3VsZCBiZSBzbWFsbCBlbm91Z2ggdG8gY29tcGxldGUgaW4gb25lIHNlc3Npb24KICAgICAgLSBPcmRlciB0YXNrcyBieSBkZXBlbmRlbmN5ICh3aGF0IG11c3QgYmUgZG9uZSBmaXJzdD8pCiAgICAgIC0gVHJhY2sgaW1wbGVtZW50YXRpb24gYW5kIHZlcmlmaWNhdGlvbiB3b3JrIHRoYXQgY2FuIGJlIGNvbXBsZXRlZCBiZWZvcmUKICAgICAgICBhcmNoaXZlLiBJZiB0aGUgcmVxdWVzdGVkIHdvcmtmbG93IGluY2x1ZGVzIGFyY2hpdmUgb3Igd29yayB0aGF0IHJlcXVpcmVzCiAgICAgICAgdGhpcyBjaGFuZ2UgdG8gYmUgYXJjaGl2ZWQsIHByZXNlcnZlIHRob3NlIHN0ZXBzIGFzIHBsYWluIGJ1bGxldHMgaW4gYW4KICAgICAgICBvcHRpb25hbCBgIyMgV29ya2Zsb3cgZm9sbG93LXVwYCBzZWN0aW9uIGF0IHRoZSBlbmQgb2YgdGFza3MubWQuIFRoZXNlCiAgICAgICAgYnVsbGV0cyBhcmUgcmVmZXJlbmNlIGluZm9ybWF0aW9uIG91dHNpZGUgdHJhY2tlZCB0YXNrIHByb2dyZXNzLgogICAgICAtIEVhY2ggdGFzayBNVVNUIHN0YXRlIGhvdyB0byB2ZXJpZnkgY29tcGxldGlvbiAoYSB0ZXN0LCBjb21tYW5kLAogICAgICAgIG9ic2VydmFibGUgYmVoYXZpb3IsIG9yIGRlbGl2ZXJlZCBhcnRpZmFjdCkuIFB1dCB0aGUgdmVyaWZpY2F0aW9uIGluCiAgICAgICAgdGhhdCB0YXNrJ3MgY2hlY2tib3ggZGVzY3JpcHRpb24uIFVzZSBhIHNlcGFyYXRlIHZlcmlmaWNhdGlvbiB0YXNrIG9ubHkKICAgICAgICB3aGVuIGl0IGNoZWNrcyBicm9hZGVyIGludGVncmF0aW9uIG9yIHN5c3RlbSBiZWhhdmlvciB0aGF0IHNwYW5zCiAgICAgICAgbXVsdGlwbGUgaW1wbGVtZW50YXRpb24gdGFza3MuCiAgICAgIC0gRWFjaCB0YXNrIGdyb3VwIE1VU1QgbGFuZCB0aGUgdGVzdHMgYW5kIGRvY3VtZW50YXRpb24gaXRzIG93biB3b3JrCiAgICAgICAgY2FsbHMgZm9yLiBEbyBOT1QgY29sbGVjdCB0ZXN0aW5nIG9yIGRvY3VtZW50YXRpb24gaW50byBhIGZpbmFsIGdyb3VwIC0KICAgICAgICB3aGVuIGEgbGF0ZSBncm91cCBmaXJzdCBleGVyY2lzZXMgd29yayBmcm9tIGFuIGVhcmx5IG9uZSwgdGhlIGZhaWx1cmVzCiAgICAgICAgY2FzY2FkZSBiYWNrIHRocm91Z2ggZXZlcnkgZ3JvdXAgaW4gYmV0d2VlbiBhbmQgZm9yY2UgcmV3b3JrLiBBIGdyb3VwCiAgICAgICAgd2hvc2Ugd29yayBjYWxscyBmb3IgbmVpdGhlciwgc3VjaCBhcyBzY2FmZm9sZGluZyBvciBkZXBlbmRlbmN5IHNldHVwLAogICAgICAgIGNhcnJpZXMgbmVpdGhlci4gQSBmaW5hbCBncm91cCBpcyBmb3IgaW50ZWdyYXRpb24gY2hlY2tzIG9ubHksIG5vdCBmb3IKICAgICAgICB0aGUgdGVzdHMgYW5kIGRvY3MgYW4gZWFybGllciBncm91cCBvd2VkLgoKICAgICAgRXhhbXBsZToKICAgICAgYGBgCiAgICAgICMgVGFza3MKCiAgICAgICMjIDEuIFNldHVwCgogICAgICAtIFsgXSAxLjEgQ3JlYXRlIG5ldyBtb2R1bGUgc3RydWN0dXJlIGFuZCB2ZXJpZnkgZXhwZWN0ZWQgZmlsZXMgYXJlIHByZXNlbnQKICAgICAgLSBbIF0gMS4yIEFkZCBkZXBlbmRlbmNpZXMgdG8gcGFja2FnZS5qc29uIGFuZCB2ZXJpZnkgcGFja2FnZSBpbnN0YWxsYXRpb24gc3VjY2VlZHMKCiAgICAgICMjIDIuIENvcmUgSW1wbGVtZW50YXRpb24KCiAgICAgIC0gWyBdIDIuMSBJbXBsZW1lbnQgZGF0YSBleHBvcnQgZnVuY3Rpb24gYW5kIHZlcmlmeSB0aGUgZXhwb3J0IHRlc3QgcGFzc2VzCiAgICAgIC0gWyBdIDIuMiBBZGQgQ1NWIGZvcm1hdHRpbmcgdXRpbGl0aWVzIGFuZCB2ZXJpZnkgdW5pdCB0ZXN0cyBjb3ZlciBxdW90aW5nIGFuZCBkZWxpbWl0ZXJzCiAgICAgIC0gWyBdIDIuMyBEb2N1bWVudCB0aGUgZXhwb3J0IEFQSSBpbiBkb2NzL2V4cG9ydC5tZCBhbmQgdmVyaWZ5IHRoZSBkb2N1bWVudGVkIGNvbW1hbmQgcnVucyBhcyB3cml0dGVuCiAgICAgIGBgYAoKICAgICAgV2hlbiBhcHBsaWNhYmxlLCBhcHBlbmQgd29ya2Zsb3cgZm9sbG93LXVwIGFzIHBsYWluIGJ1bGxldHMsIGZvciBleGFtcGxlOgogICAgICBgYGAKICAgICAgIyMgV29ya2Zsb3cgZm9sbG93LXVwCgogICAgICAtIEFyY2hpdmUgdGhlIGNoYW5nZSBhZnRlciB0aGUgcHJvamVjdCdzIHJldmlldyByZXF1aXJlbWVudHMgYXJlIHNhdGlzZmllZC4KICAgICAgLSBWZXJpZnkgdGhlIGFyY2hpdmVkIHJlc3VsdC4KICAgICAgYGBgCgoKICAgICAgU3RhYmxlIElEcyBhbmQgVmVyaWZ5IChzcGVjLWN0eCk6CiAgICAgIC0gRXZlcnkgdGFzayBjaXRlcyBhdCBsZWFzdCBvbmUgcmVxdWlyZW1lbnQgaW4gc3F1YXJlIGJyYWNrZXRzOiBgW1JFUS08Q0FQPi1OTk5dYC4KICAgICAgLSBFdmVyeSB0YXNrIGVuZHMgd2l0aCBgVmVyaWZ5OmAgYW5kIHRoZSBjb21tYW5kIGluIGEgY29kZSBzcGFuLCBmb3IgZXhhbXBsZQogICAgICAgIGBgVmVyaWZ5OiBgbnBtIHRlc3QgLS0gdGVzdHMvZXhwb3J0LnRlc3QudHNgIGBgLiBUd28gc3BhbnMgcnVuIGpvaW5lZCB3aXRoIGAmJmAuCiAgICAgIC0gVXNlIHByb3NlIGFmdGVyIGBWZXJpZnk6YCBvbmx5IHdoZW4gbm8gY29tbWFuZCBjYW4gY2hlY2sgdGhlIHdvcmsuIFRoZW4KICAgICAgICBgc3BlY2N0eCB0YXNrIGRvbmUgPGlkPiAtLW1hbnVhbCAiPG5vdGU+ImAgcmVjb3JkcyBpdC4KICAgICAgLSBDbG9zZSBhIHRhc2sgb25seSB3aXRoIGBzcGVjY3R4IHRhc2sgZG9uZSA8aWQ+YC4gSXQgcnVucyB0aGUgY29tbWFuZCwgd3JpdGVzCiAgICAgICAgZXZpZGVuY2UgdW5kZXIgYC5jdHgvZXZpZGVuY2UvYCwgYW5kIHNldHMgdGhlIGNoZWNrYm94LgoKICAgICAgUmVmZXJlbmNlIHNwZWNzIGZvciB3aGF0IG5lZWRzIHRvIGJlIGJ1aWx0LCBkZXNpZ24gZm9yIGhvdyB0byBidWlsZCBpdC4KICAgIHJlcXVpcmVzOgogICAgICAtIHNwZWNzCiAgICAgIC0gZGVzaWduCgphcHBseToKICByZXF1aXJlczogWyB0YXNrcyBdCiAgdHJhY2tzOiB0YXNrcy5tZAogIGluc3RydWN0aW9uOiB8CiAgICBSZWFkIGNvbnRleHQgZmlsZXMsIHdvcmsgdGhyb3VnaCBwZW5kaW5nIHRhc2tzLCBtYXJrIGNvbXBsZXRlIGFzIHlvdSBnby4KICAgIFBhdXNlIGlmIHlvdSBoaXQgYmxvY2tlcnMgb3IgbmVlZCBjbGFyaWZpY2F0aW9uLgo=",
|
|
4
|
+
"schemas/spec-driven-ids/README.md": "IyBzcGVjLWRyaXZlbi1pZHMKClRlbXBsYXRlcyBhbmQgY29uZmlnIHJ1bGVzIHRoYXQgYWRkIHN0YWJsZSBJRHMgZnJvbSBzcGVjLWN0eCB0byB0aGUgT3BlblNwZWMgYHNwZWMtZHJpdmVuYCB3b3JrZmxvdy4gVGhlIHByb2plY3Qga2VlcHMgYHNjaGVtYTogc3BlYy1kcml2ZW5gIGluIGBvcGVuc3BlYy9jb25maWcueWFtbGAuIFVwc3RyZWFtIE9wZW5TcGVjIGluamVjdHMgdGhlIGBydWxlc2AgYW5kIGBvcGVyYXRpb25zYCBiZWxvdyBpbnRvIGV2ZXJ5IGFydGlmYWN0IGluc3RydWN0aW9uLCBzbyB0aGUgYWdlbnQgbGVhcm5zIHRoZSBJRCBjb21tYW5kcyBmcm9tIHRoZXJlLgoKLSBgdGVtcGxhdGVzL3NwZWMubWRgIHN0YXJ0cyBlYWNoIHJlcXVpcmVtZW50IHdpdGggYFJFUS08Q0FQPi1OTk5gIGFuZCBlYWNoIHNjZW5hcmlvIHdpdGggYFNDTi08Q0FQPi1OTk4tPG4+YC4KLSBgdGVtcGxhdGVzL3Rhc2tzLm1kYCBjaXRlcyBSRVEgSURzIGFuZCBwdXRzIHRoZSBgVmVyaWZ5OmAgY29tbWFuZCBpbiBhIGNvZGUgc3Bhbiwgc28gYHNwZWNjdHggdGFzayBkb25lYCBjYW4gcnVuIGl0LgotIGB0ZW1wbGF0ZXMvZGVzaWduLm1kYCBlbmRzIGVhY2ggZGVjaXNpb24gd2l0aCBhIGBSZWZzOmAgbGluZS4KLSBgY29uZmlnLWZyYWdtZW50LnlhbWxgIGhvbGRzIHRoZSBgcnVsZXNgIGFuZCBgb3BlcmF0aW9uc2AgZm9yIGBvcGVuc3BlYy9jb25maWcueWFtbGAuIFRoZSBydWxlcyB0ZWxsIHRoZSBhZ2VudCB0byBnZXQgSURzIGZyb20gYHNwZWNjdHggaWQgbmV3YCBhbmQgbmFtZSB0aGUgdGVtcGxhdGVzIHBhdGguIFRoZSBgYXBwbHlgIGd1aWRhbmNlIHNheXMgdGhhdCBhIHRhc2sgY2xvc2VzIG9ubHkgd2l0aCBgc3BlY2N0eCB0YXNrIGRvbmUgPGlkPmAgYW5kIHRoYXQgdGhlIGFnZW50IHJ1bnMgdGhlIGBzcGVjY3R4LXJ1bmAgc2tpbGwsIG5ldmVyIGAvb3BzeDphcHBseWAuCgojIyBJbnN0YWxsIGJ5IGNvcHkKCi0gQ2xvbmUgdGhlIHJlcG9zaXRvcnk6IGBnaXQgY2xvbmUgaHR0cHM6Ly9naXRodWIuY29tL3NvbnFoL3NwZWMtY3R4YC4KLSBDb3B5IGBzY2hlbWFzL3NwZWMtZHJpdmVuLWlkcy90ZW1wbGF0ZXNgIHRvIGBvcGVuc3BlYy90ZW1wbGF0ZXMvc3BlYy1kcml2ZW4taWRzYCBpbiB5b3VyIHByb2plY3QuCi0gS2VlcCBgc2NoZW1hOiBzcGVjLWRyaXZlbmAgaW4gYG9wZW5zcGVjL2NvbmZpZy55YW1sYC4KLSBNZXJnZSBgY29uZmlnLWZyYWdtZW50LnlhbWxgIGludG8gYG9wZW5zcGVjL2NvbmZpZy55YW1sYCBieSBoYW5kLiBBZGQgaXRzIGBydWxlc2AgYW5kIGBvcGVyYXRpb25zYCBrZXlzLiBLZWVwIGV2ZXJ5IGl0ZW0gcXVvdGVkLgotIFJ1biBgb3BlbnNwZWMgdmFsaWRhdGUgLS1hbGxgLgoKIyMgSW5zdGFsbCB3aXRoIGluaXQgLS1zY2hlbWEKCmBgYGJhc2gKbnB4IC15IHNwZWMtY3R4IGluaXQgLS1zY2hlbWEKYGBgCgotIFRoZSBjb21tYW5kIGNvcGllcyB0aGUgZm91ciB0ZW1wbGF0ZXMgdG8gYG9wZW5zcGVjL3RlbXBsYXRlcy9zcGVjLWRyaXZlbi1pZHMvYC4KLSBJdCBhZGRzIHRoZSBgcnVsZXNgIGFuZCBgb3BlcmF0aW9uc2Aga2V5cyB0aGF0IHRoZSBjb25maWcgZG9lcyBub3QgaGF2ZSB5ZXQuIEEga2V5IHRoYXQgZXhpc3RzIGdldHMgdGhlIGl0ZW1zIGl0IGxhY2tzLiBPdGhlciBrZXlzIHN0YXkgYXMgdGhleSBhcmUuCi0gSXQgcXVvdGVzIGEgbGlzdCBpdGVtIHRoYXQgWUFNTCB3b3VsZCByZWFkIGFzIGEgbWFwcGluZy4KLSBJdCBkb2VzIG5vdCB0b3VjaCB0aGUgYHNjaGVtYWAga2V5LiBBIHByb2plY3QgdGhhdCBzdGlsbCBzYXlzIGBzY2hlbWE6IHNwZWMtZHJpdmVuLWlkc2AgZ2V0cyBhIG5vdGUuIFNldCBgc2NoZW1hOiBzcGVjLWRyaXZlbmAgYnkgaGFuZCBhbmQgZGVsZXRlIGBvcGVuc3BlYy9zY2hlbWFzL3NwZWMtZHJpdmVuLWlkcy9gLgoKIyMgVmVyc2lvbgoKVGhlIHRlbXBsYXRlcyBmb2xsb3cgdGhlIGBzcGVjLWRyaXZlbmAgdGVtcGxhdGVzIG9mIE9wZW5TcGVjIDEuMTQuMyB3aXRoIHRoZSBJRCBwbGFjZWhvbGRlcnMgYWRkZWQuIGB0ZXN0cy9zY2hlbWEvdmFsaWRhdGUuc2hgIGNoZWNrcyB0aGVtIG9uIHVwc3RyZWFtIE9wZW5TcGVjIGluIENJLgo=",
|
|
5
|
+
"schemas/spec-driven-ids/config-fragment.yaml": "IyBzcGVjLWN0eCBmcmFnbWVudCBmb3Igb3BlbnNwZWMvY29uZmlnLnlhbWwuIHNwZWNjdHggaW5pdCAtLXNjaGVtYSBhZGRzIGVhY2ggdG9wLWxldmVsIGtleQojIHRoYXQgdGhlIHByb2plY3QgY29uZmlnIGRvZXMgbm90IGhhdmUgeWV0LiBBIGtleSB0aGF0IGV4aXN0cyBnZXRzIHRoZSBpdGVtcyBpdCBsYWNrcy4KcnVsZXM6CiAgc3BlY3M6CiAgICAtICdFdmVyeSByZXF1aXJlbWVudCBoZWFkZXIgc3RhcnRzIHdpdGggaXRzIElELCBlLmcuICIjIyMgUmVxdWlyZW1lbnQ6IFJFUS1BQkMtMDAxIE5hbWUiLiBHZXQgdGhlIElEIGZyb20gYHNwZWNjdHggaWQgbmV3IFJFUSBBQkNgLicKICAgIC0gJ0V2ZXJ5IHNjZW5hcmlvIGhlYWRlciBzdGFydHMgd2l0aCBpdHMgSUQsIGUuZy4gIiMjIyMgU2NlbmFyaW86IFNDTi1BQkMtMDAxLTEgTmFtZSIuJwogICAgLSAnQSByZXF1aXJlbWVudCBtYXkgZW5kIHdpdGggYSBsaW5lICJSZWZzOiBSRVEtQUJDLTAwMiwgQURSLTAwMSIgdG8gbGluayByZWxhdGVkIElEcy4nCiAgICAtICdSZXF1aXJlbWVudCB0ZXh0IHN0YXlzIHVuZGVyIDUwMCBjaGFyYWN0ZXJzLiBNb3ZlIGRldGFpbCBpbnRvIHNjZW5hcmlvcy4nCiAgICAtICdUaGUgdGVtcGxhdGVzIHdpdGggdGhlIElEIHBsYWNlaG9sZGVycyBhcmUgaW4gb3BlbnNwZWMvdGVtcGxhdGVzL3NwZWMtZHJpdmVuLWlkcy8uIFN0YXJ0IGEgc3BlYyBmcm9tIHNwZWMubWQgYW5kIGEgdGFzayBsaXN0IGZyb20gdGFza3MubWQgdGhlcmUuJwogIGRlc2lnbjoKICAgIC0gJ0VhY2ggZGVjaXNpb24gaXMgb25lIGJ1bGxldCB1bmRlciAiIyMgRGVjaXNpb25zIi4gQWRkIG5ldyBkZWNpc2lvbnMgYXQgdGhlIGVuZC4nCiAgdGFza3M6CiAgICAtICdFdmVyeSB0YXNrIGNpdGVzIGF0IGxlYXN0IG9uZSBSRVEgSUQgaW4gc3F1YXJlIGJyYWNrZXRzLCBlLmcuICJbUkVRLUFCQy0wMDFdIi4nCiAgICAtICdFdmVyeSB0YXNrIHN0YXRlcyBpdHMgdmVyaWZpY2F0aW9uIGFzIGEgY29tbWFuZCBpbiBhIGNvZGUgc3BhbiBhZnRlciAiVmVyaWZ5OiIuJwoKb3BlcmF0aW9uczoKICBhcHBseToKICAgIGd1aWRhbmNlOgogICAgICAtICdTdGFydCBlYWNoIHRhc2sgd2l0aCBgc3BlY2N0eCB0YXNrIHN0YXJ0IDxpZD5gIGFuZCByZWFkIHRoZSBidW5kbGUgaXQgcHJpbnRzIGJlZm9yZSB5b3UgcmVhZCBzcGVjcy4nCiAgICAgIC0gJ1RhZyBjb2RlIHdpdGggdGhlIFJFUSBJRCBpdCBpbXBsZW1lbnRzIChhIGNvbW1lbnQgYC8vIFJFUS1BQkMtMDAxYCkuIFRhZyB0ZXN0cyB3aXRoIGAvLyBDb3ZlcnM6IFJFUS1BQkMtMDAxYC4nCiAgICAgIC0gJ0Nsb3NlIGVhY2ggdGFzayBvbmx5IHdpdGggYHNwZWNjdHggdGFzayBkb25lIDxpZD5gLiBOZXZlciBlZGl0IGEgY2hlY2tib3guIE5ldmVyIHJ1biBgL29wc3g6YXBwbHlgOyBydW4gdGhlIGBzcGVjY3R4LXJ1bmAgc2tpbGwuJwogICAgICAtICdSdW4gYHNwZWNjdHggY2hlY2sgLS1zdHJpY3RgIGFmdGVyIGVhY2ggdGFzayBncm91cC4nCiAgYXJjaGl2ZToKICAgIGd1aWRhbmNlOgogICAgICAtICdSdW4gYHNwZWNjdHggY2hlY2sgLS1zdHJpY3RgIGFuZCBgb3BlbnNwZWMgdmFsaWRhdGUgLS1hbGwgLS1zdHJpY3RgLiBCb3RoIG11c3QgcGFzcyBiZWZvcmUgYXJjaGl2ZS4nCiAgICAgIC0gJ1J1biBgb3BlbnNwZWMgdmFsaWRhdGUgLS1hbGwgLS1zdHJpY3RgIGFnYWluIGFmdGVyIGFyY2hpdmUgYW5kIGJlZm9yZSBwdXNoLicK",
|
|
7
6
|
"schemas/spec-driven-ids/templates/design.md": "IyBEZXNpZ24KCiMjIENvbnRleHQKCjwhLS0gVGhlIGN1cnJlbnQgc3RhdGUgYW5kIHRoZSBjb25zdHJhaW50cyB0aGF0IHNoYXBlIHRoZSBhcHByb2FjaC4gVGhlIHByb3Bvc2FsIGhvbGRzIHRoZSBtb3RpdmF0aW9uLiAtLT4KCiMjIEdvYWxzIC8gTm9uLUdvYWxzCgoqKkdvYWxzOioqCjwhLS0gV2hhdCB0aGlzIGRlc2lnbiBnaXZlcyAtLT4KCioqTm9uLUdvYWxzOioqCjwhLS0gV2hhdCBzdGF5cyBvdXQgb2Ygc2NvcGUgLS0+CgojIyBEZWNpc2lvbnMKCjwhLS0gT25lIGJ1bGxldCBwZXIgZGVjaXNpb24uIFRoZSBJRCBvZiBhIGRlY2lzaW9uIGlzIDxjaGFuZ2UgbnVtYmVyPi9EPG4+IGJ5IHBvc2l0aW9uLiBBZGQgbmV3IG9uZXMgYXQgdGhlIGVuZC4gLS0+Ci0gPCEtLSBUaGUgZGVjaXNpb24sIHRoZSBhbHRlcm5hdGl2ZSwgYW5kIHdoeS4gLS0+IFJlZnM6IDwhLS0gUkVRLTxDQVA+LU5OTiwgQURSLU5OTiAtLT4KCiMjIFJpc2tzIC8gVHJhZGUtb2ZmcwoKPCEtLSBLbm93biByaXNrcyBhbmQgdGhlIG1pdGlnYXRpb24gZm9yIGVhY2ggLS0+Cg==",
|
|
8
7
|
"schemas/spec-driven-ids/templates/proposal.md": "IyBQcm9wb3NhbAoKIyMgV2h5Cgo8IS0tIEV4cGxhaW4gdGhlIG1vdGl2YXRpb24gZm9yIHRoaXMgY2hhbmdlLiBXaGF0IHByb2JsZW0gZG9lcyB0aGlzIHNvbHZlPyBXaHkgbm93PyAtLT4KCiMjIFdoYXQgQ2hhbmdlcwoKPCEtLSBEZXNjcmliZSB3aGF0IHdpbGwgY2hhbmdlLiBCZSBzcGVjaWZpYyBhYm91dCBuZXcgY2FwYWJpbGl0aWVzLCBtb2RpZmljYXRpb25zLCBvciByZW1vdmFscy4gLS0+CgojIyBDYXBhYmlsaXRpZXMKCiMjIyBOZXcgQ2FwYWJpbGl0aWVzCjwhLS0gQ2FwYWJpbGl0aWVzIGJlaW5nIGludHJvZHVjZWQuIE5hbWUgZWFjaCBjYXBhYmlsaXR5IGZvciBhIGNvaGVzaXZlIHN5c3RlbQogICAgIGJlaGF2aW9yIHRoYXQgY2FuIG93biByZWxhdGVkIHJlcXVpcmVtZW50cyBhcyB0aGUgc3lzdGVtIGV2b2x2ZXMuIERvIG5vdCBuYW1lCiAgICAgaW1wbGVtZW50YXRpb24gdGFza3Mgb3IgcHJvcG9zYWwgc2VjdGlvbnMuIEF2b2lkIGJyb2FkIGNhdGNoLWFsbCBuYW1lcy4gVXNlCiAgICAga2ViYWItY2FzZSBmb3IgcGF0aCBzZWdtZW50cyB5b3UgaW50cm9kdWNlIChlLmcuLCB1c2VyLWF1dGggb3IgaWRlbnRpdHkvdXNlci1hdXRoKQogICAgIHRoYXQgZm9sbG93IHRoZSBwcm9qZWN0J3MgZXhpc3Rpbmcgc3BlYyBvcmdhbml6YXRpb24uIEVhY2ggY3JlYXRlcwogICAgIHNwZWNzLzxjYXBhYmlsaXR5LXBhdGg+L3NwZWMubWQuIC0tPgotIGA8Y2FwYWJpbGl0eS1wYXRoPmA6IDxicmllZiBkZXNjcmlwdGlvbiBvZiB3aGF0IHRoaXMgY2FwYWJpbGl0eSBjb3ZlcnM+CgojIyMgTW9kaWZpZWQgQ2FwYWJpbGl0aWVzCjwhLS0gRXhpc3RpbmcgY2FwYWJpbGl0aWVzIHdob3NlIFJFUVVJUkVNRU5UUyBhcmUgY2hhbmdpbmcgKG5vdCBqdXN0IGltcGxlbWVudGF0aW9uKS4KICAgICBPbmx5IGxpc3QgaGVyZSBpZiBzcGVjLWxldmVsIGJlaGF2aW9yIGNoYW5nZXMuIEVhY2ggbmVlZHMgYSBkZWx0YSBzcGVjIGZpbGUuCiAgICAgVXNlIHRoZSBleGFjdCBleGlzdGluZyBwYXRoIHVuZGVyIG9wZW5zcGVjL3NwZWNzLy4gTGVhdmUgZW1wdHkgaWYgbm8gcmVxdWlyZW1lbnQKICAgICBjaGFuZ2VzLiBBIGNoYW5nZSB3aXRoIG5vIGNhcGFiaWxpdGllcyBhdCBhbGwgKHB1cmUgcmVmYWN0b3IsIHRvb2xpbmcsIGRvY3MpCiAgICAgbXVzdCBzZXQgYHNraXBfc3BlY3M6IHRydWVgIGluIGl0cyAub3BlbnNwZWMueWFtbCAtIG9wZW5zcGVjIHZhbGlkYXRlIHJlamVjdHMKICAgICBhIHplcm8tZGVsdGEgY2hhbmdlIHdpdGhvdXQgdGhhdCBtYXJrZXIuIERvIG5vdCBpbnZlbnQgYSByZXF1aXJlbWVudCBqdXN0IHRvCiAgICAgc2F0aXNmeSB2YWxpZGF0aW9uLiAtLT4KLSBgPGV4aXN0aW5nLWNhcGFiaWxpdHktcGF0aD5gOiA8d2hhdCByZXF1aXJlbWVudCBpcyBjaGFuZ2luZz4KCiMjIEltcGFjdAoKPCEtLSBBZmZlY3RlZCBjb2RlLCBBUElzLCBkZXBlbmRlbmNpZXMsIHN5c3RlbXMgLS0+Cg==",
|
|
9
8
|
"schemas/spec-driven-ids/templates/spec.md": "IyBTcGVjIERlbHRhCgojIyBQdXJwb3NlCjwhLS0gTmV3IGNhcGFiaWxpdHkgb25seS4gV3JpdGUgb25lIG9yIHR3byBzZW50ZW5jZXMgb2YgNTAgY2hhcmFjdGVycyBvciBtb3JlLiBEZWxldGUgdGhpcyBzZWN0aW9uIGZvciBhbiBleGlzdGluZyBjYXBhYmlsaXR5LiAtLT4KCiMjIEFEREVEIFJlcXVpcmVtZW50cwoKIyMjIFJlcXVpcmVtZW50OiBSRVEtPENBUD4tTk5OIDwhLS0gcmVxdWlyZW1lbnQgbmFtZSAtLT4KPCEtLSBSZXF1aXJlbWVudCB0ZXh0LiBSZXBsYWNlIFJFUS08Q0FQPi1OTk4gd2l0aCB0aGUgb3V0cHV0IG9mIGBzcGVjY3R4IGlkIG5ldyBSRVEgPENBUD5gLiAtLT4KClJlZnM6IDwhLS0gcmVsYXRlZCBJRHMgc3VjaCBhcyBBRFItMDAxLiBEZWxldGUgdGhpcyBsaW5lIHdoZW4gdGhlcmUgYXJlIG5vbmUuIC0tPgoKIyMjIyBTY2VuYXJpbzogU0NOLTxDQVA+LU5OTi0xIDwhLS0gc2NlbmFyaW8gbmFtZS4gR2V0IHRoZSBJRCBmcm9tIGBzcGVjY3R4IGlkIG5ldyBTQ04gPENBUD4gLS1mb3IgUkVRLTxDQVA+LU5OTmAuIC0tPgotICoqV0hFTioqIDwhLS0gY29uZGl0aW9uIC0tPgotICoqVEhFTioqIDwhLS0gZXhwZWN0ZWQgb3V0Y29tZSAtLT4K",
|
|
10
9
|
"schemas/spec-driven-ids/templates/tasks.md": "IyBUYXNrcwoKIyMgMS4gPCEtLSBUYXNrIGdyb3VwIG5hbWUgLS0+CgotIFsgXSAxLjEgPCEtLSBUYXNrIGRlc2NyaXB0aW9uIC0tPiBbUkVRLTxDQVA+LU5OTl0gVmVyaWZ5OiBgPCEtLSBjb21tYW5kIC0tPmAKLSBbIF0gMS4yIDwhLS0gVGFzayBkZXNjcmlwdGlvbiAtLT4gW1JFUS08Q0FQPi1OTk5dIFZlcmlmeTogYDwhLS0gY29tbWFuZCAtLT5gCgojIyAyLiA8IS0tIFRhc2sgZ3JvdXAgbmFtZSAtLT4KCi0gWyBdIDIuMSA8IS0tIFRhc2sgZGVzY3JpcHRpb24gLS0+IFtSRVEtPENBUD4tTk5OXSBWZXJpZnk6IGA8IS0tIGNvbW1hbmQgLS0+YAo=",
|
|
11
|
-
"skills/specctx-bootstrap/SKILL.md": "
|
|
12
|
-
"skills/specctx-done/SKILL.md": "
|
|
13
|
-
"skills/specctx-next/SKILL.md": "
|
|
10
|
+
"skills/specctx-bootstrap/SKILL.md": "LS0tCm5hbWU6IHNwZWNjdHgtYm9vdHN0cmFwCmRlc2NyaXB0aW9uOiBTdGFydCBhIHNlc3Npb24gaW4gYW4gT3BlblNwZWMgcHJvamVjdCB0aGF0IHVzZXMgc3BlYy1jdHguIFVzZSBhdCB0aGUgc3RhcnQgb2YgZXZlcnkgc2Vzc2lvbiwgYW5kIG9uICJzdGFydCIsICJib290c3RyYXAiLCBvciAid2hhdCBzaG91bGQgSSB3b3JrIG9uIi4gTG9hZHMgb25seSB0aGUgc3BlYyB0ZXh0IHRoYXQgdGhlIG5leHQgdGFzayBuZWVkcy4KLS0tCgpUaGlzIHNraWxsIGlzIG5vdyBgc3BlY2N0eC1ydW5gLiBSdW4gaXQuCg==",
|
|
11
|
+
"skills/specctx-done/SKILL.md": "LS0tCm5hbWU6IHNwZWNjdHgtZG9uZQpkZXNjcmlwdGlvbjogQ2xvc2UgYSB0YXNrIGluIGFuIE9wZW5TcGVjIHByb2plY3QgdGhhdCB1c2VzIHNwZWMtY3R4LiBSdW5zIHRoZSBWZXJpZnkgY29tbWFuZCwgd3JpdGVzIGV2aWRlbmNlLCBzZXRzIHRoZSBjaGVja2JveCwgd3JpdGVzIHRoZSBoYW5kb2ZmLCBhbmQgc2hvd3MgdGhlIG1ldHJpY3MuIFVzZSB3aGVuIHRoZSB3b3JrIG9mIGEgdGFzayBpcyBjb21wbGV0ZSwgb3Igb24gImRvbmUiLCAiY2xvc2UgdGhlIHRhc2siLCBhbmQgImVuZCB0aGUgc2Vzc2lvbiIuCi0tLQoKVGhpcyBza2lsbCBpcyBub3cgYHNwZWNjdHgtcnVuYC4gUnVuIGl0Lgo=",
|
|
12
|
+
"skills/specctx-next/SKILL.md": "LS0tCm5hbWU6IHNwZWNjdHgtbmV4dApkZXNjcmlwdGlvbjogUGljayBhbmQgc3RhcnQgdGhlIG5leHQgdGFzayBpbiB0aGUgbWlkZGxlIG9mIGEgc2Vzc2lvbiBpbiBhbiBPcGVuU3BlYyBwcm9qZWN0IHRoYXQgdXNlcyBzcGVjLWN0eC4gVXNlIGFmdGVyIHlvdSBjbG9zZSBhIHRhc2ssIG9yIG9uICJuZXh0IHRhc2siIGFuZCAid2hhdCBpcyBuZXh0Ii4KLS0tCgpUaGlzIHNraWxsIGlzIG5vdyBgc3BlY2N0eC1ydW5gLiBSdW4gaXQuCg==",
|
|
13
|
+
"skills/specctx-run/SKILL.md": "LS0tCm5hbWU6IHNwZWNjdHgtcnVuCmRlc2NyaXB0aW9uOiBSdW4gdGhlIHNwZWMtY3R4IGxvb3AgaW4gYW4gT3BlblNwZWMgcHJvamVjdC4gVXNlIG9uICJydW4iLCAiY29udGludWUiLCAibmV4dCB0YXNrIiwgImJ1aWxkIHRoaXMiLCAiY2Fycnkgb24iLCBhbmQgb24gYSBmaXJzdCBtZXNzYWdlIHRoYXQgZGVzY3JpYmVzIGEgY2hhbmdlIHRvIGJ1aWxkLiBUaGUgZW5naW5lIHBpY2tzIHRoZSBuZXh0IGFjdGlvbi4gVGhlIHNraWxsIGFjdHMgb24gaXQgdW50aWwgdGhlIGNoYW5nZSByZWFjaGVzIHRoZSBhcmNoaXZlIG9yIGEgaHVtYW4gbXVzdCBkZWNpZGUuCi0tLQoKIyBzcGVjY3R4LXJ1bgoKUnVuIGBzcGVjY3R4YCBhcyBgbnB4IHNwZWMtY3R4YCB3aGVuIGl0IGlzIG5vdCBpbnN0YWxsZWQuIGBvcGVuc3BlY2AgaXMgdGhlIGNvbW1hbmQgaW4gYFtnYXRlXSBvcGVuc3BlY2Agb2YgYHNwZWMtY3R4LnRvbWxgLiBXaGVuIHRoYXQga2V5IGlzIGFic2VudCwgYG9wZW5zcGVjYCBpcyBgbnB4IC15IEBmaXNzaW9uLWFpL29wZW5zcGVjQGxhdGVzdGAuCgojIyBUaGUgbG9vcAoKMS4gUnVuIGBzcGVjY3R4IG5leHQgLS1qc29uYC4gUmVhZCBgYWN0aW9uYCwgYGNoYW5nZWAgKGA8Yz5gKSwgYHRhc2tgIChgPHQ+YCksIGBjb21tYW5kYCwgYHJlYXNvbmAsIGFuZCBgaHVtYW5gLgoyLiBBY3Qgb24gYGFjdGlvbmAgYXMgdGhlIHRhYmxlIGJlbG93IHNheXMuCjMuIFJ1biBgc3BlY2N0eCBoYW5kb2ZmIHdyaXRlYC4KNC4gUmVwZWF0IGZyb20gc3RlcCAxIHVudGlsIGBhY3Rpb25gIGlzIGBzdG9wYCBvciBgaHVtYW5gIGlzIHRydWUuCgojIyBBY3Rpb25zCgotIGBwbGFuYDogdGFrZSB0aGUgY2hhbmdlIG5hbWUgZnJvbSB0aGUgcmVxdWVzdCBvZiB0aGUgdXNlciBpbiBrZWJhYiBjYXNlLiBUaGF0IG5hbWUgaXMgYDxjPmAuIEZvbGxvdyB0aGUgdXBzdHJlYW0gYG9wZW5zcGVjLXByb3Bvc2VgIHNraWxsIGZvciBgPGM+YC4gUmVhZCBleGlzdGluZyByZXF1aXJlbWVudHMgd2l0aCBgc3BlY2N0eCBidW5kbGUgPGlkPmAgb3IgYHNwZWNjdHggcXVlcnlgLCBuZXZlciBgb3BlbnNwZWMgc2hvd2AuIFRha2UgZXZlcnkgSUQgZnJvbSBgc3BlY2N0eCBpZCBuZXcgUkVRIDxDQVA+YCBhbmQgYHNwZWNjdHggaWQgbmV3IFNDTiA8Q0FQPiAtLWZvciA8UkVRPmAuIFdoZW4gdGhlIGNoYW5nZSBuZWVkcyBzZXZlcmFsIElEcyBvZiBvbmUga2luZCwgcnVuIGBzcGVjY3R4IGlkIG5ldyAtLWNvdW50IDxuPmAgb25jZS4gR2l2ZSBldmVyeSB0YXNrIGEgYFtSRVEtLi4uXWAgY2l0YXRpb24gYW5kIGEgYFZlcmlmeTpgIGNvZGUgc3Bhbi4gVGhlbiBydW4gYHNwZWNjdHggY2hlY2sgLS1zdHJpY3RgIGFuZCBgc3BlY2N0eCBnYXRlIC0tc3RyaWN0YC4gVGhlIGdhdGUgcnVucyB0aGUgT3BlblNwZWMgdmFsaWRhdGUgYW5kIHRoZSBzcGVjLWxlbmd0aCBjaGVjay4gRml4IGV2ZXJ5IGZpbmRpbmcuIFRoZW4gcnVuIGBzcGVjY3R4IGhhbmRvZmYgYXNrIC0tY2hhbmdlIDxjPiAicGxhbiByZWFkeSBmb3IgPGM+LiBBcHByb3ZlIHRvIHN0YXJ0IHRoZSB0YXNrcz8iYC4gVGhpcyBpcyB0aGUgcGxhbiBzdG9wLiBgbmV4dGAgcmV0dXJucyBgYXNrYCB1bnRpbCB0aGUgdXNlciByZXNvbHZlcyB0aGUgcXVlc3Rpb24uCi0gYGNpdGVgOiBydW4gYHNwZWNjdHggcXVlcnkgLS1jaGFuZ2UgPGM+IC0ta2luZCByZXFgLiBBZGQgdGhlIGBbUkVRLS4uLl1gIGNpdGF0aW9uIGFuZCBhIGBWZXJpZnk6YCBjb2RlIHNwYW4gdG8gdGhlIHRhc2sgbGluZSBuYW1lZCBpbiBgdGFza2AuIFJ1biBgc3BlY2N0eCBjaGVja2AuIENvbnRpbnVlLgotIGBzdGFydGA6IHJ1biBgc3BlY2N0eCB0YXNrIHN0YXJ0IDx0PmAuIFJlYWQgdGhlIGJ1bmRsZSBpdCBwcmludHMuIENvbnRpbnVlLgotIGB3b3JrYDogaW1wbGVtZW50IHRoZSB0YXNrLiBUYWcgY29kZSB3aXRoIGAvLyBSRVEtLi4uYC4gVGFnIHRlc3RzIHdpdGggYC8vIENvdmVyczogUkVRLS4uLmAuIFRoZW4gcnVuIGBzcGVjY3R4IHRhc2sgZG9uZSA8dD5gLgotIGBmaXhgOiByZWFkIGByZWFzb25gLiBJdCBob2xkcyB0aGUgdGFpbCBvZiB0aGUgZmFpbGVkIFZlcmlmeSBvdXRwdXQuIEZpeCB0aGUgY2F1c2UuIFJ1biBgc3BlY2N0eCB0YXNrIGRvbmUgPHQ+YCBhZ2Fpbi4KLSBgcmVwYWlyYDogcnVuIHRoZSBjb21tYW5kIGluIHRoZSBgY29tbWFuZGAgZmllbGQuIEl0IHJ1bnMgVmVyaWZ5IGFnYWluIGZvciBhIHRpY2tlZCB0YXNrIHdob3NlIGV2aWRlbmNlIGlzIGFic2VudCBvciBzdGFsZS4KLSBgZ2F0ZWA6IHJ1biBgc3BlY2N0eCBnYXRlIC0tc3RyaWN0YC4gRml4IGV2ZXJ5IGZpbmRpbmcuIFJ1biBpdCBhZ2FpbiB1bnRpbCBpdCBleGl0cyAwLgotIGBhcmNoaXZlYDogd2hlbiBgaHVtYW5gIGlzIHRydWUsIHJ1biBgc3BlY2N0eCBoYW5kb2ZmIGFzayAtLWNoYW5nZSA8Yz4gImFyY2hpdmUgPGM+PyJgIGFuZCBzdG9wLiBUaGlzIGlzIHRoZSBhcmNoaXZlIHN0b3AuIGBuZXh0YCByZXR1cm5zIGBhc2tgIHVudGlsIHRoZSB1c2VyIHJlc29sdmVzIHRoZSBxdWVzdGlvbi4gV2hlbiBgaHVtYW5gIGlzIGZhbHNlLCB0aGUgdXNlciBhcHByb3ZlZCBvciBgW2xvb3BdIGFyY2hpdmVgIGlzIGBhdXRvYC4gVGhlbiBydW4gZWFjaCBzdGVwIHVuZGVyIGBvcGVyYXRpb25zLmFyY2hpdmUuZ3VpZGFuY2VgIGluIGBvcGVuc3BlYy9jb25maWcueWFtbGAuIFRoZW4gcnVuIHRoZSBjb21tYW5kIGluIHRoZSBgY29tbWFuZGAgZmllbGQuIFRoZW4gcnVuIGBvcGVuc3BlYyB2YWxpZGF0ZSAtLWFsbCAtLXN0cmljdGAsIGBzcGVjY3R4IGNoZWNrIC0tc3RyaWN0YCwgYW5kIGBzcGVjY3R4IGhhbmRvZmYgd3JpdGVgLgotIGBhc2tgOiBwcmludCBgcmVhc29uYC4gSXQgaXMgdGhlIG9wZW4gcXVlc3Rpb24uIFN0b3AuIFRoZSB1c2VyIGFuc3dlcnMgd2l0aCBgc3BlY2N0eCBoYW5kb2ZmIHJlc29sdmUgPG4+ICI8YW5zd2VyPiJgLgotIGBzdG9wYDogcHJpbnQgYHJlYXNvbmAuIFN0b3AuCgojIyBSdWxlcwoKLSBOZXZlciB0aWNrIGEgY2hlY2tib3ggYnkgaGFuZC4gYHNwZWNjdHggdGFzayBkb25lYCBpcyB0aGUgb25seSB3YXkgYSB0YXNrIGNsb3Nlcy4KLSBOZXZlciB1c2UgYC0tbWFudWFsYCBvbiBhIHRhc2sgd2l0aCBhIFZlcmlmeSBjb21tYW5kLgotIE5ldmVyIGNhbGwgYC9vcHN4OmFwcGx5YC4gVGhpcyBza2lsbCByZXBsYWNlcyBpdC4KLSBOZXZlciByZWFkIGBvcGVuc3BlYy9zcGVjcy9gIG9yIGBvcGVuc3BlYy9jaGFuZ2VzLyovc3BlY3MvYCBkaXJlY3RseS4gVXNlIHRoZSBidW5kbGUgZnJvbSBgdGFzayBzdGFydGAgb3IgYHNwZWNjdHggYnVuZGxlIDxpZD5gLgotIFN0b3Agb24gYW55IGBhY3Rpb25gIG5vdCBpbiB0aGUgbGlzdCBhYm92ZS4gUHJpbnQgdGhlIG9iamVjdCBhbmQgYXNrIHRoZSB1c2VyLgotIEtlZXAgbm8gc3RhdGUgaW4geW91ciBjb250ZXh0LiBBIG5ldyBzZXNzaW9uIHN0YXJ0cyB0aGUgc2FtZSBsb29wIGFnYWluIHdpdGggc3RlcCAxLgo=",
|
|
14
14
|
};
|
|
@@ -4,11 +4,14 @@ import { join } from "node:path";
|
|
|
4
4
|
import { lintLine, parseLintLine } from "../lint/counts.js";
|
|
5
5
|
import { cmpStr } from "../util/pystr.js";
|
|
6
6
|
export const BASELINE_PATH = ".ctx/baseline.json";
|
|
7
|
-
export const BASELINE_RULES = ["uncovered", "scenario-untested", "done-without-evidence", "manual-evidence", "task-without-req", "task-without-verify", "missing-id"];
|
|
7
|
+
export const BASELINE_RULES = ["uncovered", "scenario-untested", "done-without-evidence", "manual-evidence", "task-without-req", "task-without-verify", "weak-verify", "missing-id"];
|
|
8
8
|
/** REQ-ADOPT-005 a missing-id finding has no ID. Its key carries the file and the header title. */
|
|
9
9
|
const key = (f) => (f.rule === "missing-id" ? `${f.rule} ${f.file} ${f.id}` : `${f.rule} ${f.id}`);
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* A warning line reads "manual-evidence <task>" or "weak-verify <task> <file>:<line>".
|
|
12
|
+
* REQ-ADOPT-005 the key keeps the rule and the task, so a weak-verify warning matches its strict finding (028/D10).
|
|
13
|
+
*/
|
|
14
|
+
const warningKey = (w) => w.split(" ").slice(0, 2).join(" ");
|
|
12
15
|
export function readBaseline(root) {
|
|
13
16
|
const p = join(root, BASELINE_PATH);
|
|
14
17
|
if (!existsSync(p))
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// REQ-ADOPT-008 the baseline against the merge base: new entries fail the gate (020/D2).
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { parseLintLine } from "../lint/counts.js";
|
|
4
|
+
import { cmpStr } from "../util/pystr.js";
|
|
5
|
+
import { BASELINE_PATH } from "./baseline.js";
|
|
6
|
+
/**
|
|
7
|
+
* Lines of `current` that are new debt against `base`.
|
|
8
|
+
* A line that the base lacks is new. A `lint <file> <rule> <count>` line with a count above
|
|
9
|
+
* the base line of the same key is new, and reads `lint <file> <rule> <count> (base <n>)`.
|
|
10
|
+
*/
|
|
11
|
+
export function newEntries(current, base) {
|
|
12
|
+
const baseSet = new Set(base);
|
|
13
|
+
const baseLint = new Map();
|
|
14
|
+
for (const b of base) {
|
|
15
|
+
const l = parseLintLine(b);
|
|
16
|
+
if (l)
|
|
17
|
+
baseLint.set(l.key, l.count);
|
|
18
|
+
}
|
|
19
|
+
const out = [];
|
|
20
|
+
for (const c of current) {
|
|
21
|
+
if (baseSet.has(c))
|
|
22
|
+
continue;
|
|
23
|
+
const l = parseLintLine(c);
|
|
24
|
+
if (l) {
|
|
25
|
+
const n = baseLint.get(l.key);
|
|
26
|
+
if (n === undefined)
|
|
27
|
+
out.push(c);
|
|
28
|
+
else if (l.count > n)
|
|
29
|
+
out.push(`${c} (base ${n})`);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
out.push(c);
|
|
33
|
+
}
|
|
34
|
+
return [...new Set(out)].sort(cmpStr);
|
|
35
|
+
}
|
|
36
|
+
function git(root, args) {
|
|
37
|
+
const r = spawnSync("git", args, { cwd: root, encoding: "utf8" });
|
|
38
|
+
return { ok: r.status === 0, out: (r.stdout ?? "").trim() };
|
|
39
|
+
}
|
|
40
|
+
/** `.ctx/baseline.json` at `git merge-base HEAD <base>`, or the reason to skip the step. */
|
|
41
|
+
export function baseAtMergeBase(root, base) {
|
|
42
|
+
const inside = git(root, ["rev-parse", "--is-inside-work-tree"]);
|
|
43
|
+
if (!inside.ok || inside.out !== "true")
|
|
44
|
+
return { skip: "no git repository" };
|
|
45
|
+
const mb = git(root, ["merge-base", "HEAD", base]);
|
|
46
|
+
if (!mb.ok || !mb.out)
|
|
47
|
+
return { skip: `no merge base with ${base}` };
|
|
48
|
+
const sha = mb.out;
|
|
49
|
+
const show = spawnSync("git", ["show", `${sha}:${BASELINE_PATH}`], { cwd: root, encoding: "utf8" });
|
|
50
|
+
if (show.status !== 0)
|
|
51
|
+
return { skip: `no ${BASELINE_PATH} at ${sha.slice(0, 7)}` };
|
|
52
|
+
let v;
|
|
53
|
+
try {
|
|
54
|
+
v = JSON.parse(show.stdout);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return { skip: `no ${BASELINE_PATH} at ${sha.slice(0, 7)}` };
|
|
58
|
+
}
|
|
59
|
+
if (!Array.isArray(v) || !v.every((x) => typeof x === "string"))
|
|
60
|
+
return { skip: `no ${BASELINE_PATH} at ${sha.slice(0, 7)}` };
|
|
61
|
+
return { lines: v, sha };
|
|
62
|
+
}
|
|
63
|
+
/** True when the subject starts with `baseline:`, the same pattern as REQ-GOV-007 for `release:`. */
|
|
64
|
+
export const isBaselineSubject = (subject) => subject.trimStart().startsWith("baseline:");
|
|
65
|
+
/**
|
|
66
|
+
* The subject of HEAD. On a merge commit, such as a pull request checkout in CI, the subject of the
|
|
67
|
+
* second parent counts too, because that is the commit the author wrote (020/D4).
|
|
68
|
+
*/
|
|
69
|
+
export function headSubjects(root) {
|
|
70
|
+
const head = git(root, ["log", "-1", "--format=%s", "HEAD"]);
|
|
71
|
+
if (!head.ok)
|
|
72
|
+
return [];
|
|
73
|
+
const out = [head.out];
|
|
74
|
+
const parents = git(root, ["rev-list", "--parents", "-n", "1", "HEAD"]);
|
|
75
|
+
if (parents.ok && parents.out.split(/\s+/).length > 2) {
|
|
76
|
+
const second = git(root, ["log", "-1", "--format=%s", "HEAD^2"]);
|
|
77
|
+
if (second.ok)
|
|
78
|
+
out.push(second.out);
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
package/dist/check/check.js
CHANGED
|
@@ -6,7 +6,9 @@ import { changeKey } from "../index/scan.js";
|
|
|
6
6
|
import { listEvidence } from "../tasks/evidence.js";
|
|
7
7
|
import { cmpTuple } from "../util/pystr.js";
|
|
8
8
|
import { evidenceFindings } from "./evidence.js";
|
|
9
|
-
|
|
9
|
+
import { isWeakVerify, parseVerify } from "../verify/parse.js";
|
|
10
|
+
// the first ID of a retired row can be a scenario. Only a REQ or ADR can be reused, but the SCN still counts as the row ID.
|
|
11
|
+
const RETIRED_RE = /\b(REQ-[A-Z]{2,6}-\d{3}|SCN-[A-Z]{2,6}-\d{3}-\d+|ADR-\d{3})\b/g;
|
|
10
12
|
const REQ_CAP = /^REQ-([A-Z]{2,6})-/;
|
|
11
13
|
export function retiredIds(root) {
|
|
12
14
|
const p = join(root, "decisions", "retired-ids.md");
|
|
@@ -49,6 +51,77 @@ export function duplicateChangeKeys(root) {
|
|
|
49
51
|
}
|
|
50
52
|
return out;
|
|
51
53
|
}
|
|
54
|
+
// REQ-ADOPT-010 the ID of a TO line is bare or in square brackets (027/D4)
|
|
55
|
+
const TO_LINE = /^-\s*TO:\s*`###\s+Requirement:\s*(?:(REQ-[A-Z]{2,6}-\d{3})|\[(REQ-[A-Z]{2,6}-\d{3})\](?=\s|`))?\s*(.*?)`\s*$/;
|
|
56
|
+
/** The headers that the TO lines of the RENAMED blocks of one delta file name: [id or null, title]. */
|
|
57
|
+
function renamedTargets(root, file) {
|
|
58
|
+
const out = [];
|
|
59
|
+
let section = "";
|
|
60
|
+
for (const line of readFileSync(join(root, file), "utf8").split(/\r?\n/)) {
|
|
61
|
+
if (line.startsWith("## ")) {
|
|
62
|
+
section = line.slice(3).trim().split(" ")[0].toUpperCase();
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (section !== "RENAMED")
|
|
66
|
+
continue;
|
|
67
|
+
const m = TO_LINE.exec(line.trim());
|
|
68
|
+
if (m)
|
|
69
|
+
out.push([m[1] ?? m[2] ?? null, m[3].trim()]);
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
/** REQ-CHK-006 the header line of an entry as its file writes it, without the line end. */
|
|
74
|
+
function headerLine(root, e) {
|
|
75
|
+
const line = readFileSync(join(root, e.file), "utf8").split(/\r?\n/)[e.line - 1];
|
|
76
|
+
return (line ?? `### Requirement: ${e.id} ${e.title}`).trimEnd();
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* REQ-CHK-001 retitle-needs-renamed: a MODIFIED or REMOVED header of an active change whose ID exists in main
|
|
80
|
+
* with another title, and whose delta file has no RENAMED TO line with that header. Upstream OpenSpec matches
|
|
81
|
+
* delta headers by text, so the archive needs the RENAMED pair (021/D1, ADR-006 item 7).
|
|
82
|
+
*/
|
|
83
|
+
export function retitleFindings(root, entries) {
|
|
84
|
+
const main = new Map();
|
|
85
|
+
for (const e of entries)
|
|
86
|
+
if (e.kind === "req" && e.change === "main" && e.status === "active")
|
|
87
|
+
main.set(e.id, e);
|
|
88
|
+
const targets = new Map();
|
|
89
|
+
const out = [];
|
|
90
|
+
for (const e of entries) {
|
|
91
|
+
if (e.kind !== "req" || (e.status !== "modified" && e.status !== "removed") || e.file.includes("/archive/"))
|
|
92
|
+
continue;
|
|
93
|
+
const m = main.get(e.id);
|
|
94
|
+
if (!m || m.title === e.title)
|
|
95
|
+
continue;
|
|
96
|
+
if (!targets.has(e.file))
|
|
97
|
+
targets.set(e.file, renamedTargets(root, e.file));
|
|
98
|
+
if (targets.get(e.file).some(([id, title]) => title === e.title && (id === null || id === e.id)))
|
|
99
|
+
continue;
|
|
100
|
+
// REQ-CHK-006 the hint copies both header lines as the files write them, bare or in brackets (031/D7)
|
|
101
|
+
const hint = `Add to the delta before the MODIFIED block:\n## RENAMED Requirements\n- FROM: \`${headerLine(root, m)}\`\n- TO: \`${headerLine(root, e)}\``;
|
|
102
|
+
out.push({ file: e.file, line: e.line, rule: "retitle-needs-renamed", id: e.id, detail: `"${m.title}" -> "${e.title}"`, hint });
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* REQ-CHK-005 weak-verify: a task of an active change whose Verify command always exits 0 (028/D2).
|
|
108
|
+
* A task with no Verify command has task-without-verify already, so it gets no second finding.
|
|
109
|
+
*/
|
|
110
|
+
export function weakVerifyFindings(entries) {
|
|
111
|
+
const out = [];
|
|
112
|
+
for (const e of entries) {
|
|
113
|
+
if (e.kind !== "task" || e.change === "main" || e.file.includes("/archive/"))
|
|
114
|
+
continue;
|
|
115
|
+
const v = parseVerify(e.title);
|
|
116
|
+
if (v.command !== null && isWeakVerify(v.command))
|
|
117
|
+
out.push({ file: e.file, line: e.line, rule: "weak-verify", id: e.id });
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
/** REQ-CHK-005 the plain-mode form of weak-verify: one warning "weak-verify <task> <file>:<line>" per task (028/D3). */
|
|
122
|
+
export function weakVerifyWarnings(entries) {
|
|
123
|
+
return weakVerifyFindings(entries).map((f) => `weak-verify ${f.id} ${f.file}:${f.line}`);
|
|
124
|
+
}
|
|
52
125
|
/** Sorted, unique findings. The scan never reads .ctx/, so evidence adds no index entry. */
|
|
53
126
|
export function check(root, cfg, data, strict) {
|
|
54
127
|
const entries = data.entries;
|
|
@@ -122,5 +195,8 @@ export function check(root, cfg, data, strict) {
|
|
|
122
195
|
const uniq = new Map();
|
|
123
196
|
for (const f of findings)
|
|
124
197
|
uniq.set(JSON.stringify(f), f);
|
|
125
|
-
|
|
198
|
+
const plain = [...uniq.values()].map(([file, line, rule, id]) => ({ file, line, rule, id }));
|
|
199
|
+
// REQ-CHK-005 weak-verify is hard only in strict mode. Plain mode prints it as a warning (028/D3).
|
|
200
|
+
const all = [...plain, ...retitleFindings(root, entries), ...(strict ? weakVerifyFindings(entries) : [])];
|
|
201
|
+
return all.sort((a, b) => cmpTuple([a.file, a.line, a.rule, a.id], [b.file, b.line, b.rule, b.id]));
|
|
126
202
|
}
|