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