@pugi/cli 0.1.0-beta.99 → 1.0.0-alpha.10
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,303 +0,0 @@
|
|
|
1
|
-
import { AnvilEngineLoopClient } from '../engine/anvil-client.js';
|
|
2
|
-
import { NativePugiEngineAdapter } from '../engine/native-pugi.js';
|
|
3
|
-
import { loadMcpRegistry as defaultLoadMcpRegistry } from '../mcp/registry.js';
|
|
4
|
-
import { openSession } from '../session.js';
|
|
5
|
-
import { loadHookRegistryOrExit as defaultLoadHookRegistry } from '../../runtime/load-hooks-or-exit.js';
|
|
6
|
-
/**
|
|
7
|
-
* Translate `pugi-tool-route command="..."` into the SDK's
|
|
8
|
-
* `EngineTaskKind`. `code` and `fix` pass through verbatim; `build`
|
|
9
|
-
* maps to `build_task` per `apps/pugi-cli/src/core/engine/native-pugi.ts:1444`
|
|
10
|
-
* (`toCommandKind`).
|
|
11
|
-
*/
|
|
12
|
-
function commandToTaskKind(command) {
|
|
13
|
-
if (command === 'build')
|
|
14
|
-
return 'build_task';
|
|
15
|
-
return command;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Translate one `EngineStreamEvent` (rich, adapter-internal vocabulary)
|
|
19
|
-
* into the REPL bridge's narrow `BridgedEngineEvent` shape (four
|
|
20
|
-
* variants -- step / tool.start / tool.result / tokens).
|
|
21
|
-
*
|
|
22
|
-
* Returns `null` for events the bridge surface deliberately ignores
|
|
23
|
-
* (`tool.delta` payloads are surfaced via `tool.result` summary;
|
|
24
|
-
* `thinking.*` and `text.delta` deltas are not part of the bridge
|
|
25
|
-
* UX contract -- the synthetic agent-tree node renders a single
|
|
26
|
-
* `detail` line, not a streaming thinking block).
|
|
27
|
-
*
|
|
28
|
-
* Mutates `names` so a follow-up `tool.end` can resolve its callId
|
|
29
|
-
* back to the recorded tool name (`tool.end` carries only `callId`).
|
|
30
|
-
*/
|
|
31
|
-
function translateStreamEvent(event, names) {
|
|
32
|
-
if (event.type === 'status') {
|
|
33
|
-
return { type: 'step', detail: event.message };
|
|
34
|
-
}
|
|
35
|
-
if (event.type === 'tool.start') {
|
|
36
|
-
names.set(event.callId, event.name);
|
|
37
|
-
return {
|
|
38
|
-
type: 'tool.start',
|
|
39
|
-
tool: event.name,
|
|
40
|
-
args: event.arguments,
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
if (event.type === 'tool.end') {
|
|
44
|
-
const name = names.get(event.callId) ?? '';
|
|
45
|
-
names.delete(event.callId);
|
|
46
|
-
return {
|
|
47
|
-
type: 'tool.result',
|
|
48
|
-
tool: name,
|
|
49
|
-
ok: event.ok,
|
|
50
|
-
preview: event.summary,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
// tool.delta / thinking.* / text.delta intentionally dropped.
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Production factory. Returns an `EngineBridge` the REPL bootstrap
|
|
58
|
-
* passes straight to `new ReplSession({ engineBridge })`.
|
|
59
|
-
*
|
|
60
|
-
* Per invocation lifecycle:
|
|
61
|
-
* 1. `openSession(cwd)` mints a fresh session id and ensures the
|
|
62
|
-
* `.pugi/events.jsonl` ledger captures audit lines for the
|
|
63
|
-
* bridged turn alongside direct `pugi code` invocations.
|
|
64
|
-
* 2. `NativePugiEngineAdapter` is constructed eagerly with the
|
|
65
|
-
* injected (or default) `EngineLoopClient`. The adapter prewarms
|
|
66
|
-
* the real-dispatch import on construction; this happens here for
|
|
67
|
-
* free.
|
|
68
|
-
* 3. `attachStreamListener` subscribes to the adapter's
|
|
69
|
-
* `streamEmitter`, fans every translatable `EngineStreamEvent`
|
|
70
|
-
* to `input.onEvent`, and detaches on bridge exit.
|
|
71
|
-
* 4. `adapter.run(task, ctx)` drives the engine loop; the terminal
|
|
72
|
-
* `result` event maps to the bridge outcome the REPL renders.
|
|
73
|
-
* 5. The bridge's `signal` is threaded straight through `ctx.signal`
|
|
74
|
-
* so a REPL `/stop` aborts the engine loop mid-turn.
|
|
75
|
-
*
|
|
76
|
-
* Failure modes (each must be observable, never silent):
|
|
77
|
-
* - Adapter constructor throws (e.g. invalid config) -- the bridge
|
|
78
|
-
* promise rejects with the underlying error. The REPL's
|
|
79
|
-
* `runEngineBridge` catch surfaces the message on a system line.
|
|
80
|
-
* - Network error mid-loop -- adapter yields `result.status='failed'`,
|
|
81
|
-
* bridge returns `{ outcome: 'failed', detail: result.summary }`.
|
|
82
|
-
* - Operator abort -- adapter honours `ctx.signal`, surfaces an
|
|
83
|
-
* `AbortError`; we propagate the rejection so the REPL flips the
|
|
84
|
-
* synthetic node to `failed` with a clear detail line.
|
|
85
|
-
*/
|
|
86
|
-
export function createEngineBridge(deps) {
|
|
87
|
-
const buildClient = deps.clientFactory ?? ((config) => new AnvilEngineLoopClient(config));
|
|
88
|
-
const loadMcp = deps.loadMcpRegistry ?? defaultLoadMcpRegistry;
|
|
89
|
-
const loadHooks = deps.loadHookRegistry ??
|
|
90
|
-
((opts) => defaultLoadHookRegistry(opts));
|
|
91
|
-
// PR A — per-bridge-instance caches. Spawning MCP child processes
|
|
92
|
-
// (multi-second startup for some servers) and parsing hook configuration
|
|
93
|
-
// are too expensive to repeat per turn AND have no per-turn state. We
|
|
94
|
-
// load once on the first call, reuse across turns. The MCP registry
|
|
95
|
-
// shutdown happens at process exit; a future PR will wire it to the
|
|
96
|
-
// REPL's exit hook so trusted child processes are reaped before the
|
|
97
|
-
// parent terminates.
|
|
98
|
-
//
|
|
99
|
-
// /triple-review P1 round 2: cache stored as in-flight Promise<T>
|
|
100
|
-
// instead of value + boolean flag. Two concurrent first-turn calls
|
|
101
|
-
// would otherwise both observe `mcpLoaded === false`, both spawn the
|
|
102
|
-
// expensive loader, and the second result would overwrite the first —
|
|
103
|
-
// leaking the first registry's child processes. Storing the promise
|
|
104
|
-
// means the second caller awaits the same in-flight load, gets the
|
|
105
|
-
// same instance, and there is no double-spawn. On loader failure the
|
|
106
|
-
// promise resolves to `undefined` (mirrors the value-cache fallback);
|
|
107
|
-
// a future retry on transient ENOENT would mint a NEW promise after
|
|
108
|
-
// explicit invalidation (out of scope for this PR — tracked as P2 fu).
|
|
109
|
-
let mcpRegistryPromise = null;
|
|
110
|
-
let hooksPromise = null;
|
|
111
|
-
const securityHooksParseFailed = { value: false };
|
|
112
|
-
async function resolveMcpRegistry(root) {
|
|
113
|
-
if (mcpRegistryPromise === null) {
|
|
114
|
-
mcpRegistryPromise = (async () => {
|
|
115
|
-
try {
|
|
116
|
-
return await loadMcp(root);
|
|
117
|
-
}
|
|
118
|
-
catch (error) {
|
|
119
|
-
// PR A — mirror cli.ts:6394 failure mode. A bad `.pugi/mcp.json`
|
|
120
|
-
// must not crash the REPL; the operator sees a stderr warning
|
|
121
|
-
// and continues without MCP tools. They can fix the file and
|
|
122
|
-
// the next REPL launch picks up the new registry.
|
|
123
|
-
const msg = error.message;
|
|
124
|
-
process.stderr.write(`pugi REPL engine bridge: MCP registry load failed — ${msg}. ` +
|
|
125
|
-
`Continuing without MCP tools. Fix .pugi/mcp.json to enable.\n`);
|
|
126
|
-
return undefined;
|
|
127
|
-
}
|
|
128
|
-
})();
|
|
129
|
-
}
|
|
130
|
-
return mcpRegistryPromise;
|
|
131
|
-
}
|
|
132
|
-
async function resolveHooks(root, session) {
|
|
133
|
-
if (hooksPromise === null) {
|
|
134
|
-
hooksPromise = (async () => {
|
|
135
|
-
try {
|
|
136
|
-
const outcome = await loadHooks({
|
|
137
|
-
workspaceRoot: root,
|
|
138
|
-
session,
|
|
139
|
-
label: 'repl',
|
|
140
|
-
});
|
|
141
|
-
if (outcome.kind === 'parse-failure-refused') {
|
|
142
|
-
// /triple-review P1 round 2: hooks fail-open is a security
|
|
143
|
-
// regression vs cli.ts:6440 (hard-exit on parse failure).
|
|
144
|
-
// SECURITY: a workspace operator who configured a `PreToolUse
|
|
145
|
-
// onFailure: 'block'` rule that refuses bash containing `rm`
|
|
146
|
-
// loses that protection the moment the JSON file gets a
|
|
147
|
-
// typo. Stderr-only warning scrolls off the TUI.
|
|
148
|
-
//
|
|
149
|
-
// Mitigation: do NOT load hooks for this REPL session (so
|
|
150
|
-
// the operator's mental model — "I have hooks configured" —
|
|
151
|
-
// does not silently break), AND mark the bridge as "hooks
|
|
152
|
-
// parse failed" so every turn surfaces the warning until
|
|
153
|
-
// the file is fixed. The PUGI_HOOKS_BYPASS=1 env var
|
|
154
|
-
// (loadHookRegistryOrExit:97-107) is still the explicit
|
|
155
|
-
// escape hatch when the operator is mid-edit and acknowledges
|
|
156
|
-
// the risk.
|
|
157
|
-
securityHooksParseFailed.value = true;
|
|
158
|
-
process.stderr.write('pugi REPL engine bridge: SECURITY — hooks parse failed. ' +
|
|
159
|
-
'PreToolUse/PostToolUse rules are NOT active for this REPL ' +
|
|
160
|
-
'session. Fix .pugi/hooks.json and restart REPL, OR set ' +
|
|
161
|
-
'PUGI_HOOKS_BYPASS=1 to acknowledge and continue.\n');
|
|
162
|
-
return undefined;
|
|
163
|
-
}
|
|
164
|
-
return outcome.hooks;
|
|
165
|
-
}
|
|
166
|
-
catch (error) {
|
|
167
|
-
const msg = error.message;
|
|
168
|
-
process.stderr.write(`pugi REPL engine bridge: hooks loader threw — ${msg}. ` +
|
|
169
|
-
`Continuing without hooks.\n`);
|
|
170
|
-
return undefined;
|
|
171
|
-
}
|
|
172
|
-
})();
|
|
173
|
-
}
|
|
174
|
-
return hooksPromise;
|
|
175
|
-
}
|
|
176
|
-
return async (input) => {
|
|
177
|
-
const root = deps.cwd();
|
|
178
|
-
const session = openSession(root);
|
|
179
|
-
const client = buildClient(deps.config);
|
|
180
|
-
const [cachedMcpRegistry, cachedHooks] = await Promise.all([
|
|
181
|
-
resolveMcpRegistry(root),
|
|
182
|
-
resolveHooks(root, session),
|
|
183
|
-
]);
|
|
184
|
-
if (securityHooksParseFailed.value) {
|
|
185
|
-
// /triple-review P1 round 2: re-emit the security warning on every
|
|
186
|
-
// bridged turn so the operator does not miss it after scrollback.
|
|
187
|
-
// Cheap one-liner, hard to miss in TUI status pane.
|
|
188
|
-
process.stderr.write('pugi REPL engine bridge: SECURITY reminder — hooks still NOT ' +
|
|
189
|
-
'loaded for this session (.pugi/hooks.json parse failure).\n');
|
|
190
|
-
}
|
|
191
|
-
const adapter = new NativePugiEngineAdapter({
|
|
192
|
-
client,
|
|
193
|
-
session,
|
|
194
|
-
...(cachedMcpRegistry ? { mcpRegistry: cachedMcpRegistry } : {}),
|
|
195
|
-
...(cachedHooks ? { hooks: cachedHooks } : {}),
|
|
196
|
-
...(deps.intensityProfile ? { intensityProfile: deps.intensityProfile } : {}),
|
|
197
|
-
});
|
|
198
|
-
// Per-call name map for matching `tool.end` -> `tool.start`. New
|
|
199
|
-
// every invocation so concurrent bridges never cross-pollute.
|
|
200
|
-
const callNames = new Map();
|
|
201
|
-
// Subscribe BEFORE `adapter.run()` so the first `tool.start` is
|
|
202
|
-
// captured. Detach in `finally` regardless of outcome so the
|
|
203
|
-
// listener does not accumulate across REPL turns (long sessions
|
|
204
|
-
// would otherwise leak one listener per bridged turn).
|
|
205
|
-
const onStreamEvent = (streamEvent) => {
|
|
206
|
-
const translated = translateStreamEvent(streamEvent, callNames);
|
|
207
|
-
if (translated === null)
|
|
208
|
-
return;
|
|
209
|
-
try {
|
|
210
|
-
input.onEvent(translated);
|
|
211
|
-
}
|
|
212
|
-
catch {
|
|
213
|
-
// Fire-and-forget contract -- a broken consumer must never
|
|
214
|
-
// tear down the engine loop.
|
|
215
|
-
}
|
|
216
|
-
};
|
|
217
|
-
adapter.streamEmitter.on('event', onStreamEvent);
|
|
218
|
-
const taskKind = commandToTaskKind(input.command);
|
|
219
|
-
const task = {
|
|
220
|
-
id: input.bridgeId,
|
|
221
|
-
kind: taskKind,
|
|
222
|
-
prompt: input.brief,
|
|
223
|
-
workspaceRoot: root,
|
|
224
|
-
allowedPaths: [root],
|
|
225
|
-
deniedPaths: [],
|
|
226
|
-
artifacts: [],
|
|
227
|
-
permissionMode: 'auto',
|
|
228
|
-
};
|
|
229
|
-
let terminalSummary = '';
|
|
230
|
-
let terminalStatus = 'failed';
|
|
231
|
-
let filesChangedCount = 0;
|
|
232
|
-
let detail;
|
|
233
|
-
try {
|
|
234
|
-
const events = adapter.run(task, {
|
|
235
|
-
sessionId: session.id,
|
|
236
|
-
signal: input.signal,
|
|
237
|
-
});
|
|
238
|
-
for await (const event of events) {
|
|
239
|
-
if (event.type === 'status') {
|
|
240
|
-
// Already fanned out via streamEmitter above. The toplevel
|
|
241
|
-
// `EngineEvent` `status` event predates the rich emitter and
|
|
242
|
-
// is kept for backwards-compat with older adapters; we
|
|
243
|
-
// intentionally do not double-fire `onEvent` here.
|
|
244
|
-
continue;
|
|
245
|
-
}
|
|
246
|
-
// event.type === 'result' -- terminal.
|
|
247
|
-
terminalStatus = event.result.status;
|
|
248
|
-
terminalSummary = event.result.summary;
|
|
249
|
-
filesChangedCount = event.result.filesChanged.length;
|
|
250
|
-
if (event.result.status !== 'done') {
|
|
251
|
-
detail = event.result.summary;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
finally {
|
|
256
|
-
adapter.streamEmitter.off('event', onStreamEvent);
|
|
257
|
-
}
|
|
258
|
-
// Map engine `EngineResult.status` -> bridge `EngineBridgeOutcome`.
|
|
259
|
-
//
|
|
260
|
-
// `needs_verification` ( PUGI-VERIFY-GATE) downgrades a
|
|
261
|
-
// `completed` engine loop with no verification command to a status
|
|
262
|
-
// distinct from `done`. PUGI-538c-FU-OUTCOME (2026-06-05) split
|
|
263
|
-
// the bridge's failure surface: `needs_verification` now maps to
|
|
264
|
-
// the dedicated `unverified` outcome so the REPL agent-tree pane
|
|
265
|
-
// surfaces a yellow advisory instead of a red false-fail. Real
|
|
266
|
-
// verification regressions (`verification_command_failed` -> still
|
|
267
|
-
// `failed` here via the engine's `failed` status) are unchanged.
|
|
268
|
-
//
|
|
269
|
-
// Why the split matters: a fresh customer repo with no Makefile /
|
|
270
|
-
// no `package.json` test script trips `needs_verification` on
|
|
271
|
-
// every routed brief. Collapsing that to `failed` (the pre-538c
|
|
272
|
-
// mapping) produced the trust regression the CEO escalation 2026
|
|
273
|
-
// -06-04 demanded we fix: customer dogfood saw files land on
|
|
274
|
-
// disk yet read "failed" on the agent-tree, lost trust, walked
|
|
275
|
-
// away. The verify-gate contract is preserved: real test failures
|
|
276
|
-
// ALSO reach this branch via engine status `failed` (gated by
|
|
277
|
-
// `computeVerificationOutcome` when a verification command ran
|
|
278
|
-
// and exited non-zero) and still map to `failed`. Only the
|
|
279
|
-
// "no command available" path softens.
|
|
280
|
-
//
|
|
281
|
-
// `blocked` carries through (operator chose the abort / budget
|
|
282
|
-
// exhausted). `done` -> `shipped` -- the only path that produces
|
|
283
|
-
// a clean shipped status is a verified engine loop.
|
|
284
|
-
const outcome = terminalStatus === 'done'
|
|
285
|
-
? 'shipped'
|
|
286
|
-
: terminalStatus === 'needs_verification'
|
|
287
|
-
? 'unverified'
|
|
288
|
-
: terminalStatus === 'blocked'
|
|
289
|
-
? 'blocked'
|
|
290
|
-
: 'failed';
|
|
291
|
-
return {
|
|
292
|
-
outcome,
|
|
293
|
-
filesChanged: filesChangedCount,
|
|
294
|
-
// PUGI-538c scope guard: stream emitter has no typed `tokens`
|
|
295
|
-
// event today; reporting `0` keeps the bridge contract honest
|
|
296
|
-
// until the emitter gains a tokens variant (separate follow-up).
|
|
297
|
-
tokensUsed: 0,
|
|
298
|
-
finalText: terminalSummary.length > 0 ? terminalSummary : undefined,
|
|
299
|
-
...(detail !== undefined ? { detail } : {}),
|
|
300
|
-
};
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
//# sourceMappingURL=engine-bridge.js.map
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FZF-style history search - Sprint .
|
|
3
|
-
*
|
|
4
|
-
* Powers the Ctrl+R (reverse) / Ctrl+S (forward) interactive search
|
|
5
|
-
* mode in the REPL input box. Hand-rolled scorer (no dep) tuned for
|
|
6
|
-
* short query strings (operator typing a few chars) over up to
|
|
7
|
-
* MAX_HISTORY_ENTRIES candidates.
|
|
8
|
-
*
|
|
9
|
-
* Scoring model (mirrors fzf v2 mid-priorities, simplified):
|
|
10
|
-
*
|
|
11
|
-
* - Substring match required. Non-matching candidates score 0.
|
|
12
|
-
* - Prefix match: +50
|
|
13
|
-
* - Word-boundary start (' ', '/', '-', '_'): +20
|
|
14
|
-
* - Camel boundary (`abcD` → 'D'): +10
|
|
15
|
-
* - Contiguous run bonus: +5 per consecutive char after the first
|
|
16
|
-
* - Distance penalty: -1 per gap char between query characters
|
|
17
|
-
* - Recency bonus: +0.5 × (recencyRank / total) so newer entries
|
|
18
|
-
* break ties in the operator's favour
|
|
19
|
-
*
|
|
20
|
-
* The result is a list of matches ordered by descending score with
|
|
21
|
-
* the matched character positions preserved so the UI can highlight
|
|
22
|
-
* them. `cycle` steps the focused match index forward (Ctrl+R again)
|
|
23
|
-
* or backward (Ctrl+S) modulo the result length.
|
|
24
|
-
*
|
|
25
|
-
* Contract:
|
|
26
|
-
* - `searchHistory(query, entries, options)` is pure. No I/O.
|
|
27
|
-
* - Empty query returns ALL entries newest-first so the operator
|
|
28
|
-
* can browse without typing.
|
|
29
|
-
* - Matching is case-insensitive on ASCII; non-ASCII characters
|
|
30
|
-
* compare as-is.
|
|
31
|
-
* - `cycle(state, direction)` is a no-op when there are no matches.
|
|
32
|
-
*/
|
|
33
|
-
const DEFAULT_LIMIT = 50;
|
|
34
|
-
const WORD_BOUNDARIES = new Set([' ', '/', '-', '_', '.', ',', ':', ';', '\t']);
|
|
35
|
-
/**
|
|
36
|
-
* Score one candidate against the query. Returns `null` when the
|
|
37
|
-
* query cannot be matched as an in-order substring (not necessarily
|
|
38
|
-
* contiguous). The matched positions are returned so the UI can
|
|
39
|
-
* underline them.
|
|
40
|
-
*/
|
|
41
|
-
export function scoreCandidate(query, candidate) {
|
|
42
|
-
if (query.length === 0) {
|
|
43
|
-
return { score: 0, positions: [] };
|
|
44
|
-
}
|
|
45
|
-
const lowerQuery = query.toLowerCase();
|
|
46
|
-
const lowerCandidate = candidate.toLowerCase();
|
|
47
|
-
// Walk the candidate, greedily matching each query char in order.
|
|
48
|
-
const positions = [];
|
|
49
|
-
let qIdx = 0;
|
|
50
|
-
for (let i = 0; i < lowerCandidate.length && qIdx < lowerQuery.length; i += 1) {
|
|
51
|
-
if (lowerCandidate[i] === lowerQuery[qIdx]) {
|
|
52
|
-
positions.push(i);
|
|
53
|
-
qIdx += 1;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
if (qIdx < lowerQuery.length)
|
|
57
|
-
return null;
|
|
58
|
-
// Score the match.
|
|
59
|
-
let score = 0;
|
|
60
|
-
// Prefix bonus.
|
|
61
|
-
if (positions[0] === 0)
|
|
62
|
-
score += 50;
|
|
63
|
-
// Substring contiguity + boundary bonuses.
|
|
64
|
-
let lastPos = -2;
|
|
65
|
-
for (let p = 0; p < positions.length; p += 1) {
|
|
66
|
-
const pos = positions[p];
|
|
67
|
-
// Contiguous run.
|
|
68
|
-
if (pos === lastPos + 1) {
|
|
69
|
-
score += 5;
|
|
70
|
-
}
|
|
71
|
-
else if (lastPos >= 0) {
|
|
72
|
-
// Distance penalty for non-contiguous matches.
|
|
73
|
-
score -= pos - lastPos - 1;
|
|
74
|
-
}
|
|
75
|
-
// Word-boundary start.
|
|
76
|
-
if (pos === 0) {
|
|
77
|
-
// Already counted as prefix.
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
const prev = candidate[pos - 1] ?? '';
|
|
81
|
-
if (WORD_BOUNDARIES.has(prev)) {
|
|
82
|
-
score += 20;
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
// Camel boundary: prev is lowercase, current is uppercase.
|
|
86
|
-
const cur = candidate[pos] ?? '';
|
|
87
|
-
if (prev === prev.toLowerCase() && cur !== cur.toLowerCase()) {
|
|
88
|
-
score += 10;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
lastPos = pos;
|
|
93
|
-
}
|
|
94
|
-
return { score, positions };
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Search a history list for matches. Entries are passed oldest-first
|
|
98
|
-
* (the format `history.read` returns); the recency bonus uses the
|
|
99
|
-
* original index so newer entries win ties.
|
|
100
|
-
*/
|
|
101
|
-
export function searchHistory(query, entries, options = {}) {
|
|
102
|
-
const limit = options.limit ?? DEFAULT_LIMIT;
|
|
103
|
-
const total = entries.length;
|
|
104
|
-
if (query.length === 0) {
|
|
105
|
-
// Empty query - browse mode. Return newest-first up to limit, no
|
|
106
|
-
// scoring needed.
|
|
107
|
-
const out = [];
|
|
108
|
-
for (let i = total - 1; i >= 0 && out.length < limit; i -= 1) {
|
|
109
|
-
const brief = entries[i];
|
|
110
|
-
out.push({ brief, positions: [], score: 0, originalIndex: i });
|
|
111
|
-
}
|
|
112
|
-
return out;
|
|
113
|
-
}
|
|
114
|
-
const candidates = [];
|
|
115
|
-
for (let i = 0; i < total; i += 1) {
|
|
116
|
-
const brief = entries[i];
|
|
117
|
-
const scored = scoreCandidate(query, brief);
|
|
118
|
-
if (!scored)
|
|
119
|
-
continue;
|
|
120
|
-
// Recency bonus: 0..0.5 weight scaled by index/total.
|
|
121
|
-
const recency = total === 0 ? 0 : (i / total) * 0.5;
|
|
122
|
-
candidates.push({
|
|
123
|
-
brief,
|
|
124
|
-
positions: scored.positions,
|
|
125
|
-
score: scored.score + recency,
|
|
126
|
-
originalIndex: i,
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
candidates.sort((a, b) => {
|
|
130
|
-
if (b.score !== a.score)
|
|
131
|
-
return b.score - a.score;
|
|
132
|
-
// Tie-breaker: newer first.
|
|
133
|
-
return b.originalIndex - a.originalIndex;
|
|
134
|
-
});
|
|
135
|
-
return candidates.slice(0, limit);
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Initial search state for a fresh Ctrl+R press. Empty query +
|
|
139
|
-
* focusedIndex 0 over the browse list.
|
|
140
|
-
*/
|
|
141
|
-
export function initialSearchState(entries) {
|
|
142
|
-
const matches = searchHistory('', entries);
|
|
143
|
-
return { query: '', matches, focusedIndex: 0 };
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Update the search state when the operator types a new query char or
|
|
147
|
-
* removes one. The focused index is clamped to the new match list so
|
|
148
|
-
* the UI never points off-the-end.
|
|
149
|
-
*/
|
|
150
|
-
export function applyQuery(state, query, entries) {
|
|
151
|
-
const matches = searchHistory(query, entries);
|
|
152
|
-
const focusedIndex = matches.length === 0 ? 0 : Math.min(state.focusedIndex, matches.length - 1);
|
|
153
|
-
return { query, matches, focusedIndex };
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Move the focused match. `+1` = next (Ctrl+R repeat), `-1` = previous
|
|
157
|
-
* (Ctrl+S). Wraps around modulo result length so the operator never
|
|
158
|
-
* has to chase the end.
|
|
159
|
-
*/
|
|
160
|
-
export function cycle(state, direction) {
|
|
161
|
-
if (state.matches.length === 0)
|
|
162
|
-
return state;
|
|
163
|
-
const len = state.matches.length;
|
|
164
|
-
const next = (state.focusedIndex + direction + len) % len;
|
|
165
|
-
return { ...state, focusedIndex: next };
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Pull the brief from the currently focused match, or `null` when
|
|
169
|
-
* the result list is empty (Enter accepts nothing).
|
|
170
|
-
*/
|
|
171
|
-
export function currentBrief(state) {
|
|
172
|
-
const match = state.matches[state.focusedIndex];
|
|
173
|
-
return match ? match.brief : null;
|
|
174
|
-
}
|
|
175
|
-
//# sourceMappingURL=history-search.js.map
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Persistent REPL history (per-workspace) - Sprint .
|
|
3
|
-
*
|
|
4
|
-
* Stores submitted briefs in `~/.pugi/history/<workspace-slug>.jsonl`,
|
|
5
|
-
* one JSON object per line. The format is line-delimited JSON so the
|
|
6
|
-
* file can be appended to atomically (single `write(2)` per entry on
|
|
7
|
-
* Linux/macOS for entries < PIPE_BUF) and tailed by humans without a
|
|
8
|
-
* parser. Per-workspace separation lets the operator switch repos and
|
|
9
|
-
* keep brief history contextual: `brief: fix the cabinet sidebar 401`
|
|
10
|
-
* does not bleed into the agents repo.
|
|
11
|
-
*
|
|
12
|
-
* Contract:
|
|
13
|
-
*
|
|
14
|
-
* - `append({ home, workspaceSlug, brief })` writes one line. Dedups
|
|
15
|
-
* a brief that is identical to the immediately preceding entry
|
|
16
|
-
* (most common operator pattern: Up + Enter to re-run).
|
|
17
|
-
* - `read({ home, workspaceSlug })` returns entries oldest-first so
|
|
18
|
-
* the caller can navigate with `index = entries.length - 1` for
|
|
19
|
-
* "most recent" semantics.
|
|
20
|
-
* - The file is capped at MAX_ENTRIES; on overflow we keep the most
|
|
21
|
-
* recent slice and rewrite. Cheap because briefs are short text
|
|
22
|
-
* and the cap is 1000.
|
|
23
|
-
* - `slugForCwd(cwd)` normalises a working directory into a safe
|
|
24
|
-
* filename component (alphanumerics + `-`, lowercase, slashes
|
|
25
|
-
* collapsed). Empty cwd resolves to `default`.
|
|
26
|
-
* - Failures (missing $HOME, disk full, EACCES) NEVER throw. History
|
|
27
|
-
* is operator comfort, not a contract surface; degrading to "no
|
|
28
|
-
* history this session" is correct.
|
|
29
|
-
*
|
|
30
|
-
* Brand voice: file is operator-facing if they `cat` it, so the JSON
|
|
31
|
-
* keys stay readable English (`brief`, `ts`). No forbidden words.
|
|
32
|
-
*/
|
|
33
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync, renameSync, unlinkSync, } from 'node:fs';
|
|
34
|
-
import { randomBytes } from 'node:crypto';
|
|
35
|
-
import { homedir } from 'node:os';
|
|
36
|
-
import { dirname, join } from 'node:path';
|
|
37
|
-
/** Cap on stored entries per workspace. Drops oldest on overflow. */
|
|
38
|
-
export const MAX_HISTORY_ENTRIES = 1000;
|
|
39
|
-
/**
|
|
40
|
-
* Compute the on-disk path for a given workspace slug. Tests rely on
|
|
41
|
-
* this to assert per-workspace isolation without re-implementing the
|
|
42
|
-
* directory math.
|
|
43
|
-
*/
|
|
44
|
-
export function historyPath(io) {
|
|
45
|
-
const home = io.home ?? homedir();
|
|
46
|
-
const safe = sanitiseSlug(io.workspaceSlug);
|
|
47
|
-
return join(home, '.pugi', 'history', `${safe}.jsonl`);
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Append a brief to history. Dedups consecutive identical entries.
|
|
51
|
-
* Returns the entry that was written, or `null` when the entry was
|
|
52
|
-
* deduped or the brief was empty.
|
|
53
|
-
*/
|
|
54
|
-
export function append(input) {
|
|
55
|
-
const brief = input.brief.trim();
|
|
56
|
-
if (brief.length === 0)
|
|
57
|
-
return null;
|
|
58
|
-
const path = historyPath(input);
|
|
59
|
-
try {
|
|
60
|
-
ensureDir(dirname(path));
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
const existing = read({ home: input.home, workspaceSlug: input.workspaceSlug });
|
|
66
|
-
const last = existing[existing.length - 1];
|
|
67
|
-
if (last && last.brief === brief) {
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
const ts = (input.now ?? (() => new Date()))().toISOString();
|
|
71
|
-
const entry = { ts, brief };
|
|
72
|
-
// Overflow path: combined length > cap means we trim before rewrite.
|
|
73
|
-
// Write to a sibling tmp file and renameSync over the target so
|
|
74
|
-
// concurrent CLI instances in the same workspace cannot observe a
|
|
75
|
-
// half-written file or race a parallel appendFileSync into oblivion.
|
|
76
|
-
// POSIX renameSync is atomic within a directory; on Windows fs.rename
|
|
77
|
-
// is atomic too as long as both paths are on the same volume (the tmp
|
|
78
|
-
// sibling guarantees that). P2 fix from PR triple-review.
|
|
79
|
-
if (existing.length + 1 > MAX_HISTORY_ENTRIES) {
|
|
80
|
-
const trimmed = [...existing.slice(existing.length + 1 - MAX_HISTORY_ENTRIES), entry];
|
|
81
|
-
// β1b #52 : unique-per-call tmp suffix.
|
|
82
|
-
// Previous form was a fixed `${path}.tmp`, which means two CLI
|
|
83
|
-
// processes hitting the overflow rewrite at the same moment race
|
|
84
|
-
// on the same sibling file. Whichever writeFileSync lands second
|
|
85
|
-
// can corrupt the renameSync target's content (one process's
|
|
86
|
-
// serialized buffer overwrites the other mid-flight). Append a
|
|
87
|
-
// pid + monotonic-ish timestamp + 8 hex random bytes so the tmp
|
|
88
|
-
// names are collision-proof across PIDs, concurrent calls inside
|
|
89
|
-
// one PID, and rapid re-runs that share the same ms timestamp.
|
|
90
|
-
const tmpPath = `${path}.${process.pid}.${Date.now()}.${randomBytes(4).toString('hex')}.tmp`;
|
|
91
|
-
try {
|
|
92
|
-
writeFileSync(tmpPath, trimmed.map(serialize).join('\n') + '\n', { mode: 0o600 });
|
|
93
|
-
renameSync(tmpPath, path);
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
// Best-effort cleanup of the orphan tmp file; never throw out.
|
|
97
|
-
try {
|
|
98
|
-
unlinkSync(tmpPath);
|
|
99
|
-
}
|
|
100
|
-
catch {
|
|
101
|
-
/* ignore — tmp file may not exist yet */
|
|
102
|
-
}
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
return entry;
|
|
106
|
-
}
|
|
107
|
-
try {
|
|
108
|
-
appendFileSync(path, serialize(entry) + '\n', { mode: 0o600 });
|
|
109
|
-
}
|
|
110
|
-
catch {
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
return entry;
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Read history for a workspace, oldest-first. Returns `[]` when the
|
|
117
|
-
* file is missing, unreadable, or empty. Malformed lines are dropped
|
|
118
|
-
* silently - one bad line should not nuke the whole history.
|
|
119
|
-
*/
|
|
120
|
-
export function read(io) {
|
|
121
|
-
const path = historyPath(io);
|
|
122
|
-
if (!existsSync(path))
|
|
123
|
-
return [];
|
|
124
|
-
let raw;
|
|
125
|
-
try {
|
|
126
|
-
raw = readFileSync(path, 'utf8');
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
129
|
-
return [];
|
|
130
|
-
}
|
|
131
|
-
const out = [];
|
|
132
|
-
for (const line of raw.split('\n')) {
|
|
133
|
-
const trimmed = line.trim();
|
|
134
|
-
if (trimmed.length === 0)
|
|
135
|
-
continue;
|
|
136
|
-
try {
|
|
137
|
-
const parsed = JSON.parse(trimmed);
|
|
138
|
-
if (typeof parsed === 'object' &&
|
|
139
|
-
parsed !== null &&
|
|
140
|
-
typeof parsed.brief === 'string' &&
|
|
141
|
-
typeof parsed.ts === 'string') {
|
|
142
|
-
out.push({
|
|
143
|
-
ts: parsed.ts,
|
|
144
|
-
brief: parsed.brief,
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
catch {
|
|
149
|
-
// Drop malformed line.
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return out;
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Normalise a cwd or workspace name into a safe filename component.
|
|
156
|
-
* Lowercase, alphanumerics + `-` only. Slashes become `-`. Empty input
|
|
157
|
-
* resolves to `default` so we never produce an empty filename.
|
|
158
|
-
*/
|
|
159
|
-
export function slugForCwd(cwd) {
|
|
160
|
-
if (!cwd || cwd.trim().length === 0)
|
|
161
|
-
return 'default';
|
|
162
|
-
// Strip leading slash so `/Users/foo` becomes `users-foo`.
|
|
163
|
-
const normalised = cwd
|
|
164
|
-
.replace(/^[/\\]+/, '')
|
|
165
|
-
.replace(/[/\\]+/g, '-')
|
|
166
|
-
.toLowerCase();
|
|
167
|
-
return sanitiseSlug(normalised);
|
|
168
|
-
}
|
|
169
|
-
function sanitiseSlug(raw) {
|
|
170
|
-
const cleaned = raw.replace(/[^a-z0-9-]/gi, '-').toLowerCase().replace(/-+/g, '-');
|
|
171
|
-
const trimmed = cleaned.replace(/^-+|-+$/g, '');
|
|
172
|
-
return trimmed.length === 0 ? 'default' : trimmed;
|
|
173
|
-
}
|
|
174
|
-
function serialize(entry) {
|
|
175
|
-
return JSON.stringify(entry);
|
|
176
|
-
}
|
|
177
|
-
function ensureDir(dir) {
|
|
178
|
-
if (existsSync(dir))
|
|
179
|
-
return;
|
|
180
|
-
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
181
|
-
}
|
|
182
|
-
//# sourceMappingURL=history.js.map
|