artifact-graph 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.4.1
6
+
7
+ ### Changed
8
+
9
+ - Version bump for dual-repo synchronization with `artifact-chain-assistant@0.4.1`. No runtime code changes in this package.
10
+
11
+ ## 0.4.0
12
+
13
+ ### Added
14
+
15
+ - Add project-neutral Review Result Protocol v1.0 schema, TypeScript types and validateReviewResult validator API.
16
+ - Add `validate-review-result --file <path>` with absolute-path support and JSON-path diagnostics.
17
+ - Include `schemas/review-result.schema.json` in the published package.
18
+
5
19
  ## 0.3.1
6
20
 
7
21
  ### Fixed
package/README.md CHANGED
@@ -36,10 +36,17 @@ artifact-graph version-lock audit --root . --strict-missing-lock
36
36
 
37
37
  - Generate or inspect project artifact graph configuration with `artifact-graph init`.
38
38
  - Validate artifact links with `artifact-graph validate`.
39
+ - Validate Review Result Protocol v1.0 documents with `artifact-graph validate-review-result --file <path>`.
39
40
  - Build implementation context with `artifact-graph context` or `artifact-graph packet`.
40
41
  - Keep traceability freshness with `artifact-graph version-lock refresh` and `audit`.
41
42
  - Install opt-in Git hooks with `artifact-graph hooks install-git --hook all`.
42
43
 
44
+ ## Review Result Protocol
45
+
46
+ The package publishes `schemas/review-result.schema.json` and a matching TypeScript types + validateReviewResult validator API.
47
+ The protocol is project-neutral and supports review, repair, batch evidence, findings, metrics, and
48
+ fail-closed decisions. Invalid fields are reported with stable JSON paths.
49
+
43
50
  ## Related Project
44
51
 
45
52
  Use [`artifact-chain-assistant`](https://github.com/mzdbxqh/artifact-chain-assistant) for Codex and
package/README.zh-CN.md CHANGED
@@ -34,10 +34,17 @@ artifact-graph version-lock audit --root . --strict-missing-lock
34
34
 
35
35
  - 用 `artifact-graph init` 生成或检查项目制品图配置。
36
36
  - 用 `artifact-graph validate` 校验制品之间的链接。
37
+ - 用 `artifact-graph validate-review-result --file <path>` 校验 Review Result Protocol v1.0 文档。
37
38
  - 用 `artifact-graph context` 或 `artifact-graph packet` 构建实现上下文。
38
39
  - 用 `artifact-graph version-lock refresh` 和 `audit` 维护追溯新鲜度。
39
40
  - 用 `artifact-graph hooks install-git --hook all` 安装可选 Git hooks。
40
41
 
42
+ ## Review Result Protocol
43
+
44
+ 包内发布 `schemas/review-result.schema.json` 和等价的 TypeScript 类型与 validateReviewResult 校验 API。该协议不绑定具体
45
+ 项目,覆盖 review、repair、批次证据、findings、metrics 与 fail-closed decision;非法字段
46
+ 会返回稳定的 JSON path 诊断。
47
+
41
48
  ## 相关项目
42
49
 
43
50
  如果需要 Codex / Claude Code 的技能、初始化引导和维护流程,请使用
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import yaml2 from "js-yaml";
5
5
  import { realpathSync } from "fs";
6
6
  import { access as access2, mkdir as mkdir6, readFile as readFile3, writeFile as writeFile5 } from "fs/promises";
7
- import { join as join7 } from "path";
7
+ import { isAbsolute as isAbsolute3, join as join7 } from "path";
8
8
  import { fileURLToPath } from "url";
9
9
 
10
10
  // src/index.ts
@@ -2558,6 +2558,252 @@ function findManagedRange(content, begin, end) {
2558
2558
  };
2559
2559
  }
2560
2560
 
