artifact-graph 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +7 -0
- package/README.zh-CN.md +7 -0
- package/dist/cli.js +287 -1
- package/dist/index.cjs +248 -0
- package/dist/index.d.cts +121 -1
- package/dist/index.d.ts +121 -1
- package/dist/index.js +247 -0
- package/package.json +4 -2
- package/schemas/review-result.schema.json +246 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.4.0
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Add project-neutral Review Result Protocol v1.0 schema, TypeScript types and validateReviewResult validator API.
|
|
10
|
+
- Add `validate-review-result --file <path>` with absolute-path support and JSON-path diagnostics.
|
|
11
|
+
- Include `schemas/review-result.schema.json` in the published package.
|
|
12
|
+
|
|
5
13
|
## 0.3.1
|
|
6
14
|
|
|
7
15
|
### 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
|