gsd-pi 2.82.0-dev.725028083 → 2.82.0-dev.9d5798940
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/README.md +3 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +3 -1
- package/dist/resources/extensions/claude-code-cli/partial-builder.js +2 -1
- package/dist/resources/extensions/cmux/index.js +5 -0
- package/dist/resources/extensions/gsd/auto/orchestrator.js +113 -6
- package/dist/resources/extensions/gsd/auto-dispatch.js +13 -6
- package/dist/resources/extensions/gsd/auto-post-unit.js +232 -126
- package/dist/resources/extensions/gsd/auto-prompts.js +2 -2
- package/dist/resources/extensions/gsd/auto-recovery.js +31 -1
- package/dist/resources/extensions/gsd/auto-start.js +7 -3
- package/dist/resources/extensions/gsd/auto-verification.js +28 -22
- package/dist/resources/extensions/gsd/auto-worktree.js +96 -0
- package/dist/resources/extensions/gsd/auto.js +128 -52
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +4 -1
- package/dist/resources/extensions/gsd/bootstrap/subagent-input.js +16 -7
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +16 -2
- package/dist/resources/extensions/gsd/clean-root-preflight.js +170 -8
- package/dist/resources/extensions/gsd/commands/catalog.js +4 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +37 -0
- package/dist/resources/extensions/gsd/commands-bootstrap.js +5 -0
- package/dist/resources/extensions/gsd/db/unit-dispatches.js +2 -2
- package/dist/resources/extensions/gsd/export-html.js +27 -425
- package/dist/resources/extensions/gsd/md-importer.js +1 -1
- package/dist/resources/extensions/gsd/migrate/command.js +5 -0
- package/dist/resources/extensions/gsd/migrate/preview.js +9 -0
- package/dist/resources/extensions/gsd/migrate/transformer.js +51 -4
- package/dist/resources/extensions/gsd/migrate/writer.js +11 -1
- package/dist/resources/extensions/gsd/milestone-actions.js +11 -4
- package/dist/resources/extensions/gsd/native-git-bridge.js +8 -3
- package/dist/resources/extensions/gsd/state-reconciliation/drift/merge-state.js +6 -1
- package/dist/resources/extensions/gsd/tools/plan-slice.js +2 -1
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +119 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +32 -10
- package/dist/resources/extensions/gsd/verification-verdict.js +26 -0
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +49 -9
- package/dist/resources/extensions/shared/html-shell.js +388 -0
- package/dist/resources/extensions/subagent/index.js +448 -78
- package/dist/resources/extensions/subagent/launch.js +77 -0
- package/dist/resources/extensions/subagent/run-store.js +148 -0
- package/dist/resources/extensions/visual-brief/artifact-policy.js +29 -0
- package/dist/resources/extensions/visual-brief/extension-manifest.json +8 -0
- package/dist/resources/extensions/visual-brief/index.js +5 -0
- package/dist/resources/extensions/visual-brief/page-contract.js +124 -0
- package/dist/resources/extensions/visual-brief/prompts.js +140 -0
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +4 -7
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +4 -7
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +4 -5
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -5
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +4 -7
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -7
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +4 -5
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -5
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
- package/dist/web/standalone/.next/server/chunks/4266.js +2 -0
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/2973.33f26573894b6153.js +2 -0
- package/dist/web/standalone/.next/static/chunks/{8359.e059d86b255fce1c.js → 8359.7eb3bb8f8ecf4c01.js} +2 -2
- package/dist/web/standalone/.next/static/chunks/app/layout-8c10ec293ae0f1d5.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-de742b64187e13fe.js → webpack-9a4db269f9ed63ad.js} +1 -1
- package/dist/web/standalone/.next/static/css/746ee28c929d1880.css +1 -0
- package/package.json +3 -3
- package/packages/contracts/dist/rpc.test.js +7 -0
- package/packages/contracts/dist/rpc.test.js.map +1 -1
- package/packages/contracts/dist/workflow.d.ts +21 -0
- package/packages/contracts/dist/workflow.d.ts.map +1 -1
- package/packages/contracts/dist/workflow.js +24 -0
- package/packages/contracts/dist/workflow.js.map +1 -1
- package/packages/contracts/src/rpc.test.ts +8 -0
- package/packages/contracts/src/workflow.ts +24 -0
- package/packages/mcp-server/README.md +13 -4
- package/packages/mcp-server/dist/workflow-tools.d.ts +0 -3
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +80 -0
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/workflow-tools.test.ts +22 -0
- package/packages/mcp-server/src/workflow-tools.ts +168 -0
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +5 -0
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/tui.ts +6 -0
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
- package/src/resources/GSD-WORKFLOW.md +3 -1
- package/src/resources/extensions/claude-code-cli/partial-builder.ts +2 -1
- package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +19 -2
- package/src/resources/extensions/cmux/index.ts +6 -0
- package/src/resources/extensions/gsd/auto/contracts.ts +46 -11
- package/src/resources/extensions/gsd/auto/orchestrator.ts +118 -6
- package/src/resources/extensions/gsd/auto-dispatch.ts +14 -6
- package/src/resources/extensions/gsd/auto-post-unit.ts +265 -138
- package/src/resources/extensions/gsd/auto-prompts.ts +2 -2
- package/src/resources/extensions/gsd/auto-recovery.ts +29 -0
- package/src/resources/extensions/gsd/auto-start.ts +7 -3
- package/src/resources/extensions/gsd/auto-verification.ts +36 -34
- package/src/resources/extensions/gsd/auto-worktree.ts +104 -0
- package/src/resources/extensions/gsd/auto.ts +136 -51
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +6 -1
- package/src/resources/extensions/gsd/bootstrap/subagent-input.ts +16 -6
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +19 -3
- package/src/resources/extensions/gsd/clean-root-preflight.ts +174 -8
- package/src/resources/extensions/gsd/commands/catalog.ts +4 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +40 -0
- package/src/resources/extensions/gsd/commands-bootstrap.ts +10 -0
- package/src/resources/extensions/gsd/db/unit-dispatches.ts +3 -3
- package/src/resources/extensions/gsd/export-html.ts +27 -427
- package/src/resources/extensions/gsd/md-importer.ts +1 -1
- package/src/resources/extensions/gsd/migrate/command.ts +5 -0
- package/src/resources/extensions/gsd/migrate/preview.ts +10 -0
- package/src/resources/extensions/gsd/migrate/transformer.ts +58 -4
- package/src/resources/extensions/gsd/migrate/writer.ts +14 -1
- package/src/resources/extensions/gsd/milestone-actions.ts +10 -4
- package/src/resources/extensions/gsd/native-git-bridge.ts +8 -3
- package/src/resources/extensions/gsd/state-reconciliation/drift/merge-state.ts +8 -1
- package/src/resources/extensions/gsd/tests/auto-deterministic-error-classification-4973.test.ts +116 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +408 -4
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +6 -5
- package/src/resources/extensions/gsd/tests/auto-post-unit-step-message.test.ts +12 -1
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +15 -1
- package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +69 -1
- package/src/resources/extensions/gsd/tests/brief-command.test.ts +89 -0
- package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +107 -2
- package/src/resources/extensions/gsd/tests/closeout-git-deferral.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +57 -2
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/evidence-cross-ref.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/export-html-enhancements.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/integration/migrate-command.test.ts +48 -3
- package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +5 -1
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +55 -1
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +79 -1
- package/src/resources/extensions/gsd/tests/post-unit-git-failure.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +46 -2
- package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/stuck-state-via-db.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +86 -7
- package/src/resources/extensions/gsd/tests/verification-verdict.test.ts +78 -0
- package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +64 -12
- package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +47 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +2 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +135 -0
- package/src/resources/extensions/gsd/unit-context-manifest.ts +47 -11
- package/src/resources/extensions/gsd/verification-verdict.ts +47 -0
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +54 -9
- package/src/resources/extensions/shared/html-shell.ts +412 -0
- package/src/resources/extensions/subagent/index.ts +567 -103
- package/src/resources/extensions/subagent/launch.ts +131 -0
- package/src/resources/extensions/subagent/run-store.ts +218 -0
- package/src/resources/extensions/subagent/tests/launch.test.ts +115 -0
- package/src/resources/extensions/subagent/tests/run-store.test.ts +111 -0
- package/src/resources/extensions/visual-brief/artifact-policy.ts +41 -0
- package/src/resources/extensions/visual-brief/extension-manifest.json +8 -0
- package/src/resources/extensions/visual-brief/index.ts +8 -0
- package/src/resources/extensions/visual-brief/page-contract.ts +136 -0
- package/src/resources/extensions/visual-brief/prompts.ts +183 -0
- package/src/resources/extensions/visual-brief/tests/visual-brief.test.ts +212 -0
- package/dist/web/standalone/.next/server/chunks/5822.js +0 -2
- package/dist/web/standalone/.next/static/chunks/2556.0527fea66e123b7f.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/layout-a16c7a7ecdf0c2cf.js +0 -1
- package/dist/web/standalone/.next/static/css/54ec2745c1da488b.css +0 -1
- package/dist/web/standalone/.next/static/css/de70bee13400563f.css +0 -1
- package/dist/web/standalone/.next/static/media/4cf2300e9c8272f7-s.p.woff2 +0 -0
- package/dist/web/standalone/.next/static/media/747892c23ea88013-s.woff2 +0 -0
- package/dist/web/standalone/.next/static/media/8d697b304b401681-s.woff2 +0 -0
- package/dist/web/standalone/.next/static/media/93f479601ee12b01-s.p.woff2 +0 -0
- package/dist/web/standalone/.next/static/media/9610d9e46709d722-s.woff2 +0 -0
- package/dist/web/standalone/.next/static/media/ba015fad6dcf6784-s.woff2 +0 -0
- /package/dist/web/standalone/.next/static/{KDRTXR-22LPCsa80X9dey → BdZQhe8yKl6bdKLiXVEzh}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{KDRTXR-22LPCsa80X9dey → BdZQhe8yKl6bdKLiXVEzh}/_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
|
|
22
|
-
kind: "advanced"
|
|
23
|
-
reason
|
|
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: {
|
|
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
|
-
|
|
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 (
|
|
36
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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 = {
|
|
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 });
|
|
@@ -239,6 +239,12 @@ function missingSliceStop(mid: string, phase: string): DispatchAction {
|
|
|
239
239
|
};
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
function isRegistryMilestoneComplete(state: GSDState, mid: string): boolean {
|
|
243
|
+
return state.registry.some((milestone) =>
|
|
244
|
+
milestone.id === mid && milestone.status === "complete"
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
242
248
|
/**
|
|
243
249
|
* Check for milestone slices missing SUMMARY files.
|
|
244
250
|
* Returns array of missing slice IDs, or empty array if all present or DB unavailable.
|
|
@@ -395,6 +401,7 @@ export const DISPATCH_RULES: DispatchRule[] = [
|
|
|
395
401
|
match: async ({ state, mid, midTitle, basePath, prefs, structuredQuestionsAvailable }) => {
|
|
396
402
|
if (!EXECUTION_ENTRY_PHASES.has(state.phase)) return null;
|
|
397
403
|
if (!MILESTONE_ID_RE.test(mid)) return null;
|
|
404
|
+
if (isRegistryMilestoneComplete(state, mid)) return null;
|
|
398
405
|
// Align with the plan-v2 gate's lookup semantics: whitespace-only counts
|
|
399
406
|
// as missing, and an auto worktree may fall back to GSD_PROJECT_ROOT.
|
|
400
407
|
if (hasFinalizedMilestoneContext(basePath, mid)) return null;
|
|
@@ -709,6 +716,7 @@ export const DISPATCH_RULES: DispatchRule[] = [
|
|
|
709
716
|
name: "pre-planning (no context) → discuss-milestone",
|
|
710
717
|
match: async ({ state, mid, midTitle, basePath, prefs, structuredQuestionsAvailable }) => {
|
|
711
718
|
if (state.phase !== "pre-planning") return null;
|
|
719
|
+
if (isRegistryMilestoneComplete(state, mid)) return null;
|
|
712
720
|
const contextFile = resolveMilestoneFile(basePath, mid, "CONTEXT");
|
|
713
721
|
const hasContext = !!(contextFile && (await loadFile(contextFile)));
|
|
714
722
|
if (hasContext) return null; // fall through to next rule
|
|
@@ -1322,19 +1330,19 @@ export const DISPATCH_RULES: DispatchRule[] = [
|
|
|
1322
1330
|
}
|
|
1323
1331
|
}
|
|
1324
1332
|
|
|
1325
|
-
// Safety guard (#2675): block completion when VALIDATION
|
|
1326
|
-
//
|
|
1327
|
-
// terminal
|
|
1328
|
-
//
|
|
1333
|
+
// Safety guard (#2675, #5747): block completion when VALIDATION
|
|
1334
|
+
// verdict is non-passing. The state machine treats these verdicts as
|
|
1335
|
+
// terminal, but completing-milestone should NOT proceed — remediation
|
|
1336
|
+
// or human attention is needed.
|
|
1329
1337
|
const validationFile = resolveMilestoneFile(basePath, mid, "VALIDATION");
|
|
1330
1338
|
if (validationFile) {
|
|
1331
1339
|
const validationContent = await loadFile(validationFile);
|
|
1332
1340
|
if (validationContent) {
|
|
1333
1341
|
const verdict = extractVerdict(validationContent);
|
|
1334
|
-
if (verdict === "needs-remediation") {
|
|
1342
|
+
if (verdict === "needs-remediation" || verdict === "needs-attention") {
|
|
1335
1343
|
return {
|
|
1336
1344
|
action: "stop",
|
|
1337
|
-
reason: `Cannot complete milestone ${mid}: VALIDATION verdict is "
|
|
1345
|
+
reason: `Cannot complete milestone ${mid}: VALIDATION verdict is "${verdict}". Address the validation findings and re-run validation, or update the verdict manually.`,
|
|
1338
1346
|
level: "warning",
|
|
1339
1347
|
};
|
|
1340
1348
|
}
|