@pugi/cli 0.1.0-beta.10 → 0.1.0-beta.101
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +132 -0
- package/LICENSE +1 -1
- package/README.md +55 -11
- package/assets/pugi-prozr2-mascot.ansi +9 -0
- package/bin/run.js +33 -1
- package/dist/commands/deploy.js +40 -40
- package/dist/commands/flatten.js +191 -0
- package/dist/commands/jobs-watch.js +201 -0
- package/dist/commands/jobs.js +42 -27
- package/dist/commands/retro.js +210 -0
- package/dist/commands/smoke.js +133 -0
- package/dist/core/agent-progress/cleanup.js +134 -0
- package/dist/core/agent-progress/schema.js +144 -0
- package/dist/core/agent-progress/writer.js +101 -0
- package/dist/core/agents/adaptive-router.js +330 -0
- package/dist/core/agents/query-decomposer.js +297 -0
- package/dist/core/agents/registry.js +3 -3
- package/dist/core/approvals/shortcut-resolver.js +98 -0
- package/dist/core/artifact-chain/dispatcher.js +148 -0
- package/dist/core/artifact-chain/exporter.js +164 -0
- package/dist/core/artifact-chain/state.js +243 -0
- package/dist/core/artifact-chain/steps.js +169 -0
- package/dist/core/ask-user/question.js +92 -0
- package/dist/core/audit/audit-trail.js +275 -0
- package/dist/core/auth/ensure-authenticated.js +129 -0
- package/dist/core/auth/env-provider.js +238 -0
- package/dist/core/auto-open-browser.js +4 -4
- package/dist/core/auto-update/channels.js +122 -0
- package/dist/core/auto-update/checker.js +241 -0
- package/dist/core/auto-update/state.js +235 -0
- package/dist/core/bare-mode/index.js +107 -0
- package/dist/core/bash/redirect.js +281 -0
- package/dist/core/bash-classifier.js +436 -40
- package/dist/core/checkpoint/resumer.js +149 -0
- package/dist/core/checkpoint/rewinder.js +291 -0
- package/dist/core/checkpoints/shadow-git.js +670 -0
- package/dist/core/citations/parser.js +109 -0
- package/dist/core/classifier/yolo-classifier.js +88 -0
- package/dist/core/codegraph/db.js +506 -0
- package/dist/core/codegraph/decision-store.js +248 -0
- package/dist/core/codegraph/detect-repo.js +459 -0
- package/dist/core/codegraph/install.js +134 -0
- package/dist/core/codegraph/offer-hook.js +220 -0
- package/dist/core/codegraph/parser.js +598 -0
- package/dist/core/codegraph/queries/go.scm +57 -0
- package/dist/core/codegraph/queries/javascript.scm +56 -0
- package/dist/core/codegraph/queries/python.scm +55 -0
- package/dist/core/codegraph/queries/rust.scm +63 -0
- package/dist/core/codegraph/queries/typescript.scm +91 -0
- package/dist/core/codegraph/reindex.js +218 -0
- package/dist/core/codegraph/resolve-edges.js +107 -0
- package/dist/core/codegraph/types.js +34 -0
- package/dist/core/codegraph/watcher.js +440 -0
- package/dist/core/compact/auto-trigger.js +96 -0
- package/dist/core/compact/buffer-rewriter.js +115 -0
- package/dist/core/compact/summarizer.js +208 -0
- package/dist/core/compact/token-counter.js +108 -0
- package/dist/core/consensus/anvil-fanout.js +25 -25
- package/dist/core/consensus/diff-capture.js +121 -12
- package/dist/core/consensus/rubric.js +21 -21
- package/dist/core/context/builder.js +6 -6
- package/dist/core/context/compaction-events.js +8 -8
- package/dist/core/context/compaction.js +31 -31
- package/dist/core/context/index.js +15 -8
- package/dist/core/context/invariants.js +51 -51
- package/dist/core/context/markdown-loader.js +28 -10
- package/dist/core/context/markdown-traverse.js +255 -0
- package/dist/core/context/pugiignore.js +41 -41
- package/dist/core/context/repo-skeleton.js +37 -37
- package/dist/core/context/tool-eviction.js +55 -0
- package/dist/core/context/watcher.js +32 -32
- package/dist/core/context/working-set.js +23 -23
- package/dist/core/coordinator/agent-tools.js +77 -0
- package/dist/core/coordinator/agent-toolset.js +65 -0
- package/dist/core/coordinator/fsm.js +73 -0
- package/dist/core/coordinator/mode-fsm.js +70 -0
- package/dist/core/cost/rate-card.js +129 -0
- package/dist/core/cost/tracker.js +221 -0
- package/dist/core/credentials.js +13 -13
- package/dist/core/cron/scheduler.js +138 -0
- package/dist/core/denial-tracking/index.js +8 -0
- package/dist/core/denial-tracking/state.js +264 -0
- package/dist/core/diagnostics/probe-runner.js +93 -0
- package/dist/core/diagnostics/probes/api.js +46 -0
- package/dist/core/diagnostics/probes/auth.js +93 -0
- package/dist/core/diagnostics/probes/bare-mode.js +42 -0
- package/dist/core/diagnostics/probes/cli-version.js +127 -0
- package/dist/core/diagnostics/probes/config.js +72 -0
- package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
- package/dist/core/diagnostics/probes/disk.js +81 -0
- package/dist/core/diagnostics/probes/engine-live.js +46 -0
- package/dist/core/diagnostics/probes/git.js +65 -0
- package/dist/core/diagnostics/probes/hooks.js +118 -0
- package/dist/core/diagnostics/probes/mcp.js +75 -0
- package/dist/core/diagnostics/probes/node.js +59 -0
- package/dist/core/diagnostics/probes/pnpm.js +36 -0
- package/dist/core/diagnostics/probes/pugi-md.js +89 -0
- package/dist/core/diagnostics/probes/sandbox.js +67 -0
- package/dist/core/diagnostics/probes/session.js +74 -0
- package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
- package/dist/core/diagnostics/probes/workspace.js +63 -0
- package/dist/core/diagnostics/types.js +70 -0
- package/dist/core/dispatch/cache-cleanup.js +197 -0
- package/dist/core/dispatch/cache-handoff.js +295 -0
- package/dist/core/edits/apply-patch-layer-e.js +189 -0
- package/dist/core/edits/dispatch.js +333 -7
- package/dist/core/edits/format-detector.js +260 -0
- package/dist/core/edits/format-matrix.js +26 -0
- package/dist/core/edits/fuzzy-ladder.js +650 -0
- package/dist/core/edits/index.js +5 -1
- package/dist/core/edits/journal.js +199 -0
- package/dist/core/edits/layer-a-apply.js +15 -15
- package/dist/core/edits/layer-a-fuzzy-apply.js +198 -0
- package/dist/core/edits/layer-b-apply.js +9 -9
- package/dist/core/edits/layer-c-apply.js +6 -6
- package/dist/core/edits/layer-d-ast.js +557 -14
- package/dist/core/edits/marker-parser.js +12 -12
- package/dist/core/edits/security-gate.js +27 -27
- package/dist/core/edits/verify-hook.js +273 -0
- package/dist/core/edits/worktree.js +29 -29
- package/dist/core/engine/anvil-client.js +214 -26
- package/dist/core/engine/auto-compact.js +247 -0
- package/dist/core/engine/budgets.js +220 -0
- package/dist/core/engine/compact-llm-summarizer.js +124 -0
- package/dist/core/engine/context-prefix.js +155 -0
- package/dist/core/engine/index.js +1 -1
- package/dist/core/engine/intensity.js +163 -0
- package/dist/core/engine/intent.js +260 -0
- package/dist/core/engine/native-pugi.js +1559 -227
- package/dist/core/engine/prompts.js +219 -19
- package/dist/core/engine/strip-internal-fields.js +124 -0
- package/dist/core/engine/tool-bridge.js +1887 -59
- package/dist/core/engine/verification-patterns.js +195 -0
- package/dist/core/eval/v1/ledger.js +83 -0
- package/dist/core/eval/v1/runner.js +280 -0
- package/dist/core/eval/v1/scoring.js +68 -0
- package/dist/core/eval/v1/task-loader.js +191 -0
- package/dist/core/eval/v1/types.js +14 -0
- package/dist/core/eval/v1/verifier.js +176 -0
- package/dist/core/eval/v1/yaml-parser.js +250 -0
- package/dist/core/evaluation/golden-dataset.js +293 -0
- package/dist/core/feedback/queue.js +177 -0
- package/dist/core/feedback/submitter.js +145 -0
- package/dist/core/file-cache.js +113 -1
- package/dist/core/flatten/flatten-repo.js +439 -0
- package/dist/core/format/osc8-link.js +28 -0
- package/dist/core/hook-chains.js +392 -0
- package/dist/core/hooks/citation-verify-hook.js +138 -0
- package/dist/core/hooks/citation-verify.js +112 -0
- package/dist/core/hooks/events.js +46 -0
- package/dist/core/hooks/index.js +15 -0
- package/dist/core/hooks/registry.js +216 -0
- package/dist/core/hooks/runner.js +236 -0
- package/dist/core/hooks/v2/event-emitter.js +115 -0
- package/dist/core/hooks/v2/executor.js +282 -0
- package/dist/core/hooks/v2/index.js +25 -0
- package/dist/core/hooks/v2/lifecycle.js +104 -0
- package/dist/core/hooks/v2/loader.js +216 -0
- package/dist/core/hooks/v2/matcher.js +125 -0
- package/dist/core/hooks/v2/trust.js +143 -0
- package/dist/core/hooks/v2/types.js +86 -0
- package/dist/core/hooks/worktree-events.js +158 -0
- package/dist/core/image/renderer.js +71 -0
- package/dist/core/init/detector.js +582 -0
- package/dist/core/init/template-renderer.js +242 -0
- package/dist/core/jobs/registry.js +18 -18
- package/dist/core/ledger/results-tsv.js +142 -0
- package/dist/core/log-discipline/stdout-redirect.js +51 -0
- package/dist/core/lsp/cache.js +105 -0
- package/dist/core/lsp/client.js +551 -41
- package/dist/core/lsp/language-detect.js +66 -0
- package/dist/core/lsp/post-edit-diagnostics.js +171 -0
- package/dist/core/lsp/server-detect.js +173 -0
- package/dist/core/lsp/symbol-cache.js +162 -0
- package/dist/core/lsp/symbol-tools.js +664 -0
- package/dist/core/mcp/client.js +97 -28
- package/dist/core/mcp/http-server.js +553 -0
- package/dist/core/mcp/orchestrator-config.js +192 -0
- package/dist/core/mcp/orchestrator-tools.js +806 -0
- package/dist/core/mcp/permission.js +190 -0
- package/dist/core/mcp/registry.js +39 -17
- package/dist/core/mcp/server-tools.js +219 -0
- package/dist/core/mcp/server.js +397 -0
- package/dist/core/mcp/trust.js +10 -10
- package/dist/core/memory/dual-write.js +416 -0
- package/dist/core/memory/passive-extract.js +130 -0
- package/dist/core/memory/phase1-kinds.js +20 -0
- package/dist/core/memory/secret-scanner.js +304 -0
- package/dist/core/memory-sync/queue.js +170 -0
- package/dist/core/metrics/extract.js +113 -0
- package/dist/core/modes/roo-modes.js +68 -0
- package/dist/core/notes/notes-paths.js +113 -0
- package/dist/core/notes/notes-recorder.js +140 -0
- package/dist/core/notes/notes-writer.js +53 -0
- package/dist/core/notes/renderers.js +0 -0
- package/dist/core/notes/slug.js +105 -0
- package/dist/core/onboarding/ensure-initialized.js +133 -0
- package/dist/core/onboarding/marker.js +111 -0
- package/dist/core/onboarding/telemetry-state.js +108 -0
- package/dist/core/output-style/presets.js +176 -0
- package/dist/core/output-style/state.js +185 -0
- package/dist/core/path-security.js +287 -5
- package/dist/core/permission.js +82 -22
- package/dist/core/permissions/auto-classifier.js +124 -0
- package/dist/core/permissions/bash-parser.js +371 -0
- package/dist/core/permissions/circuit-breaker.js +83 -0
- package/dist/core/permissions/constrained-edit.js +91 -0
- package/dist/core/permissions/gate.js +278 -0
- package/dist/core/permissions/index.js +20 -0
- package/dist/core/permissions/mode.js +174 -0
- package/dist/core/permissions/network-egress.js +137 -0
- package/dist/core/permissions/state.js +241 -0
- package/dist/core/permissions/tool-class.js +107 -0
- package/dist/core/plan-mode/ui-state.js +51 -0
- package/dist/core/plans/plan-artifact.js +721 -0
- package/dist/core/policy-limits/etag-store.js +122 -0
- package/dist/core/prd-check/parser.js +215 -0
- package/dist/core/prd-check/reporter.js +127 -0
- package/dist/core/prd-check/session-review.js +557 -0
- package/dist/core/prd-check/verifiers.js +223 -0
- package/dist/core/prompt-cache/client-cache.js +99 -0
- package/dist/core/prompts/assembly.js +29 -0
- package/dist/core/prompts/registry.js +364 -0
- package/dist/core/pugi-gitignore.js +52 -0
- package/dist/core/pugi-md/cc-compat-rules.js +735 -0
- package/dist/core/pugi-md/context-injector.js +76 -0
- package/dist/core/pugi-md/walk-up.js +207 -0
- package/dist/core/python/uv-installer.js +270 -0
- package/dist/core/python/uv-resolver.js +83 -0
- package/dist/core/rate-limit/narrator.js +146 -0
- package/dist/core/recipes/cli-types.js +20 -0
- package/dist/core/recipes/loader.js +103 -0
- package/dist/core/recipes/runner.js +345 -0
- package/dist/core/recipes/schema.js +587 -0
- package/dist/core/release-notes/parser.js +241 -0
- package/dist/core/release-notes/state.js +116 -0
- package/dist/core/repl/ask.js +37 -37
- package/dist/core/repl/cancellation.js +26 -26
- package/dist/core/repl/cap-warning.js +4 -4
- package/dist/core/repl/clipboard-read.js +11 -11
- package/dist/core/repl/dispatch-fsm.js +12 -12
- package/dist/core/repl/engine-bridge.js +303 -0
- package/dist/core/repl/history-search.js +15 -15
- package/dist/core/repl/history.js +28 -18
- package/dist/core/repl/kill-ring.js +5 -5
- package/dist/core/repl/model-pricing.js +135 -0
- package/dist/core/repl/privacy-banner.js +22 -22
- package/dist/core/repl/session.js +2690 -229
- package/dist/core/repl/slash-commands.js +540 -41
- package/dist/core/repl/store/index.js +1 -1
- package/dist/core/repl/store/jsonl-log.js +22 -22
- package/dist/core/repl/store/lockfile.js +10 -10
- package/dist/core/repl/store/session-store.js +136 -107
- package/dist/core/repl/store/types.js +15 -15
- package/dist/core/repl/store/uuid-v7.js +12 -12
- package/dist/core/repl/tool-route.js +382 -0
- package/dist/core/repl/workspace-context.js +43 -21
- package/dist/core/repo-map/build.js +125 -0
- package/dist/core/repo-map/cache.js +185 -0
- package/dist/core/repo-map/extractor.js +254 -0
- package/dist/core/repo-map/formatter.js +145 -0
- package/dist/core/repo-map/page-rank.js +105 -0
- package/dist/core/repo-map/scanner.js +211 -0
- package/dist/core/retro/git-collector.js +251 -0
- package/dist/core/retro/health-card.js +25 -0
- package/dist/core/retro/metrics.js +342 -0
- package/dist/core/retro/narrative.js +249 -0
- package/dist/core/retro/plane-collector.js +274 -0
- package/dist/core/retro/pr-issue-link.js +65 -0
- package/dist/core/retro/types.js +16 -0
- package/dist/core/retry-budget/budget.js +284 -0
- package/dist/core/retry-budget/index.js +5 -0
- package/dist/core/retry-budget/retry-cap.js +74 -0
- package/dist/core/routing/lead-worker.js +43 -0
- package/dist/core/routing/pre-flight-estimator.js +108 -0
- package/dist/core/runs/run-tree.js +103 -0
- package/dist/core/sandboxing/adapter.js +43 -0
- package/dist/core/sandboxing/bubblewrap.js +209 -0
- package/dist/core/sandboxing/index.js +78 -0
- package/dist/core/sandboxing/none.js +19 -0
- package/dist/core/sandboxing/policy.js +97 -0
- package/dist/core/sandboxing/seatbelt.js +231 -0
- package/dist/core/security/injection-scanner.js +367 -0
- package/dist/core/security/output-filter.js +418 -0
- package/dist/core/session/env-file.js +105 -0
- package/dist/core/session/section-budgets.js +140 -0
- package/dist/core/session.js +119 -0
- package/dist/core/settings.js +402 -5
- package/dist/core/share/formatter.js +271 -0
- package/dist/core/share/redactor.js +221 -0
- package/dist/core/share/uploader.js +267 -0
- package/dist/core/skills/defaults.js +30 -30
- package/dist/core/skills/loader.js +22 -22
- package/dist/core/skills/sources.js +27 -27
- package/dist/core/smoke/headless-driver.js +174 -0
- package/dist/core/smoke/orchestrator.js +194 -0
- package/dist/core/smoke/runner.js +238 -0
- package/dist/core/smoke/scenario-parser.js +316 -0
- package/dist/core/statusline.js +99 -0
- package/dist/core/subagents/dispatcher-real.js +600 -0
- package/dist/core/subagents/dispatcher.js +146 -52
- package/dist/core/subagents/index.js +19 -6
- package/dist/core/subagents/isolation-matrix.js +213 -0
- package/dist/core/subagents/spawn.js +19 -4
- package/dist/core/telemetry/emitter.js +229 -0
- package/dist/core/telemetry/queue.js +251 -0
- package/dist/core/theme/context.js +91 -0
- package/dist/core/theme/presets.js +228 -0
- package/dist/core/theme/state.js +181 -0
- package/dist/core/todos/invariant.js +10 -0
- package/dist/core/todos/state.js +177 -0
- package/dist/core/tool-schema/compressor.js +89 -0
- package/dist/core/transport/version-interceptor.js +166 -0
- package/dist/core/trust.js +2 -2
- package/dist/core/tui/thinking-block.js +64 -0
- package/dist/core/vim/keymap.js +288 -0
- package/dist/core/vim/state.js +92 -0
- package/dist/core/watch-markers/marker-watcher.js +133 -0
- package/dist/core/worktree/include-parser.js +249 -0
- package/dist/core/worktree-manager/cleanup.js +123 -0
- package/dist/core/worktree-manager/manager.js +303 -0
- package/dist/index.js +36 -0
- package/dist/runtime/bootstrap.js +190 -0
- package/dist/runtime/cli.js +4403 -561
- package/dist/runtime/commands/agents.js +31 -31
- package/dist/runtime/commands/budget.js +5 -5
- package/dist/runtime/commands/cancel.js +231 -0
- package/dist/runtime/commands/chain.js +489 -0
- package/dist/runtime/commands/codegraph-status.js +227 -0
- package/dist/runtime/commands/compact.js +297 -0
- package/dist/runtime/commands/config.js +74 -40
- package/dist/runtime/commands/cost.js +199 -0
- package/dist/runtime/commands/delegate.js +27 -4
- package/dist/runtime/commands/dispatch.js +126 -0
- package/dist/runtime/commands/doctor.js +579 -0
- package/dist/runtime/commands/eval-v1.js +266 -0
- package/dist/runtime/commands/feedback.js +184 -0
- package/dist/runtime/commands/hooks.js +187 -0
- package/dist/runtime/commands/index-cmd.js +459 -0
- package/dist/runtime/commands/init.js +254 -0
- package/dist/runtime/commands/lsp.js +200 -38
- package/dist/runtime/commands/mcp.js +935 -0
- package/dist/runtime/commands/memory.js +582 -0
- package/dist/runtime/commands/model.js +237 -0
- package/dist/runtime/commands/onboarding.js +275 -0
- package/dist/runtime/commands/patch.js +12 -12
- package/dist/runtime/commands/permissions.js +112 -0
- package/dist/runtime/commands/plan.js +143 -0
- package/dist/runtime/commands/prd-check.js +285 -0
- package/dist/runtime/commands/privacy.js +17 -17
- package/dist/runtime/commands/recipe.js +325 -0
- package/dist/runtime/commands/redo-blob-store.js +92 -0
- package/dist/runtime/commands/redo.js +361 -0
- package/dist/runtime/commands/release-notes.js +229 -0
- package/dist/runtime/commands/repo-map.js +95 -0
- package/dist/runtime/commands/report.js +299 -0
- package/dist/runtime/commands/resume.js +118 -0
- package/dist/runtime/commands/review-consensus.js +68 -53
- package/dist/runtime/commands/rewind.js +333 -0
- package/dist/runtime/commands/roster.js +14 -14
- package/dist/runtime/commands/servers-cli.js +182 -0
- package/dist/runtime/commands/servers.js +236 -0
- package/dist/runtime/commands/sessions.js +163 -0
- package/dist/runtime/commands/share.js +316 -0
- package/dist/runtime/commands/skills.js +31 -31
- package/dist/runtime/commands/status.js +186 -0
- package/dist/runtime/commands/stickers.js +82 -0
- package/dist/runtime/commands/style.js +194 -0
- package/dist/runtime/commands/theme.js +196 -0
- package/dist/runtime/commands/undo.js +54 -22
- package/dist/runtime/commands/update.js +289 -0
- package/dist/runtime/commands/vim.js +140 -0
- package/dist/runtime/commands/worktree.js +8 -8
- package/dist/runtime/commands/worktrees.js +155 -0
- package/dist/runtime/deprecation-warning.js +69 -0
- package/dist/runtime/engine-exit-code.js +50 -0
- package/dist/runtime/headless-repl.js +195 -0
- package/dist/runtime/headless.js +548 -0
- package/dist/runtime/load-hooks-or-exit.js +71 -0
- package/dist/runtime/plan-decompose.js +22 -22
- package/dist/runtime/sigint-guard.js +272 -0
- package/dist/runtime/stream-renderer.js +195 -0
- package/dist/runtime/update-check.js +28 -28
- package/dist/runtime/version.js +65 -0
- package/dist/runtime/worktree-bootstrap.js +579 -0
- package/dist/skills/bundled/batch.js +617 -0
- package/dist/skills/bundled/index.js +45 -0
- package/dist/skills/bundled/loop.js +358 -0
- package/dist/skills/bundled/remember.js +383 -0
- package/dist/skills/bundled/simplify.js +289 -0
- package/dist/skills/bundled/skillify.js +373 -0
- package/dist/skills/bundled/stuck.js +558 -0
- package/dist/skills/bundled/verify.js +439 -0
- package/dist/testing/vcr.js +486 -0
- package/dist/tools/agent-tool.js +229 -0
- package/dist/tools/apply-patch.js +89 -28
- package/dist/tools/ask-user-question.js +337 -0
- package/dist/tools/ask-user.js +115 -0
- package/dist/tools/bash.js +811 -49
- package/dist/tools/brief.js +224 -0
- package/dist/tools/cron.js +433 -0
- package/dist/tools/enter-worktree.js +250 -0
- package/dist/tools/exit-worktree.js +147 -0
- package/dist/tools/file-tools.js +161 -44
- package/dist/tools/http-request.js +336 -0
- package/dist/tools/lsp-tools.js +377 -1
- package/dist/tools/mcp-tool.js +260 -0
- package/dist/tools/multi-edit.js +361 -0
- package/dist/tools/powershell.js +268 -0
- package/dist/tools/registry.js +120 -5
- package/dist/tools/server-tools.js +892 -0
- package/dist/tools/skill-tool.js +96 -0
- package/dist/tools/sleep.js +99 -0
- package/dist/tools/synthetic-output.js +133 -0
- package/dist/tools/tasks.js +208 -0
- package/dist/tools/todo-write.js +184 -0
- package/dist/tools/verify-plan-execution.js +295 -0
- package/dist/tools/web-fetch-injection-scanner.js +207 -0
- package/dist/tools/web-fetch.js +195 -10
- package/dist/tools/web-search.js +458 -0
- package/dist/tui/agent-progress-card.js +111 -0
- package/dist/tui/agent-tree.js +22 -1
- package/dist/tui/ask-modal.js +14 -14
- package/dist/tui/ask-user-question-chips.js +315 -0
- package/dist/tui/ask-user-question-prompt.js +203 -0
- package/dist/tui/compact-banner.js +81 -0
- package/dist/tui/conversation-pane.js +85 -11
- package/dist/tui/cost-table.js +111 -0
- package/dist/tui/device-flow.js +2 -2
- package/dist/tui/doctor-table.js +46 -0
- package/dist/tui/feedback-prompt.js +156 -0
- package/dist/tui/input-box.js +247 -32
- package/dist/tui/login-picker.js +3 -3
- package/dist/tui/markdown-render.js +6 -6
- package/dist/tui/multi-file-diff-approval.js +375 -0
- package/dist/tui/onboarding-wizard.js +240 -0
- package/dist/tui/permissions-picker.js +86 -0
- package/dist/tui/render.js +36 -1
- package/dist/tui/repl-render.js +239 -25
- package/dist/tui/repl-splash-art.js +16 -16
- package/dist/tui/repl-splash-mascot.js +48 -24
- package/dist/tui/repl-splash.js +22 -22
- package/dist/tui/repl.js +125 -45
- package/dist/tui/slash-palette.js +6 -6
- package/dist/tui/splash.js +2 -2
- package/dist/tui/status-bar.js +109 -31
- package/dist/tui/status-table.js +7 -0
- package/dist/tui/stickers-art.js +136 -0
- package/dist/tui/style-table.js +28 -0
- package/dist/tui/theme-table.js +29 -0
- package/dist/tui/thinking-spinner.js +123 -0
- package/dist/tui/tool-stream-pane.js +53 -4
- package/dist/tui/update-banner.js +27 -2
- package/dist/tui/vim-input.js +267 -0
- package/dist/tui/welcome-banner.js +107 -0
- package/dist/tui/welcome-data.js +293 -0
- package/dist/tui/workspace-context.js +2 -2
- package/package.json +29 -6
- package/test/scenarios/codegen-create-file.scenario.txt +13 -0
- package/test/scenarios/compact-force.scenario.txt +12 -0
- package/test/scenarios/identity.scenario.txt +11 -0
- package/test/scenarios/persona-handoff.scenario.txt +12 -0
- package/test/scenarios/walkback.scenario.txt +12 -0
- package/dist/core/engine/compaction-hook.js +0 -154
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
const EXT_GROUP = 'ts|tsx|js|jsx|md|json|sh|py|yaml|yml';
|
|
4
|
+
const BRACKET_RE = new RegExp(String.raw `\[([^\]\s]+\.(?:` + EXT_GROUP + String.raw `)):(\d+)(?:-(\d+))?\]`, 'g');
|
|
5
|
+
const PAREN_RE = new RegExp(String.raw `\(([^()\s]+\.(?:` + EXT_GROUP + String.raw `)):(\d+)(?:-(\d+))?\)`, 'g');
|
|
6
|
+
function collect(text, re, out) {
|
|
7
|
+
re.lastIndex = 0;
|
|
8
|
+
let match;
|
|
9
|
+
while ((match = re.exec(text)) !== null) {
|
|
10
|
+
const raw = match[0];
|
|
11
|
+
const filePath = match[1];
|
|
12
|
+
const lineStr = match[2];
|
|
13
|
+
const endStr = match[3];
|
|
14
|
+
if (filePath === undefined || lineStr === undefined)
|
|
15
|
+
continue;
|
|
16
|
+
const line = Number.parseInt(lineStr, 10);
|
|
17
|
+
if (!Number.isFinite(line))
|
|
18
|
+
continue;
|
|
19
|
+
const citation = {
|
|
20
|
+
raw,
|
|
21
|
+
path: filePath,
|
|
22
|
+
line,
|
|
23
|
+
startOffset: match.index,
|
|
24
|
+
endOffset: match.index + raw.length,
|
|
25
|
+
};
|
|
26
|
+
if (endStr !== undefined) {
|
|
27
|
+
const endLine = Number.parseInt(endStr, 10);
|
|
28
|
+
if (Number.isFinite(endLine))
|
|
29
|
+
citation.endLine = endLine;
|
|
30
|
+
}
|
|
31
|
+
out.push(citation);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function parseCitations(text) {
|
|
35
|
+
const out = [];
|
|
36
|
+
collect(text, BRACKET_RE, out);
|
|
37
|
+
collect(text, PAREN_RE, out);
|
|
38
|
+
out.sort((a, b) => a.startOffset - b.startOffset);
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
function isTraversal(rawPath) {
|
|
42
|
+
if (path.isAbsolute(rawPath))
|
|
43
|
+
return true;
|
|
44
|
+
const segments = rawPath.split(/[/\\]+/);
|
|
45
|
+
return segments.includes('..');
|
|
46
|
+
}
|
|
47
|
+
function countLines(content) {
|
|
48
|
+
if (content.length === 0)
|
|
49
|
+
return 0;
|
|
50
|
+
const matches = content.match(/\n/g);
|
|
51
|
+
const nl = matches === null ? 0 : matches.length;
|
|
52
|
+
return content.endsWith('\n') ? nl : nl + 1;
|
|
53
|
+
}
|
|
54
|
+
export async function validateCitations(citations, workspaceRoot) {
|
|
55
|
+
const rootReal = await fs.realpath(workspaceRoot);
|
|
56
|
+
const results = [];
|
|
57
|
+
for (const citation of citations) {
|
|
58
|
+
if (isTraversal(citation.path)) {
|
|
59
|
+
results.push({ citation, valid: false, reason: 'path-traversal' });
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const resolved = path.resolve(rootReal, citation.path);
|
|
63
|
+
let realResolved;
|
|
64
|
+
try {
|
|
65
|
+
realResolved = await fs.realpath(resolved);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
results.push({ citation, valid: false, reason: 'file-missing' });
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const rel = path.relative(rootReal, realResolved);
|
|
72
|
+
if (rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
73
|
+
results.push({ citation, valid: false, reason: 'outside-workspace' });
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
let stat;
|
|
77
|
+
try {
|
|
78
|
+
stat = await fs.stat(realResolved);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
results.push({ citation, valid: false, reason: 'file-missing' });
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (!stat.isFile()) {
|
|
85
|
+
results.push({ citation, valid: false, reason: 'file-missing' });
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
let content;
|
|
89
|
+
try {
|
|
90
|
+
content = await fs.readFile(realResolved, 'utf8');
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
results.push({ citation, valid: false, reason: 'file-missing' });
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const totalLines = countLines(content);
|
|
97
|
+
const lastLine = citation.endLine ?? citation.line;
|
|
98
|
+
const rangeInvalid = citation.line < 1 ||
|
|
99
|
+
lastLine < citation.line ||
|
|
100
|
+
lastLine > totalLines;
|
|
101
|
+
if (rangeInvalid) {
|
|
102
|
+
results.push({ citation, valid: false, reason: 'line-out-of-range' });
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
results.push({ citation, valid: true });
|
|
106
|
+
}
|
|
107
|
+
return results;
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export const DEFAULT_RULES = [
|
|
2
|
+
{ name: 'rm-recursive-force', pattern: /\brm\s+(-[a-zA-Z]*r[a-zA-Z]*f|-[a-zA-Z]*f[a-zA-Z]*r|-rf|-fr)\b/, kind: 'destructive', baseConfidence: 0.95 },
|
|
3
|
+
{ name: 'sudo-prefix', pattern: /^\s*sudo\b/, kind: 'destructive', baseConfidence: 0.85 },
|
|
4
|
+
{ name: 'git-force-push', pattern: /\bgit\s+push\b.*--force\b|\bgit\s+push\b.*\s-f\b/, kind: 'destructive', baseConfidence: 0.92 },
|
|
5
|
+
{ name: 'drop-table', pattern: /\bDROP\s+(TABLE|DATABASE|SCHEMA)\b/i, kind: 'destructive', baseConfidence: 0.95 },
|
|
6
|
+
{ name: 'mkfs', pattern: /\bmkfs(\.\w+)?\b/, kind: 'destructive', baseConfidence: 0.95 },
|
|
7
|
+
{ name: 'dd-of-device', pattern: /\bdd\b.*\bof=\/dev\//, kind: 'destructive', baseConfidence: 0.95 },
|
|
8
|
+
{ name: 'chmod-recursive-permissive', pattern: /\bchmod\s+-R\s+777\b/, kind: 'risky', baseConfidence: 0.8 },
|
|
9
|
+
{ name: 'curl-pipe-shell', pattern: /\bcurl\b[^|]*\|\s*(sudo\s+)?(ba)?sh\b/, kind: 'risky', baseConfidence: 0.85 },
|
|
10
|
+
{ name: 'ls-listing', pattern: /^\s*ls(\s|$)/, kind: 'safe', baseConfidence: 0.9 },
|
|
11
|
+
{ name: 'cd-navigate', pattern: /^\s*cd(\s|$)/, kind: 'safe', baseConfidence: 0.9 },
|
|
12
|
+
{ name: 'cat-read', pattern: /^\s*cat\s+/, kind: 'safe', baseConfidence: 0.88 },
|
|
13
|
+
{ name: 'echo-print', pattern: /^\s*echo\s+/, kind: 'safe', baseConfidence: 0.9 },
|
|
14
|
+
{ name: 'pwd-print', pattern: /^\s*pwd\s*$/, kind: 'safe', baseConfidence: 0.95 },
|
|
15
|
+
{ name: 'git-status', pattern: /^\s*git\s+(status|log|diff|branch|show)\b/, kind: 'safe', baseConfidence: 0.9 },
|
|
16
|
+
];
|
|
17
|
+
const DEFAULT_AMBIGUITY_THRESHOLD = 0.7;
|
|
18
|
+
function parseLlmResponse(raw) {
|
|
19
|
+
let parsed;
|
|
20
|
+
try {
|
|
21
|
+
parsed = JSON.parse(raw);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
if (!parsed || typeof parsed !== 'object')
|
|
27
|
+
return null;
|
|
28
|
+
const kind = parsed.kind;
|
|
29
|
+
const confidence = parsed.confidence;
|
|
30
|
+
if (kind !== 'safe' && kind !== 'risky' && kind !== 'destructive' && kind !== 'unknown') {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
if (typeof confidence !== 'number' || !Number.isFinite(confidence)) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const clamped = Math.max(0, Math.min(1, confidence));
|
|
37
|
+
return { kind, confidence: clamped };
|
|
38
|
+
}
|
|
39
|
+
function matchRules(input, rules) {
|
|
40
|
+
for (const rule of rules) {
|
|
41
|
+
if (rule.pattern.test(input)) {
|
|
42
|
+
return { rule };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
export async function classifyInput(input, options = {}) {
|
|
48
|
+
const rules = options.rules ?? DEFAULT_RULES;
|
|
49
|
+
const threshold = options.ambiguityThreshold ?? DEFAULT_AMBIGUITY_THRESHOLD;
|
|
50
|
+
const transport = options.transport;
|
|
51
|
+
const match = matchRules(input, rules);
|
|
52
|
+
if (match && match.rule.baseConfidence >= threshold) {
|
|
53
|
+
return {
|
|
54
|
+
kind: match.rule.kind,
|
|
55
|
+
confidence: match.rule.baseConfidence,
|
|
56
|
+
stage: 'regex',
|
|
57
|
+
matchedRule: match.rule.name,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (!transport) {
|
|
61
|
+
if (match) {
|
|
62
|
+
return {
|
|
63
|
+
kind: match.rule.kind,
|
|
64
|
+
confidence: match.rule.baseConfidence,
|
|
65
|
+
stage: 'regex',
|
|
66
|
+
matchedRule: match.rule.name,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return { kind: 'unknown', confidence: 0, stage: 'fallback' };
|
|
70
|
+
}
|
|
71
|
+
let raw;
|
|
72
|
+
try {
|
|
73
|
+
raw = await transport(input);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return { kind: 'unknown', confidence: 0, stage: 'fallback' };
|
|
77
|
+
}
|
|
78
|
+
const parsed = parseLlmResponse(raw);
|
|
79
|
+
if (!parsed) {
|
|
80
|
+
return { kind: 'unknown', confidence: 0, stage: 'fallback' };
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
kind: parsed.kind,
|
|
84
|
+
confidence: parsed.confidence,
|
|
85
|
+
stage: 'llm',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=yolo-classifier.js.map
|
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pugi local symbol index - SQLite + FTS5 wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Persists the symbol / edge / file tables that the parser (PR L1) and
|
|
5
|
+
* watcher (PR L2) write into, plus the FTS5 mirror that powers
|
|
6
|
+
* `code_search`. The DB file lives at `<workspaceRoot>/.pugi/codegraph.db`
|
|
7
|
+
* and is regenerable - losing it costs one re-parse, never customer data.
|
|
8
|
+
*
|
|
9
|
+
* Driver choice - `node:sqlite`, NOT `better-sqlite3`:
|
|
10
|
+
*
|
|
11
|
+
* - The session-store at `apps/pugi-cli/src/core/repl/store/session-store.ts`
|
|
12
|
+
* (the only prior SQLite consumer inside @pugi/cli) deliberately uses
|
|
13
|
+
* the built-in `node:sqlite` module instead of the better-sqlite3
|
|
14
|
+
* npm package. Reason documented there at lines 20-36: better-sqlite3
|
|
15
|
+
* needs a prebuilt native binary per Node ABI × platform, and missing
|
|
16
|
+
* a wheel triggers a node-gyp compile that fails on customer machines
|
|
17
|
+
* that lack build-essential. Pugi CLI ships via `npm i -g @pugi/cli`
|
|
18
|
+
* to operators who almost certainly do not have a C++ toolchain.
|
|
19
|
+
* - `engines.node >= 22.5.0` already pins the minimum runtime the
|
|
20
|
+
* `node:sqlite` module is available on (stable subset since 22.5).
|
|
21
|
+
* The CLI refuses to install on Node 20.x instead of crash-on-import.
|
|
22
|
+
* - The task spec mentions better-sqlite3 by name; we deviate here so
|
|
23
|
+
* the symbol index inherits the same zero-native-build property the
|
|
24
|
+
* session store already proved out in production. Surfaced in the
|
|
25
|
+
* PR body so the reviewer can re-litigate.
|
|
26
|
+
*
|
|
27
|
+
* The module is structured as standalone functions, not a class, so the
|
|
28
|
+
* MCP tool layer (PR L3) can compose them without instantiating a stateful
|
|
29
|
+
* object. Connection lifetime is the caller's responsibility - open at the
|
|
30
|
+
* start of an operation, close at the end, never share across processes.
|
|
31
|
+
*/
|
|
32
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
33
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
34
|
+
import { dirname, resolve } from 'node:path';
|
|
35
|
+
import { INDEX_SCHEMA_VERSION, } from './types.js';
|
|
36
|
+
/**
|
|
37
|
+
* Default search limit applied when `SearchOptions.limit` is absent.
|
|
38
|
+
* Mirrors the session-store convention (50 list / 20 search). 50 chosen
|
|
39
|
+
* here because callers expect at-least-one full screen of matches.
|
|
40
|
+
*/
|
|
41
|
+
const DEFAULT_SEARCH_LIMIT = 50;
|
|
42
|
+
/** Hard cap on search limit. Protects response payload size. */
|
|
43
|
+
const MAX_SEARCH_LIMIT = 500;
|
|
44
|
+
/** Hard cap on trace depth. Five hops is well past usable. */
|
|
45
|
+
const MAX_TRACE_DEPTH = 5;
|
|
46
|
+
/** Hard cap on trace node count. Stops runaway BFS on densely-connected graphs. */
|
|
47
|
+
const MAX_TRACE_NODES = 200;
|
|
48
|
+
/** Hard cap on disambiguation candidates in DefinitionResult. */
|
|
49
|
+
const MAX_DEFINITION_CANDIDATES = 25;
|
|
50
|
+
/**
|
|
51
|
+
* Initial schema - applied once at first `openIndex`. Future migrations
|
|
52
|
+
* append a new branch in `applyMigration` and bump `INDEX_SCHEMA_VERSION`.
|
|
53
|
+
*
|
|
54
|
+
* FTS5 contentless mirror pattern (`content='symbols'`,
|
|
55
|
+
* `content_rowid='id'`) keeps storage flat - the FTS5 virtual table
|
|
56
|
+
* stores only the inverted index, not duplicated columns. Triggers below
|
|
57
|
+
* keep it in sync with INSERT / UPDATE / DELETE on `symbols`.
|
|
58
|
+
*
|
|
59
|
+
* `kind` columns carry CHECK constraints that mirror the closed
|
|
60
|
+
* SymbolKind / EdgeKind unions in types.ts. Diverging the runtime check
|
|
61
|
+
* from the TS type without a schema migration WILL silently insert
|
|
62
|
+
* unknown values; the CHECK constraint is the safety belt.
|
|
63
|
+
*/
|
|
64
|
+
const SCHEMA_V1_SQL = `
|
|
65
|
+
CREATE TABLE files (
|
|
66
|
+
path TEXT PRIMARY KEY,
|
|
67
|
+
sha256 TEXT NOT NULL,
|
|
68
|
+
last_indexed_at TEXT NOT NULL,
|
|
69
|
+
symbol_count INTEGER NOT NULL DEFAULT 0
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
CREATE TABLE symbols (
|
|
73
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
74
|
+
name TEXT NOT NULL,
|
|
75
|
+
kind TEXT NOT NULL CHECK (kind IN ('function','class','method','interface','type','variable','import')),
|
|
76
|
+
file TEXT NOT NULL,
|
|
77
|
+
line INTEGER NOT NULL,
|
|
78
|
+
col INTEGER NOT NULL,
|
|
79
|
+
scope TEXT NOT NULL DEFAULT '',
|
|
80
|
+
signature TEXT
|
|
81
|
+
);
|
|
82
|
+
CREATE INDEX idx_symbols_name ON symbols(name);
|
|
83
|
+
CREATE INDEX idx_symbols_file ON symbols(file);
|
|
84
|
+
CREATE INDEX idx_symbols_kind ON symbols(kind);
|
|
85
|
+
|
|
86
|
+
CREATE TABLE edges (
|
|
87
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
88
|
+
from_symbol_id INTEGER NOT NULL,
|
|
89
|
+
to_symbol_id INTEGER NOT NULL,
|
|
90
|
+
kind TEXT NOT NULL CHECK (kind IN ('calls','extends','implements','imports','references')),
|
|
91
|
+
FOREIGN KEY (from_symbol_id) REFERENCES symbols(id) ON DELETE CASCADE,
|
|
92
|
+
FOREIGN KEY (to_symbol_id) REFERENCES symbols(id) ON DELETE CASCADE
|
|
93
|
+
);
|
|
94
|
+
CREATE INDEX idx_edges_from ON edges(from_symbol_id);
|
|
95
|
+
CREATE INDEX idx_edges_to ON edges(to_symbol_id);
|
|
96
|
+
CREATE INDEX idx_edges_kind ON edges(kind);
|
|
97
|
+
|
|
98
|
+
CREATE VIRTUAL TABLE symbols_fts USING fts5(
|
|
99
|
+
name,
|
|
100
|
+
scope,
|
|
101
|
+
signature,
|
|
102
|
+
content='symbols',
|
|
103
|
+
content_rowid='id',
|
|
104
|
+
tokenize='unicode61 remove_diacritics 1'
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
CREATE TRIGGER symbols_ai AFTER INSERT ON symbols BEGIN
|
|
108
|
+
INSERT INTO symbols_fts(rowid, name, scope, signature)
|
|
109
|
+
VALUES (new.id, new.name, new.scope, COALESCE(new.signature, ''));
|
|
110
|
+
END;
|
|
111
|
+
|
|
112
|
+
CREATE TRIGGER symbols_ad AFTER DELETE ON symbols BEGIN
|
|
113
|
+
INSERT INTO symbols_fts(symbols_fts, rowid, name, scope, signature)
|
|
114
|
+
VALUES ('delete', old.id, old.name, old.scope, COALESCE(old.signature, ''));
|
|
115
|
+
END;
|
|
116
|
+
|
|
117
|
+
CREATE TRIGGER symbols_au AFTER UPDATE ON symbols BEGIN
|
|
118
|
+
INSERT INTO symbols_fts(symbols_fts, rowid, name, scope, signature)
|
|
119
|
+
VALUES ('delete', old.id, old.name, old.scope, COALESCE(old.signature, ''));
|
|
120
|
+
INSERT INTO symbols_fts(rowid, name, scope, signature)
|
|
121
|
+
VALUES (new.id, new.name, new.scope, COALESCE(new.signature, ''));
|
|
122
|
+
END;
|
|
123
|
+
|
|
124
|
+
CREATE TABLE _migrations (
|
|
125
|
+
version INTEGER PRIMARY KEY,
|
|
126
|
+
applied_at TEXT NOT NULL
|
|
127
|
+
);
|
|
128
|
+
`;
|
|
129
|
+
/**
|
|
130
|
+
* Resolve `<workspaceRoot>/.pugi/codegraph.db`. Exported so the doctor +
|
|
131
|
+
* status surfaces can print the location without duplicating the path
|
|
132
|
+
* computation. Does NOT create the parent directory; that is the
|
|
133
|
+
* caller's responsibility (openIndex handles it).
|
|
134
|
+
*/
|
|
135
|
+
export function resolveIndexPath(workspaceRoot) {
|
|
136
|
+
return resolve(workspaceRoot, '.pugi', 'codegraph.db');
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Open or create the index DB at `<workspaceRoot>/.pugi/codegraph.db`.
|
|
140
|
+
*
|
|
141
|
+
* Idempotent: a second call against the same workspace returns a fresh
|
|
142
|
+
* connection but the schema (already applied) is untouched. The caller
|
|
143
|
+
* is responsible for `closeIndex` - leaking handles is a chokidar PR L2
|
|
144
|
+
* concern (the watcher holds the connection for the process lifetime).
|
|
145
|
+
*
|
|
146
|
+
* Throws on:
|
|
147
|
+
* - workspaceRoot does not exist
|
|
148
|
+
* - `.pugi/` parent cannot be created (EACCES)
|
|
149
|
+
* - SQLite cannot open the file
|
|
150
|
+
* - migration SQL fails
|
|
151
|
+
*/
|
|
152
|
+
export function openIndex(workspaceRoot) {
|
|
153
|
+
const abs = resolve(workspaceRoot);
|
|
154
|
+
if (!existsSync(abs)) {
|
|
155
|
+
throw new Error(`workspaceRoot does not exist: ${abs}`);
|
|
156
|
+
}
|
|
157
|
+
const dbPath = resolveIndexPath(abs);
|
|
158
|
+
const parent = dirname(dbPath);
|
|
159
|
+
if (!existsSync(parent)) {
|
|
160
|
+
mkdirSync(parent, { recursive: true, mode: 0o700 });
|
|
161
|
+
}
|
|
162
|
+
const conn = new DatabaseSync(dbPath);
|
|
163
|
+
// PRAGMA foreign_keys is OFF by default in SQLite; turn it on so the
|
|
164
|
+
// ON DELETE CASCADE in the edges table actually fires when a symbol
|
|
165
|
+
// row is removed.
|
|
166
|
+
conn.exec('PRAGMA foreign_keys = ON;');
|
|
167
|
+
// WAL mode for read-during-write concurrency. The watcher (PR L2)
|
|
168
|
+
// re-parses files in batches; queries from the MCP tool layer must
|
|
169
|
+
// not block on the write transaction.
|
|
170
|
+
conn.exec('PRAGMA journal_mode = WAL;');
|
|
171
|
+
const version = ensureSchema(conn);
|
|
172
|
+
return { conn, workspaceRoot: abs, dbPath, version };
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Close the connection. Safe to call multiple times - node:sqlite's
|
|
176
|
+
* `close()` is idempotent.
|
|
177
|
+
*/
|
|
178
|
+
export function closeIndex(db) {
|
|
179
|
+
db.conn.close();
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Apply migrations from the current applied version up to
|
|
183
|
+
* INDEX_SCHEMA_VERSION. Returns the version actually applied. The
|
|
184
|
+
* `_migrations` table itself is created lazily inside the v1 schema -
|
|
185
|
+
* on a fresh DB we detect that by probing for the `symbols` table.
|
|
186
|
+
*/
|
|
187
|
+
function ensureSchema(conn) {
|
|
188
|
+
const hasMigrationsTable = tableExists(conn, '_migrations');
|
|
189
|
+
if (!hasMigrationsTable) {
|
|
190
|
+
// Fresh DB - apply v1 in one transaction so a mid-script error
|
|
191
|
+
// never leaves us with half the schema.
|
|
192
|
+
conn.exec('BEGIN;');
|
|
193
|
+
try {
|
|
194
|
+
conn.exec(SCHEMA_V1_SQL);
|
|
195
|
+
conn
|
|
196
|
+
.prepare('INSERT INTO _migrations(version, applied_at) VALUES (?, ?)')
|
|
197
|
+
.run(1, new Date().toISOString());
|
|
198
|
+
conn.exec('COMMIT;');
|
|
199
|
+
}
|
|
200
|
+
catch (err) {
|
|
201
|
+
conn.exec('ROLLBACK;');
|
|
202
|
+
throw err;
|
|
203
|
+
}
|
|
204
|
+
return 1;
|
|
205
|
+
}
|
|
206
|
+
const row = conn
|
|
207
|
+
.prepare('SELECT MAX(version) AS version FROM _migrations')
|
|
208
|
+
.get();
|
|
209
|
+
const current = row?.version ?? 0;
|
|
210
|
+
// Future migrations: loop from current+1 to INDEX_SCHEMA_VERSION and
|
|
211
|
+
// call applyMigration(n). v1 is the only version this PR ships.
|
|
212
|
+
if (current < INDEX_SCHEMA_VERSION) {
|
|
213
|
+
for (let v = current + 1; v <= INDEX_SCHEMA_VERSION; v += 1) {
|
|
214
|
+
applyMigration(conn, v);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return INDEX_SCHEMA_VERSION;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Apply one numbered migration. v1 is handled inline in `ensureSchema`
|
|
221
|
+
* (fresh DB branch). This function is the extension point for v2+.
|
|
222
|
+
*/
|
|
223
|
+
function applyMigration(_conn, version) {
|
|
224
|
+
// Reserved for PR L2+ migrations.
|
|
225
|
+
throw new Error(`migration ${version} not implemented in this PR`);
|
|
226
|
+
}
|
|
227
|
+
function tableExists(conn, name) {
|
|
228
|
+
const row = conn
|
|
229
|
+
.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name = ?`)
|
|
230
|
+
.get(name);
|
|
231
|
+
return row !== undefined;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Insert a batch of symbols. Runs in one transaction so a partial
|
|
235
|
+
* failure rolls back cleanly; the FTS5 mirror is updated via triggers
|
|
236
|
+
* declared in v1. Returns the assigned row ids in the same order as
|
|
237
|
+
* the input array - callers (parser.ts) use these ids to build the
|
|
238
|
+
* subsequent edge list.
|
|
239
|
+
*
|
|
240
|
+
* Idempotency: this layer does NOT dedupe. Callers (PR L1 parser) are
|
|
241
|
+
* responsible for deleting prior rows of the same file before re-inserting.
|
|
242
|
+
* The schema's `idx_symbols_file` index makes the `DELETE FROM symbols
|
|
243
|
+
* WHERE file = ?` pattern fast.
|
|
244
|
+
*/
|
|
245
|
+
export function insertSymbols(db, symbols) {
|
|
246
|
+
if (symbols.length === 0)
|
|
247
|
+
return [];
|
|
248
|
+
const stmt = db.conn.prepare(`INSERT INTO symbols(name, kind, file, line, col, scope, signature)
|
|
249
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
250
|
+
RETURNING id`);
|
|
251
|
+
const ids = [];
|
|
252
|
+
db.conn.exec('BEGIN;');
|
|
253
|
+
try {
|
|
254
|
+
for (const s of symbols) {
|
|
255
|
+
const row = stmt.get(s.name, s.kind, s.file, s.line, s.column, s.scope, s.signature ?? null);
|
|
256
|
+
if (!row)
|
|
257
|
+
throw new Error('insertSymbols: RETURNING id missing');
|
|
258
|
+
ids.push(row.id);
|
|
259
|
+
}
|
|
260
|
+
db.conn.exec('COMMIT;');
|
|
261
|
+
}
|
|
262
|
+
catch (err) {
|
|
263
|
+
db.conn.exec('ROLLBACK;');
|
|
264
|
+
throw err;
|
|
265
|
+
}
|
|
266
|
+
return ids;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Insert a batch of edges. Single transaction; orphan edges (endpoints
|
|
270
|
+
* missing from `symbols`) raise a FOREIGN KEY constraint failure that
|
|
271
|
+
* rolls back the whole batch - callers should pre-filter on the
|
|
272
|
+
* caller-side ID map produced by `insertSymbols`.
|
|
273
|
+
*/
|
|
274
|
+
export function insertEdges(db, edges) {
|
|
275
|
+
if (edges.length === 0)
|
|
276
|
+
return;
|
|
277
|
+
const stmt = db.conn.prepare(`INSERT INTO edges(from_symbol_id, to_symbol_id, kind) VALUES (?, ?, ?)`);
|
|
278
|
+
db.conn.exec('BEGIN;');
|
|
279
|
+
try {
|
|
280
|
+
for (const e of edges) {
|
|
281
|
+
stmt.run(e.fromSymbolId, e.toSymbolId, e.kind);
|
|
282
|
+
}
|
|
283
|
+
db.conn.exec('COMMIT;');
|
|
284
|
+
}
|
|
285
|
+
catch (err) {
|
|
286
|
+
db.conn.exec('ROLLBACK;');
|
|
287
|
+
throw err;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Upsert a file fingerprint row. The watcher (PR L2) calls this after
|
|
292
|
+
* re-parsing a file so subsequent runs can skip unchanged content.
|
|
293
|
+
*/
|
|
294
|
+
export function upsertFile(db, file) {
|
|
295
|
+
db.conn
|
|
296
|
+
.prepare(`INSERT INTO files(path, sha256, last_indexed_at, symbol_count)
|
|
297
|
+
VALUES (?, ?, ?, ?)
|
|
298
|
+
ON CONFLICT(path) DO UPDATE SET
|
|
299
|
+
sha256 = excluded.sha256,
|
|
300
|
+
last_indexed_at = excluded.last_indexed_at,
|
|
301
|
+
symbol_count = excluded.symbol_count`)
|
|
302
|
+
.run(file.path, file.sha256, file.lastIndexedAt, file.symbolCount);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* FTS5 search on the contentless mirror. Returns symbols ranked by
|
|
306
|
+
* BM25 (lower is better). The query string is passed through to FTS5
|
|
307
|
+
* verbatim - callers wanting prefix match append `*`, callers wanting
|
|
308
|
+
* a phrase quote it. Invalid FTS5 syntax raises a SqliteError which
|
|
309
|
+
* we surface as 'invalid_query'.
|
|
310
|
+
*
|
|
311
|
+
* `kindFilter` is applied via a secondary JOIN - FTS5 itself does not
|
|
312
|
+
* know about the `kind` column.
|
|
313
|
+
*/
|
|
314
|
+
export function searchSymbols(db, query, options = {}) {
|
|
315
|
+
if (query.trim().length === 0)
|
|
316
|
+
return [];
|
|
317
|
+
const limit = clamp(options.limit ?? DEFAULT_SEARCH_LIMIT, 1, MAX_SEARCH_LIMIT);
|
|
318
|
+
const kindFilter = options.kindFilter ?? null;
|
|
319
|
+
// Build the kind filter dynamically if present; FTS5 + an indexed
|
|
320
|
+
// column lookup composes naturally via the rowid join.
|
|
321
|
+
const kindClause = kindFilter && kindFilter.length > 0
|
|
322
|
+
? ` AND s.kind IN (${kindFilter.map(() => '?').join(',')})`
|
|
323
|
+
: '';
|
|
324
|
+
const sql = `SELECT s.id, s.name, s.kind, s.file, s.line, s.col, s.scope, s.signature, bm25(symbols_fts) AS rank
|
|
325
|
+
FROM symbols_fts
|
|
326
|
+
JOIN symbols s ON s.id = symbols_fts.rowid
|
|
327
|
+
WHERE symbols_fts MATCH ?${kindClause}
|
|
328
|
+
ORDER BY rank
|
|
329
|
+
LIMIT ?`;
|
|
330
|
+
// node:sqlite's StatementSync.all() accepts a variadic of SQLiteAnonymousBoundValue
|
|
331
|
+
// (string | number | bigint | null | Buffer | Uint8Array). Our params are all
|
|
332
|
+
// string | number - cast through `unknown` to satisfy TS's variadic spread.
|
|
333
|
+
const sqliteParams = [query];
|
|
334
|
+
if (kindFilter && kindFilter.length > 0) {
|
|
335
|
+
sqliteParams.push(...kindFilter);
|
|
336
|
+
}
|
|
337
|
+
sqliteParams.push(limit);
|
|
338
|
+
const rows = db.conn.prepare(sql).all(...sqliteParams);
|
|
339
|
+
return rows.map((r) => ({
|
|
340
|
+
symbol: rowToSymbol(r),
|
|
341
|
+
rank: r.rank,
|
|
342
|
+
}));
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Exact-name definition lookup. Returns the canonical definition
|
|
346
|
+
* (lowest line number wins when multiple match; arbitrary but stable)
|
|
347
|
+
* plus up to `MAX_DEFINITION_CANDIDATES` disambiguations so the agent
|
|
348
|
+
* can pick a different one without a second query.
|
|
349
|
+
*/
|
|
350
|
+
export function findDefinition(db, name) {
|
|
351
|
+
const rows = db.conn
|
|
352
|
+
.prepare(`SELECT id, name, kind, file, line, col, scope, signature
|
|
353
|
+
FROM symbols
|
|
354
|
+
WHERE name = ?
|
|
355
|
+
ORDER BY
|
|
356
|
+
CASE kind
|
|
357
|
+
WHEN 'function' THEN 1
|
|
358
|
+
WHEN 'class' THEN 2
|
|
359
|
+
WHEN 'method' THEN 3
|
|
360
|
+
WHEN 'interface' THEN 4
|
|
361
|
+
WHEN 'type' THEN 5
|
|
362
|
+
WHEN 'variable' THEN 6
|
|
363
|
+
WHEN 'import' THEN 7
|
|
364
|
+
END,
|
|
365
|
+
line ASC
|
|
366
|
+
LIMIT ?`)
|
|
367
|
+
.all(name, MAX_DEFINITION_CANDIDATES + 1);
|
|
368
|
+
if (rows.length === 0) {
|
|
369
|
+
return { kind: 'not_found', name };
|
|
370
|
+
}
|
|
371
|
+
const [first, ...rest] = rows;
|
|
372
|
+
if (!first)
|
|
373
|
+
return { kind: 'not_found', name };
|
|
374
|
+
return {
|
|
375
|
+
kind: 'found',
|
|
376
|
+
definition: rowToSymbol(first),
|
|
377
|
+
disambiguations: rest.slice(0, MAX_DEFINITION_CANDIDATES).map(rowToSymbol),
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Reverse-edge query: every symbol whose outbound edge lands on
|
|
382
|
+
* `symbolId`. Filters to `calls` / `extends` / `implements` /
|
|
383
|
+
* `references` - `imports` is excluded because import edges fire on
|
|
384
|
+
* every transitive use, drowning the result set. Callers wanting
|
|
385
|
+
* imports specifically can query the `edges` table directly via the
|
|
386
|
+
* exposed `db.conn` handle (PR L3 wires a dedicated tool).
|
|
387
|
+
*/
|
|
388
|
+
export function findCallers(db, symbolId) {
|
|
389
|
+
const rows = db.conn
|
|
390
|
+
.prepare(`SELECT s.id, s.name, s.kind, s.file, s.line, s.col, s.scope, s.signature,
|
|
391
|
+
e.id AS edge_id, e.from_symbol_id, e.to_symbol_id, e.kind AS edge_kind
|
|
392
|
+
FROM edges e
|
|
393
|
+
JOIN symbols s ON s.id = e.from_symbol_id
|
|
394
|
+
WHERE e.to_symbol_id = ?
|
|
395
|
+
AND e.kind IN ('calls','extends','implements','references')
|
|
396
|
+
ORDER BY s.file ASC, s.line ASC`)
|
|
397
|
+
.all(symbolId);
|
|
398
|
+
return rows.map((r) => ({
|
|
399
|
+
caller: rowToSymbol(r),
|
|
400
|
+
edge: {
|
|
401
|
+
fromSymbolId: r.from_symbol_id,
|
|
402
|
+
toSymbolId: r.to_symbol_id,
|
|
403
|
+
kind: r.edge_kind,
|
|
404
|
+
},
|
|
405
|
+
}));
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Bounded BFS outward from `rootSymbolId` over `calls` edges. Real
|
|
409
|
+
* implementation in PR L3 (the MCP tool layer composes this with the
|
|
410
|
+
* agent loop); here we ship a minimal version that returns the root
|
|
411
|
+
* symbol with zero outbound edges + a depth marker of 0 so the schema
|
|
412
|
+
* roundtrips. Caps documented at module scope.
|
|
413
|
+
*/
|
|
414
|
+
export function traceSymbol(db, rootSymbolId, depth) {
|
|
415
|
+
const clampedDepth = clamp(depth, 1, MAX_TRACE_DEPTH);
|
|
416
|
+
const root = db.conn
|
|
417
|
+
.prepare(`SELECT id, name, kind, file, line, col, scope, signature
|
|
418
|
+
FROM symbols WHERE id = ?`)
|
|
419
|
+
.get(rootSymbolId);
|
|
420
|
+
if (!root) {
|
|
421
|
+
return {
|
|
422
|
+
rootSymbolId,
|
|
423
|
+
nodes: [],
|
|
424
|
+
edges: [],
|
|
425
|
+
depthReached: 0,
|
|
426
|
+
truncated: false,
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
// Real BFS implementation lands in PR L3. The scaffold returns the
|
|
430
|
+
// root only so callers can wire the response envelope without waiting
|
|
431
|
+
// for the parser.
|
|
432
|
+
return {
|
|
433
|
+
rootSymbolId,
|
|
434
|
+
nodes: [rowToSymbol(root)],
|
|
435
|
+
edges: [],
|
|
436
|
+
depthReached: 0,
|
|
437
|
+
truncated: false,
|
|
438
|
+
// clampedDepth is recorded but unused until PR L3; surface for the
|
|
439
|
+
// future MCP tool body. Reading it here also keeps TS happy about
|
|
440
|
+
// the unused-arg lint.
|
|
441
|
+
...{ requestedDepth: clampedDepth },
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Look up an indexed file by workspace-relative path. Returns null
|
|
446
|
+
* when the watcher has not seen this file yet. The watcher (PR L2)
|
|
447
|
+
* uses this to skip re-parsing unchanged files via `sha256` compare.
|
|
448
|
+
*/
|
|
449
|
+
export function findIndexedFile(db, path) {
|
|
450
|
+
const row = db.conn
|
|
451
|
+
.prepare(`SELECT path, sha256, last_indexed_at AS lastIndexedAt, symbol_count AS symbolCount
|
|
452
|
+
FROM files WHERE path = ?`)
|
|
453
|
+
.get(path);
|
|
454
|
+
return row ?? null;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Count of indexed symbols across all files. Surfaced by `pugi index`
|
|
458
|
+
* status output + `/codegraph-status` once PR L3 wires it. Constant-time
|
|
459
|
+
* via SQLite's COUNT estimate.
|
|
460
|
+
*/
|
|
461
|
+
export function countSymbols(db) {
|
|
462
|
+
const row = db.conn
|
|
463
|
+
.prepare(`SELECT COUNT(*) AS count FROM symbols`)
|
|
464
|
+
.get();
|
|
465
|
+
return row?.count ?? 0;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Delete every symbol + edge belonging to one file. The watcher calls
|
|
469
|
+
* this before re-inserting freshly-parsed rows so the index reflects
|
|
470
|
+
* the current state of disk. Edges to other-file symbols disappear via
|
|
471
|
+
* the FOREIGN KEY CASCADE.
|
|
472
|
+
*/
|
|
473
|
+
export function deleteFile(db, path) {
|
|
474
|
+
db.conn.exec('BEGIN;');
|
|
475
|
+
try {
|
|
476
|
+
db.conn.prepare('DELETE FROM symbols WHERE file = ?').run(path);
|
|
477
|
+
db.conn.prepare('DELETE FROM files WHERE path = ?').run(path);
|
|
478
|
+
db.conn.exec('COMMIT;');
|
|
479
|
+
}
|
|
480
|
+
catch (err) {
|
|
481
|
+
db.conn.exec('ROLLBACK;');
|
|
482
|
+
throw err;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
function rowToSymbol(r) {
|
|
486
|
+
const out = {
|
|
487
|
+
id: r.id,
|
|
488
|
+
name: r.name,
|
|
489
|
+
kind: r.kind,
|
|
490
|
+
file: r.file,
|
|
491
|
+
line: r.line,
|
|
492
|
+
column: r.col,
|
|
493
|
+
scope: r.scope,
|
|
494
|
+
};
|
|
495
|
+
if (r.signature !== null)
|
|
496
|
+
out.signature = r.signature;
|
|
497
|
+
return out;
|
|
498
|
+
}
|
|
499
|
+
function clamp(n, lo, hi) {
|
|
500
|
+
if (n < lo)
|
|
501
|
+
return lo;
|
|
502
|
+
if (n > hi)
|
|
503
|
+
return hi;
|
|
504
|
+
return n;
|
|
505
|
+
}
|
|
506
|
+
//# sourceMappingURL=db.js.map
|