@cleocode/core 2026.6.10 → 2026.6.12

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 (58) hide show
  1. package/dist/docs/export-document.js +110 -22
  2. package/dist/docs/export-document.js.map +2 -2
  3. package/dist/llm/plugin-facade.js +74 -7
  4. package/dist/llm/plugin-facade.js.map +2 -2
  5. package/dist/release/plan.d.ts +4 -6
  6. package/dist/release/plan.d.ts.map +1 -1
  7. package/dist/release/plan.js +10 -23
  8. package/dist/release/plan.js.map +1 -1
  9. package/dist/release/reconcile.d.ts +0 -2
  10. package/dist/release/reconcile.d.ts.map +1 -1
  11. package/dist/release/reconcile.js +43 -50
  12. package/dist/release/reconcile.js.map +1 -1
  13. package/dist/release/release-manifest.d.ts.map +1 -1
  14. package/dist/release/release-manifest.js +4 -1
  15. package/dist/release/release-manifest.js.map +1 -1
  16. package/dist/release/verify-provenance.js +20 -20
  17. package/dist/release/verify-provenance.js.map +1 -1
  18. package/dist/store/agent-registry-store.d.ts.map +1 -1
  19. package/dist/store/agent-registry-store.js +4 -2
  20. package/dist/store/agent-registry-store.js.map +1 -1
  21. package/dist/store/conduit-sqlite.d.ts.map +1 -1
  22. package/dist/store/conduit-sqlite.js +4 -2
  23. package/dist/store/conduit-sqlite.js.map +1 -1
  24. package/dist/store/dual-scope-db.d.ts.map +1 -1
  25. package/dist/store/dual-scope-db.js +56 -6
  26. package/dist/store/dual-scope-db.js.map +1 -1
  27. package/dist/store/migration-manager.d.ts +26 -1
  28. package/dist/store/migration-manager.d.ts.map +1 -1
  29. package/dist/store/migration-manager.js +104 -4
  30. package/dist/store/migration-manager.js.map +1 -1
  31. package/dist/store/nexus-sqlite.d.ts.map +1 -1
  32. package/dist/store/nexus-sqlite.js +4 -2
  33. package/dist/store/nexus-sqlite.js.map +1 -1
  34. package/dist/store/resolve-migrations-folder.d.ts +41 -0
  35. package/dist/store/resolve-migrations-folder.d.ts.map +1 -1
  36. package/dist/store/resolve-migrations-folder.js +53 -0
  37. package/dist/store/resolve-migrations-folder.js.map +1 -1
  38. package/dist/store/sqlite.d.ts.map +1 -1
  39. package/dist/store/sqlite.js +5 -3
  40. package/dist/store/sqlite.js.map +1 -1
  41. package/dist/store/tasks-schema.d.ts +4 -0
  42. package/dist/store/tasks-schema.d.ts.map +1 -1
  43. package/dist/store/tasks-schema.js +14 -0
  44. package/dist/store/tasks-schema.js.map +1 -1
  45. package/dist/store/validation-schemas.d.ts +62 -62
  46. package/dist/task-tools/describe-schema.d.ts +4 -2
  47. package/dist/task-tools/describe-schema.d.ts.map +1 -1
  48. package/dist/task-tools/describe-schema.js +9 -3
  49. package/dist/task-tools/describe-schema.js.map +1 -1
  50. package/migrations/drizzle-cleo-project/20260606000000_t11884-restore-prefixed-task-triggers/migration.sql +272 -0
  51. package/migrations/drizzle-cleo-project/20260606000000_t11884-restore-prefixed-task-triggers/revert.sql +29 -0
  52. package/migrations/drizzle-cleo-project/20260606000100_t11883-tasks-releases-version-unique/migration.sql +21 -0
  53. package/migrations/drizzle-cleo-project/20260606000100_t11883-tasks-releases-version-unique/revert.sql +4 -0
  54. package/package.json +12 -12
  55. package/dist/release/provenance-fk.d.ts +0 -74
  56. package/dist/release/provenance-fk.d.ts.map +0 -1
  57. package/dist/release/provenance-fk.js +0 -122
  58. package/dist/release/provenance-fk.js.map +0 -1
