@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
@@ -17,13 +17,13 @@
17
17
  * @epic T4914
18
18
  */
19
19
 
20
- import type { FileMeta, TaskWorkState } from '@cleocode/contracts';
20
+ import type { Task, TaskWorkState } from '@cleocode/contracts';
21
21
  import type { SessionMemoryContext } from '../memory/session-memory.js';
22
22
  import type { DataAccessor } from '../store/data-accessor.js';
23
23
  import { getAccessor } from '../store/data-accessor.js';
24
24
  import { depsReady } from '../tasks/deps-ready.js';
25
25
  import { getLastHandoff, type HandoffData } from './handoff.js';
26
- import type { TaskFileExt } from './types.js';
26
+ import type { TaskWorkStateExt } from './types.js';
27
27
 
28
28
  /**
29
29
  * Task summary for briefing output.
@@ -138,15 +138,9 @@ export async function computeBriefing(
138
138
  ): Promise<SessionBriefing> {
139
139
  const accessor = await getAccessor(projectRoot);
140
140
  const { tasks } = await accessor.queryTasks({});
141
- const focus = await accessor.getMetaValue<TaskWorkState>('focus_state');
142
- const fileMeta = await accessor.getMetaValue<FileMeta>('file_meta');
143
-
144
- // Build a TaskFileExt-compatible shape from targeted queries
145
- const current = {
146
- tasks,
147
- focus: focus ?? undefined,
148
- _meta: fileMeta ?? undefined,
149
- } as unknown as TaskFileExt;
141
+ const focus = (await accessor.getMetaValue<TaskWorkState>('focus_state')) as
142
+ | TaskWorkStateExt
143
+ | undefined;
150
144
 
151
145
  // Build task map for quick lookups
152
146
  const taskMap = new Map(tasks.map((t) => [t.id, t]));
@@ -155,19 +149,16 @@ export async function computeBriefing(
155
149
  const scopeFilter = await parseScope(options.scope, accessor);
156
150
 
157
151
  // Compute in-scope task IDs (undefined = all tasks in scope)
158
- const scopeTaskIds = getScopeTaskIdSet(
159
- scopeFilter,
160
- tasks as unknown as Array<{ id: string; parentId?: string; [key: string]: unknown }>,
161
- );
152
+ const scopeTaskIds = getScopeTaskIdSet(scopeFilter, tasks);
162
153
 
163
154
  // 1. Last session handoff
164
155
  const lastSession = await computeLastSession(projectRoot, scopeFilter);
165
156
 
166
157
  // 2. Current active task
167
- const currentTaskInfo = computeCurrentTask(current, taskMap);
158
+ const currentTaskInfo = computeCurrentTask(focus, taskMap);
168
159
 
169
160
  // 3. Next tasks (leverage-scored)
170
- const nextTasks = computeNextTasks(tasks, taskMap, current, {
161
+ const nextTasks = computeNextTasks(tasks, taskMap, focus, {
171
162
  maxTasks: options.maxNextTasks ?? 5,
172
163
  scopeTaskIds,
173
164
  });
@@ -191,7 +182,7 @@ export async function computeBriefing(
191
182
  });
192
183
 
193
184
  // 7. Pipeline stage (optional - may not be available)
194
- const pipelineStage = computePipelineStage(current);
185
+ const pipelineStage = await computePipelineStage(focus);
195
186
 
196
187
  // 8. Brain memory context (optional, best-effort)
197
188
  let memoryContext: SessionMemoryContext | undefined;
@@ -259,7 +250,7 @@ async function parseScope(
259
250
  */
