gsd-pi 2.82.0-dev.3709f22a5 → 2.82.0-dev.57fd453e4
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/extensions/claude-code-cli/stream-adapter.js +1 -1
- package/dist/resources/extensions/gsd/auto/loop.js +14 -1
- package/dist/resources/extensions/gsd/auto/phases.js +53 -29
- package/dist/resources/extensions/gsd/auto/session.js +4 -0
- package/dist/resources/extensions/gsd/auto/workflow-kernel.js +3 -0
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +1 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +12 -18
- package/dist/resources/extensions/gsd/auto-post-unit.js +13 -6
- package/dist/resources/extensions/gsd/auto-recovery.js +40 -13
- package/dist/resources/extensions/gsd/auto-start.js +3 -3
- package/dist/resources/extensions/gsd/auto-verification.js +17 -4
- package/dist/resources/extensions/gsd/auto-worktree.js +65 -9
- package/dist/resources/extensions/gsd/auto.js +14 -8
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +6 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +1 -1
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +7 -2
- package/dist/resources/extensions/gsd/crash-recovery.js +16 -4
- package/dist/resources/extensions/gsd/db/milestone-leases.js +24 -0
- package/dist/resources/extensions/gsd/forensics.js +3 -3
- package/dist/resources/extensions/gsd/git-service.js +6 -2
- package/dist/resources/extensions/gsd/gsd-db.js +20 -6
- package/dist/resources/extensions/gsd/guided-flow-queue.js +4 -3
- package/dist/resources/extensions/gsd/guided-flow.js +8 -5
- package/dist/resources/extensions/gsd/markdown-renderer.js +10 -8
- package/dist/resources/extensions/gsd/paths.js +4 -0
- package/dist/resources/extensions/gsd/queue-reorder-ui.js +30 -13
- package/dist/resources/extensions/gsd/state.js +1 -1
- package/dist/resources/extensions/gsd/status-guards.js +7 -0
- package/dist/resources/extensions/gsd/templates/plan.md +1 -0
- package/dist/resources/extensions/gsd/templates/task-plan.md +6 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +3 -5
- package/dist/resources/extensions/gsd/workflow-mcp.js +17 -1
- package/dist/resources/extensions/gsd/worktree-manager.js +1 -1
- package/dist/resources/extensions/ttsr/ttsr-manager.js +3 -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 +14 -14
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-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/browse-directories/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 +14 -14
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-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/.next/static/chunks/8359.65b24fac92188a6b.js +10 -0
- package/dist/web/standalone/.next/static/chunks/9441.ff70bb53f6835771.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-9a4db269f9ed63ad.js → webpack-855d616060cb6e59.js} +1 -1
- package/package.json +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.js +5 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.js +41 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.js.map +1 -0
- package/packages/pi-ai/src/providers/google-gemini-cli.test.ts +49 -0
- package/packages/pi-ai/src/providers/google-gemini-cli.ts +7 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +44 -3
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +71 -97
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.js +12 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +19 -8
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +53 -3
- package/packages/pi-coding-agent/src/core/sdk.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +75 -102
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-ordering.test.ts +14 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +23 -8
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/__tests__/terminal.test.d.ts +2 -0
- package/packages/pi-tui/dist/__tests__/terminal.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/__tests__/terminal.test.js +103 -0
- package/packages/pi-tui/dist/__tests__/terminal.test.js.map +1 -0
- package/packages/pi-tui/dist/terminal.d.ts +2 -0
- package/packages/pi-tui/dist/terminal.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal.js +12 -0
- package/packages/pi-tui/dist/terminal.js.map +1 -1
- package/packages/pi-tui/src/__tests__/terminal.test.ts +121 -0
- package/packages/pi-tui/src/terminal.ts +11 -0
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +1 -1
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +9 -0
- package/src/resources/extensions/gsd/auto/loop.ts +14 -1
- package/src/resources/extensions/gsd/auto/phases.ts +60 -36
- package/src/resources/extensions/gsd/auto/session.ts +4 -0
- package/src/resources/extensions/gsd/auto/workflow-kernel.ts +5 -1
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +1 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +12 -18
- package/src/resources/extensions/gsd/auto-post-unit.ts +14 -6
- package/src/resources/extensions/gsd/auto-recovery.ts +45 -11
- package/src/resources/extensions/gsd/auto-start.ts +2 -3
- package/src/resources/extensions/gsd/auto-verification.ts +22 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +74 -9
- package/src/resources/extensions/gsd/auto.ts +13 -8
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +9 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +1 -1
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +8 -3
- package/src/resources/extensions/gsd/crash-recovery.ts +16 -2
- package/src/resources/extensions/gsd/db/milestone-leases.ts +26 -0
- package/src/resources/extensions/gsd/forensics.ts +3 -3
- package/src/resources/extensions/gsd/git-service.ts +6 -3
- package/src/resources/extensions/gsd/gsd-db.ts +18 -6
- package/src/resources/extensions/gsd/guided-flow-queue.ts +4 -3
- package/src/resources/extensions/gsd/guided-flow.ts +8 -5
- package/src/resources/extensions/gsd/markdown-renderer.ts +10 -8
- package/src/resources/extensions/gsd/paths.ts +5 -0
- package/src/resources/extensions/gsd/queue-reorder-ui.ts +31 -13
- package/src/resources/extensions/gsd/state.ts +1 -1
- package/src/resources/extensions/gsd/status-guards.ts +8 -0
- package/src/resources/extensions/gsd/templates/plan.md +1 -0
- package/src/resources/extensions/gsd/templates/task-plan.md +6 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +139 -1
- package/src/resources/extensions/gsd/tests/auto-post-unit-step-message.test.ts +6 -5
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +76 -5
- package/src/resources/extensions/gsd/tests/checkout-branch-stash-guard.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +11 -0
- package/src/resources/extensions/gsd/tests/gsdroot-worktree-detection.test.ts +5 -2
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +179 -0
- package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +26 -1
- package/src/resources/extensions/gsd/tests/post-unit-state-rebuild.test.ts +84 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +18 -1
- package/src/resources/extensions/gsd/tests/quality-gates.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/queue-reorder-ui.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/run-uat-replay-cap.test.ts +2 -3
- package/src/resources/extensions/gsd/tests/status-guards.test.ts +13 -1
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/validate-milestone-stuck-guard.test.ts +29 -2
- package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +18 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +3 -4
- package/src/resources/extensions/gsd/workflow-mcp.ts +18 -1
- package/src/resources/extensions/gsd/worktree-manager.ts +1 -1
- package/src/resources/extensions/ttsr/ttsr-manager.ts +5 -1
- package/dist/web/standalone/.next/static/chunks/8359.7eb3bb8f8ecf4c01.js +0 -10
- package/dist/web/standalone/.next/static/chunks/9441.1081da1125d1764f.js +0 -1
- /package/dist/web/standalone/.next/static/{kkGf3_VaPFkiDNV_D7Dtl → ky6ieNHfZXB_oHPklwTJb}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{kkGf3_VaPFkiDNV_D7Dtl → ky6ieNHfZXB_oHPklwTJb}/_ssgManifest.js +0 -0
|
@@ -48,6 +48,7 @@ import { getIsolationMode, loadEffectiveGSDPreferences } from "./preferences.js"
|
|
|
48
48
|
import { resolveUokFlags } from "./uok/flags.js";
|
|
49
49
|
import { ensurePlanV2Graph, isMissingFinalizedContextResult } from "./uok/plan-v2.js";
|
|
50
50
|
import { detectProjectState, hasGsdBootstrapArtifacts } from "./detection.js";
|
|
51
|
+
import { isFutureMilestoneStatus } from "./status-guards.js";
|
|
51
52
|
import { showProjectInit, offerMigration } from "./init-wizard.js";
|
|
52
53
|
import { validateDirectory } from "./validate-directory.js";
|
|
53
54
|
import { showConfirm } from "../shared/tui.js";
|
|
@@ -1042,6 +1043,7 @@ async function dispatchWorkflow(
|
|
|
1042
1043
|
? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider)
|
|
1043
1044
|
: undefined,
|
|
1044
1045
|
baseUrl: result.appliedModel?.baseUrl ?? ctx.model?.baseUrl,
|
|
1046
|
+
activeTools: typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [],
|
|
1045
1047
|
},
|
|
1046
1048
|
);
|
|
1047
1049
|
if (compatibilityError) {
|
|
@@ -1471,7 +1473,7 @@ export async function showDiscuss(
|
|
|
1471
1473
|
// No active milestone (or corrupted milestone with undefined id) —
|
|
1472
1474
|
// check for pending milestones to discuss instead
|
|
1473
1475
|
if (!state.activeMilestone?.id) {
|
|
1474
|
-
const pendingMilestones = state.registry.filter(m => m.status
|
|
1476
|
+
const pendingMilestones = state.registry.filter(m => isFutureMilestoneStatus(m.status));
|
|
1475
1477
|
if (pendingMilestones.length === 0) {
|
|
1476
1478
|
ctx.ui.notify("No active milestone. Run /gsd to create one first.", "warning");
|
|
1477
1479
|
return;
|
|
@@ -1583,7 +1585,7 @@ export async function showDiscuss(
|
|
|
1583
1585
|
|
|
1584
1586
|
if (pendingSlices.length === 0) {
|
|
1585
1587
|
// All slices complete — but queued milestones may still need discussion (#3150)
|
|
1586
|
-
const pendingMilestones = state.registry.filter(m => m.status
|
|
1588
|
+
const pendingMilestones = state.registry.filter(m => isFutureMilestoneStatus(m.status));
|
|
1587
1589
|
if (pendingMilestones.length > 0) {
|
|
1588
1590
|
await showDiscussQueuedMilestone(ctx, pi, basePath, pendingMilestones);
|
|
1589
1591
|
return;
|
|
@@ -1607,7 +1609,7 @@ export async function showDiscuss(
|
|
|
1607
1609
|
// If all pending slices are discussed, check for queued milestones before exiting (#3150)
|
|
1608
1610
|
const allDiscussed = pendingSlices.every(s => discussedMap.get(s.id));
|
|
1609
1611
|
if (allDiscussed) {
|
|
1610
|
-
const pendingMilestones = state.registry.filter(m => m.status
|
|
1612
|
+
const pendingMilestones = state.registry.filter(m => isFutureMilestoneStatus(m.status));
|
|
1611
1613
|
if (pendingMilestones.length > 0) {
|
|
1612
1614
|
await showDiscussQueuedMilestone(ctx, pi, basePath, pendingMilestones);
|
|
1613
1615
|
return;
|
|
@@ -1643,7 +1645,7 @@ export async function showDiscuss(
|
|
|
1643
1645
|
});
|
|
1644
1646
|
|
|
1645
1647
|
// Offer access to queued milestones when any exist
|
|
1646
|
-
const pendingMilestones = state.registry.filter(m => m.status
|
|
1648
|
+
const pendingMilestones = state.registry.filter(m => isFutureMilestoneStatus(m.status));
|
|
1647
1649
|
if (pendingMilestones.length > 0) {
|
|
1648
1650
|
actions.push({
|
|
1649
1651
|
id: "discuss_queued_milestone",
|
|
@@ -1718,10 +1720,11 @@ async function showDiscussQueuedMilestone(
|
|
|
1718
1720
|
const hasContext = !!resolveMilestoneFile(basePath, m.id, "CONTEXT");
|
|
1719
1721
|
const hasDraft = !hasContext && !!resolveMilestoneFile(basePath, m.id, "CONTEXT-DRAFT");
|
|
1720
1722
|
const contextStatus = hasContext ? "context ✓" : hasDraft ? "draft context" : "no context yet";
|
|
1723
|
+
const statusLabel = m.status === "planned" ? "planned" : "queued";
|
|
1721
1724
|
return {
|
|
1722
1725
|
id: m.id,
|
|
1723
1726
|
label: `${m.id}: ${m.title}`,
|
|
1724
|
-
description: `[
|
|
1727
|
+
description: `[${statusLabel}] · ${contextStatus}`,
|
|
1725
1728
|
recommended: i === 0,
|
|
1726
1729
|
};
|
|
1727
1730
|
});
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
resolveMilestoneFile,
|
|
33
33
|
resolveSliceFile,
|
|
34
34
|
resolveSlicePath,
|
|
35
|
-
|
|
35
|
+
gsdProjectionRoot,
|
|
36
36
|
gsdRoot,
|
|
37
37
|
buildTaskFileName,
|
|
38
38
|
buildSliceFileName,
|
|
@@ -48,7 +48,11 @@ import { clearPathCache } from "./paths.js";
|
|
|
48
48
|
* E.g. "/project/.gsd/milestones/M001/M001-ROADMAP.md" → "milestones/M001/M001-ROADMAP.md"
|
|
49
49
|
*/
|
|
50
50
|
function toArtifactPath(absPath: string, basePath: string): string {
|
|
51
|
-
const
|
|
51
|
+
const projectionRoot = gsdProjectionRoot(basePath);
|
|
52
|
+
const projectionRel = relative(projectionRoot, absPath);
|
|
53
|
+
const root = projectionRel && !projectionRel.startsWith("..") && !projectionRel.startsWith("/")
|
|
54
|
+
? projectionRoot
|
|
55
|
+
: gsdRoot(basePath);
|
|
52
56
|
const rel = relative(root, absPath);
|
|
53
57
|
// Normalize to forward slashes for consistent DB keys
|
|
54
58
|
return rel.replace(/\\/g, "/");
|
|
@@ -374,10 +378,9 @@ export async function renderPlanFromDb(
|
|
|
374
378
|
throw new Error(`no tasks found for ${milestoneId}/${sliceId}`);
|
|
375
379
|
}
|
|
376
380
|
|
|
377
|
-
const slicePath =
|
|
378
|
-
|
|
379
|
-
const absPath =
|
|
380
|
-
?? join(slicePath, `${sliceId}-PLAN.md`);
|
|
381
|
+
const slicePath = join(gsdProjectionRoot(basePath), "milestones", milestoneId, "slices", sliceId);
|
|
382
|
+
mkdirSync(slicePath, { recursive: true });
|
|
383
|
+
const absPath = join(slicePath, `${sliceId}-PLAN.md`);
|
|
381
384
|
const artifactPath = toArtifactPath(absPath, basePath);
|
|
382
385
|
const sliceGates = getGateResults(milestoneId, sliceId, "slice");
|
|
383
386
|
const content = renderSlicePlanMarkdown(slice, tasks, sliceGates);
|
|
@@ -408,8 +411,7 @@ export async function renderTaskPlanFromDb(
|
|
|
408
411
|
throw new Error(`task ${milestoneId}/${sliceId}/${taskId} not found`);
|
|
409
412
|
}
|
|
410
413
|
|
|
411
|
-
const tasksDir =
|
|
412
|
-
?? join(gsdRoot(basePath), "milestones", milestoneId, "slices", sliceId, "tasks");
|
|
414
|
+
const tasksDir = join(gsdProjectionRoot(basePath), "milestones", milestoneId, "slices", sliceId, "tasks");
|
|
413
415
|
mkdirSync(tasksDir, { recursive: true });
|
|
414
416
|
const absPath = join(tasksDir, buildTaskFileName(taskId, "PLAN"));
|
|
415
417
|
const artifactPath = toArtifactPath(absPath, basePath);
|
|
@@ -357,6 +357,11 @@ export function resolveGsdPathContract(
|
|
|
357
357
|
};
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
+
export function gsdProjectionRoot(basePath: string): string {
|
|
361
|
+
const contract = resolveGsdPathContract(basePath);
|
|
362
|
+
return normalizeRealPath(contract.worktreeGsd ?? contract.projectGsd);
|
|
363
|
+
}
|
|
364
|
+
|
|
360
365
|
/**
|
|
361
366
|
* Invalidate the gsdRoot cache.
|
|
362
367
|
* Use ONLY at session-reset boundaries: workspace switch, process exit, or
|
|
@@ -43,6 +43,7 @@ export async function showQueueReorder(
|
|
|
43
43
|
const items = [...pending];
|
|
44
44
|
let cursor = 0;
|
|
45
45
|
let grabbed = false;
|
|
46
|
+
let scrollOffset = 0;
|
|
46
47
|
let cachedLines: string[] | undefined;
|
|
47
48
|
let validation: DependencyValidation;
|
|
48
49
|
|
|
@@ -154,8 +155,10 @@ export async function showQueueReorder(
|
|
|
154
155
|
|
|
155
156
|
const ui = makeUI(theme, width);
|
|
156
157
|
const lines: string[] = [];
|
|
158
|
+
const queueRows: string[] = [];
|
|
157
159
|
const push = (...rows: string[][]) => { for (const r of rows) lines.push(...r); };
|
|
158
160
|
const add = (s: string) => truncateToWidth(s, width);
|
|
161
|
+
let cursorQueueRow = 0;
|
|
159
162
|
|
|
160
163
|
const headerText = grabbed ? " Queue Reorder — Moving Item" : " Queue Reorder";
|
|
161
164
|
push(ui.bar(), ui.blank(), ui.header(headerText), ui.blank());
|
|
@@ -188,11 +191,13 @@ export async function showQueueReorder(
|
|
|
188
191
|
const label = item.title && item.title !== item.id ? `${item.id} ${item.title}` : item.id;
|
|
189
192
|
|
|
190
193
|
if (isCursor && grabbed) {
|
|
191
|
-
|
|
194
|
+
cursorQueueRow = queueRows.length;
|
|
195
|
+
queueRows.push(add(` ${theme.fg("warning", `▸▸ ${num}. ${label}`)}`));
|
|
192
196
|
} else if (isCursor) {
|
|
193
|
-
|
|
197
|
+
cursorQueueRow = queueRows.length;
|
|
198
|
+
queueRows.push(add(` ${theme.fg("accent", `${GLYPH.cursor} ${num}. ${label}`)}`));
|
|
194
199
|
} else {
|
|
195
|
-
|
|
200
|
+
queueRows.push(add(` ${theme.fg("text", `${num}. ${label}`)}`));
|
|
196
201
|
}
|
|
197
202
|
|
|
198
203
|
// depends_on annotations
|
|
@@ -201,36 +206,37 @@ export async function showQueueReorder(
|
|
|
201
206
|
if (completedIds.has(dep)) continue;
|
|
202
207
|
const pairKey = `${item.id}:${dep}`;
|
|
203
208
|
if (violatedPairs.has(pairKey)) {
|
|
204
|
-
|
|
209
|
+
queueRows.push(add(` ${theme.fg("warning", `${GLYPH.statusWarning} depends_on: ${dep} — auto-removed on confirm`)}`));
|
|
205
210
|
} else if (redundantPairs.has(pairKey)) {
|
|
206
|
-
|
|
211
|
+
queueRows.push(add(` ${theme.fg("dim", `↳ depends_on: ${dep} (redundant)`)}`));
|
|
207
212
|
} else {
|
|
208
|
-
|
|
213
|
+
queueRows.push(add(` ${theme.fg("dim", `↳ depends_on: ${dep}`)}`));
|
|
209
214
|
}
|
|
210
215
|
}
|
|
211
216
|
|
|
212
217
|
// Missing deps
|
|
213
218
|
for (const v of validation.violations.filter(v => v.milestone === item.id && v.type === 'missing_dep')) {
|
|
214
|
-
|
|
219
|
+
queueRows.push(add(` ${theme.fg("error", `${GLYPH.statusWarning} depends_on: ${v.dependsOn} (does not exist)`)}`));
|
|
215
220
|
}
|
|
216
221
|
}
|
|
217
222
|
|
|
218
223
|
// Removed deps feedback
|
|
224
|
+
const trailingLines: string[] = [];
|
|
219
225
|
if (removedDeps.length > 0) {
|
|
220
|
-
push(ui.blank());
|
|
226
|
+
trailingLines.push(...ui.blank());
|
|
221
227
|
for (const r of removedDeps) {
|
|
222
|
-
|
|
228
|
+
trailingLines.push(add(` ${theme.fg("success", `${GLYPH.statusDone} Removed: ${r.milestone} depends_on ${r.dep}`)}`));
|
|
223
229
|
}
|
|
224
230
|
}
|
|
225
231
|
|
|
226
232
|
// Circular warning
|
|
227
233
|
const circ = validation.violations.find(v => v.type === 'circular');
|
|
228
234
|
if (circ) {
|
|
229
|
-
push(ui.blank());
|
|
230
|
-
|
|
235
|
+
trailingLines.push(...ui.blank());
|
|
236
|
+
trailingLines.push(add(` ${theme.fg("error", `${GLYPH.statusWarning} ${circ.message}`)}`));
|
|
231
237
|
}
|
|
232
238
|
|
|
233
|
-
push(ui.blank());
|
|
239
|
+
trailingLines.push(...ui.blank());
|
|
234
240
|
|
|
235
241
|
// Hints — context-sensitive based on grab state
|
|
236
242
|
const hints: string[] = [];
|
|
@@ -250,7 +256,19 @@ export async function showQueueReorder(
|
|
|
250
256
|
}
|
|
251
257
|
hints.push("esc");
|
|
252
258
|
|
|
253
|
-
push(ui.hints(hints), ui.bar());
|
|
259
|
+
trailingLines.push(...ui.hints(hints), ...ui.bar());
|
|
260
|
+
|
|
261
|
+
const maxOverlayRows = Math.max(10, process.stdout.rows ? Math.floor(process.stdout.rows * 0.8) : 24);
|
|
262
|
+
const availableQueueRows = Math.max(1, maxOverlayRows - lines.length - trailingLines.length);
|
|
263
|
+
const maxScroll = Math.max(0, queueRows.length - availableQueueRows);
|
|
264
|
+
if (cursorQueueRow < scrollOffset) {
|
|
265
|
+
scrollOffset = cursorQueueRow;
|
|
266
|
+
} else if (cursorQueueRow >= scrollOffset + availableQueueRows) {
|
|
267
|
+
scrollOffset = cursorQueueRow - availableQueueRows + 1;
|
|
268
|
+
}
|
|
269
|
+
scrollOffset = Math.min(Math.max(scrollOffset, 0), maxScroll);
|
|
270
|
+
|
|
271
|
+
lines.push(...queueRows.slice(scrollOffset, scrollOffset + availableQueueRows), ...trailingLines);
|
|
254
272
|
|
|
255
273
|
cachedLines = lines;
|
|
256
274
|
return lines;
|
|
@@ -1441,7 +1441,7 @@ export async function _deriveStateImpl(
|
|
|
1441
1441
|
const summaryPath = resolveTaskFile(basePath, activeMilestone.id, activeSlice.id, t.id, "SUMMARY");
|
|
1442
1442
|
if (summaryPath && existsSync(summaryPath)) {
|
|
1443
1443
|
t.done = true;
|
|
1444
|
-
logWarning("reconcile", `task ${activeMilestone.id}/${activeSlice.id}/${t.id} reconciled via SUMMARY on disk
|
|
1444
|
+
logWarning("reconcile", `task ${activeMilestone.id}/${activeSlice.id}/${t.id} reconciled via SUMMARY on disk`, { mid: activeMilestone.id, sid: activeSlice.id, tid: t.id });
|
|
1445
1445
|
}
|
|
1446
1446
|
}
|
|
1447
1447
|
|
|
@@ -30,3 +30,11 @@ export function isInactiveStatus(status: string): boolean {
|
|
|
30
30
|
export function isSkippedForDispatch(status: string): boolean {
|
|
31
31
|
return isClosedStatus(status) || status === "parked" || isDeferredStatus(status);
|
|
32
32
|
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Returns true when a milestone is future/backlog work (not currently executing).
|
|
36
|
+
* Includes legacy/project-specific alias "planned" for compatibility.
|
|
37
|
+
*/
|
|
38
|
+
export function isFutureMilestoneStatus(status: string): boolean {
|
|
39
|
+
return status === "pending" || status === "queued" || status === "planned";
|
|
40
|
+
}
|
|
@@ -132,6 +132,7 @@
|
|
|
132
132
|
Verify field rules:
|
|
133
133
|
- MUST be a mechanically executable command: `npm test`, `grep -q "pattern" file`, `test -f path`
|
|
134
134
|
- MUST NOT use shell pipes, redirects, semicolons, backticks, command substitution, or output trimming
|
|
135
|
+
- MUST NOT use inline `node -e` assertions for verification; put assertions in a real test file and run it with `node --test` or a package test script
|
|
135
136
|
- For content/document tasks: verify file existence, section count, YAML validity, or word count
|
|
136
137
|
NOT exact phrasing, specific formulas, or "zero TBD" aspirational criteria
|
|
137
138
|
- If no command can verify the output, write: "Manual review — file exists and is non-empty"
|
|
@@ -57,6 +57,12 @@ skills_used:
|
|
|
57
57
|
- {{howToVerifyThisTaskIsActuallyDone}}
|
|
58
58
|
- {{commandToRun_OR_behaviorToCheck}}
|
|
59
59
|
|
|
60
|
+
## Verify Rules
|
|
61
|
+
|
|
62
|
+
- Use a real executable check, not prose.
|
|
63
|
+
- If the check needs file-content assertions, write a `node:test` file and run it with `node --test` or a package test script.
|
|
64
|
+
- Do not use inline `node -e` assertions for verification.
|
|
65
|
+
|
|
60
66
|
## Observability Impact
|
|
61
67
|
|
|
62
68
|
<!-- OMIT THIS SECTION ENTIRELY for simple tasks that don't touch runtime boundaries,
|
|
@@ -6,7 +6,7 @@ import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "nod
|
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
|
|
9
|
-
import { cleanupAfterLoopExit, rerootCommandSession, stopAuto } from "../auto.ts";
|
|
9
|
+
import { cleanupAfterLoopExit, pauseAuto, rerootCommandSession, stopAuto } from "../auto.ts";
|
|
10
10
|
import { autoSession } from "../auto-runtime-state.ts";
|
|
11
11
|
import { closeDatabase, insertMilestone, insertSlice, openDatabase } from "../gsd-db.ts";
|
|
12
12
|
import { WorktreeLifecycle } from "../worktree-lifecycle.ts";
|
|
@@ -43,6 +43,52 @@ test("cleanupAfterLoopExit preserves paused auto badge after provider pause", as
|
|
|
43
43
|
}
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
+
test("cleanupAfterLoopExit preserves paused worktree session and visible failure output", async (t) => {
|
|
47
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-paused-session-preserve-"));
|
|
48
|
+
const worktree = join(base, ".gsd", "worktrees", "M001");
|
|
49
|
+
const previousCwd = process.cwd();
|
|
50
|
+
const newSessionWorkspaces: string[] = [];
|
|
51
|
+
let restoreCalls = 0;
|
|
52
|
+
|
|
53
|
+
t.mock.method(WorktreeLifecycle.prototype, "restoreToProjectRoot", function () {
|
|
54
|
+
restoreCalls += 1;
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
mkdirSync(worktree, { recursive: true });
|
|
58
|
+
process.chdir(worktree);
|
|
59
|
+
autoSession.reset();
|
|
60
|
+
autoSession.active = true;
|
|
61
|
+
autoSession.paused = true;
|
|
62
|
+
autoSession.basePath = worktree;
|
|
63
|
+
autoSession.originalBasePath = base;
|
|
64
|
+
autoSession.cmdCtx = {
|
|
65
|
+
newSession: async ({ workspaceRoot }: { workspaceRoot: string }) => {
|
|
66
|
+
newSessionWorkspaces.push(workspaceRoot);
|
|
67
|
+
return { cancelled: false };
|
|
68
|
+
},
|
|
69
|
+
} as any;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
await cleanupAfterLoopExit({
|
|
73
|
+
ui: {
|
|
74
|
+
setStatus: () => {},
|
|
75
|
+
setWidget: () => {},
|
|
76
|
+
notify: () => {},
|
|
77
|
+
},
|
|
78
|
+
} as any);
|
|
79
|
+
|
|
80
|
+
assert.equal(restoreCalls, 0, "paused cleanup must not restore out of the active worktree");
|
|
81
|
+
assert.deepEqual(newSessionWorkspaces, [], "paused cleanup must not start a blank rerooted session");
|
|
82
|
+
assert.equal(autoSession.basePath, worktree);
|
|
83
|
+
assert.equal(realpathSync(process.cwd()), realpathSync(worktree));
|
|
84
|
+
assert.equal(autoSession.paused, true);
|
|
85
|
+
} finally {
|
|
86
|
+
autoSession.reset();
|
|
87
|
+
process.chdir(previousCwd);
|
|
88
|
+
rmSync(base, { recursive: true, force: true });
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
46
92
|
test("cleanupAfterLoopExit clears status and progress widget without replacing outcome surface", async () => {
|
|
47
93
|
const statusCalls: unknown[] = [];
|
|
48
94
|
const widgetCalls: unknown[] = [];
|
|
@@ -117,6 +163,98 @@ test("cleanupAfterLoopExit clears progress widget after stopAuto reset", async (
|
|
|
117
163
|
}
|
|
118
164
|
});
|
|
119
165
|
|
|
166
|
+
test("pauseAuto preserves artifact retry counts across pause/resume", async () => {
|
|
167
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-pause-retry-count-"));
|
|
168
|
+
const previousCwd = process.cwd();
|
|
169
|
+
const retryKey = "execute-task:M001/S01/T01";
|
|
170
|
+
|
|
171
|
+
autoSession.reset();
|
|
172
|
+
autoSession.active = true;
|
|
173
|
+
autoSession.verificationRetryCount.set(retryKey, 2);
|
|
174
|
+
autoSession.pendingVerificationRetry = {
|
|
175
|
+
unitId: "M001/S01/T01",
|
|
176
|
+
failureContext: "Missing expected artifact (attempt 2/3).",
|
|
177
|
+
attempt: 2,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
process.chdir(base);
|
|
182
|
+
await pauseAuto();
|
|
183
|
+
|
|
184
|
+
assert.equal(autoSession.paused, true);
|
|
185
|
+
assert.equal(autoSession.pendingVerificationRetry, null);
|
|
186
|
+
assert.equal(autoSession.verificationRetryCount.get(retryKey), 2);
|
|
187
|
+
} finally {
|
|
188
|
+
autoSession.reset();
|
|
189
|
+
process.chdir(previousCwd);
|
|
190
|
+
rmSync(base, { recursive: true, force: true });
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("cleanupAfterLoopExit preserves step-mode surface and worktree session after completed step", async (t) => {
|
|
195
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-step-surface-"));
|
|
196
|
+
const worktree = join(base, ".gsd", "worktrees", "M001");
|
|
197
|
+
const previousCwd = process.cwd();
|
|
198
|
+
const statusCalls: unknown[] = [];
|
|
199
|
+
const widgetCalls: unknown[] = [];
|
|
200
|
+
const newSessionWorkspaces: string[] = [];
|
|
201
|
+
let restoreCalls = 0;
|
|
202
|
+
|
|
203
|
+
t.mock.method(WorktreeLifecycle.prototype, "restoreToProjectRoot", function () {
|
|
204
|
+
restoreCalls += 1;
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
mkdirSync(worktree, { recursive: true });
|
|
208
|
+
process.chdir(worktree);
|
|
209
|
+
autoSession.reset();
|
|
210
|
+
autoSession.active = true;
|
|
211
|
+
autoSession.paused = false;
|
|
212
|
+
autoSession.stepMode = true;
|
|
213
|
+
autoSession.preserveStepSurfaceAfterLoopExit = true;
|
|
214
|
+
autoSession.basePath = worktree;
|
|
215
|
+
autoSession.originalBasePath = base;
|
|
216
|
+
autoSession.cmdCtx = {
|
|
217
|
+
newSession: async ({ workspaceRoot }: { workspaceRoot: string }) => {
|
|
218
|
+
newSessionWorkspaces.push(workspaceRoot);
|
|
219
|
+
return { cancelled: false };
|
|
220
|
+
},
|
|
221
|
+
} as any;
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
await cleanupAfterLoopExit({
|
|
225
|
+
hasUI: true,
|
|
226
|
+
ui: {
|
|
227
|
+
setStatus: (...args: unknown[]) => statusCalls.push(args),
|
|
228
|
+
setWidget: (...args: unknown[]) => widgetCalls.push(args),
|
|
229
|
+
setHeader: () => {},
|
|
230
|
+
notify: () => {},
|
|
231
|
+
},
|
|
232
|
+
} as any);
|
|
233
|
+
|
|
234
|
+
assert.deepEqual(statusCalls, [], "step-mode cleanup must leave the NEXT badge visible");
|
|
235
|
+
assert.equal(
|
|
236
|
+
widgetCalls.some((args) => Array.isArray(args) && args[0] === "gsd-progress" && args[1] === undefined),
|
|
237
|
+
false,
|
|
238
|
+
"step-mode cleanup must not clear the completed step progress surface",
|
|
239
|
+
);
|
|
240
|
+
assert.equal(
|
|
241
|
+
widgetCalls.some((args) => Array.isArray(args) && args[0] === "gsd-health"),
|
|
242
|
+
false,
|
|
243
|
+
"step-mode cleanup must not replace the progress surface with idle health",
|
|
244
|
+
);
|
|
245
|
+
assert.deepEqual(newSessionWorkspaces, [], "step-mode cleanup must not re-root the visible command session");
|
|
246
|
+
assert.equal(restoreCalls, 0, "step-mode cleanup must not restore out of the active worktree");
|
|
247
|
+
assert.equal(autoSession.active, false);
|
|
248
|
+
assert.equal(autoSession.preserveStepSurfaceAfterLoopExit, false);
|
|
249
|
+
assert.equal(autoSession.basePath, worktree);
|
|
250
|
+
assert.equal(realpathSync(process.cwd()), realpathSync(worktree));
|
|
251
|
+
} finally {
|
|
252
|
+
autoSession.reset();
|
|
253
|
+
process.chdir(previousCwd);
|
|
254
|
+
rmSync(base, { recursive: true, force: true });
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
120
258
|
test("cleanupAfterLoopExit restores project root through lifecycle and preserves chdir", async (t) => {
|
|
121
259
|
const base = mkdtempSync(join(tmpdir(), "gsd-cleanup-lifecycle-"));
|
|
122
260
|
const worktree = join(base, ".gsd", "worktrees", "M001");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
// GSD-2
|
|
1
|
+
// Project/App: GSD-2
|
|
2
|
+
// File Purpose: Tests for step-mode completion messages in auto-post-unit.
|
|
2
3
|
|
|
3
4
|
import test from "node:test";
|
|
4
5
|
import assert from "node:assert/strict";
|
|
@@ -31,7 +32,7 @@ test("buildStepCompleteMessage: milestone complete surfaces review guidance", ()
|
|
|
31
32
|
assert.doesNotMatch(msg, /Next:/);
|
|
32
33
|
});
|
|
33
34
|
|
|
34
|
-
test("buildStepCompleteMessage: mid-flight step includes next unit label and /
|
|
35
|
+
test("buildStepCompleteMessage: mid-flight step includes next unit label and /gsd next hint", () => {
|
|
35
36
|
const state = makeState({
|
|
36
37
|
phase: "executing",
|
|
37
38
|
activeSlice: { id: "S01", title: "Core" },
|
|
@@ -40,7 +41,7 @@ test("buildStepCompleteMessage: mid-flight step includes next unit label and /cl
|
|
|
40
41
|
const msg = buildStepCompleteMessage(state);
|
|
41
42
|
assert.match(msg, /Next: Execute T03: Wire notify/);
|
|
42
43
|
assert.match(msg, /\/clear/);
|
|
43
|
-
assert.match(msg, /\/gsd to continue/);
|
|
44
|
+
assert.match(msg, /\/gsd next to continue one step/);
|
|
44
45
|
});
|
|
45
46
|
|
|
46
47
|
test("buildStepCompleteMessage: unknown phase falls back to generic continue label", () => {
|
|
@@ -51,9 +52,9 @@ test("buildStepCompleteMessage: unknown phase falls back to generic continue lab
|
|
|
51
52
|
assert.match(msg, /\/clear/);
|
|
52
53
|
});
|
|
53
54
|
|
|
54
|
-
test("STEP_COMPLETE_FALLBACK_MESSAGE: used when deriveState throws, still points users at /clear + /gsd", () => {
|
|
55
|
+
test("STEP_COMPLETE_FALLBACK_MESSAGE: used when deriveState throws, still points users at /clear + /gsd next", () => {
|
|
55
56
|
assert.match(STEP_COMPLETE_FALLBACK_MESSAGE, /\/clear/);
|
|
56
|
-
assert.match(STEP_COMPLETE_FALLBACK_MESSAGE, /\/gsd/);
|
|
57
|
+
assert.match(STEP_COMPLETE_FALLBACK_MESSAGE, /\/gsd next/);
|
|
57
58
|
});
|
|
58
59
|
|
|
59
60
|
test("shouldReturnStepWizardAfterUnit: terminal milestone completion continues to merge-back path", () => {
|
|
@@ -807,6 +807,51 @@ test("hasImplementationArtifacts finds integration implementation-only commits w
|
|
|
807
807
|
}
|
|
808
808
|
});
|
|
809
809
|
|
|
810
|
+
test("hasImplementationArtifacts ignores corrupted milestone/* integration metadata", () => {
|
|
811
|
+
const base = makeGitBase();
|
|
812
|
+
try {
|
|
813
|
+
mkdirSync(join(base, "src"), { recursive: true });
|
|
814
|
+
writeFileSync(join(base, "src", "feature.ts"), "export function feature() {}\n");
|
|
815
|
+
execFileSync("git", ["add", "src/feature.ts"], { cwd: base, stdio: "ignore" });
|
|
816
|
+
execFileSync("git", ["commit", "-m", "feat: add milestone feature\n\nGSD-Task: S01/T01"], { cwd: base, stdio: "ignore" });
|
|
817
|
+
|
|
818
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
819
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
820
|
+
insertMilestone({ id: "M001", title: "Milestone One", status: "active" });
|
|
821
|
+
insertSlice({
|
|
822
|
+
id: "S01",
|
|
823
|
+
milestoneId: "M001",
|
|
824
|
+
title: "Slice One",
|
|
825
|
+
status: "complete",
|
|
826
|
+
risk: "low",
|
|
827
|
+
depends: [],
|
|
828
|
+
});
|
|
829
|
+
insertTask({
|
|
830
|
+
id: "T01",
|
|
831
|
+
sliceId: "S01",
|
|
832
|
+
milestoneId: "M001",
|
|
833
|
+
title: "Task One",
|
|
834
|
+
status: "complete",
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
execFileSync("git", ["checkout", "-b", "milestone/M001"], { cwd: base, stdio: "ignore" });
|
|
838
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
839
|
+
writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-SUMMARY.md"), "# Milestone Summary\nDone.");
|
|
840
|
+
execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
|
|
841
|
+
execFileSync("git", ["commit", "-m", "chore: auto-commit after complete-milestone\n\nGSD-Unit: M001"], { cwd: base, stdio: "ignore" });
|
|
842
|
+
|
|
843
|
+
writeFileSync(
|
|
844
|
+
join(base, ".gsd", "milestones", "M001", "M001-META.json"),
|
|
845
|
+
JSON.stringify({ integrationBranch: "milestone/M001" }, null, 2) + "\n",
|
|
846
|
+
);
|
|
847
|
+
|
|
848
|
+
const result = hasImplementationArtifacts(base, "M001");
|
|
849
|
+
assert.equal(result, "present", "corrupted milestone integration metadata should fall back to main branch for artifact detection");
|
|
850
|
+
} finally {
|
|
851
|
+
cleanup(base);
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
|
|
810
855
|
test("hasImplementationArtifacts backfills untagged main implementation commits from completed task file hints", () => {
|
|
811
856
|
const base = makeGitBase();
|
|
812
857
|
try {
|
|
@@ -1043,24 +1088,50 @@ test("hasImplementationArtifacts binds GSD-Task trailer to milestone via DB stat
|
|
|
1043
1088
|
}
|
|
1044
1089
|
});
|
|
1045
1090
|
|
|
1046
|
-
test("hasImplementationArtifacts does not
|
|
1091
|
+
test("hasImplementationArtifacts does not claim Sxx/Tyy commit trailers across milestones when ownership points elsewhere", () => {
|
|
1047
1092
|
const base = makeGitBase();
|
|
1048
1093
|
try {
|
|
1049
1094
|
writeFileSync(join(base, ".git", "info", "exclude"), ".gsd/\n");
|
|
1095
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
1096
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
1097
|
+
insertMilestone({ id: "M001", title: "Milestone One", status: "active" });
|
|
1098
|
+
insertMilestone({ id: "M002", title: "Milestone Two", status: "active" });
|
|
1099
|
+
insertSlice({
|
|
1100
|
+
id: "S01",
|
|
1101
|
+
milestoneId: "M002",
|
|
1102
|
+
title: "Slice One",
|
|
1103
|
+
status: "complete",
|
|
1104
|
+
risk: "low",
|
|
1105
|
+
depends: [],
|
|
1106
|
+
});
|
|
1107
|
+
insertTask({
|
|
1108
|
+
id: "T01",
|
|
1109
|
+
sliceId: "S01",
|
|
1110
|
+
milestoneId: "M002",
|
|
1111
|
+
title: "Task One",
|
|
1112
|
+
status: "complete",
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1050
1115
|
mkdirSync(join(base, "src"), { recursive: true });
|
|
1051
1116
|
writeFileSync(join(base, "src", "feature.ts"), "export function feature() {}\n");
|
|
1052
1117
|
execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
|
|
1053
1118
|
execFileSync(
|
|
1054
1119
|
"git",
|
|
1055
|
-
["commit", "-m", "feat: add feature\n\nGSD-Task: S01/T01"],
|
|
1120
|
+
["commit", "-m", "feat: add sibling feature\n\nGSD-Task: S01/T01"],
|
|
1056
1121
|
{ cwd: base, stdio: "ignore" },
|
|
1057
1122
|
);
|
|
1058
1123
|
|
|
1059
|
-
const
|
|
1124
|
+
const m001Result = hasImplementationArtifacts(base, "M001");
|
|
1125
|
+
const m002Result = hasImplementationArtifacts(base, "M002");
|
|
1060
1126
|
assert.equal(
|
|
1061
|
-
|
|
1127
|
+
m001Result,
|
|
1062
1128
|
"absent",
|
|
1063
|
-
"
|
|
1129
|
+
"Sxx/Tyy commit trailers owned by M002 must not be attributed to M001",
|
|
1130
|
+
);
|
|
1131
|
+
assert.equal(
|
|
1132
|
+
m002Result,
|
|
1133
|
+
"present",
|
|
1134
|
+
"the owning milestone should still claim the implementation-bearing commit",
|
|
1064
1135
|
);
|
|
1065
1136
|
} finally {
|
|
1066
1137
|
cleanup(base);
|