@@ -0,0 +1,272 @@
1
+ -- T11884 — Restore task-domain invariant + handoff triggers on the PREFIXED tables.
2
+ --
3
+ -- ==========================================================================
4
+ -- Problem (latent bug exposed by the SG-DB-SUBSTRATE-V2 cutover)
5
+ -- ==========================================================================
6
+ -- The T11578 dual-scope cutover repointed the runtime drizzle symbols
7
+ -- (`tasks`, `task_relations`, `task_acceptance_criteria`, session handoff, …)
8
+ -- from the BARE un-prefixed tables onto the domain-prefixed tables
9
+ -- (`tasks_tasks`, `tasks_task_relations`, …). But the 12 SQLite invariant /
10
+ -- handoff triggers were authored ONLY on the bare tables (T877, T10572,
11
+ -- T10638, T1609) and were never recreated on the prefixed tables — exodus
12
+ -- copies rows with triggers absent on the target. Result: every task invariant
13
+ -- has been SILENTLY UNENFORCED on the live write path (`tasks_tasks` has zero
14
+ -- triggers), and the session-handoff `handoff_json` mirror is dead.
15
+ --
16
+ -- This migration recreates all 12 guards on the prefixed tables, with bodies
17
+ -- rewritten to reference `tasks_tasks` / `tasks_sessions`. New trigger NAMES
18
+ -- are used (prefixed) so they do NOT collide with the still-present legacy
19
+ -- triggers; when the legacy bare tables are dropped (E5 of the cutover) their
20
+ -- triggers drop with them and these remain the SSoT.
21
+ --
22
+ -- Invariants restored:
23
+ -- - tasks_tasks.parent_id cannot introduce a containment cycle
24
+ -- - tasks_tasks.parent_id obeys the saga->epic, epic->task|subtask,
25
+ -- task->subtask type matrix (sagas/tasks are roots)
26
+ -- - tasks_tasks status/pipeline_stage T877 invariant
27
+ -- - tasks_task_relations is non-containment-only
28
+ -- - tasks_task_acceptance_criteria child_task targets must be direct children
29
+ -- - tasks_session_handoff_entries is write-once + mirrors handoff_json into
30
+ -- tasks_sessions
31
+ --
32
+ -- Trigger messages keep the same stable error codes so CLI callers surface
33
+ -- identical remediation regardless of which physical table fired.
34
+ --
35
+ -- @task T11884
36
+ -- @epic T11883
37
+ -- @saga T11242
38
+
39
+ DROP TRIGGER IF EXISTS `tasks_tasks_parent_cycle_guard_insert`;
40
+ --> statement-breakpoint
41
+ DROP TRIGGER IF EXISTS `tasks_tasks_parent_cycle_guard_update`;
42
+ --> statement-breakpoint
43
+ DROP TRIGGER IF EXISTS `tasks_tasks_parent_type_matrix_insert`;
44
+ --> statement-breakpoint
45
+ DROP TRIGGER IF EXISTS `tasks_tasks_parent_type_matrix_update`;
46
+ --> statement-breakpoint
47
+ DROP TRIGGER IF EXISTS `trg_tasks_tasks_status_pipeline_insert`;
48
+ --> statement-breakpoint
49
+ DROP TRIGGER IF EXISTS `trg_tasks_tasks_status_pipeline_update`;
50
+ --> statement-breakpoint
51
+ DROP TRIGGER IF EXISTS `tasks_task_relations_non_containment_insert`;
52
+ --> statement-breakpoint
53
+ DROP TRIGGER IF EXISTS `tasks_task_relations_non_containment_update`;
54
+ --> statement-breakpoint
55
+ DROP TRIGGER IF EXISTS `tasks_task_acceptance_child_target_insert`;
56
+ --> statement-breakpoint
57
+ DROP TRIGGER IF EXISTS `tasks_task_acceptance_child_target_update`;
58
+ --> statement-breakpoint
59
+ DROP TRIGGER IF EXISTS `trg_tasks_session_handoff_mirror`;
60
+ --> statement-breakpoint
61
+ DROP TRIGGER IF EXISTS `trg_tasks_session_handoff_no_update`;
62
+ --> statement-breakpoint
63
+
64
+ -- ---------------------------------------------------------------------------
65
+ -- Parent-cycle guard (T10572) — tasks_tasks
66
+ -- ---------------------------------------------------------------------------
67
+ CREATE TRIGGER `tasks_tasks_parent_cycle_guard_insert`
68
+ BEFORE INSERT ON `tasks_tasks`
69
+ WHEN NEW.`parent_id` IS NOT NULL
70
+ BEGIN
71
+ SELECT RAISE(ABORT, 'E_TASK_PARENT_CYCLE: tasks.parent_id cannot create a containment cycle')
72
+ WHERE EXISTS (
73
+ WITH RECURSIVE ancestors(`id`, `parent_id`) AS (
74
+ SELECT parent.`id`, parent.`parent_id`
75
+ FROM `tasks_tasks` parent
76
+ WHERE parent.`id` = NEW.`parent_id`
77
+ UNION ALL
78
+ SELECT next_parent.`id`, next_parent.`parent_id`
79
+ FROM `tasks_tasks` next_parent
80
+ JOIN ancestors ON next_parent.`id` = ancestors.`parent_id`
81
+ WHERE ancestors.`parent_id` IS NOT NULL
82
+ )
83
+ SELECT 1 FROM ancestors WHERE ancestors.`id` = NEW.`id`
84
+ );
85
+ END;
86
+ --> statement-breakpoint
87
+
88
+ CREATE TRIGGER `tasks_tasks_parent_cycle_guard_update`
89
+ BEFORE UPDATE OF `parent_id` ON `tasks_tasks`
90
+ WHEN NEW.`parent_id` IS NOT NULL
91
+ BEGIN
92
+ SELECT RAISE(ABORT, 'E_TASK_PARENT_CYCLE: tasks.parent_id cannot create a containment cycle')
93
+ WHERE EXISTS (
94
+ WITH RECURSIVE ancestors(`id`, `parent_id`) AS (
95
+ SELECT parent.`id`, parent.`parent_id`
96
+ FROM `tasks_tasks` parent
97
+ WHERE parent.`id` = NEW.`parent_id`
98
+ UNION ALL
99
+ SELECT next_parent.`id`, next_parent.`parent_id`
100
+ FROM `tasks_tasks` next_parent
101
+ JOIN ancestors ON next_parent.`id` = ancestors.`parent_id`
102
+ WHERE ancestors.`parent_id` IS NOT NULL
103
+ )
104
+ SELECT 1 FROM ancestors WHERE ancestors.`id` = NEW.`id`
105
+ );
106
+ END;
107
+ --> statement-breakpoint
108
+
109
+ -- ---------------------------------------------------------------------------
110
+ -- Parent type-matrix guard (T10638) — tasks_tasks
111
+ -- ---------------------------------------------------------------------------
112
+ CREATE TRIGGER `tasks_tasks_parent_type_matrix_insert`
113
+ BEFORE INSERT ON `tasks_tasks`
114
+ WHEN NEW.`parent_id` IS NOT NULL
115
+ AND NEW.`type` IS NOT NULL
116
+ AND EXISTS (SELECT 1 FROM `tasks_tasks` parent WHERE parent.`id` = NEW.`parent_id` AND parent.`type` IS NOT NULL)
117
+ AND NOT EXISTS (
118
+ SELECT 1
119
+ FROM `tasks_tasks` parent
120
+ WHERE parent.`id` = NEW.`parent_id`
121
+ AND (
122
+ (NEW.`type` = 'epic' AND parent.`type` = 'saga')
123
+ OR (NEW.`type` = 'task' AND parent.`type` = 'epic')
124
+ OR (NEW.`type` = 'subtask' AND parent.`type` IN ('epic', 'task'))
125
+ )
126
+ )
127
+ BEGIN
128
+ SELECT RAISE(ABORT, 'E_TASK_PARENT_TYPE_MATRIX: tasks.parent_id must follow saga->epic, epic->task|subtask, and task->subtask; sagas/tasks must be roots');
129
+ END;
130
+ --> statement-breakpoint
131
+
132
+ CREATE TRIGGER `tasks_tasks_parent_type_matrix_update`
133
+ BEFORE UPDATE OF `parent_id`, `type` ON `tasks_tasks`
134
+ WHEN NEW.`parent_id` IS NOT NULL
135
+ AND NEW.`type` IS NOT NULL
136
+ AND EXISTS (SELECT 1 FROM `tasks_tasks` parent WHERE parent.`id` = NEW.`parent_id` AND parent.`type` IS NOT NULL)
137
+ AND NOT EXISTS (
138
+ SELECT 1
139
+ FROM `tasks_tasks` parent
140
+ WHERE parent.`id` = NEW.`parent_id`
141
+ AND (
142
+ (NEW.`type` = 'epic' AND parent.`type` = 'saga')
143
+ OR (NEW.`type` = 'task' AND parent.`type` = 'epic')
144
+ OR (NEW.`type` = 'subtask' AND parent.`type` IN ('epic', 'task'))
145
+ )
146
+ )
147
+ BEGIN
148
+ SELECT RAISE(ABORT, 'E_TASK_PARENT_TYPE_MATRIX: tasks.parent_id must follow saga->epic, epic->task|subtask, and task->subtask; sagas/tasks must be roots');
149
+ END;
150
+ --> statement-breakpoint
151
+
152
+ -- ---------------------------------------------------------------------------
153
+ -- Status / pipeline_stage invariant (T877) — tasks_tasks
154
+ -- ---------------------------------------------------------------------------
155
+ CREATE TRIGGER `trg_tasks_tasks_status_pipeline_insert`
156
+ BEFORE INSERT ON `tasks_tasks`
157
+ FOR EACH ROW
158
+ WHEN (NEW.`status` = 'done' AND (NEW.`pipeline_stage` IS NULL OR NEW.`pipeline_stage` NOT IN ('contribution','cancelled')))
159
+ OR (NEW.`status` = 'cancelled' AND (NEW.`pipeline_stage` IS NULL OR NEW.`pipeline_stage` != 'cancelled'))
160
+ BEGIN
161
+ SELECT RAISE(ABORT, 'T877_INVARIANT_VIOLATION: status/pipeline_stage mismatch. status=done requires pipeline_stage IN (contribution,cancelled); status=cancelled requires pipeline_stage=cancelled.');
162
+ END;
163
+ --> statement-breakpoint
164
+
165
+ CREATE TRIGGER `trg_tasks_tasks_status_pipeline_update`
166
+ BEFORE UPDATE OF `status`, `pipeline_stage` ON `tasks_tasks`
167
+ FOR EACH ROW
168
+ WHEN (NEW.`status` = 'done' AND (NEW.`pipeline_stage` IS NULL OR NEW.`pipeline_stage` NOT IN ('contribution','cancelled')))
169
+ OR (NEW.`status` = 'cancelled' AND (NEW.`pipeline_stage` IS NULL OR NEW.`pipeline_stage` != 'cancelled'))
170
+ BEGIN
171
+ SELECT RAISE(ABORT, 'T877_INVARIANT_VIOLATION: status/pipeline_stage mismatch. status=done requires pipeline_stage IN (contribution,cancelled); status=cancelled requires pipeline_stage=cancelled.');
172
+ END;
173
+ --> statement-breakpoint
174
+
175
+ -- ---------------------------------------------------------------------------
176
+ -- Non-containment guard (T10572) — tasks_task_relations
177
+ -- ---------------------------------------------------------------------------
178
+ CREATE TRIGGER `tasks_task_relations_non_containment_insert`
179
+ BEFORE INSERT ON `tasks_task_relations`
180
+ WHEN EXISTS (
181
+ SELECT 1
182
+ FROM `tasks_tasks` child
183
+ WHERE child.`id` = NEW.`task_id`
184
+ AND child.`parent_id` = NEW.`related_to`
185
+ ) OR EXISTS (
186
+ SELECT 1
187
+ FROM `tasks_tasks` child
188
+ WHERE child.`id` = NEW.`related_to`
189
+ AND child.`parent_id` = NEW.`task_id`
190
+ )
191
+ BEGIN
192
+ SELECT RAISE(ABORT, 'E_TASK_RELATION_CONTAINMENT: task_relations is non-containment-only; use tasks.parent_id for parent/child edges');
193
+ END;
194
+ --> statement-breakpoint
195
+
196
+ CREATE TRIGGER `tasks_task_relations_non_containment_update`
197
+ BEFORE UPDATE OF `task_id`, `related_to` ON `tasks_task_relations`
198
+ WHEN EXISTS (
199
+ SELECT 1
200
+ FROM `tasks_tasks` child
201
+ WHERE child.`id` = NEW.`task_id`
202
+ AND child.`parent_id` = NEW.`related_to`
203
+ ) OR EXISTS (
204
+ SELECT 1
205
+ FROM `tasks_tasks` child
206
+ WHERE child.`id` = NEW.`related_to`
207
+ AND child.`parent_id` = NEW.`task_id`
208
+ )
209
+ BEGIN
210
+ SELECT RAISE(ABORT, 'E_TASK_RELATION_CONTAINMENT: task_relations is non-containment-only; use tasks.parent_id for parent/child edges');
211
+ END;
212
+ --> statement-breakpoint
213
+
214
+ -- ---------------------------------------------------------------------------
215
+ -- Acceptance child_target containment guard (T10572) — tasks_task_acceptance_criteria
216
+ -- ---------------------------------------------------------------------------
217
+ CREATE TRIGGER `tasks_task_acceptance_child_target_insert`
218
+ BEFORE INSERT ON `tasks_task_acceptance_criteria`
219
+ WHEN NEW.`target_task_id` IS NOT NULL
220
+ AND (
221
+ NEW.`kind` <> 'child_task'
222
+ OR NOT EXISTS (
223
+ SELECT 1
224
+ FROM `tasks_tasks` child
225
+ WHERE child.`id` = NEW.`target_task_id`
226
+ AND child.`parent_id` = NEW.`task_id`
227
+ )
228
+ )
229
+ BEGIN
230
+ SELECT RAISE(ABORT, 'E_CHILD_TASK_TARGET_CONTAINMENT: child_task acceptance target_task_id must be a direct child of task_id; non-child_task criteria must not set target_task_id');
231
+ END;
232
+ --> statement-breakpoint
233
+
234
+ CREATE TRIGGER `tasks_task_acceptance_child_target_update`
235
+ BEFORE UPDATE OF `task_id`, `kind`, `target_task_id` ON `tasks_task_acceptance_criteria`
236
+ WHEN NEW.`target_task_id` IS NOT NULL
237
+ AND (
238
+ NEW.`kind` <> 'child_task'
239
+ OR NOT EXISTS (
240
+ SELECT 1
241
+ FROM `tasks_tasks` child
242
+ WHERE child.`id` = NEW.`target_task_id`
243
+ AND child.`parent_id` = NEW.`task_id`
244
+ )
245
+ )
246
+ BEGIN
247
+ SELECT RAISE(ABORT, 'E_CHILD_TASK_TARGET_CONTAINMENT: child_task acceptance target_task_id must be a direct child of task_id; non-child_task criteria must not set target_task_id');
248
+ END;
249
+ --> statement-breakpoint
250
+
251
+ -- ---------------------------------------------------------------------------
252
+ -- Session-handoff write-once + mirror (T1609) — tasks_session_handoff_entries
253
+ -- ---------------------------------------------------------------------------
254
+ CREATE TRIGGER `trg_tasks_session_handoff_mirror`
255
+ AFTER INSERT ON `tasks_session_handoff_entries`
256
+ FOR EACH ROW
257
+ BEGIN
258
+ UPDATE `tasks_sessions`
259
+ SET `handoff_json` = NEW.handoff_json
260
+ WHERE `id` = NEW.session_id;
261
+ END;
262
+ --> statement-breakpoint
263
+
264
+ CREATE TRIGGER `trg_tasks_session_handoff_no_update`
265
+ BEFORE UPDATE ON `tasks_session_handoff_entries`
266
+ FOR EACH ROW
267
+ BEGIN
268
+ SELECT RAISE(
269
+ ABORT,
270
+ 'T1609_HANDOFF_IMMUTABLE: session_handoff_entries rows are write-once. Use persistHandoff() exactly once per session.'
271
+ );
272
+ END;
@@ -0,0 +1,29 @@
1
+ -- Revert T11884 — drop the prefixed-table invariant + handoff triggers.
2
+ -- The legacy bare-table triggers are untouched (they remain the active guards
3
+ -- until the E5 cutover drops the bare tables).
4
+ --
5
+ -- @task T11884
6
+
7
+ DROP TRIGGER IF EXISTS `tasks_tasks_parent_cycle_guard_insert`;
8
+ --> statement-breakpoint
9
+ DROP TRIGGER IF EXISTS `tasks_tasks_parent_cycle_guard_update`;
10
+ --> statement-breakpoint
11
+ DROP TRIGGER IF EXISTS `tasks_tasks_parent_type_matrix_insert`;
12
+ --> statement-breakpoint
13
+ DROP TRIGGER IF EXISTS `tasks_tasks_parent_type_matrix_update`;
14
+ --> statement-breakpoint
15
+ DROP TRIGGER IF EXISTS `trg_tasks_tasks_status_pipeline_insert`;
16
+ --> statement-breakpoint
17
+ DROP TRIGGER IF EXISTS `trg_tasks_tasks_status_pipeline_update`;
18
+ --> statement-breakpoint
19
+ DROP TRIGGER IF EXISTS `tasks_task_relations_non_containment_insert`;
20
+ --> statement-breakpoint
21
+ DROP TRIGGER IF EXISTS `tasks_task_relations_non_containment_update`;
22
+ --> statement-breakpoint
23
+ DROP TRIGGER IF EXISTS `tasks_task_acceptance_child_target_insert`;
24
+ --> statement-breakpoint
25
+ DROP TRIGGER IF EXISTS `tasks_task_acceptance_child_target_update`;
26
+ --> statement-breakpoint
27
+ DROP TRIGGER IF EXISTS `trg_tasks_session_handoff_mirror`;
28
+ --> statement-breakpoint
29
+ DROP TRIGGER IF EXISTS `trg_tasks_session_handoff_no_update`;
@@ -0,0 +1,21 @@
1
+ -- T11883 (E2) — Repair the missing UNIQUE index on tasks_releases.version.
2
+ --
3
+ -- The drizzle schema declares `version: text('version').notNull().unique()`
4
+ -- (packages/core/src/store/schema/cleo-project/provenance-rest.ts:201), but the
5
+ -- T11363 consolidation migration that built `tasks_releases` emitted only a
6
+ -- NON-unique `idx_tasks_releases_version` — the UNIQUE was dropped during the
7
+ -- exodus build. `cleo release plan` upserts with
8
+ -- `onConflictDoUpdate({ target: releases.version })`, which REQUIRES a physical
9
+ -- unique index on `version`; once the provenance `releases` symbol is rebound to
10
+ -- `tasks_releases` (E3) the upsert would otherwise throw "ON CONFLICT clause
11
+ -- does not match any PRIMARY KEY or UNIQUE constraint". This is a pure
12
+ -- drift-repair (schema↔physical), so it ships as a custom migration rather than
13
+ -- a generated one (generate has no committed snapshot to diff an index against).
14
+ --
15
+ -- Safe: the live corpus has 0 duplicate versions (74 rows), and fresh/test DBs
16
+ -- are empty. IF NOT EXISTS keeps it idempotent under the reconciler's re-probe.
17
+ --
18
+ -- @task T11883
19
+ -- @saga T11242
20
+
21
+ CREATE UNIQUE INDEX IF NOT EXISTS `uq_tasks_releases_version` ON `tasks_releases` (`version`);
@@ -0,0 +1,4 @@
1
+ -- Revert T11883 (E2) — drop the repaired UNIQUE index on tasks_releases.version.
2
+ -- @task T11883
3
+
4
+ DROP INDEX IF EXISTS `uq_tasks_releases_version`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleocode/core",
3
- "version": "2026.6.10",
3
+ "version": "2026.6.12",
4
4
  "description": "CLEO core business logic kernel — tasks, sessions, memory, orchestration, lifecycle, with bundled SQLite store",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -429,16 +429,16 @@
