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