@opengsd/gsd-pi 1.3.0-dev.72e3af2a → 1.3.0-dev.738160b9
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 +1 -1
- package/dist/cli.js +17 -4
- package/dist/headless-answers.js +1 -1
- package/dist/headless-ui.d.ts +1 -1
- package/dist/headless-ui.js +1 -1
- package/dist/headless.js +1 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/claude-code-cli/models.js +9 -0
- package/dist/resources/extensions/gsd/auto/session.js +8 -0
- package/dist/resources/extensions/gsd/auto/verification-retry-policy.js +1 -1
- package/dist/resources/extensions/gsd/auto-budget.js +5 -1
- package/dist/resources/extensions/gsd/auto-dispatch.js +14 -1
- package/dist/resources/extensions/gsd/auto-post-unit.js +1 -1
- package/dist/resources/extensions/gsd/auto-prompts.js +3 -3
- package/dist/resources/extensions/gsd/auto-recovery.js +3 -3
- package/dist/resources/extensions/gsd/auto-verification.js +5 -2
- package/dist/resources/extensions/gsd/auto.js +19 -10
- package/dist/resources/extensions/gsd/bootstrap/core-session-tools.js +1 -0
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +5 -4
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +31 -2
- package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +32 -4
- package/dist/resources/extensions/gsd/closeout-consistency-gate.js +3 -1
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +1 -1
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands-usage.js +1 -1
- package/dist/resources/extensions/gsd/db/engine.js +4 -1
- package/dist/resources/extensions/gsd/db-memory-fts-schema.js +34 -0
- package/dist/resources/extensions/gsd/delegation-policy.js +1 -1
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +2 -2
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +192 -4
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +3 -0
- package/dist/resources/extensions/gsd/exec-sandbox.js +8 -2
- package/dist/resources/extensions/gsd/flat-phase-migration.js +45 -0
- package/dist/resources/extensions/gsd/git-service.js +57 -0
- package/dist/resources/extensions/gsd/gitignore.js +4 -1
- package/dist/resources/extensions/gsd/json-persistence.js +20 -0
- package/dist/resources/extensions/gsd/knowledge-backfill.js +18 -11
- package/dist/resources/extensions/gsd/memory-backfill.js +28 -21
- package/dist/resources/extensions/gsd/metrics.js +4 -4
- package/dist/resources/extensions/gsd/migrate-external.js +11 -2
- package/dist/resources/extensions/gsd/orphan-stash-audit.js +92 -16
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +2 -2
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +3 -3
- package/dist/resources/extensions/gsd/root-write-leak-guard.js +7 -1
- package/dist/resources/extensions/gsd/safety/destructive-guard.js +8 -1
- package/dist/resources/extensions/gsd/safety/evidence-collector.js +16 -1
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +5 -2
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +4 -2
- package/dist/resources/extensions/gsd/tools/plan-slice.js +59 -44
- package/dist/resources/extensions/gsd/tools/plan-task.js +28 -5
- package/dist/resources/extensions/gsd/undo.js +43 -6
- package/dist/resources/extensions/gsd/unit-registry.js +4 -2
- package/dist/resources/extensions/gsd/verification-gate.js +7 -0
- package/dist/resources/extensions/gsd/workflow-manifest.js +126 -13
- 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 +9 -9
- 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 +9 -9
- 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 +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- 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/rpc/rpc-client.d.ts +2 -0
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js +23 -5
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/workflow-tools.js +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +3 -2
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +46 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +38 -0
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/claude-code-cli/models.ts +9 -0
- package/src/resources/extensions/claude-code-cli/tests/index.test.ts +14 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/session.ts +9 -0
- package/src/resources/extensions/gsd/auto/verification-retry-policy.ts +3 -1
- package/src/resources/extensions/gsd/auto-budget.ts +12 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +14 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +1 -1
- package/src/resources/extensions/gsd/auto-prompts.ts +3 -3
- package/src/resources/extensions/gsd/auto-recovery.ts +3 -3
- package/src/resources/extensions/gsd/auto-verification.ts +5 -1
- package/src/resources/extensions/gsd/auto.ts +23 -8
- package/src/resources/extensions/gsd/bootstrap/core-session-tools.ts +1 -0
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +5 -4
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +41 -2
- package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +34 -4
- package/src/resources/extensions/gsd/closeout-consistency-gate.ts +2 -1
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +1 -1
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands-usage.ts +1 -1
- package/src/resources/extensions/gsd/db/engine.ts +8 -1
- package/src/resources/extensions/gsd/db-memory-fts-schema.ts +45 -0
- package/src/resources/extensions/gsd/delegation-policy.ts +1 -1
- package/src/resources/extensions/gsd/docs/preferences-reference.md +2 -2
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +233 -5
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +3 -0
- package/src/resources/extensions/gsd/doctor-types.ts +3 -0
- package/src/resources/extensions/gsd/exec-sandbox.ts +7 -2
- package/src/resources/extensions/gsd/flat-phase-migration.ts +44 -0
- package/src/resources/extensions/gsd/git-service.ts +64 -0
- package/src/resources/extensions/gsd/gitignore.ts +4 -1
- package/src/resources/extensions/gsd/json-persistence.ts +20 -0
- package/src/resources/extensions/gsd/knowledge-backfill.ts +16 -10
- package/src/resources/extensions/gsd/memory-backfill.ts +26 -20
- package/src/resources/extensions/gsd/metrics.ts +4 -4
- package/src/resources/extensions/gsd/migrate-external.ts +8 -2
- package/src/resources/extensions/gsd/orphan-stash-audit.ts +108 -20
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +2 -2
- package/src/resources/extensions/gsd/prompts/plan-slice.md +3 -3
- package/src/resources/extensions/gsd/root-write-leak-guard.ts +13 -1
- package/src/resources/extensions/gsd/safety/destructive-guard.ts +9 -1
- package/src/resources/extensions/gsd/safety/evidence-collector.ts +16 -1
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +8 -2
- package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +14 -5
- package/src/resources/extensions/gsd/tests/auto-pause-double-entry-guard.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +182 -4
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +28 -0
- package/src/resources/extensions/gsd/tests/complete-slice-reopen-handoff.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/db-memory-fts-schema.test.ts +58 -3
- package/src/resources/extensions/gsd/tests/decisions-projection-from-memories.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/destructive-guard.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +143 -0
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +239 -2
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +37 -2
- package/src/resources/extensions/gsd/tests/gitignore-bg-shell-runtime.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +11 -2
- package/src/resources/extensions/gsd/tests/json-persistence-atomic.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/knowledge-backfill-projection.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +31 -1
- package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/metrics.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/migrate-external-worktree.test.ts +57 -0
- package/src/resources/extensions/gsd/tests/orphan-stash-audit.test.ts +91 -7
- package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +103 -4
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +44 -1
- package/src/resources/extensions/gsd/tests/post-mutation-hook.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +8 -6
- package/src/resources/extensions/gsd/tests/register-hooks-loop-guard-auto.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/root-write-leak-guard.test.ts +20 -1
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +8 -3
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +62 -5
- package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +6 -3
- package/src/resources/extensions/gsd/tests/undo.test.ts +74 -0
- package/src/resources/extensions/gsd/tests/uok-gitops-turn-action.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/verification-retry-policy.test.ts +69 -4
- package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +96 -15
- package/src/resources/extensions/gsd/tests/workflow-mcp-preflight.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +8 -4
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +6 -5
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -2
- package/src/resources/extensions/gsd/tools/plan-slice.ts +60 -47
- package/src/resources/extensions/gsd/tools/plan-task.ts +30 -5
- package/src/resources/extensions/gsd/undo.ts +47 -6
- package/src/resources/extensions/gsd/unit-registry.ts +4 -2
- package/src/resources/extensions/gsd/verification-gate.ts +8 -0
- package/src/resources/extensions/gsd/workflow-manifest.ts +141 -7
- /package/dist/web/standalone/.next/static/{O7xDYXO0r4zFhIzY1hrWV → 2A_WqqCS-EoYZxN9_v3d2}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{O7xDYXO0r4zFhIzY1hrWV → 2A_WqqCS-EoYZxN9_v3d2}/_ssgManifest.js +0 -0
|
@@ -4,7 +4,7 @@ import { existsSync, mkdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { getLatestAssessmentByScope, getMilestone, getMilestoneSlices, getPendingGates, getSliceTasks, insertAssessment, isDbAvailable, transaction, } from "./gsd-db.js";
|
|
6
6
|
import { getWorkflowDatabasePath, refreshWorkflowDatabaseFromDisk, } from "./db-workspace.js";
|
|
7
|
-
import { isClosedStatus } from "./status-guards.js";
|
|
7
|
+
import { isClosedStatus, isDeferredStatus } from "./status-guards.js";
|
|
8
8
|
import { closeQualityGatesFromEvidence } from "./quality-gate-closure.js";
|
|
9
9
|
import { insertMilestoneValidationGates } from "./milestone-validation-gates.js";
|
|
10
10
|
import { relMilestoneFile, resolveSliceFile } from "./paths.js";
|
|
@@ -144,6 +144,8 @@ export function checkCloseoutConsistencyGate(milestoneId, options = {}) {
|
|
|
144
144
|
});
|
|
145
145
|
}
|
|
146
146
|
for (const slice of slices) {
|
|
147
|
+
if (isDeferredStatus(slice.status))
|
|
148
|
+
continue;
|
|
147
149
|
if (!isClosedStatus(slice.status)) {
|
|
148
150
|
return blocked("slice-open", `Closeout consistency blocked for ${milestoneId}: slice ${slice.id} status is "${slice.status}".`);
|
|
149
151
|
}
|
|
@@ -190,7 +190,7 @@ export async function handleAutoCommand(trimmed, ctx, pi) {
|
|
|
190
190
|
}
|
|
191
191
|
return true;
|
|
192
192
|
}
|
|
193
|
-
await pauseAuto(ctx, pi);
|
|
193
|
+
await pauseAuto(ctx, pi, undefined, { abortActiveTurn: true });
|
|
194
194
|
return true;
|
|
195
195
|
}
|
|
196
196
|
if (trimmed === "rate" || trimmed.startsWith("rate ")) {
|
|
@@ -1164,7 +1164,7 @@ async function configureContextCodebase(ctx, prefs) {
|
|
|
1164
1164
|
cm.observation_mask_turns = maskTurns;
|
|
1165
1165
|
else if (maskTurns === "clear")
|
|
1166
1166
|
delete cm.observation_mask_turns;
|
|
1167
|
-
const thresh = await promptNumber(ctx, "
|
|
1167
|
+
const thresh = await promptNumber(ctx, "Soft context warning threshold percent (0.5–0.95)", cm.compaction_threshold_percent, "0.60");
|
|
1168
1168
|
if (thresh !== undefined && thresh !== "clear")
|
|
1169
1169
|
cm.compaction_threshold_percent = thresh;
|
|
1170
1170
|
else if (thresh === "clear")
|
|
@@ -83,7 +83,7 @@ function formatThresholdLines() {
|
|
|
83
83
|
}
|
|
84
84
|
const compactionThreshold = prefs?.context_management?.compaction_threshold_percent;
|
|
85
85
|
if (typeof compactionThreshold === "number") {
|
|
86
|
-
lines.push(`
|
|
86
|
+
lines.push(`Soft context warning: ${Math.round(compactionThreshold * 100)}%`);
|
|
87
87
|
}
|
|
88
88
|
return lines;
|
|
89
89
|
}
|
|
@@ -19,7 +19,7 @@ import { createCoordinationTablesV24 } from "../db-coordination-schema.js";
|
|
|
19
19
|
import { createDbConnectionCache } from "../db-connection-cache.js";
|
|
20
20
|
import { backupDatabaseBeforeMigration, isMigrationBackupError } from "../db-migration-backup.js";
|
|
21
21
|
import { applyMigrationV2Artifacts, applyMigrationV3Memories, applyMigrationV4DecisionMadeBy, applyMigrationV5HierarchyTables, applyMigrationV6SliceSummaries, applyMigrationV7Dependencies, applyMigrationV8PlanningFields, applyMigrationV9Ordering, applyMigrationV10ReplanTrigger, applyMigrationV11TaskPlanning, applyMigrationV12QualityGates, applyMigrationV13HotPathIndexes, applyMigrationV14SliceDependencies, applyMigrationV15AuditTables, applyMigrationV16EscalationSource, applyMigrationV17TaskEscalation, applyMigrationV18MemorySources, applyMigrationV19MemoryFts, applyMigrationV20MemoryRelations, applyMigrationV21StructuredMemories, applyMigrationV22QualityGateRepair, applyMigrationV23MilestoneQueue, applyMigrationV26MilestoneCommitAttributions, applyMigrationV27ArtifactHash, applyMigrationV28MemoryLastHitAt, applyMigrationV29RepositoryTargets, } from "../db-migration-steps.js";
|
|
22
|
-
import { isMemoriesFtsAvailableSchema, tryCreateMemoriesFtsSchema } from "../db-memory-fts-schema.js";
|
|
22
|
+
import { isMemoriesFtsAvailableSchema, rebuildMemoriesFtsSchemaOnce, tryCreateMemoriesFtsSchema, } from "../db-memory-fts-schema.js";
|
|
23
23
|
import { createDbOpenState } from "../db-open-state.js";
|
|
24
24
|
import { createRuntimeKvTableV25 } from "../db-runtime-kv-schema.js";
|
|
25
25
|
import { getCurrentSchemaVersion, recordSchemaVersion } from "../db-schema-metadata.js";
|
|
@@ -101,6 +101,9 @@ function initSchema(db, fileBacked, dbPath) {
|
|
|
101
101
|
throw err;
|
|
102
102
|
}
|
|
103
103
|
migrateSchema(db, dbPath);
|
|
104
|
+
rebuildMemoriesFtsSchemaOnce(db, {
|
|
105
|
+
onRebuildFailed: (message) => logWarning("db", message),
|
|
106
|
+
});
|
|
104
107
|
}
|
|
105
108
|
export function _isLikelyWslDrvFsPathForTest(dbPath) {
|
|
106
109
|
if (!dbPath || process.platform !== "linux")
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Project/App: gsd-pi
|
|
2
2
|
// File Purpose: Memory FTS5 SQLite schema helpers for the GSD database facade.
|
|
3
|
+
import { createRuntimeKvTableV25 } from "./db-runtime-kv-schema.js";
|
|
4
|
+
export const MEMORIES_FTS_REBUILT_KEY = "memories_fts_rebuilt_at";
|
|
3
5
|
function formatFtsUnavailableError(err) {
|
|
4
6
|
const message = err instanceof Error ? err.message : String(err);
|
|
5
7
|
return message.replace(/\bmoduel\s*:\s*/gi, "module: ");
|
|
@@ -54,3 +56,35 @@ export function isMemoriesFtsAvailableSchema(db) {
|
|
|
54
56
|
return false;
|
|
55
57
|
}
|
|
56
58
|
}
|
|
59
|
+
export function rebuildMemoriesFtsSchemaOnce(db, options = {}) {
|
|
60
|
+
if (!isMemoriesFtsAvailableSchema(db))
|
|
61
|
+
return;
|
|
62
|
+
createRuntimeKvTableV25(db);
|
|
63
|
+
const marker = db.prepare("SELECT 1 as present FROM runtime_kv WHERE scope = 'global' AND scope_id = '' AND key = :key").get({ ":key": MEMORIES_FTS_REBUILT_KEY });
|
|
64
|
+
if (marker)
|
|
65
|
+
return;
|
|
66
|
+
const now = new Date().toISOString();
|
|
67
|
+
try {
|
|
68
|
+
db.exec("BEGIN");
|
|
69
|
+
db.exec("INSERT INTO memories_fts(memories_fts) VALUES('rebuild')");
|
|
70
|
+
db.prepare(`INSERT INTO runtime_kv (scope, scope_id, key, value_json, updated_at)
|
|
71
|
+
VALUES ('global', '', :key, :value_json, :updated_at)
|
|
72
|
+
ON CONFLICT (scope, scope_id, key) DO UPDATE SET
|
|
73
|
+
value_json = excluded.value_json,
|
|
74
|
+
updated_at = excluded.updated_at`).run({
|
|
75
|
+
":key": MEMORIES_FTS_REBUILT_KEY,
|
|
76
|
+
":value_json": JSON.stringify(now),
|
|
77
|
+
":updated_at": now,
|
|
78
|
+
});
|
|
79
|
+
db.exec("COMMIT");
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
try {
|
|
83
|
+
db.exec("ROLLBACK");
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Best effort: leave startup alive and retry the rebuild on next open.
|
|
87
|
+
}
|
|
88
|
+
options.onRebuildFailed?.(`FTS5 rebuild failed: ${err.message}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -94,7 +94,7 @@ const POLICY = {
|
|
|
94
94
|
gsd_plan_task: {
|
|
95
95
|
toolName: "gsd_plan_task",
|
|
96
96
|
verdict: "no",
|
|
97
|
-
rationale: "
|
|
97
|
+
rationale: "Incremental task planning mutates the active slice plan and re-renders projections; keep it ordered in the foreground plan-slice unit.",
|
|
98
98
|
},
|
|
99
99
|
};
|
|
100
100
|
function resolveCanonical(name) {
|
|
@@ -273,10 +273,10 @@ This config sets a parent workspace with two child repositories. The implicit `p
|
|
|
273
273
|
- `audit_unified.enabled`: boolean — dual-write unified audit envelope events. Default: `true`.
|
|
274
274
|
- `plan_v2.enabled`: boolean — enable bounded clarify/research/draft/compile planning flow. Default: `true`.
|
|
275
275
|
|
|
276
|
-
- `context_management`: configures context hygiene for auto-mode sessions. Keys:
|
|
276
|
+
- `context_management`: configures context hygiene for auto-mode sessions. In step mode, the configurable threshold is a soft warning; automatic session re-rooting happens only at the fixed 90% hard context boundary. Keys:
|
|
277
277
|
- `observation_masking`: boolean — mask old tool results to reduce context bloat. Default: `true`.
|
|
278
278
|
- `observation_mask_turns`: number — keep this many recent turns verbatim (1-50). Default: `8`.
|
|
279
|
-
- `compaction_threshold_percent`: number —
|
|
279
|
+
- `compaction_threshold_percent`: number — show a soft context warning at this fraction of the context window (0.5-0.95). Lower values warn earlier so operators can compact manually before drift accumulates. Default: `0.60`.
|
|
280
280
|
- `tool_result_max_chars`: number — max chars per tool result in GSD sessions (200-10000). Default: `800`.
|
|
281
281
|
|
|
282
282
|
- `auto_visualize`: boolean — show a visualizer hint after each milestone completion in auto-mode. Default: `false`.
|
|
@@ -1,11 +1,115 @@
|
|
|
1
|
-
import { existsSync, statSync } from "node:fs";
|
|
2
|
-
import {
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, join, relative } from "node:path";
|
|
3
|
+
import { getAllMilestones, getMilestoneSlices, getSliceTasks, isDbAvailable, isMemoriesFtsAvailable, _getAdapter, } from "./gsd-db.js";
|
|
4
|
+
import { MEMORIES_FTS_REBUILT_KEY } from "./db-memory-fts-schema.js";
|
|
3
5
|
import { isAfter, latestExplicitReopenAt } from "./milestone-reopen-events.js";
|
|
4
|
-
import { resolveGsdPathContract, resolveMilestoneFile } from "./paths.js";
|
|
6
|
+
import { gsdProjectionRoot, gsdRoot, resolveGsdPathContract, resolveMilestoneFile, resolveSliceFile } from "./paths.js";
|
|
5
7
|
import { deriveState } from "./state.js";
|
|
8
|
+
import { isClosedStatus } from "./status-guards.js";
|
|
6
9
|
import { workflowEventLogPath } from "./workflow-event-ledger.js";
|
|
7
10
|
import { readEvents } from "./workflow-events.js";
|
|
8
11
|
import { flushWorkflowProjections } from "./projection-flush.js";
|
|
12
|
+
import { parseRoadmapSlices } from "./roadmap-slices.js";
|
|
13
|
+
import { parsePlan } from "./parsers-legacy.js";
|
|
14
|
+
function relativeFile(basePath, filePath) {
|
|
15
|
+
return relative(basePath, filePath).split("\\").join("/");
|
|
16
|
+
}
|
|
17
|
+
function reportCheckboxDbStatusDivergence(issues, basePath, filePath, scope, unitId, status, checkboxDone) {
|
|
18
|
+
const dbDone = isClosedStatus(status);
|
|
19
|
+
if (checkboxDone === dbDone)
|
|
20
|
+
return;
|
|
21
|
+
issues.push({
|
|
22
|
+
severity: "error",
|
|
23
|
+
code: "checkbox_db_status_divergence",
|
|
24
|
+
scope,
|
|
25
|
+
unitId,
|
|
26
|
+
message: `${scope === "slice" ? "Slice" : "Task"} ${unitId} is ${dbDone ? "closed" : "open"} in the database (status: ${status}) but the markdown checkbox is ${checkboxDone ? "checked" : "unchecked"}.`,
|
|
27
|
+
file: relativeFile(basePath, filePath),
|
|
28
|
+
fixable: false,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function checkProjectionCheckboxDbStatus(basePath, milestoneIds, issues) {
|
|
32
|
+
for (const milestoneId of milestoneIds) {
|
|
33
|
+
const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
34
|
+
const slices = getMilestoneSlices(milestoneId);
|
|
35
|
+
if (roadmapPath && existsSync(roadmapPath)) {
|
|
36
|
+
try {
|
|
37
|
+
const roadmap = readFileSync(roadmapPath, "utf-8");
|
|
38
|
+
const sliceDoneById = new Map(parseRoadmapSlices(roadmap).map((entry) => [entry.id, entry.done]));
|
|
39
|
+
for (const slice of slices) {
|
|
40
|
+
const checkboxDone = sliceDoneById.get(slice.id);
|
|
41
|
+
if (checkboxDone === undefined)
|
|
42
|
+
continue;
|
|
43
|
+
reportCheckboxDbStatusDivergence(issues, basePath, roadmapPath, "slice", `${milestoneId}/${slice.id}`, slice.status, checkboxDone);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Non-fatal — checkbox drift diagnostics must never block doctor.
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
for (const slice of slices) {
|
|
51
|
+
const planPath = resolveSliceFile(basePath, milestoneId, slice.id, "PLAN");
|
|
52
|
+
if (!planPath || !existsSync(planPath))
|
|
53
|
+
continue;
|
|
54
|
+
try {
|
|
55
|
+
const plan = readFileSync(planPath, "utf-8");
|
|
56
|
+
// parsePlan reads the authoritative task checkboxes (the flat-phase
|
|
57
|
+
// <tasks> block / ## Tasks section), so a stray task-style checkbox
|
|
58
|
+
// line elsewhere in PLAN.md (e.g. a Must-Haves or Verification bullet
|
|
59
|
+
// above <tasks>) can no longer hide real drift or fake a divergence.
|
|
60
|
+
const taskDoneById = new Map(parsePlan(plan).tasks.map((entry) => [entry.id, entry.done]));
|
|
61
|
+
for (const task of getSliceTasks(milestoneId, slice.id)) {
|
|
62
|
+
const checkboxDone = taskDoneById.get(task.id);
|
|
63
|
+
if (checkboxDone === undefined)
|
|
64
|
+
continue;
|
|
65
|
+
reportCheckboxDbStatusDivergence(issues, basePath, planPath, "task", `${milestoneId}/${slice.id}/${task.id}`, task.status, checkboxDone);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Non-fatal — checkbox drift diagnostics must never block doctor.
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function isClearedByMilestoneShellProjectionFlush(basePath, issue, reRenderedMilestoneIds) {
|
|
75
|
+
if (issue.code !== "checkbox_db_status_divergence")
|
|
76
|
+
return false;
|
|
77
|
+
if (issue.scope !== "slice")
|
|
78
|
+
return false;
|
|
79
|
+
const milestoneId = issue.unitId.split("/")[0] ?? "";
|
|
80
|
+
if (!reRenderedMilestoneIds.has(milestoneId))
|
|
81
|
+
return false;
|
|
82
|
+
const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
83
|
+
if (!roadmapPath || !issue.file)
|
|
84
|
+
return false;
|
|
85
|
+
return issue.file === relativeFile(basePath, roadmapPath);
|
|
86
|
+
}
|
|
87
|
+
function artifactExistsOnDisk(basePath, artifactPath) {
|
|
88
|
+
if (isAbsolute(artifactPath))
|
|
89
|
+
return existsSync(artifactPath);
|
|
90
|
+
return [
|
|
91
|
+
join(gsdProjectionRoot(basePath), artifactPath),
|
|
92
|
+
join(gsdRoot(basePath), artifactPath),
|
|
93
|
+
].some((candidate) => existsSync(candidate));
|
|
94
|
+
}
|
|
95
|
+
function artifactUnitId(row) {
|
|
96
|
+
if (!row.milestone_id)
|
|
97
|
+
return "project";
|
|
98
|
+
if (row.slice_id && row.task_id)
|
|
99
|
+
return `${row.milestone_id}/${row.slice_id}/${row.task_id}`;
|
|
100
|
+
if (row.slice_id)
|
|
101
|
+
return `${row.milestone_id}/${row.slice_id}`;
|
|
102
|
+
return row.milestone_id;
|
|
103
|
+
}
|
|
104
|
+
function artifactScope(row) {
|
|
105
|
+
if (row.task_id)
|
|
106
|
+
return "task";
|
|
107
|
+
if (row.slice_id)
|
|
108
|
+
return "slice";
|
|
109
|
+
if (row.milestone_id)
|
|
110
|
+
return "milestone";
|
|
111
|
+
return "project";
|
|
112
|
+
}
|
|
9
113
|
export async function checkEngineHealth(basePath, issues, fixesApplied) {
|
|
10
114
|
const dbPath = resolveGsdPathContract(basePath).projectDb;
|
|
11
115
|
if (!isDbAvailable() && existsSync(dbPath)) {
|
|
@@ -23,6 +127,30 @@ export async function checkEngineHealth(basePath, issues, fixesApplied) {
|
|
|
23
127
|
try {
|
|
24
128
|
if (isDbAvailable()) {
|
|
25
129
|
const adapter = _getAdapter();
|
|
130
|
+
try {
|
|
131
|
+
if (isMemoriesFtsAvailable(adapter)) {
|
|
132
|
+
const runtimeKv = adapter
|
|
133
|
+
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='runtime_kv'")
|
|
134
|
+
.get();
|
|
135
|
+
const marker = runtimeKv
|
|
136
|
+
? adapter.prepare("SELECT 1 as present FROM runtime_kv WHERE scope = 'global' AND scope_id = '' AND key = :key").get({ ":key": MEMORIES_FTS_REBUILT_KEY })
|
|
137
|
+
: undefined;
|
|
138
|
+
if (!marker) {
|
|
139
|
+
issues.push({
|
|
140
|
+
severity: "warning",
|
|
141
|
+
code: "memories_fts_rebuild_missing",
|
|
142
|
+
scope: "project",
|
|
143
|
+
unitId: "project",
|
|
144
|
+
message: `Memory full-text index exists but runtime_kv has no ${MEMORIES_FTS_REBUILT_KEY} marker. The index may be stale or incomplete, so memory search can silently degrade to the LIKE fallback.`,
|
|
145
|
+
file: ".gsd/gsd.db",
|
|
146
|
+
fixable: false,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// Non-fatal — memory FTS health check failed
|
|
153
|
+
}
|
|
26
154
|
// a. Orphaned tasks (task.slice_id points to non-existent slice)
|
|
27
155
|
try {
|
|
28
156
|
const orphanedTasks = adapter
|
|
@@ -172,7 +300,33 @@ export async function checkEngineHealth(basePath, issues, fixesApplied) {
|
|
|
172
300
|
catch {
|
|
173
301
|
// Non-fatal — completed-milestone reopen check failed
|
|
174
302
|
}
|
|
175
|
-
// f.
|
|
303
|
+
// f. Artifact rows reference files that no longer exist on disk.
|
|
304
|
+
try {
|
|
305
|
+
const artifactRows = adapter
|
|
306
|
+
.prepare(`SELECT path, artifact_type, milestone_id, slice_id, task_id
|
|
307
|
+
FROM artifacts
|
|
308
|
+
WHERE path != ''
|
|
309
|
+
ORDER BY path`)
|
|
310
|
+
.all();
|
|
311
|
+
for (const row of artifactRows) {
|
|
312
|
+
if (artifactExistsOnDisk(basePath, row.path))
|
|
313
|
+
continue;
|
|
314
|
+
const unitId = artifactUnitId(row);
|
|
315
|
+
issues.push({
|
|
316
|
+
severity: "error",
|
|
317
|
+
code: "artifact_file_missing",
|
|
318
|
+
scope: artifactScope(row),
|
|
319
|
+
unitId,
|
|
320
|
+
message: `Artifact ${row.path} is recorded in the database as ${row.artifact_type || "UNKNOWN"} but no matching file exists on disk`,
|
|
321
|
+
file: row.path,
|
|
322
|
+
fixable: false,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
// Non-fatal — artifact file existence check failed
|
|
328
|
+
}
|
|
329
|
+
// g. Completion artifacts disagree with open DB hierarchy rows.
|
|
176
330
|
try {
|
|
177
331
|
const rows = adapter
|
|
178
332
|
.prepare(`SELECT a.path, a.artifact_type, a.milestone_id, a.slice_id, a.task_id, a.imported_at,
|
|
@@ -189,6 +343,8 @@ export async function checkEngineHealth(basePath, issues, fixesApplied) {
|
|
|
189
343
|
.all();
|
|
190
344
|
const seen = new Set();
|
|
191
345
|
for (const row of rows) {
|
|
346
|
+
if (!artifactExistsOnDisk(basePath, row.path))
|
|
347
|
+
continue;
|
|
192
348
|
const reopenAt = latestExplicitReopenAt(basePath, row.milestone_id);
|
|
193
349
|
if (!isAfter(row.imported_at, reopenAt))
|
|
194
350
|
continue;
|
|
@@ -223,9 +379,24 @@ export async function checkEngineHealth(basePath, issues, fixesApplied) {
|
|
|
223
379
|
catch {
|
|
224
380
|
// Non-fatal — DB constraint checks failed entirely
|
|
225
381
|
}
|
|
382
|
+
// Checkbox-vs-DB divergence detection runs before projection drift auto-fix
|
|
383
|
+
// so stale re-renders cannot overwrite manually edited markdown first. Runs
|
|
384
|
+
// inside its own try/catch: getAllMilestones / getMilestoneSlices /
|
|
385
|
+
// getSliceTasks issue prepared queries that can throw on a corrupt or locked
|
|
386
|
+
// DB, and like every other DB-touching check here this diagnostic must never
|
|
387
|
+
// block doctor.
|
|
388
|
+
try {
|
|
389
|
+
if (isDbAvailable()) {
|
|
390
|
+
checkProjectionCheckboxDbStatus(basePath, getAllMilestones().map((milestone) => milestone.id), issues);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
catch {
|
|
394
|
+
// Non-fatal: checkbox-vs-DB divergence check must never block doctor
|
|
395
|
+
}
|
|
226
396
|
// ── Projection drift detection ──────────────────────────────────────────
|
|
227
397
|
// If the DB is available, check whether markdown projections are stale
|
|
228
398
|
// relative to the event log and re-render them.
|
|
399
|
+
const reRenderedMilestoneIds = [];
|
|
229
400
|
try {
|
|
230
401
|
if (isDbAvailable()) {
|
|
231
402
|
const eventLogPath = workflowEventLogPath(basePath);
|
|
@@ -241,6 +412,7 @@ export async function checkEngineHealth(basePath, issues, fixesApplied) {
|
|
|
241
412
|
try {
|
|
242
413
|
await flushWorkflowProjections(basePath, { milestoneId: milestone.id });
|
|
243
414
|
fixesApplied.push(`re-rendered missing projections for ${milestone.id}`);
|
|
415
|
+
reRenderedMilestoneIds.push(milestone.id);
|
|
244
416
|
}
|
|
245
417
|
catch {
|
|
246
418
|
// Non-fatal — projection re-render failed
|
|
@@ -252,6 +424,7 @@ export async function checkEngineHealth(basePath, issues, fixesApplied) {
|
|
|
252
424
|
try {
|
|
253
425
|
await flushWorkflowProjections(basePath, { milestoneId: milestone.id });
|
|
254
426
|
fixesApplied.push(`re-rendered stale projections for ${milestone.id}`);
|
|
427
|
+
reRenderedMilestoneIds.push(milestone.id);
|
|
255
428
|
}
|
|
256
429
|
catch {
|
|
257
430
|
// Non-fatal — projection re-render failed
|
|
@@ -264,4 +437,19 @@ export async function checkEngineHealth(basePath, issues, fixesApplied) {
|
|
|
264
437
|
catch {
|
|
265
438
|
// Non-fatal — projection drift check must never block doctor
|
|
266
439
|
}
|
|
440
|
+
if (reRenderedMilestoneIds.length > 0) {
|
|
441
|
+
const reRendered = new Set(reRenderedMilestoneIds);
|
|
442
|
+
for (let i = issues.length - 1; i >= 0; i--) {
|
|
443
|
+
const issue = issues[i];
|
|
444
|
+
// flushWorkflowProjections re-renders milestone shell projections (not
|
|
445
|
+
// slice PLAN.md files), so only clear stale ROADMAP checkbox diagnostics.
|
|
446
|
+
if (isClearedByMilestoneShellProjectionFlush(basePath, issue, reRendered)) {
|
|
447
|
+
issues.splice(i, 1);
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
if (issue.code === "artifact_file_missing" && issue.file && artifactExistsOnDisk(basePath, issue.file)) {
|
|
451
|
+
issues.splice(i, 1);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
267
455
|
}
|
|
@@ -387,6 +387,9 @@ export async function checkRuntimeHealth(basePath, issues, fixesApplied, shouldF
|
|
|
387
387
|
if (!existingLines.has(".gsd-worktrees/") && !existingLines.has(".gsd-worktrees")) {
|
|
388
388
|
missing.push(".gsd-worktrees/");
|
|
389
389
|
}
|
|
390
|
+
if (!existingLines.has(".gsd-backups/") && !existingLines.has(".gsd-backups")) {
|
|
391
|
+
missing.push(".gsd-backups/");
|
|
392
|
+
}
|
|
390
393
|
if (!hasBlanketIgnore) {
|
|
391
394
|
missing.push(...criticalPatterns.filter(p => !existingLines.has(p)));
|
|
392
395
|
}
|
|
@@ -11,7 +11,7 @@ import { spawn } from "node:child_process";
|
|
|
11
11
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { randomUUID } from "node:crypto";
|
|
13
13
|
import { resolve } from "node:path";
|
|
14
|
-
import { killProcessTree, SIGKILL_GRACE_MS, HARD_DEADLINE_MS } from "@gsd/pi-coding-agent";
|
|
14
|
+
import { getShellConfig, killProcessTree, SIGKILL_GRACE_MS, HARD_DEADLINE_MS } from "@gsd/pi-coding-agent";
|
|
15
15
|
const ALWAYS_FORWARD_ENV = ["PATH", "HOME"];
|
|
16
16
|
// SIGKILL_GRACE_MS / HARD_DEADLINE_MS are imported from @gsd/pi-coding-agent
|
|
17
17
|
// (shell.ts) — the single source of truth for the graceful-kill timing ladder —
|
|
@@ -60,7 +60,13 @@ function clampTimeout(request, opts) {
|
|
|
60
60
|
function resolveCommand(runtime) {
|
|
61
61
|
switch (runtime) {
|
|
62
62
|
case "bash":
|
|
63
|
-
|
|
63
|
+
try {
|
|
64
|
+
const { shell, args } = getShellConfig();
|
|
65
|
+
return { cmd: shell, args };
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return { cmd: "bash", args: ["-c"] };
|
|
69
|
+
}
|
|
64
70
|
case "node":
|
|
65
71
|
return { cmd: process.execPath, args: ["-e"] };
|
|
66
72
|
case "python":
|
|
@@ -59,6 +59,51 @@ export function needsFlatPhaseMigration(basePath) {
|
|
|
59
59
|
// Resume an interrupted migration where the legacy tree was renamed aside.
|
|
60
60
|
return hasLegacyMilestoneSubdirs(legacyMigratingPath(basePath));
|
|
61
61
|
}
|
|
62
|
+
/** Retention window before flat-phase migration backups are auto-pruned. */
|
|
63
|
+
export const FLAT_PHASE_BACKUP_RETENTION_MS = 30 * 24 * 60 * 60 * 1000;
|
|
64
|
+
/**
|
|
65
|
+
* Remove stale flat-phase migration backups after the retention window.
|
|
66
|
+
* Only runs when migration is complete (.gsd/phases/ exists, legacy layout gone).
|
|
67
|
+
* Returns the number of migrate-* directories removed.
|
|
68
|
+
*/
|
|
69
|
+
export function pruneStaleFlatPhaseBackups(basePath) {
|
|
70
|
+
if (needsFlatPhaseMigration(basePath))
|
|
71
|
+
return 0;
|
|
72
|
+
const phasesPath = join(basePath, ".gsd", LAYOUT_SEGMENTS.level1);
|
|
73
|
+
if (!existsSync(phasesPath))
|
|
74
|
+
return 0;
|
|
75
|
+
const backupRoot = join(basePath, ".gsd-backups");
|
|
76
|
+
if (!existsSync(backupRoot))
|
|
77
|
+
return 0;
|
|
78
|
+
const now = Date.now();
|
|
79
|
+
let removed = 0;
|
|
80
|
+
for (const entry of readdirSync(backupRoot)) {
|
|
81
|
+
if (!entry.startsWith("migrate-"))
|
|
82
|
+
continue;
|
|
83
|
+
const dirPath = join(backupRoot, entry);
|
|
84
|
+
try {
|
|
85
|
+
const st = statSync(dirPath);
|
|
86
|
+
if (!st.isDirectory())
|
|
87
|
+
continue;
|
|
88
|
+
if (now - st.mtimeMs < FLAT_PHASE_BACKUP_RETENTION_MS)
|
|
89
|
+
continue;
|
|
90
|
+
rmSync(dirPath, { recursive: true, force: true });
|
|
91
|
+
removed++;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Non-fatal: leave backup for manual recovery.
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
if (readdirSync(backupRoot).length === 0) {
|
|
99
|
+
rmSync(backupRoot, { recursive: true, force: true });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Non-fatal.
|
|
104
|
+
}
|
|
105
|
+
return removed;
|
|
106
|
+
}
|
|
62
107
|
/**
|
|
63
108
|
* Migrate from legacy nested .gsd/milestones/ to flat-phase .gsd/phases/.
|
|
64
109
|
*
|
|
@@ -208,6 +208,7 @@ export class MergeConflictError extends GSDError {
|
|
|
208
208
|
*/
|
|
209
209
|
export const RUNTIME_EXCLUSION_PATHS = [
|
|
210
210
|
".gsd-worktrees/",
|
|
211
|
+
".gsd-backups/**",
|
|
211
212
|
".gsd/activity/",
|
|
212
213
|
".gsd/audit/",
|
|
213
214
|
".gsd/forensics/",
|
|
@@ -1027,6 +1028,7 @@ function collectRepositoryDirtyStatus(basePath) {
|
|
|
1027
1028
|
const preferences = loadEffectiveGSDPreferences(basePath)?.preferences;
|
|
1028
1029
|
const registry = createRepositoryRegistryFromPreferences(basePath, preferences);
|
|
1029
1030
|
const dirtyByRepository = {};
|
|
1031
|
+
const registeredRoots = new Set(registry.repositories.map((repo) => resolve(repo.root)));
|
|
1030
1032
|
for (const repo of registry.repositories) {
|
|
1031
1033
|
try {
|
|
1032
1034
|
dirtyByRepository[repo.id] = runGit(repo.root, ["status", "--porcelain"]).length > 0;
|
|
@@ -1036,8 +1038,63 @@ function collectRepositoryDirtyStatus(basePath) {
|
|
|
1036
1038
|
dirtyByRepository[repo.id] = nativeHasChanges(repo.root);
|
|
1037
1039
|
}
|
|
1038
1040
|
}
|
|
1041
|
+
for (const repoRoot of findUndeclaredNestedGitRepositories(registry.projectRoot, registeredRoots)) {
|
|
1042
|
+
let dirty = false;
|
|
1043
|
+
try {
|
|
1044
|
+
dirty = runGit(repoRoot, ["status", "--porcelain"]).length > 0;
|
|
1045
|
+
}
|
|
1046
|
+
catch {
|
|
1047
|
+
dirty = nativeHasChanges(repoRoot);
|
|
1048
|
+
}
|
|
1049
|
+
if (!dirty)
|
|
1050
|
+
continue;
|
|
1051
|
+
const relPath = relative(registry.projectRoot, repoRoot).split(sep).join("/") || repoRoot;
|
|
1052
|
+
const label = `${relPath} (undeclared)`;
|
|
1053
|
+
dirtyByRepository[label] = true;
|
|
1054
|
+
logWarning("engine", `undeclared dirty nested git repo ${relPath}; declare it under workspace.repositories or commit manually`, { file: "git-service.ts" });
|
|
1055
|
+
}
|
|
1039
1056
|
return dirtyByRepository;
|
|
1040
1057
|
}
|
|
1058
|
+
const UNDECLARED_NESTED_REPO_SCAN_MAX_DEPTH = 4;
|
|
1059
|
+
const UNDECLARED_NESTED_REPO_SKIP_DIRS = new Set([
|
|
1060
|
+
".git",
|
|
1061
|
+
".gsd",
|
|
1062
|
+
".gsd-worktrees",
|
|
1063
|
+
"node_modules",
|
|
1064
|
+
"dist",
|
|
1065
|
+
"dist-test",
|
|
1066
|
+
]);
|
|
1067
|
+
function findUndeclaredNestedGitRepositories(projectRoot, registeredRoots) {
|
|
1068
|
+
const found = [];
|
|
1069
|
+
function visit(dir, depth) {
|
|
1070
|
+
if (depth >= UNDECLARED_NESTED_REPO_SCAN_MAX_DEPTH)
|
|
1071
|
+
return;
|
|
1072
|
+
let entries;
|
|
1073
|
+
try {
|
|
1074
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
1075
|
+
}
|
|
1076
|
+
catch {
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
for (const entry of entries) {
|
|
1080
|
+
if (!entry.isDirectory())
|
|
1081
|
+
continue;
|
|
1082
|
+
if (UNDECLARED_NESTED_REPO_SKIP_DIRS.has(entry.name))
|
|
1083
|
+
continue;
|
|
1084
|
+
const child = join(dir, entry.name);
|
|
1085
|
+
const childRoot = resolve(child);
|
|
1086
|
+
const hasGitDir = existsSync(join(child, ".git"));
|
|
1087
|
+
const isRegistered = registeredRoots.has(childRoot);
|
|
1088
|
+
if (hasGitDir && !isRegistered) {
|
|
1089
|
+
found.push(childRoot);
|
|
1090
|
+
continue;
|
|
1091
|
+
}
|
|
1092
|
+
visit(child, depth + 1);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
visit(projectRoot, 0);
|
|
1096
|
+
return found;
|
|
1097
|
+
}
|
|
1041
1098
|
function runPerRepositoryCommitAction(args) {
|
|
1042
1099
|
const preferences = loadEffectiveGSDPreferences(args.basePath)?.preferences;
|
|
1043
1100
|
const registry = createRepositoryRegistryFromPreferences(args.basePath, preferences);
|
|
@@ -22,8 +22,9 @@ import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
|
|
|
22
22
|
* With external state (symlink), these are a no-op in most cases,
|
|
23
23
|
* but retained for backwards compatibility during migration.
|
|
24
24
|
*/
|
|
25
|
-
const GSD_RUNTIME_PATTERNS = [
|
|
25
|
+
export const GSD_RUNTIME_PATTERNS = [
|
|
26
26
|
".gsd-worktrees/",
|
|
27
|
+
".gsd-backups/",
|
|
27
28
|
".gsd/activity/",
|
|
28
29
|
".gsd/audit/",
|
|
29
30
|
".gsd/forensics/",
|
|
@@ -48,6 +49,8 @@ const BASELINE_PATTERNS = [
|
|
|
48
49
|
".gsd",
|
|
49
50
|
// Worktree container sibling — NOT covered by the ".gsd" pattern above.
|
|
50
51
|
".gsd-worktrees/",
|
|
52
|
+
// Flat-phase migration snapshots — NOT covered by the ".gsd" pattern above.
|
|
53
|
+
".gsd-backups/",
|
|
51
54
|
".gsd-id",
|
|
52
55
|
".mcp.json",
|
|
53
56
|
".bg-shell/",
|
|
@@ -56,6 +56,26 @@ export function saveJsonFile(filePath, data) {
|
|
|
56
56
|
// Non-fatal — don't let persistence failures break operation
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Save a JSON file atomically using compact serialization.
|
|
61
|
+
*
|
|
62
|
+
* Intended for machine-only ledgers where pretty-printing has no consumer.
|
|
63
|
+
* Keeps the same write-tmp-rename safety and non-fatal error behavior as
|
|
64
|
+
* saveJsonFile.
|
|
65
|
+
*/
|
|
66
|
+
export function saveJsonFileCompact(filePath, data) {
|
|
67
|
+
try {
|
|
68
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
69
|
+
// Use randomized tmp suffix to prevent concurrent-write data loss
|
|
70
|
+
const tmp = `${filePath}.tmp.${randomBytes(4).toString("hex")}`;
|
|
71
|
+
writeFileSync(tmp, JSON.stringify(data) + "\n", "utf-8");
|
|
72
|
+
renameSync(tmp, filePath);
|
|
73
|
+
// No cleanup needed — renameSync atomically removes tmp on success
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Non-fatal — don't let persistence failures break operation
|
|
77
|
+
}
|
|
78
|
+
}
|
|
59
79
|
/**
|
|
60
80
|
* Write a JSON file atomically (write to .tmp, then rename).
|
|
61
81
|
* Creates parent directories as needed. Non-fatal on error.
|
|
@@ -24,8 +24,8 @@ import { logWarning } from "./workflow-logger.js";
|
|
|
24
24
|
* - Idempotent (per-row): each migrated memory carries
|
|
25
25
|
* `structured_fields.sourceKnowledgeId = "<P|L>NNN"`. Rows whose ID is
|
|
26
26
|
* already present in the memory store are skipped.
|
|
27
|
-
* - Best-effort: never throws.
|
|
28
|
-
* backfill cannot block
|
|
27
|
+
* - Best-effort: never throws. Failed memory inserts are logged and skipped;
|
|
28
|
+
* setup/read failures log and return 0 so backfill cannot block startup.
|
|
29
29
|
* - Rules (K###) are intentionally skipped — they remain manually maintained
|
|
30
30
|
* in `KNOWLEDGE.md` per ADR-013.
|
|
31
31
|
*
|
|
@@ -56,15 +56,22 @@ export function backfillKnowledgeToMemories(basePath) {
|
|
|
56
56
|
const matchPattern = `%"sourceKnowledgeId":"${synth.id}"%`;
|
|
57
57
|
if (checkExisting.get({ ":pattern": matchPattern }))
|
|
58
58
|
continue;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
59
|
+
try {
|
|
60
|
+
const id = createMemory({
|
|
61
|
+
category: synth.category,
|
|
62
|
+
content: synth.content,
|
|
63
|
+
scope: synth.scope,
|
|
64
|
+
confidence: 0.85,
|
|
65
|
+
structuredFields: synth.structuredFields,
|
|
66
|
+
});
|
|
67
|
+
if (id)
|
|
68
|
+
written += 1;
|
|
69
|
+
}
|
|
70
|
+
catch (rowErr) {
|
|
71
|
+
const message = rowErr instanceof Error ? rowErr.message : String(rowErr);
|
|
72
|
+
logWarning("knowledge-backfill", `KNOWLEDGE.md -> memories backfill skipped ${synth.id}: ${message}`);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
68
75
|
}
|
|
69
76
|
return written;
|
|
70
77
|
}
|