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
package/dist/cli.js
CHANGED
|
@@ -11,6 +11,7 @@ import { shouldRunOnboarding, runOnboarding } from './onboarding.js';
|
|
|
11
11
|
import chalk from 'chalk';
|
|
12
12
|
import { checkForUpdates } from './update-check.js';
|
|
13
13
|
import { printHelp, printSubcommandHelp } from './help-text.js';
|
|
14
|
+
import { applySecurityOverrides } from './security-overrides.js';
|
|
14
15
|
import { parseCliArgs as parseWebCliArgs, runWebCliBranch, migrateLegacyFlatSessions, } from './cli-web-branch.js';
|
|
15
16
|
import { stopWebMode } from './web-mode.js';
|
|
16
17
|
import { getProjectSessionsDir } from './project-sessions.js';
|
|
@@ -281,6 +282,7 @@ const modelsJsonPath = resolveModelsJsonPath();
|
|
|
281
282
|
const modelRegistry = new ModelRegistry(authStorage, modelsJsonPath);
|
|
282
283
|
markStartup('ModelRegistry');
|
|
283
284
|
const settingsManager = SettingsManager.create(agentDir);
|
|
285
|
+
applySecurityOverrides(settingsManager);
|
|
284
286
|
markStartup('SettingsManager.create');
|
|
285
287
|
// Run onboarding wizard on first launch (no LLM provider configured)
|
|
286
288
|
if (!isPrintMode && shouldRunOnboarding(authStorage, settingsManager.getDefaultProvider())) {
|
|
@@ -600,10 +602,19 @@ if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
|
600
602
|
// Skip when the first-run banner was already printed in loader.ts (prevents double banner).
|
|
601
603
|
if (!process.env.GSD_FIRST_RUN_BANNER) {
|
|
602
604
|
const { printWelcomeScreen } = await import('./welcome-screen.js');
|
|
605
|
+
let remoteChannel;
|
|
606
|
+
try {
|
|
607
|
+
const { resolveRemoteConfig } = await import('./resources/extensions/remote-questions/config.js');
|
|
608
|
+
const rc = resolveRemoteConfig();
|
|
609
|
+
if (rc)
|
|
610
|
+
remoteChannel = rc.channel;
|
|
611
|
+
}
|
|
612
|
+
catch { /* non-fatal */ }
|
|
603
613
|
printWelcomeScreen({
|
|
604
614
|
version: process.env.GSD_VERSION || '0.0.0',
|
|
605
615
|
modelName: settingsManager.getDefaultModel() || undefined,
|
|
606
616
|
provider: settingsManager.getDefaultProvider() || undefined,
|
|
617
|
+
remoteChannel,
|
|
607
618
|
});
|
|
608
619
|
}
|
|
609
620
|
const interactiveMode = new InteractiveMode(session);
|
|
@@ -1341,15 +1341,18 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
|
|
|
1341
1341
|
catch {
|
|
1342
1342
|
// Non-fatal — proceed with merge; untracked files may block it
|
|
1343
1343
|
}
|
|
1344
|
-
//
|
|
1345
|
-
//
|
|
1346
|
-
//
|
|
1347
|
-
//
|
|
1344
|
+
// 7b. Clean up stale merge state before attempting squash merge (#2912).
|
|
1345
|
+
// A leftover MERGE_HEAD (from a previous failed merge, libgit2 native path,
|
|
1346
|
+
// or interrupted operation) causes `git merge --squash` to refuse with
|
|
1347
|
+
// "fatal: You have not concluded your merge (MERGE_HEAD exists)".
|
|
1348
|
+
// Defensively remove merge artifacts before starting.
|
|
1348
1349
|
try {
|
|
1349
|
-
const
|
|
1350
|
-
const
|
|
1351
|
-
|
|
1352
|
-
|
|
1350
|
+
const gitDir_ = resolveGitDir(originalBasePath_);
|
|
1351
|
+
for (const f of ["SQUASH_MSG", "MERGE_MSG", "MERGE_HEAD"]) {
|
|
1352
|
+
const p = join(gitDir_, f);
|
|
1353
|
+
if (existsSync(p))
|
|
1354
|
+
unlinkSync(p);
|
|
1355
|
+
}
|
|
1353
1356
|
}
|
|
1354
1357
|
catch { /* best-effort */ }
|
|
1355
1358
|
// 8. Squash merge — auto-resolve .gsd/ state file conflicts (#530)
|
|
@@ -42,27 +42,20 @@ export function registerHooks(pi) {
|
|
|
42
42
|
if (gsdBinPath) {
|
|
43
43
|
const { dirname } = await import("node:path");
|
|
44
44
|
const { printWelcomeScreen } = await import(join(dirname(gsdBinPath), "welcome-screen.js"));
|
|
45
|
-
|
|
45
|
+
let remoteChannel;
|
|
46
|
+
try {
|
|
47
|
+
const { resolveRemoteConfig } = await import("../../remote-questions/config.js");
|
|
48
|
+
const rc = resolveRemoteConfig();
|
|
49
|
+
if (rc)
|
|
50
|
+
remoteChannel = rc.channel;
|
|
51
|
+
}
|
|
52
|
+
catch { /* non-fatal */ }
|
|
53
|
+
printWelcomeScreen({ version: process.env.GSD_VERSION || "0.0.0", remoteChannel });
|
|
46
54
|
}
|
|
47
55
|
}
|
|
48
56
|
catch { /* non-fatal */ }
|
|
49
57
|
}
|
|
50
58
|
loadToolApiKeys();
|
|
51
|
-
try {
|
|
52
|
-
const [{ getRemoteConfigStatus }, { getLatestPromptSummary }] = await Promise.all([
|
|
53
|
-
import("../../remote-questions/config.js"),
|
|
54
|
-
import("../../remote-questions/status.js"),
|
|
55
|
-
]);
|
|
56
|
-
const status = getRemoteConfigStatus();
|
|
57
|
-
const latest = getLatestPromptSummary();
|
|
58
|
-
if (!status.includes("not configured")) {
|
|
59
|
-
const suffix = latest ? `\nLast remote prompt: ${latest.id} (${latest.status})` : "";
|
|
60
|
-
ctx.ui.notify(`${status}${suffix}`, status.includes("disabled") ? "warning" : "info");
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
// ignore
|
|
65
|
-
}
|
|
66
59
|
});
|
|
67
60
|
pi.on("session_switch", async (_event, ctx) => {
|
|
68
61
|
resetWriteGateState();
|
|
@@ -71,12 +71,33 @@ export async function buildBeforeAgentStartResult(event, ctx) {
|
|
|
71
71
|
newSkillsBlock = formatSkillsXml(newSkills);
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
+
let codebaseBlock = "";
|
|
75
|
+
const codebasePath = resolveGsdRootFile(process.cwd(), "CODEBASE");
|
|
76
|
+
if (existsSync(codebasePath)) {
|
|
77
|
+
try {
|
|
78
|
+
const rawContent = readFileSync(codebasePath, "utf-8").trim();
|
|
79
|
+
if (rawContent) {
|
|
80
|
+
// Cap injection size to ~2 000 tokens to avoid bloating every request.
|
|
81
|
+
// Full map is always available at .gsd/CODEBASE.md.
|
|
82
|
+
const MAX_CODEBASE_CHARS = 8_000;
|
|
83
|
+
const generatedMatch = rawContent.match(/Generated: (\S+)/);
|
|
84
|
+
const generatedAt = generatedMatch?.[1] ?? "unknown";
|
|
85
|
+
const content = rawContent.length > MAX_CODEBASE_CHARS
|
|
86
|
+
? rawContent.slice(0, MAX_CODEBASE_CHARS) + "\n\n*(truncated — see .gsd/CODEBASE.md for full map)*"
|
|
87
|
+
: rawContent;
|
|
88
|
+
codebaseBlock = `\n\n[PROJECT CODEBASE — File structure and descriptions (generated ${generatedAt}, may be stale — run /gsd codebase update to refresh)]\n\n${content}`;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// skip
|
|
93
|
+
}
|
|
94
|
+
}
|
|
74
95
|
warnDeprecatedAgentInstructions();
|
|
75
96
|
const injection = await buildGuidedExecuteContextInjection(event.prompt, process.cwd());
|
|
76
97
|
// Re-inject forensics context on follow-up turns (#2941)
|
|
77
98
|
const forensicsInjection = !injection ? buildForensicsContextInjection(process.cwd()) : null;
|
|
78
99
|
const worktreeBlock = buildWorktreeContextBlock();
|
|
79
|
-
const fullSystem = `${event.systemPrompt}\n\n[SYSTEM CONTEXT — GSD]\n\n${systemContent}${preferenceBlock}${knowledgeBlock}${memoryBlock}${newSkillsBlock}${worktreeBlock}`;
|
|
100
|
+
const fullSystem = `${event.systemPrompt}\n\n[SYSTEM CONTEXT — GSD]\n\n${systemContent}${preferenceBlock}${knowledgeBlock}${codebaseBlock}${memoryBlock}${newSkillsBlock}${worktreeBlock}`;
|
|
80
101
|
stopContextTimer({
|
|
81
102
|
systemPromptSize: fullSystem.length,
|
|
82
103
|
injectionSize: injection?.length ?? forensicsInjection?.length ?? 0,
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Codebase Map Generator
|
|
3
|
+
*
|
|
4
|
+
* Produces .gsd/CODEBASE.md — a structural table of contents for the project.
|
|
5
|
+
* Gives fresh agent contexts instant orientation without filesystem exploration.
|
|
6
|
+
*
|
|
7
|
+
* Generation: walk `git ls-files`, group by directory, output with descriptions.
|
|
8
|
+
* Maintenance: agent updates descriptions as it works; incremental update preserves them.
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
11
|
+
import { join, dirname, extname } from "node:path";
|
|
12
|
+
import { execSync } from "node:child_process";
|
|
13
|
+
import { gsdRoot } from "./paths.js";
|
|
14
|
+
// ─── Defaults ────────────────────────────────────────────────────────────────
|
|
15
|
+
const DEFAULT_EXCLUDES = [
|
|
16
|
+
".gsd/",
|
|
17
|
+
".planning/",
|
|
18
|
+
".git/",
|
|
19
|
+
"node_modules/",
|
|
20
|
+
"dist/",
|
|
21
|
+
"build/",
|
|
22
|
+
".next/",
|
|
23
|
+
"coverage/",
|
|
24
|
+
"__pycache__/",
|
|
25
|
+
".venv/",
|
|
26
|
+
"vendor/",
|
|
27
|
+
];
|
|
28
|
+
const DEFAULT_MAX_FILES = 500;
|
|
29
|
+
const DEFAULT_COLLAPSE_THRESHOLD = 20;
|
|
30
|
+
// ─── Parsing ─────────────────────────────────────────────────────────────────
|
|
31
|
+
/**
|
|
32
|
+
* Parse an existing CODEBASE.md to extract file → description mappings.
|
|
33
|
+
* Also scans <!-- gsd:collapsed-descriptions --> comment blocks to preserve
|
|
34
|
+
* descriptions for files in collapsed directories across incremental updates.
|
|
35
|
+
*/
|
|
36
|
+
export function parseCodebaseMap(content) {
|
|
37
|
+
const descriptions = new Map();
|
|
38
|
+
let inCollapsedBlock = false;
|
|
39
|
+
for (const line of content.split("\n")) {
|
|
40
|
+
// Track collapsed-description comment blocks
|
|
41
|
+
if (line.trimStart().startsWith("<!-- gsd:collapsed-descriptions")) {
|
|
42
|
+
inCollapsedBlock = true;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (inCollapsedBlock && line.trimStart().startsWith("-->")) {
|
|
46
|
+
inCollapsedBlock = false;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
// Match: - `path/to/file.ts` — Description here
|
|
50
|
+
const match = line.match(/^- `(.+?)` — (.+)$/);
|
|
51
|
+
if (match) {
|
|
52
|
+
descriptions.set(match[1], match[2]);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
// Match: - `path/to/file.ts` (no description) — only outside collapsed blocks
|
|
56
|
+
if (!inCollapsedBlock) {
|
|
57
|
+
const bareMatch = line.match(/^- `(.+?)`\s*$/);
|
|
58
|
+
if (bareMatch) {
|
|
59
|
+
descriptions.set(bareMatch[1], "");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return descriptions;
|
|
64
|
+
}
|
|
65
|
+
// ─── File Enumeration ────────────────────────────────────────────────────────
|
|
66
|
+
function shouldExclude(filePath, excludes) {
|
|
67
|
+
for (const pattern of excludes) {
|
|
68
|
+
if (pattern.endsWith("/")) {
|
|
69
|
+
if (filePath.startsWith(pattern) || filePath.includes(`/${pattern}`))
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
else if (filePath === pattern || filePath.endsWith(`/${pattern}`)) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Skip binary/lock files
|
|
77
|
+
const ext = extname(filePath).toLowerCase();
|
|
78
|
+
if ([".lock", ".png", ".jpg", ".jpeg", ".gif", ".ico", ".woff", ".woff2", ".ttf", ".eot", ".svg"].includes(ext)) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
function lsFiles(basePath) {
|
|
84
|
+
try {
|
|
85
|
+
const result = execSync("git ls-files", { cwd: basePath, encoding: "utf-8", timeout: 10000 });
|
|
86
|
+
return result.split("\n").filter(Boolean);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Enumerate tracked files, applying exclusions and the maxFiles cap.
|
|
94
|
+
* Returns both the file list and whether truncation occurred.
|
|
95
|
+
*/
|
|
96
|
+
function enumerateFiles(basePath, excludes, maxFiles) {
|
|
97
|
+
const allFiles = lsFiles(basePath);
|
|
98
|
+
const filtered = allFiles.filter((f) => !shouldExclude(f, excludes));
|
|
99
|
+
const truncated = filtered.length > maxFiles;
|
|
100
|
+
return { files: truncated ? filtered.slice(0, maxFiles) : filtered, truncated };
|
|
101
|
+
}
|
|
102
|
+
// ─── Grouping ────────────────────────────────────────────────────────────────
|
|
103
|
+
function groupByDirectory(files, descriptions, collapseThreshold) {
|
|
104
|
+
const dirMap = new Map();
|
|
105
|
+
for (const file of files) {
|
|
106
|
+
const dir = dirname(file);
|
|
107
|
+
const dirKey = dir === "." ? "" : dir;
|
|
108
|
+
if (!dirMap.has(dirKey)) {
|
|
109
|
+
dirMap.set(dirKey, []);
|
|
110
|
+
}
|
|
111
|
+
dirMap.get(dirKey).push({
|
|
112
|
+
path: file,
|
|
113
|
+
description: descriptions.get(file) ?? "",
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
const groups = [];
|
|
117
|
+
const sortedDirs = [...dirMap.keys()].sort();
|
|
118
|
+
for (const dir of sortedDirs) {
|
|
119
|
+
const dirFiles = dirMap.get(dir);
|
|
120
|
+
dirFiles.sort((a, b) => a.path.localeCompare(b.path));
|
|
121
|
+
groups.push({
|
|
122
|
+
path: dir,
|
|
123
|
+
files: dirFiles,
|
|
124
|
+
collapsed: dirFiles.length > collapseThreshold,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return groups;
|
|
128
|
+
}
|
|
129
|
+
// ─── Rendering ───────────────────────────────────────────────────────────────
|
|
130
|
+
function renderCodebaseMap(groups, totalFiles, truncated) {
|
|
131
|
+
const lines = [];
|
|
132
|
+
const now = new Date().toISOString().split(".")[0] + "Z";
|
|
133
|
+
const described = groups.reduce((sum, g) => sum + g.files.filter((f) => f.description).length, 0);
|
|
134
|
+
lines.push("# Codebase Map");
|
|
135
|
+
lines.push("");
|
|
136
|
+
lines.push(`Generated: ${now} | Files: ${totalFiles} | Described: ${described}/${totalFiles}`);
|
|
137
|
+
if (truncated) {
|
|
138
|
+
lines.push(`Note: Truncated to first ${totalFiles} files. Run with higher --max-files to include all.`);
|
|
139
|
+
}
|
|
140
|
+
lines.push("");
|
|
141
|
+
for (const group of groups) {
|
|
142
|
+
const heading = group.path || "(root)";
|
|
143
|
+
lines.push(`### ${heading}/`);
|
|
144
|
+
if (group.collapsed) {
|
|
145
|
+
// Summarize collapsed directories
|
|
146
|
+
const extensions = new Map();
|
|
147
|
+
for (const f of group.files) {
|
|
148
|
+
const ext = extname(f.path) || "(no ext)";
|
|
149
|
+
extensions.set(ext, (extensions.get(ext) ?? 0) + 1);
|
|
150
|
+
}
|
|
151
|
+
const extSummary = [...extensions.entries()]
|
|
152
|
+
.sort((a, b) => b[1] - a[1])
|
|
153
|
+
.map(([ext, count]) => `${count} ${ext}`)
|
|
154
|
+
.join(", ");
|
|
155
|
+
lines.push(`- *(${group.files.length} files: ${extSummary})*`);
|
|
156
|
+
// Preserve any existing descriptions in a hidden comment block so
|
|
157
|
+
// incremental updates can recover them via parseCodebaseMap.
|
|
158
|
+
const descLines = group.files
|
|
159
|
+
.filter((f) => f.description)
|
|
160
|
+
.map((f) => `- \`${f.path}\` — ${f.description}`);
|
|
161
|
+
if (descLines.length > 0) {
|
|
162
|
+
lines.push("<!-- gsd:collapsed-descriptions");
|
|
163
|
+
lines.push(...descLines);
|
|
164
|
+
lines.push("-->");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
for (const file of group.files) {
|
|
169
|
+
if (file.description) {
|
|
170
|
+
lines.push(`- \`${file.path}\` — ${file.description}`);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
lines.push(`- \`${file.path}\``);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
lines.push("");
|
|
178
|
+
}
|
|
179
|
+
return lines.join("\n");
|
|
180
|
+
}
|
|
181
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
182
|
+
/**
|
|
183
|
+
* Generate a fresh CODEBASE.md from scratch.
|
|
184
|
+
* Preserves existing descriptions if `existingDescriptions` is provided.
|
|
185
|
+
*/
|
|
186
|
+
export function generateCodebaseMap(basePath, options, existingDescriptions) {
|
|
187
|
+
const excludes = [...DEFAULT_EXCLUDES, ...(options?.excludePatterns ?? [])];
|
|
188
|
+
const maxFiles = options?.maxFiles ?? DEFAULT_MAX_FILES;
|
|
189
|
+
const collapseThreshold = options?.collapseThreshold ?? DEFAULT_COLLAPSE_THRESHOLD;
|
|
190
|
+
const { files, truncated } = enumerateFiles(basePath, excludes, maxFiles);
|
|
191
|
+
const descriptions = existingDescriptions ?? new Map();
|
|
192
|
+
const groups = groupByDirectory(files, descriptions, collapseThreshold);
|
|
193
|
+
const content = renderCodebaseMap(groups, files.length, truncated);
|
|
194
|
+
return { content, fileCount: files.length, truncated, files };
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Incremental update: re-scan files, preserve existing descriptions,
|
|
198
|
+
* add new files, remove deleted files.
|
|
199
|
+
*/
|
|
200
|
+
export function updateCodebaseMap(basePath, options) {
|
|
201
|
+
const codebasePath = join(gsdRoot(basePath), "CODEBASE.md");
|
|
202
|
+
// Load existing descriptions
|
|
203
|
+
let existingDescriptions = new Map();
|
|
204
|
+
if (existsSync(codebasePath)) {
|
|
205
|
+
const existing = readFileSync(codebasePath, "utf-8");
|
|
206
|
+
existingDescriptions = parseCodebaseMap(existing);
|
|
207
|
+
}
|
|
208
|
+
const existingFiles = new Set(existingDescriptions.keys());
|
|
209
|
+
// Generate new map preserving descriptions — reuse the returned file list
|
|
210
|
+
// to avoid a second enumeration (prevents race between content and stats).
|
|
211
|
+
const result = generateCodebaseMap(basePath, options, existingDescriptions);
|
|
212
|
+
const currentSet = new Set(result.files);
|
|
213
|
+
// Count changes
|
|
214
|
+
let added = 0;
|
|
215
|
+
let removed = 0;
|
|
216
|
+
for (const f of result.files) {
|
|
217
|
+
if (!existingFiles.has(f))
|
|
218
|
+
added++;
|
|
219
|
+
}
|
|
220
|
+
for (const f of existingFiles) {
|
|
221
|
+
if (!currentSet.has(f))
|
|
222
|
+
removed++;
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
content: result.content,
|
|
226
|
+
added,
|
|
227
|
+
removed,
|
|
228
|
+
unchanged: result.files.length - added,
|
|
229
|
+
fileCount: result.fileCount,
|
|
230
|
+
truncated: result.truncated,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Write CODEBASE.md to .gsd/ directory.
|
|
235
|
+
*/
|
|
236
|
+
export function writeCodebaseMap(basePath, content) {
|
|
237
|
+
const root = gsdRoot(basePath);
|
|
238
|
+
mkdirSync(root, { recursive: true });
|
|
239
|
+
const outPath = join(root, "CODEBASE.md");
|
|
240
|
+
writeFileSync(outPath, content, "utf-8");
|
|
241
|
+
return outPath;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Read existing CODEBASE.md, or return null if it doesn't exist.
|
|
245
|
+
*/
|
|
246
|
+
export function readCodebaseMap(basePath) {
|
|
247
|
+
const codebasePath = join(gsdRoot(basePath), "CODEBASE.md");
|
|
248
|
+
if (!existsSync(codebasePath))
|
|
249
|
+
return null;
|
|
250
|
+
try {
|
|
251
|
+
return readFileSync(codebasePath, "utf-8");
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Get stats about the codebase map.
|
|
259
|
+
*/
|
|
260
|
+
export function getCodebaseMapStats(basePath) {
|
|
261
|
+
const content = readCodebaseMap(basePath);
|
|
262
|
+
if (!content) {
|
|
263
|
+
return { exists: false, fileCount: 0, describedCount: 0, undescribedCount: 0, generatedAt: null };
|
|
264
|
+
}
|
|
265
|
+
// Parse total file count from the header line (accurate even for collapsed dirs)
|
|
266
|
+
const fileCountMatch = content.match(/Files:\s*(\d+)/);
|
|
267
|
+
const totalFiles = fileCountMatch ? parseInt(fileCountMatch[1], 10) : 0;
|
|
268
|
+
// Use parseCodebaseMap to count described files (includes collapsed-description blocks)
|
|
269
|
+
const descriptions = parseCodebaseMap(content);
|
|
270
|
+
const described = [...descriptions.values()].filter((d) => d.length > 0).length;
|
|
271
|
+
const dateMatch = content.match(/Generated: (\S+)/);
|
|
272
|
+
return {
|
|
273
|
+
exists: true,
|
|
274
|
+
fileCount: totalFiles,
|
|
275
|
+
describedCount: described,
|
|
276
|
+
undescribedCount: totalFiles - described,
|
|
277
|
+
generatedAt: dateMatch?.[1] ?? null,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
@@ -4,7 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
import { loadRegistry } from "../workflow-templates.js";
|
|
5
5
|
import { resolveProjectRoot } from "../worktree.js";
|
|
6
6
|
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
7
|
-
export const GSD_COMMAND_DESCRIPTION = "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|queue|quick|discuss|capture|triage|dispatch|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|park|unpark|init|setup|inspect|extensions|update|fast|mcp|rethink";
|
|
7
|
+
export const GSD_COMMAND_DESCRIPTION = "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|queue|quick|discuss|capture|triage|dispatch|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|park|unpark|init|setup|inspect|extensions|update|fast|mcp|rethink|codebase";
|
|
8
8
|
export const TOP_LEVEL_SUBCOMMANDS = [
|
|
9
9
|
{ cmd: "help", desc: "Categorized command reference with descriptions" },
|
|
10
10
|
{ cmd: "next", desc: "Explicit step mode (same as /gsd)" },
|
|
@@ -59,6 +59,7 @@ export const TOP_LEVEL_SUBCOMMANDS = [
|
|
|
59
59
|
{ cmd: "mcp", desc: "MCP server status and connectivity check (status, check <server>)" },
|
|
60
60
|
{ cmd: "rethink", desc: "Conversational project reorganization — reorder, park, discard, add milestones" },
|
|
61
61
|
{ cmd: "workflow", desc: "Custom workflow lifecycle (new, run, list, validate, pause, resume)" },
|
|
62
|
+
{ cmd: "codebase", desc: "Generate and manage codebase map (.gsd/CODEBASE.md)" },
|
|
62
63
|
];
|
|
63
64
|
const NESTED_COMPLETIONS = {
|
|
64
65
|
auto: [
|
|
@@ -212,6 +213,14 @@ const NESTED_COMPLETIONS = {
|
|
|
212
213
|
{ cmd: "pause", desc: "Pause custom workflow auto-mode" },
|
|
213
214
|
{ cmd: "resume", desc: "Resume paused custom workflow auto-mode" },
|
|
214
215
|
],
|
|
216
|
+
codebase: [
|
|
217
|
+
{ cmd: "generate", desc: "Generate or regenerate CODEBASE.md" },
|
|
218
|
+
{ cmd: "generate --max-files", desc: "Generate with custom file limit (default: 500)" },
|
|
219
|
+
{ cmd: "update", desc: "Incremental update (preserves descriptions)" },
|
|
220
|
+
{ cmd: "update --max-files", desc: "Update with custom file limit" },
|
|
221
|
+
{ cmd: "stats", desc: "Show file count, description coverage, and generation time" },
|
|
222
|
+
{ cmd: "help", desc: "Show usage and available subcommands" },
|
|
223
|
+
],
|
|
215
224
|
};
|
|
216
225
|
function filterOptions(partial, options, prefix = "") {
|
|
217
226
|
const normalizedPrefix = prefix ? `${prefix} ` : "";
|
|
@@ -203,5 +203,10 @@ Examples:
|
|
|
203
203
|
await handleRethink(trimmed, ctx, pi);
|
|
204
204
|
return true;
|
|
205
205
|
}
|
|
206
|
+
if (trimmed === "codebase" || trimmed.startsWith("codebase ")) {
|
|
207
|
+
const { handleCodebase } = await import("../../commands-codebase.js");
|
|
208
|
+
await handleCodebase(trimmed.replace(/^codebase\s*/, "").trim(), ctx, pi);
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
206
211
|
return false;
|
|
207
212
|
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Command — /gsd codebase
|
|
3
|
+
*
|
|
4
|
+
* Generate and manage the codebase map (.gsd/CODEBASE.md).
|
|
5
|
+
* Subcommands: generate, update, stats, help
|
|
6
|
+
*/
|
|
7
|
+
import { generateCodebaseMap, updateCodebaseMap, writeCodebaseMap, getCodebaseMapStats, readCodebaseMap, } from "./codebase-generator.js";
|
|
8
|
+
const USAGE = "Usage: /gsd codebase [generate|update|stats]\n\n" +
|
|
9
|
+
" generate [--max-files N] — Generate or regenerate CODEBASE.md\n" +
|
|
10
|
+
" update — Incremental update (preserves descriptions)\n" +
|
|
11
|
+
" stats — Show file count, coverage, and generation time\n" +
|
|
12
|
+
" help — Show this help\n\n" +
|
|
13
|
+
"With no subcommand, shows stats if a map exists or help if not.";
|
|
14
|
+
export async function handleCodebase(args, ctx, _pi) {
|
|
15
|
+
const basePath = process.cwd();
|
|
16
|
+
const parts = args.trim().split(/\s+/);
|
|
17
|
+
const sub = parts[0] ?? "";
|
|
18
|
+
switch (sub) {
|
|
19
|
+
case "generate": {
|
|
20
|
+
const maxFiles = parseMaxFiles(args, ctx);
|
|
21
|
+
if (maxFiles === false)
|
|
22
|
+
return; // validation failed, message already shown
|
|
23
|
+
const existing = readCodebaseMap(basePath);
|
|
24
|
+
const existingDescriptions = existing
|
|
25
|
+
? (await import("./codebase-generator.js")).parseCodebaseMap(existing)
|
|
26
|
+
: undefined;
|
|
27
|
+
const result = generateCodebaseMap(basePath, { maxFiles: maxFiles ?? undefined }, existingDescriptions);
|
|
28
|
+
if (result.fileCount === 0) {
|
|
29
|
+
ctx.ui.notify("Codebase map generated with 0 files.\n" +
|
|
30
|
+
"Is this a git repository? Run 'git ls-files' to verify.", "warning");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const outPath = writeCodebaseMap(basePath, result.content);
|
|
34
|
+
ctx.ui.notify(`Codebase map generated: ${result.fileCount} files\n` +
|
|
35
|
+
`Written to: ${outPath}` +
|
|
36
|
+
(result.truncated ? `\n⚠ Truncated — increase --max-files to include all files` : ""), "success");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
case "update": {
|
|
40
|
+
const existing = readCodebaseMap(basePath);
|
|
41
|
+
if (!existing) {
|
|
42
|
+
ctx.ui.notify("No codebase map found. Run /gsd codebase generate to create one.", "warning");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const maxFiles = parseMaxFiles(args, ctx);
|
|
46
|
+
if (maxFiles === false)
|
|
47
|
+
return;
|
|
48
|
+
const result = updateCodebaseMap(basePath, { maxFiles: maxFiles ?? undefined });
|
|
49
|
+
writeCodebaseMap(basePath, result.content);
|
|
50
|
+
ctx.ui.notify(`Codebase map updated: ${result.fileCount} files\n` +
|
|
51
|
+
` Added: ${result.added} | Removed: ${result.removed} | Unchanged: ${result.unchanged}` +
|
|
52
|
+
(result.truncated ? `\n⚠ Truncated — increase --max-files to include all files` : ""), "success");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
case "stats": {
|
|
56
|
+
showStats(basePath, ctx);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
case "help":
|
|
60
|
+
ctx.ui.notify(USAGE, "info");
|
|
61
|
+
return;
|
|
62
|
+
case "": {
|
|
63
|
+
// Safe default: show stats if map exists, help if not
|
|
64
|
+
const existing = readCodebaseMap(basePath);
|
|
65
|
+
if (existing) {
|
|
66
|
+
showStats(basePath, ctx);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
ctx.ui.notify(USAGE, "info");
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
default:
|
|
74
|
+
ctx.ui.notify(`Unknown subcommand "${sub}".\n\n${USAGE}`, "warning");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function showStats(basePath, ctx) {
|
|
78
|
+
const stats = getCodebaseMapStats(basePath);
|
|
79
|
+
if (!stats.exists) {
|
|
80
|
+
ctx.ui.notify("No codebase map found. Run /gsd codebase generate to create one.", "info");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const coverage = stats.fileCount > 0
|
|
84
|
+
? Math.round((stats.describedCount / stats.fileCount) * 100)
|
|
85
|
+
: 0;
|
|
86
|
+
ctx.ui.notify(`Codebase Map Stats:\n` +
|
|
87
|
+
` Files: ${stats.fileCount}\n` +
|
|
88
|
+
` Described: ${stats.describedCount} (${coverage}%)\n` +
|
|
89
|
+
` Undescribed: ${stats.undescribedCount}\n` +
|
|
90
|
+
` Generated: ${stats.generatedAt ?? "unknown"}\n\n` +
|
|
91
|
+
(stats.undescribedCount > 0
|
|
92
|
+
? `Tip: Run /gsd codebase update to refresh after file changes.`
|
|
93
|
+
: `Coverage is complete.`), "info");
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Parse and validate --max-files flag.
|
|
97
|
+
* Returns the parsed number, undefined if flag not present, or false if invalid.
|
|
98
|
+
*/
|
|
99
|
+
function parseMaxFiles(args, ctx) {
|
|
100
|
+
const maxFilesStr = extractFlag(args, "--max-files");
|
|
101
|
+
if (!maxFilesStr)
|
|
102
|
+
return undefined;
|
|
103
|
+
const maxFiles = parseInt(maxFilesStr, 10);
|
|
104
|
+
if (isNaN(maxFiles) || maxFiles < 1) {
|
|
105
|
+
ctx.ui.notify("--max-files must be a positive integer (e.g. --max-files 200).", "warning");
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
return maxFiles;
|
|
109
|
+
}
|
|
110
|
+
function extractFlag(args, flag) {
|
|
111
|
+
const escaped = flag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
112
|
+
const regex = new RegExp(`${escaped}[=\\s]+(\\S+)`);
|
|
113
|
+
const match = args.match(regex);
|
|
114
|
+
return match?.[1];
|
|
115
|
+
}
|
|
@@ -151,11 +151,24 @@ export function buildCategorySummaries(prefs) {
|
|
|
151
151
|
}
|
|
152
152
|
// Git
|
|
153
153
|
const git = prefs.git;
|
|
154
|
+
const staleThreshold = prefs.stale_commit_threshold_minutes;
|
|
155
|
+
const absorbSnapshots = git?.absorb_snapshot_commits;
|
|
154
156
|
let gitSummary = "(defaults)";
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
{
|
|
158
|
+
const parts = [];
|
|
159
|
+
if (git && Object.keys(git).length > 0) {
|
|
160
|
+
const branch = git.main_branch ?? "main";
|
|
161
|
+
const push = git.auto_push ? "on" : "off";
|
|
162
|
+
parts.push(`main: ${branch}, push: ${push}`);
|
|
163
|
+
}
|
|
164
|
+
if (staleThreshold !== undefined) {
|
|
165
|
+
parts.push(`stale: ${staleThreshold === 0 ? "off" : `${staleThreshold}m`}`);
|
|
166
|
+
}
|
|
167
|
+
if (absorbSnapshots !== undefined) {
|
|
168
|
+
parts.push(`absorb: ${absorbSnapshots ? "on" : "off"}`);
|
|
169
|
+
}
|
|
170
|
+
if (parts.length > 0)
|
|
171
|
+
gitSummary = parts.join(", ");
|
|
159
172
|
}
|
|
160
173
|
// Skills
|
|
161
174
|
const discovery = prefs.skill_discovery;
|
|
@@ -394,9 +407,33 @@ async function configureGit(ctx, prefs) {
|
|
|
394
407
|
if (isolationChoice && isolationChoice !== "(keep current)") {
|
|
395
408
|
git.isolation = isolationChoice;
|
|
396
409
|
}
|
|
410
|
+
// absorb_snapshot_commits (git sub-key)
|
|
411
|
+
const currentAbsorb = git.absorb_snapshot_commits;
|
|
412
|
+
const absorbStr = currentAbsorb !== undefined ? String(currentAbsorb) : "";
|
|
413
|
+
const absorbChoice = await ctx.ui.select(`Absorb snapshot commits into real commits${absorbStr ? ` (current: ${absorbStr})` : " (default: true)"}:`, ["true", "false", "(keep current)"]);
|
|
414
|
+
if (absorbChoice && absorbChoice !== "(keep current)") {
|
|
415
|
+
git.absorb_snapshot_commits = absorbChoice === "true";
|
|
416
|
+
}
|
|
397
417
|
if (Object.keys(git).length > 0) {
|
|
398
418
|
prefs.git = git;
|
|
399
419
|
}
|
|
420
|
+
// stale_commit_threshold_minutes (top-level pref, shown in Git section)
|
|
421
|
+
const currentThreshold = prefs.stale_commit_threshold_minutes;
|
|
422
|
+
const thresholdStr = currentThreshold !== undefined ? String(currentThreshold) : "";
|
|
423
|
+
const thresholdInput = await ctx.ui.input(`Stale commit threshold (minutes, 0 to disable)${thresholdStr ? ` (current: ${thresholdStr})` : " (default: 30)"}:`, thresholdStr || "30");
|
|
424
|
+
if (thresholdInput !== null && thresholdInput !== undefined) {
|
|
425
|
+
const val = thresholdInput.trim();
|
|
426
|
+
const parsed = tryParseInteger(val);
|
|
427
|
+
if (val && parsed !== null && parsed >= 0) {
|
|
428
|
+
prefs.stale_commit_threshold_minutes = parsed;
|
|
429
|
+
}
|
|
430
|
+
else if (val && parsed === null) {
|
|
431
|
+
ctx.ui.notify(`Invalid value "${val}" — must be a whole number. Keeping previous value.`, "warning");
|
|
432
|
+
}
|
|
433
|
+
else if (!val && currentThreshold !== undefined) {
|
|
434
|
+
delete prefs.stale_commit_threshold_minutes;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
400
437
|
}
|
|
401
438
|
async function configureSkills(ctx, prefs) {
|
|
402
439
|
// Skill discovery mode
|