@pugi/cli 0.1.0-beta.99 → 1.0.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +11 -191
- package/bin/pugi +8 -0
- package/package.json +15 -71
- package/postinstall.mjs +31 -0
- package/CHANGELOG.md +0 -132
- package/THIRD_PARTY_NOTICES.md +0 -40
- package/assets/pugi-mascot.ansi +0 -16
- package/assets/pugi-prozr2-mascot.ansi +0 -9
- package/bin/run.js +0 -34
- package/dist/commands/deploy.js +0 -439
- package/dist/commands/flatten.js +0 -191
- package/dist/commands/jobs-watch.js +0 -201
- package/dist/commands/jobs.js +0 -260
- package/dist/commands/retro.js +0 -210
- package/dist/commands/smoke.js +0 -133
- package/dist/core/agent-progress/cleanup.js +0 -134
- package/dist/core/agent-progress/schema.js +0 -144
- package/dist/core/agent-progress/writer.js +0 -101
- package/dist/core/agents/adaptive-router.js +0 -330
- package/dist/core/agents/loader.js +0 -104
- package/dist/core/agents/query-decomposer.js +0 -297
- package/dist/core/agents/registry.js +0 -69
- package/dist/core/approvals/shortcut-resolver.js +0 -98
- package/dist/core/artifact-chain/dispatcher.js +0 -148
- package/dist/core/artifact-chain/exporter.js +0 -164
- package/dist/core/artifact-chain/state.js +0 -243
- package/dist/core/artifact-chain/steps.js +0 -169
- package/dist/core/ask-user/question.js +0 -92
- package/dist/core/audit/audit-trail.js +0 -275
- package/dist/core/auth/ensure-authenticated.js +0 -129
- package/dist/core/auth/env-provider.js +0 -238
- package/dist/core/auto-open-browser.js +0 -128
- package/dist/core/auto-update/channels.js +0 -122
- package/dist/core/auto-update/checker.js +0 -241
- package/dist/core/auto-update/state.js +0 -235
- package/dist/core/bare-mode/index.js +0 -107
- package/dist/core/bash/redirect.js +0 -281
- package/dist/core/bash-classifier.js +0 -1397
- package/dist/core/checkpoint/resumer.js +0 -149
- package/dist/core/checkpoint/rewinder.js +0 -291
- package/dist/core/checkpoints/shadow-git.js +0 -670
- package/dist/core/citations/parser.js +0 -109
- package/dist/core/classifier/yolo-classifier.js +0 -88
- package/dist/core/clipboard.js +0 -70
- package/dist/core/codegraph/decision-store.js +0 -248
- package/dist/core/codegraph/detect-repo.js +0 -459
- package/dist/core/codegraph/install.js +0 -134
- package/dist/core/codegraph/offer-hook.js +0 -220
- package/dist/core/compact/auto-trigger.js +0 -96
- package/dist/core/compact/buffer-rewriter.js +0 -115
- package/dist/core/compact/summarizer.js +0 -208
- package/dist/core/compact/token-counter.js +0 -108
- package/dist/core/consensus/anvil-fanout.js +0 -276
- package/dist/core/consensus/diff-capture.js +0 -491
- package/dist/core/consensus/rubric.js +0 -233
- package/dist/core/context/builder.js +0 -114
- package/dist/core/context/compaction-events.js +0 -99
- package/dist/core/context/compaction.js +0 -602
- package/dist/core/context/index.js +0 -28
- package/dist/core/context/invariants.js +0 -250
- package/dist/core/context/markdown-loader.js +0 -288
- package/dist/core/context/markdown-traverse.js +0 -255
- package/dist/core/context/pugiignore.js +0 -316
- package/dist/core/context/repo-skeleton.js +0 -533
- package/dist/core/context/tool-eviction.js +0 -55
- package/dist/core/context/watcher.js +0 -342
- package/dist/core/context/working-set.js +0 -165
- package/dist/core/coordinator/agent-tools.js +0 -77
- package/dist/core/coordinator/agent-toolset.js +0 -65
- package/dist/core/coordinator/fsm.js +0 -73
- package/dist/core/coordinator/mode-fsm.js +0 -70
- package/dist/core/cost/rate-card.js +0 -129
- package/dist/core/cost/tracker.js +0 -221
- package/dist/core/credentials.js +0 -355
- package/dist/core/cron/scheduler.js +0 -138
- package/dist/core/denial-tracking/index.js +0 -8
- package/dist/core/denial-tracking/state.js +0 -264
- package/dist/core/diagnostics/probe-runner.js +0 -93
- package/dist/core/diagnostics/probes/api.js +0 -46
- package/dist/core/diagnostics/probes/auth.js +0 -93
- package/dist/core/diagnostics/probes/bare-mode.js +0 -42
- package/dist/core/diagnostics/probes/cli-version.js +0 -127
- package/dist/core/diagnostics/probes/config.js +0 -72
- package/dist/core/diagnostics/probes/denial-tracking.js +0 -57
- package/dist/core/diagnostics/probes/disk.js +0 -81
- package/dist/core/diagnostics/probes/engine-live.js +0 -46
- package/dist/core/diagnostics/probes/git.js +0 -65
- package/dist/core/diagnostics/probes/hooks.js +0 -118
- package/dist/core/diagnostics/probes/mcp.js +0 -75
- package/dist/core/diagnostics/probes/node.js +0 -59
- package/dist/core/diagnostics/probes/pnpm.js +0 -36
- package/dist/core/diagnostics/probes/pugi-md.js +0 -89
- package/dist/core/diagnostics/probes/sandbox.js +0 -72
- package/dist/core/diagnostics/probes/session.js +0 -74
- package/dist/core/diagnostics/probes/status-snapshot.js +0 -488
- package/dist/core/diagnostics/probes/workspace.js +0 -63
- package/dist/core/diagnostics/types.js +0 -70
- package/dist/core/dispatch/cache-cleanup.js +0 -197
- package/dist/core/dispatch/cache-handoff.js +0 -295
- package/dist/core/edits/apply-patch-layer-e.js +0 -189
- package/dist/core/edits/dispatch.js +0 -511
- package/dist/core/edits/format-detector.js +0 -260
- package/dist/core/edits/format-matrix.js +0 -26
- package/dist/core/edits/fuzzy-ladder.js +0 -650
- package/dist/core/edits/index.js +0 -19
- package/dist/core/edits/journal.js +0 -199
- package/dist/core/edits/layer-a-apply.js +0 -217
- package/dist/core/edits/layer-a-fuzzy-apply.js +0 -198
- package/dist/core/edits/layer-b-apply.js +0 -211
- package/dist/core/edits/layer-c-apply.js +0 -160
- package/dist/core/edits/layer-d-ast.js +0 -572
- package/dist/core/edits/marker-parser.js +0 -401
- package/dist/core/edits/security-gate.js +0 -223
- package/dist/core/edits/verify-hook.js +0 -273
- package/dist/core/edits/worktree.js +0 -322
- package/dist/core/engine/adapter-runner.js +0 -8
- package/dist/core/engine/anvil-client.js +0 -344
- package/dist/core/engine/auto-compact.js +0 -179
- package/dist/core/engine/budgets.js +0 -195
- package/dist/core/engine/context-prefix.js +0 -155
- package/dist/core/engine/index.js +0 -12
- package/dist/core/engine/intensity.js +0 -163
- package/dist/core/engine/intent.js +0 -260
- package/dist/core/engine/native-pugi.js +0 -1616
- package/dist/core/engine/noop.js +0 -27
- package/dist/core/engine/prompts.js +0 -236
- package/dist/core/engine/strip-internal-fields.js +0 -124
- package/dist/core/engine/tool-bridge.js +0 -2173
- package/dist/core/engine/verification-patterns.js +0 -195
- package/dist/core/evaluation/golden-dataset.js +0 -293
- package/dist/core/feedback/queue.js +0 -177
- package/dist/core/feedback/submitter.js +0 -145
- package/dist/core/file-cache.js +0 -141
- package/dist/core/flatten/flatten-repo.js +0 -439
- package/dist/core/format/osc8-link.js +0 -28
- package/dist/core/hook-chains.js +0 -392
- package/dist/core/hooks/citation-verify-hook.js +0 -138
- package/dist/core/hooks/citation-verify.js +0 -112
- package/dist/core/hooks/events.js +0 -46
- package/dist/core/hooks/index.js +0 -15
- package/dist/core/hooks/registry.js +0 -216
- package/dist/core/hooks/runner.js +0 -236
- package/dist/core/hooks/v2/event-emitter.js +0 -115
- package/dist/core/hooks/v2/executor.js +0 -282
- package/dist/core/hooks/v2/index.js +0 -25
- package/dist/core/hooks/v2/lifecycle.js +0 -104
- package/dist/core/hooks/v2/loader.js +0 -216
- package/dist/core/hooks/v2/matcher.js +0 -125
- package/dist/core/hooks/v2/trust.js +0 -143
- package/dist/core/hooks/v2/types.js +0 -86
- package/dist/core/hooks/worktree-events.js +0 -158
- package/dist/core/hooks.js +0 -415
- package/dist/core/image/renderer.js +0 -71
- package/dist/core/index-store.js +0 -260
- package/dist/core/init/detector.js +0 -582
- package/dist/core/init/template-renderer.js +0 -242
- package/dist/core/jobs/registry.js +0 -462
- package/dist/core/ledger/results-tsv.js +0 -142
- package/dist/core/log-discipline/stdout-redirect.js +0 -51
- package/dist/core/lsp/cache.js +0 -105
- package/dist/core/lsp/client.js +0 -1229
- package/dist/core/lsp/language-detect.js +0 -66
- package/dist/core/lsp/post-edit-diagnostics.js +0 -171
- package/dist/core/lsp/server-detect.js +0 -173
- package/dist/core/lsp/symbol-cache.js +0 -162
- package/dist/core/lsp/symbol-tools.js +0 -664
- package/dist/core/mcp/client.js +0 -385
- package/dist/core/mcp/http-server.js +0 -553
- package/dist/core/mcp/orchestrator-config.js +0 -192
- package/dist/core/mcp/orchestrator-tools.js +0 -806
- package/dist/core/mcp/permission.js +0 -190
- package/dist/core/mcp/registry.js +0 -193
- package/dist/core/mcp/server-tools.js +0 -219
- package/dist/core/mcp/server.js +0 -397
- package/dist/core/mcp/trust.js +0 -91
- package/dist/core/memory/dual-write.js +0 -416
- package/dist/core/memory/passive-extract.js +0 -130
- package/dist/core/memory/phase1-kinds.js +0 -20
- package/dist/core/memory/secret-scanner.js +0 -304
- package/dist/core/memory-sync/queue.js +0 -170
- package/dist/core/metrics/extract.js +0 -113
- package/dist/core/modes/roo-modes.js +0 -68
- package/dist/core/onboarding/ensure-initialized.js +0 -133
- package/dist/core/onboarding/marker.js +0 -111
- package/dist/core/onboarding/telemetry-state.js +0 -108
- package/dist/core/output-style/presets.js +0 -176
- package/dist/core/output-style/state.js +0 -185
- package/dist/core/path-security.js +0 -345
- package/dist/core/permission.js +0 -369
- package/dist/core/permissions/auto-classifier.js +0 -124
- package/dist/core/permissions/bash-parser.js +0 -371
- package/dist/core/permissions/circuit-breaker.js +0 -83
- package/dist/core/permissions/constrained-edit.js +0 -91
- package/dist/core/permissions/gate.js +0 -278
- package/dist/core/permissions/index.js +0 -20
- package/dist/core/permissions/mode.js +0 -174
- package/dist/core/permissions/network-egress.js +0 -137
- package/dist/core/permissions/state.js +0 -241
- package/dist/core/permissions/tool-class.js +0 -107
- package/dist/core/plan-mode/ui-state.js +0 -51
- package/dist/core/plans/plan-artifact.js +0 -721
- package/dist/core/policy-limits/etag-store.js +0 -122
- package/dist/core/prd-check/parser.js +0 -215
- package/dist/core/prd-check/reporter.js +0 -127
- package/dist/core/prd-check/session-review.js +0 -557
- package/dist/core/prd-check/verifiers.js +0 -223
- package/dist/core/prompt-cache/client-cache.js +0 -99
- package/dist/core/prompts/assembly.js +0 -29
- package/dist/core/prompts/registry.js +0 -364
- package/dist/core/pugi-gitignore.js +0 -52
- package/dist/core/pugi-md/cc-compat-rules.js +0 -735
- package/dist/core/pugi-md/context-injector.js +0 -76
- package/dist/core/pugi-md/walk-up.js +0 -207
- package/dist/core/python/uv-installer.js +0 -270
- package/dist/core/python/uv-resolver.js +0 -83
- package/dist/core/rate-limit/narrator.js +0 -146
- package/dist/core/recipes/cli-types.js +0 -20
- package/dist/core/recipes/loader.js +0 -103
- package/dist/core/recipes/runner.js +0 -345
- package/dist/core/recipes/schema.js +0 -587
- package/dist/core/release-notes/parser.js +0 -241
- package/dist/core/release-notes/state.js +0 -116
- package/dist/core/repl/ask.js +0 -512
- package/dist/core/repl/cancellation.js +0 -98
- package/dist/core/repl/cap-warning.js +0 -91
- package/dist/core/repl/clipboard-read.js +0 -174
- package/dist/core/repl/dispatch-fsm.js +0 -220
- package/dist/core/repl/engine-bridge.js +0 -303
- package/dist/core/repl/history-search.js +0 -175
- package/dist/core/repl/history.js +0 -182
- package/dist/core/repl/kill-ring.js +0 -138
- package/dist/core/repl/model-pricing.js +0 -135
- package/dist/core/repl/privacy-banner.js +0 -71
- package/dist/core/repl/session.js +0 -4962
- package/dist/core/repl/slash-commands.js +0 -747
- package/dist/core/repl/store/index.js +0 -12
- package/dist/core/repl/store/jsonl-log.js +0 -321
- package/dist/core/repl/store/lockfile.js +0 -155
- package/dist/core/repl/store/session-store.js +0 -821
- package/dist/core/repl/store/types.js +0 -44
- package/dist/core/repl/store/uuid-v7.js +0 -68
- package/dist/core/repl/tool-route.js +0 -382
- package/dist/core/repl/workspace-context.js +0 -206
- package/dist/core/repo-map/build.js +0 -125
- package/dist/core/repo-map/cache.js +0 -185
- package/dist/core/repo-map/extractor.js +0 -254
- package/dist/core/repo-map/formatter.js +0 -145
- package/dist/core/repo-map/page-rank.js +0 -105
- package/dist/core/repo-map/scanner.js +0 -211
- package/dist/core/retro/git-collector.js +0 -251
- package/dist/core/retro/health-card.js +0 -25
- package/dist/core/retro/metrics.js +0 -342
- package/dist/core/retro/narrative.js +0 -249
- package/dist/core/retro/plane-collector.js +0 -274
- package/dist/core/retro/pr-issue-link.js +0 -65
- package/dist/core/retro/types.js +0 -16
- package/dist/core/retry-budget/budget.js +0 -284
- package/dist/core/retry-budget/index.js +0 -5
- package/dist/core/retry-budget/retry-cap.js +0 -74
- package/dist/core/routing/lead-worker.js +0 -43
- package/dist/core/routing/pre-flight-estimator.js +0 -108
- package/dist/core/runs/run-tree.js +0 -103
- package/dist/core/sandboxing/adapter.js +0 -29
- package/dist/core/sandboxing/index.js +0 -49
- package/dist/core/sandboxing/none.js +0 -19
- package/dist/core/sandboxing/seatbelt.js +0 -183
- package/dist/core/security/injection-scanner.js +0 -367
- package/dist/core/security/output-filter.js +0 -418
- package/dist/core/session/env-file.js +0 -105
- package/dist/core/session/section-budgets.js +0 -140
- package/dist/core/session.js +0 -377
- package/dist/core/settings.js +0 -400
- package/dist/core/share/formatter.js +0 -271
- package/dist/core/share/redactor.js +0 -221
- package/dist/core/share/uploader.js +0 -267
- package/dist/core/skills/defaults.js +0 -457
- package/dist/core/skills/loader.js +0 -454
- package/dist/core/skills/sources.js +0 -480
- package/dist/core/skills/trust.js +0 -172
- package/dist/core/smoke/headless-driver.js +0 -174
- package/dist/core/smoke/orchestrator.js +0 -194
- package/dist/core/smoke/runner.js +0 -238
- package/dist/core/smoke/scenario-parser.js +0 -316
- package/dist/core/statusline.js +0 -99
- package/dist/core/subagents/dispatcher-real.js +0 -600
- package/dist/core/subagents/dispatcher.js +0 -352
- package/dist/core/subagents/index.js +0 -39
- package/dist/core/subagents/isolation-matrix.js +0 -213
- package/dist/core/subagents/spawn.js +0 -101
- package/dist/core/telemetry/emitter.js +0 -229
- package/dist/core/telemetry/queue.js +0 -251
- package/dist/core/theme/context.js +0 -91
- package/dist/core/theme/presets.js +0 -228
- package/dist/core/theme/state.js +0 -181
- package/dist/core/todos/invariant.js +0 -10
- package/dist/core/todos/state.js +0 -177
- package/dist/core/tool-schema/compressor.js +0 -89
- package/dist/core/transport/version-interceptor.js +0 -166
- package/dist/core/trust.js +0 -109
- package/dist/core/tui/thinking-block.js +0 -64
- package/dist/core/vim/keymap.js +0 -288
- package/dist/core/vim/state.js +0 -92
- package/dist/core/watch-markers/marker-watcher.js +0 -133
- package/dist/core/worktree/include-parser.js +0 -249
- package/dist/core/worktree-manager/cleanup.js +0 -123
- package/dist/core/worktree-manager/manager.js +0 -303
- package/dist/index.js +0 -44
- package/dist/runtime/bootstrap.js +0 -190
- package/dist/runtime/cli.js +0 -8121
- package/dist/runtime/commands/agents.js +0 -385
- package/dist/runtime/commands/budget.js +0 -192
- package/dist/runtime/commands/cancel.js +0 -231
- package/dist/runtime/commands/chain.js +0 -489
- package/dist/runtime/commands/codegraph-status.js +0 -227
- package/dist/runtime/commands/compact.js +0 -297
- package/dist/runtime/commands/config.js +0 -595
- package/dist/runtime/commands/cost.js +0 -199
- package/dist/runtime/commands/delegate.js +0 -312
- package/dist/runtime/commands/dispatch.js +0 -126
- package/dist/runtime/commands/doctor.js +0 -579
- package/dist/runtime/commands/feedback.js +0 -184
- package/dist/runtime/commands/hooks.js +0 -187
- package/dist/runtime/commands/init.js +0 -254
- package/dist/runtime/commands/lsp.js +0 -368
- package/dist/runtime/commands/mcp.js +0 -935
- package/dist/runtime/commands/memory.js +0 -582
- package/dist/runtime/commands/model.js +0 -237
- package/dist/runtime/commands/onboarding.js +0 -275
- package/dist/runtime/commands/patch.js +0 -128
- package/dist/runtime/commands/permissions.js +0 -112
- package/dist/runtime/commands/plan.js +0 -143
- package/dist/runtime/commands/prd-check.js +0 -285
- package/dist/runtime/commands/privacy.js +0 -107
- package/dist/runtime/commands/recipe.js +0 -325
- package/dist/runtime/commands/redo-blob-store.js +0 -92
- package/dist/runtime/commands/redo.js +0 -361
- package/dist/runtime/commands/release-notes.js +0 -229
- package/dist/runtime/commands/repo-map.js +0 -95
- package/dist/runtime/commands/report.js +0 -299
- package/dist/runtime/commands/resume.js +0 -118
- package/dist/runtime/commands/review-consensus.js +0 -414
- package/dist/runtime/commands/rewind.js +0 -333
- package/dist/runtime/commands/roster.js +0 -117
- package/dist/runtime/commands/sessions.js +0 -163
- package/dist/runtime/commands/share.js +0 -316
- package/dist/runtime/commands/skills.js +0 -401
- package/dist/runtime/commands/status.js +0 -186
- package/dist/runtime/commands/stickers.js +0 -82
- package/dist/runtime/commands/style.js +0 -194
- package/dist/runtime/commands/theme.js +0 -196
- package/dist/runtime/commands/undo.js +0 -361
- package/dist/runtime/commands/update.js +0 -289
- package/dist/runtime/commands/vim.js +0 -140
- package/dist/runtime/commands/worktree.js +0 -177
- package/dist/runtime/commands/worktrees.js +0 -155
- package/dist/runtime/deprecation-warning.js +0 -69
- package/dist/runtime/engine-exit-code.js +0 -50
- package/dist/runtime/headless-repl.js +0 -195
- package/dist/runtime/headless.js +0 -548
- package/dist/runtime/load-hooks-or-exit.js +0 -71
- package/dist/runtime/plan-decompose.js +0 -531
- package/dist/runtime/sigint-guard.js +0 -272
- package/dist/runtime/stream-renderer.js +0 -195
- package/dist/runtime/update-check.js +0 -294
- package/dist/runtime/version.js +0 -65
- package/dist/runtime/worktree-bootstrap.js +0 -579
- package/dist/skills/bundled/batch.js +0 -617
- package/dist/skills/bundled/index.js +0 -45
- package/dist/skills/bundled/loop.js +0 -358
- package/dist/skills/bundled/remember.js +0 -383
- package/dist/skills/bundled/simplify.js +0 -289
- package/dist/skills/bundled/skillify.js +0 -373
- package/dist/skills/bundled/stuck.js +0 -558
- package/dist/skills/bundled/verify.js +0 -439
- package/dist/testing/vcr.js +0 -486
- package/dist/tools/agent-tool.js +0 -229
- package/dist/tools/apply-patch.js +0 -556
- package/dist/tools/ask-user-question.js +0 -337
- package/dist/tools/ask-user.js +0 -115
- package/dist/tools/bash.js +0 -1238
- package/dist/tools/brief.js +0 -224
- package/dist/tools/cron.js +0 -433
- package/dist/tools/enter-worktree.js +0 -250
- package/dist/tools/exit-worktree.js +0 -147
- package/dist/tools/file-tools.js +0 -553
- package/dist/tools/http-request.js +0 -336
- package/dist/tools/lsp-tools.js +0 -565
- package/dist/tools/mcp-tool.js +0 -260
- package/dist/tools/multi-edit.js +0 -361
- package/dist/tools/powershell.js +0 -268
- package/dist/tools/registry.js +0 -166
- package/dist/tools/server-tools.js +0 -892
- package/dist/tools/skill-tool.js +0 -96
- package/dist/tools/sleep.js +0 -99
- package/dist/tools/synthetic-output.js +0 -133
- package/dist/tools/tasks.js +0 -208
- package/dist/tools/todo-write.js +0 -184
- package/dist/tools/verify-plan-execution.js +0 -295
- package/dist/tools/web-fetch-injection-scanner.js +0 -207
- package/dist/tools/web-fetch.js +0 -720
- package/dist/tools/web-search.js +0 -458
- package/dist/tui/agent-progress-card.js +0 -111
- package/dist/tui/agent-tree-pane.js +0 -9
- package/dist/tui/agent-tree.js +0 -87
- package/dist/tui/ask-cli.js +0 -52
- package/dist/tui/ask-modal.js +0 -211
- package/dist/tui/ask-user-question-chips.js +0 -315
- package/dist/tui/ask-user-question-prompt.js +0 -203
- package/dist/tui/compact-banner.js +0 -81
- package/dist/tui/conversation-pane.js +0 -164
- package/dist/tui/cost-table.js +0 -111
- package/dist/tui/device-flow.js +0 -142
- package/dist/tui/doctor-table.js +0 -46
- package/dist/tui/feedback-prompt.js +0 -156
- package/dist/tui/input-box.js +0 -732
- package/dist/tui/login-picker.js +0 -69
- package/dist/tui/markdown-render.js +0 -266
- package/dist/tui/multi-file-diff-approval.js +0 -375
- package/dist/tui/onboarding-wizard.js +0 -240
- package/dist/tui/permissions-picker.js +0 -86
- package/dist/tui/render.js +0 -160
- package/dist/tui/repl-render.js +0 -770
- package/dist/tui/repl-splash-art.js +0 -64
- package/dist/tui/repl-splash-mascot.js +0 -154
- package/dist/tui/repl-splash.js +0 -117
- package/dist/tui/repl.js +0 -378
- package/dist/tui/slash-palette.js +0 -106
- package/dist/tui/splash-data.js +0 -61
- package/dist/tui/splash.js +0 -31
- package/dist/tui/status-bar.js +0 -209
- package/dist/tui/status-table.js +0 -7
- package/dist/tui/stickers-art.js +0 -136
- package/dist/tui/style-table.js +0 -28
- package/dist/tui/theme-table.js +0 -29
- package/dist/tui/thinking-spinner.js +0 -123
- package/dist/tui/tool-stream-pane.js +0 -140
- package/dist/tui/update-banner.js +0 -33
- package/dist/tui/vim-input.js +0 -267
- package/dist/tui/welcome-banner.js +0 -107
- package/dist/tui/welcome-data.js +0 -293
- package/dist/tui/workspace-context.js +0 -105
- package/docs/examples/codegraph.mcp.json +0 -10
- package/test/scenarios/codegen-create-file.scenario.txt +0 -13
- package/test/scenarios/compact-force.scenario.txt +0 -12
- package/test/scenarios/identity.scenario.txt +0 -11
- package/test/scenarios/persona-handoff.scenario.txt +0 -12
- package/test/scenarios/walkback.scenario.txt +0 -12
|
@@ -1,558 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `pugi stuck` — non-invasive self-diagnosis for a Pugi process that
|
|
3
|
-
* appears wedged. Bundled skill, batch 1 (backlog, inspired by
|
|
4
|
-
* the upstream tool's bundled-skills pattern surfaced in leak intel).
|
|
5
|
-
*
|
|
6
|
-
* # What this does (and does not)
|
|
7
|
-
*
|
|
8
|
-
* The operator runs `pugi stuck` when the foreground Pugi seems frozen
|
|
9
|
-
* (engine spinner, REPL silent, edit hangs). The skill walks the
|
|
10
|
-
* process tree once, gathers vitals on every peer Pugi process, flags
|
|
11
|
-
* the ones that match a fault heuristic, optionally dumps a kernel
|
|
12
|
-
* stack via `sample(1)` on macOS, and posts the redacted snapshot to a
|
|
13
|
-
* feedback webhook if one is configured.
|
|
14
|
-
*
|
|
15
|
-
* **It NEVER signals, kills, or otherwise mutates a process.** The
|
|
16
|
-
* exit code is purely informational: a non-zero return means "at least
|
|
17
|
-
* one peer looked suspicious, here is what we saw". The operator
|
|
18
|
-
* decides what to do — kill, attach a debugger, file a bug, wait.
|
|
19
|
-
*
|
|
20
|
-
* # Fault heuristic (peer is suspect when ANY holds)
|
|
21
|
-
*
|
|
22
|
-
* - sustained CPU >= CPU_THRESHOLD across the snapshot window
|
|
23
|
-
* - resident set size (RSS) >= RSS_THRESHOLD_MB
|
|
24
|
-
* - process state is `D` (uninterruptible I/O wait)
|
|
25
|
-
* - process state is `T` (stopped, e.g. Ctrl+Z)
|
|
26
|
-
* - process state is `Z` (zombie / exited but unreaped)
|
|
27
|
-
*
|
|
28
|
-
* # Output
|
|
29
|
-
*
|
|
30
|
-
* Human-readable table by default; machine-readable JSON envelope when
|
|
31
|
-
* `--json` is set. Both surfaces describe the same payload. The JSON
|
|
32
|
-
* envelope is the source of truth for the feedback webhook.
|
|
33
|
-
*
|
|
34
|
-
* # Provenance
|
|
35
|
-
*
|
|
36
|
-
* Inspired by the the upstream tool bundled-skills pattern (intel from
|
|
37
|
-
* leak-research memos, independent implementation TS). No upstream code reused.
|
|
38
|
-
*/
|
|
39
|
-
import { execFileSync, spawnSync } from 'node:child_process';
|
|
40
|
-
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
41
|
-
import { homedir, platform } from 'node:os';
|
|
42
|
-
import { join, resolve } from 'node:path';
|
|
43
|
-
/**
|
|
44
|
-
* CPU usage (percent) at which a peer Pugi is flagged. Single-core
|
|
45
|
-
* tight loops sit at ~100%; the threshold trips well before that to
|
|
46
|
-
* surface near-pegged processes. Operator-overridable via flag.
|
|
47
|
-
*/
|
|
48
|
-
const DEFAULT_CPU_THRESHOLD = 85;
|
|
49
|
-
/**
|
|
50
|
-
* Resident set size (MB) at which a peer Pugi is flagged. Engine + REPL
|
|
51
|
-
* + Ink should stay well under 1 GB in normal operation. 4 GB indicates
|
|
52
|
-
* a runaway model context or a leaking subagent.
|
|
53
|
-
*/
|
|
54
|
-
const DEFAULT_RSS_THRESHOLD_MB = 4096;
|
|
55
|
-
/**
|
|
56
|
-
* Process states that are inherently suspicious regardless of CPU / RSS.
|
|
57
|
-
* D — uninterruptible sleep (often disk / network hang)
|
|
58
|
-
* T — stopped (Ctrl+Z, debugger detached without continue)
|
|
59
|
-
* Z — zombie (exit not reaped, parent leaking)
|
|
60
|
-
*/
|
|
61
|
-
const STUCK_STATES = new Set(['D', 'T', 'Z']);
|
|
62
|
-
function parseFlags(args) {
|
|
63
|
-
const flags = {
|
|
64
|
-
json: false,
|
|
65
|
-
cpuThreshold: DEFAULT_CPU_THRESHOLD,
|
|
66
|
-
rssThresholdMb: DEFAULT_RSS_THRESHOLD_MB,
|
|
67
|
-
sampleStack: true,
|
|
68
|
-
};
|
|
69
|
-
for (let i = 0; i < args.length; i += 1) {
|
|
70
|
-
const arg = args[i];
|
|
71
|
-
if (arg === undefined)
|
|
72
|
-
continue;
|
|
73
|
-
if (arg === '--json') {
|
|
74
|
-
flags.json = true;
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
if (arg === '--no-sample') {
|
|
78
|
-
flags.sampleStack = false;
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
if (arg === '--cpu') {
|
|
82
|
-
const next = args[i + 1];
|
|
83
|
-
if (next === undefined)
|
|
84
|
-
return { flags, error: '--cpu requires a value' };
|
|
85
|
-
const parsed = Number.parseFloat(next);
|
|
86
|
-
if (!Number.isFinite(parsed) || parsed < 0 || parsed > 100) {
|
|
87
|
-
return { flags, error: '--cpu must be a number 0..100' };
|
|
88
|
-
}
|
|
89
|
-
flags.cpuThreshold = parsed;
|
|
90
|
-
i += 1;
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
if (arg === '--rss') {
|
|
94
|
-
const next = args[i + 1];
|
|
95
|
-
if (next === undefined)
|
|
96
|
-
return { flags, error: '--rss requires a value' };
|
|
97
|
-
const parsed = Number.parseInt(next, 10);
|
|
98
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
99
|
-
return { flags, error: '--rss must be a positive integer (MB)' };
|
|
100
|
-
}
|
|
101
|
-
flags.rssThresholdMb = parsed;
|
|
102
|
-
i += 1;
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
if (arg === '--help' || arg === '-h') {
|
|
106
|
-
return { flags, error: 'help' };
|
|
107
|
-
}
|
|
108
|
-
return { flags, error: `unknown argument: ${arg}` };
|
|
109
|
-
}
|
|
110
|
-
return { flags, error: null };
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* One row out of `ps -axo pid=,ppid=,%cpu=,rss=,state=,command=`. Parsed
|
|
114
|
-
* defensively because BSD ps and GNU ps disagree on whitespace + the
|
|
115
|
-
* trailing command can contain spaces (we slurp the remainder into the
|
|
116
|
-
* `command` field rather than splitting on spaces blindly).
|
|
117
|
-
*/
|
|
118
|
-
function parsePsLine(line) {
|
|
119
|
-
const trimmed = line.trim();
|
|
120
|
-
if (!trimmed)
|
|
121
|
-
return null;
|
|
122
|
-
const head = trimmed.split(/\s+/, 5);
|
|
123
|
-
if (head.length < 5)
|
|
124
|
-
return null;
|
|
125
|
-
const [pidStr, ppidStr, cpuStr, rssStr, stateRaw] = head;
|
|
126
|
-
if (pidStr === undefined ||
|
|
127
|
-
ppidStr === undefined ||
|
|
128
|
-
cpuStr === undefined ||
|
|
129
|
-
rssStr === undefined ||
|
|
130
|
-
stateRaw === undefined) {
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
const pid = Number.parseInt(pidStr, 10);
|
|
134
|
-
const parentPid = Number.parseInt(ppidStr, 10);
|
|
135
|
-
const cpuPercent = Number.parseFloat(cpuStr);
|
|
136
|
-
const rssKb = Number.parseInt(rssStr, 10);
|
|
137
|
-
if (!Number.isFinite(pid) ||
|
|
138
|
-
!Number.isFinite(parentPid) ||
|
|
139
|
-
!Number.isFinite(cpuPercent) ||
|
|
140
|
-
!Number.isFinite(rssKb)) {
|
|
141
|
-
return null;
|
|
142
|
-
}
|
|
143
|
-
// Re-walk the line to recover the command tail without losing
|
|
144
|
-
// embedded spaces. We skip the 5 leading whitespace-delimited fields.
|
|
145
|
-
let cursor = 0;
|
|
146
|
-
for (let field = 0; field < 5; field += 1) {
|
|
147
|
-
// skip any leading whitespace
|
|
148
|
-
while (cursor < trimmed.length && /\s/.test(trimmed.charAt(cursor)))
|
|
149
|
-
cursor += 1;
|
|
150
|
-
// skip the field itself
|
|
151
|
-
while (cursor < trimmed.length && !/\s/.test(trimmed.charAt(cursor)))
|
|
152
|
-
cursor += 1;
|
|
153
|
-
}
|
|
154
|
-
const command = trimmed.slice(cursor).trim();
|
|
155
|
-
// First character of the BSD `state` field is the canonical state
|
|
156
|
-
// (e.g. `S+`, `R<`, `D+`). Normalise to single letter.
|
|
157
|
-
const state = stateRaw.charAt(0) || '?';
|
|
158
|
-
return { pid, parentPid, cpuPercent, rssKb, state, command };
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Snapshot the whole process table once via `ps -axo`. Pure read, no
|
|
162
|
-
* mutation. Returns an empty list if `ps` is unavailable (extremely
|
|
163
|
-
* locked-down container) — the caller surfaces an empty snapshot
|
|
164
|
-
* rather than crashing.
|
|
165
|
-
*/
|
|
166
|
-
export function readProcessTable() {
|
|
167
|
-
try {
|
|
168
|
-
const stdout = execFileSync('ps', ['-axo', 'pid=,ppid=,%cpu=,rss=,state=,command='], {
|
|
169
|
-
encoding: 'utf8',
|
|
170
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
171
|
-
// 5 s is plenty even on a loaded host; we never want `pugi
|
|
172
|
-
// stuck` itself to wedge waiting for ps.
|
|
173
|
-
timeout: 5000,
|
|
174
|
-
});
|
|
175
|
-
const rows = [];
|
|
176
|
-
for (const line of stdout.split('\n')) {
|
|
177
|
-
const parsed = parsePsLine(line);
|
|
178
|
-
if (parsed !== null)
|
|
179
|
-
rows.push(parsed);
|
|
180
|
-
}
|
|
181
|
-
return rows;
|
|
182
|
-
}
|
|
183
|
-
catch {
|
|
184
|
-
return [];
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* Identify peer Pugi processes. A row is a "peer" when its command
|
|
189
|
-
* line looks like a `pugi` invocation — node + pugi entrypoint, or the
|
|
190
|
-
* bin shim, or a tsx-driven dev run. The current process is excluded
|
|
191
|
-
* so `pugi stuck` does not diagnose itself.
|
|
192
|
-
*/
|
|
193
|
-
function isPugiPeer(row, selfPid) {
|
|
194
|
-
if (row.pid === selfPid)
|
|
195
|
-
return false;
|
|
196
|
-
const cmd = row.command.toLowerCase();
|
|
197
|
-
// The bin shim path: `…/.npm/_npx/…/bin/pugi` or `node …/bin/pugi`.
|
|
198
|
-
if (/\bpugi\b/.test(cmd) && /(\bnode\b|\bbin\/pugi\b|\.npm\/|\.pnpm\/|tsx\b)/.test(cmd)) {
|
|
199
|
-
return true;
|
|
200
|
-
}
|
|
201
|
-
// Direct invocation of the compiled bundle inside dist/.
|
|
202
|
-
if (/pugi-cli\/(dist|bin)\//.test(cmd))
|
|
203
|
-
return true;
|
|
204
|
-
return false;
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* Walk the ps table and collect every Pugi peer with the heuristic
|
|
208
|
-
* verdict attached. We deliberately keep the verdict logic outside of
|
|
209
|
-
* any side-effects so tests can exercise the boundary by feeding a
|
|
210
|
-
* synthetic ps table.
|
|
211
|
-
*/
|
|
212
|
-
export function classifyPeers(rows, selfPid, thresholds) {
|
|
213
|
-
const peers = [];
|
|
214
|
-
for (const row of rows) {
|
|
215
|
-
if (!isPugiPeer(row, selfPid))
|
|
216
|
-
continue;
|
|
217
|
-
const rssMb = Math.round(row.rssKb / 1024);
|
|
218
|
-
const reasons = [];
|
|
219
|
-
if (row.cpuPercent >= thresholds.cpuPercent) {
|
|
220
|
-
reasons.push(`cpu ${row.cpuPercent.toFixed(1)}% >= ${thresholds.cpuPercent}%`);
|
|
221
|
-
}
|
|
222
|
-
if (rssMb >= thresholds.rssMb) {
|
|
223
|
-
reasons.push(`rss ${rssMb} MB >= ${thresholds.rssMb} MB`);
|
|
224
|
-
}
|
|
225
|
-
if (STUCK_STATES.has(row.state)) {
|
|
226
|
-
reasons.push(`state ${row.state} (${describeState(row.state)})`);
|
|
227
|
-
}
|
|
228
|
-
peers.push({
|
|
229
|
-
pid: row.pid,
|
|
230
|
-
parentPid: row.parentPid,
|
|
231
|
-
cpuPercent: row.cpuPercent,
|
|
232
|
-
rssMb,
|
|
233
|
-
state: row.state,
|
|
234
|
-
command: row.command,
|
|
235
|
-
suspect: reasons.length > 0,
|
|
236
|
-
reasons,
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
return peers;
|
|
240
|
-
}
|
|
241
|
-
function describeState(letter) {
|
|
242
|
-
switch (letter) {
|
|
243
|
-
case 'D':
|
|
244
|
-
return 'uninterruptible I/O wait';
|
|
245
|
-
case 'T':
|
|
246
|
-
return 'stopped';
|
|
247
|
-
case 'Z':
|
|
248
|
-
return 'zombie';
|
|
249
|
-
case 'R':
|
|
250
|
-
return 'runnable';
|
|
251
|
-
case 'S':
|
|
252
|
-
return 'sleeping';
|
|
253
|
-
default:
|
|
254
|
-
return 'unknown';
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
/**
|
|
258
|
-
* Walk the ps table for child processes of a peer Pugi (git, node,
|
|
259
|
-
* shell helpers). A child is flagged when it is in a suspicious state
|
|
260
|
-
* OR pegged on CPU. Children are not subject to the RSS threshold
|
|
261
|
-
* because git/sh legitimately stay tiny.
|
|
262
|
-
*/
|
|
263
|
-
export function classifyChildren(rows, peerPids, thresholds) {
|
|
264
|
-
const peerSet = new Set(peerPids);
|
|
265
|
-
const out = [];
|
|
266
|
-
for (const row of rows) {
|
|
267
|
-
if (!peerSet.has(row.parentPid))
|
|
268
|
-
continue;
|
|
269
|
-
const suspicious = STUCK_STATES.has(row.state) || row.cpuPercent >= thresholds.cpuPercent;
|
|
270
|
-
if (!suspicious)
|
|
271
|
-
continue;
|
|
272
|
-
out.push({
|
|
273
|
-
pid: row.pid,
|
|
274
|
-
parentPid: row.parentPid,
|
|
275
|
-
state: row.state,
|
|
276
|
-
command: row.command,
|
|
277
|
-
cpuPercent: row.cpuPercent,
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
return out;
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* Read the tail of `~/.pugi/debug/` log files, if any. The directory
|
|
284
|
-
* is optional — most operators do not have debug logging enabled.
|
|
285
|
-
* We sort newest-first by mtime and slurp the last few lines of the
|
|
286
|
-
* top file so the snapshot carries enough context without dragging in
|
|
287
|
-
* megabytes of history.
|
|
288
|
-
*/
|
|
289
|
-
export function readDebugTail(home, maxLines = 25) {
|
|
290
|
-
const dir = join(home, '.pugi', 'debug');
|
|
291
|
-
if (!existsSync(dir))
|
|
292
|
-
return [];
|
|
293
|
-
let entries;
|
|
294
|
-
try {
|
|
295
|
-
entries = readdirSync(dir)
|
|
296
|
-
.map((name) => ({ path: join(dir, name), mtimeMs: 0 }))
|
|
297
|
-
.map((entry) => {
|
|
298
|
-
try {
|
|
299
|
-
const st = statSync(entry.path);
|
|
300
|
-
return { path: entry.path, mtimeMs: st.mtimeMs };
|
|
301
|
-
}
|
|
302
|
-
catch {
|
|
303
|
-
return entry;
|
|
304
|
-
}
|
|
305
|
-
})
|
|
306
|
-
.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
307
|
-
}
|
|
308
|
-
catch {
|
|
309
|
-
return [];
|
|
310
|
-
}
|
|
311
|
-
const top = entries[0];
|
|
312
|
-
if (top === undefined)
|
|
313
|
-
return [];
|
|
314
|
-
try {
|
|
315
|
-
const raw = readFileSync(top.path, 'utf8');
|
|
316
|
-
const lines = raw.split('\n').filter((l) => l !== '');
|
|
317
|
-
return lines.slice(Math.max(0, lines.length - maxLines));
|
|
318
|
-
}
|
|
319
|
-
catch {
|
|
320
|
-
return [];
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
/**
|
|
324
|
-
* Capture a 1-second `sample <pid>` stack dump on macOS. Returns the
|
|
325
|
-
* raw textual output (already truncated by sample itself to a
|
|
326
|
-
* reasonable size). Returns null on any failure — sample is best-effort
|
|
327
|
-
* diagnostic, never blocking.
|
|
328
|
-
*/
|
|
329
|
-
export function captureSampleStack(pid) {
|
|
330
|
-
if (platform() !== 'darwin')
|
|
331
|
-
return null;
|
|
332
|
-
try {
|
|
333
|
-
const result = spawnSync('sample', [String(pid), '1'], {
|
|
334
|
-
encoding: 'utf8',
|
|
335
|
-
timeout: 5000,
|
|
336
|
-
maxBuffer: 8 * 1024 * 1024,
|
|
337
|
-
});
|
|
338
|
-
if (result.status !== 0)
|
|
339
|
-
return null;
|
|
340
|
-
return result.stdout.trim();
|
|
341
|
-
}
|
|
342
|
-
catch {
|
|
343
|
-
return null;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
/**
|
|
347
|
-
* Post the snapshot JSON to a feedback webhook. The webhook is
|
|
348
|
-
* configured via `PUGI_FEEDBACK_WEBHOOK` env var; if absent we record
|
|
349
|
-
* `not-configured` and skip. We deliberately use a short fetch timeout
|
|
350
|
-
* because `pugi stuck` is run when the operator already suspects
|
|
351
|
-
* something is wrong — blocking on a slow webhook defeats the purpose.
|
|
352
|
-
*/
|
|
353
|
-
/**
|
|
354
|
-
* Webhook URL validation guard — P1 fix from triple-review PR.
|
|
355
|
-
*
|
|
356
|
-
* `PUGI_FEEDBACK_WEBHOOK` is operator-set, but a malicious shell-rc
|
|
357
|
-
* injection could redirect snapshot payloads to an attacker URL —
|
|
358
|
-
* SSRF + exfiltration of process telemetry (peer command-lines,
|
|
359
|
-
* debug-log tail, sample stack with symbols/paths). Enforce:
|
|
360
|
-
* 1. HTTPS only — reject http/file/data/javascript/etc.
|
|
361
|
-
* 2. No private/loopback hostnames — reject localhost, 127.x,
|
|
362
|
-
* 10.x, 172.16-31.x, 192.168.x, *.local, link-local.
|
|
363
|
-
* Returns null when URL is invalid OR safe-to-use otherwise.
|
|
364
|
-
*/
|
|
365
|
-
function validateWebhookUrl(raw) {
|
|
366
|
-
let parsed;
|
|
367
|
-
try {
|
|
368
|
-
parsed = new URL(raw);
|
|
369
|
-
}
|
|
370
|
-
catch {
|
|
371
|
-
return null;
|
|
372
|
-
}
|
|
373
|
-
if (parsed.protocol !== 'https:')
|
|
374
|
-
return null;
|
|
375
|
-
const host = parsed.hostname.toLowerCase();
|
|
376
|
-
if (host === 'localhost' ||
|
|
377
|
-
host === '0.0.0.0' ||
|
|
378
|
-
host.endsWith('.local') ||
|
|
379
|
-
host.startsWith('127.') ||
|
|
380
|
-
host.startsWith('10.') ||
|
|
381
|
-
host.startsWith('192.168.') ||
|
|
382
|
-
/^172\.(1[6-9]|2[0-9]|3[01])\./.test(host) ||
|
|
383
|
-
/^169\.254\./.test(host) // link-local
|
|
384
|
-
) {
|
|
385
|
-
return null;
|
|
386
|
-
}
|
|
387
|
-
return parsed;
|
|
388
|
-
}
|
|
389
|
-
export async function postSnapshotToWebhook(snapshot, url) {
|
|
390
|
-
if (!url || url.trim() === '')
|
|
391
|
-
return 'not-configured';
|
|
392
|
-
const safe = validateWebhookUrl(url.trim());
|
|
393
|
-
if (!safe)
|
|
394
|
-
return 'invalid-url';
|
|
395
|
-
try {
|
|
396
|
-
const controller = new AbortController();
|
|
397
|
-
const timer = setTimeout(() => controller.abort(), 3000);
|
|
398
|
-
try {
|
|
399
|
-
const response = await fetch(safe.toString(), {
|
|
400
|
-
method: 'POST',
|
|
401
|
-
headers: { 'content-type': 'application/json' },
|
|
402
|
-
body: JSON.stringify({ kind: 'pugi-stuck', snapshot }),
|
|
403
|
-
signal: controller.signal,
|
|
404
|
-
});
|
|
405
|
-
return response.ok ? 'ok' : 'failed';
|
|
406
|
-
}
|
|
407
|
-
finally {
|
|
408
|
-
clearTimeout(timer);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
catch {
|
|
412
|
-
return 'failed';
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
function renderHumanReport(snapshot) {
|
|
416
|
-
const lines = [];
|
|
417
|
-
lines.push(`pugi stuck — diagnostic snapshot @ ${snapshot.capturedAt}`);
|
|
418
|
-
lines.push(`platform: ${snapshot.platform} selfPid: ${snapshot.selfPid}`);
|
|
419
|
-
lines.push(`thresholds: cpu>=${snapshot.thresholds.cpuPercent}% rss>=${snapshot.thresholds.rssMb} MB`);
|
|
420
|
-
lines.push('');
|
|
421
|
-
if (snapshot.peers.length === 0) {
|
|
422
|
-
lines.push('No peer Pugi processes detected.');
|
|
423
|
-
}
|
|
424
|
-
else {
|
|
425
|
-
lines.push(`Peer Pugi processes (${snapshot.peers.length}):`);
|
|
426
|
-
for (const peer of snapshot.peers) {
|
|
427
|
-
const tag = peer.suspect ? '[SUSPECT]' : '[ok]';
|
|
428
|
-
lines.push(` ${tag} pid=${peer.pid} ppid=${peer.parentPid} cpu=${peer.cpuPercent.toFixed(1)}% rss=${peer.rssMb}MB state=${peer.state}`);
|
|
429
|
-
lines.push(` ${peer.command}`);
|
|
430
|
-
for (const reason of peer.reasons) {
|
|
431
|
-
lines.push(` reason: ${reason}`);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
if (snapshot.suspectChildren.length > 0) {
|
|
436
|
-
lines.push('');
|
|
437
|
-
lines.push(`Suspect child processes (${snapshot.suspectChildren.length}):`);
|
|
438
|
-
for (const child of snapshot.suspectChildren) {
|
|
439
|
-
lines.push(` pid=${child.pid} ppid=${child.parentPid} cpu=${child.cpuPercent.toFixed(1)}% state=${child.state}`);
|
|
440
|
-
lines.push(` ${child.command}`);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
if (snapshot.debugLogTail.length > 0) {
|
|
444
|
-
lines.push('');
|
|
445
|
-
lines.push(`Tail of ~/.pugi/debug/ (${snapshot.debugLogTail.length} lines):`);
|
|
446
|
-
for (const line of snapshot.debugLogTail)
|
|
447
|
-
lines.push(` ${line}`);
|
|
448
|
-
}
|
|
449
|
-
if (snapshot.sampleDump !== null) {
|
|
450
|
-
lines.push('');
|
|
451
|
-
lines.push('sample(1) stack (truncated):');
|
|
452
|
-
const dumpLines = snapshot.sampleDump.split('\n').slice(0, 40);
|
|
453
|
-
for (const line of dumpLines)
|
|
454
|
-
lines.push(` ${line}`);
|
|
455
|
-
}
|
|
456
|
-
lines.push('');
|
|
457
|
-
lines.push(`webhook: ${snapshot.webhookPosted}`);
|
|
458
|
-
lines.push('');
|
|
459
|
-
lines.push('Diagnostic only — no process was signalled or killed.');
|
|
460
|
-
return lines.join('\n');
|
|
461
|
-
}
|
|
462
|
-
/**
|
|
463
|
-
* Public entrypoint. The CLI dispatcher wraps this. Pure async function
|
|
464
|
-
* + injected `writeOutput` so tests can capture both surfaces without
|
|
465
|
-
* touching stdout.
|
|
466
|
-
*/
|
|
467
|
-
export async function runStuckCommand(args, ctx) {
|
|
468
|
-
const { flags, error } = parseFlags(args);
|
|
469
|
-
if (error === 'help') {
|
|
470
|
-
ctx.writeOutput({ ok: true, command: 'stuck', usage: STUCK_USAGE }, STUCK_USAGE);
|
|
471
|
-
return { snapshot: emptySnapshot(ctx), exitCode: 0 };
|
|
472
|
-
}
|
|
473
|
-
if (error !== null) {
|
|
474
|
-
ctx.writeOutput({ ok: false, command: 'stuck', error }, `pugi stuck: ${error}`);
|
|
475
|
-
return { exitCode: 2 };
|
|
476
|
-
}
|
|
477
|
-
const home = ctx.env.PUGI_HOME ?? homedir();
|
|
478
|
-
const rows = readProcessTable();
|
|
479
|
-
const peers = classifyPeers(rows, process.pid, {
|
|
480
|
-
cpuPercent: flags.cpuThreshold,
|
|
481
|
-
rssMb: flags.rssThresholdMb,
|
|
482
|
-
});
|
|
483
|
-
const peerPids = peers.map((p) => p.pid);
|
|
484
|
-
const suspectChildren = classifyChildren(rows, peerPids, {
|
|
485
|
-
cpuPercent: flags.cpuThreshold,
|
|
486
|
-
});
|
|
487
|
-
const debugLogTail = readDebugTail(home);
|
|
488
|
-
const mostSuspect = peers
|
|
489
|
-
.filter((p) => p.suspect)
|
|
490
|
-
.sort((a, b) => b.cpuPercent - a.cpuPercent)[0];
|
|
491
|
-
const sampleDump = flags.sampleStack && mostSuspect !== undefined
|
|
492
|
-
? captureSampleStack(mostSuspect.pid)
|
|
493
|
-
: null;
|
|
494
|
-
const draftSnapshot = {
|
|
495
|
-
capturedAt: ctx.now().toISOString(),
|
|
496
|
-
platform: platform(),
|
|
497
|
-
selfPid: process.pid,
|
|
498
|
-
thresholds: {
|
|
499
|
-
cpuPercent: flags.cpuThreshold,
|
|
500
|
-
rssMb: flags.rssThresholdMb,
|
|
501
|
-
},
|
|
502
|
-
peers,
|
|
503
|
-
suspectChildren,
|
|
504
|
-
debugLogTail,
|
|
505
|
-
sampleDump,
|
|
506
|
-
webhookPosted: 'skipped',
|
|
507
|
-
};
|
|
508
|
-
const webhookUrl = ctx.env.PUGI_FEEDBACK_WEBHOOK;
|
|
509
|
-
const webhookPosted = await postSnapshotToWebhook(draftSnapshot, webhookUrl);
|
|
510
|
-
const snapshot = { ...draftSnapshot, webhookPosted };
|
|
511
|
-
const human = renderHumanReport(snapshot);
|
|
512
|
-
ctx.writeOutput({ ok: true, command: 'stuck', snapshot }, human);
|
|
513
|
-
const anySuspect = snapshot.peers.some((p) => p.suspect) || snapshot.suspectChildren.length > 0;
|
|
514
|
-
return { snapshot, exitCode: anySuspect ? 1 : 0 };
|
|
515
|
-
}
|
|
516
|
-
function emptySnapshot(ctx) {
|
|
517
|
-
return {
|
|
518
|
-
capturedAt: ctx.now().toISOString(),
|
|
519
|
-
platform: platform(),
|
|
520
|
-
selfPid: process.pid,
|
|
521
|
-
thresholds: {
|
|
522
|
-
cpuPercent: DEFAULT_CPU_THRESHOLD,
|
|
523
|
-
rssMb: DEFAULT_RSS_THRESHOLD_MB,
|
|
524
|
-
},
|
|
525
|
-
peers: [],
|
|
526
|
-
suspectChildren: [],
|
|
527
|
-
debugLogTail: [],
|
|
528
|
-
sampleDump: null,
|
|
529
|
-
webhookPosted: 'skipped',
|
|
530
|
-
};
|
|
531
|
-
}
|
|
532
|
-
const STUCK_USAGE = [
|
|
533
|
-
'pugi stuck — non-invasive self-diagnosis for a wedged Pugi process.',
|
|
534
|
-
'',
|
|
535
|
-
'Usage:',
|
|
536
|
-
' pugi stuck [--json] [--cpu <pct>] [--rss <mb>] [--no-sample]',
|
|
537
|
-
'',
|
|
538
|
-
'Flags:',
|
|
539
|
-
' --json Emit a JSON envelope instead of human text.',
|
|
540
|
-
' --cpu <pct> CPU % at which a peer is flagged (default 85).',
|
|
541
|
-
' --rss <mb> RSS MB at which a peer is flagged (default 4096).',
|
|
542
|
-
' --no-sample Skip the macOS sample(1) stack dump.',
|
|
543
|
-
'',
|
|
544
|
-
'Env:',
|
|
545
|
-
' PUGI_FEEDBACK_WEBHOOK POST snapshot JSON to this URL (best-effort).',
|
|
546
|
-
' PUGI_HOME Override the ~/.pugi/debug log root.',
|
|
547
|
-
'',
|
|
548
|
-
'Diagnostic only — NEVER signals or kills any process.',
|
|
549
|
-
].join('\n');
|
|
550
|
-
/**
|
|
551
|
-
* Public helper for tests + the dispatcher: assert the textual `resolve`
|
|
552
|
-
* import is still in the module graph so node resolution does not strip
|
|
553
|
-
* the side-effect-free path import. Some bundlers tree-shake otherwise.
|
|
554
|
-
*/
|
|
555
|
-
export function __debugResolveSelfPath(workspaceRoot) {
|
|
556
|
-
return resolve(workspaceRoot, '.pugi', 'debug');
|
|
557
|
-
}
|
|
558
|
-
//# sourceMappingURL=stuck.js.map
|