@magic-ingredients/tiny-brain-local 0.8.0 → 0.10.2

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 (147) hide show
  1. package/dist/agents/formatters/claude-code-formatter.d.ts +37 -0
  2. package/dist/agents/formatters/claude-code-formatter.d.ts.map +1 -0
  3. package/dist/agents/formatters/claude-code-formatter.js +217 -0
  4. package/dist/agents/formatters/claude-code-formatter.js.map +1 -0
  5. package/dist/agents/formatters/formatter-factory.d.ts +25 -0
  6. package/dist/agents/formatters/formatter-factory.d.ts.map +1 -0
  7. package/dist/agents/formatters/formatter-factory.js +61 -0
  8. package/dist/agents/formatters/formatter-factory.js.map +1 -0
  9. package/dist/agents/types.d.ts +68 -0
  10. package/dist/agents/types.d.ts.map +1 -0
  11. package/dist/agents/types.js +12 -0
  12. package/dist/agents/types.js.map +1 -0
  13. package/dist/analyser/analyzers/script-analyzer.d.ts +10 -0
  14. package/dist/analyser/analyzers/script-analyzer.d.ts.map +1 -0
  15. package/dist/analyser/analyzers/script-analyzer.js +205 -0
  16. package/dist/analyser/analyzers/script-analyzer.js.map +1 -0
  17. package/dist/analyser/detectors/base-detector.d.ts +12 -0
  18. package/dist/analyser/detectors/base-detector.d.ts.map +1 -0
  19. package/dist/analyser/detectors/base-detector.js +50 -0
  20. package/dist/analyser/detectors/base-detector.js.map +1 -0
  21. package/dist/analyser/detectors/javascript-detector.d.ts +19 -0
  22. package/dist/analyser/detectors/javascript-detector.d.ts.map +1 -0
  23. package/dist/analyser/detectors/javascript-detector.js +347 -0
  24. package/dist/analyser/detectors/javascript-detector.js.map +1 -0
  25. package/dist/analyser/index.d.ts +5 -0
  26. package/dist/analyser/index.d.ts.map +1 -0
  27. package/dist/analyser/index.js +315 -0
  28. package/dist/analyser/index.js.map +1 -0
  29. package/dist/analyser/types.d.ts +2 -0
  30. package/dist/analyser/types.d.ts.map +1 -0
  31. package/dist/analyser/types.js +2 -0
  32. package/dist/analyser/types.js.map +1 -0
  33. package/dist/analyser/utils.d.ts +5 -0
  34. package/dist/analyser/utils.d.ts.map +1 -0
  35. package/dist/analyser/utils.js +24 -0
  36. package/dist/analyser/utils.js.map +1 -0
  37. package/dist/cli/cli-factory.d.ts.map +1 -1
  38. package/dist/cli/cli-factory.js +17 -0
  39. package/dist/cli/cli-factory.js.map +1 -1
  40. package/dist/cli/commands/analyse.command.d.ts +7 -0
  41. package/dist/cli/commands/analyse.command.d.ts.map +1 -0
  42. package/dist/cli/commands/analyse.command.js +130 -0
  43. package/dist/cli/commands/analyse.command.js.map +1 -0
  44. package/dist/cli/commands/status.command.d.ts.map +1 -1
  45. package/dist/cli/commands/status.command.js +3 -1
  46. package/dist/cli/commands/status.command.js.map +1 -1
  47. package/dist/core/mcp-server.d.ts +10 -8
  48. package/dist/core/mcp-server.d.ts.map +1 -1
  49. package/dist/core/mcp-server.js +93 -85
  50. package/dist/core/mcp-server.js.map +1 -1
  51. package/dist/index.d.ts +3 -0
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +33 -8
  54. package/dist/index.js.map +1 -1
  55. package/dist/prompts/persona/persona.prompt.js +8 -8
  56. package/dist/prompts/persona/persona.prompt.js.map +1 -1
  57. package/dist/prompts/planning/planning.prompt.d.ts +0 -8
  58. package/dist/prompts/planning/planning.prompt.d.ts.map +1 -1
  59. package/dist/prompts/planning/planning.prompt.js +0 -175
  60. package/dist/prompts/planning/planning.prompt.js.map +1 -1
  61. package/dist/services/agent-installation-service.d.ts +101 -0
  62. package/dist/services/agent-installation-service.d.ts.map +1 -0
  63. package/dist/services/agent-installation-service.js +328 -0
  64. package/dist/services/agent-installation-service.js.map +1 -0
  65. package/dist/services/agent-manager.d.ts +45 -0
  66. package/dist/services/agent-manager.d.ts.map +1 -0
  67. package/dist/services/agent-manager.js +154 -0
  68. package/dist/services/agent-manager.js.map +1 -0
  69. package/dist/services/agent-service.d.ts +70 -0
  70. package/dist/services/agent-service.d.ts.map +1 -0
  71. package/dist/services/agent-service.js +273 -0
  72. package/dist/services/agent-service.js.map +1 -0
  73. package/dist/services/analyse-service.d.ts +97 -0
  74. package/dist/services/analyse-service.d.ts.map +1 -0
  75. package/dist/services/analyse-service.js +370 -0
  76. package/dist/services/analyse-service.js.map +1 -0
  77. package/dist/services/dashboard-launcher.service.d.ts +20 -0
  78. package/dist/services/dashboard-launcher.service.d.ts.map +1 -0
  79. package/dist/services/dashboard-launcher.service.js +30 -0
  80. package/dist/services/dashboard-launcher.service.js.map +1 -0
  81. package/dist/services/persona-enhancer.d.ts +52 -0
  82. package/dist/services/persona-enhancer.d.ts.map +1 -0
  83. package/dist/services/persona-enhancer.js +252 -0
  84. package/dist/services/persona-enhancer.js.map +1 -0
  85. package/dist/services/persona-grouper.d.ts +29 -0
  86. package/dist/services/persona-grouper.d.ts.map +1 -0
  87. package/dist/services/persona-grouper.js +111 -0
  88. package/dist/services/persona-grouper.js.map +1 -0
  89. package/dist/services/persona-service.d.ts +52 -0
  90. package/dist/services/persona-service.d.ts.map +1 -0
  91. package/dist/services/{enhanced-persona-service.js → persona-service.js} +125 -7
  92. package/dist/services/persona-service.js.map +1 -0
  93. package/dist/services/remote/auth-token-service.d.ts.map +1 -1
  94. package/dist/services/remote/auth-token-service.js +10 -3
  95. package/dist/services/remote/auth-token-service.js.map +1 -1
  96. package/dist/services/remote/system-persona-service.d.ts.map +1 -1
  97. package/dist/services/remote/system-persona-service.js +41 -10
  98. package/dist/services/remote/system-persona-service.js.map +1 -1
  99. package/dist/services/repo-service.d.ts +195 -0
  100. package/dist/services/repo-service.d.ts.map +1 -0
  101. package/dist/services/repo-service.js +1023 -0
  102. package/dist/services/repo-service.js.map +1 -0
  103. package/dist/services/types/persona-types.d.ts +84 -0
  104. package/dist/services/types/persona-types.d.ts.map +1 -0
  105. package/dist/services/types/persona-types.js +5 -0
  106. package/dist/services/types/persona-types.js.map +1 -0
  107. package/dist/services/versioning-service.d.ts +79 -0
  108. package/dist/services/versioning-service.d.ts.map +1 -0
  109. package/dist/services/versioning-service.js +191 -0
  110. package/dist/services/versioning-service.js.map +1 -0
  111. package/dist/storage/local-filesystem-adapter.d.ts +1 -0
  112. package/dist/storage/local-filesystem-adapter.d.ts.map +1 -1
  113. package/dist/storage/local-filesystem-adapter.js +47 -3
  114. package/dist/storage/local-filesystem-adapter.js.map +1 -1
  115. package/dist/storage/platform-config-adapter.d.ts +9 -0
  116. package/dist/storage/platform-config-adapter.d.ts.map +1 -1
  117. package/dist/storage/platform-config-adapter.js +55 -1
  118. package/dist/storage/platform-config-adapter.js.map +1 -1
  119. package/dist/tools/analyse.tool.d.ts +17 -0
  120. package/dist/tools/analyse.tool.d.ts.map +1 -0
  121. package/dist/tools/analyse.tool.js +124 -0
  122. package/dist/tools/analyse.tool.js.map +1 -0
  123. package/dist/tools/persona/as.tool.d.ts +32 -11
  124. package/dist/tools/persona/as.tool.d.ts.map +1 -1
  125. package/dist/tools/persona/as.tool.js +452 -317
  126. package/dist/tools/persona/as.tool.js.map +1 -1
  127. package/dist/tools/persona/persona.tool.js +2 -2
  128. package/dist/tools/persona/persona.tool.js.map +1 -1
  129. package/dist/tools/plan/plan.tool.d.ts +3 -3
  130. package/dist/tools/plan/plan.tool.d.ts.map +1 -1
  131. package/dist/tools/plan/plan.tool.js +78 -55
  132. package/dist/tools/plan/plan.tool.js.map +1 -1
  133. package/dist/tools/tool-registry.d.ts.map +1 -1
  134. package/dist/tools/tool-registry.js +4 -0
  135. package/dist/tools/tool-registry.js.map +1 -1
  136. package/dist/utils/repo-utils.d.ts +10 -0
  137. package/dist/utils/repo-utils.d.ts.map +1 -0
  138. package/dist/utils/repo-utils.js +55 -0
  139. package/dist/utils/repo-utils.js.map +1 -0
  140. package/package.json +6 -2
  141. package/dist/services/enhanced-persona-service.d.ts +0 -22
  142. package/dist/services/enhanced-persona-service.d.ts.map +0 -1
  143. package/dist/services/enhanced-persona-service.js.map +0 -1
  144. package/dist/services/plan-watcher.service.d.ts +0 -141
  145. package/dist/services/plan-watcher.service.d.ts.map +0 -1
  146. package/dist/services/plan-watcher.service.js +0 -1010
  147. package/dist/services/plan-watcher.service.js.map +0 -1
