@gotgenes/pi-subagents 15.0.0 → 15.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [15.0.2](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v15.0.1...pi-subagents-v15.0.2) (2026-06-12)
9
+
10
+
11
+ ### Miscellaneous Chores
12
+
13
+ * **deps:** bump Pi SDK to 0.79.1 ([#370](https://github.com/gotgenes/pi-packages/issues/370)) ([704f3b3](https://github.com/gotgenes/pi-packages/commit/704f3b3457ceb12b9df9efffe7a56812a5667d5d))
14
+ * **deps:** bump rollup to 4.61.1 ([#370](https://github.com/gotgenes/pi-packages/issues/370)) ([250b729](https://github.com/gotgenes/pi-packages/commit/250b7296093b091297c57463693eaa2db59d5fe3))
15
+
16
+ ## [15.0.1](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v15.0.0...pi-subagents-v15.0.1) (2026-06-10)
17
+
18
+
19
+ ### Miscellaneous Chores
20
+
21
+ * **deps:** bump pnpm to 11.5.2 and fallow to 2.91.0 ([b34cef4](https://github.com/gotgenes/pi-packages/commit/b34cef4df692dbb279c859d56be49894d63c0c45))
22
+ * **deps:** bump tooling dependencies to latest minor/patch ([8b9105d](https://github.com/gotgenes/pi-packages/commit/8b9105d4011816fe8085dfed3a3b9d7bc9918c56))
23
+
8
24
  ## [15.0.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v14.0.1...pi-subagents-v15.0.0) (2026-06-09)
9
25
 
10
26
 
@@ -10,8 +10,9 @@ This document describes the architecture of the pi-subagents fork: a focused, co
10
10
  3. **Typed API boundary** — this package exports a `SubagentsService` interface and `Symbol.for()` accessors (`publishSubagentsService` / `getSubagentsService`).
11
11
  Consumers declare this package as an optional peer dependency and use dynamic import for compile-time types.
12
12
  The runtime bridge is `Symbol.for("@gotgenes/pi-subagents:service")` on `globalThis` — no separate API package.
13
- 4. **No scheduling** — in-process scheduling is removed from the core.
14
- Scheduling is a separate concern that any extension can implement by calling `spawn()` on the published API.
13
+ 4. **No time-based scheduling** — cron-style timed dispatch (upstream's `schedule.ts` subsystem) is removed from the core (#52).
14
+ Timed dispatch is a separate concern that any extension can implement by calling `spawn()` on the published API.
15
+ The max-concurrent admission gate is not scheduling in this sense — concurrency management stays in core.
15
16
  5. **UI extraction is deferred** — the widget, conversation viewer, and `/agents` command menu stay in the core for now.
16
17
  They are the first candidate for extraction once the API boundary is proven stable.
17
18
  6. **Snapshot, don't capture** — mutable parent state (ctx, session, model) is read once at spawn time and frozen into a `ParentSnapshot` data object.
@@ -526,7 +527,7 @@ In the target state, pi-subagents publishes events and a provider seam; other pa
526
527
 
527
528
  Composition test: install neither extension, only permissions, only workspaces, or both — the core is byte-for-byte identical in all four cases, and the two extensions never reference each other.
528
529
 
529
- This is achieved across phases: Phase 14 (strip policy), Phase 16 (invert dependencies — extensions on a minimal core), and Phase 17 (extract UI).
530
+ This is achieved across phases: Phase 14 (strip policy), Phase 16 (invert dependencies — extensions on a minimal core), and Phase 18 (extract UI).
530
531
 
531
532
  ## Current structural analysis
532
533
 
@@ -765,16 +766,164 @@ All five steps are closed: [#261], [#262], [#263], [#264], [#265].
765
766
  The earlier "agent collaborator architecture" framing (#256 superseded, #257 parked, #258 and #259 closed not-planned) was abandoned; its structural win was reached cleanly via the workspace seam.
766
767
  See [phase-16-invert-dependencies.md](history/phase-16-invert-dependencies.md) for details.
767
768
 
768
- ## Improvement roadmap (Phase 17 — extract UI)
769
+ ## Improvement roadmap (Phase 17 — core consolidation)
769
770
 
770
- Phase 17 is the long-deferred UI extraction (originally Phase 6).
771
- The widget, conversation viewer, and `/agents` command menu move to a separate `pi-subagents-ui` extension that consumes the `SubagentsService` API.
772
- By this point the core is minimal and stable — the API boundary has been proven across Phases 14–16.
771
+ Phase 17 consolidates the core's remaining structural debt before the UI extraction (now Phase 18).
772
+ The findings come from the standard discovery pass fallow suite, entry-point trace, design-review checklist, and test-constructibility audit run after Phase 16 landed.
773
+
774
+ ### Findings summary
775
+
776
+ Updated health metrics (fallow, package-wide including tests):
777
+
778
+ | Metric | Phase 16 baseline | Current |
779
+ | -------------------------- | ------------------------------ | --------------------------------------------- |
780
+ | Health score | 78/100 (B) | 78/100 (B) |
781
+ | Source LOC | 7,778 (57 files) | ~7,400 (56 files) |
782
+ | Dead code | 0 files, 0 exports | 0 files, 0 exports |
783
+ | Maintainability index | 90.8 (good) | 90.8 (good) |
784
+ | Avg / P90 cyclomatic | 1.4 / 2 | 1.4 / 2 |
785
+ | Production duplication | 11 lines (1 internal group) | 34 lines (1 internal + 1 cross-package group) |
786
+ | Test duplication | 42 groups, 661 lines | 44 groups, ~750 lines |
787
+ | Fallow refactoring targets | 0 | 0 |
788
+ | Top churn hotspot | `index.ts` 65.0 ▲ accelerating | `index.ts` 31.3 ▼ cooling |
789
+
790
+ The syntactic metrics are healthy and stable — the remaining debt is structural, mostly invisible to fallow, and concentrated in three places:
791
+
792
+ 1. **`Subagent` construction duality.**
793
+ `SubagentInit` carries ~20 fields, nearly all optional with "required for run(), optional for tests" semantics, and `run()` compensates with runtime throws ("not configured for execution").
794
+ This violates principle 8 (construct complete): the class is simultaneously a passive record (tests build display-only snapshots) and an executor (production wires factory, observer, run config, workspace provider).
795
+ The symptoms are in the tests: external writes `record.promise = …` (manager, queue callback, four test files) and `record.notification = new NotificationState(…)` (seven test sites) are output-argument smells on fields the object should own.
796
+ 2. **Wiring debt in `index.ts`.**
797
+ Two forward references (settings → queue, queue → manager) are replicated with an `eslint-disable prefer-const` dance in `test/lifecycle/subagent-manager.test.ts`; the queue's start callback (`record.promise = record.run()` after a status check) is duplicated verbatim between `index.ts` and the test helper.
798
+ A ~70-line inline `SubagentManagerObserver` literal mixes three concerns (event emission, `appendEntry` persistence, notification dispatch).
799
+ `runtime.widget` is assigned post-construction behind five relay-only delegation methods on `SubagentRuntime`.
800
+ 3. **Duplication.**
801
+ A 23-line cross-package production clone (`settings.ts:198-211` ↔ `pi-subagents-worktrees/src/config.ts:51-73`: the layered global/project settings-file loader) and 44 test clone groups (~750 lines), with clone families concentrated in `test/lifecycle/` and `test/ui/`.
802
+
803
+ Deferred findings (scored below the priority cut, tracked here rather than as steps): the `resolveModel` error-as-string union return (callers branch on `typeof resolved === "string"`), the file-top SDK `eslint-disable` headers in 14 files (re-audit when the Pi SDK exports improve), missing unit tests for `observation/renderer.ts` (the top CRAP-risk file), and the 11-line internal clone in `ui/agent-config-editor.ts` (folds into the Phase 18 UI extraction).
804
+
805
+ ### Steps
806
+
807
+ Priority = Impact × (6 − Risk).
808
+
809
+ | Step | Title | Category | Impact | Risk | Priority |
810
+ | ---- | ------------------------------------------------------------------------------------ | -------- | ------ | ---- | -------- |
811
+ | 1 | Replace ConcurrencyQueue with a thunk-based ConcurrencyLimiter | A/C | 4 | 2 | 16 |
812
+ | 2 | Decompose `SubagentInit` into `SubagentOptions` (identity, run spec, execution deps) | B/D | 4 | 3 | 12 |
813
+ | 3 | Encapsulate run start and notification attachment on Subagent | C | 3 | 2 | 12 |
814
+ | 4 | Extract run-listener and workspace-bracket collaborators from Subagent | B/C | 3 | 2 | 12 |
815
+ | 5 | Extract the manager observer from index.ts into a class | B/E | 3 | 2 | 12 |
816
+ | 6 | Split widget delegation out of SubagentRuntime | C | 3 | 3 | 9 |
817
+ | 7 | Consolidate lifecycle test fixtures | D | 3 | 1 | 15 |
818
+ | 8 | Consolidate UI and tools test fixtures | D | 2 | 1 | 10 |
819
+ | 9 | Resolve the cross-package settings-loader duplication | A | 2 | 2 | 8 |
820
+
821
+ #### Step 1 — Replace ConcurrencyQueue with a thunk-based ConcurrencyLimiter ([#381])
822
+
823
+ - Targets: `src/lifecycle/concurrency-queue.ts` (→ `concurrency-limiter.ts`), `src/lifecycle/subagent-manager.ts`, `src/index.ts`, `test/lifecycle/concurrency-queue.test.ts`, `test/lifecycle/subagent-manager.test.ts`.
824
+ - Smell: Category C (forward references: the queue's ID-registry design forces a start callback that reaches back into the manager, duplicated between `index.ts` and the test helper) and Category A (dual counting: the queue's `running` counter is fed by `markStarted`/`markFinished` relays in the manager's observer, mirroring state the agents already carry).
825
+ - Change: replace the ID-registry queue with a `ConcurrencyLimiter` that schedules thunks FIFO against a dynamic `getLimit()` — the injected limiter knows nothing about agents, IDs, or the manager.
826
+ Spawn gates background runs with `limiter.schedule(() => record.run())` (the thunk guards on `queued` status, covering abort-while-queued; Step 3 later folds the guard into `Subagent.start()`); foreground and `bypassQueue` runs invoke directly.
827
+ The settings `onMaxConcurrentChanged` hook wires to `limiter.recheck()` in `index.ts`; `dispose()` calls `limiter.clear()` to drop pending thunks.
828
+ - Outcome: dependency direction is strictly manager → limiter (no callback back-edge; the `prefer-const` eslint-disable in the test helper is deleted); the observer's two queue relays are gone; every spawned agent has a `promise` at spawn, collapsing `waitForAll`'s `while (true)` drain loop and its eslint-disable.
829
+
830
+ #### Step 2 — Decompose `SubagentInit` into `SubagentOptions` (identity, run spec, execution deps) ([#373])
831
+
832
+ - Targets: `src/lifecycle/subagent.ts` (`SubagentInit`, constructor, `run()` guards), `src/lifecycle/subagent-manager.ts` (`spawn`), `test/helpers/make-subagent.ts`.
833
+ - Smell: Category B (god interface — ~20 fields) and Category D (constructibility: "optional for tests" fields with compensating runtime throws).
834
+ - Change: group the per-run fields (snapshot, prompt, model, maxTurns, thinkingLevel, parentSession, signal) into a `SubagentRunSpec` and the shared collaborators (createSubagentSession, observer, getRunConfig, getWorkspaceProvider, baseCwd) into `SubagentExecutionDeps`; the pair is either fully present (executable agent) or fully absent (passive record), collapsing `run()`'s two guards into one.
835
+ Rename the decomposed bag `SubagentInit` → `SubagentOptions`: it is the codebase's only DOM-style `*Init` name, while sibling constructor bags use `Options` (`SubagentManagerOptions`, `TurnLoopOptions`).
836
+ - Outcome: `SubagentOptions` (née `SubagentInit`) top-level fields ~20 → ≤ 9; a passive test record is constructible without any execution fields; the record-vs-executor duality is explicit in the types.
837
+
838
+ #### Step 3 — Encapsulate run start and notification attachment on Subagent ([#374])
839
+
840
+ - Targets: `src/lifecycle/subagent.ts`, `src/lifecycle/subagent-manager.ts`, `test/tools/get-result-tool.test.ts`, `test/lifecycle/subagent-manager.test.ts`, `test/service/service-adapter.test.ts`, `test/observation/notification.test.ts`, `test/helpers/make-subagent.ts`.
841
+ - Smell: Category C — output arguments: external writes to `record.promise` (3 production/test sites) and `record.notification` (7 test sites).
842
+ - Change: add `Subagent.start()` that runs and stores its own promise (plus an awaitable accessor for `spawnAndWait`/`waitForAll`); make `promise` and `notification` externally read-only; tests attach notification state through `SubagentOptions.parentSession.toolCallId` or a dedicated options field.
843
+ - Outcome: zero external writes to `Subagent` fields outside its own methods (grep-verifiable: `\.promise =` and `\.notification =` appear only inside `subagent.ts`).
844
+
845
+ #### Step 4 — Extract run-listener and workspace-bracket collaborators from Subagent ([#375])
846
+
847
+ - Targets: `src/lifecycle/subagent.ts` (533 LOC — largest source file, accelerating churn).
848
+ - Smell: Category B (oversized class; per-run listener fields declared mid-class) and Category C (state owns its mutations: workspace dispose logic appears in `run()`'s catch, `completeRun`, and `failRun`).
849
+ - Change: extract a `RunListeners` object owning the observer-unsubscribe and signal-detach handles (`attach`/`release`), and a workspace-bracket collaborator owning prepare/dispose-with-addendum, so the three dispose paths collapse into one.
850
+ - Outcome: `subagent.ts` ≤ 450 LOC; workspace disposal logic in exactly one place; listener handles no longer raw nullable fields.
851
+
852
+ #### Step 5 — Extract the manager observer from index.ts into a class ([#376])
853
+
854
+ - Targets: `src/index.ts` (inline `SubagentManagerObserver` literal, ~70 lines), new module under `src/observation/`.
855
+ - Smell: Category B/E — `index.ts` is the dominant churn hotspot (31.3, 91 commits); the literal mixes event emission, record persistence (`appendEntry`), and notification dispatch; principle 9 (state and behavior belong in classes, not closure-captured literals).
856
+ - Change: extract a class (e.g. `SubagentEventsObserver`) constructed with narrow deps (`emit`, `appendEntry`, the `NotificationSystem`).
857
+ - Outcome: `index.ts` < 170 lines; the observer's three concerns unit-tested directly without booting the extension.
858
+
859
+ #### Step 6 — Split widget delegation out of SubagentRuntime ([#377])
860
+
861
+ - Targets: `src/runtime.ts`, `src/tools/agent-tool.ts` (`AgentToolRuntime`), `src/tools/foreground-runner.ts`, `src/tools/background-spawner.ts`, `src/observation/notification.ts` (`NotificationManager` constructor), `src/index.ts`.
862
+ - Smell: Category C — relay-only dependency (five delegation methods that only forward to `widget`) and a post-construction `runtime.widget =` write violating principle 8.
863
+ - Change: pass the existing `WidgetLike` handle directly to the consumers that need it (tool deps, `NotificationManager`) and construct the widget before them; remove the `widget` field and the five relay methods from `SubagentRuntime`.
864
+ - Outcome: `SubagentRuntime` has zero widget knowledge; no post-construction field writes in `index.ts`; tool fixtures stub a 5-method `WidgetLike` instead of widget methods on the runtime mock.
865
+
866
+ #### Step 7 — Consolidate lifecycle test fixtures ([#378])
867
+
868
+ - Targets: `test/lifecycle/subagent-manager.test.ts` (766 LOC), `test/lifecycle/subagent.test.ts`, `test/lifecycle/subagent-session.test.ts`, `test/lifecycle/create-subagent-session.test.ts`, `test/lifecycle/create-subagent-session-extension-tools.test.ts`, `test/lifecycle/concurrency-queue.test.ts`, `test/helpers/`.
869
+ - Smell: Category D — fallow reports five clone families across the lifecycle tests.
870
+ - Change: extract the repeated spawn/run/factory arrangements into shared helpers, migrating incrementally (lift-and-shift, never a single-step rewrite of a large test file).
871
+ - Outcome: lifecycle clone families 5 → ≤ 1; package test duplication below 600 lines.
872
+
873
+ #### Step 8 — Consolidate UI and tools test fixtures ([#379])
874
+
875
+ - Targets: `test/ui/agent-creation-wizard.test.ts`, `test/ui/agent-config-editor.test.ts`, `test/ui/ui-observer.test.ts`, `test/tools/foreground-runner.test.ts`, `test/tools/background-spawner.test.ts`, `test/session/session-config.test.ts`.
876
+ - Smell: Category D — remaining clone families outside the lifecycle tree.
877
+ - Change: extract per-file repeated arrangements into local helpers or `test/helpers/` where shared across files.
878
+ - Outcome: package clone groups 44 → ≤ 25; overall duplication ≤ 0.6%.
879
+
880
+ #### Step 9 — Resolve the cross-package settings-loader duplication ([#380])
881
+
882
+ - Targets: `src/settings.ts:198-211`, `packages/pi-subagents-worktrees/src/config.ts:51-73`.
883
+ - Smell: Category A — 23-line production clone: the layered global/project JSON read-sanitize-warn-merge loader.
884
+ - Change: decide explicitly between (a) exporting a small `loadLayeredSettings` helper from pi-subagents' public surface for worktrees to consume, and (b) documenting the duplication as intentional (separate release cadences, registry-resolved dependency) with a recorded fallow suppression.
885
+ The issue weighs the public-API cost (type bundle, `verify:public-types`, docs for third-party authors) against living with the flag.
886
+ - Outcome: `pnpm fallow:dupes` no longer reports the pair, via extraction or recorded suppression.
887
+
888
+ ### Step dependencies
889
+
890
+ ```mermaid
891
+ flowchart TB
892
+ S1["Step 1 (#381)<br/>ConcurrencyLimiter replacement"]
893
+ S2["Step 2 (#373)<br/>SubagentOptions decomposition"]
894
+ S3["Step 3 (#374)<br/>Encapsulate start + notification"]
895
+ S4["Step 4 (#375)<br/>Run collaborators extraction"]
896
+ S5["Step 5 (#376)<br/>Observer class from index.ts"]
897
+ S6["Step 6 (#377)<br/>Widget handle out of runtime"]
898
+ S7["Step 7 (#378)<br/>Lifecycle test fixtures"]
899
+ S8["Step 8 (#379)<br/>UI/tools test fixtures"]
900
+ S9["Step 9 (#380)<br/>Settings-loader duplication"]
901
+
902
+ S1 --> S3
903
+ S2 --> S3
904
+ S3 --> S4
905
+ S4 --> S7
906
+ S5 --> S6
907
+ ```
908
+
909
+ Steps 8 and 9 have no dependencies and can run at any point.
910
+
911
+ ### Tracks
912
+
913
+ | Track | Steps | Theme |
914
+ | ----------------------------- | ------------- | ---------------------------------------------------------------------------------- |
915
+ | A — Subagent constructibility | 2 → 3 → 4 → 7 | Construct complete; encapsulate run state; then consolidate the tests that churned |
916
+ | B — Wiring debt | 1, 5 → 6 | Shrink index.ts; eliminate forward references and relay delegation |
917
+ | C — Test hygiene | 8 | Clone families outside the lifecycle tree |
918
+ | D — Duplication policy | 9 | Cross-package clone decision |
919
+
920
+ Tracks A and B intersect only at Step 3 (which needs Step 1's queue relocation); otherwise they proceed in parallel.
921
+ Tracks C and D are fully independent.
773
922
 
774
923
  ## Refactoring history
775
924
 
776
925
  Phases 1–5, 7–16 are complete.
777
- Phase 6 (UI extraction to a separate package) is deferred → Phase 17.
926
+ Phase 6 (UI extraction to a separate package) is deferred → Phase 18.
778
927
  Detailed records are preserved in per-phase history files:
779
928
 
780
929
  | Phase | Title | Status | History |
@@ -795,7 +944,8 @@ Detailed records are preserved in per-phase history files:
795
944
  | 14 | Strip policy from core | Complete | [phase-14-strip-policy.md](history/phase-14-strip-policy.md) |
796
945
  | 15 | Domain model evolution | Complete | [phase-15-domain-model-evolution.md](history/phase-15-domain-model-evolution.md) |
797
946
  | 16 | Invert dependencies (extensions on a minimal core) | Complete | [phase-16-invert-dependencies.md](history/phase-16-invert-dependencies.md) |
798
- | 17 | Extract UI to separate package | Planned | — |
947
+ | 17 | Core consolidation | Planned | — |
948
+ | 18 | Extract UI to separate package | Planned | — |
799
949
 
800
950
  ### Structural refactoring issues
801
951
 
@@ -861,4 +1011,13 @@ The upstream test suite is run periodically as a regression canary for the sessi
861
1011
  [#264]: https://github.com/gotgenes/pi-packages/issues/264
862
1012
  [#265]: https://github.com/gotgenes/pi-packages/issues/265
863
1013
  [#277]: https://github.com/gotgenes/pi-packages/issues/277
1014
+ [#373]: https://github.com/gotgenes/pi-packages/issues/373
1015
+ [#374]: https://github.com/gotgenes/pi-packages/issues/374
1016
+ [#375]: https://github.com/gotgenes/pi-packages/issues/375
1017
+ [#376]: https://github.com/gotgenes/pi-packages/issues/376
1018
+ [#377]: https://github.com/gotgenes/pi-packages/issues/377
1019
+ [#378]: https://github.com/gotgenes/pi-packages/issues/378
1020
+ [#379]: https://github.com/gotgenes/pi-packages/issues/379
1021
+ [#380]: https://github.com/gotgenes/pi-packages/issues/380
1022
+ [#381]: https://github.com/gotgenes/pi-packages/issues/381
864
1023
  [ADR-0002]: ../decisions/0002-extensions-on-a-minimal-core.md
@@ -37,3 +37,45 @@ Test count: 973 (unchanged — no new tests added, three assertions updated in-p
37
37
  - Full suite (59 test files, 973 tests) stayed green after the source change — the planning analysis that the broader upstream regression suite uses explicit `promptMode` values was confirmed correct.
38
38
  - `pnpm fallow dead-code` and `pnpm run check` both passed with no findings.
39
39
  - Pre-completion reviewer verdict: **PASS** — no warnings or findings raised.
40
+
41
+ ## Stage: Final Retrospective (2026-06-09T03:04:21Z)
42
+
43
+ ### Session summary
44
+
45
+ Shipped the breaking-default fix end to end across four staged sessions (plan → TDD → ship → retro), cutting `pi-subagents@15.0.0`.
46
+ The change flips the `promptMode` default for custom agents that omit `prompt_mode` from `replace` to `append`.
47
+ Execution was clean except for one significant miss in planning: the change was initially classified as a non-breaking `fix:` and corrected only after the user intervened.
48
+
49
+ ### Observations
50
+
51
+ #### What went well
52
+
53
+ - The multi-model pipeline matched models to stage complexity: planning and retro on `claude-opus-4-8`, TDD on `claude-sonnet-4-6`, and the mechanical ship stage (push, CI watch, close, merge) on a cheaper `deepseek-v4-flash` — all stages completed their work correctly.
54
+ - The breaking-change correction propagated cleanly across the stage boundary: once the plan was fixed to `fix!:` with a `BREAKING CHANGE:` footer, the TDD stage (different model, different session) picked it up from the plan and committed the footer verbatim without re-deciding.
55
+ - TDD verification was incremental, not end-loaded: green baseline (`check`/`lint`/test) before any edit, red confirmation after the test edit, green after the source edit, then full suite + `check` + `lint` + `fallow` before the reviewer dispatch.
56
+
57
+ #### What caused friction (agent side)
58
+
59
+ - `instruction-violation` (user-caught) — the planning stage classified a changed-default bug fix as a non-breaking `fix:`, producing a plan with `fix:` commit messages.
60
+ The plan template line "If the change is breaking, say so explicitly in Goals and use `feat!:`" was available but never triggered, because the agent conflated "the fix is unambiguous" (turn 8) with "the change is non-breaking" and skipped the breaking determination entirely.
61
+ Impact: the user had to intervene ("This is a breaking change, right?"); three corrective edits (plan Goals, plan TDD commit block, retro Planning notes) plus one extra commit (`docs: mark issue #360 change as breaking`).
62
+ Caught before TDD, so no code rework — but a wrong `fix:` that reached `/ship-issue` would have cut a patch release for a breaking change.
63
+
64
+ #### What caused friction (user side)
65
+
66
+ - None on the #360 work.
67
+ The single redirecting question ("This is a breaking change, right?") was a near-optimal intervention: minimal, Socratic, and it let the agent reason to the correct classification itself rather than dictating the fix.
68
+ - Environment note (not #360-specific): the retro session opened with local `main` detached onto a stale `#332` side-branch after an aborted rebase (2 duplicate commits, 36 behind `origin/main`).
69
+ `origin/main` was intact; recovery was a verified `git reset --hard origin/main` after confirming the 2 local commits were byte-identical duplicates of work already on origin.
70
+
71
+ ### Diagnostic details
72
+
73
+ - **Model-performance correlation** — the breaking-change miss happened on the *strongest* model in the pipeline (`claude-opus-4-8`) during the judgment-heaviest stage (planning).
74
+ This rules out model capability as the cause and points to a salience gap in the plan-issue prompt: the breaking determination is framed only as an `ask-user` ambiguity, conditional on the change being "ambiguous," with no unconditional classification step.
75
+ - **Feedback-loop gap analysis** — no gap; the TDD stage ran `check`/`lint`/test/`fallow` incrementally rather than only at the end.
76
+ - Escalation-delay and unused-tool lenses found nothing notable (no rabbit-holes or tool-availability misses).
77
+
78
+ ### Changes made
79
+
80
+ 1. `.pi/prompts/plan-issue.md` — added an unconditional breaking-change classification step to the `## Decide` section (before the ambiguity paragraph), so the breaking determination no longer hides behind the `ask-user` ambiguity gate.
81
+ Names the non-obvious case explicitly: a bug fix that changes a default is breaking.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-subagents",
3
- "version": "15.0.0",
3
+ "version": "15.0.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -58,16 +58,16 @@
58
58
  "node": ">=22"
59
59
  },
60
60
  "devDependencies": {
61
- "@biomejs/biome": "^2.4.14",
62
- "@earendil-works/pi-ai": "0.75.4",
63
- "@earendil-works/pi-coding-agent": "0.75.4",
64
- "@earendil-works/pi-tui": "0.75.4",
61
+ "@biomejs/biome": "^2.4.16",
62
+ "@earendil-works/pi-ai": "0.79.1",
63
+ "@earendil-works/pi-coding-agent": "0.79.1",
64
+ "@earendil-works/pi-tui": "0.79.1",
65
65
  "@types/node": "^22.15.3",
66
- "rollup": "^4.60.4",
66
+ "rollup": "^4.61.1",
67
67
  "rollup-plugin-dts": "^6.4.1",
68
- "rumdl": "^0.1.93",
68
+ "rumdl": "^0.2.10",
69
69
  "typescript": "^6.0.3",
70
- "vitest": "^4.1.5"
70
+ "vitest": "^4.1.8"
71
71
  },
72
72
  "pi": {
73
73
  "extensions": [
@@ -148,7 +148,6 @@ export class AgentTool {
148
148
  );
149
149
  }
150
150
 
151
- // fallow-ignore-next-line unused-class-member
152
151
  toToolDefinition() {
153
152
  const typeListText = this.typeListText;
154
153
  const availableTypesText = this.availableTypesText;