pi-crew 0.2.3 → 0.2.4
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/AGENTS.md +57 -32
- package/CHANGELOG.md +466 -448
- package/LICENSE +21 -21
- package/NOTICE.md +16 -16
- package/README.md +323 -323
- package/docs/FEATURE_INTAKE.md +126 -0
- package/docs/HARNESS.md +86 -0
- package/docs/HARNESS_BACKLOG.md +41 -0
- package/docs/TEST_MATRIX.md +49 -0
- package/docs/actions-reference.md +595 -595
- package/docs/architecture.md +180 -180
- package/docs/code-review-2026-05-11.md +592 -592
- package/docs/commands-reference.md +347 -347
- package/docs/comparison-pi-subagents-vs-pi-crew.md +303 -0
- package/docs/decisions/0001-durable-state.md +41 -0
- package/docs/decisions/0002-child-process-for-async.md +42 -0
- package/docs/decisions/0003-depth-guard.md +36 -0
- package/docs/decisions/0004-execfile-over-exec.md +34 -0
- package/docs/decisions/0005-no-parameter-properties.md +49 -0
- package/docs/decisions/0006-publish-bundled-esm.md +63 -0
- package/docs/decisions/0007-active-run-binary-index.md +54 -0
- package/docs/decisions/0008-child-pi-warm-pool.md +61 -0
- package/docs/decisions/README.md +23 -0
- package/docs/followup-review-round4-2026-05-13.md +107 -0
- package/docs/implementation-plan-top3.md +333 -0
- package/docs/live-mailbox-runtime.md +36 -36
- package/docs/next-upgrade-roadmap.md +808 -808
- package/docs/oh-my-pi-research.md +509 -0
- package/docs/perf/baseline-2026-05.md +113 -0
- package/docs/perf/final-report-2026-05.md +206 -0
- package/docs/perf/sprint-1-report.md +71 -0
- package/docs/perf/sprint-2-report.md +81 -0
- package/docs/perf/sprint-2.5-report.md +53 -0
- package/docs/perf/sprint-3-report.md +36 -0
- package/docs/perf/sprint-4-report.md +47 -0
- package/docs/perf/sprint-5-report.md +51 -0
- package/docs/perf/sprint-6-report.md +94 -0
- package/docs/perf/sprint-7-report.md +74 -0
- package/docs/perf/upgrade-plan-2026-05.md +147 -0
- package/docs/pi-subagents3-deep-analysis.md +508 -0
- package/docs/product/README.md +31 -0
- package/docs/product/platform.md +27 -0
- package/docs/product/runtime-safety.md +37 -0
- package/docs/product/team-run.md +39 -0
- package/docs/product/team-tool.md +37 -0
- package/docs/publishing.md +65 -65
- package/docs/resource-formats.md +134 -134
- package/docs/runtime-analysis-child-vs-live.md +171 -0
- package/docs/runtime-flow.md +148 -148
- package/docs/runtime-migration-in-process-analysis.md +250 -0
- package/docs/stories/README.md +30 -0
- package/docs/stories/backlog.md +36 -0
- package/docs/templates/decision.md +27 -0
- package/docs/templates/story.md +44 -0
- package/docs/templates/validation-report.md +32 -0
- package/docs/usage.md +238 -238
- package/index.ts +7 -6
- package/install.mjs +65 -65
- package/package.json +107 -100
- package/schema.json +222 -222
- package/skills/child-pi-spawning/SKILL.md +213 -0
- package/skills/context-artifact-hygiene/SKILL.md +32 -0
- package/skills/event-log-tracing/SKILL.md +299 -0
- package/skills/git-master/SKILL.md +225 -24
- package/skills/live-agent-lifecycle/SKILL.md +192 -0
- package/skills/mailbox-interactive/SKILL.md +300 -19
- package/skills/model-routing-context/SKILL.md +94 -0
- package/skills/multi-perspective-review/SKILL.md +88 -0
- package/skills/read-only-explorer/SKILL.md +250 -26
- package/skills/safe-bash/SKILL.md +307 -21
- package/skills/verification-before-done/SKILL.md +11 -2
- package/skills/widget-rendering/SKILL.md +258 -0
- package/skills/workspace-isolation/SKILL.md +202 -0
- package/skills/worktree-isolation/SKILL.md +202 -18
- package/src/adapters/claude-adapter.ts +25 -25
- package/src/adapters/codex-adapter.ts +21 -21
- package/src/adapters/cursor-adapter.ts +17 -17
- package/src/adapters/export-util.ts +137 -137
- package/src/adapters/index.ts +15 -15
- package/src/adapters/registry.ts +18 -18
- package/src/adapters/types.ts +23 -23
- package/src/agents/agent-config.ts +38 -38
- package/src/agents/agent-serializer.ts +38 -38
- package/src/agents/discover-agents.ts +121 -118
- package/src/config/config.ts +740 -858
- package/src/config/defaults.ts +96 -96
- package/src/config/drift-detector.ts +211 -211
- package/src/config/markers.ts +327 -327
- package/src/config/resilient-parser.ts +109 -108
- package/src/config/suggestions.ts +74 -74
- package/src/config/types.ts +199 -0
- package/src/extension/async-notifier.ts +123 -89
- package/src/extension/autonomous-policy.ts +169 -169
- package/src/extension/cross-extension-rpc.ts +104 -104
- package/src/extension/help.ts +47 -47
- package/src/extension/import-index.ts +69 -69
- package/src/extension/management.ts +395 -382
- package/src/extension/notification-router.ts +116 -116
- package/src/extension/notification-sink.ts +51 -51
- package/src/extension/project-init.ts +168 -168
- package/src/extension/register.ts +859 -668
- package/src/extension/registration/artifact-cleanup.ts +15 -15
- package/src/extension/registration/command-utils.ts +54 -54
- package/src/extension/registration/commands.ts +559 -452
- package/src/extension/registration/compaction-guard.ts +125 -125
- package/src/extension/registration/subagent-helpers.ts +102 -102
- package/src/extension/registration/subagent-tools.ts +220 -159
- package/src/extension/registration/team-tool.ts +159 -99
- package/src/extension/registration/viewers.ts +29 -0
- package/src/extension/result-watcher.ts +128 -128
- package/src/extension/run-bundle-schema.ts +89 -89
- package/src/extension/run-export.ts +73 -73
- package/src/extension/run-import.ts +84 -84
- package/src/extension/run-index.ts +94 -94
- package/src/extension/run-maintenance.ts +142 -142
- package/src/extension/session-summary.ts +8 -8
- package/src/extension/team-manager-command.ts +96 -96
- package/src/extension/team-recommendation.ts +188 -188
- package/src/extension/team-tool/api.ts +5 -2
- package/src/extension/team-tool/cancel.ts +224 -209
- package/src/extension/team-tool/config-patch.ts +36 -36
- package/src/extension/team-tool/context.ts +60 -60
- package/src/extension/team-tool/doctor.ts +242 -242
- package/src/extension/team-tool/handle-settings.ts +421 -195
- package/src/extension/team-tool/inspect.ts +41 -41
- package/src/extension/team-tool/lifecycle-actions.ts +139 -139
- package/src/extension/team-tool/parallel-dispatch.ts +156 -156
- package/src/extension/team-tool/plan.ts +19 -19
- package/src/extension/team-tool/respond.ts +112 -111
- package/src/extension/team-tool/run.ts +246 -229
- package/src/extension/team-tool/status.ts +110 -110
- package/src/extension/team-tool-types.ts +13 -13
- package/src/extension/team-tool.ts +344 -344
- package/src/extension/tool-result.ts +16 -16
- package/src/extension/validate-resources.ts +77 -77
- package/src/hooks/registry.ts +61 -61
- package/src/hooks/types.ts +40 -40
- package/src/i18n.ts +184 -184
- package/src/observability/correlation.ts +35 -35
- package/src/observability/event-to-metric.ts +68 -68
- package/src/observability/exporters/adapter.ts +30 -30
- package/src/observability/exporters/otlp-exporter.ts +106 -92
- package/src/observability/exporters/prometheus-exporter.ts +54 -54
- package/src/observability/metric-registry.ts +87 -87
- package/src/observability/metric-retention.ts +54 -54
- package/src/observability/metric-sink.ts +81 -56
- package/src/observability/metrics-primitives.ts +167 -167
- package/src/prompt/prompt-runtime.ts +72 -72
- package/src/runtime/adaptive-plan.ts +338 -0
- package/src/runtime/agent-control.ts +169 -169
- package/src/runtime/agent-memory.ts +72 -72
- package/src/runtime/agent-observability.ts +114 -114
- package/src/runtime/async-marker.ts +26 -26
- package/src/runtime/async-runner.ts +153 -153
- package/src/runtime/attention-events.ts +28 -28
- package/src/runtime/auto-resume.ts +100 -100
- package/src/runtime/background-runner.ts +122 -89
- package/src/runtime/cancellation.ts +61 -61
- package/src/runtime/capability-inventory.ts +116 -116
- package/src/runtime/child-pi-pool.ts +68 -0
- package/src/runtime/child-pi.ts +541 -461
- package/src/runtime/code-summary.ts +247 -247
- package/src/runtime/compaction-summary.ts +271 -271
- package/src/runtime/concurrency.ts +58 -58
- package/src/runtime/crash-recovery.ts +317 -301
- package/src/runtime/crew-agent-records.ts +379 -281
- package/src/runtime/crew-agent-runtime.ts +60 -60
- package/src/runtime/cross-extension-rpc.ts +72 -0
- package/src/runtime/custom-tools/irc-tool.ts +201 -201
- package/src/runtime/custom-tools/submit-result-tool.ts +90 -90
- package/src/runtime/deadletter.ts +47 -47
- package/src/runtime/delivery-coordinator.ts +176 -176
- package/src/runtime/delta-conflict.ts +360 -360
- package/src/runtime/diagnostic-export.ts +102 -102
- package/src/runtime/direct-run.ts +35 -35
- package/src/runtime/effectiveness.ts +82 -81
- package/src/runtime/errors/crew-errors.ts +166 -0
- package/src/runtime/event-stream-bridge.ts +92 -92
- package/src/runtime/foreground-control.ts +82 -82
- package/src/runtime/green-contract.ts +46 -46
- package/src/runtime/group-join.ts +234 -106
- package/src/runtime/heartbeat-watcher.ts +145 -124
- package/src/runtime/iteration-hooks.ts +267 -267
- package/src/runtime/live-agent-control.ts +88 -88
- package/src/runtime/live-agent-manager.ts +377 -179
- package/src/runtime/live-control-realtime.ts +36 -36
- package/src/runtime/live-session-runtime.ts +676 -600
- package/src/runtime/loop-gates.ts +129 -129
- package/src/runtime/manifest-cache.ts +263 -263
- package/src/runtime/mcp-proxy.ts +113 -113
- package/src/runtime/metric-parser.ts +40 -40
- package/src/runtime/model-fallback.ts +282 -274
- package/src/runtime/model-resolver.ts +118 -0
- package/src/runtime/output-validator.ts +187 -187
- package/src/runtime/overflow-recovery.ts +175 -175
- package/src/runtime/parallel-research.ts +44 -44
- package/src/runtime/parallel-utils.ts +156 -156
- package/src/runtime/parent-guard.ts +80 -80
- package/src/runtime/phase-progress.ts +217 -217
- package/src/runtime/pi-args.ts +165 -165
- package/src/runtime/pi-json-output.ts +111 -111
- package/src/runtime/pi-spawn.ts +167 -167
- package/src/runtime/policy-engine.ts +79 -79
- package/src/runtime/post-checks.ts +125 -125
- package/src/runtime/post-exit-stdio-guard.ts +86 -86
- package/src/runtime/process-status.ts +97 -73
- package/src/runtime/progress-event-coalescer.ts +43 -43
- package/src/runtime/recovery-recipes.ts +74 -74
- package/src/runtime/retry-executor.ts +81 -81
- package/src/runtime/role-permission.ts +39 -39
- package/src/runtime/run-tracker.ts +99 -0
- package/src/runtime/runtime-policy.ts +21 -0
- package/src/runtime/runtime-resolver.ts +94 -91
- package/src/runtime/scheduler.ts +294 -0
- package/src/runtime/semaphore.ts +131 -131
- package/src/runtime/sensitive-paths.ts +92 -92
- package/src/runtime/session-usage.ts +79 -79
- package/src/runtime/settings-store.ts +103 -0
- package/src/runtime/sidechain-output.ts +29 -29
- package/src/runtime/skill-instructions.ts +222 -222
- package/src/runtime/stale-reconciler.ts +198 -189
- package/src/runtime/streaming-output.ts +47 -0
- package/src/runtime/subagent-manager.ts +404 -400
- package/src/runtime/subprocess-tool-registry.ts +67 -67
- package/src/runtime/task-display.ts +38 -38
- package/src/runtime/task-graph-scheduler.ts +122 -122
- package/src/runtime/task-graph.ts +207 -207
- package/src/runtime/task-output-context.ts +177 -177
- package/src/runtime/task-packet.ts +93 -93
- package/src/runtime/task-quality.ts +207 -207
- package/src/runtime/task-runner/capabilities.ts +78 -78
- package/src/runtime/task-runner/live-executor.ts +131 -113
- package/src/runtime/task-runner/progress.ts +119 -119
- package/src/runtime/task-runner/prompt-builder.ts +139 -139
- package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
- package/src/runtime/task-runner/result-utils.ts +14 -14
- package/src/runtime/task-runner/run-projection.ts +103 -103
- package/src/runtime/task-runner/state-helpers.ts +22 -22
- package/src/runtime/task-runner.ts +469 -459
- package/src/runtime/team-runner.ts +693 -945
- package/src/runtime/usage-tracker.ts +71 -0
- package/src/runtime/worker-heartbeat.ts +21 -21
- package/src/runtime/worker-startup.ts +57 -57
- package/src/runtime/workflow-state.ts +187 -187
- package/src/runtime/yield-handler.ts +190 -190
- package/src/schema/config-schema.ts +172 -168
- package/src/schema/team-tool-schema.ts +126 -126
- package/src/schema/validation-types.ts +151 -148
- package/src/skills/discover-skills.ts +67 -67
- package/src/skills/skill-templates.ts +374 -374
- package/src/state/active-run-registry.ts +227 -191
- package/src/state/artifact-store.ts +130 -129
- package/src/state/atomic-write.ts +262 -195
- package/src/state/blob-store.ts +116 -116
- package/src/state/contracts.ts +111 -111
- package/src/state/event-log-rotation.ts +161 -158
- package/src/state/event-log.ts +383 -303
- package/src/state/event-reconstructor.ts +217 -217
- package/src/state/jsonl-writer.ts +82 -82
- package/src/state/locks.ts +146 -146
- package/src/state/mailbox.ts +446 -405
- package/src/state/state-store.ts +364 -351
- package/src/state/task-claims.ts +44 -44
- package/src/state/types.ts +285 -285
- package/src/state/usage.ts +29 -29
- package/src/subagents/async-entry.ts +1 -1
- package/src/subagents/index.ts +3 -3
- package/src/subagents/live/control.ts +1 -1
- package/src/subagents/live/manager.ts +1 -1
- package/src/subagents/live/realtime.ts +1 -1
- package/src/subagents/live/session-runtime.ts +1 -1
- package/src/subagents/manager.ts +1 -1
- package/src/subagents/spawn.ts +1 -1
- package/src/teams/discover-teams.ts +116 -116
- package/src/teams/team-config.ts +27 -27
- package/src/teams/team-serializer.ts +38 -38
- package/src/types/diff.d.ts +18 -18
- package/src/ui/agent-management-overlay.ts +144 -144
- package/src/ui/crew-widget.ts +487 -370
- package/src/ui/dashboard-panes/agents-pane.ts +109 -28
- package/src/ui/dashboard-panes/cancellation-pane.ts +42 -42
- package/src/ui/dashboard-panes/capability-pane.ts +59 -59
- package/src/ui/dashboard-panes/health-pane.ts +30 -30
- package/src/ui/dashboard-panes/mailbox-pane.ts +35 -35
- package/src/ui/dashboard-panes/progress-pane.ts +30 -30
- package/src/ui/dashboard-panes/transcript-pane.ts +10 -10
- package/src/ui/heartbeat-aggregator.ts +63 -63
- package/src/ui/keybinding-map.ts +97 -94
- package/src/ui/live-conversation-overlay.ts +152 -0
- package/src/ui/live-run-sidebar.ts +180 -180
- package/src/ui/mascot.ts +442 -442
- package/src/ui/overlays/agent-picker-overlay.ts +57 -57
- package/src/ui/overlays/confirm-overlay.ts +58 -58
- package/src/ui/overlays/mailbox-compose-overlay.ts +144 -144
- package/src/ui/overlays/mailbox-compose-preview.ts +63 -63
- package/src/ui/overlays/mailbox-detail-overlay.ts +122 -122
- package/src/ui/pi-ui-compat.ts +57 -57
- package/src/ui/powerbar-publisher.ts +221 -197
- package/src/ui/render-scheduler.ts +216 -143
- package/src/ui/run-action-dispatcher.ts +118 -118
- package/src/ui/run-dashboard.ts +526 -464
- package/src/ui/run-event-bus.ts +208 -208
- package/src/ui/run-snapshot-cache.ts +826 -777
- package/src/ui/settings-overlay.ts +721 -0
- package/src/ui/snapshot-types.ts +86 -70
- package/src/ui/theme-adapter.ts +190 -190
- package/src/ui/tool-progress-formatter.ts +89 -0
- package/src/ui/transcript-cache.ts +94 -94
- package/src/ui/transcript-viewer.ts +335 -335
- package/src/utils/conflict-detect.ts +662 -0
- package/src/utils/file-coalescer.ts +86 -86
- package/src/utils/frontmatter.ts +68 -68
- package/src/utils/fs-watch.ts +88 -31
- package/src/utils/gh-protocol.ts +479 -0
- package/src/utils/ids.ts +17 -17
- package/src/utils/incremental-reader.ts +104 -104
- package/src/utils/internal-error.ts +6 -6
- package/src/utils/names.ts +27 -27
- package/src/utils/paths.ts +102 -63
- package/src/utils/redaction.ts +44 -44
- package/src/utils/safe-paths.ts +47 -47
- package/src/utils/scan-cache.ts +136 -136
- package/src/utils/sse-parser.ts +134 -134
- package/src/utils/task-name-generator.ts +337 -337
- package/src/utils/timings.ts +33 -33
- package/src/utils/visual.ts +243 -198
- package/src/workflows/discover-workflows.ts +139 -139
- package/src/workflows/validate-workflow.ts +40 -40
- package/src/workflows/workflow-config.ts +26 -26
- package/src/workflows/workflow-serializer.ts +32 -32
- package/src/worktree/branch-freshness.ts +45 -45
- package/src/worktree/cleanup.ts +75 -75
- package/src/worktree/worktree-manager.ts +188 -188
- package/teams/default.team.md +12 -12
- package/teams/fast-fix.team.md +11 -11
- package/teams/implementation.team.md +18 -18
- package/teams/parallel-research.team.md +14 -14
- package/teams/research.team.md +11 -11
- package/teams/review.team.md +12 -12
- package/tsconfig.json +19 -19
- package/workflows/default.workflow.md +30 -30
- package/workflows/fast-fix.workflow.md +23 -23
- package/workflows/implementation.workflow.md +43 -43
- package/workflows/parallel-research.workflow.md +46 -46
- package/workflows/research.workflow.md +22 -22
- package/workflows/review.workflow.md +30 -30
- package/skills/task-packet/SKILL.md +0 -28
- package/skills/verify-evidence/SKILL.md +0 -27
|
@@ -1,207 +1,207 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DAG-based task execution order calculator.
|
|
3
|
-
*
|
|
4
|
-
* Uses Kahn's algorithm for topological sort and DFS for cycle detection.
|
|
5
|
-
* Groups tasks into parallel "waves" where all tasks in wave N can run
|
|
6
|
-
* concurrently and wave N+1 depends on at least one task in wave N.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/** A lightweight node representation for the execution DAG. */
|
|
10
|
-
export interface TaskNode {
|
|
11
|
-
id: string;
|
|
12
|
-
dependsOn: string[];
|
|
13
|
-
phase?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/** A group of tasks that can all run in parallel. */
|
|
17
|
-
export interface ExecutionWave {
|
|
18
|
-
index: number;
|
|
19
|
-
taskIds: string[];
|
|
20
|
-
label?: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** The full execution plan produced by topological sort. */
|
|
24
|
-
export interface ExecutionPlan {
|
|
25
|
-
waves: ExecutionWave[];
|
|
26
|
-
hasCycle: boolean;
|
|
27
|
-
cycleNodes?: string[];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Build an execution plan from a flat list of task nodes using Kahn's algorithm.
|
|
32
|
-
*
|
|
33
|
-
* - Tasks with empty `dependsOn` go into wave 0.
|
|
34
|
-
* - Each subsequent wave contains tasks whose dependencies are all in earlier waves.
|
|
35
|
-
* - If all tasks have empty `dependsOn`, they all go into wave 0 (backward compatible).
|
|
36
|
-
* - If a cycle is detected, `hasCycle` is true and `cycleNodes` lists the involved IDs.
|
|
37
|
-
*/
|
|
38
|
-
export function buildExecutionPlan(tasks: TaskNode[]): ExecutionPlan {
|
|
39
|
-
if (tasks.length === 0) {
|
|
40
|
-
return { waves: [], hasCycle: false };
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const idSet = new Set<string>(tasks.map((t) => t.id));
|
|
44
|
-
const adjacency = new Map<string, Set<string>>(); // id -> ids that depend on it
|
|
45
|
-
const inDegree = new Map<string, number>();
|
|
46
|
-
|
|
47
|
-
for (const task of tasks) {
|
|
48
|
-
adjacency.set(task.id, new Set<string>());
|
|
49
|
-
inDegree.set(task.id, 0);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
for (const task of tasks) {
|
|
53
|
-
let degree = 0;
|
|
54
|
-
for (const dep of task.dependsOn) {
|
|
55
|
-
if (!idSet.has(dep)) continue; // ignore unknown deps
|
|
56
|
-
adjacency.get(dep)!.add(task.id);
|
|
57
|
-
degree++;
|
|
58
|
-
}
|
|
59
|
-
inDegree.set(task.id, degree);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Kahn's algorithm with wave grouping
|
|
63
|
-
const waves: ExecutionWave[] = [];
|
|
64
|
-
const assigned = new Set<string>();
|
|
65
|
-
let currentWaveIds = tasks
|
|
66
|
-
.filter((t) => inDegree.get(t.id) === 0)
|
|
67
|
-
.map((t) => t.id);
|
|
68
|
-
|
|
69
|
-
let waveIndex = 0;
|
|
70
|
-
while (currentWaveIds.length > 0) {
|
|
71
|
-
for (const id of currentWaveIds) assigned.add(id);
|
|
72
|
-
|
|
73
|
-
const wave = buildWave(tasks, currentWaveIds, waveIndex);
|
|
74
|
-
waves.push(wave);
|
|
75
|
-
|
|
76
|
-
// Decrement in-degrees for dependents
|
|
77
|
-
const nextWaveCandidates = new Set<string>();
|
|
78
|
-
for (const id of currentWaveIds) {
|
|
79
|
-
for (const dependent of adjacency.get(id) ?? []) {
|
|
80
|
-
const current = inDegree.get(dependent)!;
|
|
81
|
-
inDegree.set(dependent, current - 1);
|
|
82
|
-
if (current - 1 === 0) nextWaveCandidates.add(dependent);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
currentWaveIds = [...nextWaveCandidates];
|
|
87
|
-
waveIndex++;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Detect cycle: if not all tasks were assigned, remaining ones form cycles
|
|
91
|
-
if (assigned.size < tasks.length) {
|
|
92
|
-
const cycleNodes = tasks
|
|
93
|
-
.filter((t) => !assigned.has(t.id))
|
|
94
|
-
.map((t) => t.id);
|
|
95
|
-
return {
|
|
96
|
-
waves,
|
|
97
|
-
hasCycle: true,
|
|
98
|
-
cycleNodes,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return { waves, hasCycle: false };
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Derive the phase label for a wave. If all tasks in the wave share the same
|
|
107
|
-
* `phase` value, use it as the wave label; otherwise leave it undefined.
|
|
108
|
-
*/
|
|
109
|
-
function buildWave(tasks: TaskNode[], ids: string[], index: number): ExecutionWave {
|
|
110
|
-
const taskMap = new Map(tasks.map((t) => [t.id, t]));
|
|
111
|
-
const waveTasks = ids.map((id) => taskMap.get(id)!).filter(Boolean);
|
|
112
|
-
|
|
113
|
-
let label: string | undefined;
|
|
114
|
-
if (waveTasks.length > 0 && waveTasks.every((t) => t.phase !== undefined)) {
|
|
115
|
-
const phases = new Set(waveTasks.map((t) => t.phase));
|
|
116
|
-
if (phases.size === 1) label = [...phases][0];
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return { index, taskIds: ids, label };
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Return the IDs of tasks that are ready to run given a set of completed tasks.
|
|
124
|
-
*
|
|
125
|
-
* A task is "ready" when all its dependencies are in `completedTaskIds` AND
|
|
126
|
-
* it has not already been completed itself. Returns tasks from the earliest
|
|
127
|
-
* wave that still has uncompleted tasks.
|
|
128
|
-
*/
|
|
129
|
-
export function getReadyTasks(plan: ExecutionPlan, completedTaskIds: Set<string>): string[] {
|
|
130
|
-
if (plan.hasCycle || plan.waves.length === 0) return [];
|
|
131
|
-
|
|
132
|
-
const completed = completedTaskIds;
|
|
133
|
-
|
|
134
|
-
for (const wave of plan.waves) {
|
|
135
|
-
// All tasks in prior waves must be completed for this wave to be ready
|
|
136
|
-
const priorWavesComplete = plan.waves
|
|
137
|
-
.slice(0, wave.index)
|
|
138
|
-
.every((w) => w.taskIds.every((id) => completed.has(id)));
|
|
139
|
-
|
|
140
|
-
if (!priorWavesComplete) continue;
|
|
141
|
-
|
|
142
|
-
// Filter to tasks not already completed
|
|
143
|
-
const ready = wave.taskIds.filter((id) => !completed.has(id));
|
|
144
|
-
if (ready.length > 0) return ready;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return [];
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Detect all cycles in the task graph using DFS.
|
|
152
|
-
*
|
|
153
|
-
* Returns an array of cycles, where each cycle is represented as an array of
|
|
154
|
-
* task IDs forming a path from a node back to itself.
|
|
155
|
-
*/
|
|
156
|
-
export function detectCycles(tasks: TaskNode[]): string[][] {
|
|
157
|
-
if (tasks.length === 0) return [];
|
|
158
|
-
|
|
159
|
-
const idSet = new Set<string>(tasks.map((t) => t.id));
|
|
160
|
-
const adjacency = new Map<string, string[]>();
|
|
161
|
-
for (const task of tasks) {
|
|
162
|
-
adjacency.set(
|
|
163
|
-
task.id,
|
|
164
|
-
task.dependsOn.filter((dep) => idSet.has(dep)),
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const WHITE = 0;
|
|
169
|
-
const GRAY = 1;
|
|
170
|
-
const BLACK = 2;
|
|
171
|
-
|
|
172
|
-
const color = new Map<string, number>();
|
|
173
|
-
for (const task of tasks) color.set(task.id, WHITE);
|
|
174
|
-
|
|
175
|
-
const cycles: string[][] = [];
|
|
176
|
-
const path: string[] = [];
|
|
177
|
-
|
|
178
|
-
function dfs(nodeId: string): void {
|
|
179
|
-
color.set(nodeId, GRAY);
|
|
180
|
-
path.push(nodeId);
|
|
181
|
-
|
|
182
|
-
const deps = adjacency.get(nodeId) ?? [];
|
|
183
|
-
for (const dep of deps) {
|
|
184
|
-
const depColor = color.get(dep);
|
|
185
|
-
if (depColor === GRAY) {
|
|
186
|
-
// Found a cycle: extract the path from dep to current node
|
|
187
|
-
const cycleStart = path.indexOf(dep);
|
|
188
|
-
if (cycleStart >= 0) {
|
|
189
|
-
cycles.push(path.slice(cycleStart));
|
|
190
|
-
}
|
|
191
|
-
} else if (depColor === WHITE) {
|
|
192
|
-
dfs(dep);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
path.pop();
|
|
197
|
-
color.set(nodeId, BLACK);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
for (const task of tasks) {
|
|
201
|
-
if (color.get(task.id) === WHITE) {
|
|
202
|
-
dfs(task.id);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return cycles;
|
|
207
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* DAG-based task execution order calculator.
|
|
3
|
+
*
|
|
4
|
+
* Uses Kahn's algorithm for topological sort and DFS for cycle detection.
|
|
5
|
+
* Groups tasks into parallel "waves" where all tasks in wave N can run
|
|
6
|
+
* concurrently and wave N+1 depends on at least one task in wave N.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** A lightweight node representation for the execution DAG. */
|
|
10
|
+
export interface TaskNode {
|
|
11
|
+
id: string;
|
|
12
|
+
dependsOn: string[];
|
|
13
|
+
phase?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** A group of tasks that can all run in parallel. */
|
|
17
|
+
export interface ExecutionWave {
|
|
18
|
+
index: number;
|
|
19
|
+
taskIds: string[];
|
|
20
|
+
label?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The full execution plan produced by topological sort. */
|
|
24
|
+
export interface ExecutionPlan {
|
|
25
|
+
waves: ExecutionWave[];
|
|
26
|
+
hasCycle: boolean;
|
|
27
|
+
cycleNodes?: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Build an execution plan from a flat list of task nodes using Kahn's algorithm.
|
|
32
|
+
*
|
|
33
|
+
* - Tasks with empty `dependsOn` go into wave 0.
|
|
34
|
+
* - Each subsequent wave contains tasks whose dependencies are all in earlier waves.
|
|
35
|
+
* - If all tasks have empty `dependsOn`, they all go into wave 0 (backward compatible).
|
|
36
|
+
* - If a cycle is detected, `hasCycle` is true and `cycleNodes` lists the involved IDs.
|
|
37
|
+
*/
|
|
38
|
+
export function buildExecutionPlan(tasks: TaskNode[]): ExecutionPlan {
|
|
39
|
+
if (tasks.length === 0) {
|
|
40
|
+
return { waves: [], hasCycle: false };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const idSet = new Set<string>(tasks.map((t) => t.id));
|
|
44
|
+
const adjacency = new Map<string, Set<string>>(); // id -> ids that depend on it
|
|
45
|
+
const inDegree = new Map<string, number>();
|
|
46
|
+
|
|
47
|
+
for (const task of tasks) {
|
|
48
|
+
adjacency.set(task.id, new Set<string>());
|
|
49
|
+
inDegree.set(task.id, 0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (const task of tasks) {
|
|
53
|
+
let degree = 0;
|
|
54
|
+
for (const dep of task.dependsOn) {
|
|
55
|
+
if (!idSet.has(dep)) continue; // ignore unknown deps
|
|
56
|
+
adjacency.get(dep)!.add(task.id);
|
|
57
|
+
degree++;
|
|
58
|
+
}
|
|
59
|
+
inDegree.set(task.id, degree);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Kahn's algorithm with wave grouping
|
|
63
|
+
const waves: ExecutionWave[] = [];
|
|
64
|
+
const assigned = new Set<string>();
|
|
65
|
+
let currentWaveIds = tasks
|
|
66
|
+
.filter((t) => inDegree.get(t.id) === 0)
|
|
67
|
+
.map((t) => t.id);
|
|
68
|
+
|
|
69
|
+
let waveIndex = 0;
|
|
70
|
+
while (currentWaveIds.length > 0) {
|
|
71
|
+
for (const id of currentWaveIds) assigned.add(id);
|
|
72
|
+
|
|
73
|
+
const wave = buildWave(tasks, currentWaveIds, waveIndex);
|
|
74
|
+
waves.push(wave);
|
|
75
|
+
|
|
76
|
+
// Decrement in-degrees for dependents
|
|
77
|
+
const nextWaveCandidates = new Set<string>();
|
|
78
|
+
for (const id of currentWaveIds) {
|
|
79
|
+
for (const dependent of adjacency.get(id) ?? []) {
|
|
80
|
+
const current = inDegree.get(dependent)!;
|
|
81
|
+
inDegree.set(dependent, current - 1);
|
|
82
|
+
if (current - 1 === 0) nextWaveCandidates.add(dependent);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
currentWaveIds = [...nextWaveCandidates];
|
|
87
|
+
waveIndex++;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Detect cycle: if not all tasks were assigned, remaining ones form cycles
|
|
91
|
+
if (assigned.size < tasks.length) {
|
|
92
|
+
const cycleNodes = tasks
|
|
93
|
+
.filter((t) => !assigned.has(t.id))
|
|
94
|
+
.map((t) => t.id);
|
|
95
|
+
return {
|
|
96
|
+
waves,
|
|
97
|
+
hasCycle: true,
|
|
98
|
+
cycleNodes,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { waves, hasCycle: false };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Derive the phase label for a wave. If all tasks in the wave share the same
|
|
107
|
+
* `phase` value, use it as the wave label; otherwise leave it undefined.
|
|
108
|
+
*/
|
|
109
|
+
function buildWave(tasks: TaskNode[], ids: string[], index: number): ExecutionWave {
|
|
110
|
+
const taskMap = new Map(tasks.map((t) => [t.id, t]));
|
|
111
|
+
const waveTasks = ids.map((id) => taskMap.get(id)!).filter(Boolean);
|
|
112
|
+
|
|
113
|
+
let label: string | undefined;
|
|
114
|
+
if (waveTasks.length > 0 && waveTasks.every((t) => t.phase !== undefined)) {
|
|
115
|
+
const phases = new Set(waveTasks.map((t) => t.phase));
|
|
116
|
+
if (phases.size === 1) label = [...phases][0];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { index, taskIds: ids, label };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Return the IDs of tasks that are ready to run given a set of completed tasks.
|
|
124
|
+
*
|
|
125
|
+
* A task is "ready" when all its dependencies are in `completedTaskIds` AND
|
|
126
|
+
* it has not already been completed itself. Returns tasks from the earliest
|
|
127
|
+
* wave that still has uncompleted tasks.
|
|
128
|
+
*/
|
|
129
|
+
export function getReadyTasks(plan: ExecutionPlan, completedTaskIds: Set<string>): string[] {
|
|
130
|
+
if (plan.hasCycle || plan.waves.length === 0) return [];
|
|
131
|
+
|
|
132
|
+
const completed = completedTaskIds;
|
|
133
|
+
|
|
134
|
+
for (const wave of plan.waves) {
|
|
135
|
+
// All tasks in prior waves must be completed for this wave to be ready
|
|
136
|
+
const priorWavesComplete = plan.waves
|
|
137
|
+
.slice(0, wave.index)
|
|
138
|
+
.every((w) => w.taskIds.every((id) => completed.has(id)));
|
|
139
|
+
|
|
140
|
+
if (!priorWavesComplete) continue;
|
|
141
|
+
|
|
142
|
+
// Filter to tasks not already completed
|
|
143
|
+
const ready = wave.taskIds.filter((id) => !completed.has(id));
|
|
144
|
+
if (ready.length > 0) return ready;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Detect all cycles in the task graph using DFS.
|
|
152
|
+
*
|
|
153
|
+
* Returns an array of cycles, where each cycle is represented as an array of
|
|
154
|
+
* task IDs forming a path from a node back to itself.
|
|
155
|
+
*/
|
|
156
|
+
export function detectCycles(tasks: TaskNode[]): string[][] {
|
|
157
|
+
if (tasks.length === 0) return [];
|
|
158
|
+
|
|
159
|
+
const idSet = new Set<string>(tasks.map((t) => t.id));
|
|
160
|
+
const adjacency = new Map<string, string[]>();
|
|
161
|
+
for (const task of tasks) {
|
|
162
|
+
adjacency.set(
|
|
163
|
+
task.id,
|
|
164
|
+
task.dependsOn.filter((dep) => idSet.has(dep)),
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const WHITE = 0;
|
|
169
|
+
const GRAY = 1;
|
|
170
|
+
const BLACK = 2;
|
|
171
|
+
|
|
172
|
+
const color = new Map<string, number>();
|
|
173
|
+
for (const task of tasks) color.set(task.id, WHITE);
|
|
174
|
+
|
|
175
|
+
const cycles: string[][] = [];
|
|
176
|
+
const path: string[] = [];
|
|
177
|
+
|
|
178
|
+
function dfs(nodeId: string): void {
|
|
179
|
+
color.set(nodeId, GRAY);
|
|
180
|
+
path.push(nodeId);
|
|
181
|
+
|
|
182
|
+
const deps = adjacency.get(nodeId) ?? [];
|
|
183
|
+
for (const dep of deps) {
|
|
184
|
+
const depColor = color.get(dep);
|
|
185
|
+
if (depColor === GRAY) {
|
|
186
|
+
// Found a cycle: extract the path from dep to current node
|
|
187
|
+
const cycleStart = path.indexOf(dep);
|
|
188
|
+
if (cycleStart >= 0) {
|
|
189
|
+
cycles.push(path.slice(cycleStart));
|
|
190
|
+
}
|
|
191
|
+
} else if (depColor === WHITE) {
|
|
192
|
+
dfs(dep);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
path.pop();
|
|
197
|
+
color.set(nodeId, BLACK);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
for (const task of tasks) {
|
|
201
|
+
if (color.get(task.id) === WHITE) {
|
|
202
|
+
dfs(task.id);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return cycles;
|
|
207
|
+
}
|