@cleocode/core 2026.3.44 → 2026.3.46

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 (166) hide show
  1. package/dist/admin/import-tasks.d.ts +10 -2
  2. package/dist/admin/import-tasks.d.ts.map +1 -1
  3. package/dist/bootstrap.d.ts.map +1 -1
  4. package/dist/index.js +1890 -504
  5. package/dist/index.js.map +4 -4
  6. package/dist/init.d.ts.map +1 -1
  7. package/dist/injection.d.ts +1 -1
  8. package/dist/injection.d.ts.map +1 -1
  9. package/dist/internal.d.ts +3 -1
  10. package/dist/internal.d.ts.map +1 -1
  11. package/dist/nexus/index.d.ts +2 -0
  12. package/dist/nexus/index.d.ts.map +1 -1
  13. package/dist/nexus/transfer-types.d.ts +123 -0
  14. package/dist/nexus/transfer-types.d.ts.map +1 -0
  15. package/dist/nexus/transfer.d.ts +31 -0
  16. package/dist/nexus/transfer.d.ts.map +1 -0
  17. package/dist/routing/capability-matrix.d.ts +6 -4
  18. package/dist/routing/capability-matrix.d.ts.map +1 -1
  19. package/dist/scaffold.d.ts +16 -9
  20. package/dist/scaffold.d.ts.map +1 -1
  21. package/dist/skills/agents/install.d.ts.map +1 -1
  22. package/dist/skills/routing-table.d.ts +17 -16
  23. package/dist/skills/routing-table.d.ts.map +1 -1
  24. package/dist/skills/skill-paths.d.ts.map +1 -1
  25. package/dist/store/brain-sqlite.d.ts +4 -1
  26. package/dist/store/brain-sqlite.d.ts.map +1 -1
  27. package/dist/store/nexus-sqlite.d.ts +4 -1
  28. package/dist/store/nexus-sqlite.d.ts.map +1 -1
  29. package/dist/store/sqlite.d.ts +4 -1
  30. package/dist/store/sqlite.d.ts.map +1 -1
  31. package/dist/store/tasks-schema.d.ts +3 -3
  32. package/dist/store/tasks-schema.d.ts.map +1 -1
  33. package/dist/store/validation-schemas.d.ts +5 -4
  34. package/dist/store/validation-schemas.d.ts.map +1 -1
  35. package/dist/system/health.d.ts.map +1 -1
  36. package/dist/ui/index.d.ts +0 -1
  37. package/dist/ui/index.d.ts.map +1 -1
  38. package/package.json +9 -4
  39. package/schemas/adr-frontmatter.schema.json +72 -0
  40. package/schemas/agent-configs.schema.json +120 -0
  41. package/schemas/agent-registry.json +243 -0
  42. package/schemas/agent-registry.schema.json +132 -0
  43. package/schemas/archive/research-manifest.schema.json +257 -0
  44. package/schemas/archive.schema.json +450 -0
  45. package/schemas/brain-decision.schema.json +69 -0
  46. package/schemas/brain-learning.schema.json +57 -0
  47. package/schemas/brain-pattern.schema.json +72 -0
  48. package/schemas/config.schema.json +2606 -0
  49. package/schemas/context-state.schema.json +137 -0
  50. package/schemas/contribution.schema.json +722 -0
  51. package/schemas/critical-path.schema.json +246 -0
  52. package/schemas/deps-cache.schema.json +97 -0
  53. package/schemas/doctor-output.schema.json +283 -0
  54. package/schemas/error.schema.json +161 -0
  55. package/schemas/export-package.schema.json +375 -0
  56. package/schemas/global-config.schema.json +219 -0
  57. package/schemas/grade.schema.json +49 -0
  58. package/schemas/log.schema.json +250 -0
  59. package/schemas/metrics.schema.json +328 -0
  60. package/schemas/migrations.schema.json +150 -0
  61. package/schemas/nexus-registry.schema.json +90 -0
  62. package/schemas/operation-constitution.schema.json +438 -0
  63. package/schemas/output.schema.json +164 -0
  64. package/schemas/project-context.schema.json +164 -0
  65. package/schemas/project-info.schema.json +180 -0
  66. package/schemas/projects-registry.schema.json +107 -0
  67. package/schemas/protocol-frontmatter.schema.json +72 -0
  68. package/schemas/rcasd-consensus-report.schema.json +10 -0
  69. package/schemas/rcasd-evidence.schema.json +42 -0
  70. package/schemas/rcasd-gate-result.schema.json +46 -0
  71. package/schemas/rcasd-hitl-resolution.schema.json +10 -0
  72. package/schemas/rcasd-index.schema.json +10 -0
  73. package/schemas/rcasd-manifest.schema.json +10 -0
  74. package/schemas/rcasd-research-output.schema.json +10 -0
  75. package/schemas/rcasd-spec-frontmatter.schema.json +10 -0
  76. package/schemas/rcasd-stage-transition.schema.json +38 -0
  77. package/schemas/releases.schema.json +267 -0
  78. package/schemas/skills-manifest.schema.json +91 -0
  79. package/schemas/skillsmp.schema.json +208 -0
  80. package/schemas/spec-index.schema.json +196 -0
  81. package/schemas/system-flow-atlas.schema.json +125 -0
  82. package/src/__tests__/injection-chain.test.ts +11 -10
  83. package/src/__tests__/injection-mvi-tiers.test.ts +4 -2
  84. package/src/admin/import-tasks.ts +53 -29
  85. package/src/agents/__tests__/capacity.test.d.ts +7 -0
  86. package/src/agents/__tests__/capacity.test.d.ts.map +1 -0
  87. package/src/agents/__tests__/capacity.test.js +173 -0
  88. package/src/agents/__tests__/capacity.test.js.map +1 -0
  89. package/src/agents/__tests__/registry.test.d.ts +8 -0
  90. package/src/agents/__tests__/registry.test.d.ts.map +1 -0
  91. package/src/agents/__tests__/registry.test.js +348 -0
  92. package/src/agents/__tests__/registry.test.js.map +1 -0
  93. package/src/agents/__tests__/retry.test.d.ts +7 -0
  94. package/src/agents/__tests__/retry.test.d.ts.map +1 -0
  95. package/src/agents/__tests__/retry.test.js +225 -0
  96. package/src/agents/__tests__/retry.test.js.map +1 -0
  97. package/src/bootstrap.ts +3 -1
  98. package/src/init.ts +63 -18
  99. package/src/injection.ts +11 -5
  100. package/src/intelligence/__tests__/impact.test.d.ts +15 -0
  101. package/src/intelligence/__tests__/impact.test.d.ts.map +1 -0
  102. package/src/intelligence/__tests__/impact.test.js +384 -0
  103. package/src/intelligence/__tests__/impact.test.js.map +1 -0
  104. package/src/intelligence/__tests__/patterns.test.d.ts +8 -0
  105. package/src/intelligence/__tests__/patterns.test.d.ts.map +1 -0
  106. package/src/intelligence/__tests__/patterns.test.js +370 -0
  107. package/src/intelligence/__tests__/patterns.test.js.map +1 -0
  108. package/src/intelligence/__tests__/prediction.test.d.ts +8 -0
  109. package/src/intelligence/__tests__/prediction.test.d.ts.map +1 -0
  110. package/src/intelligence/__tests__/prediction.test.js +314 -0
  111. package/src/intelligence/__tests__/prediction.test.js.map +1 -0
  112. package/src/internal.ts +7 -1
  113. package/src/nexus/__tests__/nexus-e2e.test.d.ts +12 -0
  114. package/src/nexus/__tests__/nexus-e2e.test.d.ts.map +1 -0
  115. package/src/nexus/__tests__/nexus-e2e.test.js +1220 -0
  116. package/src/nexus/__tests__/nexus-e2e.test.js.map +1 -0
  117. package/src/nexus/__tests__/transfer.test.d.ts +8 -0
  118. package/src/nexus/__tests__/transfer.test.d.ts.map +1 -0
  119. package/src/nexus/__tests__/transfer.test.js +372 -0
  120. package/src/nexus/__tests__/transfer.test.js.map +1 -0
  121. package/src/nexus/__tests__/transfer.test.ts +446 -0
  122. package/src/nexus/index.ts +14 -0
  123. package/src/nexus/transfer-types.ts +129 -0
  124. package/src/nexus/transfer.ts +314 -0
  125. package/src/routing/capability-matrix.ts +1435 -205
  126. package/src/scaffold.ts +18 -11
  127. package/src/skills/__tests__/routing-table.test.ts +53 -33
  128. package/src/skills/agents/install.ts +9 -1
  129. package/src/skills/routing-table.ts +39 -253
  130. package/src/skills/skill-paths.ts +3 -2
  131. package/src/store/__tests__/project-detect.test.ts +1 -1
  132. package/src/store/brain-sqlite.ts +7 -3
  133. package/src/store/nexus-sqlite.ts +7 -3
  134. package/src/store/sqlite.ts +9 -3
  135. package/src/store/tasks-schema.ts +1 -1
  136. package/src/system/health.ts +18 -7
  137. package/src/ui/index.ts +0 -6
  138. package/src/validation/operation-gate-validators.ts +2 -2
  139. package/templates/CLEO-INJECTION.md +120 -0
  140. package/templates/README.md +29 -0
  141. package/templates/agent-registry.json +305 -0
  142. package/templates/cleo-gitignore +74 -0
  143. package/templates/config.template.json +187 -0
  144. package/templates/git-hooks/commit-msg +149 -0
  145. package/templates/git-hooks/pre-commit +40 -0
  146. package/templates/git-hooks/pre-push +79 -0
  147. package/templates/github/ISSUE_TEMPLATE/bug_report.yml +143 -0
  148. package/templates/github/ISSUE_TEMPLATE/config.yml +8 -0
  149. package/templates/github/ISSUE_TEMPLATE/feature_request.yml +125 -0
  150. package/templates/github/ISSUE_TEMPLATE/help_question.yml +99 -0
  151. package/templates/global-config.template.json +56 -0
  152. package/templates/hooks/precompact-safestop.sh +89 -0
  153. package/templates/issue-templates/bug_report.yml +143 -0
  154. package/templates/issue-templates/config.yml +8 -0
  155. package/templates/issue-templates/feature_request.yml +125 -0
  156. package/templates/issue-templates/help_question.yml +99 -0
  157. package/templates/skillsmp.json.example +28 -0
  158. package/templates/skillsmp.json.example.md +214 -0
  159. package/dist/ui/injection-legacy.d.ts +0 -26
  160. package/dist/ui/injection-legacy.d.ts.map +0 -1
  161. package/src/ui/__tests__/injection-registry.test.d.ts +0 -11
  162. package/src/ui/__tests__/injection-registry.test.d.ts.map +0 -1
  163. package/src/ui/__tests__/injection-registry.test.js +0 -46
  164. package/src/ui/__tests__/injection-registry.test.js.map +0 -1
  165. package/src/ui/__tests__/injection-registry.test.ts +0 -57
  166. package/src/ui/injection-legacy.ts +0 -44
