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.
Files changed (173) hide show
  1. package/README.md +389 -315
  2. package/bin/pixel-agents.js +306 -149
  3. package/package.json +3 -1
  4. package/skill/SKILL.md +185 -0
  5. package/skill/references/anti-ai-rubric.md +30 -0
  6. package/skill/references/anti-ai-slop-encyclopedia.md +181 -0
  7. package/skill/references/commands-reference.md +135 -0
  8. package/skill/references/testing-and-qa-strategy.md +25 -0
  9. package/skill/references/token-efficiency.md +20 -0
  10. package/src/adapters/adapter.interface.js +77 -0
  11. package/src/adapters/antigravity.js +138 -0
  12. package/src/adapters/claude-code.js +149 -0
  13. package/src/adapters/codex.js +131 -0
  14. package/src/adapters/cursor.js +133 -0
  15. package/src/adapters/generic.js +63 -0
  16. package/src/adapters/index.js +15 -0
  17. package/src/adapters/kiro.js +134 -0
  18. package/src/adapters/registry.js +150 -0
  19. package/src/backend/analyzer/complexity-analyzer.js +104 -0
  20. package/src/backend/analyzer/requirement-analyzer.js +213 -0
  21. package/src/backend/analyzer/threat-analyzer.js +122 -0
  22. package/src/backend/api/api-designer.js +90 -0
  23. package/src/backend/api/error-contract.js +132 -0
  24. package/src/backend/api/route-generator.js +92 -0
  25. package/src/backend/api/schema-generator.js +64 -0
  26. package/src/backend/architecture/adr-generator.js +62 -0
  27. package/src/backend/architecture/architecture-reasoner.js +119 -0
  28. package/src/backend/architecture/architecture-selector.js +57 -0
  29. package/src/backend/architecture/tradeoff-analyzer.js +92 -0
  30. package/src/backend/data/data-modeler.js +114 -0
  31. package/src/backend/data/index-analyzer.js +93 -0
  32. package/src/backend/data/query-analyzer.js +41 -0
  33. package/src/backend/data/schema-generator.js +92 -0
  34. package/src/backend/data/transaction-analyzer.js +49 -0
  35. package/src/backend/index.js +67 -0
  36. package/src/backend/observability/logging-engine.js +80 -0
  37. package/src/backend/observability/metrics-engine.js +31 -0
  38. package/src/backend/observability/tracing-engine.js +16 -0
  39. package/src/backend/patterns/pattern-registry.js +74 -0
  40. package/src/backend/patterns/pattern-selector.js +39 -0
  41. package/src/backend/patterns/tradeoff-engine.js +17 -0
  42. package/src/backend/performance/cache-analyzer.js +31 -0
  43. package/src/backend/performance/concurrency-analyzer.js +34 -0
  44. package/src/backend/performance/performance-engine.js +98 -0
  45. package/src/backend/performance/query-optimizer.js +16 -0
  46. package/src/backend/reliability/failure-analyzer.js +22 -0
  47. package/src/backend/reliability/idempotency-engine.js +56 -0
  48. package/src/backend/reliability/resilience-engine.js +141 -0
  49. package/src/backend/reliability/retry-analyzer.js +15 -0
  50. package/src/backend/security/auth-selector.js +33 -0
  51. package/src/backend/security/authorization-engine.js +90 -0
  52. package/src/backend/security/policy-engine.js +49 -0
  53. package/src/backend/security/secret-analyzer.js +39 -0
  54. package/src/backend/security/security-engine.js +122 -0
  55. package/src/backend/security/threat-model.js +59 -0
  56. package/src/backend/synthesis/backend-synthesizer.js +98 -0
  57. package/src/backend/synthesis/infrastructure-synthesizer.js +108 -0
  58. package/src/backend/synthesis/module-synthesizer.js +262 -0
  59. package/src/backend/validation/architecture-validator.js +70 -0
  60. package/src/backend/validation/backend-quality-validator.js +71 -0
  61. package/src/backend/validation/dependency-validator.js +20 -0
  62. package/src/backend/validation/performance-validator.js +38 -0
  63. package/src/backend/validation/security-validator.js +73 -0
  64. package/src/commands/adapt.js +105 -0
  65. package/src/commands/add.js +78 -0
  66. package/src/commands/animate.js +48 -0
  67. package/src/commands/assemble.js +57 -0
  68. package/src/commands/audit.js +93 -0
  69. package/src/commands/backend.js +125 -0
  70. package/src/commands/bento.js +25 -0
  71. package/src/commands/blueprint.js +65 -0
  72. package/src/commands/bolder.js +48 -0
  73. package/src/commands/boss-fight.js +60 -0
  74. package/src/commands/build.js +39 -0
  75. package/src/commands/calibrate.js +25 -0
  76. package/src/commands/chromatic.js +25 -0
  77. package/src/commands/clarify.js +52 -0
  78. package/src/commands/colorize.js +57 -0
  79. package/src/commands/command.interface.js +28 -0
  80. package/src/commands/commands.js +84 -0
  81. package/src/commands/craft.js +57 -0
  82. package/src/commands/crew.js +66 -0
  83. package/src/commands/critique.js +25 -0
  84. package/src/commands/de-slop.js +25 -0
  85. package/src/commands/delight.js +46 -0
  86. package/src/commands/deploy.js +42 -0
  87. package/src/commands/distill.js +48 -0
  88. package/src/commands/doctor.js +89 -0
  89. package/src/commands/document.js +83 -0
  90. package/src/commands/eight-bit.js +25 -0
  91. package/src/commands/extract.js +75 -0
  92. package/src/commands/fix.js +46 -0
  93. package/src/commands/harden.js +85 -0
  94. package/src/commands/index.js +67 -0
  95. package/src/commands/install.js +75 -0
  96. package/src/commands/layout.js +48 -0
  97. package/src/commands/live.js +44 -0
  98. package/src/commands/manifest.js +25 -0
  99. package/src/commands/office.js +25 -0
  100. package/src/commands/onboard.js +46 -0
  101. package/src/commands/oneshot.js +60 -0
  102. package/src/commands/optimize.js +46 -0
  103. package/src/commands/overdrive.js +46 -0
  104. package/src/commands/parser.js +143 -0
  105. package/src/commands/pixelcrew.js +96 -0
  106. package/src/commands/plan.js +66 -0
  107. package/src/commands/polish.js +45 -0
  108. package/src/commands/quieter.js +48 -0
  109. package/src/commands/recap.js +358 -0
  110. package/src/commands/registry.js +353 -0
  111. package/src/commands/render.js +26 -0
  112. package/src/commands/retrofit.js +25 -0
  113. package/src/commands/review.js +118 -0
  114. package/src/commands/roster.js +25 -0
  115. package/src/commands/sentinel.js +25 -0
  116. package/src/commands/shape.js +66 -0
  117. package/src/commands/skills.js +57 -0
  118. package/src/commands/status.js +48 -0
  119. package/src/commands/stop.js +50 -0
  120. package/src/commands/sync.js +58 -0
  121. package/src/commands/typeset.js +58 -0
  122. package/src/commands/warp.js +25 -0
  123. package/src/core/agent-runtime.js +94 -0
  124. package/src/core/event-bus.js +98 -0
  125. package/src/core/index.js +9 -0
  126. package/src/core/orchestrator.js +234 -0
  127. package/src/core/scheduler.js +124 -0
  128. package/src/core/task-graph.js +185 -0
  129. package/src/dashboard/app.js +1172 -30
  130. package/src/dashboard/index.html +223 -23
  131. package/src/dashboard/styles.css +870 -0
  132. package/src/index.js +16 -0
  133. package/src/orchestrator/artifact-graph.js +104 -0
  134. package/src/orchestrator/ast-normalizer.js +147 -0
  135. package/src/orchestrator/code-generator.js +846 -0
  136. package/src/orchestrator/design-engine.js +203 -0
  137. package/src/orchestrator/engine.js +318 -39
  138. package/src/orchestrator/oneshot.js +351 -1561
  139. package/src/orchestrator/ontology.js +394 -0
  140. package/src/orchestrator/planner.js +302 -0
  141. package/src/orchestrator/repair-planner.js +67 -0
  142. package/src/orchestrator/requirement-contract.js +177 -0
  143. package/src/orchestrator/requirement-validator.js +39 -0
  144. package/src/orchestrator/semantic-engine.js +582 -0
  145. package/src/orchestrator/semantic-reviewer.js +179 -0
  146. package/src/orchestrator/skills-registry.js +194 -0
  147. package/src/orchestrator/task-planner.js +111 -0
  148. package/src/orchestrator/task-queue.js +128 -0
  149. package/src/orchestrator/telemetry.js +209 -0
  150. package/src/orchestrator/ui-synthesizer.js +245 -0
  151. package/src/orchestrator/visual-critic.js +106 -0
  152. package/src/policies/architecture/layering.policy.js +14 -0
  153. package/src/policies/performance/indexing.policy.js +13 -0
  154. package/src/policies/reliability/resilience.policy.js +13 -0
  155. package/src/policies/security/auth.policy.js +31 -0
  156. package/src/protocol/agent.js +124 -0
  157. package/src/protocol/event.js +78 -0
  158. package/src/protocol/index.js +8 -0
  159. package/src/protocol/skill.js +54 -0
  160. package/src/protocol/task.js +100 -0
  161. package/src/scaffold/claude-generator.js +60 -0
  162. package/src/scaffold/commands-catalog.js +321 -0
  163. package/src/scaffold/cursor-generator.js +74 -0
  164. package/src/scaffold/ide-rules.js +133 -0
  165. package/src/scaffold/init.js +415 -91
  166. package/src/scaffold/installer.js +571 -0
  167. package/src/scaffold/kiro-generator.js +86 -0
  168. package/src/scaffold/skills-bundle.js +520 -0
  169. package/src/scaffold/templates.js +57 -4
  170. package/src/scaffold/workstations.js +364 -0
  171. package/src/server/server.js +292 -42
  172. package/src/utils/fs-safe.js +168 -0
  173. package/src/utils/markdown-report.js +175 -0
