fix: add missing milestones/ segment in resolveHookArtifactPath (#1779)
resolveHookArtifactPath() built paths as .gsd/<MID>/slices/... instead of .gsd/milestones/<MID>/slices/..., causing artifact idempotency checks, retry_on detection, and skip_if in pre-dispatch hooks to all fail silently. Closes #1721 Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
dda01fa648
commit
33caef89d0
3 changed files with 15 additions and 17 deletions
|
|
@ -206,21 +206,21 @@ function handleHookCompletion(basePath: string): HookDispatchResult | null {
|
|||
/**
|
||||
* Resolve the path where a hook artifact is expected to be written.
|
||||
* Uses the trigger unit's directory context:
|
||||
* - Task-level (M001/S01/T01): .gsd/M001/slices/S01/tasks/T01-{artifact}
|
||||
* - Slice-level (M001/S01): .gsd/M001/slices/S01/{artifact}
|
||||
* - Milestone-level (M001): .gsd/M001/{artifact}
|
||||
* - Task-level (M001/S01/T01): .gsd/milestones/M001/slices/S01/tasks/T01-{artifact}
|
||||
* - Slice-level (M001/S01): .gsd/milestones/M001/slices/S01/{artifact}
|
||||
* - Milestone-level (M001): .gsd/milestones/M001/{artifact}
|
||||
*/
|
||||
export function resolveHookArtifactPath(basePath: string, unitId: string, artifactName: string): string {
|
||||
const parts = unitId.split("/");
|
||||
if (parts.length === 3) {
|
||||
const [mid, sid, tid] = parts;
|
||||
return join(basePath, ".gsd", mid, "slices", sid, "tasks", `${tid}-${artifactName}`);
|
||||
return join(basePath, ".gsd", "milestones", mid, "slices", sid, "tasks", `${tid}-${artifactName}`);
|
||||
}
|
||||
if (parts.length === 2) {
|
||||
const [mid, sid] = parts;
|
||||
return join(basePath, ".gsd", mid, "slices", sid, artifactName);
|
||||
return join(basePath, ".gsd", "milestones", mid, "slices", sid, artifactName);
|
||||
}
|
||||
return join(basePath, ".gsd", parts[0], artifactName);
|
||||
return join(basePath, ".gsd", "milestones", parts[0], artifactName);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ const { assertEq, assertTrue, assertMatch, report } = createTestContext();
|
|||
|
||||
function createFixtureBase(): string {
|
||||
const base = mkdtempSync(join(tmpdir(), "gsd-hook-test-"));
|
||||
mkdirSync(join(base, ".gsd", "M001", "slices", "S01", "tasks"), { recursive: true });
|
||||
mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks"), { recursive: true });
|
||||
return base;
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ console.log("\n=== resolveHookArtifactPath ===");
|
|||
const taskPath = resolveHookArtifactPath(base, "M001/S01/T01", "REVIEW-PASS.md");
|
||||
assertEq(
|
||||
taskPath,
|
||||
join(base, ".gsd", "M001", "slices", "S01", "tasks", "T01-REVIEW-PASS.md"),
|
||||
join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks", "T01-REVIEW-PASS.md"),
|
||||
"task-level artifact path",
|
||||
);
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ console.log("\n=== resolveHookArtifactPath ===");
|
|||
const slicePath = resolveHookArtifactPath(base, "M001/S01", "REVIEW-PASS.md");
|
||||
assertEq(
|
||||
slicePath,
|
||||
join(base, ".gsd", "M001", "slices", "S01", "REVIEW-PASS.md"),
|
||||
join(base, ".gsd", "milestones", "M001", "slices", "S01", "REVIEW-PASS.md"),
|
||||
"slice-level artifact path",
|
||||
);
|
||||
|
||||
|
|
@ -61,7 +61,7 @@ console.log("\n=== resolveHookArtifactPath ===");
|
|||
const milestonePath = resolveHookArtifactPath(base, "M001", "REVIEW-PASS.md");
|
||||
assertEq(
|
||||
milestonePath,
|
||||
join(base, ".gsd", "M001", "REVIEW-PASS.md"),
|
||||
join(base, ".gsd", "milestones", "M001", "REVIEW-PASS.md"),
|
||||
"milestone-level artifact path",
|
||||
);
|
||||
}
|
||||
|
|
@ -129,15 +129,18 @@ console.log("\n=== Variable substitution ===");
|
|||
assertTrue(path3.includes("M002"), "3-part ID extracts milestoneId");
|
||||
assertTrue(path3.includes("S03"), "3-part ID extracts sliceId");
|
||||
assertTrue(path3.includes("T05"), "3-part ID extracts taskId");
|
||||
assertTrue(path3.includes("milestones"), "3-part ID includes milestones/ segment");
|
||||
|
||||
// 2-part ID
|
||||
const path2 = resolveHookArtifactPath(base, "M002/S03", "result.md");
|
||||
assertTrue(path2.includes("M002"), "2-part ID extracts milestoneId");
|
||||
assertTrue(path2.includes("S03"), "2-part ID extracts sliceId");
|
||||
assertTrue(path2.includes("milestones"), "2-part ID includes milestones/ segment");
|
||||
|
||||
// 1-part ID
|
||||
const path1 = resolveHookArtifactPath(base, "M002", "result.md");
|
||||
assertTrue(path1.includes("M002"), "1-part ID extracts milestoneId");
|
||||
assertTrue(path1.includes("milestones"), "1-part ID includes milestones/ segment");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
|
|
|||
|
|
@ -24,14 +24,9 @@ function createRetryFixture(): { base: string; cleanup: () => void } {
|
|||
const base = mkdtempSync(join(tmpdir(), "gsd-retry-reset-"));
|
||||
|
||||
// Create the .gsd structure for M001/S01/T01
|
||||
// Plan/Summary resolution uses .gsd/milestones/M001/slices/S01/...
|
||||
const milestonesTasksDir = join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks");
|
||||
mkdirSync(milestonesTasksDir, { recursive: true });
|
||||
|
||||
// Hook artifact resolution uses .gsd/M001/slices/S01/tasks/...
|
||||
const hookTasksDir = join(base, ".gsd", "M001", "slices", "S01", "tasks");
|
||||
mkdirSync(hookTasksDir, { recursive: true });
|
||||
|
||||
// Write a PLAN.md with T01 checked [x] (as doctor would do)
|
||||
const planFile = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-PLAN.md");
|
||||
writeFileSync(planFile, [
|
||||
|
|
@ -57,7 +52,7 @@ function createRetryFixture(): { base: string; cleanup: () => void } {
|
|||
);
|
||||
|
||||
// Write the retry_on artifact in the hook artifact path
|
||||
const retryArtifact = join(hookTasksDir, "T01-NEEDS-REWORK.md");
|
||||
const retryArtifact = join(milestonesTasksDir, "T01-NEEDS-REWORK.md");
|
||||
writeFileSync(retryArtifact, "Rework needed: test coverage insufficient.", "utf-8");
|
||||
|
||||
return {
|
||||
|
|
@ -325,7 +320,7 @@ console.log("\n=== resolveHookArtifactPath: correct path for retry artifacts ===
|
|||
const path = resolveHookArtifactPath(base, "M001/S01/T01", "NEEDS-REWORK.md");
|
||||
assertEq(
|
||||
path,
|
||||
join(base, ".gsd", "M001", "slices", "S01", "tasks", "T01-NEEDS-REWORK.md"),
|
||||
join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks", "T01-NEEDS-REWORK.md"),
|
||||
"retry artifact path resolves to task directory with task prefix",
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue