gsd-pi 2.82.0-dev.9d5798940 → 2.82.0-dev.dfbc5f58f
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 +2 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +7 -0
- package/dist/resources/extensions/gsd/auto/infra-errors.js +9 -3
- package/dist/resources/extensions/gsd/auto/loop.js +5 -5
- package/dist/resources/extensions/gsd/auto/orchestrator.js +11 -0
- package/dist/resources/extensions/gsd/auto/phases.js +8 -1
- package/dist/resources/extensions/gsd/auto/workflow-memory-pressure.js +12 -0
- package/dist/resources/extensions/gsd/auto-model-selection.js +2 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +1 -1
- package/dist/resources/extensions/gsd/auto-start.js +78 -9
- package/dist/resources/extensions/gsd/auto-worktree.js +15 -1
- package/dist/resources/extensions/gsd/auto.js +30 -3
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +9 -8
- package/dist/resources/extensions/gsd/bootstrap/subagent-input.js +5 -2
- package/dist/resources/extensions/gsd/crash-recovery.js +31 -5
- package/dist/resources/extensions/gsd/db/unit-dispatches.js +1 -0
- package/dist/resources/extensions/gsd/dispatch-guard.js +2 -2
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +28 -11
- package/dist/resources/extensions/gsd/doctor.js +2 -28
- package/dist/resources/extensions/gsd/git-service.js +39 -1
- package/dist/resources/extensions/gsd/gsd-db.js +1 -0
- package/dist/resources/extensions/gsd/guided-flow.js +6 -0
- package/dist/resources/extensions/gsd/migrate/parsers.js +10 -0
- package/dist/resources/extensions/gsd/native-git-bridge.js +40 -9
- package/dist/resources/extensions/gsd/post-execution-checks.js +73 -2
- package/dist/resources/extensions/gsd/pre-execution-checks.js +28 -1
- package/dist/resources/extensions/gsd/prompt-loader.js +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +3 -3
- package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/dist/resources/extensions/gsd/status-guards.js +4 -0
- package/dist/resources/extensions/gsd/templates/plan.md +8 -5
- package/dist/resources/extensions/gsd/templates/task-plan.md +4 -2
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +6 -8
- package/dist/resources/extensions/gsd/tools/complete-slice.js +6 -8
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +7 -1
- package/dist/resources/extensions/gsd/tools/plan-slice.js +88 -14
- package/dist/resources/extensions/gsd/validation.js +23 -1
- package/dist/resources/extensions/gsd/verification-gate.js +68 -7
- package/dist/resources/extensions/gsd/workflow-projections.js +6 -8
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +5 -1
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
- 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/git/route.js +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 +12 -12
- 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/package.json +2 -2
- package/packages/mcp-server/src/workflow-tools.test.ts +1 -1
- package/packages/native/tsconfig.json +2 -1
- package/packages/native/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js +82 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.js +52 -0
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.js.map +1 -0
- package/packages/pi-ai/dist/providers/simple-options.d.ts +2 -4
- package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.js +5 -6
- package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/simple-options.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/simple-options.test.js +50 -0
- package/packages/pi-ai/dist/providers/simple-options.test.js.map +1 -0
- package/packages/pi-ai/src/providers/openai-codex-responses.test.ts +63 -0
- package/packages/pi-ai/src/providers/openai-codex-responses.ts +91 -1
- package/packages/pi-ai/src/providers/simple-options.test.ts +60 -0
- package/packages/pi-ai/src/providers/simple-options.ts +5 -6
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.js +66 -0
- package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session-thinking-level.test.ts +79 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +1 -1
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/src/resources/GSD-WORKFLOW.md +7 -0
- package/src/resources/extensions/gsd/auto/contracts.ts +14 -6
- package/src/resources/extensions/gsd/auto/infra-errors.ts +9 -3
- package/src/resources/extensions/gsd/auto/loop.ts +8 -5
- package/src/resources/extensions/gsd/auto/orchestrator.ts +11 -0
- package/src/resources/extensions/gsd/auto/phases.ts +7 -1
- package/src/resources/extensions/gsd/auto/workflow-memory-pressure.ts +13 -0
- package/src/resources/extensions/gsd/auto-model-selection.ts +2 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +1 -1
- package/src/resources/extensions/gsd/auto-start.ts +85 -6
- package/src/resources/extensions/gsd/auto-worktree.ts +15 -1
- package/src/resources/extensions/gsd/auto.ts +32 -3
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +9 -8
- package/src/resources/extensions/gsd/bootstrap/subagent-input.ts +3 -1
- package/src/resources/extensions/gsd/crash-recovery.ts +30 -4
- package/src/resources/extensions/gsd/db/unit-dispatches.ts +1 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +2 -2
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +25 -13
- package/src/resources/extensions/gsd/doctor.ts +2 -27
- package/src/resources/extensions/gsd/git-service.ts +45 -1
- package/src/resources/extensions/gsd/gsd-db.ts +3 -0
- package/src/resources/extensions/gsd/guided-flow.ts +6 -0
- package/src/resources/extensions/gsd/migrate/parsers.ts +11 -0
- package/src/resources/extensions/gsd/native-git-bridge.ts +46 -9
- package/src/resources/extensions/gsd/post-execution-checks.ts +87 -2
- package/src/resources/extensions/gsd/pre-execution-checks.ts +32 -1
- package/src/resources/extensions/gsd/prompt-loader.ts +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +3 -3
- package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/src/resources/extensions/gsd/status-guards.ts +5 -0
- package/src/resources/extensions/gsd/templates/plan.md +8 -5
- package/src/resources/extensions/gsd/templates/task-plan.md +4 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +80 -1
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +4 -1
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +43 -2
- package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/guided-flow.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/hook-model-resolution.test.ts +5 -0
- package/src/resources/extensions/gsd/tests/infra-error.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/infra-errors-cooldown.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/integration/doctor-runtime.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +103 -1
- package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +24 -1
- package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +63 -2
- package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +121 -1
- package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +200 -1
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +86 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +53 -0
- package/src/resources/extensions/gsd/tests/prompt-loader.test.ts +23 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +31 -1
- package/src/resources/extensions/gsd/tests/stuck-state-via-db.test.ts +26 -2
- package/src/resources/extensions/gsd/tests/summary-render-parity.test.ts +7 -3
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +110 -1
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/workflow-memory-pressure.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-git-pathspec.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +7 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +8 -10
- package/src/resources/extensions/gsd/tools/complete-slice.ts +6 -8
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +5 -1
- package/src/resources/extensions/gsd/tools/plan-slice.ts +96 -12
- package/src/resources/extensions/gsd/types.ts +1 -1
- package/src/resources/extensions/gsd/validation.ts +23 -1
- package/src/resources/extensions/gsd/verification-gate.ts +78 -6
- package/src/resources/extensions/gsd/workflow-projections.ts +6 -8
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +7 -1
- /package/dist/web/standalone/.next/static/{BdZQhe8yKl6bdKLiXVEzh → q0WYuDVbHeFFYbdd-fei2}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{BdZQhe8yKl6bdKLiXVEzh → q0WYuDVbHeFFYbdd-fei2}/_ssgManifest.js +0 -0
|
@@ -7,7 +7,7 @@ import { milestonesDir, gsdRoot, resolveGsdRootFile } from "./paths.js";
|
|
|
7
7
|
import { deriveState, isGhostMilestone, isReusableGhostMilestone } from "./state.js";
|
|
8
8
|
import { saveFile } from "./files.js";
|
|
9
9
|
import { nativeIsRepo, nativeForEachRef, nativeUpdateRef } from "./native-git-bridge.js";
|
|
10
|
-
import { readCrashLock, isLockProcessAlive,
|
|
10
|
+
import { readCrashLock, isLockProcessAlive, clearStaleWorkerLock } from "./crash-recovery.js";
|
|
11
11
|
import { getActiveAutoWorkers } from "./db/auto-workers.js";
|
|
12
12
|
import { normalizeRealPath } from "./paths.js";
|
|
13
13
|
import { ensureGitignore, isGsdGitignored } from "./gitignore.js";
|
|
@@ -56,7 +56,7 @@ export async function checkRuntimeHealth(
|
|
|
56
56
|
});
|
|
57
57
|
|
|
58
58
|
if (shouldFix("stale_crash_lock")) {
|
|
59
|
-
|
|
59
|
+
clearStaleWorkerLock(basePath);
|
|
60
60
|
fixesApplied.push("cleared stale auto-mode worker state");
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -80,17 +80,29 @@ export async function checkRuntimeHealth(
|
|
|
80
80
|
// heartbeat for this project?" — readCrashLock returns null for
|
|
81
81
|
// healthy live workers (it surfaces stale ones only), so we must
|
|
82
82
|
// consult getActiveAutoWorkers directly.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
83
|
+
let lockHolderAlive = false;
|
|
84
|
+
try {
|
|
85
|
+
const projectRoot = normalizeRealPath(basePath);
|
|
86
|
+
for (const worker of getActiveAutoWorkers()) {
|
|
87
|
+
if (worker.project_root_realpath !== projectRoot) continue;
|
|
88
|
+
try {
|
|
89
|
+
if (isLockProcessAlive({
|
|
90
|
+
pid: worker.pid,
|
|
91
|
+
startedAt: worker.started_at,
|
|
92
|
+
unitType: "starting",
|
|
93
|
+
unitId: "bootstrap",
|
|
94
|
+
unitStartedAt: worker.started_at,
|
|
95
|
+
})) {
|
|
96
|
+
lockHolderAlive = true;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
// Ignore malformed worker rows or transient PID probe failures.
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
// If worker lookup fails, continue with the stranded lock diagnosis.
|
|
105
|
+
}
|
|
94
106
|
if (!lockHolderAlive) {
|
|
95
107
|
issues.push({
|
|
96
108
|
severity: "error",
|
|
@@ -16,6 +16,7 @@ import type { RoadmapSliceEntry } from "./types.js";
|
|
|
16
16
|
import { checkGitHealth, checkRuntimeHealth, checkGlobalHealth, checkEngineHealth } from "./doctor-checks.js";
|
|
17
17
|
import { checkEnvironmentHealth } from "./doctor-environment.js";
|
|
18
18
|
import { runProviderChecks } from "./doctor-providers.js";
|
|
19
|
+
import { validateTitle } from "./validation.js";
|
|
19
20
|
|
|
20
21
|
// ── Re-exports ─────────────────────────────────────────────────────────────
|
|
21
22
|
// All public types and functions from extracted modules are re-exported here
|
|
@@ -25,33 +26,7 @@ export { summarizeDoctorIssues, filterDoctorIssues, formatDoctorReport, formatDo
|
|
|
25
26
|
export { runEnvironmentChecks, runFullEnvironmentChecks, formatEnvironmentReport, type EnvironmentCheckResult } from "./doctor-environment.js";
|
|
26
27
|
export { computeProgressScore, computeProgressScoreWithContext, formatProgressLine, formatProgressReport, type ProgressScore, type ProgressLevel } from "./progress-score.js";
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
* Characters that are used as delimiters in GSD state management documents
|
|
30
|
-
* and should not appear in milestone or slice titles.
|
|
31
|
-
*
|
|
32
|
-
* - "\u2014" (em dash, U+2014): used as a display separator in STATE.md and other docs.
|
|
33
|
-
* A title containing "\u2014" makes the separator ambiguous, corrupting state display
|
|
34
|
-
* and confusing the LLM agent that reads and writes these files.
|
|
35
|
-
* - "\u2013" (en dash, U+2013): visually similar to em dash; same ambiguity risk.
|
|
36
|
-
* - "/" (forward slash, U+002F): used as the path separator in unit IDs (M001/S01)
|
|
37
|
-
* and git branch names (gsd/M001/S01). A slash in a title can break path resolution.
|
|
38
|
-
*/
|
|
39
|
-
const TITLE_DELIMITER_RE = /[\u2014\u2013\/]/; // em dash, en dash, forward slash
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Check whether a milestone or slice title contains characters that conflict
|
|
43
|
-
* with GSD's state document delimiter conventions.
|
|
44
|
-
* Returns a human-readable description of the problem, or null if the title is safe.
|
|
45
|
-
*/
|
|
46
|
-
export function validateTitle(title: string): string | null {
|
|
47
|
-
if (TITLE_DELIMITER_RE.test(title)) {
|
|
48
|
-
const found: string[] = [];
|
|
49
|
-
if (/[\u2014\u2013]/.test(title)) found.push("em/en dash (\u2014 or \u2013)");
|
|
50
|
-
if (/\//.test(title)) found.push("forward slash (/)");
|
|
51
|
-
return `title contains ${found.join(" and ")}, which conflict with GSD state document delimiters`;
|
|
52
|
-
}
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
29
|
+
export { validateTitle } from "./validation.js";
|
|
55
30
|
|
|
56
31
|
function validatePreferenceShape(preferences: GSDPreferences): string[] {
|
|
57
32
|
const issues: string[] = [];
|
|
@@ -270,6 +270,30 @@ function isExcludedScopedPath(path: string, exclusions: readonly string[]): bool
|
|
|
270
270
|
return false;
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
+
function submodulePathsFromLsFiles(output: string): Set<string> {
|
|
274
|
+
const submodulePaths = new Set<string>();
|
|
275
|
+
if (!output) return submodulePaths;
|
|
276
|
+
|
|
277
|
+
for (const line of output.split("\n")) {
|
|
278
|
+
const match = line.match(/^160000\s+\S+\s+\d+\t(.+)$/);
|
|
279
|
+
if (!match) continue;
|
|
280
|
+
submodulePaths.add(match[1].replace(/\\/g, "/").replace(/\/+$/, ""));
|
|
281
|
+
}
|
|
282
|
+
return submodulePaths;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function isInsideSubmodule(path: string, submodulePaths: ReadonlySet<string>): boolean {
|
|
286
|
+
const normalizedPath = path.replace(/\\/g, "/");
|
|
287
|
+
if (submodulePaths.has(normalizedPath)) return true;
|
|
288
|
+
|
|
289
|
+
let slashIndex = normalizedPath.lastIndexOf("/");
|
|
290
|
+
while (slashIndex > 0) {
|
|
291
|
+
if (submodulePaths.has(normalizedPath.slice(0, slashIndex))) return true;
|
|
292
|
+
slashIndex = normalizedPath.lastIndexOf("/", slashIndex - 1);
|
|
293
|
+
}
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
|
|
273
297
|
/**
|
|
274
298
|
* Thrown when a slice merge hits code conflicts in non-.gsd files.
|
|
275
299
|
* The working tree is left in a conflicted state (no reset) so the
|
|
@@ -764,6 +788,26 @@ export class GitServiceImpl {
|
|
|
764
788
|
.filter(file => !nativeIsIgnored(this.basePath, file))
|
|
765
789
|
.filter(file => !isExcludedScopedPath(file, allExclusions));
|
|
766
790
|
|
|
791
|
+
const scopedPaths: string[] = [];
|
|
792
|
+
const submodulePaths: string[] = [];
|
|
793
|
+
const repoSubmodules = submodulePathsFromLsFiles(
|
|
794
|
+
runGit(this.basePath, ["ls-files", "--stage"], { allowFailure: true }),
|
|
795
|
+
);
|
|
796
|
+
for (const path of normalized) {
|
|
797
|
+
if (isInsideSubmodule(path, repoSubmodules)) {
|
|
798
|
+
submodulePaths.push(path);
|
|
799
|
+
} else {
|
|
800
|
+
scopedPaths.push(path);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
if (submodulePaths.length > 0) {
|
|
804
|
+
logWarning(
|
|
805
|
+
"engine",
|
|
806
|
+
`scoped stage: dropping ${submodulePaths.length} keyFile(s) inside git submodule(s): ${submodulePaths.join(", ")}`,
|
|
807
|
+
{ file: "git-service.ts" },
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
|
|
767
811
|
// Drop entries that don't exist on disk. The LLM occasionally lists files
|
|
768
812
|
// it intended to write but didn't (or names them with wrong casing/path).
|
|
769
813
|
// Pre-`b304f738b` `git add -A` swallowed these silently; the scoped
|
|
@@ -771,7 +815,7 @@ export class GitServiceImpl {
|
|
|
771
815
|
// the whole commit fail (see #5500). Filter so valid paths still commit.
|
|
772
816
|
const missing: string[] = [];
|
|
773
817
|
const existing: string[] = [];
|
|
774
|
-
for (const path of
|
|
818
|
+
for (const path of scopedPaths) {
|
|
775
819
|
if (existsSync(join(this.basePath, path))) {
|
|
776
820
|
existing.push(path);
|
|
777
821
|
} else {
|
|
@@ -2081,6 +2081,9 @@ export function deleteTask(milestoneId: string, sliceId: string, taskId: string)
|
|
|
2081
2081
|
currentDb!.prepare(
|
|
2082
2082
|
`DELETE FROM verification_evidence WHERE milestone_id = :mid AND slice_id = :sid AND task_id = :tid`,
|
|
2083
2083
|
).run({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
|
|
2084
|
+
currentDb!.prepare(
|
|
2085
|
+
`DELETE FROM quality_gates WHERE milestone_id = :mid AND slice_id = :sid AND task_id = :tid`,
|
|
2086
|
+
).run({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
|
|
2084
2087
|
currentDb!.prepare(
|
|
2085
2088
|
`DELETE FROM tasks WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid`,
|
|
2086
2089
|
).run({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
|
|
@@ -1539,6 +1539,7 @@ export async function showDiscuss(
|
|
|
1539
1539
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
1540
1540
|
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
1541
1541
|
const basePrompt = loadPrompt("guided-discuss-milestone", {
|
|
1542
|
+
workingDirectory: basePath,
|
|
1542
1543
|
milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
1543
1544
|
commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
|
|
1544
1545
|
fastPathInstruction: "",
|
|
@@ -1553,6 +1554,7 @@ export async function showDiscuss(
|
|
|
1553
1554
|
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
1554
1555
|
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: mid, step: false });
|
|
1555
1556
|
await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
1557
|
+
workingDirectory: basePath,
|
|
1556
1558
|
milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
1557
1559
|
commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
|
|
1558
1560
|
fastPathInstruction: "",
|
|
@@ -1822,6 +1824,7 @@ async function dispatchDiscussForMilestone(
|
|
|
1822
1824
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
1823
1825
|
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
1824
1826
|
const basePrompt = loadPrompt("guided-discuss-milestone", {
|
|
1827
|
+
workingDirectory: basePath,
|
|
1825
1828
|
milestoneId: mid,
|
|
1826
1829
|
milestoneTitle,
|
|
1827
1830
|
inlinedTemplates: discussMilestoneTemplates,
|
|
@@ -2356,6 +2359,7 @@ export async function showSmartEntry(
|
|
|
2356
2359
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
2357
2360
|
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
2358
2361
|
const basePrompt = loadPrompt("guided-discuss-milestone", {
|
|
2362
|
+
workingDirectory: basePath,
|
|
2359
2363
|
milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
2360
2364
|
commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
|
|
2361
2365
|
fastPathInstruction: "",
|
|
@@ -2370,6 +2374,7 @@ export async function showSmartEntry(
|
|
|
2370
2374
|
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
2371
2375
|
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
|
|
2372
2376
|
await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
2377
|
+
workingDirectory: basePath,
|
|
2373
2378
|
milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
2374
2379
|
commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
|
|
2375
2380
|
fastPathInstruction: "",
|
|
@@ -2462,6 +2467,7 @@ export async function showSmartEntry(
|
|
|
2462
2467
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
2463
2468
|
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
2464
2469
|
await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
2470
|
+
workingDirectory: basePath,
|
|
2465
2471
|
milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
2466
2472
|
commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
|
|
2467
2473
|
fastPathInstruction: "",
|
|
@@ -95,6 +95,17 @@ function parsePhaseEntry(line: string): PlanningRoadmapEntry | null {
|
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
// Format 3: - ✅ v1.0 MVP — Phases 1-6
|
|
99
|
+
const fmtVersionPhases = stripped.match(/^-\s+([✅🚧])\s+v\d+(?:\.\d+)*\s+(.+?)\s*[—–]\s*Phases?\s+(\d+(?:\.\d+)?)(?:\s*-\s*\d+(?:\.\d+)?)?(?:\s+\(.*\))?\s*$/iu);
|
|
100
|
+
if (fmtVersionPhases) {
|
|
101
|
+
return {
|
|
102
|
+
number: parseFloat(fmtVersionPhases[3]),
|
|
103
|
+
title: fmtVersionPhases[2].trim(),
|
|
104
|
+
done: fmtVersionPhases[1] === '✅',
|
|
105
|
+
raw: line,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
98
109
|
return null;
|
|
99
110
|
}
|
|
100
111
|
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// execSync calls because git2 credential handling is too complex.
|
|
7
7
|
|
|
8
8
|
import { execSync, execFileSync } from "node:child_process";
|
|
9
|
+
import type { ExecFileSyncOptionsWithStringEncoding } from "node:child_process";
|
|
9
10
|
import { existsSync, readFileSync, unlinkSync, rmSync, writeFileSync } from "node:fs";
|
|
10
11
|
import { join } from "node:path";
|
|
11
12
|
import { GSDError, GSD_GIT_ERROR } from "./errors.js";
|
|
@@ -16,6 +17,8 @@ import { isInfrastructureError } from "./auto/infra-errors.js";
|
|
|
16
17
|
// Issue #453: keep auto-mode bookkeeping on the stable git CLI path unless a
|
|
17
18
|
// caller explicitly opts into the native helper.
|
|
18
19
|
const NATIVE_GSD_GIT_ENABLED = process.env.GSD_ENABLE_NATIVE_GSD_GIT === "1";
|
|
20
|
+
const TRANSIENT_GIT_RETRY_CODES = new Set(["ENOBUFS", "EAGAIN"]);
|
|
21
|
+
const GIT_RETRY_DELAY_MS = 200;
|
|
19
22
|
|
|
20
23
|
// ─── Native Module Types ──────────────────────────────────────────────────
|
|
21
24
|
|
|
@@ -144,9 +147,46 @@ function gitExec(basePath: string, args: string[], allowFailure = false): string
|
|
|
144
147
|
encoding: "utf-8",
|
|
145
148
|
env: GIT_NO_PROMPT_ENV,
|
|
146
149
|
}).trim();
|
|
147
|
-
} catch {
|
|
150
|
+
} catch (err) {
|
|
148
151
|
if (allowFailure) return "";
|
|
149
|
-
throw new GSDError(GSD_GIT_ERROR, `git ${args.join(" ")} failed in ${basePath}`);
|
|
152
|
+
throw new GSDError(GSD_GIT_ERROR, `git ${args.join(" ")} failed in ${basePath}: ${getErrorMessage(err)}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** sleepSync uses Atomics.wait for a blocking pause without busy-waiting; it blocks the current thread and requires Atomics.wait support. */
|
|
157
|
+
function sleepSync(ms: number): void {
|
|
158
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function isRetryableGitError(err: unknown): boolean {
|
|
162
|
+
const code = isInfrastructureError(err)
|
|
163
|
+
?? isInfrastructureError((err as { stderr?: string })?.stderr ?? "");
|
|
164
|
+
return code !== null && TRANSIENT_GIT_RETRY_CODES.has(code);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function execGitFileSyncWithRetry(
|
|
168
|
+
basePath: string,
|
|
169
|
+
args: string[],
|
|
170
|
+
options: Partial<ExecFileSyncOptionsWithStringEncoding>,
|
|
171
|
+
): string {
|
|
172
|
+
try {
|
|
173
|
+
return execFileSync("git", args, {
|
|
174
|
+
cwd: basePath,
|
|
175
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
176
|
+
encoding: "utf-8",
|
|
177
|
+
env: GIT_NO_PROMPT_ENV,
|
|
178
|
+
...options,
|
|
179
|
+
}).trim();
|
|
180
|
+
} catch (err) {
|
|
181
|
+
if (!isRetryableGitError(err)) throw err;
|
|
182
|
+
sleepSync(GIT_RETRY_DELAY_MS);
|
|
183
|
+
return execFileSync("git", args, {
|
|
184
|
+
cwd: basePath,
|
|
185
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
186
|
+
encoding: "utf-8",
|
|
187
|
+
env: GIT_NO_PROMPT_ENV,
|
|
188
|
+
...options,
|
|
189
|
+
}).trim();
|
|
150
190
|
}
|
|
151
191
|
}
|
|
152
192
|
|
|
@@ -159,9 +199,9 @@ function gitFileExec(basePath: string, args: string[], allowFailure = false): st
|
|
|
159
199
|
encoding: "utf-8",
|
|
160
200
|
env: GIT_NO_PROMPT_ENV,
|
|
161
201
|
}).trim();
|
|
162
|
-
} catch {
|
|
202
|
+
} catch (err) {
|
|
163
203
|
if (allowFailure) return "";
|
|
164
|
-
throw new GSDError(GSD_GIT_ERROR, `git ${args.join(" ")} failed in ${basePath}`);
|
|
204
|
+
throw new GSDError(GSD_GIT_ERROR, `git ${args.join(" ")} failed in ${basePath}: ${getErrorMessage(err)}`);
|
|
165
205
|
}
|
|
166
206
|
}
|
|
167
207
|
|
|
@@ -940,13 +980,10 @@ export function nativeCommit(
|
|
|
940
980
|
try {
|
|
941
981
|
const args = ["commit", "-F", "-"];
|
|
942
982
|
if (options?.allowEmpty) args.push("--allow-empty");
|
|
943
|
-
const result =
|
|
944
|
-
cwd: basePath,
|
|
983
|
+
const result = execGitFileSyncWithRetry(basePath, args, {
|
|
945
984
|
stdio: ["pipe", "pipe", "pipe"],
|
|
946
|
-
encoding: "utf-8",
|
|
947
|
-
env: GIT_NO_PROMPT_ENV,
|
|
948
985
|
input: message,
|
|
949
|
-
})
|
|
986
|
+
});
|
|
950
987
|
return result;
|
|
951
988
|
} catch (err: unknown) {
|
|
952
989
|
const errObj = err as { stdout?: string; stderr?: string; message?: string };
|
|
@@ -46,6 +46,46 @@ export interface PostExecutionResult {
|
|
|
46
46
|
|
|
47
47
|
// ─── Import Resolution Check ─────────────────────────────────────────────────
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Replace the contents of single- and double-quoted string literals on a single
|
|
51
|
+
* source line with spaces so import patterns do not match text inside strings.
|
|
52
|
+
* Template-literal spans are handled separately via the inTemplateLiteral flag.
|
|
53
|
+
*/
|
|
54
|
+
function stripStringLiterals(line: string): string {
|
|
55
|
+
let result = "";
|
|
56
|
+
let i = 0;
|
|
57
|
+
|
|
58
|
+
while (i < line.length) {
|
|
59
|
+
const ch = line[i];
|
|
60
|
+
|
|
61
|
+
if (ch === '"' || ch === "'") {
|
|
62
|
+
result += ch;
|
|
63
|
+
i++;
|
|
64
|
+
|
|
65
|
+
while (i < line.length) {
|
|
66
|
+
const c = line[i];
|
|
67
|
+
|
|
68
|
+
if (c === "\\" && i + 1 < line.length) {
|
|
69
|
+
result += " ";
|
|
70
|
+
i += 2;
|
|
71
|
+
} else if (c === ch) {
|
|
72
|
+
result += ch;
|
|
73
|
+
i++;
|
|
74
|
+
break;
|
|
75
|
+
} else {
|
|
76
|
+
result += " ";
|
|
77
|
+
i++;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
result += ch;
|
|
82
|
+
i++;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
|
|
49
89
|
/**
|
|
50
90
|
* Extract relative import paths from TypeScript/JavaScript source code.
|
|
51
91
|
* Returns array of { importPath, lineNum } for relative imports.
|
|
@@ -62,14 +102,23 @@ export function extractRelativeImports(
|
|
|
62
102
|
// import './path'
|
|
63
103
|
// require('./path')
|
|
64
104
|
// require("../path")
|
|
65
|
-
const importPattern = /(
|
|
105
|
+
const importPattern = /(?:^|[;{}]\s*)import\s+(?:.*?\s+from\s+)?(['"])(\.\.?\/[^'"]+)\1/g;
|
|
106
|
+
const requirePattern = /require\s*\(\s*(['"])(\.\.?\/[^'"]+)\1/g;
|
|
66
107
|
|
|
67
108
|
// Track if we're inside a block comment
|
|
68
109
|
let inBlockComment = false;
|
|
110
|
+
let inTemplateLiteral = false;
|
|
69
111
|
|
|
70
112
|
for (let i = 0; i < lines.length; i++) {
|
|
71
113
|
const line = lines[i];
|
|
72
114
|
|
|
115
|
+
if (inTemplateLiteral) {
|
|
116
|
+
if ((line.match(/(?<!\\)`/g) ?? []).length % 2 === 1) {
|
|
117
|
+
inTemplateLiteral = false;
|
|
118
|
+
}
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
73
122
|
// Handle block comment boundaries
|
|
74
123
|
if (inBlockComment) {
|
|
75
124
|
if (line.includes("*/")) {
|
|
@@ -101,10 +150,22 @@ export function extractRelativeImports(
|
|
|
101
150
|
|
|
102
151
|
// Reset lastIndex for each line
|
|
103
152
|
importPattern.lastIndex = 0;
|
|
153
|
+
requirePattern.lastIndex = 0;
|
|
154
|
+
|
|
155
|
+
const strippedLine = stripStringLiterals(line);
|
|
104
156
|
|
|
105
157
|
while ((match = importPattern.exec(line)) !== null) {
|
|
158
|
+
const importOffset = match[0].indexOf("import");
|
|
159
|
+
const importStart = match.index + importOffset;
|
|
160
|
+
if (
|
|
161
|
+
strippedLine.slice(importStart, importStart + "import".length) !==
|
|
162
|
+
"import"
|
|
163
|
+
) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
106
167
|
// Check if this match is after a // comment marker on the same line
|
|
107
|
-
const beforeMatch =
|
|
168
|
+
const beforeMatch = strippedLine.substring(0, match.index);
|
|
108
169
|
if (beforeMatch.includes("//")) {
|
|
109
170
|
continue;
|
|
110
171
|
}
|
|
@@ -114,6 +175,30 @@ export function extractRelativeImports(
|
|
|
114
175
|
lineNum: i + 1,
|
|
115
176
|
});
|
|
116
177
|
}
|
|
178
|
+
|
|
179
|
+
while ((match = requirePattern.exec(line)) !== null) {
|
|
180
|
+
if (
|
|
181
|
+
strippedLine.slice(match.index, match.index + "require".length) !==
|
|
182
|
+
"require"
|
|
183
|
+
) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Check if this match is after a // comment marker on the same line
|
|
188
|
+
const beforeMatch = strippedLine.substring(0, match.index);
|
|
189
|
+
if (beforeMatch.includes("//")) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
imports.push({
|
|
194
|
+
importPath: match[2],
|
|
195
|
+
lineNum: i + 1,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if ((strippedLine.match(/(?<!\\)`/g) ?? []).length % 2 === 1) {
|
|
200
|
+
inTemplateLiteral = true;
|
|
201
|
+
}
|
|
117
202
|
}
|
|
118
203
|
|
|
119
204
|
return imports;
|
|
@@ -23,6 +23,7 @@ import { homedir } from "node:os";
|
|
|
23
23
|
import { resolve } from "node:path";
|
|
24
24
|
import type { TaskRow } from "./db-task-slice-rows.js";
|
|
25
25
|
import type { PreExecutionCheckJSON } from "./verification-evidence.ts";
|
|
26
|
+
import { validateVerificationCommand } from "./verification-gate.js";
|
|
26
27
|
|
|
27
28
|
const NPM_COMMAND = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
28
29
|
|
|
@@ -37,6 +38,35 @@ export interface PreExecutionResult {
|
|
|
37
38
|
durationMs: number;
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
export function checkVerificationCommands(tasks: TaskRow[]): PreExecutionCheckJSON[] {
|
|
42
|
+
const results: PreExecutionCheckJSON[] = [];
|
|
43
|
+
|
|
44
|
+
for (const task of tasks) {
|
|
45
|
+
const verify = task.verify.trim();
|
|
46
|
+
if (!verify) continue;
|
|
47
|
+
|
|
48
|
+
const commands = verify
|
|
49
|
+
.split("&&")
|
|
50
|
+
.map((command) => command.trim())
|
|
51
|
+
.filter(Boolean);
|
|
52
|
+
|
|
53
|
+
for (const command of commands) {
|
|
54
|
+
const validation = validateVerificationCommand(command);
|
|
55
|
+
if (!validation.ok) {
|
|
56
|
+
results.push({
|
|
57
|
+
category: "tool",
|
|
58
|
+
target: `${task.id} Verify`,
|
|
59
|
+
passed: false,
|
|
60
|
+
message: `Unsafe or non-runnable Verify command: ${command} (${validation.reason})`,
|
|
61
|
+
blocking: true,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return results;
|
|
68
|
+
}
|
|
69
|
+
|
|
40
70
|
// ─── Package Existence Check ─────────────────────────────────────────────────
|
|
41
71
|
|
|
42
72
|
/**
|
|
@@ -757,8 +787,9 @@ export async function runPreExecutionChecks(
|
|
|
757
787
|
const fileChecks = checkFilePathConsistency(tasks, basePath);
|
|
758
788
|
const orderingChecks = checkTaskOrdering(tasks, basePath);
|
|
759
789
|
const contractChecks = checkInterfaceContracts(tasks, basePath);
|
|
790
|
+
const verificationChecks = checkVerificationCommands(tasks);
|
|
760
791
|
|
|
761
|
-
allChecks.push(...fileChecks, ...orderingChecks, ...contractChecks);
|
|
792
|
+
allChecks.push(...fileChecks, ...orderingChecks, ...contractChecks, ...verificationChecks);
|
|
762
793
|
|
|
763
794
|
// Run async package checks
|
|
764
795
|
const packageChecks = await checkPackageExistence(tasks, basePath);
|
|
@@ -200,7 +200,7 @@ export function loadPrompt(name: string, vars: Record<string, string> = {}): str
|
|
|
200
200
|
if (missing.length > 0) {
|
|
201
201
|
throw new GSDError(
|
|
202
202
|
GSD_PARSE_ERROR,
|
|
203
|
-
`loadPrompt("${name}"): template declares {{${missing.join("}}, {{")}}}
|
|
203
|
+
`loadPrompt("${name}"): template declares {{${missing.join("}}, {{")}}} but no value was provided. ` +
|
|
204
204
|
`This usually means the extension code in memory is older than the template on disk. ` +
|
|
205
205
|
`Restart pi to reload the extension.`,
|
|
206
206
|
);
|
|
@@ -26,7 +26,7 @@ Before planning, validate roadmap assumptions against code and dependency summar
|
|
|
26
26
|
|
|
27
27
|
{{sourceFilePaths}}
|
|
28
28
|
|
|
29
|
-
If slice research is inlined, trust
|
|
29
|
+
If slice research is inlined, trust its architectural findings, but verify every concrete file path you place in task `inputs` or `expectedOutput` against the current tree or prior/same-task outputs. Explore enough code to confirm paths, boundaries, and verification. Executors later get only task plans, slice excerpt, and prior summaries, so put required paths, steps, inputs, and outputs in task plans.
|
|
30
30
|
|
|
31
31
|
{{executorContextConstraints}}
|
|
32
32
|
|
|
@@ -39,8 +39,8 @@ If slice research is inlined, trust it. Explore enough code to confirm paths, bo
|
|
|
39
39
|
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/`.
|
|
40
40
|
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.
|
|
41
41
|
7. Right-size tasks. Simple slices can be one task; split only when context, ownership, or verification boundaries justify it.
|
|
42
|
-
8. Each task needs
|
|
43
|
-
9. Persist with `gsd_plan_slice` using goal
|
|
42
|
+
8. Each task needs the exact `gsd_plan_slice.tasks[]` shape: `taskId`, `title`, `description`, `estimate`, `files`, `verify`, `inputs`, `expectedOutput`, and optional `observabilityImpact`. `description` should contain the Why / Do / Done-when narrative. `files`, `inputs`, and `expectedOutput` must be JSON arrays of strings, even when there is only one path (for example, `"inputs": ["src/index.ts"]`, never `"inputs": "src/index.ts"`). Use paths relative to `{{workingDirectory}}`; do not put absolute paths to the original checkout or any directory outside `{{workingDirectory}}` in `files`, `inputs`, `expectedOutput`, or verification commands. **`expectedOutput` must only list files the task actually creates or overwrites on disk.** Do NOT include files the task merely reads, verifies, or tests — those belong only in `inputs`. If a task is a pure verification or test task that produces no new files, `expectedOutput` may be `[]` or limited to test-result artifacts (e.g. a log or assertion output). A file that does not yet exist on disk and is needed as an `input` must be produced by an earlier task's `expectedOutput` — if no prior task creates it, add a task before this one that does.
|
|
43
|
+
9. Persist with `gsd_plan_slice` using `milestoneId`, `sliceId`, `goal`, optional `successCriteria`/`proofLevel`/`integrationClosure`/`observabilityImpact`, and `tasks`. `gsd_plan_slice` handles task persistence transactionally and renders `{{outputPath}}` plus task plans; do not call `gsd_plan_task`. The DB-backed tool is the canonical write path. Do **not** rely on direct `PLAN.md` writes as the source of truth.
|
|
44
44
|
10. Self-audit before finishing: goal/demo closure, requirement coverage, locked decisions, concrete paths, dependency order, wiring, scope size, proof truthfulness, feature completeness, and quality gates. Quality gates: non-trivial slices/tasks include specific Q3-Q7 coverage where applicable.
|
|
45
45
|
11. If planning creates structural decisions, append them to `.gsd/DECISIONS.md`.
|
|
46
46
|
12. {{commitInstruction}}
|
|
@@ -64,7 +64,7 @@ Then:
|
|
|
64
64
|
2. {{skillActivation}} Record the installed skills you expect executors to use in each task plan's `skills_used` frontmatter.
|
|
65
65
|
3. Define slice-level verification: the objective stopping condition. Plan real test files with real assertions; for simple slices, executable commands are fine.
|
|
66
66
|
4. For non-trivial slices, plan observability / proof level / integration closure, threat surface, and requirement impact. Omit entirely for simple slices.
|
|
67
|
-
5. Decompose the slice into tasks that fit one context window each. Every task must
|
|
67
|
+
5. Decompose the slice into tasks that fit one context window each. Every task passed to `gsd_plan_slice` must use the exact keys `taskId`, `title`, `description`, `estimate`, `files`, `verify`, `inputs`, `expectedOutput`, and optional `observabilityImpact`. Put Why / Do / Done-when detail in `description`. `files`, `inputs`, and `expectedOutput` must be JSON arrays of strings, even for one path (for example, `"expectedOutput": ["src/index.ts"]`, never `"expectedOutput": "src/index.ts"`).
|
|
68
68
|
6. **Persist planning state through `gsd_plan_slice`.** Call it with the full payload. The tool writes to the DB and renders `{{outputPath}}` and `{{slicePath}}/tasks/T##-PLAN.md` automatically. Do NOT rely on direct `PLAN.md` writes.
|
|
69
69
|
7. **Self-audit the plan.** If every task were completed exactly as written, the slice goal/demo should be true. Every must-have maps to a task. Inputs and Expected Output are backtick-wrapped file paths.
|
|
70
70
|
8. If refinement produced structural decisions that diverge from the sketch, append them to `.gsd/DECISIONS.md`.
|
|
@@ -25,3 +25,8 @@ export function isDeferredStatus(status: string): boolean {
|
|
|
25
25
|
export function isInactiveStatus(status: string): boolean {
|
|
26
26
|
return isClosedStatus(status) || isDeferredStatus(status);
|
|
27
27
|
}
|
|
28
|
+
|
|
29
|
+
/** Returns true when a prior milestone should not block dispatch ordering. */
|
|
30
|
+
export function isSkippedForDispatch(status: string): boolean {
|
|
31
|
+
return isClosedStatus(status) || status === "parked" || isDeferredStatus(status);
|
|
32
|
+
}
|
|
@@ -99,11 +99,11 @@
|
|
|
99
99
|
- "Improve UI"
|
|
100
100
|
|
|
101
101
|
Each task should usually include:
|
|
102
|
-
-
|
|
103
|
-
-
|
|
104
|
-
-
|
|
105
|
-
-
|
|
106
|
-
-
|
|
102
|
+
- description: why this task exists, concrete steps, and done-when acceptance
|
|
103
|
+
- files: JSON array of likely touched paths
|
|
104
|
+
- verify: the command, test, or runtime check that proves it worked
|
|
105
|
+
- inputs: JSON array of existing paths or prior task outputs this task consumes
|
|
106
|
+
- expectedOutput: JSON array of paths this task creates or overwrites
|
|
107
107
|
|
|
108
108
|
Keep the checkbox line format exactly:
|
|
109
109
|
- [ ] **T01: Title** `est:30m`
|
|
@@ -131,10 +131,13 @@
|
|
|
131
131
|
|
|
132
132
|
Verify field rules:
|
|
133
133
|
- MUST be a mechanically executable command: `npm test`, `grep -q "pattern" file`, `test -f path`
|
|
134
|
+
- MUST NOT use shell pipes, redirects, semicolons, backticks, command substitution, or output trimming
|
|
134
135
|
- For content/document tasks: verify file existence, section count, YAML validity, or word count
|
|
135
136
|
NOT exact phrasing, specific formulas, or "zero TBD" aspirational criteria
|
|
136
137
|
- If no command can verify the output, write: "Manual review — file exists and is non-empty"
|
|
138
|
+
- BAD: `python3 -m pytest tests/ -q --tb=short 2>&1 | tail -5`
|
|
137
139
|
- BAD: "Sections 3.1 and 3.2 exist with exact formulas. Zero TBD/TODO."
|
|
140
|
+
- GOOD: `python3 -m pytest tests/ -q --tb=short`
|
|
138
141
|
- GOOD: `grep -c "^## " doc.md` returns >= 4 (4+ sections), `! grep -q "TBD\|TODO" doc.md`
|
|
139
142
|
|
|
140
143
|
Integration closure rule:
|
|
@@ -72,7 +72,8 @@ skills_used:
|
|
|
72
72
|
<!-- Every input MUST be a backtick-wrapped file path. These paths are machine-parsed to
|
|
73
73
|
derive task dependencies — vague descriptions without paths break dependency detection.
|
|
74
74
|
For the first task in a slice with no prior task outputs, list the existing source files
|
|
75
|
-
this task reads or modifies.
|
|
75
|
+
this task reads or modifies.
|
|
76
|
+
Tool field: inputs must be an array of strings, e.g. ["src/index.ts"], never a single string. -->
|
|
76
77
|
|
|
77
78
|
- `{{filePath}}` — {{whatThisTaskNeedsFromPriorWork}}
|
|
78
79
|
|
|
@@ -82,6 +83,7 @@ skills_used:
|
|
|
82
83
|
or modifies. These paths are machine-parsed to derive task dependencies.
|
|
83
84
|
This task should produce a real increment toward making the slice goal/demo true. A full
|
|
84
85
|
slice plan should not be able to mark every task complete while the claimed slice behavior
|
|
85
|
-
still does not work at the stated proof level.
|
|
86
|
+
still does not work at the stated proof level.
|
|
87
|
+
Tool field: expectedOutput must be an array of strings, e.g. ["src/index.ts"], never a single string. -->
|
|
86
88
|
|
|
87
89
|
- `{{filePath}}` — {{whatThisTaskCreatesOrModifies}}
|