@pugi/cli 0.1.0-beta.5 → 0.1.0-beta.50
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/THIRD_PARTY_NOTICES.md +40 -0
- package/assets/pugi-mascot.ansi +15 -25
- package/assets/pugi-prozr2-mascot.ansi +9 -0
- package/bin/run.js +33 -1
- package/dist/commands/jobs-watch.js +201 -0
- package/dist/commands/jobs.js +15 -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/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/auth/ensure-authenticated.js +129 -0
- package/dist/core/auth/env-provider.js +238 -0
- 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-classifier.js +400 -4
- package/dist/core/checkpoint/resumer.js +149 -0
- package/dist/core/checkpoint/rewinder.js +291 -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/diff-capture.js +112 -3
- package/dist/core/context/index.js +7 -0
- package/dist/core/context/markdown-traverse.js +255 -0
- package/dist/core/cost/rate-card.js +129 -0
- package/dist/core/cost/tracker.js +221 -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 +86 -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/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/dispatch.js +218 -2
- package/dist/core/edits/journal.js +199 -0
- package/dist/core/edits/layer-d-ast.js +557 -14
- package/dist/core/edits/verify-hook.js +273 -0
- package/dist/core/edits/worktree.js +322 -0
- package/dist/core/engine/anvil-client.js +115 -5
- package/dist/core/engine/budgets.js +98 -0
- package/dist/core/engine/context-prefix.js +155 -0
- package/dist/core/engine/intent.js +260 -0
- package/dist/core/engine/native-pugi.js +860 -211
- package/dist/core/engine/prompts.js +88 -2
- package/dist/core/engine/strip-internal-fields.js +124 -0
- package/dist/core/engine/tool-bridge.js +1045 -36
- 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/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/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/mcp/client.js +75 -6
- 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 +24 -2
- package/dist/core/mcp/server-tools.js +219 -0
- package/dist/core/mcp/server.js +397 -0
- package/dist/core/memory/dual-write.js +416 -0
- package/dist/core/memory/phase1-kinds.js +20 -0
- package/dist/core/memory-sync/queue.js +158 -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 +284 -2
- package/dist/core/permissions/auto-classifier.js +124 -0
- package/dist/core/permissions/circuit-breaker.js +83 -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/state.js +241 -0
- package/dist/core/permissions/tool-class.js +93 -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/pugi-md/context-injector.js +76 -0
- package/dist/core/pugi-md/walk-up.js +207 -0
- package/dist/core/release-notes/parser.js +241 -0
- package/dist/core/release-notes/state.js +116 -0
- package/dist/core/repl/history.js +11 -1
- package/dist/core/repl/model-pricing.js +135 -0
- package/dist/core/repl/session.js +1897 -37
- package/dist/core/repl/slash-commands.js +430 -15
- package/dist/core/repl/store/session-store.js +31 -2
- package/dist/core/repl/workspace-context.js +22 -0
- 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/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/session.js +92 -0
- package/dist/core/settings.js +80 -0
- 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/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/subagents/dispatcher-real.js +600 -0
- package/dist/core/subagents/dispatcher.js +113 -24
- package/dist/core/subagents/index.js +18 -5
- 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/transport/version-interceptor.js +166 -0
- package/dist/core/vim/keymap.js +288 -0
- package/dist/core/vim/state.js +92 -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 +3241 -343
- 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/cost.js +199 -0
- package/dist/runtime/commands/delegate.js +242 -11
- package/dist/runtime/commands/dispatch.js +126 -0
- package/dist/runtime/commands/doctor.js +412 -0
- package/dist/runtime/commands/feedback.js +184 -0
- package/dist/runtime/commands/hooks.js +184 -0
- package/dist/runtime/commands/lsp.js +368 -0
- package/dist/runtime/commands/mcp.js +879 -0
- package/dist/runtime/commands/memory.js +508 -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/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 +17 -2
- package/dist/runtime/commands/rewind.js +333 -0
- package/dist/runtime/commands/sessions.js +163 -0
- package/dist/runtime/commands/share.js +316 -0
- 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 +32 -0
- 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/version.js +65 -0
- package/dist/tools/agent-tool.js +229 -0
- package/dist/tools/apply-patch.js +556 -0
- package/dist/tools/ask-user-question.js +213 -0
- package/dist/tools/ask-user.js +115 -0
- package/dist/tools/bash.js +203 -4
- package/dist/tools/file-tools.js +85 -14
- 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 +51 -0
- package/dist/tools/skill-tool.js +96 -0
- package/dist/tools/tasks.js +208 -0
- package/dist/tools/todo-write.js +184 -0
- package/dist/tools/web-fetch.js +147 -2
- package/dist/tools/web-search.js +458 -0
- package/dist/tui/agent-progress-card.js +111 -0
- package/dist/tui/agent-tree.js +10 -0
- package/dist/tui/ask-modal.js +2 -2
- package/dist/tui/ask-user-question-prompt.js +192 -0
- package/dist/tui/compact-banner.js +81 -0
- package/dist/tui/conversation-pane.js +82 -8
- package/dist/tui/cost-table.js +111 -0
- package/dist/tui/doctor-table.js +46 -0
- package/dist/tui/feedback-prompt.js +156 -0
- package/dist/tui/input-box.js +218 -3
- package/dist/tui/markdown-render.js +4 -4
- 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 +313 -35
- package/dist/tui/repl-splash-art.js +1 -1
- package/dist/tui/repl-splash-mascot.js +32 -8
- package/dist/tui/repl-splash.js +2 -2
- package/dist/tui/repl.js +85 -5
- package/dist/tui/splash.js +1 -1
- package/dist/tui/status-bar.js +94 -16
- 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 +52 -3
- 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/docs/examples/codegraph.mcp.json +10 -0
- package/package.json +12 -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,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language-from-extension detection — Leak L15.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for "given a file path, which `LspLanguage`
|
|
5
|
+
* slug do we route to". The α7.7 `runtime/commands/lsp.ts` shipped its
|
|
6
|
+
* own inline `inferLanguage` switch; L15 (post-edit auto-diagnostics)
|
|
7
|
+
* needs the same lookup from `core/engine/tool-bridge.ts`, so we lift
|
|
8
|
+
* the table into a dedicated module to avoid a second copy drifting
|
|
9
|
+
* out of sync.
|
|
10
|
+
*
|
|
11
|
+
* Returning `undefined` is the calling code's signal to silently skip
|
|
12
|
+
* LSP — an unsupported extension is NOT an error, it just means "no
|
|
13
|
+
* diagnostics for this file". The tool-bridge hook treats this as a
|
|
14
|
+
* no-op envelope tail.
|
|
15
|
+
*
|
|
16
|
+
* Adding a new language requires THREE coordinated changes:
|
|
17
|
+
* 1. Add the `LspLanguage` slug + server descriptor in `client.ts`.
|
|
18
|
+
* 2. Map its extensions here.
|
|
19
|
+
* 3. Add a `lsp-language-matrix` spec row exercising the new ext.
|
|
20
|
+
*
|
|
21
|
+
* Brand voice: ASCII only, no emoji, no banned words.
|
|
22
|
+
*/
|
|
23
|
+
import { extname } from 'node:path';
|
|
24
|
+
/**
|
|
25
|
+
* Lower-case extension (including the dot) → LSP language slug.
|
|
26
|
+
* Mirror of the switch in `runtime/commands/lsp.ts::inferLanguage`.
|
|
27
|
+
* The table form lets tests assert coverage and lets new languages
|
|
28
|
+
* land with one edit instead of two.
|
|
29
|
+
*/
|
|
30
|
+
export const EXTENSION_TO_LANGUAGE = {
|
|
31
|
+
'.ts': 'ts',
|
|
32
|
+
'.tsx': 'ts',
|
|
33
|
+
'.mts': 'ts',
|
|
34
|
+
'.cts': 'ts',
|
|
35
|
+
'.js': 'js',
|
|
36
|
+
'.jsx': 'js',
|
|
37
|
+
'.mjs': 'js',
|
|
38
|
+
'.cjs': 'js',
|
|
39
|
+
'.py': 'py',
|
|
40
|
+
'.pyi': 'py',
|
|
41
|
+
'.go': 'go',
|
|
42
|
+
'.rs': 'rust',
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Infer the `LspLanguage` for a workspace-relative or absolute path.
|
|
46
|
+
* Returns `undefined` for unmapped extensions — the caller decides
|
|
47
|
+
* whether that is silently skipped (post-edit hook) or surfaced as
|
|
48
|
+
* `language_unsupported` (`pugi lsp` CLI).
|
|
49
|
+
*/
|
|
50
|
+
export function languageForFile(file) {
|
|
51
|
+
const ext = extname(file).toLowerCase();
|
|
52
|
+
if (!ext)
|
|
53
|
+
return undefined;
|
|
54
|
+
return EXTENSION_TO_LANGUAGE[ext];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Return every extension currently mapped to the given language.
|
|
58
|
+
* Used by the matrix spec to assert coverage without re-typing the
|
|
59
|
+
* extension list.
|
|
60
|
+
*/
|
|
61
|
+
export function extensionsForLanguage(lang) {
|
|
62
|
+
return Object.entries(EXTENSION_TO_LANGUAGE)
|
|
63
|
+
.filter(([, value]) => value === lang)
|
|
64
|
+
.map(([ext]) => ext);
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=language-detect.js.map
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-edit diagnostics — Leak L15.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code's leak intel surfaced this pattern: after a `FileEdit` /
|
|
5
|
+
* `Write` tool call lands, an LSP diagnostic pass runs on the touched
|
|
6
|
+
* file and the result is appended to the tool envelope before the
|
|
7
|
+
* model sees it. The model then self-corrects in the same turn —
|
|
8
|
+
* "TS2304: Cannot find name 'undef'" comes back, the model fixes the
|
|
9
|
+
* typo in the next tool call, no operator round-trip needed.
|
|
10
|
+
*
|
|
11
|
+
* This module is the Pugi side of that pattern:
|
|
12
|
+
*
|
|
13
|
+
* 1. The tool-bridge calls `runPostEditDiagnostics(path, ctx)` after
|
|
14
|
+
* a successful `edit` / `write` / `multi_edit`.
|
|
15
|
+
* 2. We infer the language from the extension (`language-detect`).
|
|
16
|
+
* Unsupported extension → `{ skip: true }` and the bridge appends
|
|
17
|
+
* nothing.
|
|
18
|
+
* 3. We borrow (or lazily start) the per-language cached client
|
|
19
|
+
* from `cache.ts`. A spawn failure → `{ skip: true }` and the
|
|
20
|
+
* envelope stays clean. Silence on failure is intentional: an
|
|
21
|
+
* operator who has not installed `typescript-language-server`
|
|
22
|
+
* should not see an LSP nag on every edit.
|
|
23
|
+
* 4. We pull diagnostics with a hard 5s ceiling. A timeout logs a
|
|
24
|
+
* warning on stderr (gated on `PUGI_LSP_DEBUG=1`) and skips —
|
|
25
|
+
* the envelope is never blocked on LSP.
|
|
26
|
+
* 5. We format the surviving diagnostics into a readable tail
|
|
27
|
+
* mirroring the leak format:
|
|
28
|
+
*
|
|
29
|
+
* LSP DIAGNOSTICS (typescript):
|
|
30
|
+
* foo.ts:42:5 error TS2304: Cannot find name 'undef'.
|
|
31
|
+
* foo.ts:51:1 warn TS6133: 'unused' is declared.
|
|
32
|
+
*
|
|
33
|
+
* The bridge concatenates this tail onto its existing `wrote ...` /
|
|
34
|
+
* `edited ...` body with a single newline separator. When there are
|
|
35
|
+
* zero diagnostics we return `{ skip: true }` so the existing body
|
|
36
|
+
* is unchanged — the "no news is good news" path stays terse.
|
|
37
|
+
*
|
|
38
|
+
* Brand voice: ASCII only, no emoji, no banned words.
|
|
39
|
+
*/
|
|
40
|
+
import { isAbsolute, relative, resolve } from 'node:path';
|
|
41
|
+
import { getOrStartLspClient } from './cache.js';
|
|
42
|
+
import { languageForFile } from './language-detect.js';
|
|
43
|
+
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
44
|
+
/**
|
|
45
|
+
* Hard cap on how many diagnostics we surface to the model. A file
|
|
46
|
+
* with 200 errors after a broken bulk edit would otherwise blow the
|
|
47
|
+
* context window; the model can re-run `pugi lsp diagnostics` if
|
|
48
|
+
* it needs the full list.
|
|
49
|
+
*/
|
|
50
|
+
const MAX_DIAGNOSTICS = 25;
|
|
51
|
+
export async function runPostEditDiagnostics(filePath, opts) {
|
|
52
|
+
const lang = languageForFile(filePath);
|
|
53
|
+
if (!lang) {
|
|
54
|
+
return { skip: true, reason: 'unsupported_language' };
|
|
55
|
+
}
|
|
56
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
57
|
+
const clientResult = await loadClient(lang, opts);
|
|
58
|
+
if (!clientResult.ok) {
|
|
59
|
+
return { skip: true, reason: mapStartFailure(clientResult.reason) };
|
|
60
|
+
}
|
|
61
|
+
// Run diagnostics with a hard timeout. The underlying LspClient has
|
|
62
|
+
// its own per-request timeout (5s default) but a slow handshake
|
|
63
|
+
// can blow past it; we belt-and-suspenders here so the agent loop
|
|
64
|
+
// never blocks on LSP.
|
|
65
|
+
const relPath = toWorkspaceRelative(filePath, opts.cwd);
|
|
66
|
+
const diagnosticsPromise = clientResult.client.diagnostics(relPath);
|
|
67
|
+
let timer;
|
|
68
|
+
const timeoutPromise = new Promise((resolveFn) => {
|
|
69
|
+
timer = setTimeout(() => resolveFn({ timedOut: true }), timeoutMs);
|
|
70
|
+
timer.unref();
|
|
71
|
+
});
|
|
72
|
+
const race = await Promise.race([
|
|
73
|
+
diagnosticsPromise.then((value) => ({ timedOut: false, value })),
|
|
74
|
+
timeoutPromise,
|
|
75
|
+
]);
|
|
76
|
+
if (timer)
|
|
77
|
+
clearTimeout(timer);
|
|
78
|
+
if (race.timedOut) {
|
|
79
|
+
const writeFn = opts.debugWrite ?? ((line) => {
|
|
80
|
+
if (process.env.PUGI_LSP_DEBUG === '1')
|
|
81
|
+
process.stderr.write(`${line}\n`);
|
|
82
|
+
});
|
|
83
|
+
writeFn(`[pugi-lsp] post-edit diagnostics for ${relPath} timed out after ${timeoutMs}ms (lang=${lang})`);
|
|
84
|
+
return { skip: true, reason: 'timeout' };
|
|
85
|
+
}
|
|
86
|
+
const diag = race.value;
|
|
87
|
+
if (!diag.ok) {
|
|
88
|
+
return { skip: true, reason: 'lsp_error' };
|
|
89
|
+
}
|
|
90
|
+
if (diag.value.length === 0) {
|
|
91
|
+
return { skip: true, reason: 'no_diagnostics' };
|
|
92
|
+
}
|
|
93
|
+
const tail = formatDiagnosticsTail(relPath, lang, diag.value);
|
|
94
|
+
return { skip: false, tail, count: diag.value.length, language: lang };
|
|
95
|
+
}
|
|
96
|
+
async function loadClient(lang, opts) {
|
|
97
|
+
if (opts.clientLoader) {
|
|
98
|
+
return opts.clientLoader(lang);
|
|
99
|
+
}
|
|
100
|
+
const { cwd, timeoutMs, clientLoader: _ignoredA, debugWrite: _ignoredB, ...rest } = opts;
|
|
101
|
+
const result = await getOrStartLspClient(lang, { cwd, ...rest });
|
|
102
|
+
if (!result.ok) {
|
|
103
|
+
return { ok: false, reason: result.reason, detail: result.detail };
|
|
104
|
+
}
|
|
105
|
+
return { ok: true, client: result.client };
|
|
106
|
+
}
|
|
107
|
+
function mapStartFailure(reason) {
|
|
108
|
+
if (reason === 'lsp_unavailable' || reason === 'language_unsupported')
|
|
109
|
+
return 'lsp_unavailable';
|
|
110
|
+
if (reason === 'lsp_disabled')
|
|
111
|
+
return 'lsp_disabled';
|
|
112
|
+
return 'lsp_error';
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Convert an absolute or workspace-relative path into the form the
|
|
116
|
+
* LSP client expects — same shape as `runtime/commands/lsp.ts` uses.
|
|
117
|
+
*/
|
|
118
|
+
function toWorkspaceRelative(filePath, cwd) {
|
|
119
|
+
if (!isAbsolute(filePath))
|
|
120
|
+
return filePath;
|
|
121
|
+
const rel = relative(cwd, resolve(cwd, filePath));
|
|
122
|
+
return rel || filePath;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Format diagnostics into the leak-shaped envelope tail. Pure function
|
|
126
|
+
* exported for unit tests to assert the line format independent of
|
|
127
|
+
* any LSP plumbing.
|
|
128
|
+
*/
|
|
129
|
+
export function formatDiagnosticsTail(relPath, lang, diagnostics) {
|
|
130
|
+
const visible = diagnostics.slice(0, MAX_DIAGNOSTICS);
|
|
131
|
+
const truncated = diagnostics.length > visible.length;
|
|
132
|
+
const lines = [`LSP DIAGNOSTICS (${LANGUAGE_LABELS[lang]}):`];
|
|
133
|
+
for (const diag of visible) {
|
|
134
|
+
const line = diag.range.start.line + 1; // LSP is zero-based; humans expect 1-based.
|
|
135
|
+
const col = diag.range.start.character + 1;
|
|
136
|
+
const severity = SEVERITY_LABELS[diag.severityLabel];
|
|
137
|
+
const code = diag.code !== undefined && diag.code !== '' ? ` ${diag.code}` : '';
|
|
138
|
+
const source = diag.source ? `${diag.source}` : '';
|
|
139
|
+
const head = source ? `${severity}${code} (${source}):` : `${severity}${code}:`;
|
|
140
|
+
lines.push(` ${relPath}:${line}:${col} ${head} ${diag.message}`);
|
|
141
|
+
}
|
|
142
|
+
if (truncated) {
|
|
143
|
+
lines.push(` ... ${diagnostics.length - visible.length} more diagnostic(s) — re-run pugi lsp diagnostics ${relPath} for the full list`);
|
|
144
|
+
}
|
|
145
|
+
return lines.join('\n');
|
|
146
|
+
}
|
|
147
|
+
const LANGUAGE_LABELS = {
|
|
148
|
+
ts: 'typescript',
|
|
149
|
+
js: 'javascript',
|
|
150
|
+
py: 'python',
|
|
151
|
+
go: 'go',
|
|
152
|
+
rust: 'rust',
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Map LSP severity label → the short token the leak envelope uses.
|
|
156
|
+
* "warn" is shorter than "warning" and matches Claude Code's leak
|
|
157
|
+
* verbatim; the rest mirror LSP terminology.
|
|
158
|
+
*/
|
|
159
|
+
const SEVERITY_LABELS = {
|
|
160
|
+
error: 'error',
|
|
161
|
+
warning: 'warn ',
|
|
162
|
+
info: 'info ',
|
|
163
|
+
hint: 'hint ',
|
|
164
|
+
};
|
|
165
|
+
/** Test-only surface so specs can poke the pure helpers without LSP. */
|
|
166
|
+
export const __test__ = {
|
|
167
|
+
formatDiagnosticsTail,
|
|
168
|
+
MAX_DIAGNOSTICS,
|
|
169
|
+
DEFAULT_TIMEOUT_MS,
|
|
170
|
+
};
|
|
171
|
+
//# sourceMappingURL=post-edit-diagnostics.js.map
|
package/dist/core/mcp/client.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { createWriteStream } from 'node:fs';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
/**
|
|
4
5
|
* Minimal JSON-RPC 2.0 over stdio MCP client for Pugi CLI M1.
|
|
@@ -34,6 +35,15 @@ import { z } from 'zod';
|
|
|
34
35
|
*/
|
|
35
36
|
export const mcpServerConfigSchema = z.object({
|
|
36
37
|
command: z.string().min(1),
|
|
38
|
+
/**
|
|
39
|
+
* β4 r2 P1 #1 — operator's literal `command` argument as supplied to
|
|
40
|
+
* `pugi mcp install`. `command` above is now the RESOLVED absolute path
|
|
41
|
+
* we actually spawn. `originalCommand` is preserved for display/audit
|
|
42
|
+
* (`pugi mcp list`, audit logs) so operators can still see how they
|
|
43
|
+
* typed the install. Optional because pre-r2 mcp.json files do not
|
|
44
|
+
* carry the field — the loader treats absence as "use command directly".
|
|
45
|
+
*/
|
|
46
|
+
originalCommand: z.string().min(1).optional(),
|
|
37
47
|
args: z.array(z.string()).default([]),
|
|
38
48
|
env: z.record(z.string()).default({}),
|
|
39
49
|
/**
|
|
@@ -70,6 +80,22 @@ export async function connect(serverName, config, options = {}) {
|
|
|
70
80
|
env: { ...process.env, ...config.env },
|
|
71
81
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
72
82
|
});
|
|
83
|
+
// L13: optional stderr → log file. We open the stream lazily so a bad
|
|
84
|
+
// path surfaces synchronously to the caller instead of getting buried
|
|
85
|
+
// in the child's stderr handler. `mkdir -p` of the parent dir is the
|
|
86
|
+
// caller's responsibility (the registry does it once per session).
|
|
87
|
+
let logStream;
|
|
88
|
+
if (options.logFile) {
|
|
89
|
+
try {
|
|
90
|
+
logStream = createWriteStream(options.logFile, { flags: 'a' });
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Log directory missing or unwritable — degrade silently rather
|
|
94
|
+
// than refuse the handshake. The operator still gets the same
|
|
95
|
+
// surface they had pre-L13 (stderr dropped on the floor).
|
|
96
|
+
logStream = undefined;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
73
99
|
const connection = {
|
|
74
100
|
serverName,
|
|
75
101
|
child,
|
|
@@ -77,6 +103,8 @@ export async function connect(serverName, config, options = {}) {
|
|
|
77
103
|
pending: new Map(),
|
|
78
104
|
nextId: 1,
|
|
79
105
|
closed: false,
|
|
106
|
+
...(logStream ? { logStream } : {}),
|
|
107
|
+
startedAt: Date.now(),
|
|
80
108
|
};
|
|
81
109
|
// Attach the spawn-error handler BEFORE the reader: ENOENT and similar
|
|
82
110
|
// are emitted asynchronously on the next tick, and without this
|
|
@@ -184,11 +212,25 @@ export async function callTool(connection, name, args, options = {}) {
|
|
|
184
212
|
* SIGKILL if the child has not exited. Safe to call multiple times.
|
|
185
213
|
*/
|
|
186
214
|
export async function disconnect(connection) {
|
|
187
|
-
|
|
215
|
+
const closeLogStream = () => {
|
|
216
|
+
const stream = connection.logStream;
|
|
217
|
+
if (!stream)
|
|
218
|
+
return;
|
|
219
|
+
try {
|
|
220
|
+
stream.end();
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
// Best-effort — stream may already be destroyed.
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
if (connection.closed) {
|
|
227
|
+
closeLogStream();
|
|
188
228
|
return;
|
|
229
|
+
}
|
|
189
230
|
const { child } = connection;
|
|
190
231
|
if (child.exitCode !== null || child.killed) {
|
|
191
232
|
connection.closed = true;
|
|
233
|
+
closeLogStream();
|
|
192
234
|
return;
|
|
193
235
|
}
|
|
194
236
|
return new Promise((resolveDone) => {
|
|
@@ -198,6 +240,7 @@ export async function disconnect(connection) {
|
|
|
198
240
|
return;
|
|
199
241
|
settled = true;
|
|
200
242
|
connection.closed = true;
|
|
243
|
+
closeLogStream();
|
|
201
244
|
resolveDone();
|
|
202
245
|
};
|
|
203
246
|
child.once('exit', settle);
|
|
@@ -223,6 +266,22 @@ export async function disconnect(connection) {
|
|
|
223
266
|
}, SHUTDOWN_GRACE_MS);
|
|
224
267
|
});
|
|
225
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* L13: cheap liveness probe for `pugi mcp doctor`. True when the child
|
|
271
|
+
* process is still running AND the connection has not been torn down.
|
|
272
|
+
* Does NOT issue a JSON-RPC call — that would race with in-flight tool
|
|
273
|
+
* dispatch and the doctor surface is read-only.
|
|
274
|
+
*/
|
|
275
|
+
export function isAlive(connection) {
|
|
276
|
+
if (connection.closed)
|
|
277
|
+
return false;
|
|
278
|
+
const { child } = connection;
|
|
279
|
+
if (child.killed)
|
|
280
|
+
return false;
|
|
281
|
+
if (child.exitCode !== null)
|
|
282
|
+
return false;
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
226
285
|
function writeFrame(connection, message) {
|
|
227
286
|
if (connection.closed) {
|
|
228
287
|
throw new Error(`mcp server "${connection.serverName}" connection is closed`);
|
|
@@ -274,11 +333,21 @@ function attachReader(connection) {
|
|
|
274
333
|
newlineIndex = buffer.indexOf('\n');
|
|
275
334
|
}
|
|
276
335
|
});
|
|
277
|
-
// stderr is captured
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
//
|
|
281
|
-
connection.child.stderr.on('data', () => {
|
|
336
|
+
// stderr is captured and (when `connect({ logFile })` was set) mirrored
|
|
337
|
+
// to a per-server log file under `.pugi/logs/`. Operators can tail the
|
|
338
|
+
// file via `pugi mcp logs <server>`. Default sink remains "drop on the
|
|
339
|
+
// floor" so the CLI stdout stays pure JSON envelope output.
|
|
340
|
+
connection.child.stderr.on('data', (chunk) => {
|
|
341
|
+
const stream = connection.logStream;
|
|
342
|
+
if (!stream || stream.destroyed)
|
|
343
|
+
return;
|
|
344
|
+
try {
|
|
345
|
+
stream.write(typeof chunk === 'string' ? chunk : chunk);
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
// Disk full / fd revoked — drop silently. Mirroring is best-effort.
|
|
349
|
+
}
|
|
350
|
+
});
|
|
282
351
|
}
|
|
283
352
|
function handleLine(connection, line) {
|
|
284
353
|
let parsed;
|