@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
|
@@ -235,12 +235,16 @@ test("gsd_task_complete — enrichment arrays are optional", () => {
|
|
|
235
235
|
"milestoneId",
|
|
236
236
|
"oneLiner",
|
|
237
237
|
"narrative",
|
|
238
|
-
"verification",
|
|
239
238
|
];
|
|
240
239
|
for (const field of coreRequired) {
|
|
241
240
|
assert.ok(required.has(field), `core field "${field}" must be required`);
|
|
242
241
|
}
|
|
243
242
|
|
|
243
|
+
assert.ok(
|
|
244
|
+
!required.has("verification"),
|
|
245
|
+
"verification must be optional at the schema layer so step-mode can recover when verificationEvidence is present",
|
|
246
|
+
);
|
|
247
|
+
|
|
244
248
|
// Enrichment fields must be optional
|
|
245
249
|
const enrichmentFields = [
|
|
246
250
|
"keyFiles",
|
|
@@ -272,6 +276,25 @@ test("gsd_task_complete — validates with only core params", () => {
|
|
|
272
276
|
assert.strictEqual(errors.length, 0, `Minimal params should validate but got errors: ${errors.join(", ")}`);
|
|
273
277
|
});
|
|
274
278
|
|
|
279
|
+
test("gsd_task_complete — accepts evidence-only verification at schema layer", () => {
|
|
280
|
+
const tool = getTool("gsd_task_complete");
|
|
281
|
+
assert.ok(tool, "gsd_task_complete must be registered");
|
|
282
|
+
|
|
283
|
+
const params = {
|
|
284
|
+
taskId: "T01",
|
|
285
|
+
sliceId: "S01",
|
|
286
|
+
milestoneId: "M001",
|
|
287
|
+
oneLiner: "Implemented the feature",
|
|
288
|
+
narrative: "Created the module and wired it up.",
|
|
289
|
+
verificationEvidence: [
|
|
290
|
+
{ command: "npm test", exitCode: 0, verdict: "pass", durationMs: 1234 },
|
|
291
|
+
],
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const errors = validateSchema(tool, params);
|
|
295
|
+
assert.strictEqual(errors.length, 0, `Evidence-only params should validate but got errors: ${errors.join(", ")}`);
|
|
296
|
+
});
|
|
297
|
+
|
|
275
298
|
// ─── gsd_complete_milestone: enrichment arrays must be optional ──────────────
|
|
276
299
|
|
|
277
300
|
test("gsd_complete_milestone — enrichment arrays are optional", () => {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
|
|
3
|
+
import { visibleWidth } from "@gsd/pi-tui";
|
|
4
|
+
|
|
5
|
+
const ANSI_PATTERN = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
|
|
6
|
+
|
|
7
|
+
function stripAnsi(text: string): string {
|
|
8
|
+
return text.replace(ANSI_PATTERN, "");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function assertFullOuterBorder(lines: string[], width: number): void {
|
|
12
|
+
assert.ok(lines.length >= 2, "dialog must include top and bottom borders");
|
|
13
|
+
|
|
14
|
+
for (const [index, line] of lines.entries()) {
|
|
15
|
+
assert.equal(visibleWidth(line), width, `line ${index} must fill dialog width`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const top = stripAnsi(lines[0] ?? "");
|
|
19
|
+
const bottom = stripAnsi(lines.at(-1) ?? "");
|
|
20
|
+
assert.match(top, /^[╭┌].*[╮┐]$/, `top border missing full corners: ${top}`);
|
|
21
|
+
assert.match(bottom, /^[╰└].*[╯┘]$/, `bottom border missing full corners: ${bottom}`);
|
|
22
|
+
|
|
23
|
+
for (let index = 1; index < lines.length - 1; index++) {
|
|
24
|
+
const line = stripAnsi(lines[index] ?? "");
|
|
25
|
+
assert.match(line, /^[│┃├]/, `line ${index} missing left border: ${line}`);
|
|
26
|
+
assert.match(line, /[│┃┤]$/, `line ${index} missing right border: ${line}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -5,8 +5,10 @@ import { describe, test } from "node:test";
|
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
6
|
|
|
7
7
|
import { visibleWidth } from "@gsd/pi-tui";
|
|
8
|
+
import { assertFullOuterBorder } from "./tui-border-assertions.ts";
|
|
8
9
|
import {
|
|
9
10
|
padRightVisible,
|
|
11
|
+
renderDialogFrame,
|
|
10
12
|
renderFrame,
|
|
11
13
|
renderKeyHints,
|
|
12
14
|
renderPanel,
|
|
@@ -60,6 +62,18 @@ describe("tui render kit", () => {
|
|
|
60
62
|
}
|
|
61
63
|
});
|
|
62
64
|
|
|
65
|
+
test("renderDialogFrame draws a full titled modal border with footer", () => {
|
|
66
|
+
const lines = renderDialogFrame(theme, "Dialog", ["row", "long ".repeat(40)], 40, {
|
|
67
|
+
footer: renderKeyHints(theme, ["esc close"], 36),
|
|
68
|
+
});
|
|
69
|
+
assertWidth(lines, 40);
|
|
70
|
+
assertFullOuterBorder(lines, 40);
|
|
71
|
+
assert.match(lines[0] ?? "", /^╭─ Dialog ─+╮$/);
|
|
72
|
+
assert.ok(lines.some((line) => line.startsWith("│") && line.endsWith("│")));
|
|
73
|
+
assert.ok(lines.some((line) => line.startsWith("├") && line.endsWith("┤")));
|
|
74
|
+
assert.match(lines.at(-1) ?? "", /^╰─+╯$/);
|
|
75
|
+
});
|
|
76
|
+
|
|
63
77
|
test("renderPanel stays within width and draws no vertical borders", () => {
|
|
64
78
|
for (const width of [3, 40, 80]) {
|
|
65
79
|
const lines = renderPanel(theme, "Title", ["row", "long ".repeat(40)], width);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { chooseVisionAskVariant, VISION_ASK_VARIANTS } from "../vision-ask.ts";
|
|
4
|
+
|
|
5
|
+
test("vision ask variants stay varied and conversational", () => {
|
|
6
|
+
assert.ok(VISION_ASK_VARIANTS.length >= 6, "keep enough openers to avoid repetition");
|
|
7
|
+
assert.equal(new Set(VISION_ASK_VARIANTS).size, VISION_ASK_VARIANTS.length, "openers should be unique");
|
|
8
|
+
|
|
9
|
+
for (const opener of VISION_ASK_VARIANTS) {
|
|
10
|
+
assert.ok(opener.length <= 72, `opener should stay short: ${opener}`);
|
|
11
|
+
assert.doesNotMatch(opener, /\n/, "opener should be a single line");
|
|
12
|
+
assert.doesNotMatch(opener, /\bstakeholders?|key success metrics?|business objectives?\b/i, "avoid corporate wording");
|
|
13
|
+
assert.notEqual(opener, "What's the vision?", "do not keep the old fixed opener in rotation");
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("chooseVisionAskVariant picks from the configured opener list", () => {
|
|
18
|
+
assert.equal(chooseVisionAskVariant(() => 0), VISION_ASK_VARIANTS[0]);
|
|
19
|
+
assert.equal(
|
|
20
|
+
chooseVisionAskVariant((exclusiveMax) => exclusiveMax - 1),
|
|
21
|
+
VISION_ASK_VARIANTS[VISION_ASK_VARIANTS.length - 1],
|
|
22
|
+
);
|
|
23
|
+
});
|
|
@@ -14,6 +14,7 @@ import { test } from "node:test";
|
|
|
14
14
|
import assert from "node:assert/strict";
|
|
15
15
|
|
|
16
16
|
import { GSDVisualizerOverlay, TAB_COUNT } from "../visualizer-overlay.ts";
|
|
17
|
+
import { assertFullOuterBorder } from "./tui-border-assertions.ts";
|
|
17
18
|
|
|
18
19
|
function makeTui() {
|
|
19
20
|
const renders: number[] = [];
|
|
@@ -50,7 +51,11 @@ test("overlay renders 10 tabs (Progress, Timeline, Deps, Metrics, Health, Agent,
|
|
|
50
51
|
overlay.loading = true; // body shows loading text, but tab bar renders regardless
|
|
51
52
|
|
|
52
53
|
// Use a very wide terminal so the tab bar is not truncated.
|
|
53
|
-
const
|
|
54
|
+
const rawLines = overlay.render(200);
|
|
55
|
+
assertFullOuterBorder(rawLines, 200);
|
|
56
|
+
const lines = rawLines.map(stripAnsi);
|
|
57
|
+
assert.match(lines[0] ?? "", /^╭─ GSD Visualizer /);
|
|
58
|
+
assert.match(lines.at(-1) ?? "", /^╰─+╯$/);
|
|
54
59
|
const tabBar = lines.find((l) => l.includes("Progress") && l.includes("Export"));
|
|
55
60
|
assert.ok(tabBar, `expected a tab-bar line containing all labels, got:\n${lines.slice(0, 5).join("\n")}`);
|
|
56
61
|
for (const label of ["Progress", "Timeline", "Deps", "Metrics", "Health", "Agent", "Changes", "Knowledge", "Captures", "Export"]) {
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
3
6
|
|
|
7
|
+
import { registerHooks } from "../bootstrap/register-hooks.ts";
|
|
8
|
+
import { GSD_WORKFLOW_MCP_SERVER_NAME } from "../mcp-project-config.ts";
|
|
4
9
|
import { prepareWorkflowMcpForProject, shouldAutoPrepareWorkflowMcp } from "../workflow-mcp-auto-prep.ts";
|
|
5
10
|
|
|
6
11
|
test("shouldAutoPrepareWorkflowMcp enables prep for externalCli local transport", () => {
|
|
@@ -74,3 +79,58 @@ test("prepareWorkflowMcpForProject warns with /gsd mcp init guidance when prep f
|
|
|
74
79
|
assert.equal(notifications[0].level, "warning");
|
|
75
80
|
assert.match(notifications[0].message, /Please run \/gsd mcp init \./);
|
|
76
81
|
});
|
|
82
|
+
|
|
83
|
+
test("before_agent_start auto-prepares project workflow MCP for Claude Code CLI", async (t) => {
|
|
84
|
+
const projectRoot = mkdtempSync(join(tmpdir(), "gsd-mcp-before-agent-"));
|
|
85
|
+
const originalCwd = process.cwd();
|
|
86
|
+
const notifications: string[] = [];
|
|
87
|
+
const handlers = new Map<string, Array<(event: any, ctx?: any) => Promise<any> | any>>();
|
|
88
|
+
const pi = {
|
|
89
|
+
on(event: string, handler: (event: any, ctx?: any) => Promise<any> | any) {
|
|
90
|
+
const existing = handlers.get(event) ?? [];
|
|
91
|
+
existing.push(handler);
|
|
92
|
+
handlers.set(event, existing);
|
|
93
|
+
},
|
|
94
|
+
getActiveTools: () => [],
|
|
95
|
+
getAllTools: () => [],
|
|
96
|
+
setActiveTools() {},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
t.after(() => {
|
|
100
|
+
process.chdir(originalCwd);
|
|
101
|
+
rmSync(projectRoot, { recursive: true, force: true });
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
process.chdir(projectRoot);
|
|
105
|
+
registerHooks(pi as any, []);
|
|
106
|
+
|
|
107
|
+
const beforeAgentStart = handlers.get("before_agent_start")?.[0];
|
|
108
|
+
assert.ok(beforeAgentStart, "before_agent_start hook should be registered");
|
|
109
|
+
|
|
110
|
+
await beforeAgentStart(
|
|
111
|
+
{ prompt: "hello", systemPrompt: "base" },
|
|
112
|
+
{
|
|
113
|
+
cwd: projectRoot,
|
|
114
|
+
model: { provider: "claude-code", baseUrl: "local://claude-code" },
|
|
115
|
+
modelRegistry: {
|
|
116
|
+
getProviderAuthMode: () => "externalCli",
|
|
117
|
+
isProviderRequestReady: () => true,
|
|
118
|
+
},
|
|
119
|
+
ui: {
|
|
120
|
+
notify(message: string) {
|
|
121
|
+
notifications.push(message);
|
|
122
|
+
},
|
|
123
|
+
setWidget() {},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const configPath = join(projectRoot, ".mcp.json");
|
|
129
|
+
assert.equal(existsSync(configPath), true, "Claude Code CLI turns should create project MCP config");
|
|
130
|
+
|
|
131
|
+
const parsed = JSON.parse(readFileSync(configPath, "utf-8")) as {
|
|
132
|
+
mcpServers?: Record<string, unknown>;
|
|
133
|
+
};
|
|
134
|
+
assert.ok(parsed.mcpServers?.[GSD_WORKFLOW_MCP_SERVER_NAME]);
|
|
135
|
+
assert.match(notifications.join("\n"), /Claude Code MCP prepared/);
|
|
136
|
+
});
|
|
@@ -148,6 +148,60 @@ test("executeTaskComplete coerces string verificationEvidence entries", async ()
|
|
|
148
148
|
}
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
+
test("executeTaskComplete derives missing verification from evidence", async () => {
|
|
152
|
+
const base = makeTmpBase();
|
|
153
|
+
try {
|
|
154
|
+
openTestDb(base);
|
|
155
|
+
const planDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
156
|
+
mkdirSync(planDir, { recursive: true });
|
|
157
|
+
writeFileSync(join(planDir, "S01-PLAN.md"), "# S01\n\n- [ ] **T01: Demo** `est:5m`\n");
|
|
158
|
+
|
|
159
|
+
const result = await inProjectDir(base, () => executeTaskComplete({
|
|
160
|
+
milestoneId: "M001",
|
|
161
|
+
sliceId: "S01",
|
|
162
|
+
taskId: "T01",
|
|
163
|
+
oneLiner: "Completed task",
|
|
164
|
+
narrative: "Did the work",
|
|
165
|
+
verificationEvidence: [
|
|
166
|
+
{ command: "npm test", exitCode: 0, verdict: "pass", durationMs: 1234 },
|
|
167
|
+
],
|
|
168
|
+
}, base));
|
|
169
|
+
|
|
170
|
+
assert.equal(result.details.operation, "complete_task");
|
|
171
|
+
const db = _getAdapter();
|
|
172
|
+
assert.ok(db, "DB should be open");
|
|
173
|
+
const row = db!.prepare(
|
|
174
|
+
"SELECT verification_result FROM tasks WHERE milestone_id = ? AND slice_id = ? AND id = ?",
|
|
175
|
+
).get("M001", "S01", "T01") as Record<string, unknown> | undefined;
|
|
176
|
+
|
|
177
|
+
assert.match(String(row?.verification_result), /Verification evidence recorded/);
|
|
178
|
+
assert.match(String(row?.verification_result), /`npm test` exited 0 \(pass\)/);
|
|
179
|
+
} finally {
|
|
180
|
+
closeDatabase();
|
|
181
|
+
cleanup(base);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("executeTaskComplete returns a tool error when verification cannot be derived", async () => {
|
|
186
|
+
const base = makeTmpBase();
|
|
187
|
+
try {
|
|
188
|
+
openTestDb(base);
|
|
189
|
+
const result = await inProjectDir(base, () => executeTaskComplete({
|
|
190
|
+
milestoneId: "M001",
|
|
191
|
+
sliceId: "S01",
|
|
192
|
+
taskId: "T01",
|
|
193
|
+
oneLiner: "Completed task",
|
|
194
|
+
narrative: "Did the work",
|
|
195
|
+
}, base));
|
|
196
|
+
|
|
197
|
+
assert.equal(result.isError, true);
|
|
198
|
+
assert.match(String(result.content[0]?.text), /verification is required/);
|
|
199
|
+
} finally {
|
|
200
|
+
closeDatabase();
|
|
201
|
+
cleanup(base);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
151
205
|
test("executeSliceComplete preserves omitted optional requirement arrays", async () => {
|
|
152
206
|
const base = makeTmpBase();
|
|
153
207
|
try {
|
|
@@ -9,7 +9,7 @@ import { join } from "node:path";
|
|
|
9
9
|
import { probeGitConflictState } from "../git-conflict-state.js";
|
|
10
10
|
import { ensureWorkspaceGitReadyForPath } from "../workspace-git-preflight.js";
|
|
11
11
|
import { isWorkspaceGitAllowedCommand } from "../workspace-git-guard.js";
|
|
12
|
-
import { cleanup, git, makeTempRepo } from "./test-utils.ts";
|
|
12
|
+
import { cleanup, git, makeTempDir, makeTempRepo } from "./test-utils.ts";
|
|
13
13
|
|
|
14
14
|
function seedGsdConflict(base: string): void {
|
|
15
15
|
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
@@ -60,6 +60,21 @@ test("probeGitConflictState reports clean repo", () => {
|
|
|
60
60
|
}
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
+
test("ensureWorkspaceGitReadyForPath allows fresh non-git project setup folders", async () => {
|
|
64
|
+
const base = makeTempDir("gsd-ws-git-non-repo-");
|
|
65
|
+
try {
|
|
66
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
67
|
+
|
|
68
|
+
const probe = probeGitConflictState(base);
|
|
69
|
+
assert.equal(probe.status, "clean");
|
|
70
|
+
|
|
71
|
+
const ready = await ensureWorkspaceGitReadyForPath(base);
|
|
72
|
+
assert.equal(ready.ok, true);
|
|
73
|
+
} finally {
|
|
74
|
+
cleanup(base);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
63
78
|
test("ensureWorkspaceGitReadyForPath auto-resolves .gsd/ conflicts", async () => {
|
|
64
79
|
const base = makeTempRepo("gsd-ws-git-heal-");
|
|
65
80
|
try {
|
|
@@ -212,6 +212,34 @@ test("enterMilestone returns ok:true mode:none when isolation disabled", () => {
|
|
|
212
212
|
assert.equal(s.basePath, "/project");
|
|
213
213
|
});
|
|
214
214
|
|
|
215
|
+
test("adoptStrandedMilestone forces branch recovery even when normal preferences differ", (t) => {
|
|
216
|
+
const previousCwd = process.cwd();
|
|
217
|
+
const base = makeGitRepoBase({ isolation: "worktree" });
|
|
218
|
+
t.after(() => cleanupRepoBase(base, previousCwd));
|
|
219
|
+
|
|
220
|
+
const s = makeSession({ basePath: base, originalBasePath: base });
|
|
221
|
+
const deps = makeDeps();
|
|
222
|
+
const ctx = makeCtx();
|
|
223
|
+
const lifecycle = new WorktreeLifecycle(s, deps);
|
|
224
|
+
|
|
225
|
+
const result = lifecycle.adoptStrandedMilestone("M001", base, ctx, {
|
|
226
|
+
mode: "branch",
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
assert.equal(result.ok, true, `expected ok:true, got: ${JSON.stringify(result)}`);
|
|
230
|
+
if (result.ok) {
|
|
231
|
+
assert.equal(result.mode, "branch");
|
|
232
|
+
assert.equal(result.path, base);
|
|
233
|
+
}
|
|
234
|
+
assert.equal(s.basePath, base);
|
|
235
|
+
assert.equal(s.strandedRecoveryIsolationMode, "branch");
|
|
236
|
+
const currentBranch = execFileSync("git", ["branch", "--show-current"], {
|
|
237
|
+
cwd: base,
|
|
238
|
+
encoding: "utf-8",
|
|
239
|
+
}).trim();
|
|
240
|
+
assert.equal(currentBranch, "milestone/M001");
|
|
241
|
+
});
|
|
242
|
+
|
|
215
243
|
test("enterMilestone returns ok:false reason:isolation-degraded when session degraded", () => {
|
|
216
244
|
const s = makeSession({ isolationDegraded: true });
|
|
217
245
|
const deps = makeDeps({ getIsolationMode: () => "branch" });
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import { test } from "node:test";
|
|
16
16
|
import assert from "node:assert/strict";
|
|
17
|
-
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
17
|
+
import { existsSync, mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
18
18
|
import { join } from "node:path";
|
|
19
19
|
import { tmpdir } from "node:os";
|
|
20
20
|
|
|
@@ -79,3 +79,47 @@ test("#2942: injected existsFn — milestones/ alone is enough", () => {
|
|
|
79
79
|
p === "/proj/.gsd" || p === "/proj/.gsd/milestones";
|
|
80
80
|
assert.equal(hasGsdBootstrapArtifacts("/proj/.gsd", existsFn), true);
|
|
81
81
|
});
|
|
82
|
+
|
|
83
|
+
test("bare /gsd routes zombie .gsd folders to project init before closeout/db checks", async (t) => {
|
|
84
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-zombie-bare-command-"));
|
|
85
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
86
|
+
mkdirSync(join(base, ".gsd", "runtime"), { recursive: true });
|
|
87
|
+
|
|
88
|
+
const previousCwd = process.cwd();
|
|
89
|
+
const previousGsdHome = process.env.GSD_HOME;
|
|
90
|
+
const previousProjectRoot = process.env.GSD_PROJECT_ROOT;
|
|
91
|
+
try {
|
|
92
|
+
process.chdir(base);
|
|
93
|
+
process.env.GSD_HOME = join(base, ".test-gsd-home");
|
|
94
|
+
delete process.env.GSD_PROJECT_ROOT;
|
|
95
|
+
|
|
96
|
+
const notifications: string[] = [];
|
|
97
|
+
const ctx = {
|
|
98
|
+
hasUI: false,
|
|
99
|
+
ui: {
|
|
100
|
+
notify: (content: unknown) => notifications.push(String(content)),
|
|
101
|
+
setStatus: () => {},
|
|
102
|
+
setWidget: () => {},
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
const { handleAutoCommand } = await import("../commands/handlers/auto.ts");
|
|
106
|
+
|
|
107
|
+
await handleAutoCommand("", ctx as any, {} as any);
|
|
108
|
+
|
|
109
|
+
assert.ok(
|
|
110
|
+
notifications.some((message) => message.includes("/gsd init did not start")),
|
|
111
|
+
"bare /gsd should route unbootstrapped zombie folders to the init wizard",
|
|
112
|
+
);
|
|
113
|
+
assert.equal(
|
|
114
|
+
existsSync(join(base, ".gsd", "gsd.db")),
|
|
115
|
+
false,
|
|
116
|
+
"bare /gsd should not create the project DB before init has bootstrapped .gsd/",
|
|
117
|
+
);
|
|
118
|
+
} finally {
|
|
119
|
+
process.chdir(previousCwd);
|
|
120
|
+
if (previousGsdHome === undefined) delete process.env.GSD_HOME;
|
|
121
|
+
else process.env.GSD_HOME = previousGsdHome;
|
|
122
|
+
if (previousProjectRoot === undefined) delete process.env.GSD_PROJECT_ROOT;
|
|
123
|
+
else process.env.GSD_PROJECT_ROOT = previousProjectRoot;
|
|
124
|
+
}
|
|
125
|
+
});
|
|
@@ -173,6 +173,15 @@ export async function handleCompleteTask(
|
|
|
173
173
|
if (!params.milestoneId || typeof params.milestoneId !== "string" || params.milestoneId.trim() === "") {
|
|
174
174
|
return { error: "milestoneId is required and must be a non-empty string" };
|
|
175
175
|
}
|
|
176
|
+
if (!params.oneLiner || typeof params.oneLiner !== "string" || params.oneLiner.trim() === "") {
|
|
177
|
+
return { error: "oneLiner is required and must be a non-empty string" };
|
|
178
|
+
}
|
|
179
|
+
if (!params.narrative || typeof params.narrative !== "string" || params.narrative.trim() === "") {
|
|
180
|
+
return { error: "narrative is required and must be a non-empty string" };
|
|
181
|
+
}
|
|
182
|
+
if (!params.verification || typeof params.verification !== "string" || params.verification.trim() === "") {
|
|
183
|
+
return { error: "verification is required and must be a non-empty string" };
|
|
184
|
+
}
|
|
176
185
|
|
|
177
186
|
const artifactBasePath = resolveCanonicalMilestoneRoot(basePath, params.milestoneId);
|
|
178
187
|
|
|
@@ -306,7 +306,7 @@ export interface TaskCompleteParams {
|
|
|
306
306
|
milestoneId: string;
|
|
307
307
|
oneLiner: string;
|
|
308
308
|
narrative: string;
|
|
309
|
-
verification
|
|
309
|
+
verification?: string;
|
|
310
310
|
deviations?: string;
|
|
311
311
|
knownIssues?: string;
|
|
312
312
|
keyFiles?: string[];
|
|
@@ -315,6 +315,40 @@ export interface TaskCompleteParams {
|
|
|
315
315
|
verificationEvidence?: VerificationEvidenceInput[];
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
+
type NormalizedVerificationEvidence = {
|
|
319
|
+
command: string;
|
|
320
|
+
exitCode: number;
|
|
321
|
+
verdict: string;
|
|
322
|
+
durationMs: number;
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
function normalizeVerificationEvidence(
|
|
326
|
+
evidence: VerificationEvidenceInput[] | undefined,
|
|
327
|
+
): NormalizedVerificationEvidence[] {
|
|
328
|
+
return (evidence ?? []).map((entry) =>
|
|
329
|
+
typeof entry === "string"
|
|
330
|
+
? { command: entry, exitCode: -1, verdict: "unknown (coerced from string)", durationMs: 0 }
|
|
331
|
+
: entry,
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function deriveVerificationSummary(
|
|
336
|
+
evidence: NormalizedVerificationEvidence[],
|
|
337
|
+
): string | null {
|
|
338
|
+
if (evidence.length === 0) return null;
|
|
339
|
+
|
|
340
|
+
const rendered = evidence.slice(0, 3).map((entry) => {
|
|
341
|
+
const command = entry.command.trim() || "(unspecified command)";
|
|
342
|
+
const verdict = entry.verdict.trim() || "recorded";
|
|
343
|
+
return `\`${command}\` exited ${entry.exitCode} (${verdict})`;
|
|
344
|
+
});
|
|
345
|
+
const suffix = evidence.length > rendered.length
|
|
346
|
+
? `; ${evidence.length - rendered.length} more check(s) recorded`
|
|
347
|
+
: "";
|
|
348
|
+
|
|
349
|
+
return `Verification evidence recorded: ${rendered.join("; ")}${suffix}.`;
|
|
350
|
+
}
|
|
351
|
+
|
|
318
352
|
export type CompleteMilestoneExecutorParams = Partial<CompleteMilestoneParams> & Record<string, unknown>;
|
|
319
353
|
export type SliceCompleteExecutorParams = CompleteSliceParams;
|
|
320
354
|
export type PlanMilestoneExecutorParams = PlanMilestoneParams;
|
|
@@ -350,9 +384,27 @@ export async function executeTaskComplete(
|
|
|
350
384
|
}
|
|
351
385
|
try {
|
|
352
386
|
const coerced = { ...params };
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
387
|
+
const verificationEvidence = normalizeVerificationEvidence(params.verificationEvidence);
|
|
388
|
+
coerced.verificationEvidence = verificationEvidence;
|
|
389
|
+
|
|
390
|
+
const verification = typeof params.verification === "string" ? params.verification.trim() : "";
|
|
391
|
+
if (verification.length === 0) {
|
|
392
|
+
const derived = deriveVerificationSummary(verificationEvidence);
|
|
393
|
+
if (derived) {
|
|
394
|
+
coerced.verification = derived;
|
|
395
|
+
} else if (params.blockerDiscovered === true) {
|
|
396
|
+
coerced.verification = "Not run: blocker discovered before verification.";
|
|
397
|
+
} else {
|
|
398
|
+
return {
|
|
399
|
+
content: [{
|
|
400
|
+
type: "text",
|
|
401
|
+
text: "Error completing task: verification is required unless verificationEvidence is provided or blockerDiscovered is true.",
|
|
402
|
+
}],
|
|
403
|
+
details: { operation: "complete_task", error: "verification_required" },
|
|
404
|
+
isError: true,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
}
|
|
356
408
|
|
|
357
409
|
const result = await handleCompleteTask(coerced as any, basePath);
|
|
358
410
|
if ("error" in result) {
|
|
@@ -151,3 +151,85 @@ export function renderFrame(
|
|
|
151
151
|
lines.push(border("╰" + "─".repeat(width - 2) + "╯"));
|
|
152
152
|
return lines.map((line) => safeLine(line, width, ""));
|
|
153
153
|
}
|
|
154
|
+
|
|
155
|
+
export interface DialogFrameOptions {
|
|
156
|
+
borderColor?: string;
|
|
157
|
+
paddingX?: number;
|
|
158
|
+
footer?: string | string[];
|
|
159
|
+
scroll?: {
|
|
160
|
+
offset: number;
|
|
161
|
+
visibleRows: number;
|
|
162
|
+
totalRows: number;
|
|
163
|
+
trackOffset?: number;
|
|
164
|
+
trackRows?: number;
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function renderTitledTopBorder(
|
|
169
|
+
theme: ThemeLike,
|
|
170
|
+
title: string,
|
|
171
|
+
width: number,
|
|
172
|
+
border: (text: string) => string,
|
|
173
|
+
): string {
|
|
174
|
+
const trimmedTitle = title.trim();
|
|
175
|
+
if (!trimmedTitle || width < 10) {
|
|
176
|
+
return border("╭" + "─".repeat(width - 2) + "╮");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const maxTitleWidth = Math.max(0, width - 7);
|
|
180
|
+
const safeTitle = safeLine(trimmedTitle, maxTitleWidth);
|
|
181
|
+
const fill = Math.max(0, width - visibleWidth(safeTitle) - 5);
|
|
182
|
+
return border("╭─ ") + theme.bold(theme.fg("accent", safeTitle)) + border(" " + "─".repeat(fill) + "╮");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function renderDialogFrame(
|
|
186
|
+
theme: ThemeLike,
|
|
187
|
+
title: string,
|
|
188
|
+
inner: string[],
|
|
189
|
+
width: number,
|
|
190
|
+
options: DialogFrameOptions = {},
|
|
191
|
+
): string[] {
|
|
192
|
+
if (width < 4) return inner.map((line) => safeLine(line, width));
|
|
193
|
+
|
|
194
|
+
const borderColor = options.borderColor ?? "borderAccent";
|
|
195
|
+
const paddingX = Math.max(0, options.paddingX ?? 1);
|
|
196
|
+
const contentWidth = Math.max(0, width - 2 - paddingX * 2);
|
|
197
|
+
const border = (text: string) => theme.fg(borderColor, text);
|
|
198
|
+
const pad = " ".repeat(paddingX);
|
|
199
|
+
const lines = [renderTitledTopBorder(theme, title, width, border)];
|
|
200
|
+
|
|
201
|
+
const scroll = options.scroll;
|
|
202
|
+
const bodyRows = inner.length;
|
|
203
|
+
const trackOffset = Math.max(0, Math.min(scroll?.trackOffset ?? 0, bodyRows));
|
|
204
|
+
const trackRows = Math.max(0, Math.min(scroll?.trackRows ?? bodyRows, bodyRows - trackOffset));
|
|
205
|
+
const scrollable = !!scroll && scroll.totalRows > scroll.visibleRows && trackRows > 0;
|
|
206
|
+
const thumbLen = scrollable
|
|
207
|
+
? Math.max(1, Math.round((scroll.visibleRows / scroll.totalRows) * trackRows))
|
|
208
|
+
: 0;
|
|
209
|
+
const maxThumbStart = Math.max(0, trackRows - thumbLen);
|
|
210
|
+
const maxScrollOffset = scrollable ? Math.max(1, scroll.totalRows - scroll.visibleRows) : 1;
|
|
211
|
+
const thumbStart = scrollable
|
|
212
|
+
? trackOffset + Math.min(maxThumbStart, Math.round((scroll.offset / maxScrollOffset) * maxThumbStart))
|
|
213
|
+
: -1;
|
|
214
|
+
|
|
215
|
+
for (let i = 0; i < inner.length; i++) {
|
|
216
|
+
const line = inner[i] ?? "";
|
|
217
|
+
const rightBorder = scrollable && i >= thumbStart && i < thumbStart + thumbLen ? "┃" : "│";
|
|
218
|
+
lines.push(border("│") + pad + padRightVisible(line, contentWidth) + pad + border(rightBorder));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const footer = Array.isArray(options.footer)
|
|
222
|
+
? options.footer
|
|
223
|
+
: options.footer
|
|
224
|
+
? [options.footer]
|
|
225
|
+
: [];
|
|
226
|
+
if (footer.length > 0) {
|
|
227
|
+
lines.push(border("├" + "─".repeat(width - 2) + "┤"));
|
|
228
|
+
for (const line of footer) {
|
|
229
|
+
lines.push(border("│") + pad + padRightVisible(line, contentWidth) + pad + border("│"));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
lines.push(border("╰" + "─".repeat(width - 2) + "╯"));
|
|
234
|
+
return lines.map((line) => safeLine(line, width, ""));
|
|
235
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Natural-language openers for milestone discussion.
|
|
3
|
+
*
|
|
4
|
+
* Keep these short and conversational. They are often the user's first prompt
|
|
5
|
+
* when GSD starts shaping a project or milestone, so they should feel like a
|
|
6
|
+
* collaborator starting a working session rather than a form field.
|
|
7
|
+
*/
|
|
8
|
+
import { randomInt } from "node:crypto";
|
|
9
|
+
|
|
10
|
+
export const VISION_ASK_VARIANTS = [
|
|
11
|
+
"What are we building?",
|
|
12
|
+
"What do you want to make next?",
|
|
13
|
+
"What should this become?",
|
|
14
|
+
"What are you picturing?",
|
|
15
|
+
"Where should we take this?",
|
|
16
|
+
"What should this milestone unlock?",
|
|
17
|
+
"Tell me what you want to build.",
|
|
18
|
+
"What should GSD help you shape?",
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
export type VisionAskVariant = typeof VISION_ASK_VARIANTS[number];
|
|
22
|
+
|
|
23
|
+
export function chooseVisionAskVariant(
|
|
24
|
+
pickIndex: (exclusiveMax: number) => number = randomInt,
|
|
25
|
+
): VisionAskVariant {
|
|
26
|
+
const index = pickIndex(VISION_ASK_VARIANTS.length);
|
|
27
|
+
return VISION_ASK_VARIANTS[index] ?? VISION_ASK_VARIANTS[0];
|
|
28
|
+
}
|