@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,242 @@
|
|
|
1
|
+
export const STYLE_WORD_CAPS = Object.freeze({
|
|
2
|
+
minimal: 500,
|
|
3
|
+
standard: 1500,
|
|
4
|
+
detailed: 3500,
|
|
5
|
+
});
|
|
6
|
+
/** Render PUGI.md content for the requested tier. */
|
|
7
|
+
export function renderPugiMd(detection, options) {
|
|
8
|
+
const sections = [];
|
|
9
|
+
const style = options.style;
|
|
10
|
+
const now = options.now ?? (() => new Date());
|
|
11
|
+
sections.push(renderHeader(detection, now()));
|
|
12
|
+
sections.push(renderStack(detection));
|
|
13
|
+
sections.push(renderFrameworks(detection, style));
|
|
14
|
+
if (style !== 'minimal') {
|
|
15
|
+
sections.push(renderTesting(detection));
|
|
16
|
+
sections.push(renderCommitConvention(detection));
|
|
17
|
+
sections.push(renderMonorepo(detection));
|
|
18
|
+
sections.push(renderAiConfigs(detection, style));
|
|
19
|
+
sections.push(renderCommands(detection));
|
|
20
|
+
sections.push(renderProjectLayout(detection));
|
|
21
|
+
}
|
|
22
|
+
if (style === 'detailed') {
|
|
23
|
+
sections.push(renderContributorChecklist());
|
|
24
|
+
}
|
|
25
|
+
sections.push(renderFooter());
|
|
26
|
+
const body = sections.filter((s) => s.length > 0).join('\n\n');
|
|
27
|
+
const trimmed = enforceWordCap(body, STYLE_WORD_CAPS[style]);
|
|
28
|
+
return Object.freeze({
|
|
29
|
+
content: trimmed.endsWith('\n') ? trimmed : `${trimmed}\n`,
|
|
30
|
+
wordCount: countWords(trimmed),
|
|
31
|
+
style,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/* ------------------------------------------------------------------ */
|
|
35
|
+
/* Section renderers */
|
|
36
|
+
/* ------------------------------------------------------------------ */
|
|
37
|
+
function renderHeader(detection, now) {
|
|
38
|
+
const name = detection.stack.nodeName ?? deriveName(detection.cwd);
|
|
39
|
+
const date = now.toISOString().slice(0, 10);
|
|
40
|
+
const lines = [
|
|
41
|
+
`# ${name} — AI Context (PUGI.md)`,
|
|
42
|
+
'',
|
|
43
|
+
`Generated by \`pugi init\` on ${date}.`,
|
|
44
|
+
'Pugi reads this file on every session start. Keep it short and current —',
|
|
45
|
+
'if a section conflicts with the code, the code wins. Edit freely.',
|
|
46
|
+
];
|
|
47
|
+
return lines.join('\n');
|
|
48
|
+
}
|
|
49
|
+
function renderStack(detection) {
|
|
50
|
+
const lines = ['## Stack', ''];
|
|
51
|
+
const stack = detection.stack;
|
|
52
|
+
if (stack.kind === 'unknown') {
|
|
53
|
+
lines.push('No language manifest detected at the workspace root.');
|
|
54
|
+
if (detection.languages.length > 0) {
|
|
55
|
+
lines.push('');
|
|
56
|
+
lines.push(`Primary file extensions: ${detection.languages.join(', ')}.`);
|
|
57
|
+
}
|
|
58
|
+
return lines.join('\n');
|
|
59
|
+
}
|
|
60
|
+
const labelMap = {
|
|
61
|
+
node: 'Node.js / JavaScript / TypeScript',
|
|
62
|
+
python: 'Python',
|
|
63
|
+
rust: 'Rust',
|
|
64
|
+
go: 'Go',
|
|
65
|
+
ruby: 'Ruby',
|
|
66
|
+
php: 'PHP',
|
|
67
|
+
java: 'Java / Kotlin (JVM)',
|
|
68
|
+
};
|
|
69
|
+
lines.push(`- Primary: ${labelMap[stack.kind] ?? stack.kind}`);
|
|
70
|
+
if (stack.packageManager) {
|
|
71
|
+
lines.push(`- Package manager: ${stack.packageManager}`);
|
|
72
|
+
}
|
|
73
|
+
if (stack.manifests.length > 0) {
|
|
74
|
+
lines.push(`- Manifests: ${stack.manifests.join(', ')}`);
|
|
75
|
+
}
|
|
76
|
+
if (detection.languages.length > 0) {
|
|
77
|
+
lines.push(`- Languages observed: ${detection.languages.join(', ')}`);
|
|
78
|
+
}
|
|
79
|
+
return lines.join('\n');
|
|
80
|
+
}
|
|
81
|
+
function renderFrameworks(detection, style) {
|
|
82
|
+
if (detection.frameworks.length === 0)
|
|
83
|
+
return '';
|
|
84
|
+
const lines = ['## Frameworks', ''];
|
|
85
|
+
for (const fw of detection.frameworks) {
|
|
86
|
+
if (style === 'detailed') {
|
|
87
|
+
lines.push(`- **${fw.name}** — ${fw.evidence}`);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
lines.push(`- ${fw.name}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return lines.join('\n');
|
|
94
|
+
}
|
|
95
|
+
function renderTesting(detection) {
|
|
96
|
+
if (detection.testFrameworks.length === 0)
|
|
97
|
+
return '';
|
|
98
|
+
const lines = ['## Testing', ''];
|
|
99
|
+
lines.push(`Detected: ${detection.testFrameworks.join(', ')}.`);
|
|
100
|
+
return lines.join('\n');
|
|
101
|
+
}
|
|
102
|
+
function renderCommitConvention(detection) {
|
|
103
|
+
const lines = ['## Commit conventions', ''];
|
|
104
|
+
switch (detection.commitConvention) {
|
|
105
|
+
case 'conventional':
|
|
106
|
+
lines.push('Recent history follows Conventional Commits (`feat:`, `fix:`, `chore:`...).');
|
|
107
|
+
lines.push('Keep new commits in that style — auto-changelog tooling parses the prefix.');
|
|
108
|
+
break;
|
|
109
|
+
case 'gitmoji':
|
|
110
|
+
lines.push('Recent history uses gitmoji prefixes (`:sparkles:`, `:bug:`...).');
|
|
111
|
+
lines.push('Keep new commits in that style for visual consistency.');
|
|
112
|
+
break;
|
|
113
|
+
case 'freeform':
|
|
114
|
+
lines.push('Commits are freeform — no detected prefix convention.');
|
|
115
|
+
lines.push('Pick a short imperative subject line and keep body wrapped at 72 cols.');
|
|
116
|
+
break;
|
|
117
|
+
case 'unknown':
|
|
118
|
+
default:
|
|
119
|
+
lines.push('No commit history detected yet. Pick a convention (recommended: Conventional Commits).');
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
return lines.join('\n');
|
|
123
|
+
}
|
|
124
|
+
function renderMonorepo(detection) {
|
|
125
|
+
if (!detection.monorepo)
|
|
126
|
+
return '';
|
|
127
|
+
const lines = ['## Monorepo', ''];
|
|
128
|
+
const kindLabel = {
|
|
129
|
+
pnpm: 'pnpm workspaces',
|
|
130
|
+
lerna: 'Lerna',
|
|
131
|
+
turborepo: 'Turborepo',
|
|
132
|
+
nx: 'Nx',
|
|
133
|
+
'yarn-workspaces': 'Yarn / npm workspaces',
|
|
134
|
+
};
|
|
135
|
+
lines.push(`- Layout: ${kindLabel[detection.monorepo.kind] ?? detection.monorepo.kind} (${detection.monorepo.file})`);
|
|
136
|
+
return lines.join('\n');
|
|
137
|
+
}
|
|
138
|
+
function renderAiConfigs(detection, style) {
|
|
139
|
+
if (detection.aiConfigs.length === 0)
|
|
140
|
+
return '';
|
|
141
|
+
const lines = ['## Existing AI configs', ''];
|
|
142
|
+
for (const cfg of detection.aiConfigs) {
|
|
143
|
+
lines.push(`- \`${cfg.path}\``);
|
|
144
|
+
}
|
|
145
|
+
if (style === 'detailed') {
|
|
146
|
+
for (const cfg of detection.aiConfigs) {
|
|
147
|
+
if (!cfg.excerpt)
|
|
148
|
+
continue;
|
|
149
|
+
lines.push('');
|
|
150
|
+
lines.push(`### ${cfg.name} excerpt`);
|
|
151
|
+
lines.push('');
|
|
152
|
+
lines.push('```');
|
|
153
|
+
const trimmed = cfg.excerpt.split('\n').slice(0, 20).join('\n');
|
|
154
|
+
lines.push(trimmed);
|
|
155
|
+
lines.push('```');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return lines.join('\n');
|
|
159
|
+
}
|
|
160
|
+
function renderCommands(detection) {
|
|
161
|
+
if (detection.scripts.length === 0)
|
|
162
|
+
return '';
|
|
163
|
+
const lines = ['## Commands', ''];
|
|
164
|
+
const pm = detection.stack.packageManager ?? 'npm';
|
|
165
|
+
lines.push('```bash');
|
|
166
|
+
for (const entry of detection.scripts) {
|
|
167
|
+
lines.push(commandLine(entry, pm));
|
|
168
|
+
}
|
|
169
|
+
lines.push('```');
|
|
170
|
+
return lines.join('\n');
|
|
171
|
+
}
|
|
172
|
+
function commandLine(entry, pm) {
|
|
173
|
+
const runner = pm === 'pnpm' || pm === 'yarn' || pm === 'bun' ? pm : 'npm run';
|
|
174
|
+
if (runner === 'npm run')
|
|
175
|
+
return `npm run ${entry.name}`;
|
|
176
|
+
return `${runner} ${entry.name}`;
|
|
177
|
+
}
|
|
178
|
+
function renderProjectLayout(detection) {
|
|
179
|
+
if (detection.topLevelDirs.length === 0)
|
|
180
|
+
return '';
|
|
181
|
+
const lines = ['## Project layout', ''];
|
|
182
|
+
for (const dir of detection.topLevelDirs) {
|
|
183
|
+
lines.push(`- \`${dir}/\``);
|
|
184
|
+
}
|
|
185
|
+
return lines.join('\n');
|
|
186
|
+
}
|
|
187
|
+
function renderContributorChecklist() {
|
|
188
|
+
return [
|
|
189
|
+
'## Contributor checklist',
|
|
190
|
+
'',
|
|
191
|
+
'- Read this file + any in-repo ARCHITECTURE / DESIGN docs before code changes.',
|
|
192
|
+
'- Run the project test suite before opening a PR.',
|
|
193
|
+
'- Keep secrets in `.env` (never commit).',
|
|
194
|
+
'- Update this file when stack / framework / commands change.',
|
|
195
|
+
].join('\n');
|
|
196
|
+
}
|
|
197
|
+
function renderFooter() {
|
|
198
|
+
return [
|
|
199
|
+
'## How Pugi uses this file',
|
|
200
|
+
'',
|
|
201
|
+
'On every session, Pugi loads this file as ambient context.',
|
|
202
|
+
'Edit it like a README aimed at an AI engineer joining your team.',
|
|
203
|
+
].join('\n');
|
|
204
|
+
}
|
|
205
|
+
/* ------------------------------------------------------------------ */
|
|
206
|
+
/* Word-cap enforcement */
|
|
207
|
+
/* ------------------------------------------------------------------ */
|
|
208
|
+
function countWords(text) {
|
|
209
|
+
const trimmed = text.trim();
|
|
210
|
+
if (trimmed.length === 0)
|
|
211
|
+
return 0;
|
|
212
|
+
return trimmed.split(/\s+/).length;
|
|
213
|
+
}
|
|
214
|
+
function enforceWordCap(body, cap) {
|
|
215
|
+
const current = countWords(body);
|
|
216
|
+
if (current <= cap)
|
|
217
|
+
return body;
|
|
218
|
+
// Greedy truncation: keep paragraphs in order until cap is reached,
|
|
219
|
+
// then append a truncation marker so the reader understands the file
|
|
220
|
+
// was clipped (deterministic).
|
|
221
|
+
const paragraphs = body.split(/\n{2,}/);
|
|
222
|
+
const kept = [];
|
|
223
|
+
let used = 0;
|
|
224
|
+
for (const paragraph of paragraphs) {
|
|
225
|
+
const words = countWords(paragraph);
|
|
226
|
+
if (used + words > cap)
|
|
227
|
+
break;
|
|
228
|
+
kept.push(paragraph);
|
|
229
|
+
used += words;
|
|
230
|
+
}
|
|
231
|
+
kept.push(`<!-- truncated to fit ${cap}-word cap -->`);
|
|
232
|
+
return kept.join('\n\n');
|
|
233
|
+
}
|
|
234
|
+
/* ------------------------------------------------------------------ */
|
|
235
|
+
/* Misc */
|
|
236
|
+
/* ------------------------------------------------------------------ */
|
|
237
|
+
function deriveName(cwd) {
|
|
238
|
+
const trimmed = cwd.replace(/\/+$/, '');
|
|
239
|
+
const last = trimmed.split('/').pop();
|
|
240
|
+
return last && last.length > 0 ? last : 'project';
|
|
241
|
+
}
|
|
242
|
+
//# sourceMappingURL=template-renderer.js.map
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* JobRegistry — Sprint
|
|
2
|
+
* JobRegistry — Sprint .
|
|
3
3
|
*
|
|
4
4
|
* First-class persistent registry for background bash jobs. Lifts the
|
|
5
5
|
* inline `~/.pugi/jobs.json` management from `src/tools/bash.ts` and
|
|
6
6
|
* extends it with:
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
8
|
+
* - Atomic write (tempfile + rename) so concurrent CLI invocations
|
|
9
|
+
* cannot corrupt the ledger (Code Reviewer P2 retro on PR).
|
|
10
|
+
* - Stale-job reaping on every `list()` call: entries whose pid no
|
|
11
|
+
* longer corresponds to a live process are marked `abandoned`.
|
|
12
|
+
* - 24h retention for finished / killed / abandoned entries; older
|
|
13
|
+
* records are dropped silently.
|
|
14
|
+
* - Lifecycle status field (`running` / `finished` / `killed` /
|
|
15
|
+
* `failed` / `abandoned`) so the `pugi jobs` CLI can render an
|
|
16
|
+
* accurate snapshot regardless of which CLI invocation observed
|
|
17
|
+
* the exit.
|
|
18
18
|
*
|
|
19
19
|
* Storage stays at `~/.pugi/jobs.json` as a JSON array (not JSONL) so
|
|
20
|
-
*
|
|
20
|
+
* readers continue to work. The schema is additive: legacy
|
|
21
21
|
* entries lacking `id` / `status` / etc. are upgraded on read.
|
|
22
22
|
*/
|
|
23
23
|
import { randomUUID } from 'node:crypto';
|
|
@@ -267,7 +267,7 @@ class FileJobRegistry {
|
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
269
|
/**
|
|
270
|
-
* Normalize legacy `~/.pugi/jobs.json` entries (Sprint
|
|
270
|
+
* Normalize legacy `~/.pugi/jobs.json` entries (Sprint shape with
|
|
271
271
|
* `jobId` / `class` / no status) into the JobEntry shape used here.
|
|
272
272
|
* Returns undefined for entries that cannot be migrated so the loop
|
|
273
273
|
* silently skips garbage.
|
|
@@ -387,7 +387,7 @@ export function summarizeJobsForPrompt(entries) {
|
|
|
387
387
|
.slice(-5);
|
|
388
388
|
const lines = ['BACKGROUND JOBS:'];
|
|
389
389
|
if (running.length === 0) {
|
|
390
|
-
lines.push('
|
|
390
|
+
lines.push(' - Running: 0');
|
|
391
391
|
}
|
|
392
392
|
else {
|
|
393
393
|
const oldest = running.reduce((min, entry) => {
|
|
@@ -395,7 +395,7 @@ export function summarizeJobsForPrompt(entries) {
|
|
|
395
395
|
? entry
|
|
396
396
|
: min;
|
|
397
397
|
}, running[0]);
|
|
398
|
-
lines.push(`
|
|
398
|
+
lines.push(` - Running: ${running.length} (oldest started ${relativeAge(oldest.startedAt)} ago)`);
|
|
399
399
|
const counts = new Map();
|
|
400
400
|
for (const entry of running) {
|
|
401
401
|
counts.set(entry.bashClass, (counts.get(entry.bashClass) ?? 0) + 1);
|
|
@@ -403,15 +403,15 @@ export function summarizeJobsForPrompt(entries) {
|
|
|
403
403
|
const classSummary = [...counts.entries()]
|
|
404
404
|
.map(([cls, count]) => `${cls} (${count})`)
|
|
405
405
|
.join(', ');
|
|
406
|
-
lines.push(`
|
|
406
|
+
lines.push(` - Classes in flight: ${classSummary}`);
|
|
407
407
|
}
|
|
408
408
|
if (finished.length > 0) {
|
|
409
409
|
const compact = finished
|
|
410
410
|
.map((entry) => `${entry.id.slice(0, 8)} ${entry.status}${entry.exitCode !== undefined ? ` exit=${entry.exitCode}` : ''}`)
|
|
411
411
|
.join('; ');
|
|
412
|
-
lines.push(`
|
|
412
|
+
lines.push(` - Recent finished (last ${finished.length}): ${compact}`);
|
|
413
413
|
}
|
|
414
|
-
lines.push('
|
|
414
|
+
lines.push(' Use `pugi jobs tail <id>` to inspect output. Do not spawn another long-running background job while 3+ are already on watch.');
|
|
415
415
|
return lines.join('\n');
|
|
416
416
|
}
|
|
417
417
|
/**
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
const HEADER = 'ts\trun_id\tscope\tkey\tvalue\tmodel\tprompt_hash\tnotes\n';
|
|
3
|
+
const appendLocks = new Map();
|
|
4
|
+
export class ResultsLedger {
|
|
5
|
+
path;
|
|
6
|
+
constructor(path) {
|
|
7
|
+
this.path = path;
|
|
8
|
+
}
|
|
9
|
+
async append(row) {
|
|
10
|
+
const previous = appendLocks.get(this.path) ?? Promise.resolve();
|
|
11
|
+
const next = previous
|
|
12
|
+
.catch(() => undefined)
|
|
13
|
+
.then(async () => {
|
|
14
|
+
if (await this.needsHeader()) {
|
|
15
|
+
await fs.appendFile(this.path, HEADER, 'utf8');
|
|
16
|
+
}
|
|
17
|
+
await fs.appendFile(this.path, `${this.formatRow(row)}\n`, 'utf8');
|
|
18
|
+
});
|
|
19
|
+
appendLocks.set(this.path, next);
|
|
20
|
+
try {
|
|
21
|
+
await next;
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
if (appendLocks.get(this.path) === next) {
|
|
25
|
+
appendLocks.delete(this.path);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async query(filter) {
|
|
30
|
+
const rows = await this.readRows();
|
|
31
|
+
return rows.filter((row) => {
|
|
32
|
+
return Object.entries(filter).every(([field, expected]) => {
|
|
33
|
+
if (expected === undefined) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
const value = row[field];
|
|
37
|
+
return value !== undefined && String(value) === String(expected);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
async aggregate(scope, key, op) {
|
|
42
|
+
const rows = await this.query({ scope, key });
|
|
43
|
+
if (op === 'count') {
|
|
44
|
+
return rows.length;
|
|
45
|
+
}
|
|
46
|
+
const values = rows
|
|
47
|
+
.map((row) => Number(row.value))
|
|
48
|
+
.filter((value) => Number.isFinite(value));
|
|
49
|
+
if (values.length === 0) {
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
if (op === 'sum') {
|
|
53
|
+
return values.reduce((sum, value) => sum + value, 0);
|
|
54
|
+
}
|
|
55
|
+
if (op === 'avg') {
|
|
56
|
+
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
57
|
+
}
|
|
58
|
+
if (op === 'min') {
|
|
59
|
+
return Math.min(...values);
|
|
60
|
+
}
|
|
61
|
+
return Math.max(...values);
|
|
62
|
+
}
|
|
63
|
+
async needsHeader() {
|
|
64
|
+
try {
|
|
65
|
+
const stat = await fs.stat(this.path);
|
|
66
|
+
return stat.size === 0;
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (isNodeError(error) && error.code === 'ENOENT') {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async readRows() {
|
|
76
|
+
let data;
|
|
77
|
+
try {
|
|
78
|
+
data = await fs.readFile(this.path, 'utf8');
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (isNodeError(error) && error.code === 'ENOENT') {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
if (!data.endsWith('\n')) {
|
|
87
|
+
data = data.slice(0, data.lastIndexOf('\n') + 1);
|
|
88
|
+
}
|
|
89
|
+
const lines = data.split('\n').filter((line) => line.length > 0);
|
|
90
|
+
const dataLines = lines[0] === HEADER.trimEnd() ? lines.slice(1) : lines;
|
|
91
|
+
return dataLines.flatMap((line) => {
|
|
92
|
+
const cells = line.split('\t');
|
|
93
|
+
if (cells.length !== 8) {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
const ts = cells[0];
|
|
97
|
+
const run_id = cells[1];
|
|
98
|
+
const scope = cells[2];
|
|
99
|
+
const key = cells[3];
|
|
100
|
+
const value = cells[4];
|
|
101
|
+
const model = cells[5];
|
|
102
|
+
const prompt_hash = cells[6];
|
|
103
|
+
const notes = cells[7];
|
|
104
|
+
if (!ts || !run_id || !scope || !key || value === undefined) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
return [
|
|
108
|
+
{
|
|
109
|
+
ts,
|
|
110
|
+
run_id,
|
|
111
|
+
scope,
|
|
112
|
+
key,
|
|
113
|
+
value,
|
|
114
|
+
...(model ? { model } : {}),
|
|
115
|
+
...(prompt_hash ? { prompt_hash } : {}),
|
|
116
|
+
...(notes ? { notes } : {}),
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
formatRow(row) {
|
|
122
|
+
return [
|
|
123
|
+
row.ts,
|
|
124
|
+
row.run_id,
|
|
125
|
+
row.scope,
|
|
126
|
+
row.key,
|
|
127
|
+
row.value,
|
|
128
|
+
row.model ?? '',
|
|
129
|
+
row.prompt_hash ?? '',
|
|
130
|
+
row.notes ?? '',
|
|
131
|
+
]
|
|
132
|
+
.map((value) => cleanCell(value))
|
|
133
|
+
.join('\t');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function cleanCell(value) {
|
|
137
|
+
return String(value).replace(/\r?\n/g, ' ').replace(/\t/g, ' ');
|
|
138
|
+
}
|
|
139
|
+
function isNodeError(error) {
|
|
140
|
+
return error instanceof Error && 'code' in error;
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=results-tsv.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
function stripQuotes(s) {
|
|
4
|
+
return s
|
|
5
|
+
.replace(/"(?:\\.|[^"\\])*"/g, '""')
|
|
6
|
+
.replace(/'(?:\\.|[^'\\])*'/g, "''");
|
|
7
|
+
}
|
|
8
|
+
function hasRedirect(stripped) {
|
|
9
|
+
const noLogical = stripped.replace(/\|\|/g, ' ').replace(/&&/g, ' ');
|
|
10
|
+
if (/>\s*\S/.test(noLogical))
|
|
11
|
+
return true;
|
|
12
|
+
if (/2>&1/.test(noLogical))
|
|
13
|
+
return true;
|
|
14
|
+
if (/\|\s*tee\b/.test(noLogical))
|
|
15
|
+
return true;
|
|
16
|
+
if (/\|/.test(noLogical))
|
|
17
|
+
return true;
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
function isCompound(stripped) {
|
|
21
|
+
return /&&|\|\||;|\|/.test(stripped);
|
|
22
|
+
}
|
|
23
|
+
function shellQuote(s) {
|
|
24
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
25
|
+
}
|
|
26
|
+
function genLogPath(logDir) {
|
|
27
|
+
const rand = randomBytes(6).toString('hex');
|
|
28
|
+
return join(logDir, `cmd-${rand}.log`);
|
|
29
|
+
}
|
|
30
|
+
export function redirectCommand(command, options) {
|
|
31
|
+
if (!options.workspaceRoot || typeof options.workspaceRoot !== 'string') {
|
|
32
|
+
throw new Error('redirectCommand: workspaceRoot is required');
|
|
33
|
+
}
|
|
34
|
+
const logDir = options.logDir ?? join(options.workspaceRoot, '.pugi', 'logs');
|
|
35
|
+
const logPath = genLogPath(logDir);
|
|
36
|
+
const trimmed = command.trim();
|
|
37
|
+
if (trimmed.length === 0) {
|
|
38
|
+
return { command: trimmed, logPath, alreadyRedirected: false };
|
|
39
|
+
}
|
|
40
|
+
const stripped = stripQuotes(trimmed);
|
|
41
|
+
const already = hasRedirect(stripped);
|
|
42
|
+
if (already && !options.forceRedirect) {
|
|
43
|
+
return { command, logPath, alreadyRedirected: true };
|
|
44
|
+
}
|
|
45
|
+
const compound = isCompound(stripped);
|
|
46
|
+
const transformed = compound
|
|
47
|
+
? `sh -c ${shellQuote(trimmed)} > ${logPath} 2>&1`
|
|
48
|
+
: `${trimmed} > ${logPath} 2>&1`;
|
|
49
|
+
return { command: transformed, logPath, alreadyRedirected: already };
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=stdout-redirect.js.map
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-process LSP client cache — .
|
|
3
|
+
*
|
|
4
|
+
* The `runtime/commands/lsp.ts` CLI surface spawns one LSP server
|
|
5
|
+
* per invocation and stops it at the end. That is correct for the
|
|
6
|
+
* one-shot `pugi lsp hover ...` shape but wrong for L15's
|
|
7
|
+
* post-edit auto-diagnostics: every successful `edit`/`write` would
|
|
8
|
+
* otherwise pay the ~2-3s cold-start of `typescript-language-server`,
|
|
9
|
+
* which is unusable inside an agent loop.
|
|
10
|
+
*
|
|
11
|
+
* This module owns a singleton map keyed by `LspLanguage` with lazy
|
|
12
|
+
* initialization (`getOrStart`). The first edit of a TS file in a
|
|
13
|
+
* session pays cold-start; every subsequent edit of any TS/TSX file
|
|
14
|
+
* in the same workspace reuses the warm client.
|
|
15
|
+
*
|
|
16
|
+
* Lifecycle:
|
|
17
|
+
* - `getOrStart(lang, cwd)` — spawn if missing, return cached otherwise.
|
|
18
|
+
* - `stopAll()` — graceful shutdown of every cached client. Called from
|
|
19
|
+
* `runCli` exit so a Ctrl-C never leaves zombie LSP processes behind.
|
|
20
|
+
* - `reset()` — test-only escape hatch, drops the cache without
|
|
21
|
+
* touching child processes (specs inject stubs that own their own
|
|
22
|
+
* lifecycle).
|
|
23
|
+
*
|
|
24
|
+
* Failure handling: a startup failure is NOT cached. The next call
|
|
25
|
+
* tries again. This keeps the cache from poisoning a session when the
|
|
26
|
+
* operator installs the missing LSP binary mid-session and re-edits.
|
|
27
|
+
*
|
|
28
|
+
* Brand voice: ASCII only, no emoji, no banned words.
|
|
29
|
+
*/
|
|
30
|
+
import { isLspLanguageDisabled, startLspClient, } from './client.js';
|
|
31
|
+
const cache = new Map();
|
|
32
|
+
/**
|
|
33
|
+
* Return a warm client for `lang`, starting one if needed. The
|
|
34
|
+
* workspace `cwd` is captured at cache-insert time; if a subsequent
|
|
35
|
+
* call asks for the same language with a different `cwd` we tear
|
|
36
|
+
* down the old client and start a fresh one. This handles the
|
|
37
|
+
* agent-worktree case where the same process hops between workspace
|
|
38
|
+
* roots inside one Node lifetime.
|
|
39
|
+
*/
|
|
40
|
+
export async function getOrStartLspClient(lang, opts) {
|
|
41
|
+
// β7 L9: respect the per-language disable toggle BEFORE we attempt to
|
|
42
|
+
// spawn. The check is cheap and keeps the disabled path from paying
|
|
43
|
+
// the `npx --yes` warmup cost on first use.
|
|
44
|
+
if (isLspLanguageDisabled(lang, opts.lspSettings)) {
|
|
45
|
+
return {
|
|
46
|
+
ok: false,
|
|
47
|
+
reason: 'lsp_disabled',
|
|
48
|
+
detail: `${lang} is disabled via .pugi/settings.json::lsp`,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const existing = cache.get(lang);
|
|
52
|
+
if (existing && existing.cwd === opts.cwd) {
|
|
53
|
+
return { ok: true, client: existing.client };
|
|
54
|
+
}
|
|
55
|
+
if (existing && existing.cwd !== opts.cwd) {
|
|
56
|
+
// Workspace switched — stop the old client and fall through to spawn.
|
|
57
|
+
try {
|
|
58
|
+
await existing.client.stop();
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// best effort; stop() is idempotent + swallow-safe
|
|
62
|
+
}
|
|
63
|
+
cache.delete(lang);
|
|
64
|
+
}
|
|
65
|
+
const result = await startLspClient(lang, opts);
|
|
66
|
+
if (!result.ok) {
|
|
67
|
+
return { ok: false, reason: result.reason, detail: result.detail };
|
|
68
|
+
}
|
|
69
|
+
cache.set(lang, { client: result.value, cwd: opts.cwd });
|
|
70
|
+
return { ok: true, client: result.value };
|
|
71
|
+
}
|
|
72
|
+
/** Look up the cached client without starting one. Returns undefined when missing. */
|
|
73
|
+
export function peekLspClient(lang) {
|
|
74
|
+
return cache.get(lang)?.client;
|
|
75
|
+
}
|
|
76
|
+
/** Snapshot of currently-cached languages — used by `pugi lsp status` debug output. */
|
|
77
|
+
export function listCachedLanguages() {
|
|
78
|
+
return Array.from(cache.keys());
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Stop every cached client and clear the cache. Called from `runCli`
|
|
82
|
+
* exit and from specs that own the lifecycle of their stub servers.
|
|
83
|
+
*/
|
|
84
|
+
export async function stopAllLspClients() {
|
|
85
|
+
const snapshot = Array.from(cache.values());
|
|
86
|
+
cache.clear();
|
|
87
|
+
await Promise.all(snapshot.map(async (entry) => {
|
|
88
|
+
try {
|
|
89
|
+
await entry.client.stop();
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// best effort — shutting down anyway
|
|
93
|
+
}
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Test-only: drop the cache map WITHOUT calling stop on the children.
|
|
98
|
+
* Specs that inject stub servers manage the stub lifecycle themselves;
|
|
99
|
+
* this lets a spec swap a stub mid-test without the cache holding a
|
|
100
|
+
* stale reference to a torn-down process.
|
|
101
|
+
*/
|
|
102
|
+
export function __resetLspCacheForTests() {
|
|
103
|
+
cache.clear();
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=cache.js.map
|