pi-crew 0.9.16 → 0.9.18
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/CHANGELOG.md +130 -0
- package/README.md +19 -0
- package/dist/build-meta.json +21784 -0
- package/dist/index.mjs +83169 -0
- package/dist/index.mjs.map +7 -0
- package/docs/A +358 -0
- package/docs/M-A +357 -0
- package/docs/REVIEW-FINDINGS-2026-06 +357 -0
- package/docs/Y +357 -0
- package/docs/a +357 -0
- package/docs/aA +358 -0
- package/docs/archive/README.md +91 -0
- package/docs/migration/atomic-write-v2-migration.md +297 -0
- package/docs/patterns/command-agent-skill.md +1 -1
- package/docs/perf/performance-review-2026-07.md +287 -0
- package/docs/skills/REFERENCE.md +0 -17
- package/docs//303/242mmaAAA/303/242 +357 -0
- package/index.bundle.ts +25 -0
- package/index.ts +95 -4
- package/package.json +15 -4
- package/skills/iterative-audit/SKILL.md +0 -1
- package/skills/widget-rendering/SKILL.md +17 -0
- package/src/adapters/claude-adapter.ts +1 -3
- package/src/adapters/export-util.ts +16 -16
- package/src/adapters/index.ts +5 -5
- package/src/agents/agent-config.ts +20 -14
- package/src/agents/agent-serializer.ts +1 -1
- package/src/agents/discover-agents.ts +83 -39
- package/src/benchmark/benchmark-runner.ts +51 -51
- package/src/benchmark/feedback-loop.ts +1 -1
- package/src/config/config.ts +303 -591
- package/src/config/defaults.ts +28 -2
- package/src/config/drift-detector.ts +11 -14
- package/src/config/markers.ts +201 -203
- package/src/config/resilient-parser.ts +14 -6
- package/src/config/role-tools.ts +3 -3
- package/src/config/suggestions.ts +5 -12
- package/src/config/types.ts +9 -25
- package/src/errors.ts +151 -157
- package/src/extension/action-suggestions.ts +54 -9
- package/src/extension/async-notifier.ts +43 -13
- package/src/extension/autonomous-policy.ts +77 -37
- package/src/extension/command-completions.ts +9 -8
- package/src/extension/context-status-injection.ts +14 -12
- package/src/extension/crew-autocomplete.ts +8 -16
- package/src/extension/crew-cleanup.ts +2 -1
- package/src/extension/crew-shortcuts.ts +9 -6
- package/src/extension/cross-extension-rpc.ts +136 -42
- package/src/extension/help.ts +1 -1
- package/src/extension/import-index.ts +10 -5
- package/src/extension/knowledge-injection.ts +88 -15
- package/src/extension/management.ts +89 -349
- package/src/extension/message-renderers.ts +3 -4
- package/src/extension/notification-router.ts +22 -5
- package/src/extension/notification-sink.ts +6 -3
- package/src/extension/pi-api.ts +17 -8
- package/src/extension/plan-orchestrate.ts +8 -27
- package/src/extension/project-init.ts +21 -6
- package/src/extension/register.ts +329 -975
- package/src/extension/registration/artifact-cleanup.ts +9 -4
- package/src/extension/registration/command-utils.ts +5 -1
- package/src/extension/registration/commands.ts +806 -314
- package/src/extension/registration/compaction-guard.ts +15 -15
- package/src/extension/registration/lifecycle.ts +259 -0
- package/src/extension/registration/observability.ts +293 -0
- package/src/extension/registration/subagent-helpers.ts +29 -7
- package/src/extension/registration/subagent-tools.ts +253 -44
- package/src/extension/registration/team-tool.ts +19 -84
- package/src/extension/registration/ui.ts +168 -0
- package/src/extension/registration/viewers.ts +64 -24
- package/src/extension/result-watcher.ts +18 -7
- package/src/extension/run-bundle-schema.ts +21 -6
- package/src/extension/run-export.ts +15 -6
- package/src/extension/run-import.ts +64 -29
- package/src/extension/run-index.ts +28 -9
- package/src/extension/run-maintenance.ts +32 -12
- package/src/extension/session-summary.ts +1 -1
- package/src/extension/team-manager-command.ts +64 -7
- package/src/extension/team-onboard.ts +149 -150
- package/src/extension/team-recommendation.ts +121 -21
- package/src/extension/team-tool/anchor.ts +31 -64
- package/src/extension/team-tool/api.ts +929 -141
- package/src/extension/team-tool/auto-summarize.ts +14 -26
- package/src/extension/team-tool/cache-control.ts +1 -5
- package/src/extension/team-tool/cancel.ts +138 -38
- package/src/extension/team-tool/chain-dispatch.ts +5 -13
- package/src/extension/team-tool/chain-executor.ts +29 -51
- package/src/extension/team-tool/config-patch.ts +1 -1
- package/src/extension/team-tool/context.ts +40 -20
- package/src/extension/team-tool/destructive-gate.ts +10 -5
- package/src/extension/team-tool/doctor.ts +170 -51
- package/src/extension/team-tool/explain.ts +228 -214
- package/src/extension/team-tool/failure-patterns.ts +3 -9
- package/src/extension/team-tool/goal-wrap.ts +71 -29
- package/src/extension/team-tool/goal.ts +185 -35
- package/src/extension/team-tool/handle-schedule.ts +34 -15
- package/src/extension/team-tool/handle-settings.ts +94 -56
- package/src/extension/team-tool/health-monitor.ts +27 -108
- package/src/extension/team-tool/inspect.ts +42 -9
- package/src/extension/team-tool/intent-policy.ts +4 -1
- package/src/extension/team-tool/lifecycle-actions.ts +244 -48
- package/src/extension/team-tool/orchestrate.ts +7 -18
- package/src/extension/team-tool/parallel-dispatch.ts +51 -29
- package/src/extension/team-tool/plan.ts +29 -5
- package/src/extension/team-tool/respond.ts +49 -16
- package/src/extension/team-tool/run-not-found.ts +3 -8
- package/src/extension/team-tool/run.ts +80 -317
- package/src/extension/team-tool/status.ts +135 -43
- package/src/extension/team-tool/workflow-manage.ts +92 -25
- package/src/extension/team-tool-types.ts +8 -1
- package/src/extension/team-tool.ts +142 -551
- package/src/extension/validate-resources.ts +39 -8
- package/src/hooks/registry.ts +66 -17
- package/src/hooks/types.ts +1 -1
- package/src/i18n.ts +25 -11
- package/src/observability/correlation.ts +17 -3
- package/src/observability/event-bus.ts +51 -56
- package/src/observability/event-to-metric.ts +117 -15
- package/src/observability/exporters/otlp-exporter.ts +56 -17
- package/src/observability/exporters/prometheus-exporter.ts +1 -1
- package/src/observability/metric-registry.ts +16 -4
- package/src/observability/metric-retention.ts +8 -2
- package/src/observability/metric-sink.ts +11 -3
- package/src/observability/metrics-primitives.ts +42 -9
- package/src/plugins/plugin-define.ts +2 -2
- package/src/plugins/plugin-registry.ts +25 -25
- package/src/plugins/plugins/index.ts +1 -1
- package/src/plugins/plugins/nextjs.ts +16 -16
- package/src/plugins/plugins/vite.ts +7 -11
- package/src/plugins/plugins/vitest.ts +6 -11
- package/src/runtime/adaptive-plan.ts +225 -40
- package/src/runtime/agent-control.ts +58 -15
- package/src/runtime/agent-memory.ts +15 -9
- package/src/runtime/agent-observability.ts +10 -2
- package/src/runtime/anchor-manager.ts +27 -25
- package/src/runtime/async-marker.ts +7 -1
- package/src/runtime/async-runner.ts +39 -24
- package/src/runtime/auto-summarize.ts +7 -13
- package/src/runtime/background-runner.ts +189 -251
- package/src/runtime/batch-barrier.ts +18 -16
- package/src/runtime/cancellation-token.ts +16 -6
- package/src/runtime/cancellation.ts +46 -8
- package/src/runtime/capability-inventory.ts +6 -4
- package/src/runtime/chain-parser.ts +44 -11
- package/src/runtime/chain-runner.ts +63 -69
- package/src/runtime/checkpoint.ts +12 -56
- package/src/runtime/child-pi.ts +344 -79
- package/src/runtime/coalesce-tasks.ts +268 -0
- package/src/runtime/code-summary.ts +71 -26
- package/src/runtime/compact-stages/index.ts +15 -5
- package/src/runtime/compact-stages/tail-capture-stage.ts +7 -2
- package/src/runtime/compact-stages/truncation-stage.ts +4 -1
- package/src/runtime/compaction-summary.ts +17 -10
- package/src/runtime/completion-guard.ts +33 -16
- package/src/runtime/crash-classification.ts +28 -7
- package/src/runtime/crash-recovery.ts +177 -37
- package/src/runtime/crew-agent-records.ts +145 -47
- package/src/runtime/crew-hooks.ts +9 -24
- package/src/runtime/cross-extension-rpc.ts +25 -23
- package/src/runtime/custom-tools/irc-tool.ts +43 -15
- package/src/runtime/custom-tools/submit-result-tool.ts +16 -5
- package/src/runtime/delivery-coordinator.ts +34 -7
- package/src/runtime/delta-conflict.ts +9 -21
- package/src/runtime/deterministic-ast.ts +1 -1
- package/src/runtime/diagnostic-export.ts +53 -20
- package/src/runtime/direct-run.ts +12 -2
- package/src/runtime/dwf-state-store.ts +1 -1
- package/src/runtime/dynamic-workflow-context.ts +187 -59
- package/src/runtime/dynamic-workflow-runner.ts +42 -20
- package/src/runtime/effectiveness.ts +16 -8
- package/src/runtime/errors/crew-errors.ts +7 -11
- package/src/runtime/event-stream-bridge.ts +9 -3
- package/src/runtime/foreground-control.ts +54 -14
- package/src/runtime/foreground-watchdog.ts +9 -6
- package/src/runtime/goal-achievement.ts +27 -10
- package/src/runtime/goal-evaluator.ts +54 -19
- package/src/runtime/goal-loop-runner.ts +280 -99
- package/src/runtime/goal-state-store.ts +27 -17
- package/src/runtime/green-contract.ts +11 -2
- package/src/runtime/group-join.ts +64 -31
- package/src/runtime/handoff-manager.ts +37 -42
- package/src/runtime/heartbeat-gradient.ts +10 -2
- package/src/runtime/heartbeat-watcher.ts +32 -6
- package/src/runtime/hidden-handoff.ts +13 -31
- package/src/runtime/important-line-classifier.ts +12 -2
- package/src/runtime/iteration-hooks.ts +25 -15
- package/src/runtime/live-agent-control.ts +55 -7
- package/src/runtime/live-agent-manager.ts +130 -27
- package/src/runtime/live-control-realtime.ts +23 -3
- package/src/runtime/live-irc.ts +4 -1
- package/src/runtime/live-session-health.ts +12 -4
- package/src/runtime/live-session-runtime.ts +379 -117
- package/src/runtime/manifest-cache.ts +28 -12
- package/src/runtime/mcp-proxy.ts +10 -18
- package/src/runtime/metric-parser.ts +1 -5
- package/src/runtime/model-fallback.ts +55 -18
- package/src/runtime/model-resolver.ts +2 -6
- package/src/runtime/model-scope.ts +19 -3
- package/src/runtime/notebook-helpers.ts +60 -62
- package/src/runtime/orphan-worker-registry.ts +37 -26
- package/src/runtime/output-validator.ts +18 -3
- package/src/runtime/overflow-recovery.ts +5 -4
- package/src/runtime/parallel-research.ts +29 -5
- package/src/runtime/parallel-utils.ts +9 -11
- package/src/runtime/path-overlap.ts +150 -0
- package/src/runtime/peer-dep.ts +12 -17
- package/src/runtime/per-write-validator.ts +1 -3
- package/src/runtime/phase-tracker.ts +342 -330
- package/src/runtime/pi-args.ts +26 -8
- package/src/runtime/pi-json-output.ts +1 -1
- package/src/runtime/pi-spawn.ts +30 -19
- package/src/runtime/pipeline-runner.ts +56 -55
- package/src/runtime/plan-templates.ts +5 -6
- package/src/runtime/policy-engine.ts +43 -7
- package/src/runtime/post-checks.ts +12 -4
- package/src/runtime/post-exit-stdio-guard.ts +2 -2
- package/src/runtime/process-lifecycle.ts +20 -10
- package/src/runtime/process-status.ts +16 -5
- package/src/runtime/progress-event-coalescer.ts +2 -1
- package/src/runtime/progress-tracker.ts +103 -103
- package/src/runtime/prose-compressor.ts +9 -11
- package/src/runtime/recovery-recipes.ts +118 -24
- package/src/runtime/replace.ts +25 -10
- package/src/runtime/resilient-edit.ts +10 -25
- package/src/runtime/result-extractor.ts +2 -6
- package/src/runtime/retry-executor.ts +20 -4
- package/src/runtime/retry-runner.ts +27 -51
- package/src/runtime/role-permission.ts +6 -1
- package/src/runtime/run-coalesced-task-group.ts +256 -0
- package/src/runtime/run-drift.ts +14 -15
- package/src/runtime/run-tracker.ts +6 -28
- package/src/runtime/runtime-policy.ts +1 -6
- package/src/runtime/runtime-resolver.ts +80 -15
- package/src/runtime/scheduler.ts +47 -18
- package/src/runtime/semaphore.ts +5 -7
- package/src/runtime/sensitive-paths.ts +2 -1
- package/src/runtime/session-usage.ts +1 -1
- package/src/runtime/settings-store.ts +16 -12
- package/src/runtime/sidechain-output.ts +6 -2
- package/src/runtime/single-agent-compose.ts +1 -4
- package/src/runtime/skill-effectiveness.ts +29 -97
- package/src/runtime/skill-instructions.ts +40 -83
- package/src/runtime/stale-reconciler.ts +73 -197
- package/src/runtime/stream-preview.ts +1 -1
- package/src/runtime/streaming-output.ts +1 -1
- package/src/runtime/subagent-manager.ts +44 -169
- package/src/runtime/subprocess-tool-registry.ts +4 -1
- package/src/runtime/supervisor-contact.ts +10 -5
- package/src/runtime/task-display.ts +19 -5
- package/src/runtime/task-graph-scheduler.ts +95 -21
- package/src/runtime/task-graph.ts +5 -11
- package/src/runtime/task-health.ts +54 -47
- package/src/runtime/task-id.ts +12 -19
- package/src/runtime/task-output-context.ts +265 -81
- package/src/runtime/task-packet.ts +12 -20
- package/src/runtime/task-quality.ts +6 -14
- package/src/runtime/task-runner/context-retrieval.ts +4 -13
- package/src/runtime/task-runner/live-executor.ts +114 -36
- package/src/runtime/task-runner/output-splitter.ts +152 -0
- package/src/runtime/task-runner/progress.ts +50 -12
- package/src/runtime/task-runner/prompt-builder.ts +31 -7
- package/src/runtime/task-runner/prompt-pipeline.ts +31 -7
- package/src/runtime/task-runner/result-utils.ts +3 -1
- package/src/runtime/task-runner/retrieval-orchestrator.ts +310 -0
- package/src/runtime/task-runner/run-projection.ts +27 -8
- package/src/runtime/task-runner/state-helpers.ts +50 -14
- package/src/runtime/task-runner/tail-read.ts +2 -6
- package/src/runtime/task-runner.ts +180 -326
- package/src/runtime/team-runner-artifacts.ts +13 -0
- package/src/runtime/team-runner.ts +456 -974
- package/src/runtime/tool-output-pruner.ts +6 -9
- package/src/runtime/tool-progress.ts +11 -21
- package/src/runtime/verification-gates.ts +33 -24
- package/src/runtime/verification-integrity.ts +2 -7
- package/src/runtime/verification-worktree.ts +62 -12
- package/src/runtime/worker-heartbeat.ts +5 -1
- package/src/runtime/worker-startup.ts +29 -5
- package/src/runtime/workflow-state.ts +17 -9
- package/src/runtime/workspace-lock.ts +30 -35
- package/src/runtime/workspace-tree.ts +20 -32
- package/src/runtime/yield-handler.ts +43 -9
- package/src/runtime/zombie-scanner.ts +5 -4
- package/src/schema/config-schema.ts +266 -172
- package/src/schema/team-tool-schema.ts +29 -74
- package/src/schema/validation-types.ts +25 -17
- package/src/skills/discover-skills.ts +35 -10
- package/src/skills/skill-templates.ts +109 -27
- package/src/skills/validate.ts +26 -8
- package/src/state/active-run-registry.ts +87 -28
- package/src/state/artifact-store.ts +9 -5
- package/src/state/atomic-write-v2.ts +85 -63
- package/src/state/atomic-write.ts +106 -27
- package/src/state/blob-store.ts +47 -20
- package/src/state/contracts.ts +20 -5
- package/src/state/crew-init.ts +5 -22
- package/src/state/decision-ledger.ts +19 -73
- package/src/state/event-log-rotation.ts +41 -15
- package/src/state/event-log.ts +168 -53
- package/src/state/event-reconstructor.ts +11 -1
- package/src/state/gitignore-manager.ts +2 -8
- package/src/state/health-store.ts +57 -57
- package/src/state/hook-instinct-bridge.ts +1 -1
- package/src/state/hook-integrations.ts +1 -1
- package/src/state/instinct-store.ts +17 -5
- package/src/state/jsonl-writer.ts +1 -1
- package/src/state/locks.ts +23 -9
- package/src/state/mailbox.ts +151 -28
- package/src/state/observation-store.ts +20 -14
- package/src/state/run-cache.ts +142 -135
- package/src/state/run-graph.ts +6 -15
- package/src/state/run-metrics.ts +5 -18
- package/src/state/schedule.ts +15 -9
- package/src/state/state-store.ts +137 -47
- package/src/state/task-claims.ts +12 -2
- package/src/state/tiered-eval.ts +52 -43
- package/src/state/types-eval.ts +2 -2
- package/src/state/types.ts +24 -20
- package/src/state/usage.ts +20 -4
- package/src/state/worker-atomic-writer.ts +6 -3
- package/src/subagents/index.ts +2 -2
- package/src/teams/discover-teams.ts +21 -6
- package/src/tools/safe-bash-extension.ts +5 -6
- package/src/tools/safe-bash.ts +10 -11
- package/src/types/new-api-types.ts +6 -10
- package/src/ui/agent-management-overlay.ts +52 -40
- package/src/ui/card-colors.ts +13 -4
- package/src/ui/crew-footer.ts +8 -7
- package/src/ui/crew-select-list.ts +1 -1
- package/src/ui/dashboard-panes/agents-pane.ts +41 -25
- package/src/ui/dashboard-panes/cancellation-pane.ts +1 -1
- package/src/ui/dashboard-panes/capability-pane.ts +32 -15
- package/src/ui/dashboard-panes/health-pane.ts +2 -1
- package/src/ui/dashboard-panes/metrics-pane.ts +4 -1
- package/src/ui/dashboard-panes/progress-pane.ts +12 -9
- package/src/ui/heartbeat-aggregator.ts +15 -4
- package/src/ui/keybinding-map.ts +40 -7
- package/src/ui/live-conversation-overlay.ts +36 -20
- package/src/ui/live-duration.ts +1 -4
- package/src/ui/live-run-sidebar.ts +88 -25
- package/src/ui/loaders.ts +2 -8
- package/src/ui/mascot.ts +20 -36
- package/src/ui/overlays/agent-picker-overlay.ts +11 -3
- package/src/ui/overlays/confirm-overlay.ts +4 -2
- package/src/ui/overlays/help-overlay.ts +25 -14
- package/src/ui/overlays/mailbox-compose-overlay.ts +48 -11
- package/src/ui/overlays/mailbox-compose-preview.ts +20 -5
- package/src/ui/overlays/mailbox-detail-overlay.ts +27 -6
- package/src/ui/pi-ui-compat.ts +7 -7
- package/src/ui/powerbar-publisher.ts +120 -42
- package/src/ui/render-diff.ts +10 -3
- package/src/ui/render-scheduler.ts +12 -4
- package/src/ui/run-action-dispatcher.ts +81 -18
- package/src/ui/run-dashboard.ts +172 -76
- package/src/ui/run-event-bus.ts +59 -37
- package/src/ui/run-snapshot-cache.ts +276 -86
- package/src/ui/settings-overlay.ts +361 -74
- package/src/ui/status-colors.ts +12 -1
- package/src/ui/syntax-highlight.ts +1 -1
- package/src/ui/terminal-status.ts +1 -3
- package/src/ui/theme-adapter.ts +16 -14
- package/src/ui/theme-discovery.ts +13 -2
- package/src/ui/tool-progress-formatter.ts +7 -7
- package/src/ui/tool-render.ts +128 -56
- package/src/ui/tool-renderers/brief-mode.ts +45 -27
- package/src/ui/tool-renderers/index.ts +109 -43
- package/src/ui/transcript-cache.ts +32 -6
- package/src/ui/transcript-entries.ts +25 -23
- package/src/ui/transcript-viewer.ts +78 -29
- package/src/ui/widget/index.ts +99 -40
- package/src/ui/widget/widget-formatters.ts +13 -6
- package/src/ui/widget/widget-model.ts +19 -8
- package/src/ui/widget/widget-renderer.ts +23 -13
- package/src/ui/widget/widget-types.ts +1 -1
- package/src/utils/bm25-search.ts +199 -199
- package/src/utils/conflict-detect.ts +22 -21
- package/src/utils/env-filter.ts +14 -7
- package/src/utils/file-coalescer.ts +5 -1
- package/src/utils/fingerprint.ts +3 -6
- package/src/utils/frontmatter.ts +3 -1
- package/src/utils/fs-watch.ts +2 -6
- package/src/utils/gh-protocol.ts +119 -44
- package/src/utils/git.ts +13 -15
- package/src/utils/guards.ts +2 -5
- package/src/utils/ids.ts +9 -2
- package/src/utils/incremental-reader.ts +14 -3
- package/src/utils/internal-error.ts +2 -1
- package/src/utils/names.ts +12 -3
- package/src/utils/paths.ts +49 -5
- package/src/utils/project-detector.ts +2 -2
- package/src/utils/redaction.ts +29 -19
- package/src/utils/resolve-shell.ts +9 -7
- package/src/utils/run-watcher-registry.ts +19 -31
- package/src/utils/safe-paths.ts +46 -29
- package/src/utils/scan-cache.ts +9 -2
- package/src/utils/session-utils.ts +2 -4
- package/src/utils/sleep.ts +12 -5
- package/src/utils/sse-parser.ts +5 -17
- package/src/utils/visual.ts +52 -32
- package/src/workflows/discover-workflows.ts +41 -109
- package/src/workflows/intermediate-store.ts +5 -21
- package/src/workflows/preflight-validator.ts +6 -33
- package/src/workflows/topology-analyzer.ts +5 -21
- package/src/workflows/workflow-config.ts +7 -6
- package/src/worktree/branch-freshness.ts +66 -8
- package/src/worktree/cleanup.ts +130 -20
- package/src/worktree/worktree-manager.ts +212 -69
- package/skills/artifact-analysis-loop/SKILL.md +0 -303
- package/skills/detection-pipeline-design/SKILL.md +0 -286
- package/skills/hunting-investigation-loop/SKILL.md +0 -402
- package/skills/incident-playbook-construction/SKILL.md +0 -384
- package/skills/security-review/SKILL.md +0 -561
- package/skills/threat-hypothesis-framework/SKILL.md +0 -176
- package/skills/ui-render-performance/SKILL.md +0 -58
- /package/docs/{followup-review-round3-2026-05-12.md → archive/followup-review-round3-2026-05-12.md} +0 -0
- /package/docs/{followup-review-round4-2026-05-13.md → archive/followup-review-round4-2026-05-13.md} +0 -0
- /package/docs/{pi-crew-bugs.md → archive/pi-crew-bugs.md} +0 -0
- /package/docs/{pi-crew-test-final.md → archive/pi-crew-test-final.md} +0 -0
- /package/docs/{pi-crew-test-results.md → archive/pi-crew-test-results.md} +0 -0
- /package/docs/{pi-crew-test-round2.md → archive/pi-crew-test-round2.md} +0 -0
- /package/docs/{pi-crew-test-round4.md → archive/pi-crew-test-round4.md} +0 -0
- /package/docs/{pi-crew-test-round5.md → archive/pi-crew-test-round5.md} +0 -0
- /package/docs/{pi-crew-test-round6.md → archive/pi-crew-test-round6.md} +0 -0
- /package/docs/{pi-crew-v0.5.10-audit-fix-plan.md → archive/pi-crew-v0.5.10-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.11-audit-fix-plan.md → archive/pi-crew-v0.5.11-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.12-audit-fix-plan.md → archive/pi-crew-v0.5.12-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.13-audit-fix-plan.md → archive/pi-crew-v0.5.13-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.14-audit-fix-plan.md → archive/pi-crew-v0.5.14-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.16-audit-fix-plan.md → archive/pi-crew-v0.5.16-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.17-audit-fix-plan.md → archive/pi-crew-v0.5.17-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.5-audit-fix-plan.md → archive/pi-crew-v0.5.5-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.9-audit-fix-plan.md → archive/pi-crew-v0.5.9-audit-fix-plan.md} +0 -0
- /package/docs/{pi-mono-opportunities.md → archive/pi-mono-opportunities.md} +0 -0
- /package/docs/{pi-mono-review.md → archive/pi-mono-review.md} +0 -0
- /package/docs/{pi-subagent4-comparison.md → archive/pi-subagent4-comparison.md} +0 -0
- /package/docs/{pi-subagents3-deep-analysis.md → archive/pi-subagents3-deep-analysis.md} +0 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* coalesce-tasks.ts — micro-task coalescing for scheduler batching (M6).
|
|
3
|
+
*
|
|
4
|
+
* Implements the workflow-level `coalesceMicroTasks` opt-in (plan §11
|
|
5
|
+
* decision #7): default = false; when true, multiple ready tasks sharing
|
|
6
|
+
* `role` and `cwd` are grouped into a single "coalesced task group" so the
|
|
7
|
+
* scheduler dispatches ONE worker call instead of N. Trades individual task
|
|
8
|
+
* observability for fewer cold-starts when many small tasks of the same kind
|
|
9
|
+
* are queued simultaneously.
|
|
10
|
+
*
|
|
11
|
+
* Safety guards (per plan §5 M6):
|
|
12
|
+
* - Only groups tasks that share role + cwd (so the grouped worker can use
|
|
13
|
+
* a single worker prompt without per-task permission prompts).
|
|
14
|
+
* - Tasks that declare a non-false `output` are NEVER coalesced with
|
|
15
|
+
* another task that has the same write path (write-write conflict).
|
|
16
|
+
* - When the flag is off or the list is empty, returns the input list
|
|
17
|
+
* unchanged (no-op for production; zero overhead).
|
|
18
|
+
*
|
|
19
|
+
* Real-dispatch (v0.9.17+ follow-up, see m6-real-dispatch-design.md):
|
|
20
|
+
* - MVP only coalesces READ_ONLY roles (permissionForRole === "read_only").
|
|
21
|
+
* This eliminates write-path conflicts, worktree concerns, and mutation
|
|
22
|
+
* guards. Use case: parallel-research with 4 explorers → 1 worker.
|
|
23
|
+
* - MVP rejects coalescing when any step has: worktree: true, preStepScript,
|
|
24
|
+
* verify: true, or non-false output. These are per-step concerns that
|
|
25
|
+
* don't compose in a single multi-task worker.
|
|
26
|
+
* - maxGroupSize cap (default 5) prevents context-budget overflow when
|
|
27
|
+
* grouping many micro-tasks into one worker prompt.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import type { TeamTaskState } from "../state/types.ts";
|
|
31
|
+
import type { WorkflowConfig, WorkflowStep } from "../workflows/workflow-config.ts";
|
|
32
|
+
import { permissionForRole } from "./role-permission.ts";
|
|
33
|
+
|
|
34
|
+
/** Default cap on group size to prevent context-budget overflow. */
|
|
35
|
+
export const DEFAULT_MAX_GROUP_SIZE = 5;
|
|
36
|
+
|
|
37
|
+
/** Default cap on combined estimated prompt size (bytes). Conservative
|
|
38
|
+
* estimate — real prompt includes the stable prefix (~5KB) plus per-task
|
|
39
|
+
* sections. 100KB matches the budget heuristic in the design doc
|
|
40
|
+
* (m6-real-dispatch-design.md §R4). */
|
|
41
|
+
export const DEFAULT_MAX_GROUP_BYTES = 100_000;
|
|
42
|
+
|
|
43
|
+
export interface CoalescedGroup {
|
|
44
|
+
/** Stable id derived from the group membership; deterministic for tests. */
|
|
45
|
+
id: string;
|
|
46
|
+
role: string;
|
|
47
|
+
cwd: string;
|
|
48
|
+
/** Tasks to be coalesced into one worker invocation, in input order. */
|
|
49
|
+
tasks: TeamTaskState[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Group ready tasks by (role, cwd) while respecting write-path safety.
|
|
54
|
+
*
|
|
55
|
+
* Returns an array of `CoalescedGroup`. Each group contains tasks that:
|
|
56
|
+
* - share the same role and cwd
|
|
57
|
+
* - have NO write-path conflict with any other task in the group
|
|
58
|
+
*
|
|
59
|
+
* Singletons (groups with one task) are preserved — the caller can decide
|
|
60
|
+
* whether to dispatch them as-is or skip the coalesce entirely.
|
|
61
|
+
*
|
|
62
|
+
* If `enabled` is false, returns `[]` (signal to the caller to skip
|
|
63
|
+
* coalescing — use the original list).
|
|
64
|
+
*/
|
|
65
|
+
export function planCoalescedGroups(
|
|
66
|
+
readyTaskIds: string[],
|
|
67
|
+
tasks: TeamTaskState[],
|
|
68
|
+
workflow: WorkflowConfig,
|
|
69
|
+
enabled: boolean,
|
|
70
|
+
maxGroupSize: number = DEFAULT_MAX_GROUP_SIZE,
|
|
71
|
+
maxGroupBytes: number = DEFAULT_MAX_GROUP_BYTES,
|
|
72
|
+
): CoalescedGroup[] {
|
|
73
|
+
if (!enabled || readyTaskIds.length === 0) return [];
|
|
74
|
+
const taskById = new Map<string, TeamTaskState>(tasks.map((task) => [task.id, task]));
|
|
75
|
+
const stepById = new Map<string, WorkflowStep>(workflow.steps.map((step) => [step.id, step]));
|
|
76
|
+
|
|
77
|
+
// Bucket: key = `${role}\0${cwd}` → task list preserving input order
|
|
78
|
+
const buckets = new Map<string, TeamTaskState[]>();
|
|
79
|
+
for (const taskId of readyTaskIds) {
|
|
80
|
+
const task = taskById.get(taskId);
|
|
81
|
+
if (!task || !task.stepId) continue;
|
|
82
|
+
const step = stepById.get(task.stepId);
|
|
83
|
+
if (!step) continue;
|
|
84
|
+
|
|
85
|
+
// MVP constraint (real-dispatch safety): only coalesce READ_ONLY roles.
|
|
86
|
+
// Writing roles (executor, writer, implementer) require per-step hooks
|
|
87
|
+
// (worktree, preStepScript, verify) that don't compose in one worker.
|
|
88
|
+
if (permissionForRole(task.role) !== "read_only") continue;
|
|
89
|
+
|
|
90
|
+
// MVP constraint: reject if step has any per-step concern that
|
|
91
|
+
// doesn't compose in a multi-task worker. These flags set up side
|
|
92
|
+
// effects per-task that can't be combined.
|
|
93
|
+
if (step.worktree) continue;
|
|
94
|
+
if (step.preStepScript) continue;
|
|
95
|
+
if (step.verify) continue;
|
|
96
|
+
if (step.output !== undefined && step.output !== false && step.output !== "") continue;
|
|
97
|
+
|
|
98
|
+
const key = `${task.role}\0${task.cwd}`;
|
|
99
|
+
const list = buckets.get(key);
|
|
100
|
+
if (list) list.push(task);
|
|
101
|
+
else buckets.set(key, [task]);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Within each bucket, split further by write-path safety (groups of tasks
|
|
105
|
+
// that all have distinct write outputs), cap at maxGroupSize, AND cap
|
|
106
|
+
// combined estimated prompt size at maxGroupBytes. This addresses
|
|
107
|
+
// design-doc risk R4 (context-budget overflow): a worker handling N
|
|
108
|
+
// micro-tasks with large dependency contexts could exceed the model's
|
|
109
|
+
// context window. The byte cap is a heuristic, not a guarantee — the
|
|
110
|
+
// actual prompt includes rendered workspace trees and skill blocks too.
|
|
111
|
+
const groups: CoalescedGroup[] = [];
|
|
112
|
+
for (const [key, bucketTasks] of buckets) {
|
|
113
|
+
const [role, cwd] = key.split("\0");
|
|
114
|
+
const subgroups = splitByWriteSafety(bucketTasks, stepById);
|
|
115
|
+
for (const subgroup of subgroups) {
|
|
116
|
+
// Chunk by BOTH count and bytes. Greedy: keep adding tasks while
|
|
117
|
+
// adding the next one wouldn't exceed either limit. When a
|
|
118
|
+
// single task exceeds the byte limit by itself (rare), include
|
|
119
|
+
// it anyway as a singleton group so it's at least eligible.
|
|
120
|
+
let chunkStart = 0;
|
|
121
|
+
let chunkBytes = 0;
|
|
122
|
+
for (let i = 0; i < subgroup.length; i += 1) {
|
|
123
|
+
const task = subgroup[i]!;
|
|
124
|
+
const step = task.stepId ? stepById.get(task.stepId) : undefined;
|
|
125
|
+
const taskSize = estimateTaskPromptSize(task, step);
|
|
126
|
+
const wouldBeCount = i - chunkStart + 1;
|
|
127
|
+
const wouldBeBytes = chunkBytes + taskSize;
|
|
128
|
+
const overCount = wouldBeCount > maxGroupSize;
|
|
129
|
+
const overBytes = i > chunkStart && wouldBeBytes > maxGroupBytes;
|
|
130
|
+
if ((overCount || overBytes) && i > chunkStart) {
|
|
131
|
+
// Flush current chunk as a group.
|
|
132
|
+
const slice = subgroup.slice(chunkStart, i);
|
|
133
|
+
groups.push(buildGroup(slice, role, cwd));
|
|
134
|
+
chunkStart = i;
|
|
135
|
+
chunkBytes = taskSize;
|
|
136
|
+
} else {
|
|
137
|
+
chunkBytes += taskSize;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Flush final chunk.
|
|
141
|
+
if (chunkStart < subgroup.length) {
|
|
142
|
+
const slice = subgroup.slice(chunkStart);
|
|
143
|
+
groups.push(buildGroup(slice, role, cwd));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return groups;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function buildGroup(tasks: TeamTaskState[], role: string, cwd: string): CoalescedGroup {
|
|
151
|
+
return {
|
|
152
|
+
id: tasks.length === 1 ? tasks[0]!.id : tasks.map((t) => t.id).join("+"),
|
|
153
|
+
role,
|
|
154
|
+
cwd,
|
|
155
|
+
tasks,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Estimate the prompt-bytes contribution of a single task within a
|
|
161
|
+
* coalesced group. The actual prompt is much larger (workspace tree,
|
|
162
|
+
* skill block, dependency context) but the task-specific contribution
|
|
163
|
+
* is roughly proportional to the step's task text + a fixed per-task
|
|
164
|
+
* overhead for delimiters and headers.
|
|
165
|
+
*/
|
|
166
|
+
function estimateTaskPromptSize(task: TeamTaskState, step: WorkflowStep | undefined): number {
|
|
167
|
+
const taskText = step?.task?.length ?? 0;
|
|
168
|
+
const packetEstimate = 500; // task packet / scope / verification estimate
|
|
169
|
+
const delimiterOverhead = 200; // `<<<TASK_RESULT:id>>>` + section header
|
|
170
|
+
return taskText + packetEstimate + delimiterOverhead;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Split a bucket of tasks into write-safe subgroups: a subgroup may include
|
|
175
|
+
* tasks only if no two of them have the same `step.output`.
|
|
176
|
+
*
|
|
177
|
+
* Greedy: first task always goes to current group; subsequent tasks join the
|
|
178
|
+
* current group only if they don't conflict with any already-in-group member.
|
|
179
|
+
* Otherwise, they start (and extend) the next subgroup.
|
|
180
|
+
*/
|
|
181
|
+
function splitByWriteSafety(bucketTasks: TeamTaskState[], stepById: Map<string, WorkflowStep>): TeamTaskState[][] {
|
|
182
|
+
const result: TeamTaskState[][] = [];
|
|
183
|
+
for (const task of bucketTasks) {
|
|
184
|
+
const step = task.stepId ? stepById.get(task.stepId) : undefined;
|
|
185
|
+
const writePath = step?.output;
|
|
186
|
+
let placed = false;
|
|
187
|
+
for (const group of result) {
|
|
188
|
+
let conflict = false;
|
|
189
|
+
for (const other of group) {
|
|
190
|
+
const otherStep = other.stepId ? stepById.get(other.stepId) : undefined;
|
|
191
|
+
if (typeof writePath === "string" && typeof otherStep?.output === "string" && writePath === otherStep.output) {
|
|
192
|
+
conflict = true;
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (!conflict) {
|
|
197
|
+
group.push(task);
|
|
198
|
+
placed = true;
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (!placed) result.push([task]);
|
|
203
|
+
}
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Flatten a list of `CoalescedGroup` back into a flat task-id list (one entry
|
|
209
|
+
* per group). For a group of N tasks, this returns N task IDs in order. When
|
|
210
|
+
* M6 is enabled upstream, the caller can use this to count "effective ready
|
|
211
|
+
* units" (groups vs raw tasks).
|
|
212
|
+
*/
|
|
213
|
+
export function flattenGroupIds(groups: CoalescedGroup[]): string[] {
|
|
214
|
+
return groups.flatMap((group) => group.tasks.map((task) => task.id));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Find the coalesced group that contains the given task ID.
|
|
219
|
+
* Returns undefined if the task is not in any group (e.g., it was excluded
|
|
220
|
+
* by MVP constraints or wasn't part of the coalesced set).
|
|
221
|
+
*
|
|
222
|
+
* Used by the team-runner dispatch loop to look up a task's group metadata
|
|
223
|
+
* (role, cwd, group ID, sibling task IDs) when iterating the flat ready
|
|
224
|
+
* list and discovering which tasks should be batched together.
|
|
225
|
+
*/
|
|
226
|
+
export function findGroupContainingTask(groups: CoalescedGroup[], taskId: string): CoalescedGroup | undefined {
|
|
227
|
+
for (const group of groups) {
|
|
228
|
+
if (group.tasks.some((task) => task.id === taskId)) return group;
|
|
229
|
+
}
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Expand a list of task IDs into a list of dispatch units, where each unit
|
|
235
|
+
* is either a single task (for singletons / non-coalescable tasks) or a
|
|
236
|
+
* CoalescedGroup (for multi-task batches). The returned units can be fed
|
|
237
|
+
* directly into mapConcurrent.
|
|
238
|
+
*
|
|
239
|
+
* Tasks not present in any coalesced group are returned as singleton units.
|
|
240
|
+
*/
|
|
241
|
+
export type DispatchUnit = { kind: "singleton"; taskId: string } | { kind: "group"; group: CoalescedGroup };
|
|
242
|
+
|
|
243
|
+
export function buildDispatchUnits(readyTaskIds: string[], coalescedGroups: CoalescedGroup[]): DispatchUnit[] {
|
|
244
|
+
const groupByTaskId = new Map<string, CoalescedGroup>();
|
|
245
|
+
for (const group of coalescedGroups) {
|
|
246
|
+
if (group.tasks.length < 2) continue; // singletons handled separately
|
|
247
|
+
for (const task of group.tasks) {
|
|
248
|
+
groupByTaskId.set(task.id, group);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const visited = new Set<string>();
|
|
252
|
+
const units: DispatchUnit[] = [];
|
|
253
|
+
// First pass: emit groups (one unit per group, in input order)
|
|
254
|
+
for (const taskId of readyTaskIds) {
|
|
255
|
+
const group = groupByTaskId.get(taskId);
|
|
256
|
+
if (!group) continue;
|
|
257
|
+
if (visited.has(group.id)) continue;
|
|
258
|
+
visited.add(group.id);
|
|
259
|
+
for (const t of group.tasks) visited.add(t.id);
|
|
260
|
+
units.push({ kind: "group", group });
|
|
261
|
+
}
|
|
262
|
+
// Second pass: emit singletons (and tasks whose group is size 1)
|
|
263
|
+
for (const taskId of readyTaskIds) {
|
|
264
|
+
if (visited.has(taskId)) continue;
|
|
265
|
+
units.push({ kind: "singleton", taskId });
|
|
266
|
+
}
|
|
267
|
+
return units;
|
|
268
|
+
}
|
|
@@ -23,17 +23,21 @@ export interface SummaryResult {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export interface SummaryOptions {
|
|
26
|
-
minBodyLines?: number;
|
|
26
|
+
minBodyLines?: number; // default 4
|
|
27
27
|
minCommentLines?: number; // default 6
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
// ── Language detection ──
|
|
31
31
|
|
|
32
32
|
const EXT_MAP: ReadonlyMap<string, string> = new Map([
|
|
33
|
-
[".ts", "typescript"],
|
|
34
|
-
[".
|
|
35
|
-
[".
|
|
36
|
-
[".
|
|
33
|
+
[".ts", "typescript"],
|
|
34
|
+
[".tsx", "typescript"],
|
|
35
|
+
[".js", "javascript"],
|
|
36
|
+
[".jsx", "javascript"],
|
|
37
|
+
[".mjs", "javascript"],
|
|
38
|
+
[".cjs", "javascript"],
|
|
39
|
+
[".py", "python"],
|
|
40
|
+
[".rs", "rust"],
|
|
37
41
|
]);
|
|
38
42
|
|
|
39
43
|
export function detectLanguage(filePath: string): string | null {
|
|
@@ -44,7 +48,10 @@ export function detectLanguage(filePath: string): string | null {
|
|
|
44
48
|
|
|
45
49
|
// ── Internal range helpers ──
|
|
46
50
|
|
|
47
|
-
interface Range {
|
|
51
|
+
interface Range {
|
|
52
|
+
start: number;
|
|
53
|
+
end: number;
|
|
54
|
+
}
|
|
48
55
|
|
|
49
56
|
function mergeRanges(ranges: Range[]): Range[] {
|
|
50
57
|
if (ranges.length === 0) return [];
|
|
@@ -73,8 +80,12 @@ function findBraceRanges(lines: string[], openPattern: RegExp, minBody: number):
|
|
|
73
80
|
const start = i;
|
|
74
81
|
for (let j = i; j < lines.length; j++) {
|
|
75
82
|
for (const ch of lines[j]) {
|
|
76
|
-
if (ch === "{") {
|
|
77
|
-
|
|
83
|
+
if (ch === "{") {
|
|
84
|
+
depth++;
|
|
85
|
+
foundOpen = true;
|
|
86
|
+
} else if (ch === "}") {
|
|
87
|
+
depth--;
|
|
88
|
+
}
|
|
78
89
|
}
|
|
79
90
|
if (foundOpen && depth <= 0) {
|
|
80
91
|
if (j - start - 1 >= minBody) ranges.push({ start: start + 1, end: j - 1 });
|
|
@@ -107,12 +118,14 @@ function blockCommentRanges(lines: string[], minComment: number): Range[] {
|
|
|
107
118
|
let i = 0;
|
|
108
119
|
while (i < lines.length) {
|
|
109
120
|
const idx = lines[i].indexOf("/*");
|
|
110
|
-
if (idx === -1 || lines[i].includes("*/", idx + 2)) {
|
|
121
|
+
if (idx === -1 || lines[i].includes("*/", idx + 2)) {
|
|
122
|
+
i++;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
111
125
|
const openLine = i;
|
|
112
126
|
let j = i + 1;
|
|
113
127
|
while (j < lines.length && !lines[j].includes("*/")) j++;
|
|
114
|
-
if (j < lines.length && j - openLine - 1 >= minComment)
|
|
115
|
-
ranges.push({ start: openLine + 1, end: j - 1 });
|
|
128
|
+
if (j < lines.length && j - openLine - 1 >= minComment) ranges.push({ start: openLine + 1, end: j - 1 });
|
|
116
129
|
i = j + 1;
|
|
117
130
|
}
|
|
118
131
|
return ranges;
|
|
@@ -125,10 +138,17 @@ const PY_IMPORT_RE = /^\s*(import\s|from\s+\S+\s+import\s)/;
|
|
|
125
138
|
|
|
126
139
|
function importGroupRanges(lines: string[], pattern: RegExp): Range[] {
|
|
127
140
|
const groups: Array<{ start: number; end: number }> = [];
|
|
128
|
-
let gs = -1,
|
|
141
|
+
let gs = -1,
|
|
142
|
+
last = -1;
|
|
129
143
|
for (let i = 0; i < lines.length; i++) {
|
|
130
|
-
if (pattern.test(lines[i])) {
|
|
131
|
-
|
|
144
|
+
if (pattern.test(lines[i])) {
|
|
145
|
+
if (gs === -1) gs = i;
|
|
146
|
+
last = i;
|
|
147
|
+
} else if (gs !== -1 && i > last) {
|
|
148
|
+
groups.push({ start: gs, end: last });
|
|
149
|
+
gs = -1;
|
|
150
|
+
last = -1;
|
|
151
|
+
}
|
|
132
152
|
}
|
|
133
153
|
if (gs !== -1) groups.push({ start: gs, end: last });
|
|
134
154
|
const ranges: Range[] = [];
|
|
@@ -146,7 +166,8 @@ function pythonRanges(lines: string[], minBody: number): Range[] {
|
|
|
146
166
|
const m = /^(\s*)(async\s+)?def\s/.exec(lines[i]) || /^(\s*)class\s/.exec(lines[i]);
|
|
147
167
|
if (!m) continue;
|
|
148
168
|
const base = m[1].length;
|
|
149
|
-
let bs = -1,
|
|
169
|
+
let bs = -1,
|
|
170
|
+
be = -1;
|
|
150
171
|
for (let j = i + 1; j < lines.length; j++) {
|
|
151
172
|
if (lines[j].trim() === "") continue;
|
|
152
173
|
const indent = lines[j].length - lines[j].trimStart().length;
|
|
@@ -180,22 +201,26 @@ function rustRanges(lines: string[], minBody: number): Range[] {
|
|
|
180
201
|
|
|
181
202
|
function fullResult(language: string | null, totalLines: number, code: string): SummaryResult {
|
|
182
203
|
return {
|
|
183
|
-
language,
|
|
204
|
+
language,
|
|
205
|
+
totalLines,
|
|
206
|
+
elided: false,
|
|
184
207
|
segments: [{ kind: "kept", startLine: 1, endLine: totalLines, text: code }],
|
|
185
208
|
rendered: code,
|
|
186
209
|
};
|
|
187
210
|
}
|
|
188
211
|
|
|
189
|
-
export function summarizeCode(
|
|
190
|
-
code: string,
|
|
191
|
-
language: string | null,
|
|
192
|
-
options?: SummaryOptions,
|
|
193
|
-
): SummaryResult {
|
|
212
|
+
export function summarizeCode(code: string, language: string | null, options?: SummaryOptions): SummaryResult {
|
|
194
213
|
const minBody = options?.minBodyLines ?? 4;
|
|
195
214
|
const minComment = options?.minCommentLines ?? 6;
|
|
196
215
|
|
|
197
216
|
if (!code || code.trim() === "") {
|
|
198
|
-
return {
|
|
217
|
+
return {
|
|
218
|
+
language,
|
|
219
|
+
totalLines: 0,
|
|
220
|
+
elided: false,
|
|
221
|
+
segments: [],
|
|
222
|
+
rendered: "",
|
|
223
|
+
};
|
|
199
224
|
}
|
|
200
225
|
|
|
201
226
|
const lines = code.split("\n");
|
|
@@ -227,13 +252,27 @@ export function summarizeCode(
|
|
|
227
252
|
let cursor = 0;
|
|
228
253
|
for (const r of ranges) {
|
|
229
254
|
if (cursor < r.start) {
|
|
230
|
-
segments.push({
|
|
255
|
+
segments.push({
|
|
256
|
+
kind: "kept",
|
|
257
|
+
startLine: cursor + 1,
|
|
258
|
+
endLine: r.start,
|
|
259
|
+
text: lines.slice(cursor, r.start).join("\n"),
|
|
260
|
+
});
|
|
231
261
|
}
|
|
232
|
-
segments.push({
|
|
262
|
+
segments.push({
|
|
263
|
+
kind: "elided",
|
|
264
|
+
startLine: r.start + 1,
|
|
265
|
+
endLine: r.end + 1,
|
|
266
|
+
});
|
|
233
267
|
cursor = r.end + 1;
|
|
234
268
|
}
|
|
235
269
|
if (cursor < totalLines) {
|
|
236
|
-
segments.push({
|
|
270
|
+
segments.push({
|
|
271
|
+
kind: "kept",
|
|
272
|
+
startLine: cursor + 1,
|
|
273
|
+
endLine: totalLines,
|
|
274
|
+
text: lines.slice(cursor).join("\n"),
|
|
275
|
+
});
|
|
237
276
|
}
|
|
238
277
|
|
|
239
278
|
// Render
|
|
@@ -243,5 +282,11 @@ export function summarizeCode(
|
|
|
243
282
|
else parts.push(` ... ${seg.endLine - seg.startLine + 1} lines elided ...`);
|
|
244
283
|
}
|
|
245
284
|
|
|
246
|
-
return {
|
|
285
|
+
return {
|
|
286
|
+
language,
|
|
287
|
+
totalLines,
|
|
288
|
+
elided: true,
|
|
289
|
+
segments,
|
|
290
|
+
rendered: parts.join("\n"),
|
|
291
|
+
};
|
|
247
292
|
}
|
|
@@ -5,9 +5,19 @@
|
|
|
5
5
|
* `../../runtime/compact-stages/`) rather than reaching into individual
|
|
6
6
|
* stage files, so internal refactors do not break the public surface.
|
|
7
7
|
*/
|
|
8
|
-
export {
|
|
9
|
-
export {
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
export { ANSI_STRIP_STAGE, AnsiStripStage } from "./ansi-strip-stage.ts";
|
|
9
|
+
export {
|
|
10
|
+
BLANK_COLLAPSE_STAGE,
|
|
11
|
+
BlankCollapseStage,
|
|
12
|
+
} from "./blank-collapse-stage.ts";
|
|
13
|
+
export { DEDUPLICATE_STAGE, DeduplicateStage } from "./deduplicate-stage.ts";
|
|
12
14
|
export { HeadSnapStage, type HeadSnapStageConfig } from "./head-snap-stage.ts";
|
|
13
|
-
export {
|
|
15
|
+
export {
|
|
16
|
+
TAIL_CAPTURE_STREAM_STAGE,
|
|
17
|
+
TailCaptureStage,
|
|
18
|
+
type TailCaptureStageConfig,
|
|
19
|
+
} from "./tail-capture-stage.ts";
|
|
20
|
+
export {
|
|
21
|
+
type TruncationMarkerConfig,
|
|
22
|
+
TruncationStage,
|
|
23
|
+
} from "./truncation-stage.ts";
|
|
@@ -42,7 +42,9 @@ export class TailCaptureStage implements ICompactStage {
|
|
|
42
42
|
const hasChars = typeof config.maxChars === "number";
|
|
43
43
|
const hasBytes = typeof config.maxBytes === "number";
|
|
44
44
|
if (hasChars === hasBytes) {
|
|
45
|
-
throw new Error(
|
|
45
|
+
throw new Error(
|
|
46
|
+
`TailCaptureStage requires exactly one of maxChars or maxBytes (got chars=${config.maxChars} bytes=${config.maxBytes})`,
|
|
47
|
+
);
|
|
46
48
|
}
|
|
47
49
|
if (hasChars && (config.maxChars as number) <= 0) throw new Error(`TailCaptureStage: maxChars must be > 0, got ${config.maxChars}`);
|
|
48
50
|
if (hasBytes && (config.maxBytes as number) <= 0) throw new Error(`TailCaptureStage: maxBytes must be > 0, got ${config.maxBytes}`);
|
|
@@ -69,4 +71,7 @@ export class TailCaptureStage implements ICompactStage {
|
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
/** Singleton: char-cap tail capture with no marker (for `stream-preview.ts` textBuffer). */
|
|
72
|
-
export const TAIL_CAPTURE_STREAM_STAGE = new TailCaptureStage({
|
|
74
|
+
export const TAIL_CAPTURE_STREAM_STAGE = new TailCaptureStage({
|
|
75
|
+
maxChars: 16_384,
|
|
76
|
+
id: "tail-capture-stream",
|
|
77
|
+
});
|
|
@@ -41,7 +41,10 @@ export class TruncationStage implements ICompactStage {
|
|
|
41
41
|
private readonly marker: TruncationMarkerConfig;
|
|
42
42
|
constructor(
|
|
43
43
|
maxChars: number,
|
|
44
|
-
opts: {
|
|
44
|
+
opts: {
|
|
45
|
+
preserveImportant?: boolean;
|
|
46
|
+
marker?: Partial<TruncationMarkerConfig>;
|
|
47
|
+
} = {},
|
|
45
48
|
) {
|
|
46
49
|
if (!Number.isFinite(maxChars) || maxChars <= 0) {
|
|
47
50
|
throw new Error(`TruncationStage: maxChars must be a positive finite number, got ${maxChars}`);
|
|
@@ -5,13 +5,18 @@
|
|
|
5
5
|
* Distilled from pi-autoresearch's compaction-summary pattern.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
class GiantLineFallbackError extends Error {
|
|
8
|
+
class GiantLineFallbackError extends Error {
|
|
9
|
+
constructor() {
|
|
10
|
+
super("GIANT_LINE_FALLBACK");
|
|
11
|
+
this.name = "GiantLineFallbackError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
9
14
|
|
|
10
15
|
import * as fs from "node:fs";
|
|
11
16
|
import * as path from "node:path";
|
|
12
|
-
import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
13
17
|
import { readJsonFile } from "../state/atomic-write.ts";
|
|
14
18
|
import type { TeamEvent } from "../state/event-log.ts";
|
|
19
|
+
import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
15
20
|
|
|
16
21
|
/** Maximum number of events to read from the tail of events.jsonl. */
|
|
17
22
|
const MAX_TAIL_EVENTS = 100;
|
|
@@ -57,7 +62,10 @@ function readTailLines(filePath: string, maxLines: number): string[] {
|
|
|
57
62
|
// For small files, just read everything
|
|
58
63
|
if (fileSize <= TAIL_MAX_READ) {
|
|
59
64
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
60
|
-
return content
|
|
65
|
+
return content
|
|
66
|
+
.split("\n")
|
|
67
|
+
.filter((line) => line.trim().length > 0)
|
|
68
|
+
.slice(-maxLines);
|
|
61
69
|
}
|
|
62
70
|
|
|
63
71
|
// For large files, read only the last chunk.
|
|
@@ -98,7 +106,10 @@ function readTailLines(filePath: string, maxLines: number): string[] {
|
|
|
98
106
|
const stat = fs.statSync(filePath);
|
|
99
107
|
if (stat.size > MAX_FALLBACK_READ) return [];
|
|
100
108
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
101
|
-
return content
|
|
109
|
+
return content
|
|
110
|
+
.split("\n")
|
|
111
|
+
.filter((line) => line.trim().length > 0)
|
|
112
|
+
.slice(-maxLines);
|
|
102
113
|
}
|
|
103
114
|
return [];
|
|
104
115
|
}
|
|
@@ -212,9 +223,7 @@ export function buildCompactionSummary(stateRoot: string): string {
|
|
|
212
223
|
}
|
|
213
224
|
|
|
214
225
|
// Section: Recent Task Results
|
|
215
|
-
const completedTasks = tasks
|
|
216
|
-
.filter((t) => t.status === "completed" || t.status === "failed")
|
|
217
|
-
.slice(-MAX_RECENT_RESULTS);
|
|
226
|
+
const completedTasks = tasks.filter((t) => t.status === "completed" || t.status === "failed").slice(-MAX_RECENT_RESULTS);
|
|
218
227
|
|
|
219
228
|
if (completedTasks.length > 0) {
|
|
220
229
|
sections.push("");
|
|
@@ -242,9 +251,7 @@ export function buildCompactionSummary(stateRoot: string): string {
|
|
|
242
251
|
|
|
243
252
|
// Section: Next Steps (pending/queued tasks)
|
|
244
253
|
const pendingStatuses = new Set(["queued", "waiting", "running"]);
|
|
245
|
-
const pendingTasks = tasks.filter(
|
|
246
|
-
(t) => pendingStatuses.has(t.status),
|
|
247
|
-
);
|
|
254
|
+
const pendingTasks = tasks.filter((t) => pendingStatuses.has(t.status));
|
|
248
255
|
if (pendingTasks.length > 0) {
|
|
249
256
|
sections.push("");
|
|
250
257
|
sections.push("## Next Steps");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
-
import type {
|
|
2
|
+
import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
3
3
|
|
|
4
4
|
// ============================================================================
|
|
5
5
|
// Phase 1.2: Completion Mutation Guard — detects tasks that claim success but
|
|
@@ -21,13 +21,27 @@ export interface CompletionMutationGuardResult {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
const MUTATING_ROLES = new Set(["executor", "test-engineer"]);
|
|
24
|
-
const MUTATING_TOOLS = new Set([
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
const MUTATING_TOOLS = new Set([
|
|
25
|
+
"edit",
|
|
26
|
+
"write",
|
|
27
|
+
"multi_edit",
|
|
28
|
+
"apply_patch",
|
|
29
|
+
"replace_in_file",
|
|
30
|
+
"insert",
|
|
31
|
+
"delete_files",
|
|
32
|
+
"create_file",
|
|
33
|
+
"overwrite",
|
|
34
|
+
"patch",
|
|
35
|
+
]);
|
|
36
|
+
const READ_ONLY_COMMANDS =
|
|
37
|
+
/^(pwd|ls|dir|cat|type|sed|grep|rg|find|git\s+(status|diff|log|show|branch|remote|rev-parse|ls-files)|npm\s+(test|run\s+(typecheck|check|lint|test|ci))|node\s+--test)\b/i;
|
|
38
|
+
const MUTATING_COMMANDS =
|
|
39
|
+
/\b(rm\s+-|del\s+|erase\s+|mv\s+|move\s+|cp\s+|copy\s+|mkdir\b|touch\b|git\s+(add|commit|push|reset|clean|checkout|switch|merge|rebase|stash)|npm\s+(install|i|uninstall|publish|version)|pnpm\s+(add|install|remove)|yarn\s+(add|install|remove)|python\b.*>|node\b.*>|echo\b.*>|Set-Content|Out-File|sed\s+-i|tee\b|dd\b.*of=|wget\b.*-O|curl\b.*-o)\b/i;
|
|
40
|
+
const READ_ONLY_HINTS =
|
|
41
|
+
/\b(read-only|no edits?|do not edit|không sửa|khong sua|chỉ đọc|chi doc|plan only|chỉ lập plan|review only|audit only)\b/i;
|
|
28
42
|
|
|
29
43
|
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
30
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : undefined;
|
|
44
|
+
return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;
|
|
31
45
|
}
|
|
32
46
|
|
|
33
47
|
function commandText(value: unknown): string {
|
|
@@ -60,7 +74,10 @@ export function collectToolCallsFromEvent(event: unknown): Array<{ tool: string;
|
|
|
60
74
|
if (!record) return [];
|
|
61
75
|
const calls: Array<{ tool: string; args?: unknown }> = [];
|
|
62
76
|
const directTool = record.toolName ?? record.name ?? record.tool;
|
|
63
|
-
if (
|
|
77
|
+
if (
|
|
78
|
+
typeof directTool === "string" &&
|
|
79
|
+
(record.type === "tool_execution_start" || record.type === "toolCall" || record.type === "tool_call")
|
|
80
|
+
) {
|
|
64
81
|
calls.push({ tool: directTool, args: record.args ?? record.input });
|
|
65
82
|
}
|
|
66
83
|
const content = Array.isArray(record.content) ? record.content : asRecord(record.message)?.content;
|
|
@@ -69,7 +86,8 @@ export function collectToolCallsFromEvent(event: unknown): Array<{ tool: string;
|
|
|
69
86
|
const item = asRecord(part);
|
|
70
87
|
if (!item) continue;
|
|
71
88
|
const tool = item.name ?? item.toolName ?? item.tool;
|
|
72
|
-
if (typeof tool === "string" && (item.type === "toolCall" || item.type === "tool_call" || item.type === "tool_execution_start"))
|
|
89
|
+
if (typeof tool === "string" && (item.type === "toolCall" || item.type === "tool_call" || item.type === "tool_execution_start"))
|
|
90
|
+
calls.push({ tool, args: item.input ?? item.args });
|
|
73
91
|
}
|
|
74
92
|
}
|
|
75
93
|
return calls;
|
|
@@ -94,7 +112,11 @@ export function evaluateCompletionMutationGuard(input: CompletionMutationGuardIn
|
|
|
94
112
|
const trimmed = line.trim();
|
|
95
113
|
if (!trimmed) continue;
|
|
96
114
|
let event: unknown;
|
|
97
|
-
try {
|
|
115
|
+
try {
|
|
116
|
+
event = JSON.parse(trimmed);
|
|
117
|
+
} catch {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
98
120
|
for (const call of collectToolCallsFromEvent(event)) {
|
|
99
121
|
observedTools.push(call.tool);
|
|
100
122
|
if (isMutatingTool(call.tool, call.args)) observedMutation = true;
|
|
@@ -133,10 +155,7 @@ function isTrivialError(error: string | undefined): boolean {
|
|
|
133
155
|
return error.trim().length === 0;
|
|
134
156
|
}
|
|
135
157
|
|
|
136
|
-
export function verifyTaskCompletion(
|
|
137
|
-
task: TeamTaskState,
|
|
138
|
-
manifest: TeamRunManifest,
|
|
139
|
-
): CompletionVerifyResult {
|
|
158
|
+
export function verifyTaskCompletion(task: TeamTaskState, manifest: TeamRunManifest): CompletionVerifyResult {
|
|
140
159
|
const warnings: string[] = [];
|
|
141
160
|
let greenLevel = 0;
|
|
142
161
|
|
|
@@ -156,9 +175,7 @@ export function verifyTaskCompletion(
|
|
|
156
175
|
}
|
|
157
176
|
|
|
158
177
|
// Check 4: For implementation tasks, verify artifacts were actually produced
|
|
159
|
-
const runArtifacts = manifest.artifacts.filter(
|
|
160
|
-
(a) => a.producer === task.id || a.producer === task.agent,
|
|
161
|
-
);
|
|
178
|
+
const runArtifacts = manifest.artifacts.filter((a) => a.producer === task.id || a.producer === task.agent);
|
|
162
179
|
if (runArtifacts.length > 0) {
|
|
163
180
|
greenLevel += 1;
|
|
164
181
|
} else if (greenLevel < 3) {
|