@@ -1,34 +1,66 @@
1
1
  import { z } from 'zod';
2
2
  import { createErrorResult } from '../index.js';
3
- import { createDefaultProfile, PlanningService, RulesService, extractRulesFromProfile, extractDetailsFromProfile } from '@magic-ingredients/tiny-brain-core';
4
- import { EnhancedPersonaService } from '../../services/enhanced-persona-service.js';
5
- // Schema for 'as' tool
3
+ import { createDefaultProfile, PlanningService, RulesService, LibraryClient, parsePersonaMarkdown } from '@magic-ingredients/tiny-brain-core';
4
+ import { PersonaService } from '../../services/persona-service.js';
5
+ import { RepoService } from '../../services/repo-service.js';
6
+ import { AgentService } from '../../services/agent-service.js';
6
7
  const AsArgsSchema = z.object({
7
- personaName: z.string().optional(), // Make optional for whoami functionality
8
- createIfMissing: z.boolean().optional(),
8
+ personaName: z.string().optional(),
9
9
  confirmCreate: z.boolean().optional(),
10
10
  });
11
+ // Removed conversions - TBR accepts TBL's RepoAnalysis format directly
11
12
  /**
12
- * Top-level 'as' tool for quick persona switching
13
- * This is a convenience tool that mirrors the functionality of the persona tool
14
- * but is exposed at the top level for easier access
13
+ * AS Tool - Switch personas with repository-specific agent enhancement
14
+ *
15
+ * Flow:
16
+ * 1. Show current persona (if no name provided)
17
+ * 2. Load local persona OR import from library
18
+ * 3. Enhance imported personas with agents
19
+ * 4. Activate persona in context
20
+ * 5. Return formatted response
15
21
  */