429
429
  "write-file-atomic": "^7.0.1",
430
430
  "yaml": "^2.8.3",
431
431
  "zod": "^4.3.6",
432
- "@cleocode/adapters": "2026.6.10",
433
- "@cleocode/agents": "2026.6.10",
434
- "@cleocode/contracts": "2026.6.10",
435
- "@cleocode/git-shim": "2026.6.10",
436
- "@cleocode/caamp": "2026.6.10",
437
- "@cleocode/lafs": "2026.6.10",
438
- "@cleocode/nexus": "2026.6.10",
439
- "@cleocode/paths": "2026.6.10",
440
- "@cleocode/skills": "2026.6.10",
441
- "@cleocode/worktree": "2026.6.10"
432
+ "@cleocode/caamp": "2026.6.12",
433
+ "@cleocode/agents": "2026.6.12",
434
+ "@cleocode/contracts": "2026.6.12",
435
+ "@cleocode/adapters": "2026.6.12",
436
+ "@cleocode/git-shim": "2026.6.12",
437
+ "@cleocode/lafs": "2026.6.12",
438
+ "@cleocode/nexus": "2026.6.12",
439
+ "@cleocode/worktree": "2026.6.12",
440
+ "@cleocode/paths": "2026.6.12",
441
+ "@cleocode/skills": "2026.6.12"
442
442
  },
