@cleocode/core 2026.9.21 → 2026.9.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/scaffold.d.ts.map +1 -1
- package/dist/agents/scaffold.js +0 -1
- package/dist/agents/scaffold.js.map +1 -1
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +4 -0
- package/dist/bootstrap.js.map +1 -1
- package/dist/dispatch/mutate-projection.d.ts.map +1 -1
- package/dist/dispatch/mutate-projection.js +5 -0
- package/dist/dispatch/mutate-projection.js.map +1 -1
- package/dist/docs/docs-update.d.ts.map +1 -1
- package/dist/docs/docs-update.js +25 -9
- package/dist/docs/docs-update.js.map +1 -1
- package/dist/docs/export-document.js +323 -238
- package/dist/docs/export-document.js.map +3 -3
- package/dist/doctor/knowledge.d.ts +16 -1
- package/dist/doctor/knowledge.d.ts.map +1 -1
- package/dist/doctor/knowledge.js +53 -17
- package/dist/doctor/knowledge.js.map +1 -1
- package/dist/engine/engine-ops.d.ts +36 -6
- package/dist/engine/engine-ops.d.ts.map +1 -1
- package/dist/engine/engine-ops.js +115 -14
- package/dist/engine/engine-ops.js.map +1 -1
- package/dist/gateway-client/generated/types.gen.d.ts +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +51 -11
- package/dist/init.js.map +1 -1
- package/dist/inject/index.d.ts.map +1 -1
- package/dist/inject/index.js +8 -3
- package/dist/inject/index.js.map +1 -1
- package/dist/internal.d.ts +2 -2
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -1
- package/dist/internal.js.map +1 -1
- package/dist/llm/plugin-facade.js +323 -238
- package/dist/llm/plugin-facade.js.map +3 -3
- package/dist/memory/attention.d.ts.map +1 -1
- package/dist/memory/attention.js +2 -2
- package/dist/memory/attention.js.map +1 -1
- package/dist/nexus/analyze-orchestrator.d.ts +38 -0
- package/dist/nexus/analyze-orchestrator.d.ts.map +1 -1
- package/dist/nexus/analyze-orchestrator.js +96 -21
- package/dist/nexus/analyze-orchestrator.js.map +1 -1
- package/dist/nexus/knowledge.d.ts.map +1 -1
- package/dist/nexus/knowledge.js +15 -4
- package/dist/nexus/knowledge.js.map +1 -1
- package/dist/nexus/stored-roots.d.ts +65 -0
- package/dist/nexus/stored-roots.d.ts.map +1 -1
- package/dist/nexus/stored-roots.js +73 -0
- package/dist/nexus/stored-roots.js.map +1 -1
- package/dist/orchestrate/lifecycle-ops.d.ts.map +1 -1
- package/dist/orchestrate/lifecycle-ops.js +4 -1
- package/dist/orchestrate/lifecycle-ops.js.map +1 -1
- package/dist/orchestrate/plan.d.ts.map +1 -1
- package/dist/orchestrate/plan.js +5 -2
- package/dist/orchestrate/plan.js.map +1 -1
- package/dist/orchestrate/query-ops.d.ts +7 -2
- package/dist/orchestrate/query-ops.d.ts.map +1 -1
- package/dist/orchestrate/query-ops.js +17 -6
- package/dist/orchestrate/query-ops.js.map +1 -1
- package/dist/orchestrate/spawn-ops.d.ts.map +1 -1
- package/dist/orchestrate/spawn-ops.js +5 -14
- package/dist/orchestrate/spawn-ops.js.map +1 -1
- package/dist/orchestration/bootstrap.d.ts.map +1 -1
- package/dist/orchestration/bootstrap.js +4 -2
- package/dist/orchestration/bootstrap.js.map +1 -1
- package/dist/orchestration/index.d.ts +1 -0
- package/dist/orchestration/index.d.ts.map +1 -1
- package/dist/orchestration/index.js +4 -3
- package/dist/orchestration/index.js.map +1 -1
- package/dist/orchestration/lead-rollup.d.ts +8 -5
- package/dist/orchestration/lead-rollup.d.ts.map +1 -1
- package/dist/orchestration/lead-rollup.js +32 -15
- package/dist/orchestration/lead-rollup.js.map +1 -1
- package/dist/orchestration/parallel.d.ts.map +1 -1
- package/dist/orchestration/parallel.js +10 -17
- package/dist/orchestration/parallel.js.map +1 -1
- package/dist/orchestration/spawn-prompt.d.ts +4 -1
- package/dist/orchestration/spawn-prompt.d.ts.map +1 -1
- package/dist/orchestration/spawn-prompt.js +3 -2
- package/dist/orchestration/spawn-prompt.js.map +1 -1
- package/dist/orchestration/status.d.ts +8 -3
- package/dist/orchestration/status.d.ts.map +1 -1
- package/dist/orchestration/status.js +9 -6
- package/dist/orchestration/status.js.map +1 -1
- package/dist/orchestration/wave-topic.d.ts +54 -0
- package/dist/orchestration/wave-topic.d.ts.map +1 -0
- package/dist/orchestration/wave-topic.js +68 -0
- package/dist/orchestration/wave-topic.js.map +1 -0
- package/dist/orchestration/waves.d.ts +47 -8
- package/dist/orchestration/waves.d.ts.map +1 -1
- package/dist/orchestration/waves.js +152 -43
- package/dist/orchestration/waves.js.map +1 -1
- package/dist/phases/index.d.ts.map +1 -1
- package/dist/phases/index.js +6 -2
- package/dist/phases/index.js.map +1 -1
- package/dist/project-scope.d.ts +18 -0
- package/dist/project-scope.d.ts.map +1 -1
- package/dist/project-scope.js +37 -0
- package/dist/project-scope.js.map +1 -1
- package/dist/release/ci-evidence.d.ts +10 -0
- package/dist/release/ci-evidence.d.ts.map +1 -1
- package/dist/release/ci-evidence.js +23 -7
- package/dist/release/ci-evidence.js.map +1 -1
- package/dist/release/github-pr.d.ts +30 -0
- package/dist/release/github-pr.d.ts.map +1 -1
- package/dist/release/github-pr.js +40 -0
- package/dist/release/github-pr.js.map +1 -1
- package/dist/release/plan.d.ts.map +1 -1
- package/dist/release/plan.js +6 -3
- package/dist/release/plan.js.map +1 -1
- package/dist/release/pr-evidence.d.ts.map +1 -1
- package/dist/release/pr-evidence.js +12 -4
- package/dist/release/pr-evidence.js.map +1 -1
- package/dist/security/input-sanitization.d.ts.map +1 -1
- package/dist/security/input-sanitization.js +9 -2
- package/dist/security/input-sanitization.js.map +1 -1
- package/dist/selfimprove/fix-gen.js +323 -238
- package/dist/selfimprove/fix-gen.js.map +3 -3
- package/dist/session/engine-ops.d.ts +6 -0
- package/dist/session/engine-ops.d.ts.map +1 -1
- package/dist/session/engine-ops.js +36 -4
- package/dist/session/engine-ops.js.map +1 -1
- package/dist/sessions/briefing.d.ts +17 -0
- package/dist/sessions/briefing.d.ts.map +1 -1
- package/dist/sessions/briefing.js +47 -8
- package/dist/sessions/briefing.js.map +1 -1
- package/dist/sessions/drift-watchdog.d.ts.map +1 -1
- package/dist/sessions/drift-watchdog.js +4 -3
- package/dist/sessions/drift-watchdog.js.map +1 -1
- package/dist/sessions/focus-state-store.d.ts +89 -1
- package/dist/sessions/focus-state-store.d.ts.map +1 -1
- package/dist/sessions/focus-state-store.js +97 -1
- package/dist/sessions/focus-state-store.js.map +1 -1
- package/dist/sessions/index.d.ts +2 -2
- package/dist/sessions/index.d.ts.map +1 -1
- package/dist/sessions/index.js +1 -1
- package/dist/sessions/index.js.map +1 -1
- package/dist/sessions/session-drift.d.ts.map +1 -1
- package/dist/sessions/session-drift.js +4 -3
- package/dist/sessions/session-drift.js.map +1 -1
- package/dist/skills/discovery.d.ts +12 -1
- package/dist/skills/discovery.d.ts.map +1 -1
- package/dist/skills/discovery.js +27 -2
- package/dist/skills/discovery.js.map +1 -1
- package/dist/skills/orchestrator/startup.d.ts.map +1 -1
- package/dist/skills/orchestrator/startup.js +10 -5
- package/dist/skills/orchestrator/startup.js.map +1 -1
- package/dist/skills/prune-bundled.d.ts +267 -0
- package/dist/skills/prune-bundled.d.ts.map +1 -0
- package/dist/skills/prune-bundled.js +536 -0
- package/dist/skills/prune-bundled.js.map +1 -0
- package/dist/skills/types.d.ts +9 -1
- package/dist/skills/types.d.ts.map +1 -1
- package/dist/skills/types.js +17 -34
- package/dist/skills/types.js.map +1 -1
- package/dist/spawn/branch-lock.d.ts.map +1 -1
- package/dist/spawn/branch-lock.js +26 -2
- package/dist/spawn/branch-lock.js.map +1 -1
- package/dist/stats/index.d.ts.map +1 -1
- package/dist/stats/index.js +4 -2
- package/dist/stats/index.js.map +1 -1
- package/dist/store/skills-db.d.ts +17 -0
- package/dist/store/skills-db.d.ts.map +1 -1
- package/dist/store/skills-db.js +30 -0
- package/dist/store/skills-db.js.map +1 -1
- package/dist/store/snapshot-gate.d.ts.map +1 -1
- package/dist/store/snapshot-gate.js +3 -0
- package/dist/store/snapshot-gate.js.map +1 -1
- package/dist/store/sqlite-data-accessor.d.ts.map +1 -1
- package/dist/store/sqlite-data-accessor.js +12 -2
- package/dist/store/sqlite-data-accessor.js.map +1 -1
- package/dist/store/sqlite.d.ts +14 -0
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +26 -4
- package/dist/store/sqlite.js.map +1 -1
- package/dist/store/twin-collapse.d.ts +46 -1
- package/dist/store/twin-collapse.d.ts.map +1 -1
- package/dist/store/twin-collapse.js +131 -5
- package/dist/store/twin-collapse.js.map +1 -1
- package/dist/store/worktree-isolation-guard.d.ts.map +1 -1
- package/dist/store/worktree-isolation-guard.js +4 -2
- package/dist/store/worktree-isolation-guard.js.map +1 -1
- package/dist/system/inject-generate.d.ts.map +1 -1
- package/dist/system/inject-generate.js +4 -4
- package/dist/system/inject-generate.js.map +1 -1
- package/dist/task-work/index.d.ts +6 -0
- package/dist/task-work/index.d.ts.map +1 -1
- package/dist/task-work/index.js +6 -3
- package/dist/task-work/index.js.map +1 -1
- package/dist/tasks/affected-packages.d.ts +57 -12
- package/dist/tasks/affected-packages.d.ts.map +1 -1
- package/dist/tasks/affected-packages.js +138 -31
- package/dist/tasks/affected-packages.js.map +1 -1
- package/dist/tasks/affected-scope.d.ts +59 -0
- package/dist/tasks/affected-scope.d.ts.map +1 -0
- package/dist/tasks/affected-scope.js +81 -0
- package/dist/tasks/affected-scope.js.map +1 -0
- package/dist/tasks/analyze.d.ts.map +1 -1
- package/dist/tasks/analyze.js +7 -2
- package/dist/tasks/analyze.js.map +1 -1
- package/dist/tasks/change-set.d.ts +24 -0
- package/dist/tasks/change-set.d.ts.map +1 -1
- package/dist/tasks/change-set.js +130 -11
- package/dist/tasks/change-set.js.map +1 -1
- package/dist/tasks/complete.d.ts +10 -0
- package/dist/tasks/complete.d.ts.map +1 -1
- package/dist/tasks/complete.js +49 -0
- package/dist/tasks/complete.js.map +1 -1
- package/dist/tasks/component-pr.d.ts +106 -0
- package/dist/tasks/component-pr.d.ts.map +1 -0
- package/dist/tasks/component-pr.js +190 -0
- package/dist/tasks/component-pr.js.map +1 -0
- package/dist/tasks/done-plan.d.ts +21 -1
- package/dist/tasks/done-plan.d.ts.map +1 -1
- package/dist/tasks/done-plan.js +170 -15
- package/dist/tasks/done-plan.js.map +1 -1
- package/dist/tasks/done-record.d.ts.map +1 -1
- package/dist/tasks/done-record.js +9 -80
- package/dist/tasks/done-record.js.map +1 -1
- package/dist/tasks/evidence.d.ts +47 -2
- package/dist/tasks/evidence.d.ts.map +1 -1
- package/dist/tasks/evidence.js +57 -7
- package/dist/tasks/evidence.js.map +1 -1
- package/dist/tasks/task-next.d.ts +4 -0
- package/dist/tasks/task-next.d.ts.map +1 -1
- package/dist/tasks/task-next.js +32 -28
- package/dist/tasks/task-next.js.map +1 -1
- package/dist/validation/engine-ops.d.ts +9 -1
- package/dist/validation/engine-ops.d.ts.map +1 -1
- package/dist/validation/engine-ops.js +24 -1
- package/dist/validation/engine-ops.js.map +1 -1
- package/dist/validation/validate-ops.d.ts.map +1 -1
- package/dist/validation/validate-ops.js +9 -3
- package/dist/validation/validate-ops.js.map +1 -1
- package/dist/worktree/worktree-adopt.d.ts.map +1 -1
- package/dist/worktree/worktree-adopt.js +5 -2
- package/dist/worktree/worktree-adopt.js.map +1 -1
- package/package.json +13 -13
- package/templates/CLEO-INJECTION.md +1 -1
- package/templates/CLEO-REFERENCE.md +1 -1
|
@@ -10533,7 +10533,7 @@ var init_operations_registry = __esm({
|
|
|
10533
10533
|
type: "string",
|
|
10534
10534
|
required: false,
|
|
10535
10535
|
enum: DOCS_LIFECYCLE_STATUSES,
|
|
10536
|
-
description: `Override the new lifecycle status. Defaults to "draft" on every update. Valid: ${DOCS_LIFECYCLE_STATUSES.join("|")}.`
|
|
10536
|
+
description: `Override the new lifecycle status. Defaults to "draft" on every update. Alone (no file or content) it changes only the lifecycle status. Valid: ${DOCS_LIFECYCLE_STATUSES.join("|")}.`
|
|
10537
10537
|
},
|
|
10538
10538
|
{
|
|
10539
10539
|
name: "dryRun",
|
|
@@ -13020,11 +13020,13 @@ var init_evidence_atom_schema = __esm({
|
|
|
13020
13020
|
});
|
|
13021
13021
|
prAtomSchema = z10.object({
|
|
13022
13022
|
kind: z10.literal("pr"),
|
|
13023
|
-
prNumber: z10.number().int().positive("pr atom requires a positive integer PR number")
|
|
13023
|
+
prNumber: z10.number().int().positive("pr atom requires a positive integer PR number"),
|
|
13024
|
+
componentPrNumber: z10.number().int().positive().optional()
|
|
13024
13025
|
});
|
|
13025
13026
|
ciAtomSchema = z10.object({
|
|
13026
13027
|
kind: z10.literal("ci"),
|
|
13027
|
-
prNumber: z10.number().int().positive("ci atom requires a positive integer PR number")
|
|
13028
|
+
prNumber: z10.number().int().positive("ci atom requires a positive integer PR number"),
|
|
13029
|
+
componentPrNumber: z10.number().int().positive().optional()
|
|
13028
13030
|
});
|
|
13029
13031
|
locDropAtomSchema = z10.object({
|
|
13030
13032
|
kind: z10.literal("loc-drop"),
|
|
@@ -18759,6 +18761,22 @@ function linkedWorktreeMainRoot(dir) {
|
|
|
18759
18761
|
if (basename2(commonDir) === ".git") return dirname2(commonDir);
|
|
18760
18762
|
return readCoreWorktree(commonDir);
|
|
18761
18763
|
}
|
|
18764
|
+
function describeWorktreeOwner(dir) {
|
|
18765
|
+
const mainRoot = linkedWorktreeMainRoot(dir);
|
|
18766
|
+
if (mainRoot === null) return "owning repository unknown (unreadable gitlink)";
|
|
18767
|
+
let projectId = null;
|
|
18768
|
+
try {
|
|
18769
|
+
const parsed = JSON.parse(
|
|
18770
|
+
readFileSync2(join3(mainRoot, ".cleo", "project-info.json"), "utf-8")
|
|
18771
|
+
);
|
|
18772
|
+
if (typeof parsed === "object" && parsed !== null && "projectId" in parsed) {
|
|
18773
|
+
const value = parsed.projectId;
|
|
18774
|
+
if (typeof value === "string" && value !== "") projectId = value;
|
|
18775
|
+
}
|
|
18776
|
+
} catch {
|
|
18777
|
+
}
|
|
18778
|
+
return projectId === null ? `owning repository ${mainRoot} (not an initialised CLEO project)` : `owning project ${mainRoot} (projectId ${projectId})`;
|
|
18779
|
+
}
|
|
18762
18780
|
function readCoreWorktree(gitDir) {
|
|
18763
18781
|
try {
|
|
18764
18782
|
const config2 = readFileSync2(join3(gitDir, "config"), "utf-8");
|
|
@@ -25845,9 +25863,10 @@ function assertStorePathIsNotWorktreeResident(role, dbPath) {
|
|
|
25845
25863
|
);
|
|
25846
25864
|
return;
|
|
25847
25865
|
}
|
|
25866
|
+
const owner = describeWorktreeOwner(projectRoot);
|
|
25848
25867
|
throw new CleoError(
|
|
25849
25868
|
8 /* CONFIG_ERROR */,
|
|
25850
|
-
`E_WT_DB_ISOLATION_VIOLATION: refusing to open '${role}' DB at ${dbPath} \u2014 parent ${projectRoot} is a git worktree (gitlink). DBs must open against the canonical project root.`,
|
|
25869
|
+
`E_WT_DB_ISOLATION_VIOLATION: refusing to open '${role}' DB at ${dbPath} \u2014 parent ${projectRoot} is a git worktree (gitlink); ${owner}. DBs must open against the canonical project root.`,
|
|
25851
25870
|
{
|
|
25852
25871
|
fix: `Run from the canonical project root, or make sure the worktree's main repository is an initialised CLEO project so it resolves there. Inspect stranded worktree stores with \`cleo doctor worktree-stores\`. Emergency override (audited): CLEO_ALLOW_WORKTREE_DB_CREATE=1.`
|
|
25853
25872
|
}
|
|
@@ -32434,216 +32453,6 @@ var init_memory_schema = __esm({
|
|
|
32434
32453
|
}
|
|
32435
32454
|
});
|
|
32436
32455
|
|
|
32437
|
-
// packages/core/src/store/snapshot-gate.ts
|
|
32438
|
-
import { join as join20 } from "node:path";
|
|
32439
|
-
function readGateState(db) {
|
|
32440
|
-
const row = db.prepare("SELECT value FROM tasks_schema_meta WHERE key = ?").get(SNAPSHOT_GATE_META_KEY);
|
|
32441
|
-
const state = { generation: 0, prefixes: {} };
|
|
32442
|
-
if (!row) return state;
|
|
32443
|
-
try {
|
|
32444
|
-
const parsed = JSON.parse(row.value);
|
|
32445
|
-
if (parsed === null || typeof parsed !== "object") return state;
|
|
32446
|
-
if (typeof parsed.generation === "number" && Number.isSafeInteger(parsed.generation)) {
|
|
32447
|
-
state.generation = parsed.generation;
|
|
32448
|
-
}
|
|
32449
|
-
for (const [prefix, e] of Object.entries(parsed.prefixes ?? {})) {
|
|
32450
|
-
if (e && typeof e.generation === "number" && typeof e.startedAt === "number" && typeof e.completedAt === "number") {
|
|
32451
|
-
state.prefixes[prefix] = {
|
|
32452
|
-
generation: e.generation,
|
|
32453
|
-
startedAt: e.startedAt,
|
|
32454
|
-
completedAt: e.completedAt
|
|
32455
|
-
};
|
|
32456
|
-
}
|
|
32457
|
-
}
|
|
32458
|
-
} catch {
|
|
32459
|
-
}
|
|
32460
|
-
return state;
|
|
32461
|
-
}
|
|
32462
|
-
function writeGateState(db, state) {
|
|
32463
|
-
db.prepare(
|
|
32464
|
-
"INSERT INTO tasks_schema_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value"
|
|
32465
|
-
).run(SNAPSHOT_GATE_META_KEY, JSON.stringify(state));
|
|
32466
|
-
}
|
|
32467
|
-
function tryReadGateState(db) {
|
|
32468
|
-
if (!db) return null;
|
|
32469
|
-
try {
|
|
32470
|
-
return readGateState(db);
|
|
32471
|
-
} catch {
|
|
32472
|
-
return null;
|
|
32473
|
-
}
|
|
32474
|
-
}
|
|
32475
|
-
function needsSnapshot(entry, mode, nowMs, seenGeneration) {
|
|
32476
|
-
if (!entry) return true;
|
|
32477
|
-
if (mode === "required") return seenGeneration === null || entry.generation <= seenGeneration;
|
|
32478
|
-
const elapsed = nowMs - entry.startedAt;
|
|
32479
|
-
return elapsed < 0 || elapsed >= SNAPSHOT_DEBOUNCE_MS;
|
|
32480
|
-
}
|
|
32481
|
-
async function runGatedSnapshot(opts, snapshot) {
|
|
32482
|
-
const now = opts.now ?? Date.now;
|
|
32483
|
-
const mode = opts.mode ?? "routine";
|
|
32484
|
-
const db = opts.stateDb;
|
|
32485
|
-
const none = (skipped, error48) => ({
|
|
32486
|
-
snapshotted: [],
|
|
32487
|
-
linked: [],
|
|
32488
|
-
absent: [],
|
|
32489
|
-
failed: [],
|
|
32490
|
-
skipped,
|
|
32491
|
-
...error48 !== void 0 && { error: error48 }
|
|
32492
|
-
});
|
|
32493
|
-
const entryState = tryReadGateState(db);
|
|
32494
|
-
const seenGeneration = mode === "required" ? opts.seenGeneration ?? entryState?.generation ?? null : null;
|
|
32495
|
-
if (mode === "routine") {
|
|
32496
|
-
if (!entryState) return none("state-unavailable");
|
|
32497
|
-
const nowMs = now();
|
|
32498
|
-
if (!opts.prefixes.some((p) => needsSnapshot(entryState.prefixes[p], mode, nowMs, null))) {
|
|
32499
|
-
return none("debounced");
|
|
32500
|
-
}
|
|
32501
|
-
}
|
|
32502
|
-
const lockPath2 = join20(opts.backupDir, GATE_LOCK_BASENAME);
|
|
32503
|
-
let release;
|
|
32504
|
-
try {
|
|
32505
|
-
release = await acquireLock(lockPath2, {
|
|
32506
|
-
retries: mode === "required" ? opts.lockWaitRetries ?? SNAPSHOT_LOCK_WAIT_RETRIES : 0,
|
|
32507
|
-
stale: SNAPSHOT_LOCK_STALE_MS,
|
|
32508
|
-
// A compromised lock (e.g. the backup dir was removed) must not throw
|
|
32509
|
-
// from a timer and crash the host process.
|
|
32510
|
-
onCompromised: () => {
|
|
32511
|
-
}
|
|
32512
|
-
});
|
|
32513
|
-
} catch {
|
|
32514
|
-
if (mode === "routine") return none("in-flight");
|
|
32515
|
-
return none(
|
|
32516
|
-
"lock-timeout",
|
|
32517
|
-
`Snapshot lock ${lockPath2}.lock is still held after waiting. Another snapshot is running, or a crashed process left the lock (it expires after ${SNAPSHOT_LOCK_STALE_MS / 6e4} minutes). Remove it if no snapshot is running.`
|
|
32518
|
-
);
|
|
32519
|
-
}
|
|
32520
|
-
try {
|
|
32521
|
-
try {
|
|
32522
|
-
opts.onLockAcquired?.();
|
|
32523
|
-
} catch {
|
|
32524
|
-
}
|
|
32525
|
-
const state = tryReadGateState(db);
|
|
32526
|
-
if (!state && mode === "routine") return none("state-unavailable");
|
|
32527
|
-
const startMs = now();
|
|
32528
|
-
const admitted = mode === "required" && opts.alwaysSnapshot === true ? [...opts.prefixes] : opts.prefixes.filter(
|
|
32529
|
-
(p) => needsSnapshot(state?.prefixes[p], mode, startMs, seenGeneration)
|
|
32530
|
-
);
|
|
32531
|
-
if (admitted.length === 0) return none(mode === "routine" ? "debounced" : "covered");
|
|
32532
|
-
const generation = (state?.generation ?? 0) + 1;
|
|
32533
|
-
let claimed = false;
|
|
32534
|
-
if (db && state) {
|
|
32535
|
-
try {
|
|
32536
|
-
writeGateState(db, { ...state, generation });
|
|
32537
|
-
claimed = true;
|
|
32538
|
-
} catch {
|
|
32539
|
-
}
|
|
32540
|
-
}
|
|
32541
|
-
const snapshotted = [];
|
|
32542
|
-
const linked = [];
|
|
32543
|
-
const absent = [];
|
|
32544
|
-
const failed = [];
|
|
32545
|
-
for (const prefix of admitted) {
|
|
32546
|
-
try {
|
|
32547
|
-
const outcome = await snapshot(prefix);
|
|
32548
|
-
if (outcome === "absent") absent.push(prefix);
|
|
32549
|
-
else {
|
|
32550
|
-
snapshotted.push(prefix);
|
|
32551
|
-
if (outcome === "linked") linked.push(prefix);
|
|
32552
|
-
}
|
|
32553
|
-
} catch {
|
|
32554
|
-
failed.push(prefix);
|
|
32555
|
-
}
|
|
32556
|
-
}
|
|
32557
|
-
const satisfied = [...snapshotted, ...absent];
|
|
32558
|
-
if (db && claimed && satisfied.length > 0) {
|
|
32559
|
-
try {
|
|
32560
|
-
const latest = readGateState(db);
|
|
32561
|
-
const completedAt = now();
|
|
32562
|
-
latest.generation = Math.max(latest.generation, generation);
|
|
32563
|
-
for (const p of satisfied) {
|
|
32564
|
-
latest.prefixes[p] = { generation, startedAt: startMs, completedAt };
|
|
32565
|
-
}
|
|
32566
|
-
writeGateState(db, latest);
|
|
32567
|
-
} catch {
|
|
32568
|
-
}
|
|
32569
|
-
}
|
|
32570
|
-
return { snapshotted, linked, absent, failed, skipped: null };
|
|
32571
|
-
} finally {
|
|
32572
|
-
await release().catch(() => {
|
|
32573
|
-
});
|
|
32574
|
-
}
|
|
32575
|
-
}
|
|
32576
|
-
function isValidStamp(ymd, hms) {
|
|
32577
|
-
const y = Number(ymd.slice(0, 4));
|
|
32578
|
-
const mo = Number(ymd.slice(4, 6));
|
|
32579
|
-
const d = Number(ymd.slice(6, 8));
|
|
32580
|
-
const h = Number(hms.slice(0, 2));
|
|
32581
|
-
const mi = Number(hms.slice(2, 4));
|
|
32582
|
-
const se = Number(hms.slice(4, 6));
|
|
32583
|
-
if (mo < 1 || mo > 12 || d < 1 || h > 23 || mi > 59 || se > 59) return false;
|
|
32584
|
-
const probe = new Date(Date.UTC(y, mo - 1, d));
|
|
32585
|
-
return probe.getUTCFullYear() === y && probe.getUTCMonth() === mo - 1 && probe.getUTCDate() === d;
|
|
32586
|
-
}
|
|
32587
|
-
function selectSnapshotsToKeep(names, ctx = {}) {
|
|
32588
|
-
const policy = ctx.policy ?? DEFAULT_SNAPSHOT_RETENTION;
|
|
32589
|
-
const keep = /* @__PURE__ */ new Set();
|
|
32590
|
-
const stamped = [];
|
|
32591
|
-
for (const name15 of names) {
|
|
32592
|
-
const m = STAMP_PATTERN.exec(name15);
|
|
32593
|
-
const ymd = m?.[1];
|
|
32594
|
-
const hms = m?.[2];
|
|
32595
|
-
if (!ymd || !hms || !isValidStamp(ymd, hms)) {
|
|
32596
|
-
keep.add(name15);
|
|
32597
|
-
continue;
|
|
32598
|
-
}
|
|
32599
|
-
const stamp = `${ymd}-${hms}`;
|
|
32600
|
-
if (ctx.nowStamp !== void 0 && stamp > ctx.nowStamp) {
|
|
32601
|
-
keep.add(name15);
|
|
32602
|
-
continue;
|
|
32603
|
-
}
|
|
32604
|
-
stamped.push({ name: name15, stamp });
|
|
32605
|
-
}
|
|
32606
|
-
if (ctx.pinned !== void 0 && names.includes(ctx.pinned)) keep.add(ctx.pinned);
|
|
32607
|
-
stamped.sort((a, b) => a.stamp < b.stamp ? 1 : a.stamp > b.stamp ? -1 : 0);
|
|
32608
|
-
for (const s of stamped.slice(0, policy.latest)) keep.add(s.name);
|
|
32609
|
-
const keepNewestPerBucket = (bucketOf, slots) => {
|
|
32610
|
-
const seen = /* @__PURE__ */ new Set();
|
|
32611
|
-
for (const s of stamped) {
|
|
32612
|
-
const bucket = bucketOf(s.stamp);
|
|
32613
|
-
if (seen.has(bucket)) continue;
|
|
32614
|
-
if (seen.size >= slots) break;
|
|
32615
|
-
seen.add(bucket);
|
|
32616
|
-
keep.add(s.name);
|
|
32617
|
-
}
|
|
32618
|
-
};
|
|
32619
|
-
keepNewestPerBucket(
|
|
32620
|
-
(stamp) => `${stamp.slice(0, 11)}q${Math.floor(Number(stamp.slice(11, 13)) / 15)}`,
|
|
32621
|
-
policy.quarterHourly
|
|
32622
|
-
);
|
|
32623
|
-
keepNewestPerBucket((stamp) => stamp.slice(0, 11), policy.hourly);
|
|
32624
|
-
keepNewestPerBucket((stamp) => stamp.slice(0, 8), policy.daily);
|
|
32625
|
-
return keep;
|
|
32626
|
-
}
|
|
32627
|
-
var SNAPSHOT_DEBOUNCE_MS, SNAPSHOT_LOCK_STALE_MS, SNAPSHOT_LOCK_WAIT_RETRIES, SNAPSHOT_GATE_META_KEY, GATE_LOCK_BASENAME, DEFAULT_SNAPSHOT_RETENTION, STAMP_PATTERN;
|
|
32628
|
-
var init_snapshot_gate = __esm({
|
|
32629
|
-
"packages/core/src/store/snapshot-gate.ts"() {
|
|
32630
|
-
"use strict";
|
|
32631
|
-
init_lock();
|
|
32632
|
-
SNAPSHOT_DEBOUNCE_MS = 5 * 6e4;
|
|
32633
|
-
SNAPSHOT_LOCK_STALE_MS = 10 * 6e4;
|
|
32634
|
-
SNAPSHOT_LOCK_WAIT_RETRIES = 90;
|
|
32635
|
-
SNAPSHOT_GATE_META_KEY = "sqlite_snapshot_gate";
|
|
32636
|
-
GATE_LOCK_BASENAME = ".snapshot-gate";
|
|
32637
|
-
DEFAULT_SNAPSHOT_RETENTION = {
|
|
32638
|
-
latest: 1,
|
|
32639
|
-
quarterHourly: 3,
|
|
32640
|
-
hourly: 3,
|
|
32641
|
-
daily: 3
|
|
32642
|
-
};
|
|
32643
|
-
STAMP_PATTERN = /-(\d{8})-(\d{6})\.db$/;
|
|
32644
|
-
}
|
|
32645
|
-
});
|
|
32646
|
-
|
|
32647
32456
|
// packages/core/src/store/twin-collapse.ts
|
|
32648
32457
|
import { createHash as createHash3 } from "node:crypto";
|
|
32649
32458
|
function hasMainTable(db, name15) {
|
|
@@ -32720,6 +32529,22 @@ function mergeSchemaMetaValue(key, bare, twin) {
|
|
|
32720
32529
|
const field = SCHEMA_META_COUNTER_FIELDS[key];
|
|
32721
32530
|
return field === void 0 ? bare : withMaxCounter(bare, twin, field);
|
|
32722
32531
|
}
|
|
32532
|
+
function mirrorCounterToBare(db, key) {
|
|
32533
|
+
if (SCHEMA_META_COUNTER_FIELDS[key] === void 0) return;
|
|
32534
|
+
if (!hasMainTable(db, "schema_meta")) return;
|
|
32535
|
+
const twin = readKv(db, "tasks_schema_meta", key);
|
|
32536
|
+
if (twin === void 0) return;
|
|
32537
|
+
const bare = readKv(db, "schema_meta", key);
|
|
32538
|
+
const next = bare === void 0 ? twin : mergeSchemaMetaValue(key, bare, twin);
|
|
32539
|
+
if (next === null || next === bare) return;
|
|
32540
|
+
writeKv(db, "schema_meta", key, next);
|
|
32541
|
+
}
|
|
32542
|
+
function bareCounterOf(db, key) {
|
|
32543
|
+
const name15 = SCHEMA_META_COUNTER_FIELDS[key];
|
|
32544
|
+
if (name15 === void 0 || !hasMainTable(db, "schema_meta")) return 0;
|
|
32545
|
+
const bare = readKv(db, "schema_meta", key);
|
|
32546
|
+
return (bare === void 0 ? void 0 : counterOf(bare, name15)) ?? 0;
|
|
32547
|
+
}
|
|
32723
32548
|
function kvRows(db, table, schema = "main") {
|
|
32724
32549
|
const rows = db.prepare(`SELECT key, value FROM ${schema}.${table}`).all();
|
|
32725
32550
|
return new Map(rows.map((r) => [r.key, r.value]));
|
|
@@ -32852,6 +32677,39 @@ function applyStickyRows(db, schema, plan) {
|
|
|
32852
32677
|
}
|
|
32853
32678
|
return { inserted, replaced: 0, deleted };
|
|
32854
32679
|
}
|
|
32680
|
+
function tagsOf(tagsJson) {
|
|
32681
|
+
if (!tagsJson) return [];
|
|
32682
|
+
try {
|
|
32683
|
+
const parsed = JSON.parse(tagsJson);
|
|
32684
|
+
if (!Array.isArray(parsed)) return [];
|
|
32685
|
+
return [...new Set(parsed.filter((t) => typeof t === "string" && t.length > 0))];
|
|
32686
|
+
} catch {
|
|
32687
|
+
return [];
|
|
32688
|
+
}
|
|
32689
|
+
}
|
|
32690
|
+
function syncStickyJunction(db, schema) {
|
|
32691
|
+
const notes = db.prepare("SELECT id, tags_json FROM main.brain_sticky_notes").all();
|
|
32692
|
+
const have = /* @__PURE__ */ new Map();
|
|
32693
|
+
for (const r of db.prepare(`SELECT sticky_id, tag FROM ${schema}.brain_sticky_tags`).all()) {
|
|
32694
|
+
const set2 = have.get(r.sticky_id) ?? /* @__PURE__ */ new Set();
|
|
32695
|
+
set2.add(r.tag);
|
|
32696
|
+
have.set(r.sticky_id, set2);
|
|
32697
|
+
}
|
|
32698
|
+
const insert = db.prepare(
|
|
32699
|
+
`INSERT OR IGNORE INTO ${schema}.brain_sticky_tags (sticky_id, tag) VALUES (?, ?)`
|
|
32700
|
+
);
|
|
32701
|
+
const del = db.prepare(`DELETE FROM ${schema}.brain_sticky_tags WHERE sticky_id = ? AND tag = ?`);
|
|
32702
|
+
let inserted = 0;
|
|
32703
|
+
let deleted = 0;
|
|
32704
|
+
for (const note of notes) {
|
|
32705
|
+
const want = new Set(tagsOf(note.tags_json));
|
|
32706
|
+
const current = have.get(note.id) ?? /* @__PURE__ */ new Set();
|
|
32707
|
+
for (const tag of want)
|
|
32708
|
+
if (!current.has(tag)) inserted += Number(insert.run(note.id, tag).changes);
|
|
32709
|
+
for (const tag of current) if (!want.has(tag)) deleted += Number(del.run(note.id, tag).changes);
|
|
32710
|
+
}
|
|
32711
|
+
return { inserted, deleted };
|
|
32712
|
+
}
|
|
32855
32713
|
function twinCollapseFailureOf(db) {
|
|
32856
32714
|
return degraded.get(db);
|
|
32857
32715
|
}
|
|
@@ -33061,7 +32919,7 @@ function collapseTwinTables(nativeDb, dbPath, options = {}) {
|
|
|
33061
32919
|
}
|
|
33062
32920
|
return inOrder();
|
|
33063
32921
|
}
|
|
33064
|
-
var log6, TWIN_COLLAPSE_MARKER_PREFIX, TWIN_COLLAPSE_FAILURE_PREFIX, SCHEMA_META_COUNTER_FIELDS, SCHEMA_META_NEVER_DELETED, MAX_CONFLICTS, sha, ABSENT, hashMap, emptyPlan, SCHEMA_META, hashSets, EMPTY_SET, STICKY_TAGS, PAIRS, degraded, SHADOW_WRITE_REFUSED, SHADOW_WRITE_OPS;
|
|
32922
|
+
var log6, TWIN_COLLAPSE_MARKER_PREFIX, TWIN_COLLAPSE_FAILURE_PREFIX, SNAPSHOT_GATE_KEY, SCHEMA_META_COUNTER_FIELDS, SCHEMA_META_NEVER_DELETED, MAX_CONFLICTS, sha, ABSENT, hashMap, emptyPlan, SCHEMA_META, hashSets, EMPTY_SET, STICKY_TAGS, PAIRS, degraded, SHADOW_WRITE_REFUSED, SHADOW_WRITE_OPS;
|
|
33065
32923
|
var init_twin_collapse = __esm({
|
|
33066
32924
|
"packages/core/src/store/twin-collapse.ts"() {
|
|
33067
32925
|
"use strict";
|
|
@@ -33069,18 +32927,18 @@ var init_twin_collapse = __esm({
|
|
|
33069
32927
|
init_errors2();
|
|
33070
32928
|
init_logger2();
|
|
33071
32929
|
init_pre_repair_snapshot();
|
|
33072
|
-
init_snapshot_gate();
|
|
33073
32930
|
log6 = getLogger("twin-collapse");
|
|
33074
32931
|
TWIN_COLLAPSE_MARKER_PREFIX = "twin_collapse:";
|
|
33075
32932
|
TWIN_COLLAPSE_FAILURE_PREFIX = "twin_collapse_failed:";
|
|
32933
|
+
SNAPSHOT_GATE_KEY = "sqlite_snapshot_gate";
|
|
33076
32934
|
SCHEMA_META_COUNTER_FIELDS = {
|
|
33077
32935
|
task_id_sequence: "counter",
|
|
33078
|
-
[
|
|
32936
|
+
[SNAPSHOT_GATE_KEY]: "generation",
|
|
33079
32937
|
file_meta: "generation"
|
|
33080
32938
|
};
|
|
33081
32939
|
SCHEMA_META_NEVER_DELETED = /* @__PURE__ */ new Set([
|
|
33082
32940
|
"task_id_sequence",
|
|
33083
|
-
|
|
32941
|
+
SNAPSHOT_GATE_KEY
|
|
33084
32942
|
]);
|
|
33085
32943
|
MAX_CONFLICTS = 50;
|
|
33086
32944
|
sha = (value) => createHash3("sha256").update(value).digest("hex");
|
|
@@ -33145,7 +33003,12 @@ var init_twin_collapse = __esm({
|
|
|
33145
33003
|
throw new Error(
|
|
33146
33004
|
`sticky_tags collapse did not verify: ${missing} missing, ${lingering} not removed`
|
|
33147
33005
|
);
|
|
33148
|
-
|
|
33006
|
+
const synced = syncStickyJunction(db, "main");
|
|
33007
|
+
return {
|
|
33008
|
+
inserted: counts.inserted + synced.inserted,
|
|
33009
|
+
replaced: 0,
|
|
33010
|
+
deleted: counts.deleted + synced.deleted
|
|
33011
|
+
};
|
|
33149
33012
|
},
|
|
33150
33013
|
shadow(db, plan) {
|
|
33151
33014
|
db.exec(
|
|
@@ -33156,6 +33019,7 @@ var init_twin_collapse = __esm({
|
|
|
33156
33019
|
"INSERT INTO temp.brain_sticky_tags (sticky_id, tag) SELECT sticky_id, tag FROM main.brain_sticky_tags"
|
|
33157
33020
|
);
|
|
33158
33021
|
applyStickyRows(db, "temp", plan);
|
|
33022
|
+
syncStickyJunction(db, "temp");
|
|
33159
33023
|
}
|
|
33160
33024
|
};
|
|
33161
33025
|
PAIRS = [SCHEMA_META, STICKY_TAGS];
|
|
@@ -38630,9 +38494,12 @@ async function writeMetaValue(cwd, key, value) {
|
|
|
38630
38494
|
target: tasksSchemaMeta.key,
|
|
38631
38495
|
set: { value: json2 }
|
|
38632
38496
|
}).run();
|
|
38497
|
+
const native = getNativeTasksDb(cwd);
|
|
38498
|
+
if (native) mirrorCounterToBare(native, key);
|
|
38633
38499
|
}
|
|
38634
38500
|
function advanceTaskIdSequence(nativeDb, floor) {
|
|
38635
38501
|
assertTwinCollapseWritable(nativeDb);
|
|
38502
|
+
const lowest = Math.max(floor, bareCounterOf(nativeDb, "task_id_sequence"));
|
|
38636
38503
|
nativeDb.prepare(`
|
|
38637
38504
|
UPDATE tasks_schema_meta
|
|
38638
38505
|
SET value = json_set(value,
|
|
@@ -38641,7 +38508,8 @@ function advanceTaskIdSequence(nativeDb, floor) {
|
|
|
38641
38508
|
'$.checksum', 'alloc-' || strftime('%s','now')
|
|
38642
38509
|
)
|
|
38643
38510
|
WHERE key = 'task_id_sequence'
|
|
38644
|
-
`).run(
|
|
38511
|
+
`).run(lowest, lowest);
|
|
38512
|
+
mirrorCounterToBare(nativeDb, "task_id_sequence");
|
|
38645
38513
|
const row = nativeDb.prepare(`
|
|
38646
38514
|
SELECT json_extract(value, '$.counter') AS counter
|
|
38647
38515
|
FROM tasks_schema_meta WHERE key = 'task_id_sequence'
|
|
@@ -39685,12 +39553,12 @@ var init_sqlite_data_accessor = __esm({
|
|
|
39685
39553
|
|
|
39686
39554
|
// packages/core/src/sequence/index.ts
|
|
39687
39555
|
import { existsSync as existsSync17, readFileSync as readFileSync5, renameSync as renameSync5 } from "node:fs";
|
|
39688
|
-
import { join as
|
|
39556
|
+
import { join as join20 } from "node:path";
|
|
39689
39557
|
function getLegacySequenceJsonPath(cwd) {
|
|
39690
|
-
return
|
|
39558
|
+
return join20(resolveOrCwd(cwd), ".cleo", ".sequence.json");
|
|
39691
39559
|
}
|
|
39692
39560
|
function getLegacySequencePath(cwd) {
|
|
39693
|
-
return
|
|
39561
|
+
return join20(resolveOrCwd(cwd), ".cleo", ".sequence");
|
|
39694
39562
|
}
|
|
39695
39563
|
function isValidSequenceState(value) {
|
|
39696
39564
|
if (!value || typeof value !== "object") return false;
|
|
@@ -39865,9 +39733,9 @@ var init_sequence = __esm({
|
|
|
39865
39733
|
});
|
|
39866
39734
|
|
|
39867
39735
|
// packages/core/src/store/checkpoint-git-dir.ts
|
|
39868
|
-
import { join as
|
|
39736
|
+
import { join as join21 } from "node:path";
|
|
39869
39737
|
function checkpointGitDir(cleoDir) {
|
|
39870
|
-
return
|
|
39738
|
+
return join21(cleoDir, CHECKPOINT_GIT_DIRNAME);
|
|
39871
39739
|
}
|
|
39872
39740
|
var CHECKPOINT_GIT_DIRNAME;
|
|
39873
39741
|
var init_checkpoint_git_dir = __esm({
|
|
@@ -39881,7 +39749,7 @@ var init_checkpoint_git_dir = __esm({
|
|
|
39881
39749
|
import { execFile as execFile2 } from "node:child_process";
|
|
39882
39750
|
import { existsSync as existsSync18 } from "node:fs";
|
|
39883
39751
|
import { readFile as readFile4, writeFile } from "node:fs/promises";
|
|
39884
|
-
import { join as
|
|
39752
|
+
import { join as join22, resolve as resolve8 } from "node:path";
|
|
39885
39753
|
import { promisify as promisify2 } from "node:util";
|
|
39886
39754
|
function makeCleoGitEnv(cleoDir) {
|
|
39887
39755
|
const abs = resolve8(cleoDir);
|
|
@@ -39905,7 +39773,7 @@ async function cleoGitCommand(args, cleoDir) {
|
|
|
39905
39773
|
}
|
|
39906
39774
|
}
|
|
39907
39775
|
function isCleoGitInitialized(cleoDir) {
|
|
39908
|
-
return existsSync18(
|
|
39776
|
+
return existsSync18(join22(checkpointGitDir(cleoDir), "HEAD"));
|
|
39909
39777
|
}
|
|
39910
39778
|
async function loadStateFileAllowlist(cwd) {
|
|
39911
39779
|
try {
|
|
@@ -39949,25 +39817,25 @@ async function isCleoGitRepo(cleoDir) {
|
|
|
39949
39817
|
return result.success && (result.stdout === "true" || isCleoGitInitialized(cleoDir));
|
|
39950
39818
|
}
|
|
39951
39819
|
function isMergeInProgress(cleoDir) {
|
|
39952
|
-
return existsSync18(
|
|
39820
|
+
return existsSync18(join22(checkpointGitDir(cleoDir), "MERGE_HEAD"));
|
|
39953
39821
|
}
|
|
39954
39822
|
async function isDetachedHead(cleoDir) {
|
|
39955
39823
|
const result = await cleoGitCommand(["symbolic-ref", "HEAD"], cleoDir);
|
|
39956
39824
|
return !result.success;
|
|
39957
39825
|
}
|
|
39958
39826
|
function isRebaseInProgress(cleoDir) {
|
|
39959
|
-
return existsSync18(
|
|
39827
|
+
return existsSync18(join22(checkpointGitDir(cleoDir), "rebase-merge")) || existsSync18(join22(checkpointGitDir(cleoDir), "rebase-apply"));
|
|
39960
39828
|
}
|
|
39961
39829
|
async function recordCheckpointTime(cleoDir) {
|
|
39962
39830
|
try {
|
|
39963
|
-
const stateFile =
|
|
39831
|
+
const stateFile = join22(cleoDir, CHECKPOINT_STATE_FILE);
|
|
39964
39832
|
await writeFile(stateFile, String(Math.floor(Date.now() / 1e3)));
|
|
39965
39833
|
} catch {
|
|
39966
39834
|
}
|
|
39967
39835
|
}
|
|
39968
39836
|
async function getLastCheckpointTime(cleoDir) {
|
|
39969
39837
|
try {
|
|
39970
|
-
const stateFile =
|
|
39838
|
+
const stateFile = join22(cleoDir, CHECKPOINT_STATE_FILE);
|
|
39971
39839
|
const content = await readFile4(stateFile, "utf-8");
|
|
39972
39840
|
const epoch = parseInt(content.trim(), 10);
|
|
39973
39841
|
return Number.isNaN(epoch) ? 0 : epoch;
|
|
@@ -39979,7 +39847,7 @@ async function getChangedStateFiles(cleoDir, cwd) {
|
|
|
39979
39847
|
const changed = [];
|
|
39980
39848
|
const allStateFiles = await getAllStateFiles(cwd);
|
|
39981
39849
|
for (const stateFile of allStateFiles) {
|
|
39982
|
-
const fullPath =
|
|
39850
|
+
const fullPath = join22(cleoDir, stateFile);
|
|
39983
39851
|
if (!existsSync18(fullPath)) continue;
|
|
39984
39852
|
const diffResult = await cleoGitCommand(["diff", "--quiet", "--", stateFile], cleoDir);
|
|
39985
39853
|
const cachedResult = await cleoGitCommand(
|
|
@@ -40080,6 +39948,218 @@ var init_git_checkpoint = __esm({
|
|
|
40080
39948
|
}
|
|
40081
39949
|
});
|
|
40082
39950
|
|
|
39951
|
+
// packages/core/src/store/snapshot-gate.ts
|
|
39952
|
+
import { join as join23 } from "node:path";
|
|
39953
|
+
function readGateState(db) {
|
|
39954
|
+
const row = db.prepare("SELECT value FROM tasks_schema_meta WHERE key = ?").get(SNAPSHOT_GATE_META_KEY);
|
|
39955
|
+
const state = { generation: 0, prefixes: {} };
|
|
39956
|
+
if (!row) return state;
|
|
39957
|
+
try {
|
|
39958
|
+
const parsed = JSON.parse(row.value);
|
|
39959
|
+
if (parsed === null || typeof parsed !== "object") return state;
|
|
39960
|
+
if (typeof parsed.generation === "number" && Number.isSafeInteger(parsed.generation)) {
|
|
39961
|
+
state.generation = parsed.generation;
|
|
39962
|
+
}
|
|
39963
|
+
for (const [prefix, e] of Object.entries(parsed.prefixes ?? {})) {
|
|
39964
|
+
if (e && typeof e.generation === "number" && typeof e.startedAt === "number" && typeof e.completedAt === "number") {
|
|
39965
|
+
state.prefixes[prefix] = {
|
|
39966
|
+
generation: e.generation,
|
|
39967
|
+
startedAt: e.startedAt,
|
|
39968
|
+
completedAt: e.completedAt
|
|
39969
|
+
};
|
|
39970
|
+
}
|
|
39971
|
+
}
|
|
39972
|
+
} catch {
|
|
39973
|
+
}
|
|
39974
|
+
return state;
|
|
39975
|
+
}
|
|
39976
|
+
function writeGateState(db, state) {
|
|
39977
|
+
db.prepare(
|
|
39978
|
+
"INSERT INTO tasks_schema_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value"
|
|
39979
|
+
).run(SNAPSHOT_GATE_META_KEY, JSON.stringify(state));
|
|
39980
|
+
mirrorCounterToBare(db, SNAPSHOT_GATE_META_KEY);
|
|
39981
|
+
}
|
|
39982
|
+
function tryReadGateState(db) {
|
|
39983
|
+
if (!db) return null;
|
|
39984
|
+
try {
|
|
39985
|
+
return readGateState(db);
|
|
39986
|
+
} catch {
|
|
39987
|
+
return null;
|
|
39988
|
+
}
|
|
39989
|
+
}
|
|
39990
|
+
function needsSnapshot(entry, mode, nowMs, seenGeneration) {
|
|
39991
|
+
if (!entry) return true;
|
|
39992
|
+
if (mode === "required") return seenGeneration === null || entry.generation <= seenGeneration;
|
|
39993
|
+
const elapsed = nowMs - entry.startedAt;
|
|
39994
|
+
return elapsed < 0 || elapsed >= SNAPSHOT_DEBOUNCE_MS;
|
|
39995
|
+
}
|
|
39996
|
+
async function runGatedSnapshot(opts, snapshot) {
|
|
39997
|
+
const now = opts.now ?? Date.now;
|
|
39998
|
+
const mode = opts.mode ?? "routine";
|
|
39999
|
+
const db = opts.stateDb;
|
|
40000
|
+
const none = (skipped, error48) => ({
|
|
40001
|
+
snapshotted: [],
|
|
40002
|
+
linked: [],
|
|
40003
|
+
absent: [],
|
|
40004
|
+
failed: [],
|
|
40005
|
+
skipped,
|
|
40006
|
+
...error48 !== void 0 && { error: error48 }
|
|
40007
|
+
});
|
|
40008
|
+
const entryState = tryReadGateState(db);
|
|
40009
|
+
const seenGeneration = mode === "required" ? opts.seenGeneration ?? entryState?.generation ?? null : null;
|
|
40010
|
+
if (mode === "routine") {
|
|
40011
|
+
if (!entryState) return none("state-unavailable");
|
|
40012
|
+
const nowMs = now();
|
|
40013
|
+
if (!opts.prefixes.some((p) => needsSnapshot(entryState.prefixes[p], mode, nowMs, null))) {
|
|
40014
|
+
return none("debounced");
|
|
40015
|
+
}
|
|
40016
|
+
}
|
|
40017
|
+
const lockPath2 = join23(opts.backupDir, GATE_LOCK_BASENAME);
|
|
40018
|
+
let release;
|
|
40019
|
+
try {
|
|
40020
|
+
release = await acquireLock(lockPath2, {
|
|
40021
|
+
retries: mode === "required" ? opts.lockWaitRetries ?? SNAPSHOT_LOCK_WAIT_RETRIES : 0,
|
|
40022
|
+
stale: SNAPSHOT_LOCK_STALE_MS,
|
|
40023
|
+
// A compromised lock (e.g. the backup dir was removed) must not throw
|
|
40024
|
+
// from a timer and crash the host process.
|
|
40025
|
+
onCompromised: () => {
|
|
40026
|
+
}
|
|
40027
|
+
});
|
|
40028
|
+
} catch {
|
|
40029
|
+
if (mode === "routine") return none("in-flight");
|
|
40030
|
+
return none(
|
|
40031
|
+
"lock-timeout",
|
|
40032
|
+
`Snapshot lock ${lockPath2}.lock is still held after waiting. Another snapshot is running, or a crashed process left the lock (it expires after ${SNAPSHOT_LOCK_STALE_MS / 6e4} minutes). Remove it if no snapshot is running.`
|
|
40033
|
+
);
|
|
40034
|
+
}
|
|
40035
|
+
try {
|
|
40036
|
+
try {
|
|
40037
|
+
opts.onLockAcquired?.();
|
|
40038
|
+
} catch {
|
|
40039
|
+
}
|
|
40040
|
+
const state = tryReadGateState(db);
|
|
40041
|
+
if (!state && mode === "routine") return none("state-unavailable");
|
|
40042
|
+
const startMs = now();
|
|
40043
|
+
const admitted = mode === "required" && opts.alwaysSnapshot === true ? [...opts.prefixes] : opts.prefixes.filter(
|
|
40044
|
+
(p) => needsSnapshot(state?.prefixes[p], mode, startMs, seenGeneration)
|
|
40045
|
+
);
|
|
40046
|
+
if (admitted.length === 0) return none(mode === "routine" ? "debounced" : "covered");
|
|
40047
|
+
const generation = (state?.generation ?? 0) + 1;
|
|
40048
|
+
let claimed = false;
|
|
40049
|
+
if (db && state) {
|
|
40050
|
+
try {
|
|
40051
|
+
writeGateState(db, { ...state, generation });
|
|
40052
|
+
claimed = true;
|
|
40053
|
+
} catch {
|
|
40054
|
+
}
|
|
40055
|
+
}
|
|
40056
|
+
const snapshotted = [];
|
|
40057
|
+
const linked = [];
|
|
40058
|
+
const absent = [];
|
|
40059
|
+
const failed = [];
|
|
40060
|
+
for (const prefix of admitted) {
|
|
40061
|
+
try {
|
|
40062
|
+
const outcome = await snapshot(prefix);
|
|
40063
|
+
if (outcome === "absent") absent.push(prefix);
|
|
40064
|
+
else {
|
|
40065
|
+
snapshotted.push(prefix);
|
|
40066
|
+
if (outcome === "linked") linked.push(prefix);
|
|
40067
|
+
}
|
|
40068
|
+
} catch {
|
|
40069
|
+
failed.push(prefix);
|
|
40070
|
+
}
|
|
40071
|
+
}
|
|
40072
|
+
const satisfied = [...snapshotted, ...absent];
|
|
40073
|
+
if (db && claimed && satisfied.length > 0) {
|
|
40074
|
+
try {
|
|
40075
|
+
const latest = readGateState(db);
|
|
40076
|
+
const completedAt = now();
|
|
40077
|
+
latest.generation = Math.max(latest.generation, generation);
|
|
40078
|
+
for (const p of satisfied) {
|
|
40079
|
+
latest.prefixes[p] = { generation, startedAt: startMs, completedAt };
|
|
40080
|
+
}
|
|
40081
|
+
writeGateState(db, latest);
|
|
40082
|
+
} catch {
|
|
40083
|
+
}
|
|
40084
|
+
}
|
|
40085
|
+
return { snapshotted, linked, absent, failed, skipped: null };
|
|
40086
|
+
} finally {
|
|
40087
|
+
await release().catch(() => {
|
|
40088
|
+
});
|
|
40089
|
+
}
|
|
40090
|
+
}
|
|
40091
|
+
function isValidStamp(ymd, hms) {
|
|
40092
|
+
const y = Number(ymd.slice(0, 4));
|
|
40093
|
+
const mo = Number(ymd.slice(4, 6));
|
|
40094
|
+
const d = Number(ymd.slice(6, 8));
|
|
40095
|
+
const h = Number(hms.slice(0, 2));
|
|
40096
|
+
const mi = Number(hms.slice(2, 4));
|
|
40097
|
+
const se = Number(hms.slice(4, 6));
|
|
40098
|
+
if (mo < 1 || mo > 12 || d < 1 || h > 23 || mi > 59 || se > 59) return false;
|
|
40099
|
+
const probe = new Date(Date.UTC(y, mo - 1, d));
|
|
40100
|
+
return probe.getUTCFullYear() === y && probe.getUTCMonth() === mo - 1 && probe.getUTCDate() === d;
|
|
40101
|
+
}
|
|
40102
|
+
function selectSnapshotsToKeep(names, ctx = {}) {
|
|
40103
|
+
const policy = ctx.policy ?? DEFAULT_SNAPSHOT_RETENTION;
|
|
40104
|
+
const keep = /* @__PURE__ */ new Set();
|
|
40105
|
+
const stamped = [];
|
|
40106
|
+
for (const name15 of names) {
|
|
40107
|
+
const m = STAMP_PATTERN.exec(name15);
|
|
40108
|
+
const ymd = m?.[1];
|
|
40109
|
+
const hms = m?.[2];
|
|
40110
|
+
if (!ymd || !hms || !isValidStamp(ymd, hms)) {
|
|
40111
|
+
keep.add(name15);
|
|
40112
|
+
continue;
|
|
40113
|
+
}
|
|
40114
|
+
const stamp = `${ymd}-${hms}`;
|
|
40115
|
+
if (ctx.nowStamp !== void 0 && stamp > ctx.nowStamp) {
|
|
40116
|
+
keep.add(name15);
|
|
40117
|
+
continue;
|
|
40118
|
+
}
|
|
40119
|
+
stamped.push({ name: name15, stamp });
|
|
40120
|
+
}
|
|
40121
|
+
if (ctx.pinned !== void 0 && names.includes(ctx.pinned)) keep.add(ctx.pinned);
|
|
40122
|
+
stamped.sort((a, b) => a.stamp < b.stamp ? 1 : a.stamp > b.stamp ? -1 : 0);
|
|
40123
|
+
for (const s of stamped.slice(0, policy.latest)) keep.add(s.name);
|
|
40124
|
+
const keepNewestPerBucket = (bucketOf, slots) => {
|
|
40125
|
+
const seen = /* @__PURE__ */ new Set();
|
|
40126
|
+
for (const s of stamped) {
|
|
40127
|
+
const bucket = bucketOf(s.stamp);
|
|
40128
|
+
if (seen.has(bucket)) continue;
|
|
40129
|
+
if (seen.size >= slots) break;
|
|
40130
|
+
seen.add(bucket);
|
|
40131
|
+
keep.add(s.name);
|
|
40132
|
+
}
|
|
40133
|
+
};
|
|
40134
|
+
keepNewestPerBucket(
|
|
40135
|
+
(stamp) => `${stamp.slice(0, 11)}q${Math.floor(Number(stamp.slice(11, 13)) / 15)}`,
|
|
40136
|
+
policy.quarterHourly
|
|
40137
|
+
);
|
|
40138
|
+
keepNewestPerBucket((stamp) => stamp.slice(0, 11), policy.hourly);
|
|
40139
|
+
keepNewestPerBucket((stamp) => stamp.slice(0, 8), policy.daily);
|
|
40140
|
+
return keep;
|
|
40141
|
+
}
|
|
40142
|
+
var SNAPSHOT_DEBOUNCE_MS, SNAPSHOT_LOCK_STALE_MS, SNAPSHOT_LOCK_WAIT_RETRIES, SNAPSHOT_GATE_META_KEY, GATE_LOCK_BASENAME, DEFAULT_SNAPSHOT_RETENTION, STAMP_PATTERN;
|
|
40143
|
+
var init_snapshot_gate = __esm({
|
|
40144
|
+
"packages/core/src/store/snapshot-gate.ts"() {
|
|
40145
|
+
"use strict";
|
|
40146
|
+
init_lock();
|
|
40147
|
+
init_twin_collapse();
|
|
40148
|
+
SNAPSHOT_DEBOUNCE_MS = 5 * 6e4;
|
|
40149
|
+
SNAPSHOT_LOCK_STALE_MS = 10 * 6e4;
|
|
40150
|
+
SNAPSHOT_LOCK_WAIT_RETRIES = 90;
|
|
40151
|
+
SNAPSHOT_GATE_META_KEY = "sqlite_snapshot_gate";
|
|
40152
|
+
GATE_LOCK_BASENAME = ".snapshot-gate";
|
|
40153
|
+
DEFAULT_SNAPSHOT_RETENTION = {
|
|
40154
|
+
latest: 1,
|
|
40155
|
+
quarterHourly: 3,
|
|
40156
|
+
hourly: 3,
|
|
40157
|
+
daily: 3
|
|
40158
|
+
};
|
|
40159
|
+
STAMP_PATTERN = /-(\d{8})-(\d{6})\.db$/;
|
|
40160
|
+
}
|
|
40161
|
+
});
|
|
40162
|
+
|
|
40083
40163
|
// packages/core/src/store/data-safety-central.ts
|
|
40084
40164
|
import { eq as eq7 } from "drizzle-orm";
|
|
40085
40165
|
function inSafetyScope(cwd, operation) {
|
|
@@ -41381,7 +41461,8 @@ __export(sqlite_exports, {
|
|
|
41381
41461
|
resetDbState: () => resetDbState,
|
|
41382
41462
|
resolveMigrationsFolder: () => resolveMigrationsFolder,
|
|
41383
41463
|
schema: () => tasks_schema_exports,
|
|
41384
|
-
seedTasksMeta: () => seedTasksMeta
|
|
41464
|
+
seedTasksMeta: () => seedTasksMeta,
|
|
41465
|
+
worktreeRecoveryRefusal: () => worktreeRecoveryRefusal
|
|
41385
41466
|
});
|
|
41386
41467
|
import { copyFileSync as copyFileSync4, existsSync as existsSync20, realpathSync as realpathSync7, renameSync as renameSync7, unlinkSync as unlinkSync4 } from "node:fs";
|
|
41387
41468
|
import { createRequire as createRequire7 } from "node:module";
|
|
@@ -41432,6 +41513,12 @@ function isSameDirectory(a, b) {
|
|
|
41432
41513
|
return false;
|
|
41433
41514
|
}
|
|
41434
41515
|
}
|
|
41516
|
+
function worktreeRecoveryRefusal(dbPath) {
|
|
41517
|
+
const storeCleoDir = dirname14(dbPath);
|
|
41518
|
+
const root = dirname14(storeCleoDir);
|
|
41519
|
+
if (basename8(storeCleoDir) !== ".cleo" || !isGitLinkedCheckout(root)) return null;
|
|
41520
|
+
return `Auto-recovery refused: ${dbPath} lives inside the git worktree ${root}; ${describeWorktreeOwner(root)}. A worktree uses its owning project's store and never receives a restored copy (T12460, T12677).`;
|
|
41521
|
+
}
|
|
41435
41522
|
async function autoRecoverFromBackup(nativeDb, dbPath, cwd) {
|
|
41436
41523
|
const { openNativeDatabase: openNativeDatabase2 } = await Promise.resolve().then(() => (init_sqlite_native(), sqlite_native_exports));
|
|
41437
41524
|
const log15 = getLogger("sqlite");
|
|
@@ -41441,11 +41528,9 @@ async function autoRecoverFromBackup(nativeDb, dbPath, cwd) {
|
|
|
41441
41528
|
const taskCount = countResult?.cnt ?? 0;
|
|
41442
41529
|
if (taskCount > 0) return false;
|
|
41443
41530
|
const storeCleoDir = dirname14(dbPath);
|
|
41444
|
-
|
|
41445
|
-
|
|
41446
|
-
|
|
41447
|
-
"Auto-recovery refused: the empty store lives inside a git worktree (T12460). Worktrees must resolve the parent project store."
|
|
41448
|
-
);
|
|
41531
|
+
const worktreeRefusal = worktreeRecoveryRefusal(dbPath);
|
|
41532
|
+
if (worktreeRefusal !== null) {
|
|
41533
|
+
log15.warn({ dbPath }, worktreeRefusal);
|
|
41449
41534
|
return false;
|
|
41450
41535
|
}
|
|
41451
41536
|
const backups = listSqliteBackups(cwd);
|