From 42d3ea1c61fae4980dc9b359ad064d4545d2fb7c Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 29 Apr 2026 09:39:14 +0300 Subject: [PATCH 01/11] fix: align review promises with implementation --- README.md | 2 +- docs/MULTI-REPO-WORKFLOW.md | 1 + docs/QUICKSTART.md | 2 +- docs/architecture/REPO-BOUNDARY.md | 2 +- docs/reference/ci-gates-map.md | 14 +++++++------- docs/reference/maturity-matrix.md | 22 ++++++++++++++-------- docs/reference/trust-guarantees.md | 12 ++++++------ scripts/check-public-metadata.sh | 4 ++-- scripts/sdp-publish.sh | 4 ++++ 9 files changed, 37 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 688d7ab7..67f2fccd 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ If your goal is to **use SDP inside your own project**, start with [docs/QUICKST ## Clone ```bash -git clone https://github.com/fall-out-bug/sdp +git clone https://github.com/fall-out-bug/sdp_lab sdp_lab cd sdp_lab go build -tags "sqlite_fts5" ./... ``` diff --git a/docs/MULTI-REPO-WORKFLOW.md b/docs/MULTI-REPO-WORKFLOW.md index 8f2aae74..731b8a34 100644 --- a/docs/MULTI-REPO-WORKFLOW.md +++ b/docs/MULTI-REPO-WORKFLOW.md @@ -61,6 +61,7 @@ The script copies the relevant files from `sdp_lab` into a checkout of `fall-out | `.opencode/README.md` | `.opencode/README.md` | | `docs/reference/FALLBACK_MODE.md` | `docs/reference/FALLBACK_MODE.md` | | `prompts/commands.yml` | `prompts/commands.yml` | +| `scripts/install.sh` | `scripts/install.sh` | The manifest is maintained in `sdp_lab`. Add or remove paths there when the publish surface changes. diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md index 47d71481..831ca5ae 100644 --- a/docs/QUICKSTART.md +++ b/docs/QUICKSTART.md @@ -39,7 +39,7 @@ export PATH="$PWD/.sdp/bin:$PATH" The installer: -1. clones `fall-out-bug/sdp` to get the canonical manifest and prompts +1. clones the `sdp_lab` source repo to get the canonical manifest and prompts 2. uses `sdp` from `PATH` only if it supports the current `init --harness` contract 3. otherwise builds `cmd/sdp` with the `sqlite_fts5` tag 4. runs `sdp init --harness auto` diff --git a/docs/architecture/REPO-BOUNDARY.md b/docs/architecture/REPO-BOUNDARY.md index 887befb9..334e0f07 100644 --- a/docs/architecture/REPO-BOUNDARY.md +++ b/docs/architecture/REPO-BOUNDARY.md @@ -29,7 +29,7 @@ | **Prompts/Skills** | `prompts/skills/*/SKILL.md` + `.agents/skills/*.md` (native, dual format pending F138-03) | Yes -- via `sdp-publish.sh` | | **Hooks** | `.claude/hooks/`, `scripts/hooks/` (native) | Yes -- via `sdp-publish.sh` | | **Harness entrypoints** | `.cursorrules`, `.cursor/*.md`, `.codex/*.md`, `.opencode/hooks/*`, `.opencode/README.md` | Yes -- via `sdp-publish.sh` | -| **Fallback docs** | `docs/reference/FALLBACK_MODE.md`, `prompts/commands.yml` | Yes -- via `sdp-publish.sh` | +| **Fallback docs and installer** | `docs/reference/FALLBACK_MODE.md`, `prompts/commands.yml`, `scripts/install.sh` | Yes -- via `sdp-publish.sh` | --- diff --git a/docs/reference/ci-gates-map.md b/docs/reference/ci-gates-map.md index e2aa1c1b..623bf399 100644 --- a/docs/reference/ci-gates-map.md +++ b/docs/reference/ci-gates-map.md @@ -19,7 +19,7 @@ scope-gate ──────────────┤ │ protocol-compliance ─────┤ │ (needs: build-test) consistency-gate ────────┤ │ │ │ -coverage-gate ───────────┤ │ (needs: build-test; baseline delta + maturity-tiered minimums) +coverage-gate ───────────┤ │ (needs: build-test; blocking baseline delta + advisory maturity tiers) │ │ policy-gate ◄────────────┘ │ (needs: evidence, │ @@ -44,7 +44,7 @@ required-checks ◄───────────────┴──┘ | Scope Gate | `scope-gate` | Yes | Fail-closed | `go run ./cmd/sdp-guard --ws ` | | Protocol Compliance | `protocol-compliance` | Yes | Fail-closed | `go run ./cmd/sdp-guard --check-contract --contract --snapshot ` | | Consistency Gate | `consistency-gate` | Yes | Fail-closed | `python3 scripts/check_repo_consistency.py --strict-ac --json` | -| Coverage Gate | `coverage-gate` | Yes | Blocking (baseline delta + maturity-tiered minimums) | `go test -tags sqlite_fts5 -coverprofile=cover.out ./... && go tool cover -func=cover.out` | +| Coverage Gate | `coverage-gate` | Yes | Blocking baseline delta; maturity-tiered minimums advisory | `go test -tags sqlite_fts5 -coverprofile=cover.out ./... && go tool cover -func=cover.out` | | Policy Gate | `policy-gate` | Yes | Advisory (configurable) | See details (OPA eval) | | Auto-Attestation | `auto-attestation` | Yes | Required | `go run ./internal/evidence/cmd/auto-attest --branch ` | | Required Checks | `required-checks` | Yes | Required | Verify all gate jobs pass | @@ -138,10 +138,10 @@ required-checks ◄───────────────┴──┘ - **Owner**: platform - **Triggers**: push, PR - **Steps**: `go test -tags sqlite_fts5 -coverprofile=cov.out ./...` + two-phase check: - 1. **Baseline delta**: Compare repo-total coverage against `.sdp/metrics/coverage.txt` with -2pp threshold (existing behavior). - 2. **Maturity-tiered minimums**: Check per-package coverage against maturity-appropriate targets (see [Coverage Tier Policy](#coverage-tier-policy)). + 1. **Baseline delta**: Compare repo-total coverage against `.sdp/metrics/coverage.txt` with -2pp threshold (blocking). + 2. **Maturity-tiered minimums**: Check per-package coverage against maturity-appropriate targets (advisory rollout; see [Coverage Tier Policy](#coverage-tier-policy)). - **Dependencies**: `build-test` -- **Failure semantics**: Blocking. Fails if either (a) total coverage drops more than 2pp below baseline, or (b) any happy-path or GA package falls below its maturity-tier minimum. Beta packages are advisory only (reported but non-blocking). Experimental packages are exempt. +- **Failure semantics**: Blocking for total coverage drops more than 2pp below baseline. Maturity-tier minimum failures are reported but non-blocking during the advisory rollout. Experimental packages are exempt. - **Local reproduce**: `go test -tags sqlite_fts5 -coverprofile=cover.out ./... && go tool cover -func=cover.out | grep total` - **Output**: Coverage percentage in stdout; per-package tier results; `cov.out` locally. - **Baseline**: `.sdp/metrics/coverage.txt` (auto-updated on push to main) @@ -152,8 +152,8 @@ Coverage expectations are tiered by component maturity. See [maturity-matrix.md] | Tier | Maturity | Target | Enforced | Denominator | |------|----------|--------|----------|-------------| -| Happy-path | GA + on happy-path surface | >= 80% | **Blocking** | Per-package line coverage for packages implementing the canonical happy-path | -| GA | GA (not on happy-path) | >= 60% | **Blocking** | Per-package line coverage | +| Happy-path | GA + on happy-path surface | >= 80% | Advisory rollout | Per-package line coverage for packages implementing the canonical happy-path | +| GA | GA (not on happy-path) | >= 60% | Advisory rollout | Per-package line coverage | | Beta | Beta | >= 50% | **Advisory** (non-blocking) | Per-package line coverage | | Experimental | Experimental | None | **Exempt** | N/A | diff --git a/docs/reference/maturity-matrix.md b/docs/reference/maturity-matrix.md index ad34c51f..55aea21e 100644 --- a/docs/reference/maturity-matrix.md +++ b/docs/reference/maturity-matrix.md @@ -263,18 +263,24 @@ Current GoReleaser config (`.goreleaser.yml`) builds these binaries: | Build ID | Binary | Surface Classification | In Formula? | |---|---|---|---| +| `sdp` | `sdp` | stable | Yes | | `sdp-evidence` | `sdp-evidence` | tooling | Yes | | `sdp-guard` | `sdp-guard` | tooling | Yes | | `sdp-orchestrate` | `sdp-orchestrate` | tooling | Yes | +| `sdp-orchestrate-daemon` | `sdp-orchestrate-daemon` | tooling | Yes | | `sdp-ci-loop` | `sdp-ci-loop` | tooling | Yes | -| `sdp-eval` | `sdp-eval` | experimental | Yes (should be reviewed) | - -**Not in GoReleaser but should be considered for formula:** -- `sdp` (main binary) -- NOT in GoReleaser; must be added for formula -- `sdp-doc-sync`, `sdp-beads-bridge`, `sdp-gh-findings-sync`, `sdp-ready`, `sdp-protocol-check`, `sdp-ws-verdict-validate`, `sdp-healthcheck`, `sdp-export` -- tooling; candidates for formula or opt-in tap - -**In GoReleaser but should be reviewed:** -- `sdp-eval` -- classified experimental; consider removing from default formula build +| `sdp-doc-sync` | `sdp-doc-sync` | tooling | Yes | +| `sdp-beads-bridge` | `sdp-beads-bridge` | tooling | Yes | +| `sdp-gh-findings-sync` | `sdp-gh-findings-sync` | tooling | Yes | +| `sdp-ready` | `sdp-ready` | tooling | Yes | +| `sdp-protocol-check` | `sdp-protocol-check` | tooling | Yes | +| `sdp-ws-verdict-validate` | `sdp-ws-verdict-validate` | tooling | Yes | +| `sdp-healthcheck` | `sdp-healthcheck` | tooling | Yes | +| `sdp-export` | `sdp-export` | tooling | Yes | +| `sdp-session-audit` | `sdp-session-audit` | tooling | Yes | +| `sdp-omc-guard` | `sdp-omc-guard` | tooling | Yes | + +Experimental and lab-only binaries are intentionally absent from GoReleaser. ## Exclusion Mechanisms diff --git a/docs/reference/trust-guarantees.md b/docs/reference/trust-guarantees.md index 964ea045..38b81d8d 100644 --- a/docs/reference/trust-guarantees.md +++ b/docs/reference/trust-guarantees.md @@ -5,22 +5,22 @@ ## What SDP Guarantees -These guarantees hold for all GA-maturity components (see [maturity-matrix.md](./maturity-matrix.md)). +These guarantees hold for all GA-maturity components when the required inputs are present and the customer controls below are enabled (see [maturity-matrix.md](./maturity-matrix.md)). ### Evidence Integrity | Guarantee | Mechanism | Verification | |-----------|-----------|--------------| | Evidence artifacts are tamper-evident | in-toto attestation format with SHA-256 content hashes | `go run ./cmd/sdp-evidence validate ` | -| Evidence schema is validated before gate passage | JSON Schema validation in `evidence-gate` CI job | Schema at `schema/evidence.schema.json` | +| Changed evidence artifacts are validated before gate passage | JSON Schema validation in `evidence-gate` CI job when `.sdp/evidence/*.json` changes | Schema at `schema/evidence.schema.json` | | Auto-attestations are signed | Sigstore keyless signing of attestation bundles in `auto-attestation` CI job | `.sdp/attestations/ci-auto.bundle` | ### Scope Enforcement | Guarantee | Mechanism | Verification | |-----------|-----------|--------------| -| Changes outside declared WS scope are detected | `scope-gate` CI job runs `sdp-guard --ws ` per workstream | CI job output | -| Contract compliance is verified | `protocol-compliance` CI job validates contracts against snapshots | `go run ./cmd/sdp-guard --check-contract` | +| Changes outside declared WS scope are detected when checkpoint/workstream evidence is present | `scope-gate` CI job runs `sdp-guard --ws ` for workstreams listed in changed checkpoints | CI job output | +| Contract compliance is verified when feature contracts are changed | `protocol-compliance` CI job validates changed contracts against snapshots | `go run ./cmd/sdp-guard --check-contract` | ### CI Gate Consistency @@ -34,9 +34,9 @@ These guarantees hold for all GA-maturity components (see [maturity-matrix.md](. | Guarantee | Mechanism | Verification | |-----------|-----------|--------------| -| Every merge has a PR with gate evidence | Push protection + `required-checks` gate validates all 12 CI jobs | GitHub branch protection | +| Every protected-branch merge passes the required CI gate set | Push protection + `required-checks` validates required jobs; evidence/scope jobs may pass as not applicable when their inputs are absent | GitHub branch protection | | Attestations are persisted as CI artifacts | `auto-attestation` uploads signed bundles (90-day retention) | `.sdp/attestations/ci-auto.json` | -| Coverage baselines are tracked | `coverage-gate` compares against `.sdp/metrics/coverage.txt`, auto-updates on main push | `git log .sdp/metrics/coverage.txt` | +| Coverage baselines are tracked | `coverage-gate` compares total coverage against `.sdp/metrics/coverage.txt`, auto-updates on main push; maturity-tier package minimums are currently advisory | `git log .sdp/metrics/coverage.txt` | ## What SDP Does NOT Guarantee diff --git a/scripts/check-public-metadata.sh b/scripts/check-public-metadata.sh index dfbcbdf1..4e1625e6 100755 --- a/scripts/check-public-metadata.sh +++ b/scripts/check-public-metadata.sh @@ -56,7 +56,7 @@ check_templates() { done } -# --- Check 2: Legacy download URLs referencing sdp_dev repo --- +# --- Check 2: Legacy distribution URLs referencing sdp_lab repo --- check_legacy_downloads() { for f in "${USER_FACING_FILES[@]}"; do [ -f "$f" ] || continue @@ -70,7 +70,7 @@ check_legacy_downloads() { seen_lines[$key]=1 FINDINGS+=("legacy-url:$f:$lineno:download URL references sdp_lab repo (should be sdp)") fi - done < <(grep -n 'fall-out-bug/sdp_lab' "$f" 2>/dev/null || true) + done < <(grep -nE '(releases/download|raw\.githubusercontent\.com)/[^[:space:]]*fall-out-bug/sdp_lab|fall-out-bug/sdp_lab[^[:space:]]*/releases/download' "$f" 2>/dev/null || true) unset seen_lines done } diff --git a/scripts/sdp-publish.sh b/scripts/sdp-publish.sh index a7cc1a10..6e589f85 100755 --- a/scripts/sdp-publish.sh +++ b/scripts/sdp-publish.sh @@ -32,6 +32,7 @@ # .opencode/README.md -> .opencode/README.md # docs/reference/FALLBACK_MODE.md -> docs/reference/FALLBACK_MODE.md # prompts/commands.yml -> prompts/commands.yml +# scripts/install.sh -> scripts/install.sh # NOTE: .agents/skills/ is NOT mapped here — those are runtime harness stubs. # prompts/skills/ contains the comprehensive SKILL.md files for public publishing. # ============================================================================= @@ -82,6 +83,7 @@ ARTIFACT_FILE_MAP=( ".opencode/README.md:.opencode/README.md" "docs/reference/FALLBACK_MODE.md:docs/reference/FALLBACK_MODE.md" "prompts/commands.yml:prompts/commands.yml" + "scripts/install.sh:scripts/install.sh" ) # ---- Functions -------------------------------------------------------------- @@ -117,6 +119,7 @@ Artifact mapping (sdp_lab -> sdp repo): .opencode/README.md -> .opencode/README.md docs/reference/FALLBACK_MODE.md -> docs/reference/FALLBACK_MODE.md prompts/commands.yml -> prompts/commands.yml + scripts/install.sh -> scripts/install.sh HEREDOC } @@ -525,6 +528,7 @@ Automated sync of protocol artifacts from sdp_lab. - Cursor and Codex harness files - docs/reference/FALLBACK_MODE.md - prompts/commands.yml +- scripts/install.sh Generated by \`scripts/sdp-publish.sh\` PRBODY From 0c6838c0023e8e558f98edc3efed3a38ed7701d5 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 29 Apr 2026 16:39:25 +0300 Subject: [PATCH 02/11] fix: address review gate findings --- .beads/interactions.jsonl | 22 +++++++ .beads/issues.jsonl | 69 ++++++++++++++----- .github/workflows/sdp-doctor.yml | 3 - cmd/sdp/.snapshots/doctor-usage.snap | 5 +- cmd/sdp/.snapshots/main-usage.snap | 8 +++ cmd/sdp/.snapshots/unknown-command.snap | 8 +++ cmd/sdp/cmd_bootstrap.go | 38 ++++++++--- cmd/sdp/cmd_bootstrap_test.go | 25 +++++++ cmd/sdp/cmd_doctor.go | 4 +- cmd/sdp/cmd_init.go | 18 +---- cmd/sdp/cmd_init_test.go | 17 +++++ cmd/sdp/main.go | 12 ++++ cmd/sdp/main_test.go | 34 ++++++++-- cmd/sdp/snapshot_test.go | 2 +- docs/MULTI-REPO-WORKFLOW.md | 7 +- docs/reference/ci-gates-map.md | 8 +-- internal/adapters/generate.go | 15 ++++- internal/adapters/generate_test.go | 46 +++++++++++++ internal/control/repo_dual_test.go | 14 ++++ internal/control/repo_file.go | 32 +++++++++ internal/manifest/load.go | 36 +++++++++- internal/manifest/load_test.go | 23 +++++++ internal/manifest/schema.json | 6 +- scripts/hooks/pre-push.sh | 14 +++- scripts/install.sh | 38 ++++++++++- scripts/install_kubeopencode_remote.sh | 46 +++++++++++-- scripts/run_go_quality_gates.sh | 7 +- scripts/run_smoke_tests.sh | 88 +++++++++++++------------ sdp.manifest.yaml | 8 +-- 29 files changed, 530 insertions(+), 123 deletions(-) diff --git a/.beads/interactions.jsonl b/.beads/interactions.jsonl index ffe7c868..64517692 100644 --- a/.beads/interactions.jsonl +++ b/.beads/interactions.jsonl @@ -622,3 +622,25 @@ {"id":"int-074ebd28","kind":"field_change","created_at":"2026-04-27T15:17:29.551261Z","actor":"Andrei","issue_id":"sdplab-hfk0.5","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Closed"}} {"id":"int-790226b1","kind":"field_change","created_at":"2026-04-27T15:17:30.082941Z","actor":"Andrei","issue_id":"sdplab-hfk0.6","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Closed"}} {"id":"int-795ef8c3","kind":"field_change","created_at":"2026-04-27T15:17:41.087254Z","actor":"Andrei","issue_id":"sdplab-hfk0","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Closed"}} +{"id":"int-7a35be2c","kind":"field_change","created_at":"2026-04-27T15:28:28.565628Z","actor":"Andrei","issue_id":"sdplab-q2cb","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"ACs verified: happy-path >=80%, GA >=60%, Beta >=50% advisory, Experimental exempt, tiered coverage in CI. 5 files changed (+250/-26)."}} +{"id":"int-d46329c8","kind":"field_change","created_at":"2026-04-27T15:32:43.346397Z","actor":"Andrei","issue_id":"sdplab-hjl7","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"ACs verified: all 16 direct deps justified by stable/operator owners, sigstore chain explained, 3 experimental-only deps flagged for F150-04 build-tag isolation, 9 duplicate patterns documented with fix/defer decisions. Audit report: docs/reviews/2026-04-27-dependency-audit.md"}} +{"id":"int-0fa6a060","kind":"field_change","created_at":"2026-04-27T16:07:43.402714Z","actor":"Andrei","issue_id":"sdplab-5r4x","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"ACs verified: goreleaser has 16 stable binaries only, 18 experimental cmd/ tagged with sdp_experimental, local builds work with -tags sdp_experimental, zero tests deleted, check-release-surface.sh has drift detection checks 5-7."}} +{"id":"int-aa75f447","kind":"field_change","created_at":"2026-04-27T16:25:05.402669Z","actor":"Andrei","issue_id":"sdplab-p4hj","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"ACs verified: formula installs sdp binary locally via brew install, test block verifies --help and scout --help, no lab-only binaries exposed, evidence at docs/evidence/F150-08-homebrew-dry-run.md, tap publishing deferred."}} +{"id":"int-dbfbc25a","kind":"field_change","created_at":"2026-04-27T16:33:50.460591Z","actor":"Andrei","issue_id":"sdplab-3uep","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"ACs verified: readiness report at docs/reference/release-readiness.md lists all shipped changes with evidence, remaining debt linked, lab-only surfaces named, release blockers separated (none), honest assessment included. Commit de9fd1af."}} +{"id":"int-1a882045","kind":"field_change","created_at":"2026-04-27T17:10:14.140499Z","actor":"Andrei","issue_id":"sdplab-nyr0","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"All 10 children closed. F150 product layering and release readiness complete: module migration, experimental isolation, telemetry consent, coverage policy, Homebrew formula, docs alignment, debt ledger. PR #142."}} +{"id":"int-f5044877","kind":"field_change","created_at":"2026-04-27T17:35:20.946964Z","actor":"Andrei","issue_id":"sdplab-kw3q.1","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"merged via PR#141"}} +{"id":"int-14e1e2de","kind":"field_change","created_at":"2026-04-27T17:35:21.414699Z","actor":"Andrei","issue_id":"sdplab-kw3q.2","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"merged via PR#141"}} +{"id":"int-80d53ed6","kind":"field_change","created_at":"2026-04-27T17:35:21.855607Z","actor":"Andrei","issue_id":"sdplab-kw3q.3","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"merged via PR#141"}} +{"id":"int-784c2170","kind":"field_change","created_at":"2026-04-27T17:35:22.275819Z","actor":"Andrei","issue_id":"sdplab-kw3q.4","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"merged via PR#141"}} +{"id":"int-837aa1c3","kind":"field_change","created_at":"2026-04-27T17:35:22.756998Z","actor":"Andrei","issue_id":"sdplab-kw3q.5","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"merged via PR#141"}} +{"id":"int-541bb0a4","kind":"field_change","created_at":"2026-04-27T17:35:38.251921Z","actor":"Andrei","issue_id":"sdplab-kw3q","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"merged via PR#141"}} +{"id":"int-523fe2bf","kind":"field_change","created_at":"2026-04-28T21:30:26.619267Z","actor":"Andrei","issue_id":"sdplab-njbd","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"merged via PR#142 — LimitsCache race fix included in F150 product layering PR"}} +{"id":"int-9b20b5f4","kind":"field_change","created_at":"2026-04-29T09:26:12.384358Z","actor":"Andrei","issue_id":"sdplab-tffu.1","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"merged via PR#143"}} +{"id":"int-c1d8ac97","kind":"field_change","created_at":"2026-04-29T09:26:13.414719Z","actor":"Andrei","issue_id":"sdplab-tffu.2","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"merged via PR#143"}} +{"id":"int-af6c8aca","kind":"field_change","created_at":"2026-04-29T09:26:14.918456Z","actor":"Andrei","issue_id":"sdplab-tffu.3","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"merged via PR#143"}} +{"id":"int-e21aae28","kind":"field_change","created_at":"2026-04-29T09:26:18.500606Z","actor":"Andrei","issue_id":"sdplab-tffu.4","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"merged via PR#143"}} +{"id":"int-d220cfd5","kind":"field_change","created_at":"2026-04-29T09:26:42.683476Z","actor":"Andrei","issue_id":"sdplab-tffu","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"merged via PR#143, all 4 children closed"}} +{"id":"int-0c7be3e1","kind":"field_change","created_at":"2026-04-29T09:57:19.663242Z","actor":"Andrei","issue_id":"sdplab-5z1p","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"duplicate of sdplab-1ily"}} +{"id":"int-145550c2","kind":"field_change","created_at":"2026-04-29T11:50:02.663081Z","actor":"Andrei","issue_id":"sdplab-1ily","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Merged in PR #145 (cb5470e): Pi resources packaging done"}} +{"id":"int-0ebbc2aa","kind":"field_change","created_at":"2026-04-29T11:50:04.334166Z","actor":"Andrei","issue_id":"sdplab-vd3r","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Merged in PR #145 (cb5470e): selective init preserves existing manifest"}} +{"id":"int-8c54fcaa","kind":"field_change","created_at":"2026-04-29T11:50:05.102199Z","actor":"Andrei","issue_id":"sdplab-zdjr","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Merged in PR #145 (cb5470e): Pi prompts normalize legacy Claude skill refs"}} diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 85bf4bff..1354a373 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -1,3 +1,4 @@ +{"_type":"issue","id":"sdplab-6cw6","title":"Review: bootstrap dry-run mutates target repo","description":"source=review; aspect=ux; blocking=true; severity=critical. docs/QUICKSTART.md promises 'sdp bootstrap --dry-run --mode brownfield .' is read-only, but cmd/sdp/cmd_bootstrap.go mode handlers write DRAFT-bootstrap-delta.json via writeDraftArtifact. Fix dry-run propagation for greenfield/brownfield handlers and add regression test.","status":"in_progress","priority":0,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:47:42Z","created_by":"Andrei","updated_at":"2026-04-29T13:35:54Z","started_at":"2026-04-29T13:35:54Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-s686","title":"F145-07: Harness plumbing fix — cursor.go + opencode.go --model","description":"Закрывает silent-bug: CursorHarness.Spawn и OpenCodeHarness.Spawn игнорируют opts.Model. Cursor: добавить --model X. Opencode: добавить -m provider/model (auto-prefix ollama/ если нет provider). Regression test через mock exec.Cmd. Design §1.1. WS file: docs/workstreams/backlog/00-145-07.md","status":"closed","priority":0,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-26T18:18:54Z","created_by":"Andrei","updated_at":"2026-04-27T04:11:51Z","started_at":"2026-04-26T18:48:05Z","closed_at":"2026-04-27T04:11:51Z","close_reason":"merged via PR#135","dependency_count":0,"dependent_count":1,"comment_count":0} {"_type":"issue","id":"sdplab-qdud","title":"F108-06: Discovery → Delivery — контракт перехода","description":"Backfilled under F142-08 (sdplab-o5yv) on 2026-04-26.\n\nWS file: docs/workstreams/backlog/00-108-06.md\nStatus: backlog\nPriority: P0\n\nФормализовать контракт перехода между фазами Discovery и Delivery. Определить данные, передаваемые между фазами, и условия успешного перехода.\n\nThis bead exists to give the picker / sdp doctor backlog a target for the existing ws scaffold.","status":"closed","priority":0,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-26T08:28:25Z","created_by":"Andrei","updated_at":"2026-04-26T08:31:19Z","closed_at":"2026-04-26T08:31:19Z","close_reason":"Code already on main. All scope files verified, tests pass.","dependency_count":0,"dependent_count":1,"comment_count":0} {"_type":"issue","id":"sdplab-w23l","title":"F108-05: Wire sdp-harness с LiveGateway + интеграционный тест","description":"Backfilled under F142-08 (sdplab-o5yv) on 2026-04-26.\n\nWS file: docs/workstreams/backlog/00-108-05.md\nStatus: backlog\nPriority: P0\n\nПодключить sdp-harness к LiveGateway через ServeBridge. Написать интеграционный тест, проверяющий полный путь от harness через agentloop до LiveGateway.\n\nThis bead exists to give the picker / sdp doctor backlog a target for the existing ws scaffold.","status":"closed","priority":0,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-26T08:28:21Z","created_by":"Andrei","updated_at":"2026-04-26T08:31:19Z","closed_at":"2026-04-26T08:31:19Z","close_reason":"Code already on main. All scope files verified, tests pass.","dependency_count":0,"dependent_count":1,"comment_count":0} @@ -34,12 +35,44 @@ {"_type":"issue","id":"sdplab-22","title":"F100-01: Reference Integrity CI Gate","status":"closed","priority":0,"issue_type":"task","assignee":"Andrei","created_at":"2026-04-05T15:00:00Z","created_by":"UX Council","updated_at":"2026-04-26T13:30:49Z","closed_at":"2026-04-26T13:30:49Z","close_reason":"F100 Reference Integrity complete: CI gate + one-time cleanup. 56 tests passing.","labels":["F100","P0","harness:claude","stream-C","ux"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-23","title":"F100-02: One-Time Reference Cleanup","status":"closed","priority":0,"issue_type":"task","assignee":"Andrei","created_at":"2026-04-05T15:00:00Z","created_by":"UX Council","updated_at":"2026-04-26T13:30:50Z","closed_at":"2026-04-26T13:30:50Z","close_reason":"F100 Reference Integrity complete: CI gate + one-time cleanup. 56 tests passing.","labels":["F100","P0","harness:any","stream-C","ux"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-24","title":"F101-01: Write Plan Emission and Confirmation","status":"closed","priority":0,"issue_type":"feature","assignee":"Andrei","created_at":"2026-04-05T15:00:00Z","created_by":"UX Council","updated_at":"2026-04-19T07:33:30Z","closed_at":"2026-04-19T07:33:30Z","close_reason":"F101-01 merged to main via PR #95 after sdp PR #130 landed on sdp/main. Write Plan prompt surface is now shipped for the nine stateful skills.","labels":["F101","P0","harness:any","stream-B","ux"],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"sdplab-tffu.4","title":"F161-04: integrate pi-review verdict, telemetry, beads, and delivery loop","description":"WS 00-161-04. Persist raw review telemetry, compact verdicts, bead findings, and delivery-loop behavior so subagents repeat fix-review rounds until the pi-review gate is clean.","status":"open","priority":1,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-28T13:37:26Z","created_by":"Andrei","updated_at":"2026-04-28T13:37:26Z","labels":["F161","delivery-loop","pi-review","sdp"],"dependencies":[{"issue_id":"sdplab-tffu.4","depends_on_id":"sdplab-tffu","type":"parent-child","created_at":"2026-04-28T16:37:25Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"sdplab-tffu.3","title":"F161-03: run pi model review and synthesis","description":"WS 00-161-03. Use pi as the local runtime to call the already configured ZAI/GLM and Kimi reviewers, normalize model outputs, apply OpenRouter fallback, and synthesize SDP-style findings. Treat glm as the GLM model family under the zai provider, not as a separate provider slot.","status":"open","priority":1,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-28T13:37:06Z","created_by":"Andrei","updated_at":"2026-04-28T13:56:14Z","labels":["F161","models","pi-review","sdp"],"dependencies":[{"issue_id":"sdplab-tffu.3","depends_on_id":"sdplab-tffu","type":"parent-child","created_at":"2026-04-28T16:37:06Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"sdplab-tffu.2","title":"F161-02: build working-tree context packet and test evidence","description":"WS 00-161-02. Implement the scoped context packet for touched files, base/head diff, project rules, bead context, and deterministic test evidence captured by SDP before model review.","status":"open","priority":1,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-28T13:36:54Z","created_by":"Andrei","updated_at":"2026-04-28T13:36:54Z","labels":["F161","context","pi-review","sdp"],"dependencies":[{"issue_id":"sdplab-tffu.2","depends_on_id":"sdplab-tffu","type":"parent-child","created_at":"2026-04-28T16:36:54Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"sdplab-tffu.1","title":"F161-01: specify pi-review contracts and schemas","description":"WS 00-161-01. Define the stable sdp pi-review contract, review verdict extensions, pi-review run telemetry schema, and schema index entry without breaking the existing @review verdict shape.","status":"open","priority":1,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-28T13:36:43Z","created_by":"Andrei","updated_at":"2026-04-28T13:36:43Z","labels":["F161","contracts","pi-review","sdp"],"dependencies":[{"issue_id":"sdplab-tffu.1","depends_on_id":"sdplab-tffu","type":"parent-child","created_at":"2026-04-28T16:36:43Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"sdplab-tffu","title":"F161: pi-review external review gate","description":"Define and deliver the sdp pi-review review gate that packages touched working-tree context, runs pi as the local model-review binary with the already configured Kimi and ZAI/GLM MVP reviewers, falls back through OpenRouter, writes SDP-style verdict telemetry, and files actionable findings into beads until clean. Treat glm as the GLM model family under the zai provider, not as a separate provider slot.","status":"open","priority":1,"issue_type":"epic","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-28T13:36:29Z","created_by":"Andrei","updated_at":"2026-04-28T13:56:12Z","labels":["F161","pi-review","sdp"],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"sdplab-njbd","title":"F150 CI: fix LimitsCache header/poller overwrite race","description":"PR #137 coverage-gate failed in internal/dispatch/harness TestLimitsCache_UpdateFromHeaders_Priority. Root cause: UpdateFromHeaders stores header limits before marking header TTL, allowing a poller tick to overwrite fresh header-derived limits. Fix in same PR. Source=ci, blocking=true, PR #137.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-27T10:39:00Z","created_by":"Andrei","updated_at":"2026-04-27T10:39:05Z","started_at":"2026-04-27T10:39:05Z","dependencies":[{"issue_id":"sdplab-njbd","depends_on_id":"sdplab-qgq1","type":"discovered-from","created_at":"2026-04-27T13:39:00Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-5z1p","title":"Pi harness: skills and slash commands not discoverable","description":"Pi 0.70.6 loads Agent Skills from .pi/skills or .agents/skills/\u003cname\u003e/SKILL.md and prompt templates from .pi/prompts. Current SDP exposes commands only under prompts/commands and flat skill files are not enough for Pi auto-discovery. Fix Pi resource packaging and add smoke coverage.","acceptance_criteria":"- [ ] Pi resource smoke reports all manifest skills without skill diagnostics.\\n- [ ] Pi resource smoke reports all manifest commands as prompt templates.\\n- [ ] Generated adapters include a Pi-native skill and prompt-template surface.\\n- [ ] Harness docs name Pi CLI/version, entrypoints, limitations, and smoke test.","status":"closed","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:51:48Z","created_by":"Andrei","updated_at":"2026-04-29T09:57:20Z","started_at":"2026-04-29T09:51:51Z","closed_at":"2026-04-29T09:57:20Z","close_reason":"duplicate of sdplab-1ily","labels":["F162","commands","harness","pi","sdp","skills"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-vd3r","title":"Review: init rewrites user manifest despite update semantics","description":"source=review; aspect=code-quality,ux; blocking=true; severity=major. cmd/sdp/cmd_init.go persists filtered harnesses by yaml.Marshal into target sdp.manifest.yaml, dropping comments/formatting and narrowing future generation scope while --update help says user-modified manifest is kept. Resolve selective-harness doctor semantics without silently rewriting user manifests, or require explicit manifest-write flag.","status":"closed","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:50:10Z","created_by":"Andrei","updated_at":"2026-04-29T11:50:04Z","started_at":"2026-04-29T11:14:20Z","closed_at":"2026-04-29T11:50:04Z","close_reason":"Merged in PR #145 (cb5470e): selective init preserves existing manifest","comments":[{"id":"019dd8f2-739b-7007-a15a-06a57ec62688","issue_id":"sdplab-vd3r","author":"Andrei","text":"Fixed in PR #145 commit pending push: sdp init no longer rewrites target sdp.manifest.yaml for selective --harness installs; .sdp/generated remains full manifest-derived cache. Added regression test TestInit_ExistingManifestPreservesUserManifestBytes and quality gates passed. Keep open until PR #145 merges.","created_at":"2026-04-29T11:14:21Z"},{"id":"019dd8fb-1c19-77b4-b021-5e199f2fe70e","issue_id":"sdplab-vd3r","author":"Andrei","text":"Pushed fix in commit b307ff75 on PR #145; CI required checks green. Issue remains in_progress until PR merge per repo policy.","created_at":"2026-04-29T11:23:49Z"}],"dependency_count":0,"dependent_count":0,"comment_count":2} +{"_type":"issue","id":"sdplab-6uy6","title":"Review: installer underdeclares required Go version","description":"source=review; aspect=dx,ux; blocking=true; severity=major. scripts/install.sh says Go 1.21+ is enough, but go.mod and CI require Go 1.26 and installer calls go build directly. Preflight Go \u003e=1.26 or use project toolchain wrapper.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:48:51Z","created_by":"Andrei","updated_at":"2026-04-29T13:35:54Z","started_at":"2026-04-29T13:35:54Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-ok1o","title":"Review: backlog doctor hard rule is not enforced in CI","description":"source=review; aspect=dx,governance; blocking=true; severity=major. AGENTS says no workstream means no execution and sdp doctor backlog trips, but .github/workflows/sdp-doctor.yml marks backlog drift continue-on-error. Remove continue-on-error or enforce changed-file subset.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:48:50Z","created_by":"Andrei","updated_at":"2026-04-29T13:35:55Z","started_at":"2026-04-29T13:35:55Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-4pkp","title":"Review: consistency gate documented fail-closed but advisory in CI","description":"source=review; aspect=dx,governance; blocking=true; severity=major. docs/reference/ci-gates-map.md says consistency-gate blocks merge, but .github/workflows/ci.yml suppresses consistency/protocol/doc-sync failures with || true/echo. Enforce a blocking subset or mark advisory until drift is reconciled.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:48:49Z","created_by":"Andrei","updated_at":"2026-04-29T13:35:56Z","started_at":"2026-04-29T13:35:56Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-2zz2","title":"Review: local Go quality gate does not reproduce CI lint","description":"source=review; aspect=dx; blocking=true; severity=major. CI build-test runs golangci-lint, but scripts/run_go_quality_gates.sh only runs build/test/vet while docs say it reproduces build-test. Add pinned lint step or split docs/local commands explicitly.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:48:48Z","created_by":"Andrei","updated_at":"2026-04-29T13:35:56Z","started_at":"2026-04-29T13:35:56Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-cisq","title":"Review: manifest hooks and MCP entries validate but do not install","description":"source=review; aspect=architecture,dx; blocking=true; severity=major. Manifest schema presents hooks and MCP servers as canonical, but adapter generation only renders commands/skills/agents. Add hook/MCP renderers or document as inventory-only and exclude install claims.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:48:47Z","created_by":"Andrei","updated_at":"2026-04-29T13:35:57Z","started_at":"2026-04-29T13:35:57Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-fycv","title":"Review: manifest dispatch overrides validate but are ignored","description":"source=review; aspect=architecture,dx; blocking=true; severity=major. internal/manifest/schema.json defines per-harness command dispatch overrides, but internal/adapters/generate.go writes default dispatch paths and ignores Command.Dispatch. Implement dispatch rendering or remove field until supported.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:48:45Z","created_by":"Andrei","updated_at":"2026-04-29T13:35:57Z","started_at":"2026-04-29T13:35:57Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-tsav","title":"Review: repo boundary misstates sdp optional checkout ownership","description":"source=review; aspect=architecture,dx; blocking=true; severity=major. docs/MULTI-REPO-WORKFLOW.md mixes root/internal/cmd/docs with sdp/ under a row pointing at fall-out-bug/sdp, contradicting project-map where sdp/ is optional checkout. Split sdp_lab native paths from optional public sdp checkout.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:48:45Z","created_by":"Andrei","updated_at":"2026-04-29T13:35:58Z","started_at":"2026-04-29T13:35:58Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-abxs","title":"Review: publish workflow documents manifest as source of truth but uses hard-coded map","description":"source=review; aspect=architecture,dx; blocking=true; severity=major. docs/MULTI-REPO-WORKFLOW.md says publishing uses a manifest, while scripts/sdp-publish.sh owns a hard-coded ARTIFACT_FILE_MAP. Pick one source of truth or correct docs.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:48:44Z","created_by":"Andrei","updated_at":"2026-04-29T13:35:58Z","started_at":"2026-04-29T13:35:58Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-fkka","title":"Review: plain sdp doctor contradicts bootstrap docs","description":"source=review; aspect=ux; blocking=true; severity=major. docs/reference/bootstrap-flow.md says plain sdp doctor reports DRAFT bootstrap state, but cmd/sdp/cmd_doctor.go requires a subcommand and exits usage error. Either alias sdp doctor to doctor control or fix docs; alias is better UX.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:48:43Z","created_by":"Andrei","updated_at":"2026-04-29T13:35:58Z","started_at":"2026-04-29T13:35:58Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-7nyh","title":"Review: top-level help omits install verification commands","description":"source=review; aspect=ux,dx; blocking=true; severity=major. cmd/sdp/main.go help omits init, manifest validate, generate-adapters, and doctor adapters even though Quickstart uses them for install verification. Align help with current Toolkit surface.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:48:42Z","created_by":"Andrei","updated_at":"2026-04-29T13:35:59Z","started_at":"2026-04-29T13:35:59Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-z7al","title":"Review: sdp --help exits as error","description":"source=review; aspect=ux; blocking=true; severity=major. cmd/sdp/main.go treats --help/-h/help as missing/unknown command and exits 2, despite SLO first-install sanity check expecting sdp --help to succeed. Handle help flags before command switch and exit 0.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:48:41Z","created_by":"Andrei","updated_at":"2026-04-29T13:35:59Z","started_at":"2026-04-29T13:35:59Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-yb53","title":"Review: smoke runner executes tests twice and hides first result","description":"source=review; aspect=code-quality,dx; blocking=true; severity=major. scripts/run_smoke_tests.sh runs go test -json, pipes JSON into go run golang.org/x/tools/cmd/godoc@latest and ignores failures, then runs smoke tests again for exit/report. Remove latest godoc/network flake and parse one test run.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:48:40Z","created_by":"Andrei","updated_at":"2026-04-29T13:35:59Z","started_at":"2026-04-29T13:35:59Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-o5zd","title":"Review: first push pre-push hook diffs empty tree","description":"source=review; aspect=code-quality,dx; blocking=true; severity=major. scripts/hooks/pre-push.sh treats all-zero remote_sha as empty tree, so a first push sees the entire repo as changed and falsely blocks docs-only branches for missing evidence. Diff against merge-base/origin main or fork-point.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:48:39Z","created_by":"Andrei","updated_at":"2026-04-29T13:36:00Z","started_at":"2026-04-29T13:36:00Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-jffc","title":"Review: invalid manifests produce successful empty install","description":"source=review; aspect=code-quality,ux; blocking=true; severity=major. cmd/sdp/cmd_init.go converts manifest load failures into warnings and continues with an empty in-memory manifest. Typos/missing prompt paths can exit 0 while writing no useful adapters. Fail fast for invalid existing manifests; reserve fallback for explicit bootstrap path.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:48:37Z","created_by":"Andrei","updated_at":"2026-04-29T13:36:00Z","started_at":"2026-04-29T13:36:00Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-4gqv","title":"Review: installer trusts arbitrary sdp binary on PATH","description":"source=review; aspect=security,ux; blocking=true; severity=major. scripts/install.sh accepts any PATH sdp whose init --help contains --harness, executes it against the target, then copies it into .sdp/bin. Build from cloned source by default or verify provenance/checksum before execution/persistence.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:48:36Z","created_by":"Andrei","updated_at":"2026-04-29T13:36:01Z","started_at":"2026-04-29T13:36:01Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-zit5","title":"Review: control project IDs can traverse file-backed storage","description":"source=review; aspect=security; blocking=true; severity=major. internal/control/repo_file.go uses projectID directly in filepath.Join for project/card paths. A project registry ID like ../../outside can write outside .sdp/control. Validate IDs and enforce resolved paths under control root.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:48:35Z","created_by":"Andrei","updated_at":"2026-04-29T13:36:01Z","started_at":"2026-04-29T13:36:01Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-n082","title":"Review: kubeopencode remote installer allows shell injection","description":"source=review; aspect=security; blocking=true; severity=major. scripts/install_kubeopencode_remote.sh interpolates NAMESPACE and RELEASE into double-quoted ssh remote commands. Crafted values can execute arbitrary remote shell. Validate Kubernetes names and pass values as positional args to remote bash -s.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:48:34Z","created_by":"Andrei","updated_at":"2026-04-29T13:36:02Z","started_at":"2026-04-29T13:36:02Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-y8xh","title":"Review: manifest paths can escape repo and embed local files","description":"source=review; aspect=security,go-best-practices; blocking=true; severity=major. internal/manifest/load.go joins repoRoot with manifest path fields without rejecting absolute/../ paths; internal/adapters/generate.go readBody embeds file contents into generated adapters. Reject non-local paths and verify resolved paths stay under repoRoot.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:48:32Z","created_by":"Andrei","updated_at":"2026-04-29T13:36:03Z","started_at":"2026-04-29T13:36:03Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-1ily","title":"Pi harness: skills and slash commands not discoverable","description":"Pi 0.70.6 loads Agent Skills from .pi/skills or .agents/skills/\u003cname\u003e/SKILL.md and prompt templates from .pi/prompts. Current SDP exposes many skills only as flat .agents/skills/*.md or prompt sources, and exposes commands only under prompts/commands/Claude command files, so Pi starts with missing commands and invalid skill diagnostics (deploy/tdd YAML). Fix Pi resource packaging and add smoke coverage.","acceptance_criteria":"- [ ] Pi resource smoke reports all manifest skills without skill diagnostics.\\n- [ ] Pi resource smoke reports all manifest commands as prompt templates.\\n- [ ] Generated adapters include a Pi-native skill and prompt-template surface.\\n- [ ] Harness docs name Pi CLI/version, entrypoints, limitations, and smoke test.","status":"closed","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:36:11Z","created_by":"Andrei","updated_at":"2026-04-29T11:50:03Z","started_at":"2026-04-29T09:36:18Z","closed_at":"2026-04-29T11:50:03Z","close_reason":"Merged in PR #145 (cb5470e): Pi resources packaging done","labels":["F162","commands","harness","pi","sdp","skills"],"dependencies":[{"issue_id":"sdplab-1ily","depends_on_id":"sdplab-wuaa.6","type":"discovered-from","created_at":"2026-04-29T12:36:11Z","created_by":"Andrei","metadata":"{}"}],"comments":[{"id":"019dd8ac-2c51-7bea-a34a-e074873f3dea","issue_id":"sdplab-1ily","author":"Andrei","text":"Implemented on branch feature/F162-pi-resources: Pi harness added to manifest/schema/init/generator; .pi prompt templates preserve $ARGUMENTS; Pi Agent Skills directories materialized under .agents/skills/\u003cname\u003e/SKILL.md; deploy/tdd YAML fixed; ship skill/command restored to manifest; smoke script added. Verification: SDP_GO_QUALITY_MODE=host ./scripts/run_go_quality_gates.sh passed; scripts/smoke/pi_resources.mjs passed; sdp doctor adapters --strict passed.","created_at":"2026-04-29T09:57:35Z"},{"id":"019dd8c9-ce8d-760a-b0df-017ffd90d172","issue_id":"sdplab-1ily","author":"Andrei","text":"PR #145 updated and CI is green. Extra CI root-cause fixes: skill-eval builds cmd/sdp-eval with -tags sdp_experimental; auto-attestation/release install cosign via sigstore/cosign-installer@v3.10.0 instead of unvalidated curl download. Checks: gh pr checks 145 --watch passed (required-checks, auto-attestation, eval, doctor, build-test, coverage-gate, protocol-compliance).","created_at":"2026-04-29T10:29:58Z"},{"id":"019dd8ec-f7ca-7b04-9391-fc357dd222eb","issue_id":"sdplab-1ily","author":"Andrei","text":"Cross-model review completed with Pi subagents: zai/glm-5.1, kimi-coding/k2p6, minimax/MiniMax-M2.7. All returned no P0/P1. Fixed review findings in commit 0897f1b6: README/Quickstart counts and Pi harness docs, dispatch harness docstring, Pi template newline hygiene. Existing blocker finding for manifest rewrite remains sdplab-vd3r; new non-blocking follow-up for Pi prompt path normalization: sdplab-zdjr. PR #145 checks green after push.","created_at":"2026-04-29T11:08:22Z"},{"id":"019dd8fb-265b-7de2-9310-6c9c6281d1d3","issue_id":"sdplab-1ily","author":"Andrei","text":"Follow-up to cross-model review: fixed blocking manifest rewrite finding sdplab-vd3r in commit b307ff75; PR #145 checks green at head b307ff75.","created_at":"2026-04-29T11:23:51Z"},{"id":"019dd911-3824-712c-8c80-8debdbc4910b","issue_id":"sdplab-1ily","author":"Andrei","text":"PR #145 green after follow-up fix 0e9b5e67: no CI failures/pending checks; mergeStateStatus=CLEAN.","created_at":"2026-04-29T11:47:58Z"}],"dependency_count":0,"dependent_count":0,"comment_count":5} +{"_type":"issue","id":"sdplab-387l","title":"Review: coverage tier minimums documented blocking but implemented advisory","description":"source=review; blocking=true; severity=major. docs/reference/ci-gates-map.md says maturity-tiered coverage minimums block, but .github/workflows/ci.yml marks that step continue-on-error:true. Decide policy and align docs/workflow.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T06:23:39Z","created_by":"Andrei","updated_at":"2026-04-29T06:23:49Z","started_at":"2026-04-29T06:23:49Z","comments":[{"id":"019dd7f0-da73-7b23-971f-61c05751d797","issue_id":"sdplab-387l","author":"Andrei","text":"Implemented locally: docs/reference/ci-gates-map.md now says coverage gate blocks baseline delta while maturity-tier package minimums are advisory, matching .github/workflows/ci.yml continue-on-error behavior. Verification: check-release-surface OK.","created_at":"2026-04-29T06:32:59Z"},{"id":"019dd7f7-5503-7a06-a168-7b7f9ab63a8e","issue_id":"sdplab-387l","author":"Andrei","text":"Draft PR opened: https://github.com/fall-out-bug/sdp_lab/pull/144. Branch: fix/review-promises. Public installer sync PR: https://github.com/fall-out-bug/sdp/pull/134.","created_at":"2026-04-29T06:40:04Z"}],"dependency_count":0,"dependent_count":0,"comment_count":2} +{"_type":"issue","id":"sdplab-ndzp","title":"Review: trust guarantees overstate conditional evidence and scope gates","description":"source=review; blocking=true; severity=major. docs/reference/trust-guarantees.md claims every merge has PR gate evidence and scope enforcement, but CI skips evidence validation when no evidence files changed and skips scope gate when no checkpoint files changed. Align wording or gate behavior.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T06:23:38Z","created_by":"Andrei","updated_at":"2026-04-29T06:23:49Z","started_at":"2026-04-29T06:23:49Z","comments":[{"id":"019dd7f0-cd26-74e2-beb1-b85397cac58e","issue_id":"sdplab-ndzp","author":"Andrei","text":"Implemented locally: docs/reference/trust-guarantees.md now states evidence/scope/contract guarantees are conditional on changed inputs and enabled controls, instead of claiming every merge has PR gate evidence. Verification: sdp-doc-sync and sdp-protocol-check run; remaining warnings are pre-existing backlog/doc drift.","created_at":"2026-04-29T06:32:56Z"},{"id":"019dd7f7-528e-779d-9fa8-6a0c1ff60ae2","issue_id":"sdplab-ndzp","author":"Andrei","text":"Draft PR opened: https://github.com/fall-out-bug/sdp_lab/pull/144. Branch: fix/review-promises. Public installer sync PR: https://github.com/fall-out-bug/sdp/pull/134.","created_at":"2026-04-29T06:40:03Z"}],"dependency_count":0,"dependent_count":0,"comment_count":2} +{"_type":"issue","id":"sdplab-e11x","title":"Review: maturity matrix contradicts current release config","description":"source=review; blocking=true; severity=major. docs/reference/maturity-matrix.md has a stale lower GoReleaser section saying sdp is not in GoReleaser and sdp-eval is included, contradicting current .goreleaser.yml and the top of the same canonical doc.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T06:23:37Z","created_by":"Andrei","updated_at":"2026-04-29T06:23:48Z","started_at":"2026-04-29T06:23:48Z","comments":[{"id":"019dd7f0-bebb-7514-97de-92abc6dd7a31","issue_id":"sdplab-e11x","author":"Andrei","text":"Implemented locally: removed stale GoReleaser claims from docs/reference/maturity-matrix.md and replaced with current .goreleaser.yml build inventory. Verification: scripts/check-release-surface.sh OK.","created_at":"2026-04-29T06:32:52Z"},{"id":"019dd7f7-5012-77d7-a067-06a2d0d95c97","issue_id":"sdplab-e11x","author":"Andrei","text":"Draft PR opened: https://github.com/fall-out-bug/sdp_lab/pull/144. Branch: fix/review-promises. Public installer sync PR: https://github.com/fall-out-bug/sdp/pull/134.","created_at":"2026-04-29T06:40:03Z"}],"dependency_count":0,"dependent_count":0,"comment_count":2} +{"_type":"issue","id":"sdplab-do6j","title":"Review: selective harness install fails adapter doctor","description":"source=review; blocking=true; severity=major. sdp init can install only codex/other selected harnesses, but target sdp.manifest.yaml still declares all harnesses, so sdp doctor adapters reports missing files for uninstalled harnesses. Fix manifest/filter/doctor semantics and add regression coverage.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T06:23:36Z","created_by":"Andrei","updated_at":"2026-04-29T06:23:47Z","started_at":"2026-04-29T06:23:47Z","comments":[{"id":"019dd7f0-b0f4-71e4-9400-9bf0e2212ab7","issue_id":"sdplab-do6j","author":"Andrei","text":"Implemented locally: sdp init now persists selected harnesses into target sdp.manifest.yaml; regression added for codex-only init followed by sdp doctor adapters. Verification: go test -tags sqlite_fts5 ./cmd/sdp -count=1; temp codex-only install reports manifest validate OK and doctor adapters 0 drift.","created_at":"2026-04-29T06:32:49Z"},{"id":"019dd7f7-4d91-73ef-9f68-fcdfb501956c","issue_id":"sdplab-do6j","author":"Andrei","text":"Draft PR opened: https://github.com/fall-out-bug/sdp_lab/pull/144. Branch: fix/review-promises. Public installer sync PR: https://github.com/fall-out-bug/sdp/pull/134.","created_at":"2026-04-29T06:40:02Z"}],"dependency_count":0,"dependent_count":0,"comment_count":2} +{"_type":"issue","id":"sdplab-jglv","title":"Review: public install path mismatches documented installer","description":"source=review; blocking=true; severity=critical. README/Quickstart curl fall-out-bug/sdp/main/scripts/install.sh, but the public sdp script is a release downloader, not this repo's clone/build/init installer. Fix public install docs or publish the expected installer path so install flow and verification contract match reality.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T06:23:35Z","created_by":"Andrei","updated_at":"2026-04-29T06:23:47Z","started_at":"2026-04-29T06:23:47Z","comments":[{"id":"019dd7f0-9d34-7d3a-8b3e-1f13c29d8757","issue_id":"sdplab-jglv","author":"Andrei","text":"Implemented locally: kept public install URL on fall-out-bug/sdp, added scripts/install.sh to sdp-publish mapping, updated publish docs, and created public sdp PR #134 to sync the installer. Verification: check-public-metadata OK; check-release-surface OK; publish dry-run includes scripts/install.sh.","created_at":"2026-04-29T06:32:44Z"},{"id":"019dd7f7-4789-7cc9-b7de-ec82454f2b47","issue_id":"sdplab-jglv","author":"Andrei","text":"Draft PR opened: https://github.com/fall-out-bug/sdp_lab/pull/144. Branch: fix/review-promises. Public installer sync PR: https://github.com/fall-out-bug/sdp/pull/134.","created_at":"2026-04-29T06:40:00Z"}],"dependency_count":0,"dependent_count":0,"comment_count":2} +{"_type":"issue","id":"sdplab-wuaa.6","title":"F162-06: Cursor Pi Kilo harness profiles and smoke contracts","description":"WS 00-162-06. Promote Cursor, Pi, and Kilo into explicit F162 packaging targets. Define each harness capability profile, native instruction/config files, launch shape, limitations, and smoke tests before adapter generation and runtime dispatch claim support.","status":"open","priority":1,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T06:18:17Z","created_by":"Andrei","updated_at":"2026-04-29T06:18:17Z","labels":["F162","cursor","harness","kilo","multi-harness","pi","promptops","sdp"],"dependencies":[{"issue_id":"sdplab-wuaa.6","depends_on_id":"sdplab-wuaa","type":"parent-child","created_at":"2026-04-29T09:18:17Z","created_by":"Andrei","metadata":"{}"},{"issue_id":"sdplab-wuaa.6","depends_on_id":"sdplab-wuaa.1","type":"blocks","created_at":"2026-04-29T09:18:21Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":1,"dependent_count":3,"comment_count":0} +{"_type":"issue","id":"sdplab-wuaa.4","title":"F162-04: runtime dispatch selection and model profile integration","description":"WS 00-162-04. Make sdp dispatch/run resolve harness + model + role + task_class into one prompt bundle, invoke Claude/Codex/OpenCode/Cursor/Pi/Kilo with the correct flags when their profiles are green, and record bundle id/hash in evidence.","status":"open","priority":1,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T06:09:06Z","created_by":"Andrei","updated_at":"2026-04-29T06:20:43Z","labels":["F162","dispatch","multi-harness","promptops","sdp"],"dependencies":[{"issue_id":"sdplab-wuaa.4","depends_on_id":"sdplab-wuaa","type":"parent-child","created_at":"2026-04-29T09:09:05Z","created_by":"Andrei","metadata":"{}"},{"issue_id":"sdplab-wuaa.4","depends_on_id":"sdplab-wuaa.1","type":"blocks","created_at":"2026-04-29T09:09:20Z","created_by":"Andrei","metadata":"{}"},{"issue_id":"sdplab-wuaa.4","depends_on_id":"sdplab-wuaa.6","type":"blocks","created_at":"2026-04-29T09:18:25Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":2,"dependent_count":1,"comment_count":0} +{"_type":"issue","id":"sdplab-wuaa.3","title":"F162-03: prompt bundle doctor gates and isolation lint","description":"WS 00-162-03. Add prompt bundle doctor checks: missing sections, stale bundle hashes, cross-harness leakage, model-specific rules in universal files, generated entrypoint drift, and invalid project overlays.","status":"open","priority":1,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T06:09:02Z","created_by":"Andrei","updated_at":"2026-04-29T06:09:02Z","labels":["F162","doctor","multi-harness","promptops","sdp"],"dependencies":[{"issue_id":"sdplab-wuaa.3","depends_on_id":"sdplab-wuaa","type":"parent-child","created_at":"2026-04-29T09:09:01Z","created_by":"Andrei","metadata":"{}"},{"issue_id":"sdplab-wuaa.3","depends_on_id":"sdplab-wuaa.1","type":"blocks","created_at":"2026-04-29T09:09:17Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"comment_count":0} +{"_type":"issue","id":"sdplab-wuaa.2","title":"F162-02: harness-native system entrypoint generation","description":"WS 00-162-02. Extend adapter generation so Claude Code, Codex, OpenCode, Cursor, Pi, and Kilo receive generated project/system entrypoints that reference the resolved SDP bundle instead of loading all prompts. Preserve user-owned files through generated markers and overlays.","status":"open","priority":1,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T06:08:58Z","created_by":"Andrei","updated_at":"2026-04-29T06:20:42Z","labels":["F162","adapters","multi-harness","promptops","sdp"],"dependencies":[{"issue_id":"sdplab-wuaa.2","depends_on_id":"sdplab-wuaa","type":"parent-child","created_at":"2026-04-29T09:08:58Z","created_by":"Andrei","metadata":"{}"},{"issue_id":"sdplab-wuaa.2","depends_on_id":"sdplab-wuaa.1","type":"blocks","created_at":"2026-04-29T09:09:14Z","created_by":"Andrei","metadata":"{}"},{"issue_id":"sdplab-wuaa.2","depends_on_id":"sdplab-wuaa.6","type":"blocks","created_at":"2026-04-29T09:18:23Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":2,"dependent_count":1,"comment_count":0} +{"_type":"issue","id":"sdplab-wuaa.1","title":"F162-01: prompt bundle manifest schema and resolver contract","description":"WS 00-162-01. Define the prompt_bundles manifest extension, section types, harness/model/role/task selectors, bundle hashing, source provenance, overlay semantics, and failure behavior for unresolved or conflicting prompt sections.","status":"open","priority":1,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T06:08:57Z","created_by":"Andrei","updated_at":"2026-04-29T06:08:57Z","labels":["F162","manifest","multi-harness","promptops","sdp"],"dependencies":[{"issue_id":"sdplab-wuaa.1","depends_on_id":"sdplab-wuaa","type":"parent-child","created_at":"2026-04-29T09:08:56Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":0,"dependent_count":4,"comment_count":0} +{"_type":"issue","id":"sdplab-wuaa","title":"F162: System-level prompt bundle packaging","description":"Extend F141 from adapter installation to deterministic prompt bundle resolution for Claude Code, Codex, OpenCode, Cursor, Pi, and Kilo. SDP should install harness-native project/system entrypoints that load only the resolved bundle for harness, model, role, and task class, with AI Fluency 4D intake and SDP delivery discipline included by default. The feature also adds capability profiles and doctor gates so prompt routing is auditable instead of relying on agents to read a prompt soup.","status":"open","priority":1,"issue_type":"epic","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T06:08:53Z","created_by":"Andrei","updated_at":"2026-04-29T06:20:41Z","labels":["F162","multi-harness","promptops","sdp"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-tffu.4","title":"F161-04: integrate pi-review verdict, telemetry, beads, and delivery loop","description":"WS 00-161-04. Persist raw review telemetry, compact verdicts, bead findings, and delivery-loop behavior so subagents repeat fix-review rounds until the pi-review gate is clean.","status":"closed","priority":1,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-28T13:37:26Z","created_by":"Andrei","updated_at":"2026-04-29T09:26:18Z","closed_at":"2026-04-29T09:26:18Z","close_reason":"merged via PR#143","labels":["F161","delivery-loop","pi-review","sdp"],"dependencies":[{"issue_id":"sdplab-tffu.4","depends_on_id":"sdplab-tffu","type":"parent-child","created_at":"2026-04-28T16:37:25Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-tffu.3","title":"F161-03: run pi model review and synthesis","description":"WS 00-161-03. Use pi as the local runtime to call the already configured ZAI/GLM and Kimi reviewers, normalize model outputs, apply OpenRouter fallback, and synthesize SDP-style findings. Treat glm as the GLM model family under the zai provider, not as a separate provider slot.","status":"closed","priority":1,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-28T13:37:06Z","created_by":"Andrei","updated_at":"2026-04-29T09:26:15Z","closed_at":"2026-04-29T09:26:15Z","close_reason":"merged via PR#143","labels":["F161","models","pi-review","sdp"],"dependencies":[{"issue_id":"sdplab-tffu.3","depends_on_id":"sdplab-tffu","type":"parent-child","created_at":"2026-04-28T16:37:06Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-tffu.2","title":"F161-02: build working-tree context packet and test evidence","description":"WS 00-161-02. Implement the scoped context packet for touched files, base/head diff, project rules, bead context, and deterministic test evidence captured by SDP before model review.","status":"closed","priority":1,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-28T13:36:54Z","created_by":"Andrei","updated_at":"2026-04-29T09:26:13Z","closed_at":"2026-04-29T09:26:13Z","close_reason":"merged via PR#143","labels":["F161","context","pi-review","sdp"],"dependencies":[{"issue_id":"sdplab-tffu.2","depends_on_id":"sdplab-tffu","type":"parent-child","created_at":"2026-04-28T16:36:54Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-tffu.1","title":"F161-01: specify pi-review contracts and schemas","description":"WS 00-161-01. Define the stable sdp pi-review contract, review verdict extensions, pi-review run telemetry schema, and schema index entry without breaking the existing @review verdict shape.","status":"closed","priority":1,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-28T13:36:43Z","created_by":"Andrei","updated_at":"2026-04-29T09:26:12Z","closed_at":"2026-04-29T09:26:12Z","close_reason":"merged via PR#143","labels":["F161","contracts","pi-review","sdp"],"dependencies":[{"issue_id":"sdplab-tffu.1","depends_on_id":"sdplab-tffu","type":"parent-child","created_at":"2026-04-28T16:36:43Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-tffu","title":"F161: pi-review external review gate","description":"Define and deliver the sdp pi-review review gate that packages touched working-tree context, runs pi as the local model-review binary with the already configured Kimi and ZAI/GLM MVP reviewers, falls back through OpenRouter, writes SDP-style verdict telemetry, and files actionable findings into beads until clean. Treat glm as the GLM model family under the zai provider, not as a separate provider slot.","status":"closed","priority":1,"issue_type":"epic","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-28T13:36:29Z","created_by":"Andrei","updated_at":"2026-04-29T09:26:43Z","started_at":"2026-04-28T20:17:27Z","closed_at":"2026-04-29T09:26:43Z","close_reason":"merged via PR#143, all 4 children closed","labels":["F161","pi-review","sdp"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-njbd","title":"F150 CI: fix LimitsCache header/poller overwrite race","description":"PR #137 coverage-gate failed in internal/dispatch/harness TestLimitsCache_UpdateFromHeaders_Priority. Root cause: UpdateFromHeaders stores header limits before marking header TTL, allowing a poller tick to overwrite fresh header-derived limits. Fix in same PR. Source=ci, blocking=true, PR #137.","status":"closed","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-27T10:39:00Z","created_by":"Andrei","updated_at":"2026-04-28T21:30:27Z","started_at":"2026-04-27T10:39:05Z","closed_at":"2026-04-28T21:30:27Z","close_reason":"merged via PR#142 — LimitsCache race fix included in F150 product layering PR","dependencies":[{"issue_id":"sdplab-njbd","depends_on_id":"sdplab-qgq1","type":"discovered-from","created_at":"2026-04-27T13:39:00Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-3uep","title":"F150-10: produce debt ledger and release readiness report","description":"Close the program with a release readiness report: what shipped, what is blocked, what remains lab-only, what was intentionally deferred, and which beads own remaining work. WS: 00-150-10.","status":"closed","priority":1,"issue_type":"task","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-27T10:24:04Z","created_by":"Andrei","updated_at":"2026-04-27T16:33:50Z","started_at":"2026-04-27T16:25:07Z","closed_at":"2026-04-27T16:33:50Z","close_reason":"ACs verified: readiness report at docs/reference/release-readiness.md lists all shipped changes with evidence, remaining debt linked, lab-only surfaces named, release blockers separated (none), honest assessment included. Commit de9fd1af.","dependencies":[{"issue_id":"sdplab-3uep","depends_on_id":"sdplab-5r4x","type":"blocks","created_at":"2026-04-27T13:24:29Z","created_by":"Andrei","metadata":"{}"},{"issue_id":"sdplab-3uep","depends_on_id":"sdplab-crct","type":"blocks","created_at":"2026-04-27T13:24:32Z","created_by":"Andrei","metadata":"{}"},{"issue_id":"sdplab-3uep","depends_on_id":"sdplab-hjl7","type":"blocks","created_at":"2026-04-27T13:24:30Z","created_by":"Andrei","metadata":"{}"},{"issue_id":"sdplab-3uep","depends_on_id":"sdplab-kcrd","type":"blocks","created_at":"2026-04-27T13:24:29Z","created_by":"Andrei","metadata":"{}"},{"issue_id":"sdplab-3uep","depends_on_id":"sdplab-nyr0","type":"discovered-from","created_at":"2026-04-27T13:24:04Z","created_by":"Andrei","metadata":"{}"},{"issue_id":"sdplab-3uep","depends_on_id":"sdplab-p4hj","type":"blocks","created_at":"2026-04-27T13:24:32Z","created_by":"Andrei","metadata":"{}"},{"issue_id":"sdplab-3uep","depends_on_id":"sdplab-q2cb","type":"blocks","created_at":"2026-04-27T13:24:31Z","created_by":"Andrei","metadata":"{}"},{"issue_id":"sdplab-3uep","depends_on_id":"sdplab-sa0w","type":"blocks","created_at":"2026-04-27T13:24:33Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":7,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-sa0w","title":"F150-09: align product-facing docs to layers","description":"Update README/product-surface/quickstart or related docs so the public story matches the chosen layers: SDP Toolkit, ChangePassport, Enterprise Perimeter Control Plane, and lab-only surfaces. WS: 00-150-09.","status":"closed","priority":1,"issue_type":"task","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-27T10:23:59Z","created_by":"Andrei","updated_at":"2026-04-27T15:04:20Z","started_at":"2026-04-27T14:56:37Z","closed_at":"2026-04-27T15:04:20Z","close_reason":"ACs verified: README names SDP Toolkit as installable product, lab/research separated, ChangePassport as separate surface, EDG as governed delivery layer, formula contents documented.","dependencies":[{"issue_id":"sdplab-sa0w","depends_on_id":"sdplab-nyr0","type":"discovered-from","created_at":"2026-04-27T13:23:59Z","created_by":"Andrei","metadata":"{}"},{"issue_id":"sdplab-sa0w","depends_on_id":"sdplab-qgq1","type":"blocks","created_at":"2026-04-27T13:24:22Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"comment_count":0} {"_type":"issue","id":"sdplab-p4hj","title":"F150-08: prepare Homebrew formula dry run","description":"Create or validate a Homebrew formula path for the stable release surface without publishing a tap. Exercise formula install/test locally against GoReleaser or source build artifacts. WS: 00-150-08.","status":"closed","priority":1,"issue_type":"task","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-27T10:23:54Z","created_by":"Andrei","updated_at":"2026-04-27T16:25:05Z","started_at":"2026-04-27T16:07:45Z","closed_at":"2026-04-27T16:25:05Z","close_reason":"ACs verified: formula installs sdp binary locally via brew install, test block verifies --help and scout --help, no lab-only binaries exposed, evidence at docs/evidence/F150-08-homebrew-dry-run.md, tap publishing deferred.","dependencies":[{"issue_id":"sdplab-p4hj","depends_on_id":"sdplab-5r4x","type":"blocks","created_at":"2026-04-27T13:24:28Z","created_by":"Andrei","metadata":"{}"},{"issue_id":"sdplab-p4hj","depends_on_id":"sdplab-8rk7","type":"blocks","created_at":"2026-04-27T13:24:26Z","created_by":"Andrei","metadata":"{}"},{"issue_id":"sdplab-p4hj","depends_on_id":"sdplab-kcrd","type":"blocks","created_at":"2026-04-27T13:24:27Z","created_by":"Andrei","metadata":"{}"},{"issue_id":"sdplab-p4hj","depends_on_id":"sdplab-nyr0","type":"discovered-from","created_at":"2026-04-27T13:23:54Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":3,"dependent_count":1,"comment_count":0} @@ -254,6 +287,9 @@ {"_type":"issue","id":"sdplab-7","title":"F061-02: bd ready → sdp ready bridge","status":"closed","priority":1,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-02-28T21:35:17Z","created_by":"Andrey Zhukov","updated_at":"2026-04-20T14:27:34Z","closed_at":"2026-04-20T14:27:34Z","close_reason":"Verified: code exists, 218 tests pass across guard/evidence/monitor/beads/workstream packages. WS files marked done with all acceptance criteria checked.","labels":["F061","beads","ecosystem"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-2","title":"F059-02: Session evidence emitter","status":"closed","priority":1,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-02-28T21:34:39Z","created_by":"Andrey Zhukov","updated_at":"2026-04-20T14:27:00Z","closed_at":"2026-04-20T14:27:00Z","close_reason":"Verified: code exists, 218 tests pass across guard/evidence/monitor/beads/workstream packages. WS files marked done with all acceptance criteria checked.","labels":["F059","ecosystem","ohmyopencode"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-3","title":"F059-01: Pre-tool-call guard hook","status":"closed","priority":1,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-02-28T21:34:39Z","created_by":"Andrey Zhukov","updated_at":"2026-04-20T14:27:00Z","closed_at":"2026-04-20T14:27:00Z","close_reason":"Verified: code exists, 218 tests pass across guard/evidence/monitor/beads/workstream packages. WS files marked done with all acceptance criteria checked.","labels":["F059","ecosystem","ohmyopencode"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-jt6y","title":"Review: live bash tool denylist is not a security boundary","description":"source=review; aspect=security,architecture; blocking=false; severity=major. internal/agentloop/tools_live.go executes model-provided bash -c behind a denylist. This is acceptable only as lab/experimental, not as a trusted boundary. Add sandbox/allowlisted exec or mark/guard the surface explicitly.","status":"open","priority":2,"issue_type":"bug","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:48:53Z","created_by":"Andrei","updated_at":"2026-04-29T09:48:53Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-sjzq","title":"Review: planner/tower/fine-tune/localmodel operations lack bounded contexts","description":"source=review; aspect=go-best-practices,reliability; blocking=false; severity=major. internal/planner/scheduler.go, internal/tower/tower.go, internal/finetune/runner/openai.go, cmd/sdp-ft-run/main.go, and internal/localmodel/client.go use context.Background or timeout-free http clients in long-running/live paths. Thread request/signal contexts and default bounded HTTP timeouts.","status":"open","priority":2,"issue_type":"bug","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:48:52Z","created_by":"Andrei","updated_at":"2026-04-29T09:48:52Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-wuaa.5","title":"F162-05: downstream packaging docs and F141 migration guide","description":"WS 00-162-05. Document system-level packaging, direct harness behavior vs SDP launcher behavior, install/update/rollback, and the migration path from F141 adapter parity to F162 prompt bundle resolution for Claude/Codex/OpenCode/Cursor/Pi/Kilo.","status":"open","priority":2,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T06:09:11Z","created_by":"Andrei","updated_at":"2026-04-29T06:20:44Z","labels":["F162","docs","multi-harness","onboarding","promptops","sdp"],"dependencies":[{"issue_id":"sdplab-wuaa.5","depends_on_id":"sdplab-wuaa","type":"parent-child","created_at":"2026-04-29T09:09:11Z","created_by":"Andrei","metadata":"{}"},{"issue_id":"sdplab-wuaa.5","depends_on_id":"sdplab-wuaa.2","type":"blocks","created_at":"2026-04-29T09:09:23Z","created_by":"Andrei","metadata":"{}"},{"issue_id":"sdplab-wuaa.5","depends_on_id":"sdplab-wuaa.3","type":"blocks","created_at":"2026-04-29T09:09:26Z","created_by":"Andrei","metadata":"{}"},{"issue_id":"sdplab-wuaa.5","depends_on_id":"sdplab-wuaa.4","type":"blocks","created_at":"2026-04-29T09:09:27Z","created_by":"Andrei","metadata":"{}"},{"issue_id":"sdplab-wuaa.5","depends_on_id":"sdplab-wuaa.6","type":"blocks","created_at":"2026-04-29T09:18:27Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":4,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-guga.1","title":"F158-01: Decision artifact for Go import-path contamination (options A/B/C analysis + recommendation)","description":"Compare A/B/C across: non-SDP adopter perception, dev-experience cost, infrastructure cost, governance complexity, extraction mechanics. Recommend one. Output: docs/strategy/iip-import-path-decision.md. WS: 00-158-01.","status":"open","priority":2,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-27T13:04:53Z","created_by":"Andrei","updated_at":"2026-04-27T13:04:53Z","dependencies":[{"issue_id":"sdplab-guga.1","depends_on_id":"sdplab-guga","type":"parent-child","created_at":"2026-04-27T16:04:53Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-guga","title":"F158: Go Import-Path Contamination Decision","description":"Highest unaddressed structural risk per IIP-council (all 5 roles flagged). Module path `github.com/\u003csdp-lab-org\u003e/sdp_lab/arch-snap` permanently associates IIP with SDP in every Go import statement, potentially suppressing non-SDP adoption. Three options to analyze: (A) neutral GitHub org from inception, (B) accept contamination during incubation, change at extraction, (C) hybrid (monorepo at very early phase, neutral org at v0.1). Decision blocks F156 and F157 promotion to active IIP. Memo v3 §IIP unaddressed risks #1 and §Open Items 14.","status":"open","priority":2,"issue_type":"epic","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-27T13:04:50Z","created_by":"Andrei","updated_at":"2026-04-27T13:04:50Z","dependency_count":0,"dependent_count":2,"comment_count":0} {"_type":"issue","id":"sdplab-kw3q.5","title":"F154-05: sdp-eval-core v1","description":"API v1 of eval-harness primitives. Task-class evals, baseline comparison, scoreboard, hallucination/evidence-mismatch metric. ≤60-line AGENTS.md with assumptions. WS: 00-154-05.","status":"closed","priority":2,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-27T13:04:25Z","created_by":"Andrei","updated_at":"2026-04-27T17:35:23Z","closed_at":"2026-04-27T17:35:23Z","close_reason":"merged via PR#141","dependencies":[{"issue_id":"sdplab-kw3q.5","depends_on_id":"sdplab-kw3q","type":"parent-child","created_at":"2026-04-27T16:04:25Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} @@ -429,6 +465,7 @@ {"_type":"issue","id":"sdplab-5","title":"F060-02: Hook → Guard scope bridge","notes":"2026-04-18 triage: reframe — hook→guard bridge may merge with F059 OmO guard (already Done); re-evaluate before revival","status":"closed","priority":2,"issue_type":"task","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-02-28T21:34:56Z","created_by":"Andrey Zhukov","updated_at":"2026-04-26T13:03:07Z","closed_at":"2026-04-26T13:03:07Z","close_reason":"F060 Gas Town bridges built: Convoy→WS, Hook→Guard, Witness→Escalation.","labels":["F060","ecosystem","gastown"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-6","title":"F060-01: Convoy → Workstream bridge","notes":"2026-04-18 triage: reframe — depends on F134 evidence chain before revival; scope mapping to F134 provenance + evidence gates TBD","status":"closed","priority":2,"issue_type":"task","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-02-28T21:34:56Z","created_by":"Andrey Zhukov","updated_at":"2026-04-26T13:03:07Z","closed_at":"2026-04-26T13:03:07Z","close_reason":"F060 Gas Town bridges built: Convoy→WS, Hook→Guard, Witness→Escalation.","labels":["F060","ecosystem","gastown"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-1","title":"F059-04: Stuck detection via timestamps","status":"closed","priority":2,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-02-28T21:34:39Z","created_by":"Andrey Zhukov","updated_at":"2026-04-20T14:27:01Z","closed_at":"2026-04-20T14:27:01Z","close_reason":"Verified: code exists, 218 tests pass across guard/evidence/monitor/beads/workstream packages. WS files marked done with all acceptance criteria checked.","labels":["F059","ecosystem","ohmyopencode"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-zdjr","title":"Pi adapter: rewrite legacy Claude skill refs in Pi prompt bodies","description":"GLM-5 review on PR #145 found Pi prompts still include legacy .claude/skills/\u003cname\u003e/SKILL.md references from command bodies. The Pi wrapper tells the model to reinterpret them, so this is non-blocking, but the adapter should eventually normalize those references to Pi-native skill names/paths to reduce harness-specific leakage.","status":"closed","priority":3,"issue_type":"task","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T10:57:49Z","created_by":"Andrei","updated_at":"2026-04-29T11:50:05Z","started_at":"2026-04-29T11:27:38Z","closed_at":"2026-04-29T11:50:05Z","close_reason":"Merged in PR #145 (cb5470e): Pi prompts normalize legacy Claude skill refs","labels":["F162","harness","pi","promptops","review-finding"],"dependencies":[{"issue_id":"sdplab-zdjr","depends_on_id":"sdplab-1ily","type":"discovered-from","created_at":"2026-04-29T13:57:49Z","created_by":"Andrei","metadata":"{}"}],"comments":[{"id":"019dd908-a557-7d8c-bcc3-dca5a77950fa","issue_id":"sdplab-zdjr","author":"Andrei","text":"Fixed in PR #145 commit 0e9b5e67: Pi adapter rewrites legacy .claude/skills/\u003cname\u003e/SKILL.md command-body references to Pi skill names, live/generated .pi prompts regenerated, and smoke now fails if Pi prompts contain .claude skill refs or omit . Verification passed: SDP_GO_QUALITY_MODE=host ./scripts/run_go_quality_gates.sh; scripts/smoke/pi_resources.mjs; sdp doctor adapters --strict; manifest validate; generate-adapters --check.","created_at":"2026-04-29T11:38:36Z"},{"id":"019dd908-c74f-73ad-a59c-605b1f46a75c","issue_id":"sdplab-zdjr","author":"Andrei","text":"Correction: smoke assertion checks literal $ARGUMENTS in every Pi prompt template.","created_at":"2026-04-29T11:38:44Z"},{"id":"019dd911-3119-7497-99c6-2385a1b1dbac","issue_id":"sdplab-zdjr","author":"Andrei","text":"PR #145 checks are green at head 0e9b5e67 after the Pi prompt ref normalization fix. Issue remains in_progress until PR merge per repo policy.","created_at":"2026-04-29T11:47:56Z"}],"dependency_count":0,"dependent_count":0,"comment_count":3} {"_type":"issue","id":"sdplab-mipu.1","title":"F160-01: Procurement-ready install profile artifact","description":"Document: scoped permissions, no-egress mode, data residency, SOC2/SOC3 posture, SLA template, indemnification template, vendor-onboarding answers (DPA, BCP/DR, sub-processors). Output: docs/strategy/procurement-install-profile.md. WS: 00-160-01.","status":"open","priority":3,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-27T13:05:05Z","created_by":"Andrei","updated_at":"2026-04-27T13:05:05Z","dependencies":[{"issue_id":"sdplab-mipu.1","depends_on_id":"sdplab-mipu","type":"parent-child","created_at":"2026-04-27T16:05:05Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-mipu","title":"F160: Procurement / Compliance Install Profile","description":"Install profile that survives basic security review for dev-led-to-manager-paid path. SOC2 stance, SLA template, indemnification template, no-egress-by-default, scoped GitHub App permissions, data-residency posture. Required before any regulated-industry pilot. Memo-council and IIP-council both flagged.","status":"open","priority":3,"issue_type":"epic","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-27T13:05:02Z","created_by":"Andrei","updated_at":"2026-04-27T13:05:02Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-qqnc.1","title":"F159-01: Competitive positioning + battle card","description":"Per-competitor: what they do, where they win, where SDP differentiates (agent-neutral cross-tool evidence + override trail + Operator Mode + Toolbox + IIP family). Position statement, top 3 buying objections, top 3 SDP rebuttals. Output: docs/strategy/competitive-positioning.md. WS: 00-159-01.","status":"open","priority":3,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-27T13:04:59Z","created_by":"Andrei","updated_at":"2026-04-27T13:04:59Z","dependencies":[{"issue_id":"sdplab-qqnc.1","depends_on_id":"sdplab-qqnc","type":"parent-child","created_at":"2026-04-27T16:04:59Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} @@ -517,16 +554,16 @@ {"_type":"issue","id":"sdplab-houh","title":"F036: Orchestrate + in-toto (historical placeholder)","description":"absorbed by F064–F067 auto-attestation","status":"closed","priority":4,"issue_type":"feature","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-18T11:27:58Z","created_by":"Andrei","updated_at":"2026-04-18T11:27:58Z","closed_at":"2026-04-18T11:27:58Z","close_reason":"absorbed by F064–F067 auto-attestation","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-crg","title":"F034: Graduated Enforcement (historical placeholder)","description":"historical placeholder; no active scope","status":"closed","priority":4,"issue_type":"feature","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-18T11:27:55Z","created_by":"Andrei","updated_at":"2026-04-18T11:27:56Z","closed_at":"2026-04-18T11:27:56Z","close_reason":"historical placeholder; no active scope","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-h5y","title":"F032: PR Evidence Summary (historical placeholder)","description":"historical placeholder; no active scope (2026-04-18 triage)","status":"closed","priority":4,"issue_type":"feature","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-18T11:27:34Z","created_by":"Andrei","updated_at":"2026-04-18T11:27:54Z","closed_at":"2026-04-18T11:27:54Z","close_reason":"historical placeholder; no active scope (2026-04-18 triage)","dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"memory","key":"f142-epic-2026-04-26","value":"F142 epic (sdplab-85ji) created 2026-04-26: Workstream coverage gap — picker + ws/INDEX backfill + doctor gate. Root cause: features created via /design bypass /feature autogen → 0 ws files → picker selects leafless feature → /build fails. Affected: F141 (0 ws), F135 (0 ws), F100/F76/F80 (partial). First child: sdplab-8nkm (picker fail incident on sdplab-nj4 F135-05). Future children: audit script, per-feature backfill, INDEX update, picker hardening, sdp doctor backlog gate."} -{"_type":"memory","key":"f142-merged-2026-04-26","value":"F142 closed and merged 2026-04-26: feature/F142-workstream-coverage-gap pushed to origin (https://github.com/fall-out-bug/sdp_lab/tree/feature/F142-workstream-coverage-gap), local main merged with 7 ahead of origin/main (6 F142 commits + retains F141 merge f3ccef7). Ready for PR opening via GitHub UI when user wants. Branch can be deleted locally; remote branch retained for PR."} -{"_type":"memory","key":"f142-r3-done","value":"F142 round 3 closed via PR #123 squash to ff20222. F142-08 mass backfill 49 beads. F142-09 ghost cleanup. F142-10 picker defense. Local main out of sync, needs reset to origin."} -{"_type":"memory","key":"f145-progress-2026-04-26","value":"F145 epic in progress 2026-04-26. Branch: feature/F145-multi-provider-cascade (origin in sync). 3/14 WS committed (not yet closed in beads, awaiting merge): WS07 sdplab-s686 (cursor/opencode --model plumbing P0), WS01 sdplab-0261 (providers/ scaffold + tests), WS08 sdplab-noiq (tier_class + SelectTiers). Commits: 67f085b1 / 9ac769b4 / 30782519 on top of design 752140a3. After WS07/01/08 close, 6 WS unblock in parallel: WS02-06 (5 Provider impls) + WS13 LimitsCache. Then chain: WS09 seed → WS10 cascade pkg → WS11 confidence → WS12 replay → WS14 smoke+demo. 174 tests pass in internal/dispatch/. Next session: after PR #132 merge + restart, run @delivery-loop to dispatch remaining WS via fixed implementer subagents. F145 PR will open when epic is ready (or sensible mid-milestone)."} -{"_type":"memory","key":"subagent-format-spec-2026-04-26","value":"Claude Code subagent file format (.claude/agents/*.md): YAML frontmatter MUST use 'tools: Read, Bash, Glob, Grep' COMMA-STRING format (PascalCase). NOT 'tools: { Read: true }' object form, NOT 'tools: [Read, Bash]' array. Source: docs.claude.com/docs/en/subagents.md lines 222-301. Wrong format → empty tool registry → agent fabricates XML \u003cfunction_calls\u003e in response.text (tool_uses=0). Verified via /tmp/probe diagnostic. Built-in agents (general-purpose, Explore, claude-code-guide) work because their registry is hardcoded, file-based ones go through the broken parsing path. PR #132 fixes 13 SDP custom agents. Note: Claude Code caches agent definitions at session start — fixes require restart to take effect, not just file save."} -{"_type":"memory","key":"f129-epic-created-2026-04-16-sdplab-8k5","value":"F129 epic created 2026-04-16 (sdplab-8k5 + 10 children sdplab-8k5.1..10 + 3 cross-lane: F106-07 sdplab-lqb, F124-05 sdplab-nai, F125-05 sdplab-as0). Addresses 5 audit findings: orchestration autonomy, subagent default, superpowers skill gap, doc drift, ad-hoc regression. Design: docs/plans/2026-04-16-f129-autonomy-regression-design.md. Cross-lane additions reuse existing F106/F124/F125 tracks instead of duplicating."} +{"_type":"memory","key":"subagent-implementer-broken-2026-04-26","value":"BROKEN: subagent_type=implementer fabricates tool_use. Confirmed via 3-test diagnostic 2026-04-26. T1 (implementer+sonnet): tool_uses=0, file not created, fabricated plausible output. T2 (general-purpose+haiku): tool_uses=1, file created. T3 (Explore+haiku): honest refusal (read-only). Pattern: implementer outputs old-format XML \u003cfunction_calls\u003e\u003cinvoke name='...'\u003e as response text instead of structured tool_use blocks. Affects ANY model. Workaround: use subagent_type=general-purpose for code implementation tasks. Affected sessions: F145 WS01/WS07/WS08 dispatch all fabricated, had to redo manually."} {"_type":"memory","key":"f136-peer-memory-horizons-2026-04-20-epic","value":"f136-peer-memory-horizons-2026-04-20 | Epic F136 (sdplab-t1ty) created 2026-04-20: Peer Memory Foundation H1. Children: F136-01..06 (sdplab-r9py/lq4h/olvg/h25q/l0mm/h673). Parked: F137 H2 (sdplab-75u9, revisit 2026-07-06), F138 H3 (sdplab-7c45, revisit 2026-10-05). Design: docs/plans/2026-04-20-f136-peer-memory-h1-design.md. Market gap: peer-collaboration production framework where attribution is first-class (ChatCollab/HULA academic, no commercial product as of April 2026). H1 success = binary: did sdp memory query surface real lost context in 30-day dogfood. Label: peer-memory."} -{"_type":"memory","key":"f141-epic-2026-04-25","value":"F141 epic (sdplab-o4sp) created 2026-04-25: Multi-harness install bootstrap \u0026 adapter parity. Children: F141-01 sdplab-fojs (manifest), -02 sdplab-6ea5 (generator), -03 sdplab-cl4o (bootstrap), -04 sdplab-i9q3 (doctor gate), -05 sdplab-r4y5 (parity matrix), -06 sdplab-cnpm (migration), -07 sdplab-nqjt (README). Closes gap left by F127/F128: downstream repos install SDP piecemeal. Approach: single sdp.manifest.yaml -\u003e adapter generator -\u003e curl|bash bootstrap -\u003e sdp doctor drift gate. Design: docs/plans/2026-04-25-f141-multi-harness-install-bootstrap-design.md. Branch: feature/F141-multi-harness-install-bootstrap."} -{"_type":"memory","key":"f144-merged-2026-04-26","value":"F144 PR #131 MERGED to main at 2026-04-26T17:43Z, merge commit ed393e3. internal/inference/confidence/ live on main: Status enum, Result[T], Policy/Strategy/Checker, 3 strategies (constraint/selfcheck/nsample), 3 adapters (wsverdict/architect/dispatch). 111 tests, ≥85% coverage. Branch feature/F144-inference-confidence still exists locally. Replaces stale 'sdplab-8nkm-untracked-design-doc' status."} -{"_type":"memory","key":"f145-epic-created-2026-04-26","value":"F145 epic (sdplab-ldmq) created 2026-04-26: Multi-Provider Dispatch Matrix \u0026 Confidence-Driven Cascade. 14 children: WS01 sdplab-0261 (providers/ scaffold), WS02 sdplab-y93k (OpenAI), WS03 sdplab-w4ru (Anthropic), WS04 sdplab-bg69 (Cursor ~30 models), WS05 sdplab-60ia (Kimi), WS06 sdplab-5lve (Ollama replaces LocalConfig), WS07 sdplab-s686 (cursor/opencode --model plumbing P0 bug), WS08 sdplab-noiq (tier_class label), WS09 sdplab-3lva (profiles_default.json seed), WS10 sdplab-5ii8 (cascade pkg + Invoker), WS11 sdplab-fco9 (F144 confidence injection), WS12 sdplab-uamt (cascade-replay corpus), WS13 sdplab-135g (LimitsCache), WS14 sdplab-2p8g (smoke + Day-8 demo). Design: docs/plans/2026-04-26-f145-multi-provider-dispatch-cascade-design.md. Branch (planned): feature/F145-multi-provider-cascade. Discovery decisions: providers/ sub-pkg, cascade pkg, tier-label hybrid, composer gate default, UNSURE+FAIL+heuristic short-circuit, MaxDepth/Budget configurable, hybrid LimitsCache, F144 replay extension, harness-CLI keys (no SDP auth)."} {"_type":"memory","key":"f142-closed-2026-04-26","value":"F142 epic (sdplab-85ji) closed 2026-04-26: Workstream coverage gap closed. 6 children closed (gkl5 audit, rqsw baits, s6g7 orphans, f2h0 picker, 3obq doctor, fb1o INDEX). Plus sdplab-8nkm picker incident closed. Branch feature/F142-workstream-coverage-gap, 5 commits not yet merged to main. Delivered: scripts/deliver-pick.sh v2 (skip leafless+design-pending), internal/backlog/audit.go + cmd/sdp/cmd_doctor_backlog.go ('sdp doctor backlog'), 6 ws scaffolds (00-082-01, 00-083-01, 00-084-01, 00-085-01, 00-101-02, 00-133-01), INDEX.md updated, CI gate. 0 findings on doctor backlog. 189 Go tests pass."} {"_type":"memory","key":"f144-epic-created-2026-04-26-sdplab-sjdp","value":"F144 epic created 2026-04-26 (sdplab-sjdp): Inference Confidence \u0026 Quality Control. 8 children attached: 01 sdplab-wzit core lib, 02 sdplab-339w self-check, 03 sdplab-w7j1 n-sample, 04 sdplab-tfmg constraint+composer, 05 sdplab-kk1v ws-verdict adapter (P1), 06 sdplab-cvu3 architect adapter, 07 sdplab-s5x8 dispatch lite, 08 sdplab-kd38 replay+metrics. Critical path: 01→04→05→08. Plus 2 hygiene issues: sdplab-vuw4 (drift docs/design vs docs/plans), sdplab-bv3r (AGENTS.md design-first phrasing). Design: docs/plans/2026-04-26-f144-inference-confidence-design.md. Branch: feature/F144-inference-confidence (not yet created, untracked design doc on worktree-bulk-deliver branch)."} -{"_type":"memory","key":"subagent-implementer-broken-2026-04-26","value":"BROKEN: subagent_type=implementer fabricates tool_use. Confirmed via 3-test diagnostic 2026-04-26. T1 (implementer+sonnet): tool_uses=0, file not created, fabricated plausible output. T2 (general-purpose+haiku): tool_uses=1, file created. T3 (Explore+haiku): honest refusal (read-only). Pattern: implementer outputs old-format XML \u003cfunction_calls\u003e\u003cinvoke name='...'\u003e as response text instead of structured tool_use blocks. Affects ANY model. Workaround: use subagent_type=general-purpose for code implementation tasks. Affected sessions: F145 WS01/WS07/WS08 dispatch all fabricated, had to redo manually."} +{"_type":"memory","key":"f144-merged-2026-04-26","value":"F144 PR #131 MERGED to main at 2026-04-26T17:43Z, merge commit ed393e3. internal/inference/confidence/ live on main: Status enum, Result[T], Policy/Strategy/Checker, 3 strategies (constraint/selfcheck/nsample), 3 adapters (wsverdict/architect/dispatch). 111 tests, ≥85% coverage. Branch feature/F144-inference-confidence still exists locally. Replaces stale 'sdplab-8nkm-untracked-design-doc' status."} +{"_type":"memory","key":"f145-epic-created-2026-04-26","value":"F145 epic (sdplab-ldmq) created 2026-04-26: Multi-Provider Dispatch Matrix \u0026 Confidence-Driven Cascade. 14 children: WS01 sdplab-0261 (providers/ scaffold), WS02 sdplab-y93k (OpenAI), WS03 sdplab-w4ru (Anthropic), WS04 sdplab-bg69 (Cursor ~30 models), WS05 sdplab-60ia (Kimi), WS06 sdplab-5lve (Ollama replaces LocalConfig), WS07 sdplab-s686 (cursor/opencode --model plumbing P0 bug), WS08 sdplab-noiq (tier_class label), WS09 sdplab-3lva (profiles_default.json seed), WS10 sdplab-5ii8 (cascade pkg + Invoker), WS11 sdplab-fco9 (F144 confidence injection), WS12 sdplab-uamt (cascade-replay corpus), WS13 sdplab-135g (LimitsCache), WS14 sdplab-2p8g (smoke + Day-8 demo). Design: docs/plans/2026-04-26-f145-multi-provider-dispatch-cascade-design.md. Branch (planned): feature/F145-multi-provider-cascade. Discovery decisions: providers/ sub-pkg, cascade pkg, tier-label hybrid, composer gate default, UNSURE+FAIL+heuristic short-circuit, MaxDepth/Budget configurable, hybrid LimitsCache, F144 replay extension, harness-CLI keys (no SDP auth)."} +{"_type":"memory","key":"f129-epic-created-2026-04-16-sdplab-8k5","value":"F129 epic created 2026-04-16 (sdplab-8k5 + 10 children sdplab-8k5.1..10 + 3 cross-lane: F106-07 sdplab-lqb, F124-05 sdplab-nai, F125-05 sdplab-as0). Addresses 5 audit findings: orchestration autonomy, subagent default, superpowers skill gap, doc drift, ad-hoc regression. Design: docs/plans/2026-04-16-f129-autonomy-regression-design.md. Cross-lane additions reuse existing F106/F124/F125 tracks instead of duplicating."} +{"_type":"memory","key":"f141-epic-2026-04-25","value":"F141 epic (sdplab-o4sp) created 2026-04-25: Multi-harness install bootstrap \u0026 adapter parity. Children: F141-01 sdplab-fojs (manifest), -02 sdplab-6ea5 (generator), -03 sdplab-cl4o (bootstrap), -04 sdplab-i9q3 (doctor gate), -05 sdplab-r4y5 (parity matrix), -06 sdplab-cnpm (migration), -07 sdplab-nqjt (README). Closes gap left by F127/F128: downstream repos install SDP piecemeal. Approach: single sdp.manifest.yaml -\u003e adapter generator -\u003e curl|bash bootstrap -\u003e sdp doctor drift gate. Design: docs/plans/2026-04-25-f141-multi-harness-install-bootstrap-design.md. Branch: feature/F141-multi-harness-install-bootstrap."} +{"_type":"memory","key":"f142-epic-2026-04-26","value":"F142 epic (sdplab-85ji) created 2026-04-26: Workstream coverage gap — picker + ws/INDEX backfill + doctor gate. Root cause: features created via /design bypass /feature autogen → 0 ws files → picker selects leafless feature → /build fails. Affected: F141 (0 ws), F135 (0 ws), F100/F76/F80 (partial). First child: sdplab-8nkm (picker fail incident on sdplab-nj4 F135-05). Future children: audit script, per-feature backfill, INDEX update, picker hardening, sdp doctor backlog gate."} +{"_type":"memory","key":"f142-r3-done","value":"F142 round 3 closed via PR #123 squash to ff20222. F142-08 mass backfill 49 beads. F142-09 ghost cleanup. F142-10 picker defense. Local main out of sync, needs reset to origin."} +{"_type":"memory","key":"subagent-format-spec-2026-04-26","value":"Claude Code subagent file format (.claude/agents/*.md): YAML frontmatter MUST use 'tools: Read, Bash, Glob, Grep' COMMA-STRING format (PascalCase). NOT 'tools: { Read: true }' object form, NOT 'tools: [Read, Bash]' array. Source: docs.claude.com/docs/en/subagents.md lines 222-301. Wrong format → empty tool registry → agent fabricates XML \u003cfunction_calls\u003e in response.text (tool_uses=0). Verified via /tmp/probe diagnostic. Built-in agents (general-purpose, Explore, claude-code-guide) work because their registry is hardcoded, file-based ones go through the broken parsing path. PR #132 fixes 13 SDP custom agents. Note: Claude Code caches agent definitions at session start — fixes require restart to take effect, not just file save."} +{"_type":"memory","key":"f142-merged-2026-04-26","value":"F142 closed and merged 2026-04-26: feature/F142-workstream-coverage-gap pushed to origin (https://github.com/fall-out-bug/sdp_lab/tree/feature/F142-workstream-coverage-gap), local main merged with 7 ahead of origin/main (6 F142 commits + retains F141 merge f3ccef7). Ready for PR opening via GitHub UI when user wants. Branch can be deleted locally; remote branch retained for PR."} +{"_type":"memory","key":"f145-progress-2026-04-26","value":"F145 epic in progress 2026-04-26. Branch: feature/F145-multi-provider-cascade (origin in sync). 3/14 WS committed (not yet closed in beads, awaiting merge): WS07 sdplab-s686 (cursor/opencode --model plumbing P0), WS01 sdplab-0261 (providers/ scaffold + tests), WS08 sdplab-noiq (tier_class + SelectTiers). Commits: 67f085b1 / 9ac769b4 / 30782519 on top of design 752140a3. After WS07/01/08 close, 6 WS unblock in parallel: WS02-06 (5 Provider impls) + WS13 LimitsCache. Then chain: WS09 seed → WS10 cascade pkg → WS11 confidence → WS12 replay → WS14 smoke+demo. 174 tests pass in internal/dispatch/. Next session: after PR #132 merge + restart, run @delivery-loop to dispatch remaining WS via fixed implementer subagents. F145 PR will open when epic is ready (or sensible mid-milestone)."} diff --git a/.github/workflows/sdp-doctor.yml b/.github/workflows/sdp-doctor.yml index fd48255a..096d8c6f 100644 --- a/.github/workflows/sdp-doctor.yml +++ b/.github/workflows/sdp-doctor.yml @@ -37,6 +37,3 @@ jobs: - name: Doctor — backlog drift gate (F142-05) run: ./sdp doctor backlog # Exit 1 when open features have no ws scaffold and no children (picker bait). - # continue-on-error: true in first iteration — backfill all findings before - # enabling as hard gate. - continue-on-error: true diff --git a/cmd/sdp/.snapshots/doctor-usage.snap b/cmd/sdp/.snapshots/doctor-usage.snap index 7876dafe..72c6b15f 100644 --- a/cmd/sdp/.snapshots/doctor-usage.snap +++ b/cmd/sdp/.snapshots/doctor-usage.snap @@ -1 +1,4 @@ -usage: sdp doctor +DOCTOR CONTROL +Checks: 0 total | 0 passed | 0 issues | 0 info +Status: healthy +Next action: none — control store looks clean. diff --git a/cmd/sdp/.snapshots/main-usage.snap b/cmd/sdp/.snapshots/main-usage.snap index deeb962b..d52b08d2 100644 --- a/cmd/sdp/.snapshots/main-usage.snap +++ b/cmd/sdp/.snapshots/main-usage.snap @@ -8,6 +8,8 @@ Board commands: Doctor commands: sdp doctor control + sdp doctor adapters [--manifest ] [--out ] [--strict] + sdp doctor backlog Dispatch commands: sdp dispatch card @@ -75,6 +77,12 @@ Build commands: Reset commands: sdp reset --feature F042 [--dry-run] [--yes] Reset checkpoint for a feature +Install and adapter commands: + sdp init [--harness ] [--target ] [--manifest ] [--update] + sdp manifest validate [--manifest ] [--schema ] + sdp manifest parity [--write] + sdp generate-adapters [--manifest ] [--out ] [--check] + Coverage commands: sdp coverage-scan [--path DIR] [--threshold PCT] [--format text|json] [--skip-test] [--package PATTERN] [--coverprofile FILE] diff --git a/cmd/sdp/.snapshots/unknown-command.snap b/cmd/sdp/.snapshots/unknown-command.snap index deeb962b..d52b08d2 100644 --- a/cmd/sdp/.snapshots/unknown-command.snap +++ b/cmd/sdp/.snapshots/unknown-command.snap @@ -8,6 +8,8 @@ Board commands: Doctor commands: sdp doctor control + sdp doctor adapters [--manifest ] [--out ] [--strict] + sdp doctor backlog Dispatch commands: sdp dispatch card @@ -75,6 +77,12 @@ Build commands: Reset commands: sdp reset --feature F042 [--dry-run] [--yes] Reset checkpoint for a feature +Install and adapter commands: + sdp init [--harness ] [--target ] [--manifest ] [--update] + sdp manifest validate [--manifest ] [--schema ] + sdp manifest parity [--write] + sdp generate-adapters [--manifest ] [--out ] [--check] + Coverage commands: sdp coverage-scan [--path DIR] [--threshold PCT] [--format text|json] [--skip-test] [--package PATTERN] [--coverprofile FILE] diff --git a/cmd/sdp/cmd_bootstrap.go b/cmd/sdp/cmd_bootstrap.go index ef45efe8..dc75445e 100644 --- a/cmd/sdp/cmd_bootstrap.go +++ b/cmd/sdp/cmd_bootstrap.go @@ -84,9 +84,9 @@ func runBootstrap(args []string) { } switch *mode { case "greenfield": - appendGreenfieldArtifacts(report, repoPath, useDraft, *preset) + appendGreenfieldArtifacts(report, repoPath, useDraft, *preset, *dryRun) case "brownfield": - appendBrownfieldArtifacts(report, repoPath, useDraft) + appendBrownfieldArtifacts(report, repoPath, useDraft, *dryRun) } renderBootstrapReport(report, *format) return @@ -226,7 +226,7 @@ func appendConventionsArtifacts(report *bootstrap.BootstrapReport, repoPath stri // appendGreenfieldArtifacts runs the greenfield bootstrap flow: // preset/interactive → principles + agents rules → split → roadmap. // All outputs are DRAFT-prefixed and appended to the bootstrap report. -func appendGreenfieldArtifacts(report *bootstrap.BootstrapReport, repoPath string, useDraft bool, presetName string) { +func appendGreenfieldArtifacts(report *bootstrap.BootstrapReport, repoPath string, useDraft bool, presetName string, dryRun bool) { var result *bootstrap.BootstrapResult var err error @@ -257,11 +257,11 @@ func appendGreenfieldArtifacts(report *bootstrap.BootstrapReport, repoPath strin // Write DRAFT-PRINCIPLES.md. principlesContent := bootstrap.RenderPrinciplesFile(split.Principles) - writeDraftArtifact(report, repoPath, "PRINCIPLES.md", principlesContent, useDraft, "greenfield-principles") + writeDraftArtifact(report, repoPath, "PRINCIPLES.md", principlesContent, useDraft, dryRun, "greenfield-principles") // Write DRAFT-AGENTS.md rules section. agentsContent := bootstrap.RenderRulesSection(split.Rules) - writeDraftArtifact(report, repoPath, "AGENTS-RULES.md", agentsContent, useDraft, "greenfield-rules") + writeDraftArtifact(report, repoPath, "AGENTS-RULES.md", agentsContent, useDraft, dryRun, "greenfield-rules") // Write DRAFT-ROADMAP.md from scout data. card, scoutErr := scout.Run(repoPath) @@ -271,9 +271,19 @@ func appendGreenfieldArtifacts(report *bootstrap.BootstrapReport, repoPath strin } roadmap := bootstrap.GenerateRoadmap(card) roadmapContent := bootstrap.RenderRoadmapMarkdown(roadmap) - writeDraftArtifact(report, repoPath, "ROADMAP.md", roadmapContent, useDraft, "greenfield-roadmap") + writeDraftArtifact(report, repoPath, "ROADMAP.md", roadmapContent, useDraft, dryRun, "greenfield-roadmap") // Save bootstrap answers for reproducibility. + if dryRun { + report.Artifacts = append(report.Artifacts, bootstrap.ArtifactResult{ + Type: "greenfield-answers", + Path: ".sdp/bootstrap-answers.json", + Action: "plan", + Status: "dry_run", + Message: "would save preset answers", + }) + return + } // Resolve the actual preset name used (may differ from CLI arg when default is applied). resolvedPreset := presetName if resolvedPreset == "" { @@ -295,7 +305,7 @@ func appendGreenfieldArtifacts(report *bootstrap.BootstrapReport, repoPath strin } // appendBrownfieldArtifacts runs delta analysis: scout → compare with existing → report deltas. -func appendBrownfieldArtifacts(report *bootstrap.BootstrapReport, repoPath string, useDraft bool) { +func appendBrownfieldArtifacts(report *bootstrap.BootstrapReport, repoPath string, useDraft bool, dryRun bool) { card, err := scout.Run(repoPath) if err != nil { report.Artifacts = append(report.Artifacts, bootstrap.ArtifactResult{ @@ -328,7 +338,7 @@ func appendBrownfieldArtifacts(report *bootstrap.BootstrapReport, repoPath strin if useDraft { deltaPath = bootstrap.DraftPath(deltaPath) } - writeDraftArtifact(report, repoPath, deltaPath, string(deltaJSON), false, "brownfield-delta") + writeDraftArtifact(report, repoPath, deltaPath, string(deltaJSON), false, dryRun, "brownfield-delta") for _, d := range result.Deltas { report.Notes = append(report.Notes, @@ -389,7 +399,7 @@ func isHarnessEnabled(h harnesscfg.Harness) bool { } // writeDraftArtifact writes a single DRAFT artifact and appends to the report. -func writeDraftArtifact(report *bootstrap.BootstrapReport, repoPath, basename, content string, useDraft bool, artifactType string) { +func writeDraftArtifact(report *bootstrap.BootstrapReport, repoPath, basename, content string, useDraft bool, dryRun bool, artifactType string) { var filename string if useDraft { filename = bootstrap.DraftPath(basename) @@ -398,6 +408,15 @@ func writeDraftArtifact(report *bootstrap.BootstrapReport, repoPath, basename, c } fullPath := filepath.Join(repoPath, filename) + if dryRun { + report.Artifacts = append(report.Artifacts, bootstrap.ArtifactResult{ + Type: artifactType, Path: filename, + Action: "plan", Status: "dry_run", + Message: fmt.Sprintf("would write %d bytes", len(content)), + }) + return + } + if err := os.MkdirAll(filepath.Dir(fullPath), 0o755); err != nil { report.Artifacts = append(report.Artifacts, bootstrap.ArtifactResult{ Type: artifactType, Path: filename, @@ -419,6 +438,7 @@ func writeDraftArtifact(report *bootstrap.BootstrapReport, repoPath, basename, c Message: fmt.Sprintf("%d bytes", len(content)), }) } + // bootstrapRulesArtifactName derives a rules-specific filename from the manifest // harness ConfigFile. Handles dotfiles like .cursorrules correctly. // When useDraft is true, files get a DRAFT- prefix on the filename only. diff --git a/cmd/sdp/cmd_bootstrap_test.go b/cmd/sdp/cmd_bootstrap_test.go index 735e9898..44eaae4f 100644 --- a/cmd/sdp/cmd_bootstrap_test.go +++ b/cmd/sdp/cmd_bootstrap_test.go @@ -90,6 +90,31 @@ func TestBootstrapDefault_UseDraftTrue(t *testing.T) { } } +func TestBootstrapDryRunBrownfieldDoesNotWriteDraftDelta(t *testing.T) { + binPath := buildTestBinary(t) + repoDir := t.TempDir() + if err := os.WriteFile(filepath.Join(repoDir, "go.mod"), []byte("module example.com/app\n\ngo 1.26\n"), 0o644); err != nil { + t.Fatalf("write go.mod: %v", err) + } + + cmd := exec.Command(binPath, "bootstrap", "--dry-run", "--mode", "brownfield", repoDir) + var out bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &out + if err := cmd.Run(); err != nil { + t.Fatalf("bootstrap dry-run brownfield failed: %v\n%s", err, out.String()) + } + + if !strings.Contains(out.String(), "[plan]") { + t.Fatalf("expected dry-run plan output, got:\n%s", out.String()) + } + if _, err := os.Stat(filepath.Join(repoDir, "DRAFT-bootstrap-delta.json")); err == nil { + t.Fatal("dry-run brownfield wrote DRAFT-bootstrap-delta.json") + } else if !os.IsNotExist(err) { + t.Fatalf("stat DRAFT-bootstrap-delta.json: %v", err) + } +} + // TestBootstrapYesFlag_BypassesDraft verifies that --yes flag produces // clean files without DRAFT prefix. func TestBootstrapYesFlag_BypassesDraft(t *testing.T) { diff --git a/cmd/sdp/cmd_doctor.go b/cmd/sdp/cmd_doctor.go index 781d6a3f..ad10c715 100644 --- a/cmd/sdp/cmd_doctor.go +++ b/cmd/sdp/cmd_doctor.go @@ -12,8 +12,8 @@ import ( func runDoctor(args []string) { if len(args) < 1 { - fmt.Fprintln(os.Stderr, "usage: sdp doctor ") - os.Exit(2) + runDoctorControl() + return } switch args[0] { case "control": diff --git a/cmd/sdp/cmd_init.go b/cmd/sdp/cmd_init.go index a792cf99..5fa75f3a 100644 --- a/cmd/sdp/cmd_init.go +++ b/cmd/sdp/cmd_init.go @@ -151,22 +151,8 @@ func runInit(args []string) int { // embed real bodies instead of placeholder shells. m, warnings, sdpVersion, manifestVersion, loadErr := loadManifestForInit(manifestPath, target) if loadErr != nil { - // Non-fatal: generate with a minimal in-memory manifest so harness dirs - // are still created. - fmt.Fprintf(os.Stderr, "warning: manifest load failed (%v) — generating empty adapters\n", loadErr) - m = &manifest.Manifest{ - Version: "1.0.0", - SDPVersion: "1.0.0", - Harnesses: []manifest.Harness{ - manifest.HarnessClaudeCode, - manifest.HarnessOpenCode, - manifest.HarnessCodex, - manifest.HarnessCursor, - manifest.HarnessPi, - }, - } - sdpVersion = "1.0.0" - manifestVersion = "1.0.0" + fmt.Fprintf(os.Stderr, "error: manifest load failed: %v\n", loadErr) + return 1 } for _, w := range warnings { fmt.Fprintf(os.Stderr, "warning: %s\n", w) diff --git a/cmd/sdp/cmd_init_test.go b/cmd/sdp/cmd_init_test.go index 99e4f56a..e4c4dc4f 100644 --- a/cmd/sdp/cmd_init_test.go +++ b/cmd/sdp/cmd_init_test.go @@ -266,6 +266,23 @@ mcp_servers: [] } } +func TestInit_InvalidExistingManifestFailsFast(t *testing.T) { + target := t.TempDir() + if err := os.WriteFile(filepath.Join(target, "sdp.manifest.yaml"), []byte("version: [\n"), 0o644); err != nil { + t.Fatalf("write manifest: %v", err) + } + + code := runInit([]string{"--harness", "codex", "--target", target}) + if code == 0 { + t.Fatal("runInit returned 0 for invalid existing manifest") + } + if _, err := os.Stat(filepath.Join(target, ".codex")); err == nil { + t.Fatal("invalid manifest should not produce harness adapters") + } else if !os.IsNotExist(err) { + t.Fatalf("stat .codex: %v", err) + } +} + func TestInit_ExistingManifestEmbedsBodies(t *testing.T) { target := t.TempDir() const token = "INIT_BODY_TOKEN_42" diff --git a/cmd/sdp/main.go b/cmd/sdp/main.go index 9b91f297..97cd180b 100644 --- a/cmd/sdp/main.go +++ b/cmd/sdp/main.go @@ -10,6 +10,10 @@ func main() { usage() os.Exit(2) } + if os.Args[1] == "-h" || os.Args[1] == "--help" || os.Args[1] == "help" { + usage() + return + } switch os.Args[1] { case "card": runCard(os.Args[2:]) @@ -104,6 +108,8 @@ func usage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Doctor commands:") fmt.Fprintln(os.Stderr, " sdp doctor control") + fmt.Fprintln(os.Stderr, " sdp doctor adapters [--manifest ] [--out ] [--strict]") + fmt.Fprintln(os.Stderr, " sdp doctor backlog") fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Dispatch commands:") fmt.Fprintln(os.Stderr, " sdp dispatch card") @@ -171,6 +177,12 @@ func usage() { fmt.Fprintln(os.Stderr, "Reset commands:") fmt.Fprintln(os.Stderr, " sdp reset --feature F042 [--dry-run] [--yes] Reset checkpoint for a feature") fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, "Install and adapter commands:") + fmt.Fprintln(os.Stderr, " sdp init [--harness ] [--target ] [--manifest ] [--update]") + fmt.Fprintln(os.Stderr, " sdp manifest validate [--manifest ] [--schema ]") + fmt.Fprintln(os.Stderr, " sdp manifest parity [--write]") + fmt.Fprintln(os.Stderr, " sdp generate-adapters [--manifest ] [--out ] [--check]") + fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Coverage commands:") fmt.Fprintln(os.Stderr, " sdp coverage-scan [--path DIR] [--threshold PCT] [--format text|json] [--skip-test] [--package PATTERN] [--coverprofile FILE]") fmt.Fprintln(os.Stderr) diff --git a/cmd/sdp/main_test.go b/cmd/sdp/main_test.go index 7781418d..3214bb81 100644 --- a/cmd/sdp/main_test.go +++ b/cmd/sdp/main_test.go @@ -37,6 +37,28 @@ func TestMainUsage(t *testing.T) { } } +func TestMainHelpExitsZeroAndListsInstallCommands(t *testing.T) { + binPath := buildTestBinary(t) + cmd := exec.Command(binPath, "--help") + var out bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &out + if err := cmd.Run(); err != nil { + t.Fatalf("expected --help to exit 0, got %v\n%s", err, out.String()) + } + output := out.String() + for _, want := range []string{ + "sdp init", + "sdp manifest validate", + "sdp generate-adapters", + "sdp doctor adapters", + } { + if !strings.Contains(output, want) { + t.Fatalf("expected help to contain %q, got:\n%s", want, output) + } + } +} + func TestCardUsage(t *testing.T) { binPath := buildTestBinary(t) cmd := exec.Command(binPath, "card") @@ -78,13 +100,13 @@ func TestDoctorControlUsage(t *testing.T) { var out bytes.Buffer cmd.Stdout = &out cmd.Stderr = &out - err := cmd.Run() - if err == nil { - t.Fatalf("expected error exit, got nil") - } + _ = cmd.Run() output := out.String() - if !strings.Contains(output, "usage: sdp doctor") { - t.Fatalf("expected doctor usage in output, got: %s", output) + if strings.Contains(output, "usage: sdp doctor") { + t.Fatalf("plain doctor should run doctor control, got usage:\n%s", output) + } + if !strings.Contains(output, "DOCTOR CONTROL") { + t.Fatalf("expected doctor control output, got:\n%s", output) } } diff --git a/cmd/sdp/snapshot_test.go b/cmd/sdp/snapshot_test.go index 784bd7a0..338d6351 100644 --- a/cmd/sdp/snapshot_test.go +++ b/cmd/sdp/snapshot_test.go @@ -94,7 +94,7 @@ func TestSnapshot_CLIUsage(t *testing.T) { {"card-usage", []string{"card"}, false}, {"board-usage", []string{"board"}, false}, {"dispatch-usage", []string{"dispatch"}, false}, - {"doctor-usage", []string{"doctor"}, false}, + {"doctor-usage", []string{"doctor"}, true}, {"result-usage", []string{"result"}, false}, {"orchestrate-usage", []string{"orchestrate"}, false}, {"unknown-command", []string{"foobar"}, false}, diff --git a/docs/MULTI-REPO-WORKFLOW.md b/docs/MULTI-REPO-WORKFLOW.md index 731b8a34..6185538c 100644 --- a/docs/MULTI-REPO-WORKFLOW.md +++ b/docs/MULTI-REPO-WORKFLOW.md @@ -8,7 +8,8 @@ All files live natively in `sdp_lab`. There is no submodule, no separate git che | Path | Repo | Remote | Typical change | |------|------|--------|----------------| -| Root, `internal/`, `cmd/`, `docs/`, `sdp/` | `sdp_lab` | `https://github.com/fall-out-bug/sdp` | All work happens here | +| Root, `internal/`, `cmd/`, `docs/`, protocol artifact dirs | `sdp_lab` | `https://github.com/fall-out-bug/sdp_lab` | All development happens here | +| `sdp/` optional local checkout | `sdp` (distilled distribution repo) | `https://github.com/fall-out-bug/sdp` | Local publish target only; gitignored in `sdp_lab` | | `fall-out-bug/sdp` (separate repo) | `sdp` (distilled distribution repo) | `https://github.com/fall-out-bug/sdp` | Published artifacts only | Historical note: many workstreams and beads IDs still use `sdp_dev-*` as a legacy label for the root repo. The Go module path was migrated from `sdp_dev` to `sdp_lab` in F150-03. That is history, not a third repo. @@ -39,7 +40,7 @@ scripts/sdp-publish.sh --dry-run scripts/sdp-publish.sh --check ``` -The script copies the relevant files from `sdp_lab` into a checkout of `fall-out-bug/sdp`, commits, and pushes. It uses a manifest to determine which paths to publish. +The script copies the relevant files from `sdp_lab` into a checkout of `fall-out-bug/sdp`, commits, and pushes. The publish surface is the `ARTIFACT_FILE_MAP` array in `scripts/sdp-publish.sh`. ## Artifacts Published @@ -63,7 +64,7 @@ The script copies the relevant files from `sdp_lab` into a checkout of `fall-out | `prompts/commands.yml` | `prompts/commands.yml` | | `scripts/install.sh` | `scripts/install.sh` | -The manifest is maintained in `sdp_lab`. Add or remove paths there when the publish surface changes. +The artifact map is maintained in `scripts/sdp-publish.sh`. Add or remove paths there when the publish surface changes, and keep this table in sync. ## Branch Defaults diff --git a/docs/reference/ci-gates-map.md b/docs/reference/ci-gates-map.md index 623bf399..381220db 100644 --- a/docs/reference/ci-gates-map.md +++ b/docs/reference/ci-gates-map.md @@ -43,7 +43,7 @@ required-checks ◄───────────────┴──┘ | Evidence Gate | `evidence-gate` | Yes | Fail-closed | `go run ./cmd/sdp-evidence validate --require-pr-url=false ` | | Scope Gate | `scope-gate` | Yes | Fail-closed | `go run ./cmd/sdp-guard --ws ` | | Protocol Compliance | `protocol-compliance` | Yes | Fail-closed | `go run ./cmd/sdp-guard --check-contract --contract --snapshot ` | -| Consistency Gate | `consistency-gate` | Yes | Fail-closed | `python3 scripts/check_repo_consistency.py --strict-ac --json` | +| Consistency Gate | `consistency-gate` | Yes | Blocking for version/public metadata drift; advisory for repo/protocol/doc/skill drift during cleanup | `scripts/check-version-drift.sh --json && scripts/check-public-metadata.sh --json` | | Coverage Gate | `coverage-gate` | Yes | Blocking baseline delta; maturity-tiered minimums advisory | `go test -tags sqlite_fts5 -coverprofile=cover.out ./... && go tool cover -func=cover.out` | | Policy Gate | `policy-gate` | Yes | Advisory (configurable) | See details (OPA eval) | | Auto-Attestation | `auto-attestation` | Yes | Required | `go run ./internal/evidence/cmd/auto-attest --branch ` | @@ -127,10 +127,10 @@ required-checks ◄───────────────┴──┘ ### consistency-gate - **Owner**: platform - **Triggers**: Every PR -- **Steps**: `python3 scripts/check_repo_consistency.py --strict-ac --json` + `go run ./cmd/sdp-protocol-check --format json` + `go run ./cmd/sdp-doc-sync --mode check --format json` + `go run ./cmd/sdp-protocol-check --lint-skills --format json` +- **Steps**: Advisory findings from `python3 scripts/check_repo_consistency.py --strict-ac --json`, `go run ./cmd/sdp-protocol-check --format json`, `go run ./cmd/sdp-doc-sync --mode check --format json`, and `go run ./cmd/sdp-protocol-check --lint-skills --format json`; blocking drift checks from `scripts/check-version-drift.sh --json` and `scripts/check-public-metadata.sh --json`. - **Never skipped** -- **Failure semantics**: Blocks merge on repo consistency failure. Protocol check and doc-sync are non-blocking advisory. Skill-lint is non-blocking in advisory rollout. -- **Local reproduce**: `python3 scripts/check_repo_consistency.py --strict-ac --json` +- **Failure semantics**: Blocks merge on version drift or public metadata drift. Repository consistency, protocol-check, doc-sync, and skill-lint findings are written as advisory artifacts while historical backlog/doc drift is reconciled. +- **Local reproduce**: `scripts/check-version-drift.sh --json && scripts/check-public-metadata.sh --json`; advisory: `python3 scripts/check_repo_consistency.py --strict-ac --json` - **Output schema**: JSON findings file - **Artifact path**: `.sdp/findings/*.json` (uploaded as CI artifact) diff --git a/internal/adapters/generate.go b/internal/adapters/generate.go index 8a236987..743f92d9 100644 --- a/internal/adapters/generate.go +++ b/internal/adapters/generate.go @@ -166,6 +166,15 @@ func itemHarnesses(declared []manifest.Harness, manifestEnabled map[manifest.Har return out } +func commandOutputPath(c manifest.Command, h manifest.Harness, fallback string) string { + if c.Dispatch != nil { + if path := strings.TrimSpace(c.Dispatch[h]); path != "" { + return path + } + } + return fallback +} + func render(t *template.Template, data any) ([]byte, error) { var buf bytes.Buffer if err := t.Execute(&buf, data); err != nil { @@ -201,7 +210,7 @@ func generateClaudeCode(m *manifest.Manifest, enabled map[manifest.Harness]bool, if err != nil { return err } - out[".claude/commands/"+c.Name+".md"] = data + out[commandOutputPath(c, manifest.HarnessClaudeCode, ".claude/commands/"+c.Name+".md")] = data } // Sort agents for determinism @@ -339,7 +348,7 @@ func generateCursor(m *manifest.Manifest, enabled map[manifest.Harness]bool, rep if err != nil { return err } - out[".cursor/rules/"+c.Name+".mdc"] = data + out[commandOutputPath(c, manifest.HarnessCursor, ".cursor/rules/"+c.Name+".mdc")] = data } return nil } @@ -394,7 +403,7 @@ func generatePi(m *manifest.Manifest, enabled map[manifest.Harness]bool, repoRoo if err != nil { return err } - out[".pi/prompts/"+c.Name+".md"] = data + out[commandOutputPath(c, manifest.HarnessPi, ".pi/prompts/"+c.Name+".md")] = data } return nil diff --git a/internal/adapters/generate_test.go b/internal/adapters/generate_test.go index 91b0d7f3..fbaa0b4b 100644 --- a/internal/adapters/generate_test.go +++ b/internal/adapters/generate_test.go @@ -135,6 +135,52 @@ func TestGenerate_HarnessFilter(t *testing.T) { } } +func TestGenerate_CommandDispatchOverride(t *testing.T) { + m := &manifest.Manifest{ + Version: "1.0.0", + SDPVersion: "1.0.0", + Harnesses: []manifest.Harness{ + manifest.HarnessClaudeCode, + manifest.HarnessCursor, + manifest.HarnessPi, + }, + Commands: []manifest.Command{ + { + Name: "custom", + Path: "prompts/commands/custom.md", + Dispatch: map[manifest.Harness]string{ + manifest.HarnessClaudeCode: ".claude/commands/custom-alias.md", + manifest.HarnessCursor: ".cursor/rules/custom-alias.mdc", + manifest.HarnessPi: ".pi/prompts/custom-alias.md", + }, + }, + }, + } + + out, err := adapters.Generate(m, "") + if err != nil { + t.Fatalf("Generate: %v", err) + } + for _, path := range []string{ + ".claude/commands/custom-alias.md", + ".cursor/rules/custom-alias.mdc", + ".pi/prompts/custom-alias.md", + } { + if _, ok := out[path]; !ok { + t.Fatalf("expected dispatch override output %q, got keys: %v", path, mapKeys(out)) + } + } + for _, path := range []string{ + ".claude/commands/custom.md", + ".cursor/rules/custom.mdc", + ".pi/prompts/custom.md", + } { + if _, ok := out[path]; ok { + t.Fatalf("unexpected default output %q when dispatch override is set", path) + } + } +} + // TestGenerate_PerHarnessHasFiles verifies that a full minimal manifest has at // least one file per harness prefix. func TestGenerate_PerHarnessHasFiles(t *testing.T) { diff --git a/internal/control/repo_dual_test.go b/internal/control/repo_dual_test.go index 51825547..bffeafb2 100644 --- a/internal/control/repo_dual_test.go +++ b/internal/control/repo_dual_test.go @@ -2,6 +2,8 @@ package control import ( "context" + "os" + "path/filepath" "testing" "time" ) @@ -54,6 +56,18 @@ func TestDualWriteRepository_Compare_NilShadow(t *testing.T) { } } +func TestFileCardRepositoryRejectsPathTraversalIDs(t *testing.T) { + tmp := t.TempDir() + repo := NewFileCardRepository(tmp, filepath.Join(tmp, ".sdp", "control"), ProjectRegistry{}) + card := &FeatureCard{ID: "card-1", ProjectID: "../../outside"} + if err := repo.CreateCard(card.ProjectID, card); err == nil { + t.Fatal("expected traversal project id to fail") + } + if _, err := os.Stat(filepath.Join(tmp, "..", "..", "outside")); err == nil { + t.Fatal("repository wrote outside control root") + } +} + func TestStatusMismatch_Struct(t *testing.T) { m := StatusMismatch{ ID: "test-123", diff --git a/internal/control/repo_file.go b/internal/control/repo_file.go index eb54505c..bd2e64e6 100644 --- a/internal/control/repo_file.go +++ b/internal/control/repo_file.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "sort" + "strings" "time" "gopkg.in/yaml.v3" @@ -29,6 +30,12 @@ func (r *FileCardRepository) CreateCard(projectID string, card *FeatureCard) err if card == nil { return fmt.Errorf("nil card") } + if err := validatePathSegment("project id", projectID); err != nil { + return err + } + if err := validatePathSegment("card id", card.ID); err != nil { + return err + } if err := os.MkdirAll(r.cardsDir(projectID), 0o755); err != nil { return fmt.Errorf("create cards dir for %s: %w", projectID, err) } @@ -43,6 +50,12 @@ func (r *FileCardRepository) SaveCard(card *FeatureCard) error { if card == nil { return fmt.Errorf("nil card") } + if err := validatePathSegment("project id", card.ProjectID); err != nil { + return err + } + if err := validatePathSegment("card id", card.ID); err != nil { + return err + } if err := os.MkdirAll(r.cardsDir(card.ProjectID), 0o755); err != nil { return fmt.Errorf("create cards dir for %s: %w", card.ProjectID, err) } @@ -55,6 +68,12 @@ func (r *FileCardRepository) SaveCard(card *FeatureCard) error { } func (r *FileCardRepository) LoadCard(projectID, cardID string) (*FeatureCard, error) { + if err := validatePathSegment("project id", projectID); err != nil { + return nil, err + } + if err := validatePathSegment("card id", cardID); err != nil { + return nil, err + } cards, err := r.LoadCards(projectID) if err != nil { return nil, fmt.Errorf("load cards for %s: %w", projectID, err) @@ -69,6 +88,9 @@ func (r *FileCardRepository) LoadCard(projectID, cardID string) (*FeatureCard, e } func (r *FileCardRepository) LoadCards(projectID string) ([]FeatureCard, error) { + if err := validatePathSegment("project id", projectID); err != nil { + return nil, err + } pattern := filepath.Join(r.cardsDir(projectID), "*.yaml") files, err := filepath.Glob(pattern) if err != nil { @@ -117,3 +139,13 @@ func (r *FileCardRepository) cardsDir(projectID string) string { func (r *FileCardRepository) cardPath(projectID, cardID string) string { return filepath.Join(r.cardsDir(projectID), cardID+".yaml") } + +func validatePathSegment(label, value string) error { + if strings.TrimSpace(value) == "" { + return fmt.Errorf("invalid %s: empty", label) + } + if filepath.IsAbs(value) || value != filepath.Base(value) || value == "." || value == ".." { + return fmt.Errorf("invalid %s %q: must be a single path segment", label, value) + } + return nil +} diff --git a/internal/manifest/load.go b/internal/manifest/load.go index af51fa7d..8c8dc7f3 100644 --- a/internal/manifest/load.go +++ b/internal/manifest/load.go @@ -140,11 +140,16 @@ func formatSchemaError(err error) error { func checkPaths(m *Manifest, repoRoot string, result *LoadResult) error { var missing []string + var invalid []string check := func(field, p string) { if p == "" { return } - full := filepath.Join(repoRoot, p) + full, err := resolveManifestPath(repoRoot, p) + if err != nil { + invalid = append(invalid, fmt.Sprintf("%s: %s (%v)", field, p, err)) + return + } if _, err := os.Stat(full); err != nil { missing = append(missing, fmt.Sprintf("%s: %s", field, p)) } @@ -161,6 +166,9 @@ func checkPaths(m *Manifest, repoRoot string, result *LoadResult) error { for _, h := range m.Hooks { check(fmt.Sprintf("hooks[%s].script", h.Event), h.Script) } + if len(invalid) > 0 { + return fmt.Errorf("manifest references invalid paths:\n - %s", strings.Join(invalid, "\n - ")) + } if len(missing) > 0 { return fmt.Errorf("manifest references missing paths:\n - %s", strings.Join(missing, "\n - ")) } @@ -168,6 +176,32 @@ func checkPaths(m *Manifest, repoRoot string, result *LoadResult) error { return nil } +func resolveManifestPath(repoRoot, p string) (string, error) { + if filepath.IsAbs(p) { + return "", fmt.Errorf("absolute paths are not allowed") + } + clean := filepath.Clean(p) + if clean == "." || strings.HasPrefix(clean, ".."+string(filepath.Separator)) || clean == ".." { + return "", fmt.Errorf("path must stay inside repo") + } + rootAbs, err := filepath.Abs(repoRoot) + if err != nil { + return "", err + } + fullAbs, err := filepath.Abs(filepath.Join(rootAbs, clean)) + if err != nil { + return "", err + } + rel, err := filepath.Rel(rootAbs, fullAbs) + if err != nil { + return "", err + } + if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) { + return "", fmt.Errorf("path must stay inside repo") + } + return fullAbs, nil +} + func checkUniqueness(m *Manifest) error { seen := func(label string, names []string) error { dup := map[string]int{} diff --git a/internal/manifest/load_test.go b/internal/manifest/load_test.go index 280dcb3a..73d2539b 100644 --- a/internal/manifest/load_test.go +++ b/internal/manifest/load_test.go @@ -162,6 +162,29 @@ skills: } } +func TestParse_RejectsPathsOutsideRepo(t *testing.T) { + tmp := t.TempDir() + outside := t.TempDir() + mustWrite(t, filepath.Join(outside, "secret.md"), "secret") + src := []byte(` +version: "1.0.0" +sdp_version: "0.1.0" +skills: + - name: "leak" + path: "../outside/secret.md" +commands: + - name: "abs" + path: "` + filepath.ToSlash(filepath.Join(outside, "secret.md")) + `" +`) + _, err := manifest.Parse(src, tmp) + if err == nil { + t.Fatal("expected outside-repo path error") + } + if !strings.Contains(err.Error(), "invalid paths") { + t.Errorf("error should mention invalid paths: %v", err) + } +} + func TestParse_AcceptsExistingPaths(t *testing.T) { tmp := t.TempDir() mustWrite(t, filepath.Join(tmp, "skills", "build.md"), "# build") diff --git a/internal/manifest/schema.json b/internal/manifest/schema.json index 313e6385..0a4d42b3 100644 --- a/internal/manifest/schema.json +++ b/internal/manifest/schema.json @@ -2,7 +2,7 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://sdp.dev/schemas/sdp-manifest.schema.json", "title": "SDP Manifest", - "description": "Single source of truth for skills, commands, agents, hooks, and MCP servers across all SDP-supported harnesses. Drives the adapter generator (F141-02) and doctor drift gate (F141-04).", + "description": "Single source of truth for skills, commands, agents, and inventory metadata across all SDP-supported harnesses. The adapter generator renders skills, commands, agents, and supported harness resource surfaces. Hooks and MCP servers are validated inventory only until renderer support lands.", "type": "object", "required": ["version", "sdp_version"], "additionalProperties": false, @@ -40,10 +40,12 @@ "items": { "$ref": "#/$defs/agent" } }, "hooks": { + "description": "Validated inventory only. Hooks are not installed by generate-adapters until a hook renderer is added.", "type": "array", "items": { "$ref": "#/$defs/hook" } }, "mcp_servers": { + "description": "Validated inventory only. MCP server entries are not installed by generate-adapters until an MCP renderer is added.", "type": "array", "items": { "$ref": "#/$defs/mcp_server" } } @@ -110,7 +112,7 @@ "dispatch": { "type": "object", "additionalProperties": false, - "description": "Per-harness dispatch override. If absent, generator uses default mapping (e.g. .claude/commands/.md).", + "description": "Per-harness command output path override. If absent, generator uses default mapping (e.g. .claude/commands/.md).", "properties": { "claude-code": { "type": "string" }, "opencode": { "type": "string" }, diff --git a/scripts/hooks/pre-push.sh b/scripts/hooks/pre-push.sh index 018efb9a..6b2d4c6b 100755 --- a/scripts/hooks/pre-push.sh +++ b/scripts/hooks/pre-push.sh @@ -18,9 +18,19 @@ CHANGED="" while read local_ref local_sha remote_ref remote_sha; do [ -z "$local_sha" ] && continue if [ "$remote_sha" = "0000000000000000000000000000000000000000" ]; then - CHANGED="$CHANGED $(git diff --name-only 4b825dc642cb6eb9a060e54bf8d69288fbee4904 $local_sha 2>/dev/null || true)" + BASE="" + if git rev-parse --verify origin/main >/dev/null 2>&1; then + BASE=$(git merge-base "$local_sha" origin/main 2>/dev/null || true) + fi + if [ -z "$BASE" ] && git rev-parse --verify origin/master >/dev/null 2>&1; then + BASE=$(git merge-base "$local_sha" origin/master 2>/dev/null || true) + fi + if [ -z "$BASE" ]; then + BASE=4b825dc642cb6eb9a060e54bf8d69288fbee4904 + fi + CHANGED="$CHANGED $(git diff --name-only "$BASE" "$local_sha" 2>/dev/null || true)" else - CHANGED="$CHANGED $(git diff --name-only $remote_sha $local_sha 2>/dev/null || true)" + CHANGED="$CHANGED $(git diff --name-only "$remote_sha" "$local_sha" 2>/dev/null || true)" fi done diff --git a/scripts/install.sh b/scripts/install.sh index aa4b26b0..038b32d5 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -9,6 +9,7 @@ # SDP_HARNESS Harness selection: auto|all|claude-code,opencode,... (default: auto) # SDP_TARGET Target directory (default: .) # SDP_BIN_DIR Directory for the repo-local sdp binary (default: $SDP_TARGET/.sdp/bin) +# SDP_TRUST_PATH_SDP Reuse an existing PATH sdp binary only when set to 1 (default: 0) set -euo pipefail REPO="${SDP_REPO:-fall-out-bug/sdp_lab}" @@ -23,6 +24,7 @@ if [[ -z "$TARGET_ABS" ]]; then fi BIN_DIR="${SDP_BIN_DIR:-$TARGET_ABS/.sdp/bin}" LOCAL_SDP="$BIN_DIR/sdp" +TRUST_PATH_SDP="${SDP_TRUST_PATH_SDP:-0}" echo "→ SDP installer: harness=$HARNESS target=$TARGET_ABS" @@ -40,6 +42,28 @@ supports_current_init() { "$1" init --help 2>&1 | grep -q -- "--harness" } +go_version_at_least() { + local required_major="$1" + local required_minor="$2" + local version raw major minor + + raw="$(go env GOVERSION 2>/dev/null || go version 2>/dev/null || true)" + version="$(printf '%s\n' "$raw" | sed -E 's/^.*go([0-9]+)\.([0-9]+).*$/\1.\2/')" + if [[ ! "$version" =~ ^[0-9]+[.][0-9]+$ ]]; then + return 1 + fi + + major="${version%%.*}" + minor="${version#*.}" + if (( major > required_major )); then + return 0 + fi + if (( major == required_major && minor >= required_minor )); then + return 0 + fi + return 1 +} + SDP_BIN="" if [[ -n "$SOURCE_DIR" ]]; then @@ -62,8 +86,8 @@ else git clone --depth=1 --branch "$BRANCH" "https://github.com/$REPO.git" "$SOURCE_ROOT" 2>&1 fi -# Strategy 1: if a compatible `sdp` binary is already on PATH, use it directly. -if command -v sdp >/dev/null 2>&1; then +# Strategy 1: optionally reuse a compatible `sdp` binary already on PATH. +if [[ "$TRUST_PATH_SDP" == "1" ]] && command -v sdp >/dev/null 2>&1; then FOUND_SDP="$(command -v sdp)" if supports_current_init "$FOUND_SDP"; then echo "→ found compatible sdp binary on PATH: $FOUND_SDP" @@ -72,16 +96,24 @@ if command -v sdp >/dev/null 2>&1; then echo "warning: found incompatible sdp binary on PATH: $FOUND_SDP" >&2 echo "warning: it does not support 'sdp init --harness'; building $SOURCE_LABEL instead" >&2 fi +elif command -v sdp >/dev/null 2>&1; then + echo "→ ignoring sdp binary on PATH; set SDP_TRUST_PATH_SDP=1 to reuse it" fi # Strategy 2: clone-and-build (offline-friendly, no GitHub Releases needed in v1). -# Requires: go (1.21+) +# Requires: Go 1.26+ (matches go.mod and CI). if [[ -z "$SDP_BIN" ]]; then if ! command -v go >/dev/null 2>&1; then echo "error: required tool 'go' not found on PATH" >&2 echo " Please install go and re-run this script." >&2 exit 1 fi + if ! go_version_at_least 1 26; then + echo "error: Go 1.26+ is required to build SDP from source" >&2 + echo " Found: $(go version 2>/dev/null || go env GOVERSION 2>/dev/null || echo unknown)" >&2 + echo " Install Go 1.26+ or put a compatible 'sdp' binary on PATH." >&2 + exit 1 + fi echo "→ building sdp binary" mkdir -p "$BIN_DIR" diff --git a/scripts/install_kubeopencode_remote.sh b/scripts/install_kubeopencode_remote.sh index 6cb087df..3dc8b92c 100755 --- a/scripts/install_kubeopencode_remote.sh +++ b/scripts/install_kubeopencode_remote.sh @@ -36,14 +36,50 @@ if [[ -z "${HOST}" ]]; then echo "Usage: $0 --host [--port ] [--namespace ] [--release ]" exit 2 fi +if [[ ! "$PORT" =~ ^[0-9]+$ ]]; then + echo "Invalid --port: $PORT" >&2 + exit 2 +fi +if [[ ! "$NAMESPACE" =~ ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ ]]; then + echo "Invalid Kubernetes namespace: $NAMESPACE" >&2 + exit 2 +fi +if [[ ! "$RELEASE" =~ ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ ]]; then + echo "Invalid Helm release name: $RELEASE" >&2 + exit 2 +fi echo "[kubeopencode] install/upgrade ${RELEASE} in ${NAMESPACE}" -ssh -p "${PORT}" "${HOST}" "kubectl get ns ${NAMESPACE} >/dev/null 2>&1 || kubectl create ns ${NAMESPACE}" -ssh -p "${PORT}" "${HOST}" "helm upgrade --install ${RELEASE} oci://quay.io/kubeopencode/helm-charts/kubeopencode --namespace ${NAMESPACE} --set server.enabled=false" -DEPLOY_NAME="$(ssh -p "${PORT}" "${HOST}" "kubectl -n ${NAMESPACE} get deploy -l app.kubernetes.io/instance=${RELEASE} -o jsonpath='{.items[0].metadata.name}'")" +ssh -p "${PORT}" "${HOST}" bash -s -- "${NAMESPACE}" <<'REMOTE' +set -euo pipefail +namespace="$1" +kubectl get ns "$namespace" >/dev/null 2>&1 || kubectl create ns "$namespace" +REMOTE +ssh -p "${PORT}" "${HOST}" bash -s -- "${RELEASE}" "${NAMESPACE}" <<'REMOTE' +set -euo pipefail +release="$1" +namespace="$2" +helm upgrade --install "$release" oci://quay.io/kubeopencode/helm-charts/kubeopencode --namespace "$namespace" --set server.enabled=false +REMOTE +DEPLOY_NAME="$(ssh -p "${PORT}" "${HOST}" bash -s -- "${NAMESPACE}" "${RELEASE}" <<'REMOTE' +set -euo pipefail +namespace="$1" +release="$2" +kubectl -n "$namespace" get deploy -l "app.kubernetes.io/instance=$release" -o jsonpath='{.items[0].metadata.name}' +REMOTE +)" if [[ -z "${DEPLOY_NAME}" ]]; then echo "No deployment found for release ${RELEASE} in ${NAMESPACE}" exit 1 fi -ssh -p "${PORT}" "${HOST}" "kubectl -n ${NAMESPACE} rollout status deploy/${DEPLOY_NAME} --timeout=300s" -ssh -p "${PORT}" "${HOST}" "kubectl -n ${NAMESPACE} get deploy,pods" +ssh -p "${PORT}" "${HOST}" bash -s -- "${NAMESPACE}" "${DEPLOY_NAME}" <<'REMOTE' +set -euo pipefail +namespace="$1" +deploy="$2" +kubectl -n "$namespace" rollout status "deploy/$deploy" --timeout=300s +REMOTE +ssh -p "${PORT}" "${HOST}" bash -s -- "${NAMESPACE}" <<'REMOTE' +set -euo pipefail +namespace="$1" +kubectl -n "$namespace" get deploy,pods +REMOTE diff --git a/scripts/run_go_quality_gates.sh b/scripts/run_go_quality_gates.sh index 313fd1fe..58d866bd 100755 --- a/scripts/run_go_quality_gates.sh +++ b/scripts/run_go_quality_gates.sh @@ -5,6 +5,7 @@ ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" GO="${ROOT}/scripts/go_with_project_toolchain.sh" MODE="${SDP_GO_QUALITY_MODE:-container}" DOCKER_IMAGE="${SDP_GO_QUALITY_DOCKER_IMAGE:-golang:1.26-bookworm}" +GOLANGCI_LINT_VERSION="${SDP_GOLANGCI_LINT_VERSION:-v1.62.0}" cd "$ROOT" @@ -36,6 +37,10 @@ run_host_quality_gates() { echo "==> go vet -tags \"sqlite_fts5\" ./..." "$GO" vet -tags "sqlite_fts5" ./... + + echo "==> golangci-lint run ./..." + "$GO" install "github.com/golangci/golangci-lint/cmd/golangci-lint@${GOLANGCI_LINT_VERSION}" + "$("$GO" env GOPATH)/bin/golangci-lint" run ./... } run_container_quality_gates() { @@ -59,7 +64,7 @@ run_container_quality_gates() { -v "$ROOT:/workspace" \ -w /workspace \ "$DOCKER_IMAGE" \ - sh -c 'set -eu; go version; go build -tags "sqlite_fts5" ./...; go test -tags "sqlite_fts5" ./... -count=1; go vet -tags "sqlite_fts5" ./...' + sh -c "set -eu; go version; go build -tags \"sqlite_fts5\" ./...; go test -tags \"sqlite_fts5\" ./... -count=1; go vet -tags \"sqlite_fts5\" ./...; go install github.com/golangci/golangci-lint/cmd/golangci-lint@${GOLANGCI_LINT_VERSION}; /workspace/.cache/go/bin/golangci-lint run ./..." } case "$MODE" in diff --git a/scripts/run_smoke_tests.sh b/scripts/run_smoke_tests.sh index dea5225f..c0140377 100755 --- a/scripts/run_smoke_tests.sh +++ b/scripts/run_smoke_tests.sh @@ -17,53 +17,59 @@ trap 'rm -f "$REPORT_FILE"' EXIT START_TS=$(date -u +%Y-%m-%dT%H:%M:%SZ) -# Run smoke tests, capturing full output -if go test -tags=smoke ./test/smoke/... -v -json 2>&1 \ - | tee /tmp/sdp-smoke-raw.json \ - | go run golang.org/x/tools/cmd/godoc@latest 2>/dev/null \ - || true; then : ; fi - -# Parse go test -json output into a structured report -go test -tags=smoke ./test/smoke/... -v 2>&1 | tee /tmp/sdp-smoke-stdout.txt +# Run smoke tests once and parse the JSON event stream. +set +e +go test -tags=smoke ./test/smoke/... -v -json 2>&1 | tee /tmp/sdp-smoke-raw.json SMOKE_EXIT=${PIPESTATUS[0]:-$?} +set -e END_TS=$(date -u +%Y-%m-%dT%H:%M:%SZ) -# Extract pass/fail counts -PASS_COUNT=$(grep -c "^--- PASS:" /tmp/sdp-smoke-stdout.txt 2>/dev/null || echo 0) -FAIL_COUNT=$(grep -c "^--- FAIL:" /tmp/sdp-smoke-stdout.txt 2>/dev/null || echo 0) -SKIP_COUNT=$(grep -c "^--- SKIP:" /tmp/sdp-smoke-stdout.txt 2>/dev/null || echo 0) +START_TS="$START_TS" END_TS="$END_TS" python3 - <<'PY' > "$REPORT_FILE" +import json +import os -# Build per-test entries -TESTS_JSON="" -while IFS= read -r line; do - if [[ "$line" =~ ^---\ (PASS|FAIL|SKIP):\ (.+)\ \((.+)\)$ ]]; then - status="${BASH_REMATCH[1]}" - name="${BASH_REMATCH[2]}" - duration="${BASH_REMATCH[3]}" - entry="{\"name\":\"$name\",\"status\":\"$status\",\"duration\":\"$duration\"}" - TESTS_JSON="${TESTS_JSON:+$TESTS_JSON,}$entry" - fi -done < /tmp/sdp-smoke-stdout.txt +tests = [] +counts = {"pass": 0, "fail": 0, "skip": 0} +with open("/tmp/sdp-smoke-raw.json", "r", encoding="utf-8") as f: + for line in f: + try: + event = json.loads(line) + except json.JSONDecodeError: + continue + action = event.get("Action") + name = event.get("Test") + if action not in {"pass", "fail", "skip"} or not name: + continue + counts[action] += 1 + tests.append({ + "name": name, + "status": action.upper(), + "duration": event.get("Elapsed", 0), + "package": event.get("Package", ""), + }) -STATUS="pass" -if [[ "$FAIL_COUNT" -gt 0 ]]; then STATUS="fail"; fi +status = "fail" if counts["fail"] else "pass" +json.dump({ + "runner": "sdp-smoke", + "started_at": os.environ["START_TS"], + "finished_at": os.environ["END_TS"], + "status": status, + "summary": { + "total": sum(counts.values()), + "pass": counts["pass"], + "fail": counts["fail"], + "skip": counts["skip"], + }, + "tests": tests, +}, fp=os.sys.stdout, indent=2) +print() +PY -cat > "$REPORT_FILE" < Date: Wed, 29 Apr 2026 16:49:45 +0300 Subject: [PATCH 03/11] fix: bound pi review model calls --- .beads/issues.jsonl | 15 ++--- cmd/sdp-pi-review/main.go | 14 +++-- docs/reference/pi-review-spec.md | 13 +++-- internal/pireview/pireview.go | 26 +++++++-- internal/pireview/runner.go | 33 ++++++++--- internal/pireview/runner_test.go | 94 ++++++++++++++++++++++++++++++-- 6 files changed, 158 insertions(+), 37 deletions(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 1354a373..3f9e8d4b 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -35,6 +35,7 @@ {"_type":"issue","id":"sdplab-22","title":"F100-01: Reference Integrity CI Gate","status":"closed","priority":0,"issue_type":"task","assignee":"Andrei","created_at":"2026-04-05T15:00:00Z","created_by":"UX Council","updated_at":"2026-04-26T13:30:49Z","closed_at":"2026-04-26T13:30:49Z","close_reason":"F100 Reference Integrity complete: CI gate + one-time cleanup. 56 tests passing.","labels":["F100","P0","harness:claude","stream-C","ux"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-23","title":"F100-02: One-Time Reference Cleanup","status":"closed","priority":0,"issue_type":"task","assignee":"Andrei","created_at":"2026-04-05T15:00:00Z","created_by":"UX Council","updated_at":"2026-04-26T13:30:50Z","closed_at":"2026-04-26T13:30:50Z","close_reason":"F100 Reference Integrity complete: CI gate + one-time cleanup. 56 tests passing.","labels":["F100","P0","harness:any","stream-C","ux"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-24","title":"F101-01: Write Plan Emission and Confirmation","status":"closed","priority":0,"issue_type":"feature","assignee":"Andrei","created_at":"2026-04-05T15:00:00Z","created_by":"UX Council","updated_at":"2026-04-19T07:33:30Z","closed_at":"2026-04-19T07:33:30Z","close_reason":"F101-01 merged to main via PR #95 after sdp PR #130 landed on sdp/main. Write Plan prompt surface is now shipped for the nine stateful skills.","labels":["F101","P0","harness:any","stream-B","ux"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-u1lw","title":"Review gate: pi-review model calls can hang indefinitely","description":"source=runtime; discovered while running PR #144 review gate on 2026-04-29. cmd/sdp-pi-review invoked pi run without a per-model timeout, so a stuck external model held the delivery loop for \u003e6 minutes with no output. Add bounded model reviewer context/CLI timeout and regression coverage.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T13:46:47Z","created_by":"Andrei","updated_at":"2026-04-29T13:46:56Z","started_at":"2026-04-29T13:46:56Z","comments":[{"id":"019dd989-2a3a-7c5f-86ca-55fb82d17efa","issue_id":"sdplab-u1lw","author":"Andrei","text":"Follow-up root cause while re-running gate: F161 implementation used obsolete 'pi run' syntax. Pi 0.70.6 supports non-interactive review via 'pi --provider ... --model ... --no-tools --no-context-files --no-session -p \u003cprompt\u003e'. Fix includes correct provider/model defaults for zai/glm-5.1, kimi-coding/k2p6, minimax/MiniMax-M2.7 plus timeout coverage.","created_at":"2026-04-29T13:58:58Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1} {"_type":"issue","id":"sdplab-5z1p","title":"Pi harness: skills and slash commands not discoverable","description":"Pi 0.70.6 loads Agent Skills from .pi/skills or .agents/skills/\u003cname\u003e/SKILL.md and prompt templates from .pi/prompts. Current SDP exposes commands only under prompts/commands and flat skill files are not enough for Pi auto-discovery. Fix Pi resource packaging and add smoke coverage.","acceptance_criteria":"- [ ] Pi resource smoke reports all manifest skills without skill diagnostics.\\n- [ ] Pi resource smoke reports all manifest commands as prompt templates.\\n- [ ] Generated adapters include a Pi-native skill and prompt-template surface.\\n- [ ] Harness docs name Pi CLI/version, entrypoints, limitations, and smoke test.","status":"closed","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:51:48Z","created_by":"Andrei","updated_at":"2026-04-29T09:57:20Z","started_at":"2026-04-29T09:51:51Z","closed_at":"2026-04-29T09:57:20Z","close_reason":"duplicate of sdplab-1ily","labels":["F162","commands","harness","pi","sdp","skills"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-vd3r","title":"Review: init rewrites user manifest despite update semantics","description":"source=review; aspect=code-quality,ux; blocking=true; severity=major. cmd/sdp/cmd_init.go persists filtered harnesses by yaml.Marshal into target sdp.manifest.yaml, dropping comments/formatting and narrowing future generation scope while --update help says user-modified manifest is kept. Resolve selective-harness doctor semantics without silently rewriting user manifests, or require explicit manifest-write flag.","status":"closed","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:50:10Z","created_by":"Andrei","updated_at":"2026-04-29T11:50:04Z","started_at":"2026-04-29T11:14:20Z","closed_at":"2026-04-29T11:50:04Z","close_reason":"Merged in PR #145 (cb5470e): selective init preserves existing manifest","comments":[{"id":"019dd8f2-739b-7007-a15a-06a57ec62688","issue_id":"sdplab-vd3r","author":"Andrei","text":"Fixed in PR #145 commit pending push: sdp init no longer rewrites target sdp.manifest.yaml for selective --harness installs; .sdp/generated remains full manifest-derived cache. Added regression test TestInit_ExistingManifestPreservesUserManifestBytes and quality gates passed. Keep open until PR #145 merges.","created_at":"2026-04-29T11:14:21Z"},{"id":"019dd8fb-1c19-77b4-b021-5e199f2fe70e","issue_id":"sdplab-vd3r","author":"Andrei","text":"Pushed fix in commit b307ff75 on PR #145; CI required checks green. Issue remains in_progress until PR merge per repo policy.","created_at":"2026-04-29T11:23:49Z"}],"dependency_count":0,"dependent_count":0,"comment_count":2} {"_type":"issue","id":"sdplab-6uy6","title":"Review: installer underdeclares required Go version","description":"source=review; aspect=dx,ux; blocking=true; severity=major. scripts/install.sh says Go 1.21+ is enough, but go.mod and CI require Go 1.26 and installer calls go build directly. Preflight Go \u003e=1.26 or use project toolchain wrapper.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:48:51Z","created_by":"Andrei","updated_at":"2026-04-29T13:35:54Z","started_at":"2026-04-29T13:35:54Z","dependency_count":0,"dependent_count":0,"comment_count":0} @@ -554,16 +555,16 @@ {"_type":"issue","id":"sdplab-houh","title":"F036: Orchestrate + in-toto (historical placeholder)","description":"absorbed by F064–F067 auto-attestation","status":"closed","priority":4,"issue_type":"feature","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-18T11:27:58Z","created_by":"Andrei","updated_at":"2026-04-18T11:27:58Z","closed_at":"2026-04-18T11:27:58Z","close_reason":"absorbed by F064–F067 auto-attestation","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-crg","title":"F034: Graduated Enforcement (historical placeholder)","description":"historical placeholder; no active scope","status":"closed","priority":4,"issue_type":"feature","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-18T11:27:55Z","created_by":"Andrei","updated_at":"2026-04-18T11:27:56Z","closed_at":"2026-04-18T11:27:56Z","close_reason":"historical placeholder; no active scope","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-h5y","title":"F032: PR Evidence Summary (historical placeholder)","description":"historical placeholder; no active scope (2026-04-18 triage)","status":"closed","priority":4,"issue_type":"feature","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-18T11:27:34Z","created_by":"Andrei","updated_at":"2026-04-18T11:27:54Z","closed_at":"2026-04-18T11:27:54Z","close_reason":"historical placeholder; no active scope (2026-04-18 triage)","dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"memory","key":"subagent-implementer-broken-2026-04-26","value":"BROKEN: subagent_type=implementer fabricates tool_use. Confirmed via 3-test diagnostic 2026-04-26. T1 (implementer+sonnet): tool_uses=0, file not created, fabricated plausible output. T2 (general-purpose+haiku): tool_uses=1, file created. T3 (Explore+haiku): honest refusal (read-only). Pattern: implementer outputs old-format XML \u003cfunction_calls\u003e\u003cinvoke name='...'\u003e as response text instead of structured tool_use blocks. Affects ANY model. Workaround: use subagent_type=general-purpose for code implementation tasks. Affected sessions: F145 WS01/WS07/WS08 dispatch all fabricated, had to redo manually."} {"_type":"memory","key":"f136-peer-memory-horizons-2026-04-20-epic","value":"f136-peer-memory-horizons-2026-04-20 | Epic F136 (sdplab-t1ty) created 2026-04-20: Peer Memory Foundation H1. Children: F136-01..06 (sdplab-r9py/lq4h/olvg/h25q/l0mm/h673). Parked: F137 H2 (sdplab-75u9, revisit 2026-07-06), F138 H3 (sdplab-7c45, revisit 2026-10-05). Design: docs/plans/2026-04-20-f136-peer-memory-h1-design.md. Market gap: peer-collaboration production framework where attribution is first-class (ChatCollab/HULA academic, no commercial product as of April 2026). H1 success = binary: did sdp memory query surface real lost context in 30-day dogfood. Label: peer-memory."} -{"_type":"memory","key":"f142-closed-2026-04-26","value":"F142 epic (sdplab-85ji) closed 2026-04-26: Workstream coverage gap closed. 6 children closed (gkl5 audit, rqsw baits, s6g7 orphans, f2h0 picker, 3obq doctor, fb1o INDEX). Plus sdplab-8nkm picker incident closed. Branch feature/F142-workstream-coverage-gap, 5 commits not yet merged to main. Delivered: scripts/deliver-pick.sh v2 (skip leafless+design-pending), internal/backlog/audit.go + cmd/sdp/cmd_doctor_backlog.go ('sdp doctor backlog'), 6 ws scaffolds (00-082-01, 00-083-01, 00-084-01, 00-085-01, 00-101-02, 00-133-01), INDEX.md updated, CI gate. 0 findings on doctor backlog. 189 Go tests pass."} +{"_type":"memory","key":"f142-epic-2026-04-26","value":"F142 epic (sdplab-85ji) created 2026-04-26: Workstream coverage gap — picker + ws/INDEX backfill + doctor gate. Root cause: features created via /design bypass /feature autogen → 0 ws files → picker selects leafless feature → /build fails. Affected: F141 (0 ws), F135 (0 ws), F100/F76/F80 (partial). First child: sdplab-8nkm (picker fail incident on sdplab-nj4 F135-05). Future children: audit script, per-feature backfill, INDEX update, picker hardening, sdp doctor backlog gate."} {"_type":"memory","key":"f144-epic-created-2026-04-26-sdplab-sjdp","value":"F144 epic created 2026-04-26 (sdplab-sjdp): Inference Confidence \u0026 Quality Control. 8 children attached: 01 sdplab-wzit core lib, 02 sdplab-339w self-check, 03 sdplab-w7j1 n-sample, 04 sdplab-tfmg constraint+composer, 05 sdplab-kk1v ws-verdict adapter (P1), 06 sdplab-cvu3 architect adapter, 07 sdplab-s5x8 dispatch lite, 08 sdplab-kd38 replay+metrics. Critical path: 01→04→05→08. Plus 2 hygiene issues: sdplab-vuw4 (drift docs/design vs docs/plans), sdplab-bv3r (AGENTS.md design-first phrasing). Design: docs/plans/2026-04-26-f144-inference-confidence-design.md. Branch: feature/F144-inference-confidence (not yet created, untracked design doc on worktree-bulk-deliver branch)."} -{"_type":"memory","key":"f144-merged-2026-04-26","value":"F144 PR #131 MERGED to main at 2026-04-26T17:43Z, merge commit ed393e3. internal/inference/confidence/ live on main: Status enum, Result[T], Policy/Strategy/Checker, 3 strategies (constraint/selfcheck/nsample), 3 adapters (wsverdict/architect/dispatch). 111 tests, ≥85% coverage. Branch feature/F144-inference-confidence still exists locally. Replaces stale 'sdplab-8nkm-untracked-design-doc' status."} {"_type":"memory","key":"f145-epic-created-2026-04-26","value":"F145 epic (sdplab-ldmq) created 2026-04-26: Multi-Provider Dispatch Matrix \u0026 Confidence-Driven Cascade. 14 children: WS01 sdplab-0261 (providers/ scaffold), WS02 sdplab-y93k (OpenAI), WS03 sdplab-w4ru (Anthropic), WS04 sdplab-bg69 (Cursor ~30 models), WS05 sdplab-60ia (Kimi), WS06 sdplab-5lve (Ollama replaces LocalConfig), WS07 sdplab-s686 (cursor/opencode --model plumbing P0 bug), WS08 sdplab-noiq (tier_class label), WS09 sdplab-3lva (profiles_default.json seed), WS10 sdplab-5ii8 (cascade pkg + Invoker), WS11 sdplab-fco9 (F144 confidence injection), WS12 sdplab-uamt (cascade-replay corpus), WS13 sdplab-135g (LimitsCache), WS14 sdplab-2p8g (smoke + Day-8 demo). Design: docs/plans/2026-04-26-f145-multi-provider-dispatch-cascade-design.md. Branch (planned): feature/F145-multi-provider-cascade. Discovery decisions: providers/ sub-pkg, cascade pkg, tier-label hybrid, composer gate default, UNSURE+FAIL+heuristic short-circuit, MaxDepth/Budget configurable, hybrid LimitsCache, F144 replay extension, harness-CLI keys (no SDP auth)."} +{"_type":"memory","key":"subagent-format-spec-2026-04-26","value":"Claude Code subagent file format (.claude/agents/*.md): YAML frontmatter MUST use 'tools: Read, Bash, Glob, Grep' COMMA-STRING format (PascalCase). NOT 'tools: { Read: true }' object form, NOT 'tools: [Read, Bash]' array. Source: docs.claude.com/docs/en/subagents.md lines 222-301. Wrong format → empty tool registry → agent fabricates XML \u003cfunction_calls\u003e in response.text (tool_uses=0). Verified via /tmp/probe diagnostic. Built-in agents (general-purpose, Explore, claude-code-guide) work because their registry is hardcoded, file-based ones go through the broken parsing path. PR #132 fixes 13 SDP custom agents. Note: Claude Code caches agent definitions at session start — fixes require restart to take effect, not just file save."} {"_type":"memory","key":"f129-epic-created-2026-04-16-sdplab-8k5","value":"F129 epic created 2026-04-16 (sdplab-8k5 + 10 children sdplab-8k5.1..10 + 3 cross-lane: F106-07 sdplab-lqb, F124-05 sdplab-nai, F125-05 sdplab-as0). Addresses 5 audit findings: orchestration autonomy, subagent default, superpowers skill gap, doc drift, ad-hoc regression. Design: docs/plans/2026-04-16-f129-autonomy-regression-design.md. Cross-lane additions reuse existing F106/F124/F125 tracks instead of duplicating."} +{"_type":"memory","key":"f144-merged-2026-04-26","value":"F144 PR #131 MERGED to main at 2026-04-26T17:43Z, merge commit ed393e3. internal/inference/confidence/ live on main: Status enum, Result[T], Policy/Strategy/Checker, 3 strategies (constraint/selfcheck/nsample), 3 adapters (wsverdict/architect/dispatch). 111 tests, ≥85% coverage. Branch feature/F144-inference-confidence still exists locally. Replaces stale 'sdplab-8nkm-untracked-design-doc' status."} +{"_type":"memory","key":"f145-progress-2026-04-26","value":"F145 epic in progress 2026-04-26. Branch: feature/F145-multi-provider-cascade (origin in sync). 3/14 WS committed (not yet closed in beads, awaiting merge): WS07 sdplab-s686 (cursor/opencode --model plumbing P0), WS01 sdplab-0261 (providers/ scaffold + tests), WS08 sdplab-noiq (tier_class + SelectTiers). Commits: 67f085b1 / 9ac769b4 / 30782519 on top of design 752140a3. After WS07/01/08 close, 6 WS unblock in parallel: WS02-06 (5 Provider impls) + WS13 LimitsCache. Then chain: WS09 seed → WS10 cascade pkg → WS11 confidence → WS12 replay → WS14 smoke+demo. 174 tests pass in internal/dispatch/. Next session: after PR #132 merge + restart, run @delivery-loop to dispatch remaining WS via fixed implementer subagents. F145 PR will open when epic is ready (or sensible mid-milestone)."} {"_type":"memory","key":"f141-epic-2026-04-25","value":"F141 epic (sdplab-o4sp) created 2026-04-25: Multi-harness install bootstrap \u0026 adapter parity. Children: F141-01 sdplab-fojs (manifest), -02 sdplab-6ea5 (generator), -03 sdplab-cl4o (bootstrap), -04 sdplab-i9q3 (doctor gate), -05 sdplab-r4y5 (parity matrix), -06 sdplab-cnpm (migration), -07 sdplab-nqjt (README). Closes gap left by F127/F128: downstream repos install SDP piecemeal. Approach: single sdp.manifest.yaml -\u003e adapter generator -\u003e curl|bash bootstrap -\u003e sdp doctor drift gate. Design: docs/plans/2026-04-25-f141-multi-harness-install-bootstrap-design.md. Branch: feature/F141-multi-harness-install-bootstrap."} -{"_type":"memory","key":"f142-epic-2026-04-26","value":"F142 epic (sdplab-85ji) created 2026-04-26: Workstream coverage gap — picker + ws/INDEX backfill + doctor gate. Root cause: features created via /design bypass /feature autogen → 0 ws files → picker selects leafless feature → /build fails. Affected: F141 (0 ws), F135 (0 ws), F100/F76/F80 (partial). First child: sdplab-8nkm (picker fail incident on sdplab-nj4 F135-05). Future children: audit script, per-feature backfill, INDEX update, picker hardening, sdp doctor backlog gate."} -{"_type":"memory","key":"f142-r3-done","value":"F142 round 3 closed via PR #123 squash to ff20222. F142-08 mass backfill 49 beads. F142-09 ghost cleanup. F142-10 picker defense. Local main out of sync, needs reset to origin."} -{"_type":"memory","key":"subagent-format-spec-2026-04-26","value":"Claude Code subagent file format (.claude/agents/*.md): YAML frontmatter MUST use 'tools: Read, Bash, Glob, Grep' COMMA-STRING format (PascalCase). NOT 'tools: { Read: true }' object form, NOT 'tools: [Read, Bash]' array. Source: docs.claude.com/docs/en/subagents.md lines 222-301. Wrong format → empty tool registry → agent fabricates XML \u003cfunction_calls\u003e in response.text (tool_uses=0). Verified via /tmp/probe diagnostic. Built-in agents (general-purpose, Explore, claude-code-guide) work because their registry is hardcoded, file-based ones go through the broken parsing path. PR #132 fixes 13 SDP custom agents. Note: Claude Code caches agent definitions at session start — fixes require restart to take effect, not just file save."} +{"_type":"memory","key":"f142-closed-2026-04-26","value":"F142 epic (sdplab-85ji) closed 2026-04-26: Workstream coverage gap closed. 6 children closed (gkl5 audit, rqsw baits, s6g7 orphans, f2h0 picker, 3obq doctor, fb1o INDEX). Plus sdplab-8nkm picker incident closed. Branch feature/F142-workstream-coverage-gap, 5 commits not yet merged to main. Delivered: scripts/deliver-pick.sh v2 (skip leafless+design-pending), internal/backlog/audit.go + cmd/sdp/cmd_doctor_backlog.go ('sdp doctor backlog'), 6 ws scaffolds (00-082-01, 00-083-01, 00-084-01, 00-085-01, 00-101-02, 00-133-01), INDEX.md updated, CI gate. 0 findings on doctor backlog. 189 Go tests pass."} {"_type":"memory","key":"f142-merged-2026-04-26","value":"F142 closed and merged 2026-04-26: feature/F142-workstream-coverage-gap pushed to origin (https://github.com/fall-out-bug/sdp_lab/tree/feature/F142-workstream-coverage-gap), local main merged with 7 ahead of origin/main (6 F142 commits + retains F141 merge f3ccef7). Ready for PR opening via GitHub UI when user wants. Branch can be deleted locally; remote branch retained for PR."} -{"_type":"memory","key":"f145-progress-2026-04-26","value":"F145 epic in progress 2026-04-26. Branch: feature/F145-multi-provider-cascade (origin in sync). 3/14 WS committed (not yet closed in beads, awaiting merge): WS07 sdplab-s686 (cursor/opencode --model plumbing P0), WS01 sdplab-0261 (providers/ scaffold + tests), WS08 sdplab-noiq (tier_class + SelectTiers). Commits: 67f085b1 / 9ac769b4 / 30782519 on top of design 752140a3. After WS07/01/08 close, 6 WS unblock in parallel: WS02-06 (5 Provider impls) + WS13 LimitsCache. Then chain: WS09 seed → WS10 cascade pkg → WS11 confidence → WS12 replay → WS14 smoke+demo. 174 tests pass in internal/dispatch/. Next session: after PR #132 merge + restart, run @delivery-loop to dispatch remaining WS via fixed implementer subagents. F145 PR will open when epic is ready (or sensible mid-milestone)."} +{"_type":"memory","key":"f142-r3-done","value":"F142 round 3 closed via PR #123 squash to ff20222. F142-08 mass backfill 49 beads. F142-09 ghost cleanup. F142-10 picker defense. Local main out of sync, needs reset to origin."} +{"_type":"memory","key":"subagent-implementer-broken-2026-04-26","value":"BROKEN: subagent_type=implementer fabricates tool_use. Confirmed via 3-test diagnostic 2026-04-26. T1 (implementer+sonnet): tool_uses=0, file not created, fabricated plausible output. T2 (general-purpose+haiku): tool_uses=1, file created. T3 (Explore+haiku): honest refusal (read-only). Pattern: implementer outputs old-format XML \u003cfunction_calls\u003e\u003cinvoke name='...'\u003e as response text instead of structured tool_use blocks. Affects ANY model. Workaround: use subagent_type=general-purpose for code implementation tasks. Affected sessions: F145 WS01/WS07/WS08 dispatch all fabricated, had to redo manually."} diff --git a/cmd/sdp-pi-review/main.go b/cmd/sdp-pi-review/main.go index f7f6b2c8..2efdd4c0 100644 --- a/cmd/sdp-pi-review/main.go +++ b/cmd/sdp-pi-review/main.go @@ -19,6 +19,7 @@ func main() { base := fs.String("base", "", "Base ref for branch scope") feature := fs.String("feature", "", "Feature ID (e.g. F161)") testCmd := fs.String("test-command", "", "Explicit test command") + modelTimeout := fs.Duration("model-timeout", pireview.DefaultModelTimeout, "Timeout per model reviewer call") writeVerdict := fs.Bool("write-verdict", false, "Write .sdp/review_verdict.json") createBeads := fs.Bool("create-beads", false, "Create beads for actionable findings") round := fs.Int("round", 1, "Review round number") @@ -31,12 +32,13 @@ func main() { } cfg := pireview.Config{ - ProjectRoot: projectRoot, - Scope: pireview.ScopeMode(*scope), - BaseRef: *base, - Feature: *feature, - TestCommand: *testCmd, - Runner: executil.GetDefaultRunner(), + ProjectRoot: projectRoot, + Scope: pireview.ScopeMode(*scope), + BaseRef: *base, + Feature: *feature, + TestCommand: *testCmd, + ModelTimeout: *modelTimeout, + Runner: executil.GetDefaultRunner(), } if err := cfg.Validate(); err != nil { diff --git a/docs/reference/pi-review-spec.md b/docs/reference/pi-review-spec.md index 3002a593..4151a0e9 100644 --- a/docs/reference/pi-review-spec.md +++ b/docs/reference/pi-review-spec.md @@ -78,23 +78,24 @@ Reviewers may distrust or challenge the evidence, but they must not invent test MVP reviewers: -- Kimi through the already configured local `pi` runtime - ZAI/GLM through the already configured local `pi` runtime +- Kimi Coding through the already configured local `pi` runtime +- MiniMax through the already configured local `pi` runtime Fallback: - OpenRouter model configured in `pi` environment -SDP does not own provider keys. Keys, subscriptions, provider names, and model IDs live in the `pi` runtime environment. SDP passes a context packet and requested reviewer slots to the local `pi` binary. In the current target environment, `pi` already exists and has `kimi` and `zai` connected. For this gate, `glm` means the GLM model family exposed through `zai`; do not configure a separate `glm` provider slot. +SDP does not own provider keys. Keys, subscriptions, provider names, and model IDs live in the `pi` runtime environment. SDP passes a context packet and requested reviewer slots to the local `pi` binary. In the current target environment, `pi` already exists and has `zai`, `kimi-coding`, and `minimax` connected. For this gate, `glm` means the GLM model family exposed through `zai`; do not configure a separate `glm` provider slot. Recommended slots: | Slot | Purpose | Required | |---|---|---| -| `zai` | broad correctness and maintainability review using GLM-family models | yes | -| `kimi` | adversarial code review and missed-edge search | yes | -| `openrouter-fallback` | only if ZAI or Kimi fails or times out | no | -| `synthesizer` | normalize findings into SDP verdict | yes | +| `zai` | broad correctness and maintainability review using `zai/glm-5.1` | yes | +| `kimi` | adversarial code review and missed-edge search using `kimi-coding/k2p6` | yes | +| `minimax` | independent implementation-risk review using `minimax/MiniMax-M2.7` | yes | +| `openrouter-fallback` | only if ZAI, Kimi, or MiniMax fails or times out | no | ## Finding Contract diff --git a/internal/pireview/pireview.go b/internal/pireview/pireview.go index 5c61c2dc..63021961 100644 --- a/internal/pireview/pireview.go +++ b/internal/pireview/pireview.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "strings" + "time" "github.com/fall-out-bug/sdp_lab/internal/executil" ) @@ -21,14 +22,17 @@ const ( // Config holds all configuration for a pi-review run. type Config struct { - ProjectRoot string - Scope ScopeMode - BaseRef string - Feature string - TestCommand string - Runner executil.CommandRunner + ProjectRoot string + Scope ScopeMode + BaseRef string + Feature string + TestCommand string + ModelTimeout time.Duration + Runner executil.CommandRunner } +const DefaultModelTimeout = 5 * time.Minute + // Validate checks required fields and returns an error if invalid. func (c Config) Validate() error { if c.ProjectRoot == "" { @@ -40,12 +44,22 @@ func (c Config) Validate() error { if c.Scope == ScopeBranch && c.BaseRef == "" { return fmt.Errorf("pireview: BaseRef is required with Scope=branch") } + if c.ModelTimeout < 0 { + return fmt.Errorf("pireview: ModelTimeout must be non-negative") + } if c.Runner == nil { return fmt.Errorf("pireview: Runner is required") } return nil } +func (c Config) effectiveModelTimeout() time.Duration { + if c.ModelTimeout > 0 { + return c.ModelTimeout + } + return DefaultModelTimeout +} + // ContextPacket holds the deterministic context sent to model reviewers. type ContextPacket struct { GitStatus string `json:"git_status"` diff --git a/internal/pireview/runner.go b/internal/pireview/runner.go index 85a199c4..872bd1ab 100644 --- a/internal/pireview/runner.go +++ b/internal/pireview/runner.go @@ -124,9 +124,9 @@ type ReviewerSlot struct { // DefaultSlots returns the MVP reviewer panel. func DefaultSlots() []ReviewerSlot { return []ReviewerSlot{ - {Slot: "zai", Provider: "zai", Model: "glm", Role: "reviewer", Required: true}, - {Slot: "kimi", Provider: "kimi", Model: "kimi-k2", Role: "reviewer", Required: true}, - {Slot: "synthesizer", Provider: "zai", Model: "glm", Role: "synthesizer", Required: true}, + {Slot: "zai", Provider: "zai", Model: "glm-5.1", Role: "reviewer", Required: true}, + {Slot: "kimi", Provider: "kimi-coding", Model: "k2p6", Role: "reviewer", Required: true}, + {Slot: "minimax", Provider: "minimax", Model: "MiniMax-M2.7", Role: "reviewer", Required: true}, } } @@ -298,8 +298,11 @@ func (r *Runner) runModelPanel(ctx context.Context, runID string, pkt *ContextPa // invokePi calls the local pi binary with the review context. func (r *Runner) invokePi(ctx context.Context, slot ReviewerSlot, pkt *ContextPacket, evidence *TestEvidence) (string, error) { reviewPrompt := buildReviewPrompt(slot, pkt, evidence) - out, err := r.runner.CombinedOutput(ctx, r.cfg.ProjectRoot, - "pi", "run", "--provider", slot.Provider, "--model", slot.Model, reviewPrompt) + modelCtx, cancel := context.WithTimeout(ctx, r.cfg.effectiveModelTimeout()) + defer cancel() + out, err := r.runner.CombinedOutput(modelCtx, r.cfg.ProjectRoot, + "pi", "--provider", slot.Provider, "--model", slot.Model, + "--no-tools", "--no-context-files", "--no-session", "-p", reviewPrompt) if err != nil { return "", fmt.Errorf("pi run %s/%s: %w", slot.Provider, slot.Model, err) } @@ -309,14 +312,30 @@ func (r *Runner) invokePi(ctx context.Context, slot ReviewerSlot, pkt *ContextPa // invokeFallback attempts OpenRouter when a primary provider fails. func (r *Runner) invokeFallback(ctx context.Context, slot ReviewerSlot, pkt *ContextPacket, evidence *TestEvidence) (string, error) { reviewPrompt := buildReviewPrompt(slot, pkt, evidence) - out, err := r.runner.CombinedOutput(ctx, r.cfg.ProjectRoot, - "pi", "run", "--provider", "openrouter", "--model", "fallback", reviewPrompt) + modelCtx, cancel := context.WithTimeout(ctx, r.cfg.effectiveModelTimeout()) + defer cancel() + out, err := r.runner.CombinedOutput(modelCtx, r.cfg.ProjectRoot, + "pi", "--provider", "openrouter", "--model", fallbackModel(slot), + "--no-tools", "--no-context-files", "--no-session", "-p", reviewPrompt) if err != nil { return "", fmt.Errorf("openrouter fallback: %w", err) } return string(out), nil } +func fallbackModel(slot ReviewerSlot) string { + switch slot.Slot { + case "zai": + return "z-ai/glm-5.1" + case "kimi": + return "moonshotai/kimi-k2.6" + case "minimax": + return "minimax/minimax-m2.7" + default: + return "z-ai/glm-5.1" + } +} + // buildReviewPrompt constructs the prompt for the model reviewer. func buildReviewPrompt(slot ReviewerSlot, pkt *ContextPacket, evidence *TestEvidence) string { var b strings.Builder diff --git a/internal/pireview/runner_test.go b/internal/pireview/runner_test.go index 3543be54..a4f76a23 100644 --- a/internal/pireview/runner_test.go +++ b/internal/pireview/runner_test.go @@ -2,9 +2,12 @@ package pireview import ( "context" + "errors" "os" "path/filepath" + "strings" "testing" + "time" ) func TestDefaultSlots(t *testing.T) { @@ -18,8 +21,8 @@ func TestDefaultSlots(t *testing.T) { if slots[1].Slot != "kimi" { t.Errorf("slot[1] = %q, want %q", slots[1].Slot, "kimi") } - if slots[2].Slot != "synthesizer" { - t.Errorf("slot[2] = %q, want %q", slots[2].Slot, "synthesizer") + if slots[2].Slot != "minimax" { + t.Errorf("slot[2] = %q, want %q", slots[2].Slot, "minimax") } } @@ -179,10 +182,10 @@ func TestRunner_Run_WithFakes(t *testing.T) { fr := &fakeRunner{ responses: map[string][]byte{ - "git rev-parse --abbrev-ref HEAD": []byte("feature/F161\n"), - "git rev-parse HEAD": []byte("abc123\n"), + "git rev-parse --abbrev-ref HEAD": []byte("feature/F161\n"), + "git rev-parse HEAD": []byte("abc123\n"), "git status --porcelain --untracked-files=all": []byte(" M main.go\n"), - "git diff HEAD": []byte("+new code\n"), + "git diff HEAD": []byte("+new code\n"), }, } @@ -229,6 +232,87 @@ func TestRunner_Run_WithFakes(t *testing.T) { _ = modelOutput } +type blockingRunner struct{} + +func (blockingRunner) Output(context.Context, string, string, ...string) ([]byte, error) { + return nil, nil +} + +func (blockingRunner) Run(context.Context, string, string, ...string) error { + return nil +} + +func (blockingRunner) CombinedOutput(ctx context.Context, _ string, _ string, _ ...string) ([]byte, error) { + <-ctx.Done() + return nil, ctx.Err() +} + +func TestInvokePiHonorsModelTimeout(t *testing.T) { + r := &Runner{ + cfg: Config{ + ProjectRoot: t.TempDir(), + Scope: ScopeWorkingTree, + ModelTimeout: 10 * time.Millisecond, + Runner: blockingRunner{}, + }, + runner: blockingRunner{}, + } + + start := time.Now() + _, err := r.invokePi(context.Background(), ReviewerSlot{ + Slot: "zai", + Provider: "zai", + Model: "glm", + Role: "reviewer", + }, &ContextPacket{}, &TestEvidence{}) + + if !errors.Is(err, context.DeadlineExceeded) { + t.Fatalf("expected deadline exceeded, got %v", err) + } + if elapsed := time.Since(start); elapsed > time.Second { + t.Fatalf("model timeout was not bounded; elapsed=%s", elapsed) + } +} + +func TestInvokePiUsesPiPrintContract(t *testing.T) { + fr := &fakeRunner{} + r := &Runner{ + cfg: Config{ + ProjectRoot: t.TempDir(), + Scope: ScopeWorkingTree, + ModelTimeout: time.Second, + Runner: fr, + }, + runner: fr, + } + + _, err := r.invokePi(context.Background(), ReviewerSlot{ + Slot: "kimi", + Provider: "kimi-coding", + Model: "k2p6", + Role: "reviewer", + }, &ContextPacket{}, &TestEvidence{}) + if err != nil { + t.Fatalf("invokePi: %v", err) + } + if len(fr.calls) != 1 { + t.Fatalf("expected 1 call, got %d", len(fr.calls)) + } + call := fr.calls[0] + if call.name != "pi" { + t.Fatalf("command = %q, want pi", call.name) + } + got := strings.Join(call.args, " ") + for _, want := range []string{"--provider kimi-coding", "--model k2p6", "--no-tools", "--no-context-files", "--no-session", "-p"} { + if !strings.Contains(got, want) { + t.Fatalf("pi args missing %q: %v", want, call.args) + } + } + if len(call.args) > 0 && call.args[0] == "run" { + t.Fatalf("pi invocation must not use removed run subcommand: %v", call.args) + } +} + func TestHashString(t *testing.T) { h1 := hashString("hello") h2 := hashString("hello") From 5bebf80b8f2d9a4efa6fbbf072edcd0b339a9c98 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 29 Apr 2026 17:14:37 +0300 Subject: [PATCH 04/11] fix: preserve smoke test exit code --- .beads/issues.jsonl | 17 +++++++++-------- scripts/run_smoke_tests.sh | 12 +++++++----- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 3f9e8d4b..faa2459e 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -35,6 +35,7 @@ {"_type":"issue","id":"sdplab-22","title":"F100-01: Reference Integrity CI Gate","status":"closed","priority":0,"issue_type":"task","assignee":"Andrei","created_at":"2026-04-05T15:00:00Z","created_by":"UX Council","updated_at":"2026-04-26T13:30:49Z","closed_at":"2026-04-26T13:30:49Z","close_reason":"F100 Reference Integrity complete: CI gate + one-time cleanup. 56 tests passing.","labels":["F100","P0","harness:claude","stream-C","ux"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-23","title":"F100-02: One-Time Reference Cleanup","status":"closed","priority":0,"issue_type":"task","assignee":"Andrei","created_at":"2026-04-05T15:00:00Z","created_by":"UX Council","updated_at":"2026-04-26T13:30:50Z","closed_at":"2026-04-26T13:30:50Z","close_reason":"F100 Reference Integrity complete: CI gate + one-time cleanup. 56 tests passing.","labels":["F100","P0","harness:any","stream-C","ux"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-24","title":"F101-01: Write Plan Emission and Confirmation","status":"closed","priority":0,"issue_type":"feature","assignee":"Andrei","created_at":"2026-04-05T15:00:00Z","created_by":"UX Council","updated_at":"2026-04-19T07:33:30Z","closed_at":"2026-04-19T07:33:30Z","close_reason":"F101-01 merged to main via PR #95 after sdp PR #130 landed on sdp/main. Write Plan prompt surface is now shipped for the nine stateful skills.","labels":["F101","P0","harness:any","stream-B","ux"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-ljtr","title":"pi-review P1: run_smoke_tests.sh pipes JSON through tee without preserving exit code","description":"File: scripts/run_smoke_tests.sh:20-23\nRationale: The line 'go test -tags=smoke ./test/smoke/... -v -json 2\u003e\u00261 | tee /tmp/sdp-smoke-raw.json' runs inside 'set +e', and PIPESTATUS[0] captures the go test exit code. However, 'set +e' is set AFTER 'set -euo pipefail' at the top, and the PIPESTATUS fallback '${PIPESTATUS[0]:-$?}' means if PIPESTATUS is not available (e.g., non-bash shells), $? reflects the last command in the pipeline (tee), which always succeeds. The shebang is #!/usr/bin/env bash so PIPESTATUS should be available, but the fallback masks this class of bug.\nSuggested fix: Replace the pipeline with process substitution or use a temporary file approach: run go test redirecting to file, then cat the file, avoiding the pipeline exit-code problem entirely. The current approach works in bash but the pattern is fragile.\nDedupe key: P1:scripts/run_smoke_tests.sh:run_smoke_tests.sh pipes JSON through tee without preserving exit code","status":"open","priority":1,"issue_type":"bug","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T14:10:48Z","created_by":"Andrei","updated_at":"2026-04-29T14:10:48Z","labels":["F150","pi-review","review-finding","round-4"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-u1lw","title":"Review gate: pi-review model calls can hang indefinitely","description":"source=runtime; discovered while running PR #144 review gate on 2026-04-29. cmd/sdp-pi-review invoked pi run without a per-model timeout, so a stuck external model held the delivery loop for \u003e6 minutes with no output. Add bounded model reviewer context/CLI timeout and regression coverage.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T13:46:47Z","created_by":"Andrei","updated_at":"2026-04-29T13:46:56Z","started_at":"2026-04-29T13:46:56Z","comments":[{"id":"019dd989-2a3a-7c5f-86ca-55fb82d17efa","issue_id":"sdplab-u1lw","author":"Andrei","text":"Follow-up root cause while re-running gate: F161 implementation used obsolete 'pi run' syntax. Pi 0.70.6 supports non-interactive review via 'pi --provider ... --model ... --no-tools --no-context-files --no-session -p \u003cprompt\u003e'. Fix includes correct provider/model defaults for zai/glm-5.1, kimi-coding/k2p6, minimax/MiniMax-M2.7 plus timeout coverage.","created_at":"2026-04-29T13:58:58Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1} {"_type":"issue","id":"sdplab-5z1p","title":"Pi harness: skills and slash commands not discoverable","description":"Pi 0.70.6 loads Agent Skills from .pi/skills or .agents/skills/\u003cname\u003e/SKILL.md and prompt templates from .pi/prompts. Current SDP exposes commands only under prompts/commands and flat skill files are not enough for Pi auto-discovery. Fix Pi resource packaging and add smoke coverage.","acceptance_criteria":"- [ ] Pi resource smoke reports all manifest skills without skill diagnostics.\\n- [ ] Pi resource smoke reports all manifest commands as prompt templates.\\n- [ ] Generated adapters include a Pi-native skill and prompt-template surface.\\n- [ ] Harness docs name Pi CLI/version, entrypoints, limitations, and smoke test.","status":"closed","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:51:48Z","created_by":"Andrei","updated_at":"2026-04-29T09:57:20Z","started_at":"2026-04-29T09:51:51Z","closed_at":"2026-04-29T09:57:20Z","close_reason":"duplicate of sdplab-1ily","labels":["F162","commands","harness","pi","sdp","skills"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-vd3r","title":"Review: init rewrites user manifest despite update semantics","description":"source=review; aspect=code-quality,ux; blocking=true; severity=major. cmd/sdp/cmd_init.go persists filtered harnesses by yaml.Marshal into target sdp.manifest.yaml, dropping comments/formatting and narrowing future generation scope while --update help says user-modified manifest is kept. Resolve selective-harness doctor semantics without silently rewriting user manifests, or require explicit manifest-write flag.","status":"closed","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:50:10Z","created_by":"Andrei","updated_at":"2026-04-29T11:50:04Z","started_at":"2026-04-29T11:14:20Z","closed_at":"2026-04-29T11:50:04Z","close_reason":"Merged in PR #145 (cb5470e): selective init preserves existing manifest","comments":[{"id":"019dd8f2-739b-7007-a15a-06a57ec62688","issue_id":"sdplab-vd3r","author":"Andrei","text":"Fixed in PR #145 commit pending push: sdp init no longer rewrites target sdp.manifest.yaml for selective --harness installs; .sdp/generated remains full manifest-derived cache. Added regression test TestInit_ExistingManifestPreservesUserManifestBytes and quality gates passed. Keep open until PR #145 merges.","created_at":"2026-04-29T11:14:21Z"},{"id":"019dd8fb-1c19-77b4-b021-5e199f2fe70e","issue_id":"sdplab-vd3r","author":"Andrei","text":"Pushed fix in commit b307ff75 on PR #145; CI required checks green. Issue remains in_progress until PR merge per repo policy.","created_at":"2026-04-29T11:23:49Z"}],"dependency_count":0,"dependent_count":0,"comment_count":2} @@ -555,16 +556,16 @@ {"_type":"issue","id":"sdplab-houh","title":"F036: Orchestrate + in-toto (historical placeholder)","description":"absorbed by F064–F067 auto-attestation","status":"closed","priority":4,"issue_type":"feature","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-18T11:27:58Z","created_by":"Andrei","updated_at":"2026-04-18T11:27:58Z","closed_at":"2026-04-18T11:27:58Z","close_reason":"absorbed by F064–F067 auto-attestation","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-crg","title":"F034: Graduated Enforcement (historical placeholder)","description":"historical placeholder; no active scope","status":"closed","priority":4,"issue_type":"feature","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-18T11:27:55Z","created_by":"Andrei","updated_at":"2026-04-18T11:27:56Z","closed_at":"2026-04-18T11:27:56Z","close_reason":"historical placeholder; no active scope","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-h5y","title":"F032: PR Evidence Summary (historical placeholder)","description":"historical placeholder; no active scope (2026-04-18 triage)","status":"closed","priority":4,"issue_type":"feature","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-18T11:27:34Z","created_by":"Andrei","updated_at":"2026-04-18T11:27:54Z","closed_at":"2026-04-18T11:27:54Z","close_reason":"historical placeholder; no active scope (2026-04-18 triage)","dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"memory","key":"f136-peer-memory-horizons-2026-04-20-epic","value":"f136-peer-memory-horizons-2026-04-20 | Epic F136 (sdplab-t1ty) created 2026-04-20: Peer Memory Foundation H1. Children: F136-01..06 (sdplab-r9py/lq4h/olvg/h25q/l0mm/h673). Parked: F137 H2 (sdplab-75u9, revisit 2026-07-06), F138 H3 (sdplab-7c45, revisit 2026-10-05). Design: docs/plans/2026-04-20-f136-peer-memory-h1-design.md. Market gap: peer-collaboration production framework where attribution is first-class (ChatCollab/HULA academic, no commercial product as of April 2026). H1 success = binary: did sdp memory query surface real lost context in 30-day dogfood. Label: peer-memory."} -{"_type":"memory","key":"f142-epic-2026-04-26","value":"F142 epic (sdplab-85ji) created 2026-04-26: Workstream coverage gap — picker + ws/INDEX backfill + doctor gate. Root cause: features created via /design bypass /feature autogen → 0 ws files → picker selects leafless feature → /build fails. Affected: F141 (0 ws), F135 (0 ws), F100/F76/F80 (partial). First child: sdplab-8nkm (picker fail incident on sdplab-nj4 F135-05). Future children: audit script, per-feature backfill, INDEX update, picker hardening, sdp doctor backlog gate."} -{"_type":"memory","key":"f144-epic-created-2026-04-26-sdplab-sjdp","value":"F144 epic created 2026-04-26 (sdplab-sjdp): Inference Confidence \u0026 Quality Control. 8 children attached: 01 sdplab-wzit core lib, 02 sdplab-339w self-check, 03 sdplab-w7j1 n-sample, 04 sdplab-tfmg constraint+composer, 05 sdplab-kk1v ws-verdict adapter (P1), 06 sdplab-cvu3 architect adapter, 07 sdplab-s5x8 dispatch lite, 08 sdplab-kd38 replay+metrics. Critical path: 01→04→05→08. Plus 2 hygiene issues: sdplab-vuw4 (drift docs/design vs docs/plans), sdplab-bv3r (AGENTS.md design-first phrasing). Design: docs/plans/2026-04-26-f144-inference-confidence-design.md. Branch: feature/F144-inference-confidence (not yet created, untracked design doc on worktree-bulk-deliver branch)."} -{"_type":"memory","key":"f145-epic-created-2026-04-26","value":"F145 epic (sdplab-ldmq) created 2026-04-26: Multi-Provider Dispatch Matrix \u0026 Confidence-Driven Cascade. 14 children: WS01 sdplab-0261 (providers/ scaffold), WS02 sdplab-y93k (OpenAI), WS03 sdplab-w4ru (Anthropic), WS04 sdplab-bg69 (Cursor ~30 models), WS05 sdplab-60ia (Kimi), WS06 sdplab-5lve (Ollama replaces LocalConfig), WS07 sdplab-s686 (cursor/opencode --model plumbing P0 bug), WS08 sdplab-noiq (tier_class label), WS09 sdplab-3lva (profiles_default.json seed), WS10 sdplab-5ii8 (cascade pkg + Invoker), WS11 sdplab-fco9 (F144 confidence injection), WS12 sdplab-uamt (cascade-replay corpus), WS13 sdplab-135g (LimitsCache), WS14 sdplab-2p8g (smoke + Day-8 demo). Design: docs/plans/2026-04-26-f145-multi-provider-dispatch-cascade-design.md. Branch (planned): feature/F145-multi-provider-cascade. Discovery decisions: providers/ sub-pkg, cascade pkg, tier-label hybrid, composer gate default, UNSURE+FAIL+heuristic short-circuit, MaxDepth/Budget configurable, hybrid LimitsCache, F144 replay extension, harness-CLI keys (no SDP auth)."} -{"_type":"memory","key":"subagent-format-spec-2026-04-26","value":"Claude Code subagent file format (.claude/agents/*.md): YAML frontmatter MUST use 'tools: Read, Bash, Glob, Grep' COMMA-STRING format (PascalCase). NOT 'tools: { Read: true }' object form, NOT 'tools: [Read, Bash]' array. Source: docs.claude.com/docs/en/subagents.md lines 222-301. Wrong format → empty tool registry → agent fabricates XML \u003cfunction_calls\u003e in response.text (tool_uses=0). Verified via /tmp/probe diagnostic. Built-in agents (general-purpose, Explore, claude-code-guide) work because their registry is hardcoded, file-based ones go through the broken parsing path. PR #132 fixes 13 SDP custom agents. Note: Claude Code caches agent definitions at session start — fixes require restart to take effect, not just file save."} {"_type":"memory","key":"f129-epic-created-2026-04-16-sdplab-8k5","value":"F129 epic created 2026-04-16 (sdplab-8k5 + 10 children sdplab-8k5.1..10 + 3 cross-lane: F106-07 sdplab-lqb, F124-05 sdplab-nai, F125-05 sdplab-as0). Addresses 5 audit findings: orchestration autonomy, subagent default, superpowers skill gap, doc drift, ad-hoc regression. Design: docs/plans/2026-04-16-f129-autonomy-regression-design.md. Cross-lane additions reuse existing F106/F124/F125 tracks instead of duplicating."} +{"_type":"memory","key":"f142-merged-2026-04-26","value":"F142 closed and merged 2026-04-26: feature/F142-workstream-coverage-gap pushed to origin (https://github.com/fall-out-bug/sdp_lab/tree/feature/F142-workstream-coverage-gap), local main merged with 7 ahead of origin/main (6 F142 commits + retains F141 merge f3ccef7). Ready for PR opening via GitHub UI when user wants. Branch can be deleted locally; remote branch retained for PR."} +{"_type":"memory","key":"f145-epic-created-2026-04-26","value":"F145 epic (sdplab-ldmq) created 2026-04-26: Multi-Provider Dispatch Matrix \u0026 Confidence-Driven Cascade. 14 children: WS01 sdplab-0261 (providers/ scaffold), WS02 sdplab-y93k (OpenAI), WS03 sdplab-w4ru (Anthropic), WS04 sdplab-bg69 (Cursor ~30 models), WS05 sdplab-60ia (Kimi), WS06 sdplab-5lve (Ollama replaces LocalConfig), WS07 sdplab-s686 (cursor/opencode --model plumbing P0 bug), WS08 sdplab-noiq (tier_class label), WS09 sdplab-3lva (profiles_default.json seed), WS10 sdplab-5ii8 (cascade pkg + Invoker), WS11 sdplab-fco9 (F144 confidence injection), WS12 sdplab-uamt (cascade-replay corpus), WS13 sdplab-135g (LimitsCache), WS14 sdplab-2p8g (smoke + Day-8 demo). Design: docs/plans/2026-04-26-f145-multi-provider-dispatch-cascade-design.md. Branch (planned): feature/F145-multi-provider-cascade. Discovery decisions: providers/ sub-pkg, cascade pkg, tier-label hybrid, composer gate default, UNSURE+FAIL+heuristic short-circuit, MaxDepth/Budget configurable, hybrid LimitsCache, F144 replay extension, harness-CLI keys (no SDP auth)."} +{"_type":"memory","key":"f136-peer-memory-horizons-2026-04-20-epic","value":"f136-peer-memory-horizons-2026-04-20 | Epic F136 (sdplab-t1ty) created 2026-04-20: Peer Memory Foundation H1. Children: F136-01..06 (sdplab-r9py/lq4h/olvg/h25q/l0mm/h673). Parked: F137 H2 (sdplab-75u9, revisit 2026-07-06), F138 H3 (sdplab-7c45, revisit 2026-10-05). Design: docs/plans/2026-04-20-f136-peer-memory-h1-design.md. Market gap: peer-collaboration production framework where attribution is first-class (ChatCollab/HULA academic, no commercial product as of April 2026). H1 success = binary: did sdp memory query surface real lost context in 30-day dogfood. Label: peer-memory."} +{"_type":"memory","key":"f142-r3-done","value":"F142 round 3 closed via PR #123 squash to ff20222. F142-08 mass backfill 49 beads. F142-09 ghost cleanup. F142-10 picker defense. Local main out of sync, needs reset to origin."} {"_type":"memory","key":"f144-merged-2026-04-26","value":"F144 PR #131 MERGED to main at 2026-04-26T17:43Z, merge commit ed393e3. internal/inference/confidence/ live on main: Status enum, Result[T], Policy/Strategy/Checker, 3 strategies (constraint/selfcheck/nsample), 3 adapters (wsverdict/architect/dispatch). 111 tests, ≥85% coverage. Branch feature/F144-inference-confidence still exists locally. Replaces stale 'sdplab-8nkm-untracked-design-doc' status."} -{"_type":"memory","key":"f145-progress-2026-04-26","value":"F145 epic in progress 2026-04-26. Branch: feature/F145-multi-provider-cascade (origin in sync). 3/14 WS committed (not yet closed in beads, awaiting merge): WS07 sdplab-s686 (cursor/opencode --model plumbing P0), WS01 sdplab-0261 (providers/ scaffold + tests), WS08 sdplab-noiq (tier_class + SelectTiers). Commits: 67f085b1 / 9ac769b4 / 30782519 on top of design 752140a3. After WS07/01/08 close, 6 WS unblock in parallel: WS02-06 (5 Provider impls) + WS13 LimitsCache. Then chain: WS09 seed → WS10 cascade pkg → WS11 confidence → WS12 replay → WS14 smoke+demo. 174 tests pass in internal/dispatch/. Next session: after PR #132 merge + restart, run @delivery-loop to dispatch remaining WS via fixed implementer subagents. F145 PR will open when epic is ready (or sensible mid-milestone)."} {"_type":"memory","key":"f141-epic-2026-04-25","value":"F141 epic (sdplab-o4sp) created 2026-04-25: Multi-harness install bootstrap \u0026 adapter parity. Children: F141-01 sdplab-fojs (manifest), -02 sdplab-6ea5 (generator), -03 sdplab-cl4o (bootstrap), -04 sdplab-i9q3 (doctor gate), -05 sdplab-r4y5 (parity matrix), -06 sdplab-cnpm (migration), -07 sdplab-nqjt (README). Closes gap left by F127/F128: downstream repos install SDP piecemeal. Approach: single sdp.manifest.yaml -\u003e adapter generator -\u003e curl|bash bootstrap -\u003e sdp doctor drift gate. Design: docs/plans/2026-04-25-f141-multi-harness-install-bootstrap-design.md. Branch: feature/F141-multi-harness-install-bootstrap."} {"_type":"memory","key":"f142-closed-2026-04-26","value":"F142 epic (sdplab-85ji) closed 2026-04-26: Workstream coverage gap closed. 6 children closed (gkl5 audit, rqsw baits, s6g7 orphans, f2h0 picker, 3obq doctor, fb1o INDEX). Plus sdplab-8nkm picker incident closed. Branch feature/F142-workstream-coverage-gap, 5 commits not yet merged to main. Delivered: scripts/deliver-pick.sh v2 (skip leafless+design-pending), internal/backlog/audit.go + cmd/sdp/cmd_doctor_backlog.go ('sdp doctor backlog'), 6 ws scaffolds (00-082-01, 00-083-01, 00-084-01, 00-085-01, 00-101-02, 00-133-01), INDEX.md updated, CI gate. 0 findings on doctor backlog. 189 Go tests pass."} -{"_type":"memory","key":"f142-merged-2026-04-26","value":"F142 closed and merged 2026-04-26: feature/F142-workstream-coverage-gap pushed to origin (https://github.com/fall-out-bug/sdp_lab/tree/feature/F142-workstream-coverage-gap), local main merged with 7 ahead of origin/main (6 F142 commits + retains F141 merge f3ccef7). Ready for PR opening via GitHub UI when user wants. Branch can be deleted locally; remote branch retained for PR."} -{"_type":"memory","key":"f142-r3-done","value":"F142 round 3 closed via PR #123 squash to ff20222. F142-08 mass backfill 49 beads. F142-09 ghost cleanup. F142-10 picker defense. Local main out of sync, needs reset to origin."} +{"_type":"memory","key":"f144-epic-created-2026-04-26-sdplab-sjdp","value":"F144 epic created 2026-04-26 (sdplab-sjdp): Inference Confidence \u0026 Quality Control. 8 children attached: 01 sdplab-wzit core lib, 02 sdplab-339w self-check, 03 sdplab-w7j1 n-sample, 04 sdplab-tfmg constraint+composer, 05 sdplab-kk1v ws-verdict adapter (P1), 06 sdplab-cvu3 architect adapter, 07 sdplab-s5x8 dispatch lite, 08 sdplab-kd38 replay+metrics. Critical path: 01→04→05→08. Plus 2 hygiene issues: sdplab-vuw4 (drift docs/design vs docs/plans), sdplab-bv3r (AGENTS.md design-first phrasing). Design: docs/plans/2026-04-26-f144-inference-confidence-design.md. Branch: feature/F144-inference-confidence (not yet created, untracked design doc on worktree-bulk-deliver branch)."} +{"_type":"memory","key":"f145-progress-2026-04-26","value":"F145 epic in progress 2026-04-26. Branch: feature/F145-multi-provider-cascade (origin in sync). 3/14 WS committed (not yet closed in beads, awaiting merge): WS07 sdplab-s686 (cursor/opencode --model plumbing P0), WS01 sdplab-0261 (providers/ scaffold + tests), WS08 sdplab-noiq (tier_class + SelectTiers). Commits: 67f085b1 / 9ac769b4 / 30782519 on top of design 752140a3. After WS07/01/08 close, 6 WS unblock in parallel: WS02-06 (5 Provider impls) + WS13 LimitsCache. Then chain: WS09 seed → WS10 cascade pkg → WS11 confidence → WS12 replay → WS14 smoke+demo. 174 tests pass in internal/dispatch/. Next session: after PR #132 merge + restart, run @delivery-loop to dispatch remaining WS via fixed implementer subagents. F145 PR will open when epic is ready (or sensible mid-milestone)."} {"_type":"memory","key":"subagent-implementer-broken-2026-04-26","value":"BROKEN: subagent_type=implementer fabricates tool_use. Confirmed via 3-test diagnostic 2026-04-26. T1 (implementer+sonnet): tool_uses=0, file not created, fabricated plausible output. T2 (general-purpose+haiku): tool_uses=1, file created. T3 (Explore+haiku): honest refusal (read-only). Pattern: implementer outputs old-format XML \u003cfunction_calls\u003e\u003cinvoke name='...'\u003e as response text instead of structured tool_use blocks. Affects ANY model. Workaround: use subagent_type=general-purpose for code implementation tasks. Affected sessions: F145 WS01/WS07/WS08 dispatch all fabricated, had to redo manually."} +{"_type":"memory","key":"f142-epic-2026-04-26","value":"F142 epic (sdplab-85ji) created 2026-04-26: Workstream coverage gap — picker + ws/INDEX backfill + doctor gate. Root cause: features created via /design bypass /feature autogen → 0 ws files → picker selects leafless feature → /build fails. Affected: F141 (0 ws), F135 (0 ws), F100/F76/F80 (partial). First child: sdplab-8nkm (picker fail incident on sdplab-nj4 F135-05). Future children: audit script, per-feature backfill, INDEX update, picker hardening, sdp doctor backlog gate."} +{"_type":"memory","key":"subagent-format-spec-2026-04-26","value":"Claude Code subagent file format (.claude/agents/*.md): YAML frontmatter MUST use 'tools: Read, Bash, Glob, Grep' COMMA-STRING format (PascalCase). NOT 'tools: { Read: true }' object form, NOT 'tools: [Read, Bash]' array. Source: docs.claude.com/docs/en/subagents.md lines 222-301. Wrong format → empty tool registry → agent fabricates XML \u003cfunction_calls\u003e in response.text (tool_uses=0). Verified via /tmp/probe diagnostic. Built-in agents (general-purpose, Explore, claude-code-guide) work because their registry is hardcoded, file-based ones go through the broken parsing path. PR #132 fixes 13 SDP custom agents. Note: Claude Code caches agent definitions at session start — fixes require restart to take effect, not just file save."} diff --git a/scripts/run_smoke_tests.sh b/scripts/run_smoke_tests.sh index c0140377..ec869aeb 100755 --- a/scripts/run_smoke_tests.sh +++ b/scripts/run_smoke_tests.sh @@ -13,25 +13,27 @@ if [[ "${1:-}" == "--json" ]]; then JSON_OUTPUT=1; fi cd "$PROJECT_ROOT" REPORT_FILE="$(mktemp /tmp/sdp-smoke-XXXXXX.json)" -trap 'rm -f "$REPORT_FILE"' EXIT +RAW_FILE="$(mktemp /tmp/sdp-smoke-raw-XXXXXX.json)" +trap 'rm -f "$REPORT_FILE" "$RAW_FILE"' EXIT START_TS=$(date -u +%Y-%m-%dT%H:%M:%SZ) # Run smoke tests once and parse the JSON event stream. set +e -go test -tags=smoke ./test/smoke/... -v -json 2>&1 | tee /tmp/sdp-smoke-raw.json -SMOKE_EXIT=${PIPESTATUS[0]:-$?} +go test -tags=smoke ./test/smoke/... -v -json > "$RAW_FILE" 2>&1 +SMOKE_EXIT=$? set -e +cat "$RAW_FILE" END_TS=$(date -u +%Y-%m-%dT%H:%M:%SZ) -START_TS="$START_TS" END_TS="$END_TS" python3 - <<'PY' > "$REPORT_FILE" +START_TS="$START_TS" END_TS="$END_TS" RAW_FILE="$RAW_FILE" python3 - <<'PY' > "$REPORT_FILE" import json import os tests = [] counts = {"pass": 0, "fail": 0, "skip": 0} -with open("/tmp/sdp-smoke-raw.json", "r", encoding="utf-8") as f: +with open(os.environ["RAW_FILE"], "r", encoding="utf-8") as f: for line in f: try: event = json.loads(line) From fb8d68b28da2eccbf725573e06a44a4465245b36 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 29 Apr 2026 18:02:13 +0300 Subject: [PATCH 05/11] fix: stabilize pi review quorum --- .beads/interactions.jsonl | 1 + .beads/issues.jsonl | 20 +++++++++------- docs/reference/pi-review-spec.md | 2 ++ internal/pireview/runner.go | 41 +++++++++++++++++++++----------- internal/pireview/runner_test.go | 7 ++++++ scripts/run_smoke_tests.sh | 4 +++- 6 files changed, 51 insertions(+), 24 deletions(-) diff --git a/.beads/interactions.jsonl b/.beads/interactions.jsonl index 64517692..d58b62c8 100644 --- a/.beads/interactions.jsonl +++ b/.beads/interactions.jsonl @@ -644,3 +644,4 @@ {"id":"int-145550c2","kind":"field_change","created_at":"2026-04-29T11:50:02.663081Z","actor":"Andrei","issue_id":"sdplab-1ily","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Merged in PR #145 (cb5470e): Pi resources packaging done"}} {"id":"int-0ebbc2aa","kind":"field_change","created_at":"2026-04-29T11:50:04.334166Z","actor":"Andrei","issue_id":"sdplab-vd3r","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Merged in PR #145 (cb5470e): selective init preserves existing manifest"}} {"id":"int-8c54fcaa","kind":"field_change","created_at":"2026-04-29T11:50:05.102199Z","actor":"Andrei","issue_id":"sdplab-zdjr","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Merged in PR #145 (cb5470e): Pi prompts normalize legacy Claude skill refs"}} +{"id":"int-90b6122a","kind":"field_change","created_at":"2026-04-29T14:56:19.939554Z","actor":"Andrei","issue_id":"sdplab-i8xo","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"false positive: cmd/sdp/cmd_bootstrap.go already passes dryRun into appendGreenfieldArtifacts/appendBrownfieldArtifacts and writeDraftArtifact; TestBootstrapDryRunBrownfieldDoesNotWriteDraftDelta verifies no DRAFT-bootstrap-delta.json is written"}} diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index faa2459e..180e0500 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -35,7 +35,9 @@ {"_type":"issue","id":"sdplab-22","title":"F100-01: Reference Integrity CI Gate","status":"closed","priority":0,"issue_type":"task","assignee":"Andrei","created_at":"2026-04-05T15:00:00Z","created_by":"UX Council","updated_at":"2026-04-26T13:30:49Z","closed_at":"2026-04-26T13:30:49Z","close_reason":"F100 Reference Integrity complete: CI gate + one-time cleanup. 56 tests passing.","labels":["F100","P0","harness:claude","stream-C","ux"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-23","title":"F100-02: One-Time Reference Cleanup","status":"closed","priority":0,"issue_type":"task","assignee":"Andrei","created_at":"2026-04-05T15:00:00Z","created_by":"UX Council","updated_at":"2026-04-26T13:30:50Z","closed_at":"2026-04-26T13:30:50Z","close_reason":"F100 Reference Integrity complete: CI gate + one-time cleanup. 56 tests passing.","labels":["F100","P0","harness:any","stream-C","ux"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-24","title":"F101-01: Write Plan Emission and Confirmation","status":"closed","priority":0,"issue_type":"feature","assignee":"Andrei","created_at":"2026-04-05T15:00:00Z","created_by":"UX Council","updated_at":"2026-04-19T07:33:30Z","closed_at":"2026-04-19T07:33:30Z","close_reason":"F101-01 merged to main via PR #95 after sdp PR #130 landed on sdp/main. Write Plan prompt surface is now shipped for the nine stateful skills.","labels":["F101","P0","harness:any","stream-B","ux"],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"sdplab-ljtr","title":"pi-review P1: run_smoke_tests.sh pipes JSON through tee without preserving exit code","description":"File: scripts/run_smoke_tests.sh:20-23\nRationale: The line 'go test -tags=smoke ./test/smoke/... -v -json 2\u003e\u00261 | tee /tmp/sdp-smoke-raw.json' runs inside 'set +e', and PIPESTATUS[0] captures the go test exit code. However, 'set +e' is set AFTER 'set -euo pipefail' at the top, and the PIPESTATUS fallback '${PIPESTATUS[0]:-$?}' means if PIPESTATUS is not available (e.g., non-bash shells), $? reflects the last command in the pipeline (tee), which always succeeds. The shebang is #!/usr/bin/env bash so PIPESTATUS should be available, but the fallback masks this class of bug.\nSuggested fix: Replace the pipeline with process substitution or use a temporary file approach: run go test redirecting to file, then cat the file, avoiding the pipeline exit-code problem entirely. The current approach works in bash but the pattern is fragile.\nDedupe key: P1:scripts/run_smoke_tests.sh:run_smoke_tests.sh pipes JSON through tee without preserving exit code","status":"open","priority":1,"issue_type":"bug","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T14:10:48Z","created_by":"Andrei","updated_at":"2026-04-29T14:10:48Z","labels":["F150","pi-review","review-finding","round-4"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-i8xo","title":"pi-review P1: Bootstrap dry-run writes to target repo","description":"File: cmd/sdp/cmd_bootstrap.go:84-87\nRationale: appendGreenfieldArtifacts and appendBrownfieldArtifacts call writeDraftArtifact without passing dryRun, so bootstrap --dry-run --mode greenfield|brownfield still writes DRAFT-* files to the target repo, contradicting docs/QUICKSTART.md which promises bootstrap --dry-run is read-only.\nSuggested fix: Pass dryRun to both appendGreenfieldArtifacts and appendBrownfieldArtifacts, and thread it through to writeDraftArtifact (already done for greenfield/answers save). Verify TestBootstrapDryRunBrownfieldDoesNotWriteDraftDelta passes.\nDedupe key: P1:cmd/sdp/cmd_bootstrap.go:Bootstrap dry-run writes to target repo","status":"closed","priority":1,"issue_type":"bug","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T14:55:28Z","created_by":"Andrei","updated_at":"2026-04-29T14:56:20Z","closed_at":"2026-04-29T14:56:20Z","close_reason":"false positive: cmd/sdp/cmd_bootstrap.go already passes dryRun into appendGreenfieldArtifacts/appendBrownfieldArtifacts and writeDraftArtifact; TestBootstrapDryRunBrownfieldDoesNotWriteDraftDelta verifies no DRAFT-bootstrap-delta.json is written","labels":["F150","pi-review","review-finding","round-7"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-w1g1","title":"pi-review P1: synthesizeFindings reads artifacts by absolute path but Runner.Run does not persist models before synthesis","description":"File: internal/pireview/runner.go:322-340\nRationale: runModelPanel calls writeModelArtifact which returns an absolute path on success. synthesizeFindings then reads ModelResult.ArtifactPath via os.ReadFile. However, if writeModelArtifact fails (returns empty string), the code sets result.ArtifactPath to a planned-but-unwritten path like '.sdp/runs/pi-review/{runID}/models/{slot}.json'. When synthesis runs, it will try to read this relative path from CWD, not the project root, causing a silent miss. More critically: in Run(), modelResults are returned from runModelPanel, then synthesizeFindings(modelResults) is called BEFORE the run directory is created at line ~265. The model artifacts ARE written inside runModelPanel, but the context/evidence artifacts written later in Run() share the same runDir. This ordering is correct for models but means if MkdirAll fails at line ~265, the model artifacts are already on disk but the run context is lost.\nSuggested fix: Consider either: (1) moving the run directory creation (MkdirAll) before runModelPanel so all artifacts share the same guaranteed directory, or (2) documenting that model artifacts are written eagerly. Also fix the empty-string artifact path case to not set a path at all.\nDedupe key: P1:internal/pireview/runner.go:synthesizeFindings reads artifacts by absolute path but Runner.Run does not persist models before synthesis","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T14:55:24Z","created_by":"Andrei","updated_at":"2026-04-29T14:56:27Z","started_at":"2026-04-29T14:56:27Z","labels":["F150","pi-review","review-finding","round-7"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-ljtr","title":"pi-review P1: run_smoke_tests.sh pipes JSON through tee without preserving exit code","description":"File: scripts/run_smoke_tests.sh:20-23\nRationale: The line 'go test -tags=smoke ./test/smoke/... -v -json 2\u003e\u00261 | tee /tmp/sdp-smoke-raw.json' runs inside 'set +e', and PIPESTATUS[0] captures the go test exit code. However, 'set +e' is set AFTER 'set -euo pipefail' at the top, and the PIPESTATUS fallback '${PIPESTATUS[0]:-$?}' means if PIPESTATUS is not available (e.g., non-bash shells), $? reflects the last command in the pipeline (tee), which always succeeds. The shebang is #!/usr/bin/env bash so PIPESTATUS should be available, but the fallback masks this class of bug.\nSuggested fix: Replace the pipeline with process substitution or use a temporary file approach: run go test redirecting to file, then cat the file, avoiding the pipeline exit-code problem entirely. The current approach works in bash but the pattern is fragile.\nDedupe key: P1:scripts/run_smoke_tests.sh:run_smoke_tests.sh pipes JSON through tee without preserving exit code","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T14:10:48Z","created_by":"Andrei","updated_at":"2026-04-29T14:11:46Z","started_at":"2026-04-29T14:11:46Z","labels":["F150","pi-review","review-finding","round-4"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-u1lw","title":"Review gate: pi-review model calls can hang indefinitely","description":"source=runtime; discovered while running PR #144 review gate on 2026-04-29. cmd/sdp-pi-review invoked pi run without a per-model timeout, so a stuck external model held the delivery loop for \u003e6 minutes with no output. Add bounded model reviewer context/CLI timeout and regression coverage.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T13:46:47Z","created_by":"Andrei","updated_at":"2026-04-29T13:46:56Z","started_at":"2026-04-29T13:46:56Z","comments":[{"id":"019dd989-2a3a-7c5f-86ca-55fb82d17efa","issue_id":"sdplab-u1lw","author":"Andrei","text":"Follow-up root cause while re-running gate: F161 implementation used obsolete 'pi run' syntax. Pi 0.70.6 supports non-interactive review via 'pi --provider ... --model ... --no-tools --no-context-files --no-session -p \u003cprompt\u003e'. Fix includes correct provider/model defaults for zai/glm-5.1, kimi-coding/k2p6, minimax/MiniMax-M2.7 plus timeout coverage.","created_at":"2026-04-29T13:58:58Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1} {"_type":"issue","id":"sdplab-5z1p","title":"Pi harness: skills and slash commands not discoverable","description":"Pi 0.70.6 loads Agent Skills from .pi/skills or .agents/skills/\u003cname\u003e/SKILL.md and prompt templates from .pi/prompts. Current SDP exposes commands only under prompts/commands and flat skill files are not enough for Pi auto-discovery. Fix Pi resource packaging and add smoke coverage.","acceptance_criteria":"- [ ] Pi resource smoke reports all manifest skills without skill diagnostics.\\n- [ ] Pi resource smoke reports all manifest commands as prompt templates.\\n- [ ] Generated adapters include a Pi-native skill and prompt-template surface.\\n- [ ] Harness docs name Pi CLI/version, entrypoints, limitations, and smoke test.","status":"closed","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:51:48Z","created_by":"Andrei","updated_at":"2026-04-29T09:57:20Z","started_at":"2026-04-29T09:51:51Z","closed_at":"2026-04-29T09:57:20Z","close_reason":"duplicate of sdplab-1ily","labels":["F162","commands","harness","pi","sdp","skills"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-vd3r","title":"Review: init rewrites user manifest despite update semantics","description":"source=review; aspect=code-quality,ux; blocking=true; severity=major. cmd/sdp/cmd_init.go persists filtered harnesses by yaml.Marshal into target sdp.manifest.yaml, dropping comments/formatting and narrowing future generation scope while --update help says user-modified manifest is kept. Resolve selective-harness doctor semantics without silently rewriting user manifests, or require explicit manifest-write flag.","status":"closed","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T09:50:10Z","created_by":"Andrei","updated_at":"2026-04-29T11:50:04Z","started_at":"2026-04-29T11:14:20Z","closed_at":"2026-04-29T11:50:04Z","close_reason":"Merged in PR #145 (cb5470e): selective init preserves existing manifest","comments":[{"id":"019dd8f2-739b-7007-a15a-06a57ec62688","issue_id":"sdplab-vd3r","author":"Andrei","text":"Fixed in PR #145 commit pending push: sdp init no longer rewrites target sdp.manifest.yaml for selective --harness installs; .sdp/generated remains full manifest-derived cache. Added regression test TestInit_ExistingManifestPreservesUserManifestBytes and quality gates passed. Keep open until PR #145 merges.","created_at":"2026-04-29T11:14:21Z"},{"id":"019dd8fb-1c19-77b4-b021-5e199f2fe70e","issue_id":"sdplab-vd3r","author":"Andrei","text":"Pushed fix in commit b307ff75 on PR #145; CI required checks green. Issue remains in_progress until PR merge per repo policy.","created_at":"2026-04-29T11:23:49Z"}],"dependency_count":0,"dependent_count":0,"comment_count":2} @@ -556,16 +558,16 @@ {"_type":"issue","id":"sdplab-houh","title":"F036: Orchestrate + in-toto (historical placeholder)","description":"absorbed by F064–F067 auto-attestation","status":"closed","priority":4,"issue_type":"feature","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-18T11:27:58Z","created_by":"Andrei","updated_at":"2026-04-18T11:27:58Z","closed_at":"2026-04-18T11:27:58Z","close_reason":"absorbed by F064–F067 auto-attestation","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-crg","title":"F034: Graduated Enforcement (historical placeholder)","description":"historical placeholder; no active scope","status":"closed","priority":4,"issue_type":"feature","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-18T11:27:55Z","created_by":"Andrei","updated_at":"2026-04-18T11:27:56Z","closed_at":"2026-04-18T11:27:56Z","close_reason":"historical placeholder; no active scope","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-h5y","title":"F032: PR Evidence Summary (historical placeholder)","description":"historical placeholder; no active scope (2026-04-18 triage)","status":"closed","priority":4,"issue_type":"feature","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-18T11:27:34Z","created_by":"Andrei","updated_at":"2026-04-18T11:27:54Z","closed_at":"2026-04-18T11:27:54Z","close_reason":"historical placeholder; no active scope (2026-04-18 triage)","dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"memory","key":"f129-epic-created-2026-04-16-sdplab-8k5","value":"F129 epic created 2026-04-16 (sdplab-8k5 + 10 children sdplab-8k5.1..10 + 3 cross-lane: F106-07 sdplab-lqb, F124-05 sdplab-nai, F125-05 sdplab-as0). Addresses 5 audit findings: orchestration autonomy, subagent default, superpowers skill gap, doc drift, ad-hoc regression. Design: docs/plans/2026-04-16-f129-autonomy-regression-design.md. Cross-lane additions reuse existing F106/F124/F125 tracks instead of duplicating."} -{"_type":"memory","key":"f142-merged-2026-04-26","value":"F142 closed and merged 2026-04-26: feature/F142-workstream-coverage-gap pushed to origin (https://github.com/fall-out-bug/sdp_lab/tree/feature/F142-workstream-coverage-gap), local main merged with 7 ahead of origin/main (6 F142 commits + retains F141 merge f3ccef7). Ready for PR opening via GitHub UI when user wants. Branch can be deleted locally; remote branch retained for PR."} -{"_type":"memory","key":"f145-epic-created-2026-04-26","value":"F145 epic (sdplab-ldmq) created 2026-04-26: Multi-Provider Dispatch Matrix \u0026 Confidence-Driven Cascade. 14 children: WS01 sdplab-0261 (providers/ scaffold), WS02 sdplab-y93k (OpenAI), WS03 sdplab-w4ru (Anthropic), WS04 sdplab-bg69 (Cursor ~30 models), WS05 sdplab-60ia (Kimi), WS06 sdplab-5lve (Ollama replaces LocalConfig), WS07 sdplab-s686 (cursor/opencode --model plumbing P0 bug), WS08 sdplab-noiq (tier_class label), WS09 sdplab-3lva (profiles_default.json seed), WS10 sdplab-5ii8 (cascade pkg + Invoker), WS11 sdplab-fco9 (F144 confidence injection), WS12 sdplab-uamt (cascade-replay corpus), WS13 sdplab-135g (LimitsCache), WS14 sdplab-2p8g (smoke + Day-8 demo). Design: docs/plans/2026-04-26-f145-multi-provider-dispatch-cascade-design.md. Branch (planned): feature/F145-multi-provider-cascade. Discovery decisions: providers/ sub-pkg, cascade pkg, tier-label hybrid, composer gate default, UNSURE+FAIL+heuristic short-circuit, MaxDepth/Budget configurable, hybrid LimitsCache, F144 replay extension, harness-CLI keys (no SDP auth)."} {"_type":"memory","key":"f136-peer-memory-horizons-2026-04-20-epic","value":"f136-peer-memory-horizons-2026-04-20 | Epic F136 (sdplab-t1ty) created 2026-04-20: Peer Memory Foundation H1. Children: F136-01..06 (sdplab-r9py/lq4h/olvg/h25q/l0mm/h673). Parked: F137 H2 (sdplab-75u9, revisit 2026-07-06), F138 H3 (sdplab-7c45, revisit 2026-10-05). Design: docs/plans/2026-04-20-f136-peer-memory-h1-design.md. Market gap: peer-collaboration production framework where attribution is first-class (ChatCollab/HULA academic, no commercial product as of April 2026). H1 success = binary: did sdp memory query surface real lost context in 30-day dogfood. Label: peer-memory."} -{"_type":"memory","key":"f142-r3-done","value":"F142 round 3 closed via PR #123 squash to ff20222. F142-08 mass backfill 49 beads. F142-09 ghost cleanup. F142-10 picker defense. Local main out of sync, needs reset to origin."} +{"_type":"memory","key":"f142-closed-2026-04-26","value":"F142 epic (sdplab-85ji) closed 2026-04-26: Workstream coverage gap closed. 6 children closed (gkl5 audit, rqsw baits, s6g7 orphans, f2h0 picker, 3obq doctor, fb1o INDEX). Plus sdplab-8nkm picker incident closed. Branch feature/F142-workstream-coverage-gap, 5 commits not yet merged to main. Delivered: scripts/deliver-pick.sh v2 (skip leafless+design-pending), internal/backlog/audit.go + cmd/sdp/cmd_doctor_backlog.go ('sdp doctor backlog'), 6 ws scaffolds (00-082-01, 00-083-01, 00-084-01, 00-085-01, 00-101-02, 00-133-01), INDEX.md updated, CI gate. 0 findings on doctor backlog. 189 Go tests pass."} +{"_type":"memory","key":"f142-epic-2026-04-26","value":"F142 epic (sdplab-85ji) created 2026-04-26: Workstream coverage gap — picker + ws/INDEX backfill + doctor gate. Root cause: features created via /design bypass /feature autogen → 0 ws files → picker selects leafless feature → /build fails. Affected: F141 (0 ws), F135 (0 ws), F100/F76/F80 (partial). First child: sdplab-8nkm (picker fail incident on sdplab-nj4 F135-05). Future children: audit script, per-feature backfill, INDEX update, picker hardening, sdp doctor backlog gate."} +{"_type":"memory","key":"f142-merged-2026-04-26","value":"F142 closed and merged 2026-04-26: feature/F142-workstream-coverage-gap pushed to origin (https://github.com/fall-out-bug/sdp_lab/tree/feature/F142-workstream-coverage-gap), local main merged with 7 ahead of origin/main (6 F142 commits + retains F141 merge f3ccef7). Ready for PR opening via GitHub UI when user wants. Branch can be deleted locally; remote branch retained for PR."} {"_type":"memory","key":"f144-merged-2026-04-26","value":"F144 PR #131 MERGED to main at 2026-04-26T17:43Z, merge commit ed393e3. internal/inference/confidence/ live on main: Status enum, Result[T], Policy/Strategy/Checker, 3 strategies (constraint/selfcheck/nsample), 3 adapters (wsverdict/architect/dispatch). 111 tests, ≥85% coverage. Branch feature/F144-inference-confidence still exists locally. Replaces stale 'sdplab-8nkm-untracked-design-doc' status."} +{"_type":"memory","key":"f145-epic-created-2026-04-26","value":"F145 epic (sdplab-ldmq) created 2026-04-26: Multi-Provider Dispatch Matrix \u0026 Confidence-Driven Cascade. 14 children: WS01 sdplab-0261 (providers/ scaffold), WS02 sdplab-y93k (OpenAI), WS03 sdplab-w4ru (Anthropic), WS04 sdplab-bg69 (Cursor ~30 models), WS05 sdplab-60ia (Kimi), WS06 sdplab-5lve (Ollama replaces LocalConfig), WS07 sdplab-s686 (cursor/opencode --model plumbing P0 bug), WS08 sdplab-noiq (tier_class label), WS09 sdplab-3lva (profiles_default.json seed), WS10 sdplab-5ii8 (cascade pkg + Invoker), WS11 sdplab-fco9 (F144 confidence injection), WS12 sdplab-uamt (cascade-replay corpus), WS13 sdplab-135g (LimitsCache), WS14 sdplab-2p8g (smoke + Day-8 demo). Design: docs/plans/2026-04-26-f145-multi-provider-dispatch-cascade-design.md. Branch (planned): feature/F145-multi-provider-cascade. Discovery decisions: providers/ sub-pkg, cascade pkg, tier-label hybrid, composer gate default, UNSURE+FAIL+heuristic short-circuit, MaxDepth/Budget configurable, hybrid LimitsCache, F144 replay extension, harness-CLI keys (no SDP auth)."} +{"_type":"memory","key":"subagent-format-spec-2026-04-26","value":"Claude Code subagent file format (.claude/agents/*.md): YAML frontmatter MUST use 'tools: Read, Bash, Glob, Grep' COMMA-STRING format (PascalCase). NOT 'tools: { Read: true }' object form, NOT 'tools: [Read, Bash]' array. Source: docs.claude.com/docs/en/subagents.md lines 222-301. Wrong format → empty tool registry → agent fabricates XML \u003cfunction_calls\u003e in response.text (tool_uses=0). Verified via /tmp/probe diagnostic. Built-in agents (general-purpose, Explore, claude-code-guide) work because their registry is hardcoded, file-based ones go through the broken parsing path. PR #132 fixes 13 SDP custom agents. Note: Claude Code caches agent definitions at session start — fixes require restart to take effect, not just file save."} +{"_type":"memory","key":"f129-epic-created-2026-04-16-sdplab-8k5","value":"F129 epic created 2026-04-16 (sdplab-8k5 + 10 children sdplab-8k5.1..10 + 3 cross-lane: F106-07 sdplab-lqb, F124-05 sdplab-nai, F125-05 sdplab-as0). Addresses 5 audit findings: orchestration autonomy, subagent default, superpowers skill gap, doc drift, ad-hoc regression. Design: docs/plans/2026-04-16-f129-autonomy-regression-design.md. Cross-lane additions reuse existing F106/F124/F125 tracks instead of duplicating."} +{"_type":"memory","key":"subagent-implementer-broken-2026-04-26","value":"BROKEN: subagent_type=implementer fabricates tool_use. Confirmed via 3-test diagnostic 2026-04-26. T1 (implementer+sonnet): tool_uses=0, file not created, fabricated plausible output. T2 (general-purpose+haiku): tool_uses=1, file created. T3 (Explore+haiku): honest refusal (read-only). Pattern: implementer outputs old-format XML \u003cfunction_calls\u003e\u003cinvoke name='...'\u003e as response text instead of structured tool_use blocks. Affects ANY model. Workaround: use subagent_type=general-purpose for code implementation tasks. Affected sessions: F145 WS01/WS07/WS08 dispatch all fabricated, had to redo manually."} {"_type":"memory","key":"f141-epic-2026-04-25","value":"F141 epic (sdplab-o4sp) created 2026-04-25: Multi-harness install bootstrap \u0026 adapter parity. Children: F141-01 sdplab-fojs (manifest), -02 sdplab-6ea5 (generator), -03 sdplab-cl4o (bootstrap), -04 sdplab-i9q3 (doctor gate), -05 sdplab-r4y5 (parity matrix), -06 sdplab-cnpm (migration), -07 sdplab-nqjt (README). Closes gap left by F127/F128: downstream repos install SDP piecemeal. Approach: single sdp.manifest.yaml -\u003e adapter generator -\u003e curl|bash bootstrap -\u003e sdp doctor drift gate. Design: docs/plans/2026-04-25-f141-multi-harness-install-bootstrap-design.md. Branch: feature/F141-multi-harness-install-bootstrap."} -{"_type":"memory","key":"f142-closed-2026-04-26","value":"F142 epic (sdplab-85ji) closed 2026-04-26: Workstream coverage gap closed. 6 children closed (gkl5 audit, rqsw baits, s6g7 orphans, f2h0 picker, 3obq doctor, fb1o INDEX). Plus sdplab-8nkm picker incident closed. Branch feature/F142-workstream-coverage-gap, 5 commits not yet merged to main. Delivered: scripts/deliver-pick.sh v2 (skip leafless+design-pending), internal/backlog/audit.go + cmd/sdp/cmd_doctor_backlog.go ('sdp doctor backlog'), 6 ws scaffolds (00-082-01, 00-083-01, 00-084-01, 00-085-01, 00-101-02, 00-133-01), INDEX.md updated, CI gate. 0 findings on doctor backlog. 189 Go tests pass."} +{"_type":"memory","key":"f142-r3-done","value":"F142 round 3 closed via PR #123 squash to ff20222. F142-08 mass backfill 49 beads. F142-09 ghost cleanup. F142-10 picker defense. Local main out of sync, needs reset to origin."} {"_type":"memory","key":"f144-epic-created-2026-04-26-sdplab-sjdp","value":"F144 epic created 2026-04-26 (sdplab-sjdp): Inference Confidence \u0026 Quality Control. 8 children attached: 01 sdplab-wzit core lib, 02 sdplab-339w self-check, 03 sdplab-w7j1 n-sample, 04 sdplab-tfmg constraint+composer, 05 sdplab-kk1v ws-verdict adapter (P1), 06 sdplab-cvu3 architect adapter, 07 sdplab-s5x8 dispatch lite, 08 sdplab-kd38 replay+metrics. Critical path: 01→04→05→08. Plus 2 hygiene issues: sdplab-vuw4 (drift docs/design vs docs/plans), sdplab-bv3r (AGENTS.md design-first phrasing). Design: docs/plans/2026-04-26-f144-inference-confidence-design.md. Branch: feature/F144-inference-confidence (not yet created, untracked design doc on worktree-bulk-deliver branch)."} {"_type":"memory","key":"f145-progress-2026-04-26","value":"F145 epic in progress 2026-04-26. Branch: feature/F145-multi-provider-cascade (origin in sync). 3/14 WS committed (not yet closed in beads, awaiting merge): WS07 sdplab-s686 (cursor/opencode --model plumbing P0), WS01 sdplab-0261 (providers/ scaffold + tests), WS08 sdplab-noiq (tier_class + SelectTiers). Commits: 67f085b1 / 9ac769b4 / 30782519 on top of design 752140a3. After WS07/01/08 close, 6 WS unblock in parallel: WS02-06 (5 Provider impls) + WS13 LimitsCache. Then chain: WS09 seed → WS10 cascade pkg → WS11 confidence → WS12 replay → WS14 smoke+demo. 174 tests pass in internal/dispatch/. Next session: after PR #132 merge + restart, run @delivery-loop to dispatch remaining WS via fixed implementer subagents. F145 PR will open when epic is ready (or sensible mid-milestone)."} -{"_type":"memory","key":"subagent-implementer-broken-2026-04-26","value":"BROKEN: subagent_type=implementer fabricates tool_use. Confirmed via 3-test diagnostic 2026-04-26. T1 (implementer+sonnet): tool_uses=0, file not created, fabricated plausible output. T2 (general-purpose+haiku): tool_uses=1, file created. T3 (Explore+haiku): honest refusal (read-only). Pattern: implementer outputs old-format XML \u003cfunction_calls\u003e\u003cinvoke name='...'\u003e as response text instead of structured tool_use blocks. Affects ANY model. Workaround: use subagent_type=general-purpose for code implementation tasks. Affected sessions: F145 WS01/WS07/WS08 dispatch all fabricated, had to redo manually."} -{"_type":"memory","key":"f142-epic-2026-04-26","value":"F142 epic (sdplab-85ji) created 2026-04-26: Workstream coverage gap — picker + ws/INDEX backfill + doctor gate. Root cause: features created via /design bypass /feature autogen → 0 ws files → picker selects leafless feature → /build fails. Affected: F141 (0 ws), F135 (0 ws), F100/F76/F80 (partial). First child: sdplab-8nkm (picker fail incident on sdplab-nj4 F135-05). Future children: audit script, per-feature backfill, INDEX update, picker hardening, sdp doctor backlog gate."} -{"_type":"memory","key":"subagent-format-spec-2026-04-26","value":"Claude Code subagent file format (.claude/agents/*.md): YAML frontmatter MUST use 'tools: Read, Bash, Glob, Grep' COMMA-STRING format (PascalCase). NOT 'tools: { Read: true }' object form, NOT 'tools: [Read, Bash]' array. Source: docs.claude.com/docs/en/subagents.md lines 222-301. Wrong format → empty tool registry → agent fabricates XML \u003cfunction_calls\u003e in response.text (tool_uses=0). Verified via /tmp/probe diagnostic. Built-in agents (general-purpose, Explore, claude-code-guide) work because their registry is hardcoded, file-based ones go through the broken parsing path. PR #132 fixes 13 SDP custom agents. Note: Claude Code caches agent definitions at session start — fixes require restart to take effect, not just file save."} diff --git a/docs/reference/pi-review-spec.md b/docs/reference/pi-review-spec.md index 4151a0e9..aa838edb 100644 --- a/docs/reference/pi-review-spec.md +++ b/docs/reference/pi-review-spec.md @@ -97,6 +97,8 @@ Recommended slots: | `minimax` | independent implementation-risk review using `minimax/MiniMax-M2.7` | yes | | `openrouter-fallback` | only if ZAI, Kimi, or MiniMax fails or times out | no | +Clean verdict requires no `P0`/`P1` findings and a majority of required reviewer slots to complete. A timed-out provider is recorded in telemetry but does not block the branch when the remaining reviewer quorum is clean. + ## Finding Contract Findings use SDP review priorities: diff --git a/internal/pireview/runner.go b/internal/pireview/runner.go index 872bd1ab..5795fb25 100644 --- a/internal/pireview/runner.go +++ b/internal/pireview/runner.go @@ -4,6 +4,7 @@ import ( "context" "crypto/sha256" "encoding/json" + "errors" "fmt" "os" "path/filepath" @@ -157,6 +158,10 @@ func (r *Runner) Run(ctx context.Context) (*ReviewRun, *Verdict, error) { } runID := fmt.Sprintf("pireview-%s-%d", hashString(pkt.Branch + pkt.UnifiedDiff)[:12], time.Now().UnixMilli()) + runDir := filepath.Join(r.cfg.ProjectRoot, ".sdp", "runs", "pi-review", runID) + if err := os.MkdirAll(runDir, 0o755); err != nil { + return nil, nil, fmt.Errorf("run %s: mkdir: %w", runID, err) + } // Collect test evidence evidence, err := CollectTestEvidence(ctx, r.cfg) @@ -212,10 +217,6 @@ func (r *Runner) Run(ctx context.Context) (*ReviewRun, *Verdict, error) { } // Persist context and evidence artifacts - runDir := filepath.Join(r.cfg.ProjectRoot, ".sdp", "runs", "pi-review", runID) - if err := os.MkdirAll(runDir, 0o755); err != nil { - return nil, nil, fmt.Errorf("run %s: mkdir: %w", runID, err) - } ctxJSON, err := json.MarshalIndent(pkt, "", " ") if err != nil { return nil, nil, fmt.Errorf("run %s: marshal context: %w", runID, err) @@ -268,13 +269,17 @@ func (r *Runner) runModelPanel(ctx context.Context, runID string, pkt *ContextPa if err != nil { result.Status = "failed" result.Error = err.Error() - result.ArtifactPath = fmt.Sprintf(".sdp/runs/pi-review/%s/models/%s.json", runID, slot.Slot) - if slot.Required { + result.ArtifactPath = modelArtifactPath(r.cfg.ProjectRoot, runID, slot.Slot) + if slot.Required && !errors.Is(err, context.DeadlineExceeded) { // Try OpenRouter fallback for required slots fbOutput, fbErr := r.invokeFallback(ctx, slot, pkt, evidence) if fbErr == nil { result.Status = "ok" result.ArtifactPath = writeModelArtifact(r.cfg.ProjectRoot, runID, slot.Slot, fbOutput) + result.Provider = "openrouter" + result.Model = fallbackModel(slot) + } else { + result.Error = fmt.Sprintf("%s; fallback failed: %v", result.Error, fbErr) } } } else { @@ -282,7 +287,7 @@ func (r *Runner) runModelPanel(ctx context.Context, runID string, pkt *ContextPa if artifactPath == "" { result.Status = "failed" result.Error = "artifact write failed" - result.ArtifactPath = fmt.Sprintf(".sdp/runs/pi-review/%s/models/%s.json", runID, slot.Slot) + result.ArtifactPath = modelArtifactPath(r.cfg.ProjectRoot, runID, slot.Slot) } else { result.Status = "ok" result.ArtifactPath = artifactPath @@ -503,10 +508,14 @@ func buildVerdict(feature string, round int, findings []Finding, models []ModelR } v.P0Count = p0 v.P1Count = p1 + requiredQuorum := requiredTotal + if requiredTotal > 2 { + requiredQuorum = (requiredTotal / 2) + 1 + } if p0 > 0 || p1 > 0 { v.Verdict = "CHANGES_REQUESTED" - } else if requiredOK < requiredTotal { + } else if requiredOK < requiredQuorum { v.Verdict = "ESCALATED" } else { v.Verdict = "APPROVED" @@ -531,15 +540,15 @@ func buildVerdict(feature string, round int, findings []Finding, models []ModelR Notes: fmt.Sprintf("pi-review found %d P0 and %d P1 issues", p0, p1), } v.Summary = fmt.Sprintf("CHANGES_REQUESTED: %d P0, %d P1, %d total findings", p0, p1, len(findings)) - } else if requiredOK < requiredTotal { + } else if requiredOK < requiredQuorum { v.Reviewers["qa"] = RoleResult{ Verdict: "BLOCKED", Findings: []string{}, - Notes: fmt.Sprintf("quorum failure: %d/%d required reviewers succeeded", requiredOK, requiredTotal), + Notes: fmt.Sprintf("quorum failure: %d/%d required reviewers succeeded; quorum=%d", requiredOK, requiredTotal, requiredQuorum), } - v.Summary = fmt.Sprintf("ESCALATED: quorum failure (%d/%d required reviewers)", requiredOK, requiredTotal) + v.Summary = fmt.Sprintf("ESCALATED: quorum failure (%d/%d required reviewers; quorum=%d)", requiredOK, requiredTotal, requiredQuorum) } else { - v.Summary = fmt.Sprintf("APPROVED: %d advisory findings (P2/P3)", len(findings)) + v.Summary = fmt.Sprintf("APPROVED: %d advisory findings (P2/P3), reviewer quorum %d/%d", len(findings), requiredOK, requiredTotal) } return v @@ -547,17 +556,21 @@ func buildVerdict(feature string, round int, findings []Finding, models []ModelR // writeModelArtifact persists raw model output to disk. func writeModelArtifact(projectRoot, runID, slot string, output string) string { - dir := filepath.Join(projectRoot, ".sdp", "runs", "pi-review", runID, "models") + path := modelArtifactPath(projectRoot, runID, slot) + dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0o755); err != nil { return "" } - path := filepath.Join(dir, slot+".json") if err := os.WriteFile(path, []byte(output), 0o644); err != nil { return "" } return path } +func modelArtifactPath(projectRoot, runID, slot string) string { + return filepath.Join(projectRoot, ".sdp", "runs", "pi-review", runID, "models", slot+".json") +} + // hashString returns SHA-256 hex of a string. func hashString(s string) string { h := sha256.Sum256([]byte(s)) diff --git a/internal/pireview/runner_test.go b/internal/pireview/runner_test.go index a4f76a23..014ae34d 100644 --- a/internal/pireview/runner_test.go +++ b/internal/pireview/runner_test.go @@ -147,6 +147,13 @@ func TestBuildVerdict_PartialQuorum_Escalated(t *testing.T) { } } +func TestBuildVerdict_MajorityQuorumApproved(t *testing.T) { + verdict := buildVerdict("F161", 1, nil, nil, &ContextPacket{}, 2, 3) + if verdict.Verdict != "APPROVED" { + t.Errorf("Verdict = %q, want APPROVED when 2/3 required reviewers succeed and no P0/P1 findings", verdict.Verdict) + } +} + func TestBuildVerdict_SevenRoles(t *testing.T) { verdict := buildVerdict("F161", 1, nil, nil, &ContextPacket{}, 2, 2) roles := []string{"qa", "security", "devops", "sre", "techlead", "docs", "promptops"} diff --git a/scripts/run_smoke_tests.sh b/scripts/run_smoke_tests.sh index ec869aeb..8ae7bba8 100755 --- a/scripts/run_smoke_tests.sh +++ b/scripts/run_smoke_tests.sh @@ -23,7 +23,9 @@ set +e go test -tags=smoke ./test/smoke/... -v -json > "$RAW_FILE" 2>&1 SMOKE_EXIT=$? set -e -cat "$RAW_FILE" +if [[ "$JSON_OUTPUT" != "1" ]]; then + cat "$RAW_FILE" +fi END_TS=$(date -u +%Y-%m-%dT%H:%M:%SZ) From 85375c3e18c41854b17111067db93bb1d6cfa4c4 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 29 Apr 2026 18:30:21 +0300 Subject: [PATCH 06/11] fix: pass pi review prompts by file --- internal/pireview/runner.go | 33 ++++++++++++++++++++++++++++++-- internal/pireview/runner_test.go | 3 +++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/internal/pireview/runner.go b/internal/pireview/runner.go index 5795fb25..ec166f78 100644 --- a/internal/pireview/runner.go +++ b/internal/pireview/runner.go @@ -303,11 +303,16 @@ func (r *Runner) runModelPanel(ctx context.Context, runID string, pkt *ContextPa // invokePi calls the local pi binary with the review context. func (r *Runner) invokePi(ctx context.Context, slot ReviewerSlot, pkt *ContextPacket, evidence *TestEvidence) (string, error) { reviewPrompt := buildReviewPrompt(slot, pkt, evidence) + promptArg, cleanup, err := writeTempPrompt(reviewPrompt) + if err != nil { + return "", err + } + defer cleanup() modelCtx, cancel := context.WithTimeout(ctx, r.cfg.effectiveModelTimeout()) defer cancel() out, err := r.runner.CombinedOutput(modelCtx, r.cfg.ProjectRoot, "pi", "--provider", slot.Provider, "--model", slot.Model, - "--no-tools", "--no-context-files", "--no-session", "-p", reviewPrompt) + "--no-tools", "--no-context-files", "--no-session", "-p", promptArg) if err != nil { return "", fmt.Errorf("pi run %s/%s: %w", slot.Provider, slot.Model, err) } @@ -317,17 +322,41 @@ func (r *Runner) invokePi(ctx context.Context, slot ReviewerSlot, pkt *ContextPa // invokeFallback attempts OpenRouter when a primary provider fails. func (r *Runner) invokeFallback(ctx context.Context, slot ReviewerSlot, pkt *ContextPacket, evidence *TestEvidence) (string, error) { reviewPrompt := buildReviewPrompt(slot, pkt, evidence) + promptArg, cleanup, err := writeTempPrompt(reviewPrompt) + if err != nil { + return "", err + } + defer cleanup() modelCtx, cancel := context.WithTimeout(ctx, r.cfg.effectiveModelTimeout()) defer cancel() out, err := r.runner.CombinedOutput(modelCtx, r.cfg.ProjectRoot, "pi", "--provider", "openrouter", "--model", fallbackModel(slot), - "--no-tools", "--no-context-files", "--no-session", "-p", reviewPrompt) + "--no-tools", "--no-context-files", "--no-session", "-p", promptArg) if err != nil { return "", fmt.Errorf("openrouter fallback: %w", err) } return string(out), nil } +func writeTempPrompt(prompt string) (string, func(), error) { + f, err := os.CreateTemp("", "sdp-pi-review-*.md") + if err != nil { + return "", func() {}, fmt.Errorf("write pi prompt temp file: %w", err) + } + path := f.Name() + cleanup := func() { _ = os.Remove(path) } + if _, err := f.WriteString(prompt); err != nil { + _ = f.Close() + cleanup() + return "", func() {}, fmt.Errorf("write pi prompt temp file: %w", err) + } + if err := f.Close(); err != nil { + cleanup() + return "", func() {}, fmt.Errorf("close pi prompt temp file: %w", err) + } + return "@" + path, cleanup, nil +} + func fallbackModel(slot ReviewerSlot) string { switch slot.Slot { case "zai": diff --git a/internal/pireview/runner_test.go b/internal/pireview/runner_test.go index 014ae34d..12432f5f 100644 --- a/internal/pireview/runner_test.go +++ b/internal/pireview/runner_test.go @@ -315,6 +315,9 @@ func TestInvokePiUsesPiPrintContract(t *testing.T) { t.Fatalf("pi args missing %q: %v", want, call.args) } } + if gotPromptArg := call.args[len(call.args)-1]; !strings.HasPrefix(gotPromptArg, "@/") { + t.Fatalf("pi prompt should be passed as @file, got %q", gotPromptArg) + } if len(call.args) > 0 && call.args[0] == "run" { t.Fatalf("pi invocation must not use removed run subcommand: %v", call.args) } From e86dbd436574bc7a74c29147a16f4cab8dd3111c Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 29 Apr 2026 18:42:54 +0300 Subject: [PATCH 07/11] Revert "fix: pass pi review prompts by file" This reverts commit 85375c3e18c41854b17111067db93bb1d6cfa4c4. --- internal/pireview/runner.go | 33 ++------------------------------ internal/pireview/runner_test.go | 3 --- 2 files changed, 2 insertions(+), 34 deletions(-) diff --git a/internal/pireview/runner.go b/internal/pireview/runner.go index ec166f78..5795fb25 100644 --- a/internal/pireview/runner.go +++ b/internal/pireview/runner.go @@ -303,16 +303,11 @@ func (r *Runner) runModelPanel(ctx context.Context, runID string, pkt *ContextPa // invokePi calls the local pi binary with the review context. func (r *Runner) invokePi(ctx context.Context, slot ReviewerSlot, pkt *ContextPacket, evidence *TestEvidence) (string, error) { reviewPrompt := buildReviewPrompt(slot, pkt, evidence) - promptArg, cleanup, err := writeTempPrompt(reviewPrompt) - if err != nil { - return "", err - } - defer cleanup() modelCtx, cancel := context.WithTimeout(ctx, r.cfg.effectiveModelTimeout()) defer cancel() out, err := r.runner.CombinedOutput(modelCtx, r.cfg.ProjectRoot, "pi", "--provider", slot.Provider, "--model", slot.Model, - "--no-tools", "--no-context-files", "--no-session", "-p", promptArg) + "--no-tools", "--no-context-files", "--no-session", "-p", reviewPrompt) if err != nil { return "", fmt.Errorf("pi run %s/%s: %w", slot.Provider, slot.Model, err) } @@ -322,41 +317,17 @@ func (r *Runner) invokePi(ctx context.Context, slot ReviewerSlot, pkt *ContextPa // invokeFallback attempts OpenRouter when a primary provider fails. func (r *Runner) invokeFallback(ctx context.Context, slot ReviewerSlot, pkt *ContextPacket, evidence *TestEvidence) (string, error) { reviewPrompt := buildReviewPrompt(slot, pkt, evidence) - promptArg, cleanup, err := writeTempPrompt(reviewPrompt) - if err != nil { - return "", err - } - defer cleanup() modelCtx, cancel := context.WithTimeout(ctx, r.cfg.effectiveModelTimeout()) defer cancel() out, err := r.runner.CombinedOutput(modelCtx, r.cfg.ProjectRoot, "pi", "--provider", "openrouter", "--model", fallbackModel(slot), - "--no-tools", "--no-context-files", "--no-session", "-p", promptArg) + "--no-tools", "--no-context-files", "--no-session", "-p", reviewPrompt) if err != nil { return "", fmt.Errorf("openrouter fallback: %w", err) } return string(out), nil } -func writeTempPrompt(prompt string) (string, func(), error) { - f, err := os.CreateTemp("", "sdp-pi-review-*.md") - if err != nil { - return "", func() {}, fmt.Errorf("write pi prompt temp file: %w", err) - } - path := f.Name() - cleanup := func() { _ = os.Remove(path) } - if _, err := f.WriteString(prompt); err != nil { - _ = f.Close() - cleanup() - return "", func() {}, fmt.Errorf("write pi prompt temp file: %w", err) - } - if err := f.Close(); err != nil { - cleanup() - return "", func() {}, fmt.Errorf("close pi prompt temp file: %w", err) - } - return "@" + path, cleanup, nil -} - func fallbackModel(slot ReviewerSlot) string { switch slot.Slot { case "zai": diff --git a/internal/pireview/runner_test.go b/internal/pireview/runner_test.go index 12432f5f..014ae34d 100644 --- a/internal/pireview/runner_test.go +++ b/internal/pireview/runner_test.go @@ -315,9 +315,6 @@ func TestInvokePiUsesPiPrintContract(t *testing.T) { t.Fatalf("pi args missing %q: %v", want, call.args) } } - if gotPromptArg := call.args[len(call.args)-1]; !strings.HasPrefix(gotPromptArg, "@/") { - t.Fatalf("pi prompt should be passed as @file, got %q", gotPromptArg) - } if len(call.args) > 0 && call.args[0] == "run" { t.Fatalf("pi invocation must not use removed run subcommand: %v", call.args) } From 161861d175c6b0fc836d7aeade8faea2158d6f00 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 29 Apr 2026 19:06:41 +0300 Subject: [PATCH 08/11] fix: bootstrap beads for doctor gate --- .beads/issues.jsonl | 19 ++++----- .github/workflows/sdp-doctor.yml | 9 +++++ cmd/sdp/cmd_doctor_backlog.go | 65 +++++++++++++++++++++++++++++- cmd/sdp/cmd_doctor_backlog_test.go | 54 +++++++++++++++++++++++++ 4 files changed, 137 insertions(+), 10 deletions(-) create mode 100644 cmd/sdp/cmd_doctor_backlog_test.go diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 180e0500..af17375d 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -35,6 +35,7 @@ {"_type":"issue","id":"sdplab-22","title":"F100-01: Reference Integrity CI Gate","status":"closed","priority":0,"issue_type":"task","assignee":"Andrei","created_at":"2026-04-05T15:00:00Z","created_by":"UX Council","updated_at":"2026-04-26T13:30:49Z","closed_at":"2026-04-26T13:30:49Z","close_reason":"F100 Reference Integrity complete: CI gate + one-time cleanup. 56 tests passing.","labels":["F100","P0","harness:claude","stream-C","ux"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-23","title":"F100-02: One-Time Reference Cleanup","status":"closed","priority":0,"issue_type":"task","assignee":"Andrei","created_at":"2026-04-05T15:00:00Z","created_by":"UX Council","updated_at":"2026-04-26T13:30:50Z","closed_at":"2026-04-26T13:30:50Z","close_reason":"F100 Reference Integrity complete: CI gate + one-time cleanup. 56 tests passing.","labels":["F100","P0","harness:any","stream-C","ux"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-24","title":"F101-01: Write Plan Emission and Confirmation","status":"closed","priority":0,"issue_type":"feature","assignee":"Andrei","created_at":"2026-04-05T15:00:00Z","created_by":"UX Council","updated_at":"2026-04-19T07:33:30Z","closed_at":"2026-04-19T07:33:30Z","close_reason":"F101-01 merged to main via PR #95 after sdp PR #130 landed on sdp/main. Write Plan prompt surface is now shipped for the nine stateful skills.","labels":["F101","P0","harness:any","stream-B","ux"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-5dj9","title":"CI doctor backlog gate lacks Beads bootstrap","description":"PR #144 made sdp doctor backlog blocking in GitHub Actions, but .github/workflows/sdp-doctor.yml does not install bd or bootstrap the Beads database in a fresh checkout. Doctor job fails with: bd not found on PATH.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T15:57:53Z","created_by":"Andrei","updated_at":"2026-04-29T15:58:00Z","started_at":"2026-04-29T15:58:00Z","comments":[{"id":"019dd9fb-545d-7153-a2db-6db1c1821645","issue_id":"sdplab-5dj9","author":"Andrei","text":"Fixed in branch: sdp-doctor workflow now installs bd v1.0.3 and runs bd bootstrap --yes before backlog doctor. Also fixed sdp doctor backlog to count parent-child children from child entries instead of parent dependency_count; clean clone simulation passes manifest, doctor adapters, and doctor backlog.","created_at":"2026-04-29T16:03:40Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1} {"_type":"issue","id":"sdplab-i8xo","title":"pi-review P1: Bootstrap dry-run writes to target repo","description":"File: cmd/sdp/cmd_bootstrap.go:84-87\nRationale: appendGreenfieldArtifacts and appendBrownfieldArtifacts call writeDraftArtifact without passing dryRun, so bootstrap --dry-run --mode greenfield|brownfield still writes DRAFT-* files to the target repo, contradicting docs/QUICKSTART.md which promises bootstrap --dry-run is read-only.\nSuggested fix: Pass dryRun to both appendGreenfieldArtifacts and appendBrownfieldArtifacts, and thread it through to writeDraftArtifact (already done for greenfield/answers save). Verify TestBootstrapDryRunBrownfieldDoesNotWriteDraftDelta passes.\nDedupe key: P1:cmd/sdp/cmd_bootstrap.go:Bootstrap dry-run writes to target repo","status":"closed","priority":1,"issue_type":"bug","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T14:55:28Z","created_by":"Andrei","updated_at":"2026-04-29T14:56:20Z","closed_at":"2026-04-29T14:56:20Z","close_reason":"false positive: cmd/sdp/cmd_bootstrap.go already passes dryRun into appendGreenfieldArtifacts/appendBrownfieldArtifacts and writeDraftArtifact; TestBootstrapDryRunBrownfieldDoesNotWriteDraftDelta verifies no DRAFT-bootstrap-delta.json is written","labels":["F150","pi-review","review-finding","round-7"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-w1g1","title":"pi-review P1: synthesizeFindings reads artifacts by absolute path but Runner.Run does not persist models before synthesis","description":"File: internal/pireview/runner.go:322-340\nRationale: runModelPanel calls writeModelArtifact which returns an absolute path on success. synthesizeFindings then reads ModelResult.ArtifactPath via os.ReadFile. However, if writeModelArtifact fails (returns empty string), the code sets result.ArtifactPath to a planned-but-unwritten path like '.sdp/runs/pi-review/{runID}/models/{slot}.json'. When synthesis runs, it will try to read this relative path from CWD, not the project root, causing a silent miss. More critically: in Run(), modelResults are returned from runModelPanel, then synthesizeFindings(modelResults) is called BEFORE the run directory is created at line ~265. The model artifacts ARE written inside runModelPanel, but the context/evidence artifacts written later in Run() share the same runDir. This ordering is correct for models but means if MkdirAll fails at line ~265, the model artifacts are already on disk but the run context is lost.\nSuggested fix: Consider either: (1) moving the run directory creation (MkdirAll) before runModelPanel so all artifacts share the same guaranteed directory, or (2) documenting that model artifacts are written eagerly. Also fix the empty-string artifact path case to not set a path at all.\nDedupe key: P1:internal/pireview/runner.go:synthesizeFindings reads artifacts by absolute path but Runner.Run does not persist models before synthesis","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T14:55:24Z","created_by":"Andrei","updated_at":"2026-04-29T14:56:27Z","started_at":"2026-04-29T14:56:27Z","labels":["F150","pi-review","review-finding","round-7"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-ljtr","title":"pi-review P1: run_smoke_tests.sh pipes JSON through tee without preserving exit code","description":"File: scripts/run_smoke_tests.sh:20-23\nRationale: The line 'go test -tags=smoke ./test/smoke/... -v -json 2\u003e\u00261 | tee /tmp/sdp-smoke-raw.json' runs inside 'set +e', and PIPESTATUS[0] captures the go test exit code. However, 'set +e' is set AFTER 'set -euo pipefail' at the top, and the PIPESTATUS fallback '${PIPESTATUS[0]:-$?}' means if PIPESTATUS is not available (e.g., non-bash shells), $? reflects the last command in the pipeline (tee), which always succeeds. The shebang is #!/usr/bin/env bash so PIPESTATUS should be available, but the fallback masks this class of bug.\nSuggested fix: Replace the pipeline with process substitution or use a temporary file approach: run go test redirecting to file, then cat the file, avoiding the pipeline exit-code problem entirely. The current approach works in bash but the pattern is fragile.\nDedupe key: P1:scripts/run_smoke_tests.sh:run_smoke_tests.sh pipes JSON through tee without preserving exit code","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T14:10:48Z","created_by":"Andrei","updated_at":"2026-04-29T14:11:46Z","started_at":"2026-04-29T14:11:46Z","labels":["F150","pi-review","review-finding","round-4"],"dependency_count":0,"dependent_count":0,"comment_count":0} @@ -306,7 +307,7 @@ {"_type":"issue","id":"sdplab-mbhg.2","title":"F153-02: Naming policy","description":"When `sdp-` prefix is required vs forbidden. Working-name rename criteria. Display-vs-internal namespace decoupling. Naming rules for Toolbox tools and IIP candidates. WS: 00-153-02.","status":"in_progress","priority":2,"issue_type":"task","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-27T13:03:58Z","created_by":"Andrei","updated_at":"2026-04-27T14:20:53Z","started_at":"2026-04-27T14:20:53Z","dependencies":[{"issue_id":"sdplab-mbhg.2","depends_on_id":"sdplab-mbhg","type":"parent-child","created_at":"2026-04-27T16:03:58Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-mbhg.1","title":"F153-01: Brand family map artifact","description":"One-page map showing Lab / Toolbox (with IIP flag) / Toolkit / Operator Mode / ChangePassport (display) | sdp-pr-gate (internal) / Enterprise Delivery Governance / Shared Substrates. Names, target audience, paid status. Output: docs/strategy/sdp-brand-architecture.md. WS: 00-153-01.","status":"in_progress","priority":2,"issue_type":"task","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-27T13:03:53Z","created_by":"Andrei","updated_at":"2026-04-27T14:20:52Z","started_at":"2026-04-27T14:20:52Z","dependencies":[{"issue_id":"sdplab-mbhg.1","depends_on_id":"sdplab-mbhg","type":"parent-child","created_at":"2026-04-27T16:03:53Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-mbhg","title":"F153: SDP Brand Architecture","description":"Resolve SDP brand family confusion (Lab / Toolbox / Toolkit / Operator / ChangePassport / EDG / IIPs). Required before first external launch (memo-council Philosopher; IIP-council all roles flagged Toolbox-as-funnel framing). Independent of F150 closure; can start now.","status":"in_progress","priority":2,"issue_type":"epic","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-27T13:03:49Z","created_by":"Andrei","updated_at":"2026-04-27T14:17:45Z","started_at":"2026-04-27T14:17:45Z","dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"sdplab-qnyr.3","title":"F152-03: ≥3 discovery interviews with target ICP","description":"Boutique consulting / agency / fractional CTO with 10-50 engineers and ≥8 AI-assisted PRs/week. Interview script: review-burden quantification, current AI-PR governance pain, willingness-to-pay ranges, competitive alternatives. Document each in commercial-discovery/\u003cdate\u003e-\u003corg\u003e.md. WS: 00-152-03.","status":"open","priority":2,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-27T13:03:46Z","created_by":"Andrei","updated_at":"2026-04-27T13:03:46Z","dependencies":[{"issue_id":"sdplab-qnyr.3","depends_on_id":"sdplab-qnyr","type":"parent-child","created_at":"2026-04-27T16:03:45Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-qnyr.3","title":"F152-03: ≥3 discovery interviews with target ICP","description":"Boutique consulting / agency / fractional CTO with 10-50 engineers and ≥8 AI-assisted PRs/week. Interview script: review-burden quantification, current AI-PR governance pain, willingness-to-pay ranges, competitive alternatives. Document each in commercial-discovery/\u003cdate\u003e-\u003corg\u003e.md. WS: 00-152-03.","status":"in_progress","priority":2,"issue_type":"task","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-27T13:03:46Z","created_by":"Andrei","updated_at":"2026-04-29T16:05:54Z","started_at":"2026-04-29T16:05:54Z","dependencies":[{"issue_id":"sdplab-qnyr.3","depends_on_id":"sdplab-qnyr","type":"parent-child","created_at":"2026-04-27T16:03:45Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-qnyr.2","title":"F152-02: sdp-pr-gate pricing hypothesis doc","description":"Per-active-repo per-month base; included monthly governed-decision volume; overage by decision; expansion path. Reference manifesto v2 §Packaging Hypothesis. WS: 00-152-02.","status":"open","priority":2,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-27T13:03:41Z","created_by":"Andrei","updated_at":"2026-04-27T13:03:41Z","dependencies":[{"issue_id":"sdplab-qnyr.2","depends_on_id":"sdplab-qnyr","type":"parent-child","created_at":"2026-04-27T16:03:41Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-qnyr.1","title":"F152-01: Operator Mode pricing hypothesis doc","description":"Per-active-repo or per-team monthly base; included evidence/workstream volume; expansion path Operator → ChangePassport → EDG; comparable references (Tabnine, GitLab Duo, CodeRabbit). WS: 00-152-01.","status":"open","priority":2,"issue_type":"task","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-27T13:03:37Z","created_by":"Andrei","updated_at":"2026-04-27T13:03:37Z","dependencies":[{"issue_id":"sdplab-qnyr.1","depends_on_id":"sdplab-qnyr","type":"parent-child","created_at":"2026-04-27T16:03:37Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-qnyr","title":"F152: Pricing Hypothesis (Operator Mode + sdp-pr-gate)","description":"Provisional pricing hypothesis required before any external pilot per IIP-council Pragmatist. Not a commitment; it is the measurement instrument that lets a pilot answer 'does the buyer value this enough to pay'. Includes ≥3 discovery interviews. Memo v3 §Operator Mode and §Wedge Ordering.","status":"open","priority":2,"issue_type":"epic","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-27T13:03:32Z","created_by":"Andrei","updated_at":"2026-04-27T13:03:32Z","dependencies":[{"issue_id":"sdplab-qnyr","depends_on_id":"sdplab-hfk0","type":"blocks","created_at":"2026-04-27T16:06:07Z","created_by":"Andrei","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0} @@ -558,16 +559,16 @@ {"_type":"issue","id":"sdplab-houh","title":"F036: Orchestrate + in-toto (historical placeholder)","description":"absorbed by F064–F067 auto-attestation","status":"closed","priority":4,"issue_type":"feature","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-18T11:27:58Z","created_by":"Andrei","updated_at":"2026-04-18T11:27:58Z","closed_at":"2026-04-18T11:27:58Z","close_reason":"absorbed by F064–F067 auto-attestation","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-crg","title":"F034: Graduated Enforcement (historical placeholder)","description":"historical placeholder; no active scope","status":"closed","priority":4,"issue_type":"feature","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-18T11:27:55Z","created_by":"Andrei","updated_at":"2026-04-18T11:27:56Z","closed_at":"2026-04-18T11:27:56Z","close_reason":"historical placeholder; no active scope","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-h5y","title":"F032: PR Evidence Summary (historical placeholder)","description":"historical placeholder; no active scope (2026-04-18 triage)","status":"closed","priority":4,"issue_type":"feature","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-18T11:27:34Z","created_by":"Andrei","updated_at":"2026-04-18T11:27:54Z","closed_at":"2026-04-18T11:27:54Z","close_reason":"historical placeholder; no active scope (2026-04-18 triage)","dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"memory","key":"f136-peer-memory-horizons-2026-04-20-epic","value":"f136-peer-memory-horizons-2026-04-20 | Epic F136 (sdplab-t1ty) created 2026-04-20: Peer Memory Foundation H1. Children: F136-01..06 (sdplab-r9py/lq4h/olvg/h25q/l0mm/h673). Parked: F137 H2 (sdplab-75u9, revisit 2026-07-06), F138 H3 (sdplab-7c45, revisit 2026-10-05). Design: docs/plans/2026-04-20-f136-peer-memory-h1-design.md. Market gap: peer-collaboration production framework where attribution is first-class (ChatCollab/HULA academic, no commercial product as of April 2026). H1 success = binary: did sdp memory query surface real lost context in 30-day dogfood. Label: peer-memory."} -{"_type":"memory","key":"f142-closed-2026-04-26","value":"F142 epic (sdplab-85ji) closed 2026-04-26: Workstream coverage gap closed. 6 children closed (gkl5 audit, rqsw baits, s6g7 orphans, f2h0 picker, 3obq doctor, fb1o INDEX). Plus sdplab-8nkm picker incident closed. Branch feature/F142-workstream-coverage-gap, 5 commits not yet merged to main. Delivered: scripts/deliver-pick.sh v2 (skip leafless+design-pending), internal/backlog/audit.go + cmd/sdp/cmd_doctor_backlog.go ('sdp doctor backlog'), 6 ws scaffolds (00-082-01, 00-083-01, 00-084-01, 00-085-01, 00-101-02, 00-133-01), INDEX.md updated, CI gate. 0 findings on doctor backlog. 189 Go tests pass."} -{"_type":"memory","key":"f142-epic-2026-04-26","value":"F142 epic (sdplab-85ji) created 2026-04-26: Workstream coverage gap — picker + ws/INDEX backfill + doctor gate. Root cause: features created via /design bypass /feature autogen → 0 ws files → picker selects leafless feature → /build fails. Affected: F141 (0 ws), F135 (0 ws), F100/F76/F80 (partial). First child: sdplab-8nkm (picker fail incident on sdplab-nj4 F135-05). Future children: audit script, per-feature backfill, INDEX update, picker hardening, sdp doctor backlog gate."} -{"_type":"memory","key":"f142-merged-2026-04-26","value":"F142 closed and merged 2026-04-26: feature/F142-workstream-coverage-gap pushed to origin (https://github.com/fall-out-bug/sdp_lab/tree/feature/F142-workstream-coverage-gap), local main merged with 7 ahead of origin/main (6 F142 commits + retains F141 merge f3ccef7). Ready for PR opening via GitHub UI when user wants. Branch can be deleted locally; remote branch retained for PR."} +{"_type":"memory","key":"f142-r3-done","value":"F142 round 3 closed via PR #123 squash to ff20222. F142-08 mass backfill 49 beads. F142-09 ghost cleanup. F142-10 picker defense. Local main out of sync, needs reset to origin."} {"_type":"memory","key":"f144-merged-2026-04-26","value":"F144 PR #131 MERGED to main at 2026-04-26T17:43Z, merge commit ed393e3. internal/inference/confidence/ live on main: Status enum, Result[T], Policy/Strategy/Checker, 3 strategies (constraint/selfcheck/nsample), 3 adapters (wsverdict/architect/dispatch). 111 tests, ≥85% coverage. Branch feature/F144-inference-confidence still exists locally. Replaces stale 'sdplab-8nkm-untracked-design-doc' status."} {"_type":"memory","key":"f145-epic-created-2026-04-26","value":"F145 epic (sdplab-ldmq) created 2026-04-26: Multi-Provider Dispatch Matrix \u0026 Confidence-Driven Cascade. 14 children: WS01 sdplab-0261 (providers/ scaffold), WS02 sdplab-y93k (OpenAI), WS03 sdplab-w4ru (Anthropic), WS04 sdplab-bg69 (Cursor ~30 models), WS05 sdplab-60ia (Kimi), WS06 sdplab-5lve (Ollama replaces LocalConfig), WS07 sdplab-s686 (cursor/opencode --model plumbing P0 bug), WS08 sdplab-noiq (tier_class label), WS09 sdplab-3lva (profiles_default.json seed), WS10 sdplab-5ii8 (cascade pkg + Invoker), WS11 sdplab-fco9 (F144 confidence injection), WS12 sdplab-uamt (cascade-replay corpus), WS13 sdplab-135g (LimitsCache), WS14 sdplab-2p8g (smoke + Day-8 demo). Design: docs/plans/2026-04-26-f145-multi-provider-dispatch-cascade-design.md. Branch (planned): feature/F145-multi-provider-cascade. Discovery decisions: providers/ sub-pkg, cascade pkg, tier-label hybrid, composer gate default, UNSURE+FAIL+heuristic short-circuit, MaxDepth/Budget configurable, hybrid LimitsCache, F144 replay extension, harness-CLI keys (no SDP auth)."} +{"_type":"memory","key":"f145-progress-2026-04-26","value":"F145 epic in progress 2026-04-26. Branch: feature/F145-multi-provider-cascade (origin in sync). 3/14 WS committed (not yet closed in beads, awaiting merge): WS07 sdplab-s686 (cursor/opencode --model plumbing P0), WS01 sdplab-0261 (providers/ scaffold + tests), WS08 sdplab-noiq (tier_class + SelectTiers). Commits: 67f085b1 / 9ac769b4 / 30782519 on top of design 752140a3. After WS07/01/08 close, 6 WS unblock in parallel: WS02-06 (5 Provider impls) + WS13 LimitsCache. Then chain: WS09 seed → WS10 cascade pkg → WS11 confidence → WS12 replay → WS14 smoke+demo. 174 tests pass in internal/dispatch/. Next session: after PR #132 merge + restart, run @delivery-loop to dispatch remaining WS via fixed implementer subagents. F145 PR will open when epic is ready (or sensible mid-milestone)."} {"_type":"memory","key":"subagent-format-spec-2026-04-26","value":"Claude Code subagent file format (.claude/agents/*.md): YAML frontmatter MUST use 'tools: Read, Bash, Glob, Grep' COMMA-STRING format (PascalCase). NOT 'tools: { Read: true }' object form, NOT 'tools: [Read, Bash]' array. Source: docs.claude.com/docs/en/subagents.md lines 222-301. Wrong format → empty tool registry → agent fabricates XML \u003cfunction_calls\u003e in response.text (tool_uses=0). Verified via /tmp/probe diagnostic. Built-in agents (general-purpose, Explore, claude-code-guide) work because their registry is hardcoded, file-based ones go through the broken parsing path. PR #132 fixes 13 SDP custom agents. Note: Claude Code caches agent definitions at session start — fixes require restart to take effect, not just file save."} -{"_type":"memory","key":"f129-epic-created-2026-04-16-sdplab-8k5","value":"F129 epic created 2026-04-16 (sdplab-8k5 + 10 children sdplab-8k5.1..10 + 3 cross-lane: F106-07 sdplab-lqb, F124-05 sdplab-nai, F125-05 sdplab-as0). Addresses 5 audit findings: orchestration autonomy, subagent default, superpowers skill gap, doc drift, ad-hoc regression. Design: docs/plans/2026-04-16-f129-autonomy-regression-design.md. Cross-lane additions reuse existing F106/F124/F125 tracks instead of duplicating."} +{"_type":"memory","key":"f142-closed-2026-04-26","value":"F142 epic (sdplab-85ji) closed 2026-04-26: Workstream coverage gap closed. 6 children closed (gkl5 audit, rqsw baits, s6g7 orphans, f2h0 picker, 3obq doctor, fb1o INDEX). Plus sdplab-8nkm picker incident closed. Branch feature/F142-workstream-coverage-gap, 5 commits not yet merged to main. Delivered: scripts/deliver-pick.sh v2 (skip leafless+design-pending), internal/backlog/audit.go + cmd/sdp/cmd_doctor_backlog.go ('sdp doctor backlog'), 6 ws scaffolds (00-082-01, 00-083-01, 00-084-01, 00-085-01, 00-101-02, 00-133-01), INDEX.md updated, CI gate. 0 findings on doctor backlog. 189 Go tests pass."} +{"_type":"memory","key":"f144-epic-created-2026-04-26-sdplab-sjdp","value":"F144 epic created 2026-04-26 (sdplab-sjdp): Inference Confidence \u0026 Quality Control. 8 children attached: 01 sdplab-wzit core lib, 02 sdplab-339w self-check, 03 sdplab-w7j1 n-sample, 04 sdplab-tfmg constraint+composer, 05 sdplab-kk1v ws-verdict adapter (P1), 06 sdplab-cvu3 architect adapter, 07 sdplab-s5x8 dispatch lite, 08 sdplab-kd38 replay+metrics. Critical path: 01→04→05→08. Plus 2 hygiene issues: sdplab-vuw4 (drift docs/design vs docs/plans), sdplab-bv3r (AGENTS.md design-first phrasing). Design: docs/plans/2026-04-26-f144-inference-confidence-design.md. Branch: feature/F144-inference-confidence (not yet created, untracked design doc on worktree-bulk-deliver branch)."} {"_type":"memory","key":"subagent-implementer-broken-2026-04-26","value":"BROKEN: subagent_type=implementer fabricates tool_use. Confirmed via 3-test diagnostic 2026-04-26. T1 (implementer+sonnet): tool_uses=0, file not created, fabricated plausible output. T2 (general-purpose+haiku): tool_uses=1, file created. T3 (Explore+haiku): honest refusal (read-only). Pattern: implementer outputs old-format XML \u003cfunction_calls\u003e\u003cinvoke name='...'\u003e as response text instead of structured tool_use blocks. Affects ANY model. Workaround: use subagent_type=general-purpose for code implementation tasks. Affected sessions: F145 WS01/WS07/WS08 dispatch all fabricated, had to redo manually."} +{"_type":"memory","key":"f142-merged-2026-04-26","value":"F142 closed and merged 2026-04-26: feature/F142-workstream-coverage-gap pushed to origin (https://github.com/fall-out-bug/sdp_lab/tree/feature/F142-workstream-coverage-gap), local main merged with 7 ahead of origin/main (6 F142 commits + retains F141 merge f3ccef7). Ready for PR opening via GitHub UI when user wants. Branch can be deleted locally; remote branch retained for PR."} +{"_type":"memory","key":"f129-epic-created-2026-04-16-sdplab-8k5","value":"F129 epic created 2026-04-16 (sdplab-8k5 + 10 children sdplab-8k5.1..10 + 3 cross-lane: F106-07 sdplab-lqb, F124-05 sdplab-nai, F125-05 sdplab-as0). Addresses 5 audit findings: orchestration autonomy, subagent default, superpowers skill gap, doc drift, ad-hoc regression. Design: docs/plans/2026-04-16-f129-autonomy-regression-design.md. Cross-lane additions reuse existing F106/F124/F125 tracks instead of duplicating."} +{"_type":"memory","key":"f136-peer-memory-horizons-2026-04-20-epic","value":"f136-peer-memory-horizons-2026-04-20 | Epic F136 (sdplab-t1ty) created 2026-04-20: Peer Memory Foundation H1. Children: F136-01..06 (sdplab-r9py/lq4h/olvg/h25q/l0mm/h673). Parked: F137 H2 (sdplab-75u9, revisit 2026-07-06), F138 H3 (sdplab-7c45, revisit 2026-10-05). Design: docs/plans/2026-04-20-f136-peer-memory-h1-design.md. Market gap: peer-collaboration production framework where attribution is first-class (ChatCollab/HULA academic, no commercial product as of April 2026). H1 success = binary: did sdp memory query surface real lost context in 30-day dogfood. Label: peer-memory."} {"_type":"memory","key":"f141-epic-2026-04-25","value":"F141 epic (sdplab-o4sp) created 2026-04-25: Multi-harness install bootstrap \u0026 adapter parity. Children: F141-01 sdplab-fojs (manifest), -02 sdplab-6ea5 (generator), -03 sdplab-cl4o (bootstrap), -04 sdplab-i9q3 (doctor gate), -05 sdplab-r4y5 (parity matrix), -06 sdplab-cnpm (migration), -07 sdplab-nqjt (README). Closes gap left by F127/F128: downstream repos install SDP piecemeal. Approach: single sdp.manifest.yaml -\u003e adapter generator -\u003e curl|bash bootstrap -\u003e sdp doctor drift gate. Design: docs/plans/2026-04-25-f141-multi-harness-install-bootstrap-design.md. Branch: feature/F141-multi-harness-install-bootstrap."} -{"_type":"memory","key":"f142-r3-done","value":"F142 round 3 closed via PR #123 squash to ff20222. F142-08 mass backfill 49 beads. F142-09 ghost cleanup. F142-10 picker defense. Local main out of sync, needs reset to origin."} -{"_type":"memory","key":"f144-epic-created-2026-04-26-sdplab-sjdp","value":"F144 epic created 2026-04-26 (sdplab-sjdp): Inference Confidence \u0026 Quality Control. 8 children attached: 01 sdplab-wzit core lib, 02 sdplab-339w self-check, 03 sdplab-w7j1 n-sample, 04 sdplab-tfmg constraint+composer, 05 sdplab-kk1v ws-verdict adapter (P1), 06 sdplab-cvu3 architect adapter, 07 sdplab-s5x8 dispatch lite, 08 sdplab-kd38 replay+metrics. Critical path: 01→04→05→08. Plus 2 hygiene issues: sdplab-vuw4 (drift docs/design vs docs/plans), sdplab-bv3r (AGENTS.md design-first phrasing). Design: docs/plans/2026-04-26-f144-inference-confidence-design.md. Branch: feature/F144-inference-confidence (not yet created, untracked design doc on worktree-bulk-deliver branch)."} -{"_type":"memory","key":"f145-progress-2026-04-26","value":"F145 epic in progress 2026-04-26. Branch: feature/F145-multi-provider-cascade (origin in sync). 3/14 WS committed (not yet closed in beads, awaiting merge): WS07 sdplab-s686 (cursor/opencode --model plumbing P0), WS01 sdplab-0261 (providers/ scaffold + tests), WS08 sdplab-noiq (tier_class + SelectTiers). Commits: 67f085b1 / 9ac769b4 / 30782519 on top of design 752140a3. After WS07/01/08 close, 6 WS unblock in parallel: WS02-06 (5 Provider impls) + WS13 LimitsCache. Then chain: WS09 seed → WS10 cascade pkg → WS11 confidence → WS12 replay → WS14 smoke+demo. 174 tests pass in internal/dispatch/. Next session: after PR #132 merge + restart, run @delivery-loop to dispatch remaining WS via fixed implementer subagents. F145 PR will open when epic is ready (or sensible mid-milestone)."} +{"_type":"memory","key":"f142-epic-2026-04-26","value":"F142 epic (sdplab-85ji) created 2026-04-26: Workstream coverage gap — picker + ws/INDEX backfill + doctor gate. Root cause: features created via /design bypass /feature autogen → 0 ws files → picker selects leafless feature → /build fails. Affected: F141 (0 ws), F135 (0 ws), F100/F76/F80 (partial). First child: sdplab-8nkm (picker fail incident on sdplab-nj4 F135-05). Future children: audit script, per-feature backfill, INDEX update, picker hardening, sdp doctor backlog gate."} diff --git a/.github/workflows/sdp-doctor.yml b/.github/workflows/sdp-doctor.yml index 096d8c6f..56a0b1e6 100644 --- a/.github/workflows/sdp-doctor.yml +++ b/.github/workflows/sdp-doctor.yml @@ -26,6 +26,15 @@ jobs: - name: Build sdp binary run: go build -o sdp ./cmd/sdp + - name: Set up Beads + run: | + go install github.com/steveyegge/beads/cmd/bd@v1.0.3 + echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" + export PATH="$(go env GOPATH)/bin:$PATH" + git config beads.role maintainer + chmod 700 .beads + bd bootstrap --yes + - name: Validate manifest run: ./sdp manifest validate diff --git a/cmd/sdp/cmd_doctor_backlog.go b/cmd/sdp/cmd_doctor_backlog.go index 81c8f4b1..0191bde0 100644 --- a/cmd/sdp/cmd_doctor_backlog.go +++ b/cmd/sdp/cmd_doctor_backlog.go @@ -22,6 +22,12 @@ type beadEntry struct { Status string `json:"status"` IssueType string `json:"issue_type"` DependencyCount int `json:"dependency_count"` + DependentCount int `json:"dependent_count"` + Parent string `json:"parent"` + Dependencies []struct { + DependsOnID string `json:"depends_on_id"` + Type string `json:"type"` + } `json:"dependencies"` } // runDoctorBacklog implements `sdp doctor backlog`. @@ -128,6 +134,7 @@ func fileExists(path string) bool { // []backlog.Feature. func fetchBeadsFeatures(bdPath, status string) ([]backlog.Feature, error) { var all []backlog.Feature + featureIndex := make(map[string]int) for _, issueType := range []string{"feature", "epic"} { entries, err := bdList(bdPath, issueType) if err != nil { @@ -135,19 +142,75 @@ func fetchBeadsFeatures(bdPath, status string) ([]backlog.Feature, error) { } for _, e := range entries { fid := fidExtractRe.FindString(e.Title) + childCount := e.DependencyCount + if e.DependentCount > childCount { + childCount = e.DependentCount + } + featureIndex[e.ID] = len(all) all = append(all, backlog.Feature{ BeadID: e.ID, FID: fid, Title: e.Title, Status: e.Status, IssueType: e.IssueType, - DepCount: e.DependencyCount, + DepCount: childCount, }) } } + childEntries, err := fetchPotentialChildEntries(bdPath) + if err != nil { + return nil, err + } + all = addParentChildCounts(all, featureIndex, childEntries) + return all, nil +} + +func fetchPotentialChildEntries(bdPath string) ([]beadEntry, error) { + var all []beadEntry + for _, issueType := range []string{"bug", "task", "chore", "feature", "epic"} { + entries, err := bdList(bdPath, issueType) + if err != nil { + return nil, fmt.Errorf("bd list --type=%s: %w", issueType, err) + } + all = append(all, entries...) + } return all, nil } +func addParentChildCounts(features []backlog.Feature, featureIndex map[string]int, entries []beadEntry) []backlog.Feature { + counted := make(map[string]map[string]bool) + for _, e := range entries { + for _, parentID := range parentIDs(e) { + idx, ok := featureIndex[parentID] + if !ok || e.ID == parentID { + continue + } + if counted[parentID] == nil { + counted[parentID] = make(map[string]bool) + } + if counted[parentID][e.ID] { + continue + } + counted[parentID][e.ID] = true + features[idx].DepCount++ + } + } + return features +} + +func parentIDs(e beadEntry) []string { + var ids []string + if e.Parent != "" { + ids = append(ids, e.Parent) + } + for _, dep := range e.Dependencies { + if dep.Type == "parent-child" && dep.DependsOnID != "" { + ids = append(ids, dep.DependsOnID) + } + } + return ids +} + // bdList runs `bd list --type= --json --flat --all` and decodes the result. func bdList(bdPath, issueType string) ([]beadEntry, error) { cmd := exec.Command(bdPath, "list", "--type="+issueType, "--json", "--flat", "--all") diff --git a/cmd/sdp/cmd_doctor_backlog_test.go b/cmd/sdp/cmd_doctor_backlog_test.go new file mode 100644 index 00000000..0535b738 --- /dev/null +++ b/cmd/sdp/cmd_doctor_backlog_test.go @@ -0,0 +1,54 @@ +package main + +import ( + "testing" + + "github.com/fall-out-bug/sdp_lab/internal/backlog" +) + +func TestAddParentChildCountsUsesChildParentDependencies(t *testing.T) { + features := []backlog.Feature{ + {BeadID: "sdplab-parent", FID: "F162", Title: "F162: Parent", Status: "open", IssueType: "epic"}, + } + index := map[string]int{"sdplab-parent": 0} + entries := []beadEntry{ + { + ID: "sdplab-child", + Parent: "sdplab-parent", + Dependencies: []struct { + DependsOnID string `json:"depends_on_id"` + Type string `json:"type"` + }{ + {DependsOnID: "sdplab-parent", Type: "parent-child"}, + }, + }, + } + + got := addParentChildCounts(features, index, entries) + if got[0].DepCount != 1 { + t.Fatalf("DepCount = %d, want 1", got[0].DepCount) + } +} + +func TestAddParentChildCountsIgnoresNonParentDependencies(t *testing.T) { + features := []backlog.Feature{ + {BeadID: "sdplab-parent", FID: "F162", Title: "F162: Parent", Status: "open", IssueType: "epic"}, + } + index := map[string]int{"sdplab-parent": 0} + entries := []beadEntry{ + { + ID: "sdplab-child", + Dependencies: []struct { + DependsOnID string `json:"depends_on_id"` + Type string `json:"type"` + }{ + {DependsOnID: "sdplab-parent", Type: "blocks"}, + }, + }, + } + + got := addParentChildCounts(features, index, entries) + if got[0].DepCount != 0 { + t.Fatalf("DepCount = %d, want 0", got[0].DepCount) + } +} From c0fa1bc1d0a563a7482209acada5993adc811d4d Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 29 Apr 2026 19:38:35 +0300 Subject: [PATCH 09/11] fix: reject adapter output path traversal --- .beads/issues.jsonl | 17 +++++++++-------- internal/adapters/generate.go | 28 ++++++++++++++++++++++++++++ internal/adapters/generate_test.go | 23 +++++++++++++++++++++++ 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index af17375d..3251fd94 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -35,6 +35,7 @@ {"_type":"issue","id":"sdplab-22","title":"F100-01: Reference Integrity CI Gate","status":"closed","priority":0,"issue_type":"task","assignee":"Andrei","created_at":"2026-04-05T15:00:00Z","created_by":"UX Council","updated_at":"2026-04-26T13:30:49Z","closed_at":"2026-04-26T13:30:49Z","close_reason":"F100 Reference Integrity complete: CI gate + one-time cleanup. 56 tests passing.","labels":["F100","P0","harness:claude","stream-C","ux"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-23","title":"F100-02: One-Time Reference Cleanup","status":"closed","priority":0,"issue_type":"task","assignee":"Andrei","created_at":"2026-04-05T15:00:00Z","created_by":"UX Council","updated_at":"2026-04-26T13:30:50Z","closed_at":"2026-04-26T13:30:50Z","close_reason":"F100 Reference Integrity complete: CI gate + one-time cleanup. 56 tests passing.","labels":["F100","P0","harness:any","stream-C","ux"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-24","title":"F101-01: Write Plan Emission and Confirmation","status":"closed","priority":0,"issue_type":"feature","assignee":"Andrei","created_at":"2026-04-05T15:00:00Z","created_by":"UX Council","updated_at":"2026-04-19T07:33:30Z","closed_at":"2026-04-19T07:33:30Z","close_reason":"F101-01 merged to main via PR #95 after sdp PR #130 landed on sdp/main. Write Plan prompt surface is now shipped for the nine stateful skills.","labels":["F101","P0","harness:any","stream-B","ux"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-j811","title":"pi-review P1: Manifest dispatch override paths bypass traversal validation in .sdp/generated write","description":"File: cmd/sdp/cmd_init.go:178-189\nRationale: The PR correctly adds resolveManifestPath() for manifest source paths and validatePathSegment() for control repo IDs, but the .sdp/generated write loop at lines 178-189 writes generated map entries directly via filepath.Join(generatedRoot, rel) without checking that rel stays within the target. Since commandOutputPath() in internal/adapters/generate.go returns dispatch values verbatim (e.g., a manifest with dispatch: {claude-code: \"../../../etc/evil\"} produces rel=\"../../../etc/evil\"), filepath.Join(target, \".sdp/generated\", \"../../../etc/evil\") resolves to /etc/evil — escaping the repo root entirely. The live harness dir writes are safe due to the strings.HasPrefix(rel, harnessDir+\"/\") guard, but .sdp/generated has no equivalent check.\nSuggested fix: Add a traversal guard before writing to .sdp/generated, mirroring resolveManifestPath: after computing dest, verify filepath.Rel(target, dest) does not start with \"..\". Alternatively, validate dispatch paths in internal/manifest/load.go alongside the existing source path checks in checkPaths().\nDedupe key: P1:cmd/sdp/cmd_init.go:Manifest dispatch override paths bypass traversal validation in .sdp/generated write","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T16:33:56Z","created_by":"Andrei","updated_at":"2026-04-29T16:37:45Z","started_at":"2026-04-29T16:37:45Z","labels":["","pi-review","review-finding","round-10"],"comments":[{"id":"019dda1a-8dd9-7537-a56c-5689b5592af9","issue_id":"sdplab-j811","author":"Andrei","text":"Fixed in branch: internal/adapters.Generate now validates every generated output path and rejects absolute paths or parent traversal before callers write .sdp/generated or live harness files. Added TestGenerate_CommandDispatchOverrideRejectsTraversal. Verification: go test -tags sqlite_fts5 ./internal/adapters ./cmd/sdp -count=1 passed.","created_at":"2026-04-29T16:37:47Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1} {"_type":"issue","id":"sdplab-5dj9","title":"CI doctor backlog gate lacks Beads bootstrap","description":"PR #144 made sdp doctor backlog blocking in GitHub Actions, but .github/workflows/sdp-doctor.yml does not install bd or bootstrap the Beads database in a fresh checkout. Doctor job fails with: bd not found on PATH.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T15:57:53Z","created_by":"Andrei","updated_at":"2026-04-29T15:58:00Z","started_at":"2026-04-29T15:58:00Z","comments":[{"id":"019dd9fb-545d-7153-a2db-6db1c1821645","issue_id":"sdplab-5dj9","author":"Andrei","text":"Fixed in branch: sdp-doctor workflow now installs bd v1.0.3 and runs bd bootstrap --yes before backlog doctor. Also fixed sdp doctor backlog to count parent-child children from child entries instead of parent dependency_count; clean clone simulation passes manifest, doctor adapters, and doctor backlog.","created_at":"2026-04-29T16:03:40Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1} {"_type":"issue","id":"sdplab-i8xo","title":"pi-review P1: Bootstrap dry-run writes to target repo","description":"File: cmd/sdp/cmd_bootstrap.go:84-87\nRationale: appendGreenfieldArtifacts and appendBrownfieldArtifacts call writeDraftArtifact without passing dryRun, so bootstrap --dry-run --mode greenfield|brownfield still writes DRAFT-* files to the target repo, contradicting docs/QUICKSTART.md which promises bootstrap --dry-run is read-only.\nSuggested fix: Pass dryRun to both appendGreenfieldArtifacts and appendBrownfieldArtifacts, and thread it through to writeDraftArtifact (already done for greenfield/answers save). Verify TestBootstrapDryRunBrownfieldDoesNotWriteDraftDelta passes.\nDedupe key: P1:cmd/sdp/cmd_bootstrap.go:Bootstrap dry-run writes to target repo","status":"closed","priority":1,"issue_type":"bug","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T14:55:28Z","created_by":"Andrei","updated_at":"2026-04-29T14:56:20Z","closed_at":"2026-04-29T14:56:20Z","close_reason":"false positive: cmd/sdp/cmd_bootstrap.go already passes dryRun into appendGreenfieldArtifacts/appendBrownfieldArtifacts and writeDraftArtifact; TestBootstrapDryRunBrownfieldDoesNotWriteDraftDelta verifies no DRAFT-bootstrap-delta.json is written","labels":["F150","pi-review","review-finding","round-7"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-w1g1","title":"pi-review P1: synthesizeFindings reads artifacts by absolute path but Runner.Run does not persist models before synthesis","description":"File: internal/pireview/runner.go:322-340\nRationale: runModelPanel calls writeModelArtifact which returns an absolute path on success. synthesizeFindings then reads ModelResult.ArtifactPath via os.ReadFile. However, if writeModelArtifact fails (returns empty string), the code sets result.ArtifactPath to a planned-but-unwritten path like '.sdp/runs/pi-review/{runID}/models/{slot}.json'. When synthesis runs, it will try to read this relative path from CWD, not the project root, causing a silent miss. More critically: in Run(), modelResults are returned from runModelPanel, then synthesizeFindings(modelResults) is called BEFORE the run directory is created at line ~265. The model artifacts ARE written inside runModelPanel, but the context/evidence artifacts written later in Run() share the same runDir. This ordering is correct for models but means if MkdirAll fails at line ~265, the model artifacts are already on disk but the run context is lost.\nSuggested fix: Consider either: (1) moving the run directory creation (MkdirAll) before runModelPanel so all artifacts share the same guaranteed directory, or (2) documenting that model artifacts are written eagerly. Also fix the empty-string artifact path case to not set a path at all.\nDedupe key: P1:internal/pireview/runner.go:synthesizeFindings reads artifacts by absolute path but Runner.Run does not persist models before synthesis","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T14:55:24Z","created_by":"Andrei","updated_at":"2026-04-29T14:56:27Z","started_at":"2026-04-29T14:56:27Z","labels":["F150","pi-review","review-finding","round-7"],"dependency_count":0,"dependent_count":0,"comment_count":0} @@ -559,16 +560,16 @@ {"_type":"issue","id":"sdplab-houh","title":"F036: Orchestrate + in-toto (historical placeholder)","description":"absorbed by F064–F067 auto-attestation","status":"closed","priority":4,"issue_type":"feature","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-18T11:27:58Z","created_by":"Andrei","updated_at":"2026-04-18T11:27:58Z","closed_at":"2026-04-18T11:27:58Z","close_reason":"absorbed by F064–F067 auto-attestation","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-crg","title":"F034: Graduated Enforcement (historical placeholder)","description":"historical placeholder; no active scope","status":"closed","priority":4,"issue_type":"feature","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-18T11:27:55Z","created_by":"Andrei","updated_at":"2026-04-18T11:27:56Z","closed_at":"2026-04-18T11:27:56Z","close_reason":"historical placeholder; no active scope","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-h5y","title":"F032: PR Evidence Summary (historical placeholder)","description":"historical placeholder; no active scope (2026-04-18 triage)","status":"closed","priority":4,"issue_type":"feature","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-18T11:27:34Z","created_by":"Andrei","updated_at":"2026-04-18T11:27:54Z","closed_at":"2026-04-18T11:27:54Z","close_reason":"historical placeholder; no active scope (2026-04-18 triage)","dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"memory","key":"f142-r3-done","value":"F142 round 3 closed via PR #123 squash to ff20222. F142-08 mass backfill 49 beads. F142-09 ghost cleanup. F142-10 picker defense. Local main out of sync, needs reset to origin."} -{"_type":"memory","key":"f144-merged-2026-04-26","value":"F144 PR #131 MERGED to main at 2026-04-26T17:43Z, merge commit ed393e3. internal/inference/confidence/ live on main: Status enum, Result[T], Policy/Strategy/Checker, 3 strategies (constraint/selfcheck/nsample), 3 adapters (wsverdict/architect/dispatch). 111 tests, ≥85% coverage. Branch feature/F144-inference-confidence still exists locally. Replaces stale 'sdplab-8nkm-untracked-design-doc' status."} -{"_type":"memory","key":"f145-epic-created-2026-04-26","value":"F145 epic (sdplab-ldmq) created 2026-04-26: Multi-Provider Dispatch Matrix \u0026 Confidence-Driven Cascade. 14 children: WS01 sdplab-0261 (providers/ scaffold), WS02 sdplab-y93k (OpenAI), WS03 sdplab-w4ru (Anthropic), WS04 sdplab-bg69 (Cursor ~30 models), WS05 sdplab-60ia (Kimi), WS06 sdplab-5lve (Ollama replaces LocalConfig), WS07 sdplab-s686 (cursor/opencode --model plumbing P0 bug), WS08 sdplab-noiq (tier_class label), WS09 sdplab-3lva (profiles_default.json seed), WS10 sdplab-5ii8 (cascade pkg + Invoker), WS11 sdplab-fco9 (F144 confidence injection), WS12 sdplab-uamt (cascade-replay corpus), WS13 sdplab-135g (LimitsCache), WS14 sdplab-2p8g (smoke + Day-8 demo). Design: docs/plans/2026-04-26-f145-multi-provider-dispatch-cascade-design.md. Branch (planned): feature/F145-multi-provider-cascade. Discovery decisions: providers/ sub-pkg, cascade pkg, tier-label hybrid, composer gate default, UNSURE+FAIL+heuristic short-circuit, MaxDepth/Budget configurable, hybrid LimitsCache, F144 replay extension, harness-CLI keys (no SDP auth)."} -{"_type":"memory","key":"f145-progress-2026-04-26","value":"F145 epic in progress 2026-04-26. Branch: feature/F145-multi-provider-cascade (origin in sync). 3/14 WS committed (not yet closed in beads, awaiting merge): WS07 sdplab-s686 (cursor/opencode --model plumbing P0), WS01 sdplab-0261 (providers/ scaffold + tests), WS08 sdplab-noiq (tier_class + SelectTiers). Commits: 67f085b1 / 9ac769b4 / 30782519 on top of design 752140a3. After WS07/01/08 close, 6 WS unblock in parallel: WS02-06 (5 Provider impls) + WS13 LimitsCache. Then chain: WS09 seed → WS10 cascade pkg → WS11 confidence → WS12 replay → WS14 smoke+demo. 174 tests pass in internal/dispatch/. Next session: after PR #132 merge + restart, run @delivery-loop to dispatch remaining WS via fixed implementer subagents. F145 PR will open when epic is ready (or sensible mid-milestone)."} -{"_type":"memory","key":"subagent-format-spec-2026-04-26","value":"Claude Code subagent file format (.claude/agents/*.md): YAML frontmatter MUST use 'tools: Read, Bash, Glob, Grep' COMMA-STRING format (PascalCase). NOT 'tools: { Read: true }' object form, NOT 'tools: [Read, Bash]' array. Source: docs.claude.com/docs/en/subagents.md lines 222-301. Wrong format → empty tool registry → agent fabricates XML \u003cfunction_calls\u003e in response.text (tool_uses=0). Verified via /tmp/probe diagnostic. Built-in agents (general-purpose, Explore, claude-code-guide) work because their registry is hardcoded, file-based ones go through the broken parsing path. PR #132 fixes 13 SDP custom agents. Note: Claude Code caches agent definitions at session start — fixes require restart to take effect, not just file save."} +{"_type":"memory","key":"f141-epic-2026-04-25","value":"F141 epic (sdplab-o4sp) created 2026-04-25: Multi-harness install bootstrap \u0026 adapter parity. Children: F141-01 sdplab-fojs (manifest), -02 sdplab-6ea5 (generator), -03 sdplab-cl4o (bootstrap), -04 sdplab-i9q3 (doctor gate), -05 sdplab-r4y5 (parity matrix), -06 sdplab-cnpm (migration), -07 sdplab-nqjt (README). Closes gap left by F127/F128: downstream repos install SDP piecemeal. Approach: single sdp.manifest.yaml -\u003e adapter generator -\u003e curl|bash bootstrap -\u003e sdp doctor drift gate. Design: docs/plans/2026-04-25-f141-multi-harness-install-bootstrap-design.md. Branch: feature/F141-multi-harness-install-bootstrap."} {"_type":"memory","key":"f142-closed-2026-04-26","value":"F142 epic (sdplab-85ji) closed 2026-04-26: Workstream coverage gap closed. 6 children closed (gkl5 audit, rqsw baits, s6g7 orphans, f2h0 picker, 3obq doctor, fb1o INDEX). Plus sdplab-8nkm picker incident closed. Branch feature/F142-workstream-coverage-gap, 5 commits not yet merged to main. Delivered: scripts/deliver-pick.sh v2 (skip leafless+design-pending), internal/backlog/audit.go + cmd/sdp/cmd_doctor_backlog.go ('sdp doctor backlog'), 6 ws scaffolds (00-082-01, 00-083-01, 00-084-01, 00-085-01, 00-101-02, 00-133-01), INDEX.md updated, CI gate. 0 findings on doctor backlog. 189 Go tests pass."} +{"_type":"memory","key":"f142-epic-2026-04-26","value":"F142 epic (sdplab-85ji) created 2026-04-26: Workstream coverage gap — picker + ws/INDEX backfill + doctor gate. Root cause: features created via /design bypass /feature autogen → 0 ws files → picker selects leafless feature → /build fails. Affected: F141 (0 ws), F135 (0 ws), F100/F76/F80 (partial). First child: sdplab-8nkm (picker fail incident on sdplab-nj4 F135-05). Future children: audit script, per-feature backfill, INDEX update, picker hardening, sdp doctor backlog gate."} {"_type":"memory","key":"f144-epic-created-2026-04-26-sdplab-sjdp","value":"F144 epic created 2026-04-26 (sdplab-sjdp): Inference Confidence \u0026 Quality Control. 8 children attached: 01 sdplab-wzit core lib, 02 sdplab-339w self-check, 03 sdplab-w7j1 n-sample, 04 sdplab-tfmg constraint+composer, 05 sdplab-kk1v ws-verdict adapter (P1), 06 sdplab-cvu3 architect adapter, 07 sdplab-s5x8 dispatch lite, 08 sdplab-kd38 replay+metrics. Critical path: 01→04→05→08. Plus 2 hygiene issues: sdplab-vuw4 (drift docs/design vs docs/plans), sdplab-bv3r (AGENTS.md design-first phrasing). Design: docs/plans/2026-04-26-f144-inference-confidence-design.md. Branch: feature/F144-inference-confidence (not yet created, untracked design doc on worktree-bulk-deliver branch)."} {"_type":"memory","key":"subagent-implementer-broken-2026-04-26","value":"BROKEN: subagent_type=implementer fabricates tool_use. Confirmed via 3-test diagnostic 2026-04-26. T1 (implementer+sonnet): tool_uses=0, file not created, fabricated plausible output. T2 (general-purpose+haiku): tool_uses=1, file created. T3 (Explore+haiku): honest refusal (read-only). Pattern: implementer outputs old-format XML \u003cfunction_calls\u003e\u003cinvoke name='...'\u003e as response text instead of structured tool_use blocks. Affects ANY model. Workaround: use subagent_type=general-purpose for code implementation tasks. Affected sessions: F145 WS01/WS07/WS08 dispatch all fabricated, had to redo manually."} -{"_type":"memory","key":"f142-merged-2026-04-26","value":"F142 closed and merged 2026-04-26: feature/F142-workstream-coverage-gap pushed to origin (https://github.com/fall-out-bug/sdp_lab/tree/feature/F142-workstream-coverage-gap), local main merged with 7 ahead of origin/main (6 F142 commits + retains F141 merge f3ccef7). Ready for PR opening via GitHub UI when user wants. Branch can be deleted locally; remote branch retained for PR."} {"_type":"memory","key":"f129-epic-created-2026-04-16-sdplab-8k5","value":"F129 epic created 2026-04-16 (sdplab-8k5 + 10 children sdplab-8k5.1..10 + 3 cross-lane: F106-07 sdplab-lqb, F124-05 sdplab-nai, F125-05 sdplab-as0). Addresses 5 audit findings: orchestration autonomy, subagent default, superpowers skill gap, doc drift, ad-hoc regression. Design: docs/plans/2026-04-16-f129-autonomy-regression-design.md. Cross-lane additions reuse existing F106/F124/F125 tracks instead of duplicating."} {"_type":"memory","key":"f136-peer-memory-horizons-2026-04-20-epic","value":"f136-peer-memory-horizons-2026-04-20 | Epic F136 (sdplab-t1ty) created 2026-04-20: Peer Memory Foundation H1. Children: F136-01..06 (sdplab-r9py/lq4h/olvg/h25q/l0mm/h673). Parked: F137 H2 (sdplab-75u9, revisit 2026-07-06), F138 H3 (sdplab-7c45, revisit 2026-10-05). Design: docs/plans/2026-04-20-f136-peer-memory-h1-design.md. Market gap: peer-collaboration production framework where attribution is first-class (ChatCollab/HULA academic, no commercial product as of April 2026). H1 success = binary: did sdp memory query surface real lost context in 30-day dogfood. Label: peer-memory."} -{"_type":"memory","key":"f141-epic-2026-04-25","value":"F141 epic (sdplab-o4sp) created 2026-04-25: Multi-harness install bootstrap \u0026 adapter parity. Children: F141-01 sdplab-fojs (manifest), -02 sdplab-6ea5 (generator), -03 sdplab-cl4o (bootstrap), -04 sdplab-i9q3 (doctor gate), -05 sdplab-r4y5 (parity matrix), -06 sdplab-cnpm (migration), -07 sdplab-nqjt (README). Closes gap left by F127/F128: downstream repos install SDP piecemeal. Approach: single sdp.manifest.yaml -\u003e adapter generator -\u003e curl|bash bootstrap -\u003e sdp doctor drift gate. Design: docs/plans/2026-04-25-f141-multi-harness-install-bootstrap-design.md. Branch: feature/F141-multi-harness-install-bootstrap."} -{"_type":"memory","key":"f142-epic-2026-04-26","value":"F142 epic (sdplab-85ji) created 2026-04-26: Workstream coverage gap — picker + ws/INDEX backfill + doctor gate. Root cause: features created via /design bypass /feature autogen → 0 ws files → picker selects leafless feature → /build fails. Affected: F141 (0 ws), F135 (0 ws), F100/F76/F80 (partial). First child: sdplab-8nkm (picker fail incident on sdplab-nj4 F135-05). Future children: audit script, per-feature backfill, INDEX update, picker hardening, sdp doctor backlog gate."} +{"_type":"memory","key":"f144-merged-2026-04-26","value":"F144 PR #131 MERGED to main at 2026-04-26T17:43Z, merge commit ed393e3. internal/inference/confidence/ live on main: Status enum, Result[T], Policy/Strategy/Checker, 3 strategies (constraint/selfcheck/nsample), 3 adapters (wsverdict/architect/dispatch). 111 tests, ≥85% coverage. Branch feature/F144-inference-confidence still exists locally. Replaces stale 'sdplab-8nkm-untracked-design-doc' status."} +{"_type":"memory","key":"f142-merged-2026-04-26","value":"F142 closed and merged 2026-04-26: feature/F142-workstream-coverage-gap pushed to origin (https://github.com/fall-out-bug/sdp_lab/tree/feature/F142-workstream-coverage-gap), local main merged with 7 ahead of origin/main (6 F142 commits + retains F141 merge f3ccef7). Ready for PR opening via GitHub UI when user wants. Branch can be deleted locally; remote branch retained for PR."} +{"_type":"memory","key":"f142-r3-done","value":"F142 round 3 closed via PR #123 squash to ff20222. F142-08 mass backfill 49 beads. F142-09 ghost cleanup. F142-10 picker defense. Local main out of sync, needs reset to origin."} +{"_type":"memory","key":"f145-epic-created-2026-04-26","value":"F145 epic (sdplab-ldmq) created 2026-04-26: Multi-Provider Dispatch Matrix \u0026 Confidence-Driven Cascade. 14 children: WS01 sdplab-0261 (providers/ scaffold), WS02 sdplab-y93k (OpenAI), WS03 sdplab-w4ru (Anthropic), WS04 sdplab-bg69 (Cursor ~30 models), WS05 sdplab-60ia (Kimi), WS06 sdplab-5lve (Ollama replaces LocalConfig), WS07 sdplab-s686 (cursor/opencode --model plumbing P0 bug), WS08 sdplab-noiq (tier_class label), WS09 sdplab-3lva (profiles_default.json seed), WS10 sdplab-5ii8 (cascade pkg + Invoker), WS11 sdplab-fco9 (F144 confidence injection), WS12 sdplab-uamt (cascade-replay corpus), WS13 sdplab-135g (LimitsCache), WS14 sdplab-2p8g (smoke + Day-8 demo). Design: docs/plans/2026-04-26-f145-multi-provider-dispatch-cascade-design.md. Branch (planned): feature/F145-multi-provider-cascade. Discovery decisions: providers/ sub-pkg, cascade pkg, tier-label hybrid, composer gate default, UNSURE+FAIL+heuristic short-circuit, MaxDepth/Budget configurable, hybrid LimitsCache, F144 replay extension, harness-CLI keys (no SDP auth)."} +{"_type":"memory","key":"f145-progress-2026-04-26","value":"F145 epic in progress 2026-04-26. Branch: feature/F145-multi-provider-cascade (origin in sync). 3/14 WS committed (not yet closed in beads, awaiting merge): WS07 sdplab-s686 (cursor/opencode --model plumbing P0), WS01 sdplab-0261 (providers/ scaffold + tests), WS08 sdplab-noiq (tier_class + SelectTiers). Commits: 67f085b1 / 9ac769b4 / 30782519 on top of design 752140a3. After WS07/01/08 close, 6 WS unblock in parallel: WS02-06 (5 Provider impls) + WS13 LimitsCache. Then chain: WS09 seed → WS10 cascade pkg → WS11 confidence → WS12 replay → WS14 smoke+demo. 174 tests pass in internal/dispatch/. Next session: after PR #132 merge + restart, run @delivery-loop to dispatch remaining WS via fixed implementer subagents. F145 PR will open when epic is ready (or sensible mid-milestone)."} +{"_type":"memory","key":"subagent-format-spec-2026-04-26","value":"Claude Code subagent file format (.claude/agents/*.md): YAML frontmatter MUST use 'tools: Read, Bash, Glob, Grep' COMMA-STRING format (PascalCase). NOT 'tools: { Read: true }' object form, NOT 'tools: [Read, Bash]' array. Source: docs.claude.com/docs/en/subagents.md lines 222-301. Wrong format → empty tool registry → agent fabricates XML \u003cfunction_calls\u003e in response.text (tool_uses=0). Verified via /tmp/probe diagnostic. Built-in agents (general-purpose, Explore, claude-code-guide) work because their registry is hardcoded, file-based ones go through the broken parsing path. PR #132 fixes 13 SDP custom agents. Note: Claude Code caches agent definitions at session start — fixes require restart to take effect, not just file save."} diff --git a/internal/adapters/generate.go b/internal/adapters/generate.go index 743f92d9..08d5e22f 100644 --- a/internal/adapters/generate.go +++ b/internal/adapters/generate.go @@ -146,10 +146,38 @@ func Generate(m *manifest.Manifest, repoRoot string) (map[string][]byte, error) if err := generatePi(m, harnessEnabled, repoRoot, out); err != nil { return nil, err } + if err := validateOutputPaths(out); err != nil { + return nil, err + } return out, nil } +func validateOutputPaths(out map[string][]byte) error { + for rel := range out { + if err := validateOutputPath(rel); err != nil { + return err + } + } + return nil +} + +func validateOutputPath(rel string) error { + if rel == "" { + return fmt.Errorf("adapter output path is empty") + } + if filepath.IsAbs(rel) { + return fmt.Errorf("adapter output path %q must be relative", rel) + } + slashPath := strings.ReplaceAll(rel, "\\", "/") + for _, part := range strings.Split(slashPath, "/") { + if part == ".." { + return fmt.Errorf("adapter output path %q must not contain parent traversal", rel) + } + } + return nil +} + // itemHarnesses returns the effective harness set for an item: if the item's // own Harnesses list is empty, it inherits all harnesses enabled in the manifest. func itemHarnesses(declared []manifest.Harness, manifestEnabled map[manifest.Harness]bool) map[manifest.Harness]bool { diff --git a/internal/adapters/generate_test.go b/internal/adapters/generate_test.go index fbaa0b4b..808f680b 100644 --- a/internal/adapters/generate_test.go +++ b/internal/adapters/generate_test.go @@ -181,6 +181,29 @@ func TestGenerate_CommandDispatchOverride(t *testing.T) { } } +func TestGenerate_CommandDispatchOverrideRejectsTraversal(t *testing.T) { + m := &manifest.Manifest{ + Version: "1.0.0", + SDPVersion: "1.0.0", + Harnesses: []manifest.Harness{ + manifest.HarnessClaudeCode, + }, + Commands: []manifest.Command{ + { + Name: "escape", + Path: "prompts/commands/escape.md", + Dispatch: map[manifest.Harness]string{ + manifest.HarnessClaudeCode: "../../outside.md", + }, + }, + }, + } + + if _, err := adapters.Generate(m, ""); err == nil { + t.Fatal("Generate returned nil error for traversal dispatch override") + } +} + // TestGenerate_PerHarnessHasFiles verifies that a full minimal manifest has at // least one file per harness prefix. func TestGenerate_PerHarnessHasFiles(t *testing.T) { From 88783c522a80658c77204f3348151ef0938bff2b Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 29 Apr 2026 19:56:22 +0300 Subject: [PATCH 10/11] fix: fallback pi review timeouts --- .beads/issues.jsonl | 15 ++++----- internal/pireview/runner.go | 3 +- internal/pireview/runner_test.go | 53 ++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 9 deletions(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 3251fd94..82efb8ce 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -35,6 +35,7 @@ {"_type":"issue","id":"sdplab-22","title":"F100-01: Reference Integrity CI Gate","status":"closed","priority":0,"issue_type":"task","assignee":"Andrei","created_at":"2026-04-05T15:00:00Z","created_by":"UX Council","updated_at":"2026-04-26T13:30:49Z","closed_at":"2026-04-26T13:30:49Z","close_reason":"F100 Reference Integrity complete: CI gate + one-time cleanup. 56 tests passing.","labels":["F100","P0","harness:claude","stream-C","ux"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-23","title":"F100-02: One-Time Reference Cleanup","status":"closed","priority":0,"issue_type":"task","assignee":"Andrei","created_at":"2026-04-05T15:00:00Z","created_by":"UX Council","updated_at":"2026-04-26T13:30:50Z","closed_at":"2026-04-26T13:30:50Z","close_reason":"F100 Reference Integrity complete: CI gate + one-time cleanup. 56 tests passing.","labels":["F100","P0","harness:any","stream-C","ux"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-24","title":"F101-01: Write Plan Emission and Confirmation","status":"closed","priority":0,"issue_type":"feature","assignee":"Andrei","created_at":"2026-04-05T15:00:00Z","created_by":"UX Council","updated_at":"2026-04-19T07:33:30Z","closed_at":"2026-04-19T07:33:30Z","close_reason":"F101-01 merged to main via PR #95 after sdp PR #130 landed on sdp/main. Write Plan prompt surface is now shipped for the nine stateful skills.","labels":["F101","P0","harness:any","stream-B","ux"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"sdplab-uhri","title":"pi-review P1: Pi-review runner excludes timeout from OpenRouter fallback","description":"File: internal/pireview/runner.go:269-282\nRationale: runModelPanel checks `!errors.Is(err, context.DeadlineExceeded)` before attempting OpenRouter fallback for required slots. The pi-review spec explicitly requires fallback when a required reviewer fails OR times out. A timed-out model therefore leaves the panel under-quorum without attempting the configured fallback, which can block the delivery loop unnecessarily.\nSuggested fix: Remove the `!errors.Is(err, context.DeadlineExceeded)` guard so that all errors from required slots trigger the OpenRouter fallback path.\nDedupe key: P1:internal/pireview/runner.go:Pi-review runner excludes timeout from OpenRouter fallback","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T16:51:45Z","created_by":"Andrei","updated_at":"2026-04-29T16:54:00Z","started_at":"2026-04-29T16:54:00Z","labels":["","pi-review","review-finding","round-11"],"comments":[{"id":"019dda29-6f21-7772-abdd-f2e44f6a34ea","issue_id":"sdplab-uhri","author":"Andrei","text":"Fixed in branch: required-slot fallback now runs whenever the parent review context is still active, including primary model timeouts. Parent context cancellation still suppresses fallback. Added TestRunModelPanelAttemptsFallbackAfterTimeout. Verification: go test -tags sqlite_fts5 ./internal/pireview ./cmd/sdp-pi-review -count=1 passed.","created_at":"2026-04-29T16:54:02Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1} {"_type":"issue","id":"sdplab-j811","title":"pi-review P1: Manifest dispatch override paths bypass traversal validation in .sdp/generated write","description":"File: cmd/sdp/cmd_init.go:178-189\nRationale: The PR correctly adds resolveManifestPath() for manifest source paths and validatePathSegment() for control repo IDs, but the .sdp/generated write loop at lines 178-189 writes generated map entries directly via filepath.Join(generatedRoot, rel) without checking that rel stays within the target. Since commandOutputPath() in internal/adapters/generate.go returns dispatch values verbatim (e.g., a manifest with dispatch: {claude-code: \"../../../etc/evil\"} produces rel=\"../../../etc/evil\"), filepath.Join(target, \".sdp/generated\", \"../../../etc/evil\") resolves to /etc/evil — escaping the repo root entirely. The live harness dir writes are safe due to the strings.HasPrefix(rel, harnessDir+\"/\") guard, but .sdp/generated has no equivalent check.\nSuggested fix: Add a traversal guard before writing to .sdp/generated, mirroring resolveManifestPath: after computing dest, verify filepath.Rel(target, dest) does not start with \"..\". Alternatively, validate dispatch paths in internal/manifest/load.go alongside the existing source path checks in checkPaths().\nDedupe key: P1:cmd/sdp/cmd_init.go:Manifest dispatch override paths bypass traversal validation in .sdp/generated write","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T16:33:56Z","created_by":"Andrei","updated_at":"2026-04-29T16:37:45Z","started_at":"2026-04-29T16:37:45Z","labels":["","pi-review","review-finding","round-10"],"comments":[{"id":"019dda1a-8dd9-7537-a56c-5689b5592af9","issue_id":"sdplab-j811","author":"Andrei","text":"Fixed in branch: internal/adapters.Generate now validates every generated output path and rejects absolute paths or parent traversal before callers write .sdp/generated or live harness files. Added TestGenerate_CommandDispatchOverrideRejectsTraversal. Verification: go test -tags sqlite_fts5 ./internal/adapters ./cmd/sdp -count=1 passed.","created_at":"2026-04-29T16:37:47Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1} {"_type":"issue","id":"sdplab-5dj9","title":"CI doctor backlog gate lacks Beads bootstrap","description":"PR #144 made sdp doctor backlog blocking in GitHub Actions, but .github/workflows/sdp-doctor.yml does not install bd or bootstrap the Beads database in a fresh checkout. Doctor job fails with: bd not found on PATH.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T15:57:53Z","created_by":"Andrei","updated_at":"2026-04-29T15:58:00Z","started_at":"2026-04-29T15:58:00Z","comments":[{"id":"019dd9fb-545d-7153-a2db-6db1c1821645","issue_id":"sdplab-5dj9","author":"Andrei","text":"Fixed in branch: sdp-doctor workflow now installs bd v1.0.3 and runs bd bootstrap --yes before backlog doctor. Also fixed sdp doctor backlog to count parent-child children from child entries instead of parent dependency_count; clean clone simulation passes manifest, doctor adapters, and doctor backlog.","created_at":"2026-04-29T16:03:40Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1} {"_type":"issue","id":"sdplab-i8xo","title":"pi-review P1: Bootstrap dry-run writes to target repo","description":"File: cmd/sdp/cmd_bootstrap.go:84-87\nRationale: appendGreenfieldArtifacts and appendBrownfieldArtifacts call writeDraftArtifact without passing dryRun, so bootstrap --dry-run --mode greenfield|brownfield still writes DRAFT-* files to the target repo, contradicting docs/QUICKSTART.md which promises bootstrap --dry-run is read-only.\nSuggested fix: Pass dryRun to both appendGreenfieldArtifacts and appendBrownfieldArtifacts, and thread it through to writeDraftArtifact (already done for greenfield/answers save). Verify TestBootstrapDryRunBrownfieldDoesNotWriteDraftDelta passes.\nDedupe key: P1:cmd/sdp/cmd_bootstrap.go:Bootstrap dry-run writes to target repo","status":"closed","priority":1,"issue_type":"bug","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T14:55:28Z","created_by":"Andrei","updated_at":"2026-04-29T14:56:20Z","closed_at":"2026-04-29T14:56:20Z","close_reason":"false positive: cmd/sdp/cmd_bootstrap.go already passes dryRun into appendGreenfieldArtifacts/appendBrownfieldArtifacts and writeDraftArtifact; TestBootstrapDryRunBrownfieldDoesNotWriteDraftDelta verifies no DRAFT-bootstrap-delta.json is written","labels":["F150","pi-review","review-finding","round-7"],"dependency_count":0,"dependent_count":0,"comment_count":0} @@ -560,16 +561,16 @@ {"_type":"issue","id":"sdplab-houh","title":"F036: Orchestrate + in-toto (historical placeholder)","description":"absorbed by F064–F067 auto-attestation","status":"closed","priority":4,"issue_type":"feature","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-18T11:27:58Z","created_by":"Andrei","updated_at":"2026-04-18T11:27:58Z","closed_at":"2026-04-18T11:27:58Z","close_reason":"absorbed by F064–F067 auto-attestation","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-crg","title":"F034: Graduated Enforcement (historical placeholder)","description":"historical placeholder; no active scope","status":"closed","priority":4,"issue_type":"feature","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-18T11:27:55Z","created_by":"Andrei","updated_at":"2026-04-18T11:27:56Z","closed_at":"2026-04-18T11:27:56Z","close_reason":"historical placeholder; no active scope","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-h5y","title":"F032: PR Evidence Summary (historical placeholder)","description":"historical placeholder; no active scope (2026-04-18 triage)","status":"closed","priority":4,"issue_type":"feature","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-18T11:27:34Z","created_by":"Andrei","updated_at":"2026-04-18T11:27:54Z","closed_at":"2026-04-18T11:27:54Z","close_reason":"historical placeholder; no active scope (2026-04-18 triage)","dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"memory","key":"f141-epic-2026-04-25","value":"F141 epic (sdplab-o4sp) created 2026-04-25: Multi-harness install bootstrap \u0026 adapter parity. Children: F141-01 sdplab-fojs (manifest), -02 sdplab-6ea5 (generator), -03 sdplab-cl4o (bootstrap), -04 sdplab-i9q3 (doctor gate), -05 sdplab-r4y5 (parity matrix), -06 sdplab-cnpm (migration), -07 sdplab-nqjt (README). Closes gap left by F127/F128: downstream repos install SDP piecemeal. Approach: single sdp.manifest.yaml -\u003e adapter generator -\u003e curl|bash bootstrap -\u003e sdp doctor drift gate. Design: docs/plans/2026-04-25-f141-multi-harness-install-bootstrap-design.md. Branch: feature/F141-multi-harness-install-bootstrap."} -{"_type":"memory","key":"f142-closed-2026-04-26","value":"F142 epic (sdplab-85ji) closed 2026-04-26: Workstream coverage gap closed. 6 children closed (gkl5 audit, rqsw baits, s6g7 orphans, f2h0 picker, 3obq doctor, fb1o INDEX). Plus sdplab-8nkm picker incident closed. Branch feature/F142-workstream-coverage-gap, 5 commits not yet merged to main. Delivered: scripts/deliver-pick.sh v2 (skip leafless+design-pending), internal/backlog/audit.go + cmd/sdp/cmd_doctor_backlog.go ('sdp doctor backlog'), 6 ws scaffolds (00-082-01, 00-083-01, 00-084-01, 00-085-01, 00-101-02, 00-133-01), INDEX.md updated, CI gate. 0 findings on doctor backlog. 189 Go tests pass."} -{"_type":"memory","key":"f142-epic-2026-04-26","value":"F142 epic (sdplab-85ji) created 2026-04-26: Workstream coverage gap — picker + ws/INDEX backfill + doctor gate. Root cause: features created via /design bypass /feature autogen → 0 ws files → picker selects leafless feature → /build fails. Affected: F141 (0 ws), F135 (0 ws), F100/F76/F80 (partial). First child: sdplab-8nkm (picker fail incident on sdplab-nj4 F135-05). Future children: audit script, per-feature backfill, INDEX update, picker hardening, sdp doctor backlog gate."} -{"_type":"memory","key":"f144-epic-created-2026-04-26-sdplab-sjdp","value":"F144 epic created 2026-04-26 (sdplab-sjdp): Inference Confidence \u0026 Quality Control. 8 children attached: 01 sdplab-wzit core lib, 02 sdplab-339w self-check, 03 sdplab-w7j1 n-sample, 04 sdplab-tfmg constraint+composer, 05 sdplab-kk1v ws-verdict adapter (P1), 06 sdplab-cvu3 architect adapter, 07 sdplab-s5x8 dispatch lite, 08 sdplab-kd38 replay+metrics. Critical path: 01→04→05→08. Plus 2 hygiene issues: sdplab-vuw4 (drift docs/design vs docs/plans), sdplab-bv3r (AGENTS.md design-first phrasing). Design: docs/plans/2026-04-26-f144-inference-confidence-design.md. Branch: feature/F144-inference-confidence (not yet created, untracked design doc on worktree-bulk-deliver branch)."} -{"_type":"memory","key":"subagent-implementer-broken-2026-04-26","value":"BROKEN: subagent_type=implementer fabricates tool_use. Confirmed via 3-test diagnostic 2026-04-26. T1 (implementer+sonnet): tool_uses=0, file not created, fabricated plausible output. T2 (general-purpose+haiku): tool_uses=1, file created. T3 (Explore+haiku): honest refusal (read-only). Pattern: implementer outputs old-format XML \u003cfunction_calls\u003e\u003cinvoke name='...'\u003e as response text instead of structured tool_use blocks. Affects ANY model. Workaround: use subagent_type=general-purpose for code implementation tasks. Affected sessions: F145 WS01/WS07/WS08 dispatch all fabricated, had to redo manually."} {"_type":"memory","key":"f129-epic-created-2026-04-16-sdplab-8k5","value":"F129 epic created 2026-04-16 (sdplab-8k5 + 10 children sdplab-8k5.1..10 + 3 cross-lane: F106-07 sdplab-lqb, F124-05 sdplab-nai, F125-05 sdplab-as0). Addresses 5 audit findings: orchestration autonomy, subagent default, superpowers skill gap, doc drift, ad-hoc regression. Design: docs/plans/2026-04-16-f129-autonomy-regression-design.md. Cross-lane additions reuse existing F106/F124/F125 tracks instead of duplicating."} +{"_type":"memory","key":"f142-r3-done","value":"F142 round 3 closed via PR #123 squash to ff20222. F142-08 mass backfill 49 beads. F142-09 ghost cleanup. F142-10 picker defense. Local main out of sync, needs reset to origin."} {"_type":"memory","key":"f136-peer-memory-horizons-2026-04-20-epic","value":"f136-peer-memory-horizons-2026-04-20 | Epic F136 (sdplab-t1ty) created 2026-04-20: Peer Memory Foundation H1. Children: F136-01..06 (sdplab-r9py/lq4h/olvg/h25q/l0mm/h673). Parked: F137 H2 (sdplab-75u9, revisit 2026-07-06), F138 H3 (sdplab-7c45, revisit 2026-10-05). Design: docs/plans/2026-04-20-f136-peer-memory-h1-design.md. Market gap: peer-collaboration production framework where attribution is first-class (ChatCollab/HULA academic, no commercial product as of April 2026). H1 success = binary: did sdp memory query surface real lost context in 30-day dogfood. Label: peer-memory."} -{"_type":"memory","key":"f144-merged-2026-04-26","value":"F144 PR #131 MERGED to main at 2026-04-26T17:43Z, merge commit ed393e3. internal/inference/confidence/ live on main: Status enum, Result[T], Policy/Strategy/Checker, 3 strategies (constraint/selfcheck/nsample), 3 adapters (wsverdict/architect/dispatch). 111 tests, ≥85% coverage. Branch feature/F144-inference-confidence still exists locally. Replaces stale 'sdplab-8nkm-untracked-design-doc' status."} +{"_type":"memory","key":"f142-closed-2026-04-26","value":"F142 epic (sdplab-85ji) closed 2026-04-26: Workstream coverage gap closed. 6 children closed (gkl5 audit, rqsw baits, s6g7 orphans, f2h0 picker, 3obq doctor, fb1o INDEX). Plus sdplab-8nkm picker incident closed. Branch feature/F142-workstream-coverage-gap, 5 commits not yet merged to main. Delivered: scripts/deliver-pick.sh v2 (skip leafless+design-pending), internal/backlog/audit.go + cmd/sdp/cmd_doctor_backlog.go ('sdp doctor backlog'), 6 ws scaffolds (00-082-01, 00-083-01, 00-084-01, 00-085-01, 00-101-02, 00-133-01), INDEX.md updated, CI gate. 0 findings on doctor backlog. 189 Go tests pass."} +{"_type":"memory","key":"f141-epic-2026-04-25","value":"F141 epic (sdplab-o4sp) created 2026-04-25: Multi-harness install bootstrap \u0026 adapter parity. Children: F141-01 sdplab-fojs (manifest), -02 sdplab-6ea5 (generator), -03 sdplab-cl4o (bootstrap), -04 sdplab-i9q3 (doctor gate), -05 sdplab-r4y5 (parity matrix), -06 sdplab-cnpm (migration), -07 sdplab-nqjt (README). Closes gap left by F127/F128: downstream repos install SDP piecemeal. Approach: single sdp.manifest.yaml -\u003e adapter generator -\u003e curl|bash bootstrap -\u003e sdp doctor drift gate. Design: docs/plans/2026-04-25-f141-multi-harness-install-bootstrap-design.md. Branch: feature/F141-multi-harness-install-bootstrap."} +{"_type":"memory","key":"f142-epic-2026-04-26","value":"F142 epic (sdplab-85ji) created 2026-04-26: Workstream coverage gap — picker + ws/INDEX backfill + doctor gate. Root cause: features created via /design bypass /feature autogen → 0 ws files → picker selects leafless feature → /build fails. Affected: F141 (0 ws), F135 (0 ws), F100/F76/F80 (partial). First child: sdplab-8nkm (picker fail incident on sdplab-nj4 F135-05). Future children: audit script, per-feature backfill, INDEX update, picker hardening, sdp doctor backlog gate."} {"_type":"memory","key":"f142-merged-2026-04-26","value":"F142 closed and merged 2026-04-26: feature/F142-workstream-coverage-gap pushed to origin (https://github.com/fall-out-bug/sdp_lab/tree/feature/F142-workstream-coverage-gap), local main merged with 7 ahead of origin/main (6 F142 commits + retains F141 merge f3ccef7). Ready for PR opening via GitHub UI when user wants. Branch can be deleted locally; remote branch retained for PR."} -{"_type":"memory","key":"f142-r3-done","value":"F142 round 3 closed via PR #123 squash to ff20222. F142-08 mass backfill 49 beads. F142-09 ghost cleanup. F142-10 picker defense. Local main out of sync, needs reset to origin."} {"_type":"memory","key":"f145-epic-created-2026-04-26","value":"F145 epic (sdplab-ldmq) created 2026-04-26: Multi-Provider Dispatch Matrix \u0026 Confidence-Driven Cascade. 14 children: WS01 sdplab-0261 (providers/ scaffold), WS02 sdplab-y93k (OpenAI), WS03 sdplab-w4ru (Anthropic), WS04 sdplab-bg69 (Cursor ~30 models), WS05 sdplab-60ia (Kimi), WS06 sdplab-5lve (Ollama replaces LocalConfig), WS07 sdplab-s686 (cursor/opencode --model plumbing P0 bug), WS08 sdplab-noiq (tier_class label), WS09 sdplab-3lva (profiles_default.json seed), WS10 sdplab-5ii8 (cascade pkg + Invoker), WS11 sdplab-fco9 (F144 confidence injection), WS12 sdplab-uamt (cascade-replay corpus), WS13 sdplab-135g (LimitsCache), WS14 sdplab-2p8g (smoke + Day-8 demo). Design: docs/plans/2026-04-26-f145-multi-provider-dispatch-cascade-design.md. Branch (planned): feature/F145-multi-provider-cascade. Discovery decisions: providers/ sub-pkg, cascade pkg, tier-label hybrid, composer gate default, UNSURE+FAIL+heuristic short-circuit, MaxDepth/Budget configurable, hybrid LimitsCache, F144 replay extension, harness-CLI keys (no SDP auth)."} {"_type":"memory","key":"f145-progress-2026-04-26","value":"F145 epic in progress 2026-04-26. Branch: feature/F145-multi-provider-cascade (origin in sync). 3/14 WS committed (not yet closed in beads, awaiting merge): WS07 sdplab-s686 (cursor/opencode --model plumbing P0), WS01 sdplab-0261 (providers/ scaffold + tests), WS08 sdplab-noiq (tier_class + SelectTiers). Commits: 67f085b1 / 9ac769b4 / 30782519 on top of design 752140a3. After WS07/01/08 close, 6 WS unblock in parallel: WS02-06 (5 Provider impls) + WS13 LimitsCache. Then chain: WS09 seed → WS10 cascade pkg → WS11 confidence → WS12 replay → WS14 smoke+demo. 174 tests pass in internal/dispatch/. Next session: after PR #132 merge + restart, run @delivery-loop to dispatch remaining WS via fixed implementer subagents. F145 PR will open when epic is ready (or sensible mid-milestone)."} +{"_type":"memory","key":"subagent-implementer-broken-2026-04-26","value":"BROKEN: subagent_type=implementer fabricates tool_use. Confirmed via 3-test diagnostic 2026-04-26. T1 (implementer+sonnet): tool_uses=0, file not created, fabricated plausible output. T2 (general-purpose+haiku): tool_uses=1, file created. T3 (Explore+haiku): honest refusal (read-only). Pattern: implementer outputs old-format XML \u003cfunction_calls\u003e\u003cinvoke name='...'\u003e as response text instead of structured tool_use blocks. Affects ANY model. Workaround: use subagent_type=general-purpose for code implementation tasks. Affected sessions: F145 WS01/WS07/WS08 dispatch all fabricated, had to redo manually."} +{"_type":"memory","key":"f144-epic-created-2026-04-26-sdplab-sjdp","value":"F144 epic created 2026-04-26 (sdplab-sjdp): Inference Confidence \u0026 Quality Control. 8 children attached: 01 sdplab-wzit core lib, 02 sdplab-339w self-check, 03 sdplab-w7j1 n-sample, 04 sdplab-tfmg constraint+composer, 05 sdplab-kk1v ws-verdict adapter (P1), 06 sdplab-cvu3 architect adapter, 07 sdplab-s5x8 dispatch lite, 08 sdplab-kd38 replay+metrics. Critical path: 01→04→05→08. Plus 2 hygiene issues: sdplab-vuw4 (drift docs/design vs docs/plans), sdplab-bv3r (AGENTS.md design-first phrasing). Design: docs/plans/2026-04-26-f144-inference-confidence-design.md. Branch: feature/F144-inference-confidence (not yet created, untracked design doc on worktree-bulk-deliver branch)."} +{"_type":"memory","key":"f144-merged-2026-04-26","value":"F144 PR #131 MERGED to main at 2026-04-26T17:43Z, merge commit ed393e3. internal/inference/confidence/ live on main: Status enum, Result[T], Policy/Strategy/Checker, 3 strategies (constraint/selfcheck/nsample), 3 adapters (wsverdict/architect/dispatch). 111 tests, ≥85% coverage. Branch feature/F144-inference-confidence still exists locally. Replaces stale 'sdplab-8nkm-untracked-design-doc' status."} {"_type":"memory","key":"subagent-format-spec-2026-04-26","value":"Claude Code subagent file format (.claude/agents/*.md): YAML frontmatter MUST use 'tools: Read, Bash, Glob, Grep' COMMA-STRING format (PascalCase). NOT 'tools: { Read: true }' object form, NOT 'tools: [Read, Bash]' array. Source: docs.claude.com/docs/en/subagents.md lines 222-301. Wrong format → empty tool registry → agent fabricates XML \u003cfunction_calls\u003e in response.text (tool_uses=0). Verified via /tmp/probe diagnostic. Built-in agents (general-purpose, Explore, claude-code-guide) work because their registry is hardcoded, file-based ones go through the broken parsing path. PR #132 fixes 13 SDP custom agents. Note: Claude Code caches agent definitions at session start — fixes require restart to take effect, not just file save."} diff --git a/internal/pireview/runner.go b/internal/pireview/runner.go index 5795fb25..d8e10342 100644 --- a/internal/pireview/runner.go +++ b/internal/pireview/runner.go @@ -4,7 +4,6 @@ import ( "context" "crypto/sha256" "encoding/json" - "errors" "fmt" "os" "path/filepath" @@ -270,7 +269,7 @@ func (r *Runner) runModelPanel(ctx context.Context, runID string, pkt *ContextPa result.Status = "failed" result.Error = err.Error() result.ArtifactPath = modelArtifactPath(r.cfg.ProjectRoot, runID, slot.Slot) - if slot.Required && !errors.Is(err, context.DeadlineExceeded) { + if slot.Required && ctx.Err() == nil { // Try OpenRouter fallback for required slots fbOutput, fbErr := r.invokeFallback(ctx, slot, pkt, evidence) if fbErr == nil { diff --git a/internal/pireview/runner_test.go b/internal/pireview/runner_test.go index 014ae34d..43865b10 100644 --- a/internal/pireview/runner_test.go +++ b/internal/pireview/runner_test.go @@ -254,6 +254,59 @@ func (blockingRunner) CombinedOutput(ctx context.Context, _ string, _ string, _ return nil, ctx.Err() } +type timeoutThenFallbackRunner struct { + calls int +} + +func (r *timeoutThenFallbackRunner) Output(context.Context, string, string, ...string) ([]byte, error) { + return nil, nil +} + +func (r *timeoutThenFallbackRunner) Run(context.Context, string, string, ...string) error { + return nil +} + +func (r *timeoutThenFallbackRunner) CombinedOutput(_ context.Context, _ string, _ string, args ...string) ([]byte, error) { + r.calls++ + if r.calls == 1 { + return nil, context.DeadlineExceeded + } + if len(args) < 4 || args[1] != "openrouter" { + return nil, errors.New("fallback did not use openrouter") + } + return []byte(`[{"priority":"P3","title":"fallback note","file":"main.go","start_line":1,"rationale":"ok"}]`), nil +} + +func TestRunModelPanelAttemptsFallbackAfterTimeout(t *testing.T) { + fr := &timeoutThenFallbackRunner{} + r := &Runner{ + cfg: Config{ + ProjectRoot: t.TempDir(), + Scope: ScopeWorkingTree, + ModelTimeout: time.Second, + Runner: fr, + }, + runner: fr, + slots: []ReviewerSlot{ + {Slot: "kimi", Provider: "kimi-coding", Model: "k2p6", Role: "reviewer", Required: true}, + }, + } + + results := r.runModelPanel(context.Background(), "test-run", &ContextPacket{}, &TestEvidence{}) + if len(results) != 1 { + t.Fatalf("got %d result(s), want 1", len(results)) + } + if results[0].Status != "ok" { + t.Fatalf("status = %q, error = %q, want ok", results[0].Status, results[0].Error) + } + if results[0].Provider != "openrouter" { + t.Fatalf("provider = %q, want openrouter", results[0].Provider) + } + if fr.calls != 2 { + t.Fatalf("calls = %d, want primary + fallback", fr.calls) + } +} + func TestInvokePiHonorsModelTimeout(t *testing.T) { r := &Runner{ cfg: Config{ From f7f3651119167fa0fd75b237d9458c7ee2f46ed3 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 29 Apr 2026 20:19:56 +0300 Subject: [PATCH 11/11] fix: bound exec wait after timeouts --- .beads/issues.jsonl | 18 +++++++++--------- internal/executil/runner.go | 6 ++++++ internal/executil/runner_test.go | 18 ++++++++++++++++++ 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 82efb8ce..854b3365 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -35,7 +35,7 @@ {"_type":"issue","id":"sdplab-22","title":"F100-01: Reference Integrity CI Gate","status":"closed","priority":0,"issue_type":"task","assignee":"Andrei","created_at":"2026-04-05T15:00:00Z","created_by":"UX Council","updated_at":"2026-04-26T13:30:49Z","closed_at":"2026-04-26T13:30:49Z","close_reason":"F100 Reference Integrity complete: CI gate + one-time cleanup. 56 tests passing.","labels":["F100","P0","harness:claude","stream-C","ux"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-23","title":"F100-02: One-Time Reference Cleanup","status":"closed","priority":0,"issue_type":"task","assignee":"Andrei","created_at":"2026-04-05T15:00:00Z","created_by":"UX Council","updated_at":"2026-04-26T13:30:50Z","closed_at":"2026-04-26T13:30:50Z","close_reason":"F100 Reference Integrity complete: CI gate + one-time cleanup. 56 tests passing.","labels":["F100","P0","harness:any","stream-C","ux"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-24","title":"F101-01: Write Plan Emission and Confirmation","status":"closed","priority":0,"issue_type":"feature","assignee":"Andrei","created_at":"2026-04-05T15:00:00Z","created_by":"UX Council","updated_at":"2026-04-19T07:33:30Z","closed_at":"2026-04-19T07:33:30Z","close_reason":"F101-01 merged to main via PR #95 after sdp PR #130 landed on sdp/main. Write Plan prompt surface is now shipped for the nine stateful skills.","labels":["F101","P0","harness:any","stream-B","ux"],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"sdplab-uhri","title":"pi-review P1: Pi-review runner excludes timeout from OpenRouter fallback","description":"File: internal/pireview/runner.go:269-282\nRationale: runModelPanel checks `!errors.Is(err, context.DeadlineExceeded)` before attempting OpenRouter fallback for required slots. The pi-review spec explicitly requires fallback when a required reviewer fails OR times out. A timed-out model therefore leaves the panel under-quorum without attempting the configured fallback, which can block the delivery loop unnecessarily.\nSuggested fix: Remove the `!errors.Is(err, context.DeadlineExceeded)` guard so that all errors from required slots trigger the OpenRouter fallback path.\nDedupe key: P1:internal/pireview/runner.go:Pi-review runner excludes timeout from OpenRouter fallback","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T16:51:45Z","created_by":"Andrei","updated_at":"2026-04-29T16:54:00Z","started_at":"2026-04-29T16:54:00Z","labels":["","pi-review","review-finding","round-11"],"comments":[{"id":"019dda29-6f21-7772-abdd-f2e44f6a34ea","issue_id":"sdplab-uhri","author":"Andrei","text":"Fixed in branch: required-slot fallback now runs whenever the parent review context is still active, including primary model timeouts. Parent context cancellation still suppresses fallback. Added TestRunModelPanelAttemptsFallbackAfterTimeout. Verification: go test -tags sqlite_fts5 ./internal/pireview ./cmd/sdp-pi-review -count=1 passed.","created_at":"2026-04-29T16:54:02Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1} +{"_type":"issue","id":"sdplab-uhri","title":"pi-review P1: Pi-review runner excludes timeout from OpenRouter fallback","description":"File: internal/pireview/runner.go:269-282\nRationale: runModelPanel checks `!errors.Is(err, context.DeadlineExceeded)` before attempting OpenRouter fallback for required slots. The pi-review spec explicitly requires fallback when a required reviewer fails OR times out. A timed-out model therefore leaves the panel under-quorum without attempting the configured fallback, which can block the delivery loop unnecessarily.\nSuggested fix: Remove the `!errors.Is(err, context.DeadlineExceeded)` guard so that all errors from required slots trigger the OpenRouter fallback path.\nDedupe key: P1:internal/pireview/runner.go:Pi-review runner excludes timeout from OpenRouter fallback","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T16:51:45Z","created_by":"Andrei","updated_at":"2026-04-29T16:54:00Z","started_at":"2026-04-29T16:54:00Z","labels":["","pi-review","review-finding","round-11"],"comments":[{"id":"019dda29-6f21-7772-abdd-f2e44f6a34ea","issue_id":"sdplab-uhri","author":"Andrei","text":"Fixed in branch: required-slot fallback now runs whenever the parent review context is still active, including primary model timeouts. Parent context cancellation still suppresses fallback. Added TestRunModelPanelAttemptsFallbackAfterTimeout. Verification: go test -tags sqlite_fts5 ./internal/pireview ./cmd/sdp-pi-review -count=1 passed.","created_at":"2026-04-29T16:54:02Z"},{"id":"019dda3e-9609-7f17-9186-7504afdab882","issue_id":"sdplab-uhri","author":"Andrei","text":"Follow-up from round 12 verification: fallback timeout could still hang inside exec.Cmd.Wait when the child/grandchild kept pipes open. Fixed shared executil realRunner with a 5s Cmd.WaitDelay and added TestDefaultRunner_CombinedOutputWaitDelayBoundsPipeWait. Verification: go test -tags sqlite_fts5 ./internal/executil ./internal/pireview ./cmd/sdp-pi-review -count=1 passed.","created_at":"2026-04-29T17:17:08Z"}],"dependency_count":0,"dependent_count":0,"comment_count":2} {"_type":"issue","id":"sdplab-j811","title":"pi-review P1: Manifest dispatch override paths bypass traversal validation in .sdp/generated write","description":"File: cmd/sdp/cmd_init.go:178-189\nRationale: The PR correctly adds resolveManifestPath() for manifest source paths and validatePathSegment() for control repo IDs, but the .sdp/generated write loop at lines 178-189 writes generated map entries directly via filepath.Join(generatedRoot, rel) without checking that rel stays within the target. Since commandOutputPath() in internal/adapters/generate.go returns dispatch values verbatim (e.g., a manifest with dispatch: {claude-code: \"../../../etc/evil\"} produces rel=\"../../../etc/evil\"), filepath.Join(target, \".sdp/generated\", \"../../../etc/evil\") resolves to /etc/evil — escaping the repo root entirely. The live harness dir writes are safe due to the strings.HasPrefix(rel, harnessDir+\"/\") guard, but .sdp/generated has no equivalent check.\nSuggested fix: Add a traversal guard before writing to .sdp/generated, mirroring resolveManifestPath: after computing dest, verify filepath.Rel(target, dest) does not start with \"..\". Alternatively, validate dispatch paths in internal/manifest/load.go alongside the existing source path checks in checkPaths().\nDedupe key: P1:cmd/sdp/cmd_init.go:Manifest dispatch override paths bypass traversal validation in .sdp/generated write","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T16:33:56Z","created_by":"Andrei","updated_at":"2026-04-29T16:37:45Z","started_at":"2026-04-29T16:37:45Z","labels":["","pi-review","review-finding","round-10"],"comments":[{"id":"019dda1a-8dd9-7537-a56c-5689b5592af9","issue_id":"sdplab-j811","author":"Andrei","text":"Fixed in branch: internal/adapters.Generate now validates every generated output path and rejects absolute paths or parent traversal before callers write .sdp/generated or live harness files. Added TestGenerate_CommandDispatchOverrideRejectsTraversal. Verification: go test -tags sqlite_fts5 ./internal/adapters ./cmd/sdp -count=1 passed.","created_at":"2026-04-29T16:37:47Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1} {"_type":"issue","id":"sdplab-5dj9","title":"CI doctor backlog gate lacks Beads bootstrap","description":"PR #144 made sdp doctor backlog blocking in GitHub Actions, but .github/workflows/sdp-doctor.yml does not install bd or bootstrap the Beads database in a fresh checkout. Doctor job fails with: bd not found on PATH.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Andrei","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T15:57:53Z","created_by":"Andrei","updated_at":"2026-04-29T15:58:00Z","started_at":"2026-04-29T15:58:00Z","comments":[{"id":"019dd9fb-545d-7153-a2db-6db1c1821645","issue_id":"sdplab-5dj9","author":"Andrei","text":"Fixed in branch: sdp-doctor workflow now installs bd v1.0.3 and runs bd bootstrap --yes before backlog doctor. Also fixed sdp doctor backlog to count parent-child children from child entries instead of parent dependency_count; clean clone simulation passes manifest, doctor adapters, and doctor backlog.","created_at":"2026-04-29T16:03:40Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1} {"_type":"issue","id":"sdplab-i8xo","title":"pi-review P1: Bootstrap dry-run writes to target repo","description":"File: cmd/sdp/cmd_bootstrap.go:84-87\nRationale: appendGreenfieldArtifacts and appendBrownfieldArtifacts call writeDraftArtifact without passing dryRun, so bootstrap --dry-run --mode greenfield|brownfield still writes DRAFT-* files to the target repo, contradicting docs/QUICKSTART.md which promises bootstrap --dry-run is read-only.\nSuggested fix: Pass dryRun to both appendGreenfieldArtifacts and appendBrownfieldArtifacts, and thread it through to writeDraftArtifact (already done for greenfield/answers save). Verify TestBootstrapDryRunBrownfieldDoesNotWriteDraftDelta passes.\nDedupe key: P1:cmd/sdp/cmd_bootstrap.go:Bootstrap dry-run writes to target repo","status":"closed","priority":1,"issue_type":"bug","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-29T14:55:28Z","created_by":"Andrei","updated_at":"2026-04-29T14:56:20Z","closed_at":"2026-04-29T14:56:20Z","close_reason":"false positive: cmd/sdp/cmd_bootstrap.go already passes dryRun into appendGreenfieldArtifacts/appendBrownfieldArtifacts and writeDraftArtifact; TestBootstrapDryRunBrownfieldDoesNotWriteDraftDelta verifies no DRAFT-bootstrap-delta.json is written","labels":["F150","pi-review","review-finding","round-7"],"dependency_count":0,"dependent_count":0,"comment_count":0} @@ -561,16 +561,16 @@ {"_type":"issue","id":"sdplab-houh","title":"F036: Orchestrate + in-toto (historical placeholder)","description":"absorbed by F064–F067 auto-attestation","status":"closed","priority":4,"issue_type":"feature","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-18T11:27:58Z","created_by":"Andrei","updated_at":"2026-04-18T11:27:58Z","closed_at":"2026-04-18T11:27:58Z","close_reason":"absorbed by F064–F067 auto-attestation","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-crg","title":"F034: Graduated Enforcement (historical placeholder)","description":"historical placeholder; no active scope","status":"closed","priority":4,"issue_type":"feature","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-18T11:27:55Z","created_by":"Andrei","updated_at":"2026-04-18T11:27:56Z","closed_at":"2026-04-18T11:27:56Z","close_reason":"historical placeholder; no active scope","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"sdplab-h5y","title":"F032: PR Evidence Summary (historical placeholder)","description":"historical placeholder; no active scope (2026-04-18 triage)","status":"closed","priority":4,"issue_type":"feature","owner":"a_v_zhukov@outlook.com","created_at":"2026-04-18T11:27:34Z","created_by":"Andrei","updated_at":"2026-04-18T11:27:54Z","closed_at":"2026-04-18T11:27:54Z","close_reason":"historical placeholder; no active scope (2026-04-18 triage)","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"memory","key":"f144-merged-2026-04-26","value":"F144 PR #131 MERGED to main at 2026-04-26T17:43Z, merge commit ed393e3. internal/inference/confidence/ live on main: Status enum, Result[T], Policy/Strategy/Checker, 3 strategies (constraint/selfcheck/nsample), 3 adapters (wsverdict/architect/dispatch). 111 tests, ≥85% coverage. Branch feature/F144-inference-confidence still exists locally. Replaces stale 'sdplab-8nkm-untracked-design-doc' status."} {"_type":"memory","key":"f129-epic-created-2026-04-16-sdplab-8k5","value":"F129 epic created 2026-04-16 (sdplab-8k5 + 10 children sdplab-8k5.1..10 + 3 cross-lane: F106-07 sdplab-lqb, F124-05 sdplab-nai, F125-05 sdplab-as0). Addresses 5 audit findings: orchestration autonomy, subagent default, superpowers skill gap, doc drift, ad-hoc regression. Design: docs/plans/2026-04-16-f129-autonomy-regression-design.md. Cross-lane additions reuse existing F106/F124/F125 tracks instead of duplicating."} -{"_type":"memory","key":"f142-r3-done","value":"F142 round 3 closed via PR #123 squash to ff20222. F142-08 mass backfill 49 beads. F142-09 ghost cleanup. F142-10 picker defense. Local main out of sync, needs reset to origin."} -{"_type":"memory","key":"f136-peer-memory-horizons-2026-04-20-epic","value":"f136-peer-memory-horizons-2026-04-20 | Epic F136 (sdplab-t1ty) created 2026-04-20: Peer Memory Foundation H1. Children: F136-01..06 (sdplab-r9py/lq4h/olvg/h25q/l0mm/h673). Parked: F137 H2 (sdplab-75u9, revisit 2026-07-06), F138 H3 (sdplab-7c45, revisit 2026-10-05). Design: docs/plans/2026-04-20-f136-peer-memory-h1-design.md. Market gap: peer-collaboration production framework where attribution is first-class (ChatCollab/HULA academic, no commercial product as of April 2026). H1 success = binary: did sdp memory query surface real lost context in 30-day dogfood. Label: peer-memory."} -{"_type":"memory","key":"f142-closed-2026-04-26","value":"F142 epic (sdplab-85ji) closed 2026-04-26: Workstream coverage gap closed. 6 children closed (gkl5 audit, rqsw baits, s6g7 orphans, f2h0 picker, 3obq doctor, fb1o INDEX). Plus sdplab-8nkm picker incident closed. Branch feature/F142-workstream-coverage-gap, 5 commits not yet merged to main. Delivered: scripts/deliver-pick.sh v2 (skip leafless+design-pending), internal/backlog/audit.go + cmd/sdp/cmd_doctor_backlog.go ('sdp doctor backlog'), 6 ws scaffolds (00-082-01, 00-083-01, 00-084-01, 00-085-01, 00-101-02, 00-133-01), INDEX.md updated, CI gate. 0 findings on doctor backlog. 189 Go tests pass."} -{"_type":"memory","key":"f141-epic-2026-04-25","value":"F141 epic (sdplab-o4sp) created 2026-04-25: Multi-harness install bootstrap \u0026 adapter parity. Children: F141-01 sdplab-fojs (manifest), -02 sdplab-6ea5 (generator), -03 sdplab-cl4o (bootstrap), -04 sdplab-i9q3 (doctor gate), -05 sdplab-r4y5 (parity matrix), -06 sdplab-cnpm (migration), -07 sdplab-nqjt (README). Closes gap left by F127/F128: downstream repos install SDP piecemeal. Approach: single sdp.manifest.yaml -\u003e adapter generator -\u003e curl|bash bootstrap -\u003e sdp doctor drift gate. Design: docs/plans/2026-04-25-f141-multi-harness-install-bootstrap-design.md. Branch: feature/F141-multi-harness-install-bootstrap."} -{"_type":"memory","key":"f142-epic-2026-04-26","value":"F142 epic (sdplab-85ji) created 2026-04-26: Workstream coverage gap — picker + ws/INDEX backfill + doctor gate. Root cause: features created via /design bypass /feature autogen → 0 ws files → picker selects leafless feature → /build fails. Affected: F141 (0 ws), F135 (0 ws), F100/F76/F80 (partial). First child: sdplab-8nkm (picker fail incident on sdplab-nj4 F135-05). Future children: audit script, per-feature backfill, INDEX update, picker hardening, sdp doctor backlog gate."} {"_type":"memory","key":"f142-merged-2026-04-26","value":"F142 closed and merged 2026-04-26: feature/F142-workstream-coverage-gap pushed to origin (https://github.com/fall-out-bug/sdp_lab/tree/feature/F142-workstream-coverage-gap), local main merged with 7 ahead of origin/main (6 F142 commits + retains F141 merge f3ccef7). Ready for PR opening via GitHub UI when user wants. Branch can be deleted locally; remote branch retained for PR."} -{"_type":"memory","key":"f145-epic-created-2026-04-26","value":"F145 epic (sdplab-ldmq) created 2026-04-26: Multi-Provider Dispatch Matrix \u0026 Confidence-Driven Cascade. 14 children: WS01 sdplab-0261 (providers/ scaffold), WS02 sdplab-y93k (OpenAI), WS03 sdplab-w4ru (Anthropic), WS04 sdplab-bg69 (Cursor ~30 models), WS05 sdplab-60ia (Kimi), WS06 sdplab-5lve (Ollama replaces LocalConfig), WS07 sdplab-s686 (cursor/opencode --model plumbing P0 bug), WS08 sdplab-noiq (tier_class label), WS09 sdplab-3lva (profiles_default.json seed), WS10 sdplab-5ii8 (cascade pkg + Invoker), WS11 sdplab-fco9 (F144 confidence injection), WS12 sdplab-uamt (cascade-replay corpus), WS13 sdplab-135g (LimitsCache), WS14 sdplab-2p8g (smoke + Day-8 demo). Design: docs/plans/2026-04-26-f145-multi-provider-dispatch-cascade-design.md. Branch (planned): feature/F145-multi-provider-cascade. Discovery decisions: providers/ sub-pkg, cascade pkg, tier-label hybrid, composer gate default, UNSURE+FAIL+heuristic short-circuit, MaxDepth/Budget configurable, hybrid LimitsCache, F144 replay extension, harness-CLI keys (no SDP auth)."} {"_type":"memory","key":"f145-progress-2026-04-26","value":"F145 epic in progress 2026-04-26. Branch: feature/F145-multi-provider-cascade (origin in sync). 3/14 WS committed (not yet closed in beads, awaiting merge): WS07 sdplab-s686 (cursor/opencode --model plumbing P0), WS01 sdplab-0261 (providers/ scaffold + tests), WS08 sdplab-noiq (tier_class + SelectTiers). Commits: 67f085b1 / 9ac769b4 / 30782519 on top of design 752140a3. After WS07/01/08 close, 6 WS unblock in parallel: WS02-06 (5 Provider impls) + WS13 LimitsCache. Then chain: WS09 seed → WS10 cascade pkg → WS11 confidence → WS12 replay → WS14 smoke+demo. 174 tests pass in internal/dispatch/. Next session: after PR #132 merge + restart, run @delivery-loop to dispatch remaining WS via fixed implementer subagents. F145 PR will open when epic is ready (or sensible mid-milestone)."} {"_type":"memory","key":"subagent-implementer-broken-2026-04-26","value":"BROKEN: subagent_type=implementer fabricates tool_use. Confirmed via 3-test diagnostic 2026-04-26. T1 (implementer+sonnet): tool_uses=0, file not created, fabricated plausible output. T2 (general-purpose+haiku): tool_uses=1, file created. T3 (Explore+haiku): honest refusal (read-only). Pattern: implementer outputs old-format XML \u003cfunction_calls\u003e\u003cinvoke name='...'\u003e as response text instead of structured tool_use blocks. Affects ANY model. Workaround: use subagent_type=general-purpose for code implementation tasks. Affected sessions: F145 WS01/WS07/WS08 dispatch all fabricated, had to redo manually."} -{"_type":"memory","key":"f144-epic-created-2026-04-26-sdplab-sjdp","value":"F144 epic created 2026-04-26 (sdplab-sjdp): Inference Confidence \u0026 Quality Control. 8 children attached: 01 sdplab-wzit core lib, 02 sdplab-339w self-check, 03 sdplab-w7j1 n-sample, 04 sdplab-tfmg constraint+composer, 05 sdplab-kk1v ws-verdict adapter (P1), 06 sdplab-cvu3 architect adapter, 07 sdplab-s5x8 dispatch lite, 08 sdplab-kd38 replay+metrics. Critical path: 01→04→05→08. Plus 2 hygiene issues: sdplab-vuw4 (drift docs/design vs docs/plans), sdplab-bv3r (AGENTS.md design-first phrasing). Design: docs/plans/2026-04-26-f144-inference-confidence-design.md. Branch: feature/F144-inference-confidence (not yet created, untracked design doc on worktree-bulk-deliver branch)."} -{"_type":"memory","key":"f144-merged-2026-04-26","value":"F144 PR #131 MERGED to main at 2026-04-26T17:43Z, merge commit ed393e3. internal/inference/confidence/ live on main: Status enum, Result[T], Policy/Strategy/Checker, 3 strategies (constraint/selfcheck/nsample), 3 adapters (wsverdict/architect/dispatch). 111 tests, ≥85% coverage. Branch feature/F144-inference-confidence still exists locally. Replaces stale 'sdplab-8nkm-untracked-design-doc' status."} +{"_type":"memory","key":"f142-epic-2026-04-26","value":"F142 epic (sdplab-85ji) created 2026-04-26: Workstream coverage gap — picker + ws/INDEX backfill + doctor gate. Root cause: features created via /design bypass /feature autogen → 0 ws files → picker selects leafless feature → /build fails. Affected: F141 (0 ws), F135 (0 ws), F100/F76/F80 (partial). First child: sdplab-8nkm (picker fail incident on sdplab-nj4 F135-05). Future children: audit script, per-feature backfill, INDEX update, picker hardening, sdp doctor backlog gate."} +{"_type":"memory","key":"f145-epic-created-2026-04-26","value":"F145 epic (sdplab-ldmq) created 2026-04-26: Multi-Provider Dispatch Matrix \u0026 Confidence-Driven Cascade. 14 children: WS01 sdplab-0261 (providers/ scaffold), WS02 sdplab-y93k (OpenAI), WS03 sdplab-w4ru (Anthropic), WS04 sdplab-bg69 (Cursor ~30 models), WS05 sdplab-60ia (Kimi), WS06 sdplab-5lve (Ollama replaces LocalConfig), WS07 sdplab-s686 (cursor/opencode --model plumbing P0 bug), WS08 sdplab-noiq (tier_class label), WS09 sdplab-3lva (profiles_default.json seed), WS10 sdplab-5ii8 (cascade pkg + Invoker), WS11 sdplab-fco9 (F144 confidence injection), WS12 sdplab-uamt (cascade-replay corpus), WS13 sdplab-135g (LimitsCache), WS14 sdplab-2p8g (smoke + Day-8 demo). Design: docs/plans/2026-04-26-f145-multi-provider-dispatch-cascade-design.md. Branch (planned): feature/F145-multi-provider-cascade. Discovery decisions: providers/ sub-pkg, cascade pkg, tier-label hybrid, composer gate default, UNSURE+FAIL+heuristic short-circuit, MaxDepth/Budget configurable, hybrid LimitsCache, F144 replay extension, harness-CLI keys (no SDP auth)."} {"_type":"memory","key":"subagent-format-spec-2026-04-26","value":"Claude Code subagent file format (.claude/agents/*.md): YAML frontmatter MUST use 'tools: Read, Bash, Glob, Grep' COMMA-STRING format (PascalCase). NOT 'tools: { Read: true }' object form, NOT 'tools: [Read, Bash]' array. Source: docs.claude.com/docs/en/subagents.md lines 222-301. Wrong format → empty tool registry → agent fabricates XML \u003cfunction_calls\u003e in response.text (tool_uses=0). Verified via /tmp/probe diagnostic. Built-in agents (general-purpose, Explore, claude-code-guide) work because their registry is hardcoded, file-based ones go through the broken parsing path. PR #132 fixes 13 SDP custom agents. Note: Claude Code caches agent definitions at session start — fixes require restart to take effect, not just file save."} +{"_type":"memory","key":"f142-closed-2026-04-26","value":"F142 epic (sdplab-85ji) closed 2026-04-26: Workstream coverage gap closed. 6 children closed (gkl5 audit, rqsw baits, s6g7 orphans, f2h0 picker, 3obq doctor, fb1o INDEX). Plus sdplab-8nkm picker incident closed. Branch feature/F142-workstream-coverage-gap, 5 commits not yet merged to main. Delivered: scripts/deliver-pick.sh v2 (skip leafless+design-pending), internal/backlog/audit.go + cmd/sdp/cmd_doctor_backlog.go ('sdp doctor backlog'), 6 ws scaffolds (00-082-01, 00-083-01, 00-084-01, 00-085-01, 00-101-02, 00-133-01), INDEX.md updated, CI gate. 0 findings on doctor backlog. 189 Go tests pass."} +{"_type":"memory","key":"f142-r3-done","value":"F142 round 3 closed via PR #123 squash to ff20222. F142-08 mass backfill 49 beads. F142-09 ghost cleanup. F142-10 picker defense. Local main out of sync, needs reset to origin."} +{"_type":"memory","key":"f136-peer-memory-horizons-2026-04-20-epic","value":"f136-peer-memory-horizons-2026-04-20 | Epic F136 (sdplab-t1ty) created 2026-04-20: Peer Memory Foundation H1. Children: F136-01..06 (sdplab-r9py/lq4h/olvg/h25q/l0mm/h673). Parked: F137 H2 (sdplab-75u9, revisit 2026-07-06), F138 H3 (sdplab-7c45, revisit 2026-10-05). Design: docs/plans/2026-04-20-f136-peer-memory-h1-design.md. Market gap: peer-collaboration production framework where attribution is first-class (ChatCollab/HULA academic, no commercial product as of April 2026). H1 success = binary: did sdp memory query surface real lost context in 30-day dogfood. Label: peer-memory."} +{"_type":"memory","key":"f141-epic-2026-04-25","value":"F141 epic (sdplab-o4sp) created 2026-04-25: Multi-harness install bootstrap \u0026 adapter parity. Children: F141-01 sdplab-fojs (manifest), -02 sdplab-6ea5 (generator), -03 sdplab-cl4o (bootstrap), -04 sdplab-i9q3 (doctor gate), -05 sdplab-r4y5 (parity matrix), -06 sdplab-cnpm (migration), -07 sdplab-nqjt (README). Closes gap left by F127/F128: downstream repos install SDP piecemeal. Approach: single sdp.manifest.yaml -\u003e adapter generator -\u003e curl|bash bootstrap -\u003e sdp doctor drift gate. Design: docs/plans/2026-04-25-f141-multi-harness-install-bootstrap-design.md. Branch: feature/F141-multi-harness-install-bootstrap."} +{"_type":"memory","key":"f144-epic-created-2026-04-26-sdplab-sjdp","value":"F144 epic created 2026-04-26 (sdplab-sjdp): Inference Confidence \u0026 Quality Control. 8 children attached: 01 sdplab-wzit core lib, 02 sdplab-339w self-check, 03 sdplab-w7j1 n-sample, 04 sdplab-tfmg constraint+composer, 05 sdplab-kk1v ws-verdict adapter (P1), 06 sdplab-cvu3 architect adapter, 07 sdplab-s5x8 dispatch lite, 08 sdplab-kd38 replay+metrics. Critical path: 01→04→05→08. Plus 2 hygiene issues: sdplab-vuw4 (drift docs/design vs docs/plans), sdplab-bv3r (AGENTS.md design-first phrasing). Design: docs/plans/2026-04-26-f144-inference-confidence-design.md. Branch: feature/F144-inference-confidence (not yet created, untracked design doc on worktree-bulk-deliver branch)."} diff --git a/internal/executil/runner.go b/internal/executil/runner.go index 44c43e4e..0615998b 100644 --- a/internal/executil/runner.go +++ b/internal/executil/runner.go @@ -6,6 +6,7 @@ import ( "os/exec" "strings" "sync" + "time" ) // CommandRunner abstracts exec.Command for testability. @@ -30,12 +31,15 @@ func init() { type realRunner struct{} +var commandWaitDelay = 5 * time.Second + func (r *realRunner) Output(ctx context.Context, dir, name string, args ...string) ([]byte, error) { if ctx == nil { ctx = context.Background() } cmd := exec.CommandContext(ctx, name, args...) cmd.Dir = dir + cmd.WaitDelay = commandWaitDelay out, err := cmd.Output() if err != nil { return out, fmt.Errorf("%s %s: %w", name, strings.Join(args, " "), err) @@ -49,6 +53,7 @@ func (r *realRunner) CombinedOutput(ctx context.Context, dir, name string, args } cmd := exec.CommandContext(ctx, name, args...) cmd.Dir = dir + cmd.WaitDelay = commandWaitDelay out, err := cmd.CombinedOutput() if err != nil { return out, fmt.Errorf("%s %s: %w", name, strings.Join(args, " "), err) @@ -62,6 +67,7 @@ func (r *realRunner) Run(ctx context.Context, dir, name string, args ...string) } cmd := exec.CommandContext(ctx, name, args...) cmd.Dir = dir + cmd.WaitDelay = commandWaitDelay cmd.Stdout = nil cmd.Stderr = nil if err := cmd.Run(); err != nil { diff --git a/internal/executil/runner_test.go b/internal/executil/runner_test.go index 5117ac1e..0ed3b7a9 100644 --- a/internal/executil/runner_test.go +++ b/internal/executil/runner_test.go @@ -2,7 +2,10 @@ package executil import ( "context" + "errors" + "os/exec" "testing" + "time" ) func TestDefaultRunner_Output(t *testing.T) { @@ -21,3 +24,18 @@ func TestDefaultRunner_Run(t *testing.T) { t.Fatalf("Run true: %v", err) } } + +func TestDefaultRunner_CombinedOutputWaitDelayBoundsPipeWait(t *testing.T) { + oldDelay := commandWaitDelay + commandWaitDelay = 20 * time.Millisecond + t.Cleanup(func() { commandWaitDelay = oldDelay }) + + start := time.Now() + _, err := GetDefaultRunner().CombinedOutput(context.Background(), "", "sh", "-c", "sleep 30 &") + if !errors.Is(err, exec.ErrWaitDelay) { + t.Fatalf("CombinedOutput error = %v, want ErrWaitDelay", err) + } + if elapsed := time.Since(start); elapsed > time.Second { + t.Fatalf("CombinedOutput was not bounded by WaitDelay; elapsed=%s", elapsed) + } +}