@cleocode/core 2026.3.45 → 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 (137) hide show
  1. package/dist/bootstrap.d.ts.map +1 -1
  2. package/dist/index.js +1475 -372
  3. package/dist/index.js.map +4 -4
  4. package/dist/init.d.ts.map +1 -1
  5. package/dist/injection.d.ts +1 -1
  6. package/dist/injection.d.ts.map +1 -1
  7. package/dist/routing/capability-matrix.d.ts +6 -4
  8. package/dist/routing/capability-matrix.d.ts.map +1 -1
  9. package/dist/scaffold.d.ts +16 -9
  10. package/dist/scaffold.d.ts.map +1 -1
  11. package/dist/skills/agents/install.d.ts.map +1 -1
  12. package/dist/skills/routing-table.d.ts +17 -16
  13. package/dist/skills/routing-table.d.ts.map +1 -1
  14. package/dist/skills/skill-paths.d.ts.map +1 -1
  15. package/dist/system/health.d.ts.map +1 -1
  16. package/dist/ui/index.d.ts +0 -1
  17. package/dist/ui/index.d.ts.map +1 -1
  18. package/package.json +9 -4
  19. package/schemas/adr-frontmatter.schema.json +72 -0
  20. package/schemas/agent-configs.schema.json +120 -0
  21. package/schemas/agent-registry.json +243 -0
  22. package/schemas/agent-registry.schema.json +132 -0
  23. package/schemas/archive/research-manifest.schema.json +257 -0
  24. package/schemas/archive.schema.json +450 -0
  25. package/schemas/brain-decision.schema.json +69 -0
  26. package/schemas/brain-learning.schema.json +57 -0
  27. package/schemas/brain-pattern.schema.json +72 -0
  28. package/schemas/config.schema.json +2606 -0
  29. package/schemas/context-state.schema.json +137 -0
  30. package/schemas/contribution.schema.json +722 -0
  31. package/schemas/critical-path.schema.json +246 -0
  32. package/schemas/deps-cache.schema.json +97 -0
  33. package/schemas/doctor-output.schema.json +283 -0
  34. package/schemas/error.schema.json +161 -0
  35. package/schemas/export-package.schema.json +375 -0
  36. package/schemas/global-config.schema.json +219 -0
  37. package/schemas/grade.schema.json +49 -0
  38. package/schemas/log.schema.json +250 -0
  39. package/schemas/metrics.schema.json +328 -0
  40. package/schemas/migrations.schema.json +150 -0
  41. package/schemas/nexus-registry.schema.json +90 -0
  42. package/schemas/operation-constitution.schema.json +438 -0
  43. package/schemas/output.schema.json +164 -0
  44. package/schemas/project-context.schema.json +164 -0
  45. package/schemas/project-info.schema.json +180 -0
  46. package/schemas/projects-registry.schema.json +107 -0
  47. package/schemas/protocol-frontmatter.schema.json +72 -0
  48. package/schemas/rcasd-consensus-report.schema.json +10 -0
  49. package/schemas/rcasd-evidence.schema.json +42 -0
  50. package/schemas/rcasd-gate-result.schema.json +46 -0
  51. package/schemas/rcasd-hitl-resolution.schema.json +10 -0
  52. package/schemas/rcasd-index.schema.json +10 -0
  53. package/schemas/rcasd-manifest.schema.json +10 -0
  54. package/schemas/rcasd-research-output.schema.json +10 -0
  55. package/schemas/rcasd-spec-frontmatter.schema.json +10 -0
  56. package/schemas/rcasd-stage-transition.schema.json +38 -0
  57. package/schemas/releases.schema.json +267 -0
  58. package/schemas/skills-manifest.schema.json +91 -0
  59. package/schemas/skillsmp.schema.json +208 -0
  60. package/schemas/spec-index.schema.json +196 -0
  61. package/schemas/system-flow-atlas.schema.json +125 -0
  62. package/src/__tests__/injection-chain.test.ts +11 -10
  63. package/src/__tests__/injection-mvi-tiers.test.ts +4 -2
  64. package/src/agents/__tests__/capacity.test.d.ts +7 -0
  65. package/src/agents/__tests__/capacity.test.d.ts.map +1 -0
  66. package/src/agents/__tests__/capacity.test.js +173 -0
  67. package/src/agents/__tests__/capacity.test.js.map +1 -0
  68. package/src/agents/__tests__/registry.test.d.ts +8 -0
  69. package/src/agents/__tests__/registry.test.d.ts.map +1 -0
  70. package/src/agents/__tests__/registry.test.js +348 -0
  71. package/src/agents/__tests__/registry.test.js.map +1 -0
  72. package/src/agents/__tests__/retry.test.d.ts +7 -0
  73. package/src/agents/__tests__/retry.test.d.ts.map +1 -0
  74. package/src/agents/__tests__/retry.test.js +225 -0
  75. package/src/agents/__tests__/retry.test.js.map +1 -0
  76. package/src/bootstrap.ts +3 -1
  77. package/src/init.ts +63 -18
  78. package/src/injection.ts +11 -5
  79. package/src/intelligence/__tests__/impact.test.d.ts +15 -0
  80. package/src/intelligence/__tests__/impact.test.d.ts.map +1 -0
  81. package/src/intelligence/__tests__/impact.test.js +384 -0
  82. package/src/intelligence/__tests__/impact.test.js.map +1 -0
  83. package/src/intelligence/__tests__/patterns.test.d.ts +8 -0
  84. package/src/intelligence/__tests__/patterns.test.d.ts.map +1 -0
  85. package/src/intelligence/__tests__/patterns.test.js +370 -0
  86. package/src/intelligence/__tests__/patterns.test.js.map +1 -0
  87. package/src/intelligence/__tests__/prediction.test.d.ts +8 -0
  88. package/src/intelligence/__tests__/prediction.test.d.ts.map +1 -0
  89. package/src/intelligence/__tests__/prediction.test.js +314 -0
  90. package/src/intelligence/__tests__/prediction.test.js.map +1 -0
  91. package/src/nexus/__tests__/nexus-e2e.test.d.ts +12 -0
  92. package/src/nexus/__tests__/nexus-e2e.test.d.ts.map +1 -0
  93. package/src/nexus/__tests__/nexus-e2e.test.js +1220 -0
  94. package/src/nexus/__tests__/nexus-e2e.test.js.map +1 -0
  95. package/src/nexus/__tests__/transfer.test.d.ts +8 -0
  96. package/src/nexus/__tests__/transfer.test.d.ts.map +1 -0
  97. package/src/nexus/__tests__/transfer.test.js +372 -0
  98. package/src/nexus/__tests__/transfer.test.js.map +1 -0
  99. package/src/nexus/__tests__/transfer.test.ts +1 -1
  100. package/src/routing/capability-matrix.ts +1435 -205
  101. package/src/scaffold.ts +18 -11
  102. package/src/skills/__tests__/routing-table.test.ts +53 -33
  103. package/src/skills/agents/install.ts +9 -1
  104. package/src/skills/routing-table.ts +39 -253
  105. package/src/skills/skill-paths.ts +3 -2
  106. package/src/store/__tests__/project-detect.test.ts +1 -1
  107. package/src/system/health.ts +18 -7
  108. package/src/ui/index.ts +0 -6
  109. package/src/validation/operation-gate-validators.ts +2 -2
  110. package/templates/CLEO-INJECTION.md +120 -0
  111. package/templates/README.md +29 -0
  112. package/templates/agent-registry.json +305 -0
  113. package/templates/cleo-gitignore +74 -0
  114. package/templates/config.template.json +187 -0
  115. package/templates/git-hooks/commit-msg +149 -0
  116. package/templates/git-hooks/pre-commit +40 -0
  117. package/templates/git-hooks/pre-push +79 -0
  118. package/templates/github/ISSUE_TEMPLATE/bug_report.yml +143 -0
  119. package/templates/github/ISSUE_TEMPLATE/config.yml +8 -0
  120. package/templates/github/ISSUE_TEMPLATE/feature_request.yml +125 -0
  121. package/templates/github/ISSUE_TEMPLATE/help_question.yml +99 -0
  122. package/templates/global-config.template.json +56 -0
  123. package/templates/hooks/precompact-safestop.sh +89 -0
  124. package/templates/issue-templates/bug_report.yml +143 -0
  125. package/templates/issue-templates/config.yml +8 -0
  126. package/templates/issue-templates/feature_request.yml +125 -0
  127. package/templates/issue-templates/help_question.yml +99 -0
  128. package/templates/skillsmp.json.example +28 -0
  129. package/templates/skillsmp.json.example.md +214 -0
  130. package/dist/ui/injection-legacy.d.ts +0 -26
  131. package/dist/ui/injection-legacy.d.ts.map +0 -1
  132. package/src/ui/__tests__/injection-registry.test.d.ts +0 -11
  133. package/src/ui/__tests__/injection-registry.test.d.ts.map +0 -1
  134. package/src/ui/__tests__/injection-registry.test.js +0 -46
  135. package/src/ui/__tests__/injection-registry.test.js.map +0 -1
  136. package/src/ui/__tests__/injection-registry.test.ts +0 -57
  137. 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;
