gsd-pi 2.81.0-dev.72a81bdf3 → 2.82.0-dev.2841a1e44

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.
Files changed (243) hide show
  1. package/README.md +49 -30
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/GSD-WORKFLOW.md +3 -1
  4. package/dist/resources/extensions/browser-tools/tools/screenshot.js +1 -0
  5. package/dist/resources/extensions/browser-tools/tools/zoom.js +1 -0
  6. package/dist/resources/extensions/cmux/index.js +5 -0
  7. package/dist/resources/extensions/gsd/auto/orchestrator.js +113 -6
  8. package/dist/resources/extensions/gsd/auto/phases.js +9 -0
  9. package/dist/resources/extensions/gsd/auto-post-unit.js +169 -124
  10. package/dist/resources/extensions/gsd/auto-prompts.js +13 -5
  11. package/dist/resources/extensions/gsd/auto-verification.js +28 -22
  12. package/dist/resources/extensions/gsd/auto.js +128 -52
  13. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +5 -0
  14. package/dist/resources/extensions/gsd/bootstrap/subagent-input.js +16 -7
  15. package/dist/resources/extensions/gsd/bootstrap/system-context.js +55 -12
  16. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +3 -1
  17. package/dist/resources/extensions/gsd/clean-root-preflight.js +170 -8
  18. package/dist/resources/extensions/gsd/commands/catalog.js +4 -1
  19. package/dist/resources/extensions/gsd/commands/handlers/core.js +22 -1
  20. package/dist/resources/extensions/gsd/commands-bootstrap.js +5 -0
  21. package/dist/resources/extensions/gsd/commands-handlers.js +15 -2
  22. package/dist/resources/extensions/gsd/context-store.js +112 -0
  23. package/dist/resources/extensions/gsd/db-writer.js +150 -84
  24. package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  25. package/dist/resources/extensions/gsd/doctor-git-checks.js +41 -6
  26. package/dist/resources/extensions/gsd/knowledge-backfill.js +144 -0
  27. package/dist/resources/extensions/gsd/knowledge-capture.js +136 -0
  28. package/dist/resources/extensions/gsd/knowledge-parser.js +154 -0
  29. package/dist/resources/extensions/gsd/knowledge-projection.js +210 -0
  30. package/dist/resources/extensions/gsd/markdown-renderer.js +6 -1
  31. package/dist/resources/extensions/gsd/md-importer.js +1 -1
  32. package/dist/resources/extensions/gsd/memory-backfill.js +73 -17
  33. package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +222 -0
  34. package/dist/resources/extensions/gsd/migrate/command.js +5 -0
  35. package/dist/resources/extensions/gsd/migrate/preview.js +9 -0
  36. package/dist/resources/extensions/gsd/migrate/transformer.js +51 -4
  37. package/dist/resources/extensions/gsd/migrate/writer.js +11 -1
  38. package/dist/resources/extensions/gsd/prompts/system.md +2 -2
  39. package/dist/resources/extensions/gsd/provider-switch-observer.js +146 -0
  40. package/dist/resources/extensions/gsd/templates/knowledge.md +2 -2
  41. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +119 -0
  42. package/dist/resources/extensions/gsd/unit-context-manifest.js +25 -2
  43. package/dist/resources/extensions/gsd/verification-verdict.js +26 -0
  44. package/dist/resources/extensions/gsd/worktree-lifecycle.js +21 -2
  45. package/dist/resources/extensions/subagent/index.js +448 -78
  46. package/dist/resources/extensions/subagent/launch.js +77 -0
  47. package/dist/resources/extensions/subagent/run-store.js +148 -0
  48. package/dist/resources/extensions/visual-brief/artifact-policy.js +29 -0
  49. package/dist/resources/extensions/visual-brief/extension-manifest.json +8 -0
  50. package/dist/resources/extensions/visual-brief/index.js +5 -0
  51. package/dist/resources/extensions/visual-brief/page-contract.js +122 -0
  52. package/dist/resources/extensions/visual-brief/prompts.js +111 -0
  53. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  54. package/dist/web/standalone/.next/BUILD_ID +1 -1
  55. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  56. package/dist/web/standalone/.next/build-manifest.json +3 -3
  57. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  58. package/dist/web/standalone/.next/react-loadable-manifest.json +3 -3
  59. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  60. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.rsc +2 -2
  71. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  74. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  77. package/dist/web/standalone/.next/server/app/index.html +1 -1
  78. package/dist/web/standalone/.next/server/app/index.rsc +2 -2
  79. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  81. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +2 -2
  83. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  84. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  86. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  88. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  89. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  90. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  91. package/dist/web/standalone/.next/static/chunks/2973.33f26573894b6153.js +2 -0
  92. package/dist/web/standalone/.next/static/chunks/{8359.e059d86b255fce1c.js → 8359.7eb3bb8f8ecf4c01.js} +2 -2
  93. package/dist/web/standalone/.next/static/chunks/{webpack-de742b64187e13fe.js → webpack-6a95bc41e0f7ec89.js} +1 -1
  94. package/dist/web/standalone/.next/static/css/0262768ec1b89d34.css +1 -0
  95. package/package.json +5 -4
  96. package/packages/contracts/dist/rpc.test.js +7 -0
  97. package/packages/contracts/dist/rpc.test.js.map +1 -1
  98. package/packages/contracts/dist/workflow.d.ts +21 -0
  99. package/packages/contracts/dist/workflow.d.ts.map +1 -1
  100. package/packages/contracts/dist/workflow.js +24 -0
  101. package/packages/contracts/dist/workflow.js.map +1 -1
  102. package/packages/contracts/src/rpc.test.ts +8 -0
  103. package/packages/contracts/src/workflow.ts +24 -0
  104. package/packages/daemon/package.json +2 -2
  105. package/packages/mcp-server/README.md +14 -3
  106. package/packages/mcp-server/dist/workflow-tools.d.ts +0 -3
  107. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  108. package/packages/mcp-server/dist/workflow-tools.js +80 -0
  109. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  110. package/packages/mcp-server/package.json +2 -2
  111. package/packages/mcp-server/src/workflow-tools-parity.test.ts +244 -0
  112. package/packages/mcp-server/src/workflow-tools.test.ts +22 -0
  113. package/packages/mcp-server/src/workflow-tools.ts +168 -0
  114. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  115. package/packages/native/package.json +1 -1
  116. package/packages/pi-agent-core/package.json +1 -1
  117. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  118. package/packages/pi-ai/dist/index.d.ts +2 -2
  119. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  120. package/packages/pi-ai/dist/index.js +1 -1
  121. package/packages/pi-ai/dist/index.js.map +1 -1
  122. package/packages/pi-ai/dist/providers/transform-messages.d.ts +11 -0
  123. package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
  124. package/packages/pi-ai/dist/providers/transform-messages.js +20 -0
  125. package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
  126. package/packages/pi-ai/package.json +1 -1
  127. package/packages/pi-ai/src/index.ts +7 -2
  128. package/packages/pi-ai/src/providers/transform-messages.ts +24 -0
  129. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  130. package/packages/pi-coding-agent/dist/core/system-prompt.js +4 -4
  131. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  132. package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.d.ts +2 -0
  133. package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.d.ts.map +1 -0
  134. package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.js +17 -0
  135. package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.js.map +1 -0
  136. package/packages/pi-coding-agent/package.json +1 -1
  137. package/packages/pi-coding-agent/src/core/system-prompt.ts +4 -4
  138. package/packages/pi-coding-agent/src/tests/system-prompt-file-safety.test.ts +22 -0
  139. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  140. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  141. package/packages/pi-tui/dist/tui.js +5 -0
  142. package/packages/pi-tui/dist/tui.js.map +1 -1
  143. package/packages/pi-tui/package.json +1 -1
  144. package/packages/pi-tui/src/tui.ts +6 -0
  145. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  146. package/packages/rpc-client/package.json +1 -1
  147. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
  148. package/pkg/package.json +1 -1
  149. package/src/resources/GSD-WORKFLOW.md +3 -1
  150. package/src/resources/extensions/browser-tools/tools/screenshot.ts +1 -0
  151. package/src/resources/extensions/browser-tools/tools/zoom.ts +1 -0
  152. package/src/resources/extensions/cmux/index.ts +6 -0
  153. package/src/resources/extensions/gsd/auto/contracts.ts +46 -11
  154. package/src/resources/extensions/gsd/auto/orchestrator.ts +118 -6
  155. package/src/resources/extensions/gsd/auto/phases.ts +14 -0
  156. package/src/resources/extensions/gsd/auto-post-unit.ts +194 -137
  157. package/src/resources/extensions/gsd/auto-prompts.ts +13 -5
  158. package/src/resources/extensions/gsd/auto-verification.ts +36 -34
  159. package/src/resources/extensions/gsd/auto.ts +136 -51
  160. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +6 -0
  161. package/src/resources/extensions/gsd/bootstrap/subagent-input.ts +16 -6
  162. package/src/resources/extensions/gsd/bootstrap/system-context.ts +58 -15
  163. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +3 -2
  164. package/src/resources/extensions/gsd/clean-root-preflight.ts +174 -8
  165. package/src/resources/extensions/gsd/commands/catalog.ts +4 -1
  166. package/src/resources/extensions/gsd/commands/handlers/core.ts +25 -1
  167. package/src/resources/extensions/gsd/commands-bootstrap.ts +10 -0
  168. package/src/resources/extensions/gsd/commands-handlers.ts +19 -2
  169. package/src/resources/extensions/gsd/context-store.ts +120 -1
  170. package/src/resources/extensions/gsd/db-writer.ts +167 -84
  171. package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  172. package/src/resources/extensions/gsd/doctor-git-checks.ts +44 -6
  173. package/src/resources/extensions/gsd/doctor-types.ts +2 -0
  174. package/src/resources/extensions/gsd/knowledge-backfill.ts +164 -0
  175. package/src/resources/extensions/gsd/knowledge-capture.ts +160 -0
  176. package/src/resources/extensions/gsd/knowledge-parser.ts +174 -0
  177. package/src/resources/extensions/gsd/knowledge-projection.ts +241 -0
  178. package/src/resources/extensions/gsd/markdown-renderer.ts +6 -1
  179. package/src/resources/extensions/gsd/md-importer.ts +1 -1
  180. package/src/resources/extensions/gsd/memory-backfill.ts +89 -17
  181. package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +277 -0
  182. package/src/resources/extensions/gsd/migrate/command.ts +5 -0
  183. package/src/resources/extensions/gsd/migrate/preview.ts +10 -0
  184. package/src/resources/extensions/gsd/migrate/transformer.ts +58 -4
  185. package/src/resources/extensions/gsd/migrate/writer.ts +14 -1
  186. package/src/resources/extensions/gsd/prompts/system.md +2 -2
  187. package/src/resources/extensions/gsd/provider-switch-observer.ts +185 -0
  188. package/src/resources/extensions/gsd/templates/knowledge.md +2 -2
  189. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +75 -0
  190. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +408 -4
  191. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +6 -5
  192. package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +4 -4
  193. package/src/resources/extensions/gsd/tests/brief-command.test.ts +89 -0
  194. package/src/resources/extensions/gsd/tests/browser-tools-compatibility-declarations.test.ts +62 -0
  195. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +107 -2
  196. package/src/resources/extensions/gsd/tests/closeout-git-deferral.test.ts +16 -0
  197. package/src/resources/extensions/gsd/tests/context-store-decisions-from-memories.test.ts +312 -0
  198. package/src/resources/extensions/gsd/tests/db-writer.test.ts +13 -8
  199. package/src/resources/extensions/gsd/tests/decisions-projection-from-memories.test.ts +453 -0
  200. package/src/resources/extensions/gsd/tests/decisions-stop-table-writes.test.ts +348 -0
  201. package/src/resources/extensions/gsd/tests/evidence-cross-ref.test.ts +38 -0
  202. package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +8 -4
  203. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +11 -7
  204. package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +44 -0
  205. package/src/resources/extensions/gsd/tests/integration/integration-lifecycle.test.ts +13 -5
  206. package/src/resources/extensions/gsd/tests/integration/migrate-command.test.ts +48 -3
  207. package/src/resources/extensions/gsd/tests/knowledge-backfill-projection.test.ts +323 -0
  208. package/src/resources/extensions/gsd/tests/knowledge-capture.test.ts +242 -0
  209. package/src/resources/extensions/gsd/tests/knowledge.test.ts +47 -2
  210. package/src/resources/extensions/gsd/tests/load-knowledge-block-rules-only.test.ts +209 -0
  211. package/src/resources/extensions/gsd/tests/memory-consolidation-scanner.test.ts +316 -0
  212. package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +5 -1
  213. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +6 -1
  214. package/src/resources/extensions/gsd/tests/plan-milestone-sketch-render.test.ts +157 -0
  215. package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +79 -1
  216. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +8 -0
  217. package/src/resources/extensions/gsd/tests/provider-switch-observer.test.ts +252 -0
  218. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +16 -4
  219. package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +6 -0
  220. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +21 -0
  221. package/src/resources/extensions/gsd/tests/verification-verdict.test.ts +78 -0
  222. package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +25 -0
  223. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +16 -0
  224. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +135 -0
  225. package/src/resources/extensions/gsd/unit-context-manifest.ts +35 -2
  226. package/src/resources/extensions/gsd/verification-verdict.ts +47 -0
  227. package/src/resources/extensions/gsd/workflow-logger.ts +4 -0
  228. package/src/resources/extensions/gsd/worktree-lifecycle.ts +20 -2
  229. package/src/resources/extensions/subagent/index.ts +567 -103
  230. package/src/resources/extensions/subagent/launch.ts +131 -0
  231. package/src/resources/extensions/subagent/run-store.ts +218 -0
  232. package/src/resources/extensions/subagent/tests/launch.test.ts +115 -0
  233. package/src/resources/extensions/subagent/tests/run-store.test.ts +111 -0
  234. package/src/resources/extensions/visual-brief/artifact-policy.ts +41 -0
  235. package/src/resources/extensions/visual-brief/extension-manifest.json +8 -0
  236. package/src/resources/extensions/visual-brief/index.ts +8 -0
  237. package/src/resources/extensions/visual-brief/page-contract.ts +134 -0
  238. package/src/resources/extensions/visual-brief/prompts.ts +147 -0
  239. package/src/resources/extensions/visual-brief/tests/visual-brief.test.ts +172 -0
  240. package/dist/web/standalone/.next/static/chunks/2556.0527fea66e123b7f.js +0 -1
  241. package/dist/web/standalone/.next/static/css/54ec2745c1da488b.css +0 -1
  242. /package/dist/web/standalone/.next/static/{rIkMv4YSNlfSeqmGqWVns → Qgr2B_MRhPxC0z8fwv4vT}/_buildManifest.js +0 -0
  243. /package/dist/web/standalone/.next/static/{rIkMv4YSNlfSeqmGqWVns → Qgr2B_MRhPxC0z8fwv4vT}/_ssgManifest.js +0 -0
