gsd-pi 2.14.1 → 2.14.3
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/resources/extensions/gsd/auto-worktree.ts +39 -1
- package/dist/resources/extensions/gsd/auto.ts +4 -1
- package/dist/resources/extensions/gsd/gitignore.ts +23 -1
- package/dist/resources/extensions/gsd/prompts/discuss.md +10 -4
- package/package.json +1 -1
- package/src/resources/extensions/gsd/auto-worktree.ts +39 -1
- package/src/resources/extensions/gsd/auto.ts +4 -1
- package/src/resources/extensions/gsd/gitignore.ts +23 -1
- package/src/resources/extensions/gsd/prompts/discuss.md +10 -4
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* manages create, enter, detect, and teardown for auto-mode worktrees.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { existsSync, readFileSync, realpathSync, utimesSync } from "node:fs";
|
|
9
|
+
import { existsSync, cpSync, readFileSync, realpathSync, utimesSync } from "node:fs";
|
|
10
10
|
import { join, resolve } from "node:path";
|
|
11
11
|
import { execSync, execFileSync } from "node:child_process";
|
|
12
12
|
import {
|
|
@@ -90,6 +90,14 @@ export function autoWorktreeBranch(milestoneId: string): string {
|
|
|
90
90
|
export function createAutoWorktree(basePath: string, milestoneId: string): string {
|
|
91
91
|
const branch = autoWorktreeBranch(milestoneId);
|
|
92
92
|
const info = createWorktree(basePath, milestoneId, { branch });
|
|
93
|
+
|
|
94
|
+
// Copy .gsd/ planning artifacts from the source repo into the new worktree.
|
|
95
|
+
// Worktrees are fresh git checkouts — untracked files don't carry over.
|
|
96
|
+
// Planning artifacts may be untracked if the project's .gitignore had a
|
|
97
|
+
// blanket .gsd/ rule (pre-v2.14.0). Without this copy, auto-mode loops
|
|
98
|
+
// on plan-slice because the plan file doesn't exist in the worktree.
|
|
99
|
+
copyPlanningArtifacts(basePath, info.path);
|
|
100
|
+
|
|
93
101
|
const previousCwd = process.cwd();
|
|
94
102
|
|
|
95
103
|
try {
|
|
@@ -107,6 +115,36 @@ export function createAutoWorktree(basePath: string, milestoneId: string): strin
|
|
|
107
115
|
return info.path;
|
|
108
116
|
}
|
|
109
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Copy .gsd/ planning artifacts from source repo to a new worktree.
|
|
120
|
+
* Copies milestones/, DECISIONS.md, REQUIREMENTS.md, PROJECT.md, QUEUE.md.
|
|
121
|
+
* Skips runtime files (auto.lock, metrics.json, etc.) and the worktrees/ dir.
|
|
122
|
+
* Best-effort — failures are non-fatal since auto-mode can recreate artifacts.
|
|
123
|
+
*/
|
|
124
|
+
function copyPlanningArtifacts(srcBase: string, wtPath: string): void {
|
|
125
|
+
const srcGsd = join(srcBase, ".gsd");
|
|
126
|
+
const dstGsd = join(wtPath, ".gsd");
|
|
127
|
+
if (!existsSync(srcGsd)) return;
|
|
128
|
+
|
|
129
|
+
// Copy milestones/ directory (planning files, roadmaps, plans, research)
|
|
130
|
+
const srcMilestones = join(srcGsd, "milestones");
|
|
131
|
+
if (existsSync(srcMilestones)) {
|
|
132
|
+
try {
|
|
133
|
+
cpSync(srcMilestones, join(dstGsd, "milestones"), { recursive: true, force: true });
|
|
134
|
+
} catch { /* non-fatal */ }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Copy top-level planning files
|
|
138
|
+
for (const file of ["DECISIONS.md", "REQUIREMENTS.md", "PROJECT.md", "QUEUE.md"]) {
|
|
139
|
+
const src = join(srcGsd, file);
|
|
140
|
+
if (existsSync(src)) {
|
|
141
|
+
try {
|
|
142
|
+
cpSync(src, join(dstGsd, file), { force: true });
|
|
143
|
+
} catch { /* non-fatal */ }
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
110
148
|
/**
|
|
111
149
|
* Teardown an auto-worktree: chdir back to original base, then remove
|
|
112
150
|
* the worktree and its branch.
|
|
@@ -1520,7 +1520,7 @@ async function dispatchNextUnit(
|
|
|
1520
1520
|
return; // Another dispatch is in progress — bail silently
|
|
1521
1521
|
}
|
|
1522
1522
|
_dispatching = true;
|
|
1523
|
-
|
|
1523
|
+
try {
|
|
1524
1524
|
// Recursion depth guard: when many units are skipped in sequence (e.g., after
|
|
1525
1525
|
// crash recovery with 10+ completed units), recursive dispatchNextUnit calls
|
|
1526
1526
|
// can freeze the TUI or overflow the stack. Yield generously after MAX_SKIP_DEPTH.
|
|
@@ -2425,6 +2425,9 @@ async function dispatchNextUnit(
|
|
|
2425
2425
|
);
|
|
2426
2426
|
await pauseAuto(ctx, pi);
|
|
2427
2427
|
}
|
|
2428
|
+
} finally {
|
|
2429
|
+
_dispatching = false;
|
|
2430
|
+
}
|
|
2428
2431
|
}
|
|
2429
2432
|
|
|
2430
2433
|
// ─── Skill Discovery ──────────────────────────────────────────────────────────
|
|
@@ -87,6 +87,28 @@ export function ensureGitignore(basePath: string): boolean {
|
|
|
87
87
|
existing = readFileSync(gitignorePath, "utf-8");
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
// Self-heal: remove blanket ".gsd/" lines from pre-v2.14.0 projects.
|
|
91
|
+
// The blanket ignore prevented planning artifacts (.gsd/milestones/) from
|
|
92
|
+
// being tracked in git, causing artifacts to vanish in worktrees and
|
|
93
|
+
// triggering loop detection failures. Replace with explicit runtime-only
|
|
94
|
+
// ignores so planning files are tracked naturally.
|
|
95
|
+
let modified = false;
|
|
96
|
+
const lines = existing.split("\n");
|
|
97
|
+
const filteredLines = lines.filter(line => {
|
|
98
|
+
const trimmed = line.trim();
|
|
99
|
+
// Remove standalone ".gsd/" lines (blanket ignore) but keep specific
|
|
100
|
+
// .gsd/ subpath patterns like ".gsd/activity/" or ".gsd/auto.lock"
|
|
101
|
+
if (trimmed === ".gsd/" || trimmed === ".gsd") {
|
|
102
|
+
modified = true;
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
return true;
|
|
106
|
+
});
|
|
107
|
+
if (modified) {
|
|
108
|
+
existing = filteredLines.join("\n");
|
|
109
|
+
writeFileSync(gitignorePath, existing, "utf-8");
|
|
110
|
+
}
|
|
111
|
+
|
|
90
112
|
// Parse existing lines (trimmed, ignoring comments and blanks)
|
|
91
113
|
const existingLines = new Set(
|
|
92
114
|
existing
|
|
@@ -98,7 +120,7 @@ export function ensureGitignore(basePath: string): boolean {
|
|
|
98
120
|
// Find patterns not yet present
|
|
99
121
|
const missing = BASELINE_PATTERNS.filter((p) => !existingLines.has(p));
|
|
100
122
|
|
|
101
|
-
if (missing.length === 0) return
|
|
123
|
+
if (missing.length === 0) return modified;
|
|
102
124
|
|
|
103
125
|
// Build the block to append
|
|
104
126
|
const block = [
|
|
@@ -91,13 +91,19 @@ Do not count the reflection step as a question round. Rounds start after reflect
|
|
|
91
91
|
|
|
92
92
|
## Depth Verification
|
|
93
93
|
|
|
94
|
-
Before moving to the wrap-up gate, present a structured depth summary
|
|
94
|
+
Before moving to the wrap-up gate, present a structured depth summary as a checkpoint.
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
**Print the summary as normal chat text first** — this is where the formatting renders properly. Structure the summary across the depth checklist dimensions using the user's own terminology and framing. Cover: 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.
|
|
97
97
|
|
|
98
|
-
**
|
|
98
|
+
**Then** use `ask_user_questions` with a short confirmation question — NOT the summary itself. The question field is designed for single sentences, not multi-paragraph summaries.
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
**Convention:** The question ID must contain `depth_verification` (e.g., `depth_verification_confirm`). This naming convention enables downstream mechanical detection of this step.
|
|
101
|
+
|
|
102
|
+
Example flow:
|
|
103
|
+
1. Print in chat: the full depth summary with markdown formatting (headers, bold, bullets)
|
|
104
|
+
2. Call `ask_user_questions` with: header "Depth Check", question "Did I capture the depth right?", options "Yes, you got it (Recommended)" and "Not quite — let me clarify"
|
|
105
|
+
|
|
106
|
+
If they clarify, absorb the correction and re-verify.
|
|
101
107
|
|
|
102
108
|
## Wrap-up Gate
|
|
103
109
|
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* manages create, enter, detect, and teardown for auto-mode worktrees.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { existsSync, readFileSync, realpathSync, utimesSync } from "node:fs";
|
|
9
|
+
import { existsSync, cpSync, readFileSync, realpathSync, utimesSync } from "node:fs";
|
|
10
10
|
import { join, resolve } from "node:path";
|
|
11
11
|
import { execSync, execFileSync } from "node:child_process";
|
|
12
12
|
import {
|
|
@@ -90,6 +90,14 @@ export function autoWorktreeBranch(milestoneId: string): string {
|
|
|
90
90
|
export function createAutoWorktree(basePath: string, milestoneId: string): string {
|
|
91
91
|
const branch = autoWorktreeBranch(milestoneId);
|
|
92
92
|
const info = createWorktree(basePath, milestoneId, { branch });
|
|
93
|
+
|
|
94
|
+
// Copy .gsd/ planning artifacts from the source repo into the new worktree.
|
|
95
|
+
// Worktrees are fresh git checkouts — untracked files don't carry over.
|
|
96
|
+
// Planning artifacts may be untracked if the project's .gitignore had a
|
|
97
|
+
// blanket .gsd/ rule (pre-v2.14.0). Without this copy, auto-mode loops
|
|
98
|
+
// on plan-slice because the plan file doesn't exist in the worktree.
|
|
99
|
+
copyPlanningArtifacts(basePath, info.path);
|
|
100
|
+
|
|
93
101
|
const previousCwd = process.cwd();
|
|
94
102
|
|
|
95
103
|
try {
|
|
@@ -107,6 +115,36 @@ export function createAutoWorktree(basePath: string, milestoneId: string): strin
|
|
|
107
115
|
return info.path;
|
|
108
116
|
}
|
|
109
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Copy .gsd/ planning artifacts from source repo to a new worktree.
|
|
120
|
+
* Copies milestones/, DECISIONS.md, REQUIREMENTS.md, PROJECT.md, QUEUE.md.
|
|
121
|
+
* Skips runtime files (auto.lock, metrics.json, etc.) and the worktrees/ dir.
|
|
122
|
+
* Best-effort — failures are non-fatal since auto-mode can recreate artifacts.
|
|
123
|
+
*/
|
|
124
|
+
function copyPlanningArtifacts(srcBase: string, wtPath: string): void {
|
|
125
|
+
const srcGsd = join(srcBase, ".gsd");
|
|
126
|
+
const dstGsd = join(wtPath, ".gsd");
|
|
127
|
+
if (!existsSync(srcGsd)) return;
|
|
128
|
+
|
|
129
|
+
// Copy milestones/ directory (planning files, roadmaps, plans, research)
|
|
130
|
+
const srcMilestones = join(srcGsd, "milestones");
|
|
131
|
+
if (existsSync(srcMilestones)) {
|
|
132
|
+
try {
|
|
133
|
+
cpSync(srcMilestones, join(dstGsd, "milestones"), { recursive: true, force: true });
|
|
134
|
+
} catch { /* non-fatal */ }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Copy top-level planning files
|
|
138
|
+
for (const file of ["DECISIONS.md", "REQUIREMENTS.md", "PROJECT.md", "QUEUE.md"]) {
|
|
139
|
+
const src = join(srcGsd, file);
|
|
140
|
+
if (existsSync(src)) {
|
|
141
|
+
try {
|
|
142
|
+
cpSync(src, join(dstGsd, file), { force: true });
|
|
143
|
+
} catch { /* non-fatal */ }
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
110
148
|
/**
|
|
111
149
|
* Teardown an auto-worktree: chdir back to original base, then remove
|
|
112
150
|
* the worktree and its branch.
|
|
@@ -1520,7 +1520,7 @@ async function dispatchNextUnit(
|
|
|
1520
1520
|
return; // Another dispatch is in progress — bail silently
|
|
1521
1521
|
}
|
|
1522
1522
|
_dispatching = true;
|
|
1523
|
-
|
|
1523
|
+
try {
|
|
1524
1524
|
// Recursion depth guard: when many units are skipped in sequence (e.g., after
|
|
1525
1525
|
// crash recovery with 10+ completed units), recursive dispatchNextUnit calls
|
|
1526
1526
|
// can freeze the TUI or overflow the stack. Yield generously after MAX_SKIP_DEPTH.
|
|
@@ -2425,6 +2425,9 @@ async function dispatchNextUnit(
|
|
|
2425
2425
|
);
|
|
2426
2426
|
await pauseAuto(ctx, pi);
|
|
2427
2427
|
}
|
|
2428
|
+
} finally {
|
|
2429
|
+
_dispatching = false;
|
|
2430
|
+
}
|
|
2428
2431
|
}
|
|
2429
2432
|
|
|
2430
2433
|
// ─── Skill Discovery ──────────────────────────────────────────────────────────
|
|
@@ -87,6 +87,28 @@ export function ensureGitignore(basePath: string): boolean {
|
|
|
87
87
|
existing = readFileSync(gitignorePath, "utf-8");
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
// Self-heal: remove blanket ".gsd/" lines from pre-v2.14.0 projects.
|
|
91
|
+
// The blanket ignore prevented planning artifacts (.gsd/milestones/) from
|
|
92
|
+
// being tracked in git, causing artifacts to vanish in worktrees and
|
|
93
|
+
// triggering loop detection failures. Replace with explicit runtime-only
|
|
94
|
+
// ignores so planning files are tracked naturally.
|
|
95
|
+
let modified = false;
|
|
96
|
+
const lines = existing.split("\n");
|
|
97
|
+
const filteredLines = lines.filter(line => {
|
|
98
|
+
const trimmed = line.trim();
|
|
99
|
+
// Remove standalone ".gsd/" lines (blanket ignore) but keep specific
|
|
100
|
+
// .gsd/ subpath patterns like ".gsd/activity/" or ".gsd/auto.lock"
|
|
101
|
+
if (trimmed === ".gsd/" || trimmed === ".gsd") {
|
|
102
|
+
modified = true;
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
return true;
|
|
106
|
+
});
|
|
107
|
+
if (modified) {
|
|
108
|
+
existing = filteredLines.join("\n");
|
|
109
|
+
writeFileSync(gitignorePath, existing, "utf-8");
|
|
110
|
+
}
|
|
111
|
+
|
|
90
112
|
// Parse existing lines (trimmed, ignoring comments and blanks)
|
|
91
113
|
const existingLines = new Set(
|
|
92
114
|
existing
|
|
@@ -98,7 +120,7 @@ export function ensureGitignore(basePath: string): boolean {
|
|
|
98
120
|
// Find patterns not yet present
|
|
99
121
|
const missing = BASELINE_PATTERNS.filter((p) => !existingLines.has(p));
|
|
100
122
|
|
|
101
|
-
if (missing.length === 0) return
|
|
123
|
+
if (missing.length === 0) return modified;
|
|
102
124
|
|
|
103
125
|
// Build the block to append
|
|
104
126
|
const block = [
|
|
@@ -91,13 +91,19 @@ Do not count the reflection step as a question round. Rounds start after reflect
|
|
|
91
91
|
|
|
92
92
|
## Depth Verification
|
|
93
93
|
|
|
94
|
-
Before moving to the wrap-up gate, present a structured depth summary
|
|
94
|
+
Before moving to the wrap-up gate, present a structured depth summary as a checkpoint.
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
**Print the summary as normal chat text first** — this is where the formatting renders properly. Structure the summary across the depth checklist dimensions using the user's own terminology and framing. Cover: 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.
|
|
97
97
|
|
|
98
|
-
**
|
|
98
|
+
**Then** use `ask_user_questions` with a short confirmation question — NOT the summary itself. The question field is designed for single sentences, not multi-paragraph summaries.
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
**Convention:** The question ID must contain `depth_verification` (e.g., `depth_verification_confirm`). This naming convention enables downstream mechanical detection of this step.
|
|
101
|
+
|
|
102
|
+
Example flow:
|
|
103
|
+
1. Print in chat: the full depth summary with markdown formatting (headers, bold, bullets)
|
|
104
|
+
2. Call `ask_user_questions` with: header "Depth Check", question "Did I capture the depth right?", options "Yes, you got it (Recommended)" and "Not quite — let me clarify"
|
|
105
|
+
|
|
106
|
+
If they clarify, absorb the correction and re-verify.
|
|
101
107
|
|
|
102
108
|
## Wrap-up Gate
|
|
103
109
|
|