@opengsd/gsd-pi 1.1.1-dev.b2556262 → 1.2.0-dev.844675c9
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/project-sessions.js +4 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +17 -9
- package/dist/resources/extensions/gsd/auto/contracts.js +8 -1
- package/dist/resources/extensions/gsd/auto/orchestrator.js +659 -57
- package/dist/resources/extensions/gsd/auto-prompts.js +110 -1
- package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +5 -0
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +29 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +24 -17
- package/dist/resources/extensions/gsd/auto.js +62 -464
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +4 -1
- package/dist/resources/extensions/gsd/debug-logger.js +10 -0
- package/dist/resources/extensions/gsd/doctor-proactive.js +7 -2
- package/dist/resources/extensions/gsd/guided-flow.js +2 -2
- package/dist/resources/extensions/gsd/markdown-renderer.js +31 -32
- package/dist/resources/extensions/gsd/mcp-filter.js +6 -0
- package/dist/resources/extensions/gsd/native-git-bridge.js +45 -0
- package/dist/resources/extensions/gsd/prompts/discuss.md +6 -7
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +5 -7
- package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +3 -5
- package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +1 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -6
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +2 -2
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +5 -3
- package/dist/resources/extensions/gsd/schemas/parsers.js +6 -1
- package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +21 -1
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +169 -20
- package/dist/resources/extensions/gsd/user-input-boundary.js +42 -4
- package/dist/tsconfig.extensions.tsbuildinfo +1 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
- package/dist/web/standalone/.next/server/chunks/5047.js +2 -0
- package/dist/web/standalone/.next/server/chunks/5124.js +1 -0
- package/dist/web/standalone/.next/server/chunks/8357.js +2 -2
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/dist/web/standalone/node_modules/postcss/lib/container.js +26 -18
- package/dist/web/standalone/node_modules/postcss/lib/css-syntax-error.js +47 -14
- package/dist/web/standalone/node_modules/postcss/lib/declaration.js +4 -4
- package/dist/web/standalone/node_modules/postcss/lib/fromJSON.js +3 -3
- package/dist/web/standalone/node_modules/postcss/lib/input.js +54 -29
- package/dist/web/standalone/node_modules/postcss/lib/lazy-result.js +47 -37
- package/dist/web/standalone/node_modules/postcss/lib/map-generator.js +26 -9
- package/dist/web/standalone/node_modules/postcss/lib/no-work-result.js +57 -55
- package/dist/web/standalone/node_modules/postcss/lib/node.js +99 -31
- package/dist/web/standalone/node_modules/postcss/lib/parse.js +1 -1
- package/dist/web/standalone/node_modules/postcss/lib/parser.js +10 -9
- package/dist/web/standalone/node_modules/postcss/lib/postcss.js +12 -12
- package/dist/web/standalone/node_modules/postcss/lib/previous-map.js +30 -11
- package/dist/web/standalone/node_modules/postcss/lib/processor.js +7 -7
- package/dist/web/standalone/node_modules/postcss/lib/result.js +5 -5
- package/dist/web/standalone/node_modules/postcss/lib/rule.js +6 -6
- package/dist/web/standalone/node_modules/postcss/lib/stringifier.js +69 -28
- package/dist/web/standalone/node_modules/postcss/lib/tokenize.js +6 -2
- package/dist/web/standalone/node_modules/postcss/package.json +48 -48
- package/package.json +16 -11
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +0 -34
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +12 -46
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +11 -3
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/scripts/install/deps.js +10 -0
- package/scripts/link-workspace-packages.cjs +7 -40
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +18 -8
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +2 -2
- package/src/resources/extensions/gsd/auto/contracts.ts +8 -119
- package/src/resources/extensions/gsd/auto/orchestrator.ts +794 -58
- package/src/resources/extensions/gsd/auto-prompts.ts +114 -1
- package/src/resources/extensions/gsd/auto-runtime-state.ts +4 -0
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +5 -0
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +33 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +24 -16
- package/src/resources/extensions/gsd/auto.ts +81 -500
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +4 -0
- package/src/resources/extensions/gsd/debug-logger.ts +11 -0
- package/src/resources/extensions/gsd/doctor-proactive.ts +8 -2
- package/src/resources/extensions/gsd/guided-flow.ts +2 -2
- package/src/resources/extensions/gsd/markdown-renderer.ts +38 -19
- package/src/resources/extensions/gsd/mcp-filter.ts +7 -0
- package/src/resources/extensions/gsd/native-git-bridge.ts +48 -0
- package/src/resources/extensions/gsd/prompts/discuss.md +6 -7
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +5 -7
- package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +3 -5
- package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +1 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -6
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-milestone.md +2 -2
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +5 -3
- package/src/resources/extensions/gsd/schemas/parsers.ts +6 -1
- package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +31 -10
- package/src/resources/extensions/gsd/tests/artifact-db-drift-memo.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/auto-dispatch-baseline-harness.test.ts +53 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +590 -855
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +38 -10
- package/src/resources/extensions/gsd/tests/debug-logger.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/execute-summary-save-empty-project.test.ts +64 -1
- package/src/resources/extensions/gsd/tests/integration/merge-strategy-regular.test.ts +157 -0
- package/src/resources/extensions/gsd/tests/markdown-renderer-parse-cache.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/native-merge-regular.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/orchestrator-legacy-parity.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/parse-project-milestone-bridge.test.ts +77 -0
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +29 -2
- package/src/resources/extensions/gsd/tests/research-milestone-composer.test.ts +65 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +19 -5
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/user-input-boundary.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +15 -3
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +183 -21
- package/src/resources/extensions/gsd/user-input-boundary.ts +37 -5
- package/dist/web/standalone/.next/server/chunks/678.js +0 -2
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts +0 -21
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts.map +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js +0 -213
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js.map +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-density-prototype.d.ts +0 -28
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-density-prototype.d.ts.map +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-density-prototype.js +0 -249
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-density-prototype.js.map +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-design-prototype.d.ts +0 -19
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-design-prototype.d.ts.map +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-design-prototype.js +0 -797
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-design-prototype.js.map +0 -1
- package/scripts/ensure-workspace-builds.cjs +0 -129
- /package/dist/web/standalone/.next/static/{tJOKQbQRO-9MiFDO8DIDS → Qbr81pQ-pbQXP4bq2VXLv}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{tJOKQbQRO-9MiFDO8DIDS → Qbr81pQ-pbQXP4bq2VXLv}/_ssgManifest.js +0 -0
|
@@ -25,6 +25,7 @@ const _counters = {
|
|
|
25
25
|
parsePlanTotalMs: 0,
|
|
26
26
|
dispatches: 0,
|
|
27
27
|
renders: 0,
|
|
28
|
+
gitInvocations: 0,
|
|
28
29
|
};
|
|
29
30
|
|
|
30
31
|
/** Max debug log files to keep. Older ones are pruned on enable. */
|
|
@@ -131,6 +132,15 @@ export function debugCount(counter: keyof typeof _counters, value = 1): void {
|
|
|
131
132
|
_counters[counter] += value;
|
|
132
133
|
}
|
|
133
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Snapshot the current debug counters. Used by the per-dispatch benchmark
|
|
137
|
+
* harness (#442) to read counts without disabling debug (which is what
|
|
138
|
+
* writeDebugSummary does). Returns a copy so callers can't mutate internal state.
|
|
139
|
+
*/
|
|
140
|
+
export function getDebugCounters(): Readonly<typeof _counters> {
|
|
141
|
+
return { ..._counters };
|
|
142
|
+
}
|
|
143
|
+
|
|
134
144
|
/** Record a peak value (only updates if new value is higher). */
|
|
135
145
|
export function debugPeak(counter: keyof typeof _counters, value: number): void {
|
|
136
146
|
if (!_enabled) return;
|
|
@@ -168,6 +178,7 @@ export function writeDebugSummary(): string | null {
|
|
|
168
178
|
avgTtsrCheck_ms,
|
|
169
179
|
ttsrPeakBuffer: _counters.ttsrPeakBuffer,
|
|
170
180
|
renders: _counters.renders,
|
|
181
|
+
gitInvocations: _counters.gitInvocations,
|
|
171
182
|
});
|
|
172
183
|
|
|
173
184
|
return disableDebug();
|
|
@@ -267,12 +267,18 @@ export async function preDispatchHealthGate(basePath: string): Promise<PreDispat
|
|
|
267
267
|
// Non-fatal — dispatch continues without STATE.md if rebuild fails
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
+
// #442 Phase 1.7: resolve repo-ness once per gate. ensureWorkspaceGitReady
|
|
271
|
+
// has already run, and nothing below initializes/deinitializes the repo, so
|
|
272
|
+
// the two downstream blocks (integration-branch check, stale-changes
|
|
273
|
+
// snapshot) can share one nativeIsRepo result instead of querying twice.
|
|
274
|
+
const isRepo = nativeIsRepo(basePath);
|
|
275
|
+
|
|
270
276
|
// ── Integration branch existence check ──
|
|
271
277
|
// If the active milestone's recorded integration branch no longer exists in
|
|
272
278
|
// git, the merge-back at the end of the milestone will fail. Block dispatch
|
|
273
279
|
// now to surface this before work is lost.
|
|
274
280
|
try {
|
|
275
|
-
if (
|
|
281
|
+
if (isRepo) {
|
|
276
282
|
const state = await deriveState(basePath);
|
|
277
283
|
if (state.activeMilestone) {
|
|
278
284
|
const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git ?? {};
|
|
@@ -296,7 +302,7 @@ export async function preDispatchHealthGate(basePath: string): Promise<PreDispat
|
|
|
296
302
|
// If the working tree is dirty and no commit has happened recently,
|
|
297
303
|
// create a safety snapshot so work isn't lost if the next unit crashes.
|
|
298
304
|
try {
|
|
299
|
-
if (
|
|
305
|
+
if (isRepo) {
|
|
300
306
|
const prefs = loadEffectiveGSDPreferences()?.preferences ?? {};
|
|
301
307
|
// `git.snapshots: false` is the canonical toggle that disables WIP
|
|
302
308
|
// snapshot commits — honour it before touching the threshold path (#4420).
|
|
@@ -1387,8 +1387,8 @@ async function buildDiscussPreparationContext(
|
|
|
1387
1387
|
if (parts.length === 0) return "";
|
|
1388
1388
|
|
|
1389
1389
|
const guidance = mode === "milestone"
|
|
1390
|
-
? "Use these findings as background only — they describe what already exists, NOT what the user wants next.
|
|
1391
|
-
: "Use these findings as background context — they describe what already exists, NOT what the user wants to build. Always ask the user what they want to build first.";
|
|
1390
|
+
? "Use these findings as background only — they describe what already exists, NOT what the user wants next. This snapshot already covers code reality: do NOT survey the codebase before asking. Send **one** message: a short recap (at most 2-3 sentences) plus 1-3 focused questions, then **stop**. Do not dump a feature-menu brainstorm or send a second message restating the same question."
|
|
1391
|
+
: "Use these findings as background context — they describe what already exists, NOT what the user wants to build. This snapshot already covers code reality: do NOT survey the codebase before asking. Always ask the user what they want to build first.";
|
|
1392
1392
|
return `\n\n## Preparation Context\n\nThe system analyzed the codebase before this discussion. ${guidance}\n\n${parts.join("\n\n")}`;
|
|
1393
1393
|
} catch (err) {
|
|
1394
1394
|
logWarning("guided", `preparation failed, proceeding without context: ${(err as Error).message}`);
|
|
@@ -9,11 +9,10 @@
|
|
|
9
9
|
// Critical invariant: rendered markdown must round-trip through
|
|
10
10
|
// parseRoadmap(), parsePlan(), parseSummary() in files.ts.
|
|
11
11
|
|
|
12
|
-
import { readFileSync, existsSync, mkdirSync } from "node:fs";
|
|
12
|
+
import { readFileSync, existsSync, mkdirSync, statSync } from "node:fs";
|
|
13
13
|
import { logWarning } from "./workflow-logger.js";
|
|
14
14
|
import { isClosedStatus } from "./status-guards.js";
|
|
15
15
|
import { dirname, join, relative } from "node:path";
|
|
16
|
-
import { createRequire } from "node:module";
|
|
17
16
|
import {
|
|
18
17
|
getAllMilestones,
|
|
19
18
|
getMilestone,
|
|
@@ -38,7 +37,8 @@ import {
|
|
|
38
37
|
buildTaskFileName,
|
|
39
38
|
buildSliceFileName,
|
|
40
39
|
} from "./paths.js";
|
|
41
|
-
import { saveFile, clearParseCache } from "./files.js";
|
|
40
|
+
import { saveFile, clearParseCache, registerCacheClearCallback } from "./files.js";
|
|
41
|
+
import { parseRoadmap, parsePlan } from "./parsers-legacy.js";
|
|
42
42
|
import { invalidateStateCache } from "./state.js";
|
|
43
43
|
import { clearPathCache } from "./paths.js";
|
|
44
44
|
import type { RiskLevel } from "./types.js";
|
|
@@ -738,20 +738,41 @@ export interface StaleEntry {
|
|
|
738
738
|
* Returns a list of stale entries with file path and reason.
|
|
739
739
|
* Logs to stderr when stale files are detected.
|
|
740
740
|
*/
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
741
|
+
// #442 Phase 1.5: cache parsed ROADMAP/PLAN projections by file identity
|
|
742
|
+
// (path + mtimeMs + size) so an unchanged projection skips readFileSync AND
|
|
743
|
+
// the parse entirely on repeated dispatches. The DB-vs-disk comparison below
|
|
744
|
+
// still runs every call against fresh DB rows — only the disk-parse half is
|
|
745
|
+
// memoized, and parsed output depends solely on file bytes, so this is
|
|
746
|
+
// behavior-preserving. Invalidation rides the existing clearParseCache()
|
|
747
|
+
// callback chain (fired by invalidateCaches() after every projection write and
|
|
748
|
+
// by reconcileBeforeDispatch repairs), so a changed file always re-parses.
|
|
749
|
+
interface CachedProjection { mtimeMs: number; size: number; parsed: unknown }
|
|
750
|
+
const _projectionParseCache = new Map<string, CachedProjection>();
|
|
751
|
+
registerCacheClearCallback(() => _projectionParseCache.clear());
|
|
752
|
+
|
|
753
|
+
function parseProjectionByIdentity(path: string, parse: (content: string) => unknown): unknown {
|
|
754
|
+
let st: ReturnType<typeof statSync> | null = null;
|
|
755
|
+
try { st = statSync(path); } catch { st = null; }
|
|
756
|
+
if (st) {
|
|
757
|
+
const hit = _projectionParseCache.get(path);
|
|
758
|
+
if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size) {
|
|
759
|
+
return hit.parsed;
|
|
760
|
+
}
|
|
761
|
+
const parsed = parse(readFileSync(path, "utf-8"));
|
|
762
|
+
_projectionParseCache.set(path, { mtimeMs: st.mtimeMs, size: st.size, parsed });
|
|
763
|
+
return parsed;
|
|
753
764
|
}
|
|
765
|
+
// stat failed (e.g. file vanished between existsSync and here) — fall back to
|
|
766
|
+
// the original plain read+parse so error handling is unchanged.
|
|
767
|
+
return parse(readFileSync(path, "utf-8"));
|
|
768
|
+
}
|
|
754
769
|
|
|
770
|
+
export function detectStaleRenders(basePath: string): StaleEntry[] {
|
|
771
|
+
// parseRoadmap/parsePlan are statically imported (#442 Phase 1.4): the
|
|
772
|
+
// per-call createRequire("./parsers-legacy") that used to live here ran on
|
|
773
|
+
// every dispatch. The static `./parsers-legacy.js` specifier resolves in
|
|
774
|
+
// both packaged (.js) and source (.ts via the strip-types loader) contexts —
|
|
775
|
+
// the same form a dozen other modules already use.
|
|
755
776
|
const stale: StaleEntry[] = [];
|
|
756
777
|
const milestones = getAllMilestones();
|
|
757
778
|
|
|
@@ -762,8 +783,7 @@ export function detectStaleRenders(basePath: string): StaleEntry[] {
|
|
|
762
783
|
const roadmapPath = resolveRoadmapProjectionPath(basePath, milestone.id);
|
|
763
784
|
if (existsSync(roadmapPath)) {
|
|
764
785
|
try {
|
|
765
|
-
const
|
|
766
|
-
const parsed = parseRoadmap(content);
|
|
786
|
+
const parsed = parseProjectionByIdentity(roadmapPath, parseRoadmap) as ReturnType<typeof parseRoadmap>;
|
|
767
787
|
|
|
768
788
|
for (const slice of slices) {
|
|
769
789
|
const isCompleteInDb = isClosedStatus(slice.status);
|
|
@@ -795,8 +815,7 @@ export function detectStaleRenders(basePath: string): StaleEntry[] {
|
|
|
795
815
|
const planPath = resolveSliceFile(basePath, milestone.id, slice.id, "PLAN");
|
|
796
816
|
if (planPath && existsSync(planPath)) {
|
|
797
817
|
try {
|
|
798
|
-
const
|
|
799
|
-
const parsed = parsePlan(content);
|
|
818
|
+
const parsed = parseProjectionByIdentity(planPath, parsePlan) as ReturnType<typeof parsePlan>;
|
|
800
819
|
|
|
801
820
|
for (const task of tasks) {
|
|
802
821
|
const isDoneInDb = isClosedStatus(task.status);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
2
3
|
import { resolve } from "node:path";
|
|
3
4
|
|
|
4
5
|
import type { ClaudeCodeMcpConfig } from "./preferences-types.js";
|
|
@@ -115,6 +116,12 @@ export function discoverMcpServerNames(projectDir: string): string[] {
|
|
|
115
116
|
return discoverMcpServers(projectDir).map((server) => server.name);
|
|
116
117
|
}
|
|
117
118
|
|
|
119
|
+
export function discoverUserMcpServerNames(): string[] {
|
|
120
|
+
const userSettingsPath = resolve(homedir(), ".claude", "settings.json");
|
|
121
|
+
const userSettings = readJsonFile(userSettingsPath, true) as ClaudeSettingsFile | undefined;
|
|
122
|
+
return collectServerEntries(userSettings?.mcpServers).map((s) => s.name);
|
|
123
|
+
}
|
|
124
|
+
|
|
118
125
|
export function computeMcpDisallowedTools(
|
|
119
126
|
modelId: string,
|
|
120
127
|
mcpConfig: ClaudeCodeMcpConfig | undefined,
|
|
@@ -13,6 +13,7 @@ import { GSDError, GSD_GIT_ERROR } from "./errors.js";
|
|
|
13
13
|
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
|
|
14
14
|
import { getErrorMessage } from "./error-utils.js";
|
|
15
15
|
import { isInfrastructureError } from "./auto/infra-errors.js";
|
|
16
|
+
import { debugCount } from "./debug-logger.js";
|
|
16
17
|
|
|
17
18
|
// Issue #453: keep auto-mode bookkeeping on the stable git CLI path unless a
|
|
18
19
|
// caller explicitly opts into the native helper.
|
|
@@ -140,6 +141,8 @@ function loadNative(): typeof nativeModule {
|
|
|
140
141
|
|
|
141
142
|
/** Run a git command via execFileSync. Returns trimmed stdout. */
|
|
142
143
|
function gitExec(basePath: string, args: string[], allowFailure = false): string {
|
|
144
|
+
// Counts git CLI shell-outs only (native libgit2 paths bypass this helper).
|
|
145
|
+
debugCount("gitInvocations");
|
|
143
146
|
try {
|
|
144
147
|
return execFileSync("git", args, {
|
|
145
148
|
cwd: basePath,
|
|
@@ -169,6 +172,8 @@ function execGitFileSyncWithRetry(
|
|
|
169
172
|
args: string[],
|
|
170
173
|
options: Partial<ExecFileSyncOptionsWithStringEncoding>,
|
|
171
174
|
): string {
|
|
175
|
+
// Counts git CLI shell-outs only (native libgit2 paths bypass this helper).
|
|
176
|
+
debugCount("gitInvocations");
|
|
172
177
|
try {
|
|
173
178
|
return execFileSync("git", args, {
|
|
174
179
|
cwd: basePath,
|
|
@@ -180,6 +185,8 @@ function execGitFileSyncWithRetry(
|
|
|
180
185
|
} catch (err) {
|
|
181
186
|
if (!isRetryableGitError(err)) throw err;
|
|
182
187
|
sleepSync(GIT_RETRY_DELAY_MS);
|
|
188
|
+
// Retry is a second physical shell-out — count it too.
|
|
189
|
+
debugCount("gitInvocations");
|
|
183
190
|
return execFileSync("git", args, {
|
|
184
191
|
cwd: basePath,
|
|
185
192
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -192,6 +199,8 @@ function execGitFileSyncWithRetry(
|
|
|
192
199
|
|
|
193
200
|
/** Run a git command via execFileSync. Returns trimmed stdout. */
|
|
194
201
|
function gitFileExec(basePath: string, args: string[], allowFailure = false): string {
|
|
202
|
+
// Counts git CLI shell-outs only (native libgit2 paths bypass this helper).
|
|
203
|
+
debugCount("gitInvocations");
|
|
195
204
|
try {
|
|
196
205
|
return execFileSync("git", args, {
|
|
197
206
|
cwd: basePath,
|
|
@@ -1092,6 +1101,45 @@ export function nativeMergeSquash(basePath: string, branch: string): GitMergeRes
|
|
|
1092
1101
|
}
|
|
1093
1102
|
}
|
|
1094
1103
|
|
|
1104
|
+
/**
|
|
1105
|
+
* Regular (non-squash) merge a branch (stages changes, does NOT commit).
|
|
1106
|
+
* Uses --no-ff to ensure a proper merge commit and --no-commit so the caller
|
|
1107
|
+
* can supply a custom commit message via nativeCommit.
|
|
1108
|
+
* Respects the user's global git merge.ff config; --no-ff is additive here.
|
|
1109
|
+
*/
|
|
1110
|
+
export function nativeMergeRegular(basePath: string, branch: string): GitMergeResult {
|
|
1111
|
+
try {
|
|
1112
|
+
execFileSync("git", ["merge", "--no-ff", "--no-commit", branch], {
|
|
1113
|
+
cwd: basePath,
|
|
1114
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1115
|
+
encoding: "utf-8",
|
|
1116
|
+
env: GIT_NO_PROMPT_ENV,
|
|
1117
|
+
});
|
|
1118
|
+
return { success: true, conflicts: [] };
|
|
1119
|
+
} catch (err: unknown) {
|
|
1120
|
+
const stderr =
|
|
1121
|
+
err instanceof Error ? (err as Error & { stderr?: string }).stderr ?? err.message : String(err);
|
|
1122
|
+
if (
|
|
1123
|
+
stderr.includes("local changes would be overwritten") ||
|
|
1124
|
+
stderr.includes("not possible because you have unmerged files") ||
|
|
1125
|
+
stderr.includes("overwritten by merge")
|
|
1126
|
+
) {
|
|
1127
|
+
const dirtyFiles = stderr
|
|
1128
|
+
.split("\n")
|
|
1129
|
+
.filter((line) => line.startsWith("\t"))
|
|
1130
|
+
.map((line) => line.trim())
|
|
1131
|
+
.filter(Boolean);
|
|
1132
|
+
return { success: false, conflicts: ["__dirty_working_tree__"], dirtyFiles };
|
|
1133
|
+
}
|
|
1134
|
+
const conflictOutput = gitExec(basePath, ["diff", "--name-only", "--diff-filter=U"], true);
|
|
1135
|
+
const conflicts = conflictOutput ? conflictOutput.split("\n").filter(Boolean) : [];
|
|
1136
|
+
if (conflicts.length > 0) {
|
|
1137
|
+
return { success: false, conflicts };
|
|
1138
|
+
}
|
|
1139
|
+
throw err;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1095
1143
|
/**
|
|
1096
1144
|
* Abort an in-progress merge.
|
|
1097
1145
|
* Native: libgit2 reset + cleanup.
|
|
@@ -31,18 +31,17 @@ After reflection is confirmed, choose the approach from actual scope, not a labe
|
|
|
31
31
|
|
|
32
32
|
{{preparationContext}}
|
|
33
33
|
|
|
34
|
-
##
|
|
34
|
+
## Ground the First Question Round
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
1.
|
|
38
|
-
2.
|
|
39
|
-
3. Use web tools only for current external facts.
|
|
36
|
+
Ground your questions in the **Preparation Context above** (codebase snapshot, prior context) plus the user's reflected vision — that is authoritative. **Do not survey the codebase** with `ls`/`find`/`rg`/`scout` before asking; the snapshot already covers code reality. Read a specific file only when a question's answer genuinely hinges on it.
|
|
37
|
+
1. Check mentioned tech with `resolve_library` / `get_library_docs`.
|
|
38
|
+
2. Use web tools only for current external facts.
|
|
40
39
|
|
|
41
|
-
Budget searches across the discussion; prefer docs and one-shot `search_and_read`.
|
|
40
|
+
Budget searches across the discussion; prefer docs and one-shot `search_and_read`. Read on-demand only when an answer exposes a specific gap.
|
|
42
41
|
|
|
43
42
|
## Layered Question Rounds
|
|
44
43
|
|
|
45
|
-
Questions have four layers. At each layer, ask 1-3 open questions per round,
|
|
44
|
+
Questions have four layers. At each layer, ask 1-3 open questions per round, read a specific file on-demand if an answer needs it, and gate before advancing.
|
|
46
45
|
|
|
47
46
|
**Default to open questions.** Use `ask_user_questions` only for 2-3 distinct paths with clear tradeoffs. Use plain text for nuanced design questions.
|
|
48
47
|
|
|
@@ -19,15 +19,13 @@ Discuss milestone {{milestoneId}} ("{{milestoneTitle}}"). Identify real gray are
|
|
|
19
19
|
Before asking, read `.gsd/PROJECT.md` and find `## Project Shape` -> `**Complexity:**`. Use `simple` or `complex`; default to `complex` if missing, stale, or unclear.
|
|
20
20
|
|
|
21
21
|
- `simple`: 1-2 plain-text rounds; skip parallel-research investigation; use `ask_user_questions` only for concrete alternatives.
|
|
22
|
-
- `complex`:
|
|
22
|
+
- `complex`: ground in the preloaded context first, use 3-4-option structured questions, and expect multiple rounds.
|
|
23
23
|
|
|
24
24
|
### Before your first question round
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
- Scout existing code with `rg`, `find`, or `scout`.
|
|
28
|
-
- Check roadmap context above for surrounding scope.
|
|
29
|
-
- Use `resolve_library` / `get_library_docs` for unfamiliar libraries; prefer them over web search.
|
|
26
|
+
Ground your questions in the **preloaded context above** (milestone roadmap/context/research, the decisions register, prior-milestone summaries) plus any Preparation Context snapshot — those are authoritative. **Do not survey the codebase** with `rg`/`find`/`scout` before asking; the preloaded files are marked do-not-re-read. Read a specific file only when a question's answer genuinely hinges on it.
|
|
30
27
|
- Identify the 3-5 behavioral or architectural unknowns that materially change what gets built.
|
|
28
|
+
- Use `resolve_library` / `get_library_docs` for unfamiliar libraries; prefer them over web search.
|
|
31
29
|
|
|
32
30
|
**Web search budget:** Limited per turn, typically 3-5. Prefer docs tools and `search_and_read`; use 2-3 searches in the first pass and save the rest.
|
|
33
31
|
|
|
@@ -43,13 +41,13 @@ Ask **1–3 questions per round**. Target one focus at a time:
|
|
|
43
41
|
|
|
44
42
|
**Never fabricate or simulate user input.** Never generate fake transcript markers like `[User]`, `[Human]`, or `User:`. Ask one question round, then wait for the user's actual response before continuing.
|
|
45
43
|
|
|
46
|
-
**Single user-facing message (NON-BYPASSABLE):** After
|
|
44
|
+
**Single user-facing message (NON-BYPASSABLE):** After grounding in the preloaded context, send **one** assistant message that contains your questions (and at most a short recap — 2-3 sentences, not a feature catalog). **End the turn there.** Do not send a second message that restates the same ask ("what do you want M00X to be?", "before I can write the context file…", etc.). If you already asked, stop — do not elaborate in a follow-up message in the same turn.
|
|
47
45
|
|
|
48
46
|
**If `{{structuredQuestionsAvailable}}` is `true`:** use `ask_user_questions` exactly once per turn with 1-3 question objects. Keep labels short (3-5 words). In `complex` mode, multi-choice questions MUST offer **3 or 4 concrete, researched options** plus **"Other — let me discuss"**; options must be grounded in the investigation, not placeholders. In `simple` mode, 2 options is fine for binary alternatives. Binary depth-check/wrap-up gates are exempt. If the user chooses "Other — let me discuss" or gives a long freeform answer, switch to plain-text follow-up before resuming structured questions.
|
|
49
47
|
|
|
50
48
|
**If `{{structuredQuestionsAvailable}}` is `false`:** ask questions in plain text. Keep each round to 1–3 focused questions. Wait for answers before asking the next round.
|
|
51
49
|
|
|
52
|
-
After each answer,
|
|
50
|
+
After each answer, resolve only the new unknowns from context (re-read a specific file only if one is needed), then ask the next round.
|
|
53
51
|
|
|
54
52
|
### Round cadence
|
|
55
53
|
|
|
@@ -38,9 +38,7 @@ Persist the verdict through `gsd_summary_save` with `artifact_type: "PROJECT"` i
|
|
|
38
38
|
|
|
39
39
|
### Before deeper rounds
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
- Scout code with `rg`, `find`, or `scout` for greenfield/brownfield and framework signals.
|
|
43
|
-
- Check prior `.planning/` or `.gsd/` artifacts.
|
|
41
|
+
Ground your questions in the **Preparation Context snapshot above** (stack, structure, greenfield/brownfield and framework signals) plus any prior `.planning/` or `.gsd/` artifacts — those are authoritative. **Do not survey the codebase** with `rg`/`find`/`scout` before asking; read a specific file only when a question's answer genuinely hinges on it.
|
|
44
42
|
- Use `resolve_library` / `get_library_docs` for unfamiliar mentioned libraries.
|
|
45
43
|
|
|
46
44
|
**Web search budget:** typically 3-5 per turn. Prefer docs tools; use 2-3 searches first and save the rest.
|
|
@@ -53,13 +51,13 @@ Ask **1–3 questions per round**, one focus at a time: what, who, core value, a
|
|
|
53
51
|
|
|
54
52
|
**Shape-dependent cadence:**
|
|
55
53
|
- **`simple`**: 1-2 plain-text rounds; use `ask_user_questions` only for concrete alternatives; reach the depth checklist quickly.
|
|
56
|
-
- **`complex`**:
|
|
54
|
+
- **`complex`**: ground in the snapshot and prior artifacts, multiple rounds, structured questions when meaningful alternatives exist.
|
|
57
55
|
|
|
58
56
|
**If `{{structuredQuestionsAvailable}}` is `true` and you use `ask_user_questions`:** ask 1-3 questions per call. Every question needs stable lowercase `id`. Keep labels short (3-5 words). In **`complex`** mode, multi-choice questions MUST offer **3 or 4 concrete, researched options** plus **"Other — let me discuss"**; options must be grounded in the investigation, not placeholders. In **`simple`** mode, 2 options is fine. Binary depth-check/wrap-up gates are exempt. Wait for each tool result before the next round.
|
|
59
57
|
|
|
60
58
|
**If `{{structuredQuestionsAvailable}}` is `false`:** ask questions in plain text. Keep each round to 1–3 focused questions.
|
|
61
59
|
|
|
62
|
-
After each round,
|
|
60
|
+
After each round, resolve only new unknowns from context (read a specific file only if one is needed), then ask the next round.
|
|
63
61
|
|
|
64
62
|
### Round cadence
|
|
65
63
|
|
|
@@ -34,8 +34,7 @@ Before your first action, print this banner verbatim in chat:
|
|
|
34
34
|
|
|
35
35
|
### Before your first question round
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
- Scout existing capabilities; already-built work is `Validated` or `Active`.
|
|
37
|
+
Ground your requirements in **PROJECT.md, any existing REQUIREMENTS.md, and the Preparation Context snapshot** — those are authoritative for what already exists (already-built work is `Validated` or `Active`). **Do not survey the codebase**; read a specific file only when a requirement's scope genuinely hinges on it.
|
|
39
38
|
- Cross-check milestone sequence; every milestone needs at least one owned Active requirement.
|
|
40
39
|
- Use `resolve_library` / `get_library_docs` for libraries that imply capabilities.
|
|
41
40
|
- Identify domain table-stakes only when PROJECT.md confidence is low.
|
|
@@ -13,12 +13,11 @@ Do **not** center the discussion on tech stack trivia, naming, or speculative ar
|
|
|
13
13
|
Before the first question round, read `.gsd/PROJECT.md` and look for `## Project Shape` → `**Complexity:**`. Verdicts are **`simple`** or **`complex`**; default to `complex` if missing or unclear.
|
|
14
14
|
|
|
15
15
|
- **`simple`** — use 1–2 plain-text rounds, then write context. Skip parallel-research investigation.
|
|
16
|
-
- **`complex`** —
|
|
16
|
+
- **`complex`** — ground in the preloaded context first, then ask structured 3–4-option questions.
|
|
17
17
|
|
|
18
|
-
###
|
|
18
|
+
### Grounding
|
|
19
19
|
|
|
20
|
-
Do
|
|
21
|
-
- Scout touched code with `rg`, `find`, or `scout` for broad unfamiliar areas.
|
|
20
|
+
Ground your questions in the **preloaded slice context above** plus any Preparation Context snapshot — those are authoritative. **Do not survey the codebase** with `rg`/`find`/`scout` before asking; read a specific file only when a question's answer genuinely hinges on it.
|
|
22
21
|
- Check roadmap context for predecessor and dependent work.
|
|
23
22
|
- For unfamiliar libraries, prefer `resolve_library` / `get_library_docs` over `search-the-web`.
|
|
24
23
|
- Identify the 3–5 biggest behavioural unknowns where the user's answer materially changes the build.
|
|
@@ -31,7 +30,7 @@ Do **not** go deep; stop when you can ask grounded questions.
|
|
|
31
30
|
|
|
32
31
|
**Never fabricate or simulate user input.** Never generate fake transcript markers like `[User]`, `[Human]`, or `User:`. Ask one question round, then wait for the user's actual response before continuing.
|
|
33
32
|
|
|
34
|
-
**If `{{structuredQuestionsAvailable}}` is `true`:** Ask **1–3 questions per round** using `ask_user_questions`. In **`complex`** mode, each multi-choice question MUST present **3 or 4 concrete, researched options** plus final **"Other — let me discuss"** option; options must be grounded in the
|
|
33
|
+
**If `{{structuredQuestionsAvailable}}` is `true`:** Ask **1–3 questions per round** using `ask_user_questions`. In **`complex`** mode, each multi-choice question MUST present **3 or 4 concrete, researched options** plus final **"Other — let me discuss"** option; options must be grounded in the preloaded context above (slice context, codebase snapshot, library docs, prior `.gsd/` artifacts), not placeholders. In **`simple`** mode, 2 options is fine. Binary wrap-up gates are exempt. **Call `ask_user_questions` exactly once per turn — never make multiple calls with the same or overlapping questions. Wait for the user's response before asking the next round.**
|
|
35
34
|
**If `{{structuredQuestionsAvailable}}` is `false`:** Ask **1–3 numbered plain-text questions per round**, then wait.
|
|
36
35
|
Focus questions on:
|
|
37
36
|
- **UX and user-facing behaviour** — what users see, click, trigger, or experience.
|
|
@@ -39,7 +38,7 @@ Focus questions on:
|
|
|
39
38
|
- **Scope boundaries** — what is in, out, or deferred.
|
|
40
39
|
- **Feel and experience** — tone, responsiveness, feedback, transitions, and what "done" feels like.
|
|
41
40
|
|
|
42
|
-
After answers,
|
|
41
|
+
After answers, resolve new unknowns from context (read a specific file only if needed), then ask the next round.
|
|
43
42
|
|
|
44
43
|
### Round cadence
|
|
45
44
|
|
|
@@ -38,7 +38,7 @@ If slice research is inlined, trust its architectural findings, but verify every
|
|
|
38
38
|
|
|
39
39
|
1. If requirements are preloaded, identify owned and supporting Active requirements.
|
|
40
40
|
2. Call `memory_query` with keywords from the slice title and source files.
|
|
41
|
-
3.
|
|
41
|
+
3. Use the inlined Output Template sections already present in this prompt. Do not read template files from disk.
|
|
42
42
|
4. {{skillActivation}} Record expected executor skills in each task plan's `skills_used` frontmatter.
|
|
43
43
|
5. Define slice verification before tasks. Non-trivial slices need real tests or executable assertions; boundary contracts need contract-exercising checks. Tests must not read .gitignore/gitignored paths such as `.gsd/`, `.planning/`, or `.audits/`.
|
|
44
44
|
6. Include Threat Surface (Q3), Requirement Impact (Q4), proof level, observability, integration closure, Failure Modes (Q5), Load Profile (Q6), and Negative Tests (Q7) only where applicable.
|
|
@@ -25,12 +25,12 @@ A milestone adding a small feature to an established codebase needs targeted res
|
|
|
25
25
|
Then research the codebase and relevant technologies. Narrate key findings and surprises as you go — what exists, what's missing, what constrains the approach.
|
|
26
26
|
1. {{skillActivation}}
|
|
27
27
|
2. **Skill Discovery ({{skillDiscoveryMode}}):**{{skillDiscoveryInstructions}}
|
|
28
|
-
3.
|
|
28
|
+
3. **Ground in the preloaded context, do not re-survey.** The Codebase Snapshot and Project Classification above describe current code reality and project size — treat them as authoritative. Do NOT open-endedly survey the tree (`rg`/`find`/`scout`) to rediscover what they already cover. Read a specific file with a targeted `read` only when a research question's answer hinges on that file's exact contents. Match research depth to the classified project size: a tiny project needs a short, targeted research note, not a broad survey.
|
|
29
29
|
4. Use `resolve_library` / `get_library_docs` for unfamiliar libraries — skip this for libraries already used in the codebase
|
|
30
30
|
5. **Web search budget:** You have a limited budget of web searches (max ~15 per session). Use them strategically — prefer `resolve_library` / `get_library_docs` for library documentation. Do NOT repeat the same or similar queries. If a search didn't find what you need, rephrase once or move on. Target 3-5 total web searches for a typical research unit.
|
|
31
31
|
6. Use the **Research** output template from the inlined context above — include only sections that have real content
|
|
32
32
|
7. If `.gsd/REQUIREMENTS.md` exists, research against it. Identify which Active requirements are table stakes, likely omissions, overbuilt risks, or domain-standard behaviors the user may or may not want.
|
|
33
|
-
8. Call `gsd_summary_save` with `milestone_id: {{milestoneId}}`, `artifact_type: "RESEARCH"`, and the full research markdown as `content` — the tool computes the file path and persists to both DB and disk.
|
|
33
|
+
8. Call `gsd_summary_save` with `milestone_id: {{milestoneId}}`, `artifact_type: "RESEARCH"`, and the full research markdown as `content` — the tool computes the file path and persists to both DB and disk. Save **incrementally**: once you have substantive findings, save them, then enrich and re-save as you learn more. This makes your research durable — if this unit is interrupted, a re-dispatch resumes from your last saved draft instead of restarting. If a "Resume — Prior Partial Research" block is present above, extend that draft rather than starting over.
|
|
34
34
|
|
|
35
35
|
## Strategic Questions to Answer
|
|
36
36
|
|
|
@@ -24,16 +24,18 @@ Roadmap, slice summaries, assessments, requirements, decisions, and project cont
|
|
|
24
24
|
|
|
25
25
|
### Step 1 - Dispatch Parallel Reviewers
|
|
26
26
|
|
|
27
|
+
The Inlined Context above already preloads the evidence reviewers need — the roadmap, per-slice SUMMARY/ASSESSMENT excerpts, requirements, and verification classes. **Embed the relevant preloaded evidence directly into each reviewer's task prompt** so reviewers work from it instead of re-reading the same artifacts from disk. Each reviewer should read a full file only when its excerpt is missing, truncated, or internally inconsistent — never as a routine first step. This avoids three reviewers independently re-surveying artifacts the orchestrator already holds.
|
|
28
|
+
|
|
27
29
|
Call `subagent` with `tasks: [...]` containing ALL THREE reviewers simultaneously:
|
|
28
30
|
|
|
29
31
|
**Reviewer A - Requirements Coverage**
|
|
30
|
-
Prompt: "Review milestone {{milestoneId}} requirements coverage. Working directory: {{workingDirectory}}.
|
|
32
|
+
Prompt: "Review milestone {{milestoneId}} requirements coverage. Working directory: {{workingDirectory}}. Use the preloaded requirements and slice SUMMARY evidence embedded in this task — do not re-read them from disk. For each requirement, mark COVERED, PARTIAL, or MISSING against that evidence. Read a full SUMMARY under `.gsd/milestones/{{milestoneId}}/slices/` only if its preloaded excerpt is missing, truncated, or inconsistent. Output table: Requirement | Status | Evidence. End with one-line verdict: PASS if all covered, NEEDS-ATTENTION if partials exist, FAIL if any missing."
|
|
31
33
|
|
|
32
34
|
**Reviewer B - Cross-Slice Integration**
|
|
33
|
-
Prompt: "Review milestone {{milestoneId}} cross-slice integration. Working directory: {{workingDirectory}}.
|
|
35
|
+
Prompt: "Review milestone {{milestoneId}} cross-slice integration. Working directory: {{workingDirectory}}. Use the preloaded roadmap and slice SUMMARY evidence embedded in this task — do not re-read them from disk. Find the boundary map (produces/consumes contracts) in the roadmap. For each boundary, confirm producer SUMMARY produced the artifact and consumer SUMMARY consumed it. Read `{{roadmapPath}}` or a full SUMMARY only if the preloaded evidence is missing, truncated, or inconsistent. Output table: Boundary | Producer Summary | Consumer Summary | Status. End with one-line verdict: PASS if all boundaries honored, NEEDS-ATTENTION if any gaps."
|
|
34
36
|
|
|
35
37
|
**Reviewer C - Assessment & Acceptance Criteria**
|
|
36
|
-
Prompt: "Review milestone {{milestoneId}} assessment evidence and acceptance criteria. Working directory: {{workingDirectory}}. Read `.gsd/milestones/{{milestoneId}}/{{milestoneId}}-CONTEXT.md
|
|
38
|
+
Prompt: "Review milestone {{milestoneId}} assessment evidence and acceptance criteria. Working directory: {{workingDirectory}}. Use the preloaded milestone context, slice SUMMARY, and ASSESSMENT evidence embedded in this task — do not re-read them from disk. Read `.gsd/milestones/{{milestoneId}}/{{milestoneId}}-CONTEXT.md`, a full SUMMARY, or a full ASSESSMENT only if the preloaded excerpt is missing, truncated, or inconsistent. UAT files are specs, not evidence. Verify each criterion maps to passing evidence. Then review the inlined `Verification Classes (from planning)` table. For every planned row in that table, output a `Verification Classes` table with columns `Class | Planned Check | Evidence | Verdict`. Preserve every planned non-empty class row; do not summarize, rename, combine, or omit planned classes. The first cell of each row must be exactly `Contract`, `Integration`, `Operational`, or `UAT` when that class is present in planning. If a planned class lacks evidence, still include its canonical row and mark the verdict NEEDS-ATTENTION or FAIL. If a planned browser/UAT class has no ASSESSMENT with browser/runtime actions and assertions, return NEEDS-ATTENTION. If no verification classes were planned, say that explicitly. Output sections `Acceptance Criteria` with checklist `[ ] Criterion | Evidence`, and `Verification Classes` with the table. End with one-line verdict: PASS if all criteria and classes are covered by evidence, NEEDS-ATTENTION if gaps exist."
|
|
37
39
|
|
|
38
40
|
### Step 2 - Synthesize Findings
|
|
39
41
|
|
|
@@ -67,7 +67,12 @@ export interface ParsedRoadmap {
|
|
|
67
67
|
const TEMPLATE_TOKEN_RE = /\{\{[^}]+\}\}/;
|
|
68
68
|
const H2_RE = /^##\s+(.+)$/gm;
|
|
69
69
|
const H3_RE = /^###\s+(.+)$/gm;
|
|
70
|
-
|
|
70
|
+
// A milestone line is single-line by construction. Every inter-token gap uses
|
|
71
|
+
// horizontal-whitespace classes (`[^\S\n]`) rather than `\s`, because `\s`
|
|
72
|
+
// matches newlines: a line missing a valid separator would otherwise let the
|
|
73
|
+
// `\s+(?:—|--|-)\s+` clause "bridge" onto the NEXT bullet's `- `, consuming it
|
|
74
|
+
// as the separator and silently swallowing the following well-formed milestone.
|
|
75
|
+
const MILESTONE_LINE_RE = /^-[^\S\n]+\[([ x])\][^\S\n]+(M\d{3}):[^\S\n]+(.+?)[^\S\n]+(?:—|--|-)[^\S\n]+(.+)$/gm;
|
|
71
76
|
const SLICE_HEADER_RE = /^###\s+(S\d{2})\s*(?:—|--|-)\s+(.+)$/m;
|
|
72
77
|
const REQUIREMENT_HEADER_RE = /^###\s+(R\d{3})\s*(?:—|--|-)\s+(.+)$/m;
|
|
73
78
|
|
|
@@ -335,21 +335,42 @@ function detectDiskSliceIdDivergenceForMilestone(
|
|
|
335
335
|
return drifts;
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
+
type ArtifactDbDrift =
|
|
339
|
+
| DiskSliceIdDivergenceDrift
|
|
340
|
+
| ArtifactDbStatusDivergenceDrift
|
|
341
|
+
| CompletedMilestoneReopenedDrift;
|
|
342
|
+
|
|
343
|
+
// #442 Phase 1.6: the three artifact/DB drift handlers (disk-slice-id,
|
|
344
|
+
// artifact-db-status, completed-milestone-reopened) each call
|
|
345
|
+
// detectArtifactDbDrift and then filter for their own kind — so the full
|
|
346
|
+
// milestone→slice→task walk + artifact SQL + disk scan would run THREE times
|
|
347
|
+
// per detection pass and discard 2/3 of the work. Memoize the result per
|
|
348
|
+
// DriftContext so the three handlers share one computation. The key is the
|
|
349
|
+
// ctx object, which detectAllDrift rebuilds for every pass (and which is
|
|
350
|
+
// unreferenced once the pass ends, so the WeakMap entry is GC'd) — DB/disk
|
|
351
|
+
// state is immutable within a single pass (repairs run only after detection),
|
|
352
|
+
// so this is behavior-preserving. A fresh ctx (e.g. the maintenance command's
|
|
353
|
+
// inline { basePath, state }) always recomputes.
|
|
354
|
+
const _artifactDbDriftMemo = new WeakMap<DriftContext, ArtifactDbDrift[]>();
|
|
355
|
+
|
|
338
356
|
export function detectArtifactDbDrift(
|
|
357
|
+
state: GSDState,
|
|
358
|
+
ctx: DriftContext,
|
|
359
|
+
): ArtifactDbDrift[] {
|
|
360
|
+
const cached = _artifactDbDriftMemo.get(ctx);
|
|
361
|
+
if (cached) return cached;
|
|
362
|
+
const computed = computeArtifactDbDrift(state, ctx);
|
|
363
|
+
_artifactDbDriftMemo.set(ctx, computed);
|
|
364
|
+
return computed;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function computeArtifactDbDrift(
|
|
339
368
|
_state: GSDState,
|
|
340
369
|
ctx: DriftContext,
|
|
341
|
-
):
|
|
342
|
-
| DiskSliceIdDivergenceDrift
|
|
343
|
-
| ArtifactDbStatusDivergenceDrift
|
|
344
|
-
| CompletedMilestoneReopenedDrift
|
|
345
|
-
> {
|
|
370
|
+
): ArtifactDbDrift[] {
|
|
346
371
|
if (!isDbAvailable()) return [];
|
|
347
372
|
|
|
348
|
-
const drifts:
|
|
349
|
-
| DiskSliceIdDivergenceDrift
|
|
350
|
-
| ArtifactDbStatusDivergenceDrift
|
|
351
|
-
| CompletedMilestoneReopenedDrift
|
|
352
|
-
> = [];
|
|
373
|
+
const drifts: ArtifactDbDrift[] = [];
|
|
353
374
|
|
|
354
375
|
for (const milestone of getAllMilestones()) {
|
|
355
376
|
if (isClosedStatus(milestone.status)) continue;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: #442 Phase 1.6 — detectArtifactDbDrift is memoized per
|
|
3
|
+
// DriftContext so the three artifact/DB drift handlers share one
|
|
4
|
+
// milestone→slice→task walk per detection pass instead of recomputing it
|
|
5
|
+
// three times. Asserts external behavior: same ctx returns the same result
|
|
6
|
+
// (shared within a pass); a fresh ctx recomputes identical content (no
|
|
7
|
+
// cross-pass leakage).
|
|
8
|
+
|
|
9
|
+
import test from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
14
|
+
|
|
15
|
+
import { openDatabase, closeDatabase, insertMilestone, insertSlice } from "../gsd-db.ts";
|
|
16
|
+
import { detectArtifactDbDrift } from "../state-reconciliation/drift/artifact-db.ts";
|
|
17
|
+
import type { DriftContext } from "../state-reconciliation/types.ts";
|
|
18
|
+
import type { GSDState } from "../types.ts";
|
|
19
|
+
|
|
20
|
+
function stubState(): GSDState {
|
|
21
|
+
return {
|
|
22
|
+
activeMilestone: { id: "M001", title: "M" },
|
|
23
|
+
activeSlice: null,
|
|
24
|
+
activeTask: null,
|
|
25
|
+
phase: "executing",
|
|
26
|
+
recentDecisions: [],
|
|
27
|
+
blockers: [],
|
|
28
|
+
nextAction: "",
|
|
29
|
+
registry: [],
|
|
30
|
+
requirements: { active: 0, validated: 0, deferred: 0, outOfScope: 0, blocked: 0, total: 0 },
|
|
31
|
+
progress: { milestones: { done: 0, total: 1 } },
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
test("#442: detectArtifactDbDrift is memoized per DriftContext", (t) => {
|
|
36
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-artifact-memo-"));
|
|
37
|
+
const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
38
|
+
mkdirSync(sliceDir, { recursive: true });
|
|
39
|
+
t.after(() => {
|
|
40
|
+
try { closeDatabase(); } catch { /* noop */ }
|
|
41
|
+
rmSync(base, { recursive: true, force: true });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
45
|
+
insertMilestone({ id: "M001", title: "M", status: "active" });
|
|
46
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "pending", risk: "low", depends: [], sequence: 1 });
|
|
47
|
+
// A SUMMARY on disk while the slice is still pending = artifact/DB divergence.
|
|
48
|
+
writeFileSync(join(sliceDir, "S01-SUMMARY.md"), "# S01 Summary\n");
|
|
49
|
+
|
|
50
|
+
const state = stubState();
|
|
51
|
+
|
|
52
|
+
const ctx1: DriftContext = { basePath: base, state };
|
|
53
|
+
const first = detectArtifactDbDrift(state, ctx1);
|
|
54
|
+
const firstAgain = detectArtifactDbDrift(state, ctx1);
|
|
55
|
+
|
|
56
|
+
// Same ctx → cache hit → identical array reference (the 3 handlers in one
|
|
57
|
+
// pass share this exact result).
|
|
58
|
+
assert.strictEqual(firstAgain, first, "same ctx must return the memoized result");
|
|
59
|
+
assert.ok(first.length > 0, "fixture should produce at least one drift record");
|
|
60
|
+
|
|
61
|
+
// Fresh ctx → recomputed (distinct instance) but identical content.
|
|
62
|
+
const ctx2: DriftContext = { basePath: base, state };
|
|
63
|
+
const second = detectArtifactDbDrift(state, ctx2);
|
|
64
|
+
assert.notStrictEqual(second, first, "a fresh ctx must recompute, not reuse the prior pass");
|
|
65
|
+
assert.deepEqual(second, first, "recomputed result must be identical content");
|
|
66
|
+
});
|