@@ -2,27 +2,31 @@
2
2
  // File Purpose: Auto Orchestration module interfaces and ADR-015 invariant adapter contracts.
3
3
 
4
4
  import type { GSDState } from "../types.js";
5
+ import type { MinimalModelRegistry } from "../context-budget.js";
5
6
 
6
7
  export interface AutoSessionContext {
7
8
  basePath: string;
8
9
  trigger: "guided-flow" | "resume" | "auto-loop" | "manual";
9
10
  }
10
11
 
12
+ export interface UnitRef {
13
+ unitType: string;
14
+ unitId: string;
15
+ }
16
+
11
17
  export interface AutoStatus {
12
18
  phase: "idle" | "running" | "paused" | "stopped" | "error";
13
- activeUnit?: {
14
- unitType: string;
15
- unitId: string;
16
- };
19
+ activeUnit?: UnitRef;
17
20
  lastTransitionAt?: number;
18
21
  transitionCount: number;
19
22
  }
20
23
 
21
- export interface AutoAdvanceResult {
22
- kind: "advanced" | "blocked" | "paused" | "stopped" | "error";
23
- reason?: string;
24
- stateSnapshot?: GSDState;
25
- }
24
+ export type AutoAdvanceResult =
25
+ | { kind: "advanced"; unit: UnitRef; stateSnapshot: GSDState }
26
+ | { kind: "blocked"; reason: string; action: "pause" | "stop"; stateSnapshot?: GSDState }
27
+ | { kind: "stopped"; reason: string; stateSnapshot?: GSDState }
28
+ | { kind: "paused"; reason: string }
29
+ | { kind: "error"; reason: string };
26
30
 
