@cleocode/adapters 2026.4.47 → 2026.4.49

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 (61) hide show
  1. package/dist/cant-context.d.ts +132 -1
  2. package/dist/cant-context.d.ts.map +1 -1
  3. package/dist/index.d.ts +4 -2
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +19765 -375
  6. package/dist/index.js.map +4 -4
  7. package/dist/providers/claude-code/adapter.d.ts +12 -6
  8. package/dist/providers/claude-code/adapter.d.ts.map +1 -1
  9. package/dist/providers/claude-code/hooks.d.ts.map +1 -1
  10. package/dist/providers/claude-code/spawn.d.ts.map +1 -1
  11. package/dist/providers/claude-sdk/index.d.ts +18 -0
  12. package/dist/providers/claude-sdk/index.d.ts.map +1 -0
  13. package/dist/providers/claude-sdk/mcp-registry.d.ts +40 -0
  14. package/dist/providers/claude-sdk/mcp-registry.d.ts.map +1 -0
  15. package/dist/providers/claude-sdk/session-store.d.ts +78 -0
  16. package/dist/providers/claude-sdk/session-store.d.ts.map +1 -0
  17. package/dist/providers/claude-sdk/spawn.d.ts +79 -0
  18. package/dist/providers/claude-sdk/spawn.d.ts.map +1 -0
  19. package/dist/providers/claude-sdk/tool-bridge.d.ts +38 -0
  20. package/dist/providers/claude-sdk/tool-bridge.d.ts.map +1 -0
  21. package/dist/providers/openai-sdk/adapter.d.ts +77 -0
  22. package/dist/providers/openai-sdk/adapter.d.ts.map +1 -0
  23. package/dist/providers/openai-sdk/guardrails.d.ts +67 -0
  24. package/dist/providers/openai-sdk/guardrails.d.ts.map +1 -0
  25. package/dist/providers/openai-sdk/handoff.d.ts +94 -0
  26. package/dist/providers/openai-sdk/handoff.d.ts.map +1 -0
  27. package/dist/providers/openai-sdk/index.d.ts +39 -0
  28. package/dist/providers/openai-sdk/index.d.ts.map +1 -0
  29. package/dist/providers/openai-sdk/install.d.ts +61 -0
  30. package/dist/providers/openai-sdk/install.d.ts.map +1 -0
  31. package/dist/providers/openai-sdk/spawn.d.ts +146 -0
  32. package/dist/providers/openai-sdk/spawn.d.ts.map +1 -0
  33. package/dist/providers/openai-sdk/tracing.d.ts +89 -0
  34. package/dist/providers/openai-sdk/tracing.d.ts.map +1 -0
  35. package/dist/providers/shared/conduit-trace-writer.d.ts +72 -0
  36. package/dist/providers/shared/conduit-trace-writer.d.ts.map +1 -0
  37. package/dist/providers/shared/sdk-result-mapper.d.ts +51 -0
  38. package/dist/providers/shared/sdk-result-mapper.d.ts.map +1 -0
  39. package/package.json +5 -3
  40. package/src/cant-context.ts +397 -3
  41. package/src/index.ts +24 -2
  42. package/src/providers/claude-code/adapter.ts +41 -4
  43. package/src/providers/claude-code/hooks.ts +7 -1
  44. package/src/providers/claude-code/spawn.ts +5 -1
  45. package/src/providers/claude-sdk/__tests__/spawn.test.ts +448 -0
  46. package/src/providers/claude-sdk/index.ts +18 -0
  47. package/src/providers/claude-sdk/mcp-registry.ts +96 -0
  48. package/src/providers/claude-sdk/session-store.ts +103 -0
  49. package/src/providers/claude-sdk/spawn.ts +242 -0
  50. package/src/providers/claude-sdk/tool-bridge.ts +51 -0
  51. package/src/providers/openai-sdk/__tests__/openai-sdk-spawn.test.ts +716 -0
  52. package/src/providers/openai-sdk/adapter.ts +138 -0
  53. package/src/providers/openai-sdk/guardrails.ts +158 -0
  54. package/src/providers/openai-sdk/handoff.ts +187 -0
  55. package/src/providers/openai-sdk/index.ts +55 -0
  56. package/src/providers/openai-sdk/install.ts +135 -0
  57. package/src/providers/openai-sdk/manifest.json +45 -0
  58. package/src/providers/openai-sdk/spawn.ts +300 -0
  59. package/src/providers/openai-sdk/tracing.ts +175 -0
  60. package/src/providers/shared/conduit-trace-writer.ts +101 -0
  61. package/src/providers/shared/sdk-result-mapper.ts +83 -0
