@1tool/js-boost 1.0.0

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.
@@ -0,0 +1,92 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { glob } from 'glob';
4
+
5
+ /**
6
+ * Read all markdown files from .ai/guidelines/
7
+ * Returns array of { filename, title, content }
8
+ */
9
+ export async function readGuidelines(aiDir) {
10
+ const guidelinesDir = path.join(aiDir, 'guidelines');
11
+ if (!fs.existsSync(guidelinesDir)) return [];
12
+
13
+ const files = await glob('**/*.md', { cwd: guidelinesDir, absolute: false });
14
+ files.sort();
15
+
16
+ return files.map(file => {
17
+ const fullPath = path.join(guidelinesDir, file);
18
+ const content = fs.readFileSync(fullPath, 'utf8').trim();
19
+ const firstLine = content.split('\n')[0];
20
+ const title = firstLine.startsWith('#')
21
+ ? firstLine.replace(/^#+\s*/, '').trim()
22
+ : path.basename(file, '.md');
23
+ return { filename: file, title, content };
24
+ });
25
+ }
26
+
27
+ /**
28
+ * Read all skills from .ai/skills/
29
+ * Each skill is a folder containing SKILL.md with YAML frontmatter
30
+ * Returns array of { name, description, dir, content }
31
+ */
32
+ export async function readSkills(aiDir) {
33
+ const skillsDir = path.join(aiDir, 'skills');
34
+ if (!fs.existsSync(skillsDir)) return [];
35
+
36
+ const skillFiles = await glob('*/SKILL.md', { cwd: skillsDir, absolute: false });
37
+ skillFiles.sort();
38
+
39
+ return skillFiles.map(file => {
40
+ const fullPath = path.join(skillsDir, file);
41
+ const content = fs.readFileSync(fullPath, 'utf8').trim();
42
+ const dir = path.dirname(file);
43
+
44
+ // Parse YAML frontmatter: ---\nname: ...\ndescription: ...\n---
45
+ let name = dir;
46
+ let description = '';
47
+ const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
48
+ if (fmMatch) {
49
+ const fm = fmMatch[1];
50
+ const nameMatch = fm.match(/^name:\s*(.+)$/m);
51
+ const descMatch = fm.match(/^description:\s*(.+)$/m);
52
+ if (nameMatch) name = nameMatch[1].trim();
53
+ if (descMatch) description = descMatch[1].trim();
54
+ } else {
55
+ // Fallback: try first heading
56
+ const headingMatch = content.match(/^#+\s*(.+)$/m);
57
+ if (headingMatch) name = headingMatch[1].trim();
58
+ }
59
+
60
+ return { name, description, dir, skillFile: file, content };
61
+ });
62
+ }
63
+
64
+ /**
65
+ * Read js-boost.config.json if it exists
66
+ */
67
+ export function readConfig(projectDir) {
68
+ const configPath = path.join(projectDir, 'js-boost.config.json');
69
+ if (!fs.existsSync(configPath)) return {};
70
+ try {
71
+ return JSON.parse(fs.readFileSync(configPath, 'utf8'));
72
+ } catch {
73
+ return {};
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Ensure a directory exists
79
+ */
80
+ export function ensureDir(dirPath) {
81
+ if (!fs.existsSync(dirPath)) {
82
+ fs.mkdirSync(dirPath, { recursive: true });
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Write a file, creating parent dirs as needed
88
+ */
89
+ export function writeFile(filePath, content) {
90
+ ensureDir(path.dirname(filePath));
91
+ fs.writeFileSync(filePath, content, 'utf8');
92
+ }
package/src/watch.js ADDED
@@ -0,0 +1,58 @@
1
+ import path from 'path';
2
+ import chalk from 'chalk';
3
+ import chokidar from 'chokidar';
4
+ import { generate } from './index.js';
5
+
6
+ export function watch(projectDir) {
7
+ const aiDir = path.join(projectDir, '.ai');
8
+ const configPath = path.join(projectDir, 'js-boost.config.json');
9
+
10
+ console.log('');
11
+ console.log(chalk.bold.blue('⚡ js-boost') + chalk.dim(' — watch mode'));
12
+ console.log(chalk.dim(` Watching: ${aiDir}`));
13
+ console.log(chalk.dim(' Press Ctrl+C to stop'));
14
+ console.log('');
15
+
16
+ // Initial generation
17
+ generate(projectDir).catch(err => {
18
+ console.error(chalk.red(' Error during initial generation:'), err.message);
19
+ });
20
+
21
+ const watcher = chokidar.watch([
22
+ path.join(aiDir, '**', '*.md'),
23
+ configPath,
24
+ ], {
25
+ ignoreInitial: true,
26
+ persistent: true,
27
+ });
28
+
29
+ let debounceTimer = null;
30
+
31
+ const handleChange = (filePath) => {
32
+ const rel = path.relative(projectDir, filePath);
33
+ console.log(chalk.dim(` Changed: ${rel}`));
34
+
35
+ // Debounce — wait 300ms after last change before regenerating
36
+ clearTimeout(debounceTimer);
37
+ debounceTimer = setTimeout(async () => {
38
+ try {
39
+ await generate(projectDir);
40
+ } catch (err) {
41
+ console.error(chalk.red(' Error during regeneration:'), err.message);
42
+ }
43
+ }, 300);
44
+ };
45
+
46
+ watcher
47
+ .on('add', handleChange)
48
+ .on('change', handleChange)
49
+ .on('unlink', handleChange)
50
+ .on('error', err => console.error(chalk.red(' Watcher error:'), err));
51
+
52
+ process.on('SIGINT', () => {
53
+ console.log('');
54
+ console.log(chalk.dim(' Stopping watcher...'));
55
+ watcher.close();
56
+ process.exit(0);
57
+ });
58
+ }