@fro.bot/systematic 3.17.0 → 3.18.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/HARNESSES.md +26 -0
- package/dist/ce-review-validator.d.ts +23 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.js +438 -30
- package/dist/{index-y33enbkc.js → index-v8y6h7y4.js} +577 -368
- package/dist/index.js +1 -1
- package/dist/lib/review-artifact-schema.d.ts +171 -1
- package/dist/lib/review-return-validator.d.ts +59 -0
- package/dist/pi.js +576 -367
- package/package.json +9 -7
- package/skills/ce-review/SKILL.md +72 -36
- package/skills/ce-review/references/findings-schema.json +271 -192
- package/skills/ce-review/references/persona-catalog.md +39 -25
- package/skills/ce-review/references/review-output-template.md +3 -3
- package/skills/ce-review/references/review-summary-schema.json +7 -1
- package/skills/ce-review/references/subagent-template.md +8 -2
- package/skills/ce-review/references/synthesis-artifact-contract.md +96 -11
- package/skills/ce-review/scripts/validate-review.mjs +7187 -0
package/dist/cli.js
CHANGED
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
findCommandsInDir,
|
|
34
34
|
findSkillsInDir,
|
|
35
35
|
discoverSkills
|
|
36
|
-
} from "./index-
|
|
36
|
+
} from "./index-v8y6h7y4.js";
|
|
37
37
|
var __defProp = Object.defineProperty;
|
|
38
38
|
var __returnValue = (v) => v;
|
|
39
39
|
function __exportSetter(name, newValue) {
|
|
@@ -50,7 +50,7 @@ var __export = (target, all) => {
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
// src/cli.ts
|
|
53
|
-
import
|
|
53
|
+
import fs7 from "fs";
|
|
54
54
|
import os2 from "os";
|
|
55
55
|
import path5 from "path";
|
|
56
56
|
|
|
@@ -831,7 +831,7 @@ function buildCapabilitySnapshot(options) {
|
|
|
831
831
|
options.outputSink?.(serialized);
|
|
832
832
|
return snapshot;
|
|
833
833
|
}
|
|
834
|
-
// node_modules/.bun/zod@4.
|
|
834
|
+
// node_modules/.bun/zod@4.6.0/node_modules/zod/v4/classic/iso.js
|
|
835
835
|
var exports_iso = {};
|
|
836
836
|
__export(exports_iso, {
|
|
837
837
|
ZodISODate: () => ZodISODate,
|
|
@@ -2191,14 +2191,33 @@ var REVIEW_ARTIFACT_CUSTOM_MESSAGES = [
|
|
|
2191
2191
|
"risk-critical dispatches require a non-empty selection surface",
|
|
2192
2192
|
"satisfied risk coverage requires a citing input finding ID",
|
|
2193
2193
|
"unsatisfied risk coverage must not cite an input finding ID",
|
|
2194
|
-
"passed validation must not include a reason; non-passed validation requires a reason"
|
|
2194
|
+
"passed validation must not include a reason; non-passed validation requires a reason",
|
|
2195
|
+
"validation_unavailable dispatches must record zero input findings",
|
|
2196
|
+
"a completed run must not contain validation_unavailable evidence",
|
|
2197
|
+
"a validation_unavailable persona must not have an input finding",
|
|
2198
|
+
"every synthesized input finding ID must resolve to an admitted ledger row",
|
|
2199
|
+
"every provenance submitter must be represented by a cited admitted ledger row",
|
|
2200
|
+
"satisfied risk coverage must cite an admitted ledger row",
|
|
2201
|
+
"duplicate admitted input finding IDs are not allowed",
|
|
2202
|
+
"every cited admitted reviewer must appear in provenance.submitters",
|
|
2203
|
+
"provenance.submitters must not contain duplicate reviewers",
|
|
2204
|
+
"provenance.agreement_credit must not contain duplicate reviewers",
|
|
2205
|
+
"provenance.agreement_credit must not overlap provenance.submitters",
|
|
2206
|
+
"provenance.agreement_credit requires an eligible returned persona with admitted evidence",
|
|
2207
|
+
"satisfied risk coverage must cite a validated finding on the lost persona selection surface",
|
|
2208
|
+
"satisfied risk coverage must cite an admitted ledger row owned by another persona"
|
|
2195
2209
|
];
|
|
2196
2210
|
var boundedText = (maxLength) => string().min(1).max(maxLength).regex(/\S/);
|
|
2211
|
+
var LineNumberSchema = number().int().positive();
|
|
2197
2212
|
var DispatchOutcomeSchema = _enum([
|
|
2198
2213
|
"findings",
|
|
2199
2214
|
"empty",
|
|
2200
2215
|
"malformed",
|
|
2201
|
-
"never_returned"
|
|
2216
|
+
"never_returned",
|
|
2217
|
+
"validation_unavailable"
|
|
2218
|
+
]);
|
|
2219
|
+
var RejectedSummaryDispatchOutcomeSchema = DispatchOutcomeSchema.exclude([
|
|
2220
|
+
"validation_unavailable"
|
|
2202
2221
|
]);
|
|
2203
2222
|
var DispositionSchema = _enum([
|
|
2204
2223
|
"surviving",
|
|
@@ -2255,7 +2274,7 @@ var AdmittedInputFindingSchema = object({
|
|
|
2255
2274
|
var RejectedInputFindingSchema = object({
|
|
2256
2275
|
record_type: literal("rejected_summary"),
|
|
2257
2276
|
reviewer: ReviewerSchema,
|
|
2258
|
-
dispatch_outcome:
|
|
2277
|
+
dispatch_outcome: RejectedSummaryDispatchOutcomeSchema,
|
|
2259
2278
|
rejected_finding_count: number().int().positive().max(MAX_FINDINGS),
|
|
2260
2279
|
rejected_severities: array(SeveritySchema).max(MAX_FINDINGS),
|
|
2261
2280
|
disposition: DispositionSchema.extract(["rejected"]),
|
|
@@ -2282,7 +2301,7 @@ var SynthesizedFindingFieldsSchema = object({
|
|
|
2282
2301
|
title: FindingTitleSchema,
|
|
2283
2302
|
severity: FindingSeveritySchema,
|
|
2284
2303
|
file: RepoRelativePathSchema,
|
|
2285
|
-
line:
|
|
2304
|
+
line: LineNumberSchema,
|
|
2286
2305
|
why_it_matters: boundedText(2048),
|
|
2287
2306
|
autofix_class: AutofixClassSchema,
|
|
2288
2307
|
owner: OwnerSchema,
|
|
@@ -2412,10 +2431,367 @@ var ReviewArtifactSchema = object({
|
|
|
2412
2431
|
advisory_outputs: array(ReasonSchema).max(MAX_FINDINGS),
|
|
2413
2432
|
coverage: CoverageSchema,
|
|
2414
2433
|
validation: ValidationSchema.optional()
|
|
2434
|
+
}).strict().superRefine((artifact, ctx) => {
|
|
2435
|
+
const unavailablePersonas = new Set(artifact.dispatches.filter((dispatch) => dispatch.dispatch_outcome === "validation_unavailable").map((dispatch) => dispatch.persona));
|
|
2436
|
+
if (unavailablePersonas.size > 0) {
|
|
2437
|
+
if (artifact.run_status === "completed") {
|
|
2438
|
+
ctx.addIssue({
|
|
2439
|
+
code: "custom",
|
|
2440
|
+
path: ["run_status"],
|
|
2441
|
+
message: REVIEW_ARTIFACT_CUSTOM_MESSAGES[7]
|
|
2442
|
+
});
|
|
2443
|
+
}
|
|
2444
|
+
artifact.dispatches.forEach((dispatch, index) => {
|
|
2445
|
+
if (dispatch.dispatch_outcome === "validation_unavailable" && dispatch.input_finding_count !== 0) {
|
|
2446
|
+
ctx.addIssue({
|
|
2447
|
+
code: "custom",
|
|
2448
|
+
path: ["dispatches", index, "input_finding_count"],
|
|
2449
|
+
message: REVIEW_ARTIFACT_CUSTOM_MESSAGES[6]
|
|
2450
|
+
});
|
|
2451
|
+
}
|
|
2452
|
+
});
|
|
2453
|
+
artifact.input_findings.forEach((finding, index) => {
|
|
2454
|
+
if (unavailablePersonas.has(finding.reviewer)) {
|
|
2455
|
+
ctx.addIssue({
|
|
2456
|
+
code: "custom",
|
|
2457
|
+
path: ["input_findings", index, "reviewer"],
|
|
2458
|
+
message: REVIEW_ARTIFACT_CUSTOM_MESSAGES[8]
|
|
2459
|
+
});
|
|
2460
|
+
}
|
|
2461
|
+
});
|
|
2462
|
+
}
|
|
2463
|
+
const admittedById = new Map;
|
|
2464
|
+
artifact.input_findings.forEach((finding, index) => {
|
|
2465
|
+
if (finding.record_type !== "admitted") {
|
|
2466
|
+
return;
|
|
2467
|
+
}
|
|
2468
|
+
if (admittedById.has(finding.input_id)) {
|
|
2469
|
+
ctx.addIssue({
|
|
2470
|
+
code: "custom",
|
|
2471
|
+
path: ["input_findings", index, "input_id"],
|
|
2472
|
+
message: REVIEW_ARTIFACT_CUSTOM_MESSAGES[12]
|
|
2473
|
+
});
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
admittedById.set(finding.input_id, finding.reviewer);
|
|
2477
|
+
});
|
|
2478
|
+
const admittedReviewers = new Set(admittedById.values());
|
|
2479
|
+
const eligibleAgreementPersonas = new Set(artifact.dispatches.filter((dispatch) => dispatch.dispatch_outcome === "findings" && admittedReviewers.has(dispatch.persona)).map((dispatch) => dispatch.persona));
|
|
2480
|
+
artifact.findings.forEach((finding, findingIndex) => {
|
|
2481
|
+
const citedAdmittedReviewers = new Set;
|
|
2482
|
+
finding.input_finding_ids.forEach((inputId, idIndex) => {
|
|
2483
|
+
const owner = admittedById.get(inputId);
|
|
2484
|
+
if (owner === undefined) {
|
|
2485
|
+
ctx.addIssue({
|
|
2486
|
+
code: "custom",
|
|
2487
|
+
path: ["findings", findingIndex, "input_finding_ids", idIndex],
|
|
2488
|
+
message: REVIEW_ARTIFACT_CUSTOM_MESSAGES[9]
|
|
2489
|
+
});
|
|
2490
|
+
return;
|
|
2491
|
+
}
|
|
2492
|
+
citedAdmittedReviewers.add(owner);
|
|
2493
|
+
});
|
|
2494
|
+
const seenSubmitters = new Set;
|
|
2495
|
+
finding.provenance.submitters.forEach((submitter, submitterIndex) => {
|
|
2496
|
+
if (seenSubmitters.has(submitter)) {
|
|
2497
|
+
ctx.addIssue({
|
|
2498
|
+
code: "custom",
|
|
2499
|
+
path: [
|
|
2500
|
+
"findings",
|
|
2501
|
+
findingIndex,
|
|
2502
|
+
"provenance",
|
|
2503
|
+
"submitters",
|
|
2504
|
+
submitterIndex
|
|
2505
|
+
],
|
|
2506
|
+
message: REVIEW_ARTIFACT_CUSTOM_MESSAGES[14]
|
|
2507
|
+
});
|
|
2508
|
+
}
|
|
2509
|
+
seenSubmitters.add(submitter);
|
|
2510
|
+
if (!citedAdmittedReviewers.has(submitter)) {
|
|
2511
|
+
ctx.addIssue({
|
|
2512
|
+
code: "custom",
|
|
2513
|
+
path: [
|
|
2514
|
+
"findings",
|
|
2515
|
+
findingIndex,
|
|
2516
|
+
"provenance",
|
|
2517
|
+
"submitters",
|
|
2518
|
+
submitterIndex
|
|
2519
|
+
],
|
|
2520
|
+
message: REVIEW_ARTIFACT_CUSTOM_MESSAGES[10]
|
|
2521
|
+
});
|
|
2522
|
+
}
|
|
2523
|
+
});
|
|
2524
|
+
for (const reviewer of citedAdmittedReviewers) {
|
|
2525
|
+
if (!seenSubmitters.has(reviewer)) {
|
|
2526
|
+
ctx.addIssue({
|
|
2527
|
+
code: "custom",
|
|
2528
|
+
path: ["findings", findingIndex, "provenance", "submitters"],
|
|
2529
|
+
message: REVIEW_ARTIFACT_CUSTOM_MESSAGES[13]
|
|
2530
|
+
});
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
const seenAgreementCredit = new Set;
|
|
2534
|
+
finding.provenance.agreement_credit.forEach((credit, creditIndex) => {
|
|
2535
|
+
if (seenAgreementCredit.has(credit)) {
|
|
2536
|
+
ctx.addIssue({
|
|
2537
|
+
code: "custom",
|
|
2538
|
+
path: [
|
|
2539
|
+
"findings",
|
|
2540
|
+
findingIndex,
|
|
2541
|
+
"provenance",
|
|
2542
|
+
"agreement_credit",
|
|
2543
|
+
creditIndex
|
|
2544
|
+
],
|
|
2545
|
+
message: REVIEW_ARTIFACT_CUSTOM_MESSAGES[15]
|
|
2546
|
+
});
|
|
2547
|
+
}
|
|
2548
|
+
seenAgreementCredit.add(credit);
|
|
2549
|
+
if (seenSubmitters.has(credit)) {
|
|
2550
|
+
ctx.addIssue({
|
|
2551
|
+
code: "custom",
|
|
2552
|
+
path: [
|
|
2553
|
+
"findings",
|
|
2554
|
+
findingIndex,
|
|
2555
|
+
"provenance",
|
|
2556
|
+
"agreement_credit",
|
|
2557
|
+
creditIndex
|
|
2558
|
+
],
|
|
2559
|
+
message: REVIEW_ARTIFACT_CUSTOM_MESSAGES[16]
|
|
2560
|
+
});
|
|
2561
|
+
}
|
|
2562
|
+
if (!eligibleAgreementPersonas.has(credit)) {
|
|
2563
|
+
ctx.addIssue({
|
|
2564
|
+
code: "custom",
|
|
2565
|
+
path: [
|
|
2566
|
+
"findings",
|
|
2567
|
+
findingIndex,
|
|
2568
|
+
"provenance",
|
|
2569
|
+
"agreement_credit",
|
|
2570
|
+
creditIndex
|
|
2571
|
+
],
|
|
2572
|
+
message: REVIEW_ARTIFACT_CUSTOM_MESSAGES[17]
|
|
2573
|
+
});
|
|
2574
|
+
}
|
|
2575
|
+
});
|
|
2576
|
+
});
|
|
2577
|
+
artifact.risk_coverage?.forEach((coverage, coverageIndex) => {
|
|
2578
|
+
if (!coverage.satisfied) {
|
|
2579
|
+
return;
|
|
2580
|
+
}
|
|
2581
|
+
const citedId = coverage.input_finding_id;
|
|
2582
|
+
if (citedId === undefined) {
|
|
2583
|
+
return;
|
|
2584
|
+
}
|
|
2585
|
+
const owner = admittedById.get(citedId);
|
|
2586
|
+
if (owner === undefined || unavailablePersonas.has(owner)) {
|
|
2587
|
+
ctx.addIssue({
|
|
2588
|
+
code: "custom",
|
|
2589
|
+
path: ["risk_coverage", coverageIndex, "input_finding_id"],
|
|
2590
|
+
message: REVIEW_ARTIFACT_CUSTOM_MESSAGES[11]
|
|
2591
|
+
});
|
|
2592
|
+
return;
|
|
2593
|
+
}
|
|
2594
|
+
if (owner === coverage.persona) {
|
|
2595
|
+
ctx.addIssue({
|
|
2596
|
+
code: "custom",
|
|
2597
|
+
path: ["risk_coverage", coverageIndex, "input_finding_id"],
|
|
2598
|
+
message: REVIEW_ARTIFACT_CUSTOM_MESSAGES[19]
|
|
2599
|
+
});
|
|
2600
|
+
return;
|
|
2601
|
+
}
|
|
2602
|
+
const lostDispatch = artifact.dispatches.find((dispatch) => dispatch.persona === coverage.persona);
|
|
2603
|
+
const surface = lostDispatch?.selection_surface ?? [];
|
|
2604
|
+
const covered = artifact.findings.some((finding) => finding.input_finding_ids.includes(citedId) && finding.validated !== false && surface.includes(finding.file));
|
|
2605
|
+
if (!covered) {
|
|
2606
|
+
ctx.addIssue({
|
|
2607
|
+
code: "custom",
|
|
2608
|
+
path: ["risk_coverage", coverageIndex, "input_finding_id"],
|
|
2609
|
+
message: REVIEW_ARTIFACT_CUSTOM_MESSAGES[18]
|
|
2610
|
+
});
|
|
2611
|
+
}
|
|
2612
|
+
});
|
|
2613
|
+
});
|
|
2614
|
+
var MAX_RAW_RISK_LENGTH = 1024;
|
|
2615
|
+
var RAW_FINDINGS_LIST_DESCRIPTION = "List of code review findings. Empty array if no issues found.";
|
|
2616
|
+
var RawReviewerSchema = ReviewerSchema.describe("Persona name that produced this output (e.g., 'correctness', 'security')");
|
|
2617
|
+
var RawHarnessSchema = HarnessSchema.describe("Harness that produced the artifact; populated by the parent orchestrator");
|
|
2618
|
+
var RawDispatchOutcomeSchema = DispatchOutcomeSchema.describe("What a persona returned: findings, empty, malformed, or never returned");
|
|
2619
|
+
var RawDispositionSchema = DispositionSchema.describe("What happened to an input finding: surviving, merged, suppressed, filtered, or rejected");
|
|
2620
|
+
var RawResidualRisksSchema = array(string().max(MAX_RAW_RISK_LENGTH)).max(MAX_PERSONAS).describe("Risks the reviewer noticed but could not confirm as findings");
|
|
2621
|
+
var RawTestingGapsSchema = array(string().max(MAX_RAW_RISK_LENGTH)).max(MAX_PERSONAS).describe("Missing test coverage the reviewer identified");
|
|
2622
|
+
var RawEvidenceStringSchema = BoundedEvidenceStringSchema.describe("Bounded code-grounded evidence; absolute POSIX, drive-letter, and UNC paths are rejected");
|
|
2623
|
+
var RawOverflowExcerptSchema = BoundedEvidenceStringSchema.describe("Bounded excerpt retained when evidence must be shortened");
|
|
2624
|
+
var RawOverflowEvidenceSchema = object({
|
|
2625
|
+
overflow: literal(true).describe("Explicit marker that the complete evidence did not fit in one bounded entry"),
|
|
2626
|
+
excerpt: RawOverflowExcerptSchema
|
|
2627
|
+
}).strict();
|
|
2628
|
+
var RawEvidenceSchema = array(union([RawEvidenceStringSchema, RawOverflowEvidenceSchema])).min(1).max(5).describe("Code-grounded evidence. At least 1 and at most 5 bounded entries; split evidence across entries or use an explicit overflow marker rather than silently truncating it.");
|
|
2629
|
+
var RawFindingFieldsSchema = object({
|
|
2630
|
+
title: FindingTitleSchema.describe("Short, specific issue title. 10 words or fewer."),
|
|
2631
|
+
severity: FindingSeveritySchema.describe("Issue severity level"),
|
|
2632
|
+
file: RepoRelativePathSchema.describe("Relative file path from repository root; absolute POSIX, drive-letter, and UNC paths are rejected"),
|
|
2633
|
+
line: LineNumberSchema.describe("Primary line number of the issue"),
|
|
2634
|
+
why_it_matters: boundedText(2048).describe("Non-empty impact and failure mode -- not 'what is wrong' but 'what breaks'"),
|
|
2635
|
+
autofix_class: AutofixClassSchema.describe("Reviewer's conservative recommendation for how this issue should be handled after synthesis"),
|
|
2636
|
+
owner: OwnerSchema.describe("Who should own the next action for this finding after synthesis"),
|
|
2637
|
+
requires_verification: boolean().describe("Whether any fix for this finding must be re-verified with targeted tests or a follow-up review pass"),
|
|
2638
|
+
suggested_fix: string().max(2048).nullable().optional().describe("Concrete minimal fix. Omit or null if no good fix is obvious -- a bad suggestion is worse than none."),
|
|
2639
|
+
confidence: number().min(0).max(1).describe("Reviewer confidence in this finding, calibrated per persona"),
|
|
2640
|
+
evidence: RawEvidenceSchema,
|
|
2641
|
+
pre_existing: boolean().describe("True if this issue exists in unchanged code unrelated to the current diff")
|
|
2642
|
+
}).strict();
|
|
2643
|
+
var SubAgentFindingSchema = RawFindingFieldsSchema;
|
|
2644
|
+
var ParentFindingSchema = RawFindingFieldsSchema.extend({
|
|
2645
|
+
disposition: RawDispositionSchema
|
|
2646
|
+
}).strict();
|
|
2647
|
+
var SubAgentReturnSchema = object({
|
|
2648
|
+
reviewer: RawReviewerSchema,
|
|
2649
|
+
findings: array(SubAgentFindingSchema).max(MAX_FINDINGS).describe(RAW_FINDINGS_LIST_DESCRIPTION),
|
|
2650
|
+
residual_risks: RawResidualRisksSchema,
|
|
2651
|
+
testing_gaps: RawTestingGapsSchema
|
|
2652
|
+
}).strict();
|
|
2653
|
+
var ParentRecordSchema = object({
|
|
2654
|
+
reviewer: RawReviewerSchema,
|
|
2655
|
+
harness: RawHarnessSchema,
|
|
2656
|
+
dispatch_outcome: RawDispatchOutcomeSchema,
|
|
2657
|
+
findings: array(ParentFindingSchema).max(MAX_FINDINGS).describe(RAW_FINDINGS_LIST_DESCRIPTION),
|
|
2658
|
+
residual_risks: RawResidualRisksSchema,
|
|
2659
|
+
testing_gaps: RawTestingGapsSchema
|
|
2415
2660
|
}).strict();
|
|
2416
2661
|
|
|
2417
|
-
// src/lib/
|
|
2662
|
+
// src/lib/review-return-validator.ts
|
|
2418
2663
|
import fs5 from "fs";
|
|
2664
|
+
var VALIDATE_REVIEW_RETURN_USAGE = "Usage: systematic validate-review-return";
|
|
2665
|
+
var MAX_REVIEW_RETURN_BYTES = 1024 * 1024;
|
|
2666
|
+
var MAX_PROJECTED_ISSUE_LINES = 8;
|
|
2667
|
+
var REVIEW_RETURN_VALID_MESSAGE = "Review return is valid";
|
|
2668
|
+
var REVIEW_RETURN_EMPTY_MESSAGE = "Review return is empty";
|
|
2669
|
+
var REVIEW_RETURN_INVALID_UTF8_MESSAGE = "Review return is not valid UTF-8";
|
|
2670
|
+
var REVIEW_RETURN_MALFORMED_JSON_MESSAGE = "Review return is not valid JSON";
|
|
2671
|
+
var REVIEW_RETURN_OVERSIZED_MESSAGE = "Review return exceeds the 1 MiB input limit";
|
|
2672
|
+
var REVIEW_RETURN_READ_FAILED_MESSAGE = "Review return could not be read from stdin";
|
|
2673
|
+
var REVIEW_RETURN_TTY_MESSAGE = "validate-review-return reads one JSON document from stdin; interactive input is not supported";
|
|
2674
|
+
function validateReviewReturnValue(value) {
|
|
2675
|
+
const result = SubAgentReturnSchema.safeParse(value);
|
|
2676
|
+
if (result.success)
|
|
2677
|
+
return { ok: true };
|
|
2678
|
+
const total = result.error.issues.length;
|
|
2679
|
+
const issues = result.error.issues.slice(0, MAX_PROJECTED_ISSUE_LINES).map((issue) => ({
|
|
2680
|
+
path: formatReviewArtifactIssuePath(issue.path),
|
|
2681
|
+
code: issue.code
|
|
2682
|
+
}));
|
|
2683
|
+
return { issues, ok: false, total };
|
|
2684
|
+
}
|
|
2685
|
+
function formatReviewReturnValidationFailure(total) {
|
|
2686
|
+
return `Review return validation failed: ${total} issue(s)`;
|
|
2687
|
+
}
|
|
2688
|
+
var READ_CHUNK_BYTES = 64 * 1024;
|
|
2689
|
+
function defaultReadChunk(fd, buffer, offset, length, position) {
|
|
2690
|
+
return fs5.readSync(fd, buffer, offset, length, position);
|
|
2691
|
+
}
|
|
2692
|
+
var TRANSIENT_READ_RETRY_MS = 1;
|
|
2693
|
+
function isTransientReadError(error) {
|
|
2694
|
+
if (typeof error !== "object" || error === null)
|
|
2695
|
+
return false;
|
|
2696
|
+
const code = error.code;
|
|
2697
|
+
return code === "EAGAIN" || code === "EWOULDBLOCK";
|
|
2698
|
+
}
|
|
2699
|
+
function sleepSync(milliseconds) {
|
|
2700
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
|
|
2701
|
+
}
|
|
2702
|
+
function readChunkWithRetry(fd, buffer, toRead, readChunk) {
|
|
2703
|
+
for (;; ) {
|
|
2704
|
+
try {
|
|
2705
|
+
return {
|
|
2706
|
+
bytesRead: readChunk(fd, buffer, 0, toRead, null),
|
|
2707
|
+
status: "ok"
|
|
2708
|
+
};
|
|
2709
|
+
} catch (error) {
|
|
2710
|
+
if (!isTransientReadError(error))
|
|
2711
|
+
return { status: "read-error" };
|
|
2712
|
+
sleepSync(TRANSIENT_READ_RETRY_MS);
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
function readBoundedStdin(fd, readChunk) {
|
|
2717
|
+
const chunks = [];
|
|
2718
|
+
let total = 0;
|
|
2719
|
+
while (true) {
|
|
2720
|
+
const remaining = MAX_REVIEW_RETURN_BYTES + 1 - total;
|
|
2721
|
+
if (remaining <= 0)
|
|
2722
|
+
return { status: "oversized" };
|
|
2723
|
+
const toRead = Math.min(READ_CHUNK_BYTES, remaining);
|
|
2724
|
+
const buffer = Buffer.allocUnsafe(toRead);
|
|
2725
|
+
const read = readChunkWithRetry(fd, buffer, toRead, readChunk);
|
|
2726
|
+
if (read.status === "read-error")
|
|
2727
|
+
return { status: "read-error" };
|
|
2728
|
+
if (read.bytesRead <= 0)
|
|
2729
|
+
break;
|
|
2730
|
+
total += read.bytesRead;
|
|
2731
|
+
chunks.push(buffer.subarray(0, read.bytesRead));
|
|
2732
|
+
if (total > MAX_REVIEW_RETURN_BYTES)
|
|
2733
|
+
return { status: "oversized" };
|
|
2734
|
+
}
|
|
2735
|
+
return { buffer: Buffer.concat(chunks, total), status: "ok" };
|
|
2736
|
+
}
|
|
2737
|
+
function isValidInvocation(argv) {
|
|
2738
|
+
const commandIndex = argv[0] === "systematic" ? 1 : 0;
|
|
2739
|
+
return argv[commandIndex] === "validate-review-return" && argv.length === commandIndex + 1;
|
|
2740
|
+
}
|
|
2741
|
+
function runReviewReturnValidator(options) {
|
|
2742
|
+
const outputSink = options.outputSink ?? ((message) => console.log(message));
|
|
2743
|
+
const errorSink = options.errorSink ?? ((message) => console.error(message));
|
|
2744
|
+
const fd = options.fd ?? 0;
|
|
2745
|
+
const isTTY = options.isTTY ?? (fd === 0 && process.stdin.isTTY === true);
|
|
2746
|
+
if (!isValidInvocation(options.argv)) {
|
|
2747
|
+
errorSink(VALIDATE_REVIEW_RETURN_USAGE);
|
|
2748
|
+
return 2;
|
|
2749
|
+
}
|
|
2750
|
+
if (isTTY) {
|
|
2751
|
+
errorSink(REVIEW_RETURN_TTY_MESSAGE);
|
|
2752
|
+
return 2;
|
|
2753
|
+
}
|
|
2754
|
+
const read = readBoundedStdin(fd, options.readChunk ?? defaultReadChunk);
|
|
2755
|
+
if (read.status === "read-error") {
|
|
2756
|
+
errorSink(REVIEW_RETURN_READ_FAILED_MESSAGE);
|
|
2757
|
+
return 2;
|
|
2758
|
+
}
|
|
2759
|
+
if (read.status === "oversized") {
|
|
2760
|
+
errorSink(REVIEW_RETURN_OVERSIZED_MESSAGE);
|
|
2761
|
+
return 1;
|
|
2762
|
+
}
|
|
2763
|
+
let text;
|
|
2764
|
+
try {
|
|
2765
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(read.buffer);
|
|
2766
|
+
} catch {
|
|
2767
|
+
errorSink(REVIEW_RETURN_INVALID_UTF8_MESSAGE);
|
|
2768
|
+
return 1;
|
|
2769
|
+
}
|
|
2770
|
+
if (text.trim() === "") {
|
|
2771
|
+
errorSink(REVIEW_RETURN_EMPTY_MESSAGE);
|
|
2772
|
+
return 1;
|
|
2773
|
+
}
|
|
2774
|
+
let value;
|
|
2775
|
+
try {
|
|
2776
|
+
value = JSON.parse(text);
|
|
2777
|
+
} catch {
|
|
2778
|
+
errorSink(REVIEW_RETURN_MALFORMED_JSON_MESSAGE);
|
|
2779
|
+
return 1;
|
|
2780
|
+
}
|
|
2781
|
+
const validation = validateReviewReturnValue(value);
|
|
2782
|
+
if (validation.ok) {
|
|
2783
|
+
outputSink(REVIEW_RETURN_VALID_MESSAGE);
|
|
2784
|
+
return 0;
|
|
2785
|
+
}
|
|
2786
|
+
for (const issue of validation.issues) {
|
|
2787
|
+
errorSink(`${issue.path} ${issue.code}`);
|
|
2788
|
+
}
|
|
2789
|
+
errorSink(formatReviewReturnValidationFailure(validation.total));
|
|
2790
|
+
return 1;
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
// src/lib/setup.ts
|
|
2794
|
+
import fs6 from "fs";
|
|
2419
2795
|
import path4 from "path";
|
|
2420
2796
|
var SYSTEMATIC_PACKAGE_NAME = "@fro.bot/systematic";
|
|
2421
2797
|
var PI_PACKAGE_IDENTIFIER = `npm:${SYSTEMATIC_PACKAGE_NAME}`;
|
|
@@ -2426,10 +2802,10 @@ function createSetupError(message) {
|
|
|
2426
2802
|
return error;
|
|
2427
2803
|
}
|
|
2428
2804
|
var DEFAULT_OPS = {
|
|
2429
|
-
writeFileSync:
|
|
2430
|
-
renameSync:
|
|
2431
|
-
unlinkSync:
|
|
2432
|
-
chmodSync:
|
|
2805
|
+
writeFileSync: fs6.writeFileSync,
|
|
2806
|
+
renameSync: fs6.renameSync,
|
|
2807
|
+
unlinkSync: fs6.unlinkSync,
|
|
2808
|
+
chmodSync: fs6.chmodSync
|
|
2433
2809
|
};
|
|
2434
2810
|
function isRecord2(value) {
|
|
2435
2811
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -2451,7 +2827,7 @@ function isPiSystematicIdentifier(identifier) {
|
|
|
2451
2827
|
}
|
|
2452
2828
|
function lstatOrNull(targetPath) {
|
|
2453
2829
|
try {
|
|
2454
|
-
return
|
|
2830
|
+
return fs6.lstatSync(targetPath);
|
|
2455
2831
|
} catch (error) {
|
|
2456
2832
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
2457
2833
|
return null;
|
|
@@ -2460,8 +2836,8 @@ function lstatOrNull(targetPath) {
|
|
|
2460
2836
|
}
|
|
2461
2837
|
}
|
|
2462
2838
|
function assertRealpathUnderCwd(dir, cwd) {
|
|
2463
|
-
const realDir =
|
|
2464
|
-
const realCwd =
|
|
2839
|
+
const realDir = fs6.realpathSync(dir);
|
|
2840
|
+
const realCwd = fs6.realpathSync(cwd);
|
|
2465
2841
|
const relative = path4.relative(realCwd, realDir);
|
|
2466
2842
|
if (relative.startsWith("..") || path4.isAbsolute(relative)) {
|
|
2467
2843
|
throw createSetupError(`Refusing to write under ${dir}: resolved directory escapes the project root`);
|
|
@@ -2476,7 +2852,7 @@ function assertParentTrusted(parentDir, cwd) {
|
|
|
2476
2852
|
assertRealpathUnderCwd(parentDir, cwd);
|
|
2477
2853
|
return;
|
|
2478
2854
|
}
|
|
2479
|
-
|
|
2855
|
+
fs6.mkdirSync(parentDir, { recursive: true });
|
|
2480
2856
|
assertRealpathUnderCwd(parentDir, cwd);
|
|
2481
2857
|
}
|
|
2482
2858
|
var OPENCODE_TARGET_CANDIDATES = [
|
|
@@ -2498,7 +2874,7 @@ function resolveOpenCodeTargetPath(cwd) {
|
|
|
2498
2874
|
return path4.join(cwd, "opencode.jsonc");
|
|
2499
2875
|
}
|
|
2500
2876
|
var IS_WINDOWS = process.platform === "win32";
|
|
2501
|
-
var OPEN_FLAGS = IS_WINDOWS ?
|
|
2877
|
+
var OPEN_FLAGS = IS_WINDOWS ? fs6.constants.O_RDONLY : fs6.constants.O_RDONLY | fs6.constants.O_NOFOLLOW | fs6.constants.O_NONBLOCK;
|
|
2502
2878
|
function assertWindowsPreOpenTrust(targetPath) {
|
|
2503
2879
|
const preStat = lstatOrNull(targetPath);
|
|
2504
2880
|
if (preStat && (preStat.isSymbolicLink() || !preStat.isFile())) {
|
|
@@ -2510,7 +2886,7 @@ function openTrustedExisting(targetPath) {
|
|
|
2510
2886
|
assertWindowsPreOpenTrust(targetPath);
|
|
2511
2887
|
let fd;
|
|
2512
2888
|
try {
|
|
2513
|
-
fd =
|
|
2889
|
+
fd = fs6.openSync(targetPath, OPEN_FLAGS);
|
|
2514
2890
|
} catch (error) {
|
|
2515
2891
|
if (error instanceof Error && "code" in error) {
|
|
2516
2892
|
if (error.code === "ENOENT")
|
|
@@ -2522,13 +2898,13 @@ function openTrustedExisting(targetPath) {
|
|
|
2522
2898
|
throw error;
|
|
2523
2899
|
}
|
|
2524
2900
|
try {
|
|
2525
|
-
const stat =
|
|
2901
|
+
const stat = fs6.fstatSync(fd);
|
|
2526
2902
|
if (!stat.isFile()) {
|
|
2527
2903
|
throw createSetupError(`Refusing to read ${targetPath}: not a regular file`);
|
|
2528
2904
|
}
|
|
2529
|
-
return { bytes:
|
|
2905
|
+
return { bytes: fs6.readFileSync(fd), mode: stat.mode & 511 };
|
|
2530
2906
|
} finally {
|
|
2531
|
-
|
|
2907
|
+
fs6.closeSync(fd);
|
|
2532
2908
|
}
|
|
2533
2909
|
}
|
|
2534
2910
|
function atomicWrite(targetPath, content, originalBytes, mode, ops) {
|
|
@@ -2560,7 +2936,7 @@ function makeTempPath(parentDir, basename) {
|
|
|
2560
2936
|
return path4.join(parentDir, `.${basename}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
2561
2937
|
}
|
|
2562
2938
|
function cleanupTemp(tempPath, ops) {
|
|
2563
|
-
if (!
|
|
2939
|
+
if (!fs6.existsSync(tempPath))
|
|
2564
2940
|
return;
|
|
2565
2941
|
try {
|
|
2566
2942
|
ops.unlinkSync(tempPath);
|
|
@@ -2714,9 +3090,9 @@ function setupHarness(harness, cwd, opsOverride) {
|
|
|
2714
3090
|
function readPackageMetadata(packageRoot) {
|
|
2715
3091
|
try {
|
|
2716
3092
|
const packageJsonPath = path5.join(packageRoot, "package.json");
|
|
2717
|
-
if (!
|
|
3093
|
+
if (!fs7.existsSync(packageJsonPath))
|
|
2718
3094
|
return {};
|
|
2719
|
-
const content =
|
|
3095
|
+
const content = fs7.readFileSync(packageJsonPath, "utf8");
|
|
2720
3096
|
const parsed = JSON.parse(content);
|
|
2721
3097
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2722
3098
|
return {};
|
|
@@ -2749,6 +3125,12 @@ Commands:
|
|
|
2749
3125
|
CI, issues, or fixtures. Not for the ce:review parent's own run artifact.
|
|
2750
3126
|
Exit statuses: 0 valid artifact, 1 validation failure,
|
|
2751
3127
|
2 operational failure, 3 legacy artifact with no schema_version
|
|
3128
|
+
validate-review-return Validate one raw ce:review persona return read from stdin
|
|
3129
|
+
Reads exactly one JSON document (max 1 MiB) and checks it against the
|
|
3130
|
+
raw reviewer return schema. No files are read or written; accepts no
|
|
3131
|
+
arguments or output flags.
|
|
3132
|
+
Exit statuses: 0 valid return, 1 malformed/empty/oversized/schema-invalid,
|
|
3133
|
+
2 operational failure (usage, TTY, stdin read)
|
|
2752
3134
|
config [subcommand] Configuration management
|
|
2753
3135
|
show [--json] Show configuration (--json for a machine-readable resolved view)
|
|
2754
3136
|
path Print config file locations
|
|
@@ -2768,6 +3150,7 @@ Examples:
|
|
|
2768
3150
|
systematic capabilities
|
|
2769
3151
|
systematic validate-review-artifact .context/systematic/ce-review/review-summary.json
|
|
2770
3152
|
systematic validate-review-artifact --allow-outside-artifact-root /path/to/external-artifact.json
|
|
3153
|
+
systematic validate-review-return < reviewer-return.json
|
|
2771
3154
|
systematic list agents
|
|
2772
3155
|
systematic config show
|
|
2773
3156
|
systematic config show --json
|
|
@@ -2901,7 +3284,7 @@ function collectConfigMetadata(roots, injected) {
|
|
|
2901
3284
|
}
|
|
2902
3285
|
function resolveCapabilityRootPath(root) {
|
|
2903
3286
|
try {
|
|
2904
|
-
return
|
|
3287
|
+
return fs7.realpathSync(root);
|
|
2905
3288
|
} catch {
|
|
2906
3289
|
return path5.resolve(root);
|
|
2907
3290
|
}
|
|
@@ -2999,6 +3382,19 @@ function runValidateReviewArtifact(options) {
|
|
|
2999
3382
|
function runValidateReviewArtifactCli(options) {
|
|
3000
3383
|
return runValidateReviewArtifact(options);
|
|
3001
3384
|
}
|
|
3385
|
+
function runValidateReviewReturn(options) {
|
|
3386
|
+
return runReviewReturnValidator({
|
|
3387
|
+
argv: options.argv,
|
|
3388
|
+
fd: options.fd ?? 0,
|
|
3389
|
+
isTTY: options.isTTY ?? process.stdin.isTTY === true,
|
|
3390
|
+
readChunk: options.readChunk,
|
|
3391
|
+
outputSink: options.outputSink ?? ((message) => console.log(message)),
|
|
3392
|
+
errorSink: options.errorSink ?? ((message) => console.error(message))
|
|
3393
|
+
});
|
|
3394
|
+
}
|
|
3395
|
+
function runValidateReviewReturnCli(options) {
|
|
3396
|
+
return runValidateReviewReturn(options);
|
|
3397
|
+
}
|
|
3002
3398
|
function isHarness(value) {
|
|
3003
3399
|
return value === "opencode" || value === "pi";
|
|
3004
3400
|
}
|
|
@@ -3187,15 +3583,15 @@ function configShow(options) {
|
|
|
3187
3583
|
`);
|
|
3188
3584
|
console.log(` User config: ${paths.userConfig}`);
|
|
3189
3585
|
console.log(` Project config: ${paths.projectConfig}`);
|
|
3190
|
-
if (
|
|
3586
|
+
if (fs7.existsSync(paths.projectConfig)) {
|
|
3191
3587
|
console.log(`
|
|
3192
3588
|
Project configuration:`);
|
|
3193
|
-
console.log(
|
|
3589
|
+
console.log(fs7.readFileSync(paths.projectConfig, "utf-8"));
|
|
3194
3590
|
}
|
|
3195
|
-
if (
|
|
3591
|
+
if (fs7.existsSync(paths.userConfig)) {
|
|
3196
3592
|
console.log(`
|
|
3197
3593
|
User configuration:`);
|
|
3198
|
-
console.log(
|
|
3594
|
+
console.log(fs7.readFileSync(paths.userConfig, "utf-8"));
|
|
3199
3595
|
}
|
|
3200
3596
|
printResolvedSection();
|
|
3201
3597
|
}
|
|
@@ -3399,6 +3795,17 @@ function runLegacyCli(args) {
|
|
|
3399
3795
|
process.exit(status);
|
|
3400
3796
|
break;
|
|
3401
3797
|
}
|
|
3798
|
+
case "validate-review-return": {
|
|
3799
|
+
const status = runValidateReviewReturnCli({
|
|
3800
|
+
argv: ["systematic", ...args],
|
|
3801
|
+
errorSink: console.error,
|
|
3802
|
+
isTTY: process.stdin.isTTY === true,
|
|
3803
|
+
outputSink: console.log
|
|
3804
|
+
});
|
|
3805
|
+
if (status !== 0)
|
|
3806
|
+
process.exit(status);
|
|
3807
|
+
break;
|
|
3808
|
+
}
|
|
3402
3809
|
case "setup":
|
|
3403
3810
|
setupCommand(args.slice(1));
|
|
3404
3811
|
break;
|
|
@@ -3442,5 +3849,6 @@ if (import.meta.main) {
|
|
|
3442
3849
|
}
|
|
3443
3850
|
export {
|
|
3444
3851
|
runCapabilitiesCli,
|
|
3445
|
-
runValidateReviewArtifactCli
|
|
3852
|
+
runValidateReviewArtifactCli,
|
|
3853
|
+
runValidateReviewReturnCli
|
|
3446
3854
|
};
|