@@ -8,6 +8,7 @@
8
8
  * 1. Compiled CANT bundle (team topology, agent personas, tool ACLs)
9
9
  * 2. Memory bridge (recent decisions, handoff notes, key patterns)
10
10
  * 3. Mental model injection (validate-on-load agent-specific observations)
11
+ * 4. NEXUS code intelligence context (callers, callees, impact data) [T625]
11
12
  *
12
13
  * All operations are best-effort: if any step fails (missing packages, empty
13
14
  * directories, compilation errors), the base prompt is returned unchanged.
@@ -17,6 +18,7 @@
17
18
  * (Pi-only; this module generalizes the same logic for all providers)
18
19
  *
19
20
  * @task T555
21
+ * @task T625
20
22
  */
21
23
 
22
24
  import { execFile } from 'node:child_process';
@@ -67,6 +69,69 @@ export interface BuildCantEnrichedPromptOptions {
67
69
  * Use this to avoid double-compilation when the Pi bridge already compiled the bundle.
68
70
  */
69
71
  compiledBundle?: string;
72
+ /**
73
+ * Task ID to inject NEXUS code intelligence context for.
74
+ * When provided, step 6b fetches callers/callees/impact for symbols
75
+ * mentioned in the task description and injects them into the prompt.
76
+ *
77
+ * @task T625
78
+ */
79
+ taskId?: string;
80
+ }
81
+
82
+ // ---------------------------------------------------------------------------
83
+ // NEXUS context injection types (T625)
84
+ // ---------------------------------------------------------------------------
85
+
86
+ /** A single symbol's NEXUS context entry (callers, callees, impact). */
87
+ export interface NexusSymbolContext {
88
+ /** Symbol name as resolved in the code index. */
89
+ name: string;
90
+ /** Symbol kind (function, method, class, etc.). */
91
+ kind: string;
92
+ /** File path (relative to project root) where the symbol is defined. */
93
+ filePath: string | null;
94
+ /** Symbols that call this one (direct callers). */
95
+ callers: Array<{ name: string; kind: string; filePath: string | null }>;
96
+ /** Symbols that this one calls (direct callees). */
97
+ callees: Array<{ name: string; kind: string; filePath: string | null }>;
98
+ /** Risk level from impact analysis. */
99
+ riskLevel: string;
100
+ /** Total number of transitively impacted nodes. */
101
+ totalImpacted: number;
102
+ }
103
+
104
+ /** Options for {@link buildNexusContext}. */
105
+ export interface BuildNexusContextOptions {
106
+ /** Symbols to query NEXUS for (extracted from task description). */
107
+ symbols: string[];
108
+ /** Project root directory (used to resolve project ID and run CLI commands). */
109
+ projectDir: string;
110
+ /** Maximum callers/callees to include per symbol (default: 10). */
111
+ limit?: number;
112
+ /**
113
+ * CLI timeout in milliseconds for each `cleo nexus context` call (default: 10000).
114
+ */
115
+ timeoutMs?: number;
116
+ }
117
+
118
+ /** Result of a post-modification NEXUS check (T625). */
119
+ export interface NexusModificationCheckResult {
120
+ /**
121
+ * True if the incremental re-analysis completed successfully.
122
+ * False if the analysis failed (non-fatal — agents should still continue).
123
+ */
124
+ success: boolean;
125
+ /** Files that were re-indexed. */
126
+ reindexedFiles: string[];
127
+ /** Relations that exist in the post-check index but not in the snapshot. */
128
+ newRelations: number;
129
+ /** Relations that existed in the snapshot but are missing after re-analysis. */
130
+ removedRelations: number;
131
+ /** Human-readable summary of what changed. */
132
+ summary: string;
133
+ /** Any regressions detected (broken call chains, missing symbols). */
134
+ regressions: string[];
70
135
  }
