gsd-pi 2.78.1-dev.d8826a445 → 2.78.1-dev.eccf86e27
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 +5 -7
- package/dist/help-text.js +1 -1
- package/dist/resource-loader.js +6 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/detect-stuck.js +41 -5
- package/dist/resources/extensions/gsd/auto/loop.js +235 -36
- package/dist/resources/extensions/gsd/auto/phases.js +7 -5
- package/dist/resources/extensions/gsd/auto/session.js +33 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +46 -2
- package/dist/resources/extensions/gsd/auto-post-unit.js +19 -11
- package/dist/resources/extensions/gsd/auto-worktree.js +26 -187
- package/dist/resources/extensions/gsd/auto.js +79 -50
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +9 -4
- package/dist/resources/extensions/gsd/crash-recovery.js +160 -47
- package/dist/resources/extensions/gsd/db/auto-workers.js +227 -0
- package/dist/resources/extensions/gsd/db/command-queue.js +105 -0
- package/dist/resources/extensions/gsd/db/milestone-leases.js +210 -0
- package/dist/resources/extensions/gsd/db/runtime-kv.js +91 -0
- package/dist/resources/extensions/gsd/db/unit-dispatches.js +322 -0
- package/dist/resources/extensions/gsd/docs/COORDINATION.md +42 -0
- package/dist/resources/extensions/gsd/doctor-proactive.js +4 -0
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +22 -6
- package/dist/resources/extensions/gsd/doctor.js +12 -2
- package/dist/resources/extensions/gsd/gsd-db.js +161 -3
- package/dist/resources/extensions/gsd/guided-flow.js +6 -2
- package/dist/resources/extensions/gsd/interrupted-session.js +18 -15
- package/dist/resources/extensions/gsd/state.js +21 -6
- package/dist/resources/extensions/gsd/worktree-resolver.js +64 -0
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/src/resources/extensions/gsd/auto/detect-stuck.ts +37 -5
- package/src/resources/extensions/gsd/auto/loop.ts +263 -41
- package/src/resources/extensions/gsd/auto/phases.ts +7 -5
- package/src/resources/extensions/gsd/auto/session.ts +36 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +53 -2
- package/src/resources/extensions/gsd/auto-post-unit.ts +19 -11
- package/src/resources/extensions/gsd/auto-worktree.ts +26 -211
- package/src/resources/extensions/gsd/auto.ts +89 -44
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +9 -4
- package/src/resources/extensions/gsd/crash-recovery.ts +177 -43
- package/src/resources/extensions/gsd/db/auto-workers.ts +273 -0
- package/src/resources/extensions/gsd/db/command-queue.ts +149 -0
- package/src/resources/extensions/gsd/db/milestone-leases.ts +274 -0
- package/src/resources/extensions/gsd/db/runtime-kv.ts +127 -0
- package/src/resources/extensions/gsd/db/unit-dispatches.ts +446 -0
- package/src/resources/extensions/gsd/docs/COORDINATION.md +42 -0
- package/src/resources/extensions/gsd/doctor-proactive.ts +4 -0
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +24 -6
- package/src/resources/extensions/gsd/doctor.ts +10 -2
- package/src/resources/extensions/gsd/gsd-db.ts +170 -3
- package/src/resources/extensions/gsd/guided-flow.ts +6 -2
- package/src/resources/extensions/gsd/interrupted-session.ts +19 -12
- package/src/resources/extensions/gsd/state.ts +44 -6
- package/src/resources/extensions/gsd/tests/auto-loop-no-copy-artifacts.test.ts +72 -0
- package/src/resources/extensions/gsd/tests/auto-loop-symlink-worktree.test.ts +190 -0
- package/src/resources/extensions/gsd/tests/auto-workers.test.ts +105 -0
- package/src/resources/extensions/gsd/tests/command-queue.test.ts +141 -0
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +203 -0
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +169 -59
- package/src/resources/extensions/gsd/tests/detect-stuck-respects-retry.test.ts +173 -0
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +22 -12
- package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +24 -10
- package/src/resources/extensions/gsd/tests/integration/doctor-runtime.test.ts +35 -23
- package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +3 -5
- package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +72 -25
- package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +72 -25
- package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +9 -6
- package/src/resources/extensions/gsd/tests/milestone-leases.test.ts +152 -0
- package/src/resources/extensions/gsd/tests/parallel-milestone-isolation.test.ts +106 -0
- package/src/resources/extensions/gsd/tests/paused-session-via-db.test.ts +119 -0
- package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +3 -17
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/runtime-kv.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts +133 -28
- package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/stuck-state-via-db.test.ts +134 -0
- package/src/resources/extensions/gsd/tests/sync-layer-scope.test.ts +7 -26
- package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +4 -8
- package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +247 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +41 -1
- package/src/resources/extensions/gsd/tests/workspace.test.ts +15 -9
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +31 -23
- package/src/resources/extensions/gsd/worktree-resolver.ts +62 -0
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +0 -213
- package/src/resources/extensions/gsd/tests/auto-stale-lock-self-kill.test.ts +0 -87
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +0 -159
- /package/dist/web/standalone/.next/static/{AT5qi39nKXkdmQIOIoh0f → Y5UeGFkXTYM9WIQOWHkot}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{AT5qi39nKXkdmQIOIoh0f → Y5UeGFkXTYM9WIQOWHkot}/_ssgManifest.js +0 -0
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* without modifying orchestration code.
|
|
10
10
|
*/
|
|
11
11
|
import { loadFile, extractUatType, loadActiveOverrides } from "./files.js";
|
|
12
|
-
import { isDbAvailable, getMilestoneSlices, getPendingGates, markAllGatesOmitted, getMilestone } from "./gsd-db.js";
|
|
12
|
+
import { isDbAvailable, getMilestoneSlices, getPendingGates, markAllGatesOmitted, getMilestone, insertAssessment, transaction } from "./gsd-db.js";
|
|
13
13
|
import { isClosedStatus } from "./status-guards.js";
|
|
14
14
|
import { extractVerdict, isAcceptableUatVerdict } from "./verdict-parser.js";
|
|
15
15
|
import { gsdRoot, resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveTaskFile, relSliceFile, buildMilestoneFileName, } from "./paths.js";
|
|
@@ -32,6 +32,8 @@ import { MILESTONE_ID_RE } from "./milestone-ids.js";
|
|
|
32
32
|
import { PROJECT_RESEARCH_INFLIGHT_MARKER, } from "./project-research-policy.js";
|
|
33
33
|
import { isWorkflowPrefsCaptured, resolveDeepProjectSetupState, } from "./deep-project-setup-policy.js";
|
|
34
34
|
import { annotateBackgroundable } from "./delegation-policy.js";
|
|
35
|
+
import { invalidateAllCaches } from "./cache.js";
|
|
36
|
+
import { insertMilestoneValidationGates } from "./milestone-validation-gates.js";
|
|
35
37
|
let reassessmentChecker = checkNeedsReassessment;
|
|
36
38
|
let researchProjectPromptBuilder = buildResearchProjectPrompt;
|
|
37
39
|
function shouldBypassMilestoneDepthGateInAuto(prefs) {
|
|
@@ -996,9 +998,12 @@ export const DISPATCH_RULES = [
|
|
|
996
998
|
const skipSource = trivialVariant
|
|
997
999
|
? "trivial-scope pipeline variant (#4781)"
|
|
998
1000
|
: "`skip_milestone_validation` preference";
|
|
1001
|
+
const skipValidationReason = trivialVariant ? "trivial-scope" : "preference";
|
|
999
1002
|
const content = [
|
|
1000
1003
|
"---",
|
|
1001
1004
|
"verdict: pass",
|
|
1005
|
+
"skip_validation: true",
|
|
1006
|
+
`skip_validation_reason: ${skipValidationReason}`,
|
|
1002
1007
|
"remediation_round: 0",
|
|
1003
1008
|
"---",
|
|
1004
1009
|
"",
|
|
@@ -1007,6 +1012,39 @@ export const DISPATCH_RULES = [
|
|
|
1007
1012
|
`Milestone validation was skipped via ${skipSource}.`,
|
|
1008
1013
|
].join("\n");
|
|
1009
1014
|
writeFileSync(validationPath, content, "utf-8");
|
|
1015
|
+
try {
|
|
1016
|
+
// DB-backed state derivation keys off assessments, not only the file
|
|
1017
|
+
// projection. Persist the skipped validation there too so the next
|
|
1018
|
+
// loop iteration advances to completing-milestone instead of
|
|
1019
|
+
// re-entering validating-milestone.
|
|
1020
|
+
if (isDbAvailable()) {
|
|
1021
|
+
transaction(() => {
|
|
1022
|
+
insertAssessment({
|
|
1023
|
+
path: validationPath,
|
|
1024
|
+
milestoneId: mid,
|
|
1025
|
+
sliceId: null,
|
|
1026
|
+
taskId: null,
|
|
1027
|
+
status: "pass",
|
|
1028
|
+
scope: "milestone-validation",
|
|
1029
|
+
fullContent: content,
|
|
1030
|
+
});
|
|
1031
|
+
const gateSliceId = getMilestoneSlices(mid)[0]?.id;
|
|
1032
|
+
if (gateSliceId) {
|
|
1033
|
+
insertMilestoneValidationGates(mid, gateSliceId, "pass", new Date().toISOString());
|
|
1034
|
+
}
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
catch (err) {
|
|
1039
|
+
try {
|
|
1040
|
+
unlinkSync(validationPath);
|
|
1041
|
+
}
|
|
1042
|
+
catch (unlinkErr) {
|
|
1043
|
+
logWarning("dispatch", `failed to remove skipped validation file after DB write failure for ${mid}: ${unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr)}`);
|
|
1044
|
+
}
|
|
1045
|
+
throw err;
|
|
1046
|
+
}
|
|
1047
|
+
invalidateAllCaches();
|
|
1010
1048
|
}
|
|
1011
1049
|
return { action: "skip" };
|
|
1012
1050
|
}
|
|
@@ -1087,13 +1125,19 @@ export const DISPATCH_RULES = [
|
|
|
1087
1125
|
if (validationContent) {
|
|
1088
1126
|
// Allow completion when validation was intentionally skipped by
|
|
1089
1127
|
// preference/budget profile (#3399, #3344).
|
|
1128
|
+
const skippedByMarker = /^skip_validation:\s*true$/im.test(validationContent);
|
|
1090
1129
|
const skippedByPreference = /skip(?:ped)?[\s\-]+(?:by|per|due to)\s+(?:preference|budget|profile)/i.test(validationContent);
|
|
1130
|
+
const skippedByTrivialVariant = /trivial-scope pipeline variant/i.test(validationContent);
|
|
1091
1131
|
// Accept either the structured template format (table with MET/N/A/SATISFIED)
|
|
1092
1132
|
// or prose evidence patterns the validation agent may emit.
|
|
1093
1133
|
const structuredMatch = validationContent.includes("Operational") &&
|
|
1094
1134
|
(validationContent.includes("MET") || validationContent.includes("N/A") || validationContent.includes("SATISFIED"));
|
|
1095
1135
|
const proseMatch = /[Oo]perational[\s\S]{0,500}?(?:✅|pass|verified|confirmed|met|complete|true|yes|addressed|covered|satisfied|partially|n\/a|not[\s-]+applicable)/i.test(validationContent);
|
|
1096
|
-
const hasOperationalCheck =
|
|
1136
|
+
const hasOperationalCheck = skippedByMarker ||
|
|
1137
|
+
skippedByPreference ||
|
|
1138
|
+
skippedByTrivialVariant ||
|
|
1139
|
+
structuredMatch ||
|
|
1140
|
+
proseMatch;
|
|
1097
1141
|
if (!hasOperationalCheck) {
|
|
1098
1142
|
return {
|
|
1099
1143
|
action: "stop",
|
|
@@ -656,13 +656,15 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
656
656
|
if (s.currentUnit.type === "triage-captures") {
|
|
657
657
|
try {
|
|
658
658
|
const { executeTriageResolutions } = await import("./triage-resolution.js");
|
|
659
|
-
const state = await deriveState(s.
|
|
659
|
+
const state = await deriveState(s.canonicalProjectRoot);
|
|
660
660
|
const mid = state.activeMilestone?.id ?? "";
|
|
661
661
|
const sid = state.activeSlice?.id ?? "";
|
|
662
662
|
// executeTriageResolutions handles defer milestone creation even
|
|
663
663
|
// without an active milestone/slice (the "all milestones complete"
|
|
664
664
|
// scenario from #1562). inject/replan/quick-task still require mid+sid.
|
|
665
|
-
|
|
665
|
+
// Phase C: write to canonical project root. copyPlanningArtifacts
|
|
666
|
+
// has been deleted, so triage writes land where readers consult.
|
|
667
|
+
const triageResult = executeTriageResolutions(s.canonicalProjectRoot, mid, sid);
|
|
666
668
|
if (triageResult.injected > 0) {
|
|
667
669
|
ctx.ui.notify(`Triage: injected ${triageResult.injected} task${triageResult.injected === 1 ? "" : "s"} into ${sid} plan.`, "info");
|
|
668
670
|
}
|
|
@@ -805,10 +807,14 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
805
807
|
try {
|
|
806
808
|
const { milestone: mid, slice: sid } = parseUnitId(s.currentUnit.id);
|
|
807
809
|
if (mid && sid) {
|
|
808
|
-
|
|
810
|
+
// Phase C: write to the canonical project root (#5236 scope)
|
|
811
|
+
// so non-symlinked worktrees no longer maintain a separate
|
|
812
|
+
// local .gsd/ projection. copyPlanningArtifacts has been
|
|
813
|
+
// deleted; reads + writes converge at projectRoot.
|
|
814
|
+
const regenerated = await regenerateIfMissing(s.canonicalProjectRoot, mid, sid, "PLAN");
|
|
809
815
|
if (regenerated) {
|
|
810
816
|
// Re-check after regeneration
|
|
811
|
-
triggerArtifactVerified = verifyExpectedArtifact(s.currentUnit.type, s.currentUnit.id, s.
|
|
817
|
+
triggerArtifactVerified = verifyExpectedArtifact(s.currentUnit.type, s.currentUnit.id, s.canonicalProjectRoot);
|
|
812
818
|
if (triggerArtifactVerified) {
|
|
813
819
|
invalidateAllCaches();
|
|
814
820
|
}
|
|
@@ -999,7 +1005,7 @@ export async function postUnitPostVerification(pctx) {
|
|
|
999
1005
|
if (mid && sid && tid) {
|
|
1000
1006
|
try {
|
|
1001
1007
|
updateTaskStatus(mid, sid, tid, "pending");
|
|
1002
|
-
await renderPlanCheckboxes(s.
|
|
1008
|
+
await renderPlanCheckboxes(s.canonicalProjectRoot, mid, sid);
|
|
1003
1009
|
}
|
|
1004
1010
|
catch (dbErr) {
|
|
1005
1011
|
// DB unavailable — fail explicitly rather than silently reverting to markdown mutation.
|
|
@@ -1009,7 +1015,8 @@ export async function postUnitPostVerification(pctx) {
|
|
|
1009
1015
|
}
|
|
1010
1016
|
// 2. Delete SUMMARY.md for the task
|
|
1011
1017
|
if (mid && sid && tid) {
|
|
1012
|
-
|
|
1018
|
+
// Phase C: read+delete via canonical project root.
|
|
1019
|
+
const tasksDir = resolveTasksDir(s.canonicalProjectRoot, mid, sid);
|
|
1013
1020
|
if (tasksDir) {
|
|
1014
1021
|
const summaryFile = join(tasksDir, buildTaskFileName(tid, "SUMMARY"));
|
|
1015
1022
|
if (existsSync(summaryFile)) {
|
|
@@ -1019,7 +1026,7 @@ export async function postUnitPostVerification(pctx) {
|
|
|
1019
1026
|
}
|
|
1020
1027
|
// 3. Delete the retry_on artifact (e.g. NEEDS-REWORK.md)
|
|
1021
1028
|
if (trigger.retryArtifact) {
|
|
1022
|
-
const retryArtifactPath = resolveHookArtifactPath(s.
|
|
1029
|
+
const retryArtifactPath = resolveHookArtifactPath(s.canonicalProjectRoot, trigger.unitId, trigger.retryArtifact);
|
|
1023
1030
|
if (existsSync(retryArtifactPath)) {
|
|
1024
1031
|
unlinkSync(retryArtifactPath);
|
|
1025
1032
|
}
|
|
@@ -1255,16 +1262,17 @@ export async function postUnitPostVerification(pctx) {
|
|
|
1255
1262
|
if (hasPendingCaptures(s.basePath)) {
|
|
1256
1263
|
const pending = loadPendingCaptures(s.basePath);
|
|
1257
1264
|
if (pending.length > 0) {
|
|
1258
|
-
const
|
|
1265
|
+
const readRoot = s.canonicalProjectRoot;
|
|
1266
|
+
const state = await deriveState(readRoot);
|
|
1259
1267
|
const mid = state.activeMilestone?.id;
|
|
1260
1268
|
const sid = state.activeSlice?.id;
|
|
1261
1269
|
if (mid && sid) {
|
|
1262
1270
|
let currentPlan = "";
|
|
1263
1271
|
let roadmapContext = "";
|
|
1264
|
-
const planFile = resolveSliceFile(
|
|
1272
|
+
const planFile = resolveSliceFile(readRoot, mid, sid, "PLAN");
|
|
1265
1273
|
if (planFile)
|
|
1266
1274
|
currentPlan = (await loadFile(planFile)) ?? "";
|
|
1267
|
-
const roadmapFile = resolveMilestoneFile(
|
|
1275
|
+
const roadmapFile = resolveMilestoneFile(readRoot, mid, "ROADMAP");
|
|
1268
1276
|
if (roadmapFile)
|
|
1269
1277
|
roadmapContext = (await loadFile(roadmapFile)) ?? "";
|
|
1270
1278
|
const capturesList = pending.map(c => `- **${c.id}**: "${c.text}" (captured: ${c.timestamp})`).join("\n");
|
|
@@ -1312,7 +1320,7 @@ export async function postUnitPostVerification(pctx) {
|
|
|
1312
1320
|
// exits the loop, leaving the user with no hint to /clear and /gsd again.
|
|
1313
1321
|
if (s.stepMode) {
|
|
1314
1322
|
try {
|
|
1315
|
-
const nextState = await deriveState(s.
|
|
1323
|
+
const nextState = await deriveState(s.canonicalProjectRoot);
|
|
1316
1324
|
ctx.ui.notify(buildStepCompleteMessage(nextState), "info");
|
|
1317
1325
|
}
|
|
1318
1326
|
catch (e) {
|
|
@@ -9,7 +9,6 @@ import { existsSync, cpSync, readFileSync, readdirSync, mkdirSync, realpathSync,
|
|
|
9
9
|
import { isAbsolute, join, sep as pathSep } from "node:path";
|
|
10
10
|
import { GSDError, GSD_IO_ERROR, GSD_GIT_ERROR } from "./errors.js";
|
|
11
11
|
import { reconcileWorktreeDb, isDbAvailable, getMilestone, getMilestoneSlices, closeDatabase, openDatabase, getDbPath, } from "./gsd-db.js";
|
|
12
|
-
import { atomicWriteSync } from "./atomic-write.js";
|
|
13
12
|
import { execFileSync } from "node:child_process";
|
|
14
13
|
import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
|
|
15
14
|
import { gsdRoot, resolveGsdPathContract } from "./paths.js";
|
|
@@ -206,9 +205,12 @@ function getActiveWorkspace() {
|
|
|
206
205
|
}
|
|
207
206
|
function clearProjectRootStateFiles(basePath, milestoneId) {
|
|
208
207
|
const gsdDir = gsdRoot(basePath);
|
|
208
|
+
// Phase C pt 2: auto.lock removed from this list — the file is gone
|
|
209
|
+
// (migrated to the workers + unit_dispatches + runtime_kv tables). The
|
|
210
|
+
// remaining transient files (STATE.md, {MID}-META.json) are still
|
|
211
|
+
// worth removing on teardown.
|
|
209
212
|
const transientFiles = [
|
|
210
213
|
join(gsdDir, "STATE.md"),
|
|
211
|
-
join(gsdDir, "auto.lock"),
|
|
212
214
|
join(gsdDir, "milestones", milestoneId, `${milestoneId}-META.json`),
|
|
213
215
|
];
|
|
214
216
|
for (const file of transientFiles) {
|
|
@@ -968,120 +970,13 @@ export function enterBranchModeForMilestone(basePath, milestoneId) {
|
|
|
968
970
|
* Forward-merge plan checkbox state from the project root into a freshly
|
|
969
971
|
* re-attached worktree (#778).
|
|
970
972
|
*
|
|
971
|
-
*
|
|
972
|
-
*
|
|
973
|
-
*
|
|
974
|
-
*
|
|
975
|
-
*
|
|
976
|
-
*
|
|
977
|
-
* dispatch/skip loop.
|
|
978
|
-
*
|
|
979
|
-
* Fix: after re-attaching, read every *.md plan file in the milestone
|
|
980
|
-
* directory at the project root and apply any [x] checkbox states that are
|
|
981
|
-
* ahead of the worktree version (forward-only: never downgrade [x] → [ ]).
|
|
982
|
-
*
|
|
983
|
-
* This is forward-only compatibility for legacy projection copies. The DB
|
|
984
|
-
* remains authoritative; this never downgrades checked boxes in a local
|
|
985
|
-
* worktree projection.
|
|
973
|
+
* Phase C: deleted. Writers in workflow-projections.ts, triage-resolution.ts,
|
|
974
|
+
* rule-registry.ts, and auto-post-unit.ts now route through
|
|
975
|
+
* s.canonicalProjectRoot, so non-symlinked worktrees no longer need a local
|
|
976
|
+
* .gsd/ projection — the project-root .gsd/ is the only authoritative source
|
|
977
|
+
* for both reads and writes. copyPlanningArtifacts and reconcilePlanCheckboxes
|
|
978
|
+
* (both formerly here) became dead.
|
|
986
979
|
*/
|
|
987
|
-
/**
|
|
988
|
-
* Scope-typed variant of reconcilePlanCheckboxes.
|
|
989
|
-
*
|
|
990
|
-
* Takes an explicit (rootScope, worktreeScope) pair. milestoneId is taken
|
|
991
|
-
* from rootScope. Asserts both scopes belong to the same workspace identity
|
|
992
|
-
* to prevent silent mismatch bugs.
|
|
993
|
-
*/
|
|
994
|
-
export function reconcilePlanCheckboxesByScope(rootScope, worktreeScope) {
|
|
995
|
-
if (rootScope.workspace.identityKey !== worktreeScope.workspace.identityKey) {
|
|
996
|
-
throw new Error(`reconcilePlanCheckboxesByScope: scope identity mismatch — ` +
|
|
997
|
-
`rootScope.identityKey="${rootScope.workspace.identityKey}" ` +
|
|
998
|
-
`worktreeScope.identityKey="${worktreeScope.workspace.identityKey}"`);
|
|
999
|
-
}
|
|
1000
|
-
if (rootScope.milestoneId !== worktreeScope.milestoneId) {
|
|
1001
|
-
throw new Error(`reconcilePlanCheckboxesByScope: milestoneId mismatch — ` +
|
|
1002
|
-
`rootScope.milestoneId="${rootScope.milestoneId}" worktreeScope.milestoneId="${worktreeScope.milestoneId}"`);
|
|
1003
|
-
}
|
|
1004
|
-
const projectRoot = rootScope.workspace.projectRoot;
|
|
1005
|
-
const wtPath = worktreeScope.workspace.worktreeRoot ?? worktreeScope.workspace.projectRoot;
|
|
1006
|
-
const milestoneId = rootScope.milestoneId;
|
|
1007
|
-
reconcilePlanCheckboxes(projectRoot, wtPath, milestoneId);
|
|
1008
|
-
}
|
|
1009
|
-
/**
|
|
1010
|
-
* @deprecated Use reconcilePlanCheckboxesByScope instead.
|
|
1011
|
-
* TODO(C-future): remove once all callers migrated.
|
|
1012
|
-
*/
|
|
1013
|
-
function reconcilePlanCheckboxes(projectRoot, wtPath, milestoneId) {
|
|
1014
|
-
const srcMilestone = join(projectRoot, ".gsd", "milestones", milestoneId);
|
|
1015
|
-
const dstMilestone = join(wtPath, ".gsd", "milestones", milestoneId);
|
|
1016
|
-
if (!existsSync(srcMilestone) || !existsSync(dstMilestone))
|
|
1017
|
-
return;
|
|
1018
|
-
// Walk all markdown files in the milestone directory (plans, summaries, etc.)
|
|
1019
|
-
function walkMd(dir) {
|
|
1020
|
-
const results = [];
|
|
1021
|
-
try {
|
|
1022
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
1023
|
-
const full = join(dir, entry.name);
|
|
1024
|
-
if (entry.isDirectory()) {
|
|
1025
|
-
results.push(...walkMd(full));
|
|
1026
|
-
}
|
|
1027
|
-
else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
1028
|
-
results.push(full);
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
catch (err) {
|
|
1033
|
-
/* non-fatal */
|
|
1034
|
-
logWarning("worktree", `walkMd directory read failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1035
|
-
}
|
|
1036
|
-
return results;
|
|
1037
|
-
}
|
|
1038
|
-
for (const srcFile of walkMd(srcMilestone)) {
|
|
1039
|
-
const rel = srcFile.slice(srcMilestone.length);
|
|
1040
|
-
const dstFile = dstMilestone + rel;
|
|
1041
|
-
if (!existsSync(dstFile))
|
|
1042
|
-
continue; // only reconcile existing files
|
|
1043
|
-
let srcContent;
|
|
1044
|
-
let dstContent;
|
|
1045
|
-
try {
|
|
1046
|
-
srcContent = readFileSync(srcFile, "utf-8");
|
|
1047
|
-
dstContent = readFileSync(dstFile, "utf-8");
|
|
1048
|
-
}
|
|
1049
|
-
catch (e) {
|
|
1050
|
-
logWarning("worktree", `reconcilePlanCheckboxes read failed: ${e.message}`);
|
|
1051
|
-
continue;
|
|
1052
|
-
}
|
|
1053
|
-
if (srcContent === dstContent)
|
|
1054
|
-
continue;
|
|
1055
|
-
// Extract all checked task IDs from the source (project root)
|
|
1056
|
-
// Pattern: - [x] **T<id>: or - [x] **S<id>: (case-insensitive x)
|
|
1057
|
-
const checkedRe = /^- \[[xX]\] \*\*([TS]\d+):/gm;
|
|
1058
|
-
const srcChecked = new Set();
|
|
1059
|
-
for (const m of srcContent.matchAll(checkedRe))
|
|
1060
|
-
srcChecked.add(m[1]);
|
|
1061
|
-
if (srcChecked.size === 0)
|
|
1062
|
-
continue;
|
|
1063
|
-
// Forward-apply: replace [ ] → [x] for any IDs that are checked in src
|
|
1064
|
-
let updated = dstContent;
|
|
1065
|
-
let changed = false;
|
|
1066
|
-
for (const id of srcChecked) {
|
|
1067
|
-
const escapedId = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1068
|
-
const uncheckedRe = new RegExp(`^(- )\\[ \\]( \\*\\*${escapedId}:)`, "gm");
|
|
1069
|
-
if (uncheckedRe.test(updated)) {
|
|
1070
|
-
updated = updated.replace(new RegExp(`^(- )\\[ \\]( \\*\\*${escapedId}:)`, "gm"), "$1[x]$2");
|
|
1071
|
-
changed = true;
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
if (changed) {
|
|
1075
|
-
try {
|
|
1076
|
-
atomicWriteSync(dstFile, updated, "utf-8");
|
|
1077
|
-
}
|
|
1078
|
-
catch (err) {
|
|
1079
|
-
/* non-fatal */
|
|
1080
|
-
logWarning("worktree", `plan checkbox reconcile write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
980
|
export function createAutoWorktree(basePath, milestoneId) {
|
|
1086
981
|
basePath = resolveWorktreeProjectRoot(basePath);
|
|
1087
982
|
// Check if repo has commits — git worktree requires a valid HEAD
|
|
@@ -1127,33 +1022,15 @@ export function createAutoWorktree(basePath, milestoneId) {
|
|
|
1127
1022
|
startPoint,
|
|
1128
1023
|
});
|
|
1129
1024
|
}
|
|
1130
|
-
//
|
|
1131
|
-
//
|
|
1132
|
-
//
|
|
1133
|
-
//
|
|
1134
|
-
//
|
|
1135
|
-
//
|
|
1136
|
-
//
|
|
1137
|
-
// The
|
|
1138
|
-
//
|
|
1139
|
-
// overwrite them with stale data ([ ] checkboxes) because the root is
|
|
1140
|
-
// not always fully synced.
|
|
1141
|
-
if (!branchExists) {
|
|
1142
|
-
copyPlanningArtifacts(basePath, info.path);
|
|
1143
|
-
}
|
|
1144
|
-
else {
|
|
1145
|
-
// Re-attaching to an existing branch: forward-merge any plan checkpoint
|
|
1146
|
-
// state from the project root into the worktree (#778).
|
|
1147
|
-
//
|
|
1148
|
-
// If auto-mode stopped via crash, the milestone branch HEAD may lag behind
|
|
1149
|
-
// the project root filesystem because syncStateToProjectRoot() ran after
|
|
1150
|
-
// task completion but the auto-commit never fired. On restart the worktree
|
|
1151
|
-
// is re-created from the branch HEAD (which has [ ] for the crashed task),
|
|
1152
|
-
// causing verifyExpectedArtifact() to return false → stale-key eviction →
|
|
1153
|
-
// infinite dispatch/skip loop. Reconciling here ensures the worktree sees
|
|
1154
|
-
// the same [x] state that syncStateToProjectRoot() wrote to the root.
|
|
1155
|
-
reconcilePlanCheckboxes(basePath, info.path, milestoneId);
|
|
1156
|
-
}
|
|
1025
|
+
// Phase C: copyPlanningArtifacts and reconcilePlanCheckboxes were
|
|
1026
|
+
// deleted. Both addressed the same problem (worktree-local .gsd/
|
|
1027
|
+
// projection lagging behind project-root state) by maintaining a stale
|
|
1028
|
+
// copy. Now that auto-mode writers in workflow-projections.ts,
|
|
1029
|
+
// triage-resolution.ts, rule-registry.ts, and auto-post-unit.ts route
|
|
1030
|
+
// through s.canonicalProjectRoot, the worktree never needs a local
|
|
1031
|
+
// .gsd/ — both reads and writes converge on the project-root .gsd/.
|
|
1032
|
+
// The original concerns (#759, #778) no longer apply because there is
|
|
1033
|
+
// no second copy to drift.
|
|
1157
1034
|
// Run user-configured post-create hook (#597) — e.g. copy .env, symlink assets
|
|
1158
1035
|
const hookError = runWorktreePostCreateHook(basePath, info.path);
|
|
1159
1036
|
if (hookError) {
|
|
@@ -1173,51 +1050,13 @@ export function createAutoWorktree(basePath, milestoneId) {
|
|
|
1173
1050
|
nudgeGitBranchCache(previousCwd);
|
|
1174
1051
|
return info.path;
|
|
1175
1052
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
function copyPlanningArtifacts(srcBase, wtPath) {
|
|
1184
|
-
const srcGsd = join(srcBase, ".gsd");
|
|
1185
|
-
const dstGsd = join(wtPath, ".gsd");
|
|
1186
|
-
if (!existsSync(srcGsd))
|
|
1187
|
-
return;
|
|
1188
|
-
if (isSamePath(srcGsd, dstGsd))
|
|
1189
|
-
return;
|
|
1190
|
-
// Copy milestones/ directory (planning files, roadmaps, plans, research)
|
|
1191
|
-
safeCopyRecursive(join(srcGsd, "milestones"), join(dstGsd, "milestones"), {
|
|
1192
|
-
force: true,
|
|
1193
|
-
filter: (src) => !src.endsWith("-META.json"),
|
|
1194
|
-
});
|
|
1195
|
-
// Copy top-level planning files
|
|
1196
|
-
for (const file of [
|
|
1197
|
-
"DECISIONS.md",
|
|
1198
|
-
"REQUIREMENTS.md",
|
|
1199
|
-
"PROJECT.md",
|
|
1200
|
-
"QUEUE.md",
|
|
1201
|
-
"STATE.md",
|
|
1202
|
-
"KNOWLEDGE.md",
|
|
1203
|
-
"OVERRIDES.md",
|
|
1204
|
-
"mcp.json",
|
|
1205
|
-
]) {
|
|
1206
|
-
safeCopy(join(srcGsd, file), join(dstGsd, file), { force: true });
|
|
1207
|
-
}
|
|
1208
|
-
// Seed canonical PREFERENCES.md when available; fall back to legacy lowercase.
|
|
1209
|
-
if (existsSync(join(srcGsd, PROJECT_PREFERENCES_FILE))) {
|
|
1210
|
-
safeCopy(join(srcGsd, PROJECT_PREFERENCES_FILE), join(dstGsd, PROJECT_PREFERENCES_FILE), { force: true });
|
|
1211
|
-
}
|
|
1212
|
-
else if (existsSync(join(srcGsd, LEGACY_PROJECT_PREFERENCES_FILE))) {
|
|
1213
|
-
safeCopy(join(srcGsd, LEGACY_PROJECT_PREFERENCES_FILE), join(dstGsd, LEGACY_PROJECT_PREFERENCES_FILE), { force: true });
|
|
1214
|
-
}
|
|
1215
|
-
// Shared WAL (R012): worktrees use the project root's DB directly.
|
|
1216
|
-
// No longer copy gsd.db into the worktree — the DB path resolver in
|
|
1217
|
-
// ensureDbOpen() detects the worktree location and opens the root DB.
|
|
1218
|
-
// Compat note: reconcileWorktreeDb() in mergeMilestoneToMain handles
|
|
1219
|
-
// worktrees that already have a local gsd.db from before this change.
|
|
1220
|
-
}
|
|
1053
|
+
// Phase C: copyPlanningArtifacts removed. Planning artifacts now live
|
|
1054
|
+
// only at the project root .gsd/; auto-mode writers (workflow-projections,
|
|
1055
|
+
// triage-resolution, rule-registry, regenerateIfMissing,
|
|
1056
|
+
// resolveHookArtifactPath) all route through s.canonicalProjectRoot.
|
|
1057
|
+
// Worktrees are pure git checkouts — they no longer maintain a parallel
|
|
1058
|
+
// .gsd/ projection. The gsd.db has always lived at the project root via
|
|
1059
|
+
// the shared-WAL R012 contract; that is unchanged.
|
|
1221
1060
|
/**
|
|
1222
1061
|
* Teardown an auto-worktree: chdir back to original base, then remove
|
|
1223
1062
|
* the worktree and its branch.
|