pixelcrew 0.2.3 → 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 +632 -780
- package/src/orchestrator/design-engine.js +203 -0
- package/src/orchestrator/engine.js +307 -38
- package/src/orchestrator/oneshot.js +367 -380
- package/src/orchestrator/ontology.js +394 -0
- package/src/orchestrator/planner.js +231 -258
- 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/task-planner.js +111 -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 +283 -40
- package/src/utils/fs-safe.js +168 -0
- package/src/utils/markdown-report.js +175 -0
package/src/server/server.js
CHANGED
|
@@ -13,8 +13,7 @@ export function createServer(engineOrRootDir, maybeEngine, options = {}) {
|
|
|
13
13
|
engine = maybeEngine;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const dashboardDir = path.join(rootDir, '.pixel-
|
|
17
|
-
const legacyDashboardDir = path.join(rootDir, '.pixel-dashboard');
|
|
16
|
+
const dashboardDir = path.join(rootDir, '.pixel-crew', 'dashboard');
|
|
18
17
|
const fallbackDashboardDir = fileURLToPath(new URL('../dashboard', import.meta.url));
|
|
19
18
|
|
|
20
19
|
const sseClients = new Set();
|
|
@@ -87,15 +86,39 @@ export function createServer(engineOrRootDir, maybeEngine, options = {}) {
|
|
|
87
86
|
|
|
88
87
|
// API Routes
|
|
89
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
|
+
}
|
|
90
100
|
return sendJson(200, {
|
|
91
101
|
rootDir: engine.rootDir,
|
|
92
102
|
project: engine.getConfig()?.project || path.basename(engine.rootDir),
|
|
93
|
-
version: '0.
|
|
103
|
+
version: '0.2.5',
|
|
104
|
+
...activeProv
|
|
94
105
|
});
|
|
95
106
|
}
|
|
96
107
|
|
|
97
108
|
if (pathname === '/api/state' && req.method === 'GET') {
|
|
98
|
-
|
|
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 });
|
|
99
122
|
}
|
|
100
123
|
|
|
101
124
|
if (pathname === '/api/config' && req.method === 'GET') {
|
|
@@ -110,11 +133,24 @@ export function createServer(engineOrRootDir, maybeEngine, options = {}) {
|
|
|
110
133
|
'Connection': 'keep-alive'
|
|
111
134
|
});
|
|
112
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
|
+
|
|
113
148
|
// Send initial history
|
|
114
149
|
const initialPayload = `event: init\ndata: ${JSON.stringify({
|
|
115
|
-
state: engine.getState(),
|
|
150
|
+
state: { ...(engine.getState() || {}), ...activeProv },
|
|
116
151
|
config: engine.getConfig(),
|
|
117
|
-
history: engine.getEvents()
|
|
152
|
+
history: engine.getEvents(),
|
|
153
|
+
provider: activeProv
|
|
118
154
|
})}\n\n`;
|
|
119
155
|
res.write(initialPayload);
|
|
120
156
|
|
|
@@ -126,6 +162,82 @@ export function createServer(engineOrRootDir, maybeEngine, options = {}) {
|
|
|
126
162
|
return;
|
|
127
163
|
}
|
|
128
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
|
+
|
|
129
241
|
if (pathname === '/api/task' && req.method === 'POST') {
|
|
130
242
|
const body = await readBody();
|
|
131
243
|
const prompt = body.prompt || body.task || 'Optimize application workflow';
|
|
@@ -134,25 +246,39 @@ export function createServer(engineOrRootDir, maybeEngine, options = {}) {
|
|
|
134
246
|
return sendJson(200, { status: 'accepted', prompt });
|
|
135
247
|
}
|
|
136
248
|
|
|
137
|
-
if (pathname === '/api/oneshot' && req.method === 'POST') {
|
|
249
|
+
if ((pathname === '/api/goal' || pathname === '/api/oneshot') && req.method === 'POST') {
|
|
138
250
|
const body = await readBody();
|
|
139
|
-
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';
|
|
140
252
|
const options = {
|
|
141
|
-
targetFramework: body.targetFramework || '
|
|
142
|
-
outputDir: body.outputDir
|
|
253
|
+
targetFramework: body.targetFramework || 'nextjs',
|
|
254
|
+
outputDir: body.outputDir
|
|
143
255
|
};
|
|
144
|
-
// Asynchronously start
|
|
256
|
+
// Asynchronously start Multi-Agent Synthesis / Goal pipeline
|
|
145
257
|
engine.submitOneShotTask(prompt, options).catch(console.error);
|
|
146
|
-
return sendJson(200, { status: '
|
|
258
|
+
return sendJson(200, { status: 'goal_started', prompt, options });
|
|
147
259
|
}
|
|
148
260
|
|
|
149
|
-
if (pathname === '/api/token-stats' && req.method === 'GET') {
|
|
150
|
-
|
|
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 || {
|
|
151
264
|
rawTokensEstimated: 42500,
|
|
152
265
|
actualTokensUsed: 11800,
|
|
153
266
|
tokensSaved: 30700,
|
|
154
267
|
efficiencyRatio: 72,
|
|
155
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
|
+
},
|
|
156
282
|
strategiesActive: [
|
|
157
283
|
"AST Symbol Graph Skeletonization",
|
|
158
284
|
"Tiered Sliding Window Context Pruning",
|
|
@@ -164,15 +290,16 @@ export function createServer(engineOrRootDir, maybeEngine, options = {}) {
|
|
|
164
290
|
}
|
|
165
291
|
|
|
166
292
|
if (pathname === '/api/site-preview' && req.method === 'GET') {
|
|
167
|
-
const sitePath = path.join(engine.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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 {}
|
|
175
301
|
}
|
|
302
|
+
return sendJson(404, { error: 'No generated site preview found yet. Execute a task or goal first.' });
|
|
176
303
|
}
|
|
177
304
|
|
|
178
305
|
if (pathname === '/api/emit' && req.method === 'POST') {
|
|
@@ -275,24 +402,118 @@ export function createServer(engineOrRootDir, maybeEngine, options = {}) {
|
|
|
275
402
|
heartbeat.unref();
|
|
276
403
|
|
|
277
404
|
let isWatching = false;
|
|
405
|
+
let workspaceWatcher = null;
|
|
406
|
+
const debouncedFiles = new Map();
|
|
278
407
|
|
|
279
|
-
|
|
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() {
|
|
280
432
|
if (isWatching) return;
|
|
281
433
|
isWatching = true;
|
|
282
434
|
|
|
283
|
-
//
|
|
284
|
-
|
|
285
|
-
|
|
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;
|
|
286
507
|
try {
|
|
287
|
-
const initialContent = fsSync.readFileSync(
|
|
508
|
+
const initialContent = fsSync.readFileSync(ePath, 'utf-8');
|
|
288
509
|
lastKnownEventCount = initialContent.trim().split('\n').filter(Boolean).length;
|
|
289
510
|
} catch {
|
|
290
511
|
lastKnownEventCount = 0;
|
|
291
512
|
}
|
|
292
513
|
|
|
293
|
-
fsSync.watchFile(
|
|
514
|
+
fsSync.watchFile(ePath, { interval: 150 }, async () => {
|
|
294
515
|
try {
|
|
295
|
-
const content = await fs.readFile(
|
|
516
|
+
const content = await fs.readFile(ePath, 'utf-8');
|
|
296
517
|
const lines = content.trim().split('\n').filter(Boolean);
|
|
297
518
|
if (lines.length > lastKnownEventCount) {
|
|
298
519
|
const newLines = lines.slice(lastKnownEventCount);
|
|
@@ -301,7 +522,7 @@ export function createServer(engineOrRootDir, maybeEngine, options = {}) {
|
|
|
301
522
|
try {
|
|
302
523
|
const event = JSON.parse(line);
|
|
303
524
|
engine.eventHistory.push(event);
|
|
304
|
-
if (engine.eventHistory.length >
|
|
525
|
+
if (engine.eventHistory.length > 300) engine.eventHistory.shift();
|
|
305
526
|
|
|
306
527
|
const payload = `event: agent_event\ndata: ${JSON.stringify(event)}\n\n`;
|
|
307
528
|
for (const client of sseClients) {
|
|
@@ -314,13 +535,21 @@ export function createServer(engineOrRootDir, maybeEngine, options = {}) {
|
|
|
314
535
|
});
|
|
315
536
|
}
|
|
316
537
|
|
|
317
|
-
// Watch state.json for direct external updates
|
|
318
|
-
|
|
319
|
-
|
|
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 () => {
|
|
320
549
|
try {
|
|
321
|
-
const content = await fs.readFile(
|
|
550
|
+
const content = await fs.readFile(sPath, 'utf-8');
|
|
322
551
|
const state = JSON.parse(content);
|
|
323
|
-
engine.state = state;
|
|
552
|
+
if (engine) engine.state = state;
|
|
324
553
|
const payload = `event: state_change\ndata: ${JSON.stringify({ state })}\n\n`;
|
|
325
554
|
for (const client of sseClients) {
|
|
326
555
|
try { client.write(payload); } catch {}
|
|
@@ -328,17 +557,31 @@ export function createServer(engineOrRootDir, maybeEngine, options = {}) {
|
|
|
328
557
|
} catch {}
|
|
329
558
|
});
|
|
330
559
|
}
|
|
331
|
-
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Start watchers when server begins listening
|
|
563
|
+
server.on('listening', startWatchers);
|
|
332
564
|
|
|
333
565
|
server.on('close', () => {
|
|
334
566
|
clearInterval(heartbeat);
|
|
335
|
-
|
|
336
|
-
|
|
567
|
+
isWatching = false;
|
|
568
|
+
if (workspaceWatcher) {
|
|
569
|
+
try { workspaceWatcher.close(); } catch {}
|
|
337
570
|
}
|
|
338
|
-
|
|
339
|
-
|
|
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 {}
|
|
340
582
|
}
|
|
341
583
|
});
|
|
342
584
|
|
|
343
585
|
return server;
|
|
344
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
|
+
}
|