gsd-pi 2.74.0-dev.703eabc → 2.74.0-dev.ffbcc03

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/resources/extensions/gsd/auto-recovery.js +24 -10
  2. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +16 -5
  3. package/dist/resources/extensions/gsd/cache.js +16 -5
  4. package/dist/resources/extensions/gsd/guided-flow.js +1 -1
  5. package/dist/resources/extensions/gsd/safety/evidence-collector.js +15 -30
  6. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  7. package/dist/web/standalone/.next/BUILD_ID +1 -1
  8. package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
  9. package/dist/web/standalone/.next/build-manifest.json +2 -2
  10. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  11. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  12. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  13. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  14. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  15. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  16. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  17. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  18. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  19. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  20. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  21. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  22. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/index.html +1 -1
  28. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
  35. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  36. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  37. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  38. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  39. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  40. package/package.json +1 -1
  41. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  42. package/packages/mcp-server/dist/workflow-tools.js +88 -6
  43. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  44. package/packages/mcp-server/src/workflow-tools.ts +95 -10
  45. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  46. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +8 -0
  47. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  48. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -0
  49. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  50. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -0
  51. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  52. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +17 -0
  53. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  54. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +17 -0
  55. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +19 -0
  56. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  57. package/src/resources/extensions/gsd/auto-recovery.ts +29 -9
  58. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +16 -5
  59. package/src/resources/extensions/gsd/cache.ts +16 -5
  60. package/src/resources/extensions/gsd/guided-flow.ts +1 -1
  61. package/src/resources/extensions/gsd/safety/evidence-collector.ts +15 -31
  62. package/src/resources/extensions/gsd/tests/artifacts-table-preserved-on-cache-invalidate.test.ts +177 -0
  63. package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +272 -0
  64. /package/dist/web/standalone/.next/static/{3U-oZ5FT59BM7sm2GInic → kn6xzWKYnogsxp2b6RpDD}/_buildManifest.js +0 -0
  65. /package/dist/web/standalone/.next/static/{3U-oZ5FT59BM7sm2GInic → kn6xzWKYnogsxp2b6RpDD}/_ssgManifest.js +0 -0
@@ -215,20 +215,28 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
215
215
  const absPath = resolveExpectedArtifactPath(unitType, unitId, base);
216
216
  // For unit types with no verifiable artifact (null path), the parent directory
217
217
  // is missing on disk — treat as stale completion state so the key gets evicted (#313).
218
- if (!absPath)
218
+ if (!absPath) {
219
+ logWarning("recovery", `verify-fail ${unitType} ${unitId}: resolveExpectedArtifactPath returned null (parent dir missing)`);
219
220
  return false;
220
- if (!existsSync(absPath))
221
+ }
222
+ if (!existsSync(absPath)) {
223
+ logWarning("recovery", `verify-fail ${unitType} ${unitId}: existsSync false for ${absPath}`);
221
224
  return false;
225
+ }
222
226
  if (unitType === "validate-milestone") {
223
227
  const validationContent = readFileSync(absPath, "utf-8");
224
- if (!isValidationTerminal(validationContent))
228
+ if (!isValidationTerminal(validationContent)) {
229
+ logWarning("recovery", `verify-fail ${unitType} ${unitId}: validation not terminal (len=${validationContent.length}) at ${absPath}`);
225
230
  return false;
231
+ }
226
232
  }
