@entelligentsia/forgecli 1.0.25 → 1.0.40
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 +341 -0
- package/README.md +2 -0
- package/dist/CHANGELOG-forge-plugin.md +264 -0
- package/dist/CHANGELOG-pi.md +143 -0
- package/dist/bin/argv.d.ts +2 -2
- package/dist/bin/argv.js +37 -0
- package/dist/bin/argv.js.map +1 -1
- package/dist/bin/forge.js +30 -16
- package/dist/bin/forge.js.map +1 -1
- package/dist/bin/init.d.ts +23 -0
- package/dist/bin/init.js +123 -0
- package/dist/bin/init.js.map +1 -0
- package/dist/bin/reset.d.ts +39 -0
- package/dist/bin/reset.js +101 -0
- package/dist/bin/reset.js.map +1 -0
- package/dist/bin/uninstall.d.ts +20 -0
- package/dist/bin/uninstall.js +141 -0
- package/dist/bin/uninstall.js.map +1 -0
- package/dist/extensions/forgecli/claude-bootstrap/bootstrap.d.ts +40 -0
- package/dist/extensions/forgecli/claude-bootstrap/bootstrap.js +384 -0
- package/dist/extensions/forgecli/claude-bootstrap/bootstrap.js.map +1 -0
- package/dist/extensions/forgecli/claude-bootstrap/settings-merge.d.ts +46 -0
- package/dist/extensions/forgecli/claude-bootstrap/settings-merge.js +245 -0
- package/dist/extensions/forgecli/claude-bootstrap/settings-merge.js.map +1 -0
- package/dist/extensions/forgecli/claude-bootstrap/uninstall.d.ts +23 -0
- package/dist/extensions/forgecli/claude-bootstrap/uninstall.js +235 -0
- package/dist/extensions/forgecli/claude-bootstrap/uninstall.js.map +1 -0
- package/dist/extensions/forgecli/commands/reset.d.ts +16 -0
- package/dist/extensions/forgecli/commands/reset.js +83 -0
- package/dist/extensions/forgecli/commands/reset.js.map +1 -0
- package/dist/extensions/forgecli/dashboard/component.js +10 -7
- package/dist/extensions/forgecli/dashboard/component.js.map +1 -1
- package/dist/extensions/forgecli/forge-commands.d.ts +7 -2
- package/dist/extensions/forgecli/forge-commands.js +19 -5
- package/dist/extensions/forgecli/forge-commands.js.map +1 -1
- package/dist/extensions/forgecli/forge-subagent.d.ts +4 -4
- package/dist/extensions/forgecli/hooks/forge-permissions.js +20 -6
- package/dist/extensions/forgecli/hooks/forge-permissions.js.map +1 -1
- package/dist/extensions/forgecli/index.js +6 -3
- package/dist/extensions/forgecli/index.js.map +1 -1
- package/dist/extensions/forgecli/lib/forge-root.d.ts +6 -0
- package/dist/extensions/forgecli/lib/forge-root.js +52 -0
- package/dist/extensions/forgecli/lib/forge-root.js.map +1 -1
- package/dist/extensions/forgecli/lib/payload-manifest.d.ts +62 -0
- package/dist/extensions/forgecli/lib/payload-manifest.js +151 -0
- package/dist/extensions/forgecli/lib/payload-manifest.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/advisory-render.d.ts +9 -0
- package/dist/extensions/forgecli/orchestrators/advisory-render.js +107 -0
- package/dist/extensions/forgecli/orchestrators/advisory-render.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-body.d.ts +1 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-body.js +65 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-body.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-id.d.ts +23 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-id.js +140 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-id.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-phase-dispatch.d.ts +54 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-phase-dispatch.js +349 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-phase-dispatch.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-phases.d.ts +11 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-phases.js +82 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-phases.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-state.d.ts +14 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-state.js +100 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-state.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-triage-routing.d.ts +72 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-triage-routing.js +204 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-triage-routing.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-verdict-loop.d.ts +38 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-verdict-loop.js +198 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-verdict-loop.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-verdict.d.ts +3 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-verdict.js +55 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-verdict.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/bug/run-bug-command.d.ts +7 -0
- package/dist/extensions/forgecli/orchestrators/bug/run-bug-command.js +293 -0
- package/dist/extensions/forgecli/orchestrators/bug/run-bug-command.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/bug/run-bug-pipeline.d.ts +2 -0
- package/dist/extensions/forgecli/orchestrators/bug/run-bug-pipeline.js +501 -0
- package/dist/extensions/forgecli/orchestrators/bug/run-bug-pipeline.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/bug/run-bug-types.d.ts +41 -0
- package/dist/extensions/forgecli/orchestrators/bug/run-bug-types.js +5 -0
- package/dist/extensions/forgecli/orchestrators/bug/run-bug-types.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/common/orchestrator-entry.d.ts +43 -0
- package/dist/extensions/forgecli/orchestrators/common/orchestrator-entry.js +85 -0
- package/dist/extensions/forgecli/orchestrators/common/orchestrator-entry.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/common/orchestrator-misc.d.ts +8 -0
- package/dist/extensions/forgecli/orchestrators/common/orchestrator-misc.js +37 -0
- package/dist/extensions/forgecli/orchestrators/common/orchestrator-misc.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/common/orchestrator-notify.d.ts +28 -0
- package/dist/extensions/forgecli/orchestrators/common/orchestrator-notify.js +45 -0
- package/dist/extensions/forgecli/orchestrators/common/orchestrator-notify.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/common/orchestrator-transcript-session.d.ts +26 -0
- package/dist/extensions/forgecli/orchestrators/common/orchestrator-transcript-session.js +75 -0
- package/dist/extensions/forgecli/orchestrators/common/orchestrator-transcript-session.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/common/recovery-menu.d.ts +24 -0
- package/dist/extensions/forgecli/orchestrators/common/recovery-menu.js +58 -0
- package/dist/extensions/forgecli/orchestrators/common/recovery-menu.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/common/reset-pipeline.d.ts +53 -0
- package/dist/extensions/forgecli/orchestrators/common/reset-pipeline.js +131 -0
- package/dist/extensions/forgecli/orchestrators/common/reset-pipeline.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/common/summary-recovery.d.ts +24 -0
- package/dist/extensions/forgecli/orchestrators/common/summary-recovery.js +37 -0
- package/dist/extensions/forgecli/orchestrators/common/summary-recovery.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/fix-bug.d.ts +9 -93
- package/dist/extensions/forgecli/orchestrators/fix-bug.js +23 -1721
- package/dist/extensions/forgecli/orchestrators/fix-bug.js.map +1 -1
- package/dist/extensions/forgecli/orchestrators/halt-advisor.js +25 -3
- package/dist/extensions/forgecli/orchestrators/halt-advisor.js.map +1 -1
- package/dist/extensions/forgecli/orchestrators/run-sprint.d.ts +3 -12
- package/dist/extensions/forgecli/orchestrators/run-sprint.js +48 -270
- package/dist/extensions/forgecli/orchestrators/run-sprint.js.map +1 -1
- package/dist/extensions/forgecli/orchestrators/run-task.d.ts +10 -214
- package/dist/extensions/forgecli/orchestrators/run-task.js +31 -1481
- package/dist/extensions/forgecli/orchestrators/run-task.js.map +1 -1
- package/dist/extensions/forgecli/orchestrators/sprint/sprint-ceremony.d.ts +33 -0
- package/dist/extensions/forgecli/orchestrators/sprint/sprint-ceremony.js +135 -0
- package/dist/extensions/forgecli/orchestrators/sprint/sprint-ceremony.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/sprint/sprint-state.d.ts +18 -0
- package/dist/extensions/forgecli/orchestrators/sprint/sprint-state.js +55 -0
- package/dist/extensions/forgecli/orchestrators/sprint/sprint-state.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/task/run-task-command.d.ts +9 -0
- package/dist/extensions/forgecli/orchestrators/task/run-task-command.js +174 -0
- package/dist/extensions/forgecli/orchestrators/task/run-task-command.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/task/run-task-pipeline.d.ts +2 -0
- package/dist/extensions/forgecli/orchestrators/task/run-task-pipeline.js +494 -0
- package/dist/extensions/forgecli/orchestrators/task/run-task-pipeline.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/task/run-task-types.d.ts +62 -0
- package/dist/extensions/forgecli/orchestrators/task/run-task-types.js +5 -0
- package/dist/extensions/forgecli/orchestrators/task/run-task-types.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/task/task-body.d.ts +4 -0
- package/dist/extensions/forgecli/orchestrators/task/task-body.js +48 -0
- package/dist/extensions/forgecli/orchestrators/task/task-body.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/task/task-events.d.ts +63 -0
- package/dist/extensions/forgecli/orchestrators/task/task-events.js +185 -0
- package/dist/extensions/forgecli/orchestrators/task/task-events.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/task/task-gates.d.ts +34 -0
- package/dist/extensions/forgecli/orchestrators/task/task-gates.js +78 -0
- package/dist/extensions/forgecli/orchestrators/task/task-gates.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/task/task-phase-dispatch.d.ts +42 -0
- package/dist/extensions/forgecli/orchestrators/task/task-phase-dispatch.js +370 -0
- package/dist/extensions/forgecli/orchestrators/task/task-phase-dispatch.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/task/task-phases.d.ts +17 -0
- package/dist/extensions/forgecli/orchestrators/task/task-phases.js +48 -0
- package/dist/extensions/forgecli/orchestrators/task/task-phases.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/task/task-record.d.ts +9 -0
- package/dist/extensions/forgecli/orchestrators/task/task-record.js +58 -0
- package/dist/extensions/forgecli/orchestrators/task/task-record.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/task/task-state.d.ts +14 -0
- package/dist/extensions/forgecli/orchestrators/task/task-state.js +35 -0
- package/dist/extensions/forgecli/orchestrators/task/task-state.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/task/task-verdict-loop.d.ts +36 -0
- package/dist/extensions/forgecli/orchestrators/task/task-verdict-loop.js +187 -0
- package/dist/extensions/forgecli/orchestrators/task/task-verdict-loop.js.map +1 -0
- package/dist/extensions/forgecli/store/store-resolver.d.ts +15 -0
- package/dist/extensions/forgecli/store/store-resolver.js +118 -18
- package/dist/extensions/forgecli/store/store-resolver.js.map +1 -1
- package/dist/extensions/forgecli/update/forge-update-command.js +10 -7
- package/dist/extensions/forgecli/update/forge-update-command.js.map +1 -1
- package/dist/forge-payload/.base-pack/workflows/collator_agent.md +5 -6
- package/dist/forge-payload/.base-pack/workflows/migrate_structural.md +1 -1
- package/dist/forge-payload/.base-pack/workflows-js/wfl-init.js +449 -0
- package/dist/forge-payload/.claude-plugin/plugin.json +1 -1
- package/dist/forge-payload/.schemas/enum-catalog.json +2 -2
- package/dist/forge-payload/.schemas/migrations.json +170 -0
- package/dist/forge-payload/.schemas/payload-manifest.schema.json +100 -0
- package/dist/forge-payload/commands/add-pipeline.md +1 -1
- package/dist/forge-payload/commands/add-task.md +3 -3
- package/dist/forge-payload/{.base-pack/commands → commands}/approve.md +2 -2
- package/dist/forge-payload/commands/ask.md +1 -1
- package/dist/forge-payload/commands/check-agent.md +8 -24
- package/dist/forge-payload/{.base-pack/commands → commands}/collate.md +2 -2
- package/dist/forge-payload/{.base-pack/commands → commands}/commit.md +2 -2
- package/dist/forge-payload/commands/config.md +1 -1
- package/dist/forge-payload/commands/enhance.md +31 -5
- package/dist/forge-payload/{.base-pack/commands → commands}/fix-bug.md +2 -2
- package/dist/forge-payload/commands/health.md +1 -1
- package/dist/forge-payload/{.base-pack/commands → commands}/implement.md +2 -2
- package/dist/forge-payload/commands/init.md +186 -67
- package/dist/forge-payload/{.base-pack/commands → commands}/new-sprint.md +2 -2
- package/dist/forge-payload/{.base-pack/commands → commands}/plan-sprint.md +2 -2
- package/dist/forge-payload/{.base-pack/commands → commands}/plan.md +2 -2
- package/dist/forge-payload/commands/rebuild.md +3 -3
- package/dist/forge-payload/commands/remove.md +1 -1
- package/dist/forge-payload/commands/repair.md +1 -1
- package/dist/forge-payload/commands/report-bug.md +1 -1
- package/dist/forge-payload/commands/reset.md +117 -0
- package/dist/forge-payload/{.base-pack/commands → commands}/retro.md +2 -2
- package/dist/forge-payload/{.base-pack/commands → commands}/review-code.md +2 -2
- package/dist/forge-payload/{.base-pack/commands → commands}/review-plan.md +2 -2
- package/dist/forge-payload/{.base-pack/commands → commands}/run-sprint.md +2 -2
- package/dist/forge-payload/{.base-pack/commands → commands}/run-task.md +2 -2
- package/dist/forge-payload/commands/status.md +1 -1
- package/dist/forge-payload/commands/update.md +3 -3
- package/dist/forge-payload/{.base-pack/commands → commands}/validate.md +2 -2
- package/dist/forge-payload/hooks/forge-permissions.cjs +29 -6
- package/dist/forge-payload/hooks/lib/common.cjs +228 -0
- package/dist/forge-payload/hooks/lib/plugin-detection.cjs +106 -0
- package/dist/forge-payload/hooks/lib/update-msg.cjs +23 -0
- package/dist/forge-payload/hooks/lib/update-url.cjs +46 -0
- package/dist/forge-payload/hooks/lib/write-registry.js +53 -0
- package/dist/forge-payload/init/discovery/discover-database.md +32 -0
- package/dist/forge-payload/init/discovery/discover-processes.md +31 -0
- package/dist/forge-payload/init/discovery/discover-routing.md +31 -0
- package/dist/forge-payload/init/discovery/discover-stack.md +33 -0
- package/dist/forge-payload/init/discovery/discover-testing.md +34 -0
- package/dist/forge-payload/init/generation/generate-commands.md +171 -0
- package/dist/forge-payload/init/generation/generate-kb-doc.md +60 -0
- package/dist/forge-payload/init/generation/generate-persona.md +73 -0
- package/dist/forge-payload/init/generation/generate-skill.md +66 -0
- package/dist/forge-payload/init/generation/generate-template.md +60 -0
- package/dist/forge-payload/init/generation/generate-tools.md +133 -0
- package/dist/forge-payload/init/generation/generate-workflows.md +78 -0
- package/dist/forge-payload/init/phases/phase-1-collect.md +10 -2
- package/dist/forge-payload/init/phases/phase-3-materialize.md +5 -1
- package/dist/forge-payload/init/phases/phase-4-register.md +8 -0
- package/dist/forge-payload/init/workflow-gen-plan.json +17 -0
- package/dist/forge-payload/integrity.json +33 -18
- package/dist/forge-payload/meta/workflows/meta-collate.md +5 -6
- package/dist/forge-payload/meta/workflows/meta-migrate.md +1 -1
- package/dist/forge-payload/payload-manifest.json +314 -0
- package/dist/forge-payload/schemas/enum-catalog.json +2 -2
- package/dist/forge-payload/schemas/payload-manifest.schema.json +100 -0
- package/dist/forge-payload/schemas/structure-manifest.json +5 -12
- package/dist/forge-payload/tools/forge-preflight.cjs +268 -0
- package/dist/forge-payload/tools/lib/paths.cjs +12 -11
- package/dist/forge-payload/tools/lib/pricing.cjs +31 -11
- package/dist/forge-payload/tools/query-logger.cjs +34 -0
- package/dist/forge-payload/tools/reset-plan.cjs +210 -0
- package/dist/forge-payload/tools/store.cjs +4 -1
- package/dist/forge-payload/tools/substitute-placeholders.cjs +14 -7
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +8 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.d.ts +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/execution-env.d.ts +4 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/execution-env.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/execution-env.js +3 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/execution-env.js.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo/jsonl.d.ts +20 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo/jsonl.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo/jsonl.js +92 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo/jsonl.js.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo/memory.d.ts +18 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo/memory.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo/memory.js +42 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo/memory.js.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo/shared.d.ts +10 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo/shared.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo/shared.js +31 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo/shared.js.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/storage/jsonl.d.ts +30 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/storage/jsonl.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/storage/jsonl.js +170 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/storage/jsonl.js.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/storage/memory.d.ts +26 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/storage/memory.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/storage/memory.js +90 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/storage/memory.js.map +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts +6 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
- package/node_modules/@earendil-works/pi-ai/README.md +12 -4
- package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js +3 -0
- package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts +45 -0
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +45 -0
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +1804 -815
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +2031 -1384
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js +71 -27
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.d.ts +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js +24 -16
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js +3 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js +35 -13
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses-shared.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses-shared.js +2 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses-shared.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +12 -4
- package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js +13 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.js +4 -2
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +3 -2
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/CHANGELOG.md +143 -0
- package/node_modules/@earendil-works/pi-coding-agent/README.md +26 -4
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.d.ts +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.js +11 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/project-trust.d.ts +10 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/project-trust.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/project-trust.js +48 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/project-trust.js.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/startup-ui.d.ts +17 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/startup-ui.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/startup-ui.js +128 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/startup-ui.js.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/config.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/config.js +9 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/config.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-runtime.d.ts +3 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-runtime.js +4 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-runtime.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-services.d.ts +2 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-services.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-services.js +2 -2
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-services.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.d.ts +4 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js +16 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/auth-storage.js +4 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/branch-summarization.d.ts +3 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/branch-summarization.js +9 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/branch-summarization.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/utils.d.ts +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/utils.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/utils.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/utils.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/experimental.d.ts +2 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/experimental.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/experimental.js +4 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/experimental.js.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/export-html/template.js +19 -6
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/index.d.ts +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.d.ts +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.js +4 -4
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/runner.d.ts +10 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/runner.js +47 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.d.ts +28 -2
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/footer-data-provider.d.ts +2 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/footer-data-provider.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/footer-data-provider.js +29 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/footer-data-provider.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/index.d.ts +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/index.js +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-registry.js +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.d.ts +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.js +44 -5
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.d.ts +3 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.js +47 -13
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/project-trust.d.ts +15 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/project-trust.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/project-trust.js +58 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/project-trust.js.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/prompt-templates.d.ts +2 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/prompt-templates.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/prompt-templates.js +24 -26
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/prompt-templates.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/provider-attribution.d.ts +4 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/provider-attribution.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/provider-attribution.js +72 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/provider-attribution.js.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/provider-display-names.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/provider-display-names.js +3 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/provider-display-names.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/resource-loader.d.ts +13 -2
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/resource-loader.js +112 -37
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/sdk.js +7 -33
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.js +103 -70
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.d.ts +20 -2
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js +97 -30
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/slash-commands.js +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/find.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/find.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/find.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/grep.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/grep.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/grep.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/trust-manager.d.ts +36 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/trust-manager.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/trust-manager.js +202 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/trust-manager.js.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/index.d.ts +5 -4
- package/node_modules/@earendil-works/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/index.js +2 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/main.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/main.js +72 -32
- package/node_modules/@earendil-works/pi-coding-agent/dist/main.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/migrations.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/migrations.js +39 -34
- package/node_modules/@earendil-works/pi-coding-agent/dist/migrations.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/index.d.ts +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +2 -2
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/first-time-setup.d.ts +25 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/first-time-setup.js +103 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/first-time-setup.js.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js +7 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/index.d.ts +2 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/index.js +2 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +10 -13
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +3 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +20 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +22 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/trust-selector.d.ts +23 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/trust-selector.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/trust-selector.js +91 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/trust-selector.js.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +7 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js +101 -5
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/print-mode.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/print-mode.js +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/print-mode.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.js +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.d.ts +6 -2
- package/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.js +111 -10
- package/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/changelog.d.ts +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/changelog.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/changelog.js +78 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/changelog.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/git.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/git.js +54 -22
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/git.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/open-browser.d.ts +9 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/open-browser.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/open-browser.js +22 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/open-browser.js.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/docs/containerization.md +111 -0
- package/node_modules/@earendil-works/pi-coding-agent/docs/docs.json +8 -0
- package/node_modules/@earendil-works/pi-coding-agent/docs/extensions.md +67 -13
- package/node_modules/@earendil-works/pi-coding-agent/docs/index.md +2 -0
- package/node_modules/@earendil-works/pi-coding-agent/docs/models.md +4 -3
- package/node_modules/@earendil-works/pi-coding-agent/docs/packages.md +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/docs/prompt-templates.md +9 -2
- package/node_modules/@earendil-works/pi-coding-agent/docs/providers.md +5 -0
- package/node_modules/@earendil-works/pi-coding-agent/docs/rpc.md +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/docs/sdk.md +5 -0
- package/node_modules/@earendil-works/pi-coding-agent/docs/security.md +59 -0
- package/node_modules/@earendil-works/pi-coding-agent/docs/settings.md +15 -0
- package/node_modules/@earendil-works/pi-coding-agent/docs/skills.md +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/docs/terminal-setup.md +36 -2
- package/node_modules/@earendil-works/pi-coding-agent/docs/themes.md +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/docs/tmux.md +4 -2
- package/node_modules/@earendil-works/pi-coding-agent/docs/tui.md +10 -1
- package/node_modules/@earendil-works/pi-coding-agent/docs/usage.md +19 -2
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/README.md +2 -0
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/custom-header.ts +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/doom-overlay/index.ts +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/gondolin/index.ts +531 -0
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/gondolin/package-lock.json +185 -0
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/gondolin/package.json +19 -0
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/handoff.ts +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/interactive-shell.ts +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/overlay-qa-tests.ts +152 -81
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/project-trust.ts +64 -0
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/qna.ts +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/question.ts +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/questionnaire.ts +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/sandbox/package.json +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/snake.ts +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/space-invaders.ts +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/summarize.ts +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/tic-tac-toe.ts +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/todo.ts +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/tools.ts +5 -0
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/with-deps/package.json +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/npm-shrinkwrap.json +12 -419
- package/node_modules/@earendil-works/pi-coding-agent/package.json +5 -8
- package/node_modules/@earendil-works/pi-tui/README.md +13 -1
- package/node_modules/@earendil-works/pi-tui/dist/autocomplete.d.ts +2 -0
- package/node_modules/@earendil-works/pi-tui/dist/autocomplete.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/autocomplete.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts +6 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +102 -43
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/markdown.d.ts +2 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/markdown.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js +11 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/fuzzy.d.ts +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/fuzzy.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/fuzzy.js +2 -2
- package/node_modules/@earendil-works/pi-tui/dist/fuzzy.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/index.d.ts +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts +4 -7
- package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js +38 -77
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts +20 -4
- package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui.js +244 -42
- package/node_modules/@earendil-works/pi-tui/dist/tui.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/utils.d.ts +1 -0
- package/node_modules/@earendil-works/pi-tui/dist/utils.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/utils.js +46 -15
- package/node_modules/@earendil-works/pi-tui/dist/utils.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +8 -7
- package/dist/forge-payload/.base-pack/commands/check-agent.md +0 -22
- package/dist/forge-payload/.base-pack/commands/enhance.md +0 -37
|
@@ -1,1491 +1,41 @@
|
|
|
1
1
|
// run-task.ts — /forge:run-task native Orchestrator handler (FORGE-S21-T02).
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// BARREL (FORGE-S31 file-size refactor). The implementation was decomposed into
|
|
4
|
+
// themed modules under ./task/ and ./common/ to satisfy the per-file
|
|
5
|
+
// architectural line cap. This file re-exports the full public surface so
|
|
6
|
+
// existing importers (run-sprint.ts, fix-bug.ts, index.ts, bin/config.ts, the
|
|
7
|
+
// test suite, calibrate.ts, wf-engine/engine.ts) keep resolving unchanged.
|
|
7
8
|
//
|
|
8
|
-
// Iron Laws enforced
|
|
9
|
+
// Iron Laws (enforced in the extracted modules):
|
|
9
10
|
// IL1 — code only under forge-cli/src/extensions/forgecli/
|
|
10
11
|
// IL6 — no shell-string interpolation; all external calls via spawnSync argv arrays
|
|
11
12
|
// IL7 — every failure path emits ctx.ui.notify and returns; no silent continuation
|
|
12
13
|
// IL10 — ALL LLM dispatch goes through runForgeSubagent (NO sendKickoff calls here)
|
|
13
14
|
//
|
|
14
|
-
// sendKickoff is NEVER called from this file.
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
import { attachViewportObserver } from "../viewport/events.js";
|
|
40
|
-
import { fmtPhaseSummary } from "../viewport/renderer.js";
|
|
41
|
-
// ── Non-interactive helpers ───────────────────────────────────────────────
|
|
42
|
-
export function isNonInteractive() {
|
|
43
|
-
return process.env.FORGE_YES === "1" || process.env.FORGE_NON_INTERACTIVE === "1";
|
|
44
|
-
}
|
|
45
|
-
export const PHASES = [
|
|
46
|
-
{ role: "plan", workflowFile: "plan_task", personaNoun: "engineer", isReview: false, maxIterations: 1 },
|
|
47
|
-
{ role: "review-plan", workflowFile: "review_plan", personaNoun: "supervisor", isReview: true, maxIterations: 3 },
|
|
48
|
-
{ role: "implement", workflowFile: "implement_plan", personaNoun: "engineer", isReview: false, maxIterations: 1 },
|
|
49
|
-
{ role: "review-code", workflowFile: "review_code", personaNoun: "supervisor", isReview: true, maxIterations: 3 },
|
|
50
|
-
{ role: "validate", workflowFile: "validate_task", personaNoun: "qa-engineer", isReview: true, maxIterations: 3 },
|
|
51
|
-
{ role: "approve", workflowFile: "architect_approve", personaNoun: "architect", isReview: true, maxIterations: 3 },
|
|
52
|
-
{ role: "writeback", workflowFile: "collator_agent", personaNoun: "collator", isReview: false, maxIterations: 1 },
|
|
53
|
-
{ role: "commit", workflowFile: "commit_task", personaNoun: "engineer", isReview: false, maxIterations: 1 },
|
|
54
|
-
];
|
|
55
|
-
// Map phase.role → canonical summary key written by base-pack workflows
|
|
56
|
-
// (see forge/forge/tools/store-cli.cjs VALID_SUMMARY_PHASES). Phases whose
|
|
57
|
-
// workflows do not write a summaries entry (e.g. approve, which transitions
|
|
58
|
-
// task.status=approved instead) map to null and are verdict-checked via
|
|
59
|
-
// task status rather than the summaries map.
|
|
60
|
-
export const SUMMARY_KEY_BY_ROLE = {
|
|
61
|
-
plan: "plan",
|
|
62
|
-
"review-plan": "review_plan",
|
|
63
|
-
implement: "implementation",
|
|
64
|
-
"review-code": "code_review",
|
|
65
|
-
validate: "validation",
|
|
66
|
-
approve: null,
|
|
67
|
-
};
|
|
68
|
-
/** Validate that an ID contains no path-traversal characters. */
|
|
69
|
-
export function validateId(id) {
|
|
70
|
-
return /^[A-Za-z0-9_-]+$/.test(id) && !id.includes("..");
|
|
71
|
-
}
|
|
72
|
-
// FORGE-S25-T16 (N-H-B): state helpers delegate to lib/state-helpers.ts.
|
|
73
|
-
// Public API (readState, writeState, deleteState, isStateStale) is preserved —
|
|
74
|
-
// run-sprint.ts imports readState as readTaskState from this file.
|
|
75
|
-
function stateFilePath(cwd, taskId) {
|
|
76
|
-
if (!validateId(taskId)) {
|
|
77
|
-
throw new Error(`Invalid taskId for state file path: ${taskId}`);
|
|
78
|
-
}
|
|
79
|
-
return taskStateFilePath(cwd, taskId);
|
|
80
|
-
}
|
|
81
|
-
export function readState(cwd, taskId) {
|
|
82
|
-
return readJsonState(stateFilePath(cwd, taskId));
|
|
83
|
-
}
|
|
84
|
-
export function writeState(cwd, state) {
|
|
85
|
-
writeJsonState(stateFilePath(cwd, state.taskId), state);
|
|
86
|
-
}
|
|
87
|
-
export function deleteState(cwd, taskId) {
|
|
88
|
-
const fp = stateFilePath(cwd, taskId);
|
|
89
|
-
try {
|
|
90
|
-
if (fs.existsSync(fp))
|
|
91
|
-
fs.unlinkSync(fp);
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
// non-fatal
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
export function isStateStale(state) {
|
|
98
|
-
return isJsonStateStale(state.savedAt);
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Format an ISO timestamp for human display in the user's local timezone.
|
|
102
|
-
* Falls back to the raw ISO string if parsing fails.
|
|
103
|
-
*/
|
|
104
|
-
export function formatLocalTime(iso) {
|
|
105
|
-
const d = new Date(iso);
|
|
106
|
-
if (Number.isNaN(d.getTime()))
|
|
107
|
-
return iso;
|
|
108
|
-
const date = d.toLocaleString(undefined, {
|
|
109
|
-
year: "numeric",
|
|
110
|
-
month: "short",
|
|
111
|
-
day: "2-digit",
|
|
112
|
-
hour: "2-digit",
|
|
113
|
-
minute: "2-digit",
|
|
114
|
-
second: "2-digit",
|
|
115
|
-
hour12: false,
|
|
116
|
-
});
|
|
117
|
-
// Append short timezone abbreviation for unambiguous reading.
|
|
118
|
-
const tz = new Intl.DateTimeFormat(undefined, { timeZoneName: "short" })
|
|
119
|
-
.formatToParts(d)
|
|
120
|
-
.find((p) => p.type === "timeZoneName")?.value ?? "";
|
|
121
|
-
return tz ? `${date} ${tz}` : date;
|
|
122
|
-
}
|
|
123
|
-
export function readVerdict(taskId, phaseRole, storeCli, cwd) {
|
|
124
|
-
const result = spawnSync("node", [storeCli, "read", "task", taskId], { cwd, encoding: "utf8" });
|
|
125
|
-
if (result.status !== 0)
|
|
126
|
-
return "missing";
|
|
127
|
-
try {
|
|
128
|
-
const raw = typeof result.stdout === "string" ? result.stdout : String(result.stdout);
|
|
129
|
-
const record = JSON.parse(raw);
|
|
130
|
-
// Phases like `approve` do not write a summaries entry; they
|
|
131
|
-
// transition task.status to "approved" instead. For those, the
|
|
132
|
-
// verdict source is task.status.
|
|
133
|
-
const summaryKey = SUMMARY_KEY_BY_ROLE[phaseRole];
|
|
134
|
-
if (summaryKey === null) {
|
|
135
|
-
return record.status === "approved" ? "approved" : "missing";
|
|
136
|
-
}
|
|
137
|
-
// Verdict lookup with three fallbacks:
|
|
138
|
-
// 1. Canonical mapped summary key (e.g. "code_review" for review-code).
|
|
139
|
-
// 2. Underscore-swapped phase role ("review_code") — legacy/defensive.
|
|
140
|
-
// 3. Raw hyphenated phase role ("review-code") — defensive only.
|
|
141
|
-
const summaries = record.summaries ?? {};
|
|
142
|
-
const underscoreKey = phaseRole.replace(/-/g, "_");
|
|
143
|
-
const candidates = [summaryKey ?? "", underscoreKey, phaseRole].filter(Boolean);
|
|
144
|
-
let verdict;
|
|
145
|
-
for (const k of candidates) {
|
|
146
|
-
if (summaries[k]?.verdict) {
|
|
147
|
-
verdict = summaries[k].verdict;
|
|
148
|
-
break;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
if (!verdict)
|
|
152
|
-
return "missing";
|
|
153
|
-
if (verdict === "approved")
|
|
154
|
-
return "approved";
|
|
155
|
-
if (verdict === "revision")
|
|
156
|
-
return "revision";
|
|
157
|
-
return "missing";
|
|
158
|
-
}
|
|
159
|
-
catch {
|
|
160
|
-
return "missing";
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
export function readTaskRecord(taskId, storeCli, cwd) {
|
|
164
|
-
const result = spawnSync("node", [storeCli, "read", "task", taskId], { cwd, encoding: "utf8" });
|
|
165
|
-
if (result.status !== 0)
|
|
166
|
-
return null;
|
|
167
|
-
try {
|
|
168
|
-
const raw = typeof result.stdout === "string" ? result.stdout : String(result.stdout);
|
|
169
|
-
return JSON.parse(raw);
|
|
170
|
-
}
|
|
171
|
-
catch {
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
// Map phase.role → action token used in event.action / eventId.
|
|
176
|
-
export function actionForRole(role) {
|
|
177
|
-
return role.replace(/-/g, "_");
|
|
178
|
-
}
|
|
179
|
-
export function isoCompact(ms) {
|
|
180
|
-
return new Date(ms)
|
|
181
|
-
.toISOString()
|
|
182
|
-
.replace(/[-:]/g, "")
|
|
183
|
-
.replace(/\.\d{3}Z$/, "Z");
|
|
184
|
-
}
|
|
185
|
-
export function buildPhaseEvent(ec) {
|
|
186
|
-
const action = actionForRole(ec.phase.role);
|
|
187
|
-
const entityId = ec.entityType === "bug" ? ec.bugId : ec.taskId;
|
|
188
|
-
const eventId = `${isoCompact(ec.startMs)}_${entityId}_${ec.phase.personaNoun}_${action}`;
|
|
189
|
-
const durationMs = Math.max(0, ec.endMs - ec.startMs);
|
|
190
|
-
const event = {
|
|
191
|
-
eventId,
|
|
192
|
-
sprintId: ec.sprintId,
|
|
193
|
-
role: ec.phase.role,
|
|
194
|
-
action: `/forge:${action.replace(/_/g, "-")}`,
|
|
195
|
-
phase: ec.phase.role,
|
|
196
|
-
iteration: ec.iteration,
|
|
197
|
-
startTimestamp: new Date(ec.startMs).toISOString(),
|
|
198
|
-
endTimestamp: new Date(ec.endMs).toISOString(),
|
|
199
|
-
durationMinutes: Math.round((durationMs / 60000) * 100) / 100,
|
|
200
|
-
model: ec.model,
|
|
201
|
-
provider: ec.provider,
|
|
202
|
-
};
|
|
203
|
-
if (ec.entityType === "bug") {
|
|
204
|
-
event.bugId = ec.bugId;
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
event.taskId = ec.taskId;
|
|
208
|
-
}
|
|
209
|
-
if (ec.usage.input > 0 || ec.usage.output > 0 || ec.usage.cacheRead > 0 || ec.usage.cacheWrite > 0) {
|
|
210
|
-
event.inputTokens = ec.usage.input;
|
|
211
|
-
event.outputTokens = ec.usage.output;
|
|
212
|
-
event.cacheReadTokens = ec.usage.cacheRead;
|
|
213
|
-
event.cacheWriteTokens = ec.usage.cacheWrite;
|
|
214
|
-
event.tokenSource = "reported";
|
|
215
|
-
}
|
|
216
|
-
if (ec.judgement && typeof ec.judgement === "object") {
|
|
217
|
-
const j = ec.judgement;
|
|
218
|
-
if (typeof j.verdict === "string")
|
|
219
|
-
event.verdict = j.verdict;
|
|
220
|
-
if (typeof j.notes === "string")
|
|
221
|
-
event.notes = j.notes;
|
|
222
|
-
}
|
|
223
|
-
return event;
|
|
224
|
-
}
|
|
225
|
-
export function emitEvent(storeCli, cwd, sprintId, event) {
|
|
226
|
-
const result = spawnSync("node", [storeCli, "emit", sprintId, JSON.stringify(event)], {
|
|
227
|
-
cwd,
|
|
228
|
-
encoding: "utf8",
|
|
229
|
-
});
|
|
230
|
-
return { ok: result.status === 0, stderr: typeof result.stderr === "string" ? result.stderr : "" };
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* Emit a phase event for an INCOMPLETE attempt (cancelled / failed) so its
|
|
234
|
-
* provider-billed tokens reach the store. Bug B: the cancel and halt-on-failure
|
|
235
|
-
* branches used to return without emitting, so collate's COST_REPORT
|
|
236
|
-
* under-counted real spend by exactly the aborted passes (CART-S02-T03
|
|
237
|
-
* baseline: 259,950 tokens across two aborted plan attempts, invisible).
|
|
238
|
-
*
|
|
239
|
-
* The event is the canonical phase event (schema-unchanged) with
|
|
240
|
-
* `verdict: "aborted" | "failed"` marking the outcome.
|
|
241
|
-
*
|
|
242
|
-
* Zero-token attempts are skipped — there is no spend to account, and a
|
|
243
|
-
* token-less event would be flagged as a husk by collate's ingestion-quality
|
|
244
|
-
* pass. Never throws: emission must not perturb the cancel/halt return paths.
|
|
245
|
-
*
|
|
246
|
-
* @param opts.decorate Optional event mutation hook applied before emit
|
|
247
|
-
* (fix-bug uses it for the BUG_TYPE_TOKENS `type` field).
|
|
248
|
-
* @returns true when the event was emitted and store-cli accepted it.
|
|
249
|
-
*/
|
|
250
|
-
export function emitIncompletePhaseEvent(opts) {
|
|
251
|
-
try {
|
|
252
|
-
const { emitCtx, outcome } = opts;
|
|
253
|
-
const u = emitCtx.usage;
|
|
254
|
-
if (u.input + u.output + u.cacheRead + u.cacheWrite <= 0) {
|
|
255
|
-
opts.onDebug?.({ kind: "incomplete_emit_skipped", reason: "no-tokens", outcome });
|
|
256
|
-
return false;
|
|
257
|
-
}
|
|
258
|
-
const judgement = { verdict: outcome };
|
|
259
|
-
if (opts.notes)
|
|
260
|
-
judgement.notes = opts.notes;
|
|
261
|
-
const event = buildPhaseEvent({ ...emitCtx, judgement });
|
|
262
|
-
opts.decorate?.(event);
|
|
263
|
-
const res = emitEvent(emitCtx.storeCli, emitCtx.cwd, emitCtx.sprintId, event);
|
|
264
|
-
opts.onDebug?.(res.ok
|
|
265
|
-
? { kind: "incomplete_emit_ok", eventId: event.eventId, outcome }
|
|
266
|
-
: { kind: "incomplete_emit_failed", stderr: res.stderr, outcome });
|
|
267
|
-
return res.ok;
|
|
268
|
-
}
|
|
269
|
-
catch (err) {
|
|
270
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
271
|
-
opts.onDebug?.({ kind: "incomplete_emit_failed", stderr: msg, outcome: opts.outcome });
|
|
272
|
-
return false;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
export function judgementFromSummary(record, phaseRole, summaryKeyByRole) {
|
|
276
|
-
if (!record || !record.summaries)
|
|
277
|
-
return undefined;
|
|
278
|
-
const keyMap = summaryKeyByRole ?? SUMMARY_KEY_BY_ROLE;
|
|
279
|
-
const summaryKey = keyMap[phaseRole];
|
|
280
|
-
if (!summaryKey)
|
|
281
|
-
return undefined;
|
|
282
|
-
const blob = record.summaries[summaryKey];
|
|
283
|
-
return blob && typeof blob === "object" ? blob : undefined;
|
|
284
|
-
}
|
|
285
|
-
// Drain .forge/cache/FRICTION-{phase}.jsonl: stamp each judgement-only record
|
|
286
|
-
// with the subagent's runtime attribution and emit as event type "friction".
|
|
287
|
-
// Truncate only after all emits succeed (Plan-11 open-question A.3).
|
|
288
|
-
export function drainFrictionFile(frictionPath, ec) {
|
|
289
|
-
if (!fs.existsSync(frictionPath))
|
|
290
|
-
return { emitted: 0, failed: 0 };
|
|
291
|
-
const raw = fs.readFileSync(frictionPath, "utf8");
|
|
292
|
-
const lines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
293
|
-
if (lines.length === 0)
|
|
294
|
-
return { emitted: 0, failed: 0 };
|
|
295
|
-
let emitted = 0;
|
|
296
|
-
let failed = 0;
|
|
297
|
-
for (let i = 0; i < lines.length; i++) {
|
|
298
|
-
let judgement;
|
|
299
|
-
try {
|
|
300
|
-
judgement = JSON.parse(lines[i]);
|
|
301
|
-
}
|
|
302
|
-
catch {
|
|
303
|
-
failed++;
|
|
304
|
-
continue;
|
|
305
|
-
}
|
|
306
|
-
const action = actionForRole(ec.phase.role);
|
|
307
|
-
const entityId = ec.entityType === "bug" ? ec.bugId : ec.taskId;
|
|
308
|
-
const eventId = `${isoCompact(ec.startMs)}_${entityId}_${ec.phase.personaNoun}_friction_${i}`;
|
|
309
|
-
const event = {
|
|
310
|
-
eventId,
|
|
311
|
-
sprintId: ec.sprintId,
|
|
312
|
-
role: ec.phase.role,
|
|
313
|
-
action: `/forge:${action.replace(/_/g, "-")}`,
|
|
314
|
-
phase: ec.phase.role,
|
|
315
|
-
iteration: ec.iteration,
|
|
316
|
-
startTimestamp: new Date(ec.startMs).toISOString(),
|
|
317
|
-
endTimestamp: new Date(ec.endMs).toISOString(),
|
|
318
|
-
durationMinutes: Math.round(((ec.endMs - ec.startMs) / 60000) * 100) / 100,
|
|
319
|
-
model: ec.model,
|
|
320
|
-
provider: ec.provider,
|
|
321
|
-
type: "friction",
|
|
322
|
-
workflow: typeof judgement.workflow === "string" ? judgement.workflow : ec.phase.role,
|
|
323
|
-
persona: typeof judgement.persona === "string" ? judgement.persona : ec.phase.personaNoun,
|
|
324
|
-
issue: judgement.issue,
|
|
325
|
-
};
|
|
326
|
-
if (ec.entityType === "bug") {
|
|
327
|
-
event.bugId = ec.bugId;
|
|
328
|
-
}
|
|
329
|
-
else {
|
|
330
|
-
event.taskId = ec.taskId;
|
|
331
|
-
}
|
|
332
|
-
if (judgement.subkind !== undefined)
|
|
333
|
-
event.subkind = judgement.subkind;
|
|
334
|
-
if (judgement.evidence !== undefined)
|
|
335
|
-
event.evidence = judgement.evidence;
|
|
336
|
-
if (judgement.notes !== undefined)
|
|
337
|
-
event.notes = judgement.notes;
|
|
338
|
-
const r = emitEvent(ec.storeCli, ec.cwd, ec.sprintId, event);
|
|
339
|
-
if (r.ok)
|
|
340
|
-
emitted++;
|
|
341
|
-
else
|
|
342
|
-
failed++;
|
|
343
|
-
}
|
|
344
|
-
if (failed === 0) {
|
|
345
|
-
try {
|
|
346
|
-
fs.unlinkSync(frictionPath);
|
|
347
|
-
}
|
|
348
|
-
catch {
|
|
349
|
-
/* non-fatal */
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
return { emitted, failed };
|
|
353
|
-
}
|
|
354
|
-
// ── Find predecessor non-review phase for revision loop ───────────────────
|
|
355
|
-
export function findPredecessorIndex(phases, reviewIndex) {
|
|
356
|
-
for (let i = reviewIndex - 1; i >= 0; i--) {
|
|
357
|
-
if (!phases[i].isReview)
|
|
358
|
-
return i;
|
|
359
|
-
}
|
|
360
|
-
return 0;
|
|
361
|
-
}
|
|
362
|
-
// Phase ordering for summary injection — earlier phases first.
|
|
363
|
-
const PHASE_ORDER = ["plan", "review_plan", "implementation", "code_review", "validation"];
|
|
364
|
-
export function buildSummariesBlock(summaries) {
|
|
365
|
-
if (!summaries)
|
|
366
|
-
return "";
|
|
367
|
-
const lines = [];
|
|
368
|
-
for (const key of PHASE_ORDER) {
|
|
369
|
-
const raw = summaries[key];
|
|
370
|
-
if (!raw || typeof raw !== "object")
|
|
371
|
-
continue;
|
|
372
|
-
const s = raw;
|
|
373
|
-
const parts = [`### ${key}`];
|
|
374
|
-
if (s.objective)
|
|
375
|
-
parts.push(`Objective: ${s.objective}`);
|
|
376
|
-
if (s.verdict)
|
|
377
|
-
parts.push(`Verdict: ${s.verdict}`);
|
|
378
|
-
if (s.key_changes?.length)
|
|
379
|
-
parts.push(`Key changes: ${s.key_changes.join("; ")}`);
|
|
380
|
-
if (s.findings?.length)
|
|
381
|
-
parts.push(`Findings: ${s.findings.join("; ")}`);
|
|
382
|
-
if (s.artifact_ref)
|
|
383
|
-
parts.push(`Full artifact: ${s.artifact_ref}`);
|
|
384
|
-
lines.push(parts.join("\n"));
|
|
385
|
-
}
|
|
386
|
-
if (lines.length === 0)
|
|
387
|
-
return "";
|
|
388
|
-
return ["## Prior phase summaries (carry-forward)", "", ...lines].join("\n");
|
|
389
|
-
}
|
|
390
|
-
export function composeTaskBody(subWorkflowMd, taskId, summariesBlock) {
|
|
391
|
-
const parts = [`Read the workflow below and follow it. Task ID: ${taskId}.`, "", "---", ""];
|
|
392
|
-
if (summariesBlock) {
|
|
393
|
-
parts.push(summariesBlock, "", "---", "");
|
|
394
|
-
}
|
|
395
|
-
parts.push(subWorkflowMd.trim());
|
|
396
|
-
return parts.join("\n");
|
|
397
|
-
}
|
|
398
|
-
export function runPreflightGate(preflightGate, role, taskId, cwd, entityType) {
|
|
399
|
-
const outcome = runPreflightGateWithData(preflightGate, role, taskId, cwd, entityType);
|
|
400
|
-
return outcome.result;
|
|
401
|
-
}
|
|
402
|
-
/**
|
|
403
|
-
* Run postflight-gate.cjs after a phase subagent returns, before FSM advance.
|
|
404
|
-
* Mirrors runPreflightGateWithData — same argv-array discipline, same structured-JSON
|
|
405
|
-
* parsing from stdout on exit 1.
|
|
406
|
-
*
|
|
407
|
-
* Returns:
|
|
408
|
-
* "ok" — gate passed (or no outputs block for this phase); advance may proceed.
|
|
409
|
-
* "unsatisfied" — gate failed; do NOT advance FSM; halt and call runHaltAdvisor.
|
|
410
|
-
* "error" — gate binary missing or parse error; treat as pass-through (additive).
|
|
411
|
-
*/
|
|
412
|
-
export function runPostflightGate(postflightGate, role, taskId, cwd) {
|
|
413
|
-
if (!fs.existsSync(postflightGate)) {
|
|
414
|
-
// postflight-gate.cjs not present in this forgeRoot — pass through (additive).
|
|
415
|
-
return { result: "ok", gateFailure: null };
|
|
416
|
-
}
|
|
417
|
-
const spawnResult = spawnSync("node", [postflightGate, "--phase", role, "--task", taskId], { cwd, encoding: "utf8" });
|
|
418
|
-
if (spawnResult.status === 0)
|
|
419
|
-
return { result: "ok", gateFailure: null };
|
|
420
|
-
if (spawnResult.status === 2)
|
|
421
|
-
return { result: "error", gateFailure: null };
|
|
422
|
-
// Exit 1: parse structured JSON from stdout
|
|
423
|
-
let gateFailure = null;
|
|
424
|
-
try {
|
|
425
|
-
const stdout = typeof spawnResult.stdout === "string" ? spawnResult.stdout.trim() : "";
|
|
426
|
-
if (stdout) {
|
|
427
|
-
const parsed = JSON.parse(stdout);
|
|
428
|
-
if (parsed && typeof parsed.reasonCode === "string") {
|
|
429
|
-
gateFailure = parsed;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
catch {
|
|
434
|
-
// stdout not valid JSON — gate failure but no structured data
|
|
435
|
-
}
|
|
436
|
-
return { result: "unsatisfied", gateFailure };
|
|
437
|
-
}
|
|
438
|
-
/**
|
|
439
|
-
* Upgraded variant that returns structured failure data alongside the status enum.
|
|
440
|
-
* Callers that need the advisory data should use this function directly.
|
|
441
|
-
*/
|
|
442
|
-
export function runPreflightGateWithData(preflightGate, role, taskId, cwd, entityType) {
|
|
443
|
-
const entityFlag = entityType === "bug" ? "--bug" : "--task";
|
|
444
|
-
const spawnResult = spawnSync("node", [preflightGate, "--phase", role, entityFlag, taskId], { cwd, encoding: "utf8" });
|
|
445
|
-
if (spawnResult.status === 0)
|
|
446
|
-
return { result: "proceed", gateFailure: null };
|
|
447
|
-
if (spawnResult.status === 2)
|
|
448
|
-
return { result: "escalate", gateFailure: null };
|
|
449
|
-
// Exit 1: parse structured JSON from stdout
|
|
450
|
-
let gateFailure = null;
|
|
451
|
-
try {
|
|
452
|
-
const stdout = typeof spawnResult.stdout === "string" ? spawnResult.stdout.trim() : "";
|
|
453
|
-
if (stdout) {
|
|
454
|
-
const parsed = JSON.parse(stdout);
|
|
455
|
-
if (parsed && typeof parsed.reasonCode === "string") {
|
|
456
|
-
gateFailure = parsed;
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
catch {
|
|
461
|
-
// stdout not valid JSON — gate failure but no structured data
|
|
462
|
-
}
|
|
463
|
-
return { result: "halt", gateFailure };
|
|
464
|
-
}
|
|
465
|
-
// ── Per-task orchestrator pipeline (FORGE-S21-T03 extracted) ──────────────
|
|
466
|
-
// The entire phase loop was inline in registerRunTask. It is now a standalone
|
|
467
|
-
// exported function so that run-sprint.ts can delegate per-task execution
|
|
468
|
-
// without duplicating phase-loop logic. registerRunTask becomes a thin
|
|
469
|
-
// wrapper: config discovery, resume detection, then delegate to runTaskPipeline.
|
|
470
|
-
const STATUS_KEY = "forge:run-task";
|
|
471
|
-
const MESSAGE_KEY = "forge:run-task:message";
|
|
472
|
-
// extractTurnPreview moved to viewport/renderer.ts; re-exported below for
|
|
473
|
-
// backwards-compatibility with existing imports (e.g. tests).
|
|
15
|
+
// sendKickoff is NEVER called from this file or its extracted modules.
|
|
16
|
+
//
|
|
17
|
+
// Orchestrator emit path (Plan 11 / Slice 2): the pipeline composes each phase
|
|
18
|
+
// event and emits it via `node <forgeRoot>/tools/store-cli.cjs "emit" <sprintId>
|
|
19
|
+
// <eventJson>` — see emitEvent in ./task/task-events.js. The subagent never
|
|
20
|
+
// calls store-cli emit itself; the orchestrator owns the emit path here.
|
|
21
|
+
// ── Generic orchestrator helpers (common) ─────────────────────────────────
|
|
22
|
+
export { formatLocalTime, isNonInteractive, validateId } from "./common/orchestrator-misc.js";
|
|
23
|
+
// ── Phase descriptor table ─────────────────────────────────────────────────
|
|
24
|
+
export { PHASES, SUMMARY_KEY_BY_ROLE } from "./task/task-phases.js";
|
|
25
|
+
// ── State persistence ─────────────────────────────────────────────────────
|
|
26
|
+
export { deleteState, isStateStale, readState, writeState, } from "./task/task-state.js";
|
|
27
|
+
// ── Verdict + task-record reads ────────────────────────────────────────────
|
|
28
|
+
export { readTaskRecord, readVerdict } from "./task/task-record.js";
|
|
29
|
+
// ── Event composition + emission ───────────────────────────────────────────
|
|
30
|
+
export { actionForRole, buildPhaseEvent, drainFrictionFile, emitEvent, emitIncompletePhaseEvent, isoCompact, judgementFromSummary, } from "./task/task-events.js";
|
|
31
|
+
// ── Gates ──────────────────────────────────────────────────────────────────
|
|
32
|
+
export { runPostflightGate, runPreflightGate, runPreflightGateWithData, } from "./task/task-gates.js";
|
|
33
|
+
// ── Task body composition + predecessor finder ─────────────────────────────
|
|
34
|
+
export { buildSummariesBlock, composeTaskBody, findPredecessorIndex } from "./task/task-body.js";
|
|
35
|
+
// ── Pipeline + command handler ─────────────────────────────────────────────
|
|
36
|
+
export { runTaskPipeline } from "./task/run-task-pipeline.js";
|
|
37
|
+
export { registerRunTask } from "./task/run-task-command.js";
|
|
38
|
+
// extractTurnPreview moved to viewport/renderer.ts; re-exported here for
|
|
39
|
+
// backwards-compatibility with existing imports (e.g. tests, wf-engine/engine).
|
|
474
40
|
export { extractTurnPreview } from "../viewport/renderer.js";
|
|
475
|
-
// ── runTaskPipeline ──────────────────────────────────────────────────────
|
|
476
|
-
/** Map a pipeline status to the orchestrator-transcript outcome vocabulary.
|
|
477
|
-
* Shared with fix-bug.ts (RunBugPipelineStatus is the same string union). */
|
|
478
|
-
export function transcriptOutcomeFor(status) {
|
|
479
|
-
switch (status) {
|
|
480
|
-
case "completed":
|
|
481
|
-
return "complete";
|
|
482
|
-
case "cancelled":
|
|
483
|
-
return "cancelled";
|
|
484
|
-
case "halted":
|
|
485
|
-
case "escalated":
|
|
486
|
-
return "halted";
|
|
487
|
-
case "failed":
|
|
488
|
-
return "error";
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
export async function runTaskPipeline(opts) {
|
|
492
|
-
// Thin wrapper: capture the orchestrator transcript writer the inner
|
|
493
|
-
// pipeline creates, surface its path on the result (so callers can
|
|
494
|
-
// archive the run), and GUARANTEE a pipeline-end on every outcome —
|
|
495
|
-
// cancel/halt/failure paths that return without recording one left runs
|
|
496
|
-
// archived as "incomplete" instead of their true outcome.
|
|
497
|
-
let orchTranscript;
|
|
498
|
-
const result = await runTaskPipelineInner(opts, (writer) => {
|
|
499
|
-
orchTranscript = writer;
|
|
500
|
-
});
|
|
501
|
-
if (orchTranscript) {
|
|
502
|
-
orchTranscript.close(transcriptOutcomeFor(result.status), result.lastError);
|
|
503
|
-
result.orchestratorTranscriptPath = orchTranscript.filePath;
|
|
504
|
-
}
|
|
505
|
-
return result;
|
|
506
|
-
}
|
|
507
|
-
async function runTaskPipelineInner(opts, onOrchestratorTranscript) {
|
|
508
|
-
const { taskId, cwd, ctx, forgeRoot, storeCli, preflightGate, registry, resumeFromState, onPhaseEvent } = opts;
|
|
509
|
-
// Bridge: OrchestratorTree for the dashboard overlay.
|
|
510
|
-
const tree = getOrchestratorTree();
|
|
511
|
-
// Load per-phase model routing config once at task entry (Plan 16 Slice 2).
|
|
512
|
-
// Empty / absent config produces inherit for every phase — no behaviour change.
|
|
513
|
-
// N-B-E: surface schema errors to caller (Decision 9 — orchestrators fail-fast).
|
|
514
|
-
// See doc/decisions/layered-config-error-policy.md.
|
|
515
|
-
const { merged: modelRoutingConfig, errors: layeredConfigErrors } = loadLayeredConfig(cwd);
|
|
516
|
-
if (layeredConfigErrors.length > 0) {
|
|
517
|
-
for (const e of layeredConfigErrors) {
|
|
518
|
-
ctx.ui.notify(`× forge:run-task — forge-cli config schema error: ${e}`, "error");
|
|
519
|
-
}
|
|
520
|
-
return {
|
|
521
|
-
status: "failed",
|
|
522
|
-
lastPhaseIndex: 0,
|
|
523
|
-
iterationCounts: {},
|
|
524
|
-
lastError: `forge-cli config schema errors: ${layeredConfigErrors.join("; ")}`,
|
|
525
|
-
};
|
|
526
|
-
}
|
|
527
|
-
// Pre-flight model config validation (Plan 16 Slice 3).
|
|
528
|
-
// Warns on unknown persona names / unavailable models; errors on unresolvable
|
|
529
|
-
// overrides / unknown pipelines. With FORGE_STRICT_MODELS=1, warnings → errors.
|
|
530
|
-
// FORGE-S25-T17: delegated to orchestrator-preflight.ts (H-13).
|
|
531
|
-
{
|
|
532
|
-
const personasDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "forge-payload", ".base-pack", "personas");
|
|
533
|
-
const personaCatalogue = readPersonaDir(personasDir);
|
|
534
|
-
const forgeCfgPath = path.join(cwd, ".forge", "config.json");
|
|
535
|
-
const pipelineCatalogue = readPipelineNames(forgeCfgPath);
|
|
536
|
-
const availableModels = ctx.modelRegistry?.getAvailable?.() ?? [];
|
|
537
|
-
const preflightResult = runOrchestratorPreflight({
|
|
538
|
-
mode: "task",
|
|
539
|
-
ctx,
|
|
540
|
-
notifyPrefix: "forge:run-task",
|
|
541
|
-
personaCatalogue,
|
|
542
|
-
pipelineCatalogue,
|
|
543
|
-
modelRoutingConfig,
|
|
544
|
-
availableModels: availableModels.map((m) => ({ provider: m.provider, id: m.id })),
|
|
545
|
-
});
|
|
546
|
-
if (!preflightResult.proceed) {
|
|
547
|
-
return preflightResult.result;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
// Determine starting phase from resumeFromState (if provided) or phase 0.
|
|
551
|
-
let currentPhaseIndex = resumeFromState?.phaseIndex ?? 0;
|
|
552
|
-
const iterationCounts = resumeFromState?.iterationCounts ?? {};
|
|
553
|
-
// Per-role dispatch counter for OrchestratorTree node identity. Distinct
|
|
554
|
-
// from iterationCounts (which only tracks review-verdict revisions): every
|
|
555
|
-
// dispatch of a role — including a plan re-run after a review loopback —
|
|
556
|
-
// gets its own `<taskId>:<role>:<attempt>` node, so the dashboard renders
|
|
557
|
-
// one leaf per run in dispatch order instead of merging attempts into one
|
|
558
|
-
// node (CART-BUG-003 dashboard regression). Seeded from resume state so
|
|
559
|
-
// resumed runs continue numbering past prior attempts.
|
|
560
|
-
const dispatchCounts = { ...(resumeFromState?.iterationCounts ?? {}) };
|
|
561
|
-
// Track model/provider from last successful subagent result (REVIEW FIX #1).
|
|
562
|
-
let lastModel;
|
|
563
|
-
let lastProvider;
|
|
564
|
-
// Resolve the task's sprintId once for prompt-cache session affinity. All
|
|
565
|
-
// phases in this task share the cache key `forge:${sprintId}` so the
|
|
566
|
-
// system-prompt + project orientation prefix (which is identical across
|
|
567
|
-
// personas) stays warm on Anthropic and shares a stable prompt_cache_key
|
|
568
|
-
// on OpenAI. Falls back to a task-scoped key if the task is unattached.
|
|
569
|
-
const taskRecordAtStart = readTaskRecord(taskId, storeCli, cwd);
|
|
570
|
-
const cacheSessionId = taskRecordAtStart?.sprintId ? `forge:${taskRecordAtStart.sprintId}` : `forge:task:${taskId}`;
|
|
571
|
-
// ── Orchestrator transcript ──────────────────────────────────────────
|
|
572
|
-
// FORGE-BUG-040 follow-up: one JSONL file per pipeline run, ISO-prefixed
|
|
573
|
-
// in its filename so review-loop iterations preserve their own logs
|
|
574
|
-
// instead of overwriting. Captures every ctx.ui.notify line plus
|
|
575
|
-
// structured phase-boundary events.
|
|
576
|
-
const orchTranscript = new OrchestratorTranscriptWriter({
|
|
577
|
-
cwd,
|
|
578
|
-
entityKind: "task",
|
|
579
|
-
entityId: taskId,
|
|
580
|
-
});
|
|
581
|
-
onOrchestratorTranscript(orchTranscript);
|
|
582
|
-
const __origNotify = ctx.ui.notify.bind(ctx.ui);
|
|
583
|
-
ctx.ui.notify = ((msg, level) => {
|
|
584
|
-
__origNotify(msg, level);
|
|
585
|
-
orchTranscript.record({
|
|
586
|
-
kind: "notify",
|
|
587
|
-
ts: new Date().toISOString(),
|
|
588
|
-
level: (level ?? "info"),
|
|
589
|
-
message: typeof msg === "string" ? msg : String(msg),
|
|
590
|
-
});
|
|
591
|
-
});
|
|
592
|
-
const pipelineStartMs = Date.now();
|
|
593
|
-
try {
|
|
594
|
-
while (currentPhaseIndex < PHASES.length) {
|
|
595
|
-
// ── Between-phase cancellation gate ────────────────────────────
|
|
596
|
-
if (opts.signal?.aborted) {
|
|
597
|
-
ctx.ui.notify(`⊘ forge:run-task — ${taskId} cancelled by user.`, "info");
|
|
598
|
-
registry.completePhase(taskId, PHASES[currentPhaseIndex]?.role ?? "unknown", "cancelled");
|
|
599
|
-
registry.confirmCancelled(taskId);
|
|
600
|
-
// ADR-S21-01: preserve state file so cancelled runs are resumable
|
|
601
|
-
// from the beginning of the cancelled phase (not deleted).
|
|
602
|
-
writeState(cwd, {
|
|
603
|
-
taskId,
|
|
604
|
-
phaseIndex: currentPhaseIndex,
|
|
605
|
-
iterationCounts,
|
|
606
|
-
halted: false,
|
|
607
|
-
status: "cancelled",
|
|
608
|
-
lastError: undefined,
|
|
609
|
-
savedAt: new Date().toISOString(),
|
|
610
|
-
});
|
|
611
|
-
return { status: "cancelled", lastPhaseIndex: currentPhaseIndex, iterationCounts };
|
|
612
|
-
}
|
|
613
|
-
const phase = PHASES[currentPhaseIndex];
|
|
614
|
-
if (!phase) {
|
|
615
|
-
ctx.ui.notify(`× forge:run-task — invalid phase index ${currentPhaseIndex}`, "error");
|
|
616
|
-
return {
|
|
617
|
-
status: "failed",
|
|
618
|
-
lastPhaseIndex: currentPhaseIndex,
|
|
619
|
-
iterationCounts,
|
|
620
|
-
lastError: `invalid phase index ${currentPhaseIndex}`,
|
|
621
|
-
};
|
|
622
|
-
}
|
|
623
|
-
ctx.ui.setStatus?.(STATUS_KEY, `run-task ${taskId}: phase ${currentPhaseIndex + 1}/${PHASES.length} (${phase.role})`);
|
|
624
|
-
ctx.ui.notify(`→ ${taskId}: ${phase.role} (phase ${currentPhaseIndex + 1}/${PHASES.length})`, "info");
|
|
625
|
-
orchTranscript.record({
|
|
626
|
-
kind: "phase-start",
|
|
627
|
-
ts: new Date().toISOString(),
|
|
628
|
-
phase: phase.role,
|
|
629
|
-
phaseIndex: currentPhaseIndex,
|
|
630
|
-
phaseCount: PHASES.length,
|
|
631
|
-
attempt: (iterationCounts[phase.role] ?? 0) + 1,
|
|
632
|
-
workflowFile: phase.workflowFile,
|
|
633
|
-
persona: phase.personaNoun,
|
|
634
|
-
});
|
|
635
|
-
const subWorkflowPath = path.join(cwd, ".forge", "workflows", `${phase.workflowFile}.md`);
|
|
636
|
-
// ── Read sub-workflow ─────────────────────────────────────────
|
|
637
|
-
let subWorkflowMd;
|
|
638
|
-
let subWorkflowAudience = "any";
|
|
639
|
-
try {
|
|
640
|
-
const loaded = loadWorkflow(subWorkflowPath);
|
|
641
|
-
subWorkflowMd = loaded.rawMarkdown;
|
|
642
|
-
subWorkflowAudience = loaded.audience;
|
|
643
|
-
}
|
|
644
|
-
catch (err) {
|
|
645
|
-
const e = err;
|
|
646
|
-
ctx.ui.notify(`× forge:run-task — failed to read sub-workflow for ${phase.role}: ${e.message ?? "unknown"}`, "error");
|
|
647
|
-
writeState(cwd, {
|
|
648
|
-
taskId,
|
|
649
|
-
phaseIndex: currentPhaseIndex,
|
|
650
|
-
iterationCounts,
|
|
651
|
-
halted: true,
|
|
652
|
-
lastError: `sub-workflow read failed: ${e.message ?? "unknown"}`,
|
|
653
|
-
savedAt: new Date().toISOString(),
|
|
654
|
-
});
|
|
655
|
-
return {
|
|
656
|
-
status: "failed",
|
|
657
|
-
lastPhaseIndex: currentPhaseIndex,
|
|
658
|
-
iterationCounts,
|
|
659
|
-
lastError: `sub-workflow read failed: ${e.message ?? "unknown"}`,
|
|
660
|
-
};
|
|
661
|
-
}
|
|
662
|
-
// ── 6a. Preflight gate ────────────────────────────────────────
|
|
663
|
-
if (fs.existsSync(preflightGate)) {
|
|
664
|
-
const preflightOutcome = runPreflightGateWithData(preflightGate, phase.role, taskId, cwd);
|
|
665
|
-
if (preflightOutcome.result === "halt") {
|
|
666
|
-
// Render structured failure reason if available.
|
|
667
|
-
if (preflightOutcome.gateFailure) {
|
|
668
|
-
ctx.ui.notify(`× forge:run-task — preflight gate failed for phase ${phase.role} ` +
|
|
669
|
-
`[${preflightOutcome.gateFailure.reasonCode}]: ${preflightOutcome.gateFailure.detail}`, "error");
|
|
670
|
-
}
|
|
671
|
-
else {
|
|
672
|
-
ctx.ui.notify(`× forge:run-task — preflight gate failed for phase ${phase.role} (exit 1); halting.`, "error");
|
|
673
|
-
}
|
|
674
|
-
writeState(cwd, {
|
|
675
|
-
taskId,
|
|
676
|
-
phaseIndex: currentPhaseIndex,
|
|
677
|
-
iterationCounts,
|
|
678
|
-
halted: true,
|
|
679
|
-
lastError: `preflight gate exit 1 for ${phase.role}`,
|
|
680
|
-
savedAt: new Date().toISOString(),
|
|
681
|
-
});
|
|
682
|
-
// Spawn halt-recovery advisor (Tier 1, best-effort — non-fatal).
|
|
683
|
-
if (preflightOutcome.gateFailure) {
|
|
684
|
-
const advisorModel = resolveAdvisorModel(modelRoutingConfig, ctx.model);
|
|
685
|
-
void runHaltAdvisor({
|
|
686
|
-
gateFailure: preflightOutcome.gateFailure,
|
|
687
|
-
advisorModel,
|
|
688
|
-
taskId,
|
|
689
|
-
cwd,
|
|
690
|
-
ctx: { ui: ctx.ui },
|
|
691
|
-
forgeRoot,
|
|
692
|
-
});
|
|
693
|
-
}
|
|
694
|
-
return {
|
|
695
|
-
status: "halted",
|
|
696
|
-
lastPhaseIndex: currentPhaseIndex,
|
|
697
|
-
iterationCounts,
|
|
698
|
-
lastError: `preflight gate exit 1 for ${phase.role}`,
|
|
699
|
-
};
|
|
700
|
-
}
|
|
701
|
-
if (preflightOutcome.result === "escalate") {
|
|
702
|
-
ctx.ui.notify(`× forge:run-task — preflight gate escalated for phase ${phase.role} (exit 2); manual intervention required.`, "error");
|
|
703
|
-
writeState(cwd, {
|
|
704
|
-
taskId,
|
|
705
|
-
phaseIndex: currentPhaseIndex,
|
|
706
|
-
iterationCounts,
|
|
707
|
-
halted: true,
|
|
708
|
-
lastError: `preflight gate exit 2 (escalate) for ${phase.role}`,
|
|
709
|
-
savedAt: new Date().toISOString(),
|
|
710
|
-
});
|
|
711
|
-
return {
|
|
712
|
-
status: "escalated",
|
|
713
|
-
lastPhaseIndex: currentPhaseIndex,
|
|
714
|
-
iterationCounts,
|
|
715
|
-
lastError: `preflight gate exit 2 (escalate) for ${phase.role}`,
|
|
716
|
-
};
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
// ── 6. Materialization-marker check ───────────────────────────
|
|
720
|
-
const markerCheck = checkMaterialization(subWorkflowPath, subWorkflowMd);
|
|
721
|
-
if (!markerCheck.ok) {
|
|
722
|
-
for (const marker of markerCheck.missing) {
|
|
723
|
-
ctx.ui.notify(`× workflow regression: ${marker} not found in ${subWorkflowPath}`, "error");
|
|
724
|
-
}
|
|
725
|
-
return {
|
|
726
|
-
status: "failed",
|
|
727
|
-
lastPhaseIndex: currentPhaseIndex,
|
|
728
|
-
iterationCounts,
|
|
729
|
-
lastError: `materialization markers missing: ${markerCheck.missing.join(", ")}`,
|
|
730
|
-
};
|
|
731
|
-
}
|
|
732
|
-
// ── 5. Audience check ─────────────────────────────────────────
|
|
733
|
-
// Wrap with CallerContextStore.asSubagent so assertAudience treats
|
|
734
|
-
// this as a subagent context (IL10: we ARE dispatching from subagent chain).
|
|
735
|
-
const audienceOk = CallerContextStore.asSubagent(phase.role, () => assertAudience({ workflowName: phase.workflowFile, audience: subWorkflowAudience }, ctx));
|
|
736
|
-
if (!audienceOk) {
|
|
737
|
-
writeState(cwd, {
|
|
738
|
-
taskId,
|
|
739
|
-
phaseIndex: currentPhaseIndex,
|
|
740
|
-
iterationCounts,
|
|
741
|
-
halted: true,
|
|
742
|
-
lastError: `audience check failed for ${phase.workflowFile}`,
|
|
743
|
-
savedAt: new Date().toISOString(),
|
|
744
|
-
});
|
|
745
|
-
return {
|
|
746
|
-
status: "failed",
|
|
747
|
-
lastPhaseIndex: currentPhaseIndex,
|
|
748
|
-
iterationCounts,
|
|
749
|
-
lastError: `audience check failed for ${phase.workflowFile}`,
|
|
750
|
-
};
|
|
751
|
-
}
|
|
752
|
-
// ── Persona load ──────────────────────────────────────────────
|
|
753
|
-
let persona;
|
|
754
|
-
try {
|
|
755
|
-
persona = loadForgePersona(phase.personaNoun, cwd);
|
|
756
|
-
}
|
|
757
|
-
catch (err) {
|
|
758
|
-
const e = err;
|
|
759
|
-
ctx.ui.notify(`× forge:run-task — persona '${phase.personaNoun}' not found for phase ${phase.role}: ${e.message ?? "unknown"}. ` +
|
|
760
|
-
"Run /forge:regenerate to materialize persona files.", "error");
|
|
761
|
-
writeState(cwd, {
|
|
762
|
-
taskId,
|
|
763
|
-
phaseIndex: currentPhaseIndex,
|
|
764
|
-
iterationCounts,
|
|
765
|
-
halted: true,
|
|
766
|
-
lastError: `persona load failed: ${e.message ?? "unknown"}`,
|
|
767
|
-
savedAt: new Date().toISOString(),
|
|
768
|
-
});
|
|
769
|
-
return {
|
|
770
|
-
status: "failed",
|
|
771
|
-
lastPhaseIndex: currentPhaseIndex,
|
|
772
|
-
iterationCounts,
|
|
773
|
-
lastError: `persona load failed: ${e.message ?? "unknown"}`,
|
|
774
|
-
};
|
|
775
|
-
}
|
|
776
|
-
// ── 4. Dispatch via runForgeSubagent (IL10) ───────────────────
|
|
777
|
-
// NEVER sendKickoff here — that would reproduce issue #30 (same-context inline = no fork).
|
|
778
|
-
// Read fresh task record to carry forward prior phase summaries (forge-cli#19).
|
|
779
|
-
const taskRecordForSummaries = currentPhaseIndex > 0 ? readTaskRecord(taskId, storeCli, cwd) : null;
|
|
780
|
-
const summariesBlock = buildSummariesBlock(taskRecordForSummaries?.summaries);
|
|
781
|
-
const taskBody = composeTaskBody(subWorkflowMd, taskId, summariesBlock || undefined);
|
|
782
|
-
// Log whether carry-forward summaries were injected (forge-cli#19).
|
|
783
|
-
if (summariesBlock) {
|
|
784
|
-
const debugCarryPath = path.join(cwd, ".forge", "cache", `run-task-debug-${taskId}.jsonl`);
|
|
785
|
-
try {
|
|
786
|
-
fs.mkdirSync(path.dirname(debugCarryPath), { recursive: true });
|
|
787
|
-
fs.appendFileSync(debugCarryPath, `${JSON.stringify({ ts: new Date().toISOString(), phase: phase.role, kind: "carry_forward_injected", summariesLength: summariesBlock.length, summariesBlock })}\n`, "utf8");
|
|
788
|
-
}
|
|
789
|
-
catch { /* best-effort debug log */ }
|
|
790
|
-
}
|
|
791
|
-
// Resolve per-phase model from layered config (Plan 16 Slice 2).
|
|
792
|
-
// Pipeline name "default" matches the Forge plugin's shipped pipeline.
|
|
793
|
-
// When config is absent or cascade bottoms out, resolves to inherit
|
|
794
|
-
// (model: undefined) — setModel is skipped and pi's current model is used.
|
|
795
|
-
const modelResolution = resolveModelForPhase("default", phase.role, phase.personaNoun, modelRoutingConfig);
|
|
796
|
-
const phaseStart = Date.now();
|
|
797
|
-
// Stabilization debug log — every subagent event appended as JSONL.
|
|
798
|
-
const debugLogPath = path.join(cwd, ".forge", "cache", `run-task-debug-${taskId}.jsonl`);
|
|
799
|
-
const writeDebug = (rec) => {
|
|
800
|
-
try {
|
|
801
|
-
fs.mkdirSync(path.dirname(debugLogPath), { recursive: true });
|
|
802
|
-
fs.appendFileSync(debugLogPath, `${JSON.stringify({ ts: new Date().toISOString(), phase: phase.role, ...rec })}\n`, "utf8");
|
|
803
|
-
}
|
|
804
|
-
catch {
|
|
805
|
-
// non-fatal; debug log is best-effort
|
|
806
|
-
}
|
|
807
|
-
};
|
|
808
|
-
writeDebug({ kind: "phase_start", phaseIndex: currentPhaseIndex });
|
|
809
|
-
writeDebug({
|
|
810
|
-
kind: "requested_model",
|
|
811
|
-
requested: modelResolution.model ?? null,
|
|
812
|
-
source: modelResolution.source,
|
|
813
|
-
persona: phase.personaNoun,
|
|
814
|
-
});
|
|
815
|
-
registry.startPhase(taskId, phase.role, currentPhaseIndex);
|
|
816
|
-
// Bridge: register phase in OrchestratorTree. Node identity is
|
|
817
|
-
// per-dispatch (see dispatchCounts above) — never reuse an ID for a
|
|
818
|
-
// re-dispatched role.
|
|
819
|
-
const iteration = (dispatchCounts[phase.role] = (dispatchCounts[phase.role] ?? 0) + 1);
|
|
820
|
-
const phaseNodeId = `${taskId}:${phase.role}:${iteration}`;
|
|
821
|
-
tree.startNode(phaseNodeId, {
|
|
822
|
-
parentId: taskId,
|
|
823
|
-
label: `${phase.role}:${iteration}`,
|
|
824
|
-
kind: "leaf",
|
|
825
|
-
// Full body — display clamping/expansion is the view's decision
|
|
826
|
-
// (the tree applies only a storage cap).
|
|
827
|
-
promptPreview: taskBody,
|
|
828
|
-
});
|
|
829
|
-
// Capture the first stream-observed model on turn_end (IL10 visibility).
|
|
830
|
-
// If pi auto-substitutes or setModel silently no-ops, this line will diverge
|
|
831
|
-
// from requested_model — exactly the diagnostic signal we want.
|
|
832
|
-
let modelObservedLogged = false;
|
|
833
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
834
|
-
const wrappedOnEvent = (event) => {
|
|
835
|
-
if (!modelObservedLogged && event?.type === "turn_end" && typeof event?.message?.model === "string") {
|
|
836
|
-
modelObservedLogged = true;
|
|
837
|
-
writeDebug({
|
|
838
|
-
kind: "model_observed",
|
|
839
|
-
provider: event.message.provider ?? null,
|
|
840
|
-
model: event.message.model,
|
|
841
|
-
});
|
|
842
|
-
}
|
|
843
|
-
observer.onEvent(event);
|
|
844
|
-
};
|
|
845
|
-
const refreshStatus = () => {
|
|
846
|
-
if (process.env.FORGE_VERBOSE !== "1")
|
|
847
|
-
return;
|
|
848
|
-
const elapsed = Math.floor((Date.now() - phaseStart) / 1000);
|
|
849
|
-
const tail = observer.state.lastTool ? ` · ${observer.state.lastTool}` : "";
|
|
850
|
-
ctx.ui.setStatus?.(STATUS_KEY, `run-task ${taskId}: ${phase.role} · t${observer.state.turn} · tools ${observer.state.toolCount}${observer.state.errCount ? ` · err ${observer.state.errCount}` : ""} · ${elapsed}s${tail}`);
|
|
851
|
-
};
|
|
852
|
-
const observer = attachViewportObserver({
|
|
853
|
-
registry,
|
|
854
|
-
sessionId: taskId,
|
|
855
|
-
phaseRole: phase.role,
|
|
856
|
-
nodeId: phaseNodeId,
|
|
857
|
-
beginHeader: `─── phase ${currentPhaseIndex + 1}/${PHASES.length} ${phase.role} begin · ${taskId} ───`,
|
|
858
|
-
writeDebug,
|
|
859
|
-
notify: (msg, level) => ctx.ui.notify(msg, level),
|
|
860
|
-
setStatusVerbose: process.env.FORGE_VERBOSE === "1" ? (k, v) => ctx.ui.setStatus?.(k, v) : undefined,
|
|
861
|
-
verboseKeys: { messageKey: MESSAGE_KEY },
|
|
862
|
-
afterEach: refreshStatus,
|
|
863
|
-
});
|
|
864
|
-
// ── Context governor injection (completes FORGE-S30-T07) ──────
|
|
865
|
-
// Per-phase factories built HERE because only the pipeline knows the
|
|
866
|
-
// `${personaNoun}/${role}` phase key — pi never sets persona/phase on
|
|
867
|
-
// ExtensionContext, and the parent session's registerHookDispatcher
|
|
868
|
-
// governor never sees subagent tool traffic (dormant-governor defect,
|
|
869
|
-
// CART-S02-T03 benchmark). Flag-gated: FORGE_CTX_GOVERNOR=1.
|
|
870
|
-
// buildGovernorFactory — Mechanisms A/B/C/D in the subagent
|
|
871
|
-
// buildForgeCompactionFactory — Mechanism E with warm-tier path opts
|
|
872
|
-
// (previously injected from index.ts with NO opts → warm-tier dead)
|
|
873
|
-
const phaseKey = `${phase.personaNoun}/${phase.role}`;
|
|
874
|
-
// Sprint ID from the task record's sprint FK (the store owns that
|
|
875
|
-
// relationship); the taskId-shape regex is only a fallback for records
|
|
876
|
-
// missing the FK (FORGE-BUG-043 PR 2).
|
|
877
|
-
const sprintIdForSummaries = taskRecordAtStart?.sprintId ?? /^(.*)-T\d+$/.exec(taskId)?.[1];
|
|
878
|
-
const governorFactories = process.env.FORGE_CTX_GOVERNOR === "1"
|
|
879
|
-
? [
|
|
880
|
-
buildGovernorFactory({ phaseKey, cwd }),
|
|
881
|
-
buildForgeCompactionFactory({
|
|
882
|
-
cwd,
|
|
883
|
-
phaseKey,
|
|
884
|
-
entityId: taskId,
|
|
885
|
-
sprintId: sprintIdForSummaries,
|
|
886
|
-
}),
|
|
887
|
-
]
|
|
888
|
-
: [];
|
|
889
|
-
const phaseExtensionFactories = [...(opts.extensionFactories ?? []), ...governorFactories];
|
|
890
|
-
let result;
|
|
891
|
-
try {
|
|
892
|
-
// FORGE-BUG-040: wrap the runForgeSubagent dispatch in the phase
|
|
893
|
-
// caller context (parity with fix-bug.ts) so the phase-ownership
|
|
894
|
-
// guard can verify tool calls from the subagent. Single setter
|
|
895
|
-
// of phase context for the task pipeline.
|
|
896
|
-
result = await CallerContextStore.asSubagent(phase.role, () => runForgeSubagent({
|
|
897
|
-
persona,
|
|
898
|
-
task: taskBody,
|
|
899
|
-
cwd,
|
|
900
|
-
exportTag: `${taskId}__${phase.role}`,
|
|
901
|
-
tailLog: observer.state.tailLog,
|
|
902
|
-
cacheSessionId,
|
|
903
|
-
streamFn: opts.streamFnFactory?.({ kind: "task-phase", persona: persona.name, phase: phase.role, taskId }),
|
|
904
|
-
onEvent: wrappedOnEvent,
|
|
905
|
-
requestedModel: modelResolution.model,
|
|
906
|
-
modelRegistry: ctx.modelRegistry,
|
|
907
|
-
signal: opts.signal,
|
|
908
|
-
customTools: opts.forgeToolDefs ? getSubagentTools(opts.forgeToolDefs, persona.name) : undefined,
|
|
909
|
-
extensionFactories: phaseExtensionFactories.length > 0 ? phaseExtensionFactories : undefined,
|
|
910
|
-
}));
|
|
911
|
-
}
|
|
912
|
-
catch (err) {
|
|
913
|
-
const e = err;
|
|
914
|
-
ctx.ui.notify(`× forge:run-task — runForgeSubagent threw for phase ${phase.role}: ${e.message ?? "unknown"}`, "error");
|
|
915
|
-
tree.completeNode(phaseNodeId, "failed");
|
|
916
|
-
writeState(cwd, {
|
|
917
|
-
taskId,
|
|
918
|
-
phaseIndex: currentPhaseIndex,
|
|
919
|
-
iterationCounts,
|
|
920
|
-
halted: true,
|
|
921
|
-
lastError: `runForgeSubagent threw: ${e.message ?? "unknown"}`,
|
|
922
|
-
savedAt: new Date().toISOString(),
|
|
923
|
-
});
|
|
924
|
-
return {
|
|
925
|
-
status: "failed",
|
|
926
|
-
lastPhaseIndex: currentPhaseIndex,
|
|
927
|
-
iterationCounts,
|
|
928
|
-
lastError: `runForgeSubagent threw: ${e.message ?? "unknown"}`,
|
|
929
|
-
};
|
|
930
|
-
}
|
|
931
|
-
// Close this dispatch's tree node with final usage/model — MUST be
|
|
932
|
-
// called on every exit path below (failure, halt, escalation,
|
|
933
|
-
// loopback, advance). A node left `running` keeps a live spinner in
|
|
934
|
-
// the dashboard forever AND absorbs the next same-role dispatch's
|
|
935
|
-
// telemetry via the observer's legacy role-prefix scan.
|
|
936
|
-
const finishPhaseNode = (status) => {
|
|
937
|
-
tree.setNodeUsage(phaseNodeId, {
|
|
938
|
-
input: result.usage.input,
|
|
939
|
-
output: result.usage.output,
|
|
940
|
-
cacheRead: result.usage.cacheRead,
|
|
941
|
-
});
|
|
942
|
-
if (result.model)
|
|
943
|
-
tree.setNodeModel(phaseNodeId, result.model, result.provider ?? "");
|
|
944
|
-
tree.completeNode(phaseNodeId, status);
|
|
945
|
-
};
|
|
946
|
-
// ── Post-subagent abort detection ─────────────────────────────────
|
|
947
|
-
// If the abort signal fired during the subagent run, treat it as
|
|
948
|
-
// cancellation regardless of the exit code (subagent may have been
|
|
949
|
-
// mid-turn when aborted — exitCode could be 0 or 1).
|
|
950
|
-
// This check MUST come before halt-on-failure so that
|
|
951
|
-
// stopReason="aborted" + exitCode=1 is classified as cancellation,
|
|
952
|
-
// not a phase failure.
|
|
953
|
-
if (result.stopReason === "aborted" || opts.signal?.aborted) {
|
|
954
|
-
ctx.ui.notify(`⊘ forge:run-task — ${taskId} phase ${phase.role} cancelled.`, "info");
|
|
955
|
-
registry.completePhase(taskId, phase.role, "cancelled");
|
|
956
|
-
tree.completeNode(phaseNodeId, "cancelled");
|
|
957
|
-
registry.confirmCancelled(taskId);
|
|
958
|
-
// Bug B: account the billed tokens of this aborted attempt before returning.
|
|
959
|
-
{
|
|
960
|
-
const abortSprintId = readTaskRecord(taskId, storeCli, cwd)?.sprintId;
|
|
961
|
-
if (abortSprintId) {
|
|
962
|
-
emitIncompletePhaseEvent({
|
|
963
|
-
emitCtx: {
|
|
964
|
-
entityType: "task",
|
|
965
|
-
taskId,
|
|
966
|
-
sprintId: abortSprintId,
|
|
967
|
-
phase,
|
|
968
|
-
iteration: (iterationCounts[phase.role] ?? 0) + 1,
|
|
969
|
-
startMs: phaseStart,
|
|
970
|
-
endMs: Date.now(),
|
|
971
|
-
model: result.model ?? "unknown",
|
|
972
|
-
provider: result.provider ?? "unknown",
|
|
973
|
-
usage: {
|
|
974
|
-
input: result.usage.input,
|
|
975
|
-
output: result.usage.output,
|
|
976
|
-
cacheRead: result.usage.cacheRead,
|
|
977
|
-
cacheWrite: result.usage.cacheWrite,
|
|
978
|
-
},
|
|
979
|
-
judgement: undefined,
|
|
980
|
-
storeCli,
|
|
981
|
-
cwd,
|
|
982
|
-
},
|
|
983
|
-
outcome: "aborted",
|
|
984
|
-
notes: result.errorMessage ?? result.stopReason ?? undefined,
|
|
985
|
-
onDebug: writeDebug,
|
|
986
|
-
});
|
|
987
|
-
}
|
|
988
|
-
else {
|
|
989
|
-
writeDebug({ kind: "incomplete_emit_skipped", reason: "no-sprintId", outcome: "aborted" });
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
// ADR-S21-01: preserve state file so cancelled runs are resumable
|
|
993
|
-
writeState(cwd, {
|
|
994
|
-
taskId,
|
|
995
|
-
phaseIndex: currentPhaseIndex,
|
|
996
|
-
iterationCounts,
|
|
997
|
-
halted: false,
|
|
998
|
-
status: "cancelled",
|
|
999
|
-
lastError: undefined,
|
|
1000
|
-
savedAt: new Date().toISOString(),
|
|
1001
|
-
});
|
|
1002
|
-
return { status: "cancelled", lastPhaseIndex: currentPhaseIndex, iterationCounts };
|
|
1003
|
-
}
|
|
1004
|
-
// ── Halt-on-failure ───────────────────────────────────────────
|
|
1005
|
-
if (result.exitCode !== 0) {
|
|
1006
|
-
ctx.ui.notify(`× forge:run-task — phase ${phase.role} failed (exit ${result.exitCode})` +
|
|
1007
|
-
(result.errorMessage ? `: ${result.errorMessage}` : "") +
|
|
1008
|
-
(result.stopReason ? ` [${result.stopReason}]` : ""), "error");
|
|
1009
|
-
finishPhaseNode("failed");
|
|
1010
|
-
// Bug B: account the billed tokens of this failed attempt before returning.
|
|
1011
|
-
{
|
|
1012
|
-
const failSprintId = readTaskRecord(taskId, storeCli, cwd)?.sprintId;
|
|
1013
|
-
if (failSprintId) {
|
|
1014
|
-
emitIncompletePhaseEvent({
|
|
1015
|
-
emitCtx: {
|
|
1016
|
-
entityType: "task",
|
|
1017
|
-
taskId,
|
|
1018
|
-
sprintId: failSprintId,
|
|
1019
|
-
phase,
|
|
1020
|
-
iteration: (iterationCounts[phase.role] ?? 0) + 1,
|
|
1021
|
-
startMs: phaseStart,
|
|
1022
|
-
endMs: Date.now(),
|
|
1023
|
-
model: result.model ?? "unknown",
|
|
1024
|
-
provider: result.provider ?? "unknown",
|
|
1025
|
-
usage: {
|
|
1026
|
-
input: result.usage.input,
|
|
1027
|
-
output: result.usage.output,
|
|
1028
|
-
cacheRead: result.usage.cacheRead,
|
|
1029
|
-
cacheWrite: result.usage.cacheWrite,
|
|
1030
|
-
},
|
|
1031
|
-
judgement: undefined,
|
|
1032
|
-
storeCli,
|
|
1033
|
-
cwd,
|
|
1034
|
-
},
|
|
1035
|
-
outcome: "failed",
|
|
1036
|
-
notes: result.errorMessage ?? result.stopReason ?? undefined,
|
|
1037
|
-
onDebug: writeDebug,
|
|
1038
|
-
});
|
|
1039
|
-
}
|
|
1040
|
-
else {
|
|
1041
|
-
writeDebug({ kind: "incomplete_emit_skipped", reason: "no-sprintId", outcome: "failed" });
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
writeState(cwd, {
|
|
1045
|
-
taskId,
|
|
1046
|
-
phaseIndex: currentPhaseIndex,
|
|
1047
|
-
iterationCounts,
|
|
1048
|
-
halted: true,
|
|
1049
|
-
lastError: result.errorMessage ?? result.stopReason ?? "subagent exit non-zero",
|
|
1050
|
-
savedAt: new Date().toISOString(),
|
|
1051
|
-
});
|
|
1052
|
-
return {
|
|
1053
|
-
status: "failed",
|
|
1054
|
-
lastPhaseIndex: currentPhaseIndex,
|
|
1055
|
-
iterationCounts,
|
|
1056
|
-
lastError: result.errorMessage ?? result.stopReason ?? "subagent exit non-zero",
|
|
1057
|
-
};
|
|
1058
|
-
}
|
|
1059
|
-
// Capture model/provider from subagent result (REVIEW FIX #1).
|
|
1060
|
-
if (result.model)
|
|
1061
|
-
lastModel = result.model;
|
|
1062
|
-
if (result.provider)
|
|
1063
|
-
lastProvider = result.provider;
|
|
1064
|
-
// Phase-complete liveliness ping (counts + duration).
|
|
1065
|
-
{
|
|
1066
|
-
const elapsed = Math.floor((Date.now() - phaseStart) / 1000);
|
|
1067
|
-
const { turn, toolCount, errCount, cumUsage } = observer.state;
|
|
1068
|
-
ctx.ui.notify(`✓ ${phase.role}: ${turn} turn${turn === 1 ? "" : "s"} · ${toolCount} tool call${toolCount === 1 ? "" : "s"}${errCount ? ` · ${errCount} err` : ""} · ${elapsed}s`, "info");
|
|
1069
|
-
orchTranscript.record({
|
|
1070
|
-
kind: "phase-end",
|
|
1071
|
-
ts: new Date().toISOString(),
|
|
1072
|
-
phase: phase.role,
|
|
1073
|
-
phaseIndex: currentPhaseIndex,
|
|
1074
|
-
attempt: (iterationCounts[phase.role] ?? 0) + 1,
|
|
1075
|
-
verdict: "n/a",
|
|
1076
|
-
elapsedMs: Date.now() - phaseStart,
|
|
1077
|
-
turns: turn,
|
|
1078
|
-
toolCount,
|
|
1079
|
-
errCount,
|
|
1080
|
-
subagentTranscriptPath: result.subagentTranscriptPath,
|
|
1081
|
-
});
|
|
1082
|
-
const { cumCompression } = observer.state;
|
|
1083
|
-
registry.appendTail(taskId, phase.role, fmtPhaseSummary({
|
|
1084
|
-
role: phase.role,
|
|
1085
|
-
turns: turn,
|
|
1086
|
-
tools: toolCount,
|
|
1087
|
-
errors: errCount,
|
|
1088
|
-
wallSeconds: elapsed,
|
|
1089
|
-
usage: cumUsage,
|
|
1090
|
-
model: result.model,
|
|
1091
|
-
provider: result.provider,
|
|
1092
|
-
compression: cumCompression.tokensSaved > 0 ? cumCompression : undefined,
|
|
1093
|
-
}));
|
|
1094
|
-
}
|
|
1095
|
-
// ── Plan 11 / Slice 2: orchestrator emits phase event ─────────
|
|
1096
|
-
const phaseEndMs = Date.now();
|
|
1097
|
-
const taskRecord = readTaskRecord(taskId, storeCli, cwd);
|
|
1098
|
-
const sprintId = taskRecord?.sprintId;
|
|
1099
|
-
if (!sprintId) {
|
|
1100
|
-
ctx.ui.notify(`⚠ forge:run-task — could not resolve sprintId for ${taskId}; ` +
|
|
1101
|
-
`skipping orchestrator emit for phase ${phase.role}`, "warning");
|
|
1102
|
-
writeDebug({ kind: "emit_skipped", reason: "no-sprintId" });
|
|
1103
|
-
}
|
|
1104
|
-
else {
|
|
1105
|
-
const phaseIteration = (iterationCounts[phase.role] ?? 0) + 1;
|
|
1106
|
-
const emitCtx = {
|
|
1107
|
-
entityType: "task",
|
|
1108
|
-
taskId,
|
|
1109
|
-
sprintId,
|
|
1110
|
-
phase,
|
|
1111
|
-
iteration: phaseIteration,
|
|
1112
|
-
startMs: phaseStart,
|
|
1113
|
-
endMs: phaseEndMs,
|
|
1114
|
-
model: result.model ?? "unknown",
|
|
1115
|
-
provider: result.provider ?? "unknown",
|
|
1116
|
-
usage: {
|
|
1117
|
-
input: result.usage.input,
|
|
1118
|
-
output: result.usage.output,
|
|
1119
|
-
cacheRead: result.usage.cacheRead,
|
|
1120
|
-
cacheWrite: result.usage.cacheWrite,
|
|
1121
|
-
},
|
|
1122
|
-
judgement: judgementFromSummary(taskRecord, phase.role),
|
|
1123
|
-
storeCli,
|
|
1124
|
-
cwd,
|
|
1125
|
-
};
|
|
1126
|
-
const phaseEvent = buildPhaseEvent(emitCtx);
|
|
1127
|
-
const emitResult = emitEvent(storeCli, cwd, sprintId, phaseEvent);
|
|
1128
|
-
if (!emitResult.ok) {
|
|
1129
|
-
ctx.ui.notify(`⚠ forge:run-task — phase event emit failed for ${phase.role}: ${emitResult.stderr.trim()}`, "warning");
|
|
1130
|
-
writeDebug({ kind: "emit_failed", stderr: emitResult.stderr });
|
|
1131
|
-
}
|
|
1132
|
-
else {
|
|
1133
|
-
writeDebug({ kind: "emit_ok", eventId: phaseEvent.eventId });
|
|
1134
|
-
}
|
|
1135
|
-
// Notify sprint-level observer (FORGE-S21-T03).
|
|
1136
|
-
if (onPhaseEvent)
|
|
1137
|
-
onPhaseEvent(phaseEvent);
|
|
1138
|
-
// Drain friction file for this phase, if any.
|
|
1139
|
-
const frictionPath = path.join(cwd, ".forge", "cache", `FRICTION-${phase.role}.jsonl`);
|
|
1140
|
-
const drain = drainFrictionFile(frictionPath, emitCtx);
|
|
1141
|
-
if (drain.emitted + drain.failed > 0) {
|
|
1142
|
-
writeDebug({ kind: "friction_drain", ...drain });
|
|
1143
|
-
if (drain.failed > 0) {
|
|
1144
|
-
ctx.ui.notify(`⚠ forge:run-task — friction drain for ${phase.role}: ${drain.emitted} ok, ${drain.failed} failed`, "warning");
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
// ── 6b. Verdict check (review phases only) ────────────────────
|
|
1149
|
-
if (phase.isReview) {
|
|
1150
|
-
const verdict = readVerdict(taskId, phase.role, storeCli, cwd);
|
|
1151
|
-
if (verdict === "missing") {
|
|
1152
|
-
ctx.ui.notify(`× forge:run-task — verdict missing for phase ${phase.role} after subagent completed. ` +
|
|
1153
|
-
"Subagent may have crashed or failed to write summaries. Halting for advisory.", "error");
|
|
1154
|
-
finishPhaseNode("failed");
|
|
1155
|
-
writeState(cwd, {
|
|
1156
|
-
taskId,
|
|
1157
|
-
phaseIndex: currentPhaseIndex,
|
|
1158
|
-
iterationCounts,
|
|
1159
|
-
halted: true,
|
|
1160
|
-
lastError: `verdict missing for ${phase.role}`,
|
|
1161
|
-
savedAt: new Date().toISOString(),
|
|
1162
|
-
});
|
|
1163
|
-
// A missing verdict IS a postflight-outputs failure: the canonical
|
|
1164
|
-
// phase summary the subagent must write (e.g. summaries.code_review)
|
|
1165
|
-
// was never recorded, so there is no verdict to route on. review-phase
|
|
1166
|
-
// workflows declare no `outputs` block, so runPostflightGate is a
|
|
1167
|
-
// pass-through here — this readVerdict check is the effective gate.
|
|
1168
|
-
// Route it through the halt-recovery advisor (FORGE-S26-T18), the same
|
|
1169
|
-
// hand-off the postflight-gate-unsatisfied branch uses, instead of a
|
|
1170
|
-
// bare escalation — so the strongest configured model can diagnose the
|
|
1171
|
-
// missing-summary cause. Best-effort, non-fatal (FORGE-S26-T19 parity).
|
|
1172
|
-
const gateFailure = {
|
|
1173
|
-
phase: phase.role,
|
|
1174
|
-
reasonCode: "verdict-missing",
|
|
1175
|
-
detail: `Phase '${phase.role}' completed but no verdict was found in the store. ` +
|
|
1176
|
-
"The canonical phase summary was not written, so the orchestrator has no verdict to route on.",
|
|
1177
|
-
remediation: "Re-run the phase and ensure the subagent's forge_store set-summary call " +
|
|
1178
|
-
'uses args:["<recordId>", "<phaseKey>"] with the literal phase key as args[1] ' +
|
|
1179
|
-
"(e.g. code_review), and that the call exits zero before the subagent returns.",
|
|
1180
|
-
};
|
|
1181
|
-
const advisorModel = resolveAdvisorModel(modelRoutingConfig, ctx.model);
|
|
1182
|
-
void runHaltAdvisor({
|
|
1183
|
-
gateFailure,
|
|
1184
|
-
advisorModel,
|
|
1185
|
-
taskId,
|
|
1186
|
-
cwd,
|
|
1187
|
-
ctx: { ui: ctx.ui },
|
|
1188
|
-
forgeRoot,
|
|
1189
|
-
});
|
|
1190
|
-
return {
|
|
1191
|
-
status: "halted",
|
|
1192
|
-
lastPhaseIndex: currentPhaseIndex,
|
|
1193
|
-
iterationCounts,
|
|
1194
|
-
lastError: `verdict missing for ${phase.role}`,
|
|
1195
|
-
};
|
|
1196
|
-
}
|
|
1197
|
-
if (verdict === "revision") {
|
|
1198
|
-
// Increment iteration count for this review phase
|
|
1199
|
-
iterationCounts[phase.role] = (iterationCounts[phase.role] ?? 0) + 1;
|
|
1200
|
-
if (iterationCounts[phase.role] >= phase.maxIterations) {
|
|
1201
|
-
ctx.ui.notify(`× forge:run-task — revision cap reached for phase ${phase.role} ` +
|
|
1202
|
-
`(${iterationCounts[phase.role]}/${phase.maxIterations} iterations). Escalating.`, "error");
|
|
1203
|
-
finishPhaseNode("escalated");
|
|
1204
|
-
writeState(cwd, {
|
|
1205
|
-
taskId,
|
|
1206
|
-
phaseIndex: currentPhaseIndex,
|
|
1207
|
-
iterationCounts,
|
|
1208
|
-
halted: true,
|
|
1209
|
-
lastError: `revision cap reached for ${phase.role}`,
|
|
1210
|
-
savedAt: new Date().toISOString(),
|
|
1211
|
-
});
|
|
1212
|
-
return {
|
|
1213
|
-
status: "escalated",
|
|
1214
|
-
lastPhaseIndex: currentPhaseIndex,
|
|
1215
|
-
iterationCounts,
|
|
1216
|
-
lastError: `revision cap reached for ${phase.role}`,
|
|
1217
|
-
};
|
|
1218
|
-
}
|
|
1219
|
-
// Loop back to predecessor non-review phase. The review
|
|
1220
|
-
// subagent itself finished cleanly — `revision` is its verdict,
|
|
1221
|
-
// not a failure — so its node closes as completed before the
|
|
1222
|
-
// predecessor re-dispatches under a fresh node ID.
|
|
1223
|
-
finishPhaseNode("completed");
|
|
1224
|
-
const predIndex = findPredecessorIndex(PHASES, currentPhaseIndex);
|
|
1225
|
-
ctx.ui.notify(`⟳ forge:run-task — ${phase.role} returned revision; looping to ${PHASES[predIndex]?.role ?? predIndex} ` +
|
|
1226
|
-
`(attempt ${iterationCounts[phase.role]}/${phase.maxIterations})`, "info");
|
|
1227
|
-
orchTranscript.record({
|
|
1228
|
-
kind: "phase-loopback",
|
|
1229
|
-
ts: new Date().toISOString(),
|
|
1230
|
-
fromPhase: phase.role,
|
|
1231
|
-
toPhase: PHASES[predIndex]?.role ?? String(predIndex),
|
|
1232
|
-
fromPhaseIndex: currentPhaseIndex,
|
|
1233
|
-
toPhaseIndex: predIndex,
|
|
1234
|
-
reason: `${phase.role} returned revision (attempt ${iterationCounts[phase.role]}/${phase.maxIterations})`,
|
|
1235
|
-
});
|
|
1236
|
-
// Write intermediate state (not halted — still running)
|
|
1237
|
-
writeState(cwd, {
|
|
1238
|
-
taskId,
|
|
1239
|
-
phaseIndex: predIndex,
|
|
1240
|
-
iterationCounts,
|
|
1241
|
-
halted: false,
|
|
1242
|
-
savedAt: new Date().toISOString(),
|
|
1243
|
-
});
|
|
1244
|
-
currentPhaseIndex = predIndex;
|
|
1245
|
-
continue;
|
|
1246
|
-
}
|
|
1247
|
-
// verdict === "approved": fall through to advance
|
|
1248
|
-
}
|
|
1249
|
-
// Postflight gate: evaluate `outputs` block after subagent returns,
|
|
1250
|
-
// before FSM status advance (FORGE-S26-T19). Hard enforcement in forge-cli;
|
|
1251
|
-
// plugin LLM route treats postflight as advisory. On UNSATISFIED: do not
|
|
1252
|
-
// advance currentPhaseIndex, halt, hand off to existing runHaltAdvisor.
|
|
1253
|
-
{
|
|
1254
|
-
const postflightGatePath = preflightGate.replace("preflight-gate.cjs", "postflight-gate.cjs");
|
|
1255
|
-
const postflightOutcome = runPostflightGate(postflightGatePath, phase.role, taskId, cwd);
|
|
1256
|
-
if (postflightOutcome.result === "unsatisfied") {
|
|
1257
|
-
finishPhaseNode("failed");
|
|
1258
|
-
if (postflightOutcome.gateFailure) {
|
|
1259
|
-
ctx.ui.notify(`× forge:run-task — postflight gate failed for phase ${phase.role} ` +
|
|
1260
|
-
`[${postflightOutcome.gateFailure.reasonCode}]: ${postflightOutcome.gateFailure.detail}`, "error");
|
|
1261
|
-
}
|
|
1262
|
-
else {
|
|
1263
|
-
ctx.ui.notify(`× forge:run-task — postflight gate failed for phase ${phase.role}; halting.`, "error");
|
|
1264
|
-
}
|
|
1265
|
-
// Do NOT advance FSM — write state at current phaseIndex (halted)
|
|
1266
|
-
writeState(cwd, {
|
|
1267
|
-
taskId,
|
|
1268
|
-
phaseIndex: currentPhaseIndex,
|
|
1269
|
-
iterationCounts,
|
|
1270
|
-
halted: true,
|
|
1271
|
-
lastError: `postflight gate exit 1 for ${phase.role}`,
|
|
1272
|
-
savedAt: new Date().toISOString(),
|
|
1273
|
-
});
|
|
1274
|
-
// Spawn halt-recovery advisor (Tier 1, best-effort — non-fatal).
|
|
1275
|
-
if (postflightOutcome.gateFailure) {
|
|
1276
|
-
const advisorModel = resolveAdvisorModel(modelRoutingConfig, ctx.model);
|
|
1277
|
-
void runHaltAdvisor({
|
|
1278
|
-
gateFailure: postflightOutcome.gateFailure,
|
|
1279
|
-
advisorModel,
|
|
1280
|
-
taskId,
|
|
1281
|
-
cwd,
|
|
1282
|
-
ctx: { ui: ctx.ui },
|
|
1283
|
-
forgeRoot,
|
|
1284
|
-
});
|
|
1285
|
-
}
|
|
1286
|
-
return {
|
|
1287
|
-
status: "halted",
|
|
1288
|
-
lastPhaseIndex: currentPhaseIndex,
|
|
1289
|
-
iterationCounts,
|
|
1290
|
-
lastError: `postflight gate exit 1 for ${phase.role}`,
|
|
1291
|
-
};
|
|
1292
|
-
}
|
|
1293
|
-
// "ok" or "error" — proceed to advance
|
|
1294
|
-
}
|
|
1295
|
-
// ── Advance to next phase ─────────────────────────────────────
|
|
1296
|
-
registry.completePhase(taskId, phase.role, "completed");
|
|
1297
|
-
finishPhaseNode("completed");
|
|
1298
|
-
writeState(cwd, {
|
|
1299
|
-
taskId,
|
|
1300
|
-
phaseIndex: currentPhaseIndex,
|
|
1301
|
-
iterationCounts,
|
|
1302
|
-
halted: false,
|
|
1303
|
-
savedAt: new Date().toISOString(),
|
|
1304
|
-
});
|
|
1305
|
-
currentPhaseIndex++;
|
|
1306
|
-
}
|
|
1307
|
-
// ── All phases complete ───────────────────────────────────────────
|
|
1308
|
-
deleteState(cwd, taskId);
|
|
1309
|
-
orchTranscript.record({
|
|
1310
|
-
kind: "pipeline-end",
|
|
1311
|
-
ts: new Date().toISOString(),
|
|
1312
|
-
outcome: "complete",
|
|
1313
|
-
elapsedMs: Date.now() - pipelineStartMs,
|
|
1314
|
-
});
|
|
1315
|
-
return {
|
|
1316
|
-
status: "completed",
|
|
1317
|
-
lastPhaseIndex: PHASES.length - 1,
|
|
1318
|
-
iterationCounts,
|
|
1319
|
-
model: lastModel,
|
|
1320
|
-
provider: lastProvider,
|
|
1321
|
-
};
|
|
1322
|
-
}
|
|
1323
|
-
finally {
|
|
1324
|
-
ctx.ui.notify = __origNotify;
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1327
|
-
export function registerRunTask(pi, options = {}) {
|
|
1328
|
-
pi.registerCommand("forge:run-task", {
|
|
1329
|
-
description: "Run the full task pipeline (plan → review → implement → validate → approve → commit). " +
|
|
1330
|
-
"Usage: /forge:run-task <TASK_ID>. " +
|
|
1331
|
-
"Orchestrator archetype: each phase is an isolated subagent session (IL10).",
|
|
1332
|
-
async handler(args, ctx) {
|
|
1333
|
-
const cwd = options.cwd ?? process.cwd();
|
|
1334
|
-
let taskId = args.trim();
|
|
1335
|
-
if (!taskId) {
|
|
1336
|
-
ctx.ui.notify("× forge:run-task — task ID required. Usage: /forge:run-task <TASK_ID>", "error");
|
|
1337
|
-
return;
|
|
1338
|
-
}
|
|
1339
|
-
ctx.ui.setStatus?.(STATUS_KEY, `run-task ${taskId}: initializing…`);
|
|
1340
|
-
// ── Discover forge config ────────────────────────────────────────
|
|
1341
|
-
const forgeConfig = discoverForgeConfigCached(cwd);
|
|
1342
|
-
if (!forgeConfig) {
|
|
1343
|
-
ctx.ui.notify("× forge:run-task — no Forge project found at cwd. Run /forge:init first.", "error");
|
|
1344
|
-
ctx.ui.setStatus?.(STATUS_KEY, undefined);
|
|
1345
|
-
ctx.ui.setStatus?.(MESSAGE_KEY, undefined);
|
|
1346
|
-
return;
|
|
1347
|
-
}
|
|
1348
|
-
const forgeRoot = forgeConfig.forgeRoot;
|
|
1349
|
-
// Best-effort transcript-archive sweep: adopt any project-local runs
|
|
1350
|
-
// not yet in the central index (crash recovery + pre-existing
|
|
1351
|
-
// history). Runs BEFORE this pipeline creates its own transcript
|
|
1352
|
-
// writer, so the in-flight run is never swept half-written.
|
|
1353
|
-
sweepProjectTranscripts(cwd);
|
|
1354
|
-
// ── Resolve task ID (prefix-normalize, suffix-match, NLP fallback) ──
|
|
1355
|
-
// Handles unprefixed IDs like "S22-T03" → "FORGE-S22-T03".
|
|
1356
|
-
// Issue #20: unprefixed task IDs silently poisoned substitutions.
|
|
1357
|
-
// NOTE: resolveToCanonicalId may surface ctx.ui.select (disambiguation)
|
|
1358
|
-
// or ctx.ui.confirm prompts. The session must NOT be registered yet
|
|
1359
|
-
// at this point — the chip strip would appear before the user has
|
|
1360
|
-
// chosen which task they meant, stealing arrow keys from the dialog.
|
|
1361
|
-
const toolDir = resolveToolDir(forgeRoot);
|
|
1362
|
-
const resolvedTaskId = await resolveToCanonicalId(taskId, toolDir, cwd, "task", {
|
|
1363
|
-
ctx,
|
|
1364
|
-
commandLabel: "forge:run-task",
|
|
1365
|
-
});
|
|
1366
|
-
if (!resolvedTaskId) {
|
|
1367
|
-
// Error already emitted by resolver
|
|
1368
|
-
ctx.ui.setStatus?.(STATUS_KEY, undefined);
|
|
1369
|
-
ctx.ui.setStatus?.(MESSAGE_KEY, undefined);
|
|
1370
|
-
return;
|
|
1371
|
-
}
|
|
1372
|
-
// Replace raw arg with canonical ID for all subsequent operations
|
|
1373
|
-
// (state files, store reads, preflight gates, orchestrator emits).
|
|
1374
|
-
// Issue #20: unprefixed task IDs silently poisoned substitutions.
|
|
1375
|
-
taskId = resolvedTaskId;
|
|
1376
|
-
// Update status with canonical ID so the user sees the resolved form.
|
|
1377
|
-
ctx.ui.setStatus?.(STATUS_KEY, `run-task ${taskId}: ready`);
|
|
1378
|
-
// Tool paths
|
|
1379
|
-
const storeCli = path.join(forgeRoot, "tools", "store-cli.cjs");
|
|
1380
|
-
const preflightGate = path.join(forgeRoot, "tools", "preflight-gate.cjs");
|
|
1381
|
-
// ── Resume detection ─────────────────────────────────────────────
|
|
1382
|
-
const existing = readState(cwd, taskId);
|
|
1383
|
-
let resumeFromState;
|
|
1384
|
-
if (existing) {
|
|
1385
|
-
if (isStateStale(existing)) {
|
|
1386
|
-
// Stale state: notify + offer purge
|
|
1387
|
-
ctx.ui.notify(`⚠ forge:run-task — cached state for ${taskId} is stale (>7 days old, saved at ${formatLocalTime(existing.savedAt)}). ` +
|
|
1388
|
-
"Offering purge.", "warning");
|
|
1389
|
-
if (!isNonInteractive()) {
|
|
1390
|
-
const purge = await ctx.ui.confirm(`Purge stale state for ${taskId}?`, "The cached state is older than 7 days. Purge and restart from the beginning?");
|
|
1391
|
-
if (purge) {
|
|
1392
|
-
deleteState(cwd, taskId);
|
|
1393
|
-
}
|
|
1394
|
-
else {
|
|
1395
|
-
ctx.ui.notify("forge:run-task — stale state kept; aborting.", "info");
|
|
1396
|
-
ctx.ui.setStatus?.(STATUS_KEY, undefined);
|
|
1397
|
-
ctx.ui.setStatus?.(MESSAGE_KEY, undefined);
|
|
1398
|
-
return;
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
else {
|
|
1402
|
-
// Non-interactive: auto-abort on stale state
|
|
1403
|
-
ctx.ui.notify("forge:run-task — stale state; non-interactive mode auto-aborting.", "info");
|
|
1404
|
-
ctx.ui.setStatus?.(STATUS_KEY, undefined);
|
|
1405
|
-
ctx.ui.setStatus?.(MESSAGE_KEY, undefined);
|
|
1406
|
-
return;
|
|
1407
|
-
}
|
|
1408
|
-
}
|
|
1409
|
-
else {
|
|
1410
|
-
// Fresh state: offer resume for ALL non-stale states — halted=true
|
|
1411
|
-
// (explicit failure), halted=false (cancelled/interrupted), and
|
|
1412
|
-
// any state with existing.status set (ADR-S21-01).
|
|
1413
|
-
const stateStatus = existing.status ?? (existing.halted ? "halted" : "interrupted");
|
|
1414
|
-
const statusLabel = stateStatus === "cancelled" ? "cancelled" : stateStatus === "halted" ? "halted" : "interrupted";
|
|
1415
|
-
const phaseRole = PHASES[existing.phaseIndex]?.role ?? existing.phaseIndex;
|
|
1416
|
-
if (!isNonInteractive()) {
|
|
1417
|
-
const resume = await ctx.ui.confirm(`Resume ${taskId}?`, `Cached state — phase ${existing.phaseIndex} (${phaseRole}), ${statusLabel}, ` +
|
|
1418
|
-
`saved at ${formatLocalTime(existing.savedAt)}. Resume from here?`);
|
|
1419
|
-
if (resume) {
|
|
1420
|
-
resumeFromState = existing;
|
|
1421
|
-
ctx.ui.notify(`forge:run-task — resuming ${taskId} from phase ${phaseRole} (${statusLabel})`, "info");
|
|
1422
|
-
}
|
|
1423
|
-
else {
|
|
1424
|
-
deleteState(cwd, taskId);
|
|
1425
|
-
}
|
|
1426
|
-
}
|
|
1427
|
-
else {
|
|
1428
|
-
// Non-interactive: auto-resume from state (no confirmation).
|
|
1429
|
-
// Cancelled/interrupted states are valid resume points.
|
|
1430
|
-
resumeFromState = existing;
|
|
1431
|
-
ctx.ui.notify(`forge:run-task — resuming ${taskId} from phase ${phaseRole} (${statusLabel})`, "info");
|
|
1432
|
-
}
|
|
1433
|
-
}
|
|
1434
|
-
}
|
|
1435
|
-
// ── Register session & delegate to pipeline ────────────────────
|
|
1436
|
-
// Session registration MUST happen after all interactive disambiguation
|
|
1437
|
-
// (resolveToCanonicalId, resume confirm) so the chip strip doesn't appear
|
|
1438
|
-
// before the user has confirmed which task they meant — the strip would
|
|
1439
|
-
// steal arrow keys from ctx.ui.select / ctx.ui.confirm dialogs.
|
|
1440
|
-
const registry = getSessionRegistry();
|
|
1441
|
-
registry.startSession(taskId);
|
|
1442
|
-
// Bridge: also register in OrchestratorTree for the dashboard overlay.
|
|
1443
|
-
const tree = getOrchestratorTree();
|
|
1444
|
-
tree.startNode(taskId, { label: taskId, kind: "orchestrator" });
|
|
1445
|
-
const signal = registry.getAbortSignal(taskId);
|
|
1446
|
-
const pipelineResult = await runTaskPipeline({
|
|
1447
|
-
taskId,
|
|
1448
|
-
cwd,
|
|
1449
|
-
ctx,
|
|
1450
|
-
forgeRoot,
|
|
1451
|
-
storeCli,
|
|
1452
|
-
preflightGate,
|
|
1453
|
-
registry,
|
|
1454
|
-
resumeFromState,
|
|
1455
|
-
signal,
|
|
1456
|
-
forgeToolDefs: options.forgeToolDefs,
|
|
1457
|
-
extensionFactories: options.extensionFactories,
|
|
1458
|
-
});
|
|
1459
|
-
// ── Handle result ────────────────────────────────────────────────
|
|
1460
|
-
if (pipelineResult.status === "completed") {
|
|
1461
|
-
registry.completeSession(taskId, "completed");
|
|
1462
|
-
tree.completeNode(taskId, "completed");
|
|
1463
|
-
ctx.ui.notify(`〇 forge:run-task — ${taskId} pipeline complete (${PHASES.length} phases).`, "info");
|
|
1464
|
-
}
|
|
1465
|
-
else if (pipelineResult.status === "cancelled") {
|
|
1466
|
-
// confirmCancelled was already called by the pipeline, but
|
|
1467
|
-
// completeSession("cancelled") ensures the session ends cleanly.
|
|
1468
|
-
registry.completeSession(taskId, "cancelled");
|
|
1469
|
-
tree.completeNode(taskId, "cancelled");
|
|
1470
|
-
}
|
|
1471
|
-
else {
|
|
1472
|
-
registry.completeSession(taskId, "failed");
|
|
1473
|
-
tree.completeNode(taskId, "failed");
|
|
1474
|
-
}
|
|
1475
|
-
// Mirror this run into the central transcript archive (best-effort —
|
|
1476
|
-
// archiveRun never throws). sprintId back-reference from the task
|
|
1477
|
-
// record; list/timeline group on it (no synthetic sprint container).
|
|
1478
|
-
if (pipelineResult.orchestratorTranscriptPath) {
|
|
1479
|
-
const sprintIdForArchive = readTaskRecord(taskId, storeCli, cwd)?.sprintId;
|
|
1480
|
-
archiveRun({
|
|
1481
|
-
cwd,
|
|
1482
|
-
orchestratorJsonlPath: pipelineResult.orchestratorTranscriptPath,
|
|
1483
|
-
...(sprintIdForArchive ? { sprintId: sprintIdForArchive } : {}),
|
|
1484
|
-
});
|
|
1485
|
-
}
|
|
1486
|
-
ctx.ui.setStatus?.(STATUS_KEY, undefined);
|
|
1487
|
-
ctx.ui.setStatus?.(MESSAGE_KEY, undefined);
|
|
1488
|
-
},
|
|
1489
|
-
});
|
|
1490
|
-
}
|
|
1491
41
|
//# sourceMappingURL=run-task.js.map
|