gsd-pi 2.35.0-dev.640d5c7 → 2.35.0-dev.67d0e02

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 (94) hide show
  1. package/README.md +3 -1
  2. package/dist/cli.js +7 -2
  3. package/dist/resource-loader.d.ts +1 -1
  4. package/dist/resource-loader.js +13 -1
  5. package/dist/resources/extensions/async-jobs/await-tool.js +0 -2
  6. package/dist/resources/extensions/async-jobs/job-manager.js +0 -6
  7. package/dist/resources/extensions/bg-shell/output-formatter.js +1 -19
  8. package/dist/resources/extensions/bg-shell/process-manager.js +0 -4
  9. package/dist/resources/extensions/bg-shell/types.js +0 -2
  10. package/dist/resources/extensions/context7/index.js +5 -0
  11. package/dist/resources/extensions/get-secrets-from-user.js +2 -30
  12. package/dist/resources/extensions/google-search/index.js +5 -0
  13. package/dist/resources/extensions/gsd/auto-dispatch.js +43 -1
  14. package/dist/resources/extensions/gsd/auto-loop.js +10 -1
  15. package/dist/resources/extensions/gsd/auto-recovery.js +35 -0
  16. package/dist/resources/extensions/gsd/auto-start.js +35 -2
  17. package/dist/resources/extensions/gsd/auto.js +59 -4
  18. package/dist/resources/extensions/gsd/commands-handlers.js +2 -2
  19. package/dist/resources/extensions/gsd/doctor-environment.js +26 -17
  20. package/dist/resources/extensions/gsd/files.js +9 -1
  21. package/dist/resources/extensions/gsd/gitignore.js +54 -7
  22. package/dist/resources/extensions/gsd/guided-flow.js +1 -1
  23. package/dist/resources/extensions/gsd/health-widget-core.js +96 -0
  24. package/dist/resources/extensions/gsd/health-widget.js +97 -46
  25. package/dist/resources/extensions/gsd/index.js +26 -33
  26. package/dist/resources/extensions/gsd/migrate-external.js +55 -2
  27. package/dist/resources/extensions/gsd/milestone-ids.js +3 -2
  28. package/dist/resources/extensions/gsd/paths.js +74 -7
  29. package/dist/resources/extensions/gsd/post-unit-hooks.js +4 -1
  30. package/dist/resources/extensions/gsd/preferences-validation.js +16 -1
  31. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  32. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  33. package/dist/resources/extensions/gsd/roadmap-mutations.js +55 -0
  34. package/dist/resources/extensions/gsd/session-lock.js +53 -2
  35. package/dist/resources/extensions/gsd/state.js +2 -1
  36. package/dist/resources/extensions/gsd/templates/plan.md +8 -0
  37. package/dist/resources/extensions/gsd/worktree-resolver.js +12 -0
  38. package/dist/resources/extensions/remote-questions/remote-command.js +2 -22
  39. package/dist/resources/extensions/shared/mod.js +1 -1
  40. package/dist/resources/extensions/shared/sanitize.js +30 -0
  41. package/dist/resources/extensions/subagent/index.js +6 -14
  42. package/package.json +2 -1
  43. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  44. package/packages/pi-coding-agent/dist/core/resource-loader.js +13 -2
  45. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  46. package/packages/pi-coding-agent/src/core/resource-loader.ts +13 -2
  47. package/src/resources/extensions/async-jobs/await-tool.ts +0 -2
  48. package/src/resources/extensions/async-jobs/job-manager.ts +0 -7
  49. package/src/resources/extensions/bg-shell/output-formatter.ts +0 -17
  50. package/src/resources/extensions/bg-shell/process-manager.ts +0 -4
  51. package/src/resources/extensions/bg-shell/types.ts +0 -12
  52. package/src/resources/extensions/context7/index.ts +7 -0
  53. package/src/resources/extensions/get-secrets-from-user.ts +2 -35
  54. package/src/resources/extensions/google-search/index.ts +7 -0
  55. package/src/resources/extensions/gsd/auto-dispatch.ts +49 -1
  56. package/src/resources/extensions/gsd/auto-loop.ts +11 -1
  57. package/src/resources/extensions/gsd/auto-recovery.ts +39 -0
  58. package/src/resources/extensions/gsd/auto-start.ts +42 -2
  59. package/src/resources/extensions/gsd/auto.ts +61 -3
  60. package/src/resources/extensions/gsd/commands-handlers.ts +2 -2
  61. package/src/resources/extensions/gsd/doctor-environment.ts +26 -16
  62. package/src/resources/extensions/gsd/files.ts +10 -1
  63. package/src/resources/extensions/gsd/gitignore.ts +54 -7
  64. package/src/resources/extensions/gsd/guided-flow.ts +1 -1
  65. package/src/resources/extensions/gsd/health-widget-core.ts +129 -0
  66. package/src/resources/extensions/gsd/health-widget.ts +103 -59
  67. package/src/resources/extensions/gsd/index.ts +30 -33
  68. package/src/resources/extensions/gsd/migrate-external.ts +47 -2
  69. package/src/resources/extensions/gsd/milestone-ids.ts +3 -2
  70. package/src/resources/extensions/gsd/paths.ts +73 -7
  71. package/src/resources/extensions/gsd/post-unit-hooks.ts +5 -1
  72. package/src/resources/extensions/gsd/preferences-validation.ts +16 -1
  73. package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  74. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  75. package/src/resources/extensions/gsd/roadmap-mutations.ts +66 -0
  76. package/src/resources/extensions/gsd/session-lock.ts +59 -2
  77. package/src/resources/extensions/gsd/state.ts +2 -1
  78. package/src/resources/extensions/gsd/templates/plan.md +8 -0
  79. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +214 -0
  80. package/src/resources/extensions/gsd/tests/health-widget.test.ts +158 -0
  81. package/src/resources/extensions/gsd/tests/paths.test.ts +113 -0
  82. package/src/resources/extensions/gsd/tests/preferences.test.ts +12 -2
  83. package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +26 -0
  84. package/src/resources/extensions/gsd/tests/test-utils.ts +165 -0
  85. package/src/resources/extensions/gsd/tests/validate-directory.test.ts +15 -0
  86. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +7 -0
  87. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +32 -0
  88. package/src/resources/extensions/gsd/worktree-resolver.ts +11 -0
  89. package/src/resources/extensions/remote-questions/remote-command.ts +2 -23
  90. package/src/resources/extensions/shared/mod.ts +1 -1
  91. package/src/resources/extensions/shared/sanitize.ts +36 -0
  92. package/src/resources/extensions/subagent/index.ts +6 -12
  93. package/dist/resources/extensions/shared/wizard-ui.js +0 -478
  94. package/src/resources/extensions/shared/wizard-ui.ts +0 -551