@@ -0,0 +1,234 @@
1
+ /**
2
+ * PIXEL CREW — Decoupled Universal Core Orchestrator
3
+ *
4
+ * Orchestrates multi-agent pipelines, task graph scheduling, provider routing,
5
+ * and real-time state telemetry completely decoupled from specific IDEs.
6
+ */
7
+
8
+ import { EventEmitter } from 'node:events';
9
+ import fs from 'node:fs/promises';
10
+ import path from 'node:path';
11
+ import { TaskGraph } from './task-graph.js';
12
+ import { Scheduler } from './scheduler.js';
13
+ import { AgentRuntime } from './agent-runtime.js';
14
+ import { EventBus } from './event-bus.js';
15
+ import { AGENT_STATES, AGENT_EXPRESSIONS, ORCHESTRATOR_EXPRESSIONS } from '../protocol/agent.js';
16
+ import { EVENT_TYPES } from '../protocol/event.js';
17
+ import { TASK_STATUS } from '../protocol/task.js';
18
+
19
+ export class CoreOrchestrator extends EventEmitter {
20
+ constructor(rootDir = process.cwd(), options = {}) {
21
+ super();
22
+ this.rootDir = rootDir;
23
+ this.options = options;
24
+
25
+ // Filesystem compatibility paths (.pixel-crew primary, .pixel-agents legacy fallback)
26
+ this.crewDir = path.join(rootDir, '.pixel-crew');
27
+ this.agentsDir = path.join(rootDir, '.pixel-agents');
28
+ this.activeDir = this.crewDir;
29
+
30
+ this.eventBus = new EventBus();
31
+ this.agentRuntime = new AgentRuntime({ eventBus: this.eventBus });
32
+ this.scheduler = new Scheduler({
33
+ maxConcurrent: options.maxConcurrent || 4,
34
+ agentRuntime: this.agentRuntime
35
+ });
36
+
37
+ this.providerRegistry = null;
38
+ this.skillRegistry = null;
39
+ this.config = null;
40
+ this.state = null;
41
+ this.activeTaskGraph = null;
42
+ this.activeSprintAbort = null;
43
+
44
+ // Forward events from eventBus to orchestrator listeners
45
+ this.eventBus.on('event', (evt) => {
46
+ this.emit('event', evt);
47
+ this.emit('agent_event', evt);
48
+ this.syncStateFromEvent(evt);
49
+ });
50
+ }
51
+
52
+ setProviderRegistry(registry) {
53
+ this.providerRegistry = registry;
54
+ this.scheduler.setProviderRegistry(registry);
55
+ }
56
+
57
+ setSkillRegistry(registry) {
58
+ this.skillRegistry = registry;
59
+ }
60
+
61
+ async initialize() {
62
+ // Check if .pixel-crew exists, fallback to .pixel-agents
63
+ let hasCrewDir = false;
64
+ try {
65
+ await fs.access(this.crewDir);
66
+ hasCrewDir = true;
67
+ this.activeDir = this.crewDir;
68
+ } catch {
69
+ try {
70
+ await fs.access(this.agentsDir);
71
+ this.activeDir = this.agentsDir;
72
+ } catch {
73
+ this.activeDir = this.crewDir;
74
+ }
75
+ }
76
+
77
+ const configPath = path.join(this.activeDir, 'config.json');
78
+ const statePath = path.join(this.activeDir, 'state.json');
79
+ const eventsPath = path.join(this.activeDir, 'events.jsonl');
80
+
81
+ // Register log paths on event bus
82
+ this.eventBus.addLogFilePath(eventsPath);
83
+ if (this.activeDir !== this.agentsDir) {
84
+ // Also register legacy path if .pixel-agents exists
85
+ try {
86
+ await fs.access(this.agentsDir);
87
+ this.eventBus.addLogFilePath(path.join(this.agentsDir, 'events.jsonl'));
88
+ } catch {}
89
+ }
90
+
91
+ // Load Config
92
+ try {
93
+ const raw = await fs.readFile(configPath, 'utf-8');
94
+ this.config = JSON.parse(raw);
95
+ } catch {
96
+ this.config = {
97
+ project: path.basename(this.rootDir),
98
+ orchestrator: { enabled: true, maxConcurrentAgents: 4, runtimeStrategy: 'auto' },
99
+ agents: {},
100
+ dashboard: { enabled: true, port: 4747 }
101
+ };
102
+ }
103
+
104
+ // Load State
105
+ try {
106
+ const raw = await fs.readFile(statePath, 'utf-8');
107
+ this.state = JSON.parse(raw);
108
+ } catch {
109
+ this.state = {
110
+ status: 'READY',
111
+ activeTask: 'Waiting for tasks',
112
+ startedAt: null,
113
+ completedAt: null,
114
+ orchestrator: { state: AGENT_STATES.IDLE, expression: ORCHESTRATOR_EXPRESSIONS.IDLE, activeSubtasks: 0, totalSubtasks: 0, progress: 0 },
115
+ agents: {}
116
+ };
117
+ }
118
+
119
+ // Load Events
120
+ await this.eventBus.loadFromDisk(eventsPath);
121
+
122
+ return this;
123
+ }
124
+
125
+ getState() {
126
+ return this.state;
127
+ }
128
+
129
+ getConfig() {
130
+ return this.config;
131
+ }
132
+
133
+ getEvents() {
134
+ return this.eventBus.getHistory();
135
+ }
136
+
137
+ async emitEvent(eventData) {
138
+ return await this.eventBus.emitEvent(eventData);
139
+ }
140
+
141
+ syncStateFromEvent(event) {
142
+ if (!this.state) return;
143
+
144
+ if (event.type === EVENT_TYPES.ORCHESTRATION_STARTED) {
145
+ this.state.status = 'RUNNING';
146
+ this.state.activeTask = event.taskName || event.message;
147
+ this.state.startedAt = event.timestamp;
148
+ this.state.completedAt = null;
149
+ this.state.orchestrator = {
150
+ state: AGENT_STATES.WORKING,
151
+ expression: ORCHESTRATOR_EXPRESSIONS.COORDINATING,
152
+ progress: 5
153
+ };
154
+ } else if (event.type === EVENT_TYPES.ORCHESTRATION_COMPLETED) {
155
+ this.state.status = 'COMPLETED';
156
+ this.state.completedAt = event.timestamp;
157
+ this.state.orchestrator = {
158
+ state: AGENT_STATES.COMPLETED,
159
+ expression: ORCHESTRATOR_EXPRESSIONS.COMPLETED,
160
+ progress: 100
161
+ };
162
+ } else if (event.type === EVENT_TYPES.AGENT_STARTED) {
163
+ if (event.agent && this.state.agents) {
164
+ this.state.agents[event.agent] = {
165
+ state: AGENT_STATES.WORKING,
166
+ expression: AGENT_EXPRESSIONS.WORKING,
167
+ currentTask: event.taskName || event.message,
168
+ provider: event.provider
169
+ };
170
+ }
171
+ } else if (event.type === EVENT_TYPES.TASK_COMPLETED) {
172
+ if (event.agent && this.state.agents) {
173
+ this.state.agents[event.agent] = {
174
+ state: AGENT_STATES.COMPLETED,
175
+ expression: AGENT_EXPRESSIONS.COMPLETED,
176
+ currentTask: `Completed: ${event.taskName || ''}`,
177
+ provider: event.provider
178
+ };
179
+ }
180
+ }
181
+
182
+ this.emit('state_change', { state: this.state });
183
+ }
184
+
185
+ /**
186
+ * Dispatches and executes a TaskGraph
187
+ */
188
+ async executeTaskGraph(taskGraph, options = {}) {
189
+ this.activeTaskGraph = taskGraph;
190
+
191
+ await this.emitEvent({
192
+ agent: 'orchestrator',
193
+ type: EVENT_TYPES.ORCHESTRATION_STARTED,
194
+ taskName: options.title || 'Multi-Agent Task Sprint',
195
+ message: `Orchestrator dispatching DAG task graph (${taskGraph.tasks.size} tasks)`
196
+ });
197
+
198
+ try {
199
+ const result = await this.scheduler.execute(taskGraph, {
200
+ strategy: options.strategy || this.config?.orchestrator?.runtimeStrategy || 'auto',
201
+ ...options
202
+ });
203
+
204
+ await this.emitEvent({
205
+ agent: 'orchestrator',
206
+ type: EVENT_TYPES.ORCHESTRATION_COMPLETED,
207
+ taskName: options.title || 'Multi-Agent Task Sprint',
208
+ message: `Orchestration completed successfully (${result.completed}/${result.total} tasks resolved)`
209
+ });
210
+
211
+ return result;
212
+ } catch (err) {
213
+ await this.emitEvent({
214
+ agent: 'orchestrator',
215
+ type: EVENT_TYPES.ORCHESTRATION_FAILED,
216
+ taskName: options.title || 'Multi-Agent Task Sprint',
217
+ message: `Orchestration sprint failed: ${err.message}`,
218
+ data: { error: err.message }
219
+ });
220
+ throw err;
221
+ } finally {
222
+ this.activeTaskGraph = null;
223
+ }
224
+ }
225
+
226
+ cancelActiveExecution() {
227
+ this.scheduler.cancel();
228
+ if (this.state) {
229
+ this.state.status = 'READY';
230
+ this.state.orchestrator.state = AGENT_STATES.IDLE;
231
+ this.state.orchestrator.expression = ORCHESTRATOR_EXPRESSIONS.IDLE;
232
+ }
233
+ }
234
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * PIXEL CREW — Core Scheduler & Concurrency Dispatcher
3
+ *
4
+ * Schedules task batches from a TaskGraph to Worker Slots respecting dependencies,
5
+ * concurrency limits, and provider routing.
6
+ */
7
+
8
+ import { EventEmitter } from 'node:events';
9
+ import { TASK_STATUS } from '../protocol/task.js';
10
+
11
+ export class Scheduler extends EventEmitter {
12
+ constructor(options = {}) {
13
+ super();
14
+ this.maxConcurrent = options.maxConcurrent || 4;
15
+ this.agentRuntime = options.agentRuntime || null;
16
+ this.providerRegistry = options.providerRegistry || null;
17
+ this.abortController = null;
18
+ this.activeWorkers = new Set();
19
+ }
20
+
21
+ setAgentRuntime(runtime) {
22
+ this.agentRuntime = runtime;
23
+ }
24
+
25
+ setProviderRegistry(registry) {
26
+ this.providerRegistry = registry;
27
+ }
28
+
29
+ /**
30
+ * Executes a TaskGraph until completion or error
31
+ */
32
+ async execute(taskGraph, options = {}) {
33
+ this.abortController = new AbortController();
34
+ const runtimeStrategy = options.strategy || 'auto';
35
+ const handler = options.executeTaskHandler || null;
36
+
37
+ if (taskGraph.hasCycle()) {
38
+ throw new Error('Deadlock detected in TaskGraph: Cyclic dependencies present');
39
+ }
40
+
41
+ const runningPromises = new Map();
42
+
43
+ while (!taskGraph.isCompleted() && !taskGraph.hasFailed()) {
44
+ if (this.abortController.signal.aborted) {
45
+ throw new Error('Scheduler execution aborted');
46
+ }
47
+
48
+ const readyTasks = taskGraph.getReadyTasks();
49
+
50
+ // If no tasks ready and none running, graph is stuck
51
+ if (readyTasks.length === 0 && runningPromises.size === 0) {
52
+ if (!taskGraph.isCompleted()) {
53
+ throw new Error('TaskGraph stalled: unresolvable dependencies or missing prerequisite tasks');
54
+ }
55
+ break;
56
+ }
57
+
58
+ // Fill available concurrency slots
59
+ const availableSlots = this.maxConcurrent - runningPromises.size;
60
+ const tasksToDispatch = readyTasks.slice(0, Math.max(0, availableSlots));
61
+
62
+ for (const task of tasksToDispatch) {
63
+ taskGraph.markRunning(task.id);
64
+
65
+ const taskPromise = (async () => {
66
+ let adapter = null;
67
+ if (this.providerRegistry) {
68
+ adapter = await this.providerRegistry.getBestAgent(task, runtimeStrategy);
69
+ }
70
+
71
+ if (this.agentRuntime) {
72
+ return await this.agentRuntime.executeTask(task, adapter, {
73
+ abortController: this.abortController,
74
+ executeTaskHandler: handler,
75
+ ...options
76
+ });
77
+ } else if (handler) {
78
+ return await handler(task);
79
+ } else {
80
+ await new Promise(r => setTimeout(r, 200));
81
+ return { success: true };
82
+ }
83
+ })()
84
+ .then((result) => {
85
+ taskGraph.markCompleted(task.id, result);
86
+ this.emit('task_completed', { task, result });
87
+ })
88
+ .catch((err) => {
89
+ taskGraph.markFailed(task.id, err);
90
+ this.emit('task_failed', { task, error: err });
91
+ throw err;
92
+ })
93
+ .finally(() => {
94
+ runningPromises.delete(task.id);
95
+ });
96
+
97
+ runningPromises.set(task.id, taskPromise);
98
+ }
99
+
100
+ if (runningPromises.size > 0) {
101
+ // Wait for at least one running task to finish before next loop iteration
102
+ await Promise.race(runningPromises.values());
103
+ }
104
+ }
105
+
106
+ // Wait for any remaining tasks to settle
107
+ if (runningPromises.size > 0) {
108
+ await Promise.all(runningPromises.values());
109
+ }
110
+
111
+ return {
112
+ success: !taskGraph.hasFailed(),
113
+ completed: taskGraph.completedTaskIds.size,
114
+ total: taskGraph.tasks.size,
115
+ tasks: taskGraph.getAllTasks()
116
+ };
117
+ }
118
+
119
+ cancel() {
120
+ if (this.abortController) {
121
+ this.abortController.abort();
122
+ }
123
+ }
124
+ }
@@ -0,0 +1,185 @@
1
+ /**
2
+ * PIXEL CREW — Dynamic Directed Acyclic Graph (DAG) Task Engine
3
+ *
4
+ * Manages task graph construction, topological sorting, cycle detection,
5
+ * and parallel dependency resolution.
6
+ */
7
+
8
+ import { createTask, TASK_STATUS, validateTask } from '../protocol/task.js';
9
+
10
+ export class TaskGraph {
11
+ constructor(tasks = []) {
12
+ this.tasks = new Map();
13
+ this.completedTaskIds = new Set();
14
+ this.runningTaskIds = new Set();
15
+ this.failedTaskIds = new Set();
16
+
17
+ for (const t of tasks) {
18
+ this.addTask(t);
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Adds a task to the graph
24
+ */
25
+ addTask(taskInput) {
26
+ const task = taskInput.id && taskInput.title ? taskInput : createTask(taskInput);
27
+ validateTask(task);
28
+ this.tasks.set(task.id, task);
29
+ if (task.status === TASK_STATUS.COMPLETED) {
30
+ this.completedTaskIds.add(task.id);
31
+ } else if (task.status === TASK_STATUS.RUNNING) {
32
+ this.runningTaskIds.add(task.id);
33
+ } else if (task.status === TASK_STATUS.FAILED) {
34
+ this.failedTaskIds.add(task.id);
35
+ }
36
+ return task;
37
+ }
38
+
39
+ getTask(id) {
40
+ return this.tasks.get(id) || null;
41
+ }
42
+
43
+ getAllTasks() {
44
+ return Array.from(this.tasks.values());
45
+ }
46
+
47
+ /**
48
+ * Detects cycles in the task graph using DFS
49
+ */
50
+ hasCycle() {
51
+ const visited = new Set();
52
+ const recursionStack = new Set();
53
+
54
+ const dfs = (taskId) => {
55
+ visited.add(taskId);
56
+ recursionStack.add(taskId);
57
+
58
+ const task = this.tasks.get(taskId);
59
+ if (task && Array.isArray(task.dependencies)) {
60
+ for (const depId of task.dependencies) {
61
+ if (!this.tasks.has(depId)) {
62
+ // Unresolved external dependency
63
+ continue;
64
+ }
65
+ if (!visited.has(depId)) {
66
+ if (dfs(depId)) return true;
67
+ } else if (recursionStack.has(depId)) {
68
+ return true;
69
+ }
70
+ }
71
+ }
72
+
73
+ recursionStack.delete(taskId);
74
+ return false;
75
+ };
76
+
77
+ for (const taskId of this.tasks.keys()) {
78
+ if (!visited.has(taskId)) {
79
+ if (dfs(taskId)) return true;
80
+ }
81
+ }
82
+ return false;
83
+ }
84
+
85
+ /**
86
+ * Returns topological ordering of tasks
87
+ */
88
+ getTopologicalOrder() {
89
+ if (this.hasCycle()) {
90
+ throw new Error('Cannot compute topological order: Cycle detected in task graph');
91
+ }
92
+
93
+ const visited = new Set();
94
+ const order = [];
95
+
96
+ const visit = (taskId) => {
97
+ if (visited.has(taskId)) return;
98
+ visited.add(taskId);
99
+
100
+ const task = this.tasks.get(taskId);
101
+ if (task && Array.isArray(task.dependencies)) {
102
+ for (const depId of task.dependencies) {
103
+ if (this.tasks.has(depId)) {
104
+ visit(depId);
105
+ }
106
+ }
107
+ }
108
+ order.push(task);
109
+ };
110
+
111
+ for (const taskId of this.tasks.keys()) {
112
+ visit(taskId);
113
+ }
114
+
115
+ return order;
116
+ }
117
+
118
+ /**
119
+ * Returns all tasks ready to run (dependencies completed and task is queued/pending)
120
+ */
121
+ getReadyTasks() {
122
+ const ready = [];
123
+ for (const [id, task] of this.tasks.entries()) {
124
+ if (task.status === TASK_STATUS.QUEUED || task.status === TASK_STATUS.PENDING) {
125
+ const depsMet = task.dependencies.every(depId => this.completedTaskIds.has(depId));
126
+ if (depsMet) {
127
+ ready.push(task);
128
+ }
129
+ }
130
+ }
131
+ return ready;
132
+ }
133
+
134
+ markRunning(taskId) {
135
+ const task = this.tasks.get(taskId);
136
+ if (!task) return;
137
+ task.status = TASK_STATUS.RUNNING;
138
+ task.updatedAt = Date.now();
139
+ this.runningTaskIds.add(taskId);
140
+ }
141
+
142
+ markCompleted(taskId, result = null) {
143
+ const task = this.tasks.get(taskId);
144
+ if (!task) return;
145
+ task.status = TASK_STATUS.COMPLETED;
146
+ task.result = result;
147
+ task.updatedAt = Date.now();
148
+ this.runningTaskIds.delete(taskId);
149
+ this.completedTaskIds.add(taskId);
150
+ }
151
+
152
+ markFailed(taskId, error = null) {
153
+ const task = this.tasks.get(taskId);
154
+ if (!task) return;
155
+ task.status = TASK_STATUS.FAILED;
156
+ task.error = typeof error === 'string' ? error : (error?.message || 'Unknown error');
157
+ task.updatedAt = Date.now();
158
+ this.runningTaskIds.delete(taskId);
159
+ this.failedTaskIds.add(taskId);
160
+ }
161
+
162
+ isCompleted() {
163
+ return this.tasks.size > 0 && this.tasks.size === this.completedTaskIds.size;
164
+ }
165
+
166
+ hasFailed() {
167
+ return this.failedTaskIds.size > 0;
168
+ }
169
+
170
+ reset() {
171
+ this.completedTaskIds.clear();
172
+ this.runningTaskIds.clear();
173
+ this.failedTaskIds.clear();
174
+ for (const task of this.tasks.values()) {
175
+ task.status = TASK_STATUS.QUEUED;
176
+ task.result = null;
177
+ task.error = null;
178
+ task.updatedAt = Date.now();
179
+ }
180
+ }
181
+
182
+ toJSON() {
183
+ return Array.from(this.tasks.values());
184
+ }
185
+ }