@pugi/cli 0.1.0-beta.10 → 0.1.0-beta.101
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 +132 -0
- package/LICENSE +1 -1
- package/README.md +55 -11
- package/assets/pugi-prozr2-mascot.ansi +9 -0
- package/bin/run.js +33 -1
- package/dist/commands/deploy.js +40 -40
- package/dist/commands/flatten.js +191 -0
- package/dist/commands/jobs-watch.js +201 -0
- package/dist/commands/jobs.js +42 -27
- package/dist/commands/retro.js +210 -0
- package/dist/commands/smoke.js +133 -0
- package/dist/core/agent-progress/cleanup.js +134 -0
- package/dist/core/agent-progress/schema.js +144 -0
- package/dist/core/agent-progress/writer.js +101 -0
- package/dist/core/agents/adaptive-router.js +330 -0
- package/dist/core/agents/query-decomposer.js +297 -0
- package/dist/core/agents/registry.js +3 -3
- package/dist/core/approvals/shortcut-resolver.js +98 -0
- package/dist/core/artifact-chain/dispatcher.js +148 -0
- package/dist/core/artifact-chain/exporter.js +164 -0
- package/dist/core/artifact-chain/state.js +243 -0
- package/dist/core/artifact-chain/steps.js +169 -0
- package/dist/core/ask-user/question.js +92 -0
- package/dist/core/audit/audit-trail.js +275 -0
- package/dist/core/auth/ensure-authenticated.js +129 -0
- package/dist/core/auth/env-provider.js +238 -0
- package/dist/core/auto-open-browser.js +4 -4
- package/dist/core/auto-update/channels.js +122 -0
- package/dist/core/auto-update/checker.js +241 -0
- package/dist/core/auto-update/state.js +235 -0
- package/dist/core/bare-mode/index.js +107 -0
- package/dist/core/bash/redirect.js +281 -0
- package/dist/core/bash-classifier.js +436 -40
- package/dist/core/checkpoint/resumer.js +149 -0
- package/dist/core/checkpoint/rewinder.js +291 -0
- package/dist/core/checkpoints/shadow-git.js +670 -0
- package/dist/core/citations/parser.js +109 -0
- package/dist/core/classifier/yolo-classifier.js +88 -0
- package/dist/core/codegraph/db.js +506 -0
- package/dist/core/codegraph/decision-store.js +248 -0
- package/dist/core/codegraph/detect-repo.js +459 -0
- package/dist/core/codegraph/install.js +134 -0
- package/dist/core/codegraph/offer-hook.js +220 -0
- package/dist/core/codegraph/parser.js +598 -0
- package/dist/core/codegraph/queries/go.scm +57 -0
- package/dist/core/codegraph/queries/javascript.scm +56 -0
- package/dist/core/codegraph/queries/python.scm +55 -0
- package/dist/core/codegraph/queries/rust.scm +63 -0
- package/dist/core/codegraph/queries/typescript.scm +91 -0
- package/dist/core/codegraph/reindex.js +218 -0
- package/dist/core/codegraph/resolve-edges.js +107 -0
- package/dist/core/codegraph/types.js +34 -0
- package/dist/core/codegraph/watcher.js +440 -0
- package/dist/core/compact/auto-trigger.js +96 -0
- package/dist/core/compact/buffer-rewriter.js +115 -0
- package/dist/core/compact/summarizer.js +208 -0
- package/dist/core/compact/token-counter.js +108 -0
- package/dist/core/consensus/anvil-fanout.js +25 -25
- package/dist/core/consensus/diff-capture.js +121 -12
- package/dist/core/consensus/rubric.js +21 -21
- package/dist/core/context/builder.js +6 -6
- package/dist/core/context/compaction-events.js +8 -8
- package/dist/core/context/compaction.js +31 -31
- package/dist/core/context/index.js +15 -8
- package/dist/core/context/invariants.js +51 -51
- package/dist/core/context/markdown-loader.js +28 -10
- package/dist/core/context/markdown-traverse.js +255 -0
- package/dist/core/context/pugiignore.js +41 -41
- package/dist/core/context/repo-skeleton.js +37 -37
- package/dist/core/context/tool-eviction.js +55 -0
- package/dist/core/context/watcher.js +32 -32
- package/dist/core/context/working-set.js +23 -23
- package/dist/core/coordinator/agent-tools.js +77 -0
- package/dist/core/coordinator/agent-toolset.js +65 -0
- package/dist/core/coordinator/fsm.js +73 -0
- package/dist/core/coordinator/mode-fsm.js +70 -0
- package/dist/core/cost/rate-card.js +129 -0
- package/dist/core/cost/tracker.js +221 -0
- package/dist/core/credentials.js +13 -13
- package/dist/core/cron/scheduler.js +138 -0
- package/dist/core/denial-tracking/index.js +8 -0
- package/dist/core/denial-tracking/state.js +264 -0
- package/dist/core/diagnostics/probe-runner.js +93 -0
- package/dist/core/diagnostics/probes/api.js +46 -0
- package/dist/core/diagnostics/probes/auth.js +93 -0
- package/dist/core/diagnostics/probes/bare-mode.js +42 -0
- package/dist/core/diagnostics/probes/cli-version.js +127 -0
- package/dist/core/diagnostics/probes/config.js +72 -0
- package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
- package/dist/core/diagnostics/probes/disk.js +81 -0
- package/dist/core/diagnostics/probes/engine-live.js +46 -0
- package/dist/core/diagnostics/probes/git.js +65 -0
- package/dist/core/diagnostics/probes/hooks.js +118 -0
- package/dist/core/diagnostics/probes/mcp.js +75 -0
- package/dist/core/diagnostics/probes/node.js +59 -0
- package/dist/core/diagnostics/probes/pnpm.js +36 -0
- package/dist/core/diagnostics/probes/pugi-md.js +89 -0
- package/dist/core/diagnostics/probes/sandbox.js +67 -0
- package/dist/core/diagnostics/probes/session.js +74 -0
- package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
- package/dist/core/diagnostics/probes/workspace.js +63 -0
- package/dist/core/diagnostics/types.js +70 -0
- package/dist/core/dispatch/cache-cleanup.js +197 -0
- package/dist/core/dispatch/cache-handoff.js +295 -0
- package/dist/core/edits/apply-patch-layer-e.js +189 -0
- package/dist/core/edits/dispatch.js +333 -7
- package/dist/core/edits/format-detector.js +260 -0
- package/dist/core/edits/format-matrix.js +26 -0
- package/dist/core/edits/fuzzy-ladder.js +650 -0
- package/dist/core/edits/index.js +5 -1
- package/dist/core/edits/journal.js +199 -0
- package/dist/core/edits/layer-a-apply.js +15 -15
- package/dist/core/edits/layer-a-fuzzy-apply.js +198 -0
- package/dist/core/edits/layer-b-apply.js +9 -9
- package/dist/core/edits/layer-c-apply.js +6 -6
- package/dist/core/edits/layer-d-ast.js +557 -14
- package/dist/core/edits/marker-parser.js +12 -12
- package/dist/core/edits/security-gate.js +27 -27
- package/dist/core/edits/verify-hook.js +273 -0
- package/dist/core/edits/worktree.js +29 -29
- package/dist/core/engine/anvil-client.js +214 -26
- package/dist/core/engine/auto-compact.js +247 -0
- package/dist/core/engine/budgets.js +220 -0
- package/dist/core/engine/compact-llm-summarizer.js +124 -0
- package/dist/core/engine/context-prefix.js +155 -0
- package/dist/core/engine/index.js +1 -1
- package/dist/core/engine/intensity.js +163 -0
- package/dist/core/engine/intent.js +260 -0
- package/dist/core/engine/native-pugi.js +1559 -227
- package/dist/core/engine/prompts.js +219 -19
- package/dist/core/engine/strip-internal-fields.js +124 -0
- package/dist/core/engine/tool-bridge.js +1887 -59
- package/dist/core/engine/verification-patterns.js +195 -0
- package/dist/core/eval/v1/ledger.js +83 -0
- package/dist/core/eval/v1/runner.js +280 -0
- package/dist/core/eval/v1/scoring.js +68 -0
- package/dist/core/eval/v1/task-loader.js +191 -0
- package/dist/core/eval/v1/types.js +14 -0
- package/dist/core/eval/v1/verifier.js +176 -0
- package/dist/core/eval/v1/yaml-parser.js +250 -0
- package/dist/core/evaluation/golden-dataset.js +293 -0
- package/dist/core/feedback/queue.js +177 -0
- package/dist/core/feedback/submitter.js +145 -0
- package/dist/core/file-cache.js +113 -1
- package/dist/core/flatten/flatten-repo.js +439 -0
- package/dist/core/format/osc8-link.js +28 -0
- package/dist/core/hook-chains.js +392 -0
- package/dist/core/hooks/citation-verify-hook.js +138 -0
- package/dist/core/hooks/citation-verify.js +112 -0
- package/dist/core/hooks/events.js +46 -0
- package/dist/core/hooks/index.js +15 -0
- package/dist/core/hooks/registry.js +216 -0
- package/dist/core/hooks/runner.js +236 -0
- package/dist/core/hooks/v2/event-emitter.js +115 -0
- package/dist/core/hooks/v2/executor.js +282 -0
- package/dist/core/hooks/v2/index.js +25 -0
- package/dist/core/hooks/v2/lifecycle.js +104 -0
- package/dist/core/hooks/v2/loader.js +216 -0
- package/dist/core/hooks/v2/matcher.js +125 -0
- package/dist/core/hooks/v2/trust.js +143 -0
- package/dist/core/hooks/v2/types.js +86 -0
- package/dist/core/hooks/worktree-events.js +158 -0
- package/dist/core/image/renderer.js +71 -0
- package/dist/core/init/detector.js +582 -0
- package/dist/core/init/template-renderer.js +242 -0
- package/dist/core/jobs/registry.js +18 -18
- package/dist/core/ledger/results-tsv.js +142 -0
- package/dist/core/log-discipline/stdout-redirect.js +51 -0
- package/dist/core/lsp/cache.js +105 -0
- package/dist/core/lsp/client.js +551 -41
- package/dist/core/lsp/language-detect.js +66 -0
- package/dist/core/lsp/post-edit-diagnostics.js +171 -0
- package/dist/core/lsp/server-detect.js +173 -0
- package/dist/core/lsp/symbol-cache.js +162 -0
- package/dist/core/lsp/symbol-tools.js +664 -0
- package/dist/core/mcp/client.js +97 -28
- package/dist/core/mcp/http-server.js +553 -0
- package/dist/core/mcp/orchestrator-config.js +192 -0
- package/dist/core/mcp/orchestrator-tools.js +806 -0
- package/dist/core/mcp/permission.js +190 -0
- package/dist/core/mcp/registry.js +39 -17
- package/dist/core/mcp/server-tools.js +219 -0
- package/dist/core/mcp/server.js +397 -0
- package/dist/core/mcp/trust.js +10 -10
- package/dist/core/memory/dual-write.js +416 -0
- package/dist/core/memory/passive-extract.js +130 -0
- package/dist/core/memory/phase1-kinds.js +20 -0
- package/dist/core/memory/secret-scanner.js +304 -0
- package/dist/core/memory-sync/queue.js +170 -0
- package/dist/core/metrics/extract.js +113 -0
- package/dist/core/modes/roo-modes.js +68 -0
- package/dist/core/notes/notes-paths.js +113 -0
- package/dist/core/notes/notes-recorder.js +140 -0
- package/dist/core/notes/notes-writer.js +53 -0
- package/dist/core/notes/renderers.js +0 -0
- package/dist/core/notes/slug.js +105 -0
- package/dist/core/onboarding/ensure-initialized.js +133 -0
- package/dist/core/onboarding/marker.js +111 -0
- package/dist/core/onboarding/telemetry-state.js +108 -0
- package/dist/core/output-style/presets.js +176 -0
- package/dist/core/output-style/state.js +185 -0
- package/dist/core/path-security.js +287 -5
- package/dist/core/permission.js +82 -22
- package/dist/core/permissions/auto-classifier.js +124 -0
- package/dist/core/permissions/bash-parser.js +371 -0
- package/dist/core/permissions/circuit-breaker.js +83 -0
- package/dist/core/permissions/constrained-edit.js +91 -0
- package/dist/core/permissions/gate.js +278 -0
- package/dist/core/permissions/index.js +20 -0
- package/dist/core/permissions/mode.js +174 -0
- package/dist/core/permissions/network-egress.js +137 -0
- package/dist/core/permissions/state.js +241 -0
- package/dist/core/permissions/tool-class.js +107 -0
- package/dist/core/plan-mode/ui-state.js +51 -0
- package/dist/core/plans/plan-artifact.js +721 -0
- package/dist/core/policy-limits/etag-store.js +122 -0
- package/dist/core/prd-check/parser.js +215 -0
- package/dist/core/prd-check/reporter.js +127 -0
- package/dist/core/prd-check/session-review.js +557 -0
- package/dist/core/prd-check/verifiers.js +223 -0
- package/dist/core/prompt-cache/client-cache.js +99 -0
- package/dist/core/prompts/assembly.js +29 -0
- package/dist/core/prompts/registry.js +364 -0
- package/dist/core/pugi-gitignore.js +52 -0
- package/dist/core/pugi-md/cc-compat-rules.js +735 -0
- package/dist/core/pugi-md/context-injector.js +76 -0
- package/dist/core/pugi-md/walk-up.js +207 -0
- package/dist/core/python/uv-installer.js +270 -0
- package/dist/core/python/uv-resolver.js +83 -0
- package/dist/core/rate-limit/narrator.js +146 -0
- package/dist/core/recipes/cli-types.js +20 -0
- package/dist/core/recipes/loader.js +103 -0
- package/dist/core/recipes/runner.js +345 -0
- package/dist/core/recipes/schema.js +587 -0
- package/dist/core/release-notes/parser.js +241 -0
- package/dist/core/release-notes/state.js +116 -0
- package/dist/core/repl/ask.js +37 -37
- package/dist/core/repl/cancellation.js +26 -26
- package/dist/core/repl/cap-warning.js +4 -4
- package/dist/core/repl/clipboard-read.js +11 -11
- package/dist/core/repl/dispatch-fsm.js +12 -12
- package/dist/core/repl/engine-bridge.js +303 -0
- package/dist/core/repl/history-search.js +15 -15
- package/dist/core/repl/history.js +28 -18
- package/dist/core/repl/kill-ring.js +5 -5
- package/dist/core/repl/model-pricing.js +135 -0
- package/dist/core/repl/privacy-banner.js +22 -22
- package/dist/core/repl/session.js +2690 -229
- package/dist/core/repl/slash-commands.js +540 -41
- package/dist/core/repl/store/index.js +1 -1
- package/dist/core/repl/store/jsonl-log.js +22 -22
- package/dist/core/repl/store/lockfile.js +10 -10
- package/dist/core/repl/store/session-store.js +136 -107
- package/dist/core/repl/store/types.js +15 -15
- package/dist/core/repl/store/uuid-v7.js +12 -12
- package/dist/core/repl/tool-route.js +382 -0
- package/dist/core/repl/workspace-context.js +43 -21
- package/dist/core/repo-map/build.js +125 -0
- package/dist/core/repo-map/cache.js +185 -0
- package/dist/core/repo-map/extractor.js +254 -0
- package/dist/core/repo-map/formatter.js +145 -0
- package/dist/core/repo-map/page-rank.js +105 -0
- package/dist/core/repo-map/scanner.js +211 -0
- package/dist/core/retro/git-collector.js +251 -0
- package/dist/core/retro/health-card.js +25 -0
- package/dist/core/retro/metrics.js +342 -0
- package/dist/core/retro/narrative.js +249 -0
- package/dist/core/retro/plane-collector.js +274 -0
- package/dist/core/retro/pr-issue-link.js +65 -0
- package/dist/core/retro/types.js +16 -0
- package/dist/core/retry-budget/budget.js +284 -0
- package/dist/core/retry-budget/index.js +5 -0
- package/dist/core/retry-budget/retry-cap.js +74 -0
- package/dist/core/routing/lead-worker.js +43 -0
- package/dist/core/routing/pre-flight-estimator.js +108 -0
- package/dist/core/runs/run-tree.js +103 -0
- package/dist/core/sandboxing/adapter.js +43 -0
- package/dist/core/sandboxing/bubblewrap.js +209 -0
- package/dist/core/sandboxing/index.js +78 -0
- package/dist/core/sandboxing/none.js +19 -0
- package/dist/core/sandboxing/policy.js +97 -0
- package/dist/core/sandboxing/seatbelt.js +231 -0
- package/dist/core/security/injection-scanner.js +367 -0
- package/dist/core/security/output-filter.js +418 -0
- package/dist/core/session/env-file.js +105 -0
- package/dist/core/session/section-budgets.js +140 -0
- package/dist/core/session.js +119 -0
- package/dist/core/settings.js +402 -5
- package/dist/core/share/formatter.js +271 -0
- package/dist/core/share/redactor.js +221 -0
- package/dist/core/share/uploader.js +267 -0
- package/dist/core/skills/defaults.js +30 -30
- package/dist/core/skills/loader.js +22 -22
- package/dist/core/skills/sources.js +27 -27
- package/dist/core/smoke/headless-driver.js +174 -0
- package/dist/core/smoke/orchestrator.js +194 -0
- package/dist/core/smoke/runner.js +238 -0
- package/dist/core/smoke/scenario-parser.js +316 -0
- package/dist/core/statusline.js +99 -0
- package/dist/core/subagents/dispatcher-real.js +600 -0
- package/dist/core/subagents/dispatcher.js +146 -52
- package/dist/core/subagents/index.js +19 -6
- package/dist/core/subagents/isolation-matrix.js +213 -0
- package/dist/core/subagents/spawn.js +19 -4
- package/dist/core/telemetry/emitter.js +229 -0
- package/dist/core/telemetry/queue.js +251 -0
- package/dist/core/theme/context.js +91 -0
- package/dist/core/theme/presets.js +228 -0
- package/dist/core/theme/state.js +181 -0
- package/dist/core/todos/invariant.js +10 -0
- package/dist/core/todos/state.js +177 -0
- package/dist/core/tool-schema/compressor.js +89 -0
- package/dist/core/transport/version-interceptor.js +166 -0
- package/dist/core/trust.js +2 -2
- package/dist/core/tui/thinking-block.js +64 -0
- package/dist/core/vim/keymap.js +288 -0
- package/dist/core/vim/state.js +92 -0
- package/dist/core/watch-markers/marker-watcher.js +133 -0
- package/dist/core/worktree/include-parser.js +249 -0
- package/dist/core/worktree-manager/cleanup.js +123 -0
- package/dist/core/worktree-manager/manager.js +303 -0
- package/dist/index.js +36 -0
- package/dist/runtime/bootstrap.js +190 -0
- package/dist/runtime/cli.js +4403 -561
- package/dist/runtime/commands/agents.js +31 -31
- package/dist/runtime/commands/budget.js +5 -5
- package/dist/runtime/commands/cancel.js +231 -0
- package/dist/runtime/commands/chain.js +489 -0
- package/dist/runtime/commands/codegraph-status.js +227 -0
- package/dist/runtime/commands/compact.js +297 -0
- package/dist/runtime/commands/config.js +74 -40
- package/dist/runtime/commands/cost.js +199 -0
- package/dist/runtime/commands/delegate.js +27 -4
- package/dist/runtime/commands/dispatch.js +126 -0
- package/dist/runtime/commands/doctor.js +579 -0
- package/dist/runtime/commands/eval-v1.js +266 -0
- package/dist/runtime/commands/feedback.js +184 -0
- package/dist/runtime/commands/hooks.js +187 -0
- package/dist/runtime/commands/index-cmd.js +459 -0
- package/dist/runtime/commands/init.js +254 -0
- package/dist/runtime/commands/lsp.js +200 -38
- package/dist/runtime/commands/mcp.js +935 -0
- package/dist/runtime/commands/memory.js +582 -0
- package/dist/runtime/commands/model.js +237 -0
- package/dist/runtime/commands/onboarding.js +275 -0
- package/dist/runtime/commands/patch.js +12 -12
- package/dist/runtime/commands/permissions.js +112 -0
- package/dist/runtime/commands/plan.js +143 -0
- package/dist/runtime/commands/prd-check.js +285 -0
- package/dist/runtime/commands/privacy.js +17 -17
- package/dist/runtime/commands/recipe.js +325 -0
- package/dist/runtime/commands/redo-blob-store.js +92 -0
- package/dist/runtime/commands/redo.js +361 -0
- package/dist/runtime/commands/release-notes.js +229 -0
- package/dist/runtime/commands/repo-map.js +95 -0
- package/dist/runtime/commands/report.js +299 -0
- package/dist/runtime/commands/resume.js +118 -0
- package/dist/runtime/commands/review-consensus.js +68 -53
- package/dist/runtime/commands/rewind.js +333 -0
- package/dist/runtime/commands/roster.js +14 -14
- package/dist/runtime/commands/servers-cli.js +182 -0
- package/dist/runtime/commands/servers.js +236 -0
- package/dist/runtime/commands/sessions.js +163 -0
- package/dist/runtime/commands/share.js +316 -0
- package/dist/runtime/commands/skills.js +31 -31
- package/dist/runtime/commands/status.js +186 -0
- package/dist/runtime/commands/stickers.js +82 -0
- package/dist/runtime/commands/style.js +194 -0
- package/dist/runtime/commands/theme.js +196 -0
- package/dist/runtime/commands/undo.js +54 -22
- package/dist/runtime/commands/update.js +289 -0
- package/dist/runtime/commands/vim.js +140 -0
- package/dist/runtime/commands/worktree.js +8 -8
- package/dist/runtime/commands/worktrees.js +155 -0
- package/dist/runtime/deprecation-warning.js +69 -0
- package/dist/runtime/engine-exit-code.js +50 -0
- package/dist/runtime/headless-repl.js +195 -0
- package/dist/runtime/headless.js +548 -0
- package/dist/runtime/load-hooks-or-exit.js +71 -0
- package/dist/runtime/plan-decompose.js +22 -22
- package/dist/runtime/sigint-guard.js +272 -0
- package/dist/runtime/stream-renderer.js +195 -0
- package/dist/runtime/update-check.js +28 -28
- package/dist/runtime/version.js +65 -0
- package/dist/runtime/worktree-bootstrap.js +579 -0
- package/dist/skills/bundled/batch.js +617 -0
- package/dist/skills/bundled/index.js +45 -0
- package/dist/skills/bundled/loop.js +358 -0
- package/dist/skills/bundled/remember.js +383 -0
- package/dist/skills/bundled/simplify.js +289 -0
- package/dist/skills/bundled/skillify.js +373 -0
- package/dist/skills/bundled/stuck.js +558 -0
- package/dist/skills/bundled/verify.js +439 -0
- package/dist/testing/vcr.js +486 -0
- package/dist/tools/agent-tool.js +229 -0
- package/dist/tools/apply-patch.js +89 -28
- package/dist/tools/ask-user-question.js +337 -0
- package/dist/tools/ask-user.js +115 -0
- package/dist/tools/bash.js +811 -49
- package/dist/tools/brief.js +224 -0
- package/dist/tools/cron.js +433 -0
- package/dist/tools/enter-worktree.js +250 -0
- package/dist/tools/exit-worktree.js +147 -0
- package/dist/tools/file-tools.js +161 -44
- package/dist/tools/http-request.js +336 -0
- package/dist/tools/lsp-tools.js +377 -1
- package/dist/tools/mcp-tool.js +260 -0
- package/dist/tools/multi-edit.js +361 -0
- package/dist/tools/powershell.js +268 -0
- package/dist/tools/registry.js +120 -5
- package/dist/tools/server-tools.js +892 -0
- package/dist/tools/skill-tool.js +96 -0
- package/dist/tools/sleep.js +99 -0
- package/dist/tools/synthetic-output.js +133 -0
- package/dist/tools/tasks.js +208 -0
- package/dist/tools/todo-write.js +184 -0
- package/dist/tools/verify-plan-execution.js +295 -0
- package/dist/tools/web-fetch-injection-scanner.js +207 -0
- package/dist/tools/web-fetch.js +195 -10
- package/dist/tools/web-search.js +458 -0
- package/dist/tui/agent-progress-card.js +111 -0
- package/dist/tui/agent-tree.js +22 -1
- package/dist/tui/ask-modal.js +14 -14
- package/dist/tui/ask-user-question-chips.js +315 -0
- package/dist/tui/ask-user-question-prompt.js +203 -0
- package/dist/tui/compact-banner.js +81 -0
- package/dist/tui/conversation-pane.js +85 -11
- package/dist/tui/cost-table.js +111 -0
- package/dist/tui/device-flow.js +2 -2
- package/dist/tui/doctor-table.js +46 -0
- package/dist/tui/feedback-prompt.js +156 -0
- package/dist/tui/input-box.js +247 -32
- package/dist/tui/login-picker.js +3 -3
- package/dist/tui/markdown-render.js +6 -6
- package/dist/tui/multi-file-diff-approval.js +375 -0
- package/dist/tui/onboarding-wizard.js +240 -0
- package/dist/tui/permissions-picker.js +86 -0
- package/dist/tui/render.js +36 -1
- package/dist/tui/repl-render.js +239 -25
- package/dist/tui/repl-splash-art.js +16 -16
- package/dist/tui/repl-splash-mascot.js +48 -24
- package/dist/tui/repl-splash.js +22 -22
- package/dist/tui/repl.js +125 -45
- package/dist/tui/slash-palette.js +6 -6
- package/dist/tui/splash.js +2 -2
- package/dist/tui/status-bar.js +109 -31
- package/dist/tui/status-table.js +7 -0
- package/dist/tui/stickers-art.js +136 -0
- package/dist/tui/style-table.js +28 -0
- package/dist/tui/theme-table.js +29 -0
- package/dist/tui/thinking-spinner.js +123 -0
- package/dist/tui/tool-stream-pane.js +53 -4
- package/dist/tui/update-banner.js +27 -2
- package/dist/tui/vim-input.js +267 -0
- package/dist/tui/welcome-banner.js +107 -0
- package/dist/tui/welcome-data.js +293 -0
- package/dist/tui/workspace-context.js +2 -2
- package/package.json +29 -6
- package/test/scenarios/codegen-create-file.scenario.txt +13 -0
- package/test/scenarios/compact-force.scenario.txt +12 -0
- package/test/scenarios/identity.scenario.txt +11 -0
- package/test/scenarios/persona-handoff.scenario.txt +12 -0
- package/test/scenarios/walkback.scenario.txt +12 -0
- package/dist/core/engine/compaction-hook.js +0 -154
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Diff dispatch —
|
|
2
|
+
* Diff dispatch — escalation Phase 1, β1b Pl8 transactional layer
|
|
3
|
+
* .
|
|
3
4
|
*
|
|
4
5
|
* Reads a raw model response containing one or more SEARCH/REPLACE
|
|
5
6
|
* envelopes, normalises them through `marker-parser`, and routes each
|
|
6
7
|
* parsed edit to the correct applicator:
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* - `layer-a` → applyLayerA
|
|
10
|
+
* - `layer-b` → applyLayerB
|
|
11
|
+
* - `layer-c` → applyLayerC
|
|
12
|
+
* - `layer-d` → throws LayerDDeferredError, surfaced as a clean
|
|
13
|
+
* dispatch failure (Layer D ships in )
|
|
13
14
|
*
|
|
14
15
|
* Per-edit results are aggregated into `DispatchResult[]` so callers
|
|
15
16
|
* can render the full apply transcript even when some edits failed.
|
|
@@ -21,16 +22,39 @@
|
|
|
21
22
|
* the writeFile. A crash between the two leaves a recoverable trail —
|
|
22
23
|
* the operator (or `pugi resume`) sees the intent and can re-attempt.
|
|
23
24
|
*
|
|
25
|
+
* β1b Pl8 — transactional rollback: when the caller supplies
|
|
26
|
+
* `transactional: { sessionId, taskId, workspaceRoot }`, the dispatcher
|
|
27
|
+
* wraps the multi-file edits in a session journal (see
|
|
28
|
+
* `core/edits/journal.ts`). On non-zero exit / budget kill / partial
|
|
29
|
+
* fail it runs `rollbackDispatch()`:
|
|
30
|
+
*
|
|
31
|
+
* - tracked files that EXISTED before → `git restore -- <file>`
|
|
32
|
+
* - newly created files (existed=false) → `fs.unlink`
|
|
33
|
+
* - untracked files that EXISTED before → restore from in-memory
|
|
34
|
+
* pre-content snapshot (sha256_before validated)
|
|
35
|
+
*
|
|
36
|
+
* Pattern mirrors `tools/apply-patch.ts::rollbackFiles` (PR). The
|
|
37
|
+
* journal is the durability layer that lets a process crash recover
|
|
38
|
+
* across PIDs; the in-memory snapshot covers the single-process case
|
|
39
|
+
* where the journal write itself failed.
|
|
40
|
+
*
|
|
24
41
|
* The dispatcher is intentionally side-effect-light: no logging, no
|
|
25
42
|
* stdout writes, no exit-code mutation. The CLI integration layer in
|
|
26
43
|
* `cli.ts` owns operator-facing rendering; the dispatcher returns
|
|
27
44
|
* structured data and lets the caller decide UX.
|
|
28
45
|
*/
|
|
46
|
+
import { spawnSync } from 'node:child_process';
|
|
47
|
+
import { readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
48
|
+
import { resolve, sep } from 'node:path';
|
|
49
|
+
import { commitCheckpoint, formatCheckpointMessage, initShadowRepo, ShadowGitUnavailableError, } from '../checkpoints/shadow-git.js';
|
|
50
|
+
import { detectEditFormat } from './format-detector.js';
|
|
29
51
|
import { LayerDDeferredError, applyLayerD } from './layer-d-ast.js';
|
|
30
52
|
import { applyLayerA } from './layer-a-apply.js';
|
|
53
|
+
import { applyLayerAFuzzy } from './layer-a-fuzzy-apply.js';
|
|
31
54
|
import { applyLayerB } from './layer-b-apply.js';
|
|
32
55
|
import { applyLayerC } from './layer-c-apply.js';
|
|
33
56
|
import { MarkerParseError, parseMarkers, } from './marker-parser.js';
|
|
57
|
+
import { appendEntry, snapshotForDispatch, } from './journal.js';
|
|
34
58
|
/**
|
|
35
59
|
* Parse `raw` into edits and apply each in order. Aggregate results,
|
|
36
60
|
* preserving order. Never throws — parse failures surface as a single
|
|
@@ -39,6 +63,15 @@ import { MarkerParseError, parseMarkers, } from './marker-parser.js';
|
|
|
39
63
|
*/
|
|
40
64
|
export async function dispatchEdit(raw, opts) {
|
|
41
65
|
const family = resolveFamily(opts.modelTag);
|
|
66
|
+
// PUGI-79 — resolve the preferred-layer chain ONCE per dispatch.
|
|
67
|
+
// Caller supplied wins; otherwise the detector derives from modelTag.
|
|
68
|
+
// The chain is informational here (the parser still routes per
|
|
69
|
+
// envelope); a future fuzzy-ladder change can read it to influence
|
|
70
|
+
// retry ordering without revisiting the dispatcher's escalation
|
|
71
|
+
// contract.
|
|
72
|
+
const preferredChain = resolvePreferredChain(opts);
|
|
73
|
+
if (preferredChain)
|
|
74
|
+
opts.onPreferredLayer?.(preferredChain);
|
|
42
75
|
let parsed;
|
|
43
76
|
try {
|
|
44
77
|
parsed = parseMarkers(raw, family);
|
|
@@ -52,6 +85,7 @@ export async function dispatchEdit(raw, opts) {
|
|
|
52
85
|
bytesWritten: 0,
|
|
53
86
|
reason: 'marker_parse_error',
|
|
54
87
|
detail: `${error.message}${error.atLine ? ` (line ${error.atLine})` : ''} — modelHint=${error.modelHint}`,
|
|
88
|
+
...(preferredChain ? { expectedLayer: preferredChain.primary } : {}),
|
|
55
89
|
};
|
|
56
90
|
opts.onResult?.(result);
|
|
57
91
|
return [result];
|
|
@@ -65,16 +99,286 @@ export async function dispatchEdit(raw, opts) {
|
|
|
65
99
|
// render "no edits proposed".
|
|
66
100
|
return [];
|
|
67
101
|
}
|
|
102
|
+
// β1b Pl8 — transactional path. When enabled, snapshot every target
|
|
103
|
+
// file BEFORE the first applicator runs so we can roll back if a
|
|
104
|
+
// later edit fails. Snapshot also drives the journal entry so a
|
|
105
|
+
// post-crash recovery can replay rollback in a fresh process.
|
|
106
|
+
//
|
|
107
|
+
// The journal write itself is best-effort: a disk-full / EACCES
|
|
108
|
+
// failure must NOT block the dispatch. The in-memory snapshot
|
|
109
|
+
// still carries every pre-existing file's content, so single-
|
|
110
|
+
// process rollback degrades cleanly. The operator sees the
|
|
111
|
+
// journal-failed warning via the session events mirror (caller's
|
|
112
|
+
// responsibility to emit).
|
|
113
|
+
let snapshot = null;
|
|
114
|
+
let preContent = null;
|
|
115
|
+
if (opts.transactional && !opts.dryRun) {
|
|
116
|
+
const targets = Array.from(new Set(parsed.map((e) => editFile(e))));
|
|
117
|
+
snapshot = snapshotForDispatch(opts.transactional.workspaceRoot, targets);
|
|
118
|
+
preContent = new Map();
|
|
119
|
+
for (const e of snapshot) {
|
|
120
|
+
if (!e.existed)
|
|
121
|
+
continue;
|
|
122
|
+
const abs = resolve(opts.transactional.workspaceRoot, e.path);
|
|
123
|
+
try {
|
|
124
|
+
preContent.set(e.path, readFileSync(abs));
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
/* file vanished between snapshot + read — treat as not-snapshotted */
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
appendEntry(opts.transactional.workspaceRoot, opts.transactional.sessionId, {
|
|
131
|
+
ts: Date.now(),
|
|
132
|
+
taskId: opts.transactional.taskId,
|
|
133
|
+
files: snapshot,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
// Per-task shadow git checkpoint hook (backlog). Init the
|
|
137
|
+
// shadow once per dispatch, then commit after every successful
|
|
138
|
+
// edit. The init is best-effort — a missing `git` binary or a
|
|
139
|
+
// chmod failure surfaces through `onCheckpointError` but does not
|
|
140
|
+
// disable the dispatch.
|
|
141
|
+
let checkpointStep = opts.checkpoint?.stepStart && Number.isFinite(opts.checkpoint.stepStart)
|
|
142
|
+
? Math.max(1, Math.floor(opts.checkpoint.stepStart))
|
|
143
|
+
: 1;
|
|
144
|
+
let checkpointEnabled = false;
|
|
145
|
+
if (opts.checkpoint && !opts.dryRun) {
|
|
146
|
+
try {
|
|
147
|
+
initShadowRepo(opts.cwd, opts.checkpoint.taskId);
|
|
148
|
+
checkpointEnabled = true;
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
152
|
+
opts.checkpoint.onCheckpointError?.(err, '');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const expectedLayer = preferredChain?.primary;
|
|
68
156
|
const out = [];
|
|
157
|
+
let crashError = null;
|
|
69
158
|
for (const edit of parsed) {
|
|
70
159
|
const intent = makeIntent(edit);
|
|
71
160
|
opts.onIntent?.(intent);
|
|
72
|
-
|
|
161
|
+
let result;
|
|
162
|
+
try {
|
|
163
|
+
result = await applyOne(edit, opts);
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
// applyOne does not throw today (errors are returned as
|
|
167
|
+
// `ok: false`), but a future applicator that does throw —
|
|
168
|
+
// or a budget-kill that arrives mid-write — needs deterministic
|
|
169
|
+
// rollback. Catch + record + break so the rollback below runs.
|
|
170
|
+
crashError = error;
|
|
171
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
172
|
+
result = {
|
|
173
|
+
layer: 'layer-a',
|
|
174
|
+
file: editFile(edit),
|
|
175
|
+
ok: false,
|
|
176
|
+
bytesWritten: 0,
|
|
177
|
+
reason: 'apply_error',
|
|
178
|
+
detail: `dispatch threw: ${msg}`,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (expectedLayer !== undefined) {
|
|
182
|
+
// PUGI-79: stamp the expected layer on every result so
|
|
183
|
+
// observability surfaces can compute drift (expected vs actual)
|
|
184
|
+
// per family. The actual `layer` field is unchanged — the
|
|
185
|
+
// hint is informational, not a routing override.
|
|
186
|
+
result = { ...result, expectedLayer };
|
|
187
|
+
}
|
|
73
188
|
out.push(result);
|
|
74
189
|
opts.onResult?.(result);
|
|
190
|
+
if (result.ok && checkpointEnabled && opts.checkpoint && !opts.dryRun) {
|
|
191
|
+
// Best-effort shadow commit. Failures here MUST NOT block the
|
|
192
|
+
// dispatch — the operator already has the edit applied; the
|
|
193
|
+
// checkpoint is a customer-trust safety net, not a hard gate.
|
|
194
|
+
try {
|
|
195
|
+
const absPath = result.absPath ?? resolve(opts.cwd, result.file);
|
|
196
|
+
const message = formatCheckpointMessage({
|
|
197
|
+
taskId: opts.checkpoint.taskId,
|
|
198
|
+
step: checkpointStep,
|
|
199
|
+
toolName: result.layer,
|
|
200
|
+
cwd: opts.cwd,
|
|
201
|
+
absPath,
|
|
202
|
+
});
|
|
203
|
+
commitCheckpoint(opts.cwd, opts.checkpoint.taskId, message);
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
207
|
+
// Disable the shadow hook for the rest of this dispatch if
|
|
208
|
+
// the git binary is unavailable — repeated probes burn CPU
|
|
209
|
+
// and add no signal beyond the first failure.
|
|
210
|
+
if (err instanceof ShadowGitUnavailableError) {
|
|
211
|
+
checkpointEnabled = false;
|
|
212
|
+
}
|
|
213
|
+
opts.checkpoint.onCheckpointError?.(err, result.file);
|
|
214
|
+
}
|
|
215
|
+
checkpointStep += 1;
|
|
216
|
+
}
|
|
217
|
+
if (!result.ok && opts.transactional && snapshot && preContent) {
|
|
218
|
+
// Rollback every snapshotted file then break out — partial
|
|
219
|
+
// success is unacceptable in transactional mode.
|
|
220
|
+
const rollback = rollbackDispatch(opts.transactional.workspaceRoot, snapshot, preContent);
|
|
221
|
+
if (!rollback.ok) {
|
|
222
|
+
// Surface the rollback failure as an additional synthetic
|
|
223
|
+
// result so the caller can render the operator-facing
|
|
224
|
+
// message without losing the original failure context.
|
|
225
|
+
const failure = {
|
|
226
|
+
layer: 'layer-a',
|
|
227
|
+
file: '',
|
|
228
|
+
ok: false,
|
|
229
|
+
bytesWritten: 0,
|
|
230
|
+
reason: 'rollback_failed',
|
|
231
|
+
detail: rollback.detail,
|
|
232
|
+
};
|
|
233
|
+
out.push(failure);
|
|
234
|
+
opts.onResult?.(failure);
|
|
235
|
+
}
|
|
236
|
+
if (crashError) {
|
|
237
|
+
// Re-throw post-rollback so the caller learns the dispatch
|
|
238
|
+
// crashed (vs returned ok: false). Rollback already completed
|
|
239
|
+
// so the workspace is consistent.
|
|
240
|
+
throw crashError;
|
|
241
|
+
}
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
75
244
|
}
|
|
76
245
|
return out;
|
|
77
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* Workspace-relative path for a parsed edit, regardless of layer.
|
|
249
|
+
* Hoisted because the snapshot + intent both need the same answer.
|
|
250
|
+
*/
|
|
251
|
+
function editFile(edit) {
|
|
252
|
+
switch (edit.kind) {
|
|
253
|
+
case 'layer-a':
|
|
254
|
+
case 'layer-b':
|
|
255
|
+
case 'layer-c':
|
|
256
|
+
case 'layer-d':
|
|
257
|
+
return edit.edit.file;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Roll the workspace back to the pre-dispatch state captured in
|
|
262
|
+
* `snapshot`. Mirrors `tools/apply-patch.ts::rollbackFiles`:
|
|
263
|
+
*
|
|
264
|
+
* - existed-before + git-tracked → `git restore -- <file>` (cheap
|
|
265
|
+
* + atomic against the index).
|
|
266
|
+
* - existed-before + untracked → restore from the in-memory
|
|
267
|
+
* preContent buffer (sha256_before validates the snapshot still
|
|
268
|
+
* matches what we hold; if not we punt with `partial_rollback`).
|
|
269
|
+
* - newly created → fs.unlink (force).
|
|
270
|
+
*
|
|
271
|
+
* Best-effort: every per-file failure is collected into the detail
|
|
272
|
+
* string so the operator can manually fix the residual state. The
|
|
273
|
+
* dispatcher does not abort on the first error so an unrelated
|
|
274
|
+
* permission glitch on one file doesn't strand the others.
|
|
275
|
+
*
|
|
276
|
+
* Exported for the spec suite + a future operator command
|
|
277
|
+
* (`pugi resume --rollback <taskId>` ships in β2).
|
|
278
|
+
*/
|
|
279
|
+
export function rollbackDispatch(workspaceRoot, snapshot, preContent) {
|
|
280
|
+
if (snapshot.length === 0)
|
|
281
|
+
return { ok: true };
|
|
282
|
+
// Filter to workspace-internal paths only. A snapshot entry that
|
|
283
|
+
// escaped the workspace would already have aborted upstream; the
|
|
284
|
+
// filter is belt + braces against a future bug.
|
|
285
|
+
const safe = snapshot.filter((e) => {
|
|
286
|
+
const abs = resolve(workspaceRoot, e.path);
|
|
287
|
+
return abs === workspaceRoot || abs.startsWith(workspaceRoot + sep);
|
|
288
|
+
});
|
|
289
|
+
const failures = [];
|
|
290
|
+
const tracked = listTrackedFiles(workspaceRoot, safe.map((e) => e.path));
|
|
291
|
+
for (const entry of safe) {
|
|
292
|
+
const abs = resolve(workspaceRoot, entry.path);
|
|
293
|
+
if (!entry.existed) {
|
|
294
|
+
try {
|
|
295
|
+
// β1b r1: `recursive: true` so rollback handles the case where
|
|
296
|
+
// the dispatcher created an intermediate directory (e.g. a new
|
|
297
|
+
// `src/feature/` tree). Without it the unlink fails on a dir
|
|
298
|
+
// and the journal-replay leaves an orphan workspace path.
|
|
299
|
+
rmSync(abs, { force: true, recursive: true });
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
failures.push(`${entry.path}: unlink failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
303
|
+
}
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
if (tracked.has(entry.path)) {
|
|
307
|
+
const result = spawnSync('git', ['restore', '--', entry.path], {
|
|
308
|
+
cwd: workspaceRoot,
|
|
309
|
+
encoding: 'utf8',
|
|
310
|
+
});
|
|
311
|
+
if (result.status !== 0) {
|
|
312
|
+
failures.push(`${entry.path}: git restore failed: ${(result.stderr ?? '').trim() || 'non-zero exit'}`);
|
|
313
|
+
}
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
// Untracked-but-existed: write back from memory.
|
|
317
|
+
const buf = preContent.get(entry.path);
|
|
318
|
+
if (!buf) {
|
|
319
|
+
failures.push(`${entry.path}: pre-content snapshot missing (partial rollback)`);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
writeFileSync(abs, buf);
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
failures.push(`${entry.path}: rewrite failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (failures.length === 0)
|
|
330
|
+
return { ok: true };
|
|
331
|
+
return { ok: false, detail: failures.join('; ') };
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Ask git which of `paths` are tracked. A single `git ls-files
|
|
335
|
+
* --error-unmatch` call would fail-fast on the first untracked path,
|
|
336
|
+
* so we use `git ls-files -- <paths...>` which lists only tracked
|
|
337
|
+
* matches. Returns a Set of workspace-relative paths.
|
|
338
|
+
*
|
|
339
|
+
* Pure-stdlib fallback when git is unavailable: returns an empty
|
|
340
|
+
* Set — every "existed" entry then routes to the untracked-restore
|
|
341
|
+
* path via the in-memory preContent map. Slower per file but still
|
|
342
|
+
* correct.
|
|
343
|
+
*/
|
|
344
|
+
function listTrackedFiles(cwd, paths) {
|
|
345
|
+
if (paths.length === 0)
|
|
346
|
+
return new Set();
|
|
347
|
+
const result = spawnSync('git', ['ls-files', '--', ...paths], {
|
|
348
|
+
cwd,
|
|
349
|
+
encoding: 'utf8',
|
|
350
|
+
});
|
|
351
|
+
if (result.status !== 0)
|
|
352
|
+
return new Set();
|
|
353
|
+
const out = new Set();
|
|
354
|
+
for (const line of (result.stdout ?? '').split('\n')) {
|
|
355
|
+
const trimmed = line.trim();
|
|
356
|
+
if (trimmed.length > 0)
|
|
357
|
+
out.add(trimmed);
|
|
358
|
+
}
|
|
359
|
+
return out;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* PUGI-79 helper — resolve the preferred-layer chain for this
|
|
363
|
+
* dispatch. Caller-supplied `preferredLayer` wins; otherwise the
|
|
364
|
+
* detector derives from `modelTag`. Returns null when neither path
|
|
365
|
+
* yields a chain (no caller hint AND no model tag) — the dispatcher
|
|
366
|
+
* skips the observability hook in that case so we do not emit a
|
|
367
|
+
* misleading wildcard reading.
|
|
368
|
+
*/
|
|
369
|
+
function resolvePreferredChain(opts) {
|
|
370
|
+
if (opts.preferredLayer) {
|
|
371
|
+
// Defensive clone so caller mutations after dispatchEdit returns
|
|
372
|
+
// cannot poison subsequent calls that share the chain reference.
|
|
373
|
+
return {
|
|
374
|
+
primary: opts.preferredLayer.primary,
|
|
375
|
+
fallback: [...opts.preferredLayer.fallback],
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
if (opts.modelTag)
|
|
379
|
+
return detectEditFormat(opts.modelTag);
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
78
382
|
/**
|
|
79
383
|
* Public helper exposed for the marker parser tests + CLI surface that
|
|
80
384
|
* may want to know the resolved family without re-running the auto
|
|
@@ -125,6 +429,28 @@ async function applyOne(edit, opts) {
|
|
|
125
429
|
switch (edit.kind) {
|
|
126
430
|
case 'layer-a': {
|
|
127
431
|
const r = await applyLayerA(edit.edit, applyOpts);
|
|
432
|
+
// Backlog — Layer A.5 escalation. The fuzzy ladder is the
|
|
433
|
+
// ONLY retry path: every other Layer A failure mode (ambiguous,
|
|
434
|
+
// file_missing, identical_replacement, security gate denial)
|
|
435
|
+
// bubbles up untouched because the ladder can't fix them.
|
|
436
|
+
// ambiguous in particular MUST stay loud — a fuzzy retry on top
|
|
437
|
+
// of an ambiguous strict match would just pick one of the
|
|
438
|
+
// strict candidates silently, defeating the disambiguation
|
|
439
|
+
// contract.
|
|
440
|
+
if (!r.ok && r.reason === 'no_match' && opts.fuzzy === true) {
|
|
441
|
+
const fuzzy = await applyLayerAFuzzy(edit.edit, {
|
|
442
|
+
cwd: opts.cwd,
|
|
443
|
+
dryRun: opts.dryRun,
|
|
444
|
+
minRatio: opts.fuzzyMinRatio,
|
|
445
|
+
lengthFlex: opts.fuzzyLengthFlex,
|
|
446
|
+
});
|
|
447
|
+
const base = toResult('layer-a-fuzzy', edit.edit.file, fuzzy);
|
|
448
|
+
return {
|
|
449
|
+
...base,
|
|
450
|
+
...(fuzzy.fuzzyTier ? { fuzzyTier: fuzzy.fuzzyTier } : {}),
|
|
451
|
+
...(fuzzy.fuzzyScore !== undefined ? { fuzzyScore: fuzzy.fuzzyScore } : {}),
|
|
452
|
+
};
|
|
453
|
+
}
|
|
128
454
|
return toResult('layer-a', edit.edit.file, r);
|
|
129
455
|
}
|
|
130
456
|
case 'layer-b': {
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edit-format auto-select (PUGI-79).
|
|
3
|
+
*
|
|
4
|
+
* The 4-layer diff dispatcher (Layer A single-block, B ordered batch,
|
|
5
|
+
* C sha256-gated rewrite, D AST-aware) is layer-agnostic at the wire
|
|
6
|
+
* — the model emits whichever marker family it was prompted with, and
|
|
7
|
+
* `dispatch.ts` routes per envelope. Empirically though, different
|
|
8
|
+
* model families have markedly different success rates per layer:
|
|
9
|
+
*
|
|
10
|
+
* - Anthropic Claude family — native function-calling shape;
|
|
11
|
+
* produces clean single-block Layer A edits with high recall
|
|
12
|
+
* against `+++ NEW / --- OLD / ===` markers.
|
|
13
|
+
* - Open-weight Qwen3 / Kimi / DeepSeek — historically stronger
|
|
14
|
+
* with whole-file Layer C rewrites OR sha256-gated patches; the
|
|
15
|
+
* conflict-marker (Gemini-style) Layer A surface has higher
|
|
16
|
+
* ambiguity rates on these models.
|
|
17
|
+
* - DeepSeek-coder family — anchors well on Layer C primary with
|
|
18
|
+
* Layer A as fallback (their RLHF set leaned hard on patch-style
|
|
19
|
+
* emit).
|
|
20
|
+
* - Reasoning models (o-series, gpt-5) — handle Layer D AST-aware
|
|
21
|
+
* operations when an LSP bridge is available; Layer C primary
|
|
22
|
+
* otherwise.
|
|
23
|
+
*
|
|
24
|
+
* The existing `format-matrix.ts` keyed exact slugs only — fine for
|
|
25
|
+
* the canonical set but blind to vendor-prefixed slugs that come back
|
|
26
|
+
* from the Anvil gateway (e.g. `anthropic/claude-sonnet-4-20250514`,
|
|
27
|
+
* `qwen/qwen3-coder`, `deepseek/deepseek-chat-v3.1`). This detector
|
|
28
|
+
* layers ON TOP of the matrix:
|
|
29
|
+
*
|
|
30
|
+
* 1. exact-key match against `EDIT_FORMAT_MATRIX` — wins outright.
|
|
31
|
+
* 2. vendor-prefix strip → second exact-key probe (so
|
|
32
|
+
* `anthropic/claude-sonnet-4-6` resolves the same as `claude-sonnet-4-6`).
|
|
33
|
+
* 3. family inference from the (possibly prefixed) slug — returns
|
|
34
|
+
* the family-default chain (see `FAMILY_DEFAULTS`).
|
|
35
|
+
* 4. unknown — wildcard `*` from the matrix.
|
|
36
|
+
*
|
|
37
|
+
* The output is the same `EditFormatChain` shape the matrix already
|
|
38
|
+
* exposes so callers (dispatcher hint, persona prompt hint) consume a
|
|
39
|
+
* single contract.
|
|
40
|
+
*
|
|
41
|
+
* NOT a routing decision: the dispatcher's per-layer routing is still
|
|
42
|
+
* driven by what the model actually emits. The detector's output is a
|
|
43
|
+
* HINT — surfaced to the persona prompt so the model has the right
|
|
44
|
+
* marker template loaded, and surfaced to the dispatcher's fuzzy ladder
|
|
45
|
+
* / fallback ordering so an ambiguous response gets retried in an order
|
|
46
|
+
* that matches the model's empirical strengths.
|
|
47
|
+
*
|
|
48
|
+
* Spec: this is the PUGI-79 implementation; the issue ships the
|
|
49
|
+
* detector + dispatcher hint + persona hint together.
|
|
50
|
+
*/
|
|
51
|
+
import { EDIT_FORMAT_MATRIX, } from './format-matrix.js';
|
|
52
|
+
/**
|
|
53
|
+
* Family-level default chains. These are deliberately CONSERVATIVE —
|
|
54
|
+
* each family's primary is the layer with the highest observed apply-
|
|
55
|
+
* rate across the 2026-05 dogfood corpus; fallbacks rank in observed
|
|
56
|
+
* success order. The exact-slug entries in `EDIT_FORMAT_MATRIX` can
|
|
57
|
+
* override per-model; family defaults are the floor.
|
|
58
|
+
*
|
|
59
|
+
* Anthropic:
|
|
60
|
+
* The Anvil corpus shows Claude family hitting ~95% Layer A apply on
|
|
61
|
+
* first try; Layer C is the rescue when the file is large enough
|
|
62
|
+
* that the operator's region selection ambiguates. Matrix entries
|
|
63
|
+
* for `claude-opus-4-7` + `claude-sonnet-4-6` override to Layer C
|
|
64
|
+
* primary because the larger context windows let those models hold
|
|
65
|
+
* the whole file comfortably.
|
|
66
|
+
*
|
|
67
|
+
* Open-weight (qwen / deepseek / kimi):
|
|
68
|
+
* These ship whole-file rewrites more reliably than partial diffs —
|
|
69
|
+
* the patch-style envelopes their fine-tune sets prefer match Layer
|
|
70
|
+
* C semantics. Layer A is the fallback because they can still emit
|
|
71
|
+
* sensible search/replace pairs when prompted explicitly.
|
|
72
|
+
*
|
|
73
|
+
* Reasoning (gpt-5 / o-series):
|
|
74
|
+
* Layer C primary because the longer reasoning trace burns through
|
|
75
|
+
* the partial-diff format's positional constraints. Layer D rides
|
|
76
|
+
* the wire when an LSP is bridged; the matrix entries override per-
|
|
77
|
+
* model when that's available (see `qwen3-coder-480b → D`).
|
|
78
|
+
*
|
|
79
|
+
* Unknown:
|
|
80
|
+
* Defers to the wildcard chain from the matrix (Layer A primary).
|
|
81
|
+
*/
|
|
82
|
+
const FAMILY_DEFAULTS = {
|
|
83
|
+
anthropic: { primary: 'A', fallback: ['C', 'B'] },
|
|
84
|
+
openai: { primary: 'C', fallback: ['A', 'B'] },
|
|
85
|
+
gemini: { primary: 'A', fallback: ['C', 'B'] },
|
|
86
|
+
qwen: { primary: 'A', fallback: ['C', 'B'] },
|
|
87
|
+
deepseek: { primary: 'C', fallback: ['A', 'B'] },
|
|
88
|
+
kimi: { primary: 'C', fallback: ['A', 'B'] },
|
|
89
|
+
unknown: { primary: 'A', fallback: ['B', 'C'] },
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Detect the model family from a slug. Handles vendor-prefixed forms
|
|
93
|
+
* (`<vendor>/<model>` and `<provider>:<model>`) plus bare-name forms.
|
|
94
|
+
* Case-insensitive on the family name; case-preserving on the rest.
|
|
95
|
+
*
|
|
96
|
+
* Order matters: vendor prefix wins when present (the gateway is
|
|
97
|
+
* authoritative about which family routed). Otherwise the bare slug
|
|
98
|
+
* gets prefix-matched against well-known family stems. Unknown slugs
|
|
99
|
+
* surface as `unknown` — the caller falls back to the matrix wildcard.
|
|
100
|
+
*/
|
|
101
|
+
export function detectModelFamily(modelSlug) {
|
|
102
|
+
if (!modelSlug)
|
|
103
|
+
return 'unknown';
|
|
104
|
+
const raw = modelSlug.trim();
|
|
105
|
+
if (raw.length === 0)
|
|
106
|
+
return 'unknown';
|
|
107
|
+
const lower = raw.toLowerCase();
|
|
108
|
+
// Vendor-prefix form: `<vendor>/<model>`. The vendor wins outright
|
|
109
|
+
// because the gateway is authoritative — `anthropic/<anything>`
|
|
110
|
+
// routes Claude family regardless of the suffix.
|
|
111
|
+
const slashIdx = lower.indexOf('/');
|
|
112
|
+
if (slashIdx > 0) {
|
|
113
|
+
const vendor = lower.slice(0, slashIdx);
|
|
114
|
+
switch (vendor) {
|
|
115
|
+
case 'anthropic':
|
|
116
|
+
return 'anthropic';
|
|
117
|
+
case 'openai':
|
|
118
|
+
return 'openai';
|
|
119
|
+
case 'google':
|
|
120
|
+
case 'gemini':
|
|
121
|
+
case 'xai':
|
|
122
|
+
return 'gemini';
|
|
123
|
+
case 'qwen':
|
|
124
|
+
case 'alibaba':
|
|
125
|
+
return 'qwen';
|
|
126
|
+
case 'deepseek':
|
|
127
|
+
return 'deepseek';
|
|
128
|
+
case 'moonshot':
|
|
129
|
+
case 'kimi':
|
|
130
|
+
return 'kimi';
|
|
131
|
+
default:
|
|
132
|
+
// Unknown vendor — fall through to bare-name detection on the
|
|
133
|
+
// suffix. Some gateways (OpenRouter style) put the family
|
|
134
|
+
// name AFTER the vendor (e.g. `together/qwen-coder`), so the
|
|
135
|
+
// suffix still carries signal.
|
|
136
|
+
return detectFamilyFromBareName(lower.slice(slashIdx + 1));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Provider-prefix form: `ollama:<model>` (engine-VM local runtime).
|
|
140
|
+
// The prefix is informational; strip and continue.
|
|
141
|
+
const colonIdx = lower.indexOf(':');
|
|
142
|
+
if (colonIdx > 0) {
|
|
143
|
+
const prefix = lower.slice(0, colonIdx);
|
|
144
|
+
if (prefix === 'ollama' || prefix === 'lmstudio' || prefix === 'llama-cpp') {
|
|
145
|
+
return detectFamilyFromBareName(lower.slice(colonIdx + 1));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return detectFamilyFromBareName(lower);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Bare-name detector. Lower-case input. Prefix-match against family
|
|
152
|
+
* stems; longest match wins so `claude-opus-4-7` doesn't accidentally
|
|
153
|
+
* route through the generic `c*` lane.
|
|
154
|
+
*/
|
|
155
|
+
function detectFamilyFromBareName(name) {
|
|
156
|
+
if (name.startsWith('claude'))
|
|
157
|
+
return 'anthropic';
|
|
158
|
+
if (name.startsWith('gpt') || name.startsWith('o1') || name.startsWith('o3') || name.startsWith('o4')) {
|
|
159
|
+
return 'openai';
|
|
160
|
+
}
|
|
161
|
+
if (name.startsWith('gemini') || name.startsWith('grok'))
|
|
162
|
+
return 'gemini';
|
|
163
|
+
if (name.startsWith('qwen'))
|
|
164
|
+
return 'qwen';
|
|
165
|
+
if (name.startsWith('deepseek'))
|
|
166
|
+
return 'deepseek';
|
|
167
|
+
if (name.startsWith('kimi') || name.startsWith('moonshot'))
|
|
168
|
+
return 'kimi';
|
|
169
|
+
return 'unknown';
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Family-default chain. Exported for callers that want the family-
|
|
173
|
+
* level recommendation without running the full detector pipeline
|
|
174
|
+
* (e.g. observability surfaces that already resolved the family).
|
|
175
|
+
*/
|
|
176
|
+
export function familyDefaultChain(family) {
|
|
177
|
+
// Defensive clone: the FAMILY_DEFAULTS table is readonly conceptually
|
|
178
|
+
// but the EditFormatChain type allows fallback array mutation. Clone
|
|
179
|
+
// so callers cannot accidentally mutate the table.
|
|
180
|
+
const base = FAMILY_DEFAULTS[family];
|
|
181
|
+
return { primary: base.primary, fallback: [...base.fallback] };
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Resolve the preferred edit-format chain for a model slug.
|
|
185
|
+
*
|
|
186
|
+
* Resolution order:
|
|
187
|
+
* 1. Exact slug match in EDIT_FORMAT_MATRIX — overrides everything.
|
|
188
|
+
* This lets per-model tuning (e.g. `qwen3-coder-480b → D`) win
|
|
189
|
+
* over the family default.
|
|
190
|
+
* 2. Vendor-prefix strip + exact match — so `anthropic/claude-...`
|
|
191
|
+
* resolves to the bare `claude-...` entry when present.
|
|
192
|
+
* 3. Family inference — `FAMILY_DEFAULTS[detectModelFamily(slug)]`.
|
|
193
|
+
* 4. Wildcard fallback — `EDIT_FORMAT_MATRIX['*']`.
|
|
194
|
+
*
|
|
195
|
+
* The returned chain is always a fresh object so caller mutations
|
|
196
|
+
* cannot leak back into the matrix.
|
|
197
|
+
*/
|
|
198
|
+
export function detectEditFormat(modelSlug) {
|
|
199
|
+
const fallbackWildcard = EDIT_FORMAT_MATRIX['*'] ?? { primary: 'A', fallback: [] };
|
|
200
|
+
if (!modelSlug || modelSlug.trim().length === 0) {
|
|
201
|
+
return { primary: fallbackWildcard.primary, fallback: [...fallbackWildcard.fallback] };
|
|
202
|
+
}
|
|
203
|
+
const raw = modelSlug.trim();
|
|
204
|
+
// Step 1 — exact match wins.
|
|
205
|
+
const exact = EDIT_FORMAT_MATRIX[raw];
|
|
206
|
+
if (exact)
|
|
207
|
+
return { primary: exact.primary, fallback: [...exact.fallback] };
|
|
208
|
+
// Step 2 — vendor-prefix strip (e.g. `anthropic/claude-sonnet-4-6`).
|
|
209
|
+
const slashIdx = raw.indexOf('/');
|
|
210
|
+
if (slashIdx > 0) {
|
|
211
|
+
const suffix = raw.slice(slashIdx + 1);
|
|
212
|
+
const stripped = EDIT_FORMAT_MATRIX[suffix];
|
|
213
|
+
if (stripped)
|
|
214
|
+
return { primary: stripped.primary, fallback: [...stripped.fallback] };
|
|
215
|
+
}
|
|
216
|
+
// Step 3 — family inference.
|
|
217
|
+
const family = detectModelFamily(raw);
|
|
218
|
+
if (family !== 'unknown')
|
|
219
|
+
return familyDefaultChain(family);
|
|
220
|
+
// Step 4 — wildcard.
|
|
221
|
+
return { primary: fallbackWildcard.primary, fallback: [...fallbackWildcard.fallback] };
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Render a one-line marker-format hint for the persona prompt. The
|
|
225
|
+
* dispatcher already accepts every marker family (anthropic / gemini /
|
|
226
|
+
* openai), so the hint is preference, not gating — surface the
|
|
227
|
+
* preferred template + alternative so the model knows which envelope
|
|
228
|
+
* has the highest apply-rate for its family.
|
|
229
|
+
*
|
|
230
|
+
* Returns `null` when the slug is unknown / unset; the caller drops
|
|
231
|
+
* the hint module on null so the prompt is not polluted with empty
|
|
232
|
+
* sections.
|
|
233
|
+
*/
|
|
234
|
+
export function renderEditFormatHint(modelSlug) {
|
|
235
|
+
if (!modelSlug || modelSlug.trim().length === 0)
|
|
236
|
+
return null;
|
|
237
|
+
const family = detectModelFamily(modelSlug);
|
|
238
|
+
if (family === 'unknown')
|
|
239
|
+
return null;
|
|
240
|
+
const chain = detectEditFormat(modelSlug);
|
|
241
|
+
const markerStyle = FAMILY_MARKER_TEMPLATE[family];
|
|
242
|
+
return `# Edit-format hint (auto-selected for ${family})
|
|
243
|
+
Preferred layer: Layer ${chain.primary}${chain.fallback.length > 0 ? ` (fallback: ${chain.fallback.map((l) => `Layer ${l}`).join(' -> ')})` : ''}.
|
|
244
|
+
Marker template: ${markerStyle}
|
|
245
|
+
The dispatcher accepts any marker family; this hint optimises apply-rate for the resolved model.`;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Marker template literal per family. Mirrors the three envelopes the
|
|
249
|
+
* marker-parser accepts; used by `renderEditFormatHint` so the model
|
|
250
|
+
* sees a concrete shape next to the layer recommendation.
|
|
251
|
+
*/
|
|
252
|
+
const FAMILY_MARKER_TEMPLATE = {
|
|
253
|
+
anthropic: '+++ NEW <file> / <new contents> / --- OLD <file> / <old contents> / ===',
|
|
254
|
+
openai: '@@@ REWRITE <file> sha256=<hex> / <full new contents> / @@@ END',
|
|
255
|
+
gemini: '<<<<<<< SEARCH <file> / <old> / ======= / <new> / >>>>>>> REPLACE',
|
|
256
|
+
qwen: '<<<<<<< SEARCH <file> / <old> / ======= / <new> / >>>>>>> REPLACE',
|
|
257
|
+
deepseek: '@@@ REWRITE <file> sha256=<hex> / <full new contents> / @@@ END',
|
|
258
|
+
kimi: '@@@ REWRITE <file> sha256=<hex> / <full new contents> / @@@ END',
|
|
259
|
+
};
|
|
260
|
+
//# sourceMappingURL=format-detector.js.map
|