@@ -67,8 +67,9 @@ export function findMilestoneIds(basePath) {
67
67
  .filter((d) => d.isDirectory())
68
68
  .map((d) => {
69
69
  const match = d.name.match(/^(M\d+(?:-[a-z0-9]{6})?)/);
70
- return match ? match[1] : d.name;
71
- });
70
+ return match ? match[1] : null;
71
+ })
72
+ .filter((id) => id !== null);
72
73
  // Apply custom queue order if available, else fall back to numeric sort
73
74
  const customOrder = loadQueueOrder(basePath);
74
75
  return sortByQueueOrder(ids, customOrder);
@@ -9,7 +9,8 @@
9
9
  * via prefix matching, so existing projects work without migration.
10
10
  */
11
11
  import { readdirSync, existsSync, realpathSync, Dirent } from "node:fs";
12
- import { join } from "node:path";
12
+ import { join, dirname, normalize } from "node:path";
13
+ import { spawnSync } from "node:child_process";
13
14
  import { nativeScanGsdTree } from "./native-parser-bridge.js";
14
15
  import { DIR_CACHE_MAX } from "./constants.js";
15
16
  // ─── Directory Listing Cache ──────────────────────────────────────────────────
@@ -263,15 +264,81 @@ const LEGACY_GSD_ROOT_FILES = {
263
264
  OVERRIDES: "overrides.md",
264
265
  KNOWLEDGE: "knowledge.md",
265
266
  };
