@cleocode/core 2026.3.43 → 2026.3.45

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 (183) hide show
  1. package/dist/admin/export-tasks.d.ts.map +1 -1
  2. package/dist/admin/import-tasks.d.ts +10 -2
  3. package/dist/admin/import-tasks.d.ts.map +1 -1
  4. package/dist/agents/agent-schema.d.ts +358 -0
  5. package/dist/agents/agent-schema.d.ts.map +1 -0
  6. package/dist/agents/capacity.d.ts +57 -0
  7. package/dist/agents/capacity.d.ts.map +1 -0
  8. package/dist/agents/index.d.ts +17 -0
  9. package/dist/agents/index.d.ts.map +1 -0
  10. package/dist/agents/registry.d.ts +115 -0
  11. package/dist/agents/registry.d.ts.map +1 -0
  12. package/dist/agents/retry.d.ts +83 -0
  13. package/dist/agents/retry.d.ts.map +1 -0
  14. package/dist/hooks/index.d.ts +4 -1
  15. package/dist/hooks/index.d.ts.map +1 -1
  16. package/dist/hooks/payload-schemas.d.ts +214 -0
  17. package/dist/hooks/payload-schemas.d.ts.map +1 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +16937 -2371
  21. package/dist/index.js.map +4 -4
  22. package/dist/inject/index.d.ts.map +1 -1
  23. package/dist/intelligence/impact.d.ts +51 -0
  24. package/dist/intelligence/impact.d.ts.map +1 -0
  25. package/dist/intelligence/index.d.ts +15 -0
  26. package/dist/intelligence/index.d.ts.map +1 -0
  27. package/dist/intelligence/patterns.d.ts +66 -0
  28. package/dist/intelligence/patterns.d.ts.map +1 -0
  29. package/dist/intelligence/prediction.d.ts +51 -0
  30. package/dist/intelligence/prediction.d.ts.map +1 -0
  31. package/dist/intelligence/types.d.ts +221 -0
  32. package/dist/intelligence/types.d.ts.map +1 -0
  33. package/dist/internal.d.ts +12 -1
  34. package/dist/internal.d.ts.map +1 -1
  35. package/dist/issue/template-parser.d.ts +8 -2
  36. package/dist/issue/template-parser.d.ts.map +1 -1
  37. package/dist/lifecycle/pipeline.d.ts +2 -2
  38. package/dist/lifecycle/pipeline.d.ts.map +1 -1
  39. package/dist/lifecycle/state-machine.d.ts +1 -1
  40. package/dist/lifecycle/state-machine.d.ts.map +1 -1
  41. package/dist/memory/brain-lifecycle.d.ts.map +1 -1
  42. package/dist/memory/brain-retrieval.d.ts.map +1 -1
  43. package/dist/memory/brain-row-types.d.ts +40 -6
  44. package/dist/memory/brain-row-types.d.ts.map +1 -1
  45. package/dist/memory/brain-search.d.ts.map +1 -1
  46. package/dist/memory/brain-similarity.d.ts.map +1 -1
  47. package/dist/memory/claude-mem-migration.d.ts.map +1 -1
  48. package/dist/nexus/discover.d.ts.map +1 -1
  49. package/dist/nexus/index.d.ts +2 -0
  50. package/dist/nexus/index.d.ts.map +1 -1
  51. package/dist/nexus/transfer-types.d.ts +123 -0
  52. package/dist/nexus/transfer-types.d.ts.map +1 -0
  53. package/dist/nexus/transfer.d.ts +31 -0
  54. package/dist/nexus/transfer.d.ts.map +1 -0
  55. package/dist/orchestration/bootstrap.d.ts.map +1 -1
  56. package/dist/orchestration/skill-ops.d.ts +4 -4
  57. package/dist/orchestration/skill-ops.d.ts.map +1 -1
  58. package/dist/otel/index.d.ts +1 -1
  59. package/dist/otel/index.d.ts.map +1 -1
  60. package/dist/sessions/briefing.d.ts.map +1 -1
  61. package/dist/sessions/handoff.d.ts.map +1 -1
  62. package/dist/sessions/index.d.ts +1 -1
  63. package/dist/sessions/index.d.ts.map +1 -1
  64. package/dist/sessions/types.d.ts +8 -42
  65. package/dist/sessions/types.d.ts.map +1 -1
  66. package/dist/signaldock/signaldock-transport.d.ts +1 -1
  67. package/dist/signaldock/signaldock-transport.d.ts.map +1 -1
  68. package/dist/skills/injection/subagent.d.ts +3 -3
  69. package/dist/skills/injection/subagent.d.ts.map +1 -1
  70. package/dist/skills/manifests/contribution.d.ts +2 -2
  71. package/dist/skills/manifests/contribution.d.ts.map +1 -1
  72. package/dist/skills/orchestrator/spawn.d.ts +6 -6
  73. package/dist/skills/orchestrator/spawn.d.ts.map +1 -1
  74. package/dist/skills/orchestrator/startup.d.ts +1 -1
  75. package/dist/skills/orchestrator/startup.d.ts.map +1 -1
  76. package/dist/skills/orchestrator/validator.d.ts +2 -2
  77. package/dist/skills/orchestrator/validator.d.ts.map +1 -1
  78. package/dist/skills/precedence-types.d.ts +24 -1
  79. package/dist/skills/precedence-types.d.ts.map +1 -1
  80. package/dist/skills/types.d.ts +70 -4
  81. package/dist/skills/types.d.ts.map +1 -1
  82. package/dist/store/brain-sqlite.d.ts +4 -1
  83. package/dist/store/brain-sqlite.d.ts.map +1 -1
  84. package/dist/store/export.d.ts +5 -4
  85. package/dist/store/export.d.ts.map +1 -1
  86. package/dist/store/nexus-sqlite.d.ts +4 -1
  87. package/dist/store/nexus-sqlite.d.ts.map +1 -1
  88. package/dist/store/sqlite.d.ts +4 -1
  89. package/dist/store/sqlite.d.ts.map +1 -1
  90. package/dist/store/tasks-schema.d.ts +14 -4
  91. package/dist/store/tasks-schema.d.ts.map +1 -1
  92. package/dist/store/typed-query.d.ts +12 -0
  93. package/dist/store/typed-query.d.ts.map +1 -0
  94. package/dist/store/validation-schemas.d.ts +2423 -50
  95. package/dist/store/validation-schemas.d.ts.map +1 -1
  96. package/dist/system/inject-generate.d.ts.map +1 -1
  97. package/dist/validation/doctor/checks.d.ts +5 -0
  98. package/dist/validation/doctor/checks.d.ts.map +1 -1
  99. package/dist/validation/engine.d.ts +10 -10
  100. package/dist/validation/engine.d.ts.map +1 -1
  101. package/dist/validation/index.d.ts +6 -2
  102. package/dist/validation/index.d.ts.map +1 -1
  103. package/dist/validation/protocol-common.d.ts +10 -2
  104. package/dist/validation/protocol-common.d.ts.map +1 -1
  105. package/migrations/drizzle-tasks/20260320013731_wave0-schema-hardening/migration.sql +84 -0
  106. package/migrations/drizzle-tasks/20260320013731_wave0-schema-hardening/snapshot.json +4060 -0
  107. package/migrations/drizzle-tasks/20260320020000_agent-dimension/migration.sql +35 -0
  108. package/migrations/drizzle-tasks/20260320020000_agent-dimension/snapshot.json +4312 -0
  109. package/package.json +2 -2
  110. package/src/admin/export-tasks.ts +2 -5
  111. package/src/admin/import-tasks.ts +53 -29
  112. package/src/agents/__tests__/capacity.test.ts +219 -0
  113. package/src/agents/__tests__/registry.test.ts +457 -0
  114. package/src/agents/__tests__/retry.test.ts +289 -0
  115. package/src/agents/agent-schema.ts +107 -0
  116. package/src/agents/capacity.ts +151 -0
  117. package/src/agents/index.ts +68 -0
  118. package/src/agents/registry.ts +449 -0
  119. package/src/agents/retry.ts +255 -0
  120. package/src/hooks/index.ts +20 -1
  121. package/src/hooks/payload-schemas.ts +199 -0
  122. package/src/index.ts +69 -0
  123. package/src/inject/index.ts +14 -14
  124. package/src/intelligence/__tests__/impact.test.ts +453 -0
  125. package/src/intelligence/__tests__/patterns.test.ts +450 -0
  126. package/src/intelligence/__tests__/prediction.test.ts +418 -0
  127. package/src/intelligence/impact.ts +638 -0
  128. package/src/intelligence/index.ts +47 -0
  129. package/src/intelligence/patterns.ts +621 -0
  130. package/src/intelligence/prediction.ts +621 -0
  131. package/src/intelligence/types.ts +273 -0
  132. package/src/internal.ts +89 -2
  133. package/src/issue/template-parser.ts +65 -4
  134. package/src/lifecycle/pipeline.ts +14 -7
  135. package/src/lifecycle/state-machine.ts +6 -2
  136. package/src/memory/brain-lifecycle.ts +5 -11
  137. package/src/memory/brain-retrieval.ts +44 -38
  138. package/src/memory/brain-row-types.ts +43 -6
  139. package/src/memory/brain-search.ts +53 -32
  140. package/src/memory/brain-similarity.ts +9 -8
  141. package/src/memory/claude-mem-migration.ts +4 -3
  142. package/src/nexus/__tests__/nexus-e2e.test.ts +1481 -0
  143. package/src/nexus/__tests__/transfer.test.ts +446 -0
  144. package/src/nexus/discover.ts +1 -0
  145. package/src/nexus/index.ts +14 -0
  146. package/src/nexus/transfer-types.ts +129 -0
  147. package/src/nexus/transfer.ts +314 -0
  148. package/src/orchestration/bootstrap.ts +11 -17
  149. package/src/orchestration/skill-ops.ts +52 -32
  150. package/src/otel/index.ts +48 -4
  151. package/src/sessions/__tests__/briefing.test.ts +31 -2
  152. package/src/sessions/briefing.ts +27 -42
  153. package/src/sessions/handoff.ts +52 -86
  154. package/src/sessions/index.ts +5 -1
  155. package/src/sessions/types.ts +9 -43
  156. package/src/signaldock/signaldock-transport.ts +5 -2
  157. package/src/skills/injection/subagent.ts +10 -16
  158. package/src/skills/manifests/contribution.ts +5 -13
  159. package/src/skills/orchestrator/__tests__/spawn-tier.test.ts +44 -30
  160. package/src/skills/orchestrator/spawn.ts +18 -31
  161. package/src/skills/orchestrator/startup.ts +78 -65
  162. package/src/skills/orchestrator/validator.ts +26 -31
  163. package/src/skills/precedence-types.ts +24 -1
  164. package/src/skills/types.ts +72 -5
  165. package/src/store/__tests__/test-db-helper.d.ts +4 -4
  166. package/src/store/__tests__/test-db-helper.js +5 -16
  167. package/src/store/__tests__/test-db-helper.ts +5 -18
  168. package/src/store/brain-sqlite.ts +7 -3
  169. package/src/store/chain-schema.ts +1 -1
  170. package/src/store/export.ts +22 -12
  171. package/src/store/nexus-sqlite.ts +7 -3
  172. package/src/store/sqlite.ts +9 -3
  173. package/src/store/tasks-schema.ts +65 -8
  174. package/src/store/typed-query.ts +17 -0
  175. package/src/store/validation-schemas.ts +347 -23
  176. package/src/system/inject-generate.ts +9 -23
  177. package/src/validation/doctor/checks.ts +24 -2
  178. package/src/validation/engine.ts +11 -11
  179. package/src/validation/index.ts +131 -3
  180. package/src/validation/protocol-common.ts +54 -3
  181. package/dist/tasks/reparent.d.ts +0 -38
  182. package/dist/tasks/reparent.d.ts.map +0 -1
  183. package/src/tasks/reparent.ts +0 -134
