@cleocode/core 2026.3.39 → 2026.3.41
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.
- package/dist/index.js +670 -655
- package/dist/index.js.map +4 -4
- package/package.json +2 -2
- package/src/admin/export.ts +2 -17
- package/src/cleo.ts +16 -8
- package/src/inject/index.ts +7 -7
- package/src/internal.ts +14 -23
- package/src/reconciliation/index.ts +11 -2
- package/src/reconciliation/link-store.ts +174 -0
- package/src/reconciliation/reconciliation-engine.ts +136 -83
- package/src/release/release-manifest.ts +55 -1
- package/src/routing/capability-matrix.ts +4 -5
- package/src/store/tasks-schema.ts +52 -0
- package/src/task-work/index.ts +0 -14
- package/src/admin/sync.ts +0 -164
- package/src/reconciliation/sync-state.ts +0 -73
- package/src/task-work/todowrite-merge.ts +0 -289
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cleocode/core",
|
|
3
|
-
"version": "2026.3.
|
|
3
|
+
"version": "2026.3.41",
|
|
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",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"write-file-atomic": "^6.0.0",
|
|
37
37
|
"yaml": "^2.8.2",
|
|
38
38
|
"zod": "^3.25.76",
|
|
39
|
-
"@cleocode/contracts": "2026.3.
|
|
39
|
+
"@cleocode/contracts": "2026.3.41"
|
|
40
40
|
},
|
|
41
41
|
"engines": {
|
|
42
42
|
"node": ">=24.0.0"
|
package/src/admin/export.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { writeFile } from 'node:fs/promises';
|
|
|
10
10
|
import type { Task } from '@cleocode/contracts';
|
|
11
11
|
import { getAccessor } from '../store/data-accessor.js';
|
|
12
12
|
|
|
13
|
-
export type ExportFormat = 'json' | 'csv' | 'tsv' | 'markdown'
|
|
13
|
+
export type ExportFormat = 'json' | 'csv' | 'tsv' | 'markdown';
|
|
14
14
|
|
|
15
15
|
function taskToCsvRow(task: Task, delimiter: string): string {
|
|
16
16
|
const escapeField = (val: string) => {
|
|
@@ -38,16 +38,6 @@ function taskToMarkdown(task: Task): string {
|
|
|
38
38
|
return `- [${status}] **${task.id}** ${priority} ${task.title}`;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
function taskToTodoWrite(task: Task): Record<string, unknown> {
|
|
42
|
-
return {
|
|
43
|
-
id: task.id,
|
|
44
|
-
content: task.title,
|
|
45
|
-
status:
|
|
46
|
-
task.status === 'done' ? 'completed' : task.status === 'active' ? 'in_progress' : 'pending',
|
|
47
|
-
priority: task.priority === 'critical' ? 'high' : task.priority,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
41
|
export interface ExportParams {
|
|
52
42
|
format?: ExportFormat;
|
|
53
43
|
output?: string;
|
|
@@ -132,13 +122,8 @@ export async function exportTasks(params: ExportParams): Promise<ExportResult> {
|
|
|
132
122
|
content = lines.join('\n');
|
|
133
123
|
break;
|
|
134
124
|
}
|
|
135
|
-
case 'todowrite': {
|
|
136
|
-
const items = tasks.map(taskToTodoWrite);
|
|
137
|
-
content = JSON.stringify(items, null, 2);
|
|
138
|
-
break;
|
|
139
|
-
}
|
|
140
125
|
default:
|
|
141
|
-
throw new Error(`Unknown format: ${format}. Valid: json, csv, tsv, markdown
|
|
126
|
+
throw new Error(`Unknown format: ${format}. Valid: json, csv, tsv, markdown`);
|
|
142
127
|
}
|
|
143
128
|
|
|
144
129
|
if (params.output) {
|
package/src/cleo.ts
CHANGED
|
@@ -18,9 +18,9 @@ import path from 'node:path';
|
|
|
18
18
|
import type {
|
|
19
19
|
DataAccessor,
|
|
20
20
|
ExternalTask,
|
|
21
|
+
ExternalTaskLink,
|
|
21
22
|
ReconcileOptions,
|
|
22
23
|
ReconcileResult,
|
|
23
|
-
SyncSessionState,
|
|
24
24
|
Task,
|
|
25
25
|
TaskPriority,
|
|
26
26
|
TaskSize,
|
|
@@ -80,7 +80,11 @@ import {
|
|
|
80
80
|
} from './orchestration/index.js';
|
|
81
81
|
// Reconciliation (sync)
|
|
82
82
|
import { reconcile } from './reconciliation/index.js';
|
|
83
|
-
import {
|
|
83
|
+
import {
|
|
84
|
+
getLinksByProvider,
|
|
85
|
+
getLinksByTaskId,
|
|
86
|
+
removeLinksByProvider,
|
|
87
|
+
} from './reconciliation/link-store.js';
|
|
84
88
|
|
|
85
89
|
// Release
|
|
86
90
|
import {
|
|
@@ -303,6 +307,7 @@ export interface NexusAPI {
|
|
|
303
307
|
}
|
|
304
308
|
|
|
305
309
|
export interface SyncAPI {
|
|
310
|
+
/** Reconcile external tasks with CLEO as SSoT. */
|
|
306
311
|
reconcile(params: {
|
|
307
312
|
externalTasks: ExternalTask[];
|
|
308
313
|
providerId: string;
|
|
@@ -311,9 +316,12 @@ export interface SyncAPI {
|
|
|
311
316
|
defaultPhase?: string;
|
|
312
317
|
defaultLabels?: string[];
|
|
313
318
|
}): Promise<ReconcileResult>;
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
319
|
+
/** Get all external task links for a provider. */
|
|
320
|
+
getLinks(providerId: string): Promise<ExternalTaskLink[]>;
|
|
321
|
+
/** Get all external task links for a CLEO task. */
|
|
322
|
+
getTaskLinks(taskId: string): Promise<ExternalTaskLink[]>;
|
|
323
|
+
/** Remove all external task links for a provider. */
|
|
324
|
+
removeProviderLinks(providerId: string): Promise<number>;
|
|
317
325
|
}
|
|
318
326
|
|
|
319
327
|
// ============================================================================
|
|
@@ -580,9 +588,9 @@ export class Cleo {
|
|
|
580
588
|
},
|
|
581
589
|
store,
|
|
582
590
|
),
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
591
|
+
getLinks: (providerId) => getLinksByProvider(providerId, root),
|
|
592
|
+
getTaskLinks: (taskId) => getLinksByTaskId(taskId, root),
|
|
593
|
+
removeProviderLinks: (providerId) => removeLinksByProvider(providerId, root),
|
|
586
594
|
};
|
|
587
595
|
}
|
|
588
596
|
}
|
package/src/inject/index.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Task injection core module.
|
|
3
|
+
*
|
|
4
|
+
* Selects and formats tasks for injection into external systems.
|
|
3
5
|
*
|
|
4
6
|
* ARCHITECTURE NOTE: Instruction injection is a CAAMP domain responsibility.
|
|
5
7
|
* Once @cleocode/caamp is available as a dependency, the injection formatting
|
|
@@ -15,8 +17,6 @@ import type { Task, TaskFile } from '@cleocode/contracts';
|
|
|
15
17
|
import type { DataAccessor } from '../store/data-accessor.js';
|
|
16
18
|
import { getAccessor } from '../store/data-accessor.js';
|
|
17
19
|
|
|
18
|
-
// CLEO-native injection (CAAMP InstructionInjector not yet available as separate package)
|
|
19
|
-
|
|
20
20
|
/**
|
|
21
21
|
* Select tasks eligible for injection based on filters.
|
|
22
22
|
* This is CLEO-specific task selection logic (stays in CLEO).
|
|
@@ -59,11 +59,11 @@ function selectTasksForInjection(
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
|
-
* Format tasks for
|
|
62
|
+
* Format tasks for injection.
|
|
63
63
|
* Format: [T###] [!]? [BLOCKED]? <title>
|
|
64
64
|
* @task T4539
|
|
65
65
|
*/
|
|
66
|
-
function
|
|
66
|
+
function formatForInjection(tasks: Task[]): Array<{ id: string; text: string; status: string }> {
|
|
67
67
|
return tasks.map((t) => {
|
|
68
68
|
let prefix = `[${t.id}]`;
|
|
69
69
|
if (t.priority === 'critical' || t.priority === 'high') prefix += ' [!]';
|
|
@@ -72,7 +72,7 @@ function formatForTodoWrite(tasks: Task[]): Array<{ id: string; text: string; st
|
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
/** Inject tasks
|
|
75
|
+
/** Inject tasks for external consumption. */
|
|
76
76
|
export async function injectTasks(
|
|
77
77
|
opts: {
|
|
78
78
|
maxTasks?: number;
|
|
@@ -97,7 +97,7 @@ export async function injectTasks(
|
|
|
97
97
|
} as TaskFile;
|
|
98
98
|
|
|
99
99
|
const selectedTasks = selectTasksForInjection(data, opts);
|
|
100
|
-
const formatted =
|
|
100
|
+
const formatted = formatForInjection(selectedTasks);
|
|
101
101
|
|
|
102
102
|
const phase = opts.phase ?? projectMeta?.currentPhase ?? null;
|
|
103
103
|
|
package/src/internal.ts
CHANGED
|
@@ -28,12 +28,6 @@ export { exportTasksPackage } from './admin/export-tasks.js';
|
|
|
28
28
|
export { computeHelp } from './admin/help.js';
|
|
29
29
|
export { importTasks } from './admin/import.js';
|
|
30
30
|
export { importTasksPackage } from './admin/import-tasks.js';
|
|
31
|
-
// Alias for cleo compatibility (admin sync status)
|
|
32
|
-
export {
|
|
33
|
-
clearSyncState,
|
|
34
|
-
getSyncStatus,
|
|
35
|
-
getSyncStatus as getAdminSyncStatus,
|
|
36
|
-
} from './admin/sync.js';
|
|
37
31
|
// ADRs
|
|
38
32
|
export { findAdrs } from './adrs/find.js';
|
|
39
33
|
export { listAdrs, showAdr, syncAdrsToDb, validateAllAdrs } from './adrs/index.js';
|
|
@@ -206,10 +200,13 @@ export { listPhases, showPhase } from './pipeline/index.js';
|
|
|
206
200
|
export { getNodeUpgradeInstructions, getNodeVersionInfo } from './platform.js';
|
|
207
201
|
// Reconciliation (additional)
|
|
208
202
|
export {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
203
|
+
createLink,
|
|
204
|
+
getLinkByExternalId,
|
|
205
|
+
getLinksByProvider,
|
|
206
|
+
getLinksByTaskId,
|
|
207
|
+
removeLinksByProvider,
|
|
208
|
+
touchLink,
|
|
209
|
+
} from './reconciliation/link-store.js';
|
|
213
210
|
// Release
|
|
214
211
|
export { channelToDistTag, describeChannel, resolveChannelFromBranch } from './release/channel.js';
|
|
215
212
|
export type { PRResult } from './release/github-pr.js';
|
|
@@ -338,7 +335,13 @@ export { computeChecksum, readJson } from './store/json.js';
|
|
|
338
335
|
export { createSession, getActiveSession } from './store/session-store.js';
|
|
339
336
|
export { getDb, getNativeDb } from './store/sqlite.js';
|
|
340
337
|
export { createTask } from './store/task-store.js';
|
|
341
|
-
export {
|
|
338
|
+
export {
|
|
339
|
+
auditLog,
|
|
340
|
+
externalTaskLinks,
|
|
341
|
+
releaseManifests,
|
|
342
|
+
taskDependencies,
|
|
343
|
+
tasks,
|
|
344
|
+
} from './store/tasks-schema.js';
|
|
342
345
|
export { AuditLogInsertSchema } from './store/validation-schemas.js';
|
|
343
346
|
export type {
|
|
344
347
|
AnalyzeArchiveOptions,
|
|
@@ -384,18 +387,6 @@ export {
|
|
|
384
387
|
// Task work (additional)
|
|
385
388
|
export type { TaskWorkHistoryEntry } from './task-work/index.js';
|
|
386
389
|
export { getTaskHistory } from './task-work/index.js';
|
|
387
|
-
export type {
|
|
388
|
-
ChangeSet as TodoWriteChangeSet,
|
|
389
|
-
SyncSessionState,
|
|
390
|
-
TodoWriteItem,
|
|
391
|
-
TodoWriteMergeOptions,
|
|
392
|
-
TodoWriteMergeResult,
|
|
393
|
-
TodoWriteState,
|
|
394
|
-
} from './task-work/todowrite-merge.js';
|
|
395
|
-
export {
|
|
396
|
-
analyzeChanges as analyzeTodoWriteChanges,
|
|
397
|
-
mergeTodoWriteState,
|
|
398
|
-
} from './task-work/todowrite-merge.js';
|
|
399
390
|
// Tasks (additional)
|
|
400
391
|
export { validateLabels } from './tasks/add.js';
|
|
401
392
|
export { getCriticalPath } from './tasks/graph-ops.js';
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Provider-agnostic task reconciliation module.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Provides the reconciliation engine for syncing external task systems
|
|
5
|
+
* (Linear, Jira, GitHub Issues, etc.) with CLEO as SSoT, plus DB-backed
|
|
6
|
+
* link tracking via the external_task_links table.
|
|
5
7
|
*/
|
|
6
8
|
|
|
9
|
+
export {
|
|
10
|
+
createLink,
|
|
11
|
+
getLinkByExternalId,
|
|
12
|
+
getLinksByProvider,
|
|
13
|
+
getLinksByTaskId,
|
|
14
|
+
removeLinksByProvider,
|
|
15
|
+
touchLink,
|
|
16
|
+
} from './link-store.js';
|
|
7
17
|
export { reconcile } from './reconciliation-engine.js';
|
|
8
|
-
export { clearSyncState, readSyncState, writeSyncState } from './sync-state.js';
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External task link persistence — DB-backed link tracking for reconciliation.
|
|
3
|
+
*
|
|
4
|
+
* Manages the external_task_links table in tasks.db. Used by the reconciliation
|
|
5
|
+
* engine to match external tasks to existing CLEO tasks and track sync history.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { randomUUID } from 'node:crypto';
|
|
9
|
+
import type { ExternalTaskLink } from '@cleocode/contracts';
|
|
10
|
+
import { and, eq } from 'drizzle-orm';
|
|
11
|
+
import { getDb } from '../store/sqlite.js';
|
|
12
|
+
import { externalTaskLinks } from '../store/tasks-schema.js';
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Read operations
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Find all links for a given provider.
|
|
20
|
+
*/
|
|
21
|
+
export async function getLinksByProvider(
|
|
22
|
+
providerId: string,
|
|
23
|
+
cwd?: string,
|
|
24
|
+
): Promise<ExternalTaskLink[]> {
|
|
25
|
+
const db = await getDb(cwd);
|
|
26
|
+
const rows = await db
|
|
27
|
+
.select()
|
|
28
|
+
.from(externalTaskLinks)
|
|
29
|
+
.where(eq(externalTaskLinks.providerId, providerId));
|
|
30
|
+
return rows.map(rowToLink);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Find a link by provider + external ID.
|
|
35
|
+
*/
|
|
36
|
+
export async function getLinkByExternalId(
|
|
37
|
+
providerId: string,
|
|
38
|
+
externalId: string,
|
|
39
|
+
cwd?: string,
|
|
40
|
+
): Promise<ExternalTaskLink | null> {
|
|
41
|
+
const db = await getDb(cwd);
|
|
42
|
+
const rows = await db
|
|
43
|
+
.select()
|
|
44
|
+
.from(externalTaskLinks)
|
|
45
|
+
.where(
|
|
46
|
+
and(
|
|
47
|
+
eq(externalTaskLinks.providerId, providerId),
|
|
48
|
+
eq(externalTaskLinks.externalId, externalId),
|
|
49
|
+
),
|
|
50
|
+
);
|
|
51
|
+
return rows.length > 0 ? rowToLink(rows[0]!) : null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Find all links for a given CLEO task.
|
|
56
|
+
*/
|
|
57
|
+
export async function getLinksByTaskId(taskId: string, cwd?: string): Promise<ExternalTaskLink[]> {
|
|
58
|
+
const db = await getDb(cwd);
|
|
59
|
+
const rows = await db
|
|
60
|
+
.select()
|
|
61
|
+
.from(externalTaskLinks)
|
|
62
|
+
.where(eq(externalTaskLinks.taskId, taskId));
|
|
63
|
+
return rows.map(rowToLink);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Write operations
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Create a new external task link.
|
|
72
|
+
*/
|
|
73
|
+
export async function createLink(
|
|
74
|
+
params: {
|
|
75
|
+
taskId: string;
|
|
76
|
+
providerId: string;
|
|
77
|
+
externalId: string;
|
|
78
|
+
externalUrl?: string;
|
|
79
|
+
externalTitle?: string;
|
|
80
|
+
linkType: ExternalTaskLink['linkType'];
|
|
81
|
+
syncDirection?: ExternalTaskLink['syncDirection'];
|
|
82
|
+
metadata?: Record<string, unknown>;
|
|
83
|
+
},
|
|
84
|
+
cwd?: string,
|
|
85
|
+
): Promise<ExternalTaskLink> {
|
|
86
|
+
const db = await getDb(cwd);
|
|
87
|
+
const now = new Date().toISOString();
|
|
88
|
+
const id = randomUUID();
|
|
89
|
+
|
|
90
|
+
await db.insert(externalTaskLinks).values({
|
|
91
|
+
id,
|
|
92
|
+
taskId: params.taskId,
|
|
93
|
+
providerId: params.providerId,
|
|
94
|
+
externalId: params.externalId,
|
|
95
|
+
externalUrl: params.externalUrl ?? null,
|
|
96
|
+
externalTitle: params.externalTitle ?? null,
|
|
97
|
+
linkType: params.linkType,
|
|
98
|
+
syncDirection: params.syncDirection ?? 'inbound',
|
|
99
|
+
metadataJson: params.metadata ? JSON.stringify(params.metadata) : '{}',
|
|
100
|
+
linkedAt: now,
|
|
101
|
+
lastSyncAt: now,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
id,
|
|
106
|
+
taskId: params.taskId,
|
|
107
|
+
providerId: params.providerId,
|
|
108
|
+
externalId: params.externalId,
|
|
109
|
+
externalUrl: params.externalUrl ?? null,
|
|
110
|
+
externalTitle: params.externalTitle ?? null,
|
|
111
|
+
linkType: params.linkType,
|
|
112
|
+
syncDirection: params.syncDirection ?? 'inbound',
|
|
113
|
+
metadata: params.metadata,
|
|
114
|
+
linkedAt: now,
|
|
115
|
+
lastSyncAt: now,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Update the lastSyncAt and optionally the title/metadata for an existing link.
|
|
121
|
+
*/
|
|
122
|
+
export async function touchLink(
|
|
123
|
+
linkId: string,
|
|
124
|
+
updates?: {
|
|
125
|
+
externalTitle?: string;
|
|
126
|
+
metadata?: Record<string, unknown>;
|
|
127
|
+
},
|
|
128
|
+
cwd?: string,
|
|
129
|
+
): Promise<void> {
|
|
130
|
+
const db = await getDb(cwd);
|
|
131
|
+
const now = new Date().toISOString();
|
|
132
|
+
const values: Record<string, unknown> = { lastSyncAt: now };
|
|
133
|
+
if (updates?.externalTitle !== undefined) {
|
|
134
|
+
values.externalTitle = updates.externalTitle;
|
|
135
|
+
}
|
|
136
|
+
if (updates?.metadata !== undefined) {
|
|
137
|
+
values.metadataJson = JSON.stringify(updates.metadata);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
await db.update(externalTaskLinks).set(values).where(eq(externalTaskLinks.id, linkId));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Remove all links for a provider (used during provider deregistration).
|
|
145
|
+
*/
|
|
146
|
+
export async function removeLinksByProvider(providerId: string, cwd?: string): Promise<number> {
|
|
147
|
+
const db = await getDb(cwd);
|
|
148
|
+
const result = await db
|
|
149
|
+
.delete(externalTaskLinks)
|
|
150
|
+
.where(eq(externalTaskLinks.providerId, providerId));
|
|
151
|
+
return Number(result.changes);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
// Helpers
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
function rowToLink(row: typeof externalTaskLinks.$inferSelect): ExternalTaskLink {
|
|
159
|
+
return {
|
|
160
|
+
id: row.id,
|
|
161
|
+
taskId: row.taskId,
|
|
162
|
+
providerId: row.providerId,
|
|
163
|
+
externalId: row.externalId,
|
|
164
|
+
externalUrl: row.externalUrl,
|
|
165
|
+
externalTitle: row.externalTitle,
|
|
166
|
+
linkType: row.linkType as ExternalTaskLink['linkType'],
|
|
167
|
+
syncDirection: row.syncDirection as ExternalTaskLink['syncDirection'],
|
|
168
|
+
metadata: row.metadataJson
|
|
169
|
+
? (JSON.parse(row.metadataJson) as Record<string, unknown>)
|
|
170
|
+
: undefined,
|
|
171
|
+
linkedAt: row.linkedAt,
|
|
172
|
+
lastSyncAt: row.lastSyncAt,
|
|
173
|
+
};
|
|
174
|
+
}
|