@compilr-dev/cli 0.5.3 → 0.5.5

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.
@@ -10,6 +10,25 @@ import { setCurrentProject, getCurrentProject } from '../../tools/project-db.js'
10
10
  import { getSkillPrompt } from '../../repl-helpers.js';
11
11
  import { getModelTier, modelMeetsTier } from '../../utils/model-tiers.js';
12
12
  import { getCurrentProjectIdForTracking, handleProjectSwitch } from './session.js';
13
+ import { loadCapabilitiesForSkill } from '../../multi-agent/capability-loader.js';
14
+ /**
15
+ * Load capabilities for a skill and warn about forbidden packs.
16
+ * Called before queuing a skill-driven agent message.
17
+ */
18
+ function loadSkillCapabilities(skillName, ctx) {
19
+ const result = loadCapabilitiesForSkill(skillName);
20
+ if (!result)
21
+ return; // No capability loading active or no requirements
22
+ if (result.forbidden.length > 0) {
23
+ const suggestions = result.forbidden
24
+ .map((f) => `${f.packId}${f.suggestedAgent ? ` (try ${f.suggestedAgent})` : ''}`)
25
+ .join(', ');
26
+ ctx.ui.print({
27
+ type: 'warning',
28
+ message: `Some capabilities are restricted: ${suggestions}`,
29
+ });
30
+ }
31
+ }
13
32
  // =============================================================================
14
33
  // Helper Functions
15
34
  // =============================================================================
@@ -335,6 +354,8 @@ export const designCommand = {
335
354
  break;
336
355
  }
337
356
  }
357
+ // Load capabilities for the design skill
358
+ loadSkillCapabilities('design', ctx);
338
359
  // Get design skill prompt
339
360
  const designPrompt = getSkillPrompt('design');
340
361
  if (!designPrompt) {
@@ -384,6 +405,8 @@ export const sketchCommand = {
384
405
  ctx.ui.print({ type: 'error', message: 'No project selected. Run /new or /projects first.' });
385
406
  return Promise.resolve(true);
386
407
  }
408
+ // Load capabilities for the sketch skill
409
+ loadSkillCapabilities('sketch', ctx);
387
410
  // Get sketch skill prompt
388
411
  const sketchPrompt = getSkillPrompt('sketch');
389
412
  if (!sketchPrompt) {
@@ -474,8 +497,9 @@ export const refineCommand = {
474
497
  }
475
498
  }
476
499
  }
477
- // Get appropriate skill prompt
500
+ // Get appropriate skill prompt and load capabilities
478
501
  const skillName = isFocusedRefine ? 'refine-item' : 'refine';
502
+ loadSkillCapabilities(skillName, ctx);
479
503
  const refinePrompt = getSkillPrompt(skillName);
480
504
  if (!refinePrompt) {
481
505
  ctx.ui.print({ type: 'error', message: `${skillName} skill not found.` });
@@ -605,7 +629,8 @@ export const buildCommand = {
605
629
  }
606
630
  }
607
631
  }
608
- // Get build skill prompt and replace placeholders
632
+ // Load capabilities and get build skill prompt
633
+ loadSkillCapabilities('build', ctx);
609
634
  let buildPrompt = getSkillPrompt('build');
