@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/dist/cli.js CHANGED
@@ -33,7 +33,7 @@ import {
33
33
  findCommandsInDir,
34
34
  findSkillsInDir,
35
35
  discoverSkills
36
- } from "./index-y33enbkc.js";
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 fs6 from "fs";
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.5.4/node_modules/zod/v4/classic/iso.js
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: DispatchOutcomeSchema,
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: number().int().positive(),
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/setup.ts
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: fs5.writeFileSync,
2430
- renameSync: fs5.renameSync,
2431
- unlinkSync: fs5.unlinkSync,
2432
- chmodSync: fs5.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 fs5.lstatSync(targetPath);
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 = fs5.realpathSync(dir);
2464
- const realCwd = fs5.realpathSync(cwd);
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
- fs5.mkdirSync(parentDir, { recursive: true });
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 ? fs5.constants.O_RDONLY : fs5.constants.O_RDONLY | fs5.constants.O_NOFOLLOW | fs5.constants.O_NONBLOCK;
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 = fs5.openSync(targetPath, OPEN_FLAGS);
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 = fs5.fstatSync(fd);
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: fs5.readFileSync(fd), mode: stat.mode & 511 };
2905
+ return { bytes: fs6.readFileSync(fd), mode: stat.mode & 511 };
2530
2906
  } finally {
2531
- fs5.closeSync(fd);
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 (!fs5.existsSync(tempPath))
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 (!fs6.existsSync(packageJsonPath))
3093
+ if (!fs7.existsSync(packageJsonPath))
2718
3094
  return {};
2719
- const content = fs6.readFileSync(packageJsonPath, "utf8");
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 fs6.realpathSync(root);
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 (fs6.existsSync(paths.projectConfig)) {
3586
+ if (fs7.existsSync(paths.projectConfig)) {
3191
3587
  console.log(`
3192
3588
  Project configuration:`);
3193
- console.log(fs6.readFileSync(paths.projectConfig, "utf-8"));
3589
+ console.log(fs7.readFileSync(paths.projectConfig, "utf-8"));
3194
3590
  }
3195
- if (fs6.existsSync(paths.userConfig)) {
3591
+ if (fs7.existsSync(paths.userConfig)) {
3196
3592
  console.log(`
3197
3593
  User configuration:`);
3198
- console.log(fs6.readFileSync(paths.userConfig, "utf-8"));
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
  };