2561
+ // src/review-result-validator.ts
2562
+ var VALID_STATUSES = /* @__PURE__ */ new Set([
2563
+ "SUCCEEDED",
2564
+ "FAILED",
2565
+ "BLOCKED",
2566
+ "NEEDS_INPUT",
2567
+ "SKIPPED"
2568
+ ]);
2569
+ var VALID_DECISIONS = /* @__PURE__ */ new Set([
2570
+ "PASS",
2571
+ "FAIL",
2572
+ "PASS_WITH_RESIDUAL_MINOR",
2573
+ "BLOCKED",
2574
+ "NEEDS_INPUT",
2575
+ "NOT_APPLICABLE"
2576
+ ]);
2577
+ var VALID_SEVERITIES = /* @__PURE__ */ new Set(["block", "warn", "info"]);
2578
+ var VALID_FINDING_STATUSES = /* @__PURE__ */ new Set(["open", "resolved", "accepted", "superseded"]);
2579
+ var VALID_EXECUTORS = /* @__PURE__ */ new Set(["script", "worker", "agent", "manual", "cli"]);
2580
+ function validateReviewResult(input) {
2581
+ const errors = [];
2582
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
2583
+ return [{ path: "$", message: "Root must be a non-null object" }];
2584
+ }
2585
+ const obj = input;
2586
+ if (obj.schema_version !== "1.0") {
2587
+ errors.push({ path: "$.schema_version", message: `Must be "1.0", got ${JSON.stringify(obj.schema_version)}` });
2588
+ }
2589
+ if (typeof obj.run_id !== "string" || obj.run_id.length === 0) {
2590
+ errors.push({ path: "$.run_id", message: "Must be a non-empty string" });
2591
+ }
2592
+ if (!VALID_STATUSES.has(String(obj.status))) {
2593
+ errors.push({ path: "$.status", message: `Must be one of ${[...VALID_STATUSES].join(", ")}, got ${JSON.stringify(obj.status)}` });
2594
+ }
2595
+ if (!VALID_DECISIONS.has(String(obj.decision))) {
2596
+ errors.push({ path: "$.decision", message: `Must be one of ${[...VALID_DECISIONS].join(", ")}, got ${JSON.stringify(obj.decision)}` });
2597
+ }
2598
+ if (typeof obj.summary !== "string") {
2599
+ errors.push({ path: "$.summary", message: "Must be a string" });
2600
+ }
2601
+ if (obj.stage_id !== void 0 && typeof obj.stage_id !== "string") {
2602
+ errors.push({ path: "$.stage_id", message: "Must be a string if present" });
2603
+ }
2604
+ if (obj.attempt !== void 0 && (!Number.isInteger(obj.attempt) || obj.attempt < 1)) {
2605
+ errors.push({ path: "$.attempt", message: "Must be a positive integer if present" });
2606
+ }
2607
+ if (obj.outputs !== void 0) {
2608
+ checkStringArray(obj.outputs, "$.outputs", errors);
2609
+ }
2610
+ if (obj.warnings !== void 0) {
2611
+ checkStringArray(obj.warnings, "$.warnings", errors);
2612
+ }
2613
+ checkOptionalNullableString(obj.blocking_reason, "$.blocking_reason", errors);
2614
+ checkOptionalNullableString(obj.degradation, "$.degradation", errors);
2615
+ if (obj.producer !== void 0) {
2616
+ if (!isPlainObject(obj.producer)) {
2617
+ errors.push({ path: "$.producer", message: "Must be an object" });
2618
+ } else {
2619
+ const p = obj.producer;
2620
+ if (typeof p.executor !== "string") {
2621
+ errors.push({ path: "$.producer.executor", message: "Must be a string" });
2622
+ } else if (!VALID_EXECUTORS.has(p.executor)) {
2623
+ errors.push({ path: "$.producer.executor", message: `Must be one of ${[...VALID_EXECUTORS].join(", ")}; got ${JSON.stringify(p.executor)}` });
2624
+ }
2625
+ if (typeof p.name !== "string") {
2626
+ errors.push({ path: "$.producer.name", message: "Must be a string" });
2627
+ }
2628
+ checkOptionalString(p.skill, "$.producer.skill", errors);
2629
+ }
2630
+ }
2631
+ if (obj.evidence !== void 0) {
2632
+ if (!Array.isArray(obj.evidence)) {
2633
+ errors.push({ path: "$.evidence", message: "Must be an array" });
2634
+ } else for (let i = 0; i < obj.evidence.length; i++) {
2635
+ const e = obj.evidence[i];
2636
+ if (typeof e === "string") continue;
2637
+ if (isPlainObject(e)) {
2638
+ const ev = e;
2639
+ if (typeof ev.type !== "string") {
2640
+ errors.push({ path: `$.evidence[${i}].type`, message: "Must be a string" });
2641
+ }
2642
+ if (typeof ev.path !== "string") {
2643
+ errors.push({ path: `$.evidence[${i}].path`, message: "Must be a string" });
2644
+ }
2645
+ for (const key of ["status", "decision", "summary", "command", "result"]) {
2646
+ checkOptionalString(ev[key], `$.evidence[${i}].${key}`, errors);
2647
+ }
2648
+ } else {
2649
+ errors.push({ path: `$.evidence[${i}]`, message: "Must be a string or object" });
2650
+ }
2651
+ }
2652
+ }
2653
+ if (obj.review !== void 0) {
2654
+ validateReviewData(obj.review, "$.review", errors);
2655
+ }
2656
+ if (obj.repair !== void 0) {
2657
+ if (!isPlainObject(obj.repair)) {
2658
+ errors.push({ path: "$.repair", message: "Must be an object" });
2659
+ } else {
2660
+ const r = obj.repair;
2661
+ checkOptionalString(r.source_review_run_id, "$.repair.source_review_run_id", errors);
2662
+ checkOptionalString(r.source_review_stage_id, "$.repair.source_review_stage_id", errors);
2663
+ if (r.findings_addressed !== void 0) {
2664
+ validateFindingArray(r.findings_addressed, "$.repair.findings_addressed", errors);
2665
+ }
2666
+ if (r.files_modified !== void 0) {
2667
+ checkStringArray(r.files_modified, "$.repair.files_modified", errors);
2668
+ }
2669
+ if (r.validation_after_repair !== void 0) {
2670
+ validateRepairValidation(r.validation_after_repair, "$.repair.validation_after_repair", errors);
2671
+ }
2672
+ }
2673
+ }
2674
+ return errors;
2675
+ }
2676
+ function checkStringArray(val, path, errors) {
2677
+ if (!Array.isArray(val)) {
2678
+ errors.push({ path, message: "Must be an array" });
2679
+ return;
2680
+ }
2681
+ for (let i = 0; i < val.length; i++) {
2682
+ if (typeof val[i] !== "string") {
2683
+ errors.push({ path: `${path}[${i}]`, message: "Must be a string" });
2684
+ }
2685
+ }
2686
+ }
2687
+ function isPlainObject(val) {
2688
+ return typeof val === "object" && val !== null && !Array.isArray(val);
2689
+ }
2690
+ function checkOptionalString(val, path, errors) {
2691
+ if (val !== void 0 && typeof val !== "string") {
2692
+ errors.push({ path, message: "Must be a string if present" });
2693
+ }
2694
+ }
2695
+ function checkOptionalNullableString(val, path, errors) {
2696
+ if (val !== void 0 && val !== null && typeof val !== "string") {
2697
+ errors.push({ path, message: "Must be a string or null if present" });
2698
+ }
2699
+ }
2700
+ function checkOptionalInteger(val, path, errors, minimum) {
2701
+ if (val === void 0) return;
2702
+ if (!Number.isInteger(val) || minimum !== void 0 && val < minimum) {
2703
+ errors.push({ path, message: minimum === void 0 ? "Must be an integer if present" : `Must be an integer >= ${minimum} if present` });
2704
+ }
2705
+ }
2706
+ function validateFinding(val, path, errors) {
2707
+ if (!isPlainObject(val)) {
2708
+ errors.push({ path, message: "Must be a non-null object" });
2709
+ return;
2710
+ }
2711
+ const f = val;
2712
+ if (typeof f.id !== "string" || f.id.length === 0) {
2713
+ errors.push({ path: `${path}.id`, message: "Must be a non-empty string" });
2714
+ }
2715
+ if (!VALID_SEVERITIES.has(String(f.severity))) {
2716
+ errors.push({ path: `${path}.severity`, message: `Must be one of block, warn, info; got ${JSON.stringify(f.severity)}` });
2717
+ }
2718
+ if (typeof f.message !== "string") {
2719
+ errors.push({ path: `${path}.message`, message: "Must be a string" });
2720
+ }
2721
+ if (f.status !== void 0 && !VALID_FINDING_STATUSES.has(String(f.status))) {
2722
+ errors.push({ path: `${path}.status`, message: `Must be one of ${[...VALID_FINDING_STATUSES].join(", ")}; got ${JSON.stringify(f.status)}` });
2723
+ }
2724
+ for (const key of ["category", "artifact_id", "evidence", "suggested_fix", "resolved_by"]) {
2725
+ checkOptionalString(f[key], `${path}.${key}`, errors);
2726
+ }
2727
+ if (f.location !== void 0) {
2728
+ if (!isPlainObject(f.location)) {
2729
+ errors.push({ path: `${path}.location`, message: "Must be an object if present" });
2730
+ } else {
2731
+ checkOptionalString(f.location.file, `${path}.location.file`, errors);
2732
+ checkOptionalInteger(f.location.line, `${path}.location.line`, errors);
2733
+ checkOptionalInteger(f.location.column, `${path}.location.column`, errors);
2734
+ }
2735
+ }
2736
+ }
2737
+ function validateFindingArray(val, path, errors) {
2738
+ if (!Array.isArray(val)) {
2739
+ errors.push({ path, message: "Must be an array" });
2740
+ return;
2741
+ }
2742
+ for (let i = 0; i < val.length; i++) validateFinding(val[i], `${path}[${i}]`, errors);
2743
+ }
2744
+ function validateReviewData(val, path, errors) {
2745
+ if (!isPlainObject(val)) {
2746
+ errors.push({ path, message: "Must be a non-null object" });
2747
+ return;
2748
+ }
2749
+ const r = val;
2750
+ if (r.source_files !== void 0) checkStringArray(r.source_files, `${path}.source_files`, errors);
2751
+ if (r.files !== void 0) checkStringArray(r.files, `${path}.files`, errors);
2752
+ if (r.findings !== void 0) validateFindingArray(r.findings, `${path}.findings`, errors);
2753
+ if (r.resolved_findings !== void 0) validateFindingArray(r.resolved_findings, `${path}.resolved_findings`, errors);
2754
+ if (r.batches !== void 0) validateBatches(r.batches, `${path}.batches`, errors);
2755
+ if (r.metrics !== void 0) validateMetrics(r.metrics, `${path}.metrics`, errors);
2756
+ if (r.repair_worker_needed !== void 0 && typeof r.repair_worker_needed !== "boolean") {
2757
+ errors.push({ path: `${path}.repair_worker_needed`, message: "Must be a boolean if present" });
2758
+ }
2759
+ }
2760
+ function validateBatches(val, path, errors) {
2761
+ if (!Array.isArray(val)) {
2762
+ errors.push({ path, message: "Must be an array" });
2763
+ return;
2764
+ }
2765
+ for (let i = 0; i < val.length; i++) {
2766
+ const itemPath = `${path}[${i}]`;
2767
+ const b = val[i];
2768
+ if (!isPlainObject(b)) {
2769
+ errors.push({ path: itemPath, message: "Must be an object" });
2770
+ continue;
2771
+ }
2772
+ if (typeof b.id !== "string") errors.push({ path: `${itemPath}.id`, message: "Must be a string" });
2773
+ checkStringArray(b.files, `${itemPath}.files`, errors);
2774
+ checkOptionalInteger(b.chars, `${itemPath}.chars`, errors);
2775
+ }
2776
+ }
2777
+ var METRIC_FIELDS = [
2778
+ "files_reviewed",
2779
+ "files_scanned",
2780
+ "findings_count",
2781
+ "deterministic_findings_count",
2782
+ "semantic_findings_count",
2783
+ "block_count",
2784
+ "warn_count",
2785
+ "info_count",
2786
+ "resolved_count",
2787
+ "batch_count",
2788
+ "scenario_count"
2789
+ ];
2790
+ function validateMetrics(val, path, errors) {
2791
+ if (!isPlainObject(val)) {
2792
+ errors.push({ path, message: "Must be an object" });
2793
+ return;
2794
+ }
2795
+ for (const key of METRIC_FIELDS) checkOptionalInteger(val[key], `${path}.${key}`, errors, 0);
2796
+ }
2797
+ function validateRepairValidation(val, path, errors) {
2798
+ if (!isPlainObject(val)) {
2799
+ errors.push({ path, message: "Must be an object" });
2800
+ return;
2801
+ }
2802
+ checkOptionalString(val.command, `${path}.command`, errors);
2803
+ checkOptionalInteger(val.exit_code, `${path}.exit_code`, errors);
2804
+ checkOptionalInteger(val.findings_remaining, `${path}.findings_remaining`, errors, 0);
2805
+ }
2806
+
2561
2807
  // src/index.ts
