@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
|
@@ -10668,7 +10668,7 @@ var init_operations_registry = __esm({
|
|
|
10668
10668
|
type: "string",
|
|
10669
10669
|
required: false,
|
|
10670
10670
|
enum: DOCS_LIFECYCLE_STATUSES,
|
|
10671
|
-
description: `Override the new lifecycle status. Defaults to "draft" on every update. Valid: ${DOCS_LIFECYCLE_STATUSES.join("|")}.`
|
|
10671
|
+
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("|")}.`
|
|
10672
10672
|
},
|
|
10673
10673
|
{
|
|
10674
10674
|
name: "dryRun",
|
|
@@ -13155,11 +13155,13 @@ var init_evidence_atom_schema = __esm({
|
|
|
13155
13155
|
});
|
|
13156
13156
|
prAtomSchema = z10.object({
|
|
13157
13157
|
kind: z10.literal("pr"),
|
|
13158
|
-
prNumber: z10.number().int().positive("pr atom requires a positive integer PR number")
|
|
13158
|
+
prNumber: z10.number().int().positive("pr atom requires a positive integer PR number"),
|
|
13159
|
+
componentPrNumber: z10.number().int().positive().optional()
|
|
13159
13160
|
});
|
|
13160
13161
|
ciAtomSchema = z10.object({
|
|
13161
13162
|
kind: z10.literal("ci"),
|
|
13162
|
-
prNumber: z10.number().int().positive("ci atom requires a positive integer PR number")
|
|
13163
|
+
prNumber: z10.number().int().positive("ci atom requires a positive integer PR number"),
|
|
13164
|
+
componentPrNumber: z10.number().int().positive().optional()
|
|
13163
13165
|
});
|
|
13164
13166
|
locDropAtomSchema = z10.object({
|
|
13165
13167
|
kind: z10.literal("loc-drop"),
|
|
@@ -18928,6 +18930,22 @@ function linkedWorktreeMainRoot(dir) {
|
|
|
18928
18930
|
if (basename2(commonDir) === ".git") return dirname3(commonDir);
|
|
18929
18931
|
return readCoreWorktree(commonDir);
|
|
18930
18932
|
}
|
|
18933
|
+
function describeWorktreeOwner(dir) {
|
|
18934
|
+
const mainRoot = linkedWorktreeMainRoot(dir);
|
|
18935
|
+
if (mainRoot === null) return "owning repository unknown (unreadable gitlink)";
|
|
18936
|
+
let projectId = null;
|
|
18937
|
+
try {
|
|
18938
|
+
const parsed = JSON.parse(
|
|
18939
|
+
readFileSync3(join5(mainRoot, ".cleo", "project-info.json"), "utf-8")
|
|
18940
|
+
);
|
|
18941
|
+
if (typeof parsed === "object" && parsed !== null && "projectId" in parsed) {
|
|
18942
|
+
const value = parsed.projectId;
|
|
18943
|
+
if (typeof value === "string" && value !== "") projectId = value;
|
|
18944
|
+
}
|
|
18945
|
+
} catch {
|
|
18946
|
+
}
|
|
18947
|
+
return projectId === null ? `owning repository ${mainRoot} (not an initialised CLEO project)` : `owning project ${mainRoot} (projectId ${projectId})`;
|
|
18948
|
+
}
|
|
18931
18949
|
function readCoreWorktree(gitDir) {
|
|
18932
18950
|
try {
|
|
18933
18951
|
const config2 = readFileSync3(join5(gitDir, "config"), "utf-8");
|
|
@@ -25991,9 +26009,10 @@ function assertStorePathIsNotWorktreeResident(role, dbPath) {
|
|
|
25991
26009
|
);
|
|
25992
26010
|
return;
|
|
25993
26011
|
}
|
|
26012
|
+
const owner = describeWorktreeOwner(projectRoot);
|
|
25994
26013
|
throw new CleoError(
|
|
25995
26014
|
8 /* CONFIG_ERROR */,
|
|
25996
|
-
`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.`,
|
|
26015
|
+
`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.`,
|
|
25997
26016
|
{
|
|
25998
26017
|
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.`
|
|
25999
26018
|
}
|
|
@@ -32580,216 +32599,6 @@ var init_memory_schema = __esm({
|
|
|
32580
32599
|
}
|
|
32581
32600
|
});
|
|
32582
32601
|
|
|
32583
|
-
// packages/core/src/store/snapshot-gate.ts
|
|
32584
|
-
import { join as join22 } from "node:path";
|
|
32585
|
-
function readGateState(db) {
|
|
32586
|
-
const row = db.prepare("SELECT value FROM tasks_schema_meta WHERE key = ?").get(SNAPSHOT_GATE_META_KEY);
|
|
32587
|
-
const state = { generation: 0, prefixes: {} };
|
|
32588
|
-
if (!row) return state;
|
|
32589
|
-
try {
|
|
32590
|
-
const parsed = JSON.parse(row.value);
|
|
32591
|
-
if (parsed === null || typeof parsed !== "object") return state;
|
|
32592
|
-
if (typeof parsed.generation === "number" && Number.isSafeInteger(parsed.generation)) {
|
|
32593
|
-
state.generation = parsed.generation;
|
|
32594
|
-
}
|
|
32595
|
-
for (const [prefix, e] of Object.entries(parsed.prefixes ?? {})) {
|
|
32596
|
-
if (e && typeof e.generation === "number" && typeof e.startedAt === "number" && typeof e.completedAt === "number") {
|
|
32597
|
-
state.prefixes[prefix] = {
|
|
32598
|
-
generation: e.generation,
|
|
32599
|
-
startedAt: e.startedAt,
|
|
32600
|
-
completedAt: e.completedAt
|
|
32601
|
-
};
|
|
32602
|
-
}
|
|
32603
|
-
}
|
|
32604
|
-
} catch {
|
|
32605
|
-
}
|
|
32606
|
-
return state;
|
|
32607
|
-
}
|
|
32608
|
-
function writeGateState(db, state) {
|
|
32609
|
-
db.prepare(
|
|
32610
|
-
"INSERT INTO tasks_schema_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value"
|
|
32611
|
-
).run(SNAPSHOT_GATE_META_KEY, JSON.stringify(state));
|
|
32612
|
-
}
|
|
32613
|
-
function tryReadGateState(db) {
|
|
32614
|
-
if (!db) return null;
|
|
32615
|
-
try {
|
|
32616
|
-
return readGateState(db);
|
|
32617
|
-
} catch {
|
|
32618
|
-
return null;
|
|
32619
|
-
}
|
|
32620
|
-
}
|
|
32621
|
-
function needsSnapshot(entry, mode, nowMs, seenGeneration) {
|
|
32622
|
-
if (!entry) return true;
|
|
32623
|
-
if (mode === "required") return seenGeneration === null || entry.generation <= seenGeneration;
|
|
32624
|
-
const elapsed = nowMs - entry.startedAt;
|
|
32625
|
-
return elapsed < 0 || elapsed >= SNAPSHOT_DEBOUNCE_MS;
|
|
32626
|
-
}
|
|
32627
|
-
async function runGatedSnapshot(opts, snapshot) {
|
|
32628
|
-
const now = opts.now ?? Date.now;
|
|
32629
|
-
const mode = opts.mode ?? "routine";
|
|
32630
|
-
const db = opts.stateDb;
|
|
32631
|
-
const none = (skipped, error48) => ({
|
|
32632
|
-
snapshotted: [],
|
|
32633
|
-
linked: [],
|
|
32634
|
-
absent: [],
|
|
32635
|
-
failed: [],
|
|
32636
|
-
skipped,
|
|
32637
|
-
...error48 !== void 0 && { error: error48 }
|
|
32638
|
-
});
|
|
32639
|
-
const entryState = tryReadGateState(db);
|
|
32640
|
-
const seenGeneration = mode === "required" ? opts.seenGeneration ?? entryState?.generation ?? null : null;
|
|
32641
|
-
if (mode === "routine") {
|
|
32642
|
-
if (!entryState) return none("state-unavailable");
|
|
32643
|
-
const nowMs = now();
|
|
32644
|
-
if (!opts.prefixes.some((p) => needsSnapshot(entryState.prefixes[p], mode, nowMs, null))) {
|
|
32645
|
-
return none("debounced");
|
|
32646
|
-
}
|
|
32647
|
-
}
|
|
32648
|
-
const lockPath2 = join22(opts.backupDir, GATE_LOCK_BASENAME);
|
|
32649
|
-
let release;
|
|
32650
|
-
try {
|
|
32651
|
-
release = await acquireLock(lockPath2, {
|
|
32652
|
-
retries: mode === "required" ? opts.lockWaitRetries ?? SNAPSHOT_LOCK_WAIT_RETRIES : 0,
|
|
32653
|
-
stale: SNAPSHOT_LOCK_STALE_MS,
|
|
32654
|
-
// A compromised lock (e.g. the backup dir was removed) must not throw
|
|
32655
|
-
// from a timer and crash the host process.
|
|
32656
|
-
onCompromised: () => {
|
|
32657
|
-
}
|
|
32658
|
-
});
|
|
32659
|
-
} catch {
|
|
32660
|
-
if (mode === "routine") return none("in-flight");
|
|
32661
|
-
return none(
|
|
32662
|
-
"lock-timeout",
|
|
32663
|
-
`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.`
|
|
32664
|
-
);
|
|
32665
|
-
}
|
|
32666
|
-
try {
|
|
32667
|
-
try {
|
|
32668
|
-
opts.onLockAcquired?.();
|
|
32669
|
-
} catch {
|
|
32670
|
-
}
|
|
32671
|
-
const state = tryReadGateState(db);
|
|
32672
|
-
if (!state && mode === "routine") return none("state-unavailable");
|
|
32673
|
-
const startMs = now();
|
|
32674
|
-
const admitted = mode === "required" && opts.alwaysSnapshot === true ? [...opts.prefixes] : opts.prefixes.filter(
|
|
32675
|
-
(p) => needsSnapshot(state?.prefixes[p], mode, startMs, seenGeneration)
|
|
32676
|
-
);
|
|
32677
|
-
if (admitted.length === 0) return none(mode === "routine" ? "debounced" : "covered");
|
|
32678
|
-
const generation = (state?.generation ?? 0) + 1;
|
|
32679
|
-
let claimed = false;
|
|
32680
|
-
if (db && state) {
|
|
32681
|
-
try {
|
|
32682
|
-
writeGateState(db, { ...state, generation });
|
|
32683
|
-
claimed = true;
|
|
32684
|
-
} catch {
|
|
32685
|
-
}
|
|
32686
|
-
}
|
|
32687
|
-
const snapshotted = [];
|
|
32688
|
-
const linked = [];
|
|
32689
|
-
const absent = [];
|
|
32690
|
-
const failed = [];
|
|
32691
|
-
for (const prefix of admitted) {
|
|
32692
|
-
try {
|
|
32693
|
-
const outcome = await snapshot(prefix);
|
|
32694
|
-
if (outcome === "absent") absent.push(prefix);
|
|
32695
|
-
else {
|
|
32696
|
-
snapshotted.push(prefix);
|
|
32697
|
-
if (outcome === "linked") linked.push(prefix);
|
|
32698
|
-
}
|
|
32699
|
-
} catch {
|
|
32700
|
-
failed.push(prefix);
|
|
32701
|
-
}
|
|
32702
|
-
}
|
|
32703
|
-
const satisfied = [...snapshotted, ...absent];
|
|
32704
|
-
if (db && claimed && satisfied.length > 0) {
|
|
32705
|
-
try {
|
|
32706
|
-
const latest = readGateState(db);
|
|
32707
|
-
const completedAt = now();
|
|
32708
|
-
latest.generation = Math.max(latest.generation, generation);
|
|
32709
|
-
for (const p of satisfied) {
|
|
32710
|
-
latest.prefixes[p] = { generation, startedAt: startMs, completedAt };
|
|
32711
|
-
}
|
|
32712
|
-
writeGateState(db, latest);
|
|
32713
|
-
} catch {
|
|
32714
|
-
}
|
|
32715
|
-
}
|
|
32716
|
-
return { snapshotted, linked, absent, failed, skipped: null };
|
|
32717
|
-
} finally {
|
|
32718
|
-
await release().catch(() => {
|
|
32719
|
-
});
|
|
32720
|
-
}
|
|
32721
|
-
}
|
|
32722
|
-
function isValidStamp(ymd, hms) {
|
|
32723
|
-
const y = Number(ymd.slice(0, 4));
|
|
32724
|
-
const mo = Number(ymd.slice(4, 6));
|
|
32725
|
-
const d = Number(ymd.slice(6, 8));
|
|
32726
|
-
const h = Number(hms.slice(0, 2));
|
|
32727
|
-
const mi = Number(hms.slice(2, 4));
|
|
32728
|
-
const se = Number(hms.slice(4, 6));
|
|
32729
|
-
if (mo < 1 || mo > 12 || d < 1 || h > 23 || mi > 59 || se > 59) return false;
|
|
32730
|
-
const probe = new Date(Date.UTC(y, mo - 1, d));
|
|
32731
|
-
return probe.getUTCFullYear() === y && probe.getUTCMonth() === mo - 1 && probe.getUTCDate() === d;
|
|
32732
|
-
}
|
|
32733
|
-
function selectSnapshotsToKeep(names, ctx = {}) {
|
|
32734
|
-
const policy = ctx.policy ?? DEFAULT_SNAPSHOT_RETENTION;
|
|
32735
|
-
const keep = /* @__PURE__ */ new Set();
|
|
32736
|
-
const stamped = [];
|
|
32737
|
-
for (const name15 of names) {
|
|
32738
|
-
const m = STAMP_PATTERN.exec(name15);
|
|
32739
|
-
const ymd = m?.[1];
|
|
32740
|
-
const hms = m?.[2];
|
|
32741
|
-
if (!ymd || !hms || !isValidStamp(ymd, hms)) {
|
|
32742
|
-
keep.add(name15);
|
|
32743
|
-
continue;
|
|
32744
|
-
}
|
|
32745
|
-
const stamp = `${ymd}-${hms}`;
|
|
32746
|
-
if (ctx.nowStamp !== void 0 && stamp > ctx.nowStamp) {
|
|
32747
|
-
keep.add(name15);
|
|
32748
|
-
continue;
|
|
32749
|
-
}
|
|
32750
|
-
stamped.push({ name: name15, stamp });
|
|
32751
|
-
}
|
|
32752
|
-
if (ctx.pinned !== void 0 && names.includes(ctx.pinned)) keep.add(ctx.pinned);
|
|
32753
|
-
stamped.sort((a, b) => a.stamp < b.stamp ? 1 : a.stamp > b.stamp ? -1 : 0);
|
|
32754
|
-
for (const s of stamped.slice(0, policy.latest)) keep.add(s.name);
|
|
32755
|
-
const keepNewestPerBucket = (bucketOf, slots) => {
|
|
32756
|
-
const seen = /* @__PURE__ */ new Set();
|
|
32757
|
-
for (const s of stamped) {
|
|
32758
|
-
const bucket = bucketOf(s.stamp);
|
|
32759
|
-
if (seen.has(bucket)) continue;
|
|
32760
|
-
if (seen.size >= slots) break;
|
|
32761
|
-
seen.add(bucket);
|
|
32762
|
-
keep.add(s.name);
|
|
32763
|
-
}
|
|
32764
|
-
};
|
|
32765
|
-
keepNewestPerBucket(
|
|
32766
|
-
(stamp) => `${stamp.slice(0, 11)}q${Math.floor(Number(stamp.slice(11, 13)) / 15)}`,
|
|
32767
|
-
policy.quarterHourly
|
|
32768
|
-
);
|
|
32769
|
-
keepNewestPerBucket((stamp) => stamp.slice(0, 11), policy.hourly);
|
|
32770
|
-
keepNewestPerBucket((stamp) => stamp.slice(0, 8), policy.daily);
|
|
32771
|
-
return keep;
|
|
32772
|
-
}
|
|
32773
|
-
var SNAPSHOT_DEBOUNCE_MS, SNAPSHOT_LOCK_STALE_MS, SNAPSHOT_LOCK_WAIT_RETRIES, SNAPSHOT_GATE_META_KEY, GATE_LOCK_BASENAME, DEFAULT_SNAPSHOT_RETENTION, STAMP_PATTERN;
|
|
32774
|
-
var init_snapshot_gate = __esm({
|
|
32775
|
-
"packages/core/src/store/snapshot-gate.ts"() {
|
|
32776
|
-
"use strict";
|
|
32777
|
-
init_lock();
|
|
32778
|
-
SNAPSHOT_DEBOUNCE_MS = 5 * 6e4;
|
|
32779
|
-
SNAPSHOT_LOCK_STALE_MS = 10 * 6e4;
|
|
32780
|
-
SNAPSHOT_LOCK_WAIT_RETRIES = 90;
|
|
32781
|
-
SNAPSHOT_GATE_META_KEY = "sqlite_snapshot_gate";
|
|
32782
|
-
GATE_LOCK_BASENAME = ".snapshot-gate";
|
|
32783
|
-
DEFAULT_SNAPSHOT_RETENTION = {
|
|
32784
|
-
latest: 1,
|
|
32785
|
-
quarterHourly: 3,
|
|
32786
|
-
hourly: 3,
|
|
32787
|
-
daily: 3
|
|
32788
|
-
};
|
|
32789
|
-
STAMP_PATTERN = /-(\d{8})-(\d{6})\.db$/;
|
|
32790
|
-
}
|
|
32791
|
-
});
|
|
32792
|
-
|
|
32793
32602
|
// packages/core/src/store/twin-collapse.ts
|
|
32794
32603
|
import { createHash as createHash3 } from "node:crypto";
|
|
32795
32604
|
function hasMainTable(db, name15) {
|
|
@@ -32866,6 +32675,22 @@ function mergeSchemaMetaValue(key, bare, twin) {
|
|
|
32866
32675
|
const field = SCHEMA_META_COUNTER_FIELDS[key];
|
|
32867
32676
|
return field === void 0 ? bare : withMaxCounter(bare, twin, field);
|
|
32868
32677
|
}
|
|
32678
|
+
function mirrorCounterToBare(db, key) {
|
|
32679
|
+
if (SCHEMA_META_COUNTER_FIELDS[key] === void 0) return;
|
|
32680
|
+
if (!hasMainTable(db, "schema_meta")) return;
|
|
32681
|
+
const twin = readKv(db, "tasks_schema_meta", key);
|
|
32682
|
+
if (twin === void 0) return;
|
|
32683
|
+
const bare = readKv(db, "schema_meta", key);
|
|
32684
|
+
const next = bare === void 0 ? twin : mergeSchemaMetaValue(key, bare, twin);
|
|
32685
|
+
if (next === null || next === bare) return;
|
|
32686
|
+
writeKv(db, "schema_meta", key, next);
|
|
32687
|
+
}
|
|
32688
|
+
function bareCounterOf(db, key) {
|
|
32689
|
+
const name15 = SCHEMA_META_COUNTER_FIELDS[key];
|
|
32690
|
+
if (name15 === void 0 || !hasMainTable(db, "schema_meta")) return 0;
|
|
32691
|
+
const bare = readKv(db, "schema_meta", key);
|
|
32692
|
+
return (bare === void 0 ? void 0 : counterOf(bare, name15)) ?? 0;
|
|
32693
|
+
}
|
|
32869
32694
|
function kvRows(db, table, schema = "main") {
|
|
32870
32695
|
const rows = db.prepare(`SELECT key, value FROM ${schema}.${table}`).all();
|
|
32871
32696
|
return new Map(rows.map((r) => [r.key, r.value]));
|
|
@@ -32998,6 +32823,39 @@ function applyStickyRows(db, schema, plan) {
|
|
|
32998
32823
|
}
|
|
32999
32824
|
return { inserted, replaced: 0, deleted };
|
|
33000
32825
|
}
|
|
32826
|
+
function tagsOf(tagsJson) {
|
|
32827
|
+
if (!tagsJson) return [];
|
|
32828
|
+
try {
|
|
32829
|
+
const parsed = JSON.parse(tagsJson);
|
|
32830
|
+
if (!Array.isArray(parsed)) return [];
|
|
32831
|
+
return [...new Set(parsed.filter((t) => typeof t === "string" && t.length > 0))];
|
|
32832
|
+
} catch {
|
|
32833
|
+
return [];
|
|
32834
|
+
}
|
|
32835
|
+
}
|
|
32836
|
+
function syncStickyJunction(db, schema) {
|
|
32837
|
+
const notes = db.prepare("SELECT id, tags_json FROM main.brain_sticky_notes").all();
|
|
32838
|
+
const have = /* @__PURE__ */ new Map();
|
|
32839
|
+
for (const r of db.prepare(`SELECT sticky_id, tag FROM ${schema}.brain_sticky_tags`).all()) {
|
|
32840
|
+
const set2 = have.get(r.sticky_id) ?? /* @__PURE__ */ new Set();
|
|
32841
|
+
set2.add(r.tag);
|
|
32842
|
+
have.set(r.sticky_id, set2);
|
|
32843
|
+
}
|
|
32844
|
+
const insert = db.prepare(
|
|
32845
|
+
`INSERT OR IGNORE INTO ${schema}.brain_sticky_tags (sticky_id, tag) VALUES (?, ?)`
|
|
32846
|
+
);
|
|
32847
|
+
const del = db.prepare(`DELETE FROM ${schema}.brain_sticky_tags WHERE sticky_id = ? AND tag = ?`);
|
|
32848
|
+
let inserted = 0;
|
|
32849
|
+
let deleted = 0;
|
|
32850
|
+
for (const note of notes) {
|
|
32851
|
+
const want = new Set(tagsOf(note.tags_json));
|
|
32852
|
+
const current = have.get(note.id) ?? /* @__PURE__ */ new Set();
|
|
32853
|
+
for (const tag of want)
|
|
32854
|
+
if (!current.has(tag)) inserted += Number(insert.run(note.id, tag).changes);
|
|
32855
|
+
for (const tag of current) if (!want.has(tag)) deleted += Number(del.run(note.id, tag).changes);
|
|
32856
|
+
}
|
|
32857
|
+
return { inserted, deleted };
|
|
32858
|
+
}
|
|
33001
32859
|
function twinCollapseFailureOf(db) {
|
|
33002
32860
|
return degraded.get(db);
|
|
33003
32861
|
}
|
|
@@ -33207,7 +33065,7 @@ function collapseTwinTables(nativeDb, dbPath, options = {}) {
|
|
|
33207
33065
|
}
|
|
33208
33066
|
return inOrder();
|
|
33209
33067
|
}
|
|
33210
|
-
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;
|
|
33068
|
+
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;
|
|
33211
33069
|
var init_twin_collapse = __esm({
|
|
33212
33070
|
"packages/core/src/store/twin-collapse.ts"() {
|
|
33213
33071
|
"use strict";
|
|
@@ -33215,18 +33073,18 @@ var init_twin_collapse = __esm({
|
|
|
33215
33073
|
init_errors2();
|
|
33216
33074
|
init_logger();
|
|
33217
33075
|
init_pre_repair_snapshot();
|
|
33218
|
-
init_snapshot_gate();
|
|
33219
33076
|
log6 = getLogger("twin-collapse");
|
|
33220
33077
|
TWIN_COLLAPSE_MARKER_PREFIX = "twin_collapse:";
|
|
33221
33078
|
TWIN_COLLAPSE_FAILURE_PREFIX = "twin_collapse_failed:";
|
|
33079
|
+
SNAPSHOT_GATE_KEY = "sqlite_snapshot_gate";
|
|
33222
33080
|
SCHEMA_META_COUNTER_FIELDS = {
|
|
33223
33081
|
task_id_sequence: "counter",
|
|
33224
|
-
[
|
|
33082
|
+
[SNAPSHOT_GATE_KEY]: "generation",
|
|
33225
33083
|
file_meta: "generation"
|
|
33226
33084
|
};
|
|
33227
33085
|
SCHEMA_META_NEVER_DELETED = /* @__PURE__ */ new Set([
|
|
33228
33086
|
"task_id_sequence",
|
|
33229
|
-
|
|
33087
|
+
SNAPSHOT_GATE_KEY
|
|
33230
33088
|
]);
|
|
33231
33089
|
MAX_CONFLICTS = 50;
|
|
33232
33090
|
sha = (value) => createHash3("sha256").update(value).digest("hex");
|
|
@@ -33291,7 +33149,12 @@ var init_twin_collapse = __esm({
|
|
|
33291
33149
|
throw new Error(
|
|
33292
33150
|
`sticky_tags collapse did not verify: ${missing} missing, ${lingering} not removed`
|
|
33293
33151
|
);
|
|
33294
|
-
|
|
33152
|
+
const synced = syncStickyJunction(db, "main");
|
|
33153
|
+
return {
|
|
33154
|
+
inserted: counts.inserted + synced.inserted,
|
|
33155
|
+
replaced: 0,
|
|
33156
|
+
deleted: counts.deleted + synced.deleted
|
|
33157
|
+
};
|
|
33295
33158
|
},
|
|
33296
33159
|
shadow(db, plan) {
|
|
33297
33160
|
db.exec(
|
|
@@ -33302,6 +33165,7 @@ var init_twin_collapse = __esm({
|
|
|
33302
33165
|
"INSERT INTO temp.brain_sticky_tags (sticky_id, tag) SELECT sticky_id, tag FROM main.brain_sticky_tags"
|
|
33303
33166
|
);
|
|
33304
33167
|
applyStickyRows(db, "temp", plan);
|
|
33168
|
+
syncStickyJunction(db, "temp");
|
|
33305
33169
|
}
|
|
33306
33170
|
};
|
|
33307
33171
|
PAIRS = [SCHEMA_META, STICKY_TAGS];
|
|
@@ -38776,9 +38640,12 @@ async function writeMetaValue(cwd, key, value) {
|
|
|
38776
38640
|
target: tasksSchemaMeta.key,
|
|
38777
38641
|
set: { value: json2 }
|
|
38778
38642
|
}).run();
|
|
38643
|
+
const native = getNativeTasksDb(cwd);
|
|
38644
|
+
if (native) mirrorCounterToBare(native, key);
|
|
38779
38645
|
}
|
|
38780
38646
|
function advanceTaskIdSequence(nativeDb, floor) {
|
|
38781
38647
|
assertTwinCollapseWritable(nativeDb);
|
|
38648
|
+
const lowest = Math.max(floor, bareCounterOf(nativeDb, "task_id_sequence"));
|
|
38782
38649
|
nativeDb.prepare(`
|
|
38783
38650
|
UPDATE tasks_schema_meta
|
|
38784
38651
|
SET value = json_set(value,
|
|
@@ -38787,7 +38654,8 @@ function advanceTaskIdSequence(nativeDb, floor) {
|
|
|
38787
38654
|
'$.checksum', 'alloc-' || strftime('%s','now')
|
|
38788
38655
|
)
|
|
38789
38656
|
WHERE key = 'task_id_sequence'
|
|
38790
|
-
`).run(
|
|
38657
|
+
`).run(lowest, lowest);
|
|
38658
|
+
mirrorCounterToBare(nativeDb, "task_id_sequence");
|
|
38791
38659
|
const row = nativeDb.prepare(`
|
|
38792
38660
|
SELECT json_extract(value, '$.counter') AS counter
|
|
38793
38661
|
FROM tasks_schema_meta WHERE key = 'task_id_sequence'
|
|
@@ -39831,12 +39699,12 @@ var init_sqlite_data_accessor = __esm({
|
|
|
39831
39699
|
|
|
39832
39700
|
// packages/core/src/sequence/index.ts
|
|
39833
39701
|
import { existsSync as existsSync18, readFileSync as readFileSync6, renameSync as renameSync5 } from "node:fs";
|
|
39834
|
-
import { join as
|
|
39702
|
+
import { join as join22 } from "node:path";
|
|
39835
39703
|
function getLegacySequenceJsonPath(cwd) {
|
|
39836
|
-
return
|
|
39704
|
+
return join22(resolveOrCwd(cwd), ".cleo", ".sequence.json");
|
|
39837
39705
|
}
|
|
39838
39706
|
function getLegacySequencePath(cwd) {
|
|
39839
|
-
return
|
|
39707
|
+
return join22(resolveOrCwd(cwd), ".cleo", ".sequence");
|
|
39840
39708
|
}
|
|
39841
39709
|
function isValidSequenceState(value) {
|
|
39842
39710
|
if (!value || typeof value !== "object") return false;
|
|
@@ -40011,9 +39879,9 @@ var init_sequence = __esm({
|
|
|
40011
39879
|
});
|
|
40012
39880
|
|
|
40013
39881
|
// packages/core/src/store/checkpoint-git-dir.ts
|
|
40014
|
-
import { join as
|
|
39882
|
+
import { join as join23 } from "node:path";
|
|
40015
39883
|
function checkpointGitDir(cleoDir) {
|
|
40016
|
-
return
|
|
39884
|
+
return join23(cleoDir, CHECKPOINT_GIT_DIRNAME);
|
|
40017
39885
|
}
|
|
40018
39886
|
var CHECKPOINT_GIT_DIRNAME;
|
|
40019
39887
|
var init_checkpoint_git_dir = __esm({
|
|
@@ -40027,7 +39895,7 @@ var init_checkpoint_git_dir = __esm({
|
|
|
40027
39895
|
import { execFile as execFile2 } from "node:child_process";
|
|
40028
39896
|
import { existsSync as existsSync19 } from "node:fs";
|
|
40029
39897
|
import { readFile as readFile4, writeFile } from "node:fs/promises";
|
|
40030
|
-
import { join as
|
|
39898
|
+
import { join as join24, resolve as resolve8 } from "node:path";
|
|
40031
39899
|
import { promisify as promisify2 } from "node:util";
|
|
40032
39900
|
function makeCleoGitEnv(cleoDir) {
|
|
40033
39901
|
const abs = resolve8(cleoDir);
|
|
@@ -40051,7 +39919,7 @@ async function cleoGitCommand(args, cleoDir) {
|
|
|
40051
39919
|
}
|
|
40052
39920
|
}
|
|
40053
39921
|
function isCleoGitInitialized(cleoDir) {
|
|
40054
|
-
return existsSync19(
|
|
39922
|
+
return existsSync19(join24(checkpointGitDir(cleoDir), "HEAD"));
|
|
40055
39923
|
}
|
|
40056
39924
|
async function loadStateFileAllowlist(cwd) {
|
|
40057
39925
|
try {
|
|
@@ -40095,25 +39963,25 @@ async function isCleoGitRepo(cleoDir) {
|
|
|
40095
39963
|
return result.success && (result.stdout === "true" || isCleoGitInitialized(cleoDir));
|
|
40096
39964
|
}
|
|
40097
39965
|
function isMergeInProgress(cleoDir) {
|
|
40098
|
-
return existsSync19(
|
|
39966
|
+
return existsSync19(join24(checkpointGitDir(cleoDir), "MERGE_HEAD"));
|
|
40099
39967
|
}
|
|
40100
39968
|
async function isDetachedHead(cleoDir) {
|
|
40101
39969
|
const result = await cleoGitCommand(["symbolic-ref", "HEAD"], cleoDir);
|
|
40102
39970
|
return !result.success;
|
|
40103
39971
|
}
|
|
40104
39972
|
function isRebaseInProgress(cleoDir) {
|
|
40105
|
-
return existsSync19(
|
|
39973
|
+
return existsSync19(join24(checkpointGitDir(cleoDir), "rebase-merge")) || existsSync19(join24(checkpointGitDir(cleoDir), "rebase-apply"));
|
|
40106
39974
|
}
|
|
40107
39975
|
async function recordCheckpointTime(cleoDir) {
|
|
40108
39976
|
try {
|
|
40109
|
-
const stateFile =
|
|
39977
|
+
const stateFile = join24(cleoDir, CHECKPOINT_STATE_FILE);
|
|
40110
39978
|
await writeFile(stateFile, String(Math.floor(Date.now() / 1e3)));
|
|
40111
39979
|
} catch {
|
|
40112
39980
|
}
|
|
40113
39981
|
}
|
|
40114
39982
|
async function getLastCheckpointTime(cleoDir) {
|
|
40115
39983
|
try {
|
|
40116
|
-
const stateFile =
|
|
39984
|
+
const stateFile = join24(cleoDir, CHECKPOINT_STATE_FILE);
|
|
40117
39985
|
const content = await readFile4(stateFile, "utf-8");
|
|
40118
39986
|
const epoch = parseInt(content.trim(), 10);
|
|
40119
39987
|
return Number.isNaN(epoch) ? 0 : epoch;
|
|
@@ -40125,7 +39993,7 @@ async function getChangedStateFiles(cleoDir, cwd) {
|
|
|
40125
39993
|
const changed = [];
|
|
40126
39994
|
const allStateFiles = await getAllStateFiles(cwd);
|
|
40127
39995
|
for (const stateFile of allStateFiles) {
|
|
40128
|
-
const fullPath =
|
|
39996
|
+
const fullPath = join24(cleoDir, stateFile);
|
|
40129
39997
|
if (!existsSync19(fullPath)) continue;
|
|
40130
39998
|
const diffResult = await cleoGitCommand(["diff", "--quiet", "--", stateFile], cleoDir);
|
|
40131
39999
|
const cachedResult = await cleoGitCommand(
|
|
@@ -40226,6 +40094,218 @@ var init_git_checkpoint = __esm({
|
|
|
40226
40094
|
}
|
|
40227
40095
|
});
|
|
40228
40096
|
|
|
40097
|
+
// packages/core/src/store/snapshot-gate.ts
|
|
40098
|
+
import { join as join25 } from "node:path";
|
|
40099
|
+
function readGateState(db) {
|
|
40100
|
+
const row = db.prepare("SELECT value FROM tasks_schema_meta WHERE key = ?").get(SNAPSHOT_GATE_META_KEY);
|
|
40101
|
+
const state = { generation: 0, prefixes: {} };
|
|
40102
|
+
if (!row) return state;
|
|
40103
|
+
try {
|
|
40104
|
+
const parsed = JSON.parse(row.value);
|
|
40105
|
+
if (parsed === null || typeof parsed !== "object") return state;
|
|
40106
|
+
if (typeof parsed.generation === "number" && Number.isSafeInteger(parsed.generation)) {
|
|
40107
|
+
state.generation = parsed.generation;
|
|
40108
|
+
}
|
|
40109
|
+
for (const [prefix, e] of Object.entries(parsed.prefixes ?? {})) {
|
|
40110
|
+
if (e && typeof e.generation === "number" && typeof e.startedAt === "number" && typeof e.completedAt === "number") {
|
|
40111
|
+
state.prefixes[prefix] = {
|
|
40112
|
+
generation: e.generation,
|
|
40113
|
+
startedAt: e.startedAt,
|
|
40114
|
+
completedAt: e.completedAt
|
|
40115
|
+
};
|
|
40116
|
+
}
|
|
40117
|
+
}
|
|
40118
|
+
} catch {
|
|
40119
|
+
}
|
|
40120
|
+
return state;
|
|
40121
|
+
}
|
|
40122
|
+
function writeGateState(db, state) {
|
|
40123
|
+
db.prepare(
|
|
40124
|
+
"INSERT INTO tasks_schema_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value"
|
|
40125
|
+
).run(SNAPSHOT_GATE_META_KEY, JSON.stringify(state));
|
|
40126
|
+
mirrorCounterToBare(db, SNAPSHOT_GATE_META_KEY);
|
|
40127
|
+
}
|
|
40128
|
+
function tryReadGateState(db) {
|
|
40129
|
+
if (!db) return null;
|
|
40130
|
+
try {
|
|
40131
|
+
return readGateState(db);
|
|
40132
|
+
} catch {
|
|
40133
|
+
return null;
|
|
40134
|
+
}
|
|
40135
|
+
}
|
|
40136
|
+
function needsSnapshot(entry, mode, nowMs, seenGeneration) {
|
|
40137
|
+
if (!entry) return true;
|
|
40138
|
+
if (mode === "required") return seenGeneration === null || entry.generation <= seenGeneration;
|
|
40139
|
+
const elapsed = nowMs - entry.startedAt;
|
|
40140
|
+
return elapsed < 0 || elapsed >= SNAPSHOT_DEBOUNCE_MS;
|
|
40141
|
+
}
|
|
40142
|
+
async function runGatedSnapshot(opts, snapshot) {
|
|
40143
|
+
const now = opts.now ?? Date.now;
|
|
40144
|
+
const mode = opts.mode ?? "routine";
|
|
40145
|
+
const db = opts.stateDb;
|
|
40146
|
+
const none = (skipped, error48) => ({
|
|
40147
|
+
snapshotted: [],
|
|
40148
|
+
linked: [],
|
|
40149
|
+
absent: [],
|
|
40150
|
+
failed: [],
|
|
40151
|
+
skipped,
|
|
40152
|
+
...error48 !== void 0 && { error: error48 }
|
|
40153
|
+
});
|
|
40154
|
+
const entryState = tryReadGateState(db);
|
|
40155
|
+
const seenGeneration = mode === "required" ? opts.seenGeneration ?? entryState?.generation ?? null : null;
|
|
40156
|
+
if (mode === "routine") {
|
|
40157
|
+
if (!entryState) return none("state-unavailable");
|
|
40158
|
+
const nowMs = now();
|
|
40159
|
+
if (!opts.prefixes.some((p) => needsSnapshot(entryState.prefixes[p], mode, nowMs, null))) {
|
|
40160
|
+
return none("debounced");
|
|
40161
|
+
}
|
|
40162
|
+
}
|
|
40163
|
+
const lockPath2 = join25(opts.backupDir, GATE_LOCK_BASENAME);
|
|
40164
|
+
let release;
|
|
40165
|
+
try {
|
|
40166
|
+
release = await acquireLock(lockPath2, {
|
|
40167
|
+
retries: mode === "required" ? opts.lockWaitRetries ?? SNAPSHOT_LOCK_WAIT_RETRIES : 0,
|
|
40168
|
+
stale: SNAPSHOT_LOCK_STALE_MS,
|
|
40169
|
+
// A compromised lock (e.g. the backup dir was removed) must not throw
|
|
40170
|
+
// from a timer and crash the host process.
|
|
40171
|
+
onCompromised: () => {
|
|
40172
|
+
}
|
|
40173
|
+
});
|
|
40174
|
+
} catch {
|
|
40175
|
+
if (mode === "routine") return none("in-flight");
|
|
40176
|
+
return none(
|
|
40177
|
+
"lock-timeout",
|
|
40178
|
+
`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.`
|
|
40179
|
+
);
|
|
40180
|
+
}
|
|
40181
|
+
try {
|
|
40182
|
+
try {
|
|
40183
|
+
opts.onLockAcquired?.();
|
|
40184
|
+
} catch {
|
|
40185
|
+
}
|
|
40186
|
+
const state = tryReadGateState(db);
|
|
40187
|
+
if (!state && mode === "routine") return none("state-unavailable");
|
|
40188
|
+
const startMs = now();
|
|
40189
|
+
const admitted = mode === "required" && opts.alwaysSnapshot === true ? [...opts.prefixes] : opts.prefixes.filter(
|
|
40190
|
+
(p) => needsSnapshot(state?.prefixes[p], mode, startMs, seenGeneration)
|
|
40191
|
+
);
|
|
40192
|
+
if (admitted.length === 0) return none(mode === "routine" ? "debounced" : "covered");
|
|
40193
|
+
const generation = (state?.generation ?? 0) + 1;
|
|
40194
|
+
let claimed = false;
|
|
40195
|
+
if (db && state) {
|
|
40196
|
+
try {
|
|
40197
|
+
writeGateState(db, { ...state, generation });
|
|
40198
|
+
claimed = true;
|
|
40199
|
+
} catch {
|
|
40200
|
+
}
|
|
40201
|
+
}
|
|
40202
|
+
const snapshotted = [];
|
|
40203
|
+
const linked = [];
|
|
40204
|
+
const absent = [];
|
|
40205
|
+
const failed = [];
|
|
40206
|
+
for (const prefix of admitted) {
|
|
40207
|
+
try {
|
|
40208
|
+
const outcome = await snapshot(prefix);
|
|
40209
|
+
if (outcome === "absent") absent.push(prefix);
|
|
40210
|
+
else {
|
|
40211
|
+
snapshotted.push(prefix);
|
|
40212
|
+
if (outcome === "linked") linked.push(prefix);
|
|
40213
|
+
}
|
|
40214
|
+
} catch {
|
|
40215
|
+
failed.push(prefix);
|
|
40216
|
+
}
|
|
40217
|
+
}
|
|
40218
|
+
const satisfied = [...snapshotted, ...absent];
|
|
40219
|
+
if (db && claimed && satisfied.length > 0) {
|
|
40220
|
+
try {
|
|
40221
|
+
const latest = readGateState(db);
|
|
40222
|
+
const completedAt = now();
|
|
40223
|
+
latest.generation = Math.max(latest.generation, generation);
|
|
40224
|
+
for (const p of satisfied) {
|
|
40225
|
+
latest.prefixes[p] = { generation, startedAt: startMs, completedAt };
|
|
40226
|
+
}
|
|
40227
|
+
writeGateState(db, latest);
|
|
40228
|
+
} catch {
|
|
40229
|
+
}
|
|
40230
|
+
}
|
|
40231
|
+
return { snapshotted, linked, absent, failed, skipped: null };
|
|
40232
|
+
} finally {
|
|
40233
|
+
await release().catch(() => {
|
|
40234
|
+
});
|
|
40235
|
+
}
|
|
40236
|
+
}
|
|
40237
|
+
function isValidStamp(ymd, hms) {
|
|
40238
|
+
const y = Number(ymd.slice(0, 4));
|
|
40239
|
+
const mo = Number(ymd.slice(4, 6));
|
|
40240
|
+
const d = Number(ymd.slice(6, 8));
|
|
40241
|
+
const h = Number(hms.slice(0, 2));
|
|
40242
|
+
const mi = Number(hms.slice(2, 4));
|
|
40243
|
+
const se = Number(hms.slice(4, 6));
|
|
40244
|
+
if (mo < 1 || mo > 12 || d < 1 || h > 23 || mi > 59 || se > 59) return false;
|
|
40245
|
+
const probe = new Date(Date.UTC(y, mo - 1, d));
|
|
40246
|
+
return probe.getUTCFullYear() === y && probe.getUTCMonth() === mo - 1 && probe.getUTCDate() === d;
|
|
40247
|
+
}
|
|
40248
|
+
function selectSnapshotsToKeep(names, ctx = {}) {
|
|
40249
|
+
const policy = ctx.policy ?? DEFAULT_SNAPSHOT_RETENTION;
|
|
40250
|
+
const keep = /* @__PURE__ */ new Set();
|
|
40251
|
+
const stamped = [];
|
|
40252
|
+
for (const name15 of names) {
|
|
40253
|
+
const m = STAMP_PATTERN.exec(name15);
|
|
40254
|
+
const ymd = m?.[1];
|
|
40255
|
+
const hms = m?.[2];
|
|
40256
|
+
if (!ymd || !hms || !isValidStamp(ymd, hms)) {
|
|
40257
|
+
keep.add(name15);
|
|
40258
|
+
continue;
|
|
40259
|
+
}
|
|
40260
|
+
const stamp = `${ymd}-${hms}`;
|
|
40261
|
+
if (ctx.nowStamp !== void 0 && stamp > ctx.nowStamp) {
|
|
40262
|
+
keep.add(name15);
|
|
40263
|
+
continue;
|
|
40264
|
+
}
|
|
40265
|
+
stamped.push({ name: name15, stamp });
|
|
40266
|
+
}
|
|
40267
|
+
if (ctx.pinned !== void 0 && names.includes(ctx.pinned)) keep.add(ctx.pinned);
|
|
40268
|
+
stamped.sort((a, b) => a.stamp < b.stamp ? 1 : a.stamp > b.stamp ? -1 : 0);
|
|
40269
|
+
for (const s of stamped.slice(0, policy.latest)) keep.add(s.name);
|
|
40270
|
+
const keepNewestPerBucket = (bucketOf, slots) => {
|
|
40271
|
+
const seen = /* @__PURE__ */ new Set();
|
|
40272
|
+
for (const s of stamped) {
|
|
40273
|
+
const bucket = bucketOf(s.stamp);
|
|
40274
|
+
if (seen.has(bucket)) continue;
|
|
40275
|
+
if (seen.size >= slots) break;
|
|
40276
|
+
seen.add(bucket);
|
|
40277
|
+
keep.add(s.name);
|
|
40278
|
+
}
|
|
40279
|
+
};
|
|
40280
|
+
keepNewestPerBucket(
|
|
40281
|
+
(stamp) => `${stamp.slice(0, 11)}q${Math.floor(Number(stamp.slice(11, 13)) / 15)}`,
|
|
40282
|
+
policy.quarterHourly
|
|
40283
|
+
);
|
|
40284
|
+
keepNewestPerBucket((stamp) => stamp.slice(0, 11), policy.hourly);
|
|
40285
|
+
keepNewestPerBucket((stamp) => stamp.slice(0, 8), policy.daily);
|
|
40286
|
+
return keep;
|
|
40287
|
+
}
|
|
40288
|
+
var SNAPSHOT_DEBOUNCE_MS, SNAPSHOT_LOCK_STALE_MS, SNAPSHOT_LOCK_WAIT_RETRIES, SNAPSHOT_GATE_META_KEY, GATE_LOCK_BASENAME, DEFAULT_SNAPSHOT_RETENTION, STAMP_PATTERN;
|
|
40289
|
+
var init_snapshot_gate = __esm({
|
|
40290
|
+
"packages/core/src/store/snapshot-gate.ts"() {
|
|
40291
|
+
"use strict";
|
|
40292
|
+
init_lock();
|
|
40293
|
+
init_twin_collapse();
|
|
40294
|
+
SNAPSHOT_DEBOUNCE_MS = 5 * 6e4;
|
|
40295
|
+
SNAPSHOT_LOCK_STALE_MS = 10 * 6e4;
|
|
40296
|
+
SNAPSHOT_LOCK_WAIT_RETRIES = 90;
|
|
40297
|
+
SNAPSHOT_GATE_META_KEY = "sqlite_snapshot_gate";
|
|
40298
|
+
GATE_LOCK_BASENAME = ".snapshot-gate";
|
|
40299
|
+
DEFAULT_SNAPSHOT_RETENTION = {
|
|
40300
|
+
latest: 1,
|
|
40301
|
+
quarterHourly: 3,
|
|
40302
|
+
hourly: 3,
|
|
40303
|
+
daily: 3
|
|
40304
|
+
};
|
|
40305
|
+
STAMP_PATTERN = /-(\d{8})-(\d{6})\.db$/;
|
|
40306
|
+
}
|
|
40307
|
+
});
|
|
40308
|
+
|
|
40229
40309
|
// packages/core/src/store/data-safety-central.ts
|
|
40230
40310
|
import { eq as eq7 } from "drizzle-orm";
|
|
40231
40311
|
function inSafetyScope(cwd, operation) {
|
|
@@ -41527,7 +41607,8 @@ __export(sqlite_exports, {
|
|
|
41527
41607
|
resetDbState: () => resetDbState,
|
|
41528
41608
|
resolveMigrationsFolder: () => resolveMigrationsFolder,
|
|
41529
41609
|
schema: () => tasks_schema_exports,
|
|
41530
|
-
seedTasksMeta: () => seedTasksMeta
|
|
41610
|
+
seedTasksMeta: () => seedTasksMeta,
|
|
41611
|
+
worktreeRecoveryRefusal: () => worktreeRecoveryRefusal
|
|
41531
41612
|
});
|
|
41532
41613
|
import { copyFileSync as copyFileSync4, existsSync as existsSync21, realpathSync as realpathSync7, renameSync as renameSync7, unlinkSync as unlinkSync4 } from "node:fs";
|
|
41533
41614
|
import { createRequire as createRequire7 } from "node:module";
|
|
@@ -41578,6 +41659,12 @@ function isSameDirectory(a, b) {
|
|
|
41578
41659
|
return false;
|
|
41579
41660
|
}
|
|
41580
41661
|
}
|
|
41662
|
+
function worktreeRecoveryRefusal(dbPath) {
|
|
41663
|
+
const storeCleoDir = dirname15(dbPath);
|
|
41664
|
+
const root = dirname15(storeCleoDir);
|
|
41665
|
+
if (basename8(storeCleoDir) !== ".cleo" || !isGitLinkedCheckout(root)) return null;
|
|
41666
|
+
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).`;
|
|
41667
|
+
}
|
|
41581
41668
|
async function autoRecoverFromBackup(nativeDb, dbPath, cwd) {
|
|
41582
41669
|
const { openNativeDatabase: openNativeDatabase2 } = await Promise.resolve().then(() => (init_sqlite_native(), sqlite_native_exports));
|
|
41583
41670
|
const log15 = getLogger("sqlite");
|
|
@@ -41587,11 +41674,9 @@ async function autoRecoverFromBackup(nativeDb, dbPath, cwd) {
|
|
|
41587
41674
|
const taskCount = countResult?.cnt ?? 0;
|
|
41588
41675
|
if (taskCount > 0) return false;
|
|
41589
41676
|
const storeCleoDir = dirname15(dbPath);
|
|
41590
|
-
|
|
41591
|
-
|
|
41592
|
-
|
|
41593
|
-
"Auto-recovery refused: the empty store lives inside a git worktree (T12460). Worktrees must resolve the parent project store."
|
|
41594
|
-
);
|
|
41677
|
+
const worktreeRefusal = worktreeRecoveryRefusal(dbPath);
|
|
41678
|
+
if (worktreeRefusal !== null) {
|
|
41679
|
+
log15.warn({ dbPath }, worktreeRefusal);
|
|
41595
41680
|
return false;
|
|
41596
41681
|
}
|
|
41597
41682
|
const backups = listSqliteBackups(cwd);
|