@entelligentsia/forgecli 0.21.0 → 1.0.3
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 +44 -0
- package/README.md +31 -33
- package/dist/CHANGELOG-forge-plugin.md +142 -0
- package/dist/CHANGELOG-pi.md +24 -1
- package/dist/bin/forgecli.d.ts +2 -0
- package/dist/bin/forgecli.js +6 -0
- package/dist/bin/forgecli.js.map +1 -0
- package/dist/extensions/forgecli/add-pipeline.js +1 -1
- package/dist/extensions/forgecli/add-pipeline.js.map +1 -1
- package/dist/extensions/forgecli/add-task.js +1 -1
- package/dist/extensions/forgecli/add-task.js.map +1 -1
- package/dist/extensions/forgecli/approve.js +17 -2
- package/dist/extensions/forgecli/approve.js.map +1 -1
- package/dist/extensions/forgecli/audience-gate.js +1 -1
- package/dist/extensions/forgecli/audience-gate.js.map +1 -1
- package/dist/extensions/forgecli/calibrate.js +11 -8
- package/dist/extensions/forgecli/calibrate.js.map +1 -1
- package/dist/extensions/forgecli/collate.js +1 -1
- package/dist/extensions/forgecli/collate.js.map +1 -1
- package/dist/extensions/forgecli/commit.js +17 -2
- package/dist/extensions/forgecli/commit.js.map +1 -1
- package/dist/extensions/forgecli/enhance.js +1 -1
- package/dist/extensions/forgecli/enhance.js.map +1 -1
- package/dist/extensions/forgecli/fix-bug.d.ts +1 -2
- package/dist/extensions/forgecli/fix-bug.js +678 -609
- package/dist/extensions/forgecli/fix-bug.js.map +1 -1
- package/dist/extensions/forgecli/forge-artifact-tool.d.ts +6 -8
- package/dist/extensions/forgecli/forge-artifact-tool.js +94 -197
- package/dist/extensions/forgecli/forge-artifact-tool.js.map +1 -1
- package/dist/extensions/forgecli/forge-commands.js +57 -18
- package/dist/extensions/forgecli/forge-commands.js.map +1 -1
- package/dist/extensions/forgecli/forge-init/phase4-register.js +6 -7
- package/dist/extensions/forgecli/forge-init/phase4-register.js.map +1 -1
- package/dist/extensions/forgecli/forge-init/run-phases.d.ts +4 -0
- package/dist/extensions/forgecli/forge-init/run-phases.js +304 -0
- package/dist/extensions/forgecli/forge-init/run-phases.js.map +1 -0
- package/dist/extensions/forgecli/forge-init/verifiers.d.ts +14 -5
- package/dist/extensions/forgecli/forge-init/verifiers.js +79 -62
- package/dist/extensions/forgecli/forge-init/verifiers.js.map +1 -1
- package/dist/extensions/forgecli/forge-init.js +131 -76
- package/dist/extensions/forgecli/forge-init.js.map +1 -1
- package/dist/extensions/forgecli/forge-subagent.d.ts +26 -0
- package/dist/extensions/forgecli/forge-subagent.js +42 -18
- package/dist/extensions/forgecli/forge-subagent.js.map +1 -1
- package/dist/extensions/forgecli/forge-tools.d.ts +34 -4
- package/dist/extensions/forgecli/forge-tools.js +191 -79
- package/dist/extensions/forgecli/forge-tools.js.map +1 -1
- package/dist/extensions/forgecli/health-check.js +3 -3
- package/dist/extensions/forgecli/health-check.js.map +1 -1
- package/dist/extensions/forgecli/hook-dispatcher.d.ts +1 -1
- package/dist/extensions/forgecli/hooks/check-update.d.ts +8 -0
- package/dist/extensions/forgecli/hooks/check-update.js +29 -1
- package/dist/extensions/forgecli/hooks/check-update.js.map +1 -1
- package/dist/extensions/forgecli/hooks/post-init-hook.js +6 -6
- package/dist/extensions/forgecli/hooks/post-init-hook.js.map +1 -1
- package/dist/extensions/forgecli/hooks/post-sprint-hook.js +6 -6
- package/dist/extensions/forgecli/hooks/post-sprint-hook.js.map +1 -1
- package/dist/extensions/forgecli/hooks/triage-error.js +1 -0
- package/dist/extensions/forgecli/hooks/triage-error.js.map +1 -1
- package/dist/extensions/forgecli/implement.js +20 -2
- package/dist/extensions/forgecli/implement.js.map +1 -1
- package/dist/extensions/forgecli/index.js +39 -32
- package/dist/extensions/forgecli/index.js.map +1 -1
- package/dist/extensions/forgecli/lib/pipeline-guard.d.ts +41 -0
- package/dist/extensions/forgecli/lib/pipeline-guard.js +100 -0
- package/dist/extensions/forgecli/lib/pipeline-guard.js.map +1 -0
- package/dist/extensions/forgecli/loaders/persona-skill-loader.js +2 -2
- package/dist/extensions/forgecli/loaders/persona-skill-loader.js.map +1 -1
- package/dist/extensions/forgecli/migrate.d.ts +3 -0
- package/dist/extensions/forgecli/migrate.js +4 -2
- package/dist/extensions/forgecli/migrate.js.map +1 -1
- package/dist/extensions/forgecli/plan.js +21 -2
- package/dist/extensions/forgecli/plan.js.map +1 -1
- package/dist/extensions/forgecli/quiz-agent.js +7 -7
- package/dist/extensions/forgecli/quiz-agent.js.map +1 -1
- package/dist/extensions/forgecli/regenerate.js +49 -18
- package/dist/extensions/forgecli/regenerate.js.map +1 -1
- package/dist/extensions/forgecli/remove-command.js +1 -1
- package/dist/extensions/forgecli/remove-command.js.map +1 -1
- package/dist/extensions/forgecli/report-bug.js +1 -1
- package/dist/extensions/forgecli/report-bug.js.map +1 -1
- package/dist/extensions/forgecli/retrospective.js +9 -9
- package/dist/extensions/forgecli/retrospective.js.map +1 -1
- package/dist/extensions/forgecli/review-code.d.ts +13 -0
- package/dist/extensions/forgecli/review-code.js +62 -3
- package/dist/extensions/forgecli/review-code.js.map +1 -1
- package/dist/extensions/forgecli/review-plan.d.ts +13 -0
- package/dist/extensions/forgecli/review-plan.js +65 -3
- package/dist/extensions/forgecli/review-plan.js.map +1 -1
- package/dist/extensions/forgecli/run-task.js +461 -391
- package/dist/extensions/forgecli/run-task.js.map +1 -1
- package/dist/extensions/forgecli/session-registry.d.ts +12 -0
- package/dist/extensions/forgecli/session-registry.js +23 -0
- package/dist/extensions/forgecli/session-registry.js.map +1 -1
- package/dist/extensions/forgecli/skill-curator-subagent.d.ts +2 -1
- package/dist/extensions/forgecli/skill-curator-subagent.js +2 -1
- package/dist/extensions/forgecli/skill-curator-subagent.js.map +1 -1
- package/dist/extensions/forgecli/sprint-intake.js +6 -6
- package/dist/extensions/forgecli/sprint-intake.js.map +1 -1
- package/dist/extensions/forgecli/sprint-plan.js +9 -9
- package/dist/extensions/forgecli/sprint-plan.js.map +1 -1
- package/dist/extensions/forgecli/status-command.js +1 -1
- package/dist/extensions/forgecli/status-command.js.map +1 -1
- package/dist/extensions/forgecli/store-query.js +11 -11
- package/dist/extensions/forgecli/store-query.js.map +1 -1
- package/dist/extensions/forgecli/store-repair.js +7 -7
- package/dist/extensions/forgecli/store-repair.js.map +1 -1
- package/dist/extensions/forgecli/subagent/caller-context.d.ts +35 -11
- package/dist/extensions/forgecli/subagent/caller-context.js +49 -21
- package/dist/extensions/forgecli/subagent/caller-context.js.map +1 -1
- package/dist/extensions/forgecli/subagent/orchestrator-transcript.d.ts +66 -0
- package/dist/extensions/forgecli/subagent/orchestrator-transcript.js +66 -0
- package/dist/extensions/forgecli/subagent/orchestrator-transcript.js.map +1 -0
- package/dist/extensions/forgecli/subagent/phase-guard.d.ts +34 -0
- package/dist/extensions/forgecli/subagent/phase-guard.js +139 -0
- package/dist/extensions/forgecli/subagent/phase-guard.js.map +1 -0
- package/dist/extensions/forgecli/subagent/phase-summary-map.d.ts +1 -0
- package/dist/extensions/forgecli/subagent/phase-summary-map.js +22 -0
- package/dist/extensions/forgecli/subagent/phase-summary-map.js.map +1 -0
- package/dist/extensions/forgecli/thread-switcher.js +2 -2
- package/dist/extensions/forgecli/thread-switcher.js.map +1 -1
- package/dist/extensions/forgecli/validate.js +17 -2
- package/dist/extensions/forgecli/validate.js.map +1 -1
- package/dist/extensions/forgecli/viewport-events.d.ts +4 -0
- package/dist/extensions/forgecli/viewport-events.js +18 -1
- package/dist/extensions/forgecli/viewport-events.js.map +1 -1
- package/dist/extensions/forgecli/viewport-renderer.d.ts +12 -2
- package/dist/extensions/forgecli/viewport-renderer.js +8 -6
- package/dist/extensions/forgecli/viewport-renderer.js.map +1 -1
- package/dist/forge-payload/.base-pack/commands/check-agent.md +22 -0
- package/dist/forge-payload/.base-pack/commands/new-sprint.md +22 -0
- package/dist/forge-payload/.base-pack/commands/plan-sprint.md +22 -0
- package/dist/forge-payload/.base-pack/commands/quiz-agent.md +2 -18
- package/dist/forge-payload/.base-pack/commands/retro.md +22 -0
- package/dist/forge-payload/.base-pack/commands/retrospective.md +2 -18
- package/dist/forge-payload/.base-pack/commands/sprint-intake.md +2 -18
- package/dist/forge-payload/.base-pack/commands/sprint-plan.md +2 -18
- package/dist/forge-payload/.base-pack/workflows/_fragments/friction-emit.md +2 -2
- package/dist/forge-payload/.base-pack/workflows/_fragments/generation-instructions.md +4 -4
- package/dist/forge-payload/.base-pack/workflows/_fragments/iron-laws.md +1 -1
- package/dist/forge-payload/.base-pack/workflows/architect_approve.md +13 -1
- package/dist/forge-payload/.base-pack/workflows/commit_task.md +12 -1
- package/dist/forge-payload/.base-pack/workflows/enhance.md +6 -6
- package/dist/forge-payload/.base-pack/workflows/fix_bug.md +11 -29
- package/dist/forge-payload/.base-pack/workflows/implement_plan.md +14 -2
- package/dist/forge-payload/.base-pack/workflows/migrate_structural.md +2 -2
- package/dist/forge-payload/.base-pack/workflows/orchestrate_task.md +20 -4
- package/dist/forge-payload/.base-pack/workflows/plan_task.md +14 -2
- package/dist/forge-payload/.base-pack/workflows/review_code.md +37 -7
- package/dist/forge-payload/.base-pack/workflows/review_plan.md +36 -6
- package/dist/forge-payload/.base-pack/workflows/run_sprint.md +2 -2
- package/dist/forge-payload/.base-pack/workflows/triage.md +190 -0
- package/dist/forge-payload/.base-pack/workflows/validate_task.md +37 -7
- package/dist/forge-payload/.claude-plugin/plugin.json +1 -1
- package/dist/forge-payload/.schemas/config.schema.json +0 -5
- package/dist/forge-payload/.schemas/enum-catalog.json +9 -13
- package/dist/forge-payload/.schemas/migrations.json +63 -0
- package/dist/forge-payload/agents/tomoshibi.md +150 -6
- package/dist/forge-payload/commands/add-pipeline.md +1 -1
- package/dist/forge-payload/commands/add-task.md +1 -1
- package/dist/forge-payload/commands/calibrate.md +4 -350
- package/dist/forge-payload/commands/check-agent.md +38 -0
- package/dist/forge-payload/commands/config.md +3 -113
- package/dist/forge-payload/commands/enhance.md +5 -32
- package/dist/forge-payload/commands/health.md +155 -13
- package/dist/forge-payload/commands/init.md +25 -31
- package/dist/forge-payload/commands/migrate.md +6 -154
- package/dist/forge-payload/commands/quiz-agent.md +2 -34
- package/dist/forge-payload/commands/rebuild.md +664 -0
- package/dist/forge-payload/commands/regenerate.md +2 -774
- package/dist/forge-payload/commands/remove.md +10 -13
- package/dist/forge-payload/commands/repair.md +187 -0
- package/dist/forge-payload/commands/search.md +73 -0
- package/dist/forge-payload/commands/status.md +105 -0
- package/dist/forge-payload/commands/store-query.md +2 -69
- package/dist/forge-payload/commands/store-repair.md +2 -183
- package/dist/forge-payload/commands/update-tools.md +4 -50
- package/dist/forge-payload/commands/update.md +64 -58
- package/dist/forge-payload/hooks/check-update.cjs +1 -1
- package/dist/forge-payload/hooks/post-init.cjs +2 -2
- package/dist/forge-payload/hooks/post-sprint.cjs +2 -2
- package/dist/forge-payload/hooks/triage-error.cjs +11 -10
- package/dist/forge-payload/init/phases/phase-1-collect.md +138 -0
- package/dist/forge-payload/init/phases/phase-2-discover.md +127 -0
- package/dist/forge-payload/init/phases/phase-3-materialize.md +113 -0
- package/dist/forge-payload/init/phases/phase-4-register.md +159 -0
- package/dist/forge-payload/integrity.json +21 -24
- package/dist/forge-payload/meta/fragments/tool-discipline.md +41 -0
- package/dist/forge-payload/meta/templates/meta-retro.md +28 -0
- package/dist/forge-payload/meta/workflows/_fragments/friction-emit.md +2 -2
- package/dist/forge-payload/meta/workflows/_fragments/generation-instructions.md +4 -4
- package/dist/forge-payload/meta/workflows/_fragments/iron-laws.md +1 -1
- package/dist/forge-payload/meta/workflows/meta-approve.md +13 -1
- package/dist/forge-payload/meta/workflows/meta-bug-triage.md +210 -0
- package/dist/forge-payload/meta/workflows/meta-check-agent.md +138 -0
- package/dist/forge-payload/meta/workflows/meta-commit.md +12 -1
- package/dist/forge-payload/meta/workflows/meta-enhance.md +5 -5
- package/dist/forge-payload/meta/workflows/meta-fix-bug.md +11 -29
- package/dist/forge-payload/meta/workflows/meta-implement.md +13 -1
- package/dist/forge-payload/meta/workflows/meta-migrate.md +3 -3
- package/dist/forge-payload/meta/workflows/meta-new-sprint.md +84 -0
- package/dist/forge-payload/meta/workflows/meta-orchestrate.md +20 -4
- package/dist/forge-payload/meta/workflows/meta-plan-sprint.md +152 -0
- package/dist/forge-payload/meta/workflows/meta-plan-task.md +13 -1
- package/dist/forge-payload/meta/workflows/meta-retro.md +73 -0
- package/dist/forge-payload/meta/workflows/meta-review-implementation.md +37 -7
- package/dist/forge-payload/meta/workflows/meta-review-plan.md +36 -6
- package/dist/forge-payload/meta/workflows/meta-validate.md +37 -7
- package/dist/forge-payload/schemas/config.schema.json +0 -5
- package/dist/forge-payload/schemas/enum-catalog.json +9 -13
- package/dist/forge-payload/schemas/structure-manifest.json +25 -8
- package/dist/forge-payload/tools/artifact.cjs +324 -0
- package/dist/forge-payload/tools/banners.cjs +3 -4
- package/dist/forge-payload/tools/build-context-pack.cjs +1 -1
- package/dist/forge-payload/tools/check-structure.cjs +8 -3
- package/dist/forge-payload/tools/store-cli.cjs +67 -7
- package/dist/forge-payload/tools/substitute-placeholders.cjs +1 -1
- package/dist/forge-payload/tools/verify-apply.cjs +75 -0
- package/dist/forge-payload/tools/verify-phase.cjs +259 -0
- package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +0 -17
- 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 +21 -38
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.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 +5 -4
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts +2 -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 +5 -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 +24 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/file-processor.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/file-processor.js +2 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/file-processor.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/output-guard.d.ts +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/output-guard.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/output-guard.js +52 -22
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/output-guard.js.map +1 -1
- 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 +16 -4
- 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/tools/bash.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.js +45 -50
- 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/edit.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.js +43 -81
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/file-mutation-queue.js +27 -12
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/file-mutation-queue.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 +2 -3
- 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 +3 -3
- 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 +5 -5
- 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/path-utils.d.ts +2 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/path-utils.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/path-utils.js +37 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/path-utils.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 +9 -8
- 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 +20 -35
- 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/modes/rpc/rpc-client.d.ts +3 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-client.js +64 -7
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-client.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 +15 -3
- 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/utils/clipboard-native.d.ts +3 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/clipboard-native.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/clipboard-native.js +14 -8
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/clipboard-native.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-resize-core.d.ts +30 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-resize-core.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-resize-core.js +124 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-resize-core.js.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-resize-worker.d.ts +2 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-resize-worker.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-resize-worker.js +31 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-resize-worker.js.map +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-resize.d.ts +7 -27
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-resize.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-resize.js +75 -115
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-resize.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/docs/terminal-setup.md +6 -0
- 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/sandbox/package.json +1 -1
- 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 -14
- package/node_modules/@earendil-works/pi-coding-agent/package.json +5 -5
- package/node_modules/@earendil-works/pi-tui/dist/native-modifiers.d.ts +3 -0
- package/node_modules/@earendil-works/pi-tui/dist/native-modifiers.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-tui/dist/native-modifiers.js +53 -0
- package/node_modules/@earendil-works/pi-tui/dist/native-modifiers.js.map +1 -0
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js +4 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts +2 -0
- package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js +13 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-modifiers.node +0 -0
- package/node_modules/@earendil-works/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
- package/node_modules/@earendil-works/pi-tui/package.json +2 -2
- package/node_modules/@entelligentsia/forge-compress/dist/compressor.d.ts +6 -0
- package/node_modules/@entelligentsia/forge-compress/dist/compressor.js +137 -0
- package/node_modules/@entelligentsia/forge-compress/dist/entropy.d.ts +3 -0
- package/node_modules/@entelligentsia/forge-compress/dist/entropy.js +99 -0
- package/node_modules/@entelligentsia/forge-compress/dist/forge/entity.d.ts +8 -0
- package/node_modules/@entelligentsia/forge-compress/dist/forge/entity.js +149 -0
- package/node_modules/@entelligentsia/forge-compress/dist/forge/index.d.ts +7 -0
- package/node_modules/@entelligentsia/forge-compress/dist/forge/index.js +4 -0
- package/node_modules/@entelligentsia/forge-compress/dist/forge/markdown.d.ts +5 -0
- package/node_modules/@entelligentsia/forge-compress/dist/forge/markdown.js +92 -0
- package/node_modules/@entelligentsia/forge-compress/dist/forge/query.d.ts +7 -0
- package/node_modules/@entelligentsia/forge-compress/dist/forge/query.js +60 -0
- package/node_modules/@entelligentsia/forge-compress/dist/forge/validate.d.ts +1 -0
- package/node_modules/@entelligentsia/forge-compress/dist/forge/validate.js +82 -0
- package/node_modules/@entelligentsia/forge-compress/dist/index.d.ts +6 -0
- package/node_modules/@entelligentsia/forge-compress/dist/index.js +5 -0
- package/node_modules/@entelligentsia/forge-compress/dist/progressive.d.ts +1 -0
- package/node_modules/@entelligentsia/forge-compress/dist/progressive.js +108 -0
- package/node_modules/@entelligentsia/forge-compress/dist/strip.d.ts +4 -0
- package/node_modules/@entelligentsia/forge-compress/dist/strip.js +55 -0
- package/node_modules/@entelligentsia/forge-compress/dist/tokens.d.ts +2 -0
- package/node_modules/@entelligentsia/forge-compress/dist/tokens.js +17 -0
- package/node_modules/@entelligentsia/forge-compress/package.json +45 -0
- package/node_modules/@entelligentsia/forge-compress/src/__tests__/compress.test.ts +409 -0
- package/node_modules/@entelligentsia/forge-compress/src/compressor.ts +147 -0
- package/node_modules/@entelligentsia/forge-compress/src/entropy.ts +105 -0
- package/node_modules/@entelligentsia/forge-compress/src/forge/entity.ts +184 -0
- package/node_modules/@entelligentsia/forge-compress/src/forge/index.ts +10 -0
- package/node_modules/@entelligentsia/forge-compress/src/forge/markdown.ts +122 -0
- package/node_modules/@entelligentsia/forge-compress/src/forge/query.ts +105 -0
- package/node_modules/@entelligentsia/forge-compress/src/forge/validate.ts +86 -0
- package/node_modules/@entelligentsia/forge-compress/src/index.ts +22 -0
- package/node_modules/@entelligentsia/forge-compress/src/progressive.ts +123 -0
- package/node_modules/@entelligentsia/forge-compress/src/strip.ts +58 -0
- package/node_modules/@entelligentsia/forge-compress/src/tokens.ts +19 -0
- package/package.json +10 -15
- package/dist/extensions/forgecli/forge-init/phase-descriptors.d.ts +0 -72
- package/dist/extensions/forgecli/forge-init/phase-descriptors.js +0 -359
- package/dist/extensions/forgecli/forge-init/phase-descriptors.js.map +0 -1
- package/dist/extensions/forgecli/forge-init/prompts.d.ts +0 -10
- package/dist/extensions/forgecli/forge-init/prompts.js +0 -91
- package/dist/extensions/forgecli/forge-init/prompts.js.map +0 -1
- package/dist/extensions/forgecli/lib/store-error-remediation.d.ts +0 -65
- package/dist/extensions/forgecli/lib/store-error-remediation.js +0 -298
- package/dist/extensions/forgecli/lib/store-error-remediation.js.map +0 -1
- package/dist/forge-payload/hooks/check-update.js +0 -378
- package/dist/forge-payload/hooks/forge-permissions.js +0 -164
- package/dist/forge-payload/hooks/triage-error.js +0 -77
- package/dist/forge-payload/hooks/validate-write.js +0 -250
|
@@ -29,6 +29,7 @@ import { isStateStale as isJsonStateStale, readJsonState, taskStateFilePath, wri
|
|
|
29
29
|
import { resolveModelForPhase } from "./model-resolver.js";
|
|
30
30
|
import { loadWorkflow } from "./parsers/workflow-loader.js";
|
|
31
31
|
import { getSessionRegistry } from "./session-registry.js";
|
|
32
|
+
import { OrchestratorTranscriptWriter } from "./subagent/orchestrator-transcript.js";
|
|
32
33
|
import { resolveToCanonicalId, resolveToolDir } from "./store-resolver.js";
|
|
33
34
|
import { attachViewportObserver } from "./viewport-events.js";
|
|
34
35
|
import { fmtPhaseSummary } from "./viewport-renderer.js";
|
|
@@ -420,463 +421,532 @@ export async function runTaskPipeline(opts) {
|
|
|
420
421
|
// on OpenAI. Falls back to a task-scoped key if the task is unattached.
|
|
421
422
|
const taskRecordAtStart = readTaskRecord(taskId, storeCli, cwd);
|
|
422
423
|
const cacheSessionId = taskRecordAtStart?.sprintId ? `forge:${taskRecordAtStart.sprintId}` : `forge:task:${taskId}`;
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
const
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
424
|
+
// ── Orchestrator transcript ──────────────────────────────────────────
|
|
425
|
+
// FORGE-BUG-040 follow-up: one JSONL file per pipeline run, ISO-prefixed
|
|
426
|
+
// in its filename so review-loop iterations preserve their own logs
|
|
427
|
+
// instead of overwriting. Captures every ctx.ui.notify line plus
|
|
428
|
+
// structured phase-boundary events.
|
|
429
|
+
const orchTranscript = new OrchestratorTranscriptWriter({
|
|
430
|
+
cwd,
|
|
431
|
+
entityKind: "task",
|
|
432
|
+
entityId: taskId,
|
|
433
|
+
});
|
|
434
|
+
const __origNotify = ctx.ui.notify.bind(ctx.ui);
|
|
435
|
+
ctx.ui.notify = ((msg, level) => {
|
|
436
|
+
__origNotify(msg, level);
|
|
437
|
+
orchTranscript.record({
|
|
438
|
+
kind: "notify",
|
|
439
|
+
ts: new Date().toISOString(),
|
|
440
|
+
level: (level ?? "info"),
|
|
441
|
+
message: typeof msg === "string" ? msg : String(msg),
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
const pipelineStartMs = Date.now();
|
|
445
|
+
try {
|
|
446
|
+
while (currentPhaseIndex < PHASES.length) {
|
|
447
|
+
// ── Between-phase cancellation gate ────────────────────────────
|
|
448
|
+
if (opts.signal?.aborted) {
|
|
449
|
+
ctx.ui.notify(`⊘ forge:run-task — ${taskId} cancelled by user.`, "info");
|
|
450
|
+
registry.completePhase(taskId, PHASES[currentPhaseIndex]?.role ?? "unknown", "cancelled");
|
|
451
|
+
registry.confirmCancelled(taskId);
|
|
452
|
+
// ADR-S21-01: preserve state file so cancelled runs are resumable
|
|
453
|
+
// from the beginning of the cancelled phase (not deleted).
|
|
454
|
+
writeState(cwd, {
|
|
455
|
+
taskId,
|
|
456
|
+
phaseIndex: currentPhaseIndex,
|
|
457
|
+
iterationCounts,
|
|
458
|
+
halted: false,
|
|
459
|
+
status: "cancelled",
|
|
460
|
+
lastError: undefined,
|
|
461
|
+
savedAt: new Date().toISOString(),
|
|
462
|
+
});
|
|
463
|
+
return { status: "cancelled", lastPhaseIndex: currentPhaseIndex, iterationCounts };
|
|
464
|
+
}
|
|
465
|
+
const phase = PHASES[currentPhaseIndex];
|
|
466
|
+
if (!phase) {
|
|
467
|
+
ctx.ui.notify(`× forge:run-task — invalid phase index ${currentPhaseIndex}`, "error");
|
|
468
|
+
return {
|
|
469
|
+
status: "failed",
|
|
470
|
+
lastPhaseIndex: currentPhaseIndex,
|
|
471
|
+
iterationCounts,
|
|
472
|
+
lastError: `invalid phase index ${currentPhaseIndex}`,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
ctx.ui.setStatus?.(STATUS_KEY, `run-task ${taskId}: phase ${currentPhaseIndex + 1}/${PHASES.length} (${phase.role})`);
|
|
476
|
+
ctx.ui.notify(`→ ${taskId}: ${phase.role} (phase ${currentPhaseIndex + 1}/${PHASES.length})`, "info");
|
|
477
|
+
orchTranscript.record({
|
|
478
|
+
kind: "phase-start",
|
|
479
|
+
ts: new Date().toISOString(),
|
|
480
|
+
phase: phase.role,
|
|
468
481
|
phaseIndex: currentPhaseIndex,
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
482
|
+
phaseCount: PHASES.length,
|
|
483
|
+
attempt: (iterationCounts[phase.role] ?? 0) + 1,
|
|
484
|
+
workflowFile: phase.workflowFile,
|
|
485
|
+
persona: phase.personaNoun,
|
|
473
486
|
});
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
ctx.ui.notify(`× forge:run-task —
|
|
487
|
+
const subWorkflowPath = path.join(cwd, ".forge", "workflows", `${phase.workflowFile}.md`);
|
|
488
|
+
// ── Read sub-workflow ─────────────────────────────────────────
|
|
489
|
+
let subWorkflowMd;
|
|
490
|
+
let subWorkflowAudience = "any";
|
|
491
|
+
try {
|
|
492
|
+
const loaded = loadWorkflow(subWorkflowPath);
|
|
493
|
+
subWorkflowMd = loaded.rawMarkdown;
|
|
494
|
+
subWorkflowAudience = loaded.audience;
|
|
495
|
+
}
|
|
496
|
+
catch (err) {
|
|
497
|
+
const e = err;
|
|
498
|
+
ctx.ui.notify(`× forge:run-task — failed to read sub-workflow for ${phase.role}: ${e.message ?? "unknown"}`, "error");
|
|
486
499
|
writeState(cwd, {
|
|
487
500
|
taskId,
|
|
488
501
|
phaseIndex: currentPhaseIndex,
|
|
489
502
|
iterationCounts,
|
|
490
503
|
halted: true,
|
|
491
|
-
lastError: `
|
|
504
|
+
lastError: `sub-workflow read failed: ${e.message ?? "unknown"}`,
|
|
492
505
|
savedAt: new Date().toISOString(),
|
|
493
506
|
});
|
|
494
507
|
return {
|
|
495
|
-
status: "
|
|
508
|
+
status: "failed",
|
|
509
|
+
lastPhaseIndex: currentPhaseIndex,
|
|
510
|
+
iterationCounts,
|
|
511
|
+
lastError: `sub-workflow read failed: ${e.message ?? "unknown"}`,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
// ── 6a. Preflight gate ────────────────────────────────────────
|
|
515
|
+
if (fs.existsSync(preflightGate)) {
|
|
516
|
+
const preflightResult = runPreflightGate(preflightGate, phase.role, taskId, cwd);
|
|
517
|
+
if (preflightResult === "halt") {
|
|
518
|
+
ctx.ui.notify(`× forge:run-task — preflight gate failed for phase ${phase.role} (exit 1); halting.`, "error");
|
|
519
|
+
writeState(cwd, {
|
|
520
|
+
taskId,
|
|
521
|
+
phaseIndex: currentPhaseIndex,
|
|
522
|
+
iterationCounts,
|
|
523
|
+
halted: true,
|
|
524
|
+
lastError: `preflight gate exit 1 for ${phase.role}`,
|
|
525
|
+
savedAt: new Date().toISOString(),
|
|
526
|
+
});
|
|
527
|
+
return {
|
|
528
|
+
status: "halted",
|
|
529
|
+
lastPhaseIndex: currentPhaseIndex,
|
|
530
|
+
iterationCounts,
|
|
531
|
+
lastError: `preflight gate exit 1 for ${phase.role}`,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
if (preflightResult === "escalate") {
|
|
535
|
+
ctx.ui.notify(`× forge:run-task — preflight gate escalated for phase ${phase.role} (exit 2); manual intervention required.`, "error");
|
|
536
|
+
writeState(cwd, {
|
|
537
|
+
taskId,
|
|
538
|
+
phaseIndex: currentPhaseIndex,
|
|
539
|
+
iterationCounts,
|
|
540
|
+
halted: true,
|
|
541
|
+
lastError: `preflight gate exit 2 (escalate) for ${phase.role}`,
|
|
542
|
+
savedAt: new Date().toISOString(),
|
|
543
|
+
});
|
|
544
|
+
return {
|
|
545
|
+
status: "escalated",
|
|
546
|
+
lastPhaseIndex: currentPhaseIndex,
|
|
547
|
+
iterationCounts,
|
|
548
|
+
lastError: `preflight gate exit 2 (escalate) for ${phase.role}`,
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
// ── 6. Materialization-marker check ───────────────────────────
|
|
553
|
+
const markerCheck = checkMaterialization(subWorkflowPath, subWorkflowMd);
|
|
554
|
+
if (!markerCheck.ok) {
|
|
555
|
+
for (const marker of markerCheck.missing) {
|
|
556
|
+
ctx.ui.notify(`× workflow regression: ${marker} not found in ${subWorkflowPath}`, "error");
|
|
557
|
+
}
|
|
558
|
+
return {
|
|
559
|
+
status: "failed",
|
|
496
560
|
lastPhaseIndex: currentPhaseIndex,
|
|
497
561
|
iterationCounts,
|
|
498
|
-
lastError: `
|
|
562
|
+
lastError: `materialization markers missing: ${markerCheck.missing.join(", ")}`,
|
|
499
563
|
};
|
|
500
564
|
}
|
|
501
|
-
|
|
502
|
-
|
|
565
|
+
// ── 5. Audience check ─────────────────────────────────────────
|
|
566
|
+
// Wrap with CallerContextStore.asSubagent so assertAudience treats
|
|
567
|
+
// this as a subagent context (IL10: we ARE dispatching from subagent chain).
|
|
568
|
+
const audienceOk = CallerContextStore.asSubagent(phase.role, () => assertAudience({ workflowName: phase.workflowFile, audience: subWorkflowAudience }, ctx));
|
|
569
|
+
if (!audienceOk) {
|
|
503
570
|
writeState(cwd, {
|
|
504
571
|
taskId,
|
|
505
572
|
phaseIndex: currentPhaseIndex,
|
|
506
573
|
iterationCounts,
|
|
507
574
|
halted: true,
|
|
508
|
-
lastError: `
|
|
575
|
+
lastError: `audience check failed for ${phase.workflowFile}`,
|
|
509
576
|
savedAt: new Date().toISOString(),
|
|
510
577
|
});
|
|
511
578
|
return {
|
|
512
|
-
status: "
|
|
579
|
+
status: "failed",
|
|
513
580
|
lastPhaseIndex: currentPhaseIndex,
|
|
514
581
|
iterationCounts,
|
|
515
|
-
lastError: `
|
|
582
|
+
lastError: `audience check failed for ${phase.workflowFile}`,
|
|
516
583
|
};
|
|
517
584
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
const markerCheck = checkMaterialization(subWorkflowPath, subWorkflowMd);
|
|
521
|
-
if (!markerCheck.ok) {
|
|
522
|
-
for (const marker of markerCheck.missing) {
|
|
523
|
-
ctx.ui.notify(`× workflow regression: ${marker} not found in ${subWorkflowPath}`, "error");
|
|
524
|
-
}
|
|
525
|
-
return {
|
|
526
|
-
status: "failed",
|
|
527
|
-
lastPhaseIndex: currentPhaseIndex,
|
|
528
|
-
iterationCounts,
|
|
529
|
-
lastError: `materialization markers missing: ${markerCheck.missing.join(", ")}`,
|
|
530
|
-
};
|
|
531
|
-
}
|
|
532
|
-
// ── 5. Audience check ─────────────────────────────────────────
|
|
533
|
-
// Wrap with CallerContextStore.asSubagent so assertAudience treats
|
|
534
|
-
// this as a subagent context (IL10: we ARE dispatching from subagent chain).
|
|
535
|
-
const audienceOk = CallerContextStore.asSubagent(() => assertAudience({ workflowName: phase.workflowFile, audience: subWorkflowAudience }, ctx));
|
|
536
|
-
if (!audienceOk) {
|
|
537
|
-
writeState(cwd, {
|
|
538
|
-
taskId,
|
|
539
|
-
phaseIndex: currentPhaseIndex,
|
|
540
|
-
iterationCounts,
|
|
541
|
-
halted: true,
|
|
542
|
-
lastError: `audience check failed for ${phase.workflowFile}`,
|
|
543
|
-
savedAt: new Date().toISOString(),
|
|
544
|
-
});
|
|
545
|
-
return {
|
|
546
|
-
status: "failed",
|
|
547
|
-
lastPhaseIndex: currentPhaseIndex,
|
|
548
|
-
iterationCounts,
|
|
549
|
-
lastError: `audience check failed for ${phase.workflowFile}`,
|
|
550
|
-
};
|
|
551
|
-
}
|
|
552
|
-
// ── Persona load ──────────────────────────────────────────────
|
|
553
|
-
let persona;
|
|
554
|
-
try {
|
|
555
|
-
persona = loadForgePersona(phase.personaNoun, cwd);
|
|
556
|
-
}
|
|
557
|
-
catch (err) {
|
|
558
|
-
const e = err;
|
|
559
|
-
ctx.ui.notify(`× forge:run-task — persona '${phase.personaNoun}' not found for phase ${phase.role}: ${e.message ?? "unknown"}. ` +
|
|
560
|
-
"Run /forge:regenerate to materialize persona files.", "error");
|
|
561
|
-
writeState(cwd, {
|
|
562
|
-
taskId,
|
|
563
|
-
phaseIndex: currentPhaseIndex,
|
|
564
|
-
iterationCounts,
|
|
565
|
-
halted: true,
|
|
566
|
-
lastError: `persona load failed: ${e.message ?? "unknown"}`,
|
|
567
|
-
savedAt: new Date().toISOString(),
|
|
568
|
-
});
|
|
569
|
-
return {
|
|
570
|
-
status: "failed",
|
|
571
|
-
lastPhaseIndex: currentPhaseIndex,
|
|
572
|
-
iterationCounts,
|
|
573
|
-
lastError: `persona load failed: ${e.message ?? "unknown"}`,
|
|
574
|
-
};
|
|
575
|
-
}
|
|
576
|
-
// ── 4. Dispatch via runForgeSubagent (IL10) ───────────────────
|
|
577
|
-
// NEVER sendKickoff here — that would reproduce issue #30 (same-context inline = no fork).
|
|
578
|
-
// Read fresh task record to carry forward prior phase summaries (forge-cli#19).
|
|
579
|
-
const taskRecordForSummaries = currentPhaseIndex > 0 ? readTaskRecord(taskId, storeCli, cwd) : null;
|
|
580
|
-
const summariesBlock = buildSummariesBlock(taskRecordForSummaries?.summaries);
|
|
581
|
-
const taskBody = composeTaskBody(subWorkflowMd, taskId, summariesBlock || undefined);
|
|
582
|
-
// Log whether carry-forward summaries were injected (forge-cli#19).
|
|
583
|
-
if (summariesBlock) {
|
|
584
|
-
const debugCarryPath = path.join(cwd, ".forge", "cache", `run-task-debug-${taskId}.jsonl`);
|
|
585
|
-
try {
|
|
586
|
-
fs.mkdirSync(path.dirname(debugCarryPath), { recursive: true });
|
|
587
|
-
fs.appendFileSync(debugCarryPath, `${JSON.stringify({ ts: new Date().toISOString(), phase: phase.role, kind: "carry_forward_injected", summariesLength: summariesBlock.length, summariesBlock })}\n`, "utf8");
|
|
588
|
-
}
|
|
589
|
-
catch { /* best-effort debug log */ }
|
|
590
|
-
}
|
|
591
|
-
// Resolve per-phase model from layered config (Plan 16 Slice 2).
|
|
592
|
-
// Pipeline name "default" matches the Forge plugin's shipped pipeline.
|
|
593
|
-
// When config is absent or cascade bottoms out, resolves to inherit
|
|
594
|
-
// (model: undefined) — setModel is skipped and pi's current model is used.
|
|
595
|
-
const modelResolution = resolveModelForPhase("default", phase.role, phase.personaNoun, modelRoutingConfig);
|
|
596
|
-
const phaseStart = Date.now();
|
|
597
|
-
// Stabilization debug log — every subagent event appended as JSONL.
|
|
598
|
-
const debugLogPath = path.join(cwd, ".forge", "cache", `run-task-debug-${taskId}.jsonl`);
|
|
599
|
-
const writeDebug = (rec) => {
|
|
585
|
+
// ── Persona load ──────────────────────────────────────────────
|
|
586
|
+
let persona;
|
|
600
587
|
try {
|
|
601
|
-
|
|
602
|
-
fs.appendFileSync(debugLogPath, `${JSON.stringify({ ts: new Date().toISOString(), phase: phase.role, ...rec })}\n`, "utf8");
|
|
588
|
+
persona = loadForgePersona(phase.personaNoun, cwd);
|
|
603
589
|
}
|
|
604
|
-
catch {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
registry.startPhase(taskId, phase.role, currentPhaseIndex);
|
|
616
|
-
// Capture the first stream-observed model on turn_end (IL10 visibility).
|
|
617
|
-
// If pi auto-substitutes or setModel silently no-ops, this line will diverge
|
|
618
|
-
// from requested_model — exactly the diagnostic signal we want.
|
|
619
|
-
let modelObservedLogged = false;
|
|
620
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
621
|
-
const wrappedOnEvent = (event) => {
|
|
622
|
-
if (!modelObservedLogged && event?.type === "turn_end" && typeof event?.message?.model === "string") {
|
|
623
|
-
modelObservedLogged = true;
|
|
624
|
-
writeDebug({
|
|
625
|
-
kind: "model_observed",
|
|
626
|
-
provider: event.message.provider ?? null,
|
|
627
|
-
model: event.message.model,
|
|
590
|
+
catch (err) {
|
|
591
|
+
const e = err;
|
|
592
|
+
ctx.ui.notify(`× forge:run-task — persona '${phase.personaNoun}' not found for phase ${phase.role}: ${e.message ?? "unknown"}. ` +
|
|
593
|
+
"Run /forge:regenerate to materialize persona files.", "error");
|
|
594
|
+
writeState(cwd, {
|
|
595
|
+
taskId,
|
|
596
|
+
phaseIndex: currentPhaseIndex,
|
|
597
|
+
iterationCounts,
|
|
598
|
+
halted: true,
|
|
599
|
+
lastError: `persona load failed: ${e.message ?? "unknown"}`,
|
|
600
|
+
savedAt: new Date().toISOString(),
|
|
628
601
|
});
|
|
602
|
+
return {
|
|
603
|
+
status: "failed",
|
|
604
|
+
lastPhaseIndex: currentPhaseIndex,
|
|
605
|
+
iterationCounts,
|
|
606
|
+
lastError: `persona load failed: ${e.message ?? "unknown"}`,
|
|
607
|
+
};
|
|
629
608
|
}
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
const
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
modelRegistry: ctx.modelRegistry,
|
|
662
|
-
signal: opts.signal,
|
|
663
|
-
customTools: opts.forgeToolDefs ? getSubagentTools(opts.forgeToolDefs, persona.name) : undefined,
|
|
664
|
-
});
|
|
665
|
-
}
|
|
666
|
-
catch (err) {
|
|
667
|
-
const e = err;
|
|
668
|
-
ctx.ui.notify(`× forge:run-task — runForgeSubagent threw for phase ${phase.role}: ${e.message ?? "unknown"}`, "error");
|
|
669
|
-
writeState(cwd, {
|
|
670
|
-
taskId,
|
|
671
|
-
phaseIndex: currentPhaseIndex,
|
|
672
|
-
iterationCounts,
|
|
673
|
-
halted: true,
|
|
674
|
-
lastError: `runForgeSubagent threw: ${e.message ?? "unknown"}`,
|
|
675
|
-
savedAt: new Date().toISOString(),
|
|
676
|
-
});
|
|
677
|
-
return {
|
|
678
|
-
status: "failed",
|
|
679
|
-
lastPhaseIndex: currentPhaseIndex,
|
|
680
|
-
iterationCounts,
|
|
681
|
-
lastError: `runForgeSubagent threw: ${e.message ?? "unknown"}`,
|
|
609
|
+
// ── 4. Dispatch via runForgeSubagent (IL10) ───────────────────
|
|
610
|
+
// NEVER sendKickoff here — that would reproduce issue #30 (same-context inline = no fork).
|
|
611
|
+
// Read fresh task record to carry forward prior phase summaries (forge-cli#19).
|
|
612
|
+
const taskRecordForSummaries = currentPhaseIndex > 0 ? readTaskRecord(taskId, storeCli, cwd) : null;
|
|
613
|
+
const summariesBlock = buildSummariesBlock(taskRecordForSummaries?.summaries);
|
|
614
|
+
const taskBody = composeTaskBody(subWorkflowMd, taskId, summariesBlock || undefined);
|
|
615
|
+
// Log whether carry-forward summaries were injected (forge-cli#19).
|
|
616
|
+
if (summariesBlock) {
|
|
617
|
+
const debugCarryPath = path.join(cwd, ".forge", "cache", `run-task-debug-${taskId}.jsonl`);
|
|
618
|
+
try {
|
|
619
|
+
fs.mkdirSync(path.dirname(debugCarryPath), { recursive: true });
|
|
620
|
+
fs.appendFileSync(debugCarryPath, `${JSON.stringify({ ts: new Date().toISOString(), phase: phase.role, kind: "carry_forward_injected", summariesLength: summariesBlock.length, summariesBlock })}\n`, "utf8");
|
|
621
|
+
}
|
|
622
|
+
catch { /* best-effort debug log */ }
|
|
623
|
+
}
|
|
624
|
+
// Resolve per-phase model from layered config (Plan 16 Slice 2).
|
|
625
|
+
// Pipeline name "default" matches the Forge plugin's shipped pipeline.
|
|
626
|
+
// When config is absent or cascade bottoms out, resolves to inherit
|
|
627
|
+
// (model: undefined) — setModel is skipped and pi's current model is used.
|
|
628
|
+
const modelResolution = resolveModelForPhase("default", phase.role, phase.personaNoun, modelRoutingConfig);
|
|
629
|
+
const phaseStart = Date.now();
|
|
630
|
+
// Stabilization debug log — every subagent event appended as JSONL.
|
|
631
|
+
const debugLogPath = path.join(cwd, ".forge", "cache", `run-task-debug-${taskId}.jsonl`);
|
|
632
|
+
const writeDebug = (rec) => {
|
|
633
|
+
try {
|
|
634
|
+
fs.mkdirSync(path.dirname(debugLogPath), { recursive: true });
|
|
635
|
+
fs.appendFileSync(debugLogPath, `${JSON.stringify({ ts: new Date().toISOString(), phase: phase.role, ...rec })}\n`, "utf8");
|
|
636
|
+
}
|
|
637
|
+
catch {
|
|
638
|
+
// non-fatal; debug log is best-effort
|
|
639
|
+
}
|
|
682
640
|
};
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
// stopReason="aborted" + exitCode=1 is classified as cancellation,
|
|
690
|
-
// not a phase failure.
|
|
691
|
-
if (result.stopReason === "aborted" || opts.signal?.aborted) {
|
|
692
|
-
ctx.ui.notify(`⊘ forge:run-task — ${taskId} phase ${phase.role} cancelled.`, "info");
|
|
693
|
-
registry.completePhase(taskId, phase.role, "cancelled");
|
|
694
|
-
registry.confirmCancelled(taskId);
|
|
695
|
-
// ADR-S21-01: preserve state file so cancelled runs are resumable
|
|
696
|
-
writeState(cwd, {
|
|
697
|
-
taskId,
|
|
698
|
-
phaseIndex: currentPhaseIndex,
|
|
699
|
-
iterationCounts,
|
|
700
|
-
halted: false,
|
|
701
|
-
status: "cancelled",
|
|
702
|
-
lastError: undefined,
|
|
703
|
-
savedAt: new Date().toISOString(),
|
|
704
|
-
});
|
|
705
|
-
return { status: "cancelled", lastPhaseIndex: currentPhaseIndex, iterationCounts };
|
|
706
|
-
}
|
|
707
|
-
// ── Halt-on-failure ───────────────────────────────────────────
|
|
708
|
-
if (result.exitCode !== 0) {
|
|
709
|
-
ctx.ui.notify(`× forge:run-task — phase ${phase.role} failed (exit ${result.exitCode})` +
|
|
710
|
-
(result.errorMessage ? `: ${result.errorMessage}` : "") +
|
|
711
|
-
(result.stopReason ? ` [${result.stopReason}]` : ""), "error");
|
|
712
|
-
writeState(cwd, {
|
|
713
|
-
taskId,
|
|
714
|
-
phaseIndex: currentPhaseIndex,
|
|
715
|
-
iterationCounts,
|
|
716
|
-
halted: true,
|
|
717
|
-
lastError: result.errorMessage ?? result.stopReason ?? "subagent exit non-zero",
|
|
718
|
-
savedAt: new Date().toISOString(),
|
|
641
|
+
writeDebug({ kind: "phase_start", phaseIndex: currentPhaseIndex });
|
|
642
|
+
writeDebug({
|
|
643
|
+
kind: "requested_model",
|
|
644
|
+
requested: modelResolution.model ?? null,
|
|
645
|
+
source: modelResolution.source,
|
|
646
|
+
persona: phase.personaNoun,
|
|
719
647
|
});
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
648
|
+
registry.startPhase(taskId, phase.role, currentPhaseIndex);
|
|
649
|
+
// Capture the first stream-observed model on turn_end (IL10 visibility).
|
|
650
|
+
// If pi auto-substitutes or setModel silently no-ops, this line will diverge
|
|
651
|
+
// from requested_model — exactly the diagnostic signal we want.
|
|
652
|
+
let modelObservedLogged = false;
|
|
653
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
654
|
+
const wrappedOnEvent = (event) => {
|
|
655
|
+
if (!modelObservedLogged && event?.type === "turn_end" && typeof event?.message?.model === "string") {
|
|
656
|
+
modelObservedLogged = true;
|
|
657
|
+
writeDebug({
|
|
658
|
+
kind: "model_observed",
|
|
659
|
+
provider: event.message.provider ?? null,
|
|
660
|
+
model: event.message.model,
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
observer.onEvent(event);
|
|
725
664
|
};
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
// Phase-complete liveliness ping (counts + duration).
|
|
733
|
-
{
|
|
734
|
-
const elapsed = Math.floor((Date.now() - phaseStart) / 1000);
|
|
735
|
-
const { turn, toolCount, errCount, cumUsage } = observer.state;
|
|
736
|
-
ctx.ui.notify(`✓ ${phase.role}: ${turn} turn${turn === 1 ? "" : "s"} · ${toolCount} tool call${toolCount === 1 ? "" : "s"}${errCount ? ` · ${errCount} err` : ""} · ${elapsed}s`, "info");
|
|
737
|
-
registry.appendTail(taskId, phase.role, fmtPhaseSummary({
|
|
738
|
-
role: phase.role,
|
|
739
|
-
turns: turn,
|
|
740
|
-
tools: toolCount,
|
|
741
|
-
errors: errCount,
|
|
742
|
-
wallSeconds: elapsed,
|
|
743
|
-
usage: cumUsage,
|
|
744
|
-
model: result.model,
|
|
745
|
-
provider: result.provider,
|
|
746
|
-
}));
|
|
747
|
-
}
|
|
748
|
-
// ── Plan 11 / Slice 2: orchestrator emits phase event ─────────
|
|
749
|
-
const phaseEndMs = Date.now();
|
|
750
|
-
const taskRecord = readTaskRecord(taskId, storeCli, cwd);
|
|
751
|
-
const sprintId = taskRecord?.sprintId;
|
|
752
|
-
if (!sprintId) {
|
|
753
|
-
ctx.ui.notify(`⚠ forge:run-task — could not resolve sprintId for ${taskId}; ` +
|
|
754
|
-
`skipping orchestrator emit for phase ${phase.role}`, "warning");
|
|
755
|
-
writeDebug({ kind: "emit_skipped", reason: "no-sprintId" });
|
|
756
|
-
}
|
|
757
|
-
else {
|
|
758
|
-
const phaseIteration = (iterationCounts[phase.role] ?? 0) + 1;
|
|
759
|
-
const emitCtx = {
|
|
760
|
-
entityType: "task",
|
|
761
|
-
taskId,
|
|
762
|
-
sprintId,
|
|
763
|
-
phase,
|
|
764
|
-
iteration: phaseIteration,
|
|
765
|
-
startMs: phaseStart,
|
|
766
|
-
endMs: phaseEndMs,
|
|
767
|
-
model: result.model ?? "unknown",
|
|
768
|
-
provider: result.provider ?? "unknown",
|
|
769
|
-
usage: {
|
|
770
|
-
input: result.usage.input,
|
|
771
|
-
output: result.usage.output,
|
|
772
|
-
cacheRead: result.usage.cacheRead,
|
|
773
|
-
cacheWrite: result.usage.cacheWrite,
|
|
774
|
-
},
|
|
775
|
-
judgement: judgementFromSummary(taskRecord, phase.role),
|
|
776
|
-
storeCli,
|
|
777
|
-
cwd,
|
|
665
|
+
const refreshStatus = () => {
|
|
666
|
+
if (process.env.FORGE_VERBOSE !== "1")
|
|
667
|
+
return;
|
|
668
|
+
const elapsed = Math.floor((Date.now() - phaseStart) / 1000);
|
|
669
|
+
const tail = observer.state.lastTool ? ` · ${observer.state.lastTool}` : "";
|
|
670
|
+
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}`);
|
|
778
671
|
};
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
672
|
+
const observer = attachViewportObserver({
|
|
673
|
+
registry,
|
|
674
|
+
sessionId: taskId,
|
|
675
|
+
phaseRole: phase.role,
|
|
676
|
+
beginHeader: `─── phase ${currentPhaseIndex + 1}/${PHASES.length} ${phase.role} begin · ${taskId} ───`,
|
|
677
|
+
writeDebug,
|
|
678
|
+
notify: (msg, level) => ctx.ui.notify(msg, level),
|
|
679
|
+
setStatusVerbose: process.env.FORGE_VERBOSE === "1" ? (k, v) => ctx.ui.setStatus?.(k, v) : undefined,
|
|
680
|
+
verboseKeys: { messageKey: MESSAGE_KEY },
|
|
681
|
+
afterEach: refreshStatus,
|
|
682
|
+
});
|
|
683
|
+
let result;
|
|
684
|
+
try {
|
|
685
|
+
// FORGE-BUG-040: wrap the runForgeSubagent dispatch in the phase
|
|
686
|
+
// caller context (parity with fix-bug.ts) so the phase-ownership
|
|
687
|
+
// guard can verify tool calls from the subagent. Single setter
|
|
688
|
+
// of phase context for the task pipeline.
|
|
689
|
+
result = await CallerContextStore.asSubagent(phase.role, () => runForgeSubagent({
|
|
690
|
+
persona,
|
|
691
|
+
task: taskBody,
|
|
692
|
+
cwd,
|
|
693
|
+
exportTag: `${taskId}__${phase.role}`,
|
|
694
|
+
cacheSessionId,
|
|
695
|
+
streamFn: opts.streamFnFactory?.({ kind: "task-phase", persona: persona.name, phase: phase.role, taskId }),
|
|
696
|
+
onEvent: wrappedOnEvent,
|
|
697
|
+
requestedModel: modelResolution.model,
|
|
698
|
+
modelRegistry: ctx.modelRegistry,
|
|
699
|
+
signal: opts.signal,
|
|
700
|
+
customTools: opts.forgeToolDefs ? getSubagentTools(opts.forgeToolDefs, persona.name) : undefined,
|
|
701
|
+
}));
|
|
784
702
|
}
|
|
785
|
-
|
|
786
|
-
|
|
703
|
+
catch (err) {
|
|
704
|
+
const e = err;
|
|
705
|
+
ctx.ui.notify(`× forge:run-task — runForgeSubagent threw for phase ${phase.role}: ${e.message ?? "unknown"}`, "error");
|
|
706
|
+
writeState(cwd, {
|
|
707
|
+
taskId,
|
|
708
|
+
phaseIndex: currentPhaseIndex,
|
|
709
|
+
iterationCounts,
|
|
710
|
+
halted: true,
|
|
711
|
+
lastError: `runForgeSubagent threw: ${e.message ?? "unknown"}`,
|
|
712
|
+
savedAt: new Date().toISOString(),
|
|
713
|
+
});
|
|
714
|
+
return {
|
|
715
|
+
status: "failed",
|
|
716
|
+
lastPhaseIndex: currentPhaseIndex,
|
|
717
|
+
iterationCounts,
|
|
718
|
+
lastError: `runForgeSubagent threw: ${e.message ?? "unknown"}`,
|
|
719
|
+
};
|
|
787
720
|
}
|
|
788
|
-
//
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
//
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
721
|
+
// ── Post-subagent abort detection ─────────────────────────────────
|
|
722
|
+
// If the abort signal fired during the subagent run, treat it as
|
|
723
|
+
// cancellation regardless of the exit code (subagent may have been
|
|
724
|
+
// mid-turn when aborted — exitCode could be 0 or 1).
|
|
725
|
+
// This check MUST come before halt-on-failure so that
|
|
726
|
+
// stopReason="aborted" + exitCode=1 is classified as cancellation,
|
|
727
|
+
// not a phase failure.
|
|
728
|
+
if (result.stopReason === "aborted" || opts.signal?.aborted) {
|
|
729
|
+
ctx.ui.notify(`⊘ forge:run-task — ${taskId} phase ${phase.role} cancelled.`, "info");
|
|
730
|
+
registry.completePhase(taskId, phase.role, "cancelled");
|
|
731
|
+
registry.confirmCancelled(taskId);
|
|
732
|
+
// ADR-S21-01: preserve state file so cancelled runs are resumable
|
|
733
|
+
writeState(cwd, {
|
|
734
|
+
taskId,
|
|
735
|
+
phaseIndex: currentPhaseIndex,
|
|
736
|
+
iterationCounts,
|
|
737
|
+
halted: false,
|
|
738
|
+
status: "cancelled",
|
|
739
|
+
lastError: undefined,
|
|
740
|
+
savedAt: new Date().toISOString(),
|
|
741
|
+
});
|
|
742
|
+
return { status: "cancelled", lastPhaseIndex: currentPhaseIndex, iterationCounts };
|
|
799
743
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
ctx.ui.notify(`× forge:run-task — verdict missing for phase ${phase.role} after subagent completed. ` +
|
|
806
|
-
"Subagent may have crashed or failed to write summaries. Escalating.", "error");
|
|
744
|
+
// ── Halt-on-failure ───────────────────────────────────────────
|
|
745
|
+
if (result.exitCode !== 0) {
|
|
746
|
+
ctx.ui.notify(`× forge:run-task — phase ${phase.role} failed (exit ${result.exitCode})` +
|
|
747
|
+
(result.errorMessage ? `: ${result.errorMessage}` : "") +
|
|
748
|
+
(result.stopReason ? ` [${result.stopReason}]` : ""), "error");
|
|
807
749
|
writeState(cwd, {
|
|
808
750
|
taskId,
|
|
809
751
|
phaseIndex: currentPhaseIndex,
|
|
810
752
|
iterationCounts,
|
|
811
753
|
halted: true,
|
|
812
|
-
lastError:
|
|
754
|
+
lastError: result.errorMessage ?? result.stopReason ?? "subagent exit non-zero",
|
|
813
755
|
savedAt: new Date().toISOString(),
|
|
814
756
|
});
|
|
815
757
|
return {
|
|
816
758
|
status: "failed",
|
|
817
759
|
lastPhaseIndex: currentPhaseIndex,
|
|
818
760
|
iterationCounts,
|
|
819
|
-
lastError:
|
|
761
|
+
lastError: result.errorMessage ?? result.stopReason ?? "subagent exit non-zero",
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
// Capture model/provider from subagent result (REVIEW FIX #1).
|
|
765
|
+
if (result.model)
|
|
766
|
+
lastModel = result.model;
|
|
767
|
+
if (result.provider)
|
|
768
|
+
lastProvider = result.provider;
|
|
769
|
+
// Phase-complete liveliness ping (counts + duration).
|
|
770
|
+
{
|
|
771
|
+
const elapsed = Math.floor((Date.now() - phaseStart) / 1000);
|
|
772
|
+
const { turn, toolCount, errCount, cumUsage } = observer.state;
|
|
773
|
+
ctx.ui.notify(`✓ ${phase.role}: ${turn} turn${turn === 1 ? "" : "s"} · ${toolCount} tool call${toolCount === 1 ? "" : "s"}${errCount ? ` · ${errCount} err` : ""} · ${elapsed}s`, "info");
|
|
774
|
+
orchTranscript.record({
|
|
775
|
+
kind: "phase-end",
|
|
776
|
+
ts: new Date().toISOString(),
|
|
777
|
+
phase: phase.role,
|
|
778
|
+
phaseIndex: currentPhaseIndex,
|
|
779
|
+
attempt: (iterationCounts[phase.role] ?? 0) + 1,
|
|
780
|
+
verdict: "n/a",
|
|
781
|
+
elapsedMs: Date.now() - phaseStart,
|
|
782
|
+
turns: turn,
|
|
783
|
+
toolCount,
|
|
784
|
+
errCount,
|
|
785
|
+
});
|
|
786
|
+
const { cumCompression } = observer.state;
|
|
787
|
+
registry.appendTail(taskId, phase.role, fmtPhaseSummary({
|
|
788
|
+
role: phase.role,
|
|
789
|
+
turns: turn,
|
|
790
|
+
tools: toolCount,
|
|
791
|
+
errors: errCount,
|
|
792
|
+
wallSeconds: elapsed,
|
|
793
|
+
usage: cumUsage,
|
|
794
|
+
model: result.model,
|
|
795
|
+
provider: result.provider,
|
|
796
|
+
compression: cumCompression.tokensSaved > 0 ? cumCompression : undefined,
|
|
797
|
+
}));
|
|
798
|
+
}
|
|
799
|
+
// ── Plan 11 / Slice 2: orchestrator emits phase event ─────────
|
|
800
|
+
const phaseEndMs = Date.now();
|
|
801
|
+
const taskRecord = readTaskRecord(taskId, storeCli, cwd);
|
|
802
|
+
const sprintId = taskRecord?.sprintId;
|
|
803
|
+
if (!sprintId) {
|
|
804
|
+
ctx.ui.notify(`⚠ forge:run-task — could not resolve sprintId for ${taskId}; ` +
|
|
805
|
+
`skipping orchestrator emit for phase ${phase.role}`, "warning");
|
|
806
|
+
writeDebug({ kind: "emit_skipped", reason: "no-sprintId" });
|
|
807
|
+
}
|
|
808
|
+
else {
|
|
809
|
+
const phaseIteration = (iterationCounts[phase.role] ?? 0) + 1;
|
|
810
|
+
const emitCtx = {
|
|
811
|
+
entityType: "task",
|
|
812
|
+
taskId,
|
|
813
|
+
sprintId,
|
|
814
|
+
phase,
|
|
815
|
+
iteration: phaseIteration,
|
|
816
|
+
startMs: phaseStart,
|
|
817
|
+
endMs: phaseEndMs,
|
|
818
|
+
model: result.model ?? "unknown",
|
|
819
|
+
provider: result.provider ?? "unknown",
|
|
820
|
+
usage: {
|
|
821
|
+
input: result.usage.input,
|
|
822
|
+
output: result.usage.output,
|
|
823
|
+
cacheRead: result.usage.cacheRead,
|
|
824
|
+
cacheWrite: result.usage.cacheWrite,
|
|
825
|
+
},
|
|
826
|
+
judgement: judgementFromSummary(taskRecord, phase.role),
|
|
827
|
+
storeCli,
|
|
828
|
+
cwd,
|
|
820
829
|
};
|
|
830
|
+
const phaseEvent = buildPhaseEvent(emitCtx);
|
|
831
|
+
const emitResult = emitEvent(storeCli, cwd, sprintId, phaseEvent);
|
|
832
|
+
if (!emitResult.ok) {
|
|
833
|
+
ctx.ui.notify(`⚠ forge:run-task — phase event emit failed for ${phase.role}: ${emitResult.stderr.trim()}`, "warning");
|
|
834
|
+
writeDebug({ kind: "emit_failed", stderr: emitResult.stderr });
|
|
835
|
+
}
|
|
836
|
+
else {
|
|
837
|
+
writeDebug({ kind: "emit_ok", eventId: phaseEvent.eventId });
|
|
838
|
+
}
|
|
839
|
+
// Notify sprint-level observer (FORGE-S21-T03).
|
|
840
|
+
if (onPhaseEvent)
|
|
841
|
+
onPhaseEvent(phaseEvent);
|
|
842
|
+
// Drain friction file for this phase, if any.
|
|
843
|
+
const frictionPath = path.join(cwd, ".forge", "cache", `FRICTION-${phase.role}.jsonl`);
|
|
844
|
+
const drain = drainFrictionFile(frictionPath, emitCtx);
|
|
845
|
+
if (drain.emitted + drain.failed > 0) {
|
|
846
|
+
writeDebug({ kind: "friction_drain", ...drain });
|
|
847
|
+
if (drain.failed > 0) {
|
|
848
|
+
ctx.ui.notify(`⚠ forge:run-task — friction drain for ${phase.role}: ${drain.emitted} ok, ${drain.failed} failed`, "warning");
|
|
849
|
+
}
|
|
850
|
+
}
|
|
821
851
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
if (
|
|
826
|
-
ctx.ui.notify(`× forge:run-task —
|
|
827
|
-
|
|
852
|
+
// ── 6b. Verdict check (review phases only) ────────────────────
|
|
853
|
+
if (phase.isReview) {
|
|
854
|
+
const verdict = readVerdict(taskId, phase.role, storeCli, cwd);
|
|
855
|
+
if (verdict === "missing") {
|
|
856
|
+
ctx.ui.notify(`× forge:run-task — verdict missing for phase ${phase.role} after subagent completed. ` +
|
|
857
|
+
"Subagent may have crashed or failed to write summaries. Escalating.", "error");
|
|
828
858
|
writeState(cwd, {
|
|
829
859
|
taskId,
|
|
830
860
|
phaseIndex: currentPhaseIndex,
|
|
831
861
|
iterationCounts,
|
|
832
862
|
halted: true,
|
|
833
|
-
lastError: `
|
|
863
|
+
lastError: `verdict missing for ${phase.role}`,
|
|
834
864
|
savedAt: new Date().toISOString(),
|
|
835
865
|
});
|
|
836
866
|
return {
|
|
837
|
-
status: "
|
|
867
|
+
status: "failed",
|
|
838
868
|
lastPhaseIndex: currentPhaseIndex,
|
|
839
869
|
iterationCounts,
|
|
840
|
-
lastError: `
|
|
870
|
+
lastError: `verdict missing for ${phase.role}`,
|
|
841
871
|
};
|
|
842
872
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
873
|
+
if (verdict === "revision") {
|
|
874
|
+
// Increment iteration count for this review phase
|
|
875
|
+
iterationCounts[phase.role] = (iterationCounts[phase.role] ?? 0) + 1;
|
|
876
|
+
if (iterationCounts[phase.role] >= phase.maxIterations) {
|
|
877
|
+
ctx.ui.notify(`× forge:run-task — revision cap reached for phase ${phase.role} ` +
|
|
878
|
+
`(${iterationCounts[phase.role]}/${phase.maxIterations} iterations). Escalating.`, "error");
|
|
879
|
+
writeState(cwd, {
|
|
880
|
+
taskId,
|
|
881
|
+
phaseIndex: currentPhaseIndex,
|
|
882
|
+
iterationCounts,
|
|
883
|
+
halted: true,
|
|
884
|
+
lastError: `revision cap reached for ${phase.role}`,
|
|
885
|
+
savedAt: new Date().toISOString(),
|
|
886
|
+
});
|
|
887
|
+
return {
|
|
888
|
+
status: "escalated",
|
|
889
|
+
lastPhaseIndex: currentPhaseIndex,
|
|
890
|
+
iterationCounts,
|
|
891
|
+
lastError: `revision cap reached for ${phase.role}`,
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
// Loop back to predecessor non-review phase
|
|
895
|
+
const predIndex = findPredecessorIndex(PHASES, currentPhaseIndex);
|
|
896
|
+
ctx.ui.notify(`⟳ forge:run-task — ${phase.role} returned revision; looping to ${PHASES[predIndex]?.role ?? predIndex} ` +
|
|
897
|
+
`(attempt ${iterationCounts[phase.role]}/${phase.maxIterations})`, "info");
|
|
898
|
+
orchTranscript.record({
|
|
899
|
+
kind: "phase-loopback",
|
|
900
|
+
ts: new Date().toISOString(),
|
|
901
|
+
fromPhase: phase.role,
|
|
902
|
+
toPhase: PHASES[predIndex]?.role ?? String(predIndex),
|
|
903
|
+
fromPhaseIndex: currentPhaseIndex,
|
|
904
|
+
toPhaseIndex: predIndex,
|
|
905
|
+
reason: `${phase.role} returned revision (attempt ${iterationCounts[phase.role]}/${phase.maxIterations})`,
|
|
906
|
+
});
|
|
907
|
+
// Write intermediate state (not halted — still running)
|
|
908
|
+
writeState(cwd, {
|
|
909
|
+
taskId,
|
|
910
|
+
phaseIndex: predIndex,
|
|
911
|
+
iterationCounts,
|
|
912
|
+
halted: false,
|
|
913
|
+
savedAt: new Date().toISOString(),
|
|
914
|
+
});
|
|
915
|
+
currentPhaseIndex = predIndex;
|
|
916
|
+
continue;
|
|
917
|
+
}
|
|
918
|
+
// verdict === "approved": fall through to advance
|
|
857
919
|
}
|
|
858
|
-
//
|
|
920
|
+
// ── Advance to next phase ─────────────────────────────────────
|
|
921
|
+
registry.completePhase(taskId, phase.role, "completed");
|
|
922
|
+
writeState(cwd, {
|
|
923
|
+
taskId,
|
|
924
|
+
phaseIndex: currentPhaseIndex,
|
|
925
|
+
iterationCounts,
|
|
926
|
+
halted: false,
|
|
927
|
+
savedAt: new Date().toISOString(),
|
|
928
|
+
});
|
|
929
|
+
currentPhaseIndex++;
|
|
859
930
|
}
|
|
860
|
-
// ──
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
savedAt: new Date().toISOString(),
|
|
931
|
+
// ── All phases complete ───────────────────────────────────────────
|
|
932
|
+
deleteState(cwd, taskId);
|
|
933
|
+
orchTranscript.record({
|
|
934
|
+
kind: "pipeline-end",
|
|
935
|
+
ts: new Date().toISOString(),
|
|
936
|
+
outcome: "complete",
|
|
937
|
+
elapsedMs: Date.now() - pipelineStartMs,
|
|
868
938
|
});
|
|
869
|
-
|
|
939
|
+
return {
|
|
940
|
+
status: "completed",
|
|
941
|
+
lastPhaseIndex: PHASES.length - 1,
|
|
942
|
+
iterationCounts,
|
|
943
|
+
model: lastModel,
|
|
944
|
+
provider: lastProvider,
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
finally {
|
|
948
|
+
ctx.ui.notify = __origNotify;
|
|
870
949
|
}
|
|
871
|
-
// ── All phases complete ───────────────────────────────────────────
|
|
872
|
-
deleteState(cwd, taskId);
|
|
873
|
-
return {
|
|
874
|
-
status: "completed",
|
|
875
|
-
lastPhaseIndex: PHASES.length - 1,
|
|
876
|
-
iterationCounts,
|
|
877
|
-
model: lastModel,
|
|
878
|
-
provider: lastProvider,
|
|
879
|
-
};
|
|
880
950
|
}
|
|
881
951
|
export function registerRunTask(pi, options = {}) {
|
|
882
952
|
pi.registerCommand("forge:run-task", {
|