610
635
  if (!buildPrompt) {
611
636
  ctx.ui.print({ type: 'error', message: 'Build skill not found.' });
@@ -683,7 +708,8 @@ export const noteCommand = {
683
708
  ctx.ui.print({ type: 'error', message: 'No project selected. Run /new or /projects first.' });
684
709
  return Promise.resolve(true);
685
710
  }
686
- // Get session-notes skill prompt
711
+ // Load capabilities and get session-notes skill prompt
712
+ loadSkillCapabilities('session-notes', ctx);
687
713
  const notePrompt = getSkillPrompt('session-notes');
688
714
  if (!notePrompt) {
689
715
  ctx.ui.print({ type: 'error', message: 'Session-notes skill not found.' });
@@ -741,7 +767,8 @@ export const prdCommand = {
741
767
  ctx.ui.print({ type: 'error', message: 'No project selected. Run /new or /projects first.' });
742
768
  return Promise.resolve(true);
743
769
  }
744
- // Get PRD skill prompt
770
+ // Load capabilities and get PRD skill prompt
771
+ loadSkillCapabilities('prd', ctx);
745
772
  const prdPrompt = getSkillPrompt('prd');
746
773
  if (!prdPrompt) {
747
774
  ctx.ui.print({ type: 'error', message: 'PRD skill not found.' });
@@ -866,6 +893,8 @@ export const scaffoldCommand = {
866
893
  else {
867
894
  skillName = 'scaffold';
868
895
  }
896
+ // Load capability packs for this skill
897
+ loadSkillCapabilities(skillName, ctx);
869
898
  // Get skill prompt and replace path placeholder
870
899
  let scaffoldPrompt = getSkillPrompt(skillName);
871
900
  if (!scaffoldPrompt) {
@@ -946,7 +975,8 @@ export const archCommand = {
946
975
  // User cancelled
947
976
  return true;
948
977
  }
949
- // Get architecture skill prompt
978
+ // Load capabilities and get architecture skill prompt
979
+ loadSkillCapabilities('architecture', ctx);
950
980
  const archPrompt = getSkillPrompt('architecture');
951
981
  if (!archPrompt) {
952
982
  ctx.ui.print({ type: 'error', message: 'Architecture skill not found.' });
Binary file
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Capability Loader — Bridge between slash commands and the CapabilityManager.
3
+ *
4
+ * Stores a module-level reference to the current CapabilityManager
5
+ * (set by agent.ts during createAgent()). Provides a high-level function
6
+ * for slash command handlers to load capability packs required by a skill.
7
+ */
8
+ import type { CapabilityManager, CapabilityLoadResult } from '@compilr-dev/sdk';
9
+ /**
10
+ * Set the current CapabilityManager instance.
11
+ * Called by createAgent() after constructing the manager.
12
+ */
13
+ export declare function setCapabilityManager(manager: CapabilityManager | null): void;
14
+ /**
15
+ * Get the current CapabilityManager instance.
16
+ * Returns null if capability loading is not active.
17
+ */
18
+ export declare function getCapabilityManager(): CapabilityManager | null;
19
+ /**
20
+ * Load capability packs required by a skill invocation.
21
+ *
22
+ * Maps the skill's required + optional tool names to capability pack IDs,
23
+ * then loads each loadable pack. Returns a result indicating what was loaded,
24
+ * what was already loaded, and what is forbidden.
25
+ *
26
+ * @param skillName - Skill name (e.g., 'design', 'build', 'refactor')
27
+ * @returns Load result, or null if capability loading is not active or skill has no requirements
28
+ */
29
+ export declare function loadCapabilitiesForSkill(skillName: string): CapabilityLoadResult | null;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Capability Loader — Bridge between slash commands and the CapabilityManager.
3
+ *
4
+ * Stores a module-level reference to the current CapabilityManager
5
+ * (set by agent.ts during createAgent()). Provides a high-level function
6
+ * for slash command handlers to load capability packs required by a skill.
7
+ */
8
+ import { SKILL_REQUIREMENTS } from './skill-requirements.js';
9
+ // =============================================================================
10
+ // Module-level state
11
+ // =============================================================================
12
+ let currentManager = null;
13
+ /**
14
+ * Set the current CapabilityManager instance.
15
+ * Called by createAgent() after constructing the manager.
16
+ */
17
+ export function setCapabilityManager(manager) {
18
+ currentManager = manager;
19
+ }
20
+ /**
21
+ * Get the current CapabilityManager instance.
22
+ * Returns null if capability loading is not active.
23
+ */
24
+ export function getCapabilityManager() {
25
+ return currentManager;
26
+ }
27
+ // =============================================================================
28
+ // Skill-based capability loading
29
+ // =============================================================================
30
+ /**
31
+ * Load capability packs required by a skill invocation.
32
+ *
33
+ * Maps the skill's required + optional tool names to capability pack IDs,
34
+ * then loads each loadable pack. Returns a result indicating what was loaded,
35
+ * what was already loaded, and what is forbidden.
36
+ *
37
+ * @param skillName - Skill name (e.g., 'design', 'build', 'refactor')
38
+ * @returns Load result, or null if capability loading is not active or skill has no requirements
39
+ */
40
+ export function loadCapabilitiesForSkill(skillName) {
41
+ if (!currentManager) {
42
+ return null;
43
+ }
44
+ if (!(skillName in SKILL_REQUIREMENTS)) {
45
+ return null;
46
+ }
47
+ const requirements = SKILL_REQUIREMENTS[skillName];
48
+ // Combine required + optional tools
49
+ const allTools = [
50
+ ...requirements.required,
51
+ ...(requirements.optional ?? []),
52
+ ];
53
+ return currentManager.loadForSkill(allTools, 'slash-command');
54
+ }
package/dist/repl-v2.js CHANGED
@@ -19,7 +19,7 @@ import { initSessionRegistry, getSessionRegistry, setActiveTerminalSessionId, ge
19
19
  import { initFileLockManager } from './multi-agent/file-locks.js';
20
20
  import { initNotificationManager, getNotificationManager } from './multi-agent/notification-manager.js';
21
21
  import { getSessionsPath } from './settings/paths.js';
22
- import { setMetaToolFilter } from './tools.js';
22
+ import { setMetaToolFilter, isMetaToolSilent, todoStore } from './tools.js';
23
23
  import { getCurrentProject } from './tools/project-db.js';
24
24
  import { TerminalUI } from './ui/terminal-ui.js';
25
25
  import * as terminal from './ui/terminal.js';
@@ -2357,7 +2357,7 @@ export class ReplV2 {
2357
2357
  this.ui.addBashCommand(event.toolUseId, command);
2358
2358
  }
2359
2359
  // Update spinner with tool name (skip silent tools)
2360
- if (!this.agent?.isToolSilent(event.name)) {
2360
+ if (!this.agent?.isToolSilent(event.name) && !isMetaToolSilent(event.name)) {
2361
2361
  if (event.name === 'task' && event.toolUseId) {
2362
2362
  const subagentType = typeof lastToolInput.subagent_type === 'string'
2363
2363
  ? lastToolInput.subagent_type
@@ -2434,30 +2434,37 @@ export class ReplV2 {
2434
2434
  this.ui.setCurrentTool(null);
2435
2435
  continue;
2436
2436
  }
2437
- // Handle todo_write - update UI state
2438
- if (toolName === 'todo_write' && toolInput && Array.isArray(toolInput.todos)) {
2439
- const todos = toolInput.todos.map((t) => {
2440
- const content = typeof t.content === 'string' ? t.content :
2441
- typeof t.title === 'string' ? t.title :
2442
- typeof t.task === 'string' ? t.task :
2443
- typeof t.description === 'string' ? t.description :
2444
- typeof t.text === 'string' ? t.text : 'Untitled task';
2445
- const status = (typeof t.status === 'string' && ['pending', 'in_progress', 'completed'].includes(t.status)
2446
- ? t.status
2447
- : 'pending');
2448
- const activeForm = typeof t.activeForm === 'string' ? t.activeForm : undefined;
2449
- const owner = typeof t.owner === 'string' ? t.owner : undefined;
2450
- const blockedBy = Array.isArray(t.blockedBy)
2451
- ? t.blockedBy.filter((n) => typeof n === 'number')
2452
- : undefined;
2453
- return { content, status, activeForm, owner, blockedBy };
2437
+ // Handle todo_write - update UI state from todoStore
2438
+ // (store is already updated by the tool's execute handler, regardless of
2439
+ // whether the tool was called directly or via the meta-registry fallback)
2440
+ if (toolName === 'todo_write') {
2441
+ // Get todos in creation order (getAll() sorts by status which breaks
2442
+ // task numbering the footer renderer handles status sorting itself)
2443
+ const storeTodos = todoStore.getAll()
2444
+ .sort((a, b) => {
2445
+ const aNum = parseInt(a.id.replace('todo-', ''), 10);
2446
+ const bNum = parseInt(b.id.replace('todo-', ''), 10);
2447
+ return aNum - bNum;
2454
2448
  });
2455
- this.ui.setTodos(todos);
2449
+ const allDone = storeTodos.length > 0 && storeTodos.every((t) => t.status === 'completed');
2450
+ if (allDone) {
2451
+ this.ui.setTodos([]);
2452
+ }
2453
+ else {
2454
+ this.ui.setTodos(storeTodos.map((t) => ({
2455
+ content: t.content,
2456
+ status: t.status,
2457
+ activeForm: t.activeForm,
2458
+ owner: t.owner,
2459
+ blockedBy: t.blockedBy,
2460
+ })));
2461
+ }
2456
2462
  this.ui.setCurrentTool(null);
2457
2463
  continue;
2458
2464
  }
2459
2465
  // Skip silent tools (no output needed)
2460
- if (this.agent?.isToolSilent(toolName)) {
2466
+ // Check both direct registry and meta-registry for the silent flag
2467
+ if (this.agent?.isToolSilent(toolName) || isMetaToolSilent(toolName)) {
2461
2468
  this.ui.setCurrentTool(null);
2462
2469
  continue;
2463
2470
  }
@@ -97,6 +97,7 @@ export declare const TOOL_NAMES: {
97
97
  readonly APP_MODEL_VALIDATE: "app_model_validate";
98
98
  readonly FACTORY_SCAFFOLD: "factory_scaffold";
99
99
  readonly FACTORY_LIST_TOOLKITS: "factory_list_toolkits";
100
+ readonly LOAD_CAPABILITY: "load_capability";
100
101
  };
101
102
  /** Type for all valid tool names */
102
103
  export type ToolName = (typeof TOOL_NAMES)[keyof typeof TOOL_NAMES];
@@ -118,6 +118,8 @@ export const TOOL_NAMES = {
118
118
  APP_MODEL_VALIDATE: 'app_model_validate',
119
119
  FACTORY_SCAFFOLD: 'factory_scaffold',
120
120
  FACTORY_LIST_TOOLKITS: 'factory_list_toolkits',
121
+ // Capability loading
122
+ LOAD_CAPABILITY: 'load_capability',
121
123
  };
122
124
  /** Array of all direct tool names (always available) */
123
125
  export const DIRECT_TOOL_NAMES = [
@@ -23,4 +23,9 @@ export declare const getToolInfoTool: Tool<object>;
23
23
  export declare const useToolTool: Tool<object>;
24
24
  export declare const metaTools: Tool<object>[];
25
25
  export declare function createMetaToolFallback(): (name: string, input: Record<string, unknown>) => Promise<ToolExecutionResult | null>;
26
+ /**
27
+ * Generate a filtered tool index showing only tools in the given set.
28
+ * Sets the registry filter, generates the index, then restores the previous filter.
29
+ */
30
+ export declare function generateFilteredToolIndex(activeToolNames: string[]): string;
26
31
  export declare const META_TOOLS_SYSTEM_PROMPT_PREFIX = "\n## Tool Index (Specialized Tools)\n\nThese tools are called **exactly like direct tools** \u2014 just use the tool name with parameters. No special syntax or wrapper needed. The system routes them automatically.\n\n**IMPORTANT \u2014 These are CLI system tools, NOT your project's backend. Never use localhost/curl to access them.**\n\n**Parameter Rules:**\n- For **zero-parameter calls**: call the tool directly (e.g., `workitem_query()`, `git_status()`).\n- For **calls WITH parameters**: you MUST call `get_tool_info(\"tool_name\")` first to get parameter details. The signatures below are summaries only \u2014 do NOT guess parameter structure from them.\n- After a failed tool call, always call `get_tool_info()` before retrying.\n\n";
@@ -44,4 +44,15 @@ export const metaTools = [listToolsTool, getToolInfoTool, useToolTool];
44
44
  export function createMetaToolFallback() {
45
45
  return metaToolSet.createFallback();
46
46
  }
47
+ /**
48
+ * Generate a filtered tool index showing only tools in the given set.
49
+ * Sets the registry filter, generates the index, then restores the previous filter.
50
+ */
51
+ export function generateFilteredToolIndex(activeToolNames) {
52
+ const previousFilter = registry.getFilter();
53
+ registry.setFilter(activeToolNames);
54
+ const index = registry.generateToolIndex();
55
+ registry.setFilter(previousFilter ? [...previousFilter] : null);
56
+ return index;
57
+ }
47
58
  export const META_TOOLS_SYSTEM_PROMPT_PREFIX = SDK_META_TOOLS_SYSTEM_PROMPT_PREFIX;
package/dist/tools.d.ts CHANGED
@@ -19,6 +19,11 @@ import { TodoStore, type Tool } from '@compilr-dev/sdk';
19
19
  export declare const todoStore: TodoStore;
20
20
  import { setMetaToolFilter, getRegisteredMetaTools } from './tools/meta-tools.js';
21
21
  export { setMetaToolFilter, getRegisteredMetaTools };
22
+ /**
23
+ * Check if a tool in the meta-registry is marked as silent.
24
+ * Used by the repl to suppress output for warm tools like todo_write/todo_read.
25
+ */
26
+ export declare function isMetaToolSilent(name: string): boolean;
22
27
  /**
23
28
  * Creates the tool registry with all available tools.
24
29
  * Returns tools as-is - the Agent.registerTools() method handles typing.
@@ -65,6 +70,12 @@ export declare function initializeMetaTools(includeWarmTools?: boolean): void;
65
70
  * This lists all meta-registry tools with their signatures.
66
71
  */
67
72
  export declare function getToolIndexForSystemPrompt(): string;
73
+ /**
74
+ * Get a filtered tool index for the system prompt.
75
+ * Only includes tools whose names are in the given set.
76
+ * Used by the capability-aware BeforeLLM hook.
77
+ */
78
+ export declare function getFilteredToolIndexForSystemPrompt(activeToolNames: string[]): string;
68
79
  /**
69
80
  * Get count of meta-registry tools.
70
81
  */
package/dist/tools.js CHANGED
@@ -129,9 +129,18 @@ import { guideTool } from './tools/guide-tool.js';
129
129
  // DB tools for project, work item, document, plan, backlog, anchor, artifact, and episode management
130
130
  import { allDbTools, allFactoryTools } from './tools/db-tools.js';
131
131
  // Meta-tools for dynamic tool loading
132
- import { initializeMetaToolRegistry, generateToolIndex, getMetaToolCount, META_TOOLS_SYSTEM_PROMPT_PREFIX, setMetaToolFilter, getRegisteredMetaTools, createMetaToolFallback, getToolInfoTool, } from './tools/meta-tools.js';
132
+ import { initializeMetaToolRegistry, generateToolIndex, generateFilteredToolIndex, getMetaToolCount, META_TOOLS_SYSTEM_PROMPT_PREFIX, setMetaToolFilter, getRegisteredMetaTools, createMetaToolFallback, getToolInfoTool, } from './tools/meta-tools.js';
133
133
  // Re-export for use in agent.ts
134
134
  export { setMetaToolFilter, getRegisteredMetaTools };
135
+ /**
136
+ * Check if a tool in the meta-registry is marked as silent.
137
+ * Used by the repl to suppress output for warm tools like todo_write/todo_read.
138
+ */
139
+ export function isMetaToolSilent(name) {
140
+ const tools = getRegisteredMetaTools();
141
+ const tool = tools.find((t) => t.definition.name === name);
142
+ return tool?.silent === true;
143
+ }
135
144
  // =============================================================================
136
145
  // DIRECT TOOLS - Always available, called by name
137
146
  // =============================================================================
@@ -315,6 +324,14 @@ export function initializeMetaTools(includeWarmTools) {
315
324
  export function getToolIndexForSystemPrompt() {
316
325
  return META_TOOLS_SYSTEM_PROMPT_PREFIX + generateToolIndex();
317
326
  }
327
+ /**
328
+ * Get a filtered tool index for the system prompt.
329
+ * Only includes tools whose names are in the given set.
330
+ * Used by the capability-aware BeforeLLM hook.
331
+ */
332
+ export function getFilteredToolIndexForSystemPrompt(activeToolNames) {
333
+ return META_TOOLS_SYSTEM_PROMPT_PREFIX + generateFilteredToolIndex(activeToolNames);
334
+ }
318
335
  /**
319
336
  * Get count of meta-registry tools.
320
337
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compilr-dev/cli",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "description": "AI-powered coding assistant CLI using @compilr-dev/agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -54,11 +54,11 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "@anthropic-ai/sdk": "^0.74.0",
57
- "@compilr-dev/agents": "^0.3.22",
57
+ "@compilr-dev/agents": "^0.3.24",
58
58
  "@compilr-dev/agents-coding": "^1.0.4",
59
59
  "@compilr-dev/editor-core": "^0.0.2",
60
60
  "@compilr-dev/factory": "^0.1.12",
61
- "@compilr-dev/sdk": "^0.1.18",
61
+ "@compilr-dev/sdk": "^0.1.21",
62
62
  "@compilr-dev/ui-core": "^0.0.1",
63
63
  "@modelcontextprotocol/sdk": "^1.23.0",
64
64
  "better-sqlite3": "^12.5.0",