@spec-engine/spec-engine 0.7.3 → 0.8.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/dist/docs/agent-reference/index.html +1 -1
- package/dist/docs/checks/index.html +11 -1
- package/dist/docs/commands/index.html +4 -4
- package/dist/docs/format/index.html +5 -1
- package/dist/docs/pagefind/fragment/en_24f76b6.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_2ee687a.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_4bd9de6.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_9cc1878.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_9cfc306.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_c3abf3d.pf_fragment +0 -0
- package/dist/docs/pagefind/index/en_59c3abf.pf_index +0 -0
- package/dist/docs/pagefind/index/en_a630d23.pf_index +0 -0
- package/dist/docs/pagefind/pagefind-entry.json +1 -1
- package/dist/docs/pagefind/pagefind.en_ced49c317e.pf_meta +0 -0
- package/dist/docs/platform-mapping/index.html +2 -2
- package/dist/docs/tags/index.html +47 -1
- package/dist/impl.js +408 -221
- package/package.json +1 -1
- package/dist/docs/pagefind/fragment/en_70e8a6f.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_907a67d.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_b2867b9.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_de9d958.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_e616a50.pf_fragment +0 -0
- package/dist/docs/pagefind/fragment/en_f77a622.pf_fragment +0 -0
- package/dist/docs/pagefind/index/en_19cbfc6.pf_index +0 -0
- package/dist/docs/pagefind/index/en_4be71c4.pf_index +0 -0
- package/dist/docs/pagefind/pagefind.en_f697eb692e.pf_meta +0 -0
package/dist/impl.js
CHANGED
|
@@ -20,7 +20,7 @@ var package_default;
|
|
|
20
20
|
var init_package = __esm(() => {
|
|
21
21
|
package_default = {
|
|
22
22
|
name: "@spec-engine/spec-engine",
|
|
23
|
-
version: "0.
|
|
23
|
+
version: "0.8.1",
|
|
24
24
|
description: "Cross-repo spec engine \u2014 durable requirements with permanent IDs, bound to code via @spec tags, checked in CI. Ships the spec CLI, the local coverage webapp (spec serve), and offline docs (spec docs).",
|
|
25
25
|
license: "MIT",
|
|
26
26
|
type: "module",
|
|
@@ -73,6 +73,31 @@ var init_package = __esm(() => {
|
|
|
73
73
|
};
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
+
// src/stdout.ts
|
|
77
|
+
import { format } from "util";
|
|
78
|
+
function isEpipe(e) {
|
|
79
|
+
return typeof e === "object" && e !== null && "code" in e && (e.code === "EPIPE" || e.code === "ERR_STREAM_DESTROYED");
|
|
80
|
+
}
|
|
81
|
+
function routeStdout() {
|
|
82
|
+
process.stdout.on("error", (e) => {
|
|
83
|
+
if (!isEpipe(e))
|
|
84
|
+
throw e;
|
|
85
|
+
});
|
|
86
|
+
console.log = (...args) => {
|
|
87
|
+
const text = `${format(...args)}
|
|
88
|
+
`;
|
|
89
|
+
lastWrite = new Promise((written) => process.stdout.write(text, () => written()));
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
async function exit(code) {
|
|
93
|
+
await lastWrite;
|
|
94
|
+
process.exit(code);
|
|
95
|
+
}
|
|
96
|
+
var lastWrite;
|
|
97
|
+
var init_stdout = __esm(() => {
|
|
98
|
+
lastWrite = Promise.resolve();
|
|
99
|
+
});
|
|
100
|
+
|
|
76
101
|
// ../shared/src/diagnostics.ts
|
|
77
102
|
var DiagnosticCode, NotASpecPlatformError, InvalidDomainFilesError;
|
|
78
103
|
var init_diagnostics = __esm(() => {
|
|
@@ -91,6 +116,8 @@ var init_diagnostics = __esm(() => {
|
|
|
91
116
|
DRIFT: "DRIFT",
|
|
92
117
|
NO_SPEC_CONFIG: "NO_SPEC_CONFIG",
|
|
93
118
|
BROKEN_FILE_REF: "BROKEN_FILE_REF",
|
|
119
|
+
UNIMPLEMENTED_IN_FILE: "UNIMPLEMENTED_IN_FILE",
|
|
120
|
+
UNVERIFIED_IN_FILE: "UNVERIFIED_IN_FILE",
|
|
94
121
|
BROKEN_RELATES: "BROKEN_RELATES",
|
|
95
122
|
RELATES_SUPERSEDED: "RELATES_SUPERSEDED",
|
|
96
123
|
CYCLIC_SUPERSEDE: "CYCLIC_SUPERSEDE",
|
|
@@ -213,6 +240,7 @@ function orderDomain(d) {
|
|
|
213
240
|
supersededBy: r.supersededBy ?? null,
|
|
214
241
|
relates: r.relates,
|
|
215
242
|
livesIn: r.livesIn,
|
|
243
|
+
verifiedIn: r.verifiedIn,
|
|
216
244
|
issues: r.issues.map((iss) => ({ role: iss.role, id: iss.id })),
|
|
217
245
|
changedAtVersion: r.changedAtVersion,
|
|
218
246
|
supersededAtVersion: r.supersededAtVersion,
|
|
@@ -351,6 +379,7 @@ var init_domain = __esm(() => {
|
|
|
351
379
|
supersededBy: z.string().nullable().optional(),
|
|
352
380
|
relates: z.array(z.string()).optional().default([]),
|
|
353
381
|
livesIn: z.array(z.string()).optional().default([]),
|
|
382
|
+
verifiedIn: z.array(z.string()).optional().default([]),
|
|
354
383
|
issues: z.array(SpecIssueSchema).optional().default([]),
|
|
355
384
|
changedAtVersion: z.number().int().optional(),
|
|
356
385
|
supersededAtVersion: z.number().int().optional(),
|
|
@@ -1106,6 +1135,12 @@ function isPathIgnored(path, extra = []) {
|
|
|
1106
1135
|
const anchored = `/${path}`;
|
|
1107
1136
|
return IGNORE_SUBSTR.some((t) => anchored.includes(`/${t}`)) || extra.some((t) => path.startsWith(t.endsWith("/") ? t : `${t}/`));
|
|
1108
1137
|
}
|
|
1138
|
+
function isHiddenEntry(name) {
|
|
1139
|
+
return name.startsWith(".") && name !== ".github";
|
|
1140
|
+
}
|
|
1141
|
+
function isHiddenPath(rel) {
|
|
1142
|
+
return rel.split("/").some(isHiddenEntry);
|
|
1143
|
+
}
|
|
1109
1144
|
async function walkInto(rootDir, relDir, keep, extraIgnore, out) {
|
|
1110
1145
|
const absDir = relDir ? join2(rootDir, relDir) : rootDir;
|
|
1111
1146
|
let entries;
|
|
@@ -1115,7 +1150,7 @@ async function walkInto(rootDir, relDir, keep, extraIgnore, out) {
|
|
|
1115
1150
|
return;
|
|
1116
1151
|
}
|
|
1117
1152
|
for (const ent of entries) {
|
|
1118
|
-
if (ent.name
|
|
1153
|
+
if (isHiddenEntry(ent.name))
|
|
1119
1154
|
continue;
|
|
1120
1155
|
const rel = relDir ? `${relDir}/${ent.name}` : ent.name;
|
|
1121
1156
|
if (ent.isDirectory()) {
|
|
@@ -1147,8 +1182,11 @@ async function findDomainJsonFiles(canonicalDir) {
|
|
|
1147
1182
|
async function findCodeFiles(repoDir, exts = DEFAULT_EXTS, extraIgnore = []) {
|
|
1148
1183
|
return walkFiles(repoDir, (rel) => isTagSource(rel, exts), extraIgnore);
|
|
1149
1184
|
}
|
|
1185
|
+
function isDocSource(rel) {
|
|
1186
|
+
return rel.endsWith(".md") && rel !== "SPEC.md" && !rel.endsWith("/SPEC.md");
|
|
1187
|
+
}
|
|
1150
1188
|
async function findDocFiles(repoDir, extraIgnore = []) {
|
|
1151
|
-
return walkFiles(repoDir,
|
|
1189
|
+
return walkFiles(repoDir, isDocSource, extraIgnore);
|
|
1152
1190
|
}
|
|
1153
1191
|
var IGNORE_SUBSTR, DEFAULT_EXTS;
|
|
1154
1192
|
var init_fs = __esm(() => {
|
|
@@ -1179,7 +1217,10 @@ var init_fs = __esm(() => {
|
|
|
1179
1217
|
"tf",
|
|
1180
1218
|
"toml",
|
|
1181
1219
|
"sql",
|
|
1182
|
-
"css"
|
|
1220
|
+
"css",
|
|
1221
|
+
"yml",
|
|
1222
|
+
"yaml",
|
|
1223
|
+
"astro"
|
|
1183
1224
|
];
|
|
1184
1225
|
});
|
|
1185
1226
|
|
|
@@ -1405,8 +1446,9 @@ function toRepos(columns) {
|
|
|
1405
1446
|
return columns.map((c) => ({ ...c.repo, dependency_depth: depth.get(c.repo.name) ?? 0 })).sort(byName);
|
|
1406
1447
|
}
|
|
1407
1448
|
function selfMember(absPlatform, platformVersion) {
|
|
1449
|
+
const dir = basename(absPlatform);
|
|
1408
1450
|
return {
|
|
1409
|
-
name:
|
|
1451
|
+
name: dir === CANONICAL_SPECS_DIR ? "root" : dir,
|
|
1410
1452
|
path: absPlatform,
|
|
1411
1453
|
pinned_spec_version: platformVersion,
|
|
1412
1454
|
dependency_depth: 0,
|
|
@@ -1446,7 +1488,7 @@ function packageDirs(repo) {
|
|
|
1446
1488
|
function loneRootColumn(absPlatform, repo, platformVersion) {
|
|
1447
1489
|
const dirs = packageDirs(repo);
|
|
1448
1490
|
const { dependency_depth: _depth, ...self } = selfMember(absPlatform, platformVersion);
|
|
1449
|
-
if (dirs === null
|
|
1491
|
+
if (dirs === null)
|
|
1450
1492
|
return [];
|
|
1451
1493
|
return [{ repo: { ...self, ignore: dirs }, node: repo }];
|
|
1452
1494
|
}
|
|
@@ -1525,14 +1567,15 @@ async function membersOf(reading, absPlatform, platformVersion) {
|
|
|
1525
1567
|
const undeclared = mapped.diagnostics.filter((d) => d.code === "UNLISTED_REPO").map((d) => ({ name: d.subject, path: join3(absPlatform, d.subject) }));
|
|
1526
1568
|
return { mode: "multi-repo", members: toRepos(columns), unpinned, undeclared, diagnostics: diagnostics2 };
|
|
1527
1569
|
}
|
|
1528
|
-
async function discoverRepos(platformDir) {
|
|
1570
|
+
async function discoverRepos(platformDir, opts = {}) {
|
|
1529
1571
|
const absPlatform = resolve2(platformDir);
|
|
1530
1572
|
const canonicalPath = join3(absPlatform, CANONICAL_SPECS_DIR);
|
|
1531
1573
|
if (!isExistingDir(canonicalPath)) {
|
|
1532
1574
|
throw new NotASpecPlatformError(absPlatform);
|
|
1533
1575
|
}
|
|
1534
1576
|
const platformVersion = await derivePlatformVersion(absPlatform);
|
|
1535
|
-
|
|
1577
|
+
if (!opts.quiet)
|
|
1578
|
+
warnIfRetiredManifest(absPlatform, platformVersion);
|
|
1536
1579
|
const canonical = {
|
|
1537
1580
|
name: CANONICAL_SPECS_DIR,
|
|
1538
1581
|
path: canonicalPath,
|
|
@@ -1862,7 +1905,14 @@ ORDER BY tc.source_file, tc.line, tc.req_id
|
|
|
1862
1905
|
`;
|
|
1863
1906
|
|
|
1864
1907
|
// src/storage/sql/fts.ts
|
|
1865
|
-
|
|
1908
|
+
function quoteBareWords(text) {
|
|
1909
|
+
return text.split(/\s+/).map((token) => token === "" || FTS_OPERATORS.has(token) || FTS_SYNTAX.test(token) || /^[\p{L}\p{N}_]+$/u.test(token) ? token : `"${token}"`).join(" ");
|
|
1910
|
+
}
|
|
1911
|
+
var FTS_OPERATORS, FTS_SYNTAX, FTS_SEARCH_SQL = `SELECT r.id AS req_id, r.key AS key, r.text AS text, r.why AS why, r.source_file AS source_file, r.line AS line, bm25(requirements_fts, 1.0, 0.5) AS rank FROM requirements_fts JOIN requirements r ON r.rowid = requirements_fts.rowid WHERE requirements_fts MATCH $query AND r.status NOT IN ('Superseded', 'Deprecated') ORDER BY rank ASC LIMIT $limit`, RESOLVE_BY_FILES_SQL_PREFIX = "SELECT DISTINCT r.id, r.key, r.seq, r.status, r.superseded_by, r.text, r.why, r.source_file, r.line, r.spec_version, r.changed_at_version, r.superseded_at_version FROM tags t JOIN requirements r ON r.id = t.req_id WHERE t.file IN (", RESOLVE_BY_FILES_SQL_SUFFIX = ") ORDER BY r.key, r.seq";
|
|
1912
|
+
var init_fts = __esm(() => {
|
|
1913
|
+
FTS_OPERATORS = new Set(["AND", "OR", "NOT", "NEAR"]);
|
|
1914
|
+
FTS_SYNTAX = /["()*:^]/;
|
|
1915
|
+
});
|
|
1866
1916
|
|
|
1867
1917
|
// src/storage/sql/propagation.ts
|
|
1868
1918
|
var PROP_REPO_STATES_SQL = `
|
|
@@ -2139,7 +2189,7 @@ class SqliteStorage {
|
|
|
2139
2189
|
}
|
|
2140
2190
|
searchFts(text, limit = 10) {
|
|
2141
2191
|
try {
|
|
2142
|
-
return this.#db.query(FTS_SEARCH_SQL).all({ query: text, limit });
|
|
2192
|
+
return this.#db.query(FTS_SEARCH_SQL).all({ query: quoteBareWords(text), limit });
|
|
2143
2193
|
} catch (e) {
|
|
2144
2194
|
const msg = e instanceof Error ? e.message : String(e);
|
|
2145
2195
|
if (/fts5:|syntax error/i.test(msg)) {
|
|
@@ -2168,33 +2218,9 @@ class SqliteStorage {
|
|
|
2168
2218
|
if (files.length > FILES_MAX) {
|
|
2169
2219
|
throw new Error(`resolveByFiles: too many files (max ${FILES_MAX} per call; got ${files.length})`);
|
|
2170
2220
|
}
|
|
2171
|
-
const
|
|
2172
|
-
if (queryFiles.length > 32000) {
|
|
2173
|
-
throw new Error(`resolveByFiles: expanded bind count ${queryFiles.length} would exceed ` + "SQLITE_MAX_VARIABLE_NUMBER (32766); lower FILES_MAX");
|
|
2174
|
-
}
|
|
2175
|
-
const placeholders = queryFiles.map(() => "?").join(",");
|
|
2221
|
+
const placeholders = files.map(() => "?").join(",");
|
|
2176
2222
|
const sql = RESOLVE_BY_FILES_SQL_PREFIX + placeholders + RESOLVE_BY_FILES_SQL_SUFFIX;
|
|
2177
|
-
return this.#db.query(sql).all(...
|
|
2178
|
-
}
|
|
2179
|
-
#selfMemberName() {
|
|
2180
|
-
const canonical = this.getRepo("spec-engine");
|
|
2181
|
-
if (!canonical)
|
|
2182
|
-
return null;
|
|
2183
|
-
const platformRoot = dirname2(canonical.path);
|
|
2184
|
-
const row = this.#db.query("SELECT name FROM repos WHERE name != 'spec-engine' AND path = $path").get({ path: platformRoot });
|
|
2185
|
-
return row?.name ?? null;
|
|
2186
|
-
}
|
|
2187
|
-
#expandSelfMemberFiles(files) {
|
|
2188
|
-
const self = this.#selfMemberName();
|
|
2189
|
-
if (!self)
|
|
2190
|
-
return files;
|
|
2191
|
-
const prefix = `${self}/`;
|
|
2192
|
-
const expanded = [...files];
|
|
2193
|
-
for (const f of files) {
|
|
2194
|
-
if (!f.startsWith(prefix))
|
|
2195
|
-
expanded.push(prefix + f);
|
|
2196
|
-
}
|
|
2197
|
-
return expanded;
|
|
2223
|
+
return this.#db.query(sql).all(...files);
|
|
2198
2224
|
}
|
|
2199
2225
|
withWriteTx(fn) {
|
|
2200
2226
|
const wrapped = this.#db.transaction((arg) => fn(arg));
|
|
@@ -2433,6 +2459,7 @@ var BUSY_TIMEOUT_MS = 2000;
|
|
|
2433
2459
|
var init_sqlite = __esm(() => {
|
|
2434
2460
|
init_src();
|
|
2435
2461
|
init_constants();
|
|
2462
|
+
init_fts();
|
|
2436
2463
|
init_reads();
|
|
2437
2464
|
});
|
|
2438
2465
|
|
|
@@ -2599,7 +2626,7 @@ var init_diagnostics2 = __esm(() => {
|
|
|
2599
2626
|
});
|
|
2600
2627
|
|
|
2601
2628
|
// src/indexer/pipeline.ts
|
|
2602
|
-
import { basename as basename2, dirname as dirname3, join as join4, resolve as resolve3 } from "path";
|
|
2629
|
+
import { basename as basename2, dirname as dirname3, isAbsolute, join as join4, relative, resolve as resolve3, sep as sep2 } from "path";
|
|
2603
2630
|
async function runIndex(opts) {
|
|
2604
2631
|
const platformDir = resolve3(opts.platformDir);
|
|
2605
2632
|
const {
|
|
@@ -2659,13 +2686,35 @@ function repoExtraIgnore(member) {
|
|
|
2659
2686
|
const repoIgnore = (member.ignore ?? []).map((e) => e.endsWith("/") ? e : `${e}/`);
|
|
2660
2687
|
return [...member.selfMember ? ["spec-engine/"] : [], ...repoIgnore];
|
|
2661
2688
|
}
|
|
2689
|
+
function scannedTagFile(members, platformDir, platformRel) {
|
|
2690
|
+
const abs = resolve3(platformDir, platformRel);
|
|
2691
|
+
let owner = null;
|
|
2692
|
+
for (const member2 of members) {
|
|
2693
|
+
const rel2 = relative(member2.path, abs).split(sep2).join("/");
|
|
2694
|
+
if (rel2 === "" || rel2 === ".." || rel2.startsWith("../") || isAbsolute(rel2))
|
|
2695
|
+
continue;
|
|
2696
|
+
if (owner === null || member2.path.length > owner.member.path.length)
|
|
2697
|
+
owner = { member: member2, rel: rel2 };
|
|
2698
|
+
}
|
|
2699
|
+
if (owner === null)
|
|
2700
|
+
return null;
|
|
2701
|
+
const { member, rel } = owner;
|
|
2702
|
+
if (isHiddenPath(rel) || isPathIgnored(rel, repoExtraIgnore(member)))
|
|
2703
|
+
return null;
|
|
2704
|
+
if (!isTagSource(rel) && !isDocSource(rel))
|
|
2705
|
+
return null;
|
|
2706
|
+
return tagFile(member, rel);
|
|
2707
|
+
}
|
|
2708
|
+
function tagFile(member, rel) {
|
|
2709
|
+
return member.selfMember ? rel : `${member.name}/${rel}`;
|
|
2710
|
+
}
|
|
2662
2711
|
async function scanMemberCode(member, extraIgnore) {
|
|
2663
2712
|
const hitsOut = [];
|
|
2664
2713
|
const codePaths = await findCodeFiles(member.path, DEFAULT_EXTS, extraIgnore);
|
|
2665
2714
|
for (const rel of codePaths) {
|
|
2666
2715
|
const absPath = join4(member.path, rel);
|
|
2667
2716
|
const text = await Bun.file(absPath).text();
|
|
2668
|
-
const platformRelative =
|
|
2717
|
+
const platformRelative = tagFile(member, rel);
|
|
2669
2718
|
const hits = scanTagsInFile(member.name, platformRelative, text);
|
|
2670
2719
|
for (const h of hits)
|
|
2671
2720
|
hitsOut.push(h);
|
|
@@ -2679,7 +2728,7 @@ async function scanMemberDocs(member, extraIgnore) {
|
|
|
2679
2728
|
for (const rel of docPaths) {
|
|
2680
2729
|
const absPath = join4(member.path, rel);
|
|
2681
2730
|
const text = await Bun.file(absPath).text();
|
|
2682
|
-
const platformRelative =
|
|
2731
|
+
const platformRelative = tagFile(member, rel);
|
|
2683
2732
|
const { tags, mentions } = scanDocFile(member.name, platformRelative, text);
|
|
2684
2733
|
for (const t of tags)
|
|
2685
2734
|
tagsOut.push(t);
|
|
@@ -2892,7 +2941,7 @@ var init__result = __esm(() => {
|
|
|
2892
2941
|
|
|
2893
2942
|
// src/operations/init.ts
|
|
2894
2943
|
import { existsSync as existsSync3, realpathSync, statSync as statSync2 } from "fs";
|
|
2895
|
-
import { basename as basename3, join as join5, relative, resolve as resolve4, sep as
|
|
2944
|
+
import { basename as basename3, join as join5, relative as relative2, resolve as resolve4, sep as sep3 } from "path";
|
|
2896
2945
|
import {
|
|
2897
2946
|
applyInit,
|
|
2898
2947
|
applyLink,
|
|
@@ -2905,7 +2954,7 @@ function errMessage(err) {
|
|
|
2905
2954
|
return err instanceof Error ? err.message : String(err);
|
|
2906
2955
|
}
|
|
2907
2956
|
function hasSpecEngineSegment(path) {
|
|
2908
|
-
return path.split(
|
|
2957
|
+
return path.split(sep3).filter((s) => s.length > 0).includes(CANONICAL_SPECS_DIR);
|
|
2909
2958
|
}
|
|
2910
2959
|
function canonicalize(repoDir) {
|
|
2911
2960
|
let repoStat;
|
|
@@ -2926,7 +2975,7 @@ function canonicalize(repoDir) {
|
|
|
2926
2975
|
function judgeSpecTree(repoDir, canonical, root) {
|
|
2927
2976
|
if (!hasSpecEngineSegment(canonical))
|
|
2928
2977
|
return { ok: true };
|
|
2929
|
-
const judged = root !== null && isEnginePlatform(root, ENGINE_PACKAGE_NAME) ?
|
|
2978
|
+
const judged = root !== null && isEnginePlatform(root, ENGINE_PACKAGE_NAME) ? relative2(root, canonical) : canonical;
|
|
2930
2979
|
if (hasSpecEngineSegment(judged)) {
|
|
2931
2980
|
return fail("usage", `${repoDir} resolves to ${canonical}, which is inside a spec-engine/ tree \u2014 refusing to scaffold there.`);
|
|
2932
2981
|
}
|
|
@@ -3303,14 +3352,14 @@ function handleNotAPlatform(e) {
|
|
|
3303
3352
|
}
|
|
3304
3353
|
throw e;
|
|
3305
3354
|
}
|
|
3306
|
-
function handleInvalidDomainFiles(e, json, code = EXIT.FAILURE) {
|
|
3355
|
+
async function handleInvalidDomainFiles(e, json, code = EXIT.FAILURE) {
|
|
3307
3356
|
if (!(e instanceof InvalidDomainFilesError))
|
|
3308
3357
|
return;
|
|
3309
3358
|
if (json)
|
|
3310
3359
|
console.log(renderDiagnostics(e.diagnostics, "json"));
|
|
3311
3360
|
else
|
|
3312
3361
|
console.error(renderDiagnostics(e.diagnostics, "text"));
|
|
3313
|
-
|
|
3362
|
+
await exit(code);
|
|
3314
3363
|
}
|
|
3315
3364
|
function cliFreshTags(platformDir) {
|
|
3316
3365
|
const fresh = coldFreshTags(platformDir);
|
|
@@ -3318,7 +3367,7 @@ function cliFreshTags(platformDir) {
|
|
|
3318
3367
|
try {
|
|
3319
3368
|
return await fresh(reqId);
|
|
3320
3369
|
} catch (e) {
|
|
3321
|
-
handleInvalidDomainFiles(e, false, EXIT.USAGE);
|
|
3370
|
+
await handleInvalidDomainFiles(e, false, EXIT.USAGE);
|
|
3322
3371
|
throw e;
|
|
3323
3372
|
}
|
|
3324
3373
|
};
|
|
@@ -3360,7 +3409,7 @@ async function withReadStorage(opts, fn) {
|
|
|
3360
3409
|
});
|
|
3361
3410
|
} catch (e) {
|
|
3362
3411
|
handleStorageUnavailable(e, dbPath);
|
|
3363
|
-
handleInvalidDomainFiles(e, json);
|
|
3412
|
+
await handleInvalidDomainFiles(e, json);
|
|
3364
3413
|
handleNotAPlatform(e);
|
|
3365
3414
|
}
|
|
3366
3415
|
}
|
|
@@ -3369,6 +3418,7 @@ var init__shared = __esm(() => {
|
|
|
3369
3418
|
init_constants();
|
|
3370
3419
|
init_discover();
|
|
3371
3420
|
init__index();
|
|
3421
|
+
init_stdout();
|
|
3372
3422
|
init_errors();
|
|
3373
3423
|
init_sqlite();
|
|
3374
3424
|
});
|
|
@@ -3567,7 +3617,7 @@ function listFlag(rawArgs, name, parsed) {
|
|
|
3567
3617
|
}
|
|
3568
3618
|
return raw.flatMap((v) => v.split(",")).map((v) => v.trim()).filter((v) => v !== "");
|
|
3569
3619
|
}
|
|
3570
|
-
var platformDirArg, outArg, jsonArg, freshArg, noPromptArg, livesArg, relatesArg;
|
|
3620
|
+
var platformDirArg, outArg, jsonArg, freshArg, noPromptArg, livesArg, testsArg, relatesArg;
|
|
3571
3621
|
var init__args = __esm(() => {
|
|
3572
3622
|
init_constants();
|
|
3573
3623
|
platformDirArg = {
|
|
@@ -3592,6 +3642,10 @@ var init__args = __esm(() => {
|
|
|
3592
3642
|
type: "string",
|
|
3593
3643
|
description: "Lives in (livesIn) paths: repeat the flag or comma-separate; an empty value clears the list"
|
|
3594
3644
|
};
|
|
3645
|
+
testsArg = {
|
|
3646
|
+
type: "string",
|
|
3647
|
+
description: "Verified in (verifiedIn) test paths: repeat the flag or comma-separate; an empty value clears the list"
|
|
3648
|
+
};
|
|
3595
3649
|
relatesArg = {
|
|
3596
3650
|
type: "string",
|
|
3597
3651
|
description: "Related requirement ids (relates): repeat the flag or comma-separate; an empty value clears the list"
|
|
@@ -3670,7 +3724,7 @@ var init_commands = __esm(() => {
|
|
|
3670
3724
|
console.error(formatNotASpecPlatform(err.platformDir));
|
|
3671
3725
|
process.exit(EXIT.USAGE);
|
|
3672
3726
|
}
|
|
3673
|
-
handleInvalidDomainFiles(err, !!args.json);
|
|
3727
|
+
await handleInvalidDomainFiles(err, !!args.json);
|
|
3674
3728
|
console.error("spec index FAILED:", err);
|
|
3675
3729
|
process.exit(EXIT.FAILURE);
|
|
3676
3730
|
} finally {
|
|
@@ -4049,67 +4103,117 @@ var init_filerefs = __esm(() => {
|
|
|
4049
4103
|
});
|
|
4050
4104
|
|
|
4051
4105
|
// src/check/filerefs.ts
|
|
4052
|
-
|
|
4053
|
-
const
|
|
4054
|
-
|
|
4055
|
-
try {
|
|
4056
|
-
raw = await Bun.file(abs).text();
|
|
4057
|
-
} catch {
|
|
4058
|
-
return null;
|
|
4059
|
-
}
|
|
4060
|
-
const parsed = parseDomainText(raw, rel);
|
|
4061
|
-
return parsed.ok ? { raw, requirements: parsed.data.requirements } : null;
|
|
4062
|
-
}
|
|
4063
|
-
function rowsForRequirement(platformDir, key, reqId, livesIn, line) {
|
|
4064
|
-
const rows = [];
|
|
4065
|
-
for (const entry of livesIn) {
|
|
4066
|
-
const ref = entry.startsWith("@") ? entry.slice(1) : entry;
|
|
4067
|
-
if (ref.length === 0 || resolveFileRef(platformDir, ref))
|
|
4068
|
-
continue;
|
|
4069
|
-
rows.push({
|
|
4070
|
-
code: DiagnosticCode.BROKEN_FILE_REF,
|
|
4071
|
-
severity: "error",
|
|
4072
|
-
repo: null,
|
|
4073
|
-
source_file: specPaths(platformDir, key).rel,
|
|
4074
|
-
line,
|
|
4075
|
-
req_id: reqId,
|
|
4076
|
-
detail: `livesIn entry ${entry} does not resolve to a file under the platform root`
|
|
4077
|
-
});
|
|
4078
|
-
}
|
|
4079
|
-
return rows;
|
|
4080
|
-
}
|
|
4081
|
-
function rowsForDomain(platformDir, key, domain2) {
|
|
4082
|
-
const rows = [];
|
|
4083
|
-
const rawLines = domain2.raw.split(`
|
|
4106
|
+
function locateInDomain(raw, sourceFile, requirements, statuses) {
|
|
4107
|
+
const out = [];
|
|
4108
|
+
const rawLines = raw.split(`
|
|
4084
4109
|
`);
|
|
4085
4110
|
let lineCursor = 0;
|
|
4086
|
-
for (const req of
|
|
4111
|
+
for (const req of requirements) {
|
|
4087
4112
|
const found = findRequirementIdLine(rawLines, req.id, lineCursor);
|
|
4088
4113
|
if (found >= 0)
|
|
4089
4114
|
lineCursor = found + 1;
|
|
4090
|
-
if (
|
|
4115
|
+
if (statuses.has(req.status.toLowerCase())) {
|
|
4116
|
+
out.push({ sourceFile, line: found >= 0 ? found + 1 : 0, req });
|
|
4117
|
+
}
|
|
4118
|
+
}
|
|
4119
|
+
return out;
|
|
4120
|
+
}
|
|
4121
|
+
async function locatedEntries(platformDir, statuses) {
|
|
4122
|
+
const out = [];
|
|
4123
|
+
for (const key of listDomainKeys(platformDir)) {
|
|
4124
|
+
const { abs, rel } = specPaths(platformDir, key);
|
|
4125
|
+
let raw;
|
|
4126
|
+
try {
|
|
4127
|
+
raw = await Bun.file(abs).text();
|
|
4128
|
+
} catch {
|
|
4091
4129
|
continue;
|
|
4092
|
-
|
|
4130
|
+
}
|
|
4131
|
+
const parsed = parseDomainText(raw, rel);
|
|
4132
|
+
if (parsed.ok)
|
|
4133
|
+
out.push(...locateInDomain(raw, rel, parsed.data.requirements, statuses));
|
|
4093
4134
|
}
|
|
4094
|
-
return
|
|
4135
|
+
return out;
|
|
4136
|
+
}
|
|
4137
|
+
function sortRows(rows) {
|
|
4138
|
+
return rows.sort((a, b) => (a.source_file ?? "").localeCompare(b.source_file ?? "") || (a.line ?? 0) - (b.line ?? 0) || (a.req_id ?? "").localeCompare(b.req_id ?? "") || a.code.localeCompare(b.code) || a.detail.localeCompare(b.detail));
|
|
4139
|
+
}
|
|
4140
|
+
function row(at, code, severity, detail) {
|
|
4141
|
+
return {
|
|
4142
|
+
code,
|
|
4143
|
+
severity,
|
|
4144
|
+
repo: null,
|
|
4145
|
+
source_file: at.sourceFile,
|
|
4146
|
+
line: at.line,
|
|
4147
|
+
req_id: at.req.id,
|
|
4148
|
+
detail
|
|
4149
|
+
};
|
|
4095
4150
|
}
|
|
4096
4151
|
async function brokenFileRefDiagnostics(platformDir) {
|
|
4097
4152
|
const rows = [];
|
|
4098
|
-
for (const
|
|
4099
|
-
const
|
|
4100
|
-
|
|
4101
|
-
|
|
4153
|
+
for (const at of await locatedEntries(platformDir, FILE_REF_STATUSES)) {
|
|
4154
|
+
for (const [field, entries] of [
|
|
4155
|
+
["livesIn", at.req.livesIn],
|
|
4156
|
+
["verifiedIn", at.req.verifiedIn]
|
|
4157
|
+
]) {
|
|
4158
|
+
for (const entry of entries) {
|
|
4159
|
+
const ref = refOf(entry);
|
|
4160
|
+
if (ref.length === 0 || resolveFileRef(platformDir, ref))
|
|
4161
|
+
continue;
|
|
4162
|
+
rows.push(row(at, DiagnosticCode.BROKEN_FILE_REF, "error", `${field} entry ${entry} does not resolve to a file under the platform root`));
|
|
4163
|
+
}
|
|
4164
|
+
}
|
|
4165
|
+
}
|
|
4166
|
+
return sortRows(rows);
|
|
4167
|
+
}
|
|
4168
|
+
function tagKeys(tags, kinds) {
|
|
4169
|
+
return new Set(tags.filter((t) => kinds.includes(t.kind)).map((t) => `${t.req_id} ${t.file}`));
|
|
4170
|
+
}
|
|
4171
|
+
function inFileRows(platformDir, members, at, check) {
|
|
4172
|
+
const rows = [];
|
|
4173
|
+
for (const entry of at.req[check.field]) {
|
|
4174
|
+
const ref = refOf(entry);
|
|
4175
|
+
if (ref.length === 0 || !resolveFileRef(platformDir, ref))
|
|
4176
|
+
continue;
|
|
4177
|
+
const file = scannedTagFile(members, platformDir, ref);
|
|
4178
|
+
if (file === null || check.keys.has(`${at.req.id} ${file}`))
|
|
4179
|
+
continue;
|
|
4180
|
+
rows.push(row(at, check.code, "warning", `${check.field} entry ${entry} holds no ${check.missing} tag for ${at.req.id}`));
|
|
4102
4181
|
}
|
|
4103
|
-
rows.sort((a, b) => (a.source_file ?? "").localeCompare(b.source_file ?? "") || (a.line ?? 0) - (b.line ?? 0) || (a.req_id ?? "").localeCompare(b.req_id ?? "") || a.detail.localeCompare(b.detail));
|
|
4104
4182
|
return rows;
|
|
4105
4183
|
}
|
|
4106
|
-
|
|
4184
|
+
async function inFileTagDiagnostics(platformDir, tags) {
|
|
4185
|
+
const { members } = await discoverRepos(platformDir, { quiet: true });
|
|
4186
|
+
const checks = [
|
|
4187
|
+
{
|
|
4188
|
+
field: "livesIn",
|
|
4189
|
+
code: DiagnosticCode.UNIMPLEMENTED_IN_FILE,
|
|
4190
|
+
keys: tagKeys(tags, ["implements", "documents"]),
|
|
4191
|
+
missing: "implementing or documenting"
|
|
4192
|
+
},
|
|
4193
|
+
{
|
|
4194
|
+
field: "verifiedIn",
|
|
4195
|
+
code: DiagnosticCode.UNVERIFIED_IN_FILE,
|
|
4196
|
+
keys: tagKeys(tags, ["verifies"]),
|
|
4197
|
+
missing: "verifying"
|
|
4198
|
+
}
|
|
4199
|
+
];
|
|
4200
|
+
const rows = [];
|
|
4201
|
+
for (const at of await locatedEntries(platformDir, IN_FILE_STATUSES)) {
|
|
4202
|
+
for (const check of checks)
|
|
4203
|
+
rows.push(...inFileRows(platformDir, members, at, check));
|
|
4204
|
+
}
|
|
4205
|
+
return sortRows(rows);
|
|
4206
|
+
}
|
|
4207
|
+
var FILE_REF_STATUSES, IN_FILE_STATUSES, refOf = (entry) => entry.startsWith("@") ? entry.slice(1) : entry;
|
|
4107
4208
|
var init_filerefs2 = __esm(() => {
|
|
4108
4209
|
init_src();
|
|
4109
4210
|
init_domains();
|
|
4110
4211
|
init_filerefs();
|
|
4111
4212
|
init_constants();
|
|
4112
|
-
|
|
4213
|
+
init_discover();
|
|
4214
|
+
init_pipeline();
|
|
4215
|
+
FILE_REF_STATUSES = new Set(["active", "draft"]);
|
|
4216
|
+
IN_FILE_STATUSES = new Set(["active"]);
|
|
4113
4217
|
});
|
|
4114
4218
|
|
|
4115
4219
|
// src/check/grammar.ts
|
|
@@ -4130,7 +4234,7 @@ async function readDeclaredDomain(platformDir, key) {
|
|
|
4130
4234
|
requirements: parsed.data.requirements
|
|
4131
4235
|
};
|
|
4132
4236
|
}
|
|
4133
|
-
function
|
|
4237
|
+
function rowsForDomain(key, domain2) {
|
|
4134
4238
|
const rows = [];
|
|
4135
4239
|
const rawLines = domain2.raw.split(`
|
|
4136
4240
|
`);
|
|
@@ -4139,7 +4243,7 @@ function rowsForDomain2(key, domain2) {
|
|
|
4139
4243
|
const found = findRequirementIdLine(rawLines, req.id, lineCursor);
|
|
4140
4244
|
if (found >= 0)
|
|
4141
4245
|
lineCursor = found + 1;
|
|
4142
|
-
if (!
|
|
4246
|
+
if (!CHECKED_STATUSES.has(req.status.toLowerCase()))
|
|
4143
4247
|
continue;
|
|
4144
4248
|
const result = parseEarsStatement(req.statement);
|
|
4145
4249
|
if (result.ok)
|
|
@@ -4163,17 +4267,17 @@ async function statementGrammarDiagnostics(platformDir) {
|
|
|
4163
4267
|
continue;
|
|
4164
4268
|
const domain2 = await readDeclaredDomain(platformDir, key);
|
|
4165
4269
|
if (domain2 !== null)
|
|
4166
|
-
rows.push(...
|
|
4270
|
+
rows.push(...rowsForDomain(key, domain2));
|
|
4167
4271
|
}
|
|
4168
4272
|
rows.sort((a, b) => (a.source_file ?? "").localeCompare(b.source_file ?? "") || (a.line ?? 0) - (b.line ?? 0) || (a.req_id ?? "").localeCompare(b.req_id ?? ""));
|
|
4169
4273
|
return rows;
|
|
4170
4274
|
}
|
|
4171
|
-
var
|
|
4275
|
+
var CHECKED_STATUSES;
|
|
4172
4276
|
var init_grammar = __esm(() => {
|
|
4173
4277
|
init_src();
|
|
4174
4278
|
init_domains();
|
|
4175
4279
|
init_constants();
|
|
4176
|
-
|
|
4280
|
+
CHECKED_STATUSES = new Set(["active", "draft"]);
|
|
4177
4281
|
});
|
|
4178
4282
|
|
|
4179
4283
|
// src/check/propagation-teeth.ts
|
|
@@ -4250,12 +4354,12 @@ function readCheckConfig(platformDir) {
|
|
|
4250
4354
|
return { ok: true, config: { severity: [] } };
|
|
4251
4355
|
return parseCheckConfigText(readFileSync(path, "utf-8"), CHECK_CONFIG_REL);
|
|
4252
4356
|
}
|
|
4253
|
-
function specificity(rule,
|
|
4254
|
-
if (rule.code !==
|
|
4357
|
+
function specificity(rule, row2) {
|
|
4358
|
+
if (rule.code !== row2.code)
|
|
4255
4359
|
return Specificity.none;
|
|
4256
4360
|
if (rule.match === undefined)
|
|
4257
4361
|
return Specificity.code;
|
|
4258
|
-
const id =
|
|
4362
|
+
const id = row2.req_id;
|
|
4259
4363
|
if (id === null)
|
|
4260
4364
|
return Specificity.none;
|
|
4261
4365
|
if (rule.match.includes(id))
|
|
@@ -4265,20 +4369,20 @@ function specificity(rule, row) {
|
|
|
4265
4369
|
}
|
|
4266
4370
|
function applySeverity(rows, rules) {
|
|
4267
4371
|
const out = [];
|
|
4268
|
-
for (const
|
|
4372
|
+
for (const row2 of rows) {
|
|
4269
4373
|
let best;
|
|
4270
4374
|
let bestScore = Specificity.none;
|
|
4271
4375
|
for (const rule of rules) {
|
|
4272
|
-
const score = specificity(rule,
|
|
4376
|
+
const score = specificity(rule, row2);
|
|
4273
4377
|
if (score !== Specificity.none && score >= bestScore) {
|
|
4274
4378
|
best = rule;
|
|
4275
4379
|
bestScore = score;
|
|
4276
4380
|
}
|
|
4277
4381
|
}
|
|
4278
4382
|
if (best === undefined)
|
|
4279
|
-
out.push(
|
|
4383
|
+
out.push(row2);
|
|
4280
4384
|
else if (best.severity !== "off")
|
|
4281
|
-
out.push({ ...
|
|
4385
|
+
out.push({ ...row2, severity: best.severity });
|
|
4282
4386
|
}
|
|
4283
4387
|
return out;
|
|
4284
4388
|
}
|
|
@@ -4797,6 +4901,7 @@ function successorEntry(id, pred, fields) {
|
|
|
4797
4901
|
supersededBy: null,
|
|
4798
4902
|
relates: fields.relates,
|
|
4799
4903
|
livesIn: fields.livesIn,
|
|
4904
|
+
verifiedIn: fields.verifiedIn,
|
|
4800
4905
|
issues: fields.issue ? [{ role: "created", id: fields.issue }] : [],
|
|
4801
4906
|
aliases: [...pred.aliases],
|
|
4802
4907
|
cites: pred.cites.map((c) => ({ ...c }))
|
|
@@ -4884,7 +4989,8 @@ async function mint(input) {
|
|
|
4884
4989
|
const warnings = unresolvableRefWarnings(platformDir, [
|
|
4885
4990
|
input.statement,
|
|
4886
4991
|
input.why,
|
|
4887
|
-
...input.livesIn
|
|
4992
|
+
...input.livesIn,
|
|
4993
|
+
...input.verifiedIn ?? []
|
|
4888
4994
|
]);
|
|
4889
4995
|
const verdict = await judgeStatementGrammar(platformDir, key, input.statement);
|
|
4890
4996
|
if (!verdict.ok) {
|
|
@@ -4908,6 +5014,7 @@ async function mint(input) {
|
|
|
4908
5014
|
supersededBy: null,
|
|
4909
5015
|
relates: splitList(input.relates ?? []),
|
|
4910
5016
|
livesIn: input.livesIn,
|
|
5017
|
+
verifiedIn: input.verifiedIn ?? [],
|
|
4911
5018
|
issues: input.issue ? [{ role: "created", id: input.issue }] : [],
|
|
4912
5019
|
aliases: [],
|
|
4913
5020
|
cites: input.cites ?? []
|
|
@@ -4965,6 +5072,7 @@ async function mintTerm(input) {
|
|
|
4965
5072
|
supersededBy: null,
|
|
4966
5073
|
relates: [],
|
|
4967
5074
|
livesIn: [],
|
|
5075
|
+
verifiedIn: [],
|
|
4968
5076
|
issues: [],
|
|
4969
5077
|
aliases: input.aliases,
|
|
4970
5078
|
cites: [],
|
|
@@ -5151,6 +5259,7 @@ async function migrateGlossary(platformDir) {
|
|
|
5151
5259
|
supersededBy: null,
|
|
5152
5260
|
relates: [],
|
|
5153
5261
|
livesIn: [],
|
|
5262
|
+
verifiedIn: [],
|
|
5154
5263
|
issues: [],
|
|
5155
5264
|
aliases: [],
|
|
5156
5265
|
cites: [],
|
|
@@ -5508,6 +5617,7 @@ async function check(input, storage2) {
|
|
|
5508
5617
|
}
|
|
5509
5618
|
diagnostics2.push(...await statementGrammarDiagnostics(platformDir));
|
|
5510
5619
|
diagnostics2.push(...await brokenFileRefDiagnostics(platformDir));
|
|
5620
|
+
diagnostics2.push(...await inFileTagDiagnostics(platformDir, storage2.listTags()));
|
|
5511
5621
|
diagnostics2.push(...await supersedesPointerDiagnostics(platformDir));
|
|
5512
5622
|
const glossaryDrift = glossaryDriftDiagnostic(platformDir);
|
|
5513
5623
|
if (glossaryDrift !== null)
|
|
@@ -5594,6 +5704,7 @@ var init_check2 = __esm(() => {
|
|
|
5594
5704
|
init_prompt();
|
|
5595
5705
|
init__index();
|
|
5596
5706
|
init_check();
|
|
5707
|
+
init_stdout();
|
|
5597
5708
|
init__args();
|
|
5598
5709
|
init__shared();
|
|
5599
5710
|
checkCommand = defineCommand3({
|
|
@@ -5667,7 +5778,7 @@ var init_check2 = __esm(() => {
|
|
|
5667
5778
|
return result;
|
|
5668
5779
|
});
|
|
5669
5780
|
} catch (e) {
|
|
5670
|
-
handleInvalidDomainFiles(e, jsonMode);
|
|
5781
|
+
await handleInvalidDomainFiles(e, jsonMode);
|
|
5671
5782
|
exitAfterCrash(e);
|
|
5672
5783
|
}
|
|
5673
5784
|
if (outcome === undefined) {
|
|
@@ -5685,7 +5796,7 @@ var init_check2 = __esm(() => {
|
|
|
5685
5796
|
console.log(renderDiagnostics(diagnostics2, jsonMode ? "json" : "text"));
|
|
5686
5797
|
if (!jsonMode)
|
|
5687
5798
|
console.log(`build_id: ${outcome.buildId}`);
|
|
5688
|
-
|
|
5799
|
+
await exit(outcome.failing ? EXIT.FAILURE : EXIT.OK);
|
|
5689
5800
|
}
|
|
5690
5801
|
});
|
|
5691
5802
|
});
|
|
@@ -5935,12 +6046,12 @@ function sortMatrix(rows) {
|
|
|
5935
6046
|
return a.repo.localeCompare(b.repo);
|
|
5936
6047
|
});
|
|
5937
6048
|
}
|
|
5938
|
-
function cellStatus(
|
|
5939
|
-
if (
|
|
6049
|
+
function cellStatus(row2) {
|
|
6050
|
+
if (row2.implemented === 1 && row2.verified === 1)
|
|
5940
6051
|
return "src+test";
|
|
5941
|
-
if (
|
|
6052
|
+
if (row2.implemented === 1)
|
|
5942
6053
|
return "src";
|
|
5943
|
-
if (
|
|
6054
|
+
if (row2.verified === 1)
|
|
5944
6055
|
return "test";
|
|
5945
6056
|
return EMPTY_CELL;
|
|
5946
6057
|
}
|
|
@@ -5953,18 +6064,18 @@ function renderMatrix(rows, mode) {
|
|
|
5953
6064
|
return "";
|
|
5954
6065
|
const repos = Array.from(new Set(sorted.map((r) => r.repo))).sort((a, b) => a.localeCompare(b));
|
|
5955
6066
|
const byReq = new Map;
|
|
5956
|
-
for (const
|
|
5957
|
-
let entry = byReq.get(
|
|
6067
|
+
for (const row2 of sorted) {
|
|
6068
|
+
let entry = byReq.get(row2.req_id);
|
|
5958
6069
|
if (!entry) {
|
|
5959
6070
|
entry = {
|
|
5960
|
-
domain_key:
|
|
5961
|
-
req_id:
|
|
5962
|
-
req_status:
|
|
6071
|
+
domain_key: row2.domain_key,
|
|
6072
|
+
req_id: row2.req_id,
|
|
6073
|
+
req_status: row2.req_status,
|
|
5963
6074
|
cells: new Map
|
|
5964
6075
|
};
|
|
5965
|
-
byReq.set(
|
|
6076
|
+
byReq.set(row2.req_id, entry);
|
|
5966
6077
|
}
|
|
5967
|
-
entry.cells.set(
|
|
6078
|
+
entry.cells.set(row2.repo, cellStatus(row2));
|
|
5968
6079
|
}
|
|
5969
6080
|
const header = ["DOMAIN", "REQUIREMENT", "STATUS", ...repos];
|
|
5970
6081
|
const lines = [header];
|
|
@@ -5972,8 +6083,8 @@ function renderMatrix(rows, mode) {
|
|
|
5972
6083
|
const cells = repos.map((r) => entry.cells.get(r) ?? EMPTY_CELL);
|
|
5973
6084
|
lines.push([entry.domain_key, entry.req_id, entry.req_status, ...cells]);
|
|
5974
6085
|
}
|
|
5975
|
-
const widths = header.map((_, col) => Math.max(...lines.map((
|
|
5976
|
-
return lines.map((
|
|
6086
|
+
const widths = header.map((_, col) => Math.max(...lines.map((row2) => row2[col]?.length ?? 0)));
|
|
6087
|
+
return lines.map((row2) => row2.map((c, i) => c.padEnd(widths[i] ?? 0)).join(" ")).join(`
|
|
5977
6088
|
`);
|
|
5978
6089
|
}
|
|
5979
6090
|
var EMPTY_CELL = "\u2014";
|
|
@@ -6109,11 +6220,11 @@ var init_map = __esm(() => {
|
|
|
6109
6220
|
|
|
6110
6221
|
// src/operations/records.ts
|
|
6111
6222
|
function getRecord(storage2, id) {
|
|
6112
|
-
const
|
|
6223
|
+
const row2 = storage2.getRequirement(id);
|
|
6113
6224
|
return {
|
|
6114
6225
|
ok: true,
|
|
6115
|
-
row,
|
|
6116
|
-
platformEmpty:
|
|
6226
|
+
row: row2,
|
|
6227
|
+
platformEmpty: row2 === null && storage2.listRequirements().length === 0
|
|
6117
6228
|
};
|
|
6118
6229
|
}
|
|
6119
6230
|
function listRecords(storage2, filter = {}) {
|
|
@@ -6150,8 +6261,8 @@ function renderRecords(rows, mode) {
|
|
|
6150
6261
|
lines.push([r.id, r.status, r.key, String(r.seq), String(r.changed_at_version), r.text]);
|
|
6151
6262
|
}
|
|
6152
6263
|
const fixed = HEADER.length - 1;
|
|
6153
|
-
const widths = HEADER.slice(0, fixed).map((_, col) => Math.max(...lines.map((
|
|
6154
|
-
return lines.map((
|
|
6264
|
+
const widths = HEADER.slice(0, fixed).map((_, col) => Math.max(...lines.map((row2) => row2[col]?.length ?? 0)));
|
|
6265
|
+
return lines.map((row2) => row2.map((cell, i) => i < fixed ? cell.padEnd(widths[i] ?? 0) : cell).join(" ").trimEnd()).join(`
|
|
6155
6266
|
`);
|
|
6156
6267
|
}
|
|
6157
6268
|
var HEADER;
|
|
@@ -6303,11 +6414,11 @@ function renderPropagation(rows, mode) {
|
|
|
6303
6414
|
return "";
|
|
6304
6415
|
const header = ["REPO", "STATE", "VIA", "DRIFT?"];
|
|
6305
6416
|
const lines = [header];
|
|
6306
|
-
for (const
|
|
6307
|
-
lines.push([
|
|
6417
|
+
for (const row2 of rows) {
|
|
6418
|
+
lines.push([row2.repo, row2.state, row2.via_req_id ?? EMPTY_CELL2, row2.drifted ? "yes" : "no"]);
|
|
6308
6419
|
}
|
|
6309
|
-
const widths = header.map((_, col) => Math.max(...lines.map((
|
|
6310
|
-
return lines.map((
|
|
6420
|
+
const widths = header.map((_, col) => Math.max(...lines.map((row2) => row2[col]?.length ?? 0)));
|
|
6421
|
+
return lines.map((row2) => row2.map((c, i) => c.padEnd(widths[i] ?? 0)).join(" ")).join(`
|
|
6311
6422
|
`);
|
|
6312
6423
|
}
|
|
6313
6424
|
var EMPTY_CELL2 = "\u2014";
|
|
@@ -6381,8 +6492,8 @@ function excerptOf(raw) {
|
|
|
6381
6492
|
}
|
|
6382
6493
|
function renderTable(header, rows) {
|
|
6383
6494
|
const lines = [header, ...rows];
|
|
6384
|
-
const widths = header.map((_, col) => Math.max(...lines.map((
|
|
6385
|
-
return lines.map((
|
|
6495
|
+
const widths = header.map((_, col) => Math.max(...lines.map((row2) => row2[col]?.length ?? 0)));
|
|
6496
|
+
return lines.map((row2) => row2.map((c, i) => c.padEnd(widths[i] ?? 0)).join(" ")).join(`
|
|
6386
6497
|
`);
|
|
6387
6498
|
}
|
|
6388
6499
|
function renderQuery(hits, mode) {
|
|
@@ -6637,17 +6748,17 @@ function compareReqIds2(a, b) {
|
|
|
6637
6748
|
function sortProvenance(rows) {
|
|
6638
6749
|
return [...rows].sort((a, b) => compareReqIds2(a.req_id, b.req_id) || a.role.localeCompare(b.role) || a.issue_id.localeCompare(b.issue_id) || a.source_file.localeCompare(b.source_file) || a.line - b.line);
|
|
6639
6750
|
}
|
|
6640
|
-
function backingTests(
|
|
6751
|
+
function backingTests(row2) {
|
|
6641
6752
|
let base;
|
|
6642
|
-
if (
|
|
6753
|
+
if (row2.implemented === 1 && row2.verified === 1)
|
|
6643
6754
|
base = "src+test";
|
|
6644
|
-
else if (
|
|
6755
|
+
else if (row2.implemented === 1)
|
|
6645
6756
|
base = "src";
|
|
6646
|
-
else if (
|
|
6757
|
+
else if (row2.verified === 1)
|
|
6647
6758
|
base = "test";
|
|
6648
6759
|
else
|
|
6649
6760
|
base = "\u2014";
|
|
6650
|
-
return
|
|
6761
|
+
return row2.test_levels ? `${base} (${row2.test_levels})` : base;
|
|
6651
6762
|
}
|
|
6652
6763
|
function renderProvenance(rows, mode) {
|
|
6653
6764
|
const sorted = sortProvenance(rows);
|
|
@@ -6657,12 +6768,12 @@ function renderProvenance(rows, mode) {
|
|
|
6657
6768
|
if (sorted.length === 0)
|
|
6658
6769
|
return "";
|
|
6659
6770
|
const byReq = new Map;
|
|
6660
|
-
for (const
|
|
6661
|
-
const bucket = byReq.get(
|
|
6771
|
+
for (const row2 of sorted) {
|
|
6772
|
+
const bucket = byReq.get(row2.req_id);
|
|
6662
6773
|
if (bucket)
|
|
6663
|
-
bucket.push(
|
|
6774
|
+
bucket.push(row2);
|
|
6664
6775
|
else
|
|
6665
|
-
byReq.set(
|
|
6776
|
+
byReq.set(row2.req_id, [row2]);
|
|
6666
6777
|
}
|
|
6667
6778
|
const lines = [];
|
|
6668
6779
|
for (const [reqId, links] of byReq) {
|
|
@@ -6677,8 +6788,8 @@ function renderProvenance(rows, mode) {
|
|
|
6677
6788
|
return lines.join(`
|
|
6678
6789
|
`);
|
|
6679
6790
|
}
|
|
6680
|
-
function decorateRow(
|
|
6681
|
-
const prefix = ` ${
|
|
6791
|
+
function decorateRow(row2, resolved) {
|
|
6792
|
+
const prefix = ` ${row2.role} ${row2.issue_id} ${row2.source_file}:${row2.line}`;
|
|
6682
6793
|
if (resolved?.ok && resolved.title && resolved.url) {
|
|
6683
6794
|
const staleMark = resolved.stale ? " (cached \u2014 refresh failed)" : "";
|
|
6684
6795
|
return `${prefix} ${resolved.title} [${resolved.status ?? ""}] ${resolved.url}${staleMark}`;
|
|
@@ -6702,12 +6813,12 @@ function renderProvenanceDecorated(rows, resolved, mode) {
|
|
|
6702
6813
|
if (sorted.length === 0)
|
|
6703
6814
|
return "";
|
|
6704
6815
|
const byReq = new Map;
|
|
6705
|
-
for (const
|
|
6706
|
-
const bucket = byReq.get(
|
|
6816
|
+
for (const row2 of sorted) {
|
|
6817
|
+
const bucket = byReq.get(row2.req_id);
|
|
6707
6818
|
if (bucket)
|
|
6708
|
-
bucket.push(
|
|
6819
|
+
bucket.push(row2);
|
|
6709
6820
|
else
|
|
6710
|
-
byReq.set(
|
|
6821
|
+
byReq.set(row2.req_id, [row2]);
|
|
6711
6822
|
}
|
|
6712
6823
|
const lines = [];
|
|
6713
6824
|
for (const [reqId, links] of byReq) {
|
|
@@ -7074,6 +7185,7 @@ async function mintFromFlags(platformDir, key, textFlag, args, rawArgs) {
|
|
|
7074
7185
|
statement,
|
|
7075
7186
|
why: (args.why ?? "").trim(),
|
|
7076
7187
|
livesIn: listFlag(rawArgs, "lives", args.lives) ?? [],
|
|
7188
|
+
verifiedIn: listFlag(rawArgs, "tests", args.tests) ?? [],
|
|
7077
7189
|
relates: listFlag(rawArgs, "relates", args.relates) ?? [],
|
|
7078
7190
|
issue: (args.issue ?? "").trim() || undefined,
|
|
7079
7191
|
status: statusOf(args),
|
|
@@ -7153,6 +7265,7 @@ var init_req = __esm(() => {
|
|
|
7153
7265
|
},
|
|
7154
7266
|
why: { type: "string", description: "Why it matters field (only with --text)" },
|
|
7155
7267
|
lives: livesArg,
|
|
7268
|
+
tests: testsArg,
|
|
7156
7269
|
relates: relatesArg,
|
|
7157
7270
|
issue: {
|
|
7158
7271
|
type: "string",
|
|
@@ -7186,9 +7299,9 @@ var init_req = __esm(() => {
|
|
|
7186
7299
|
await mintFromFlags(platformDir, key, textFlag, args, rawArgs);
|
|
7187
7300
|
return;
|
|
7188
7301
|
}
|
|
7189
|
-
const hasFieldFlags = typeof args.why === "string" || typeof args.lives === "string" || typeof args.relates === "string" || typeof args.issue === "string";
|
|
7302
|
+
const hasFieldFlags = typeof args.why === "string" || typeof args.lives === "string" || typeof args.tests === "string" || typeof args.relates === "string" || typeof args.issue === "string";
|
|
7190
7303
|
if (hasFieldFlags) {
|
|
7191
|
-
console.error("spec req: --why/--lives/--relates/--issue require --text (the Requirement field is mandatory)");
|
|
7304
|
+
console.error("spec req: --why/--lives/--tests/--relates/--issue require --text (the Requirement field is mandatory)");
|
|
7192
7305
|
process.exit(EXIT.USAGE);
|
|
7193
7306
|
return;
|
|
7194
7307
|
}
|
|
@@ -7276,8 +7389,8 @@ async function listTermsCommand(platformDirRaw, json) {
|
|
|
7276
7389
|
console.log(JSON.stringify(result.rows));
|
|
7277
7390
|
return;
|
|
7278
7391
|
}
|
|
7279
|
-
for (const
|
|
7280
|
-
console.log(`${
|
|
7392
|
+
for (const row2 of result.rows) {
|
|
7393
|
+
console.log(`${row2.id} ${row2.term} ${row2.status}`);
|
|
7281
7394
|
}
|
|
7282
7395
|
}
|
|
7283
7396
|
async function reviseTermCommand(opts) {
|
|
@@ -7324,8 +7437,8 @@ async function confirmTermCommand(opts) {
|
|
|
7324
7437
|
exitOnFailure("spec term confirm", result);
|
|
7325
7438
|
await withIndex({ platformDir, build: "fresh" }, () => {
|
|
7326
7439
|
return;
|
|
7327
|
-
}).catch((e) => {
|
|
7328
|
-
handleInvalidDomainFiles(e, false, EXIT.USAGE);
|
|
7440
|
+
}).catch(async (e) => {
|
|
7441
|
+
await handleInvalidDomainFiles(e, false, EXIT.USAGE);
|
|
7329
7442
|
throw e;
|
|
7330
7443
|
});
|
|
7331
7444
|
const { pinned, file } = result;
|
|
@@ -7532,7 +7645,7 @@ async function generate(platformDir, json) {
|
|
|
7532
7645
|
else
|
|
7533
7646
|
console.log(`generated GLOSSARY.md from ${result.generated} terms`);
|
|
7534
7647
|
}
|
|
7535
|
-
function check2(platformDir, json) {
|
|
7648
|
+
async function check2(platformDir, json) {
|
|
7536
7649
|
if (checkGlossary(platformDir).clean) {
|
|
7537
7650
|
if (json)
|
|
7538
7651
|
console.log(JSON.stringify({ ok: true }));
|
|
@@ -7544,13 +7657,14 @@ function check2(platformDir, json) {
|
|
|
7544
7657
|
console.error("run `spec glossary .` to regenerate the human view from the store");
|
|
7545
7658
|
if (json)
|
|
7546
7659
|
console.log(JSON.stringify({ ok: false }));
|
|
7547
|
-
|
|
7660
|
+
await exit(EXIT.FAILURE);
|
|
7548
7661
|
}
|
|
7549
7662
|
var glossaryCommand;
|
|
7550
7663
|
var init_glossary2 = __esm(() => {
|
|
7551
7664
|
init_constants();
|
|
7552
7665
|
init_discover();
|
|
7553
7666
|
init_glossary();
|
|
7667
|
+
init_stdout();
|
|
7554
7668
|
init__args();
|
|
7555
7669
|
init__shared();
|
|
7556
7670
|
glossaryCommand = defineCommand14({
|
|
@@ -7576,7 +7690,7 @@ var init_glossary2 = __esm(() => {
|
|
|
7576
7690
|
if (args.migrate)
|
|
7577
7691
|
await migrate(platformDir, json);
|
|
7578
7692
|
else if (args.check)
|
|
7579
|
-
check2(platformDir, json);
|
|
7693
|
+
await check2(platformDir, json);
|
|
7580
7694
|
else
|
|
7581
7695
|
await generate(platformDir, json);
|
|
7582
7696
|
}
|
|
@@ -7602,6 +7716,7 @@ function successorFields(input, req) {
|
|
|
7602
7716
|
return {
|
|
7603
7717
|
why: (input.why ?? req.why ?? "").trim(),
|
|
7604
7718
|
livesIn: input.livesIn ?? [...req.livesIn],
|
|
7719
|
+
verifiedIn: input.verifiedIn ?? [...req.verifiedIn],
|
|
7605
7720
|
relates: input.relates === undefined ? [...req.relates] : splitList(input.relates)
|
|
7606
7721
|
};
|
|
7607
7722
|
}
|
|
@@ -7646,14 +7761,15 @@ async function supersede(input, freshTags) {
|
|
|
7646
7761
|
}
|
|
7647
7762
|
warnings.push({ kind: "grammar", text: grammarWarningText("", verdict).slice(2) });
|
|
7648
7763
|
}
|
|
7649
|
-
const { why, livesIn, relates } = successorFields(input, req);
|
|
7650
|
-
warnings.push(...unresolvableRefWarnings(platformDir, [input.statement, why, ...livesIn]));
|
|
7764
|
+
const { why, livesIn, verifiedIn, relates } = successorFields(input, req);
|
|
7765
|
+
warnings.push(...unresolvableRefWarnings(platformDir, [input.statement, why, ...livesIn, ...verifiedIn]));
|
|
7651
7766
|
const newId = await nextRequirementId(platformDir, key);
|
|
7652
7767
|
const issue = input.issue?.trim() || undefined;
|
|
7653
7768
|
const successor = successorEntry(newId, req, {
|
|
7654
7769
|
statement: input.statement,
|
|
7655
7770
|
why,
|
|
7656
7771
|
livesIn,
|
|
7772
|
+
verifiedIn,
|
|
7657
7773
|
relates,
|
|
7658
7774
|
issue
|
|
7659
7775
|
});
|
|
@@ -7697,14 +7813,14 @@ function renderResolve(rows, mode) {
|
|
|
7697
7813
|
return "";
|
|
7698
7814
|
const header = ["REQ_ID", "STATUS", "TEXT"];
|
|
7699
7815
|
const lines = [header];
|
|
7700
|
-
for (const
|
|
7701
|
-
const raw =
|
|
7816
|
+
for (const row2 of sorted) {
|
|
7817
|
+
const raw = row2.text ?? "";
|
|
7702
7818
|
const truncated = raw.length > TEXT_MAX ? raw.slice(0, TEXT_MAX) + ELLIPSIS2 : raw;
|
|
7703
7819
|
const text = truncated.replace(/\s+/g, " ").trim();
|
|
7704
|
-
lines.push([
|
|
7820
|
+
lines.push([row2.id, row2.status, text || EMPTY_CELL4]);
|
|
7705
7821
|
}
|
|
7706
|
-
const widths = header.map((_, col) => Math.max(...lines.map((
|
|
7707
|
-
return lines.map((
|
|
7822
|
+
const widths = header.map((_, col) => Math.max(...lines.map((row2) => row2[col]?.length ?? 0)));
|
|
7823
|
+
return lines.map((row2) => row2.map((c, i) => c.padEnd(widths[i] ?? 0)).join(" ")).join(`
|
|
7708
7824
|
`);
|
|
7709
7825
|
}
|
|
7710
7826
|
function sortReqTags(rows) {
|
|
@@ -7719,8 +7835,8 @@ function renderReqTags(rows, mode) {
|
|
|
7719
7835
|
return "";
|
|
7720
7836
|
const header = ["REPO", "FILE", "LINE", "KIND", "LEVEL"];
|
|
7721
7837
|
const lines = [header];
|
|
7722
|
-
for (const
|
|
7723
|
-
lines.push([
|
|
7838
|
+
for (const row2 of sorted) {
|
|
7839
|
+
lines.push([row2.repo, row2.file, String(row2.line), row2.kind, row2.level ?? EMPTY_CELL4]);
|
|
7724
7840
|
}
|
|
7725
7841
|
const widths = header.map((_, col) => Math.max(...lines.map((l) => (l[col] ?? "").length)));
|
|
7726
7842
|
return lines.map((l) => l.map((cell, col) => cell.padEnd(widths[col])).join(" ").trimEnd()).join(`
|
|
@@ -7767,6 +7883,9 @@ function inputFromArgs(args, rawArgs, platformDir, id, statement) {
|
|
|
7767
7883
|
const livesIn = listFlag(rawArgs, "lives", args.lives);
|
|
7768
7884
|
if (livesIn !== undefined)
|
|
7769
7885
|
input.livesIn = livesIn;
|
|
7886
|
+
const verifiedIn = listFlag(rawArgs, "tests", args.tests);
|
|
7887
|
+
if (verifiedIn !== undefined)
|
|
7888
|
+
input.verifiedIn = verifiedIn;
|
|
7770
7889
|
const relates = listFlag(rawArgs, "relates", args.relates);
|
|
7771
7890
|
if (relates !== undefined)
|
|
7772
7891
|
input.relates = relates;
|
|
@@ -7807,6 +7926,10 @@ var init_supersede2 = __esm(() => {
|
|
|
7807
7926
|
type: "string",
|
|
7808
7927
|
description: "Successor's Lives in: repeat or comma-separate; empty clears (default: copied from the old entry)"
|
|
7809
7928
|
},
|
|
7929
|
+
tests: {
|
|
7930
|
+
type: "string",
|
|
7931
|
+
description: "Successor's Verified in (verifiedIn): repeat or comma-separate; empty clears (default: copied from the old entry)"
|
|
7932
|
+
},
|
|
7810
7933
|
relates: {
|
|
7811
7934
|
type: "string",
|
|
7812
7935
|
description: "Successor's related ids: repeat or comma-separate; empty clears (default: copied from the old entry)"
|
|
@@ -8000,6 +8123,7 @@ function successorFields2(input, req) {
|
|
|
8000
8123
|
statement: (input.statement ?? req.statement).trim(),
|
|
8001
8124
|
why: (input.why ?? req.why ?? "").trim(),
|
|
8002
8125
|
livesIn: input.livesIn ?? [...req.livesIn],
|
|
8126
|
+
verifiedIn: input.verifiedIn ?? [...req.verifiedIn],
|
|
8003
8127
|
relates: [...req.relates]
|
|
8004
8128
|
};
|
|
8005
8129
|
}
|
|
@@ -8044,7 +8168,12 @@ async function move(input, freshTags) {
|
|
|
8044
8168
|
warnings.push({ kind: "grammar", text: grammarWarningText("", verdict).slice(2) });
|
|
8045
8169
|
}
|
|
8046
8170
|
}
|
|
8047
|
-
warnings.push(...unresolvableRefWarnings(platformDir, [
|
|
8171
|
+
warnings.push(...unresolvableRefWarnings(platformDir, [
|
|
8172
|
+
fields.statement,
|
|
8173
|
+
fields.why,
|
|
8174
|
+
...fields.livesIn,
|
|
8175
|
+
...fields.verifiedIn
|
|
8176
|
+
]));
|
|
8048
8177
|
const newId = await nextRequirementId(platformDir, targetKey);
|
|
8049
8178
|
const sourceCurrent = source.domain.specVersion ?? 1;
|
|
8050
8179
|
req.status = "superseded";
|
|
@@ -8103,6 +8232,9 @@ function inputFromArgs2(args, rawArgs, platformDir, id, targetKey) {
|
|
|
8103
8232
|
const livesIn = listFlag(rawArgs, "lives", args.lives);
|
|
8104
8233
|
if (livesIn !== undefined)
|
|
8105
8234
|
input.livesIn = livesIn;
|
|
8235
|
+
const verifiedIn = listFlag(rawArgs, "tests", args.tests);
|
|
8236
|
+
if (verifiedIn !== undefined)
|
|
8237
|
+
input.verifiedIn = verifiedIn;
|
|
8106
8238
|
return input;
|
|
8107
8239
|
}
|
|
8108
8240
|
var moveArgs, moveCommand;
|
|
@@ -8138,6 +8270,10 @@ var init_move2 = __esm(() => {
|
|
|
8138
8270
|
type: "string",
|
|
8139
8271
|
description: "Rewrite the successor's Lives in: repeat or comma-separate; empty clears (default: copied from the source)"
|
|
8140
8272
|
},
|
|
8273
|
+
tests: {
|
|
8274
|
+
type: "string",
|
|
8275
|
+
description: "Rewrite the successor's Verified in (verifiedIn): repeat or comma-separate; empty clears (default: copied from the source)"
|
|
8276
|
+
},
|
|
8141
8277
|
noBump: {
|
|
8142
8278
|
type: "boolean",
|
|
8143
8279
|
description: "Do not bump either envelope's specVersion"
|
|
@@ -8228,7 +8364,7 @@ function gateRejection(platformDir, relFile, req, fields) {
|
|
|
8228
8364
|
const tag = releaseHolding(platformDir, relFile, id);
|
|
8229
8365
|
if (tag === null)
|
|
8230
8366
|
return null;
|
|
8231
|
-
return fail("conflict", `${id} is released in ${tag} \u2014 its promise is immutable. ` + `Run \`spec supersede ${id}\` to change the statement or why; ` + "pass only --lives or --relates to amend its metadata in place.");
|
|
8367
|
+
return fail("conflict", `${id} is released in ${tag} \u2014 its promise is immutable. ` + `Run \`spec supersede ${id}\` to change the statement or why; ` + "pass only --lives, --tests, or --relates to amend its metadata in place.");
|
|
8232
8368
|
}
|
|
8233
8369
|
function applyFields(req, fields) {
|
|
8234
8370
|
const fieldsChanged = [];
|
|
@@ -8248,6 +8384,11 @@ function applyFields(req, fields) {
|
|
|
8248
8384
|
fieldsChanged.push("lives");
|
|
8249
8385
|
refValues.push(...fields.livesIn);
|
|
8250
8386
|
}
|
|
8387
|
+
if (fields.verifiedIn !== undefined) {
|
|
8388
|
+
req.verifiedIn = fields.verifiedIn;
|
|
8389
|
+
fieldsChanged.push("tests");
|
|
8390
|
+
refValues.push(...fields.verifiedIn);
|
|
8391
|
+
}
|
|
8251
8392
|
if (fields.relates !== undefined) {
|
|
8252
8393
|
req.relates = splitList(fields.relates);
|
|
8253
8394
|
fieldsChanged.push("relates");
|
|
@@ -8317,9 +8458,9 @@ __export(exports_amend, {
|
|
|
8317
8458
|
import { defineCommand as defineCommand18 } from "citty";
|
|
8318
8459
|
function assertAmendFlags(args) {
|
|
8319
8460
|
const hasIssue = args.issue !== undefined && args.issue.trim() !== "";
|
|
8320
|
-
const hasField = args.text !== undefined || args.why !== undefined || args.lives !== undefined || args.relates !== undefined || args.term !== undefined || args.aliases !== undefined;
|
|
8461
|
+
const hasField = args.text !== undefined || args.why !== undefined || args.lives !== undefined || args.tests !== undefined || args.relates !== undefined || args.term !== undefined || args.aliases !== undefined;
|
|
8321
8462
|
if (!hasField && !hasIssue) {
|
|
8322
|
-
console.error("spec amend: nothing to amend \u2014 pass at least one of --text / --why / --lives / --relates / --term / --aliases");
|
|
8463
|
+
console.error("spec amend: nothing to amend \u2014 pass at least one of --text / --why / --lives / --tests / --relates / --term / --aliases");
|
|
8323
8464
|
process.exit(EXIT.USAGE);
|
|
8324
8465
|
}
|
|
8325
8466
|
if (args.text !== undefined && args.text.trim() === "") {
|
|
@@ -8346,6 +8487,9 @@ function fieldsFromArgs(args, rawArgs) {
|
|
|
8346
8487
|
const livesIn = listFlag(rawArgs, "lives", args.lives);
|
|
8347
8488
|
if (livesIn !== undefined)
|
|
8348
8489
|
fields.livesIn = livesIn;
|
|
8490
|
+
const verifiedIn = listFlag(rawArgs, "tests", args.tests);
|
|
8491
|
+
if (verifiedIn !== undefined)
|
|
8492
|
+
fields.verifiedIn = verifiedIn;
|
|
8349
8493
|
const relates = listFlag(rawArgs, "relates", args.relates);
|
|
8350
8494
|
if (relates !== undefined)
|
|
8351
8495
|
fields.relates = relates;
|
|
@@ -8375,6 +8519,7 @@ var init_amend2 = __esm(() => {
|
|
|
8375
8519
|
text: { type: "string", description: "New Requirement (statement) field value" },
|
|
8376
8520
|
why: { type: "string", description: "New Why it matters field value" },
|
|
8377
8521
|
lives: livesArg,
|
|
8522
|
+
tests: testsArg,
|
|
8378
8523
|
relates: relatesArg,
|
|
8379
8524
|
issue: {
|
|
8380
8525
|
type: "string",
|
|
@@ -8390,7 +8535,7 @@ var init_amend2 = __esm(() => {
|
|
|
8390
8535
|
amendCommand = defineCommand18({
|
|
8391
8536
|
meta: {
|
|
8392
8537
|
name: "amend",
|
|
8393
|
-
description: "Revise a requirement's fields in place (same id, no version bump). A released requirement's statement and why are superseded instead; --lives and --relates still amend."
|
|
8538
|
+
description: "Revise a requirement's fields in place (same id, no version bump). A released requirement's statement and why are superseded instead; --lives, --tests, and --relates still amend."
|
|
8394
8539
|
},
|
|
8395
8540
|
args: amendArgs,
|
|
8396
8541
|
async run({ args, rawArgs }) {
|
|
@@ -8544,6 +8689,8 @@ function amendFieldsOf(args) {
|
|
|
8544
8689
|
fields.why = args.why || null;
|
|
8545
8690
|
if (args.lives !== undefined)
|
|
8546
8691
|
fields.livesIn = args.lives;
|
|
8692
|
+
if (args.verified_in !== undefined)
|
|
8693
|
+
fields.verifiedIn = args.verified_in;
|
|
8547
8694
|
if (args.relates !== undefined)
|
|
8548
8695
|
fields.relates = args.relates;
|
|
8549
8696
|
if (args.issue)
|
|
@@ -8621,8 +8768,8 @@ function buildMcpServer(platformDir) {
|
|
|
8621
8768
|
return errorResult(`req_id must be a requirement id (KEY-NNN); got ${req_id}`);
|
|
8622
8769
|
}
|
|
8623
8770
|
return fresh((s) => {
|
|
8624
|
-
const { row } = getRecord(s, req_id);
|
|
8625
|
-
return jsonResult(
|
|
8771
|
+
const { row: row2 } = getRecord(s, req_id);
|
|
8772
|
+
return jsonResult(row2 ?? []);
|
|
8626
8773
|
});
|
|
8627
8774
|
});
|
|
8628
8775
|
server.registerTool("spec_list", {
|
|
@@ -8653,14 +8800,24 @@ function buildMcpServer(platformDir) {
|
|
|
8653
8800
|
statement: z4.string().min(1).describe("The successor's statement"),
|
|
8654
8801
|
why: z4.string().optional().describe("Successor's why (default: copied from the predecessor)"),
|
|
8655
8802
|
lives_in: z4.array(z4.string()).optional().describe("Successor's livesIn paths (default: copied from the predecessor)"),
|
|
8803
|
+
verified_in: z4.array(z4.string()).optional().describe("Successor's verifiedIn paths (default: copied from the predecessor)"),
|
|
8656
8804
|
relates: z4.array(z4.string()).optional().describe("Successor's related ids, each comma-split; an empty list clears (default: copied from the predecessor)"),
|
|
8657
8805
|
issue: z4.string().optional().describe("Ticket recorded as supersedes-via on the predecessor and created on the successor")
|
|
8658
8806
|
}
|
|
8659
|
-
}, async ({ req_id, statement, why, lives_in, relates, issue }) => {
|
|
8807
|
+
}, async ({ req_id, statement, why, lives_in, verified_in, relates, issue }) => {
|
|
8660
8808
|
if (!ID_RE.test(req_id)) {
|
|
8661
8809
|
return errorResult(`req_id must be a requirement id (KEY-NNN); got ${req_id}`);
|
|
8662
8810
|
}
|
|
8663
|
-
const r = await supersede({
|
|
8811
|
+
const r = await supersede({
|
|
8812
|
+
platformDir,
|
|
8813
|
+
id: req_id,
|
|
8814
|
+
statement,
|
|
8815
|
+
why,
|
|
8816
|
+
livesIn: lives_in,
|
|
8817
|
+
verifiedIn: verified_in,
|
|
8818
|
+
relates,
|
|
8819
|
+
issue
|
|
8820
|
+
}, coldFreshTags(platformDir));
|
|
8664
8821
|
if (!r.ok)
|
|
8665
8822
|
return errorResult(r.detail);
|
|
8666
8823
|
for (const w of r.warnings)
|
|
@@ -8711,11 +8868,12 @@ function buildMcpServer(platformDir) {
|
|
|
8711
8868
|
text: z4.string().trim().min(1).describe("The requirement statement"),
|
|
8712
8869
|
why: z4.string().trim().default("").describe("What breaks without it"),
|
|
8713
8870
|
lives: z4.array(z4.string()).default([]).describe("livesIn paths"),
|
|
8871
|
+
verified_in: z4.array(z4.string()).default([]).describe("verifiedIn test paths"),
|
|
8714
8872
|
relates: z4.array(z4.string()).default([]).describe("Related requirement ids, each comma-split"),
|
|
8715
8873
|
issue: z4.string().trim().optional().describe("Originating ticket, recorded as created"),
|
|
8716
8874
|
draft: z4.boolean().default(false).describe("Author as Draft instead of Active")
|
|
8717
8875
|
}
|
|
8718
|
-
}, async ({ domain: domain2, text, why, lives, relates, issue, draft }) => {
|
|
8876
|
+
}, async ({ domain: domain2, text, why, lives, verified_in, relates, issue, draft }) => {
|
|
8719
8877
|
const allocated = await nextId(platformDir, domain2);
|
|
8720
8878
|
if (!allocated.ok)
|
|
8721
8879
|
return errorResult(allocated.detail);
|
|
@@ -8725,6 +8883,7 @@ function buildMcpServer(platformDir) {
|
|
|
8725
8883
|
statement: text,
|
|
8726
8884
|
why,
|
|
8727
8885
|
livesIn: lives,
|
|
8886
|
+
verifiedIn: verified_in,
|
|
8728
8887
|
relates,
|
|
8729
8888
|
issue: issue || undefined,
|
|
8730
8889
|
status: draft ? "draft" : "active"
|
|
@@ -8743,15 +8902,16 @@ function buildMcpServer(platformDir) {
|
|
|
8743
8902
|
text: z4.string().trim().min(1).optional().describe("New statement"),
|
|
8744
8903
|
why: z4.string().trim().optional().describe("New why; empty clears it"),
|
|
8745
8904
|
lives: z4.array(z4.string()).optional().describe("Replacement livesIn list; empty clears"),
|
|
8905
|
+
verified_in: z4.array(z4.string()).optional().describe("Replacement verifiedIn list; empty clears"),
|
|
8746
8906
|
relates: z4.array(z4.string()).optional().describe("Replacement related ids, each comma-split; empty clears"),
|
|
8747
8907
|
issue: z4.string().trim().optional().describe("Ticket recorded as amends-via")
|
|
8748
8908
|
}
|
|
8749
|
-
}, async ({ id, text, why, lives, relates, issue }) => {
|
|
8909
|
+
}, async ({ id, text, why, lives, verified_in, relates, issue }) => {
|
|
8750
8910
|
if (!ID_RE.test(id))
|
|
8751
8911
|
return errorResult(`id must be a requirement id (KEY-NNN); got ${id}`);
|
|
8752
|
-
const fields = amendFieldsOf({ text, why, lives, relates, issue });
|
|
8912
|
+
const fields = amendFieldsOf({ text, why, lives, verified_in, relates, issue });
|
|
8753
8913
|
if (Object.keys(fields).length === 0) {
|
|
8754
|
-
return errorResult("nothing to amend \u2014 pass at least one of text, why, lives, relates");
|
|
8914
|
+
return errorResult("nothing to amend \u2014 pass at least one of text, why, lives, verified_in, relates");
|
|
8755
8915
|
}
|
|
8756
8916
|
const r = await amend({ platformDir, id, fields });
|
|
8757
8917
|
if (!r.ok)
|
|
@@ -8768,15 +8928,24 @@ function buildMcpServer(platformDir) {
|
|
|
8768
8928
|
newDomain: z4.string().min(1).describe("The existing target domain key"),
|
|
8769
8929
|
text: z4.string().optional().describe("Successor statement (default: copied)"),
|
|
8770
8930
|
why: z4.string().optional().describe("Successor why (default: copied)"),
|
|
8771
|
-
lives: z4.array(z4.string()).optional().describe("Successor livesIn list; empty clears (default: copied)")
|
|
8931
|
+
lives: z4.array(z4.string()).optional().describe("Successor livesIn list; empty clears (default: copied)"),
|
|
8932
|
+
verified_in: z4.array(z4.string()).optional().describe("Successor verifiedIn list; empty clears (default: copied)")
|
|
8772
8933
|
}
|
|
8773
|
-
}, async ({ id, newDomain: newDomain2, text, why, lives }) => {
|
|
8934
|
+
}, async ({ id, newDomain: newDomain2, text, why, lives, verified_in }) => {
|
|
8774
8935
|
if (!ID_RE.test(id))
|
|
8775
8936
|
return errorResult(`id must be a requirement id (KEY-NNN); got ${id}`);
|
|
8776
8937
|
const targetKey = normalizeDomainKey(newDomain2);
|
|
8777
8938
|
if (targetKey === "")
|
|
8778
8939
|
return errorResult(`newDomain must be a domain key; got ${newDomain2}`);
|
|
8779
|
-
const r = await move({
|
|
8940
|
+
const r = await move({
|
|
8941
|
+
platformDir,
|
|
8942
|
+
id,
|
|
8943
|
+
targetKey,
|
|
8944
|
+
statement: text,
|
|
8945
|
+
why,
|
|
8946
|
+
livesIn: lives,
|
|
8947
|
+
verifiedIn: verified_in
|
|
8948
|
+
}, coldFreshTags(platformDir));
|
|
8780
8949
|
if (!r.ok)
|
|
8781
8950
|
return errorResult(r.detail);
|
|
8782
8951
|
for (const w of r.warnings)
|
|
@@ -8880,7 +9049,7 @@ __export(exports_resolve, {
|
|
|
8880
9049
|
resolveCommand: () => resolveCommand
|
|
8881
9050
|
});
|
|
8882
9051
|
import { statSync as statSync5 } from "fs";
|
|
8883
|
-
import { join as join14, relative as
|
|
9052
|
+
import { join as join14, relative as relative3, resolve as resolve13, sep as sep4 } from "path";
|
|
8884
9053
|
import { defineCommand as defineCommand21 } from "citty";
|
|
8885
9054
|
function classifyArg(a) {
|
|
8886
9055
|
if (a.startsWith("--")) {
|
|
@@ -9016,8 +9185,8 @@ async function dispatchReverseQuery(reqArg, resolved, args) {
|
|
|
9016
9185
|
function normalizeFileInputs(files, platformDir) {
|
|
9017
9186
|
const normalized = [];
|
|
9018
9187
|
for (const input of files) {
|
|
9019
|
-
const rel =
|
|
9020
|
-
const segments = rel.split(
|
|
9188
|
+
const rel = relative3(platformDir, resolve13(platformDir, input));
|
|
9189
|
+
const segments = rel.split(sep4);
|
|
9021
9190
|
if (rel === "" || rel === "." || segments[0] === "..") {
|
|
9022
9191
|
console.error(`spec resolve: file path must be inside platformDir: ${input}`);
|
|
9023
9192
|
process.exit(EXIT.USAGE);
|
|
@@ -9233,7 +9402,7 @@ __export(exports_gate, {
|
|
|
9233
9402
|
import { existsSync as existsSync13 } from "fs";
|
|
9234
9403
|
import { join as join15 } from "path";
|
|
9235
9404
|
import { defineCommand as defineCommand22 } from "citty";
|
|
9236
|
-
function
|
|
9405
|
+
function isEpipe2(e) {
|
|
9237
9406
|
if (e === null || typeof e !== "object" || !("code" in e))
|
|
9238
9407
|
return false;
|
|
9239
9408
|
const code = e.code;
|
|
@@ -9243,7 +9412,7 @@ function writeStdoutTolerant(line) {
|
|
|
9243
9412
|
try {
|
|
9244
9413
|
console.log(line);
|
|
9245
9414
|
} catch (writeErr) {
|
|
9246
|
-
if (!
|
|
9415
|
+
if (!isEpipe2(writeErr))
|
|
9247
9416
|
throw writeErr;
|
|
9248
9417
|
}
|
|
9249
9418
|
}
|
|
@@ -9268,6 +9437,7 @@ var init_gate2 = __esm(() => {
|
|
|
9268
9437
|
init_prompt();
|
|
9269
9438
|
init__index();
|
|
9270
9439
|
init_gate();
|
|
9440
|
+
init_stdout();
|
|
9271
9441
|
init__args();
|
|
9272
9442
|
init__shared();
|
|
9273
9443
|
gateArgs = {
|
|
@@ -9328,7 +9498,7 @@ var init_gate2 = __esm(() => {
|
|
|
9328
9498
|
writeStdoutTolerant(`build_id: ${gated.buildId}`);
|
|
9329
9499
|
}
|
|
9330
9500
|
} catch (e) {
|
|
9331
|
-
handleInvalidDomainFiles(e, Boolean(args.json));
|
|
9501
|
+
await handleInvalidDomainFiles(e, Boolean(args.json));
|
|
9332
9502
|
const msg = e instanceof Error ? e.message : String(e);
|
|
9333
9503
|
console.error(`spec gate: crashed: ${msg}`);
|
|
9334
9504
|
process.exit(EXIT.USAGE);
|
|
@@ -9338,7 +9508,7 @@ var init_gate2 = __esm(() => {
|
|
|
9338
9508
|
process.exit(EXIT.USAGE);
|
|
9339
9509
|
return;
|
|
9340
9510
|
}
|
|
9341
|
-
|
|
9511
|
+
await exit(gated.outcome.pass ? EXIT.OK : EXIT.FAILURE);
|
|
9342
9512
|
}
|
|
9343
9513
|
});
|
|
9344
9514
|
});
|
|
@@ -9424,6 +9594,7 @@ var guardCommand;
|
|
|
9424
9594
|
var init_guard2 = __esm(() => {
|
|
9425
9595
|
init_constants();
|
|
9426
9596
|
init_guard();
|
|
9597
|
+
init_stdout();
|
|
9427
9598
|
init__args();
|
|
9428
9599
|
init__shared();
|
|
9429
9600
|
guardCommand = defineCommand23({
|
|
@@ -9449,14 +9620,14 @@ var init_guard2 = __esm(() => {
|
|
|
9449
9620
|
if (!gitRefResolves(platformDir, ref)) {
|
|
9450
9621
|
console.error(`spec guard: NOT_A_GIT_REPO \u2014 ref '${ref}' does not resolve in ${platformDir} ` + "(not a git repo, a fresh repo with no commit, or an unfetched/misspelled ref); " + "skipping loss detection");
|
|
9451
9622
|
console.log(jsonMode ? "[]" : `\u2713 spec guard: no requirements about to be lost (non-git context)`);
|
|
9452
|
-
|
|
9623
|
+
await exit(EXIT.OK);
|
|
9453
9624
|
}
|
|
9454
9625
|
let losses = [];
|
|
9455
9626
|
await withReadStorage({ platformDir, dbPath: defaultIndexPath(platformDir), fresh: true, json: jsonMode }, async (storage2) => {
|
|
9456
9627
|
losses = (await guard(storage2, platformDir, ref)).losses;
|
|
9457
9628
|
});
|
|
9458
9629
|
console.log(renderGuard(losses, jsonMode ? "json" : "text", ref));
|
|
9459
|
-
|
|
9630
|
+
await exit(losses.length > 0 ? EXIT.FAILURE : EXIT.OK);
|
|
9460
9631
|
}
|
|
9461
9632
|
});
|
|
9462
9633
|
});
|
|
@@ -11058,9 +11229,9 @@ function requirementStatusBadge(status) {
|
|
|
11058
11229
|
const variant = status === "Active" ? "active" : status === "Superseded" ? "superseded" : status === "Draft" ? "draft" : "retired";
|
|
11059
11230
|
return badge(variant, STATUS_LABEL[status]);
|
|
11060
11231
|
}
|
|
11061
|
-
function propagationStateBadge(
|
|
11232
|
+
function propagationStateBadge(row2) {
|
|
11062
11233
|
let variant;
|
|
11063
|
-
switch (
|
|
11234
|
+
switch (row2.state) {
|
|
11064
11235
|
case "MIGRATED_VERIFIED":
|
|
11065
11236
|
variant = "verified";
|
|
11066
11237
|
break;
|
|
@@ -11073,7 +11244,7 @@ function propagationStateBadge(row) {
|
|
|
11073
11244
|
default:
|
|
11074
11245
|
variant = "unbound";
|
|
11075
11246
|
}
|
|
11076
|
-
return badge(variant,
|
|
11247
|
+
return badge(variant, row2.state);
|
|
11077
11248
|
}
|
|
11078
11249
|
function coverageGlyph(impl, verif) {
|
|
11079
11250
|
const [variant, char, title] = impl && verif ? ["both", "\u2713", "src + test"] : impl ? ["impl", "S", "src only"] : verif ? ["test", "T", "test only"] : ["none", "", "none"];
|
|
@@ -11207,18 +11378,18 @@ import { html as html4, raw as raw3 } from "hono/html";
|
|
|
11207
11378
|
function pivot(rows) {
|
|
11208
11379
|
const repos = [...new Set(rows.map((r) => r.repo))].sort();
|
|
11209
11380
|
const byDomain = new Map;
|
|
11210
|
-
for (const
|
|
11211
|
-
let dom = byDomain.get(
|
|
11381
|
+
for (const row2 of rows) {
|
|
11382
|
+
let dom = byDomain.get(row2.domain_key);
|
|
11212
11383
|
if (!dom) {
|
|
11213
11384
|
dom = new Map;
|
|
11214
|
-
byDomain.set(
|
|
11385
|
+
byDomain.set(row2.domain_key, dom);
|
|
11215
11386
|
}
|
|
11216
|
-
let req = dom.get(
|
|
11387
|
+
let req = dom.get(row2.req_id);
|
|
11217
11388
|
if (!req) {
|
|
11218
|
-
req = { id:
|
|
11219
|
-
dom.set(
|
|
11389
|
+
req = { id: row2.req_id, status: row2.req_status, cells: new Map };
|
|
11390
|
+
dom.set(row2.req_id, req);
|
|
11220
11391
|
}
|
|
11221
|
-
req.cells.set(
|
|
11392
|
+
req.cells.set(row2.repo, { impl: row2.implemented, verif: row2.verified });
|
|
11222
11393
|
}
|
|
11223
11394
|
const domains = [...byDomain.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([key, reqMap]) => ({
|
|
11224
11395
|
key,
|
|
@@ -11469,6 +11640,10 @@ function mountEditor(app, bindHost = DEFAULT_BIND_HOST) {
|
|
|
11469
11640
|
Lives in (optional, single path)
|
|
11470
11641
|
<input type="text" name="livesIn" placeholder="path/to/impl.ts" />
|
|
11471
11642
|
</label>
|
|
11643
|
+
<label>
|
|
11644
|
+
Verified in (optional, single path)
|
|
11645
|
+
<input type="text" name="verifiedIn" placeholder="path/to/impl.test.ts" />
|
|
11646
|
+
</label>
|
|
11472
11647
|
<label>
|
|
11473
11648
|
Relates to (optional, comma-separated ids)
|
|
11474
11649
|
<input type="text" name="relates" placeholder="BILLING-001, BILLING-002" />
|
|
@@ -11507,6 +11682,7 @@ function mountEditor(app, bindHost = DEFAULT_BIND_HOST) {
|
|
|
11507
11682
|
const statement = field(form, "statement");
|
|
11508
11683
|
const why = field(form, "why");
|
|
11509
11684
|
const livesIn = field(form, "livesIn");
|
|
11685
|
+
const verifiedIn = field(form, "verifiedIn");
|
|
11510
11686
|
const relates = field(form, "relates");
|
|
11511
11687
|
const payload = {
|
|
11512
11688
|
key: field(form, "key"),
|
|
@@ -11516,6 +11692,8 @@ function mountEditor(app, bindHost = DEFAULT_BIND_HOST) {
|
|
|
11516
11692
|
payload.why = why;
|
|
11517
11693
|
if (livesIn.length > 0)
|
|
11518
11694
|
payload.livesIn = livesIn;
|
|
11695
|
+
if (verifiedIn.length > 0)
|
|
11696
|
+
payload.verifiedIn = verifiedIn;
|
|
11519
11697
|
if (relates.length > 0)
|
|
11520
11698
|
payload.relates = relates;
|
|
11521
11699
|
const res = await app.request("/api/requirements", {
|
|
@@ -11832,15 +12010,15 @@ var init_relations2 = __esm(() => {
|
|
|
11832
12010
|
import { html as html10, raw as raw9 } from "hono/html";
|
|
11833
12011
|
function coverageRollups(coverage) {
|
|
11834
12012
|
const cov = new Map;
|
|
11835
|
-
for (const
|
|
11836
|
-
if (
|
|
12013
|
+
for (const row2 of coverage) {
|
|
12014
|
+
if (row2.req_status !== "Active")
|
|
11837
12015
|
continue;
|
|
11838
|
-
const c = cov.get(
|
|
11839
|
-
if (
|
|
12016
|
+
const c = cov.get(row2.req_id) ?? { impl: 0, verif: 0 };
|
|
12017
|
+
if (row2.implemented)
|
|
11840
12018
|
c.impl += 1;
|
|
11841
|
-
if (
|
|
12019
|
+
if (row2.verified)
|
|
11842
12020
|
c.verif += 1;
|
|
11843
|
-
cov.set(
|
|
12021
|
+
cov.set(row2.req_id, c);
|
|
11844
12022
|
}
|
|
11845
12023
|
return cov;
|
|
11846
12024
|
}
|
|
@@ -12187,15 +12365,15 @@ function mountSetup(app) {
|
|
|
12187
12365
|
const memberCount = members.length;
|
|
12188
12366
|
const note = readingNote(mode, memberCount);
|
|
12189
12367
|
const boundByRepo = new Map;
|
|
12190
|
-
for (const
|
|
12191
|
-
if (!
|
|
12368
|
+
for (const row2 of cov) {
|
|
12369
|
+
if (!row2.implemented)
|
|
12192
12370
|
continue;
|
|
12193
|
-
let set = boundByRepo.get(
|
|
12371
|
+
let set = boundByRepo.get(row2.repo);
|
|
12194
12372
|
if (!set) {
|
|
12195
12373
|
set = new Set;
|
|
12196
|
-
boundByRepo.set(
|
|
12374
|
+
boundByRepo.set(row2.repo, set);
|
|
12197
12375
|
}
|
|
12198
|
-
set.add(
|
|
12376
|
+
set.add(row2.req_id);
|
|
12199
12377
|
}
|
|
12200
12378
|
const body = html11`
|
|
12201
12379
|
${navBar("setup")}
|
|
@@ -12433,6 +12611,7 @@ function mintFieldsFromBody(body) {
|
|
|
12433
12611
|
statement: typeof body.statement === "string" ? body.statement : "",
|
|
12434
12612
|
why: typeof body.why === "string" ? body.why : "",
|
|
12435
12613
|
livesIn: toStringList(body.livesIn),
|
|
12614
|
+
verifiedIn: toStringList(body.verifiedIn),
|
|
12436
12615
|
relates: toStringList(body.relates),
|
|
12437
12616
|
issue: typeof body.issue === "string" && body.issue.trim() !== "" ? body.issue.trim() : undefined,
|
|
12438
12617
|
status: body.status === "draft" ? "draft" : "active"
|
|
@@ -12442,6 +12621,7 @@ function successorFieldsFromBody(body) {
|
|
|
12442
12621
|
return {
|
|
12443
12622
|
why: typeof body.why === "string" ? body.why : undefined,
|
|
12444
12623
|
livesIn: "livesIn" in body ? toStringList(body.livesIn) : undefined,
|
|
12624
|
+
verifiedIn: "verifiedIn" in body ? toStringList(body.verifiedIn) : undefined,
|
|
12445
12625
|
relates: "relates" in body ? toStringList(body.relates) : undefined,
|
|
12446
12626
|
issue: typeof body.issue === "string" && body.issue.trim() !== "" ? body.issue.trim() : undefined
|
|
12447
12627
|
};
|
|
@@ -12454,6 +12634,8 @@ function amendFieldsFromBody(body) {
|
|
|
12454
12634
|
fields.why = typeof body.why === "string" ? body.why : null;
|
|
12455
12635
|
if ("livesIn" in body)
|
|
12456
12636
|
fields.livesIn = toStringList(body.livesIn);
|
|
12637
|
+
if ("verifiedIn" in body)
|
|
12638
|
+
fields.verifiedIn = toStringList(body.verifiedIn);
|
|
12457
12639
|
if ("relates" in body)
|
|
12458
12640
|
fields.relates = toStringList(body.relates);
|
|
12459
12641
|
return Object.keys(fields).length === 0 ? null : fields;
|
|
@@ -12515,8 +12697,8 @@ function mountApi(app, storage2, platformDir = process.cwd(), bindHost = DEFAULT
|
|
|
12515
12697
|
}));
|
|
12516
12698
|
app.get("/api/requirements/:id", guarded((c) => {
|
|
12517
12699
|
const id = c.req.param("id") ?? "";
|
|
12518
|
-
const { row } = getRecord(storage2, id);
|
|
12519
|
-
return
|
|
12700
|
+
const { row: row2 } = getRecord(storage2, id);
|
|
12701
|
+
return row2 ? c.json(row2) : c.json({ error: "not found" }, 404);
|
|
12520
12702
|
}));
|
|
12521
12703
|
app.post("/api/requirements", guarded(async function createRequirement(c) {
|
|
12522
12704
|
if (!featureEnabled("editor")) {
|
|
@@ -12562,7 +12744,7 @@ function mountApi(app, storage2, platformDir = process.cwd(), bindHost = DEFAULT
|
|
|
12562
12744
|
return withWriteLock(async () => {
|
|
12563
12745
|
const fields = amendFieldsFromBody(body);
|
|
12564
12746
|
if (fields === null) {
|
|
12565
|
-
return c.json({ error: "nothing to amend \u2014 provide statement, why, livesIn, or relates" }, 400);
|
|
12747
|
+
return c.json({ error: "nothing to amend \u2014 provide statement, why, livesIn, verifiedIn, or relates" }, 400);
|
|
12566
12748
|
}
|
|
12567
12749
|
const r = await amend({ platformDir, id, fields });
|
|
12568
12750
|
if (!r.ok)
|
|
@@ -12635,7 +12817,8 @@ function mountApi(app, storage2, platformDir = process.cwd(), bindHost = DEFAULT
|
|
|
12635
12817
|
targetKey,
|
|
12636
12818
|
statement: typeof body.text === "string" ? body.text : undefined,
|
|
12637
12819
|
why: typeof body.why === "string" ? body.why : undefined,
|
|
12638
|
-
livesIn: "lives" in body ? toStringList(body.lives) : undefined
|
|
12820
|
+
livesIn: "lives" in body ? toStringList(body.lives) : undefined,
|
|
12821
|
+
verifiedIn: "verifiedIn" in body ? toStringList(body.verifiedIn) : undefined
|
|
12639
12822
|
}, async (reqId) => {
|
|
12640
12823
|
await runIndex({ platformDir, storage: storage2 });
|
|
12641
12824
|
return storage2.listTags({ req_id: reqId });
|
|
@@ -12727,12 +12910,12 @@ function mountApi(app, storage2, platformDir = process.cwd(), bindHost = DEFAULT
|
|
|
12727
12910
|
if (!featureEnabled("relations")) {
|
|
12728
12911
|
return c.json({ error: featureDisabledMessage("relations") }, 404);
|
|
12729
12912
|
}
|
|
12730
|
-
const
|
|
12731
|
-
if (
|
|
12913
|
+
const format2 = c.req.query("format");
|
|
12914
|
+
if (format2 !== undefined && format2 !== "mermaid") {
|
|
12732
12915
|
return c.json({ error: 'format must be "mermaid" when provided' }, 400);
|
|
12733
12916
|
}
|
|
12734
12917
|
const rows = relations(storage2).rows;
|
|
12735
|
-
if (
|
|
12918
|
+
if (format2 === "mermaid") {
|
|
12736
12919
|
return c.text(renderRelations(rows, "mermaid"));
|
|
12737
12920
|
}
|
|
12738
12921
|
return c.json(sortRelations(rows));
|
|
@@ -12987,7 +13170,7 @@ async function runProbe() {
|
|
|
12987
13170
|
}
|
|
12988
13171
|
console.log("serve --probe OK");
|
|
12989
13172
|
server.stop();
|
|
12990
|
-
|
|
13173
|
+
await exit(EXIT.OK);
|
|
12991
13174
|
}
|
|
12992
13175
|
var serveCommand;
|
|
12993
13176
|
var init_serve = __esm(() => {
|
|
@@ -12999,6 +13182,7 @@ var init_serve = __esm(() => {
|
|
|
12999
13182
|
init_prompt();
|
|
13000
13183
|
init_api();
|
|
13001
13184
|
init_lan();
|
|
13185
|
+
init_stdout();
|
|
13002
13186
|
init_sqlite();
|
|
13003
13187
|
init__args();
|
|
13004
13188
|
init__shared();
|
|
@@ -13096,7 +13280,7 @@ var init_serve = __esm(() => {
|
|
|
13096
13280
|
});
|
|
13097
13281
|
} catch (err) {
|
|
13098
13282
|
storage2.close();
|
|
13099
|
-
handleInvalidDomainFiles(err, false);
|
|
13283
|
+
await handleInvalidDomainFiles(err, false);
|
|
13100
13284
|
handleStorageUnavailable(err, dbPath);
|
|
13101
13285
|
console.error(`spec serve: failed to start on ${urlHost(host, port)}:`, err);
|
|
13102
13286
|
process.exit(EXIT.FAILURE);
|
|
@@ -13124,7 +13308,7 @@ __export(exports_docs, {
|
|
|
13124
13308
|
createDocsFetchHandler: () => createDocsFetchHandler
|
|
13125
13309
|
});
|
|
13126
13310
|
import { existsSync as existsSync15, statSync as statSync6 } from "fs";
|
|
13127
|
-
import { join as join16, normalize, sep as
|
|
13311
|
+
import { join as join16, normalize, sep as sep5 } from "path";
|
|
13128
13312
|
import { fileURLToPath } from "url";
|
|
13129
13313
|
import { defineCommand as defineCommand25 } from "citty";
|
|
13130
13314
|
function resolveDocsRootFrom(candidates) {
|
|
@@ -13142,7 +13326,7 @@ function resolveDocsRoot() {
|
|
|
13142
13326
|
}
|
|
13143
13327
|
function createDocsFetchHandler(root) {
|
|
13144
13328
|
const normalRoot = normalize(root);
|
|
13145
|
-
const rootPrefix = normalRoot.endsWith(
|
|
13329
|
+
const rootPrefix = normalRoot.endsWith(sep5) ? normalRoot : normalRoot + sep5;
|
|
13146
13330
|
return (req) => {
|
|
13147
13331
|
let pathname;
|
|
13148
13332
|
try {
|
|
@@ -13195,7 +13379,7 @@ async function runProbe2(root) {
|
|
|
13195
13379
|
}
|
|
13196
13380
|
console.log("docs --probe OK");
|
|
13197
13381
|
server.stop();
|
|
13198
|
-
|
|
13382
|
+
await exit(EXIT.OK);
|
|
13199
13383
|
}
|
|
13200
13384
|
function exitNoDocsRoot() {
|
|
13201
13385
|
console.error("spec docs: no built documentation found. From a checkout run `bun run build:site` " + "(writes packages/site/dist); the published npm package ships it at dist/docs.");
|
|
@@ -13204,6 +13388,7 @@ function exitNoDocsRoot() {
|
|
|
13204
13388
|
var docsCommand;
|
|
13205
13389
|
var init_docs2 = __esm(() => {
|
|
13206
13390
|
init_constants();
|
|
13391
|
+
init_stdout();
|
|
13207
13392
|
docsCommand = defineCommand25({
|
|
13208
13393
|
meta: {
|
|
13209
13394
|
name: "docs",
|
|
@@ -13337,7 +13522,9 @@ var init___smoke = __esm(() => {
|
|
|
13337
13522
|
|
|
13338
13523
|
// src/cli.ts
|
|
13339
13524
|
init_package();
|
|
13525
|
+
init_stdout();
|
|
13340
13526
|
import { defineCommand as defineCommand27, runMain, showUsage } from "citty";
|
|
13527
|
+
routeStdout();
|
|
13341
13528
|
var main = defineCommand27({
|
|
13342
13529
|
meta: {
|
|
13343
13530
|
name: "spec",
|