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/cli.js
CHANGED
|
@@ -1,57 +1,55 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import Database from "better-sqlite3";
|
|
12
|
-
import matter from "gray-matter";
|
|
13
|
-
import yaml from "js-yaml";
|
|
14
|
-
import { accessSync, constants as fsConstants, existsSync as existsSync2, statSync } from "fs";
|
|
15
|
-
import { mkdir as mkdir4, readFile as readFile2, readdir, writeFile as writeFile3 } from "fs/promises";
|
|
16
|
-
import { basename as basename2, dirname as dirname3, extname, isAbsolute as isAbsolute3, join as join5, relative as relative2, resolve as resolve3 } from "path";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
17
11
|
|
|
18
12
|
// src/packet-constants.ts
|
|
19
|
-
var ALWAYS_PRESENT_ITEMS
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
13
|
+
var ALWAYS_PRESENT_ITEMS, BASELINE_ITEMS_COUNT, BASELINE_CONSTRAINTS, BASELINE_CONSTRAINTS_COUNT;
|
|
14
|
+
var init_packet_constants = __esm({
|
|
15
|
+
"src/packet-constants.ts"() {
|
|
16
|
+
"use strict";
|
|
17
|
+
ALWAYS_PRESENT_ITEMS = [
|
|
18
|
+
{ path: "AGENTS.md", reason: "project agent instructions" },
|
|
19
|
+
{ path: "CLAUDE.md", reason: "project instructions" },
|
|
20
|
+
{ path: "artifacts/artifact-chain-spec.md", reason: "artifact chain specification" },
|
|
21
|
+
{ path: "artifacts/blueprints/generation-packet-spec.md", reason: "generation packet spec" },
|
|
22
|
+
{ path: "artifacts/blueprints/implementation-blueprint.md", reason: "implementation blueprint" },
|
|
23
|
+
{ path: "artifacts/contracts/interface-contracts.md", reason: "interface contracts" },
|
|
24
|
+
{ path: "artifacts/contracts/data-contracts.md", reason: "data contracts" },
|
|
25
|
+
{ path: "artifacts/contracts/application-state-machines.md", reason: "application state machines" },
|
|
26
|
+
{ path: "artifacts/contracts/error-model.md", reason: "error model" },
|
|
27
|
+
{ path: "artifacts/contracts/report-contracts.md", reason: "report contracts" },
|
|
28
|
+
{ path: "artifacts/contracts/ui-flow-contracts.md", reason: "UI flow contracts" },
|
|
29
|
+
{ path: "artifacts/contracts/non-functional-budgets.md", reason: "non-functional budgets" },
|
|
30
|
+
{ path: "artifacts/domain/domain-glossary.md", reason: "domain glossary" },
|
|
31
|
+
{ path: "artifacts/domain/bounded-context-map.md", reason: "bounded context map" },
|
|
32
|
+
{ path: "artifacts/domain/domain-invariants.md", reason: "domain invariants" },
|
|
33
|
+
{ path: "artifacts/tests/rule-golden-cases.md", reason: "rule golden cases" },
|
|
34
|
+
{ path: "artifacts/tests/verification-fixtures.md", reason: "verification fixtures" },
|
|
35
|
+
{ path: "artifacts/design/test-strategy.md", reason: "test strategy" },
|
|
36
|
+
{ path: "artifacts/traceability-matrix-v2.md", reason: "traceability matrix" }
|
|
37
|
+
];
|
|
38
|
+
BASELINE_ITEMS_COUNT = ALWAYS_PRESENT_ITEMS.length;
|
|
39
|
+
BASELINE_CONSTRAINTS = [
|
|
40
|
+
{ id: "C-ARCH-01", description: "\u6280\u672F\u6808\u5FC5\u987B\u4E3A TypeScript \u5168\u6808", source: "artifacts/decisions/D-ARCH-01.md" },
|
|
41
|
+
{ id: "C-ARCH-03", description: "\u4E0D\u53EF\u5728 Quick Check \u8DEF\u5F84\u5F15\u5165 LLM", source: "artifacts/decisions/D-ARCH-03.md" },
|
|
42
|
+
{ 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" },
|
|
43
|
+
{ id: "C-RULE-01", description: "SEC severity \u4E0D\u5141\u8BB8\u964D\u7EA7", source: "artifacts/decisions/D-RULE-01.md" },
|
|
44
|
+
{ 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" },
|
|
45
|
+
{ 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" },
|
|
46
|
+
{ 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" }
|
|
47
|
+
];
|
|
48
|
+
BASELINE_CONSTRAINTS_COUNT = BASELINE_CONSTRAINTS.length;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
51
|
|
|
52
52
|
// src/packet-validator.ts
|
|
53
|
-
var MIN_VALIDATION_COMMANDS = 4;
|
|
54
|
-
var VALID_PACKET_TARGET_TYPES = ["feature", "scenario", "decision", "design", "e2e_test"];
|
|
55
53
|
function isPacketTargetType(type) {
|
|
56
54
|
return VALID_PACKET_TARGET_TYPES.includes(type);
|
|
57
55
|
}
|
|
@@ -64,7 +62,6 @@ function isPacketTargetTypeDynamic(type, schema) {
|
|
|
64
62
|
}
|
|
65
63
|
return isPacketTargetType(type);
|
|
66
64
|
}
|
|
67
|
-
var VALID_TARGET_TYPES = [...VALID_PACKET_TARGET_TYPES];
|
|
68
65
|
function validatePacket(packet, schema) {
|
|
69
66
|
const issues = [];
|
|
70
67
|
if (packet.schemaVersion !== "1.0") {
|
|
@@ -242,6 +239,16 @@ function validatePacket(packet, schema) {
|
|
|
242
239
|
const hasError = issues.some((i) => i.severity === "error");
|
|
243
240
|
return { ok: !hasError, issues };
|
|
244
241
|
}
|
|
242
|
+
var MIN_VALIDATION_COMMANDS, VALID_PACKET_TARGET_TYPES, VALID_TARGET_TYPES;
|
|
243
|
+
var init_packet_validator = __esm({
|
|
244
|
+
"src/packet-validator.ts"() {
|
|
245
|
+
"use strict";
|
|
246
|
+
init_packet_constants();
|
|
247
|
+
MIN_VALIDATION_COMMANDS = 4;
|
|
248
|
+
VALID_PACKET_TARGET_TYPES = ["feature", "scenario", "decision", "design", "e2e_test"];
|
|
249
|
+
VALID_TARGET_TYPES = [...VALID_PACKET_TARGET_TYPES];
|
|
250
|
+
}
|
|
251
|
+
});
|
|
245
252
|
|
|
246
253
|
// src/glob-matcher.ts
|
|
247
254
|
function matchesRunnerGlob(filePath, pattern) {
|
|
@@ -281,6 +288,11 @@ function normalizeGlobValue(value) {
|
|
|
281
288
|
function escapeRegexCharacter(character) {
|
|
282
289
|
return "\\^$+?.()|{}[]".includes(character) ? String.fromCharCode(92) + character : character;
|
|
283
290
|
}
|
|
291
|
+
var init_glob_matcher = __esm({
|
|
292
|
+
"src/glob-matcher.ts"() {
|
|
293
|
+
"use strict";
|
|
294
|
+
}
|
|
295
|
+
});
|
|
284
296
|
|
|
285
297
|
// src/target-selector.ts
|
|
286
298
|
function parseTargetSelector(value) {
|
|
@@ -290,14 +302,6 @@ function parseTargetSelector(value) {
|
|
|
290
302
|
}
|
|
291
303
|
return { type: value.slice(0, separator), id: value.slice(separator + 1) };
|
|
292
304
|
}
|
|
293
|
-
var LEGACY_FLAGS = ["feature", "scenario", "decision", "design", "e2e-test"];
|
|
294
|
-
var LEGACY_FLAG_TO_TYPE = {
|
|
295
|
-
feature: "feature",
|
|
296
|
-
scenario: "scenario",
|
|
297
|
-
decision: "decision",
|
|
298
|
-
design: "design",
|
|
299
|
-
"e2e-test": "e2e_test"
|
|
300
|
-
};
|
|
301
305
|
function resolveCliTarget(flags, schema) {
|
|
302
306
|
const hasTarget = typeof flags.target === "string";
|
|
303
307
|
const legacyEntries = LEGACY_FLAGS.filter((flag) => typeof flags[flag] === "string").map((flag) => flag);
|
|
@@ -330,14 +334,23 @@ function resolveCliTarget(flags, schema) {
|
|
|
330
334
|
"\u672A\u6307\u5B9A target\uFF1A\u8BF7\u4F7F\u7528 --target <type>:<id> \u6216 --feature/--scenario/--decision/--design/--e2e-test <id>"
|
|
331
335
|
);
|
|
332
336
|
}
|
|
337
|
+
var LEGACY_FLAGS, LEGACY_FLAG_TO_TYPE;
|
|
338
|
+
var init_target_selector = __esm({
|
|
339
|
+
"src/target-selector.ts"() {
|
|
340
|
+
"use strict";
|
|
341
|
+
init_index();
|
|
342
|
+
LEGACY_FLAGS = ["feature", "scenario", "decision", "design", "e2e-test"];
|
|
343
|
+
LEGACY_FLAG_TO_TYPE = {
|
|
344
|
+
feature: "feature",
|
|
345
|
+
scenario: "scenario",
|
|
346
|
+
decision: "decision",
|
|
347
|
+
design: "design",
|
|
348
|
+
"e2e-test": "e2e_test"
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
});
|
|
333
352
|
|
|
334
353
|
// src/packet-assembler.ts
|
|
335
|
-
var DEFAULT_VALIDATION_COMMANDS = [
|
|
336
|
-
"pnpm build",
|
|
337
|
-
"pnpm test",
|
|
338
|
-
"artifact-graph validate --root . --warning-only",
|
|
339
|
-
"artifact-graph version-lock audit --root . --strict-missing-lock"
|
|
340
|
-
];
|
|
341
354
|
function toPacketItem(item) {
|
|
342
355
|
const pi = {
|
|
343
356
|
path: item.path,
|
|
@@ -474,12 +487,6 @@ function renderContextCategory(cat, opts) {
|
|
|
474
487
|
}
|
|
475
488
|
return lines;
|
|
476
489
|
}
|
|
477
|
-
var COMMAND_COMMENTS = {
|
|
478
|
-
"pnpm build": "\u6784\u5EFA\u9879\u76EE",
|
|
479
|
-
"pnpm test": "\u8FD0\u884C\u9879\u76EE\u6D4B\u8BD5",
|
|
480
|
-
"artifact-graph validate --root . --warning-only": "\u5236\u54C1\u94FE\u4E00\u81F4\u6027\u6821\u9A8C\uFF08warning-only\uFF09",
|
|
481
|
-
"artifact-graph version-lock audit --root . --strict-missing-lock": "\u5236\u54C1\u94FE\u7248\u672C\u9501\u4E25\u683C\u5BA1\u8BA1"
|
|
482
|
-
};
|
|
483
490
|
function classifyCategories(manifest) {
|
|
484
491
|
let baseline = { category: "baseline", total: 0, items: [] };
|
|
485
492
|
const direct = [];
|
|
@@ -776,12 +783,29 @@ function renderPacketMarkdown(packet) {
|
|
|
776
783
|
lines.push("> \u63D0\u793A\uFF1A\u4F7F\u7528 Ctrl+F \u641C\u7D22 `[\u5FC5\u8BFB]` \u5FEB\u901F\u5B9A\u4F4D\u5FC5\u8BFB\u5236\u54C1\u3002");
|
|
777
784
|
return lines.join("\n");
|
|
778
785
|
}
|
|
786
|
+
var DEFAULT_VALIDATION_COMMANDS, COMMAND_COMMENTS;
|
|
787
|
+
var init_packet_assembler = __esm({
|
|
788
|
+
"src/packet-assembler.ts"() {
|
|
789
|
+
"use strict";
|
|
790
|
+
init_packet_constants();
|
|
791
|
+
DEFAULT_VALIDATION_COMMANDS = [
|
|
792
|
+
"pnpm build",
|
|
793
|
+
"pnpm test",
|
|
794
|
+
"artifact-graph validate --root . --warning-only",
|
|
795
|
+
"artifact-graph version-lock audit --root . --strict-missing-lock"
|
|
796
|
+
];
|
|
797
|
+
COMMAND_COMMENTS = {
|
|
798
|
+
"pnpm build": "\u6784\u5EFA\u9879\u76EE",
|
|
799
|
+
"pnpm test": "\u8FD0\u884C\u9879\u76EE\u6D4B\u8BD5",
|
|
800
|
+
"artifact-graph validate --root . --warning-only": "\u5236\u54C1\u94FE\u4E00\u81F4\u6027\u6821\u9A8C\uFF08warning-only\uFF09",
|
|
801
|
+
"artifact-graph version-lock audit --root . --strict-missing-lock": "\u5236\u54C1\u94FE\u7248\u672C\u9501\u4E25\u683C\u5BA1\u8BA1"
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
});
|
|
779
805
|
|
|
780
806
|
// src/packet-audit.ts
|
|
781
807
|
import { mkdir, writeFile } from "fs/promises";
|
|
782
808
|
import { join } from "path";
|
|
783
|
-
var VALID_TYPES = new Set(VALID_PACKET_TARGET_TYPES);
|
|
784
|
-
var VALID_TYPES_LABEL = VALID_PACKET_TARGET_TYPES.join(", ");
|
|
785
809
|
function parseTargetsFile(content, schema) {
|
|
786
810
|
const validTypes = schema ? new Set(getTargetArtifactTypes(schema)) : VALID_TYPES;
|
|
787
811
|
const validTypesLabel = [...validTypes].join(", ");
|
|
@@ -979,20 +1003,21 @@ async function discoverAndAuditPackets(root, options) {
|
|
|
979
1003
|
universalBaseline: effectiveBaseline
|
|
980
1004
|
}, graph);
|
|
981
1005
|
}
|
|
1006
|
+
var VALID_TYPES, VALID_TYPES_LABEL;
|
|
1007
|
+
var init_packet_audit = __esm({
|
|
1008
|
+
"src/packet-audit.ts"() {
|
|
1009
|
+
"use strict";
|
|
1010
|
+
init_index();
|
|
1011
|
+
init_packet_validator();
|
|
1012
|
+
VALID_TYPES = new Set(VALID_PACKET_TARGET_TYPES);
|
|
1013
|
+
VALID_TYPES_LABEL = VALID_PACKET_TARGET_TYPES.join(", ");
|
|
1014
|
+
}
|
|
1015
|
+
});
|
|
982
1016
|
|
|
983
1017
|
// src/packet-prompt.ts
|
|
984
|
-
var TYPE_CLI_FLAG = {
|
|
985
|
-
feature: "feature",
|
|
986
|
-
scenario: "scenario",
|
|
987
|
-
decision: "decision",
|
|
988
|
-
design: "design",
|
|
989
|
-
e2e_test: "e2e-test"
|
|
990
|
-
};
|
|
991
1018
|
function cliFlag(type) {
|
|
992
1019
|
return TYPE_CLI_FLAG[type] ?? type;
|
|
993
1020
|
}
|
|
994
|
-
var DEFAULT_MAX_CHARS = 4e3;
|
|
995
|
-
var MIN_PROMPT_CHARS = 320;
|
|
996
1021
|
function renderPromptHeader(packet) {
|
|
997
1022
|
const t = packet.target;
|
|
998
1023
|
const typeLabelMap = { feature: "\u529F\u80FD", scenario: "\u573A\u666F", decision: "\u51B3\u7B56", design: "\u8BBE\u8BA1\u89C4\u683C", e2e_test: "E2E \u6D4B\u8BD5" };
|
|
@@ -1216,52 +1241,24 @@ function renderPacketPrompt(packet, options) {
|
|
|
1216
1241
|
minRequired: absMinText.length
|
|
1217
1242
|
};
|
|
1218
1243
|
}
|
|
1244
|
+
var TYPE_CLI_FLAG, DEFAULT_MAX_CHARS, MIN_PROMPT_CHARS;
|
|
1245
|
+
var init_packet_prompt = __esm({
|
|
1246
|
+
"src/packet-prompt.ts"() {
|
|
1247
|
+
"use strict";
|
|
1248
|
+
init_packet_constants();
|
|
1249
|
+
TYPE_CLI_FLAG = {
|
|
1250
|
+
feature: "feature",
|
|
1251
|
+
scenario: "scenario",
|
|
1252
|
+
decision: "decision",
|
|
1253
|
+
design: "design",
|
|
1254
|
+
e2e_test: "e2e-test"
|
|
1255
|
+
};
|
|
1256
|
+
DEFAULT_MAX_CHARS = 4e3;
|
|
1257
|
+
MIN_PROMPT_CHARS = 320;
|
|
1258
|
+
}
|
|
1259
|
+
});
|
|
1219
1260
|
|
|
1220
1261
|
// src/packet-prompt-validator.ts
|
|
1221
|
-
var REQUIRED_CHECKS = [
|
|
1222
|
-
{
|
|
1223
|
-
code: "PP-001",
|
|
1224
|
-
description: "\u76EE\u6807\u4FE1\u606F",
|
|
1225
|
-
test: (t) => /实现任务/.test(t),
|
|
1226
|
-
severity: "error"
|
|
1227
|
-
},
|
|
1228
|
-
{
|
|
1229
|
-
code: "PP-002",
|
|
1230
|
-
description: "packet \u547D\u4EE4\u6216\u6765\u6E90\u5F15\u7528",
|
|
1231
|
-
test: (t) => /packet\s+--root/.test(t) || /packet\.json/.test(t) || /artifact-graph\s+packet/.test(t),
|
|
1232
|
-
severity: "error"
|
|
1233
|
-
},
|
|
1234
|
-
{
|
|
1235
|
-
code: "PP-003",
|
|
1236
|
-
description: "\u9A8C\u8BC1\u547D\u4EE4",
|
|
1237
|
-
test: (t) => /验证命令/.test(t) || /artifact-graph\s+(build|test)/.test(t),
|
|
1238
|
-
severity: "error"
|
|
1239
|
-
},
|
|
1240
|
-
{
|
|
1241
|
-
code: "PP-004",
|
|
1242
|
-
description: "\u63D0\u4EA4\u8981\u6C42",
|
|
1243
|
-
test: (t) => /提交要求/.test(t) || /git\s+diff\s+--check/.test(t),
|
|
1244
|
-
severity: "error"
|
|
1245
|
-
},
|
|
1246
|
-
{
|
|
1247
|
-
code: "PP-005",
|
|
1248
|
-
description: "\u7981\u6B62\u4E8B\u9879",
|
|
1249
|
-
test: (t) => /禁止事项/.test(t),
|
|
1250
|
-
severity: "error"
|
|
1251
|
-
},
|
|
1252
|
-
{
|
|
1253
|
-
code: "PP-006",
|
|
1254
|
-
description: "\u4E0D\u5F97\u56DE\u9000\u89C4\u5219",
|
|
1255
|
-
test: (t) => /不得回退/.test(t) || /不要回退/.test(t) || /no[- ]?revert/i.test(t),
|
|
1256
|
-
severity: "error"
|
|
1257
|
-
},
|
|
1258
|
-
{
|
|
1259
|
-
code: "PP-007",
|
|
1260
|
-
description: "SEC severity \u89C4\u5219",
|
|
1261
|
-
test: (t) => /SEC\s+severity/.test(t) || /severity/.test(t) && /降级/.test(t),
|
|
1262
|
-
severity: "error"
|
|
1263
|
-
}
|
|
1264
|
-
];
|
|
1265
1262
|
function checkChineseDominance(text) {
|
|
1266
1263
|
const textOutsideCode = text.replace(/```[\s\S]*?```/g, "");
|
|
1267
1264
|
const cleanText = textOutsideCode.replace(/^[#>]+ .*$/gm, "").replace(/\[.*?\]\(.*?\)/g, "");
|
|
@@ -1296,15 +1293,62 @@ function validatePacketPrompt(prompt) {
|
|
|
1296
1293
|
const hasErrors = issues.some((i) => i.severity === "error");
|
|
1297
1294
|
return { ok: !hasErrors, issues };
|
|
1298
1295
|
}
|
|
1296
|
+
var REQUIRED_CHECKS;
|
|
1297
|
+
var init_packet_prompt_validator = __esm({
|
|
1298
|
+
"src/packet-prompt-validator.ts"() {
|
|
1299
|
+
"use strict";
|
|
1300
|
+
REQUIRED_CHECKS = [
|
|
1301
|
+
{
|
|
1302
|
+
code: "PP-001",
|
|
1303
|
+
description: "\u76EE\u6807\u4FE1\u606F",
|
|
1304
|
+
test: (t) => /实现任务/.test(t),
|
|
1305
|
+
severity: "error"
|
|
1306
|
+
},
|
|
1307
|
+
{
|
|
1308
|
+
code: "PP-002",
|
|
1309
|
+
description: "packet \u547D\u4EE4\u6216\u6765\u6E90\u5F15\u7528",
|
|
1310
|
+
test: (t) => /packet\s+--root/.test(t) || /packet\.json/.test(t) || /artifact-graph\s+packet/.test(t),
|
|
1311
|
+
severity: "error"
|
|
1312
|
+
},
|
|
1313
|
+
{
|
|
1314
|
+
code: "PP-003",
|
|
1315
|
+
description: "\u9A8C\u8BC1\u547D\u4EE4",
|
|
1316
|
+
test: (t) => /验证命令/.test(t) || /artifact-graph\s+(build|test)/.test(t),
|
|
1317
|
+
severity: "error"
|
|
1318
|
+
},
|
|
1319
|
+
{
|
|
1320
|
+
code: "PP-004",
|
|
1321
|
+
description: "\u63D0\u4EA4\u8981\u6C42",
|
|
1322
|
+
test: (t) => /提交要求/.test(t) || /git\s+diff\s+--check/.test(t),
|
|
1323
|
+
severity: "error"
|
|
1324
|
+
},
|
|
1325
|
+
{
|
|
1326
|
+
code: "PP-005",
|
|
1327
|
+
description: "\u7981\u6B62\u4E8B\u9879",
|
|
1328
|
+
test: (t) => /禁止事项/.test(t),
|
|
1329
|
+
severity: "error"
|
|
1330
|
+
},
|
|
1331
|
+
{
|
|
1332
|
+
code: "PP-006",
|
|
1333
|
+
description: "\u4E0D\u5F97\u56DE\u9000\u89C4\u5219",
|
|
1334
|
+
test: (t) => /不得回退/.test(t) || /不要回退/.test(t) || /no[- ]?revert/i.test(t),
|
|
1335
|
+
severity: "error"
|
|
1336
|
+
},
|
|
1337
|
+
{
|
|
1338
|
+
code: "PP-007",
|
|
1339
|
+
description: "SEC severity \u89C4\u5219",
|
|
1340
|
+
test: (t) => /SEC\s+severity/.test(t) || /severity/.test(t) && /降级/.test(t),
|
|
1341
|
+
severity: "error"
|
|
1342
|
+
}
|
|
1343
|
+
];
|
|
1344
|
+
}
|
|
1345
|
+
});
|
|
1299
1346
|
|
|
1300
1347
|
// src/versioned-traceability.ts
|
|
1301
1348
|
import { createHash } from "crypto";
|
|
1302
1349
|
import { existsSync } from "fs";
|
|
1303
1350
|
import { mkdir as mkdir2, readFile, writeFile as writeFile2 } from "fs/promises";
|
|
1304
1351
|
import { dirname, join as join2, relative } from "path";
|
|
1305
|
-
var VERSION_LOCK_PATH = "artifacts/traceability-version-lock.json";
|
|
1306
|
-
var VERSION_INDEX_SCHEMA_VERSION = "1.0";
|
|
1307
|
-
var VERSION_LOCK_SCHEMA_VERSION = "1.0";
|
|
1308
1352
|
async function buildVersionIndex(root, graph) {
|
|
1309
1353
|
const scannedGraph = graph ?? await scanArtifacts(root);
|
|
1310
1354
|
const hashCache = /* @__PURE__ */ new Map();
|
|
@@ -2104,6 +2148,17 @@ async function isFileActiveInRunner(root, filePath, runner) {
|
|
|
2104
2148
|
function sortUnique(items) {
|
|
2105
2149
|
return [...new Set(items)].sort((left, right) => left.localeCompare(right));
|
|
2106
2150
|
}
|
|
2151
|
+
var VERSION_LOCK_PATH, VERSION_INDEX_SCHEMA_VERSION, VERSION_LOCK_SCHEMA_VERSION;
|
|
2152
|
+
var init_versioned_traceability = __esm({
|
|
2153
|
+
"src/versioned-traceability.ts"() {
|
|
2154
|
+
"use strict";
|
|
2155
|
+
init_index();
|
|
2156
|
+
init_glob_matcher();
|
|
2157
|
+
VERSION_LOCK_PATH = "artifacts/traceability-version-lock.json";
|
|
2158
|
+
VERSION_INDEX_SCHEMA_VERSION = "1.0";
|
|
2159
|
+
VERSION_LOCK_SCHEMA_VERSION = "1.0";
|
|
2160
|
+
}
|
|
2161
|
+
});
|
|
2107
2162
|
|
|
2108
2163
|
// src/cli-resolver.ts
|
|
2109
2164
|
import { execFile } from "child_process";
|
|
@@ -2111,27 +2166,6 @@ import { constants } from "fs";
|
|
|
2111
2166
|
import { access } from "fs/promises";
|
|
2112
2167
|
import { isAbsolute, join as join3, resolve } from "path";
|
|
2113
2168
|
import { promisify } from "util";
|
|
2114
|
-
var execFileAsync = promisify(execFile);
|
|
2115
|
-
var KNOWN_COMMANDS = [
|
|
2116
|
-
"init",
|
|
2117
|
-
"scan",
|
|
2118
|
-
"validate",
|
|
2119
|
-
"query",
|
|
2120
|
-
"context",
|
|
2121
|
-
"packet",
|
|
2122
|
-
"packet-prompt",
|
|
2123
|
-
"packet-audit",
|
|
2124
|
-
"packet-prompt-audit",
|
|
2125
|
-
"version-index",
|
|
2126
|
-
"version-lock audit",
|
|
2127
|
-
"version-lock update",
|
|
2128
|
-
"version-lock bootstrap",
|
|
2129
|
-
"version-lock refresh",
|
|
2130
|
-
"trace-version",
|
|
2131
|
-
"next-id",
|
|
2132
|
-
"render",
|
|
2133
|
-
"doctor"
|
|
2134
|
-
];
|
|
2135
2169
|
async function resolveArtifactGraphCli(root, options = {}) {
|
|
2136
2170
|
const pathCli = await findCommandOnPath("artifact-graph");
|
|
2137
2171
|
const legacyCliPath = options.projectCliPath ?? process.env.ARTIFACT_GRAPH_LEGACY_CLI;
|
|
@@ -2267,11 +2301,38 @@ function isNodeCompatible(version) {
|
|
|
2267
2301
|
const major = Number(version.split(".")[0]);
|
|
2268
2302
|
return Number.isFinite(major) && major >= 22;
|
|
2269
2303
|
}
|
|
2304
|
+
var execFileAsync, KNOWN_COMMANDS;
|
|
2305
|
+
var init_cli_resolver = __esm({
|
|
2306
|
+
"src/cli-resolver.ts"() {
|
|
2307
|
+
"use strict";
|
|
2308
|
+
init_versioned_traceability();
|
|
2309
|
+
execFileAsync = promisify(execFile);
|
|
2310
|
+
KNOWN_COMMANDS = [
|
|
2311
|
+
"init",
|
|
2312
|
+
"scan",
|
|
2313
|
+
"validate",
|
|
2314
|
+
"query",
|
|
2315
|
+
"context",
|
|
2316
|
+
"packet",
|
|
2317
|
+
"packet-prompt",
|
|
2318
|
+
"packet-audit",
|
|
2319
|
+
"packet-prompt-audit",
|
|
2320
|
+
"version-index",
|
|
2321
|
+
"version-lock audit",
|
|
2322
|
+
"version-lock update",
|
|
2323
|
+
"version-lock bootstrap",
|
|
2324
|
+
"version-lock refresh",
|
|
2325
|
+
"trace-version",
|
|
2326
|
+
"next-id",
|
|
2327
|
+
"render",
|
|
2328
|
+
"doctor"
|
|
2329
|
+
];
|
|
2330
|
+
}
|
|
2331
|
+
});
|
|
2270
2332
|
|
|
2271
2333
|
// src/git-changes.ts
|
|
2272
2334
|
import { execFile as execFile2 } from "child_process";
|
|
2273
2335
|
import { promisify as promisify2 } from "util";
|
|
2274
|
-
var execFileAsync2 = promisify2(execFile2);
|
|
2275
2336
|
async function collectChangedPaths(root, options) {
|
|
2276
2337
|
const args = gitDiffArgs(options);
|
|
2277
2338
|
let stdout;
|
|
@@ -2281,8 +2342,10 @@ async function collectChangedPaths(root, options) {
|
|
|
2281
2342
|
const message = error instanceof Error ? error.message : String(error);
|
|
2282
2343
|
throw new Error(`Unable to collect git changed paths (${options.mode}): ${message}`);
|
|
2283
2344
|
}
|
|
2284
|
-
const
|
|
2285
|
-
const
|
|
2345
|
+
const trackedChangedPaths = normalizeGitPathList(stdout);
|
|
2346
|
+
const untrackedPaths = options.mode === "worktree" || options.mode === "staged" ? await collectUntrackedPaths(root) : [];
|
|
2347
|
+
const changedPaths = options.mode === "worktree" ? sortUnique2([...trackedChangedPaths, ...untrackedPaths]) : trackedChangedPaths;
|
|
2348
|
+
const unstagedPaths = options.mode === "staged" ? sortUnique2([...await collectUnstagedPaths(root), ...untrackedPaths]) : [];
|
|
2286
2349
|
const stagedUnstagedConflictPaths = options.mode === "staged" ? intersect(changedPaths, unstagedPaths) : [];
|
|
2287
2350
|
return {
|
|
2288
2351
|
root,
|
|
@@ -2314,6 +2377,14 @@ async function collectUnstagedPaths(root) {
|
|
|
2314
2377
|
return [];
|
|
2315
2378
|
}
|
|
2316
2379
|
}
|
|
2380
|
+
async function collectUntrackedPaths(root) {
|
|
2381
|
+
try {
|
|
2382
|
+
const { stdout } = await execFileAsync2("git", ["ls-files", "--others", "--exclude-standard"], { cwd: root });
|
|
2383
|
+
return normalizeGitPathList(stdout);
|
|
2384
|
+
} catch {
|
|
2385
|
+
return [];
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2317
2388
|
function normalizeGitPathList(stdout) {
|
|
2318
2389
|
return sortUnique2(stdout.split(/\r?\n/).map((line) => line.trim().replace(/\\/g, "/").replace(/^\.\//, "")).filter((line) => line.length > 0 && !line.startsWith("../") && !line.startsWith("/")));
|
|
2319
2390
|
}
|
|
@@ -2324,12 +2395,18 @@ function intersect(left, right) {
|
|
|
2324
2395
|
function sortUnique2(items) {
|
|
2325
2396
|
return [...new Set(items)].sort((left, right) => left.localeCompare(right));
|
|
2326
2397
|
}
|
|
2398
|
+
var execFileAsync2;
|
|
2399
|
+
var init_git_changes = __esm({
|
|
2400
|
+
"src/git-changes.ts"() {
|
|
2401
|
+
"use strict";
|
|
2402
|
+
execFileAsync2 = promisify2(execFile2);
|
|
2403
|
+
}
|
|
2404
|
+
});
|
|
2327
2405
|
|
|
2328
2406
|
// src/git-hook-path.ts
|
|
2329
2407
|
import { execFile as execFile3 } from "child_process";
|
|
2330
2408
|
import { isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
|
|
2331
2409
|
import { promisify as promisify3 } from "util";
|
|
2332
|
-
var execFileAsync3 = promisify3(execFile3);
|
|
2333
2410
|
async function resolveGitHookPath(root, hookName) {
|
|
2334
2411
|
const { stdout } = await execFileAsync3("git", [
|
|
2335
2412
|
"-C",
|
|
@@ -2342,74 +2419,19 @@ async function resolveGitHookPath(root, hookName) {
|
|
|
2342
2419
|
if (!value) throw new Error(`Git returned an empty hook path for ${hookName}`);
|
|
2343
2420
|
return isAbsolute2(value) ? resolve2(value) : resolve2(root, value);
|
|
2344
2421
|
}
|
|
2422
|
+
var execFileAsync3;
|
|
2423
|
+
var init_git_hook_path = __esm({
|
|
2424
|
+
"src/git-hook-path.ts"() {
|
|
2425
|
+
"use strict";
|
|
2426
|
+
execFileAsync3 = promisify3(execFile3);
|
|
2427
|
+
}
|
|
2428
|
+
});
|
|
2345
2429
|
|
|
2346
2430
|
// src/hook-installer.ts
|
|
2347
2431
|
import { constants as constants2 } from "fs";
|
|
2348
2432
|
import { randomUUID } from "crypto";
|
|
2349
2433
|
import { lstat, mkdir as mkdir3, open, readlink, rename, unlink } from "fs/promises";
|
|
2350
2434
|
import { basename, dirname as dirname2, join as join4 } from "path";
|
|
2351
|
-
var DEFAULT_MARKER_ID = "artifact-chain-assistant";
|
|
2352
|
-
var MANAGED_STATE_PREFIX = "# artifact-chain-assistant managed state v1:";
|
|
2353
|
-
var UnsupportedHookInterpreterError = class extends Error {
|
|
2354
|
-
constructor(hookPath, shebang) {
|
|
2355
|
-
super(
|
|
2356
|
-
`Refusing to modify non-shell Git hook ${hookPath} (${shebang || "missing shebang"}). Call the artifact-chain hook from the existing hook explicitly.`
|
|
2357
|
-
);
|
|
2358
|
-
this.hookPath = hookPath;
|
|
2359
|
-
this.shebang = shebang;
|
|
2360
|
-
}
|
|
2361
|
-
hookPath;
|
|
2362
|
-
shebang;
|
|
2363
|
-
code = "UNSUPPORTED_HOOK_INTERPRETER";
|
|
2364
|
-
};
|
|
2365
|
-
var SymlinkHookUnsupportedError = class extends Error {
|
|
2366
|
-
constructor(hookPath, linkTarget) {
|
|
2367
|
-
super(
|
|
2368
|
-
`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.`
|
|
2369
|
-
);
|
|
2370
|
-
this.hookPath = hookPath;
|
|
2371
|
-
this.linkTarget = linkTarget;
|
|
2372
|
-
}
|
|
2373
|
-
hookPath;
|
|
2374
|
-
linkTarget;
|
|
2375
|
-
code = "SYMLINK_HOOK_UNSUPPORTED";
|
|
2376
|
-
};
|
|
2377
|
-
var UnsupportedHookTypeError = class extends Error {
|
|
2378
|
-
constructor(hookPath) {
|
|
2379
|
-
super(`UNSUPPORTED_HOOK_TYPE: Git hook path ${hookPath} is not a regular file. Resolve it manually before retrying.`);
|
|
2380
|
-
this.hookPath = hookPath;
|
|
2381
|
-
}
|
|
2382
|
-
hookPath;
|
|
2383
|
-
code = "UNSUPPORTED_HOOK_TYPE";
|
|
2384
|
-
};
|
|
2385
|
-
var InvalidManagedHookStateError = class extends Error {
|
|
2386
|
-
constructor(hookPath, detail) {
|
|
2387
|
-
super(`INVALID_MANAGED_HOOK_STATE: Cannot safely update ${hookPath}: ${detail}`);
|
|
2388
|
-
this.hookPath = hookPath;
|
|
2389
|
-
}
|
|
2390
|
-
hookPath;
|
|
2391
|
-
code = "INVALID_MANAGED_HOOK_STATE";
|
|
2392
|
-
};
|
|
2393
|
-
var ConcurrentHookModificationError = class extends Error {
|
|
2394
|
-
constructor(hookPath) {
|
|
2395
|
-
super(`Refusing to overwrite concurrently modified Git hook ${hookPath}. Retry the operation after reviewing the hook.`);
|
|
2396
|
-
this.hookPath = hookPath;
|
|
2397
|
-
}
|
|
2398
|
-
hookPath;
|
|
2399
|
-
code = "CONCURRENT_HOOK_MODIFICATION";
|
|
2400
|
-
};
|
|
2401
|
-
var HookTransactionRollbackError = class extends Error {
|
|
2402
|
-
constructor(operationError, rollbackErrors) {
|
|
2403
|
-
super(
|
|
2404
|
-
`HOOK_TRANSACTION_ROLLBACK_FAILED: Hook update failed and ${rollbackErrors.length} rollback operation(s) also failed: ` + rollbackErrors.map(({ hookPath, error }) => `${hookPath}: ${error.message}`).join("; ")
|
|
2405
|
-
);
|
|
2406
|
-
this.operationError = operationError;
|
|
2407
|
-
this.rollbackErrors = rollbackErrors;
|
|
2408
|
-
}
|
|
2409
|
-
operationError;
|
|
2410
|
-
rollbackErrors;
|
|
2411
|
-
code = "HOOK_TRANSACTION_ROLLBACK_FAILED";
|
|
2412
|
-
};
|
|
2413
2435
|
function detectHookInterpreter(content) {
|
|
2414
2436
|
if (content.trim().length === 0) {
|
|
2415
2437
|
return "empty";
|
|
@@ -2763,44 +2785,76 @@ function findManagedRange(content, begin, end) {
|
|
|
2763
2785
|
end: endLine < 0 ? content.length : endLine + 1
|
|
2764
2786
|
};
|
|
2765
2787
|
}
|
|
2788
|
+
var DEFAULT_MARKER_ID, MANAGED_STATE_PREFIX, UnsupportedHookInterpreterError, SymlinkHookUnsupportedError, UnsupportedHookTypeError, InvalidManagedHookStateError, ConcurrentHookModificationError, HookTransactionRollbackError;
|
|
2789
|
+
var init_hook_installer = __esm({
|
|
2790
|
+
"src/hook-installer.ts"() {
|
|
2791
|
+
"use strict";
|
|
2792
|
+
DEFAULT_MARKER_ID = "artifact-chain-assistant";
|
|
2793
|
+
MANAGED_STATE_PREFIX = "# artifact-chain-assistant managed state v1:";
|
|
2794
|
+
UnsupportedHookInterpreterError = class extends Error {
|
|
2795
|
+
constructor(hookPath, shebang) {
|
|
2796
|
+
super(
|
|
2797
|
+
`Refusing to modify non-shell Git hook ${hookPath} (${shebang || "missing shebang"}). Call the artifact-chain hook from the existing hook explicitly.`
|
|
2798
|
+
);
|
|
2799
|
+
this.hookPath = hookPath;
|
|
2800
|
+
this.shebang = shebang;
|
|
2801
|
+
}
|
|
2802
|
+
hookPath;
|
|
2803
|
+
shebang;
|
|
2804
|
+
code = "UNSUPPORTED_HOOK_INTERPRETER";
|
|
2805
|
+
};
|
|
2806
|
+
SymlinkHookUnsupportedError = class extends Error {
|
|
2807
|
+
constructor(hookPath, linkTarget) {
|
|
2808
|
+
super(
|
|
2809
|
+
`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.`
|
|
2810
|
+
);
|
|
2811
|
+
this.hookPath = hookPath;
|
|
2812
|
+
this.linkTarget = linkTarget;
|
|
2813
|
+
}
|
|
2814
|
+
hookPath;
|
|
2815
|
+
linkTarget;
|
|
2816
|
+
code = "SYMLINK_HOOK_UNSUPPORTED";
|
|
2817
|
+
};
|
|
2818
|
+
UnsupportedHookTypeError = class extends Error {
|
|
2819
|
+
constructor(hookPath) {
|
|
2820
|
+
super(`UNSUPPORTED_HOOK_TYPE: Git hook path ${hookPath} is not a regular file. Resolve it manually before retrying.`);
|
|
2821
|
+
this.hookPath = hookPath;
|
|
2822
|
+
}
|
|
2823
|
+
hookPath;
|
|
2824
|
+
code = "UNSUPPORTED_HOOK_TYPE";
|
|
2825
|
+
};
|
|
2826
|
+
InvalidManagedHookStateError = class extends Error {
|
|
2827
|
+
constructor(hookPath, detail) {
|
|
2828
|
+
super(`INVALID_MANAGED_HOOK_STATE: Cannot safely update ${hookPath}: ${detail}`);
|
|
2829
|
+
this.hookPath = hookPath;
|
|
2830
|
+
}
|
|
2831
|
+
hookPath;
|
|
2832
|
+
code = "INVALID_MANAGED_HOOK_STATE";
|
|
2833
|
+
};
|
|
2834
|
+
ConcurrentHookModificationError = class extends Error {
|
|
2835
|
+
constructor(hookPath) {
|
|
2836
|
+
super(`Refusing to overwrite concurrently modified Git hook ${hookPath}. Retry the operation after reviewing the hook.`);
|
|
2837
|
+
this.hookPath = hookPath;
|
|
2838
|
+
}
|
|
2839
|
+
hookPath;
|
|
2840
|
+
code = "CONCURRENT_HOOK_MODIFICATION";
|
|
2841
|
+
};
|
|
2842
|
+
HookTransactionRollbackError = class extends Error {
|
|
2843
|
+
constructor(operationError, rollbackErrors) {
|
|
2844
|
+
super(
|
|
2845
|
+
`HOOK_TRANSACTION_ROLLBACK_FAILED: Hook update failed and ${rollbackErrors.length} rollback operation(s) also failed: ` + rollbackErrors.map(({ hookPath, error }) => `${hookPath}: ${error.message}`).join("; ")
|
|
2846
|
+
);
|
|
2847
|
+
this.operationError = operationError;
|
|
2848
|
+
this.rollbackErrors = rollbackErrors;
|
|
2849
|
+
}
|
|
2850
|
+
operationError;
|
|
2851
|
+
rollbackErrors;
|
|
2852
|
+
code = "HOOK_TRANSACTION_ROLLBACK_FAILED";
|
|
2853
|
+
};
|
|
2854
|
+
}
|
|
2855
|
+
});
|
|
2766
2856
|
|
|
2767
2857
|
// src/review-result-validator.ts
|
|
2768
|
-
var VALID_STATUSES = /* @__PURE__ */ new Set([
|
|
2769
|
-
"SUCCEEDED",
|
|
2770
|
-
"FAILED",
|
|
2771
|
-
"BLOCKED",
|
|
2772
|
-
"NEEDS_INPUT",
|
|
2773
|
-
"SKIPPED"
|
|
2774
|
-
]);
|
|
2775
|
-
var VALID_DECISIONS = /* @__PURE__ */ new Set([
|
|
2776
|
-
"PASS",
|
|
2777
|
-
"FAIL",
|
|
2778
|
-
"PASS_WITH_RESIDUAL_MINOR",
|
|
2779
|
-
"BLOCKED",
|
|
2780
|
-
"NEEDS_INPUT",
|
|
2781
|
-
"NOT_APPLICABLE"
|
|
2782
|
-
]);
|
|
2783
|
-
var VALID_SEVERITIES = /* @__PURE__ */ new Set(["block", "warn", "info"]);
|
|
2784
|
-
var VALID_FINDING_STATUSES = /* @__PURE__ */ new Set(["open", "resolved", "accepted", "superseded"]);
|
|
2785
|
-
var VALID_EXECUTORS = /* @__PURE__ */ new Set(["script", "worker", "agent", "manual", "cli"]);
|
|
2786
|
-
var TOP_LEVEL_FIELDS = /* @__PURE__ */ new Set([
|
|
2787
|
-
"schema_version",
|
|
2788
|
-
"run_id",
|
|
2789
|
-
"stage_id",
|
|
2790
|
-
"attempt",
|
|
2791
|
-
"status",
|
|
2792
|
-
"decision",
|
|
2793
|
-
"summary",
|
|
2794
|
-
"outputs",
|
|
2795
|
-
"warnings",
|
|
2796
|
-
"blocking_reason",
|
|
2797
|
-
"degradation",
|
|
2798
|
-
"producer",
|
|
2799
|
-
"acceptance",
|
|
2800
|
-
"evidence",
|
|
2801
|
-
"review",
|
|
2802
|
-
"repair"
|
|
2803
|
-
]);
|
|
2804
2858
|
function validateReviewResult(input) {
|
|
2805
2859
|
const errors = [];
|
|
2806
2860
|
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
@@ -3049,19 +3103,6 @@ function validateBatches(val, path, errors) {
|
|
|
3049
3103
|
checkOptionalInteger(b.chars, `${itemPath}.chars`, errors);
|
|
3050
3104
|
}
|
|
3051
3105
|
}
|
|
3052
|
-
var METRIC_FIELDS = [
|
|
3053
|
-
"files_reviewed",
|
|
3054
|
-
"files_scanned",
|
|
3055
|
-
"findings_count",
|
|
3056
|
-
"deterministic_findings_count",
|
|
3057
|
-
"semantic_findings_count",
|
|
3058
|
-
"block_count",
|
|
3059
|
-
"warn_count",
|
|
3060
|
-
"info_count",
|
|
3061
|
-
"resolved_count",
|
|
3062
|
-
"batch_count",
|
|
3063
|
-
"scenario_count"
|
|
3064
|
-
];
|
|
3065
3106
|
function validateMetrics(val, path, errors) {
|
|
3066
3107
|
if (!isPlainObject(val)) {
|
|
3067
3108
|
errors.push({ path, message: "Must be an object" });
|
|
@@ -3078,10 +3119,999 @@ function validateRepairValidation(val, path, errors) {
|
|
|
3078
3119
|
checkOptionalInteger(val.exit_code, `${path}.exit_code`, errors);
|
|
3079
3120
|
checkOptionalInteger(val.findings_remaining, `${path}.findings_remaining`, errors, 0);
|
|
3080
3121
|
}
|
|
3122
|
+
var VALID_STATUSES, VALID_DECISIONS, VALID_SEVERITIES, VALID_FINDING_STATUSES, VALID_EXECUTORS, TOP_LEVEL_FIELDS, METRIC_FIELDS;
|
|
3123
|
+
var init_review_result_validator = __esm({
|
|
3124
|
+
"src/review-result-validator.ts"() {
|
|
3125
|
+
"use strict";
|
|
3126
|
+
VALID_STATUSES = /* @__PURE__ */ new Set([
|
|
3127
|
+
"SUCCEEDED",
|
|
3128
|
+
"FAILED",
|
|
3129
|
+
"BLOCKED",
|
|
3130
|
+
"NEEDS_INPUT",
|
|
3131
|
+
"SKIPPED"
|
|
3132
|
+
]);
|
|
3133
|
+
VALID_DECISIONS = /* @__PURE__ */ new Set([
|
|
3134
|
+
"PASS",
|
|
3135
|
+
"FAIL",
|
|
3136
|
+
"PASS_WITH_RESIDUAL_MINOR",
|
|
3137
|
+
"BLOCKED",
|
|
3138
|
+
"NEEDS_INPUT",
|
|
3139
|
+
"NOT_APPLICABLE"
|
|
3140
|
+
]);
|
|
3141
|
+
VALID_SEVERITIES = /* @__PURE__ */ new Set(["block", "warn", "info"]);
|
|
3142
|
+
VALID_FINDING_STATUSES = /* @__PURE__ */ new Set(["open", "resolved", "accepted", "superseded"]);
|
|
3143
|
+
VALID_EXECUTORS = /* @__PURE__ */ new Set(["script", "worker", "agent", "manual", "cli"]);
|
|
3144
|
+
TOP_LEVEL_FIELDS = /* @__PURE__ */ new Set([
|
|
3145
|
+
"schema_version",
|
|
3146
|
+
"run_id",
|
|
3147
|
+
"stage_id",
|
|
3148
|
+
"attempt",
|
|
3149
|
+
"status",
|
|
3150
|
+
"decision",
|
|
3151
|
+
"summary",
|
|
3152
|
+
"outputs",
|
|
3153
|
+
"warnings",
|
|
3154
|
+
"blocking_reason",
|
|
3155
|
+
"degradation",
|
|
3156
|
+
"producer",
|
|
3157
|
+
"acceptance",
|
|
3158
|
+
"evidence",
|
|
3159
|
+
"review",
|
|
3160
|
+
"repair"
|
|
3161
|
+
]);
|
|
3162
|
+
METRIC_FIELDS = [
|
|
3163
|
+
"files_reviewed",
|
|
3164
|
+
"files_scanned",
|
|
3165
|
+
"findings_count",
|
|
3166
|
+
"deterministic_findings_count",
|
|
3167
|
+
"semantic_findings_count",
|
|
3168
|
+
"block_count",
|
|
3169
|
+
"warn_count",
|
|
3170
|
+
"info_count",
|
|
3171
|
+
"resolved_count",
|
|
3172
|
+
"batch_count",
|
|
3173
|
+
"scenario_count"
|
|
3174
|
+
];
|
|
3175
|
+
}
|
|
3176
|
+
});
|
|
3177
|
+
|
|
3178
|
+
// src/contract-kernel.ts
|
|
3179
|
+
var contract_kernel_exports = {};
|
|
3180
|
+
__export(contract_kernel_exports, {
|
|
3181
|
+
CONTRACT_ERROR_CODES: () => CONTRACT_ERROR_CODES,
|
|
3182
|
+
ContractCatalog: () => ContractCatalog,
|
|
3183
|
+
ContractError: () => ContractError,
|
|
3184
|
+
ContractRegistry: () => ContractRegistry,
|
|
3185
|
+
E2E_NORMALIZER_CONFIG: () => E2E_NORMALIZER_CONFIG,
|
|
3186
|
+
__AjvCtor: () => __AjvCtor,
|
|
3187
|
+
applyProjectPolicy: () => applyProjectPolicy,
|
|
3188
|
+
canonicalizeForDigest: () => canonicalizeForDigest,
|
|
3189
|
+
computeRevisionDigest: () => computeRevisionDigest,
|
|
3190
|
+
failClosedValidation: () => failClosedValidation,
|
|
3191
|
+
isOfficialNamespace: () => isOfficialNamespace,
|
|
3192
|
+
loadContract: () => loadContract,
|
|
3193
|
+
loadContractCatalog: () => loadContractCatalog,
|
|
3194
|
+
loadContractsFromDirectory: () => loadContractsFromDirectory,
|
|
3195
|
+
normalizeE2eLegacyArtifact: () => normalizeE2eLegacyArtifact,
|
|
3196
|
+
normalizeToCanonical: () => normalizeToCanonical,
|
|
3197
|
+
runContractCli: () => runContractCli,
|
|
3198
|
+
setAjvConstructor: () => setAjvConstructor,
|
|
3199
|
+
validateContractAgainstSchema: () => validateContractAgainstSchema,
|
|
3200
|
+
validateNamespaceAuthority: () => validateNamespaceAuthority,
|
|
3201
|
+
validatePolicyCompatibility: () => validatePolicyCompatibility,
|
|
3202
|
+
validateRelations: () => validateRelations,
|
|
3203
|
+
validateSemanticMarkers: () => validateSemanticMarkers,
|
|
3204
|
+
verifyDigest: () => verifyDigest
|
|
3205
|
+
});
|
|
3206
|
+
import { createHash as createHash2 } from "crypto";
|
|
3207
|
+
import { readFile as readFile2, readdir } from "fs/promises";
|
|
3208
|
+
import { join as join5 } from "path";
|
|
3209
|
+
import _AjvModule from "ajv";
|
|
3210
|
+
function isOfficialNamespace(namespace) {
|
|
3211
|
+
return OFFICIAL_NAMESPACE_PATTERN.test(namespace);
|
|
3212
|
+
}
|
|
3213
|
+
function validateNamespaceAuthority(identity, expectedAuthority) {
|
|
3214
|
+
if (isOfficialNamespace(identity.namespace) && identity.authority !== OFFICIAL_AUTHORITY) {
|
|
3215
|
+
throw new ContractError(
|
|
3216
|
+
CONTRACT_ERROR_CODES.AUTHORITY_VIOLATION,
|
|
3217
|
+
`Namespace ${identity.namespace} requires authority "${OFFICIAL_AUTHORITY}", got "${identity.authority}"`,
|
|
3218
|
+
{ identity, expectedAuthority: OFFICIAL_AUTHORITY }
|
|
3219
|
+
);
|
|
3220
|
+
}
|
|
3221
|
+
if (expectedAuthority && identity.authority !== expectedAuthority) {
|
|
3222
|
+
throw new ContractError(
|
|
3223
|
+
CONTRACT_ERROR_CODES.AUTHORITY_VIOLATION,
|
|
3224
|
+
`Expected authority "${expectedAuthority}", got "${identity.authority}"`,
|
|
3225
|
+
{ identity, expectedAuthority }
|
|
3226
|
+
);
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
function canonicalizeForDigest(rawContent) {
|
|
3230
|
+
try {
|
|
3231
|
+
const parsed = JSON.parse(rawContent);
|
|
3232
|
+
if (parsed.contractIdentity && typeof parsed.contractIdentity === "object") {
|
|
3233
|
+
const ci = { ...parsed.contractIdentity };
|
|
3234
|
+
delete ci.revisionDigest;
|
|
3235
|
+
parsed.contractIdentity = ci;
|
|
3236
|
+
}
|
|
3237
|
+
return JSON.stringify(parsed);
|
|
3238
|
+
} catch {
|
|
3239
|
+
return rawContent;
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
function computeRevisionDigest(content) {
|
|
3243
|
+
const canonical = canonicalizeForDigest(content);
|
|
3244
|
+
const hash = createHash2("sha256").update(canonical, "utf-8").digest("hex");
|
|
3245
|
+
return `sha256:${hash}`;
|
|
3246
|
+
}
|
|
3247
|
+
function verifyDigest(content, expectedDigest) {
|
|
3248
|
+
const actualDigest = computeRevisionDigest(content);
|
|
3249
|
+
return actualDigest === expectedDigest;
|
|
3250
|
+
}
|
|
3251
|
+
function setNestedValue(obj, path, value) {
|
|
3252
|
+
const parts = path.split(".");
|
|
3253
|
+
let current = obj;
|
|
3254
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
3255
|
+
const part = parts[i];
|
|
3256
|
+
if (current[part] === void 0 || typeof current[part] !== "object" || current[part] === null) {
|
|
3257
|
+
current[part] = {};
|
|
3258
|
+
}
|
|
3259
|
+
current = current[part];
|
|
3260
|
+
}
|
|
3261
|
+
current[parts[parts.length - 1]] = value;
|
|
3262
|
+
}
|
|
3263
|
+
function matchesSchemaType(value, definition) {
|
|
3264
|
+
const type = definition?.type;
|
|
3265
|
+
if (type === "array") return Array.isArray(value);
|
|
3266
|
+
if (type === "object") return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3267
|
+
if (type === "string") return typeof value === "string";
|
|
3268
|
+
if (type === "number" || type === "integer") return typeof value === "number";
|
|
3269
|
+
if (type === "boolean") return typeof value === "boolean";
|
|
3270
|
+
return value !== void 0;
|
|
3271
|
+
}
|
|
3272
|
+
function getNestedValue(source, path) {
|
|
3273
|
+
let current = source;
|
|
3274
|
+
for (const part of path.split(".")) {
|
|
3275
|
+
if (typeof current !== "object" || current === null || Array.isArray(current)) return void 0;
|
|
3276
|
+
current = current[part];
|
|
3277
|
+
}
|
|
3278
|
+
return current;
|
|
3279
|
+
}
|
|
3280
|
+
function valuesEqual(left, right) {
|
|
3281
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
3282
|
+
}
|
|
3283
|
+
function normalizeToCanonical(legacyData, config, contract) {
|
|
3284
|
+
const errors = [];
|
|
3285
|
+
const warnings = [];
|
|
3286
|
+
const legacyFieldMap = /* @__PURE__ */ new Map();
|
|
3287
|
+
for (const mapping of config.fieldMappings) {
|
|
3288
|
+
legacyFieldMap.set(mapping.legacy, mapping.canonical);
|
|
3289
|
+
}
|
|
3290
|
+
const canonical = {};
|
|
3291
|
+
const canonicalRoots = new Set(Object.keys(contract.schema.properties));
|
|
3292
|
+
const presentCanonicalRoots = /* @__PURE__ */ new Set();
|
|
3293
|
+
for (const root of canonicalRoots) {
|
|
3294
|
+
const value = legacyData[root];
|
|
3295
|
+
if (value !== void 0 && matchesSchemaType(value, contract.schema.properties[root])) {
|
|
3296
|
+
canonical[root] = structuredClone(value);
|
|
3297
|
+
presentCanonicalRoots.add(root);
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
const unmappedLegacyFields = new Set(
|
|
3301
|
+
Object.keys(legacyData).filter((field) => !presentCanonicalRoots.has(field) && !legacyFieldMap.has(field))
|
|
3302
|
+
);
|
|
3303
|
+
let usedLegacyExpression = false;
|
|
3304
|
+
for (const mapping of config.fieldMappings) {
|
|
3305
|
+
const legacyValue = legacyData[mapping.legacy];
|
|
3306
|
+
const existingCanonicalValue = getNestedValue(canonical, mapping.canonical);
|
|
3307
|
+
if (legacyValue !== void 0 && !presentCanonicalRoots.has(mapping.legacy)) {
|
|
3308
|
+
usedLegacyExpression = true;
|
|
3309
|
+
const canonicalValue = mapping.transform ? mapping.transform(legacyValue) : legacyValue;
|
|
3310
|
+
if (existingCanonicalValue !== void 0) {
|
|
3311
|
+
if (!valuesEqual(existingCanonicalValue, canonicalValue)) {
|
|
3312
|
+
errors.push({
|
|
3313
|
+
code: CONTRACT_ERROR_CODES.CANONICAL_LEGACY_CONFLICT,
|
|
3314
|
+
path: `/${mapping.canonical.replaceAll(".", "/")}`,
|
|
3315
|
+
message: `Canonical field "${mapping.canonical}" conflicts with legacy field "${mapping.legacy}"`
|
|
3316
|
+
});
|
|
3317
|
+
}
|
|
3318
|
+
} else {
|
|
3319
|
+
setNestedValue(canonical, mapping.canonical, canonicalValue);
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
if (mapping.required && getNestedValue(canonical, mapping.canonical) === void 0) {
|
|
3323
|
+
errors.push({
|
|
3324
|
+
code: "MISSING_REQUIRED_FIELD",
|
|
3325
|
+
path: `/${mapping.canonical.replaceAll(".", "/")}`,
|
|
3326
|
+
message: `Required canonical field "${mapping.canonical}" missing (legacy: "${mapping.legacy}")`
|
|
3327
|
+
});
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
3330
|
+
for (const field of unmappedLegacyFields) {
|
|
3331
|
+
errors.push({
|
|
3332
|
+
code: "LOSSY_NORMALIZATION",
|
|
3333
|
+
path: field,
|
|
3334
|
+
message: `Legacy field "${field}" not mapped to canonical form \u2014 would be lost`
|
|
3335
|
+
});
|
|
3336
|
+
}
|
|
3337
|
+
if (config.validate) {
|
|
3338
|
+
const validationErrors = config.validate(canonical);
|
|
3339
|
+
for (const error of validationErrors) {
|
|
3340
|
+
errors.push({
|
|
3341
|
+
code: "VALIDATION_FAILED",
|
|
3342
|
+
path: "",
|
|
3343
|
+
message: error
|
|
3344
|
+
});
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
if (errors.length > 0) {
|
|
3348
|
+
return { success: false, errors, warnings };
|
|
3349
|
+
}
|
|
3350
|
+
const schemaResult = validateContractAgainstSchema(canonical, contract);
|
|
3351
|
+
if (!schemaResult.valid) {
|
|
3352
|
+
return {
|
|
3353
|
+
success: false,
|
|
3354
|
+
errors: schemaResult.errors.map((e) => ({
|
|
3355
|
+
code: "SCHEMA_VALIDATION_FAILED",
|
|
3356
|
+
path: e.split(":")[0]?.trim() ?? "",
|
|
3357
|
+
message: e
|
|
3358
|
+
})),
|
|
3359
|
+
warnings
|
|
3360
|
+
};
|
|
3361
|
+
}
|
|
3362
|
+
if (Array.isArray(canonical.relations)) {
|
|
3363
|
+
const relationResult = validateRelations(canonical.relations, contract);
|
|
3364
|
+
if (!relationResult.valid) {
|
|
3365
|
+
return {
|
|
3366
|
+
success: false,
|
|
3367
|
+
errors: relationResult.issues.map((i) => ({
|
|
3368
|
+
code: i.code,
|
|
3369
|
+
path: i.path,
|
|
3370
|
+
message: i.message
|
|
3371
|
+
})),
|
|
3372
|
+
warnings
|
|
3373
|
+
};
|
|
3374
|
+
}
|
|
3375
|
+
}
|
|
3376
|
+
const sourceRevision = usedLegacyExpression ? "legacy" : "canonical";
|
|
3377
|
+
return {
|
|
3378
|
+
success: true,
|
|
3379
|
+
ir: {
|
|
3380
|
+
type: contract.identity.major.split(".")[1]?.split("@")[0] ?? "unknown",
|
|
3381
|
+
id: String(canonical.metadata?.id ?? canonical["id"] ?? ""),
|
|
3382
|
+
contractMajor: contract.identity.major,
|
|
3383
|
+
contractDigest: contract.identity.revisionDigest,
|
|
3384
|
+
canonical,
|
|
3385
|
+
sourceRevision,
|
|
3386
|
+
warnings
|
|
3387
|
+
},
|
|
3388
|
+
errors: [],
|
|
3389
|
+
warnings
|
|
3390
|
+
};
|
|
3391
|
+
}
|
|
3392
|
+
function resolvePropertyPath(schema, fieldPath) {
|
|
3393
|
+
const parts = fieldPath.split(".");
|
|
3394
|
+
let current = schema.properties;
|
|
3395
|
+
for (const part of parts) {
|
|
3396
|
+
if (!current || typeof current !== "object") return void 0;
|
|
3397
|
+
const propDef = current[part];
|
|
3398
|
+
if (!propDef) return void 0;
|
|
3399
|
+
if (parts.indexOf(part) < parts.length - 1) {
|
|
3400
|
+
current = propDef.properties;
|
|
3401
|
+
} else {
|
|
3402
|
+
return propDef;
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
return void 0;
|
|
3406
|
+
}
|
|
3407
|
+
function validatePolicyCompatibility(policy, baseContract) {
|
|
3408
|
+
const errors = [];
|
|
3409
|
+
const warnings = [];
|
|
3410
|
+
if (policy.baseContractMajor !== baseContract.identity.major) {
|
|
3411
|
+
errors.push(
|
|
3412
|
+
`POLICY_INCOMPATIBLE: Policy references ${policy.baseContractMajor}, but base contract is ${baseContract.identity.major}`
|
|
3413
|
+
);
|
|
3414
|
+
return { compatible: false, errors, warnings };
|
|
3415
|
+
}
|
|
3416
|
+
const schema = baseContract.schema;
|
|
3417
|
+
if (policy.additionalRequired) {
|
|
3418
|
+
for (const field of policy.additionalRequired) {
|
|
3419
|
+
const propDef = resolvePropertyPath(schema, field);
|
|
3420
|
+
if (!propDef) {
|
|
3421
|
+
errors.push(`POLICY_INCOMPATIBLE: Policy requires field "${field}" which does not exist in base contract`);
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
if (policy.restrictedEnums) {
|
|
3426
|
+
for (const [field, values] of Object.entries(policy.restrictedEnums)) {
|
|
3427
|
+
const baseProperty = resolvePropertyPath(schema, field);
|
|
3428
|
+
if (!baseProperty) {
|
|
3429
|
+
errors.push(`POLICY_INCOMPATIBLE: Policy restricts enum for field "${field}" which does not exist in base contract`);
|
|
3430
|
+
continue;
|
|
3431
|
+
}
|
|
3432
|
+
if (!Array.isArray(baseProperty.enum)) {
|
|
3433
|
+
errors.push(`POLICY_INCOMPATIBLE: Field "${field}" is not an enum in base contract \u2014 cannot restrict`);
|
|
3434
|
+
continue;
|
|
3435
|
+
}
|
|
3436
|
+
for (const value of values) {
|
|
3437
|
+
if (!baseProperty.enum.includes(value)) {
|
|
3438
|
+
errors.push(`POLICY_INCOMPATIBLE: Policy enum value "${String(value)}" for field "${field}" not in base contract enum [${baseProperty.enum.join(", ")}]`);
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
if (policy.minCardinality) {
|
|
3444
|
+
for (const [field, min] of Object.entries(policy.minCardinality)) {
|
|
3445
|
+
if (!Number.isFinite(min) || min < 0 || !Number.isInteger(min)) {
|
|
3446
|
+
errors.push(`POLICY_INCOMPATIBLE: Policy min cardinality for "${field}" must be a non-negative integer, got ${min}`);
|
|
3447
|
+
continue;
|
|
3448
|
+
}
|
|
3449
|
+
const baseProperty = resolvePropertyPath(schema, field);
|
|
3450
|
+
if (!baseProperty) {
|
|
3451
|
+
errors.push(`POLICY_INCOMPATIBLE: Policy sets min cardinality for field "${field}" which does not exist in base contract`);
|
|
3452
|
+
continue;
|
|
3453
|
+
}
|
|
3454
|
+
const baseMin = baseProperty.type === "array" ? baseProperty.minItems : baseProperty.minimum;
|
|
3455
|
+
if (baseMin !== void 0 && min < baseMin) {
|
|
3456
|
+
errors.push(`POLICY_INCOMPATIBLE: Policy min cardinality ${min} for field "${field}" loosens base ${baseProperty.type === "array" ? "minItems" : "minimum"}=${baseMin}`);
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
if (policy.maxCardinality) {
|
|
3461
|
+
for (const [field, max] of Object.entries(policy.maxCardinality)) {
|
|
3462
|
+
if (!Number.isFinite(max) || max < 0 || !Number.isInteger(max)) {
|
|
3463
|
+
errors.push(`POLICY_INCOMPATIBLE: Policy max cardinality for "${field}" must be a non-negative integer, got ${max}`);
|
|
3464
|
+
continue;
|
|
3465
|
+
}
|
|
3466
|
+
const baseProperty = resolvePropertyPath(schema, field);
|
|
3467
|
+
if (!baseProperty) {
|
|
3468
|
+
errors.push(`POLICY_INCOMPATIBLE: Policy sets max cardinality for field "${field}" which does not exist in base contract`);
|
|
3469
|
+
continue;
|
|
3470
|
+
}
|
|
3471
|
+
const baseMax = baseProperty.type === "array" ? baseProperty.maxItems : baseProperty.maximum;
|
|
3472
|
+
if (baseMax !== void 0 && max > baseMax) {
|
|
3473
|
+
errors.push(`POLICY_INCOMPATIBLE: Policy max cardinality ${max} for field "${field}" loosens base ${baseProperty.type === "array" ? "maxItems" : "maximum"}=${baseMax}`);
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
if (policy.constraints && Object.keys(policy.constraints).length > 0) {
|
|
3478
|
+
for (const key of Object.keys(policy.constraints)) {
|
|
3479
|
+
errors.push(`POLICY_INCOMPATIBLE: Constraint "${key}" is not implemented \u2014 rejected as unvalidated escape hatch`);
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
return {
|
|
3483
|
+
compatible: errors.length === 0,
|
|
3484
|
+
errors,
|
|
3485
|
+
warnings
|
|
3486
|
+
};
|
|
3487
|
+
}
|
|
3488
|
+
function applyProjectPolicy(policy, baseContract) {
|
|
3489
|
+
const compatibility = validatePolicyCompatibility(policy, baseContract);
|
|
3490
|
+
if (!compatibility.compatible) {
|
|
3491
|
+
return {
|
|
3492
|
+
success: false,
|
|
3493
|
+
errors: compatibility.errors,
|
|
3494
|
+
warnings: compatibility.warnings
|
|
3495
|
+
};
|
|
3496
|
+
}
|
|
3497
|
+
const effectiveSchema = JSON.parse(JSON.stringify(baseContract.schema));
|
|
3498
|
+
if (policy.additionalRequired) {
|
|
3499
|
+
for (const fieldPath of policy.additionalRequired) {
|
|
3500
|
+
const parts = fieldPath.split(".");
|
|
3501
|
+
let parentSchema = effectiveSchema;
|
|
3502
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
3503
|
+
parentSchema = parentSchema.properties?.[parts[i]];
|
|
3504
|
+
}
|
|
3505
|
+
const requiredField = parts[parts.length - 1];
|
|
3506
|
+
if (!Array.isArray(parentSchema.required)) parentSchema.required = [];
|
|
3507
|
+
if (!parentSchema.required.includes(requiredField)) {
|
|
3508
|
+
parentSchema.required.push(requiredField);
|
|
3509
|
+
}
|
|
3510
|
+
}
|
|
3511
|
+
}
|
|
3512
|
+
if (policy.restrictedEnums) {
|
|
3513
|
+
for (const [fieldPath, values] of Object.entries(policy.restrictedEnums)) {
|
|
3514
|
+
const propDef = resolvePropertyPath(effectiveSchema, fieldPath);
|
|
3515
|
+
if (propDef && Array.isArray(propDef.enum)) {
|
|
3516
|
+
propDef.enum = propDef.enum.filter((v) => values.includes(v));
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
if (policy.minCardinality) {
|
|
3521
|
+
for (const [fieldPath, min] of Object.entries(policy.minCardinality)) {
|
|
3522
|
+
const propDef = resolvePropertyPath(effectiveSchema, fieldPath);
|
|
3523
|
+
if (propDef) {
|
|
3524
|
+
if (propDef.type === "array") {
|
|
3525
|
+
propDef.minItems = min;
|
|
3526
|
+
} else {
|
|
3527
|
+
propDef.minimum = min;
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
if (policy.maxCardinality) {
|
|
3533
|
+
for (const [fieldPath, max] of Object.entries(policy.maxCardinality)) {
|
|
3534
|
+
const propDef = resolvePropertyPath(effectiveSchema, fieldPath);
|
|
3535
|
+
if (propDef) {
|
|
3536
|
+
if (propDef.type === "array") {
|
|
3537
|
+
propDef.maxItems = max;
|
|
3538
|
+
} else {
|
|
3539
|
+
propDef.maximum = max;
|
|
3540
|
+
}
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
effectiveSchema.contractIdentity = {
|
|
3545
|
+
...baseContract.identity,
|
|
3546
|
+
revisionDigest: ""
|
|
3547
|
+
// Placeholder — will be computed below
|
|
3548
|
+
};
|
|
3549
|
+
const effectiveDigest = computeRevisionDigest(JSON.stringify(effectiveSchema));
|
|
3550
|
+
effectiveSchema.contractIdentity.revisionDigest = effectiveDigest;
|
|
3551
|
+
const effectiveIdentity = {
|
|
3552
|
+
...baseContract.identity,
|
|
3553
|
+
revisionDigest: effectiveDigest
|
|
3554
|
+
};
|
|
3555
|
+
effectiveSchema.contractIdentity = effectiveIdentity;
|
|
3556
|
+
const effectiveRawContent = JSON.stringify(effectiveSchema);
|
|
3557
|
+
const effectiveContract = {
|
|
3558
|
+
identity: effectiveIdentity,
|
|
3559
|
+
schema: effectiveSchema,
|
|
3560
|
+
rawContent: effectiveRawContent
|
|
3561
|
+
};
|
|
3562
|
+
return {
|
|
3563
|
+
success: true,
|
|
3564
|
+
effectiveContract,
|
|
3565
|
+
errors: [],
|
|
3566
|
+
warnings: compatibility.warnings
|
|
3567
|
+
};
|
|
3568
|
+
}
|
|
3569
|
+
async function loadContract(contractPath, options) {
|
|
3570
|
+
const rawContent = await readFile2(contractPath, "utf-8");
|
|
3571
|
+
let schema;
|
|
3572
|
+
try {
|
|
3573
|
+
schema = JSON.parse(rawContent);
|
|
3574
|
+
} catch (error) {
|
|
3575
|
+
throw new ContractError(
|
|
3576
|
+
CONTRACT_ERROR_CODES.INVALID_IDENTITY,
|
|
3577
|
+
`Failed to parse contract JSON: ${error.message}`,
|
|
3578
|
+
{ path: contractPath }
|
|
3579
|
+
);
|
|
3580
|
+
}
|
|
3581
|
+
if (!schema.contractIdentity) {
|
|
3582
|
+
throw new ContractError(
|
|
3583
|
+
CONTRACT_ERROR_CODES.INVALID_IDENTITY,
|
|
3584
|
+
"Contract missing contractIdentity",
|
|
3585
|
+
{ path: contractPath }
|
|
3586
|
+
);
|
|
3587
|
+
}
|
|
3588
|
+
const identity = schema.contractIdentity;
|
|
3589
|
+
if (!identity.major || !identity.authority || !identity.namespace) {
|
|
3590
|
+
throw new ContractError(
|
|
3591
|
+
CONTRACT_ERROR_CODES.INVALID_IDENTITY,
|
|
3592
|
+
"Contract identity missing required fields (major, authority, namespace)",
|
|
3593
|
+
{ identity }
|
|
3594
|
+
);
|
|
3595
|
+
}
|
|
3596
|
+
if (options?.expectedAuthority) {
|
|
3597
|
+
validateNamespaceAuthority(identity, options.expectedAuthority);
|
|
3598
|
+
} else {
|
|
3599
|
+
validateNamespaceAuthority(identity);
|
|
3600
|
+
}
|
|
3601
|
+
const computedDigest = computeRevisionDigest(rawContent);
|
|
3602
|
+
if (identity.revisionDigest && identity.revisionDigest !== computedDigest) {
|
|
3603
|
+
throw new ContractError(
|
|
3604
|
+
CONTRACT_ERROR_CODES.DIGEST_MISMATCH,
|
|
3605
|
+
`Contract digest mismatch: declared ${identity.revisionDigest}, computed ${computedDigest}`,
|
|
3606
|
+
{ identity, computedDigest }
|
|
3607
|
+
);
|
|
3608
|
+
}
|
|
3609
|
+
identity.revisionDigest = computedDigest;
|
|
3610
|
+
return {
|
|
3611
|
+
identity,
|
|
3612
|
+
schema,
|
|
3613
|
+
rawContent
|
|
3614
|
+
};
|
|
3615
|
+
}
|
|
3616
|
+
async function loadContractsFromDirectory(contractsDir, options) {
|
|
3617
|
+
const contracts = [];
|
|
3618
|
+
const entries = await readdir(contractsDir, { withFileTypes: true });
|
|
3619
|
+
for (const entry of entries) {
|
|
3620
|
+
if (entry.isDirectory()) {
|
|
3621
|
+
const schemaPath = join5(contractsDir, entry.name, "schema.json");
|
|
3622
|
+
const contract = await loadContract(schemaPath, options);
|
|
3623
|
+
contracts.push(contract);
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
return contracts;
|
|
3627
|
+
}
|
|
3628
|
+
function getValidator(schema) {
|
|
3629
|
+
const key = `${schema.$id || "default"}#${computeRevisionDigest(JSON.stringify(schema))}`;
|
|
3630
|
+
if (!_validatorCache.has(key)) {
|
|
3631
|
+
const AjvCtor = _AjvModule;
|
|
3632
|
+
const ajv = new AjvCtor({ allErrors: true, strict: false });
|
|
3633
|
+
_validatorCache.set(key, ajv.compile(schema));
|
|
3634
|
+
}
|
|
3635
|
+
return _validatorCache.get(key);
|
|
3636
|
+
}
|
|
3637
|
+
function validateContractAgainstSchema(data, contract) {
|
|
3638
|
+
const validate = getValidator(contract.schema);
|
|
3639
|
+
const valid = validate(data);
|
|
3640
|
+
if (valid) {
|
|
3641
|
+
return { valid: true, errors: [] };
|
|
3642
|
+
}
|
|
3643
|
+
const errors = (validate.errors ?? []).map(
|
|
3644
|
+
(err) => `${err.instancePath || "/"}: ${err.message ?? "validation error"}`
|
|
3645
|
+
);
|
|
3646
|
+
return { valid: false, errors };
|
|
3647
|
+
}
|
|
3648
|
+
function validateRelations(relations, contract) {
|
|
3649
|
+
const issues = [];
|
|
3650
|
+
const relationRules = contract.identity.relationRules;
|
|
3651
|
+
if (!relationRules) {
|
|
3652
|
+
issues.push({
|
|
3653
|
+
code: "RELATION_RULES_MISSING",
|
|
3654
|
+
path: "/contractIdentity/relationRules",
|
|
3655
|
+
message: "Contract does not define relation rules \u2014 cannot validate relations"
|
|
3656
|
+
});
|
|
3657
|
+
return { valid: false, issues };
|
|
3658
|
+
}
|
|
3659
|
+
const relationsByKind = /* @__PURE__ */ new Map();
|
|
3660
|
+
for (const relation of relations) {
|
|
3661
|
+
const kind = relation.kind;
|
|
3662
|
+
if (!relationsByKind.has(kind)) {
|
|
3663
|
+
relationsByKind.set(kind, []);
|
|
3664
|
+
}
|
|
3665
|
+
relationsByKind.get(kind).push(relation);
|
|
3666
|
+
}
|
|
3667
|
+
for (let i = 0; i < relations.length; i++) {
|
|
3668
|
+
const relation = relations[i];
|
|
3669
|
+
const kind = relation.kind;
|
|
3670
|
+
const targetType = relation.target_type;
|
|
3671
|
+
const path = `/relations/${i}`;
|
|
3672
|
+
if (!relationRules[kind]) {
|
|
3673
|
+
issues.push({
|
|
3674
|
+
code: "RELATION_INVALID_KIND",
|
|
3675
|
+
path: `${path}/kind`,
|
|
3676
|
+
message: `Relation kind "${kind}" is not allowed by contract rules`
|
|
3677
|
+
});
|
|
3678
|
+
continue;
|
|
3679
|
+
}
|
|
3680
|
+
const rule = relationRules[kind];
|
|
3681
|
+
if (!rule.allowedTargetTypes.includes(targetType)) {
|
|
3682
|
+
issues.push({
|
|
3683
|
+
code: "RELATION_INVALID_TARGET_TYPE",
|
|
3684
|
+
path: `${path}/target_type`,
|
|
3685
|
+
message: `Target type "${targetType}" is not allowed for relation kind "${kind}". Allowed: ${rule.allowedTargetTypes.join(", ")}`
|
|
3686
|
+
});
|
|
3687
|
+
}
|
|
3688
|
+
const hasAnchor = relation.anchor !== void 0 && relation.anchor !== "";
|
|
3689
|
+
if (rule.anchorPolicy === "required" && !hasAnchor) {
|
|
3690
|
+
issues.push({
|
|
3691
|
+
code: "RELATION_MISSING_ANCHOR",
|
|
3692
|
+
path: `${path}/anchor`,
|
|
3693
|
+
message: `Relation kind "${kind}" requires an anchor`
|
|
3694
|
+
});
|
|
3695
|
+
} else if (rule.anchorPolicy === "forbidden" && hasAnchor) {
|
|
3696
|
+
issues.push({
|
|
3697
|
+
code: "RELATION_FORBIDDEN_ANCHOR",
|
|
3698
|
+
path: `${path}/anchor`,
|
|
3699
|
+
message: `Relation kind "${kind}" forbids anchors`
|
|
3700
|
+
});
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
for (const [kind, rule] of Object.entries(relationRules)) {
|
|
3704
|
+
const count = relationsByKind.get(kind)?.length ?? 0;
|
|
3705
|
+
if (count < rule.min) {
|
|
3706
|
+
issues.push({
|
|
3707
|
+
code: "RELATION_BELOW_MIN",
|
|
3708
|
+
path: "/relations",
|
|
3709
|
+
message: `Relation kind "${kind}" has ${count} instances, minimum is ${rule.min}`
|
|
3710
|
+
});
|
|
3711
|
+
}
|
|
3712
|
+
if (count > rule.max) {
|
|
3713
|
+
issues.push({
|
|
3714
|
+
code: "RELATION_ABOVE_MAX",
|
|
3715
|
+
path: "/relations",
|
|
3716
|
+
message: `Relation kind "${kind}" has ${count} instances, maximum is ${rule.max}`
|
|
3717
|
+
});
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3720
|
+
return {
|
|
3721
|
+
valid: issues.length === 0,
|
|
3722
|
+
issues
|
|
3723
|
+
};
|
|
3724
|
+
}
|
|
3725
|
+
function validateSemanticMarkers(markdownContent, contract) {
|
|
3726
|
+
const errors = [];
|
|
3727
|
+
const semanticMarkers = contract.identity.semanticMarkers;
|
|
3728
|
+
if (!semanticMarkers) {
|
|
3729
|
+
return { valid: true, errors: [] };
|
|
3730
|
+
}
|
|
3731
|
+
const markerRegex = /<!--\s*marker:\s*([a-zA-Z0-9_-]+)\s*-->/g;
|
|
3732
|
+
const foundMarkers = /* @__PURE__ */ new Map();
|
|
3733
|
+
let match;
|
|
3734
|
+
while ((match = markerRegex.exec(markdownContent)) !== null) {
|
|
3735
|
+
const markerName = match[1];
|
|
3736
|
+
foundMarkers.set(markerName, (foundMarkers.get(markerName) ?? 0) + 1);
|
|
3737
|
+
}
|
|
3738
|
+
for (const [markerName, markerDef] of Object.entries(semanticMarkers)) {
|
|
3739
|
+
if (markerDef.required && !foundMarkers.has(markerDef.markdownMarker)) {
|
|
3740
|
+
errors.push(`MARKER_MISSING: Required semantic marker "${markerDef.markdownMarker}" not found in Markdown content`);
|
|
3741
|
+
}
|
|
3742
|
+
}
|
|
3743
|
+
for (const [markerName, count] of foundMarkers.entries()) {
|
|
3744
|
+
if (count > 1) {
|
|
3745
|
+
errors.push(`MARKER_DUPLICATE: Semantic marker "${markerName}" appears ${count} times, must be unique`);
|
|
3746
|
+
}
|
|
3747
|
+
}
|
|
3748
|
+
const knownMarkers = new Set(Object.values(semanticMarkers).map((m) => m.markdownMarker));
|
|
3749
|
+
for (const markerName of foundMarkers.keys()) {
|
|
3750
|
+
if (!knownMarkers.has(markerName)) {
|
|
3751
|
+
errors.push(`MARKER_UNKNOWN: Unknown semantic marker "${markerName}" not defined in contract`);
|
|
3752
|
+
}
|
|
3753
|
+
}
|
|
3754
|
+
return {
|
|
3755
|
+
valid: errors.length === 0,
|
|
3756
|
+
errors
|
|
3757
|
+
};
|
|
3758
|
+
}
|
|
3759
|
+
function failClosedValidation() {
|
|
3760
|
+
return {
|
|
3761
|
+
valid: false,
|
|
3762
|
+
errors: ["/: Schema validator unavailable \u2014 fail-closed rejection"]
|
|
3763
|
+
};
|
|
3764
|
+
}
|
|
3765
|
+
function setAjvConstructor(_ctor) {
|
|
3766
|
+
}
|
|
3767
|
+
function normalizeE2eLegacyArtifact(legacyData, contract) {
|
|
3768
|
+
return normalizeToCanonical(legacyData, E2E_NORMALIZER_CONFIG, contract);
|
|
3769
|
+
}
|
|
3770
|
+
async function loadContractCatalog(contractsDir, options) {
|
|
3771
|
+
const catalog = new ContractCatalog();
|
|
3772
|
+
const contracts = await loadContractsFromDirectory(contractsDir, options);
|
|
3773
|
+
for (const contract of contracts) {
|
|
3774
|
+
catalog.add(contract);
|
|
3775
|
+
}
|
|
3776
|
+
return catalog;
|
|
3777
|
+
}
|
|
3778
|
+
async function runContractCli(args, io) {
|
|
3779
|
+
const { runCli: runCli2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
|
|
3780
|
+
return runCli2(args, io);
|
|
3781
|
+
}
|
|
3782
|
+
var CONTRACT_ERROR_CODES, ContractError, OFFICIAL_AUTHORITY, OFFICIAL_NAMESPACE_PATTERN, ContractRegistry, _validatorCache, __AjvCtor, E2E_NORMALIZER_CONFIG, ContractCatalog;
|
|
3783
|
+
var init_contract_kernel = __esm({
|
|
3784
|
+
"src/contract-kernel.ts"() {
|
|
3785
|
+
"use strict";
|
|
3786
|
+
CONTRACT_ERROR_CODES = {
|
|
3787
|
+
/** Contract identity not found */
|
|
3788
|
+
CONTRACT_NOT_FOUND: "CONTRACT_NOT_FOUND",
|
|
3789
|
+
/** Invalid contract identity format */
|
|
3790
|
+
INVALID_IDENTITY: "INVALID_IDENTITY",
|
|
3791
|
+
/** Revision digest mismatch */
|
|
3792
|
+
DIGEST_MISMATCH: "DIGEST_MISMATCH",
|
|
3793
|
+
/** Duplicate contract identity */
|
|
3794
|
+
DUPLICATE_IDENTITY: "DUPLICATE_IDENTITY",
|
|
3795
|
+
/** Multiple active write contracts for same type */
|
|
3796
|
+
MULTIPLE_ACTIVE_WRITE: "MULTIPLE_ACTIVE_WRITE",
|
|
3797
|
+
/** Unknown authority namespace */
|
|
3798
|
+
UNKNOWN_AUTHORITY: "UNKNOWN_AUTHORITY",
|
|
3799
|
+
/** Namespace authority violation */
|
|
3800
|
+
AUTHORITY_VIOLATION: "AUTHORITY_VIOLATION",
|
|
3801
|
+
/** Schema validation failed */
|
|
3802
|
+
SCHEMA_VALIDATION_FAILED: "SCHEMA_VALIDATION_FAILED",
|
|
3803
|
+
/** Canonical IR normalization failed */
|
|
3804
|
+
NORMALIZATION_FAILED: "NORMALIZATION_FAILED",
|
|
3805
|
+
/** Policy compatibility check failed */
|
|
3806
|
+
POLICY_INCOMPATIBLE: "POLICY_INCOMPATIBLE",
|
|
3807
|
+
/** Legacy revision cannot be normalized */
|
|
3808
|
+
LEGACY_NORMALIZATION_FAILED: "LEGACY_NORMALIZATION_FAILED",
|
|
3809
|
+
/** Canonical and legacy conflict */
|
|
3810
|
+
CANONICAL_LEGACY_CONFLICT: "CANONICAL_LEGACY_CONFLICT",
|
|
3811
|
+
/** Relation rule violation */
|
|
3812
|
+
RELATION_RULE_VIOLATION: "RELATION_RULE_VIOLATION",
|
|
3813
|
+
/** Relation rules missing in contract (fail closed) */
|
|
3814
|
+
RELATION_RULES_MISSING: "RELATION_RULES_MISSING",
|
|
3815
|
+
/** Ambiguous revision — multiple revisions found, no unique active write */
|
|
3816
|
+
AMBIGUOUS_REVISION: "AMBIGUOUS_REVISION",
|
|
3817
|
+
/** Invalid relation kind */
|
|
3818
|
+
RELATION_INVALID_KIND: "RELATION_INVALID_KIND",
|
|
3819
|
+
/** Invalid relation target type */
|
|
3820
|
+
RELATION_INVALID_TARGET_TYPE: "RELATION_INVALID_TARGET_TYPE",
|
|
3821
|
+
/** Relation below minimum cardinality */
|
|
3822
|
+
RELATION_BELOW_MIN: "RELATION_BELOW_MIN",
|
|
3823
|
+
/** Relation above maximum cardinality */
|
|
3824
|
+
RELATION_ABOVE_MAX: "RELATION_ABOVE_MAX",
|
|
3825
|
+
/** Missing required anchor */
|
|
3826
|
+
RELATION_MISSING_ANCHOR: "RELATION_MISSING_ANCHOR",
|
|
3827
|
+
/** Forbidden anchor present */
|
|
3828
|
+
RELATION_FORBIDDEN_ANCHOR: "RELATION_FORBIDDEN_ANCHOR",
|
|
3829
|
+
/** Missing required semantic marker */
|
|
3830
|
+
MARKER_MISSING: "MARKER_MISSING",
|
|
3831
|
+
/** Duplicate semantic marker */
|
|
3832
|
+
MARKER_DUPLICATE: "MARKER_DUPLICATE",
|
|
3833
|
+
/** Unknown semantic marker */
|
|
3834
|
+
MARKER_UNKNOWN: "MARKER_UNKNOWN"
|
|
3835
|
+
};
|
|
3836
|
+
ContractError = class extends Error {
|
|
3837
|
+
constructor(code, message, details) {
|
|
3838
|
+
super(message);
|
|
3839
|
+
this.code = code;
|
|
3840
|
+
this.details = details;
|
|
3841
|
+
this.name = "ContractError";
|
|
3842
|
+
}
|
|
3843
|
+
code;
|
|
3844
|
+
details;
|
|
3845
|
+
};
|
|
3846
|
+
OFFICIAL_AUTHORITY = "artifact";
|
|
3847
|
+
OFFICIAL_NAMESPACE_PATTERN = /^artifact(?:\.|$)/;
|
|
3848
|
+
ContractRegistry = class {
|
|
3849
|
+
contracts = /* @__PURE__ */ new Map();
|
|
3850
|
+
typeToActiveWrite = /* @__PURE__ */ new Map();
|
|
3851
|
+
majorToContracts = /* @__PURE__ */ new Map();
|
|
3852
|
+
/**
|
|
3853
|
+
* Register a contract
|
|
3854
|
+
* @throws ContractError if duplicate identity or multiple active write contracts
|
|
3855
|
+
*/
|
|
3856
|
+
register(contract, options) {
|
|
3857
|
+
const major = contract.identity.major;
|
|
3858
|
+
const digest = contract.identity.revisionDigest;
|
|
3859
|
+
const key = `${major}@${digest}`;
|
|
3860
|
+
const isActive = options?.isActive ?? true;
|
|
3861
|
+
const isWriteTarget = options?.isWriteTarget ?? true;
|
|
3862
|
+
if (this.contracts.has(key)) {
|
|
3863
|
+
throw new ContractError(
|
|
3864
|
+
CONTRACT_ERROR_CODES.DUPLICATE_IDENTITY,
|
|
3865
|
+
`Contract ${key} already registered`,
|
|
3866
|
+
{ identity: contract.identity }
|
|
3867
|
+
);
|
|
3868
|
+
}
|
|
3869
|
+
if (isActive && isWriteTarget) {
|
|
3870
|
+
const type = major.split("@")[0];
|
|
3871
|
+
if (this.typeToActiveWrite.has(type)) {
|
|
3872
|
+
throw new ContractError(
|
|
3873
|
+
CONTRACT_ERROR_CODES.MULTIPLE_ACTIVE_WRITE,
|
|
3874
|
+
`Type ${type} already has active write contract: ${this.typeToActiveWrite.get(type)}`,
|
|
3875
|
+
{ identity: contract.identity, existing: this.typeToActiveWrite.get(type) }
|
|
3876
|
+
);
|
|
3877
|
+
}
|
|
3878
|
+
this.typeToActiveWrite.set(type, key);
|
|
3879
|
+
}
|
|
3880
|
+
this.contracts.set(key, {
|
|
3881
|
+
contract,
|
|
3882
|
+
isActive,
|
|
3883
|
+
isWriteTarget,
|
|
3884
|
+
loadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3885
|
+
});
|
|
3886
|
+
if (!this.majorToContracts.has(major)) {
|
|
3887
|
+
this.majorToContracts.set(major, /* @__PURE__ */ new Set());
|
|
3888
|
+
}
|
|
3889
|
+
this.majorToContracts.get(major).add(key);
|
|
3890
|
+
}
|
|
3891
|
+
/**
|
|
3892
|
+
* Resolve by major identity. Multiple revisions require a unique active write
|
|
3893
|
+
* revision; insertion order is never a resolution policy.
|
|
3894
|
+
*/
|
|
3895
|
+
get(major) {
|
|
3896
|
+
const keys = this.majorToContracts.get(major);
|
|
3897
|
+
if (!keys || keys.size === 0) return void 0;
|
|
3898
|
+
if (keys.size === 1) {
|
|
3899
|
+
const onlyKey = keys.values().next().value;
|
|
3900
|
+
return onlyKey ? this.contracts.get(onlyKey)?.contract : void 0;
|
|
3901
|
+
}
|
|
3902
|
+
const type = major.split("@")[0];
|
|
3903
|
+
const activeWriteKey = this.typeToActiveWrite.get(type);
|
|
3904
|
+
if (activeWriteKey && keys.has(activeWriteKey)) {
|
|
3905
|
+
return this.contracts.get(activeWriteKey)?.contract;
|
|
3906
|
+
}
|
|
3907
|
+
throw new ContractError(
|
|
3908
|
+
CONTRACT_ERROR_CODES.AMBIGUOUS_REVISION,
|
|
3909
|
+
`Multiple revisions found for ${major} with no unique active write \u2014 specify digest`,
|
|
3910
|
+
{
|
|
3911
|
+
major,
|
|
3912
|
+
availableDigests: Array.from(keys).map((key) => this.contracts.get(key)?.contract.identity.revisionDigest).filter((digest) => Boolean(digest))
|
|
3913
|
+
}
|
|
3914
|
+
);
|
|
3915
|
+
}
|
|
3916
|
+
/**
|
|
3917
|
+
* Get contract by major identity and digest
|
|
3918
|
+
*/
|
|
3919
|
+
getByMajorAndDigest(major, digest) {
|
|
3920
|
+
const key = `${major}@${digest}`;
|
|
3921
|
+
return this.contracts.get(key)?.contract;
|
|
3922
|
+
}
|
|
3923
|
+
/**
|
|
3924
|
+
* Get all contracts for a major identity
|
|
3925
|
+
*/
|
|
3926
|
+
getByMajor(major) {
|
|
3927
|
+
const keys = this.majorToContracts.get(major);
|
|
3928
|
+
if (!keys) return [];
|
|
3929
|
+
return Array.from(keys).map((key) => this.contracts.get(key)?.contract).filter((c) => c !== void 0);
|
|
3930
|
+
}
|
|
3931
|
+
/**
|
|
3932
|
+
* Get active write contract for a type
|
|
3933
|
+
*/
|
|
3934
|
+
getActiveWriteContract(typePrefix) {
|
|
3935
|
+
const key = this.typeToActiveWrite.get(typePrefix);
|
|
3936
|
+
return key ? this.contracts.get(key)?.contract : void 0;
|
|
3937
|
+
}
|
|
3938
|
+
/**
|
|
3939
|
+
* List all registered contracts
|
|
3940
|
+
*/
|
|
3941
|
+
list() {
|
|
3942
|
+
return Array.from(this.contracts.values());
|
|
3943
|
+
}
|
|
3944
|
+
/**
|
|
3945
|
+
* Check if a contract is registered by major identity
|
|
3946
|
+
*/
|
|
3947
|
+
has(major) {
|
|
3948
|
+
const keys = this.majorToContracts.get(major);
|
|
3949
|
+
return keys !== void 0 && keys.size > 0;
|
|
3950
|
+
}
|
|
3951
|
+
/**
|
|
3952
|
+
* Check if a contract is registered by major identity and digest
|
|
3953
|
+
*/
|
|
3954
|
+
hasByMajorAndDigest(major, digest) {
|
|
3955
|
+
const key = `${major}@${digest}`;
|
|
3956
|
+
return this.contracts.has(key);
|
|
3957
|
+
}
|
|
3958
|
+
};
|
|
3959
|
+
_validatorCache = /* @__PURE__ */ new Map();
|
|
3960
|
+
__AjvCtor = null;
|
|
3961
|
+
E2E_NORMALIZER_CONFIG = {
|
|
3962
|
+
contractMajor: "artifact.e2e-test@1",
|
|
3963
|
+
fieldMappings: [
|
|
3964
|
+
{ legacy: "id", canonical: "metadata.id", required: true },
|
|
3965
|
+
{ legacy: "title", canonical: "metadata.title", required: true },
|
|
3966
|
+
{ legacy: "status", canonical: "metadata.status", required: true },
|
|
3967
|
+
{ legacy: "test_batch", canonical: "metadata.test_batch" },
|
|
3968
|
+
{ legacy: "scope", canonical: "scope.business_goal", transform: (v) => String(v) },
|
|
3969
|
+
{ legacy: "actors", canonical: "scope.actors", transform: (v) => Array.isArray(v) ? v : [v] },
|
|
3970
|
+
{ legacy: "system_boundaries", canonical: "system_boundary.components", transform: (v) => Array.isArray(v) ? v : [v] },
|
|
3971
|
+
{ legacy: "test_cases", canonical: "test_cases", required: true },
|
|
3972
|
+
{ legacy: "relations", canonical: "relations" },
|
|
3973
|
+
// Map legacy coverage fields if present
|
|
3974
|
+
{ legacy: "ac_coverage", canonical: "coverage.ac_coverage" },
|
|
3975
|
+
{ legacy: "related_scenarios", canonical: "coverage.related_scenarios" },
|
|
3976
|
+
// Map legacy environment fields if present
|
|
3977
|
+
{ legacy: "topology", canonical: "environment_data.topology" },
|
|
3978
|
+
{ legacy: "fixtures", canonical: "environment_data.fixtures" },
|
|
3979
|
+
{ legacy: "isolation_strategy", canonical: "environment_data.isolation_strategy" },
|
|
3980
|
+
// Map legacy evidence fields if present
|
|
3981
|
+
{ legacy: "required_artifacts", canonical: "evidence_contract.required_artifacts" },
|
|
3982
|
+
{ legacy: "runner_binding", canonical: "evidence_contract.runner_binding" }
|
|
3983
|
+
],
|
|
3984
|
+
validate: (canonical) => {
|
|
3985
|
+
const errors = [];
|
|
3986
|
+
if (!canonical.metadata || typeof canonical.metadata !== "object") {
|
|
3987
|
+
errors.push("metadata must be an object");
|
|
3988
|
+
}
|
|
3989
|
+
if (!canonical.scope || typeof canonical.scope !== "object") {
|
|
3990
|
+
errors.push("scope must be an object");
|
|
3991
|
+
}
|
|
3992
|
+
if (!canonical.system_boundary || typeof canonical.system_boundary !== "object") {
|
|
3993
|
+
errors.push("system_boundary must be an object");
|
|
3994
|
+
}
|
|
3995
|
+
if (!Array.isArray(canonical.test_cases) || canonical.test_cases.length === 0) {
|
|
3996
|
+
errors.push("test_cases must be a non-empty array");
|
|
3997
|
+
}
|
|
3998
|
+
if (Array.isArray(canonical.test_cases)) {
|
|
3999
|
+
for (let i = 0; i < canonical.test_cases.length; i++) {
|
|
4000
|
+
const tc = canonical.test_cases[i];
|
|
4001
|
+
if (!Array.isArray(tc.trace_targets) || tc.trace_targets.length === 0) {
|
|
4002
|
+
errors.push(`test_cases[${i}].trace_targets must be a non-empty array`);
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
4006
|
+
return errors;
|
|
4007
|
+
}
|
|
4008
|
+
};
|
|
4009
|
+
ContractCatalog = class {
|
|
4010
|
+
contracts = /* @__PURE__ */ new Map();
|
|
4011
|
+
majorToContracts = /* @__PURE__ */ new Map();
|
|
4012
|
+
majorToActiveWrite = /* @__PURE__ */ new Map();
|
|
4013
|
+
/**
|
|
4014
|
+
* Add a contract to the catalog
|
|
4015
|
+
* @throws ContractError if duplicate identity
|
|
4016
|
+
*/
|
|
4017
|
+
add(contract, options) {
|
|
4018
|
+
const major = contract.identity.major;
|
|
4019
|
+
const digest = contract.identity.revisionDigest;
|
|
4020
|
+
const key = `${major}@${digest}`;
|
|
4021
|
+
const isActive = options?.isActive ?? true;
|
|
4022
|
+
const isWriteTarget = options?.isWriteTarget ?? true;
|
|
4023
|
+
if (this.contracts.has(key)) {
|
|
4024
|
+
throw new ContractError(
|
|
4025
|
+
CONTRACT_ERROR_CODES.DUPLICATE_IDENTITY,
|
|
4026
|
+
`Contract ${key} already in catalog`,
|
|
4027
|
+
{ identity: contract.identity }
|
|
4028
|
+
);
|
|
4029
|
+
}
|
|
4030
|
+
this.contracts.set(key, contract);
|
|
4031
|
+
if (!this.majorToContracts.has(major)) {
|
|
4032
|
+
this.majorToContracts.set(major, /* @__PURE__ */ new Set());
|
|
4033
|
+
}
|
|
4034
|
+
this.majorToContracts.get(major).add(key);
|
|
4035
|
+
if (isActive && isWriteTarget) {
|
|
4036
|
+
const existingActive = this.majorToActiveWrite.get(major);
|
|
4037
|
+
if (existingActive && existingActive !== key) {
|
|
4038
|
+
this.contracts.delete(key);
|
|
4039
|
+
this.majorToContracts.get(major)?.delete(key);
|
|
4040
|
+
throw new ContractError(
|
|
4041
|
+
CONTRACT_ERROR_CODES.MULTIPLE_ACTIVE_WRITE,
|
|
4042
|
+
`Major ${major} already has active write revision`,
|
|
4043
|
+
{ major, existing: existingActive, attempted: key }
|
|
4044
|
+
);
|
|
4045
|
+
}
|
|
4046
|
+
this.majorToActiveWrite.set(major, key);
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
/**
|
|
4050
|
+
* Resolve a contract by major identity.
|
|
4051
|
+
* - 0 entries: returns undefined
|
|
4052
|
+
* - 1 entry: returns it
|
|
4053
|
+
* - multiple: if there's a unique active write, returns it; otherwise throws AMBIGUOUS_REVISION
|
|
4054
|
+
*/
|
|
4055
|
+
resolve(major) {
|
|
4056
|
+
const keys = this.majorToContracts.get(major);
|
|
4057
|
+
if (!keys || keys.size === 0) return void 0;
|
|
4058
|
+
if (keys.size === 1) {
|
|
4059
|
+
const firstKey = keys.values().next().value;
|
|
4060
|
+
return firstKey ? this.contracts.get(firstKey) : void 0;
|
|
4061
|
+
}
|
|
4062
|
+
const activeWriteKey = this.majorToActiveWrite.get(major);
|
|
4063
|
+
if (activeWriteKey) {
|
|
4064
|
+
return this.contracts.get(activeWriteKey);
|
|
4065
|
+
}
|
|
4066
|
+
throw new ContractError(
|
|
4067
|
+
CONTRACT_ERROR_CODES.AMBIGUOUS_REVISION,
|
|
4068
|
+
`Multiple revisions found for ${major} with no unique active write \u2014 specify digest`,
|
|
4069
|
+
{
|
|
4070
|
+
major,
|
|
4071
|
+
availableDigests: Array.from(keys).map((key) => this.contracts.get(key)?.identity.revisionDigest).filter((digest) => Boolean(digest))
|
|
4072
|
+
}
|
|
4073
|
+
);
|
|
4074
|
+
}
|
|
4075
|
+
/**
|
|
4076
|
+
* Resolve a contract by major identity and exact digest
|
|
4077
|
+
*/
|
|
4078
|
+
resolveByDigest(major, digest) {
|
|
4079
|
+
const key = `${major}@${digest}`;
|
|
4080
|
+
return this.contracts.get(key);
|
|
4081
|
+
}
|
|
4082
|
+
/**
|
|
4083
|
+
* List all catalog entries (all revisions)
|
|
4084
|
+
*/
|
|
4085
|
+
list() {
|
|
4086
|
+
return Array.from(this.contracts.values()).map((contract) => ({
|
|
4087
|
+
identity: contract.identity,
|
|
4088
|
+
contract
|
|
4089
|
+
}));
|
|
4090
|
+
}
|
|
4091
|
+
/**
|
|
4092
|
+
* Get catalog as JSON-serializable object (all revisions)
|
|
4093
|
+
*/
|
|
4094
|
+
toJSON() {
|
|
4095
|
+
return {
|
|
4096
|
+
contracts: this.list().map((entry) => ({
|
|
4097
|
+
major: entry.identity.major,
|
|
4098
|
+
authority: entry.identity.authority,
|
|
4099
|
+
namespace: entry.identity.namespace,
|
|
4100
|
+
revisionDigest: entry.identity.revisionDigest
|
|
4101
|
+
}))
|
|
4102
|
+
};
|
|
4103
|
+
}
|
|
4104
|
+
};
|
|
4105
|
+
}
|
|
4106
|
+
});
|
|
3081
4107
|
|
|
3082
4108
|
// src/index.ts
|
|
3083
|
-
|
|
3084
|
-
|
|
4109
|
+
import Database from "better-sqlite3";
|
|
4110
|
+
import matter from "gray-matter";
|
|
4111
|
+
import yaml from "js-yaml";
|
|
4112
|
+
import { accessSync, constants as fsConstants, existsSync as existsSync2, statSync } from "fs";
|
|
4113
|
+
import { mkdir as mkdir4, readFile as readFile3, readdir as readdir2, writeFile as writeFile3 } from "fs/promises";
|
|
4114
|
+
import { basename as basename3, dirname as dirname3, extname, isAbsolute as isAbsolute3, join as join6, relative as relative2, resolve as resolve3 } from "path";
|
|
3085
4115
|
function isTargetArtifactType(type) {
|
|
3086
4116
|
return isPacketTargetType(type);
|
|
3087
4117
|
}
|
|
@@ -3122,56 +4152,11 @@ function resolveArtifactTypeName(schema, token) {
|
|
|
3122
4152
|
}
|
|
3123
4153
|
return void 0;
|
|
3124
4154
|
}
|
|
3125
|
-
var DEFAULT_SCHEMA = {
|
|
3126
|
-
types: {
|
|
3127
|
-
decision: { paths: ["artifacts/decisions/**/*.md"], displayName: "\u51B3\u7B56\u6587\u6863", role: "decision", layer: "decision", aliases: ["decisions", "adr"] },
|
|
3128
|
-
entity: { paths: ["artifacts/entities/entity-registry.md"], displayName: "\u5B9E\u4F53\u6CE8\u518C\u8868", role: "context", layer: "domain", aliases: ["entity-registry"] },
|
|
3129
|
-
feature: { paths: ["artifacts/prd/features/**/*.md"], displayName: "\u529F\u80FD\u7279\u6027", role: "feature", layer: "prd", aliases: ["prd-feature", "prd_features"] },
|
|
3130
|
-
scenario: { paths: ["artifacts/scenarios/**/*.md"], displayName: "\u573A\u666F\u5267\u672C", role: "scenario", layer: "scenario", aliases: ["scenarios", "scenario-script"] },
|
|
3131
|
-
design: { paths: ["artifacts/design/**/*.md"], displayName: "\u8BBE\u8BA1\u89C4\u683C", role: "design", layer: "design", aliases: ["design-spec", "design_docs"] },
|
|
3132
|
-
test: { paths: ["heimdall/packages/**/*.test.ts"], displayName: "\u4EE3\u7801\u6CE8\u91CA\u8FFD\u6EAF", role: "context", layer: "implementation", aliases: ["code-test", "code-trace", "unit-test"] },
|
|
3133
|
-
e2e_test: { paths: ["artifacts/tests/e2e/*.md"], displayName: "E2E \u6D4B\u8BD5\u89C4\u683C", role: "e2e_test", layer: "verification", aliases: ["e2e-test", "e2e_tests", "tc"] },
|
|
3134
|
-
e2e_registry: { paths: ["artifacts/tests/e2e/e2e-test-registry.json"], displayName: "E2E \u6D4B\u8BD5\u6CE8\u518C\u8868", role: "context", layer: "verification", aliases: ["e2e-registry"] },
|
|
3135
|
-
"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"] },
|
|
3136
|
-
"test-strategy": { paths: ["artifacts/design/test-strategy.md"], displayName: "\u6D4B\u8BD5\u7B56\u7565", role: "context", layer: "verification", aliases: ["test_strategy"] },
|
|
3137
|
-
"traceability-matrix-v2": { paths: ["artifacts/traceability-matrix-v2.md"], displayName: "\u8FFD\u6EAF\u77E9\u9635 v2", role: "context", layer: "traceability", aliases: ["traceability_matrix_v2"] }
|
|
3138
|
-
},
|
|
3139
|
-
idPatterns: {
|
|
3140
|
-
decision: "^D-[A-Z]+-\\d+$",
|
|
3141
|
-
entity: "^E-\\d{3,}$",
|
|
3142
|
-
feature: "^[A-Z]{1,4}\\d+$",
|
|
3143
|
-
scenario: "^S-\\d+[a-z]?$",
|
|
3144
|
-
design: "^[A-Za-z0-9._-]+$",
|
|
3145
|
-
test: "^.+\\.test\\.ts$",
|
|
3146
|
-
e2e_test: "^.+:(TC-\\d+[a-z]?|FILE)$",
|
|
3147
|
-
e2e_registry: "^e2e-test-registry$",
|
|
3148
|
-
"rule-golden-cases": "^[A-Z]+-\\d{3}:(pass|fail|edge)$",
|
|
3149
|
-
"test-strategy": "^(unit|integration|e2e|desktop_chain|rule|contract)$",
|
|
3150
|
-
"traceability-matrix-v2": "^.+$"
|
|
3151
|
-
},
|
|
3152
|
-
relationFields: {
|
|
3153
|
-
feature: ["scenarios", "decisions", "depends_on", "design_docs"],
|
|
3154
|
-
scenario: ["\u5173\u8054\u529F\u80FD", "\u5173\u8054\u51B3\u7B56"],
|
|
3155
|
-
design: ["related_features", "related_scenarios"],
|
|
3156
|
-
test: ["@scenario", "@feature", "@entity", "@decision"],
|
|
3157
|
-
e2e_test: ["test_batch", "scope", "ac_coverage", "related_scenarios", "related_decisions", "related_entities", "\u8986\u76D6\u573A\u666F", "\u8986\u76D6\u529F\u80FD"],
|
|
3158
|
-
e2e_registry: ["batches"]
|
|
3159
|
-
},
|
|
3160
|
-
allowedEdges: [],
|
|
3161
|
-
forbiddenEdges: [{ from: "scenario", to: "entity", kind: "references" }],
|
|
3162
|
-
statuses: ["planned", "active", "done", "deprecated"],
|
|
3163
|
-
idRanges: {},
|
|
3164
|
-
e2e: {
|
|
3165
|
-
report_uncovered_scenarios: true,
|
|
3166
|
-
report_uncovered_features: true,
|
|
3167
|
-
runners: []
|
|
3168
|
-
}
|
|
3169
|
-
};
|
|
3170
4155
|
async function loadConfig(root) {
|
|
3171
|
-
const configPath =
|
|
4156
|
+
const configPath = join6(root, "artifact-graph.config.yaml");
|
|
3172
4157
|
let parsed = {};
|
|
3173
4158
|
try {
|
|
3174
|
-
const raw = await
|
|
4159
|
+
const raw = await readFile3(configPath, "utf-8");
|
|
3175
4160
|
parsed = yaml.load(raw) ?? {};
|
|
3176
4161
|
} catch (error) {
|
|
3177
4162
|
if (error.code !== "ENOENT") {
|
|
@@ -3345,7 +4330,7 @@ async function scanArtifacts(root, schema) {
|
|
|
3345
4330
|
continue;
|
|
3346
4331
|
}
|
|
3347
4332
|
scannedFiles.set(file, type);
|
|
3348
|
-
const raw = await
|
|
4333
|
+
const raw = await readFile3(join6(root, file), "utf-8");
|
|
3349
4334
|
const parsed = parseFile(type, file, raw, config);
|
|
3350
4335
|
nodes.push(...parsed.nodes);
|
|
3351
4336
|
edges.push(...parsed.edges);
|
|
@@ -3657,7 +4642,7 @@ async function validateScenarioPrdLinkIndex(root, graph) {
|
|
|
3657
4642
|
const indexPath = "artifacts/prd/feature-index.md";
|
|
3658
4643
|
let raw = "";
|
|
3659
4644
|
try {
|
|
3660
|
-
raw = await
|
|
4645
|
+
raw = await readFile3(join6(root, indexPath), "utf-8");
|
|
3661
4646
|
} catch (error) {
|
|
3662
4647
|
if (error.code === "ENOENT") {
|
|
3663
4648
|
return [];
|
|
@@ -3847,11 +4832,11 @@ function nextId(graph, schema, type, rangeName) {
|
|
|
3847
4832
|
throw new Error(`ID range ${type}.${rangeName} is exhausted`);
|
|
3848
4833
|
}
|
|
3849
4834
|
async function writeGraphCache(root, graph) {
|
|
3850
|
-
const cacheDir =
|
|
4835
|
+
const cacheDir = join6(root, ".artifact-graph");
|
|
3851
4836
|
await mkdir4(cacheDir, { recursive: true });
|
|
3852
|
-
await writeFile3(
|
|
4837
|
+
await writeFile3(join6(cacheDir, "index.json"), `${JSON.stringify(graph, null, 2)}
|
|
3853
4838
|
`);
|
|
3854
|
-
const db = new Database(
|
|
4839
|
+
const db = new Database(join6(cacheDir, "graph.sqlite"));
|
|
3855
4840
|
try {
|
|
3856
4841
|
db.exec(`
|
|
3857
4842
|
DROP TABLE IF EXISTS nodes;
|
|
@@ -4189,7 +5174,7 @@ function parseDecisions(path, raw) {
|
|
|
4189
5174
|
}
|
|
4190
5175
|
function isTestFile(filePath) {
|
|
4191
5176
|
const normalized = filePath.replace(/\\/g, "/");
|
|
4192
|
-
const name =
|
|
5177
|
+
const name = basename3(normalized);
|
|
4193
5178
|
if (/\.(test|spec)\.[^.]+$/.test(name)) return true;
|
|
4194
5179
|
if (/(^|\/)(tests|test|__tests__)\//.test(normalized)) return true;
|
|
4195
5180
|
if (/\w+Tests?\.java$/.test(name)) return true;
|
|
@@ -4480,7 +5465,7 @@ function parseE2eTest(path, raw) {
|
|
|
4480
5465
|
});
|
|
4481
5466
|
const nodes = [];
|
|
4482
5467
|
const edges = [];
|
|
4483
|
-
const batch = String(data.test_batch ??
|
|
5468
|
+
const batch = String(data.test_batch ?? basename3(path, extname(path))).trim();
|
|
4484
5469
|
const frontmatterScenarios = toArray(data.related_scenarios).map((value) => String(value).trim()).filter(Boolean);
|
|
4485
5470
|
const scopeFeatures = extractCodes(String(data.scope ?? ""), "feature");
|
|
4486
5471
|
const frontmatterFeatures = [.../* @__PURE__ */ new Set([...Object.keys(asRecord(data.ac_coverage)), ...scopeFeatures])];
|
|
@@ -4553,7 +5538,7 @@ function parseE2eRegistry(path, raw) {
|
|
|
4553
5538
|
data = { parseError: error.message };
|
|
4554
5539
|
}
|
|
4555
5540
|
return {
|
|
4556
|
-
nodes: [{ type: "e2e_registry", code:
|
|
5541
|
+
nodes: [{ type: "e2e_registry", code: basename3(path, extname(path)), title: "E2E Test Registry", path, line: 1, attrs: data }],
|
|
4557
5542
|
edges: []
|
|
4558
5543
|
};
|
|
4559
5544
|
}
|
|
@@ -5534,10 +6519,10 @@ function validateE2eRegistry(graph) {
|
|
|
5534
6519
|
async function validateExecutableTraceability(root, config) {
|
|
5535
6520
|
const issues = [];
|
|
5536
6521
|
const schema = config ?? await loadConfig(root);
|
|
5537
|
-
const e2eDir =
|
|
6522
|
+
const e2eDir = join6(root, "artifacts", "tests", "e2e");
|
|
5538
6523
|
let e2eFiles;
|
|
5539
6524
|
try {
|
|
5540
|
-
e2eFiles = (await
|
|
6525
|
+
e2eFiles = (await readdir2(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join6(e2eDir, name));
|
|
5541
6526
|
} catch {
|
|
5542
6527
|
return [];
|
|
5543
6528
|
}
|
|
@@ -5546,11 +6531,11 @@ async function validateExecutableTraceability(root, config) {
|
|
|
5546
6531
|
const tcKeyToFields = /* @__PURE__ */ new Map();
|
|
5547
6532
|
const mdBatches = /* @__PURE__ */ new Set();
|
|
5548
6533
|
for (const filePath of e2eFiles) {
|
|
5549
|
-
const raw = await
|
|
6534
|
+
const raw = await readFile3(filePath, "utf-8");
|
|
5550
6535
|
const relPath = relative2(root, filePath).split("\\").join("/");
|
|
5551
6536
|
const parsed = matter(raw);
|
|
5552
6537
|
const data = parsed.data;
|
|
5553
|
-
const batch = String(data.test_batch ??
|
|
6538
|
+
const batch = String(data.test_batch ?? basename3(filePath, extname(filePath))).trim();
|
|
5554
6539
|
const lines = raw.split(/\r?\n/);
|
|
5555
6540
|
const tcStarts = [];
|
|
5556
6541
|
lines.forEach((line, index) => {
|
|
@@ -5595,10 +6580,10 @@ async function validateExecutableTraceability(root, config) {
|
|
|
5595
6580
|
const tcAnnotationRegex = /\/\/!?\s*@(?:e2e_test|tc)\s+(\S+?)\s+\[(\w+)\]/;
|
|
5596
6581
|
const tcAnnotationNoLevelRegex = /\/\/!?\s*@(?:e2e_test|tc)\s+(\S+)/;
|
|
5597
6582
|
for (const specFile of specFiles) {
|
|
5598
|
-
const fullSpecPath =
|
|
6583
|
+
const fullSpecPath = join6(root, specFile);
|
|
5599
6584
|
let content;
|
|
5600
6585
|
try {
|
|
5601
|
-
content = await
|
|
6586
|
+
content = await readFile3(fullSpecPath, "utf-8");
|
|
5602
6587
|
} catch {
|
|
5603
6588
|
continue;
|
|
5604
6589
|
}
|
|
@@ -5675,7 +6660,7 @@ async function validateExecutableTraceability(root, config) {
|
|
|
5675
6660
|
if (entry.testId) {
|
|
5676
6661
|
let content;
|
|
5677
6662
|
try {
|
|
5678
|
-
content = await
|
|
6663
|
+
content = await readFile3(join6(root, normalizedRefFile), "utf-8");
|
|
5679
6664
|
} catch {
|
|
5680
6665
|
continue;
|
|
5681
6666
|
}
|
|
@@ -5852,16 +6837,16 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
5852
6837
|
let withExecutableRef = 0;
|
|
5853
6838
|
const statusBreakdown = {};
|
|
5854
6839
|
const chainTypeBreakdown = {};
|
|
5855
|
-
const e2eDir =
|
|
6840
|
+
const e2eDir = join6(root, "artifacts", "tests", "e2e");
|
|
5856
6841
|
const tcFieldsMap = /* @__PURE__ */ new Map();
|
|
5857
6842
|
let e2eFiles;
|
|
5858
6843
|
try {
|
|
5859
|
-
e2eFiles = (await
|
|
6844
|
+
e2eFiles = (await readdir2(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join6(e2eDir, name));
|
|
5860
6845
|
} catch {
|
|
5861
6846
|
e2eFiles = [];
|
|
5862
6847
|
}
|
|
5863
6848
|
for (const filePath of e2eFiles) {
|
|
5864
|
-
const raw = await
|
|
6849
|
+
const raw = await readFile3(filePath, "utf-8");
|
|
5865
6850
|
const lines = raw.split(/\r?\n/);
|
|
5866
6851
|
const tcStarts = [];
|
|
5867
6852
|
lines.forEach((line, index) => {
|
|
@@ -5871,7 +6856,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
5871
6856
|
}
|
|
5872
6857
|
});
|
|
5873
6858
|
const parsed = matter(raw);
|
|
5874
|
-
const batch = String(parsed.data.test_batch ??
|
|
6859
|
+
const batch = String(parsed.data.test_batch ?? basename3(filePath, extname(filePath))).trim();
|
|
5875
6860
|
for (let i = 0; i < tcStarts.length; i++) {
|
|
5876
6861
|
const start = tcStarts[i];
|
|
5877
6862
|
const end = tcStarts[i + 1]?.index ?? lines.length;
|
|
@@ -5934,7 +6919,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
5934
6919
|
let hasActiveE2eRef = false;
|
|
5935
6920
|
for (const entry of parseExecutableRefLines(execRef)) {
|
|
5936
6921
|
const normalized = resolveExecutableRefFile(entry.file, allProjectFiles);
|
|
5937
|
-
if (!normalized || !existsSync2(
|
|
6922
|
+
if (!normalized || !existsSync2(join6(root, normalized))) continue;
|
|
5938
6923
|
const accepting = await getAcceptingRunners(root, normalized, runners);
|
|
5939
6924
|
if (accepting.some((runner) => runner.kind === "e2e")) {
|
|
5940
6925
|
hasActiveE2eRef = true;
|
|
@@ -5993,7 +6978,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
5993
6978
|
const acCoverageRateByFeature = {};
|
|
5994
6979
|
const featureAcMap = /* @__PURE__ */ new Map();
|
|
5995
6980
|
for (const node of featureNodes) {
|
|
5996
|
-
const acs = parseAcceptanceCriteria(await
|
|
6981
|
+
const acs = parseAcceptanceCriteria(await readFile3(join6(root, node.path), "utf-8"));
|
|
5997
6982
|
featureAcMap.set(node.code, new Set(acs));
|
|
5998
6983
|
}
|
|
5999
6984
|
const coveredAcByFeature = /* @__PURE__ */ new Map();
|
|
@@ -6035,10 +7020,10 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
6035
7020
|
};
|
|
6036
7021
|
}
|
|
6037
7022
|
async function generateE2eRegistry(root, opts) {
|
|
6038
|
-
const e2eDir =
|
|
7023
|
+
const e2eDir = join6(root, "artifacts", "tests", "e2e");
|
|
6039
7024
|
let files;
|
|
6040
7025
|
try {
|
|
6041
|
-
files = (await
|
|
7026
|
+
files = (await readdir2(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).sort();
|
|
6042
7027
|
} catch {
|
|
6043
7028
|
return {
|
|
6044
7029
|
registry_version: "1.0",
|
|
@@ -6051,11 +7036,11 @@ async function generateE2eRegistry(root, opts) {
|
|
|
6051
7036
|
const batches = [];
|
|
6052
7037
|
let totalTestCases = 0;
|
|
6053
7038
|
for (const file of files) {
|
|
6054
|
-
const filePath =
|
|
6055
|
-
const raw = await
|
|
7039
|
+
const filePath = join6(e2eDir, file);
|
|
7040
|
+
const raw = await readFile3(filePath, "utf-8");
|
|
6056
7041
|
const parsed = matter(raw);
|
|
6057
7042
|
const data = parsed.data;
|
|
6058
|
-
const batch = String(data.test_batch ??
|
|
7043
|
+
const batch = String(data.test_batch ?? basename3(file, extname(file))).trim();
|
|
6059
7044
|
const relPath = `artifacts/tests/e2e/${file}`;
|
|
6060
7045
|
const scope = String(data.scope ?? "").trim();
|
|
6061
7046
|
const acCoverage = normalizeAcCoverageForRegistry(data.ac_coverage);
|
|
@@ -6144,20 +7129,6 @@ function parseExecutableRefLines(ref) {
|
|
|
6144
7129
|
}
|
|
6145
7130
|
return results;
|
|
6146
7131
|
}
|
|
6147
|
-
var VALID_TC_STATUSES = /* @__PURE__ */ new Set(["created", "automated", "verified", "waived"]);
|
|
6148
|
-
var VALID_CHAIN_TYPES = /* @__PURE__ */ new Set([
|
|
6149
|
-
"desktop_chain",
|
|
6150
|
-
"mock_playwright",
|
|
6151
|
-
"core_e2e",
|
|
6152
|
-
"cli_e2e",
|
|
6153
|
-
"ui_sidecar_bridge",
|
|
6154
|
-
"partial_sidecar",
|
|
6155
|
-
"partial_rust"
|
|
6156
|
-
]);
|
|
6157
|
-
var DEPRECATED_CHAIN_TYPE_ALIASES = {
|
|
6158
|
-
core_only: "core_e2e",
|
|
6159
|
-
frontend_only: "mock_playwright"
|
|
6160
|
-
};
|
|
6161
7132
|
function isDesktopChainType(chainType) {
|
|
6162
7133
|
const normalizedChainType = chainType.trim().toLowerCase();
|
|
6163
7134
|
return normalizedChainType === "desktop_chain" || normalizedChainType === "";
|
|
@@ -6193,10 +7164,10 @@ async function validatePartialRustEvidence(tcFields, tcKey, root, allFiles) {
|
|
|
6193
7164
|
if (!normalizedPath) {
|
|
6194
7165
|
return { hasValidPartialRust: false, detail: `partial_rust file not found: ${ref.file}` };
|
|
6195
7166
|
}
|
|
6196
|
-
const fullPath =
|
|
7167
|
+
const fullPath = join6(root, normalizedPath);
|
|
6197
7168
|
let content;
|
|
6198
7169
|
try {
|
|
6199
|
-
content = await
|
|
7170
|
+
content = await readFile3(fullPath, "utf-8");
|
|
6200
7171
|
} catch {
|
|
6201
7172
|
return { hasValidPartialRust: false, detail: `partial_rust file not found: ${ref.file}` };
|
|
6202
7173
|
}
|
|
@@ -6305,9 +7276,9 @@ function escapeRegExp(value) {
|
|
|
6305
7276
|
}
|
|
6306
7277
|
async function hasMarkdownTc(tcKey, e2eDir) {
|
|
6307
7278
|
const [batch, tcId] = tcKey.split(":");
|
|
6308
|
-
const filePath =
|
|
7279
|
+
const filePath = join6(e2eDir, `${batch}.md`);
|
|
6309
7280
|
try {
|
|
6310
|
-
const raw = await
|
|
7281
|
+
const raw = await readFile3(filePath, "utf-8");
|
|
6311
7282
|
const tcRegex = new RegExp(`^#{2,3}\\s+${escapeRegExp(tcId)}\\s*[:\uFF1A]?`, "m");
|
|
6312
7283
|
return tcRegex.test(raw);
|
|
6313
7284
|
} catch {
|
|
@@ -6327,13 +7298,13 @@ async function findFiles(root, patterns) {
|
|
|
6327
7298
|
return [...matched].sort();
|
|
6328
7299
|
}
|
|
6329
7300
|
async function walk(root, current = root) {
|
|
6330
|
-
const entries = await
|
|
7301
|
+
const entries = await readdir2(current, { withFileTypes: true });
|
|
6331
7302
|
const files = [];
|
|
6332
7303
|
for (const entry of entries) {
|
|
6333
7304
|
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git" || entry.name === ".artifact-graph") {
|
|
6334
7305
|
continue;
|
|
6335
7306
|
}
|
|
6336
|
-
const fullPath =
|
|
7307
|
+
const fullPath = join6(current, entry.name);
|
|
6337
7308
|
if (entry.isDirectory()) {
|
|
6338
7309
|
files.push(...await walk(root, fullPath));
|
|
6339
7310
|
} else {
|
|
@@ -6552,7 +7523,7 @@ function normalizeDesignCode(value) {
|
|
|
6552
7523
|
if (!raw) {
|
|
6553
7524
|
return "";
|
|
6554
7525
|
}
|
|
6555
|
-
return
|
|
7526
|
+
return basename3(raw, extname(raw));
|
|
6556
7527
|
}
|
|
6557
7528
|
function toUid(type, code) {
|
|
6558
7529
|
return `${type}:${code}`;
|
|
@@ -6649,30 +7620,6 @@ function discoverTargets(graph, options) {
|
|
|
6649
7620
|
}
|
|
6650
7621
|
return result;
|
|
6651
7622
|
}
|
|
6652
|
-
var CONTEXT_CATEGORIES = {
|
|
6653
|
-
feature: "prd",
|
|
6654
|
-
scenario: "scenario",
|
|
6655
|
-
decision: "decision",
|
|
6656
|
-
design: "design",
|
|
6657
|
-
entity: "entity",
|
|
6658
|
-
"interface_contracts": "contract",
|
|
6659
|
-
"data_contracts": "contract",
|
|
6660
|
-
"application_state_machines": "contract",
|
|
6661
|
-
"error_model": "contract",
|
|
6662
|
-
"report-contracts": "contract",
|
|
6663
|
-
"ui-flow-contracts": "contract",
|
|
6664
|
-
"non-functional-budgets": "contract",
|
|
6665
|
-
"domain-glossary": "domain",
|
|
6666
|
-
"bounded-context-map": "domain",
|
|
6667
|
-
"domain-invariants": "domain",
|
|
6668
|
-
"generation-packet-spec": "domain",
|
|
6669
|
-
"rule-golden-cases": "verification",
|
|
6670
|
-
"test-strategy": "verification",
|
|
6671
|
-
"verification-fixtures": "verification",
|
|
6672
|
-
"implementation-blueprint": "blueprint",
|
|
6673
|
-
"traceability-matrix-v2": "matrix"
|
|
6674
|
-
};
|
|
6675
|
-
var TIER_ORDER = ["baseline", "target", "direct", "matrix", "transitive"];
|
|
6676
7623
|
function resolveArtifactContext(graph, opts) {
|
|
6677
7624
|
const mode = opts.mode ?? "full";
|
|
6678
7625
|
const maxPerCategory = opts.maxPerCategory ?? 20;
|
|
@@ -6841,7 +7788,7 @@ function resolveArtifactContext(graph, opts) {
|
|
|
6841
7788
|
}
|
|
6842
7789
|
if (root) {
|
|
6843
7790
|
for (const ap of ALWAYS_PRESENT_ITEMS) {
|
|
6844
|
-
const fullPath =
|
|
7791
|
+
const fullPath = join6(root, ap.path);
|
|
6845
7792
|
let stat;
|
|
6846
7793
|
try {
|
|
6847
7794
|
stat = statSync(fullPath);
|
|
@@ -7071,10 +8018,118 @@ function formatContextMarkdown(manifest) {
|
|
|
7071
8018
|
}
|
|
7072
8019
|
return lines.join("\n");
|
|
7073
8020
|
}
|
|
8021
|
+
var TARGET_ARTIFACT_TYPES, NON_TARGET_ROLES, DEFAULT_SCHEMA, VALID_TC_STATUSES, VALID_CHAIN_TYPES, DEPRECATED_CHAIN_TYPE_ALIASES, CONTEXT_CATEGORIES, TIER_ORDER;
|
|
8022
|
+
var init_index = __esm({
|
|
8023
|
+
"src/index.ts"() {
|
|
8024
|
+
"use strict";
|
|
8025
|
+
init_packet_constants();
|
|
8026
|
+
init_packet_validator();
|
|
8027
|
+
init_glob_matcher();
|
|
8028
|
+
init_packet_constants();
|
|
8029
|
+
init_target_selector();
|
|
8030
|
+
init_packet_assembler();
|
|
8031
|
+
init_packet_audit();
|
|
8032
|
+
init_packet_validator();
|
|
8033
|
+
init_packet_prompt();
|
|
8034
|
+
init_packet_prompt_validator();
|
|
8035
|
+
init_versioned_traceability();
|
|
8036
|
+
init_cli_resolver();
|
|
8037
|
+
init_git_changes();
|
|
8038
|
+
init_git_hook_path();
|
|
8039
|
+
init_hook_installer();
|
|
8040
|
+
init_review_result_validator();
|
|
8041
|
+
init_contract_kernel();
|
|
8042
|
+
TARGET_ARTIFACT_TYPES = VALID_PACKET_TARGET_TYPES;
|
|
8043
|
+
NON_TARGET_ROLES = ["context", "candidate", "not-recommended"];
|
|
8044
|
+
DEFAULT_SCHEMA = {
|
|
8045
|
+
types: {
|
|
8046
|
+
decision: { paths: ["artifacts/decisions/**/*.md"], displayName: "\u51B3\u7B56\u6587\u6863", role: "decision", layer: "decision", aliases: ["decisions", "adr"] },
|
|
8047
|
+
entity: { paths: ["artifacts/entities/entity-registry.md"], displayName: "\u5B9E\u4F53\u6CE8\u518C\u8868", role: "context", layer: "domain", aliases: ["entity-registry"] },
|
|
8048
|
+
feature: { paths: ["artifacts/prd/features/**/*.md"], displayName: "\u529F\u80FD\u7279\u6027", role: "feature", layer: "prd", aliases: ["prd-feature", "prd_features"] },
|
|
8049
|
+
scenario: { paths: ["artifacts/scenarios/**/*.md"], displayName: "\u573A\u666F\u5267\u672C", role: "scenario", layer: "scenario", aliases: ["scenarios", "scenario-script"] },
|
|
8050
|
+
design: { paths: ["artifacts/design/**/*.md"], displayName: "\u8BBE\u8BA1\u89C4\u683C", role: "design", layer: "design", aliases: ["design-spec", "design_docs"] },
|
|
8051
|
+
test: { paths: ["heimdall/packages/**/*.test.ts"], displayName: "\u4EE3\u7801\u6CE8\u91CA\u8FFD\u6EAF", role: "context", layer: "implementation", aliases: ["code-test", "code-trace", "unit-test"] },
|
|
8052
|
+
e2e_test: { paths: ["artifacts/tests/e2e/*.md"], displayName: "E2E \u6D4B\u8BD5\u89C4\u683C", role: "e2e_test", layer: "verification", aliases: ["e2e-test", "e2e_tests", "tc"] },
|
|
8053
|
+
e2e_registry: { paths: ["artifacts/tests/e2e/e2e-test-registry.json"], displayName: "E2E \u6D4B\u8BD5\u6CE8\u518C\u8868", role: "context", layer: "verification", aliases: ["e2e-registry"] },
|
|
8054
|
+
"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"] },
|
|
8055
|
+
"test-strategy": { paths: ["artifacts/design/test-strategy.md"], displayName: "\u6D4B\u8BD5\u7B56\u7565", role: "context", layer: "verification", aliases: ["test_strategy"] },
|
|
8056
|
+
"traceability-matrix-v2": { paths: ["artifacts/traceability-matrix-v2.md"], displayName: "\u8FFD\u6EAF\u77E9\u9635 v2", role: "context", layer: "traceability", aliases: ["traceability_matrix_v2"] }
|
|
8057
|
+
},
|
|
8058
|
+
idPatterns: {
|
|
8059
|
+
decision: "^D-[A-Z]+-\\d+$",
|
|
8060
|
+
entity: "^E-\\d{3,}$",
|
|
8061
|
+
feature: "^[A-Z]{1,4}\\d+$",
|
|
8062
|
+
scenario: "^S-\\d+[a-z]?$",
|
|
8063
|
+
design: "^[A-Za-z0-9._-]+$",
|
|
8064
|
+
test: "^.+\\.test\\.ts$",
|
|
8065
|
+
e2e_test: "^.+:(TC-\\d+[a-z]?|FILE)$",
|
|
8066
|
+
e2e_registry: "^e2e-test-registry$",
|
|
8067
|
+
"rule-golden-cases": "^[A-Z]+-\\d{3}:(pass|fail|edge)$",
|
|
8068
|
+
"test-strategy": "^(unit|integration|e2e|desktop_chain|rule|contract)$",
|
|
8069
|
+
"traceability-matrix-v2": "^.+$"
|
|
8070
|
+
},
|
|
8071
|
+
relationFields: {
|
|
8072
|
+
feature: ["scenarios", "decisions", "depends_on", "design_docs"],
|
|
8073
|
+
scenario: ["\u5173\u8054\u529F\u80FD", "\u5173\u8054\u51B3\u7B56"],
|
|
8074
|
+
design: ["related_features", "related_scenarios"],
|
|
8075
|
+
test: ["@scenario", "@feature", "@entity", "@decision"],
|
|
8076
|
+
e2e_test: ["test_batch", "scope", "ac_coverage", "related_scenarios", "related_decisions", "related_entities", "\u8986\u76D6\u573A\u666F", "\u8986\u76D6\u529F\u80FD"],
|
|
8077
|
+
e2e_registry: ["batches"]
|
|
8078
|
+
},
|
|
8079
|
+
allowedEdges: [],
|
|
8080
|
+
forbiddenEdges: [{ from: "scenario", to: "entity", kind: "references" }],
|
|
8081
|
+
statuses: ["planned", "active", "done", "deprecated"],
|
|
8082
|
+
idRanges: {},
|
|
8083
|
+
e2e: {
|
|
8084
|
+
report_uncovered_scenarios: true,
|
|
8085
|
+
report_uncovered_features: true,
|
|
8086
|
+
runners: []
|
|
8087
|
+
}
|
|
8088
|
+
};
|
|
8089
|
+
VALID_TC_STATUSES = /* @__PURE__ */ new Set(["created", "automated", "verified", "waived"]);
|
|
8090
|
+
VALID_CHAIN_TYPES = /* @__PURE__ */ new Set([
|
|
8091
|
+
"desktop_chain",
|
|
8092
|
+
"mock_playwright",
|
|
8093
|
+
"core_e2e",
|
|
8094
|
+
"cli_e2e",
|
|
8095
|
+
"ui_sidecar_bridge",
|
|
8096
|
+
"partial_sidecar",
|
|
8097
|
+
"partial_rust"
|
|
8098
|
+
]);
|
|
8099
|
+
DEPRECATED_CHAIN_TYPE_ALIASES = {
|
|
8100
|
+
core_only: "core_e2e",
|
|
8101
|
+
frontend_only: "mock_playwright"
|
|
8102
|
+
};
|
|
8103
|
+
CONTEXT_CATEGORIES = {
|
|
8104
|
+
feature: "prd",
|
|
8105
|
+
scenario: "scenario",
|
|
8106
|
+
decision: "decision",
|
|
8107
|
+
design: "design",
|
|
8108
|
+
entity: "entity",
|
|
8109
|
+
"interface_contracts": "contract",
|
|
8110
|
+
"data_contracts": "contract",
|
|
8111
|
+
"application_state_machines": "contract",
|
|
8112
|
+
"error_model": "contract",
|
|
8113
|
+
"report-contracts": "contract",
|
|
8114
|
+
"ui-flow-contracts": "contract",
|
|
8115
|
+
"non-functional-budgets": "contract",
|
|
8116
|
+
"domain-glossary": "domain",
|
|
8117
|
+
"bounded-context-map": "domain",
|
|
8118
|
+
"domain-invariants": "domain",
|
|
8119
|
+
"generation-packet-spec": "domain",
|
|
8120
|
+
"rule-golden-cases": "verification",
|
|
8121
|
+
"test-strategy": "verification",
|
|
8122
|
+
"verification-fixtures": "verification",
|
|
8123
|
+
"implementation-blueprint": "blueprint",
|
|
8124
|
+
"traceability-matrix-v2": "matrix"
|
|
8125
|
+
};
|
|
8126
|
+
TIER_ORDER = ["baseline", "target", "direct", "matrix", "transitive"];
|
|
8127
|
+
}
|
|
8128
|
+
});
|
|
7074
8129
|
|
|
7075
8130
|
// src/packet-prompt-audit.ts
|
|
7076
8131
|
import { mkdir as mkdir5, writeFile as writeFile4 } from "fs/promises";
|
|
7077
|
-
import { join as
|
|
8132
|
+
import { join as join7 } from "path";
|
|
7078
8133
|
function promptFilename(target) {
|
|
7079
8134
|
return `prompt-${target.type}-${target.id}.md`;
|
|
7080
8135
|
}
|
|
@@ -7126,7 +8181,7 @@ async function auditSinglePromptTarget(target, graph, options) {
|
|
|
7126
8181
|
}
|
|
7127
8182
|
if (options.outDir) {
|
|
7128
8183
|
const filename = promptFilename(target);
|
|
7129
|
-
const outPath =
|
|
8184
|
+
const outPath = join7(options.outDir, filename);
|
|
7130
8185
|
await writeFile4(outPath, prompt, "utf-8");
|
|
7131
8186
|
entry.outputPath = outPath;
|
|
7132
8187
|
}
|
|
@@ -7232,9 +8287,9 @@ async function auditPromptBatch(root, targets, options, graph) {
|
|
|
7232
8287
|
};
|
|
7233
8288
|
if (options.outDir) {
|
|
7234
8289
|
await mkdir5(options.outDir, { recursive: true });
|
|
7235
|
-
const jsonPath =
|
|
8290
|
+
const jsonPath = join7(options.outDir, "prompt-audit-summary.json");
|
|
7236
8291
|
await writeFile4(jsonPath, JSON.stringify(summary, null, 2) + "\n", "utf-8");
|
|
7237
|
-
const mdPath =
|
|
8292
|
+
const mdPath = join7(options.outDir, "prompt-audit-summary.md");
|
|
7238
8293
|
await writeFile4(mdPath, renderPromptAuditSummaryMarkdown(summary), "utf-8");
|
|
7239
8294
|
}
|
|
7240
8295
|
return summary;
|
|
@@ -7258,8 +8313,26 @@ async function discoverAndAuditPromptBatch(root, options) {
|
|
|
7258
8313
|
universalBaseline: effectiveBaseline
|
|
7259
8314
|
}, graph);
|
|
7260
8315
|
}
|
|
8316
|
+
var init_packet_prompt_audit = __esm({
|
|
8317
|
+
"src/packet-prompt-audit.ts"() {
|
|
8318
|
+
"use strict";
|
|
8319
|
+
init_index();
|
|
8320
|
+
init_packet_prompt();
|
|
8321
|
+
init_packet_prompt_validator();
|
|
8322
|
+
init_packet_audit();
|
|
8323
|
+
}
|
|
8324
|
+
});
|
|
7261
8325
|
|
|
7262
8326
|
// src/cli.ts
|
|
8327
|
+
var cli_exports = {};
|
|
8328
|
+
__export(cli_exports, {
|
|
8329
|
+
runCli: () => runCli
|
|
8330
|
+
});
|
|
8331
|
+
import yaml2 from "js-yaml";
|
|
8332
|
+
import { realpathSync } from "fs";
|
|
8333
|
+
import { access as access2, mkdir as mkdir6, readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
|
|
8334
|
+
import { dirname as dirname4, isAbsolute as isAbsolute4, join as join8 } from "path";
|
|
8335
|
+
import { fileURLToPath } from "url";
|
|
7263
8336
|
async function runCli(argv, io = {}) {
|
|
7264
8337
|
const parsed = parseArgs(argv);
|
|
7265
8338
|
const cwd = io.cwd ?? process.cwd();
|
|
@@ -7275,7 +8348,7 @@ async function runCli(argv, io = {}) {
|
|
|
7275
8348
|
switch (parsed.command) {
|
|
7276
8349
|
case "init": {
|
|
7277
8350
|
await initConfig(root);
|
|
7278
|
-
out(`Created ${
|
|
8351
|
+
out(`Created ${join8(root, "artifact-graph.config.yaml")}
|
|
7279
8352
|
`);
|
|
7280
8353
|
return 0;
|
|
7281
8354
|
}
|
|
@@ -7599,7 +8672,7 @@ async function runCli(argv, io = {}) {
|
|
|
7599
8672
|
if (packetJsonPath) {
|
|
7600
8673
|
let rawJson;
|
|
7601
8674
|
try {
|
|
7602
|
-
rawJson = await
|
|
8675
|
+
rawJson = await readFile4(packetJsonPath, "utf-8");
|
|
7603
8676
|
} catch (readErr) {
|
|
7604
8677
|
err(`\u9519\u8BEF\uFF1A\u65E0\u6CD5\u8BFB\u53D6 packet \u6587\u4EF6: "${packetJsonPath}" \u2014 ${readErr.message}
|
|
7605
8678
|
`);
|
|
@@ -7800,7 +8873,7 @@ async function runCli(argv, io = {}) {
|
|
|
7800
8873
|
universalBaseline: discoverConfig.context?.universal_baseline
|
|
7801
8874
|
});
|
|
7802
8875
|
} else {
|
|
7803
|
-
const targetsContent = await
|
|
8876
|
+
const targetsContent = await readFile4(targetsFile, "utf-8");
|
|
7804
8877
|
const auditConfig2 = await loadConfig(root);
|
|
7805
8878
|
const parseResult = parseTargetsFile(targetsContent, auditConfig2);
|
|
7806
8879
|
if (parseResult.errors.length > 0) {
|
|
@@ -7925,7 +8998,7 @@ async function runCli(argv, io = {}) {
|
|
|
7925
8998
|
} else {
|
|
7926
8999
|
let ppaTargetsContent;
|
|
7927
9000
|
try {
|
|
7928
|
-
ppaTargetsContent = await
|
|
9001
|
+
ppaTargetsContent = await readFile4(ppaTargetsFile, "utf-8");
|
|
7929
9002
|
} catch (readErr) {
|
|
7930
9003
|
err(`\u9519\u8BEF\uFF1A\u65E0\u6CD5\u8BFB\u53D6 targets \u6587\u4EF6: "${ppaTargetsFile}" \u2014 ${readErr.message}
|
|
7931
9004
|
`);
|
|
@@ -8172,7 +9245,7 @@ async function runCli(argv, io = {}) {
|
|
|
8172
9245
|
const prepared = [];
|
|
8173
9246
|
for (const hookName of hooks) {
|
|
8174
9247
|
const templatePath = fileURLToPath(new URL(`../templates/git-hooks/${hookName}.sh`, import.meta.url));
|
|
8175
|
-
const block = await
|
|
9248
|
+
const block = await readFile4(templatePath, "utf-8");
|
|
8176
9249
|
prepared.push(await prepareManagedHookBlock({
|
|
8177
9250
|
hookPath: await resolveGitHookPath(root, hookName),
|
|
8178
9251
|
block,
|
|
@@ -8213,10 +9286,10 @@ async function runCli(argv, io = {}) {
|
|
|
8213
9286
|
err("Usage: artifact-graph validate-review-result --file <path> [--format json]\n");
|
|
8214
9287
|
return 1;
|
|
8215
9288
|
}
|
|
8216
|
-
const resolvedPath = isAbsolute4(filePath) ? filePath :
|
|
9289
|
+
const resolvedPath = isAbsolute4(filePath) ? filePath : join8(root, filePath);
|
|
8217
9290
|
let content;
|
|
8218
9291
|
try {
|
|
8219
|
-
content = await
|
|
9292
|
+
content = await readFile4(resolvedPath, "utf-8");
|
|
8220
9293
|
} catch (readErr) {
|
|
8221
9294
|
err(`Error: Cannot read file: "${resolvedPath}" \u2014 ${readErr.message}
|
|
8222
9295
|
`);
|
|
@@ -8251,11 +9324,11 @@ async function runCli(argv, io = {}) {
|
|
|
8251
9324
|
const deterministic = checkMode || parsed.flags.deterministic === true;
|
|
8252
9325
|
const registry = await generateE2eRegistry(root, { deterministic });
|
|
8253
9326
|
const output = JSON.stringify(registry, null, 2) + "\n";
|
|
8254
|
-
const outPath = typeof parsed.flags.out === "string" ? parsed.flags.out :
|
|
9327
|
+
const outPath = typeof parsed.flags.out === "string" ? parsed.flags.out : join8(root, "artifacts/tests/e2e/e2e-test-registry.json");
|
|
8255
9328
|
if (checkMode) {
|
|
8256
9329
|
let existing = "";
|
|
8257
9330
|
try {
|
|
8258
|
-
existing = await
|
|
9331
|
+
existing = await readFile4(outPath, "utf-8");
|
|
8259
9332
|
} catch {
|
|
8260
9333
|
err(`Check failed: ${outPath} does not exist or is not readable
|
|
8261
9334
|
`);
|
|
@@ -8279,11 +9352,266 @@ async function runCli(argv, io = {}) {
|
|
|
8279
9352
|
}
|
|
8280
9353
|
return 0;
|
|
8281
9354
|
}
|
|
9355
|
+
case "contract": {
|
|
9356
|
+
const contractAction = parsed.positional[0];
|
|
9357
|
+
const contractFormat = typeof parsed.flags.format === "string" ? parsed.flags.format : "json";
|
|
9358
|
+
if (contractFormat !== "json") {
|
|
9359
|
+
out(`${JSON.stringify({ ok: false, error: { code: "INVALID_FORMAT", path: "/format", message: `Invalid --format: "${contractFormat}". Allowed values: json` } }, null, 2)}
|
|
9360
|
+
`);
|
|
9361
|
+
return 1;
|
|
9362
|
+
}
|
|
9363
|
+
const packageDir = dirname4(fileURLToPath(import.meta.url));
|
|
9364
|
+
const contractsDir = typeof parsed.flags["contracts-dir"] === "string" ? parsed.flags["contracts-dir"] : join8(packageDir, "..", "contracts");
|
|
9365
|
+
const revisionDigest = typeof parsed.flags["revision-digest"] === "string" ? parsed.flags["revision-digest"] : void 0;
|
|
9366
|
+
async function resolveContract(contractId) {
|
|
9367
|
+
const catalog = await loadContractCatalog(contractsDir);
|
|
9368
|
+
if (revisionDigest) {
|
|
9369
|
+
return catalog.resolveByDigest(contractId, revisionDigest);
|
|
9370
|
+
}
|
|
9371
|
+
try {
|
|
9372
|
+
return catalog.resolve(contractId);
|
|
9373
|
+
} catch (e) {
|
|
9374
|
+
if (e instanceof ContractError && e.code === "AMBIGUOUS_REVISION") {
|
|
9375
|
+
throw e;
|
|
9376
|
+
}
|
|
9377
|
+
return void 0;
|
|
9378
|
+
}
|
|
9379
|
+
}
|
|
9380
|
+
if (contractAction === "list") {
|
|
9381
|
+
const catalog = await loadContractCatalog(contractsDir);
|
|
9382
|
+
out(`${JSON.stringify({ ok: true, data: catalog.toJSON() }, null, 2)}
|
|
9383
|
+
`);
|
|
9384
|
+
return 0;
|
|
9385
|
+
}
|
|
9386
|
+
if (contractAction === "explain") {
|
|
9387
|
+
const contractId = typeof parsed.flags.contract === "string" ? parsed.flags.contract : void 0;
|
|
9388
|
+
if (!contractId) {
|
|
9389
|
+
out(`${JSON.stringify({ ok: false, error: { code: "CONTRACT_NOT_FOUND", path: "/contract", message: "--contract is required" } }, null, 2)}
|
|
9390
|
+
`);
|
|
9391
|
+
return 1;
|
|
9392
|
+
}
|
|
9393
|
+
let contract;
|
|
9394
|
+
try {
|
|
9395
|
+
contract = await resolveContract(contractId);
|
|
9396
|
+
} catch (e) {
|
|
9397
|
+
if (e instanceof ContractError && e.code === "AMBIGUOUS_REVISION") {
|
|
9398
|
+
out(`${JSON.stringify({ ok: false, error: { code: "AMBIGUOUS_REVISION", path: "/contract", message: e.message, details: e.details } }, null, 2)}
|
|
9399
|
+
`);
|
|
9400
|
+
return 1;
|
|
9401
|
+
}
|
|
9402
|
+
contract = void 0;
|
|
9403
|
+
}
|
|
9404
|
+
if (!contract) {
|
|
9405
|
+
out(`${JSON.stringify({ ok: false, error: { code: "CONTRACT_NOT_FOUND", path: "/contract", message: `Contract "${contractId}" not found` } }, null, 2)}
|
|
9406
|
+
`);
|
|
9407
|
+
return 1;
|
|
9408
|
+
}
|
|
9409
|
+
out(`${JSON.stringify({ ok: true, data: {
|
|
9410
|
+
identity: contract.identity,
|
|
9411
|
+
schema: { $id: contract.schema.$id, title: contract.schema.title, version: contract.schema.version }
|
|
9412
|
+
} }, null, 2)}
|
|
9413
|
+
`);
|
|
9414
|
+
return 0;
|
|
9415
|
+
}
|
|
9416
|
+
if (contractAction === "validate") {
|
|
9417
|
+
const contractId = typeof parsed.flags.contract === "string" ? parsed.flags.contract : void 0;
|
|
9418
|
+
const dataRaw = typeof parsed.flags.data === "string" ? parsed.flags.data : void 0;
|
|
9419
|
+
if (!contractId || !dataRaw) {
|
|
9420
|
+
out(`${JSON.stringify({ ok: false, error: { code: "CONTRACT_NOT_FOUND", path: "/contract", message: "--contract and --data are required" } }, null, 2)}
|
|
9421
|
+
`);
|
|
9422
|
+
return 1;
|
|
9423
|
+
}
|
|
9424
|
+
let contract;
|
|
9425
|
+
try {
|
|
9426
|
+
contract = await resolveContract(contractId);
|
|
9427
|
+
} catch (e) {
|
|
9428
|
+
if (e instanceof ContractError && e.code === "AMBIGUOUS_REVISION") {
|
|
9429
|
+
out(`${JSON.stringify({ ok: false, error: { code: "AMBIGUOUS_REVISION", path: "/contract", message: e.message, details: e.details } }, null, 2)}
|
|
9430
|
+
`);
|
|
9431
|
+
return 1;
|
|
9432
|
+
}
|
|
9433
|
+
contract = void 0;
|
|
9434
|
+
}
|
|
9435
|
+
if (!contract) {
|
|
9436
|
+
out(`${JSON.stringify({ ok: false, error: { code: "CONTRACT_NOT_FOUND", path: "/contract", message: `Contract "${contractId}" not found` } }, null, 2)}
|
|
9437
|
+
`);
|
|
9438
|
+
return 1;
|
|
9439
|
+
}
|
|
9440
|
+
let data;
|
|
9441
|
+
try {
|
|
9442
|
+
data = JSON.parse(dataRaw);
|
|
9443
|
+
} catch {
|
|
9444
|
+
out(`${JSON.stringify({ ok: false, error: { code: "SCHEMA_VALIDATION_FAILED", path: "/data", message: "--data is not valid JSON" } }, null, 2)}
|
|
9445
|
+
`);
|
|
9446
|
+
return 1;
|
|
9447
|
+
}
|
|
9448
|
+
const schemaResult = validateContractAgainstSchema(data, contract);
|
|
9449
|
+
if (!schemaResult.valid) {
|
|
9450
|
+
out(`${JSON.stringify({ ok: false, errors: schemaResult.errors.map((e) => ({ code: "SCHEMA_VALIDATION_FAILED", path: "/", message: e })) }, null, 2)}
|
|
9451
|
+
`);
|
|
9452
|
+
return 1;
|
|
9453
|
+
}
|
|
9454
|
+
const dataObj = data;
|
|
9455
|
+
if (Array.isArray(dataObj.relations)) {
|
|
9456
|
+
const relationResult = validateRelations(dataObj.relations, contract);
|
|
9457
|
+
if (!relationResult.valid) {
|
|
9458
|
+
out(`${JSON.stringify({ ok: false, errors: relationResult.issues }, null, 2)}
|
|
9459
|
+
`);
|
|
9460
|
+
return 1;
|
|
9461
|
+
}
|
|
9462
|
+
}
|
|
9463
|
+
out(`${JSON.stringify({ ok: true, data: { valid: true, issues: [] } }, null, 2)}
|
|
9464
|
+
`);
|
|
9465
|
+
return 0;
|
|
9466
|
+
}
|
|
9467
|
+
if (contractAction === "check-policy") {
|
|
9468
|
+
const contractId = typeof parsed.flags.contract === "string" ? parsed.flags.contract : void 0;
|
|
9469
|
+
const policyRaw = typeof parsed.flags.policy === "string" ? parsed.flags.policy : void 0;
|
|
9470
|
+
if (!contractId || !policyRaw) {
|
|
9471
|
+
out(`${JSON.stringify({ ok: false, error: { code: "CONTRACT_NOT_FOUND", path: "/contract", message: "--contract and --policy are required" } }, null, 2)}
|
|
9472
|
+
`);
|
|
9473
|
+
return 1;
|
|
9474
|
+
}
|
|
9475
|
+
let contract;
|
|
9476
|
+
try {
|
|
9477
|
+
contract = await resolveContract(contractId);
|
|
9478
|
+
} catch (e) {
|
|
9479
|
+
if (e instanceof ContractError && e.code === "AMBIGUOUS_REVISION") {
|
|
9480
|
+
out(`${JSON.stringify({ ok: false, error: { code: "AMBIGUOUS_REVISION", path: "/contract", message: e.message, details: e.details } }, null, 2)}
|
|
9481
|
+
`);
|
|
9482
|
+
return 1;
|
|
9483
|
+
}
|
|
9484
|
+
contract = void 0;
|
|
9485
|
+
}
|
|
9486
|
+
if (!contract) {
|
|
9487
|
+
out(`${JSON.stringify({ ok: false, error: { code: "CONTRACT_NOT_FOUND", path: "/contract", message: `Contract "${contractId}" not found` } }, null, 2)}
|
|
9488
|
+
`);
|
|
9489
|
+
return 1;
|
|
9490
|
+
}
|
|
9491
|
+
let policy;
|
|
9492
|
+
try {
|
|
9493
|
+
policy = JSON.parse(policyRaw);
|
|
9494
|
+
} catch {
|
|
9495
|
+
out(`${JSON.stringify({ ok: false, error: { code: "SCHEMA_VALIDATION_FAILED", path: "/policy", message: "--policy is not valid JSON" } }, null, 2)}
|
|
9496
|
+
`);
|
|
9497
|
+
return 1;
|
|
9498
|
+
}
|
|
9499
|
+
const result = validatePolicyCompatibility(policy, contract);
|
|
9500
|
+
if (!result.compatible) {
|
|
9501
|
+
out(`${JSON.stringify({ ok: false, errors: result.errors.map((message) => ({ code: "POLICY_INCOMPATIBLE", path: "/policy", message })) }, null, 2)}
|
|
9502
|
+
`);
|
|
9503
|
+
return 1;
|
|
9504
|
+
}
|
|
9505
|
+
out(`${JSON.stringify({ ok: true, data: { compatible: true, warnings: result.warnings } }, null, 2)}
|
|
9506
|
+
`);
|
|
9507
|
+
return 0;
|
|
9508
|
+
}
|
|
9509
|
+
if (contractAction === "normalize") {
|
|
9510
|
+
const contractId = typeof parsed.flags.contract === "string" ? parsed.flags.contract : void 0;
|
|
9511
|
+
const dataRaw = typeof parsed.flags.data === "string" ? parsed.flags.data : void 0;
|
|
9512
|
+
if (!contractId || !dataRaw) {
|
|
9513
|
+
out(`${JSON.stringify({ ok: false, error: { code: "CONTRACT_NOT_FOUND", path: "/contract", message: "--contract and --data are required" } }, null, 2)}
|
|
9514
|
+
`);
|
|
9515
|
+
return 1;
|
|
9516
|
+
}
|
|
9517
|
+
let data;
|
|
9518
|
+
try {
|
|
9519
|
+
data = JSON.parse(dataRaw);
|
|
9520
|
+
} catch {
|
|
9521
|
+
out(`${JSON.stringify({ ok: false, error: { code: "SCHEMA_VALIDATION_FAILED", path: "/data", message: "--data is not valid JSON" } }, null, 2)}
|
|
9522
|
+
`);
|
|
9523
|
+
return 1;
|
|
9524
|
+
}
|
|
9525
|
+
let contract;
|
|
9526
|
+
try {
|
|
9527
|
+
contract = await resolveContract(contractId);
|
|
9528
|
+
} catch (e) {
|
|
9529
|
+
if (e instanceof ContractError && e.code === "AMBIGUOUS_REVISION") {
|
|
9530
|
+
out(`${JSON.stringify({ ok: false, error: { code: "AMBIGUOUS_REVISION", path: "/contract", message: e.message, details: e.details } }, null, 2)}
|
|
9531
|
+
`);
|
|
9532
|
+
return 1;
|
|
9533
|
+
}
|
|
9534
|
+
contract = void 0;
|
|
9535
|
+
}
|
|
9536
|
+
if (!contract) {
|
|
9537
|
+
out(`${JSON.stringify({ ok: false, error: { code: "CONTRACT_NOT_FOUND", path: "/contract", message: `Contract "${contractId}" not found` } }, null, 2)}
|
|
9538
|
+
`);
|
|
9539
|
+
return 1;
|
|
9540
|
+
}
|
|
9541
|
+
const { normalizeE2eLegacyArtifact: normalizeE2eLegacyArtifact2 } = await Promise.resolve().then(() => (init_contract_kernel(), contract_kernel_exports));
|
|
9542
|
+
const result = normalizeE2eLegacyArtifact2(data, contract);
|
|
9543
|
+
if (!result.success) {
|
|
9544
|
+
out(`${JSON.stringify({ ok: false, errors: result.errors }, null, 2)}
|
|
9545
|
+
`);
|
|
9546
|
+
return 1;
|
|
9547
|
+
}
|
|
9548
|
+
out(`${JSON.stringify({ ok: true, data: result }, null, 2)}
|
|
9549
|
+
`);
|
|
9550
|
+
return 0;
|
|
9551
|
+
}
|
|
9552
|
+
if (contractAction === "validate-markers") {
|
|
9553
|
+
const contractId = typeof parsed.flags.contract === "string" ? parsed.flags.contract : void 0;
|
|
9554
|
+
const markdownPath = typeof parsed.flags.markdown === "string" ? parsed.flags.markdown : void 0;
|
|
9555
|
+
if (!contractId || !markdownPath) {
|
|
9556
|
+
out(`${JSON.stringify({ ok: false, error: { code: "CONTRACT_NOT_FOUND", path: "/contract", message: "--contract and --markdown are required" } }, null, 2)}
|
|
9557
|
+
`);
|
|
9558
|
+
return 1;
|
|
9559
|
+
}
|
|
9560
|
+
let contract;
|
|
9561
|
+
try {
|
|
9562
|
+
contract = await resolveContract(contractId);
|
|
9563
|
+
} catch (e) {
|
|
9564
|
+
if (e instanceof ContractError && e.code === "AMBIGUOUS_REVISION") {
|
|
9565
|
+
out(`${JSON.stringify({ ok: false, error: { code: "AMBIGUOUS_REVISION", path: "/contract", message: e.message, details: e.details } }, null, 2)}
|
|
9566
|
+
`);
|
|
9567
|
+
return 1;
|
|
9568
|
+
}
|
|
9569
|
+
contract = void 0;
|
|
9570
|
+
}
|
|
9571
|
+
if (!contract) {
|
|
9572
|
+
out(`${JSON.stringify({ ok: false, error: { code: "CONTRACT_NOT_FOUND", path: "/contract", message: `Contract "${contractId}" not found` } }, null, 2)}
|
|
9573
|
+
`);
|
|
9574
|
+
return 1;
|
|
9575
|
+
}
|
|
9576
|
+
let markdownContent;
|
|
9577
|
+
try {
|
|
9578
|
+
markdownContent = await readFile4(markdownPath, "utf-8");
|
|
9579
|
+
} catch {
|
|
9580
|
+
out(`${JSON.stringify({ ok: false, error: { code: "SCHEMA_VALIDATION_FAILED", path: "/markdown", message: `Could not read markdown file "${markdownPath}"` } }, null, 2)}
|
|
9581
|
+
`);
|
|
9582
|
+
return 1;
|
|
9583
|
+
}
|
|
9584
|
+
const { validateSemanticMarkers: validateSemanticMarkers2 } = await Promise.resolve().then(() => (init_contract_kernel(), contract_kernel_exports));
|
|
9585
|
+
const result = validateSemanticMarkers2(markdownContent, contract);
|
|
9586
|
+
if (!result.valid) {
|
|
9587
|
+
out(`${JSON.stringify({ ok: false, errors: result.errors.map((e) => ({ code: "MARKER_VALIDATION_FAILED", path: "/", message: e })) }, null, 2)}
|
|
9588
|
+
`);
|
|
9589
|
+
return 1;
|
|
9590
|
+
}
|
|
9591
|
+
out(`${JSON.stringify({ ok: true, data: { valid: true, issues: [] } }, null, 2)}
|
|
9592
|
+
`);
|
|
9593
|
+
return 0;
|
|
9594
|
+
}
|
|
9595
|
+
out(`${JSON.stringify({ ok: false, error: { code: "INVALID_COMMAND", path: "/command", message: "Usage: artifact-graph contract list|explain|validate|normalize|check-policy|validate-markers [options]" } }, null, 2)}
|
|
9596
|
+
`);
|
|
9597
|
+
return 1;
|
|
9598
|
+
}
|
|
8282
9599
|
default:
|
|
8283
9600
|
err(helpText());
|
|
8284
9601
|
return 1;
|
|
8285
9602
|
}
|
|
8286
9603
|
} catch (error) {
|
|
9604
|
+
if (parsed.command === "contract") {
|
|
9605
|
+
const contractError = error;
|
|
9606
|
+
out(`${JSON.stringify({ ok: false, error: {
|
|
9607
|
+
code: contractError.code ?? "CONTRACT_INTERNAL_ERROR",
|
|
9608
|
+
path: "/contract",
|
|
9609
|
+
message: contractError.message,
|
|
9610
|
+
...contractError.details ? { details: contractError.details } : {}
|
|
9611
|
+
} }, null, 2)}
|
|
9612
|
+
`);
|
|
9613
|
+
return 1;
|
|
9614
|
+
}
|
|
8287
9615
|
err(`${error.message}
|
|
8288
9616
|
`);
|
|
8289
9617
|
return 1;
|
|
@@ -8296,7 +9624,7 @@ function isGraphRelevantPath(path) {
|
|
|
8296
9624
|
return path === "artifact-graph.config.yaml" || path === VERSION_LOCK_PATH || path.startsWith("artifacts/") || /\.(md|mdx|json|ya?ml|ts|tsx|js|jsx|mts|cts|rs|py|go)$/.test(path);
|
|
8297
9625
|
}
|
|
8298
9626
|
async function initConfig(root) {
|
|
8299
|
-
const configPath =
|
|
9627
|
+
const configPath = join8(root, "artifact-graph.config.yaml");
|
|
8300
9628
|
try {
|
|
8301
9629
|
await access2(configPath);
|
|
8302
9630
|
throw new Error(`Config already exists: ${configPath}`);
|
|
@@ -8360,6 +9688,12 @@ Commands:
|
|
|
8360
9688
|
doctor [--format json|markdown]
|
|
8361
9689
|
validate-review-result --file <path> [--format json]
|
|
8362
9690
|
generate-e2e-registry [--deterministic] [--out <path>] [--check]
|
|
9691
|
+
contract list [--contracts-dir <path>] [--format json]
|
|
9692
|
+
contract explain --contract <major-id> [--contracts-dir <path>] [--format json]
|
|
9693
|
+
contract validate --contract <major-id> --data <json> [--contracts-dir <path>] [--format json]
|
|
9694
|
+
contract normalize --contract <major-id> --data <json> [--format json]
|
|
9695
|
+
contract check-policy --contract <major-id> --policy <json> [--contracts-dir <path>] [--format json]
|
|
9696
|
+
contract validate-markers --contract <major-id> --markdown <path> [--contracts-dir <path>] [--format json]
|
|
8363
9697
|
`;
|
|
8364
9698
|
}
|
|
8365
9699
|
function isCliEntrypoint(argvPath) {
|
|
@@ -8372,11 +9706,30 @@ function isCliEntrypoint(argvPath) {
|
|
|
8372
9706
|
return false;
|
|
8373
9707
|
}
|
|
8374
9708
|
}
|
|
8375
|
-
|
|
8376
|
-
|
|
8377
|
-
|
|
8378
|
-
|
|
8379
|
-
|
|
9709
|
+
var init_cli = __esm({
|
|
9710
|
+
"src/cli.ts"() {
|
|
9711
|
+
init_index();
|
|
9712
|
+
init_contract_kernel();
|
|
9713
|
+
init_target_selector();
|
|
9714
|
+
init_packet_audit();
|
|
9715
|
+
init_packet_validator();
|
|
9716
|
+
init_packet_prompt();
|
|
9717
|
+
init_packet_prompt_validator();
|
|
9718
|
+
init_packet_prompt_audit();
|
|
9719
|
+
init_cli_resolver();
|
|
9720
|
+
init_review_result_validator();
|
|
9721
|
+
init_git_changes();
|
|
9722
|
+
init_git_hook_path();
|
|
9723
|
+
init_hook_installer();
|
|
9724
|
+
init_versioned_traceability();
|
|
9725
|
+
if (isCliEntrypoint(process.argv[1])) {
|
|
9726
|
+
void runCli(process.argv.slice(2)).then((code) => {
|
|
9727
|
+
process.exitCode = code;
|
|
9728
|
+
});
|
|
9729
|
+
}
|
|
9730
|
+
}
|
|
9731
|
+
});
|
|
9732
|
+
init_cli();
|
|
8380
9733
|
export {
|
|
8381
9734
|
runCli
|
|
8382
9735
|
};
|