@pugi/cli 0.1.0-beta.99 → 1.0.0-alpha.2
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/LICENSE +1 -1
- package/README.md +11 -191
- package/bin/pugi +8 -0
- package/package.json +15 -71
- package/postinstall.mjs +31 -0
- package/CHANGELOG.md +0 -132
- package/THIRD_PARTY_NOTICES.md +0 -40
- package/assets/pugi-mascot.ansi +0 -16
- package/assets/pugi-prozr2-mascot.ansi +0 -9
- package/bin/run.js +0 -34
- package/dist/commands/deploy.js +0 -439
- package/dist/commands/flatten.js +0 -191
- package/dist/commands/jobs-watch.js +0 -201
- package/dist/commands/jobs.js +0 -260
- package/dist/commands/retro.js +0 -210
- package/dist/commands/smoke.js +0 -133
- package/dist/core/agent-progress/cleanup.js +0 -134
- package/dist/core/agent-progress/schema.js +0 -144
- package/dist/core/agent-progress/writer.js +0 -101
- package/dist/core/agents/adaptive-router.js +0 -330
- package/dist/core/agents/loader.js +0 -104
- package/dist/core/agents/query-decomposer.js +0 -297
- package/dist/core/agents/registry.js +0 -69
- package/dist/core/approvals/shortcut-resolver.js +0 -98
- package/dist/core/artifact-chain/dispatcher.js +0 -148
- package/dist/core/artifact-chain/exporter.js +0 -164
- package/dist/core/artifact-chain/state.js +0 -243
- package/dist/core/artifact-chain/steps.js +0 -169
- package/dist/core/ask-user/question.js +0 -92
- package/dist/core/audit/audit-trail.js +0 -275
- package/dist/core/auth/ensure-authenticated.js +0 -129
- package/dist/core/auth/env-provider.js +0 -238
- package/dist/core/auto-open-browser.js +0 -128
- package/dist/core/auto-update/channels.js +0 -122
- package/dist/core/auto-update/checker.js +0 -241
- package/dist/core/auto-update/state.js +0 -235
- package/dist/core/bare-mode/index.js +0 -107
- package/dist/core/bash/redirect.js +0 -281
- package/dist/core/bash-classifier.js +0 -1397
- package/dist/core/checkpoint/resumer.js +0 -149
- package/dist/core/checkpoint/rewinder.js +0 -291
- package/dist/core/checkpoints/shadow-git.js +0 -670
- package/dist/core/citations/parser.js +0 -109
- package/dist/core/classifier/yolo-classifier.js +0 -88
- package/dist/core/clipboard.js +0 -70
- package/dist/core/codegraph/decision-store.js +0 -248
- package/dist/core/codegraph/detect-repo.js +0 -459
- package/dist/core/codegraph/install.js +0 -134
- package/dist/core/codegraph/offer-hook.js +0 -220
- package/dist/core/compact/auto-trigger.js +0 -96
- package/dist/core/compact/buffer-rewriter.js +0 -115
- package/dist/core/compact/summarizer.js +0 -208
- package/dist/core/compact/token-counter.js +0 -108
- package/dist/core/consensus/anvil-fanout.js +0 -276
- package/dist/core/consensus/diff-capture.js +0 -491
- package/dist/core/consensus/rubric.js +0 -233
- package/dist/core/context/builder.js +0 -114
- package/dist/core/context/compaction-events.js +0 -99
- package/dist/core/context/compaction.js +0 -602
- package/dist/core/context/index.js +0 -28
- package/dist/core/context/invariants.js +0 -250
- package/dist/core/context/markdown-loader.js +0 -288
- package/dist/core/context/markdown-traverse.js +0 -255
- package/dist/core/context/pugiignore.js +0 -316
- package/dist/core/context/repo-skeleton.js +0 -533
- package/dist/core/context/tool-eviction.js +0 -55
- package/dist/core/context/watcher.js +0 -342
- package/dist/core/context/working-set.js +0 -165
- package/dist/core/coordinator/agent-tools.js +0 -77
- package/dist/core/coordinator/agent-toolset.js +0 -65
- package/dist/core/coordinator/fsm.js +0 -73
- package/dist/core/coordinator/mode-fsm.js +0 -70
- package/dist/core/cost/rate-card.js +0 -129
- package/dist/core/cost/tracker.js +0 -221
- package/dist/core/credentials.js +0 -355
- package/dist/core/cron/scheduler.js +0 -138
- package/dist/core/denial-tracking/index.js +0 -8
- package/dist/core/denial-tracking/state.js +0 -264
- package/dist/core/diagnostics/probe-runner.js +0 -93
- package/dist/core/diagnostics/probes/api.js +0 -46
- package/dist/core/diagnostics/probes/auth.js +0 -93
- package/dist/core/diagnostics/probes/bare-mode.js +0 -42
- package/dist/core/diagnostics/probes/cli-version.js +0 -127
- package/dist/core/diagnostics/probes/config.js +0 -72
- package/dist/core/diagnostics/probes/denial-tracking.js +0 -57
- package/dist/core/diagnostics/probes/disk.js +0 -81
- package/dist/core/diagnostics/probes/engine-live.js +0 -46
- package/dist/core/diagnostics/probes/git.js +0 -65
- package/dist/core/diagnostics/probes/hooks.js +0 -118
- package/dist/core/diagnostics/probes/mcp.js +0 -75
- package/dist/core/diagnostics/probes/node.js +0 -59
- package/dist/core/diagnostics/probes/pnpm.js +0 -36
- package/dist/core/diagnostics/probes/pugi-md.js +0 -89
- package/dist/core/diagnostics/probes/sandbox.js +0 -72
- package/dist/core/diagnostics/probes/session.js +0 -74
- package/dist/core/diagnostics/probes/status-snapshot.js +0 -488
- package/dist/core/diagnostics/probes/workspace.js +0 -63
- package/dist/core/diagnostics/types.js +0 -70
- package/dist/core/dispatch/cache-cleanup.js +0 -197
- package/dist/core/dispatch/cache-handoff.js +0 -295
- package/dist/core/edits/apply-patch-layer-e.js +0 -189
- package/dist/core/edits/dispatch.js +0 -511
- package/dist/core/edits/format-detector.js +0 -260
- package/dist/core/edits/format-matrix.js +0 -26
- package/dist/core/edits/fuzzy-ladder.js +0 -650
- package/dist/core/edits/index.js +0 -19
- package/dist/core/edits/journal.js +0 -199
- package/dist/core/edits/layer-a-apply.js +0 -217
- package/dist/core/edits/layer-a-fuzzy-apply.js +0 -198
- package/dist/core/edits/layer-b-apply.js +0 -211
- package/dist/core/edits/layer-c-apply.js +0 -160
- package/dist/core/edits/layer-d-ast.js +0 -572
- package/dist/core/edits/marker-parser.js +0 -401
- package/dist/core/edits/security-gate.js +0 -223
- package/dist/core/edits/verify-hook.js +0 -273
- package/dist/core/edits/worktree.js +0 -322
- package/dist/core/engine/adapter-runner.js +0 -8
- package/dist/core/engine/anvil-client.js +0 -344
- package/dist/core/engine/auto-compact.js +0 -179
- package/dist/core/engine/budgets.js +0 -195
- package/dist/core/engine/context-prefix.js +0 -155
- package/dist/core/engine/index.js +0 -12
- package/dist/core/engine/intensity.js +0 -163
- package/dist/core/engine/intent.js +0 -260
- package/dist/core/engine/native-pugi.js +0 -1616
- package/dist/core/engine/noop.js +0 -27
- package/dist/core/engine/prompts.js +0 -236
- package/dist/core/engine/strip-internal-fields.js +0 -124
- package/dist/core/engine/tool-bridge.js +0 -2173
- package/dist/core/engine/verification-patterns.js +0 -195
- package/dist/core/evaluation/golden-dataset.js +0 -293
- package/dist/core/feedback/queue.js +0 -177
- package/dist/core/feedback/submitter.js +0 -145
- package/dist/core/file-cache.js +0 -141
- package/dist/core/flatten/flatten-repo.js +0 -439
- package/dist/core/format/osc8-link.js +0 -28
- package/dist/core/hook-chains.js +0 -392
- package/dist/core/hooks/citation-verify-hook.js +0 -138
- package/dist/core/hooks/citation-verify.js +0 -112
- package/dist/core/hooks/events.js +0 -46
- package/dist/core/hooks/index.js +0 -15
- package/dist/core/hooks/registry.js +0 -216
- package/dist/core/hooks/runner.js +0 -236
- package/dist/core/hooks/v2/event-emitter.js +0 -115
- package/dist/core/hooks/v2/executor.js +0 -282
- package/dist/core/hooks/v2/index.js +0 -25
- package/dist/core/hooks/v2/lifecycle.js +0 -104
- package/dist/core/hooks/v2/loader.js +0 -216
- package/dist/core/hooks/v2/matcher.js +0 -125
- package/dist/core/hooks/v2/trust.js +0 -143
- package/dist/core/hooks/v2/types.js +0 -86
- package/dist/core/hooks/worktree-events.js +0 -158
- package/dist/core/hooks.js +0 -415
- package/dist/core/image/renderer.js +0 -71
- package/dist/core/index-store.js +0 -260
- package/dist/core/init/detector.js +0 -582
- package/dist/core/init/template-renderer.js +0 -242
- package/dist/core/jobs/registry.js +0 -462
- package/dist/core/ledger/results-tsv.js +0 -142
- package/dist/core/log-discipline/stdout-redirect.js +0 -51
- package/dist/core/lsp/cache.js +0 -105
- package/dist/core/lsp/client.js +0 -1229
- package/dist/core/lsp/language-detect.js +0 -66
- package/dist/core/lsp/post-edit-diagnostics.js +0 -171
- package/dist/core/lsp/server-detect.js +0 -173
- package/dist/core/lsp/symbol-cache.js +0 -162
- package/dist/core/lsp/symbol-tools.js +0 -664
- package/dist/core/mcp/client.js +0 -385
- package/dist/core/mcp/http-server.js +0 -553
- package/dist/core/mcp/orchestrator-config.js +0 -192
- package/dist/core/mcp/orchestrator-tools.js +0 -806
- package/dist/core/mcp/permission.js +0 -190
- package/dist/core/mcp/registry.js +0 -193
- package/dist/core/mcp/server-tools.js +0 -219
- package/dist/core/mcp/server.js +0 -397
- package/dist/core/mcp/trust.js +0 -91
- package/dist/core/memory/dual-write.js +0 -416
- package/dist/core/memory/passive-extract.js +0 -130
- package/dist/core/memory/phase1-kinds.js +0 -20
- package/dist/core/memory/secret-scanner.js +0 -304
- package/dist/core/memory-sync/queue.js +0 -170
- package/dist/core/metrics/extract.js +0 -113
- package/dist/core/modes/roo-modes.js +0 -68
- package/dist/core/onboarding/ensure-initialized.js +0 -133
- package/dist/core/onboarding/marker.js +0 -111
- package/dist/core/onboarding/telemetry-state.js +0 -108
- package/dist/core/output-style/presets.js +0 -176
- package/dist/core/output-style/state.js +0 -185
- package/dist/core/path-security.js +0 -345
- package/dist/core/permission.js +0 -369
- package/dist/core/permissions/auto-classifier.js +0 -124
- package/dist/core/permissions/bash-parser.js +0 -371
- package/dist/core/permissions/circuit-breaker.js +0 -83
- package/dist/core/permissions/constrained-edit.js +0 -91
- package/dist/core/permissions/gate.js +0 -278
- package/dist/core/permissions/index.js +0 -20
- package/dist/core/permissions/mode.js +0 -174
- package/dist/core/permissions/network-egress.js +0 -137
- package/dist/core/permissions/state.js +0 -241
- package/dist/core/permissions/tool-class.js +0 -107
- package/dist/core/plan-mode/ui-state.js +0 -51
- package/dist/core/plans/plan-artifact.js +0 -721
- package/dist/core/policy-limits/etag-store.js +0 -122
- package/dist/core/prd-check/parser.js +0 -215
- package/dist/core/prd-check/reporter.js +0 -127
- package/dist/core/prd-check/session-review.js +0 -557
- package/dist/core/prd-check/verifiers.js +0 -223
- package/dist/core/prompt-cache/client-cache.js +0 -99
- package/dist/core/prompts/assembly.js +0 -29
- package/dist/core/prompts/registry.js +0 -364
- package/dist/core/pugi-gitignore.js +0 -52
- package/dist/core/pugi-md/cc-compat-rules.js +0 -735
- package/dist/core/pugi-md/context-injector.js +0 -76
- package/dist/core/pugi-md/walk-up.js +0 -207
- package/dist/core/python/uv-installer.js +0 -270
- package/dist/core/python/uv-resolver.js +0 -83
- package/dist/core/rate-limit/narrator.js +0 -146
- package/dist/core/recipes/cli-types.js +0 -20
- package/dist/core/recipes/loader.js +0 -103
- package/dist/core/recipes/runner.js +0 -345
- package/dist/core/recipes/schema.js +0 -587
- package/dist/core/release-notes/parser.js +0 -241
- package/dist/core/release-notes/state.js +0 -116
- package/dist/core/repl/ask.js +0 -512
- package/dist/core/repl/cancellation.js +0 -98
- package/dist/core/repl/cap-warning.js +0 -91
- package/dist/core/repl/clipboard-read.js +0 -174
- package/dist/core/repl/dispatch-fsm.js +0 -220
- package/dist/core/repl/engine-bridge.js +0 -303
- package/dist/core/repl/history-search.js +0 -175
- package/dist/core/repl/history.js +0 -182
- package/dist/core/repl/kill-ring.js +0 -138
- package/dist/core/repl/model-pricing.js +0 -135
- package/dist/core/repl/privacy-banner.js +0 -71
- package/dist/core/repl/session.js +0 -4962
- package/dist/core/repl/slash-commands.js +0 -747
- package/dist/core/repl/store/index.js +0 -12
- package/dist/core/repl/store/jsonl-log.js +0 -321
- package/dist/core/repl/store/lockfile.js +0 -155
- package/dist/core/repl/store/session-store.js +0 -821
- package/dist/core/repl/store/types.js +0 -44
- package/dist/core/repl/store/uuid-v7.js +0 -68
- package/dist/core/repl/tool-route.js +0 -382
- package/dist/core/repl/workspace-context.js +0 -206
- package/dist/core/repo-map/build.js +0 -125
- package/dist/core/repo-map/cache.js +0 -185
- package/dist/core/repo-map/extractor.js +0 -254
- package/dist/core/repo-map/formatter.js +0 -145
- package/dist/core/repo-map/page-rank.js +0 -105
- package/dist/core/repo-map/scanner.js +0 -211
- package/dist/core/retro/git-collector.js +0 -251
- package/dist/core/retro/health-card.js +0 -25
- package/dist/core/retro/metrics.js +0 -342
- package/dist/core/retro/narrative.js +0 -249
- package/dist/core/retro/plane-collector.js +0 -274
- package/dist/core/retro/pr-issue-link.js +0 -65
- package/dist/core/retro/types.js +0 -16
- package/dist/core/retry-budget/budget.js +0 -284
- package/dist/core/retry-budget/index.js +0 -5
- package/dist/core/retry-budget/retry-cap.js +0 -74
- package/dist/core/routing/lead-worker.js +0 -43
- package/dist/core/routing/pre-flight-estimator.js +0 -108
- package/dist/core/runs/run-tree.js +0 -103
- package/dist/core/sandboxing/adapter.js +0 -29
- package/dist/core/sandboxing/index.js +0 -49
- package/dist/core/sandboxing/none.js +0 -19
- package/dist/core/sandboxing/seatbelt.js +0 -183
- package/dist/core/security/injection-scanner.js +0 -367
- package/dist/core/security/output-filter.js +0 -418
- package/dist/core/session/env-file.js +0 -105
- package/dist/core/session/section-budgets.js +0 -140
- package/dist/core/session.js +0 -377
- package/dist/core/settings.js +0 -400
- package/dist/core/share/formatter.js +0 -271
- package/dist/core/share/redactor.js +0 -221
- package/dist/core/share/uploader.js +0 -267
- package/dist/core/skills/defaults.js +0 -457
- package/dist/core/skills/loader.js +0 -454
- package/dist/core/skills/sources.js +0 -480
- package/dist/core/skills/trust.js +0 -172
- package/dist/core/smoke/headless-driver.js +0 -174
- package/dist/core/smoke/orchestrator.js +0 -194
- package/dist/core/smoke/runner.js +0 -238
- package/dist/core/smoke/scenario-parser.js +0 -316
- package/dist/core/statusline.js +0 -99
- package/dist/core/subagents/dispatcher-real.js +0 -600
- package/dist/core/subagents/dispatcher.js +0 -352
- package/dist/core/subagents/index.js +0 -39
- package/dist/core/subagents/isolation-matrix.js +0 -213
- package/dist/core/subagents/spawn.js +0 -101
- package/dist/core/telemetry/emitter.js +0 -229
- package/dist/core/telemetry/queue.js +0 -251
- package/dist/core/theme/context.js +0 -91
- package/dist/core/theme/presets.js +0 -228
- package/dist/core/theme/state.js +0 -181
- package/dist/core/todos/invariant.js +0 -10
- package/dist/core/todos/state.js +0 -177
- package/dist/core/tool-schema/compressor.js +0 -89
- package/dist/core/transport/version-interceptor.js +0 -166
- package/dist/core/trust.js +0 -109
- package/dist/core/tui/thinking-block.js +0 -64
- package/dist/core/vim/keymap.js +0 -288
- package/dist/core/vim/state.js +0 -92
- package/dist/core/watch-markers/marker-watcher.js +0 -133
- package/dist/core/worktree/include-parser.js +0 -249
- package/dist/core/worktree-manager/cleanup.js +0 -123
- package/dist/core/worktree-manager/manager.js +0 -303
- package/dist/index.js +0 -44
- package/dist/runtime/bootstrap.js +0 -190
- package/dist/runtime/cli.js +0 -8121
- package/dist/runtime/commands/agents.js +0 -385
- package/dist/runtime/commands/budget.js +0 -192
- package/dist/runtime/commands/cancel.js +0 -231
- package/dist/runtime/commands/chain.js +0 -489
- package/dist/runtime/commands/codegraph-status.js +0 -227
- package/dist/runtime/commands/compact.js +0 -297
- package/dist/runtime/commands/config.js +0 -595
- package/dist/runtime/commands/cost.js +0 -199
- package/dist/runtime/commands/delegate.js +0 -312
- package/dist/runtime/commands/dispatch.js +0 -126
- package/dist/runtime/commands/doctor.js +0 -579
- package/dist/runtime/commands/feedback.js +0 -184
- package/dist/runtime/commands/hooks.js +0 -187
- package/dist/runtime/commands/init.js +0 -254
- package/dist/runtime/commands/lsp.js +0 -368
- package/dist/runtime/commands/mcp.js +0 -935
- package/dist/runtime/commands/memory.js +0 -582
- package/dist/runtime/commands/model.js +0 -237
- package/dist/runtime/commands/onboarding.js +0 -275
- package/dist/runtime/commands/patch.js +0 -128
- package/dist/runtime/commands/permissions.js +0 -112
- package/dist/runtime/commands/plan.js +0 -143
- package/dist/runtime/commands/prd-check.js +0 -285
- package/dist/runtime/commands/privacy.js +0 -107
- package/dist/runtime/commands/recipe.js +0 -325
- package/dist/runtime/commands/redo-blob-store.js +0 -92
- package/dist/runtime/commands/redo.js +0 -361
- package/dist/runtime/commands/release-notes.js +0 -229
- package/dist/runtime/commands/repo-map.js +0 -95
- package/dist/runtime/commands/report.js +0 -299
- package/dist/runtime/commands/resume.js +0 -118
- package/dist/runtime/commands/review-consensus.js +0 -414
- package/dist/runtime/commands/rewind.js +0 -333
- package/dist/runtime/commands/roster.js +0 -117
- package/dist/runtime/commands/sessions.js +0 -163
- package/dist/runtime/commands/share.js +0 -316
- package/dist/runtime/commands/skills.js +0 -401
- package/dist/runtime/commands/status.js +0 -186
- package/dist/runtime/commands/stickers.js +0 -82
- package/dist/runtime/commands/style.js +0 -194
- package/dist/runtime/commands/theme.js +0 -196
- package/dist/runtime/commands/undo.js +0 -361
- package/dist/runtime/commands/update.js +0 -289
- package/dist/runtime/commands/vim.js +0 -140
- package/dist/runtime/commands/worktree.js +0 -177
- package/dist/runtime/commands/worktrees.js +0 -155
- package/dist/runtime/deprecation-warning.js +0 -69
- package/dist/runtime/engine-exit-code.js +0 -50
- package/dist/runtime/headless-repl.js +0 -195
- package/dist/runtime/headless.js +0 -548
- package/dist/runtime/load-hooks-or-exit.js +0 -71
- package/dist/runtime/plan-decompose.js +0 -531
- package/dist/runtime/sigint-guard.js +0 -272
- package/dist/runtime/stream-renderer.js +0 -195
- package/dist/runtime/update-check.js +0 -294
- package/dist/runtime/version.js +0 -65
- package/dist/runtime/worktree-bootstrap.js +0 -579
- package/dist/skills/bundled/batch.js +0 -617
- package/dist/skills/bundled/index.js +0 -45
- package/dist/skills/bundled/loop.js +0 -358
- package/dist/skills/bundled/remember.js +0 -383
- package/dist/skills/bundled/simplify.js +0 -289
- package/dist/skills/bundled/skillify.js +0 -373
- package/dist/skills/bundled/stuck.js +0 -558
- package/dist/skills/bundled/verify.js +0 -439
- package/dist/testing/vcr.js +0 -486
- package/dist/tools/agent-tool.js +0 -229
- package/dist/tools/apply-patch.js +0 -556
- package/dist/tools/ask-user-question.js +0 -337
- package/dist/tools/ask-user.js +0 -115
- package/dist/tools/bash.js +0 -1238
- package/dist/tools/brief.js +0 -224
- package/dist/tools/cron.js +0 -433
- package/dist/tools/enter-worktree.js +0 -250
- package/dist/tools/exit-worktree.js +0 -147
- package/dist/tools/file-tools.js +0 -553
- package/dist/tools/http-request.js +0 -336
- package/dist/tools/lsp-tools.js +0 -565
- package/dist/tools/mcp-tool.js +0 -260
- package/dist/tools/multi-edit.js +0 -361
- package/dist/tools/powershell.js +0 -268
- package/dist/tools/registry.js +0 -166
- package/dist/tools/server-tools.js +0 -892
- package/dist/tools/skill-tool.js +0 -96
- package/dist/tools/sleep.js +0 -99
- package/dist/tools/synthetic-output.js +0 -133
- package/dist/tools/tasks.js +0 -208
- package/dist/tools/todo-write.js +0 -184
- package/dist/tools/verify-plan-execution.js +0 -295
- package/dist/tools/web-fetch-injection-scanner.js +0 -207
- package/dist/tools/web-fetch.js +0 -720
- package/dist/tools/web-search.js +0 -458
- package/dist/tui/agent-progress-card.js +0 -111
- package/dist/tui/agent-tree-pane.js +0 -9
- package/dist/tui/agent-tree.js +0 -87
- package/dist/tui/ask-cli.js +0 -52
- package/dist/tui/ask-modal.js +0 -211
- package/dist/tui/ask-user-question-chips.js +0 -315
- package/dist/tui/ask-user-question-prompt.js +0 -203
- package/dist/tui/compact-banner.js +0 -81
- package/dist/tui/conversation-pane.js +0 -164
- package/dist/tui/cost-table.js +0 -111
- package/dist/tui/device-flow.js +0 -142
- package/dist/tui/doctor-table.js +0 -46
- package/dist/tui/feedback-prompt.js +0 -156
- package/dist/tui/input-box.js +0 -732
- package/dist/tui/login-picker.js +0 -69
- package/dist/tui/markdown-render.js +0 -266
- package/dist/tui/multi-file-diff-approval.js +0 -375
- package/dist/tui/onboarding-wizard.js +0 -240
- package/dist/tui/permissions-picker.js +0 -86
- package/dist/tui/render.js +0 -160
- package/dist/tui/repl-render.js +0 -770
- package/dist/tui/repl-splash-art.js +0 -64
- package/dist/tui/repl-splash-mascot.js +0 -154
- package/dist/tui/repl-splash.js +0 -117
- package/dist/tui/repl.js +0 -378
- package/dist/tui/slash-palette.js +0 -106
- package/dist/tui/splash-data.js +0 -61
- package/dist/tui/splash.js +0 -31
- package/dist/tui/status-bar.js +0 -209
- package/dist/tui/status-table.js +0 -7
- package/dist/tui/stickers-art.js +0 -136
- package/dist/tui/style-table.js +0 -28
- package/dist/tui/theme-table.js +0 -29
- package/dist/tui/thinking-spinner.js +0 -123
- package/dist/tui/tool-stream-pane.js +0 -140
- package/dist/tui/update-banner.js +0 -33
- package/dist/tui/vim-input.js +0 -267
- package/dist/tui/welcome-banner.js +0 -107
- package/dist/tui/welcome-data.js +0 -293
- package/dist/tui/workspace-context.js +0 -105
- package/docs/examples/codegraph.mcp.json +0 -10
- package/test/scenarios/codegen-create-file.scenario.txt +0 -13
- package/test/scenarios/compact-force.scenario.txt +0 -12
- package/test/scenarios/identity.scenario.txt +0 -11
- package/test/scenarios/persona-handoff.scenario.txt +0 -12
- package/test/scenarios/walkback.scenario.txt +0 -12
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Edit journal — β1b Pl8 .
|
|
3
|
-
*
|
|
4
|
-
* Per-session append-only log of file-mutation INTENT before the
|
|
5
|
-
* dispatcher runs. Lives at `<root>/.pugi/sessions/<sessionId>/journal.jsonl`,
|
|
6
|
-
* one JSON entry per multi-file dispatch. On a non-zero exit / budget
|
|
7
|
-
* kill / OOM crash, the journal lets `git restore` / `fs.unlink` revert
|
|
8
|
-
* the workspace to its pre-dispatch state.
|
|
9
|
-
*
|
|
10
|
-
* Shape:
|
|
11
|
-
*
|
|
12
|
-
* ```ts
|
|
13
|
-
* {
|
|
14
|
-
* ts: number, // ms epoch — opens chronological replay
|
|
15
|
-
* taskId: string, // engine task id (`<kind>-<ts>`) for correlation
|
|
16
|
-
* files: [{
|
|
17
|
-
* path: string, // workspace-relative
|
|
18
|
-
* existed: boolean, // existed BEFORE the dispatch
|
|
19
|
-
* sha256_before?: string, // present iff existed === true
|
|
20
|
-
* }],
|
|
21
|
-
* }
|
|
22
|
-
* ```
|
|
23
|
-
*
|
|
24
|
-
* The journal records ONLY intent: it does not enumerate the post-state
|
|
25
|
-
* (a per-edit `applied` event already lives in `events.jsonl`). On
|
|
26
|
-
* rollback we need the pre-state to know:
|
|
27
|
-
* - whether to `git restore` (the file existed before and is git-
|
|
28
|
-
* tracked) OR `fs.unlink` (the file did NOT exist before — it was
|
|
29
|
-
* created by the failed dispatch).
|
|
30
|
-
* - whether to fall back to writing the cached `sha256_before` source
|
|
31
|
-
* when neither git nor fs can recover (untracked file that existed
|
|
32
|
-
* before — the dispatcher MUST have cached the pre-content; we
|
|
33
|
-
* surface a `partial_rollback` reason and let the operator decide).
|
|
34
|
-
*
|
|
35
|
-
* Why a separate journal vs reusing `events.jsonl`:
|
|
36
|
-
* - events.jsonl mixes status + tool_call + tool_result + outcome
|
|
37
|
-
* records from the engine loop. A crash mid-dispatch leaves the
|
|
38
|
-
* file in a state where reconstructing "files we MIGHT have
|
|
39
|
-
* touched" requires correlating multiple event types. The journal
|
|
40
|
-
* hoists the rollback-relevant subset into one line per dispatch
|
|
41
|
-
* so the recovery code is grep-able + audit-readable.
|
|
42
|
-
* - The journal is also forward-compatible with a future
|
|
43
|
-
* `pugi resume --rollback <taskId>` operator command — single
|
|
44
|
-
* source of truth for "what would I undo".
|
|
45
|
-
*
|
|
46
|
-
* Best-effort writes: a journal failure (disk full, .pugi unwritable)
|
|
47
|
-
* must NEVER block the actual edit. The dispatcher proceeds with
|
|
48
|
-
* `journalEntryId: null` and surfaces a warning in the session's
|
|
49
|
-
* status events. Rollback then degrades to apply-patch-style
|
|
50
|
-
* pre-existing snapshot (the dispatcher keeps an in-memory copy too).
|
|
51
|
-
*
|
|
52
|
-
* Brand voice: ASCII only, no banned words. Operator-facing tail
|
|
53
|
-
* (jq friendly).
|
|
54
|
-
*/
|
|
55
|
-
import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, } from 'node:fs';
|
|
56
|
-
import { createHash } from 'node:crypto';
|
|
57
|
-
import { join, resolve } from 'node:path';
|
|
58
|
-
/**
|
|
59
|
-
* Compute the on-disk journal path for a session. The directory is
|
|
60
|
-
* created lazily by `appendEntry` so a read against a missing session
|
|
61
|
-
* does not silently mkdir.
|
|
62
|
-
*/
|
|
63
|
-
export function journalPath(workspaceRoot, sessionId) {
|
|
64
|
-
return resolve(workspaceRoot, '.pugi', 'sessions', sessionId, 'journal.jsonl');
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* sha256 of a file's bytes. Used by `snapshotForDispatch` to capture
|
|
68
|
-
* the pre-content fingerprint so rollback can detect "file was changed
|
|
69
|
-
* between intent + rollback" cases (rare but real — a concurrent
|
|
70
|
-
* external editor + the dispatcher hitting the same path).
|
|
71
|
-
*
|
|
72
|
-
* Returns null when the file cannot be hashed (missing / unreadable);
|
|
73
|
-
* caller treats absent fingerprint as `existed=false`.
|
|
74
|
-
*/
|
|
75
|
-
export function sha256File(absPath) {
|
|
76
|
-
try {
|
|
77
|
-
const buf = readFileSync(absPath);
|
|
78
|
-
const h = createHash('sha256');
|
|
79
|
-
h.update(buf);
|
|
80
|
-
return h.digest('hex');
|
|
81
|
-
}
|
|
82
|
-
catch {
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Build a snapshot of the pre-dispatch state for every workspace-relative
|
|
88
|
-
* path the dispatcher is about to touch. Pure read; no writes.
|
|
89
|
-
*
|
|
90
|
-
* `paths` are workspace-relative. The journal records the SAME shape so
|
|
91
|
-
* the rollback path can re-resolve against the same `workspaceRoot`
|
|
92
|
-
* without ambiguity.
|
|
93
|
-
*/
|
|
94
|
-
export function snapshotForDispatch(workspaceRoot, paths) {
|
|
95
|
-
const out = [];
|
|
96
|
-
for (const rel of paths) {
|
|
97
|
-
const abs = resolve(workspaceRoot, rel);
|
|
98
|
-
if (existsSync(abs)) {
|
|
99
|
-
// Hash only on regular files; sha256File returns null for
|
|
100
|
-
// directories or unreadable inodes and we degrade cleanly.
|
|
101
|
-
try {
|
|
102
|
-
const st = statSync(abs);
|
|
103
|
-
if (st.isFile()) {
|
|
104
|
-
const sha = sha256File(abs);
|
|
105
|
-
out.push({ path: rel, existed: true, ...(sha ? { sha256_before: sha } : {}) });
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
/* fall through — treat as non-existent for rollback */
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
out.push({ path: rel, existed: false });
|
|
114
|
-
}
|
|
115
|
-
return out;
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Append one journal entry. Best-effort: any I/O failure returns false
|
|
119
|
-
* and the dispatcher proceeds without journal-backed rollback. The
|
|
120
|
-
* in-memory `JournalFileEntry[]` snapshot still drives the immediate
|
|
121
|
-
* post-crash rollback inside the same process.
|
|
122
|
-
*/
|
|
123
|
-
export function appendEntry(workspaceRoot, sessionId, entry) {
|
|
124
|
-
const path = journalPath(workspaceRoot, sessionId);
|
|
125
|
-
try {
|
|
126
|
-
mkdirSync(join(workspaceRoot, '.pugi', 'sessions', sessionId), {
|
|
127
|
-
recursive: true,
|
|
128
|
-
mode: 0o700,
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
catch {
|
|
132
|
-
return false;
|
|
133
|
-
}
|
|
134
|
-
try {
|
|
135
|
-
appendFileSync(path, `${JSON.stringify(entry)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
136
|
-
return true;
|
|
137
|
-
}
|
|
138
|
-
catch {
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Read every journal entry for a session, oldest-first. Malformed
|
|
144
|
-
* lines are dropped silently — a single bad line should not nuke
|
|
145
|
-
* recovery. Returns `[]` when the file is missing.
|
|
146
|
-
*
|
|
147
|
-
* Synchronous (mirrors `recordToolCall` etc) because the operator-
|
|
148
|
-
* facing rollback path needs to be deterministic; a partially-
|
|
149
|
-
* streamed read would race the very crash recovery it is supporting.
|
|
150
|
-
*/
|
|
151
|
-
export function readEntries(workspaceRoot, sessionId) {
|
|
152
|
-
const path = journalPath(workspaceRoot, sessionId);
|
|
153
|
-
if (!existsSync(path))
|
|
154
|
-
return [];
|
|
155
|
-
let raw;
|
|
156
|
-
try {
|
|
157
|
-
raw = readFileSync(path, 'utf8');
|
|
158
|
-
}
|
|
159
|
-
catch {
|
|
160
|
-
return [];
|
|
161
|
-
}
|
|
162
|
-
const out = [];
|
|
163
|
-
for (const line of raw.split('\n')) {
|
|
164
|
-
const trimmed = line.trim();
|
|
165
|
-
if (trimmed.length === 0)
|
|
166
|
-
continue;
|
|
167
|
-
try {
|
|
168
|
-
const parsed = JSON.parse(trimmed);
|
|
169
|
-
if (!parsed ||
|
|
170
|
-
typeof parsed !== 'object' ||
|
|
171
|
-
typeof parsed.taskId !== 'string' ||
|
|
172
|
-
typeof parsed.ts !== 'number' ||
|
|
173
|
-
!Array.isArray(parsed.files)) {
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
// β1b r1 defense-in-depth: validate every `files[]` entry too.
|
|
177
|
-
// A malformed file entry (missing `path`, wrong `existed` type)
|
|
178
|
-
// would silently feed garbage into the rollback path; better to
|
|
179
|
-
// drop the whole journal line than to attempt restore against a
|
|
180
|
-
// bogus snapshot.
|
|
181
|
-
const candidate = parsed;
|
|
182
|
-
const allFilesValid = candidate.files.every((f) => f !== null &&
|
|
183
|
-
typeof f === 'object' &&
|
|
184
|
-
typeof f.path === 'string' &&
|
|
185
|
-
f.path.length > 0 &&
|
|
186
|
-
typeof f.existed === 'boolean' &&
|
|
187
|
-
(f.sha256_before === undefined ||
|
|
188
|
-
typeof f.sha256_before === 'string'));
|
|
189
|
-
if (!allFilesValid)
|
|
190
|
-
continue;
|
|
191
|
-
out.push(candidate);
|
|
192
|
-
}
|
|
193
|
-
catch {
|
|
194
|
-
/* drop malformed line */
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
return out;
|
|
198
|
-
}
|
|
199
|
-
//# sourceMappingURL=journal.js.map
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Layer A diff applicator — diff escalation Phase 1.
|
|
3
|
-
*
|
|
4
|
-
* Layer A is the default and lowest-risk edit primitive: a single
|
|
5
|
-
* `{file, oldString, newString}` block that must match exactly once
|
|
6
|
-
* (whitespace-sensitive). It mirrors the the upstream tool Edit-tool semantics
|
|
7
|
-
* by design: agents emit a chunk of context guaranteed to be unique
|
|
8
|
-
* inside the file, and we replace it.
|
|
9
|
-
*
|
|
10
|
-
* Three failure modes are surfaced LOUD (never silent partial match):
|
|
11
|
-
*
|
|
12
|
-
* - `no_match` — oldString does not appear in the file at all.
|
|
13
|
-
* Model must re-read with broader context.
|
|
14
|
-
* - `ambiguous_match` — oldString appears 2+ times. Model must extend
|
|
15
|
-
* the context to disambiguate (line above /
|
|
16
|
-
* below / function name / etc.). The result
|
|
17
|
-
* carries the match count so the dispatcher
|
|
18
|
-
* can surface it to the operator.
|
|
19
|
-
* - `file_missing` — the target file does not exist on disk.
|
|
20
|
-
* Layer A NEVER creates files — that is the
|
|
21
|
-
* job of the write tool or Layer C.
|
|
22
|
-
*
|
|
23
|
-
* Cursor's silent partial-match falling back to inline rewrite is the
|
|
24
|
-
* documented anti-pattern in the spec; we do not replicate it.
|
|
25
|
-
*
|
|
26
|
-
* Writes use the tmp + rename atomic pattern (matches `writeTool` in
|
|
27
|
-
* `apps/pugi-cli/src/tools/file-tools.ts`). A crash mid-write leaves
|
|
28
|
-
* the original file intact; the `.pugi-tmp-*` file is the only
|
|
29
|
-
* orphan and trivially identifiable.
|
|
30
|
-
*/
|
|
31
|
-
import { existsSync, readFileSync, renameSync, writeFileSync, unlinkSync } from 'node:fs';
|
|
32
|
-
import { applySecurityGate } from './security-gate.js';
|
|
33
|
-
/**
|
|
34
|
-
* Apply a single Layer A edit. The function is intentionally pure with
|
|
35
|
-
* respect to errors — any failure returns a structured `ApplyResult`
|
|
36
|
-
* rather than throwing. The dispatcher aggregates results; throwing
|
|
37
|
-
* here would force the dispatcher to translate exceptions back into the
|
|
38
|
-
* same shape on every layer.
|
|
39
|
-
*/
|
|
40
|
-
export async function applyLayerA(edit, opts) {
|
|
41
|
-
// SECURITY GATE — fail-fast before ANY filesystem read/write.
|
|
42
|
-
// The gate runs three checks in order: workspace path scoping,
|
|
43
|
-
// protected-basename / suffix / credential-path deny, and a
|
|
44
|
-
// realpath-based symlink-escape re-check. See `security-gate.ts`
|
|
45
|
-
// for the full rationale. Bypassing this is how PR's pre-fix
|
|
46
|
-
// version would have written through `+++ NEW ../../../../etc/passwd`
|
|
47
|
-
// and `+++ NEW .env` markers (triple-review P0).
|
|
48
|
-
let gateResult;
|
|
49
|
-
try {
|
|
50
|
-
gateResult = applySecurityGate(edit.file, { cwd: opts.cwd, toolName: 'layer-a' });
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
// The gate only throws on unexpected filesystem errors (anything
|
|
54
|
-
// other than ENOENT / ENOTDIR on the realpath calls). Treat those
|
|
55
|
-
// as a write error so the dispatcher records the actual cause.
|
|
56
|
-
return {
|
|
57
|
-
ok: false,
|
|
58
|
-
bytesWritten: 0,
|
|
59
|
-
reason: 'write_error',
|
|
60
|
-
absPath: edit.file,
|
|
61
|
-
detail: error instanceof Error ? error.message : String(error),
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
if (!gateResult.ok) {
|
|
65
|
-
return {
|
|
66
|
-
ok: false,
|
|
67
|
-
bytesWritten: 0,
|
|
68
|
-
reason: gateResult.reason,
|
|
69
|
-
absPath: edit.file,
|
|
70
|
-
detail: gateResult.detail,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
const absPath = gateResult.absPath;
|
|
74
|
-
if (!existsSync(absPath)) {
|
|
75
|
-
return {
|
|
76
|
-
ok: false,
|
|
77
|
-
bytesWritten: 0,
|
|
78
|
-
reason: 'file_missing',
|
|
79
|
-
absPath,
|
|
80
|
-
detail: `file does not exist: ${edit.file}`,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
// Whitespace-sensitive: read as UTF-8 with no normalisation. CRLF
|
|
84
|
-
// vs LF differences in the model's emitted `oldString` will fail
|
|
85
|
-
// `no_match` LOUD rather than silently re-line-ending the file.
|
|
86
|
-
const before = readFileSync(absPath, 'utf8');
|
|
87
|
-
const matchCount = countOccurrences(before, edit.oldString);
|
|
88
|
-
if (matchCount === 0) {
|
|
89
|
-
return {
|
|
90
|
-
ok: false,
|
|
91
|
-
bytesWritten: 0,
|
|
92
|
-
reason: 'no_match',
|
|
93
|
-
absPath,
|
|
94
|
-
matchCount: 0,
|
|
95
|
-
detail: `oldString not found in ${edit.file}`,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
if (matchCount > 1) {
|
|
99
|
-
return {
|
|
100
|
-
ok: false,
|
|
101
|
-
bytesWritten: 0,
|
|
102
|
-
reason: 'ambiguous_match',
|
|
103
|
-
absPath,
|
|
104
|
-
matchCount,
|
|
105
|
-
detail: `oldString matches ${matchCount} times in ${edit.file}; extend context to disambiguate`,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
if (edit.oldString === edit.newString) {
|
|
109
|
-
// Treat no-op edits as a failure so the model corrects itself
|
|
110
|
-
// rather than silently believing the patch landed. This matches
|
|
111
|
-
// the the upstream tool Edit-tool contract.
|
|
112
|
-
return {
|
|
113
|
-
ok: false,
|
|
114
|
-
bytesWritten: 0,
|
|
115
|
-
reason: 'identical_replacement',
|
|
116
|
-
absPath,
|
|
117
|
-
matchCount: 1,
|
|
118
|
-
detail: 'oldString and newString are identical — no-op',
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
const after = applySingleReplace(before, edit.oldString, edit.newString);
|
|
122
|
-
if (opts.dryRun) {
|
|
123
|
-
return {
|
|
124
|
-
ok: true,
|
|
125
|
-
bytesWritten: 0,
|
|
126
|
-
absPath,
|
|
127
|
-
matchCount: 1,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
try {
|
|
131
|
-
atomicWrite(absPath, after);
|
|
132
|
-
}
|
|
133
|
-
catch (error) {
|
|
134
|
-
return {
|
|
135
|
-
ok: false,
|
|
136
|
-
bytesWritten: 0,
|
|
137
|
-
reason: 'write_error',
|
|
138
|
-
absPath,
|
|
139
|
-
matchCount: 1,
|
|
140
|
-
detail: error instanceof Error ? error.message : String(error),
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
return {
|
|
144
|
-
ok: true,
|
|
145
|
-
bytesWritten: Buffer.byteLength(after, 'utf8'),
|
|
146
|
-
absPath,
|
|
147
|
-
matchCount: 1,
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Count exact (whitespace-sensitive) occurrences of `needle` in `haystack`.
|
|
152
|
-
* Empty needle returns 0 — a model that emits an empty `oldString` is
|
|
153
|
-
* either bug or attempting a full-file replacement, both of which
|
|
154
|
-
* Layer A rejects in favour of Layer C.
|
|
155
|
-
*
|
|
156
|
-
* Implemented via repeated `indexOf` (not `split(needle).length - 1`)
|
|
157
|
-
* to avoid building an O(N) intermediate array for large files.
|
|
158
|
-
*/
|
|
159
|
-
export function countOccurrences(haystack, needle) {
|
|
160
|
-
if (needle.length === 0)
|
|
161
|
-
return 0;
|
|
162
|
-
let count = 0;
|
|
163
|
-
let from = 0;
|
|
164
|
-
while (true) {
|
|
165
|
-
const idx = haystack.indexOf(needle, from);
|
|
166
|
-
if (idx === -1)
|
|
167
|
-
return count;
|
|
168
|
-
count += 1;
|
|
169
|
-
from = idx + needle.length;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Replace the SINGLE occurrence of `needle` in `haystack` with
|
|
174
|
-
* `replacement`. Pre-condition: caller verified `countOccurrences === 1`.
|
|
175
|
-
* Returns the original buffer if no match (defensive; should not occur).
|
|
176
|
-
*/
|
|
177
|
-
function applySingleReplace(haystack, needle, replacement) {
|
|
178
|
-
const idx = haystack.indexOf(needle);
|
|
179
|
-
if (idx === -1)
|
|
180
|
-
return haystack;
|
|
181
|
-
return haystack.slice(0, idx) + replacement + haystack.slice(idx + needle.length);
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Atomic write helper — writes contents to `<path>.pugi-tmp-<ts>-<rand>`
|
|
185
|
-
* then renames over the target. The rename(2) syscall is atomic on
|
|
186
|
-
* POSIX filesystems, so a crash between `writeFileSync` and `renameSync`
|
|
187
|
-
* leaves the ORIGINAL file intact. The orphaned tmp file is named with
|
|
188
|
-
* `pugi-tmp` so a janitor sweep can clean stale artifacts.
|
|
189
|
-
*
|
|
190
|
-
* Mirrors the same pattern used by `writeTool` and `editTool` in
|
|
191
|
-
* `apps/pugi-cli/src/tools/file-tools.ts` so the audit story is
|
|
192
|
-
* uniform.
|
|
193
|
-
*/
|
|
194
|
-
function atomicWrite(absPath, contents) {
|
|
195
|
-
// Suffix combines clock + random so two concurrent edits on the same
|
|
196
|
-
// file from a single process do not collide (Date.now() resolution is
|
|
197
|
-
// millisecond-bounded; two writes inside the same ms would clobber).
|
|
198
|
-
const suffix = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
199
|
-
const tmp = `${absPath}.pugi-tmp-${suffix}`;
|
|
200
|
-
try {
|
|
201
|
-
writeFileSync(tmp, contents, { encoding: 'utf8', mode: 0o600 });
|
|
202
|
-
renameSync(tmp, absPath);
|
|
203
|
-
}
|
|
204
|
-
catch (error) {
|
|
205
|
-
// Best-effort cleanup; if the tmp never landed the unlink will
|
|
206
|
-
// ENOENT and we swallow that. Surface the original error to the
|
|
207
|
-
// caller via re-throw so the apply result records `write_error`.
|
|
208
|
-
try {
|
|
209
|
-
unlinkSync(tmp);
|
|
210
|
-
}
|
|
211
|
-
catch {
|
|
212
|
-
// tmp file may not exist if writeFileSync itself failed.
|
|
213
|
-
}
|
|
214
|
-
throw error;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
//# sourceMappingURL=layer-a-apply.js.map
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Layer A.5 — fuzzy SEARCH/REPLACE applicator.
|
|
3
|
-
*
|
|
4
|
-
* Wraps `tryFuzzyLadder` with the same security gate + atomic-write
|
|
5
|
-
* machinery Layer A uses, then escalates to disk. Sits between Layer
|
|
6
|
-
* A strict-exact (which already failed with `no_match`) and Layer C
|
|
7
|
-
* rewrite (which requires the model к re-emit the whole file).
|
|
8
|
-
*
|
|
9
|
-
* Failure semantics:
|
|
10
|
-
*
|
|
11
|
-
* - `no_match` — all three tiers failed. Operator + model
|
|
12
|
-
* treat this identically к Layer A's
|
|
13
|
-
* `no_match`: re-read with broader context.
|
|
14
|
-
* - `ambiguous_match` — a tier found 2+ equally-scored candidates.
|
|
15
|
-
* Surfaces matchCount so the dispatcher can
|
|
16
|
-
* tell the model "extend context".
|
|
17
|
-
* - `file_missing` — target file does not exist; ladder never
|
|
18
|
-
* creates files (parity с Layer A).
|
|
19
|
-
* - `identical_replacement` — search and replace are identical;
|
|
20
|
-
* would no-op; surfaced LOUD.
|
|
21
|
-
*
|
|
22
|
-
* Inspired by Aider editblock_coder.py (Apache-2.0). independent implementation
|
|
23
|
-
* implementation; no Aider source code copied.
|
|
24
|
-
*/
|
|
25
|
-
import { existsSync, readFileSync, renameSync, writeFileSync, unlinkSync } from 'node:fs';
|
|
26
|
-
import { applySecurityGate } from './security-gate.js';
|
|
27
|
-
import { tryFuzzyLadder } from './fuzzy-ladder.js';
|
|
28
|
-
export async function applyLayerAFuzzy(edit, opts) {
|
|
29
|
-
// Security gate — identical к Layer A. Path scoping + protected
|
|
30
|
-
// basename + symlink-escape re-check. Bypassing this is how a
|
|
31
|
-
// fuzzy retry could rescue an otherwise-failed `.env` edit.
|
|
32
|
-
let gateResult;
|
|
33
|
-
try {
|
|
34
|
-
gateResult = applySecurityGate(edit.file, { cwd: opts.cwd, toolName: 'layer-a-fuzzy' });
|
|
35
|
-
}
|
|
36
|
-
catch (error) {
|
|
37
|
-
return {
|
|
38
|
-
ok: false,
|
|
39
|
-
bytesWritten: 0,
|
|
40
|
-
reason: 'write_error',
|
|
41
|
-
absPath: edit.file,
|
|
42
|
-
detail: error instanceof Error ? error.message : String(error),
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
if (!gateResult.ok) {
|
|
46
|
-
return {
|
|
47
|
-
ok: false,
|
|
48
|
-
bytesWritten: 0,
|
|
49
|
-
reason: gateResult.reason,
|
|
50
|
-
absPath: edit.file,
|
|
51
|
-
detail: gateResult.detail,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
const absPath = gateResult.absPath;
|
|
55
|
-
if (!existsSync(absPath)) {
|
|
56
|
-
return {
|
|
57
|
-
ok: false,
|
|
58
|
-
bytesWritten: 0,
|
|
59
|
-
reason: 'file_missing',
|
|
60
|
-
absPath,
|
|
61
|
-
detail: `file does not exist: ${edit.file}`,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
if (edit.oldString === edit.newString) {
|
|
65
|
-
return {
|
|
66
|
-
ok: false,
|
|
67
|
-
bytesWritten: 0,
|
|
68
|
-
reason: 'identical_replacement',
|
|
69
|
-
absPath,
|
|
70
|
-
detail: 'oldString and newString are identical — no-op',
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
const before = readFileSync(absPath, 'utf8');
|
|
74
|
-
const ladder = tryFuzzyLadder(before, edit.oldString, edit.newString, {
|
|
75
|
-
minRatio: opts.minRatio,
|
|
76
|
-
lengthFlex: opts.lengthFlex,
|
|
77
|
-
});
|
|
78
|
-
if (ladder.kind === 'no_match') {
|
|
79
|
-
return {
|
|
80
|
-
ok: false,
|
|
81
|
-
bytesWritten: 0,
|
|
82
|
-
reason: 'no_match',
|
|
83
|
-
absPath,
|
|
84
|
-
matchCount: 0,
|
|
85
|
-
detail: ladder.detail,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
if (ladder.kind === 'ambiguous') {
|
|
89
|
-
return {
|
|
90
|
-
ok: false,
|
|
91
|
-
bytesWritten: 0,
|
|
92
|
-
reason: 'ambiguous_match',
|
|
93
|
-
absPath,
|
|
94
|
-
matchCount: ladder.candidateCount,
|
|
95
|
-
detail: ladder.detail,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
if (opts.dryRun) {
|
|
99
|
-
return {
|
|
100
|
-
ok: true,
|
|
101
|
-
bytesWritten: 0,
|
|
102
|
-
absPath,
|
|
103
|
-
matchCount: 1,
|
|
104
|
-
fuzzyTier: ladder.tier,
|
|
105
|
-
fuzzyScore: ladder.score,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
// Triple-review P1-3 — re-verify the security gate immediately
|
|
109
|
-
// before the destructive write. The upstream gate ran BEFORE the
|
|
110
|
-
// ladder; on a slow ladder (large file, tier-3 LCS scan) a symlink
|
|
111
|
-
// swap could have re-targeted `absPath` between the two checks. The
|
|
112
|
-
// gate is cheap relative to a fuzzy edit, and double-running it
|
|
113
|
-
// closes the TOCTOU window opened by the per-tool reuse of `absPath`.
|
|
114
|
-
// We pin the re-check to the same tool-name so audit logs correlate.
|
|
115
|
-
let gateRecheck;
|
|
116
|
-
try {
|
|
117
|
-
gateRecheck = applySecurityGate(edit.file, { cwd: opts.cwd, toolName: 'layer-a-fuzzy' });
|
|
118
|
-
}
|
|
119
|
-
catch (error) {
|
|
120
|
-
return {
|
|
121
|
-
ok: false,
|
|
122
|
-
bytesWritten: 0,
|
|
123
|
-
reason: 'write_error',
|
|
124
|
-
absPath,
|
|
125
|
-
matchCount: 1,
|
|
126
|
-
detail: error instanceof Error ? error.message : String(error),
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
if (!gateRecheck.ok) {
|
|
130
|
-
return {
|
|
131
|
-
ok: false,
|
|
132
|
-
bytesWritten: 0,
|
|
133
|
-
reason: gateRecheck.reason,
|
|
134
|
-
absPath,
|
|
135
|
-
matchCount: 1,
|
|
136
|
-
detail: `pre-write recheck: ${gateRecheck.detail}`,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
if (gateRecheck.absPath !== absPath) {
|
|
140
|
-
// The gate resolved to a DIFFERENT path on re-run — a symlink swap
|
|
141
|
-
// or a parent-dir mutation occurred between the two gate calls.
|
|
142
|
-
// Refuse the write loud rather than landing on the new target.
|
|
143
|
-
return {
|
|
144
|
-
ok: false,
|
|
145
|
-
bytesWritten: 0,
|
|
146
|
-
reason: 'symlink_escape',
|
|
147
|
-
absPath,
|
|
148
|
-
matchCount: 1,
|
|
149
|
-
detail: `pre-write recheck resolved to a different path: ${gateRecheck.absPath} != ${absPath}`,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
try {
|
|
153
|
-
atomicWrite(absPath, ladder.after);
|
|
154
|
-
}
|
|
155
|
-
catch (error) {
|
|
156
|
-
return {
|
|
157
|
-
ok: false,
|
|
158
|
-
bytesWritten: 0,
|
|
159
|
-
reason: 'write_error',
|
|
160
|
-
absPath,
|
|
161
|
-
matchCount: 1,
|
|
162
|
-
detail: error instanceof Error ? error.message : String(error),
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
return {
|
|
166
|
-
ok: true,
|
|
167
|
-
bytesWritten: Buffer.byteLength(ladder.after, 'utf8'),
|
|
168
|
-
absPath,
|
|
169
|
-
matchCount: 1,
|
|
170
|
-
fuzzyTier: ladder.tier,
|
|
171
|
-
fuzzyScore: ladder.score,
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* Atomic write — mirrors `layer-a-apply::atomicWrite`. Duplicated
|
|
176
|
-
* intentionally (not exported from Layer A) because the fuzzy module
|
|
177
|
-
* stays decoupled from Layer A's internals — if Layer A's write path
|
|
178
|
-
* ever diverges (e.g. mode bits for a future executable-bit support)
|
|
179
|
-
* the fuzzy ladder should evolve independently.
|
|
180
|
-
*/
|
|
181
|
-
function atomicWrite(absPath, contents) {
|
|
182
|
-
const suffix = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
183
|
-
const tmp = `${absPath}.pugi-tmp-${suffix}`;
|
|
184
|
-
try {
|
|
185
|
-
writeFileSync(tmp, contents, { encoding: 'utf8', mode: 0o600 });
|
|
186
|
-
renameSync(tmp, absPath);
|
|
187
|
-
}
|
|
188
|
-
catch (error) {
|
|
189
|
-
try {
|
|
190
|
-
unlinkSync(tmp);
|
|
191
|
-
}
|
|
192
|
-
catch {
|
|
193
|
-
// tmp may not exist if writeFileSync itself failed.
|
|
194
|
-
}
|
|
195
|
-
throw error;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
//# sourceMappingURL=layer-a-fuzzy-apply.js.map
|