71
136
 
72
137
  // ---------------------------------------------------------------------------
@@ -462,6 +527,320 @@ export async function buildIdentityBootstrap(projectDir: string): Promise<string
462
527
  return sections.join('\n');
463
528
  }
464
529
 
530
+ // ---------------------------------------------------------------------------
531
+ // NEXUS context injection (T625)
532
+ // ---------------------------------------------------------------------------
533
+
534
+ /**
535
+ * Extract candidate symbol names from a task description or title.
536
+ *
537
+ * Uses a simple heuristic: camelCase, PascalCase, and snake_case tokens
538
+ * longer than 3 characters that look like code identifiers.
539
+ *
540
+ * Pure function — no I/O.
541
+ *
542
+ * @param text - Raw text to scan for symbol names (task title + description).
543
+ * @returns Deduplicated array of candidate symbol names, longest first.
544
+ */
545
+ export function extractSymbolsFromText(text: string): string[] {
546
+ // Match camelCase, PascalCase, snake_case identifiers of length >= 4
547
+ const identifierPattern =
548
+ /\b([A-Z][a-zA-Z0-9]{3,}|[a-z][a-zA-Z0-9]{3,}[A-Z][a-zA-Z0-9]*|[a-z]{2,}_[a-z][a-zA-Z0-9_]*)\b/g;
549
+ const seen = new Set<string>();
550
+ const matches: string[] = [];
551
+
552
+ for (const matchResult of text.matchAll(identifierPattern)) {
553
+ const symbol = matchResult[1];
554
+ if (symbol && !seen.has(symbol)) {
555
+ seen.add(symbol);
556
+ matches.push(symbol);
557
+ }
558
+ }
559
+
560
+ // Sort longest first (more specific symbols first)
561
+ return matches.sort((a, b) => b.length - a.length).slice(0, 8);
562
+ }
563
+
564
+ /**
565
+ * Build a NEXUS code intelligence context block for a set of symbols.
566
+ *
567
+ * Queries `cleo nexus context <symbol> --json` for each symbol and
568
+ * returns a formatted prompt block with callers, callees, and impact data.
569
+ *
570
+ * All CLI calls are best-effort with a configurable timeout. Failures
571
+ * for individual symbols are silently skipped.
572
+ *
573
+ * @param options - Symbols, project dir, and optional limits.
574
+ * @returns Array of resolved NEXUS symbol contexts.
575
+ * @task T625
576
+ */
577
+ export async function buildNexusContext(
578
+ options: BuildNexusContextOptions,
579
+ ): Promise<NexusSymbolContext[]> {
580
+ const { symbols, projectDir, limit = 10, timeoutMs = 10_000 } = options;
581
+ if (symbols.length === 0) return [];
582
+
583
+ const results: NexusSymbolContext[] = [];
584
+
585
+ for (const symbol of symbols) {
586
+ try {
587
+ const { stdout } = await execFileAsync(
588
+ 'cleo',
589
+ ['nexus', 'context', symbol, '--json', '--limit', String(limit)],
590
+ { timeout: timeoutMs, cwd: projectDir || undefined },
591
+ );
592
+
593
+ const parsed = JSON.parse(stdout) as {
594
+ success?: boolean;
595
+ data?: {
596
+ results?: Array<{
597
+ name?: unknown;
598
+ kind?: unknown;
599
+ filePath?: unknown;
600
+ callers?: Array<{ name?: unknown; kind?: unknown; filePath?: unknown }>;
601
+ callees?: Array<{ name?: unknown; kind?: unknown; filePath?: unknown }>;
602
+ }>;
603
+ };
604
+ };
605
+
606
+ if (!parsed.success || !parsed.data?.results?.length) continue;
607
+
608
+ // Also fetch impact for risk classification
609
+ let riskLevel = 'UNKNOWN';
610
+ let totalImpacted = 0;
611
+ try {
612
+ const { stdout: impactStdout } = await execFileAsync(
613
+ 'cleo',
614
+ ['nexus', 'impact', symbol, '--json', '--depth', '2'],
615
+ { timeout: timeoutMs, cwd: projectDir || undefined },
616
+ );
617
+ const impactParsed = JSON.parse(impactStdout) as {
618
+ success?: boolean;
619
+ data?: { riskLevel?: unknown; totalImpactedNodes?: unknown };
620
+ };
621
+ if (impactParsed.success && impactParsed.data) {
622
+ riskLevel = String(impactParsed.data.riskLevel ?? 'UNKNOWN');
623
+ totalImpacted = Number(impactParsed.data.totalImpactedNodes ?? 0);
624
+ }
625
+ } catch {
626
+ // Impact fetch failure — non-fatal, use defaults
627
+ }
628
+
629
+ const primary = parsed.data.results[0];
630
+ if (!primary) continue;
631
+
632
+ results.push({
633
+ name: String(primary.name ?? symbol),
634
+ kind: String(primary.kind ?? 'unknown'),
635
+ filePath: primary.filePath ? String(primary.filePath) : null,
636
+ callers: (primary.callers ?? []).slice(0, limit).map((c) => ({
637
+ name: String(c.name ?? ''),
638
+ kind: String(c.kind ?? 'unknown'),
639
+ filePath: c.filePath ? String(c.filePath) : null,
640
+ })),
641
+ callees: (primary.callees ?? []).slice(0, limit).map((c) => ({
642
+ name: String(c.name ?? ''),
643
+ kind: String(c.kind ?? 'unknown'),
644
+ filePath: c.filePath ? String(c.filePath) : null,
645
+ })),
646
+ riskLevel,
647
+ totalImpacted,
648
+ });
649
+ } catch {
650
+ // Symbol not found or CLI unavailable — skip
651
+ }
652
+ }
653
+
654
+ return results;
655
+ }
656
+
657
+ /**
658
+ * Format a NEXUS context array into a system-prompt block.
659
+ *
660
+ * Pure function — no I/O, safe to call in tests without a real DB.
661
+ *
662
+ * @param contexts - Resolved NEXUS symbol contexts.
663
+ * @returns Formatted prompt block, or empty string when contexts is empty.
664
+ * @task T625
665
+ */
666
+ export function buildNexusContextBlock(contexts: NexusSymbolContext[]): string {
667
+ if (contexts.length === 0) return '';
668
+
669
+ const lines: string[] = [
670
+ '',
671
+ '===== NEXUS CODE INTELLIGENCE (pre-modification context) =====',
672
+ 'Consult this before modifying any of these symbols.',
673
+ 'High-risk symbols MUST be checked for callers before editing.',
674
+ '',
675
+ ];
676
+
677
+ for (const ctx of contexts) {
678
+ lines.push(`## ${ctx.name} [${ctx.kind}]${ctx.filePath ? ` ${ctx.filePath}` : ''}`);
679
+ lines.push(` Risk: ${ctx.riskLevel} | Impacted nodes: ${ctx.totalImpacted}`);
680
+
681
+ if (ctx.callers.length > 0) {
682
+ lines.push(
683
+ ` Callers (${ctx.callers.length}): ${ctx.callers.map((c) => c.name).join(', ')}`,
684
+ );
685
+ } else {
686
+ lines.push(' Callers: none');
687
+ }
688
+
689
+ if (ctx.callees.length > 0) {
690
+ lines.push(
691
+ ` Callees (${ctx.callees.length}): ${ctx.callees.map((c) => c.name).join(', ')}`,
692
+ );
693
+ } else {
694
+ lines.push(' Callees: none');
695
+ }
696
+
697
+ lines.push('');
698
+ }
699
+
700
+ lines.push('===== END NEXUS CONTEXT =====');
701
+ return lines.join('\n');
702
+ }
703
+
704
+ /**
705
+ * Inject NEXUS context for a task into the agent prompt.
706
+ *
707
+ * Fetches the task details from CLEO, extracts symbol names from the
708
+ * task title and description, then queries NEXUS for each symbol.
709
+ * Returns a formatted prompt block or empty string on any failure.
710
+ *
711
+ * @param taskId - CLEO task ID (e.g. "T625").
712
+ * @param projectDir - Project root directory.
713
+ * @returns Formatted NEXUS context block, or "" if unavailable.
714
+ * @task T625
715
+ */
716
+ export async function buildNexusContextForTask(
717
+ taskId: string,
718
+ projectDir: string,
719
+ ): Promise<string> {
720
+ try {
721
+ // Fetch task details to extract symbols
722
+ const { stdout: taskStdout } = await execFileAsync('cleo', ['show', taskId, '--json'], {
723
+ timeout: 8_000,
724
+ cwd: projectDir || undefined,
725
+ });
726
+
727
+ const taskData = JSON.parse(taskStdout) as {
728
+ data?: { title?: string; description?: string };
729
+ };
730
+ const title = taskData?.data?.title ?? '';
731
+ const description = taskData?.data?.description ?? '';
732
+ const combinedText = `${title} ${description}`;
733
+
734
+ const symbols = extractSymbolsFromText(combinedText);
735
+ if (symbols.length === 0) return '';
736
+
737
+ const contexts = await buildNexusContext({ symbols, projectDir });
738
+ return buildNexusContextBlock(contexts);
739
+ } catch {
740
+ return '';
741
+ }
742
+ }
743
+
744
+ /**
745
+ * Run a post-modification NEXUS check on a set of changed files.
746
+ *
747
+ * Performs an incremental re-analysis of the changed files and compares
748
+ * relation counts before/after. Regressions (net loss of relations after
749
+ * modification) are flagged and returned for BRAIN storage.
750
+ *
751
+ * All operations are best-effort — never throws.
752
+ *
753
+ * @param changedFiles - Absolute paths to files that were modified.
754
+ * @param projectDir - Project root directory.
755
+ * @returns Check result with regression data.
756
+ * @task T625
757
+ */
758
+ export async function runNexusPostModificationCheck(
759
+ changedFiles: string[],
760
+ projectDir: string,
761
+ ): Promise<NexusModificationCheckResult> {
762
+ const empty: NexusModificationCheckResult = {
763
+ success: false,
764
+ reindexedFiles: [],
765
+ newRelations: 0,
766
+ removedRelations: 0,
767
+ summary: 'NEXUS post-check unavailable',
768
+ regressions: [],
769
+ };
770
+
771
+ if (changedFiles.length === 0) return empty;
772
+
773
+ try {
774
+ // 1. Snapshot current relation count before re-analysis
775
+ const { stdout: beforeStdout } = await execFileAsync('cleo', ['nexus', 'status', '--json'], {
776
+ timeout: 8_000,
777
+ cwd: projectDir || undefined,
778
+ });
779
+
780
+ let relationsBefore = 0;
781
+ try {
782
+ const beforeData = JSON.parse(beforeStdout) as {
783
+ data?: { relationCount?: number; relations?: number };
784
+ };
785
+ relationsBefore = beforeData?.data?.relationCount ?? beforeData?.data?.relations ?? 0;
786
+ } catch {
787
+ // Count unavailable — proceed anyway
788
+ }
789
+
790
+ // 2. Run incremental analysis on the project (covers changed files)
791
+ await execFileAsync('cleo', ['nexus', 'analyze', projectDir, '--incremental', '--json'], {
792
+ timeout: 60_000,
793
+ cwd: projectDir || undefined,
794
+ });
795
+
796
+ // 3. Snapshot relation count after re-analysis
797
+ const { stdout: afterStdout } = await execFileAsync('cleo', ['nexus', 'status', '--json'], {
798
+ timeout: 8_000,
799
+ cwd: projectDir || undefined,
800
+ });
801
+
802
+ let relationsAfter = 0;
803
+ try {
804
+ const afterData = JSON.parse(afterStdout) as {
805
+ data?: { relationCount?: number; relations?: number };
806
+ };
807
+ relationsAfter = afterData?.data?.relationCount ?? afterData?.data?.relations ?? 0;
808
+ } catch {
809
+ // Count unavailable
810
+ }
811
+
812
+ const delta = relationsAfter - relationsBefore;
813
+ const newRelations = Math.max(0, delta);
814
+ const removedRelations = Math.max(0, -delta);
815
+ const regressions: string[] = [];
816
+
817
+ // Flag significant relation loss as a potential regression
818
+ if (removedRelations > 5) {
819
+ regressions.push(
820
+ `Lost ${removedRelations} relations after modifying: ${changedFiles.map((f) => f.split('/').pop() ?? f).join(', ')}`,
821
+ );
822
+ }
823
+
824
+ const summary =
825
+ delta === 0
826
+ ? `NEXUS stable — no relation changes after modifying ${changedFiles.length} file(s)`
827
+ : delta > 0
828
+ ? `NEXUS: +${newRelations} new relations from ${changedFiles.length} file(s)`
829
+ : `NEXUS: -${removedRelations} relations removed from ${changedFiles.length} file(s)${regressions.length > 0 ? ' — REGRESSION DETECTED' : ''}`;
830
+
831
+ return {
832
+ success: true,
833
+ reindexedFiles: changedFiles,
834
+ newRelations,
835
+ removedRelations,
836
+ summary,
837
+ regressions,
838
+ };
839
+ } catch {
840
+ return empty;
841
+ }
842
+ }
843
+
465
844
  // ---------------------------------------------------------------------------