260
251
  function getScopeTaskIdSet(
261
252
  scopeFilter: { type: 'global' | 'epic'; epicId?: string } | undefined,
262
- tasks: Array<{ id: string; parentId?: string; [key: string]: unknown }>,
253
+ tasks: Task[],
263
254
  ): Set<string> | undefined {
264
255
  if (!scopeFilter || scopeFilter.type === 'global') {
265
256
  return undefined; // All tasks in scope
@@ -325,10 +316,10 @@ async function computeLastSession(
325
316
  * Compute current active task from task file.
326
317
  */
327
318
  function computeCurrentTask(
328
- current: TaskFileExt,
319
+ focus: TaskWorkStateExt | undefined,
329
320
  taskMap: Map<string, unknown>,
330
321
  ): CurrentTaskInfo | null {
331
- const focusTaskId = current.focus?.currentTask;
322
+ const focusTaskId = focus?.currentTask;
332
323
  if (!focusTaskId) return null;
333
324
 
334
325
  const task = taskMap.get(focusTaskId) as
@@ -376,7 +367,7 @@ function calculateLeverage(taskId: string, taskMap: Map<string, unknown>): numbe
376
367
  function computeNextTasks(
377
368
  tasks: unknown[],
378
369
  taskMap: Map<string, unknown>,
379
- current: TaskFileExt,
370
+ focus: TaskWorkStateExt | undefined,
380
371
  options: { maxTasks: number; scopeTaskIds?: Set<string> },
381
372
  ): BriefingTask[] {
382
373
  const pendingTasks = tasks.filter((t) => {
@@ -387,7 +378,7 @@ function computeNextTasks(
387
378
  });
388
379
 
389
380
  const scored: BriefingTask[] = [];
390
- const currentPhase = current.focus?.currentPhase;
381
+ const currentPhase = focus?.currentPhase;
391
382
 
392
383
  for (const task of pendingTasks) {
393
384
  const t = task as {
@@ -593,28 +584,22 @@ function calculateEpicCompletion(epicId: string, taskMap: Map<string, unknown>):
593
584
  /**
594
585
  * Compute pipeline stage info from task file metadata.
595
586
  */
596
- function computePipelineStage(current: TaskFileExt): PipelineStageInfo | undefined {
597
- // Try to get from _meta or focus
598
- const stage = (current._meta as Record<string, unknown>)?.pipelineStage as string | undefined;
599
- const stageStatus = (current._meta as Record<string, unknown>)?.pipelineStageStatus as
600
- | string
601
- | undefined;
587
+ async function computePipelineStage(
588
+ focus: TaskWorkStateExt | undefined,
589
+ ): Promise<PipelineStageInfo | undefined> {
590
+ const taskId = focus?.currentTask;
591
+ if (!taskId) return undefined;
602
592
 
603
- if (stage) {
604
- return {
605
- currentStage: stage,
606
- stageStatus: stageStatus || 'active',
607
- };
608
- }
593
+ try {
594
+ const { getPipeline } = await import('../lifecycle/pipeline.js');
595
+ const pipeline = await getPipeline(taskId);
596
+ if (!pipeline) return undefined;
609
597
 
610
- // Try from lifecycle state if available
611
- const lifecycleState = current._meta?.lifecycleState as string | undefined;
612
- if (lifecycleState) {
613
598
  return {
614
- currentStage: lifecycleState,
615
- stageStatus: 'active',
599
+ currentStage: pipeline.currentStage,
600
+ stageStatus: pipeline.isActive ? 'active' : (pipeline.status ?? 'completed'),
616
601
  };
602
+ } catch {
603
+ return undefined;
617
604
  }
618
-
619
- return undefined;
620
605
  }
@@ -15,12 +15,11 @@
15
15
 
16
16
  import { execFile } from 'node:child_process';
17
17
  import { promisify } from 'node:util';
18
- import type { FileMeta, Session, TaskWorkState } from '@cleocode/contracts';
18
+ import type { Session, Task } from '@cleocode/contracts';
19
19
  import { ExitCode } from '@cleocode/contracts';
20
20
  import { CleoError } from '../errors.js';
21
21
  import { getAccessor } from '../store/data-accessor.js';
22
22
  import { getDecisionLog } from './decisions.js';
23
- import type { TaskFileExt } from './types.js';
24
23
 
25
24
  const execFileAsync = promisify(execFile);
26
25
 
@@ -77,15 +76,8 @@ export async function computeHandoff(
77
76
  throw new CleoError(ExitCode.SESSION_NOT_FOUND, `Session '${options.sessionId}' not found`);
78
77
  }
79
78
 
80
- // Load task data for scope analysis
79
+ // Load tasks directly from SQLite via DataAccessor
81
80
  const { tasks } = await accessor.queryTasks({});
82
- const focus = await accessor.getMetaValue<TaskWorkState>('focus_state');
83
- const fileMeta = await accessor.getMetaValue<FileMeta>('file_meta');
84
- const current = {
85
- tasks,
86
- focus: focus ?? undefined,
87
- _meta: fileMeta ?? undefined,
88
- } as unknown as TaskFileExt;
89
81
 
90
82
  // Get decisions recorded during this session
91
83
  const decisions = await getDecisionLog(projectRoot, { sessionId: options.sessionId });
@@ -97,9 +89,9 @@ export async function computeHandoff(
97
89
  tasksCompleted: session.tasksCompleted ?? [],
98
90
  tasksCreated: session.tasksCreated ?? [],
99
91
  decisionsRecorded: decisions.length,
100
- nextSuggested: computeNextSuggested(session, current),
101
- openBlockers: findOpenBlockers(current, session),
102
- openBugs: findOpenBugs(current, session),
92
+ nextSuggested: computeNextSuggested(session, tasks),
93
+ openBlockers: findOpenBlockers(tasks, session),
94
+ openBugs: findOpenBugs(tasks, session),
103
95
  };
104
96
 
105
97
  // Apply human overrides
@@ -117,20 +109,15 @@ export async function computeHandoff(
117
109
  * Compute top-3 next suggested tasks.
118
110
  * Prioritizes uncompleted tasks within the session scope.
119
111
  */
120
- function computeNextSuggested(session: Session, current: TaskFileExt): string[] {
121
- const suggestions: string[] = [];
122
-
123
- if (!current.tasks) return suggestions;
124
-
112
+ function computeNextSuggested(session: Session, tasks: Task[]): string[] {
125
113
  // Filter to tasks in scope
126
- const scopeTaskIds = getScopeTaskIds(session, current);
114
+ const scopeTaskIds = getScopeTaskIds(session, tasks);
127
115
 
128
116
  // Get uncompleted tasks in scope
129
- const pendingTasks = current.tasks.filter(
117
+ const pendingTasks = tasks.filter(
130
118
  (t) =>
131
119
  scopeTaskIds.has(t.id) &&
132
120
  t.status !== 'done' &&
133
- t.status !== 'completed' &&
134
121
  t.status !== 'archived' &&
135
122
  t.status !== 'cancelled',
136
123
  );
@@ -145,11 +132,9 @@ function computeNextSuggested(session: Session, current: TaskFileExt): string[]
145
132
 
146
133
  pendingTasks.sort((a, b) => {
147
134
  const priorityDiff =
148
- (priorityOrder[a.priority as string] ?? 99) - (priorityOrder[b.priority as string] ?? 99);
135
+ (priorityOrder[a.priority ?? 'medium'] ?? 99) - (priorityOrder[b.priority ?? 'medium'] ?? 99);
149
136
  if (priorityDiff !== 0) return priorityDiff;
150
- const aCreated = typeof a.createdAt === 'string' ? a.createdAt : '1970-01-01T00:00:00Z';
151
- const bCreated = typeof b.createdAt === 'string' ? b.createdAt : '1970-01-01T00:00:00Z';
152
- return new Date(aCreated).getTime() - new Date(bCreated).getTime();
137
+ return (a.createdAt ?? '').localeCompare(b.createdAt ?? '');
153
138
  });
154
139
 
155
140
  // Take top 3
@@ -159,87 +144,68 @@ function computeNextSuggested(session: Session, current: TaskFileExt): string[]
159
144
  /**
160
145
  * Find tasks with blockers in the session scope.
161
146
  */
162
- function findOpenBlockers(current: TaskFileExt, session: Session): string[] {
163
- const blockers: string[] = [];
164
-
165
- if (!current.tasks) return blockers;
166
-
167
- const scopeTaskIds = getScopeTaskIds(session, current);
147
+ function findOpenBlockers(tasks: Task[], session: Session): string[] {
148
+ const scopeTaskIds = getScopeTaskIds(session, tasks);
168
149
 
169
- // Find blocked tasks in scope
170
- const blockedTasks = current.tasks.filter(
171
- (t) => scopeTaskIds.has(t.id) && t.status === 'blocked',
172
- );
173
-
174
- return blockedTasks.map((t) => t.id);
150
+ return tasks.filter((t) => scopeTaskIds.has(t.id) && t.status === 'blocked').map((t) => t.id);
175
151
  }
176
152
 
177
153
  /**
178
154
  * Find open bugs in the session scope.
179
155
  */
180
- function findOpenBugs(current: TaskFileExt, session: Session): string[] {
181
- const bugs: string[] = [];
182
-
183
- if (!current.tasks) return bugs;
184
-
185
- const scopeTaskIds = getScopeTaskIds(session, current);
186
-
187
- // Find bug-type tasks that aren't closed
188
- const bugTasks = current.tasks.filter(
189
- (t) =>
190
- scopeTaskIds.has(t.id) &&
191
- (t.type === 'bug' ||
192
- (Array.isArray(t.labels) && t.labels.some((l: string) => l === 'bug'))) &&
193
- t.status !== 'done' &&
194
- t.status !== 'completed' &&
195
- t.status !== 'archived' &&
196
- t.status !== 'cancelled',
197
- );
198
-
199
- return bugTasks.map((t) => t.id);
156
+ function findOpenBugs(tasks: Task[], session: Session): string[] {
157
+ const scopeTaskIds = getScopeTaskIds(session, tasks);
158
+
159
+ return tasks
160
+ .filter(
161
+ (t) =>
162
+ scopeTaskIds.has(t.id) &&
163
+ (t.labels ?? []).includes('bug') &&
164
+ t.status !== 'done' &&
165
+ t.status !== 'archived' &&
166
+ t.status !== 'cancelled',
167
+ )
168
+ .map((t) => t.id);
200
169
  }
201
170
 
202
171
  /**
203
172
  * Get set of task IDs within the session scope.
204
173
  */
205
- function getScopeTaskIds(session: Session, current: TaskFileExt): Set<string> {
174
+ function getScopeTaskIds(session: Session, tasks: Task[]): Set<string> {
206
175
  const taskIds = new Set<string>();
207
176
 
208
- if (!current.tasks) return taskIds;
209
-
210
177
  if (session.scope.type === 'global') {
211
- // Global scope: all tasks
212
- for (const t of current.tasks) {
178
+ for (const t of tasks) {
213
179
  taskIds.add(t.id);
214
180
  }
215
- } else {
216
- // Epic/task scope: root task and descendants
217
- // Prefer rootTaskId (engine-layer), fall back to epicId (core-layer)
218
- const rootId = session.scope.rootTaskId ?? session.scope.epicId;
219
- if (!rootId) {
220
- // No root ID, fall back to global
221
- for (const t of current.tasks) {
222
- taskIds.add(t.id);
223
- }
224
- return taskIds;
181
+ return taskIds;
182
+ }
183
+
184
+ // Epic/task scope: root task and descendants
185
+ const rootId = session.scope.rootTaskId ?? session.scope.epicId;
186
+ if (!rootId) {
187
+ // No root ID, fall back to global
188
+ for (const t of tasks) {
189
+ taskIds.add(t.id);
225
190
  }
191
+ return taskIds;
192
+ }
226
193
 
227
- const addDescendants = (taskId: string) => {
228
- taskIds.add(taskId);
229
- current.tasks?.forEach((t) => {
230
- if (t.parentId === taskId) {
231
- addDescendants(t.id);
232
- }
233
- });
234
- };
194
+ const addDescendants = (taskId: string) => {
195
+ taskIds.add(taskId);
196
+ for (const t of tasks) {
197
+ if (t.parentId === taskId) {
198
+ addDescendants(t.id);
199
+ }
200
+ }
201
+ };
235
202
 
236
- addDescendants(rootId);
203
+ addDescendants(rootId);
237
204
 
238
- // Include explicitTaskIds if present in scope
239
- if (session.scope.explicitTaskIds) {
240
- for (const id of session.scope.explicitTaskIds) {
241
- taskIds.add(id);
242
- }
205
+ // Include explicitTaskIds if present in scope
206
+ if (session.scope.explicitTaskIds) {
207
+ for (const id of session.scope.explicitTaskIds) {
208
+ taskIds.add(id);
243
209
  }
244
210
  }
245
211
 
@@ -471,4 +471,8 @@ export { getSessionStats } from './session-stats.js';
471
471
  export { suspendSession } from './session-suspend.js';
472
472
  export { switchSession } from './session-switch.js';
473
473
  export { SessionView } from './session-view.js';
474
- export type { AssumptionRecord, DecisionRecord, TaskFileExt, TaskWorkStateExt } from './types.js';
474
+ export type {
475
+ AssumptionRecord,
476
+ DecisionRecord,
477
+ TaskWorkStateExt,
478
+ } from './types.js';
@@ -5,6 +5,8 @@
5
5
  * @epic T4654
6
6
  */
7
7
 
8
+ import type { SessionNote, TaskWorkState } from '@cleocode/contracts';
9
+
8
10
  /**
9
11
  * Session object (engine-compatible).
10
12
  */
@@ -67,45 +69,22 @@ export interface SessionRecord {
67
69
 
68
70
  /**
69
71
  * Task work state from the task store.
72
+ *
73
+ * Extends the strict contracts {@link TaskWorkState} with required-null
74
+ * fields for session engine compatibility. The engine layer always expects
75
+ * these fields to be present (even if null), whereas the contracts type
76
+ * marks them as optional.
70
77
  */
71
- export interface TaskWorkStateExt {
78
+ export interface TaskWorkStateExt extends TaskWorkState {
72
79
  currentTask: string | null;
73
80
  currentPhase: string | null;
74
81
  blockedUntil: string | null;
75
82
  sessionNote: string | null;
76
- sessionNotes: unknown[];
83
+ sessionNotes: SessionNote[];
77
84
  nextAction: string | null;
78
85
  primarySession: string | null;
79
86
  }
80
87
 
81
- /**
82
- * Task file structure (subset for session operations).
83
- */
84
- export interface TaskFileExt {
85
- focus?:
86
- | TaskWorkStateExt
87
- | { currentTask?: string | null; currentPhase?: string | null; [key: string]: unknown };
88
- _meta?: {
89
- schemaVersion: string;
90
- checksum?: string;
91
- configVersion?: string;
92
- lastSessionId?: string | null;
93
- activeSessionCount?: number;
94
- sessionsFile?: string | null;
95
- generation?: number;
96
- [key: string]: unknown;
97
- };
98
- tasks?: Array<{
99
- id: string;
100
- status: string;
101
- parentId?: string;
102
- completedAt?: string;
103
- [key: string]: unknown;
104
- }>;
105
- lastUpdated?: string;
106
- [key: string]: unknown;
107
- }
108
-
109
88
  /**
110
89
  * Decision record stored in decisions.jsonl.
111
90
  */
@@ -131,16 +110,3 @@ export interface AssumptionRecord {
131
110
  validatedAt: string | null;
132
111
  timestamp: string;
133
112
  }
134
-
135
- /**
136
- * Convert a TaskFile (from contracts) to the looser TaskFileExt shape.
137
- * Accepts any object with at least the basic TaskFileExt structure.
138
- * The runtime object is the same reference — this only changes the TS type.
139
- */
140
- export function toTaskFileExt<
141
- T extends { _meta?: object; tasks?: unknown[]; focus?: object; lastUpdated?: string },
142
- >(taskFile: T): TaskFileExt {
143
- // The incoming object structurally satisfies TaskFileExt at runtime;
144
- // this conversion bridges the strict contracts type to the loose session type.
145
- return taskFile as TaskFileExt;
146
- }
@@ -104,8 +104,11 @@ export class SignalDockTransport implements AgentTransport {
104
104
  };
105
105
  }
106
106
 
107
- async poll(agentId: string, _since?: string): Promise<Message[]> {
108
- const messages = await this.request<Message[]>('GET', '/messages/poll/new', undefined, agentId);
107
+ async poll(agentId: string, since?: string): Promise<Message[]> {
108
+ const path = since
109
+ ? `/messages/poll/new?since=${encodeURIComponent(since)}`
110
+ : '/messages/poll/new';
111
+ const messages = await this.request<Message[]>('GET', path, undefined, agentId);
109
112
  return messages;
110
113
  }
111
114
 
@@ -19,10 +19,9 @@
19
19
 
20
20
  import { existsSync, readFileSync } from 'node:fs';
21
21
  import { join } from 'node:path';
22
- import type { Task } from '@cleocode/contracts';
23
22
  import { ExitCode } from '@cleocode/contracts';
24
23
  import { CleoError } from '../../errors.js';
25
- import { getProjectRoot, getTaskPath } from '../../paths.js';
24
+ import { getProjectRoot } from '../../paths.js';
26
25
  import { findSkill } from '../discovery.js';
27
26
  import { injectTokens, type TokenValues } from './token.js';
28
27
 
@@ -55,15 +54,10 @@ export function loadProtocolBase(cwd?: string): string | null {
55
54
  * Build task context block for injection into a subagent prompt.
56
55
  * @task T4521
57
56
  */
58
- export function buildTaskContext(taskId: string, cwd?: string): string {
59
- const taskPath = getTaskPath(cwd);
60
- if (!existsSync(taskPath)) {
61
- return `## Task Context\n\n**Task**: ${taskId}\n**Status**: unknown\n`;
62
- }
63
-
64
- const data = JSON.parse(readFileSync(taskPath, 'utf-8'));
65
- const tasks: Task[] = data.tasks ?? [];
66
- const task = tasks.find((t) => t.id === taskId);
57
+ export async function buildTaskContext(taskId: string, cwd?: string): Promise<string> {
58
+ const { getAccessor } = await import('../../store/data-accessor.js');
59
+ const acc = await getAccessor(cwd);
60
+ const task = await acc.loadSingleTask(taskId);
67
61
 
68
62
  if (!task) {
69
63
  return `## Task Context\n\n**Task**: ${taskId}\n**Status**: not found\n`;
@@ -165,15 +159,15 @@ export function filterProtocolByTier(content: string, tier: 0 | 1 | 2): string {
165
159
  * Composes: skill content + protocol base + task context.
166
160
  * @task T4521
167
161
  */
168
- export function injectProtocol(
162
+ export async function injectProtocol(
169
163
  skillContent: string,
170
164
  taskId: string,
171
165
  tokenValues: TokenValues,
172
166
  cwd?: string,
173
167
  tier?: 0 | 1 | 2,
174
- ): string {
168
+ ): Promise<string> {
175
169
  const protocolBase = loadProtocolBase(cwd);
176
- const taskContext = buildTaskContext(taskId, cwd);
170
+ const taskContext = await buildTaskContext(taskId, cwd);
177
171
 
178
172
  // Inject tokens into skill content
179
173
  const resolvedSkill = injectTokens(skillContent, tokenValues);
@@ -202,13 +196,13 @@ export function injectProtocol(
202
196
  * High-level function that loads the skill, injects protocol, and returns the prompt.
203
197
  * @task T4521
204
198
  */
205
- export function orchestratorSpawnSkill(
199
+ export async function orchestratorSpawnSkill(
206
200
  taskId: string,
207
201
  skillName: string,
208
202
  tokenValues: TokenValues,
209
203
  cwd?: string,
210
204
  tier?: 0 | 1 | 2,
211
- ): string {
205
+ ): Promise<string> {
212
206
  // Find the skill
213
207
  const skill = findSkill(skillName, cwd);
214
208
  if (!skill || !skill.content) {
@@ -12,8 +12,7 @@
12
12
 
13
13
  import { randomBytes } from 'node:crypto';
14
14
  import { existsSync, readFileSync } from 'node:fs';
15
- import type { Task } from '@cleocode/contracts';
16
- import { getTaskPath } from '../../paths.js';
15
+ import { getAccessor } from '../../store/data-accessor.js';
17
16
  import type { ManifestEntry } from '../types.js';
18
17
 
19
18
  // ============================================================================
@@ -65,20 +64,13 @@ export function generateContributionId(taskId: string): string {
65
64
  * Validate that a task is suitable for contribution protocol.
66
65
  * @task T4520
67
66
  */
68
- export function validateContributionTask(
67
+ export async function validateContributionTask(
69
68
  taskId: string,
70
69
  cwd?: string,
71
- ): { valid: boolean; issues: string[] } {
70
+ ): Promise<{ valid: boolean; issues: string[] }> {
72
71
  const issues: string[] = [];
73
- const taskPath = getTaskPath(cwd);
74
-
75
- if (!existsSync(taskPath)) {
76
- return { valid: false, issues: ['Todo file not found'] };
77
- }
78
-
79
- const data = JSON.parse(readFileSync(taskPath, 'utf-8'));
80
- const tasks: Task[] = data.tasks ?? [];
81
- const task = tasks.find((t) => t.id === taskId);
72
+ const acc = await getAccessor(cwd);
73
+ const task = await acc.loadSingleTask(taskId);
82
74
 
83
75
  if (!task) {
84
76
  return { valid: false, issues: [`Task ${taskId} not found`] };