@adityaaria/spark 6.0.19 → 6.0.21

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.
@@ -14,6 +14,15 @@ const LOCK_FILE = path.join(process.cwd(), '.spark-lock.json');
14
14
  const server = http.createServer((req, res) => {
15
15
  res.setHeader('Access-Control-Allow-Origin', '*');
16
16
 
17
+ // Handle JSON body for POST
18
+ const getBody = (request) => new Promise((resolve) => {
19
+ let body = '';
20
+ request.on('data', chunk => body += chunk.toString());
21
+ request.on('end', () => {
22
+ try { resolve(JSON.parse(body)); } catch(e) { resolve({}); }
23
+ });
24
+ });
25
+
17
26
  // API Route: Get all markdown docs
18
27
  if (req.url === '/api/docs' && req.method === 'GET') {
19
28
  res.setHeader('Content-Type', 'application/json');
@@ -33,6 +42,129 @@ const server = http.createServer((req, res) => {
33
42
  }
34
43
  }
35
44
 
45
+ // API Route: Get File Tree (for Heatmap)
46
+ if (req.url === '/api/tree' && req.method === 'GET') {
47
+ res.setHeader('Content-Type', 'application/json');
48
+
49
+ const getDirectoryTree = (dirPath, relativePath = '') => {
50
+ const items = fs.readdirSync(dirPath);
51
+ let result = [];
52
+
53
+ for (const item of items) {
54
+ if (['node_modules', '.git', '.docs', '.agents', '.spark'].includes(item)) continue;
55
+
56
+ const fullPath = path.join(dirPath, item);
57
+ const itemRelativePath = path.join(relativePath, item);
58
+ const stat = fs.statSync(fullPath);
59
+
60
+ let isDanger = false;
61
+ let dangerReason = '';
62
+
63
+ const lowerItem = item.toLowerCase();
64
+ if (lowerItem.includes('controller')) {
65
+ isDanger = true;
66
+ dangerReason = 'Anti-Pattern: "God Object" tendency. Move business logic to specific Domain Services instead of Controllers.';
67
+ } else if (lowerItem.includes('utils')) {
68
+ isDanger = true;
69
+ dangerReason = 'Legacy Trap: "Utils" folders become untracked dumping grounds for dead code. Use specific domain modules.';
70
+ } else if (lowerItem.includes('helper')) {
71
+ isDanger = true;
72
+ dangerReason = 'Legacy Trap: "Helpers" lack clear boundaries and violate single responsibility principles.';
73
+ } else if (lowerItem.includes('api') && stat.isDirectory()) {
74
+ isDanger = true;
75
+ dangerReason = 'Anti-Pattern: Generic "API" folders often lead to poor separation of concerns. Group by feature/domain instead.';
76
+ }
77
+
78
+ if (stat.isDirectory()) {
79
+ const children = getDirectoryTree(fullPath, itemRelativePath);
80
+ result.push({
81
+ name: item,
82
+ path: itemRelativePath,
83
+ type: 'dir',
84
+ isDanger,
85
+ dangerReason,
86
+ children
87
+ });
88
+ } else {
89
+ result.push({
90
+ name: item,
91
+ path: itemRelativePath,
92
+ type: 'file',
93
+ isDanger,
94
+ dangerReason
95
+ });
96
+ }
97
+ }
98
+ return result;
99
+ };
100
+
101
+ return res.end(JSON.stringify({ tree: getDirectoryTree(process.cwd()) }));
102
+ }
103
+
104
+ // API Route: Get SPARK Readme (for Guide)
105
+ if (req.url === '/api/readme' && req.method === 'GET') {
106
+ res.setHeader('Content-Type', 'application/json');
107
+ const readmePath = path.join(__dirname, '../../README.md');
108
+ if (fs.existsSync(readmePath)) {
109
+ return res.end(JSON.stringify({ content: fs.readFileSync(readmePath, 'utf-8') }));
110
+ }
111
+ return res.end(JSON.stringify({ content: '# Documentation not found' }));
112
+ }
113
+
114
+ // API Route: Save Custom Skill (for Skill Studio)
115
+ if (req.url === '/api/skills' && req.method === 'GET') {
116
+ res.setHeader('Content-Type', 'application/json');
117
+ let skills = [];
118
+
119
+ // 1. Core skills
120
+ const coreDir = path.join(__dirname, '../../skills');
121
+ if (fs.existsSync(coreDir)) {
122
+ try {
123
+ const items = fs.readdirSync(coreDir);
124
+ for (const item of items) {
125
+ const stat = fs.statSync(path.join(coreDir, item));
126
+ if (stat.isDirectory()) skills.push({ name: item, type: 'core' });
127
+ }
128
+ } catch (e) {}
129
+ }
130
+
131
+ // 2. Custom skills
132
+ const customDir = path.join(process.cwd(), '.agents', 'skills');
133
+ if (fs.existsSync(customDir)) {
134
+ try {
135
+ const items = fs.readdirSync(customDir);
136
+ for (const item of items) {
137
+ const stat = fs.statSync(path.join(customDir, item));
138
+ if (stat.isDirectory()) skills.push({ name: item, type: 'custom' });
139
+ }
140
+ } catch (e) {}
141
+ }
142
+
143
+ return res.end(JSON.stringify({ skills }));
144
+ }
145
+
146
+ if (req.url === '/api/skills' && req.method === 'POST') {
147
+ res.setHeader('Content-Type', 'application/json');
148
+ getBody(req).then(data => {
149
+ if (!data.name || !data.content) {
150
+ return res.end(JSON.stringify({ error: 'Missing name or content' }));
151
+ }
152
+
153
+ // Save to a safe, user-level directory that SPARK updates won't touch
154
+ // The Anthropic/Gemini standard allows putting skills in .agents/skills/
155
+ const customSkillsDir = path.join(process.cwd(), '.agents', 'skills', data.name);
156
+
157
+ try {
158
+ fs.mkdirSync(customSkillsDir, { recursive: true });
159
+ fs.writeFileSync(path.join(customSkillsDir, 'SKILL.md'), data.content, 'utf-8');
160
+ return res.end(JSON.stringify({ success: true, path: customSkillsDir }));
161
+ } catch (e) {
162
+ return res.end(JSON.stringify({ error: e.message }));
163
+ }
164
+ });
165
+ return;
166
+ }
167
+
36
168
  // API Route: Get Spark Lock status
37
169
  if (req.url === '/api/status' && req.method === 'GET') {
38
170
  res.setHeader('Content-Type', 'application/json');