443
443
  "engines": {
444
444
  "node": ">=24.16.0"
@@ -472,7 +472,7 @@
472
472
  },
473
473
  "optionalDependencies": {
474
474
  "llmtxt": "^2026.5.15",
475
- "@cleocode/studio": "^2026.6.10"
475
+ "@cleocode/studio": "^2026.6.12"
476
476
  },
477
477
  "scripts": {
478
478
  "build": "tsc",
@@ -1,74 +0,0 @@
1
- /**
2
- * Post-cutover provenance `task_id` / `epic_id` FK reconciliation (DHQ-051).
3
- *
4
- * Shared between `cleo release reconcile` ({@link releaseReconcileV2}) and
5
- * `cleo release plan` ({@link releasePlan} → `upsertReleasesRow`). Both verbs
6
- * write provenance rows (`task_commits`, `pr_tasks`, `releases`,
7
- * `release_changes`) whose `task_id` / `epic_id` foreign keys reference the
8
- * BARE `tasks` table — which is empty on a consolidated dual-scope `cleo.db`
9
- * after the T11578 cutover (runtime task data moved to the prefixed
10
- * `tasks_tasks` table). A row inserted with an `epicId` / `taskId` that exists
11
- * only in `tasks_tasks` violates the FK at INSERT time, aborting the verb.
12
- *
13
- * This module is the single source of truth for the FK-ordered parent backfill
14
- * that makes those FKs satisfiable. `reconcile.ts` re-exports both symbols so
15
- * its public surface is unchanged.
16
- *
17
- * @task T11659 — reconcile slice (original)
18
- * @task T11818 — plan/open slice (DRY hoist)
19
- * @epic T11466
20
- */
21
- import type { DatabaseSync } from '../store/sqlite.js';
22
- /** Result of resolving + reconciling the provenance `task_id` FK parent table. */
23
- export interface FkParentTaskResolution {
24
- /** Physical table the provenance `task_id` FK references, or null if absent. */
25
- parentTable: string | null;
26
- /**
27
- * Task IDs the FK can now resolve (FK parent rows present after any
28
- * FK-ordered shim backfill). A `task_id` reference to an id in this set is
29
- * safe to INSERT under strict FK enforcement.
30
- */
31
- resolvableIds: Set<string>;
32
- }
33
- /**
34
- * Make the provenance `task_id` foreign keys satisfiable on a consolidated
35
- * `cleo.db` by inserting any missing FK-parent rows in FK order (parent-before
36
- * -child), then returning the set of IDs the FK can resolve.
37
- *
38
- * ## Background (DHQ-051 · T11659 / T11818 — same class as DHQ-045)
39
- *
40
- * After the dual-scope `cleo.db` cutover (T11578) the runtime task store moved
41
- * from the bare `tasks` table to the prefixed `tasks_tasks` table. The drizzle
42
- * `schema.tasks` symbol is shadowed at the barrel (`tasks-schema.ts`) onto
43
- * `tasks_tasks`, so token-validity probes read the populated prefixed table.
44
- * BUT the provenance tables (`task_commits`, `pr_tasks`, `releases`,
45
- * `release_changes`) predate the cutover — their `task_id` / `epic_id` FKs
46
- * still reference the BARE `tasks` table, which is empty on a consolidated
47
- * `cleo.db`. A token validated against `tasks_tasks` therefore passes the
48
- * legacy gate yet violates the FK at INSERT time, aborting the whole verb with
49
- * `E_PROVENANCE_FAILED` (reconcile) or `E_INTERNAL` (plan).
50
- *
51
- * ## Strategy — FK-ordered parent backfill (Strategy 1)
52
- *
53
- * For every task referenced by this release we ensure a row exists in the FK
54
- * parent table BEFORE the child provenance rows are written. The parent table
55
- * is discovered dynamically via `PRAGMA foreign_key_list('task_commits')` (so
56
- * the logic tracks whatever the live schema enforces — including a future
57
- * schema that repoints the FK at `tasks_tasks`, where this becomes a no-op).
58
- * Missing parent rows are copied from the runtime `tasks_tasks` store via
59
- * `INSERT OR IGNORE … SELECT` of only the NOT NULL columns, so the copied rows
60
- * satisfy the parent table's own CHECK/NOT NULL constraints. Tasks that exist
61
- * in neither table remain unresolvable and are skipped-with-warn / NULLed by
62
- * the callers (the row, never the whole verb).
63
- *
64
- * Idempotent: `INSERT OR IGNORE` never duplicates and never overwrites an
65
- * existing parent row. Callers run it inside their write transaction so a later
66
- * failure rolls the shim rows back too.
67
- *
68
- * @param nativeDb - The consolidated `cleo.db` native handle.
69
- * @param referencedIds - Every task id this verb may reference (commit tokens,
70
- * PR tokens, plan tasks, epic).
71
- * @returns The FK parent table name + the IDs it can now resolve.
72
- */
73
- export declare function ensureProvenanceTaskFkParents(nativeDb: DatabaseSync, referencedIds: ReadonlySet<string>): FkParentTaskResolution;
74
- //# sourceMappingURL=provenance-fk.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"provenance-fk.d.ts","sourceRoot":"","sources":["../../src/release/provenance-fk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIvD,kFAAkF;AAClF,MAAM,WAAW,sBAAsB;IACrC,gFAAgF;IAChF,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;;;;OAIG;IACH,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,YAAY,EACtB,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,GACjC,sBAAsB,CAmExB"}
@@ -1,122 +0,0 @@
1
- /**
2
- * Post-cutover provenance `task_id` / `epic_id` FK reconciliation (DHQ-051).
3
- *
4
- * Shared between `cleo release reconcile` ({@link releaseReconcileV2}) and
5
- * `cleo release plan` ({@link releasePlan} → `upsertReleasesRow`). Both verbs
6
- * write provenance rows (`task_commits`, `pr_tasks`, `releases`,
7
- * `release_changes`) whose `task_id` / `epic_id` foreign keys reference the
8
- * BARE `tasks` table — which is empty on a consolidated dual-scope `cleo.db`
9
- * after the T11578 cutover (runtime task data moved to the prefixed
10
- * `tasks_tasks` table). A row inserted with an `epicId` / `taskId` that exists
11
- * only in `tasks_tasks` violates the FK at INSERT time, aborting the verb.
12
- *
13
- * This module is the single source of truth for the FK-ordered parent backfill
14
- * that makes those FKs satisfiable. `reconcile.ts` re-exports both symbols so
15
- * its public surface is unchanged.
16
- *
17
- * @task T11659 — reconcile slice (original)
18
- * @task T11818 — plan/open slice (DRY hoist)
19
- * @epic T11466
20
- */
21
- import { getLogger } from '../logger.js';
22
- const log = getLogger('release:provenance-fk');
23
- /**
24
- * Make the provenance `task_id` foreign keys satisfiable on a consolidated
25
- * `cleo.db` by inserting any missing FK-parent rows in FK order (parent-before
26
- * -child), then returning the set of IDs the FK can resolve.
27
- *
28
- * ## Background (DHQ-051 · T11659 / T11818 — same class as DHQ-045)
29
- *
30
- * After the dual-scope `cleo.db` cutover (T11578) the runtime task store moved
31
- * from the bare `tasks` table to the prefixed `tasks_tasks` table. The drizzle
32
- * `schema.tasks` symbol is shadowed at the barrel (`tasks-schema.ts`) onto
33
- * `tasks_tasks`, so token-validity probes read the populated prefixed table.
34
- * BUT the provenance tables (`task_commits`, `pr_tasks`, `releases`,
35
- * `release_changes`) predate the cutover — their `task_id` / `epic_id` FKs
36
- * still reference the BARE `tasks` table, which is empty on a consolidated
37
- * `cleo.db`. A token validated against `tasks_tasks` therefore passes the
38
- * legacy gate yet violates the FK at INSERT time, aborting the whole verb with
39
- * `E_PROVENANCE_FAILED` (reconcile) or `E_INTERNAL` (plan).
40
- *
41
- * ## Strategy — FK-ordered parent backfill (Strategy 1)
42
- *
43
- * For every task referenced by this release we ensure a row exists in the FK
44
- * parent table BEFORE the child provenance rows are written. The parent table
45
- * is discovered dynamically via `PRAGMA foreign_key_list('task_commits')` (so
46
- * the logic tracks whatever the live schema enforces — including a future
47
- * schema that repoints the FK at `tasks_tasks`, where this becomes a no-op).
48
- * Missing parent rows are copied from the runtime `tasks_tasks` store via
49
- * `INSERT OR IGNORE … SELECT` of only the NOT NULL columns, so the copied rows
50
- * satisfy the parent table's own CHECK/NOT NULL constraints. Tasks that exist
51
- * in neither table remain unresolvable and are skipped-with-warn / NULLed by
52
- * the callers (the row, never the whole verb).
53
- *
54
- * Idempotent: `INSERT OR IGNORE` never duplicates and never overwrites an
55
- * existing parent row. Callers run it inside their write transaction so a later
56
- * failure rolls the shim rows back too.
57
- *
58
- * @param nativeDb - The consolidated `cleo.db` native handle.
59
- * @param referencedIds - Every task id this verb may reference (commit tokens,
60
- * PR tokens, plan tasks, epic).
61
- * @returns The FK parent table name + the IDs it can now resolve.
62
- */
63
- export function ensureProvenanceTaskFkParents(nativeDb, referencedIds) {
64
- // Discover the physical parent table the `task_commits.task_id` FK targets.
65
- let parentTable = 'tasks';
66
- try {
67
- const fks = nativeDb.prepare(`PRAGMA foreign_key_list('task_commits')`).all();
68
- const taskFk = fks.find((fk) => fk.from === 'task_id');
69
- if (taskFk?.table)
70
- parentTable = taskFk.table;
71
- }
72
- catch {
73
- // PRAGMA unavailable (table absent / older sqlite) — keep the bare default.
74
- }
75
- const tableExists = (name) => nativeDb.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name=?`).get(name) !=
76
- null;
77
- // Parent table absent entirely → nothing resolvable, nothing to backfill.
78
- if (!tableExists(parentTable)) {
79
- return { parentTable: null, resolvableIds: new Set() };
80
- }
81
- // FK-ordered shim backfill: copy any referenced task that lives in the
82
- // runtime `tasks_tasks` store but is missing from the FK parent. Only when
83
- // the parent is the bare legacy table AND the prefixed store exists (i.e. the
84
- // post-cutover split-brain) — when the FK already points at `tasks_tasks`
85
- // this is skipped and the parent is read as-is.
86
- // Identifiers come from sqlite_master / the FK pragma (verified existing
87
- // table names), never user input — safe to interpolate (node:sqlite exposes
88
- // no bound-identifier form). All VALUES are bound parameters.
89
- if (parentTable !== 'tasks_tasks' && tableExists('tasks_tasks') && referencedIds.size > 0) {
90
- // `pipeline_stage` is copied alongside the NOT NULL columns so the bare
91
- // `tasks` table's T877 invariant trigger (status='done' ⇒ pipeline_stage IN
92
- // ('contribution','cancelled'); status='cancelled' ⇒ pipeline_stage=
93
- // 'cancelled') is satisfied. A released epic is typically status='done', so
94
- // omitting `pipeline_stage` would RAISE(ABORT) the shim copy and leave the
95
- // epic unresolvable (DHQ-051 · T11818).
96
- const insertShim = nativeDb.prepare(`INSERT OR IGNORE INTO "${parentTable}" (id, title, status, priority, role, scope, pipeline_stage)
97
- SELECT id, title, status, priority, role, scope, pipeline_stage FROM tasks_tasks WHERE id = ?`);
98
- for (const id of referencedIds) {
99
- try {
100
- insertShim.run(id);
101
- }
102
- catch (err) {
103
- // A single shim copy that violates the parent's own constraints (e.g. a
104
- // NOT NULL / CHECK the prefixed row does not satisfy) must NOT abort the
105
- // whole verb. Leave the id unresolvable — the writers below
106
- // skip-with-warn that one link (Strategy 3). SAVEPOINT-free: the failed
107
- // statement is rolled back individually by SQLite, leaving the
108
- // surrounding transaction intact.
109
- log.warn({ taskId: id, parentTable, err: err instanceof Error ? err.message : String(err) }, 'provenance: FK-parent shim copy failed — task_id stays unresolvable, link will be skipped');
110
- }
111
- }
112
- }
113
- // Read back what the FK can now resolve.
114
- const resolvableIds = new Set();
115
- const rows = nativeDb.prepare(`SELECT id FROM "${parentTable}"`).all();
116
- for (const r of rows) {
117
- if (typeof r.id === 'string')
118
- resolvableIds.add(r.id);
119
- }
120
- return { parentTable, resolvableIds };
121
- }
122
- //# sourceMappingURL=provenance-fk.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"provenance-fk.js","sourceRoot":"","sources":["../../src/release/provenance-fk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGzC,MAAM,GAAG,GAAG,SAAS,CAAC,uBAAuB,CAAC,CAAC;AAc/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,UAAU,6BAA6B,CAC3C,QAAsB,EACtB,aAAkC;IAElC,4EAA4E;IAC5E,IAAI,WAAW,GAAG,OAAO,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,yCAAyC,CAAC,CAAC,GAAG,EAGzE,CAAC;QACH,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;QACvD,IAAI,MAAM,EAAE,KAAK;YAAE,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,4EAA4E;IAC9E,CAAC;IAED,MAAM,WAAW,GAAG,CAAC,IAAY,EAAW,EAAE,CAC5C,QAAQ,CAAC,OAAO,CAAC,8DAA8D,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC;QAC1F,IAAI,CAAC;IAEP,0EAA0E;IAC1E,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,EAAE,CAAC;QAC9B,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,GAAG,EAAU,EAAE,CAAC;IACjE,CAAC;IAED,uEAAuE;IACvE,2EAA2E;IAC3E,8EAA8E;IAC9E,0EAA0E;IAC1E,gDAAgD;IAChD,yEAAyE;IACzE,4EAA4E;IAC5E,8DAA8D;IAC9D,IAAI,WAAW,KAAK,aAAa,IAAI,WAAW,CAAC,aAAa,CAAC,IAAI,aAAa,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QAC1F,wEAAwE;QACxE,4EAA4E;QAC5E,qEAAqE;QACrE,4EAA4E;QAC5E,2EAA2E;QAC3E,wCAAwC;QACxC,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CACjC,0BAA0B,WAAW;qGAC0D,CAChG,CAAC;QACF,KAAK,MAAM,EAAE,IAAI,aAAa,EAAE,CAAC;YAC/B,IAAI,CAAC;gBACH,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACrB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,wEAAwE;gBACxE,yEAAyE;gBACzE,4DAA4D;gBAC5D,wEAAwE;gBACxE,+DAA+D;gBAC/D,kCAAkC;gBAClC,GAAG,CAAC,IAAI,CACN,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,EAAE,GAAG,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAClF,2FAA2F,CAC5F,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,yCAAyC;IACzC,MAAM,aAAa,GAAG,IAAI,GAAG,EAAU,CAAC;IACxC,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,mBAAmB,WAAW,GAAG,CAAC,CAAC,GAAG,EAA4B,CAAC;IACjG,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ;YAAE,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,CAAC;AACxC,CAAC"}