267
+ // ─── GSD Root Discovery ───────────────────────────────────────────────────────
268
+ const gsdRootCache = new Map();
269
+ /** Exported for tests only — do not call in production code. */
270
+ export function _clearGsdRootCache() {
271
+ gsdRootCache.clear();
272
+ }
273
+ /**
274
+ * Resolve the `.gsd` directory for a given project base path.
275
+ *
276
+ * Probe order:
277
+ * 1. basePath/.gsd — fast path (common case)
278
+ * 2. git rev-parse root — handles cwd-is-a-subdirectory
279
+ * 3. Walk up from basePath — handles moved .gsd in an ancestor (bounded by git root)
280
+ * 4. basePath/.gsd — creation fallback (init scenario)
281
+ *
282
+ * Result is cached per basePath for the process lifetime.
283
+ */
266
284
  export function gsdRoot(basePath) {
267
- const local = join(basePath, ".gsd");
285
+ const cached = gsdRootCache.get(basePath);
286
+ if (cached)
287
+ return cached;
288
+ const result = probeGsdRoot(basePath);
289
+ gsdRootCache.set(basePath, result);
290
+ return result;
291
+ }
292
+ function probeGsdRoot(rawBasePath) {
293
+ // 1. Fast path — check the input path directly
294
+ const local = join(rawBasePath, ".gsd");
295
+ if (existsSync(local))
296
+ return local;
297
+ // Resolve symlinks so path comparisons work correctly across platforms
298
+ // (e.g. macOS /var → /private/var). Use rawBasePath as fallback if not resolvable.
299
+ let basePath;
268
300
  try {
269
- const resolved = realpathSync(local);
270
- if (resolved !== local)
271
- return resolved; // symlink resolved
301
+ basePath = realpathSync.native(rawBasePath);
302
+ }
303
+ catch {
304
+ basePath = rawBasePath;
305
+ }
306
+ // 2. Git root anchor — used as both probe target and walk-up boundary
307
+ // Only walk if we're inside a git project — prevents escaping into
308
+ // unrelated filesystem territory when running outside any repo.
309
+ let gitRoot = null;
310
+ try {
311
+ const out = spawnSync("git", ["rev-parse", "--show-toplevel"], {
312
+ cwd: basePath,
313
+ encoding: "utf-8",
314
+ });
315
+ if (out.status === 0) {
316
+ const r = out.stdout.trim();
317
+ if (r)
318
+ gitRoot = normalize(r);
319
+ }
320
+ }
321
+ catch { /* git not available */ }
322
+ if (gitRoot) {
323
+ const candidate = join(gitRoot, ".gsd");
324
+ if (existsSync(candidate))
325
+ return candidate;
326
+ }
327
+ // 3. Walk up from basePath to the git root (only if we are in a subdirectory)
328
+ if (gitRoot && basePath !== gitRoot) {
329
+ let cur = dirname(basePath);
330
+ while (cur !== basePath) {
331
+ const candidate = join(cur, ".gsd");
332
+ if (existsSync(candidate))
333
+ return candidate;
334
+ if (cur === gitRoot)
335
+ break;
336
+ basePath = cur;
337
+ cur = dirname(cur);
338
+ }
272
339
  }
273
- catch { /* doesn't exist yet — fall through */ }
274
- return local; // backwards compat: unmigrated projects
340
+ // 4. Fallback for init/creation
341
+ return local;
275
342
  }
