pi-crew 0.9.53 → 0.9.55
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 +45 -0
- package/package.json +6 -3
- package/scripts/clean-strip-types.mjs +60 -0
- package/src/adapters/claude-adapter.ts +23 -0
- package/src/adapters/codex-adapter.ts +21 -0
- package/src/adapters/cursor-adapter.ts +17 -0
- package/src/adapters/export-util.ts +143 -0
- package/src/adapters/index.ts +15 -0
- package/src/adapters/registry.ts +18 -0
- package/src/adapters/types.ts +23 -0
- package/src/agents/agent-config.ts +194 -0
- package/src/agents/agent-search.ts +98 -0
- package/src/agents/agent-serializer.ts +38 -0
- package/src/agents/discover-agents.ts +623 -0
- package/src/benchmark/benchmark-runner.ts +313 -0
- package/src/benchmark/feedback-loop.ts +73 -0
- package/src/config/config.ts +1270 -0
- package/src/config/defaults.ts +193 -0
- package/src/config/drift-detector.ts +290 -0
- package/src/config/markers.ts +330 -0
- package/src/config/resilient-parser.ts +117 -0
- package/src/config/role-tools.ts +118 -0
- package/src/config/suggestions.ts +75 -0
- package/src/config/types.ts +280 -0
- package/src/errors.ts +194 -0
- package/src/extension/action-suggestions.ts +117 -0
- package/src/extension/async-notifier.ts +179 -0
- package/src/extension/autonomous-policy.ts +209 -0
- package/src/extension/command-completions.ts +120 -0
- package/src/extension/context-status-injection.ts +185 -0
- package/src/extension/crew-autocomplete.ts +131 -0
- package/src/extension/crew-cleanup.ts +168 -0
- package/src/extension/crew-input-router.ts +93 -0
- package/src/extension/crew-shortcuts.ts +87 -0
- package/src/extension/crew-vibes/cat-frames.ts +18 -0
- package/src/extension/crew-vibes/config.ts +195 -0
- package/src/extension/crew-vibes/figures.ts +101 -0
- package/src/extension/crew-vibes/font-detect.ts +71 -0
- package/src/extension/crew-vibes/footer.ts +292 -0
- package/src/extension/crew-vibes/index.ts +432 -0
- package/src/extension/crew-vibes/provider-usage.ts +396 -0
- package/src/extension/crew-vibes/render.ts +206 -0
- package/src/extension/crew-vibes/speed.ts +286 -0
- package/src/extension/cross-extension-rpc.ts +350 -0
- package/src/extension/help.ts +62 -0
- package/src/extension/import-index.ts +74 -0
- package/src/extension/knowledge-injection.ts +402 -0
- package/src/extension/management.ts +571 -0
- package/src/extension/message-renderers.ts +108 -0
- package/src/extension/notification-router.ts +152 -0
- package/src/extension/notification-sink.ts +54 -0
- package/src/extension/pi-api.ts +56 -0
- package/src/extension/plan-orchestrate.ts +302 -0
- package/src/extension/project-init.ts +170 -0
- package/src/extension/register.ts +113 -0
- package/src/extension/registration/artifact-cleanup.ts +20 -0
- package/src/extension/registration/command-registration.ts +58 -0
- package/src/extension/registration/command-utils.ts +58 -0
- package/src/extension/registration/commands.ts +1228 -0
- package/src/extension/registration/compaction-guard.ts +333 -0
- package/src/extension/registration/context-builder.ts +135 -0
- package/src/extension/registration/crash-recovery-cache.ts +54 -0
- package/src/extension/registration/foreground-run-controller.ts +238 -0
- package/src/extension/registration/hook-registration.ts +119 -0
- package/src/extension/registration/lazy-configurers.ts +124 -0
- package/src/extension/registration/lifecycle-handlers.ts +923 -0
- package/src/extension/registration/lifecycle.ts +259 -0
- package/src/extension/registration/observability.ts +322 -0
- package/src/extension/registration/registration-types.ts +158 -0
- package/src/extension/registration/runtime-cleanup.ts +230 -0
- package/src/extension/registration/subagent-helpers.ts +125 -0
- package/src/extension/registration/subagent-manager-setup.ts +330 -0
- package/src/extension/registration/subagent-tools.ts +456 -0
- package/src/extension/registration/team-tool.ts +225 -0
- package/src/extension/registration/tool-registration.ts +50 -0
- package/src/extension/registration/ui.ts +172 -0
- package/src/extension/registration/viewers.ts +121 -0
- package/src/extension/registration/wire-cross-extension.ts +37 -0
- package/src/extension/result-watcher.ts +139 -0
- package/src/extension/rpc-hmac.ts +256 -0
- package/src/extension/run-bundle-schema.ts +104 -0
- package/src/extension/run-export.ts +97 -0
- package/src/extension/run-import.ts +168 -0
- package/src/extension/run-index.ts +131 -0
- package/src/extension/run-maintenance.ts +190 -0
- package/src/extension/session-summary.ts +18 -0
- package/src/extension/team-manager-command.ts +153 -0
- package/src/extension/team-onboard.ts +180 -0
- package/src/extension/team-recommendation.ts +288 -0
- package/src/extension/team-tool/anchor.ts +172 -0
- package/src/extension/team-tool/api.ts +1244 -0
- package/src/extension/team-tool/auto-summarize.ts +142 -0
- package/src/extension/team-tool/cache-control.ts +19 -0
- package/src/extension/team-tool/cancel.ts +337 -0
- package/src/extension/team-tool/chain-dispatch.ts +95 -0
- package/src/extension/team-tool/chain-executor.ts +379 -0
- package/src/extension/team-tool/config-patch.ts +50 -0
- package/src/extension/team-tool/context.ts +149 -0
- package/src/extension/team-tool/destructive-gate.ts +52 -0
- package/src/extension/team-tool/dispatch/automate.ts +80 -0
- package/src/extension/team-tool/dispatch/control.ts +42 -0
- package/src/extension/team-tool/dispatch/index.ts +95 -0
- package/src/extension/team-tool/dispatch/manage.ts +161 -0
- package/src/extension/team-tool/dispatch/run.ts +53 -0
- package/src/extension/team-tool/dispatch/status.ts +187 -0
- package/src/extension/team-tool/doctor.ts +431 -0
- package/src/extension/team-tool/explain.ts +282 -0
- package/src/extension/team-tool/failure-patterns.ts +118 -0
- package/src/extension/team-tool/goal-wrap.ts +330 -0
- package/src/extension/team-tool/goal.ts +556 -0
- package/src/extension/team-tool/handle-schedule.ts +332 -0
- package/src/extension/team-tool/handle-settings.ts +520 -0
- package/src/extension/team-tool/health-monitor.ts +481 -0
- package/src/extension/team-tool/inspect.ts +88 -0
- package/src/extension/team-tool/intent-policy.ts +45 -0
- package/src/extension/team-tool/lifecycle-actions.ts +615 -0
- package/src/extension/team-tool/orchestrate.ts +98 -0
- package/src/extension/team-tool/parallel-dispatch.ts +183 -0
- package/src/extension/team-tool/plan.ts +50 -0
- package/src/extension/team-tool/respond.ts +150 -0
- package/src/extension/team-tool/run-deadline.ts +66 -0
- package/src/extension/team-tool/run-not-found.ts +49 -0
- package/src/extension/team-tool/run.ts +1051 -0
- package/src/extension/team-tool/status.ts +260 -0
- package/src/extension/team-tool/workflow-manage.ts +261 -0
- package/src/extension/team-tool-types.ts +26 -0
- package/src/extension/team-tool.ts +750 -0
- package/src/extension/tool-result.ts +16 -0
- package/src/extension/validate-resources.ts +108 -0
- package/src/hooks/registry.ts +200 -0
- package/src/hooks/types.ts +69 -0
- package/src/i18n.ts +212 -0
- package/src/observability/correlation.ts +50 -0
- package/src/observability/event-bus.ts +86 -0
- package/src/observability/event-to-metric.ts +170 -0
- package/src/observability/exporters/adapter.ts +27 -0
- package/src/observability/exporters/otlp-exporter.ts +356 -0
- package/src/observability/exporters/prometheus-exporter.ts +54 -0
- package/src/observability/metric-registry.ts +100 -0
- package/src/observability/metric-retention.ts +64 -0
- package/src/observability/metric-sink.ts +99 -0
- package/src/observability/metrics-primitives.ts +219 -0
- package/src/plugins/plugin-define.ts +6 -0
- package/src/plugins/plugin-registry.ts +32 -0
- package/src/plugins/plugins/index.ts +3 -0
- package/src/plugins/plugins/nextjs.ts +19 -0
- package/src/plugins/plugins/vite.ts +10 -0
- package/src/plugins/plugins/vitest.ts +9 -0
- package/src/prompt/prompt-runtime.ts +380 -0
- package/src/runtime/adaptive-plan.ts +563 -0
- package/src/runtime/agent-control.ts +215 -0
- package/src/runtime/agent-memory.ts +81 -0
- package/src/runtime/agent-observability.ts +122 -0
- package/src/runtime/anchor-manager.ts +475 -0
- package/src/runtime/async-marker.ts +32 -0
- package/src/runtime/async-runner.ts +381 -0
- package/src/runtime/attention-events.ts +28 -0
- package/src/runtime/auto-summarize.ts +344 -0
- package/src/runtime/background-runner.ts +840 -0
- package/src/runtime/batch-barrier.ts +147 -0
- package/src/runtime/broker-issuer.ts +39 -0
- package/src/runtime/cancellation-token.ts +99 -0
- package/src/runtime/cancellation.ts +99 -0
- package/src/runtime/capability-inventory.ts +137 -0
- package/src/runtime/chain-parser.ts +237 -0
- package/src/runtime/chain-runner.ts +606 -0
- package/src/runtime/checkpoint.ts +291 -0
- package/src/runtime/child-pi-constants.ts +42 -0
- package/src/runtime/child-pi-kill.ts +180 -0
- package/src/runtime/child-pi-pool.ts +68 -0
- package/src/runtime/child-pi-spawn.ts +287 -0
- package/src/runtime/child-pi-steering.ts +128 -0
- package/src/runtime/child-pi-streams.ts +296 -0
- package/src/runtime/child-pi-transcript.ts +169 -0
- package/src/runtime/child-pi.ts +1105 -0
- package/src/runtime/coalesce-tasks.ts +268 -0
- package/src/runtime/code-summary.ts +292 -0
- package/src/runtime/command-trace.ts +105 -0
- package/src/runtime/compact-pipeline.ts +56 -0
- package/src/runtime/compact-stages/ansi-strip-stage.ts +25 -0
- package/src/runtime/compact-stages/blank-collapse-stage.ts +31 -0
- package/src/runtime/compact-stages/deduplicate-stage.ts +34 -0
- package/src/runtime/compact-stages/head-snap-stage.ts +57 -0
- package/src/runtime/compact-stages/index.ts +23 -0
- package/src/runtime/compact-stages/tail-capture-stage.ts +77 -0
- package/src/runtime/compact-stages/truncation-stage.ts +74 -0
- package/src/runtime/compaction-summary.ts +278 -0
- package/src/runtime/completion-guard.ts +207 -0
- package/src/runtime/concurrency.ts +58 -0
- package/src/runtime/crash-classification.ts +229 -0
- package/src/runtime/crash-recovery.ts +594 -0
- package/src/runtime/crew-agent-records.ts +595 -0
- package/src/runtime/crew-agent-runtime.ts +62 -0
- package/src/runtime/crew-broker-child.ts +88 -0
- package/src/runtime/crew-broker-client.ts +673 -0
- package/src/runtime/crew-broker-tokens.ts +159 -0
- package/src/runtime/crew-broker.ts +1304 -0
- package/src/runtime/crew-hooks.ts +227 -0
- package/src/runtime/cross-extension-rpc.ts +143 -0
- package/src/runtime/custom-tools/irc-tool.ts +282 -0
- package/src/runtime/custom-tools/submit-result-tool.ts +102 -0
- package/src/runtime/deadletter.ts +47 -0
- package/src/runtime/delivery-coordinator.ts +211 -0
- package/src/runtime/delta-conflict.ts +348 -0
- package/src/runtime/deterministic-ast.ts +161 -0
- package/src/runtime/diagnostic-export.ts +171 -0
- package/src/runtime/direct-run.ts +45 -0
- package/src/runtime/dwf-state-store.ts +102 -0
- package/src/runtime/dynamic-workflow-context.ts +1013 -0
- package/src/runtime/dynamic-workflow-runner.ts +325 -0
- package/src/runtime/effectiveness.ts +90 -0
- package/src/runtime/errors/crew-errors.ts +162 -0
- package/src/runtime/event-stream-bridge.ts +98 -0
- package/src/runtime/foreground-control.ts +193 -0
- package/src/runtime/foreground-watchdog.ts +132 -0
- package/src/runtime/global-worker-cap.ts +96 -0
- package/src/runtime/goal-achievement.ts +148 -0
- package/src/runtime/goal-evaluator.ts +365 -0
- package/src/runtime/goal-loop-runner.ts +825 -0
- package/src/runtime/goal-state-store.ts +219 -0
- package/src/runtime/green-contract.ts +55 -0
- package/src/runtime/group-join.ts +267 -0
- package/src/runtime/handoff-manager.ts +598 -0
- package/src/runtime/heartbeat-gradient.ts +36 -0
- package/src/runtime/heartbeat-watcher.ts +239 -0
- package/src/runtime/hidden-handoff.ts +407 -0
- package/src/runtime/important-line-classifier.ts +140 -0
- package/src/runtime/intercom-bridge.ts +187 -0
- package/src/runtime/iteration-hooks.ts +305 -0
- package/src/runtime/live-agent-control.ts +136 -0
- package/src/runtime/live-agent-manager.ts +704 -0
- package/src/runtime/live-control-realtime.ts +56 -0
- package/src/runtime/live-extension-bridge.ts +148 -0
- package/src/runtime/live-irc.ts +97 -0
- package/src/runtime/live-session-health.ts +108 -0
- package/src/runtime/live-session-runtime.ts +1146 -0
- package/src/runtime/loop-gates.ts +128 -0
- package/src/runtime/manifest-cache.ts +375 -0
- package/src/runtime/mcp-proxy.ts +105 -0
- package/src/runtime/metric-parser.ts +36 -0
- package/src/runtime/model-fallback.ts +409 -0
- package/src/runtime/model-resolver.ts +126 -0
- package/src/runtime/model-scope.ts +158 -0
- package/src/runtime/orphan-worker-registry.ts +465 -0
- package/src/runtime/output-validator.ts +202 -0
- package/src/runtime/overflow-recovery.ts +206 -0
- package/src/runtime/parallel-research.ts +68 -0
- package/src/runtime/parallel-utils.ts +160 -0
- package/src/runtime/parent-guard.ts +134 -0
- package/src/runtime/path-overlap.ts +150 -0
- package/src/runtime/peer-dep.ts +292 -0
- package/src/runtime/per-write-validator.ts +181 -0
- package/src/runtime/phase-progress.ts +217 -0
- package/src/runtime/phase-tracker.ts +385 -0
- package/src/runtime/pi-args.ts +655 -0
- package/src/runtime/pi-json-output.ts +170 -0
- package/src/runtime/pi-spawn.ts +273 -0
- package/src/runtime/pipeline-runner.ts +523 -0
- package/src/runtime/plan-templates.ts +201 -0
- package/src/runtime/policy-engine.ts +115 -0
- package/src/runtime/post-checks.ts +142 -0
- package/src/runtime/post-exit-stdio-guard.ts +109 -0
- package/src/runtime/process-lifecycle.ts +491 -0
- package/src/runtime/process-status.ts +154 -0
- package/src/runtime/progress-event-coalescer.ts +44 -0
- package/src/runtime/progress-tracker.ts +124 -0
- package/src/runtime/prose-compressor.ts +162 -0
- package/src/runtime/recovery-recipes.ts +203 -0
- package/src/runtime/replace.ts +570 -0
- package/src/runtime/resilient-edit.ts +153 -0
- package/src/runtime/result-extractor.ts +217 -0
- package/src/runtime/retry-executor.ts +109 -0
- package/src/runtime/retry-runner.ts +336 -0
- package/src/runtime/role-permission.ts +59 -0
- package/src/runtime/run-coalesced-task-group.ts +308 -0
- package/src/runtime/run-drift.ts +219 -0
- package/src/runtime/run-tracker.ts +116 -0
- package/src/runtime/run-worker.ts +78 -0
- package/src/runtime/runtime-policy.ts +29 -0
- package/src/runtime/runtime-resolver.ts +172 -0
- package/src/runtime/runtime-warmup.ts +160 -0
- package/src/runtime/scheduler.ts +353 -0
- package/src/runtime/semaphore.ts +141 -0
- package/src/runtime/sensitive-paths.ts +93 -0
- package/src/runtime/session-resources.ts +25 -0
- package/src/runtime/session-snapshot.ts +59 -0
- package/src/runtime/session-usage.ts +79 -0
- package/src/runtime/settings-store.ts +155 -0
- package/src/runtime/sidechain-output.ts +35 -0
- package/src/runtime/single-agent-compose.ts +84 -0
- package/src/runtime/skill-effectiveness.ts +524 -0
- package/src/runtime/skill-instructions.ts +394 -0
- package/src/runtime/stale-reconciler.ts +680 -0
- package/src/runtime/stream-preview.ts +184 -0
- package/src/runtime/streaming-output.ts +50 -0
- package/src/runtime/subagent-manager.ts +561 -0
- package/src/runtime/subprocess-tool-registry.ts +70 -0
- package/src/runtime/supervisor-contact.ts +64 -0
- package/src/runtime/task-display.ts +52 -0
- package/src/runtime/task-graph-scheduler.ts +227 -0
- package/src/runtime/task-graph.ts +290 -0
- package/src/runtime/task-health.ts +83 -0
- package/src/runtime/task-id.ts +161 -0
- package/src/runtime/task-output-context.ts +602 -0
- package/src/runtime/task-packet.ts +268 -0
- package/src/runtime/task-quality.ts +199 -0
- package/src/runtime/task-runner/capabilities.ts +78 -0
- package/src/runtime/task-runner/child-executor.ts +790 -0
- package/src/runtime/task-runner/context-retrieval.ts +163 -0
- package/src/runtime/task-runner/live-executor.ts +227 -0
- package/src/runtime/task-runner/output-splitter.ts +152 -0
- package/src/runtime/task-runner/post-execution.ts +480 -0
- package/src/runtime/task-runner/pre-execution.ts +365 -0
- package/src/runtime/task-runner/progress.ts +157 -0
- package/src/runtime/task-runner/prompt-builder.ts +278 -0
- package/src/runtime/task-runner/prompt-pipeline.ts +88 -0
- package/src/runtime/task-runner/result-utils.ts +16 -0
- package/src/runtime/task-runner/retrieval-orchestrator.ts +310 -0
- package/src/runtime/task-runner/run-projection.ts +125 -0
- package/src/runtime/task-runner/scaffold-executor.ts +25 -0
- package/src/runtime/task-runner/state-helpers.ts +176 -0
- package/src/runtime/task-runner/tail-read.ts +34 -0
- package/src/runtime/task-runner.ts +216 -0
- package/src/runtime/team-runner-artifacts.ts +13 -0
- package/src/runtime/team-runner.ts +2396 -0
- package/src/runtime/tool-output-pruner.ts +333 -0
- package/src/runtime/tool-progress.ts +278 -0
- package/src/runtime/usage-tracker.ts +73 -0
- package/src/runtime/verification-gates.ts +579 -0
- package/src/runtime/verification-integrity.ts +108 -0
- package/src/runtime/verification-worktree.ts +186 -0
- package/src/runtime/worker-heartbeat.ts +25 -0
- package/src/runtime/worker-startup.ts +83 -0
- package/src/runtime/workflow-state.ts +195 -0
- package/src/runtime/workspace-lock.ts +443 -0
- package/src/runtime/workspace-tree.ts +312 -0
- package/src/runtime/yield-handler.ts +224 -0
- package/src/runtime/zombie-scanner.ts +298 -0
- package/src/schema/config-schema.ts +327 -0
- package/src/schema/team-tool-schema.ts +492 -0
- package/src/schema/validation-types.ts +159 -0
- package/src/skills/discover-skills.ts +203 -0
- package/src/skills/skill-templates.ts +456 -0
- package/src/skills/validate.ts +285 -0
- package/src/state/active-run-registry.ts +439 -0
- package/src/state/artifact-store.ts +176 -0
- package/src/state/atomic-write.ts +970 -0
- package/src/state/blob-store.ts +308 -0
- package/src/state/contracts.ts +160 -0
- package/src/state/crew-init.ts +269 -0
- package/src/state/decision-ledger.ts +372 -0
- package/src/state/event-log-rotation.ts +399 -0
- package/src/state/event-log.ts +1465 -0
- package/src/state/event-reconstructor.ts +229 -0
- package/src/state/gitignore-manager.ts +46 -0
- package/src/state/health-store.ts +79 -0
- package/src/state/hook-instinct-bridge.ts +94 -0
- package/src/state/hook-integrations.ts +51 -0
- package/src/state/instinct-store.ts +275 -0
- package/src/state/jsonl-writer.ts +107 -0
- package/src/state/locks.ts +562 -0
- package/src/state/mailbox.ts +916 -0
- package/src/state/observation-store.ts +176 -0
- package/src/state/run-cache.ts +193 -0
- package/src/state/run-graph.ts +183 -0
- package/src/state/run-metrics.ts +165 -0
- package/src/state/schedule.ts +166 -0
- package/src/state/session-state-map.ts +51 -0
- package/src/state/state-store.ts +973 -0
- package/src/state/task-claims.ts +61 -0
- package/src/state/tiered-eval.ts +480 -0
- package/src/state/types-eval.ts +58 -0
- package/src/state/types.ts +447 -0
- package/src/state/usage.ts +138 -0
- package/src/state/worker-atomic-writer.ts +198 -0
- package/src/subagents/async-entry.ts +1 -0
- package/src/subagents/index.ts +3 -0
- package/src/subagents/live/control.ts +1 -0
- package/src/subagents/live/manager.ts +1 -0
- package/src/subagents/live/realtime.ts +1 -0
- package/src/subagents/live/session-runtime.ts +1 -0
- package/src/subagents/manager.ts +1 -0
- package/src/subagents/spawn.ts +1 -0
- package/src/teams/discover-teams.ts +187 -0
- package/src/teams/team-config.ts +27 -0
- package/src/teams/team-serializer.ts +38 -0
- package/src/tools/safe-bash-extension.ts +54 -0
- package/src/tools/safe-bash.ts +505 -0
- package/src/types/diff.d.ts +18 -0
- package/src/types/new-api-types.ts +35 -0
- package/src/ui/agent-management-overlay.ts +160 -0
- package/src/ui/card-colors.ts +139 -0
- package/src/ui/crew-footer.ts +102 -0
- package/src/ui/crew-select-list.ts +112 -0
- package/src/ui/dashboard-panes/agents-pane.ts +164 -0
- package/src/ui/dashboard-panes/cancellation-pane.ts +66 -0
- package/src/ui/dashboard-panes/capability-pane.ts +77 -0
- package/src/ui/dashboard-panes/health-pane.ts +31 -0
- package/src/ui/dashboard-panes/mailbox-pane.ts +35 -0
- package/src/ui/dashboard-panes/metrics-pane.ts +37 -0
- package/src/ui/dashboard-panes/progress-pane.ts +38 -0
- package/src/ui/dashboard-panes/transcript-pane.ts +10 -0
- package/src/ui/deploy-bundled-themes.ts +71 -0
- package/src/ui/dwf-phase-display.ts +151 -0
- package/src/ui/dynamic-border.ts +35 -0
- package/src/ui/format-helpers.ts +31 -0
- package/src/ui/heartbeat-aggregator.ts +74 -0
- package/src/ui/key-utils.ts +42 -0
- package/src/ui/keybinding-map.ts +248 -0
- package/src/ui/layout-primitives.ts +106 -0
- package/src/ui/live-conversation-overlay.ts +183 -0
- package/src/ui/live-duration.ts +55 -0
- package/src/ui/live-run-sidebar.ts +293 -0
- package/src/ui/loaders.ts +6 -0
- package/src/ui/mascot.ts +449 -0
- package/src/ui/overlays/agent-picker-overlay.ts +66 -0
- package/src/ui/overlays/confirm-overlay.ts +60 -0
- package/src/ui/overlays/help-overlay.ts +177 -0
- package/src/ui/overlays/mailbox-compose-overlay.ts +181 -0
- package/src/ui/overlays/mailbox-compose-preview.ts +78 -0
- package/src/ui/overlays/mailbox-detail-overlay.ts +155 -0
- package/src/ui/pi-ui-compat.ts +68 -0
- package/src/ui/powerbar-publisher.ts +459 -0
- package/src/ui/render-coalescer.ts +101 -0
- package/src/ui/render-diff.ts +160 -0
- package/src/ui/render-scheduler.ts +278 -0
- package/src/ui/run-action-dispatcher.ts +182 -0
- package/src/ui/run-dashboard.ts +910 -0
- package/src/ui/run-event-bus.ts +381 -0
- package/src/ui/run-snapshot-cache.ts +1057 -0
- package/src/ui/settings-overlay.ts +1067 -0
- package/src/ui/shared-overlay-scheduler.ts +96 -0
- package/src/ui/snapshot-types.ts +90 -0
- package/src/ui/spinner.ts +17 -0
- package/src/ui/status-colors.ts +143 -0
- package/src/ui/syntax-highlight.ts +136 -0
- package/src/ui/terminal-status.ts +269 -0
- package/src/ui/theme-adapter.ts +271 -0
- package/src/ui/theme-discovery.ts +199 -0
- package/src/ui/tool-progress-formatter.ts +93 -0
- package/src/ui/tool-renderers/brief-mode.ts +225 -0
- package/src/ui/tool-renderers/index.ts +726 -0
- package/src/ui/transcript-cache.ts +128 -0
- package/src/ui/transcript-entries.ts +256 -0
- package/src/ui/transcript-viewer.ts +424 -0
- package/src/ui/widget/index.ts +418 -0
- package/src/ui/widget/widget-formatters.ts +182 -0
- package/src/ui/widget/widget-model.ts +110 -0
- package/src/ui/widget/widget-renderer.ts +182 -0
- package/src/ui/widget/widget-types.ts +36 -0
- package/src/utils/bm25-search.ts +235 -0
- package/src/utils/completion-dedupe.ts +63 -0
- package/src/utils/conflict-detect.ts +668 -0
- package/src/utils/env-allowlist.ts +30 -0
- package/src/utils/env-filter.ts +155 -0
- package/src/utils/file-coalescer.ts +98 -0
- package/src/utils/fingerprint.ts +180 -0
- package/src/utils/frontmatter.ts +70 -0
- package/src/utils/fs-watch.ts +37 -0
- package/src/utils/gh-protocol.ts +556 -0
- package/src/utils/git.ts +260 -0
- package/src/utils/guards.ts +107 -0
- package/src/utils/ids.ts +24 -0
- package/src/utils/incremental-reader.ts +220 -0
- package/src/utils/internal-error.ts +10 -0
- package/src/utils/names.ts +36 -0
- package/src/utils/ndjson.ts +115 -0
- package/src/utils/paths.ts +227 -0
- package/src/utils/project-detector.ts +160 -0
- package/src/utils/redaction.ts +370 -0
- package/src/utils/resolve-shell.ts +36 -0
- package/src/utils/run-watcher-registry.ts +152 -0
- package/src/utils/safe-paths.ts +393 -0
- package/src/utils/scan-cache.ts +144 -0
- package/src/utils/session-utils.ts +110 -0
- package/src/utils/sleep.ts +54 -0
- package/src/utils/socket-path.ts +164 -0
- package/src/utils/sse-parser.ts +131 -0
- package/src/utils/task-name-generator.ts +337 -0
- package/src/utils/timings.ts +33 -0
- package/src/utils/token-counter.ts +200 -0
- package/src/utils/visual.ts +207 -0
- package/src/workflows/cost-estimator.ts +34 -0
- package/src/workflows/discover-workflows.ts +308 -0
- package/src/workflows/intermediate-store.ts +166 -0
- package/src/workflows/preflight-validator.ts +168 -0
- package/src/workflows/topology-analyzer.ts +203 -0
- package/src/workflows/validate-workflow.ts +50 -0
- package/src/workflows/workflow-config.ts +75 -0
- package/src/workflows/workflow-serializer.ts +32 -0
- package/src/worktree/branch-freshness.ts +112 -0
- package/src/worktree/cleanup.ts +341 -0
- package/src/worktree/worktree-manager.ts +1204 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-initialize .crew directory structure and .gitignore entries.
|
|
3
|
+
* Called on first team run in a workspace to ensure all required
|
|
4
|
+
* directories and files exist.
|
|
5
|
+
*
|
|
6
|
+
* IMPORTANT: This module is dynamically `import()`'d from concurrent child
|
|
7
|
+
* Pi subprocesses (3+ parallel subagents). Under load, the `path` namespace
|
|
8
|
+
* binding can intermittently arrive as `undefined` in jiti's ESM/CJS interop
|
|
9
|
+
* layer. We therefore use the inline helpers `parseRoot`, `safeJoin`,
|
|
10
|
+
* `safeDirname`, and `safeResolve` so that critical path operations do not
|
|
11
|
+
* depend on the `path` namespace binding.
|
|
12
|
+
*
|
|
13
|
+
* The `node:path` import is retained as a *fallback* (only used when the
|
|
14
|
+
* binding is healthy). Don't add new dependencies on other pi-crew modules.
|
|
15
|
+
*
|
|
16
|
+
* See: https://github.com/baphuongna/pi-crew/issues/28
|
|
17
|
+
*/
|
|
18
|
+
import * as fs from "node:fs";
|
|
19
|
+
import * as path from "node:path";
|
|
20
|
+
import { atomicWriteFile } from "./atomic-write.ts";
|
|
21
|
+
import { updateGitignore } from "./gitignore-manager.ts";
|
|
22
|
+
|
|
23
|
+
// Re-export updateGitignore for backwards compatibility with tests.
|
|
24
|
+
export { updateGitignore };
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* README content for the .crew directory.
|
|
28
|
+
*
|
|
29
|
+
* Defined as a function (not a `const`) to avoid the Temporal Dead Zone race
|
|
30
|
+
* documented in issue #28 + RFC 17. When this module is loaded via
|
|
31
|
+
* `jiti.import()` (pi's extension loader) wrapped in an async function, a
|
|
32
|
+
* `const` initializer can be hit in TDZ by functions hoisted above it (the
|
|
33
|
+
* same pattern that bit `crewInitPromise` in team-tool/run.ts — see commit
|
|
34
|
+
* fixing it). A function declaration is hoisted with its body available
|
|
35
|
+
* immediately, so callers always get the fully-built string.
|
|
36
|
+
*/
|
|
37
|
+
function buildCrewReadme(): string {
|
|
38
|
+
return `# .crew — pi-crew Runtime Directory
|
|
39
|
+
|
|
40
|
+
This directory contains pi-crew runtime state and artifacts.
|
|
41
|
+
|
|
42
|
+
## What's Here
|
|
43
|
+
|
|
44
|
+
| Directory | Purpose | Commit? |
|
|
45
|
+
|-----------|---------|---------|
|
|
46
|
+
| \`state/runs/\` | Run manifests, tasks, events | No |
|
|
47
|
+
| \`state/subagents/\` | Subagent state | No |
|
|
48
|
+
| \`artifacts/\` | Run outputs (test files, docs, etc.) | Optional |
|
|
49
|
+
| \`cache/\` | Cached run results (fingerprint-based) | No |
|
|
50
|
+
| \`graphs/\` | Archived run graphs | Optional |
|
|
51
|
+
| \`audit/\` | Security event logs | No |
|
|
52
|
+
|
|
53
|
+
## Cleanup
|
|
54
|
+
|
|
55
|
+
To prune old runs:
|
|
56
|
+
\`\`\`bash
|
|
57
|
+
team action='prune' keep=5
|
|
58
|
+
\`\`\`
|
|
59
|
+
|
|
60
|
+
To clear cache:
|
|
61
|
+
\`\`\`bash
|
|
62
|
+
team action='cache' action='clear'
|
|
63
|
+
\`\`\`
|
|
64
|
+
`;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Find the project root by walking up from start directory.
|
|
68
|
+
* Inline implementation to avoid module dependency on paths.ts.
|
|
69
|
+
* Matches the logic in src/utils/paths.ts:computeRepoRoot().
|
|
70
|
+
*/
|
|
71
|
+
/**
|
|
72
|
+
* Detect filesystem root for `start` without relying on `path.parse()`.
|
|
73
|
+
*
|
|
74
|
+
* **Why this exists**: This module is dynamically `import()`'d from concurrent
|
|
75
|
+
* child Pi subprocesses (3+ parallel subagents). Under load, the `path` namespace
|
|
76
|
+
* binding can intermittently arrive as `undefined` in jiti's ESM/CJS interop layer,
|
|
77
|
+
* crashing `findProjectRoot` with `TypeError: Cannot read properties of undefined
|
|
78
|
+
* (reading 'parse')` — see https://github.com/baphuongna/pi-crew/issues/28.
|
|
79
|
+
*
|
|
80
|
+
* Inlining `parse` for the termination root eliminates the dependency on the
|
|
81
|
+
* `path` binding for that critical call path.
|
|
82
|
+
*/
|
|
83
|
+
function parseRoot(start: string): string {
|
|
84
|
+
if (!start) return "/";
|
|
85
|
+
if (start[0] === "/") return "/";
|
|
86
|
+
// Windows: "C:\\" or "C:/" -> "C:\\"
|
|
87
|
+
if (/^[A-Za-z]:[\\/]/.test(start)) return start.slice(0, 3);
|
|
88
|
+
// UNC: "\\\\server\\share" — find the second path separator.
|
|
89
|
+
if (start.startsWith("\\\\") || start.startsWith("//")) {
|
|
90
|
+
const rest = start.slice(2);
|
|
91
|
+
const firstSep = Math.max(rest.indexOf("\\"), rest.indexOf("/"));
|
|
92
|
+
if (firstSep === -1) return start;
|
|
93
|
+
const secondSep = Math.max(rest.indexOf("\\", firstSep + 1), rest.indexOf("/", firstSep + 1));
|
|
94
|
+
if (secondSep === -1) return start;
|
|
95
|
+
// secondSep is an index into `rest`; add 2 to map back to `start`.
|
|
96
|
+
return start.slice(0, 2 + secondSep);
|
|
97
|
+
}
|
|
98
|
+
// Relative path — no fixed root, use start itself as terminator.
|
|
99
|
+
return start;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Defensive wrappers around `path` for use in dynamic-import contexts.
|
|
104
|
+
*
|
|
105
|
+
* **Why these exist**: This module is dynamically `import()`'d from concurrent
|
|
106
|
+
* child Pi subprocesses (3+ parallel subagents). Under load, the `path` namespace
|
|
107
|
+
* binding can intermittently arrive as `undefined` in jiti's ESM/CJS interop layer,
|
|
108
|
+
* crashing `findProjectRoot` with `TypeError: Cannot read properties of undefined
|
|
109
|
+
* (reading 'parse')` — see https://github.com/baphuongna/pi-crew/issues/28.
|
|
110
|
+
*
|
|
111
|
+
* Each helper checks that the corresponding `path` function exists before
|
|
112
|
+
* calling it, falling back to an inline implementation. This keeps the file
|
|
113
|
+
* self-contained even if the namespace binding is missing.
|
|
114
|
+
*/
|
|
115
|
+
function safeJoin(...parts: string[]): string {
|
|
116
|
+
// Cross-platform join — picks the separator based on the parts.
|
|
117
|
+
// Don't delegate to `path.join` because POSIX/Windows disagree on which
|
|
118
|
+
// separator is the path separator, and the dynamic-import context (issue
|
|
119
|
+
// #28) may have a partially-initialized `path` namespace.
|
|
120
|
+
const filtered = parts.filter(Boolean);
|
|
121
|
+
if (filtered.length === 0) return "";
|
|
122
|
+
const sep = filtered.some((p) => p.includes("\\")) ? "\\" : "/";
|
|
123
|
+
// Detect if the first part begins with a leading separator (or UNC "\\\\")
|
|
124
|
+
// so we can preserve it. F-8: collapses runs of the separator everywhere
|
|
125
|
+
// (including the body), but re-prepends the leading separator that the
|
|
126
|
+
// collapse regex would otherwise eat.
|
|
127
|
+
const firstPart = filtered[0];
|
|
128
|
+
let leading = "";
|
|
129
|
+
if (sep === "\\") {
|
|
130
|
+
if (firstPart.startsWith("\\\\")) leading = "\\\\";
|
|
131
|
+
else if (firstPart.startsWith("\\")) leading = "\\";
|
|
132
|
+
} else if (firstPart.startsWith("/")) {
|
|
133
|
+
leading = "/";
|
|
134
|
+
}
|
|
135
|
+
// Strip the leading separator(s) from the first part before joining, so
|
|
136
|
+
// the collapse regex doesn't re-collapse them.
|
|
137
|
+
const firstPartStripped = sep === "\\" ? firstPart.replace(/^\\{1,2}/, "") : firstPart.replace(/^\/+/, "");
|
|
138
|
+
const rest = filtered.slice(1);
|
|
139
|
+
const joined = [firstPartStripped, ...rest].filter(Boolean).join(sep);
|
|
140
|
+
// Collapse internal runs of the separator.
|
|
141
|
+
const collapsed = joined.replace(new RegExp(`${sep === "\\" ? "\\\\" : "/"}{2,}`, "g"), sep);
|
|
142
|
+
return leading + collapsed;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function safeDirname(p: string): string {
|
|
146
|
+
// Cross-platform dirname — handles BOTH `/` and `\` separators.
|
|
147
|
+
// Note: we don't delegate to `path.dirname` here because on POSIX it treats
|
|
148
|
+
// backslashes as part of a filename, and on Windows it treats forward
|
|
149
|
+
// slashes the same way. The dynamic-import context (issue #28) may also
|
|
150
|
+
// have a partially-initialized `path` namespace. Using a unified inline
|
|
151
|
+
// implementation ensures consistent behavior across all platforms.
|
|
152
|
+
const idx = Math.max(p.lastIndexOf("/"), p.lastIndexOf("\\"));
|
|
153
|
+
if (idx === -1) return p; // No separator at all
|
|
154
|
+
if (idx === 0) return p[0] === "/" || p[0] === "\\" ? p[0] : p; // Root: "/" or "\"
|
|
155
|
+
// Preserve drive letter roots like "C:\"
|
|
156
|
+
if (idx === 2 && /[A-Za-z]:/.test(p.slice(0, 2))) return p.slice(0, 3);
|
|
157
|
+
return p.slice(0, idx);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function safeResolve(p: string, pathDep?: typeof path): string {
|
|
161
|
+
const dep = pathDep ?? path;
|
|
162
|
+
if (dep && typeof dep.resolve === "function") return dep.resolve(p);
|
|
163
|
+
return p;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function findProjectRoot(start: string, pathDep?: typeof path): string | undefined {
|
|
167
|
+
const dirMarkers = [".git", ".hg", ".svn"];
|
|
168
|
+
const fileMarkers = ["package.json", "pyproject.toml", "Cargo.toml", "go.mod"];
|
|
169
|
+
// Use `parseRoot` (inlined above) to avoid `path.parse` for the critical
|
|
170
|
+
// termination root — fixes the jiti namespace race in issue #28.
|
|
171
|
+
const root = parseRoot(start);
|
|
172
|
+
let current = safeResolve(start, pathDep);
|
|
173
|
+
// Walk up to find project root
|
|
174
|
+
while (current !== root) {
|
|
175
|
+
for (const marker of dirMarkers) {
|
|
176
|
+
if (fs.existsSync(safeJoin(current, marker))) return current;
|
|
177
|
+
}
|
|
178
|
+
for (const marker of fileMarkers) {
|
|
179
|
+
if (fs.existsSync(safeJoin(current, marker))) return current;
|
|
180
|
+
}
|
|
181
|
+
const parent = safeDirname(current);
|
|
182
|
+
if (parent === current) break;
|
|
183
|
+
current = parent;
|
|
184
|
+
}
|
|
185
|
+
// Check root as fallback
|
|
186
|
+
if (dirMarkers.some((m) => fs.existsSync(safeJoin(root, m)))) return root;
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Compute the crew root directory for a given working directory.
|
|
192
|
+
* Matches src/utils/paths.ts:projectCrewRoot() logic.
|
|
193
|
+
*/
|
|
194
|
+
function computeCrewRoot(cwd: string): string {
|
|
195
|
+
const repoRoot = findProjectRoot(cwd) ?? cwd;
|
|
196
|
+
const crewDir = safeJoin(repoRoot, ".crew");
|
|
197
|
+
// Keep existing .crew/ stable even when .pi/ exists for project config.
|
|
198
|
+
if (fs.existsSync(crewDir)) return crewDir;
|
|
199
|
+
// Legacy reuse: if .pi/ already exists, namespace under .pi/teams/
|
|
200
|
+
const piDir = safeJoin(repoRoot, ".pi");
|
|
201
|
+
return fs.existsSync(piDir) ? safeJoin(piDir, "teams") : crewDir;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Ensure the .crew directory structure exists with all required subdirectories,
|
|
206
|
+
* placeholder files, README, and .gitignore entries.
|
|
207
|
+
*
|
|
208
|
+
* This function is self-contained with NO dependencies on other pi-crew modules.
|
|
209
|
+
* It uses inline implementations of findProjectRoot and computeCrewRoot to avoid
|
|
210
|
+
* module binding issues in child-process contexts.
|
|
211
|
+
*/
|
|
212
|
+
export async function ensureCrewDirectory(cwd: string): Promise<void> {
|
|
213
|
+
const crewRoot = computeCrewRoot(cwd);
|
|
214
|
+
|
|
215
|
+
// 1. Create directory structure
|
|
216
|
+
const dirs = [
|
|
217
|
+
crewRoot,
|
|
218
|
+
safeJoin(crewRoot, "state", "runs"),
|
|
219
|
+
safeJoin(crewRoot, "state", "subagents"),
|
|
220
|
+
safeJoin(crewRoot, "artifacts"),
|
|
221
|
+
safeJoin(crewRoot, "cache"),
|
|
222
|
+
safeJoin(crewRoot, "graphs"),
|
|
223
|
+
safeJoin(crewRoot, "audit"),
|
|
224
|
+
];
|
|
225
|
+
|
|
226
|
+
for (const dir of dirs) {
|
|
227
|
+
// Use mkdirSync directly with recursive:true to avoid TOCTOU race.
|
|
228
|
+
// This is atomic and doesn't require existsSync check.
|
|
229
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// 2. Create .gitkeep placeholders in directories that should be tracked
|
|
233
|
+
const placeholders = [
|
|
234
|
+
safeJoin(crewRoot, "artifacts", ".gitkeep"),
|
|
235
|
+
safeJoin(crewRoot, "cache", ".gitkeep"),
|
|
236
|
+
safeJoin(crewRoot, "graphs", ".gitkeep"),
|
|
237
|
+
safeJoin(crewRoot, "audit", ".gitkeep"),
|
|
238
|
+
];
|
|
239
|
+
|
|
240
|
+
for (const placeholder of placeholders) {
|
|
241
|
+
if (!fs.existsSync(placeholder)) {
|
|
242
|
+
atomicWriteFile(placeholder, "");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// 3. Write README.md (always overwrite to keep it current)
|
|
247
|
+
atomicWriteFile(safeJoin(crewRoot, "README.md"), buildCrewReadme());
|
|
248
|
+
|
|
249
|
+
// 4. Update .gitignore at project root
|
|
250
|
+
const repoRoot = findProjectRoot(cwd);
|
|
251
|
+
if (repoRoot) {
|
|
252
|
+
const gitignorePath = safeJoin(repoRoot, ".gitignore");
|
|
253
|
+
// LAZY: dodge the jiti ESM/CJS interop TDZ race on the static `import { updateGitignore }` above (issue #28, RFC 17). At this point the module body has fully evaluated, so the dynamic import resolves to a live binding.
|
|
254
|
+
const { updateGitignore: updateGitignoreFn } = await import("./gitignore-manager.ts");
|
|
255
|
+
await updateGitignoreFn(gitignorePath);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Exported only for regression tests of issue #28.
|
|
260
|
+
// NOT part of the public API — the `__test__` prefix follows the project
|
|
261
|
+
// convention used in atomic-write.ts, state-store.ts, team-runner.ts, etc.
|
|
262
|
+
// See F-4 in the post-fix review for the convention rationale.
|
|
263
|
+
export const __test__internals = {
|
|
264
|
+
parseRoot,
|
|
265
|
+
safeJoin,
|
|
266
|
+
safeDirname,
|
|
267
|
+
safeResolve,
|
|
268
|
+
findProjectRoot,
|
|
269
|
+
};
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { projectCrewRoot } from "../utils/paths.ts";
|
|
4
|
+
import { assertSafePathId } from "../utils/safe-paths.ts";
|
|
5
|
+
import { withFileLockSync } from "./locks.ts";
|
|
6
|
+
|
|
7
|
+
export interface CoherenceMark {
|
|
8
|
+
matchesPrior: boolean;
|
|
9
|
+
matchesRecursive: boolean;
|
|
10
|
+
promotionAllowed: boolean;
|
|
11
|
+
reason: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface RolloutEntry {
|
|
15
|
+
rolloutId: string;
|
|
16
|
+
timestamp: string;
|
|
17
|
+
priorWinner?: string;
|
|
18
|
+
searchSpace: string;
|
|
19
|
+
trialCount: number;
|
|
20
|
+
topCandidates: string[];
|
|
21
|
+
decisionMark: "accept" | "watch" | "reject" | "decay";
|
|
22
|
+
coherenceMark: CoherenceMark;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get the ledger file path for a given run ID.
|
|
27
|
+
* SECURITY: Accept stateRoot param to use it for path computation
|
|
28
|
+
* instead of hardcoded path, ensuring stateRoot containment.
|
|
29
|
+
* Uses projectCrewRoot() to honour the `.pi/teams/` fallback for `.pi`-based
|
|
30
|
+
* projects (see issue #29).
|
|
31
|
+
*/
|
|
32
|
+
function getLedgerPath(runId: string, stateRoot?: string, cwd?: string): string {
|
|
33
|
+
const base = stateRoot ?? join(projectCrewRoot(cwd ?? process.cwd()), "state", "runs", runId);
|
|
34
|
+
return `${base}/decision-ledger.jsonl`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Compute coherence marks based on existing ledger entries.
|
|
39
|
+
*/
|
|
40
|
+
function computeCoherence(entry: RolloutEntry, ledger: RolloutEntry[]): CoherenceMark {
|
|
41
|
+
if (ledger.length === 0) {
|
|
42
|
+
return {
|
|
43
|
+
matchesPrior: false,
|
|
44
|
+
matchesRecursive: false,
|
|
45
|
+
promotionAllowed: true,
|
|
46
|
+
reason: "No prior entries - first rollout, promotion allowed",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const previousEntry = ledger[ledger.length - 1];
|
|
51
|
+
const matchesPrior: boolean =
|
|
52
|
+
entry.decisionMark === previousEntry.decisionMark || Boolean(entry.priorWinner && entry.topCandidates.includes(entry.priorWinner));
|
|
53
|
+
|
|
54
|
+
// Check last 10 entries for recursive pattern
|
|
55
|
+
const recentEntries = ledger.slice(-10);
|
|
56
|
+
const recentDecisions = recentEntries.map((e) => e.decisionMark);
|
|
57
|
+
const currentDecision = entry.decisionMark;
|
|
58
|
+
|
|
59
|
+
const recursiveMatches = recentDecisions.filter((d) => d === currentDecision).length;
|
|
60
|
+
const matchesRecursive = recursiveMatches >= Math.ceil(recentDecisions.length / 2); // At least half match
|
|
61
|
+
|
|
62
|
+
const promotionAllowed = matchesPrior || matchesRecursive;
|
|
63
|
+
|
|
64
|
+
let reason: string;
|
|
65
|
+
if (matchesPrior && matchesRecursive) {
|
|
66
|
+
reason = `Matches prior winner and recursive pattern (${recursiveMatches}/${recentDecisions.length} recent decisions)`;
|
|
67
|
+
} else if (matchesPrior) {
|
|
68
|
+
reason = `Matches prior winner decision`;
|
|
69
|
+
} else if (matchesRecursive) {
|
|
70
|
+
reason = `Matches recursive pattern (${recursiveMatches}/3 recent decisions)`;
|
|
71
|
+
} else {
|
|
72
|
+
reason = `No match with prior or recursive pattern - requires human review`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
matchesPrior,
|
|
77
|
+
matchesRecursive,
|
|
78
|
+
promotionAllowed,
|
|
79
|
+
reason,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Initialize a new decision ledger for a run.
|
|
85
|
+
* Creates the directory and ledger file if they don't exist.
|
|
86
|
+
*/
|
|
87
|
+
export function initLedger(runId: string): void {
|
|
88
|
+
assertSafePathId("runId", runId);
|
|
89
|
+
const ledgerPath = getLedgerPath(runId);
|
|
90
|
+
const dir = dirname(ledgerPath);
|
|
91
|
+
|
|
92
|
+
if (!existsSync(dir)) {
|
|
93
|
+
mkdirSync(dir, { recursive: true });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Create empty file if it doesn't exist
|
|
97
|
+
if (!existsSync(ledgerPath)) {
|
|
98
|
+
writeFileSync(ledgerPath, "", "utf-8");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Append a new entry to the decision ledger.
|
|
104
|
+
* Automatically computes and adds coherence marks.
|
|
105
|
+
* FIX: Uses atomic write to prevent partial writes on crash.
|
|
106
|
+
* FIX: Uses withFileLockSync to prevent concurrent appendEntry calls from
|
|
107
|
+
* losing entries (classic read-check-write race).
|
|
108
|
+
*/
|
|
109
|
+
export function appendEntry(runId: string, entry: RolloutEntry): RolloutEntry {
|
|
110
|
+
assertSafePathId("runId", runId);
|
|
111
|
+
// Ensure directory exists
|
|
112
|
+
const ledgerPath = getLedgerPath(runId);
|
|
113
|
+
const dir = dirname(ledgerPath);
|
|
114
|
+
if (!existsSync(dir)) {
|
|
115
|
+
mkdirSync(dir, { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// FIX: Wrap read+write in file lock to prevent concurrent writers from
|
|
119
|
+
// reading stale state and overwriting each other's entries.
|
|
120
|
+
// P-02: Previously read the ENTIRE ledger (O(n)) and rewrote the ENTIRE
|
|
121
|
+
// file (atomicWriteFile) on every append → O(n²). Now reads only the tail
|
|
122
|
+
// (last 10 entries, which is all coherence needs) and appends a single
|
|
123
|
+
// line via appendFileSync (one write syscall → no partial-line risk).
|
|
124
|
+
let entryWithCoherence: RolloutEntry;
|
|
125
|
+
withFileLockSync(ledgerPath, () => {
|
|
126
|
+
// P-02: read tail only (last 10) for coherence computation.
|
|
127
|
+
// computeCoherence only uses ledger.slice(-10), so the full read
|
|
128
|
+
// was wasted work.
|
|
129
|
+
const tail = readTailEntries(runId, 10);
|
|
130
|
+
|
|
131
|
+
// Compute coherence
|
|
132
|
+
const coherenceMark = computeCoherence(entry, tail);
|
|
133
|
+
entryWithCoherence = { ...entry, coherenceMark };
|
|
134
|
+
|
|
135
|
+
// P-02: append single JSONL line instead of rewriting the entire
|
|
136
|
+
// file. appendFileSync issues one write syscall for small buffers,
|
|
137
|
+
// so a crash mid-write cannot corrupt earlier entries (at worst the
|
|
138
|
+
// last line is truncated — getLedger filters empty lines).
|
|
139
|
+
const line = JSON.stringify(entryWithCoherence) + "\n";
|
|
140
|
+
appendFileSync(ledgerPath, line, "utf-8");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return entryWithCoherence!;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Read all entries from the decision ledger.
|
|
148
|
+
*/
|
|
149
|
+
export function getLedger(runId: string): RolloutEntry[] {
|
|
150
|
+
assertSafePathId("runId", runId);
|
|
151
|
+
const ledgerPath = getLedgerPath(runId);
|
|
152
|
+
|
|
153
|
+
if (!existsSync(ledgerPath)) {
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const content = readFileSync(ledgerPath, "utf-8");
|
|
158
|
+
if (!content.trim()) {
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return content
|
|
163
|
+
.split("\n")
|
|
164
|
+
.filter((line) => line.trim())
|
|
165
|
+
.map((line) => JSON.parse(line) as RolloutEntry);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Read only the last `n` entries from the decision ledger.
|
|
170
|
+
*
|
|
171
|
+
* P-02: coherence computation only inspects `ledger.slice(-10)`, so reading
|
|
172
|
+
* the entire file on every append is O(n) per write → O(n²) overall. This
|
|
173
|
+
* helper reads just the tail, making append O(1) amortised. The file is
|
|
174
|
+
* bounded by run lifetime (no unbounded growth across runs).
|
|
175
|
+
*/
|
|
176
|
+
function readTailEntries(runId: string, n: number = 10): RolloutEntry[] {
|
|
177
|
+
assertSafePathId("runId", runId);
|
|
178
|
+
const ledgerPath = getLedgerPath(runId);
|
|
179
|
+
|
|
180
|
+
if (!existsSync(ledgerPath)) {
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const content = readFileSync(ledgerPath, "utf-8");
|
|
185
|
+
if (!content.trim()) {
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const lines = content.split("\n").filter((line) => line.trim());
|
|
190
|
+
return lines.slice(-n).map((line) => JSON.parse(line) as RolloutEntry);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Get the most recent entry from the decision ledger.
|
|
195
|
+
*/
|
|
196
|
+
export function getLatestDecision(runId: string): RolloutEntry | null {
|
|
197
|
+
assertSafePathId("runId", runId);
|
|
198
|
+
const tail = readTailEntries(runId, 1);
|
|
199
|
+
if (tail.length === 0) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
return tail[tail.length - 1];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Generate a human-readable markdown summary of the ledger.
|
|
207
|
+
*/
|
|
208
|
+
export function summarizeLedger(runId: string): string {
|
|
209
|
+
assertSafePathId("runId", runId);
|
|
210
|
+
const ledger = getLedger(runId);
|
|
211
|
+
|
|
212
|
+
if (ledger.length === 0) {
|
|
213
|
+
return "# Decision Ledger Summary\n\n*No entries recorded yet.*";
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const lines: string[] = ["# Decision Ledger Summary", "", `Run ID: ${runId}`, `Total Entries: ${ledger.length}`, "", "## Entries", ""];
|
|
217
|
+
|
|
218
|
+
for (let i = 0; i < ledger.length; i++) {
|
|
219
|
+
const entry = ledger[i];
|
|
220
|
+
lines.push(`### ${i + 1}. ${entry.rolloutId}`);
|
|
221
|
+
lines.push("");
|
|
222
|
+
lines.push(`- **Timestamp**: ${entry.timestamp}`);
|
|
223
|
+
lines.push(`- **Search Space**: ${entry.searchSpace}`);
|
|
224
|
+
lines.push(`- **Trial Count**: ${entry.trialCount}`);
|
|
225
|
+
lines.push(`- **Decision**: ${entry.decisionMark}`);
|
|
226
|
+
|
|
227
|
+
if (entry.priorWinner) {
|
|
228
|
+
lines.push(`- **Prior Winner**: ${entry.priorWinner}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
lines.push(`- **Top Candidates**: ${entry.topCandidates.join(", ") || "(none)"}`);
|
|
232
|
+
lines.push("");
|
|
233
|
+
lines.push("#### Coherence");
|
|
234
|
+
lines.push(`- **Matches Prior**: ${entry.coherenceMark.matchesPrior ? "✓" : "✗"}`);
|
|
235
|
+
lines.push(`- **Matches Recursive**: ${entry.coherenceMark.matchesRecursive ? "✓" : "✗"}`);
|
|
236
|
+
lines.push(`- **Promotion Allowed**: ${entry.coherenceMark.promotionAllowed ? "✓" : "✗"}`);
|
|
237
|
+
lines.push(`- **Reason**: ${entry.coherenceMark.reason}`);
|
|
238
|
+
lines.push("");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Summary statistics
|
|
242
|
+
const decisions = ledger.map((e) => e.decisionMark);
|
|
243
|
+
const acceptCount = decisions.filter((d) => d === "accept").length;
|
|
244
|
+
const watchCount = decisions.filter((d) => d === "watch").length;
|
|
245
|
+
const rejectCount = decisions.filter((d) => d === "reject").length;
|
|
246
|
+
const decayCount = decisions.filter((d) => d === "decay").length;
|
|
247
|
+
|
|
248
|
+
lines.push("## Summary");
|
|
249
|
+
lines.push("");
|
|
250
|
+
lines.push(`| Decision | Count |`);
|
|
251
|
+
lines.push(`|----------|-------|`);
|
|
252
|
+
lines.push(`| Accept | ${acceptCount} |`);
|
|
253
|
+
lines.push(`| Watch | ${watchCount} |`);
|
|
254
|
+
lines.push(`| Reject | ${rejectCount} |`);
|
|
255
|
+
lines.push(`| Decay | ${decayCount} |`);
|
|
256
|
+
lines.push("");
|
|
257
|
+
|
|
258
|
+
const promotedCount = ledger.filter((e) => e.coherenceMark.promotionAllowed).length;
|
|
259
|
+
lines.push(`**Promotion Rate**: ${promotedCount}/${ledger.length} (${((promotedCount / ledger.length) * 100).toFixed(1)}%)`);
|
|
260
|
+
|
|
261
|
+
return lines.join("\n");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Override the coherence mark of the last entry in the ledger.
|
|
266
|
+
* FIX: This preserves all previous entries while updating just the last one.
|
|
267
|
+
* Previously this would truncate the entire ledger!
|
|
268
|
+
*/
|
|
269
|
+
// NOTE: overrideLastEntry was dead code (never called). Removed in post-v0.6.2 review.
|
|
270
|
+
// If needed in the future, re-implement with assertSafePathId guard.
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Promote a candidate by marking it as accepted with proper coherence.
|
|
274
|
+
* FIX: Wrap read+write in file lock to prevent concurrent promotion/decay
|
|
275
|
+
* calls from losing entries.
|
|
276
|
+
*/
|
|
277
|
+
export function promoteCandidate(runId: string, candidate: string): RolloutEntry {
|
|
278
|
+
assertSafePathId("runId", runId);
|
|
279
|
+
assertSafePathId("candidate", candidate);
|
|
280
|
+
|
|
281
|
+
const ledgerPath = getLedgerPath(runId);
|
|
282
|
+
const dir = dirname(ledgerPath);
|
|
283
|
+
if (!existsSync(dir)) {
|
|
284
|
+
mkdirSync(dir, { recursive: true });
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
let entry: RolloutEntry;
|
|
288
|
+
withFileLockSync(ledgerPath, () => {
|
|
289
|
+
// P-02: read tail only (last 10) for coherence + latest decision.
|
|
290
|
+
// Avoids O(n) full read on every promote/decay.
|
|
291
|
+
const tail = readTailEntries(runId, 10);
|
|
292
|
+
const latestDecision = tail.length > 0 ? tail[tail.length - 1] : null;
|
|
293
|
+
|
|
294
|
+
// Create entry without coherence first
|
|
295
|
+
const entryWithoutCoherence = {
|
|
296
|
+
rolloutId: `promote-${Date.now()}`,
|
|
297
|
+
timestamp: new Date().toISOString(),
|
|
298
|
+
priorWinner: latestDecision?.topCandidates[0],
|
|
299
|
+
searchSpace: latestDecision?.searchSpace || "unknown",
|
|
300
|
+
trialCount: (latestDecision?.trialCount || 0) + 1,
|
|
301
|
+
topCandidates: [candidate],
|
|
302
|
+
decisionMark: "accept" as const,
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// Compute coherence (empty ledger = no matches)
|
|
306
|
+
const coherenceMark = computeCoherence(entryWithoutCoherence as RolloutEntry, tail);
|
|
307
|
+
|
|
308
|
+
// Manual promotion always allows further promotion
|
|
309
|
+
coherenceMark.promotionAllowed = true;
|
|
310
|
+
coherenceMark.reason = "Manual promotion - promotion allowed";
|
|
311
|
+
|
|
312
|
+
// Create full entry with coherence
|
|
313
|
+
entry = { ...entryWithoutCoherence, coherenceMark };
|
|
314
|
+
|
|
315
|
+
// P-02: append single JSONL line instead of rewriting entire file.
|
|
316
|
+
const line = JSON.stringify(entry) + "\n";
|
|
317
|
+
appendFileSync(ledgerPath, line, "utf-8");
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
return entry!;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Decay a candidate by marking it as accepted with proper coherence.
|
|
325
|
+
* FIX: Wrap read+write in file lock to prevent concurrent promotion/decay
|
|
326
|
+
* calls from losing entries.
|
|
327
|
+
*/
|
|
328
|
+
export function decayCandidate(runId: string, candidate: string): RolloutEntry {
|
|
329
|
+
assertSafePathId("runId", runId);
|
|
330
|
+
assertSafePathId("candidate", candidate);
|
|
331
|
+
|
|
332
|
+
const ledgerPath = getLedgerPath(runId);
|
|
333
|
+
const dir = dirname(ledgerPath);
|
|
334
|
+
if (!existsSync(dir)) {
|
|
335
|
+
mkdirSync(dir, { recursive: true });
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
let entry: RolloutEntry;
|
|
339
|
+
withFileLockSync(ledgerPath, () => {
|
|
340
|
+
// P-02: read tail only (last 10) for coherence + latest decision.
|
|
341
|
+
// Avoids O(n) full read on every promote/decay.
|
|
342
|
+
const tail = readTailEntries(runId, 10);
|
|
343
|
+
const latestDecision = tail.length > 0 ? tail[tail.length - 1] : null;
|
|
344
|
+
|
|
345
|
+
// Create entry without coherence first
|
|
346
|
+
const entryWithoutCoherence = {
|
|
347
|
+
rolloutId: `decay-${Date.now()}`,
|
|
348
|
+
timestamp: new Date().toISOString(),
|
|
349
|
+
priorWinner: latestDecision?.topCandidates[0],
|
|
350
|
+
searchSpace: latestDecision?.searchSpace || "unknown",
|
|
351
|
+
trialCount: (latestDecision?.trialCount || 0) + 1,
|
|
352
|
+
topCandidates: [candidate],
|
|
353
|
+
decisionMark: "decay" as const,
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
// Compute coherence (empty ledger = no matches)
|
|
357
|
+
const coherenceMark = computeCoherence(entryWithoutCoherence as RolloutEntry, tail);
|
|
358
|
+
|
|
359
|
+
// Manual decay never allows promotion
|
|
360
|
+
coherenceMark.promotionAllowed = false;
|
|
361
|
+
coherenceMark.reason = "Manual decay - promotion not allowed";
|
|
362
|
+
|
|
363
|
+
// Create full entry with coherence
|
|
364
|
+
entry = { ...entryWithoutCoherence, coherenceMark };
|
|
365
|
+
|
|
366
|
+
// P-02: append single JSONL line instead of rewriting entire file.
|
|
367
|
+
const line = JSON.stringify(entry) + "\n";
|
|
368
|
+
appendFileSync(ledgerPath, line, "utf-8");
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
return entry!;
|
|
372
|
+
}
|