2562
2808
  var TARGET_ARTIFACT_TYPES = VALID_PACKET_TARGET_TYPES;
2563
2809
  var NON_TARGET_ROLES = ["context", "candidate", "not-recommended"];
@@ -6961,6 +7207,45 @@ async function runCli(argv, io = {}) {
6961
7207
  }
6962
7208
  return 0;
6963
7209
  }
7210
+ case "validate-review-result": {
7211
+ const filePath = typeof parsed.flags.file === "string" ? parsed.flags.file : void 0;
7212
+ if (!filePath) {
7213
+ err("Usage: artifact-graph validate-review-result --file <path> [--format json]\n");
7214
+ return 1;
7215
+ }
7216
+ const resolvedPath = isAbsolute3(filePath) ? filePath : join7(root, filePath);
7217
+ let content;
7218
+ try {
7219
+ content = await readFile3(resolvedPath, "utf-8");
7220
+ } catch (readErr) {
7221
+ err(`Error: Cannot read file: "${resolvedPath}" \u2014 ${readErr.message}
7222
+ `);
7223
+ return 1;
7224
+ }
7225
+ let parsed_json;
7226
+ try {
7227
+ parsed_json = JSON.parse(content);
7228
+ } catch (parseErr) {
7229
+ err(`Error: Invalid JSON in "${resolvedPath}" \u2014 ${parseErr.message}
7230
+ `);
7231
+ return 1;
7232
+ }
7233
+ const validationErrors = validateReviewResult(parsed_json);
7234
+ if (parsed.flags.format === "json") {
7235
+ out(`${JSON.stringify({ valid: validationErrors.length === 0, errors: validationErrors }, null, 2)}
7236
+ `);
7237
+ } else if (validationErrors.length === 0) {
7238
+ out("Review result is valid\n");
7239
+ } else {
7240
+ out(`Review result has ${validationErrors.length} error(s):
7241
+ `);
7242
+ for (const errItem of validationErrors) {
7243
+ out(` ${errItem.path}: ${errItem.message}
7244
+ `);
7245
+ }
7246
+ }
7247
+ return validationErrors.length === 0 ? 0 : 1;
7248
+ }
6964
7249
  default:
