@pugi/cli 0.1.0-beta.7 → 0.1.0-beta.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +96 -0
- package/THIRD_PARTY_NOTICES.md +40 -0
- package/assets/pugi-prozr2-mascot.ansi +9 -0
- package/bin/run.js +33 -1
- package/dist/commands/deploy.js +40 -40
- package/dist/commands/flatten.js +191 -0
- package/dist/commands/jobs-watch.js +201 -0
- package/dist/commands/jobs.js +42 -27
- package/dist/commands/smoke.js +133 -0
- package/dist/core/agent-progress/cleanup.js +134 -0
- package/dist/core/agent-progress/schema.js +144 -0
- package/dist/core/agent-progress/writer.js +101 -0
- package/dist/core/agents/adaptive-router.js +330 -0
- package/dist/core/agents/query-decomposer.js +297 -0
- package/dist/core/agents/registry.js +2 -2
- package/dist/core/approvals/shortcut-resolver.js +98 -0
- package/dist/core/artifact-chain/dispatcher.js +148 -0
- package/dist/core/artifact-chain/exporter.js +164 -0
- package/dist/core/artifact-chain/state.js +243 -0
- package/dist/core/artifact-chain/steps.js +169 -0
- package/dist/core/ask-user/question.js +92 -0
- package/dist/core/audit/audit-trail.js +275 -0
- package/dist/core/auth/ensure-authenticated.js +129 -0
- package/dist/core/auth/env-provider.js +238 -0
- package/dist/core/auto-open-browser.js +4 -4
- package/dist/core/auto-update/channels.js +122 -0
- package/dist/core/auto-update/checker.js +241 -0
- package/dist/core/auto-update/state.js +235 -0
- package/dist/core/bare-mode/index.js +107 -0
- package/dist/core/bash/redirect.js +281 -0
- package/dist/core/bash-classifier.js +436 -40
- package/dist/core/checkpoint/resumer.js +149 -0
- package/dist/core/checkpoint/rewinder.js +291 -0
- package/dist/core/checkpoints/shadow-git.js +670 -0
- package/dist/core/citations/parser.js +109 -0
- package/dist/core/classifier/yolo-classifier.js +88 -0
- package/dist/core/codegraph/decision-store.js +248 -0
- package/dist/core/codegraph/detect-repo.js +459 -0
- package/dist/core/codegraph/install.js +134 -0
- package/dist/core/codegraph/offer-hook.js +220 -0
- package/dist/core/compact/auto-trigger.js +96 -0
- package/dist/core/compact/buffer-rewriter.js +115 -0
- package/dist/core/compact/summarizer.js +208 -0
- package/dist/core/compact/token-counter.js +108 -0
- package/dist/core/consensus/anvil-fanout.js +25 -25
- package/dist/core/consensus/diff-capture.js +121 -12
- package/dist/core/consensus/rubric.js +21 -21
- package/dist/core/context/builder.js +6 -6
- package/dist/core/context/compaction-events.js +8 -8
- package/dist/core/context/compaction.js +31 -31
- package/dist/core/context/index.js +15 -8
- package/dist/core/context/invariants.js +51 -51
- package/dist/core/context/markdown-loader.js +28 -10
- package/dist/core/context/markdown-traverse.js +255 -0
- package/dist/core/context/pugiignore.js +41 -41
- package/dist/core/context/repo-skeleton.js +37 -37
- package/dist/core/context/tool-eviction.js +55 -0
- package/dist/core/context/watcher.js +32 -32
- package/dist/core/context/working-set.js +23 -23
- package/dist/core/coordinator/agent-tools.js +77 -0
- package/dist/core/coordinator/agent-toolset.js +65 -0
- package/dist/core/coordinator/fsm.js +73 -0
- package/dist/core/coordinator/mode-fsm.js +70 -0
- package/dist/core/cost/rate-card.js +129 -0
- package/dist/core/cost/tracker.js +221 -0
- package/dist/core/credentials.js +12 -12
- package/dist/core/cron/scheduler.js +138 -0
- package/dist/core/denial-tracking/index.js +8 -0
- package/dist/core/denial-tracking/state.js +264 -0
- package/dist/core/diagnostics/probe-runner.js +93 -0
- package/dist/core/diagnostics/probes/api.js +46 -0
- package/dist/core/diagnostics/probes/auth.js +93 -0
- package/dist/core/diagnostics/probes/bare-mode.js +42 -0
- package/dist/core/diagnostics/probes/cli-version.js +127 -0
- package/dist/core/diagnostics/probes/config.js +72 -0
- package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
- package/dist/core/diagnostics/probes/disk.js +81 -0
- package/dist/core/diagnostics/probes/engine-live.js +46 -0
- package/dist/core/diagnostics/probes/git.js +65 -0
- package/dist/core/diagnostics/probes/hooks.js +118 -0
- package/dist/core/diagnostics/probes/mcp.js +75 -0
- package/dist/core/diagnostics/probes/node.js +59 -0
- package/dist/core/diagnostics/probes/pnpm.js +36 -0
- package/dist/core/diagnostics/probes/pugi-md.js +89 -0
- package/dist/core/diagnostics/probes/sandbox.js +40 -0
- package/dist/core/diagnostics/probes/session.js +74 -0
- package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
- package/dist/core/diagnostics/probes/workspace.js +63 -0
- package/dist/core/diagnostics/types.js +70 -0
- package/dist/core/dispatch/cache-cleanup.js +197 -0
- package/dist/core/dispatch/cache-handoff.js +295 -0
- package/dist/core/edits/apply-patch-layer-e.js +189 -0
- package/dist/core/edits/dispatch.js +293 -7
- package/dist/core/edits/format-matrix.js +26 -0
- package/dist/core/edits/fuzzy-ladder.js +650 -0
- package/dist/core/edits/index.js +3 -1
- package/dist/core/edits/journal.js +199 -0
- package/dist/core/edits/layer-a-apply.js +15 -15
- package/dist/core/edits/layer-a-fuzzy-apply.js +198 -0
- package/dist/core/edits/layer-b-apply.js +9 -9
- package/dist/core/edits/layer-c-apply.js +6 -6
- package/dist/core/edits/layer-d-ast.js +557 -14
- package/dist/core/edits/marker-parser.js +12 -12
- package/dist/core/edits/security-gate.js +27 -27
- package/dist/core/edits/verify-hook.js +273 -0
- package/dist/core/edits/worktree.js +322 -0
- package/dist/core/engine/anvil-client.js +140 -26
- package/dist/core/engine/auto-compact.js +179 -0
- package/dist/core/engine/budgets.js +186 -0
- package/dist/core/engine/context-prefix.js +155 -0
- package/dist/core/engine/index.js +1 -1
- package/dist/core/engine/intensity.js +158 -0
- package/dist/core/engine/intent.js +260 -0
- package/dist/core/engine/native-pugi.js +1295 -227
- package/dist/core/engine/prompts.js +134 -16
- package/dist/core/engine/strip-internal-fields.js +124 -0
- package/dist/core/engine/tool-bridge.js +1295 -59
- package/dist/core/evaluation/golden-dataset.js +293 -0
- package/dist/core/feedback/queue.js +177 -0
- package/dist/core/feedback/submitter.js +145 -0
- package/dist/core/file-cache.js +113 -1
- package/dist/core/flatten/flatten-repo.js +439 -0
- package/dist/core/format/osc8-link.js +28 -0
- package/dist/core/hook-chains.js +392 -0
- package/dist/core/hooks/citation-verify-hook.js +138 -0
- package/dist/core/hooks/citation-verify.js +112 -0
- package/dist/core/hooks/events.js +44 -0
- package/dist/core/hooks/index.js +15 -0
- package/dist/core/hooks/registry.js +213 -0
- package/dist/core/hooks/runner.js +236 -0
- package/dist/core/hooks/v2/event-emitter.js +115 -0
- package/dist/core/hooks/v2/executor.js +282 -0
- package/dist/core/hooks/v2/index.js +25 -0
- package/dist/core/hooks/v2/lifecycle.js +104 -0
- package/dist/core/hooks/v2/loader.js +216 -0
- package/dist/core/hooks/v2/matcher.js +125 -0
- package/dist/core/hooks/v2/trust.js +143 -0
- package/dist/core/hooks/v2/types.js +86 -0
- package/dist/core/image/renderer.js +71 -0
- package/dist/core/init/detector.js +582 -0
- package/dist/core/init/template-renderer.js +242 -0
- package/dist/core/jobs/registry.js +18 -18
- package/dist/core/ledger/results-tsv.js +142 -0
- package/dist/core/log-discipline/stdout-redirect.js +51 -0
- package/dist/core/lsp/cache.js +105 -0
- package/dist/core/lsp/client.js +776 -0
- package/dist/core/lsp/language-detect.js +66 -0
- package/dist/core/lsp/post-edit-diagnostics.js +171 -0
- package/dist/core/lsp/symbol-tools.js +372 -0
- package/dist/core/mcp/client.js +97 -28
- package/dist/core/mcp/http-server.js +553 -0
- package/dist/core/mcp/orchestrator-tools.js +662 -0
- package/dist/core/mcp/permission.js +190 -0
- package/dist/core/mcp/registry.js +39 -17
- package/dist/core/mcp/server-tools.js +219 -0
- package/dist/core/mcp/server.js +397 -0
- package/dist/core/mcp/trust.js +10 -10
- package/dist/core/memory/dual-write.js +416 -0
- package/dist/core/memory/passive-extract.js +130 -0
- package/dist/core/memory/phase1-kinds.js +20 -0
- package/dist/core/memory/secret-scanner.js +304 -0
- package/dist/core/memory-sync/queue.js +170 -0
- package/dist/core/metrics/extract.js +113 -0
- package/dist/core/modes/roo-modes.js +68 -0
- package/dist/core/onboarding/ensure-initialized.js +133 -0
- package/dist/core/onboarding/marker.js +111 -0
- package/dist/core/onboarding/telemetry-state.js +108 -0
- package/dist/core/output-style/presets.js +176 -0
- package/dist/core/output-style/state.js +185 -0
- package/dist/core/path-security.js +287 -5
- package/dist/core/permission.js +82 -22
- package/dist/core/permissions/auto-classifier.js +124 -0
- package/dist/core/permissions/bash-parser.js +371 -0
- package/dist/core/permissions/circuit-breaker.js +83 -0
- package/dist/core/permissions/constrained-edit.js +91 -0
- package/dist/core/permissions/gate.js +278 -0
- package/dist/core/permissions/index.js +20 -0
- package/dist/core/permissions/mode.js +174 -0
- package/dist/core/permissions/network-egress.js +137 -0
- package/dist/core/permissions/state.js +241 -0
- package/dist/core/permissions/tool-class.js +93 -0
- package/dist/core/plan-mode/ui-state.js +51 -0
- package/dist/core/plans/plan-artifact.js +721 -0
- package/dist/core/policy-limits/etag-store.js +122 -0
- package/dist/core/prd-check/parser.js +215 -0
- package/dist/core/prd-check/reporter.js +127 -0
- package/dist/core/prd-check/session-review.js +557 -0
- package/dist/core/prd-check/verifiers.js +223 -0
- package/dist/core/prompt-cache/client-cache.js +99 -0
- package/dist/core/prompts/assembly.js +29 -0
- package/dist/core/prompts/registry.js +364 -0
- package/dist/core/pugi-md/cc-compat-rules.js +735 -0
- package/dist/core/pugi-md/context-injector.js +76 -0
- package/dist/core/pugi-md/walk-up.js +207 -0
- package/dist/core/python/uv-installer.js +270 -0
- package/dist/core/python/uv-resolver.js +83 -0
- package/dist/core/rate-limit/narrator.js +146 -0
- package/dist/core/recipes/cli-types.js +20 -0
- package/dist/core/recipes/loader.js +103 -0
- package/dist/core/recipes/runner.js +345 -0
- package/dist/core/recipes/schema.js +587 -0
- package/dist/core/release-notes/parser.js +241 -0
- package/dist/core/release-notes/state.js +116 -0
- package/dist/core/repl/ask.js +37 -37
- package/dist/core/repl/cancellation.js +26 -26
- package/dist/core/repl/cap-warning.js +4 -4
- package/dist/core/repl/clipboard-read.js +11 -11
- package/dist/core/repl/dispatch-fsm.js +12 -12
- package/dist/core/repl/history-search.js +15 -15
- package/dist/core/repl/history.js +28 -18
- package/dist/core/repl/kill-ring.js +5 -5
- package/dist/core/repl/model-pricing.js +135 -0
- package/dist/core/repl/privacy-banner.js +22 -22
- package/dist/core/repl/session.js +2157 -214
- package/dist/core/repl/slash-commands.js +533 -40
- package/dist/core/repl/store/index.js +1 -1
- package/dist/core/repl/store/jsonl-log.js +22 -22
- package/dist/core/repl/store/lockfile.js +10 -10
- package/dist/core/repl/store/session-store.js +136 -107
- package/dist/core/repl/store/types.js +15 -15
- package/dist/core/repl/store/uuid-v7.js +12 -12
- package/dist/core/repl/workspace-context.js +43 -21
- package/dist/core/repo-map/build.js +125 -0
- package/dist/core/repo-map/cache.js +185 -0
- package/dist/core/repo-map/extractor.js +254 -0
- package/dist/core/repo-map/formatter.js +145 -0
- package/dist/core/repo-map/page-rank.js +105 -0
- package/dist/core/repo-map/scanner.js +211 -0
- package/dist/core/retry-budget/budget.js +284 -0
- package/dist/core/retry-budget/index.js +5 -0
- package/dist/core/retry-budget/retry-cap.js +74 -0
- package/dist/core/routing/lead-worker.js +43 -0
- package/dist/core/routing/pre-flight-estimator.js +108 -0
- package/dist/core/runs/run-tree.js +103 -0
- package/dist/core/security/injection-scanner.js +367 -0
- package/dist/core/security/output-filter.js +418 -0
- package/dist/core/session/env-file.js +105 -0
- package/dist/core/session/section-budgets.js +140 -0
- package/dist/core/session.js +92 -0
- package/dist/core/settings.js +286 -5
- package/dist/core/share/formatter.js +271 -0
- package/dist/core/share/redactor.js +221 -0
- package/dist/core/share/uploader.js +267 -0
- package/dist/core/skills/defaults.js +457 -0
- package/dist/core/skills/loader.js +22 -22
- package/dist/core/skills/sources.js +27 -27
- package/dist/core/smoke/headless-driver.js +174 -0
- package/dist/core/smoke/orchestrator.js +194 -0
- package/dist/core/smoke/runner.js +238 -0
- package/dist/core/smoke/scenario-parser.js +316 -0
- package/dist/core/statusline.js +99 -0
- package/dist/core/subagents/dispatcher-real.js +600 -0
- package/dist/core/subagents/dispatcher.js +132 -43
- package/dist/core/subagents/index.js +19 -6
- package/dist/core/subagents/isolation-matrix.js +213 -0
- package/dist/core/subagents/spawn.js +19 -4
- package/dist/core/telemetry/emitter.js +229 -0
- package/dist/core/telemetry/queue.js +251 -0
- package/dist/core/theme/context.js +91 -0
- package/dist/core/theme/presets.js +228 -0
- package/dist/core/theme/state.js +181 -0
- package/dist/core/todos/invariant.js +10 -0
- package/dist/core/todos/state.js +177 -0
- package/dist/core/tool-schema/compressor.js +89 -0
- package/dist/core/transport/version-interceptor.js +166 -0
- package/dist/core/trust.js +2 -2
- package/dist/core/tui/thinking-block.js +64 -0
- package/dist/core/vim/keymap.js +288 -0
- package/dist/core/vim/state.js +92 -0
- package/dist/core/watch-markers/marker-watcher.js +133 -0
- package/dist/core/worktree-manager/cleanup.js +123 -0
- package/dist/core/worktree-manager/manager.js +303 -0
- package/dist/index.js +28 -0
- package/dist/runtime/bootstrap.js +190 -0
- package/dist/runtime/cli.js +4162 -488
- package/dist/runtime/commands/agents.js +30 -30
- package/dist/runtime/commands/budget.js +5 -5
- package/dist/runtime/commands/cancel.js +231 -0
- package/dist/runtime/commands/chain.js +489 -0
- package/dist/runtime/commands/codegraph-status.js +227 -0
- package/dist/runtime/commands/compact.js +297 -0
- package/dist/runtime/commands/config.js +32 -32
- package/dist/runtime/commands/cost.js +199 -0
- package/dist/runtime/commands/delegate.js +244 -13
- package/dist/runtime/commands/dispatch.js +126 -0
- package/dist/runtime/commands/doctor.js +579 -0
- package/dist/runtime/commands/feedback.js +184 -0
- package/dist/runtime/commands/hooks.js +184 -0
- package/dist/runtime/commands/init.js +254 -0
- package/dist/runtime/commands/lsp.js +368 -0
- package/dist/runtime/commands/mcp.js +879 -0
- package/dist/runtime/commands/memory.js +582 -0
- package/dist/runtime/commands/model.js +237 -0
- package/dist/runtime/commands/onboarding.js +275 -0
- package/dist/runtime/commands/patch.js +128 -0
- package/dist/runtime/commands/permissions.js +112 -0
- package/dist/runtime/commands/plan.js +143 -0
- package/dist/runtime/commands/prd-check.js +285 -0
- package/dist/runtime/commands/privacy.js +17 -17
- package/dist/runtime/commands/recipe.js +325 -0
- package/dist/runtime/commands/redo-blob-store.js +92 -0
- package/dist/runtime/commands/redo.js +361 -0
- package/dist/runtime/commands/release-notes.js +229 -0
- package/dist/runtime/commands/repo-map.js +95 -0
- package/dist/runtime/commands/report.js +299 -0
- package/dist/runtime/commands/resume.js +118 -0
- package/dist/runtime/commands/review-consensus.js +68 -53
- package/dist/runtime/commands/rewind.js +333 -0
- package/dist/runtime/commands/roster.js +14 -14
- package/dist/runtime/commands/sessions.js +163 -0
- package/dist/runtime/commands/share.js +316 -0
- package/dist/runtime/commands/skills.js +31 -31
- package/dist/runtime/commands/status.js +186 -0
- package/dist/runtime/commands/stickers.js +82 -0
- package/dist/runtime/commands/style.js +194 -0
- package/dist/runtime/commands/theme.js +196 -0
- package/dist/runtime/commands/undo.js +54 -22
- package/dist/runtime/commands/update.js +289 -0
- package/dist/runtime/commands/vim.js +140 -0
- package/dist/runtime/commands/worktree.js +177 -0
- package/dist/runtime/commands/worktrees.js +155 -0
- package/dist/runtime/headless-repl.js +195 -0
- package/dist/runtime/headless.js +543 -0
- package/dist/runtime/load-hooks-or-exit.js +71 -0
- package/dist/runtime/plan-decompose.js +531 -0
- package/dist/runtime/update-check.js +28 -28
- package/dist/runtime/version.js +65 -0
- package/dist/skills/bundled/batch.js +617 -0
- package/dist/skills/bundled/index.js +45 -0
- package/dist/skills/bundled/loop.js +358 -0
- package/dist/skills/bundled/remember.js +383 -0
- package/dist/skills/bundled/simplify.js +289 -0
- package/dist/skills/bundled/skillify.js +373 -0
- package/dist/skills/bundled/stuck.js +558 -0
- package/dist/skills/bundled/verify.js +439 -0
- package/dist/testing/vcr.js +486 -0
- package/dist/tools/agent-tool.js +229 -0
- package/dist/tools/apply-patch.js +556 -0
- package/dist/tools/ask-user-question.js +222 -0
- package/dist/tools/ask-user.js +115 -0
- package/dist/tools/bash.js +623 -45
- package/dist/tools/brief.js +224 -0
- package/dist/tools/enter-worktree.js +250 -0
- package/dist/tools/exit-worktree.js +147 -0
- package/dist/tools/file-tools.js +161 -44
- package/dist/tools/lsp-tools.js +189 -0
- package/dist/tools/mcp-tool.js +260 -0
- package/dist/tools/multi-edit.js +361 -0
- package/dist/tools/powershell.js +268 -0
- package/dist/tools/registry.js +85 -0
- package/dist/tools/skill-tool.js +96 -0
- package/dist/tools/sleep.js +99 -0
- package/dist/tools/synthetic-output.js +133 -0
- package/dist/tools/tasks.js +208 -0
- package/dist/tools/todo-write.js +184 -0
- package/dist/tools/verify-plan-execution.js +295 -0
- package/dist/tools/web-fetch-injection-scanner.js +207 -0
- package/dist/tools/web-fetch.js +195 -10
- package/dist/tools/web-search.js +458 -0
- package/dist/tui/agent-progress-card.js +111 -0
- package/dist/tui/agent-tree.js +11 -1
- package/dist/tui/ask-modal.js +14 -14
- package/dist/tui/ask-user-question-prompt.js +203 -0
- package/dist/tui/compact-banner.js +81 -0
- package/dist/tui/conversation-pane.js +85 -11
- package/dist/tui/cost-table.js +111 -0
- package/dist/tui/device-flow.js +2 -2
- package/dist/tui/doctor-table.js +46 -0
- package/dist/tui/feedback-prompt.js +156 -0
- package/dist/tui/input-box.js +247 -32
- package/dist/tui/login-picker.js +3 -3
- package/dist/tui/markdown-render.js +6 -6
- package/dist/tui/onboarding-wizard.js +240 -0
- package/dist/tui/permissions-picker.js +86 -0
- package/dist/tui/render.js +35 -0
- package/dist/tui/repl-render.js +332 -54
- package/dist/tui/repl-splash-art.js +16 -16
- package/dist/tui/repl-splash-mascot.js +48 -24
- package/dist/tui/repl-splash.js +22 -22
- package/dist/tui/repl.js +124 -44
- package/dist/tui/slash-palette.js +6 -6
- package/dist/tui/splash.js +2 -2
- package/dist/tui/status-bar.js +109 -31
- package/dist/tui/status-table.js +7 -0
- package/dist/tui/stickers-art.js +136 -0
- package/dist/tui/style-table.js +28 -0
- package/dist/tui/theme-table.js +29 -0
- package/dist/tui/thinking-spinner.js +123 -0
- package/dist/tui/tool-stream-pane.js +53 -4
- package/dist/tui/update-banner.js +27 -2
- package/dist/tui/vim-input.js +267 -0
- package/dist/tui/welcome-banner.js +107 -0
- package/dist/tui/welcome-data.js +293 -0
- package/dist/tui/workspace-context.js +2 -2
- package/docs/examples/codegraph.mcp.json +10 -0
- package/package.json +23 -6
- package/test/scenarios/codegen-create-file.scenario.txt +13 -0
- package/test/scenarios/compact-force.scenario.txt +11 -0
- package/test/scenarios/identity.scenario.txt +11 -0
- package/test/scenarios/persona-handoff.scenario.txt +11 -0
- package/test/scenarios/walkback.scenario.txt +12 -0
- package/dist/core/engine/compaction-hook.js +0 -154
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/** Hard cap on the rendered `<context>` block, bytes. */
|
|
2
|
+
export const CONTEXT_PREFIX_MAX_BYTES = 5 * 1024;
|
|
3
|
+
/** Hard cap on working-set entries surfaced in the prefix. */
|
|
4
|
+
export const CONTEXT_PREFIX_MAX_WORKING_SET = 50;
|
|
5
|
+
/** Hard cap on per-dir markdown files surfaced inline. */
|
|
6
|
+
export const CONTEXT_PREFIX_MAX_MARKDOWN = 3;
|
|
7
|
+
/** Per-markdown-file inline excerpt cap, bytes. Keeps any one file from dominating. */
|
|
8
|
+
export const CONTEXT_PREFIX_MAX_PER_MARKDOWN_BYTES = 1024;
|
|
9
|
+
/**
|
|
10
|
+
* Build the `<context>` block. Always returns a result — when there
|
|
11
|
+
* is nothing to surface (no cwd hint, no working set, no per-dir
|
|
12
|
+
* files), returns `{ block: '', bytes: 0, truncated: false, counts: ... }`
|
|
13
|
+
* so the caller can short-circuit the splice cleanly.
|
|
14
|
+
*
|
|
15
|
+
* Determinism: same input always produces byte-identical output. The
|
|
16
|
+
* working-set order comes from the caller's summary; we preserve it
|
|
17
|
+
* verbatim. Per-dir files are sorted by `distanceFromCwd` ascending
|
|
18
|
+
* (closest first) so two equal `TraversedMarkdown` arrays produce
|
|
19
|
+
* identical blocks.
|
|
20
|
+
*/
|
|
21
|
+
export function buildContextPrefix(input) {
|
|
22
|
+
const lines = [];
|
|
23
|
+
let bytes = 0;
|
|
24
|
+
let truncated = false;
|
|
25
|
+
// Walk a write-then-measure loop so we never overflow the byte cap.
|
|
26
|
+
// Each push checks budget; when full, set `truncated = true` and
|
|
27
|
+
// stop. The opener / closer tags always fit (small) — we reserve
|
|
28
|
+
// their byte cost up-front from the budget.
|
|
29
|
+
const opener = '<context>';
|
|
30
|
+
const closer = '</context>';
|
|
31
|
+
const reservedTagBytes = Buffer.byteLength(opener, 'utf8') + 1 + Buffer.byteLength(closer, 'utf8') + 1;
|
|
32
|
+
let budget = CONTEXT_PREFIX_MAX_BYTES - reservedTagBytes;
|
|
33
|
+
if (budget < 0) {
|
|
34
|
+
return {
|
|
35
|
+
block: '',
|
|
36
|
+
bytes: 0,
|
|
37
|
+
truncated: false,
|
|
38
|
+
counts: {
|
|
39
|
+
workingSetIncluded: 0,
|
|
40
|
+
workingSetTotal: input.workingSet?.length ?? 0,
|
|
41
|
+
markdownIncluded: 0,
|
|
42
|
+
markdownTotal: input.traversedMarkdown?.length ?? 0,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const pushLine = (line) => {
|
|
47
|
+
const lineBytes = Buffer.byteLength(line, 'utf8') + 1; // newline
|
|
48
|
+
if (lineBytes > budget) {
|
|
49
|
+
truncated = true;
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
lines.push(line);
|
|
53
|
+
budget -= lineBytes;
|
|
54
|
+
bytes += lineBytes;
|
|
55
|
+
return true;
|
|
56
|
+
};
|
|
57
|
+
// cwd — always cheap, always first.
|
|
58
|
+
pushLine(`cwd: ${input.cwdRelative || '.'}`);
|
|
59
|
+
// intent hint, if provided.
|
|
60
|
+
if (input.intentHint) {
|
|
61
|
+
pushLine(`intent: ${input.intentHint}`);
|
|
62
|
+
}
|
|
63
|
+
// Working set — render `open-files:` only when there is at least one entry.
|
|
64
|
+
const wsTotal = input.workingSet?.length ?? 0;
|
|
65
|
+
let wsIncluded = 0;
|
|
66
|
+
if (wsTotal > 0 && input.workingSet) {
|
|
67
|
+
const wsCap = Math.min(CONTEXT_PREFIX_MAX_WORKING_SET, wsTotal);
|
|
68
|
+
pushLine('open-files:');
|
|
69
|
+
for (let i = 0; i < wsCap; i += 1) {
|
|
70
|
+
const entry = input.workingSet[i];
|
|
71
|
+
if (!entry)
|
|
72
|
+
continue;
|
|
73
|
+
const ok = pushLine(` - ${entry.absPath}`);
|
|
74
|
+
if (!ok)
|
|
75
|
+
break;
|
|
76
|
+
wsIncluded += 1;
|
|
77
|
+
}
|
|
78
|
+
if (wsTotal > wsCap) {
|
|
79
|
+
pushLine(` ... (+${wsTotal - wsCap} more)`);
|
|
80
|
+
truncated = truncated || true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Per-dir markdown — closest-first, max 3, each capped to 1 KB excerpt.
|
|
84
|
+
const mdTotal = input.traversedMarkdown?.length ?? 0;
|
|
85
|
+
let mdIncluded = 0;
|
|
86
|
+
if (mdTotal > 0 && input.traversedMarkdown) {
|
|
87
|
+
const sorted = [...input.traversedMarkdown].sort((a, b) => a.distanceFromCwd - b.distanceFromCwd);
|
|
88
|
+
const top = sorted.slice(0, CONTEXT_PREFIX_MAX_MARKDOWN);
|
|
89
|
+
if (top.length > 0) {
|
|
90
|
+
pushLine('per-dir-conventions:');
|
|
91
|
+
for (const md of top) {
|
|
92
|
+
// Header line names the source file for traceability.
|
|
93
|
+
const header = ` [${md.resolvedPath}]`;
|
|
94
|
+
if (!pushLine(header))
|
|
95
|
+
break;
|
|
96
|
+
// Excerpt: cap to 1 KB, single-line collapse (replace newlines
|
|
97
|
+
// with " | " so the YAML-ish block stays parseable by humans).
|
|
98
|
+
const excerpt = excerpt1KB(md.content);
|
|
99
|
+
// Indent two spaces so it nests under the file header.
|
|
100
|
+
const indented = excerpt.split('\n').map((l) => ` ${l}`).join('\n');
|
|
101
|
+
if (!pushLine(indented))
|
|
102
|
+
break;
|
|
103
|
+
mdIncluded += 1;
|
|
104
|
+
}
|
|
105
|
+
if (mdTotal > top.length) {
|
|
106
|
+
pushLine(` ... (+${mdTotal - top.length} more files; closest-3 shown)`);
|
|
107
|
+
truncated = truncated || true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (lines.length === 0) {
|
|
112
|
+
return {
|
|
113
|
+
block: '',
|
|
114
|
+
bytes: 0,
|
|
115
|
+
truncated: false,
|
|
116
|
+
counts: {
|
|
117
|
+
workingSetIncluded: 0,
|
|
118
|
+
workingSetTotal: wsTotal,
|
|
119
|
+
markdownIncluded: 0,
|
|
120
|
+
markdownTotal: mdTotal,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const block = [opener, ...lines, closer].join('\n');
|
|
125
|
+
const totalBytes = Buffer.byteLength(block, 'utf8');
|
|
126
|
+
return {
|
|
127
|
+
block,
|
|
128
|
+
bytes: totalBytes,
|
|
129
|
+
truncated,
|
|
130
|
+
counts: {
|
|
131
|
+
workingSetIncluded: wsIncluded,
|
|
132
|
+
workingSetTotal: wsTotal,
|
|
133
|
+
markdownIncluded: mdIncluded,
|
|
134
|
+
markdownTotal: mdTotal,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Splice a built context block onto the front of a user message.
|
|
140
|
+
* Empty block → message returned verbatim (no leading blank line, no
|
|
141
|
+
* empty `<context>` tag).
|
|
142
|
+
*/
|
|
143
|
+
export function spliceContextPrefix(block, userMessage) {
|
|
144
|
+
if (block.length === 0)
|
|
145
|
+
return userMessage;
|
|
146
|
+
return `${block}\n\n${userMessage}`;
|
|
147
|
+
}
|
|
148
|
+
function excerpt1KB(content) {
|
|
149
|
+
const capped = content.length <= CONTEXT_PREFIX_MAX_PER_MARKDOWN_BYTES
|
|
150
|
+
? content
|
|
151
|
+
: content.slice(0, CONTEXT_PREFIX_MAX_PER_MARKDOWN_BYTES);
|
|
152
|
+
// Trim trailing newlines so the YAML-ish render stays tight.
|
|
153
|
+
return capped.replace(/\s+$/g, '');
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=context-prefix.js.map
|
|
@@ -5,7 +5,7 @@ export * from './noop.js';
|
|
|
5
5
|
export * from './prompts.js';
|
|
6
6
|
export * from './tool-bridge.js';
|
|
7
7
|
// Subagent dispatch helper. Engine adapter code calls this from a
|
|
8
|
-
// future `task_dispatch` tool
|
|
8
|
+
// future `task_dispatch` tool ; the re-export keeps the
|
|
9
9
|
// import path stable so adapter code does not have to reach into the
|
|
10
10
|
// subagents submodule.
|
|
11
11
|
export { spawnSubagent } from '../subagents/spawn.js';
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intensity dial — single knob that derives max_turns, token budget,
|
|
3
|
+
* model tag, and parallel-agent posture for `pugi code/fix/build/plan/explain`.
|
|
4
|
+
*
|
|
5
|
+
* Spec: Pugi backlog. Pattern lifted from Karpathy's nanochat
|
|
6
|
+
* `--depth` flag — one dial that captures "how much do you want me to
|
|
7
|
+
* spend on this turn?" so operators do not have to tune four separate
|
|
8
|
+
* knobs in lockstep.
|
|
9
|
+
*
|
|
10
|
+
* Four discrete levels were chosen over a free-form integer:
|
|
11
|
+
*
|
|
12
|
+
* - operators have a mental model for "quick / standard / deep / marathon"
|
|
13
|
+
* - discrete tiers let us tune model + parallel-agent posture per tier
|
|
14
|
+
* without exposing a continuous knob that has no meaning above 200 turns
|
|
15
|
+
* - keeps env override (`PUGI_INTENSITY`) trivially parseable
|
|
16
|
+
*
|
|
17
|
+
* Defaults are conservative — the operator can always override any
|
|
18
|
+
* individual knob via the per-command flag (`--max-turns`, `--max-tokens`,
|
|
19
|
+
* `PUGI_ENGINE_MODEL_<COMMAND>`) and the explicit flag wins.
|
|
20
|
+
*
|
|
21
|
+
* | level | max_turns | budget tokens | model | parallel agents |
|
|
22
|
+
* |----------|-----------|---------------|----------|------------------|
|
|
23
|
+
* | quick | 3 | 30 000 | haiku | off |
|
|
24
|
+
* | standard | 15 | 80 000 | sonnet | off |
|
|
25
|
+
* | deep | 50 | 200 000 | sonnet | up to 3 |
|
|
26
|
+
* | marathon | 200 | 800 000 | opus | up to 3 |
|
|
27
|
+
*
|
|
28
|
+
* Model tags are abstract (`light`/`standard`/`heavy`). The CLI maps
|
|
29
|
+
* each tag to a concrete model slug via env override
|
|
30
|
+
* (`PUGI_INTENSITY_MODEL_<TAG>`) OR falls back to the per-persona default
|
|
31
|
+
* resolved by the admin-api runtime. We do NOT hard-code Anthropic /
|
|
32
|
+
* OpenAI slugs here — those move per provider release and would couple
|
|
33
|
+
* the CLI to the runtime's routing table. Operators pin a concrete model
|
|
34
|
+
* via `PUGI_ENGINE_MODEL_<COMMAND>` when they need exact control.
|
|
35
|
+
*/
|
|
36
|
+
export const INTENSITY_LEVELS = [
|
|
37
|
+
'quick',
|
|
38
|
+
'standard',
|
|
39
|
+
'deep',
|
|
40
|
+
'marathon',
|
|
41
|
+
];
|
|
42
|
+
export const DEFAULT_INTENSITY = 'standard';
|
|
43
|
+
const PROFILES = {
|
|
44
|
+
quick: {
|
|
45
|
+
level: 'quick',
|
|
46
|
+
maxTurns: 3,
|
|
47
|
+
budgetTokens: 30_000,
|
|
48
|
+
modelTag: 'light',
|
|
49
|
+
allowParallelAgents: false,
|
|
50
|
+
maxParallelAgents: 0,
|
|
51
|
+
},
|
|
52
|
+
standard: {
|
|
53
|
+
level: 'standard',
|
|
54
|
+
maxTurns: 15,
|
|
55
|
+
budgetTokens: 80_000,
|
|
56
|
+
modelTag: 'standard',
|
|
57
|
+
allowParallelAgents: false,
|
|
58
|
+
maxParallelAgents: 0,
|
|
59
|
+
},
|
|
60
|
+
deep: {
|
|
61
|
+
level: 'deep',
|
|
62
|
+
maxTurns: 50,
|
|
63
|
+
budgetTokens: 200_000,
|
|
64
|
+
modelTag: 'standard',
|
|
65
|
+
allowParallelAgents: true,
|
|
66
|
+
maxParallelAgents: 3,
|
|
67
|
+
},
|
|
68
|
+
marathon: {
|
|
69
|
+
level: 'marathon',
|
|
70
|
+
maxTurns: 200,
|
|
71
|
+
budgetTokens: 800_000,
|
|
72
|
+
modelTag: 'heavy',
|
|
73
|
+
allowParallelAgents: true,
|
|
74
|
+
maxParallelAgents: 3,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Look up the profile for a level. Pure — returns the same frozen
|
|
79
|
+
* object on every call so callers can reference-compare cheaply.
|
|
80
|
+
*/
|
|
81
|
+
export function profileFor(level) {
|
|
82
|
+
return PROFILES[level];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Resolve the effective per-task `maxTurns` cap.
|
|
86
|
+
*
|
|
87
|
+
* Precedence (triple-review P1 follow-up):
|
|
88
|
+
* 1. explicit `--max-turns=N` flag wins outright
|
|
89
|
+
* 2. otherwise the intensity profile's per-tier preset wins
|
|
90
|
+
*
|
|
91
|
+
* The function is pure so the CLI seam can call it inline and the test
|
|
92
|
+
* corpus can pin the precedence without spinning up a full adapter.
|
|
93
|
+
* Returning the resolved primitive instead of an object keeps the
|
|
94
|
+
* call site readable: `const turns = resolveMaxTurns(flags.maxTurns,
|
|
95
|
+
* profile);`. The CLI threads the result into `EngineTask.budget.turns`
|
|
96
|
+
* which the native-pugi adapter forwards to `EngineBudget.maxTurns` in
|
|
97
|
+
* the SDK loop.
|
|
98
|
+
*/
|
|
99
|
+
export function resolveMaxTurns(flagMaxTurns, profile) {
|
|
100
|
+
return flagMaxTurns ?? profile.maxTurns;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Type guard for the four-level enum. Rejects everything else,
|
|
104
|
+
* including capitalised variants — operators surface the typo at parse
|
|
105
|
+
* time rather than seeing the dial silently degrade to`standard`.
|
|
106
|
+
*/
|
|
107
|
+
export function isIntensityLevel(value) {
|
|
108
|
+
return (typeof value === 'string' &&
|
|
109
|
+
INTENSITY_LEVELS.includes(value));
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Resolve the effective intensity level from CLI input + env fallback.
|
|
113
|
+
*
|
|
114
|
+
* 1. CLI `--intensity <level>` wins when supplied.
|
|
115
|
+
* 2. `PUGI_INTENSITY` env wins next when `envFallback` (default true).
|
|
116
|
+
* 3. `DEFAULT_INTENSITY` ('standard') is the final fallback.
|
|
117
|
+
*
|
|
118
|
+
* Explicit-flag typos throw — an operator who typed `--intensity marathn`
|
|
119
|
+
* gets an immediate, deterministic error rather than silently dropping
|
|
120
|
+
* to `standard`. This matches the rule for every other named CLI flag
|
|
121
|
+
* (single source of truth lives in `parseArgs`).
|
|
122
|
+
*
|
|
123
|
+
* Env-var typos WARN and fall back — `PUGI_INTENSITY` is shell state that
|
|
124
|
+
* can persist across many sessions (rc files, container env, CI matrix).
|
|
125
|
+
* A typo there used to throw on every invocation and prevent any `pugi`
|
|
126
|
+
* command from running at all; the warn-not-throw path keeps the CLI
|
|
127
|
+
* usable and surfaces the bad value once on stderr so the operator can
|
|
128
|
+
* fix their environment. Triple-review P1 follow-up .
|
|
129
|
+
*
|
|
130
|
+
* The `warn` hook is injectable so unit tests can capture the message
|
|
131
|
+
* without depending on `process.stderr`. Production callers pass nothing
|
|
132
|
+
* and the helper falls back to `process.stderr.write`.
|
|
133
|
+
*/
|
|
134
|
+
export function resolveIntensity(opts) {
|
|
135
|
+
if (opts.intensity !== undefined && opts.intensity !== '') {
|
|
136
|
+
if (!isIntensityLevel(opts.intensity)) {
|
|
137
|
+
throw new Error(`unknown --intensity "${opts.intensity}"; expected one of ${INTENSITY_LEVELS.join(', ')}`);
|
|
138
|
+
}
|
|
139
|
+
return opts.intensity;
|
|
140
|
+
}
|
|
141
|
+
const envFallback = opts.envFallback !== false;
|
|
142
|
+
if (envFallback) {
|
|
143
|
+
const fromEnv = process.env['PUGI_INTENSITY'];
|
|
144
|
+
if (fromEnv !== undefined && fromEnv !== '') {
|
|
145
|
+
if (!isIntensityLevel(fromEnv)) {
|
|
146
|
+
const message = `pugi: PUGI_INTENSITY="${fromEnv}" is not one of ${INTENSITY_LEVELS.join(', ')}; ignoring and using default (${DEFAULT_INTENSITY}).\n`;
|
|
147
|
+
const writer = opts.warn ?? ((m) => {
|
|
148
|
+
process.stderr.write(m);
|
|
149
|
+
});
|
|
150
|
+
writer(message);
|
|
151
|
+
return DEFAULT_INTENSITY;
|
|
152
|
+
}
|
|
153
|
+
return fromEnv;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return DEFAULT_INTENSITY;
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=intensity.js.map
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* β5a P1+P6 — CLI-side intent classifier: definitional vs operational.
|
|
3
|
+
*
|
|
4
|
+
* Background (CEO quality gate ≥80% vs the upstream tool): the
|
|
5
|
+
* .X Phase 2 comparative eval surfaced a high-frequency Pugi loss
|
|
6
|
+
* mode where the model interpreted EVERY user message as an operational
|
|
7
|
+
* task and reached for tools. Concrete reproducer: when an operator
|
|
8
|
+
* asks "What is grep?", pre-β5a Pugi would issue a `bash` tool call
|
|
9
|
+
* (`man grep`, `grep --help`, sometimes a `glob` over `**`) instead
|
|
10
|
+
* of answering explanatorily. the upstream tool answers in one paragraph,
|
|
11
|
+
* no tool calls. That single failure mode costs ~3 of the 23
|
|
12
|
+
* comparative fixtures.
|
|
13
|
+
*
|
|
14
|
+
* Root cause: the system prompt advertises tools as the primary way
|
|
15
|
+
* to make progress; without a counter-instruction, the model defaults
|
|
16
|
+
* to tools even for pure-knowledge questions. Prompt v2 (P6) adds
|
|
17
|
+
* definitional EXAMPLES in the prompt; this classifier is the
|
|
18
|
+
* deterministic safety net that flags definitional questions BEFORE
|
|
19
|
+
* the engine loop fires so we can:
|
|
20
|
+
*
|
|
21
|
+
* 1. Inject a one-line `<intent>definitional</intent>` marker into
|
|
22
|
+
* the user message that the prompt explicitly teaches the model
|
|
23
|
+
* to honour ("when you see this marker, answer in prose, do not
|
|
24
|
+
* call tools").
|
|
25
|
+
* 2. Make the marker visible in the SSE stream so cabinet UI / eval
|
|
26
|
+
* reports can show why a tool-free answer was correct.
|
|
27
|
+
*
|
|
28
|
+
* The classifier is intentionally simple — pure regex/keyword scoring,
|
|
29
|
+
* sub-millisecond, no LLM call, no fs reads. It must be:
|
|
30
|
+
*
|
|
31
|
+
* - Deterministic (same input always → same output, the eval relies
|
|
32
|
+
* on this for stable comparison runs).
|
|
33
|
+
* - Conservative on the operational side. False-positive
|
|
34
|
+
* definitional is mostly harmless (the model can still call tools
|
|
35
|
+
* if needed — the marker is a hint, not a hard gate). False-
|
|
36
|
+
* positive operational on a definitional question is what we are
|
|
37
|
+
* fixing — biggest loss surface.
|
|
38
|
+
*
|
|
39
|
+
* Coverage: EN + RU (the two primary CLI languages per
|
|
40
|
+
* `feedback_chat_always_russian_no_drift.md`). Uses lowercase compare
|
|
41
|
+
* for keyword matches, anchored regex for prefix patterns. Stop-word
|
|
42
|
+
* agnostic — the question "what is X?" is the dominant template and
|
|
43
|
+
* survives every common stop-word filter.
|
|
44
|
+
*
|
|
45
|
+
* Out of scope: deciding WHICH tool to call when operational. That
|
|
46
|
+
* is the model's job. We only decide whether to suggest "no tools
|
|
47
|
+
* needed" up front.
|
|
48
|
+
*/
|
|
49
|
+
/**
|
|
50
|
+
* EN + RU definitional question openers. Anchored at start-of-string
|
|
51
|
+
* (after trim + lower) so a sentence-medial "what is" inside a larger
|
|
52
|
+
* operational sentence does not flip the verdict. Each entry is
|
|
53
|
+
* either a literal prefix or a RegExp matched against the lowercase
|
|
54
|
+
* trimmed input.
|
|
55
|
+
*
|
|
56
|
+
* Boundary policy: JS `\b` is ASCII-only — Cyrillic letters do not
|
|
57
|
+
* count as word characters, so `^объясни\b` matches "объясни" against
|
|
58
|
+
* a space follower BUT fails when the next char is a Cyrillic letter
|
|
59
|
+
* (which it never is at this position) and surprisingly fails on
|
|
60
|
+
* "объясни X" because `\b` between `и` (non-word in ASCII regex) and
|
|
61
|
+
* ` ` (non-word) is never a boundary. We use an explicit
|
|
62
|
+
* `(?=\s|$|[!?.,])` lookahead so Cyrillic prefixes match the same
|
|
63
|
+
* way ASCII ones do.
|
|
64
|
+
*/
|
|
65
|
+
const WB = '(?=\\s|$|[!?.,;:])';
|
|
66
|
+
const DEFINITIONAL_PATTERNS = [
|
|
67
|
+
// EN
|
|
68
|
+
new RegExp(`^what\\s+(is|are|does|do)${WB}`),
|
|
69
|
+
new RegExp(`^who\\s+(is|are|was|were)${WB}`),
|
|
70
|
+
new RegExp(`^when\\s+(is|was|does|did)${WB}`),
|
|
71
|
+
new RegExp(`^where\\s+(is|are|does|did)${WB}`),
|
|
72
|
+
new RegExp(`^why\\s+(is|are|does|did)${WB}`),
|
|
73
|
+
new RegExp(`^how\\s+(does|do|did)\\s+\\S+\\s+work${WB}`),
|
|
74
|
+
new RegExp(`^explain\\s+(what|why|how)${WB}`),
|
|
75
|
+
new RegExp(`^define${WB}`),
|
|
76
|
+
new RegExp(`^tell\\s+me\\s+(what|about)${WB}`),
|
|
77
|
+
new RegExp(`^can\\s+you\\s+(tell\\s+me|explain|describe)${WB}`),
|
|
78
|
+
// RU
|
|
79
|
+
new RegExp(`^что\\s+(такое|это|значит)${WB}`),
|
|
80
|
+
new RegExp(`^кто\\s+(такой|такая|такие|это)${WB}`),
|
|
81
|
+
new RegExp(`^как\\s+работает${WB}`),
|
|
82
|
+
new RegExp(`^зачем${WB}`),
|
|
83
|
+
new RegExp(`^почему${WB}`),
|
|
84
|
+
new RegExp(`^объясни${WB}`),
|
|
85
|
+
new RegExp(`^расскажи\\s+(про|о|об)${WB}`),
|
|
86
|
+
];
|
|
87
|
+
/**
|
|
88
|
+
* Operational verbs that strongly imply a tool-use task. A match
|
|
89
|
+
* here overrides a weak definitional signal (e.g. "explain the bug
|
|
90
|
+
* and fix it" -> operational, not definitional).
|
|
91
|
+
*/
|
|
92
|
+
const OPERATIONAL_KEYWORDS = [
|
|
93
|
+
// EN
|
|
94
|
+
'fix', 'patch', 'add', 'remove', 'delete', 'create', 'build',
|
|
95
|
+
'implement', 'refactor', 'rename', 'migrate', 'deploy', 'install',
|
|
96
|
+
'run', 'test', 'debug', 'investigate', 'open a pr', 'commit',
|
|
97
|
+
'push', 'merge', 'review my', 'write a', 'write the', 'replace',
|
|
98
|
+
'update the', 'modify', 'edit', 'change the',
|
|
99
|
+
// RU
|
|
100
|
+
'почини', 'исправь', 'добавь', 'удали', 'создай', 'собери',
|
|
101
|
+
'реализуй', 'отрефактори', 'переименуй', 'мигрируй', 'задеплой',
|
|
102
|
+
'установи', 'запусти', 'протестируй', 'отладь', 'разберись',
|
|
103
|
+
'закоммить', 'смержи', 'напиши', 'замени', 'обнови', 'измени',
|
|
104
|
+
'отредактируй',
|
|
105
|
+
];
|
|
106
|
+
/**
|
|
107
|
+
* File-path / identifier signals that, when combined with any verb,
|
|
108
|
+
* push us toward operational regardless of question framing. A bare
|
|
109
|
+
* "what is" question that names a file (`what is in apps/admin-api/
|
|
110
|
+
* src/main.ts?`) reads as operational — the operator wants the file
|
|
111
|
+
* inspected, not a dictionary definition.
|
|
112
|
+
*/
|
|
113
|
+
const PATH_SIGNAL = /[./][a-z0-9_-]+\.(ts|tsx|js|jsx|json|md|yaml|yml|sh|py|prisma|css|scss|html|sql|env)\b/i;
|
|
114
|
+
const CODE_SYMBOL_SIGNAL = /\b[A-Z][a-zA-Z0-9]+\.(prototype|find|create|run|build|register)\b/;
|
|
115
|
+
/**
|
|
116
|
+
* Classify a single user message. Returns one of:
|
|
117
|
+
*
|
|
118
|
+
* - `definitional` — high confidence the operator wants knowledge,
|
|
119
|
+
* not edits. Prompt the model to answer in prose, no tools.
|
|
120
|
+
* - `operational` — verbs / paths / explicit "fix X" framing.
|
|
121
|
+
* Model decides tools as normal.
|
|
122
|
+
* - `ambiguous` — neither side won decisively. The model gets no
|
|
123
|
+
* extra hint; default behaviour applies. The marker is NOT
|
|
124
|
+
* injected for ambiguous to keep the prompt cache stable.
|
|
125
|
+
*
|
|
126
|
+
* Confidence: rough heuristic — `1.0` when a pattern matched cleanly
|
|
127
|
+
* with no opposing signal; lower when both sides matched.
|
|
128
|
+
*
|
|
129
|
+
* Pure function, no side effects.
|
|
130
|
+
*/
|
|
131
|
+
export function classifyIntent(rawMessage) {
|
|
132
|
+
const trimmed = rawMessage.trim();
|
|
133
|
+
if (trimmed.length === 0) {
|
|
134
|
+
return {
|
|
135
|
+
intent: 'ambiguous',
|
|
136
|
+
confidence: 0,
|
|
137
|
+
matched: [],
|
|
138
|
+
rationale: 'empty input',
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const lower = trimmed.toLowerCase();
|
|
142
|
+
const matched = [];
|
|
143
|
+
let definitionalScore = 0;
|
|
144
|
+
let operationalScore = 0;
|
|
145
|
+
let hasDefinitionalOpener = false;
|
|
146
|
+
for (const pattern of DEFINITIONAL_PATTERNS) {
|
|
147
|
+
if (pattern.test(lower)) {
|
|
148
|
+
// Three points — heavier than a single operational keyword
|
|
149
|
+
// match (two) so a definitional opener cannot be flipped by
|
|
150
|
+
// ONE incidental verb appearing as part of a noun phrase
|
|
151
|
+
// (e.g. "what does prisma MIGRATE do" — the verb "migrate"
|
|
152
|
+
// here names a subcommand, not an action the operator wants
|
|
153
|
+
// executed). Operational still wins when multiple operational
|
|
154
|
+
// signals stack (verb + path + imperative).
|
|
155
|
+
definitionalScore += 3;
|
|
156
|
+
matched.push(`def:${pattern.source}`);
|
|
157
|
+
hasDefinitionalOpener = true;
|
|
158
|
+
break; // a single anchored prefix is enough
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// ?-terminated short utterance with no verbs is a strong
|
|
162
|
+
// definitional signal even without a what/who/why opener.
|
|
163
|
+
if (lower.endsWith('?') && lower.split(/\s+/).length <= 6) {
|
|
164
|
+
definitionalScore += 1;
|
|
165
|
+
matched.push('def:short-question');
|
|
166
|
+
}
|
|
167
|
+
// A definitional opener PLUS an explicit `?` terminator together
|
|
168
|
+
// form a pure question. Verbs mentioned inside ("what does prisma
|
|
169
|
+
// MIGRATE do?") are sub-noun-phrase references, not commands —
|
|
170
|
+
// boost definitional so the verb-keyword scoring below cannot tie.
|
|
171
|
+
if (hasDefinitionalOpener && lower.endsWith('?')) {
|
|
172
|
+
definitionalScore += 2;
|
|
173
|
+
matched.push('def:opener+question');
|
|
174
|
+
}
|
|
175
|
+
for (const keyword of OPERATIONAL_KEYWORDS) {
|
|
176
|
+
if (lower.includes(keyword)) {
|
|
177
|
+
operationalScore += 2;
|
|
178
|
+
matched.push(`op:${keyword}`);
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (PATH_SIGNAL.test(trimmed)) {
|
|
183
|
+
operationalScore += 1;
|
|
184
|
+
matched.push('op:path-signal');
|
|
185
|
+
}
|
|
186
|
+
if (CODE_SYMBOL_SIGNAL.test(trimmed)) {
|
|
187
|
+
operationalScore += 1;
|
|
188
|
+
matched.push('op:code-symbol');
|
|
189
|
+
}
|
|
190
|
+
// Imperative ("do X") without any question framing is operational.
|
|
191
|
+
if (!lower.endsWith('?') && /^(please\s+)?(do|run|make|let'?s)\b/.test(lower)) {
|
|
192
|
+
operationalScore += 1;
|
|
193
|
+
matched.push('op:imperative');
|
|
194
|
+
}
|
|
195
|
+
// Decision.
|
|
196
|
+
if (definitionalScore === 0 && operationalScore === 0) {
|
|
197
|
+
return {
|
|
198
|
+
intent: 'ambiguous',
|
|
199
|
+
confidence: 0,
|
|
200
|
+
matched,
|
|
201
|
+
rationale: 'no rules matched',
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
if (definitionalScore > operationalScore + 1) {
|
|
205
|
+
return {
|
|
206
|
+
intent: 'definitional',
|
|
207
|
+
confidence: clamp01(definitionalScore / (definitionalScore + operationalScore + 1)),
|
|
208
|
+
matched,
|
|
209
|
+
rationale: `definitional ${definitionalScore} > operational ${operationalScore}`,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
if (operationalScore >= definitionalScore) {
|
|
213
|
+
return {
|
|
214
|
+
intent: 'operational',
|
|
215
|
+
confidence: clamp01(operationalScore / (definitionalScore + operationalScore + 1)),
|
|
216
|
+
matched,
|
|
217
|
+
rationale: `operational ${operationalScore} >= definitional ${definitionalScore}`,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
// Definitional won but margin is tight — keep but lower confidence.
|
|
221
|
+
return {
|
|
222
|
+
intent: 'definitional',
|
|
223
|
+
confidence: clamp01(definitionalScore / (definitionalScore + operationalScore + 1)) * 0.7,
|
|
224
|
+
matched,
|
|
225
|
+
rationale: `definitional ${definitionalScore} marginal over operational ${operationalScore}`,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Marker the engine wraps the user message in when the classifier
|
|
230
|
+
* picked `definitional`. The system prompt v2 teaches the model to
|
|
231
|
+
* answer in prose when it sees this marker.
|
|
232
|
+
*
|
|
233
|
+
* Format choice: angle-bracket pseudo-XML, matches the existing
|
|
234
|
+
* `<context>` / `<privacy-mode>` markers in the prompt — consistent
|
|
235
|
+
* marker grammar across the codebase keeps the model attentive.
|
|
236
|
+
*/
|
|
237
|
+
export const DEFINITIONAL_MARKER_OPEN = '<intent kind="definitional">';
|
|
238
|
+
export const DEFINITIONAL_MARKER_CLOSE = '</intent>';
|
|
239
|
+
/**
|
|
240
|
+
* Wrap a user message with the intent marker when the classifier
|
|
241
|
+
* fires `definitional`. Returns the original message verbatim for
|
|
242
|
+
* the `operational` and `ambiguous` cases — we never lie to the
|
|
243
|
+
* model about ambiguity, and we never restrict the model's tool
|
|
244
|
+
* use on an unrelated operational task.
|
|
245
|
+
*/
|
|
246
|
+
export function applyIntentMarker(message, intent) {
|
|
247
|
+
if (intent !== 'definitional')
|
|
248
|
+
return message;
|
|
249
|
+
return `${DEFINITIONAL_MARKER_OPEN}\n${message}\n${DEFINITIONAL_MARKER_CLOSE}`;
|
|
250
|
+
}
|
|
251
|
+
function clamp01(value) {
|
|
252
|
+
if (!Number.isFinite(value))
|
|
253
|
+
return 0;
|
|
254
|
+
if (value < 0)
|
|
255
|
+
return 0;
|
|
256
|
+
if (value > 1)
|
|
257
|
+
return 1;
|
|
258
|
+
return value;
|
|
259
|
+
}
|
|
260
|
+
//# sourceMappingURL=intent.js.map
|