27
31
  export interface AutoOrchestrationModule {
28
32
  start(sessionContext: AutoSessionContext): Promise<AutoAdvanceResult>;
@@ -33,7 +37,17 @@ export interface AutoOrchestrationModule {
33
37
  }
34
38
 
35
39
  export interface DispatchAdapter {
36
- decideNextUnit(input: { stateSnapshot: GSDState }): Promise<{
40
+ decideNextUnit(input: {
41
+ stateSnapshot: GSDState;
42
+ /** Mirrors `DispatchContext.structuredQuestionsAvailable` — "true"/"false" string per the dispatch contract. */
43
+ structuredQuestionsAvailable?: "true" | "false";
44
+ /** Session model context window in tokens, forwarded to the budget engine. */
45
+ sessionContextWindow?: number;
46
+ /** Session model provider, used for provider-specific effective context windows. */
47
+ sessionProvider?: string;
48
+ /** Model registry for executor-model lookups inside the budget engine. */
49
+ modelRegistry?: MinimalModelRegistry;
50
+ }): Promise<{
37
51
  unitType: string;
38
52
  unitId: string;
39
53
  reason: string;
@@ -70,11 +84,31 @@ export interface WorktreeAdapter {
70
84
  cleanupOnStop(reason: string): Promise<void>;
71
85
  }
72
86
 
87
+ export type HealthGateResult =
88
+ | { kind: "pass"; fixesApplied?: readonly string[] }
89
+ | { kind: "fail"; reason: string }
90
+ | { kind: "threw"; error: unknown };
91
+
73
92
  export interface HealthAdapter {
74
- preAdvanceGate(): Promise<{ allow: boolean; reason?: string }>;
93
+ checkResourcesStale(): string | null;
94
+ preAdvanceGate(): Promise<HealthGateResult>;
75
95
  postAdvanceRecord(result: AutoAdvanceResult): Promise<void>;
76
96
  }
77
97
 
98
+ export interface UokGateInput {
99
+ gateId: string;
100
+ gateType: "policy" | "execution";
101
+ outcome: "pass" | "fail" | "manual-attention";
102
+ failureClass: "none" | "policy" | "manual-attention";
103
+ rationale: string;
104
+ findings?: string;
105
+ milestoneId?: string;
106
+ }
107
+
108
+ export interface UokGateAdapter {
109
+ emit(input: UokGateInput): Promise<void>;
110
+ }
111
+
78
112
  export interface RuntimePersistenceAdapter {
79
113
  ensureLockOwnership(): Promise<void>;
80
114
  journalTransition(event: {
@@ -101,4 +135,5 @@ export interface AutoOrchestratorDeps {
101
135
  health: HealthAdapter;
102
136
  runtime: RuntimePersistenceAdapter;
103
137
  notifications: NotificationAdapter;
138
+ uokGate: UokGateAdapter;
104
139
  }
@@ -7,6 +7,16 @@ function now(): number {
7
7
  return Date.now();
8
8
  }
9
9
 
10
+ /**
11
+ * Size of the dispatch-decision ring buffer used by the Auto Orchestration
12
+ * module's stuck-loop detector. When the same `${unitType}:${unitId}` key
13
+ * fills the window, advance() blocks with `action: "stop"`.
14
+ *
15
+ * Mirrors the legacy `STUCK_WINDOW_SIZE` in auto/phases.ts so behaviour is
16
+ * preserved across the eventual cutover (issue #5791).
17
+ */
18
+ export const STUCK_WINDOW_SIZE = 6;
19
+
10
20
  export class AutoOrchestrator implements AutoOrchestrationModule {
11
21
  private status: AutoStatus = {
12
22
  phase: "idle",
@@ -14,6 +24,7 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
14
24
  };
15
25
  private readonly deps: AutoOrchestratorDeps;
16
26
  private lastAdvanceKey: string | null = null;
27
+ private dispatchKeyWindow: string[] = [];
17
28
 
18
29
  public constructor(deps: AutoOrchestratorDeps) {
19
30
  this.deps = deps;
@@ -21,6 +32,7 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
21
32
 
22
33
  public async start(_sessionContext: AutoSessionContext): Promise<AutoAdvanceResult> {
23
34
  this.lastAdvanceKey = null;
35
+ this.dispatchKeyWindow = [];
24
36
  this.status.phase = "running";
25
37
  this.bumpTransition();
26
38
  await this.deps.runtime.journalTransition({ name: "start" });
@@ -31,19 +43,72 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
31
43
  public async advance(): Promise<AutoAdvanceResult> {
32
44
  try {
33
45
  await this.deps.runtime.ensureLockOwnership();
46
+
47
+ const staleMsg = this.deps.health.checkResourcesStale();
48
+ if (staleMsg) {
49
+ await this.deps.uokGate.emit({
50
+ gateId: "resource-version-guard",
51
+ gateType: "policy",
52
+ outcome: "fail",
53
+ failureClass: "policy",
54
+ rationale: "resource version guard blocked dispatch",
55
+ findings: staleMsg,
56
+ });
57
+ const blocked: AutoAdvanceResult = { kind: "blocked", reason: staleMsg, action: "stop" };
58
+ await this.deps.runtime.journalTransition({ name: "advance-blocked", reason: blocked.reason });
59
+ await this.deps.health.postAdvanceRecord(blocked);
60
+ return blocked;
61
+ }
62
+ await this.deps.uokGate.emit({
63
+ gateId: "resource-version-guard",
64
+ gateType: "policy",
65
+ outcome: "pass",
66
+ failureClass: "none",
67
+ rationale: "resource version guard passed",
68
+ });
69
+
34
70
  const gate = await this.deps.health.preAdvanceGate();
35
- if (!gate.allow) {
36
- const blocked: AutoAdvanceResult = { kind: "blocked", reason: gate.reason ?? "health gate blocked" };
71
+ if (gate.kind === "fail") {
72
+ await this.deps.uokGate.emit({
73
+ gateId: "pre-dispatch-health-gate",
74
+ gateType: "execution",
75
+ outcome: "manual-attention",
76
+ failureClass: "manual-attention",
77
+ rationale: "pre-dispatch health gate blocked dispatch",
78
+ findings: gate.reason,
79
+ });
80
+ const blocked: AutoAdvanceResult = { kind: "blocked", reason: gate.reason, action: "pause" };
37
81
  await this.deps.runtime.journalTransition({ name: "advance-blocked", reason: blocked.reason });
38
82
  await this.deps.health.postAdvanceRecord(blocked);
39
83
  return blocked;
40
84
  }
85
+ if (gate.kind === "threw") {
86
+ await this.deps.uokGate.emit({
87
+ gateId: "pre-dispatch-health-gate",
88
+ gateType: "execution",
89
+ outcome: "manual-attention",
90
+ failureClass: "manual-attention",
91
+ rationale: "pre-dispatch health gate threw unexpectedly",
92
+ findings: String(gate.error),
93
+ });
94
+ // intentional fall-through: matches runPreDispatch behaviour
95
+ } else {
96
+ await this.deps.uokGate.emit({
97
+ gateId: "pre-dispatch-health-gate",
98
+ gateType: "execution",
99
+ outcome: "pass",
100
+ failureClass: "none",
101
+ rationale: "pre-dispatch health gate passed",
102
+ findings: gate.fixesApplied?.join(", ") ?? "",
103
+ });
104
+ }
41
105
 
42
106
  const reconciliation = await this.deps.stateReconciliation.reconcileBeforeDispatch();
43
107
  if (!reconciliation.ok || !reconciliation.stateSnapshot) {
44
108
  const blocked: AutoAdvanceResult = {
45
109
  kind: "blocked",
46
- reason: reconciliation.reason,
110
+ reason: reconciliation.reason ?? "state reconciliation produced no snapshot",
111
+ action: "pause",
47
112
  stateSnapshot: reconciliation.stateSnapshot,
48
113
  };
49
114
  await this.deps.runtime.journalTransition({ name: "advance-blocked", reason: blocked.reason });
@@ -57,6 +122,7 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
57
122
  this.status.phase = "stopped";
58
123
  this.status.activeUnit = undefined;
59
124
  this.lastAdvanceKey = null;
125
+ this.dispatchKeyWindow = [];
60
126
  this.bumpTransition();
61
127
  await this.deps.runtime.journalTransition({ name: "advance-stopped", reason: stopped.reason });
62
128
  await this.deps.health.postAdvanceRecord(stopped);
@@ -64,8 +130,45 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
64
130
  }
65
131
 
66
132
  const nextKey = `${decision.unitType}:${decision.unitId}`;
67
- if (this.lastAdvanceKey === nextKey) {
68
- const blocked: AutoAdvanceResult = { kind: "blocked", reason: "idempotent advance: unit already active" };
133
+
134
+ // Record every dispatch decision in the ring buffer before pre-flight
135
+ // checks so the stuck-loop detector observes the full decision history
136
+ // (including decisions that idempotency would otherwise short-circuit).
137
+ // The ring is capped at STUCK_WINDOW_SIZE and evicts oldest-first.
138
+ this.dispatchKeyWindow.push(nextKey);
139
+ if (this.dispatchKeyWindow.length > STUCK_WINDOW_SIZE) {
140
+ this.dispatchKeyWindow.shift();
141
+ }
142
+
143
+ // Idempotency: same key as immediately previous successful advance.
144
+ // This is the soft, fast-path block kept from #5786. It only fires when
145
+ // the ring is NOT yet saturated for this key — once the ring is full of
146
+ // `nextKey`, the stuck-loop verdict takes precedence (see below). Both
147
+ // checks coexist: idempotency for the common immediate-repeat case,
148
+ // stuck-loop for the saturated-window case.
149
+ const matchingCount = this.dispatchKeyWindow.filter((k) => k === nextKey).length;
150
+ if (this.lastAdvanceKey === nextKey && matchingCount < STUCK_WINDOW_SIZE) {
151
+ const blocked: AutoAdvanceResult = { kind: "blocked", reason: "idempotent advance: unit already active", action: "stop" };
152
+ await this.deps.runtime.journalTransition({
153
+ name: "advance-blocked",
154
+ reason: blocked.reason,
155
+ unitType: decision.unitType,
156
+ unitId: decision.unitId,
157
+ });
158
+ await this.deps.health.postAdvanceRecord(blocked);
159
+ return blocked;
160
+ }
161
+
162
+ // Stuck-loop detection: when the ring is saturated with copies of
163
+ // `nextKey` (count >= STUCK_WINDOW_SIZE), the orchestrator has been
164
+ // picking the same unit across the whole window and must hard-stop with
165
+ // a diagnosable reason.
166
+ if (matchingCount >= STUCK_WINDOW_SIZE) {
167
+ const blocked: AutoAdvanceResult = {
168
+ kind: "blocked",
169
+ reason: `stuck-loop: ${nextKey} picked ${matchingCount} times`,
170
+ action: "stop",
171
+ };
69
172
  await this.deps.runtime.journalTransition({
70
173
  name: "advance-blocked",
71
174
  reason: blocked.reason,
@@ -81,6 +184,7 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
81
184
  const blocked: AutoAdvanceResult = {
82
185
  kind: "blocked",
83
186
  reason: contract.reason,
187
+ action: "pause",
84
188
  stateSnapshot: reconciliation.stateSnapshot,
85
189
  };
86
190
  await this.deps.runtime.journalTransition({
@@ -98,6 +202,7 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
98
202
  const blocked: AutoAdvanceResult = {
99
203
  kind: "blocked",
100
204
  reason: worktree.reason,
205
+ action: "pause",
101
206
  stateSnapshot: reconciliation.stateSnapshot,
102
207
  };
103
208
  await this.deps.runtime.journalTransition({
@@ -123,7 +228,11 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
123
228
  });
124
229
  await this.deps.worktree.syncAfterUnit(decision.unitType, decision.unitId);
125
230
 
126
- const advanced: AutoAdvanceResult = { kind: "advanced", stateSnapshot: reconciliation.stateSnapshot };
231
+ const advanced: AutoAdvanceResult = {
232
+ kind: "advanced",
233
+ unit: { unitType: decision.unitType, unitId: decision.unitId },
234
+ stateSnapshot: reconciliation.stateSnapshot,
235
+ };
127
236
  await this.deps.health.postAdvanceRecord(advanced);
128
237
  return advanced;
129
238
  } catch (error) {
@@ -148,6 +257,7 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
148
257
 
149
258
  if (result.kind === "stopped") {
150
259
  this.lastAdvanceKey = null;
260
+ this.dispatchKeyWindow = [];
151
261
  this.status.activeUnit = undefined;
152
262
  }
153
263
  this.bumpTransition();
@@ -173,6 +283,7 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
173
283
 
174
284
  public async resume(): Promise<AutoAdvanceResult> {
175
285
  this.lastAdvanceKey = null;
286
+ this.dispatchKeyWindow = [];
176
287
  this.status.phase = "running";
177
288
  this.bumpTransition();
178
289
  await this.deps.runtime.journalTransition({ name: "resume" });
@@ -188,6 +299,7 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
188
299
  this.status.phase = "stopped";
189
300
  this.status.activeUnit = undefined;
190
301
  this.lastAdvanceKey = null;
302
+ this.dispatchKeyWindow = [];
191
303
  this.bumpTransition();
192
304
  await this.deps.runtime.journalTransition({ name: "stop", reason });
193
305
  await this.deps.notifications.notifyLifecycle({ name: "stop", detail: reason });
@@ -2233,6 +2233,20 @@ export async function runUnitPhase(
2233
2233
  await emitCancelledUnitEnd(ic, unitType, unitId, unitStartSeq, unitResult.errorContext);
2234
2234
  return { action: "break", reason: "session-timeout" };
2235
2235
  }
2236
+ if (
2237
+ unitResult.errorContext?.isTransient &&
2238
+ errorCategory === "aborted"
2239
+ ) {
2240
+ ctx.ui.notify(
2241
+ `Unit ${unitType} ${unitId} was aborted by the user. Pausing auto-mode (recoverable).`,
2242
+ "warning",
2243
+ );
2244
+ debugLog("autoLoop", { phase: "unit-aborted-transient-pause", unitType, unitId, category: errorCategory });
2245
+ await deps.pauseAuto(ctx, pi);
2246
+ await deps.autoCommitUnit?.(s.basePath, unitType, unitId, ctx);
2247
+ await emitCancelledUnitEnd(ic, unitType, unitId, unitStartSeq, unitResult.errorContext);
2248
+ return { action: "break", reason: "unit-aborted-pause" };
2249
+ }
2236
2250
  // All other cancelled states (structural errors, non-transient failures): hard stop
2237
2251
  if (s.currentUnit) {
2238
2252
  await deps.closeoutUnit(