@opengsd/gsd-pi 1.1.1-dev.616a1a1 → 1.1.1-dev.74e8dd1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +167 -16
- package/dist/resources/extensions/gsd/auto/phases.js +4 -3
- package/dist/resources/extensions/gsd/auto-dashboard.js +15 -4
- package/dist/resources/extensions/gsd/auto-dispatch.js +39 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +113 -7
- package/dist/resources/extensions/gsd/auto-prompts.js +9 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +4 -4
- package/dist/resources/extensions/gsd/auto-start.js +94 -15
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +2 -1
- package/dist/resources/extensions/gsd/auto.js +22 -4
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +79 -0
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +43 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +30 -9
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +16 -10
- package/dist/resources/extensions/gsd/commands/catalog.js +6 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +6 -2
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +7 -3
- package/dist/resources/extensions/gsd/commands-maintenance.js +172 -2
- package/dist/resources/extensions/gsd/commands-mcp-status.js +107 -59
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +3 -1
- package/dist/resources/extensions/gsd/commands-verdict.js +1 -1
- package/dist/resources/extensions/gsd/config-overlay.js +2 -1
- package/dist/resources/extensions/gsd/error-classifier.js +2 -1
- package/dist/resources/extensions/gsd/exec-sandbox.js +2 -0
- package/dist/resources/extensions/gsd/gsd-db.js +37 -4
- package/dist/resources/extensions/gsd/guided-flow.js +1 -1
- package/dist/resources/extensions/gsd/mcp-filter.js +3 -0
- package/dist/resources/extensions/gsd/mcp-project-config.js +67 -8
- package/dist/resources/extensions/gsd/migration-auto-check.js +2 -2
- package/dist/resources/extensions/gsd/prompts/run-uat.md +10 -4
- package/dist/resources/extensions/gsd/prompts/system.md +3 -1
- package/dist/resources/extensions/gsd/safety/destructive-guard.js +3 -0
- package/dist/resources/extensions/gsd/skill-activation.js +20 -3
- package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +4 -2
- package/dist/resources/extensions/gsd/state-reconciliation/drift/project-md.js +1 -1
- package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +18 -1
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +6 -0
- package/dist/resources/extensions/gsd/state.js +15 -12
- package/dist/resources/extensions/gsd/tool-presentation-plan.js +120 -0
- package/dist/resources/extensions/gsd/tools/exec-tool.js +109 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +14 -9
- package/dist/resources/extensions/gsd/tools/reopen-milestone.js +2 -2
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +366 -3
- package/dist/resources/extensions/gsd/unit-context-manifest.js +8 -3
- package/dist/resources/extensions/gsd/validation-block-guard.js +2 -0
- package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +3 -1
- package/dist/resources/extensions/gsd/workflow-mcp.js +5 -1
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +24 -0
- package/dist/resources/extensions/mcp-client/manager.js +31 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +4 -4
- 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/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 +4 -4
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- 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/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/dist/workflow.d.ts +14 -0
- package/packages/contracts/dist/workflow.d.ts.map +1 -1
- package/packages/contracts/dist/workflow.js +16 -0
- package/packages/contracts/dist/workflow.js.map +1 -1
- 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/settings-selector.d.ts +2 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js +10 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +72 -31
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.js +2 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js +5 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +82 -0
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- 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/image-models.generated.d.ts +15 -0
- package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/image-models.generated.js +15 -0
- package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +338 -17
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +412 -112
- 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/settings-manager.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/dist/terminal.d.ts +1 -0
- package/packages/pi-tui/dist/terminal.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal.js +8 -4
- package/packages/pi-tui/dist/terminal.js.map +1 -1
- package/packages/pi-tui/package.json +1 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +196 -16
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +239 -63
- package/src/resources/extensions/gsd/auto/phases.ts +5 -3
- package/src/resources/extensions/gsd/auto-dashboard.ts +16 -4
- package/src/resources/extensions/gsd/auto-dispatch.ts +48 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +138 -7
- package/src/resources/extensions/gsd/auto-prompts.ts +9 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +4 -4
- package/src/resources/extensions/gsd/auto-start.ts +112 -17
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +2 -1
- package/src/resources/extensions/gsd/auto.ts +35 -3
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +86 -0
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +51 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +51 -14
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +21 -10
- package/src/resources/extensions/gsd/commands/catalog.ts +6 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +6 -2
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +7 -3
- package/src/resources/extensions/gsd/commands-maintenance.ts +197 -2
- package/src/resources/extensions/gsd/commands-mcp-status.ts +134 -57
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +4 -1
- package/src/resources/extensions/gsd/commands-verdict.ts +1 -1
- package/src/resources/extensions/gsd/config-overlay.ts +3 -1
- package/src/resources/extensions/gsd/error-classifier.ts +2 -1
- package/src/resources/extensions/gsd/exec-sandbox.ts +4 -0
- package/src/resources/extensions/gsd/gsd-db.ts +41 -6
- package/src/resources/extensions/gsd/guided-flow.ts +1 -1
- package/src/resources/extensions/gsd/mcp-filter.ts +3 -0
- package/src/resources/extensions/gsd/mcp-project-config.ts +92 -10
- package/src/resources/extensions/gsd/migration-auto-check.ts +2 -2
- package/src/resources/extensions/gsd/preferences-types.ts +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +10 -4
- package/src/resources/extensions/gsd/prompts/system.md +3 -1
- package/src/resources/extensions/gsd/safety/destructive-guard.ts +3 -0
- package/src/resources/extensions/gsd/skill-activation.ts +20 -2
- package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +4 -2
- package/src/resources/extensions/gsd/state-reconciliation/drift/project-md.ts +1 -1
- package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +20 -0
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +6 -0
- package/src/resources/extensions/gsd/state-reconciliation/types.ts +1 -0
- package/src/resources/extensions/gsd/state.ts +16 -12
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +86 -0
- package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +143 -2
- package/src/resources/extensions/gsd/tests/auto-start-project-milestone-reconcile.test.ts +24 -2
- package/src/resources/extensions/gsd/tests/commands-dispatcher-validation-block.test.ts +38 -3
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +6 -2
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +50 -13
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +60 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/exec-tool.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/gsd-rebuild.test.ts +199 -0
- package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +13 -6
- package/src/resources/extensions/gsd/tests/mcp-filter.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +177 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +54 -7
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +39 -1
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +18 -1
- package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +6 -2
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +84 -10
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +12 -2
- package/src/resources/extensions/gsd/tests/tui-header-lifecycle.test.ts +29 -6
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +29 -6
- package/src/resources/extensions/gsd/tests/validation-block-guard.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +17 -2
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +83 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +25 -0
- package/src/resources/extensions/gsd/tool-presentation-plan.ts +167 -0
- package/src/resources/extensions/gsd/tools/exec-tool.ts +130 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +14 -9
- package/src/resources/extensions/gsd/tools/reopen-milestone.ts +2 -2
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +440 -2
- package/src/resources/extensions/gsd/unit-context-manifest.ts +14 -5
- package/src/resources/extensions/gsd/validation-block-guard.ts +2 -0
- package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -1
- package/src/resources/extensions/gsd/workflow-mcp.ts +5 -1
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +26 -0
- package/src/resources/extensions/mcp-client/manager.ts +33 -1
- package/src/resources/extensions/mcp-client/tests/manager.test.ts +35 -0
- /package/dist/web/standalone/.next/static/{L9N5SPFi7f-Ne4u2uXzCe → eRWf-RI9bzbrwEurm_3uI}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{L9N5SPFi7f-Ne4u2uXzCe → eRWf-RI9bzbrwEurm_3uI}/_ssgManifest.js +0 -0
|
@@ -22,23 +22,31 @@ import { tmpdir } from "node:os";
|
|
|
22
22
|
|
|
23
23
|
import { loadSkills } from "@gsd/pi-coding-agent";
|
|
24
24
|
import {
|
|
25
|
-
|
|
25
|
+
buildCompleteSlicePrompt,
|
|
26
26
|
buildParallelResearchSlicesPrompt,
|
|
27
|
+
buildResearchSlicePrompt,
|
|
27
28
|
} from "../auto-prompts.ts";
|
|
28
29
|
|
|
29
30
|
const SKILL_NAME = "testskill";
|
|
31
|
+
const COMPLETE_SLICE_SKILL_NAME = "complete-slice-policies";
|
|
30
32
|
const SKILL_ACTIVATION_SUBSTRING = `Call Skill({ skill: '${SKILL_NAME}' })`;
|
|
33
|
+
const COMPLETE_SLICE_SKILL_ACTIVATION_SUBSTRING = `Call Skill({ skill: '${COMPLETE_SLICE_SKILL_NAME}' })`;
|
|
31
34
|
|
|
32
35
|
const tmpDirs: string[] = [];
|
|
33
36
|
let savedCwd: string | undefined;
|
|
34
37
|
|
|
35
|
-
function setupProjectWithSkill(
|
|
38
|
+
function setupProjectWithSkill(options: {
|
|
39
|
+
skillName?: string;
|
|
40
|
+
preferencesLines?: string[];
|
|
41
|
+
} = {}): string {
|
|
42
|
+
const skillName = options.skillName ?? SKILL_NAME;
|
|
36
43
|
const base = mkdtempSync(join(tmpdir(), "gsd-worker-skill-int-"));
|
|
37
44
|
tmpDirs.push(base);
|
|
38
45
|
|
|
39
46
|
// Milestone roadmap — buildResearchSlicePrompt inlines the roadmap excerpt.
|
|
40
47
|
const milestoneDir = join(base, ".gsd", "milestones", "M001");
|
|
41
|
-
|
|
48
|
+
const sliceOneDir = join(milestoneDir, "slices", "S01");
|
|
49
|
+
mkdirSync(join(sliceOneDir, "tasks"), { recursive: true });
|
|
42
50
|
mkdirSync(join(milestoneDir, "slices", "S02"), { recursive: true });
|
|
43
51
|
writeFileSync(
|
|
44
52
|
join(milestoneDir, "M001-ROADMAP.md"),
|
|
@@ -55,27 +63,41 @@ function setupProjectWithSkill(): string {
|
|
|
55
63
|
].join("\n"),
|
|
56
64
|
"utf-8",
|
|
57
65
|
);
|
|
66
|
+
writeFileSync(
|
|
67
|
+
join(sliceOneDir, "S01-PLAN.md"),
|
|
68
|
+
[
|
|
69
|
+
"# S01: Alpha",
|
|
70
|
+
"",
|
|
71
|
+
"**Goal:** Verify worker x skill prompt plumbing.",
|
|
72
|
+
"**Demo:** Rendered prompts include the skill activation block.",
|
|
73
|
+
"",
|
|
74
|
+
"## Tasks",
|
|
75
|
+
"- [x] **T01: Task** `est:10m`",
|
|
76
|
+
"",
|
|
77
|
+
].join("\n"),
|
|
78
|
+
"utf-8",
|
|
79
|
+
);
|
|
58
80
|
|
|
59
81
|
// Project preferences — buildSkillActivationBlock picks these up via
|
|
60
82
|
// loadEffectiveGSDPreferences(), which reads from `${cwd}/.gsd/PREFERENCES.md`.
|
|
61
83
|
writeFileSync(
|
|
62
84
|
join(base, ".gsd", "PREFERENCES.md"),
|
|
63
|
-
["---", `always_use_skills:`, ` - ${
|
|
85
|
+
["---", ...(options.preferencesLines ?? [`always_use_skills:`, ` - ${skillName}`]), "---", ""].join("\n"),
|
|
64
86
|
"utf-8",
|
|
65
87
|
);
|
|
66
88
|
|
|
67
89
|
// Project-scoped skill — resolveSkillReference scans `${cwd}/.agents/skills/`.
|
|
68
|
-
const skillDir = join(base, ".agents", "skills",
|
|
90
|
+
const skillDir = join(base, ".agents", "skills", skillName);
|
|
69
91
|
mkdirSync(skillDir, { recursive: true });
|
|
70
92
|
writeFileSync(
|
|
71
93
|
join(skillDir, "SKILL.md"),
|
|
72
94
|
[
|
|
73
95
|
"---",
|
|
74
|
-
`name: ${
|
|
96
|
+
`name: ${skillName}`,
|
|
75
97
|
`description: Integration-test skill for worker × skill prompt plumbing.`,
|
|
76
98
|
"---",
|
|
77
99
|
"",
|
|
78
|
-
`# ${
|
|
100
|
+
`# ${skillName}`,
|
|
79
101
|
"",
|
|
80
102
|
"Test skill body.",
|
|
81
103
|
].join("\n"),
|
|
@@ -122,6 +144,31 @@ test("worker prompt (buildResearchSlicePrompt) includes <skill_activation> from
|
|
|
122
144
|
);
|
|
123
145
|
});
|
|
124
146
|
|
|
147
|
+
test("complete-slice prompt includes <skill_activation> from unit-specific skill_rules", async () => {
|
|
148
|
+
const base = setupProjectWithSkill({
|
|
149
|
+
skillName: COMPLETE_SLICE_SKILL_NAME,
|
|
150
|
+
preferencesLines: [
|
|
151
|
+
"skill_rules:",
|
|
152
|
+
" - when: complete-slice",
|
|
153
|
+
" use:",
|
|
154
|
+
` - ${COMPLETE_SLICE_SKILL_NAME}`,
|
|
155
|
+
],
|
|
156
|
+
});
|
|
157
|
+
savedCwd = process.cwd();
|
|
158
|
+
process.chdir(base);
|
|
159
|
+
|
|
160
|
+
const prompt = await buildCompleteSlicePrompt("M001", "Test Milestone", "S01", "Alpha", base, "minimal");
|
|
161
|
+
|
|
162
|
+
assert.ok(
|
|
163
|
+
prompt.includes("<skill_activation>"),
|
|
164
|
+
"complete-slice prompt should contain a <skill_activation> block",
|
|
165
|
+
);
|
|
166
|
+
assert.ok(
|
|
167
|
+
prompt.includes(COMPLETE_SLICE_SKILL_ACTIVATION_SUBSTRING),
|
|
168
|
+
`complete-slice prompt should reference the skill-rule skill '${COMPLETE_SLICE_SKILL_NAME}'`,
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
|
|
125
172
|
test("subagent dispatch prompt (buildParallelResearchSlicesPrompt) carries <skill_activation> into each embedded per-slice section", async () => {
|
|
126
173
|
const base = setupProjectWithSkill();
|
|
127
174
|
savedCwd = process.cwd();
|
|
@@ -6,7 +6,7 @@ import { mkdtempSync, mkdirSync, rmSync, readFileSync, existsSync, writeFileSync
|
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
import { tmpdir } from 'node:os';
|
|
8
8
|
|
|
9
|
-
import { openDatabase, closeDatabase, insertMilestone, insertSlice, getSlice, getSliceTasks, getTask, getGateResults, updateTaskStatus } from '../gsd-db.ts';
|
|
9
|
+
import { openDatabase, closeDatabase, insertMilestone, insertSlice, insertTask, getSlice, getSliceTasks, getTask, getGateResults, updateTaskStatus } from '../gsd-db.ts';
|
|
10
10
|
import { handlePlanSlice } from '../tools/plan-slice.ts';
|
|
11
11
|
import { parsePlan } from '../parsers-legacy.ts';
|
|
12
12
|
import { parseTaskPlanFile } from '../files.ts';
|
|
@@ -265,6 +265,44 @@ test('handlePlanSlice renders plan artifacts under worktree-local .gsd while usi
|
|
|
265
265
|
}
|
|
266
266
|
});
|
|
267
267
|
|
|
268
|
+
test('handlePlanSlice preserves completed task closeout state when replanning the same task', async () => {
|
|
269
|
+
const base = makeTmpBase();
|
|
270
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
seedParentSlice();
|
|
274
|
+
insertTask({
|
|
275
|
+
id: 'T01',
|
|
276
|
+
sliceId: 'S02',
|
|
277
|
+
milestoneId: 'M001',
|
|
278
|
+
title: 'Completed implementation',
|
|
279
|
+
status: 'complete',
|
|
280
|
+
oneLiner: 'Completed implementation',
|
|
281
|
+
narrative: 'Already finished.',
|
|
282
|
+
verificationResult: 'passed',
|
|
283
|
+
fullSummaryMd: '# T01 Summary\n\nAlready finished.\n',
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
const before = getTask('M001', 'S02', 'T01');
|
|
287
|
+
assert.equal(before?.status, 'complete');
|
|
288
|
+
assert.ok(before?.completed_at, 'completed task should have a completion timestamp');
|
|
289
|
+
assert.equal(before?.full_summary_md, '# T01 Summary\n\nAlready finished.\n');
|
|
290
|
+
|
|
291
|
+
const result = await handlePlanSlice(validParams(), base);
|
|
292
|
+
assert.ok(!('error' in result), `unexpected error: ${'error' in result ? result.error : ''}`);
|
|
293
|
+
|
|
294
|
+
const after = getTask('M001', 'S02', 'T01');
|
|
295
|
+
assert.equal(after?.status, 'complete', 'replanning must not reset completed task status to pending');
|
|
296
|
+
assert.equal(after?.completed_at, before?.completed_at, 'replanning must not clear completed_at');
|
|
297
|
+
assert.equal(after?.full_summary_md, before?.full_summary_md, 'replanning must not clear task summary content');
|
|
298
|
+
assert.equal(after?.verification_result, 'passed', 'replanning must not clear closeout verification');
|
|
299
|
+
assert.equal(after?.description, 'Implement the slice planning handler.', 'planning fields should still refresh');
|
|
300
|
+
assert.equal(getTask('M001', 'S02', 'T02')?.status, 'pending', 'new tasks are still inserted as pending');
|
|
301
|
+
} finally {
|
|
302
|
+
cleanup(base);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
268
306
|
test('handlePlanSlice advances DB-derived state out of planning immediately', async () => {
|
|
269
307
|
const base = makeTmpBase();
|
|
270
308
|
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
@@ -32,6 +32,16 @@ test("run-uat prompt branches on dynamic UAT mode and supports runtime evidence"
|
|
|
32
32
|
assert.doesNotMatch(prompt, /uatType:\s*artifact-driven/);
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
+
test("run-uat prompt lists canonical gsd_uat_exec intent values", () => {
|
|
36
|
+
const prompt = readPrompt("run-uat");
|
|
37
|
+
assert.match(prompt, /`uat-artifact-check`/);
|
|
38
|
+
assert.match(prompt, /`uat-runtime-check`/);
|
|
39
|
+
assert.match(prompt, /`uat-browser-check`/);
|
|
40
|
+
assert.match(prompt, /`uat-service-start`/);
|
|
41
|
+
assert.match(prompt, /`uat-log-inspection`/);
|
|
42
|
+
assert.match(prompt, /do not use `artifact`, `runtime`, or `human-follow-up` as `intent`/i);
|
|
43
|
+
});
|
|
44
|
+
|
|
35
45
|
test("workflow-start prompt defaults to autonomy instead of per-phase confirmation", () => {
|
|
36
46
|
const prompt = readPrompt("workflow-start");
|
|
37
47
|
assert.match(prompt, /Keep moving by default/i);
|
|
@@ -17,7 +17,10 @@ import {
|
|
|
17
17
|
shouldDeferTransientErrorToCoreRetry,
|
|
18
18
|
suppressTerminalDeletedWorktreeMessageEnd,
|
|
19
19
|
} from "../bootstrap/agent-end-recovery.ts";
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
_buildCancelledUnitStopReason,
|
|
22
|
+
_classifyZeroToolProviderMessageForTest,
|
|
23
|
+
} from "../auto/phases.ts";
|
|
21
24
|
import { autoSession } from "../auto-runtime-state.ts";
|
|
22
25
|
import { getNextFallbackModel } from "../preferences.ts";
|
|
23
26
|
// Zero-import module — imported by path rather than through the package
|
|
@@ -53,6 +56,20 @@ test("classifyError treats usage-limit phrasing as transient rate-limit (#4373)"
|
|
|
53
56
|
assert.equal(result.kind, "rate-limit");
|
|
54
57
|
});
|
|
55
58
|
|
|
59
|
+
test("zero-tool provider classifier treats Claude session-limit wording as transient rate-limit (#371)", () => {
|
|
60
|
+
const result = _classifyZeroToolProviderMessageForTest("Claude Code session limit reached. Limit resets at 5 PM.");
|
|
61
|
+
assert.ok(result, "session-limit wording should be recognized");
|
|
62
|
+
assert.ok(isTransient(result));
|
|
63
|
+
assert.equal(result.kind, "rate-limit");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("zero-tool provider classifier treats weekly limit wording as transient rate-limit (#371)", () => {
|
|
67
|
+
const result = _classifyZeroToolProviderMessageForTest("You've reached your weekly limit. Try again later.");
|
|
68
|
+
assert.ok(result, "weekly limit wording should be recognized");
|
|
69
|
+
assert.ok(isTransient(result));
|
|
70
|
+
assert.equal(result.kind, "rate-limit");
|
|
71
|
+
});
|
|
72
|
+
|
|
56
73
|
test("classifyError treats extra-usage phrasing as transient rate-limit (#4397)", () => {
|
|
57
74
|
const result = classifyError("You are out of extra usage. Please wait before retrying.");
|
|
58
75
|
assert.ok(isTransient(result));
|
|
@@ -17,6 +17,10 @@ import { validatePreferences } from "../preferences-validation.ts";
|
|
|
17
17
|
import type { ReactiveExecutionState } from "../types.ts";
|
|
18
18
|
import { parseUnitId } from "../unit-id.ts";
|
|
19
19
|
import { resolveDispatch } from "../auto-dispatch.ts";
|
|
20
|
+
import {
|
|
21
|
+
_getPlannedKeyFilesForTest,
|
|
22
|
+
_parseReactiveBatchTaskIdsForTest,
|
|
23
|
+
} from "../auto-post-unit.ts";
|
|
20
24
|
|
|
21
25
|
// ─── Preference Validation ────────────────────────────────────────────────
|
|
22
26
|
|
|
@@ -71,6 +75,38 @@ test("reactive_execution validation warns on unknown keys", () => {
|
|
|
71
75
|
assert.ok(result.warnings.some((w) => w.includes("unknown_thing")));
|
|
72
76
|
});
|
|
73
77
|
|
|
78
|
+
test("reactive batch unit ids are parsed and deduped for commit context", () => {
|
|
79
|
+
assert.deepEqual(
|
|
80
|
+
_parseReactiveBatchTaskIdsForTest("M001/S01/reactive+T01,t02,T01"),
|
|
81
|
+
["T01", "T02"],
|
|
82
|
+
);
|
|
83
|
+
assert.deepEqual(_parseReactiveBatchTaskIdsForTest("M001/S01/T01"), []);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("reactive commit context key files include planned output, files, and key_files once", () => {
|
|
87
|
+
const result = _getPlannedKeyFilesForTest([
|
|
88
|
+
{
|
|
89
|
+
expected_output: ["src/new.ts", "src/shared.ts"],
|
|
90
|
+
files: ["src/input.ts", "src/shared.ts"],
|
|
91
|
+
key_files: ["src/key.ts"],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
expected_output: ["src/new.ts"],
|
|
95
|
+
files: ["src/other.ts"],
|
|
96
|
+
key_files: ["src/key.ts", "src/final.ts"],
|
|
97
|
+
},
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
assert.deepEqual(result, [
|
|
101
|
+
"src/new.ts",
|
|
102
|
+
"src/shared.ts",
|
|
103
|
+
"src/input.ts",
|
|
104
|
+
"src/key.ts",
|
|
105
|
+
"src/other.ts",
|
|
106
|
+
"src/final.ts",
|
|
107
|
+
]);
|
|
108
|
+
});
|
|
109
|
+
|
|
74
110
|
// ─── Dispatch Rule Matching Logic ─────────────────────────────────────────
|
|
75
111
|
|
|
76
112
|
test("reactive dispatch requires enabled config and multiple ready tasks", async () => {
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
resetWriteGateState,
|
|
11
11
|
shouldBlockContextArtifactSave,
|
|
12
12
|
} from "../bootstrap/write-gate.ts";
|
|
13
|
+
import { classifyCommand } from "../safety/destructive-guard.ts";
|
|
13
14
|
import { toRoundResultResponse } from "../../remote-questions/manager.ts";
|
|
14
15
|
|
|
15
16
|
function makeTempDir(prefix: string): string {
|
|
@@ -35,6 +36,40 @@ async function armDepthGate(
|
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
test("destructive guard classifies infrastructure mutation commands", () => {
|
|
40
|
+
assert.deepEqual(classifyCommand("terraform destroy -auto-approve").labels, ["IaC apply/destroy"]);
|
|
41
|
+
assert.deepEqual(classifyCommand("terragrunt apply").labels, ["IaC apply/destroy"]);
|
|
42
|
+
assert.deepEqual(classifyCommand("aws s3 delete-bucket --bucket example").labels, ["AWS mutation"]);
|
|
43
|
+
assert.deepEqual(classifyCommand("kubectl delete namespace prod").labels, ["kubectl mutation"]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("register-hooks hard-blocks destructive bash commands outside auto-mode", async () => {
|
|
47
|
+
const handlers = new Map<string, Array<(event: any, ctx?: any) => Promise<any> | any>>();
|
|
48
|
+
const pi = {
|
|
49
|
+
on(event: string, handler: (event: any, ctx?: any) => Promise<any> | any) {
|
|
50
|
+
const existing = handlers.get(event) ?? [];
|
|
51
|
+
existing.push(handler);
|
|
52
|
+
handlers.set(event, existing);
|
|
53
|
+
},
|
|
54
|
+
} as any;
|
|
55
|
+
|
|
56
|
+
registerHooks(pi, []);
|
|
57
|
+
|
|
58
|
+
let block: any;
|
|
59
|
+
for (const handler of handlers.get("tool_call") ?? []) {
|
|
60
|
+
const result = await handler({
|
|
61
|
+
toolCallId: "call-1",
|
|
62
|
+
toolName: "bash",
|
|
63
|
+
input: { command: "terraform apply -auto-approve" },
|
|
64
|
+
});
|
|
65
|
+
if (result?.block) block = result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
assert.equal(block?.block, true);
|
|
69
|
+
assert.match(block?.reason ?? "", /HARD BLOCK: destructive Bash command requires explicit human confirmation/);
|
|
70
|
+
assert.match(block?.reason ?? "", /IaC apply\/destroy/);
|
|
71
|
+
});
|
|
72
|
+
|
|
38
73
|
test("register-hooks unlocks milestone depth verification from question id without guided-flow state (#4047)", async (t) => {
|
|
39
74
|
const dir = makeTempDir("manual");
|
|
40
75
|
const originalCwd = process.cwd();
|
|
@@ -51,7 +51,7 @@ test("discuss workflow scopes tools for the queued turn and restores the full to
|
|
|
51
51
|
// discuss allowlist: gsd_summary_save and gsd_plan_milestone (the latter
|
|
52
52
|
// is needed for the discuss.md output phase — see DISCUSS_TOOLS_ALLOWLIST).
|
|
53
53
|
// gsd_task_complete and the broad shell_exec tool are scoped out.
|
|
54
|
-
assert.deepEqual(sentTools, ["
|
|
54
|
+
assert.deepEqual(sentTools, ["gsd_plan_milestone", "gsd_summary_save", "ToolSearch"]);
|
|
55
55
|
assert.deepEqual(activeTools, originalTools);
|
|
56
56
|
assert.equal(triggerTurn, true);
|
|
57
57
|
} finally {
|
|
@@ -5,6 +5,7 @@ import { join } from "node:path";
|
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { loadSkills } from "@gsd/pi-coding-agent";
|
|
7
7
|
import {
|
|
8
|
+
buildCompleteSlicePrompt,
|
|
8
9
|
buildPlanMilestonePrompt,
|
|
9
10
|
buildResearchMilestonePrompt,
|
|
10
11
|
buildSkillActivationBlock,
|
|
@@ -143,6 +144,29 @@ test("buildSkillActivationBlock includes skill_rules matches and task-plan skill
|
|
|
143
144
|
}
|
|
144
145
|
});
|
|
145
146
|
|
|
147
|
+
test("buildSkillActivationBlock matches skill_rules against exact unit type context", () => {
|
|
148
|
+
const base = makeTempBase();
|
|
149
|
+
try {
|
|
150
|
+
writeSkill(base, "complete-slice-policies", "Use for complete-slice closeout policy checks.");
|
|
151
|
+
writeSkill(base, "slice-broad", "Use for broad slice work.");
|
|
152
|
+
loadOnlyTestSkills(base);
|
|
153
|
+
|
|
154
|
+
const result = buildBlock(base, {
|
|
155
|
+
unitType: "complete-slice",
|
|
156
|
+
}, {
|
|
157
|
+
skill_rules: [
|
|
158
|
+
{ when: "complete-slice", use: ["complete-slice-policies"] },
|
|
159
|
+
{ when: "slice", use: ["slice-broad"] },
|
|
160
|
+
],
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
assert.match(result, /Call Skill\(\{ skill: 'complete-slice-policies' \}\)/);
|
|
164
|
+
assert.doesNotMatch(result, /slice-broad/);
|
|
165
|
+
} finally {
|
|
166
|
+
cleanup(base);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
146
170
|
test("buildSkillActivationBlock honors avoid_skills against always_use_skills", () => {
|
|
147
171
|
const base = makeTempBase();
|
|
148
172
|
try {
|
|
@@ -328,6 +352,37 @@ test("milestone prompt builders propagate always_use_skills through buildSkillAc
|
|
|
328
352
|
}
|
|
329
353
|
});
|
|
330
354
|
|
|
355
|
+
test("complete-slice prompt propagates always_use_skills through buildSkillActivationBlock", async () => {
|
|
356
|
+
const base = makeTempBase();
|
|
357
|
+
try {
|
|
358
|
+
writeSkill(base, "write-docs", "Use when writing docs or RFCs.");
|
|
359
|
+
writeProjectPreferences(base, "always_use_skills:\n - write-docs\n");
|
|
360
|
+
loadOnlyTestSkills(base);
|
|
361
|
+
|
|
362
|
+
const milestoneDir = join(base, ".gsd", "milestones", "M001");
|
|
363
|
+
const sliceDir = join(milestoneDir, "slices", "S01");
|
|
364
|
+
mkdirSync(sliceDir, { recursive: true });
|
|
365
|
+
writeFileSync(
|
|
366
|
+
join(milestoneDir, "M001-ROADMAP.md"),
|
|
367
|
+
[
|
|
368
|
+
"# M001: Test",
|
|
369
|
+
"",
|
|
370
|
+
"## Slices",
|
|
371
|
+
"",
|
|
372
|
+
"- [ ] **S01: Slice** `risk:low` `depends:[]`",
|
|
373
|
+
"",
|
|
374
|
+
].join("\n"),
|
|
375
|
+
);
|
|
376
|
+
writeFileSync(join(sliceDir, "S01-PLAN.md"), "# S01: Slice\n\n## Tasks\n\n- [x] **T01: Done**\n");
|
|
377
|
+
|
|
378
|
+
const prompt = await buildCompleteSlicePrompt("M001", "Test", "S01", "Slice", base);
|
|
379
|
+
|
|
380
|
+
assert.match(prompt, /Call Skill\(\{ skill: 'write-docs' \}\)/);
|
|
381
|
+
} finally {
|
|
382
|
+
cleanup(base);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
|
|
331
386
|
test("skill manifest strict warnings require GSD_SKILL_MANIFEST_STRICT=1", (t) => {
|
|
332
387
|
const previousStrict = process.env.GSD_SKILL_MANIFEST_STRICT;
|
|
333
388
|
const previousStderr = setStderrLoggingEnabled(false);
|
|
@@ -148,6 +148,7 @@ test("fresh start registers the auto worker before bootstrap enters worktree flo
|
|
|
148
148
|
const freshStartSectionIdx = startAutoBody.indexOf("// ── Fresh start path — delegated to auto-start.ts ──");
|
|
149
149
|
const resumeBody = startAutoBody.slice(resumeSectionIdx, freshStartSectionIdx);
|
|
150
150
|
const resumeDbOpenIdx = resumeBody.indexOf("await openProjectDbIfPresent(base);");
|
|
151
|
+
const resumeMergeReconcileIdx = resumeBody.indexOf("reconcileMergedMilestonesFromJournal(base);");
|
|
151
152
|
const resumeRegisterIdx = resumeBody.indexOf("registerAutoWorkerForSession(s, base);");
|
|
152
153
|
const resumeEnterMilestoneIdx = resumeBody.indexOf("buildLifecycle().enterMilestone");
|
|
153
154
|
const dbOpenIdx = bootstrapBody.indexOf("await openProjectDbIfPresent(base);");
|
|
@@ -164,6 +165,7 @@ test("fresh start registers the auto worker before bootstrap enters worktree flo
|
|
|
164
165
|
assert.ok(resumeSectionIdx > -1, "startAuto should have resume milestone entry flow");
|
|
165
166
|
assert.ok(freshStartSectionIdx > resumeSectionIdx, "resume assertions should be scoped before fresh start");
|
|
166
167
|
assert.ok(resumeDbOpenIdx > -1, "resume should open DB before milestone entry");
|
|
168
|
+
assert.ok(resumeMergeReconcileIdx > -1, "resume should reconcile merged milestones before deriving dispatch state");
|
|
167
169
|
assert.ok(resumeRegisterIdx > -1, "resume should register worker before milestone entry");
|
|
168
170
|
assert.ok(resumeEnterMilestoneIdx > -1, "resume should enter milestones through lifecycle");
|
|
169
171
|
assert.ok(bootstrapIdx > -1, "bootstrapAutoSession should exist");
|
|
@@ -179,8 +181,10 @@ test("fresh start registers the auto worker before bootstrap enters worktree flo
|
|
|
179
181
|
"bootstrap must open DB and register worker before first enterMilestone",
|
|
180
182
|
);
|
|
181
183
|
assert.ok(
|
|
182
|
-
resumeDbOpenIdx <
|
|
183
|
-
|
|
184
|
+
resumeDbOpenIdx < resumeMergeReconcileIdx &&
|
|
185
|
+
resumeMergeReconcileIdx < resumeRegisterIdx &&
|
|
186
|
+
resumeRegisterIdx < resumeEnterMilestoneIdx,
|
|
187
|
+
"resume must open DB, reconcile merged milestones, and register worker before first enterMilestone",
|
|
184
188
|
);
|
|
185
189
|
});
|
|
186
190
|
|
|
@@ -906,6 +906,51 @@ test("ADR-017 (#5704): registered milestone (DB row present) → no drift", asyn
|
|
|
906
906
|
|
|
907
907
|
// ─── #5705: roadmap-divergence drift ─────────────────────────────────────────
|
|
908
908
|
|
|
909
|
+
test("ADR-017 (#391): roadmap-divergence skips slices before task planning completes", async (t) => {
|
|
910
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-adr017-roadmap-unplanned-"));
|
|
911
|
+
const milestoneDir = join(base, ".gsd", "milestones", "M001");
|
|
912
|
+
const roadmapPath = join(milestoneDir, "M001-ROADMAP.md");
|
|
913
|
+
mkdirSync(milestoneDir, { recursive: true });
|
|
914
|
+
const originalRoadmap = [
|
|
915
|
+
"# M001: Test",
|
|
916
|
+
"",
|
|
917
|
+
"**Vision:** Verify transient milestone planning state",
|
|
918
|
+
"",
|
|
919
|
+
"## Slices",
|
|
920
|
+
"",
|
|
921
|
+
"- [ ] **S01: Foundation** `risk:medium` `depends:[]`",
|
|
922
|
+
"- [ ] **S02: Feature** `risk:medium` `depends:[S01]`",
|
|
923
|
+
"",
|
|
924
|
+
].join("\n");
|
|
925
|
+
writeFileSync(roadmapPath, originalRoadmap);
|
|
926
|
+
t.after(() => {
|
|
927
|
+
try { closeDatabase(); } catch { /* noop */ }
|
|
928
|
+
rmSync(base, { recursive: true, force: true });
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
932
|
+
insertMilestone({ id: "M001", title: "Test", status: "active" });
|
|
933
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "Foundation", status: "pending", risk: "medium", depends: [], demo: "", sequence: 1 });
|
|
934
|
+
insertSlice({ id: "S02", milestoneId: "M001", title: "Feature", status: "pending", risk: "medium", depends: [], demo: "", sequence: 2 });
|
|
935
|
+
|
|
936
|
+
assert.equal(getSliceTasks("M001", "S01").length, 0, "pre: S01 has not been planned");
|
|
937
|
+
assert.equal(getSliceTasks("M001", "S02").length, 0, "pre: S02 has not been planned");
|
|
938
|
+
|
|
939
|
+
const result = await reconcileBeforeDispatch(base, {
|
|
940
|
+
invalidateStateCache: () => {},
|
|
941
|
+
deriveState: async () => makeState(),
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
assert.equal(result.ok, true);
|
|
945
|
+
assert.equal(
|
|
946
|
+
result.repaired.some((d) => d.kind === "roadmap-divergence"),
|
|
947
|
+
false,
|
|
948
|
+
"unplanned slices should not trigger roadmap-divergence repair",
|
|
949
|
+
);
|
|
950
|
+
assert.equal(readFileSync(roadmapPath, "utf-8"), originalRoadmap);
|
|
951
|
+
assert.deepEqual(getSlice("M001", "S02")?.depends, [], "DB remains unchanged");
|
|
952
|
+
});
|
|
953
|
+
|
|
909
954
|
test("ADR-017 (#5705): roadmap-divergence re-renders projection without syncing depends into DB", async (t) => {
|
|
910
955
|
const base = mkdtempSync(join(tmpdir(), "gsd-adr017-roadmap-"));
|
|
911
956
|
const milestoneDir = join(base, ".gsd", "milestones", "M001");
|
|
@@ -936,6 +981,8 @@ test("ADR-017 (#5705): roadmap-divergence re-renders projection without syncing
|
|
|
936
981
|
// Seed DB with S02 depending on [] — diverges from ROADMAP.md
|
|
937
982
|
insertSlice({ id: "S01", milestoneId: "M001", title: "Foundation", status: "pending", risk: "medium", depends: [], demo: "", sequence: 1 });
|
|
938
983
|
insertSlice({ id: "S02", milestoneId: "M001", title: "Feature", status: "pending", risk: "medium", depends: [], demo: "", sequence: 2 });
|
|
984
|
+
insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "Plan S01", status: "pending" });
|
|
985
|
+
insertTask({ id: "T01", sliceId: "S02", milestoneId: "M001", title: "Plan S02", status: "pending" });
|
|
939
986
|
|
|
940
987
|
assert.deepEqual(getSlice("M001", "S02")?.depends, [], "pre: DB has S02.depends = []");
|
|
941
988
|
|
|
@@ -987,6 +1034,7 @@ test("ADR-017 (#5705): ROADMAP-only slice is removed from projection and not ins
|
|
|
987
1034
|
insertMilestone({ id: "M001", title: "Test", status: "active" });
|
|
988
1035
|
// Only insert S01 — S02 is intentionally absent from the DB.
|
|
989
1036
|
insertSlice({ id: "S01", milestoneId: "M001", title: "Foundation", status: "pending", risk: "medium", depends: [], demo: "", sequence: 1 });
|
|
1037
|
+
insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "Plan S01", status: "pending" });
|
|
990
1038
|
|
|
991
1039
|
assert.equal(getSlice("M001", "S02"), null, "pre: S02 has no DB row");
|
|
992
1040
|
|
|
@@ -1035,6 +1083,8 @@ test("ADR-017 (#5705): ROADMAP sequence drift re-renders from DB order without m
|
|
|
1035
1083
|
insertMilestone({ id: "M001", title: "Test", status: "active" });
|
|
1036
1084
|
insertSlice({ id: "S01", milestoneId: "M001", title: "Foundation", status: "pending", risk: "medium", depends: [], demo: "", sequence: 1 });
|
|
1037
1085
|
insertSlice({ id: "S02", milestoneId: "M001", title: "Feature", status: "pending", risk: "medium", depends: [], demo: "", sequence: 2 });
|
|
1086
|
+
insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "Plan S01", status: "pending" });
|
|
1087
|
+
insertTask({ id: "T01", sliceId: "S02", milestoneId: "M001", title: "Plan S02", status: "pending" });
|
|
1038
1088
|
|
|
1039
1089
|
const result = await reconcileBeforeDispatch(base, {
|
|
1040
1090
|
invalidateStateCache: () => {},
|
|
@@ -1078,6 +1128,7 @@ test("ADR-017 (#5705): ROADMAP checkbox drift re-renders from DB status without
|
|
|
1078
1128
|
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
1079
1129
|
insertMilestone({ id: "M001", title: "Test", status: "active" });
|
|
1080
1130
|
insertSlice({ id: "S01", milestoneId: "M001", title: "Foundation", status: "pending", risk: "medium", depends: [], demo: "", sequence: 1 });
|
|
1131
|
+
insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "Plan S01", status: "pending" });
|
|
1081
1132
|
|
|
1082
1133
|
const result = await reconcileBeforeDispatch(base, {
|
|
1083
1134
|
invalidateStateCache: () => {},
|
|
@@ -1119,6 +1170,7 @@ test("ADR-017 (#5705): in-sync ROADMAP and DB → no roadmap-divergence drift",
|
|
|
1119
1170
|
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
1120
1171
|
insertMilestone({ id: "M001", title: "Test", status: "active" });
|
|
1121
1172
|
insertSlice({ id: "S01", milestoneId: "M001", title: "Foundation", status: "pending", risk: "low", depends: [], demo: "", sequence: 1 });
|
|
1173
|
+
insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "Plan S01", status: "pending" });
|
|
1122
1174
|
|
|
1123
1175
|
const result = await reconcileBeforeDispatch(base, {
|
|
1124
1176
|
invalidateStateCache: () => {},
|