@fro.bot/systematic 3.13.3 → 3.13.5

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
@@ -2116,6 +2116,11 @@ var CoverageSchema = exports_external.object({
2116
2116
  validator_failures: exports_external.array(ReasonSchema).max(MAX_PERSONAS),
2117
2117
  intent_uncertainty: exports_external.array(ReasonSchema).max(MAX_PERSONAS)
2118
2118
  }).strict();
2119
+ var DeclinedMergeSchema = exports_external.object({
2120
+ file: RepoRelativePathSchema,
2121
+ input_finding_ids: exports_external.array(boundedText(MAX_INPUT_ID_LENGTH)).min(2).max(MAX_FINDINGS),
2122
+ reason: ReasonSchema
2123
+ }).strict();
2119
2124
  var ReviewArtifactSchema = exports_external.object({
2120
2125
  schema_version: exports_external.literal(1),
2121
2126
  run_id: boundedText(MAX_RUN_ID_LENGTH),
@@ -2134,6 +2139,7 @@ var ReviewArtifactSchema = exports_external.object({
2134
2139
  dispatches: exports_external.array(DispatchSchema).max(MAX_PERSONAS),
2135
2140
  input_findings: exports_external.array(InputFindingSchema).max(MAX_FINDINGS * MAX_PERSONAS),
2136
2141
  findings: exports_external.array(SynthesizedFindingSchema).max(MAX_FINDINGS),
2142
+ declined_merges: exports_external.array(DeclinedMergeSchema).max(MAX_FINDINGS).optional(),
2137
2143
  disposition_counts: DispositionCountsSchema,
2138
2144
  applied_fixes: exports_external.array(ReasonSchema).max(MAX_FINDINGS),
2139
2145
  residual_actionable_work: exports_external.array(ReasonSchema).max(MAX_FINDINGS),
@@ -218,6 +218,11 @@ export declare const ReviewArtifactSchema: z.ZodObject<{
218
218
  agreement_credit: string[];
219
219
  };
220
220
  }>>>;
221
+ declined_merges: z.ZodOptional<z.ZodArray<z.ZodObject<{
222
+ file: z.ZodString;
223
+ input_finding_ids: z.ZodArray<z.ZodString>;
224
+ reason: z.ZodString;
225
+ }, z.core.$strict>>>;
221
226
  disposition_counts: z.ZodObject<{
222
227
  surviving: z.ZodNumber;
223
228
  merged: z.ZodNumber;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fro.bot/systematic",
3
- "version": "3.13.3",
3
+ "version": "3.13.5",
4
4
  "description": "Compound-engineering loops for OpenCode, Pi, and Claude Code",
5
5
  "type": "module",
6
6
  "homepage": "https://fro.bot/systematic",
@@ -443,6 +443,8 @@ For each unit, include:
443
443
 
444
444
  Every feature-bearing unit should include the test file path in `**Files:**`.
445
445
 
446
+ When a unit creates a file in a directory that carries registration obligations — a manifest, index, codemap, module table, or generated artifact that must list it — name those surfaces in the same unit's `**Files:**`, and name the regeneration command in its `**Verification**`. Phase 1.1 already collects this from the repository's own contributor guidance; carry it into the unit rather than leaving it for a failing gate to report. An obligation that no gate enforces is the one most likely to be missed, so state it even when nothing will fail.
447
+
446
448
  Use `Execution note` sparingly. Good uses include:
447
449
  - `Execution note: Start with a failing integration test for the request/response contract.`
448
450
  - `Execution note: Add characterization coverage before modifying this legacy parser.`
@@ -468,8 +468,10 @@ Before applying the confidence gate, keep the parent-owned ledger through every
468
468
  - **Dispatch outcome:** Record the parent-owned dispatch outcomes and ledger dispositions according to the [synthesis artifact contract](./references/synthesis-artifact-contract.md); keep dispatch outcomes separate from finding dispositions.
469
469
  - **Rejection policy: degrade, do not fail the whole review.** Continue merging conforming returns when a persona or finding is rejected; record the rejection and apply the risk-aware verdict according to the [synthesis artifact contract](./references/synthesis-artifact-contract.md). If every persona fails or times out, use the existing degraded-review behavior.
470
470
  2. **Confidence gate.** Suppress findings below 0.60 confidence. Exception: P0 findings at 0.50+ confidence survive the gate -- critical-but-uncertain issues must not be silently dropped. Record the suppressed finding's original confidence and an explicit reason in the input ledger. A retained P0 at 0.50+ is recorded as `surviving` unless it later participates in a deduplication merge. This matches the persona instructions and the schema's confidence thresholds.
471
- 3. **Deduplicate.** Compute fingerprint: `normalize(file) + line_bucket(line, +/-3) + normalize(title)`. When fingerprints match, merge: keep highest severity, keep highest confidence, preserve the exact fingerprint, and retain the input IDs that produced the merged entry. A singleton that passes the gate is `surviving`; each input in a multi-input merge is provisionally `merged`.
472
- 4. **Cross-reviewer agreement.** When 2+ independent reviewers flag the same issue (same fingerprint), boost the merged confidence by 0.10 (capped at 1.0). Cross-reviewer agreement is strong signal -- independent reviewers converging on the same issue is more reliable than any single reviewer's confidence. Preserve the distinction in the merged finding's artifact provenance according to the [synthesis artifact contract](./references/synthesis-artifact-contract.md).
471
+ 3. **Deduplicate.** Group all gated findings by `normalize(file)`. A file with two or more findings from different personas forms a candidate group. Do not use line number to form groups. Sort findings within each group by line. Adjudicate each candidate group: merge findings judged to describe the same underlying defect; keep genuinely different defects separate. Adjacency creates a candidate, not a conclusion -- findings on the same line that describe different defects must not merge. For each resulting finding, derive the fingerprint from its file and line as `normalize(file) + "|" + line`. Keep highest severity, keep highest confidence, and retain the input IDs that produced the merged entry. Record each declined merge in the artifact's optional `declined_merges` field with the normalized file, input finding IDs considered but not merged, and a brief reason. A singleton that passes the gate is `surviving`; each input in a multi-input merge is provisionally `merged`.
472
+
473
+ Worked example: at `src/lib/model-availability.ts:139`, reliability's `Config hook awaits providers API without a timeout` and adversarial's `Config startup can hang forever behind a stalled /config` describe the same underlying defect in different words, so they merge.
474
+ 4. **Cross-reviewer agreement.** When an adjudicated merge contains findings from 2+ independent reviewers, boost the merged confidence by 0.10 (capped at 1.0). Cross-reviewer agreement is strong signal -- independent reviewers converging on the same issue is more reliable than any single reviewer's confidence. Preserve the distinction in the merged finding's artifact provenance according to the [synthesis artifact contract](./references/synthesis-artifact-contract.md).
473
475
  5. **Separate pre-existing.** Pull out findings with `pre_existing: true` into a separate list.
474
476
  6. **Resolve disagreements.** When reviewers flag the same code region but disagree on severity, autofix_class, or owner, annotate the Reviewer column with the disagreement (e.g., "security (P0), correctness (P1) -- kept P0"). This transparency helps the user understand why a finding was routed the way it was.
475
477
  7. **Normalize routing.** For each merged finding, set the final `autofix_class`, `owner`, and `requires_verification`. If reviewers disagree, keep the most conservative route. Synthesis may narrow a finding from `safe_auto` to `gated_auto` or `manual`, but must not widen it without new evidence.
@@ -596,7 +598,7 @@ Review complete
596
598
 
597
599
  **Detail enrichment (headless only):** The headless envelope includes `Why:`, `Evidence:`, and `Suggested fix:` lines. After merge (Stage 5), use the validated full persona returns retained in parent memory for only the findings that survived dedup and confidence gating.
598
600
  - **Field tiers:** `Why:` and `Evidence:` are detail-tier and are already present in the validated inline return. `Suggested fix:` is also available directly from that return and survives merge as optional fix context.
599
- - **In-memory matching:** For each surviving finding, look up its detail-tier fields in the validated returns of the contributing reviewers. Match on `file + line_bucket(line, +/-3)` (the same tolerance used in Stage 5 dedup). When multiple entries fall within the line bucket, apply `normalize(title)` to the merged finding's title and each candidate entry's title as a tie-breaker.
601
+ - **In-memory matching:** For each surviving finding, look up its detail-tier fields in the validated returns of the contributing reviewers. Use the merged finding's `input_finding_ids` (`<reviewer>#<1-based finding index>`) to identify the contributing return and source finding. When an input ID cannot be resolved or multiple candidates remain, match on normalized `file`, then use line and `normalize(title)` only as tie-breakers.
600
602
  - **Reviewer order:** Try contributing reviewers in the order they appear in the merged finding's reviewer list; use the first validated match.
601
603
  - **No-match fallback:** If no validated in-memory return contains a match, omit the `Why:` and `Evidence:` lines for that finding and note the gap in Coverage. This should indicate a synthesis/matching gap, not a failed artifact-file write. Never re-read per-agent files to recover detail.
602
604
 
@@ -374,6 +374,49 @@
374
374
  "additionalProperties": false
375
375
  }
376
376
  },
377
+ "declined_merges": {
378
+ "maxItems": 32,
379
+ "type": "array",
380
+ "items": {
381
+ "type": "object",
382
+ "properties": {
383
+ "file": {
384
+ "type": "string",
385
+ "minLength": 1,
386
+ "maxLength": 256,
387
+ "allOf": [
388
+ {
389
+ "type": "string",
390
+ "pattern": "\\S"
391
+ },
392
+ {
393
+ "type": "string",
394
+ "pattern": "^(?!\\/)(?![A-Za-z]:[\\\\/])(?!\\\\).+"
395
+ }
396
+ ]
397
+ },
398
+ "input_finding_ids": {
399
+ "minItems": 2,
400
+ "maxItems": 32,
401
+ "type": "array",
402
+ "items": {
403
+ "type": "string",
404
+ "minLength": 1,
405
+ "maxLength": 128,
406
+ "pattern": "\\S"
407
+ }
408
+ },
409
+ "reason": {
410
+ "type": "string",
411
+ "minLength": 1,
412
+ "maxLength": 2048,
413
+ "pattern": "\\S"
414
+ }
415
+ },
416
+ "required": ["file", "input_finding_ids", "reason"],
417
+ "additionalProperties": false
418
+ }
419
+ },
377
420
  "disposition_counts": {
378
421
  "type": "object",
379
422
  "properties": {
@@ -78,12 +78,19 @@ The artifact must preserve these distinctions:
78
78
  "title": "<merged finding>",
79
79
  "input_finding_ids": ["correctness#2", "testing#1"],
80
80
  "provenance": {
81
- "fingerprint": "<normalize(file) + line_bucket(line, +/-3) + normalize(title)>",
81
+ "fingerprint": "<normalize(file) + \"|\" + line>",
82
82
  "submitters": ["correctness", "testing"],
83
83
  "agreement_credit": []
84
84
  }
85
85
  }
86
86
  ],
87
+ "declined_merges": [
88
+ {
89
+ "file": "src/example.ts",
90
+ "input_finding_ids": ["correctness#3", "testing#2"],
91
+ "reason": "The findings concern separate validation paths."
92
+ }
93
+ ],
87
94
  "disposition_counts": {
88
95
  "surviving": 0,
89
96
  "merged": 2,
@@ -119,12 +126,24 @@ The artifact must preserve these distinctions:
119
126
  finding has zero ledger entries, not a fabricated finding. Never include the
120
127
  offending value in a rejection reason.
121
128
  - Synthesized and filtered findings retain their original fields plus
122
- `input_finding_ids` and provenance. Provenance contains the exact dedup
123
- fingerprint `normalize(file) + line_bucket(line, +/-3) + normalize(title)`,
124
- `submitters`, and `agreement_credit` arrays.
125
- - `submitters` contains only personas with an input finding in the merged
126
- fingerprint group. `agreement_credit` contains only personas credited by the
127
- cross-reviewer agreement boost without an input finding in that group. A
129
+ `input_finding_ids` and provenance. Provenance contains the fingerprint
130
+ `normalize(file) + "|" + line`, derived from the merged finding's file and
131
+ line, plus `submitters` and `agreement_credit` arrays.
132
+ - `declined_merges` is optional. For each candidate group that is not merged,
133
+ record the normalized file, the input finding IDs considered and not merged,
134
+ and a brief reason. Candidate groups contain two or more findings from
135
+ different personas on the same normalized file; sort their findings by line.
136
+ Adjacency creates a candidate, not a conclusion: genuinely different
137
+ defects on the same line remain separate.
138
+ - A candidate group may resolve partially. When some findings in a group merge
139
+ and others stay separate, record one entry per declined separation, listing
140
+ the input finding IDs on both sides of it. A group of three where two merge
141
+ and one stays separate records a single entry naming all three IDs, with a
142
+ reason describing why the third is a different defect. Record the separation,
143
+ not the merge; a fully merged group produces no entry.
144
+ - `submitters` contains only personas with an input finding in the adjudicated
145
+ merge. `agreement_credit` contains only personas credited by the
146
+ cross-reviewer agreement boost without an input finding in that merge. A
128
147
  persona returning zero findings never appears in `submitters`; do not infer
129
148
  submission from the report's Reviewer column.
130
149
  - A `filtered` finding remains available for human review with the validator's