16
22
  export class AsTool {
17
23
  static getToolDefinition() {
18
24
  return {
19
25
  name: 'as',
20
- description: "🎭 PERSONA SWITCHER 🎭\n\n⚡ INSTANT CONTEXT: Switch to or create a persona for context-aware conversations.\n\n✅ FEATURES:\n • Activate existing personas instantly\n • Create new personas with confirmation prompt\n • Auto-load persona's associated files\n • Maintain conversation context\n • Show current persona (whoami) when called without parameters\n\n💡 USAGE:\n • as <persona_name> [createIfMissing: true] - Switch to persona\n • as - Show current persona details\n\n🛡️ SAFETY: New personas require confirmation to prevent accidental creation\n\n🎯 USE CASES:\n • Project-specific contexts\n • Different communication styles\n • Specialized knowledge domains",
26
+ description: `🎭 PERSONA SWITCHER 🎭
27
+
28
+ ⚡ INSTANT CONTEXT: Switch to or create a persona for context-aware conversations.
29
+
30
+ ✅ FEATURES:
31
+ • Activate existing personas instantly
32
+ • Create new personas with confirmation prompt
33
+ • Rename existing personas
34
+ • Import from library with repository analysis
35
+ • Show current persona when called without parameters
36
+
37
+ 💡 USAGE:
38
+ • as - Show current persona (whoami)
39
+ • as <persona_name> - Switch to persona
40
+ • as <persona_name> <new_name> - Rename persona
41
+ • as library:<name> - Import from library
42
+ • as library:<name> <local_name> - Import and rename
43
+
44
+ 📝 EXAMPLES:
45
+ • as - Show current active persona
46
+ • as developer - Switch to 'developer' persona
47
+ • as developer fred - Rename 'developer' to 'fred'
48
+ • as library:developer - Import 'developer' from library
49
+ • as library:developer fred - Import as 'fred' locally
50
+
51
+ 🛡️ SAFETY: New personas require confirmation to prevent accidental creation
52
+
53
+ 🎯 USE CASES:
54
+ • Project-specific contexts
55
+ • Different communication styles
56
+ • Specialized knowledge domains
57
+ • Repository-aware agent installation`,
21
58
  inputSchema: {
22
59
  type: 'object',
23
60
  properties: {
24
61
  personaName: {
25
62
  type: 'string',
26
- description: 'Name of the persona to switch to (optional - omit to show current persona)',
27
- },
28
- createIfMissing: {
29
- type: 'boolean',
30
- description: "Create the persona if it doesn't exist",
31
- default: false,
63
+ description: 'Persona command: name to switch to, "name newName" to rename, or "library:name [localName]" to import',
32
64
  },
33
65
  confirmCreate: {
34
66
  type: 'boolean',
@@ -40,150 +72,24 @@ export class AsTool {
40
72
  },
41
73
  };
42
74
  }
75
+ /**
76
+ * Main execution entry point
77
+ */
43
78
  static async execute(args, context) {
79
+ context.logger.debug('[As tool] [execute]');
80
+ // Cast to LocalRequestContext to access auth token
81
+ const localContext = context;
44
82
  try {
45
83
  const validatedArgs = AsArgsSchema.parse(args);
46
- // If no personaName provided, return current persona (whoami functionality)
84
+ // Handle "whoami" functionality
47
85
  if (!validatedArgs.personaName) {
48
- // Check if there's an active persona in context
49
- if (!context.activePersona || !context.activePersona.id) {
50
- return createErrorResult('No active persona. Use "as <persona_name>" to switch to a persona.');
51
- }
52
- const currentPersonaName = context.activePersona.id;
53
- // Load optimized context for the current persona
54
- // Note: No need to switch persona here since we're just showing current state
55
- const optimizedResponse = await AsTool.loadOptimizedContext(currentPersonaName, context);
56
- return {
57
- content: [
58
- {
59
- type: 'text',
60
- text: JSON.stringify(optimizedResponse, null, 2),
61
- },
62
- ],
63
- isError: false,
64
- };
65
- }
66
- // Create enhanced service with RequestContext
67
- const service = new EnhancedPersonaService(context);
68
- // Load persona using new pattern with explicit args
69
- let personaProfile = await service.loadPersona({
70
- personaName: validatedArgs.personaName
71
- });
72
- if (!personaProfile) {
73
- // Try to import from library if not found locally
74
- try {
75
- const importedName = await service.importLibraryPersonaByName(validatedArgs.personaName);
76
- if (importedName) {
77
- // Successfully imported from library - message can be shown later if needed
78
- // const message = importedName !== validatedArgs.personaName
79
- // ? `Imported library persona as '${importedName}' (name conflict with existing local persona)`
80
- // : `Imported library persona '${importedName}'`;
81
- // Load the newly imported persona
82
- personaProfile = await service.loadPersona({
83
- personaName: importedName
84
- });
85
- // Update the args to use the actual imported name
86
- validatedArgs.personaName = importedName;
87
- }
88
- else {
89
- // Not found in library either (or not authenticated), check if we should create
90
- const availablePersonas = await service.listPersonas({});
91
- const availableList = availablePersonas.length > 0
92
- ? `Available personas: ${availablePersonas.join(', ')}`
93
- : 'No personas available.';
94
- if (!validatedArgs.createIfMissing) {
95
- return createErrorResult(`Persona '${validatedArgs.personaName}' not found. ${availableList} Use createIfMissing: true to create it.`);
96
- }
97
- }
98
- }
99
- catch {
100
- // If library import fails for any reason, fall back to local-only behavior
101
- const availablePersonas = await service.listPersonas({});
102
- const availableList = availablePersonas.length > 0
103
- ? `Available personas: ${availablePersonas.join(', ')}`
104
- : 'No personas available.';
105
- if (!validatedArgs.createIfMissing) {
106
- return createErrorResult(`Persona '${validatedArgs.personaName}' not found. ${availableList} Use createIfMissing: true to create it.`);
107
- }
108
- }
109
- }
110
- // Continue with existing logic if persona still doesn't exist and createIfMissing is true
111
- if (!personaProfile) {
112
- // Get available personas for display
113
- const availablePersonas = await service.listPersonas({});
114
- // If createIfMissing is true but no explicit confirmation, prompt for it
115
- if (validatedArgs.createIfMissing && !validatedArgs.confirmCreate) {
116
- return {
117
- content: [
118
- {
119
- type: 'text',
120
- text: JSON.stringify({
121
- requiresConfirmation: true,
122
- action: 'createPersona',
123
- personaName: validatedArgs.personaName,
124
- message: `Persona "${validatedArgs.personaName}" doesn't exist. Would you like to create it?`,
125
- availablePersonas: availablePersonas.length > 0 ? availablePersonas : undefined,
126
- hint: 'To confirm creation, call the tool again with confirmCreate: true',
127
- }, null, 2),
128
- },
129
- ],
130
- isError: false,
131
- };
132
- }
133
- }
134
- if (!personaProfile && validatedArgs.createIfMissing && validatedArgs.confirmCreate) {
135
- // Create new persona with confirmation
136
- const profileString = createDefaultProfile(validatedArgs.personaName, new Date().toISOString());
137
- // Create persona using new pattern with explicit args
138
- await service.createPersona({
139
- personaName: validatedArgs.personaName,
140
- profile: profileString
141
- });
142
- // Load the created persona to get the full PersonaProfile
143
- personaProfile = await service.loadPersona({
144
- personaName: validatedArgs.personaName
145
- });
146
- }
147
- // Set the active persona using the callback for subsequent tools
148
- if (personaProfile && context.updateActivePersona) {
149
- // Create a minimal PersonaProfile object for the context
150
- const activePersona = {
151
- id: validatedArgs.personaName,
152
- userContent: {
153
- profile: typeof personaProfile.profile === 'string' ? personaProfile.profile : undefined,
154
- },
155
- };
156
- context.logger.info(`AS tool calling updateActivePersona with persona: ${validatedArgs.personaName}`);
157
- context.updateActivePersona(activePersona);
158
- context.logger.info(`AS tool completed updateActivePersona call`);
159
- // Load optimized persona context for AI AFTER persona is switched
160
- const optimizedResponse = await AsTool.loadOptimizedContext(validatedArgs.personaName, context);
161
- // Return clean context for AI (no operational metadata)
162
- return {
163
- content: [
164
- {
165
- type: 'text',
166
- text: JSON.stringify(optimizedResponse, null, 2),
167
- },
168
- ],
169
- isError: false,
170
- };
171
- }
172
- else {
173
- context.logger.warn(`AS tool: Cannot update active persona - ${!personaProfile ? 'no profile' : 'no callback'}`);
174
- // Load optimized persona context for AI (fallback without persona switch)
175
- const optimizedResponse = await AsTool.loadOptimizedContext(validatedArgs.personaName, context);
176
- // Return clean context for AI (no operational metadata)
177
- return {
178
- content: [
179
- {
180
- type: 'text',
181
- text: JSON.stringify(optimizedResponse, null, 2),
182
- },
183
- ],
184
- isError: false,
185
- };
86
+ return AsTool.handleShowCurrentPersona(context);
186
87
  }
88
+ // Switch to specified persona (pass localContext)
89
+ return AsTool.handleSwitchPersona({
90
+ personaName: validatedArgs.personaName,
91
+ confirmCreate: validatedArgs.confirmCreate
92
+ }, localContext);
187
93
  }
188
94
  catch (error) {
189
95
  if (error instanceof z.ZodError) {
@@ -193,194 +99,423 @@ export class AsTool {
193
99
  }
194
100
  }
195
101
  /**
196
- * Load optimized context focused on actionable AI guidance
102
+ * Handle showing the current active persona
197
103
  */
198
- static async loadOptimizedContext(personaName, context) {
199
- try {
200
- // 1. Load Rules (cross-persona directives)
201
- const rulesService = new RulesService(context);
202
- const rules = await rulesService.loadRulesForUser(context.userId);
203
- // Add default rules if none exist
204
- if (rules.length === 0) {
205
- // No default rules - let the user define their own
206
- context.logger.debug('No rules defined for user');
104
+ static async handleShowCurrentPersona(context) {
105
+ context.logger.debug('No persona specified: showing current persona');
106
+ if (!context.activePersona?.id) {
107
+ // Not an error - just helpful guidance
108
+ const service = new PersonaService(context);
109
+ const availablePersonas = await service.listPersonas({});
110
+ let helpMessage = '## No Active Persona\n\n';
111
+ helpMessage += 'No persona is currently selected.\n\n';
112
+ if (availablePersonas.length > 0) {
113
+ helpMessage += '### Available Personas:\n';
114
+ availablePersonas.forEach(persona => {
115
+ helpMessage += `- ${persona}\n`;
116
+ });
117
+ helpMessage += '\n';
207
118
  }
208
- // 2. Load Persona Profile and Patterns
209
- const service = new EnhancedPersonaService(context);
210
- const fullContext = await service.loadPersona({ personaName });
211
- if (!fullContext) {
212
- throw new Error(`Persona ${personaName} not found`);
119
+ helpMessage += '### How to use:\n';
120
+ helpMessage += '- `as <persona_name>` - Switch to a persona\n';
121
+ helpMessage += '- `as library:<name>` - Import from library\n';
122
+ helpMessage += '- `manage_personas list` - See all personas with details\n';
123
+ return {
124
+ content: [{
125
+ type: 'text',
126
+ text: helpMessage,
127
+ }],
128
+ isError: false,
129
+ };
130
+ }
131
+ // Load and parse the current persona
132
+ const service = new PersonaService(context);
133
+ const persona = await service.loadPersona({ personaName: context.activePersona.id });
134
+ if (!persona?.profile) {
135
+ return createErrorResult(`Failed to load current persona '${context.activePersona.id}'`);
136
+ }
137
+ const parsedPersona = parsePersonaMarkdown(typeof persona.profile === 'string' ? persona.profile : JSON.stringify(persona.profile));
138
+ // Override with actual persona name from context (id is the persona name)
139
+ if (context.activePersona?.id) {
140
+ parsedPersona.name = context.activePersona.id;
141
+ }
142
+ // Get context file path for agent info
143
+ const agentService = new AgentService(context);
144
+ const formatter = agentService.getDefaultFormatter();
145
+ const contextFilePath = formatter.getRepoContextFilePath();
146
+ const response = await AsTool.buildResponse(context, parsedPersona, contextFilePath);
147
+ return {
148
+ content: [{
149
+ type: 'text',
150
+ text: AsTool.formatResponse(response, contextFilePath),
151
+ }],
152
+ isError: false,
153
+ };
154
+ }
155
+ /**
156
+ * Handle persona switch/creation with clear flow
157
+ */
158
+ static async handleSwitchPersona(args, context) {
159
+ context.logger.debug(`[As tool] [handleSwitchPersona: ${args.personaName} ]`);
160
+ const fullPersonaName = args.personaName;
161
+ // Check if this is a library persona request
162
+ if (fullPersonaName.startsWith('library:')) {
163
+ return AsTool.handleLibraryPersona(fullPersonaName, context);
164
+ }
165
+ // Parse potential rename: "personaName newName"
166
+ const parts = fullPersonaName.split(/\s+/);
167
+ const personaName = parts[0];
168
+ const newName = parts.length > 1 ? parts.slice(1).join(' ') : null;
169
+ // Local persona flow
170
+ const personaService = new PersonaService(context);
171
+ const exists = await personaService.exists(personaName);
172
+ if (exists) {
173
+ context.logger.debug(`[As tool] [handleSwitchPersona] persona already exists`);
174
+ // Check if this is a rename operation
175
+ if (newName) {
176
+ context.logger.debug('[As tool] [handleSwitchPersona] found persona name and rename');
177
+ return AsTool.handlePersonaRename(personaName, newName, personaService, context);
213
178
  }
214
- const profileContent = typeof fullContext.profile === 'string'
215
- ? fullContext.profile
216
- : JSON.stringify(fullContext.profile, null, 2);
217
- // 3. Load Active Plan for the specific persona
218
- // Create a context with the correct persona to get the right plan
219
- const personaContext = {
220
- ...context,
221
- activePersona: {
222
- id: personaName,
223
- userContent: {
224
- profile: profileContent
225
- }
226
- }
179
+ // Switch to existing persona
180
+ context.logger.debug('[As tool] [handleSwitchPersona] switching to existing persona');
181
+ const profile = await personaService.loadPersona({ personaName });
182
+ if (!profile) {
183
+ return createErrorResult(`[As tool] [handleSwitchPersona] failed to load persona '${personaName}'`);
184
+ }
185
+ // Parse the persona
186
+ const parsedPersona = parsePersonaMarkdown(typeof profile.profile === 'string' ? profile.profile : JSON.stringify(profile.profile));
187
+ // Override the parsed name with the actual persona name from the folder
188
+ parsedPersona.name = personaName;
189
+ // Get context file path for agent info
190
+ const agentService = new AgentService(context);
191
+ const formatter = agentService.getDefaultFormatter();
192
+ const contextFilePath = formatter?.getRepoContextFilePath();
193
+ // Activate persona and return response with updated context
194
+ const updatedContext = await AsTool.activatePersona(personaName, profile, context);
195
+ const response = await AsTool.buildResponse(updatedContext, parsedPersona, contextFilePath);
196
+ return {
197
+ content: [{
198
+ type: 'text',
199
+ text: AsTool.formatResponse(response, contextFilePath),
200
+ }],
201
+ isError: false,
227
202
  };
228
- const planningService = new PlanningService(personaContext);
229
- const activePlan = await planningService.getActivePlan();
230
- let activePlanContext = null;
231
- if (activePlan) {
232
- // Get recent completed todos and next actions
233
- const recentProgress = [];
234
- const nextActions = [];
235
- const blockers = [];
236
- if (activePlan.phases) {
237
- activePlan.phases.forEach(phase => {
238
- if (phase.status === 'completed' && phase.todos) {
239
- phase.todos.slice(-2).forEach(todo => {
240
- if (todo.completed) {
241
- recentProgress.push(todo.content);
242
- }
243
- });
244
- }
245
- if (phase.status === 'in_progress' && phase.todos) {
246
- phase.todos.forEach(todo => {
247
- if (!todo.completed) {
248
- nextActions.push(todo.content);
249
- }
250
- });
251
- }
252
- });
253
- }
254
- // Get blockers from plan metadata
255
- if (activePlan.metadata?.blockers) {
256
- activePlan.metadata.blockers.forEach(blocker => {
257
- blockers.push(blocker.description);
258
- });
203
+ }
204
+ else {
205
+ // Persona doesn't exist - confirm creation
206
+ return AsTool.handlePersonaNotFound({ personaName, confirmCreate: args.confirmCreate }, personaService, context);
207
+ }
208
+ }
209
+ /**
210
+ * Handle library persona import - simplified to only import persona
211
+ */
212
+ static async handleLibraryPersona(personaName, context) {
213
+ // Parse library:sourceName targetName
214
+ // e.g., "library:developer fred" → fetch "developer", create as "fred"
215
+ const afterPrefix = personaName.substring('library:'.length).trim();
216
+ const parts = afterPrefix.split(/\s+/);
217
+ const libraryPersonaName = parts[0];
218
+ const localPersonaName = parts.slice(1).join(' ') || libraryPersonaName;
219
+ if (!libraryPersonaName) {
220
+ return createErrorResult('Invalid library persona format. Use: library:<persona_name> [local_name]');
221
+ }
222
+ // Check auth
223
+ if (!context.authToken?.token) {
224
+ return createErrorResult('authentication required to fetch library personas');
225
+ }
226
+ try {
227
+ // Step 1: Fetch library persona
228
+ const libraryClient = new LibraryClient();
229
+ const personaResponse = await libraryClient.getPersona(libraryPersonaName, context.authToken?.token || '', false // Without agents
230
+ );
231
+ // Step 2: Create local persona
232
+ const personaService = new PersonaService(context);
233
+ // Handle both response formats
234
+ const personaData = typeof personaResponse === 'string'
235
+ ? { systemBlock: personaResponse }
236
+ : personaResponse.systemBlock
237
+ ? personaResponse
238
+ : { systemBlock: personaResponse.persona?.systemBlock || JSON.stringify(personaResponse) };
239
+ const enhancedPersona = personaService.addUserBlock(personaData, localPersonaName);
240
+ // Step 3: Create metadata
241
+ const metadata = {
242
+ id: localPersonaName,
243
+ name: localPersonaName,
244
+ description: personaResponse.description || `${personaResponse.name} persona`,
245
+ created: new Date().toISOString(),
246
+ lastUpdated: new Date().toISOString(),
247
+ sourceLibrary: {
248
+ id: personaResponse.id,
249
+ name: libraryPersonaName,
250
+ version: personaResponse.version || '1.0.0',
251
+ importedAt: new Date().toISOString()
259
252
  }
260
- const currentPhase = activePlan.phases?.find(p => p.status === 'in_progress');
261
- activePlanContext = {
262
- id: activePlan.id,
263
- title: activePlan.title || 'Untitled Plan',
264
- goal: activePlan.overview || '',
265
- current_phase: currentPhase?.title || 'Planning',
266
- next_actions: nextActions.slice(0, 5),
267
- recent_progress: recentProgress.slice(0, 5),
268
- blockers
269
- };
270
- }
271
- // 4. Extract persona-specific rules from profile
272
- const personaRules = extractRulesFromProfile(profileContent);
273
- // 5. Extract other profile sections
274
- const background = AsTool.extractBackground(profileContent);
275
- const communicationStyle = AsTool.extractCommunicationStyleText(profileContent);
276
- const details = extractDetailsFromProfile(profileContent);
277
- // 6. Build persona object with only non-empty fields
278
- const persona = {
279
- name: personaName,
280
- rules: personaRules
281
253
  };
282
- if (background)
283
- persona.background = background;
284
- if (communicationStyle)
285
- persona.communication_style = communicationStyle;
286
- if (details)
287
- persona.details = details;
288
- // Only add expertise if non-empty
289
- if (fullContext.insights?.expertise && fullContext.insights.expertise.length > 0) {
290
- persona.expertise = fullContext.insights.expertise;
291
- }
292
- // Only add learned patterns if they have content
293
- const preferences = fullContext.insights?.preferences || [];
294
- const avoidPatterns = AsTool.extractAvoidPatterns(fullContext.insights);
295
- if (preferences.length > 0 || avoidPatterns.length > 0) {
296
- persona.learned_patterns = {
297
- preferences,
298
- avoid: avoidPatterns
299
- };
254
+ // Step 4: Create the persona and get parsed result
255
+ const parsedPersona = await personaService.create(localPersonaName, enhancedPersona, metadata);
256
+ context.logger.info(`Imported library persona '${libraryPersonaName}' as '${localPersonaName}'`);
257
+ // Step 5: Activate persona
258
+ const updatedContext = await AsTool.activatePersona(localPersonaName, { profile: enhancedPersona.systemBlock }, context);
259
+ // Step 6: Check if in repository and if it needs initialization
260
+ const repoService = new RepoService(context);
261
+ let needsInitMessage = '';
262
+ if (repoService.isInRepository()) {
263
+ const initialized = await repoService.isRepositoryInitialized();
264
+ if (!initialized) {
265
+ needsInitMessage = '\n\n📝 Note: Repository not initialized. Run `analyse` to fetch recommended agents for your tech stack.';
266
+ }
300
267
  }
301
- // 7. Build response with only necessary fields
302
- const response = {
303
- RULES: rules,
304
- PERSONA: persona
268
+ // Step 7: Get context file path for agent info (if available)
269
+ const agentService = new AgentService(context);
270
+ const formatter = agentService.getDefaultFormatter();
271
+ const contextFilePath = formatter?.getRepoContextFilePath();
272
+ // Step 8: Build and return response
273
+ const response = await AsTool.buildResponse(updatedContext, parsedPersona, contextFilePath);
274
+ return {
275
+ content: [{
276
+ type: 'text',
277
+ text: AsTool.formatResponse(response, contextFilePath) + needsInitMessage,
278
+ }],
279
+ isError: false,
305
280
  };
306
- // Only add active plan if it exists
307
- if (activePlanContext) {
308
- response.ACTIVE_PLAN = activePlanContext;
309
- }
310
- return response;
311
281
  }
312
282
  catch (error) {
313
- context.logger.error(`Failed to load optimized context for ${personaName}:`, error);
314
- // Return minimal context on error
315
- return {
316
- RULES: [],
317
- PERSONA: {
318
- name: personaName,
319
- rules: []
283
+ context.logger.error(`Failed to fetch library persona: ${error}`);
284
+ return createErrorResult(`Failed to fetch library persona: ${error instanceof Error ? error.message : 'Unknown error'}`);
285
+ }
286
+ }
287
+ /**
288
+ * Activate persona in context and return updated context
289
+ */
290
+ static async activatePersona(personaName, profile, context) {
291
+ // Update the active persona in context and get the new context
292
+ if (context.updateActivePersona) {
293
+ const newContext = await context.updateActivePersona({
294
+ id: personaName,
295
+ userContent: {
296
+ profile: profile.profile || profile
320
297
  }
321
- };
298
+ });
299
+ return newContext || context; // Return new context or original if update doesn't return one
322
300
  }
301
+ return context;
323
302
  }
324
303
  /**
325
- * Extract background from profile markdown
304
+ * Build persona response data structure
326
305
  */
327
- static extractBackground(profile) {
328
- if (!profile)
329
- return undefined;
330
- const match = profile.match(/##\s+Background\s*\n([\s\S]*?)(?=\n##|\n#|$)/i);
331
- if (match && match[1]) {
332
- const background = match[1].trim();
333
- return background || undefined;
306
+ static async buildResponse(context, parsedPersona, _contextFilePath // Used in formatResponse, not here
307
+ ) {
308
+ const rulesService = new RulesService(context);
309
+ const planningService = new PlanningService(context);
310
+ // Get additional rules from rules service
311
+ const allRules = await rulesService.getAllRules(parsedPersona.name);
312
+ const additionalUserRules = allRules?.rules || [];
313
+ // Get active plan if any
314
+ let activePlan = null;
315
+ try {
316
+ activePlan = await planningService.getActivePlan();
317
+ }
318
+ catch {
319
+ context.logger.debug('No active persona for plan lookup');
320
+ }
321
+ // Note: contextFilePath is passed but not used here since we removed AGENT USAGE INSTRUCTIONS
322
+ // It's used later in formatResponse to add the reminder to re-read CLAUDE.md
323
+ const response = {
324
+ PERSONA: {
325
+ name: parsedPersona.name,
326
+ "GOLDEN RULES": additionalUserRules,
327
+ "USER RULES": parsedPersona.user?.rules || [],
328
+ "USER DETAILS": parsedPersona.user?.details || {},
329
+ "SYSTEM RULES": parsedPersona.system?.rules || [],
330
+ "SYSTEM DETAILS": parsedPersona.system?.details || {}
331
+ }
332
+ };
333
+ if (activePlan) {
334
+ // Extract just the first line or a short summary of the plan
335
+ let goalSummary = '';
336
+ if (activePlan.overview) {
337
+ const lines = activePlan.overview.split('\n').filter(l => l.trim());
338
+ goalSummary = lines[0] || activePlan.title;
339
+ // Truncate if still too long
340
+ if (goalSummary.length > 200) {
341
+ goalSummary = goalSummary.substring(0, 197) + '...';
342
+ }
343
+ }
344
+ else {
345
+ goalSummary = activePlan.title;
346
+ }
347
+ response.ACTIVE_PLAN = {
348
+ id: activePlan.id,
349
+ title: activePlan.title,
350
+ goal: goalSummary,
351
+ current_phase: activePlan.currentState?.phaseTitle || '',
352
+ next_actions: activePlan.currentState?.nextAction ? [activePlan.currentState.nextAction.description] : [],
353
+ blockers: activePlan.metadata?.blockers?.map((b) => b.description)
354
+ };
334
355
  }
335
- return undefined;
356
+ return response;
336
357
  }
337
358
  /**
338
- * Extract communication style from profile markdown
359
+ * Format the persona response as human-readable markdown with enforced workflow
339
360
  */
340
- static extractCommunicationStyleText(profile) {
341
- if (!profile)
342
- return undefined;
343
- const match = profile.match(/##\s+Communication Style\s*\n([\s\S]*?)(?=\n##|\n#|$)/i);
344
- if (match && match[1]) {
345
- const style = match[1].trim();
346
- return style || undefined;
361
+ static formatResponse(response, contextFilePath) {
362
+ let formatted = `# THIS IS YOUR CONTEXT FOR THIS SESSION - YOU MUST USE IT\n\n`;
363
+ formatted += `IMPORTANT: USER RULES and USER DETAILS ALWAYS take precedence over SYSTEM RULES and SYSTEM DETAILS. When there are conflicts, follow the user's preferences.\n\n`;
364
+ formatted += `## PERSONA: ${response.PERSONA.name}\n\n`;
365
+ // Golden Rules
366
+ if (response.PERSONA["GOLDEN RULES"] && response.PERSONA["GOLDEN RULES"].length > 0) {
367
+ formatted += `### GOLDEN RULES\n`;
368
+ response.PERSONA["GOLDEN RULES"].forEach(rule => {
369
+ formatted += `- ${rule}\n`;
370
+ });
371
+ formatted += '\n';
372
+ }
373
+ // User Rules
374
+ if (response.PERSONA["USER RULES"] && response.PERSONA["USER RULES"].length > 0) {
375
+ formatted += `### USER RULES\n`;
376
+ response.PERSONA["USER RULES"].forEach(rule => {
377
+ formatted += `- ${rule}\n`;
378
+ });
379
+ formatted += '\n';
380
+ }
381
+ // User Details
382
+ const userDetails = response.PERSONA["USER DETAILS"];
383
+ if (userDetails && Object.keys(userDetails).length > 0) {
384
+ formatted += `### USER DETAILS\n`;
385
+ Object.entries(userDetails).forEach(([key, value]) => {
386
+ formatted += `#### ${key}\n${value}\n\n`;
387
+ });
347
388
  }
348
- return undefined;
389
+ // System Rules
390
+ if (response.PERSONA["SYSTEM RULES"] && response.PERSONA["SYSTEM RULES"].length > 0) {
391
+ formatted += `### SYSTEM RULES\n`;
392
+ response.PERSONA["SYSTEM RULES"].forEach(rule => {
393
+ formatted += `- ${rule}\n`;
394
+ });
395
+ formatted += '\n';
396
+ }
397
+ // System Details
398
+ const systemDetails = response.PERSONA["SYSTEM DETAILS"];
399
+ if (systemDetails && Object.keys(systemDetails).length > 0) {
400
+ formatted += `### SYSTEM DETAILS\n`;
401
+ Object.entries(systemDetails).forEach(([key, value]) => {
402
+ formatted += `#### ${key}\n${value}\n\n`;
403
+ });
404
+ }
405
+ // Active Plan - Keep it concise
406
+ if (response.ACTIVE_PLAN) {
407
+ formatted += `### ACTIVE PLAN\n`;
408
+ formatted += `**${response.ACTIVE_PLAN.title}**\n`;
409
+ if (response.ACTIVE_PLAN.goal && response.ACTIVE_PLAN.goal !== response.ACTIVE_PLAN.title) {
410
+ formatted += `${response.ACTIVE_PLAN.goal}\n`;
411
+ }
412
+ formatted += `\n`;
413
+ formatted += `- Current Phase: ${response.ACTIVE_PLAN.current_phase || 'Not specified'}\n`;
414
+ if (response.ACTIVE_PLAN.next_actions && response.ACTIVE_PLAN.next_actions.length > 0) {
415
+ formatted += `- Next Action: ${response.ACTIVE_PLAN.next_actions[0]}\n`;
416
+ }
417
+ if (response.ACTIVE_PLAN.blockers && response.ACTIVE_PLAN.blockers.length > 0) {
418
+ formatted += `- Blockers: ${response.ACTIVE_PLAN.blockers.length} active\n`;
419
+ }
420
+ }
421
+ // REFLECTION REQUIREMENT
422
+ formatted += `\n## 🔍 MANDATORY REFLECTION - AFTER EVERY TOOL USE\n\n`;
423
+ formatted += `After using ANY tool, you MUST ask yourself:\n`;
424
+ formatted += `- Was this the right phase for this action?\n`;
425
+ formatted += `- Should I have used a specialized agent instead?\n`;
426
+ formatted += `- Am I following the checklist workflow?\n`;
427
+ formatted += `- Does this align with the user's actual request?\n\n`;
428
+ // Add reminder to re-read context file if in a repo with agents installed
429
+ if (contextFilePath) {
430
+ formatted += `---\n\n`;
431
+ formatted += `**⚡ AGENT ORCHESTRATION REQUIRED**\n\n`;
432
+ formatted += `**RE-READ ${contextFilePath} to understand available agents for this repository.**\n`;
433
+ formatted += `**DEFAULT BEHAVIOR**: Use Task tool with specialized agents\n`;
434
+ formatted += `**EXCEPTION**: Only skip agents if you can explicitly justify why none apply\n\n`;
435
+ formatted += `**MANDATORY CONFIRMATION:** When ready to proceed, you MUST respond with exactly this format (copy it exactly, do not modify):\n\n`;
436
+ formatted += `\`\`\`\n`;
437
+ formatted += ` ✅ Loaded context\n`;
438
+ formatted += ` ✅ Re-read ${contextFilePath}\n`;
439
+ formatted += ` ✅ Completed pre-flight checklist\n`;
440
+ formatted += ` ✅ Ready for agent-first workflow\n`;
441
+ formatted += ` \n\n`;
442
+ formatted += ` I've switched to your **${response.PERSONA.name}** persona.\n`;
443
+ formatted += ` \n`;
444
+ formatted += ` 🧠 Dashboard available at: [http://localhost:8765](http://localhost:8765)\n`;
445
+ formatted += `\`\`\`\n\n`;
446
+ }
447
+ else {
448
+ // Add persona switch confirmation message when no context file
449
+ formatted += `I've switched to your **${response.PERSONA.name}** persona.\n\n`;
450
+ formatted += `🧠 Dashboard available at: [http://localhost:8765](http://localhost:8765)\n`;
451
+ }
452
+ return formatted;
349
453
  }
350
454
  /**
351
- * Extract patterns to avoid from insights
455
+ * Handle renaming an existing persona
352
456
  */
353
- static extractAvoidPatterns(insights) {
354
- if (!insights || !insights.patterns)
355
- return [];
356
- const avoidPatterns = [];
357
- // Look for patterns that indicate things to avoid
358
- insights.patterns.forEach((pattern) => {
359
- const lowerPattern = pattern.toLowerCase();
360
- if (lowerPattern.includes('avoid') ||
361
- lowerPattern.includes('never') ||
362
- lowerPattern.includes('don\'t') ||
363
- lowerPattern.includes('do not') ||
364
- lowerPattern.includes('should not') ||
365
- lowerPattern.includes('must not')) {
366
- avoidPatterns.push(pattern);
457
+ static async handlePersonaRename(oldName, newName, service, context) {
458
+ try {
459
+ // Use storage renamePersona method
460
+ await context.storage.renamePersona(oldName, newName, context.userId);
461
+ // Load and activate the renamed persona
462
+ const profile = await service.loadPersona({ personaName: newName });
463
+ if (!profile) {
464
+ return createErrorResult(`Failed to load renamed persona '${newName}'`);
367
465
  }
368
- });
369
- // Also check preferences for negative patterns
370
- if (insights.preferences) {
371
- insights.preferences.forEach((pref) => {
372
- const lowerPref = pref.toLowerCase();
373
- if (lowerPref.includes('avoid') ||
374
- lowerPref.includes('never') ||
375
- lowerPref.includes('don\'t') ||
376
- lowerPref.includes('do not')) {
377
- if (!avoidPatterns.includes(pref)) {
378
- avoidPatterns.push(pref);
379
- }
380
- }
381
- });
466
+ // Parse the persona
467
+ const parsedPersona = parsePersonaMarkdown(typeof profile.profile === 'string' ? profile.profile : JSON.stringify(profile.profile));
468
+ // Override with the new name being set
469
+ parsedPersona.name = newName;
470
+ // Get context file path for agent info
471
+ const agentService = new AgentService(context);
472
+ const formatter = agentService.getDefaultFormatter();
473
+ const contextFilePath = formatter?.getRepoContextFilePath();
474
+ const updatedContext = await AsTool.activatePersona(newName, profile, context);
475
+ context.logger.info(`Renamed persona '${oldName}' to '${newName}'`);
476
+ // Return response showing the renamed persona is now active with updated context
477
+ const response = await AsTool.buildResponse(updatedContext, parsedPersona, contextFilePath);
478
+ return {
479
+ content: [{
480
+ type: 'text',
481
+ text: AsTool.formatResponse(response, contextFilePath),
482
+ }],
483
+ isError: false,
484
+ };
485
+ }
486
+ catch (error) {
487
+ context.logger.error(`Failed to rename persona: ${error}`);
488
+ return createErrorResult(`Failed to rename persona '${oldName}' to '${newName}': ${error instanceof Error ? error.message : 'Unknown error'}`);
382
489
  }
383
- return avoidPatterns;
490
+ }
491
+ static async handlePersonaNotFound(args, service, context) {
492
+ const availablePersonas = await service.listPersonas({}) || [];
493
+ // Always prompt for confirmation when persona doesn't exist
494
+ if (!args.confirmCreate) {
495
+ return {
496
+ content: [{
497
+ type: 'text',
498
+ text: JSON.stringify({
499
+ requiresConfirmation: true,
500
+ action: 'createPersona',
501
+ personaName: args.personaName,
502
+ message: `Persona "${args.personaName}" doesn't exist. Would you like to create it?`,
503
+ availablePersonas: availablePersonas.length > 0 ? availablePersonas : undefined,
504
+ hint: 'To confirm creation, call the tool again with confirmCreate: true',
505
+ }, null, 2),
506
+ }],
507
+ isError: false,
508
+ };
509
+ }
510
+ // Create the persona
511
+ const profileString = createDefaultProfile(args.personaName, new Date().toISOString());
512
+ await service.createPersona({
513
+ personaName: args.personaName,
514
+ profile: profileString
515
+ });
516
+ // After creation, switch to the new persona
517
+ return AsTool.handleSwitchPersona({ ...args }, // confirmCreate is already true, no recursion issue
518
+ context);
384
519
  }
385
520
  }
386
521
  //# sourceMappingURL=as.tool.js.map