gsd-pi 2.35.0 → 2.36.0-dev.d612764
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/cli.js +7 -2
- package/dist/resource-loader.d.ts +1 -1
- package/dist/resource-loader.js +13 -1
- package/dist/resources/extensions/async-jobs/await-tool.js +0 -2
- package/dist/resources/extensions/async-jobs/job-manager.js +0 -6
- package/dist/resources/extensions/bg-shell/output-formatter.js +1 -19
- package/dist/resources/extensions/bg-shell/process-manager.js +0 -4
- package/dist/resources/extensions/bg-shell/types.js +0 -2
- package/dist/resources/extensions/cmux/index.js +321 -0
- package/dist/resources/extensions/context7/index.js +5 -0
- package/dist/resources/extensions/get-secrets-from-user.js +2 -30
- package/dist/resources/extensions/google-search/index.js +5 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +334 -104
- package/dist/resources/extensions/gsd/auto-dispatch.js +43 -1
- package/dist/resources/extensions/gsd/auto-loop.js +28 -3
- package/dist/resources/extensions/gsd/auto-model-selection.js +15 -3
- package/dist/resources/extensions/gsd/auto-recovery.js +35 -0
- package/dist/resources/extensions/gsd/auto-start.js +35 -2
- package/dist/resources/extensions/gsd/auto.js +75 -4
- package/dist/resources/extensions/gsd/commands-cmux.js +120 -0
- package/dist/resources/extensions/gsd/commands-handlers.js +2 -2
- package/dist/resources/extensions/gsd/commands-inspect.js +10 -3
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands-rate.js +31 -0
- package/dist/resources/extensions/gsd/commands.js +94 -2
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +25 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +26 -17
- package/dist/resources/extensions/gsd/files.js +11 -2
- package/dist/resources/extensions/gsd/gitignore.js +54 -7
- package/dist/resources/extensions/gsd/guided-flow.js +8 -2
- package/dist/resources/extensions/gsd/health-widget-core.js +96 -0
- package/dist/resources/extensions/gsd/health-widget.js +97 -46
- package/dist/resources/extensions/gsd/index.js +31 -33
- package/dist/resources/extensions/gsd/migrate-external.js +55 -2
- package/dist/resources/extensions/gsd/milestone-ids.js +3 -2
- package/dist/resources/extensions/gsd/notifications.js +10 -1
- package/dist/resources/extensions/gsd/paths.js +74 -7
- package/dist/resources/extensions/gsd/post-unit-hooks.js +4 -1
- package/dist/resources/extensions/gsd/preferences-types.js +2 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +45 -1
- package/dist/resources/extensions/gsd/preferences.js +15 -0
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +4 -3
- package/dist/resources/extensions/gsd/prompts/research-slice.md +3 -2
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/dist/resources/extensions/gsd/roadmap-mutations.js +55 -0
- package/dist/resources/extensions/gsd/session-lock.js +53 -2
- package/dist/resources/extensions/gsd/state.js +2 -1
- package/dist/resources/extensions/gsd/templates/plan.md +8 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +6 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +12 -0
- package/dist/resources/extensions/remote-questions/remote-command.js +2 -22
- package/dist/resources/extensions/search-the-web/native-search.js +45 -4
- package/dist/resources/extensions/shared/mod.js +1 -1
- package/dist/resources/extensions/shared/sanitize.js +30 -0
- package/dist/resources/extensions/shared/terminal.js +5 -0
- package/dist/resources/extensions/subagent/index.js +186 -74
- package/dist/resources/skills/core-web-vitals/SKILL.md +1 -1
- package/dist/resources/skills/create-gsd-extension/workflows/debug-extension.md +1 -1
- package/dist/resources/skills/github-workflows/SKILL.md +0 -2
- package/dist/resources/skills/web-quality-audit/SKILL.md +0 -2
- package/package.json +2 -1
- package/packages/pi-agent-core/dist/agent.d.ts +10 -2
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +19 -8
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/src/agent.ts +31 -10
- package/packages/pi-ai/dist/providers/openai-responses.js +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/src/providers/openai-responses.ts +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +20 -4
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +13 -2
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +36 -12
- package/packages/pi-coding-agent/src/core/resource-loader.ts +13 -2
- package/packages/pi-tui/dist/terminal-image.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal-image.js +4 -0
- package/packages/pi-tui/dist/terminal-image.js.map +1 -1
- package/packages/pi-tui/src/terminal-image.ts +5 -0
- package/pkg/package.json +1 -1
- package/src/resources/extensions/async-jobs/await-tool.ts +0 -2
- package/src/resources/extensions/async-jobs/job-manager.ts +0 -7
- package/src/resources/extensions/bg-shell/output-formatter.ts +0 -17
- package/src/resources/extensions/bg-shell/process-manager.ts +0 -4
- package/src/resources/extensions/bg-shell/types.ts +0 -12
- package/src/resources/extensions/cmux/index.ts +384 -0
- package/src/resources/extensions/context7/index.ts +7 -0
- package/src/resources/extensions/get-secrets-from-user.ts +2 -35
- package/src/resources/extensions/google-search/index.ts +7 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +363 -116
- package/src/resources/extensions/gsd/auto-dispatch.ts +49 -1
- package/src/resources/extensions/gsd/auto-loop.ts +64 -2
- package/src/resources/extensions/gsd/auto-model-selection.ts +23 -2
- package/src/resources/extensions/gsd/auto-recovery.ts +39 -0
- package/src/resources/extensions/gsd/auto-start.ts +42 -2
- package/src/resources/extensions/gsd/auto.ts +82 -3
- package/src/resources/extensions/gsd/commands-cmux.ts +143 -0
- package/src/resources/extensions/gsd/commands-handlers.ts +2 -2
- package/src/resources/extensions/gsd/commands-inspect.ts +10 -3
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands-rate.ts +55 -0
- package/src/resources/extensions/gsd/commands.ts +97 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +26 -16
- package/src/resources/extensions/gsd/files.ts +12 -2
- package/src/resources/extensions/gsd/gitignore.ts +54 -7
- package/src/resources/extensions/gsd/guided-flow.ts +8 -2
- package/src/resources/extensions/gsd/health-widget-core.ts +129 -0
- package/src/resources/extensions/gsd/health-widget.ts +103 -59
- package/src/resources/extensions/gsd/index.ts +37 -32
- package/src/resources/extensions/gsd/migrate-external.ts +47 -2
- package/src/resources/extensions/gsd/milestone-ids.ts +3 -2
- package/src/resources/extensions/gsd/notifications.ts +10 -1
- package/src/resources/extensions/gsd/paths.ts +73 -7
- package/src/resources/extensions/gsd/post-unit-hooks.ts +5 -1
- package/src/resources/extensions/gsd/preferences-types.ts +13 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +42 -1
- package/src/resources/extensions/gsd/preferences.ts +18 -1
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/src/resources/extensions/gsd/prompts/research-milestone.md +4 -3
- package/src/resources/extensions/gsd/prompts/research-slice.md +3 -2
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/src/resources/extensions/gsd/roadmap-mutations.ts +66 -0
- package/src/resources/extensions/gsd/session-lock.ts +59 -2
- package/src/resources/extensions/gsd/state.ts +2 -1
- package/src/resources/extensions/gsd/templates/plan.md +8 -0
- package/src/resources/extensions/gsd/templates/preferences.md +6 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/cmux.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +214 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +158 -0
- package/src/resources/extensions/gsd/tests/paths.test.ts +113 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +35 -2
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/test-utils.ts +165 -0
- package/src/resources/extensions/gsd/tests/validate-directory.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +32 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +11 -0
- package/src/resources/extensions/remote-questions/remote-command.ts +2 -23
- package/src/resources/extensions/search-the-web/native-search.ts +50 -4
- package/src/resources/extensions/shared/mod.ts +1 -1
- package/src/resources/extensions/shared/sanitize.ts +36 -0
- package/src/resources/extensions/shared/terminal.ts +5 -0
- package/src/resources/extensions/subagent/index.ts +242 -91
- package/src/resources/skills/core-web-vitals/SKILL.md +1 -1
- package/src/resources/skills/create-gsd-extension/workflows/debug-extension.md +1 -1
- package/src/resources/skills/github-workflows/SKILL.md +0 -2
- package/src/resources/skills/web-quality-audit/SKILL.md +0 -2
- package/dist/resources/extensions/shared/wizard-ui.js +0 -478
- package/dist/resources/skills/swiftui/SKILL.md +0 -208
- package/dist/resources/skills/swiftui/references/animations.md +0 -921
- package/dist/resources/skills/swiftui/references/architecture.md +0 -1561
- package/dist/resources/skills/swiftui/references/layout-system.md +0 -1186
- package/dist/resources/skills/swiftui/references/navigation.md +0 -1492
- package/dist/resources/skills/swiftui/references/networking-async.md +0 -214
- package/dist/resources/skills/swiftui/references/performance.md +0 -1706
- package/dist/resources/skills/swiftui/references/platform-integration.md +0 -204
- package/dist/resources/skills/swiftui/references/state-management.md +0 -1443
- package/dist/resources/skills/swiftui/references/swiftdata.md +0 -297
- package/dist/resources/skills/swiftui/references/testing-debugging.md +0 -247
- package/dist/resources/skills/swiftui/references/uikit-appkit-interop.md +0 -218
- package/dist/resources/skills/swiftui/workflows/add-feature.md +0 -191
- package/dist/resources/skills/swiftui/workflows/build-new-app.md +0 -311
- package/dist/resources/skills/swiftui/workflows/debug-swiftui.md +0 -192
- package/dist/resources/skills/swiftui/workflows/optimize-performance.md +0 -197
- package/dist/resources/skills/swiftui/workflows/ship-app.md +0 -203
- package/dist/resources/skills/swiftui/workflows/write-tests.md +0 -235
- package/src/resources/extensions/shared/wizard-ui.ts +0 -551
- package/src/resources/skills/swiftui/SKILL.md +0 -208
- package/src/resources/skills/swiftui/references/animations.md +0 -921
- package/src/resources/skills/swiftui/references/architecture.md +0 -1561
- package/src/resources/skills/swiftui/references/layout-system.md +0 -1186
- package/src/resources/skills/swiftui/references/navigation.md +0 -1492
- package/src/resources/skills/swiftui/references/networking-async.md +0 -214
- package/src/resources/skills/swiftui/references/performance.md +0 -1706
- package/src/resources/skills/swiftui/references/platform-integration.md +0 -204
- package/src/resources/skills/swiftui/references/state-management.md +0 -1443
- package/src/resources/skills/swiftui/references/swiftdata.md +0 -297
- package/src/resources/skills/swiftui/references/testing-debugging.md +0 -247
- package/src/resources/skills/swiftui/references/uikit-appkit-interop.md +0 -218
- package/src/resources/skills/swiftui/workflows/add-feature.md +0 -191
- package/src/resources/skills/swiftui/workflows/build-new-app.md +0 -311
- package/src/resources/skills/swiftui/workflows/debug-swiftui.md +0 -192
- package/src/resources/skills/swiftui/workflows/optimize-performance.md +0 -197
- package/src/resources/skills/swiftui/workflows/ship-app.md +0 -203
- package/src/resources/skills/swiftui/workflows/write-tests.md +0 -235
|
@@ -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."
|
|
@@ -25,9 +25,10 @@ Then research the codebase and relevant technologies. Narrate key findings and s
|
|
|
25
25
|
2. **Skill Discovery ({{skillDiscoveryMode}}):**{{skillDiscoveryInstructions}}
|
|
26
26
|
3. Explore relevant code. For small/familiar codebases, use `rg`, `find`, and targeted reads. For large or unfamiliar codebases, use `scout` to build a broad map efficiently before diving in.
|
|
27
27
|
4. Use `resolve_library` / `get_library_docs` for unfamiliar libraries — skip this for libraries already used in the codebase
|
|
28
|
-
5. Use
|
|
29
|
-
6.
|
|
30
|
-
7.
|
|
28
|
+
5. **Web search budget:** You have a limited budget of web searches (max ~15 per session). Use them strategically — prefer `resolve_library` / `get_library_docs` for library documentation. Do NOT repeat the same or similar queries. If a search didn't find what you need, rephrase once or move on. Target 3-5 total web searches for a typical research unit.
|
|
29
|
+
6. Use the **Research** output template from the inlined context above — include only sections that have real content
|
|
30
|
+
7. If `.gsd/REQUIREMENTS.md` exists, research against it. Identify which Active requirements are table stakes, likely omissions, overbuilt risks, or domain-standard behaviors the user may or may not want.
|
|
31
|
+
8. Write `{{outputPath}}`
|
|
31
32
|
|
|
32
33
|
## Strategic Questions to Answer
|
|
33
34
|
|
|
@@ -46,8 +46,9 @@ Research what this slice needs. Narrate key findings and surprises as you go —
|
|
|
46
46
|
2. **Skill Discovery ({{skillDiscoveryMode}}):**{{skillDiscoveryInstructions}}
|
|
47
47
|
3. Explore relevant code for this slice's scope. For targeted exploration, use `rg`, `find`, and reads. For broad or unfamiliar subsystems, use `scout` to map the relevant area first.
|
|
48
48
|
4. Use `resolve_library` / `get_library_docs` for unfamiliar libraries — skip this for libraries already used in the codebase
|
|
49
|
-
5.
|
|
50
|
-
6.
|
|
49
|
+
5. **Web search budget:** You have a limited budget of web searches (max ~15 per session). Use them strategically — prefer `resolve_library` / `get_library_docs` for library documentation. Do NOT repeat the same or similar queries. If a search didn't find what you need, rephrase once or move on. Target 3-5 total web searches for a typical research unit.
|
|
50
|
+
6. Use the **Research** output template from the inlined context above — include only sections that have real content. The template is already inlined above; do NOT attempt to read any template file from disk (there is no `templates/SLICE-RESEARCH.md` — the correct template is already present in this prompt).
|
|
51
|
+
7. Write `{{outputPath}}`
|
|
51
52
|
|
|
52
53
|
The slice directory already exists at `{{slicePath}}/`. Do NOT mkdir — just write the file.
|
|
53
54
|
|
|
@@ -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.
|
|
179
|
-
//
|
|
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
|
|
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 {
|
|
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/
|
|
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;
|
|
@@ -11,6 +11,15 @@ export const BRAVE_TOOL_NAMES = ["search-the-web", "search_and_read"];
|
|
|
11
11
|
export const CUSTOM_SEARCH_TOOL_NAMES = ["search-the-web", "search_and_read", "google_search"];
|
|
12
12
|
/** Thinking block types that require signature validation by the API */
|
|
13
13
|
const THINKING_TYPES = new Set(["thinking", "redacted_thinking"]);
|
|
14
|
+
/**
|
|
15
|
+
* Maximum number of native web searches allowed per session (agent unit).
|
|
16
|
+
* The Anthropic API's `max_uses` is per-request — it resets on each API call.
|
|
17
|
+
* When `pause_turn` triggers a resubmit, the model gets a fresh budget.
|
|
18
|
+
* This session-level cap prevents unbounded search accumulation (#1309).
|
|
19
|
+
*
|
|
20
|
+
* 15 = 3 full turns of 5 searches each — generous for research, but bounded.
|
|
21
|
+
*/
|
|
22
|
+
export const MAX_NATIVE_SEARCHES_PER_SESSION = 15;
|
|
14
23
|
/** When true, skip native web search injection and keep Brave/custom tools active on Anthropic. */
|
|
15
24
|
export function preferBraveSearch() {
|
|
16
25
|
// preferences.md takes priority over env var
|
|
@@ -57,6 +66,10 @@ export function stripThinkingFromHistory(messages) {
|
|
|
57
66
|
export function registerNativeSearchHooks(pi) {
|
|
58
67
|
let isAnthropicProvider = false;
|
|
59
68
|
let modelSelectFired = false;
|
|
69
|
+
// Session-level native search counter (#1309).
|
|
70
|
+
// Tracks cumulative web_search_tool_result blocks across all turns in a session.
|
|
71
|
+
// Reset on session_start. Used to compute remaining budget for max_uses.
|
|
72
|
+
let sessionSearchCount = 0;
|
|
60
73
|
// Track provider changes via model selection — also handles diagnostics
|
|
61
74
|
// since model_select fires AFTER session_start and knows the provider.
|
|
62
75
|
pi.on("model_select", async (event, ctx) => {
|
|
@@ -135,18 +148,46 @@ export function registerNativeSearchHooks(pi) {
|
|
|
135
148
|
// the model and causes it to pick custom tools which can fail with network errors.
|
|
136
149
|
tools = tools.filter((t) => !CUSTOM_SEARCH_TOOL_NAMES.includes(t.name));
|
|
137
150
|
payload.tools = tools;
|
|
151
|
+
// ── Session-level search budget (#1309) ──────────────────────────────
|
|
152
|
+
// Count web_search_tool_result blocks in the conversation history to
|
|
153
|
+
// determine how many native searches have already been used this session.
|
|
154
|
+
// The Anthropic API's max_uses resets per request, so without this guard,
|
|
155
|
+
// pause_turn → resubmit cycles allow unlimited total searches.
|
|
156
|
+
if (Array.isArray(messages)) {
|
|
157
|
+
let historySearchCount = 0;
|
|
158
|
+
for (const msg of messages) {
|
|
159
|
+
const content = msg.content;
|
|
160
|
+
if (!Array.isArray(content))
|
|
161
|
+
continue;
|
|
162
|
+
for (const block of content) {
|
|
163
|
+
if (block?.type === "web_search_tool_result") {
|
|
164
|
+
historySearchCount++;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Sync counter from history (handles session restore / context replay)
|
|
169
|
+
sessionSearchCount = historySearchCount;
|
|
170
|
+
}
|
|
171
|
+
const remaining = Math.max(0, MAX_NATIVE_SEARCHES_PER_SESSION - sessionSearchCount);
|
|
172
|
+
if (remaining <= 0) {
|
|
173
|
+
// Budget exhausted — don't inject the search tool at all.
|
|
174
|
+
// The model will proceed without web search capability.
|
|
175
|
+
return payload;
|
|
176
|
+
}
|
|
138
177
|
tools.push({
|
|
139
178
|
type: "web_search_20250305",
|
|
140
179
|
name: "web_search",
|
|
141
|
-
// Cap
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
max_uses: 5,
|
|
180
|
+
// Cap per-request searches to the lesser of 5 (per-turn cap) or the
|
|
181
|
+
// remaining session budget (#1309). This prevents the model from
|
|
182
|
+
// consuming unlimited searches via pause_turn → resubmit cycles.
|
|
183
|
+
max_uses: Math.min(5, remaining),
|
|
145
184
|
});
|
|
146
185
|
return payload;
|
|
147
186
|
});
|
|
148
187
|
// Basic startup diagnostics — provider-specific info comes from model_select
|
|
149
188
|
pi.on("session_start", async (_event, ctx) => {
|
|
189
|
+
// Reset session-level search budget (#1309)
|
|
190
|
+
sessionSearchCount = 0;
|
|
150
191
|
const hasBrave = !!process.env.BRAVE_API_KEY;
|
|
151
192
|
const hasJina = !!process.env.JINA_API_KEY;
|
|
152
193
|
const hasAnswers = !!process.env.BRAVE_ANSWERS_KEY;
|
|
@@ -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
|
+
}
|
|
@@ -5,9 +5,14 @@
|
|
|
5
5
|
* Terminals that lack this support silently swallow the key combos.
|
|
6
6
|
*/
|
|
7
7
|
const UNSUPPORTED_TERMS = ["apple_terminal", "warpterm"];
|
|
8
|
+
export function isCmuxTerminal(env = process.env) {
|
|
9
|
+
return Boolean(env.CMUX_WORKSPACE_ID && env.CMUX_SURFACE_ID);
|
|
10
|
+
}
|
|
8
11
|
export function supportsCtrlAltShortcuts() {
|
|
9
12
|
const term = (process.env.TERM_PROGRAM || "").toLowerCase();
|
|
10
13
|
const jetbrains = (process.env.TERMINAL_EMULATOR || "").toLowerCase().includes("jetbrains");
|
|
14
|
+
if (isCmuxTerminal())
|
|
15
|
+
return true;
|
|
11
16
|
return !UNSUPPORTED_TERMS.some((t) => term.includes(t)) && !jetbrains;
|
|
12
17
|
}
|
|
13
18
|
/**
|