227
233
  if (unitType === "plan-milestone") {
228
234
  try {
229
235
  const roadmap = parseLegacyRoadmap(readFileSync(absPath, "utf-8"));
230
- if (roadmap.slices.length === 0)
236
+ if (roadmap.slices.length === 0) {
237
+ logWarning("recovery", `verify-fail ${unitType} ${unitId}: roadmap has zero slices at ${absPath}`);
231
238
  return false;
239
+ }
232
240
  }
233
241
  catch (err) {
234
242
  logWarning("recovery", `plan-milestone roadmap verification failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -245,8 +253,10 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
245
253
  // Accept checkbox-style (- [x] **T01: ...) or heading-style (### T01 -- / ### T01: / ### T01 —)
246
254
  const hasCheckboxTask = /^- \[[xX ]\] \*\*T\d+:/m.test(planContent);
247
255
  const hasHeadingTask = /^#{2,4}\s+T\d+\s*(?:--|—|:)/m.test(planContent);
248
- if (!hasCheckboxTask && !hasHeadingTask)
256
+ if (!hasCheckboxTask && !hasHeadingTask) {
257
+ logWarning("recovery", `verify-fail ${unitType} ${unitId}: plan has no task checkbox/heading (len=${planContent.length}) at ${absPath}`);
249
258
  return false;
259
+ }
250
260
  }
251
261
  // execute-task: DB status is authoritative. Fall back to checked-checkbox
252
262
  // detection when the DB is unavailable (unmigrated projects).
@@ -306,11 +316,15 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
306
316
  }
307
317
  if (taskIds && taskIds.length > 0) {
308
318
  const tasksDir = resolveTasksDir(base, mid, sid);
309
- if (tasksDir) {
310
- for (const tid of taskIds) {
311
- const taskPlanFile = join(tasksDir, `${tid}-PLAN.md`);
312
- if (!existsSync(taskPlanFile))
313
- return false;
319
+ if (!tasksDir) {
320
+ logWarning("recovery", `verify-fail ${unitType} ${unitId}: resolveTasksDir returned null for ${mid}/${sid}`);
321
+ return false;
322
+ }
323
+ for (const tid of taskIds) {
324
+ const taskPlanFile = join(tasksDir, `${tid}-PLAN.md`);
325
+ if (!existsSync(taskPlanFile)) {
326
+ logWarning("recovery", `verify-fail ${unitType} ${unitId}: task plan missing ${taskPlanFile}`);
327
+ return false;
314
328
  }
315
329
  }
316
330
  }
@@ -41,8 +41,12 @@ export function registerHooks(pi, ecosystemHandlers) {
41
41
  resetToolCallLoopGuard();
42
42
  resetAskUserQuestionsCache();
43
43
  await syncServiceTierStatus(ctx);
44
- const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
45
- prepareWorkflowMcpForProject(ctx, process.cwd());
44
+ // Skip MCP auto-prep when running inside an auto-worktree (see session_switch below).
45
+ const { isInAutoWorktree } = await import("../auto-worktree.js");
46
+ if (!isInAutoWorktree(process.cwd())) {
47
+ const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
48
+ prepareWorkflowMcpForProject(ctx, process.cwd());
49
+ }
46
50
  // Apply show_token_cost preference (#1515)
47
51
  try {
48
52
  const { loadEffectiveGSDPreferences } = await import("../preferences.js");
@@ -82,8 +86,15 @@ export function registerHooks(pi, ecosystemHandlers) {
82
86
  resetAskUserQuestionsCache();
83
87
  clearDiscussionFlowState();
84
88
  await syncServiceTierStatus(ctx);
85
- const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
86
- prepareWorkflowMcpForProject(ctx, process.cwd());
89
+ // Skip MCP auto-prep when running inside an auto-worktree. The worktree
90
+ // already has .mcp.json from createAutoWorktree, and re-running the writer
91
+ // post-chdir rewrites the file mid-run (non-idempotent due to cwd-relative
92
+ // CLI path resolution), dirtying the tree and breaking the milestone merge.
93
+ const { isInAutoWorktree } = await import("../auto-worktree.js");
94
+ if (!isInAutoWorktree(process.cwd())) {
95
+ const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
96
+ prepareWorkflowMcpForProject(ctx, process.cwd());
97
+ }
87
98
  loadToolApiKeys();
88
99
  });
89
100
  pi.on("before_agent_start", async (event, ctx) => {
@@ -281,7 +292,7 @@ export function registerHooks(pi, ecosystemHandlers) {
281
292
  pi.on("tool_call", async (event, ctx) => {
282
293
  if (!isAutoActive())
283
294
  return;
284
- safetyRecordToolCall(event.toolName, event.input);
295
+ safetyRecordToolCall(event.toolCallId, event.toolName, event.input);
285
296
  // Destructive command classification (warn only, never block)
286
297
  if (isToolCallEventType("bash", event)) {
287
298
  const classification = classifyCommand(event.input.command);
@@ -1,6 +1,6 @@
1
1
  // GSD Extension — Unified Cache Invalidation
2
2
  //
3
- // Three module-scoped caches exist across the GSD extension:
3
+ // Three module-scoped read caches exist across the GSD extension:
4
4
  // 1. State cache (state.ts) — memoized deriveState() result
5
5
  // 2. Path cache (paths.ts) — directory listing results (readdirSync)
6
6
  // 3. Parse cache (files.ts) — parsed markdown file results
@@ -8,20 +8,31 @@
8
8
  // After any file write that changes .gsd/ contents, all three must be
9
9
  // invalidated together to prevent stale reads. This module provides a
10
10
  // single function that clears all three atomically.
11
+ //
12
+ // NOTE: The DB `artifacts` table is NOT included here. Earlier versions
13
+ // called clearArtifacts() as part of this bundle (#793), intending to
14
+ // force deriveState() to re-parse from disk when files were edited
15
+ // out-of-band. But invalidateAllCaches() fires on every post-unit pass,
16
+ // so bundling a DESTRUCTIVE `DELETE FROM artifacts` with routine cache
17
+ // invalidation meant every row written by saveArtifactToDb / writeAndStore
18
+ // was wiped within seconds — leaving the milestone completed on disk but
19
+ // the `artifacts` table empty and the agent looping on "file exists but
20
+ // DB record missing" recovery calls. If a call site genuinely needs the
21
+ // artifact table cleared after an out-of-band file mutation, it should
22
+ // invoke clearArtifacts() from gsd-db.js explicitly — do not add it back
23
+ // here.
11
24
  import { invalidateStateCache } from './state.js';
12
25
  import { clearPathCache } from './paths.js';
13
26
  import { clearParseCache } from './files.js';
14
- import { clearArtifacts } from './gsd-db.js';
15
27
  /**
16
- * Invalidate all GSD runtime caches in one call.
28
+ * Invalidate all GSD runtime read caches in one call.
17
29
  *
18
30
  * Call this after file writes, milestone transitions, merge reconciliation,
19
31
  * or any operation that changes .gsd/ contents on disk. Forgetting to clear
20
- * any single cache causes stale reads (see #431, #793).
32
+ * any single cache causes stale reads (see #431).
21
33
  */
22
34
  export function invalidateAllCaches() {
23
35
  invalidateStateCache();
24
36
  clearPathCache();
25
37
  clearParseCache();
26
- clearArtifacts();
27
38
  }
@@ -219,7 +219,7 @@ export function checkAutoStartAfterDiscuss() {
219
219
  logWarning("guided", `manifest unlink failed: ${e.message}`);
220
220
  }
221
221
  pendingAutoStartMap.delete(basePath);
222
- ctx.ui.notify(`Milestone ${milestoneId} ready.`, "info");
222
+ ctx.ui.notify(`Milestone ${milestoneId} ready.`, "success");
223
223
  startAutoDetached(ctx, pi, basePath, false, { step });
224
224
  return true;
225
225
  }
@@ -32,11 +32,11 @@ export function getFilePaths() {
32
32
  * Record a tool call at dispatch time (before execution).
33
33
  * Exit codes and output are filled in by recordToolResult after execution.
34
34
  */
35
- export function recordToolCall(toolName, input) {
35
+ export function recordToolCall(toolCallId, toolName, input) {
36
36
  if (toolName === "bash" || toolName === "Bash") {
37
37
  unitEvidence.push({
38
38
  kind: "bash",
39
- toolCallId: "",
39
+ toolCallId,
40
40
  command: String(input.command ?? ""),
41
41
  exitCode: -1,
42
42
  outputSnippet: "",
@@ -46,7 +46,7 @@ export function recordToolCall(toolName, input) {
46
46
  else if (toolName === "write" || toolName === "Write") {
47
47
  unitEvidence.push({
48
48
  kind: "write",
49
- toolCallId: "",
49
+ toolCallId,
50
50
  path: String(input.file_path ?? input.path ?? ""),
51
51
  timestamp: Date.now(),
52
52
  });
@@ -54,44 +54,29 @@ export function recordToolCall(toolName, input) {
54
54
  else if (toolName === "edit" || toolName === "Edit") {
55
55
  unitEvidence.push({
56
56
  kind: "edit",
57
- toolCallId: "",
57
+ toolCallId,
58
58
  path: String(input.file_path ?? input.path ?? ""),
59
59
  timestamp: Date.now(),
60
60
  });
61
61
  }
62
62
  }
63
63
  /**
64
- * Record a tool execution result. Matches the most recent unresolved entry
65
- * of the same kind and fills in the toolCallId, exit code, and output.
64
+ * Record a tool execution result. Matches the entry by toolCallId (assigned
65
+ * at dispatch time) and fills in exit code + output. Prior versions matched
66
+ * by `kind + empty-string` which corrupted parallel tool calls.
66
67
  */
67
68
  export function recordToolResult(toolCallId, toolName, result, isError) {
68
- const normalizedName = toolName.toLowerCase();
69
- if (normalizedName === "bash") {
70
- const entry = findLastUnresolved("bash");
71
- if (entry) {
72
- entry.toolCallId = toolCallId;
73
- const text = extractResultText(result);
74
- entry.outputSnippet = text.slice(0, 500);
75
- const exitMatch = text.match(/Command exited with code (\d+)/);
76
- entry.exitCode = exitMatch ? Number(exitMatch[1]) : (isError ? 1 : 0);
77
- }
78
- }
79
- else if (normalizedName === "write" || normalizedName === "edit") {
80
- const entry = findLastUnresolved(normalizedName);
81
- if (entry) {
82
- entry.toolCallId = toolCallId;
83
- }
69
+ const entry = unitEvidence.find(e => e.toolCallId === toolCallId);
70
+ if (!entry)
71
+ return;
72
+ if (entry.kind === "bash") {
73
+ const text = extractResultText(result);
74
+ entry.outputSnippet = text.slice(0, 500);
75
+ const exitMatch = text.match(/Command exited with code (\d+)/);
76
+ entry.exitCode = exitMatch ? Number(exitMatch[1]) : (isError ? 1 : 0);
84
77
  }
85
78
  }
86
79
  // ─── Internals ──────────────────────────────────────────────────────────────
87
- function findLastUnresolved(kind) {
88
- for (let i = unitEvidence.length - 1; i >= 0; i--) {
89
- if (unitEvidence[i].kind === kind && unitEvidence[i].toolCallId === "") {
90
- return unitEvidence[i];
91
- }
92
- }
93
- return undefined;
94
- }
95
80
  function extractResultText(result) {
96
81
  if (typeof result === "string")
97
82
  return result;