spec-ctx 0.0.0-stage → 0.1.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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +245 -2
  3. package/action.yml +33 -0
  4. package/dist/adopt/config-write.js +59 -0
  5. package/dist/adopt/plan.js +276 -0
  6. package/dist/adopt/write.js +126 -0
  7. package/dist/assets.generated.js +14 -0
  8. package/dist/baseline/baseline.js +65 -0
  9. package/dist/check/check.js +126 -0
  10. package/dist/check/evidence.js +70 -0
  11. package/dist/check/hints.js +26 -0
  12. package/dist/cli/args.js +90 -0
  13. package/dist/cli/commands.js +275 -0
  14. package/dist/cli/errors.js +3 -0
  15. package/dist/cli/gate.js +187 -0
  16. package/dist/cli/help.js +230 -0
  17. package/dist/cli/root.js +32 -0
  18. package/dist/cli/status.js +116 -0
  19. package/dist/cli/workflow.js +274 -0
  20. package/dist/cli.js +13 -0
  21. package/dist/config.js +119 -0
  22. package/dist/graph/bundle.js +94 -0
  23. package/dist/graph/query.js +31 -0
  24. package/dist/graph/related.js +76 -0
  25. package/dist/handoff/handoff.js +148 -0
  26. package/dist/ids/ids.js +115 -0
  27. package/dist/index/build.js +57 -0
  28. package/dist/index/canonical.js +44 -0
  29. package/dist/index/scan.js +357 -0
  30. package/dist/index/types.js +2 -0
  31. package/dist/init/init.js +141 -0
  32. package/dist/lint/cli.js +253 -0
  33. package/dist/lint/py.js +152 -0
  34. package/dist/lint/selftest.js +246 -0
  35. package/dist/lint/ste.js +395 -0
  36. package/dist/lint/tells.js +116 -0
  37. package/dist/main.js +74 -0
  38. package/dist/stats/stats.js +143 -0
  39. package/dist/stats/write.js +26 -0
  40. package/dist/tasks/evidence.js +102 -0
  41. package/dist/tasks/tasks.js +105 -0
  42. package/dist/util/glob.js +43 -0
  43. package/dist/util/hash.js +14 -0
  44. package/dist/util/json.js +16 -0
  45. package/dist/util/pystr.js +67 -0
  46. package/dist/util/time.js +11 -0
  47. package/dist/verify/parse.js +58 -0
  48. package/dist/verify/run.js +42 -0
  49. package/dist/version.js +2 -0
  50. package/package.json +50 -4
  51. package/schemas/spec-driven-ids/README.md +18 -0
  52. package/schemas/spec-driven-ids/config-fragment.yaml +25 -0
  53. package/schemas/spec-driven-ids/schema.yaml +301 -0
  54. package/schemas/spec-driven-ids/templates/design.md +22 -0
  55. package/schemas/spec-driven-ids/templates/proposal.md +34 -0
  56. package/schemas/spec-driven-ids/templates/spec.md +15 -0
  57. package/schemas/spec-driven-ids/templates/tasks.md +10 -0
  58. package/skills/specctx-bootstrap/SKILL.md +27 -0
  59. package/skills/specctx-done/SKILL.md +25 -0
  60. package/skills/specctx-next/SKILL.md +23 -0
