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