gsd-pi 2.24.0 → 2.26.0
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 +13 -3
- package/dist/headless.js +24 -4
- package/dist/models-resolver.d.ts +0 -11
- package/dist/models-resolver.js +0 -15
- package/dist/resource-loader.d.ts +0 -1
- package/dist/resource-loader.js +0 -9
- package/dist/resources/GSD-WORKFLOW.md +12 -9
- package/dist/resources/extensions/async-jobs/index.ts +9 -1
- package/dist/resources/extensions/bg-shell/index.ts +3 -2
- package/dist/resources/extensions/bg-shell/overlay.ts +18 -17
- package/dist/resources/extensions/get-secrets-from-user.ts +5 -23
- package/dist/resources/extensions/gsd/activity-log.ts +5 -3
- package/dist/resources/extensions/gsd/auto-prompts.ts +14 -0
- package/dist/resources/extensions/gsd/auto-recovery.ts +7 -4
- package/dist/resources/extensions/gsd/auto-worktree.ts +132 -3
- package/dist/resources/extensions/gsd/auto.ts +265 -48
- package/dist/resources/extensions/gsd/cache.ts +3 -1
- package/dist/resources/extensions/gsd/doctor-proactive.ts +7 -6
- package/dist/resources/extensions/gsd/doctor.ts +26 -1
- package/dist/resources/extensions/gsd/files.ts +13 -2
- package/dist/resources/extensions/gsd/git-service.ts +74 -14
- package/dist/resources/extensions/gsd/gsd-db.ts +78 -1
- package/dist/resources/extensions/gsd/guided-flow.ts +54 -22
- package/dist/resources/extensions/gsd/index.ts +62 -8
- package/dist/resources/extensions/gsd/memory-extractor.ts +352 -0
- package/dist/resources/extensions/gsd/memory-store.ts +441 -0
- package/dist/resources/extensions/gsd/migrate/command.ts +2 -2
- package/dist/resources/extensions/gsd/migrate/writer.ts +39 -0
- package/dist/resources/extensions/gsd/parallel-orchestrator.ts +122 -4
- package/dist/resources/extensions/gsd/preferences.ts +2 -1
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +4 -4
- package/dist/resources/extensions/gsd/prompts/discuss.md +5 -5
- package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +3 -3
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/dist/resources/extensions/gsd/roadmap-slices.ts +45 -1
- package/dist/resources/extensions/gsd/state.ts +17 -6
- package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
- package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
- package/dist/resources/extensions/gsd/tests/derive-state.test.ts +70 -0
- package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +23 -3
- package/dist/resources/extensions/gsd/tests/git-service.test.ts +70 -4
- package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
- package/dist/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
- package/dist/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
- package/dist/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
- package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +13 -7
- package/dist/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +171 -0
- package/dist/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +8 -4
- package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
- package/dist/resources/extensions/gsd/triage-ui.ts +1 -1
- package/dist/resources/extensions/gsd/types.ts +2 -0
- package/dist/resources/extensions/gsd/visualizer-data.ts +291 -10
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +237 -28
- package/dist/resources/extensions/gsd/visualizer-views.ts +462 -48
- package/dist/resources/extensions/gsd/worktree.ts +9 -2
- package/dist/resources/extensions/search-the-web/native-search.ts +19 -5
- package/dist/resources/extensions/shared/path-display.ts +19 -0
- package/package.json +1 -6
- package/packages/pi-agent-core/dist/agent-loop.js +2 -0
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.ts +2 -0
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +64 -0
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/mistral.js +3 -0
- package/packages/pi-ai/dist/providers/mistral.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +23 -1
- 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/src/providers/anthropic.ts +65 -1
- package/packages/pi-ai/src/providers/mistral.ts +3 -0
- package/packages/pi-ai/src/types.ts +19 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +32 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.js +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js +12 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.js +7 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts +2 -2
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +8 -3
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -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 +8 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
- package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +2 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +2 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +5 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +41 -3
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +301 -62
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.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 +17 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +5 -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 +135 -30
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/tests/path-display.test.d.ts +8 -0
- package/packages/pi-coding-agent/dist/tests/path-display.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/tests/path-display.test.js +60 -0
- package/packages/pi-coding-agent/dist/tests/path-display.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/utils/clipboard-image.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/utils/clipboard-image.js +32 -6
- package/packages/pi-coding-agent/dist/utils/clipboard-image.js.map +1 -1
- package/packages/pi-coding-agent/dist/utils/path-display.d.ts +34 -0
- package/packages/pi-coding-agent/dist/utils/path-display.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/utils/path-display.js +36 -0
- package/packages/pi-coding-agent/dist/utils/path-display.js.map +1 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +36 -0
- package/packages/pi-coding-agent/src/core/keybindings.ts +1 -1
- package/packages/pi-coding-agent/src/core/lsp/client.ts +11 -1
- package/packages/pi-coding-agent/src/core/lsp/index.ts +7 -0
- package/packages/pi-coding-agent/src/core/sdk.ts +17 -1
- package/packages/pi-coding-agent/src/core/settings-manager.ts +11 -0
- package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
- package/packages/pi-coding-agent/src/core/system-prompt.ts +2 -1
- package/packages/pi-coding-agent/src/index.ts +15 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +347 -62
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +124 -4
- package/packages/pi-coding-agent/src/tests/path-display.test.ts +85 -0
- package/packages/pi-coding-agent/src/utils/clipboard-image.ts +33 -6
- package/packages/pi-coding-agent/src/utils/path-display.ts +36 -0
- package/src/resources/GSD-WORKFLOW.md +12 -9
- package/src/resources/extensions/async-jobs/index.ts +9 -1
- package/src/resources/extensions/bg-shell/index.ts +3 -2
- package/src/resources/extensions/bg-shell/overlay.ts +18 -17
- package/src/resources/extensions/get-secrets-from-user.ts +5 -23
- package/src/resources/extensions/gsd/activity-log.ts +5 -3
- package/src/resources/extensions/gsd/auto-prompts.ts +14 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +7 -4
- package/src/resources/extensions/gsd/auto-worktree.ts +132 -3
- package/src/resources/extensions/gsd/auto.ts +265 -48
- package/src/resources/extensions/gsd/cache.ts +3 -1
- package/src/resources/extensions/gsd/doctor-proactive.ts +7 -6
- package/src/resources/extensions/gsd/doctor.ts +26 -1
- package/src/resources/extensions/gsd/files.ts +13 -2
- package/src/resources/extensions/gsd/git-service.ts +74 -14
- package/src/resources/extensions/gsd/gsd-db.ts +78 -1
- package/src/resources/extensions/gsd/guided-flow.ts +54 -22
- package/src/resources/extensions/gsd/index.ts +62 -8
- package/src/resources/extensions/gsd/memory-extractor.ts +352 -0
- package/src/resources/extensions/gsd/memory-store.ts +441 -0
- package/src/resources/extensions/gsd/migrate/command.ts +2 -2
- package/src/resources/extensions/gsd/migrate/writer.ts +39 -0
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +122 -4
- package/src/resources/extensions/gsd/preferences.ts +2 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +4 -4
- package/src/resources/extensions/gsd/prompts/discuss.md +5 -5
- package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/queue.md +3 -3
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/src/resources/extensions/gsd/roadmap-slices.ts +45 -1
- package/src/resources/extensions/gsd/state.ts +17 -6
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +70 -0
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +23 -3
- package/src/resources/extensions/gsd/tests/git-service.test.ts +70 -4
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
- package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +13 -7
- package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +171 -0
- package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +8 -4
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
- package/src/resources/extensions/gsd/triage-ui.ts +1 -1
- package/src/resources/extensions/gsd/types.ts +2 -0
- package/src/resources/extensions/gsd/visualizer-data.ts +291 -10
- package/src/resources/extensions/gsd/visualizer-overlay.ts +237 -28
- package/src/resources/extensions/gsd/visualizer-views.ts +462 -48
- package/src/resources/extensions/gsd/worktree.ts +9 -2
- package/src/resources/extensions/search-the-web/native-search.ts +19 -5
- package/src/resources/extensions/shared/path-display.ts +19 -0
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { promises as fs } from 'node:fs';
|
|
7
7
|
import { dirname, resolve } from 'node:path';
|
|
8
|
+
import { randomBytes } from 'node:crypto';
|
|
8
9
|
import { resolveMilestoneFile, relMilestoneFile, resolveGsdRootFile } from './paths.js';
|
|
9
10
|
import { milestoneIdSort, findMilestoneIds } from './guided-flow.js';
|
|
10
11
|
|
|
@@ -705,9 +706,19 @@ export async function saveFile(path: string, content: string): Promise<void> {
|
|
|
705
706
|
const dir = dirname(path);
|
|
706
707
|
await fs.mkdir(dir, { recursive: true });
|
|
707
708
|
|
|
708
|
-
|
|
709
|
+
// Use a unique temp path per call to avoid collisions when parallel
|
|
710
|
+
// tool calls target the same file (e.g. concurrent gsd_save_decision).
|
|
711
|
+
// rename() is atomic on POSIX, so last-writer-wins is correct for
|
|
712
|
+
// regenerate-from-DB writes.
|
|
713
|
+
const tmpPath = path + `.tmp.${randomBytes(4).toString("hex")}`;
|
|
709
714
|
await fs.writeFile(tmpPath, content, 'utf-8');
|
|
710
|
-
|
|
715
|
+
try {
|
|
716
|
+
await fs.rename(tmpPath, path);
|
|
717
|
+
} catch (err) {
|
|
718
|
+
// Clean up orphaned temp file on rename failure
|
|
719
|
+
await fs.unlink(tmpPath).catch(() => {});
|
|
720
|
+
throw err;
|
|
721
|
+
}
|
|
711
722
|
}
|
|
712
723
|
|
|
713
724
|
export function parseRequirementCounts(content: string | null): RequirementCounts {
|
|
@@ -68,6 +68,50 @@ export interface CommitOptions {
|
|
|
68
68
|
allowEmpty?: boolean;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
// ─── Meaningful Commit Message Generation ───────────────────────────────────
|
|
72
|
+
|
|
73
|
+
/** Context for generating a meaningful commit message from task execution results. */
|
|
74
|
+
export interface TaskCommitContext {
|
|
75
|
+
taskId: string;
|
|
76
|
+
taskTitle: string;
|
|
77
|
+
/** The one-liner from the task summary (e.g. "Added retry-aware worker status logging") */
|
|
78
|
+
oneLiner?: string;
|
|
79
|
+
/** Files modified by this task (from task summary frontmatter) */
|
|
80
|
+
keyFiles?: string[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Build a meaningful conventional commit message from task execution context.
|
|
85
|
+
* Format: `{type}({sliceId}/{taskId}): {description}`
|
|
86
|
+
*
|
|
87
|
+
* The description is the task summary one-liner if available (it describes
|
|
88
|
+
* what was actually built), falling back to the task title (what was planned).
|
|
89
|
+
*/
|
|
90
|
+
export function buildTaskCommitMessage(ctx: TaskCommitContext): string {
|
|
91
|
+
const scope = ctx.taskId; // e.g. "S01/T02" or just "T02"
|
|
92
|
+
const description = ctx.oneLiner || ctx.taskTitle;
|
|
93
|
+
const type = inferCommitType(ctx.taskTitle, ctx.oneLiner);
|
|
94
|
+
|
|
95
|
+
// Truncate description to ~72 chars for subject line
|
|
96
|
+
const maxDescLen = 68 - type.length - scope.length;
|
|
97
|
+
const truncated = description.length > maxDescLen
|
|
98
|
+
? description.slice(0, maxDescLen - 1).trimEnd() + "…"
|
|
99
|
+
: description;
|
|
100
|
+
|
|
101
|
+
const subject = `${type}(${scope}): ${truncated}`;
|
|
102
|
+
|
|
103
|
+
// Build body with key files if available
|
|
104
|
+
if (ctx.keyFiles && ctx.keyFiles.length > 0) {
|
|
105
|
+
const fileLines = ctx.keyFiles
|
|
106
|
+
.slice(0, 8) // cap at 8 files to keep commit concise
|
|
107
|
+
.map(f => `- ${f}`)
|
|
108
|
+
.join("\n");
|
|
109
|
+
return `${subject}\n\n${fileLines}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return subject;
|
|
113
|
+
}
|
|
114
|
+
|
|
71
115
|
/**
|
|
72
116
|
* Thrown when a slice merge hits code conflicts in non-.gsd files.
|
|
73
117
|
* The working tree is left in a conflicted state (no reset) so the
|
|
@@ -253,18 +297,14 @@ export function runGit(basePath: string, args: string[], options: { allowFailure
|
|
|
253
297
|
* Each entry: [keywords[], commitType]
|
|
254
298
|
*/
|
|
255
299
|
const COMMIT_TYPE_RULES: [string[], string][] = [
|
|
256
|
-
[["fix", "bug", "patch", "hotfix"], "fix"],
|
|
300
|
+
[["fix", "fixed", "fixes", "bug", "patch", "hotfix", "repair", "correct"], "fix"],
|
|
257
301
|
[["refactor", "restructure", "reorganize"], "refactor"],
|
|
258
|
-
[["doc", "docs", "documentation"], "docs"],
|
|
259
|
-
[["test", "tests", "testing"], "test"],
|
|
260
|
-
[["
|
|
302
|
+
[["doc", "docs", "documentation", "readme", "changelog"], "docs"],
|
|
303
|
+
[["test", "tests", "testing", "spec", "coverage"], "test"],
|
|
304
|
+
[["perf", "performance", "optimize", "speed", "cache"], "perf"],
|
|
305
|
+
[["chore", "cleanup", "clean up", "dependencies", "deps", "bump", "config", "ci", "archive", "remove", "delete"], "chore"],
|
|
261
306
|
];
|
|
262
307
|
|
|
263
|
-
/**
|
|
264
|
-
* Infer a conventional commit type from a slice title.
|
|
265
|
-
* Uses case-insensitive word-boundary matching against known keywords.
|
|
266
|
-
* Returns "feat" when no keywords match.
|
|
267
|
-
*/
|
|
268
308
|
// ─── GitServiceImpl ────────────────────────────────────────────────────
|
|
269
309
|
|
|
270
310
|
export class GitServiceImpl {
|
|
@@ -356,11 +396,22 @@ export class GitServiceImpl {
|
|
|
356
396
|
}
|
|
357
397
|
|
|
358
398
|
/**
|
|
359
|
-
* Auto-commit dirty working tree
|
|
399
|
+
* Auto-commit dirty working tree.
|
|
400
|
+
*
|
|
401
|
+
* When `taskContext` is provided, generates a meaningful conventional commit
|
|
402
|
+
* message from the task execution results (one-liner, title, inferred type).
|
|
403
|
+
* Falls back to a generic `chore()` message when no context is available
|
|
404
|
+
* (e.g. pre-switch commits, stop commits, state rebuild commits).
|
|
405
|
+
*
|
|
360
406
|
* Returns the commit message on success, or null if nothing to commit.
|
|
361
407
|
* @param extraExclusions Additional paths to exclude from staging (e.g. [".gsd/"] for pre-switch commits).
|
|
362
408
|
*/
|
|
363
|
-
autoCommit(
|
|
409
|
+
autoCommit(
|
|
410
|
+
unitType: string,
|
|
411
|
+
unitId: string,
|
|
412
|
+
extraExclusions: readonly string[] = [],
|
|
413
|
+
taskContext?: TaskCommitContext,
|
|
414
|
+
): string | null {
|
|
364
415
|
// Quick check: is there anything dirty at all?
|
|
365
416
|
// Native path uses libgit2 (single syscall), fallback spawns git.
|
|
366
417
|
if (!nativeHasChanges(this.basePath)) return null;
|
|
@@ -371,7 +422,9 @@ export class GitServiceImpl {
|
|
|
371
422
|
// (all changes might have been runtime files that got excluded)
|
|
372
423
|
if (!nativeHasStagedChanges(this.basePath)) return null;
|
|
373
424
|
|
|
374
|
-
const message =
|
|
425
|
+
const message = taskContext
|
|
426
|
+
? buildTaskCommitMessage(taskContext)
|
|
427
|
+
: `chore(${unitId}): auto-commit after ${unitType}`;
|
|
375
428
|
nativeCommit(this.basePath, message, { allowEmpty: false });
|
|
376
429
|
return message;
|
|
377
430
|
}
|
|
@@ -497,8 +550,15 @@ export class GitServiceImpl {
|
|
|
497
550
|
|
|
498
551
|
// ─── Commit Type Inference ─────────────────────────────────────────────────
|
|
499
552
|
|
|
500
|
-
|
|
501
|
-
|
|
553
|
+
/**
|
|
554
|
+
* Infer a conventional commit type from a title (and optional one-liner).
|
|
555
|
+
* Uses case-insensitive word-boundary matching against known keywords.
|
|
556
|
+
* Returns "feat" when no keywords match.
|
|
557
|
+
*
|
|
558
|
+
* Used for both slice squash-merge titles and task commit messages.
|
|
559
|
+
*/
|
|
560
|
+
export function inferCommitType(title: string, oneLiner?: string): string {
|
|
561
|
+
const lower = `${title} ${oneLiner || ""}`.toLowerCase();
|
|
502
562
|
|
|
503
563
|
for (const [keywords, commitType] of COMMIT_TYPE_RULES) {
|
|
504
564
|
for (const keyword of keywords) {
|
|
@@ -161,7 +161,7 @@ function openRawDb(path: string): unknown {
|
|
|
161
161
|
|
|
162
162
|
// ─── Schema ────────────────────────────────────────────────────────────────
|
|
163
163
|
|
|
164
|
-
const SCHEMA_VERSION =
|
|
164
|
+
const SCHEMA_VERSION = 3;
|
|
165
165
|
|
|
166
166
|
function initSchema(db: DbAdapter, fileBacked: boolean): void {
|
|
167
167
|
// WAL mode for file-backed databases (must be outside transaction)
|
|
@@ -221,9 +221,36 @@ function initSchema(db: DbAdapter, fileBacked: boolean): void {
|
|
|
221
221
|
)
|
|
222
222
|
`);
|
|
223
223
|
|
|
224
|
+
db.exec(`
|
|
225
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
226
|
+
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
227
|
+
id TEXT NOT NULL UNIQUE,
|
|
228
|
+
category TEXT NOT NULL,
|
|
229
|
+
content TEXT NOT NULL,
|
|
230
|
+
confidence REAL NOT NULL DEFAULT 0.8,
|
|
231
|
+
source_unit_type TEXT,
|
|
232
|
+
source_unit_id TEXT,
|
|
233
|
+
created_at TEXT NOT NULL,
|
|
234
|
+
updated_at TEXT NOT NULL,
|
|
235
|
+
superseded_by TEXT DEFAULT NULL,
|
|
236
|
+
hit_count INTEGER NOT NULL DEFAULT 0
|
|
237
|
+
)
|
|
238
|
+
`);
|
|
239
|
+
|
|
240
|
+
db.exec(`
|
|
241
|
+
CREATE TABLE IF NOT EXISTS memory_processed_units (
|
|
242
|
+
unit_key TEXT PRIMARY KEY,
|
|
243
|
+
activity_file TEXT,
|
|
244
|
+
processed_at TEXT NOT NULL
|
|
245
|
+
)
|
|
246
|
+
`);
|
|
247
|
+
|
|
248
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_memories_active ON memories(superseded_by)');
|
|
249
|
+
|
|
224
250
|
// Views — DROP + CREATE since CREATE VIEW IF NOT EXISTS doesn't update definitions
|
|
225
251
|
db.exec(`CREATE VIEW IF NOT EXISTS active_decisions AS SELECT * FROM decisions WHERE superseded_by IS NULL`);
|
|
226
252
|
db.exec(`CREATE VIEW IF NOT EXISTS active_requirements AS SELECT * FROM requirements WHERE superseded_by IS NULL`);
|
|
253
|
+
db.exec(`CREATE VIEW IF NOT EXISTS active_memories AS SELECT * FROM memories WHERE superseded_by IS NULL`);
|
|
227
254
|
|
|
228
255
|
// Insert schema version if not already present
|
|
229
256
|
const existing = db.prepare('SELECT count(*) as cnt FROM schema_version').get();
|
|
@@ -274,6 +301,41 @@ function migrateSchema(db: DbAdapter): void {
|
|
|
274
301
|
);
|
|
275
302
|
}
|
|
276
303
|
|
|
304
|
+
// v2 → v3: add memories + memory_processed_units tables
|
|
305
|
+
if (currentVersion < 3) {
|
|
306
|
+
db.exec(`
|
|
307
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
308
|
+
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
309
|
+
id TEXT NOT NULL UNIQUE,
|
|
310
|
+
category TEXT NOT NULL,
|
|
311
|
+
content TEXT NOT NULL,
|
|
312
|
+
confidence REAL NOT NULL DEFAULT 0.8,
|
|
313
|
+
source_unit_type TEXT,
|
|
314
|
+
source_unit_id TEXT,
|
|
315
|
+
created_at TEXT NOT NULL,
|
|
316
|
+
updated_at TEXT NOT NULL,
|
|
317
|
+
superseded_by TEXT DEFAULT NULL,
|
|
318
|
+
hit_count INTEGER NOT NULL DEFAULT 0
|
|
319
|
+
)
|
|
320
|
+
`);
|
|
321
|
+
|
|
322
|
+
db.exec(`
|
|
323
|
+
CREATE TABLE IF NOT EXISTS memory_processed_units (
|
|
324
|
+
unit_key TEXT PRIMARY KEY,
|
|
325
|
+
activity_file TEXT,
|
|
326
|
+
processed_at TEXT NOT NULL
|
|
327
|
+
)
|
|
328
|
+
`);
|
|
329
|
+
|
|
330
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_memories_active ON memories(superseded_by)');
|
|
331
|
+
db.exec('DROP VIEW IF EXISTS active_memories');
|
|
332
|
+
db.exec('CREATE VIEW active_memories AS SELECT * FROM memories WHERE superseded_by IS NULL');
|
|
333
|
+
|
|
334
|
+
db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)').run(
|
|
335
|
+
{ ':version': 3, ':applied_at': new Date().toISOString() },
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
277
339
|
db.exec('COMMIT');
|
|
278
340
|
} catch (err) {
|
|
279
341
|
db.exec('ROLLBACK');
|
|
@@ -728,6 +790,21 @@ export function upsertRequirement(r: Requirement): void {
|
|
|
728
790
|
/**
|
|
729
791
|
* Insert or replace an artifact. Uses the `path` PK for idempotency.
|
|
730
792
|
*/
|
|
793
|
+
/**
|
|
794
|
+
* Delete all rows from the artifacts table.
|
|
795
|
+
* The artifacts table is a read cache — clearing it forces the next
|
|
796
|
+
* deriveState() to fall through to disk reads (native Rust batch parse).
|
|
797
|
+
* Safe to call when no database is open (no-op).
|
|
798
|
+
*/
|
|
799
|
+
export function clearArtifacts(): void {
|
|
800
|
+
if (!currentDb) return;
|
|
801
|
+
try {
|
|
802
|
+
currentDb.exec('DELETE FROM artifacts');
|
|
803
|
+
} catch {
|
|
804
|
+
// Clearing a cache should never be fatal
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
731
808
|
export function insertArtifact(a: {
|
|
732
809
|
path: string;
|
|
733
810
|
artifact_type: string;
|
|
@@ -29,6 +29,17 @@ import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
|
29
29
|
import { showConfirm } from "../shared/confirm-ui.js";
|
|
30
30
|
import { loadQueueOrder, sortByQueueOrder, saveQueueOrder } from "./queue-order.js";
|
|
31
31
|
|
|
32
|
+
// ─── Commit Instruction Helpers ──────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
/** Build conditional commit instruction for planning prompts based on commit_docs preference. */
|
|
35
|
+
function buildDocsCommitInstruction(message: string): string {
|
|
36
|
+
const prefs = loadEffectiveGSDPreferences();
|
|
37
|
+
const commitDocsEnabled = prefs?.preferences?.git?.commit_docs !== false;
|
|
38
|
+
return commitDocsEnabled
|
|
39
|
+
? `Commit: \`${message}\``
|
|
40
|
+
: "Do not commit — planning docs are not tracked in git for this project.";
|
|
41
|
+
}
|
|
42
|
+
|
|
32
43
|
// ─── Auto-start after discuss ─────────────────────────────────────────────────
|
|
33
44
|
|
|
34
45
|
/** Stashed context + flag for auto-starting after discuss phase completes */
|
|
@@ -198,6 +209,8 @@ function buildDiscussPrompt(nextId: string, preamble: string, _basePath: string)
|
|
|
198
209
|
contextPath: `${milestoneRel}/${nextId}-CONTEXT.md`,
|
|
199
210
|
roadmapPath: `${milestoneRel}/${nextId}-ROADMAP.md`,
|
|
200
211
|
inlinedTemplates,
|
|
212
|
+
commitInstruction: buildDocsCommitInstruction(`docs(${nextId}): context, requirements, and roadmap`),
|
|
213
|
+
multiMilestoneCommitInstruction: buildDocsCommitInstruction("docs: project plan — N milestones"),
|
|
201
214
|
});
|
|
202
215
|
}
|
|
203
216
|
|
|
@@ -220,6 +233,8 @@ function buildHeadlessDiscussPrompt(nextId: string, seedContext: string, _basePa
|
|
|
220
233
|
contextPath: `${milestoneRel}/${nextId}-CONTEXT.md`,
|
|
221
234
|
roadmapPath: `${milestoneRel}/${nextId}-ROADMAP.md`,
|
|
222
235
|
inlinedTemplates,
|
|
236
|
+
commitInstruction: buildDocsCommitInstruction(`docs(${nextId}): context, requirements, and roadmap`),
|
|
237
|
+
multiMilestoneCommitInstruction: buildDocsCommitInstruction("docs: project plan — N milestones"),
|
|
223
238
|
});
|
|
224
239
|
}
|
|
225
240
|
|
|
@@ -621,9 +636,11 @@ async function showQueueAdd(
|
|
|
621
636
|
const existingContext = await buildExistingMilestonesContext(basePath, milestoneIds, state);
|
|
622
637
|
|
|
623
638
|
// ── Determine next milestone ID ─────────────────────────────────────
|
|
639
|
+
// Note: the LLM will use the gsd_generate_milestone_id tool to get IDs
|
|
640
|
+
// at creation time, but we still mention the next ID in the preamble
|
|
641
|
+
// for context about where the sequence is.
|
|
624
642
|
const uniqueEnabled = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
625
643
|
const nextId = nextMilestoneId(milestoneIds, uniqueEnabled);
|
|
626
|
-
const nextIdPlus1 = nextMilestoneId([...milestoneIds, nextId], uniqueEnabled);
|
|
627
644
|
|
|
628
645
|
// ── Build preamble ──────────────────────────────────────────────────
|
|
629
646
|
const activePart = state.activeMilestone
|
|
@@ -644,10 +661,9 @@ async function showQueueAdd(
|
|
|
644
661
|
const queueInlinedTemplates = inlineTemplate("context", "Context");
|
|
645
662
|
const prompt = loadPrompt("queue", {
|
|
646
663
|
preamble,
|
|
647
|
-
nextId,
|
|
648
|
-
nextIdPlus1,
|
|
649
664
|
existingMilestonesContext: existingContext,
|
|
650
665
|
inlinedTemplates: queueInlinedTemplates,
|
|
666
|
+
commitInstruction: buildDocsCommitInstruction("docs: queue <milestone list>"),
|
|
651
667
|
});
|
|
652
668
|
|
|
653
669
|
pi.sendMessage(
|
|
@@ -834,6 +850,7 @@ async function buildDiscussSlicePrompt(
|
|
|
834
850
|
contextPath: sliceContextPath,
|
|
835
851
|
projectRoot: base,
|
|
836
852
|
inlinedTemplates,
|
|
853
|
+
commitInstruction: buildDocsCommitInstruction(`docs(${mid}/${sid}): slice context from discuss`),
|
|
837
854
|
});
|
|
838
855
|
}
|
|
839
856
|
|
|
@@ -870,7 +887,7 @@ export async function showDiscuss(
|
|
|
870
887
|
const draftFile = resolveMilestoneFile(basePath, mid, "CONTEXT-DRAFT");
|
|
871
888
|
const draftContent = draftFile ? await loadFile(draftFile) : null;
|
|
872
889
|
|
|
873
|
-
const choice = await showNextAction(ctx
|
|
890
|
+
const choice = await showNextAction(ctx, {
|
|
874
891
|
title: `GSD — ${mid}: ${milestoneTitle}`,
|
|
875
892
|
summary: ["This milestone has a draft context from a prior discussion.", "It needs a dedicated discussion before auto-planning can begin."],
|
|
876
893
|
actions: [
|
|
@@ -899,6 +916,7 @@ export async function showDiscuss(
|
|
|
899
916
|
const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
|
|
900
917
|
const basePrompt = loadPrompt("guided-discuss-milestone", {
|
|
901
918
|
milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
919
|
+
commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
|
|
902
920
|
});
|
|
903
921
|
const seed = draftContent
|
|
904
922
|
? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
|
|
@@ -911,6 +929,7 @@ export async function showDiscuss(
|
|
|
911
929
|
pendingAutoStart = { ctx, pi, basePath, milestoneId: mid, step: false };
|
|
912
930
|
dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
913
931
|
milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
932
|
+
commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
|
|
914
933
|
}), "gsd-discuss");
|
|
915
934
|
} else if (choice === "skip_milestone") {
|
|
916
935
|
const milestoneIds = findMilestoneIds(basePath);
|
|
@@ -940,14 +959,24 @@ export async function showDiscuss(
|
|
|
940
959
|
|
|
941
960
|
// Loop: show picker, dispatch discuss, repeat until "not_yet"
|
|
942
961
|
while (true) {
|
|
943
|
-
const actions = pendingSlices.map((s, i) =>
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
962
|
+
const actions = pendingSlices.map((s, i) => {
|
|
963
|
+
// Check if this slice has already been discussed (CONTEXT file exists)
|
|
964
|
+
const contextFile = resolveSliceFile(basePath, mid, s.id, "CONTEXT");
|
|
965
|
+
const discussed = !!contextFile;
|
|
966
|
+
const statusParts: string[] = [];
|
|
967
|
+
if (state.activeSlice?.id === s.id) statusParts.push("active");
|
|
968
|
+
else statusParts.push("upcoming");
|
|
969
|
+
statusParts.push(discussed ? "discussed ✓" : "not discussed");
|
|
970
|
+
|
|
971
|
+
return {
|
|
972
|
+
id: s.id,
|
|
973
|
+
label: `${s.id}: ${s.title}`,
|
|
974
|
+
description: statusParts.join(" · "),
|
|
975
|
+
recommended: i === 0,
|
|
976
|
+
};
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
const choice = await showNextAction(ctx, {
|
|
951
980
|
title: "GSD — Discuss a slice",
|
|
952
981
|
summary: [
|
|
953
982
|
`${mid}: ${milestoneTitle}`,
|
|
@@ -1056,7 +1085,7 @@ export async function showSmartEntry(
|
|
|
1056
1085
|
const crashLock = readCrashLock(basePath);
|
|
1057
1086
|
if (crashLock) {
|
|
1058
1087
|
clearLock(basePath);
|
|
1059
|
-
const resume = await showNextAction(ctx
|
|
1088
|
+
const resume = await showNextAction(ctx, {
|
|
1060
1089
|
title: "GSD — Interrupted Session Detected",
|
|
1061
1090
|
summary: [formatCrashInfo(crashLock)],
|
|
1062
1091
|
actions: [
|
|
@@ -1116,7 +1145,7 @@ export async function showSmartEntry(
|
|
|
1116
1145
|
basePath
|
|
1117
1146
|
));
|
|
1118
1147
|
} else {
|
|
1119
|
-
const choice = await showNextAction(ctx
|
|
1148
|
+
const choice = await showNextAction(ctx, {
|
|
1120
1149
|
title: "GSD — Get Shit Done",
|
|
1121
1150
|
summary: ["No active milestone."],
|
|
1122
1151
|
actions: [
|
|
@@ -1146,7 +1175,7 @@ export async function showSmartEntry(
|
|
|
1146
1175
|
|
|
1147
1176
|
// ── All milestones complete → New milestone ──────────────────────────
|
|
1148
1177
|
if (state.phase === "complete") {
|
|
1149
|
-
const choice = await showNextAction(ctx
|
|
1178
|
+
const choice = await showNextAction(ctx, {
|
|
1150
1179
|
title: `GSD — ${milestoneId}: ${milestoneTitle}`,
|
|
1151
1180
|
summary: ["All milestones complete."],
|
|
1152
1181
|
actions: [
|
|
@@ -1187,7 +1216,7 @@ export async function showSmartEntry(
|
|
|
1187
1216
|
const draftFile = resolveMilestoneFile(basePath, milestoneId, "CONTEXT-DRAFT");
|
|
1188
1217
|
const draftContent = draftFile ? await loadFile(draftFile) : null;
|
|
1189
1218
|
|
|
1190
|
-
const choice = await showNextAction(ctx
|
|
1219
|
+
const choice = await showNextAction(ctx, {
|
|
1191
1220
|
title: `GSD — ${milestoneId}: ${milestoneTitle}`,
|
|
1192
1221
|
summary: ["This milestone has a draft context from a prior discussion.", "It needs a dedicated discussion before auto-planning can begin."],
|
|
1193
1222
|
actions: [
|
|
@@ -1216,6 +1245,7 @@ export async function showSmartEntry(
|
|
|
1216
1245
|
const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
|
|
1217
1246
|
const basePrompt = loadPrompt("guided-discuss-milestone", {
|
|
1218
1247
|
milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
1248
|
+
commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
|
|
1219
1249
|
});
|
|
1220
1250
|
const seed = draftContent
|
|
1221
1251
|
? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
|
|
@@ -1228,6 +1258,7 @@ export async function showSmartEntry(
|
|
|
1228
1258
|
pendingAutoStart = { ctx, pi, basePath, milestoneId, step: stepMode };
|
|
1229
1259
|
dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
1230
1260
|
milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
1261
|
+
commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
|
|
1231
1262
|
}), "gsd-discuss");
|
|
1232
1263
|
} else if (choice === "skip_milestone") {
|
|
1233
1264
|
const milestoneIds = findMilestoneIds(basePath);
|
|
@@ -1278,7 +1309,7 @@ export async function showSmartEntry(
|
|
|
1278
1309
|
},
|
|
1279
1310
|
];
|
|
1280
1311
|
|
|
1281
|
-
const choice = await showNextAction(ctx
|
|
1312
|
+
const choice = await showNextAction(ctx, {
|
|
1282
1313
|
title: `GSD — ${milestoneId}: ${milestoneTitle}`,
|
|
1283
1314
|
summary: [hasContext ? "Context captured. Ready to create roadmap." : "New milestone — no roadmap yet."],
|
|
1284
1315
|
actions,
|
|
@@ -1302,6 +1333,7 @@ export async function showSmartEntry(
|
|
|
1302
1333
|
const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
|
|
1303
1334
|
dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
1304
1335
|
milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
1336
|
+
commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
|
|
1305
1337
|
}));
|
|
1306
1338
|
} else if (choice === "skip_milestone") {
|
|
1307
1339
|
const milestoneIds = findMilestoneIds(basePath);
|
|
@@ -1315,7 +1347,7 @@ export async function showSmartEntry(
|
|
|
1315
1347
|
} else if (choice === "discard_milestone") {
|
|
1316
1348
|
const mDir = resolveMilestonePath(basePath, milestoneId);
|
|
1317
1349
|
if (!mDir) return;
|
|
1318
|
-
const confirmed = await showConfirm(ctx
|
|
1350
|
+
const confirmed = await showConfirm(ctx, {
|
|
1319
1351
|
title: "Discard milestone?",
|
|
1320
1352
|
message: `This will permanently delete ${milestoneId} and all its contents.`,
|
|
1321
1353
|
confirmLabel: "Discard",
|
|
@@ -1342,7 +1374,7 @@ export async function showSmartEntry(
|
|
|
1342
1374
|
},
|
|
1343
1375
|
];
|
|
1344
1376
|
|
|
1345
|
-
const choice = await showNextAction(ctx
|
|
1377
|
+
const choice = await showNextAction(ctx, {
|
|
1346
1378
|
title: `GSD — ${milestoneId}: ${milestoneTitle}`,
|
|
1347
1379
|
summary: ["Roadmap exists. Ready to execute."],
|
|
1348
1380
|
actions,
|
|
@@ -1400,7 +1432,7 @@ export async function showSmartEntry(
|
|
|
1400
1432
|
? `${sliceId}: ${sliceTitle} (${summaryParts.join(", ")})`
|
|
1401
1433
|
: `${sliceId}: ${sliceTitle} — ready for planning.`;
|
|
1402
1434
|
|
|
1403
|
-
const choice = await showNextAction(ctx
|
|
1435
|
+
const choice = await showNextAction(ctx, {
|
|
1404
1436
|
title: `GSD — ${milestoneId} / ${sliceId}: ${sliceTitle}`,
|
|
1405
1437
|
summary: [summaryLine],
|
|
1406
1438
|
actions,
|
|
@@ -1431,7 +1463,7 @@ export async function showSmartEntry(
|
|
|
1431
1463
|
|
|
1432
1464
|
// ── All tasks done → Complete slice ──────────────────────────────────
|
|
1433
1465
|
if (state.phase === "summarizing") {
|
|
1434
|
-
const choice = await showNextAction(ctx
|
|
1466
|
+
const choice = await showNextAction(ctx, {
|
|
1435
1467
|
title: `GSD — ${milestoneId} / ${sliceId}: ${sliceTitle}`,
|
|
1436
1468
|
summary: ["All tasks complete. Ready for slice summary."],
|
|
1437
1469
|
actions: [
|
|
@@ -1475,7 +1507,7 @@ export async function showSmartEntry(
|
|
|
1475
1507
|
const hasInterrupted = !!(continueFile && await loadFile(continueFile)) ||
|
|
1476
1508
|
!!(sDir && await loadFile(join(sDir, "continue.md")));
|
|
1477
1509
|
|
|
1478
|
-
const choice = await showNextAction(ctx
|
|
1510
|
+
const choice = await showNextAction(ctx, {
|
|
1479
1511
|
title: `GSD — ${milestoneId} / ${sliceId}: ${sliceTitle}`,
|
|
1480
1512
|
summary: [
|
|
1481
1513
|
hasInterrupted
|
|
@@ -36,7 +36,7 @@ import { loadPrompt } from "./prompt-loader.js";
|
|
|
36
36
|
import { deriveState } from "./state.js";
|
|
37
37
|
import { isAutoActive, isAutoPaused, handleAgentEnd, pauseAuto, getAutoDashboardData, markToolStart, markToolEnd } from "./auto.js";
|
|
38
38
|
import { saveActivityLog } from "./activity-log.js";
|
|
39
|
-
import { checkAutoStartAfterDiscuss, getDiscussionMilestoneId } from "./guided-flow.js";
|
|
39
|
+
import { checkAutoStartAfterDiscuss, getDiscussionMilestoneId, findMilestoneIds, nextMilestoneId } from "./guided-flow.js";
|
|
40
40
|
import { GSDDashboardOverlay } from "./dashboard-overlay.js";
|
|
41
41
|
import {
|
|
42
42
|
loadEffectiveGSDPreferences,
|
|
@@ -59,6 +59,7 @@ import { homedir } from "node:os";
|
|
|
59
59
|
import { shortcutDesc } from "../shared/terminal.js";
|
|
60
60
|
import { Text } from "@gsd/pi-tui";
|
|
61
61
|
import { pauseAutoForProviderError } from "./provider-error-pause.js";
|
|
62
|
+
import { toPosixPath } from "../shared/path-display.js";
|
|
62
63
|
|
|
63
64
|
// ── Agent Instructions ────────────────────────────────────────────────────
|
|
64
65
|
// Lightweight "always follow" files injected into every GSD agent session.
|
|
@@ -467,6 +468,46 @@ export default function (pi: ExtensionAPI) {
|
|
|
467
468
|
},
|
|
468
469
|
});
|
|
469
470
|
|
|
471
|
+
// ── gsd_generate_milestone_id — canonical milestone ID generation ──────
|
|
472
|
+
// The LLM cannot generate random suffixes for unique_milestone_ids on its
|
|
473
|
+
// own. This tool calls back into the TS code that owns ID generation,
|
|
474
|
+
// ensuring the preference is always respected and IDs are always valid.
|
|
475
|
+
pi.registerTool({
|
|
476
|
+
name: "gsd_generate_milestone_id",
|
|
477
|
+
label: "Generate Milestone ID",
|
|
478
|
+
description:
|
|
479
|
+
"Generate the next milestone ID for a new GSD milestone. " +
|
|
480
|
+
"Scans existing milestones on disk and respects the unique_milestone_ids preference. " +
|
|
481
|
+
"Always use this tool when creating a new milestone — never invent milestone IDs manually.",
|
|
482
|
+
promptSnippet: "Generate a valid milestone ID (respects unique_milestone_ids preference)",
|
|
483
|
+
promptGuidelines: [
|
|
484
|
+
"ALWAYS call gsd_generate_milestone_id before creating a new milestone directory or writing milestone files.",
|
|
485
|
+
"Never invent or hardcode milestone IDs like M001, M002 — always use this tool.",
|
|
486
|
+
"Call it once per milestone you need to create. For multi-milestone projects, call it once for each milestone in sequence.",
|
|
487
|
+
"The tool returns the correct format based on project preferences (e.g. M001 or M001-r5jzab).",
|
|
488
|
+
],
|
|
489
|
+
parameters: Type.Object({}),
|
|
490
|
+
async execute(_toolCallId, _params, _signal, _onUpdate, _ctx) {
|
|
491
|
+
try {
|
|
492
|
+
const basePath = process.cwd();
|
|
493
|
+
const existingIds = findMilestoneIds(basePath);
|
|
494
|
+
const uniqueEnabled = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
495
|
+
const newId = nextMilestoneId(existingIds, uniqueEnabled);
|
|
496
|
+
return {
|
|
497
|
+
content: [{ type: "text" as const, text: newId }],
|
|
498
|
+
details: { operation: "generate_milestone_id", id: newId, existingCount: existingIds.length, uniqueEnabled },
|
|
499
|
+
};
|
|
500
|
+
} catch (err) {
|
|
501
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
502
|
+
return {
|
|
503
|
+
content: [{ type: "text" as const, text: `Error generating milestone ID: ${msg}` }],
|
|
504
|
+
isError: true,
|
|
505
|
+
details: { operation: "generate_milestone_id", error: msg },
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
});
|
|
510
|
+
|
|
470
511
|
// ── session_start: render branded GSD header + load tool keys + remote status ──
|
|
471
512
|
pi.on("session_start", async (_event, ctx) => {
|
|
472
513
|
// Theme access throws in RPC mode (no TUI) — header is decorative, skip it
|
|
@@ -566,6 +607,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
566
607
|
}
|
|
567
608
|
}
|
|
568
609
|
|
|
610
|
+
// Inject auto-learned project memories
|
|
611
|
+
let memoryBlock = "";
|
|
612
|
+
try {
|
|
613
|
+
const { getActiveMemoriesRanked, formatMemoriesForPrompt } = await import("./memory-store.js");
|
|
614
|
+
const memories = getActiveMemoriesRanked(30);
|
|
615
|
+
if (memories.length > 0) {
|
|
616
|
+
const formatted = formatMemoriesForPrompt(memories, 2000);
|
|
617
|
+
if (formatted) {
|
|
618
|
+
memoryBlock = `\n\n${formatted}`;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
} catch { /* non-fatal */ }
|
|
622
|
+
|
|
569
623
|
// Detect skills installed during this auto-mode session
|
|
570
624
|
let newSkillsBlock = "";
|
|
571
625
|
if (hasSkillSnapshot()) {
|
|
@@ -595,12 +649,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
595
649
|
"",
|
|
596
650
|
"[WORKTREE CONTEXT — OVERRIDES CURRENT WORKING DIRECTORY ABOVE]",
|
|
597
651
|
`IMPORTANT: Ignore the "Current working directory" shown earlier in this prompt.`,
|
|
598
|
-
`The actual current working directory is: ${process.cwd()}`,
|
|
652
|
+
`The actual current working directory is: ${toPosixPath(process.cwd())}`,
|
|
599
653
|
"",
|
|
600
654
|
`You are working inside a GSD worktree.`,
|
|
601
655
|
`- Worktree name: ${worktreeName}`,
|
|
602
|
-
`- Worktree path (this is the real cwd): ${process.cwd()}`,
|
|
603
|
-
`- Main project: ${worktreeMainCwd}`,
|
|
656
|
+
`- Worktree path (this is the real cwd): ${toPosixPath(process.cwd())}`,
|
|
657
|
+
`- Main project: ${toPosixPath(worktreeMainCwd)}`,
|
|
604
658
|
`- Branch: worktree/${worktreeName}`,
|
|
605
659
|
"",
|
|
606
660
|
"All file operations, bash commands, and GSD state resolve against the worktree path above.",
|
|
@@ -612,12 +666,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
612
666
|
"",
|
|
613
667
|
"[WORKTREE CONTEXT — OVERRIDES CURRENT WORKING DIRECTORY ABOVE]",
|
|
614
668
|
`IMPORTANT: Ignore the "Current working directory" shown earlier in this prompt.`,
|
|
615
|
-
`The actual current working directory is: ${process.cwd()}`,
|
|
669
|
+
`The actual current working directory is: ${toPosixPath(process.cwd())}`,
|
|
616
670
|
"",
|
|
617
671
|
"You are working inside a GSD auto-worktree.",
|
|
618
672
|
`- Milestone worktree: ${autoWorktree.worktreeName}`,
|
|
619
|
-
`- Worktree path (this is the real cwd): ${process.cwd()}`,
|
|
620
|
-
`- Main project: ${autoWorktree.originalBase}`,
|
|
673
|
+
`- Worktree path (this is the real cwd): ${toPosixPath(process.cwd())}`,
|
|
674
|
+
`- Main project: ${toPosixPath(autoWorktree.originalBase)}`,
|
|
621
675
|
`- Branch: ${autoWorktree.branch}`,
|
|
622
676
|
"",
|
|
623
677
|
"All file operations, bash commands, and GSD state resolve against the worktree path above.",
|
|
@@ -625,7 +679,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
625
679
|
].join("\n");
|
|
626
680
|
}
|
|
627
681
|
|
|
628
|
-
const fullSystem = `${event.systemPrompt}\n\n[SYSTEM CONTEXT — GSD]\n\n${systemContent}${preferenceBlock}${agentInstructionsBlock}${knowledgeBlock}${newSkillsBlock}${worktreeBlock}`;
|
|
682
|
+
const fullSystem = `${event.systemPrompt}\n\n[SYSTEM CONTEXT — GSD]\n\n${systemContent}${preferenceBlock}${agentInstructionsBlock}${knowledgeBlock}${memoryBlock}${newSkillsBlock}${worktreeBlock}`;
|
|
629
683
|
stopContextTimer({
|
|
630
684
|
systemPromptSize: fullSystem.length,
|
|
631
685
|
injectionSize: injection?.length ?? 0,
|