gsd-pi 2.38.0-dev.63ad7e5 → 2.38.0-dev.785052f
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 +15 -11
- package/dist/resource-loader.js +34 -1
- package/dist/resources/extensions/browser-tools/index.js +3 -1
- package/dist/resources/extensions/browser-tools/tools/verify.js +97 -0
- package/dist/resources/extensions/github-sync/cli.js +284 -0
- package/dist/resources/extensions/github-sync/index.js +73 -0
- package/dist/resources/extensions/github-sync/mapping.js +67 -0
- package/dist/resources/extensions/github-sync/sync.js +424 -0
- package/dist/resources/extensions/github-sync/templates.js +118 -0
- package/dist/resources/extensions/github-sync/types.js +7 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +1 -1
- package/dist/resources/extensions/gsd/auto-loop.js +593 -516
- package/dist/resources/extensions/gsd/auto-post-unit.js +28 -3
- package/dist/resources/extensions/gsd/auto-prompts.js +197 -19
- package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
- package/dist/resources/extensions/gsd/commands.js +2 -1
- package/dist/resources/extensions/gsd/doctor-providers.js +3 -0
- package/dist/resources/extensions/gsd/doctor.js +20 -1
- package/dist/resources/extensions/gsd/exit-command.js +2 -1
- package/dist/resources/extensions/gsd/files.js +46 -7
- package/dist/resources/extensions/gsd/git-service.js +30 -12
- package/dist/resources/extensions/gsd/gitignore.js +16 -3
- package/dist/resources/extensions/gsd/guided-flow.js +149 -38
- package/dist/resources/extensions/gsd/health-widget-core.js +32 -70
- package/dist/resources/extensions/gsd/health-widget.js +3 -86
- package/dist/resources/extensions/gsd/index.js +22 -19
- package/dist/resources/extensions/gsd/migrate-external.js +18 -1
- package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
- package/dist/resources/extensions/gsd/paths.js +3 -0
- package/dist/resources/extensions/gsd/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +58 -0
- package/dist/resources/extensions/gsd/preferences.js +20 -9
- package/dist/resources/extensions/gsd/prompt-loader.js +6 -2
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -1
- package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/run-uat.md +3 -1
- package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
- package/dist/resources/extensions/gsd/state.js +41 -22
- package/dist/resources/extensions/gsd/templates/runtime.md +21 -0
- package/dist/resources/extensions/gsd/templates/task-plan.md +3 -0
- package/dist/resources/extensions/mcp-client/index.js +14 -1
- package/dist/resources/extensions/remote-questions/status.js +4 -2
- package/dist/resources/extensions/remote-questions/store.js +4 -2
- package/dist/resources/extensions/shared/frontmatter.js +1 -1
- package/package.json +1 -1
- package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
- package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
- package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +6 -1
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
- package/packages/pi-coding-agent/src/core/skills.ts +9 -1
- package/packages/pi-coding-agent/src/index.ts +1 -0
- package/src/resources/extensions/browser-tools/index.ts +3 -0
- package/src/resources/extensions/browser-tools/tools/verify.ts +117 -0
- package/src/resources/extensions/github-sync/cli.ts +364 -0
- package/src/resources/extensions/github-sync/index.ts +93 -0
- package/src/resources/extensions/github-sync/mapping.ts +81 -0
- package/src/resources/extensions/github-sync/sync.ts +556 -0
- package/src/resources/extensions/github-sync/templates.ts +183 -0
- package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
- package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
- package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
- package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
- package/src/resources/extensions/github-sync/types.ts +47 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +1 -1
- package/src/resources/extensions/gsd/auto-loop.ts +472 -434
- package/src/resources/extensions/gsd/auto-post-unit.ts +29 -3
- package/src/resources/extensions/gsd/auto-prompts.ts +242 -19
- package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
- package/src/resources/extensions/gsd/commands.ts +2 -2
- package/src/resources/extensions/gsd/doctor-providers.ts +4 -0
- package/src/resources/extensions/gsd/doctor.ts +22 -1
- package/src/resources/extensions/gsd/exit-command.ts +2 -2
- package/src/resources/extensions/gsd/files.ts +49 -9
- package/src/resources/extensions/gsd/git-service.ts +44 -10
- package/src/resources/extensions/gsd/gitignore.ts +17 -3
- package/src/resources/extensions/gsd/guided-flow.ts +177 -44
- package/src/resources/extensions/gsd/health-widget-core.ts +28 -80
- package/src/resources/extensions/gsd/health-widget.ts +3 -89
- package/src/resources/extensions/gsd/index.ts +21 -16
- package/src/resources/extensions/gsd/migrate-external.ts +18 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
- package/src/resources/extensions/gsd/paths.ts +4 -0
- package/src/resources/extensions/gsd/preferences-types.ts +4 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +50 -0
- package/src/resources/extensions/gsd/preferences.ts +23 -9
- package/src/resources/extensions/gsd/prompt-loader.ts +7 -2
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -1
- package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- 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 +3 -1
- package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
- package/src/resources/extensions/gsd/state.ts +38 -20
- package/src/resources/extensions/gsd/templates/runtime.md +21 -0
- package/src/resources/extensions/gsd/templates/task-plan.md +3 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +111 -37
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +16 -54
- package/src/resources/extensions/gsd/tests/parsers.test.ts +131 -14
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +5 -1
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +140 -0
- package/src/resources/extensions/gsd/types.ts +18 -0
- package/src/resources/extensions/gsd/verification-evidence.ts +16 -0
- package/src/resources/extensions/mcp-client/index.ts +17 -1
- package/src/resources/extensions/remote-questions/status.ts +4 -2
- package/src/resources/extensions/remote-questions/store.ts +4 -2
- package/src/resources/extensions/shared/frontmatter.ts +1 -1
|
@@ -72,11 +72,21 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
72
72
|
const summaryContent = await loadFile(summaryPath);
|
|
73
73
|
if (summaryContent) {
|
|
74
74
|
const summary = parseSummary(summaryContent);
|
|
75
|
+
// Look up GitHub issue number for commit linking
|
|
76
|
+
let ghIssueNumber;
|
|
77
|
+
try {
|
|
78
|
+
const { getTaskIssueNumberForCommit } = await import("../github-sync/sync.js");
|
|
79
|
+
ghIssueNumber = getTaskIssueNumberForCommit(s.basePath, mid, sid, tid) ?? undefined;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// GitHub sync not available — skip
|
|
83
|
+
}
|
|
75
84
|
taskContext = {
|
|
76
85
|
taskId: `${sid}/${tid}`,
|
|
77
86
|
taskTitle: summary.title?.replace(/^T\d+:\s*/, "") || tid,
|
|
78
87
|
oneLiner: summary.oneLiner || undefined,
|
|
79
88
|
keyFiles: summary.frontmatter.key_files?.filter(f => !f.includes("{{")) || undefined,
|
|
89
|
+
issueNumber: ghIssueNumber,
|
|
80
90
|
};
|
|
81
91
|
}
|
|
82
92
|
}
|
|
@@ -94,6 +104,14 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
94
104
|
catch (e) {
|
|
95
105
|
debugLog("postUnit", { phase: "auto-commit", error: String(e) });
|
|
96
106
|
}
|
|
107
|
+
// GitHub sync (non-blocking, opt-in)
|
|
108
|
+
try {
|
|
109
|
+
const { runGitHubSync } = await import("../github-sync/sync.js");
|
|
110
|
+
await runGitHubSync(s.basePath, s.currentUnit.type, s.currentUnit.id);
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
debugLog("postUnit", { phase: "github-sync", error: String(e) });
|
|
114
|
+
}
|
|
97
115
|
// Doctor: fix mechanical bookkeeping (skipped for lightweight sidecars)
|
|
98
116
|
if (!opts?.skipDoctor)
|
|
99
117
|
try {
|
|
@@ -105,12 +123,18 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
105
123
|
if (report.fixesApplied.length > 0) {
|
|
106
124
|
ctx.ui.notify(`Post-hook: applied ${report.fixesApplied.length} fix(es).`, "info");
|
|
107
125
|
}
|
|
108
|
-
// Proactive health tracking
|
|
109
|
-
|
|
126
|
+
// Proactive health tracking — filter to current milestone to avoid
|
|
127
|
+
// cross-milestone stale errors inflating the escalation counter
|
|
128
|
+
const currentMilestoneId = s.currentUnit.id.split("/")[0];
|
|
129
|
+
const milestoneIssues = currentMilestoneId
|
|
130
|
+
? report.issues.filter(i => i.unitId === currentMilestoneId ||
|
|
131
|
+
i.unitId.startsWith(`${currentMilestoneId}/`))
|
|
132
|
+
: report.issues;
|
|
133
|
+
const summary = summarizeDoctorIssues(milestoneIssues);
|
|
110
134
|
recordHealthSnapshot(summary.errors, summary.warnings, report.fixesApplied.length);
|
|
111
135
|
// Check if we should escalate to LLM-assisted heal
|
|
112
136
|
if (summary.errors > 0) {
|
|
113
|
-
const unresolvedErrors =
|
|
137
|
+
const unresolvedErrors = milestoneIssues
|
|
114
138
|
.filter(i => i.severity === "error" && !i.fixable)
|
|
115
139
|
.map(i => ({ code: i.code, message: i.message, unitId: i.unitId }));
|
|
116
140
|
const escalation = checkHealEscalation(summary.errors, unresolvedErrors);
|
|
@@ -123,6 +147,7 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
123
147
|
const reportText = formatDoctorReport(report, { scope: doctorScope, includeWarnings: true });
|
|
124
148
|
const structuredIssues = formatDoctorIssuesForPrompt(actionable);
|
|
125
149
|
dispatchDoctorHeal(pi, doctorScope, reportText, structuredIssues);
|
|
150
|
+
return "dispatched";
|
|
126
151
|
}
|
|
127
152
|
catch (e) {
|
|
128
153
|
debugLog("postUnit", { phase: "doctor-heal-dispatch", error: String(e) });
|
|
@@ -5,14 +5,22 @@
|
|
|
5
5
|
* state, no globals — every dependency is passed as a parameter or imported as a
|
|
6
6
|
* utility.
|
|
7
7
|
*/
|
|
8
|
-
import { loadFile, parseContinue, parsePlan, parseRoadmap, parseSummary, extractUatType, loadActiveOverrides, formatOverridesSection } from "./files.js";
|
|
8
|
+
import { loadFile, parseContinue, parsePlan, parseRoadmap, parseSummary, extractUatType, loadActiveOverrides, formatOverridesSection, parseTaskPlanFile } from "./files.js";
|
|
9
9
|
import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
|
|
10
|
-
import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksDir, resolveTaskFiles, resolveTaskFile, relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath, resolveGsdRootFile, relGsdRootFile, } from "./paths.js";
|
|
11
|
-
import { resolveSkillDiscoveryMode, resolveInlineLevel, loadEffectiveGSDPreferences } from "./preferences.js";
|
|
12
|
-
import {
|
|
10
|
+
import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksDir, resolveTaskFiles, resolveTaskFile, relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath, resolveGsdRootFile, relGsdRootFile, resolveRuntimeFile, } from "./paths.js";
|
|
11
|
+
import { resolveSkillDiscoveryMode, resolveInlineLevel, loadEffectiveGSDPreferences, resolveAllSkillReferences } from "./preferences.js";
|
|
12
|
+
import { getLoadedSkills } from "@gsd/pi-coding-agent";
|
|
13
|
+
import { join, basename } from "node:path";
|
|
13
14
|
import { existsSync } from "node:fs";
|
|
14
|
-
import { computeBudgets, resolveExecutorContextWindow } from "./context-budget.js";
|
|
15
|
+
import { computeBudgets, resolveExecutorContextWindow, truncateAtSectionBoundary } from "./context-budget.js";
|
|
15
16
|
import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
|
|
17
|
+
// ─── Preamble Cap ─────────────────────────────────────────────────────────────
|
|
18
|
+
const MAX_PREAMBLE_CHARS = 30_000;
|
|
19
|
+
function capPreamble(preamble) {
|
|
20
|
+
if (preamble.length <= MAX_PREAMBLE_CHARS)
|
|
21
|
+
return preamble;
|
|
22
|
+
return truncateAtSectionBoundary(preamble, MAX_PREAMBLE_CHARS).content;
|
|
23
|
+
}
|
|
16
24
|
// ─── Executor Constraints ─────────────────────────────────────────────────────
|
|
17
25
|
/**
|
|
18
26
|
* Format executor context constraints for injection into the plan-slice prompt.
|
|
@@ -124,7 +132,6 @@ export async function inlineFileSmart(absPath, relPath, label, query, threshold
|
|
|
124
132
|
return `### ${label}\nSource: \`${relPath}\`\n\n${content.trim()}`;
|
|
125
133
|
}
|
|
126
134
|
// For large files, truncate at section boundary
|
|
127
|
-
const { truncateAtSectionBoundary } = await import("./context-budget.js");
|
|
128
135
|
const truncated = truncateAtSectionBoundary(content, threshold).content;
|
|
129
136
|
return `### ${label}\nSource: \`${relPath}\`\n\n${truncated}`;
|
|
130
137
|
}
|
|
@@ -158,7 +165,6 @@ export async function inlineDependencySummaries(mid, sid, base, budgetChars) {
|
|
|
158
165
|
}
|
|
159
166
|
const result = sections.join("\n\n");
|
|
160
167
|
if (budgetChars !== undefined && result.length > budgetChars) {
|
|
161
|
-
const { truncateAtSectionBoundary } = await import("./context-budget.js");
|
|
162
168
|
return truncateAtSectionBoundary(result, budgetChars).content;
|
|
163
169
|
}
|
|
164
170
|
return result;
|
|
@@ -245,7 +251,129 @@ export async function inlineProjectFromDb(base) {
|
|
|
245
251
|
}
|
|
246
252
|
return inlineGsdRootFile(base, "project.md", "Project");
|
|
247
253
|
}
|
|
248
|
-
// ─── Skill Discovery
|
|
254
|
+
// ─── Skill Activation & Discovery ─────────────────────────────────────────
|
|
255
|
+
function normalizeSkillReference(ref) {
|
|
256
|
+
const normalized = ref.replace(/\\/g, "/").trim();
|
|
257
|
+
const base = basename(normalized).replace(/\.md$/i, "");
|
|
258
|
+
const name = /^SKILL$/i.test(base)
|
|
259
|
+
? basename(normalized.replace(/\/SKILL(?:\.md)?$/i, ""))
|
|
260
|
+
: base;
|
|
261
|
+
return name.trim().toLowerCase();
|
|
262
|
+
}
|
|
263
|
+
function tokenizeSkillContext(...parts) {
|
|
264
|
+
const tokens = new Set();
|
|
265
|
+
const addVariants = (raw) => {
|
|
266
|
+
const value = raw.trim().toLowerCase();
|
|
267
|
+
if (!value || value.length < 2)
|
|
268
|
+
return;
|
|
269
|
+
tokens.add(value);
|
|
270
|
+
tokens.add(value.replace(/[-_]+/g, " "));
|
|
271
|
+
tokens.add(value.replace(/\s+/g, "-"));
|
|
272
|
+
tokens.add(value.replace(/\s+/g, ""));
|
|
273
|
+
};
|
|
274
|
+
for (const part of parts) {
|
|
275
|
+
if (!part)
|
|
276
|
+
continue;
|
|
277
|
+
const text = part.toLowerCase();
|
|
278
|
+
const phraseMatches = text.match(/[a-z0-9][a-z0-9+.#/_-]{1,}/g) ?? [];
|
|
279
|
+
for (const match of phraseMatches) {
|
|
280
|
+
addVariants(match);
|
|
281
|
+
for (const piece of match.split(/[^a-z0-9+.#]+/g)) {
|
|
282
|
+
if (piece.length >= 3)
|
|
283
|
+
addVariants(piece);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return tokens;
|
|
288
|
+
}
|
|
289
|
+
function skillMatchesContext(skill, contextTokens) {
|
|
290
|
+
const haystacks = [
|
|
291
|
+
skill.name.toLowerCase(),
|
|
292
|
+
skill.name.toLowerCase().replace(/[-_]+/g, " "),
|
|
293
|
+
skill.description.toLowerCase(),
|
|
294
|
+
];
|
|
295
|
+
return [...contextTokens].some(token => token.length >= 3 && haystacks.some(haystack => haystack.includes(token)));
|
|
296
|
+
}
|
|
297
|
+
function resolvePreferenceSkillNames(refs, base) {
|
|
298
|
+
if (refs.length === 0)
|
|
299
|
+
return [];
|
|
300
|
+
const prefs = { always_use_skills: refs };
|
|
301
|
+
const report = resolveAllSkillReferences(prefs, base);
|
|
302
|
+
return refs.map(ref => {
|
|
303
|
+
const resolution = report.resolutions.get(ref);
|
|
304
|
+
return normalizeSkillReference(resolution?.resolvedPath ?? ref);
|
|
305
|
+
}).filter(Boolean);
|
|
306
|
+
}
|
|
307
|
+
function ruleMatchesContext(when, contextTokens) {
|
|
308
|
+
const whenTokens = tokenizeSkillContext(when);
|
|
309
|
+
return [...whenTokens].some(token => contextTokens.has(token) || [...contextTokens].some(ctx => ctx.includes(token) || token.includes(ctx)));
|
|
310
|
+
}
|
|
311
|
+
function resolveSkillRuleMatches(prefs, contextTokens, base) {
|
|
312
|
+
if (!prefs?.skill_rules?.length)
|
|
313
|
+
return { include: [], avoid: [] };
|
|
314
|
+
const include = [];
|
|
315
|
+
const avoid = [];
|
|
316
|
+
for (const rule of prefs.skill_rules) {
|
|
317
|
+
if (!ruleMatchesContext(rule.when, contextTokens))
|
|
318
|
+
continue;
|
|
319
|
+
include.push(...resolvePreferenceSkillNames([...(rule.use ?? []), ...(rule.prefer ?? [])], base));
|
|
320
|
+
avoid.push(...resolvePreferenceSkillNames(rule.avoid ?? [], base));
|
|
321
|
+
}
|
|
322
|
+
return { include, avoid };
|
|
323
|
+
}
|
|
324
|
+
function resolvePreferredSkillNames(prefs, visibleSkills, contextTokens, base) {
|
|
325
|
+
if (!prefs?.prefer_skills?.length)
|
|
326
|
+
return [];
|
|
327
|
+
const preferred = new Set(resolvePreferenceSkillNames(prefs.prefer_skills, base));
|
|
328
|
+
return visibleSkills
|
|
329
|
+
.filter(skill => preferred.has(normalizeSkillReference(skill.name)) && skillMatchesContext(skill, contextTokens))
|
|
330
|
+
.map(skill => normalizeSkillReference(skill.name));
|
|
331
|
+
}
|
|
332
|
+
function formatSkillActivationBlock(skillNames) {
|
|
333
|
+
if (skillNames.length === 0)
|
|
334
|
+
return "";
|
|
335
|
+
const calls = skillNames.map(name => `Call Skill('${name}')`).join('. ');
|
|
336
|
+
return `<skill_activation>${calls}.</skill_activation>`;
|
|
337
|
+
}
|
|
338
|
+
export function buildSkillActivationBlock(params) {
|
|
339
|
+
const prefs = params.preferences ?? loadEffectiveGSDPreferences()?.preferences;
|
|
340
|
+
const contextTokens = tokenizeSkillContext(params.milestoneId, params.milestoneTitle, params.sliceId, params.sliceTitle, params.taskId, params.taskTitle, ...(params.extraContext ?? []), params.taskPlanContent ?? undefined);
|
|
341
|
+
const visibleSkills = getLoadedSkills().filter(skill => !skill.disableModelInvocation);
|
|
342
|
+
const installedNames = new Set(visibleSkills.map(skill => normalizeSkillReference(skill.name)));
|
|
343
|
+
const avoided = new Set(resolvePreferenceSkillNames(prefs?.avoid_skills ?? [], params.base));
|
|
344
|
+
const matched = new Set();
|
|
345
|
+
for (const name of resolvePreferenceSkillNames(prefs?.always_use_skills ?? [], params.base)) {
|
|
346
|
+
matched.add(name);
|
|
347
|
+
}
|
|
348
|
+
const ruleMatches = resolveSkillRuleMatches(prefs, contextTokens, params.base);
|
|
349
|
+
for (const name of ruleMatches.include)
|
|
350
|
+
matched.add(name);
|
|
351
|
+
for (const name of ruleMatches.avoid)
|
|
352
|
+
avoided.add(name);
|
|
353
|
+
for (const name of resolvePreferredSkillNames(prefs, visibleSkills, contextTokens, params.base)) {
|
|
354
|
+
matched.add(name);
|
|
355
|
+
}
|
|
356
|
+
if (params.taskPlanContent) {
|
|
357
|
+
try {
|
|
358
|
+
const taskPlan = parseTaskPlanFile(params.taskPlanContent);
|
|
359
|
+
for (const skillName of taskPlan.frontmatter.skills_used) {
|
|
360
|
+
matched.add(normalizeSkillReference(skillName));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
// Non-fatal — malformed task plan should not break prompt construction
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
for (const skill of visibleSkills) {
|
|
368
|
+
if (skillMatchesContext(skill, contextTokens)) {
|
|
369
|
+
matched.add(normalizeSkillReference(skill.name));
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
const ordered = [...matched]
|
|
373
|
+
.filter(name => installedNames.has(name) && !avoided.has(name))
|
|
374
|
+
.sort();
|
|
375
|
+
return formatSkillActivationBlock(ordered);
|
|
376
|
+
}
|
|
249
377
|
/**
|
|
250
378
|
* Build the skill discovery template variables for research prompts.
|
|
251
379
|
* Returns { skillDiscoveryMode, skillDiscoveryInstructions } for template substitution.
|
|
@@ -525,7 +653,7 @@ export async function buildResearchMilestonePrompt(mid, midTitle, base) {
|
|
|
525
653
|
if (knowledgeInlineRM)
|
|
526
654
|
inlined.push(knowledgeInlineRM);
|
|
527
655
|
inlined.push(inlineTemplate("research", "Research"));
|
|
528
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
656
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
529
657
|
const outputRelPath = relMilestoneFile(base, mid, "RESEARCH");
|
|
530
658
|
return loadPrompt("research-milestone", {
|
|
531
659
|
workingDirectory: base,
|
|
@@ -534,6 +662,12 @@ export async function buildResearchMilestonePrompt(mid, midTitle, base) {
|
|
|
534
662
|
contextPath: contextRel,
|
|
535
663
|
outputPath: join(base, outputRelPath),
|
|
536
664
|
inlinedContext,
|
|
665
|
+
skillActivation: buildSkillActivationBlock({
|
|
666
|
+
base,
|
|
667
|
+
milestoneId: mid,
|
|
668
|
+
milestoneTitle: midTitle,
|
|
669
|
+
extraContext: [inlinedContext],
|
|
670
|
+
}),
|
|
537
671
|
...buildSkillDiscoveryVars(),
|
|
538
672
|
});
|
|
539
673
|
}
|
|
@@ -578,7 +712,7 @@ export async function buildPlanMilestonePrompt(mid, midTitle, base, level) {
|
|
|
578
712
|
inlined.push(inlineTemplate("plan", "Slice Plan"));
|
|
579
713
|
inlined.push(inlineTemplate("task-plan", "Task Plan"));
|
|
580
714
|
}
|
|
581
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
715
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
582
716
|
const outputRelPath = relMilestoneFile(base, mid, "ROADMAP");
|
|
583
717
|
const researchOutputPath = join(base, relMilestoneFile(base, mid, "RESEARCH"));
|
|
584
718
|
const secretsOutputPath = join(base, relMilestoneFile(base, mid, "SECRETS"));
|
|
@@ -593,6 +727,12 @@ export async function buildPlanMilestonePrompt(mid, midTitle, base, level) {
|
|
|
593
727
|
secretsOutputPath,
|
|
594
728
|
inlinedContext,
|
|
595
729
|
sourceFilePaths: buildSourceFilePaths(base, mid),
|
|
730
|
+
skillActivation: buildSkillActivationBlock({
|
|
731
|
+
base,
|
|
732
|
+
milestoneId: mid,
|
|
733
|
+
milestoneTitle: midTitle,
|
|
734
|
+
extraContext: [inlinedContext],
|
|
735
|
+
}),
|
|
596
736
|
...buildSkillDiscoveryVars(),
|
|
597
737
|
});
|
|
598
738
|
}
|
|
@@ -626,7 +766,7 @@ export async function buildResearchSlicePrompt(mid, _midTitle, sid, sTitle, base
|
|
|
626
766
|
const overridesInline = formatOverridesSection(activeOverrides);
|
|
627
767
|
if (overridesInline)
|
|
628
768
|
inlined.unshift(overridesInline);
|
|
629
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
769
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
630
770
|
const outputRelPath = relSliceFile(base, mid, sid, "RESEARCH");
|
|
631
771
|
return loadPrompt("research-slice", {
|
|
632
772
|
workingDirectory: base,
|
|
@@ -638,6 +778,13 @@ export async function buildResearchSlicePrompt(mid, _midTitle, sid, sTitle, base
|
|
|
638
778
|
outputPath: join(base, outputRelPath),
|
|
639
779
|
inlinedContext,
|
|
640
780
|
dependencySummaries: depContent,
|
|
781
|
+
skillActivation: buildSkillActivationBlock({
|
|
782
|
+
base,
|
|
783
|
+
milestoneId: mid,
|
|
784
|
+
sliceId: sid,
|
|
785
|
+
sliceTitle: sTitle,
|
|
786
|
+
extraContext: [inlinedContext, depContent],
|
|
787
|
+
}),
|
|
641
788
|
...buildSkillDiscoveryVars(),
|
|
642
789
|
});
|
|
643
790
|
}
|
|
@@ -672,7 +819,7 @@ export async function buildPlanSlicePrompt(mid, _midTitle, sid, sTitle, base, le
|
|
|
672
819
|
const planOverridesInline = formatOverridesSection(planActiveOverrides);
|
|
673
820
|
if (planOverridesInline)
|
|
674
821
|
inlined.unshift(planOverridesInline);
|
|
675
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
822
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
676
823
|
// Build executor context constraints from the budget engine
|
|
677
824
|
const executorContextConstraints = formatExecutorConstraints();
|
|
678
825
|
const outputRelPath = relSliceFile(base, mid, sid, "PLAN");
|
|
@@ -693,6 +840,13 @@ export async function buildPlanSlicePrompt(mid, _midTitle, sid, sTitle, base, le
|
|
|
693
840
|
sourceFilePaths: buildSourceFilePaths(base, mid, sid),
|
|
694
841
|
executorContextConstraints,
|
|
695
842
|
commitInstruction,
|
|
843
|
+
skillActivation: buildSkillActivationBlock({
|
|
844
|
+
base,
|
|
845
|
+
milestoneId: mid,
|
|
846
|
+
sliceId: sid,
|
|
847
|
+
sliceTitle: sTitle,
|
|
848
|
+
extraContext: [inlinedContext, depContent],
|
|
849
|
+
}),
|
|
696
850
|
});
|
|
697
851
|
}
|
|
698
852
|
export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base, level) {
|
|
@@ -760,11 +914,17 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
|
|
|
760
914
|
const carryForwardBudget = Math.floor(budgets.inlineContextBudgetChars * 0.4);
|
|
761
915
|
let finalCarryForward = carryForwardSection;
|
|
762
916
|
if (carryForwardSection.length > carryForwardBudget) {
|
|
763
|
-
const { truncateAtSectionBoundary } = await import("./context-budget.js");
|
|
764
917
|
finalCarryForward = truncateAtSectionBoundary(carryForwardSection, carryForwardBudget).content;
|
|
765
918
|
}
|
|
919
|
+
// Inline RUNTIME.md if present
|
|
920
|
+
const runtimePath = resolveRuntimeFile(base);
|
|
921
|
+
const runtimeContent = existsSync(runtimePath) ? await loadFile(runtimePath) : null;
|
|
922
|
+
const runtimeContext = runtimeContent
|
|
923
|
+
? `### Runtime Context\nSource: \`.gsd/RUNTIME.md\`\n\n${runtimeContent.trim()}`
|
|
924
|
+
: "";
|
|
766
925
|
return loadPrompt("execute-task", {
|
|
767
926
|
overridesSection,
|
|
927
|
+
runtimeContext,
|
|
768
928
|
workingDirectory: base,
|
|
769
929
|
milestoneId: mid, sliceId: sid, sliceTitle: sTitle, taskId: tid, taskTitle: tTitle,
|
|
770
930
|
planPath: join(base, relSliceFile(base, mid, sid, "PLAN")),
|
|
@@ -778,6 +938,16 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
|
|
|
778
938
|
taskSummaryPath,
|
|
779
939
|
inlinedTemplates,
|
|
780
940
|
verificationBudget,
|
|
941
|
+
skillActivation: buildSkillActivationBlock({
|
|
942
|
+
base,
|
|
943
|
+
milestoneId: mid,
|
|
944
|
+
sliceId: sid,
|
|
945
|
+
sliceTitle: sTitle,
|
|
946
|
+
taskId: tid,
|
|
947
|
+
taskTitle: tTitle,
|
|
948
|
+
taskPlanContent,
|
|
949
|
+
extraContext: [taskPlanInline, slicePlanExcerpt, finalCarryForward, resumeSection],
|
|
950
|
+
}),
|
|
781
951
|
});
|
|
782
952
|
}
|
|
783
953
|
export async function buildCompleteSlicePrompt(mid, _midTitle, sid, sTitle, base, level) {
|
|
@@ -819,7 +989,7 @@ export async function buildCompleteSlicePrompt(mid, _midTitle, sid, sTitle, base
|
|
|
819
989
|
const completeOverridesInline = formatOverridesSection(completeActiveOverrides);
|
|
820
990
|
if (completeOverridesInline)
|
|
821
991
|
inlined.unshift(completeOverridesInline);
|
|
822
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
992
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
823
993
|
const sliceRel = relSlicePath(base, mid, sid);
|
|
824
994
|
const sliceSummaryPath = join(base, `${sliceRel}/${sid}-SUMMARY.md`);
|
|
825
995
|
const sliceUatPath = join(base, `${sliceRel}/${sid}-UAT.md`);
|
|
@@ -875,7 +1045,7 @@ export async function buildCompleteMilestonePrompt(mid, midTitle, base, level) {
|
|
|
875
1045
|
if (contextInline)
|
|
876
1046
|
inlined.push(contextInline);
|
|
877
1047
|
inlined.push(inlineTemplate("milestone-summary", "Milestone Summary"));
|
|
878
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1048
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
879
1049
|
const milestoneSummaryPath = join(base, `${relMilestonePath(base, mid)}/${mid}-SUMMARY.md`);
|
|
880
1050
|
return loadPrompt("complete-milestone", {
|
|
881
1051
|
workingDirectory: base,
|
|
@@ -942,7 +1112,7 @@ export async function buildValidateMilestonePrompt(mid, midTitle, base, level) {
|
|
|
942
1112
|
const contextInline = await inlineFileOptional(contextPath, contextRel, "Milestone Context");
|
|
943
1113
|
if (contextInline)
|
|
944
1114
|
inlined.push(contextInline);
|
|
945
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1115
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
946
1116
|
const validationOutputPath = join(base, `${relMilestonePath(base, mid)}/${mid}-VALIDATION.md`);
|
|
947
1117
|
const roadmapOutputPath = `${relMilestonePath(base, mid)}/${mid}-ROADMAP.md`;
|
|
948
1118
|
return loadPrompt("validate-milestone", {
|
|
@@ -990,7 +1160,7 @@ export async function buildReplanSlicePrompt(mid, midTitle, sid, sTitle, base) {
|
|
|
990
1160
|
const replanOverridesInline = formatOverridesSection(replanActiveOverrides);
|
|
991
1161
|
if (replanOverridesInline)
|
|
992
1162
|
inlined.unshift(replanOverridesInline);
|
|
993
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1163
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
994
1164
|
const replanPath = join(base, `${relSlicePath(base, mid, sid)}/${sid}-REPLAN.md`);
|
|
995
1165
|
// Build capture context for replan prompt (captures that triggered this replan)
|
|
996
1166
|
let captureContext = "(none)";
|
|
@@ -1015,6 +1185,14 @@ export async function buildReplanSlicePrompt(mid, midTitle, sid, sTitle, base) {
|
|
|
1015
1185
|
inlinedContext,
|
|
1016
1186
|
replanPath,
|
|
1017
1187
|
captureContext,
|
|
1188
|
+
skillActivation: buildSkillActivationBlock({
|
|
1189
|
+
base,
|
|
1190
|
+
milestoneId: mid,
|
|
1191
|
+
milestoneTitle: midTitle,
|
|
1192
|
+
sliceId: sid,
|
|
1193
|
+
sliceTitle: sTitle,
|
|
1194
|
+
extraContext: [inlinedContext, captureContext],
|
|
1195
|
+
}),
|
|
1018
1196
|
});
|
|
1019
1197
|
}
|
|
1020
1198
|
export async function buildRunUatPrompt(mid, sliceId, uatPath, uatContent, base) {
|
|
@@ -1030,7 +1208,7 @@ export async function buildRunUatPrompt(mid, sliceId, uatPath, uatContent, base)
|
|
|
1030
1208
|
const projectInline = await inlineProjectFromDb(base);
|
|
1031
1209
|
if (projectInline)
|
|
1032
1210
|
inlined.push(projectInline);
|
|
1033
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1211
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1034
1212
|
const uatResultPath = join(base, relSliceFile(base, mid, sliceId, "UAT-RESULT"));
|
|
1035
1213
|
const uatType = extractUatType(uatContent) ?? "human-experience";
|
|
1036
1214
|
return loadPrompt("run-uat", {
|
|
@@ -1066,7 +1244,7 @@ export async function buildReassessRoadmapPrompt(mid, midTitle, completedSliceId
|
|
|
1066
1244
|
const knowledgeInlineRA = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
|
|
1067
1245
|
if (knowledgeInlineRA)
|
|
1068
1246
|
inlined.push(knowledgeInlineRA);
|
|
1069
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1247
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1070
1248
|
const assessmentPath = join(base, relSliceFile(base, mid, completedSliceId, "ASSESSMENT"));
|
|
1071
1249
|
// Build deferred captures context for reassess prompt
|
|
1072
1250
|
let deferredCaptures = "(none)";
|
|
@@ -15,10 +15,10 @@ import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
|
|
|
15
15
|
import { gsdRoot } from "./paths.js";
|
|
16
16
|
import { createWorktree, removeWorktree, worktreePath, } from "./worktree-manager.js";
|
|
17
17
|
import { detectWorktreeName, nudgeGitBranchCache, } from "./worktree.js";
|
|
18
|
-
import { MergeConflictError, readIntegrationBranch } from "./git-service.js";
|
|
18
|
+
import { MergeConflictError, readIntegrationBranch, RUNTIME_EXCLUSION_PATHS } from "./git-service.js";
|
|
19
19
|
import { parseRoadmap } from "./files.js";
|
|
20
20
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
21
|
-
import { nativeGetCurrentBranch, nativeWorkingTreeStatus,
|
|
21
|
+
import { nativeGetCurrentBranch, nativeWorkingTreeStatus, nativeAddAllWithExclusions, nativeCommit, nativeCheckoutBranch, nativeMergeSquash, nativeConflictFiles, nativeCheckoutTheirs, nativeAddPaths, nativeRmForce, nativeBranchDelete, nativeBranchExists, } from "./native-git-bridge.js";
|
|
22
22
|
// ─── Module State ──────────────────────────────────────────────────────────
|
|
23
23
|
/** Original project root before chdir into auto-worktree. */
|
|
24
24
|
let originalBase = null;
|
|
@@ -656,7 +656,7 @@ function autoCommitDirtyState(cwd) {
|
|
|
656
656
|
const status = nativeWorkingTreeStatus(cwd);
|
|
657
657
|
if (!status)
|
|
658
658
|
return false;
|
|
659
|
-
|
|
659
|
+
nativeAddAllWithExclusions(cwd, RUNTIME_EXCLUSION_PATHS);
|
|
660
660
|
const result = nativeCommit(cwd, "chore: auto-commit before milestone merge");
|
|
661
661
|
return result !== null;
|
|
662
662
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* One command, one wizard. Routes to smart entry or status.
|
|
5
5
|
*/
|
|
6
|
+
import { importExtensionModule } from "@gsd/pi-coding-agent";
|
|
6
7
|
import { existsSync, readFileSync, readdirSync, unlinkSync } from "node:fs";
|
|
7
8
|
import { homedir } from "node:os";
|
|
8
9
|
import { join } from "node:path";
|
|
@@ -529,7 +530,7 @@ export async function handleGSDCommand(args, ctx, pi) {
|
|
|
529
530
|
return;
|
|
530
531
|
}
|
|
531
532
|
if (trimmed === "widget" || trimmed.startsWith("widget ")) {
|
|
532
|
-
const { cycleWidgetMode, setWidgetMode, getWidgetMode } = await import
|
|
533
|
+
const { cycleWidgetMode, setWidgetMode, getWidgetMode } = await importExtensionModule(import.meta.url, "./auto-dashboard.js");
|
|
533
534
|
const arg = trimmed.replace(/^widget\s*/, "").trim();
|
|
534
535
|
if (arg === "full" || arg === "small" || arg === "min" || arg === "off") {
|
|
535
536
|
setWidgetMode(arg);
|
|
@@ -98,6 +98,9 @@ function collectConfiguredModelProviders() {
|
|
|
98
98
|
}
|
|
99
99
|
function resolveKey(providerId) {
|
|
100
100
|
const info = PROVIDER_REGISTRY.find(p => p.id === providerId);
|
|
101
|
+
if (providerId === "anthropic-vertex" && process.env.ANTHROPIC_VERTEX_PROJECT_ID) {
|
|
102
|
+
return { found: true, source: "env", backedOff: false };
|
|
103
|
+
}
|
|
101
104
|
// Check auth.json
|
|
102
105
|
const authPath = getAuthPath();
|
|
103
106
|
if (existsSync(authPath)) {
|
|
@@ -257,10 +257,23 @@ async function markSliceDoneInRoadmap(basePath, milestoneId, sliceId, fixesAppli
|
|
|
257
257
|
fixesApplied.push(`marked ${sliceId} done in ${roadmapPath}`);
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
|
+
async function markSliceUndoneInRoadmap(basePath, milestoneId, sliceId, fixesApplied) {
|
|
261
|
+
const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
262
|
+
if (!roadmapPath)
|
|
263
|
+
return;
|
|
264
|
+
const content = await loadFile(roadmapPath);
|
|
265
|
+
if (!content)
|
|
266
|
+
return;
|
|
267
|
+
const updated = content.replace(new RegExp(`^(\\s*-\\s+)\\[x\\]\\s+\\*\\*${sliceId}:`, "m"), `$1[ ] **${sliceId}:`);
|
|
268
|
+
if (updated !== content) {
|
|
269
|
+
await saveFile(roadmapPath, updated);
|
|
270
|
+
fixesApplied.push(`unmarked ${sliceId} in ${roadmapPath} (premature completion)`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
260
273
|
function matchesScope(unitId, scope) {
|
|
261
274
|
if (!scope)
|
|
262
275
|
return true;
|
|
263
|
-
return unitId === scope || unitId.startsWith(`${scope}/`)
|
|
276
|
+
return unitId === scope || unitId.startsWith(`${scope}/`);
|
|
264
277
|
}
|
|
265
278
|
function auditRequirements(content) {
|
|
266
279
|
if (!content)
|
|
@@ -828,6 +841,12 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
828
841
|
file: relSliceFile(basePath, milestoneId, slice.id, "SUMMARY"),
|
|
829
842
|
fixable: true,
|
|
830
843
|
});
|
|
844
|
+
if (!allTasksDone) {
|
|
845
|
+
dryRunCanFix("slice_checked_missing_summary", `uncheck ${slice.id} in roadmap (tasks incomplete)`);
|
|
846
|
+
if (shouldFix("slice_checked_missing_summary")) {
|
|
847
|
+
await markSliceUndoneInRoadmap(basePath, milestoneId, slice.id, fixesApplied);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
831
850
|
}
|
|
832
851
|
if (slice.done && !hasSliceUat) {
|
|
833
852
|
issues.push({
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { importExtensionModule } from "@gsd/pi-coding-agent";
|
|
1
2
|
export function registerExitCommand(pi, deps = {}) {
|
|
2
3
|
pi.registerCommand("exit", {
|
|
3
4
|
description: "Exit GSD gracefully",
|
|
4
5
|
handler: async (_args, ctx) => {
|
|
5
6
|
// Stop auto-mode first so locks and activity state are cleaned up before shutdown.
|
|
6
|
-
const stopAuto = deps.stopAuto ?? (await import
|
|
7
|
+
const stopAuto = deps.stopAuto ?? (await importExtensionModule(import.meta.url, "./auto.js")).stopAuto;
|
|
7
8
|
await stopAuto(ctx, pi, "Graceful exit");
|
|
8
9
|
ctx.shutdown();
|
|
9
10
|
},
|
|
@@ -222,13 +222,48 @@ export function formatSecretsManifest(manifest) {
|
|
|
222
222
|
return lines.join('\n') + '\n';
|
|
223
223
|
}
|
|
224
224
|
// ─── Slice Plan Parser ─────────────────────────────────────────────────────
|
|
225
|
+
function normalizeTaskPlanFrontmatter(frontmatter) {
|
|
226
|
+
const estimatedStepsRaw = frontmatter.estimated_steps;
|
|
227
|
+
const estimatedFilesRaw = frontmatter.estimated_files;
|
|
228
|
+
const skillsUsedRaw = frontmatter.skills_used;
|
|
229
|
+
const parseOptionalNumber = (value) => {
|
|
230
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
231
|
+
return value;
|
|
232
|
+
if (typeof value === 'string' && value.trim()) {
|
|
233
|
+
const parsed = parseInt(value, 10);
|
|
234
|
+
if (Number.isFinite(parsed))
|
|
235
|
+
return parsed;
|
|
236
|
+
}
|
|
237
|
+
return undefined;
|
|
238
|
+
};
|
|
239
|
+
const estimated_steps = parseOptionalNumber(estimatedStepsRaw);
|
|
240
|
+
const estimated_files = parseOptionalNumber(estimatedFilesRaw);
|
|
241
|
+
const skills_used = Array.isArray(skillsUsedRaw)
|
|
242
|
+
? skillsUsedRaw.map(v => String(v).trim()).filter(Boolean)
|
|
243
|
+
: typeof skillsUsedRaw === 'string' && skillsUsedRaw.trim()
|
|
244
|
+
? [skillsUsedRaw.trim()]
|
|
245
|
+
: [];
|
|
246
|
+
return {
|
|
247
|
+
...(estimated_steps !== undefined ? { estimated_steps } : {}),
|
|
248
|
+
...(estimated_files !== undefined ? { estimated_files } : {}),
|
|
249
|
+
skills_used,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
export function parseTaskPlanFile(content) {
|
|
253
|
+
const [fmLines] = splitFrontmatter(content);
|
|
254
|
+
const fm = fmLines ? parseFrontmatterMap(fmLines) : {};
|
|
255
|
+
return {
|
|
256
|
+
frontmatter: normalizeTaskPlanFrontmatter(fm),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
225
259
|
export function parsePlan(content) {
|
|
226
260
|
return cachedParse(content, 'plan', _parsePlanImpl);
|
|
227
261
|
}
|
|
228
262
|
function _parsePlanImpl(content) {
|
|
229
263
|
const stopTimer = debugTime("parse-plan");
|
|
264
|
+
const [, body] = splitFrontmatter(content);
|
|
230
265
|
// Try native parser first for better performance
|
|
231
|
-
const nativeResult = nativeParsePlanFile(
|
|
266
|
+
const nativeResult = nativeParsePlanFile(body);
|
|
232
267
|
if (nativeResult) {
|
|
233
268
|
stopTimer({ native: true });
|
|
234
269
|
return {
|
|
@@ -249,7 +284,7 @@ function _parsePlanImpl(content) {
|
|
|
249
284
|
filesLikelyTouched: nativeResult.filesLikelyTouched,
|
|
250
285
|
};
|
|
251
286
|
}
|
|
252
|
-
const lines =
|
|
287
|
+
const lines = body.split('\n');
|
|
253
288
|
const h1 = lines.find(l => l.startsWith('# '));
|
|
254
289
|
let id = '';
|
|
255
290
|
let title = '';
|
|
@@ -263,11 +298,11 @@ function _parsePlanImpl(content) {
|
|
|
263
298
|
title = h1.slice(2).trim();
|
|
264
299
|
}
|
|
265
300
|
}
|
|
266
|
-
const goal = extractBoldField(
|
|
267
|
-
const demo = extractBoldField(
|
|
268
|
-
const mhSection = extractSection(
|
|
301
|
+
const goal = extractBoldField(body, 'Goal') || '';
|
|
302
|
+
const demo = extractBoldField(body, 'Demo') || '';
|
|
303
|
+
const mhSection = extractSection(body, 'Must-Haves');
|
|
269
304
|
const mustHaves = mhSection ? parseBullets(mhSection) : [];
|
|
270
|
-
const tasksSection = extractSection(
|
|
305
|
+
const tasksSection = extractSection(body, 'Tasks');
|
|
271
306
|
const tasks = [];
|
|
272
307
|
if (tasksSection) {
|
|
273
308
|
const taskLines = tasksSection.split('\n');
|
|
@@ -315,7 +350,7 @@ function _parsePlanImpl(content) {
|
|
|
315
350
|
if (currentTask)
|
|
316
351
|
tasks.push(currentTask);
|
|
317
352
|
}
|
|
318
|
-
const filesSection = extractSection(
|
|
353
|
+
const filesSection = extractSection(body, 'Files Likely Touched');
|
|
319
354
|
const filesLikelyTouched = filesSection ? parseBullets(filesSection) : [];
|
|
320
355
|
const result = { id, title, goal, demo, mustHaves, tasks, filesLikelyTouched };
|
|
321
356
|
stopTimer({ tasks: tasks.length });
|
|
@@ -692,6 +727,10 @@ export function extractUatType(content) {
|
|
|
692
727
|
const rawValue = modeBullet.slice('UAT mode:'.length).trim().toLowerCase();
|
|
693
728
|
if (rawValue.startsWith('artifact-driven'))
|
|
694
729
|
return 'artifact-driven';
|
|
730
|
+
if (rawValue.startsWith('browser-executable'))
|
|
731
|
+
return 'browser-executable';
|
|
732
|
+
if (rawValue.startsWith('runtime-executable'))
|
|
733
|
+
return 'runtime-executable';
|
|
695
734
|
if (rawValue.startsWith('live-runtime'))
|
|
696
735
|
return 'live-runtime';
|
|
697
736
|
if (rawValue.startsWith('human-experience'))
|