466
845
  // Main entry point
467
846
  // ---------------------------------------------------------------------------
@@ -478,7 +857,8 @@ export async function buildIdentityBootstrap(projectDir: string): Promise<string
478
857
  * 3. Renders the compiled system prompt
479
858
  * 4. Reads the memory bridge from `.cleo/memory-bridge.md`
480
859
  * 5. Fetches mental model observations for the named agent
481
- * 6. Concatenates: basePrompt + CANT bundle + memory bridge + mental model
860
+ * 6b. Injects NEXUS code intelligence context for the task (T625)
861
+ * 7. Concatenates: basePrompt + CANT bundle + memory bridge + mental model + NEXUS
482
862
  *
483
863
  * All operations are best-effort. If any step fails, the base prompt is
484
864
  * returned unchanged. CANT context is an enrichment, not a gate — agents
@@ -490,7 +870,7 @@ export async function buildIdentityBootstrap(projectDir: string): Promise<string
490
870
  export async function buildCantEnrichedPrompt(
491
871
  options: BuildCantEnrichedPromptOptions,
492
872
  ): Promise<string> {
493
- const { projectDir, basePrompt, agentName, isMainAgent, compiledBundle } = options;
873
+ const { projectDir, basePrompt, agentName, isMainAgent, compiledBundle, taskId } = options;
494
874
  let appendix = '';
495
875
 
496
876
  // Step 0: Identity bootstrap for the main session agent.
@@ -564,6 +944,20 @@ export async function buildCantEnrichedPrompt(
564
944
  }
565
945
  }
