gsd-pi 2.58.0-dev.778d6ac → 2.58.0-dev.e002a57
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/dist/cli.js +11 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +11 -8
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +9 -16
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +22 -1
- package/dist/resources/extensions/gsd/codebase-generator.js +279 -0
- package/dist/resources/extensions/gsd/commands/catalog.js +10 -1
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
- package/dist/resources/extensions/gsd/commands-codebase.js +115 -0
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +41 -4
- package/dist/resources/extensions/gsd/complexity-classifier.js +8 -6
- package/dist/resources/extensions/gsd/doctor-git-checks.js +48 -1
- package/dist/resources/extensions/gsd/doctor-proactive.js +34 -1
- package/dist/resources/extensions/gsd/error-classifier.js +3 -4
- package/dist/resources/extensions/gsd/git-service.js +82 -1
- package/dist/resources/extensions/gsd/native-git-bridge.js +22 -0
- package/dist/resources/extensions/gsd/paths.js +2 -0
- package/dist/resources/extensions/gsd/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/watch/header-renderer.js +241 -0
- package/dist/resources/extensions/search-the-web/url-utils.js +17 -0
- package/dist/security-overrides.d.ts +11 -0
- package/dist/security-overrides.js +41 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +16 -16
- 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 +2 -2
- 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/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 +16 -16
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/welcome-screen.d.ts +1 -0
- package/dist/welcome-screen.js +32 -6
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/resolve-config-value.d.ts +8 -0
- package/packages/pi-coding-agent/dist/core/resolve-config-value.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resolve-config-value.js +23 -2
- package/packages/pi-coding-agent/dist/core/resolve-config-value.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +89 -2
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager-security.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/settings-manager-security.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/settings-manager-security.test.js +83 -0
- package/packages/pi-coding-agent/dist/core/settings-manager-security.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +14 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +36 -3
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -0
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/armin.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/armin.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/armin.js +9 -8
- package/packages/pi-coding-agent/dist/modes/interactive/components/armin.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +0 -3
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +2 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bordered-loader.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bordered-loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/config-selector.js +5 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/countdown-timer.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/countdown-timer.js +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/countdown-timer.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/custom-message.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/daxnuts.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/daxnuts.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/daxnuts.js +4 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/daxnuts.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js +2 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js +8 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.js +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-selector.js +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +26 -12
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/oauth-selector.js +4 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +46 -14
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.js +2 -8
- package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.js +4 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +2 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +8 -3
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message-selector.js +3 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +15 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +16 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +27 -4
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +6 -0
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
- package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +111 -1
- package/packages/pi-coding-agent/src/core/resolve-config-value.ts +26 -2
- package/packages/pi-coding-agent/src/core/settings-manager-security.test.ts +102 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +44 -3
- package/packages/pi-coding-agent/src/index.ts +5 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/armin.ts +9 -9
- package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +0 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/bash-execution.ts +3 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/bordered-loader.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/branch-summary-message.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/compaction-summary-message.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/config-selector.ts +7 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/countdown-timer.ts +3 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/custom-message.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/daxnuts.ts +4 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/diff.ts +2 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.ts +3 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/extension-input.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/extension-selector.ts +4 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +27 -13
- package/packages/pi-coding-agent/src/modes/interactive/components/oauth-selector.ts +4 -4
- package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +45 -14
- package/packages/pi-coding-agent/src/modes/interactive/components/scoped-models-selector.ts +2 -7
- package/packages/pi-coding-agent/src/modes/interactive/components/session-selector.ts +4 -4
- package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +2 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +8 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/user-message-selector.ts +3 -2
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +17 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +14 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +35 -3
- package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +7 -0
- package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +1 -1
- package/pkg/dist/modes/interactive/theme/themes.js +1 -1
- package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
- package/src/resources/extensions/gsd/auto-worktree.ts +10 -7
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +10 -16
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +22 -1
- package/src/resources/extensions/gsd/codebase-generator.ts +351 -0
- package/src/resources/extensions/gsd/commands/catalog.ts +10 -1
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
- package/src/resources/extensions/gsd/commands-codebase.ts +164 -0
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +46 -4
- package/src/resources/extensions/gsd/complexity-classifier.ts +8 -6
- package/src/resources/extensions/gsd/doctor-git-checks.ts +49 -1
- package/src/resources/extensions/gsd/doctor-proactive.ts +35 -1
- package/src/resources/extensions/gsd/doctor-types.ts +2 -0
- package/src/resources/extensions/gsd/error-classifier.ts +3 -4
- package/src/resources/extensions/gsd/git-service.ts +93 -0
- package/src/resources/extensions/gsd/native-git-bridge.ts +24 -0
- package/src/resources/extensions/gsd/paths.ts +2 -0
- package/src/resources/extensions/gsd/preferences-types.ts +8 -0
- package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +488 -0
- package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +33 -0
- package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +72 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +44 -0
- package/src/resources/extensions/gsd/watch/header-renderer.ts +275 -0
- package/src/resources/extensions/search-the-web/url-utils.ts +19 -0
- /package/dist/web/standalone/.next/static/{R0D4xaIPl5kg93edN7Oo0 → nUA6d2OJrDSVq9RNb-c8b}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{R0D4xaIPl5kg93edN7Oo0 → nUA6d2OJrDSVq9RNb-c8b}/_ssgManifest.js +0 -0
|
@@ -1455,4 +1455,72 @@ describe('git-service', async () => {
|
|
|
1455
1455
|
try { rmSync(repo, { recursive: true, force: true }); } catch {}
|
|
1456
1456
|
try { rmSync(externalGsd, { recursive: true, force: true }); } catch {}
|
|
1457
1457
|
});
|
|
1458
|
+
|
|
1459
|
+
// ─── autoCommit: absorbs preceding gsd snapshot commits ─────────────────
|
|
1460
|
+
|
|
1461
|
+
test('autoCommit: absorbs preceding gsd snapshot commits', () => {
|
|
1462
|
+
const repo = initTempRepo();
|
|
1463
|
+
|
|
1464
|
+
// Simulate 2 gsd snapshot commits
|
|
1465
|
+
createFile(repo, "file1.ts", "v1");
|
|
1466
|
+
run("git add -A", repo);
|
|
1467
|
+
run('git commit -m "gsd snapshot: uncommitted changes after 35m inactivity"', repo);
|
|
1468
|
+
|
|
1469
|
+
createFile(repo, "file2.ts", "v2");
|
|
1470
|
+
run("git add -A", repo);
|
|
1471
|
+
run('git commit -m "gsd snapshot: pre-dispatch, uncommitted changes after 40m inactivity"', repo);
|
|
1472
|
+
|
|
1473
|
+
// Verify we have 3 commits (init + 2 snapshots)
|
|
1474
|
+
const countBefore = run("git rev-list --count HEAD", repo);
|
|
1475
|
+
assert.deepStrictEqual(countBefore, "3", "precondition: 3 commits before autoCommit");
|
|
1476
|
+
|
|
1477
|
+
// Now make a real change and autoCommit
|
|
1478
|
+
createFile(repo, "feature.ts", "real work");
|
|
1479
|
+
|
|
1480
|
+
const svc = new GitServiceImpl(repo);
|
|
1481
|
+
const msg = svc.autoCommit("execute-task", "S01/T01");
|
|
1482
|
+
assert.ok(msg !== null, "autoCommit succeeds");
|
|
1483
|
+
|
|
1484
|
+
// Should be 2 commits: init + squashed real commit (snapshots absorbed)
|
|
1485
|
+
const countAfter = run("git rev-list --count HEAD", repo);
|
|
1486
|
+
assert.deepStrictEqual(countAfter, "2", "snapshot commits absorbed into real commit");
|
|
1487
|
+
|
|
1488
|
+
// All files should be present
|
|
1489
|
+
const files = run("git show --name-only HEAD", repo);
|
|
1490
|
+
assert.ok(files.includes("file1.ts"), "file1.ts from snapshot 1 preserved");
|
|
1491
|
+
assert.ok(files.includes("file2.ts"), "file2.ts from snapshot 2 preserved");
|
|
1492
|
+
assert.ok(files.includes("feature.ts"), "feature.ts from real commit preserved");
|
|
1493
|
+
|
|
1494
|
+
// No gsd snapshot commits in log
|
|
1495
|
+
const log = run("git log --oneline", repo);
|
|
1496
|
+
assert.ok(!log.includes("gsd snapshot"), "no gsd snapshot commits remain in history");
|
|
1497
|
+
|
|
1498
|
+
rmSync(repo, { recursive: true, force: true });
|
|
1499
|
+
});
|
|
1500
|
+
|
|
1501
|
+
// ─── autoCommit: does not absorb non-snapshot commits ───────────────────
|
|
1502
|
+
|
|
1503
|
+
test('autoCommit: does not absorb non-snapshot commits', () => {
|
|
1504
|
+
const repo = initTempRepo();
|
|
1505
|
+
|
|
1506
|
+
// Create a normal (non-snapshot) commit
|
|
1507
|
+
createFile(repo, "earlier.ts", "earlier work");
|
|
1508
|
+
run("git add -A", repo);
|
|
1509
|
+
run('git commit -m "feat: earlier work"', repo);
|
|
1510
|
+
|
|
1511
|
+
const countBefore = run("git rev-list --count HEAD", repo);
|
|
1512
|
+
assert.deepStrictEqual(countBefore, "2", "precondition: 2 commits before autoCommit");
|
|
1513
|
+
|
|
1514
|
+
// Make a real change and autoCommit
|
|
1515
|
+
createFile(repo, "feature.ts", "new work");
|
|
1516
|
+
|
|
1517
|
+
const svc = new GitServiceImpl(repo);
|
|
1518
|
+
svc.autoCommit("execute-task", "S01/T02");
|
|
1519
|
+
|
|
1520
|
+
// Should be 3 commits — earlier commit not absorbed
|
|
1521
|
+
const countAfter = run("git rev-list --count HEAD", repo);
|
|
1522
|
+
assert.deepStrictEqual(countAfter, "3", "non-snapshot commits NOT absorbed");
|
|
1523
|
+
|
|
1524
|
+
rmSync(repo, { recursive: true, force: true });
|
|
1525
|
+
});
|
|
1458
1526
|
});
|
|
@@ -82,3 +82,47 @@ test("#2572: 'SyntaxError' with JSON context (truncated stream) is transient", (
|
|
|
82
82
|
assert.equal(isTransient(result), true, "'SyntaxError...JSON' should be transient");
|
|
83
83
|
assert.equal(result.kind, "stream", "JSON parse errors are stream kind");
|
|
84
84
|
});
|
|
85
|
+
|
|
86
|
+
// --- Catch-all: all V8 JSON.parse variants matched by "in JSON at position" ---
|
|
87
|
+
|
|
88
|
+
test("V8 JSON.parse: 'No number after minus sign in JSON' is transient (#2882)", () => {
|
|
89
|
+
const result = classifyError("No number after minus sign in JSON at position 42");
|
|
90
|
+
assert.equal(isTransient(result), true);
|
|
91
|
+
assert.equal(result.kind, "stream");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("V8 JSON.parse: 'Expected property value after colon' is transient", () => {
|
|
95
|
+
const result = classifyError("Expected ',' or '}' after property value in JSON at position 108");
|
|
96
|
+
assert.equal(isTransient(result), true);
|
|
97
|
+
assert.equal(result.kind, "stream");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("V8 JSON.parse: 'Bad control character in string literal' is transient", () => {
|
|
101
|
+
const result = classifyError("Bad control character in string literal in JSON at position 5");
|
|
102
|
+
assert.equal(isTransient(result), true);
|
|
103
|
+
assert.equal(result.kind, "stream");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("V8 JSON.parse: 'Bad escaped character' is transient", () => {
|
|
107
|
+
const result = classifyError("Bad escaped character in JSON at position 17");
|
|
108
|
+
assert.equal(isTransient(result), true);
|
|
109
|
+
assert.equal(result.kind, "stream");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("V8 JSON.parse: 'Unexpected number' is transient", () => {
|
|
113
|
+
const result = classifyError("Unexpected number in JSON at position 0");
|
|
114
|
+
assert.equal(isTransient(result), true);
|
|
115
|
+
assert.equal(result.kind, "stream");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("V8 JSON.parse: 'Unexpected string' is transient", () => {
|
|
119
|
+
const result = classifyError("Unexpected string in JSON at position 12");
|
|
120
|
+
assert.equal(isTransient(result), true);
|
|
121
|
+
assert.equal(result.kind, "stream");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("V8 JSON.parse with line/column suffix is transient", () => {
|
|
125
|
+
const result = classifyError("Unexpected token x in JSON at position 99 (line 3 column 14)");
|
|
126
|
+
assert.equal(isTransient(result), true);
|
|
127
|
+
assert.equal(result.kind, "stream");
|
|
128
|
+
});
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// GSD Watch — Header renderer: ASCII logo, session info, MCP status, remote questions
|
|
2
|
+
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
3
|
+
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { visibleWidth, truncateToWidth } from "@gsd/pi-tui";
|
|
9
|
+
import { loadEffectiveGSDPreferences } from "../preferences.js";
|
|
10
|
+
|
|
11
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* GSD ASCII logo — inlined here because the canonical src/logo.ts is outside
|
|
15
|
+
* the resources rootDir and cannot be imported directly.
|
|
16
|
+
*/
|
|
17
|
+
const GSD_LOGO: readonly string[] = [
|
|
18
|
+
' ██████╗ ███████╗██████╗ ',
|
|
19
|
+
' ██╔════╝ ██╔════╝██╔══██╗',
|
|
20
|
+
' ██║ ███╗███████╗██║ ██║',
|
|
21
|
+
' ██║ ██║╚════██║██║ ██║',
|
|
22
|
+
' ╚██████╔╝███████║██████╔╝',
|
|
23
|
+
' ╚═════╝ ╚══════╝╚═════╝ ',
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
/** Separator character for the horizontal divider line. */
|
|
27
|
+
const SEPARATOR_CHAR = "─";
|
|
28
|
+
|
|
29
|
+
/** Vertical bar between logo and info panel. */
|
|
30
|
+
const PANEL_DIVIDER = "│";
|
|
31
|
+
|
|
32
|
+
/** Label column width for Model/Provider/Directory/Branch rows. */
|
|
33
|
+
const LABEL_COL_WIDTH = 10;
|
|
34
|
+
|
|
35
|
+
// ─── Data Readers ─────────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Read the configured execution model from GSD preferences.
|
|
39
|
+
* Falls back through execution -> planning -> research -> first found.
|
|
40
|
+
* Returns "default" if nothing is configured.
|
|
41
|
+
*/
|
|
42
|
+
export function readModelFromPreferences(): string {
|
|
43
|
+
try {
|
|
44
|
+
const prefs = loadEffectiveGSDPreferences();
|
|
45
|
+
if (!prefs?.preferences.models) return "default";
|
|
46
|
+
const m = prefs.preferences.models as Record<string, unknown>;
|
|
47
|
+
// Try common phases in priority order
|
|
48
|
+
for (const phase of ["execution", "planning", "research", "discuss", "subagent"]) {
|
|
49
|
+
const val = m[phase];
|
|
50
|
+
if (typeof val === "string") return val;
|
|
51
|
+
if (val && typeof val === "object" && "model" in val) {
|
|
52
|
+
const model = (val as { model: string }).model;
|
|
53
|
+
if (typeof model === "string") return model;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
// Non-fatal
|
|
58
|
+
}
|
|
59
|
+
return "default";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Derive provider name from model ID prefix.
|
|
64
|
+
*/
|
|
65
|
+
export function deriveProvider(modelId: string): string {
|
|
66
|
+
if (modelId.startsWith("claude")) return "anthropic";
|
|
67
|
+
if (modelId.startsWith("gpt") || modelId.startsWith("o1") || modelId.startsWith("o3")) return "openai";
|
|
68
|
+
if (modelId.startsWith("gemini")) return "google";
|
|
69
|
+
if (modelId.startsWith("deepseek")) return "deepseek";
|
|
70
|
+
if (modelId === "default") return "anthropic";
|
|
71
|
+
return "unknown";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Shorten a directory path by replacing the home directory with ~.
|
|
76
|
+
*/
|
|
77
|
+
export function shortenPath(fullPath: string): string {
|
|
78
|
+
const home = homedir();
|
|
79
|
+
if (fullPath.startsWith(home)) {
|
|
80
|
+
return "~" + fullPath.slice(home.length);
|
|
81
|
+
}
|
|
82
|
+
return fullPath;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Read the current git branch name. Returns "unknown" on failure.
|
|
87
|
+
*/
|
|
88
|
+
export function readGitBranch(projectRoot: string): string {
|
|
89
|
+
try {
|
|
90
|
+
return execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
91
|
+
cwd: projectRoot,
|
|
92
|
+
encoding: "utf-8",
|
|
93
|
+
timeout: 2000,
|
|
94
|
+
}).trim();
|
|
95
|
+
} catch {
|
|
96
|
+
return "unknown";
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Read MCP server names from .mcp.json or .gsd/mcp.json.
|
|
102
|
+
* Returns array of server name strings.
|
|
103
|
+
*/
|
|
104
|
+
export function readMcpServerNames(projectRoot: string): string[] {
|
|
105
|
+
const configPaths = [
|
|
106
|
+
join(projectRoot, ".mcp.json"),
|
|
107
|
+
join(projectRoot, ".gsd", "mcp.json"),
|
|
108
|
+
];
|
|
109
|
+
const names: string[] = [];
|
|
110
|
+
const seen = new Set<string>();
|
|
111
|
+
|
|
112
|
+
for (const configPath of configPaths) {
|
|
113
|
+
try {
|
|
114
|
+
if (!existsSync(configPath)) continue;
|
|
115
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
116
|
+
const data = JSON.parse(raw) as Record<string, unknown>;
|
|
117
|
+
const mcpServers = (data.mcpServers ?? data.servers) as
|
|
118
|
+
| Record<string, unknown>
|
|
119
|
+
| undefined;
|
|
120
|
+
if (!mcpServers || typeof mcpServers !== "object") continue;
|
|
121
|
+
for (const name of Object.keys(mcpServers)) {
|
|
122
|
+
if (!seen.has(name)) {
|
|
123
|
+
seen.add(name);
|
|
124
|
+
names.push(name);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
// Non-fatal
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return names;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ─── Header Layout ────────────────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
export interface HeaderData {
|
|
138
|
+
model: string;
|
|
139
|
+
provider: string;
|
|
140
|
+
directory: string;
|
|
141
|
+
branch: string;
|
|
142
|
+
mcpServers: string[];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Gather all header data from filesystem and preferences.
|
|
147
|
+
*/
|
|
148
|
+
export function gatherHeaderData(projectRoot: string): HeaderData {
|
|
149
|
+
const model = readModelFromPreferences();
|
|
150
|
+
const provider = deriveProvider(model);
|
|
151
|
+
const directory = shortenPath(projectRoot);
|
|
152
|
+
const branch = readGitBranch(projectRoot);
|
|
153
|
+
const mcpServers = readMcpServerNames(projectRoot);
|
|
154
|
+
|
|
155
|
+
return { model, provider, directory, branch, mcpServers };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Build an info panel line: "Label value" with proper padding.
|
|
160
|
+
* Returns empty string if value is empty.
|
|
161
|
+
*/
|
|
162
|
+
function formatInfoLine(label: string, value: string, availableWidth: number): string {
|
|
163
|
+
const bold = `\x1b[1m${label}\x1b[0m`;
|
|
164
|
+
const labelVis = visibleWidth(bold);
|
|
165
|
+
const padding = " ".repeat(Math.max(1, LABEL_COL_WIDTH - labelVis));
|
|
166
|
+
const maxValueWidth = Math.max(1, availableWidth - LABEL_COL_WIDTH);
|
|
167
|
+
const truncValue = truncateToWidth(value, maxValueWidth, "…");
|
|
168
|
+
return bold + padding + truncValue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Format MCP server names as a dot-separated row with checkmarks.
|
|
173
|
+
* e.g. "Brave ✓ · Answers ✓ · Context7 ✓"
|
|
174
|
+
*/
|
|
175
|
+
export function formatMcpRow(servers: string[], width: number): string {
|
|
176
|
+
if (servers.length === 0) return "";
|
|
177
|
+
|
|
178
|
+
// Capitalize first letter of each server name
|
|
179
|
+
const items = servers.map(s => {
|
|
180
|
+
const cap = s.charAt(0).toUpperCase() + s.slice(1);
|
|
181
|
+
return `${cap} ✓`;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const full = items.join(" · ");
|
|
185
|
+
if (visibleWidth(full) <= width) return full;
|
|
186
|
+
|
|
187
|
+
// Truncate if too wide
|
|
188
|
+
return truncateToWidth(full, width, "…");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Render the full header as an array of terminal-safe strings.
|
|
193
|
+
*
|
|
194
|
+
* Layout: GSD ASCII logo on the left, info panel on the right separated by │.
|
|
195
|
+
* Below: MCP server row, remote questions row, separator line.
|
|
196
|
+
*/
|
|
197
|
+
export function renderHeaderLines(data: HeaderData, width: number): string[] {
|
|
198
|
+
const lines: string[] = [];
|
|
199
|
+
|
|
200
|
+
// Logo is 6 lines tall. Info panel has: title + blank + model + provider + directory + branch = 6 lines
|
|
201
|
+
const logoLines = GSD_LOGO;
|
|
202
|
+
const logoWidth = Math.max(...logoLines.map(l => visibleWidth(l)));
|
|
203
|
+
|
|
204
|
+
// Calculate available width for the info panel
|
|
205
|
+
// Layout: logo + " " + "│" + " " = logoWidth + 3
|
|
206
|
+
const dividerOverhead = 3; // " │ "
|
|
207
|
+
const infoPanelWidth = width - logoWidth - dividerOverhead;
|
|
208
|
+
|
|
209
|
+
// If terminal is too narrow for side-by-side, fall back to stacked layout
|
|
210
|
+
if (infoPanelWidth < 20) {
|
|
211
|
+
return renderStackedHeader(data, width);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Build info panel lines (6 lines to match logo height)
|
|
215
|
+
const infoLines: string[] = [
|
|
216
|
+
`\x1b[1mGet Shit Done\x1b[0m`,
|
|
217
|
+
"",
|
|
218
|
+
formatInfoLine("Model", data.model, infoPanelWidth),
|
|
219
|
+
formatInfoLine("Provider", data.provider, infoPanelWidth),
|
|
220
|
+
formatInfoLine("Directory", data.directory, infoPanelWidth),
|
|
221
|
+
formatInfoLine("Branch", data.branch, infoPanelWidth),
|
|
222
|
+
];
|
|
223
|
+
|
|
224
|
+
// Merge logo and info panel side by side
|
|
225
|
+
const maxLines = Math.max(logoLines.length, infoLines.length);
|
|
226
|
+
for (let i = 0; i < maxLines; i++) {
|
|
227
|
+
const logoLine = i < logoLines.length ? logoLines[i] : "";
|
|
228
|
+
const infoLine = i < infoLines.length ? infoLines[i] : "";
|
|
229
|
+
|
|
230
|
+
// Pad logo line to consistent width
|
|
231
|
+
const logoPad = " ".repeat(Math.max(0, logoWidth - visibleWidth(logoLine)));
|
|
232
|
+
lines.push(`${logoLine}${logoPad} ${PANEL_DIVIDER} ${infoLine}`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Blank line after logo+info block
|
|
236
|
+
lines.push("");
|
|
237
|
+
|
|
238
|
+
// MCP server row
|
|
239
|
+
const mcpRow = formatMcpRow(data.mcpServers, width);
|
|
240
|
+
if (mcpRow) {
|
|
241
|
+
lines.push(` ${mcpRow}`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Separator line
|
|
245
|
+
lines.push(SEPARATOR_CHAR.repeat(width));
|
|
246
|
+
|
|
247
|
+
return lines;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Fallback stacked layout for narrow terminals (< 20 cols for info panel).
|
|
252
|
+
*/
|
|
253
|
+
function renderStackedHeader(data: HeaderData, width: number): string[] {
|
|
254
|
+
const lines: string[] = [];
|
|
255
|
+
|
|
256
|
+
// Title
|
|
257
|
+
lines.push(`\x1b[1mGet Shit Done\x1b[0m`);
|
|
258
|
+
lines.push("");
|
|
259
|
+
|
|
260
|
+
// Info
|
|
261
|
+
lines.push(formatInfoLine("Model", data.model, width));
|
|
262
|
+
lines.push(formatInfoLine("Provider", data.provider, width));
|
|
263
|
+
lines.push(formatInfoLine("Directory", data.directory, width));
|
|
264
|
+
lines.push(formatInfoLine("Branch", data.branch, width));
|
|
265
|
+
lines.push("");
|
|
266
|
+
|
|
267
|
+
// MCP
|
|
268
|
+
const mcpRow = formatMcpRow(data.mcpServers, width);
|
|
269
|
+
if (mcpRow) lines.push(` ${mcpRow}`);
|
|
270
|
+
|
|
271
|
+
// Separator
|
|
272
|
+
lines.push(SEPARATOR_CHAR.repeat(width));
|
|
273
|
+
|
|
274
|
+
return lines;
|
|
275
|
+
}
|
|
@@ -21,11 +21,30 @@ const PRIVATE_IP_PATTERNS = [
|
|
|
21
21
|
/^fe80:/i,
|
|
22
22
|
];
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Hostnames exempted from SSRF blocking. Set via setFetchAllowedUrls()
|
|
26
|
+
* from global settings.json or GSD_FETCH_ALLOWED_URLS env var.
|
|
27
|
+
*/
|
|
28
|
+
let fetchAllowedHostnames: Set<string> = new Set();
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Replace the fetch URL allowlist (hostnames exempted from SSRF checks).
|
|
32
|
+
*/
|
|
33
|
+
export function setFetchAllowedUrls(hostnames: string[]): void {
|
|
34
|
+
fetchAllowedHostnames = new Set(hostnames.map((h) => h.toLowerCase()));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Get the currently active fetch URL allowlist. */
|
|
38
|
+
export function getFetchAllowedUrls(): readonly string[] {
|
|
39
|
+
return [...fetchAllowedHostnames];
|
|
40
|
+
}
|
|
41
|
+
|
|
24
42
|
export function isBlockedUrl(url: string): boolean {
|
|
25
43
|
try {
|
|
26
44
|
const parsed = new URL(url);
|
|
27
45
|
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return true;
|
|
28
46
|
const hostname = parsed.hostname.toLowerCase();
|
|
47
|
+
if (fetchAllowedHostnames.has(hostname)) return false;
|
|
29
48
|
if (BLOCKED_HOSTNAMES.has(hostname)) return true;
|
|
30
49
|
for (const pattern of PRIVATE_IP_PATTERNS) {
|
|
31
50
|
if (pattern.test(hostname)) return true;
|
|
File without changes
|
|
File without changes
|