@hailer/mcp 0.1.14 → 0.1.16

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 (112) hide show
  1. package/.claude/agents/agent-giuseppe-app-builder.md +7 -6
  2. package/.claude/agents/agent-lars-code-inspector.md +26 -14
  3. package/dist/agents/bot-manager.d.ts +48 -0
  4. package/dist/agents/bot-manager.js +254 -0
  5. package/dist/agents/factory.d.ts +150 -0
  6. package/dist/agents/factory.js +650 -0
  7. package/dist/agents/giuseppe/ai.d.ts +83 -0
  8. package/dist/agents/giuseppe/ai.js +466 -0
  9. package/dist/agents/giuseppe/bot.d.ts +110 -0
  10. package/dist/agents/giuseppe/bot.js +780 -0
  11. package/dist/agents/giuseppe/config.d.ts +25 -0
  12. package/dist/agents/giuseppe/config.js +227 -0
  13. package/dist/agents/giuseppe/files.d.ts +52 -0
  14. package/dist/agents/giuseppe/files.js +338 -0
  15. package/dist/agents/giuseppe/git.d.ts +48 -0
  16. package/dist/agents/giuseppe/git.js +298 -0
  17. package/dist/agents/giuseppe/index.d.ts +97 -0
  18. package/dist/agents/giuseppe/index.js +258 -0
  19. package/dist/agents/giuseppe/lsp.d.ts +113 -0
  20. package/dist/agents/giuseppe/lsp.js +485 -0
  21. package/dist/agents/giuseppe/monitor.d.ts +118 -0
  22. package/dist/agents/giuseppe/monitor.js +621 -0
  23. package/dist/agents/giuseppe/prompt.d.ts +5 -0
  24. package/dist/agents/giuseppe/prompt.js +94 -0
  25. package/dist/agents/giuseppe/registries/pending-classification.d.ts +28 -0
  26. package/dist/agents/giuseppe/registries/pending-classification.js +50 -0
  27. package/dist/agents/giuseppe/registries/pending-fix.d.ts +30 -0
  28. package/dist/agents/giuseppe/registries/pending-fix.js +42 -0
  29. package/dist/agents/giuseppe/registries/pending.d.ts +27 -0
  30. package/dist/agents/giuseppe/registries/pending.js +49 -0
  31. package/dist/agents/giuseppe/specialist.d.ts +47 -0
  32. package/dist/agents/giuseppe/specialist.js +237 -0
  33. package/dist/agents/giuseppe/types.d.ts +123 -0
  34. package/dist/agents/giuseppe/types.js +9 -0
  35. package/dist/agents/hailer-expert/index.d.ts +8 -0
  36. package/dist/agents/hailer-expert/index.js +14 -0
  37. package/dist/agents/hal/daemon.d.ts +142 -0
  38. package/dist/agents/hal/daemon.js +1103 -0
  39. package/dist/agents/hal/definitions.d.ts +55 -0
  40. package/dist/agents/hal/definitions.js +263 -0
  41. package/dist/agents/hal/index.d.ts +3 -0
  42. package/dist/agents/hal/index.js +8 -0
  43. package/dist/agents/index.d.ts +18 -0
  44. package/dist/agents/index.js +48 -0
  45. package/dist/agents/shared/base.d.ts +216 -0
  46. package/dist/agents/shared/base.js +846 -0
  47. package/dist/agents/shared/services/agent-registry.d.ts +107 -0
  48. package/dist/agents/shared/services/agent-registry.js +629 -0
  49. package/dist/agents/shared/services/conversation-manager.d.ts +50 -0
  50. package/dist/agents/shared/services/conversation-manager.js +136 -0
  51. package/dist/agents/shared/services/mcp-client.d.ts +56 -0
  52. package/dist/agents/shared/services/mcp-client.js +124 -0
  53. package/dist/agents/shared/services/message-classifier.d.ts +37 -0
  54. package/dist/agents/shared/services/message-classifier.js +187 -0
  55. package/dist/agents/shared/services/message-formatter.d.ts +89 -0
  56. package/dist/agents/shared/services/message-formatter.js +371 -0
  57. package/dist/agents/shared/services/session-logger.d.ts +106 -0
  58. package/dist/agents/shared/services/session-logger.js +446 -0
  59. package/dist/agents/shared/services/tool-executor.d.ts +41 -0
  60. package/dist/agents/shared/services/tool-executor.js +169 -0
  61. package/dist/agents/shared/services/workspace-schema-cache.d.ts +125 -0
  62. package/dist/agents/shared/services/workspace-schema-cache.js +578 -0
  63. package/dist/agents/shared/specialist.d.ts +91 -0
  64. package/dist/agents/shared/specialist.js +399 -0
  65. package/dist/agents/shared/tool-schema-loader.d.ts +62 -0
  66. package/dist/agents/shared/tool-schema-loader.js +232 -0
  67. package/dist/agents/shared/types.d.ts +327 -0
  68. package/dist/agents/shared/types.js +121 -0
  69. package/dist/app.js +21 -4
  70. package/dist/cli.js +0 -0
  71. package/dist/client/agents/orchestrator.d.ts +1 -0
  72. package/dist/client/agents/orchestrator.js +12 -1
  73. package/dist/commands/seed-config.d.ts +9 -0
  74. package/dist/commands/seed-config.js +372 -0
  75. package/dist/config.d.ts +10 -0
  76. package/dist/config.js +61 -1
  77. package/dist/core.d.ts +8 -0
  78. package/dist/core.js +137 -6
  79. package/dist/lib/discussion-lock.d.ts +42 -0
  80. package/dist/lib/discussion-lock.js +110 -0
  81. package/dist/mcp/UserContextCache.js +2 -2
  82. package/dist/mcp/hailer-clients.d.ts +15 -0
  83. package/dist/mcp/hailer-clients.js +100 -6
  84. package/dist/mcp/signal-handler.d.ts +16 -5
  85. package/dist/mcp/signal-handler.js +173 -122
  86. package/dist/mcp/tools/activity.js +9 -1
  87. package/dist/mcp/tools/bot-config.d.ts +184 -9
  88. package/dist/mcp/tools/bot-config.js +2177 -163
  89. package/dist/mcp/tools/giuseppe-tools.d.ts +21 -0
  90. package/dist/mcp/tools/giuseppe-tools.js +525 -0
  91. package/dist/mcp/utils/hailer-api-client.d.ts +42 -1
  92. package/dist/mcp/utils/hailer-api-client.js +128 -2
  93. package/dist/mcp/webhook-handler.d.ts +87 -0
  94. package/dist/mcp/webhook-handler.js +343 -0
  95. package/dist/mcp/workspace-cache.d.ts +5 -0
  96. package/dist/mcp/workspace-cache.js +11 -0
  97. package/dist/mcp-server.js +55 -5
  98. package/dist/modules/bug-reports/giuseppe-agent.d.ts +58 -0
  99. package/dist/modules/bug-reports/giuseppe-agent.js +467 -0
  100. package/dist/modules/bug-reports/giuseppe-ai.d.ts +25 -1
  101. package/dist/modules/bug-reports/giuseppe-ai.js +133 -2
  102. package/dist/modules/bug-reports/giuseppe-bot.d.ts +3 -2
  103. package/dist/modules/bug-reports/giuseppe-bot.js +75 -36
  104. package/dist/modules/bug-reports/giuseppe-daemon.d.ts +80 -0
  105. package/dist/modules/bug-reports/giuseppe-daemon.js +617 -0
  106. package/dist/modules/bug-reports/giuseppe-files.d.ts +12 -0
  107. package/dist/modules/bug-reports/giuseppe-files.js +37 -0
  108. package/dist/modules/bug-reports/giuseppe-lsp.d.ts +113 -0
  109. package/dist/modules/bug-reports/giuseppe-lsp.js +485 -0
  110. package/dist/modules/bug-reports/index.d.ts +1 -0
  111. package/dist/modules/bug-reports/index.js +31 -29
  112. package/package.json +5 -4
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Bug Reports Module - Configuration Discovery
3
+ *
4
+ * Discovers Bug Reports workflow by name pattern.
5
+ * No hardcoded IDs - works in any workspace.
6
+ */
7
+ import type { UserContext } from '../../mcp/UserContextCache';
8
+ import type { BugReportsConfig, WorkflowDiscoveryResult } from './types';
9
+ /**
10
+ * Discover Bug Reports workflow in a workspace
11
+ */
12
+ export declare function discoverWorkflow(userContext: UserContext, config: BugReportsConfig): Promise<WorkflowDiscoveryResult | null>;
13
+ /**
14
+ * Load config from MCP Config workflow or environment
15
+ */
16
+ export declare function loadConfig(userContext: UserContext): Promise<BugReportsConfig>;
17
+ /**
18
+ * Get default configuration
19
+ */
20
+ export declare function getDefaultConfig(): BugReportsConfig;
21
+ /**
22
+ * Save config back to Hailer (creates/updates config activity)
23
+ */
24
+ export declare function saveConfig(_userContext: UserContext, _config: BugReportsConfig): Promise<boolean>;
25
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1,227 @@
1
+ "use strict";
2
+ /**
3
+ * Bug Reports Module - Configuration Discovery
4
+ *
5
+ * Discovers Bug Reports workflow by name pattern.
6
+ * No hardcoded IDs - works in any workspace.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.discoverWorkflow = discoverWorkflow;
10
+ exports.loadConfig = loadConfig;
11
+ exports.getDefaultConfig = getDefaultConfig;
12
+ exports.saveConfig = saveConfig;
13
+ const logger_1 = require("../../lib/logger");
14
+ const logger = (0, logger_1.createLogger)({ component: 'bug-reports-config' });
15
+ /**
16
+ * Discover Bug Reports workflow in a workspace
17
+ */
18
+ async function discoverWorkflow(userContext, config) {
19
+ const { workspaceCache } = userContext;
20
+ // Find workflow by name pattern
21
+ const workflows = Object.values(workspaceCache.rawInit.processes || {});
22
+ const bugWorkflow = workflows.find((w) => w.name?.toLowerCase().includes(config.workflowNamePattern.toLowerCase()));
23
+ if (!bugWorkflow) {
24
+ logger.warn(`Workflow matching "${config.workflowNamePattern}" not found`);
25
+ return null;
26
+ }
27
+ logger.info('Found Bug Reports workflow', {
28
+ id: bugWorkflow._id,
29
+ name: bugWorkflow.name
30
+ });
31
+ // Discover phases by name
32
+ const phases = Object.values(bugWorkflow.phases || {});
33
+ const phaseMap = {
34
+ new: phases.find((p) => p.name?.toLowerCase() === config.phaseNames.new.toLowerCase())?._id,
35
+ inProgress: phases.find((p) => p.name?.toLowerCase() === config.phaseNames.inProgress.toLowerCase())?._id,
36
+ fixed: phases.find((p) => p.name?.toLowerCase() === config.phaseNames.fixed.toLowerCase())?._id,
37
+ closed: phases.find((p) => p.name?.toLowerCase() === config.phaseNames.closed.toLowerCase())?._id,
38
+ declined: phases.find((p) => p.name?.toLowerCase() === config.phaseNames.declined.toLowerCase())?._id
39
+ };
40
+ // Discover fields by label pattern
41
+ const fieldMap = await discoverFields(bugWorkflow);
42
+ const result = {
43
+ workflowId: bugWorkflow._id,
44
+ workflowName: bugWorkflow.name,
45
+ phases: phaseMap,
46
+ fields: fieldMap
47
+ };
48
+ logger.info('Workflow discovery complete', {
49
+ workflowId: result.workflowId,
50
+ phases: Object.entries(result.phases).filter(([, v]) => v).map(([k]) => k),
51
+ fields: Object.entries(result.fields).filter(([, v]) => v).map(([k]) => k)
52
+ });
53
+ return result;
54
+ }
55
+ /**
56
+ * Discover field IDs by label patterns
57
+ */
58
+ async function discoverFields(workflow) {
59
+ const fields = {};
60
+ // Get all fields from all phases
61
+ const allFields = [];
62
+ for (const phase of Object.values(workflow.phases || {})) {
63
+ for (const field of Object.values(phase.fields || {})) {
64
+ allFields.push(field);
65
+ }
66
+ }
67
+ // Also check workflow-level fields (some workflows store fields here)
68
+ for (const field of Object.values(workflow.fields || {})) {
69
+ allFields.push(field);
70
+ }
71
+ logger.info('Field discovery', {
72
+ workflowName: workflow.name,
73
+ phaseCount: Object.keys(workflow.phases || {}).length,
74
+ fieldCount: allFields.length,
75
+ fieldSample: allFields.slice(0, 5).map(f => ({
76
+ id: f._id,
77
+ key: f.key,
78
+ label: f.label,
79
+ type: f.type
80
+ }))
81
+ });
82
+ // Known field keys for Bug Reports template (marketplace standard)
83
+ const knownKeys = {
84
+ 'appId': 'appId',
85
+ 'appName': 'appName',
86
+ 'description': 'description',
87
+ 'reportedBy': 'reportedBy',
88
+ 'stepsToReproduce': 'stepsToReproduce',
89
+ 'expectedBehavior': 'expectedBehavior',
90
+ 'actualBehavior': 'actualBehavior',
91
+ 'priority': 'priority'
92
+ };
93
+ // First pass: match by field key (most reliable for templates)
94
+ for (const field of allFields) {
95
+ const fieldKey = field.key;
96
+ if (fieldKey && knownKeys[fieldKey]) {
97
+ const mappedKey = knownKeys[fieldKey];
98
+ if (!fields[mappedKey]) {
99
+ fields[mappedKey] = field._id;
100
+ logger.debug(`Mapped field by key "${fieldKey}" -> ${mappedKey}`, { fieldId: field._id });
101
+ }
102
+ }
103
+ }
104
+ // Second pass: match by label patterns (fallback)
105
+ const patterns = {
106
+ appId: /app\s*(id|identifier)/i,
107
+ appName: /app\s*name/i,
108
+ description: /description|details|bug\s*description/i,
109
+ stepsToReproduce: /steps|reproduce|how\s*to|repro/i,
110
+ expectedBehavior: /expected|should/i,
111
+ actualBehavior: /actual|instead|result/i,
112
+ reportedBy: /reported\s*by|reporter|submitter|author/i,
113
+ priority: /priority|severity|urgency/i
114
+ };
115
+ for (const field of allFields) {
116
+ const label = field.label || field.key || '';
117
+ for (const [key, pattern] of Object.entries(patterns)) {
118
+ if (!fields[key] && pattern.test(label)) {
119
+ fields[key] = field._id;
120
+ logger.debug(`Mapped field by label "${label}" -> ${key}`, { fieldId: field._id });
121
+ }
122
+ }
123
+ }
124
+ logger.info('Field discovery complete', {
125
+ mappedFields: Object.keys(fields).filter(k => fields[k])
126
+ });
127
+ return fields;
128
+ }
129
+ /**
130
+ * Load config from MCP Config workflow or environment
131
+ */
132
+ async function loadConfig(userContext) {
133
+ const { workspaceCache, hailer } = userContext;
134
+ // Try to load from MCP Config workflow
135
+ try {
136
+ const workflows = Object.values(workspaceCache.rawInit.processes || {});
137
+ const mcpConfigWorkflow = workflows.find((w) => w.name?.toLowerCase().includes('mcp config'));
138
+ if (mcpConfigWorkflow) {
139
+ const phases = Object.values(mcpConfigWorkflow.phases || {});
140
+ if (phases.length > 0) {
141
+ const result = await hailer.fetchActivityList(mcpConfigWorkflow._id, phases[0]._id, 100);
142
+ const activities = result?.activities || result || [];
143
+ const configActivity = activities.find((a) => a.name === 'Bug Monitor Service' || a.name === 'Bug Reports Config');
144
+ if (configActivity) {
145
+ const fullActivity = await hailer.fetchActivityById(configActivity._id);
146
+ const configJson = extractJsonFromFields(fullActivity.fields);
147
+ if (configJson) {
148
+ logger.info('Loaded Bug Reports config from Hailer');
149
+ return {
150
+ ...getDefaultConfig(),
151
+ ...configJson
152
+ };
153
+ }
154
+ }
155
+ }
156
+ }
157
+ }
158
+ catch (error) {
159
+ logger.warn('Failed to load config from Hailer, using defaults', { error });
160
+ }
161
+ // Fall back to environment variables
162
+ return loadConfigFromEnv();
163
+ }
164
+ /**
165
+ * Extract JSON config from activity fields
166
+ */
167
+ function extractJsonFromFields(fields) {
168
+ for (const fieldData of Object.values(fields || {})) {
169
+ if (fieldData.type === 'textarea' && fieldData.value) {
170
+ try {
171
+ return JSON.parse(fieldData.value);
172
+ }
173
+ catch {
174
+ // Not JSON, continue
175
+ }
176
+ }
177
+ }
178
+ return null;
179
+ }
180
+ /**
181
+ * Load config from environment variables
182
+ */
183
+ function loadConfigFromEnv() {
184
+ return {
185
+ enabled: process.env.BUG_MONITOR_ENABLED === 'true',
186
+ workflowNamePattern: process.env.BUG_MONITOR_WORKFLOW_PATTERN || 'Bug Reports',
187
+ phaseNames: {
188
+ new: process.env.BUG_MONITOR_PHASE_NEW || 'New',
189
+ inProgress: process.env.BUG_MONITOR_PHASE_IN_PROGRESS || 'In Progress',
190
+ fixed: process.env.BUG_MONITOR_PHASE_FIXED || 'Fixed',
191
+ closed: process.env.BUG_MONITOR_PHASE_CLOSED || 'Closed',
192
+ declined: process.env.BUG_MONITOR_PHASE_DECLINED || 'Declined'
193
+ },
194
+ intervalMs: parseInt(process.env.BUG_MONITOR_INTERVAL_MS || '300000', 10),
195
+ autoFix: process.env.BUG_MONITOR_AUTO_FIX === 'true',
196
+ notifyOnNew: process.env.BUG_MONITOR_NOTIFY !== 'false',
197
+ anthropicApiKey: process.env.ANTHROPIC_API_KEY
198
+ };
199
+ }
200
+ /**
201
+ * Get default configuration
202
+ */
203
+ function getDefaultConfig() {
204
+ return {
205
+ enabled: false,
206
+ workflowNamePattern: 'Bug Reports',
207
+ phaseNames: {
208
+ new: 'New',
209
+ inProgress: 'In Progress',
210
+ fixed: 'Fixed',
211
+ closed: 'Closed',
212
+ declined: 'Declined'
213
+ },
214
+ intervalMs: 300000,
215
+ autoFix: false,
216
+ notifyOnNew: true
217
+ };
218
+ }
219
+ /**
220
+ * Save config back to Hailer (creates/updates config activity)
221
+ */
222
+ async function saveConfig(_userContext, _config) {
223
+ // TODO: Implement save config to MCP Config workflow
224
+ logger.warn('saveConfig not yet implemented');
225
+ return false;
226
+ }
227
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Giuseppe Files Module - File scanning, reading, and modification
3
+ */
4
+ import type { BugReport, AppRegistryEntry } from './types';
5
+ import type { FixPlan } from './ai';
6
+ export interface FileContent {
7
+ path: string;
8
+ content: string;
9
+ }
10
+ export interface ApplyResult {
11
+ success: boolean;
12
+ files: string[];
13
+ error?: string;
14
+ }
15
+ export interface ScannedApp {
16
+ path: string;
17
+ name: string;
18
+ appId?: string;
19
+ }
20
+ export declare class GiuseppeFiles {
21
+ private appsBasePath?;
22
+ constructor(appsBasePath?: string);
23
+ /**
24
+ * Check if file exists
25
+ */
26
+ fileExists(filePath: string): Promise<boolean>;
27
+ /**
28
+ * Scan for source file NAMES only (no content)
29
+ */
30
+ scanSourceFiles(projectPath: string): Promise<string[]>;
31
+ /**
32
+ * Read specific files by path
33
+ */
34
+ readSelectedFiles(projectPath: string, filePaths: string[]): Promise<FileContent[]>;
35
+ /**
36
+ * Find relevant files by searching for keywords from feedback/bug
37
+ */
38
+ findRelevantFiles(projectPath: string, feedback: string, bug: BugReport): Promise<string[]>;
39
+ /**
40
+ * Apply fixes to files
41
+ */
42
+ applyFixes(app: AppRegistryEntry, fixes: FixPlan['fix']['files']): Promise<ApplyResult>;
43
+ /**
44
+ * Scan apps directory for projects
45
+ */
46
+ scanAppsDirectory(): Promise<ScannedApp[]>;
47
+ /**
48
+ * Find app project by appId, appName, or bug title
49
+ */
50
+ findAppProject(bug: BugReport, appsRegistry: Map<string, AppRegistryEntry>): Promise<AppRegistryEntry | null>;
51
+ }
52
+ //# sourceMappingURL=files.d.ts.map
@@ -0,0 +1,338 @@
1
+ "use strict";
2
+ /**
3
+ * Giuseppe Files Module - File scanning, reading, and modification
4
+ */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.GiuseppeFiles = void 0;
40
+ const child_process_1 = require("child_process");
41
+ const fs = __importStar(require("fs/promises"));
42
+ const path = __importStar(require("path"));
43
+ const logger_1 = require("../../lib/logger");
44
+ const logger = (0, logger_1.createLogger)({ component: 'giuseppe-files' });
45
+ /**
46
+ * Escape shell metacharacters to prevent command injection
47
+ */
48
+ function escapeShellArg(arg) {
49
+ return arg.replace(/["\$`\\!]/g, '\\$&');
50
+ }
51
+ class GiuseppeFiles {
52
+ appsBasePath;
53
+ constructor(appsBasePath) {
54
+ this.appsBasePath = appsBasePath;
55
+ }
56
+ /**
57
+ * Check if file exists
58
+ */
59
+ async fileExists(filePath) {
60
+ try {
61
+ await fs.access(filePath);
62
+ return true;
63
+ }
64
+ catch {
65
+ return false;
66
+ }
67
+ }
68
+ /**
69
+ * Scan for source file NAMES only (no content)
70
+ */
71
+ async scanSourceFiles(projectPath) {
72
+ const files = [];
73
+ const extensions = ['.tsx', '.ts', '.jsx', '.js', '.css', '.scss'];
74
+ const ignoreDirs = ['node_modules', 'dist', 'build', '.git', 'coverage', '.next', 'public'];
75
+ const scanDir = async (dir, depth = 0) => {
76
+ if (depth > 5)
77
+ return;
78
+ try {
79
+ const entries = await fs.readdir(dir, { withFileTypes: true });
80
+ for (const entry of entries) {
81
+ const fullPath = path.join(dir, entry.name);
82
+ const relativePath = path.relative(projectPath, fullPath);
83
+ if (entry.isDirectory()) {
84
+ if (!ignoreDirs.includes(entry.name) && !entry.name.startsWith('.')) {
85
+ await scanDir(fullPath, depth + 1);
86
+ }
87
+ }
88
+ else if (extensions.some(ext => entry.name.endsWith(ext))) {
89
+ // Skip test files
90
+ if (!entry.name.includes('.test.') && !entry.name.includes('.spec.')) {
91
+ files.push(relativePath);
92
+ }
93
+ }
94
+ }
95
+ }
96
+ catch {
97
+ // Skip unreadable directories
98
+ }
99
+ };
100
+ await scanDir(projectPath);
101
+ return files;
102
+ }
103
+ /**
104
+ * Read specific files by path
105
+ */
106
+ async readSelectedFiles(projectPath, filePaths) {
107
+ const files = [];
108
+ for (const filePath of filePaths) {
109
+ try {
110
+ const fullPath = path.join(projectPath, filePath);
111
+ const content = await fs.readFile(fullPath, 'utf-8');
112
+ files.push({ path: filePath, content });
113
+ logger.debug('Read file', { path: filePath, size: content.length });
114
+ }
115
+ catch (error) {
116
+ logger.warn('Could not read file', { path: filePath, error });
117
+ }
118
+ }
119
+ return files;
120
+ }
121
+ /**
122
+ * Find relevant files by searching for keywords from feedback/bug
123
+ */
124
+ async findRelevantFiles(projectPath, feedback, bug) {
125
+ const relevantFiles = new Set();
126
+ // Extract keywords from feedback and bug description
127
+ const text = `${feedback} ${bug.name} ${bug.description || ''} ${bug.stepsToReproduce || ''}`;
128
+ // Find component names (PascalCase words)
129
+ const componentMatches = text.match(/[A-Z][a-zA-Z]+(?:Tool|Button|Modal|Panel|Canvas|Toolbar|Card|Slot)?/g) || [];
130
+ // Find function/variable names (camelCase or specific keywords)
131
+ const functionMatches = text.match(/(?:handle|on|set|get|use)[A-Z][a-zA-Z]+|delete|remove|add|select|click|drag|drop|cursor/gi) || [];
132
+ const keywords = [...new Set([...componentMatches, ...functionMatches])];
133
+ // Search for each keyword using grep
134
+ for (const keyword of keywords.slice(0, 10)) { // Limit searches
135
+ try {
136
+ const escapedKeyword = escapeShellArg(keyword);
137
+ const result = (0, child_process_1.execSync)(`git grep -l "${escapedKeyword}" -- "*.tsx" "*.ts" 2>/dev/null || true`, { cwd: projectPath, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
138
+ result.trim().split('\n').filter(f => f).forEach(f => relevantFiles.add(f));
139
+ }
140
+ catch {
141
+ // Ignore grep errors
142
+ }
143
+ }
144
+ return Array.from(relevantFiles);
145
+ }
146
+ /**
147
+ * Apply fixes to files
148
+ */
149
+ async applyFixes(app, fixes) {
150
+ const modifiedFiles = [];
151
+ try {
152
+ for (const fix of fixes) {
153
+ const filePath = path.join(app.projectPath, fix.path);
154
+ if (fix.action === 'edit' && fix.search && fix.replace !== undefined) {
155
+ // Check file exists before reading
156
+ if (!await this.fileExists(filePath)) {
157
+ return {
158
+ success: false,
159
+ files: modifiedFiles,
160
+ error: `File not found: ${fix.path} - check the path is correct`
161
+ };
162
+ }
163
+ const content = await fs.readFile(filePath, 'utf-8');
164
+ // Try exact match first
165
+ if (content.includes(fix.search)) {
166
+ const newContent = content.replace(fix.search, fix.replace);
167
+ await fs.writeFile(filePath, newContent, 'utf-8');
168
+ modifiedFiles.push(fix.path);
169
+ }
170
+ else {
171
+ // Try normalized whitespace match
172
+ const normalizeWs = (s) => s.replace(/\s+/g, ' ').trim();
173
+ const normalizedContent = normalizeWs(content);
174
+ const normalizedSearch = normalizeWs(fix.search);
175
+ if (normalizedContent.includes(normalizedSearch)) {
176
+ // Find the actual line(s) to replace using line-by-line search
177
+ const searchLines = fix.search.split('\n').map(l => l.trim()).filter(l => l);
178
+ const contentLines = content.split('\n');
179
+ let found = false;
180
+ for (let i = 0; i <= contentLines.length - searchLines.length; i++) {
181
+ const candidateLines = contentLines.slice(i, i + searchLines.length);
182
+ if (candidateLines.every((line, j) => line.trim() === searchLines[j])) {
183
+ // Found match - replace these lines
184
+ const before = contentLines.slice(0, i);
185
+ const after = contentLines.slice(i + searchLines.length);
186
+ const replaceLines = fix.replace.split('\n');
187
+ const newContent = [...before, ...replaceLines, ...after].join('\n');
188
+ await fs.writeFile(filePath, newContent, 'utf-8');
189
+ modifiedFiles.push(fix.path);
190
+ found = true;
191
+ break;
192
+ }
193
+ }
194
+ if (!found) {
195
+ return {
196
+ success: false,
197
+ files: modifiedFiles,
198
+ error: `Search string not found in ${fix.path} (whitespace mismatch)`
199
+ };
200
+ }
201
+ }
202
+ else {
203
+ return {
204
+ success: false,
205
+ files: modifiedFiles,
206
+ error: `Search string not found in ${fix.path}`
207
+ };
208
+ }
209
+ }
210
+ }
211
+ else if (fix.action === 'create' && fix.content) {
212
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
213
+ await fs.writeFile(filePath, fix.content, 'utf-8');
214
+ modifiedFiles.push(fix.path);
215
+ }
216
+ else if (fix.action === 'delete') {
217
+ await fs.unlink(filePath);
218
+ modifiedFiles.push(fix.path);
219
+ }
220
+ }
221
+ return { success: true, files: modifiedFiles };
222
+ }
223
+ catch (error) {
224
+ return {
225
+ success: false,
226
+ files: modifiedFiles,
227
+ error: error instanceof Error ? error.message : String(error)
228
+ };
229
+ }
230
+ }
231
+ /**
232
+ * Scan apps directory for projects
233
+ */
234
+ async scanAppsDirectory() {
235
+ if (!this.appsBasePath) {
236
+ logger.warn('DEV_APPS_PATH not set - cannot scan for apps');
237
+ return [];
238
+ }
239
+ logger.debug('Scanning apps directory', { path: this.appsBasePath });
240
+ const results = [];
241
+ try {
242
+ const entries = await fs.readdir(this.appsBasePath, { withFileTypes: true });
243
+ for (const entry of entries) {
244
+ if (entry.isDirectory()) {
245
+ const projectPath = path.join(this.appsBasePath, entry.name);
246
+ const manifestPath = path.join(projectPath, 'manifest.json');
247
+ try {
248
+ const manifestContent = await fs.readFile(manifestPath, 'utf-8');
249
+ const manifest = JSON.parse(manifestContent);
250
+ results.push({
251
+ path: projectPath,
252
+ name: manifest.name || entry.name,
253
+ appId: manifest.appId
254
+ });
255
+ }
256
+ catch {
257
+ // No manifest, just use directory name
258
+ results.push({
259
+ path: projectPath,
260
+ name: entry.name
261
+ });
262
+ }
263
+ }
264
+ }
265
+ }
266
+ catch (error) {
267
+ logger.warn('Failed to scan apps directory', { error });
268
+ }
269
+ logger.debug('Found apps', { count: results.length, apps: results.map(a => a.name) });
270
+ return results;
271
+ }
272
+ /**
273
+ * Find app project by appId, appName, or bug title
274
+ */
275
+ async findAppProject(bug, appsRegistry) {
276
+ // 1. Check registry first
277
+ if (bug.appId && appsRegistry.has(bug.appId)) {
278
+ return appsRegistry.get(bug.appId);
279
+ }
280
+ // 2. Try to find by appId in apps directory
281
+ if (this.appsBasePath && bug.appId) {
282
+ const apps = await this.scanAppsDirectory();
283
+ for (const app of apps) {
284
+ if (app.appId === bug.appId) {
285
+ return {
286
+ projectPath: app.path,
287
+ name: app.name
288
+ };
289
+ }
290
+ }
291
+ }
292
+ // 3. Try to find by app name
293
+ if (this.appsBasePath && bug.appName) {
294
+ const apps = await this.scanAppsDirectory();
295
+ for (const app of apps) {
296
+ if (app.name.toLowerCase().includes(bug.appName.toLowerCase())) {
297
+ return {
298
+ projectPath: app.path,
299
+ name: app.name
300
+ };
301
+ }
302
+ }
303
+ }
304
+ // 4. Try to extract app name from bug title (e.g., "Bug Report - Lineup Manager")
305
+ if (this.appsBasePath) {
306
+ const apps = await this.scanAppsDirectory();
307
+ const bugTitle = bug.name.toLowerCase();
308
+ for (const app of apps) {
309
+ const appNameLower = app.name.toLowerCase();
310
+ // Check if app name appears in bug title
311
+ if (bugTitle.includes(appNameLower) ||
312
+ bugTitle.includes(appNameLower.replace(/-/g, ' ')) ||
313
+ bugTitle.includes(appNameLower.replace(/_/g, ' '))) {
314
+ logger.info('Found app from bug title', { bugTitle: bug.name, appName: app.name });
315
+ return {
316
+ projectPath: app.path,
317
+ name: app.name
318
+ };
319
+ }
320
+ }
321
+ // Also try matching directory name directly
322
+ for (const app of apps) {
323
+ const dirName = app.path.split('/').pop()?.toLowerCase() || '';
324
+ if (bugTitle.includes(dirName) ||
325
+ bugTitle.includes(dirName.replace(/-/g, ' '))) {
326
+ logger.info('Found app from directory name', { bugTitle: bug.name, dirName });
327
+ return {
328
+ projectPath: app.path,
329
+ name: app.name
330
+ };
331
+ }
332
+ }
333
+ }
334
+ return null;
335
+ }
336
+ }
337
+ exports.GiuseppeFiles = GiuseppeFiles;
338
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Giuseppe Git Module - Git operations for committing, reverting, tagging
3
+ */
4
+ import type { BugReport, AppRegistryEntry } from './types';
5
+ export declare class GiuseppeGit {
6
+ /**
7
+ * Get source files using git ls-files (fast and accurate)
8
+ */
9
+ getSourceFilesFromGit(projectPath: string): Promise<string[]>;
10
+ /**
11
+ * Commit changes to git
12
+ */
13
+ commitChanges(app: AppRegistryEntry, bug: BugReport): Promise<{
14
+ success: boolean;
15
+ hash?: string;
16
+ }>;
17
+ /**
18
+ * Revert changes using git checkout
19
+ */
20
+ revertChanges(app: AppRegistryEntry, files: string[]): Promise<void>;
21
+ /**
22
+ * Get latest version from git tags (e.g., v1.0.1 -> 1.0.1)
23
+ * Returns null if no version tags found
24
+ */
25
+ getLatestVersionFromTags(projectPath: string): string | null;
26
+ /**
27
+ * Create and push a version tag after successful publish
28
+ */
29
+ createVersionTag(projectPath: string, version: string): boolean;
30
+ /**
31
+ * Bump patch version in manifest.json (bug fixes always bump patch)
32
+ * Uses git tags to determine latest version, falls back to manifest
33
+ * 1.0.0 -> 1.0.1 -> 1.0.2 etc.
34
+ */
35
+ bumpPatchVersion(projectPath: string): Promise<{
36
+ oldVersion: string;
37
+ newVersion: string;
38
+ } | null>;
39
+ /**
40
+ * Push to git remote
41
+ */
42
+ push(projectPath: string): Promise<boolean>;
43
+ /**
44
+ * Stage and commit version bump
45
+ */
46
+ commitVersionBump(projectPath: string, version: string): Promise<boolean>;
47
+ }
48
+ //# sourceMappingURL=git.d.ts.map