566
946
 
567
- // Step 6: Return enriched prompt (or unchanged basePrompt if no context found)
947
+ // Step 6b: Inject NEXUS code intelligence context for the task (T625).
948
+ // Extracts symbols from the task title+description, queries NEXUS for
949
+ // callers/callees/impact data, and injects as a pre-modification guide.
950
+ if (taskId) {
951
+ try {
952
+ const nexusBlock = await buildNexusContextForTask(taskId, projectDir);
953
+ if (nexusBlock) {
954
+ appendix += nexusBlock;
955
+ }
956
+ } catch {
957
+ // NEXUS context fetch failure — non-fatal
958
+ }
959
+ }
960
+
961
+ // Step 7: Return enriched prompt (or unchanged basePrompt if no context found)
568
962
  return appendix ? basePrompt + appendix : basePrompt;
569
963
  }
package/src/index.ts CHANGED
@@ -13,9 +13,23 @@
13
13
  * registry enable dynamic adapter loading by AdapterManager.
14
14
  */
15
15
 
16
- export type { BuildCantEnrichedPromptOptions, TierDiscoveryStats } from './cant-context.js';
16
+ export type {
17
+ BuildCantEnrichedPromptOptions,
18
+ BuildNexusContextOptions,
19
+ NexusModificationCheckResult,
20
+ NexusSymbolContext,
21
+ TierDiscoveryStats,
22
+ } from './cant-context.js';
17
23
  // Shared CANT context builder — used by all spawn providers
