@fro.bot/systematic 3.12.3 → 3.13.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/ATTRIBUTIONS.md CHANGED
@@ -272,7 +272,7 @@ The following is the full Apache License, Version 2.0 text, reproduced here in c
272
272
  ## vercel-labs/agent-browser — Apache-2.0
273
273
 
274
274
  **Source repository:** [`vercel-labs/agent-browser`](https://github.com/vercel-labs/agent-browser)
275
- **Pinned version:** `v0.33.1` (npm `agent-browser@0.33.1`)
275
+ **Pinned version:** `v0.34.0` (npm `agent-browser@0.34.0`)
276
276
  **License:** Apache-2.0
277
277
  **Copyright:** Copyright 2025 Vercel Inc.
278
278
 
@@ -296,7 +296,7 @@ This is fully automated — no human adaptation pass is required, in contrast to
296
296
 
297
297
  ### Upstream Apache-2.0 license text
298
298
 
299
- The following is the full Apache License, Version 2.0 text from `agent-browser@0.33.1`, reproduced here in compliance with the license's notice requirements:
299
+ The following is the full Apache License, Version 2.0 text from `agent-browser@0.34.0`, reproduced here in compliance with the license's notice requirements:
300
300
 
301
301
  ```
302
302
  Apache License
package/dist/cli.d.ts CHANGED
@@ -17,4 +17,11 @@ interface CapabilityCliOptions {
17
17
  readonly errorSink?: (message: string) => void;
18
18
  }
19
19
  export declare function runCapabilitiesCli(options: CapabilityCliOptions): number;
20
+ interface ValidateReviewArtifactCliOptions {
21
+ readonly argv: readonly string[];
22
+ readonly cwd?: string;
23
+ readonly outputSink?: (message: string) => void;
24
+ readonly errorSink?: (message: string) => void;
25
+ }
26
+ export declare function runValidateReviewArtifactCli(options: ValidateReviewArtifactCliOptions): number;
20
27
  export {};
package/dist/cli.js CHANGED
@@ -3,6 +3,7 @@
3
3
  import {
4
4
  applyEdits,
5
5
  discoverSkills,
6
+ exports_external,
6
7
  extractString,
7
8
  findAgentsInDir,
8
9
  findCommandsInDir,
@@ -13,7 +14,7 @@ import {
13
14
  parse,
14
15
  parseFrontmatter,
15
16
  parseTree
16
- } from "./index-h26p98ny.js";
17
+ } from "./index-0stf3ag0.js";
17
18
 
18
19
  // src/cli.ts
19
20
  import fs5 from "fs";
@@ -1977,6 +1978,162 @@ function cleanup(agentsRoot, configOptions) {
1977
1978
  return { status: "ok" };
1978
1979
  }
1979
1980
 
1981
+ // src/lib/review-artifact-schema.ts
1982
+ var MAX_REVIEWER_LENGTH = 64;
1983
+ var MAX_RUN_ID_LENGTH = 64;
1984
+ var MAX_INPUT_ID_LENGTH = 128;
1985
+ var MAX_REASON_LENGTH = 2048;
1986
+ var MAX_FINDINGS = 32;
1987
+ var MAX_PERSONAS = 64;
1988
+ var REVIEW_ARTIFACT_CUSTOM_MESSAGES = [
1989
+ "severity count must match rejected finding count",
1990
+ "filtered findings require a validation reason"
1991
+ ];
1992
+ var boundedText = (maxLength) => exports_external.string().min(1).max(maxLength).regex(/\S/);
1993
+ var DispatchOutcomeSchema = exports_external.enum([
1994
+ "findings",
1995
+ "empty",
1996
+ "malformed",
1997
+ "never_returned"
1998
+ ]);
1999
+ var DispositionSchema = exports_external.enum([
2000
+ "surviving",
2001
+ "merged",
2002
+ "suppressed",
2003
+ "filtered",
2004
+ "rejected"
2005
+ ]);
2006
+ var AdmittedDispositionSchema = DispositionSchema.exclude(["rejected"]);
2007
+ var HarnessSchema = exports_external.enum(["opencode", "pi", "claude-code"]);
2008
+ var RepoRelativePathSchema = boundedText(256).regex(/^(?!\/)(?![A-Za-z]:[\\/])(?!\\).+/);
2009
+ var ReviewerSchema = boundedText(MAX_REVIEWER_LENGTH);
2010
+ var ReasonSchema = boundedText(MAX_REASON_LENGTH);
2011
+ var FindingTitleSchema = boundedText(256);
2012
+ var SeveritySchema = exports_external.enum(["P0", "P1", "P2", "P3", "unknown"]);
2013
+ var FindingSeveritySchema = SeveritySchema.exclude(["unknown"]);
2014
+ var AutofixClassSchema = exports_external.enum([
2015
+ "safe_auto",
2016
+ "gated_auto",
2017
+ "manual",
2018
+ "advisory"
2019
+ ]);
2020
+ var OwnerSchema = exports_external.enum([
2021
+ "review-fixer",
2022
+ "downstream-resolver",
2023
+ "human",
2024
+ "release"
2025
+ ]);
2026
+ var BoundedEvidenceStringSchema = boundedText(500).regex(/^(?!\/)(?![A-Za-z]:[\\/])(?!\\).+/);
2027
+ var OverflowEvidenceSchema = exports_external.object({
2028
+ overflow: exports_external.literal(true),
2029
+ excerpt: BoundedEvidenceStringSchema
2030
+ }).strict();
2031
+ var EvidenceSchema = exports_external.array(exports_external.union([BoundedEvidenceStringSchema, OverflowEvidenceSchema])).min(1).max(5);
2032
+ var AdmittedInputFindingSchema = exports_external.object({
2033
+ record_type: exports_external.literal("admitted"),
2034
+ input_id: boundedText(MAX_INPUT_ID_LENGTH),
2035
+ reviewer: ReviewerSchema,
2036
+ confidence: exports_external.number().min(0).max(1),
2037
+ disposition: AdmittedDispositionSchema,
2038
+ reason: ReasonSchema
2039
+ }).strict();
2040
+ var RejectedInputFindingSchema = exports_external.object({
2041
+ record_type: exports_external.literal("rejected_summary"),
2042
+ reviewer: ReviewerSchema,
2043
+ dispatch_outcome: DispatchOutcomeSchema,
2044
+ rejected_finding_count: exports_external.number().int().positive().max(MAX_FINDINGS),
2045
+ rejected_severities: exports_external.array(SeveritySchema).max(MAX_FINDINGS),
2046
+ disposition: DispositionSchema.extract(["rejected"]),
2047
+ reason: ReasonSchema
2048
+ }).strict().superRefine((row, ctx) => {
2049
+ if (row.rejected_severities.length !== row.rejected_finding_count) {
2050
+ ctx.addIssue({
2051
+ code: "custom",
2052
+ path: ["rejected_severities"],
2053
+ message: REVIEW_ARTIFACT_CUSTOM_MESSAGES[0]
2054
+ });
2055
+ }
2056
+ });
2057
+ var InputFindingSchema = exports_external.discriminatedUnion("record_type", [
2058
+ AdmittedInputFindingSchema,
2059
+ RejectedInputFindingSchema
2060
+ ]);
2061
+ var ProvenanceSchema = exports_external.object({
2062
+ fingerprint: boundedText(512),
2063
+ submitters: exports_external.array(ReviewerSchema).max(MAX_PERSONAS),
2064
+ agreement_credit: exports_external.array(ReviewerSchema).max(MAX_PERSONAS)
2065
+ }).strict();
2066
+ var SynthesizedFindingFieldsSchema = exports_external.object({
2067
+ title: FindingTitleSchema,
2068
+ severity: FindingSeveritySchema,
2069
+ file: RepoRelativePathSchema,
2070
+ line: exports_external.number().int().positive(),
2071
+ why_it_matters: boundedText(2048),
2072
+ autofix_class: AutofixClassSchema,
2073
+ owner: OwnerSchema,
2074
+ requires_verification: exports_external.boolean(),
2075
+ confidence: exports_external.number().min(0).max(1),
2076
+ evidence: EvidenceSchema,
2077
+ pre_existing: exports_external.boolean(),
2078
+ suggested_fix: exports_external.string().max(2048).nullable().optional(),
2079
+ validated: exports_external.boolean().optional(),
2080
+ validation_reason: ReasonSchema.optional(),
2081
+ input_finding_ids: exports_external.array(boundedText(MAX_INPUT_ID_LENGTH)).min(1).max(MAX_FINDINGS),
2082
+ provenance: ProvenanceSchema
2083
+ }).strict();
2084
+ var SynthesizedFindingSchema = SynthesizedFindingFieldsSchema.superRefine((finding, ctx) => {
2085
+ if (finding.validated === false && finding.validation_reason === undefined) {
2086
+ ctx.addIssue({
2087
+ code: "custom",
2088
+ path: ["validation_reason"],
2089
+ message: REVIEW_ARTIFACT_CUSTOM_MESSAGES[1]
2090
+ });
2091
+ }
2092
+ });
2093
+ var DispatchSchema = exports_external.object({
2094
+ persona: ReviewerSchema,
2095
+ dispatch_outcome: DispatchOutcomeSchema,
2096
+ input_finding_count: exports_external.number().int().nonnegative().max(MAX_FINDINGS),
2097
+ rejection_reason: ReasonSchema.optional()
2098
+ }).strict();
2099
+ var DispositionCountsSchema = exports_external.object({
2100
+ surviving: exports_external.number().int().nonnegative().max(MAX_FINDINGS * MAX_PERSONAS),
2101
+ merged: exports_external.number().int().nonnegative().max(MAX_FINDINGS * MAX_PERSONAS),
2102
+ suppressed: exports_external.number().int().nonnegative().max(MAX_FINDINGS * MAX_PERSONAS),
2103
+ filtered: exports_external.number().int().nonnegative().max(MAX_FINDINGS * MAX_PERSONAS),
2104
+ rejected: exports_external.number().int().nonnegative().max(MAX_FINDINGS * MAX_PERSONAS)
2105
+ }).strict();
2106
+ var CoverageSchema = exports_external.object({
2107
+ reviewers: exports_external.number().int().nonnegative().max(MAX_PERSONAS).optional(),
2108
+ validators: exports_external.number().int().nonnegative().max(MAX_PERSONAS).optional(),
2109
+ residual_risks: exports_external.array(ReasonSchema).max(MAX_PERSONAS),
2110
+ testing_gaps: exports_external.array(ReasonSchema).max(MAX_PERSONAS),
2111
+ failed_reviewers: exports_external.array(ReviewerSchema).max(MAX_PERSONAS),
2112
+ validator_failures: exports_external.array(ReasonSchema).max(MAX_PERSONAS),
2113
+ intent_uncertainty: exports_external.array(ReasonSchema).max(MAX_PERSONAS)
2114
+ }).strict();
2115
+ var ReviewArtifactSchema = exports_external.object({
2116
+ schema_version: exports_external.literal(1),
2117
+ run_id: boundedText(MAX_RUN_ID_LENGTH),
2118
+ mode: exports_external.enum(["interactive", "autofix", "headless"]),
2119
+ harness: HarnessSchema,
2120
+ run_status: exports_external.enum([
2121
+ "in_progress",
2122
+ "completed",
2123
+ "degraded",
2124
+ "abnormal"
2125
+ ]),
2126
+ verdict: boundedText(256),
2127
+ dispatches: exports_external.array(DispatchSchema).max(MAX_PERSONAS),
2128
+ input_findings: exports_external.array(InputFindingSchema).max(MAX_FINDINGS * MAX_PERSONAS),
2129
+ findings: exports_external.array(SynthesizedFindingSchema).max(MAX_FINDINGS),
2130
+ disposition_counts: DispositionCountsSchema,
2131
+ applied_fixes: exports_external.array(ReasonSchema).max(MAX_FINDINGS),
2132
+ residual_actionable_work: exports_external.array(ReasonSchema).max(MAX_FINDINGS),
2133
+ advisory_outputs: exports_external.array(ReasonSchema).max(MAX_FINDINGS),
2134
+ coverage: CoverageSchema
2135
+ }).strict();
2136
+
1980
2137
  // src/lib/setup.ts
1981
2138
  import fs4 from "fs";
1982
2139
  import path3 from "path";
@@ -2304,6 +2461,11 @@ Usage:
2304
2461
  Commands:
2305
2462
  list [type] List available skills, agents, or commands
2306
2463
  capabilities Read-only standalone-CLI observation; not a host-runtime or canonical-registry view
2464
+ validate-review-artifact <path>
2465
+ Validate a ce:review run artifact
2466
+ The <path> argument is required by design; no artifact discovery is performed.
2467
+ Exit statuses: 0 valid artifact, 1 validation failure,
2468
+ 2 operational failure, 3 legacy artifact with no schema_version
2307
2469
  config [subcommand] Configuration management
2308
2470
  show Show configuration
2309
2471
  path Print config file locations
@@ -2321,6 +2483,7 @@ Options:
2321
2483
  Examples:
2322
2484
  systematic list skills
2323
2485
  systematic capabilities
2486
+ systematic validate-review-artifact .context/systematic/ce-review/review-summary.json
2324
2487
  systematic list agents
2325
2488
  systematic config show
2326
2489
  systematic setup --harness opencode
@@ -2343,6 +2506,8 @@ Scope:
2343
2506
  project <cwd>/.pi/agents (default)
2344
2507
  global $PI_CODING_AGENT_DIR/agents or ~/.pi/agent/agents
2345
2508
  `;
2509
+ var VALIDATE_REVIEW_ARTIFACT_USAGE = "Usage: systematic validate-review-artifact <path>";
2510
+ var REVIEW_ARTIFACT_SCHEMA_RELATIVE_PATH = "skills/ce-review/references/review-summary-schema.json";
2346
2511
  function defaultCapabilityRoots() {
2347
2512
  const configDir = process.env.XDG_CONFIG_HOME ? path4.join(process.env.XDG_CONFIG_HOME, "opencode") : path4.join(os2.homedir(), ".config/opencode");
2348
2513
  return {
@@ -2498,6 +2663,151 @@ function runCapabilities(options) {
2498
2663
  function runCapabilitiesCli(options) {
2499
2664
  return runCapabilities(options);
2500
2665
  }
2666
+ function hasParentDirectoryTraversal(input) {
2667
+ return input.split(/[\\/]+/).some((segment) => segment === "..");
2668
+ }
2669
+ function pathContainsSymlink(candidate) {
2670
+ let current = path4.parse(candidate).root;
2671
+ const relative2 = path4.relative(current, candidate);
2672
+ for (const segment of relative2.split(path4.sep)) {
2673
+ if (!segment)
2674
+ continue;
2675
+ current = path4.join(current, segment);
2676
+ try {
2677
+ if (fs5.lstatSync(current).isSymbolicLink())
2678
+ return true;
2679
+ } catch {
2680
+ return false;
2681
+ }
2682
+ }
2683
+ return false;
2684
+ }
2685
+ function isWithinDirectory(candidate, directory) {
2686
+ const relative2 = path4.relative(directory, candidate);
2687
+ return relative2 !== "" && relative2 !== ".." && !relative2.startsWith(`..${path4.sep}`) && !path4.isAbsolute(relative2);
2688
+ }
2689
+ function resolveReviewArtifactPath(input, cwd) {
2690
+ if (hasParentDirectoryTraversal(input)) {
2691
+ return {
2692
+ message: "Review artifact path must not contain parent-directory traversal",
2693
+ ok: false
2694
+ };
2695
+ }
2696
+ const artifactRoot = path4.resolve(cwd, ".context", "systematic", "ce-review");
2697
+ let canonicalRoot;
2698
+ try {
2699
+ canonicalRoot = fs5.realpathSync(artifactRoot);
2700
+ if (!fs5.statSync(canonicalRoot).isDirectory()) {
2701
+ return {
2702
+ message: "Review artifact directory is not a directory",
2703
+ ok: false
2704
+ };
2705
+ }
2706
+ } catch {
2707
+ return { message: "Review artifact directory is unavailable", ok: false };
2708
+ }
2709
+ const candidate = path4.resolve(cwd, input);
2710
+ if (pathContainsSymlink(candidate)) {
2711
+ return {
2712
+ message: "Review artifact path must not contain symlinks",
2713
+ ok: false
2714
+ };
2715
+ }
2716
+ let canonicalTarget;
2717
+ try {
2718
+ canonicalTarget = fs5.realpathSync(candidate);
2719
+ } catch {
2720
+ return { message: "Review artifact file was not found", ok: false };
2721
+ }
2722
+ if (!isWithinDirectory(canonicalTarget, canonicalRoot)) {
2723
+ return {
2724
+ message: "Review artifact path must remain inside .context/systematic/ce-review",
2725
+ ok: false
2726
+ };
2727
+ }
2728
+ try {
2729
+ if (!fs5.lstatSync(canonicalTarget).isFile()) {
2730
+ return {
2731
+ message: "Review artifact target is not a regular file",
2732
+ ok: false
2733
+ };
2734
+ }
2735
+ } catch {
2736
+ return { message: "Review artifact file was not found", ok: false };
2737
+ }
2738
+ return { ok: true, path: canonicalTarget };
2739
+ }
2740
+ function formatReviewArtifactIssuePath(issuePath) {
2741
+ if (issuePath.length === 0)
2742
+ return "$";
2743
+ return issuePath.map((segment) => typeof segment === "number" ? String(segment) : segment).join(".");
2744
+ }
2745
+ function validateReviewArtifactArgument(argv) {
2746
+ const commandIndex = argv[0] === "systematic" ? 1 : 0;
2747
+ if (argv[commandIndex] !== "validate-review-artifact")
2748
+ return;
2749
+ if (argv.length !== commandIndex + 2)
2750
+ return;
2751
+ return argv[commandIndex + 1];
2752
+ }
2753
+ function readReviewArtifact(filePath) {
2754
+ let content;
2755
+ try {
2756
+ content = fs5.readFileSync(filePath, "utf8");
2757
+ } catch {
2758
+ return { message: "Review artifact file could not be read", ok: false };
2759
+ }
2760
+ try {
2761
+ return { ok: true, value: JSON.parse(content) };
2762
+ } catch (error) {
2763
+ return {
2764
+ message: error instanceof SyntaxError ? "Review artifact contains malformed JSON" : "Review artifact file could not be read",
2765
+ ok: false
2766
+ };
2767
+ }
2768
+ }
2769
+ function isLegacyReviewArtifact(value) {
2770
+ return value !== null && typeof value === "object" && !Array.isArray(value) && !Object.hasOwn(value, "schema_version");
2771
+ }
2772
+ function runValidateReviewArtifact(options) {
2773
+ const outputSink = options.outputSink ?? ((message) => console.log(message));
2774
+ const errorSink = options.errorSink ?? ((message) => console.error(message));
2775
+ const input = validateReviewArtifactArgument(options.argv);
2776
+ if (input === undefined) {
2777
+ errorSink(VALIDATE_REVIEW_ARTIFACT_USAGE);
2778
+ return 2;
2779
+ }
2780
+ const resolved = resolveReviewArtifactPath(input, options.cwd ?? process.cwd());
2781
+ if (!resolved.ok) {
2782
+ errorSink(resolved.message);
2783
+ return 2;
2784
+ }
2785
+ const artifact = readReviewArtifact(resolved.path);
2786
+ if (!artifact.ok) {
2787
+ errorSink(artifact.message);
2788
+ return 2;
2789
+ }
2790
+ if (isLegacyReviewArtifact(artifact.value)) {
2791
+ errorSink("Legacy review artifact: no schema_version field");
2792
+ return 3;
2793
+ }
2794
+ const result = ReviewArtifactSchema.safeParse(artifact.value);
2795
+ if (!result.success) {
2796
+ for (const issue of result.error.issues) {
2797
+ const issuePath = formatReviewArtifactIssuePath(issue.path);
2798
+ const authoredMessage = issue.code === "custom" ? `: ${issue.message}` : "";
2799
+ errorSink(`${issuePath} ${issue.code}${authoredMessage}`);
2800
+ }
2801
+ errorSink(`Schema: ${REVIEW_ARTIFACT_SCHEMA_RELATIVE_PATH}`);
2802
+ errorSink(`Review artifact validation failed: ${result.error.issues.length} issue(s)`);
2803
+ return 1;
2804
+ }
2805
+ outputSink("Review artifact is valid");
2806
+ return 0;
2807
+ }
2808
+ function runValidateReviewArtifactCli(options) {
2809
+ return runValidateReviewArtifact(options);
2810
+ }
2501
2811
  function isHarness(value) {
2502
2812
  return value === "opencode" || value === "pi";
2503
2813
  }
@@ -2750,6 +3060,17 @@ function runLegacyCli(args) {
2750
3060
  process.exit(status);
2751
3061
  break;
2752
3062
  }
3063
+ case "validate-review-artifact": {
3064
+ const status = runValidateReviewArtifactCli({
3065
+ argv: ["systematic", ...args],
3066
+ cwd: process.cwd(),
3067
+ errorSink: console.error,
3068
+ outputSink: console.log
3069
+ });
3070
+ if (status !== 0)
3071
+ process.exit(status);
3072
+ break;
3073
+ }
2753
3074
  case "setup":
2754
3075
  setupCommand(args.slice(1));
2755
3076
  break;
@@ -2792,5 +3113,6 @@ if (import.meta.main) {
2792
3113
  runLegacyCli(process.argv.slice(2));
2793
3114
  }
2794
3115
  export {
2795
- runCapabilitiesCli
3116
+ runCapabilitiesCli,
3117
+ runValidateReviewArtifactCli
2796
3118
  };