@@ -1036,7 +1036,7 @@ export async function startupHealthCheck(projectRoot?: string): Promise<StartupH
1036
1036
  !globalSchemaCheck.ok;
1037
1037
 
1038
1038
  if (globalNeedsRepair) {
1039
- // Auto-repair: ensureGlobalScaffold is idempotent and safe
1039
+ // Auto-repair home and templates via ensureGlobalScaffold (idempotent, safe)
1040
1040
  const scaffoldResult = await ensureGlobalScaffold();
1041
1041
 
1042
1042
  checks.push({
@@ -1049,12 +1049,23 @@ export async function startupHealthCheck(projectRoot?: string): Promise<StartupH
1049
1049
  repaired: scaffoldResult.home.action !== 'skipped',
1050
1050
  });
1051
1051
 
1052
- checks.push({
1053
- check: 'global_schemas',
1054
- status: 'pass',
1055
- message: `Schemas: ${scaffoldResult.schemas.installed} installed, ${scaffoldResult.schemas.updated} updated of ${scaffoldResult.schemas.total}`,
1056
- repaired: scaffoldResult.schemas.installed > 0 || scaffoldResult.schemas.updated > 0,
1057
- });
1052
+ // Schemas are read at runtime from the package path; only copy if stale
1053
+ if (!globalSchemaCheck.ok) {
1054
+ const { ensureGlobalSchemas } = await import('../schema-management.js');
1055
+ const schemaResult = ensureGlobalSchemas();
1056
+ checks.push({
1057
+ check: 'global_schemas',
1058
+ status: 'pass',
1059
+ message: `Schemas: ${schemaResult.installed} installed, ${schemaResult.updated} updated of ${schemaResult.total}`,
1060
+ repaired: schemaResult.installed > 0 || schemaResult.updated > 0,
1061
+ });
1062
+ } else {
1063
+ checks.push({
1064
+ check: 'global_schemas',
1065
+ status: 'pass',
1066
+ message: `All ${globalSchemaCheck.installed} schemas current`,
1067
+ });
1068
+ }
1058
1069
 
1059
1070
  checks.push({
1060
1071
  check: 'global_templates',
package/src/ui/index.ts CHANGED
@@ -59,9 +59,3 @@ export {
59
59
  // injection.ts -> CAAMP inject()/checkInjection()
60
60
  // mcp-config.ts -> CAAMP detectAllProviders()/installMcpServer()
61
61
  // injection-registry.ts -> CAAMP getInstructionFiles()
62
-
63
- // Injection legacy utilities (kept for validation output support)
64
- export {
65
- getValidationKey,
66
- INJECTION_VALIDATION_KEYS,
67
- } from './injection-legacy.js';
@@ -326,7 +326,7 @@ export async function validateLayer2Semantic(context: OperationContext): Promise
326
326
  // Session scope validation
327
327
  if (context.domain === 'session' && context.operation === 'start') {
328
328
  const scope = context.params?.scope as string | undefined;
329
- if (scope && !scope.match(/^(epic|task|global):/)) {
329
+ if (scope && scope !== 'global' && !scope.match(/^(epic|task):/)) {
330
330
  violations.push({
331
331
  layer: GateLayer.SEMANTIC,
332
332
  severity: ErrorSeverity.ERROR,
@@ -334,7 +334,7 @@ export async function validateLayer2Semantic(context: OperationContext): Promise
334
334
  message: `Invalid session scope format: ${scope}`,
335
335
  field: 'scope',
336
336
  constraint: 'Must be epic:<id>, task:<id>, or global',
337
- fix: 'Use format: epic:T1234, task:T5678, or global',
337
+ fix: 'Provide scope as "global" or "epic:TXXX"',
338
338
  });
339
339
  }
340
340
  }