18
- export { buildCantEnrichedPrompt } from './cant-context.js';
24
+ export {
25
+ buildCantEnrichedPrompt,
26
+ // NEXUS code intelligence helpers (T625)
27
+ buildNexusContext,
28
+ buildNexusContextBlock,
29
+ buildNexusContextForTask,
30
+ extractSymbolsFromText,
31
+ runNexusPostModificationCheck,
32
+ } from './cant-context.js';
19
33
  // Re-export adapter classes for direct use
20
34
  // Per-provider factory functions (renamed to avoid collisions)
21
35
  export {
@@ -31,6 +45,14 @@ export {
31
45
  getSetupInstructions,
32
46
  getStatuslineConfig,
33
47
  } from './providers/claude-code/index.js';
48
+ export type { McpServerMap, McpStdioConfig, SessionEntry } from './providers/claude-sdk/index.js';
49
+ export {
50
+ ClaudeSDKSpawnProvider,
51
+ DEFAULT_TOOLS,
52
+ getServers,
53
+ resolveTools,
54
+ SessionStore,
55
+ } from './providers/claude-sdk/index.js';
34
56
  export {
35
57
  CodexAdapter,
36
58
  CodexHookProvider,
@@ -15,8 +15,10 @@ import { promisify } from 'node:util';
15
15
  import type {
16
16
  AdapterCapabilities,
17
17
  AdapterHealthStatus,
18
+ AdapterSpawnProvider,
18
19
  CLEOProviderAdapter,
19
20
  } from '@cleocode/contracts';
21
+ import { ClaudeSDKSpawnProvider } from '../claude-sdk/spawn.js';
20
22
  import { ClaudeCodeContextMonitorProvider } from './context-monitor.js';
21
23
  import { ClaudeCodeHookProvider } from './hooks.js';
22
24
  import { ClaudeCodeInstallProvider } from './install.js';
@@ -84,8 +86,14 @@ export class ClaudeCodeAdapter implements CLEOProviderAdapter {
84
86
 
85
87
  /** Hook provider for CAAMP event mapping and registration. */
86
88
  hooks: ClaudeCodeHookProvider;
87
- /** Spawn provider for launching subagent processes via `claude` CLI. */
88
- spawn: ClaudeCodeSpawnProvider;
89
+ /**
90
+ * Spawn provider for launching subagent processes.
91
+ *
92
+ * Defaults to `ClaudeCodeSpawnProvider` (CLI mode). When
93
+ * `provider.claude.mode === 'sdk'` is set in CLEO config, the adapter
94
+ * swaps this for `ClaudeSDKSpawnProvider` at initialization time.
95
+ */
96
+ spawn: AdapterSpawnProvider;
89
97
  /** Install provider for managing instruction files and plugin registration. */
90
98
  install: ClaudeCodeInstallProvider;
91
99
  /** Path provider for resolving Claude Code directory locations. */
@@ -115,8 +123,9 @@ export class ClaudeCodeAdapter implements CLEOProviderAdapter {
115
123
  /**
116
124
  * Initialize the adapter for a given project directory.
117
125
  *
118
- * Validates the environment by checking for the Claude CLI
119
- * and Claude Code configuration directory.
126
+ * Reads the CLEO config to determine the spawn mode and swaps the spawn
127
+ * provider to `ClaudeSDKSpawnProvider` when `provider.claude.mode === 'sdk'`.
128
+ * Defaults to `ClaudeCodeSpawnProvider` (CLI) for backwards compatibility.
120
129
  *
121
130
  * @param projectDir - Root directory of the project
122
131
  */
@@ -124,6 +133,34 @@ export class ClaudeCodeAdapter implements CLEOProviderAdapter {
124
133
  this.projectDir = projectDir;
125
134
  this.initialized = true;
126
135
 
136
+ // Determine spawn mode from CLEO config. Best-effort: any error falls
137
+ // back to the default CLI provider.
138
+ try {
139
+ const { execFile } = await import('node:child_process');
140
+ const { promisify: pfy } = await import('node:util');
141
+ const execFileAsync = pfy(execFile);
142
+ const { stdout } = await execFileAsync('cleo', [
143
+ 'config',
144
+ 'get',
145
+ 'provider.claude.mode',
146
+ '--output',
147
+ 'json',
148
+ ]);
149
+ const parsed: unknown = JSON.parse(stdout.trim());
150
+ const mode =
151
+ parsed !== null &&
152
+ typeof parsed === 'object' &&
153
+ 'data' in parsed &&
154
+ typeof (parsed as Record<string, unknown>).data === 'string'
155
+ ? (parsed as Record<string, string>).data
156
+ : undefined;
157
+ if (mode === 'sdk') {
158
+ this.spawn = new ClaudeSDKSpawnProvider();
159
+ }
160
+ } catch {
161
+ // Config unavailable or mode is default 'cli' — keep CLI provider
162
+ }
163
+
127
164
  // Activate CLEO hook bridge for this project — connects Claude Code
128
165
  // native events to CLEO's internal hook dispatch (T555).
129
166
  await this.hooks.registerNativeHooks(projectDir);
@@ -180,7 +180,7 @@ export class ClaudeCodeHookProvider implements AdapterHookProvider {
180
180
  ],
181
181
  });
182
182
 
183
- // Register PostToolUse hook → brain observation for file writes
183
+ // Register PostToolUse hook → brain observation for file writes + NEXUS post-check (T625)
184
184
  if (!hooks.PostToolUse) hooks.PostToolUse = [];
185
185
  (hooks.PostToolUse as unknown[]).push({
186
186
  matcher: 'Write|Edit',
@@ -189,6 +189,12 @@ export class ClaudeCodeHookProvider implements AdapterHookProvider {
189
189
  type: 'command',
190
190
  command: `cleo observe "File modified via $TOOL_NAME" --title "tool-use" --quiet # cleo-hook`,
191
191
  },
192
+ {
193
+ // NEXUS post-modification check: re-index changed files and flag regressions.
194
+ // $TOOL_INPUT_file_path is populated by Claude Code for Write/Edit events.
195
+ type: 'command',
196
+ command: `cleo nexus analyze --incremental --json > /dev/null 2>&1 && cleo observe "NEXUS re-indexed after $TOOL_NAME on $TOOL_INPUT_file_path" --title "nexus-post-check" --quiet # cleo-hook`,
197
+ },
192
198
  ],
193
199
  });
194
200
 
@@ -74,7 +74,7 @@ export class ClaudeCodeSpawnProvider implements AdapterSpawnProvider {
74
74
  let tmpFile: string | undefined;
75
75
 
76
76
  try {
77
- // Enrich prompt with CANT bundle, memory bridge, and mental model (T555).
77
+ // Enrich prompt with CANT bundle, memory bridge, mental model, and NEXUS context (T555, T625).
78
78
  // Best-effort: if CANT context is unavailable, the raw prompt is used.
79
79
  let enrichedPrompt = context.prompt;
80
80
  try {
@@ -83,6 +83,10 @@ export class ClaudeCodeSpawnProvider implements AdapterSpawnProvider {
83
83
  projectDir: context.workingDirectory ?? process.cwd(),
84
84
  basePrompt: context.prompt,
85
85
  agentName: (context.options?.agentName as string) ?? undefined,
86
+ // Inject NEXUS code intelligence context for the task scope (T625).
87
+ // This injects callers/callees/impact data so the agent understands
88
+ // blast radius before modifying any symbol.
89
+ taskId: context.taskId ?? undefined,
86
90
  });
87
91
  } catch {
88
92
  // CANT enrichment unavailable — use raw prompt