@pugi/cli 0.1.0-beta.8 → 0.1.0-beta.88
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +132 -0
- package/LICENSE +1 -1
- package/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 +3 -3
- package/dist/core/approvals/shortcut-resolver.js +98 -0
- package/dist/core/artifact-chain/dispatcher.js +148 -0
- package/dist/core/artifact-chain/exporter.js +164 -0
- package/dist/core/artifact-chain/state.js +243 -0
- package/dist/core/artifact-chain/steps.js +169 -0
- package/dist/core/ask-user/question.js +92 -0
- package/dist/core/audit/audit-trail.js +275 -0
- package/dist/core/auth/ensure-authenticated.js +129 -0
- package/dist/core/auth/env-provider.js +238 -0
- package/dist/core/auto-open-browser.js +4 -4
- package/dist/core/auto-update/channels.js +122 -0
- package/dist/core/auto-update/checker.js +241 -0
- package/dist/core/auto-update/state.js +235 -0
- package/dist/core/bare-mode/index.js +107 -0
- package/dist/core/bash/redirect.js +281 -0
- package/dist/core/bash-classifier.js +436 -40
- package/dist/core/checkpoint/resumer.js +149 -0
- package/dist/core/checkpoint/rewinder.js +291 -0
- package/dist/core/checkpoints/shadow-git.js +670 -0
- package/dist/core/citations/parser.js +109 -0
- package/dist/core/classifier/yolo-classifier.js +88 -0
- package/dist/core/codegraph/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 +151 -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 +298 -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 +36 -0
- package/dist/runtime/bootstrap.js +190 -0
- package/dist/runtime/cli.js +4203 -493
- 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 +73 -39
- 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/sigint-guard.js +272 -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 +288 -0
- package/dist/tools/ask-user.js +115 -0
- package/dist/tools/bash.js +624 -46
- 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-chips.js +257 -0
- package/dist/tui/ask-user-question-prompt.js +203 -0
- package/dist/tui/compact-banner.js +81 -0
- package/dist/tui/conversation-pane.js +85 -11
- package/dist/tui/cost-table.js +111 -0
- package/dist/tui/device-flow.js +2 -2
- package/dist/tui/doctor-table.js +46 -0
- package/dist/tui/feedback-prompt.js +156 -0
- package/dist/tui/input-box.js +247 -32
- package/dist/tui/login-picker.js +3 -3
- package/dist/tui/markdown-render.js +6 -6
- package/dist/tui/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 +25 -7
- 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,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persona-memory secret scanner (backlog, hardening).
|
|
3
|
+
*
|
|
4
|
+
* Defense against API keys / credentials accidentally landing in shared
|
|
5
|
+
* persona memory. A naive operator typing `pugi memory write fact "Use
|
|
6
|
+
* API key sk-ant-..."` would silently persist that secret to the
|
|
7
|
+
* `the platform database.persona_memory` table — visible to every persona, every
|
|
8
|
+
* recall query, every dual-write sink. This module is the chokepoint
|
|
9
|
+
* that refuses such writes.
|
|
10
|
+
*
|
|
11
|
+
* # Scope
|
|
12
|
+
*
|
|
13
|
+
* Scoped to the persona-memory write surface (`pugi memory write` and
|
|
14
|
+
* the `pugi remember` curator's `defaultPersist`). NOT a global secret
|
|
15
|
+
* scanner — the tarball pre-publish gate
|
|
16
|
+
* (`apps/pugi-cli/scripts/secret-scanner.mjs`) covers npm publish, and
|
|
17
|
+
* gitleaks covers git history. The two scanners share the pattern
|
|
18
|
+
* vocabulary but live at different boundaries; do NOT consolidate
|
|
19
|
+
* without re-validating each call site.
|
|
20
|
+
*
|
|
21
|
+
* # Patterns shipped
|
|
22
|
+
*
|
|
23
|
+
* 1. Anthropic key sk-ant-… high
|
|
24
|
+
* 2. Generic OpenAI-style sk-… high
|
|
25
|
+
* 3. GitHub PAT / installation tokens (ghp_/ghs_/gho_/ghu_)
|
|
26
|
+
* high
|
|
27
|
+
* 4. AWS access key id AKIA… high
|
|
28
|
+
* 5. Plane API token plane_api_… high [pugi-leak-ok]
|
|
29
|
+
* 6. npm token npm_… high
|
|
30
|
+
* 7. Slack token xox[bpoars]-… high
|
|
31
|
+
* 8. Stripe secret key sk_(live|test)_… high
|
|
32
|
+
* 9. JWT-shaped token eyJ…\.…\.… medium
|
|
33
|
+
* 10. Generic Bearer header Bearer <opaque> medium
|
|
34
|
+
* 11. Private-key PEM header -----BEGIN … PRIVATE… high
|
|
35
|
+
* 12. Pugi internal Anvil key anvil_… / sk_anvil_… high
|
|
36
|
+
*
|
|
37
|
+
* Each rule names its pattern; matches are surfaced by `pattern` name
|
|
38
|
+
* so the operator can map the rejection back to a credential type.
|
|
39
|
+
*
|
|
40
|
+
* # Confidence levels
|
|
41
|
+
*
|
|
42
|
+
* `high` — the regex is prefix-anchored to a vendor-issued shape that
|
|
43
|
+
* is rare in normal English text (e.g. `sk-ant-`, `AKIA`, `ghp_`). A
|
|
44
|
+
* single match is enough to reject.
|
|
45
|
+
*
|
|
46
|
+
* `medium` — the regex is structural (JWT, Bearer, hex blob). False
|
|
47
|
+
* positives ARE possible. Default behaviour rejects on medium matches
|
|
48
|
+
* too (security-first), but operators can downgrade to high-only via
|
|
49
|
+
* the `PUGI_MEMORY_SECRET_STRICT=0` opt-out — see `scanForSecrets`'s
|
|
50
|
+
* `strict` option.
|
|
51
|
+
*
|
|
52
|
+
* # Opt-outs
|
|
53
|
+
*
|
|
54
|
+
* - `PUGI_MEMORY_SECRET_SCAN_DISABLE=1` bypass entirely (tests).
|
|
55
|
+
* - `PUGI_MEMORY_SECRET_STRICT=0` ignore medium-confidence
|
|
56
|
+
* matches (still rejects
|
|
57
|
+
* on high).
|
|
58
|
+
* - `--allow-redacted` flag (handled by caller) opts into auto-redact
|
|
59
|
+
* instead of reject. The scanner exposes `redactSecrets` for that
|
|
60
|
+
* caller — see `runtime/commands/memory.ts`.
|
|
61
|
+
*
|
|
62
|
+
* # independent implementation provenance
|
|
63
|
+
*
|
|
64
|
+
* Inspired by the the upstream tool teamMemorySync.secretScanner pattern
|
|
65
|
+
* (intel from leak-research memos). independent implementation TypeScript
|
|
66
|
+
* implementation — no upstream code reused. Pattern vocabulary was
|
|
67
|
+
* cross-referenced against the existing
|
|
68
|
+
* `apps/pugi-cli/scripts/secret-scanner.mjs` tarball gate so a single
|
|
69
|
+
* vendor (AWS, GitHub, etc.) is handled the same way in both surfaces.
|
|
70
|
+
*/
|
|
71
|
+
/**
|
|
72
|
+
* Pattern registry. Each regex is anchored with a non-capturing
|
|
73
|
+
* boundary and uses bounded quantifiers — no `.*` and no nested
|
|
74
|
+
* alternations that would invite catastrophic backtracking.
|
|
75
|
+
*
|
|
76
|
+
* NOTE: regexes are constructed with the `g` flag here because
|
|
77
|
+
* `String.prototype.matchAll` requires it. The scanner clones each
|
|
78
|
+
* regex before iterating so concurrent calls do not share `lastIndex`.
|
|
79
|
+
*/
|
|
80
|
+
const SECRET_RULES = [
|
|
81
|
+
{
|
|
82
|
+
pattern: 'anthropic-api-key',
|
|
83
|
+
// sk-ant- followed by 40+ url-safe chars. Anthropic keys are
|
|
84
|
+
// typically 95+ chars; production keys can run 200+ chars. The
|
|
85
|
+
// greedy `{40,}` plus a negative lookahead terminates cleanly on
|
|
86
|
+
// any length without an upper bound that would silently miss
|
|
87
|
+
// longer keys (Claude reviewer P1).
|
|
88
|
+
regex: /\bsk-ant-[A-Za-z0-9_-]{40,}(?![A-Za-z0-9_-])/g,
|
|
89
|
+
confidence: 'high',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
pattern: 'openai-project-key',
|
|
93
|
+
// OpenAI project keys (`sk-proj-…`) include hyphens/underscores
|
|
94
|
+
// in the tail, so the legacy alphanumeric class would silently
|
|
95
|
+
// miss them. Anchored to the literal `sk-proj-` prefix and
|
|
96
|
+
// terminated via lookahead so length never caps detection.
|
|
97
|
+
regex: /\bsk-proj-[A-Za-z0-9_-]{40,}(?![A-Za-z0-9_-])/g,
|
|
98
|
+
confidence: 'high',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
pattern: 'openai-sk-key',
|
|
102
|
+
// OpenAI legacy "sk-" + 32+ alphanumeric. We anchor with a word
|
|
103
|
+
// boundary so prose like "the SK-Mafia" can't collide. The lower
|
|
104
|
+
// bound is 20 to align with the spec; OpenAI in practice issues
|
|
105
|
+
// 48-char keys. Lookahead-terminated so length cannot cap us out.
|
|
106
|
+
regex: /\bsk-[A-Za-z0-9]{20,}(?![A-Za-z0-9])/g,
|
|
107
|
+
confidence: 'high',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
pattern: 'github-token',
|
|
111
|
+
// ghp_ / ghs_ / gho_ / ghu_ + exactly 36 base62 chars (vendor
|
|
112
|
+
// doc'd shape).
|
|
113
|
+
regex: /\bgh[psou]_[A-Za-z0-9]{36}\b/g,
|
|
114
|
+
confidence: 'high',
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
pattern: 'aws-access-key-id',
|
|
118
|
+
// 16 uppercase alphanumerics after `AKIA` — vendor format. Word
|
|
119
|
+
// boundary keeps `MAKIAYZX...` (unlikely but possible) out.
|
|
120
|
+
regex: /\bAKIA[0-9A-Z]{16}\b/g,
|
|
121
|
+
confidence: 'high',
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
pattern: 'plane-api-token',
|
|
125
|
+
// Plane (project management) personal API tokens. Bounded to 20+
|
|
126
|
+
// url-safe chars after the prefix.
|
|
127
|
+
regex: /\bplane_api_[A-Za-z0-9]{20,}(?![A-Za-z0-9])/g, // [pugi-leak-ok]
|
|
128
|
+
confidence: 'high',
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
pattern: 'npm-token',
|
|
132
|
+
// npm_ + 36 base62 chars (vendor format).
|
|
133
|
+
regex: /\bnpm_[A-Za-z0-9]{36}\b/g,
|
|
134
|
+
confidence: 'high',
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
pattern: 'slack-token',
|
|
138
|
+
// xoxb / xoxp / xoxo / xoxa / xoxr / xoxs. Slack tokens are
|
|
139
|
+
// hyphen-segmented; we bound the trailing segment length to dodge
|
|
140
|
+
// `.*`-style backtracking.
|
|
141
|
+
regex: /\bxox[bpoars]-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])/g,
|
|
142
|
+
confidence: 'high',
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
pattern: 'stripe-key',
|
|
146
|
+
// sk_live_ / sk_test_ + 24+ base62 chars.
|
|
147
|
+
regex: /\bsk_(?:live|test)_[A-Za-z0-9]{24,}(?![A-Za-z0-9])/g,
|
|
148
|
+
confidence: 'high',
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
pattern: 'private-key-pem',
|
|
152
|
+
// PEM block headers — RSA / OPENSSH / EC / DSA / PGP. Anchored
|
|
153
|
+
// alternation, no nested quantifiers.
|
|
154
|
+
regex: /-----BEGIN (?:RSA|OPENSSH|EC|DSA|PGP) PRIVATE KEY-----/g,
|
|
155
|
+
confidence: 'high',
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
pattern: 'anvil-api-key',
|
|
159
|
+
// Pugi-internal Anvil keys — both the legacy and scoped shapes.
|
|
160
|
+
regex: /\b(?:sk_)?anvil_[A-Za-z0-9_-]{16,}(?![A-Za-z0-9_-])/g,
|
|
161
|
+
confidence: 'high',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
pattern: 'jwt-token',
|
|
165
|
+
// Three base64url segments separated by `.`. The 20-char lower
|
|
166
|
+
// bound per segment dodges the "u.s.a" trap; real JWTs have
|
|
167
|
+
// headers like `eyJhbGciOi…` which are well above this floor.
|
|
168
|
+
regex: /\beyJ[A-Za-z0-9_-]{18,}\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\b/g,
|
|
169
|
+
confidence: 'medium',
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
pattern: 'bearer-token',
|
|
173
|
+
// Authorization-header shape with an opaque 20+ char tail.
|
|
174
|
+
// Case-insensitive `Bearer` keyword. Bounded tail to avoid
|
|
175
|
+
// matching whole prose paragraphs.
|
|
176
|
+
regex: /\b[Bb]earer\s+[A-Za-z0-9_.~+/=-]{20,200}\b/g,
|
|
177
|
+
confidence: 'medium',
|
|
178
|
+
},
|
|
179
|
+
];
|
|
180
|
+
/** Resolve the strict-mode setting (env-first, opt-out via "0" / "false"). */
|
|
181
|
+
function resolveStrict(opt) {
|
|
182
|
+
if (typeof opt === 'boolean')
|
|
183
|
+
return opt;
|
|
184
|
+
const raw = process.env.PUGI_MEMORY_SECRET_STRICT;
|
|
185
|
+
if (raw === undefined)
|
|
186
|
+
return true;
|
|
187
|
+
const lowered = raw.trim().toLowerCase();
|
|
188
|
+
return !(lowered === '0' || lowered === 'false' || lowered === 'no');
|
|
189
|
+
}
|
|
190
|
+
/** Predicate — is the scanner currently disabled via env? */
|
|
191
|
+
export function isScanDisabled() {
|
|
192
|
+
const raw = process.env.PUGI_MEMORY_SECRET_SCAN_DISABLE;
|
|
193
|
+
if (raw === undefined)
|
|
194
|
+
return false;
|
|
195
|
+
const lowered = raw.trim().toLowerCase();
|
|
196
|
+
return lowered === '1' || lowered === 'true' || lowered === 'yes';
|
|
197
|
+
}
|
|
198
|
+
/** Compute 1-based line number for a byte offset. */
|
|
199
|
+
function lineNumberFor(text, offset) {
|
|
200
|
+
let line = 1;
|
|
201
|
+
for (let i = 0; i < offset && i < text.length; i += 1) {
|
|
202
|
+
if (text.charCodeAt(i) === 10)
|
|
203
|
+
line += 1;
|
|
204
|
+
}
|
|
205
|
+
return line;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Scan `text` for secret patterns. Returns one `SecretMatch` per hit,
|
|
209
|
+
* in occurrence order. Empty array means clean.
|
|
210
|
+
*
|
|
211
|
+
* The function is pure — it does NOT read env outside of resolving the
|
|
212
|
+
* strict-mode default for `options.strict`. Callers that need a fully
|
|
213
|
+
* deterministic invocation should pass `options.strict` explicitly.
|
|
214
|
+
*/
|
|
215
|
+
export function scanForSecrets(text, options) {
|
|
216
|
+
if (text.length === 0)
|
|
217
|
+
return [];
|
|
218
|
+
const strict = resolveStrict(options?.strict);
|
|
219
|
+
const out = [];
|
|
220
|
+
for (const rule of SECRET_RULES) {
|
|
221
|
+
if (!strict && rule.confidence === 'medium')
|
|
222
|
+
continue;
|
|
223
|
+
// Clone the regex per-rule so concurrent scans (e.g. parallel
|
|
224
|
+
// tests) don't share `lastIndex`. This also defends against
|
|
225
|
+
// accidental mutation if a future rule forgets the `g` flag.
|
|
226
|
+
const rx = new RegExp(rule.regex.source, rule.regex.flags);
|
|
227
|
+
for (const m of text.matchAll(rx)) {
|
|
228
|
+
if (m.index === undefined)
|
|
229
|
+
continue;
|
|
230
|
+
out.push({
|
|
231
|
+
pattern: rule.pattern,
|
|
232
|
+
match: m[0],
|
|
233
|
+
offset: m.index,
|
|
234
|
+
lineNumber: lineNumberFor(text, m.index),
|
|
235
|
+
confidence: rule.confidence,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Sort by offset so callers see a left-to-right walk of the input
|
|
240
|
+
// even when multiple rules fired in different orders.
|
|
241
|
+
out.sort((a, b) => a.offset - b.offset);
|
|
242
|
+
return out;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Replace every detected secret with a `[SECRET:<pattern>]` placeholder
|
|
246
|
+
* and return both the redacted text and the original match list. The
|
|
247
|
+
* pattern name is included in the placeholder so the operator can see
|
|
248
|
+
* what category was scrubbed.
|
|
249
|
+
*
|
|
250
|
+
* Redaction respects the same strict-mode resolution as `scanForSecrets`.
|
|
251
|
+
*/
|
|
252
|
+
export function redactSecrets(text, options) {
|
|
253
|
+
const matches = scanForSecrets(text, options);
|
|
254
|
+
if (matches.length === 0)
|
|
255
|
+
return { redacted: text, matches };
|
|
256
|
+
// Walk right-to-left so earlier offsets stay valid as we splice.
|
|
257
|
+
let redacted = text;
|
|
258
|
+
for (let i = matches.length - 1; i >= 0; i -= 1) {
|
|
259
|
+
const m = matches[i];
|
|
260
|
+
if (m === undefined)
|
|
261
|
+
continue;
|
|
262
|
+
const before = redacted.slice(0, m.offset);
|
|
263
|
+
const after = redacted.slice(m.offset + m.match.length);
|
|
264
|
+
redacted = `${before}[SECRET:${m.pattern}]${after}`;
|
|
265
|
+
}
|
|
266
|
+
return { redacted, matches };
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Hard guard for memory write entry points. Throws a
|
|
270
|
+
* `MemorySecretGuardError` on any non-empty match set (after the env
|
|
271
|
+
* disable check). Caller side handles the `--allow-redacted` flow by
|
|
272
|
+
* calling `redactSecrets` directly.
|
|
273
|
+
*
|
|
274
|
+
* Returns the (unchanged) input string when the scan is clean — lets
|
|
275
|
+
* the call site stay one-liner: `content = assertNoSecrets(content)`.
|
|
276
|
+
*/
|
|
277
|
+
export function assertNoSecrets(text, options) {
|
|
278
|
+
if (isScanDisabled())
|
|
279
|
+
return text;
|
|
280
|
+
const matches = scanForSecrets(text, options);
|
|
281
|
+
if (matches.length === 0)
|
|
282
|
+
return text;
|
|
283
|
+
throw new MemorySecretGuardError(matches);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Thrown when memory content would persist a credential. The error
|
|
287
|
+
* carries the structured match list so the CLI can render an
|
|
288
|
+
* actionable message (pattern names + line numbers) without
|
|
289
|
+
* re-leaking the secrets back into the operator's terminal.
|
|
290
|
+
*/
|
|
291
|
+
export class MemorySecretGuardError extends Error {
|
|
292
|
+
matches;
|
|
293
|
+
constructor(matches) {
|
|
294
|
+
const names = Array.from(new Set(matches.map((m) => m.pattern))).join(', ');
|
|
295
|
+
super(`pugi memory: refused to persist content containing secret(s) — pattern(s): ${names}. ` +
|
|
296
|
+
`Set PUGI_MEMORY_SECRET_SCAN_DISABLE=1 to bypass, or rewrite the content. ` +
|
|
297
|
+
`Use \`pugi memory write --allow-redacted\` to auto-scrub.`);
|
|
298
|
+
this.name = 'MemorySecretGuardError';
|
|
299
|
+
this.matches = matches;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/** Stable export for tests + integration call sites. */
|
|
303
|
+
export const SECRET_PATTERN_NAMES = SECRET_RULES.map((r) => r.pattern);
|
|
304
|
+
//# sourceMappingURL=secret-scanner.js.map
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pugi memory sync queue .
|
|
3
|
+
*
|
|
4
|
+
* Local pending-write queue for `pugi memory` commands when the
|
|
5
|
+
* operator is offline or the admin-api is unreachable. Each pending
|
|
6
|
+
* mutation lands on disk as one JSONL line; `pugi memory sync` reads
|
|
7
|
+
* the queue, fires them to the admin-api in order, and rewrites the
|
|
8
|
+
* file with only the entries that still failed.
|
|
9
|
+
*
|
|
10
|
+
* Storage:
|
|
11
|
+
*
|
|
12
|
+
* ~/.pugi/memory-queue.jsonl (mode 0600)
|
|
13
|
+
*
|
|
14
|
+
* Each line is a fully-typed `PendingMemoryOperation` envelope. The
|
|
15
|
+
* envelope is forward-compatible: an older CLI reading a JSONL file
|
|
16
|
+
* written by a newer CLI silently skips lines whose `op` field is
|
|
17
|
+
* not in its known set (so a partial-rollback scenario doesn't crash
|
|
18
|
+
* the queue).
|
|
19
|
+
*
|
|
20
|
+
* Design intent:
|
|
21
|
+
* - Append-only on disk for the hot path (`pugi memory write` /
|
|
22
|
+
* `pugi memory forget` queue when offline). Rewrites only on
|
|
23
|
+
* successful sync.
|
|
24
|
+
* - One file per operator (PUGI_HOME-aware). Queue is local to the
|
|
25
|
+
* machine — no cross-host coordination. Multi-device sync is
|
|
26
|
+
* deferred to Phase 6 (server-side outbox).
|
|
27
|
+
* - No fsync / atomic rename ceremony in v1 — best effort. The
|
|
28
|
+
* queue is a convenience surface, not a durability primitive;
|
|
29
|
+
* the source of truth is the admin-api row.
|
|
30
|
+
*/
|
|
31
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from 'node:fs';
|
|
32
|
+
import { homedir } from 'node:os';
|
|
33
|
+
import { dirname, resolve } from 'node:path';
|
|
34
|
+
import { z } from 'zod';
|
|
35
|
+
import { assertNoSecrets } from '../memory/secret-scanner.js';
|
|
36
|
+
/** Six canonical kinds — must mirror `apps/admin-api/src/persona-memory/persona-memory.types.ts`. */
|
|
37
|
+
export const PERSONA_MEMORY_KINDS = [
|
|
38
|
+
'pattern',
|
|
39
|
+
'preference',
|
|
40
|
+
'architecture',
|
|
41
|
+
'bug',
|
|
42
|
+
'workflow',
|
|
43
|
+
'fact',
|
|
44
|
+
];
|
|
45
|
+
const writeOpSchema = z.object({
|
|
46
|
+
op: z.literal('write'),
|
|
47
|
+
enqueuedAt: z.string().datetime(),
|
|
48
|
+
personaSlug: z.string().min(1).max(64),
|
|
49
|
+
kind: z.enum(PERSONA_MEMORY_KINDS),
|
|
50
|
+
content: z.string().min(1).max(4000),
|
|
51
|
+
forgetAfter: z.string().datetime().nullable().optional(),
|
|
52
|
+
});
|
|
53
|
+
const forgetOpSchema = z.object({
|
|
54
|
+
op: z.literal('forget'),
|
|
55
|
+
enqueuedAt: z.string().datetime(),
|
|
56
|
+
id: z.string().min(1),
|
|
57
|
+
});
|
|
58
|
+
const pendingMemoryOpSchema = z.discriminatedUnion('op', [
|
|
59
|
+
writeOpSchema,
|
|
60
|
+
forgetOpSchema,
|
|
61
|
+
]);
|
|
62
|
+
/** Default storage path. Override via `PUGI_HOME` for tests / multi-account. */
|
|
63
|
+
export function defaultQueuePath() {
|
|
64
|
+
const root = process.env.PUGI_HOME ?? resolve(homedir(), '.pugi');
|
|
65
|
+
return resolve(root, 'memory-queue.jsonl');
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Append one pending operation to the queue file. Creates the parent
|
|
69
|
+
* directory + file with mode 0600 if missing. Pure-disk, no network.
|
|
70
|
+
*
|
|
71
|
+
* Returns the count of pending ops after the append (1-based) so the
|
|
72
|
+
* CLI command can render "queued (3 pending) — run `pugi memory sync`".
|
|
73
|
+
*/
|
|
74
|
+
export function enqueueMemoryOp(op, pathOverride) {
|
|
75
|
+
const fullOp = {
|
|
76
|
+
...op,
|
|
77
|
+
enqueuedAt: new Date().toISOString(),
|
|
78
|
+
};
|
|
79
|
+
pendingMemoryOpSchema.parse(fullOp);
|
|
80
|
+
// Backlog (P1 security hardening): refuse to enqueue any write
|
|
81
|
+
// whose `content` matches a known secret shape (sk-ant-, ghp_, AKIA…
|
|
82
|
+
// etc). `assertNoSecrets` throws `MemorySecretGuardError` carrying
|
|
83
|
+
// the matched pattern list, which the CLI catches and renders
|
|
84
|
+
// without re-leaking the value back to the operator. The opt-out
|
|
85
|
+
// env (`PUGI_MEMORY_SECRET_SCAN_DISABLE=1`) is honoured inside
|
|
86
|
+
// `assertNoSecrets` so tests + emergency overrides have a knob.
|
|
87
|
+
// `forget` ops carry only an id, so they bypass the scan.
|
|
88
|
+
if (fullOp.op === 'write') {
|
|
89
|
+
assertNoSecrets(fullOp.content);
|
|
90
|
+
}
|
|
91
|
+
const queuePath = pathOverride ?? defaultQueuePath();
|
|
92
|
+
ensureQueueFile(queuePath);
|
|
93
|
+
const existing = readFileSync(queuePath, 'utf-8');
|
|
94
|
+
const line = `${JSON.stringify(fullOp)}\n`;
|
|
95
|
+
writeFileSync(queuePath, `${existing}${line}`, { encoding: 'utf-8', mode: 0o600 });
|
|
96
|
+
// Best-effort chmod (in case the file existed already at the wrong mode).
|
|
97
|
+
try {
|
|
98
|
+
chmodSync(queuePath, 0o600);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// ignore — the file was just written above, mode might be platform-dependent.
|
|
102
|
+
}
|
|
103
|
+
return countPending(queuePath);
|
|
104
|
+
}
|
|
105
|
+
/** Read the queue file and return parsed entries. Skips unknown / malformed lines. */
|
|
106
|
+
export function readMemoryQueue(pathOverride) {
|
|
107
|
+
const queuePath = pathOverride ?? defaultQueuePath();
|
|
108
|
+
if (!existsSync(queuePath))
|
|
109
|
+
return [];
|
|
110
|
+
const raw = readFileSync(queuePath, 'utf-8');
|
|
111
|
+
const out = [];
|
|
112
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
113
|
+
const trimmed = line.trim();
|
|
114
|
+
if (!trimmed)
|
|
115
|
+
continue;
|
|
116
|
+
try {
|
|
117
|
+
const parsed = pendingMemoryOpSchema.parse(JSON.parse(trimmed));
|
|
118
|
+
out.push(parsed);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// forward-compat: a future op kind we don't recognise should not
|
|
122
|
+
// crash the queue; just drop the line during this read.
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
/** Rewrite the queue file with `remaining` entries only. Empty array clears the file. */
|
|
129
|
+
export function rewriteMemoryQueue(remaining, pathOverride) {
|
|
130
|
+
const queuePath = pathOverride ?? defaultQueuePath();
|
|
131
|
+
ensureQueueFile(queuePath);
|
|
132
|
+
if (remaining.length === 0) {
|
|
133
|
+
writeFileSync(queuePath, '', { encoding: 'utf-8', mode: 0o600 });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const body = remaining.map((op) => JSON.stringify(op)).join('\n') + '\n';
|
|
137
|
+
writeFileSync(queuePath, body, { encoding: 'utf-8', mode: 0o600 });
|
|
138
|
+
}
|
|
139
|
+
/** Count pending ops without re-parsing every line individually for the typed shape. */
|
|
140
|
+
export function countPending(pathOverride) {
|
|
141
|
+
const queuePath = pathOverride ?? defaultQueuePath();
|
|
142
|
+
if (!existsSync(queuePath))
|
|
143
|
+
return 0;
|
|
144
|
+
const raw = readFileSync(queuePath, 'utf-8');
|
|
145
|
+
let n = 0;
|
|
146
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
147
|
+
if (line.trim().length > 0)
|
|
148
|
+
n++;
|
|
149
|
+
}
|
|
150
|
+
return n;
|
|
151
|
+
}
|
|
152
|
+
/** Quick predicate — was anything ever queued? */
|
|
153
|
+
export function hasPendingOps(pathOverride) {
|
|
154
|
+
return countPending(pathOverride) > 0;
|
|
155
|
+
}
|
|
156
|
+
function ensureQueueFile(queuePath) {
|
|
157
|
+
const dir = dirname(queuePath);
|
|
158
|
+
if (!existsSync(dir))
|
|
159
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
160
|
+
if (!existsSync(queuePath)) {
|
|
161
|
+
writeFileSync(queuePath, '', { encoding: 'utf-8', mode: 0o600 });
|
|
162
|
+
try {
|
|
163
|
+
chmodSync(queuePath, 0o600);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// ignore
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=queue.js.map
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metric extraction primitive (task P1).
|
|
3
|
+
*
|
|
4
|
+
* Scans a log buffer line by line for named regex patterns and emits one
|
|
5
|
+
* `ExtractedMetric` per match. Used by the pugi_eval harness to lift
|
|
6
|
+
* karpathy-style flat log lines (e.g. `e2e: total=51 pass=45 fail=6
|
|
7
|
+
* dur=170.48s`) into structured measurements without committing to a
|
|
8
|
+
* full log-parser dependency.
|
|
9
|
+
*
|
|
10
|
+
* Contract:
|
|
11
|
+
* - Every supplied pattern MUST contain a numeric capture group 1.
|
|
12
|
+
* Patterns lacking a capture group throw a clear error rather than
|
|
13
|
+
* silently emitting NaN — a missing group is a programming bug, not
|
|
14
|
+
* a data condition.
|
|
15
|
+
* - Lines that don't match are skipped, not reported. Callers that
|
|
16
|
+
* need per-line trace coverage should layer their own scanner.
|
|
17
|
+
* - Each match emits a fresh ISO-8601 timestamp via `new Date()`. The
|
|
18
|
+
* timestamp reflects extraction time, not the moment the log line
|
|
19
|
+
* was produced — log producers that need true event time should
|
|
20
|
+
* embed it themselves and pattern it out.
|
|
21
|
+
* - Regex `lastIndex` state is not relied upon: we test each line as
|
|
22
|
+
* a self-contained string. Patterns may be sticky or global without
|
|
23
|
+
* affecting correctness.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Scan `logText` for each named pattern and emit one metric per match.
|
|
27
|
+
*
|
|
28
|
+
* Output order: outer loop over the patterns map insertion order, inner
|
|
29
|
+
* loop over lines in source order. So all `pugi_score` hits arrive
|
|
30
|
+
* before all `tokens_in` hits when both patterns fire on the same
|
|
31
|
+
* buffer. Tests rely on this stable ordering; do not switch to a
|
|
32
|
+
* line-major loop without updating the spec.
|
|
33
|
+
*/
|
|
34
|
+
export function extractMetrics(logText, patterns) {
|
|
35
|
+
const lines = logText.split(/\r?\n/);
|
|
36
|
+
const out = [];
|
|
37
|
+
for (const [name, pattern] of Object.entries(patterns)) {
|
|
38
|
+
assertHasCaptureGroup(name, pattern);
|
|
39
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
40
|
+
const line = lines[i] ?? '';
|
|
41
|
+
const match = line.match(pattern);
|
|
42
|
+
if (!match)
|
|
43
|
+
continue;
|
|
44
|
+
const raw = match[1];
|
|
45
|
+
if (raw === undefined)
|
|
46
|
+
continue;
|
|
47
|
+
const value = parseFloat(raw);
|
|
48
|
+
if (!Number.isFinite(value))
|
|
49
|
+
continue;
|
|
50
|
+
out.push({
|
|
51
|
+
name,
|
|
52
|
+
value,
|
|
53
|
+
ts: new Date().toISOString(),
|
|
54
|
+
line: i + 1,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Common pugi_eval log patterns. Case-insensitive on the metric key so
|
|
62
|
+
* `Pugi_Score`, `PUGI_SCORE`, and `pugi_score` all match — karpathy-style
|
|
63
|
+
* harness output varies. The numeric capture group is always group 1.
|
|
64
|
+
*
|
|
65
|
+
* Separator class `[:\s=]+` accepts `key: value`, `key=value`, and
|
|
66
|
+
* `key value` shapes. Numeric class `[0-9.]+` is liberal enough for
|
|
67
|
+
* scientific-notation-free floats; exotic encodings (1e6, NaN, hex)
|
|
68
|
+
* are not in scope — pugi_eval emits decimal-only.
|
|
69
|
+
*/
|
|
70
|
+
export const DEFAULT_PATTERNS = {
|
|
71
|
+
pugi_score: /pugi_score[:\s=]+([0-9.]+)/i,
|
|
72
|
+
tokens_in: /tokens.in[:\s=]+([0-9]+)/i,
|
|
73
|
+
tokens_out: /tokens.out[:\s=]+([0-9]+)/i,
|
|
74
|
+
wall_clock_sec: /wall.clock.*?([0-9.]+)\s*s(?:ec)?/i,
|
|
75
|
+
dur: /dur[:\s=]+([0-9.]+)/i,
|
|
76
|
+
pass: /pass[:\s=]+([0-9]+)/i,
|
|
77
|
+
fail: /fail[:\s=]+([0-9]+)/i,
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Validate that `pattern` has at least one capture group by probing the
|
|
81
|
+
* regex source. Throws a clear `Error` rather than letting `match[1]`
|
|
82
|
+
* silently return `undefined` at every line — a missing group is a
|
|
83
|
+
* programmer error and we want it loud on first call.
|
|
84
|
+
*
|
|
85
|
+
* Detection is heuristic but conservative: counts `(` characters that
|
|
86
|
+
* are not preceded by `\` and not opening a `(?:` non-capturing group
|
|
87
|
+
* or `(?=` / `(?!` lookaround. Good enough for the patterns we ship;
|
|
88
|
+
* patterns that fool the heuristic will still be caught by the runtime
|
|
89
|
+
* `parseFloat(undefined) → NaN` skip — they just won't emit metrics.
|
|
90
|
+
*/
|
|
91
|
+
function assertHasCaptureGroup(name, pattern) {
|
|
92
|
+
const src = pattern.source;
|
|
93
|
+
let depth = 0;
|
|
94
|
+
for (let i = 0; i < src.length; i += 1) {
|
|
95
|
+
const ch = src[i];
|
|
96
|
+
if (ch === '\\') {
|
|
97
|
+
i += 1;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (ch !== '(')
|
|
101
|
+
continue;
|
|
102
|
+
const next = src[i + 1];
|
|
103
|
+
const next2 = src[i + 2];
|
|
104
|
+
if (next === '?' && (next2 === ':' || next2 === '=' || next2 === '!' || next2 === '<')) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
depth += 1;
|
|
108
|
+
}
|
|
109
|
+
if (depth === 0) {
|
|
110
|
+
throw new Error(`extractMetrics: pattern "${name}" has no capture group; add (...) around the numeric value`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=extract.js.map
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Defines the operational modes for the Roo agent, controlling tool access and behavior.
|
|
3
|
+
*/
|
|
4
|
+
export const MODES = {
|
|
5
|
+
architect: {
|
|
6
|
+
name: 'architect',
|
|
7
|
+
description: 'Researches and proposes solutions, but does not implement them.',
|
|
8
|
+
allowed_groups: ['read', 'web', 'memory'],
|
|
9
|
+
system_prompt_addendum: 'You are an architect. Read + propose. Do not edit.',
|
|
10
|
+
},
|
|
11
|
+
code: {
|
|
12
|
+
name: 'code',
|
|
13
|
+
description: 'Implements code changes as specified.',
|
|
14
|
+
allowed_groups: ['read', 'edit', 'bash', 'web', 'mcp', 'memory'],
|
|
15
|
+
system_prompt_addendum: 'Implement как specified.',
|
|
16
|
+
},
|
|
17
|
+
ask: {
|
|
18
|
+
name: 'ask',
|
|
19
|
+
description: 'Answers questions about the codebase without making changes.',
|
|
20
|
+
allowed_groups: ['read', 'web', 'memory'],
|
|
21
|
+
system_prompt_addendum: 'Answer questions. Do not modify state.',
|
|
22
|
+
},
|
|
23
|
+
debug: {
|
|
24
|
+
name: 'debug',
|
|
25
|
+
description: 'Investigates issues and can run commands, but requires approval for edits.',
|
|
26
|
+
allowed_groups: ['read', 'bash', 'web', 'memory'],
|
|
27
|
+
system_prompt_addendum: 'Investigate root cause. Edit only after explicit user approval.',
|
|
28
|
+
},
|
|
29
|
+
orchestrator: {
|
|
30
|
+
name: 'orchestrator',
|
|
31
|
+
description: 'Coordinates sub-agents to accomplish complex tasks.',
|
|
32
|
+
allowed_groups: ['read', 'web', 'agent_spawn', 'memory'],
|
|
33
|
+
system_prompt_addendum: 'Coordinate sub-agents. Delegate edits.',
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Checks if a specific tool is allowed in a given agent mode.
|
|
38
|
+
*
|
|
39
|
+
* @param mode The current mode of the agent.
|
|
40
|
+
* @param tool The name of the tool being checked.
|
|
41
|
+
* @param group The tool group the tool belongs to.
|
|
42
|
+
* @returns True if the tool is allowed, false otherwise.
|
|
43
|
+
*/
|
|
44
|
+
export function isToolAllowed(mode, tool, group) {
|
|
45
|
+
const modeDef = MODES[mode];
|
|
46
|
+
if (!modeDef) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
const groupAllowed = modeDef.allowed_groups.includes(group);
|
|
50
|
+
if (!groupAllowed) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
const toolDenied = modeDef.denied_tools?.includes(tool) ?? false;
|
|
54
|
+
if (toolDenied) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Retrieves the system prompt addendum for a given mode.
|
|
61
|
+
*
|
|
62
|
+
* @param mode The current mode of the agent.
|
|
63
|
+
* @returns The system prompt addendum string, or an empty string if none is defined.
|
|
64
|
+
*/
|
|
65
|
+
export function getModeAddendum(mode) {
|
|
66
|
+
return MODES[mode]?.system_prompt_addendum ?? '';
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=roo-modes.js.map
|