@opengsd/gsd-pi 1.0.2-dev.5961fbf → 1.0.2-dev.5f7864c
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 +63 -12
- package/dist/onboarding.js +22 -3
- package/dist/resource-loader.d.ts +2 -0
- package/dist/resource-loader.js +18 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/context7/index.js +12 -2
- package/dist/resources/extensions/get-secrets-from-user.js +16 -16
- package/dist/resources/extensions/google-cli/index.js +30 -0
- package/dist/resources/extensions/google-cli/models.js +55 -0
- package/dist/resources/extensions/google-cli/package.json +11 -0
- package/dist/resources/extensions/google-cli/readiness.js +12 -0
- package/dist/resources/extensions/google-cli/stream-adapter.js +191 -0
- package/dist/resources/extensions/gsd/auto/session.js +3 -0
- package/dist/resources/extensions/gsd/auto-start.js +232 -49
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +4 -3
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -15
- package/dist/resources/extensions/gsd/closeout-recovery.js +7 -1
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +9 -1
- package/dist/resources/extensions/gsd/commands-handlers.js +3 -0
- package/dist/resources/extensions/gsd/commands-usage.js +105 -1
- package/dist/resources/extensions/gsd/config-overlay.js +20 -14
- package/dist/resources/extensions/gsd/context-overlay.js +22 -16
- package/dist/resources/extensions/gsd/dashboard-overlay.js +10 -23
- package/dist/resources/extensions/gsd/doctor-providers.js +54 -24
- package/dist/resources/extensions/gsd/git-conflict-state.js +26 -1
- package/dist/resources/extensions/gsd/guided-flow.js +1 -1
- package/dist/resources/extensions/gsd/key-manager.js +45 -13
- package/dist/resources/extensions/gsd/notification-overlay.js +8 -9
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +15 -13
- package/dist/resources/extensions/gsd/prompt-loader.js +2 -0
- package/dist/resources/extensions/gsd/prompts/discuss.md +4 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
- package/dist/resources/extensions/gsd/queue-reorder-ui.js +28 -18
- package/dist/resources/extensions/gsd/tools/complete-task.js +9 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +40 -1
- package/dist/resources/extensions/gsd/tui/render-kit.js +51 -0
- package/dist/resources/extensions/gsd/vision-ask.js +22 -0
- package/dist/resources/extensions/gsd/visualizer-overlay.js +8 -36
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +24 -3
- package/dist/resources/extensions/search-the-web/native-search.js +57 -8
- package/dist/resources/extensions/shared/confirm-ui.js +9 -6
- package/dist/resources/extensions/shared/dialog-frame.js +42 -0
- package/dist/resources/extensions/shared/interview-ui.js +42 -30
- package/dist/resources/extensions/shared/next-action-ui.js +6 -6
- package/dist/resources/shared/package-manager-detection.js +36 -0
- package/dist/update-check.d.ts +6 -2
- package/dist/update-check.js +7 -3
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +6 -6
- package/dist/web/standalone/.next/server/chunks/1834.js +2 -2
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.d.ts +12 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.js +45 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.d.ts +3 -2
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.js +11 -11
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.d.ts +3 -3
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.js +13 -11
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.d.ts +3 -3
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.js +12 -10
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/index.d.ts +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/index.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/index.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.d.ts +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.js +2 -2
- package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.d.ts +6 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.js +9 -6
- package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts +3 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js +144 -2
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.js +2 -14
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/workflow-tools.js +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +13 -13
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +57 -17
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +64 -28
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +50 -0
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +2 -0
- 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 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +1 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/scripts/install/detect-existing.js +17 -3
- package/scripts/install/npm-global.js +103 -33
- package/scripts/install.js +1 -0
- package/src/resources/extensions/context7/index.ts +15 -2
- package/src/resources/extensions/get-secrets-from-user.ts +17 -16
- package/src/resources/extensions/google-cli/index.ts +34 -0
- package/src/resources/extensions/google-cli/models.ts +57 -0
- package/src/resources/extensions/google-cli/package.json +11 -0
- package/src/resources/extensions/google-cli/readiness.ts +15 -0
- package/src/resources/extensions/google-cli/stream-adapter.ts +245 -0
- package/src/resources/extensions/gsd/auto/session.ts +3 -0
- package/src/resources/extensions/gsd/auto-start.ts +307 -56
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +4 -3
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -15
- package/src/resources/extensions/gsd/closeout-recovery.ts +6 -1
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -1
- package/src/resources/extensions/gsd/commands-handlers.ts +2 -0
- package/src/resources/extensions/gsd/commands-usage.ts +110 -5
- package/src/resources/extensions/gsd/config-overlay.ts +19 -16
- package/src/resources/extensions/gsd/context-overlay.ts +24 -19
- package/src/resources/extensions/gsd/dashboard-overlay.ts +14 -27
- package/src/resources/extensions/gsd/doctor-providers.ts +55 -27
- package/src/resources/extensions/gsd/git-conflict-state.ts +25 -1
- package/src/resources/extensions/gsd/guided-flow.ts +1 -1
- package/src/resources/extensions/gsd/key-manager.ts +57 -14
- package/src/resources/extensions/gsd/notification-overlay.ts +12 -11
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +16 -12
- package/src/resources/extensions/gsd/prompt-loader.ts +2 -0
- package/src/resources/extensions/gsd/prompts/discuss.md +4 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
- package/src/resources/extensions/gsd/queue-reorder-ui.ts +29 -20
- package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +436 -0
- package/src/resources/extensions/gsd/tests/closeout-recovery.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/commands-context.test.ts +5 -3
- package/src/resources/extensions/gsd/tests/commands-dispatcher-workspace-git.test.ts +15 -2
- package/src/resources/extensions/gsd/tests/commands-usage.test.ts +97 -0
- package/src/resources/extensions/gsd/tests/context-chart.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/discuss-prompt.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +105 -0
- package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/key-manager.test.ts +23 -4
- package/src/resources/extensions/gsd/tests/notification-overlay.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +70 -10
- package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/queue-reorder-ui.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/show-config-command.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +13 -2
- package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +24 -1
- package/src/resources/extensions/gsd/tests/tui-border-assertions.ts +28 -0
- package/src/resources/extensions/gsd/tests/tui-render-kit.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/vision-ask.test.ts +23 -0
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +60 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/workspace-git-preflight.test.ts +16 -1
- package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +28 -0
- package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +45 -1
- package/src/resources/extensions/gsd/tools/complete-task.ts +9 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +56 -4
- package/src/resources/extensions/gsd/tui/render-kit.ts +82 -0
- package/src/resources/extensions/gsd/vision-ask.ts +28 -0
- package/src/resources/extensions/gsd/visualizer-overlay.ts +12 -40
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +37 -2
- package/src/resources/extensions/search-the-web/native-search.ts +60 -8
- package/src/resources/extensions/shared/confirm-ui.ts +8 -12
- package/src/resources/extensions/shared/dialog-frame.ts +71 -0
- package/src/resources/extensions/shared/interview-ui.ts +43 -42
- package/src/resources/extensions/shared/next-action-ui.ts +6 -6
- package/src/resources/extensions/shared/tests/confirm-ui.test.ts +57 -0
- package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +163 -0
- package/src/resources/extensions/shared/tests/next-action-ui-hasui.test.ts +55 -0
- package/src/resources/shared/package-manager-detection.ts +39 -0
- /package/dist/web/standalone/.next/static/{spUYLkQXoHJyxYOMH9VQy → IjxvcC7sl_MHNKXsUZrAy}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{spUYLkQXoHJyxYOMH9VQy → IjxvcC7sl_MHNKXsUZrAy}/_ssgManifest.js +0 -0
|
@@ -52,6 +52,103 @@ function makeRepoWithUnmergedCompletedMilestone(): string {
|
|
|
52
52
|
return base;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
function makeRepoWithStrandedActiveMilestone(options: { deepPlanning?: boolean } = {}): string {
|
|
56
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-stranded-bootstrap-"));
|
|
57
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
58
|
+
writeFileSync(
|
|
59
|
+
join(base, ".gsd", "PREFERENCES.md"),
|
|
60
|
+
options.deepPlanning
|
|
61
|
+
? "---\nplanning_depth: deep\ngit:\n isolation: \"none\"\n---\n"
|
|
62
|
+
: "---\ngit:\n isolation: \"none\"\n---\n",
|
|
63
|
+
);
|
|
64
|
+
runGit(base, ["init"]);
|
|
65
|
+
runGit(base, ["config", "user.email", "test@test.com"]);
|
|
66
|
+
runGit(base, ["config", "user.name", "Test"]);
|
|
67
|
+
writeFileSync(join(base, "README.md"), "# test\n");
|
|
68
|
+
runGit(base, ["add", "-A"]);
|
|
69
|
+
runGit(base, ["commit", "-m", "init"]);
|
|
70
|
+
runGit(base, ["branch", "-M", "main"]);
|
|
71
|
+
|
|
72
|
+
runGit(base, ["checkout", "-b", "milestone/M001"]);
|
|
73
|
+
writeFileSync(join(base, "m001.txt"), "in-progress stranded work\n");
|
|
74
|
+
runGit(base, ["add", "-A"]);
|
|
75
|
+
runGit(base, ["commit", "-m", "feat: M001 in progress"]);
|
|
76
|
+
runGit(base, ["checkout", "main"]);
|
|
77
|
+
|
|
78
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
79
|
+
insertMilestone({ id: "M001", title: "Active milestone", status: "active" });
|
|
80
|
+
closeDatabase();
|
|
81
|
+
|
|
82
|
+
return base;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function makeRepoWithMultipleStrandedMilestones(): string {
|
|
86
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-multiple-stranded-bootstrap-"));
|
|
87
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
88
|
+
mkdirSync(join(base, ".gsd", "milestones", "M002"), { recursive: true });
|
|
89
|
+
writeFileSync(
|
|
90
|
+
join(base, ".gsd", "PREFERENCES.md"),
|
|
91
|
+
"---\ngit:\n isolation: \"none\"\n---\n",
|
|
92
|
+
);
|
|
93
|
+
runGit(base, ["init"]);
|
|
94
|
+
runGit(base, ["config", "user.email", "test@test.com"]);
|
|
95
|
+
runGit(base, ["config", "user.name", "Test"]);
|
|
96
|
+
writeFileSync(join(base, "README.md"), "# test\n");
|
|
97
|
+
runGit(base, ["add", "-A"]);
|
|
98
|
+
runGit(base, ["commit", "-m", "init"]);
|
|
99
|
+
runGit(base, ["branch", "-M", "main"]);
|
|
100
|
+
|
|
101
|
+
runGit(base, ["checkout", "-b", "milestone/M001"]);
|
|
102
|
+
writeFileSync(join(base, "m001.txt"), "active stranded work\n");
|
|
103
|
+
runGit(base, ["add", "-A"]);
|
|
104
|
+
runGit(base, ["commit", "-m", "feat: M001 in progress"]);
|
|
105
|
+
runGit(base, ["checkout", "main"]);
|
|
106
|
+
|
|
107
|
+
runGit(base, ["checkout", "-b", "milestone/M002"]);
|
|
108
|
+
writeFileSync(join(base, "m002.txt"), "additional stranded work\n");
|
|
109
|
+
runGit(base, ["add", "-A"]);
|
|
110
|
+
runGit(base, ["commit", "-m", "feat: M002 in progress"]);
|
|
111
|
+
runGit(base, ["checkout", "main"]);
|
|
112
|
+
|
|
113
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
114
|
+
insertMilestone({ id: "M001", title: "Active milestone", status: "active" });
|
|
115
|
+
insertMilestone({ id: "M002", title: "Pending milestone", status: "pending" });
|
|
116
|
+
closeDatabase();
|
|
117
|
+
|
|
118
|
+
return base;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function makeRepoWithRecoveredCleanupAndStrandedMismatch(): string {
|
|
122
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-headless-stranded-bootstrap-"));
|
|
123
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
124
|
+
mkdirSync(join(base, ".gsd", "milestones", "M002"), { recursive: true });
|
|
125
|
+
writeFileSync(
|
|
126
|
+
join(base, ".gsd", "PREFERENCES.md"),
|
|
127
|
+
"---\ngit:\n isolation: \"none\"\n---\n",
|
|
128
|
+
);
|
|
129
|
+
runGit(base, ["init"]);
|
|
130
|
+
runGit(base, ["config", "user.email", "test@test.com"]);
|
|
131
|
+
runGit(base, ["config", "user.name", "Test"]);
|
|
132
|
+
writeFileSync(join(base, "README.md"), "# test\n");
|
|
133
|
+
runGit(base, ["add", "-A"]);
|
|
134
|
+
runGit(base, ["commit", "-m", "init"]);
|
|
135
|
+
runGit(base, ["branch", "-M", "main"]);
|
|
136
|
+
|
|
137
|
+
runGit(base, ["branch", "milestone/M001"]);
|
|
138
|
+
runGit(base, ["checkout", "-b", "milestone/M002"]);
|
|
139
|
+
writeFileSync(join(base, "m002.txt"), "in-progress stranded work\n");
|
|
140
|
+
runGit(base, ["add", "-A"]);
|
|
141
|
+
runGit(base, ["commit", "-m", "feat: M002 in progress"]);
|
|
142
|
+
runGit(base, ["checkout", "main"]);
|
|
143
|
+
|
|
144
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
145
|
+
insertMilestone({ id: "M001", title: "Completed milestone", status: "complete" });
|
|
146
|
+
insertMilestone({ id: "M002", title: "Stranded milestone", status: "active" });
|
|
147
|
+
closeDatabase();
|
|
148
|
+
|
|
149
|
+
return base;
|
|
150
|
+
}
|
|
151
|
+
|
|
55
152
|
function makeCtx(notifications: Array<{ message: string; level?: string }>) {
|
|
56
153
|
const model = { provider: "claude-code", id: "claude-sonnet-4-6", contextWindow: 128000 };
|
|
57
154
|
return {
|
|
@@ -159,3 +256,342 @@ test("bootstrap aborts before starting next milestone when completed orphan merg
|
|
|
159
256
|
rmSync(base, { recursive: true, force: true });
|
|
160
257
|
}
|
|
161
258
|
});
|
|
259
|
+
|
|
260
|
+
test("headless bootstrap checks stranded work before recovered-complete shortcut", async () => {
|
|
261
|
+
const base = makeRepoWithRecoveredCleanupAndStrandedMismatch();
|
|
262
|
+
const previousCwd = process.cwd();
|
|
263
|
+
const previousHeadless = process.env.GSD_HEADLESS;
|
|
264
|
+
const previousParallelWorker = process.env.GSD_PARALLEL_WORKER;
|
|
265
|
+
const previousMilestoneLock = process.env.GSD_MILESTONE_LOCK;
|
|
266
|
+
const s = new AutoSession();
|
|
267
|
+
const notifications: Array<{ message: string; level?: string }> = [];
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
process.env.GSD_HEADLESS = "1";
|
|
271
|
+
process.env.GSD_PARALLEL_WORKER = "1";
|
|
272
|
+
process.env.GSD_MILESTONE_LOCK = "M001";
|
|
273
|
+
|
|
274
|
+
const ready = await bootstrapAutoSession(
|
|
275
|
+
s,
|
|
276
|
+
makeCtx(notifications) as any,
|
|
277
|
+
{
|
|
278
|
+
getThinkingLevel: () => "medium",
|
|
279
|
+
getActiveTools: () => [],
|
|
280
|
+
events: { emit: () => {} },
|
|
281
|
+
} as any,
|
|
282
|
+
base,
|
|
283
|
+
false,
|
|
284
|
+
false,
|
|
285
|
+
{
|
|
286
|
+
shouldUseWorktreeIsolation: () => false,
|
|
287
|
+
registerSigtermHandler: () => {},
|
|
288
|
+
registerAutoWorkerForSession: () => {},
|
|
289
|
+
lockBase: () => base,
|
|
290
|
+
buildLifecycle: () => ({
|
|
291
|
+
adoptSessionRoot: (sessionBase: string, originalBase?: string) => {
|
|
292
|
+
s.basePath = sessionBase;
|
|
293
|
+
if (originalBase !== undefined) {
|
|
294
|
+
s.originalBasePath = originalBase;
|
|
295
|
+
} else if (!s.originalBasePath) {
|
|
296
|
+
s.originalBasePath = sessionBase;
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
enterMilestone: () => ({ ok: true, mode: "none", path: base }),
|
|
300
|
+
adoptOrphanWorktree: <T extends { merged: boolean }>(
|
|
301
|
+
_mid: string,
|
|
302
|
+
_base: string,
|
|
303
|
+
run: () => T,
|
|
304
|
+
): T => run(),
|
|
305
|
+
}) as any,
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
classification: "none",
|
|
309
|
+
lock: null,
|
|
310
|
+
pausedSession: null,
|
|
311
|
+
state: null,
|
|
312
|
+
recovery: null,
|
|
313
|
+
recoveryPrompt: null,
|
|
314
|
+
recoveryToolCallCount: 0,
|
|
315
|
+
artifactSatisfied: false,
|
|
316
|
+
hasResumableDiskState: false,
|
|
317
|
+
isBootstrapCrash: false,
|
|
318
|
+
},
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
const messages = notifications.map((entry) => entry.message).join("\n");
|
|
322
|
+
assert.equal(ready, false);
|
|
323
|
+
assert.match(messages, /Stranded work for M002 blocks auto-mode/);
|
|
324
|
+
assert.doesNotMatch(messages, /all milestones complete/);
|
|
325
|
+
} finally {
|
|
326
|
+
if (previousHeadless === undefined) {
|
|
327
|
+
delete process.env.GSD_HEADLESS;
|
|
328
|
+
} else {
|
|
329
|
+
process.env.GSD_HEADLESS = previousHeadless;
|
|
330
|
+
}
|
|
331
|
+
if (previousParallelWorker === undefined) {
|
|
332
|
+
delete process.env.GSD_PARALLEL_WORKER;
|
|
333
|
+
} else {
|
|
334
|
+
process.env.GSD_PARALLEL_WORKER = previousParallelWorker;
|
|
335
|
+
}
|
|
336
|
+
if (previousMilestoneLock === undefined) {
|
|
337
|
+
delete process.env.GSD_MILESTONE_LOCK;
|
|
338
|
+
} else {
|
|
339
|
+
process.env.GSD_MILESTONE_LOCK = previousMilestoneLock;
|
|
340
|
+
}
|
|
341
|
+
try {
|
|
342
|
+
closeDatabase();
|
|
343
|
+
} catch {}
|
|
344
|
+
process.chdir(previousCwd);
|
|
345
|
+
rmSync(base, { recursive: true, force: true });
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test("bootstrap blocks active stranded recovery when another open milestone also has stranded work", async () => {
|
|
350
|
+
const base = makeRepoWithMultipleStrandedMilestones();
|
|
351
|
+
const previousCwd = process.cwd();
|
|
352
|
+
const s = new AutoSession();
|
|
353
|
+
const adoptCalls: string[] = [];
|
|
354
|
+
const notifications: Array<{ message: string; level?: string }> = [];
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
const ready = await bootstrapAutoSession(
|
|
358
|
+
s,
|
|
359
|
+
makeCtx(notifications) as any,
|
|
360
|
+
{
|
|
361
|
+
getThinkingLevel: () => "medium",
|
|
362
|
+
getActiveTools: () => [],
|
|
363
|
+
events: { emit: () => {} },
|
|
364
|
+
} as any,
|
|
365
|
+
base,
|
|
366
|
+
false,
|
|
367
|
+
false,
|
|
368
|
+
{
|
|
369
|
+
shouldUseWorktreeIsolation: () => false,
|
|
370
|
+
registerSigtermHandler: () => {},
|
|
371
|
+
registerAutoWorkerForSession: () => {},
|
|
372
|
+
lockBase: () => base,
|
|
373
|
+
buildLifecycle: () => ({
|
|
374
|
+
adoptSessionRoot: (sessionBase: string, originalBase?: string) => {
|
|
375
|
+
s.basePath = sessionBase;
|
|
376
|
+
if (originalBase !== undefined) {
|
|
377
|
+
s.originalBasePath = originalBase;
|
|
378
|
+
} else if (!s.originalBasePath) {
|
|
379
|
+
s.originalBasePath = sessionBase;
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
enterMilestone: () => ({ ok: true, mode: "none", path: base }),
|
|
383
|
+
adoptStrandedMilestone: (milestoneId: string) => {
|
|
384
|
+
adoptCalls.push(milestoneId);
|
|
385
|
+
return { ok: true, mode: "branch", path: base };
|
|
386
|
+
},
|
|
387
|
+
adoptOrphanWorktree: <T extends { merged: boolean }>(
|
|
388
|
+
_mid: string,
|
|
389
|
+
_base: string,
|
|
390
|
+
run: () => T,
|
|
391
|
+
): T => run(),
|
|
392
|
+
}) as any,
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
classification: "none",
|
|
396
|
+
lock: null,
|
|
397
|
+
pausedSession: null,
|
|
398
|
+
state: null,
|
|
399
|
+
recovery: null,
|
|
400
|
+
recoveryPrompt: null,
|
|
401
|
+
recoveryToolCallCount: 0,
|
|
402
|
+
artifactSatisfied: false,
|
|
403
|
+
hasResumableDiskState: false,
|
|
404
|
+
isBootstrapCrash: false,
|
|
405
|
+
},
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
const messages = notifications.map((entry) => entry.message).join("\n");
|
|
409
|
+
assert.equal(ready, false);
|
|
410
|
+
assert.deepEqual(adoptCalls, []);
|
|
411
|
+
assert.match(messages, /Stranded work for M002 blocks auto-mode before M001/);
|
|
412
|
+
} finally {
|
|
413
|
+
try {
|
|
414
|
+
closeDatabase();
|
|
415
|
+
} catch {}
|
|
416
|
+
process.chdir(previousCwd);
|
|
417
|
+
rmSync(base, { recursive: true, force: true });
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
test("bootstrap adopts stranded active branch even when isolation is none", async () => {
|
|
422
|
+
const base = makeRepoWithStrandedActiveMilestone();
|
|
423
|
+
const previousCwd = process.cwd();
|
|
424
|
+
const s = new AutoSession();
|
|
425
|
+
const adoptCalls: Array<{ milestoneId: string; mode: string }> = [];
|
|
426
|
+
const enterCalls: string[] = [];
|
|
427
|
+
const notifications: Array<{ message: string; level?: string }> = [];
|
|
428
|
+
|
|
429
|
+
try {
|
|
430
|
+
const ready = await bootstrapAutoSession(
|
|
431
|
+
s,
|
|
432
|
+
makeCtx(notifications) as any,
|
|
433
|
+
{
|
|
434
|
+
getThinkingLevel: () => "medium",
|
|
435
|
+
getActiveTools: () => [],
|
|
436
|
+
events: { emit: () => {} },
|
|
437
|
+
} as any,
|
|
438
|
+
base,
|
|
439
|
+
false,
|
|
440
|
+
false,
|
|
441
|
+
{
|
|
442
|
+
shouldUseWorktreeIsolation: () => false,
|
|
443
|
+
registerSigtermHandler: () => {},
|
|
444
|
+
registerAutoWorkerForSession: () => {},
|
|
445
|
+
lockBase: () => base,
|
|
446
|
+
buildLifecycle: () => ({
|
|
447
|
+
adoptSessionRoot: (sessionBase: string, originalBase?: string) => {
|
|
448
|
+
s.basePath = sessionBase;
|
|
449
|
+
if (originalBase !== undefined) {
|
|
450
|
+
s.originalBasePath = originalBase;
|
|
451
|
+
} else if (!s.originalBasePath) {
|
|
452
|
+
s.originalBasePath = sessionBase;
|
|
453
|
+
}
|
|
454
|
+
},
|
|
455
|
+
enterMilestone: (milestoneId: string) => {
|
|
456
|
+
enterCalls.push(milestoneId);
|
|
457
|
+
return { ok: true, mode: "none", path: base };
|
|
458
|
+
},
|
|
459
|
+
adoptStrandedMilestone: (
|
|
460
|
+
milestoneId: string,
|
|
461
|
+
sessionBase: string,
|
|
462
|
+
_ctx: unknown,
|
|
463
|
+
opts: { mode: "worktree" | "branch" },
|
|
464
|
+
) => {
|
|
465
|
+
adoptCalls.push({ milestoneId, mode: opts.mode });
|
|
466
|
+
s.basePath = sessionBase;
|
|
467
|
+
s.originalBasePath = sessionBase;
|
|
468
|
+
s.strandedRecoveryIsolationMode = opts.mode;
|
|
469
|
+
return { ok: true, mode: opts.mode, path: sessionBase };
|
|
470
|
+
},
|
|
471
|
+
adoptOrphanWorktree: <T extends { merged: boolean }>(
|
|
472
|
+
_mid: string,
|
|
473
|
+
_base: string,
|
|
474
|
+
run: () => T,
|
|
475
|
+
): T => run(),
|
|
476
|
+
}) as any,
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
classification: "none",
|
|
480
|
+
lock: null,
|
|
481
|
+
pausedSession: null,
|
|
482
|
+
state: null,
|
|
483
|
+
recovery: null,
|
|
484
|
+
recoveryPrompt: null,
|
|
485
|
+
recoveryToolCallCount: 0,
|
|
486
|
+
artifactSatisfied: false,
|
|
487
|
+
hasResumableDiskState: false,
|
|
488
|
+
isBootstrapCrash: false,
|
|
489
|
+
},
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
assert.equal(ready, true);
|
|
493
|
+
assert.deepEqual(adoptCalls, [{ milestoneId: "M001", mode: "branch" }]);
|
|
494
|
+
assert.deepEqual(enterCalls, []);
|
|
495
|
+
assert.equal(s.currentMilestoneId, "M001");
|
|
496
|
+
assert.equal(s.strandedRecoveryIsolationMode, "branch");
|
|
497
|
+
assert.match(
|
|
498
|
+
notifications.map((entry) => entry.message).join("\n"),
|
|
499
|
+
/Recovering stranded work for M001/,
|
|
500
|
+
);
|
|
501
|
+
} finally {
|
|
502
|
+
try {
|
|
503
|
+
closeDatabase();
|
|
504
|
+
} catch {}
|
|
505
|
+
process.chdir(previousCwd);
|
|
506
|
+
rmSync(base, { recursive: true, force: true });
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
test("bootstrap adopts stranded active branch before deep project setup", async () => {
|
|
511
|
+
const base = makeRepoWithStrandedActiveMilestone({ deepPlanning: true });
|
|
512
|
+
const previousCwd = process.cwd();
|
|
513
|
+
const s = new AutoSession();
|
|
514
|
+
const adoptCalls: Array<{ milestoneId: string; mode: string }> = [];
|
|
515
|
+
const enterCalls: string[] = [];
|
|
516
|
+
const notifications: Array<{ message: string; level?: string }> = [];
|
|
517
|
+
|
|
518
|
+
try {
|
|
519
|
+
const ready = await bootstrapAutoSession(
|
|
520
|
+
s,
|
|
521
|
+
makeCtx(notifications) as any,
|
|
522
|
+
{
|
|
523
|
+
getThinkingLevel: () => "medium",
|
|
524
|
+
getActiveTools: () => [],
|
|
525
|
+
events: { emit: () => {} },
|
|
526
|
+
} as any,
|
|
527
|
+
base,
|
|
528
|
+
false,
|
|
529
|
+
false,
|
|
530
|
+
{
|
|
531
|
+
shouldUseWorktreeIsolation: () => false,
|
|
532
|
+
registerSigtermHandler: () => {},
|
|
533
|
+
registerAutoWorkerForSession: () => {},
|
|
534
|
+
lockBase: () => base,
|
|
535
|
+
buildLifecycle: () => ({
|
|
536
|
+
adoptSessionRoot: (sessionBase: string, originalBase?: string) => {
|
|
537
|
+
s.basePath = sessionBase;
|
|
538
|
+
if (originalBase !== undefined) {
|
|
539
|
+
s.originalBasePath = originalBase;
|
|
540
|
+
} else if (!s.originalBasePath) {
|
|
541
|
+
s.originalBasePath = sessionBase;
|
|
542
|
+
}
|
|
543
|
+
},
|
|
544
|
+
enterMilestone: (milestoneId: string) => {
|
|
545
|
+
enterCalls.push(milestoneId);
|
|
546
|
+
return { ok: true, mode: "none", path: base };
|
|
547
|
+
},
|
|
548
|
+
adoptStrandedMilestone: (
|
|
549
|
+
milestoneId: string,
|
|
550
|
+
sessionBase: string,
|
|
551
|
+
_ctx: unknown,
|
|
552
|
+
opts: { mode: "worktree" | "branch" },
|
|
553
|
+
) => {
|
|
554
|
+
adoptCalls.push({ milestoneId, mode: opts.mode });
|
|
555
|
+
s.basePath = sessionBase;
|
|
556
|
+
s.originalBasePath = sessionBase;
|
|
557
|
+
s.strandedRecoveryIsolationMode = opts.mode;
|
|
558
|
+
return { ok: true, mode: opts.mode, path: sessionBase };
|
|
559
|
+
},
|
|
560
|
+
adoptOrphanWorktree: <T extends { merged: boolean }>(
|
|
561
|
+
_mid: string,
|
|
562
|
+
_base: string,
|
|
563
|
+
run: () => T,
|
|
564
|
+
): T => run(),
|
|
565
|
+
}) as any,
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
classification: "none",
|
|
569
|
+
lock: null,
|
|
570
|
+
pausedSession: null,
|
|
571
|
+
state: null,
|
|
572
|
+
recovery: null,
|
|
573
|
+
recoveryPrompt: null,
|
|
574
|
+
recoveryToolCallCount: 0,
|
|
575
|
+
artifactSatisfied: false,
|
|
576
|
+
hasResumableDiskState: false,
|
|
577
|
+
isBootstrapCrash: false,
|
|
578
|
+
},
|
|
579
|
+
);
|
|
580
|
+
|
|
581
|
+
assert.equal(ready, true);
|
|
582
|
+
assert.deepEqual(adoptCalls, [{ milestoneId: "M001", mode: "branch" }]);
|
|
583
|
+
assert.deepEqual(enterCalls, []);
|
|
584
|
+
assert.equal(s.currentMilestoneId, "M001");
|
|
585
|
+
assert.equal(s.strandedRecoveryIsolationMode, "branch");
|
|
586
|
+
assert.match(
|
|
587
|
+
notifications.map((entry) => entry.message).join("\n"),
|
|
588
|
+
/Recovering stranded work for M001/,
|
|
589
|
+
);
|
|
590
|
+
} finally {
|
|
591
|
+
try {
|
|
592
|
+
closeDatabase();
|
|
593
|
+
} catch {}
|
|
594
|
+
process.chdir(previousCwd);
|
|
595
|
+
rmSync(base, { recursive: true, force: true });
|
|
596
|
+
}
|
|
597
|
+
});
|
|
@@ -9,6 +9,7 @@ import { tmpdir } from "node:os";
|
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
|
+
getCloseoutManualResolveBlocker,
|
|
12
13
|
listUnresolvedCloseoutFailures,
|
|
13
14
|
markLatestCloseoutFailureResolved,
|
|
14
15
|
retryLatestCloseoutFailure,
|
|
@@ -99,3 +100,17 @@ test("closeout manual resolve refuses a dirty worktree", () => {
|
|
|
99
100
|
rmSync(base, { recursive: true, force: true });
|
|
100
101
|
}
|
|
101
102
|
});
|
|
103
|
+
|
|
104
|
+
test("closeout manual resolve blocks non-git project roots without throwing", () => {
|
|
105
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-closeout-recovery-non-git-"));
|
|
106
|
+
try {
|
|
107
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
108
|
+
|
|
109
|
+
assert.equal(
|
|
110
|
+
getCloseoutManualResolveBlocker(base),
|
|
111
|
+
`Could not inspect git status in ${base}.`,
|
|
112
|
+
);
|
|
113
|
+
} finally {
|
|
114
|
+
rmSync(base, { recursive: true, force: true });
|
|
115
|
+
}
|
|
116
|
+
});
|
|
@@ -16,8 +16,34 @@ import assert from "node:assert/strict";
|
|
|
16
16
|
import { mkdirSync, writeFileSync, readFileSync, rmSync } from "node:fs";
|
|
17
17
|
import { join } from "node:path";
|
|
18
18
|
import { tmpdir } from "node:os";
|
|
19
|
+
import { visibleWidth } from "@gsd/pi-tui";
|
|
19
20
|
import type { SecretsManifest, SecretsManifestEntry } from "../types.ts";
|
|
20
21
|
|
|
22
|
+
const ANSI_PATTERN = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
|
|
23
|
+
|
|
24
|
+
function stripAnsi(text: string): string {
|
|
25
|
+
return text.replace(ANSI_PATTERN, "");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function assertFullOuterBorder(lines: string[], width: number): void {
|
|
29
|
+
assert.ok(lines.length >= 2, "dialog must include top and bottom borders");
|
|
30
|
+
|
|
31
|
+
for (const [index, line] of lines.entries()) {
|
|
32
|
+
assert.equal(visibleWidth(line), width, `line ${index} must fill dialog width`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const top = stripAnsi(lines[0] ?? "");
|
|
36
|
+
const bottom = stripAnsi(lines.at(-1) ?? "");
|
|
37
|
+
assert.match(top, /^╭.*╮$/, `top border missing full corners: ${top}`);
|
|
38
|
+
assert.match(bottom, /^╰.*╯$/, `bottom border missing full corners: ${bottom}`);
|
|
39
|
+
|
|
40
|
+
for (let index = 1; index < lines.length - 1; index++) {
|
|
41
|
+
const line = stripAnsi(lines[index] ?? "");
|
|
42
|
+
assert.match(line, /^[│├]/, `line ${index} missing left border: ${line}`);
|
|
43
|
+
assert.match(line, /[│┤]$/, `line ${index} missing right border: ${line}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
21
47
|
// Dynamic imports for files.ts functions to avoid cascading failure
|
|
22
48
|
// when paths.js isn't available (files.ts statically imports paths.js)
|
|
23
49
|
async function loadFilesExports(): Promise<{
|
|
@@ -300,6 +326,7 @@ test("showSecretsSummary: produces lines with correct status glyphs for each ent
|
|
|
300
326
|
|
|
301
327
|
assert.ok(renderFn, "render function should have been captured from factory");
|
|
302
328
|
const lines = renderFn!(80);
|
|
329
|
+
assertFullOuterBorder(lines, 80);
|
|
303
330
|
|
|
304
331
|
// Verify each key appears in the output
|
|
305
332
|
const output = lines.join("\n");
|
|
@@ -341,6 +368,7 @@ test("showSecretsSummary: existing keys shown with distinct status indicator", a
|
|
|
341
368
|
|
|
342
369
|
assert.ok(renderFn, "render function should have been captured");
|
|
343
370
|
const lines = renderFn!(80);
|
|
371
|
+
assertFullOuterBorder(lines, 80);
|
|
344
372
|
const output = lines.join("\n");
|
|
345
373
|
|
|
346
374
|
assert.ok(output.includes("NEW_KEY"), "should include NEW_KEY");
|
|
@@ -380,6 +408,7 @@ test("collectOneSecret: guidance lines appear in render output when guidance is
|
|
|
380
408
|
|
|
381
409
|
assert.ok(renderFn, "render function should have been captured");
|
|
382
410
|
const lines = renderFn!(80);
|
|
411
|
+
assertFullOuterBorder(lines, 80);
|
|
383
412
|
const output = lines.join("\n");
|
|
384
413
|
|
|
385
414
|
// Verify guidance steps appear in the output
|
|
@@ -417,6 +446,7 @@ test("collectOneSecret: guidance lines wrap long URLs instead of truncating", as
|
|
|
417
446
|
assert.ok(renderFn, "render function should have been captured");
|
|
418
447
|
// Render at narrow width to force wrapping
|
|
419
448
|
const lines = renderFn!(50);
|
|
449
|
+
assertFullOuterBorder(lines, 50);
|
|
420
450
|
const output = lines.join("\n");
|
|
421
451
|
|
|
422
452
|
// The full URL should be present (wrapped, not truncated)
|
|
@@ -449,6 +479,7 @@ test("collectOneSecret: no guidance provided — render output has no guidance s
|
|
|
449
479
|
|
|
450
480
|
assert.ok(renderFn, "render function should have been captured");
|
|
451
481
|
const lines = renderFn!(80);
|
|
482
|
+
assertFullOuterBorder(lines, 80);
|
|
452
483
|
const output = lines.join("\n");
|
|
453
484
|
|
|
454
485
|
// Should include the key name and hint but no numbered guidance steps
|
|
@@ -156,9 +156,11 @@ test("handleContext writes open reports under the command project root", async (
|
|
|
156
156
|
process.chdir(processProject);
|
|
157
157
|
try {
|
|
158
158
|
const binDir = mkdtempSync(join(tmpdir(), "gsd-context-bin-"));
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
for (const opener of ["open", "xdg-open"]) {
|
|
160
|
+
const openerPath = join(binDir, opener);
|
|
161
|
+
writeFileSync(openerPath, "#!/bin/sh\nexit 0\n");
|
|
162
|
+
chmodSync(openerPath, 0o755);
|
|
163
|
+
}
|
|
162
164
|
process.env.PATH = `${binDir}:${originalPath ?? ""}`;
|
|
163
165
|
|
|
164
166
|
const notifications: string[] = [];
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
|
|
4
4
|
import test from "node:test";
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
|
-
import { writeFileSync } from "node:fs";
|
|
6
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
|
|
9
9
|
import { getWorkspaceGitBlockMessageForBase } from "../workspace-git-guard.js";
|
|
10
|
-
import { cleanup, git, makeTempRepo } from "./test-utils.ts";
|
|
10
|
+
import { cleanup, git, makeTempDir, makeTempRepo } from "./test-utils.ts";
|
|
11
11
|
|
|
12
12
|
function seedProductConflict(base: string): void {
|
|
13
13
|
writeFileSync(join(base, "app.ts"), "root\n");
|
|
@@ -41,6 +41,19 @@ test("getWorkspaceGitBlockMessageForBase blocks auto when product conflicts rema
|
|
|
41
41
|
}
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
+
test("getWorkspaceGitBlockMessageForBase does not block project setup in non-git folders", async () => {
|
|
45
|
+
const base = makeTempDir("gsd-dispatch-ws-git-new-project-");
|
|
46
|
+
try {
|
|
47
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
48
|
+
|
|
49
|
+
assert.equal(await getWorkspaceGitBlockMessageForBase(base, ""), null);
|
|
50
|
+
assert.equal(await getWorkspaceGitBlockMessageForBase(base, "init"), null);
|
|
51
|
+
assert.equal(await getWorkspaceGitBlockMessageForBase(base, "new-project"), null);
|
|
52
|
+
} finally {
|
|
53
|
+
cleanup(base);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
44
57
|
test("getWorkspaceGitBlockMessageForBase allows doctor on product conflicts", async () => {
|
|
45
58
|
const base = makeTempRepo("gsd-dispatch-ws-git-doctor-");
|
|
46
59
|
try {
|