@@ -0,0 +1,126 @@
1
+ // REQ-ADOPT-002 REQ-ADOPT-003 the adopt write: every file in one operation, a check before and after, rollback.
2
+ import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { UsageExit } from "../cli/errors.js";
5
+ import { CONFIG_FILE, loadConfig } from "../config.js";
6
+ import { dumps } from "../util/json.js";
7
+ import { configText, withCaps } from "./config-write.js";
8
+ export const REPORT_FILE = "adopt-report.md";
9
+ async function findings(root) {
10
+ const { buildIndex } = await import("../index/build.js");
11
+ const { check } = await import("../check/check.js");
12
+ const cfg = loadConfig(root);
13
+ const data = buildIndex(root, cfg);
14
+ return new Set(check(root, cfg, data, false).map((f) => `${f.file}:${f.line} ${f.rule} ${f.id}`));
15
+ }
16
+ function rawLines(text) {
17
+ return text.match(/[^\n]*\n|[^\n]+$/g) ?? [];
18
+ }
19
+ /** New file contents with the edited header lines. Every other byte stays. */
20
+ function editedFiles(root, plan) {
21
+ const out = new Map();
22
+ const byFile = new Map();
23
+ for (const e of plan.edits)
24
+ byFile.set(e.file, [...(byFile.get(e.file) ?? []), e]);
25
+ for (const [file, edits] of byFile) {
26
+ const lines = rawLines(readFileSync(join(root, file)).toString("utf8"));
27
+ for (const e of edits) {
28
+ const cur = lines[e.line - 1];
29
+ if (cur === undefined)
30
+ throw new Error(`${file}:${e.line} is missing`);
31
+ const end = cur.match(/\r?\n$/)?.[0] ?? "";
32
+ if (cur.slice(0, cur.length - end.length) !== e.old)
33
+ throw new UsageExit(`${file}:${e.line} changed during adopt. Run adopt again`);
34
+ lines[e.line - 1] = e.new + end;
35
+ }
36
+ out.set(file, lines.join(""));
37
+ }
38
+ return out;
39
+ }
40
+ export function reportText(plan) {
41
+ const out = ["# Adopt report", "", "`specctx adopt` gave these headers an ID. Review the diff in the pull request, then delete this file.", ""];
42
+ out.push("## Capability codes", "", "Rename a code in `spec-ctx.toml` before the IDs spread.", "", "| Code | Path | Source |", "| --- | --- | --- |");
43
+ for (const c of plan.caps)
44
+ out.push(`| ${c.code} | ${c.path} | ${c.derived ? "derived" : "existing"} |`);
45
+ out.push("", "## Headers", "", "| File | Line | Old header | New header |", "| --- | --- | --- | --- |");
46
+ const cell = (s) => "`" + s.replace(/\|/g, "\\|").replace(/`/g, "'") + "`";
47
+ for (const e of plan.edits)
48
+ out.push(`| ${e.file} | ${e.line} | ${cell(e.old)} | ${cell(e.new)} |`);
49
+ out.push("", "## Unmatched", "");
50
+ if (!plan.unmatched.length)
51
+ out.push("None.");
52
+ else {
53
+ out.push("These delta headers match no main header and no RENAMED line. They kept their text.", "");
54
+ for (const u of plan.unmatched)
55
+ out.push(`- ${u.file}:${u.line} ${cell(u.header)}`);
56
+ }
57
+ return out.join("\n") + "\n";
58
+ }
59
+ export async function applyAdopt(root, _cfg, plan, io, json) {
60
+ const cfgPath = join(root, CONFIG_FILE);
61
+ const cfgBefore = existsSync(cfgPath) ? readFileSync(cfgPath, "utf8") : null;
62
+ const cfgAfter = withCaps(configText(root), plan.caps);
63
+ const cfgChanges = cfgBefore === null ? plan.edits.length > 0 : cfgAfter !== cfgBefore;
64
+ if (!plan.edits.length && !cfgChanges) {
65
+ if (json)
66
+ io.out(dumps({ adopted: 0, unmatched: plan.unmatched }) + "\n");
67
+ else
68
+ io.out(plan.unmatched.map((u) => `unmatched: ${u.file}:${u.line} ${u.header}\n`).join("") + "nothing to adopt\n");
69
+ return 0;
70
+ }
71
+ const next = editedFiles(root, plan);
72
+ next.set(CONFIG_FILE, cfgAfter);
73
+ // REQ-ADOPT-002 findings before the write do not block. Only new ones roll back.
74
+ const before = await findings(root);
75
+ const originals = new Map();
76
+ for (const file of [...next.keys(), REPORT_FILE]) {
77
+ const p = join(root, file);
78
+ originals.set(file, existsSync(p) ? readFileSync(p).toString("utf8") : null);
79
+ }
80
+ const restore = () => {
81
+ for (const [file, text] of originals) {
82
+ const p = join(root, file);
83
+ if (text === null)
84
+ rmSync(p, { force: true });
85
+ else
86
+ writeFileSync(p, text);
87
+ }
88
+ rmSync(join(root, ".ctx/index.json"), { force: true });
89
+ };
90
+ try {
91
+ for (const [file, text] of next)
92
+ writeFileSync(join(root, file), text);
93
+ const after = await findings(root);
94
+ const introduced = [...after].filter((f) => !before.has(f)).sort();
95
+ if (introduced.length) {
96
+ restore();
97
+ if (json)
98
+ io.out(dumps({ adopted: 0, introduced }) + "\n");
99
+ else
100
+ io.out(introduced.map((f) => f + "\n").join(""));
101
+ io.err(`specctx: adopt restored every file. The write added ${introduced.length} finding(s).\n`);
102
+ return 1;
103
+ }
104
+ // REQ-CLI-007 no header changed, so no report
105
+ if (plan.edits.length)
106
+ writeFileSync(join(root, REPORT_FILE), reportText(plan));
107
+ }
108
+ catch (e) {
109
+ restore();
110
+ throw e;
111
+ }
112
+ rmSync(join(root, ".ctx/index.json"), { force: true });
113
+ const files = new Set(plan.edits.map((e) => e.file)).size;
114
+ const report = plan.edits.length ? REPORT_FILE : null;
115
+ if (json)
116
+ io.out(dumps({ adopted: plan.edits.length, caps: plan.caps, files, report, unmatched: plan.unmatched }) + "\n");
117
+ else {
118
+ io.out(plan.unmatched.map((u) => `unmatched: ${u.file}:${u.line} ${u.header}\n`).join(""));
119
+ // REQ-CLI-007 only the codes in spec-ctx.toml changed: no header, no report
120
+ if (!report)
121
+ io.out(`wrote the codes to ${CONFIG_FILE}\nnothing to adopt\n`);
122
+ else
123
+ io.out(`adopted ${plan.edits.length} header(s) in ${files} file(s). Report: ${REPORT_FILE}\n`);
124
+ }
125
+ return 0;
126
+ }
@@ -0,0 +1,14 @@
1
+ // Generated by scripts/gen-assets.mjs from schemas/spec-driven-ids/ and skills/. Do not edit.
2
+ // REQ-SCH-001 REQ-SKL-002
3
+ export const ASSETS = {
4
+ "schemas/spec-driven-ids/README.md": "IyBzcGVjLWRyaXZlbi1pZHMKCkEgZm9yayBvZiB0aGUgT3BlblNwZWMgYHNwZWMtZHJpdmVuYCBzY2hlbWEuIEl0IGFkZHMgc3RhYmxlIElEcyBmcm9tIHNwZWMtY3R4IHRvIGV2ZXJ5IGFydGlmYWN0LgoKLSBUaGUgc3BlYyB0ZW1wbGF0ZSBzdGFydHMgZWFjaCByZXF1aXJlbWVudCB3aXRoIGBSRVEtPENBUD4tTk5OYCBhbmQgZWFjaCBzY2VuYXJpbyB3aXRoIGBTQ04tPENBUD4tTk5OLTxuPmAuCi0gVGhlIGluc3RydWN0aW9ucyB0ZWxsIHRoZSBhZ2VudCB0byBnZXQgSURzIGZyb20gYHNwZWNjdHggaWQgbmV3YCBhbmQgdG8gbGluayBJRHMgd2l0aCBgUmVmczpgIGxpbmVzLgotIFRoZSB0YXNrcyB0ZW1wbGF0ZSBjaXRlcyBSRVEgSURzIGFuZCBwdXRzIHRoZSBgVmVyaWZ5OmAgY29tbWFuZCBpbiBhIGNvZGUgc3Bhbiwgc28gYHNwZWNjdHggdGFzayBkb25lYCBjYW4gcnVuIGl0LgotIGBjb25maWctZnJhZ21lbnQueWFtbGAgaG9sZHMgdGhlIGBydWxlc2AgYW5kIGBvcGVyYXRpb25zYCB0aGF0IGBzcGVjY3R4IGluaXQgLS1zY2hlbWFgIGFkZHMgdG8gYG9wZW5zcGVjL2NvbmZpZy55YW1sYC4KCkZvcmtlZCBmcm9tIE9wZW5TcGVjIDEuMTQuMyB3aXRoIGBvcGVuc3BlYyBzY2hlbWEgZm9yayBzcGVjLWRyaXZlbiBzcGVjLWRyaXZlbi1pZHNgLiBDSSB2YWxpZGF0ZXMgaXQgYWdhaW5zdCB0aGF0IHZlcnNpb24uCgojIyBJbnN0YWxsCgpgYGBiYXNoCm5weCBzcGVjLWN0eCBpbml0IC0tc2NoZW1hCmBgYAoKVGhlIGNvbW1hbmQgY29waWVzIHRoaXMgZm9sZGVyIHRvIGBvcGVuc3BlYy9zY2hlbWFzL3NwZWMtZHJpdmVuLWlkcy9gIGFuZCBzZXRzIGBzY2hlbWE6IHNwZWMtZHJpdmVuLWlkc2AgaW4gYG9wZW5zcGVjL2NvbmZpZy55YW1sYC4K",
5
+ "schemas/spec-driven-ids/config-fragment.yaml": "IyBzcGVjLWN0eCBmcmFnbWVudCBmb3Igb3BlbnNwZWMvY29uZmlnLnlhbWwuIHNwZWNjdHggaW5pdCAtLXNjaGVtYSBhZGRzIGVhY2ggdG9wLWxldmVsIGtleQojIHRoYXQgdGhlIHByb2plY3QgY29uZmlnIGRvZXMgbm90IGhhdmUgeWV0LiBJdCBuZXZlciBjaGFuZ2VzIGEga2V5IHRoYXQgZXhpc3RzLgpydWxlczoKICBzcGVjczoKICAgIC0gRXZlcnkgcmVxdWlyZW1lbnQgaGVhZGVyIHN0YXJ0cyB3aXRoIGl0cyBJRCwgZS5nLiAiIyMjIFJlcXVpcmVtZW50OiBSRVEtQUJDLTAwMSBOYW1lIi4gR2V0IHRoZSBJRCBmcm9tIGBzcGVjY3R4IGlkIG5ldyBSRVEgQUJDYC4KICAgIC0gRXZlcnkgc2NlbmFyaW8gaGVhZGVyIHN0YXJ0cyB3aXRoIGl0cyBJRCwgZS5nLiAiIyMjIyBTY2VuYXJpbzogU0NOLUFCQy0wMDEtMSBOYW1lIi4KICAgIC0gQSByZXF1aXJlbWVudCBtYXkgZW5kIHdpdGggYSBsaW5lICJSZWZzOiBSRVEtQUJDLTAwMiwgQURSLTAwMSIgdG8gbGluayByZWxhdGVkIElEcy4KICAgIC0gUmVxdWlyZW1lbnQgdGV4dCBzdGF5cyB1bmRlciA1MDAgY2hhcmFjdGVycy4gTW92ZSBkZXRhaWwgaW50byBzY2VuYXJpb3MuCiAgZGVzaWduOgogICAgLSBFYWNoIGRlY2lzaW9uIGlzIG9uZSBidWxsZXQgdW5kZXIgIiMjIERlY2lzaW9ucyIuIEFkZCBuZXcgZGVjaXNpb25zIGF0IHRoZSBlbmQuCiAgdGFza3M6CiAgICAtIEV2ZXJ5IHRhc2sgY2l0ZXMgYXQgbGVhc3Qgb25lIFJFUSBJRCBpbiBzcXVhcmUgYnJhY2tldHMsIGUuZy4gIltSRVEtQUJDLTAwMV0iLgogICAgLSBFdmVyeSB0YXNrIHN0YXRlcyBpdHMgdmVyaWZpY2F0aW9uIGFzIGEgY29tbWFuZCBpbiBhIGNvZGUgc3BhbiBhZnRlciAiVmVyaWZ5OiIuCgpvcGVyYXRpb25zOgogIGFwcGx5OgogICAgZ3VpZGFuY2U6CiAgICAgIC0gU3RhcnQgZWFjaCB0YXNrIHdpdGggYHNwZWNjdHggdGFzayBzdGFydCA8aWQ+YCBhbmQgcmVhZCB0aGUgYnVuZGxlIGl0IHByaW50cyBiZWZvcmUgeW91IHJlYWQgc3BlY3MuCiAgICAgIC0gVGFnIGNvZGUgd2l0aCB0aGUgUkVRIElEIGl0IGltcGxlbWVudHMgKGEgY29tbWVudCBgLy8gUkVRLUFCQy0wMDFgKS4gVGFnIHRlc3RzIHdpdGggYC8vIENvdmVyczogUkVRLUFCQy0wMDFgLgogICAgICAtIENsb3NlIGVhY2ggdGFzayB3aXRoIGBzcGVjY3R4IHRhc2sgZG9uZSA8aWQ+YC4gTmV2ZXIgdGljayBhIGNoZWNrYm94IGJ5IGhhbmQuCiAgICAgIC0gUnVuIGBzcGVjY3R4IGNoZWNrIC0tc3RyaWN0YCBhZnRlciBlYWNoIHRhc2sgZ3JvdXAuCiAgYXJjaGl2ZToKICAgIGd1aWRhbmNlOgogICAgICAtIFJ1biBgc3BlY2N0eCBjaGVjayAtLXN0cmljdGAgYW5kIGBvcGVuc3BlYyB2YWxpZGF0ZSAtLWFsbCAtLXN0cmljdGAuIEJvdGggbXVzdCBwYXNzIGJlZm9yZSBhcmNoaXZlLgogICAgICAtIFJ1biBgb3BlbnNwZWMgdmFsaWRhdGUgLS1hbGwgLS1zdHJpY3RgIGFnYWluIGFmdGVyIGFyY2hpdmUgYW5kIGJlZm9yZSBwdXNoLgo=",
6
+ "schemas/spec-driven-ids/schema.yaml": "bmFtZTogc3BlYy1kcml2ZW4taWRzCnZlcnNpb246IDEKZGVzY3JpcHRpb246IE9wZW5TcGVjIHNwZWMtZHJpdmVuIHdvcmtmbG93IHdpdGggc3RhYmxlIElEcyBmcm9tIHNwZWMtY3R4IC0gcHJvcG9zYWwg4oaSIHNwZWNzIOKGkiBkZXNpZ24g4oaSIHRhc2tzCmFydGlmYWN0czoKICAtIGlkOiBwcm9wb3NhbAogICAgZ2VuZXJhdGVzOiBwcm9wb3NhbC5tZAogICAgZGVzY3JpcHRpb246IEluaXRpYWwgcHJvcG9zYWwgZG9jdW1lbnQgb3V0bGluaW5nIHRoZSBjaGFuZ2UKICAgIHRlbXBsYXRlOiBwcm9wb3NhbC5tZAogICAgaW5zdHJ1Y3Rpb246IHwKICAgICAgQ3JlYXRlIHRoZSBwcm9wb3NhbCBkb2N1bWVudCB0aGF0IGVzdGFibGlzaGVzIFdIWSB0aGlzIGNoYW5nZSBpcyBuZWVkZWQuCgogICAgICBTZWN0aW9uczoKICAgICAgLSAqKldoeSoqOiAxLTIgc2VudGVuY2VzIG9uIHRoZSBwcm9ibGVtIG9yIG9wcG9ydHVuaXR5LiBXaGF0IHByb2JsZW0gZG9lcyB0aGlzIHNvbHZlPyBXaHkgbm93PwogICAgICAtICoqV2hhdCBDaGFuZ2VzKio6IEJ1bGxldCBsaXN0IG9mIGNoYW5nZXMuIEJlIHNwZWNpZmljIGFib3V0IG5ldyBjYXBhYmlsaXRpZXMsIG1vZGlmaWNhdGlvbnMsIG9yIHJlbW92YWxzLiBNYXJrIGJyZWFraW5nIGNoYW5nZXMgd2l0aCAqKkJSRUFLSU5HKiouCiAgICAgIC0gKipDYXBhYmlsaXRpZXMqKjogSWRlbnRpZnkgd2hpY2ggc3BlY3Mgd2lsbCBiZSBjcmVhdGVkIG9yIG1vZGlmaWVkOgogICAgICAgIC0gKipOZXcgQ2FwYWJpbGl0aWVzKio6IExpc3QgY2FwYWJpbGl0aWVzIGJlaW5nIGludHJvZHVjZWQuIEVhY2ggYmVjb21lcyBhIG5ldyBgc3BlY3MvPGNhcGFiaWxpdHktcGF0aD4vc3BlYy5tZGAuIE5hbWUgZWFjaCBjYXBhYmlsaXR5IGZvciBhIGR1cmFibGUgc3lzdGVtIGJlaGF2aW9yIChmb3IgZXhhbXBsZSwgYHVzZXItYXV0aGApLCBub3QgdGhlIHdvcmsgaW4gdGhpcyBjaGFuZ2UgKGZvciBleGFtcGxlLCBgYWRkLWxvZ2luLWVuZHBvaW50YCkuIENob29zZSBhIGNvaGVzaXZlIGJvdW5kYXJ5IHRoYXQgY2FuIG93biByZWxhdGVkIHJlcXVpcmVtZW50cyBhcyB0aGUgc3lzdGVtIGV2b2x2ZXM7IGF2b2lkIGJyb2FkIGNhdGNoLWFsbCBjYXBhYmlsaXRpZXMuIFVzZSBrZWJhYi1jYXNlIGZvciBwYXRoIHNlZ21lbnRzIHlvdSBpbnRyb2R1Y2UgKGUuZy4sIGB1c2VyLWF1dGhgIG9yIGBpZGVudGl0eS91c2VyLWF1dGhgKSBhbmQgZm9sbG93IHRoZSBwcm9qZWN0J3MgZXhpc3Rpbmcgc3BlYyBvcmdhbml6YXRpb24uCiAgICAgICAgLSAqKk1vZGlmaWVkIENhcGFiaWxpdGllcyoqOiBMaXN0IGV4aXN0aW5nIGNhcGFiaWxpdGllcyB3aG9zZSBSRVFVSVJFTUVOVFMgYXJlIGNoYW5naW5nLiBPbmx5IGluY2x1ZGUgaWYgc3BlYy1sZXZlbCBiZWhhdmlvciBjaGFuZ2VzIChub3QganVzdCBpbXBsZW1lbnRhdGlvbiBkZXRhaWxzKS4gRWFjaCBuZWVkcyBhIGRlbHRhIHNwZWMgZmlsZS4gVXNlIHRoZSBleGFjdCBleGlzdGluZyBwYXRoIHVuZGVyIGBvcGVuc3BlYy9zcGVjcy9gLiBMZWF2ZSBlbXB0eSBpZiBubyByZXF1aXJlbWVudCBjaGFuZ2VzLgogICAgICAtICoqSW1wYWN0Kio6IEFmZmVjdGVkIGNvZGUsIEFQSXMsIGRlcGVuZGVuY2llcywgb3Igc3lzdGVtcy4KCiAgICAgIElNUE9SVEFOVDogVGhlIENhcGFiaWxpdGllcyBzZWN0aW9uIGlzIGNyaXRpY2FsLiBJdCBjcmVhdGVzIHRoZSBjb250cmFjdCBiZXR3ZWVuCiAgICAgIHByb3Bvc2FsIGFuZCBzcGVjcyBwaGFzZXMuIFJlc2VhcmNoIGV4aXN0aW5nIHNwZWNzIGJlZm9yZSBmaWxsaW5nIHRoaXMgaW46CiAgICAgIHJ1biBgb3BlbnNwZWMgbGlzdCAtLXNwZWNzYCBmb3IgdGhlIHByb2plY3QncyBjYXBhYmlsaXR5IGludmVudG9yeSwgdGhlbgogICAgICBgb3BlbnNwZWMgc2hvdyAiPHNwZWMtaWQ+IiAtLXR5cGUgc3BlYyAtLWpzb24gLS1uby1zY2VuYXJpb3NgIGZvciBhbnkgdGhhdAogICAgICBsb29rIHJlbGF0ZWQgLSB0aGF0IHJldHVybnMgYSBjYXBhYmlsaXR5J3MgcHVycG9zZSBhbmQgcmVxdWlyZW1lbnQgdGV4dHMKICAgICAgd2l0aG91dCBwdWxsaW5nIHdob2xlIHNwZWMgZmlsZXMgaW50byBjb250ZXh0LiBBcHBlbmQgYC0tc3RvcmUgIjxpZD4iYCB0bwogICAgICBib3RoIGNvbW1hbmRzIG9ubHkgZm9yIGEgcmVnaXN0ZXJlZCBzdGFuZGFsb25lIHN0b3JlLCBhbmQga2VlcCBgLS10eXBlCiAgICAgIHNwZWNgOiBhIGNoYW5nZSBhbmQgYSBzcGVjIHNoYXJpbmcgYSBuYW1lIGlzIG90aGVyd2lzZSBhbiBhbWJpZ3VvdXMtaXRlbQogICAgICBlcnJvci4gYG9wZW5zcGVjIGxpc3RgIHdpdGhvdXQgYC0tc3BlY3NgIGxpc3RzIGluLWZsaWdodCBjaGFuZ2VzLCBub3QKICAgICAgc3BlY3MgLSBpdCBuZXZlciBzaG93cyB3aGF0IHRoZSBwcm9qZWN0IGFscmVhZHkgY292ZXJzLiBSZXVzZSBhbiBleGlzdGluZwogICAgICBjYXBhYmlsaXR5J3MgZXhhY3QgcGF0aCBpbnN0ZWFkIG9mIGludHJvZHVjaW5nIGEgbmVhci1kdXBsaWNhdGUgbmFtZS4KICAgICAgVGhlIGZpbHRlcmVkIHJlYWQgaXMgb25seSBhbiBvdmVydmlldy4gQmVmb3JlIGRlY2lkaW5nIHdoYXQgaXMgYWxyZWFkeQogICAgICBjb3ZlcmVkIG9yIHdoYXQgc2hvdWxkIGNoYW5nZSwgcmVhZCBlYWNoIHJlbGV2YW50IHNwZWMgaW4gZnVsbCwgaW5jbHVkaW5nCiAgICAgIHNjZW5hcmlvcywgd2l0aCBgb3BlbnNwZWMgc2hvdyAiPHNwZWMtaWQ+IiAtLXR5cGUgc3BlY2AgKHNhbWUgYC0tc3RvcmVgIHJ1bGUpLgogICAgICBFYWNoIGNhcGFiaWxpdHkgbGlzdGVkIGhlcmUgd2lsbCBuZWVkIGEgY29ycmVzcG9uZGluZyBzcGVjIGZpbGUuCgogICAgICBFdmVyeSBjaGFuZ2UgbXVzdCBlaXRoZXIgZGVjbGFyZSBhdCBsZWFzdCBvbmUgY2FwYWJpbGl0eSAobmV3IG9yCiAgICAgIG1vZGlmaWVkKSBvciBleHBsaWNpdGx5IG9wdCBvdXQgb2Ygc3BlY3M6IGBvcGVuc3BlYyB2YWxpZGF0ZWAgcmVqZWN0cyBhCiAgICAgIGNoYW5nZSB3aXRoIHplcm8gZGVsdGFzIHVubGVzcyB0aGUgY2hhbmdlJ3MgYC5vcGVuc3BlYy55YW1sYCBzZXRzCiAgICAgIGBza2lwX3NwZWNzOiB0cnVlYC4gVXNlIGBza2lwX3NwZWNzOiB0cnVlYCBvbmx5IHdoZW4gbm8gc3BlYy1sZXZlbAogICAgICBiZWhhdmlvciBjaGFuZ2VzIChwdXJlIHJlZmFjdG9yLCB0b29saW5nLCBkb2NzKSAtIHNwZWNzIGRlc2NyaWJlCiAgICAgIGJlaGF2aW9yLCBzbyBpZiBiZWhhdmlvciBkb2VzIG5vdCBjaGFuZ2UsIG5vIHNwZWMgc2hvdWxkIGNoYW5nZSBlaXRoZXIuCiAgICAgIERvIG5vdCBpbnZlbnQgYSByZXF1aXJlbWVudCBqdXN0IHRvIHNhdGlzZnkgdmFsaWRhdGlvbi4KCiAgICAgIEtlZXAgaXQgY29uY2lzZSAoMS0yIHBhZ2VzKS4gRm9jdXMgb24gdGhlICJ3aHkiIG5vdCB0aGUgImhvdyIgLQogICAgICBpbXBsZW1lbnRhdGlvbiBkZXRhaWxzIGJlbG9uZyBpbiBkZXNpZ24ubWQuCgogICAgICBUaGlzIGlzIHRoZSBmb3VuZGF0aW9uIC0gc3BlY3MsIGRlc2lnbiwgYW5kIHRhc2tzIGFsbCBidWlsZCBvbiB0aGlzLgogICAgcmVxdWlyZXM6IFtdCgogIC0gaWQ6IHNwZWNzCiAgICBnZW5lcmF0ZXM6ICJzcGVjcy8qKi8qLm1kIgogICAgZGVzY3JpcHRpb246IERldGFpbGVkIHNwZWNpZmljYXRpb25zIGZvciB0aGUgY2hhbmdlCiAgICB0ZW1wbGF0ZTogc3BlYy5tZAogICAgaW5zdHJ1Y3Rpb246IHwKICAgICAgQ3JlYXRlIHNwZWNpZmljYXRpb24gZmlsZXMgdGhhdCBkZWZpbmUgV0hBVCB0aGUgc3lzdGVtIHNob3VsZCBkby4KCiAgICAgIEEgc3BlYyBpcyBhIGJlaGF2aW9yIGNvbnRyYWN0LCBub3QgYW4gaW1wbGVtZW50YXRpb24gcGxhbi4KCiAgICAgIEdvb2Qgc3BlYyBjb250ZW50OgogICAgICAtIE9ic2VydmFibGUgYmVoYXZpb3IgdXNlcnMgb3IgZG93bnN0cmVhbSBzeXN0ZW1zIHJlbHkgb24KICAgICAgLSBJbnB1dHMsIG91dHB1dHMsIGFuZCBlcnJvciBjb25kaXRpb25zCiAgICAgIC0gRXh0ZXJuYWwgY29uc3RyYWludHMgKHNlY3VyaXR5LCBwcml2YWN5LCByZWxpYWJpbGl0eSwgY29tcGF0aWJpbGl0eSkKICAgICAgLSBTY2VuYXJpb3MgdGhhdCBjYW4gYmUgdGVzdGVkIG9yIGV4cGxpY2l0bHkgdmFsaWRhdGVkCgogICAgICBBdm9pZCBpbiBzcGVjczoKICAgICAgLSBJbnRlcm5hbCBjbGFzcy9mdW5jdGlvbiBuYW1lcwogICAgICAtIExpYnJhcnkgb3IgZnJhbWV3b3JrIGNob2ljZXMKICAgICAgLSBTdGVwLWJ5LXN0ZXAgaW1wbGVtZW50YXRpb24gZGV0YWlscwogICAgICAtIERldGFpbGVkIGV4ZWN1dGlvbiBwbGFucyAodGhvc2UgYmVsb25nIGluIGRlc2lnbi5tZCBvciB0YXNrcy5tZCkKCiAgICAgIFF1aWNrIHRlc3Q6IGlmIHRoZSBpbXBsZW1lbnRhdGlvbiBjYW4gY2hhbmdlIHdpdGhvdXQgY2hhbmdpbmcgZXh0ZXJuYWxseQogICAgICB2aXNpYmxlIGJlaGF2aW9yLCBpdCBsaWtlbHkgZG9lcyBub3QgYmVsb25nIGluIHRoZSBzcGVjLgoKICAgICAgQ3JlYXRlIG9uZSBzcGVjIGZpbGUgcGVyIGNhcGFiaWxpdHkgbGlzdGVkIGluIHRoZSBwcm9wb3NhbCdzIENhcGFiaWxpdGllcyBzZWN0aW9uLgogICAgICBgPGNhcGFiaWxpdHktcGF0aD5gIGlzIHRoZSBzcGVjIGRpcmVjdG9yeSByZWxhdGl2ZSB0byBgc3BlY3MvYCAoZm9yIGV4YW1wbGUsCiAgICAgIGB1c2VyLWF1dGhgIG9yIGBpZGVudGl0eS91c2VyLWF1dGhgKS4gUHJlc2VydmUgdGhlIGZ1bGwgcGF0aDoKICAgICAgLSBOZXcgY2FwYWJpbGl0aWVzOiB1c2UgdGhlIGV4YWN0IHBhdGggZnJvbSB0aGUgcHJvcG9zYWwgYXQgYHNwZWNzLzxjYXBhYmlsaXR5LXBhdGg+L3NwZWMubWRgLiBBbnkgcGF0aCBzZWdtZW50IG5ld2x5IGludHJvZHVjZWQgaW4gdGhlIHByb3Bvc2FsIG11c3QgYmUga2ViYWItY2FzZS4gRm9sbG93IHRoZSBwcm9qZWN0J3MgZXhpc3Rpbmcgb3JnYW5pemF0aW9uOyBkbyBub3QgYWRkIGEgbmV3IGRvbWFpbiBsZXZlbCB3aGVuIHRoZSBwcm9qZWN0IHVzZXMgYSBmbGF0IGxheW91dC4KICAgICAgLSBNb2RpZmllZCBjYXBhYmlsaXRpZXM6IHVzZSB0aGUgZXhhY3QgZXhpc3RpbmcgcGF0aCBmcm9tIGBvcGVuc3BlYy9zcGVjcy88Y2FwYWJpbGl0eS1wYXRoPi9gIHdoZW4gY3JlYXRpbmcgdGhlIGRlbHRhIGF0IGBzcGVjcy88Y2FwYWJpbGl0eS1wYXRoPi9zcGVjLm1kYC4gUnVuIGBvcGVuc3BlYyBsaXN0IC0tc3BlY3NgIHRvIGNvbmZpcm0gdGhhdCBwYXRoIGJlZm9yZSB3cml0aW5nIHRoZSBkZWx0YSwgYXBwZW5kaW5nIGAtLXN0b3JlICI8aWQ+ImAgb25seSBmb3IgYSByZWdpc3RlcmVkIHN0YW5kYWxvbmUgc3RvcmUgLSBhIG1pc3R5cGVkIG9yIGludmVudGVkIHBhdGggdGFyZ2V0cyBhIGNhcGFiaWxpdHkgdGhhdCBkb2VzIG5vdCBleGlzdCByYXRoZXIgdGhhbiB0aGUgb25lIHlvdSBtZWFudC4gRG8gbm90IG1vdmUgb3IgcmVuYW1lIHRoZSBjYXBhYmlsaXR5LgoKICAgICAgV3JpdGUgdGhlIHNwZWMgZmlsZXMgb25lIGF0IGEgdGltZSwgc28gdGhlIHVzZXIgc2VlcyBwcm9ncmVzcyBhbmQgYW4KICAgICAgaW50ZXJydXB0ZWQgcnVuIGtlZXBzIHRoZSBmaWxlcyBpdCBhbHJlYWR5IHdyb3RlOgogICAgICAtIEJlZm9yZSB3cml0aW5nIGFueXRoaW5nLCBjb21wYXJlIHRoZSBjYXBhYmlsaXRpZXMgdGhlIHByb3Bvc2FsIGxpc3RzCiAgICAgICAgd2l0aCB0aGUgYHNwZWNzLzxjYXBhYmlsaXR5LXBhdGg+L3NwZWMubWRgIGZpbGVzIHRoYXQgYWxyZWFkeSBleGlzdCwKICAgICAgICBhbmQgc3RhcnQgd2l0aCB0aGUgZmlyc3QgY2FwYWJpbGl0eSB0aGF0IGhhcyBubyBmaWxlLiBEbyBub3QgcmV3cml0ZQogICAgICAgIGFuIGV4aXN0aW5nIGZpbGUgdW5sZXNzIHRoZSB1c2VyIGFza3MgeW91IHRvLgogICAgICAtIEJlZm9yZSBlYWNoIGZpbGUsIHNheSBpbiBvbmUgbGluZSB3aGljaCBjYXBhYmlsaXR5IHlvdSBhcmUgd3JpdGluZy4KICAgICAgLSBXcml0ZSBlYWNoIGZpbGUgYXMgc29vbiBhcyB5b3UgaGF2ZSBkcmFmdGVkIGl0LiBEbyBub3QgcGxhbiBldmVyeSBmaWxlCiAgICAgICAgYmVmb3JlIHdyaXRpbmcgdGhlIGZpcnN0LgogICAgICAtIFN0b3Agd2hlbiBldmVyeSBjYXBhYmlsaXR5IHRoZSBwcm9wb3NhbCBsaXN0cyBoYXMgaXRzIGZpbGUuCgogICAgICBUaGVyZSBtdXN0IGJlIGF0IGxlYXN0IG9uZSBzcGVjIGZpbGUgdW5sZXNzIHRoZSBjaGFuZ2UncyBgLm9wZW5zcGVjLnlhbWxgCiAgICAgIHNldHMgYHNraXBfc3BlY3M6IHRydWVgIChubyBzcGVjLWxldmVsIGJlaGF2aW9yIGNoYW5nZSkgLSBgb3BlbnNwZWMgdmFsaWRhdGVgCiAgICAgIHJlamVjdHMgYSB6ZXJvLWRlbHRhIGNoYW5nZSB3aXRob3V0IHRoYXQgbWFya2VyLiBJZiB0aGUgcHJvcG9zYWwgbGlzdHMgbm8KICAgICAgY2FwYWJpbGl0aWVzIGFuZCBgc2tpcF9zcGVjc2AgaXMgbm90IHNldCwgcmV2aXNpdCB0aGUgcHJvcG9zYWwgZmlyc3QuCgogICAgICBEZWx0YSBvcGVyYXRpb25zICh1c2UgIyMgaGVhZGVycyk6CiAgICAgIC0gKipBRERFRCBSZXF1aXJlbWVudHMqKjogTmV3IGNhcGFiaWxpdGllcwogICAgICAtICoqTU9ESUZJRUQgUmVxdWlyZW1lbnRzKio6IENoYW5nZWQgYmVoYXZpb3IgLSBNVVNUIGluY2x1ZGUgZnVsbCB1cGRhdGVkIGNvbnRlbnQKICAgICAgLSAqKlJFTU9WRUQgUmVxdWlyZW1lbnRzKio6IERlcHJlY2F0ZWQgZmVhdHVyZXMgLSBNVVNUIGluY2x1ZGUgKipSZWFzb24qKiBhbmQgKipNaWdyYXRpb24qKgogICAgICAtICoqUkVOQU1FRCBSZXF1aXJlbWVudHMqKjogTmFtZSBjaGFuZ2VzIG9ubHkgLSB1c2UgRlJPTTovVE86IGZvcm1hdAoKICAgICAgRm9ybWF0IHJlcXVpcmVtZW50czoKICAgICAgLSBFYWNoIHJlcXVpcmVtZW50OiBgIyMjIFJlcXVpcmVtZW50OiA8bmFtZT5gIGZvbGxvd2VkIGJ5IGRlc2NyaXB0aW9uCiAgICAgIC0gVXNlIFNIQUxML01VU1QgZm9yIG5vcm1hdGl2ZSByZXF1aXJlbWVudHMgKGF2b2lkIHNob3VsZC9tYXkpCiAgICAgIC0gRWFjaCBzY2VuYXJpbzogYCMjIyMgU2NlbmFyaW86IDxuYW1lPmAgd2l0aCBXSEVOL1RIRU4gZm9ybWF0CiAgICAgIC0gKipDUklUSUNBTCoqOiBTY2VuYXJpb3MgTVVTVCB1c2UgZXhhY3RseSA0IGhhc2h0YWdzIChgIyMjI2ApLiBVc2luZyAzIGhhc2h0YWdzIG9yIGJ1bGxldHMgd2lsbCBmYWlsIHNpbGVudGx5LgogICAgICAtIEV2ZXJ5IHJlcXVpcmVtZW50IE1VU1QgaGF2ZSBhdCBsZWFzdCBvbmUgc2NlbmFyaW8uCiAgICAgIC0gS2VlcCBlYWNoIHJlcXVpcmVtZW50J3MgZGVzY3JpcHRpb24gKHRoZSB0ZXh0IGJldHdlZW4gYCMjIyBSZXF1aXJlbWVudDpgIGFuZCBpdHMgZmlyc3Qgc2NlbmFyaW8pIHRvIDUwMCBjaGFyYWN0ZXJzIG9yIGZld2VyLiBgb3BlbnNwZWMgdmFsaWRhdGVgIGZsYWdzIGxvbmdlciBkZXNjcmlwdGlvbnMgaW4gQURERUQgcmVxdWlyZW1lbnRzIGFuZCBpbiB0aGUgbWFpbiBzcGVjLiBUaGlzIGlzIGEgd2FybmluZzogbm9ybWFsIHZhbGlkYXRpb24gc3RpbGwgcGFzc2VzLCBidXQgYG9wZW5zcGVjIHZhbGlkYXRlIC0tc3RyaWN0YCBmYWlscyBvbiBpdC4gV2hlbiB3cml0aW5nIGEgbmV3IHJlcXVpcmVtZW50LCBzdGF0ZSBvbmUgYmVoYXZpb3IgcGVyIHJlcXVpcmVtZW50OiBtb3ZlIGV4YW1wbGVzIGFuZCBlZGdlIGNhc2VzIGludG8gc2NlbmFyaW9zLCBhbmQgc3BsaXQgYSByZXF1aXJlbWVudCB0aGF0IGNvdmVycyBzZXZlcmFsIGJlaGF2aW9ycyBpbnRvIHNlcGFyYXRlIGAjIyMgUmVxdWlyZW1lbnQ6YCBibG9ja3MsIGVhY2ggd2l0aCBpdHMgb3duIHNjZW5hcmlvcy4gVW5kZXIgTU9ESUZJRUQsIGtlZXAgdGhlIGV4aXN0aW5nIHJlcXVpcmVtZW50IGJsb2NrIHdob2xlOyBuZXZlciBzcGxpdCwgdHJpbSBvciByZXdyaXRlIGV4aXN0aW5nIHRleHQganVzdCB0byBtZWV0IHRoZSBsZW5ndGguIFNwbGl0IGFuIGV4aXN0aW5nIGxvbmcgcmVxdWlyZW1lbnQgb25seSB3aGVuIHRoZSB1c2VyIGFza3MgZm9yIGl0LCBpbiBhIGNoYW5nZSBtYWRlIGZvciB0aGF0IHB1cnBvc2U6IHVuZGVyIE1PRElGSUVELCBrZWVwIGl0cyBoZWFkZXIgYW5kIGV2ZXJ5IHNjZW5hcmlvIGFuZCBjdXQgaXRzIGRlc2NyaXB0aW9uIGRvd24gdG8gb25lIGJlaGF2aW9yIHdpdGhvdXQgY2hhbmdpbmcgaXRzIG1lYW5pbmcsIHRoZW4gYWRkIGVhY2ggYmVoYXZpb3IgeW91IHJlbW92ZWQgYXMgaXRzIG93biBBRERFRCByZXF1aXJlbWVudCB3aXRoIGl0cyBvd24gc2NlbmFyaW9zLgoKICAgICAgTmV3IGNhcGFiaWxpdGllcyBvbmx5OiB0aGUgZGVsdGEgc3BlYydzIGZpcnN0IHNlY3Rpb24gaXMgYCMjIFB1cnBvc2VgIC0KICAgICAgb25lIG9yIHR3byBzZW50ZW5jZXMgKDUwKyBjaGFyYWN0ZXJzLCBvciBgb3BlbnNwZWMgdmFsaWRhdGUgLS1zdHJpY3RgCiAgICAgIHJlcG9ydHMgaXQgYXMgdG9vIGJyaWVmKSBkZXNjcmliaW5nIHdoYXQgdGhlIGNhcGFiaWxpdHkgaXMgZm9yLiBBcmNoaXZlCiAgICAgIGNvcGllcyBpdCBpbnRvIHRoZSBtYWluIHNwZWMgaXQgY3JlYXRlczsgd2l0aG91dCBpdCB0aGUgbmV3IG1haW4gc3BlYyBpcwogICAgICBsZWZ0IHdpdGggYSBgVEJEIC4uLiBVcGRhdGUgUHVycG9zZSBhZnRlciBhcmNoaXZlYCBwbGFjZWhvbGRlciB0byBmaWxsIGluCiAgICAgIGJ5IGhhbmQuIERvIE5PVCBhZGQgYCMjIFB1cnBvc2VgIHRvIGEgZGVsdGEgZm9yIGFuIGV4aXN0aW5nIGNhcGFiaWxpdHkgLQogICAgICB0aGF0IHNwZWMgYWxyZWFkeSBoYXMgb25lIGFuZCB0aGUgZGVsdGEncyBpcyBpZ25vcmVkLiBUbyBjaGFuZ2UgYW4KICAgICAgZXhpc3RpbmcgY2FwYWJpbGl0eSdzIFB1cnBvc2UgLSBpbmNsdWRpbmcgYSBsZWZ0b3ZlciBgVEJEYCBwbGFjZWhvbGRlciAtCiAgICAgIGVkaXQgYDxwbGFubmluZ0hvbWUucm9vdD4vb3BlbnNwZWMvc3BlY3MvPGNhcGFiaWxpdHktcGF0aD4vc3BlYy5tZGAKICAgICAgZGlyZWN0bHkuIGBwbGFubmluZ0hvbWUucm9vdGAgY29tZXMgZnJvbSB0aGUgYG9wZW5zcGVjIGluc3RydWN0aW9ucyAuLi4KICAgICAgLS1qc29uYCByZXNwb25zZS4gQWx3YXlzIHVzZSBpdCByYXRoZXIgdGhhbiBhIHJlcG8tcmVsYXRpdmUgcGF0aDogaXQKICAgICAgcmVzb2x2ZXMgdG8gdGhlIHN0b3JlIHdoZW5ldmVyIHRoZSBjaGFuZ2UgbGl2ZXMgaW4gb25lIC0gd2hldGhlciB0aGF0CiAgICAgIGNhbWUgZnJvbSBgLS1zdG9yZWAsIGEgcHJvamVjdCBgc3RvcmU6YCBwb2ludGVyLCBvciBhIGdsb2JhbCBkZWZhdWx0CiAgICAgIHN0b3JlIC0gYW5kIHRvIHRoZSBjdXJyZW50IHJlcG9zaXRvcnkgb3RoZXJ3aXNlLiBEbyBub3QgdHJ5IHRvIHdvcmsgb3V0CiAgICAgIHdoaWNoIGNhc2UgYXBwbGllczsgdGhlIGZpZWxkIGFscmVhZHkgaGFzLgoKICAgICAgTU9ESUZJRUQgcmVxdWlyZW1lbnRzIHdvcmtmbG93OgogICAgICAxLiBMb2NhdGUgdGhlIGV4aXN0aW5nIHJlcXVpcmVtZW50IGluIGA8cGxhbm5pbmdIb21lLnJvb3Q+L29wZW5zcGVjL3NwZWNzLzxjYXBhYmlsaXR5LXBhdGg+L3NwZWMubWRgICh0aGUgc2FtZSBzdG9yZS1hd2FyZSByb290IGFzIGFib3ZlKQogICAgICAyLiBDb3B5IHRoZSBFTlRJUkUgcmVxdWlyZW1lbnQgYmxvY2sgKGZyb20gYCMjIyBSZXF1aXJlbWVudDpgIHRocm91Z2ggYWxsIHNjZW5hcmlvcykKICAgICAgMy4gUGFzdGUgdW5kZXIgYCMjIE1PRElGSUVEIFJlcXVpcmVtZW50c2AgYW5kIGVkaXQgdG8gcmVmbGVjdCBuZXcgYmVoYXZpb3IKICAgICAgNC4gRW5zdXJlIGhlYWRlciB0ZXh0IG1hdGNoZXMgZXhhY3RseSAod2hpdGVzcGFjZS1pbnNlbnNpdGl2ZSkKCiAgICAgIENvbW1vbiBwaXRmYWxsOiBVc2luZyBNT0RJRklFRCB3aXRoIHBhcnRpYWwgY29udGVudCBsb3NlcyBkZXRhaWwgYXQgYXJjaGl2ZSB0aW1lLgogICAgICBJZiBhZGRpbmcgbmV3IGNvbmNlcm5zIHdpdGhvdXQgY2hhbmdpbmcgZXhpc3RpbmcgYmVoYXZpb3IsIHVzZSBBRERFRCBpbnN0ZWFkLgoKICAgICAgRXhhbXBsZSAoYSBuZXcgY2FwYWJpbGl0eSwgc28gaXRzIGZpcnN0IHNlY3Rpb24gaXMgYCMjIFB1cnBvc2VgKToKICAgICAgYGBgCiAgICAgICMgU3BlYyBEZWx0YQoKICAgICAgIyMgUHVycG9zZQoKICAgICAgTGV0cyB1c2VycyB0YWtlIHRoZWlyIGRhdGEgb3V0IG9mIHRoZSBwcm9kdWN0IGluIGEgcG9ydGFibGUgZm9ybWF0LgoKICAgICAgIyMgQURERUQgUmVxdWlyZW1lbnRzCgogICAgICAjIyMgUmVxdWlyZW1lbnQ6IFVzZXIgY2FuIGV4cG9ydCBkYXRhCiAgICAgIFRoZSBzeXN0ZW0gU0hBTEwgYWxsb3cgdXNlcnMgdG8gZXhwb3J0IHRoZWlyIGRhdGEgaW4gQ1NWIGZvcm1hdC4KCiAgICAgICMjIyMgU2NlbmFyaW86IFN1Y2Nlc3NmdWwgZXhwb3J0CiAgICAgIC0gKipXSEVOKiogdXNlciBjbGlja3MgIkV4cG9ydCIgYnV0dG9uCiAgICAgIC0gKipUSEVOKiogc3lzdGVtIGRvd25sb2FkcyBhIENTViBmaWxlIHdpdGggYWxsIHVzZXIgZGF0YQoKICAgICAgIyMgUkVNT1ZFRCBSZXF1aXJlbWVudHMKCiAgICAgICMjIyBSZXF1aXJlbWVudDogTGVnYWN5IGV4cG9ydAogICAgICAqKlJlYXNvbioqOiBSZXBsYWNlZCBieSBuZXcgZXhwb3J0IHN5c3RlbQogICAgICAqKk1pZ3JhdGlvbioqOiBVc2UgbmV3IGV4cG9ydCBlbmRwb2ludCBhdCAvYXBpL3YyL2V4cG9ydAogICAgICBgYGAKCgogICAgICBTdGFibGUgSURzIChzcGVjLWN0eCk6CiAgICAgIC0gRXZlcnkgcmVxdWlyZW1lbnQgaGVhZGVyIHN0YXJ0cyB3aXRoIGFuIElEOiBgIyMjIFJlcXVpcmVtZW50OiBSRVEtPENBUD4tTk5OIDxuYW1lPmAuCiAgICAgICAgR2V0IGEgbmV3IElEIGZyb20gYHNwZWNjdHggaWQgbmV3IFJFUSA8Q0FQPmAuIE5ldmVyIGNvdW50IG51bWJlcnMgYnkgaGFuZC4KICAgICAgICBgPENBUD5gIGlzIGEgY2FwYWJpbGl0eSBjb2RlIGZyb20gYFtpZHNdIGNhcHNgIGluIGBzcGVjLWN0eC50b21sYC4KICAgICAgLSBFdmVyeSBzY2VuYXJpbyBoZWFkZXIgc3RhcnRzIHdpdGggYW4gSUQ6IGAjIyMjIFNjZW5hcmlvOiBTQ04tPENBUD4tTk5OLTxuPiA8bmFtZT5gLgogICAgICAgIEdldCBpdCBmcm9tIGBzcGVjY3R4IGlkIG5ldyBTQ04gPENBUD4gLS1mb3IgUkVRLTxDQVA+LU5OTmAuCiAgICAgIC0gVW5kZXIgTU9ESUZJRUQsIFJFTU9WRUQsIG9yIFJFTkFNRUQsIGtlZXAgdGhlIGV4aXN0aW5nIElELiBBbiBJRCBuZXZlciBjaGFuZ2VzLgogICAgICAtIExpbmsgcmVsYXRlZCBJRHMgd2l0aCBvbmUgbGluZSBgUmVmczogSUQsIElEYCBhZnRlciB0aGUgcmVxdWlyZW1lbnQgdGV4dC4KICAgICAgLSBSZXBsYWNlIGV2ZXJ5IHBsYWNlaG9sZGVyIG9mIHRoZSB0ZW1wbGF0ZS4gYHNwZWNjdHggY2hlY2tgIHJlcG9ydHMgYSBoZWFkZXIgd2l0aAogICAgICAgIG5vIElEIGFzIGBtaXNzaW5nLWlkYC4KCiAgICAgIFNwZWNzIHNob3VsZCBiZSB0ZXN0YWJsZSAtIGVhY2ggc2NlbmFyaW8gaXMgYSBwb3RlbnRpYWwgdGVzdCBjYXNlLgogICAgcmVxdWlyZXM6CiAgICAgIC0gcHJvcG9zYWwKCiAgLSBpZDogZGVzaWduCiAgICBnZW5lcmF0ZXM6IGRlc2lnbi5tZAogICAgZGVzY3JpcHRpb246IFRlY2huaWNhbCBkZXNpZ24gZG9jdW1lbnQgd2l0aCBpbXBsZW1lbnRhdGlvbiBkZXRhaWxzCiAgICB0ZW1wbGF0ZTogZGVzaWduLm1kCiAgICBpbnN0cnVjdGlvbjogfAogICAgICBDcmVhdGUgdGhlIGRlc2lnbiBkb2N1bWVudCB0aGF0IGV4cGxhaW5zIEhPVyB0byBpbXBsZW1lbnQgdGhlIGNoYW5nZS4KCiAgICAgIFdoZW4gdG8gaW5jbHVkZSBkZXNpZ24ubWQgKGNyZWF0ZSBvbmx5IGlmIGFueSBhcHBseSk6CiAgICAgIC0gQ3Jvc3MtY3V0dGluZyBjaGFuZ2UgKG11bHRpcGxlIHNlcnZpY2VzL21vZHVsZXMpIG9yIG5ldyBhcmNoaXRlY3R1cmFsIHBhdHRlcm4KICAgICAgLSBOZXcgZXh0ZXJuYWwgZGVwZW5kZW5jeSBvciBzaWduaWZpY2FudCBkYXRhIG1vZGVsIGNoYW5nZXMKICAgICAgLSBTZWN1cml0eSwgcGVyZm9ybWFuY2UsIG9yIG1pZ3JhdGlvbiBjb21wbGV4aXR5CiAgICAgIC0gQW1iaWd1aXR5IHRoYXQgYmVuZWZpdHMgZnJvbSB0ZWNobmljYWwgZGVjaXNpb25zIGJlZm9yZSBjb2RpbmcKCiAgICAgIFNlY3Rpb25zOgogICAgICAtICoqQ29udGV4dCoqOiBPbmx5IHRoZSBjdXJyZW50IHN0YXRlIGFuZCBjb25zdHJhaW50cyBuZWVkZWQgdG8gZXhwbGFpbiB0aGUgYXBwcm9hY2guIFJlZmVyZW5jZSB0aGUgcHJvcG9zYWwgZm9yIG1vdGl2YXRpb24gaW5zdGVhZCBvZiByZXN0YXRpbmcgaXQgKGUuZy4sICJTZWUgcHJvcG9zYWwubWQgLSBXaHkiKS4KICAgICAgLSAqKkdvYWxzIC8gTm9uLUdvYWxzKio6IFdoYXQgdGhpcyBkZXNpZ24gYWNoaWV2ZXMgYW5kIGV4cGxpY2l0bHkgZXhjbHVkZXMuIERvbid0IHJlc3RhdGUgdGhlIHByb3Bvc2FsJ3Mgc2NvcGUgLSBhZGQgb25seSBkZXNpZ24tbGV2ZWwgYm91bmRhcmllcy4KICAgICAgLSAqKkRlY2lzaW9ucyoqOiBLZXkgdGVjaG5pY2FsIGNob2ljZXMgd2l0aCByYXRpb25hbGUgKHdoeSBYIG92ZXIgWT8pLiBJbmNsdWRlIGFsdGVybmF0aXZlcyBjb25zaWRlcmVkIGZvciBlYWNoIGRlY2lzaW9uLgogICAgICAtICoqUmlza3MgLyBUcmFkZS1vZmZzKio6IEtub3duIGxpbWl0YXRpb25zLCB0aGluZ3MgdGhhdCBjb3VsZCBnbyB3cm9uZy4gRm9ybWF0OiBbUmlza10g4oaSIE1pdGlnYXRpb24KICAgICAgLSAqKk1pZ3JhdGlvbiBQbGFuKio6IFN0ZXBzIHRvIGRlcGxveSwgcm9sbGJhY2sgc3RyYXRlZ3kgKGlmIGFwcGxpY2FibGUpCiAgICAgIC0gKipPcGVuIFF1ZXN0aW9ucyoqOiBVbmtub3ducyB0aGF0IGNhbiBzYWZlbHkgYmUgYW5zd2VyZWQgbGF0ZXIgd2l0aG91dAogICAgICAgIGNoYW5naW5nIHRoZSBzcGVjcywgdGhlIGFwcHJvYWNoLCBvciB0aGUgdGFzayBicmVha2Rvd24uIE9taXQgaWYgbm9uZS4KCiAgICAgIE9wZW4gcXVlc3Rpb25zIGFyZSBmb3IgZ2VudWluZWx5IGRlZmVycmFibGUgdW5rbm93bnMsIG5vdCBkZWNpc2lvbnMgeW91CiAgICAgIHNraXBwZWQuIElmIGEgcXVlc3Rpb24gd291bGQgY2hhbmdlIHRoZSBzcGVjcywgdGhlIGNob3NlbiBhcHByb2FjaCwgb3IKICAgICAgdGhlIHRhc2sgYnJlYWtkb3duLCByZXNvbHZlIGl0IG5vdyAtIGFzayB0aGUgdXNlciBpbnN0ZWFkIG9mIGd1ZXNzaW5nLgoKICAgICAgRm9jdXMgb24gYXJjaGl0ZWN0dXJlIGFuZCBhcHByb2FjaCwgbm90IGxpbmUtYnktbGluZSBpbXBsZW1lbnRhdGlvbi4KICAgICAgVGhlIHByb3Bvc2FsIGNvdmVycyB3aHkgYW5kIHdoYXQ7IGRlc2lnbiBjb3ZlcnMgaG93LiBSZWZlcmVuY2UgdGhlCiAgICAgIHByb3Bvc2FsIGZvciBtb3RpdmF0aW9uIGFuZCwgb25jZSB3cml0dGVuLCB0aGUgc3BlY3MgZm9yIHJlcXVpcmVtZW50cyAtCiAgICAgIGlmIGEgc2VjdGlvbiB3b3VsZCBvbmx5IHJlc3RhdGUgdGhlbSwgcG9pbnQgdG8gdGhlbSBpbnN0ZWFkLgoKCiAgICAgIFN0YWJsZSBJRHMgKHNwZWMtY3R4KToKICAgICAgLSBFYWNoIGJ1bGxldCB1bmRlciBgIyMgRGVjaXNpb25zYCBpcyBvbmUgZGVjaXNpb24uIEl0cyBJRCBpcyBgPGNoYW5nZSBudW1iZXI+L0Q8bj5gCiAgICAgICAgYnkgcG9zaXRpb24sIHNvIGFkZCBuZXcgZGVjaXNpb25zIGF0IHRoZSBlbmQgYW5kIG5ldmVyIHJlb3JkZXIgdGhlbS4KICAgICAgLSBFbmQgYSBkZWNpc2lvbiB3aXRoIGBSZWZzOiBgIGFuZCB0aGUgUkVRIG9yIEFEUiBJRHMgaXQgc2VydmVzLiBUaGVuIGBzcGVjY3R4IGJ1bmRsZWAKICAgICAgICBwcmludHMgdGhlIGRlY2lzaW9uIHdpdGggdGhvc2UgcmVxdWlyZW1lbnRzLgoKICAgICAgR29vZCBkZXNpZ24gZG9jcyBleHBsYWluIHRoZSAid2h5IiBiZWhpbmQgdGVjaG5pY2FsIGRlY2lzaW9ucy4KICAgIHJlcXVpcmVzOgogICAgICAtIHByb3Bvc2FsCgogIC0gaWQ6IHRhc2tzCiAgICBnZW5lcmF0ZXM6IHRhc2tzLm1kCiAgICBkZXNjcmlwdGlvbjogSW1wbGVtZW50YXRpb24gY2hlY2tsaXN0IHdpdGggdHJhY2thYmxlIHRhc2tzCiAgICB0ZW1wbGF0ZTogdGFza3MubWQKICAgIGluc3RydWN0aW9uOiB8CiAgICAgIENyZWF0ZSB0aGUgdGFzayBsaXN0IHRoYXQgYnJlYWtzIGRvd24gdGhlIGltcGxlbWVudGF0aW9uIHdvcmsuCgogICAgICBCZWZvcmUgd3JpdGluZyB0YXNrcywgY2hlY2sgZGVzaWduLm1kIGZvciBPcGVuIFF1ZXN0aW9ucy4gSWYgYW55IG9mIHRoZW0KICAgICAgd291bGQgY2hhbmdlIHdoYXQgZ2V0cyBidWlsdCwgcmVzb2x2ZSB0aGVtIHdpdGggdGhlIHVzZXIgZmlyc3QgLSBkbyBub3QKICAgICAgYmFrZSBhbiB1bnN0YXRlZCBhc3N1bXB0aW9uIGludG8gdGhlIHRhc2sgbGlzdC4KCiAgICAgICoqSU1QT1JUQU5UOiBGb2xsb3cgdGhlIHRlbXBsYXRlIGJlbG93IGZvciB0cmFja2VkIHRhc2tzLioqIFRoZSBhcHBseSBwaGFzZSBwYXJzZXMKICAgICAgY2hlY2tib3ggZm9ybWF0IHRvIHRyYWNrIHByb2dyZXNzLiBBIGJveCBob2xkaW5nIG9ubHkgYHhgIGNvdW50cyBhcyBkb25lLAogICAgICB1cHBlciBvciBsb3dlciBjYXNlIGFuZCB3aXRoIGFueSBzcGFjaW5nLCBzbyBgLSBbIHhdYCBpcyBkb25lIHRvby4gRXZlcnkKICAgICAgb3RoZXIgbWFya2VyLCBpbmNsdWRpbmcgYC0gW35dYCwgYC0gWy1dYCBhbmQgYW4gZW1wdHkgYC0gW11gLCByZWFkcyBhcwogICAgICB1bmZpbmlzaGVkLiBBIGxpbmUgd2l0aCBubyBjaGVja2JveCBpcyBub3QgdHJhY2tlZCBhdCBhbGwuCgogICAgICBHdWlkZWxpbmVzOgogICAgICAtIEdyb3VwIHJlbGF0ZWQgdHJhY2tlZCB0YXNrcyB1bmRlciAjIyBudW1iZXJlZCBoZWFkaW5ncwogICAgICAtIEVhY2ggdHJhY2tlZCB0YXNrIE1VU1QgYmUgYSBjaGVja2JveDogYC0gWyBdIFguWSBUYXNrIGRlc2NyaXB0aW9uYAogICAgICAtIFRhc2tzIHNob3VsZCBiZSBzbWFsbCBlbm91Z2ggdG8gY29tcGxldGUgaW4gb25lIHNlc3Npb24KICAgICAgLSBPcmRlciB0YXNrcyBieSBkZXBlbmRlbmN5ICh3aGF0IG11c3QgYmUgZG9uZSBmaXJzdD8pCiAgICAgIC0gVHJhY2sgaW1wbGVtZW50YXRpb24gYW5kIHZlcmlmaWNhdGlvbiB3b3JrIHRoYXQgY2FuIGJlIGNvbXBsZXRlZCBiZWZvcmUKICAgICAgICBhcmNoaXZlLiBJZiB0aGUgcmVxdWVzdGVkIHdvcmtmbG93IGluY2x1ZGVzIGFyY2hpdmUgb3Igd29yayB0aGF0IHJlcXVpcmVzCiAgICAgICAgdGhpcyBjaGFuZ2UgdG8gYmUgYXJjaGl2ZWQsIHByZXNlcnZlIHRob3NlIHN0ZXBzIGFzIHBsYWluIGJ1bGxldHMgaW4gYW4KICAgICAgICBvcHRpb25hbCBgIyMgV29ya2Zsb3cgZm9sbG93LXVwYCBzZWN0aW9uIGF0IHRoZSBlbmQgb2YgdGFza3MubWQuIFRoZXNlCiAgICAgICAgYnVsbGV0cyBhcmUgcmVmZXJlbmNlIGluZm9ybWF0aW9uIG91dHNpZGUgdHJhY2tlZCB0YXNrIHByb2dyZXNzLgogICAgICAtIEVhY2ggdGFzayBNVVNUIHN0YXRlIGhvdyB0byB2ZXJpZnkgY29tcGxldGlvbiAoYSB0ZXN0LCBjb21tYW5kLAogICAgICAgIG9ic2VydmFibGUgYmVoYXZpb3IsIG9yIGRlbGl2ZXJlZCBhcnRpZmFjdCkuIFB1dCB0aGUgdmVyaWZpY2F0aW9uIGluCiAgICAgICAgdGhhdCB0YXNrJ3MgY2hlY2tib3ggZGVzY3JpcHRpb24uIFVzZSBhIHNlcGFyYXRlIHZlcmlmaWNhdGlvbiB0YXNrIG9ubHkKICAgICAgICB3aGVuIGl0IGNoZWNrcyBicm9hZGVyIGludGVncmF0aW9uIG9yIHN5c3RlbSBiZWhhdmlvciB0aGF0IHNwYW5zCiAgICAgICAgbXVsdGlwbGUgaW1wbGVtZW50YXRpb24gdGFza3MuCiAgICAgIC0gRWFjaCB0YXNrIGdyb3VwIE1VU1QgbGFuZCB0aGUgdGVzdHMgYW5kIGRvY3VtZW50YXRpb24gaXRzIG93biB3b3JrCiAgICAgICAgY2FsbHMgZm9yLiBEbyBOT1QgY29sbGVjdCB0ZXN0aW5nIG9yIGRvY3VtZW50YXRpb24gaW50byBhIGZpbmFsIGdyb3VwIC0KICAgICAgICB3aGVuIGEgbGF0ZSBncm91cCBmaXJzdCBleGVyY2lzZXMgd29yayBmcm9tIGFuIGVhcmx5IG9uZSwgdGhlIGZhaWx1cmVzCiAgICAgICAgY2FzY2FkZSBiYWNrIHRocm91Z2ggZXZlcnkgZ3JvdXAgaW4gYmV0d2VlbiBhbmQgZm9yY2UgcmV3b3JrLiBBIGdyb3VwCiAgICAgICAgd2hvc2Ugd29yayBjYWxscyBmb3IgbmVpdGhlciwgc3VjaCBhcyBzY2FmZm9sZGluZyBvciBkZXBlbmRlbmN5IHNldHVwLAogICAgICAgIGNhcnJpZXMgbmVpdGhlci4gQSBmaW5hbCBncm91cCBpcyBmb3IgaW50ZWdyYXRpb24gY2hlY2tzIG9ubHksIG5vdCBmb3IKICAgICAgICB0aGUgdGVzdHMgYW5kIGRvY3MgYW4gZWFybGllciBncm91cCBvd2VkLgoKICAgICAgRXhhbXBsZToKICAgICAgYGBgCiAgICAgICMgVGFza3MKCiAgICAgICMjIDEuIFNldHVwCgogICAgICAtIFsgXSAxLjEgQ3JlYXRlIG5ldyBtb2R1bGUgc3RydWN0dXJlIGFuZCB2ZXJpZnkgZXhwZWN0ZWQgZmlsZXMgYXJlIHByZXNlbnQKICAgICAgLSBbIF0gMS4yIEFkZCBkZXBlbmRlbmNpZXMgdG8gcGFja2FnZS5qc29uIGFuZCB2ZXJpZnkgcGFja2FnZSBpbnN0YWxsYXRpb24gc3VjY2VlZHMKCiAgICAgICMjIDIuIENvcmUgSW1wbGVtZW50YXRpb24KCiAgICAgIC0gWyBdIDIuMSBJbXBsZW1lbnQgZGF0YSBleHBvcnQgZnVuY3Rpb24gYW5kIHZlcmlmeSB0aGUgZXhwb3J0IHRlc3QgcGFzc2VzCiAgICAgIC0gWyBdIDIuMiBBZGQgQ1NWIGZvcm1hdHRpbmcgdXRpbGl0aWVzIGFuZCB2ZXJpZnkgdW5pdCB0ZXN0cyBjb3ZlciBxdW90aW5nIGFuZCBkZWxpbWl0ZXJzCiAgICAgIC0gWyBdIDIuMyBEb2N1bWVudCB0aGUgZXhwb3J0IEFQSSBpbiBkb2NzL2V4cG9ydC5tZCBhbmQgdmVyaWZ5IHRoZSBkb2N1bWVudGVkIGNvbW1hbmQgcnVucyBhcyB3cml0dGVuCiAgICAgIGBgYAoKICAgICAgV2hlbiBhcHBsaWNhYmxlLCBhcHBlbmQgd29ya2Zsb3cgZm9sbG93LXVwIGFzIHBsYWluIGJ1bGxldHMsIGZvciBleGFtcGxlOgogICAgICBgYGAKICAgICAgIyMgV29ya2Zsb3cgZm9sbG93LXVwCgogICAgICAtIEFyY2hpdmUgdGhlIGNoYW5nZSBhZnRlciB0aGUgcHJvamVjdCdzIHJldmlldyByZXF1aXJlbWVudHMgYXJlIHNhdGlzZmllZC4KICAgICAgLSBWZXJpZnkgdGhlIGFyY2hpdmVkIHJlc3VsdC4KICAgICAgYGBgCgoKICAgICAgU3RhYmxlIElEcyBhbmQgVmVyaWZ5IChzcGVjLWN0eCk6CiAgICAgIC0gRXZlcnkgdGFzayBjaXRlcyBhdCBsZWFzdCBvbmUgcmVxdWlyZW1lbnQgaW4gc3F1YXJlIGJyYWNrZXRzOiBgW1JFUS08Q0FQPi1OTk5dYC4KICAgICAgLSBFdmVyeSB0YXNrIGVuZHMgd2l0aCBgVmVyaWZ5OmAgYW5kIHRoZSBjb21tYW5kIGluIGEgY29kZSBzcGFuLCBmb3IgZXhhbXBsZQogICAgICAgIGBgVmVyaWZ5OiBgbnBtIHRlc3QgLS0gdGVzdHMvZXhwb3J0LnRlc3QudHNgIGBgLiBUd28gc3BhbnMgcnVuIGpvaW5lZCB3aXRoIGAmJmAuCiAgICAgIC0gVXNlIHByb3NlIGFmdGVyIGBWZXJpZnk6YCBvbmx5IHdoZW4gbm8gY29tbWFuZCBjYW4gY2hlY2sgdGhlIHdvcmsuIFRoZW4KICAgICAgICBgc3BlY2N0eCB0YXNrIGRvbmUgPGlkPiAtLW1hbnVhbCAiPG5vdGU+ImAgcmVjb3JkcyBpdC4KICAgICAgLSBDbG9zZSBhIHRhc2sgb25seSB3aXRoIGBzcGVjY3R4IHRhc2sgZG9uZSA8aWQ+YC4gSXQgcnVucyB0aGUgY29tbWFuZCwgd3JpdGVzCiAgICAgICAgZXZpZGVuY2UgdW5kZXIgYC5jdHgvZXZpZGVuY2UvYCwgYW5kIHNldHMgdGhlIGNoZWNrYm94LgoKICAgICAgUmVmZXJlbmNlIHNwZWNzIGZvciB3aGF0IG5lZWRzIHRvIGJlIGJ1aWx0LCBkZXNpZ24gZm9yIGhvdyB0byBidWlsZCBpdC4KICAgIHJlcXVpcmVzOgogICAgICAtIHNwZWNzCiAgICAgIC0gZGVzaWduCgphcHBseToKICByZXF1aXJlczogWyB0YXNrcyBdCiAgdHJhY2tzOiB0YXNrcy5tZAogIGluc3RydWN0aW9uOiB8CiAgICBSZWFkIGNvbnRleHQgZmlsZXMsIHdvcmsgdGhyb3VnaCBwZW5kaW5nIHRhc2tzLCBtYXJrIGNvbXBsZXRlIGFzIHlvdSBnby4KICAgIFBhdXNlIGlmIHlvdSBoaXQgYmxvY2tlcnMgb3IgbmVlZCBjbGFyaWZpY2F0aW9uLgo=",
7
+ "schemas/spec-driven-ids/templates/design.md": "IyBEZXNpZ24KCiMjIENvbnRleHQKCjwhLS0gVGhlIGN1cnJlbnQgc3RhdGUgYW5kIHRoZSBjb25zdHJhaW50cyB0aGF0IHNoYXBlIHRoZSBhcHByb2FjaC4gVGhlIHByb3Bvc2FsIGhvbGRzIHRoZSBtb3RpdmF0aW9uLiAtLT4KCiMjIEdvYWxzIC8gTm9uLUdvYWxzCgoqKkdvYWxzOioqCjwhLS0gV2hhdCB0aGlzIGRlc2lnbiBnaXZlcyAtLT4KCioqTm9uLUdvYWxzOioqCjwhLS0gV2hhdCBzdGF5cyBvdXQgb2Ygc2NvcGUgLS0+CgojIyBEZWNpc2lvbnMKCjwhLS0gT25lIGJ1bGxldCBwZXIgZGVjaXNpb24uIFRoZSBJRCBvZiBhIGRlY2lzaW9uIGlzIDxjaGFuZ2UgbnVtYmVyPi9EPG4+IGJ5IHBvc2l0aW9uLiBBZGQgbmV3IG9uZXMgYXQgdGhlIGVuZC4gLS0+Ci0gPCEtLSBUaGUgZGVjaXNpb24sIHRoZSBhbHRlcm5hdGl2ZSwgYW5kIHdoeS4gLS0+IFJlZnM6IDwhLS0gUkVRLTxDQVA+LU5OTiwgQURSLU5OTiAtLT4KCiMjIFJpc2tzIC8gVHJhZGUtb2ZmcwoKPCEtLSBLbm93biByaXNrcyBhbmQgdGhlIG1pdGlnYXRpb24gZm9yIGVhY2ggLS0+Cg==",
8
+ "schemas/spec-driven-ids/templates/proposal.md": "IyBQcm9wb3NhbAoKIyMgV2h5Cgo8IS0tIEV4cGxhaW4gdGhlIG1vdGl2YXRpb24gZm9yIHRoaXMgY2hhbmdlLiBXaGF0IHByb2JsZW0gZG9lcyB0aGlzIHNvbHZlPyBXaHkgbm93PyAtLT4KCiMjIFdoYXQgQ2hhbmdlcwoKPCEtLSBEZXNjcmliZSB3aGF0IHdpbGwgY2hhbmdlLiBCZSBzcGVjaWZpYyBhYm91dCBuZXcgY2FwYWJpbGl0aWVzLCBtb2RpZmljYXRpb25zLCBvciByZW1vdmFscy4gLS0+CgojIyBDYXBhYmlsaXRpZXMKCiMjIyBOZXcgQ2FwYWJpbGl0aWVzCjwhLS0gQ2FwYWJpbGl0aWVzIGJlaW5nIGludHJvZHVjZWQuIE5hbWUgZWFjaCBjYXBhYmlsaXR5IGZvciBhIGNvaGVzaXZlIHN5c3RlbQogICAgIGJlaGF2aW9yIHRoYXQgY2FuIG93biByZWxhdGVkIHJlcXVpcmVtZW50cyBhcyB0aGUgc3lzdGVtIGV2b2x2ZXMuIERvIG5vdCBuYW1lCiAgICAgaW1wbGVtZW50YXRpb24gdGFza3Mgb3IgcHJvcG9zYWwgc2VjdGlvbnMuIEF2b2lkIGJyb2FkIGNhdGNoLWFsbCBuYW1lcy4gVXNlCiAgICAga2ViYWItY2FzZSBmb3IgcGF0aCBzZWdtZW50cyB5b3UgaW50cm9kdWNlIChlLmcuLCB1c2VyLWF1dGggb3IgaWRlbnRpdHkvdXNlci1hdXRoKQogICAgIHRoYXQgZm9sbG93IHRoZSBwcm9qZWN0J3MgZXhpc3Rpbmcgc3BlYyBvcmdhbml6YXRpb24uIEVhY2ggY3JlYXRlcwogICAgIHNwZWNzLzxjYXBhYmlsaXR5LXBhdGg+L3NwZWMubWQuIC0tPgotIGA8Y2FwYWJpbGl0eS1wYXRoPmA6IDxicmllZiBkZXNjcmlwdGlvbiBvZiB3aGF0IHRoaXMgY2FwYWJpbGl0eSBjb3ZlcnM+CgojIyMgTW9kaWZpZWQgQ2FwYWJpbGl0aWVzCjwhLS0gRXhpc3RpbmcgY2FwYWJpbGl0aWVzIHdob3NlIFJFUVVJUkVNRU5UUyBhcmUgY2hhbmdpbmcgKG5vdCBqdXN0IGltcGxlbWVudGF0aW9uKS4KICAgICBPbmx5IGxpc3QgaGVyZSBpZiBzcGVjLWxldmVsIGJlaGF2aW9yIGNoYW5nZXMuIEVhY2ggbmVlZHMgYSBkZWx0YSBzcGVjIGZpbGUuCiAgICAgVXNlIHRoZSBleGFjdCBleGlzdGluZyBwYXRoIHVuZGVyIG9wZW5zcGVjL3NwZWNzLy4gTGVhdmUgZW1wdHkgaWYgbm8gcmVxdWlyZW1lbnQKICAgICBjaGFuZ2VzLiBBIGNoYW5nZSB3aXRoIG5vIGNhcGFiaWxpdGllcyBhdCBhbGwgKHB1cmUgcmVmYWN0b3IsIHRvb2xpbmcsIGRvY3MpCiAgICAgbXVzdCBzZXQgYHNraXBfc3BlY3M6IHRydWVgIGluIGl0cyAub3BlbnNwZWMueWFtbCAtIG9wZW5zcGVjIHZhbGlkYXRlIHJlamVjdHMKICAgICBhIHplcm8tZGVsdGEgY2hhbmdlIHdpdGhvdXQgdGhhdCBtYXJrZXIuIERvIG5vdCBpbnZlbnQgYSByZXF1aXJlbWVudCBqdXN0IHRvCiAgICAgc2F0aXNmeSB2YWxpZGF0aW9uLiAtLT4KLSBgPGV4aXN0aW5nLWNhcGFiaWxpdHktcGF0aD5gOiA8d2hhdCByZXF1aXJlbWVudCBpcyBjaGFuZ2luZz4KCiMjIEltcGFjdAoKPCEtLSBBZmZlY3RlZCBjb2RlLCBBUElzLCBkZXBlbmRlbmNpZXMsIHN5c3RlbXMgLS0+Cg==",
9
+ "schemas/spec-driven-ids/templates/spec.md": "IyBTcGVjIERlbHRhCgojIyBQdXJwb3NlCjwhLS0gTmV3IGNhcGFiaWxpdHkgb25seS4gV3JpdGUgb25lIG9yIHR3byBzZW50ZW5jZXMgb2YgNTAgY2hhcmFjdGVycyBvciBtb3JlLiBEZWxldGUgdGhpcyBzZWN0aW9uIGZvciBhbiBleGlzdGluZyBjYXBhYmlsaXR5LiAtLT4KCiMjIEFEREVEIFJlcXVpcmVtZW50cwoKIyMjIFJlcXVpcmVtZW50OiBSRVEtPENBUD4tTk5OIDwhLS0gcmVxdWlyZW1lbnQgbmFtZSAtLT4KPCEtLSBSZXF1aXJlbWVudCB0ZXh0LiBSZXBsYWNlIFJFUS08Q0FQPi1OTk4gd2l0aCB0aGUgb3V0cHV0IG9mIGBzcGVjY3R4IGlkIG5ldyBSRVEgPENBUD5gLiAtLT4KClJlZnM6IDwhLS0gcmVsYXRlZCBJRHMgc3VjaCBhcyBBRFItMDAxLiBEZWxldGUgdGhpcyBsaW5lIHdoZW4gdGhlcmUgYXJlIG5vbmUuIC0tPgoKIyMjIyBTY2VuYXJpbzogU0NOLTxDQVA+LU5OTi0xIDwhLS0gc2NlbmFyaW8gbmFtZS4gR2V0IHRoZSBJRCBmcm9tIGBzcGVjY3R4IGlkIG5ldyBTQ04gPENBUD4gLS1mb3IgUkVRLTxDQVA+LU5OTmAuIC0tPgotICoqV0hFTioqIDwhLS0gY29uZGl0aW9uIC0tPgotICoqVEhFTioqIDwhLS0gZXhwZWN0ZWQgb3V0Y29tZSAtLT4K",
10
+ "schemas/spec-driven-ids/templates/tasks.md": "IyBUYXNrcwoKIyMgMS4gPCEtLSBUYXNrIGdyb3VwIG5hbWUgLS0+CgotIFsgXSAxLjEgPCEtLSBUYXNrIGRlc2NyaXB0aW9uIC0tPiBbUkVRLTxDQVA+LU5OTl0gVmVyaWZ5OiBgPCEtLSBjb21tYW5kIC0tPmAKLSBbIF0gMS4yIDwhLS0gVGFzayBkZXNjcmlwdGlvbiAtLT4gW1JFUS08Q0FQPi1OTk5dIFZlcmlmeTogYDwhLS0gY29tbWFuZCAtLT5gCgojIyAyLiA8IS0tIFRhc2sgZ3JvdXAgbmFtZSAtLT4KCi0gWyBdIDIuMSA8IS0tIFRhc2sgZGVzY3JpcHRpb24gLS0+IFtSRVEtPENBUD4tTk5OXSBWZXJpZnk6IGA8IS0tIGNvbW1hbmQgLS0+YAo=",
11
+ "skills/specctx-bootstrap/SKILL.md": "LS0tCm5hbWU6IHNwZWNjdHgtYm9vdHN0cmFwCmRlc2NyaXB0aW9uOiBTdGFydCBhIHNlc3Npb24gaW4gYW4gT3BlblNwZWMgcHJvamVjdCB0aGF0IHVzZXMgc3BlYy1jdHguIFVzZSBhdCB0aGUgc3RhcnQgb2YgZXZlcnkgc2Vzc2lvbiwgYW5kIG9uICJzdGFydCIsICJib290c3RyYXAiLCBvciAid2hhdCBzaG91bGQgSSB3b3JrIG9uIi4gTG9hZHMgb25seSB0aGUgc3BlYyB0ZXh0IHRoYXQgdGhlIG5leHQgdGFzayBuZWVkcy4KLS0tCgojIHNwZWNjdHgtYm9vdHN0cmFwCgpMb2FkIHRoZSBtaW5pbXVtIGNvbnRleHQgZm9yIG9uZSB0YXNrLiBSdW4gYHNwZWNjdHhgIGFzIGBucHggc3BlYy1jdHhgIHdoZW4gaXQgaXMgbm90IGluc3RhbGxlZC4KCiMjIFN0ZXBzCgoxLiBSdW4gYHNwZWNjdHggc3RhdHVzYC4gSXQgcmVidWlsZHMgYSBzdGFsZSBpbmRleCBmaXJzdC4gTm90ZSB0aGUgYWN0aXZlIGNoYW5nZSwgdGhlIGN1cnJlbnQgdGFzaywgYW5kIHRoZSBjb3VudCBvZiBvcGVuIHF1ZXN0aW9ucy4gVGhlIGxhc3QgbGluZSBgbmV4dDogPHN0ZXA+YCBuYW1lcyB0aGUgbmV4dCBzdGVwLiBFeGl0IDIgd2l0aCBgbm90IGFuIE9wZW5TcGVjIHByb2plY3RgIG1lYW5zIHRoZSBmb2xkZXIgaGFzIG5vIGBvcGVuc3BlYy9gLiBUZWxsIHRoZSB1c2VyIHRvIHJ1biBgbnB4IC15IEBmaXNzaW9uLWFpL29wZW5zcGVjQGxhdGVzdCBpbml0YCBhbmQgc3RvcC4KMi4gUmVhZCBgLmN0eC9oYW5kb2ZmLm1kYCBpZiBpdCBleGlzdHMuIE5vdGUgYGN1cnJlbnRgLCBgcmVvcGVuZWRgLCBhbmQgYG9wZW5fcXVlc3Rpb25zYC4KMy4gSWYgYHN0YXR1c2AgcHJpbnRzIGBubyBhY3RpdmUgY2hhbmdlYCwgc2hvdyB0aGUgb3BlbiBxdWVzdGlvbnMgdG8gdGhlIHVzZXIuIEFzayB3aGljaCBjaGFuZ2UgdG8gc3RhcnQuIFN0b3AgaGVyZS4KNC4gUGljayB0aGUgdGFzay4gVXNlIGBjdXJyZW50YCBmcm9tIHRoZSBoYW5kb2ZmLCB0aGVuIHRoZSB1c2VyJ3MgbWVzc2FnZSwgdGhlbiB0aGUgb3V0cHV0IG9mIGBzcGVjY3R4IHRhc2sgbmV4dGAuCjUuIFJ1biBgc3BlY2N0eCB0YXNrIHN0YXJ0IDx0YXNrPmAuIEl0IHByaW50cyB0aGUgYnVuZGxlIGZvciB0aGUgUkVRIElEcyBvZiB0aGUgdGFzayBhbmQgc2V0cyB0aGUgY3VycmVudCB0YXNrLgo2LiBSZWFkIHRoZSBidW5kbGUuIEVhY2ggYmxvY2sgc3RhcnRzIHdpdGggYC0tIDxpZD4gfCA8ZmlsZT46PGxpbmU+IHwgPHN0YXR1cz4gfCA8cmVhc29uPmAuIEEgYG1vZGlmaWVkYCBibG9jayBpcyB0aGUgcGVuZGluZyB0ZXh0IG9mIGFuIGFjdGl2ZSBjaGFuZ2UuIFRydXN0IGl0IG92ZXIgdGhlIG1haW4gc3BlYy4KNy4gSWYgdGhlIGJ1bmRsZSBlbmRzIHdpdGggYHRydW5jYXRlZDogTiBlbnRyaWVzIG9taXR0ZWQ6IDxpZHM+YCwgcnVuIGBzcGVjY3R4IGJ1bmRsZSA8aWQ+YCBmb3IgZWFjaCBJRCB0aGF0IHRoZSB0YXNrIG5lZWRzLgo4LiBTYXkgaW4gb25lIGxpbmUgd2hpY2ggdGFzayB5b3UgYXJlIG9uIGFuZCB3aGljaCBJRHMgeW91IGxvYWRlZC4gVGhlbiBzdGFydCB0aGUgd29yay4KCiMjIFJ1bGVzCgotIERvIG5vdCBvcGVuIGBvcGVuc3BlYy9zcGVjcy9gIG9yIGBvcGVuc3BlYy9jaGFuZ2VzLyovc3BlY3MvYCBieSBoYW5kLiBVc2UgYHNwZWNjdHggYnVuZGxlYCBvciBgc3BlY2N0eCB0YXNrIHN0YXJ0YC4KLSBSZWFkIGF0IG1vc3QgMTAsMDAwIGNoYXJhY3RlcnMgb2Ygc3BlYyB0ZXh0IGJlZm9yZSB0aGUgZmlyc3QgY29kZSBlZGl0LgotIFRhZyBjb2RlIHdpdGggYC8vIFJFUS1BQkMtMDAxYCBhbmQgdGVzdHMgd2l0aCBgLy8gQ292ZXJzOiBSRVEtQUJDLTAwMWAuCi0gR2V0IGEgbmV3IElEIGZyb20gYHNwZWNjdHggaWQgbmV3IFJFUSA8Q0FQPmAuIE5ldmVyIGNvdW50IG51bWJlcnMgYnkgaGFuZC4KLSBDbG9zZSB0aGUgdGFzayB3aXRoIHRoZSBgc3BlY2N0eC1kb25lYCBza2lsbC4K",
12
+ "skills/specctx-done/SKILL.md": "LS0tCm5hbWU6IHNwZWNjdHgtZG9uZQpkZXNjcmlwdGlvbjogQ2xvc2UgYSB0YXNrIGluIGFuIE9wZW5TcGVjIHByb2plY3QgdGhhdCB1c2VzIHNwZWMtY3R4LiBSdW5zIHRoZSBWZXJpZnkgY29tbWFuZCwgd3JpdGVzIGV2aWRlbmNlLCBzZXRzIHRoZSBjaGVja2JveCwgd3JpdGVzIHRoZSBoYW5kb2ZmLCBhbmQgc2hvd3MgdGhlIG1ldHJpY3MuIFVzZSB3aGVuIHRoZSB3b3JrIG9mIGEgdGFzayBpcyBjb21wbGV0ZSwgb3Igb24gImRvbmUiLCAiY2xvc2UgdGhlIHRhc2siLCBhbmQgImVuZCB0aGUgc2Vzc2lvbiIuCi0tLQoKIyBzcGVjY3R4LWRvbmUKCkNsb3NlIHRoZSBjdXJyZW50IHRhc2sgd2l0aCBldmlkZW5jZS4gUnVuIGBzcGVjY3R4YCBhcyBgbnB4IHNwZWMtY3R4YCB3aGVuIGl0IGlzIG5vdCBpbnN0YWxsZWQuCgojIyBTdGVwcwoKMS4gUnVuIGBzcGVjY3R4IHRhc2sgZG9uZSA8dGFzaz5gLiBJdCBydW5zIHRoZSBgVmVyaWZ5OmAgY29tbWFuZCBvZiB0aGUgdGFzayBpbiB0aGUgcHJvamVjdCByb290LgoyLiBPbiBleGl0IDAgaXQgd3JpdGVzIGAuY3R4L2V2aWRlbmNlLzxjaGFuZ2U+Lzx0YXNrPi5qc29uYCBhbmQgc2V0cyBgW3hdYCBvbiB0aGUgdGFzayBsaW5lLiBOb3RoaW5nIGVsc2UgaW4gYHRhc2tzLm1kYCBjaGFuZ2VzLgozLiBPbiBhIGZhaWx1cmUgaXQgcHJpbnRzIHRoZSBsYXN0IDIwIGxpbmVzIG9mIG91dHB1dCBhbmQgZXhpdHMgMS4gVGhlIHRhc2sgc3RheXMgb3Blbi4gRml4IHRoZSBjYXVzZSBhbmQgcnVuIHN0ZXAgMSBhZ2Fpbi4KNC4gSWYgdGhlIHRhc2sgaGFzIHByb3NlIGFmdGVyIGBWZXJpZnk6YCBhbmQgbm8gY29tbWFuZCwgcnVuIGBzcGVjY3R4IHRhc2sgZG9uZSA8dGFzaz4gLS1tYW51YWwgIjx3aGF0IHlvdSBjaGVja2VkPiJgLgo1LiBBZnRlciB0aGUgbGFzdCB0YXNrIG9mIGEgZ3JvdXAsIHJ1biBgc3BlY2N0eCBjaGVjayAtLXN0cmljdGAuIEZpeCBldmVyeSBmaW5kaW5nIGJlZm9yZSB0aGUgbmV4dCBncm91cC4KNi4gUnVuIGBzcGVjY3R4IGhhbmRvZmYgd3JpdGVgLiBJdCB3cml0ZXMgYC5jdHgvaGFuZG9mZi5tZGAgZnJvbSB0aGUgaW5kZXgsIHRoZSBldmlkZW5jZSwgYW5kIHRoZSBvcGVuIHF1ZXN0aW9ucy4KNy4gUnVuIGBzcGVjY3R4IHN0YXRzIC0tY2hhbmdlIDxjaGFuZ2U+YC4gUmVwb3J0IHRoZSBmaXZlIG1ldHJpY3Mgb2YgdGhlIGNoYW5nZSBpbiBvbmUgbGluZSBlYWNoLgo4LiBDb21taXQgdGhlIHdvcmsgdG9nZXRoZXIgd2l0aCBgLmN0eC9gLiBMZWF2ZSBgLmN0eC9pbmRleC5qc29uYCBvdXQgb2YgZ2l0LgoKIyMgUnVsZXMKCi0gTmV2ZXIgdGljayBhIGNoZWNrYm94IGJ5IGhhbmQuIGBjaGVjayAtLXN0cmljdGAgcmVwb3J0cyBhIHRpY2tlZCB0YXNrIHdpdGggbm8gZXZpZGVuY2UgYXMgYGRvbmUtd2l0aG91dC1ldmlkZW5jZWAuCi0gSWYgYSBzcGVjIGNoYW5nZWQgYWZ0ZXIgeW91IGNsb3NlZCBhIHRhc2ssIHJ1biBgc3BlY2N0eCB0YXNrIHJlb3BlbiA8dGFzaz4gIjxyZWFzb24+ImAuCi0gVG8gbGVhdmUgYSBxdWVzdGlvbiBmb3IgdGhlIG5leHQgc2Vzc2lvbiwgcnVuIGBzcGVjY3R4IGhhbmRvZmYgYXNrICI8cXVlc3Rpb24+ImAgYmVmb3JlIHN0ZXAgNi4K",
13
+ "skills/specctx-next/SKILL.md": "LS0tCm5hbWU6IHNwZWNjdHgtbmV4dApkZXNjcmlwdGlvbjogUGljayBhbmQgc3RhcnQgdGhlIG5leHQgdGFzayBpbiB0aGUgbWlkZGxlIG9mIGEgc2Vzc2lvbiBpbiBhbiBPcGVuU3BlYyBwcm9qZWN0IHRoYXQgdXNlcyBzcGVjLWN0eC4gVXNlIGFmdGVyIHlvdSBjbG9zZSBhIHRhc2ssIG9yIG9uICJuZXh0IHRhc2siIGFuZCAid2hhdCBpcyBuZXh0Ii4KLS0tCgojIHNwZWNjdHgtbmV4dAoKTW92ZSB0byB0aGUgbmV4dCBvcGVuIHRhc2suIFJ1biBgc3BlY2N0eGAgYXMgYG5weCBzcGVjLWN0eGAgd2hlbiBpdCBpcyBub3QgaW5zdGFsbGVkLgoKIyMgU3RlcHMKCjEuIFJ1biBgc3BlY2N0eCB0YXNrIG5leHRgLiBJdCBwcmludHMgdGhlIHRhc2sgSUQgYW5kIHRleHQsIHRoZW4gYHJlcXM6YCBhbmQgYHZlcmlmeTpgLgoyLiBJZiBpdCBwcmludHMgYG5vIG9wZW4gdGFzay4gRXZlcnkgdGFzayBpbiB0aGUgYWN0aXZlIGNoYW5nZXMgaXMgZG9uZS4gTmV4dDogYXJjaGl2ZSA8Y2hhbmdlPi5gLCBldmVyeSB0YXNrIG9mIHRoZSBhY3RpdmUgY2hhbmdlcyBoYXMgaXRzIGNoZWNrYm94IHNldC4gUnVuIGBzcGVjY3R4IHN0YXR1c2AuIEl0cyBsYXN0IGxpbmUgaXMgYG5leHQ6IGFyY2hpdmUgPGNoYW5nZT5gLiBUZWxsIHRoZSB1c2VyIHRoYXQgdGhlIGNoYW5nZSBpcyByZWFkeSBmb3IgYHNwZWNjdHggY2hlY2sgLS1zdHJpY3RgIGFuZCBhcmNoaXZlLiBTdG9wIGhlcmUuCjMuIFJ1biBgc3BlY2N0eCB0YXNrIHN0YXJ0IDx0YXNrPmAuIEl0IHByaW50cyB0aGUgYnVuZGxlIGZvciB0aGUgUkVRIElEcyBvZiB0aGUgdGFzayBhbmQgc2V0cyB0aGUgY3VycmVudCB0YXNrIGluIGAuY3R4L2N1cnJlbnQuanNvbmAuCjQuIFJlYWQgdGhlIGJ1bmRsZS4gUmVxdWVzdCBhbiBvbWl0dGVkIElEIHdpdGggYHNwZWNjdHggYnVuZGxlIDxpZD5gIG9ubHkgd2hlbiB0aGUgdGFzayBuZWVkcyBpdC4KNS4gUnVuIGBzcGVjY3R4IHRhc2sgdmVyaWZ5IDx0YXNrPmAgb25jZSBiZWZvcmUgdGhlIHdvcmsuIEl0IHJ1bnMgdGhlIGBWZXJpZnk6YCBjb21tYW5kIGFuZCB3cml0ZXMgbm90aGluZy4gQSBmYWlsdXJlIG5vdyBzaG93cyB3aGF0IHRoZSB3b3JrIG11c3QgZml4Lgo2LiBTYXkgaW4gb25lIGxpbmUgd2hpY2ggdGFzayB5b3UgYXJlIG9uLiBUaGVuIHN0YXJ0IHRoZSB3b3JrLgoKIyMgUnVsZXMKCi0gVGFrZSB0aGUgdGFza3MgaW4gdGhlIG9yZGVyIHRoYXQgYHNwZWNjdHggdGFzayBuZXh0YCBnaXZlcy4gRWFybGllciB0YXNrcyBpbiBhIGdyb3VwIGNvbWUgZmlyc3QuCi0gQSBxdWVzdGlvbiB0aGF0IGJsb2NrcyB5b3UgZ29lcyB0byBgc3BlY2N0eCBoYW5kb2ZmIGFzayAiPHF1ZXN0aW9uPiJgLiBUaGVuIGNvbnRpbnVlIHdpdGggd2hhdCB5b3UgY2FuIGRvLgotIENsb3NlIHRoZSB0YXNrIHdpdGggdGhlIGBzcGVjY3R4LWRvbmVgIHNraWxsLgo=",
14
+ };
@@ -0,0 +1,65 @@
1
+ // REQ-ADOPT-005 REQ-CHK-002 the baseline ratchet in .ctx/baseline.json: sorted "rule id" pairs.
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { cmpStr } from "../util/pystr.js";
5
+ export const BASELINE_PATH = ".ctx/baseline.json";
6
+ export const BASELINE_RULES = ["uncovered", "scenario-untested", "done-without-evidence", "manual-evidence", "task-without-req", "task-without-verify"];
7
+ const key = (rule, id) => `${rule} ${id}`;
8
+ /** A warning line reads "manual-evidence <task>". */
9
+ const warningKey = (w) => w;
10
+ export function readBaseline(root) {
11
+ const p = join(root, BASELINE_PATH);
12
+ if (!existsSync(p))
13
+ return null;
14
+ const v = JSON.parse(readFileSync(p, "utf8"));
15
+ if (!Array.isArray(v) || !v.every((x) => typeof x === "string"))
16
+ throw new Error(`${BASELINE_PATH} must be a JSON list of "rule id" strings`);
17
+ return new Set(v);
18
+ }
19
+ function save(root, entries) {
20
+ const list = [...new Set(entries)].sort(cmpStr);
21
+ mkdirSync(join(root, ".ctx"), { recursive: true });
22
+ writeFileSync(join(root, BASELINE_PATH), JSON.stringify(list, null, 2) + "\n");
23
+ return list;
24
+ }
25
+ /** The current debt that the baseline may hold: strict findings and warnings of the listed rules. */
26
+ export function debt(findings, warnings) {
27
+ const out = findings.filter((f) => BASELINE_RULES.includes(f.rule)).map((f) => key(f.rule, f.id));
28
+ for (const w of warnings)
29
+ if (BASELINE_RULES.includes(w.split(" ")[0]))
30
+ out.push(warningKey(w));
31
+ return out;
32
+ }
33
+ export function writeBaseline(root, findings, warnings) {
34
+ return save(root, debt(findings, warnings));
35
+ }
36
+ /** Drop entries that no longer fire. Returns the number dropped. */
37
+ export function pruneBaseline(root, findings, warnings) {
38
+ const cur = readBaseline(root);
39
+ if (!cur)
40
+ return 0;
41
+ const live = new Set(debt(findings, warnings));
42
+ const kept = [...cur].filter((e) => live.has(e));
43
+ save(root, kept);
44
+ return cur.size - kept.length;
45
+ }
46
+ /** REQ-CHK-002 a finding or warning in the baseline leaves the output and counts as baselined. */
47
+ export function applyBaseline(root, findings, warnings) {
48
+ const base = readBaseline(root);
49
+ if (!base)
50
+ return { findings, warnings, baselined: 0 };
51
+ let n = 0;
52
+ const f2 = findings.filter((f) => {
53
+ const hit = base.has(key(f.rule, f.id));
54
+ if (hit)
55
+ n++;
56
+ return !hit;
57
+ });
58
+ const w2 = warnings.filter((w) => {
59
+ const hit = base.has(warningKey(w));
60
+ if (hit)
61
+ n++;
62
+ return !hit;
63
+ });
64
+ return { findings: f2, warnings: w2, baselined: n };
65
+ }
@@ -0,0 +1,126 @@
1
+ // REQ-CHK-001 REQ-CHK-002 REQ-GOV-002 integrity rules over the index.
2
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { canon, SPEC_KINDS } from "../index/canonical.js";
5
+ import { changeKey } from "../index/scan.js";
6
+ import { listEvidence } from "../tasks/evidence.js";
7
+ import { cmpTuple } from "../util/pystr.js";
8
+ import { evidenceFindings } from "./evidence.js";
9
+ const RETIRED_RE = /\b(REQ-[A-Z]{2,6}-\d{3}|ADR-\d{3})\b/g;
10
+ const REQ_CAP = /^REQ-([A-Z]{2,6})-/;
11
+ export function retiredIds(root) {
12
+ const p = join(root, "decisions", "retired-ids.md");
13
+ if (!existsSync(p))
14
+ return new Set();
15
+ // REQ-GOV-002 the retired ID is the first ID of a line. A reason may name the replacement ID, which stays live.
16
+ const out = new Set();
17
+ for (const line of readFileSync(p, "utf8").split("\n")) {
18
+ const m = new RegExp(RETIRED_RE.source).exec(line);
19
+ if (m)
20
+ out.add(m[1]);
21
+ }
22
+ return out;
23
+ }
24
+ /**
25
+ * REQ-CHK-004 two change folders, active or archived, with one key are a hard finding.
26
+ * The first folder in path order keeps the key. Every later folder gets a finding.
27
+ */
28
+ export function duplicateChangeKeys(root) {
29
+ const base = join(root, "openspec/changes");
30
+ const dirs = (abs) => {
31
+ if (!existsSync(abs))
32
+ return [];
33
+ return readdirSync(abs).filter((n) => statSync(join(abs, n)).isDirectory()).sort();
34
+ };
35
+ const folders = [];
36
+ for (const n of dirs(base))
37
+ if (n !== "archive")
38
+ folders.push([`openspec/changes/${n}`, n]);
39
+ // openspec archive prefixes the folder with a date: 2026-10-23-001-foundation
40
+ for (const n of dirs(join(base, "archive")))
41
+ folders.push([`openspec/changes/archive/${n}`, n.replace(/^\d{4}-\d{2}-\d{2}-/, "")]);
42
+ const seen = new Set();
43
+ const out = [];
44
+ for (const [rel, name] of folders.sort((a, b) => cmpTuple(a, b))) {
45
+ const key = changeKey(name);
46
+ if (seen.has(key))
47
+ out.push([rel, 1, "duplicate-change-key", key]);
48
+ seen.add(key);
49
+ }
50
+ return out;
51
+ }
52
+ /** Sorted, unique findings. The scan never reads .ctx/, so evidence adds no index entry. */
53
+ export function check(root, cfg, data, strict) {
54
+ const entries = data.entries;
55
+ const findings = data.problems.map((p) => [...p]);
56
+ findings.push(...duplicateChangeKeys(root));
57
+ const seen = new Set();
58
+ for (const e of entries) {
59
+ if (!SPEC_KINDS.has(e.kind))
60
+ continue;
61
+ // archived folders keep a copy of main specs. A MODIFIED or REMOVED block is a pending edit of one.
62
+ if (e.status === "archived" || e.status === "modified" || e.status === "removed")
63
+ continue;
64
+ if (seen.has(e.id))
65
+ findings.push([e.file, e.line, "duplicate-id", e.id]);
66
+ seen.add(e.id);
67
+ }
68
+ const known = new Set(entries.filter((e) => ["req", "scn", "adr", "task"].includes(e.kind)).map((e) => e.id));
69
+ const ids = canon(entries);
70
+ const retired = retiredIds(root);
71
+ const caps = new Set(cfg.ids.caps);
72
+ for (const e of entries) {
73
+ if ((e.kind === "req" || e.kind === "adr") && retired.has(e.id) && e.status !== "archived") {
74
+ findings.push([e.file, e.line, "reused-id", e.id]);
75
+ }
76
+ for (const r of e.refs) {
77
+ if (!known.has(r)) {
78
+ if (e.kind === "task" && !strict)
79
+ continue;
80
+ findings.push([e.file, e.line, e.kind === "task" ? "unknown-task-req" : "dangling-ref", r]);
81
+ }
82
+ else if (e.kind === "code" && (!ids.has(r) || ids.get(r).status === "archived")) {
83
+ // the tag points at a requirement that lives only in an archived change
84
+ findings.push([e.file, e.line, "code-tag-no-canonical", r]);
85
+ }
86
+ }
87
+ // an archived block is history. An archived REMOVED block has no scenario by design.
88
+ if (e.kind === "req" && e.status !== "removed" && e.status !== "archived" && !(e.scenarios ?? []).length) {
89
+ findings.push([e.file, e.line, "req-without-scn", e.id]);
90
+ }
91
+ if (caps.size && e.kind === "req" && e.status !== "archived") {
92
+ const m = REQ_CAP.exec(e.id);
93
+ if (m && !caps.has(m[1]))
94
+ findings.push([e.file, e.line, "unknown-cap", e.id]);
95
+ }
96
+ }
97
+ if (strict) {
98
+ const covered = new Set(entries.filter((e) => e.kind === "test").flatMap((e) => e.refs));
99
+ const named = new Set(data.scn_named ?? []);
100
+ // an ID that a REMOVED block of an active change names waits for archive, so it needs no test
101
+ const pending = new Set();
102
+ for (const e of entries) {
103
+ if (e.status !== "removed")
104
+ continue;
105
+ pending.add(e.id);
106
+ for (const s of e.scenarios ?? [])
107
+ pending.add(s);
108
+ }
109
+ const scnOf = new Map(entries.filter((e) => e.kind === "req").flatMap((e) => (e.scenarios ?? []).map((s) => [s, e.id])));
110
+ for (const e of entries) {
111
+ if (e.change !== "main" || e.status !== "active")
112
+ continue;
113
+ if (pending.has(e.id) || (e.kind === "scn" && pending.has(scnOf.get(e.id) ?? "")))
114
+ continue;
115
+ if (e.kind === "req" && !covered.has(e.id))
116
+ findings.push([e.file, e.line, "uncovered", e.id]);
117
+ if (e.kind === "scn" && !named.has(e.id))
118
+ findings.push([e.file, e.line, "scenario-untested", e.id]);
119
+ }
120
+ findings.push(...evidenceFindings(root, entries, listEvidence(root)));
121
+ }
122
+ const uniq = new Map();
123
+ for (const f of findings)
124
+ uniq.set(JSON.stringify(f), f);
125
+ return [...uniq.values()].sort((a, b) => cmpTuple(a, b)).map(([file, line, rule, id]) => ({ file, line, rule, id }));
126
+ }
@@ -0,0 +1,70 @@
1
+ // REQ-CHK-003 evidence rules of check --strict.
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { fileSha256 } from "../util/hash.js";
5
+ export function evidenceFindings(root, entries, evidence) {
6
+ const out = [];
7
+ const tasks = new Map();
8
+ for (const e of entries) {
9
+ if (e.kind !== "task")
10
+ continue;
11
+ const l = tasks.get(e.id) ?? [];
12
+ l.push(e);
13
+ tasks.set(e.id, l);
14
+ }
15
+ const have = new Set();
16
+ for (const rel of evidence) {
17
+ const parts = rel.split("/");
18
+ const id = `${parts[2]}/${parts[3].replace(/\.json$/, "")}`;
19
+ have.add(id);
20
+ const copies = tasks.get(id) ?? [];
21
+ const active = copies.find((t) => !t.file.includes("/archive/"));
22
+ const task = active ?? copies[0];
23
+ if (!task || task.status !== "done") {
24
+ out.push([rel, 1, "evidence-without-done", id]);
25
+ continue;
26
+ }
27
+ // archived changes are exempt from stale-evidence
28
+ if (!active)
29
+ continue;
30
+ let files = {};
31
+ try {
32
+ const ev = JSON.parse(readFileSync(join(root, rel), "utf8"));
33
+ files = ev.files ?? {};
34
+ }
35
+ catch {
36
+ out.push([rel, 1, "stale-evidence", `${id} ${rel}`]);
37
+ continue;
38
+ }
39
+ for (const [path, digest] of Object.entries(files)) {
40
+ if (fileSha256(join(root, path)) !== digest)
41
+ out.push([rel, 1, "stale-evidence", `${id} ${path}`]);
42
+ }
43
+ }
44
+ for (const e of entries) {
45
+ if (e.kind === "task" && e.status === "done" && !e.file.includes("/archive/") && !have.has(e.id)) {
46
+ out.push([e.file, e.line, "done-without-evidence", e.id]);
47
+ }
48
+ }
49
+ return out;
50
+ }
51
+ /** REQ-CHK-003 manual evidence for a task in an active change is a warning, not a finding. */
52
+ export function manualWarnings(root, entries, evidence) {
53
+ const active = new Set(entries.filter((e) => e.kind === "task" && !e.file.includes("/archive/") && e.change !== "main").map((e) => e.id));
54
+ const out = [];
55
+ for (const rel of evidence) {
56
+ const parts = rel.split("/");
57
+ const id = `${parts[2]}/${parts[3].replace(/\.json$/, "")}`;
58
+ if (!active.has(id))
59
+ continue;
60
+ try {
61
+ const ev = JSON.parse(readFileSync(join(root, rel), "utf8"));
62
+ if (ev.kind === "manual")
63
+ out.push(`manual-evidence ${id}`);
64
+ }
65
+ catch {
66
+ // a damaged file is a stale-evidence finding already
67
+ }
68
+ }
69
+ return out.sort();
70
+ }
@@ -0,0 +1,26 @@
1
+ // REQ-CHK-004 the rules of check and one fix hint per rule. The check summary prints the hint of each rule it found.
2
+ /** `hard` rules set exit 1. `advisory` rules print a warning and keep exit 0. */
3
+ export const RULES = {
4
+ "missing-id": { level: "hard", hint: "Add an ID to the header. Run specctx adopt --dry-run to see the plan." },
5
+ "scenario-id-mismatch": { level: "hard", hint: "Give the scenario the number of its requirement." },
6
+ "bad-ref": { level: "hard", hint: "Write only IDs on the Refs line, separated by commas." },
7
+ "task-without-req": { level: "hard", hint: "Cite a requirement in brackets on the task line, for example [REQ-ABC-001]." },
8
+ "task-without-verify": { level: "hard", hint: "Add Verify: and a command in backticks to the task line." },
9
+ "duplicate-id": { level: "hard", hint: "Give one of the two entries a new ID from specctx id new." },
10
+ "duplicate-change-key": { level: "hard", hint: "Rename one change folder, so that each key names one change." },
11
+ "reused-id": { level: "hard", hint: "The ID is retired. Get a new ID from specctx id new." },
12
+ "dangling-ref": { level: "hard", hint: "Fix the ID or remove the link. No entry has this ID." },
13
+ "unknown-task-req": { level: "hard", hint: "The task cites an ID that the index does not hold. Fix the ID on the task line." },
14
+ "req-without-scn": { level: "hard", hint: "Add a scenario under the requirement." },
15
+ "code-tag-no-canonical": { level: "hard", hint: "The tag names an archived requirement. Tag the current requirement." },
16
+ "unknown-cap": { level: "hard", hint: "Add the code to caps under [ids] in spec-ctx.toml." },
17
+ uncovered: { level: "hard", hint: "Add a test with a Covers tag for the requirement." },
18
+ "scenario-untested": { level: "hard", hint: "Name the scenario ID in a test." },
19
+ "done-without-evidence": { level: "hard", hint: "Run specctx task done, or specctx task backfill for old tasks." },
20
+ "stale-evidence": { level: "hard", hint: "Run specctx task reopen and then specctx task done." },
21
+ "evidence-without-done": { level: "hard", hint: "Run specctx task done, or remove the evidence file." },
22
+ "manual-evidence": { level: "advisory", hint: "Give the task a Verify command and run specctx task done again." },
23
+ };
24
+ export function hintOf(rule) {
25
+ return RULES[rule]?.hint ?? "Read the check rules in the check spec.";
26
+ }
@@ -0,0 +1,90 @@
1
+ // REQ-CLI-001 argument parser. It loads nothing else, so --version stays fast.
2
+ export const GLOBAL_FLAGS = { root: "string", json: "bool", help: "bool", version: "bool" };
3
+ /** Flags per command. A group command such as `task done` has a two word key. */
4
+ export const COMMAND_FLAGS = {
5
+ build: {},
6
+ status: {},
7
+ query: { kind: "string", change: "string", all: "bool", limit: "int" },
8
+ related: { depth: "int", all: "bool" },
9
+ bundle: { budget: "int", depth: "int", all: "bool" },
10
+ check: { strict: "bool" },
11
+ stats: { change: "string", write: "bool" },
12
+ gate: { strict: "bool", markdown: "string" },
13
+ "id new": { for: "string", count: "int" },
14
+ "id retire": {},
15
+ "task next": { change: "string" },
16
+ "task start": {},
17
+ "task verify": {},
18
+ "task done": { manual: "string" },
19
+ "task reopen": {},
20
+ "task backfill": { manual: "string", change: "string" },
21
+ "handoff write": {},
22
+ "handoff ask": {},
23
+ "handoff resolve": {},
24
+ adopt: { "dry-run": "bool" },
25
+ "baseline write": {},
26
+ "baseline prune": {},
27
+ init: { schema: "bool", skills: "bool", all: "bool", force: "bool" },
28
+ };
29
+ export const GROUPS = ["id", "task", "handoff", "baseline"];
30
+ export const COMMANDS = Object.keys(COMMAND_FLAGS);
31
+ export class UsageError extends Error {
32
+ }
33
+ const SHORT = { "-h": "help", "-V": "version" };
34
+ export function parseArgs(argv) {
35
+ const out = { command: null, positional: [], flags: {} };
36
+ const spec = () => ({ ...GLOBAL_FLAGS, ...(out.command ? COMMAND_FLAGS[out.command] ?? {} : {}) });
37
+ let onlyPositional = false;
38
+ const positional = (a) => {
39
+ if (out.command === null)
40
+ out.command = a;
41
+ else if (GROUPS.includes(out.command) && !out.positional.length && !COMMAND_FLAGS[out.command])
42
+ out.command = `${out.command} ${a}`;
43
+ else
44
+ out.positional.push(a);
45
+ };
46
+ for (let i = 0; i < argv.length; i++) {
47
+ const a = argv[i];
48
+ if (onlyPositional || !a.startsWith("-") || a === "-") {
49
+ positional(a);
50
+ continue;
51
+ }
52
+ if (a === "--") {
53
+ onlyPositional = true;
54
+ continue;
55
+ }
56
+ let name;
57
+ let value;
58
+ if (SHORT[a])
59
+ name = SHORT[a];
60
+ else if (a.startsWith("--")) {
61
+ const eq = a.indexOf("=");
62
+ name = eq < 0 ? a.slice(2) : a.slice(2, eq);
63
+ value = eq < 0 ? undefined : a.slice(eq + 1);
64
+ }
65
+ else
66
+ throw new UsageError(`unknown flag: ${a}`);
67
+ const kind = spec()[name];
68
+ if (!kind)
69
+ throw new UsageError(`unknown flag: --${name}`);
70
+ if (kind === "bool") {
71
+ if (value !== undefined)
72
+ throw new UsageError(`flag --${name} takes no value`);
73
+ out.flags[name] = true;
74
+ continue;
75
+ }
76
+ if (value === undefined) {
77
+ value = argv[++i];
78
+ if (value === undefined)
79
+ throw new UsageError(`flag --${name} needs a value`);
80
+ }
81
+ if (kind === "int") {
82
+ if (!/^-?\d+$/.test(value))
83
+ throw new UsageError(`flag --${name} needs an integer`);
84
+ out.flags[name] = Number(value);
85
+ }
86
+ else
87
+ out.flags[name] = value;
88
+ }
89
+ return out;
90
+ }