276
343
  export function milestonesDir(basePath) {
277
344
  return join(gsdRoot(basePath), "milestones");
@@ -111,10 +111,13 @@ function dequeueNextHook(basePath) {
111
111
  };
112
112
  // Build the prompt with variable substitution
113
113
  const [mid, sid, tid] = triggerUnitId.split("/");
114
- const prompt = config.prompt
114
+ let prompt = config.prompt
115
115
  .replace(/\{milestoneId\}/g, mid ?? "")
116
116
  .replace(/\{sliceId\}/g, sid ?? "")
117
117
  .replace(/\{taskId\}/g, tid ?? "");
118
+ // Inject browser safety instruction for hooks that may use browser tools (#1345).
119
+ // Vite HMR and other persistent connections prevent networkidle from resolving.
120
+ prompt += "\n\n**Browser tool safety:** Do NOT use `browser_wait_for` with `condition: \"network_idle\"` — it hangs indefinitely when dev servers keep persistent connections (Vite HMR, WebSocket). Use `selector_visible`, `text_visible`, or `delay` instead.";
118
121
  return {
119
122
  hookName: config.name,
120
123
  prompt,
@@ -14,9 +14,24 @@ export function validatePreferences(preferences) {
14
14
  const warnings = [];
15
15
  const validated = {};
16
16
  // ─── Unknown Key Detection ──────────────────────────────────────────
17
+ // Common key migration hints for pi-level settings that don't map to GSD prefs
18
+ const KEY_MIGRATION_HINTS = {
19
+ taskIsolation: 'use "git.isolation" instead (values: worktree, branch, none)',
20
+ task_isolation: 'use "git.isolation" instead (values: worktree, branch, none)',
21
+ isolation: 'use "git.isolation" instead (values: worktree, branch, none)',
22
+ manage_gitignore: 'use "git.manage_gitignore" instead',
23
+ auto_push: 'use "git.auto_push" instead',
24
+ main_branch: 'use "git.main_branch" instead',
25
+ };
17
26
  for (const key of Object.keys(preferences)) {
18
27
  if (!KNOWN_PREFERENCE_KEYS.has(key)) {
19
- warnings.push(`unknown preference key "${key}" — ignored`);
28
+ const hint = KEY_MIGRATION_HINTS[key];
29
+ if (hint) {
30
+ warnings.push(`unknown preference key "${key}" — ${hint}`);
31
+ }
32
+ else {
33
+ warnings.push(`unknown preference key "${key}" — ignored`);
34
+ }
20
35
  }
21
36
  }
22
37
  if (preferences.version !== undefined) {
@@ -28,6 +28,8 @@ Then:
28
28
 
29
29
  **Important:** Do NOT skip the success criteria and definition of done verification (steps 3-4). The milestone summary must reflect actual verified outcomes, not assumed success. If any criterion was not met, document it clearly in the summary and do not mark the milestone as passing verification.
30
30
 
31
+ **File system safety:** When scanning milestone directories for evidence, use `ls` or `find` to list directory contents first — never pass a directory path (e.g. `tasks/`, `slices/`) directly to the `read` tool. The `read` tool only accepts file paths, not directories.
32
+
31
33
  **You MUST write `{{milestoneSummaryPath}}` AND update PROJECT.md before finishing.**
32
34
 
33
35
  When done, say: "Milestone {{milestoneId}} complete."
@@ -67,4 +67,6 @@ If verdict is `needs-remediation`:
67
67
 
68
68
  **You MUST write `{{validationPath}}` before finishing.**
69
69
 
70
+ **File system safety:** When scanning milestone directories for evidence, use `ls` or `find` to list directory contents first — never pass a directory path (e.g. `tasks/`, `slices/`) directly to the `read` tool. The `read` tool only accepts file paths, not directories.
71
+
70
72
  When done, say: "Milestone {{milestoneId}} validation complete — verdict: <verdict>."
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Roadmap Mutations — shared utilities for modifying roadmap checkbox state.
3
+ *
4
+ * Extracts the duplicated "flip slice checkbox" pattern that existed in
5
+ * doctor.ts, mechanical-completion.ts, and auto-recovery.ts.
6
+ */
7
+ import { readFileSync } from "node:fs";
8
+ import { atomicWriteSync } from "./atomic-write.js";
9
+ import { resolveMilestoneFile } from "./paths.js";
10
+ import { clearParseCache } from "./files.js";
11
+ /**
12
+ * Mark a slice as done ([x]) in the milestone roadmap.
13
+ * Idempotent — no-op if already checked or if the slice isn't found.
14
+ *
15
+ * @returns true if the roadmap was modified, false if no change was needed
16
+ */
17
+ export function markSliceDoneInRoadmap(basePath, mid, sid) {
18
+ const roadmapFile = resolveMilestoneFile(basePath, mid, "ROADMAP");
19
+ if (!roadmapFile)
20
+ return false;
21
+ let content;
22
+ try {
23
+ content = readFileSync(roadmapFile, "utf-8");
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ const updated = content.replace(new RegExp(`^(\\s*-\\s+)\\[ \\]\\s+\\*\\*${sid}:`, "m"), `$1[x] **${sid}:`);
29
+ if (updated === content)
30
+ return false;
31
+ atomicWriteSync(roadmapFile, updated);
32
+ clearParseCache();
33
+ return true;
34
+ }
35
+ /**
36
+ * Mark a task as done ([x]) in the slice plan.
37
+ * Idempotent — no-op if already checked or if the task isn't found.
38
+ *
39
+ * @returns true if the plan was modified, false if no change was needed
40
+ */
41
+ export function markTaskDoneInPlan(basePath, planPath, tid) {
42
+ let content;
43
+ try {
44
+ content = readFileSync(planPath, "utf-8");
45
+ }
46
+ catch {
47
+ return false;
48
+ }
49
+ const updated = content.replace(new RegExp(`^(\\s*-\\s+)\\[ \\]\\s+\\*\\*${tid}:`, "m"), `$1[x] **${tid}:`);
50
+ if (updated === content)
51
+ return false;
52
+ atomicWriteSync(planPath, updated);
53
+ clearParseCache();
54
+ return true;
55
+ }
@@ -32,8 +32,17 @@ let _lockPid = 0;
32
32
  let _lockCompromised = false;
33
33
  /** Whether we've already registered a process.on('exit') handler. */
34
34
  let _exitHandlerRegistered = false;
35
+ /** Snapshotted lock file path — captured at acquireSessionLock time to avoid
36
+ * gsdRoot() resolving differently in worktree vs project root contexts (#1363). */
37
+ let _snapshotLockPath = null;
38
+ /** Timestamp when the session lock was acquired — used to detect false-positive
39
+ * onCompromised events from event loop stalls within the stale window (#1362). */
40
+ let _lockAcquiredAt = 0;
35
41
  const LOCK_FILE = "auto.lock";
36
42
  function lockPath(basePath) {
43
+ // If we have a snapshotted path from acquisition, use it for consistency
44
+ if (_snapshotLockPath)
45
+ return _snapshotLockPath;
37
46
  return join(gsdRoot(basePath), LOCK_FILE);
38
47
  }
39
48
  // ─── Stray Lock Cleanup ─────────────────────────────────────────────────────
@@ -175,8 +184,17 @@ export function acquireSessionLock(basePath) {
175
184
  onCompromised: () => {
176
185
  // proper-lockfile detected mtime drift (system sleep, event loop stall, etc.).
177
186
  // Default handler throws inside setTimeout — an uncaught exception that crashes
178
- // or corrupts process state. Instead, set a flag so validateSessionLock() can
179
- // detect the compromise gracefully on the next dispatch cycle.
187
+ // or corrupts process state.
188
+ //
189
+ // False-positive suppression (#1362): If we're still within the stale window
190
+ // (30 min since acquisition), the mtime mismatch is from an event loop stall
191
+ // during a long LLM call — not a real takeover. Log and continue.
192
+ const elapsed = Date.now() - _lockAcquiredAt;
193
+ if (elapsed < 1_800_000) {
194
+ process.stderr.write(`[gsd] Lock heartbeat mismatch after ${Math.round(elapsed / 1000)}s — event loop stall, continuing.\n`);
195
+ return; // Suppress false positive
196
+ }
197
+ // Past the stale window — this is a real compromise
180
198
  _lockCompromised = true;
181
199
  _releaseFunction = null;
182
200
  },
@@ -185,6 +203,8 @@ export function acquireSessionLock(basePath) {
185
203
  _lockedPath = basePath;
186
204
  _lockPid = process.pid;
187
205
  _lockCompromised = false;
206
+ _lockAcquiredAt = Date.now();
207
+ _snapshotLockPath = lp; // Snapshot the resolved path for consistent access (#1363)
188
208
  // Safety net: clean up lock dir on process exit if _releaseFunction
189
209
  // wasn't called (e.g., normal exit after clean completion) (#1245).
190
210
  ensureExitHandler(gsdDir);
@@ -211,6 +231,14 @@ export function acquireSessionLock(basePath) {
211
231
  stale: 1_800_000, // 30 minutes — match primary lock settings
212
232
  update: 10_000,
213
233
  onCompromised: () => {
234
+ // Same false-positive suppression as the primary lock (#1512).
235
+ // Without this, the retry path fires _lockCompromised unconditionally
236
+ // on benign mtime drift (laptop sleep, heavy LLM event loop stalls).
237
+ const elapsed = Date.now() - _lockAcquiredAt;
238
+ if (elapsed < 1_800_000) {
239
+ process.stderr.write(`[gsd] Lock heartbeat mismatch after ${Math.round(elapsed / 1000)}s — event loop stall, continuing.\n`);
240
+ return;
241
+ }
214
242
  _lockCompromised = true;
215
243
  _releaseFunction = null;
216
244
  },
@@ -219,6 +247,8 @@ export function acquireSessionLock(basePath) {
219
247
  _lockedPath = basePath;
220
248
  _lockPid = process.pid;
221
249
  _lockCompromised = false;
250
+ _lockAcquiredAt = Date.now();
251
+ _snapshotLockPath = lp; // Snapshot for retry path too (#1363)
222
252
  // Safety net — uses centralized handler to avoid double-registration
223
253
  ensureExitHandler(gsdDir);
224
254
  atomicWriteSync(lp, JSON.stringify(lockData, null, 2));
@@ -293,6 +323,25 @@ export function updateSessionLock(basePath, unitType, unitId, completedUnits, se
293
323
  export function validateSessionLock(basePath) {
294
324
  // Lock was compromised by proper-lockfile (mtime drift from sleep, stall, etc.)
295
325
  if (_lockCompromised) {
326
+ // Recovery gate (#1512): Before declaring the lock lost, check if the lock
327
+ // file still contains our PID. If it does, no other process took over — the
328
+ // onCompromised fired from benign mtime drift (laptop sleep, event loop stall
329
+ // beyond the stale window). Attempt re-acquisition instead of giving up.
330
+ const lp = lockPath(basePath);
331
+ const existing = readExistingLockData(lp);
332
+ if (existing && existing.pid === process.pid) {
333
+ // Lock file still ours — try to re-acquire the OS lock
334
+ try {
335
+ const result = acquireSessionLock(basePath);
336
+ if (result.acquired) {
337
+ process.stderr.write(`[gsd] Lock recovered after onCompromised — lock file PID matched, re-acquired.\n`);
338
+ return true;
339
+ }
340
+ }
341
+ catch {
342
+ // Re-acquisition failed — fall through to return false
343
+ }
344
+ }
296
345
  return false;
297
346
  }
298
347
  // If we have an OS-level lock, we're still the owner
@@ -348,6 +397,8 @@ export function releaseSessionLock(basePath) {
348
397
  _lockedPath = null;
349
398
  _lockPid = 0;
350
399
  _lockCompromised = false;
400
+ _lockAcquiredAt = 0;
401
+ _snapshotLockPath = null;
351
402
  }
352
403
  /**
353
404
  * Check if a session lock exists and return its data (for crash recovery).
@@ -33,11 +33,12 @@ export function isValidationTerminal(validationContent) {
33
33
  const verdict = match[1].match(/verdict:\s*(\S+)/);
34
34
  if (!verdict)
35
35
  return false;
36
+ const v = verdict[1] === 'passed' ? 'pass' : verdict[1];
36
37
  // 'pass' and 'needs-attention' are always terminal.
37
38
  // 'needs-remediation' is treated as terminal to prevent infinite loops
38
39
  // when no remediation slices exist in the roadmap (#832). The validation
39
40
  // report is preserved on disk for manual review.
40
- return verdict[1] === 'pass' || verdict[1] === 'needs-attention' || verdict[1] === 'needs-remediation';
41
+ return v === 'pass' || v === 'needs-attention' || v === 'needs-remediation';
41
42
  }
42
43
  const CACHE_TTL_MS = 100;
43
44
  let _stateCache = null;
@@ -113,6 +113,14 @@
113
113
  - Tasks execute sequentially in order (T01, T02, T03, ...)
114
114
  - est: is informational (e.g. 30m, 1h, 2h) and optional
115
115
 
116
+ Verify field rules:
117
+ - MUST be a mechanically executable command: `npm test`, `grep -q "pattern" file`, `test -f path`
118
+ - For content/document tasks: verify file existence, section count, YAML validity, or word count
119
+ NOT exact phrasing, specific formulas, or "zero TBD" aspirational criteria
120
+ - If no command can verify the output, write: "Manual review — file exists and is non-empty"
121
+ - BAD: "Sections 3.1 and 3.2 exist with exact formulas. Zero TBD/TODO."
122
+ - GOOD: `grep -c "^## " doc.md` returns >= 4 (4+ sections), `! grep -q "TBD\|TODO" doc.md`
123
+
116
124
  Integration closure rule:
117
125
  - At least one slice in any multi-boundary milestone should perform real composition/wiring, not just contract hardening
118
126
  - For the final assembly slice, verification must exercise the real entrypoint or runtime path
@@ -12,6 +12,8 @@
12
12
  * Key invariant: `createAutoWorktree()` and `enterAutoWorktree()` call
13
13
  * `process.chdir()` internally — this class MUST NOT double-chdir.
14
14
  */
15
+ import { existsSync, unlinkSync } from "node:fs";
16
+ import { join } from "node:path";
15
17
  import { debugLog } from "./debug-logger.js";
16
18
  // ─── WorktreeResolver ──────────────────────────────────────────────────────
17
19
  export class WorktreeResolver {
@@ -253,6 +255,16 @@ export class WorktreeResolver {
253
255
  fallback: "chdir-to-project-root",
254
256
  });
255
257
  ctx.notify(`Milestone merge failed: ${msg}`, "warning");
258
+ // Clean up stale merge state left by failed squash-merge (#1389)
259
+ try {
260
+ const gitDir = join(originalBase || this.s.basePath, ".git");
261
+ for (const f of ["SQUASH_MSG", "MERGE_HEAD", "MERGE_MSG"]) {
262
+ const p = join(gitDir, f);
263
+ if (existsSync(p))
264
+ unlinkSync(p);
265
+ }
266
+ }
267
+ catch { /* best-effort */ }
256
268
  // Error recovery: always restore to project root
257
269
  if (originalBase) {
258
270
  try {
@@ -2,12 +2,12 @@
2
2
  * Remote Questions — /gsd remote command
3
3
  */
4
4
  import { AuthStorage } from "@gsd/pi-coding-agent";
5
- import { CURSOR_MARKER, Editor, Key, matchesKey, truncateToWidth } from "@gsd/pi-tui";
5
+ import { Editor, Key, matchesKey, truncateToWidth } from "@gsd/pi-tui";
6
6
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
7
7
  import { dirname, join } from "node:path";
8
8
  import { getGlobalGSDPreferencesPath, loadEffectiveGSDPreferences } from "../gsd/preferences.js";
9
9
  import { getRemoteConfigStatus, isValidChannelId, resolveRemoteConfig } from "./config.js";
10
- import { sanitizeError } from "../shared/sanitize.js";
10
+ import { maskEditorLine, sanitizeError } from "../shared/mod.js";
11
11
  import { getLatestPromptSummary } from "./status.js";
12
12
  export async function handleRemote(subcommand, ctx, _pi) {
13
13
  const trimmed = subcommand.trim();
@@ -339,26 +339,6 @@ function removeRemoteQuestionsConfig() {
339
339
  const next = frontmatter ? `---\n${frontmatter}\n---${content.slice(fmMatch[0].length)}` : content.slice(fmMatch[0].length).replace(/^\n+/, "");
340
340
  writeFileSync(prefsPath, next, "utf-8");
341
341
  }
342
- function maskEditorLine(line) {
343
- let output = "";
344
- let i = 0;
345
- while (i < line.length) {
346
- if (line.startsWith(CURSOR_MARKER, i)) {
347
- output += CURSOR_MARKER;
348
- i += CURSOR_MARKER.length;
349
- continue;
350
- }
351
- const ansiMatch = /^\x1b\[[0-9;]*m/.exec(line.slice(i));
352
- if (ansiMatch) {
353
- output += ansiMatch[0];
354
- i += ansiMatch[0].length;
355
- continue;
356
- }
357
- output += line[i] === " " ? " " : "*";
358
- i += 1;
359
- }
360
- return output;
361
- }
362
342
  async function promptMaskedInput(ctx, label, hint) {
363
343
  if (!ctx.hasUI)
364
344
  return null;
@@ -6,6 +6,6 @@ export { toPosixPath } from "./path-display.js";
6
6
  export { showInterviewRound } from "./interview-ui.js";
7
7
  export { showNextAction } from "./next-action-ui.js";
8
8
  export { showConfirm } from "./confirm-ui.js";
9
- export { sanitizeError } from "./sanitize.js";
9
+ export { sanitizeError, maskEditorLine } from "./sanitize.js";
10
10
  export { formatDateShort, truncateWithEllipsis } from "./format-utils.js";
11
11
  export { splitFrontmatter, parseFrontmatterMap } from "./frontmatter.js";
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Sanitize error messages by redacting token-like strings before surfacing.
3
+ * Also provides maskEditorLine for masking sensitive TUI editor input.
3
4
  */
5
+ import { CURSOR_MARKER } from "@gsd/pi-tui";
4
6
  const TOKEN_PATTERNS = [
5
7
  /xoxb-[A-Za-z0-9\-]+/g, // Slack bot tokens
6
8
  /xoxp-[A-Za-z0-9\-]+/g, // Slack user tokens
@@ -15,3 +17,31 @@ export function sanitizeError(msg) {
15
17
  }
16
18
  return sanitized;
17
19
  }
20
+ /**
21
+ * Replace editor visible text with masked characters while preserving
22
+ * ANSI cursor/sequencer codes. Keeps border/metadata lines readable.
23
+ */
24
+ export function maskEditorLine(line) {
25
+ if (line.startsWith("─")) {
26
+ return line;
27
+ }
28
+ let output = "";
29
+ let i = 0;
30
+ while (i < line.length) {
31
+ if (line.startsWith(CURSOR_MARKER, i)) {
32
+ output += CURSOR_MARKER;
33
+ i += CURSOR_MARKER.length;
34
+ continue;
35
+ }
36
+ const ansiMatch = /^\x1b\[[0-9;]*m/.exec(line.slice(i));
37
+ if (ansiMatch) {
38
+ output += ansiMatch[0];
39
+ i += ansiMatch[0].length;
40
+ continue;
41
+ }
42
+ const ch = line[i];
43
+ output += ch === " " ? " " : "*";
44
+ i += 1;
45
+ }
46
+ return output;
47
+ }
@@ -20,6 +20,7 @@ import { StringEnum } from "@gsd/pi-ai";
20
20
  import { getMarkdownTheme } from "@gsd/pi-coding-agent";
21
21
  import { Container, Markdown, Spacer, Text } from "@gsd/pi-tui";
22
22
  import { Type } from "@sinclair/typebox";
23
+ import { formatTokenCount } from "../shared/mod.js";
23
24
  import { discoverAgents } from "./agents.js";
24
25
  import { createIsolation, mergeDeltaPatches, readIsolationMode, } from "./isolation.js";
25
26
  import { registerWorker, updateWorker } from "./worker-registry.js";
@@ -58,31 +59,22 @@ async function stopLiveSubagents() {
58
59
  }
59
60
  }
60
61
  }
61
- function formatTokens(count) {
62
- if (count < 1000)
63
- return count.toString();
64
- if (count < 10000)
65
- return `${(count / 1000).toFixed(1)}k`;
66
- if (count < 1000000)
67
- return `${Math.round(count / 1000)}k`;
68
- return `${(count / 1000000).toFixed(1)}M`;
69
- }
70
62
  function formatUsageStats(usage, model) {
71
63
  const parts = [];
72
64
  if (usage.turns)
73
65
  parts.push(`${usage.turns} turn${usage.turns > 1 ? "s" : ""}`);
74
66
  if (usage.input)
75
- parts.push(`↑${formatTokens(usage.input)}`);
67
+ parts.push(`↑${formatTokenCount(usage.input)}`);
76
68
  if (usage.output)
77
- parts.push(`↓${formatTokens(usage.output)}`);
69
+ parts.push(`↓${formatTokenCount(usage.output)}`);
78
70
  if (usage.cacheRead)
79
- parts.push(`R${formatTokens(usage.cacheRead)}`);
71
+ parts.push(`R${formatTokenCount(usage.cacheRead)}`);
80
72
  if (usage.cacheWrite)
81
- parts.push(`W${formatTokens(usage.cacheWrite)}`);
73
+ parts.push(`W${formatTokenCount(usage.cacheWrite)}`);
82
74
  if (usage.cost)
83
75
  parts.push(`$${(Number(usage.cost) || 0).toFixed(4)}`);
84
76
  if (usage.contextTokens && usage.contextTokens > 0) {
85
- parts.push(`ctx:${formatTokens(usage.contextTokens)}`);
77
+ parts.push(`ctx:${formatTokenCount(usage.contextTokens)}`);
86
78
  }
87
79
  if (model)
88
80
  parts.push(model);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gsd-pi",
3
- "version": "2.35.0-dev.640d5c7",
3
+ "version": "2.35.0-dev.67d0e02",
4
4
  "description": "GSD — Get Shit Done coding agent",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -50,6 +50,7 @@
50
50
  "copy-themes": "node scripts/copy-themes.cjs",
51
51
  "copy-export-html": "node scripts/copy-export-html.cjs",
52
52
  "test:unit": "node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/*.test.ts src/resources/extensions/gsd/tests/*.test.mjs src/tests/*.test.ts",
53
+ "test:marketplace": "GSD_TEST_CLONE_MARKETPLACES=1 node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/claude-import-tui.test.ts src/resources/extensions/gsd/tests/plugin-importer-live.test.ts src/tests/marketplace-discovery.test.ts",
53
54
  "test:coverage": "c8 --reporter=text --reporter=lcov --exclude='src/resources/extensions/gsd/tests/**' --exclude='src/tests/**' --exclude='scripts/**' --exclude='native/**' --exclude='node_modules/**' --check-coverage --statements=50 --lines=50 --branches=20 --functions=20 node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/*.test.ts src/resources/extensions/gsd/tests/*.test.mjs src/tests/*.test.ts",
54
55
  "test:integration": "node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/*integration*.test.ts src/tests/integration/*.test.ts",
55
56
  "test": "npm run test:unit && npm run test:integration",
@@ -1 +1 @@
1
- {"version":3,"file":"resource-loader.d.ts","sourceRoot":"","sources":["../../src/core/resource-loader.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resource-loader.d.ts","sourceRoot":"","sources":["../../src/core/resource-loader.ts"],"names":[],"mappings":"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"}
@@ -566,9 +566,15 @@ export class DefaultResourceLoader {
566
566
  for (const toolName of ext.tools.keys()) {
567
567
  const existingOwner = toolOwners.get(toolName);
568
568
  if (existingOwner && existingOwner !== ext.path) {
569
+ // Determine if the existing owner is a built-in (not a user extension)
570
+ const isBuiltIn = !existingOwner.includes("/.gsd/agent/extensions/") &&
571
+ !existingOwner.includes("/.gsd/extensions/");
572
+ const hint = isBuiltIn
573
+ ? ` (built-in tool supersedes — consider removing ${ext.path})`
574
+ : "";
569
575
  conflicts.push({
570
576
  path: ext.path,
571
- message: `Tool "${toolName}" conflicts with ${existingOwner}`,
577
+ message: `Tool "${toolName}" conflicts with ${existingOwner}${hint}`,
572
578
  });
573
579
  }
574
580
  else {
@@ -579,9 +585,14 @@ export class DefaultResourceLoader {
579
585
  for (const commandName of ext.commands.keys()) {
580
586
  const existingOwner = commandOwners.get(commandName);
581
587
  if (existingOwner && existingOwner !== ext.path) {
588
+ const isBuiltIn = !existingOwner.includes("/.gsd/agent/extensions/") &&
589
+ !existingOwner.includes("/.gsd/extensions/");
590
+ const hint = isBuiltIn
591
+ ? ` (built-in command supersedes — consider removing ${ext.path})`
592
+ : "";
582
593
  conflicts.push({
583
594
  path: ext.path,
584
- message: `Command "/${commandName}" conflicts with ${existingOwner}`,
595
+ message: `Command "/${commandName}" conflicts with ${existingOwner}${hint}`,
585
596
  });
586
597
  }
587
598
  else {