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/dist/cli.js CHANGED
@@ -1,57 +1,55 @@
1
1
  #!/usr/bin/env node
2
-
3
- // src/cli.ts
4
- import yaml2 from "js-yaml";
5
- import { realpathSync } from "fs";
6
- import { access as access2, mkdir as mkdir6, readFile as readFile3, writeFile as writeFile5 } from "fs/promises";
7
- import { isAbsolute as isAbsolute4, join as join7 } from "path";
8
- import { fileURLToPath } from "url";
9
-
10
- // src/index.ts
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
- { path: "AGENTS.md", reason: "project agent instructions" },
21
- { path: "CLAUDE.md", reason: "project instructions" },
22
- { path: "artifacts/artifact-chain-spec.md", reason: "artifact chain specification" },
23
- { path: "artifacts/blueprints/generation-packet-spec.md", reason: "generation packet spec" },
24
- { path: "artifacts/blueprints/implementation-blueprint.md", reason: "implementation blueprint" },
25
- { path: "artifacts/contracts/interface-contracts.md", reason: "interface contracts" },
26
- { path: "artifacts/contracts/data-contracts.md", reason: "data contracts" },
27
- { path: "artifacts/contracts/application-state-machines.md", reason: "application state machines" },
28
- { path: "artifacts/contracts/error-model.md", reason: "error model" },
29
- { path: "artifacts/contracts/report-contracts.md", reason: "report contracts" },
30
- { path: "artifacts/contracts/ui-flow-contracts.md", reason: "UI flow contracts" },
31
- { path: "artifacts/contracts/non-functional-budgets.md", reason: "non-functional budgets" },
32
- { path: "artifacts/domain/domain-glossary.md", reason: "domain glossary" },
33
- { path: "artifacts/domain/bounded-context-map.md", reason: "bounded context map" },
34
- { path: "artifacts/domain/domain-invariants.md", reason: "domain invariants" },
35
- { path: "artifacts/tests/rule-golden-cases.md", reason: "rule golden cases" },
36
- { path: "artifacts/tests/verification-fixtures.md", reason: "verification fixtures" },
37
- { path: "artifacts/design/test-strategy.md", reason: "test strategy" },
38
- { path: "artifacts/traceability-matrix-v2.md", reason: "traceability matrix" }
39
- ];
40
- var BASELINE_ITEMS_COUNT = ALWAYS_PRESENT_ITEMS.length;
41
- var BASELINE_CONSTRAINTS = [
42
- { id: "C-ARCH-01", description: "\u6280\u672F\u6808\u5FC5\u987B\u4E3A TypeScript \u5168\u6808", source: "artifacts/decisions/D-ARCH-01.md" },
43
- { id: "C-ARCH-03", description: "\u4E0D\u53EF\u5728 Quick Check \u8DEF\u5F84\u5F15\u5165 LLM", source: "artifacts/decisions/D-ARCH-03.md" },
44
- { 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" },
45
- { id: "C-RULE-01", description: "SEC severity \u4E0D\u5141\u8BB8\u964D\u7EA7", source: "artifacts/decisions/D-RULE-01.md" },
46
- { 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" },
47
- { 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" },
48
- { 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" }
49
- ];
50
- var BASELINE_CONSTRAINTS_COUNT = BASELINE_CONSTRAINTS.length;
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 changedPaths = normalizeGitPathList(stdout);
2285
- const unstagedPaths = options.mode === "staged" ? await collectUnstagedPaths(root) : [];
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
- var TARGET_ARTIFACT_TYPES = VALID_PACKET_TARGET_TYPES;
3084
- var NON_TARGET_ROLES = ["context", "candidate", "not-recommended"];
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 = join5(root, "artifact-graph.config.yaml");
4156
+ const configPath = join6(root, "artifact-graph.config.yaml");
3172
4157
  let parsed = {};
3173
4158
  try {
3174
- const raw = await readFile2(configPath, "utf-8");
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 readFile2(join5(root, file), "utf-8");
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 readFile2(join5(root, indexPath), "utf-8");
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 = join5(root, ".artifact-graph");
4835
+ const cacheDir = join6(root, ".artifact-graph");
3851
4836
  await mkdir4(cacheDir, { recursive: true });
3852
- await writeFile3(join5(cacheDir, "index.json"), `${JSON.stringify(graph, null, 2)}
4837
+ await writeFile3(join6(cacheDir, "index.json"), `${JSON.stringify(graph, null, 2)}
3853
4838
  `);
3854
- const db = new Database(join5(cacheDir, "graph.sqlite"));
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 = basename2(normalized);
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 ?? basename2(path, extname(path))).trim();
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: basename2(path, extname(path)), title: "E2E Test Registry", path, line: 1, attrs: data }],
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 = join5(root, "artifacts", "tests", "e2e");
6522
+ const e2eDir = join6(root, "artifacts", "tests", "e2e");
5538
6523
  let e2eFiles;
5539
6524
  try {
5540
- e2eFiles = (await readdir(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join5(e2eDir, name));
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 readFile2(filePath, "utf-8");
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 ?? basename2(filePath, extname(filePath))).trim();
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 = join5(root, specFile);
6583
+ const fullSpecPath = join6(root, specFile);
5599
6584
  let content;
5600
6585
  try {
5601
- content = await readFile2(fullSpecPath, "utf-8");
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 readFile2(join5(root, normalizedRefFile), "utf-8");
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 = join5(root, "artifacts", "tests", "e2e");
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 readdir(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join5(e2eDir, name));
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 readFile2(filePath, "utf-8");
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 ?? basename2(filePath, extname(filePath))).trim();
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(join5(root, normalized))) continue;
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 readFile2(join5(root, node.path), "utf-8"));
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 = join5(root, "artifacts", "tests", "e2e");
7023
+ const e2eDir = join6(root, "artifacts", "tests", "e2e");
6039
7024
  let files;
6040
7025
  try {
6041
- files = (await readdir(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).sort();
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 = join5(e2eDir, file);
6055
- const raw = await readFile2(filePath, "utf-8");
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 ?? basename2(file, extname(file))).trim();
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 = join5(root, normalizedPath);
7167
+ const fullPath = join6(root, normalizedPath);
6197
7168
  let content;
6198
7169
  try {
6199
- content = await readFile2(fullPath, "utf-8");
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 = join5(e2eDir, `${batch}.md`);
7279
+ const filePath = join6(e2eDir, `${batch}.md`);
6309
7280
  try {
6310
- const raw = await readFile2(filePath, "utf-8");
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 readdir(current, { withFileTypes: true });
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 = join5(current, entry.name);
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 basename2(raw, extname(raw));
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 = join5(root, ap.path);
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 join6 } from "path";
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 = join6(options.outDir, filename);
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 = join6(options.outDir, "prompt-audit-summary.json");
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 = join6(options.outDir, "prompt-audit-summary.md");
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 ${join7(root, "artifact-graph.config.yaml")}
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 readFile3(packetJsonPath, "utf-8");
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 readFile3(targetsFile, "utf-8");
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 readFile3(ppaTargetsFile, "utf-8");
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 readFile3(templatePath, "utf-8");
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 : join7(root, filePath);
9289
+ const resolvedPath = isAbsolute4(filePath) ? filePath : join8(root, filePath);
8217
9290
  let content;
8218
9291
  try {
8219
- content = await readFile3(resolvedPath, "utf-8");
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 : join7(root, "artifacts/tests/e2e/e2e-test-registry.json");
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 readFile3(outPath, "utf-8");
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 = join7(root, "artifact-graph.config.yaml");
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
- if (isCliEntrypoint(process.argv[1])) {
8376
- void runCli(process.argv.slice(2)).then((code) => {
8377
- process.exitCode = code;
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
  };