6965
7250
  err(helpText());
6966
7251
  return 1;
@@ -7034,6 +7319,7 @@ Commands:
7034
7319
  next-id <type> --range <name>
7035
7320
  render [--format mermaid]
7036
7321
  doctor [--format json|markdown]
7322
+ validate-review-result --file <path> [--format json]
7037
7323
  `;
7038
7324
  }
7039
7325
  function isCliEntrypoint(argvPath) {
package/dist/index.cjs CHANGED
@@ -90,6 +90,7 @@ __export(index_exports, {
90
90
  validatePacket: () => validatePacket,
91
91
  validatePacketMarkdown: () => validatePacketMarkdown,
92
92
  validatePacketPrompt: () => validatePacketPrompt,
93
+ validateReviewResult: () => validateReviewResult,
93
94
  validateScenarioPrdLinkIndex: () => validateScenarioPrdLinkIndex,
94
95
  validateScenarioPrdLinks: () => validateScenarioPrdLinks,
95
96
  writeGraphCache: () => writeGraphCache
@@ -2674,6 +2675,252 @@ function findManagedRange(content, begin, end) {
2674
2675
  };
2675
2676
  }
2676
2677
 
2678
+ // src/review-result-validator.ts
2679
+ var VALID_STATUSES = /* @__PURE__ */ new Set([
2680
+ "SUCCEEDED",
2681
+ "FAILED",
2682
+ "BLOCKED",
2683
+ "NEEDS_INPUT",
2684
+ "SKIPPED"
2685
+ ]);
2686
+ var VALID_DECISIONS = /* @__PURE__ */ new Set([
2687
+ "PASS",
2688
+ "FAIL",
2689
+ "PASS_WITH_RESIDUAL_MINOR",
2690
+ "BLOCKED",
2691
+ "NEEDS_INPUT",
2692
+ "NOT_APPLICABLE"
2693
+ ]);
2694
+ var VALID_SEVERITIES = /* @__PURE__ */ new Set(["block", "warn", "info"]);
2695
+ var VALID_FINDING_STATUSES = /* @__PURE__ */ new Set(["open", "resolved", "accepted", "superseded"]);
2696
+ var VALID_EXECUTORS = /* @__PURE__ */ new Set(["script", "worker", "agent", "manual", "cli"]);
2697
+ function validateReviewResult(input) {
2698
+ const errors = [];
2699
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
2700
+ return [{ path: "$", message: "Root must be a non-null object" }];
2701
+ }
2702
+ const obj = input;
2703
+ if (obj.schema_version !== "1.0") {
2704
+ errors.push({ path: "$.schema_version", message: `Must be "1.0", got ${JSON.stringify(obj.schema_version)}` });
2705
+ }
2706
+ if (typeof obj.run_id !== "string" || obj.run_id.length === 0) {
2707
+ errors.push({ path: "$.run_id", message: "Must be a non-empty string" });
2708
+ }
2709
+ if (!VALID_STATUSES.has(String(obj.status))) {
2710
+ errors.push({ path: "$.status", message: `Must be one of ${[...VALID_STATUSES].join(", ")}, got ${JSON.stringify(obj.status)}` });
2711
+ }
2712
+ if (!VALID_DECISIONS.has(String(obj.decision))) {
2713
+ errors.push({ path: "$.decision", message: `Must be one of ${[...VALID_DECISIONS].join(", ")}, got ${JSON.stringify(obj.decision)}` });
2714
+ }
2715
+ if (typeof obj.summary !== "string") {
2716
+ errors.push({ path: "$.summary", message: "Must be a string" });
2717
+ }
2718
+ if (obj.stage_id !== void 0 && typeof obj.stage_id !== "string") {
2719
+ errors.push({ path: "$.stage_id", message: "Must be a string if present" });
2720
+ }
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" });
2723
+ }
2724
+ if (obj.outputs !== void 0) {
2725
+ checkStringArray(obj.outputs, "$.outputs", errors);
2726
+ }
2727
+ if (obj.warnings !== void 0) {
2728
+ checkStringArray(obj.warnings, "$.warnings", errors);
2729
+ }
2730
+ checkOptionalNullableString(obj.blocking_reason, "$.blocking_reason", errors);
2731
+ checkOptionalNullableString(obj.degradation, "$.degradation", errors);
2732
+ 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
+ }
2747
+ }
2748
+ if (obj.evidence !== void 0) {
2749
+ if (!Array.isArray(obj.evidence)) {
2750
+ errors.push({ path: "$.evidence", message: "Must be an array" });
2751
+ } else for (let i = 0; i < obj.evidence.length; i++) {
2752
+ const e = obj.evidence[i];
2753
+ if (typeof e === "string") continue;
2754
+ if (isPlainObject(e)) {
2755
+ const ev = e;
2756
+ if (typeof ev.type !== "string") {
2757
+ errors.push({ path: `$.evidence[${i}].type`, message: "Must be a string" });
2758
+ }
2759
+ if (typeof ev.path !== "string") {
2760
+ errors.push({ path: `$.evidence[${i}].path`, message: "Must be a string" });
2761
+ }
2762
+ for (const key of ["status", "decision", "summary", "command", "result"]) {
2763
+ checkOptionalString(ev[key], `$.evidence[${i}].${key}`, errors);
2764
+ }
2765
+ } else {
2766
+ errors.push({ path: `$.evidence[${i}]`, message: "Must be a string or object" });
2767
+ }
2768
+ }
2769
+ }
2770
+ if (obj.review !== void 0) {
2771
+ validateReviewData(obj.review, "$.review", errors);
2772
+ }
2773
+ if (obj.repair !== void 0) {
2774
+ if (!isPlainObject(obj.repair)) {
2775
+ errors.push({ path: "$.repair", message: "Must be an object" });
2776
+ } else {
2777
+ const r = obj.repair;
2778
+ checkOptionalString(r.source_review_run_id, "$.repair.source_review_run_id", errors);
2779
+ checkOptionalString(r.source_review_stage_id, "$.repair.source_review_stage_id", errors);
2780
+ if (r.findings_addressed !== void 0) {
2781
+ validateFindingArray(r.findings_addressed, "$.repair.findings_addressed", errors);
2782
+ }
2783
+ if (r.files_modified !== void 0) {
2784
+ checkStringArray(r.files_modified, "$.repair.files_modified", errors);
2785
+ }
2786
+ if (r.validation_after_repair !== void 0) {
2787
+ validateRepairValidation(r.validation_after_repair, "$.repair.validation_after_repair", errors);
2788
+ }
2789
+ }
2790
+ }
2791
+ return errors;
2792
+ }
2793
+ function checkStringArray(val, path, errors) {
2794
+ if (!Array.isArray(val)) {
2795
+ errors.push({ path, message: "Must be an array" });
2796
+ return;
2797
+ }
2798
+ for (let i = 0; i < val.length; i++) {
2799
+ if (typeof val[i] !== "string") {
2800
+ errors.push({ path: `${path}[${i}]`, message: "Must be a string" });
2801
+ }
2802
+ }
2803
+ }
2804
+ function isPlainObject(val) {
2805
+ return typeof val === "object" && val !== null && !Array.isArray(val);
2806
+ }
2807
+ function checkOptionalString(val, path, errors) {
2808
+ if (val !== void 0 && typeof val !== "string") {
2809
+ errors.push({ path, message: "Must be a string if present" });
2810
+ }
2811
+ }
2812
+ function checkOptionalNullableString(val, path, errors) {
2813
+ if (val !== void 0 && val !== null && typeof val !== "string") {
2814
+ errors.push({ path, message: "Must be a string or null if present" });
2815
+ }
2816
+ }
2817
+ function checkOptionalInteger(val, path, errors, minimum) {
2818
+ if (val === void 0) return;
2819
+ if (!Number.isInteger(val) || minimum !== void 0 && val < minimum) {
2820
+ errors.push({ path, message: minimum === void 0 ? "Must be an integer if present" : `Must be an integer >= ${minimum} if present` });
2821
+ }
2822
+ }
2823
+ function validateFinding(val, path, errors) {
2824
+ if (!isPlainObject(val)) {
2825
+ errors.push({ path, message: "Must be a non-null object" });
2826
+ return;
2827
+ }
2828
+ const f = val;
2829
+ if (typeof f.id !== "string" || f.id.length === 0) {
2830
+ errors.push({ path: `${path}.id`, message: "Must be a non-empty string" });
2831
+ }
2832
+ if (!VALID_SEVERITIES.has(String(f.severity))) {
2833
+ errors.push({ path: `${path}.severity`, message: `Must be one of block, warn, info; got ${JSON.stringify(f.severity)}` });
2834
+ }
2835
+ if (typeof f.message !== "string") {
2836
+ errors.push({ path: `${path}.message`, message: "Must be a string" });
2837
+ }
2838
+ if (f.status !== void 0 && !VALID_FINDING_STATUSES.has(String(f.status))) {
2839
+ errors.push({ path: `${path}.status`, message: `Must be one of ${[...VALID_FINDING_STATUSES].join(", ")}; got ${JSON.stringify(f.status)}` });
2840
+ }
2841
+ for (const key of ["category", "artifact_id", "evidence", "suggested_fix", "resolved_by"]) {
2842
+ checkOptionalString(f[key], `${path}.${key}`, errors);
2843
+ }
2844
+ if (f.location !== void 0) {
2845
+ if (!isPlainObject(f.location)) {
2846
+ errors.push({ path: `${path}.location`, message: "Must be an object if present" });
2847
+ } else {
2848
+ checkOptionalString(f.location.file, `${path}.location.file`, errors);
2849
+ checkOptionalInteger(f.location.line, `${path}.location.line`, errors);
2850
+ checkOptionalInteger(f.location.column, `${path}.location.column`, errors);
2851
+ }
2852
+ }
2853
+ }
2854
+ function validateFindingArray(val, path, errors) {
2855
+ if (!Array.isArray(val)) {
2856
+ errors.push({ path, message: "Must be an array" });
2857
+ return;
2858
+ }
2859
+ for (let i = 0; i < val.length; i++) validateFinding(val[i], `${path}[${i}]`, errors);
2860
+ }
2861
+ function validateReviewData(val, path, errors) {
2862
+ if (!isPlainObject(val)) {
2863
+ errors.push({ path, message: "Must be a non-null object" });
2864
+ return;
2865
+ }
2866
+ const r = val;
2867
+ if (r.source_files !== void 0) checkStringArray(r.source_files, `${path}.source_files`, errors);
2868
+ if (r.files !== void 0) checkStringArray(r.files, `${path}.files`, errors);
2869
+ if (r.findings !== void 0) validateFindingArray(r.findings, `${path}.findings`, errors);
2870
+ if (r.resolved_findings !== void 0) validateFindingArray(r.resolved_findings, `${path}.resolved_findings`, errors);
2871
+ if (r.batches !== void 0) validateBatches(r.batches, `${path}.batches`, errors);
2872
+ if (r.metrics !== void 0) validateMetrics(r.metrics, `${path}.metrics`, errors);
2873
+ if (r.repair_worker_needed !== void 0 && typeof r.repair_worker_needed !== "boolean") {
2874
+ errors.push({ path: `${path}.repair_worker_needed`, message: "Must be a boolean if present" });
2875
+ }
2876
+ }
2877
+ function validateBatches(val, path, errors) {
2878
+ if (!Array.isArray(val)) {
2879
+ errors.push({ path, message: "Must be an array" });
2880
+ return;
2881
+ }
2882
+ for (let i = 0; i < val.length; i++) {
2883
+ const itemPath = `${path}[${i}]`;
2884
+ const b = val[i];
2885
+ if (!isPlainObject(b)) {
2886
+ errors.push({ path: itemPath, message: "Must be an object" });
2887
+ continue;
2888
+ }
2889
+ if (typeof b.id !== "string") errors.push({ path: `${itemPath}.id`, message: "Must be a string" });
2890
+ checkStringArray(b.files, `${itemPath}.files`, errors);
2891
+ checkOptionalInteger(b.chars, `${itemPath}.chars`, errors);
2892
+ }
2893
+ }
2894
+ var METRIC_FIELDS = [
2895
+ "files_reviewed",
2896
+ "files_scanned",
2897
+ "findings_count",
2898
+ "deterministic_findings_count",
2899
+ "semantic_findings_count",
2900
+ "block_count",
2901
+ "warn_count",
2902
+ "info_count",
2903
+ "resolved_count",
2904
+ "batch_count",
2905
+ "scenario_count"
2906
+ ];
2907
+ function validateMetrics(val, path, errors) {
2908
+ if (!isPlainObject(val)) {
2909
+ errors.push({ path, message: "Must be an object" });
2910
+ return;
2911
+ }
2912
+ for (const key of METRIC_FIELDS) checkOptionalInteger(val[key], `${path}.${key}`, errors, 0);
2913
+ }
2914
+ function validateRepairValidation(val, path, errors) {
2915
+ if (!isPlainObject(val)) {
2916
+ errors.push({ path, message: "Must be an object" });
2917
+ return;
2918
+ }
2919
+ checkOptionalString(val.command, `${path}.command`, errors);
2920
+ checkOptionalInteger(val.exit_code, `${path}.exit_code`, errors);
2921
+ checkOptionalInteger(val.findings_remaining, `${path}.findings_remaining`, errors, 0);
2922
+ }
2923
+
2677
2924
  // src/index.ts
2678
2925
  var TARGET_ARTIFACT_TYPES = VALID_PACKET_TARGET_TYPES;
2679
2926
  var NON_TARGET_ROLES = ["context", "candidate", "not-recommended"];
@@ -6120,6 +6367,7 @@ function formatContextMarkdown(manifest) {
6120
6367
  validatePacket,
6121
6368
  validatePacketMarkdown,
6122
6369
  validatePacketPrompt,
6370
+ validateReviewResult,
6123
6371
  validateScenarioPrdLinkIndex,
6124
6372
  validateScenarioPrdLinks,
6125
6373
  writeGraphCache