artifact-graph 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -4
- package/INSTALL.md +3 -3
- package/LICENSE +1 -1
- package/NOTICE +17 -3
- package/README.md +2 -2
- package/README.zh-CN.md +15 -15
- package/contracts/e2e-test/schema.json +315 -0
- package/dist/cli.js +1757 -404
- package/dist/index.cjs +1418 -485
- package/dist/index.d.cts +332 -1
- package/dist/index.d.ts +332 -1
- package/dist/index.js +1306 -387
- package/package.json +12 -6
package/dist/index.js
CHANGED
|
@@ -1,48 +1,49 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { accessSync, constants as fsConstants, existsSync as existsSync2, statSync } from "fs";
|
|
6
|
-
import { mkdir as mkdir4, readFile as readFile2, readdir, writeFile as writeFile3 } from "fs/promises";
|
|
7
|
-
import { basename as basename2, dirname as dirname3, extname, isAbsolute as isAbsolute3, join as join5, relative as relative2, resolve as resolve3 } from "path";
|
|
1
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
|
+
var __esm = (fn, res) => function __init() {
|
|
3
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
4
|
+
};
|
|
8
5
|
|
|
9
6
|
// src/packet-constants.ts
|
|
10
|
-
var ALWAYS_PRESENT_ITEMS
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
7
|
+
var ALWAYS_PRESENT_ITEMS, BASELINE_ITEMS_COUNT, BASELINE_CONSTRAINTS, BASELINE_CONSTRAINTS_COUNT;
|
|
8
|
+
var init_packet_constants = __esm({
|
|
9
|
+
"src/packet-constants.ts"() {
|
|
10
|
+
"use strict";
|
|
11
|
+
ALWAYS_PRESENT_ITEMS = [
|
|
12
|
+
{ path: "AGENTS.md", reason: "project agent instructions" },
|
|
13
|
+
{ path: "CLAUDE.md", reason: "project instructions" },
|
|
14
|
+
{ path: "artifacts/artifact-chain-spec.md", reason: "artifact chain specification" },
|
|
15
|
+
{ path: "artifacts/blueprints/generation-packet-spec.md", reason: "generation packet spec" },
|
|
16
|
+
{ path: "artifacts/blueprints/implementation-blueprint.md", reason: "implementation blueprint" },
|
|
17
|
+
{ path: "artifacts/contracts/interface-contracts.md", reason: "interface contracts" },
|
|
18
|
+
{ path: "artifacts/contracts/data-contracts.md", reason: "data contracts" },
|
|
19
|
+
{ path: "artifacts/contracts/application-state-machines.md", reason: "application state machines" },
|
|
20
|
+
{ path: "artifacts/contracts/error-model.md", reason: "error model" },
|
|
21
|
+
{ path: "artifacts/contracts/report-contracts.md", reason: "report contracts" },
|
|
22
|
+
{ path: "artifacts/contracts/ui-flow-contracts.md", reason: "UI flow contracts" },
|
|
23
|
+
{ path: "artifacts/contracts/non-functional-budgets.md", reason: "non-functional budgets" },
|
|
24
|
+
{ path: "artifacts/domain/domain-glossary.md", reason: "domain glossary" },
|
|
25
|
+
{ path: "artifacts/domain/bounded-context-map.md", reason: "bounded context map" },
|
|
26
|
+
{ path: "artifacts/domain/domain-invariants.md", reason: "domain invariants" },
|
|
27
|
+
{ path: "artifacts/tests/rule-golden-cases.md", reason: "rule golden cases" },
|
|
28
|
+
{ path: "artifacts/tests/verification-fixtures.md", reason: "verification fixtures" },
|
|
29
|
+
{ path: "artifacts/design/test-strategy.md", reason: "test strategy" },
|
|
30
|
+
{ path: "artifacts/traceability-matrix-v2.md", reason: "traceability matrix" }
|
|
31
|
+
];
|
|
32
|
+
BASELINE_ITEMS_COUNT = ALWAYS_PRESENT_ITEMS.length;
|
|
33
|
+
BASELINE_CONSTRAINTS = [
|
|
34
|
+
{ id: "C-ARCH-01", description: "\u6280\u672F\u6808\u5FC5\u987B\u4E3A TypeScript \u5168\u6808", source: "artifacts/decisions/D-ARCH-01.md" },
|
|
35
|
+
{ id: "C-ARCH-03", description: "\u4E0D\u53EF\u5728 Quick Check \u8DEF\u5F84\u5F15\u5165 LLM", source: "artifacts/decisions/D-ARCH-03.md" },
|
|
36
|
+
{ id: "C-ARCH-05", description: "\u4EA7\u54C1\u5F62\u6001\u4E3A Tauri v2 \u684C\u9762\u5E94\u7528\uFF0C\u4E0D\u4F7F\u7528\u5DF2\u5E9F\u5F03\u7684 Web Dashboard", source: "artifacts/decisions/D-ARCH-05.md" },
|
|
37
|
+
{ id: "C-RULE-01", description: "SEC severity \u4E0D\u5141\u8BB8\u964D\u7EA7", source: "artifacts/decisions/D-RULE-01.md" },
|
|
38
|
+
{ id: "C-GEN-01", description: "\u5B9E\u73B0\u84DD\u56FE\u4E0D\u5F97\u5F15\u5165 PRD \u4E2D\u672A\u51FA\u73B0\u7684\u65B0\u529F\u80FD", source: "artifacts/blueprints/implementation-blueprint.md" },
|
|
39
|
+
{ id: "C-GEN-02", description: "\u6587\u4EF6\u53D8\u66F4\u5FC5\u987B\u5148\u68C0\u67E5 interface-contracts\u3001data-contracts\u3001error-model \u662F\u5426\u9700\u8981\u540C\u6B65", source: "artifacts/blueprints/implementation-blueprint.md" },
|
|
40
|
+
{ id: "C-GEN-03", description: "\u4E0D\u5F97\u5C06\u5E9F\u5F03 Web Dashboard \u8BBE\u8BA1\u4F5C\u4E3A\u5B9E\u73B0\u4F9D\u636E", source: "artifacts/blueprints/implementation-blueprint.md" }
|
|
41
|
+
];
|
|
42
|
+
BASELINE_CONSTRAINTS_COUNT = BASELINE_CONSTRAINTS.length;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
42
45
|
|
|
43
46
|
// src/packet-validator.ts
|
|
44
|
-
var MIN_VALIDATION_COMMANDS = 4;
|
|
45
|
-
var VALID_PACKET_TARGET_TYPES = ["feature", "scenario", "decision", "design", "e2e_test"];
|
|
46
47
|
function isPacketTargetType(type) {
|
|
47
48
|
return VALID_PACKET_TARGET_TYPES.includes(type);
|
|
48
49
|
}
|
|
@@ -55,7 +56,6 @@ function isPacketTargetTypeDynamic(type, schema) {
|
|
|
55
56
|
}
|
|
56
57
|
return isPacketTargetType(type);
|
|
57
58
|
}
|
|
58
|
-
var VALID_TARGET_TYPES = [...VALID_PACKET_TARGET_TYPES];
|
|
59
59
|
function validatePacket(packet, schema) {
|
|
60
60
|
const issues = [];
|
|
61
61
|
if (packet.schemaVersion !== "1.0") {
|
|
@@ -233,16 +233,6 @@ function validatePacket(packet, schema) {
|
|
|
233
233
|
const hasError = issues.some((i) => i.severity === "error");
|
|
234
234
|
return { ok: !hasError, issues };
|
|
235
235
|
}
|
|
236
|
-
var REQUIRED_SECTIONS = [
|
|
237
|
-
"## 1. \u5B9E\u73B0\u76EE\u6807",
|
|
238
|
-
"## 2. \u4E0A\u4E0B\u6587\u6E05\u5355\u6458\u8981",
|
|
239
|
-
"## 3. Required Baseline\uFF08\u5FC5\u8BFB\u5236\u54C1\uFF09",
|
|
240
|
-
"## 8. Implementation Blueprint Draft\uFF08\u5B9E\u73B0\u84DD\u56FE\u8349\u6848\uFF09",
|
|
241
|
-
"### 8.3 \u7EA6\u675F\u4E0E\u8FB9\u754C",
|
|
242
|
-
"### 8.4 \u9A8C\u8BC1\u547D\u4EE4",
|
|
243
|
-
"### 8.5 \u63A8\u8350\u5BA1\u9605\u987A\u5E8F",
|
|
244
|
-
"### 8.6 \u98CE\u9669\u68C0\u67E5\u6E05\u5355"
|
|
245
|
-
];
|
|
246
236
|
function validatePacketMarkdown(markdown) {
|
|
247
237
|
const issues = [];
|
|
248
238
|
for (const section of REQUIRED_SECTIONS) {
|
|
@@ -258,6 +248,26 @@ function validatePacketMarkdown(markdown) {
|
|
|
258
248
|
const hasError = issues.some((i) => i.severity === "error");
|
|
259
249
|
return { ok: !hasError, issues };
|
|
260
250
|
}
|
|
251
|
+
var MIN_VALIDATION_COMMANDS, VALID_PACKET_TARGET_TYPES, VALID_TARGET_TYPES, REQUIRED_SECTIONS;
|
|
252
|
+
var init_packet_validator = __esm({
|
|
253
|
+
"src/packet-validator.ts"() {
|
|
254
|
+
"use strict";
|
|
255
|
+
init_packet_constants();
|
|
256
|
+
MIN_VALIDATION_COMMANDS = 4;
|
|
257
|
+
VALID_PACKET_TARGET_TYPES = ["feature", "scenario", "decision", "design", "e2e_test"];
|
|
258
|
+
VALID_TARGET_TYPES = [...VALID_PACKET_TARGET_TYPES];
|
|
259
|
+
REQUIRED_SECTIONS = [
|
|
260
|
+
"## 1. \u5B9E\u73B0\u76EE\u6807",
|
|
261
|
+
"## 2. \u4E0A\u4E0B\u6587\u6E05\u5355\u6458\u8981",
|
|
262
|
+
"## 3. Required Baseline\uFF08\u5FC5\u8BFB\u5236\u54C1\uFF09",
|
|
263
|
+
"## 8. Implementation Blueprint Draft\uFF08\u5B9E\u73B0\u84DD\u56FE\u8349\u6848\uFF09",
|
|
264
|
+
"### 8.3 \u7EA6\u675F\u4E0E\u8FB9\u754C",
|
|
265
|
+
"### 8.4 \u9A8C\u8BC1\u547D\u4EE4",
|
|
266
|
+
"### 8.5 \u63A8\u8350\u5BA1\u9605\u987A\u5E8F",
|
|
267
|
+
"### 8.6 \u98CE\u9669\u68C0\u67E5\u6E05\u5355"
|
|
268
|
+
];
|
|
269
|
+
}
|
|
270
|
+
});
|
|
261
271
|
|
|
262
272
|
// src/glob-matcher.ts
|
|
263
273
|
function matchesRunnerGlob(filePath, pattern) {
|
|
@@ -297,6 +307,11 @@ function normalizeGlobValue(value) {
|
|
|
297
307
|
function escapeRegexCharacter(character) {
|
|
298
308
|
return "\\^$+?.()|{}[]".includes(character) ? String.fromCharCode(92) + character : character;
|
|
299
309
|
}
|
|
310
|
+
var init_glob_matcher = __esm({
|
|
311
|
+
"src/glob-matcher.ts"() {
|
|
312
|
+
"use strict";
|
|
313
|
+
}
|
|
314
|
+
});
|
|
300
315
|
|
|
301
316
|
// src/target-selector.ts
|
|
302
317
|
function parseTargetSelector(value) {
|
|
@@ -306,14 +321,6 @@ function parseTargetSelector(value) {
|
|
|
306
321
|
}
|
|
307
322
|
return { type: value.slice(0, separator), id: value.slice(separator + 1) };
|
|
308
323
|
}
|
|
309
|
-
var LEGACY_FLAGS = ["feature", "scenario", "decision", "design", "e2e-test"];
|
|
310
|
-
var LEGACY_FLAG_TO_TYPE = {
|
|
311
|
-
feature: "feature",
|
|
312
|
-
scenario: "scenario",
|
|
313
|
-
decision: "decision",
|
|
314
|
-
design: "design",
|
|
315
|
-
"e2e-test": "e2e_test"
|
|
316
|
-
};
|
|
317
324
|
function resolveCliTarget(flags, schema) {
|
|
318
325
|
const hasTarget = typeof flags.target === "string";
|
|
319
326
|
const legacyEntries = LEGACY_FLAGS.filter((flag) => typeof flags[flag] === "string").map((flag) => flag);
|
|
@@ -346,14 +353,23 @@ function resolveCliTarget(flags, schema) {
|
|
|
346
353
|
"\u672A\u6307\u5B9A target\uFF1A\u8BF7\u4F7F\u7528 --target <type>:<id> \u6216 --feature/--scenario/--decision/--design/--e2e-test <id>"
|
|
347
354
|
);
|
|
348
355
|
}
|
|
356
|
+
var LEGACY_FLAGS, LEGACY_FLAG_TO_TYPE;
|
|
357
|
+
var init_target_selector = __esm({
|
|
358
|
+
"src/target-selector.ts"() {
|
|
359
|
+
"use strict";
|
|
360
|
+
init_index();
|
|
361
|
+
LEGACY_FLAGS = ["feature", "scenario", "decision", "design", "e2e-test"];
|
|
362
|
+
LEGACY_FLAG_TO_TYPE = {
|
|
363
|
+
feature: "feature",
|
|
364
|
+
scenario: "scenario",
|
|
365
|
+
decision: "decision",
|
|
366
|
+
design: "design",
|
|
367
|
+
"e2e-test": "e2e_test"
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
});
|
|
349
371
|
|
|
350
372
|
// src/packet-assembler.ts
|
|
351
|
-
var DEFAULT_VALIDATION_COMMANDS = [
|
|
352
|
-
"pnpm build",
|
|
353
|
-
"pnpm test",
|
|
354
|
-
"artifact-graph validate --root . --warning-only",
|
|
355
|
-
"artifact-graph version-lock audit --root . --strict-missing-lock"
|
|
356
|
-
];
|
|
357
373
|
function toPacketItem(item) {
|
|
358
374
|
const pi = {
|
|
359
375
|
path: item.path,
|
|
@@ -490,12 +506,6 @@ function renderContextCategory(cat, opts) {
|
|
|
490
506
|
}
|
|
491
507
|
return lines;
|
|
492
508
|
}
|
|
493
|
-
var COMMAND_COMMENTS = {
|
|
494
|
-
"pnpm build": "\u6784\u5EFA\u9879\u76EE",
|
|
495
|
-
"pnpm test": "\u8FD0\u884C\u9879\u76EE\u6D4B\u8BD5",
|
|
496
|
-
"artifact-graph validate --root . --warning-only": "\u5236\u54C1\u94FE\u4E00\u81F4\u6027\u6821\u9A8C\uFF08warning-only\uFF09",
|
|
497
|
-
"artifact-graph version-lock audit --root . --strict-missing-lock": "\u5236\u54C1\u94FE\u7248\u672C\u9501\u4E25\u683C\u5BA1\u8BA1"
|
|
498
|
-
};
|
|
499
509
|
function classifyCategories(manifest) {
|
|
500
510
|
let baseline = { category: "baseline", total: 0, items: [] };
|
|
501
511
|
const direct = [];
|
|
@@ -792,12 +802,29 @@ function renderPacketMarkdown(packet) {
|
|
|
792
802
|
lines.push("> \u63D0\u793A\uFF1A\u4F7F\u7528 Ctrl+F \u641C\u7D22 `[\u5FC5\u8BFB]` \u5FEB\u901F\u5B9A\u4F4D\u5FC5\u8BFB\u5236\u54C1\u3002");
|
|
793
803
|
return lines.join("\n");
|
|
794
804
|
}
|
|
805
|
+
var DEFAULT_VALIDATION_COMMANDS, COMMAND_COMMENTS;
|
|
806
|
+
var init_packet_assembler = __esm({
|
|
807
|
+
"src/packet-assembler.ts"() {
|
|
808
|
+
"use strict";
|
|
809
|
+
init_packet_constants();
|
|
810
|
+
DEFAULT_VALIDATION_COMMANDS = [
|
|
811
|
+
"pnpm build",
|
|
812
|
+
"pnpm test",
|
|
813
|
+
"artifact-graph validate --root . --warning-only",
|
|
814
|
+
"artifact-graph version-lock audit --root . --strict-missing-lock"
|
|
815
|
+
];
|
|
816
|
+
COMMAND_COMMENTS = {
|
|
817
|
+
"pnpm build": "\u6784\u5EFA\u9879\u76EE",
|
|
818
|
+
"pnpm test": "\u8FD0\u884C\u9879\u76EE\u6D4B\u8BD5",
|
|
819
|
+
"artifact-graph validate --root . --warning-only": "\u5236\u54C1\u94FE\u4E00\u81F4\u6027\u6821\u9A8C\uFF08warning-only\uFF09",
|
|
820
|
+
"artifact-graph version-lock audit --root . --strict-missing-lock": "\u5236\u54C1\u94FE\u7248\u672C\u9501\u4E25\u683C\u5BA1\u8BA1"
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
});
|
|
795
824
|
|
|
796
825
|
// src/packet-audit.ts
|
|
797
826
|
import { mkdir, writeFile } from "fs/promises";
|
|
798
827
|
import { join } from "path";
|
|
799
|
-
var VALID_TYPES = new Set(VALID_PACKET_TARGET_TYPES);
|
|
800
|
-
var VALID_TYPES_LABEL = VALID_PACKET_TARGET_TYPES.join(", ");
|
|
801
828
|
function parseTargetsFile(content, schema) {
|
|
802
829
|
const validTypes = schema ? new Set(getTargetArtifactTypes(schema)) : VALID_TYPES;
|
|
803
830
|
const validTypesLabel = [...validTypes].join(", ");
|
|
@@ -995,20 +1022,21 @@ async function discoverAndAuditPackets(root, options) {
|
|
|
995
1022
|
universalBaseline: effectiveBaseline
|
|
996
1023
|
}, graph);
|
|
997
1024
|
}
|
|
1025
|
+
var VALID_TYPES, VALID_TYPES_LABEL;
|
|
1026
|
+
var init_packet_audit = __esm({
|
|
1027
|
+
"src/packet-audit.ts"() {
|
|
1028
|
+
"use strict";
|
|
1029
|
+
init_index();
|
|
1030
|
+
init_packet_validator();
|
|
1031
|
+
VALID_TYPES = new Set(VALID_PACKET_TARGET_TYPES);
|
|
1032
|
+
VALID_TYPES_LABEL = VALID_PACKET_TARGET_TYPES.join(", ");
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
998
1035
|
|
|
999
1036
|
// src/packet-prompt.ts
|
|
1000
|
-
var TYPE_CLI_FLAG = {
|
|
1001
|
-
feature: "feature",
|
|
1002
|
-
scenario: "scenario",
|
|
1003
|
-
decision: "decision",
|
|
1004
|
-
design: "design",
|
|
1005
|
-
e2e_test: "e2e-test"
|
|
1006
|
-
};
|
|
1007
1037
|
function cliFlag(type) {
|
|
1008
1038
|
return TYPE_CLI_FLAG[type] ?? type;
|
|
1009
1039
|
}
|
|
1010
|
-
var DEFAULT_MAX_CHARS = 4e3;
|
|
1011
|
-
var MIN_PROMPT_CHARS = 320;
|
|
1012
1040
|
function renderPromptHeader(packet) {
|
|
1013
1041
|
const t = packet.target;
|
|
1014
1042
|
const typeLabelMap = { feature: "\u529F\u80FD", scenario: "\u573A\u666F", decision: "\u51B3\u7B56", design: "\u8BBE\u8BA1\u89C4\u683C", e2e_test: "E2E \u6D4B\u8BD5" };
|
|
@@ -1232,52 +1260,24 @@ function renderPacketPrompt(packet, options) {
|
|
|
1232
1260
|
minRequired: absMinText.length
|
|
1233
1261
|
};
|
|
1234
1262
|
}
|
|
1263
|
+
var TYPE_CLI_FLAG, DEFAULT_MAX_CHARS, MIN_PROMPT_CHARS;
|
|
1264
|
+
var init_packet_prompt = __esm({
|
|
1265
|
+
"src/packet-prompt.ts"() {
|
|
1266
|
+
"use strict";
|
|
1267
|
+
init_packet_constants();
|
|
1268
|
+
TYPE_CLI_FLAG = {
|
|
1269
|
+
feature: "feature",
|
|
1270
|
+
scenario: "scenario",
|
|
1271
|
+
decision: "decision",
|
|
1272
|
+
design: "design",
|
|
1273
|
+
e2e_test: "e2e-test"
|
|
1274
|
+
};
|
|
1275
|
+
DEFAULT_MAX_CHARS = 4e3;
|
|
1276
|
+
MIN_PROMPT_CHARS = 320;
|
|
1277
|
+
}
|
|
1278
|
+
});
|
|
1235
1279
|
|
|
1236
1280
|
// src/packet-prompt-validator.ts
|
|
1237
|
-
var REQUIRED_CHECKS = [
|
|
1238
|
-
{
|
|
1239
|
-
code: "PP-001",
|
|
1240
|
-
description: "\u76EE\u6807\u4FE1\u606F",
|
|
1241
|
-
test: (t) => /实现任务/.test(t),
|
|
1242
|
-
severity: "error"
|
|
1243
|
-
},
|
|
1244
|
-
{
|
|
1245
|
-
code: "PP-002",
|
|
1246
|
-
description: "packet \u547D\u4EE4\u6216\u6765\u6E90\u5F15\u7528",
|
|
1247
|
-
test: (t) => /packet\s+--root/.test(t) || /packet\.json/.test(t) || /artifact-graph\s+packet/.test(t),
|
|
1248
|
-
severity: "error"
|
|
1249
|
-
},
|
|
1250
|
-
{
|
|
1251
|
-
code: "PP-003",
|
|
1252
|
-
description: "\u9A8C\u8BC1\u547D\u4EE4",
|
|
1253
|
-
test: (t) => /验证命令/.test(t) || /artifact-graph\s+(build|test)/.test(t),
|
|
1254
|
-
severity: "error"
|
|
1255
|
-
},
|
|
1256
|
-
{
|
|
1257
|
-
code: "PP-004",
|
|
1258
|
-
description: "\u63D0\u4EA4\u8981\u6C42",
|
|
1259
|
-
test: (t) => /提交要求/.test(t) || /git\s+diff\s+--check/.test(t),
|
|
1260
|
-
severity: "error"
|
|
1261
|
-
},
|
|
1262
|
-
{
|
|
1263
|
-
code: "PP-005",
|
|
1264
|
-
description: "\u7981\u6B62\u4E8B\u9879",
|
|
1265
|
-
test: (t) => /禁止事项/.test(t),
|
|
1266
|
-
severity: "error"
|
|
1267
|
-
},
|
|
1268
|
-
{
|
|
1269
|
-
code: "PP-006",
|
|
1270
|
-
description: "\u4E0D\u5F97\u56DE\u9000\u89C4\u5219",
|
|
1271
|
-
test: (t) => /不得回退/.test(t) || /不要回退/.test(t) || /no[- ]?revert/i.test(t),
|
|
1272
|
-
severity: "error"
|
|
1273
|
-
},
|
|
1274
|
-
{
|
|
1275
|
-
code: "PP-007",
|
|
1276
|
-
description: "SEC severity \u89C4\u5219",
|
|
1277
|
-
test: (t) => /SEC\s+severity/.test(t) || /severity/.test(t) && /降级/.test(t),
|
|
1278
|
-
severity: "error"
|
|
1279
|
-
}
|
|
1280
|
-
];
|
|
1281
1281
|
function checkChineseDominance(text) {
|
|
1282
1282
|
const textOutsideCode = text.replace(/```[\s\S]*?```/g, "");
|
|
1283
1283
|
const cleanText = textOutsideCode.replace(/^[#>]+ .*$/gm, "").replace(/\[.*?\]\(.*?\)/g, "");
|
|
@@ -1312,15 +1312,62 @@ function validatePacketPrompt(prompt) {
|
|
|
1312
1312
|
const hasErrors = issues.some((i) => i.severity === "error");
|
|
1313
1313
|
return { ok: !hasErrors, issues };
|
|
1314
1314
|
}
|
|
1315
|
+
var REQUIRED_CHECKS;
|
|
1316
|
+
var init_packet_prompt_validator = __esm({
|
|
1317
|
+
"src/packet-prompt-validator.ts"() {
|
|
1318
|
+
"use strict";
|
|
1319
|
+
REQUIRED_CHECKS = [
|
|
1320
|
+
{
|
|
1321
|
+
code: "PP-001",
|
|
1322
|
+
description: "\u76EE\u6807\u4FE1\u606F",
|
|
1323
|
+
test: (t) => /实现任务/.test(t),
|
|
1324
|
+
severity: "error"
|
|
1325
|
+
},
|
|
1326
|
+
{
|
|
1327
|
+
code: "PP-002",
|
|
1328
|
+
description: "packet \u547D\u4EE4\u6216\u6765\u6E90\u5F15\u7528",
|
|
1329
|
+
test: (t) => /packet\s+--root/.test(t) || /packet\.json/.test(t) || /artifact-graph\s+packet/.test(t),
|
|
1330
|
+
severity: "error"
|
|
1331
|
+
},
|
|
1332
|
+
{
|
|
1333
|
+
code: "PP-003",
|
|
1334
|
+
description: "\u9A8C\u8BC1\u547D\u4EE4",
|
|
1335
|
+
test: (t) => /验证命令/.test(t) || /artifact-graph\s+(build|test)/.test(t),
|
|
1336
|
+
severity: "error"
|
|
1337
|
+
},
|
|
1338
|
+
{
|
|
1339
|
+
code: "PP-004",
|
|
1340
|
+
description: "\u63D0\u4EA4\u8981\u6C42",
|
|
1341
|
+
test: (t) => /提交要求/.test(t) || /git\s+diff\s+--check/.test(t),
|
|
1342
|
+
severity: "error"
|
|
1343
|
+
},
|
|
1344
|
+
{
|
|
1345
|
+
code: "PP-005",
|
|
1346
|
+
description: "\u7981\u6B62\u4E8B\u9879",
|
|
1347
|
+
test: (t) => /禁止事项/.test(t),
|
|
1348
|
+
severity: "error"
|
|
1349
|
+
},
|
|
1350
|
+
{
|
|
1351
|
+
code: "PP-006",
|
|
1352
|
+
description: "\u4E0D\u5F97\u56DE\u9000\u89C4\u5219",
|
|
1353
|
+
test: (t) => /不得回退/.test(t) || /不要回退/.test(t) || /no[- ]?revert/i.test(t),
|
|
1354
|
+
severity: "error"
|
|
1355
|
+
},
|
|
1356
|
+
{
|
|
1357
|
+
code: "PP-007",
|
|
1358
|
+
description: "SEC severity \u89C4\u5219",
|
|
1359
|
+
test: (t) => /SEC\s+severity/.test(t) || /severity/.test(t) && /降级/.test(t),
|
|
1360
|
+
severity: "error"
|
|
1361
|
+
}
|
|
1362
|
+
];
|
|
1363
|
+
}
|
|
1364
|
+
});
|
|
1315
1365
|
|
|
1316
1366
|
// src/versioned-traceability.ts
|
|
1317
1367
|
import { createHash } from "crypto";
|
|
1318
1368
|
import { existsSync } from "fs";
|
|
1319
1369
|
import { mkdir as mkdir2, readFile, writeFile as writeFile2 } from "fs/promises";
|
|
1320
1370
|
import { dirname, join as join2, relative } from "path";
|
|
1321
|
-
var VERSION_LOCK_PATH = "artifacts/traceability-version-lock.json";
|
|
1322
|
-
var VERSION_INDEX_SCHEMA_VERSION = "1.0";
|
|
1323
|
-
var VERSION_LOCK_SCHEMA_VERSION = "1.0";
|
|
1324
1371
|
async function buildVersionIndex(root, graph) {
|
|
1325
1372
|
const scannedGraph = graph ?? await scanArtifacts(root);
|
|
1326
1373
|
const hashCache = /* @__PURE__ */ new Map();
|
|
@@ -2120,6 +2167,17 @@ async function isFileActiveInRunner(root, filePath, runner) {
|
|
|
2120
2167
|
function sortUnique(items) {
|
|
2121
2168
|
return [...new Set(items)].sort((left, right) => left.localeCompare(right));
|
|
2122
2169
|
}
|
|
2170
|
+
var VERSION_LOCK_PATH, VERSION_INDEX_SCHEMA_VERSION, VERSION_LOCK_SCHEMA_VERSION;
|
|
2171
|
+
var init_versioned_traceability = __esm({
|
|
2172
|
+
"src/versioned-traceability.ts"() {
|
|
2173
|
+
"use strict";
|
|
2174
|
+
init_index();
|
|
2175
|
+
init_glob_matcher();
|
|
2176
|
+
VERSION_LOCK_PATH = "artifacts/traceability-version-lock.json";
|
|
2177
|
+
VERSION_INDEX_SCHEMA_VERSION = "1.0";
|
|
2178
|
+
VERSION_LOCK_SCHEMA_VERSION = "1.0";
|
|
2179
|
+
}
|
|
2180
|
+
});
|
|
2123
2181
|
|
|
2124
2182
|
// src/cli-resolver.ts
|
|
2125
2183
|
import { execFile } from "child_process";
|
|
@@ -2127,27 +2185,6 @@ import { constants } from "fs";
|
|
|
2127
2185
|
import { access } from "fs/promises";
|
|
2128
2186
|
import { isAbsolute, join as join3, resolve } from "path";
|
|
2129
2187
|
import { promisify } from "util";
|
|
2130
|
-
var execFileAsync = promisify(execFile);
|
|
2131
|
-
var KNOWN_COMMANDS = [
|
|
2132
|
-
"init",
|
|
2133
|
-
"scan",
|
|
2134
|
-
"validate",
|
|
2135
|
-
"query",
|
|
2136
|
-
"context",
|
|
2137
|
-
"packet",
|
|
2138
|
-
"packet-prompt",
|
|
2139
|
-
"packet-audit",
|
|
2140
|
-
"packet-prompt-audit",
|
|
2141
|
-
"version-index",
|
|
2142
|
-
"version-lock audit",
|
|
2143
|
-
"version-lock update",
|
|
2144
|
-
"version-lock bootstrap",
|
|
2145
|
-
"version-lock refresh",
|
|
2146
|
-
"trace-version",
|
|
2147
|
-
"next-id",
|
|
2148
|
-
"render",
|
|
2149
|
-
"doctor"
|
|
2150
|
-
];
|
|
2151
2188
|
async function resolveArtifactGraphCli(root, options = {}) {
|
|
2152
2189
|
const pathCli = await findCommandOnPath("artifact-graph");
|
|
2153
2190
|
const legacyCliPath = options.projectCliPath ?? process.env.ARTIFACT_GRAPH_LEGACY_CLI;
|
|
@@ -2283,11 +2320,38 @@ function isNodeCompatible(version) {
|
|
|
2283
2320
|
const major = Number(version.split(".")[0]);
|
|
2284
2321
|
return Number.isFinite(major) && major >= 22;
|
|
2285
2322
|
}
|
|
2323
|
+
var execFileAsync, KNOWN_COMMANDS;
|
|
2324
|
+
var init_cli_resolver = __esm({
|
|
2325
|
+
"src/cli-resolver.ts"() {
|
|
2326
|
+
"use strict";
|
|
2327
|
+
init_versioned_traceability();
|
|
2328
|
+
execFileAsync = promisify(execFile);
|
|
2329
|
+
KNOWN_COMMANDS = [
|
|
2330
|
+
"init",
|
|
2331
|
+
"scan",
|
|
2332
|
+
"validate",
|
|
2333
|
+
"query",
|
|
2334
|
+
"context",
|
|
2335
|
+
"packet",
|
|
2336
|
+
"packet-prompt",
|
|
2337
|
+
"packet-audit",
|
|
2338
|
+
"packet-prompt-audit",
|
|
2339
|
+
"version-index",
|
|
2340
|
+
"version-lock audit",
|
|
2341
|
+
"version-lock update",
|
|
2342
|
+
"version-lock bootstrap",
|
|
2343
|
+
"version-lock refresh",
|
|
2344
|
+
"trace-version",
|
|
2345
|
+
"next-id",
|
|
2346
|
+
"render",
|
|
2347
|
+
"doctor"
|
|
2348
|
+
];
|
|
2349
|
+
}
|
|
2350
|
+
});
|
|
2286
2351
|
|
|
2287
2352
|
// src/git-changes.ts
|
|
2288
2353
|
import { execFile as execFile2 } from "child_process";
|
|
2289
2354
|
import { promisify as promisify2 } from "util";
|
|
2290
|
-
var execFileAsync2 = promisify2(execFile2);
|
|
2291
2355
|
async function collectChangedPaths(root, options) {
|
|
2292
2356
|
const args = gitDiffArgs(options);
|
|
2293
2357
|
let stdout;
|
|
@@ -2297,8 +2361,10 @@ async function collectChangedPaths(root, options) {
|
|
|
2297
2361
|
const message = error instanceof Error ? error.message : String(error);
|
|
2298
2362
|
throw new Error(`Unable to collect git changed paths (${options.mode}): ${message}`);
|
|
2299
2363
|
}
|
|
2300
|
-
const
|
|
2301
|
-
const
|
|
2364
|
+
const trackedChangedPaths = normalizeGitPathList(stdout);
|
|
2365
|
+
const untrackedPaths = options.mode === "worktree" || options.mode === "staged" ? await collectUntrackedPaths(root) : [];
|
|
2366
|
+
const changedPaths = options.mode === "worktree" ? sortUnique2([...trackedChangedPaths, ...untrackedPaths]) : trackedChangedPaths;
|
|
2367
|
+
const unstagedPaths = options.mode === "staged" ? sortUnique2([...await collectUnstagedPaths(root), ...untrackedPaths]) : [];
|
|
2302
2368
|
const stagedUnstagedConflictPaths = options.mode === "staged" ? intersect(changedPaths, unstagedPaths) : [];
|
|
2303
2369
|
return {
|
|
2304
2370
|
root,
|
|
@@ -2330,6 +2396,14 @@ async function collectUnstagedPaths(root) {
|
|
|
2330
2396
|
return [];
|
|
2331
2397
|
}
|
|
2332
2398
|
}
|
|
2399
|
+
async function collectUntrackedPaths(root) {
|
|
2400
|
+
try {
|
|
2401
|
+
const { stdout } = await execFileAsync2("git", ["ls-files", "--others", "--exclude-standard"], { cwd: root });
|
|
2402
|
+
return normalizeGitPathList(stdout);
|
|
2403
|
+
} catch {
|
|
2404
|
+
return [];
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2333
2407
|
function normalizeGitPathList(stdout) {
|
|
2334
2408
|
return sortUnique2(stdout.split(/\r?\n/).map((line) => line.trim().replace(/\\/g, "/").replace(/^\.\//, "")).filter((line) => line.length > 0 && !line.startsWith("../") && !line.startsWith("/")));
|
|
2335
2409
|
}
|
|
@@ -2340,12 +2414,18 @@ function intersect(left, right) {
|
|
|
2340
2414
|
function sortUnique2(items) {
|
|
2341
2415
|
return [...new Set(items)].sort((left, right) => left.localeCompare(right));
|
|
2342
2416
|
}
|
|
2417
|
+
var execFileAsync2;
|
|
2418
|
+
var init_git_changes = __esm({
|
|
2419
|
+
"src/git-changes.ts"() {
|
|
2420
|
+
"use strict";
|
|
2421
|
+
execFileAsync2 = promisify2(execFile2);
|
|
2422
|
+
}
|
|
2423
|
+
});
|
|
2343
2424
|
|
|
2344
2425
|
// src/git-hook-path.ts
|
|
2345
2426
|
import { execFile as execFile3 } from "child_process";
|
|
2346
2427
|
import { isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
|
|
2347
2428
|
import { promisify as promisify3 } from "util";
|
|
2348
|
-
var execFileAsync3 = promisify3(execFile3);
|
|
2349
2429
|
async function resolveGitHookPath(root, hookName) {
|
|
2350
2430
|
const { stdout } = await execFileAsync3("git", [
|
|
2351
2431
|
"-C",
|
|
@@ -2358,74 +2438,19 @@ async function resolveGitHookPath(root, hookName) {
|
|
|
2358
2438
|
if (!value) throw new Error(`Git returned an empty hook path for ${hookName}`);
|
|
2359
2439
|
return isAbsolute2(value) ? resolve2(value) : resolve2(root, value);
|
|
2360
2440
|
}
|
|
2441
|
+
var execFileAsync3;
|
|
2442
|
+
var init_git_hook_path = __esm({
|
|
2443
|
+
"src/git-hook-path.ts"() {
|
|
2444
|
+
"use strict";
|
|
2445
|
+
execFileAsync3 = promisify3(execFile3);
|
|
2446
|
+
}
|
|
2447
|
+
});
|
|
2361
2448
|
|
|
2362
2449
|
// src/hook-installer.ts
|
|
2363
2450
|
import { constants as constants2 } from "fs";
|
|
2364
2451
|
import { randomUUID } from "crypto";
|
|
2365
2452
|
import { lstat, mkdir as mkdir3, open, readlink, rename, unlink } from "fs/promises";
|
|
2366
2453
|
import { basename, dirname as dirname2, join as join4 } from "path";
|
|
2367
|
-
var DEFAULT_MARKER_ID = "artifact-chain-assistant";
|
|
2368
|
-
var MANAGED_STATE_PREFIX = "# artifact-chain-assistant managed state v1:";
|
|
2369
|
-
var UnsupportedHookInterpreterError = class extends Error {
|
|
2370
|
-
constructor(hookPath, shebang) {
|
|
2371
|
-
super(
|
|
2372
|
-
`Refusing to modify non-shell Git hook ${hookPath} (${shebang || "missing shebang"}). Call the artifact-chain hook from the existing hook explicitly.`
|
|
2373
|
-
);
|
|
2374
|
-
this.hookPath = hookPath;
|
|
2375
|
-
this.shebang = shebang;
|
|
2376
|
-
}
|
|
2377
|
-
hookPath;
|
|
2378
|
-
shebang;
|
|
2379
|
-
code = "UNSUPPORTED_HOOK_INTERPRETER";
|
|
2380
|
-
};
|
|
2381
|
-
var SymlinkHookUnsupportedError = class extends Error {
|
|
2382
|
-
constructor(hookPath, linkTarget) {
|
|
2383
|
-
super(
|
|
2384
|
-
`SYMLINK_HOOK_UNSUPPORTED: Refusing to modify Git hook symlink ${hookPath} -> ${linkTarget}. Integrate the artifact-graph commands manually in the symlink target or replace the link yourself.`
|
|
2385
|
-
);
|
|
2386
|
-
this.hookPath = hookPath;
|
|
2387
|
-
this.linkTarget = linkTarget;
|
|
2388
|
-
}
|
|
2389
|
-
hookPath;
|
|
2390
|
-
linkTarget;
|
|
2391
|
-
code = "SYMLINK_HOOK_UNSUPPORTED";
|
|
2392
|
-
};
|
|
2393
|
-
var UnsupportedHookTypeError = class extends Error {
|
|
2394
|
-
constructor(hookPath) {
|
|
2395
|
-
super(`UNSUPPORTED_HOOK_TYPE: Git hook path ${hookPath} is not a regular file. Resolve it manually before retrying.`);
|
|
2396
|
-
this.hookPath = hookPath;
|
|
2397
|
-
}
|
|
2398
|
-
hookPath;
|
|
2399
|
-
code = "UNSUPPORTED_HOOK_TYPE";
|
|
2400
|
-
};
|
|
2401
|
-
var InvalidManagedHookStateError = class extends Error {
|
|
2402
|
-
constructor(hookPath, detail) {
|
|
2403
|
-
super(`INVALID_MANAGED_HOOK_STATE: Cannot safely update ${hookPath}: ${detail}`);
|
|
2404
|
-
this.hookPath = hookPath;
|
|
2405
|
-
}
|
|
2406
|
-
hookPath;
|
|
2407
|
-
code = "INVALID_MANAGED_HOOK_STATE";
|
|
2408
|
-
};
|
|
2409
|
-
var ConcurrentHookModificationError = class extends Error {
|
|
2410
|
-
constructor(hookPath) {
|
|
2411
|
-
super(`Refusing to overwrite concurrently modified Git hook ${hookPath}. Retry the operation after reviewing the hook.`);
|
|
2412
|
-
this.hookPath = hookPath;
|
|
2413
|
-
}
|
|
2414
|
-
hookPath;
|
|
2415
|
-
code = "CONCURRENT_HOOK_MODIFICATION";
|
|
2416
|
-
};
|
|
2417
|
-
var HookTransactionRollbackError = class extends Error {
|
|
2418
|
-
constructor(operationError, rollbackErrors) {
|
|
2419
|
-
super(
|
|
2420
|
-
`HOOK_TRANSACTION_ROLLBACK_FAILED: Hook update failed and ${rollbackErrors.length} rollback operation(s) also failed: ` + rollbackErrors.map(({ hookPath, error }) => `${hookPath}: ${error.message}`).join("; ")
|
|
2421
|
-
);
|
|
2422
|
-
this.operationError = operationError;
|
|
2423
|
-
this.rollbackErrors = rollbackErrors;
|
|
2424
|
-
}
|
|
2425
|
-
operationError;
|
|
2426
|
-
rollbackErrors;
|
|
2427
|
-
code = "HOOK_TRANSACTION_ROLLBACK_FAILED";
|
|
2428
|
-
};
|
|
2429
2454
|
function detectHookInterpreter(content) {
|
|
2430
2455
|
if (content.trim().length === 0) {
|
|
2431
2456
|
return "empty";
|
|
@@ -2783,44 +2808,76 @@ function findManagedRange(content, begin, end) {
|
|
|
2783
2808
|
end: endLine < 0 ? content.length : endLine + 1
|
|
2784
2809
|
};
|
|
2785
2810
|
}
|
|
2811
|
+
var DEFAULT_MARKER_ID, MANAGED_STATE_PREFIX, UnsupportedHookInterpreterError, SymlinkHookUnsupportedError, UnsupportedHookTypeError, InvalidManagedHookStateError, ConcurrentHookModificationError, HookTransactionRollbackError;
|
|
2812
|
+
var init_hook_installer = __esm({
|
|
2813
|
+
"src/hook-installer.ts"() {
|
|
2814
|
+
"use strict";
|
|
2815
|
+
DEFAULT_MARKER_ID = "artifact-chain-assistant";
|
|
2816
|
+
MANAGED_STATE_PREFIX = "# artifact-chain-assistant managed state v1:";
|
|
2817
|
+
UnsupportedHookInterpreterError = class extends Error {
|
|
2818
|
+
constructor(hookPath, shebang) {
|
|
2819
|
+
super(
|
|
2820
|
+
`Refusing to modify non-shell Git hook ${hookPath} (${shebang || "missing shebang"}). Call the artifact-chain hook from the existing hook explicitly.`
|
|
2821
|
+
);
|
|
2822
|
+
this.hookPath = hookPath;
|
|
2823
|
+
this.shebang = shebang;
|
|
2824
|
+
}
|
|
2825
|
+
hookPath;
|
|
2826
|
+
shebang;
|
|
2827
|
+
code = "UNSUPPORTED_HOOK_INTERPRETER";
|
|
2828
|
+
};
|
|
2829
|
+
SymlinkHookUnsupportedError = class extends Error {
|
|
2830
|
+
constructor(hookPath, linkTarget) {
|
|
2831
|
+
super(
|
|
2832
|
+
`SYMLINK_HOOK_UNSUPPORTED: Refusing to modify Git hook symlink ${hookPath} -> ${linkTarget}. Integrate the artifact-graph commands manually in the symlink target or replace the link yourself.`
|
|
2833
|
+
);
|
|
2834
|
+
this.hookPath = hookPath;
|
|
2835
|
+
this.linkTarget = linkTarget;
|
|
2836
|
+
}
|
|
2837
|
+
hookPath;
|
|
2838
|
+
linkTarget;
|
|
2839
|
+
code = "SYMLINK_HOOK_UNSUPPORTED";
|
|
2840
|
+
};
|
|
2841
|
+
UnsupportedHookTypeError = class extends Error {
|
|
2842
|
+
constructor(hookPath) {
|
|
2843
|
+
super(`UNSUPPORTED_HOOK_TYPE: Git hook path ${hookPath} is not a regular file. Resolve it manually before retrying.`);
|
|
2844
|
+
this.hookPath = hookPath;
|
|
2845
|
+
}
|
|
2846
|
+
hookPath;
|
|
2847
|
+
code = "UNSUPPORTED_HOOK_TYPE";
|
|
2848
|
+
};
|
|
2849
|
+
InvalidManagedHookStateError = class extends Error {
|
|
2850
|
+
constructor(hookPath, detail) {
|
|
2851
|
+
super(`INVALID_MANAGED_HOOK_STATE: Cannot safely update ${hookPath}: ${detail}`);
|
|
2852
|
+
this.hookPath = hookPath;
|
|
2853
|
+
}
|
|
2854
|
+
hookPath;
|
|
2855
|
+
code = "INVALID_MANAGED_HOOK_STATE";
|
|
2856
|
+
};
|
|
2857
|
+
ConcurrentHookModificationError = class extends Error {
|
|
2858
|
+
constructor(hookPath) {
|
|
2859
|
+
super(`Refusing to overwrite concurrently modified Git hook ${hookPath}. Retry the operation after reviewing the hook.`);
|
|
2860
|
+
this.hookPath = hookPath;
|
|
2861
|
+
}
|
|
2862
|
+
hookPath;
|
|
2863
|
+
code = "CONCURRENT_HOOK_MODIFICATION";
|
|
2864
|
+
};
|
|
2865
|
+
HookTransactionRollbackError = class extends Error {
|
|
2866
|
+
constructor(operationError, rollbackErrors) {
|
|
2867
|
+
super(
|
|
2868
|
+
`HOOK_TRANSACTION_ROLLBACK_FAILED: Hook update failed and ${rollbackErrors.length} rollback operation(s) also failed: ` + rollbackErrors.map(({ hookPath, error }) => `${hookPath}: ${error.message}`).join("; ")
|
|
2869
|
+
);
|
|
2870
|
+
this.operationError = operationError;
|
|
2871
|
+
this.rollbackErrors = rollbackErrors;
|
|
2872
|
+
}
|
|
2873
|
+
operationError;
|
|
2874
|
+
rollbackErrors;
|
|
2875
|
+
code = "HOOK_TRANSACTION_ROLLBACK_FAILED";
|
|
2876
|
+
};
|
|
2877
|
+
}
|
|
2878
|
+
});
|
|
2786
2879
|
|
|
2787
2880
|
// src/review-result-validator.ts
|
|
2788
|
-
var VALID_STATUSES = /* @__PURE__ */ new Set([
|
|
2789
|
-
"SUCCEEDED",
|
|
2790
|
-
"FAILED",
|
|
2791
|
-
"BLOCKED",
|
|
2792
|
-
"NEEDS_INPUT",
|
|
2793
|
-
"SKIPPED"
|
|
2794
|
-
]);
|
|
2795
|
-
var VALID_DECISIONS = /* @__PURE__ */ new Set([
|
|
2796
|
-
"PASS",
|
|
2797
|
-
"FAIL",
|
|
2798
|
-
"PASS_WITH_RESIDUAL_MINOR",
|
|
2799
|
-
"BLOCKED",
|
|
2800
|
-
"NEEDS_INPUT",
|
|
2801
|
-
"NOT_APPLICABLE"
|
|
2802
|
-
]);
|
|
2803
|
-
var VALID_SEVERITIES = /* @__PURE__ */ new Set(["block", "warn", "info"]);
|
|
2804
|
-
var VALID_FINDING_STATUSES = /* @__PURE__ */ new Set(["open", "resolved", "accepted", "superseded"]);
|
|
2805
|
-
var VALID_EXECUTORS = /* @__PURE__ */ new Set(["script", "worker", "agent", "manual", "cli"]);
|
|
2806
|
-
var TOP_LEVEL_FIELDS = /* @__PURE__ */ new Set([
|
|
2807
|
-
"schema_version",
|
|
2808
|
-
"run_id",
|
|
2809
|
-
"stage_id",
|
|
2810
|
-
"attempt",
|
|
2811
|
-
"status",
|
|
2812
|
-
"decision",
|
|
2813
|
-
"summary",
|
|
2814
|
-
"outputs",
|
|
2815
|
-
"warnings",
|
|
2816
|
-
"blocking_reason",
|
|
2817
|
-
"degradation",
|
|
2818
|
-
"producer",
|
|
2819
|
-
"acceptance",
|
|
2820
|
-
"evidence",
|
|
2821
|
-
"review",
|
|
2822
|
-
"repair"
|
|
2823
|
-
]);
|
|
2824
2881
|
function validateReviewResult(input) {
|
|
2825
2882
|
const errors = [];
|
|
2826
2883
|
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
@@ -3069,19 +3126,6 @@ function validateBatches(val, path, errors) {
|
|
|
3069
3126
|
checkOptionalInteger(b.chars, `${itemPath}.chars`, errors);
|
|
3070
3127
|
}
|
|
3071
3128
|
}
|
|
3072
|
-
var METRIC_FIELDS = [
|
|
3073
|
-
"files_reviewed",
|
|
3074
|
-
"files_scanned",
|
|
3075
|
-
"findings_count",
|
|
3076
|
-
"deterministic_findings_count",
|
|
3077
|
-
"semantic_findings_count",
|
|
3078
|
-
"block_count",
|
|
3079
|
-
"warn_count",
|
|
3080
|
-
"info_count",
|
|
3081
|
-
"resolved_count",
|
|
3082
|
-
"batch_count",
|
|
3083
|
-
"scenario_count"
|
|
3084
|
-
];
|
|
3085
3129
|
function validateMetrics(val, path, errors) {
|
|
3086
3130
|
if (!isPlainObject(val)) {
|
|
3087
3131
|
errors.push({ path, message: "Must be an object" });
|
|
@@ -3098,10 +3142,844 @@ function validateRepairValidation(val, path, errors) {
|
|
|
3098
3142
|
checkOptionalInteger(val.exit_code, `${path}.exit_code`, errors);
|
|
3099
3143
|
checkOptionalInteger(val.findings_remaining, `${path}.findings_remaining`, errors, 0);
|
|
3100
3144
|
}
|
|
3145
|
+
var VALID_STATUSES, VALID_DECISIONS, VALID_SEVERITIES, VALID_FINDING_STATUSES, VALID_EXECUTORS, TOP_LEVEL_FIELDS, METRIC_FIELDS;
|
|
3146
|
+
var init_review_result_validator = __esm({
|
|
3147
|
+
"src/review-result-validator.ts"() {
|
|
3148
|
+
"use strict";
|
|
3149
|
+
VALID_STATUSES = /* @__PURE__ */ new Set([
|
|
3150
|
+
"SUCCEEDED",
|
|
3151
|
+
"FAILED",
|
|
3152
|
+
"BLOCKED",
|
|
3153
|
+
"NEEDS_INPUT",
|
|
3154
|
+
"SKIPPED"
|
|
3155
|
+
]);
|
|
3156
|
+
VALID_DECISIONS = /* @__PURE__ */ new Set([
|
|
3157
|
+
"PASS",
|
|
3158
|
+
"FAIL",
|
|
3159
|
+
"PASS_WITH_RESIDUAL_MINOR",
|
|
3160
|
+
"BLOCKED",
|
|
3161
|
+
"NEEDS_INPUT",
|
|
3162
|
+
"NOT_APPLICABLE"
|
|
3163
|
+
]);
|
|
3164
|
+
VALID_SEVERITIES = /* @__PURE__ */ new Set(["block", "warn", "info"]);
|
|
3165
|
+
VALID_FINDING_STATUSES = /* @__PURE__ */ new Set(["open", "resolved", "accepted", "superseded"]);
|
|
3166
|
+
VALID_EXECUTORS = /* @__PURE__ */ new Set(["script", "worker", "agent", "manual", "cli"]);
|
|
3167
|
+
TOP_LEVEL_FIELDS = /* @__PURE__ */ new Set([
|
|
3168
|
+
"schema_version",
|
|
3169
|
+
"run_id",
|
|
3170
|
+
"stage_id",
|
|
3171
|
+
"attempt",
|
|
3172
|
+
"status",
|
|
3173
|
+
"decision",
|
|
3174
|
+
"summary",
|
|
3175
|
+
"outputs",
|
|
3176
|
+
"warnings",
|
|
3177
|
+
"blocking_reason",
|
|
3178
|
+
"degradation",
|
|
3179
|
+
"producer",
|
|
3180
|
+
"acceptance",
|
|
3181
|
+
"evidence",
|
|
3182
|
+
"review",
|
|
3183
|
+
"repair"
|
|
3184
|
+
]);
|
|
3185
|
+
METRIC_FIELDS = [
|
|
3186
|
+
"files_reviewed",
|
|
3187
|
+
"files_scanned",
|
|
3188
|
+
"findings_count",
|
|
3189
|
+
"deterministic_findings_count",
|
|
3190
|
+
"semantic_findings_count",
|
|
3191
|
+
"block_count",
|
|
3192
|
+
"warn_count",
|
|
3193
|
+
"info_count",
|
|
3194
|
+
"resolved_count",
|
|
3195
|
+
"batch_count",
|
|
3196
|
+
"scenario_count"
|
|
3197
|
+
];
|
|
3198
|
+
}
|
|
3199
|
+
});
|
|
3200
|
+
|
|
3201
|
+
// src/contract-kernel.ts
|
|
3202
|
+
import { createHash as createHash2 } from "crypto";
|
|
3203
|
+
import { readFile as readFile2, readdir } from "fs/promises";
|
|
3204
|
+
import { join as join5 } from "path";
|
|
3205
|
+
import _AjvModule from "ajv";
|
|
3206
|
+
function isOfficialNamespace(namespace) {
|
|
3207
|
+
return OFFICIAL_NAMESPACE_PATTERN.test(namespace);
|
|
3208
|
+
}
|
|
3209
|
+
function validateNamespaceAuthority(identity, expectedAuthority) {
|
|
3210
|
+
if (isOfficialNamespace(identity.namespace) && identity.authority !== OFFICIAL_AUTHORITY) {
|
|
3211
|
+
throw new ContractError(
|
|
3212
|
+
CONTRACT_ERROR_CODES.AUTHORITY_VIOLATION,
|
|
3213
|
+
`Namespace ${identity.namespace} requires authority "${OFFICIAL_AUTHORITY}", got "${identity.authority}"`,
|
|
3214
|
+
{ identity, expectedAuthority: OFFICIAL_AUTHORITY }
|
|
3215
|
+
);
|
|
3216
|
+
}
|
|
3217
|
+
if (expectedAuthority && identity.authority !== expectedAuthority) {
|
|
3218
|
+
throw new ContractError(
|
|
3219
|
+
CONTRACT_ERROR_CODES.AUTHORITY_VIOLATION,
|
|
3220
|
+
`Expected authority "${expectedAuthority}", got "${identity.authority}"`,
|
|
3221
|
+
{ identity, expectedAuthority }
|
|
3222
|
+
);
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
function canonicalizeForDigest(rawContent) {
|
|
3226
|
+
try {
|
|
3227
|
+
const parsed = JSON.parse(rawContent);
|
|
3228
|
+
if (parsed.contractIdentity && typeof parsed.contractIdentity === "object") {
|
|
3229
|
+
const ci = { ...parsed.contractIdentity };
|
|
3230
|
+
delete ci.revisionDigest;
|
|
3231
|
+
parsed.contractIdentity = ci;
|
|
3232
|
+
}
|
|
3233
|
+
return JSON.stringify(parsed);
|
|
3234
|
+
} catch {
|
|
3235
|
+
return rawContent;
|
|
3236
|
+
}
|
|
3237
|
+
}
|
|
3238
|
+
function computeRevisionDigest(content) {
|
|
3239
|
+
const canonical = canonicalizeForDigest(content);
|
|
3240
|
+
const hash = createHash2("sha256").update(canonical, "utf-8").digest("hex");
|
|
3241
|
+
return `sha256:${hash}`;
|
|
3242
|
+
}
|
|
3243
|
+
function verifyDigest(content, expectedDigest) {
|
|
3244
|
+
const actualDigest = computeRevisionDigest(content);
|
|
3245
|
+
return actualDigest === expectedDigest;
|
|
3246
|
+
}
|
|
3247
|
+
function setNestedValue(obj, path, value) {
|
|
3248
|
+
const parts = path.split(".");
|
|
3249
|
+
let current = obj;
|
|
3250
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
3251
|
+
const part = parts[i];
|
|
3252
|
+
if (current[part] === void 0 || typeof current[part] !== "object" || current[part] === null) {
|
|
3253
|
+
current[part] = {};
|
|
3254
|
+
}
|
|
3255
|
+
current = current[part];
|
|
3256
|
+
}
|
|
3257
|
+
current[parts[parts.length - 1]] = value;
|
|
3258
|
+
}
|
|
3259
|
+
function matchesSchemaType(value, definition) {
|
|
3260
|
+
const type = definition?.type;
|
|
3261
|
+
if (type === "array") return Array.isArray(value);
|
|
3262
|
+
if (type === "object") return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3263
|
+
if (type === "string") return typeof value === "string";
|
|
3264
|
+
if (type === "number" || type === "integer") return typeof value === "number";
|
|
3265
|
+
if (type === "boolean") return typeof value === "boolean";
|
|
3266
|
+
return value !== void 0;
|
|
3267
|
+
}
|
|
3268
|
+
function getNestedValue(source, path) {
|
|
3269
|
+
let current = source;
|
|
3270
|
+
for (const part of path.split(".")) {
|
|
3271
|
+
if (typeof current !== "object" || current === null || Array.isArray(current)) return void 0;
|
|
3272
|
+
current = current[part];
|
|
3273
|
+
}
|
|
3274
|
+
return current;
|
|
3275
|
+
}
|
|
3276
|
+
function valuesEqual(left, right) {
|
|
3277
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
3278
|
+
}
|
|
3279
|
+
function normalizeToCanonical(legacyData, config, contract) {
|
|
3280
|
+
const errors = [];
|
|
3281
|
+
const warnings = [];
|
|
3282
|
+
const legacyFieldMap = /* @__PURE__ */ new Map();
|
|
3283
|
+
for (const mapping of config.fieldMappings) {
|
|
3284
|
+
legacyFieldMap.set(mapping.legacy, mapping.canonical);
|
|
3285
|
+
}
|
|
3286
|
+
const canonical = {};
|
|
3287
|
+
const canonicalRoots = new Set(Object.keys(contract.schema.properties));
|
|
3288
|
+
const presentCanonicalRoots = /* @__PURE__ */ new Set();
|
|
3289
|
+
for (const root of canonicalRoots) {
|
|
3290
|
+
const value = legacyData[root];
|
|
3291
|
+
if (value !== void 0 && matchesSchemaType(value, contract.schema.properties[root])) {
|
|
3292
|
+
canonical[root] = structuredClone(value);
|
|
3293
|
+
presentCanonicalRoots.add(root);
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
const unmappedLegacyFields = new Set(
|
|
3297
|
+
Object.keys(legacyData).filter((field) => !presentCanonicalRoots.has(field) && !legacyFieldMap.has(field))
|
|
3298
|
+
);
|
|
3299
|
+
let usedLegacyExpression = false;
|
|
3300
|
+
for (const mapping of config.fieldMappings) {
|
|
3301
|
+
const legacyValue = legacyData[mapping.legacy];
|
|
3302
|
+
const existingCanonicalValue = getNestedValue(canonical, mapping.canonical);
|
|
3303
|
+
if (legacyValue !== void 0 && !presentCanonicalRoots.has(mapping.legacy)) {
|
|
3304
|
+
usedLegacyExpression = true;
|
|
3305
|
+
const canonicalValue = mapping.transform ? mapping.transform(legacyValue) : legacyValue;
|
|
3306
|
+
if (existingCanonicalValue !== void 0) {
|
|
3307
|
+
if (!valuesEqual(existingCanonicalValue, canonicalValue)) {
|
|
3308
|
+
errors.push({
|
|
3309
|
+
code: CONTRACT_ERROR_CODES.CANONICAL_LEGACY_CONFLICT,
|
|
3310
|
+
path: `/${mapping.canonical.replaceAll(".", "/")}`,
|
|
3311
|
+
message: `Canonical field "${mapping.canonical}" conflicts with legacy field "${mapping.legacy}"`
|
|
3312
|
+
});
|
|
3313
|
+
}
|
|
3314
|
+
} else {
|
|
3315
|
+
setNestedValue(canonical, mapping.canonical, canonicalValue);
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
if (mapping.required && getNestedValue(canonical, mapping.canonical) === void 0) {
|
|
3319
|
+
errors.push({
|
|
3320
|
+
code: "MISSING_REQUIRED_FIELD",
|
|
3321
|
+
path: `/${mapping.canonical.replaceAll(".", "/")}`,
|
|
3322
|
+
message: `Required canonical field "${mapping.canonical}" missing (legacy: "${mapping.legacy}")`
|
|
3323
|
+
});
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
for (const field of unmappedLegacyFields) {
|
|
3327
|
+
errors.push({
|
|
3328
|
+
code: "LOSSY_NORMALIZATION",
|
|
3329
|
+
path: field,
|
|
3330
|
+
message: `Legacy field "${field}" not mapped to canonical form \u2014 would be lost`
|
|
3331
|
+
});
|
|
3332
|
+
}
|
|
3333
|
+
if (config.validate) {
|
|
3334
|
+
const validationErrors = config.validate(canonical);
|
|
3335
|
+
for (const error of validationErrors) {
|
|
3336
|
+
errors.push({
|
|
3337
|
+
code: "VALIDATION_FAILED",
|
|
3338
|
+
path: "",
|
|
3339
|
+
message: error
|
|
3340
|
+
});
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
if (errors.length > 0) {
|
|
3344
|
+
return { success: false, errors, warnings };
|
|
3345
|
+
}
|
|
3346
|
+
const schemaResult = validateContractAgainstSchema(canonical, contract);
|
|
3347
|
+
if (!schemaResult.valid) {
|
|
3348
|
+
return {
|
|
3349
|
+
success: false,
|
|
3350
|
+
errors: schemaResult.errors.map((e) => ({
|
|
3351
|
+
code: "SCHEMA_VALIDATION_FAILED",
|
|
3352
|
+
path: e.split(":")[0]?.trim() ?? "",
|
|
3353
|
+
message: e
|
|
3354
|
+
})),
|
|
3355
|
+
warnings
|
|
3356
|
+
};
|
|
3357
|
+
}
|
|
3358
|
+
if (Array.isArray(canonical.relations)) {
|
|
3359
|
+
const relationResult = validateRelations(canonical.relations, contract);
|
|
3360
|
+
if (!relationResult.valid) {
|
|
3361
|
+
return {
|
|
3362
|
+
success: false,
|
|
3363
|
+
errors: relationResult.issues.map((i) => ({
|
|
3364
|
+
code: i.code,
|
|
3365
|
+
path: i.path,
|
|
3366
|
+
message: i.message
|
|
3367
|
+
})),
|
|
3368
|
+
warnings
|
|
3369
|
+
};
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
const sourceRevision = usedLegacyExpression ? "legacy" : "canonical";
|
|
3373
|
+
return {
|
|
3374
|
+
success: true,
|
|
3375
|
+
ir: {
|
|
3376
|
+
type: contract.identity.major.split(".")[1]?.split("@")[0] ?? "unknown",
|
|
3377
|
+
id: String(canonical.metadata?.id ?? canonical["id"] ?? ""),
|
|
3378
|
+
contractMajor: contract.identity.major,
|
|
3379
|
+
contractDigest: contract.identity.revisionDigest,
|
|
3380
|
+
canonical,
|
|
3381
|
+
sourceRevision,
|
|
3382
|
+
warnings
|
|
3383
|
+
},
|
|
3384
|
+
errors: [],
|
|
3385
|
+
warnings
|
|
3386
|
+
};
|
|
3387
|
+
}
|
|
3388
|
+
function resolvePropertyPath(schema, fieldPath) {
|
|
3389
|
+
const parts = fieldPath.split(".");
|
|
3390
|
+
let current = schema.properties;
|
|
3391
|
+
for (const part of parts) {
|
|
3392
|
+
if (!current || typeof current !== "object") return void 0;
|
|
3393
|
+
const propDef = current[part];
|
|
3394
|
+
if (!propDef) return void 0;
|
|
3395
|
+
if (parts.indexOf(part) < parts.length - 1) {
|
|
3396
|
+
current = propDef.properties;
|
|
3397
|
+
} else {
|
|
3398
|
+
return propDef;
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
return void 0;
|
|
3402
|
+
}
|
|
3403
|
+
function validatePolicyCompatibility(policy, baseContract) {
|
|
3404
|
+
const errors = [];
|
|
3405
|
+
const warnings = [];
|
|
3406
|
+
if (policy.baseContractMajor !== baseContract.identity.major) {
|
|
3407
|
+
errors.push(
|
|
3408
|
+
`POLICY_INCOMPATIBLE: Policy references ${policy.baseContractMajor}, but base contract is ${baseContract.identity.major}`
|
|
3409
|
+
);
|
|
3410
|
+
return { compatible: false, errors, warnings };
|
|
3411
|
+
}
|
|
3412
|
+
const schema = baseContract.schema;
|
|
3413
|
+
if (policy.additionalRequired) {
|
|
3414
|
+
for (const field of policy.additionalRequired) {
|
|
3415
|
+
const propDef = resolvePropertyPath(schema, field);
|
|
3416
|
+
if (!propDef) {
|
|
3417
|
+
errors.push(`POLICY_INCOMPATIBLE: Policy requires field "${field}" which does not exist in base contract`);
|
|
3418
|
+
}
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
if (policy.restrictedEnums) {
|
|
3422
|
+
for (const [field, values] of Object.entries(policy.restrictedEnums)) {
|
|
3423
|
+
const baseProperty = resolvePropertyPath(schema, field);
|
|
3424
|
+
if (!baseProperty) {
|
|
3425
|
+
errors.push(`POLICY_INCOMPATIBLE: Policy restricts enum for field "${field}" which does not exist in base contract`);
|
|
3426
|
+
continue;
|
|
3427
|
+
}
|
|
3428
|
+
if (!Array.isArray(baseProperty.enum)) {
|
|
3429
|
+
errors.push(`POLICY_INCOMPATIBLE: Field "${field}" is not an enum in base contract \u2014 cannot restrict`);
|
|
3430
|
+
continue;
|
|
3431
|
+
}
|
|
3432
|
+
for (const value of values) {
|
|
3433
|
+
if (!baseProperty.enum.includes(value)) {
|
|
3434
|
+
errors.push(`POLICY_INCOMPATIBLE: Policy enum value "${String(value)}" for field "${field}" not in base contract enum [${baseProperty.enum.join(", ")}]`);
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
if (policy.minCardinality) {
|
|
3440
|
+
for (const [field, min] of Object.entries(policy.minCardinality)) {
|
|
3441
|
+
if (!Number.isFinite(min) || min < 0 || !Number.isInteger(min)) {
|
|
3442
|
+
errors.push(`POLICY_INCOMPATIBLE: Policy min cardinality for "${field}" must be a non-negative integer, got ${min}`);
|
|
3443
|
+
continue;
|
|
3444
|
+
}
|
|
3445
|
+
const baseProperty = resolvePropertyPath(schema, field);
|
|
3446
|
+
if (!baseProperty) {
|
|
3447
|
+
errors.push(`POLICY_INCOMPATIBLE: Policy sets min cardinality for field "${field}" which does not exist in base contract`);
|
|
3448
|
+
continue;
|
|
3449
|
+
}
|
|
3450
|
+
const baseMin = baseProperty.type === "array" ? baseProperty.minItems : baseProperty.minimum;
|
|
3451
|
+
if (baseMin !== void 0 && min < baseMin) {
|
|
3452
|
+
errors.push(`POLICY_INCOMPATIBLE: Policy min cardinality ${min} for field "${field}" loosens base ${baseProperty.type === "array" ? "minItems" : "minimum"}=${baseMin}`);
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
if (policy.maxCardinality) {
|
|
3457
|
+
for (const [field, max] of Object.entries(policy.maxCardinality)) {
|
|
3458
|
+
if (!Number.isFinite(max) || max < 0 || !Number.isInteger(max)) {
|
|
3459
|
+
errors.push(`POLICY_INCOMPATIBLE: Policy max cardinality for "${field}" must be a non-negative integer, got ${max}`);
|
|
3460
|
+
continue;
|
|
3461
|
+
}
|
|
3462
|
+
const baseProperty = resolvePropertyPath(schema, field);
|
|
3463
|
+
if (!baseProperty) {
|
|
3464
|
+
errors.push(`POLICY_INCOMPATIBLE: Policy sets max cardinality for field "${field}" which does not exist in base contract`);
|
|
3465
|
+
continue;
|
|
3466
|
+
}
|
|
3467
|
+
const baseMax = baseProperty.type === "array" ? baseProperty.maxItems : baseProperty.maximum;
|
|
3468
|
+
if (baseMax !== void 0 && max > baseMax) {
|
|
3469
|
+
errors.push(`POLICY_INCOMPATIBLE: Policy max cardinality ${max} for field "${field}" loosens base ${baseProperty.type === "array" ? "maxItems" : "maximum"}=${baseMax}`);
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
if (policy.constraints && Object.keys(policy.constraints).length > 0) {
|
|
3474
|
+
for (const key of Object.keys(policy.constraints)) {
|
|
3475
|
+
errors.push(`POLICY_INCOMPATIBLE: Constraint "${key}" is not implemented \u2014 rejected as unvalidated escape hatch`);
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
return {
|
|
3479
|
+
compatible: errors.length === 0,
|
|
3480
|
+
errors,
|
|
3481
|
+
warnings
|
|
3482
|
+
};
|
|
3483
|
+
}
|
|
3484
|
+
async function loadContract(contractPath, options) {
|
|
3485
|
+
const rawContent = await readFile2(contractPath, "utf-8");
|
|
3486
|
+
let schema;
|
|
3487
|
+
try {
|
|
3488
|
+
schema = JSON.parse(rawContent);
|
|
3489
|
+
} catch (error) {
|
|
3490
|
+
throw new ContractError(
|
|
3491
|
+
CONTRACT_ERROR_CODES.INVALID_IDENTITY,
|
|
3492
|
+
`Failed to parse contract JSON: ${error.message}`,
|
|
3493
|
+
{ path: contractPath }
|
|
3494
|
+
);
|
|
3495
|
+
}
|
|
3496
|
+
if (!schema.contractIdentity) {
|
|
3497
|
+
throw new ContractError(
|
|
3498
|
+
CONTRACT_ERROR_CODES.INVALID_IDENTITY,
|
|
3499
|
+
"Contract missing contractIdentity",
|
|
3500
|
+
{ path: contractPath }
|
|
3501
|
+
);
|
|
3502
|
+
}
|
|
3503
|
+
const identity = schema.contractIdentity;
|
|
3504
|
+
if (!identity.major || !identity.authority || !identity.namespace) {
|
|
3505
|
+
throw new ContractError(
|
|
3506
|
+
CONTRACT_ERROR_CODES.INVALID_IDENTITY,
|
|
3507
|
+
"Contract identity missing required fields (major, authority, namespace)",
|
|
3508
|
+
{ identity }
|
|
3509
|
+
);
|
|
3510
|
+
}
|
|
3511
|
+
if (options?.expectedAuthority) {
|
|
3512
|
+
validateNamespaceAuthority(identity, options.expectedAuthority);
|
|
3513
|
+
} else {
|
|
3514
|
+
validateNamespaceAuthority(identity);
|
|
3515
|
+
}
|
|
3516
|
+
const computedDigest = computeRevisionDigest(rawContent);
|
|
3517
|
+
if (identity.revisionDigest && identity.revisionDigest !== computedDigest) {
|
|
3518
|
+
throw new ContractError(
|
|
3519
|
+
CONTRACT_ERROR_CODES.DIGEST_MISMATCH,
|
|
3520
|
+
`Contract digest mismatch: declared ${identity.revisionDigest}, computed ${computedDigest}`,
|
|
3521
|
+
{ identity, computedDigest }
|
|
3522
|
+
);
|
|
3523
|
+
}
|
|
3524
|
+
identity.revisionDigest = computedDigest;
|
|
3525
|
+
return {
|
|
3526
|
+
identity,
|
|
3527
|
+
schema,
|
|
3528
|
+
rawContent
|
|
3529
|
+
};
|
|
3530
|
+
}
|
|
3531
|
+
async function loadContractsFromDirectory(contractsDir, options) {
|
|
3532
|
+
const contracts = [];
|
|
3533
|
+
const entries = await readdir(contractsDir, { withFileTypes: true });
|
|
3534
|
+
for (const entry of entries) {
|
|
3535
|
+
if (entry.isDirectory()) {
|
|
3536
|
+
const schemaPath = join5(contractsDir, entry.name, "schema.json");
|
|
3537
|
+
const contract = await loadContract(schemaPath, options);
|
|
3538
|
+
contracts.push(contract);
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
return contracts;
|
|
3542
|
+
}
|
|
3543
|
+
function getValidator(schema) {
|
|
3544
|
+
const key = `${schema.$id || "default"}#${computeRevisionDigest(JSON.stringify(schema))}`;
|
|
3545
|
+
if (!_validatorCache.has(key)) {
|
|
3546
|
+
const AjvCtor = _AjvModule;
|
|
3547
|
+
const ajv = new AjvCtor({ allErrors: true, strict: false });
|
|
3548
|
+
_validatorCache.set(key, ajv.compile(schema));
|
|
3549
|
+
}
|
|
3550
|
+
return _validatorCache.get(key);
|
|
3551
|
+
}
|
|
3552
|
+
function validateContractAgainstSchema(data, contract) {
|
|
3553
|
+
const validate = getValidator(contract.schema);
|
|
3554
|
+
const valid = validate(data);
|
|
3555
|
+
if (valid) {
|
|
3556
|
+
return { valid: true, errors: [] };
|
|
3557
|
+
}
|
|
3558
|
+
const errors = (validate.errors ?? []).map(
|
|
3559
|
+
(err) => `${err.instancePath || "/"}: ${err.message ?? "validation error"}`
|
|
3560
|
+
);
|
|
3561
|
+
return { valid: false, errors };
|
|
3562
|
+
}
|
|
3563
|
+
function validateRelations(relations, contract) {
|
|
3564
|
+
const issues = [];
|
|
3565
|
+
const relationRules = contract.identity.relationRules;
|
|
3566
|
+
if (!relationRules) {
|
|
3567
|
+
issues.push({
|
|
3568
|
+
code: "RELATION_RULES_MISSING",
|
|
3569
|
+
path: "/contractIdentity/relationRules",
|
|
3570
|
+
message: "Contract does not define relation rules \u2014 cannot validate relations"
|
|
3571
|
+
});
|
|
3572
|
+
return { valid: false, issues };
|
|
3573
|
+
}
|
|
3574
|
+
const relationsByKind = /* @__PURE__ */ new Map();
|
|
3575
|
+
for (const relation of relations) {
|
|
3576
|
+
const kind = relation.kind;
|
|
3577
|
+
if (!relationsByKind.has(kind)) {
|
|
3578
|
+
relationsByKind.set(kind, []);
|
|
3579
|
+
}
|
|
3580
|
+
relationsByKind.get(kind).push(relation);
|
|
3581
|
+
}
|
|
3582
|
+
for (let i = 0; i < relations.length; i++) {
|
|
3583
|
+
const relation = relations[i];
|
|
3584
|
+
const kind = relation.kind;
|
|
3585
|
+
const targetType = relation.target_type;
|
|
3586
|
+
const path = `/relations/${i}`;
|
|
3587
|
+
if (!relationRules[kind]) {
|
|
3588
|
+
issues.push({
|
|
3589
|
+
code: "RELATION_INVALID_KIND",
|
|
3590
|
+
path: `${path}/kind`,
|
|
3591
|
+
message: `Relation kind "${kind}" is not allowed by contract rules`
|
|
3592
|
+
});
|
|
3593
|
+
continue;
|
|
3594
|
+
}
|
|
3595
|
+
const rule = relationRules[kind];
|
|
3596
|
+
if (!rule.allowedTargetTypes.includes(targetType)) {
|
|
3597
|
+
issues.push({
|
|
3598
|
+
code: "RELATION_INVALID_TARGET_TYPE",
|
|
3599
|
+
path: `${path}/target_type`,
|
|
3600
|
+
message: `Target type "${targetType}" is not allowed for relation kind "${kind}". Allowed: ${rule.allowedTargetTypes.join(", ")}`
|
|
3601
|
+
});
|
|
3602
|
+
}
|
|
3603
|
+
const hasAnchor = relation.anchor !== void 0 && relation.anchor !== "";
|
|
3604
|
+
if (rule.anchorPolicy === "required" && !hasAnchor) {
|
|
3605
|
+
issues.push({
|
|
3606
|
+
code: "RELATION_MISSING_ANCHOR",
|
|
3607
|
+
path: `${path}/anchor`,
|
|
3608
|
+
message: `Relation kind "${kind}" requires an anchor`
|
|
3609
|
+
});
|
|
3610
|
+
} else if (rule.anchorPolicy === "forbidden" && hasAnchor) {
|
|
3611
|
+
issues.push({
|
|
3612
|
+
code: "RELATION_FORBIDDEN_ANCHOR",
|
|
3613
|
+
path: `${path}/anchor`,
|
|
3614
|
+
message: `Relation kind "${kind}" forbids anchors`
|
|
3615
|
+
});
|
|
3616
|
+
}
|
|
3617
|
+
}
|
|
3618
|
+
for (const [kind, rule] of Object.entries(relationRules)) {
|
|
3619
|
+
const count = relationsByKind.get(kind)?.length ?? 0;
|
|
3620
|
+
if (count < rule.min) {
|
|
3621
|
+
issues.push({
|
|
3622
|
+
code: "RELATION_BELOW_MIN",
|
|
3623
|
+
path: "/relations",
|
|
3624
|
+
message: `Relation kind "${kind}" has ${count} instances, minimum is ${rule.min}`
|
|
3625
|
+
});
|
|
3626
|
+
}
|
|
3627
|
+
if (count > rule.max) {
|
|
3628
|
+
issues.push({
|
|
3629
|
+
code: "RELATION_ABOVE_MAX",
|
|
3630
|
+
path: "/relations",
|
|
3631
|
+
message: `Relation kind "${kind}" has ${count} instances, maximum is ${rule.max}`
|
|
3632
|
+
});
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
return {
|
|
3636
|
+
valid: issues.length === 0,
|
|
3637
|
+
issues
|
|
3638
|
+
};
|
|
3639
|
+
}
|
|
3640
|
+
function normalizeE2eLegacyArtifact(legacyData, contract) {
|
|
3641
|
+
return normalizeToCanonical(legacyData, E2E_NORMALIZER_CONFIG, contract);
|
|
3642
|
+
}
|
|
3643
|
+
async function loadContractCatalog(contractsDir, options) {
|
|
3644
|
+
const catalog = new ContractCatalog();
|
|
3645
|
+
const contracts = await loadContractsFromDirectory(contractsDir, options);
|
|
3646
|
+
for (const contract of contracts) {
|
|
3647
|
+
catalog.add(contract);
|
|
3648
|
+
}
|
|
3649
|
+
return catalog;
|
|
3650
|
+
}
|
|
3651
|
+
var CONTRACT_ERROR_CODES, ContractError, OFFICIAL_AUTHORITY, OFFICIAL_NAMESPACE_PATTERN, ContractRegistry, _validatorCache, E2E_NORMALIZER_CONFIG, ContractCatalog;
|
|
3652
|
+
var init_contract_kernel = __esm({
|
|
3653
|
+
"src/contract-kernel.ts"() {
|
|
3654
|
+
"use strict";
|
|
3655
|
+
CONTRACT_ERROR_CODES = {
|
|
3656
|
+
/** Contract identity not found */
|
|
3657
|
+
CONTRACT_NOT_FOUND: "CONTRACT_NOT_FOUND",
|
|
3658
|
+
/** Invalid contract identity format */
|
|
3659
|
+
INVALID_IDENTITY: "INVALID_IDENTITY",
|
|
3660
|
+
/** Revision digest mismatch */
|
|
3661
|
+
DIGEST_MISMATCH: "DIGEST_MISMATCH",
|
|
3662
|
+
/** Duplicate contract identity */
|
|
3663
|
+
DUPLICATE_IDENTITY: "DUPLICATE_IDENTITY",
|
|
3664
|
+
/** Multiple active write contracts for same type */
|
|
3665
|
+
MULTIPLE_ACTIVE_WRITE: "MULTIPLE_ACTIVE_WRITE",
|
|
3666
|
+
/** Unknown authority namespace */
|
|
3667
|
+
UNKNOWN_AUTHORITY: "UNKNOWN_AUTHORITY",
|
|
3668
|
+
/** Namespace authority violation */
|
|
3669
|
+
AUTHORITY_VIOLATION: "AUTHORITY_VIOLATION",
|
|
3670
|
+
/** Schema validation failed */
|
|
3671
|
+
SCHEMA_VALIDATION_FAILED: "SCHEMA_VALIDATION_FAILED",
|
|
3672
|
+
/** Canonical IR normalization failed */
|
|
3673
|
+
NORMALIZATION_FAILED: "NORMALIZATION_FAILED",
|
|
3674
|
+
/** Policy compatibility check failed */
|
|
3675
|
+
POLICY_INCOMPATIBLE: "POLICY_INCOMPATIBLE",
|
|
3676
|
+
/** Legacy revision cannot be normalized */
|
|
3677
|
+
LEGACY_NORMALIZATION_FAILED: "LEGACY_NORMALIZATION_FAILED",
|
|
3678
|
+
/** Canonical and legacy conflict */
|
|
3679
|
+
CANONICAL_LEGACY_CONFLICT: "CANONICAL_LEGACY_CONFLICT",
|
|
3680
|
+
/** Relation rule violation */
|
|
3681
|
+
RELATION_RULE_VIOLATION: "RELATION_RULE_VIOLATION",
|
|
3682
|
+
/** Relation rules missing in contract (fail closed) */
|
|
3683
|
+
RELATION_RULES_MISSING: "RELATION_RULES_MISSING",
|
|
3684
|
+
/** Ambiguous revision — multiple revisions found, no unique active write */
|
|
3685
|
+
AMBIGUOUS_REVISION: "AMBIGUOUS_REVISION",
|
|
3686
|
+
/** Invalid relation kind */
|
|
3687
|
+
RELATION_INVALID_KIND: "RELATION_INVALID_KIND",
|
|
3688
|
+
/** Invalid relation target type */
|
|
3689
|
+
RELATION_INVALID_TARGET_TYPE: "RELATION_INVALID_TARGET_TYPE",
|
|
3690
|
+
/** Relation below minimum cardinality */
|
|
3691
|
+
RELATION_BELOW_MIN: "RELATION_BELOW_MIN",
|
|
3692
|
+
/** Relation above maximum cardinality */
|
|
3693
|
+
RELATION_ABOVE_MAX: "RELATION_ABOVE_MAX",
|
|
3694
|
+
/** Missing required anchor */
|
|
3695
|
+
RELATION_MISSING_ANCHOR: "RELATION_MISSING_ANCHOR",
|
|
3696
|
+
/** Forbidden anchor present */
|
|
3697
|
+
RELATION_FORBIDDEN_ANCHOR: "RELATION_FORBIDDEN_ANCHOR",
|
|
3698
|
+
/** Missing required semantic marker */
|
|
3699
|
+
MARKER_MISSING: "MARKER_MISSING",
|
|
3700
|
+
/** Duplicate semantic marker */
|
|
3701
|
+
MARKER_DUPLICATE: "MARKER_DUPLICATE",
|
|
3702
|
+
/** Unknown semantic marker */
|
|
3703
|
+
MARKER_UNKNOWN: "MARKER_UNKNOWN"
|
|
3704
|
+
};
|
|
3705
|
+
ContractError = class extends Error {
|
|
3706
|
+
constructor(code, message, details) {
|
|
3707
|
+
super(message);
|
|
3708
|
+
this.code = code;
|
|
3709
|
+
this.details = details;
|
|
3710
|
+
this.name = "ContractError";
|
|
3711
|
+
}
|
|
3712
|
+
code;
|
|
3713
|
+
details;
|
|
3714
|
+
};
|
|
3715
|
+
OFFICIAL_AUTHORITY = "artifact";
|
|
3716
|
+
OFFICIAL_NAMESPACE_PATTERN = /^artifact(?:\.|$)/;
|
|
3717
|
+
ContractRegistry = class {
|
|
3718
|
+
contracts = /* @__PURE__ */ new Map();
|
|
3719
|
+
typeToActiveWrite = /* @__PURE__ */ new Map();
|
|
3720
|
+
majorToContracts = /* @__PURE__ */ new Map();
|
|
3721
|
+
/**
|
|
3722
|
+
* Register a contract
|
|
3723
|
+
* @throws ContractError if duplicate identity or multiple active write contracts
|
|
3724
|
+
*/
|
|
3725
|
+
register(contract, options) {
|
|
3726
|
+
const major = contract.identity.major;
|
|
3727
|
+
const digest = contract.identity.revisionDigest;
|
|
3728
|
+
const key = `${major}@${digest}`;
|
|
3729
|
+
const isActive = options?.isActive ?? true;
|
|
3730
|
+
const isWriteTarget = options?.isWriteTarget ?? true;
|
|
3731
|
+
if (this.contracts.has(key)) {
|
|
3732
|
+
throw new ContractError(
|
|
3733
|
+
CONTRACT_ERROR_CODES.DUPLICATE_IDENTITY,
|
|
3734
|
+
`Contract ${key} already registered`,
|
|
3735
|
+
{ identity: contract.identity }
|
|
3736
|
+
);
|
|
3737
|
+
}
|
|
3738
|
+
if (isActive && isWriteTarget) {
|
|
3739
|
+
const type = major.split("@")[0];
|
|
3740
|
+
if (this.typeToActiveWrite.has(type)) {
|
|
3741
|
+
throw new ContractError(
|
|
3742
|
+
CONTRACT_ERROR_CODES.MULTIPLE_ACTIVE_WRITE,
|
|
3743
|
+
`Type ${type} already has active write contract: ${this.typeToActiveWrite.get(type)}`,
|
|
3744
|
+
{ identity: contract.identity, existing: this.typeToActiveWrite.get(type) }
|
|
3745
|
+
);
|
|
3746
|
+
}
|
|
3747
|
+
this.typeToActiveWrite.set(type, key);
|
|
3748
|
+
}
|
|
3749
|
+
this.contracts.set(key, {
|
|
3750
|
+
contract,
|
|
3751
|
+
isActive,
|
|
3752
|
+
isWriteTarget,
|
|
3753
|
+
loadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3754
|
+
});
|
|
3755
|
+
if (!this.majorToContracts.has(major)) {
|
|
3756
|
+
this.majorToContracts.set(major, /* @__PURE__ */ new Set());
|
|
3757
|
+
}
|
|
3758
|
+
this.majorToContracts.get(major).add(key);
|
|
3759
|
+
}
|
|
3760
|
+
/**
|
|
3761
|
+
* Resolve by major identity. Multiple revisions require a unique active write
|
|
3762
|
+
* revision; insertion order is never a resolution policy.
|
|
3763
|
+
*/
|
|
3764
|
+
get(major) {
|
|
3765
|
+
const keys = this.majorToContracts.get(major);
|
|
3766
|
+
if (!keys || keys.size === 0) return void 0;
|
|
3767
|
+
if (keys.size === 1) {
|
|
3768
|
+
const onlyKey = keys.values().next().value;
|
|
3769
|
+
return onlyKey ? this.contracts.get(onlyKey)?.contract : void 0;
|
|
3770
|
+
}
|
|
3771
|
+
const type = major.split("@")[0];
|
|
3772
|
+
const activeWriteKey = this.typeToActiveWrite.get(type);
|
|
3773
|
+
if (activeWriteKey && keys.has(activeWriteKey)) {
|
|
3774
|
+
return this.contracts.get(activeWriteKey)?.contract;
|
|
3775
|
+
}
|
|
3776
|
+
throw new ContractError(
|
|
3777
|
+
CONTRACT_ERROR_CODES.AMBIGUOUS_REVISION,
|
|
3778
|
+
`Multiple revisions found for ${major} with no unique active write \u2014 specify digest`,
|
|
3779
|
+
{
|
|
3780
|
+
major,
|
|
3781
|
+
availableDigests: Array.from(keys).map((key) => this.contracts.get(key)?.contract.identity.revisionDigest).filter((digest) => Boolean(digest))
|
|
3782
|
+
}
|
|
3783
|
+
);
|
|
3784
|
+
}
|
|
3785
|
+
/**
|
|
3786
|
+
* Get contract by major identity and digest
|
|
3787
|
+
*/
|
|
3788
|
+
getByMajorAndDigest(major, digest) {
|
|
3789
|
+
const key = `${major}@${digest}`;
|
|
3790
|
+
return this.contracts.get(key)?.contract;
|
|
3791
|
+
}
|
|
3792
|
+
/**
|
|
3793
|
+
* Get all contracts for a major identity
|
|
3794
|
+
*/
|
|
3795
|
+
getByMajor(major) {
|
|
3796
|
+
const keys = this.majorToContracts.get(major);
|
|
3797
|
+
if (!keys) return [];
|
|
3798
|
+
return Array.from(keys).map((key) => this.contracts.get(key)?.contract).filter((c) => c !== void 0);
|
|
3799
|
+
}
|
|
3800
|
+
/**
|
|
3801
|
+
* Get active write contract for a type
|
|
3802
|
+
*/
|
|
3803
|
+
getActiveWriteContract(typePrefix) {
|
|
3804
|
+
const key = this.typeToActiveWrite.get(typePrefix);
|
|
3805
|
+
return key ? this.contracts.get(key)?.contract : void 0;
|
|
3806
|
+
}
|
|
3807
|
+
/**
|
|
3808
|
+
* List all registered contracts
|
|
3809
|
+
*/
|
|
3810
|
+
list() {
|
|
3811
|
+
return Array.from(this.contracts.values());
|
|
3812
|
+
}
|
|
3813
|
+
/**
|
|
3814
|
+
* Check if a contract is registered by major identity
|
|
3815
|
+
*/
|
|
3816
|
+
has(major) {
|
|
3817
|
+
const keys = this.majorToContracts.get(major);
|
|
3818
|
+
return keys !== void 0 && keys.size > 0;
|
|
3819
|
+
}
|
|
3820
|
+
/**
|
|
3821
|
+
* Check if a contract is registered by major identity and digest
|
|
3822
|
+
*/
|
|
3823
|
+
hasByMajorAndDigest(major, digest) {
|
|
3824
|
+
const key = `${major}@${digest}`;
|
|
3825
|
+
return this.contracts.has(key);
|
|
3826
|
+
}
|
|
3827
|
+
};
|
|
3828
|
+
_validatorCache = /* @__PURE__ */ new Map();
|
|
3829
|
+
E2E_NORMALIZER_CONFIG = {
|
|
3830
|
+
contractMajor: "artifact.e2e-test@1",
|
|
3831
|
+
fieldMappings: [
|
|
3832
|
+
{ legacy: "id", canonical: "metadata.id", required: true },
|
|
3833
|
+
{ legacy: "title", canonical: "metadata.title", required: true },
|
|
3834
|
+
{ legacy: "status", canonical: "metadata.status", required: true },
|
|
3835
|
+
{ legacy: "test_batch", canonical: "metadata.test_batch" },
|
|
3836
|
+
{ legacy: "scope", canonical: "scope.business_goal", transform: (v) => String(v) },
|
|
3837
|
+
{ legacy: "actors", canonical: "scope.actors", transform: (v) => Array.isArray(v) ? v : [v] },
|
|
3838
|
+
{ legacy: "system_boundaries", canonical: "system_boundary.components", transform: (v) => Array.isArray(v) ? v : [v] },
|
|
3839
|
+
{ legacy: "test_cases", canonical: "test_cases", required: true },
|
|
3840
|
+
{ legacy: "relations", canonical: "relations" },
|
|
3841
|
+
// Map legacy coverage fields if present
|
|
3842
|
+
{ legacy: "ac_coverage", canonical: "coverage.ac_coverage" },
|
|
3843
|
+
{ legacy: "related_scenarios", canonical: "coverage.related_scenarios" },
|
|
3844
|
+
// Map legacy environment fields if present
|
|
3845
|
+
{ legacy: "topology", canonical: "environment_data.topology" },
|
|
3846
|
+
{ legacy: "fixtures", canonical: "environment_data.fixtures" },
|
|
3847
|
+
{ legacy: "isolation_strategy", canonical: "environment_data.isolation_strategy" },
|
|
3848
|
+
// Map legacy evidence fields if present
|
|
3849
|
+
{ legacy: "required_artifacts", canonical: "evidence_contract.required_artifacts" },
|
|
3850
|
+
{ legacy: "runner_binding", canonical: "evidence_contract.runner_binding" }
|
|
3851
|
+
],
|
|
3852
|
+
validate: (canonical) => {
|
|
3853
|
+
const errors = [];
|
|
3854
|
+
if (!canonical.metadata || typeof canonical.metadata !== "object") {
|
|
3855
|
+
errors.push("metadata must be an object");
|
|
3856
|
+
}
|
|
3857
|
+
if (!canonical.scope || typeof canonical.scope !== "object") {
|
|
3858
|
+
errors.push("scope must be an object");
|
|
3859
|
+
}
|
|
3860
|
+
if (!canonical.system_boundary || typeof canonical.system_boundary !== "object") {
|
|
3861
|
+
errors.push("system_boundary must be an object");
|
|
3862
|
+
}
|
|
3863
|
+
if (!Array.isArray(canonical.test_cases) || canonical.test_cases.length === 0) {
|
|
3864
|
+
errors.push("test_cases must be a non-empty array");
|
|
3865
|
+
}
|
|
3866
|
+
if (Array.isArray(canonical.test_cases)) {
|
|
3867
|
+
for (let i = 0; i < canonical.test_cases.length; i++) {
|
|
3868
|
+
const tc = canonical.test_cases[i];
|
|
3869
|
+
if (!Array.isArray(tc.trace_targets) || tc.trace_targets.length === 0) {
|
|
3870
|
+
errors.push(`test_cases[${i}].trace_targets must be a non-empty array`);
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
}
|
|
3874
|
+
return errors;
|
|
3875
|
+
}
|
|
3876
|
+
};
|
|
3877
|
+
ContractCatalog = class {
|
|
3878
|
+
contracts = /* @__PURE__ */ new Map();
|
|
3879
|
+
majorToContracts = /* @__PURE__ */ new Map();
|
|
3880
|
+
majorToActiveWrite = /* @__PURE__ */ new Map();
|
|
3881
|
+
/**
|
|
3882
|
+
* Add a contract to the catalog
|
|
3883
|
+
* @throws ContractError if duplicate identity
|
|
3884
|
+
*/
|
|
3885
|
+
add(contract, options) {
|
|
3886
|
+
const major = contract.identity.major;
|
|
3887
|
+
const digest = contract.identity.revisionDigest;
|
|
3888
|
+
const key = `${major}@${digest}`;
|
|
3889
|
+
const isActive = options?.isActive ?? true;
|
|
3890
|
+
const isWriteTarget = options?.isWriteTarget ?? true;
|
|
3891
|
+
if (this.contracts.has(key)) {
|
|
3892
|
+
throw new ContractError(
|
|
3893
|
+
CONTRACT_ERROR_CODES.DUPLICATE_IDENTITY,
|
|
3894
|
+
`Contract ${key} already in catalog`,
|
|
3895
|
+
{ identity: contract.identity }
|
|
3896
|
+
);
|
|
3897
|
+
}
|
|
3898
|
+
this.contracts.set(key, contract);
|
|
3899
|
+
if (!this.majorToContracts.has(major)) {
|
|
3900
|
+
this.majorToContracts.set(major, /* @__PURE__ */ new Set());
|
|
3901
|
+
}
|
|
3902
|
+
this.majorToContracts.get(major).add(key);
|
|
3903
|
+
if (isActive && isWriteTarget) {
|
|
3904
|
+
const existingActive = this.majorToActiveWrite.get(major);
|
|
3905
|
+
if (existingActive && existingActive !== key) {
|
|
3906
|
+
this.contracts.delete(key);
|
|
3907
|
+
this.majorToContracts.get(major)?.delete(key);
|
|
3908
|
+
throw new ContractError(
|
|
3909
|
+
CONTRACT_ERROR_CODES.MULTIPLE_ACTIVE_WRITE,
|
|
3910
|
+
`Major ${major} already has active write revision`,
|
|
3911
|
+
{ major, existing: existingActive, attempted: key }
|
|
3912
|
+
);
|
|
3913
|
+
}
|
|
3914
|
+
this.majorToActiveWrite.set(major, key);
|
|
3915
|
+
}
|
|
3916
|
+
}
|
|
3917
|
+
/**
|
|
3918
|
+
* Resolve a contract by major identity.
|
|
3919
|
+
* - 0 entries: returns undefined
|
|
3920
|
+
* - 1 entry: returns it
|
|
3921
|
+
* - multiple: if there's a unique active write, returns it; otherwise throws AMBIGUOUS_REVISION
|
|
3922
|
+
*/
|
|
3923
|
+
resolve(major) {
|
|
3924
|
+
const keys = this.majorToContracts.get(major);
|
|
3925
|
+
if (!keys || keys.size === 0) return void 0;
|
|
3926
|
+
if (keys.size === 1) {
|
|
3927
|
+
const firstKey = keys.values().next().value;
|
|
3928
|
+
return firstKey ? this.contracts.get(firstKey) : void 0;
|
|
3929
|
+
}
|
|
3930
|
+
const activeWriteKey = this.majorToActiveWrite.get(major);
|
|
3931
|
+
if (activeWriteKey) {
|
|
3932
|
+
return this.contracts.get(activeWriteKey);
|
|
3933
|
+
}
|
|
3934
|
+
throw new ContractError(
|
|
3935
|
+
CONTRACT_ERROR_CODES.AMBIGUOUS_REVISION,
|
|
3936
|
+
`Multiple revisions found for ${major} with no unique active write \u2014 specify digest`,
|
|
3937
|
+
{
|
|
3938
|
+
major,
|
|
3939
|
+
availableDigests: Array.from(keys).map((key) => this.contracts.get(key)?.identity.revisionDigest).filter((digest) => Boolean(digest))
|
|
3940
|
+
}
|
|
3941
|
+
);
|
|
3942
|
+
}
|
|
3943
|
+
/**
|
|
3944
|
+
* Resolve a contract by major identity and exact digest
|
|
3945
|
+
*/
|
|
3946
|
+
resolveByDigest(major, digest) {
|
|
3947
|
+
const key = `${major}@${digest}`;
|
|
3948
|
+
return this.contracts.get(key);
|
|
3949
|
+
}
|
|
3950
|
+
/**
|
|
3951
|
+
* List all catalog entries (all revisions)
|
|
3952
|
+
*/
|
|
3953
|
+
list() {
|
|
3954
|
+
return Array.from(this.contracts.values()).map((contract) => ({
|
|
3955
|
+
identity: contract.identity,
|
|
3956
|
+
contract
|
|
3957
|
+
}));
|
|
3958
|
+
}
|
|
3959
|
+
/**
|
|
3960
|
+
* Get catalog as JSON-serializable object (all revisions)
|
|
3961
|
+
*/
|
|
3962
|
+
toJSON() {
|
|
3963
|
+
return {
|
|
3964
|
+
contracts: this.list().map((entry) => ({
|
|
3965
|
+
major: entry.identity.major,
|
|
3966
|
+
authority: entry.identity.authority,
|
|
3967
|
+
namespace: entry.identity.namespace,
|
|
3968
|
+
revisionDigest: entry.identity.revisionDigest
|
|
3969
|
+
}))
|
|
3970
|
+
};
|
|
3971
|
+
}
|
|
3972
|
+
};
|
|
3973
|
+
}
|
|
3974
|
+
});
|
|
3101
3975
|
|
|
3102
3976
|
// src/index.ts
|
|
3103
|
-
|
|
3104
|
-
|
|
3977
|
+
import Database from "better-sqlite3";
|
|
3978
|
+
import matter from "gray-matter";
|
|
3979
|
+
import yaml from "js-yaml";
|
|
3980
|
+
import { accessSync, constants as fsConstants, existsSync as existsSync2, statSync } from "fs";
|
|
3981
|
+
import { mkdir as mkdir4, readFile as readFile3, readdir as readdir2, writeFile as writeFile3 } from "fs/promises";
|
|
3982
|
+
import { basename as basename3, dirname as dirname3, extname, isAbsolute as isAbsolute3, join as join6, relative as relative2, resolve as resolve3 } from "path";
|
|
3105
3983
|
function isTargetArtifactType(type) {
|
|
3106
3984
|
return isPacketTargetType(type);
|
|
3107
3985
|
}
|
|
@@ -3142,56 +4020,11 @@ function resolveArtifactTypeName(schema, token) {
|
|
|
3142
4020
|
}
|
|
3143
4021
|
return void 0;
|
|
3144
4022
|
}
|
|
3145
|
-
var DEFAULT_SCHEMA = {
|
|
3146
|
-
types: {
|
|
3147
|
-
decision: { paths: ["artifacts/decisions/**/*.md"], displayName: "\u51B3\u7B56\u6587\u6863", role: "decision", layer: "decision", aliases: ["decisions", "adr"] },
|
|
3148
|
-
entity: { paths: ["artifacts/entities/entity-registry.md"], displayName: "\u5B9E\u4F53\u6CE8\u518C\u8868", role: "context", layer: "domain", aliases: ["entity-registry"] },
|
|
3149
|
-
feature: { paths: ["artifacts/prd/features/**/*.md"], displayName: "\u529F\u80FD\u7279\u6027", role: "feature", layer: "prd", aliases: ["prd-feature", "prd_features"] },
|
|
3150
|
-
scenario: { paths: ["artifacts/scenarios/**/*.md"], displayName: "\u573A\u666F\u5267\u672C", role: "scenario", layer: "scenario", aliases: ["scenarios", "scenario-script"] },
|
|
3151
|
-
design: { paths: ["artifacts/design/**/*.md"], displayName: "\u8BBE\u8BA1\u89C4\u683C", role: "design", layer: "design", aliases: ["design-spec", "design_docs"] },
|
|
3152
|
-
test: { paths: ["heimdall/packages/**/*.test.ts"], displayName: "\u4EE3\u7801\u6CE8\u91CA\u8FFD\u6EAF", role: "context", layer: "implementation", aliases: ["code-test", "code-trace", "unit-test"] },
|
|
3153
|
-
e2e_test: { paths: ["artifacts/tests/e2e/*.md"], displayName: "E2E \u6D4B\u8BD5\u89C4\u683C", role: "e2e_test", layer: "verification", aliases: ["e2e-test", "e2e_tests", "tc"] },
|
|
3154
|
-
e2e_registry: { paths: ["artifacts/tests/e2e/e2e-test-registry.json"], displayName: "E2E \u6D4B\u8BD5\u6CE8\u518C\u8868", role: "context", layer: "verification", aliases: ["e2e-registry"] },
|
|
3155
|
-
"rule-golden-cases": { paths: ["artifacts/tests/rule-golden-cases.md"], displayName: "\u89C4\u5219\u9EC4\u91D1\u6D4B\u8BD5\u7528\u4F8B", role: "context", layer: "verification", aliases: ["rule_golden_cases"] },
|
|
3156
|
-
"test-strategy": { paths: ["artifacts/design/test-strategy.md"], displayName: "\u6D4B\u8BD5\u7B56\u7565", role: "context", layer: "verification", aliases: ["test_strategy"] },
|
|
3157
|
-
"traceability-matrix-v2": { paths: ["artifacts/traceability-matrix-v2.md"], displayName: "\u8FFD\u6EAF\u77E9\u9635 v2", role: "context", layer: "traceability", aliases: ["traceability_matrix_v2"] }
|
|
3158
|
-
},
|
|
3159
|
-
idPatterns: {
|
|
3160
|
-
decision: "^D-[A-Z]+-\\d+$",
|
|
3161
|
-
entity: "^E-\\d{3,}$",
|
|
3162
|
-
feature: "^[A-Z]{1,4}\\d+$",
|
|
3163
|
-
scenario: "^S-\\d+[a-z]?$",
|
|
3164
|
-
design: "^[A-Za-z0-9._-]+$",
|
|
3165
|
-
test: "^.+\\.test\\.ts$",
|
|
3166
|
-
e2e_test: "^.+:(TC-\\d+[a-z]?|FILE)$",
|
|
3167
|
-
e2e_registry: "^e2e-test-registry$",
|
|
3168
|
-
"rule-golden-cases": "^[A-Z]+-\\d{3}:(pass|fail|edge)$",
|
|
3169
|
-
"test-strategy": "^(unit|integration|e2e|desktop_chain|rule|contract)$",
|
|
3170
|
-
"traceability-matrix-v2": "^.+$"
|
|
3171
|
-
},
|
|
3172
|
-
relationFields: {
|
|
3173
|
-
feature: ["scenarios", "decisions", "depends_on", "design_docs"],
|
|
3174
|
-
scenario: ["\u5173\u8054\u529F\u80FD", "\u5173\u8054\u51B3\u7B56"],
|
|
3175
|
-
design: ["related_features", "related_scenarios"],
|
|
3176
|
-
test: ["@scenario", "@feature", "@entity", "@decision"],
|
|
3177
|
-
e2e_test: ["test_batch", "scope", "ac_coverage", "related_scenarios", "related_decisions", "related_entities", "\u8986\u76D6\u573A\u666F", "\u8986\u76D6\u529F\u80FD"],
|
|
3178
|
-
e2e_registry: ["batches"]
|
|
3179
|
-
},
|
|
3180
|
-
allowedEdges: [],
|
|
3181
|
-
forbiddenEdges: [{ from: "scenario", to: "entity", kind: "references" }],
|
|
3182
|
-
statuses: ["planned", "active", "done", "deprecated"],
|
|
3183
|
-
idRanges: {},
|
|
3184
|
-
e2e: {
|
|
3185
|
-
report_uncovered_scenarios: true,
|
|
3186
|
-
report_uncovered_features: true,
|
|
3187
|
-
runners: []
|
|
3188
|
-
}
|
|
3189
|
-
};
|
|
3190
4023
|
async function loadConfig(root) {
|
|
3191
|
-
const configPath =
|
|
4024
|
+
const configPath = join6(root, "artifact-graph.config.yaml");
|
|
3192
4025
|
let parsed = {};
|
|
3193
4026
|
try {
|
|
3194
|
-
const raw = await
|
|
4027
|
+
const raw = await readFile3(configPath, "utf-8");
|
|
3195
4028
|
parsed = yaml.load(raw) ?? {};
|
|
3196
4029
|
} catch (error) {
|
|
3197
4030
|
if (error.code !== "ENOENT") {
|
|
@@ -3365,7 +4198,7 @@ async function scanArtifacts(root, schema) {
|
|
|
3365
4198
|
continue;
|
|
3366
4199
|
}
|
|
3367
4200
|
scannedFiles.set(file, type);
|
|
3368
|
-
const raw = await
|
|
4201
|
+
const raw = await readFile3(join6(root, file), "utf-8");
|
|
3369
4202
|
const parsed = parseFile(type, file, raw, config);
|
|
3370
4203
|
nodes.push(...parsed.nodes);
|
|
3371
4204
|
edges.push(...parsed.edges);
|
|
@@ -3677,7 +4510,7 @@ async function validateScenarioPrdLinkIndex(root, graph) {
|
|
|
3677
4510
|
const indexPath = "artifacts/prd/feature-index.md";
|
|
3678
4511
|
let raw = "";
|
|
3679
4512
|
try {
|
|
3680
|
-
raw = await
|
|
4513
|
+
raw = await readFile3(join6(root, indexPath), "utf-8");
|
|
3681
4514
|
} catch (error) {
|
|
3682
4515
|
if (error.code === "ENOENT") {
|
|
3683
4516
|
return [];
|
|
@@ -3867,11 +4700,11 @@ function nextId(graph, schema, type, rangeName) {
|
|
|
3867
4700
|
throw new Error(`ID range ${type}.${rangeName} is exhausted`);
|
|
3868
4701
|
}
|
|
3869
4702
|
async function writeGraphCache(root, graph) {
|
|
3870
|
-
const cacheDir =
|
|
4703
|
+
const cacheDir = join6(root, ".artifact-graph");
|
|
3871
4704
|
await mkdir4(cacheDir, { recursive: true });
|
|
3872
|
-
await writeFile3(
|
|
4705
|
+
await writeFile3(join6(cacheDir, "index.json"), `${JSON.stringify(graph, null, 2)}
|
|
3873
4706
|
`);
|
|
3874
|
-
const db = new Database(
|
|
4707
|
+
const db = new Database(join6(cacheDir, "graph.sqlite"));
|
|
3875
4708
|
try {
|
|
3876
4709
|
db.exec(`
|
|
3877
4710
|
DROP TABLE IF EXISTS nodes;
|
|
@@ -4209,7 +5042,7 @@ function parseDecisions(path, raw) {
|
|
|
4209
5042
|
}
|
|
4210
5043
|
function isTestFile(filePath) {
|
|
4211
5044
|
const normalized = filePath.replace(/\\/g, "/");
|
|
4212
|
-
const name =
|
|
5045
|
+
const name = basename3(normalized);
|
|
4213
5046
|
if (/\.(test|spec)\.[^.]+$/.test(name)) return true;
|
|
4214
5047
|
if (/(^|\/)(tests|test|__tests__)\//.test(normalized)) return true;
|
|
4215
5048
|
if (/\w+Tests?\.java$/.test(name)) return true;
|
|
@@ -4500,7 +5333,7 @@ function parseE2eTest(path, raw) {
|
|
|
4500
5333
|
});
|
|
4501
5334
|
const nodes = [];
|
|
4502
5335
|
const edges = [];
|
|
4503
|
-
const batch = String(data.test_batch ??
|
|
5336
|
+
const batch = String(data.test_batch ?? basename3(path, extname(path))).trim();
|
|
4504
5337
|
const frontmatterScenarios = toArray(data.related_scenarios).map((value) => String(value).trim()).filter(Boolean);
|
|
4505
5338
|
const scopeFeatures = extractCodes(String(data.scope ?? ""), "feature");
|
|
4506
5339
|
const frontmatterFeatures = [.../* @__PURE__ */ new Set([...Object.keys(asRecord(data.ac_coverage)), ...scopeFeatures])];
|
|
@@ -4573,7 +5406,7 @@ function parseE2eRegistry(path, raw) {
|
|
|
4573
5406
|
data = { parseError: error.message };
|
|
4574
5407
|
}
|
|
4575
5408
|
return {
|
|
4576
|
-
nodes: [{ type: "e2e_registry", code:
|
|
5409
|
+
nodes: [{ type: "e2e_registry", code: basename3(path, extname(path)), title: "E2E Test Registry", path, line: 1, attrs: data }],
|
|
4577
5410
|
edges: []
|
|
4578
5411
|
};
|
|
4579
5412
|
}
|
|
@@ -5554,10 +6387,10 @@ function validateE2eRegistry(graph) {
|
|
|
5554
6387
|
async function validateExecutableTraceability(root, config) {
|
|
5555
6388
|
const issues = [];
|
|
5556
6389
|
const schema = config ?? await loadConfig(root);
|
|
5557
|
-
const e2eDir =
|
|
6390
|
+
const e2eDir = join6(root, "artifacts", "tests", "e2e");
|
|
5558
6391
|
let e2eFiles;
|
|
5559
6392
|
try {
|
|
5560
|
-
e2eFiles = (await
|
|
6393
|
+
e2eFiles = (await readdir2(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join6(e2eDir, name));
|
|
5561
6394
|
} catch {
|
|
5562
6395
|
return [];
|
|
5563
6396
|
}
|
|
@@ -5566,11 +6399,11 @@ async function validateExecutableTraceability(root, config) {
|
|
|
5566
6399
|
const tcKeyToFields = /* @__PURE__ */ new Map();
|
|
5567
6400
|
const mdBatches = /* @__PURE__ */ new Set();
|
|
5568
6401
|
for (const filePath of e2eFiles) {
|
|
5569
|
-
const raw = await
|
|
6402
|
+
const raw = await readFile3(filePath, "utf-8");
|
|
5570
6403
|
const relPath = relative2(root, filePath).split("\\").join("/");
|
|
5571
6404
|
const parsed = matter(raw);
|
|
5572
6405
|
const data = parsed.data;
|
|
5573
|
-
const batch = String(data.test_batch ??
|
|
6406
|
+
const batch = String(data.test_batch ?? basename3(filePath, extname(filePath))).trim();
|
|
5574
6407
|
const lines = raw.split(/\r?\n/);
|
|
5575
6408
|
const tcStarts = [];
|
|
5576
6409
|
lines.forEach((line, index) => {
|
|
@@ -5615,10 +6448,10 @@ async function validateExecutableTraceability(root, config) {
|
|
|
5615
6448
|
const tcAnnotationRegex = /\/\/!?\s*@(?:e2e_test|tc)\s+(\S+?)\s+\[(\w+)\]/;
|
|
5616
6449
|
const tcAnnotationNoLevelRegex = /\/\/!?\s*@(?:e2e_test|tc)\s+(\S+)/;
|
|
5617
6450
|
for (const specFile of specFiles) {
|
|
5618
|
-
const fullSpecPath =
|
|
6451
|
+
const fullSpecPath = join6(root, specFile);
|
|
5619
6452
|
let content;
|
|
5620
6453
|
try {
|
|
5621
|
-
content = await
|
|
6454
|
+
content = await readFile3(fullSpecPath, "utf-8");
|
|
5622
6455
|
} catch {
|
|
5623
6456
|
continue;
|
|
5624
6457
|
}
|
|
@@ -5695,7 +6528,7 @@ async function validateExecutableTraceability(root, config) {
|
|
|
5695
6528
|
if (entry.testId) {
|
|
5696
6529
|
let content;
|
|
5697
6530
|
try {
|
|
5698
|
-
content = await
|
|
6531
|
+
content = await readFile3(join6(root, normalizedRefFile), "utf-8");
|
|
5699
6532
|
} catch {
|
|
5700
6533
|
continue;
|
|
5701
6534
|
}
|
|
@@ -5872,16 +6705,16 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
5872
6705
|
let withExecutableRef = 0;
|
|
5873
6706
|
const statusBreakdown = {};
|
|
5874
6707
|
const chainTypeBreakdown = {};
|
|
5875
|
-
const e2eDir =
|
|
6708
|
+
const e2eDir = join6(root, "artifacts", "tests", "e2e");
|
|
5876
6709
|
const tcFieldsMap = /* @__PURE__ */ new Map();
|
|
5877
6710
|
let e2eFiles;
|
|
5878
6711
|
try {
|
|
5879
|
-
e2eFiles = (await
|
|
6712
|
+
e2eFiles = (await readdir2(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join6(e2eDir, name));
|
|
5880
6713
|
} catch {
|
|
5881
6714
|
e2eFiles = [];
|
|
5882
6715
|
}
|
|
5883
6716
|
for (const filePath of e2eFiles) {
|
|
5884
|
-
const raw = await
|
|
6717
|
+
const raw = await readFile3(filePath, "utf-8");
|
|
5885
6718
|
const lines = raw.split(/\r?\n/);
|
|
5886
6719
|
const tcStarts = [];
|
|
5887
6720
|
lines.forEach((line, index) => {
|
|
@@ -5891,7 +6724,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
5891
6724
|
}
|
|
5892
6725
|
});
|
|
5893
6726
|
const parsed = matter(raw);
|
|
5894
|
-
const batch = String(parsed.data.test_batch ??
|
|
6727
|
+
const batch = String(parsed.data.test_batch ?? basename3(filePath, extname(filePath))).trim();
|
|
5895
6728
|
for (let i = 0; i < tcStarts.length; i++) {
|
|
5896
6729
|
const start = tcStarts[i];
|
|
5897
6730
|
const end = tcStarts[i + 1]?.index ?? lines.length;
|
|
@@ -5954,7 +6787,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
5954
6787
|
let hasActiveE2eRef = false;
|
|
5955
6788
|
for (const entry of parseExecutableRefLines(execRef)) {
|
|
5956
6789
|
const normalized = resolveExecutableRefFile(entry.file, allProjectFiles);
|
|
5957
|
-
if (!normalized || !existsSync2(
|
|
6790
|
+
if (!normalized || !existsSync2(join6(root, normalized))) continue;
|
|
5958
6791
|
const accepting = await getAcceptingRunners(root, normalized, runners);
|
|
5959
6792
|
if (accepting.some((runner) => runner.kind === "e2e")) {
|
|
5960
6793
|
hasActiveE2eRef = true;
|
|
@@ -6013,7 +6846,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
6013
6846
|
const acCoverageRateByFeature = {};
|
|
6014
6847
|
const featureAcMap = /* @__PURE__ */ new Map();
|
|
6015
6848
|
for (const node of featureNodes) {
|
|
6016
|
-
const acs = parseAcceptanceCriteria(await
|
|
6849
|
+
const acs = parseAcceptanceCriteria(await readFile3(join6(root, node.path), "utf-8"));
|
|
6017
6850
|
featureAcMap.set(node.code, new Set(acs));
|
|
6018
6851
|
}
|
|
6019
6852
|
const coveredAcByFeature = /* @__PURE__ */ new Map();
|
|
@@ -6055,10 +6888,10 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
6055
6888
|
};
|
|
6056
6889
|
}
|
|
6057
6890
|
async function generateE2eRegistry(root, opts) {
|
|
6058
|
-
const e2eDir =
|
|
6891
|
+
const e2eDir = join6(root, "artifacts", "tests", "e2e");
|
|
6059
6892
|
let files;
|
|
6060
6893
|
try {
|
|
6061
|
-
files = (await
|
|
6894
|
+
files = (await readdir2(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).sort();
|
|
6062
6895
|
} catch {
|
|
6063
6896
|
return {
|
|
6064
6897
|
registry_version: "1.0",
|
|
@@ -6071,11 +6904,11 @@ async function generateE2eRegistry(root, opts) {
|
|
|
6071
6904
|
const batches = [];
|
|
6072
6905
|
let totalTestCases = 0;
|
|
6073
6906
|
for (const file of files) {
|
|
6074
|
-
const filePath =
|
|
6075
|
-
const raw = await
|
|
6907
|
+
const filePath = join6(e2eDir, file);
|
|
6908
|
+
const raw = await readFile3(filePath, "utf-8");
|
|
6076
6909
|
const parsed = matter(raw);
|
|
6077
6910
|
const data = parsed.data;
|
|
6078
|
-
const batch = String(data.test_batch ??
|
|
6911
|
+
const batch = String(data.test_batch ?? basename3(file, extname(file))).trim();
|
|
6079
6912
|
const relPath = `artifacts/tests/e2e/${file}`;
|
|
6080
6913
|
const scope = String(data.scope ?? "").trim();
|
|
6081
6914
|
const acCoverage = normalizeAcCoverageForRegistry(data.ac_coverage);
|
|
@@ -6164,20 +6997,6 @@ function parseExecutableRefLines(ref) {
|
|
|
6164
6997
|
}
|
|
6165
6998
|
return results;
|
|
6166
6999
|
}
|
|
6167
|
-
var VALID_TC_STATUSES = /* @__PURE__ */ new Set(["created", "automated", "verified", "waived"]);
|
|
6168
|
-
var VALID_CHAIN_TYPES = /* @__PURE__ */ new Set([
|
|
6169
|
-
"desktop_chain",
|
|
6170
|
-
"mock_playwright",
|
|
6171
|
-
"core_e2e",
|
|
6172
|
-
"cli_e2e",
|
|
6173
|
-
"ui_sidecar_bridge",
|
|
6174
|
-
"partial_sidecar",
|
|
6175
|
-
"partial_rust"
|
|
6176
|
-
]);
|
|
6177
|
-
var DEPRECATED_CHAIN_TYPE_ALIASES = {
|
|
6178
|
-
core_only: "core_e2e",
|
|
6179
|
-
frontend_only: "mock_playwright"
|
|
6180
|
-
};
|
|
6181
7000
|
function isDesktopChainType(chainType) {
|
|
6182
7001
|
const normalizedChainType = chainType.trim().toLowerCase();
|
|
6183
7002
|
return normalizedChainType === "desktop_chain" || normalizedChainType === "";
|
|
@@ -6213,10 +7032,10 @@ async function validatePartialRustEvidence(tcFields, tcKey, root, allFiles) {
|
|
|
6213
7032
|
if (!normalizedPath) {
|
|
6214
7033
|
return { hasValidPartialRust: false, detail: `partial_rust file not found: ${ref.file}` };
|
|
6215
7034
|
}
|
|
6216
|
-
const fullPath =
|
|
7035
|
+
const fullPath = join6(root, normalizedPath);
|
|
6217
7036
|
let content;
|
|
6218
7037
|
try {
|
|
6219
|
-
content = await
|
|
7038
|
+
content = await readFile3(fullPath, "utf-8");
|
|
6220
7039
|
} catch {
|
|
6221
7040
|
return { hasValidPartialRust: false, detail: `partial_rust file not found: ${ref.file}` };
|
|
6222
7041
|
}
|
|
@@ -6325,9 +7144,9 @@ function escapeRegExp(value) {
|
|
|
6325
7144
|
}
|
|
6326
7145
|
async function hasMarkdownTc(tcKey, e2eDir) {
|
|
6327
7146
|
const [batch, tcId] = tcKey.split(":");
|
|
6328
|
-
const filePath =
|
|
7147
|
+
const filePath = join6(e2eDir, `${batch}.md`);
|
|
6329
7148
|
try {
|
|
6330
|
-
const raw = await
|
|
7149
|
+
const raw = await readFile3(filePath, "utf-8");
|
|
6331
7150
|
const tcRegex = new RegExp(`^#{2,3}\\s+${escapeRegExp(tcId)}\\s*[:\uFF1A]?`, "m");
|
|
6332
7151
|
return tcRegex.test(raw);
|
|
6333
7152
|
} catch {
|
|
@@ -6347,13 +7166,13 @@ async function findFiles(root, patterns) {
|
|
|
6347
7166
|
return [...matched].sort();
|
|
6348
7167
|
}
|
|
6349
7168
|
async function walk(root, current = root) {
|
|
6350
|
-
const entries = await
|
|
7169
|
+
const entries = await readdir2(current, { withFileTypes: true });
|
|
6351
7170
|
const files = [];
|
|
6352
7171
|
for (const entry of entries) {
|
|
6353
7172
|
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git" || entry.name === ".artifact-graph") {
|
|
6354
7173
|
continue;
|
|
6355
7174
|
}
|
|
6356
|
-
const fullPath =
|
|
7175
|
+
const fullPath = join6(current, entry.name);
|
|
6357
7176
|
if (entry.isDirectory()) {
|
|
6358
7177
|
files.push(...await walk(root, fullPath));
|
|
6359
7178
|
} else {
|
|
@@ -6572,7 +7391,7 @@ function normalizeDesignCode(value) {
|
|
|
6572
7391
|
if (!raw) {
|
|
6573
7392
|
return "";
|
|
6574
7393
|
}
|
|
6575
|
-
return
|
|
7394
|
+
return basename3(raw, extname(raw));
|
|
6576
7395
|
}
|
|
6577
7396
|
function toUid(type, code) {
|
|
6578
7397
|
return `${type}:${code}`;
|
|
@@ -6669,30 +7488,6 @@ function discoverTargets(graph, options) {
|
|
|
6669
7488
|
}
|
|
6670
7489
|
return result;
|
|
6671
7490
|
}
|
|
6672
|
-
var CONTEXT_CATEGORIES = {
|
|
6673
|
-
feature: "prd",
|
|
6674
|
-
scenario: "scenario",
|
|
6675
|
-
decision: "decision",
|
|
6676
|
-
design: "design",
|
|
6677
|
-
entity: "entity",
|
|
6678
|
-
"interface_contracts": "contract",
|
|
6679
|
-
"data_contracts": "contract",
|
|
6680
|
-
"application_state_machines": "contract",
|
|
6681
|
-
"error_model": "contract",
|
|
6682
|
-
"report-contracts": "contract",
|
|
6683
|
-
"ui-flow-contracts": "contract",
|
|
6684
|
-
"non-functional-budgets": "contract",
|
|
6685
|
-
"domain-glossary": "domain",
|
|
6686
|
-
"bounded-context-map": "domain",
|
|
6687
|
-
"domain-invariants": "domain",
|
|
6688
|
-
"generation-packet-spec": "domain",
|
|
6689
|
-
"rule-golden-cases": "verification",
|
|
6690
|
-
"test-strategy": "verification",
|
|
6691
|
-
"verification-fixtures": "verification",
|
|
6692
|
-
"implementation-blueprint": "blueprint",
|
|
6693
|
-
"traceability-matrix-v2": "matrix"
|
|
6694
|
-
};
|
|
6695
|
-
var TIER_ORDER = ["baseline", "target", "direct", "matrix", "transitive"];
|
|
6696
7491
|
function resolveArtifactContext(graph, opts) {
|
|
6697
7492
|
const mode = opts.mode ?? "full";
|
|
6698
7493
|
const maxPerCategory = opts.maxPerCategory ?? 20;
|
|
@@ -6861,7 +7656,7 @@ function resolveArtifactContext(graph, opts) {
|
|
|
6861
7656
|
}
|
|
6862
7657
|
if (root) {
|
|
6863
7658
|
for (const ap of ALWAYS_PRESENT_ITEMS) {
|
|
6864
|
-
const fullPath =
|
|
7659
|
+
const fullPath = join6(root, ap.path);
|
|
6865
7660
|
let stat;
|
|
6866
7661
|
try {
|
|
6867
7662
|
stat = statSync(fullPath);
|
|
@@ -7091,13 +7886,126 @@ function formatContextMarkdown(manifest) {
|
|
|
7091
7886
|
}
|
|
7092
7887
|
return lines.join("\n");
|
|
7093
7888
|
}
|
|
7889
|
+
var TARGET_ARTIFACT_TYPES, NON_TARGET_ROLES, DEFAULT_SCHEMA, VALID_TC_STATUSES, VALID_CHAIN_TYPES, DEPRECATED_CHAIN_TYPE_ALIASES, CONTEXT_CATEGORIES, TIER_ORDER;
|
|
7890
|
+
var init_index = __esm({
|
|
7891
|
+
"src/index.ts"() {
|
|
7892
|
+
init_packet_constants();
|
|
7893
|
+
init_packet_validator();
|
|
7894
|
+
init_glob_matcher();
|
|
7895
|
+
init_packet_constants();
|
|
7896
|
+
init_target_selector();
|
|
7897
|
+
init_packet_assembler();
|
|
7898
|
+
init_packet_audit();
|
|
7899
|
+
init_packet_validator();
|
|
7900
|
+
init_packet_prompt();
|
|
7901
|
+
init_packet_prompt_validator();
|
|
7902
|
+
init_versioned_traceability();
|
|
7903
|
+
init_cli_resolver();
|
|
7904
|
+
init_git_changes();
|
|
7905
|
+
init_git_hook_path();
|
|
7906
|
+
init_hook_installer();
|
|
7907
|
+
init_review_result_validator();
|
|
7908
|
+
init_contract_kernel();
|
|
7909
|
+
TARGET_ARTIFACT_TYPES = VALID_PACKET_TARGET_TYPES;
|
|
7910
|
+
NON_TARGET_ROLES = ["context", "candidate", "not-recommended"];
|
|
7911
|
+
DEFAULT_SCHEMA = {
|
|
7912
|
+
types: {
|
|
7913
|
+
decision: { paths: ["artifacts/decisions/**/*.md"], displayName: "\u51B3\u7B56\u6587\u6863", role: "decision", layer: "decision", aliases: ["decisions", "adr"] },
|
|
7914
|
+
entity: { paths: ["artifacts/entities/entity-registry.md"], displayName: "\u5B9E\u4F53\u6CE8\u518C\u8868", role: "context", layer: "domain", aliases: ["entity-registry"] },
|
|
7915
|
+
feature: { paths: ["artifacts/prd/features/**/*.md"], displayName: "\u529F\u80FD\u7279\u6027", role: "feature", layer: "prd", aliases: ["prd-feature", "prd_features"] },
|
|
7916
|
+
scenario: { paths: ["artifacts/scenarios/**/*.md"], displayName: "\u573A\u666F\u5267\u672C", role: "scenario", layer: "scenario", aliases: ["scenarios", "scenario-script"] },
|
|
7917
|
+
design: { paths: ["artifacts/design/**/*.md"], displayName: "\u8BBE\u8BA1\u89C4\u683C", role: "design", layer: "design", aliases: ["design-spec", "design_docs"] },
|
|
7918
|
+
test: { paths: ["heimdall/packages/**/*.test.ts"], displayName: "\u4EE3\u7801\u6CE8\u91CA\u8FFD\u6EAF", role: "context", layer: "implementation", aliases: ["code-test", "code-trace", "unit-test"] },
|
|
7919
|
+
e2e_test: { paths: ["artifacts/tests/e2e/*.md"], displayName: "E2E \u6D4B\u8BD5\u89C4\u683C", role: "e2e_test", layer: "verification", aliases: ["e2e-test", "e2e_tests", "tc"] },
|
|
7920
|
+
e2e_registry: { paths: ["artifacts/tests/e2e/e2e-test-registry.json"], displayName: "E2E \u6D4B\u8BD5\u6CE8\u518C\u8868", role: "context", layer: "verification", aliases: ["e2e-registry"] },
|
|
7921
|
+
"rule-golden-cases": { paths: ["artifacts/tests/rule-golden-cases.md"], displayName: "\u89C4\u5219\u9EC4\u91D1\u6D4B\u8BD5\u7528\u4F8B", role: "context", layer: "verification", aliases: ["rule_golden_cases"] },
|
|
7922
|
+
"test-strategy": { paths: ["artifacts/design/test-strategy.md"], displayName: "\u6D4B\u8BD5\u7B56\u7565", role: "context", layer: "verification", aliases: ["test_strategy"] },
|
|
7923
|
+
"traceability-matrix-v2": { paths: ["artifacts/traceability-matrix-v2.md"], displayName: "\u8FFD\u6EAF\u77E9\u9635 v2", role: "context", layer: "traceability", aliases: ["traceability_matrix_v2"] }
|
|
7924
|
+
},
|
|
7925
|
+
idPatterns: {
|
|
7926
|
+
decision: "^D-[A-Z]+-\\d+$",
|
|
7927
|
+
entity: "^E-\\d{3,}$",
|
|
7928
|
+
feature: "^[A-Z]{1,4}\\d+$",
|
|
7929
|
+
scenario: "^S-\\d+[a-z]?$",
|
|
7930
|
+
design: "^[A-Za-z0-9._-]+$",
|
|
7931
|
+
test: "^.+\\.test\\.ts$",
|
|
7932
|
+
e2e_test: "^.+:(TC-\\d+[a-z]?|FILE)$",
|
|
7933
|
+
e2e_registry: "^e2e-test-registry$",
|
|
7934
|
+
"rule-golden-cases": "^[A-Z]+-\\d{3}:(pass|fail|edge)$",
|
|
7935
|
+
"test-strategy": "^(unit|integration|e2e|desktop_chain|rule|contract)$",
|
|
7936
|
+
"traceability-matrix-v2": "^.+$"
|
|
7937
|
+
},
|
|
7938
|
+
relationFields: {
|
|
7939
|
+
feature: ["scenarios", "decisions", "depends_on", "design_docs"],
|
|
7940
|
+
scenario: ["\u5173\u8054\u529F\u80FD", "\u5173\u8054\u51B3\u7B56"],
|
|
7941
|
+
design: ["related_features", "related_scenarios"],
|
|
7942
|
+
test: ["@scenario", "@feature", "@entity", "@decision"],
|
|
7943
|
+
e2e_test: ["test_batch", "scope", "ac_coverage", "related_scenarios", "related_decisions", "related_entities", "\u8986\u76D6\u573A\u666F", "\u8986\u76D6\u529F\u80FD"],
|
|
7944
|
+
e2e_registry: ["batches"]
|
|
7945
|
+
},
|
|
7946
|
+
allowedEdges: [],
|
|
7947
|
+
forbiddenEdges: [{ from: "scenario", to: "entity", kind: "references" }],
|
|
7948
|
+
statuses: ["planned", "active", "done", "deprecated"],
|
|
7949
|
+
idRanges: {},
|
|
7950
|
+
e2e: {
|
|
7951
|
+
report_uncovered_scenarios: true,
|
|
7952
|
+
report_uncovered_features: true,
|
|
7953
|
+
runners: []
|
|
7954
|
+
}
|
|
7955
|
+
};
|
|
7956
|
+
VALID_TC_STATUSES = /* @__PURE__ */ new Set(["created", "automated", "verified", "waived"]);
|
|
7957
|
+
VALID_CHAIN_TYPES = /* @__PURE__ */ new Set([
|
|
7958
|
+
"desktop_chain",
|
|
7959
|
+
"mock_playwright",
|
|
7960
|
+
"core_e2e",
|
|
7961
|
+
"cli_e2e",
|
|
7962
|
+
"ui_sidecar_bridge",
|
|
7963
|
+
"partial_sidecar",
|
|
7964
|
+
"partial_rust"
|
|
7965
|
+
]);
|
|
7966
|
+
DEPRECATED_CHAIN_TYPE_ALIASES = {
|
|
7967
|
+
core_only: "core_e2e",
|
|
7968
|
+
frontend_only: "mock_playwright"
|
|
7969
|
+
};
|
|
7970
|
+
CONTEXT_CATEGORIES = {
|
|
7971
|
+
feature: "prd",
|
|
7972
|
+
scenario: "scenario",
|
|
7973
|
+
decision: "decision",
|
|
7974
|
+
design: "design",
|
|
7975
|
+
entity: "entity",
|
|
7976
|
+
"interface_contracts": "contract",
|
|
7977
|
+
"data_contracts": "contract",
|
|
7978
|
+
"application_state_machines": "contract",
|
|
7979
|
+
"error_model": "contract",
|
|
7980
|
+
"report-contracts": "contract",
|
|
7981
|
+
"ui-flow-contracts": "contract",
|
|
7982
|
+
"non-functional-budgets": "contract",
|
|
7983
|
+
"domain-glossary": "domain",
|
|
7984
|
+
"bounded-context-map": "domain",
|
|
7985
|
+
"domain-invariants": "domain",
|
|
7986
|
+
"generation-packet-spec": "domain",
|
|
7987
|
+
"rule-golden-cases": "verification",
|
|
7988
|
+
"test-strategy": "verification",
|
|
7989
|
+
"verification-fixtures": "verification",
|
|
7990
|
+
"implementation-blueprint": "blueprint",
|
|
7991
|
+
"traceability-matrix-v2": "matrix"
|
|
7992
|
+
};
|
|
7993
|
+
TIER_ORDER = ["baseline", "target", "direct", "matrix", "transitive"];
|
|
7994
|
+
}
|
|
7995
|
+
});
|
|
7996
|
+
init_index();
|
|
7094
7997
|
export {
|
|
7095
7998
|
ALWAYS_PRESENT_ITEMS as ALWAYS_PRESENT,
|
|
7096
7999
|
BASELINE_CONSTRAINTS,
|
|
7097
8000
|
BASELINE_CONSTRAINTS_COUNT,
|
|
7098
8001
|
BASELINE_ITEMS_COUNT,
|
|
8002
|
+
CONTRACT_ERROR_CODES,
|
|
8003
|
+
ContractCatalog,
|
|
8004
|
+
ContractError,
|
|
8005
|
+
ContractRegistry,
|
|
7099
8006
|
DEFAULT_MAX_CHARS,
|
|
7100
8007
|
DEFAULT_SCHEMA,
|
|
8008
|
+
E2E_NORMALIZER_CONFIG,
|
|
7101
8009
|
MIN_PROMPT_CHARS,
|
|
7102
8010
|
TARGET_ARTIFACT_TYPES,
|
|
7103
8011
|
VALID_PACKET_TARGET_TYPES,
|
|
@@ -7113,6 +8021,7 @@ export {
|
|
|
7113
8021
|
buildVersionIndex,
|
|
7114
8022
|
collectChangedPaths,
|
|
7115
8023
|
computeE2eCoverageStats,
|
|
8024
|
+
computeRevisionDigest,
|
|
7116
8025
|
dirname3 as dirname,
|
|
7117
8026
|
discoverAndAuditPackets,
|
|
7118
8027
|
discoverTargets,
|
|
@@ -7123,11 +8032,17 @@ export {
|
|
|
7123
8032
|
getArtifactTypeMetadata,
|
|
7124
8033
|
getTargetArtifactTypes,
|
|
7125
8034
|
installManagedHookBlock,
|
|
8035
|
+
isOfficialNamespace,
|
|
7126
8036
|
isPacketTargetType,
|
|
7127
8037
|
isPacketTargetTypeDynamic,
|
|
7128
8038
|
isTargetArtifactType,
|
|
7129
8039
|
loadConfig,
|
|
8040
|
+
loadContract,
|
|
8041
|
+
loadContractCatalog,
|
|
8042
|
+
loadContractsFromDirectory,
|
|
7130
8043
|
nextId,
|
|
8044
|
+
normalizeE2eLegacyArtifact,
|
|
8045
|
+
normalizeToCanonical,
|
|
7131
8046
|
parseTargetSelector,
|
|
7132
8047
|
parseTargetsFile,
|
|
7133
8048
|
prepareManagedHookBlock,
|
|
@@ -7149,13 +8064,17 @@ export {
|
|
|
7149
8064
|
scanArtifacts,
|
|
7150
8065
|
traceVersion,
|
|
7151
8066
|
updateVersionLock,
|
|
8067
|
+
validateContractAgainstSchema,
|
|
7152
8068
|
validateExecutableTraceability,
|
|
7153
8069
|
validateGraph,
|
|
8070
|
+
validateNamespaceAuthority,
|
|
7154
8071
|
validatePacket,
|
|
7155
8072
|
validatePacketMarkdown,
|
|
7156
8073
|
validatePacketPrompt,
|
|
8074
|
+
validatePolicyCompatibility,
|
|
7157
8075
|
validateReviewResult,
|
|
7158
8076
|
validateScenarioPrdLinkIndex,
|
|
7159
8077
|
validateScenarioPrdLinks,
|
|
8078
|
+
verifyDigest,
|
|
7160
8079
|
writeGraphCache
|
|
7161
8080
|
};
|