@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
|
@@ -10509,7 +10509,7 @@ var init_operations_registry = __esm({
|
|
|
10509
10509
|
type: "string",
|
|
10510
10510
|
required: false,
|
|
10511
10511
|
enum: DOCS_LIFECYCLE_STATUSES,
|
|
10512
|
-
description: `Override the new lifecycle status. Defaults to "draft" on every update. Valid: ${DOCS_LIFECYCLE_STATUSES.join("|")}.`
|
|
10512
|
+
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("|")}.`
|
|
10513
10513
|
},
|
|
10514
10514
|
{
|
|
10515
10515
|
name: "dryRun",
|
|
@@ -12996,11 +12996,13 @@ var init_evidence_atom_schema = __esm({
|
|
|
12996
12996
|
});
|
|
12997
12997
|
prAtomSchema = z10.object({
|
|
12998
12998
|
kind: z10.literal("pr"),
|
|
12999
|
-
prNumber: z10.number().int().positive("pr atom requires a positive integer PR number")
|
|
12999
|
+
prNumber: z10.number().int().positive("pr atom requires a positive integer PR number"),
|
|
13000
|
+
componentPrNumber: z10.number().int().positive().optional()
|
|
13000
13001
|
});
|
|
13001
13002
|
ciAtomSchema = z10.object({
|
|
13002
13003
|
kind: z10.literal("ci"),
|
|
13003
|
-
prNumber: z10.number().int().positive("ci atom requires a positive integer PR number")
|
|
13004
|
+
prNumber: z10.number().int().positive("ci atom requires a positive integer PR number"),
|
|
13005
|
+
componentPrNumber: z10.number().int().positive().optional()
|
|
13004
13006
|
});
|
|
13005
13007
|
locDropAtomSchema = z10.object({
|
|
13006
13008
|
kind: z10.literal("loc-drop"),
|
|
@@ -18640,6 +18642,22 @@ function linkedWorktreeMainRoot(dir) {
|
|
|
18640
18642
|
if (basename2(commonDir) === ".git") return dirname2(commonDir);
|
|
18641
18643
|
return readCoreWorktree(commonDir);
|
|
18642
18644
|
}
|
|
18645
|
+
function describeWorktreeOwner(dir) {
|
|
18646
|
+
const mainRoot = linkedWorktreeMainRoot(dir);
|
|
18647
|
+
if (mainRoot === null) return "owning repository unknown (unreadable gitlink)";
|
|
18648
|
+
let projectId = null;
|
|
18649
|
+
try {
|
|
18650
|
+
const parsed = JSON.parse(
|
|
18651
|
+
readFileSync2(join3(mainRoot, ".cleo", "project-info.json"), "utf-8")
|
|
18652
|
+
);
|
|
18653
|
+
if (typeof parsed === "object" && parsed !== null && "projectId" in parsed) {
|
|
18654
|
+
const value = parsed.projectId;
|
|
18655
|
+
if (typeof value === "string" && value !== "") projectId = value;
|
|
18656
|
+
}
|
|
18657
|
+
} catch {
|
|
18658
|
+
}
|
|
18659
|
+
return projectId === null ? `owning repository ${mainRoot} (not an initialised CLEO project)` : `owning project ${mainRoot} (projectId ${projectId})`;
|
|
18660
|
+
}
|
|
18643
18661
|
function readCoreWorktree(gitDir) {
|
|
18644
18662
|
try {
|
|
18645
18663
|
const config = readFileSync2(join3(gitDir, "config"), "utf-8");
|
|
@@ -25748,9 +25766,10 @@ function assertStorePathIsNotWorktreeResident(role, dbPath) {
|
|
|
25748
25766
|
);
|
|
25749
25767
|
return;
|
|
25750
25768
|
}
|
|
25769
|
+
const owner = describeWorktreeOwner(projectRoot);
|
|
25751
25770
|
throw new CleoError(
|
|
25752
25771
|
8 /* CONFIG_ERROR */,
|
|
25753
|
-
`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.`,
|
|
25772
|
+
`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.`,
|
|
25754
25773
|
{
|
|
25755
25774
|
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.`
|
|
25756
25775
|
}
|
|
@@ -32337,216 +32356,6 @@ var init_memory_schema = __esm({
|
|
|
32337
32356
|
}
|
|
32338
32357
|
});
|
|
32339
32358
|
|
|
32340
|
-
// packages/core/src/store/snapshot-gate.ts
|
|
32341
|
-
import { join as join20 } from "node:path";
|
|
32342
|
-
function readGateState(db) {
|
|
32343
|
-
const row = db.prepare("SELECT value FROM tasks_schema_meta WHERE key = ?").get(SNAPSHOT_GATE_META_KEY);
|
|
32344
|
-
const state = { generation: 0, prefixes: {} };
|
|
32345
|
-
if (!row) return state;
|
|
32346
|
-
try {
|
|
32347
|
-
const parsed = JSON.parse(row.value);
|
|
32348
|
-
if (parsed === null || typeof parsed !== "object") return state;
|
|
32349
|
-
if (typeof parsed.generation === "number" && Number.isSafeInteger(parsed.generation)) {
|
|
32350
|
-
state.generation = parsed.generation;
|
|
32351
|
-
}
|
|
32352
|
-
for (const [prefix, e] of Object.entries(parsed.prefixes ?? {})) {
|
|
32353
|
-
if (e && typeof e.generation === "number" && typeof e.startedAt === "number" && typeof e.completedAt === "number") {
|
|
32354
|
-
state.prefixes[prefix] = {
|
|
32355
|
-
generation: e.generation,
|
|
32356
|
-
startedAt: e.startedAt,
|
|
32357
|
-
completedAt: e.completedAt
|
|
32358
|
-
};
|
|
32359
|
-
}
|
|
32360
|
-
}
|
|
32361
|
-
} catch {
|
|
32362
|
-
}
|
|
32363
|
-
return state;
|
|
32364
|
-
}
|
|
32365
|
-
function writeGateState(db, state) {
|
|
32366
|
-
db.prepare(
|
|
32367
|
-
"INSERT INTO tasks_schema_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value"
|
|
32368
|
-
).run(SNAPSHOT_GATE_META_KEY, JSON.stringify(state));
|
|
32369
|
-
}
|
|
32370
|
-
function tryReadGateState(db) {
|
|
32371
|
-
if (!db) return null;
|
|
32372
|
-
try {
|
|
32373
|
-
return readGateState(db);
|
|
32374
|
-
} catch {
|
|
32375
|
-
return null;
|
|
32376
|
-
}
|
|
32377
|
-
}
|
|
32378
|
-
function needsSnapshot(entry, mode, nowMs, seenGeneration) {
|
|
32379
|
-
if (!entry) return true;
|
|
32380
|
-
if (mode === "required") return seenGeneration === null || entry.generation <= seenGeneration;
|
|
32381
|
-
const elapsed = nowMs - entry.startedAt;
|
|
32382
|
-
return elapsed < 0 || elapsed >= SNAPSHOT_DEBOUNCE_MS;
|
|
32383
|
-
}
|
|
32384
|
-
async function runGatedSnapshot(opts, snapshot) {
|
|
32385
|
-
const now = opts.now ?? Date.now;
|
|
32386
|
-
const mode = opts.mode ?? "routine";
|
|
32387
|
-
const db = opts.stateDb;
|
|
32388
|
-
const none = (skipped, error) => ({
|
|
32389
|
-
snapshotted: [],
|
|
32390
|
-
linked: [],
|
|
32391
|
-
absent: [],
|
|
32392
|
-
failed: [],
|
|
32393
|
-
skipped,
|
|
32394
|
-
...error !== void 0 && { error }
|
|
32395
|
-
});
|
|
32396
|
-
const entryState = tryReadGateState(db);
|
|
32397
|
-
const seenGeneration = mode === "required" ? opts.seenGeneration ?? entryState?.generation ?? null : null;
|
|
32398
|
-
if (mode === "routine") {
|
|
32399
|
-
if (!entryState) return none("state-unavailable");
|
|
32400
|
-
const nowMs = now();
|
|
32401
|
-
if (!opts.prefixes.some((p) => needsSnapshot(entryState.prefixes[p], mode, nowMs, null))) {
|
|
32402
|
-
return none("debounced");
|
|
32403
|
-
}
|
|
32404
|
-
}
|
|
32405
|
-
const lockPath2 = join20(opts.backupDir, GATE_LOCK_BASENAME);
|
|
32406
|
-
let release;
|
|
32407
|
-
try {
|
|
32408
|
-
release = await acquireLock(lockPath2, {
|
|
32409
|
-
retries: mode === "required" ? opts.lockWaitRetries ?? SNAPSHOT_LOCK_WAIT_RETRIES : 0,
|
|
32410
|
-
stale: SNAPSHOT_LOCK_STALE_MS,
|
|
32411
|
-
// A compromised lock (e.g. the backup dir was removed) must not throw
|
|
32412
|
-
// from a timer and crash the host process.
|
|
32413
|
-
onCompromised: () => {
|
|
32414
|
-
}
|
|
32415
|
-
});
|
|
32416
|
-
} catch {
|
|
32417
|
-
if (mode === "routine") return none("in-flight");
|
|
32418
|
-
return none(
|
|
32419
|
-
"lock-timeout",
|
|
32420
|
-
`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.`
|
|
32421
|
-
);
|
|
32422
|
-
}
|
|
32423
|
-
try {
|
|
32424
|
-
try {
|
|
32425
|
-
opts.onLockAcquired?.();
|
|
32426
|
-
} catch {
|
|
32427
|
-
}
|
|
32428
|
-
const state = tryReadGateState(db);
|
|
32429
|
-
if (!state && mode === "routine") return none("state-unavailable");
|
|
32430
|
-
const startMs = now();
|
|
32431
|
-
const admitted = mode === "required" && opts.alwaysSnapshot === true ? [...opts.prefixes] : opts.prefixes.filter(
|
|
32432
|
-
(p) => needsSnapshot(state?.prefixes[p], mode, startMs, seenGeneration)
|
|
32433
|
-
);
|
|
32434
|
-
if (admitted.length === 0) return none(mode === "routine" ? "debounced" : "covered");
|
|
32435
|
-
const generation = (state?.generation ?? 0) + 1;
|
|
32436
|
-
let claimed = false;
|
|
32437
|
-
if (db && state) {
|
|
32438
|
-
try {
|
|
32439
|
-
writeGateState(db, { ...state, generation });
|
|
32440
|
-
claimed = true;
|
|
32441
|
-
} catch {
|
|
32442
|
-
}
|
|
32443
|
-
}
|
|
32444
|
-
const snapshotted = [];
|
|
32445
|
-
const linked = [];
|
|
32446
|
-
const absent = [];
|
|
32447
|
-
const failed = [];
|
|
32448
|
-
for (const prefix of admitted) {
|
|
32449
|
-
try {
|
|
32450
|
-
const outcome = await snapshot(prefix);
|
|
32451
|
-
if (outcome === "absent") absent.push(prefix);
|
|
32452
|
-
else {
|
|
32453
|
-
snapshotted.push(prefix);
|
|
32454
|
-
if (outcome === "linked") linked.push(prefix);
|
|
32455
|
-
}
|
|
32456
|
-
} catch {
|
|
32457
|
-
failed.push(prefix);
|
|
32458
|
-
}
|
|
32459
|
-
}
|
|
32460
|
-
const satisfied = [...snapshotted, ...absent];
|
|
32461
|
-
if (db && claimed && satisfied.length > 0) {
|
|
32462
|
-
try {
|
|
32463
|
-
const latest = readGateState(db);
|
|
32464
|
-
const completedAt = now();
|
|
32465
|
-
latest.generation = Math.max(latest.generation, generation);
|
|
32466
|
-
for (const p of satisfied) {
|
|
32467
|
-
latest.prefixes[p] = { generation, startedAt: startMs, completedAt };
|
|
32468
|
-
}
|
|
32469
|
-
writeGateState(db, latest);
|
|
32470
|
-
} catch {
|
|
32471
|
-
}
|
|
32472
|
-
}
|
|
32473
|
-
return { snapshotted, linked, absent, failed, skipped: null };
|
|
32474
|
-
} finally {
|
|
32475
|
-
await release().catch(() => {
|
|
32476
|
-
});
|
|
32477
|
-
}
|
|
32478
|
-
}
|
|
32479
|
-
function isValidStamp(ymd, hms) {
|
|
32480
|
-
const y = Number(ymd.slice(0, 4));
|
|
32481
|
-
const mo = Number(ymd.slice(4, 6));
|
|
32482
|
-
const d = Number(ymd.slice(6, 8));
|
|
32483
|
-
const h = Number(hms.slice(0, 2));
|
|
32484
|
-
const mi = Number(hms.slice(2, 4));
|
|
32485
|
-
const se = Number(hms.slice(4, 6));
|
|
32486
|
-
if (mo < 1 || mo > 12 || d < 1 || h > 23 || mi > 59 || se > 59) return false;
|
|
32487
|
-
const probe = new Date(Date.UTC(y, mo - 1, d));
|
|
32488
|
-
return probe.getUTCFullYear() === y && probe.getUTCMonth() === mo - 1 && probe.getUTCDate() === d;
|
|
32489
|
-
}
|
|
32490
|
-
function selectSnapshotsToKeep(names, ctx = {}) {
|
|
32491
|
-
const policy = ctx.policy ?? DEFAULT_SNAPSHOT_RETENTION;
|
|
32492
|
-
const keep = /* @__PURE__ */ new Set();
|
|
32493
|
-
const stamped = [];
|
|
32494
|
-
for (const name2 of names) {
|
|
32495
|
-
const m = STAMP_PATTERN.exec(name2);
|
|
32496
|
-
const ymd = m?.[1];
|
|
32497
|
-
const hms = m?.[2];
|
|
32498
|
-
if (!ymd || !hms || !isValidStamp(ymd, hms)) {
|
|
32499
|
-
keep.add(name2);
|
|
32500
|
-
continue;
|
|
32501
|
-
}
|
|
32502
|
-
const stamp = `${ymd}-${hms}`;
|
|
32503
|
-
if (ctx.nowStamp !== void 0 && stamp > ctx.nowStamp) {
|
|
32504
|
-
keep.add(name2);
|
|
32505
|
-
continue;
|
|
32506
|
-
}
|
|
32507
|
-
stamped.push({ name: name2, stamp });
|
|
32508
|
-
}
|
|
32509
|
-
if (ctx.pinned !== void 0 && names.includes(ctx.pinned)) keep.add(ctx.pinned);
|
|
32510
|
-
stamped.sort((a, b) => a.stamp < b.stamp ? 1 : a.stamp > b.stamp ? -1 : 0);
|
|
32511
|
-
for (const s of stamped.slice(0, policy.latest)) keep.add(s.name);
|
|
32512
|
-
const keepNewestPerBucket = (bucketOf, slots) => {
|
|
32513
|
-
const seen = /* @__PURE__ */ new Set();
|
|
32514
|
-
for (const s of stamped) {
|
|
32515
|
-
const bucket = bucketOf(s.stamp);
|
|
32516
|
-
if (seen.has(bucket)) continue;
|
|
32517
|
-
if (seen.size >= slots) break;
|
|
32518
|
-
seen.add(bucket);
|
|
32519
|
-
keep.add(s.name);
|
|
32520
|
-
}
|
|
32521
|
-
};
|
|
32522
|
-
keepNewestPerBucket(
|
|
32523
|
-
(stamp) => `${stamp.slice(0, 11)}q${Math.floor(Number(stamp.slice(11, 13)) / 15)}`,
|
|
32524
|
-
policy.quarterHourly
|
|
32525
|
-
);
|
|
32526
|
-
keepNewestPerBucket((stamp) => stamp.slice(0, 11), policy.hourly);
|
|
32527
|
-
keepNewestPerBucket((stamp) => stamp.slice(0, 8), policy.daily);
|
|
32528
|
-
return keep;
|
|
32529
|
-
}
|
|
32530
|
-
var SNAPSHOT_DEBOUNCE_MS, SNAPSHOT_LOCK_STALE_MS, SNAPSHOT_LOCK_WAIT_RETRIES, SNAPSHOT_GATE_META_KEY, GATE_LOCK_BASENAME, DEFAULT_SNAPSHOT_RETENTION, STAMP_PATTERN;
|
|
32531
|
-
var init_snapshot_gate = __esm({
|
|
32532
|
-
"packages/core/src/store/snapshot-gate.ts"() {
|
|
32533
|
-
"use strict";
|
|
32534
|
-
init_lock();
|
|
32535
|
-
SNAPSHOT_DEBOUNCE_MS = 5 * 6e4;
|
|
32536
|
-
SNAPSHOT_LOCK_STALE_MS = 10 * 6e4;
|
|
32537
|
-
SNAPSHOT_LOCK_WAIT_RETRIES = 90;
|
|
32538
|
-
SNAPSHOT_GATE_META_KEY = "sqlite_snapshot_gate";
|
|
32539
|
-
GATE_LOCK_BASENAME = ".snapshot-gate";
|
|
32540
|
-
DEFAULT_SNAPSHOT_RETENTION = {
|
|
32541
|
-
latest: 1,
|
|
32542
|
-
quarterHourly: 3,
|
|
32543
|
-
hourly: 3,
|
|
32544
|
-
daily: 3
|
|
32545
|
-
};
|
|
32546
|
-
STAMP_PATTERN = /-(\d{8})-(\d{6})\.db$/;
|
|
32547
|
-
}
|
|
32548
|
-
});
|
|
32549
|
-
|
|
32550
32359
|
// packages/core/src/store/twin-collapse.ts
|
|
32551
32360
|
import { createHash as createHash3 } from "node:crypto";
|
|
32552
32361
|
function hasMainTable(db, name2) {
|
|
@@ -32623,6 +32432,22 @@ function mergeSchemaMetaValue(key, bare, twin) {
|
|
|
32623
32432
|
const field = SCHEMA_META_COUNTER_FIELDS[key];
|
|
32624
32433
|
return field === void 0 ? bare : withMaxCounter(bare, twin, field);
|
|
32625
32434
|
}
|
|
32435
|
+
function mirrorCounterToBare(db, key) {
|
|
32436
|
+
if (SCHEMA_META_COUNTER_FIELDS[key] === void 0) return;
|
|
32437
|
+
if (!hasMainTable(db, "schema_meta")) return;
|
|
32438
|
+
const twin = readKv(db, "tasks_schema_meta", key);
|
|
32439
|
+
if (twin === void 0) return;
|
|
32440
|
+
const bare = readKv(db, "schema_meta", key);
|
|
32441
|
+
const next = bare === void 0 ? twin : mergeSchemaMetaValue(key, bare, twin);
|
|
32442
|
+
if (next === null || next === bare) return;
|
|
32443
|
+
writeKv(db, "schema_meta", key, next);
|
|
32444
|
+
}
|
|
32445
|
+
function bareCounterOf(db, key) {
|
|
32446
|
+
const name2 = SCHEMA_META_COUNTER_FIELDS[key];
|
|
32447
|
+
if (name2 === void 0 || !hasMainTable(db, "schema_meta")) return 0;
|
|
32448
|
+
const bare = readKv(db, "schema_meta", key);
|
|
32449
|
+
return (bare === void 0 ? void 0 : counterOf(bare, name2)) ?? 0;
|
|
32450
|
+
}
|
|
32626
32451
|
function kvRows(db, table, schema = "main") {
|
|
32627
32452
|
const rows = db.prepare(`SELECT key, value FROM ${schema}.${table}`).all();
|
|
32628
32453
|
return new Map(rows.map((r) => [r.key, r.value]));
|
|
@@ -32755,6 +32580,39 @@ function applyStickyRows(db, schema, plan) {
|
|
|
32755
32580
|
}
|
|
32756
32581
|
return { inserted, replaced: 0, deleted };
|
|
32757
32582
|
}
|
|
32583
|
+
function tagsOf(tagsJson) {
|
|
32584
|
+
if (!tagsJson) return [];
|
|
32585
|
+
try {
|
|
32586
|
+
const parsed = JSON.parse(tagsJson);
|
|
32587
|
+
if (!Array.isArray(parsed)) return [];
|
|
32588
|
+
return [...new Set(parsed.filter((t) => typeof t === "string" && t.length > 0))];
|
|
32589
|
+
} catch {
|
|
32590
|
+
return [];
|
|
32591
|
+
}
|
|
32592
|
+
}
|
|
32593
|
+
function syncStickyJunction(db, schema) {
|
|
32594
|
+
const notes = db.prepare("SELECT id, tags_json FROM main.brain_sticky_notes").all();
|
|
32595
|
+
const have = /* @__PURE__ */ new Map();
|
|
32596
|
+
for (const r of db.prepare(`SELECT sticky_id, tag FROM ${schema}.brain_sticky_tags`).all()) {
|
|
32597
|
+
const set = have.get(r.sticky_id) ?? /* @__PURE__ */ new Set();
|
|
32598
|
+
set.add(r.tag);
|
|
32599
|
+
have.set(r.sticky_id, set);
|
|
32600
|
+
}
|
|
32601
|
+
const insert = db.prepare(
|
|
32602
|
+
`INSERT OR IGNORE INTO ${schema}.brain_sticky_tags (sticky_id, tag) VALUES (?, ?)`
|
|
32603
|
+
);
|
|
32604
|
+
const del = db.prepare(`DELETE FROM ${schema}.brain_sticky_tags WHERE sticky_id = ? AND tag = ?`);
|
|
32605
|
+
let inserted = 0;
|
|
32606
|
+
let deleted = 0;
|
|
32607
|
+
for (const note of notes) {
|
|
32608
|
+
const want = new Set(tagsOf(note.tags_json));
|
|
32609
|
+
const current = have.get(note.id) ?? /* @__PURE__ */ new Set();
|
|
32610
|
+
for (const tag of want)
|
|
32611
|
+
if (!current.has(tag)) inserted += Number(insert.run(note.id, tag).changes);
|
|
32612
|
+
for (const tag of current) if (!want.has(tag)) deleted += Number(del.run(note.id, tag).changes);
|
|
32613
|
+
}
|
|
32614
|
+
return { inserted, deleted };
|
|
32615
|
+
}
|
|
32758
32616
|
function twinCollapseFailureOf(db) {
|
|
32759
32617
|
return degraded.get(db);
|
|
32760
32618
|
}
|
|
@@ -32964,7 +32822,7 @@ function collapseTwinTables(nativeDb, dbPath, options = {}) {
|
|
|
32964
32822
|
}
|
|
32965
32823
|
return inOrder();
|
|
32966
32824
|
}
|
|
32967
|
-
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;
|
|
32825
|
+
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;
|
|
32968
32826
|
var init_twin_collapse = __esm({
|
|
32969
32827
|
"packages/core/src/store/twin-collapse.ts"() {
|
|
32970
32828
|
"use strict";
|
|
@@ -32972,18 +32830,18 @@ var init_twin_collapse = __esm({
|
|
|
32972
32830
|
init_errors2();
|
|
32973
32831
|
init_logger2();
|
|
32974
32832
|
init_pre_repair_snapshot();
|
|
32975
|
-
init_snapshot_gate();
|
|
32976
32833
|
log6 = getLogger("twin-collapse");
|
|
32977
32834
|
TWIN_COLLAPSE_MARKER_PREFIX = "twin_collapse:";
|
|
32978
32835
|
TWIN_COLLAPSE_FAILURE_PREFIX = "twin_collapse_failed:";
|
|
32836
|
+
SNAPSHOT_GATE_KEY = "sqlite_snapshot_gate";
|
|
32979
32837
|
SCHEMA_META_COUNTER_FIELDS = {
|
|
32980
32838
|
task_id_sequence: "counter",
|
|
32981
|
-
[
|
|
32839
|
+
[SNAPSHOT_GATE_KEY]: "generation",
|
|
32982
32840
|
file_meta: "generation"
|
|
32983
32841
|
};
|
|
32984
32842
|
SCHEMA_META_NEVER_DELETED = /* @__PURE__ */ new Set([
|
|
32985
32843
|
"task_id_sequence",
|
|
32986
|
-
|
|
32844
|
+
SNAPSHOT_GATE_KEY
|
|
32987
32845
|
]);
|
|
32988
32846
|
MAX_CONFLICTS = 50;
|
|
32989
32847
|
sha = (value) => createHash3("sha256").update(value).digest("hex");
|
|
@@ -33048,7 +32906,12 @@ var init_twin_collapse = __esm({
|
|
|
33048
32906
|
throw new Error(
|
|
33049
32907
|
`sticky_tags collapse did not verify: ${missing} missing, ${lingering} not removed`
|
|
33050
32908
|
);
|
|
33051
|
-
|
|
32909
|
+
const synced = syncStickyJunction(db, "main");
|
|
32910
|
+
return {
|
|
32911
|
+
inserted: counts.inserted + synced.inserted,
|
|
32912
|
+
replaced: 0,
|
|
32913
|
+
deleted: counts.deleted + synced.deleted
|
|
32914
|
+
};
|
|
33052
32915
|
},
|
|
33053
32916
|
shadow(db, plan) {
|
|
33054
32917
|
db.exec(
|
|
@@ -33059,6 +32922,7 @@ var init_twin_collapse = __esm({
|
|
|
33059
32922
|
"INSERT INTO temp.brain_sticky_tags (sticky_id, tag) SELECT sticky_id, tag FROM main.brain_sticky_tags"
|
|
33060
32923
|
);
|
|
33061
32924
|
applyStickyRows(db, "temp", plan);
|
|
32925
|
+
syncStickyJunction(db, "temp");
|
|
33062
32926
|
}
|
|
33063
32927
|
};
|
|
33064
32928
|
PAIRS = [SCHEMA_META, STICKY_TAGS];
|
|
@@ -38533,9 +38397,12 @@ async function writeMetaValue(cwd, key, value) {
|
|
|
38533
38397
|
target: tasksSchemaMeta.key,
|
|
38534
38398
|
set: { value: json }
|
|
38535
38399
|
}).run();
|
|
38400
|
+
const native = getNativeTasksDb(cwd);
|
|
38401
|
+
if (native) mirrorCounterToBare(native, key);
|
|
38536
38402
|
}
|
|
38537
38403
|
function advanceTaskIdSequence(nativeDb, floor) {
|
|
38538
38404
|
assertTwinCollapseWritable(nativeDb);
|
|
38405
|
+
const lowest = Math.max(floor, bareCounterOf(nativeDb, "task_id_sequence"));
|
|
38539
38406
|
nativeDb.prepare(`
|
|
38540
38407
|
UPDATE tasks_schema_meta
|
|
38541
38408
|
SET value = json_set(value,
|
|
@@ -38544,7 +38411,8 @@ function advanceTaskIdSequence(nativeDb, floor) {
|
|
|
38544
38411
|
'$.checksum', 'alloc-' || strftime('%s','now')
|
|
38545
38412
|
)
|
|
38546
38413
|
WHERE key = 'task_id_sequence'
|
|
38547
|
-
`).run(
|
|
38414
|
+
`).run(lowest, lowest);
|
|
38415
|
+
mirrorCounterToBare(nativeDb, "task_id_sequence");
|
|
38548
38416
|
const row = nativeDb.prepare(`
|
|
38549
38417
|
SELECT json_extract(value, '$.counter') AS counter
|
|
38550
38418
|
FROM tasks_schema_meta WHERE key = 'task_id_sequence'
|
|
@@ -39588,12 +39456,12 @@ var init_sqlite_data_accessor = __esm({
|
|
|
39588
39456
|
|
|
39589
39457
|
// packages/core/src/sequence/index.ts
|
|
39590
39458
|
import { existsSync as existsSync17, readFileSync as readFileSync5, renameSync as renameSync5 } from "node:fs";
|
|
39591
|
-
import { join as
|
|
39459
|
+
import { join as join20 } from "node:path";
|
|
39592
39460
|
function getLegacySequenceJsonPath(cwd) {
|
|
39593
|
-
return
|
|
39461
|
+
return join20(resolveOrCwd(cwd), ".cleo", ".sequence.json");
|
|
39594
39462
|
}
|
|
39595
39463
|
function getLegacySequencePath(cwd) {
|
|
39596
|
-
return
|
|
39464
|
+
return join20(resolveOrCwd(cwd), ".cleo", ".sequence");
|
|
39597
39465
|
}
|
|
39598
39466
|
function isValidSequenceState(value) {
|
|
39599
39467
|
if (!value || typeof value !== "object") return false;
|
|
@@ -39768,9 +39636,9 @@ var init_sequence = __esm({
|
|
|
39768
39636
|
});
|
|
39769
39637
|
|
|
39770
39638
|
// packages/core/src/store/checkpoint-git-dir.ts
|
|
39771
|
-
import { join as
|
|
39639
|
+
import { join as join21 } from "node:path";
|
|
39772
39640
|
function checkpointGitDir(cleoDir) {
|
|
39773
|
-
return
|
|
39641
|
+
return join21(cleoDir, CHECKPOINT_GIT_DIRNAME);
|
|
39774
39642
|
}
|
|
39775
39643
|
var CHECKPOINT_GIT_DIRNAME;
|
|
39776
39644
|
var init_checkpoint_git_dir = __esm({
|
|
@@ -39784,7 +39652,7 @@ var init_checkpoint_git_dir = __esm({
|
|
|
39784
39652
|
import { execFile as execFile2 } from "node:child_process";
|
|
39785
39653
|
import { existsSync as existsSync18 } from "node:fs";
|
|
39786
39654
|
import { readFile as readFile4, writeFile } from "node:fs/promises";
|
|
39787
|
-
import { join as
|
|
39655
|
+
import { join as join22, resolve as resolve8 } from "node:path";
|
|
39788
39656
|
import { promisify as promisify2 } from "node:util";
|
|
39789
39657
|
function makeCleoGitEnv(cleoDir) {
|
|
39790
39658
|
const abs = resolve8(cleoDir);
|
|
@@ -39808,7 +39676,7 @@ async function cleoGitCommand(args, cleoDir) {
|
|
|
39808
39676
|
}
|
|
39809
39677
|
}
|
|
39810
39678
|
function isCleoGitInitialized(cleoDir) {
|
|
39811
|
-
return existsSync18(
|
|
39679
|
+
return existsSync18(join22(checkpointGitDir(cleoDir), "HEAD"));
|
|
39812
39680
|
}
|
|
39813
39681
|
async function loadStateFileAllowlist(cwd) {
|
|
39814
39682
|
try {
|
|
@@ -39852,25 +39720,25 @@ async function isCleoGitRepo(cleoDir) {
|
|
|
39852
39720
|
return result.success && (result.stdout === "true" || isCleoGitInitialized(cleoDir));
|
|
39853
39721
|
}
|
|
39854
39722
|
function isMergeInProgress(cleoDir) {
|
|
39855
|
-
return existsSync18(
|
|
39723
|
+
return existsSync18(join22(checkpointGitDir(cleoDir), "MERGE_HEAD"));
|
|
39856
39724
|
}
|
|
39857
39725
|
async function isDetachedHead(cleoDir) {
|
|
39858
39726
|
const result = await cleoGitCommand(["symbolic-ref", "HEAD"], cleoDir);
|
|
39859
39727
|
return !result.success;
|
|
39860
39728
|
}
|
|
39861
39729
|
function isRebaseInProgress(cleoDir) {
|
|
39862
|
-
return existsSync18(
|
|
39730
|
+
return existsSync18(join22(checkpointGitDir(cleoDir), "rebase-merge")) || existsSync18(join22(checkpointGitDir(cleoDir), "rebase-apply"));
|
|
39863
39731
|
}
|
|
39864
39732
|
async function recordCheckpointTime(cleoDir) {
|
|
39865
39733
|
try {
|
|
39866
|
-
const stateFile =
|
|
39734
|
+
const stateFile = join22(cleoDir, CHECKPOINT_STATE_FILE);
|
|
39867
39735
|
await writeFile(stateFile, String(Math.floor(Date.now() / 1e3)));
|
|
39868
39736
|
} catch {
|
|
39869
39737
|
}
|
|
39870
39738
|
}
|
|
39871
39739
|
async function getLastCheckpointTime(cleoDir) {
|
|
39872
39740
|
try {
|
|
39873
|
-
const stateFile =
|
|
39741
|
+
const stateFile = join22(cleoDir, CHECKPOINT_STATE_FILE);
|
|
39874
39742
|
const content = await readFile4(stateFile, "utf-8");
|
|
39875
39743
|
const epoch = parseInt(content.trim(), 10);
|
|
39876
39744
|
return Number.isNaN(epoch) ? 0 : epoch;
|
|
@@ -39882,7 +39750,7 @@ async function getChangedStateFiles(cleoDir, cwd) {
|
|
|
39882
39750
|
const changed = [];
|
|
39883
39751
|
const allStateFiles = await getAllStateFiles(cwd);
|
|
39884
39752
|
for (const stateFile of allStateFiles) {
|
|
39885
|
-
const fullPath =
|
|
39753
|
+
const fullPath = join22(cleoDir, stateFile);
|
|
39886
39754
|
if (!existsSync18(fullPath)) continue;
|
|
39887
39755
|
const diffResult = await cleoGitCommand(["diff", "--quiet", "--", stateFile], cleoDir);
|
|
39888
39756
|
const cachedResult = await cleoGitCommand(
|
|
@@ -39983,6 +39851,218 @@ var init_git_checkpoint = __esm({
|
|
|
39983
39851
|
}
|
|
39984
39852
|
});
|
|
39985
39853
|
|
|
39854
|
+
// packages/core/src/store/snapshot-gate.ts
|
|
39855
|
+
import { join as join23 } from "node:path";
|
|
39856
|
+
function readGateState(db) {
|
|
39857
|
+
const row = db.prepare("SELECT value FROM tasks_schema_meta WHERE key = ?").get(SNAPSHOT_GATE_META_KEY);
|
|
39858
|
+
const state = { generation: 0, prefixes: {} };
|
|
39859
|
+
if (!row) return state;
|
|
39860
|
+
try {
|
|
39861
|
+
const parsed = JSON.parse(row.value);
|
|
39862
|
+
if (parsed === null || typeof parsed !== "object") return state;
|
|
39863
|
+
if (typeof parsed.generation === "number" && Number.isSafeInteger(parsed.generation)) {
|
|
39864
|
+
state.generation = parsed.generation;
|
|
39865
|
+
}
|
|
39866
|
+
for (const [prefix, e] of Object.entries(parsed.prefixes ?? {})) {
|
|
39867
|
+
if (e && typeof e.generation === "number" && typeof e.startedAt === "number" && typeof e.completedAt === "number") {
|
|
39868
|
+
state.prefixes[prefix] = {
|
|
39869
|
+
generation: e.generation,
|
|
39870
|
+
startedAt: e.startedAt,
|
|
39871
|
+
completedAt: e.completedAt
|
|
39872
|
+
};
|
|
39873
|
+
}
|
|
39874
|
+
}
|
|
39875
|
+
} catch {
|
|
39876
|
+
}
|
|
39877
|
+
return state;
|
|
39878
|
+
}
|
|
39879
|
+
function writeGateState(db, state) {
|
|
39880
|
+
db.prepare(
|
|
39881
|
+
"INSERT INTO tasks_schema_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value"
|
|
39882
|
+
).run(SNAPSHOT_GATE_META_KEY, JSON.stringify(state));
|
|
39883
|
+
mirrorCounterToBare(db, SNAPSHOT_GATE_META_KEY);
|
|
39884
|
+
}
|
|
39885
|
+
function tryReadGateState(db) {
|
|
39886
|
+
if (!db) return null;
|
|
39887
|
+
try {
|
|
39888
|
+
return readGateState(db);
|
|
39889
|
+
} catch {
|
|
39890
|
+
return null;
|
|
39891
|
+
}
|
|
39892
|
+
}
|
|
39893
|
+
function needsSnapshot(entry, mode, nowMs, seenGeneration) {
|
|
39894
|
+
if (!entry) return true;
|
|
39895
|
+
if (mode === "required") return seenGeneration === null || entry.generation <= seenGeneration;
|
|
39896
|
+
const elapsed = nowMs - entry.startedAt;
|
|
39897
|
+
return elapsed < 0 || elapsed >= SNAPSHOT_DEBOUNCE_MS;
|
|
39898
|
+
}
|
|
39899
|
+
async function runGatedSnapshot(opts, snapshot) {
|
|
39900
|
+
const now = opts.now ?? Date.now;
|
|
39901
|
+
const mode = opts.mode ?? "routine";
|
|
39902
|
+
const db = opts.stateDb;
|
|
39903
|
+
const none = (skipped, error) => ({
|
|
39904
|
+
snapshotted: [],
|
|
39905
|
+
linked: [],
|
|
39906
|
+
absent: [],
|
|
39907
|
+
failed: [],
|
|
39908
|
+
skipped,
|
|
39909
|
+
...error !== void 0 && { error }
|
|
39910
|
+
});
|
|
39911
|
+
const entryState = tryReadGateState(db);
|
|
39912
|
+
const seenGeneration = mode === "required" ? opts.seenGeneration ?? entryState?.generation ?? null : null;
|
|
39913
|
+
if (mode === "routine") {
|
|
39914
|
+
if (!entryState) return none("state-unavailable");
|
|
39915
|
+
const nowMs = now();
|
|
39916
|
+
if (!opts.prefixes.some((p) => needsSnapshot(entryState.prefixes[p], mode, nowMs, null))) {
|
|
39917
|
+
return none("debounced");
|
|
39918
|
+
}
|
|
39919
|
+
}
|
|
39920
|
+
const lockPath2 = join23(opts.backupDir, GATE_LOCK_BASENAME);
|
|
39921
|
+
let release;
|
|
39922
|
+
try {
|
|
39923
|
+
release = await acquireLock(lockPath2, {
|
|
39924
|
+
retries: mode === "required" ? opts.lockWaitRetries ?? SNAPSHOT_LOCK_WAIT_RETRIES : 0,
|
|
39925
|
+
stale: SNAPSHOT_LOCK_STALE_MS,
|
|
39926
|
+
// A compromised lock (e.g. the backup dir was removed) must not throw
|
|
39927
|
+
// from a timer and crash the host process.
|
|
39928
|
+
onCompromised: () => {
|
|
39929
|
+
}
|
|
39930
|
+
});
|
|
39931
|
+
} catch {
|
|
39932
|
+
if (mode === "routine") return none("in-flight");
|
|
39933
|
+
return none(
|
|
39934
|
+
"lock-timeout",
|
|
39935
|
+
`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.`
|
|
39936
|
+
);
|
|
39937
|
+
}
|
|
39938
|
+
try {
|
|
39939
|
+
try {
|
|
39940
|
+
opts.onLockAcquired?.();
|
|
39941
|
+
} catch {
|
|
39942
|
+
}
|
|
39943
|
+
const state = tryReadGateState(db);
|
|
39944
|
+
if (!state && mode === "routine") return none("state-unavailable");
|
|
39945
|
+
const startMs = now();
|
|
39946
|
+
const admitted = mode === "required" && opts.alwaysSnapshot === true ? [...opts.prefixes] : opts.prefixes.filter(
|
|
39947
|
+
(p) => needsSnapshot(state?.prefixes[p], mode, startMs, seenGeneration)
|
|
39948
|
+
);
|
|
39949
|
+
if (admitted.length === 0) return none(mode === "routine" ? "debounced" : "covered");
|
|
39950
|
+
const generation = (state?.generation ?? 0) + 1;
|
|
39951
|
+
let claimed = false;
|
|
39952
|
+
if (db && state) {
|
|
39953
|
+
try {
|
|
39954
|
+
writeGateState(db, { ...state, generation });
|
|
39955
|
+
claimed = true;
|
|
39956
|
+
} catch {
|
|
39957
|
+
}
|
|
39958
|
+
}
|
|
39959
|
+
const snapshotted = [];
|
|
39960
|
+
const linked = [];
|
|
39961
|
+
const absent = [];
|
|
39962
|
+
const failed = [];
|
|
39963
|
+
for (const prefix of admitted) {
|
|
39964
|
+
try {
|
|
39965
|
+
const outcome = await snapshot(prefix);
|
|
39966
|
+
if (outcome === "absent") absent.push(prefix);
|
|
39967
|
+
else {
|
|
39968
|
+
snapshotted.push(prefix);
|
|
39969
|
+
if (outcome === "linked") linked.push(prefix);
|
|
39970
|
+
}
|
|
39971
|
+
} catch {
|
|
39972
|
+
failed.push(prefix);
|
|
39973
|
+
}
|
|
39974
|
+
}
|
|
39975
|
+
const satisfied = [...snapshotted, ...absent];
|
|
39976
|
+
if (db && claimed && satisfied.length > 0) {
|
|
39977
|
+
try {
|
|
39978
|
+
const latest = readGateState(db);
|
|
39979
|
+
const completedAt = now();
|
|
39980
|
+
latest.generation = Math.max(latest.generation, generation);
|
|
39981
|
+
for (const p of satisfied) {
|
|
39982
|
+
latest.prefixes[p] = { generation, startedAt: startMs, completedAt };
|
|
39983
|
+
}
|
|
39984
|
+
writeGateState(db, latest);
|
|
39985
|
+
} catch {
|
|
39986
|
+
}
|
|
39987
|
+
}
|
|
39988
|
+
return { snapshotted, linked, absent, failed, skipped: null };
|
|
39989
|
+
} finally {
|
|
39990
|
+
await release().catch(() => {
|
|
39991
|
+
});
|
|
39992
|
+
}
|
|
39993
|
+
}
|
|
39994
|
+
function isValidStamp(ymd, hms) {
|
|
39995
|
+
const y = Number(ymd.slice(0, 4));
|
|
39996
|
+
const mo = Number(ymd.slice(4, 6));
|
|
39997
|
+
const d = Number(ymd.slice(6, 8));
|
|
39998
|
+
const h = Number(hms.slice(0, 2));
|
|
39999
|
+
const mi = Number(hms.slice(2, 4));
|
|
40000
|
+
const se = Number(hms.slice(4, 6));
|
|
40001
|
+
if (mo < 1 || mo > 12 || d < 1 || h > 23 || mi > 59 || se > 59) return false;
|
|
40002
|
+
const probe = new Date(Date.UTC(y, mo - 1, d));
|
|
40003
|
+
return probe.getUTCFullYear() === y && probe.getUTCMonth() === mo - 1 && probe.getUTCDate() === d;
|
|
40004
|
+
}
|
|
40005
|
+
function selectSnapshotsToKeep(names, ctx = {}) {
|
|
40006
|
+
const policy = ctx.policy ?? DEFAULT_SNAPSHOT_RETENTION;
|
|
40007
|
+
const keep = /* @__PURE__ */ new Set();
|
|
40008
|
+
const stamped = [];
|
|
40009
|
+
for (const name2 of names) {
|
|
40010
|
+
const m = STAMP_PATTERN.exec(name2);
|
|
40011
|
+
const ymd = m?.[1];
|
|
40012
|
+
const hms = m?.[2];
|
|
40013
|
+
if (!ymd || !hms || !isValidStamp(ymd, hms)) {
|
|
40014
|
+
keep.add(name2);
|
|
40015
|
+
continue;
|
|
40016
|
+
}
|
|
40017
|
+
const stamp = `${ymd}-${hms}`;
|
|
40018
|
+
if (ctx.nowStamp !== void 0 && stamp > ctx.nowStamp) {
|
|
40019
|
+
keep.add(name2);
|
|
40020
|
+
continue;
|
|
40021
|
+
}
|
|
40022
|
+
stamped.push({ name: name2, stamp });
|
|
40023
|
+
}
|
|
40024
|
+
if (ctx.pinned !== void 0 && names.includes(ctx.pinned)) keep.add(ctx.pinned);
|
|
40025
|
+
stamped.sort((a, b) => a.stamp < b.stamp ? 1 : a.stamp > b.stamp ? -1 : 0);
|
|
40026
|
+
for (const s of stamped.slice(0, policy.latest)) keep.add(s.name);
|
|
40027
|
+
const keepNewestPerBucket = (bucketOf, slots) => {
|
|
40028
|
+
const seen = /* @__PURE__ */ new Set();
|
|
40029
|
+
for (const s of stamped) {
|
|
40030
|
+
const bucket = bucketOf(s.stamp);
|
|
40031
|
+
if (seen.has(bucket)) continue;
|
|
40032
|
+
if (seen.size >= slots) break;
|
|
40033
|
+
seen.add(bucket);
|
|
40034
|
+
keep.add(s.name);
|
|
40035
|
+
}
|
|
40036
|
+
};
|
|
40037
|
+
keepNewestPerBucket(
|
|
40038
|
+
(stamp) => `${stamp.slice(0, 11)}q${Math.floor(Number(stamp.slice(11, 13)) / 15)}`,
|
|
40039
|
+
policy.quarterHourly
|
|
40040
|
+
);
|
|
40041
|
+
keepNewestPerBucket((stamp) => stamp.slice(0, 11), policy.hourly);
|
|
40042
|
+
keepNewestPerBucket((stamp) => stamp.slice(0, 8), policy.daily);
|
|
40043
|
+
return keep;
|
|
40044
|
+
}
|
|
40045
|
+
var SNAPSHOT_DEBOUNCE_MS, SNAPSHOT_LOCK_STALE_MS, SNAPSHOT_LOCK_WAIT_RETRIES, SNAPSHOT_GATE_META_KEY, GATE_LOCK_BASENAME, DEFAULT_SNAPSHOT_RETENTION, STAMP_PATTERN;
|
|
40046
|
+
var init_snapshot_gate = __esm({
|
|
40047
|
+
"packages/core/src/store/snapshot-gate.ts"() {
|
|
40048
|
+
"use strict";
|
|
40049
|
+
init_lock();
|
|
40050
|
+
init_twin_collapse();
|
|
40051
|
+
SNAPSHOT_DEBOUNCE_MS = 5 * 6e4;
|
|
40052
|
+
SNAPSHOT_LOCK_STALE_MS = 10 * 6e4;
|
|
40053
|
+
SNAPSHOT_LOCK_WAIT_RETRIES = 90;
|
|
40054
|
+
SNAPSHOT_GATE_META_KEY = "sqlite_snapshot_gate";
|
|
40055
|
+
GATE_LOCK_BASENAME = ".snapshot-gate";
|
|
40056
|
+
DEFAULT_SNAPSHOT_RETENTION = {
|
|
40057
|
+
latest: 1,
|
|
40058
|
+
quarterHourly: 3,
|
|
40059
|
+
hourly: 3,
|
|
40060
|
+
daily: 3
|
|
40061
|
+
};
|
|
40062
|
+
STAMP_PATTERN = /-(\d{8})-(\d{6})\.db$/;
|
|
40063
|
+
}
|
|
40064
|
+
});
|
|
40065
|
+
|
|
39986
40066
|
// packages/core/src/store/data-safety-central.ts
|
|
39987
40067
|
import { eq as eq7 } from "drizzle-orm";
|
|
39988
40068
|
function inSafetyScope(cwd, operation) {
|
|
@@ -41284,7 +41364,8 @@ __export(sqlite_exports, {
|
|
|
41284
41364
|
resetDbState: () => resetDbState,
|
|
41285
41365
|
resolveMigrationsFolder: () => resolveMigrationsFolder,
|
|
41286
41366
|
schema: () => tasks_schema_exports,
|
|
41287
|
-
seedTasksMeta: () => seedTasksMeta
|
|
41367
|
+
seedTasksMeta: () => seedTasksMeta,
|
|
41368
|
+
worktreeRecoveryRefusal: () => worktreeRecoveryRefusal
|
|
41288
41369
|
});
|
|
41289
41370
|
import { copyFileSync as copyFileSync4, existsSync as existsSync20, realpathSync as realpathSync7, renameSync as renameSync7, unlinkSync as unlinkSync4 } from "node:fs";
|
|
41290
41371
|
import { createRequire as createRequire7 } from "node:module";
|
|
@@ -41335,6 +41416,12 @@ function isSameDirectory(a, b) {
|
|
|
41335
41416
|
return false;
|
|
41336
41417
|
}
|
|
41337
41418
|
}
|
|
41419
|
+
function worktreeRecoveryRefusal(dbPath) {
|
|
41420
|
+
const storeCleoDir = dirname14(dbPath);
|
|
41421
|
+
const root = dirname14(storeCleoDir);
|
|
41422
|
+
if (basename8(storeCleoDir) !== ".cleo" || !isGitLinkedCheckout(root)) return null;
|
|
41423
|
+
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).`;
|
|
41424
|
+
}
|
|
41338
41425
|
async function autoRecoverFromBackup(nativeDb, dbPath, cwd) {
|
|
41339
41426
|
const { openNativeDatabase: openNativeDatabase2 } = await Promise.resolve().then(() => (init_sqlite_native(), sqlite_native_exports));
|
|
41340
41427
|
const log15 = getLogger("sqlite");
|
|
@@ -41344,11 +41431,9 @@ async function autoRecoverFromBackup(nativeDb, dbPath, cwd) {
|
|
|
41344
41431
|
const taskCount = countResult?.cnt ?? 0;
|
|
41345
41432
|
if (taskCount > 0) return false;
|
|
41346
41433
|
const storeCleoDir = dirname14(dbPath);
|
|
41347
|
-
|
|
41348
|
-
|
|
41349
|
-
|
|
41350
|
-
"Auto-recovery refused: the empty store lives inside a git worktree (T12460). Worktrees must resolve the parent project store."
|
|
41351
|
-
);
|
|
41434
|
+
const worktreeRefusal = worktreeRecoveryRefusal(dbPath);
|
|
41435
|
+
if (worktreeRefusal !== null) {
|
|
41436
|
+
log15.warn({ dbPath }, worktreeRefusal);
|
|
41352
41437
|
return false;
|
|
41353
41438
|
}
|
|
41354
41439
|
const backups = listSqliteBackups(cwd);
|