gsd-pi 2.8.0 → 2.8.2
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/loader.js +5 -0
- package/node_modules/@gsd/pi-coding-agent/dist/config.d.ts +2 -0
- package/node_modules/@gsd/pi-coding-agent/dist/config.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/config.js +4 -0
- package/node_modules/@gsd/pi-coding-agent/dist/config.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/artifact-manager.d.ts +52 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/artifact-manager.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/artifact-manager.js +117 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/artifact-manager.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/bash-executor.js +2 -2
- package/node_modules/@gsd/pi-coding-agent/dist/core/bash-executor.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/blob-store.d.ts +31 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/blob-store.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/blob-store.js +97 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/blob-store.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/session-manager.d.ts +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/session-manager.js +112 -3
- package/node_modules/@gsd/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash.d.ts +4 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash.js +32 -22
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.d.ts +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.js +13 -2
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.test.d.ts +2 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.test.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.test.js +57 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.test.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/index.d.ts +3 -1
- package/node_modules/@gsd/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/index.js +4 -1
- package/node_modules/@gsd/pi-coding-agent/dist/index.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js +7 -5
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/utils/shell.d.ts +7 -0
- package/node_modules/@gsd/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/utils/shell.js +11 -0
- package/node_modules/@gsd/pi-coding-agent/dist/utils/shell.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/src/config.ts +5 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/artifact-manager.ts +125 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/bash-executor.ts +2 -2
- package/node_modules/@gsd/pi-coding-agent/src/core/blob-store.ts +106 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/session-manager.ts +119 -3
- package/node_modules/@gsd/pi-coding-agent/src/core/tools/bash.ts +35 -22
- package/node_modules/@gsd/pi-coding-agent/src/core/tools/path-utils.test.ts +66 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/tools/path-utils.ts +14 -2
- package/node_modules/@gsd/pi-coding-agent/src/index.ts +4 -1
- package/node_modules/@gsd/pi-coding-agent/src/modes/interactive/interactive-mode.ts +6 -4
- package/node_modules/@gsd/pi-coding-agent/src/utils/shell.ts +11 -0
- package/package.json +6 -1
- package/packages/pi-coding-agent/dist/config.d.ts +2 -0
- package/packages/pi-coding-agent/dist/config.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/config.js +4 -0
- package/packages/pi-coding-agent/dist/config.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/artifact-manager.d.ts +52 -0
- package/packages/pi-coding-agent/dist/core/artifact-manager.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/artifact-manager.js +117 -0
- package/packages/pi-coding-agent/dist/core/artifact-manager.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/bash-executor.js +2 -2
- package/packages/pi-coding-agent/dist/core/bash-executor.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/blob-store.d.ts +31 -0
- package/packages/pi-coding-agent/dist/core/blob-store.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/blob-store.js +97 -0
- package/packages/pi-coding-agent/dist/core/blob-store.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js +112 -3
- package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +4 -0
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js +32 -22
- package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/path-utils.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/tools/path-utils.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/path-utils.js +13 -2
- package/packages/pi-coding-agent/dist/core/tools/path-utils.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/path-utils.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/tools/path-utils.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/path-utils.test.js +57 -0
- package/packages/pi-coding-agent/dist/core/tools/path-utils.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/index.d.ts +3 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +4 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- 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 +7 -5
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/utils/shell.d.ts +7 -0
- package/packages/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/utils/shell.js +11 -0
- package/packages/pi-coding-agent/dist/utils/shell.js.map +1 -1
- package/packages/pi-coding-agent/src/config.ts +5 -0
- package/packages/pi-coding-agent/src/core/artifact-manager.ts +125 -0
- package/packages/pi-coding-agent/src/core/bash-executor.ts +2 -2
- package/packages/pi-coding-agent/src/core/blob-store.ts +106 -0
- package/packages/pi-coding-agent/src/core/session-manager.ts +119 -3
- package/packages/pi-coding-agent/src/core/tools/bash.ts +35 -22
- package/packages/pi-coding-agent/src/core/tools/path-utils.test.ts +66 -0
- package/packages/pi-coding-agent/src/core/tools/path-utils.ts +14 -2
- package/packages/pi-coding-agent/src/index.ts +4 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +6 -4
- package/packages/pi-coding-agent/src/utils/shell.ts +11 -0
- package/src/resources/extensions/bg-shell/index.ts +2 -1
- package/src/resources/extensions/browser-tools/lifecycle.ts +6 -1
- package/src/resources/extensions/gsd/auto.ts +92 -49
- package/src/resources/extensions/gsd/dispatch-guard.ts +65 -0
- package/src/resources/extensions/gsd/docs/preferences-reference.md +76 -0
- package/src/resources/extensions/gsd/exit-command.ts +18 -0
- package/src/resources/extensions/gsd/files.ts +9 -40
- package/src/resources/extensions/gsd/git-service.ts +62 -17
- package/src/resources/extensions/gsd/gitignore.ts +28 -0
- package/src/resources/extensions/gsd/guided-flow.ts +49 -11
- package/src/resources/extensions/gsd/index.ts +111 -16
- package/src/resources/extensions/gsd/preferences.ts +8 -0
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -2
- package/src/resources/extensions/gsd/prompts/complete-slice.md +3 -3
- package/src/resources/extensions/gsd/prompts/discuss.md +27 -2
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -3
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -2
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +3 -3
- package/src/resources/extensions/gsd/prompts/replan-slice.md +2 -2
- package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +4 -4
- package/src/resources/extensions/gsd/roadmap-slices.ts +50 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/exit-command.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +116 -39
- package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +5 -5
- package/src/resources/extensions/gsd/tests/replan-slice.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +2 -4
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +122 -0
- package/src/resources/extensions/ttsr/index.ts +163 -0
- package/src/resources/extensions/ttsr/rule-loader.ts +121 -0
- package/src/resources/extensions/ttsr/ttsr-interrupt.md +6 -0
- package/src/resources/extensions/ttsr/ttsr-manager.ts +344 -0
|
@@ -27,6 +27,7 @@ export interface GitPreferences {
|
|
|
27
27
|
pre_merge_check?: boolean | string;
|
|
28
28
|
commit_type?: string;
|
|
29
29
|
main_branch?: string;
|
|
30
|
+
merge_strategy?: "squash" | "merge";
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
export const VALID_BRANCH_NAME = /^[a-zA-Z0-9_\-\/.]+$/;
|
|
@@ -55,7 +56,7 @@ export interface PreMergeCheckResult {
|
|
|
55
56
|
* GSD runtime paths that should be excluded from smart staging.
|
|
56
57
|
* These are transient/generated artifacts that should never be committed.
|
|
57
58
|
* Matches the union of SKIP_PATHS + SKIP_EXACT in worktree-manager.ts
|
|
58
|
-
* and the first
|
|
59
|
+
* and the first 7 entries in gitignore.ts BASELINE_PATTERNS.
|
|
59
60
|
*/
|
|
60
61
|
export const RUNTIME_EXCLUSION_PATHS: readonly string[] = [
|
|
61
62
|
".gsd/activity/",
|
|
@@ -63,6 +64,7 @@ export const RUNTIME_EXCLUSION_PATHS: readonly string[] = [
|
|
|
63
64
|
".gsd/worktrees/",
|
|
64
65
|
".gsd/auto.lock",
|
|
65
66
|
".gsd/metrics.json",
|
|
67
|
+
".gsd/completed-units.json",
|
|
66
68
|
".gsd/STATE.md",
|
|
67
69
|
];
|
|
68
70
|
|
|
@@ -130,16 +132,42 @@ export class GitServiceImpl {
|
|
|
130
132
|
*/
|
|
131
133
|
private smartStage(extraExclusions: readonly string[] = []): void {
|
|
132
134
|
const allExclusions = [...RUNTIME_EXCLUSION_PATHS, ...extraExclusions];
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
135
|
+
|
|
136
|
+
// One-time cleanup: if runtime files are already tracked in the index
|
|
137
|
+
// (from older versions where the fallback bug staged them), untrack them
|
|
138
|
+
// in a dedicated commit. This must happen as a separate commit because
|
|
139
|
+
// the git reset HEAD step below would otherwise undo the rm --cached.
|
|
140
|
+
if (!this._runtimeFilesCleanedUp) {
|
|
141
|
+
let cleaned = false;
|
|
142
|
+
for (const exclusion of RUNTIME_EXCLUSION_PATHS) {
|
|
143
|
+
const result = this.git(["rm", "--cached", "-r", "--ignore-unmatch", exclusion], { allowFailure: true });
|
|
144
|
+
if (result && result.includes("rm '")) cleaned = true;
|
|
145
|
+
}
|
|
146
|
+
if (cleaned) {
|
|
147
|
+
this.git(["commit", "-F", "-"], { input: "chore: untrack .gsd/ runtime files from git index" });
|
|
148
|
+
}
|
|
149
|
+
this._runtimeFilesCleanedUp = true;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Stage everything, then unstage excluded paths.
|
|
153
|
+
//
|
|
154
|
+
// Previous approach used pathspec excludes (:(exclude)...) with git add -A,
|
|
155
|
+
// but that fails when .gsd/ is in .gitignore — git exits non-zero before
|
|
156
|
+
// evaluating the excludes. The catch fallback ran plain `git add -A`,
|
|
157
|
+
// staging all tracked runtime files unconditionally and defeating the
|
|
158
|
+
// exclusion list entirely.
|
|
159
|
+
//
|
|
160
|
+
// git reset HEAD silently succeeds when the path isn't staged, so no
|
|
161
|
+
// error handling is needed per-path.
|
|
162
|
+
this.git(["add", "-A"]);
|
|
163
|
+
for (const exclusion of allExclusions) {
|
|
164
|
+
this.git(["reset", "HEAD", "--", exclusion], { allowFailure: true });
|
|
140
165
|
}
|
|
141
166
|
}
|
|
142
167
|
|
|
168
|
+
/** Tracks whether runtime file cleanup has run this session. */
|
|
169
|
+
private _runtimeFilesCleanedUp = false;
|
|
170
|
+
|
|
143
171
|
/**
|
|
144
172
|
* Stage files (smart staging) and commit.
|
|
145
173
|
* Returns the commit message string on success, or null if nothing to commit.
|
|
@@ -312,6 +340,12 @@ export class GitServiceImpl {
|
|
|
312
340
|
// Exclude .gsd/ to prevent merge conflicts when both branches modify planning artifacts.
|
|
313
341
|
this.autoCommit("pre-switch", current, [".gsd/"]);
|
|
314
342
|
|
|
343
|
+
// Discard uncommitted .gsd/ changes so checkout doesn't fail.
|
|
344
|
+
// These are runtime files (metrics, completed-units, STATE) that were
|
|
345
|
+
// intentionally excluded from the commit above. If they remain dirty,
|
|
346
|
+
// git checkout refuses when the target branch has different versions.
|
|
347
|
+
this.git(["checkout", "--", ".gsd/"], { allowFailure: true });
|
|
348
|
+
|
|
315
349
|
this.git(["checkout", branch]);
|
|
316
350
|
return created;
|
|
317
351
|
}
|
|
@@ -327,6 +361,9 @@ export class GitServiceImpl {
|
|
|
327
361
|
// Exclude .gsd/ to prevent merge conflicts when both branches modify planning artifacts.
|
|
328
362
|
this.autoCommit("pre-switch", current, [".gsd/"]);
|
|
329
363
|
|
|
364
|
+
// Discard uncommitted .gsd/ changes so checkout doesn't fail.
|
|
365
|
+
this.git(["checkout", "--", ".gsd/"], { allowFailure: true });
|
|
366
|
+
|
|
330
367
|
this.git(["checkout", mainBranch]);
|
|
331
368
|
}
|
|
332
369
|
|
|
@@ -490,25 +527,33 @@ export class GitServiceImpl {
|
|
|
490
527
|
// Pull latest main before merging to avoid conflicts from remote changes
|
|
491
528
|
this.git(["pull", "--rebase", "origin", mainBranch], { allowFailure: true });
|
|
492
529
|
|
|
493
|
-
//
|
|
494
|
-
//
|
|
530
|
+
// Merge slice branch — strategy is configurable via git.merge_strategy
|
|
531
|
+
// preference. Default: "squash" (preserves existing behavior).
|
|
532
|
+
// "merge" uses --no-ff which is more resilient to conflicts from
|
|
533
|
+
// long-lived branches or frequently-changing .gsd/* artifacts.
|
|
534
|
+
const strategy = this.prefs.merge_strategy ?? "squash";
|
|
535
|
+
const mergeArgs = strategy === "merge"
|
|
536
|
+
? ["merge", "--no-ff", "-m", message, branch]
|
|
537
|
+
: ["merge", "--squash", branch];
|
|
538
|
+
|
|
495
539
|
try {
|
|
496
|
-
this.git(
|
|
540
|
+
this.git(mergeArgs);
|
|
497
541
|
} catch (mergeError) {
|
|
498
|
-
//
|
|
499
|
-
// has conflict markers and a dirty index. Reset to restore a clean state.
|
|
542
|
+
// Merge exits non-zero on conflict. Reset to restore a clean state.
|
|
500
543
|
this.git(["reset", "--hard", "HEAD"], { allowFailure: true });
|
|
501
544
|
const msg = mergeError instanceof Error ? mergeError.message : String(mergeError);
|
|
502
545
|
throw new Error(
|
|
503
|
-
|
|
546
|
+
`${strategy === "merge" ? "Merge" : "Squash-merge"} of "${branch}" into "${mainBranch}" failed with conflicts. ` +
|
|
504
547
|
`Working tree has been reset to a clean state. ` +
|
|
505
|
-
`Resolve manually: git checkout ${mainBranch} && git merge --squash ${branch}\n` +
|
|
548
|
+
`Resolve manually: git checkout ${mainBranch} && git merge ${strategy === "merge" ? "--no-ff" : "--squash"} ${branch}\n` +
|
|
506
549
|
`Original error: ${msg}`,
|
|
507
550
|
);
|
|
508
551
|
}
|
|
509
552
|
|
|
510
|
-
//
|
|
511
|
-
|
|
553
|
+
// Squash merge needs a separate commit; --no-ff merge already committed
|
|
554
|
+
if (strategy === "squash") {
|
|
555
|
+
this.git(["commit", "-F", "-"], { input: message });
|
|
556
|
+
}
|
|
512
557
|
|
|
513
558
|
// Delete the merged branch
|
|
514
559
|
this.git(["branch", "-D", branch]);
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { execSync } from "node:child_process";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Patterns that are always correct regardless of project type.
|
|
@@ -20,6 +21,7 @@ const BASELINE_PATTERNS = [
|
|
|
20
21
|
".gsd/worktrees/",
|
|
21
22
|
".gsd/auto.lock",
|
|
22
23
|
".gsd/metrics.json",
|
|
24
|
+
".gsd/completed-units.json",
|
|
23
25
|
".gsd/STATE.md",
|
|
24
26
|
|
|
25
27
|
// ── OS junk ──
|
|
@@ -105,6 +107,32 @@ export function ensureGitignore(basePath: string): boolean {
|
|
|
105
107
|
return true;
|
|
106
108
|
}
|
|
107
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Remove BASELINE_PATTERNS runtime paths from the git index if they are
|
|
112
|
+
* currently tracked. This fixes repos that started tracking these files
|
|
113
|
+
* before the .gitignore rule was added — git continues tracking files
|
|
114
|
+
* already in the index even after .gitignore is updated.
|
|
115
|
+
*
|
|
116
|
+
* Only removes from the index (`--cached`), never from disk. Idempotent.
|
|
117
|
+
*/
|
|
118
|
+
export function untrackRuntimeFiles(basePath: string): void {
|
|
119
|
+
// The GSD runtime paths are the first 7 entries in BASELINE_PATTERNS
|
|
120
|
+
const runtimePaths = BASELINE_PATTERNS.slice(0, 7);
|
|
121
|
+
|
|
122
|
+
for (const pattern of runtimePaths) {
|
|
123
|
+
// Use -r for directory patterns (trailing slash), strip the slash for the command
|
|
124
|
+
const target = pattern.endsWith("/") ? pattern.slice(0, -1) : pattern;
|
|
125
|
+
try {
|
|
126
|
+
execSync(`git rm -r --cached ${target}`, {
|
|
127
|
+
cwd: basePath,
|
|
128
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
129
|
+
});
|
|
130
|
+
} catch {
|
|
131
|
+
// File not tracked or doesn't exist — expected, ignore
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
108
136
|
/**
|
|
109
137
|
* Ensure basePath/.gsd/PREFERENCES.md exists as an empty template.
|
|
110
138
|
* Creates the file with frontmatter only if it doesn't exist.
|
|
@@ -14,15 +14,16 @@ import { deriveState } from "./state.js";
|
|
|
14
14
|
import { startAuto } from "./auto.js";
|
|
15
15
|
import { readCrashLock, clearLock, formatCrashInfo } from "./crash-recovery.js";
|
|
16
16
|
import {
|
|
17
|
-
gsdRoot, milestonesDir, resolveMilestoneFile,
|
|
17
|
+
gsdRoot, milestonesDir, resolveMilestoneFile, resolveMilestonePath,
|
|
18
18
|
resolveSliceFile, resolveSlicePath, resolveGsdRootFile, relGsdRootFile,
|
|
19
19
|
relMilestoneFile, relSliceFile, relSlicePath,
|
|
20
20
|
} from "./paths.js";
|
|
21
21
|
import { join } from "node:path";
|
|
22
|
-
import { readFileSync, existsSync, mkdirSync, readdirSync } from "node:fs";
|
|
22
|
+
import { readFileSync, existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
|
|
23
23
|
import { execSync, execFileSync } from "node:child_process";
|
|
24
|
-
import { ensureGitignore, ensurePreferences } from "./gitignore.js";
|
|
24
|
+
import { ensureGitignore, ensurePreferences, untrackRuntimeFiles } from "./gitignore.js";
|
|
25
25
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
26
|
+
import { showConfirm } from "../shared/confirm-ui.js";
|
|
26
27
|
|
|
27
28
|
// ─── Auto-start after discuss ─────────────────────────────────────────────────
|
|
28
29
|
|
|
@@ -35,6 +36,11 @@ let pendingAutoStart: {
|
|
|
35
36
|
step?: boolean; // preserve step mode through discuss → auto transition
|
|
36
37
|
} | null = null;
|
|
37
38
|
|
|
39
|
+
/** Returns the milestoneId being discussed, or null if no discussion is active */
|
|
40
|
+
export function getDiscussionMilestoneId(): string | null {
|
|
41
|
+
return pendingAutoStart?.milestoneId ?? null;
|
|
42
|
+
}
|
|
43
|
+
|
|
38
44
|
/** Called from agent_end to check if auto-mode should start after discuss */
|
|
39
45
|
export function checkAutoStartAfterDiscuss(): boolean {
|
|
40
46
|
if (!pendingAutoStart) return false;
|
|
@@ -81,13 +87,13 @@ function dispatchWorkflow(pi: ExtensionAPI, note: string, customType = "gsd-run"
|
|
|
81
87
|
* Build the discuss-and-plan prompt for a new milestone.
|
|
82
88
|
* Used by all three "new milestone" paths (first ever, no active, all complete).
|
|
83
89
|
*/
|
|
84
|
-
function buildDiscussPrompt(nextId: string, preamble: string,
|
|
85
|
-
const
|
|
90
|
+
function buildDiscussPrompt(nextId: string, preamble: string, _basePath: string): string {
|
|
91
|
+
const milestoneRel = `.gsd/milestones/${nextId}`;
|
|
86
92
|
return loadPrompt("discuss", {
|
|
87
93
|
milestoneId: nextId,
|
|
88
94
|
preamble,
|
|
89
|
-
|
|
90
|
-
|
|
95
|
+
contextPath: `${milestoneRel}/${nextId}-CONTEXT.md`,
|
|
96
|
+
roadmapPath: `${milestoneRel}/${nextId}-ROADMAP.md`,
|
|
91
97
|
});
|
|
92
98
|
}
|
|
93
99
|
|
|
@@ -339,16 +345,16 @@ async function buildDiscussSlicePrompt(
|
|
|
339
345
|
? `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`
|
|
340
346
|
: `## Inlined Context\n\n_(no context files found yet — go in blind and ask broad questions)_`;
|
|
341
347
|
|
|
342
|
-
const
|
|
343
|
-
const
|
|
348
|
+
const sliceDirPath = `.gsd/milestones/${mid}/slices/${sid}`;
|
|
349
|
+
const sliceContextPath = `${sliceDirPath}/${sid}-CONTEXT.md`;
|
|
344
350
|
|
|
345
351
|
return loadPrompt("guided-discuss-slice", {
|
|
346
352
|
milestoneId: mid,
|
|
347
353
|
sliceId: sid,
|
|
348
354
|
sliceTitle: sTitle,
|
|
349
355
|
inlinedContext,
|
|
350
|
-
|
|
351
|
-
|
|
356
|
+
sliceDirPath,
|
|
357
|
+
contextPath: sliceContextPath,
|
|
352
358
|
projectRoot: base,
|
|
353
359
|
});
|
|
354
360
|
}
|
|
@@ -451,6 +457,7 @@ export async function showSmartEntry(
|
|
|
451
457
|
|
|
452
458
|
// ── Ensure .gitignore has baseline patterns ──────────────────────────
|
|
453
459
|
ensureGitignore(basePath);
|
|
460
|
+
untrackRuntimeFiles(basePath);
|
|
454
461
|
|
|
455
462
|
// ── No GSD project OR no milestone → Create first/next milestone ────
|
|
456
463
|
if (!existsSync(join(basePath, ".gsd"))) {
|
|
@@ -601,6 +608,16 @@ export async function showSmartEntry(
|
|
|
601
608
|
label: "Discuss first",
|
|
602
609
|
description: "Capture decisions on gray areas before planning.",
|
|
603
610
|
}] : []),
|
|
611
|
+
{
|
|
612
|
+
id: "skip_milestone",
|
|
613
|
+
label: "Skip — create new milestone",
|
|
614
|
+
description: "Leave this milestone on disk and start a fresh one.",
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
id: "discard_milestone",
|
|
618
|
+
label: "Discard this milestone",
|
|
619
|
+
description: "Delete the milestone directory and start over.",
|
|
620
|
+
},
|
|
604
621
|
];
|
|
605
622
|
|
|
606
623
|
const choice = await showNextAction(ctx as any, {
|
|
@@ -619,6 +636,27 @@ export async function showSmartEntry(
|
|
|
619
636
|
dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
620
637
|
milestoneId, milestoneTitle,
|
|
621
638
|
}));
|
|
639
|
+
} else if (choice === "skip_milestone") {
|
|
640
|
+
const milestoneIds = findMilestoneIds(basePath);
|
|
641
|
+
const nextId = `M${String(milestoneIds.length + 1).padStart(3, "0")}`;
|
|
642
|
+
pendingAutoStart = { ctx, pi, basePath, milestoneId: nextId, step: stepMode };
|
|
643
|
+
dispatchWorkflow(pi, buildDiscussPrompt(nextId,
|
|
644
|
+
`New milestone ${nextId}.`,
|
|
645
|
+
basePath
|
|
646
|
+
));
|
|
647
|
+
} else if (choice === "discard_milestone") {
|
|
648
|
+
const mDir = resolveMilestonePath(basePath, milestoneId);
|
|
649
|
+
if (!mDir) return;
|
|
650
|
+
const confirmed = await showConfirm(ctx as any, {
|
|
651
|
+
title: "Discard milestone?",
|
|
652
|
+
message: `This will permanently delete ${milestoneId} and all its contents.`,
|
|
653
|
+
confirmLabel: "Discard",
|
|
654
|
+
declineLabel: "Cancel",
|
|
655
|
+
});
|
|
656
|
+
if (confirmed) {
|
|
657
|
+
rmSync(mDir, { recursive: true, force: true });
|
|
658
|
+
return showSmartEntry(ctx, pi, basePath, options);
|
|
659
|
+
}
|
|
622
660
|
}
|
|
623
661
|
} else {
|
|
624
662
|
// Roadmap exists — either blocked or ready for auto
|
|
@@ -20,18 +20,20 @@
|
|
|
20
20
|
|
|
21
21
|
import type {
|
|
22
22
|
ExtensionAPI,
|
|
23
|
+
ExtensionCommandContext,
|
|
23
24
|
ExtensionContext,
|
|
24
25
|
} from "@gsd/pi-coding-agent";
|
|
25
|
-
import { createBashTool, createWriteTool, createReadTool, createEditTool } from "@gsd/pi-coding-agent";
|
|
26
|
+
import { createBashTool, createWriteTool, createReadTool, createEditTool, isToolCallEventType } from "@gsd/pi-coding-agent";
|
|
26
27
|
|
|
27
28
|
import { registerGSDCommand } from "./commands.js";
|
|
29
|
+
import { registerExitCommand } from "./exit-command.js";
|
|
28
30
|
import { registerWorktreeCommand, getWorktreeOriginalCwd, getActiveWorktreeName } from "./worktree-command.js";
|
|
29
31
|
import { saveFile, formatContinue, loadFile, parseContinue, parseSummary } from "./files.js";
|
|
30
32
|
import { loadPrompt } from "./prompt-loader.js";
|
|
31
33
|
import { deriveState } from "./state.js";
|
|
32
34
|
import { isAutoActive, isAutoPaused, handleAgentEnd, pauseAuto, getAutoDashboardData } from "./auto.js";
|
|
33
35
|
import { saveActivityLog } from "./activity-log.js";
|
|
34
|
-
import { checkAutoStartAfterDiscuss } from "./guided-flow.js";
|
|
36
|
+
import { checkAutoStartAfterDiscuss, getDiscussionMilestoneId } from "./guided-flow.js";
|
|
35
37
|
import { GSDDashboardOverlay } from "./dashboard-overlay.js";
|
|
36
38
|
import {
|
|
37
39
|
loadEffectiveGSDPreferences,
|
|
@@ -42,7 +44,7 @@ import { hasSkillSnapshot, detectNewSkills, formatSkillsXml } from "./skill-disc
|
|
|
42
44
|
import {
|
|
43
45
|
resolveSlicePath, resolveSliceFile, resolveTaskFile, resolveTaskFiles, resolveTasksDir,
|
|
44
46
|
relSliceFile, relSlicePath, relTaskFile,
|
|
45
|
-
buildSliceFileName, gsdRoot,
|
|
47
|
+
buildSliceFileName, buildMilestoneFileName, gsdRoot, resolveMilestonePath,
|
|
46
48
|
} from "./paths.js";
|
|
47
49
|
import { Key } from "@gsd/pi-tui";
|
|
48
50
|
import { join } from "node:path";
|
|
@@ -50,6 +52,32 @@ import { existsSync } from "node:fs";
|
|
|
50
52
|
import { shortcutDesc } from "../shared/terminal.js";
|
|
51
53
|
import { Text } from "@gsd/pi-tui";
|
|
52
54
|
|
|
55
|
+
// ── Depth verification state ──────────────────────────────────────────────
|
|
56
|
+
let depthVerificationDone = false;
|
|
57
|
+
|
|
58
|
+
export function isDepthVerified(): boolean {
|
|
59
|
+
return depthVerificationDone;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── Write-gate: block CONTEXT.md writes during discussion without depth verification ──
|
|
63
|
+
const MILESTONE_CONTEXT_RE = /M\d+-CONTEXT\.md$/;
|
|
64
|
+
|
|
65
|
+
export function shouldBlockContextWrite(
|
|
66
|
+
toolName: string,
|
|
67
|
+
inputPath: string,
|
|
68
|
+
milestoneId: string | null,
|
|
69
|
+
depthVerified: boolean,
|
|
70
|
+
): { block: boolean; reason?: string } {
|
|
71
|
+
if (toolName !== "write") return { block: false };
|
|
72
|
+
if (!milestoneId) return { block: false };
|
|
73
|
+
if (!MILESTONE_CONTEXT_RE.test(inputPath)) return { block: false };
|
|
74
|
+
if (depthVerified) return { block: false };
|
|
75
|
+
return {
|
|
76
|
+
block: true,
|
|
77
|
+
reason: `Blocked: Cannot write to milestone CONTEXT.md during discussion phase without depth verification. Call ask_user_questions with question id "depth_verification" first to confirm discussion depth before writing context.`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
53
81
|
// ── ASCII logo ────────────────────────────────────────────────────────────
|
|
54
82
|
const GSD_LOGO_LINES = [
|
|
55
83
|
" ██████╗ ███████╗██████╗ ",
|
|
@@ -63,22 +91,12 @@ const GSD_LOGO_LINES = [
|
|
|
63
91
|
export default function (pi: ExtensionAPI) {
|
|
64
92
|
registerGSDCommand(pi);
|
|
65
93
|
registerWorktreeCommand(pi);
|
|
66
|
-
|
|
67
|
-
// ── /exit — graceful exit (cleanup auto-mode, save state) ──────────────
|
|
68
|
-
pi.registerCommand("exit", {
|
|
69
|
-
description: "Exit GSD gracefully (saves auto-mode state)",
|
|
70
|
-
handler: async (_ctx) => {
|
|
71
|
-
// Gracefully stop auto-mode if running (saves activity log, clears locks)
|
|
72
|
-
const { stopAuto } = await import("./auto.js");
|
|
73
|
-
await stopAuto(_ctx, pi);
|
|
74
|
-
process.exit(0);
|
|
75
|
-
},
|
|
76
|
-
});
|
|
94
|
+
registerExitCommand(pi);
|
|
77
95
|
|
|
78
96
|
// ── /kill — immediate exit (bypass cleanup) ─────────────────────────────
|
|
79
97
|
pi.registerCommand("kill", {
|
|
80
98
|
description: "Exit GSD immediately (no cleanup)",
|
|
81
|
-
handler: async (_ctx) => {
|
|
99
|
+
handler: async (_args: string, _ctx: ExtensionCommandContext) => {
|
|
82
100
|
process.exit(0);
|
|
83
101
|
},
|
|
84
102
|
});
|
|
@@ -299,7 +317,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
299
317
|
// ── agent_end: auto-mode advancement or auto-start after discuss ───────────
|
|
300
318
|
pi.on("agent_end", async (event, ctx: ExtensionContext) => {
|
|
301
319
|
// If discuss phase just finished, start auto-mode
|
|
302
|
-
if (checkAutoStartAfterDiscuss())
|
|
320
|
+
if (checkAutoStartAfterDiscuss()) {
|
|
321
|
+
depthVerificationDone = false;
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
303
324
|
|
|
304
325
|
// If auto-mode is already running, advance to next unit
|
|
305
326
|
if (!isAutoActive()) return;
|
|
@@ -373,6 +394,80 @@ export default function (pi: ExtensionAPI) {
|
|
|
373
394
|
saveActivityLog(ctx, dash.basePath, dash.currentUnit.type, dash.currentUnit.id);
|
|
374
395
|
}
|
|
375
396
|
});
|
|
397
|
+
|
|
398
|
+
// ── tool_call: block CONTEXT.md writes during discussion without depth verification ──
|
|
399
|
+
pi.on("tool_call", async (event) => {
|
|
400
|
+
if (!isToolCallEventType("write", event)) return;
|
|
401
|
+
const result = shouldBlockContextWrite(
|
|
402
|
+
event.toolName,
|
|
403
|
+
event.input.path,
|
|
404
|
+
getDiscussionMilestoneId(),
|
|
405
|
+
isDepthVerified(),
|
|
406
|
+
);
|
|
407
|
+
if (result.block) return result;
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// ── tool_result: persist discussion exchanges & detect depth gate ──────
|
|
411
|
+
pi.on("tool_result", async (event) => {
|
|
412
|
+
if (event.toolName !== "ask_user_questions") return;
|
|
413
|
+
|
|
414
|
+
const milestoneId = getDiscussionMilestoneId();
|
|
415
|
+
if (!milestoneId) return;
|
|
416
|
+
|
|
417
|
+
const details = event.details as any;
|
|
418
|
+
if (details?.cancelled || !details?.response) return;
|
|
419
|
+
|
|
420
|
+
// ── Depth gate detection ──────────────────────────────────────────
|
|
421
|
+
const questions: any[] = (event.input as any)?.questions ?? [];
|
|
422
|
+
for (const q of questions) {
|
|
423
|
+
if (typeof q.id === "string" && q.id.includes("depth_verification")) {
|
|
424
|
+
depthVerificationDone = true;
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// ── Persist exchange to DISCUSSION.md ──────────────────────────────
|
|
430
|
+
const basePath = process.cwd();
|
|
431
|
+
const milestoneDir = resolveMilestonePath(basePath, milestoneId);
|
|
432
|
+
if (!milestoneDir) return;
|
|
433
|
+
|
|
434
|
+
const fileName = buildMilestoneFileName(milestoneId, "DISCUSSION");
|
|
435
|
+
const discussionPath = join(milestoneDir, fileName);
|
|
436
|
+
const timestamp = new Date().toISOString();
|
|
437
|
+
|
|
438
|
+
// Format exchange as markdown
|
|
439
|
+
const lines: string[] = [`## Exchange — ${timestamp}`, ""];
|
|
440
|
+
|
|
441
|
+
for (const q of questions) {
|
|
442
|
+
lines.push(`### ${q.header ?? "Question"}`);
|
|
443
|
+
lines.push("");
|
|
444
|
+
lines.push(q.question ?? "");
|
|
445
|
+
if (Array.isArray(q.options)) {
|
|
446
|
+
lines.push("");
|
|
447
|
+
for (const opt of q.options) {
|
|
448
|
+
lines.push(`- **${opt.label}** — ${opt.description ?? ""}`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Append user response for this question
|
|
453
|
+
const answer = details.response?.answers?.[q.id];
|
|
454
|
+
if (answer) {
|
|
455
|
+
lines.push("");
|
|
456
|
+
const selected = Array.isArray(answer.selected) ? answer.selected.join(", ") : answer.selected;
|
|
457
|
+
lines.push(`**Selected:** ${selected}`);
|
|
458
|
+
if (answer.notes) {
|
|
459
|
+
lines.push(`**Notes:** ${answer.notes}`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
lines.push("");
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
lines.push("---", "");
|
|
466
|
+
|
|
467
|
+
const newBlock = lines.join("\n");
|
|
468
|
+
const existing = await loadFile(discussionPath) ?? `# ${milestoneId} Discussion Log\n\n`;
|
|
469
|
+
await saveFile(discussionPath, existing + newBlock);
|
|
470
|
+
});
|
|
376
471
|
}
|
|
377
472
|
|
|
378
473
|
async function buildGuidedExecuteContextInjection(prompt: string, basePath: string): Promise<string | null> {
|
|
@@ -702,6 +702,14 @@ function validatePreferences(preferences: GSDPreferences): {
|
|
|
702
702
|
errors.push(`git.commit_type must be one of: feat, fix, refactor, docs, test, chore, perf, ci, build, style`);
|
|
703
703
|
}
|
|
704
704
|
}
|
|
705
|
+
if (g.merge_strategy !== undefined) {
|
|
706
|
+
const validStrategies = new Set(["squash", "merge"]);
|
|
707
|
+
if (typeof g.merge_strategy === "string" && validStrategies.has(g.merge_strategy)) {
|
|
708
|
+
git.merge_strategy = g.merge_strategy as "squash" | "merge";
|
|
709
|
+
} else {
|
|
710
|
+
errors.push("git.merge_strategy must be one of: squash, merge");
|
|
711
|
+
}
|
|
712
|
+
}
|
|
705
713
|
if (g.main_branch !== undefined) {
|
|
706
714
|
if (typeof g.main_branch === "string" && g.main_branch.trim() !== "" && VALID_BRANCH_NAME.test(g.main_branch)) {
|
|
707
715
|
git.main_branch = g.main_branch;
|
|
@@ -12,7 +12,7 @@ Then:
|
|
|
12
12
|
3. Verify each **success criterion** from the milestone definition in `{{roadmapPath}}`. For each criterion, confirm it was met with specific evidence from slice summaries, test results, or observable behavior. List any criterion that was NOT met.
|
|
13
13
|
4. Verify the milestone's **definition of done** — all slices are `[x]`, all slice summaries exist, and any cross-slice integration points work correctly.
|
|
14
14
|
5. Validate **requirement status transitions**. For each requirement that changed status during this milestone, confirm the transition is supported by evidence. Requirements can move between Active, Validated, Deferred, Blocked, or Out of Scope — but only with proof.
|
|
15
|
-
6. Write `{{
|
|
15
|
+
6. Write `{{milestoneSummaryPath}}` using the milestone-summary template. Fill all frontmatter fields and narrative sections. The `requirement_outcomes` field must list every requirement that changed status with `from_status`, `to_status`, and `proof`.
|
|
16
16
|
7. Update `.gsd/REQUIREMENTS.md` if any requirement status transitions were validated in step 5.
|
|
17
17
|
8. Update `.gsd/PROJECT.md` to reflect milestone completion and current project state.
|
|
18
18
|
9. Do not commit manually — the system auto-commits your changes after this unit completes.
|
|
@@ -20,6 +20,6 @@ Then:
|
|
|
20
20
|
|
|
21
21
|
**Important:** Do NOT skip the success criteria and definition of done verification (steps 3-4). The milestone summary must reflect actual verified outcomes, not assumed success. If any criterion was not met, document it clearly in the summary and do not mark the milestone as passing verification.
|
|
22
22
|
|
|
23
|
-
**You MUST write `{{
|
|
23
|
+
**You MUST write `{{milestoneSummaryPath}}` AND update PROJECT.md before finishing.**
|
|
24
24
|
|
|
25
25
|
When done, say: "Milestone {{milestoneId}} complete."
|
|
@@ -16,14 +16,14 @@ Then:
|
|
|
16
16
|
3. Run all slice-level verification checks defined in the slice plan. All must pass before marking the slice done. If any fail, fix them first.
|
|
17
17
|
4. If the slice plan includes observability/diagnostic surfaces, confirm they work. Skip this for simple slices that don't have observability sections.
|
|
18
18
|
5. If `.gsd/REQUIREMENTS.md` exists, update it based on what this slice actually proved. Move requirements between Active, Validated, Deferred, Blocked, or Out of Scope only when the evidence from execution supports that change.
|
|
19
|
-
6. Write `{{
|
|
20
|
-
7. Write `{{
|
|
19
|
+
6. Write `{{sliceSummaryPath}}` (compress all task summaries).
|
|
20
|
+
7. Write `{{sliceUatPath}}` — a concrete UAT script with real test cases derived from the slice plan and task summaries. Include preconditions, numbered steps with expected outcomes, and edge cases. This must NOT be a placeholder or generic template — tailor every test case to what this slice actually built.
|
|
21
21
|
8. Review task summaries for `key_decisions`. Append any significant decisions to `.gsd/DECISIONS.md` if missing.
|
|
22
22
|
9. Mark {{sliceId}} done in `{{roadmapPath}}` (change `[ ]` to `[x]`)
|
|
23
23
|
10. Do not commit or squash-merge manually — the system auto-commits your changes and handles the merge after this unit succeeds.
|
|
24
24
|
11. Update `.gsd/PROJECT.md` if it exists — refresh current state if needed.
|
|
25
25
|
12. Update `.gsd/STATE.md`
|
|
26
26
|
|
|
27
|
-
**You MUST mark {{sliceId}} as `[x]` in `{{roadmapPath}}
|
|
27
|
+
**You MUST do ALL THREE before finishing: (1) write `{{sliceSummaryPath}}`, (2) write `{{sliceUatPath}}`, (3) mark {{sliceId}} as `[x]` in `{{roadmapPath}}`. The unit will not be marked complete if any of these files are missing.**
|
|
28
28
|
|
|
29
29
|
When done, say: "Slice {{sliceId}} complete."
|
|
@@ -52,6 +52,16 @@ You are a thinking partner, not an interviewer.
|
|
|
52
52
|
|
|
53
53
|
**Freeform rule:** When the user selects "Other" or clearly wants to explain something freely, stop using `ask_user_questions` and switch to plain text follow-ups. Let them talk. Resume structured questions when appropriate.
|
|
54
54
|
|
|
55
|
+
**Depth-signal awareness.** When a user writes extensively about something — long notes, detailed explanations, specific examples — that's a signal. Probe that area deeper. Don't spread attention evenly across all topics when the user is clearly investing energy in one.
|
|
56
|
+
|
|
57
|
+
**Enrichment fusion.** Weave the user's specific language, terminology, and framing into your subsequent questions. If they said "craft feel," your next question references "craft feel" — don't paraphrase it into "user experience quality." Their precision is signal, not noise.
|
|
58
|
+
|
|
59
|
+
**Position-first framing.** Have opinions. State your read of a tradeoff with rationale before asking what they think. "I'd lean toward X because Y — does that match your thinking, or am I missing context?" is better than "what do you think about X vs Y?" You're a thinking partner, not a neutral interviewer.
|
|
60
|
+
|
|
61
|
+
**Negative constraints.** Ask what would disappoint them. What they explicitly don't want. What the product should never feel like. Negative constraints are sharper than positive wishes — "never feel sluggish" defines the performance bar more precisely than "should be fast."
|
|
62
|
+
|
|
63
|
+
**Observation ≠ Conclusion.** Technical facts you discover in the codebase during investigation are context, not decisions. Present them as context and let the user decide what they mean for direction. "The current auth uses JWT with 24h expiry" is an observation. Whether to keep that pattern is the user's call.
|
|
64
|
+
|
|
55
65
|
**Anti-patterns — never do these:**
|
|
56
66
|
- **Checklist walking** — going through a predetermined list of topics regardless of what the user said
|
|
57
67
|
- **Canned questions** — asking generic questions that could apply to any project
|
|
@@ -73,10 +83,22 @@ Do NOT offer to proceed until ALL of the following are satisfied. Track these in
|
|
|
73
83
|
- [ ] **The biggest technical unknowns / risks** — what could fail, what hasn't been proven
|
|
74
84
|
- [ ] **What external systems/services this touches** — APIs, databases, third-party services, hardware
|
|
75
85
|
|
|
86
|
+
Before offering to proceed, demonstrate absorption: reference specific things the user emphasized, specific terminology they used, specific nuance they sharpened — and show how those shaped your understanding. Synthesize, don't recite. "Your emphasis on X led me to prioritize Y over Z" is good. "You said X, you said Y, you said Z" is not. The user should feel heard in the specifics, not just acknowledged in the abstract.
|
|
87
|
+
|
|
76
88
|
**Questioning depth should match scope.** Simple, well-defined work needs fewer rounds — maybe 1-2. Large, ambiguous visions need more — maybe 4+. Don't pad rounds to hit a number. Stop when the depth checklist is satisfied and you genuinely understand the work.
|
|
77
89
|
|
|
78
90
|
Do not count the reflection step as a question round. Rounds start after reflection is confirmed.
|
|
79
91
|
|
|
92
|
+
## Depth Verification
|
|
93
|
+
|
|
94
|
+
Before moving to the wrap-up gate, present a structured depth summary to the user via `ask_user_questions`. This is a checkpoint — show what you captured across the depth checklist dimensions, using the user's own terminology and framing.
|
|
95
|
+
|
|
96
|
+
The question should summarize: what you understood them to be building, what shaped your understanding most (their emphasis, constraints, concerns), and any areas where you're least confident in your understanding. Frame it as: "Before we move to planning, here's what I captured — did I get the depth right?"
|
|
97
|
+
|
|
98
|
+
**Convention:** The question ID must contain `depth_verification` (e.g., `depth_verification_summary`). This naming convention enables downstream mechanical detection of this step.
|
|
99
|
+
|
|
100
|
+
Offer two options: "Yes, you got it (Recommended)" and "Not quite — let me clarify." If they clarify, absorb the correction and re-verify.
|
|
101
|
+
|
|
80
102
|
## Wrap-up Gate
|
|
81
103
|
|
|
82
104
|
Only after the depth checklist is fully satisfied and you genuinely understand the work, offer to proceed.
|
|
@@ -166,8 +188,11 @@ Once the user is satisfied, in a single pass:
|
|
|
166
188
|
1. `mkdir -p .gsd/milestones/{{milestoneId}}/slices`
|
|
167
189
|
2. Write or update `.gsd/PROJECT.md` — read the template at `~/.gsd/agent/extensions/gsd/templates/project.md` first. Describe what the project is, its current state, and list the milestone sequence.
|
|
168
190
|
3. Write or update `.gsd/REQUIREMENTS.md` — read the template at `~/.gsd/agent/extensions/gsd/templates/requirements.md` first. Confirm requirement states, ownership, and traceability before roadmap creation.
|
|
169
|
-
|
|
170
|
-
|
|
191
|
+
**Depth-Preservation Guidance for context.md:**
|
|
192
|
+
When writing context.md, preserve the user's exact terminology, emphasis, and specific framing from the discussion. Do not paraphrase user nuance into generic summaries. If the user said "craft feel," write "craft feel" — not "high-quality user experience." If they emphasized a specific constraint or negative requirement, carry that emphasis through verbatim. The context file is downstream agents' only window into this conversation — flattening specifics into generics loses the signal that shaped every decision.
|
|
193
|
+
|
|
194
|
+
4. Write `{{contextPath}}` — read the template at `~/.gsd/agent/extensions/gsd/templates/context.md` first. Preserve key risks, unknowns, existing codebase constraints, integration points, and relevant requirements surfaced during discussion.
|
|
195
|
+
5. Write `{{roadmapPath}}` — read the template at `~/.gsd/agent/extensions/gsd/templates/roadmap.md` first. Decompose into demoable vertical slices with checkboxes, risk, depends, demo sentences, proof strategy, verification classes, milestone definition of done, requirement coverage, and a boundary map. If the milestone crosses multiple runtime boundaries, include an explicit final integration slice that proves the assembled system works end-to-end in a real environment.
|
|
171
196
|
6. Seed `.gsd/DECISIONS.md` — read the template at `~/.gsd/agent/extensions/gsd/templates/decisions.md` first. Append rows for any architectural or pattern decisions made during discussion.
|
|
172
197
|
7. Update `.gsd/STATE.md`
|
|
173
198
|
8. Commit: `docs({{milestoneId}}): context, requirements, and roadmap`
|
|
@@ -49,13 +49,13 @@ Then:
|
|
|
49
49
|
11. **Blocker discovery:** If execution reveals that the remaining slice plan is fundamentally invalid — not just a bug or minor deviation, but a plan-invalidating finding like a wrong API, missing capability, or architectural mismatch — set `blocker_discovered: true` in the task summary frontmatter and describe the blocker clearly in the summary narrative. Do NOT set `blocker_discovered: true` for ordinary debugging, minor deviations, or issues that can be fixed within the current task or the remaining plan. This flag triggers an automatic replan of the slice.
|
|
50
50
|
12. If you made an architectural, pattern, library, or observability decision during this task that downstream work should know about, append it to `.gsd/DECISIONS.md` (read the template at `~/.gsd/agent/extensions/gsd/templates/decisions.md` if the file doesn't exist yet). Not every task produces decisions — only append when a meaningful choice was made.
|
|
51
51
|
13. Read the template at `~/.gsd/agent/extensions/gsd/templates/task-summary.md`
|
|
52
|
-
14. Write `{{
|
|
52
|
+
14. Write `{{taskSummaryPath}}`
|
|
53
53
|
15. Mark {{taskId}} done in `{{planPath}}` (change `[ ]` to `[x]`)
|
|
54
54
|
16. Do not commit manually — the system auto-commits your changes after this unit completes.
|
|
55
55
|
17. Update `.gsd/STATE.md`
|
|
56
56
|
|
|
57
57
|
You are on the slice branch. All work stays here.
|
|
58
58
|
|
|
59
|
-
**You MUST mark {{taskId}} as `[x]` in `{{planPath}}` AND write `{{
|
|
59
|
+
**You MUST mark {{taskId}} as `[x]` in `{{planPath}}` AND write `{{taskSummaryPath}}` before finishing.**
|
|
60
60
|
|
|
61
61
|
When done, say: "Task {{taskId}} complete."
|
|
@@ -46,8 +46,8 @@ If the user wants to keep going, keep asking. Stop when they say wrap up.
|
|
|
46
46
|
Once the user is ready to wrap up:
|
|
47
47
|
|
|
48
48
|
1. Read the slice context template at `~/.gsd/agent/extensions/gsd/templates/slice-context.md`
|
|
49
|
-
2. `mkdir -p {{
|
|
50
|
-
3. Write `{{
|
|
49
|
+
2. `mkdir -p {{sliceDirPath}}`
|
|
50
|
+
3. Write `{{contextPath}}` — use the template structure, filling in:
|
|
51
51
|
- **Goal** — one sentence: what this slice delivers
|
|
52
52
|
- **Why this Slice** — why now, what it unblocks
|
|
53
53
|
- **Scope / In Scope** — what was confirmed in scope during the interview
|
|
@@ -55,5 +55,5 @@ Once the user is ready to wrap up:
|
|
|
55
55
|
- **Constraints** — anything the user flagged as a hard constraint
|
|
56
56
|
- **Integration Points** — what this slice consumes and produces
|
|
57
57
|
- **Open Questions** — anything still unresolved, with current thinking
|
|
58
|
-
4. Commit: `git -C {{projectRoot}} add {{
|
|
58
|
+
4. Commit: `git -C {{projectRoot}} add {{contextPath}} && git -C {{projectRoot}} commit -m "docs({{milestoneId}}/{{sliceId}}): slice context from discuss"`
|
|
59
59
|
5. Say exactly: `"{{sliceId}} context written."` — nothing else.
|