gsd-pi 2.71.0-dev.e17e0ce → 2.72.0-dev.593fa74
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 +34 -1
- package/dist/cli.js +17 -0
- package/dist/mcp-server.js +37 -14
- package/dist/resources/agents/debugger.md +58 -0
- package/dist/resources/agents/doc-writer.md +43 -0
- package/dist/resources/agents/git-ops.md +56 -0
- package/dist/resources/agents/javascript-pro.md +46 -271
- package/dist/resources/agents/planner.md +55 -0
- package/dist/resources/agents/refactorer.md +47 -0
- package/dist/resources/agents/reviewer.md +48 -0
- package/dist/resources/agents/security.md +59 -0
- package/dist/resources/agents/tester.md +50 -0
- package/dist/resources/agents/typescript-pro.md +41 -235
- package/dist/resources/extensions/claude-code-cli/partial-builder.js +40 -12
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +103 -6
- package/dist/resources/extensions/gsd/auto/phases.js +4 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +88 -33
- package/dist/resources/extensions/gsd/auto-start.js +24 -4
- package/dist/resources/extensions/gsd/auto.js +4 -0
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
- package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +2 -5
- package/dist/resources/extensions/gsd/doctor-providers.js +23 -0
- package/dist/resources/extensions/gsd/error-classifier.js +4 -1
- package/dist/resources/extensions/gsd/gate-registry.js +208 -0
- package/dist/resources/extensions/gsd/gsd-db.js +41 -0
- package/dist/resources/extensions/gsd/milestone-validation-gates.js +11 -12
- package/dist/resources/extensions/gsd/notification-overlay.js +26 -12
- package/dist/resources/extensions/gsd/notification-store.js +5 -4
- package/dist/resources/extensions/gsd/prompt-validation.js +126 -0
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +3 -1
- package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -0
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/dist/resources/extensions/gsd/shortcut-defs.js +7 -1
- package/dist/resources/extensions/gsd/state.js +9 -2
- package/dist/resources/extensions/gsd/tools/complete-slice.js +52 -1
- package/dist/resources/extensions/gsd/tools/complete-task.js +51 -1
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +4 -1
- package/dist/resources/extensions/ollama/index.js +13 -5
- package/dist/resources/extensions/shared/gsd-phase-state.js +35 -0
- package/dist/resources/extensions/subagent/agents.js +8 -0
- package/dist/resources/extensions/subagent/index.js +17 -0
- package/dist/startup-model-validation.d.ts +0 -1
- package/dist/startup-model-validation.js +6 -2
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- 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.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- 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 +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- 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 +1 -1
- 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 +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +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/package.json +1 -1
- package/packages/mcp-server/dist/server.d.ts +12 -1
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +90 -42
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/server.ts +110 -38
- package/packages/mcp-server/src/workflow-tools.ts +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts +8 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.test.js +75 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +5 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js +55 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js +57 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +36 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +9 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +87 -12
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +6 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/model-resolver.test.ts +85 -0
- package/packages/pi-coding-agent/src/core/retry-handler.test.ts +83 -0
- package/packages/pi-coding-agent/src/core/retry-handler.ts +60 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +72 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +15 -6
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +84 -12
- package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +6 -1
- package/pkg/package.json +1 -1
- package/src/resources/agents/debugger.md +58 -0
- package/src/resources/agents/doc-writer.md +43 -0
- package/src/resources/agents/git-ops.md +56 -0
- package/src/resources/agents/javascript-pro.md +46 -271
- package/src/resources/agents/planner.md +55 -0
- package/src/resources/agents/refactorer.md +47 -0
- package/src/resources/agents/reviewer.md +48 -0
- package/src/resources/agents/security.md +59 -0
- package/src/resources/agents/tester.md +50 -0
- package/src/resources/agents/typescript-pro.md +41 -235
- package/src/resources/extensions/claude-code-cli/partial-builder.ts +45 -12
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +109 -3
- package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +91 -2
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +133 -2
- package/src/resources/extensions/gsd/auto/phases.ts +4 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +111 -33
- package/src/resources/extensions/gsd/auto-start.ts +31 -4
- package/src/resources/extensions/gsd/auto.ts +4 -0
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
- package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +2 -5
- package/src/resources/extensions/gsd/doctor-providers.ts +24 -0
- package/src/resources/extensions/gsd/error-classifier.ts +4 -1
- package/src/resources/extensions/gsd/gate-registry.ts +251 -0
- package/src/resources/extensions/gsd/gsd-db.ts +51 -0
- package/src/resources/extensions/gsd/milestone-validation-gates.ts +11 -13
- package/src/resources/extensions/gsd/notification-overlay.ts +27 -11
- package/src/resources/extensions/gsd/notification-store.ts +5 -4
- package/src/resources/extensions/gsd/prompt-validation.ts +157 -0
- package/src/resources/extensions/gsd/prompts/complete-slice.md +3 -1
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -0
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/src/resources/extensions/gsd/shortcut-defs.ts +8 -1
- package/src/resources/extensions/gsd/state.ts +13 -2
- package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/complete-slice-gate-closure.test.ts +167 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/gate-registry.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/prompt-system-gate-coverage.test.ts +208 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +3 -2
- package/src/resources/extensions/gsd/tools/complete-slice.ts +63 -0
- package/src/resources/extensions/gsd/tools/complete-task.ts +63 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +4 -1
- package/src/resources/extensions/gsd/types.ts +26 -0
- package/src/resources/extensions/ollama/index.ts +13 -3
- package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +28 -0
- package/src/resources/extensions/shared/gsd-phase-state.ts +42 -0
- package/src/resources/extensions/shared/tests/gsd-phase-state.test.ts +48 -0
- package/src/resources/extensions/subagent/agents.ts +10 -0
- package/src/resources/extensions/subagent/index.ts +18 -0
- package/src/resources/extensions/subagent/tests/agents-conflicts.test.ts +33 -0
- /package/dist/web/standalone/.next/static/{cYPZv_bAhZk2ms-Pz6vsY → h8B07q4xc-ujHRD7esO6O}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{cYPZv_bAhZk2ms-Pz6vsY → h8B07q4xc-ujHRD7esO6O}/_ssgManifest.js +0 -0
|
@@ -6,17 +6,12 @@
|
|
|
6
6
|
* records in the DB. This module inserts milestone-level validation gates
|
|
7
7
|
* that correspond to the validation checks performed.
|
|
8
8
|
*
|
|
9
|
-
* Gate IDs for milestone validation
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* MV03 — Cross-slice integration
|
|
13
|
-
* MV04 — Requirement coverage
|
|
14
|
-
*
|
|
15
|
-
* These use the existing quality_gates table with scope "milestone".
|
|
9
|
+
* Gate IDs for milestone validation (MV01–MV04) are sourced from the
|
|
10
|
+
* gate registry so the definitions stay in lockstep with prompt builders,
|
|
11
|
+
* dispatch rules, and state derivation. See gate-registry.ts.
|
|
16
12
|
*/
|
|
17
13
|
import { _getAdapter } from "./gsd-db.js";
|
|
18
|
-
|
|
19
|
-
const MILESTONE_GATE_IDS = ["MV01", "MV02", "MV03", "MV04"];
|
|
14
|
+
import { getGatesForTurn } from "./gate-registry.js";
|
|
20
15
|
/**
|
|
21
16
|
* Insert milestone-level quality_gates records for a validation run.
|
|
22
17
|
*
|
|
@@ -24,21 +19,25 @@ const MILESTONE_GATE_IDS = ["MV01", "MV02", "MV03", "MV04"];
|
|
|
24
19
|
* from the overall milestone validation verdict. Individual gate-level
|
|
25
20
|
* verdicts are not available (the handler receives a single verdict),
|
|
26
21
|
* so all gates share the overall verdict.
|
|
22
|
+
*
|
|
23
|
+
* Gate IDs come from the registry — adding/removing an MV-scoped gate
|
|
24
|
+
* in gate-registry.ts automatically flows through here.
|
|
27
25
|
*/
|
|
28
26
|
export function insertMilestoneValidationGates(milestoneId, sliceId, verdict, evaluatedAt) {
|
|
29
27
|
const db = _getAdapter();
|
|
30
28
|
if (!db)
|
|
31
29
|
return;
|
|
32
30
|
const gateVerdict = verdict === "pass" ? "pass" : "flag";
|
|
33
|
-
|
|
31
|
+
const milestoneGates = getGatesForTurn("validate-milestone");
|
|
32
|
+
for (const def of milestoneGates) {
|
|
34
33
|
db.prepare(`INSERT OR REPLACE INTO quality_gates
|
|
35
34
|
(milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at)
|
|
36
35
|
VALUES (:mid, :sid, :gid, 'milestone', '', 'complete', :verdict, :rationale, '', :evaluated_at)`).run({
|
|
37
36
|
":mid": milestoneId,
|
|
38
37
|
":sid": sliceId,
|
|
39
|
-
":gid":
|
|
38
|
+
":gid": def.id,
|
|
40
39
|
":verdict": gateVerdict,
|
|
41
|
-
":rationale":
|
|
40
|
+
":rationale": `${def.promptSection} — milestone validation verdict: ${verdict}`,
|
|
42
41
|
":evaluated_at": evaluatedAt,
|
|
43
42
|
});
|
|
44
43
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Scrollable panel showing all persisted notifications with severity filtering.
|
|
3
3
|
// Toggled with Ctrl+Alt+N (⌃⌥N on macOS), Ctrl+Shift+N fallback, or /gsd notifications.
|
|
4
4
|
import { truncateToWidth, visibleWidth, matchesKey, Key } from "@gsd/pi-tui";
|
|
5
|
-
import { readNotifications, markAllRead, clearNotifications, } from "./notification-store.js";
|
|
5
|
+
import { readNotifications, markAllRead, clearNotifications, onNotificationStoreChange, } from "./notification-store.js";
|
|
6
6
|
import { formattedShortcutPair } from "./shortcut-defs.js";
|
|
7
7
|
import { padRight, joinColumns } from "../shared/mod.js";
|
|
8
8
|
const FILTER_CYCLE = ["all", "error", "warning", "info"];
|
|
@@ -74,6 +74,7 @@ export class GSDNotificationOverlay {
|
|
|
74
74
|
refreshTimer;
|
|
75
75
|
disposed = false;
|
|
76
76
|
resizeHandler = null;
|
|
77
|
+
unsubscribeStore = null;
|
|
77
78
|
constructor(tui, theme, onClose) {
|
|
78
79
|
this.tui = tui;
|
|
79
80
|
this.theme = theme;
|
|
@@ -90,20 +91,18 @@ export class GSDNotificationOverlay {
|
|
|
90
91
|
this.tui.requestRender();
|
|
91
92
|
};
|
|
92
93
|
process.stdout.on("resize", this.resizeHandler);
|
|
93
|
-
//
|
|
94
|
+
// Subscribe to store mutations for immediate updates
|
|
95
|
+
this.unsubscribeStore = onNotificationStoreChange(() => {
|
|
96
|
+
if (this.disposed)
|
|
97
|
+
return;
|
|
98
|
+
this._refreshFromDisk();
|
|
99
|
+
});
|
|
100
|
+
// 30s safety-net for cross-process edits (web subprocess, parallel workers)
|
|
94
101
|
this.refreshTimer = setInterval(() => {
|
|
95
102
|
if (this.disposed)
|
|
96
103
|
return;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (signature !== this.entriesSignature) {
|
|
100
|
-
markAllRead();
|
|
101
|
-
this.entries = readNotifications();
|
|
102
|
-
this.entriesSignature = notificationSignature(this.entries);
|
|
103
|
-
this.invalidate();
|
|
104
|
-
this.tui.requestRender();
|
|
105
|
-
}
|
|
106
|
-
}, 3000);
|
|
104
|
+
this._refreshFromDisk();
|
|
105
|
+
}, 30_000);
|
|
107
106
|
}
|
|
108
107
|
get filter() {
|
|
109
108
|
return FILTER_CYCLE[this.filterIndex];
|
|
@@ -188,11 +187,26 @@ export class GSDNotificationOverlay {
|
|
|
188
187
|
dispose() {
|
|
189
188
|
this.disposed = true;
|
|
190
189
|
clearInterval(this.refreshTimer);
|
|
190
|
+
if (this.unsubscribeStore) {
|
|
191
|
+
this.unsubscribeStore();
|
|
192
|
+
this.unsubscribeStore = null;
|
|
193
|
+
}
|
|
191
194
|
if (this.resizeHandler) {
|
|
192
195
|
process.stdout.removeListener("resize", this.resizeHandler);
|
|
193
196
|
this.resizeHandler = null;
|
|
194
197
|
}
|
|
195
198
|
}
|
|
199
|
+
_refreshFromDisk() {
|
|
200
|
+
const fresh = readNotifications();
|
|
201
|
+
const signature = notificationSignature(fresh);
|
|
202
|
+
if (signature !== this.entriesSignature) {
|
|
203
|
+
markAllRead();
|
|
204
|
+
this.entries = readNotifications();
|
|
205
|
+
this.entriesSignature = notificationSignature(this.entries);
|
|
206
|
+
this.invalidate();
|
|
207
|
+
this.tui.requestRender();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
196
210
|
wrapInBox(inner, width) {
|
|
197
211
|
const th = this.theme;
|
|
198
212
|
const border = (s) => th.fg("borderAccent", s);
|
|
@@ -301,10 +301,11 @@ function _withLock(basePath, fn) {
|
|
|
301
301
|
break;
|
|
302
302
|
}
|
|
303
303
|
}
|
|
304
|
-
//
|
|
305
|
-
|
|
304
|
+
// Best-effort: mutation runs regardless of lock status (idempotent overwrites).
|
|
305
|
+
// createdLock gates cleanup only — never skip fn() on lock failure.
|
|
306
|
+
const createdLock = fd !== null;
|
|
306
307
|
try {
|
|
307
|
-
if (
|
|
308
|
+
if (createdLock && fd !== null) {
|
|
308
309
|
// Write our PID timestamp into the lock for stale detection
|
|
309
310
|
writeFileSync(lockPath, String(Date.now()), "utf-8");
|
|
310
311
|
closeSync(fd);
|
|
@@ -313,7 +314,7 @@ function _withLock(basePath, fn) {
|
|
|
313
314
|
}
|
|
314
315
|
finally {
|
|
315
316
|
// Only delete the lock if we created it — never remove another process's lock
|
|
316
|
-
if (
|
|
317
|
+
if (createdLock) {
|
|
317
318
|
try {
|
|
318
319
|
unlinkSync(lockPath);
|
|
319
320
|
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Prompt Validation — Validates enhanced context and turn output
|
|
3
|
+
* artifacts before writing.
|
|
4
|
+
*
|
|
5
|
+
* Implements R109 validation requirement: CONTEXT.md must have required
|
|
6
|
+
* sections before being written to disk. Additionally, per-turn validators
|
|
7
|
+
* check that artifacts produced by gate-owning turns contain the gate
|
|
8
|
+
* sections declared in gate-registry.ts, so a malformed summary/validation
|
|
9
|
+
* markdown file cannot silently drop a quality gate.
|
|
10
|
+
*/
|
|
11
|
+
import { getGatesForTurn } from "./gate-registry.js";
|
|
12
|
+
/**
|
|
13
|
+
* Validate that enhanced context content has all required sections.
|
|
14
|
+
*
|
|
15
|
+
* Required sections per R109:
|
|
16
|
+
* - Scope section (## Scope, ## Milestone Scope, or ## Why This Milestone)
|
|
17
|
+
* - Architectural Decisions section (## Architectural Decisions)
|
|
18
|
+
* - Acceptance Criteria section (## Acceptance Criteria or ## Final Integrated Acceptance)
|
|
19
|
+
*
|
|
20
|
+
* Additionally validates that the Architectural Decisions section contains
|
|
21
|
+
* at least one decision entry (### heading or **Decision marker).
|
|
22
|
+
*
|
|
23
|
+
* @param content - The enhanced context markdown content
|
|
24
|
+
* @returns ValidationResult with valid flag and list of missing sections
|
|
25
|
+
*/
|
|
26
|
+
export function validateEnhancedContext(content) {
|
|
27
|
+
const missing = [];
|
|
28
|
+
// Required section 1: Scope (multiple acceptable header variants)
|
|
29
|
+
const hasScopeSection = /^## Scope\b/m.test(content) ||
|
|
30
|
+
/^## Milestone Scope\b/m.test(content) ||
|
|
31
|
+
/^## Why This Milestone\b/m.test(content);
|
|
32
|
+
if (!hasScopeSection) {
|
|
33
|
+
missing.push("Milestone Scope or Why This Milestone");
|
|
34
|
+
}
|
|
35
|
+
// Required section 2: Architectural Decisions
|
|
36
|
+
const hasArchitecturalDecisions = /^## Architectural Decisions\b/m.test(content);
|
|
37
|
+
if (!hasArchitecturalDecisions) {
|
|
38
|
+
missing.push("Architectural Decisions");
|
|
39
|
+
}
|
|
40
|
+
// Required section 3: Acceptance Criteria (multiple acceptable header variants)
|
|
41
|
+
const hasAcceptanceCriteria = /^## Acceptance Criteria\b/m.test(content) ||
|
|
42
|
+
/^## Final Integrated Acceptance\b/m.test(content);
|
|
43
|
+
if (!hasAcceptanceCriteria) {
|
|
44
|
+
missing.push("Acceptance Criteria");
|
|
45
|
+
}
|
|
46
|
+
// Additional validation: Architectural Decisions must have at least one entry
|
|
47
|
+
if (hasArchitecturalDecisions) {
|
|
48
|
+
// Extract the section content between ## Architectural Decisions and the next ## heading.
|
|
49
|
+
// Uses indexOf-based extraction instead of regex with \z (which is invalid in JavaScript
|
|
50
|
+
// regex — it's PCRE/Ruby syntax and JS treats it as literal 'z').
|
|
51
|
+
const sectionStart = content.indexOf("## Architectural Decisions");
|
|
52
|
+
if (sectionStart === -1) {
|
|
53
|
+
missing.push("Architectural Decisions");
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const afterHeading = content.slice(sectionStart + "## Architectural Decisions".length);
|
|
57
|
+
const nextSection = afterHeading.search(/^## /m);
|
|
58
|
+
const sectionContent = nextSection === -1 ? afterHeading : afterHeading.slice(0, nextSection);
|
|
59
|
+
// Check for actual decision entries:
|
|
60
|
+
// - ### heading (subsection per decision)
|
|
61
|
+
// - **Decision marker (inline decision format)
|
|
62
|
+
const hasDecisionEntry = /^### /m.test(sectionContent) || /^\*\*Decision/m.test(sectionContent);
|
|
63
|
+
if (!hasDecisionEntry) {
|
|
64
|
+
missing.push("At least one architectural decision entry");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
valid: missing.length === 0,
|
|
70
|
+
missing,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// ─── Per-Turn Gate Section Validators ─────────────────────────────────────
|
|
74
|
+
//
|
|
75
|
+
// Each validator checks that the artifact written by a turn contains a
|
|
76
|
+
// heading for every gate owned by that turn. The registry is the source
|
|
77
|
+
// of truth for which sections must exist; adding a new gate automatically
|
|
78
|
+
// flows through via `getGatesForTurn(turn)`.
|
|
79
|
+
/**
|
|
80
|
+
* Escape a string so it can be embedded safely inside a regular expression.
|
|
81
|
+
*/
|
|
82
|
+
function escapeRegExp(value) {
|
|
83
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Validate that an artifact contains an `## H2` heading for every gate the
|
|
87
|
+
* named turn owns. Returns the list of missing gate section headers.
|
|
88
|
+
*
|
|
89
|
+
* Soft rule: a section counts as "present" if it is declared (H2 heading
|
|
90
|
+
* exists) — empty-body sections are allowed and handled by the tool
|
|
91
|
+
* handler, which will record such gates as `omitted`.
|
|
92
|
+
*/
|
|
93
|
+
export function validateGateSections(content, turn) {
|
|
94
|
+
const missing = [];
|
|
95
|
+
for (const def of getGatesForTurn(turn)) {
|
|
96
|
+
const pattern = new RegExp(`^##\\s+${escapeRegExp(def.promptSection)}\\b`, "m");
|
|
97
|
+
if (!pattern.test(content)) {
|
|
98
|
+
missing.push(`${def.id} (## ${def.promptSection})`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return { valid: missing.length === 0, missing };
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Validate a SUMMARY.md produced by the complete-slice turn. Requires
|
|
105
|
+
* an H2 heading for every gate owned by complete-slice (e.g. Q8 →
|
|
106
|
+
* "## Operational Readiness"). Intended for use in the tool handler's
|
|
107
|
+
* pre-write checks or in the post-unit validation sweep.
|
|
108
|
+
*/
|
|
109
|
+
export function validateSliceSummaryOutput(content) {
|
|
110
|
+
return validateGateSections(content, "complete-slice");
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Validate a task SUMMARY.md produced by the execute-task turn. Only
|
|
114
|
+
* flags gates that are still pending for the task; skips the check
|
|
115
|
+
* when no rows are seeded (simple task).
|
|
116
|
+
*/
|
|
117
|
+
export function validateTaskSummaryOutput(content) {
|
|
118
|
+
return validateGateSections(content, "execute-task");
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Validate a VALIDATION.md produced by the validate-milestone turn.
|
|
122
|
+
* Requires an H2 heading for every MV gate declared in the registry.
|
|
123
|
+
*/
|
|
124
|
+
export function validateMilestoneValidationOutput(content) {
|
|
125
|
+
return validateGateSections(content, "validate-milestone");
|
|
126
|
+
}
|
|
@@ -16,6 +16,8 @@ All relevant context has been preloaded below — the slice plan, all task summa
|
|
|
16
16
|
|
|
17
17
|
{{inlinedContext}}
|
|
18
18
|
|
|
19
|
+
{{gatesToClose}}
|
|
20
|
+
|
|
19
21
|
**Match effort to complexity.** A simple slice with 1-2 tasks needs a brief summary and lightweight verification. A complex slice with 5 tasks across multiple subsystems needs thorough verification and a detailed summary. Scale the work below accordingly.
|
|
20
22
|
|
|
21
23
|
Then:
|
|
@@ -23,7 +25,7 @@ Then:
|
|
|
23
25
|
2. {{skillActivation}}
|
|
24
26
|
3. Run all slice-level verification checks defined in the slice plan. All must pass before marking the slice done. If any fail, fix them first. Task artifacts use a **flat file layout** directly inside `tasks/` (for example `T01-SUMMARY.md`, `T02-SUMMARY.md`) rather than per-task subdirectories. If you need to count or re-read task summaries during verification, use `find .gsd/milestones/{{milestoneId}}/slices/{{sliceId}}/tasks -name "*-SUMMARY.md"` or `ls .gsd/milestones/{{milestoneId}}/slices/{{sliceId}}/tasks/*-SUMMARY.md`. Never use `tasks/*/SUMMARY.md` — that glob expects subdirectories that do not exist.
|
|
25
27
|
4. If the slice plan includes observability/diagnostic surfaces, confirm they work. Skip this for simple slices that don't have observability sections.
|
|
26
|
-
5.
|
|
28
|
+
5. Address every gate listed in the **Gates to Close** section above — each gate maps to a specific slice-summary section the handler inspects (for example, Q8 maps to **Operational Readiness**: health signal, failure signal, recovery procedure, and monitoring gaps). Leaving a section empty records the gate as `omitted`.
|
|
27
29
|
6. If this slice produced evidence that a requirement changed status (Active → Validated, Active → Deferred, etc.), call `gsd_requirement_update` with the requirement ID, updated `status`, and `validation` evidence. Do NOT write `.gsd/REQUIREMENTS.md` directly — the engine renders it from the database.
|
|
28
30
|
7. Prepare the slice completion content you will pass to `gsd_complete_slice` using the camelCase fields `milestoneId`, `sliceId`, `sliceTitle`, `oneLiner`, `narrative`, `verification`, and `uatContent`. Do **not** manually write `{{sliceSummaryPath}}`. Do **not** manually write `{{sliceUatPath}}` — the DB-backed tool is the canonical write path for both artifacts.
|
|
29
31
|
8. Draft the UAT content you will pass as `uatContent` — a concrete UAT script with real test cases derived from the slice plan and task summaries. Include preconditions, numbered steps with expected outcomes, and edge cases. This must NOT be a placeholder or generic template — tailor every test case to what this slice actually built.
|
|
@@ -5,16 +5,19 @@ export const GSD_SHORTCUTS = {
|
|
|
5
5
|
key: "g",
|
|
6
6
|
action: "Open GSD dashboard",
|
|
7
7
|
command: "/gsd status",
|
|
8
|
+
hasFallback: true,
|
|
8
9
|
},
|
|
9
10
|
notifications: {
|
|
10
11
|
key: "n",
|
|
11
12
|
action: "Open notification history",
|
|
12
13
|
command: "/gsd notifications",
|
|
14
|
+
hasFallback: true,
|
|
13
15
|
},
|
|
14
16
|
parallel: {
|
|
15
17
|
key: "p",
|
|
16
18
|
action: "Open parallel worker monitor",
|
|
17
19
|
command: "/gsd parallel watch",
|
|
20
|
+
hasFallback: false, // Ctrl+Shift+P conflicts with cycleModelBackward
|
|
18
21
|
},
|
|
19
22
|
};
|
|
20
23
|
function combo(prefix, key) {
|
|
@@ -27,7 +30,10 @@ export function fallbackShortcutCombo(id) {
|
|
|
27
30
|
return combo("Ctrl+Shift+", GSD_SHORTCUTS[id].key);
|
|
28
31
|
}
|
|
29
32
|
export function shortcutPair(id, formatter = (combo) => combo) {
|
|
30
|
-
|
|
33
|
+
const primary = formatter(primaryShortcutCombo(id));
|
|
34
|
+
if (!GSD_SHORTCUTS[id].hasFallback)
|
|
35
|
+
return primary;
|
|
36
|
+
return `${primary} / ${formatter(fallbackShortcutCombo(id))}`;
|
|
31
37
|
}
|
|
32
38
|
export function formattedShortcutPair(id) {
|
|
33
39
|
return shortcutPair(id, formatShortcut);
|
|
@@ -13,7 +13,7 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
|
13
13
|
import { debugCount, debugTime } from './debug-logger.js';
|
|
14
14
|
import { logWarning, logError } from './workflow-logger.js';
|
|
15
15
|
import { extractVerdict } from './verdict-parser.js';
|
|
16
|
-
import { isDbAvailable, wasDbOpenAttempted, getAllMilestones, getMilestone, getMilestoneSlices, getSliceTasks, getReplanHistory, getSlice, insertMilestone, insertSlice, insertTask, updateTaskStatus,
|
|
16
|
+
import { isDbAvailable, wasDbOpenAttempted, getAllMilestones, getMilestone, getMilestoneSlices, getSliceTasks, getReplanHistory, getSlice, insertMilestone, insertSlice, insertTask, updateTaskStatus, getPendingGateCountForTurn, } from './gsd-db.js';
|
|
17
17
|
/**
|
|
18
18
|
* A "ghost" milestone directory contains only META.json (and no substantive
|
|
19
19
|
* files like CONTEXT, CONTEXT-DRAFT, ROADMAP, or SUMMARY). These appear when
|
|
@@ -724,7 +724,14 @@ export async function deriveStateFromDb(basePath) {
|
|
|
724
724
|
};
|
|
725
725
|
}
|
|
726
726
|
}
|
|
727
|
-
|
|
727
|
+
// ── Quality gate evaluation check ──────────────────────────────────
|
|
728
|
+
// Pause before execution only when gates owned by the `gate-evaluate`
|
|
729
|
+
// turn (Q3/Q4) are still pending. Q8 is also `scope:"slice"` but is
|
|
730
|
+
// owned by `complete-slice`, so it must NOT block the evaluating-gates
|
|
731
|
+
// phase — otherwise auto-loop stalls forever waiting for a gate that
|
|
732
|
+
// this turn never evaluates. See gate-registry.ts for the ownership map.
|
|
733
|
+
// Slices with zero gate rows (pre-feature or simple) skip straight through.
|
|
734
|
+
const pendingGateCount = getPendingGateCountForTurn(activeMilestone.id, activeSlice.id, "gate-evaluate");
|
|
728
735
|
if (pendingGateCount > 0) {
|
|
729
736
|
return {
|
|
730
737
|
activeMilestone, activeSlice, activeTask: null,
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { mkdirSync } from "node:fs";
|
|
11
11
|
import { isClosedStatus } from "../status-guards.js";
|
|
12
|
-
import { transaction, insertMilestone, insertSlice, getSlice, getSliceTasks, getMilestone, updateSliceStatus, setSliceSummaryMd, } from "../gsd-db.js";
|
|
12
|
+
import { transaction, insertMilestone, insertSlice, getSlice, getSliceTasks, getMilestone, updateSliceStatus, setSliceSummaryMd, saveGateResult, getPendingGatesForTurn, } from "../gsd-db.js";
|
|
13
|
+
import { getGatesForTurn } from "../gate-registry.js";
|
|
13
14
|
import { resolveSlicePath, clearPathCache } from "../paths.js";
|
|
14
15
|
import { checkOwnership, sliceUnitKey } from "../unit-ownership.js";
|
|
15
16
|
import { saveFile, clearParseCache } from "../files.js";
|
|
@@ -19,6 +20,19 @@ import { renderAllProjections } from "../workflow-projections.js";
|
|
|
19
20
|
import { writeManifest } from "../workflow-manifest.js";
|
|
20
21
|
import { appendEvent } from "../workflow-events.js";
|
|
21
22
|
import { logWarning, logError } from "../workflow-logger.js";
|
|
23
|
+
/**
|
|
24
|
+
* Map a complete-slice-owned gate id to the CompleteSliceParams field
|
|
25
|
+
* whose presence drives `pass` vs. `omitted`. Keep this in lockstep with
|
|
26
|
+
* the gates declared in gate-registry.ts under ownerTurn "complete-slice".
|
|
27
|
+
*/
|
|
28
|
+
function sliceGateFieldForId(id, params) {
|
|
29
|
+
switch (id) {
|
|
30
|
+
case "Q8":
|
|
31
|
+
return params.operationalReadiness;
|
|
32
|
+
default:
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
22
36
|
/**
|
|
23
37
|
* Render slice summary markdown matching the template format.
|
|
24
38
|
* YAML frontmatter uses snake_case keys for parseSummary() compatibility.
|
|
@@ -134,6 +148,10 @@ ${reqSurfaced}
|
|
|
134
148
|
|
|
135
149
|
${reqInvalidated}
|
|
136
150
|
|
|
151
|
+
## Operational Readiness
|
|
152
|
+
|
|
153
|
+
${params.operationalReadiness?.trim() || "None."}
|
|
154
|
+
|
|
137
155
|
## Deviations
|
|
138
156
|
|
|
139
157
|
${params.deviations || "None."}
|
|
@@ -271,6 +289,39 @@ export async function handleCompleteSlice(params, basePath) {
|
|
|
271
289
|
}
|
|
272
290
|
// Store rendered markdown in DB for D004 recovery
|
|
273
291
|
setSliceSummaryMd(params.milestoneId, params.sliceId, summaryMd, uatMd);
|
|
292
|
+
// ── Close gates owned by complete-slice (Q8) ───────────────────────────
|
|
293
|
+
// Each owned gate maps to a specific summary section via the registry.
|
|
294
|
+
// If the caller populated the corresponding field, record `pass`; if the
|
|
295
|
+
// field is empty, record `omitted`. Without this loop, Q8 would stay
|
|
296
|
+
// pending forever and block future state derivation (see gate-registry).
|
|
297
|
+
try {
|
|
298
|
+
const pendingGates = getPendingGatesForTurn(params.milestoneId, params.sliceId, "complete-slice");
|
|
299
|
+
if (pendingGates.length > 0) {
|
|
300
|
+
const ownedDefs = new Map(getGatesForTurn("complete-slice").map((g) => [g.id, g]));
|
|
301
|
+
for (const row of pendingGates) {
|
|
302
|
+
const def = ownedDefs.get(row.gate_id);
|
|
303
|
+
if (!def)
|
|
304
|
+
continue;
|
|
305
|
+
// Map gate id → param field it maps to. Keep the map local so
|
|
306
|
+
// adding a new complete-slice gate is a single place change.
|
|
307
|
+
const field = sliceGateFieldForId(def.id, params);
|
|
308
|
+
const hasContent = typeof field === "string" && field.trim().length > 0;
|
|
309
|
+
saveGateResult({
|
|
310
|
+
milestoneId: params.milestoneId,
|
|
311
|
+
sliceId: params.sliceId,
|
|
312
|
+
gateId: def.id,
|
|
313
|
+
verdict: hasContent ? "pass" : "omitted",
|
|
314
|
+
rationale: hasContent
|
|
315
|
+
? `${def.promptSection} section populated in slice summary`
|
|
316
|
+
: `${def.promptSection} section left empty — recorded as omitted`,
|
|
317
|
+
findings: hasContent ? field.trim() : "",
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
catch (gateErr) {
|
|
323
|
+
logWarning("tool", `complete-slice gate close warning for ${params.milestoneId}/${params.sliceId}: ${gateErr.message}`);
|
|
324
|
+
}
|
|
274
325
|
// Invalidate all caches
|
|
275
326
|
invalidateStateCache();
|
|
276
327
|
clearPathCache();
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { mkdirSync } from "node:fs";
|
|
11
11
|
import { isClosedStatus } from "../status-guards.js";
|
|
12
|
-
import { transaction, insertMilestone, insertSlice, insertTask, insertVerificationEvidence, getMilestone, getSlice, getTask, updateTaskStatus, setTaskSummaryMd, deleteVerificationEvidence, } from "../gsd-db.js";
|
|
12
|
+
import { transaction, insertMilestone, insertSlice, insertTask, insertVerificationEvidence, getMilestone, getSlice, getTask, updateTaskStatus, setTaskSummaryMd, deleteVerificationEvidence, saveGateResult, getPendingGatesForTurn, } from "../gsd-db.js";
|
|
13
|
+
import { getGatesForTurn } from "../gate-registry.js";
|
|
13
14
|
import { resolveSliceFile, resolveTasksDir, clearPathCache } from "../paths.js";
|
|
14
15
|
import { checkOwnership, taskUnitKey } from "../unit-ownership.js";
|
|
15
16
|
import { saveFile, clearParseCache } from "../files.js";
|
|
@@ -19,6 +20,23 @@ import { renderAllProjections, renderSummaryContent } from "../workflow-projecti
|
|
|
19
20
|
import { writeManifest } from "../workflow-manifest.js";
|
|
20
21
|
import { appendEvent } from "../workflow-events.js";
|
|
21
22
|
import { logWarning, logError } from "../workflow-logger.js";
|
|
23
|
+
/**
|
|
24
|
+
* Map an execute-task-owned gate id to the CompleteTaskParams field whose
|
|
25
|
+
* presence drives `pass` vs. `omitted`. Keep in lockstep with the gates
|
|
26
|
+
* declared in gate-registry.ts under ownerTurn "execute-task".
|
|
27
|
+
*/
|
|
28
|
+
function taskGateFieldForId(id, params) {
|
|
29
|
+
switch (id) {
|
|
30
|
+
case "Q5":
|
|
31
|
+
return params.failureModes;
|
|
32
|
+
case "Q6":
|
|
33
|
+
return params.loadProfile;
|
|
34
|
+
case "Q7":
|
|
35
|
+
return params.negativeTests;
|
|
36
|
+
default:
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
22
40
|
/**
|
|
23
41
|
* Normalize a list parameter that may arrive as a string (newline-delimited
|
|
24
42
|
* bullet list from the LLM) into a string array (#3361).
|
|
@@ -189,6 +207,38 @@ export async function handleCompleteTask(params, basePath) {
|
|
|
189
207
|
}
|
|
190
208
|
// Store rendered markdown in DB for D004 recovery
|
|
191
209
|
setTaskSummaryMd(params.milestoneId, params.sliceId, params.taskId, summaryMd);
|
|
210
|
+
// ── Close gates owned by execute-task (Q5/Q6/Q7) for this task ────────
|
|
211
|
+
// Each gate id maps to a specific params field via taskGateFieldForId.
|
|
212
|
+
// When the model populates the field, record `pass`; when it's empty,
|
|
213
|
+
// record `omitted`. Task-scoped rows are filtered by taskId so a single
|
|
214
|
+
// task's completion doesn't touch sibling tasks' gate rows.
|
|
215
|
+
try {
|
|
216
|
+
const pendingGates = getPendingGatesForTurn(params.milestoneId, params.sliceId, "execute-task", params.taskId);
|
|
217
|
+
if (pendingGates.length > 0) {
|
|
218
|
+
const ownedDefs = new Map(getGatesForTurn("execute-task").map((g) => [g.id, g]));
|
|
219
|
+
for (const row of pendingGates) {
|
|
220
|
+
const def = ownedDefs.get(row.gate_id);
|
|
221
|
+
if (!def)
|
|
222
|
+
continue;
|
|
223
|
+
const field = taskGateFieldForId(def.id, params);
|
|
224
|
+
const hasContent = typeof field === "string" && field.trim().length > 0;
|
|
225
|
+
saveGateResult({
|
|
226
|
+
milestoneId: params.milestoneId,
|
|
227
|
+
sliceId: params.sliceId,
|
|
228
|
+
taskId: params.taskId,
|
|
229
|
+
gateId: def.id,
|
|
230
|
+
verdict: hasContent ? "pass" : "omitted",
|
|
231
|
+
rationale: hasContent
|
|
232
|
+
? `${def.promptSection} section populated in task summary`
|
|
233
|
+
: `${def.promptSection} section left empty — recorded as omitted`,
|
|
234
|
+
findings: hasContent ? field.trim() : "",
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch (gateErr) {
|
|
240
|
+
logWarning("tool", `complete-task gate close warning for ${params.milestoneId}/${params.sliceId}/${params.taskId}: ${gateErr.message}`);
|
|
241
|
+
}
|
|
192
242
|
// Invalidate all caches
|
|
193
243
|
invalidateStateCache();
|
|
194
244
|
clearPathCache();
|
|
@@ -2,6 +2,7 @@ import { ensureDbOpen } from "../bootstrap/dynamic-tools.js";
|
|
|
2
2
|
import { sanitizeCompleteMilestoneParams } from "../bootstrap/sanitize-complete-milestone.js";
|
|
3
3
|
import { loadWriteGateSnapshot, shouldBlockContextArtifactSaveInSnapshot } from "../bootstrap/write-gate.js";
|
|
4
4
|
import { getMilestone, getSliceStatusSummary, getSliceTaskCounts, _getAdapter, saveGateResult, } from "../gsd-db.js";
|
|
5
|
+
import { GATE_REGISTRY } from "../gate-registry.js";
|
|
5
6
|
import { saveArtifactToDb } from "../db-writer.js";
|
|
6
7
|
import { handleCompleteMilestone } from "./complete-milestone.js";
|
|
7
8
|
import { handleCompleteTask } from "./complete-task.js";
|
|
@@ -323,7 +324,9 @@ export async function executeSaveGateResult(params, basePath = process.cwd()) {
|
|
|
323
324
|
isError: true,
|
|
324
325
|
};
|
|
325
326
|
}
|
|
326
|
-
|
|
327
|
+
// Source of truth: gate-registry.ts. Every declared GateId is accepted,
|
|
328
|
+
// so adding a new gate in one place automatically flows through here.
|
|
329
|
+
const validGates = Object.keys(GATE_REGISTRY);
|
|
327
330
|
if (!validGates.includes(params.gateId)) {
|
|
328
331
|
return {
|
|
329
332
|
content: [{ type: "text", text: `Error: Invalid gateId "${params.gateId}". Must be one of: ${validGates.join(", ")}` }],
|
|
@@ -49,8 +49,15 @@ async function probeAndRegister(pi) {
|
|
|
49
49
|
return false;
|
|
50
50
|
}
|
|
51
51
|
const models = await discoverModels();
|
|
52
|
-
if (models.length === 0)
|
|
53
|
-
|
|
52
|
+
if (models.length === 0) {
|
|
53
|
+
// No local models means there's nothing usable to register in GSD.
|
|
54
|
+
// Keep the footer/status clean instead of advertising Ollama availability.
|
|
55
|
+
if (providerRegistered) {
|
|
56
|
+
pi.unregisterProvider("ollama");
|
|
57
|
+
providerRegistered = false;
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
54
61
|
const baseUrl = client.getOllamaHost();
|
|
55
62
|
// Use authMode "apiKey" with a dummy key (#3440).
|
|
56
63
|
// authMode "none" requires a custom streamSimple handler, but Ollama uses
|
|
@@ -102,10 +109,11 @@ export default function ollama(pi) {
|
|
|
102
109
|
else {
|
|
103
110
|
probeAndRegister(pi)
|
|
104
111
|
.then((found) => {
|
|
105
|
-
|
|
106
|
-
ctx.ui.setStatus("ollama", "Ollama");
|
|
112
|
+
ctx.ui.setStatus("ollama", found ? "Ollama" : undefined);
|
|
107
113
|
})
|
|
108
|
-
.catch(() => {
|
|
114
|
+
.catch(() => {
|
|
115
|
+
ctx.ui.setStatus("ollama", undefined);
|
|
116
|
+
});
|
|
109
117
|
}
|
|
110
118
|
});
|
|
111
119
|
pi.on("session_shutdown", async () => {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Phase State — cross-extension coordination
|
|
3
|
+
* Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
4
|
+
*
|
|
5
|
+
* Lightweight module-level state that GSD auto-mode writes to and the
|
|
6
|
+
* subagent tool reads from. Both extensions run in the same process so
|
|
7
|
+
* a module variable is sufficient — no file I/O needed.
|
|
8
|
+
*/
|
|
9
|
+
let _active = false;
|
|
10
|
+
let _currentPhase = null;
|
|
11
|
+
/** Mark GSD auto-mode as active. */
|
|
12
|
+
export function activateGSD() {
|
|
13
|
+
_active = true;
|
|
14
|
+
}
|
|
15
|
+
/** Mark GSD auto-mode as inactive and clear the current phase. */
|
|
16
|
+
export function deactivateGSD() {
|
|
17
|
+
_active = false;
|
|
18
|
+
_currentPhase = null;
|
|
19
|
+
}
|
|
20
|
+
/** Set the currently dispatched GSD phase (e.g. "plan-milestone"). */
|
|
21
|
+
export function setCurrentPhase(phase) {
|
|
22
|
+
_currentPhase = phase;
|
|
23
|
+
}
|
|
24
|
+
/** Clear the current phase (unit completed or aborted). */
|
|
25
|
+
export function clearCurrentPhase() {
|
|
26
|
+
_currentPhase = null;
|
|
27
|
+
}
|
|
28
|
+
/** Returns true if GSD auto-mode is currently active. */
|
|
29
|
+
export function isGSDActive() {
|
|
30
|
+
return _active;
|
|
31
|
+
}
|
|
32
|
+
/** Returns the current GSD phase, or null if none is active. */
|
|
33
|
+
export function getCurrentPhase() {
|
|
34
|
+
return _active ? _currentPhase : null;
|
|
35
|
+
}
|
|
@@ -5,6 +5,12 @@ import * as fs from "node:fs";
|
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { getAgentDir, parseFrontmatter } from "@gsd/pi-coding-agent";
|
|
7
7
|
const PROJECT_AGENT_DIR_CANDIDATES = [".gsd", ".pi"];
|
|
8
|
+
export function parseConflictsWith(value) {
|
|
9
|
+
if (typeof value !== "string")
|
|
10
|
+
return undefined;
|
|
11
|
+
const conflicts = value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
12
|
+
return conflicts.length > 0 ? conflicts : undefined;
|
|
13
|
+
}
|
|
8
14
|
function parseAgentTools(value) {
|
|
9
15
|
if (typeof value === "string") {
|
|
10
16
|
const tools = value
|
|
@@ -52,11 +58,13 @@ function loadAgentsFromDir(dir, source) {
|
|
|
52
58
|
continue;
|
|
53
59
|
}
|
|
54
60
|
const tools = parseAgentTools(frontmatter.tools);
|
|
61
|
+
const conflictsWith = parseConflictsWith(frontmatter.conflicts_with);
|
|
55
62
|
agents.push({
|
|
56
63
|
name: frontmatter.name,
|
|
57
64
|
description: frontmatter.description,
|
|
58
65
|
tools: tools && tools.length > 0 ? tools : undefined,
|
|
59
66
|
model: frontmatter.model,
|
|
67
|
+
conflictsWith,
|
|
60
68
|
systemPrompt: body,
|
|
61
69
|
source,
|
|
62
70
|
filePath,
|