@@ -29,6 +29,22 @@ vi.mock('../../discovery.js', () => ({
29
29
  mapSkillName: vi.fn(() => ({ canonical: 'task-executor', mapped: true })),
30
30
  }));
31
31
 
32
+ vi.mock('../../../store/data-accessor.js', () => ({
33
+ getAccessor: vi.fn().mockResolvedValue({
34
+ loadSingleTask: vi.fn().mockResolvedValue({
35
+ id: 'T100',
36
+ title: 'Test task',
37
+ status: 'active',
38
+ description: 'A test',
39
+ priority: 'medium',
40
+ labels: [],
41
+ depends: [],
42
+ }),
43
+ loadTasks: vi.fn().mockResolvedValue([]),
44
+ queryTasks: vi.fn().mockResolvedValue({ tasks: [], total: 0 }),
45
+ }),
46
+ }));
47
+
32
48
  import { existsSync, readFileSync } from 'node:fs';
33
49
  import { findSkill } from '../../discovery.js';
34
50
  import { injectProtocol, orchestratorSpawnSkill } from '../../injection/subagent.js';
@@ -67,7 +83,7 @@ describe('orchestratorSpawnSkill tier passthrough', () => {
67
83
  } as any);
68
84
  });
69
85
 
70
- it('passes tier 0 through — output excludes standard and orchestrator', () => {
86
+ it('passes tier 0 through — output excludes standard and orchestrator', async () => {
71
87
  vi.mocked(readFileSync).mockImplementation((path: unknown) => {
72
88
  const p = String(path);
73
89
  if (p.includes('subagent-protocol-base')) return TIERED_PROTOCOL;
@@ -75,7 +91,7 @@ describe('orchestratorSpawnSkill tier passthrough', () => {
75
91
  return '';
76
92
  });
77
93
 
78
- const result = orchestratorSpawnSkill(
94
+ const result = await orchestratorSpawnSkill(
79
95
  'T100',
80
96
  'test-skill',
81
97
  { TASK_ID: 'T100' },
@@ -89,7 +105,7 @@ describe('orchestratorSpawnSkill tier passthrough', () => {
89
105
  expect(result).toContain('Test Skill');
90
106
  });
91
107
 
92
- it('passes tier 1 through — output includes minimal + standard', () => {
108
+ it('passes tier 1 through — output includes minimal + standard', async () => {
93
109
  vi.mocked(readFileSync).mockImplementation((path: unknown) => {
94
110
  const p = String(path);
95
111
  if (p.includes('subagent-protocol-base')) return TIERED_PROTOCOL;
@@ -97,7 +113,7 @@ describe('orchestratorSpawnSkill tier passthrough', () => {
97
113
  return '';
98
114
  });
99
115
 
100
- const result = orchestratorSpawnSkill(
116
+ const result = await orchestratorSpawnSkill(
101
117
  'T100',
102
118
  'test-skill',
103
119
  { TASK_ID: 'T100' },
@@ -110,7 +126,7 @@ describe('orchestratorSpawnSkill tier passthrough', () => {
110
126
  expect(result).not.toContain('Orchestrator Section');
111
127
  });
112
128
 
113
- it('passes tier 2 through — output includes all tiers', () => {
129
+ it('passes tier 2 through — output includes all tiers', async () => {
114
130
  vi.mocked(readFileSync).mockImplementation((path: unknown) => {
115
131
  const p = String(path);
116
132
  if (p.includes('subagent-protocol-base')) return TIERED_PROTOCOL;
@@ -118,7 +134,7 @@ describe('orchestratorSpawnSkill tier passthrough', () => {
118
134
  return '';
119
135
  });
120
136
 
121
- const result = orchestratorSpawnSkill(
137
+ const result = await orchestratorSpawnSkill(
122
138
  'T100',
123
139
  'test-skill',
124
140
  { TASK_ID: 'T100' },
@@ -131,7 +147,7 @@ describe('orchestratorSpawnSkill tier passthrough', () => {
131
147
  expect(result).toContain('Orchestrator Section');
132
148
  });
133
149
 
134
- it('omitting tier includes full protocol (backward compat)', () => {
150
+ it('omitting tier includes full protocol (backward compat)', async () => {
135
151
  vi.mocked(readFileSync).mockImplementation((path: unknown) => {
136
152
  const p = String(path);
137
153
  if (p.includes('subagent-protocol-base')) return TIERED_PROTOCOL;
@@ -140,7 +156,7 @@ describe('orchestratorSpawnSkill tier passthrough', () => {
140
156
  });
141
157
 
142
158
  // No tier parameter — should pass through unfiltered
143
- const result = orchestratorSpawnSkill(
159
+ const result = await orchestratorSpawnSkill(
144
160
  'T100',
145
161
  'test-skill',
146
162
  { TASK_ID: 'T100' },
@@ -153,12 +169,12 @@ describe('orchestratorSpawnSkill tier passthrough', () => {
153
169
  expect(result).toContain('TIER:orchestrator');
154
170
  });
155
171
 
156
- it('throws when skill not found', () => {
172
+ it('throws when skill not found', async () => {
157
173
  vi.mocked(findSkill).mockReturnValue(null);
158
174
 
159
- expect(() => {
160
- orchestratorSpawnSkill('T100', 'nonexistent', { TASK_ID: 'T100' }, '/mock/project', 0);
161
- }).toThrow(/Skill not found/);
175
+ await expect(
176
+ orchestratorSpawnSkill('T100', 'nonexistent', { TASK_ID: 'T100' }, '/mock/project', 0),
177
+ ).rejects.toThrow(/Skill not found/);
162
178
  });
163
179
  });
164
180
 
@@ -168,29 +184,27 @@ describe('injectProtocol tier parameter', () => {
168
184
  vi.mocked(existsSync).mockReturnValue(true);
169
185
  });
170
186
 
171
- it('tier undefined leaves protocol unfiltered', () => {
187
+ it('tier undefined leaves protocol unfiltered', async () => {
172
188
  vi.mocked(readFileSync).mockImplementation((path: unknown) => {
173
189
  const p = String(path);
174
190
  if (p.includes('subagent-protocol-base')) return TIERED_PROTOCOL;
175
- if (p.includes('tasks.json')) return MOCK_TASKS_JSON;
176
191
  return '';
177
192
  });
178
193
 
179
- const result = injectProtocol('# Skill', 'T100', {}, '/mock/project', undefined);
194
+ const result = await injectProtocol('# Skill', 'T100', {}, '/mock/project', undefined);
180
195
 
181
196
  expect(result).toContain('TIER:minimal');
182
197
  expect(result).toContain('TIER:orchestrator');
183
198
  });
184
199
 
185
- it('tier 0 filters to minimal only', () => {
200
+ it('tier 0 filters to minimal only', async () => {
186
201
  vi.mocked(readFileSync).mockImplementation((path: unknown) => {
187
202
  const p = String(path);
188
203
  if (p.includes('subagent-protocol-base')) return TIERED_PROTOCOL;
189
- if (p.includes('tasks.json')) return MOCK_TASKS_JSON;
190
204
  return '';
191
205
  });
192
206
 
193
- const result = injectProtocol('# Skill', 'T100', {}, '/mock/project', 0);
207
+ const result = await injectProtocol('# Skill', 'T100', {}, '/mock/project', 0);
194
208
 
195
209
  expect(result).toContain('Minimal Section');
196
210
  expect(result).not.toContain('Standard Section');
@@ -218,43 +232,43 @@ describe('buildPrompt tier passthrough', () => {
218
232
  } as any);
219
233
  });
220
234
 
221
- it('buildPrompt with tier 0 filters protocol to minimal only', () => {
222
- const result = buildPrompt('T100', 'TASK-EXECUTOR', '/mock/project', 0);
235
+ it('buildPrompt with tier 0 filters protocol to minimal only', async () => {
236
+ const result = await buildPrompt('T100', 'TASK-EXECUTOR', '/mock/project', 0);
223
237
  expect(result.prompt).toContain('Minimal Section');
224
238
  expect(result.prompt).not.toContain('Standard Section');
225
239
  expect(result.prompt).not.toContain('Orchestrator Section');
226
240
  expect(result.prompt).toContain('SUBAGENT PROTOCOL');
227
241
  });
228
242
 
229
- it('buildPrompt with tier 1 includes minimal + standard', () => {
230
- const result = buildPrompt('T100', 'TASK-EXECUTOR', '/mock/project', 1);
243
+ it('buildPrompt with tier 1 includes minimal + standard', async () => {
244
+ const result = await buildPrompt('T100', 'TASK-EXECUTOR', '/mock/project', 1);
231
245
  expect(result.prompt).toContain('Minimal Section');
232
246
  expect(result.prompt).toContain('Standard Section');
233
247
  expect(result.prompt).not.toContain('Orchestrator Section');
234
248
  });
235
249
 
236
- it('buildPrompt without tier includes full protocol', () => {
237
- const result = buildPrompt('T100', 'TASK-EXECUTOR', '/mock/project');
250
+ it('buildPrompt without tier includes full protocol', async () => {
251
+ const result = await buildPrompt('T100', 'TASK-EXECUTOR', '/mock/project');
238
252
  // No tier = unfiltered, so tier markers present
239
253
  expect(result.prompt).toContain('TIER:minimal');
240
254
  expect(result.prompt).toContain('TIER:orchestrator');
241
255
  });
242
256
 
243
- it('buildPrompt injects task context', () => {
244
- const result = buildPrompt('T100', 'TASK-EXECUTOR', '/mock/project', 0);
257
+ it('buildPrompt injects task context', async () => {
258
+ const result = await buildPrompt('T100', 'TASK-EXECUTOR', '/mock/project', 0);
245
259
  expect(result.prompt).toContain('Task Context');
246
260
  expect(result.prompt).toContain('T100');
247
261
  });
248
262
 
249
- it('spawn passes tier through', () => {
250
- const result = spawn('T100', 'TASK-EXECUTOR', '/mock/project', 0);
263
+ it('spawn passes tier through', async () => {
264
+ const result = await spawn('T100', 'TASK-EXECUTOR', '/mock/project', 0);
251
265
  expect(result.prompt).toContain('Minimal Section');
252
266
  expect(result.prompt).not.toContain('Orchestrator Section');
253
267
  expect(result.spawnTimestamp).toBeDefined();
254
268
  });
255
269
 
256
- it('spawnBatch passes tier through', () => {
257
- const result = spawnBatch(['T100'], 'TASK-EXECUTOR', '/mock/project', 0);
270
+ it('spawnBatch passes tier through', async () => {
271
+ const result = await spawnBatch(['T100'], 'TASK-EXECUTOR', '/mock/project', 0);
258
272
  expect(result.succeeded).toBe(1);
259
273
  expect(result.spawns[0].result!.prompt).toContain('Minimal Section');
260
274
  expect(result.spawns[0].result!.prompt).not.toContain('Orchestrator Section');
@@ -12,11 +12,11 @@
12
12
  * @task T4519
13
13
  */
14
14
 
15
- import { existsSync, readFileSync } from 'node:fs';
16
15
  import type { Task } from '@cleocode/contracts';
17
16
  import { ExitCode } from '@cleocode/contracts';
18
17
  import { CleoError } from '../../errors.js';
19
- import { getAgentOutputsDir, getTaskPath } from '../../paths.js';
18
+ import { getAgentOutputsDir } from '../../paths.js';
19
+ import { getAccessor } from '../../store/data-accessor.js';
20
20
  import { findSkill, mapSkillName } from '../discovery.js';
21
21
  import { injectProtocol } from '../injection/subagent.js';
22
22
  import type { TokenValues } from '../injection/token.js';
@@ -30,22 +30,14 @@ import type { SpawnPromptResult } from '../types.js';
30
30
  * Build a fully-resolved prompt for spawning a subagent.
31
31
  * @task T4519
32
32
  */
33
- export function buildPrompt(
33
+ export async function buildPrompt(
34
34
  taskId: string,
35
35
  templateName: string = 'TASK-EXECUTOR',
36
36
  cwd?: string,
37
37
  tier?: 0 | 1 | 2,
38
- ): SpawnPromptResult {
39
- const taskPath = getTaskPath(cwd);
40
-
41
- if (!existsSync(taskPath)) {
42
- throw new CleoError(ExitCode.NOT_FOUND, 'Todo file not found. Run: cleo init');
43
- }
44
-
45
- // Load task
46
- const data = JSON.parse(readFileSync(taskPath, 'utf-8'));
47
- const tasks: Task[] = data.tasks ?? [];
48
- const task = tasks.find((t) => t.id === taskId);
38
+ ): Promise<SpawnPromptResult> {
39
+ const acc = await getAccessor(cwd);
40
+ const task = await acc.loadSingleTask(taskId);
49
41
 
50
42
  if (!task) {
51
43
  throw new CleoError(ExitCode.NOT_FOUND, `Task ${taskId} not found`);
@@ -121,7 +113,7 @@ export function buildPrompt(
121
113
  }
122
114
 
123
115
  // Inject tokens and protocol into template
124
- const promptContent = injectProtocol(skill.content, taskId, tokenValues, cwd, tier);
116
+ const promptContent = await injectProtocol(skill.content, taskId, tokenValues, cwd, tier);
125
117
 
126
118
  return {
127
119
  taskId,
@@ -138,13 +130,13 @@ export function buildPrompt(
138
130
  * Generate full spawn command with metadata.
139
131
  * @task T4519
140
132
  */
141
- export function spawn(
133
+ export async function spawn(
142
134
  taskId: string,
143
135
  templateName: string = 'TASK-EXECUTOR',
144
136
  cwd?: string,
145
137
  tier?: 0 | 1 | 2,
146
- ): SpawnPromptResult & { spawnTimestamp: string } {
147
- const result = buildPrompt(taskId, templateName, cwd, tier);
138
+ ): Promise<SpawnPromptResult & { spawnTimestamp: string }> {
139
+ const result = await buildPrompt(taskId, templateName, cwd, tier);
148
140
 
149
141
  return {
150
142
  ...result,
@@ -156,25 +148,20 @@ export function spawn(
156
148
  * Check if tasks can be spawned in parallel (no inter-dependencies).
157
149
  * @task T4519
158
150
  */
159
- export function canParallelize(
151
+ export async function canParallelize(
160
152
  taskIds: string[],
161
153
  cwd?: string,
162
- ): {
154
+ ): Promise<{
163
155
  canParallelize: boolean;
164
156
  conflicts: Array<Pick<Task, 'id'> & { dependsOn: string[] }>;
165
157
  safeToSpawn: string[];
166
- } {
158
+ }> {
167
159
  if (taskIds.length === 0) {
168
160
  return { canParallelize: true, conflicts: [], safeToSpawn: [] };
169
161
  }
170
162
 
171
- const taskPath = getTaskPath(cwd);
172
- if (!existsSync(taskPath)) {
173
- return { canParallelize: true, conflicts: [], safeToSpawn: taskIds };
174
- }
175
-
176
- const data = JSON.parse(readFileSync(taskPath, 'utf-8'));
177
- const tasks: Task[] = data.tasks ?? [];
163
+ const acc = await getAccessor(cwd);
164
+ const tasks = await acc.loadTasks(taskIds);
178
165
  const taskIdSet = new Set(taskIds);
179
166
 
180
167
  const conflicts: Array<Pick<Task, 'id'> & { dependsOn: string[] }> = [];
@@ -229,17 +216,17 @@ export interface BatchSpawnResult {
229
216
  * @task T4712
230
217
  * @epic T4663
231
218
  */
232
- export function spawnBatch(
219
+ export async function spawnBatch(
233
220
  taskIds: string[],
234
221
  templateName?: string,
235
222
  cwd?: string,
236
223
  tier?: 0 | 1 | 2,
237
- ): BatchSpawnResult {
224
+ ): Promise<BatchSpawnResult> {
238
225
  const spawns: BatchSpawnEntry[] = [];
239
226
 
240
227
  for (const taskId of taskIds) {
241
228
  try {
242
- const result = spawn(taskId, templateName, cwd, tier);
229
+ const result = await spawn(taskId, templateName, cwd, tier);
243
230
  spawns.push({ taskId, success: true, result });
244
231
  } catch (err: unknown) {
245
232
  const message = err instanceof Error ? err.message : String(err);
@@ -11,8 +11,9 @@
11
11
 
12
12
  import { existsSync, readFileSync } from 'node:fs';
13
13
  import { join } from 'node:path';
14
- import type { Task, TaskRef, TaskRefPriority } from '@cleocode/contracts';
15
- import { getCleoDirAbsolute, getSessionsPath, getTaskPath } from '../../paths.js';
14
+ import type { Task, TaskRef, TaskRefPriority, TaskWorkState } from '@cleocode/contracts';
15
+ import { getCleoDirAbsolute } from '../../paths.js';
16
+ import { getAccessor } from '../../store/data-accessor.js';
16
17
  import type {
17
18
  DependencyAnalysis,
18
19
  DependencyWave,
@@ -134,47 +135,65 @@ export interface SessionInitResult {
134
135
  * Determines the recommended action based on current state.
135
136
  * @task T4519
136
137
  */
137
- export async function sessionInit(_epicId?: string, cwd?: string): Promise<SessionInitResult> {
138
- const sessionsPath = getSessionsPath(cwd);
139
- const cleoDirAbs = getCleoDirAbsolute(cwd);
140
- const focusPath = join(cleoDirAbs, 'focus.json');
138
+ export async function sessionInit(epicId?: string, cwd?: string): Promise<SessionInitResult> {
139
+ const acc = await getAccessor(cwd);
141
140
 
142
- // Check active sessions
141
+ // Check active sessions from SQLite (ADR-006/ADR-020)
143
142
  let activeSessions = 0;
144
143
  let activeSessionId: string | null = null;
145
144
  let activeScope: string | null = null;
146
145
 
147
- if (existsSync(sessionsPath)) {
148
- try {
149
- const data = JSON.parse(readFileSync(sessionsPath, 'utf-8'));
150
- const active = (data.sessions ?? []).filter((s: { status: string }) => s.status === 'active');
151
- activeSessions = active.length;
152
- if (activeSessions > 0) {
153
- activeSessionId = active[0].id ?? null;
154
- activeScope = active[0].scope?.rootId ?? active[0].scope?.epicId ?? null;
146
+ try {
147
+ const sessions = await acc.loadSessions();
148
+ let active = sessions.filter((s) => s.status === 'active');
149
+
150
+ // If epicId provided, prefer sessions scoped to that epic
151
+ if (epicId && active.length > 0) {
152
+ const epicScoped = active.filter(
153
+ (s) => s.scope?.rootTaskId === epicId || s.scope?.epicId === epicId,
154
+ );
155
+ if (epicScoped.length > 0) {
156
+ active = epicScoped;
155
157
  }
156
- } catch {
157
- // Invalid sessions file
158
158
  }
159
+
160
+ activeSessions = active.length;
161
+ if (activeSessions > 0) {
162
+ activeSessionId = active[0].id ?? null;
163
+ activeScope = active[0].scope?.rootTaskId ?? active[0].scope?.epicId ?? null;
164
+ }
165
+ } catch {
166
+ // DB unavailable
167
+ }
168
+
169
+ // If epicId provided and no active session found, set activeScope to the epic
170
+ if (epicId && !activeScope) {
171
+ activeScope = epicId;
159
172
  }
160
173
 
161
- // Check focus
174
+ // Check focus state from SQLite meta KV
162
175
  let hasFocus = false;
163
176
  let focusedTask: string | null = null;
164
177
 
165
- if (existsSync(focusPath)) {
178
+ try {
179
+ const focus = await acc.getMetaValue<TaskWorkState>('focus_state');
180
+ focusedTask = focus?.currentTask ?? null;
181
+ hasFocus = !!focusedTask;
182
+ } catch {
183
+ // Focus unavailable
184
+ }
185
+
186
+ // Determine pending work: check if the epic has ready tasks via DataAccessor
187
+ let hasPending = false;
188
+ if (epicId) {
166
189
  try {
167
- const focus = JSON.parse(readFileSync(focusPath, 'utf-8'));
168
- focusedTask = focus.focusedTaskId ?? null;
169
- hasFocus = !!focusedTask;
190
+ const children = await acc.getChildren(epicId);
191
+ hasPending = children.some((t) => t.status === 'pending');
170
192
  } catch {
171
- // Invalid focus file
193
+ // Tasks unavailable
172
194
  }
173
195
  }
174
196
 
175
- // Determine pending work (simplified - check manifest for needs_followup)
176
- const hasPending = false; // Will be populated by manifest checks
177
-
178
197
  // Decision matrix
179
198
  let recommendedAction: SessionInitResult['recommendedAction'];
180
199
  let actionReason: string;
@@ -187,7 +206,9 @@ export async function sessionInit(_epicId?: string, cwd?: string): Promise<Sessi
187
206
  actionReason = 'Active session without focus - query manifest and spawn next agent';
188
207
  } else if (hasPending) {
189
208
  recommendedAction = 'create_and_spawn';
190
- actionReason = 'No session but manifest has followups - create session and spawn';
209
+ actionReason = epicId
210
+ ? `No session but epic ${epicId} has pending tasks - create session and spawn`
211
+ : 'No session but manifest has followups - create session and spawn';
191
212
  } else {
192
213
  recommendedAction = 'request_direction';
193
214
  actionReason = 'No session, no pending work - await user direction';
@@ -275,9 +296,10 @@ export async function analyzeDependencies(
275
296
  epicId: string,
276
297
  cwd?: string,
277
298
  ): Promise<DependencyAnalysis> {
278
- const taskPath = getTaskPath(cwd);
299
+ const acc = await getAccessor(cwd);
300
+ const epicTasks = await acc.getChildren(epicId);
279
301
 
280
- if (!existsSync(taskPath)) {
302
+ if (epicTasks.length === 0) {
281
303
  return {
282
304
  epicId,
283
305
  totalTasks: 0,
@@ -290,10 +312,9 @@ export async function analyzeDependencies(
290
312
  };
291
313
  }
292
314
 
293
- const data = JSON.parse(readFileSync(taskPath, 'utf-8'));
294
- const tasks: Task[] = data.tasks ?? [];
295
- const epicTasks = tasks.filter((t) => t.parentId === epicId);
296
- const doneIds = new Set(tasks.filter((t) => t.status === 'done').map((t) => t.id));
315
+ // Need full task set to resolve cross-epic dependency status
316
+ const { tasks: allTasks } = await acc.queryTasks({});
317
+ const doneIds = new Set(allTasks.filter((t) => t.status === 'done').map((t) => t.id));
297
318
  const epicIds = new Set(epicTasks.map((t) => t.id));
298
319
 
299
320
  // Compute waves iteratively
@@ -389,12 +410,10 @@ export async function getNextTask(
389
410
  return { task: null, readyCount: 0 };
390
411
  }
391
412
 
392
- // Load full task details for the first ready task
393
- const taskPath = getTaskPath(cwd);
394
- const data = JSON.parse(readFileSync(taskPath, 'utf-8'));
395
- const tasks: Task[] = data.tasks ?? [];
413
+ // Load full task details from SQLite
414
+ const acc = await getAccessor(cwd);
396
415
  const nextId = analysis.readyToSpawn[0].id;
397
- const task = tasks.find((t) => t.id === nextId) ?? null;
416
+ const task = await acc.loadSingleTask(nextId);
398
417
 
399
418
  return { task, readyCount: analysis.readyToSpawn.length };
400
419
  }
@@ -407,16 +426,13 @@ export async function getReadyTasks(epicId: string, cwd?: string): Promise<TaskR
407
426
  const analysis = await analyzeDependencies(epicId, cwd);
408
427
  const readyIds = new Set(analysis.readyToSpawn.map((t) => t.id));
409
428
 
410
- // Filter out tasks that depend on other ready tasks
411
- const taskPath = getTaskPath(cwd);
412
- if (!existsSync(taskPath)) return [];
413
-
414
- const data = JSON.parse(readFileSync(taskPath, 'utf-8'));
415
- const tasks: Task[] = data.tasks ?? [];
429
+ // Load full task details from SQLite to check inter-ready dependencies
430
+ const acc = await getAccessor(cwd);
431
+ const readyTasksFull = await acc.loadTasks([...readyIds]);
416
432
 
417
433
  return analysis.readyToSpawn
418
434
  .filter((ready) => {
419
- const task = tasks.find((t) => t.id === ready.id);
435
+ const task = readyTasksFull.find((t) => t.id === ready.id);
420
436
  if (!task) return false;
421
437
  const deps = task.depends ?? [];
422
438
  // Only include if no deps are also in the ready set
@@ -438,31 +454,29 @@ export async function generateHitlSummary(
438
454
  stopReason: string = 'context-limit',
439
455
  cwd?: string,
440
456
  ): Promise<HitlSummary> {
441
- const taskPath = getTaskPath(cwd);
442
- const cleoDirAbs = getCleoDirAbsolute(cwd);
457
+ const acc = await getAccessor(cwd);
443
458
 
444
- // Session info
459
+ // Session info from SQLite (ADR-006/ADR-020)
445
460
  let sessionId: string | null = null;
446
- const currentSessionFile = join(cleoDirAbs, '.current-session');
447
- if (existsSync(currentSessionFile)) {
448
- sessionId = readFileSync(currentSessionFile, 'utf-8').trim() || null;
461
+ try {
462
+ const activeSession = await acc.getActiveSession();
463
+ sessionId = activeSession?.id ?? null;
464
+ } catch {
465
+ // DB unavailable
449
466
  }
450
467
 
451
- // Focus info
468
+ // Focus info from SQLite meta KV
452
469
  let focusedTask: string | null = null;
453
470
  let progressNote: string | null = null;
454
- const focusPath = join(cleoDirAbs, 'focus.json');
455
- if (existsSync(focusPath)) {
456
- try {
457
- const focus = JSON.parse(readFileSync(focusPath, 'utf-8'));
458
- focusedTask = focus.focusedTaskId ?? null;
459
- progressNote = focus.sessionNote ?? null;
460
- } catch {
461
- // ignore
462
- }
471
+ try {
472
+ const focus = await acc.getMetaValue<TaskWorkState>('focus_state');
473
+ focusedTask = focus?.currentTask ?? null;
474
+ progressNote = focus?.sessionNote ?? null;
475
+ } catch {
476
+ // Focus unavailable
463
477
  }
464
478
 
465
- // Task statistics
479
+ // Task statistics from SQLite via DataAccessor
466
480
  let completed = 0,
467
481
  pending = 0,
468
482
  active = 0,
@@ -471,9 +485,8 @@ export async function generateHitlSummary(
471
485
  let remainingTasks: Array<TaskRef & { priority?: string }> = [];
472
486
  let readyToSpawn: TaskRefPriority[] = [];
473
487
 
474
- if (existsSync(taskPath) && epicId) {
475
- const data = JSON.parse(readFileSync(taskPath, 'utf-8'));
476
- const tasks: Task[] = (data.tasks ?? []).filter((t: Task) => t.parentId === epicId);
488
+ if (epicId) {
489
+ const tasks = await acc.getChildren(epicId);
477
490
 
478
491
  completed = tasks.filter((t) => t.status === 'done').length;
479
492
  pending = tasks.filter((t) => t.status === 'pending').length;
@@ -11,13 +11,12 @@
11
11
 
12
12
  import { existsSync, readFileSync } from 'node:fs';
13
13
  import { join } from 'node:path';
14
- import type { Task } from '@cleocode/contracts';
15
14
  import { MANIFEST_STATUSES } from '@cleocode/contracts';
16
15
  import {
17
16
  getAgentOutputsAbsolute,
18
17
  getManifestPath as getManifestPathFromPaths,
19
- getTaskPath,
20
18
  } from '../../paths.js';
19
+ import { getAccessor } from '../../store/data-accessor.js';
21
20
  import type { ComplianceResult, ManifestEntry, ManifestValidationResult } from '../types.js';
22
21
 
23
22
  // ============================================================================
@@ -310,14 +309,14 @@ export function verifyCompliance(
310
309
  * Validate orchestrator compliance (post-hoc behavioral checks).
311
310
  * @task T4519
312
311
  */
313
- export function validateOrchestratorCompliance(
312
+ export async function validateOrchestratorCompliance(
314
313
  epicId?: string,
315
314
  cwd?: string,
316
- ): {
315
+ ): Promise<{
317
316
  compliant: boolean;
318
317
  violations: string[];
319
318
  warnings: string[];
320
- } {
319
+ }> {
321
320
  const violations: string[] = [];
322
321
  const warnings: string[] = [];
323
322
 
@@ -333,36 +332,32 @@ export function validateOrchestratorCompliance(
333
332
  }
334
333
  }
335
334
 
336
- // Check dependency order (ORC-004) for completed tasks
335
+ // Check dependency order (ORC-004) for completed tasks via DataAccessor
337
336
  if (epicId) {
338
- const taskPath = getTaskPath(cwd);
339
- if (existsSync(taskPath)) {
340
- try {
341
- const data = JSON.parse(readFileSync(taskPath, 'utf-8'));
342
- const tasks: Task[] = (data.tasks ?? []).filter(
343
- (t: Task) => t.parentId === epicId && t.status === 'done',
344
- );
345
-
346
- // Sort by updatedAt and check for dependency violations
347
- const sorted = tasks.sort(
348
- (a, b) => new Date(a.updatedAt ?? '').getTime() - new Date(b.updatedAt ?? '').getTime(),
349
- );
350
- const completionOrder = sorted.map((t) => t.id);
351
-
352
- for (let i = 0; i < sorted.length; i++) {
353
- const deps = sorted[i].depends ?? [];
354
- for (const dep of deps) {
355
- const depIdx = completionOrder.indexOf(dep);
356
- if (depIdx >= 0 && depIdx >= i) {
357
- violations.push(
358
- `ORC-004_DEPENDENCY_ORDER: Task ${sorted[i].id} completed before dependency ${dep}`,
359
- );
360
- }
337
+ try {
338
+ const acc = await getAccessor(cwd);
339
+ const children = await acc.getChildren(epicId);
340
+ const tasks = children.filter((t) => t.status === 'done');
341
+
342
+ // Sort by updatedAt and check for dependency violations
343
+ const sorted = tasks.sort(
344
+ (a, b) => new Date(a.updatedAt ?? '').getTime() - new Date(b.updatedAt ?? '').getTime(),
345
+ );
346
+ const completionOrder = sorted.map((t) => t.id);
347
+
348
+ for (let i = 0; i < sorted.length; i++) {
349
+ const deps = sorted[i].depends ?? [];
350
+ for (const dep of deps) {
351
+ const depIdx = completionOrder.indexOf(dep);
352
+ if (depIdx >= 0 && depIdx >= i) {
353
+ violations.push(
354
+ `ORC-004_DEPENDENCY_ORDER: Task ${sorted[i].id} completed before dependency ${dep}`,
355
+ );
361
356
  }
362
357
  }
363
- } catch {
364
- // Skip on parse error
365
358
  }
359
+ } catch {
360
+ // Skip on DB error
366
361
  }
367
362
  }
368
363