pixelcrew 0.2.2 → 0.2.6
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/README.md +389 -315
- package/bin/pixel-agents.js +306 -149
- package/package.json +3 -1
- package/skill/SKILL.md +185 -0
- package/skill/references/anti-ai-rubric.md +30 -0
- package/skill/references/anti-ai-slop-encyclopedia.md +181 -0
- package/skill/references/commands-reference.md +135 -0
- package/skill/references/testing-and-qa-strategy.md +25 -0
- package/skill/references/token-efficiency.md +20 -0
- package/src/adapters/adapter.interface.js +77 -0
- package/src/adapters/antigravity.js +138 -0
- package/src/adapters/claude-code.js +149 -0
- package/src/adapters/codex.js +131 -0
- package/src/adapters/cursor.js +133 -0
- package/src/adapters/generic.js +63 -0
- package/src/adapters/index.js +15 -0
- package/src/adapters/kiro.js +134 -0
- package/src/adapters/registry.js +150 -0
- package/src/backend/analyzer/complexity-analyzer.js +104 -0
- package/src/backend/analyzer/requirement-analyzer.js +213 -0
- package/src/backend/analyzer/threat-analyzer.js +122 -0
- package/src/backend/api/api-designer.js +90 -0
- package/src/backend/api/error-contract.js +132 -0
- package/src/backend/api/route-generator.js +92 -0
- package/src/backend/api/schema-generator.js +64 -0
- package/src/backend/architecture/adr-generator.js +62 -0
- package/src/backend/architecture/architecture-reasoner.js +119 -0
- package/src/backend/architecture/architecture-selector.js +57 -0
- package/src/backend/architecture/tradeoff-analyzer.js +92 -0
- package/src/backend/data/data-modeler.js +114 -0
- package/src/backend/data/index-analyzer.js +93 -0
- package/src/backend/data/query-analyzer.js +41 -0
- package/src/backend/data/schema-generator.js +92 -0
- package/src/backend/data/transaction-analyzer.js +49 -0
- package/src/backend/index.js +67 -0
- package/src/backend/observability/logging-engine.js +80 -0
- package/src/backend/observability/metrics-engine.js +31 -0
- package/src/backend/observability/tracing-engine.js +16 -0
- package/src/backend/patterns/pattern-registry.js +74 -0
- package/src/backend/patterns/pattern-selector.js +39 -0
- package/src/backend/patterns/tradeoff-engine.js +17 -0
- package/src/backend/performance/cache-analyzer.js +31 -0
- package/src/backend/performance/concurrency-analyzer.js +34 -0
- package/src/backend/performance/performance-engine.js +98 -0
- package/src/backend/performance/query-optimizer.js +16 -0
- package/src/backend/reliability/failure-analyzer.js +22 -0
- package/src/backend/reliability/idempotency-engine.js +56 -0
- package/src/backend/reliability/resilience-engine.js +141 -0
- package/src/backend/reliability/retry-analyzer.js +15 -0
- package/src/backend/security/auth-selector.js +33 -0
- package/src/backend/security/authorization-engine.js +90 -0
- package/src/backend/security/policy-engine.js +49 -0
- package/src/backend/security/secret-analyzer.js +39 -0
- package/src/backend/security/security-engine.js +122 -0
- package/src/backend/security/threat-model.js +59 -0
- package/src/backend/synthesis/backend-synthesizer.js +98 -0
- package/src/backend/synthesis/infrastructure-synthesizer.js +108 -0
- package/src/backend/synthesis/module-synthesizer.js +262 -0
- package/src/backend/validation/architecture-validator.js +70 -0
- package/src/backend/validation/backend-quality-validator.js +71 -0
- package/src/backend/validation/dependency-validator.js +20 -0
- package/src/backend/validation/performance-validator.js +38 -0
- package/src/backend/validation/security-validator.js +73 -0
- package/src/commands/adapt.js +105 -0
- package/src/commands/add.js +78 -0
- package/src/commands/animate.js +48 -0
- package/src/commands/assemble.js +57 -0
- package/src/commands/audit.js +93 -0
- package/src/commands/backend.js +125 -0
- package/src/commands/bento.js +25 -0
- package/src/commands/blueprint.js +65 -0
- package/src/commands/bolder.js +48 -0
- package/src/commands/boss-fight.js +60 -0
- package/src/commands/build.js +39 -0
- package/src/commands/calibrate.js +25 -0
- package/src/commands/chromatic.js +25 -0
- package/src/commands/clarify.js +52 -0
- package/src/commands/colorize.js +57 -0
- package/src/commands/command.interface.js +28 -0
- package/src/commands/commands.js +84 -0
- package/src/commands/craft.js +57 -0
- package/src/commands/crew.js +66 -0
- package/src/commands/critique.js +25 -0
- package/src/commands/de-slop.js +25 -0
- package/src/commands/delight.js +46 -0
- package/src/commands/deploy.js +42 -0
- package/src/commands/distill.js +48 -0
- package/src/commands/doctor.js +89 -0
- package/src/commands/document.js +83 -0
- package/src/commands/eight-bit.js +25 -0
- package/src/commands/extract.js +75 -0
- package/src/commands/fix.js +46 -0
- package/src/commands/harden.js +85 -0
- package/src/commands/index.js +67 -0
- package/src/commands/install.js +75 -0
- package/src/commands/layout.js +48 -0
- package/src/commands/live.js +44 -0
- package/src/commands/manifest.js +25 -0
- package/src/commands/office.js +25 -0
- package/src/commands/onboard.js +46 -0
- package/src/commands/oneshot.js +60 -0
- package/src/commands/optimize.js +46 -0
- package/src/commands/overdrive.js +46 -0
- package/src/commands/parser.js +143 -0
- package/src/commands/pixelcrew.js +96 -0
- package/src/commands/plan.js +66 -0
- package/src/commands/polish.js +45 -0
- package/src/commands/quieter.js +48 -0
- package/src/commands/recap.js +358 -0
- package/src/commands/registry.js +353 -0
- package/src/commands/render.js +26 -0
- package/src/commands/retrofit.js +25 -0
- package/src/commands/review.js +118 -0
- package/src/commands/roster.js +25 -0
- package/src/commands/sentinel.js +25 -0
- package/src/commands/shape.js +66 -0
- package/src/commands/skills.js +57 -0
- package/src/commands/status.js +48 -0
- package/src/commands/stop.js +50 -0
- package/src/commands/sync.js +58 -0
- package/src/commands/typeset.js +58 -0
- package/src/commands/warp.js +25 -0
- package/src/core/agent-runtime.js +94 -0
- package/src/core/event-bus.js +98 -0
- package/src/core/index.js +9 -0
- package/src/core/orchestrator.js +234 -0
- package/src/core/scheduler.js +124 -0
- package/src/core/task-graph.js +185 -0
- package/src/dashboard/app.js +1172 -30
- package/src/dashboard/index.html +223 -23
- package/src/dashboard/styles.css +870 -0
- package/src/index.js +16 -0
- package/src/orchestrator/artifact-graph.js +104 -0
- package/src/orchestrator/ast-normalizer.js +147 -0
- package/src/orchestrator/code-generator.js +846 -0
- package/src/orchestrator/design-engine.js +203 -0
- package/src/orchestrator/engine.js +318 -39
- package/src/orchestrator/oneshot.js +351 -1561
- package/src/orchestrator/ontology.js +394 -0
- package/src/orchestrator/planner.js +302 -0
- package/src/orchestrator/repair-planner.js +67 -0
- package/src/orchestrator/requirement-contract.js +177 -0
- package/src/orchestrator/requirement-validator.js +39 -0
- package/src/orchestrator/semantic-engine.js +582 -0
- package/src/orchestrator/semantic-reviewer.js +179 -0
- package/src/orchestrator/skills-registry.js +194 -0
- package/src/orchestrator/task-planner.js +111 -0
- package/src/orchestrator/task-queue.js +128 -0
- package/src/orchestrator/telemetry.js +209 -0
- package/src/orchestrator/ui-synthesizer.js +245 -0
- package/src/orchestrator/visual-critic.js +106 -0
- package/src/policies/architecture/layering.policy.js +14 -0
- package/src/policies/performance/indexing.policy.js +13 -0
- package/src/policies/reliability/resilience.policy.js +13 -0
- package/src/policies/security/auth.policy.js +31 -0
- package/src/protocol/agent.js +124 -0
- package/src/protocol/event.js +78 -0
- package/src/protocol/index.js +8 -0
- package/src/protocol/skill.js +54 -0
- package/src/protocol/task.js +100 -0
- package/src/scaffold/claude-generator.js +60 -0
- package/src/scaffold/commands-catalog.js +321 -0
- package/src/scaffold/cursor-generator.js +74 -0
- package/src/scaffold/ide-rules.js +133 -0
- package/src/scaffold/init.js +415 -91
- package/src/scaffold/installer.js +571 -0
- package/src/scaffold/kiro-generator.js +86 -0
- package/src/scaffold/skills-bundle.js +520 -0
- package/src/scaffold/templates.js +57 -4
- package/src/scaffold/workstations.js +364 -0
- package/src/server/server.js +292 -42
- package/src/utils/fs-safe.js +168 -0
- package/src/utils/markdown-report.js +175 -0
package/src/server/server.js
CHANGED
|
@@ -4,10 +4,16 @@ import fsSync from 'node:fs';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
|
|
7
|
-
export function createServer(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
export function createServer(engineOrRootDir, maybeEngine, options = {}) {
|
|
8
|
+
let engine = engineOrRootDir;
|
|
9
|
+
let rootDir = engine?.rootDir || process.cwd();
|
|
10
|
+
|
|
11
|
+
if (typeof engineOrRootDir === 'string') {
|
|
12
|
+
rootDir = engineOrRootDir;
|
|
13
|
+
engine = maybeEngine;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const dashboardDir = path.join(rootDir, '.pixel-crew', 'dashboard');
|
|
11
17
|
const fallbackDashboardDir = fileURLToPath(new URL('../dashboard', import.meta.url));
|
|
12
18
|
|
|
13
19
|
const sseClients = new Set();
|
|
@@ -80,15 +86,39 @@ export function createServer(engine, options = {}) {
|
|
|
80
86
|
|
|
81
87
|
// API Routes
|
|
82
88
|
if (pathname === '/api/info' && req.method === 'GET') {
|
|
89
|
+
let activeProv = { activeProvider: 'generic', activeProviderName: 'Generic CLI Runner', activeProviderIcon: '💻' };
|
|
90
|
+
if (engine.providerRegistry) {
|
|
91
|
+
try {
|
|
92
|
+
const scan = await engine.providerRegistry.scanEnvironment();
|
|
93
|
+
activeProv = {
|
|
94
|
+
activeProvider: scan.activeProvider,
|
|
95
|
+
activeProviderName: scan.activeProviderName,
|
|
96
|
+
activeProviderIcon: scan.activeProviderIcon
|
|
97
|
+
};
|
|
98
|
+
} catch {}
|
|
99
|
+
}
|
|
83
100
|
return sendJson(200, {
|
|
84
101
|
rootDir: engine.rootDir,
|
|
85
102
|
project: engine.getConfig()?.project || path.basename(engine.rootDir),
|
|
86
|
-
version: '0.
|
|
103
|
+
version: '0.2.5',
|
|
104
|
+
...activeProv
|
|
87
105
|
});
|
|
88
106
|
}
|
|
89
107
|
|
|
90
108
|
if (pathname === '/api/state' && req.method === 'GET') {
|
|
91
|
-
|
|
109
|
+
const state = engine.getState() || {};
|
|
110
|
+
let activeProv = { activeProvider: 'generic', activeProviderName: 'Generic CLI Runner', activeProviderIcon: '💻' };
|
|
111
|
+
if (engine.providerRegistry) {
|
|
112
|
+
try {
|
|
113
|
+
const scan = await engine.providerRegistry.scanEnvironment();
|
|
114
|
+
activeProv = {
|
|
115
|
+
activeProvider: scan.activeProvider,
|
|
116
|
+
activeProviderName: scan.activeProviderName,
|
|
117
|
+
activeProviderIcon: scan.activeProviderIcon
|
|
118
|
+
};
|
|
119
|
+
} catch {}
|
|
120
|
+
}
|
|
121
|
+
return sendJson(200, { ...state, ...activeProv });
|
|
92
122
|
}
|
|
93
123
|
|
|
94
124
|
if (pathname === '/api/config' && req.method === 'GET') {
|
|
@@ -103,11 +133,24 @@ export function createServer(engine, options = {}) {
|
|
|
103
133
|
'Connection': 'keep-alive'
|
|
104
134
|
});
|
|
105
135
|
|
|
136
|
+
let activeProv = { activeProvider: 'generic', activeProviderName: 'Generic CLI Runner', activeProviderIcon: '💻' };
|
|
137
|
+
if (engine.providerRegistry) {
|
|
138
|
+
try {
|
|
139
|
+
const scan = await engine.providerRegistry.scanEnvironment();
|
|
140
|
+
activeProv = {
|
|
141
|
+
activeProvider: scan.activeProvider,
|
|
142
|
+
activeProviderName: scan.activeProviderName,
|
|
143
|
+
activeProviderIcon: scan.activeProviderIcon
|
|
144
|
+
};
|
|
145
|
+
} catch {}
|
|
146
|
+
}
|
|
147
|
+
|
|
106
148
|
// Send initial history
|
|
107
149
|
const initialPayload = `event: init\ndata: ${JSON.stringify({
|
|
108
|
-
state: engine.getState(),
|
|
150
|
+
state: { ...(engine.getState() || {}), ...activeProv },
|
|
109
151
|
config: engine.getConfig(),
|
|
110
|
-
history: engine.getEvents()
|
|
152
|
+
history: engine.getEvents(),
|
|
153
|
+
provider: activeProv
|
|
111
154
|
})}\n\n`;
|
|
112
155
|
res.write(initialPayload);
|
|
113
156
|
|
|
@@ -119,6 +162,82 @@ export function createServer(engine, options = {}) {
|
|
|
119
162
|
return;
|
|
120
163
|
}
|
|
121
164
|
|
|
165
|
+
if (pathname === '/api/command' && req.method === 'POST') {
|
|
166
|
+
const body = await readBody();
|
|
167
|
+
const input = body.input || body.command || body.prompt || '';
|
|
168
|
+
try {
|
|
169
|
+
const result = await engine.executeCommand(input, body.options || {});
|
|
170
|
+
return sendJson(200, result);
|
|
171
|
+
} catch (err) {
|
|
172
|
+
return sendJson(500, { success: false, error: err.message });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (pathname === '/api/commands/autocomplete' && req.method === 'GET') {
|
|
177
|
+
const query = url.searchParams.get('q') || '';
|
|
178
|
+
const suggestions = engine.commandRegistry ? engine.commandRegistry.getAutocompleteSuggestions(query) : [];
|
|
179
|
+
return sendJson(200, { suggestions });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (pathname === '/api/providers' && req.method === 'GET') {
|
|
183
|
+
if (engine.providerRegistry) {
|
|
184
|
+
const scan = await engine.providerRegistry.scanEnvironment(true);
|
|
185
|
+
return sendJson(200, {
|
|
186
|
+
activeProvider: scan.activeProvider,
|
|
187
|
+
activeProviderName: scan.activeProviderName,
|
|
188
|
+
activeProviderIcon: scan.activeProviderIcon,
|
|
189
|
+
activeProviderDescription: scan.activeProviderDescription,
|
|
190
|
+
available: scan.available.map(a => ({
|
|
191
|
+
id: a.id,
|
|
192
|
+
name: a.name,
|
|
193
|
+
icon: a.icon || '🤖',
|
|
194
|
+
description: a.description,
|
|
195
|
+
capabilities: a.capabilities
|
|
196
|
+
})),
|
|
197
|
+
missing: scan.missing.map(a => ({
|
|
198
|
+
id: a.id,
|
|
199
|
+
name: a.name,
|
|
200
|
+
icon: a.icon || '🤖'
|
|
201
|
+
}))
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
return sendJson(200, {
|
|
205
|
+
activeProvider: 'generic',
|
|
206
|
+
activeProviderName: 'Generic CLI Runner',
|
|
207
|
+
activeProviderIcon: '💻',
|
|
208
|
+
available: [{ id: 'generic', name: 'Generic CLI Runner', icon: '💻' }],
|
|
209
|
+
missing: []
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (pathname === '/api/providers/select' && req.method === 'POST') {
|
|
214
|
+
const body = await readBody();
|
|
215
|
+
const providerId = body.provider || body.id;
|
|
216
|
+
if (engine.providerRegistry && providerId) {
|
|
217
|
+
const adapter = engine.providerRegistry.getAdapter(providerId);
|
|
218
|
+
if (adapter) {
|
|
219
|
+
return sendJson(200, {
|
|
220
|
+
success: true,
|
|
221
|
+
activeProvider: adapter.id,
|
|
222
|
+
activeProviderName: adapter.name,
|
|
223
|
+
activeProviderIcon: adapter.icon || '🤖'
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return sendJson(400, { success: false, error: 'Provider not found' });
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (pathname === '/api/plan' && req.method === 'POST') {
|
|
231
|
+
const body = await readBody();
|
|
232
|
+
const prompt = body.prompt || 'Synthesize responsive web architecture';
|
|
233
|
+
try {
|
|
234
|
+
const result = await engine.executeCommand(`/plan ${prompt}`, body.options || {});
|
|
235
|
+
return sendJson(200, result);
|
|
236
|
+
} catch (err) {
|
|
237
|
+
return sendJson(500, { success: false, error: err.message });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
122
241
|
if (pathname === '/api/task' && req.method === 'POST') {
|
|
123
242
|
const body = await readBody();
|
|
124
243
|
const prompt = body.prompt || body.task || 'Optimize application workflow';
|
|
@@ -127,25 +246,39 @@ export function createServer(engine, options = {}) {
|
|
|
127
246
|
return sendJson(200, { status: 'accepted', prompt });
|
|
128
247
|
}
|
|
129
248
|
|
|
130
|
-
if (pathname === '/api/oneshot' && req.method === 'POST') {
|
|
249
|
+
if ((pathname === '/api/goal' || pathname === '/api/oneshot') && req.method === 'POST') {
|
|
131
250
|
const body = await readBody();
|
|
132
|
-
const prompt = body.prompt || 'Build a modern
|
|
251
|
+
const prompt = body.prompt || body.goal || 'Build a modern website for a design agency specializing in AI products';
|
|
133
252
|
const options = {
|
|
134
|
-
targetFramework: body.targetFramework || '
|
|
135
|
-
outputDir: body.outputDir
|
|
253
|
+
targetFramework: body.targetFramework || 'nextjs',
|
|
254
|
+
outputDir: body.outputDir
|
|
136
255
|
};
|
|
137
|
-
// Asynchronously start
|
|
256
|
+
// Asynchronously start Multi-Agent Synthesis / Goal pipeline
|
|
138
257
|
engine.submitOneShotTask(prompt, options).catch(console.error);
|
|
139
|
-
return sendJson(200, { status: '
|
|
258
|
+
return sendJson(200, { status: 'goal_started', prompt, options });
|
|
140
259
|
}
|
|
141
260
|
|
|
142
|
-
if (pathname === '/api/token-stats' && req.method === 'GET') {
|
|
143
|
-
|
|
261
|
+
if ((pathname === '/api/token-stats' || pathname === '/api/token-telemetry') && req.method === 'GET') {
|
|
262
|
+
const stats = engine.getTokenTelemetry ? engine.getTokenTelemetry() : null;
|
|
263
|
+
return sendJson(200, stats || {
|
|
144
264
|
rawTokensEstimated: 42500,
|
|
145
265
|
actualTokensUsed: 11800,
|
|
146
266
|
tokensSaved: 30700,
|
|
147
267
|
efficiencyRatio: 72,
|
|
148
268
|
savingsPercent: "72%",
|
|
269
|
+
costUsd: 0.0284,
|
|
270
|
+
perAgent: {
|
|
271
|
+
creativeDirector: 2400,
|
|
272
|
+
uxPlanner: 2140,
|
|
273
|
+
designSystem: 1850,
|
|
274
|
+
frontend: 6200,
|
|
275
|
+
backend: 2300,
|
|
276
|
+
database: 1770,
|
|
277
|
+
performance: 1530,
|
|
278
|
+
security: 1400,
|
|
279
|
+
qa: 2800,
|
|
280
|
+
orchestrator: 1200
|
|
281
|
+
},
|
|
149
282
|
strategiesActive: [
|
|
150
283
|
"AST Symbol Graph Skeletonization",
|
|
151
284
|
"Tiered Sliding Window Context Pruning",
|
|
@@ -157,15 +290,16 @@ export function createServer(engine, options = {}) {
|
|
|
157
290
|
}
|
|
158
291
|
|
|
159
292
|
if (pathname === '/api/site-preview' && req.method === 'GET') {
|
|
160
|
-
const sitePath = path.join(engine.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
293
|
+
const sitePath = engine.lastGeneratedSitePath || (engine.lastGeneratedOutputDir ? path.join(engine.lastGeneratedOutputDir, 'index.html') : null);
|
|
294
|
+
if (sitePath) {
|
|
295
|
+
try {
|
|
296
|
+
const html = await fs.readFile(sitePath, 'utf-8');
|
|
297
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
298
|
+
res.end(html);
|
|
299
|
+
return;
|
|
300
|
+
} catch {}
|
|
168
301
|
}
|
|
302
|
+
return sendJson(404, { error: 'No generated site preview found yet. Execute a task or goal first.' });
|
|
169
303
|
}
|
|
170
304
|
|
|
171
305
|
if (pathname === '/api/emit' && req.method === 'POST') {
|
|
@@ -268,24 +402,118 @@ export function createServer(engine, options = {}) {
|
|
|
268
402
|
heartbeat.unref();
|
|
269
403
|
|
|
270
404
|
let isWatching = false;
|
|
405
|
+
let workspaceWatcher = null;
|
|
406
|
+
const debouncedFiles = new Map();
|
|
271
407
|
|
|
272
|
-
|
|
408
|
+
function inferAgentFromFile(relPath) {
|
|
409
|
+
const p = relPath.toLowerCase();
|
|
410
|
+
if (p.includes('test') || p.includes('spec') || p.includes('playwright') || p.includes('vitest') || p.includes('cypress')) {
|
|
411
|
+
return { agent: 'qa', skill: 'playwright-e2e' };
|
|
412
|
+
}
|
|
413
|
+
if (p.includes('prisma') || p.includes('schema') || p.includes('sql') || p.includes('database') || p.includes('migration') || p.includes('postgres')) {
|
|
414
|
+
return { agent: 'database', skill: 'postgresql' };
|
|
415
|
+
}
|
|
416
|
+
if (p.includes('auth') || p.includes('security') || p.includes('sentinel') || p.includes('policy') || p.includes('cert')) {
|
|
417
|
+
return { agent: 'security', skill: 'security-audit' };
|
|
418
|
+
}
|
|
419
|
+
if (p.includes('perf') || p.includes('benchmark') || p.includes('docker') || p.includes('k8s') || p.includes('sre') || p.includes('lcp')) {
|
|
420
|
+
return { agent: 'performance', skill: 'performance-profiling' };
|
|
421
|
+
}
|
|
422
|
+
if (p.includes('api/') || p.includes('server/') || p.includes('routes/') || p.includes('services/') || p.includes('backend') || p.endsWith('.go') || p.endsWith('.py') || p.endsWith('.rs')) {
|
|
423
|
+
return { agent: 'backend', skill: 'backend' };
|
|
424
|
+
}
|
|
425
|
+
if (p.endsWith('.css') || p.endsWith('.scss') || p.includes('tailwind') || p.includes('theme') || p.includes('tokens')) {
|
|
426
|
+
return { agent: 'frontend', skill: 'design-tokens' };
|
|
427
|
+
}
|
|
428
|
+
return { agent: 'frontend', skill: 'react' };
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function startWatchers() {
|
|
273
432
|
if (isWatching) return;
|
|
274
433
|
isWatching = true;
|
|
275
434
|
|
|
276
|
-
//
|
|
277
|
-
|
|
278
|
-
|
|
435
|
+
// 1. Live Workspace File Watcher (Captures IDE code edits in real-time)
|
|
436
|
+
try {
|
|
437
|
+
workspaceWatcher = fsSync.watch(rootDir, { recursive: true }, (eventType, filename) => {
|
|
438
|
+
if (!filename) return;
|
|
439
|
+
|
|
440
|
+
// Filter out noisy / internal directories
|
|
441
|
+
const normalized = filename.replace(/\\/g, '/');
|
|
442
|
+
if (
|
|
443
|
+
normalized.startsWith('.git') ||
|
|
444
|
+
normalized.includes('node_modules') ||
|
|
445
|
+
normalized.includes('.next') ||
|
|
446
|
+
normalized.includes('.vite') ||
|
|
447
|
+
normalized.includes('dist') ||
|
|
448
|
+
normalized.includes('build') ||
|
|
449
|
+
normalized.includes('coverage') ||
|
|
450
|
+
normalized.includes('.DS_Store') ||
|
|
451
|
+
normalized.includes('events.jsonl') ||
|
|
452
|
+
normalized.includes('state.json') ||
|
|
453
|
+
normalized.includes('daemon.json') ||
|
|
454
|
+
normalized.startsWith('.gemini') ||
|
|
455
|
+
normalized.endsWith('.log')
|
|
456
|
+
) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const now = Date.now();
|
|
461
|
+
const lastSeen = debouncedFiles.get(normalized) || 0;
|
|
462
|
+
if (now - lastSeen < 200) return; // Debounce rapid writes
|
|
463
|
+
debouncedFiles.set(normalized, now);
|
|
464
|
+
|
|
465
|
+
const inferred = inferAgentFromFile(normalized);
|
|
466
|
+
const event = {
|
|
467
|
+
agent: inferred.agent,
|
|
468
|
+
type: 'tool',
|
|
469
|
+
skill: inferred.skill,
|
|
470
|
+
message: `Edited ${normalized} (+live IDE workspace update)`,
|
|
471
|
+
timestamp: now,
|
|
472
|
+
metadata: {
|
|
473
|
+
file: normalized,
|
|
474
|
+
action: 'EDIT',
|
|
475
|
+
source: 'ide_realtime_watcher'
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
if (engine && typeof engine.emitEvent === 'function') {
|
|
480
|
+
engine.emitEvent(event).catch(() => {});
|
|
481
|
+
} else {
|
|
482
|
+
engine.eventHistory.push(event);
|
|
483
|
+
if (engine.eventHistory.length > 300) engine.eventHistory.shift();
|
|
484
|
+
const payload = `event: agent_event\ndata: ${JSON.stringify(event)}\n\n`;
|
|
485
|
+
for (const client of sseClients) {
|
|
486
|
+
try { client.write(payload); } catch {}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
if (workspaceWatcher && typeof workspaceWatcher.on === 'function') {
|
|
492
|
+
workspaceWatcher.on('error', () => {});
|
|
493
|
+
}
|
|
494
|
+
} catch {}
|
|
495
|
+
|
|
496
|
+
// 2. Watch events.jsonl for direct external CLI / IDE writes
|
|
497
|
+
const possibleEventsPaths = [
|
|
498
|
+
engine?.eventsPath,
|
|
499
|
+
path.join(rootDir, '.pixel-crew', 'events.jsonl'),
|
|
500
|
+
path.join(rootDir, '.pixel-agents', 'events.jsonl')
|
|
501
|
+
].filter(Boolean);
|
|
502
|
+
|
|
503
|
+
const uniqueEventsPaths = Array.from(new Set(possibleEventsPaths));
|
|
504
|
+
|
|
505
|
+
for (const ePath of uniqueEventsPaths) {
|
|
506
|
+
let lastKnownEventCount = 0;
|
|
279
507
|
try {
|
|
280
|
-
const initialContent = fsSync.readFileSync(
|
|
508
|
+
const initialContent = fsSync.readFileSync(ePath, 'utf-8');
|
|
281
509
|
lastKnownEventCount = initialContent.trim().split('\n').filter(Boolean).length;
|
|
282
510
|
} catch {
|
|
283
511
|
lastKnownEventCount = 0;
|
|
284
512
|
}
|
|
285
513
|
|
|
286
|
-
fsSync.watchFile(
|
|
514
|
+
fsSync.watchFile(ePath, { interval: 150 }, async () => {
|
|
287
515
|
try {
|
|
288
|
-
const content = await fs.readFile(
|
|
516
|
+
const content = await fs.readFile(ePath, 'utf-8');
|
|
289
517
|
const lines = content.trim().split('\n').filter(Boolean);
|
|
290
518
|
if (lines.length > lastKnownEventCount) {
|
|
291
519
|
const newLines = lines.slice(lastKnownEventCount);
|
|
@@ -294,7 +522,7 @@ export function createServer(engine, options = {}) {
|
|
|
294
522
|
try {
|
|
295
523
|
const event = JSON.parse(line);
|
|
296
524
|
engine.eventHistory.push(event);
|
|
297
|
-
if (engine.eventHistory.length >
|
|
525
|
+
if (engine.eventHistory.length > 300) engine.eventHistory.shift();
|
|
298
526
|
|
|
299
527
|
const payload = `event: agent_event\ndata: ${JSON.stringify(event)}\n\n`;
|
|
300
528
|
for (const client of sseClients) {
|
|
@@ -307,13 +535,21 @@ export function createServer(engine, options = {}) {
|
|
|
307
535
|
});
|
|
308
536
|
}
|
|
309
537
|
|
|
310
|
-
// Watch state.json for direct external updates
|
|
311
|
-
|
|
312
|
-
|
|
538
|
+
// 3. Watch state.json for direct external updates
|
|
539
|
+
const possibleStatePaths = [
|
|
540
|
+
engine?.statePath,
|
|
541
|
+
path.join(rootDir, '.pixel-crew', 'state.json'),
|
|
542
|
+
path.join(rootDir, '.pixel-agents', 'state.json')
|
|
543
|
+
].filter(Boolean);
|
|
544
|
+
|
|
545
|
+
const uniqueStatePaths = Array.from(new Set(possibleStatePaths));
|
|
546
|
+
|
|
547
|
+
for (const sPath of uniqueStatePaths) {
|
|
548
|
+
fsSync.watchFile(sPath, { interval: 150 }, async () => {
|
|
313
549
|
try {
|
|
314
|
-
const content = await fs.readFile(
|
|
550
|
+
const content = await fs.readFile(sPath, 'utf-8');
|
|
315
551
|
const state = JSON.parse(content);
|
|
316
|
-
engine.state = state;
|
|
552
|
+
if (engine) engine.state = state;
|
|
317
553
|
const payload = `event: state_change\ndata: ${JSON.stringify({ state })}\n\n`;
|
|
318
554
|
for (const client of sseClients) {
|
|
319
555
|
try { client.write(payload); } catch {}
|
|
@@ -321,17 +557,31 @@ export function createServer(engine, options = {}) {
|
|
|
321
557
|
} catch {}
|
|
322
558
|
});
|
|
323
559
|
}
|
|
324
|
-
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Start watchers when server begins listening
|
|
563
|
+
server.on('listening', startWatchers);
|
|
325
564
|
|
|
326
565
|
server.on('close', () => {
|
|
327
566
|
clearInterval(heartbeat);
|
|
328
|
-
|
|
329
|
-
|
|
567
|
+
isWatching = false;
|
|
568
|
+
if (workspaceWatcher) {
|
|
569
|
+
try { workspaceWatcher.close(); } catch {}
|
|
330
570
|
}
|
|
331
|
-
|
|
332
|
-
|
|
571
|
+
const possiblePaths = [
|
|
572
|
+
engine?.eventsPath,
|
|
573
|
+
engine?.statePath,
|
|
574
|
+
path.join(rootDir, '.pixel-crew', 'events.jsonl'),
|
|
575
|
+
path.join(rootDir, '.pixel-agents', 'events.jsonl'),
|
|
576
|
+
path.join(rootDir, '.pixel-crew', 'state.json'),
|
|
577
|
+
path.join(rootDir, '.pixel-agents', 'state.json')
|
|
578
|
+
].filter(Boolean);
|
|
579
|
+
|
|
580
|
+
for (const p of possiblePaths) {
|
|
581
|
+
try { fsSync.unwatchFile(p); } catch {}
|
|
333
582
|
}
|
|
334
583
|
});
|
|
335
584
|
|
|
336
585
|
return server;
|
|
337
586
|
}
|
|
587
|
+
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PIXEL CREW — Safe File System Operations & Dry-Run Layer
|
|
3
|
+
*
|
|
4
|
+
* Provides atomic, reversible, and previewable filesystem write operations
|
|
5
|
+
* preventing unintentional mutations during development and testing.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs/promises';
|
|
9
|
+
import fsSync from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
|
|
12
|
+
export class DryRunReporter {
|
|
13
|
+
constructor(targetDir = process.cwd()) {
|
|
14
|
+
this.targetDir = targetDir;
|
|
15
|
+
this.creates = new Set();
|
|
16
|
+
this.updates = new Set();
|
|
17
|
+
this.dirs = new Set();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
recordCreate(filePath) {
|
|
21
|
+
const rel = path.relative(this.targetDir, filePath) || filePath;
|
|
22
|
+
this.creates.add(rel);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
recordUpdate(filePath) {
|
|
26
|
+
const rel = path.relative(this.targetDir, filePath) || filePath;
|
|
27
|
+
this.updates.add(rel);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
recordDir(dirPath) {
|
|
31
|
+
const rel = path.relative(this.targetDir, dirPath) || dirPath;
|
|
32
|
+
this.dirs.add(rel);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
hasChanges() {
|
|
36
|
+
return this.creates.size > 0 || this.updates.size > 0 || this.dirs.size > 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
formatOutput() {
|
|
40
|
+
const lines = [];
|
|
41
|
+
lines.push('\x1b[33m\x1b[1m[DRY RUN PREVIEW] — No files were written to disk.\x1b[0m\n');
|
|
42
|
+
|
|
43
|
+
if (!this.hasChanges()) {
|
|
44
|
+
lines.push(' No files or directories would be modified.');
|
|
45
|
+
return lines.join('\n');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (this.creates.size > 0) {
|
|
49
|
+
lines.push('\x1b[32mWould create files:\x1b[0m');
|
|
50
|
+
for (const f of Array.from(this.creates).sort()) {
|
|
51
|
+
lines.push(` \x1b[90m+\x1b[0m \x1b[36m${f}\x1b[0m`);
|
|
52
|
+
}
|
|
53
|
+
lines.push('');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (this.updates.size > 0) {
|
|
57
|
+
lines.push('\x1b[33mWould update files:\x1b[0m');
|
|
58
|
+
for (const f of Array.from(this.updates).sort()) {
|
|
59
|
+
lines.push(` \x1b[90m~\x1b[0m \x1b[33m${f}\x1b[0m`);
|
|
60
|
+
}
|
|
61
|
+
lines.push('');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (this.dirs.size > 0) {
|
|
65
|
+
lines.push('\x1b[35mWould ensure directories:\x1b[0m');
|
|
66
|
+
for (const d of Array.from(this.dirs).sort()) {
|
|
67
|
+
lines.push(` \x1b[90m📁\x1b[0m \x1b[35m${d}/\x1b[0m`);
|
|
68
|
+
}
|
|
69
|
+
lines.push('');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
lines.push('\x1b[90mRun without \x1b[0m\x1b[33m--dry-run\x1b[0m\x1b[90m to apply these changes.\x1b[0m');
|
|
73
|
+
return lines.join('\n');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
print() {
|
|
77
|
+
console.log('\n' + this.formatOutput() + '\n');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Safely writes a file, supporting dry-run mode and directory auto-creation
|
|
83
|
+
*/
|
|
84
|
+
export async function safeWriteFile(filePath, content, options = {}) {
|
|
85
|
+
const {
|
|
86
|
+
dryRun = false,
|
|
87
|
+
encoding = 'utf-8',
|
|
88
|
+
reporter = null,
|
|
89
|
+
targetDir = process.cwd()
|
|
90
|
+
} = options;
|
|
91
|
+
|
|
92
|
+
let exists = false;
|
|
93
|
+
try {
|
|
94
|
+
await fs.access(filePath);
|
|
95
|
+
exists = true;
|
|
96
|
+
} catch {
|
|
97
|
+
exists = false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (reporter) {
|
|
101
|
+
if (exists) {
|
|
102
|
+
reporter.recordUpdate(filePath);
|
|
103
|
+
} else {
|
|
104
|
+
reporter.recordCreate(filePath);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (dryRun) {
|
|
109
|
+
return { written: false, dryRun: true, path: filePath, action: exists ? 'update' : 'create' };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Ensure parent directory exists
|
|
113
|
+
const parentDir = path.dirname(filePath);
|
|
114
|
+
await fs.mkdir(parentDir, { recursive: true });
|
|
115
|
+
|
|
116
|
+
await fs.writeFile(filePath, content, encoding);
|
|
117
|
+
return { written: true, dryRun: false, path: filePath, action: exists ? 'update' : 'create' };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Safely creates a directory, supporting dry-run mode
|
|
122
|
+
*/
|
|
123
|
+
export async function safeMkdir(dirPath, options = {}) {
|
|
124
|
+
const { dryRun = false, recursive = true, reporter = null } = options;
|
|
125
|
+
|
|
126
|
+
if (reporter) {
|
|
127
|
+
reporter.recordDir(dirPath);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (dryRun) {
|
|
131
|
+
return { created: false, dryRun: true, path: dirPath };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
await fs.mkdir(dirPath, { recursive });
|
|
135
|
+
return { created: true, dryRun: false, path: dirPath };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Safely copies a file, supporting dry-run mode
|
|
140
|
+
*/
|
|
141
|
+
export async function safeCopyFile(srcPath, destPath, options = {}) {
|
|
142
|
+
const { dryRun = false, reporter = null } = options;
|
|
143
|
+
|
|
144
|
+
let exists = false;
|
|
145
|
+
try {
|
|
146
|
+
await fs.access(destPath);
|
|
147
|
+
exists = true;
|
|
148
|
+
} catch {
|
|
149
|
+
exists = false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (reporter) {
|
|
153
|
+
if (exists) {
|
|
154
|
+
reporter.recordUpdate(destPath);
|
|
155
|
+
} else {
|
|
156
|
+
reporter.recordCreate(destPath);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (dryRun) {
|
|
161
|
+
return { copied: false, dryRun: true, path: destPath, action: exists ? 'update' : 'create' };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const parentDir = path.dirname(destPath);
|
|
165
|
+
await fs.mkdir(parentDir, { recursive: true });
|
|
166
|
+
await fs.copyFile(srcPath, destPath);
|
|
167
|
+
return { copied: true, dryRun: false, path: destPath, action: exists ? 'update' : 'create' };
|
|
168
|
+
}
|