multi-forge 0.4.0__tar.gz → 0.6.0__tar.gz
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.
- {multi_forge-0.4.0 → multi_forge-0.6.0}/PKG-INFO +16 -11
- {multi_forge-0.4.0 → multi_forge-0.6.0}/README.md +15 -10
- {multi_forge-0.4.0 → multi_forge-0.6.0}/pyproject.toml +1 -1
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/__init__.py +1 -1
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/activity.py +24 -7
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/extensions.py +69 -8
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/gc.py +4 -4
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/hooks/_group.py +5 -4
- multi_forge-0.6.0/src/forge/cli/hooks/codex_patch.py +129 -0
- multi_forge-0.6.0/src/forge/cli/hooks/codex_policy.py +197 -0
- multi_forge-0.6.0/src/forge/cli/hooks/codex_transfer.py +113 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/hooks/commands.py +223 -19
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/hooks/direct_commands.py +116 -4
- multi_forge-0.6.0/src/forge/cli/hooks/policy.py +315 -0
- multi_forge-0.6.0/src/forge/cli/hooks/protocols.py +57 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/memory.py +6 -6
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/policy.py +248 -8
- multi_forge-0.6.0/src/forge/cli/runtime.py +241 -0
- multi_forge-0.6.0/src/forge/cli/session_codex.py +489 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/session_fork.py +29 -3
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/session_lifecycle.py +87 -175
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/session_manage.py +35 -8
- multi_forge-0.6.0/src/forge/cli/session_model_pin.py +196 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/status_line.py +5 -1
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/transfer.py +12 -4
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/defaults/templates/anthropic-passthrough.yaml +3 -2
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/defaults/templates/litellm-anthropic-local.yaml +5 -1
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/defaults/templates/litellm-anthropic.yaml +6 -3
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/defaults/templates/openrouter-anthropic.yaml +5 -3
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/auth/capabilities.py +17 -2
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/data/model_catalog.yaml +41 -3
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/invoker/__init__.py +3 -0
- multi_forge-0.4.0/src/forge/core/invoker/claude.py → multi_forge-0.6.0/src/forge/core/invoker/_lifecycle.py +150 -174
- multi_forge-0.6.0/src/forge/core/invoker/claude.py +154 -0
- multi_forge-0.6.0/src/forge/core/invoker/codex.py +243 -0
- multi_forge-0.6.0/src/forge/core/invoker/codex_stream.py +158 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/invoker/types.py +11 -0
- multi_forge-0.6.0/src/forge/core/ops/codex_bridge.py +390 -0
- multi_forge-0.6.0/src/forge/core/ops/codex_enrollment.py +307 -0
- multi_forge-0.6.0/src/forge/core/ops/codex_interactive.py +560 -0
- multi_forge-0.6.0/src/forge/core/ops/codex_session.py +579 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/ops/gc.py +27 -9
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/ops/resolution.py +7 -7
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/ops/session.py +7 -7
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/ops/transfer.py +18 -4
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/ops/usage_summary.py +180 -44
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/reactive/env.py +92 -17
- multi_forge-0.6.0/src/forge/core/run_id.py +44 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/runtime/__init__.py +18 -0
- multi_forge-0.6.0/src/forge/core/runtime/codex_preflight.py +541 -0
- multi_forge-0.6.0/src/forge/core/runtime/codex_rollouts.py +187 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/runtime/registry.py +71 -26
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/usage/__init__.py +2 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/usage/emit.py +65 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/usage/ledger.py +5 -1
- multi_forge-0.6.0/src/forge/install/codex_hooks.py +515 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/install/installer.py +126 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/install/models.py +33 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/policy/deterministic/base.py +15 -8
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/policy/engine.py +115 -63
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/policy/queries.py +2 -2
- multi_forge-0.6.0/src/forge/policy/semantic/plan_check.py +561 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/policy/semantic/promotion.py +1 -1
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/policy/semantic/supervisor.py +4 -4
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/policy/types.py +8 -5
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/proxy/cost_logger.py +83 -1
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/proxy/data_models.py +4 -2
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/proxy/server.py +45 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/review/models.py +7 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/search/extractor.py +1 -1
- multi_forge-0.6.0/src/forge/session/codex_handoff.py +253 -0
- multi_forge-0.6.0/src/forge/session/codex_invoke.py +81 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/direct_model.py +4 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/exceptions.py +13 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/manager.py +28 -6
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/models.py +51 -1
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/overrides.py +9 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/transfer.py +153 -21
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/panel/SKILL.md +3 -3
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/resources/checklist/2-extension.md +43 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/resources/checklist.md +13 -11
- multi_forge-0.4.0/src/forge/cli/hooks/policy.py +0 -212
- multi_forge-0.4.0/src/forge/cli/hooks/protocols.py +0 -53
- multi_forge-0.4.0/src/forge/cli/runtime.py +0 -105
- {multi_forge-0.4.0 → multi_forge-0.6.0}/.gitignore +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/LICENSE +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/NOTICE +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/agents/.gitkeep +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/commands/.gitkeep +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/backend/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/backend/adapters/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/backend/adapters/litellm.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/backend/creation.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/backend/registry.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/auth.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/backend.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/claude.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/config_cmd.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/editor.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/guards.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/hooks/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/hooks/_helpers.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/hooks/install.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/hooks/read_hygiene.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/hooks/verification.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/launch_confirmation.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/logs.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/main.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/memory_report.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/memory_writer.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/output.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/proxy.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/proxy_audit.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/proxy_costs.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/search.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/session.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/session_addendum.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/statusline/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/statusline/context.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/statusline/names.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/statusline/palette.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/statusline/registry.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/statusline/throttle.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/cli/workflow.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/dataclass_utils.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/defaults/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/defaults/backends/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/defaults/backends/litellm.yaml +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/defaults/templates/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/defaults/templates/litellm-gemini-flash-local.yaml +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/defaults/templates/litellm-gemini-local.yaml +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/defaults/templates/litellm-gemini-test.yaml +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/defaults/templates/litellm-gemini.yaml +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/defaults/templates/litellm-openai-codex-local.yaml +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/defaults/templates/litellm-openai-local.yaml +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/defaults/templates/litellm-openai.yaml +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/defaults/templates/openrouter-deepseek.yaml +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/defaults/templates/openrouter-gemini-flash.yaml +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/defaults/templates/openrouter-gemini.yaml +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/defaults/templates/openrouter-glm.yaml +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/defaults/templates/openrouter-kimi.yaml +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/defaults/templates/openrouter-minimax.yaml +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/defaults/templates/openrouter-openai-codex.yaml +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/defaults/templates/openrouter-openai.yaml +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/defaults/templates/openrouter-qwen.yaml +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/loader.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/config/schema.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/auth/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/auth/credentials_file.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/auth/protocols.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/auth/secrets.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/auth/template_secrets.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/data/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/data/system_prompt_addendums/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/data/system_prompt_addendums/gemini.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/data/system_prompt_addendums/openai.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/llm/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/llm/clients/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/llm/clients/base.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/llm/clients/litellm.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/llm/clients/openai_compat.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/llm/clients/openrouter.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/llm/credentials.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/llm/detection.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/llm/errors.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/llm/protocols.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/llm/types.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/logging.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/models/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/models/catalog.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/models/types.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/naming.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/ops/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/ops/context.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/ops/proxy.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/ops/session_context.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/paths.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/process.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/reactive/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/reactive/cost_tracking.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/reactive/headless_json.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/reactive/proxy.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/reactive/routing.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/reactive/session_runner.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/reactive/structured_output.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/reactive/tagger.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/reactive/throttle.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/state/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/state/exceptions.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/state/io.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/state/lock.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/state/timestamps.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/transcript.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/typing_helpers.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/usage/billing.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/usage/correlation.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/usage/vocabulary.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/workqueue/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/workqueue/queue.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/core/workqueue/types.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/install/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/install/cli.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/install/exceptions.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/install/hooks.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/install/preset.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/install/settings_merge.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/install/tracking.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/install/version.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/policy/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/policy/deterministic/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/policy/deterministic/coding_standards.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/policy/deterministic/registry.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/policy/deterministic/tdd.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/policy/protocols.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/policy/semantic/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/policy/semantic/verdict.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/policy/store.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/policy/team/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/policy/team/config.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/policy/team/handlers.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/policy/team/prompts.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/policy/workflow/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/policy/workflow/branches.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/policy/workflow/config.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/policy/workflow/divergence.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/policy/workflow/policy.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/policy/workflow/stages.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/proxy/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/proxy/audit_logger.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/proxy/base_client.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/proxy/client_adapter.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/proxy/client_factory.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/proxy/converters.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/proxy/cost_tracker.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/proxy/error_hints.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/proxy/intercept.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/proxy/metrics.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/proxy/model_spec.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/proxy/passthrough.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/proxy/proxies.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/proxy/proxy_identity.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/proxy/proxy_orchestrator.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/proxy/proxy_startup.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/proxy/utils.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/review/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/review/adversarial.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/review/consensus.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/review/engine.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/review/resources/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/review/resources/codereview-performance.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/review/resources/codereview-quick.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/review/resources/codereview-security.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/review/resources/codereview.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/review/resources/docreview-quick.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/review/resources/docreview.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/review/resources/thinkdeep.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/review/routing.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/review/synthesis.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/runtime_config.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/search/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/search/bm25_store.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/search/content_store.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/search/engine.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/search/exceptions.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/search/index_state.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/search/store.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/search/tokenizer.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/active.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/artifacts.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/claude/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/claude/cleanup.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/claude/invoke.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/claude/paths.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/claude/relocate.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/cleanup.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/config.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/effective.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/hooks/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/hooks/models.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/hooks/session_start.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/identity.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/index.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/memory_inheritance.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/memory_writer.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/passport.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/plan_resolution.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/prev_sessions.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/project_memory.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/shadow_curation.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/store.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/validation.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/worktree/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/worktree/cleanup.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/worktree/config_copy.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/session/worktree/create.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/sidecar/__init__.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/sidecar/container.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/sidecar/docker.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/forge/sidecar/secrets.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/analyze/SKILL.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/challenge/SKILL.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/consensus/SKILL.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/consensus/resources/code_consensus_evaluation.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/consensus/resources/consensus_evaluation.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/consensus/resources/synthesis.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/debate/SKILL.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/debate/resources/code_debate_evaluation.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/debate/resources/debate_evaluation.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/panel/resources/synthesis.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/SKILL.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/resources/checklist/0-enable.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/resources/checklist/1-preflight.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/resources/checklist/10-resume.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/resources/checklist/11-config.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/resources/checklist/12-search.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/resources/checklist/13-policy.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/resources/checklist/14-workflow.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/resources/checklist/15-skills.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/resources/checklist/16-memory.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/resources/checklist/17-info.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/resources/checklist/18-disable.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/resources/checklist/19-uninstall.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/resources/checklist/20-cleanup.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/resources/checklist/3-authentication.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/resources/checklist/4-proxy.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/resources/checklist/5-session.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/resources/checklist/6-hook.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/resources/checklist/7-costs.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/resources/checklist/8-status-line.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/resources/checklist/9-direct-commands.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/resources/report-template.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/scripts/start-container.sh +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/qa/scripts/walkthrough-state.py +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/review/SKILL.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/review/references/claude-4.6.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/review/references/claude-4.8.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/review/references/gemini-3.1.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/review/references/gpt-5.5.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/review/references/skills-writing-guide.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/review/resources/code-anthropic.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/review/resources/code-gemini.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/review/resources/code-openai.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/review/resources/code.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/review-docs/SKILL.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/review-docs/resources/docs-anthropic.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/review-docs/resources/docs-gemini.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/review-docs/resources/docs-openai.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/review-docs/resources/docs.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/smoke-test/SKILL.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/smoke-test/scripts/smoke-test.sh +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/understand/SKILL.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/understand/resources/code-anthropic.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/understand/resources/code-gemini.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/understand/resources/code-openai.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/understand/resources/code.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/understand/resources/docs-anthropic.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/understand/resources/docs-gemini.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/understand/resources/docs-openai.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/understand/resources/docs.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/walkthrough/SKILL.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/walkthrough/resources/checklist.md +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/walkthrough/scripts/run-in-repo.sh +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/walkthrough/scripts/setup-test-repo.sh +0 -0
- {multi_forge-0.4.0 → multi_forge-0.6.0}/src/skills/walkthrough/scripts/walkthrough-state.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: multi-forge
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Multi-runtime agent toolkit: proxy routing, cost control, session management, policy enforcement, and workflow orchestration
|
|
5
5
|
Project-URL: Homepage, https://github.com/hapa1i/multi-forge
|
|
6
6
|
Project-URL: Repository, https://github.com/hapa1i/multi-forge
|
|
@@ -56,15 +56,18 @@ Description-Content-Type: text/markdown
|
|
|
56
56
|
**Multi-runtime agent toolkit: proxy routing, cost control, session management, and policy enforcement for coding
|
|
57
57
|
agents.**
|
|
58
58
|
|
|
59
|
-
Forge sits between you and your coding agent (Claude Code
|
|
60
|
-
multi-provider model routing, cost visibility with spend caps, and autonomous
|
|
61
|
-
`forge session start` instead of `claude`, and Forge handles the rest -- routing to your chosen
|
|
62
|
-
state across sessions, and enforcing policies.
|
|
59
|
+
Forge sits between you and your coding agent (Claude Code by default, with Codex as an alternate runtime and Gemini
|
|
60
|
+
next), adding persistent sessions, multi-provider model routing, cost visibility with spend caps, and autonomous
|
|
61
|
+
verification. You run `forge session start` instead of `claude`, and Forge handles the rest -- routing to your chosen
|
|
62
|
+
model provider, tracking state across sessions, and enforcing policies.
|
|
63
63
|
|
|
64
64
|
```bash
|
|
65
65
|
# Use Claude with session tracking (no proxy needed)
|
|
66
66
|
forge session start
|
|
67
67
|
|
|
68
|
+
# Or run a different runtime entirely -- Codex as an alternate frontend
|
|
69
|
+
forge session start --runtime codex # interactive TUI; hooks/policy need a one-time Codex trust enrollment
|
|
70
|
+
|
|
68
71
|
# Or route through different model providers (after creating proxies -- see Quick Start)
|
|
69
72
|
forge session start planner --proxy openrouter-openai # GPT for planning
|
|
70
73
|
forge session start --proxy openrouter-gemini # Gemini for review
|
|
@@ -223,12 +226,14 @@ Run `forge <command> --help` for details on any command.
|
|
|
223
226
|
|
|
224
227
|
## Documentation
|
|
225
228
|
|
|
226
|
-
| Audience
|
|
227
|
-
|
|
|
228
|
-
| **Users**
|
|
229
|
-
| **Developers**
|
|
230
|
-
| **Architecture**
|
|
231
|
-
| **
|
|
229
|
+
| Audience | Location | Contents |
|
|
230
|
+
| ------------------- | ---------------------------------------------------- | ----------------------------------------------------- |
|
|
231
|
+
| **Users** | [docs/end-user/](docs/end-user/) | Tour, guides for sessions, proxies, policies, ... |
|
|
232
|
+
| **Developers** | [docs/developer/](docs/developer/) | Setup, coding standards, testing guidelines |
|
|
233
|
+
| **Architecture** | [docs/design.md](docs/design.md) | Core system narrative, data flow, invariants |
|
|
234
|
+
| **Workflow design** | [docs/design_workflows.md](docs/design_workflows.md) | Policy, skills, workflow runners, memory architecture |
|
|
235
|
+
| **CLI reference** | [docs/cli_reference.md](docs/cli_reference.md) | Terminal and direct-command inventory |
|
|
236
|
+
| **Work Board** | [docs/board/](docs/board/) | Cards, checklists, change log, implementation memory |
|
|
232
237
|
|
|
233
238
|
## Contributing
|
|
234
239
|
|
|
@@ -15,15 +15,18 @@
|
|
|
15
15
|
**Multi-runtime agent toolkit: proxy routing, cost control, session management, and policy enforcement for coding
|
|
16
16
|
agents.**
|
|
17
17
|
|
|
18
|
-
Forge sits between you and your coding agent (Claude Code
|
|
19
|
-
multi-provider model routing, cost visibility with spend caps, and autonomous
|
|
20
|
-
`forge session start` instead of `claude`, and Forge handles the rest -- routing to your chosen
|
|
21
|
-
state across sessions, and enforcing policies.
|
|
18
|
+
Forge sits between you and your coding agent (Claude Code by default, with Codex as an alternate runtime and Gemini
|
|
19
|
+
next), adding persistent sessions, multi-provider model routing, cost visibility with spend caps, and autonomous
|
|
20
|
+
verification. You run `forge session start` instead of `claude`, and Forge handles the rest -- routing to your chosen
|
|
21
|
+
model provider, tracking state across sessions, and enforcing policies.
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
24
|
# Use Claude with session tracking (no proxy needed)
|
|
25
25
|
forge session start
|
|
26
26
|
|
|
27
|
+
# Or run a different runtime entirely -- Codex as an alternate frontend
|
|
28
|
+
forge session start --runtime codex # interactive TUI; hooks/policy need a one-time Codex trust enrollment
|
|
29
|
+
|
|
27
30
|
# Or route through different model providers (after creating proxies -- see Quick Start)
|
|
28
31
|
forge session start planner --proxy openrouter-openai # GPT for planning
|
|
29
32
|
forge session start --proxy openrouter-gemini # Gemini for review
|
|
@@ -182,12 +185,14 @@ Run `forge <command> --help` for details on any command.
|
|
|
182
185
|
|
|
183
186
|
## Documentation
|
|
184
187
|
|
|
185
|
-
| Audience
|
|
186
|
-
|
|
|
187
|
-
| **Users**
|
|
188
|
-
| **Developers**
|
|
189
|
-
| **Architecture**
|
|
190
|
-
| **
|
|
188
|
+
| Audience | Location | Contents |
|
|
189
|
+
| ------------------- | ---------------------------------------------------- | ----------------------------------------------------- |
|
|
190
|
+
| **Users** | [docs/end-user/](docs/end-user/) | Tour, guides for sessions, proxies, policies, ... |
|
|
191
|
+
| **Developers** | [docs/developer/](docs/developer/) | Setup, coding standards, testing guidelines |
|
|
192
|
+
| **Architecture** | [docs/design.md](docs/design.md) | Core system narrative, data flow, invariants |
|
|
193
|
+
| **Workflow design** | [docs/design_workflows.md](docs/design_workflows.md) | Policy, skills, workflow runners, memory architecture |
|
|
194
|
+
| **CLI reference** | [docs/cli_reference.md](docs/cli_reference.md) | Terminal and direct-command inventory |
|
|
195
|
+
| **Work Board** | [docs/board/](docs/board/) | Cards, checklists, change log, implementation memory |
|
|
191
196
|
|
|
192
197
|
## Contributing
|
|
193
198
|
|
|
@@ -100,7 +100,10 @@ def _render(summary: SessionActivitySummary, *, days: int | None) -> None:
|
|
|
100
100
|
for c in summary.commands:
|
|
101
101
|
errors = f"[red]{c.errors}[/red]" if c.errors else "0"
|
|
102
102
|
tokens = f"{c.input_tokens}/{c.output_tokens}" if (c.input_tokens or c.output_tokens) else "-"
|
|
103
|
-
|
|
103
|
+
if c.cost_micro_usd is None:
|
|
104
|
+
cost = "-"
|
|
105
|
+
else:
|
|
106
|
+
cost = f"{'~' if c.cost_estimated else ''}{_fmt_usd(c.cost_micro_usd)}"
|
|
104
107
|
row = [c.command, str(c.calls)]
|
|
105
108
|
if show_workers:
|
|
106
109
|
row.append(str(c.workers) if c.workers else "-")
|
|
@@ -110,17 +113,26 @@ def _render(summary: SessionActivitySummary, *, days: int | None) -> None:
|
|
|
110
113
|
|
|
111
114
|
pol = summary.policy
|
|
112
115
|
if pol and pol.has_content:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
if pol.plan_check_allow or pol.plan_check_needs_review:
|
|
117
|
+
console.print(
|
|
118
|
+
f"\n[bold]Plan check (tier-1)[/bold]: {pol.plan_check_allow} allow · "
|
|
119
|
+
f"{pol.plan_check_needs_review} needs review"
|
|
120
|
+
)
|
|
121
|
+
if pol.supervisor_allow or pol.supervisor_warn or pol.supervisor_deny or pol.total_warnings:
|
|
122
|
+
console.print(
|
|
123
|
+
f"\n[bold]Supervisor[/bold]: {pol.supervisor_allow} allow · "
|
|
124
|
+
f"{pol.supervisor_warn} warn · {pol.supervisor_deny} block"
|
|
125
|
+
)
|
|
117
126
|
for warning in pol.recent_warnings:
|
|
118
127
|
console.print(f" [yellow]•[/yellow] {warning}")
|
|
119
128
|
|
|
120
129
|
if summary.subagents:
|
|
121
130
|
console.print(f"\n[bold]Subagents[/bold]: {summary.subagents}")
|
|
122
131
|
|
|
123
|
-
|
|
132
|
+
if summary.total_cost_micro_usd is None:
|
|
133
|
+
total_cost = "n/a"
|
|
134
|
+
else:
|
|
135
|
+
total_cost = f"{'~' if summary.cost_estimated else ''}{_fmt_usd(summary.total_cost_micro_usd)}"
|
|
124
136
|
console.print(
|
|
125
137
|
f"\n[dim]Total:[/dim] {summary.total_events} events · "
|
|
126
138
|
f"{summary.total_input_tokens}/{summary.total_output_tokens} tok · {total_cost}"
|
|
@@ -138,7 +150,12 @@ def _footnotes(summary: SessionActivitySummary) -> list[str]:
|
|
|
138
150
|
notes.append("policy decision log is at capacity — older decisions may not be shown")
|
|
139
151
|
if summary.session_tagging_partial:
|
|
140
152
|
notes.append("some calls (e.g. the action tagger) are not session-attributed")
|
|
141
|
-
|
|
153
|
+
# "no snapshot estimates" covers both exact sources: the 4g cost-plane root-join
|
|
154
|
+
# and runtime-reported (runtime_native) self-reports. A cost-less summary keeps
|
|
155
|
+
# the generic caveat -- there is no figure to call exact.
|
|
156
|
+
exact = summary.total_cost_micro_usd is not None and not summary.cost_estimated
|
|
157
|
+
evidence = "reported (no snapshot estimates mixed in)" if exact else "reported-or-estimated"
|
|
158
|
+
notes.append(f"cost is {evidence}, best-effort; 'forge proxy costs show' is the authoritative spend view")
|
|
142
159
|
return notes
|
|
143
160
|
|
|
144
161
|
|
|
@@ -99,15 +99,18 @@ def _parse_modules(modules_str: str | None) -> set[InstallModule] | None:
|
|
|
99
99
|
return {InstallModule(m.strip()) for m in modules_str.split(",")}
|
|
100
100
|
|
|
101
101
|
|
|
102
|
-
def _count_actions(plan: InstallPlan) -> tuple[int, int]:
|
|
102
|
+
def _count_actions(plan: InstallPlan) -> tuple[int, int, int]:
|
|
103
103
|
"""Count non-skip actions in a plan.
|
|
104
104
|
|
|
105
105
|
Returns:
|
|
106
|
-
Tuple of (file_actions, settings_actions) that
|
|
106
|
+
Tuple of (file_actions, settings_actions, codex_actions) that
|
|
107
|
+
actually change something. A codex install/update counts as an
|
|
108
|
+
action so a codex-only change never renders "Already up to date.".
|
|
107
109
|
"""
|
|
108
110
|
file_actions = sum(1 for f in plan.files if f.action != "skip")
|
|
109
111
|
settings_actions = sum(1 for s in plan.settings if s.action != "skip")
|
|
110
|
-
|
|
112
|
+
codex_actions = 1 if plan.codex is not None and plan.codex.action in ("install", "update") else 0
|
|
113
|
+
return file_actions, settings_actions, codex_actions
|
|
111
114
|
|
|
112
115
|
|
|
113
116
|
# Modules that are intentionally empty in the source tree (only .gitkeep).
|
|
@@ -165,8 +168,8 @@ def _print_completion_message(
|
|
|
165
168
|
tracking: TrackingStore,
|
|
166
169
|
) -> None:
|
|
167
170
|
"""Print appropriate completion message based on what was done."""
|
|
168
|
-
file_actions, settings_actions = _count_actions(plan)
|
|
169
|
-
total_actions = file_actions + settings_actions
|
|
171
|
+
file_actions, settings_actions, codex_actions = _count_actions(plan)
|
|
172
|
+
total_actions = file_actions + settings_actions + codex_actions
|
|
170
173
|
|
|
171
174
|
_warn_if_modules_have_no_files(plan, scope, project_root, tracking)
|
|
172
175
|
|
|
@@ -178,6 +181,8 @@ def _print_completion_message(
|
|
|
178
181
|
parts.append(f"{file_actions} file{'s' if file_actions != 1 else ''}")
|
|
179
182
|
if settings_actions > 0:
|
|
180
183
|
parts.append(f"{settings_actions} setting{'s' if settings_actions != 1 else ''}")
|
|
184
|
+
if codex_actions > 0:
|
|
185
|
+
parts.append("Codex hooks")
|
|
181
186
|
console.print(f"\n[green]Extensions enabled.[/green] ({', '.join(parts)} updated)")
|
|
182
187
|
|
|
183
188
|
print_tip(
|
|
@@ -200,6 +205,30 @@ def _print_completion_message(
|
|
|
200
205
|
required = gated[0][1].value
|
|
201
206
|
print_tip(f"Additional skills available with --profile {required}: {skill_list}", console=console)
|
|
202
207
|
|
|
208
|
+
_print_codex_completion(plan, scope)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _print_codex_completion(plan: InstallPlan, scope: InstallScope) -> None:
|
|
212
|
+
"""Print the trust-ceremony guidance (or skip notice) for the codex plan.
|
|
213
|
+
|
|
214
|
+
Registration alone is inert: Codex hooks fire only after the user's
|
|
215
|
+
one-time interactive trust ceremony, which Forge can neither perform nor
|
|
216
|
+
verify -- so a fresh registration always names the ceremony explicitly.
|
|
217
|
+
"""
|
|
218
|
+
codex = plan.codex
|
|
219
|
+
if codex is None:
|
|
220
|
+
return
|
|
221
|
+
if codex.action in ("install", "update"):
|
|
222
|
+
where = "in any project" if scope == InstallScope.USER else "in this project"
|
|
223
|
+
config = display_path(codex.config_path) if codex.config_path else "config.toml"
|
|
224
|
+
console.print("\n[dim]Next steps (Codex hooks):[/dim]")
|
|
225
|
+
console.print(f" - Forge hooks are registered in {config} but stay inert until trusted.")
|
|
226
|
+
console.print(f" - Run 'codex' interactively {where} and grant trust when prompted (one-time).")
|
|
227
|
+
elif codex.action == "conflict":
|
|
228
|
+
console.print(f"\n[yellow]Warning:[/yellow] Codex hook registration skipped: {codex.reason}")
|
|
229
|
+
elif codex.action == "unavailable":
|
|
230
|
+
console.print(f"\n[dim]Codex hooks skipped: {codex.reason}.[/dim]")
|
|
231
|
+
|
|
203
232
|
|
|
204
233
|
def _validate_anchor(anchor: Path) -> None:
|
|
205
234
|
"""Reject anchors that point inside a ``.claude/`` directory.
|
|
@@ -324,6 +353,23 @@ def _print_plan(plan: InstallPlan, dry_run: bool = False) -> None:
|
|
|
324
353
|
|
|
325
354
|
console.print(table)
|
|
326
355
|
|
|
356
|
+
if plan.codex is not None:
|
|
357
|
+
console.print(f"\n{prefix}[bold]Codex hooks (config.toml):[/bold]")
|
|
358
|
+
table = Table(show_header=True, header_style="bold", box=None)
|
|
359
|
+
table.add_column("ACTION", style="dim")
|
|
360
|
+
table.add_column("TARGET")
|
|
361
|
+
table.add_column("REASON", style="dim")
|
|
362
|
+
style = {
|
|
363
|
+
"install": "green",
|
|
364
|
+
"update": "yellow",
|
|
365
|
+
"skip": "dim",
|
|
366
|
+
"conflict": "yellow", # best-effort: degrades to skip, never blocks
|
|
367
|
+
"unavailable": "dim",
|
|
368
|
+
}.get(plan.codex.action, "")
|
|
369
|
+
target = display_path(plan.codex.config_path) if plan.codex.config_path else ""
|
|
370
|
+
table.add_row(plan.codex.action, target, plan.codex.reason or "", style=style)
|
|
371
|
+
console.print(table)
|
|
372
|
+
|
|
327
373
|
if plan.has_conflicts:
|
|
328
374
|
console.print(f"\n{prefix}[bold red]Conflicts detected:[/bold red]")
|
|
329
375
|
for c in plan.conflicts:
|
|
@@ -467,7 +513,7 @@ def extensions() -> None:
|
|
|
467
513
|
"--with",
|
|
468
514
|
"-w",
|
|
469
515
|
"with_modules",
|
|
470
|
-
help="Add modules (comma-separated: commands,agents,skills,hooks,status-line,permissions)",
|
|
516
|
+
help="Add modules (comma-separated: commands,agents,skills,hooks,status-line,permissions,codex-hooks)",
|
|
471
517
|
)
|
|
472
518
|
@click.option(
|
|
473
519
|
"--without",
|
|
@@ -672,8 +718,8 @@ def sync_cmd(scope: str | None, force: bool) -> None:
|
|
|
672
718
|
console.print("\n[red]Sync failed due to conflicts.[/red]")
|
|
673
719
|
sys.exit(1)
|
|
674
720
|
else:
|
|
675
|
-
file_actions, settings_actions = _count_actions(plan)
|
|
676
|
-
total_actions = file_actions + settings_actions
|
|
721
|
+
file_actions, settings_actions, codex_actions = _count_actions(plan)
|
|
722
|
+
total_actions = file_actions + settings_actions + codex_actions
|
|
677
723
|
if total_actions == 0:
|
|
678
724
|
console.print("\n[dim]Already up to date.[/dim]")
|
|
679
725
|
else:
|
|
@@ -682,8 +728,15 @@ def sync_cmd(scope: str | None, force: bool) -> None:
|
|
|
682
728
|
parts.append(f"{file_actions} file{'s' if file_actions != 1 else ''}")
|
|
683
729
|
if settings_actions > 0:
|
|
684
730
|
parts.append(f"{settings_actions} setting{'s' if settings_actions != 1 else ''}")
|
|
731
|
+
if codex_actions > 0:
|
|
732
|
+
parts.append("Codex hooks")
|
|
685
733
|
console.print(f"\n[green]Sync complete.[/green] ({', '.join(parts)} updated)")
|
|
686
734
|
|
|
735
|
+
# A synced block can carry NEW entries whose trust is not yet
|
|
736
|
+
# granted (per-entry trusted_hash) -- the ceremony guidance
|
|
737
|
+
# matters most exactly here.
|
|
738
|
+
_print_codex_completion(plan, install_scope)
|
|
739
|
+
|
|
687
740
|
except NoForgeInstallationError as e:
|
|
688
741
|
console.print(f"[red]Error:[/red] {e}")
|
|
689
742
|
sys.exit(1)
|
|
@@ -788,6 +841,10 @@ def disable_cmd(scope: str | None, uninstall_all: bool, yes: bool) -> None:
|
|
|
788
841
|
console.print("[bold]Settings:[/bold]")
|
|
789
842
|
console.print(table)
|
|
790
843
|
|
|
844
|
+
if existing.codex_config_path:
|
|
845
|
+
console.print("\n[bold]Codex hooks:[/bold]")
|
|
846
|
+
console.print(f" [red]remove[/red] managed block in {display_path(existing.codex_config_path)}")
|
|
847
|
+
|
|
791
848
|
if not yes:
|
|
792
849
|
if not click.confirm("\nProceed with disable?"):
|
|
793
850
|
console.print("[dim]Cancelled.[/dim]")
|
|
@@ -926,6 +983,8 @@ def status_cmd(scope: str | None, path: str | None, show_all: bool, as_json: boo
|
|
|
926
983
|
"modules": list(inst.modules_enabled),
|
|
927
984
|
"files_count": len(inst.files),
|
|
928
985
|
"settings_count": len(inst.settings_entries),
|
|
986
|
+
"codex_config_path": inst.codex_config_path,
|
|
987
|
+
"codex_commands": list(inst.codex_commands),
|
|
929
988
|
"installed_at": inst.installed_at,
|
|
930
989
|
"updated_at": inst.updated_at,
|
|
931
990
|
}
|
|
@@ -966,6 +1025,8 @@ def status_cmd(scope: str | None, path: str | None, show_all: bool, as_json: boo
|
|
|
966
1025
|
console.print(f" Modules: {', '.join(installation.modules_enabled)}")
|
|
967
1026
|
console.print(f" Files: {len(installation.files)}")
|
|
968
1027
|
console.print(f" Settings: {len(installation.settings_entries)} entries")
|
|
1028
|
+
if installation.codex_config_path:
|
|
1029
|
+
console.print(f" Codex: hooks registered in {display_path(installation.codex_config_path)}")
|
|
969
1030
|
console.print(f" Installed: {installation.installed_at}")
|
|
970
1031
|
console.print(f" Updated: {installation.updated_at}")
|
|
971
1032
|
|
|
@@ -16,9 +16,9 @@ from forge.core.ops.gc import CleanError, CleanReport, collect_clean_report, run
|
|
|
16
16
|
@click.command("clean")
|
|
17
17
|
@click.option(
|
|
18
18
|
"--scope",
|
|
19
|
-
type=click.Choice(["
|
|
20
|
-
default="
|
|
21
|
-
help="Scope:
|
|
19
|
+
type=click.Choice(["workspace", "project", "all"]),
|
|
20
|
+
default="workspace",
|
|
21
|
+
help="Scope: workspace (default), project, or all",
|
|
22
22
|
)
|
|
23
23
|
@click.option("--yes", "-y", is_flag=True, help="Actually delete (default is dry-run)")
|
|
24
24
|
@click.option("--verbose", "-v", is_flag=True, help="Show individual items")
|
|
@@ -31,7 +31,7 @@ def clean_cmd(scope: str, yes: bool, verbose: bool, as_json: bool) -> None:
|
|
|
31
31
|
Examples:
|
|
32
32
|
|
|
33
33
|
\b
|
|
34
|
-
forge clean # Dry-run (scope:
|
|
34
|
+
forge clean # Dry-run (scope: workspace)
|
|
35
35
|
forge clean --yes # Actually clean
|
|
36
36
|
forge clean --scope all --yes # Clean globally
|
|
37
37
|
forge clean --scope project # Current Forge project only
|
|
@@ -8,11 +8,12 @@ import click
|
|
|
8
8
|
@click.group(name="hook", hidden=True)
|
|
9
9
|
@click.pass_context
|
|
10
10
|
def hooks(ctx: click.Context) -> None:
|
|
11
|
-
"""Hook handlers invoked by
|
|
11
|
+
"""Hook handlers invoked by agent runtimes.
|
|
12
12
|
|
|
13
|
-
Most subcommands are invoked automatically by Claude Code
|
|
14
|
-
configured in .claude/settings.local.json
|
|
15
|
-
|
|
13
|
+
Most subcommands are invoked automatically by runtime hooks: Claude Code's
|
|
14
|
+
are configured in .claude/settings.local.json; Codex's (codex-policy-check)
|
|
15
|
+
are registered in a Codex config and require trust enrollment. The 'enable'
|
|
16
|
+
and 'disable' subcommands are user-facing.
|
|
16
17
|
"""
|
|
17
18
|
from forge.core.logging import configure_debug_logging
|
|
18
19
|
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Parser for Codex's apply_patch envelope (PreToolUse ``tool_input.command``).
|
|
2
|
+
|
|
3
|
+
System boundary: the patch text arrives in a Codex hook payload (external data),
|
|
4
|
+
so the parser is strict but the caller fails open -- ``None`` means "not a patch
|
|
5
|
+
Forge can reason about", and the hook allows the action rather than guessing.
|
|
6
|
+
Codex's own apply_patch rejects input outside this grammar, so failing open on
|
|
7
|
+
malformed text converges with native behavior.
|
|
8
|
+
|
|
9
|
+
Grammar (codex-cli 0.138.0; witness fixture in ``tests/fixtures/codex/hooks/``)::
|
|
10
|
+
|
|
11
|
+
*** Begin Patch
|
|
12
|
+
*** Add File: <path> | *** Update File: <path> | *** Delete File: <path>
|
|
13
|
+
*** Move to: <path> (only immediately after an Update header)
|
|
14
|
+
<body lines prefixed +, -, space, or @@; blank lines are context>
|
|
15
|
+
*** End of File (tolerated inside add/update sections)
|
|
16
|
+
*** End Patch
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from typing import Literal
|
|
23
|
+
|
|
24
|
+
from forge.policy.types import extract_added_lines
|
|
25
|
+
|
|
26
|
+
PatchOpKind = Literal["add", "update", "delete"]
|
|
27
|
+
|
|
28
|
+
_BEGIN = "*** Begin Patch"
|
|
29
|
+
_END = "*** End Patch"
|
|
30
|
+
_EOF_MARKER = "*** End of File"
|
|
31
|
+
_MOVE_TO = "*** Move to: "
|
|
32
|
+
_HEADERS: tuple[tuple[str, PatchOpKind], ...] = (
|
|
33
|
+
("*** Add File: ", "add"),
|
|
34
|
+
("*** Update File: ", "update"),
|
|
35
|
+
("*** Delete File: ", "delete"),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class PatchFileOp:
|
|
41
|
+
"""One file operation parsed from an apply_patch envelope.
|
|
42
|
+
|
|
43
|
+
``path`` is the post-op path (the "Move to" target when present) -- policies
|
|
44
|
+
judge where content lands, not where it came from. The pre-move path is
|
|
45
|
+
recoverable from ``raw_section``.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
kind: PatchOpKind
|
|
49
|
+
path: str
|
|
50
|
+
move_to: str | None
|
|
51
|
+
added_content: str # introduced lines ("" for delete)
|
|
52
|
+
raw_section: str # verbatim header + body (raw_diff source)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class _Section:
|
|
57
|
+
kind: PatchOpKind
|
|
58
|
+
path: str
|
|
59
|
+
move_to: str | None = None
|
|
60
|
+
header_lines: list[str] = field(default_factory=list)
|
|
61
|
+
body: list[str] = field(default_factory=list)
|
|
62
|
+
|
|
63
|
+
def finalize(self) -> PatchFileOp:
|
|
64
|
+
body = "\n".join(self.body)
|
|
65
|
+
return PatchFileOp(
|
|
66
|
+
kind=self.kind,
|
|
67
|
+
path=self.move_to or self.path,
|
|
68
|
+
move_to=self.move_to,
|
|
69
|
+
added_content="" if self.kind == "delete" else extract_added_lines(body),
|
|
70
|
+
raw_section="\n".join(self.header_lines + self.body),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def parse_apply_patch(command: str) -> list[PatchFileOp] | None:
|
|
75
|
+
"""Parse an apply_patch envelope into per-file operations.
|
|
76
|
+
|
|
77
|
+
Returns None for anything outside the known grammar (caller fails open);
|
|
78
|
+
an empty envelope (Begin + End only) returns [].
|
|
79
|
+
"""
|
|
80
|
+
lines = [line.rstrip("\r") for line in command.split("\n")]
|
|
81
|
+
while lines and not lines[0].strip():
|
|
82
|
+
lines.pop(0)
|
|
83
|
+
while lines and not lines[-1].strip():
|
|
84
|
+
lines.pop()
|
|
85
|
+
if not lines or lines[0] != _BEGIN or lines[-1] != _END or len(lines) < 2:
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
ops: list[PatchFileOp] = []
|
|
89
|
+
current: _Section | None = None
|
|
90
|
+
|
|
91
|
+
for line in lines[1:-1]:
|
|
92
|
+
header = _match_header(line)
|
|
93
|
+
if header is not None:
|
|
94
|
+
kind, path = header
|
|
95
|
+
if not path:
|
|
96
|
+
return None
|
|
97
|
+
if current is not None:
|
|
98
|
+
ops.append(current.finalize())
|
|
99
|
+
current = _Section(kind=kind, path=path, header_lines=[line])
|
|
100
|
+
elif line.startswith(_MOVE_TO):
|
|
101
|
+
# Only valid immediately after an Update header (no body yet, one move max).
|
|
102
|
+
target = line[len(_MOVE_TO) :].strip()
|
|
103
|
+
if current is None or current.kind != "update" or current.body or current.move_to or not target:
|
|
104
|
+
return None
|
|
105
|
+
current.move_to = target
|
|
106
|
+
current.header_lines.append(line)
|
|
107
|
+
elif line == _EOF_MARKER:
|
|
108
|
+
if current is None or current.kind == "delete":
|
|
109
|
+
return None
|
|
110
|
+
current.body.append(line) # kept verbatim in raw_section; extract_added_lines ignores it
|
|
111
|
+
elif current is None:
|
|
112
|
+
return None # body line before any section header
|
|
113
|
+
elif current.kind == "delete":
|
|
114
|
+
return None # Delete sections are bodyless in the grammar
|
|
115
|
+
elif line == "" or line.startswith(("+", "-", " ", "@@")):
|
|
116
|
+
current.body.append(line)
|
|
117
|
+
else:
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
if current is not None:
|
|
121
|
+
ops.append(current.finalize())
|
|
122
|
+
return ops
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _match_header(line: str) -> tuple[PatchOpKind, str] | None:
|
|
126
|
+
for prefix, kind in _HEADERS:
|
|
127
|
+
if line.startswith(prefix):
|
|
128
|
+
return kind, line[len(prefix) :].strip()
|
|
129
|
+
return None
|