spec-ctx 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +298 -2
- package/action.yml +33 -0
- package/dist/adopt/config-write.js +59 -0
- package/dist/adopt/plan.js +276 -0
- package/dist/adopt/write.js +126 -0
- package/dist/assets.generated.js +14 -0
- package/dist/baseline/baseline.js +65 -0
- package/dist/check/check.js +126 -0
- package/dist/check/evidence.js +70 -0
- package/dist/check/hints.js +26 -0
- package/dist/cli/args.js +90 -0
- package/dist/cli/color.js +23 -0
- package/dist/cli/commands.js +278 -0
- package/dist/cli/errors.js +3 -0
- package/dist/cli/gate.js +187 -0
- package/dist/cli/help.js +230 -0
- package/dist/cli/root.js +32 -0
- package/dist/cli/status.js +116 -0
- package/dist/cli/workflow.js +285 -0
- package/dist/cli.js +14 -0
- package/dist/config.js +119 -0
- package/dist/graph/bundle.js +94 -0
- package/dist/graph/query.js +31 -0
- package/dist/graph/related.js +76 -0
- package/dist/handoff/handoff.js +148 -0
- package/dist/ids/ids.js +115 -0
- package/dist/index/build.js +57 -0
- package/dist/index/canonical.js +44 -0
- package/dist/index/scan.js +357 -0
- package/dist/index/types.js +2 -0
- package/dist/init/init.js +169 -0
- package/dist/lint/cli.js +253 -0
- package/dist/lint/py.js +152 -0
- package/dist/lint/selftest.js +246 -0
- package/dist/lint/ste.js +395 -0
- package/dist/lint/tells.js +116 -0
- package/dist/main.js +74 -0
- package/dist/stats/stats.js +147 -0
- package/dist/stats/write.js +26 -0
- package/dist/tasks/evidence.js +102 -0
- package/dist/tasks/tasks.js +105 -0
- package/dist/util/glob.js +43 -0
- package/dist/util/hash.js +14 -0
- package/dist/util/json.js +16 -0
- package/dist/util/pystr.js +67 -0
- package/dist/util/time.js +11 -0
- package/dist/verify/parse.js +58 -0
- package/dist/verify/run.js +42 -0
- package/dist/version.js +2 -0
- package/package.json +50 -4
- package/schemas/spec-driven-ids/README.md +31 -0
- package/schemas/spec-driven-ids/config-fragment.yaml +25 -0
- package/schemas/spec-driven-ids/schema.yaml +301 -0
- package/schemas/spec-driven-ids/templates/design.md +22 -0
- package/schemas/spec-driven-ids/templates/proposal.md +34 -0
- package/schemas/spec-driven-ids/templates/spec.md +15 -0
- package/schemas/spec-driven-ids/templates/tasks.md +10 -0
- package/skills/specctx-bootstrap/SKILL.md +27 -0
- package/skills/specctx-done/SKILL.md +25 -0
- package/skills/specctx-next/SKILL.md +23 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
// REQ-ADOPT-001 REQ-ADOPT-004 the adopt plan: which header gets which ID. It reads files and writes nothing.
|
|
2
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { UsageExit } from "../cli/errors.js";
|
|
5
|
+
import { retiredAll } from "../ids/ids.js";
|
|
6
|
+
import { cmpStr, splitLines } from "../util/pystr.js";
|
|
7
|
+
const REQ_HDR = /^### Requirement:[ \t]*(.*)$/s;
|
|
8
|
+
const SCN_HDR = /^#### Scenario:[ \t]*(.*)$/s;
|
|
9
|
+
const HAS_REQ = /^REQ-([A-Z]{2,6})-(\d{3})(?:[ \t]|$)/;
|
|
10
|
+
const HAS_SCN = /^SCN-([A-Z]{2,6})-(\d{3})-(\d+)(?:[ \t]|$)/;
|
|
11
|
+
const RENAME = /^- (FROM|TO):[ \t]*`### Requirement:[ \t]*(.*)`[ \t]*$/s;
|
|
12
|
+
function listSpecs(root, base) {
|
|
13
|
+
const out = [];
|
|
14
|
+
const walk = (abs, rel) => {
|
|
15
|
+
if (!existsSync(abs))
|
|
16
|
+
return;
|
|
17
|
+
for (const n of readdirSync(abs).sort(cmpStr)) {
|
|
18
|
+
const p = join(abs, n);
|
|
19
|
+
const r = `${rel}/${n}`;
|
|
20
|
+
if (statSync(p).isDirectory())
|
|
21
|
+
walk(p, r);
|
|
22
|
+
else if (n === "spec.md")
|
|
23
|
+
out.push(r);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
walk(join(root, base), base);
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
function splitId(text, re) {
|
|
30
|
+
const m = re.exec(text);
|
|
31
|
+
if (!m)
|
|
32
|
+
return { id: null, name: text.trim() };
|
|
33
|
+
const id = m[0].trim();
|
|
34
|
+
return { id, name: text.slice(m[0].length).trim() };
|
|
35
|
+
}
|
|
36
|
+
function parse(root, rel, cap) {
|
|
37
|
+
const lines = splitLines(readFileSync(join(root, rel)).toString("utf8"));
|
|
38
|
+
const blocks = [];
|
|
39
|
+
const renames = [];
|
|
40
|
+
let section = "";
|
|
41
|
+
let fence = false;
|
|
42
|
+
let req;
|
|
43
|
+
let from = null;
|
|
44
|
+
lines.forEach((line, i) => {
|
|
45
|
+
const t = line.trim();
|
|
46
|
+
if (t.startsWith("```") || t.startsWith("~~~"))
|
|
47
|
+
fence = !fence;
|
|
48
|
+
if (fence)
|
|
49
|
+
return;
|
|
50
|
+
if (line.startsWith("## ")) {
|
|
51
|
+
section = line.slice(3).trim().split(" ")[0].toUpperCase();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
let m = REQ_HDR.exec(line);
|
|
55
|
+
if (m) {
|
|
56
|
+
const { id, name } = splitId(m[1], HAS_REQ);
|
|
57
|
+
req = { kind: "req", line: i + 1, raw: line, name, id, section };
|
|
58
|
+
blocks.push(req);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
m = SCN_HDR.exec(line);
|
|
62
|
+
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 });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
m = RENAME.exec(line);
|
|
68
|
+
if (m && section === "RENAMED") {
|
|
69
|
+
const { id, name } = splitId(m[2], HAS_REQ);
|
|
70
|
+
const item = { line: i + 1, name, id, raw: line };
|
|
71
|
+
if (m[1] === "FROM")
|
|
72
|
+
from = item;
|
|
73
|
+
else if (from) {
|
|
74
|
+
renames.push({ from, to: item });
|
|
75
|
+
from = null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
return { rel, cap, lines, blocks, renames };
|
|
80
|
+
}
|
|
81
|
+
/** REQ-ADOPT-004 candidate codes in order: the base code, then two and three letters of the first segment, then a suffix letter. */
|
|
82
|
+
export function codeCandidates(path) {
|
|
83
|
+
const segs = path.split(/[/-]/).map((x) => x.toUpperCase().replace(/[^A-Z]/g, "")).filter(Boolean);
|
|
84
|
+
if (!segs.length)
|
|
85
|
+
return [];
|
|
86
|
+
const rest = segs.slice(1).map((x) => x[0]).join("");
|
|
87
|
+
const base = segs.length === 1 ? segs[0].slice(0, 3) : segs.map((x) => x[0]).join("").slice(0, 6);
|
|
88
|
+
const out = [base, (segs[0].slice(0, 2) + rest).slice(0, 6), (segs[0].slice(0, 3) + rest).slice(0, 6)];
|
|
89
|
+
for (let c = 66; c <= 90; c++)
|
|
90
|
+
out.push((base.slice(0, 5) + String.fromCharCode(c)));
|
|
91
|
+
return [...new Set(out.filter((x) => /^[A-Z]{2,6}$/.test(x)))];
|
|
92
|
+
}
|
|
93
|
+
export function deriveCode(path, taken = new Set()) {
|
|
94
|
+
const code = codeCandidates(path).find((c) => !taken.has(c));
|
|
95
|
+
if (!code)
|
|
96
|
+
throw new UsageExit(`no free capability code for ${path}. Add one under [ids.paths] in spec-ctx.toml`);
|
|
97
|
+
return code;
|
|
98
|
+
}
|
|
99
|
+
export function planAdopt(root, cfg) {
|
|
100
|
+
const mains = listSpecs(root, "openspec/specs");
|
|
101
|
+
const changesDir = join(root, "openspec/changes");
|
|
102
|
+
const deltas = [];
|
|
103
|
+
if (existsSync(changesDir)) {
|
|
104
|
+
for (const c of readdirSync(changesDir).sort(cmpStr)) {
|
|
105
|
+
if (c === "archive" || !statSync(join(changesDir, c)).isDirectory())
|
|
106
|
+
continue;
|
|
107
|
+
deltas.push(...listSpecs(root, `openspec/changes/${c}/specs`));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const capOf = (rel) => rel.replace(/^openspec\/(changes\/[^/]+\/)?specs\//, "").replace(/\/spec\.md$/, "");
|
|
111
|
+
const files = [...mains, ...deltas].map((r) => parse(root, r, capOf(r)));
|
|
112
|
+
const mainFiles = files.filter((f) => f.rel.startsWith("openspec/specs/"));
|
|
113
|
+
const deltaFiles = files.filter((f) => !f.rel.startsWith("openspec/specs/"));
|
|
114
|
+
// the code of each capability: config paths, then an ID already in its spec, then derived
|
|
115
|
+
const paths = cfg.ids.paths ?? {};
|
|
116
|
+
const codeOfPath = new Map();
|
|
117
|
+
for (const [code, p] of Object.entries(paths))
|
|
118
|
+
codeOfPath.set(p, code);
|
|
119
|
+
// on a collision the shorter path keeps the base code: user-auth keeps UA, upload-api takes UPA
|
|
120
|
+
const capList = [...new Set(files.map((f) => f.cap))].sort((a, b) => a.length - b.length || cmpStr(a, b));
|
|
121
|
+
const taken = new Set([...cfg.ids.caps, ...codeOfPath.values()]);
|
|
122
|
+
const caps = [];
|
|
123
|
+
for (const cap of capList) {
|
|
124
|
+
let code = codeOfPath.get(cap);
|
|
125
|
+
let derived = false;
|
|
126
|
+
if (!code) {
|
|
127
|
+
const existing = files.filter((f) => f.cap === cap).flatMap((f) => f.blocks).find((b) => b.kind === "req" && b.id);
|
|
128
|
+
if (existing)
|
|
129
|
+
code = HAS_REQ.exec(existing.id)[1];
|
|
130
|
+
}
|
|
131
|
+
if (!code) {
|
|
132
|
+
code = deriveCode(cap, taken);
|
|
133
|
+
derived = true;
|
|
134
|
+
taken.add(code);
|
|
135
|
+
}
|
|
136
|
+
caps.push({ code, path: cap, derived });
|
|
137
|
+
}
|
|
138
|
+
caps.sort((a, b) => cmpStr(a.path, b.path));
|
|
139
|
+
const codeFor = new Map(caps.map((c) => [c.path, c.code]));
|
|
140
|
+
// next free number per code: every ID in the tree, the archive, and the retired list
|
|
141
|
+
const used = new Map();
|
|
142
|
+
const bump = (code, n) => {
|
|
143
|
+
used.set(code, Math.max(used.get(code) ?? 0, n));
|
|
144
|
+
};
|
|
145
|
+
const allIds = [...retiredAll(root)];
|
|
146
|
+
for (const f of files)
|
|
147
|
+
for (const b of f.blocks)
|
|
148
|
+
if (b.id)
|
|
149
|
+
allIds.push(b.id);
|
|
150
|
+
const archive = join(root, "openspec/changes/archive");
|
|
151
|
+
if (existsSync(archive)) {
|
|
152
|
+
const walk = (abs) => {
|
|
153
|
+
for (const n of readdirSync(abs)) {
|
|
154
|
+
const p = join(abs, n);
|
|
155
|
+
if (statSync(p).isDirectory())
|
|
156
|
+
walk(p);
|
|
157
|
+
else if (n.endsWith(".md"))
|
|
158
|
+
for (const m of readFileSync(p, "utf8").matchAll(/\bREQ-([A-Z]{2,6})-(\d{3})\b/g))
|
|
159
|
+
allIds.push(m[0]);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
walk(archive);
|
|
163
|
+
}
|
|
164
|
+
for (const id of allIds) {
|
|
165
|
+
const m = /^REQ-([A-Z]{2,6})-(\d{3})$/.exec(id);
|
|
166
|
+
if (m)
|
|
167
|
+
bump(m[1], Number(m[2]));
|
|
168
|
+
}
|
|
169
|
+
const nextReq = (code) => {
|
|
170
|
+
const n = (used.get(code) ?? 0) + 1;
|
|
171
|
+
if (n > 999)
|
|
172
|
+
throw new UsageExit(`no free REQ number for ${code}`);
|
|
173
|
+
used.set(code, n);
|
|
174
|
+
return `REQ-${code}-${String(n).padStart(3, "0")}`;
|
|
175
|
+
};
|
|
176
|
+
const scnUsed = new Map();
|
|
177
|
+
for (const f of files)
|
|
178
|
+
for (const b of f.blocks) {
|
|
179
|
+
if (b.kind === "scn" && b.id) {
|
|
180
|
+
const m = HAS_SCN.exec(b.id + " ");
|
|
181
|
+
const key = `REQ-${m[1]}-${m[2]}`;
|
|
182
|
+
scnUsed.set(key, Math.max(scnUsed.get(key) ?? 0, Number(m[3])));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const nextScn = (reqId) => {
|
|
186
|
+
const n = (scnUsed.get(reqId) ?? 0) + 1;
|
|
187
|
+
scnUsed.set(reqId, n);
|
|
188
|
+
return `SCN-${reqId.slice(4)}-${n}`;
|
|
189
|
+
};
|
|
190
|
+
const edits = [];
|
|
191
|
+
const unmatched = [];
|
|
192
|
+
const reqHeader = (id, name) => `### Requirement: ${id} ${name}`;
|
|
193
|
+
const scnHeader = (id, name) => `#### Scenario: ${id} ${name}`;
|
|
194
|
+
// main spec: requirement id by name, and scenario id by requirement and name
|
|
195
|
+
const mainReq = new Map();
|
|
196
|
+
const mainScn = new Map();
|
|
197
|
+
for (const f of mainFiles) {
|
|
198
|
+
const code = codeFor.get(f.cap);
|
|
199
|
+
const seen = new Set();
|
|
200
|
+
for (const b of f.blocks) {
|
|
201
|
+
if (b.kind !== "req")
|
|
202
|
+
continue;
|
|
203
|
+
if (seen.has(b.name))
|
|
204
|
+
throw new UsageExit(`${f.rel}:${b.line} repeats the header "${b.name}". Give one of the two requirements another name, then run adopt again`);
|
|
205
|
+
seen.add(b.name);
|
|
206
|
+
}
|
|
207
|
+
for (const b of f.blocks) {
|
|
208
|
+
if (b.kind === "req") {
|
|
209
|
+
const id = b.id ?? nextReq(code);
|
|
210
|
+
if (!b.id)
|
|
211
|
+
edits.push({ file: f.rel, line: b.line, old: b.raw, new: reqHeader(id, b.name) });
|
|
212
|
+
mainReq.set(`${f.cap}\u0000${b.name}`, id);
|
|
213
|
+
b.id = id;
|
|
214
|
+
}
|
|
215
|
+
else if (b.req?.id) {
|
|
216
|
+
const id = b.id ?? nextScn(b.req.id);
|
|
217
|
+
if (!b.id)
|
|
218
|
+
edits.push({ file: f.rel, line: b.line, old: b.raw, new: scnHeader(id, b.name) });
|
|
219
|
+
mainScn.set(`${b.req.id}\u0000${b.name}`, id);
|
|
220
|
+
b.id = id;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// deltas: the same ID as the main header, through text match or the RENAMED mapping
|
|
225
|
+
for (const f of deltaFiles) {
|
|
226
|
+
const code = codeFor.get(f.cap);
|
|
227
|
+
const renamedTo = new Map();
|
|
228
|
+
for (const r of f.renames) {
|
|
229
|
+
const id = r.from.id ?? mainReq.get(`${f.cap}\u0000${r.from.name}`) ?? null;
|
|
230
|
+
if (!id) {
|
|
231
|
+
unmatched.push({ file: f.rel, line: r.from.line, header: r.from.raw });
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
renamedTo.set(r.to.name, id);
|
|
235
|
+
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)}\`` });
|
|
237
|
+
if (!r.to.id)
|
|
238
|
+
edits.push({ file: f.rel, line: r.to.line, old: r.to.raw, new: `- TO: \`${reqHeader(id, r.to.name)}\`` });
|
|
239
|
+
}
|
|
240
|
+
for (const b of f.blocks) {
|
|
241
|
+
if (b.kind === "req") {
|
|
242
|
+
if (b.id)
|
|
243
|
+
continue;
|
|
244
|
+
let id = null;
|
|
245
|
+
if (b.section === "ADDED")
|
|
246
|
+
id = nextReq(code);
|
|
247
|
+
else
|
|
248
|
+
id = renamedTo.get(b.name) ?? mainReq.get(`${f.cap}\u0000${b.name}`) ?? null;
|
|
249
|
+
if (!id) {
|
|
250
|
+
unmatched.push({ file: f.rel, line: b.line, header: b.raw });
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
edits.push({ file: f.rel, line: b.line, old: b.raw, new: reqHeader(id, b.name) });
|
|
254
|
+
b.id = id;
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
if (b.id || !b.req?.id)
|
|
258
|
+
continue;
|
|
259
|
+
const id = mainScn.get(`${b.req.id}\u0000${b.name}`) ?? nextScn(b.req.id);
|
|
260
|
+
edits.push({ file: f.rel, line: b.line, old: b.raw, new: scnHeader(id, b.name) });
|
|
261
|
+
b.id = id;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
edits.sort((a, b) => cmpStr(a.file, b.file) || a.line - b.line);
|
|
266
|
+
return { edits, caps, unmatched };
|
|
267
|
+
}
|
|
268
|
+
export function planLines(p) {
|
|
269
|
+
const out = p.edits.map((e) => `${e.file}:${e.line} ${e.old} -> ${e.new}`);
|
|
270
|
+
const derived = p.caps.filter((c) => c.derived);
|
|
271
|
+
if (derived.length)
|
|
272
|
+
out.push(...derived.map((c) => `cap: ${c.code} ${c.path}`));
|
|
273
|
+
for (const u of p.unmatched)
|
|
274
|
+
out.push(`unmatched: ${u.file}:${u.line} ${u.header}`);
|
|
275
|
+
return out;
|
|
276
|
+
}
|
|
@@ -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": "IyBzcGVjLWRyaXZlbi1pZHMKCkEgZm9yayBvZiB0aGUgT3BlblNwZWMgYHNwZWMtZHJpdmVuYCBzY2hlbWEuIEl0IGFkZHMgc3RhYmxlIElEcyBmcm9tIHNwZWMtY3R4IHRvIGV2ZXJ5IGFydGlmYWN0LgoKLSBUaGUgc3BlYyB0ZW1wbGF0ZSBzdGFydHMgZWFjaCByZXF1aXJlbWVudCB3aXRoIGBSRVEtPENBUD4tTk5OYCBhbmQgZWFjaCBzY2VuYXJpbyB3aXRoIGBTQ04tPENBUD4tTk5OLTxuPmAuCi0gVGhlIGluc3RydWN0aW9ucyB0ZWxsIHRoZSBhZ2VudCB0byBnZXQgSURzIGZyb20gYHNwZWNjdHggaWQgbmV3YCBhbmQgdG8gbGluayBJRHMgd2l0aCBgUmVmczpgIGxpbmVzLgotIFRoZSB0YXNrcyB0ZW1wbGF0ZSBjaXRlcyBSRVEgSURzIGFuZCBwdXRzIHRoZSBgVmVyaWZ5OmAgY29tbWFuZCBpbiBhIGNvZGUgc3Bhbiwgc28gYHNwZWNjdHggdGFzayBkb25lYCBjYW4gcnVuIGl0LgotIGBjb25maWctZnJhZ21lbnQueWFtbGAgaG9sZHMgdGhlIGBydWxlc2AgYW5kIGBvcGVyYXRpb25zYCBmb3IgYG9wZW5zcGVjL2NvbmZpZy55YW1sYC4KCiMjIEluc3RhbGwgYnkgY29weQoKLSBDbG9uZSB0aGUgcmVwb3NpdG9yeTogYGdpdCBjbG9uZSBodHRwczovL2dpdGh1Yi5jb20vc29ucWgvc3BlYy1jdHhgLgotIENvcHkgYHNjaGVtYXMvc3BlYy1kcml2ZW4taWRzYCB0byBgb3BlbnNwZWMvc2NoZW1hcy9zcGVjLWRyaXZlbi1pZHNgIGluIHlvdXIgcHJvamVjdC4KLSBTZXQgYHNjaGVtYTogc3BlYy1kcml2ZW4taWRzYCBpbiBgb3BlbnNwZWMvY29uZmlnLnlhbWxgLgotIE1lcmdlIGBjb25maWctZnJhZ21lbnQueWFtbGAgaW50byBgb3BlbnNwZWMvY29uZmlnLnlhbWxgIGJ5IGhhbmQuIEFkZCBpdHMgYHJ1bGVzYCBhbmQgYG9wZXJhdGlvbnNgIGtleXMuIEtlZXAgZXZlcnkgaXRlbSBxdW90ZWQuCi0gUnVuIGBvcGVuc3BlYyBzY2hlbWEgdmFsaWRhdGUgc3BlYy1kcml2ZW4taWRzYC4KCiMjIEluc3RhbGwgd2l0aCBpbml0IC0tc2NoZW1hCgpgYGBiYXNoCm5weCAteSBzcGVjLWN0eCBpbml0IC0tc2NoZW1hCmBgYAoKLSBUaGUgY29tbWFuZCBjb3BpZXMgdGhpcyBmb2xkZXIgdG8gYG9wZW5zcGVjL3NjaGVtYXMvc3BlYy1kcml2ZW4taWRzL2AuCi0gSXQgc2V0cyBgc2NoZW1hOiBzcGVjLWRyaXZlbi1pZHNgIGluIGBvcGVuc3BlYy9jb25maWcueWFtbGAuCi0gSXQgYWRkcyB0aGUgYHJ1bGVzYCBhbmQgYG9wZXJhdGlvbnNgIGtleXMgdGhhdCB0aGUgY29uZmlnIGRvZXMgbm90IGhhdmUgeWV0LiBJdCBsZWF2ZXMgb3RoZXIga2V5cyBhcyB0aGV5IGFyZS4KLSBJdCBxdW90ZXMgYSBsaXN0IGl0ZW0gdGhhdCBZQU1MIHdvdWxkIHJlYWQgYXMgYSBtYXBwaW5nLgoKIyMgVmVyc2lvbgoKRm9ya2VkIGZyb20gT3BlblNwZWMgMS4xNC4zIHdpdGggYG9wZW5zcGVjIHNjaGVtYSBmb3JrIHNwZWMtZHJpdmVuIHNwZWMtZHJpdmVuLWlkc2AuIENJIHZhbGlkYXRlcyBpdCBhZ2FpbnN0IHRoYXQgdmVyc2lvbi4K",
|
|
5
|
+
"schemas/spec-driven-ids/config-fragment.yaml": "IyBzcGVjLWN0eCBmcmFnbWVudCBmb3Igb3BlbnNwZWMvY29uZmlnLnlhbWwuIHNwZWNjdHggaW5pdCAtLXNjaGVtYSBhZGRzIGVhY2ggdG9wLWxldmVsIGtleQojIHRoYXQgdGhlIHByb2plY3QgY29uZmlnIGRvZXMgbm90IGhhdmUgeWV0LiBJdCBuZXZlciBjaGFuZ2VzIGEga2V5IHRoYXQgZXhpc3RzLgpydWxlczoKICBzcGVjczoKICAgIC0gJ0V2ZXJ5IHJlcXVpcmVtZW50IGhlYWRlciBzdGFydHMgd2l0aCBpdHMgSUQsIGUuZy4gIiMjIyBSZXF1aXJlbWVudDogUkVRLUFCQy0wMDEgTmFtZSIuIEdldCB0aGUgSUQgZnJvbSBgc3BlY2N0eCBpZCBuZXcgUkVRIEFCQ2AuJwogICAgLSAnRXZlcnkgc2NlbmFyaW8gaGVhZGVyIHN0YXJ0cyB3aXRoIGl0cyBJRCwgZS5nLiAiIyMjIyBTY2VuYXJpbzogU0NOLUFCQy0wMDEtMSBOYW1lIi4nCiAgICAtICdBIHJlcXVpcmVtZW50IG1heSBlbmQgd2l0aCBhIGxpbmUgIlJlZnM6IFJFUS1BQkMtMDAyLCBBRFItMDAxIiB0byBsaW5rIHJlbGF0ZWQgSURzLicKICAgIC0gJ1JlcXVpcmVtZW50IHRleHQgc3RheXMgdW5kZXIgNTAwIGNoYXJhY3RlcnMuIE1vdmUgZGV0YWlsIGludG8gc2NlbmFyaW9zLicKICBkZXNpZ246CiAgICAtICdFYWNoIGRlY2lzaW9uIGlzIG9uZSBidWxsZXQgdW5kZXIgIiMjIERlY2lzaW9ucyIuIEFkZCBuZXcgZGVjaXNpb25zIGF0IHRoZSBlbmQuJwogIHRhc2tzOgogICAgLSAnRXZlcnkgdGFzayBjaXRlcyBhdCBsZWFzdCBvbmUgUkVRIElEIGluIHNxdWFyZSBicmFja2V0cywgZS5nLiAiW1JFUS1BQkMtMDAxXSIuJwogICAgLSAnRXZlcnkgdGFzayBzdGF0ZXMgaXRzIHZlcmlmaWNhdGlvbiBhcyBhIGNvbW1hbmQgaW4gYSBjb2RlIHNwYW4gYWZ0ZXIgIlZlcmlmeToiLicKCm9wZXJhdGlvbnM6CiAgYXBwbHk6CiAgICBndWlkYW5jZToKICAgICAgLSAnU3RhcnQgZWFjaCB0YXNrIHdpdGggYHNwZWNjdHggdGFzayBzdGFydCA8aWQ+YCBhbmQgcmVhZCB0aGUgYnVuZGxlIGl0IHByaW50cyBiZWZvcmUgeW91IHJlYWQgc3BlY3MuJwogICAgICAtICdUYWcgY29kZSB3aXRoIHRoZSBSRVEgSUQgaXQgaW1wbGVtZW50cyAoYSBjb21tZW50IGAvLyBSRVEtQUJDLTAwMWApLiBUYWcgdGVzdHMgd2l0aCBgLy8gQ292ZXJzOiBSRVEtQUJDLTAwMWAuJwogICAgICAtICdDbG9zZSBlYWNoIHRhc2sgd2l0aCBgc3BlY2N0eCB0YXNrIGRvbmUgPGlkPmAuIE5ldmVyIHRpY2sgYSBjaGVja2JveCBieSBoYW5kLicKICAgICAgLSAnUnVuIGBzcGVjY3R4IGNoZWNrIC0tc3RyaWN0YCBhZnRlciBlYWNoIHRhc2sgZ3JvdXAuJwogIGFyY2hpdmU6CiAgICBndWlkYW5jZToKICAgICAgLSAnUnVuIGBzcGVjY3R4IGNoZWNrIC0tc3RyaWN0YCBhbmQgYG9wZW5zcGVjIHZhbGlkYXRlIC0tYWxsIC0tc3RyaWN0YC4gQm90aCBtdXN0IHBhc3MgYmVmb3JlIGFyY2hpdmUuJwogICAgICAtICdSdW4gYG9wZW5zcGVjIHZhbGlkYXRlIC0tYWxsIC0tc3RyaWN0YCBhZ2FpbiBhZnRlciBhcmNoaXZlIGFuZCBiZWZvcmUgcHVzaC4nCg==",
|
|
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+Lzx0YXNrPi5qc29uYCBhbmQgc2V0cyBgW3hdYCBvbiB0aGUgdGFzayBsaW5lLiBOb3RoaW5nIGVsc2UgaW4gYHRhc2tzLm1kYCBjaGFuZ2VzLgozLiBPbiBhIGZhaWx1cmUgaXQgcHJpbnRzIHRoZSBsYXN0IDIwIGxpbmVzIG9mIG91dHB1dCBhbmQgdGhlbiB0aGUgbGluZSBgVmVyaWZ5IGZhaWxlZCAoZXhpdCA8bj4pLiBUaGUgdGFzayBzdGF5cyBvcGVuLmAgSXQgZXhpdHMgMS4gRml4IHRoZSBjYXVzZSBhbmQgcnVuIHN0ZXAgMSBhZ2Fpbi4KNC4gSWYgdGhlIHRhc2sgaGFzIHByb3NlIGFmdGVyIGBWZXJpZnk6YCBhbmQgbm8gY29tbWFuZCwgcnVuIGBzcGVjY3R4IHRhc2sgZG9uZSA8dGFzaz4gLS1tYW51YWwgIjx3aGF0IHlvdSBjaGVja2VkPiJgLgo1LiBBZnRlciB0aGUgbGFzdCB0YXNrIG9mIGEgZ3JvdXAsIHJ1biBgc3BlY2N0eCBjaGVjayAtLXN0cmljdGAuIEZpeCBldmVyeSBmaW5kaW5nIGJlZm9yZSB0aGUgbmV4dCBncm91cC4KNi4gUnVuIGBzcGVjY3R4IGhhbmRvZmYgd3JpdGVgLiBJdCB3cml0ZXMgYC5jdHgvaGFuZG9mZi5tZGAgZnJvbSB0aGUgaW5kZXgsIHRoZSBldmlkZW5jZSwgYW5kIHRoZSBvcGVuIHF1ZXN0aW9ucy4KNy4gUnVuIGBzcGVjY3R4IHN0YXRzIC0tY2hhbmdlIDxjaGFuZ2U+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
|
+
}
|