package/src/scaffold.ts CHANGED
@@ -523,7 +523,7 @@ export async function ensureProjectContext(
523
523
  try {
524
524
  const schemaPath = join(
525
525
  dirname(fileURLToPath(import.meta.url)),
526
- '../../schemas/project-context.schema.json',
526
+ '../schemas/project-context.schema.json',
527
527
  );
528
528
  if (existsSync(schemaPath)) {
529
529
  const AjvModule = await import('ajv');
@@ -1037,8 +1037,16 @@ export function checkMemoryBridge(projectRoot: string): CheckResult {
1037
1037
  * Required subdirectories under the global ~/.cleo/ home.
1038
1038
  * These are infrastructure directories managed by CLEO itself,
1039
1039
  * not project-specific data.
1040
+ *
1041
+ * Truly global:
1042
+ * - logs — global log output
1043
+ * - templates — CLEO-INJECTION.md symlink target
1044
+ *
1045
+ * Note: nexus.db lives directly in ~/.cleo/, not a subdir.
1046
+ * Schemas are read at runtime from getPackageRoot()/schemas/ — no copy needed.
1047
+ * Project-level dirs (adrs/, rcasd/, agent-outputs/, backups/) live in .cleo/ only.
1040
1048
  */
1041
- export const REQUIRED_GLOBAL_SUBDIRS = ['schemas', 'templates'] as const;
1049
+ export const REQUIRED_GLOBAL_SUBDIRS = ['logs', 'templates'] as const;
1042
1050
 
1043
1051
  /**
1044
1052
  * Ensure the global ~/.cleo/ home directory and its required
@@ -1111,9 +1119,13 @@ export async function ensureGlobalTemplates(): Promise<ScaffoldResult> {
1111
1119
  }
1112
1120
 
1113
1121
  /**
1114
- * Perform a complete global scaffold operation: ensure home, schemas,
1115
- * and templates are all present and current. This is the single entry
1116
- * point for global infrastructure scaffolding.
1122
+ * Perform a complete global scaffold operation: ensure home and templates
1123
+ * are all present and current. This is the single entry point for global
1124
+ * infrastructure scaffolding.
1125
+ *
1126
+ * Schemas are NOT copied here — they are read at runtime from the npm
1127
+ * package path (getPackageRoot() + '/schemas/'). Use ensureGlobalSchemas()
1128
+ * explicitly from init or upgrade if a copy is needed for a specific workflow.
1117
1129
  *
1118
1130
  * Used by:
1119
1131
  * - MCP startup (via startupHealthCheck in health.ts)
@@ -1122,17 +1134,12 @@ export async function ensureGlobalTemplates(): Promise<ScaffoldResult> {
1122
1134
  */
1123
1135
  export async function ensureGlobalScaffold(): Promise<{
1124
1136
  home: ScaffoldResult;
1125
- schemas: { installed: number; updated: number; total: number };
1126
1137
  templates: ScaffoldResult;
1127
1138
  }> {
1128
- // Lazy import to avoid circular dependency
1129
- const { ensureGlobalSchemas } = await import('./schema-management.js');
1130
-
1131
1139
  const home = await ensureGlobalHome();
1132
- const schemas = ensureGlobalSchemas();
1133
1140
  const templates = await ensureGlobalTemplates();
1134
1141
 
1135
- return { home, schemas, templates };
1142
+ return { home, templates };
1136
1143
  }
1137
1144
 
1138
1145
  // ── Global check* functions (read-only diagnostics) ──────────────────
@@ -3,44 +3,16 @@ import {
3
3
  getOperationsByChannel,
4
4
  getPreferredChannel,
5
5
  getRoutingForDomain,
6
- ROUTING_TABLE,
7
6
  } from '../routing-table.js';
8
7
 
9
8
  describe('routing-table', () => {
10
- describe('ROUTING_TABLE structure', () => {
11
- it('has entries for all 10 canonical domains', () => {
12
- const domains = new Set(ROUTING_TABLE.map((e) => e.domain));
13
- expect(domains.has('memory')).toBe(true);
14
- expect(domains.has('tasks')).toBe(true);
15
- expect(domains.has('session')).toBe(true);
16
- expect(domains.has('admin')).toBe(true);
17
- expect(domains.has('tools')).toBe(true);
18
- expect(domains.has('check')).toBe(true);
19
- expect(domains.has('pipeline')).toBe(true);
20
- expect(domains.has('orchestrate')).toBe(true);
21
- expect(domains.has('nexus')).toBe(true);
22
- expect(domains.has('sticky')).toBe(true);
23
- });
24
-
25
- it('every entry has required fields', () => {
26
- for (const entry of ROUTING_TABLE) {
27
- expect(entry.domain).toBeTruthy();
28
- expect(entry.operation).toBeTruthy();
29
- expect(['mcp', 'cli', 'either']).toContain(entry.preferredChannel);
30
- expect(entry.reason).toBeTruthy();
31
- }
9
+ describe('getPreferredChannel (via capability matrix)', () => {
10
+ it('returns mcp for memory.find', () => {
11
+ expect(getPreferredChannel('memory', 'find')).toBe('mcp');
32
12
  });
33
13
 
34
- it('has no duplicate domain+operation pairs', () => {
35
- const keys = ROUTING_TABLE.map((e) => `${e.domain}.${e.operation}`);
36
- const unique = new Set(keys);
37
- expect(unique.size).toBe(keys.length);
38
- });
39
- });
40
-
41
- describe('getPreferredChannel', () => {
42
- it('returns mcp for memory.brain.search', () => {
43
- expect(getPreferredChannel('memory', 'brain.search')).toBe('mcp');
14
+ it('returns mcp for memory.fetch', () => {
15
+ expect(getPreferredChannel('memory', 'fetch')).toBe('mcp');
44
16
  });
45
17
 
46
18
  it('returns cli for pipeline.release.ship', () => {
@@ -54,15 +26,56 @@ describe('routing-table', () => {
54
26
  it('returns either for unknown operations', () => {
55
27
  expect(getPreferredChannel('nonexistent', 'op')).toBe('either');
56
28
  });
29
+
30
+ it('returns mcp for tasks.show', () => {
31
+ expect(getPreferredChannel('tasks', 'show')).toBe('mcp');
32
+ });
33
+
34
+ it('returns mcp for session.status', () => {
35
+ expect(getPreferredChannel('session', 'status')).toBe('mcp');
36
+ });
37
+
38
+ it('returns mcp for admin.dash', () => {
39
+ expect(getPreferredChannel('admin', 'dash')).toBe('mcp');
40
+ });
57
41
  });
58
42
 
59
43
  describe('getRoutingForDomain', () => {
44
+ it('returns entries covering all 10 canonical domains', () => {
45
+ const domains = [
46
+ 'memory',
47
+ 'tasks',
48
+ 'session',
49
+ 'admin',
50
+ 'tools',
51
+ 'check',
52
+ 'pipeline',
53
+ 'orchestrate',
54
+ 'nexus',
55
+ 'sticky',
56
+ ];
57
+ for (const domain of domains) {
58
+ const entries = getRoutingForDomain(domain);
59
+ expect(entries.length).toBeGreaterThan(0);
60
+ expect(entries.every((e) => e.domain === domain)).toBe(true);
61
+ }
62
+ });
63
+
60
64
  it('returns all memory domain entries', () => {
61
65
  const entries = getRoutingForDomain('memory');
62
66
  expect(entries.length).toBeGreaterThan(0);
63
67
  expect(entries.every((e) => e.domain === 'memory')).toBe(true);
64
68
  });
65
69
 
70
+ it('every entry has required fields with valid channel', () => {
71
+ const entries = getRoutingForDomain('tasks');
72
+ for (const entry of entries) {
73
+ expect(entry.domain).toBeTruthy();
74
+ expect(entry.operation).toBeTruthy();
75
+ expect(['mcp', 'cli', 'either']).toContain(entry.preferredChannel);
76
+ }
77
+ });
78
+
66
79
  it('returns empty array for unknown domain', () => {
67
80
  expect(getRoutingForDomain('nonexistent')).toEqual([]);
68
81
  });
@@ -86,5 +99,12 @@ describe('routing-table', () => {
86
99
  const cliCount = getOperationsByChannel('cli').length;
87
100
  expect(mcpCount).toBeGreaterThan(cliCount);
88
101
  });
102
+
103
+ it('has no duplicate domain+operation pairs within a channel', () => {
104
+ const mcpOps = getOperationsByChannel('mcp');
105
+ const keys = mcpOps.map((e) => `${e.domain}.${e.operation}`);
106
+ const unique = new Set(keys);
107
+ expect(unique.size).toBe(keys.length);
108
+ });
89
109
  });
90
110
  });
@@ -9,10 +9,18 @@
9
9
  */
10
10
 
11
11
  import { existsSync, mkdirSync, readdirSync, readlinkSync, symlinkSync, unlinkSync } from 'node:fs';
12
+ import { platform } from 'node:os';
12
13
  import { basename, join } from 'node:path';
13
14
  import { getClaudeAgentsDir } from '../../paths.js';
14
15
  import { getAgentsDir } from './config.js';
15
16
 
17
+ /**
18
+ * Symlink type for directory symlinks.
19
+ * On Windows, use 'junction' (no admin privileges required).
20
+ * On Unix, use 'dir'.
21
+ */
22
+ const DIR_SYMLINK_TYPE: 'junction' | 'dir' = platform() === 'win32' ? 'junction' : 'dir';
23
+
16
24
  // ============================================================================
17
25
  // Agent Installation
18
26
  // ============================================================================
@@ -65,7 +73,7 @@ export function installAgent(agentDir: string): {
65
73
  }
66
74
 
67
75
  try {
68
- symlinkSync(agentDir, targetPath, 'dir');
76
+ symlinkSync(agentDir, targetPath, DIR_SYMLINK_TYPE);
69
77
  return { installed: true, path: targetPath };
70
78
  } catch (err) {
71
79
  return { installed: false, path: targetPath, error: `Symlink failed: ${err}` };
@@ -1,24 +1,31 @@
1
1
  /**
2
2
  * Dynamic Skill Routing Table
3
3
  *
4
- * Token-efficiency routing table that determines whether each operation
5
- * is more efficient via MCP (lower overhead) or CLI (more flexible).
6
- *
7
- * MCP operations avoid CLI process startup overhead (~200ms + ~50 tokens)
8
- * and are preferred for rapid, repeated queries. CLI is preferred for
9
- * operations that benefit from human-readable output or shell integration.
4
+ * Thin wrapper over the merged capability matrix. PreferredChannel data
5
+ * is now stored as part of OperationCapability in the capability matrix
6
+ * (packages/core/src/routing/capability-matrix.ts), which is the single
7
+ * SSoT for both execution mode and channel preference.
10
8
  *
11
9
  * @task T5240
12
10
  * @epic T5149
11
+ * @see packages/core/src/routing/capability-matrix.ts
13
12
  */
14
13
 
14
+ import { getCapabilityMatrix } from '../routing/capability-matrix.js';
15
+
16
+ export type { PreferredChannel } from '../routing/capability-matrix.js';
17
+
15
18
  /**
16
19
  * Routing entry describing the preferred channel for an operation.
20
+ *
21
+ * Derived from OperationCapability in the capability matrix.
22
+ * Use this type when consuming domain-level routing results from
23
+ * getRoutingForDomain() or getOperationsByChannel().
17
24
  */
18
25
  export interface RoutingEntry {
19
26
  /** Domain name (e.g. 'tasks', 'memory', 'session') */
20
27
  domain: string;
21
- /** Operation name (e.g. 'brain.search', 'show') */
28
+ /** Operation name (e.g. 'show', 'find') */
22
29
  operation: string;
23
30
  /** Preferred channel for token efficiency */
24
31
  preferredChannel: 'mcp' | 'cli' | 'either';
@@ -26,277 +33,56 @@ export interface RoutingEntry {
26
33
  reason: string;
27
34
  }
28
35
 
29
- /**
30
- * Static routing table for all canonical operations.
31
- *
32
- * Operations are grouped by domain with channel preferences based on:
33
- * - MCP: lower overhead (no CLI startup), direct DB access, structured JSON
34
- * - CLI: human-readable output, shell integration, interactive prompts
35
- * - Either: no significant difference or context-dependent
36
- */
37
- export const ROUTING_TABLE: RoutingEntry[] = [
38
- // === Memory domain -- MCP strongly preferred (direct DB, no CLI startup) ===
39
- {
40
- domain: 'memory',
41
- operation: 'brain.search',
42
- preferredChannel: 'mcp',
43
- reason: 'Low overhead search, direct DB access',
44
- },
45
- {
46
- domain: 'memory',
47
- operation: 'brain.fetch',
48
- preferredChannel: 'mcp',
49
- reason: 'Direct DB access, structured response',
50
- },
51
- {
52
- domain: 'memory',
53
- operation: 'brain.timeline',
54
- preferredChannel: 'mcp',
55
- reason: 'Complex multi-table query, direct DB',
56
- },
57
- {
58
- domain: 'memory',
59
- operation: 'brain.observe',
60
- preferredChannel: 'mcp',
61
- reason: 'Direct DB write, no CLI overhead',
62
- },
63
- {
64
- domain: 'memory',
65
- operation: 'find',
66
- preferredChannel: 'mcp',
67
- reason: 'Alias for brain.search',
68
- },
69
- { domain: 'memory', operation: 'show', preferredChannel: 'mcp', reason: 'Alias for brain.fetch' },
70
- {
71
- domain: 'memory',
72
- operation: 'decision.store',
73
- preferredChannel: 'mcp',
74
- reason: 'Direct DB write',
75
- },
76
- {
77
- domain: 'memory',
78
- operation: 'decision.find',
79
- preferredChannel: 'mcp',
80
- reason: 'Direct DB query',
81
- },
82
- {
83
- domain: 'memory',
84
- operation: 'learning.store',
85
- preferredChannel: 'mcp',
86
- reason: 'Direct DB write',
87
- },
88
- {
89
- domain: 'memory',
90
- operation: 'learning.find',
91
- preferredChannel: 'mcp',
92
- reason: 'Direct DB query',
93
- },
94
- {
95
- domain: 'memory',
96
- operation: 'pattern.store',
97
- preferredChannel: 'mcp',
98
- reason: 'Direct DB write',
99
- },
100
- {
101
- domain: 'memory',
102
- operation: 'pattern.find',
103
- preferredChannel: 'mcp',
104
- reason: 'Direct DB query',
105
- },
106
-
107
- // === Tasks domain -- MCP preferred for queries, CLI for complex ops ===
108
- {
109
- domain: 'tasks',
110
- operation: 'show',
111
- preferredChannel: 'mcp',
112
- reason: 'Structured JSON response',
113
- },
114
- { domain: 'tasks', operation: 'find', preferredChannel: 'mcp', reason: 'Low overhead search' },
115
- { domain: 'tasks', operation: 'list', preferredChannel: 'mcp', reason: 'Paginated response' },
116
- {
117
- domain: 'tasks',
118
- operation: 'next',
119
- preferredChannel: 'mcp',
120
- reason: 'Algorithm runs in-process',
121
- },
122
- { domain: 'tasks', operation: 'current', preferredChannel: 'mcp', reason: 'Simple DB lookup' },
123
- {
124
- domain: 'tasks',
125
- operation: 'plan',
126
- preferredChannel: 'mcp',
127
- reason: 'Composite view, in-process',
128
- },
129
- { domain: 'tasks', operation: 'add', preferredChannel: 'mcp', reason: 'Atomic DB write' },
130
- { domain: 'tasks', operation: 'update', preferredChannel: 'mcp', reason: 'Atomic DB write' },
131
- {
132
- domain: 'tasks',
133
- operation: 'complete',
134
- preferredChannel: 'mcp',
135
- reason: 'Atomic DB write with hooks',
136
- },
137
- { domain: 'tasks', operation: 'start', preferredChannel: 'mcp', reason: 'Atomic DB write' },
138
- { domain: 'tasks', operation: 'stop', preferredChannel: 'mcp', reason: 'Atomic DB write' },
139
-
140
- // === Session domain -- MCP preferred (stateful, in-process) ===
141
- { domain: 'session', operation: 'status', preferredChannel: 'mcp', reason: 'Quick state lookup' },
142
- { domain: 'session', operation: 'start', preferredChannel: 'mcp', reason: 'State transition' },
143
- {
144
- domain: 'session',
145
- operation: 'end',
146
- preferredChannel: 'mcp',
147
- reason: 'State transition with hooks',
148
- },
149
- {
150
- domain: 'session',
151
- operation: 'handoff.show',
152
- preferredChannel: 'mcp',
153
- reason: 'Structured handoff data',
154
- },
155
- {
156
- domain: 'session',
157
- operation: 'briefing.show',
158
- preferredChannel: 'mcp',
159
- reason: 'Composite cold-start',
160
- },
161
-
162
- // === Admin domain -- either (human interaction common) ===
163
- { domain: 'admin', operation: 'version', preferredChannel: 'either', reason: 'Simple lookup' },
164
- { domain: 'admin', operation: 'health', preferredChannel: 'either', reason: 'Diagnostics' },
165
- { domain: 'admin', operation: 'dash', preferredChannel: 'mcp', reason: 'Composite view' },
166
- { domain: 'admin', operation: 'help', preferredChannel: 'mcp', reason: 'Operation discovery' },
167
- {
168
- domain: 'admin',
169
- operation: 'map',
170
- preferredChannel: 'mcp',
171
- reason: 'Structured codebase analysis',
172
- },
173
-
174
- // === Tools domain -- MCP preferred (structured responses) ===
175
- {
176
- domain: 'tools',
177
- operation: 'skill.list',
178
- preferredChannel: 'mcp',
179
- reason: 'Structured catalog',
180
- },
181
- {
182
- domain: 'tools',
183
- operation: 'skill.show',
184
- preferredChannel: 'mcp',
185
- reason: 'Structured skill data',
186
- },
187
- { domain: 'tools', operation: 'skill.find', preferredChannel: 'mcp', reason: 'Search response' },
188
- {
189
- domain: 'tools',
190
- operation: 'skill.install',
191
- preferredChannel: 'either',
192
- reason: 'May need shell access',
193
- },
194
- {
195
- domain: 'tools',
196
- operation: 'provider.list',
197
- preferredChannel: 'mcp',
198
- reason: 'Structured list',
199
- },
200
- {
201
- domain: 'tools',
202
- operation: 'provider.detect',
203
- preferredChannel: 'mcp',
204
- reason: 'Detection result',
205
- },
206
- {
207
- domain: 'tools',
208
- operation: 'adapter.list',
209
- preferredChannel: 'mcp',
210
- reason: 'Structured list',
211
- },
212
- {
213
- domain: 'tools',
214
- operation: 'adapter.show',
215
- preferredChannel: 'mcp',
216
- reason: 'Structured manifest',
217
- },
218
- {
219
- domain: 'tools',
220
- operation: 'adapter.activate',
221
- preferredChannel: 'mcp',
222
- reason: 'State transition',
223
- },
224
-
225
- // === Check domain -- MCP preferred (validation results) ===
226
- {
227
- domain: 'check',
228
- operation: 'validate',
229
- preferredChannel: 'mcp',
230
- reason: 'Structured validation',
231
- },
232
- {
233
- domain: 'check',
234
- operation: 'compliance',
235
- preferredChannel: 'mcp',
236
- reason: 'Compliance report',
237
- },
238
-
239
- // === Pipeline domain -- MCP preferred (lifecycle gates) ===
240
- {
241
- domain: 'pipeline',
242
- operation: 'lifecycle.status',
243
- preferredChannel: 'mcp',
244
- reason: 'Gate status',
245
- },
246
- {
247
- domain: 'pipeline',
248
- operation: 'release.ship',
249
- preferredChannel: 'cli',
250
- reason: 'Complex multi-step with git ops',
251
- },
252
-
253
- // === Orchestrate domain -- MCP preferred (agent coordination) ===
254
- { domain: 'orchestrate', operation: 'spawn', preferredChannel: 'mcp', reason: 'Agent spawning' },
255
- { domain: 'orchestrate', operation: 'plan', preferredChannel: 'mcp', reason: 'Decomposition' },
256
-
257
- // === Nexus domain -- either (cross-project queries) ===
258
- {
259
- domain: 'nexus',
260
- operation: 'search',
261
- preferredChannel: 'either',
262
- reason: 'Cross-project search',
263
- },
264
- { domain: 'nexus', operation: 'sync', preferredChannel: 'either', reason: 'Cross-project sync' },
265
-
266
- // === Sticky domain -- MCP preferred (quick ephemeral notes) ===
267
- { domain: 'sticky', operation: 'add', preferredChannel: 'mcp', reason: 'Quick DB write' },
268
- { domain: 'sticky', operation: 'list', preferredChannel: 'mcp', reason: 'Quick DB read' },
269
- { domain: 'sticky', operation: 'show', preferredChannel: 'mcp', reason: 'Quick DB read' },
270
- ];
271
-
272
36
  /**
273
37
  * Look up the preferred channel for a given domain + operation.
274
38
  *
39
+ * Reads from the merged capability matrix (single SSoT).
40
+ *
275
41
  * @param domain - Domain name
276
42
  * @param operation - Operation name
277
43
  * @returns Preferred channel ('mcp', 'cli', or 'either' as fallback)
278
44
  */
279
45
  export function getPreferredChannel(domain: string, operation: string): 'mcp' | 'cli' | 'either' {
280
- const entry = ROUTING_TABLE.find((e) => e.domain === domain && e.operation === operation);
46
+ const entry = getCapabilityMatrix().find(
47
+ (cap) => cap.domain === domain && cap.operation === operation,
48
+ );
281
49
  return entry?.preferredChannel ?? 'either';
282
50
  }
283
51
 
284
52
  /**
285
53
  * Get routing entries for a specific domain.
286
54
  *
55
+ * Derives entries from the capability matrix.
56
+ *
287
57
  * @param domain - Domain name
288
58
  * @returns All routing entries for the domain
289
59
  */
290
60
  export function getRoutingForDomain(domain: string): RoutingEntry[] {
291
- return ROUTING_TABLE.filter((e) => e.domain === domain);
61
+ return getCapabilityMatrix()
62
+ .filter((cap) => cap.domain === domain)
63
+ .map((cap) => ({
64
+ domain: cap.domain,
65
+ operation: cap.operation,
66
+ preferredChannel: cap.preferredChannel,
67
+ reason: '',
68
+ }));
292
69
  }
293
70
 
294
71
  /**
295
72
  * Get all operations that prefer a specific channel.
296
73
  *
74
+ * Derives entries from the capability matrix.
75
+ *
297
76
  * @param channel - Channel preference to filter by
298
77
  * @returns Matching routing entries
299
78
  */
300
79
  export function getOperationsByChannel(channel: 'mcp' | 'cli' | 'either'): RoutingEntry[] {
301
- return ROUTING_TABLE.filter((e) => e.preferredChannel === channel);
80
+ return getCapabilityMatrix()
81
+ .filter((cap) => cap.preferredChannel === channel)
82
+ .map((cap) => ({
83
+ domain: cap.domain,
84
+ operation: cap.operation,
85
+ preferredChannel: cap.preferredChannel,
86
+ reason: '',
87
+ }));
302
88
  }
@@ -15,7 +15,7 @@
15
15
 
16
16
  import { existsSync, lstatSync, readlinkSync, realpathSync } from 'node:fs';
17
17
  import { delimiter, join, resolve } from 'node:path';
18
- import { getAgentsHome } from '../paths.js';
18
+ import { getCanonicalSkillsDir } from '@cleocode/caamp';
19
19
 
20
20
  /** Source type classification for a skill directory. */
21
21
  export type SkillSourceType = 'embedded' | 'caamp' | 'project-link' | 'global-link';
@@ -31,10 +31,11 @@ export interface SkillSearchPath {
31
31
 
32
32
  /**
33
33
  * Get the CAAMP canonical skill location.
34
+ * Delegates to caamp's getCanonicalSkillsDir() which is XDG-aware.
34
35
  * @task T4552
35
36
  */
36
37
  function getCaampCanonical(): string {
37
- return join(getAgentsHome(), 'skills');
38
+ return getCanonicalSkillsDir();
38
39
  }
39
40
 
40
41
  /**
@@ -14,7 +14,7 @@ import { detectProjectType } from '../project-detect.js';
14
14
 
15
15
  // ─── Schema validator setup ───────────────────────────────────────────────────
16
16
  const schemaPath = fileURLToPath(
17
- new URL('../../../../../schemas/project-context.schema.json', import.meta.url),
17
+ new URL('../../../schemas/project-context.schema.json', import.meta.url),
18
18
  );
19
19
  const schema = JSON.parse(readFileSync(schemaPath, 'utf-8'));
20
20
  const Ajv = (AjvModule as any).default ?? AjvModule;
@@ -50,13 +50,17 @@ export function getBrainDbPath(cwd?: string): string {
50
50
 
51
51
  /**
52
52
  * Resolve the path to the drizzle-brain migrations folder.
53
- * Works from both src/ (dev via tsx) and dist/ (compiled).
53
+ * Works from both src/ (dev via tsx) and dist/ (compiled via esbuild bundle).
54
+ *
55
+ * - Source layout: __dirname = src/store/ → need ../../migrations/drizzle-brain
56
+ * - Bundled layout: __dirname = dist/ → need ../migrations/drizzle-brain
54
57
  */
55
58
  export function resolveBrainMigrationsFolder(): string {
56
59
  const __filename = fileURLToPath(import.meta.url);
57
60
  const __dirname = dirname(__filename);
58
- // Both src/store/ and dist/store/ are 2 levels deep from package root
59
- return join(__dirname, '..', '..', 'migrations', 'drizzle-brain');
61
+ const isBundled = __dirname.endsWith('/dist') || __dirname.endsWith('\\dist');
62
+ const pkgRoot = isBundled ? join(__dirname, '..') : join(__dirname, '..', '..');
63
+ return join(pkgRoot, 'migrations', 'drizzle-brain');
60
64
  }
61
65
 
62
66
  /**
@@ -46,13 +46,17 @@ export function getNexusDbPath(): string {
46
46
 
47
47
  /**
48
48
  * Resolve the path to the drizzle-nexus migrations folder.
49
- * Works from both src/ (dev via tsx) and dist/ (compiled).
49
+ * Works from both src/ (dev via tsx) and dist/ (compiled via esbuild bundle).
50
+ *
51
+ * - Source layout: __dirname = src/store/ → need ../../migrations/drizzle-nexus
52
+ * - Bundled layout: __dirname = dist/ → need ../migrations/drizzle-nexus
50
53
  */
51
54
  export function resolveNexusMigrationsFolder(): string {
52
55
  const __filename = fileURLToPath(import.meta.url);
53
56
  const __dirname = dirname(__filename);
54
- // Both src/store/ and dist/store/ are 2 levels deep from package root
55
- return join(__dirname, '..', '..', 'migrations', 'drizzle-nexus');
57
+ const isBundled = __dirname.endsWith('/dist') || __dirname.endsWith('\\dist');
58
+ const pkgRoot = isBundled ? join(__dirname, '..') : join(__dirname, '..', '..');
59
+ return join(pkgRoot, 'migrations', 'drizzle-nexus');
56
60
  }
57
61
 
58
62
  /**
@@ -357,13 +357,19 @@ export async function getDb(cwd?: string): Promise<NodeSQLiteDatabase<typeof sch
357
357
 
358
358
  /**
359
359
  * Resolve the path to the drizzle migrations folder.
360
- * Works from both src/ (dev via tsx) and dist/ (compiled).
360
+ * Works from both src/ (dev via tsx) and dist/ (compiled via esbuild bundle).
361
+ *
362
+ * - Source layout: __dirname = src/store/ → need ../../migrations/drizzle-tasks
363
+ * - Bundled layout: __dirname = dist/ → need ../migrations/drizzle-tasks
361
364
  */
362
365
  export function resolveMigrationsFolder(): string {
363
366
  const __filename = fileURLToPath(import.meta.url);
364
367
  const __dirname = dirname(__filename);
365
- // Both src/store/ and dist/store/ are 2 levels deep from package root
366
- return join(__dirname, '..', '..', 'migrations', 'drizzle-tasks');
368
+ // When esbuild bundles into dist/index.js, __dirname is dist/ (1 level deep).
369
+ // When running from source via tsx, __dirname is src/store/ (2 levels deep).
370
+ const isBundled = __dirname.endsWith('/dist') || __dirname.endsWith('\\dist');
371
+ const pkgRoot = isBundled ? join(__dirname, '..') : join(__dirname, '..', '..');
372
+ return join(pkgRoot, 'migrations', 'drizzle-tasks');
367
373
  }
368
374
 
369
375
  /**
@@ -127,7 +127,7 @@ export const TASK_RELATION_TYPES = [
127
127
  export const LIFECYCLE_TRANSITION_TYPES = ['automatic', 'manual', 'forced'] as const;
128
128
 
129
129
  /** External task link types matching DB constraint on external_task_links.link_type. */
130
- export const EXTERNAL_LINK_TYPES = ['created', 'matched', 'manual'] as const;
130
+ export const EXTERNAL_LINK_TYPES = ['created', 'matched', 'manual', 'transferred'] as const;
131
131
 
132
132
  /** Sync direction types matching DB constraint on external_task_links.sync_direction. */
133
133
  export const SYNC_DIRECTIONS = ['inbound', 'outbound', 'bidirectional'] as const;