gsd-pi 2.37.1 → 2.38.0-dev.96dc7fb
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 +1 -1
- package/dist/cli.js +9 -0
- package/dist/extension-discovery.d.ts +5 -3
- package/dist/extension-discovery.js +14 -9
- package/dist/onboarding.js +1 -0
- package/dist/resources/extensions/browser-tools/package.json +3 -1
- package/dist/resources/extensions/cmux/index.js +55 -1
- package/dist/resources/extensions/context7/package.json +1 -1
- package/dist/resources/extensions/google-search/package.json +3 -1
- package/dist/resources/extensions/gsd/auto-dispatch.js +67 -1
- package/dist/resources/extensions/gsd/auto-loop.js +7 -1
- package/dist/resources/extensions/gsd/auto-post-unit.js +14 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +91 -2
- package/dist/resources/extensions/gsd/auto-recovery.js +37 -1
- package/dist/resources/extensions/gsd/auto-start.js +6 -1
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +11 -4
- package/dist/resources/extensions/gsd/captures.js +9 -1
- package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
- package/dist/resources/extensions/gsd/commands.js +20 -1
- package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
- package/dist/resources/extensions/gsd/doctor-format.js +15 -0
- package/dist/resources/extensions/gsd/doctor-providers.js +35 -1
- package/dist/resources/extensions/gsd/doctor.js +184 -11
- package/dist/resources/extensions/gsd/files.js +41 -0
- package/dist/resources/extensions/gsd/observability-validator.js +24 -0
- package/dist/resources/extensions/gsd/package.json +1 -1
- package/dist/resources/extensions/gsd/preferences-types.js +2 -1
- package/dist/resources/extensions/gsd/preferences-validation.js +42 -0
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +2 -1
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +41 -0
- package/dist/resources/extensions/gsd/reactive-graph.js +227 -0
- package/dist/resources/extensions/gsd/templates/task-plan.md +11 -3
- package/dist/resources/extensions/gsd/worktree.js +35 -16
- package/dist/resources/extensions/subagent/index.js +12 -3
- package/dist/resources/extensions/universal-config/package.json +1 -1
- package/dist/welcome-screen.d.ts +12 -0
- package/dist/welcome-screen.js +53 -0
- package/package.json +2 -1
- package/packages/pi-ai/dist/env-api-keys.js +13 -0
- package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +172 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +172 -0
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +64 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.js +668 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts +5 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.js +85 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.js.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic.d.ts +4 -30
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +47 -764
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/register-builtins.js +6 -0
- package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +2 -2
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/package.json +1 -0
- package/packages/pi-ai/src/env-api-keys.ts +14 -0
- package/packages/pi-ai/src/models.generated.ts +172 -0
- package/packages/pi-ai/src/providers/anthropic-shared.ts +761 -0
- package/packages/pi-ai/src/providers/anthropic-vertex.ts +130 -0
- package/packages/pi-ai/src/providers/anthropic.ts +76 -868
- package/packages/pi-ai/src/providers/register-builtins.ts +7 -0
- package/packages/pi-ai/src/types.ts +2 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
- package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
- package/pkg/package.json +1 -1
- package/src/resources/extensions/cmux/index.ts +57 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +93 -0
- package/src/resources/extensions/gsd/auto-loop.ts +13 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +14 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +125 -3
- package/src/resources/extensions/gsd/auto-recovery.ts +42 -0
- package/src/resources/extensions/gsd/auto-start.ts +7 -1
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +12 -3
- package/src/resources/extensions/gsd/captures.ts +10 -1
- package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
- package/src/resources/extensions/gsd/commands.ts +21 -1
- package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
- package/src/resources/extensions/gsd/doctor-format.ts +20 -0
- package/src/resources/extensions/gsd/doctor-providers.ts +38 -1
- package/src/resources/extensions/gsd/doctor-types.ts +16 -1
- package/src/resources/extensions/gsd/doctor.ts +177 -13
- package/src/resources/extensions/gsd/files.ts +45 -0
- package/src/resources/extensions/gsd/observability-validator.ts +27 -0
- package/src/resources/extensions/gsd/preferences-types.ts +5 -1
- package/src/resources/extensions/gsd/preferences-validation.ts +41 -0
- package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -1
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +41 -0
- package/src/resources/extensions/gsd/reactive-graph.ts +289 -0
- package/src/resources/extensions/gsd/templates/task-plan.md +11 -3
- package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +108 -3
- package/src/resources/extensions/gsd/tests/plan-quality-validator.test.ts +111 -0
- package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +511 -0
- package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +299 -0
- package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
- package/src/resources/extensions/gsd/types.ts +43 -0
- package/src/resources/extensions/gsd/worktree.ts +35 -15
- package/src/resources/extensions/subagent/index.ts +12 -3
|
@@ -289,10 +289,17 @@ export class CmuxClient {
|
|
|
289
289
|
}
|
|
290
290
|
|
|
291
291
|
async createSplit(direction: "right" | "down" | "left" | "up"): Promise<string | null> {
|
|
292
|
+
return this.createSplitFrom(this.config.surfaceId, direction);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async createSplitFrom(
|
|
296
|
+
sourceSurfaceId: string | undefined,
|
|
297
|
+
direction: "right" | "down" | "left" | "up",
|
|
298
|
+
): Promise<string | null> {
|
|
292
299
|
if (!this.config.splits) return null;
|
|
293
300
|
const before = new Set(await this.listSurfaceIds());
|
|
294
301
|
const args = ["new-split", direction];
|
|
295
|
-
const scopedArgs = this.appendSurface(this.appendWorkspace(args),
|
|
302
|
+
const scopedArgs = this.appendSurface(this.appendWorkspace(args), sourceSurfaceId);
|
|
296
303
|
await this.runAsync(scopedArgs);
|
|
297
304
|
const after = await this.listSurfaceIds();
|
|
298
305
|
for (const id of after) {
|
|
@@ -301,6 +308,55 @@ export class CmuxClient {
|
|
|
301
308
|
return null;
|
|
302
309
|
}
|
|
303
310
|
|
|
311
|
+
/**
|
|
312
|
+
* Create a grid of surfaces for parallel agent execution.
|
|
313
|
+
*
|
|
314
|
+
* Layout strategy (gsd stays in the original surface):
|
|
315
|
+
* 1 agent: [gsd | A]
|
|
316
|
+
* 2 agents: [gsd | A]
|
|
317
|
+
* [ | B]
|
|
318
|
+
* 3 agents: [gsd | A]
|
|
319
|
+
* [ C | B]
|
|
320
|
+
* 4 agents: [gsd | A]
|
|
321
|
+
* [ C | B] (D splits from B downward)
|
|
322
|
+
* [ | D]
|
|
323
|
+
*
|
|
324
|
+
* Returns surface IDs in order, or empty array on failure.
|
|
325
|
+
*/
|
|
326
|
+
async createGridLayout(count: number): Promise<string[]> {
|
|
327
|
+
if (!this.config.splits || count <= 0) return [];
|
|
328
|
+
const surfaces: string[] = [];
|
|
329
|
+
|
|
330
|
+
// First split: create right column from the gsd surface
|
|
331
|
+
const rightCol = await this.createSplitFrom(this.config.surfaceId, "right");
|
|
332
|
+
if (!rightCol) return [];
|
|
333
|
+
surfaces.push(rightCol);
|
|
334
|
+
if (count === 1) return surfaces;
|
|
335
|
+
|
|
336
|
+
// Second split: split right column down → bottom-right
|
|
337
|
+
const bottomRight = await this.createSplitFrom(rightCol, "down");
|
|
338
|
+
if (!bottomRight) return surfaces;
|
|
339
|
+
surfaces.push(bottomRight);
|
|
340
|
+
if (count === 2) return surfaces;
|
|
341
|
+
|
|
342
|
+
// Third split: split gsd surface down → bottom-left
|
|
343
|
+
const bottomLeft = await this.createSplitFrom(this.config.surfaceId, "down");
|
|
344
|
+
if (!bottomLeft) return surfaces;
|
|
345
|
+
surfaces.push(bottomLeft);
|
|
346
|
+
if (count === 3) return surfaces;
|
|
347
|
+
|
|
348
|
+
// Fourth+: split subsequent surfaces down from the last created
|
|
349
|
+
let lastSurface = bottomRight;
|
|
350
|
+
for (let i = 3; i < count; i++) {
|
|
351
|
+
const next = await this.createSplitFrom(lastSurface, "down");
|
|
352
|
+
if (!next) break;
|
|
353
|
+
surfaces.push(next);
|
|
354
|
+
lastSurface = next;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return surfaces;
|
|
358
|
+
}
|
|
359
|
+
|
|
304
360
|
async sendSurface(surfaceId: string, text: string): Promise<boolean> {
|
|
305
361
|
const payload = text.endsWith("\n") ? text : `${text}\n`;
|
|
306
362
|
const stdout = await this.runAsync(["send-surface", "--surface", surfaceId, payload]);
|
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
buildRunUatPrompt,
|
|
39
39
|
buildReassessRoadmapPrompt,
|
|
40
40
|
buildRewriteDocsPrompt,
|
|
41
|
+
buildReactiveExecutePrompt,
|
|
41
42
|
checkNeedsReassessment,
|
|
42
43
|
checkNeedsRunUat,
|
|
43
44
|
} from "./auto-prompts.js";
|
|
@@ -309,6 +310,98 @@ const DISPATCH_RULES: DispatchRule[] = [
|
|
|
309
310
|
};
|
|
310
311
|
},
|
|
311
312
|
},
|
|
313
|
+
{
|
|
314
|
+
name: "executing → reactive-execute (parallel dispatch)",
|
|
315
|
+
match: async ({ state, mid, midTitle, basePath, prefs }) => {
|
|
316
|
+
if (state.phase !== "executing" || !state.activeTask) return null;
|
|
317
|
+
if (!state.activeSlice) return null; // fall through
|
|
318
|
+
|
|
319
|
+
// Only activate when reactive_execution is explicitly enabled
|
|
320
|
+
const reactiveConfig = prefs?.reactive_execution;
|
|
321
|
+
if (!reactiveConfig?.enabled) return null;
|
|
322
|
+
|
|
323
|
+
const sid = state.activeSlice.id;
|
|
324
|
+
const sTitle = state.activeSlice.title;
|
|
325
|
+
const maxParallel = reactiveConfig.max_parallel ?? 2;
|
|
326
|
+
|
|
327
|
+
// Dry-run mode: max_parallel=1 means graph is derived and logged but
|
|
328
|
+
// execution remains sequential
|
|
329
|
+
if (maxParallel <= 1) return null;
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
const {
|
|
333
|
+
loadSliceTaskIO,
|
|
334
|
+
deriveTaskGraph,
|
|
335
|
+
isGraphAmbiguous,
|
|
336
|
+
getReadyTasks,
|
|
337
|
+
chooseNonConflictingSubset,
|
|
338
|
+
graphMetrics,
|
|
339
|
+
} = await import("./reactive-graph.js");
|
|
340
|
+
|
|
341
|
+
const taskIO = await loadSliceTaskIO(basePath, mid, sid);
|
|
342
|
+
if (taskIO.length < 2) return null; // single task, no point
|
|
343
|
+
|
|
344
|
+
const graph = deriveTaskGraph(taskIO);
|
|
345
|
+
|
|
346
|
+
// Ambiguous graph → fall through to sequential
|
|
347
|
+
if (isGraphAmbiguous(graph)) return null;
|
|
348
|
+
|
|
349
|
+
const completed = new Set(graph.filter((n) => n.done).map((n) => n.id));
|
|
350
|
+
const readyIds = getReadyTasks(graph, completed, new Set());
|
|
351
|
+
|
|
352
|
+
// Only activate reactive dispatch when >1 task is ready
|
|
353
|
+
if (readyIds.length <= 1) return null;
|
|
354
|
+
|
|
355
|
+
const selected = chooseNonConflictingSubset(
|
|
356
|
+
readyIds,
|
|
357
|
+
graph,
|
|
358
|
+
maxParallel,
|
|
359
|
+
new Set(),
|
|
360
|
+
);
|
|
361
|
+
if (selected.length <= 1) return null;
|
|
362
|
+
|
|
363
|
+
// Log graph metrics for observability
|
|
364
|
+
const metrics = graphMetrics(graph);
|
|
365
|
+
process.stderr.write(
|
|
366
|
+
`gsd-reactive: ${mid}/${sid} graph — tasks:${metrics.taskCount} edges:${metrics.edgeCount} ` +
|
|
367
|
+
`ready:${metrics.readySetSize} dispatching:${selected.length} ambiguous:${metrics.ambiguous}\n`,
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
// Persist dispatched batch so verification and recovery can check
|
|
371
|
+
// exactly which tasks were sent.
|
|
372
|
+
const { saveReactiveState } = await import("./reactive-graph.js");
|
|
373
|
+
saveReactiveState(basePath, mid, sid, {
|
|
374
|
+
sliceId: sid,
|
|
375
|
+
completed: [...completed],
|
|
376
|
+
dispatched: selected,
|
|
377
|
+
graphSnapshot: metrics,
|
|
378
|
+
updatedAt: new Date().toISOString(),
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// Encode selected task IDs in unitId for artifact verification.
|
|
382
|
+
// Format: M001/S01/reactive+T02,T03
|
|
383
|
+
const batchSuffix = selected.join(",");
|
|
384
|
+
|
|
385
|
+
return {
|
|
386
|
+
action: "dispatch",
|
|
387
|
+
unitType: "reactive-execute",
|
|
388
|
+
unitId: `${mid}/${sid}/reactive+${batchSuffix}`,
|
|
389
|
+
prompt: await buildReactiveExecutePrompt(
|
|
390
|
+
mid,
|
|
391
|
+
midTitle,
|
|
392
|
+
sid,
|
|
393
|
+
sTitle,
|
|
394
|
+
selected,
|
|
395
|
+
basePath,
|
|
396
|
+
),
|
|
397
|
+
};
|
|
398
|
+
} catch (err) {
|
|
399
|
+
// Non-fatal — fall through to sequential execution
|
|
400
|
+
process.stderr.write(`gsd-reactive: graph derivation failed: ${(err as Error).message}\n`);
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
},
|
|
312
405
|
{
|
|
313
406
|
name: "executing → execute-task (recover missing task plan → plan-slice)",
|
|
314
407
|
match: async ({ state, mid, midTitle, basePath }) => {
|
|
@@ -787,7 +787,7 @@ export async function autoLoop(
|
|
|
787
787
|
(m: { status: string }) =>
|
|
788
788
|
m.status !== "complete" && m.status !== "parked",
|
|
789
789
|
);
|
|
790
|
-
if (incomplete.length === 0) {
|
|
790
|
+
if (incomplete.length === 0 && state.registry.length > 0) {
|
|
791
791
|
// All milestones complete — merge milestone branch before stopping
|
|
792
792
|
if (s.currentMilestoneId) {
|
|
793
793
|
deps.resolver.mergeAndExit(s.currentMilestoneId, ctx.ui);
|
|
@@ -804,6 +804,18 @@ export async function autoLoop(
|
|
|
804
804
|
"success",
|
|
805
805
|
);
|
|
806
806
|
await deps.stopAuto(ctx, pi, "All milestones complete");
|
|
807
|
+
} else if (incomplete.length === 0 && state.registry.length === 0) {
|
|
808
|
+
// Empty registry — no milestones visible, likely a path resolution bug
|
|
809
|
+
const diag = `basePath=${s.basePath}, phase=${state.phase}`;
|
|
810
|
+
ctx.ui.notify(
|
|
811
|
+
`No milestones visible in current scope. Possible path resolution issue.\n Diagnostic: ${diag}`,
|
|
812
|
+
"error",
|
|
813
|
+
);
|
|
814
|
+
await deps.stopAuto(
|
|
815
|
+
ctx,
|
|
816
|
+
pi,
|
|
817
|
+
`No milestones found — check basePath resolution`,
|
|
818
|
+
);
|
|
807
819
|
} else if (state.phase === "blocked") {
|
|
808
820
|
const blockerMsg = `Blocked: ${state.blockers.join(", ")}`;
|
|
809
821
|
await deps.stopAuto(ctx, pi, blockerMsg);
|
|
@@ -217,6 +217,20 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
|
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
// Reactive state cleanup on slice completion
|
|
221
|
+
if (s.currentUnit.type === "complete-slice") {
|
|
222
|
+
try {
|
|
223
|
+
const parts = s.currentUnit.id.split("/");
|
|
224
|
+
const [mid, sid] = parts;
|
|
225
|
+
if (mid && sid) {
|
|
226
|
+
const { clearReactiveState } = await import("./reactive-graph.js");
|
|
227
|
+
clearReactiveState(s.basePath, mid, sid);
|
|
228
|
+
}
|
|
229
|
+
} catch {
|
|
230
|
+
// Non-fatal
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
220
234
|
// Post-triage: execute actionable resolutions
|
|
221
235
|
if (s.currentUnit.type === "triage-captures") {
|
|
222
236
|
try {
|
|
@@ -485,6 +485,41 @@ export async function getPriorTaskSummaryPaths(
|
|
|
485
485
|
.map(f => `${sRel}/tasks/${f}`);
|
|
486
486
|
}
|
|
487
487
|
|
|
488
|
+
/**
|
|
489
|
+
* Get carry-forward summary paths scoped to a task's derived dependencies.
|
|
490
|
+
*
|
|
491
|
+
* Instead of all prior tasks (order-based), returns only summaries for task
|
|
492
|
+
* IDs in `dependsOn`. Used by reactive-execute to give each subagent only
|
|
493
|
+
* the context it actually needs — not sibling tasks from a parallel batch.
|
|
494
|
+
*
|
|
495
|
+
* Falls back to order-based when dependsOn is empty (root tasks still get
|
|
496
|
+
* any available prior summaries for continuity).
|
|
497
|
+
*/
|
|
498
|
+
export async function getDependencyTaskSummaryPaths(
|
|
499
|
+
mid: string, sid: string, currentTid: string,
|
|
500
|
+
dependsOn: string[], base: string,
|
|
501
|
+
): Promise<string[]> {
|
|
502
|
+
// If no dependencies, fall back to order-based for root tasks
|
|
503
|
+
if (dependsOn.length === 0) {
|
|
504
|
+
return getPriorTaskSummaryPaths(mid, sid, currentTid, base);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const tDir = resolveTasksDir(base, mid, sid);
|
|
508
|
+
if (!tDir) return [];
|
|
509
|
+
|
|
510
|
+
const summaryFiles = resolveTaskFiles(tDir, "SUMMARY");
|
|
511
|
+
const sRel = relSlicePath(base, mid, sid);
|
|
512
|
+
const depSet = new Set(dependsOn.map((d) => d.toUpperCase()));
|
|
513
|
+
|
|
514
|
+
return summaryFiles
|
|
515
|
+
.filter((f) => {
|
|
516
|
+
// Extract task ID from filename: "T02-SUMMARY.md" → "T02"
|
|
517
|
+
const tid = f.replace(/-SUMMARY\.md$/i, "").toUpperCase();
|
|
518
|
+
return depSet.has(tid);
|
|
519
|
+
})
|
|
520
|
+
.map((f) => `${sRel}/tasks/${f}`);
|
|
521
|
+
}
|
|
522
|
+
|
|
488
523
|
// ─── Adaptive Replanning Checks ────────────────────────────────────────────
|
|
489
524
|
|
|
490
525
|
/**
|
|
@@ -772,13 +807,24 @@ export async function buildPlanSlicePrompt(
|
|
|
772
807
|
});
|
|
773
808
|
}
|
|
774
809
|
|
|
810
|
+
/** Options for customizing execute-task prompt construction. */
|
|
811
|
+
export interface ExecuteTaskPromptOptions {
|
|
812
|
+
level?: InlineLevel;
|
|
813
|
+
/** Override carry-forward paths (dependency-based instead of order-based). */
|
|
814
|
+
carryForwardPaths?: string[];
|
|
815
|
+
}
|
|
816
|
+
|
|
775
817
|
export async function buildExecuteTaskPrompt(
|
|
776
818
|
mid: string, sid: string, sTitle: string,
|
|
777
|
-
tid: string, tTitle: string, base: string,
|
|
819
|
+
tid: string, tTitle: string, base: string,
|
|
820
|
+
level?: InlineLevel | ExecuteTaskPromptOptions,
|
|
778
821
|
): Promise<string> {
|
|
779
|
-
const
|
|
822
|
+
const opts: ExecuteTaskPromptOptions = typeof level === "object" && level !== null && !Array.isArray(level)
|
|
823
|
+
? level
|
|
824
|
+
: { level: level as InlineLevel | undefined };
|
|
825
|
+
const inlineLevel = opts.level ?? resolveInlineLevel();
|
|
780
826
|
|
|
781
|
-
const priorSummaries = await getPriorTaskSummaryPaths(mid, sid, tid, base);
|
|
827
|
+
const priorSummaries = opts.carryForwardPaths ?? await getPriorTaskSummaryPaths(mid, sid, tid, base);
|
|
782
828
|
const priorLines = priorSummaries.length > 0
|
|
783
829
|
? priorSummaries.map(p => `- \`${p}\``).join("\n")
|
|
784
830
|
: "- (no prior tasks)";
|
|
@@ -1234,6 +1280,82 @@ export async function buildReassessRoadmapPrompt(
|
|
|
1234
1280
|
});
|
|
1235
1281
|
}
|
|
1236
1282
|
|
|
1283
|
+
// ─── Reactive Execute Prompt ──────────────────────────────────────────────
|
|
1284
|
+
|
|
1285
|
+
export async function buildReactiveExecutePrompt(
|
|
1286
|
+
mid: string, midTitle: string, sid: string, sTitle: string,
|
|
1287
|
+
readyTaskIds: string[], base: string,
|
|
1288
|
+
): Promise<string> {
|
|
1289
|
+
const { loadSliceTaskIO, deriveTaskGraph, graphMetrics } = await import("./reactive-graph.js");
|
|
1290
|
+
|
|
1291
|
+
// Build graph for context
|
|
1292
|
+
const taskIO = await loadSliceTaskIO(base, mid, sid);
|
|
1293
|
+
const graph = deriveTaskGraph(taskIO);
|
|
1294
|
+
const metrics = graphMetrics(graph);
|
|
1295
|
+
|
|
1296
|
+
// Build graph context section
|
|
1297
|
+
const graphLines: string[] = [];
|
|
1298
|
+
for (const node of graph) {
|
|
1299
|
+
const status = node.done ? "✅ done" : readyTaskIds.includes(node.id) ? "🟢 ready" : "⏳ waiting";
|
|
1300
|
+
const deps = node.dependsOn.length > 0 ? ` (depends on: ${node.dependsOn.join(", ")})` : "";
|
|
1301
|
+
graphLines.push(`- **${node.id}: ${node.title}** — ${status}${deps}`);
|
|
1302
|
+
if (node.outputFiles.length > 0) {
|
|
1303
|
+
graphLines.push(` - Outputs: ${node.outputFiles.map(f => `\`${f}\``).join(", ")}`);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
const graphContext = [
|
|
1307
|
+
`Tasks: ${metrics.taskCount}, Edges: ${metrics.edgeCount}, Ready: ${metrics.readySetSize}`,
|
|
1308
|
+
"",
|
|
1309
|
+
...graphLines,
|
|
1310
|
+
].join("\n");
|
|
1311
|
+
|
|
1312
|
+
// Build individual subagent prompts for each ready task
|
|
1313
|
+
const subagentSections: string[] = [];
|
|
1314
|
+
const readyTaskListLines: string[] = [];
|
|
1315
|
+
|
|
1316
|
+
for (const tid of readyTaskIds) {
|
|
1317
|
+
const node = graph.find((n) => n.id === tid);
|
|
1318
|
+
const tTitle = node?.title ?? tid;
|
|
1319
|
+
readyTaskListLines.push(`- **${tid}: ${tTitle}**`);
|
|
1320
|
+
|
|
1321
|
+
// Build dependency-scoped carry-forward paths for this task
|
|
1322
|
+
const depPaths = await getDependencyTaskSummaryPaths(
|
|
1323
|
+
mid, sid, tid, node?.dependsOn ?? [], base,
|
|
1324
|
+
);
|
|
1325
|
+
|
|
1326
|
+
// Build a full execute-task prompt with dependency-based carry-forward
|
|
1327
|
+
const taskPrompt = await buildExecuteTaskPrompt(
|
|
1328
|
+
mid, sid, sTitle, tid, tTitle, base,
|
|
1329
|
+
{ carryForwardPaths: depPaths },
|
|
1330
|
+
);
|
|
1331
|
+
|
|
1332
|
+
subagentSections.push([
|
|
1333
|
+
`### ${tid}: ${tTitle}`,
|
|
1334
|
+
"",
|
|
1335
|
+
"Use this as the prompt for a `subagent` call:",
|
|
1336
|
+
"",
|
|
1337
|
+
"```",
|
|
1338
|
+
taskPrompt,
|
|
1339
|
+
"```",
|
|
1340
|
+
].join("\n"));
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
const inlinedTemplates = inlineTemplate("task-summary", "Task Summary");
|
|
1344
|
+
|
|
1345
|
+
return loadPrompt("reactive-execute", {
|
|
1346
|
+
workingDirectory: base,
|
|
1347
|
+
milestoneId: mid,
|
|
1348
|
+
milestoneTitle: midTitle,
|
|
1349
|
+
sliceId: sid,
|
|
1350
|
+
sliceTitle: sTitle,
|
|
1351
|
+
graphContext,
|
|
1352
|
+
readyTaskCount: String(readyTaskIds.length),
|
|
1353
|
+
readyTaskList: readyTaskListLines.join("\n"),
|
|
1354
|
+
subagentPrompts: subagentSections.join("\n\n---\n\n"),
|
|
1355
|
+
inlinedTemplates,
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1237
1359
|
export async function buildRewriteDocsPrompt(
|
|
1238
1360
|
mid: string, midTitle: string,
|
|
1239
1361
|
activeSlice: { id: string; title: string } | null,
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
resolveSlicePath,
|
|
27
27
|
resolveSliceFile,
|
|
28
28
|
resolveTasksDir,
|
|
29
|
+
resolveTaskFiles,
|
|
29
30
|
relMilestoneFile,
|
|
30
31
|
relSliceFile,
|
|
31
32
|
relSlicePath,
|
|
@@ -110,6 +111,9 @@ export function resolveExpectedArtifactPath(
|
|
|
110
111
|
}
|
|
111
112
|
case "rewrite-docs":
|
|
112
113
|
return null;
|
|
114
|
+
case "reactive-execute":
|
|
115
|
+
// Reactive execute produces multiple task summaries — verified separately
|
|
116
|
+
return null;
|
|
113
117
|
default:
|
|
114
118
|
return null;
|
|
115
119
|
}
|
|
@@ -148,6 +152,44 @@ export function verifyExpectedArtifact(
|
|
|
148
152
|
return !content.includes("**Scope:** active");
|
|
149
153
|
}
|
|
150
154
|
|
|
155
|
+
// Reactive-execute: verify that each dispatched task's summary exists.
|
|
156
|
+
// The unitId encodes the batch: "{mid}/{sid}/reactive+T02,T03"
|
|
157
|
+
if (unitType === "reactive-execute") {
|
|
158
|
+
const parts = unitId.split("/");
|
|
159
|
+
const mid = parts[0];
|
|
160
|
+
const sidAndBatch = parts[1];
|
|
161
|
+
const batchPart = parts[2]; // "reactive+T02,T03"
|
|
162
|
+
if (!mid || !sidAndBatch || !batchPart) return false;
|
|
163
|
+
|
|
164
|
+
const sid = sidAndBatch;
|
|
165
|
+
const plusIdx = batchPart.indexOf("+");
|
|
166
|
+
if (plusIdx === -1) {
|
|
167
|
+
// Legacy format "reactive" without batch IDs — fall back to "any summary"
|
|
168
|
+
const tDir = resolveTasksDir(base, mid, sid);
|
|
169
|
+
if (!tDir) return false;
|
|
170
|
+
const summaryFiles = resolveTaskFiles(tDir, "SUMMARY");
|
|
171
|
+
return summaryFiles.length > 0;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const batchIds = batchPart.slice(plusIdx + 1).split(",").filter(Boolean);
|
|
175
|
+
if (batchIds.length === 0) return false;
|
|
176
|
+
|
|
177
|
+
const tDir = resolveTasksDir(base, mid, sid);
|
|
178
|
+
if (!tDir) return false;
|
|
179
|
+
|
|
180
|
+
const existingSummaries = new Set(
|
|
181
|
+
resolveTaskFiles(tDir, "SUMMARY").map((f) =>
|
|
182
|
+
f.replace(/-SUMMARY\.md$/i, "").toUpperCase(),
|
|
183
|
+
),
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
// Every dispatched task must have a summary file
|
|
187
|
+
for (const tid of batchIds) {
|
|
188
|
+
if (!existingSummaries.has(tid.toUpperCase())) return false;
|
|
189
|
+
}
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
|
|
151
193
|
const absPath = resolveExpectedArtifactPath(unitType, unitId, base);
|
|
152
194
|
// For unit types with no verifiable artifact (null path), the parent directory
|
|
153
195
|
// is missing on disk — treat as stale completion state so the key gets evicted (#313).
|
|
@@ -429,10 +429,16 @@ export async function bootstrapAutoSession(
|
|
|
429
429
|
s.originalBasePath = base;
|
|
430
430
|
|
|
431
431
|
const isUnderGsdWorktrees = (p: string): boolean => {
|
|
432
|
+
// Direct layout: /.gsd/worktrees/
|
|
432
433
|
const marker = `${pathSep}.gsd${pathSep}worktrees${pathSep}`;
|
|
433
434
|
if (p.includes(marker)) return true;
|
|
434
435
|
const worktreesSuffix = `${pathSep}.gsd${pathSep}worktrees`;
|
|
435
|
-
|
|
436
|
+
if (p.endsWith(worktreesSuffix)) return true;
|
|
437
|
+
// Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/
|
|
438
|
+
const symlinkRe = new RegExp(
|
|
439
|
+
`\\${pathSep}\\.gsd\\${pathSep}projects\\${pathSep}[a-f0-9]+\\${pathSep}worktrees(?:\\${pathSep}|$)`,
|
|
440
|
+
);
|
|
441
|
+
return symlinkRe.test(p);
|
|
436
442
|
};
|
|
437
443
|
|
|
438
444
|
if (
|
|
@@ -153,9 +153,18 @@ export function checkResourcesStale(
|
|
|
153
153
|
* Returns the corrected base path.
|
|
154
154
|
*/
|
|
155
155
|
export function escapeStaleWorktree(base: string): string {
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
|
|
156
|
+
// Direct layout: /.gsd/worktrees/
|
|
157
|
+
const directMarker = `${pathSep}.gsd${pathSep}worktrees${pathSep}`;
|
|
158
|
+
let idx = base.indexOf(directMarker);
|
|
159
|
+
if (idx === -1) {
|
|
160
|
+
// Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/
|
|
161
|
+
const symlinkRe = new RegExp(
|
|
162
|
+
`\\${pathSep}\\.gsd\\${pathSep}projects\\${pathSep}[a-f0-9]+\\${pathSep}worktrees\\${pathSep}`,
|
|
163
|
+
);
|
|
164
|
+
const match = base.match(symlinkRe);
|
|
165
|
+
if (!match || match.index === undefined) return base;
|
|
166
|
+
idx = match.index;
|
|
167
|
+
}
|
|
159
168
|
|
|
160
169
|
// base is inside .gsd/worktrees/<something> — extract the project root
|
|
161
170
|
const projectRoot = base.slice(0, idx);
|
|
@@ -59,8 +59,17 @@ const VALID_CLASSIFICATIONS: readonly string[] = [
|
|
|
59
59
|
*/
|
|
60
60
|
export function resolveCapturesPath(basePath: string): string {
|
|
61
61
|
const resolved = resolve(basePath);
|
|
62
|
+
// Direct layout: /.gsd/worktrees/
|
|
62
63
|
const worktreeMarker = `${sep}.gsd${sep}worktrees${sep}`;
|
|
63
|
-
|
|
64
|
+
let idx = resolved.indexOf(worktreeMarker);
|
|
65
|
+
if (idx === -1) {
|
|
66
|
+
// Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/
|
|
67
|
+
const symlinkRe = new RegExp(
|
|
68
|
+
`\\${sep}\\.gsd\\${sep}projects\\${sep}[a-f0-9]+\\${sep}worktrees\\${sep}`,
|
|
69
|
+
);
|
|
70
|
+
const match = resolved.match(symlinkRe);
|
|
71
|
+
if (match && match.index !== undefined) idx = match.index;
|
|
72
|
+
}
|
|
64
73
|
if (idx !== -1) {
|
|
65
74
|
// basePath is inside a worktree — resolve to project root
|
|
66
75
|
const projectRoot = resolved.slice(0, idx);
|
|
@@ -15,6 +15,7 @@ import { appendOverride, appendKnowledge } from "./files.js";
|
|
|
15
15
|
import {
|
|
16
16
|
formatDoctorIssuesForPrompt,
|
|
17
17
|
formatDoctorReport,
|
|
18
|
+
formatDoctorReportJson,
|
|
18
19
|
runGSDDoctor,
|
|
19
20
|
selectDoctorScope,
|
|
20
21
|
filterDoctorIssues,
|
|
@@ -43,16 +44,30 @@ export function dispatchDoctorHeal(pi: ExtensionAPI, scope: string | undefined,
|
|
|
43
44
|
|
|
44
45
|
export async function handleDoctor(args: string, ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<void> {
|
|
45
46
|
const trimmed = args.trim();
|
|
46
|
-
|
|
47
|
+
// Extract flags before positional parsing
|
|
48
|
+
const jsonMode = trimmed.includes("--json");
|
|
49
|
+
const dryRun = trimmed.includes("--dry-run");
|
|
50
|
+
const includeBuild = trimmed.includes("--build");
|
|
51
|
+
const includeTests = trimmed.includes("--test");
|
|
52
|
+
const stripped = trimmed.replace(/--json|--dry-run|--build|--test/g, "").trim();
|
|
53
|
+
const parts = stripped ? stripped.split(/\s+/) : [];
|
|
47
54
|
const mode = parts[0] === "fix" || parts[0] === "heal" || parts[0] === "audit" ? parts[0] : "doctor";
|
|
48
55
|
const requestedScope = mode === "doctor" ? parts[0] : parts[1];
|
|
49
56
|
const scope = await selectDoctorScope(projectRoot(), requestedScope);
|
|
50
57
|
const effectiveScope = mode === "audit" ? requestedScope : scope;
|
|
51
58
|
const report = await runGSDDoctor(projectRoot(), {
|
|
52
|
-
fix: mode === "fix" || mode === "heal",
|
|
59
|
+
fix: mode === "fix" || mode === "heal" || dryRun,
|
|
60
|
+
dryRun,
|
|
53
61
|
scope: effectiveScope,
|
|
62
|
+
includeBuild,
|
|
63
|
+
includeTests,
|
|
54
64
|
});
|
|
55
65
|
|
|
66
|
+
if (jsonMode) {
|
|
67
|
+
ctx.ui.notify(formatDoctorReportJson(report), "info");
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
56
71
|
const reportText = formatDoctorReport(report, {
|
|
57
72
|
scope: effectiveScope,
|
|
58
73
|
includeWarnings: mode === "audit",
|
|
@@ -159,7 +159,7 @@ async function guardRemoteSession(
|
|
|
159
159
|
|
|
160
160
|
export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
161
161
|
pi.registerCommand("gsd", {
|
|
162
|
-
description: "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|visualize|queue|quick|capture|triage|dispatch|history|undo|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|update",
|
|
162
|
+
description: "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|queue|quick|capture|triage|dispatch|history|undo|rate|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|park|unpark|init|setup|inspect|extensions|update",
|
|
163
163
|
getArgumentCompletions: (prefix: string) => {
|
|
164
164
|
const subcommands = [
|
|
165
165
|
{ cmd: "help", desc: "Categorized command reference with descriptions" },
|
|
@@ -210,7 +210,11 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
210
210
|
{ cmd: "templates", desc: "List available workflow templates" },
|
|
211
211
|
{ cmd: "extensions", desc: "Manage extensions (list, enable, disable, info)" },
|
|
212
212
|
];
|
|
213
|
+
const hasTrailingSpace = prefix.endsWith(" ");
|
|
213
214
|
const parts = prefix.trim().split(/\s+/);
|
|
215
|
+
if (hasTrailingSpace && parts.length >= 1) {
|
|
216
|
+
parts.push("");
|
|
217
|
+
}
|
|
214
218
|
|
|
215
219
|
if (parts.length <= 1) {
|
|
216
220
|
return subcommands
|
|
@@ -509,6 +513,10 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
509
513
|
{ cmd: "fix", desc: "Auto-fix detected issues" },
|
|
510
514
|
{ cmd: "heal", desc: "AI-driven deep healing" },
|
|
511
515
|
{ cmd: "audit", desc: "Run health audit without fixing" },
|
|
516
|
+
{ cmd: "--dry-run", desc: "Show what --fix would change without applying" },
|
|
517
|
+
{ cmd: "--json", desc: "Output report as JSON (CI/tooling friendly)" },
|
|
518
|
+
{ cmd: "--build", desc: "Include slow build health check (npm run build)" },
|
|
519
|
+
{ cmd: "--test", desc: "Include slow test health check (npm test)" },
|
|
512
520
|
];
|
|
513
521
|
|
|
514
522
|
if (parts.length <= 2) {
|
|
@@ -536,6 +544,18 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
536
544
|
.map((p) => ({ value: `dispatch ${p.cmd}`, label: p.cmd, description: p.desc }));
|
|
537
545
|
}
|
|
538
546
|
|
|
547
|
+
if (parts[0] === "rate" && parts.length <= 2) {
|
|
548
|
+
const tierPrefix = parts[1] ?? "";
|
|
549
|
+
const tiers = [
|
|
550
|
+
{ cmd: "over", desc: "Model was overqualified for this task" },
|
|
551
|
+
{ cmd: "ok", desc: "Model was appropriate for this task" },
|
|
552
|
+
{ cmd: "under", desc: "Model was underqualified for this task" },
|
|
553
|
+
];
|
|
554
|
+
return tiers
|
|
555
|
+
.filter((t) => t.cmd.startsWith(tierPrefix))
|
|
556
|
+
.map((t) => ({ value: `rate ${t.cmd}`, label: t.cmd, description: t.desc }));
|
|
557
|
+
}
|
|
558
|
+
|
|
539
559
|
return [];
|
|
540
560
|
},
|
|
541
561
|
|