@tdsoft-tech/aikit 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,4038 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // node_modules/tsup/assets/esm_shims.js
13
+ import path from "path";
14
+ import { fileURLToPath } from "url";
15
+ var init_esm_shims = __esm({
16
+ "node_modules/tsup/assets/esm_shims.js"() {
17
+ "use strict";
18
+ }
19
+ });
20
+
21
+ // src/utils/paths.ts
22
+ import { homedir } from "os";
23
+ import { join } from "path";
24
+ import { existsSync } from "fs";
25
+ var paths;
26
+ var init_paths = __esm({
27
+ "src/utils/paths.ts"() {
28
+ "use strict";
29
+ init_esm_shims();
30
+ paths = {
31
+ /**
32
+ * Get the global AIKit configuration directory
33
+ * ~/.config/aikit/ on Unix, %APPDATA%/aikit/ on Windows
34
+ */
35
+ globalConfig() {
36
+ const base = process.platform === "win32" ? process.env.APPDATA || join(homedir(), "AppData", "Roaming") : join(homedir(), ".config");
37
+ return join(base, "aikit");
38
+ },
39
+ /**
40
+ * Get the project-level AIKit configuration directory
41
+ */
42
+ projectConfig(projectPath) {
43
+ const base = projectPath || process.cwd();
44
+ return join(base, ".aikit");
45
+ },
46
+ /**
47
+ * Get the OpenCode configuration directory
48
+ */
49
+ opencodeConfig() {
50
+ const base = process.platform === "win32" ? process.env.APPDATA || join(homedir(), "AppData", "Roaming") : join(homedir(), ".config");
51
+ return join(base, "opencode");
52
+ },
53
+ /**
54
+ * Get the Beads directory for the current project
55
+ */
56
+ beadsDir(projectPath) {
57
+ const base = projectPath || process.cwd();
58
+ return join(base, ".beads");
59
+ },
60
+ /**
61
+ * Check if a project has AIKit initialized
62
+ */
63
+ hasProjectConfig(projectPath) {
64
+ return existsSync(this.projectConfig(projectPath));
65
+ },
66
+ /**
67
+ * Check if global AIKit is initialized
68
+ */
69
+ hasGlobalConfig() {
70
+ return existsSync(this.globalConfig());
71
+ },
72
+ /**
73
+ * Get effective config path (project takes precedence over global)
74
+ */
75
+ effectiveConfig(projectPath) {
76
+ if (this.hasProjectConfig(projectPath)) {
77
+ return this.projectConfig(projectPath);
78
+ }
79
+ if (this.hasGlobalConfig()) {
80
+ return this.globalConfig();
81
+ }
82
+ return null;
83
+ },
84
+ /**
85
+ * Get skills directory
86
+ */
87
+ skills(configPath) {
88
+ return join(configPath, "skills");
89
+ },
90
+ /**
91
+ * Get agents directory
92
+ */
93
+ agents(configPath) {
94
+ return join(configPath, "agents");
95
+ },
96
+ /**
97
+ * Get commands directory
98
+ */
99
+ commands(configPath) {
100
+ return join(configPath, "commands");
101
+ },
102
+ /**
103
+ * Get tools directory
104
+ */
105
+ tools(configPath) {
106
+ return join(configPath, "tools");
107
+ },
108
+ /**
109
+ * Get plugins directory
110
+ */
111
+ plugins(configPath) {
112
+ return join(configPath, "plugins");
113
+ },
114
+ /**
115
+ * Get memory directory
116
+ */
117
+ memory(configPath) {
118
+ return join(configPath, "memory");
119
+ }
120
+ };
121
+ }
122
+ });
123
+
124
+ // src/utils/logger.ts
125
+ import chalk from "chalk";
126
+ var logger;
127
+ var init_logger = __esm({
128
+ "src/utils/logger.ts"() {
129
+ "use strict";
130
+ init_esm_shims();
131
+ logger = {
132
+ info(...args) {
133
+ console.log(chalk.blue("\u2139"), ...args);
134
+ },
135
+ success(...args) {
136
+ console.log(chalk.green("\u2713"), ...args);
137
+ },
138
+ warn(...args) {
139
+ console.log(chalk.yellow("\u26A0"), ...args);
140
+ },
141
+ error(...args) {
142
+ console.error(chalk.red("\u2716"), ...args);
143
+ },
144
+ debug(...args) {
145
+ if (process.env.DEBUG || process.env.AIKIT_DEBUG) {
146
+ console.log(chalk.gray("\u22EF"), ...args);
147
+ }
148
+ },
149
+ step(step, total, message) {
150
+ console.log(chalk.cyan(`[${step}/${total}]`), message);
151
+ },
152
+ header(message) {
153
+ console.log(chalk.bold.underline(`
154
+ ${message}
155
+ `));
156
+ },
157
+ list(items, prefix = "\u2022") {
158
+ for (const item of items) {
159
+ console.log(` ${prefix} ${item}`);
160
+ }
161
+ }
162
+ };
163
+ }
164
+ });
165
+
166
+ // src/core/tools/figma-mcp.ts
167
+ var figma_mcp_exports = {};
168
+ __export(figma_mcp_exports, {
169
+ FigmaMcpClient: () => FigmaMcpClient
170
+ });
171
+ import { writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
172
+ import { join as join5 } from "path";
173
+ import { existsSync as existsSync2 } from "fs";
174
+ var FigmaMcpClient;
175
+ var init_figma_mcp = __esm({
176
+ "src/core/tools/figma-mcp.ts"() {
177
+ "use strict";
178
+ init_esm_shims();
179
+ init_logger();
180
+ FigmaMcpClient = class {
181
+ apiKey;
182
+ configManager;
183
+ constructor(apiKey, configManager) {
184
+ this.apiKey = apiKey;
185
+ this.configManager = configManager;
186
+ }
187
+ /**
188
+ * Fetch helper with simple retry/backoff for 429/5xx
189
+ */
190
+ async fetchWithRetry(url, options, label, retries = 3, backoffMs = 1500) {
191
+ let attempt = 0;
192
+ let lastError;
193
+ while (attempt <= retries) {
194
+ try {
195
+ const res = await fetch(url, options);
196
+ if (res.ok) return res;
197
+ if (res.status === 429 || res.status >= 500) {
198
+ const retryAfter = Number(res.headers.get("retry-after")) || 0;
199
+ const delay = retryAfter > 0 ? retryAfter * 1e3 : backoffMs * (attempt + 1);
200
+ logger.warn(`${label} failed (status ${res.status}), retrying in ${Math.round(delay / 1e3)}s...`);
201
+ await new Promise((r) => setTimeout(r, delay));
202
+ attempt += 1;
203
+ continue;
204
+ }
205
+ const text = await res.text();
206
+ throw new Error(`${label} error: ${res.status} ${res.statusText}
207
+ ${text}`);
208
+ } catch (err) {
209
+ lastError = err;
210
+ logger.warn(`${label} network error, attempt ${attempt + 1}/${retries + 1}: ${err instanceof Error ? err.message : String(err)}`);
211
+ if (attempt >= retries) break;
212
+ await new Promise((r) => setTimeout(r, backoffMs * (attempt + 1)));
213
+ attempt += 1;
214
+ }
215
+ }
216
+ throw lastError instanceof Error ? lastError : new Error(`${label} failed after retries`);
217
+ }
218
+ /**
219
+ * Extract file key from Figma URL
220
+ */
221
+ extractFileKey(url) {
222
+ const match = url.match(/figma\.com\/design\/([a-zA-Z0-9]+)/);
223
+ return match ? match[1] : null;
224
+ }
225
+ /**
226
+ * Extract node ID from Figma URL
227
+ */
228
+ extractNodeId(url) {
229
+ const match = url.match(/[?&]node-id=([^&]+)/);
230
+ if (!match) return null;
231
+ let nodeId = decodeURIComponent(match[1]);
232
+ if (nodeId.includes("-") && !nodeId.includes(":")) {
233
+ nodeId = nodeId.replace(/-/g, ":");
234
+ }
235
+ return nodeId;
236
+ }
237
+ /**
238
+ * Get Figma file data using API
239
+ */
240
+ async getFileData(url) {
241
+ const fileKey = this.extractFileKey(url);
242
+ if (!fileKey) {
243
+ throw new Error(`Invalid Figma URL: ${url}`);
244
+ }
245
+ const nodeId = this.extractNodeId(url);
246
+ const apiUrl = nodeId ? `https://api.figma.com/v1/files/${fileKey}/nodes?ids=${encodeURIComponent(nodeId)}` : `https://api.figma.com/v1/files/${fileKey}`;
247
+ const response = await this.fetchWithRetry(apiUrl, {
248
+ headers: {
249
+ "X-Figma-Token": this.apiKey
250
+ }
251
+ }, "Figma file fetch");
252
+ const data = await response.json();
253
+ if (nodeId) {
254
+ const nodes = data.nodes;
255
+ const nodeData = Object.values(nodes)[0];
256
+ if (!nodeData) {
257
+ throw new Error(`Node not found: ${nodeId}`);
258
+ }
259
+ return {
260
+ document: nodeData.document,
261
+ components: {},
262
+ styles: {}
263
+ };
264
+ }
265
+ return data;
266
+ }
267
+ /**
268
+ * Extract design tokens from Figma file
269
+ */
270
+ async extractDesignTokens(url, downloadAssets = true, assetsDir) {
271
+ const fileData = await this.getFileData(url);
272
+ const fileKey = this.extractFileKey(url);
273
+ if (!fileKey) {
274
+ throw new Error(`Invalid Figma URL: ${url}`);
275
+ }
276
+ const tokens = {
277
+ colors: [],
278
+ typography: [],
279
+ spacing: {
280
+ unit: 8,
281
+ // Default 8px grid
282
+ scale: []
283
+ },
284
+ components: [],
285
+ screens: [],
286
+ breakpoints: [375, 768, 1024, 1280, 1920]
287
+ // Common breakpoints
288
+ };
289
+ const colorMap = /* @__PURE__ */ new Map();
290
+ this.extractColors(fileData.document, colorMap);
291
+ tokens.colors = Array.from(colorMap.entries()).map(([name, hex]) => ({
292
+ name,
293
+ hex,
294
+ rgba: hex
295
+ // Simplified
296
+ }));
297
+ const typographyMap = /* @__PURE__ */ new Map();
298
+ this.extractTypography(fileData.document, typographyMap);
299
+ tokens.typography = Array.from(typographyMap.values());
300
+ Object.values(fileData.components).forEach((component) => {
301
+ tokens.components.push({
302
+ name: component.name,
303
+ type: "component",
304
+ description: component.description
305
+ });
306
+ });
307
+ this.extractScreens(fileData.document, tokens.screens);
308
+ tokens.structure = this.extractStructure(fileData.document);
309
+ if (downloadAssets && tokens.structure) {
310
+ try {
311
+ tokens.assets = await this.downloadAssets(
312
+ fileKey,
313
+ fileData.document,
314
+ assetsDir || "./assets/images"
315
+ );
316
+ } catch (error) {
317
+ logger.warn(`Failed to download assets: ${error instanceof Error ? error.message : String(error)}`);
318
+ }
319
+ }
320
+ return tokens;
321
+ }
322
+ /**
323
+ * Recursively extract colors from nodes
324
+ */
325
+ extractColors(node, colorMap) {
326
+ if (node.fills && Array.isArray(node.fills)) {
327
+ node.fills.forEach((fill) => {
328
+ if (fill.type === "SOLID" && fill.color) {
329
+ const { r, g, b, a } = fill.color;
330
+ const hex = this.rgbaToHex(r, g, b, a);
331
+ const name = node.name || "Color";
332
+ if (!colorMap.has(hex)) {
333
+ colorMap.set(hex, hex);
334
+ }
335
+ }
336
+ });
337
+ }
338
+ if (node.strokes && Array.isArray(node.strokes)) {
339
+ node.strokes.forEach((stroke) => {
340
+ if (stroke.type === "SOLID" && stroke.color) {
341
+ const { r, g, b, a } = stroke.color;
342
+ const hex = this.rgbaToHex(r, g, b, a);
343
+ if (!colorMap.has(hex)) {
344
+ colorMap.set(hex, hex);
345
+ }
346
+ }
347
+ });
348
+ }
349
+ if (node.children) {
350
+ node.children.forEach((child) => this.extractColors(child, colorMap));
351
+ }
352
+ }
353
+ /**
354
+ * Recursively extract typography from nodes
355
+ */
356
+ extractTypography(node, typographyMap) {
357
+ if (node.type === "TEXT" && node.style) {
358
+ const key = `${node.style.fontFamily}-${node.style.fontSize}-${node.style.fontWeight}`;
359
+ if (!typographyMap.has(key)) {
360
+ typographyMap.set(key, {
361
+ name: `${node.style.fontSize}px ${node.style.fontFamily}`,
362
+ fontFamily: node.style.fontFamily || "Inter",
363
+ fontSize: node.style.fontSize || 16,
364
+ fontWeight: node.style.fontWeight || 400,
365
+ lineHeight: node.style.lineHeightPx || node.style.fontSize || 16,
366
+ letterSpacing: node.style.letterSpacing
367
+ });
368
+ }
369
+ }
370
+ if (node.children) {
371
+ node.children.forEach((child) => this.extractTypography(child, typographyMap));
372
+ }
373
+ }
374
+ /**
375
+ * Extract screens/frames with detailed information
376
+ */
377
+ extractScreens(node, screens) {
378
+ if (node.type === "FRAME" || node.type === "COMPONENT") {
379
+ if (node.absoluteBoundingBox) {
380
+ const isMainScreen = node.absoluteBoundingBox.width >= 800 && node.absoluteBoundingBox.height >= 400;
381
+ if (isMainScreen) {
382
+ screens.push({
383
+ id: node.id,
384
+ name: node.name,
385
+ width: node.absoluteBoundingBox.width,
386
+ height: node.absoluteBoundingBox.height,
387
+ type: node.type,
388
+ childrenCount: node.children?.length || 0
389
+ });
390
+ }
391
+ }
392
+ }
393
+ if (node.children) {
394
+ node.children.forEach((child) => this.extractScreens(child, screens));
395
+ }
396
+ }
397
+ /**
398
+ * Extract structure, content, and layout from nodes
399
+ */
400
+ extractStructure(node, depth = 0) {
401
+ const nodes = [];
402
+ const hierarchyLines = [];
403
+ const processNode = (n, level = 0) => {
404
+ const indent = " ".repeat(level);
405
+ const childIds = [];
406
+ const nodeData = {
407
+ id: n.id,
408
+ name: n.name || "Unnamed",
409
+ type: n.type
410
+ };
411
+ if (n.absoluteBoundingBox) {
412
+ nodeData.position = {
413
+ x: n.absoluteBoundingBox.x,
414
+ y: n.absoluteBoundingBox.y,
415
+ width: n.absoluteBoundingBox.width,
416
+ height: n.absoluteBoundingBox.height
417
+ };
418
+ }
419
+ if (n.type === "TEXT" && n.characters) {
420
+ nodeData.content = n.characters;
421
+ }
422
+ const styles = {};
423
+ if (n.fills && Array.isArray(n.fills)) {
424
+ const solidFill = n.fills.find((f) => f.type === "SOLID" && f.color);
425
+ if (solidFill && solidFill.color) {
426
+ const { r, g, b, a } = solidFill.color;
427
+ styles.backgroundColor = this.rgbaToHex(r, g, b, a);
428
+ }
429
+ }
430
+ if (n.style) {
431
+ if (n.style.fontFamily) styles.fontFamily = n.style.fontFamily;
432
+ if (n.style.fontSize) styles.fontSize = n.style.fontSize;
433
+ if (n.style.fontWeight) styles.fontWeight = n.style.fontWeight;
434
+ if (n.style.lineHeightPx) styles.lineHeight = n.style.lineHeightPx;
435
+ if (n.fills && Array.isArray(n.fills)) {
436
+ const textFill = n.fills.find((f) => f.type === "SOLID" && f.color);
437
+ if (textFill && textFill.color) {
438
+ const { r, g, b, a } = textFill.color;
439
+ styles.color = this.rgbaToHex(r, g, b, a);
440
+ }
441
+ }
442
+ }
443
+ if (n.layoutMode) {
444
+ styles.layout = n.layoutMode;
445
+ }
446
+ if (n.itemSpacing !== void 0) {
447
+ styles.gap = n.itemSpacing;
448
+ }
449
+ if (n.paddingLeft || n.paddingRight || n.paddingTop || n.paddingBottom) {
450
+ styles.padding = {
451
+ top: n.paddingTop || 0,
452
+ right: n.paddingRight || 0,
453
+ bottom: n.paddingBottom || 0,
454
+ left: n.paddingLeft || 0
455
+ };
456
+ }
457
+ if (Object.keys(styles).length > 0) {
458
+ nodeData.styles = styles;
459
+ }
460
+ if (n.children && n.children.length > 0) {
461
+ n.children.forEach((child) => {
462
+ const childNodeIds = processNode(child, level + 1);
463
+ childIds.push(child.id);
464
+ childIds.push(...childNodeIds);
465
+ });
466
+ nodeData.children = n.children.map((c) => c.id);
467
+ }
468
+ nodes.push(nodeData);
469
+ const typeLabel = n.type.toLowerCase();
470
+ const nameLabel = n.name || "Unnamed";
471
+ const contentPreview = n.type === "TEXT" && n.characters ? `: "${n.characters.substring(0, 50)}${n.characters.length > 50 ? "..." : ""}"` : "";
472
+ const sizeLabel = n.absoluteBoundingBox ? ` [${Math.round(n.absoluteBoundingBox.width)}\xD7${Math.round(n.absoluteBoundingBox.height)}]` : "";
473
+ hierarchyLines.push(`${indent}${typeLabel} "${nameLabel}"${contentPreview}${sizeLabel}`);
474
+ return [n.id, ...childIds];
475
+ };
476
+ processNode(node, depth);
477
+ return {
478
+ nodes,
479
+ hierarchy: hierarchyLines.join("\n")
480
+ };
481
+ }
482
+ /**
483
+ * Find all nodes that can be exported as images (optionally filtered by screen)
484
+ */
485
+ findImageNodes(node, imageNodes = [], screenId, isWithinScreen = false) {
486
+ const currentIsScreen = node.id === screenId;
487
+ const nowWithinScreen = isWithinScreen || currentIsScreen;
488
+ if (screenId && !nowWithinScreen && node.type !== "PAGE") {
489
+ if (node.children) {
490
+ node.children.forEach((child) => this.findImageNodes(child, imageNodes, screenId, false));
491
+ }
492
+ return;
493
+ }
494
+ const exportableTypes = ["VECTOR", "COMPONENT", "INSTANCE", "FRAME", "GROUP", "RECTANGLE", "ELLIPSE"];
495
+ const hasImageFill = node.fills?.some((fill) => fill.type === "IMAGE" || fill.imageRef);
496
+ const isExportable = exportableTypes.includes(node.type) || hasImageFill;
497
+ if (isExportable && node.absoluteBoundingBox) {
498
+ const minSize = 16;
499
+ if (node.absoluteBoundingBox.width >= minSize && node.absoluteBoundingBox.height >= minSize) {
500
+ imageNodes.push({
501
+ id: node.id,
502
+ name: node.name || "Unnamed",
503
+ type: node.type,
504
+ width: node.absoluteBoundingBox.width,
505
+ height: node.absoluteBoundingBox.height
506
+ });
507
+ }
508
+ }
509
+ if (node.children) {
510
+ node.children.forEach((child) => this.findImageNodes(child, imageNodes, screenId, nowWithinScreen));
511
+ }
512
+ }
513
+ /**
514
+ * Download images/assets from Figma (optionally filtered by screen)
515
+ */
516
+ async downloadAssets(fileKey, rootNode, assetsDir, screenId) {
517
+ const imageNodes = [];
518
+ this.findImageNodes(rootNode, imageNodes, screenId);
519
+ if (imageNodes.length === 0) {
520
+ logger.info("No image nodes found to download");
521
+ return [];
522
+ }
523
+ logger.info(`Found ${imageNodes.length} image nodes to download`);
524
+ const nodesToDownload = imageNodes.slice(0, 50);
525
+ const nodeIds = nodesToDownload.map((n) => n.id).join(",");
526
+ const imageUrl = `https://api.figma.com/v1/images/${fileKey}?ids=${encodeURIComponent(nodeIds)}&format=png&scale=2`;
527
+ const response = await this.fetchWithRetry(imageUrl, {
528
+ headers: {
529
+ "X-Figma-Token": this.apiKey
530
+ }
531
+ }, "Figma images listing");
532
+ const imageData = await response.json();
533
+ const images = imageData.images;
534
+ const fullAssetsDir = assetsDir.startsWith("/") ? assetsDir : join5(process.cwd(), assetsDir);
535
+ if (!existsSync2(fullAssetsDir)) {
536
+ await mkdir3(fullAssetsDir, { recursive: true });
537
+ }
538
+ const downloadedAssets = [];
539
+ for (const node of nodesToDownload) {
540
+ const imageUrl2 = images[node.id];
541
+ if (!imageUrl2) {
542
+ logger.warn(`No image URL returned for node ${node.id} (${node.name})`);
543
+ continue;
544
+ }
545
+ try {
546
+ const imageResponse = await this.fetchWithRetry(
547
+ imageUrl2,
548
+ {},
549
+ `Download image ${node.id}`
550
+ );
551
+ const imageBuffer = await imageResponse.arrayBuffer();
552
+ const safeName = node.name.replace(/[^a-z0-9]/gi, "_").toLowerCase().substring(0, 50);
553
+ const extension = "png";
554
+ const filename = `${safeName}_${node.id.substring(0, 8)}.${extension}`;
555
+ const filePath = join5(fullAssetsDir, filename);
556
+ await writeFile3(filePath, Buffer.from(imageBuffer));
557
+ downloadedAssets.push({
558
+ nodeId: node.id,
559
+ nodeName: node.name,
560
+ nodeType: node.type,
561
+ format: extension,
562
+ path: filePath,
563
+ url: imageUrl2,
564
+ width: node.width,
565
+ height: node.height
566
+ });
567
+ logger.info(`Downloaded: ${filename} (${node.name})`);
568
+ } catch (error) {
569
+ logger.warn(`Error downloading image for node ${node.id}: ${error instanceof Error ? error.message : String(error)}`);
570
+ }
571
+ }
572
+ logger.info(`Downloaded ${downloadedAssets.length} assets to ${fullAssetsDir}`);
573
+ return downloadedAssets;
574
+ }
575
+ /**
576
+ * Convert RGBA to hex
577
+ */
578
+ rgbaToHex(r, g, b, a = 1) {
579
+ const toHex = (n) => {
580
+ const hex = Math.round(n * 255).toString(16);
581
+ return hex.length === 1 ? "0" + hex : hex;
582
+ };
583
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}${a < 1 ? toHex(a) : ""}`;
584
+ }
585
+ };
586
+ }
587
+ });
588
+
589
+ // src/core/tools/figma-screen-developer.ts
590
+ var figma_screen_developer_exports = {};
591
+ __export(figma_screen_developer_exports, {
592
+ checkCurrentCodeStatus: () => checkCurrentCodeStatus,
593
+ compareCodeWithFigma: () => compareCodeWithFigma
594
+ });
595
+ import { readFile as readFile4, readdir as readdir3 } from "fs/promises";
596
+ import { join as join6 } from "path";
597
+ import { existsSync as existsSync3 } from "fs";
598
+ async function checkCurrentCodeStatus(projectPath = process.cwd()) {
599
+ const status = {
600
+ hasHTML: false,
601
+ htmlFile: void 0,
602
+ hasCSS: false,
603
+ cssFiles: [],
604
+ hasAssets: false,
605
+ assetCount: 0,
606
+ sections: []
607
+ };
608
+ try {
609
+ const htmlFiles = ["index.html", "index.htm", "main.html"].filter(
610
+ (file) => existsSync3(join6(projectPath, file))
611
+ );
612
+ if (htmlFiles.length > 0) {
613
+ status.hasHTML = true;
614
+ status.htmlFile = htmlFiles[0];
615
+ try {
616
+ const htmlContent = await readFile4(join6(projectPath, htmlFiles[0]), "utf-8");
617
+ const sectionMatches = htmlContent.match(/<(section|div|header|footer|main|article|aside)[^>]*(?:id|class)=["']([^"']+)["']/gi);
618
+ if (sectionMatches) {
619
+ status.sections = sectionMatches.map((match) => {
620
+ const idMatch = match.match(/id=["']([^"']+)["']/i);
621
+ const classMatch = match.match(/class=["']([^"']+)["']/i);
622
+ return idMatch ? idMatch[1] : classMatch ? classMatch[1].split(" ")[0] : "";
623
+ }).filter(Boolean);
624
+ }
625
+ } catch (e) {
626
+ }
627
+ }
628
+ const stylesDir = join6(projectPath, "styles");
629
+ if (existsSync3(stylesDir)) {
630
+ try {
631
+ const files = await readdir3(stylesDir);
632
+ const cssFiles = files.filter((f) => f.endsWith(".css"));
633
+ if (cssFiles.length > 0) {
634
+ status.hasCSS = true;
635
+ status.cssFiles = cssFiles.map((f) => join6(stylesDir, f));
636
+ }
637
+ } catch (e) {
638
+ }
639
+ }
640
+ const assetsDir = join6(projectPath, "assets", "images");
641
+ if (existsSync3(assetsDir)) {
642
+ try {
643
+ const files = await readdir3(assetsDir);
644
+ const imageFiles = files.filter((f) => /\.(png|jpg|jpeg|svg|webp)$/i.test(f));
645
+ if (imageFiles.length > 0) {
646
+ status.hasAssets = true;
647
+ status.assetCount = imageFiles.length;
648
+ }
649
+ } catch (e) {
650
+ }
651
+ }
652
+ } catch (error) {
653
+ logger.warn(`Error checking code status: ${error instanceof Error ? error.message : String(error)}`);
654
+ }
655
+ return status;
656
+ }
657
+ async function compareCodeWithFigma(figmaTokens, selectedScreenId, projectPath = process.cwd()) {
658
+ const codeStatus = await checkCurrentCodeStatus(projectPath);
659
+ const result = {
660
+ missingSections: [],
661
+ missingAssets: [],
662
+ needsUpdate: false,
663
+ recommendations: []
664
+ };
665
+ const selectedScreen = figmaTokens.screens?.find((s) => s.id === selectedScreenId);
666
+ if (!selectedScreen) {
667
+ result.recommendations.push("Selected screen not found in Figma design");
668
+ return result;
669
+ }
670
+ const figmaSections = [];
671
+ if (figmaTokens.structure?.nodes) {
672
+ const screenNode = figmaTokens.structure.nodes.find((n) => n.id === selectedScreenId);
673
+ if (screenNode?.children) {
674
+ screenNode.children.forEach((childId) => {
675
+ const childNode = figmaTokens.structure.nodes.find((n) => n.id === childId);
676
+ if (childNode && (childNode.type === "FRAME" || childNode.type === "COMPONENT")) {
677
+ const sectionName = childNode.name.toLowerCase().replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-");
678
+ figmaSections.push(sectionName);
679
+ }
680
+ });
681
+ }
682
+ }
683
+ const existingSections = codeStatus.sections.map((s) => s.toLowerCase());
684
+ result.missingSections = figmaSections.filter(
685
+ (s) => !existingSections.some((existing) => existing.includes(s) || s.includes(existing))
686
+ );
687
+ if (codeStatus.assetCount === 0) {
688
+ result.missingAssets.push("All assets need to be downloaded");
689
+ result.needsUpdate = true;
690
+ }
691
+ if (!codeStatus.hasHTML) {
692
+ result.recommendations.push("Create index.html with HTML5 structure");
693
+ }
694
+ if (!codeStatus.hasCSS) {
695
+ result.recommendations.push("Create CSS files (variables.css, base.css, components.css)");
696
+ }
697
+ if (result.missingSections.length > 0) {
698
+ result.recommendations.push(`Implement missing sections: ${result.missingSections.join(", ")}`);
699
+ }
700
+ if (result.missingAssets.length > 0) {
701
+ result.recommendations.push("Download required assets from Figma");
702
+ }
703
+ return result;
704
+ }
705
+ var init_figma_screen_developer = __esm({
706
+ "src/core/tools/figma-screen-developer.ts"() {
707
+ "use strict";
708
+ init_esm_shims();
709
+ init_logger();
710
+ }
711
+ });
712
+
713
+ // src/core/memory.ts
714
+ var memory_exports = {};
715
+ __export(memory_exports, {
716
+ MemoryManager: () => MemoryManager
717
+ });
718
+ import { readFile as readFile5, writeFile as writeFile4, mkdir as mkdir4, access as access2, constants as constants2 } from "fs/promises";
719
+ import { join as join7 } from "path";
720
+ var MemoryManager;
721
+ var init_memory = __esm({
722
+ "src/core/memory.ts"() {
723
+ "use strict";
724
+ init_esm_shims();
725
+ init_paths();
726
+ MemoryManager = class {
727
+ config;
728
+ constructor(config) {
729
+ this.config = config;
730
+ }
731
+ /**
732
+ * List all memory entries
733
+ */
734
+ async list() {
735
+ const memories = [];
736
+ const memoryPath = paths.memory(this.config.configPath);
737
+ const subDirs = ["observations", "handoffs", "research"];
738
+ for (const subDir of subDirs) {
739
+ const dirPath = join7(memoryPath, subDir);
740
+ try {
741
+ const { readdir: readdir8 } = await import("fs/promises");
742
+ const files = await readdir8(dirPath);
743
+ for (const file of files) {
744
+ if (!file.endsWith(".md")) continue;
745
+ const content = await readFile5(join7(dirPath, file), "utf-8");
746
+ const summary = this.extractSummary(content);
747
+ memories.push({
748
+ key: `${subDir}/${file.replace(".md", "")}`,
749
+ content,
750
+ summary,
751
+ createdAt: /* @__PURE__ */ new Date(),
752
+ // Would get from file stats
753
+ updatedAt: /* @__PURE__ */ new Date(),
754
+ type: subDir
755
+ });
756
+ }
757
+ } catch {
758
+ }
759
+ }
760
+ return memories;
761
+ }
762
+ /**
763
+ * Read a memory entry
764
+ */
765
+ async read(key) {
766
+ const memoryPath = paths.memory(this.config.configPath);
767
+ let filePath;
768
+ if (key.includes("/")) {
769
+ filePath = join7(memoryPath, `${key}.md`);
770
+ } else {
771
+ const subDirs = ["observations", "handoffs", "research", "_templates"];
772
+ for (const subDir of subDirs) {
773
+ const testPath = join7(memoryPath, subDir, `${key}.md`);
774
+ try {
775
+ await access2(testPath, constants2.R_OK);
776
+ filePath = testPath;
777
+ break;
778
+ } catch {
779
+ continue;
780
+ }
781
+ }
782
+ filePath = filePath || join7(memoryPath, `${key}.md`);
783
+ }
784
+ try {
785
+ return await readFile5(filePath, "utf-8");
786
+ } catch {
787
+ return null;
788
+ }
789
+ }
790
+ /**
791
+ * Update a memory entry
792
+ */
793
+ async update(key, content, options) {
794
+ const memoryPath = paths.memory(this.config.configPath);
795
+ const type = options?.type || "custom";
796
+ let filePath;
797
+ if (key.includes("/")) {
798
+ filePath = join7(memoryPath, `${key}.md`);
799
+ } else {
800
+ const subDir = type === "observation" ? "observations" : type === "handoff" ? "handoffs" : type === "research" ? "research" : "";
801
+ filePath = join7(memoryPath, subDir, `${key}.md`);
802
+ }
803
+ const { dirname: dirname2 } = await import("path");
804
+ await mkdir4(dirname2(filePath), { recursive: true });
805
+ if (options?.append) {
806
+ try {
807
+ const existing = await readFile5(filePath, "utf-8");
808
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
809
+ content = `${existing}
810
+
811
+ ---
812
+ _Updated: ${timestamp}_
813
+
814
+ ${content}`;
815
+ } catch {
816
+ }
817
+ }
818
+ await writeFile4(filePath, content);
819
+ }
820
+ /**
821
+ * Create a handoff bundle
822
+ */
823
+ async createHandoff(summary) {
824
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
825
+ const key = `handoffs/${timestamp}`;
826
+ const content = `# Handoff: ${(/* @__PURE__ */ new Date()).toLocaleString()}
827
+
828
+ ## Completed
829
+ ${summary.completed.map((item) => `- [x] ${item}`).join("\n") || "- None"}
830
+
831
+ ## In Progress
832
+ ${summary.inProgress.map((item) => `- [ ] ${item}`).join("\n") || "- None"}
833
+
834
+ ## Remaining
835
+ ${summary.remaining.map((item) => `- [ ] ${item}`).join("\n") || "- None"}
836
+
837
+ ## Context
838
+ ${summary.context || "No additional context."}
839
+
840
+ ## Next Steps
841
+ ${summary.nextSteps.map((step, i) => `${i + 1}. ${step}`).join("\n") || "- Continue from where left off"}
842
+ `;
843
+ await this.update(key, content, { type: "handoff" });
844
+ return key;
845
+ }
846
+ /**
847
+ * Get the latest handoff
848
+ */
849
+ async getLatestHandoff() {
850
+ const memories = await this.list();
851
+ const handoffs = memories.filter((m) => m.type === "handoff");
852
+ if (handoffs.length === 0) return null;
853
+ handoffs.sort((a, b) => b.key.localeCompare(a.key));
854
+ return handoffs[0];
855
+ }
856
+ /**
857
+ * Create an observation
858
+ */
859
+ async createObservation(title, observation) {
860
+ const slug = title.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
861
+ const key = `observations/${slug}`;
862
+ const content = `# ${title}
863
+
864
+ ## What
865
+ ${observation.what}
866
+
867
+ ## Why
868
+ ${observation.why}
869
+
870
+ ## Impact
871
+ ${observation.impact}
872
+
873
+ ${observation.tags?.length ? `## Tags
874
+ ${observation.tags.map((t) => `- ${t}`).join("\n")}` : ""}
875
+
876
+ ---
877
+ _Created: ${(/* @__PURE__ */ new Date()).toISOString()}_
878
+ `;
879
+ await this.update(key, content, { type: "observation" });
880
+ return key;
881
+ }
882
+ /**
883
+ * Save research findings
884
+ */
885
+ async saveResearch(topic, findings) {
886
+ const slug = topic.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
887
+ const key = `research/${slug}`;
888
+ const content = `# Research: ${topic}
889
+
890
+ ## Summary
891
+ ${findings.summary}
892
+
893
+ ## Sources
894
+ ${findings.sources.map((s) => `- ${s}`).join("\n")}
895
+
896
+ ## Recommendations
897
+ ${findings.recommendations.map((r, i) => `${i + 1}. ${r}`).join("\n")}
898
+
899
+ ${findings.codeExamples ? `## Code Examples
900
+ \`\`\`
901
+ ${findings.codeExamples}
902
+ \`\`\`` : ""}
903
+
904
+ ---
905
+ _Researched: ${(/* @__PURE__ */ new Date()).toISOString()}_
906
+ `;
907
+ await this.update(key, content, { type: "research" });
908
+ return key;
909
+ }
910
+ /**
911
+ * Extract a summary from content (first paragraph or heading)
912
+ */
913
+ extractSummary(content) {
914
+ const lines = content.split("\n").filter((l) => l.trim());
915
+ for (const line of lines) {
916
+ if (!line.startsWith("#") && line.trim().length > 0) {
917
+ return line.slice(0, 100) + (line.length > 100 ? "..." : "");
918
+ }
919
+ }
920
+ if (lines[0]?.startsWith("#")) {
921
+ return lines[0].replace(/^#+\s*/, "");
922
+ }
923
+ return "No summary available";
924
+ }
925
+ };
926
+ }
927
+ });
928
+
929
+ // src/index.ts
930
+ init_esm_shims();
931
+
932
+ // src/core/config.ts
933
+ init_esm_shims();
934
+ init_paths();
935
+ import { readFile, access, constants } from "fs/promises";
936
+ import { join as join2 } from "path";
937
+ import { z } from "zod";
938
+ var ConfigSchema = z.object({
939
+ version: z.string(),
940
+ skills: z.object({
941
+ enabled: z.boolean().default(true),
942
+ directory: z.string().optional()
943
+ }).default({}),
944
+ agents: z.object({
945
+ enabled: z.boolean().default(true),
946
+ default: z.string().default("build")
947
+ }).default({}),
948
+ commands: z.object({
949
+ enabled: z.boolean().default(true)
950
+ }).default({}),
951
+ tools: z.object({
952
+ enabled: z.boolean().default(true)
953
+ }).default({}),
954
+ plugins: z.object({
955
+ enabled: z.boolean().default(true),
956
+ autoload: z.array(z.string()).optional()
957
+ }).default({}),
958
+ memory: z.object({
959
+ enabled: z.boolean().default(true),
960
+ maxSize: z.number().optional()
961
+ }).default({}),
962
+ beads: z.object({
963
+ enabled: z.boolean().default(true),
964
+ autoInit: z.boolean().default(false)
965
+ }).default({}),
966
+ antiHallucination: z.object({
967
+ enabled: z.boolean().default(true),
968
+ specFile: z.string().default("spec.md"),
969
+ reviewFile: z.string().default("review.md")
970
+ }).default({}),
971
+ mcp: z.object({
972
+ context7: z.boolean().default(false),
973
+ githubGrep: z.boolean().default(false),
974
+ gkg: z.boolean().default(false)
975
+ }).optional()
976
+ });
977
+ var Config = class {
978
+ config;
979
+ constructor(config) {
980
+ this.config = config;
981
+ }
982
+ get() {
983
+ return this.config;
984
+ }
985
+ get skills() {
986
+ return this.config.skills;
987
+ }
988
+ get agents() {
989
+ return this.config.agents;
990
+ }
991
+ get commands() {
992
+ return this.config.commands;
993
+ }
994
+ get tools() {
995
+ return this.config.tools;
996
+ }
997
+ get plugins() {
998
+ return this.config.plugins;
999
+ }
1000
+ get memory() {
1001
+ return this.config.memory;
1002
+ }
1003
+ get beads() {
1004
+ return this.config.beads;
1005
+ }
1006
+ get antiHallucination() {
1007
+ return this.config.antiHallucination;
1008
+ }
1009
+ get configPath() {
1010
+ return this.config.configPath;
1011
+ }
1012
+ get projectPath() {
1013
+ return this.config.projectPath;
1014
+ }
1015
+ /**
1016
+ * Get path to a specific resource
1017
+ */
1018
+ getPath(resource) {
1019
+ return paths[resource](this.configPath);
1020
+ }
1021
+ };
1022
+ async function loadConfig(projectPath) {
1023
+ const project = projectPath || process.cwd();
1024
+ const projectConfigPath = paths.projectConfig(project);
1025
+ const globalConfigPath = paths.globalConfig();
1026
+ let configPath;
1027
+ let configData = {};
1028
+ try {
1029
+ await access(join2(globalConfigPath, "aikit.json"), constants.R_OK);
1030
+ const globalContent = await readFile(join2(globalConfigPath, "aikit.json"), "utf-8");
1031
+ configData = JSON.parse(globalContent);
1032
+ configPath = globalConfigPath;
1033
+ } catch {
1034
+ configPath = projectConfigPath;
1035
+ }
1036
+ try {
1037
+ await access(join2(projectConfigPath, "aikit.json"), constants.R_OK);
1038
+ const projectContent = await readFile(join2(projectConfigPath, "aikit.json"), "utf-8");
1039
+ const projectData = JSON.parse(projectContent);
1040
+ configData = deepMerge(configData, projectData);
1041
+ configPath = projectConfigPath;
1042
+ } catch {
1043
+ }
1044
+ if (!configData.version) {
1045
+ configData.version = "0.1.0";
1046
+ }
1047
+ const parsed = ConfigSchema.parse(configData);
1048
+ return new Config({
1049
+ ...parsed,
1050
+ configPath,
1051
+ projectPath: project
1052
+ });
1053
+ }
1054
+ function deepMerge(base, override) {
1055
+ const result = { ...base };
1056
+ for (const key in override) {
1057
+ const baseValue = base[key];
1058
+ const overrideValue = override[key];
1059
+ if (typeof baseValue === "object" && baseValue !== null && typeof overrideValue === "object" && overrideValue !== null && !Array.isArray(baseValue) && !Array.isArray(overrideValue)) {
1060
+ result[key] = deepMerge(
1061
+ baseValue,
1062
+ overrideValue
1063
+ );
1064
+ } else if (overrideValue !== void 0) {
1065
+ result[key] = overrideValue;
1066
+ }
1067
+ }
1068
+ return result;
1069
+ }
1070
+
1071
+ // src/core/skills.ts
1072
+ init_esm_shims();
1073
+ init_paths();
1074
+ import { readFile as readFile2, readdir, writeFile, mkdir } from "fs/promises";
1075
+ import { join as join3, basename, extname } from "path";
1076
+ import matter from "gray-matter";
1077
+ var SkillEngine = class {
1078
+ config;
1079
+ skillsCache = /* @__PURE__ */ new Map();
1080
+ constructor(config) {
1081
+ this.config = config;
1082
+ }
1083
+ /**
1084
+ * List all available skills
1085
+ */
1086
+ async listSkills() {
1087
+ const skills = [];
1088
+ const globalSkillsPath = paths.skills(paths.globalConfig());
1089
+ try {
1090
+ const globalSkills = await this.loadSkillsFromDir(globalSkillsPath);
1091
+ skills.push(...globalSkills);
1092
+ } catch {
1093
+ }
1094
+ const projectSkillsPath = paths.skills(this.config.configPath);
1095
+ if (projectSkillsPath !== globalSkillsPath) {
1096
+ try {
1097
+ const projectSkills = await this.loadSkillsFromDir(projectSkillsPath);
1098
+ for (const skill of projectSkills) {
1099
+ const existingIndex = skills.findIndex((s) => s.name === skill.name);
1100
+ if (existingIndex >= 0) {
1101
+ skills[existingIndex] = skill;
1102
+ } else {
1103
+ skills.push(skill);
1104
+ }
1105
+ }
1106
+ } catch {
1107
+ }
1108
+ }
1109
+ return skills.sort((a, b) => a.name.localeCompare(b.name));
1110
+ }
1111
+ /**
1112
+ * Get a specific skill by name
1113
+ */
1114
+ async getSkill(name) {
1115
+ if (this.skillsCache.has(name)) {
1116
+ return this.skillsCache.get(name);
1117
+ }
1118
+ const skills = await this.listSkills();
1119
+ const skill = skills.find((s) => s.name === name);
1120
+ if (skill) {
1121
+ this.skillsCache.set(name, skill);
1122
+ }
1123
+ return skill || null;
1124
+ }
1125
+ /**
1126
+ * Find skills matching a query
1127
+ */
1128
+ async findSkills(query) {
1129
+ const skills = await this.listSkills();
1130
+ if (!query) {
1131
+ return skills;
1132
+ }
1133
+ const lowerQuery = query.toLowerCase();
1134
+ return skills.filter(
1135
+ (skill) => skill.name.toLowerCase().includes(lowerQuery) || skill.description.toLowerCase().includes(lowerQuery) || skill.tags.some((tag) => tag.toLowerCase().includes(lowerQuery)) || skill.category.toLowerCase().includes(lowerQuery)
1136
+ );
1137
+ }
1138
+ /**
1139
+ * Create a new skill
1140
+ */
1141
+ async createSkill(name, options) {
1142
+ const configPath = options?.global ? paths.globalConfig() : this.config.configPath;
1143
+ const skillsDir = paths.skills(configPath);
1144
+ await mkdir(skillsDir, { recursive: true });
1145
+ const fileName = `${name.replace(/\s+/g, "-").toLowerCase()}.md`;
1146
+ const filePath = join3(skillsDir, fileName);
1147
+ const frontmatter = {
1148
+ name,
1149
+ description: options?.description || `Use when you need to ${name}`,
1150
+ useWhen: options?.useWhen || `The user asks you to ${name}`,
1151
+ category: options?.category || "custom",
1152
+ tags: options?.tags || ["custom"]
1153
+ };
1154
+ const content = options?.content || `## ${name}
1155
+
1156
+ ### Overview
1157
+ Describe what this skill does.
1158
+
1159
+ ### Workflow
1160
+
1161
+ #### Step 1: Understand the Task
1162
+ - Gather context
1163
+ - Clarify requirements
1164
+
1165
+ #### Step 2: Plan the Approach
1166
+ - Break down into sub-tasks
1167
+ - Identify dependencies
1168
+
1169
+ #### Step 3: Execute
1170
+ - Follow TDD principles
1171
+ - Write tests first
1172
+
1173
+ #### Step 4: Verify
1174
+ - Run all tests
1175
+ - Check for regressions
1176
+
1177
+ ### Checklist
1178
+ - [ ] Requirements understood
1179
+ - [ ] Tests written
1180
+ - [ ] Implementation complete
1181
+ - [ ] All tests passing
1182
+ - [ ] Code reviewed
1183
+ `;
1184
+ const fileContent = matter.stringify(content, frontmatter);
1185
+ await writeFile(filePath, fileContent);
1186
+ const skill = {
1187
+ name,
1188
+ description: frontmatter.description,
1189
+ useWhen: frontmatter.useWhen,
1190
+ category: frontmatter.category,
1191
+ tags: frontmatter.tags,
1192
+ content,
1193
+ filePath
1194
+ };
1195
+ this.skillsCache.set(name, skill);
1196
+ return skill;
1197
+ }
1198
+ /**
1199
+ * Sync global skills to project directory
1200
+ */
1201
+ async syncSkillsToProject() {
1202
+ const globalSkillsPath = paths.skills(paths.globalConfig());
1203
+ const projectSkillsPath = paths.skills(this.config.configPath);
1204
+ if (globalSkillsPath === projectSkillsPath) {
1205
+ return { count: 0, synced: [] };
1206
+ }
1207
+ const globalSkills = await this.loadSkillsFromDir(globalSkillsPath);
1208
+ await mkdir(projectSkillsPath, { recursive: true });
1209
+ const synced = [];
1210
+ for (const skill of globalSkills) {
1211
+ const fileName = `${skill.name.replace(/\s+/g, "-").toLowerCase()}.md`;
1212
+ const destPath = join3(projectSkillsPath, fileName);
1213
+ const srcContent = await readFile2(skill.filePath, "utf-8");
1214
+ await writeFile(destPath, srcContent);
1215
+ synced.push(skill.name);
1216
+ }
1217
+ return { count: synced.length, synced };
1218
+ }
1219
+ /**
1220
+ * Format skill for agent consumption
1221
+ */
1222
+ formatForAgent(skill) {
1223
+ return `# Skill: ${skill.name}
1224
+
1225
+ ## When to Use
1226
+ ${skill.useWhen}
1227
+
1228
+ ## Description
1229
+ ${skill.description}
1230
+
1231
+ ## Workflow
1232
+ ${skill.content}
1233
+
1234
+ ---
1235
+ **IMPORTANT**: Follow this workflow step by step. Do not skip steps.
1236
+ `;
1237
+ }
1238
+ /**
1239
+ * Load skills from a directory
1240
+ */
1241
+ async loadSkillsFromDir(dir) {
1242
+ const files = await readdir(dir);
1243
+ const skills = [];
1244
+ for (const file of files) {
1245
+ if (extname(file) !== ".md") continue;
1246
+ const filePath = join3(dir, file);
1247
+ const content = await readFile2(filePath, "utf-8");
1248
+ const { data, content: body } = matter(content);
1249
+ const frontmatter = data;
1250
+ const name = frontmatter.name || basename(file, ".md");
1251
+ skills.push({
1252
+ name,
1253
+ description: frontmatter.description || "",
1254
+ useWhen: frontmatter.useWhen || "",
1255
+ category: frontmatter.category || "general",
1256
+ tags: frontmatter.tags || [],
1257
+ content: body.trim(),
1258
+ filePath
1259
+ });
1260
+ }
1261
+ return skills;
1262
+ }
1263
+ };
1264
+
1265
+ // src/core/agents.ts
1266
+ init_esm_shims();
1267
+ var DEFAULT_AGENTS = [
1268
+ {
1269
+ name: "planner",
1270
+ displayName: "@planner",
1271
+ description: "Strategic planning and multi-agent coordination",
1272
+ useWhen: "Complex tasks requiring architecture decisions, multi-step planning, or coordination between specialists",
1273
+ capabilities: [
1274
+ "Break down complex tasks into sub-tasks",
1275
+ "Coordinate between specialist agents",
1276
+ "Make architecture decisions",
1277
+ "Create implementation plans"
1278
+ ],
1279
+ systemPrompt: `You are a strategic planner agent. Your role is to:
1280
+
1281
+ 1. ANALYZE the task and understand the full scope
1282
+ 2. BREAK DOWN complex tasks into smaller, manageable sub-tasks
1283
+ 3. DELEGATE to appropriate specialist agents
1284
+ 4. COORDINATE the overall workflow
1285
+ 5. VERIFY completion of the overall goal
1286
+
1287
+ When delegating:
1288
+ - Use @build for implementation tasks
1289
+ - Use @scout for external research
1290
+ - Use @review for code review and security audits
1291
+ - Use @explore for codebase navigation
1292
+ - Use @vision for visual analysis
1293
+
1294
+ Always create a clear plan before delegating. Track progress and ensure all sub-tasks complete successfully.`,
1295
+ delegatesTo: ["build", "scout", "review", "explore", "vision"]
1296
+ },
1297
+ {
1298
+ name: "build",
1299
+ displayName: "@build",
1300
+ description: "Primary execution agent for implementing features",
1301
+ useWhen: "Implementing features, writing code, making changes to the codebase",
1302
+ capabilities: [
1303
+ "Write production code",
1304
+ "Write tests",
1305
+ "Refactor code",
1306
+ "Fix bugs",
1307
+ "Implement features"
1308
+ ],
1309
+ systemPrompt: `You are the build agent. Your role is to implement code changes.
1310
+
1311
+ Follow these principles:
1312
+ 1. TEST-DRIVEN DEVELOPMENT: Write tests before implementation
1313
+ 2. INCREMENTAL CHANGES: Make small, focused commits
1314
+ 3. VERIFY: Run tests and type checks after each change
1315
+ 4. DOCUMENT: Add comments for complex logic
1316
+
1317
+ Before starting:
1318
+ - Understand the requirements fully
1319
+ - Check existing patterns in the codebase
1320
+ - Plan the implementation approach
1321
+
1322
+ After completing:
1323
+ - Ensure all tests pass
1324
+ - Run linting and type checks
1325
+ - Create a clear commit message`,
1326
+ delegatesTo: ["review", "explore"]
1327
+ },
1328
+ {
1329
+ name: "rush",
1330
+ displayName: "@rush",
1331
+ description: "Fast execution with minimal planning",
1332
+ useWhen: "Quick fixes, hotfixes, simple edits that need minimal planning",
1333
+ capabilities: [
1334
+ "Quick bug fixes",
1335
+ "Simple refactoring",
1336
+ "Minor changes",
1337
+ "Hotfixes"
1338
+ ],
1339
+ systemPrompt: `You are the rush agent. Your role is to make quick, focused changes.
1340
+
1341
+ Guidelines:
1342
+ 1. ACT FAST: Minimal planning, direct execution
1343
+ 2. FOCUS: One change at a time
1344
+ 3. VERIFY: Quick sanity check after change
1345
+ 4. MINIMAL SCOPE: Don't expand beyond the immediate task
1346
+
1347
+ Use for:
1348
+ - Typo fixes
1349
+ - Simple bug fixes
1350
+ - Minor adjustments
1351
+ - Urgent hotfixes`,
1352
+ delegatesTo: []
1353
+ },
1354
+ {
1355
+ name: "review",
1356
+ displayName: "@review",
1357
+ description: "Code review, debugging, and security audits",
1358
+ useWhen: "Reviewing code quality, finding bugs, security review, debugging issues",
1359
+ capabilities: [
1360
+ "Code review",
1361
+ "Security audit",
1362
+ "Performance analysis",
1363
+ "Bug finding",
1364
+ "Best practices enforcement"
1365
+ ],
1366
+ systemPrompt: `You are the review agent. Your role is to review and improve code quality.
1367
+
1368
+ Review checklist:
1369
+ 1. CORRECTNESS: Does the code do what it's supposed to?
1370
+ 2. SECURITY: Are there any security vulnerabilities?
1371
+ 3. PERFORMANCE: Are there performance issues?
1372
+ 4. MAINTAINABILITY: Is the code clean and readable?
1373
+ 5. TESTS: Are there adequate tests?
1374
+ 6. PATTERNS: Does it follow project conventions?
1375
+
1376
+ When reviewing:
1377
+ - Be specific about issues
1378
+ - Suggest fixes, not just problems
1379
+ - Prioritize by severity
1380
+ - Check for edge cases`,
1381
+ delegatesTo: []
1382
+ },
1383
+ {
1384
+ name: "scout",
1385
+ displayName: "@scout",
1386
+ description: "External research - library docs, GitHub patterns, frameworks",
1387
+ useWhen: "Researching external libraries, finding code patterns on GitHub, learning about frameworks",
1388
+ capabilities: [
1389
+ "Web research",
1390
+ "GitHub code search",
1391
+ "Documentation lookup",
1392
+ "Framework exploration",
1393
+ "Best practices research"
1394
+ ],
1395
+ systemPrompt: `You are the scout agent. Your role is to research external resources.
1396
+
1397
+ Research strategy:
1398
+ 1. UNDERSTAND what information is needed
1399
+ 2. SEARCH appropriate sources (docs, GitHub, web)
1400
+ 3. EVALUATE quality and relevance of findings
1401
+ 4. SUMMARIZE key findings concisely
1402
+ 5. PROVIDE actionable recommendations
1403
+
1404
+ Sources to use:
1405
+ - Official documentation
1406
+ - GitHub code examples
1407
+ - Stack Overflow (verified answers)
1408
+ - Framework guides
1409
+ - Community best practices
1410
+
1411
+ Always cite your sources and verify information accuracy.`,
1412
+ delegatesTo: []
1413
+ },
1414
+ {
1415
+ name: "explore",
1416
+ displayName: "@explore",
1417
+ description: "Fast codebase navigation and pattern search",
1418
+ useWhen: "Finding files, understanding codebase structure, searching for patterns in code",
1419
+ capabilities: [
1420
+ "File discovery",
1421
+ "Pattern search",
1422
+ "Codebase navigation",
1423
+ "Structure analysis",
1424
+ "Dependency mapping"
1425
+ ],
1426
+ systemPrompt: `You are the explore agent. Your role is to navigate and understand the codebase.
1427
+
1428
+ Exploration techniques:
1429
+ 1. FILE STRUCTURE: Understand project organization
1430
+ 2. GREP SEARCH: Find specific patterns or usages
1431
+ 3. DEPENDENCY ANALYSIS: Map relationships between modules
1432
+ 4. PATTERN DISCOVERY: Find existing patterns to follow
1433
+ 5. QUICK CONTEXT: Gather just enough context for the task
1434
+
1435
+ Focus on speed and accuracy. Provide concise summaries of findings.`,
1436
+ delegatesTo: []
1437
+ },
1438
+ {
1439
+ name: "vision",
1440
+ displayName: "@vision",
1441
+ description: "Multimodal analysis - mockups, PDFs, diagrams",
1442
+ useWhen: "Analyzing images, mockups, screenshots, PDFs, or diagrams",
1443
+ capabilities: [
1444
+ "Image analysis",
1445
+ "Mockup interpretation",
1446
+ "PDF extraction",
1447
+ "Diagram understanding",
1448
+ "UI/UX analysis"
1449
+ ],
1450
+ systemPrompt: `You are the vision agent. Your role is to analyze visual content.
1451
+
1452
+ Analysis approach:
1453
+ 1. OBSERVE: Carefully examine the visual content
1454
+ 2. EXTRACT: Identify key information, text, structure
1455
+ 3. INTERPRET: Understand the intent and requirements
1456
+ 4. TRANSLATE: Convert visual specs to actionable tasks
1457
+ 5. VALIDATE: Ensure accurate interpretation
1458
+
1459
+ For mockups:
1460
+ - Identify components and layout
1461
+ - Note colors, spacing, typography
1462
+ - Extract interaction patterns
1463
+
1464
+ For diagrams:
1465
+ - Understand relationships
1466
+ - Map to code structure
1467
+ - Note data flow`,
1468
+ delegatesTo: []
1469
+ }
1470
+ ];
1471
+ var AgentManager = class {
1472
+ config;
1473
+ agents;
1474
+ constructor(config) {
1475
+ this.config = config;
1476
+ this.agents = /* @__PURE__ */ new Map();
1477
+ for (const agent of DEFAULT_AGENTS) {
1478
+ this.agents.set(agent.name, agent);
1479
+ }
1480
+ }
1481
+ /**
1482
+ * List all available agents
1483
+ */
1484
+ listAgents() {
1485
+ return Array.from(this.agents.values());
1486
+ }
1487
+ /**
1488
+ * Get a specific agent
1489
+ */
1490
+ getAgent(name) {
1491
+ return this.agents.get(name);
1492
+ }
1493
+ /**
1494
+ * Get the default agent
1495
+ */
1496
+ getDefaultAgent() {
1497
+ const defaultName = this.config.agents.default;
1498
+ return this.agents.get(defaultName) || this.agents.get("build");
1499
+ }
1500
+ /**
1501
+ * Decide which agent should handle a task
1502
+ */
1503
+ decideAgent(task, _context) {
1504
+ const lowerTask = task.toLowerCase();
1505
+ for (const [name, agent] of this.agents) {
1506
+ if (lowerTask.includes(`@${name}`)) {
1507
+ return {
1508
+ agent,
1509
+ reason: `Explicitly requested @${name}`,
1510
+ shouldDelegate: false
1511
+ };
1512
+ }
1513
+ }
1514
+ if (this.matchesKeywords(lowerTask, ["plan", "architect", "design", "coordinate", "complex"])) {
1515
+ return {
1516
+ agent: this.agents.get("planner"),
1517
+ reason: "Task requires planning and coordination",
1518
+ shouldDelegate: true
1519
+ };
1520
+ }
1521
+ if (this.matchesKeywords(lowerTask, ["research", "docs", "documentation", "library", "framework", "how to"])) {
1522
+ return {
1523
+ agent: this.agents.get("scout"),
1524
+ reason: "Task requires external research",
1525
+ shouldDelegate: false
1526
+ };
1527
+ }
1528
+ if (this.matchesKeywords(lowerTask, ["review", "audit", "security", "check", "debug"])) {
1529
+ return {
1530
+ agent: this.agents.get("review"),
1531
+ reason: "Task requires code review or debugging",
1532
+ shouldDelegate: false
1533
+ };
1534
+ }
1535
+ if (this.matchesKeywords(lowerTask, ["find", "search", "where", "locate", "explore"])) {
1536
+ return {
1537
+ agent: this.agents.get("explore"),
1538
+ reason: "Task requires codebase exploration",
1539
+ shouldDelegate: false
1540
+ };
1541
+ }
1542
+ if (this.matchesKeywords(lowerTask, ["image", "mockup", "screenshot", "pdf", "diagram", "visual"])) {
1543
+ return {
1544
+ agent: this.agents.get("vision"),
1545
+ reason: "Task requires visual analysis",
1546
+ shouldDelegate: false
1547
+ };
1548
+ }
1549
+ if (this.matchesKeywords(lowerTask, ["fix quickly", "hotfix", "urgent", "quick fix", "typo"])) {
1550
+ return {
1551
+ agent: this.agents.get("rush"),
1552
+ reason: "Task is a quick fix",
1553
+ shouldDelegate: false
1554
+ };
1555
+ }
1556
+ return {
1557
+ agent: this.agents.get("build"),
1558
+ reason: "Default to build agent for implementation",
1559
+ shouldDelegate: false
1560
+ };
1561
+ }
1562
+ /**
1563
+ * Format agent instructions for prompt
1564
+ */
1565
+ formatAgentPrompt(agent) {
1566
+ return `# Agent: ${agent.displayName}
1567
+
1568
+ ${agent.systemPrompt}
1569
+
1570
+ ## Capabilities
1571
+ ${agent.capabilities.map((c) => `- ${c}`).join("\n")}
1572
+
1573
+ ${agent.delegatesTo.length > 0 ? `## Can Delegate To
1574
+ ${agent.delegatesTo.map((a) => `- @${a}`).join("\n")}` : ""}
1575
+ `;
1576
+ }
1577
+ matchesKeywords(text, keywords) {
1578
+ return keywords.some((kw) => text.includes(kw));
1579
+ }
1580
+ };
1581
+
1582
+ // src/core/commands.ts
1583
+ init_esm_shims();
1584
+ init_paths();
1585
+ import { readFile as readFile3, readdir as readdir2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1586
+ import { join as join4, basename as basename2, extname as extname2 } from "path";
1587
+ import matter2 from "gray-matter";
1588
+ var DEFAULT_COMMANDS = [
1589
+ // Core Workflow Commands (Beads integration)
1590
+ {
1591
+ name: "create",
1592
+ description: "Create a new Beads task for tracking",
1593
+ category: "core",
1594
+ usage: "/create <task description>",
1595
+ examples: ["/create Add user authentication", "/create Fix navigation bug"],
1596
+ content: `Create a new task in the Beads system (.beads/ directory).
1597
+
1598
+ ## Workflow
1599
+ 1. Create a new bead file with unique ID
1600
+ 2. Add task description, status, and notes
1601
+ 3. Set status to "in-progress"
1602
+ 4. Initialize working notes section
1603
+
1604
+ ## Required Output
1605
+ - Task ID for reference
1606
+ - Confirmation of task creation
1607
+ - Next steps`
1608
+ },
1609
+ {
1610
+ name: "plan",
1611
+ description: "Create a detailed implementation plan",
1612
+ category: "core",
1613
+ usage: "/plan <feature or task>",
1614
+ examples: ["/plan user authentication system", "/plan refactor database layer"],
1615
+ content: `Create a comprehensive plan before implementation.
1616
+
1617
+ ## Workflow
1618
+ 1. UNDERSTAND: Clarify requirements through Socratic questioning
1619
+ 2. RESEARCH: Check existing patterns and dependencies
1620
+ 3. BREAK DOWN: Create 2-5 minute sub-tasks with:
1621
+ - Exact file paths
1622
+ - Expected changes
1623
+ - Verification steps
1624
+ 4. DOCUMENT: Write plan to memory/plans/
1625
+
1626
+ ## Output Format
1627
+ \`\`\`markdown
1628
+ # Plan: [Feature Name]
1629
+
1630
+ ## Overview
1631
+ Brief description of the goal.
1632
+
1633
+ ## Tasks
1634
+ 1. [ ] Task 1 - file.ts
1635
+ 2. [ ] Task 2 - component.tsx
1636
+ ...
1637
+
1638
+ ## Dependencies
1639
+ - List dependencies
1640
+
1641
+ ## Risks
1642
+ - Potential issues
1643
+
1644
+ ## Verification
1645
+ - How to verify completion
1646
+ \`\`\``
1647
+ },
1648
+ {
1649
+ name: "implement",
1650
+ description: "Implement a planned task with TDD",
1651
+ category: "core",
1652
+ usage: "/implement <task reference>",
1653
+ examples: ["/implement task-001", '/implement "add login form"'],
1654
+ content: `Implement a task following TDD principles.
1655
+
1656
+ ## Workflow
1657
+ 1. LOAD: Get task details from .beads/ or plan
1658
+ 2. TEST: Write failing tests first (RED)
1659
+ 3. IMPLEMENT: Write minimal code to pass (GREEN)
1660
+ 4. REFACTOR: Clean up while keeping tests green
1661
+ 5. VERIFY: Run full test suite
1662
+
1663
+ ## Hard Gates
1664
+ Before marking complete:
1665
+ - [ ] All new tests pass
1666
+ - [ ] No regressions
1667
+ - [ ] Type check passes
1668
+ - [ ] Linting passes`
1669
+ },
1670
+ {
1671
+ name: "finish",
1672
+ description: "Complete a task with quality gates",
1673
+ category: "core",
1674
+ usage: "/finish [task-id]",
1675
+ examples: ["/finish", "/finish task-001"],
1676
+ content: `Complete the current task with mandatory quality checks.
1677
+
1678
+ ## Hard Gates (Must ALL Pass)
1679
+ 1. \`npm run typecheck\` - No type errors
1680
+ 2. \`npm run test\` - All tests pass
1681
+ 3. \`npm run lint\` - No linting errors
1682
+ 4. \`npm run build\` - Build succeeds
1683
+
1684
+ ## Workflow
1685
+ 1. Run all quality gates
1686
+ 2. If any fail, report issues and stop
1687
+ 3. If all pass, update task status to "completed"
1688
+ 4. Create summary of changes
1689
+ 5. Suggest commit message`
1690
+ },
1691
+ {
1692
+ name: "handoff",
1693
+ description: "Create handoff bundle for session continuity",
1694
+ category: "core",
1695
+ usage: "/handoff",
1696
+ examples: ["/handoff"],
1697
+ content: `Create a handoff bundle for context transfer to next session.
1698
+
1699
+ ## Workflow
1700
+ 1. Summarize current progress
1701
+ 2. Document:
1702
+ - What was completed
1703
+ - What remains
1704
+ - Current blockers
1705
+ - Key decisions made
1706
+ 3. Save to memory/handoffs/[timestamp].md
1707
+
1708
+ ## Output Format
1709
+ \`\`\`markdown
1710
+ # Handoff: [Date/Time]
1711
+
1712
+ ## Completed
1713
+ - List of completed items
1714
+
1715
+ ## In Progress
1716
+ - Current work state
1717
+
1718
+ ## Remaining
1719
+ - What still needs to be done
1720
+
1721
+ ## Context
1722
+ - Important context for next session
1723
+
1724
+ ## Next Steps
1725
+ - Recommended actions
1726
+ \`\`\``
1727
+ },
1728
+ {
1729
+ name: "resume",
1730
+ description: "Resume from last handoff",
1731
+ category: "core",
1732
+ usage: "/resume",
1733
+ examples: ["/resume"],
1734
+ content: `Resume work from the most recent handoff.
1735
+
1736
+ ## Workflow
1737
+ 1. Load latest handoff from memory/handoffs/
1738
+ 2. Display summary to user
1739
+ 3. Propose next actions
1740
+ 4. Continue from where left off`
1741
+ },
1742
+ // Quick Actions
1743
+ {
1744
+ name: "fix",
1745
+ description: "Quick fix for an issue",
1746
+ category: "quick",
1747
+ usage: "/fix <issue description>",
1748
+ examples: ["/fix button not clickable", "/fix type error in auth.ts"],
1749
+ content: `Quick fix with minimal ceremony.
1750
+
1751
+ ## Workflow
1752
+ 1. Identify the issue
1753
+ 2. Make minimal change to fix
1754
+ 3. Verify fix works
1755
+ 4. Run affected tests`
1756
+ },
1757
+ {
1758
+ name: "fix-types",
1759
+ description: "Fix TypeScript type errors",
1760
+ category: "quick",
1761
+ usage: "/fix-types [file]",
1762
+ examples: ["/fix-types", "/fix-types src/auth.ts"],
1763
+ content: `Fix TypeScript type errors systematically.
1764
+
1765
+ ## Workflow
1766
+ 1. Run \`npm run typecheck\`
1767
+ 2. Parse error output
1768
+ 3. Fix each error in dependency order
1769
+ 4. Verify all types pass`
1770
+ },
1771
+ {
1772
+ name: "fix-ci",
1773
+ description: "Fix CI/CD pipeline failures",
1774
+ category: "quick",
1775
+ usage: "/fix-ci",
1776
+ examples: ["/fix-ci"],
1777
+ content: `Diagnose and fix CI failures.
1778
+
1779
+ ## Workflow
1780
+ 1. Check CI logs for errors
1781
+ 2. Reproduce locally if possible
1782
+ 3. Fix issues in order:
1783
+ - Type errors
1784
+ - Test failures
1785
+ - Lint errors
1786
+ - Build errors
1787
+ 4. Verify full CI pipeline locally`
1788
+ },
1789
+ {
1790
+ name: "commit",
1791
+ description: "Create a well-formatted commit",
1792
+ category: "quick",
1793
+ usage: "/commit [message]",
1794
+ examples: ["/commit", '/commit "feat: add login"'],
1795
+ content: `Create a conventional commit.
1796
+
1797
+ ## Workflow
1798
+ 1. Stage changes: \`git add -A\`
1799
+ 2. Generate commit message following conventional commits:
1800
+ - feat: New feature
1801
+ - fix: Bug fix
1802
+ - docs: Documentation
1803
+ - refactor: Code refactoring
1804
+ - test: Adding tests
1805
+ - chore: Maintenance
1806
+ 3. Commit with message`
1807
+ },
1808
+ {
1809
+ name: "pr",
1810
+ description: "Create a pull request",
1811
+ category: "quick",
1812
+ usage: "/pr [title]",
1813
+ examples: ["/pr", '/pr "Add user authentication"'],
1814
+ content: `Create a pull request with proper description.
1815
+
1816
+ ## Workflow
1817
+ 1. Push current branch
1818
+ 2. Generate PR description:
1819
+ - Summary of changes
1820
+ - Related issues
1821
+ - Testing done
1822
+ - Screenshots if UI changes
1823
+ 3. Create PR via GitHub CLI or provide URL`
1824
+ },
1825
+ // Research & Analysis
1826
+ {
1827
+ name: "research",
1828
+ description: "Deep research on a topic",
1829
+ category: "research",
1830
+ usage: "/research <topic>",
1831
+ examples: ["/research React Server Components", "/research OAuth 2.0 best practices"],
1832
+ content: `Conduct thorough research and document findings.
1833
+
1834
+ ## Workflow
1835
+ 1. Search documentation and resources
1836
+ 2. Find code examples and patterns
1837
+ 3. Evaluate options and trade-offs
1838
+ 4. Document findings in memory/research/
1839
+
1840
+ ## Output
1841
+ - Summary of findings
1842
+ - Recommended approach
1843
+ - Code examples
1844
+ - Links to resources`
1845
+ },
1846
+ {
1847
+ name: "analyze-project",
1848
+ description: "Analyze project structure and patterns",
1849
+ category: "research",
1850
+ usage: "/analyze-project",
1851
+ examples: ["/analyze-project"],
1852
+ content: `Comprehensive project analysis.
1853
+
1854
+ ## Workflow
1855
+ 1. Scan directory structure
1856
+ 2. Identify:
1857
+ - Tech stack
1858
+ - Architecture patterns
1859
+ - Key dependencies
1860
+ - Coding conventions
1861
+ 3. Document findings in AGENTS.md`
1862
+ },
1863
+ {
1864
+ name: "review-codebase",
1865
+ description: "Review codebase quality",
1866
+ category: "research",
1867
+ usage: "/review-codebase [path]",
1868
+ examples: ["/review-codebase", "/review-codebase src/"],
1869
+ content: `Review codebase for quality issues.
1870
+
1871
+ ## Workflow
1872
+ 1. Check code quality metrics
1873
+ 2. Identify:
1874
+ - Code smells
1875
+ - Security issues
1876
+ - Performance concerns
1877
+ - Test coverage gaps
1878
+ 3. Prioritize findings
1879
+ 4. Suggest improvements`
1880
+ },
1881
+ // Design & Planning
1882
+ {
1883
+ name: "design",
1884
+ description: "Design a feature or system",
1885
+ category: "design",
1886
+ usage: "/design <feature>",
1887
+ examples: ["/design notification system", "/design API gateway"],
1888
+ content: `Design a feature with thorough planning.
1889
+
1890
+ ## Workflow
1891
+ 1. Requirements gathering (Socratic questioning)
1892
+ 2. Research existing solutions
1893
+ 3. Design options with trade-offs
1894
+ 4. Choose approach
1895
+ 5. Document design in memory/
1896
+
1897
+ ## Output
1898
+ - Design document
1899
+ - Architecture diagrams (described)
1900
+ - API contracts
1901
+ - Data models`
1902
+ },
1903
+ {
1904
+ name: "brainstorm",
1905
+ description: "Brainstorm ideas for a problem",
1906
+ category: "design",
1907
+ usage: "/brainstorm <problem>",
1908
+ examples: ["/brainstorm user retention", "/brainstorm performance optimization"],
1909
+ content: `Collaborative brainstorming session.
1910
+
1911
+ ## Workflow
1912
+ 1. Define the problem clearly
1913
+ 2. Generate diverse ideas (no judgement)
1914
+ 3. Group related ideas
1915
+ 4. Evaluate feasibility
1916
+ 5. Select top candidates`
1917
+ },
1918
+ // Git & Version Control
1919
+ {
1920
+ name: "branch",
1921
+ description: "Create a new feature branch",
1922
+ category: "git",
1923
+ usage: "/branch <name>",
1924
+ examples: ["/branch feat/auth", "/branch fix/navigation-bug"],
1925
+ content: `Create and switch to a new branch.
1926
+
1927
+ ## Workflow
1928
+ 1. Ensure clean working directory
1929
+ 2. Pull latest main/master
1930
+ 3. Create branch with naming convention:
1931
+ - feat/* for features
1932
+ - fix/* for bug fixes
1933
+ - refactor/* for refactoring
1934
+ - docs/* for documentation
1935
+ 4. Switch to new branch`
1936
+ },
1937
+ {
1938
+ name: "merge",
1939
+ description: "Merge current branch to target",
1940
+ category: "git",
1941
+ usage: "/merge [target]",
1942
+ examples: ["/merge", "/merge main"],
1943
+ content: `Merge current branch to target.
1944
+
1945
+ ## Workflow
1946
+ 1. Run quality gates first
1947
+ 2. Commit any pending changes
1948
+ 3. Switch to target branch
1949
+ 4. Pull latest
1950
+ 5. Merge feature branch
1951
+ 6. Resolve conflicts if any
1952
+ 7. Push`
1953
+ },
1954
+ // Utilities
1955
+ {
1956
+ name: "status",
1957
+ description: "Show current status overview",
1958
+ category: "utility",
1959
+ usage: "/status",
1960
+ examples: ["/status"],
1961
+ content: `Display comprehensive status.
1962
+
1963
+ ## Shows
1964
+ - Current task (from Beads)
1965
+ - Git status
1966
+ - Active branch
1967
+ - Pending changes
1968
+ - Test status
1969
+ - Recent activity`
1970
+ },
1971
+ {
1972
+ name: "help",
1973
+ description: "Show available commands",
1974
+ category: "utility",
1975
+ usage: "/help [command]",
1976
+ examples: ["/help", "/help plan"],
1977
+ content: `Display help information.
1978
+
1979
+ If no command specified, list all available commands.
1980
+ If command specified, show detailed help for that command.`
1981
+ },
1982
+ {
1983
+ name: "analyze-figma",
1984
+ description: "Analyze Figma design and extract design tokens using Figma API",
1985
+ category: "design",
1986
+ usage: "/analyze-figma <figma-url>",
1987
+ examples: [
1988
+ "/analyze-figma https://www.figma.com/design/...",
1989
+ "/analyze-figma [figma-url]"
1990
+ ],
1991
+ content: `Analyze a Figma design and extract all design tokens automatically using Figma API.
1992
+
1993
+ ## Workflow
1994
+
1995
+ **Step 1: Extract URL from User Input**
1996
+
1997
+ The Figma URL is provided in the SAME message as the command. Extract it:
1998
+ - Check the full user input message
1999
+ - Look for URL pattern: \`https://www.figma.com/design/...\` or \`http://www.figma.com/design/...\`
2000
+ - Extract the ENTIRE URL including all query parameters
2001
+ - If URL not found in current message, check previous messages
2002
+
2003
+ **Step 2: Check Tool Configuration**
2004
+
2005
+ Before calling the tool, verify that Figma tool is configured:
2006
+ - If not configured, inform user to run: \`aikit skills figma-analysis config\`
2007
+ - The tool requires a Figma Personal Access Token
2008
+
2009
+ **Step 3: Call MCP Tool**
2010
+
2011
+ **CRITICAL**: You MUST use the MCP tool \`tool_read_figma_design\`, NOT web fetch!
2012
+
2013
+ **The correct tool name is**: \`tool_read_figma_design\` (exposed via MCP)
2014
+
2015
+ **DO NOT use**:
2016
+ - \u274C \`read_figma_design\` (wrong - missing "tool_" prefix)
2017
+ - \u274C \`figma-analysis/read_figma_design\` (wrong format)
2018
+ - \u274C Web fetch (file requires authentication)
2019
+
2020
+ **DO use**:
2021
+ - \u2705 \`tool_read_figma_design\` (correct MCP tool name)
2022
+
2023
+ Use the MCP tool:
2024
+ \`\`\`
2025
+ Use MCP tool: tool_read_figma_design
2026
+ Arguments: { "url": "[extracted URL]" }
2027
+ \`\`\`
2028
+
2029
+ The tool has the Figma API token configured and will authenticate automatically.
2030
+
2031
+ This tool will:
2032
+ 1. Validate the Figma URL format
2033
+ 2. Check if Figma tool is configured
2034
+ 3. Call Figma API to fetch design data
2035
+ 4. Extract design tokens:
2036
+ - Colors (from fills and strokes)
2037
+ - Typography (font families, sizes, weights, line heights)
2038
+ - Spacing system (8px grid detection)
2039
+ - Components (from Figma components)
2040
+ - Screens/Frames (dimensions and names)
2041
+ - Breakpoints (common responsive breakpoints)
2042
+ 5. Return formatted markdown with all extracted tokens
2043
+
2044
+ **Step 4: Format and Save**
2045
+
2046
+ Format extracted tokens as structured markdown:
2047
+ \`\`\`markdown
2048
+ # Figma Design Analysis
2049
+
2050
+ **Source**: [Figma URL]
2051
+ **Analyzed**: [Date]
2052
+
2053
+ ## Screens/Pages
2054
+ - [List all screens]
2055
+
2056
+ ## Color Palette
2057
+ ### Primary Colors
2058
+ - Color Name: #hexcode
2059
+ [Continue for all colors]
2060
+
2061
+ ## Typography
2062
+ ### Font Families
2063
+ - Primary: Font Name
2064
+ [Continue]
2065
+
2066
+ ### Font Sizes
2067
+ - Heading 1: 48px
2068
+ [Continue for all sizes]
2069
+
2070
+ ## Spacing System
2071
+ - Base unit: 8px
2072
+ - Values used: [list]
2073
+
2074
+ ## Component Structure
2075
+ - Header: [description]
2076
+ [Continue for all components]
2077
+
2078
+ ## Layout Grid
2079
+ - Container max-width: [value]
2080
+ - Columns: [value]
2081
+
2082
+ ## Responsive Breakpoints
2083
+ - Mobile: [range]
2084
+ - Tablet: [range]
2085
+ - Desktop: [range]
2086
+
2087
+ ## Assets Needed
2088
+ ### Images
2089
+ - [List]
2090
+
2091
+ ### Icons
2092
+ - [List]
2093
+
2094
+ ### Fonts
2095
+ - [List]
2096
+ \`\`\`
2097
+
2098
+ Save to memory using memory-update tool:
2099
+ \`\`\`
2100
+ Use tool: memory-update
2101
+ Arguments: {
2102
+ "key": "research/figma-analysis",
2103
+ "content": "[formatted markdown]"
2104
+ }
2105
+ \`\`\`
2106
+
2107
+ **Step 5: Report Results**
2108
+
2109
+ Summarize what was extracted:
2110
+ - Number of colors found
2111
+ - Number of typography styles
2112
+ - Number of components
2113
+ - Number of screens/frames
2114
+ - Confirm save location: \`memory/research/figma-analysis.md\`
2115
+
2116
+ ## Important Notes
2117
+
2118
+ - **DO NOT** ask user to provide URL again - extract it from input
2119
+ - **DO NOT** wait - start immediately after extracting URL
2120
+ - The URL is in the SAME message as the command
2121
+ - The tool uses Figma API, so the file must be accessible with your API token
2122
+ - If the tool returns an error about configuration, guide user to run: \`aikit skills figma-analysis config\`
2123
+ - If the tool returns an error about access, verify the file is accessible with your token
2124
+
2125
+ The analysis will be saved automatically for later reference.`
2126
+ },
2127
+ {
2128
+ name: "refactor",
2129
+ description: "Refactor code to improve structure without changing behavior",
2130
+ category: "quick",
2131
+ usage: "/refactor [file or pattern]",
2132
+ examples: ["/refactor src/utils.ts", "/refactor duplicate code"],
2133
+ content: `Refactor code following best practices.
2134
+
2135
+ ## Workflow
2136
+ 1. Ensure tests are in place
2137
+ 2. Identify refactoring opportunities
2138
+ 3. Apply refactoring incrementally
2139
+ 4. Run tests after each change
2140
+ 5. Verify no behavior changes`
2141
+ },
2142
+ {
2143
+ name: "test",
2144
+ description: "Run tests and show results",
2145
+ category: "utility",
2146
+ usage: "/test [pattern]",
2147
+ examples: ["/test", "/test auth", "/test --watch"],
2148
+ content: `Run test suite and display results.
2149
+
2150
+ ## Workflow
2151
+ 1. Run test command: \`npm run test\`
2152
+ 2. Parse and display results
2153
+ 3. Show coverage if available
2154
+ 4. Highlight failures
2155
+ 5. Suggest fixes for failures`
2156
+ },
2157
+ {
2158
+ name: "lint",
2159
+ description: "Run linter and fix issues",
2160
+ category: "quick",
2161
+ usage: "/lint [--fix]",
2162
+ examples: ["/lint", "/lint --fix"],
2163
+ content: `Run linter and optionally fix issues.
2164
+
2165
+ ## Workflow
2166
+ 1. Run linter: \`npm run lint\`
2167
+ 2. Parse errors and warnings
2168
+ 3. If --fix flag, run auto-fix
2169
+ 4. Report remaining issues
2170
+ 5. Suggest manual fixes if needed`
2171
+ },
2172
+ {
2173
+ name: "deploy",
2174
+ description: "Deploy application to production",
2175
+ category: "utility",
2176
+ usage: "/deploy [environment]",
2177
+ examples: ["/deploy", "/deploy staging", "/deploy production"],
2178
+ content: `Deploy application with quality checks.
2179
+
2180
+ ## Workflow
2181
+ 1. Run quality gates (test, lint, build)
2182
+ 2. Check for uncommitted changes
2183
+ 3. Build production bundle
2184
+ 4. Deploy to target environment
2185
+ 5. Verify deployment success`
2186
+ },
2187
+ {
2188
+ name: "rollback",
2189
+ description: "Rollback to previous deployment",
2190
+ category: "utility",
2191
+ usage: "/rollback [version]",
2192
+ examples: ["/rollback", "/rollback v1.2.3"],
2193
+ content: `Rollback to previous version.
2194
+
2195
+ ## Workflow
2196
+ 1. Identify current version
2197
+ 2. List available versions
2198
+ 3. Confirm rollback target
2199
+ 4. Execute rollback
2200
+ 5. Verify rollback success`
2201
+ },
2202
+ {
2203
+ name: "logs",
2204
+ description: "View application logs",
2205
+ category: "utility",
2206
+ usage: "/logs [--tail] [--follow]",
2207
+ examples: ["/logs", "/logs --tail 100", "/logs --follow"],
2208
+ content: `View and filter application logs.
2209
+
2210
+ ## Workflow
2211
+ 1. Determine log location
2212
+ 2. Apply filters if specified
2213
+ 3. Display logs
2214
+ 4. If --follow, stream updates
2215
+ 5. Format for readability`
2216
+ }
2217
+ ];
2218
+ var CommandRunner = class {
2219
+ config;
2220
+ commandsCache = /* @__PURE__ */ new Map();
2221
+ constructor(config) {
2222
+ this.config = config;
2223
+ }
2224
+ /**
2225
+ * List all available commands
2226
+ */
2227
+ async listCommands() {
2228
+ const commands = [];
2229
+ for (const cmd of DEFAULT_COMMANDS) {
2230
+ commands.push({
2231
+ ...cmd,
2232
+ filePath: "built-in"
2233
+ });
2234
+ }
2235
+ const globalCommandsPath = paths.commands(paths.globalConfig());
2236
+ try {
2237
+ const globalCommands = await this.loadCommandsFromDir(globalCommandsPath);
2238
+ commands.push(...globalCommands);
2239
+ } catch {
2240
+ }
2241
+ const projectCommandsPath = paths.commands(this.config.configPath);
2242
+ if (projectCommandsPath !== globalCommandsPath) {
2243
+ try {
2244
+ const projectCommands = await this.loadCommandsFromDir(projectCommandsPath);
2245
+ for (const cmd of projectCommands) {
2246
+ const existingIndex = commands.findIndex((c) => c.name === cmd.name);
2247
+ if (existingIndex >= 0) {
2248
+ commands[existingIndex] = cmd;
2249
+ } else {
2250
+ commands.push(cmd);
2251
+ }
2252
+ }
2253
+ } catch {
2254
+ }
2255
+ }
2256
+ return commands.sort((a, b) => a.name.localeCompare(b.name));
2257
+ }
2258
+ /**
2259
+ * Get a specific command
2260
+ */
2261
+ async getCommand(name) {
2262
+ if (this.commandsCache.has(name)) {
2263
+ return this.commandsCache.get(name);
2264
+ }
2265
+ const commands = await this.listCommands();
2266
+ const command = commands.find((c) => c.name === name);
2267
+ if (command) {
2268
+ this.commandsCache.set(name, command);
2269
+ }
2270
+ return command || null;
2271
+ }
2272
+ /**
2273
+ * Create a custom command
2274
+ */
2275
+ async createCommand(name, options) {
2276
+ const configPath = options?.global ? paths.globalConfig() : this.config.configPath;
2277
+ const category = options?.category || "utility";
2278
+ const commandsDir = join4(paths.commands(configPath), category);
2279
+ await mkdir2(commandsDir, { recursive: true });
2280
+ const fileName = `${name}.md`;
2281
+ const filePath = join4(commandsDir, fileName);
2282
+ const frontmatter = {
2283
+ name,
2284
+ description: options?.description || `Custom command: ${name}`,
2285
+ category,
2286
+ usage: options?.usage || `/${name}`,
2287
+ examples: options?.examples || [`/${name}`]
2288
+ };
2289
+ const content = options?.content || `## /${name}
2290
+
2291
+ Describe what this command does.
2292
+
2293
+ ## Workflow
2294
+ 1. Step 1
2295
+ 2. Step 2
2296
+ 3. Step 3
2297
+ `;
2298
+ const fileContent = matter2.stringify(content, frontmatter);
2299
+ await writeFile2(filePath, fileContent);
2300
+ const command = {
2301
+ name,
2302
+ description: frontmatter.description,
2303
+ category,
2304
+ usage: frontmatter.usage,
2305
+ examples: frontmatter.examples,
2306
+ content,
2307
+ filePath
2308
+ };
2309
+ this.commandsCache.set(name, command);
2310
+ return command;
2311
+ }
2312
+ /**
2313
+ * Format command for agent consumption
2314
+ */
2315
+ formatForAgent(command) {
2316
+ return `# Command: /${command.name}
2317
+
2318
+ ## Usage
2319
+ \`${command.usage}\`
2320
+
2321
+ ## Description
2322
+ ${command.description}
2323
+
2324
+ ## Examples
2325
+ ${command.examples.map((e) => `- \`${e}\``).join("\n")}
2326
+
2327
+ ## Workflow
2328
+ ${command.content}
2329
+ `;
2330
+ }
2331
+ /**
2332
+ * Load commands from directory (recursively)
2333
+ */
2334
+ async loadCommandsFromDir(dir) {
2335
+ const commands = [];
2336
+ const processDir = async (currentDir, category) => {
2337
+ try {
2338
+ const entries = await readdir2(currentDir, { withFileTypes: true });
2339
+ for (const entry of entries) {
2340
+ const fullPath = join4(currentDir, entry.name);
2341
+ if (entry.isDirectory()) {
2342
+ await processDir(fullPath, entry.name);
2343
+ } else if (extname2(entry.name) === ".md") {
2344
+ const content = await readFile3(fullPath, "utf-8");
2345
+ const { data, content: body } = matter2(content);
2346
+ const frontmatter = data;
2347
+ const name = frontmatter.name || basename2(entry.name, ".md");
2348
+ commands.push({
2349
+ name,
2350
+ description: frontmatter.description || "",
2351
+ category: frontmatter.category || category,
2352
+ usage: frontmatter.usage || `/${name}`,
2353
+ examples: frontmatter.examples || [],
2354
+ content: body.trim(),
2355
+ filePath: fullPath
2356
+ });
2357
+ }
2358
+ }
2359
+ } catch {
2360
+ return;
2361
+ }
2362
+ };
2363
+ await processDir(dir, "custom");
2364
+ return commands;
2365
+ }
2366
+ };
2367
+
2368
+ // src/core/tools.ts
2369
+ init_esm_shims();
2370
+ init_paths();
2371
+ init_logger();
2372
+ import { readdir as readdir4, writeFile as writeFile5, mkdir as mkdir5 } from "fs/promises";
2373
+ import { join as join8, extname as extname3 } from "path";
2374
+ import { z as z2 } from "zod";
2375
+ var ToolArgSchema = z2.object({
2376
+ type: z2.enum(["string", "number", "boolean", "array", "object"]),
2377
+ description: z2.string(),
2378
+ required: z2.boolean().optional().default(true),
2379
+ default: z2.any().optional()
2380
+ });
2381
+ function defineTool(config) {
2382
+ return config;
2383
+ }
2384
+ var BUILT_IN_TOOLS = [
2385
+ {
2386
+ name: "websearch",
2387
+ description: "Search the web for documentation, articles, and current information",
2388
+ args: {
2389
+ query: {
2390
+ type: "string",
2391
+ description: "The search query",
2392
+ required: true
2393
+ },
2394
+ numResults: {
2395
+ type: "number",
2396
+ description: "Number of results to return (default: 5)",
2397
+ required: false,
2398
+ default: 5
2399
+ }
2400
+ },
2401
+ async execute({ query }) {
2402
+ return `Web search results for: "${query}"
2403
+
2404
+ Note: Configure a search provider in AIKit settings for actual results.`;
2405
+ }
2406
+ },
2407
+ {
2408
+ name: "codesearch",
2409
+ description: "Search GitHub for code patterns and examples across millions of repositories",
2410
+ args: {
2411
+ query: {
2412
+ type: "string",
2413
+ description: "The code pattern or search query",
2414
+ required: true
2415
+ },
2416
+ language: {
2417
+ type: "string",
2418
+ description: "Programming language to filter by",
2419
+ required: false
2420
+ }
2421
+ },
2422
+ async execute({ query, language }) {
2423
+ const langFilter = language ? ` in ${language}` : "";
2424
+ return `GitHub code search for: "${query}"${langFilter}
2425
+
2426
+ Note: Configure GitHub integration in AIKit settings for actual results.`;
2427
+ }
2428
+ },
2429
+ {
2430
+ name: "memory-read",
2431
+ description: "Read from persistent memory (project or global)",
2432
+ args: {
2433
+ key: {
2434
+ type: "string",
2435
+ description: "The memory key to read",
2436
+ required: true
2437
+ }
2438
+ },
2439
+ async execute({ key }) {
2440
+ return `Memory read: ${key}`;
2441
+ }
2442
+ },
2443
+ {
2444
+ name: "memory-update",
2445
+ description: "Update persistent memory with new information",
2446
+ args: {
2447
+ key: {
2448
+ type: "string",
2449
+ description: "The memory key to update",
2450
+ required: true
2451
+ },
2452
+ content: {
2453
+ type: "string",
2454
+ description: "The content to write",
2455
+ required: true
2456
+ },
2457
+ append: {
2458
+ type: "boolean",
2459
+ description: "Whether to append to existing content (default: true)",
2460
+ required: false,
2461
+ default: true
2462
+ }
2463
+ },
2464
+ async execute({ key, append = true }) {
2465
+ return `Memory updated: ${key} (append: ${append})`;
2466
+ }
2467
+ },
2468
+ {
2469
+ name: "find_skills",
2470
+ description: "Find available workflow skills",
2471
+ args: {
2472
+ query: {
2473
+ type: "string",
2474
+ description: "Optional search query to filter skills",
2475
+ required: false
2476
+ }
2477
+ },
2478
+ async execute({ query }) {
2479
+ return `Skills matching: ${query || "all"}`;
2480
+ }
2481
+ },
2482
+ {
2483
+ name: "use_skill",
2484
+ description: "Load and use a specific skill workflow",
2485
+ args: {
2486
+ name: {
2487
+ type: "string",
2488
+ description: "Name of the skill to use",
2489
+ required: true
2490
+ }
2491
+ },
2492
+ async execute({ name }) {
2493
+ return `Loading skill: ${name}`;
2494
+ }
2495
+ },
2496
+ {
2497
+ name: "read_figma_design",
2498
+ description: "Read and analyze a Figma design using Figma API. Extracts design tokens including colors, typography, spacing, components, and layout.",
2499
+ args: {
2500
+ url: {
2501
+ type: "string",
2502
+ description: "Figma design URL to analyze (must start with https://www.figma.com/design/)",
2503
+ required: true
2504
+ }
2505
+ },
2506
+ async execute({ url }, context) {
2507
+ if (!url || typeof url !== "string") {
2508
+ return "Error: Invalid URL provided";
2509
+ }
2510
+ if (!url.startsWith("https://www.figma.com/design/") && !url.startsWith("http://www.figma.com/design/")) {
2511
+ return `Error: Invalid Figma URL format. URL must start with https://www.figma.com/design/
2512
+
2513
+ Provided URL: ${url}`;
2514
+ }
2515
+ const configManager = context?.toolConfigManager;
2516
+ if (!configManager) {
2517
+ return `Error: Tool configuration manager not available. This usually means the MCP server isn't properly initialized. Please restart OpenCode.
2518
+
2519
+ If the issue persists, configure Figma tool manually: aikit skills figma-analysis config`;
2520
+ }
2521
+ const isReady = await configManager.isToolReady("figma-analysis");
2522
+ if (!isReady) {
2523
+ const toolConfig = await configManager.getToolConfig("figma-analysis");
2524
+ if (toolConfig?.status === "error") {
2525
+ return `Error: Figma tool configuration error: ${toolConfig.errorMessage || "Unknown error"}
2526
+
2527
+ Please reconfigure: aikit skills figma-analysis config`;
2528
+ }
2529
+ return `Error: Figma tool is not configured. Please run: aikit skills figma-analysis config
2530
+
2531
+ This will guide you through setting up your Figma Personal Access Token.`;
2532
+ }
2533
+ const apiKey = await configManager.getApiKey("figma-analysis");
2534
+ if (!apiKey) {
2535
+ return `Error: Figma API key not found. Please run: aikit skills figma-analysis config`;
2536
+ }
2537
+ try {
2538
+ const { FigmaMcpClient: FigmaMcpClient2 } = await Promise.resolve().then(() => (init_figma_mcp(), figma_mcp_exports));
2539
+ const client = new FigmaMcpClient2(apiKey, configManager);
2540
+ const assetsDir = "./assets/images";
2541
+ const tokens = await client.extractDesignTokens(url, false, assetsDir);
2542
+ let result = `# Figma Design Analysis
2543
+
2544
+ `;
2545
+ result += `**URL**: ${url}
2546
+
2547
+ `;
2548
+ result += `## Design Structure & Content
2549
+
2550
+ `;
2551
+ if (tokens.structure) {
2552
+ result += `### Node Hierarchy (${tokens.structure.nodes.length} nodes)
2553
+
2554
+ `;
2555
+ result += `\`\`\`
2556
+ ${tokens.structure.hierarchy}
2557
+ \`\`\`
2558
+
2559
+ `;
2560
+ const textNodes = tokens.structure.nodes.filter((n) => n.type === "TEXT" && n.content);
2561
+ if (textNodes.length > 0) {
2562
+ result += `### Text Content (${textNodes.length} text elements)
2563
+
2564
+ `;
2565
+ textNodes.slice(0, 20).forEach((node) => {
2566
+ const preview = node.content && node.content.length > 100 ? node.content.substring(0, 100) + "..." : node.content;
2567
+ result += `- **${node.name}**: "${preview}"
2568
+ `;
2569
+ if (node.styles) {
2570
+ result += ` - Style: ${node.styles.fontFamily || "N/A"} ${node.styles.fontSize || "N/A"}px, weight ${node.styles.fontWeight || "N/A"}
2571
+ `;
2572
+ }
2573
+ });
2574
+ if (textNodes.length > 20) {
2575
+ result += `
2576
+ ... and ${textNodes.length - 20} more text elements
2577
+ `;
2578
+ }
2579
+ result += `
2580
+ `;
2581
+ }
2582
+ const frameNodes = tokens.structure.nodes.filter((n) => n.type === "FRAME" || n.type === "COMPONENT");
2583
+ if (frameNodes.length > 0) {
2584
+ result += `### Layout Structure (${frameNodes.length} frames/components)
2585
+
2586
+ `;
2587
+ frameNodes.slice(0, 15).forEach((node) => {
2588
+ result += `- **${node.name}** (${node.type})
2589
+ `;
2590
+ if (node.position) {
2591
+ result += ` - Position: x=${Math.round(node.position.x)}, y=${Math.round(node.position.y)}
2592
+ `;
2593
+ result += ` - Size: ${Math.round(node.position.width)}\xD7${Math.round(node.position.height)}px
2594
+ `;
2595
+ }
2596
+ if (node.styles?.layout) {
2597
+ result += ` - Layout: ${node.styles.layout}${node.styles.gap ? `, gap: ${node.styles.gap}px` : ""}
2598
+ `;
2599
+ }
2600
+ if (node.children && node.children.length > 0) {
2601
+ result += ` - Children: ${node.children.length}
2602
+ `;
2603
+ }
2604
+ });
2605
+ if (frameNodes.length > 15) {
2606
+ result += `
2607
+ ... and ${frameNodes.length - 15} more frames/components
2608
+ `;
2609
+ }
2610
+ result += `
2611
+ `;
2612
+ }
2613
+ }
2614
+ result += `## Design Tokens
2615
+
2616
+ `;
2617
+ if (tokens.colors.length > 0) {
2618
+ result += `### Colors (${tokens.colors.length} found)
2619
+
2620
+ `;
2621
+ tokens.colors.slice(0, 30).forEach((color) => {
2622
+ result += `- \`${color.hex}\`
2623
+ `;
2624
+ });
2625
+ if (tokens.colors.length > 30) {
2626
+ result += `
2627
+ ... and ${tokens.colors.length - 30} more colors
2628
+ `;
2629
+ }
2630
+ result += `
2631
+ `;
2632
+ }
2633
+ if (tokens.typography.length > 0) {
2634
+ result += `### Typography (${tokens.typography.length} styles)
2635
+
2636
+ `;
2637
+ tokens.typography.forEach((typography) => {
2638
+ result += `- **${typography.name}**: ${typography.fontFamily}, ${typography.fontSize}px, weight ${typography.fontWeight}, line-height ${typography.lineHeight}px
2639
+ `;
2640
+ });
2641
+ result += `
2642
+ `;
2643
+ }
2644
+ result += `### Spacing System
2645
+
2646
+ `;
2647
+ result += `- Base unit: ${tokens.spacing.unit}px
2648
+ `;
2649
+ result += `- Scale: ${tokens.spacing.scale.length > 0 ? tokens.spacing.scale.join(", ") : "Not detected"}
2650
+
2651
+ `;
2652
+ if (tokens.components.length > 0) {
2653
+ result += `### Components (${tokens.components.length} found)
2654
+
2655
+ `;
2656
+ tokens.components.forEach((component) => {
2657
+ result += `- **${component.name}**: ${component.type}${component.description ? ` - ${component.description}` : ""}
2658
+ `;
2659
+ });
2660
+ result += `
2661
+ `;
2662
+ }
2663
+ if (tokens.screens.length > 0) {
2664
+ result += `## Available Screens/Frames (${tokens.screens.length} found)
2665
+
2666
+ `;
2667
+ result += `**Please confirm which screen(s) you want to develop:**
2668
+
2669
+ `;
2670
+ tokens.screens.forEach((screen, index) => {
2671
+ result += `${index + 1}. **${screen.name}**
2672
+ `;
2673
+ result += ` - Size: ${screen.width}\xD7${screen.height}px
2674
+ `;
2675
+ result += ` - Type: ${screen.type}
2676
+ `;
2677
+ if (screen.childrenCount) {
2678
+ result += ` - Components: ${screen.childrenCount}
2679
+ `;
2680
+ }
2681
+ result += ` - ID: \`${screen.id}\`
2682
+
2683
+ `;
2684
+ });
2685
+ result += `
2686
+ **To proceed, simply reply with the screen number(s) or name(s) you want to develop.**
2687
+ `;
2688
+ result += `Example: "1" or "Main Page" or "1, 2, 3"
2689
+
2690
+ `;
2691
+ }
2692
+ result += `### Responsive Breakpoints
2693
+
2694
+ `;
2695
+ result += `- ${tokens.breakpoints.join("px, ")}px
2696
+
2697
+ `;
2698
+ if (tokens.assets && tokens.assets.length > 0) {
2699
+ result += `## Downloaded Assets (${tokens.assets.length} files)
2700
+
2701
+ `;
2702
+ result += `All assets have been downloaded to: \`${tokens.assets[0].path.split("/").slice(0, -1).join("/")}\`
2703
+
2704
+ `;
2705
+ result += `### Image Files
2706
+
2707
+ `;
2708
+ tokens.assets.forEach((asset) => {
2709
+ const relativePath = asset.path.replace(process.cwd() + "/", "");
2710
+ result += `- **${asset.nodeName}** (${asset.nodeType})
2711
+ `;
2712
+ result += ` - File: \`${relativePath}\`
2713
+ `;
2714
+ if (asset.width && asset.height) {
2715
+ result += ` - Size: ${Math.round(asset.width)}\xD7${Math.round(asset.height)}px
2716
+ `;
2717
+ }
2718
+ result += ` - Format: ${asset.format.toUpperCase()}
2719
+ `;
2720
+ result += ` - Usage: \`<img src="${relativePath}" alt="${asset.nodeName}" />\`
2721
+
2722
+ `;
2723
+ });
2724
+ }
2725
+ result += `## Implementation Guide
2726
+
2727
+ `;
2728
+ result += `### Structure Analysis
2729
+ `;
2730
+ result += `The design contains ${tokens.structure?.nodes.length || 0} nodes organized in a hierarchical structure.
2731
+ `;
2732
+ result += `Use the node hierarchy above to understand:
2733
+ `;
2734
+ result += `1. **Component structure** - How elements are organized
2735
+ `;
2736
+ result += `2. **Text content** - All text content from TEXT nodes
2737
+ `;
2738
+ result += `3. **Layout properties** - Flex direction, gaps, padding
2739
+ `;
2740
+ result += `4. **Positioning** - Exact x, y, width, height values
2741
+
2742
+ `;
2743
+ result += `### Next Steps
2744
+
2745
+ `;
2746
+ result += `1. Review the structure hierarchy to understand component organization
2747
+ `;
2748
+ result += `2. Extract text content from TEXT nodes for HTML content
2749
+ `;
2750
+ result += `3. Use position and size data for pixel-perfect CSS
2751
+ `;
2752
+ result += `4. Use layout properties (HORIZONTAL/VERTICAL) for flexbox/grid
2753
+ `;
2754
+ result += `5. Use extracted design tokens (colors, typography) for styling
2755
+ `;
2756
+ result += `6. Save this analysis: \`memory-update("research/figma-analysis", "[this analysis]")\`
2757
+ `;
2758
+ return result;
2759
+ } catch (error) {
2760
+ const errorMessage = error instanceof Error ? error.message : String(error);
2761
+ return `Error analyzing Figma design: ${errorMessage}
2762
+
2763
+ Please check:
2764
+ 1. The Figma URL is correct and accessible
2765
+ 2. Your Figma API token has proper permissions
2766
+ 3. The design file is shared with your account`;
2767
+ }
2768
+ }
2769
+ },
2770
+ {
2771
+ name: "analyze_figma",
2772
+ description: "Analyze a Figma design URL and extract all design tokens automatically. The URL should be provided in the user input after the command.",
2773
+ args: {
2774
+ url: {
2775
+ type: "string",
2776
+ description: "Figma design URL to analyze",
2777
+ required: true
2778
+ }
2779
+ },
2780
+ async execute({ url }) {
2781
+ return `Figma analysis tool called for: ${url}
2782
+
2783
+ Next steps:
2784
+ 1. Use @vision agent to analyze the design
2785
+ 2. Extract all design tokens
2786
+ 3. Save to memory/research/figma-analysis.md`;
2787
+ }
2788
+ },
2789
+ {
2790
+ name: "develop_figma_screen",
2791
+ description: "Smart workflow to develop a specific Figma screen: check current code, pull needed assets, plan, and develop. User just needs to confirm the screen number/name.",
2792
+ args: {
2793
+ figmaUrl: {
2794
+ type: "string",
2795
+ description: "Figma design URL",
2796
+ required: true
2797
+ },
2798
+ screenId: {
2799
+ type: "string",
2800
+ description: "Screen ID or name to develop (from read_figma_design output)",
2801
+ required: true
2802
+ }
2803
+ },
2804
+ async execute({ figmaUrl, screenId }, context) {
2805
+ const configManager = context?.toolConfigManager;
2806
+ if (!configManager) {
2807
+ return "Error: Tool configuration manager not available.";
2808
+ }
2809
+ const isReady = await configManager.isToolReady("figma-analysis");
2810
+ if (!isReady) {
2811
+ return "Error: Figma tool is not configured. Please run: aikit skills figma-analysis config";
2812
+ }
2813
+ const apiKey = await configManager.getApiKey("figma-analysis");
2814
+ if (!apiKey) {
2815
+ return "Error: Figma API key not found.";
2816
+ }
2817
+ try {
2818
+ const { FigmaMcpClient: FigmaMcpClient2 } = await Promise.resolve().then(() => (init_figma_mcp(), figma_mcp_exports));
2819
+ const { checkCurrentCodeStatus: checkCurrentCodeStatus2, compareCodeWithFigma: compareCodeWithFigma2 } = await Promise.resolve().then(() => (init_figma_screen_developer(), figma_screen_developer_exports));
2820
+ const client = new FigmaMcpClient2(apiKey, configManager);
2821
+ const tokens = await client.extractDesignTokens(figmaUrl, false, "./assets/images");
2822
+ const screenIdStr = String(screenId);
2823
+ const selectedScreen = tokens.screens?.find(
2824
+ (s) => s.id === screenIdStr || s.name.toLowerCase() === screenIdStr.toLowerCase()
2825
+ );
2826
+ if (!selectedScreen) {
2827
+ return `Error: Screen "${screenId}" not found. Available screens:
2828
+ ${tokens.screens?.map((s, i) => `${i + 1}. ${s.name} (ID: ${s.id})`).join("\n") || "None"}`;
2829
+ }
2830
+ const codeStatus = await checkCurrentCodeStatus2();
2831
+ const comparison = await compareCodeWithFigma2(tokens, selectedScreen.id);
2832
+ let downloadedAssets = [];
2833
+ const fileKey = client.extractFileKey(figmaUrl);
2834
+ if (fileKey) {
2835
+ try {
2836
+ const fileData = await client.getFileData(figmaUrl);
2837
+ const projectPath = process.cwd();
2838
+ const assetsDir = join8(projectPath, "assets", "images");
2839
+ const assets = await client.downloadAssets(fileKey, fileData.document, assetsDir, selectedScreen.id);
2840
+ downloadedAssets = assets || [];
2841
+ logger.info(`Downloaded ${downloadedAssets.length} assets for screen ${selectedScreen.name}`);
2842
+ } catch (error) {
2843
+ logger.warn(`Failed to download assets: ${error instanceof Error ? error.message : String(error)}`);
2844
+ downloadedAssets = [];
2845
+ }
2846
+ }
2847
+ let result = `# Development Plan for Screen: ${selectedScreen.name}
2848
+
2849
+ `;
2850
+ result += `## Current Code Status
2851
+
2852
+ `;
2853
+ result += `- HTML: ${codeStatus.hasHTML ? `\u2705 ${codeStatus.htmlFile}` : "\u274C Not found"}
2854
+ `;
2855
+ result += `- CSS: ${codeStatus.hasCSS ? `\u2705 ${codeStatus.cssFiles.length} files` : "\u274C Not found"}
2856
+ `;
2857
+ result += `- Assets: ${codeStatus.hasAssets ? `\u2705 ${codeStatus.assetCount} files` : "\u274C Not found"}
2858
+ `;
2859
+ result += `- Existing Sections: ${codeStatus.sections.length > 0 ? codeStatus.sections.join(", ") : "None"}
2860
+
2861
+ `;
2862
+ result += `## Comparison with Figma Design
2863
+
2864
+ `;
2865
+ result += `- Missing Sections: ${comparison.missingSections.length > 0 ? comparison.missingSections.join(", ") : "None"}
2866
+ `;
2867
+ result += `- Missing Assets: ${comparison.missingAssets.length > 0 ? comparison.missingAssets.join(", ") : "None"}
2868
+ `;
2869
+ result += `- Needs Update: ${comparison.needsUpdate ? "Yes" : "No"}
2870
+
2871
+ `;
2872
+ result += `## Recommendations
2873
+
2874
+ `;
2875
+ comparison.recommendations.forEach((rec, i) => {
2876
+ result += `${i + 1}. ${rec}
2877
+ `;
2878
+ });
2879
+ result += `
2880
+ `;
2881
+ result += `## Downloaded Assets (${downloadedAssets.length})
2882
+
2883
+ `;
2884
+ if (downloadedAssets.length > 0) {
2885
+ downloadedAssets.forEach((asset) => {
2886
+ const relativePath = asset.path.replace(process.cwd() + "/", "");
2887
+ result += `- **${asset.nodeName}** (${asset.nodeType})
2888
+ `;
2889
+ result += ` - File: \`${relativePath}\`
2890
+ `;
2891
+ if (asset.width && asset.height) {
2892
+ result += ` - Size: ${Math.round(asset.width)}\xD7${Math.round(asset.height)}px
2893
+ `;
2894
+ }
2895
+ result += ` - Usage: \`<img src="${relativePath}" alt="${asset.nodeName}" />\`
2896
+
2897
+ `;
2898
+ });
2899
+ result += `
2900
+ **\u26A0\uFE0F IMPORTANT: Use downloaded assets above instead of placeholder images!**
2901
+ `;
2902
+ result += `Replace all Unsplash/placeholder image URLs with the downloaded asset paths.
2903
+
2904
+ `;
2905
+ } else {
2906
+ result += `**No assets downloaded.** This may indicate:
2907
+ `;
2908
+ result += `1. The screen doesn't contain exportable image nodes
2909
+ `;
2910
+ result += `2. Assets download failed (check logs)
2911
+ `;
2912
+ result += `3. Figma API permissions issue
2913
+
2914
+ `;
2915
+ }
2916
+ result += `
2917
+ ## Next Steps
2918
+
2919
+ `;
2920
+ result += `1. Review the plan above
2921
+ `;
2922
+ result += `2. Use @build agent to implement missing sections
2923
+ `;
2924
+ result += `3. Use extracted design tokens for CSS variables
2925
+ `;
2926
+ result += `4. Use downloaded assets in HTML
2927
+ `;
2928
+ result += `5. Verify pixel-perfect match with Figma design
2929
+ `;
2930
+ return result;
2931
+ } catch (error) {
2932
+ return `Error: ${error instanceof Error ? error.message : String(error)}`;
2933
+ }
2934
+ }
2935
+ },
2936
+ {
2937
+ name: "list_session",
2938
+ description: "List previous sessions to discover what happened and when. Use this before read_session to find the right context.",
2939
+ args: {
2940
+ limit: {
2941
+ type: "number",
2942
+ description: "Maximum number of sessions to return (default: 10)",
2943
+ required: false,
2944
+ default: 10
2945
+ }
2946
+ },
2947
+ async execute({ limit = 10 }, context) {
2948
+ const config = context?.config;
2949
+ if (!config) {
2950
+ return "Error: Configuration not available";
2951
+ }
2952
+ const limitNum = typeof limit === "number" ? limit : 10;
2953
+ try {
2954
+ const { MemoryManager: MemoryManager2 } = await Promise.resolve().then(() => (init_memory(), memory_exports));
2955
+ const memory = new MemoryManager2(config);
2956
+ const memories = await memory.list();
2957
+ const handoffs = memories.filter((m) => m.type === "handoff").sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()).slice(0, limitNum);
2958
+ if (handoffs.length === 0) {
2959
+ return "No previous sessions found. Use /handoff to create a session handoff.";
2960
+ }
2961
+ let result = `# Previous Sessions (${handoffs.length})
2962
+
2963
+ `;
2964
+ handoffs.forEach((handoff, index) => {
2965
+ const sessionId = handoff.key.replace("handoffs/", "");
2966
+ result += `${index + 1}. **${sessionId}**
2967
+ `;
2968
+ result += ` - Updated: ${handoff.updatedAt.toLocaleString()}
2969
+ `;
2970
+ result += ` - Summary: ${handoff.summary}
2971
+
2972
+ `;
2973
+ });
2974
+ result += `
2975
+ Use \`read_session\` with a session ID to load full context.`;
2976
+ return result;
2977
+ } catch (error) {
2978
+ return `Error listing sessions: ${error instanceof Error ? error.message : String(error)}`;
2979
+ }
2980
+ }
2981
+ },
2982
+ {
2983
+ name: "read_session",
2984
+ description: "Load context from a previous session. Returns session summary, user tasks, and file changes.",
2985
+ args: {
2986
+ sessionId: {
2987
+ type: "string",
2988
+ description: 'Session ID from list_session (e.g., "2024-01-15T10-30-00")',
2989
+ required: true
2990
+ }
2991
+ },
2992
+ async execute({ sessionId }, context) {
2993
+ const config = context?.config;
2994
+ if (!config) {
2995
+ return "Error: Configuration not available";
2996
+ }
2997
+ try {
2998
+ const { MemoryManager: MemoryManager2 } = await Promise.resolve().then(() => (init_memory(), memory_exports));
2999
+ const memory = new MemoryManager2(config);
3000
+ const content = await memory.read(`handoffs/${sessionId}`);
3001
+ if (!content) {
3002
+ return `Session not found: ${sessionId}
3003
+
3004
+ Use \`list_session\` to see available sessions.`;
3005
+ }
3006
+ return `# Session Context: ${sessionId}
3007
+
3008
+ ${content}
3009
+
3010
+ ---
3011
+
3012
+ This context has been loaded. Use /resume to continue from this point.`;
3013
+ } catch (error) {
3014
+ return `Error reading session: ${error instanceof Error ? error.message : String(error)}`;
3015
+ }
3016
+ }
3017
+ }
3018
+ ];
3019
+ var ToolRegistry = class {
3020
+ config;
3021
+ tools = /* @__PURE__ */ new Map();
3022
+ toolConfigManager;
3023
+ constructor(config) {
3024
+ this.config = config;
3025
+ for (const tool of BUILT_IN_TOOLS) {
3026
+ this.tools.set(tool.name, tool);
3027
+ }
3028
+ }
3029
+ /**
3030
+ * Set tool config manager (for tools that need configuration)
3031
+ */
3032
+ setToolConfigManager(manager) {
3033
+ this.toolConfigManager = manager;
3034
+ }
3035
+ /**
3036
+ * List all available tools
3037
+ */
3038
+ async listTools() {
3039
+ await this.loadCustomTools();
3040
+ return Array.from(this.tools.values());
3041
+ }
3042
+ /**
3043
+ * Get a specific tool
3044
+ */
3045
+ getTool(name) {
3046
+ return this.tools.get(name);
3047
+ }
3048
+ /**
3049
+ * Register a new tool
3050
+ */
3051
+ registerTool(tool) {
3052
+ this.tools.set(tool.name, tool);
3053
+ }
3054
+ /**
3055
+ * Execute a tool
3056
+ */
3057
+ async executeTool(name, args, context) {
3058
+ const tool = this.tools.get(name);
3059
+ if (!tool) {
3060
+ throw new Error(`Tool not found: ${name}`);
3061
+ }
3062
+ for (const [argName, argDef] of Object.entries(tool.args)) {
3063
+ if (argDef.required && args[argName] === void 0) {
3064
+ throw new Error(`Missing required argument: ${argName}`);
3065
+ }
3066
+ }
3067
+ const mergedContext = {
3068
+ ...context,
3069
+ toolConfigManager: context?.toolConfigManager || this.toolConfigManager
3070
+ };
3071
+ if (tool.execute.length === 2) {
3072
+ return await tool.execute(args, mergedContext);
3073
+ }
3074
+ return await tool.execute(args);
3075
+ }
3076
+ /**
3077
+ * Create a custom tool
3078
+ */
3079
+ async createTool(name, options) {
3080
+ const configPath = options.global ? paths.globalConfig() : this.config.configPath;
3081
+ const toolsDir = paths.tools(configPath);
3082
+ await mkdir5(toolsDir, { recursive: true });
3083
+ const fileName = `${name}.ts`;
3084
+ const filePath = join8(toolsDir, fileName);
3085
+ const argsSchema = Object.entries(options.args).map(([argName, arg]) => ` ${argName}: {
3086
+ type: '${arg.type}',
3087
+ description: '${arg.description}',
3088
+ required: ${arg.required ?? true},
3089
+ }`).join(",\n");
3090
+ const content = `import { defineTool } from 'aikit';
3091
+
3092
+ export default defineTool({
3093
+ name: '${name}',
3094
+ description: '${options.description}',
3095
+ args: {
3096
+ ${argsSchema}
3097
+ },
3098
+ async execute(args) {
3099
+ ${options.code}
3100
+ }
3101
+ });
3102
+ `;
3103
+ await writeFile5(filePath, content);
3104
+ }
3105
+ /**
3106
+ * Format tool for agent consumption
3107
+ */
3108
+ formatForAgent(tool) {
3109
+ const argsDesc = Object.entries(tool.args).map(([name, arg]) => ` - ${name} (${arg.type}${arg.required ? ", required" : ""}): ${arg.description}`).join("\n");
3110
+ return `## Tool: ${tool.name}
3111
+
3112
+ ${tool.description}
3113
+
3114
+ ### Arguments
3115
+ ${argsDesc}
3116
+ `;
3117
+ }
3118
+ /**
3119
+ * Load custom tools from disk
3120
+ */
3121
+ async loadCustomTools() {
3122
+ const globalToolsPath = paths.tools(paths.globalConfig());
3123
+ await this.loadToolsFromDir(globalToolsPath);
3124
+ const projectToolsPath = paths.tools(this.config.configPath);
3125
+ if (projectToolsPath !== globalToolsPath) {
3126
+ await this.loadToolsFromDir(projectToolsPath);
3127
+ }
3128
+ }
3129
+ async loadToolsFromDir(dir) {
3130
+ let files;
3131
+ try {
3132
+ files = await readdir4(dir);
3133
+ } catch {
3134
+ return;
3135
+ }
3136
+ for (const file of files) {
3137
+ if (extname3(file) !== ".ts" && extname3(file) !== ".js") continue;
3138
+ const filePath = join8(dir, file);
3139
+ try {
3140
+ const toolModule = await import(`file://${filePath}`);
3141
+ const tool = toolModule.default;
3142
+ if (tool?.name && typeof tool.execute === "function") {
3143
+ tool.filePath = filePath;
3144
+ this.tools.set(tool.name, tool);
3145
+ }
3146
+ } catch (error) {
3147
+ logger.warn(`Failed to load tool from ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
3148
+ }
3149
+ }
3150
+ }
3151
+ };
3152
+
3153
+ // src/core/plugins.ts
3154
+ init_esm_shims();
3155
+ init_paths();
3156
+ init_logger();
3157
+ import { readdir as readdir5, writeFile as writeFile6, mkdir as mkdir6 } from "fs/promises";
3158
+ import { join as join9, basename as basename3, extname as extname4 } from "path";
3159
+ var PluginSystem = class {
3160
+ config;
3161
+ plugins = /* @__PURE__ */ new Map();
3162
+ loadedPlugins = /* @__PURE__ */ new Map();
3163
+ eventQueue = [];
3164
+ processing = false;
3165
+ constructor(config) {
3166
+ this.config = config;
3167
+ }
3168
+ /**
3169
+ * Initialize and load all plugins
3170
+ */
3171
+ async initialize() {
3172
+ await this.loadPlugins();
3173
+ for (const [name, info] of this.plugins) {
3174
+ if (info.enabled) {
3175
+ try {
3176
+ const handlers = await this.initializePlugin(info);
3177
+ this.loadedPlugins.set(name, handlers);
3178
+ info.handlers = handlers;
3179
+ } catch (error) {
3180
+ logger.warn(`Failed to initialize plugin ${name}: ${error instanceof Error ? error.message : String(error)}`);
3181
+ }
3182
+ }
3183
+ }
3184
+ }
3185
+ /**
3186
+ * List all available plugins
3187
+ */
3188
+ async listPlugins() {
3189
+ await this.loadPlugins();
3190
+ return Array.from(this.plugins.values());
3191
+ }
3192
+ /**
3193
+ * Enable a plugin
3194
+ */
3195
+ async enablePlugin(name) {
3196
+ const plugin = this.plugins.get(name);
3197
+ if (plugin) {
3198
+ plugin.enabled = true;
3199
+ if (!this.loadedPlugins.has(name)) {
3200
+ const handlers = await this.initializePlugin(plugin);
3201
+ this.loadedPlugins.set(name, handlers);
3202
+ plugin.handlers = handlers;
3203
+ }
3204
+ }
3205
+ }
3206
+ /**
3207
+ * Disable a plugin
3208
+ */
3209
+ disablePlugin(name) {
3210
+ const plugin = this.plugins.get(name);
3211
+ if (plugin) {
3212
+ plugin.enabled = false;
3213
+ this.loadedPlugins.delete(name);
3214
+ plugin.handlers = void 0;
3215
+ }
3216
+ }
3217
+ /**
3218
+ * Emit an event to all plugins
3219
+ */
3220
+ async emit(event) {
3221
+ this.eventQueue.push(event);
3222
+ if (!this.processing) {
3223
+ await this.processEventQueue();
3224
+ }
3225
+ }
3226
+ /**
3227
+ * Execute before hooks for tool execution
3228
+ */
3229
+ async executeBeforeHooks(_toolName, input) {
3230
+ let result = input;
3231
+ for (const handlers of this.loadedPlugins.values()) {
3232
+ if (handlers["tool.execute.before"]) {
3233
+ result = await handlers["tool.execute.before"](result);
3234
+ }
3235
+ }
3236
+ return result;
3237
+ }
3238
+ /**
3239
+ * Execute after hooks for tool execution
3240
+ */
3241
+ async executeAfterHooks(_toolName, input, output) {
3242
+ let result = output;
3243
+ for (const handlers of this.loadedPlugins.values()) {
3244
+ if (handlers["tool.execute.after"]) {
3245
+ result = await handlers["tool.execute.after"](input, result);
3246
+ }
3247
+ }
3248
+ return result;
3249
+ }
3250
+ /**
3251
+ * Create a new plugin
3252
+ */
3253
+ async createPlugin(name, options) {
3254
+ const configPath = options.global ? paths.globalConfig() : this.config.configPath;
3255
+ const pluginsDir = paths.plugins(configPath);
3256
+ await mkdir6(pluginsDir, { recursive: true });
3257
+ const fileName = `${name}.ts`;
3258
+ const filePath = join9(pluginsDir, fileName);
3259
+ const content = `import { Plugin } from 'aikit';
3260
+
3261
+ /**
3262
+ * ${options.description || `Custom plugin: ${name}`}
3263
+ */
3264
+ export const ${toPascalCase(name)}Plugin: Plugin = async ({ project, config, emit }) => {
3265
+ return {
3266
+ event: async ({ event }) => {
3267
+ ${options.code}
3268
+ }
3269
+ };
3270
+ };
3271
+
3272
+ export default ${toPascalCase(name)}Plugin;
3273
+ `;
3274
+ await writeFile6(filePath, content);
3275
+ }
3276
+ /**
3277
+ * Process event queue
3278
+ */
3279
+ async processEventQueue() {
3280
+ this.processing = true;
3281
+ while (this.eventQueue.length > 0) {
3282
+ const event = this.eventQueue.shift();
3283
+ for (const handlers of this.loadedPlugins.values()) {
3284
+ if (handlers.event) {
3285
+ try {
3286
+ await handlers.event(event);
3287
+ } catch (error) {
3288
+ logger.warn(`Plugin error handling event ${event.type}: ${error instanceof Error ? error.message : String(error)}`);
3289
+ }
3290
+ }
3291
+ }
3292
+ }
3293
+ this.processing = false;
3294
+ }
3295
+ /**
3296
+ * Load plugins from disk
3297
+ */
3298
+ async loadPlugins() {
3299
+ this.registerBuiltInPlugins();
3300
+ const globalPluginsPath = paths.plugins(paths.globalConfig());
3301
+ await this.loadPluginsFromDir(globalPluginsPath);
3302
+ const projectPluginsPath = paths.plugins(this.config.configPath);
3303
+ if (projectPluginsPath !== globalPluginsPath) {
3304
+ await this.loadPluginsFromDir(projectPluginsPath);
3305
+ }
3306
+ }
3307
+ registerBuiltInPlugins() {
3308
+ this.plugins.set("enforcer", {
3309
+ name: "enforcer",
3310
+ description: "Warns when session idles with TODO items remaining",
3311
+ enabled: true,
3312
+ filePath: "built-in"
3313
+ });
3314
+ this.plugins.set("compactor", {
3315
+ name: "compactor",
3316
+ description: "Warns when context usage reaches 70%, 85%, 95%",
3317
+ enabled: true,
3318
+ filePath: "built-in"
3319
+ });
3320
+ this.plugins.set("truncator", {
3321
+ name: "truncator",
3322
+ description: "Auto-truncates tool output to preserve context space",
3323
+ enabled: true,
3324
+ filePath: "built-in"
3325
+ });
3326
+ this.plugins.set("notification", {
3327
+ name: "notification",
3328
+ description: "OS notifications when OpenCode completes a session",
3329
+ enabled: true,
3330
+ filePath: "built-in"
3331
+ });
3332
+ this.plugins.set("session-management", {
3333
+ name: "session-management",
3334
+ description: "Cross-session context transfer based on handoffs",
3335
+ enabled: true,
3336
+ filePath: "built-in"
3337
+ });
3338
+ }
3339
+ async loadPluginsFromDir(dir) {
3340
+ let files;
3341
+ try {
3342
+ files = await readdir5(dir);
3343
+ } catch {
3344
+ return;
3345
+ }
3346
+ for (const file of files) {
3347
+ if (extname4(file) !== ".ts" && extname4(file) !== ".js") continue;
3348
+ const filePath = join9(dir, file);
3349
+ const name = basename3(file, extname4(file));
3350
+ this.plugins.set(name, {
3351
+ name,
3352
+ description: `Custom plugin: ${name}`,
3353
+ enabled: true,
3354
+ filePath
3355
+ });
3356
+ }
3357
+ }
3358
+ async initializePlugin(info) {
3359
+ if (info.filePath === "built-in") {
3360
+ return this.getBuiltInPluginHandlers(info.name);
3361
+ }
3362
+ try {
3363
+ const pluginModule = await import(`file://${info.filePath}`);
3364
+ const factory = pluginModule.default;
3365
+ const context = {
3366
+ project: {
3367
+ path: this.config.projectPath,
3368
+ name: basename3(this.config.projectPath)
3369
+ },
3370
+ config: this.config,
3371
+ emit: this.emit.bind(this)
3372
+ };
3373
+ return await factory(context);
3374
+ } catch (error) {
3375
+ logger.warn(`Failed to load plugin ${info.name}: ${error instanceof Error ? error.message : String(error)}`);
3376
+ return {};
3377
+ }
3378
+ }
3379
+ getBuiltInPluginHandlers(name) {
3380
+ switch (name) {
3381
+ case "enforcer":
3382
+ return {
3383
+ event: async (event) => {
3384
+ if (event.type === "session.idle") {
3385
+ logger.info("[Enforcer] Session idle - check for remaining work");
3386
+ }
3387
+ }
3388
+ };
3389
+ case "compactor":
3390
+ return {
3391
+ event: async (event) => {
3392
+ const usage = event.properties?.contextUsage;
3393
+ if (usage && usage > 70) {
3394
+ logger.info(`[Compactor] Context usage at ${usage}%`);
3395
+ }
3396
+ }
3397
+ };
3398
+ case "truncator":
3399
+ return {
3400
+ "tool.execute.after": async (_input, output) => {
3401
+ if (typeof output === "string" && output.length > 5e4) {
3402
+ return output.slice(0, 5e4) + "\n\n[Output truncated - exceeded 50KB limit]";
3403
+ }
3404
+ return output;
3405
+ }
3406
+ };
3407
+ case "notification":
3408
+ return {
3409
+ event: async (event) => {
3410
+ if (event.type === "session.idle") {
3411
+ try {
3412
+ const { exec: exec2 } = await import("child_process");
3413
+ const { promisify: promisify2 } = await import("util");
3414
+ const execAsync2 = promisify2(exec2);
3415
+ const platform = process.platform;
3416
+ const summary = event.properties?.summary || "Session completed";
3417
+ if (platform === "darwin") {
3418
+ await execAsync2(`osascript -e 'display notification "${summary}" with title "OpenCode Session Complete"'`);
3419
+ } else if (platform === "linux") {
3420
+ await execAsync2(`notify-send "OpenCode Session Complete" "${summary}"`);
3421
+ } else if (platform === "win32") {
3422
+ await execAsync2(`powershell -Command "New-BurntToastNotification -Text 'OpenCode Session Complete', '${summary}'"`);
3423
+ }
3424
+ } catch (error) {
3425
+ logger.warn(`[Notification] Failed to send notification: ${error instanceof Error ? error.message : String(error)}`);
3426
+ }
3427
+ }
3428
+ }
3429
+ };
3430
+ case "session-management":
3431
+ return {
3432
+ event: async (event) => {
3433
+ if (event.type === "session.idle") {
3434
+ logger.info("[Session Management] Session idle - context saved for next session");
3435
+ }
3436
+ }
3437
+ };
3438
+ default:
3439
+ return {};
3440
+ }
3441
+ }
3442
+ };
3443
+ function toPascalCase(str) {
3444
+ return str.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
3445
+ }
3446
+
3447
+ // src/index.ts
3448
+ init_memory();
3449
+
3450
+ // src/core/beads.ts
3451
+ init_esm_shims();
3452
+ init_paths();
3453
+ init_logger();
3454
+ import { readFile as readFile6, writeFile as writeFile7, readdir as readdir6, access as access3, constants as constants3, mkdir as mkdir7 } from "fs/promises";
3455
+ import { join as join10 } from "path";
3456
+ import { exec } from "child_process";
3457
+ import { promisify } from "util";
3458
+ var execAsync = promisify(exec);
3459
+ var BeadsIntegration = class {
3460
+ projectPath;
3461
+ constructor(projectPath) {
3462
+ this.projectPath = projectPath || process.cwd();
3463
+ }
3464
+ /**
3465
+ * Check if Beads CLI is installed
3466
+ */
3467
+ async isInstalled() {
3468
+ try {
3469
+ await execAsync("bd --version");
3470
+ return true;
3471
+ } catch {
3472
+ return false;
3473
+ }
3474
+ }
3475
+ /**
3476
+ * Get Beads version
3477
+ */
3478
+ async getVersion() {
3479
+ try {
3480
+ const { stdout } = await execAsync("bd --version");
3481
+ const match = stdout.match(/bd version (\S+)/);
3482
+ return match?.[1] || null;
3483
+ } catch {
3484
+ return null;
3485
+ }
3486
+ }
3487
+ /**
3488
+ * Check if Beads is initialized in project
3489
+ */
3490
+ async isInitialized() {
3491
+ const beadsDir = paths.beadsDir(this.projectPath);
3492
+ try {
3493
+ await access3(beadsDir, constants3.R_OK);
3494
+ return true;
3495
+ } catch {
3496
+ return false;
3497
+ }
3498
+ }
3499
+ /**
3500
+ * Initialize Beads in project
3501
+ */
3502
+ async init() {
3503
+ try {
3504
+ await execAsync("bd init", { cwd: this.projectPath });
3505
+ logger.success("Beads initialized");
3506
+ return true;
3507
+ } catch (error) {
3508
+ logger.error("Failed to initialize Beads:", error);
3509
+ return false;
3510
+ }
3511
+ }
3512
+ /**
3513
+ * Get current status
3514
+ */
3515
+ async getStatus() {
3516
+ const installed = await this.isInstalled();
3517
+ const version = await this.getVersion();
3518
+ const initialized = await this.isInitialized();
3519
+ let activeTasks = 0;
3520
+ let completedTasks = 0;
3521
+ let currentTask;
3522
+ if (initialized) {
3523
+ const beads = await this.listBeads();
3524
+ activeTasks = beads.filter((b) => b.status === "in-progress" || b.status === "todo").length;
3525
+ completedTasks = beads.filter((b) => b.status === "completed").length;
3526
+ const active = beads.find((b) => b.status === "in-progress");
3527
+ currentTask = active?.title;
3528
+ }
3529
+ return {
3530
+ installed,
3531
+ version: version || void 0,
3532
+ initialized,
3533
+ activeTasks,
3534
+ completedTasks,
3535
+ currentTask
3536
+ };
3537
+ }
3538
+ /**
3539
+ * List all beads in the project
3540
+ */
3541
+ async listBeads() {
3542
+ const beadsDir = paths.beadsDir(this.projectPath);
3543
+ const beads = [];
3544
+ try {
3545
+ const files = await readdir6(beadsDir);
3546
+ for (const file of files) {
3547
+ if (!file.match(/^bead-\d+\.md$/)) continue;
3548
+ const content = await readFile6(join10(beadsDir, file), "utf-8");
3549
+ const bead = this.parseBeadFile(file, content);
3550
+ if (bead) beads.push(bead);
3551
+ }
3552
+ } catch {
3553
+ }
3554
+ return beads.sort((a, b) => a.id.localeCompare(b.id));
3555
+ }
3556
+ /**
3557
+ * Get a specific bead
3558
+ */
3559
+ async getBead(id) {
3560
+ const beadsDir = paths.beadsDir(this.projectPath);
3561
+ const fileName = id.endsWith(".md") ? id : `${id}.md`;
3562
+ try {
3563
+ const content = await readFile6(join10(beadsDir, fileName), "utf-8");
3564
+ return this.parseBeadFile(fileName, content);
3565
+ } catch {
3566
+ return null;
3567
+ }
3568
+ }
3569
+ /**
3570
+ * Create a new bead
3571
+ */
3572
+ async createBead(title, description) {
3573
+ const beadsDir = paths.beadsDir(this.projectPath);
3574
+ await mkdir7(beadsDir, { recursive: true });
3575
+ const beads = await this.listBeads();
3576
+ const maxId = beads.reduce((max, b) => {
3577
+ const num = parseInt(b.id.replace("bead-", ""), 10);
3578
+ return num > max ? num : max;
3579
+ }, 0);
3580
+ const id = `bead-${String(maxId + 1).padStart(3, "0")}`;
3581
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3582
+ const content = `---
3583
+ id: ${id}
3584
+ title: ${title}
3585
+ status: in-progress
3586
+ created: ${now}
3587
+ updated: ${now}
3588
+ ---
3589
+
3590
+ # ${title}
3591
+
3592
+ ## Description
3593
+ ${description}
3594
+
3595
+ ## Notes
3596
+
3597
+
3598
+ ## Checklist
3599
+ - [ ] Requirements understood
3600
+ - [ ] Implementation complete
3601
+ - [ ] Tests passing
3602
+ - [ ] Code reviewed
3603
+
3604
+ ## Progress
3605
+
3606
+ `;
3607
+ await writeFile7(join10(beadsDir, `${id}.md`), content);
3608
+ return {
3609
+ id,
3610
+ title,
3611
+ description,
3612
+ status: "in-progress",
3613
+ createdAt: new Date(now),
3614
+ updatedAt: new Date(now),
3615
+ notes: []
3616
+ };
3617
+ }
3618
+ /**
3619
+ * Update bead status
3620
+ */
3621
+ async updateBeadStatus(id, status) {
3622
+ const beadsDir = paths.beadsDir(this.projectPath);
3623
+ const fileName = id.endsWith(".md") ? id : `${id}.md`;
3624
+ const filePath = join10(beadsDir, fileName);
3625
+ try {
3626
+ let content = await readFile6(filePath, "utf-8");
3627
+ content = content.replace(
3628
+ /status:\s*\w+/,
3629
+ `status: ${status}`
3630
+ );
3631
+ content = content.replace(
3632
+ /updated:\s*.+/,
3633
+ `updated: ${(/* @__PURE__ */ new Date()).toISOString()}`
3634
+ );
3635
+ await writeFile7(filePath, content);
3636
+ return true;
3637
+ } catch {
3638
+ return false;
3639
+ }
3640
+ }
3641
+ /**
3642
+ * Add note to bead
3643
+ */
3644
+ async addNote(id, note) {
3645
+ const beadsDir = paths.beadsDir(this.projectPath);
3646
+ const fileName = id.endsWith(".md") ? id : `${id}.md`;
3647
+ const filePath = join10(beadsDir, fileName);
3648
+ try {
3649
+ let content = await readFile6(filePath, "utf-8");
3650
+ const notesMatch = content.match(/## Notes\n([\s\S]*?)(?=\n##|$)/);
3651
+ if (notesMatch) {
3652
+ const timestamp = (/* @__PURE__ */ new Date()).toLocaleString();
3653
+ const newNote = `- [${timestamp}] ${note}`;
3654
+ content = content.replace(
3655
+ notesMatch[0],
3656
+ `## Notes
3657
+ ${notesMatch[1]}${newNote}
3658
+ `
3659
+ );
3660
+ }
3661
+ content = content.replace(
3662
+ /updated:\s*.+/,
3663
+ `updated: ${(/* @__PURE__ */ new Date()).toISOString()}`
3664
+ );
3665
+ await writeFile7(filePath, content);
3666
+ return true;
3667
+ } catch {
3668
+ return false;
3669
+ }
3670
+ }
3671
+ /**
3672
+ * Complete a bead with quality gates
3673
+ */
3674
+ async completeBead(id) {
3675
+ const gates = [
3676
+ { name: "Type Check", command: "npm run typecheck" },
3677
+ { name: "Tests", command: "npm run test" },
3678
+ { name: "Lint", command: "npm run lint" },
3679
+ { name: "Build", command: "npm run build" }
3680
+ ];
3681
+ const results = [];
3682
+ for (const gate of gates) {
3683
+ try {
3684
+ await execAsync(gate.command, { cwd: this.projectPath });
3685
+ results.push({ name: gate.name, passed: true });
3686
+ logger.success(`${gate.name}: passed`);
3687
+ } catch (error) {
3688
+ results.push({
3689
+ name: gate.name,
3690
+ passed: false,
3691
+ error: error instanceof Error ? error.message.slice(0, 200) : "Failed"
3692
+ });
3693
+ logger.error(`${gate.name}: failed`);
3694
+ }
3695
+ }
3696
+ const allPassed = results.every((r) => r.passed);
3697
+ if (allPassed) {
3698
+ await this.updateBeadStatus(id, "completed");
3699
+ await this.addNote(id, "Task completed - all quality gates passed");
3700
+ } else {
3701
+ await this.addNote(id, "Completion attempted but quality gates failed");
3702
+ }
3703
+ return {
3704
+ success: allPassed,
3705
+ gates: results
3706
+ };
3707
+ }
3708
+ /**
3709
+ * Get current active bead
3710
+ */
3711
+ async getCurrentBead() {
3712
+ const beads = await this.listBeads();
3713
+ return beads.find((b) => b.status === "in-progress") || null;
3714
+ }
3715
+ /**
3716
+ * Parse a bead file
3717
+ */
3718
+ parseBeadFile(fileName, content) {
3719
+ try {
3720
+ const id = fileName.replace(".md", "");
3721
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
3722
+ const frontmatter = frontmatterMatch?.[1] || "";
3723
+ const getValue = (key) => {
3724
+ const match = frontmatter.match(new RegExp(`${key}:\\s*(.+)`));
3725
+ return match?.[1]?.trim() || "";
3726
+ };
3727
+ const titleMatch = content.match(/^# (.+)/m);
3728
+ const title = getValue("title") || titleMatch?.[1] || id;
3729
+ const descMatch = content.match(/## Description\n([\s\S]*?)(?=\n##|$)/);
3730
+ const description = descMatch?.[1]?.trim() || "";
3731
+ const notesMatch = content.match(/## Notes\n([\s\S]*?)(?=\n##|$)/);
3732
+ const notesContent = notesMatch?.[1] || "";
3733
+ const notes = notesContent.split("\n").filter((line) => line.trim().startsWith("-")).map((line) => line.replace(/^-\s*/, "").trim());
3734
+ return {
3735
+ id,
3736
+ title,
3737
+ description,
3738
+ status: getValue("status") || "todo",
3739
+ createdAt: new Date(getValue("created") || Date.now()),
3740
+ updatedAt: new Date(getValue("updated") || Date.now()),
3741
+ notes
3742
+ };
3743
+ } catch {
3744
+ return null;
3745
+ }
3746
+ }
3747
+ };
3748
+
3749
+ // src/core/anti-hallucination.ts
3750
+ init_esm_shims();
3751
+ init_logger();
3752
+ import { readFile as readFile7, writeFile as writeFile8, readdir as readdir7, access as access4, constants as constants4 } from "fs/promises";
3753
+ import { join as join11 } from "path";
3754
+ var AntiHallucination = class {
3755
+ config;
3756
+ constructor(config) {
3757
+ this.config = config;
3758
+ }
3759
+ /**
3760
+ * Layer 1: Validate task exists before work begins
3761
+ */
3762
+ async validateTask(taskId) {
3763
+ const beadsDir = join11(this.config.projectPath, ".beads");
3764
+ try {
3765
+ await access4(beadsDir, constants4.R_OK);
3766
+ } catch {
3767
+ return {
3768
+ valid: false,
3769
+ error: 'No .beads directory found. Run "bd init" to initialize task tracking.'
3770
+ };
3771
+ }
3772
+ if (!taskId) {
3773
+ const files = await readdir7(beadsDir);
3774
+ const beadFiles = files.filter((f) => f.match(/^bead-\d+\.md$/));
3775
+ if (beadFiles.length === 0) {
3776
+ return {
3777
+ valid: false,
3778
+ error: "No tasks found. Create a task with /create before starting work."
3779
+ };
3780
+ }
3781
+ for (const file of beadFiles) {
3782
+ const content = await readFile7(join11(beadsDir, file), "utf-8");
3783
+ if (content.includes("status: in-progress") || content.includes("Status: In Progress")) {
3784
+ const id = file.replace(".md", "");
3785
+ const descMatch = content.match(/description:\s*(.+)/i) || content.match(/# (.+)/);
3786
+ return {
3787
+ valid: true,
3788
+ task: {
3789
+ id,
3790
+ description: descMatch?.[1] || "Unknown task",
3791
+ status: "in-progress"
3792
+ }
3793
+ };
3794
+ }
3795
+ }
3796
+ return {
3797
+ valid: false,
3798
+ error: "No active task found. Start a task with /create or /implement."
3799
+ };
3800
+ }
3801
+ const taskFile = join11(beadsDir, `${taskId}.md`);
3802
+ try {
3803
+ const content = await readFile7(taskFile, "utf-8");
3804
+ const statusMatch = content.match(/status:\s*(\w+)/i);
3805
+ const descMatch = content.match(/description:\s*(.+)/i) || content.match(/# (.+)/);
3806
+ return {
3807
+ valid: true,
3808
+ task: {
3809
+ id: taskId,
3810
+ description: descMatch?.[1] || "Unknown task",
3811
+ status: statusMatch?.[1] || "unknown"
3812
+ }
3813
+ };
3814
+ } catch {
3815
+ return {
3816
+ valid: false,
3817
+ error: `Task not found: ${taskId}`
3818
+ };
3819
+ }
3820
+ }
3821
+ /**
3822
+ * Layer 2: Check spec constraints
3823
+ */
3824
+ async checkSpec() {
3825
+ const specFile = this.config.antiHallucination.specFile;
3826
+ const specPath = join11(this.config.projectPath, specFile);
3827
+ try {
3828
+ const content = await readFile7(specPath, "utf-8");
3829
+ const constraints = {
3830
+ naming: this.extractConstraints(content, "Naming"),
3831
+ forbidden: this.extractConstraints(content, "Forbidden"),
3832
+ required: this.extractConstraints(content, "Required")
3833
+ };
3834
+ return { hasSpec: true, constraints };
3835
+ } catch {
3836
+ return { hasSpec: false };
3837
+ }
3838
+ }
3839
+ /**
3840
+ * Validate code against spec constraints
3841
+ */
3842
+ async validateAgainstSpec(code, filePath) {
3843
+ const spec = await this.checkSpec();
3844
+ const violations = [];
3845
+ if (!spec.hasSpec || !spec.constraints) {
3846
+ return violations;
3847
+ }
3848
+ for (const forbidden of spec.constraints.forbidden) {
3849
+ const pattern = this.patternToRegex(forbidden);
3850
+ if (pattern && pattern.test(code)) {
3851
+ violations.push({
3852
+ rule: "Forbidden Pattern",
3853
+ description: forbidden,
3854
+ severity: "error",
3855
+ location: filePath
3856
+ });
3857
+ }
3858
+ }
3859
+ return violations;
3860
+ }
3861
+ /**
3862
+ * Layer 3: Create review documentation
3863
+ */
3864
+ async createReview(changes) {
3865
+ const reviewFile = this.config.antiHallucination.reviewFile;
3866
+ const reviewPath = join11(this.config.projectPath, reviewFile);
3867
+ const content = `# Code Review
3868
+
3869
+ _Generated: ${(/* @__PURE__ */ new Date()).toISOString()}_
3870
+
3871
+ ## What Changed
3872
+
3873
+ ### Files Modified
3874
+ ${changes.filesChanged.map((f) => `- ${f}`).join("\n") || "- None"}
3875
+
3876
+ ### Functions Added
3877
+ ${changes.functionsAdded.map((f) => `- ${f}`).join("\n") || "- None"}
3878
+
3879
+ ### Tests Added
3880
+ ${changes.testsAdded.map((t) => `- ${t}`).join("\n") || "- None"}
3881
+
3882
+ ## What Was Skipped
3883
+ ${changes.skipped?.map((s) => `- ${s}`).join("\n") || "- Nothing skipped"}
3884
+
3885
+ ## Inconsistencies
3886
+ ${changes.inconsistencies?.map((i) => `- \u26A0\uFE0F ${i}`).join("\n") || "- None found"}
3887
+
3888
+ ## Verification
3889
+ - [ ] All tests pass
3890
+ - [ ] Type check passes
3891
+ - [ ] Linting passes
3892
+ - [ ] Build succeeds
3893
+ - [ ] Manual testing completed
3894
+ `;
3895
+ await writeFile8(reviewPath, content);
3896
+ return reviewPath;
3897
+ }
3898
+ /**
3899
+ * Verify completion (hard gates)
3900
+ */
3901
+ async verifyCompletion() {
3902
+ const { exec: exec2 } = await import("child_process");
3903
+ const { promisify: promisify2 } = await import("util");
3904
+ const execAsync2 = promisify2(exec2);
3905
+ const gates = [
3906
+ { name: "Type Check", command: "npm run typecheck" },
3907
+ { name: "Tests", command: "npm run test" },
3908
+ { name: "Lint", command: "npm run lint" },
3909
+ { name: "Build", command: "npm run build" }
3910
+ ];
3911
+ const results = [];
3912
+ for (const gate of gates) {
3913
+ try {
3914
+ await execAsync2(gate.command, { cwd: this.config.projectPath });
3915
+ results.push({ name: gate.name, passed: true });
3916
+ } catch (error) {
3917
+ results.push({
3918
+ name: gate.name,
3919
+ passed: false,
3920
+ error: error instanceof Error ? error.message : "Failed"
3921
+ });
3922
+ }
3923
+ }
3924
+ return {
3925
+ passed: results.every((r) => r.passed),
3926
+ gates: results
3927
+ };
3928
+ }
3929
+ /**
3930
+ * Recovery: Check for context loss
3931
+ */
3932
+ async checkContextLoss() {
3933
+ const handoffsDir = join11(this.config.configPath, "memory", "handoffs");
3934
+ try {
3935
+ const files = await readdir7(handoffsDir);
3936
+ const handoffs = files.filter((f) => f.endsWith(".md")).sort().reverse();
3937
+ if (handoffs.length > 0) {
3938
+ return {
3939
+ hasHandoff: true,
3940
+ latestHandoff: handoffs[0]
3941
+ };
3942
+ }
3943
+ } catch {
3944
+ }
3945
+ return { hasHandoff: false };
3946
+ }
3947
+ /**
3948
+ * Initialize spec.md template
3949
+ */
3950
+ async initSpec() {
3951
+ const specPath = join11(this.config.projectPath, this.config.antiHallucination.specFile);
3952
+ try {
3953
+ await access4(specPath, constants4.R_OK);
3954
+ logger.info("spec.md already exists");
3955
+ return;
3956
+ } catch {
3957
+ }
3958
+ const template = `# Project Specification
3959
+
3960
+ ## Constraints
3961
+
3962
+ ### Naming
3963
+ - Components: PascalCase
3964
+ - Files: kebab-case
3965
+ - Variables: camelCase
3966
+ - Constants: SCREAMING_SNAKE_CASE
3967
+
3968
+ ### Forbidden
3969
+ - No inline styles
3970
+ - No \`any\` types
3971
+ - No console.log in production code
3972
+ - No hardcoded secrets
3973
+ - No disabled ESLint rules
3974
+
3975
+ ### Required
3976
+ - JSDoc on all exported functions
3977
+ - Input validation on API routes
3978
+ - Error handling for async operations
3979
+ - Unit tests for business logic
3980
+
3981
+ ## Architecture
3982
+
3983
+ Describe your project architecture here.
3984
+
3985
+ ## Dependencies
3986
+
3987
+ List approved dependencies here.
3988
+ `;
3989
+ await writeFile8(specPath, template);
3990
+ logger.success("Created spec.md template");
3991
+ }
3992
+ /**
3993
+ * Extract constraints from a section
3994
+ */
3995
+ extractConstraints(content, section) {
3996
+ const sectionMatch = content.match(new RegExp(`### ${section}[\\s\\S]*?(?=###|$)`, "i"));
3997
+ if (!sectionMatch) return [];
3998
+ const lines = sectionMatch[0].split("\n");
3999
+ return lines.filter((line) => line.trim().startsWith("-")).map((line) => line.replace(/^-\s*/, "").trim());
4000
+ }
4001
+ /**
4002
+ * Convert a constraint description to a regex pattern
4003
+ */
4004
+ patternToRegex(description) {
4005
+ if (description.includes("console.log")) {
4006
+ return /console\.log/;
4007
+ }
4008
+ if (description.includes("any") && description.includes("type")) {
4009
+ return /:\s*any\b/;
4010
+ }
4011
+ if (description.includes("inline style")) {
4012
+ return /style=\{/;
4013
+ }
4014
+ return null;
4015
+ }
4016
+ };
4017
+
4018
+ // src/index.ts
4019
+ init_logger();
4020
+ init_paths();
4021
+ var VERSION = "0.1.0";
4022
+ export {
4023
+ AgentManager,
4024
+ AntiHallucination,
4025
+ BeadsIntegration,
4026
+ CommandRunner,
4027
+ Config,
4028
+ MemoryManager,
4029
+ PluginSystem,
4030
+ SkillEngine,
4031
+ ToolRegistry,
4032
+ VERSION,
4033
+ defineTool,
4034
+ loadConfig,
4035
+ logger,
4036
+ paths
4037
+ };
4038
+ //# sourceMappingURL=index.js.map