opencode-swarm 7.87.3 → 7.88.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/.opencode/skills/swarm-pr-review/SKILL.md +304 -9
- package/README.md +1 -0
- package/dist/background/candidate-parser.d.ts +189 -0
- package/dist/background/candidate-sidecar-store.d.ts +56 -0
- package/dist/cli/{config-doctor-6h64pn8n.js → config-doctor-jzbgpbdh.js} +2 -2
- package/dist/cli/{guardrail-explain-2q9myk7c.js → guardrail-explain-sw5bjxtk.js} +5 -5
- package/dist/cli/{guardrail-log-eegabqcp.js → guardrail-log-c7egm5km.js} +3 -3
- package/dist/cli/{index-q9h0wb04.js → index-0asbrmdx.js} +4 -0
- package/dist/cli/{index-1cb4wxnm.js → index-819xp49y.js} +1 -1
- package/dist/cli/{index-r3f47swm.js → index-dkytd370.js} +6 -6
- package/dist/cli/{index-amwa268r.js → index-fwb5f2gr.js} +2 -2
- package/dist/cli/{index-5hvbw5xh.js → index-g00qm2gf.js} +1 -1
- package/dist/cli/{index-kz1bmebr.js → index-jch711dq.js} +9 -9
- package/dist/cli/{index-5vpe6vq9.js → index-vjsr9bqt.js} +1 -1
- package/dist/cli/index.js +4 -4
- package/dist/cli/{schema-84146tvk.js → schema-vb6jkxgg.js} +1 -1
- package/dist/index.js +1015 -244
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/manifest.d.ts +1 -0
- package/dist/tools/parse-lane-candidates.d.ts +2 -0
- package/dist/tools/tool-metadata.d.ts +4 -0
- package/package.json +1 -1
|
@@ -78,6 +78,8 @@ The orchestrator may:
|
|
|
78
78
|
- determine scope,
|
|
79
79
|
- build or request the context pack,
|
|
80
80
|
- launch explorers and triggered micro-lanes,
|
|
81
|
+
- extract candidates from lane artifacts via `parse_lane_candidates` or equivalent parser,
|
|
82
|
+
- filter, group, and chunk candidates for reviewer dispatch,
|
|
81
83
|
- route candidates to reviewers,
|
|
82
84
|
- route reviewer-confirmed findings to critics,
|
|
83
85
|
- group validated findings,
|
|
@@ -88,7 +90,8 @@ The orchestrator MUST NOT:
|
|
|
88
90
|
- re-read a candidate's target code to decide if it is valid,
|
|
89
91
|
- silently downgrade or discard an explorer candidate,
|
|
90
92
|
- treat tool output as a confirmed finding,
|
|
91
|
-
- report a finding that no reviewer validated
|
|
93
|
+
- report a finding that no reviewer validated,
|
|
94
|
+
- classify or judge candidates based on preview text alone — always use the structured parser output.
|
|
92
95
|
|
|
93
96
|
If the orchestrator catches itself validating code, it must stop and delegate validation to a reviewer subagent.
|
|
94
97
|
|
|
@@ -495,9 +498,43 @@ Launch all base lanes with `dispatch_lanes_async` when available. Pass the six l
|
|
|
495
498
|
|
|
496
499
|
Before Phase 4 or synthesis, call `collect_lane_results` with `wait: true` for the base-lane batch and treat the collected `lane_results` as the join barrier. Missing, stale, cancelled, or failed base lanes are explicit review coverage gaps. If `dispatch_lanes_async` is unavailable, use blocking `dispatch_lanes`; if that is also unavailable, simulate isolated passes. Do not let one lane's conclusions bias another lane, and record unavailable deterministic dispatch in the validation gate.
|
|
497
500
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
+
### Candidate extraction via parser
|
|
502
|
+
|
|
503
|
+
After `collect_lane_results` returns for base lanes, process each lane result
|
|
504
|
+
that carries an `output_ref`. The orchestrator MUST use the candidate parser
|
|
505
|
+
rather than preview-text extraction:
|
|
506
|
+
|
|
507
|
+
1. For each `output_ref` (or batched), call `parse_lane_candidates` (or the
|
|
508
|
+
internal `parseAndPersist` module function) with `output_ref` and `producer`
|
|
509
|
+
flags; the parser auto-detects the format family per row. The parser reads
|
|
510
|
+
the full artifact from disk (no preview truncation issue) and returns
|
|
511
|
+
structured `ParseResultWithSidecar` records.
|
|
512
|
+
2. Filter the returned `candidates[]` array by `producer: "swarm-pr-review"` and
|
|
513
|
+
the relevant `row_format_family` (e.g., `base_explorer` for base lanes,
|
|
514
|
+
`micro_lane` for micro-lanes). Filtering happens on the parsed results, NOT
|
|
515
|
+
on the tool input.
|
|
516
|
+
3. Group the filtered candidates into reviewer-sized chunks:
|
|
517
|
+
- by file area (group by the directory or module of the `file_line` field),
|
|
518
|
+
- by category (group by the `category` field),
|
|
519
|
+
- by count (target max 50 candidates per chunk; smaller chunks are fine).
|
|
520
|
+
4. Dispatch reviewer lanes (one per chunk) with bounded in-context candidate
|
|
521
|
+
lists. Each reviewer lane receives only the candidates from its assigned
|
|
522
|
+
chunk.
|
|
523
|
+
|
|
524
|
+
If a lane has `output_degraded: true`, `transcript_incomplete: true`, or no usable `output_ref`, record an explicit
|
|
525
|
+
coverage gap and re-dispatch a narrower lane or mark affected candidates
|
|
526
|
+
UNVERIFIED. Never infer candidate absence from a preview.
|
|
527
|
+
|
|
528
|
+
**Fallback convention:** If the parser is unavailable, the explorer MAY emit
|
|
529
|
+
`[CANDIDATE]` rows in the lane output as a fallback convention (see the
|
|
530
|
+
Explorer Prompt Template at the end of this skill), but the orchestrator
|
|
531
|
+
SHOULD use the parser as the primary extraction mechanism.
|
|
532
|
+
|
|
533
|
+
**lane id uniqueness for parallel dispatches:** When re-dispatching failed or
|
|
534
|
+
re-running explorer lanes, every `dispatch_lanes_async` or `dispatch_lanes`
|
|
535
|
+
lane `id` MUST be unique within that dispatch batch and should include lane and
|
|
536
|
+
attempt suffixes (e.g., `pr_review_explore_lane1_attempt2`). Never reuse an id
|
|
537
|
+
in the same batch unless intentionally replacing that exact lane before dispatch.
|
|
501
538
|
|
|
502
539
|
Explorers optimize for recall. Over-reporting is expected. Explorers produce candidates only.
|
|
503
540
|
|
|
@@ -507,7 +544,7 @@ Explorers optimize for recall. Over-reporting is expected. Explorers produce can
|
|
|
507
544
|
| Lane 2: Security and trust boundaries | Injection, authz/authn bypass, SSRF, path traversal, secret exposure, unsafe deserialization, prompt injection | untrusted input sources, sanitization, credential handling, permission boundary, private network access, output escaping |
|
|
508
545
|
| Lane 3: Dependencies and deployment safety | Import changes, version bumps, lockfile drift, breaking APIs, package scripts, runtime assumptions | lockfile consistency, new transitive deps, Node/Bun/runtime compatibility, platform assumptions, license red flags |
|
|
509
546
|
| Lane 4: Docs, intent, and drift | PR claims vs implementation, docs mismatch, migration/changelog gaps, stale examples | obligation mapping, changed behavior not documented, docs promising behavior not implemented |
|
|
510
|
-
| Lane 5: Tests and falsifiability | Weak assertions, missing edge tests, flaky patterns, mock leakage, fixture drift | assertion strength, tautology patterns (`expect(true).toBe(true)`, `expect(res).toBeDefined()` without further checks, `assertDoesNotThrow` wrapping trivial code), negative paths, isolation, deterministic timing, cross-platform path coverage |
|
|
547
|
+
| Lane 5: Tests and falsifiability | Weak assertions, missing edge tests, flaky patterns, mock leakage, fixture drift | assertion strength, tautology patterns (`expect(true).toBe(true)`, `expect(res).toBeDefined()` without further checks), `assertDoesNotThrow` wrapping trivial code), negative paths, isolation, deterministic timing, cross-platform path coverage |
|
|
511
548
|
| Lane 6: Performance and architecture | Complexity regressions, memory leaks, over-coupling, inefficient graph scans, global mutable state | algorithmic deltas, caching, resource lifecycle, state ownership, architectural boundary violations |
|
|
512
549
|
|
|
513
550
|
### Explorer context contract
|
|
@@ -523,12 +560,19 @@ Every explorer must inspect or explicitly mark unavailable:
|
|
|
523
560
|
7. relevant Swarm knowledge/evidence entries, if present.
|
|
524
561
|
8. the commit range to analyze (`base_ref..head_ref`),
|
|
525
562
|
|
|
526
|
-
Explorer output format
|
|
563
|
+
### Explorer output format
|
|
564
|
+
|
|
565
|
+
Explorers emit structured candidate records. The parser reads the full lane
|
|
566
|
+
artifact and extracts these records. The canonical record shape is:
|
|
527
567
|
|
|
528
568
|
```text
|
|
529
569
|
[CANDIDATE] | candidate_id | lane | severity | category | file:line | claim | evidence_summary | impact_context | confidence: LOW/MEDIUM/HIGH
|
|
530
570
|
```
|
|
531
571
|
|
|
572
|
+
The parser normalizes this into a structured `candidates[]` array. If the
|
|
573
|
+
parser is unavailable, the explorer MAY emit the `[CANDIDATE]` row format
|
|
574
|
+
directly in the lane output as a fallback convention.
|
|
575
|
+
|
|
532
576
|
Explorers must not use `CONFIRMED`, `DISPROVED`, or `PRE_EXISTING`.
|
|
533
577
|
|
|
534
578
|
---
|
|
@@ -537,7 +581,7 @@ Explorers must not use `CONFIRMED`, `DISPROVED`, or `PRE_EXISTING`.
|
|
|
537
581
|
|
|
538
582
|
After `collect_lane_results` returns for base lanes, inspect the context pack risk triggers. Launch focused micro-lanes for triggered categories only, using `dispatch_lanes_async` again when more than one read-only micro-lane is needed. Collect every micro-lane batch with `wait: true` before reviewer classification. Do not launch irrelevant micro-lanes.
|
|
539
583
|
|
|
540
|
-
Apply the same
|
|
584
|
+
Apply the same parser-based extraction to micro-lanes: call `parse_lane_candidates` on each micro-lane `output_ref` (filter the returned `candidates[]` array by `row_format_family === "micro_lane"` after parsing), and treat degraded or incomplete lane artifacts as UNVERIFIED coverage rather than as clean negative evidence.
|
|
541
585
|
|
|
542
586
|
Each micro-lane receives:
|
|
543
587
|
|
|
@@ -547,7 +591,8 @@ Each micro-lane receives:
|
|
|
547
591
|
- relevant deterministic signals,
|
|
548
592
|
- related historical knowledge with quarantine/staleness status,
|
|
549
593
|
- expected invariants,
|
|
550
|
-
- output
|
|
594
|
+
- structured candidate output (parser-extracted). If the parser is unavailable,
|
|
595
|
+
the micro-lane MAY emit `[CANDIDATE]` rows as a fallback convention.
|
|
551
596
|
|
|
552
597
|
### Swarm plugin risk trigger map
|
|
553
598
|
|
|
@@ -596,7 +641,12 @@ Verifier output is advisory until incorporated by the independent reviewer or cr
|
|
|
596
641
|
|
|
597
642
|
## Phase 6: Independent Reviewer Confirmation
|
|
598
643
|
|
|
599
|
-
Route candidates to reviewer subagents. The
|
|
644
|
+
Route candidates to reviewer subagents. The orchestrator routes candidates
|
|
645
|
+
in bounded chunks produced by the parser-based extraction in Phase 3-4. Each
|
|
646
|
+
reviewer lane receives a bounded list of candidates from a single chunk — by
|
|
647
|
+
file area, category, or count — not the full candidate set. The reviewer must
|
|
648
|
+
re-read the candidate's file:line evidence and relevant context pack entries
|
|
649
|
+
directly.
|
|
600
650
|
|
|
601
651
|
### Noise budget and universal validation
|
|
602
652
|
|
|
@@ -813,6 +863,245 @@ Update the verdict only after re-verifying all previously blocking findings.
|
|
|
813
863
|
|
|
814
864
|
---
|
|
815
865
|
|
|
866
|
+
## Dry-Run: Parser-Based Candidate Extraction
|
|
867
|
+
|
|
868
|
+
This section demonstrates the new parser-based extraction path end-to-end
|
|
869
|
+
using synthetic data. It is concrete enough to implement the same pattern in
|
|
870
|
+
another skill.
|
|
871
|
+
|
|
872
|
+
### Scenario
|
|
873
|
+
|
|
874
|
+
A PR review has dispatched six base explorer lanes via `dispatch_lanes_async`.
|
|
875
|
+
The batch completed and `collect_lane_results` returned:
|
|
876
|
+
|
|
877
|
+
```json
|
|
878
|
+
{
|
|
879
|
+
"batch_id": "batch-a1b2c3",
|
|
880
|
+
"lane_results": [
|
|
881
|
+
{
|
|
882
|
+
"lane_id": "pr_review_lane1_correctness",
|
|
883
|
+
"status": "completed",
|
|
884
|
+
"output_ref": ".swarm/lane-results/batch-a1b2c3/lane-1/out-abc123.json",
|
|
885
|
+
"output_degraded": false
|
|
886
|
+
},
|
|
887
|
+
{
|
|
888
|
+
"lane_id": "pr_review_lane2_security",
|
|
889
|
+
"status": "completed",
|
|
890
|
+
"output_ref": ".swarm/lane-results/batch-a1b2c3/lane-2/out-def456.json",
|
|
891
|
+
"output_degraded": false
|
|
892
|
+
}
|
|
893
|
+
]
|
|
894
|
+
}
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
### Step 1 — Call the parser
|
|
898
|
+
|
|
899
|
+
The orchestrator calls `parse_lane_candidates` for each `output_ref`:
|
|
900
|
+
|
|
901
|
+
```json
|
|
902
|
+
{
|
|
903
|
+
"tool": "parse_lane_candidates",
|
|
904
|
+
"arguments": {
|
|
905
|
+
"output_ref": ".swarm/lane-results/batch-a1b2c3/lane-1/out-abc123.json",
|
|
906
|
+
"producer": "swarm-pr-review"
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
### Step 2 — Structured response
|
|
912
|
+
|
|
913
|
+
The parser returns a `ParseResultWithSidecar`. On success, `error` and `error_code` are absent:
|
|
914
|
+
|
|
915
|
+
```json
|
|
916
|
+
{
|
|
917
|
+
"candidates": [
|
|
918
|
+
{
|
|
919
|
+
"record_type": "candidate",
|
|
920
|
+
"row_format_family": "base_explorer",
|
|
921
|
+
"row_format_version": 1,
|
|
922
|
+
"record_version": { "major": 1, "minor": 0 },
|
|
923
|
+
"source_output_ref": ".swarm/lane-results/batch-a1b2c3/lane-1/out-abc123.json",
|
|
924
|
+
"source_batch_id": "B-2025-06-22-001",
|
|
925
|
+
"source_lane_id": "explorer-1",
|
|
926
|
+
"source_agent": "paid_explorer",
|
|
927
|
+
"source_digest": "sha256:abc123def456...",
|
|
928
|
+
"extracted_from_partial_source": false,
|
|
929
|
+
"sessionId": "ses_01HXYZ...",
|
|
930
|
+
"parentSessionId": "ses_01HABC...",
|
|
931
|
+
"producer": "swarm-pr-review",
|
|
932
|
+
"candidate_id": "C-001",
|
|
933
|
+
"lane": "Lane 1: Correctness and edge cases",
|
|
934
|
+
"micro_lane": null,
|
|
935
|
+
"severity": "HIGH",
|
|
936
|
+
"category": "null-safety",
|
|
937
|
+
"file_line": "src/utils/cache.ts:142",
|
|
938
|
+
"claim": "Uncached getter may return undefined on cold start",
|
|
939
|
+
"evidence_summary": "The `getCached` function returns `cache[key]` without a fallback when the cache is empty.",
|
|
940
|
+
"impact_context": "Downstream callers in `src/handlers/*.ts` expect a defined value and call `.toString()` directly.",
|
|
941
|
+
"invariant_violated": null,
|
|
942
|
+
"confidence": "HIGH"
|
|
943
|
+
},
|
|
944
|
+
{
|
|
945
|
+
"record_type": "candidate",
|
|
946
|
+
"row_format_family": "base_explorer",
|
|
947
|
+
"row_format_version": 1,
|
|
948
|
+
"record_version": { "major": 1, "minor": 0 },
|
|
949
|
+
"source_output_ref": ".swarm/lane-results/batch-a1b2c3/lane-1/out-abc123.json",
|
|
950
|
+
"source_batch_id": "B-2025-06-22-001",
|
|
951
|
+
"source_lane_id": "explorer-1",
|
|
952
|
+
"source_agent": "paid_explorer",
|
|
953
|
+
"source_digest": "sha256:abc123def456...",
|
|
954
|
+
"extracted_from_partial_source": false,
|
|
955
|
+
"sessionId": "ses_01HXYZ...",
|
|
956
|
+
"parentSessionId": "ses_01HABC...",
|
|
957
|
+
"producer": "swarm-pr-review",
|
|
958
|
+
"candidate_id": "C-002",
|
|
959
|
+
"lane": "Lane 1: Correctness and edge cases",
|
|
960
|
+
"micro_lane": null,
|
|
961
|
+
"severity": "MEDIUM",
|
|
962
|
+
"category": "async-ordering",
|
|
963
|
+
"file_line": "src/services/queue.ts:88",
|
|
964
|
+
"claim": "Race between `drain` and `processNext` may drop items",
|
|
965
|
+
"evidence_summary": "`drain` sets `active = false` before awaiting `processNext`, which also checks `active`.",
|
|
966
|
+
"impact_context": "Items submitted during the drain window are silently dropped.",
|
|
967
|
+
"invariant_violated": null,
|
|
968
|
+
"confidence": "MEDIUM"
|
|
969
|
+
}
|
|
970
|
+
],
|
|
971
|
+
"invocation_envelope": {
|
|
972
|
+
"record_type": "invocation",
|
|
973
|
+
"source_output_ref": ".swarm/lane-results/batch-a1b2c3/lane-1/out-abc123.json",
|
|
974
|
+
"source_batch_id": "B-2025-06-22-001",
|
|
975
|
+
"source_lane_id": "explorer-1",
|
|
976
|
+
"source_agent": "paid_explorer",
|
|
977
|
+
"source_digest": "sha256:abc123def456...",
|
|
978
|
+
"row_format_version": 1,
|
|
979
|
+
"record_version": { "major": 1, "minor": 0 },
|
|
980
|
+
"sessionId": "ses_01HXYZ...",
|
|
981
|
+
"parentSessionId": "ses_01HABC...",
|
|
982
|
+
"producer": "swarm-pr-review",
|
|
983
|
+
"produced_at": "2025-06-22T14:30:00.000Z",
|
|
984
|
+
"format_families_detected": ["base_explorer"],
|
|
985
|
+
"candidate_count": 2,
|
|
986
|
+
"parse_errors": 2,
|
|
987
|
+
"malformed_rows": 0
|
|
988
|
+
},
|
|
989
|
+
"diagnostics": {
|
|
990
|
+
"candidate_count": 2,
|
|
991
|
+
"parse_errors": 2,
|
|
992
|
+
"parse_error_details": [
|
|
993
|
+
{
|
|
994
|
+
"row_index": 0,
|
|
995
|
+
"field": "row",
|
|
996
|
+
"message": "Both format-family discriminators present; defaulting to base_explorer"
|
|
997
|
+
},
|
|
998
|
+
{
|
|
999
|
+
"row_index": 1,
|
|
1000
|
+
"field": "row",
|
|
1001
|
+
"message": "Both format-family discriminators present; defaulting to base_explorer"
|
|
1002
|
+
}
|
|
1003
|
+
],
|
|
1004
|
+
"malformed_rows": 0,
|
|
1005
|
+
"duplicate_id_count": 0,
|
|
1006
|
+
"duplicate_id_warnings": [],
|
|
1007
|
+
"degraded_source_count": 0,
|
|
1008
|
+
"incomplete_source_count": 0,
|
|
1009
|
+
"format_families_detected": ["base_explorer"]
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
```
|
|
1013
|
+
> **Note**: `parse_errors: 2` reflects FR-017/SC-017 position-based detection: when a `[CANDIDATE]` row has both `evidence_summary` and `impact_context` populated, the parser emits a `parse_error_details` entry per row with `field: "row"` and `message: "Both format-family discriminators present; defaulting to base_explorer"`. This is documented behavior, not a parser bug. To get `parse_errors: 0` with the row format, leave one of the two fields empty; to silence the warning entirely, emit structured JSON candidate records.
|
|
1014
|
+
|
|
1015
|
+
On refusal (e.g. `output_ref` does not exist), `error` and `error_code` are present; `candidates` is `[]`; `invocation_envelope` and `diagnostics` are populated with empty fields for traceability:
|
|
1016
|
+
|
|
1017
|
+
```json
|
|
1018
|
+
{
|
|
1019
|
+
"error": "Artifact reference not found in store",
|
|
1020
|
+
"error_code": "ref-not-found",
|
|
1021
|
+
"candidates": [],
|
|
1022
|
+
"invocation_envelope": {
|
|
1023
|
+
"record_type": "invocation",
|
|
1024
|
+
"source_output_ref": ".swarm/lane-results/batch-a1b2c3/lane-1/missing.json",
|
|
1025
|
+
"source_batch_id": "",
|
|
1026
|
+
"source_lane_id": "",
|
|
1027
|
+
"source_agent": "",
|
|
1028
|
+
"source_digest": "",
|
|
1029
|
+
"row_format_version": 1,
|
|
1030
|
+
"record_version": { "major": 1, "minor": 0 },
|
|
1031
|
+
"produced_at": "2025-06-22T14:30:00.000Z",
|
|
1032
|
+
"format_families_detected": [],
|
|
1033
|
+
"candidate_count": 0,
|
|
1034
|
+
"parse_errors": 0,
|
|
1035
|
+
"malformed_rows": 0
|
|
1036
|
+
},
|
|
1037
|
+
"diagnostics": {
|
|
1038
|
+
"candidate_count": 0,
|
|
1039
|
+
"parse_errors": 0,
|
|
1040
|
+
"parse_error_details": [],
|
|
1041
|
+
"malformed_rows": 0,
|
|
1042
|
+
"duplicate_id_count": 0,
|
|
1043
|
+
"duplicate_id_warnings": [],
|
|
1044
|
+
"degraded_source_count": 0,
|
|
1045
|
+
"incomplete_source_count": 0,
|
|
1046
|
+
"format_families_detected": []
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
```
|
|
1050
|
+
|
|
1051
|
+
### Step 3 — Filter and group
|
|
1052
|
+
|
|
1053
|
+
The orchestrator filters the returned `candidates[]` array by `producer: "swarm-pr-review"` and `row_format_family` (e.g. `base_explorer` or `micro_lane`), then groups
|
|
1054
|
+
the candidates. In this synthetic example, the two candidates above are grouped
|
|
1055
|
+
by file area:
|
|
1056
|
+
|
|
1057
|
+
- **Chunk A — `src/utils/`** (1 candidate): C-001
|
|
1058
|
+
- **Chunk B — `src/services/`** (1 candidate): C-002
|
|
1059
|
+
|
|
1060
|
+
If there were more candidates, the orchestrator would also group by category
|
|
1061
|
+
(e.g., `null-safety`, `async-ordering`) and cap each chunk at 50 candidates.
|
|
1062
|
+
|
|
1063
|
+
### Step 4 — Dispatch reviewer lanes
|
|
1064
|
+
|
|
1065
|
+
The orchestrator dispatches one reviewer lane per chunk:
|
|
1066
|
+
|
|
1067
|
+
```text
|
|
1068
|
+
You are the independent reviewer. Validate only the candidates assigned below.
|
|
1069
|
+
Do not search for new issues except where needed to validate reachability or
|
|
1070
|
+
mitigation. Do not trust explorer severity.
|
|
1071
|
+
|
|
1072
|
+
Context pack summary:
|
|
1073
|
+
- scope: ...
|
|
1074
|
+
- obligations: ...
|
|
1075
|
+
- impact cone: ...
|
|
1076
|
+
- deterministic signals: ...
|
|
1077
|
+
- relevant Swarm artifacts / knowledge: ...
|
|
1078
|
+
- base_ref: <commit SHA of base branch>
|
|
1079
|
+
- head_ref: <commit SHA of PR head branch>
|
|
1080
|
+
|
|
1081
|
+
Candidates (Chunk A — src/utils/):
|
|
1082
|
+
- C-001 | HIGH | null-safety | src/utils/cache.ts:142 | Uncached getter may return undefined on cold start
|
|
1083
|
+
|
|
1084
|
+
For each candidate, return:
|
|
1085
|
+
[REVIEWED] | candidate_id | CONFIRMED/DISPROVED/UNVERIFIED/PRE_EXISTING | evidence_type | final_severity | introduced_by_pr | file:line | rationale | falsification_probe | reviewer_id
|
|
1086
|
+
|
|
1087
|
+
You must check caller context, reachability, schema/middleware/framework mitigations, state-machine constraints, test coverage, PR-introducedness, and severity.
|
|
1088
|
+
|
|
1089
|
+
IMPORTANT: If a finding claims behavior is "new" or "introduced by the PR", you MUST read the equivalent code on the base branch (git show <base_ref>:<file>) to verify it was not present before. A reviewer claim of "this is new" is invalid without base-branch evidence. Do not compare the new code to an idealized baseline — compare it to what actually existed on the base branch at the time of the PR.
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
### Key invariants
|
|
1093
|
+
|
|
1094
|
+
- The parser reads the **full artifact**, not a preview. Truncation in the
|
|
1095
|
+
`dispatch_lanes` preview does not affect candidate extraction.
|
|
1096
|
+
- The orchestrator never classifies candidates — it only filters, groups, and
|
|
1097
|
+
routes them.
|
|
1098
|
+
- Each reviewer receives a bounded chunk. A chunk with more than 50 candidates
|
|
1099
|
+
is split before dispatch.
|
|
1100
|
+
- The `invocation_envelope` in the parser response provides audit provenance
|
|
1101
|
+
for every extracted candidate.
|
|
1102
|
+
|
|
1103
|
+
---
|
|
1104
|
+
|
|
816
1105
|
# Council Mode Workflow
|
|
817
1106
|
|
|
818
1107
|
Council mode is opt-in only and adversarial.
|
|
@@ -1116,4 +1405,10 @@ Return:
|
|
|
1116
1405
|
[CANDIDATE] | candidate_id | lane | severity | category | file:line | claim | evidence_summary | impact_context | confidence
|
|
1117
1406
|
```
|
|
1118
1407
|
|
|
1408
|
+
The orchestrator extracts candidates from the full lane artifact via
|
|
1409
|
+
`parse_lane_candidates` as the primary mechanism. The `[CANDIDATE]` row
|
|
1410
|
+
format above is a fallback convention for environments where the parser is
|
|
1411
|
+
unavailable. Explorers should still emit structured records regardless of
|
|
1412
|
+
whether the parser is present.
|
|
1413
|
+
|
|
1119
1414
|
Do not let speed degrade validation quality.
|
package/README.md
CHANGED
|
@@ -800,6 +800,7 @@ Every candidate passes a 3-gate pipeline before entering quarantine:
|
|
|
800
800
|
| mutation_test | Applies LLM-generated mutation patches to source files and runs tests to measure kill rate; verdict is pass/warn/fail based on configurable thresholds; used by the mutation_test gate (opt-in, off by default) |
|
|
801
801
|
| generate_mutants | Architect-only: generates LLM-based mutation patches (5–10 per function across 6 types: off-by-one, null substitution, operator swap, guard removal, branch swap, side-effect deletion) for direct consumption by the mutation_test tool; returns SKIP verdict on LLM failure rather than throwing |
|
|
802
802
|
| write_mutation_evidence | Architect-only: writes mutation gate results atomically to `.swarm/evidence/{phase}/mutation-gate.json`; accepts verdict (PASS/WARN/FAIL/SKIP), kill rate metrics, and optional survived mutant details; normalizes uppercase-to-lowercase before persisting |
|
|
803
|
+
| parse_lane_candidates | Architect-only: parses `[CANDIDATE]` rows from a `dispatch_lanes` or `collect_lane_results` artifact by `output_ref`; produces structured records with provenance and optional sidecar JSONL persistence; returns `ParseResultWithSidecar` on success or `{ error, error_code, candidates: [] }` on refusal |
|
|
803
804
|
| git_blame | Per-line git blame metadata (sha, author, date, summary) via `git blame --porcelain`; supports optional line range filtering |
|
|
804
805
|
| diff | Structured git diff with contract change detection; supports `summaryOnly` mode returning file list with additions/deletions counts |
|
|
805
806
|
| suggest_patch | Reviewer-safe structured patch suggestion; supports `format` parameter ('json' or 'unified') where unified outputs valid unified diff with `diff --git` headers, hunks, and context |
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
declare const ArtifactInputSchema: z.ZodObject<{
|
|
3
|
+
output_ref: z.ZodString;
|
|
4
|
+
batchId: z.ZodString;
|
|
5
|
+
laneId: z.ZodString;
|
|
6
|
+
agent: z.ZodString;
|
|
7
|
+
role: z.ZodString;
|
|
8
|
+
sessionId: z.ZodOptional<z.ZodString>;
|
|
9
|
+
parentSessionId: z.ZodOptional<z.ZodString>;
|
|
10
|
+
digest: z.ZodString;
|
|
11
|
+
text: z.ZodString;
|
|
12
|
+
transcriptIncomplete: z.ZodOptional<z.ZodBoolean>;
|
|
13
|
+
artifact_status: z.ZodEnum<{
|
|
14
|
+
ok: "ok";
|
|
15
|
+
"ref-not-found": "ref-not-found";
|
|
16
|
+
"artifact-corrupted": "artifact-corrupted";
|
|
17
|
+
}>;
|
|
18
|
+
source: z.ZodEnum<{
|
|
19
|
+
dispatch_lanes: "dispatch_lanes";
|
|
20
|
+
collect_lane_results: "collect_lane_results";
|
|
21
|
+
}>;
|
|
22
|
+
produced_at: z.ZodString;
|
|
23
|
+
}, z.core.$strict>;
|
|
24
|
+
declare const ParseFlagsSchema: z.ZodObject<{
|
|
25
|
+
accept_partial: z.ZodBoolean;
|
|
26
|
+
accept_degraded: z.ZodBoolean;
|
|
27
|
+
degraded: z.ZodBoolean;
|
|
28
|
+
row_format_version: z.ZodNumber;
|
|
29
|
+
producer: z.ZodOptional<z.ZodString>;
|
|
30
|
+
}, z.core.$strict>;
|
|
31
|
+
export type ArtifactInput = z.infer<typeof ArtifactInputSchema>;
|
|
32
|
+
export type ParseFlags = z.infer<typeof ParseFlagsSchema>;
|
|
33
|
+
/**
|
|
34
|
+
* The two supported pipe-delimited format families produced by lane agents.
|
|
35
|
+
*/
|
|
36
|
+
export type RowFormatFamily = 'base_explorer' | 'micro_lane';
|
|
37
|
+
/**
|
|
38
|
+
* A single parsed candidate record extracted from lane text.
|
|
39
|
+
*/
|
|
40
|
+
export interface CandidateRecord {
|
|
41
|
+
record_type: 'candidate';
|
|
42
|
+
row_format_family: RowFormatFamily;
|
|
43
|
+
row_format_version: number;
|
|
44
|
+
record_version: {
|
|
45
|
+
major: number;
|
|
46
|
+
minor: number;
|
|
47
|
+
};
|
|
48
|
+
source_output_ref: string;
|
|
49
|
+
source_batch_id: string;
|
|
50
|
+
source_lane_id: string;
|
|
51
|
+
source_agent: string;
|
|
52
|
+
source_digest: string;
|
|
53
|
+
extracted_from_partial_source: boolean;
|
|
54
|
+
sessionId?: string;
|
|
55
|
+
parentSessionId?: string;
|
|
56
|
+
producer?: string;
|
|
57
|
+
candidate_id: string;
|
|
58
|
+
lane: string | null;
|
|
59
|
+
micro_lane: string | null;
|
|
60
|
+
severity: string | null;
|
|
61
|
+
category: string | null;
|
|
62
|
+
file_line: string | null;
|
|
63
|
+
claim: string | null;
|
|
64
|
+
evidence_summary: string | null;
|
|
65
|
+
impact_context: string | null;
|
|
66
|
+
invariant_violated: string | null;
|
|
67
|
+
confidence: string | null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* One invocation-envelope record per parseCandidates call.
|
|
71
|
+
* Part of the return value but not persisted to a sidecar in this phase.
|
|
72
|
+
*/
|
|
73
|
+
export interface InvocationEnvelope {
|
|
74
|
+
record_type: 'invocation';
|
|
75
|
+
source_output_ref: string;
|
|
76
|
+
source_batch_id: string;
|
|
77
|
+
source_lane_id: string;
|
|
78
|
+
source_agent: string;
|
|
79
|
+
source_digest: string;
|
|
80
|
+
row_format_version: number;
|
|
81
|
+
producer?: string;
|
|
82
|
+
produced_at: string;
|
|
83
|
+
record_version: {
|
|
84
|
+
major: number;
|
|
85
|
+
minor: number;
|
|
86
|
+
};
|
|
87
|
+
sessionId?: string;
|
|
88
|
+
parentSessionId?: string;
|
|
89
|
+
format_families_detected: string[];
|
|
90
|
+
candidate_count: number;
|
|
91
|
+
parse_errors: number;
|
|
92
|
+
malformed_rows: number;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Detail record for a required-field violation inside a data row.
|
|
96
|
+
*/
|
|
97
|
+
export interface ParseErrorDetail {
|
|
98
|
+
row_index: number;
|
|
99
|
+
field: string;
|
|
100
|
+
message: string;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Warning record for a candidate_id that occurs more than once.
|
|
104
|
+
*/
|
|
105
|
+
export interface DuplicateIdWarning {
|
|
106
|
+
candidate_id: string;
|
|
107
|
+
occurrences: number;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Aggregate diagnostics returned alongside every parse result.
|
|
111
|
+
*/
|
|
112
|
+
export interface DiagnosticsSummary {
|
|
113
|
+
candidate_count: number;
|
|
114
|
+
parse_errors: number;
|
|
115
|
+
parse_error_details: ParseErrorDetail[];
|
|
116
|
+
malformed_rows: number;
|
|
117
|
+
duplicate_id_count: number;
|
|
118
|
+
duplicate_id_warnings: DuplicateIdWarning[];
|
|
119
|
+
degraded_source_count: number;
|
|
120
|
+
incomplete_source_count: number;
|
|
121
|
+
format_families_detected: string[];
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Top-level return value from parseCandidates.
|
|
125
|
+
*/
|
|
126
|
+
export interface ParseResult {
|
|
127
|
+
error?: string;
|
|
128
|
+
error_code?: string;
|
|
129
|
+
candidates: CandidateRecord[];
|
|
130
|
+
invocation_envelope: InvocationEnvelope;
|
|
131
|
+
diagnostics: DiagnosticsSummary;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Options for the parse-and-persist path.
|
|
135
|
+
*/
|
|
136
|
+
export interface ParsePersistOptions {
|
|
137
|
+
/** Project root directory (OpenCode process working directory). */
|
|
138
|
+
projectRoot: string;
|
|
139
|
+
/** Override the batch digest. When omitted, SHA-256(batchId) is used. */
|
|
140
|
+
batchDigest?: string;
|
|
141
|
+
/**
|
|
142
|
+
* Passed through to the sidecar store's `useLockfile` option.
|
|
143
|
+
* When true, a proper-lockfile lock is acquired on the batch directory
|
|
144
|
+
* before the append; on lock failure `sidecar_write_error` is set.
|
|
145
|
+
* Default: false (no lock — existing append-only pattern).
|
|
146
|
+
*/
|
|
147
|
+
useLockfile?: boolean;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* ParseResult extended with an optional sidecar write error.
|
|
151
|
+
* When sidecar_write_error is present, the parse succeeded but the
|
|
152
|
+
* sidecar append failed; the caller should treat the parse as valid
|
|
153
|
+
* and log/report the write error separately.
|
|
154
|
+
*/
|
|
155
|
+
export interface ParseResultWithSidecar extends ParseResult {
|
|
156
|
+
sidecar_write_error?: string;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Parse candidate records from structured lane-output text.
|
|
160
|
+
*
|
|
161
|
+
* This is a pure function: no filesystem writes, no store lookups, no I/O.
|
|
162
|
+
* The caller is responsible for store lookup, constructing the ArtifactInput,
|
|
163
|
+
* and setting artifact_status based on lookup outcome.
|
|
164
|
+
*
|
|
165
|
+
* @param input Structured artifact metadata and raw text.
|
|
166
|
+
* @param flags Caller-controlled acceptance flags and format version.
|
|
167
|
+
* @returns Parsed candidates, invocation envelope, and diagnostics.
|
|
168
|
+
*/
|
|
169
|
+
export declare function parseCandidates(input: ArtifactInput, flags: ParseFlags): ParseResult;
|
|
170
|
+
/**
|
|
171
|
+
* Parse candidates and append the invocation envelope + candidate records
|
|
172
|
+
* to the sidecar JSONL file.
|
|
173
|
+
*
|
|
174
|
+
* parseCandidates remains pure (no I/O). This wrapper adds sidecar
|
|
175
|
+
* persistence: on success the envelope + candidates are appended to
|
|
176
|
+
* `.swarm/lane-results/{batchDigest}/candidates.jsonl`; on append
|
|
177
|
+
* failure the parse still succeeds and sidecar_write_error is populated
|
|
178
|
+
* (SC-023).
|
|
179
|
+
*
|
|
180
|
+
* batchDigest is derived as SHA-256(batchId) when not explicitly provided
|
|
181
|
+
* in options (Option A — consistent with lane-output-store.ts internals).
|
|
182
|
+
*
|
|
183
|
+
* @param input Structured artifact metadata and raw text.
|
|
184
|
+
* @param flags Caller-controlled acceptance flags and format version.
|
|
185
|
+
* @param options Persistence options (projectRoot, optional batchDigest).
|
|
186
|
+
* @returns Parse result with optional sidecar_write_error field.
|
|
187
|
+
*/
|
|
188
|
+
export declare function parseAndPersist(input: ArtifactInput, flags: ParseFlags, options: ParsePersistOptions): ParseResultWithSidecar;
|
|
189
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface SidecarStoreOptions {
|
|
2
|
+
/** Project root directory (the OpenCode process working directory). */
|
|
3
|
+
projectRoot: string;
|
|
4
|
+
/** Override the batch digest. When omitted, SHA-256(batchId) is used. */
|
|
5
|
+
batchDigest?: string;
|
|
6
|
+
/**
|
|
7
|
+
* When true, acquire a `proper-lockfile` lock on the batch directory
|
|
8
|
+
* before the append and release it afterwards. Default: false (no lock —
|
|
9
|
+
* matches the existing append-only pattern the codebase already uses).
|
|
10
|
+
*
|
|
11
|
+
* On lock acquisition failure the function throws; the caller
|
|
12
|
+
* (`parseAndPersist`) catches and records `sidecar_write_error`.
|
|
13
|
+
*/
|
|
14
|
+
useLockfile?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Sanitize a single string value for sidecar persistence.
|
|
18
|
+
*
|
|
19
|
+
* Transformations applied in order:
|
|
20
|
+
* 1. NUL bytes (`\x00`) → `\u0000` escape (preserves information).
|
|
21
|
+
* 2. ANSI escape sequences (`\x1B[...]`) → entire sequence stripped.
|
|
22
|
+
* Handles CSI (`ESC [ params intermediate final`), OSC (`ESC ] ... BEL/ST`),
|
|
23
|
+
* and two-character escape sequences.
|
|
24
|
+
* 3. Unicode bidi markers (U+202A–U+202E, U+2066–U+2069) → stripped.
|
|
25
|
+
*
|
|
26
|
+
* All other characters pass through unchanged.
|
|
27
|
+
*
|
|
28
|
+
* @param value - Raw string value (may contain NUL bytes, ANSI, bidi markers).
|
|
29
|
+
* @returns Sanitized string safe for JSONL persistence.
|
|
30
|
+
*/
|
|
31
|
+
export declare function sanitizeString(value: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Append an invocation envelope followed by zero or more candidate records
|
|
34
|
+
* to the sidecar JSONL file for the given batch.
|
|
35
|
+
*
|
|
36
|
+
* The envelope is always the first record in the append batch (SC-015).
|
|
37
|
+
* Cross-skill coexistence is enabled via the `producer` field on the
|
|
38
|
+
* envelope (FR-019).
|
|
39
|
+
*
|
|
40
|
+
* Content sanitization (FR-021) is applied to the persisted copy: NUL bytes
|
|
41
|
+
* are escaped to `\u0000`, ANSI ESC bytes are stripped, and Unicode bidi
|
|
42
|
+
* markers are stripped. The caller's in-memory records are NOT mutated.
|
|
43
|
+
*
|
|
44
|
+
* Concurrency safety (FR-014): when `options.useLockfile` is true, a
|
|
45
|
+
* `proper-lockfile` lock is acquired on the batch directory before the
|
|
46
|
+
* append and released afterwards. Lock acquisition failures propagate as
|
|
47
|
+
* throws. Default behaviour (useLockfile omitted or false) is lockless
|
|
48
|
+
* append — the existing pattern used throughout the codebase.
|
|
49
|
+
*
|
|
50
|
+
* @param options Store configuration (projectRoot, optional batchDigest, useLockfile).
|
|
51
|
+
* @param batchId The batch identifier (used to derive batchDigest when not provided).
|
|
52
|
+
* @param envelope The invocation envelope record (validated before write).
|
|
53
|
+
* @param candidates Candidate records to append after the envelope (validated before write).
|
|
54
|
+
* @throws If schema validation fails, lock acquisition fails, or the filesystem write fails.
|
|
55
|
+
*/
|
|
56
|
+
export declare function appendToSidecar(options: SidecarStoreOptions, batchId: string, envelope: unknown, candidates: unknown[]): void;
|
|
@@ -12,8 +12,8 @@ import {
|
|
|
12
12
|
shouldRunOnStartup,
|
|
13
13
|
writeBackupArtifact,
|
|
14
14
|
writeDoctorArtifact
|
|
15
|
-
} from "./index-
|
|
16
|
-
import"./index-
|
|
15
|
+
} from "./index-819xp49y.js";
|
|
16
|
+
import"./index-0asbrmdx.js";
|
|
17
17
|
import"./index-5e4e2hvv.js";
|
|
18
18
|
import"./index-p0arc26j.js";
|
|
19
19
|
import"./index-zgwm4ryv.js";
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
handleGuardrailExplain
|
|
4
|
-
} from "./index-
|
|
5
|
-
import"./index-
|
|
6
|
-
import"./index-
|
|
4
|
+
} from "./index-fwb5f2gr.js";
|
|
5
|
+
import"./index-jch711dq.js";
|
|
6
|
+
import"./index-g00qm2gf.js";
|
|
7
7
|
import"./index-yhsmmv2z.js";
|
|
8
8
|
import"./index-32axfg6h.js";
|
|
9
9
|
import"./index-e8pk68cc.js";
|
|
10
|
-
import"./index-
|
|
11
|
-
import"./index-
|
|
10
|
+
import"./index-819xp49y.js";
|
|
11
|
+
import"./index-0asbrmdx.js";
|
|
12
12
|
import"./index-8y7qetpg.js";
|
|
13
13
|
import"./index-adz3nk9b.js";
|
|
14
14
|
import"./index-v4fcn4tr.js";
|