artifact-graph 0.4.0 → 0.5.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.cjs CHANGED
@@ -99,6 +99,7 @@ module.exports = __toCommonJS(index_exports);
99
99
  var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
100
100
  var import_gray_matter = __toESM(require("gray-matter"), 1);
101
101
  var import_js_yaml = __toESM(require("js-yaml"), 1);
102
+ var import_node_fs3 = require("fs");
102
103
  var import_promises5 = require("fs/promises");
103
104
  var import_node_path6 = require("path");
104
105
 
@@ -179,13 +180,76 @@ function validatePacket(packet, schema) {
179
180
  path: "target.id"
180
181
  });
181
182
  }
182
- if (packet.requiredBaseline.total !== BASELINE_ITEMS_COUNT) {
183
- issues.push({
184
- severity: "error",
185
- code: "PKT-004",
186
- message: `requiredBaseline.total must be ${BASELINE_ITEMS_COUNT}, got ${packet.requiredBaseline.total}`,
187
- path: "requiredBaseline.total"
188
- });
183
+ const isBaselineExplicitlyDisabled = packet.baselinePolicy === false;
184
+ const expectedPaths = new Set(ALWAYS_PRESENT_ITEMS.map((item) => item.path));
185
+ if (isBaselineExplicitlyDisabled) {
186
+ if (packet.requiredBaseline.total !== 0) {
187
+ issues.push({
188
+ severity: "error",
189
+ code: "PKT-004",
190
+ message: `baselinePolicy=false requires requiredBaseline.total=0, got ${packet.requiredBaseline.total}`,
191
+ path: "requiredBaseline.total"
192
+ });
193
+ }
194
+ if (packet.requiredBaseline.items.length !== 0) {
195
+ issues.push({
196
+ severity: "error",
197
+ code: "PKT-004",
198
+ message: `baselinePolicy=false requires requiredBaseline.items=[], got ${packet.requiredBaseline.items.length} item(s)`,
199
+ path: "requiredBaseline.items"
200
+ });
201
+ }
202
+ } else {
203
+ if (packet.requiredBaseline.total !== BASELINE_ITEMS_COUNT) {
204
+ issues.push({
205
+ severity: "error",
206
+ code: "PKT-004",
207
+ message: `requiredBaseline.total must be ${BASELINE_ITEMS_COUNT}, got ${packet.requiredBaseline.total}`,
208
+ path: "requiredBaseline.total"
209
+ });
210
+ }
211
+ if (packet.requiredBaseline.items.length !== BASELINE_ITEMS_COUNT) {
212
+ issues.push({
213
+ severity: "error",
214
+ code: "PKT-004",
215
+ message: `requiredBaseline.items.length must be ${BASELINE_ITEMS_COUNT}, got ${packet.requiredBaseline.items.length}`,
216
+ path: "requiredBaseline.items"
217
+ });
218
+ }
219
+ const actualPaths = packet.requiredBaseline.items.map((item) => item.path);
220
+ const actualPathSet = new Set(actualPaths);
221
+ if (actualPathSet.size !== actualPaths.length) {
222
+ issues.push({
223
+ severity: "error",
224
+ code: "PKT-004",
225
+ message: `requiredBaseline.items contains duplicate paths (${actualPaths.length} items, ${actualPathSet.size} unique)`,
226
+ path: "requiredBaseline.items"
227
+ });
228
+ }
229
+ const missingPaths = [];
230
+ for (const ep of expectedPaths) {
231
+ if (!actualPathSet.has(ep)) missingPaths.push(ep);
232
+ }
233
+ if (missingPaths.length > 0) {
234
+ issues.push({
235
+ severity: "error",
236
+ code: "PKT-004",
237
+ message: `requiredBaseline.items missing expected path(s): ${missingPaths.join(", ")}`,
238
+ path: "requiredBaseline.items"
239
+ });
240
+ }
241
+ const extraPaths = [];
242
+ for (const ap of actualPathSet) {
243
+ if (!expectedPaths.has(ap)) extraPaths.push(ap);
244
+ }
245
+ if (extraPaths.length > 0) {
246
+ issues.push({
247
+ severity: "error",
248
+ code: "PKT-004",
249
+ message: `requiredBaseline.items contains unexpected path(s): ${extraPaths.join(", ")}`,
250
+ path: "requiredBaseline.items"
251
+ });
252
+ }
189
253
  }
190
254
  const constraints = packet.implementationBlueprintDraft.constraints;
191
255
  if (constraints.length !== BASELINE_CONSTRAINTS_COUNT) {
@@ -612,7 +676,10 @@ function assemblePacket(manifest, options) {
612
676
  missing: [...manifest.missing],
613
677
  missingDetails: manifest.missingDetails ? [...manifest.missingDetails] : void 0,
614
678
  implementationBlueprintDraft: blueprintDraft,
615
- validationCommands
679
+ validationCommands,
680
+ // @feature ACA17
681
+ // @decision D-ACA-17
682
+ baselinePolicy: manifest.baselinePolicy
616
683
  };
617
684
  return packet;
618
685
  }
@@ -833,7 +900,9 @@ async function auditSingleTarget(target, graph, options) {
833
900
  const manifest = resolveArtifactContext(graph, {
834
901
  target: { type: target.type, id: target.id },
835
902
  mode: options.mode,
836
- maxPerCategory: options.maxPerCategory
903
+ maxPerCategory: options.maxPerCategory,
904
+ universalBaseline: options.universalBaseline,
905
+ root: options.root
837
906
  });
838
907
  const packet = assemblePacket(manifest, {
839
908
  mode: options.mode,
@@ -970,6 +1039,7 @@ async function discoverAndAuditPackets(root, options) {
970
1039
  type: d.type,
971
1040
  id: d.id
972
1041
  }));
1042
+ const effectiveBaseline = options.universalBaseline ?? config.context?.universal_baseline;
973
1043
  return auditPackets(root, targets, {
974
1044
  root,
975
1045
  outDir: options.outDir,
@@ -979,7 +1049,8 @@ async function discoverAndAuditPackets(root, options) {
979
1049
  summaryOnly: options.summaryOnly,
980
1050
  sampleTargets: options.sampleTargets,
981
1051
  summaryDetail: options.summaryDetail,
982
- schema: config
1052
+ schema: config,
1053
+ universalBaseline: effectiveBaseline
983
1054
  }, graph);
984
1055
  }
985
1056
 
@@ -2694,12 +2765,35 @@ var VALID_DECISIONS = /* @__PURE__ */ new Set([
2694
2765
  var VALID_SEVERITIES = /* @__PURE__ */ new Set(["block", "warn", "info"]);
2695
2766
  var VALID_FINDING_STATUSES = /* @__PURE__ */ new Set(["open", "resolved", "accepted", "superseded"]);
2696
2767
  var VALID_EXECUTORS = /* @__PURE__ */ new Set(["script", "worker", "agent", "manual", "cli"]);
2768
+ var TOP_LEVEL_FIELDS = /* @__PURE__ */ new Set([
2769
+ "schema_version",
2770
+ "run_id",
2771
+ "stage_id",
2772
+ "attempt",
2773
+ "status",
2774
+ "decision",
2775
+ "summary",
2776
+ "outputs",
2777
+ "warnings",
2778
+ "blocking_reason",
2779
+ "degradation",
2780
+ "producer",
2781
+ "acceptance",
2782
+ "evidence",
2783
+ "review",
2784
+ "repair"
2785
+ ]);
2697
2786
  function validateReviewResult(input) {
2698
2787
  const errors = [];
2699
2788
  if (!input || typeof input !== "object" || Array.isArray(input)) {
2700
2789
  return [{ path: "$", message: "Root must be a non-null object" }];
2701
2790
  }
2702
2791
  const obj = input;
2792
+ for (const key of Object.keys(obj)) {
2793
+ if (!TOP_LEVEL_FIELDS.has(key)) {
2794
+ errors.push({ path: `$.${key}`, message: "Unknown top-level property" });
2795
+ }
2796
+ }
2703
2797
  if (obj.schema_version !== "1.0") {
2704
2798
  errors.push({ path: "$.schema_version", message: `Must be "1.0", got ${JSON.stringify(obj.schema_version)}` });
2705
2799
  }
@@ -2718,8 +2812,8 @@ function validateReviewResult(input) {
2718
2812
  if (obj.stage_id !== void 0 && typeof obj.stage_id !== "string") {
2719
2813
  errors.push({ path: "$.stage_id", message: "Must be a string if present" });
2720
2814
  }
2721
- if (obj.attempt !== void 0 && (!Number.isInteger(obj.attempt) || obj.attempt < 1)) {
2722
- errors.push({ path: "$.attempt", message: "Must be a positive integer if present" });
2815
+ if (obj.attempt !== void 0 && (!Number.isInteger(obj.attempt) || obj.attempt < 1 || obj.attempt > 3)) {
2816
+ errors.push({ path: "$.attempt", message: "Must be an integer from 1 through 3 if present" });
2723
2817
  }
2724
2818
  if (obj.outputs !== void 0) {
2725
2819
  checkStringArray(obj.outputs, "$.outputs", errors);
@@ -2730,20 +2824,14 @@ function validateReviewResult(input) {
2730
2824
  checkOptionalNullableString(obj.blocking_reason, "$.blocking_reason", errors);
2731
2825
  checkOptionalNullableString(obj.degradation, "$.degradation", errors);
2732
2826
  if (obj.producer !== void 0) {
2733
- if (!isPlainObject(obj.producer)) {
2734
- errors.push({ path: "$.producer", message: "Must be an object" });
2735
- } else {
2736
- const p = obj.producer;
2737
- if (typeof p.executor !== "string") {
2738
- errors.push({ path: "$.producer.executor", message: "Must be a string" });
2739
- } else if (!VALID_EXECUTORS.has(p.executor)) {
2740
- errors.push({ path: "$.producer.executor", message: `Must be one of ${[...VALID_EXECUTORS].join(", ")}; got ${JSON.stringify(p.executor)}` });
2741
- }
2742
- if (typeof p.name !== "string") {
2743
- errors.push({ path: "$.producer.name", message: "Must be a string" });
2744
- }
2745
- checkOptionalString(p.skill, "$.producer.skill", errors);
2746
- }
2827
+ validateProducer(obj.producer, "$.producer", errors);
2828
+ }
2829
+ const successfulAcceptance = obj.status === "SUCCEEDED" && (obj.decision === "PASS" || obj.decision === "PASS_WITH_RESIDUAL_MINOR");
2830
+ if (successfulAcceptance && obj.producer === void 0) {
2831
+ errors.push({ path: "$.producer", message: "Successful acceptance requires producer identity" });
2832
+ }
2833
+ if (obj.acceptance !== void 0) {
2834
+ validateAcceptance(obj.acceptance, obj.producer, "$.acceptance", errors);
2747
2835
  }
2748
2836
  if (obj.evidence !== void 0) {
2749
2837
  if (!Array.isArray(obj.evidence)) {
@@ -2769,6 +2857,17 @@ function validateReviewResult(input) {
2769
2857
  }
2770
2858
  if (obj.review !== void 0) {
2771
2859
  validateReviewData(obj.review, "$.review", errors);
2860
+ if (obj.decision === "PASS" || obj.decision === "PASS_WITH_RESIDUAL_MINOR") {
2861
+ const findings = isPlainObject(obj.review) && Array.isArray(obj.review.findings) ? obj.review.findings : [];
2862
+ findings.forEach((finding, index) => {
2863
+ if (isPlainObject(finding) && finding.severity === "block" && (finding.status === void 0 || finding.status === "open")) {
2864
+ errors.push({
2865
+ path: `$.review.findings[${index}]`,
2866
+ message: `${obj.decision} cannot contain an open block finding`
2867
+ });
2868
+ }
2869
+ });
2870
+ }
2772
2871
  }
2773
2872
  if (obj.repair !== void 0) {
2774
2873
  if (!isPlainObject(obj.repair)) {
@@ -2804,6 +2903,47 @@ function checkStringArray(val, path, errors) {
2804
2903
  function isPlainObject(val) {
2805
2904
  return typeof val === "object" && val !== null && !Array.isArray(val);
2806
2905
  }
2906
+ function validateProducer(val, path, errors) {
2907
+ if (!isPlainObject(val)) {
2908
+ errors.push({ path, message: "Must be an object" });
2909
+ return;
2910
+ }
2911
+ if (typeof val.executor !== "string") {
2912
+ errors.push({ path: `${path}.executor`, message: "Must be a string" });
2913
+ } else if (!VALID_EXECUTORS.has(val.executor)) {
2914
+ errors.push({ path: `${path}.executor`, message: `Must be one of ${[...VALID_EXECUTORS].join(", ")}; got ${JSON.stringify(val.executor)}` });
2915
+ }
2916
+ if (typeof val.name !== "string" || val.name.length === 0) {
2917
+ errors.push({ path: `${path}.name`, message: "Must be a non-empty string" });
2918
+ }
2919
+ checkOptionalString(val.skill, `${path}.skill`, errors);
2920
+ }
2921
+ function producerIdentity(val) {
2922
+ return JSON.stringify([val.executor, val.name]);
2923
+ }
2924
+ function validateAcceptance(val, resultProducer, path, errors) {
2925
+ if (!isPlainObject(val)) {
2926
+ errors.push({ path, message: "Must be an object" });
2927
+ return;
2928
+ }
2929
+ validateProducer(val.reviewer, `${path}.reviewer`, errors);
2930
+ if (!isPlainObject(val.source_result)) {
2931
+ errors.push({ path: `${path}.source_result`, message: "Must be an object" });
2932
+ return;
2933
+ }
2934
+ const source = val.source_result;
2935
+ if (typeof source.run_id !== "string" || source.run_id.length === 0) {
2936
+ errors.push({ path: `${path}.source_result.run_id`, message: "Must be a non-empty string" });
2937
+ }
2938
+ checkOptionalString(source.stage_id, `${path}.source_result.stage_id`, errors);
2939
+ validateProducer(source.producer, `${path}.source_result.producer`, errors);
2940
+ if (isPlainObject(val.reviewer) && isPlainObject(resultProducer) && producerIdentity(val.reviewer) !== producerIdentity(resultProducer)) {
2941
+ errors.push({ path: `${path}.reviewer`, message: "Acceptance reviewer must match the result producer" });
2942
+ }
2943
+ if (isPlainObject(val.reviewer) && isPlainObject(source.producer) && producerIdentity(val.reviewer) === producerIdentity(source.producer)) {
2944
+ errors.push({ path: `${path}.reviewer`, message: "Repair producer cannot accept its own result" });
2945
+ }
2946
+ }
2807
2947
  function checkOptionalString(val, path, errors) {
2808
2948
  if (val !== void 0 && typeof val !== "string") {
2809
2949
  errors.push({ path, message: "Must be a string if present" });
@@ -2972,7 +3112,7 @@ var DEFAULT_SCHEMA = {
2972
3112
  scenario: { paths: ["artifacts/scenarios/**/*.md"], displayName: "\u573A\u666F\u5267\u672C", role: "scenario", layer: "scenario", aliases: ["scenarios", "scenario-script"] },
2973
3113
  design: { paths: ["artifacts/design/**/*.md"], displayName: "\u8BBE\u8BA1\u89C4\u683C", role: "design", layer: "design", aliases: ["design-spec", "design_docs"] },
2974
3114
  test: { paths: ["heimdall/packages/**/*.test.ts"], displayName: "\u4EE3\u7801\u6CE8\u91CA\u8FFD\u6EAF", role: "context", layer: "implementation", aliases: ["code-test", "code-trace", "unit-test"] },
2975
- e2e_test: { paths: ["artifacts/tests/e2e/*.md"], displayName: "E2E \u6D4B\u8BD5\u89C4\u683C", role: "e2e_test", layer: "verification", aliases: ["e2e-test", "e2e_tests"] },
3115
+ e2e_test: { paths: ["artifacts/tests/e2e/*.md"], displayName: "E2E \u6D4B\u8BD5\u89C4\u683C", role: "e2e_test", layer: "verification", aliases: ["e2e-test", "e2e_tests", "tc"] },
2976
3116
  e2e_registry: { paths: ["artifacts/tests/e2e/e2e-test-registry.json"], displayName: "E2E \u6D4B\u8BD5\u6CE8\u518C\u8868", role: "context", layer: "verification", aliases: ["e2e-registry"] },
2977
3117
  "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"] },
2978
3118
  "test-strategy": { paths: ["artifacts/design/test-strategy.md"], displayName: "\u6D4B\u8BD5\u7B56\u7565", role: "context", layer: "verification", aliases: ["test_strategy"] },
@@ -3015,6 +3155,12 @@ async function loadConfig(root) {
3015
3155
  throw error;
3016
3156
  }
3017
3157
  }
3158
+ const ub = parsed.context?.universal_baseline;
3159
+ if (ub !== void 0 && typeof ub !== "boolean") {
3160
+ throw new Error(
3161
+ `Invalid context.universal_baseline: ${JSON.stringify(ub)}. Must be boolean (true or false).`
3162
+ );
3163
+ }
3018
3164
  return {
3019
3165
  ...DEFAULT_SCHEMA,
3020
3166
  ...parsed,
@@ -3027,7 +3173,7 @@ async function loadConfig(root) {
3027
3173
  idRanges: mergeRecord(DEFAULT_SCHEMA.idRanges, parsed.idRanges)
3028
3174
  };
3029
3175
  }
3030
- function buildGraph(nodes, edges, diagnostics = []) {
3176
+ function buildGraph(nodes, edges, diagnostics = [], root) {
3031
3177
  const graphNodes = nodes.map((node) => ({ ...node, uid: toUid(node.type, node.code) }));
3032
3178
  graphNodes.sort(compareNode);
3033
3179
  edges.sort(compareEdge);
@@ -3044,6 +3190,7 @@ function buildGraph(nodes, edges, diagnostics = []) {
3044
3190
  nodes: graphNodes,
3045
3191
  edges: dedupedEdges,
3046
3192
  generatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
3193
+ ...root ? { root } : {},
3047
3194
  diagnostics: diagnostics.sort((left, right) => left.code.localeCompare(right.code) || left.path.localeCompare(right.path) || left.line - right.line)
3048
3195
  };
3049
3196
  }
@@ -3075,7 +3222,8 @@ async function scanArtifacts(root, schema) {
3075
3222
  scanDiagnostics.push(...parsed.diagnostics);
3076
3223
  }
3077
3224
  }
3078
- const graph = buildGraph(nodes, edges, scanDiagnostics);
3225
+ const absoluteRoot = (0, import_node_path6.isAbsolute)(root) ? root : (0, import_node_path6.resolve)(root);
3226
+ const graph = buildGraph(nodes, edges, scanDiagnostics, absoluteRoot);
3079
3227
  return resolveMatrixEdges(graph);
3080
3228
  }
3081
3229
  function artifactTypeEntriesBySpecificity(schema) {
@@ -3415,23 +3563,34 @@ async function validateScenarioPrdLinkIndex(root, graph) {
3415
3563
  function validateCodeCommentTraceabilityFormat(graph) {
3416
3564
  const issues = [];
3417
3565
  for (const node of graph.nodes) {
3418
- if (node.type !== "test") {
3566
+ if (node.type !== "test" && node.type !== "implementation") {
3419
3567
  continue;
3420
3568
  }
3421
3569
  const invalidComments = node.attrs?.invalidTraceabilityComments;
3422
- if (!Array.isArray(invalidComments)) {
3423
- continue;
3570
+ if (Array.isArray(invalidComments)) {
3571
+ for (const invalid of invalidComments) {
3572
+ const line = typeof invalid?.line === "number" ? invalid.line : node.line;
3573
+ const reason = typeof invalid?.reason === "string" ? invalid.reason : "traceability tags must use standalone // comments";
3574
+ issues.push(issue(
3575
+ "CODE_COMMENT_TRACEABILITY_FORMAT",
3576
+ `${reason}: ${typeof invalid?.text === "string" ? invalid.text : ""}`.trim(),
3577
+ node.path,
3578
+ line,
3579
+ { node: node.uid }
3580
+ ));
3581
+ }
3424
3582
  }
3425
- for (const invalid of invalidComments) {
3426
- const line = typeof invalid?.line === "number" ? invalid.line : node.line;
3427
- const reason = typeof invalid?.reason === "string" ? invalid.reason : "traceability tags must use standalone // comments";
3428
- issues.push(issue(
3429
- "CODE_COMMENT_TRACEABILITY_FORMAT",
3430
- `${reason}: ${typeof invalid?.text === "string" ? invalid.text : ""}`.trim(),
3431
- node.path,
3432
- line,
3433
- { node: node.uid }
3434
- ));
3583
+ const deprecatedComments = node.attrs?.deprecatedTraceabilityComments;
3584
+ if (Array.isArray(deprecatedComments)) {
3585
+ for (const deprecated of deprecatedComments) {
3586
+ issues.push(issue(
3587
+ "E2E-TRACE-007",
3588
+ "@tc is deprecated; use @e2e_test instead",
3589
+ node.path,
3590
+ typeof deprecated?.line === "number" ? deprecated.line : node.line,
3591
+ { node: node.uid, severity: "warning" }
3592
+ ));
3593
+ }
3435
3594
  }
3436
3595
  }
3437
3596
  return issues;
@@ -3913,13 +4072,16 @@ function parseTest(path, raw, schema = DEFAULT_SCHEMA) {
3913
4072
  const isTest = isTestFile(path);
3914
4073
  const nodeType = isTest ? "test" : "implementation";
3915
4074
  const edgeKind = isTest ? "verifies" : "implements";
4075
+ const attrs = {};
4076
+ if (traceabilityComments.invalid.length > 0) attrs.invalidTraceabilityComments = traceabilityComments.invalid;
4077
+ if (traceabilityComments.deprecated.length > 0) attrs.deprecatedTraceabilityComments = traceabilityComments.deprecated;
3916
4078
  const node = {
3917
4079
  type: nodeType,
3918
4080
  code: path,
3919
4081
  title: path.split("/").at(-1) ?? path,
3920
4082
  path,
3921
4083
  line: 1,
3922
- attrs: traceabilityComments.invalid.length > 0 ? { invalidTraceabilityComments: traceabilityComments.invalid } : {}
4084
+ attrs
3923
4085
  };
3924
4086
  let hasTags = false;
3925
4087
  for (const { tags, lineNumber } of traceabilityComments.canonical) {
@@ -3930,7 +4092,7 @@ function parseTest(path, raw, schema = DEFAULT_SCHEMA) {
3930
4092
  }
3931
4093
  }
3932
4094
  }
3933
- if (hasTags || traceabilityComments.invalid.length > 0) {
4095
+ if (hasTags || traceabilityComments.invalid.length > 0 || traceabilityComments.deprecated.length > 0) {
3934
4096
  nodes.push(node);
3935
4097
  }
3936
4098
  return { nodes, edges };
@@ -3938,6 +4100,7 @@ function parseTest(path, raw, schema = DEFAULT_SCHEMA) {
3938
4100
  function scanTraceabilityComments(raw, schema = DEFAULT_SCHEMA) {
3939
4101
  const canonical = [];
3940
4102
  const invalid = [];
4103
+ const deprecated = [];
3941
4104
  for (const comment of scanCodeComments(raw)) {
3942
4105
  if (!containsTraceabilityTag(comment.text, schema)) {
3943
4106
  continue;
@@ -3953,6 +4116,9 @@ function scanTraceabilityComments(raw, schema = DEFAULT_SCHEMA) {
3953
4116
  const parsed = parseTraceabilityTagLine(comment.text.trim(), schema);
3954
4117
  if (parsed.valid) {
3955
4118
  canonical.push({ tags: parsed.tags, lineNumber: comment.lineNumber });
4119
+ if (/(?:^|\s)@tc(?=\s|$)/.test(comment.text.trim())) {
4120
+ deprecated.push({ line: comment.lineNumber, text: comment.text.trim() });
4121
+ }
3956
4122
  } else {
3957
4123
  invalid.push({ line: comment.lineNumber, text: comment.text.trim(), reason: parsed.reason });
3958
4124
  }
@@ -3968,11 +4134,14 @@ function scanTraceabilityComments(raw, schema = DEFAULT_SCHEMA) {
3968
4134
  const parsed = parseTraceabilityTagLine(comment.text.trim(), schema);
3969
4135
  if (parsed.valid) {
3970
4136
  canonical.push({ tags: parsed.tags, lineNumber: comment.lineNumber });
4137
+ if (/(?:^|\s)@tc(?=\s|$)/.test(comment.text.trim())) {
4138
+ deprecated.push({ line: comment.lineNumber, text: comment.text.trim() });
4139
+ }
3971
4140
  } else {
3972
4141
  invalid.push({ line: comment.lineNumber, text: comment.text.trim(), reason: parsed.reason });
3973
4142
  }
3974
4143
  }
3975
- return { canonical, invalid };
4144
+ return { canonical, invalid, deprecated };
3976
4145
  }
3977
4146
  function scanCodeComments(raw) {
3978
4147
  const comments = [];
@@ -4139,7 +4308,14 @@ function expandCodeRange(value) {
4139
4308
  return Array.from({ length: end - start + 1 }, (_, index) => `${prefix}${String(start + index).padStart(width, "0")}`);
4140
4309
  }
4141
4310
  function containsTraceabilityTag(value, schema = DEFAULT_SCHEMA) {
4142
- return /@[\w][\w-]*\b/.test(value);
4311
+ const tokens = /* @__PURE__ */ new Set();
4312
+ for (const [type, definition] of Object.entries(schema.types)) {
4313
+ tokens.add(type);
4314
+ for (const alias of definition.aliases ?? []) tokens.add(alias);
4315
+ }
4316
+ if (tokens.size === 0) return false;
4317
+ const alternatives = [...tokens].sort((a, b) => b.length - a.length).map((token) => token.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&")).join("|");
4318
+ return new RegExp(`(?:^|\\s)@(?:${alternatives})(?=\\s|$)`).test(value);
4143
4319
  }
4144
4320
  function parseDesign(path, raw) {
4145
4321
  const parsed = (0, import_gray_matter.default)(raw);
@@ -5256,8 +5432,8 @@ async function validateExecutableTraceability(root) {
5256
5432
  }
5257
5433
  }
5258
5434
  const refToSource = /* @__PURE__ */ new Map();
5259
- const tcAnnotationRegex = /\/\/!?\s*@tc\s+(\S+?)\s+\[(\w+)\]/;
5260
- const tcAnnotationNoLevelRegex = /\/\/!?\s*@tc\s+(\S+)/;
5435
+ const tcAnnotationRegex = /\/\/!?\s*@(?:e2e_test|tc)\s+(\S+?)\s+\[(\w+)\]/;
5436
+ const tcAnnotationNoLevelRegex = /\/\/!?\s*@(?:e2e_test|tc)\s+(\S+)/;
5261
5437
  for (const specFile of specFiles) {
5262
5438
  const fullSpecPath = (0, import_node_path6.join)(root, specFile);
5263
5439
  let content;
@@ -5346,7 +5522,7 @@ async function validateExecutableTraceability(root) {
5346
5522
  return normalizedAnnFile === normalizedRefFile;
5347
5523
  }) ?? false;
5348
5524
  if (!hasAnnotationInFile) {
5349
- issues.push(issue("E2E-TRACE-003", `executable_ref target ${entry.file} has no // @tc ${tcKey} line-comment annotation`, path, line, { node: tcKey, severity: "warning" }));
5525
+ issues.push(issue("E2E-TRACE-003", `executable_ref target ${entry.file} has no E2E trace annotation ${tcKey}`, path, line, { node: tcKey, severity: "warning" }));
5350
5526
  continue;
5351
5527
  }
5352
5528
  validFiles.push(normalizedRefFile);
@@ -5357,13 +5533,13 @@ async function validateExecutableTraceability(root) {
5357
5533
  const batch = tcKey.split(":")[0];
5358
5534
  if (!mdBatches.has(batch)) {
5359
5535
  for (const ann of annotations) {
5360
- issues.push(issue("E2E-TRACE-002", `@tc annotation ${tcKey} references non-existent E2E batch "${batch}"`, ann.file, ann.line, { node: tcKey, severity: "warning" }));
5536
+ issues.push(issue("E2E-TRACE-002", `E2E trace annotation ${tcKey} references non-existent E2E batch "${batch}"`, ann.file, ann.line, { node: tcKey, severity: "warning" }));
5361
5537
  }
5362
5538
  continue;
5363
5539
  }
5364
5540
  if (!mdToRef.has(tcKey) && !await hasMarkdownTc(tcKey, e2eDir)) {
5365
5541
  for (const ann of annotations) {
5366
- issues.push(issue("E2E-TRACE-002", `@tc annotation ${tcKey} references non-existent Markdown TC`, ann.file, ann.line, { node: tcKey, severity: "warning" }));
5542
+ issues.push(issue("E2E-TRACE-002", `E2E trace annotation ${tcKey} references non-existent Markdown TC`, ann.file, ann.line, { node: tcKey, severity: "warning" }));
5367
5543
  }
5368
5544
  }
5369
5545
  }
@@ -5385,7 +5561,7 @@ async function validateExecutableTraceability(root) {
5385
5561
  const primaryRef = refEntries[0];
5386
5562
  const normalizedPrimary = primaryRef?.file.startsWith("heimdall/") ? primaryRef?.file : `heimdall/${primaryRef?.file ?? ""}`;
5387
5563
  const detail = `file: MD refs=[${refEntries.map((e) => e.file).join(", ")}] vs source=${ann.file}`;
5388
- issues.push(issue("E2E-TRACE-003", `executable_ref \u2194 @tc mismatch for ${tcKey}: ${detail}`, path, line, { node: tcKey, severity: "warning" }));
5564
+ issues.push(issue("E2E-TRACE-003", `executable_ref \u2194 E2E trace annotation mismatch for ${tcKey}: ${detail}`, path, line, { node: tcKey, severity: "warning" }));
5389
5565
  }
5390
5566
  }
5391
5567
  for (const [tcKey, { chainType, path, line }] of mdToRef) {
@@ -5551,14 +5727,14 @@ async function validatePartialRustEvidence(tcFields, tcKey, root) {
5551
5727
  return { hasValidPartialRust: false, detail: `partial_rust file not found: ${ref.file}` };
5552
5728
  }
5553
5729
  const tcAnnotationPattern = new RegExp(
5554
- `//[/!]?\\s*@tc\\s+${escapeRegExp(tcKey)}\\s+\\[partial_rust\\]`
5730
+ `//[/!]?\\s*@(?:e2e_test|tc)\\s+${escapeRegExp(tcKey)}\\s+\\[partial_rust\\]`
5555
5731
  );
5556
5732
  if (!tcAnnotationPattern.test(content)) {
5557
- const noLevelPattern = new RegExp(`//[/!]?\\s*@tc\\s+${escapeRegExp(tcKey)}\\b`);
5733
+ const noLevelPattern = new RegExp(`//[/!]?\\s*@(?:e2e_test|tc)\\s+${escapeRegExp(tcKey)}\\b`);
5558
5734
  if (noLevelPattern.test(content)) {
5559
- return { hasValidPartialRust: false, detail: `partial_rust file ${ref.file} has @tc ${tcKey} but not tagged [partial_rust]` };
5735
+ return { hasValidPartialRust: false, detail: `partial_rust file ${ref.file} has E2E trace annotation ${tcKey} but not tagged [partial_rust]` };
5560
5736
  }
5561
- return { hasValidPartialRust: false, detail: `partial_rust file ${ref.file} has no @tc ${tcKey} annotation` };
5737
+ return { hasValidPartialRust: false, detail: `partial_rust file ${ref.file} has no E2E trace annotation ${tcKey}` };
5562
5738
  }
5563
5739
  }
5564
5740
  return { hasValidPartialRust: true, detail: "ok" };
@@ -5885,9 +6061,14 @@ function mergeRecord(base, override) {
5885
6061
  function mergeArtifactTypes(base, override) {
5886
6062
  const result = { ...base };
5887
6063
  for (const [type, definition] of Object.entries(override ?? {})) {
6064
+ const aliases = [
6065
+ ...base[type]?.aliases ?? [],
6066
+ ...definition.aliases ?? []
6067
+ ].filter((alias, index, all) => all.indexOf(alias) === index);
5888
6068
  result[type] = {
5889
6069
  ...base[type] ?? {},
5890
- ...definition
6070
+ ...definition,
6071
+ ...aliases.length > 0 ? { aliases } : {}
5891
6072
  };
5892
6073
  }
5893
6074
  return result;
@@ -5981,6 +6162,8 @@ var TIER_ORDER = ["baseline", "target", "direct", "matrix", "transitive"];
5981
6162
  function resolveArtifactContext(graph, opts) {
5982
6163
  const mode = opts.mode ?? "full";
5983
6164
  const maxPerCategory = opts.maxPerCategory ?? 20;
6165
+ const universalBaseline = opts.universalBaseline ?? true;
6166
+ const root = opts.root ?? graph.root;
5984
6167
  const legacyCount = [opts.feature, opts.scenario, opts.decision, opts.design, opts.e2e_test].filter(Boolean).length;
5985
6168
  if (opts.target && legacyCount > 0) {
5986
6169
  return {
@@ -6133,12 +6316,80 @@ function resolveArtifactContext(graph, opts) {
6133
6316
  return "direct";
6134
6317
  }
6135
6318
  const pathMap = /* @__PURE__ */ new Map();
6136
- for (const ap of ALWAYS_PRESENT_ITEMS) {
6137
- const existing = pathMap.get(ap.path);
6138
- if (existing) {
6139
- if (!existing.reasons.includes(ap.reason)) existing.reasons.push(ap.reason);
6319
+ if (universalBaseline) {
6320
+ for (const ap of ALWAYS_PRESENT_ITEMS) {
6321
+ const existing = pathMap.get(ap.path);
6322
+ if (existing) {
6323
+ if (!existing.reasons.includes(ap.reason)) existing.reasons.push(ap.reason);
6324
+ } else {
6325
+ pathMap.set(ap.path, { path: ap.path, reasons: [ap.reason], category: "baseline", required: true, tier: "baseline" });
6326
+ }
6327
+ }
6328
+ if (root) {
6329
+ for (const ap of ALWAYS_PRESENT_ITEMS) {
6330
+ const fullPath = (0, import_node_path6.join)(root, ap.path);
6331
+ let stat;
6332
+ try {
6333
+ stat = (0, import_node_fs3.statSync)(fullPath);
6334
+ } catch {
6335
+ stat = null;
6336
+ }
6337
+ if (!stat) {
6338
+ const msg = `Required baseline artifact not found: ${ap.path}`;
6339
+ if (!missing.includes(msg)) {
6340
+ missing.push(msg);
6341
+ missingDetails.push({
6342
+ ref: ap.path,
6343
+ from: "baseline",
6344
+ kind: "missing-baseline",
6345
+ message: msg,
6346
+ suggestedAction: `\u521B\u5EFA\u6587\u4EF6 ${ap.path} \u6216\u914D\u7F6E\u8DF3\u8FC7 universal baseline`
6347
+ });
6348
+ }
6349
+ } else if (!stat.isFile()) {
6350
+ const msg = `Required baseline artifact is not a regular file: ${ap.path}`;
6351
+ if (!missing.includes(msg)) {
6352
+ missing.push(msg);
6353
+ missingDetails.push({
6354
+ ref: ap.path,
6355
+ from: "baseline",
6356
+ kind: "missing-baseline",
6357
+ message: msg,
6358
+ suggestedAction: `\u5C06 ${ap.path} \u4ECE\u76EE\u5F55\u6539\u4E3A\u6587\u4EF6\uFF0C\u6216\u914D\u7F6E\u8DF3\u8FC7 universal baseline`
6359
+ });
6360
+ }
6361
+ } else {
6362
+ try {
6363
+ (0, import_node_fs3.accessSync)(fullPath, import_node_fs3.constants.R_OK);
6364
+ } catch {
6365
+ const msg = `Required baseline artifact is not readable: ${ap.path}`;
6366
+ if (!missing.includes(msg)) {
6367
+ missing.push(msg);
6368
+ missingDetails.push({
6369
+ ref: ap.path,
6370
+ from: "baseline",
6371
+ kind: "missing-baseline",
6372
+ message: msg,
6373
+ suggestedAction: `\u4FEE\u590D ${ap.path} \u7684\u6587\u4EF6\u6743\u9650\uFF0C\u6216\u914D\u7F6E\u8DF3\u8FC7 universal baseline`
6374
+ });
6375
+ }
6376
+ }
6377
+ }
6378
+ }
6140
6379
  } else {
6141
- pathMap.set(ap.path, { path: ap.path, reasons: [ap.reason], category: "baseline", required: true, tier: "baseline" });
6380
+ for (const ap of ALWAYS_PRESENT_ITEMS) {
6381
+ const msg = `Cannot verify baseline without root: ${ap.path}`;
6382
+ if (!missing.includes(msg)) {
6383
+ missing.push(msg);
6384
+ missingDetails.push({
6385
+ ref: ap.path,
6386
+ from: "baseline",
6387
+ kind: "missing-baseline",
6388
+ message: msg,
6389
+ suggestedAction: `\u4F20\u9012 root \u53C2\u6570\u6216\u914D\u7F6E\u8DF3\u8FC7 universal baseline`
6390
+ });
6391
+ }
6392
+ }
6142
6393
  }
6143
6394
  }
6144
6395
  pathMap.set(targetNode.path, {
@@ -6239,7 +6490,8 @@ function resolveArtifactContext(graph, opts) {
6239
6490
  context,
6240
6491
  missing,
6241
6492
  missingDetails,
6242
- omitted
6493
+ omitted,
6494
+ baselinePolicy: universalBaseline
6243
6495
  };
6244
6496
  }
6245
6497
  function formatContextMarkdown(manifest) {