@pugi/cli 0.1.0-beta.7 → 0.1.0-beta.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +96 -0
- package/THIRD_PARTY_NOTICES.md +40 -0
- package/assets/pugi-prozr2-mascot.ansi +9 -0
- package/bin/run.js +33 -1
- package/dist/commands/deploy.js +40 -40
- package/dist/commands/flatten.js +191 -0
- package/dist/commands/jobs-watch.js +201 -0
- package/dist/commands/jobs.js +42 -27
- package/dist/commands/smoke.js +133 -0
- package/dist/core/agent-progress/cleanup.js +134 -0
- package/dist/core/agent-progress/schema.js +144 -0
- package/dist/core/agent-progress/writer.js +101 -0
- package/dist/core/agents/adaptive-router.js +330 -0
- package/dist/core/agents/query-decomposer.js +297 -0
- package/dist/core/agents/registry.js +2 -2
- package/dist/core/approvals/shortcut-resolver.js +98 -0
- package/dist/core/artifact-chain/dispatcher.js +148 -0
- package/dist/core/artifact-chain/exporter.js +164 -0
- package/dist/core/artifact-chain/state.js +243 -0
- package/dist/core/artifact-chain/steps.js +169 -0
- package/dist/core/ask-user/question.js +92 -0
- package/dist/core/audit/audit-trail.js +275 -0
- package/dist/core/auth/ensure-authenticated.js +129 -0
- package/dist/core/auth/env-provider.js +238 -0
- package/dist/core/auto-open-browser.js +4 -4
- package/dist/core/auto-update/channels.js +122 -0
- package/dist/core/auto-update/checker.js +241 -0
- package/dist/core/auto-update/state.js +235 -0
- package/dist/core/bare-mode/index.js +107 -0
- package/dist/core/bash/redirect.js +281 -0
- package/dist/core/bash-classifier.js +436 -40
- package/dist/core/checkpoint/resumer.js +149 -0
- package/dist/core/checkpoint/rewinder.js +291 -0
- package/dist/core/checkpoints/shadow-git.js +670 -0
- package/dist/core/citations/parser.js +109 -0
- package/dist/core/classifier/yolo-classifier.js +88 -0
- package/dist/core/codegraph/decision-store.js +248 -0
- package/dist/core/codegraph/detect-repo.js +459 -0
- package/dist/core/codegraph/install.js +134 -0
- package/dist/core/codegraph/offer-hook.js +220 -0
- package/dist/core/compact/auto-trigger.js +96 -0
- package/dist/core/compact/buffer-rewriter.js +115 -0
- package/dist/core/compact/summarizer.js +208 -0
- package/dist/core/compact/token-counter.js +108 -0
- package/dist/core/consensus/anvil-fanout.js +25 -25
- package/dist/core/consensus/diff-capture.js +121 -12
- package/dist/core/consensus/rubric.js +21 -21
- package/dist/core/context/builder.js +6 -6
- package/dist/core/context/compaction-events.js +8 -8
- package/dist/core/context/compaction.js +31 -31
- package/dist/core/context/index.js +15 -8
- package/dist/core/context/invariants.js +51 -51
- package/dist/core/context/markdown-loader.js +28 -10
- package/dist/core/context/markdown-traverse.js +255 -0
- package/dist/core/context/pugiignore.js +41 -41
- package/dist/core/context/repo-skeleton.js +37 -37
- package/dist/core/context/tool-eviction.js +55 -0
- package/dist/core/context/watcher.js +32 -32
- package/dist/core/context/working-set.js +23 -23
- package/dist/core/coordinator/agent-tools.js +77 -0
- package/dist/core/coordinator/agent-toolset.js +65 -0
- package/dist/core/coordinator/fsm.js +73 -0
- package/dist/core/coordinator/mode-fsm.js +70 -0
- package/dist/core/cost/rate-card.js +129 -0
- package/dist/core/cost/tracker.js +221 -0
- package/dist/core/credentials.js +12 -12
- package/dist/core/cron/scheduler.js +138 -0
- package/dist/core/denial-tracking/index.js +8 -0
- package/dist/core/denial-tracking/state.js +264 -0
- package/dist/core/diagnostics/probe-runner.js +93 -0
- package/dist/core/diagnostics/probes/api.js +46 -0
- package/dist/core/diagnostics/probes/auth.js +93 -0
- package/dist/core/diagnostics/probes/bare-mode.js +42 -0
- package/dist/core/diagnostics/probes/cli-version.js +127 -0
- package/dist/core/diagnostics/probes/config.js +72 -0
- package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
- package/dist/core/diagnostics/probes/disk.js +81 -0
- package/dist/core/diagnostics/probes/engine-live.js +46 -0
- package/dist/core/diagnostics/probes/git.js +65 -0
- package/dist/core/diagnostics/probes/hooks.js +118 -0
- package/dist/core/diagnostics/probes/mcp.js +75 -0
- package/dist/core/diagnostics/probes/node.js +59 -0
- package/dist/core/diagnostics/probes/pnpm.js +36 -0
- package/dist/core/diagnostics/probes/pugi-md.js +89 -0
- package/dist/core/diagnostics/probes/sandbox.js +40 -0
- package/dist/core/diagnostics/probes/session.js +74 -0
- package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
- package/dist/core/diagnostics/probes/workspace.js +63 -0
- package/dist/core/diagnostics/types.js +70 -0
- package/dist/core/dispatch/cache-cleanup.js +197 -0
- package/dist/core/dispatch/cache-handoff.js +295 -0
- package/dist/core/edits/apply-patch-layer-e.js +189 -0
- package/dist/core/edits/dispatch.js +293 -7
- package/dist/core/edits/format-matrix.js +26 -0
- package/dist/core/edits/fuzzy-ladder.js +650 -0
- package/dist/core/edits/index.js +3 -1
- package/dist/core/edits/journal.js +199 -0
- package/dist/core/edits/layer-a-apply.js +15 -15
- package/dist/core/edits/layer-a-fuzzy-apply.js +198 -0
- package/dist/core/edits/layer-b-apply.js +9 -9
- package/dist/core/edits/layer-c-apply.js +6 -6
- package/dist/core/edits/layer-d-ast.js +557 -14
- package/dist/core/edits/marker-parser.js +12 -12
- package/dist/core/edits/security-gate.js +27 -27
- package/dist/core/edits/verify-hook.js +273 -0
- package/dist/core/edits/worktree.js +322 -0
- package/dist/core/engine/anvil-client.js +140 -26
- package/dist/core/engine/auto-compact.js +179 -0
- package/dist/core/engine/budgets.js +186 -0
- package/dist/core/engine/context-prefix.js +155 -0
- package/dist/core/engine/index.js +1 -1
- package/dist/core/engine/intensity.js +158 -0
- package/dist/core/engine/intent.js +260 -0
- package/dist/core/engine/native-pugi.js +1295 -227
- package/dist/core/engine/prompts.js +134 -16
- package/dist/core/engine/strip-internal-fields.js +124 -0
- package/dist/core/engine/tool-bridge.js +1295 -59
- package/dist/core/evaluation/golden-dataset.js +293 -0
- package/dist/core/feedback/queue.js +177 -0
- package/dist/core/feedback/submitter.js +145 -0
- package/dist/core/file-cache.js +113 -1
- package/dist/core/flatten/flatten-repo.js +439 -0
- package/dist/core/format/osc8-link.js +28 -0
- package/dist/core/hook-chains.js +392 -0
- package/dist/core/hooks/citation-verify-hook.js +138 -0
- package/dist/core/hooks/citation-verify.js +112 -0
- package/dist/core/hooks/events.js +44 -0
- package/dist/core/hooks/index.js +15 -0
- package/dist/core/hooks/registry.js +213 -0
- package/dist/core/hooks/runner.js +236 -0
- package/dist/core/hooks/v2/event-emitter.js +115 -0
- package/dist/core/hooks/v2/executor.js +282 -0
- package/dist/core/hooks/v2/index.js +25 -0
- package/dist/core/hooks/v2/lifecycle.js +104 -0
- package/dist/core/hooks/v2/loader.js +216 -0
- package/dist/core/hooks/v2/matcher.js +125 -0
- package/dist/core/hooks/v2/trust.js +143 -0
- package/dist/core/hooks/v2/types.js +86 -0
- package/dist/core/image/renderer.js +71 -0
- package/dist/core/init/detector.js +582 -0
- package/dist/core/init/template-renderer.js +242 -0
- package/dist/core/jobs/registry.js +18 -18
- package/dist/core/ledger/results-tsv.js +142 -0
- package/dist/core/log-discipline/stdout-redirect.js +51 -0
- package/dist/core/lsp/cache.js +105 -0
- package/dist/core/lsp/client.js +776 -0
- package/dist/core/lsp/language-detect.js +66 -0
- package/dist/core/lsp/post-edit-diagnostics.js +171 -0
- package/dist/core/lsp/symbol-tools.js +372 -0
- package/dist/core/mcp/client.js +97 -28
- package/dist/core/mcp/http-server.js +553 -0
- package/dist/core/mcp/orchestrator-tools.js +662 -0
- package/dist/core/mcp/permission.js +190 -0
- package/dist/core/mcp/registry.js +39 -17
- package/dist/core/mcp/server-tools.js +219 -0
- package/dist/core/mcp/server.js +397 -0
- package/dist/core/mcp/trust.js +10 -10
- package/dist/core/memory/dual-write.js +416 -0
- package/dist/core/memory/passive-extract.js +130 -0
- package/dist/core/memory/phase1-kinds.js +20 -0
- package/dist/core/memory/secret-scanner.js +304 -0
- package/dist/core/memory-sync/queue.js +170 -0
- package/dist/core/metrics/extract.js +113 -0
- package/dist/core/modes/roo-modes.js +68 -0
- package/dist/core/onboarding/ensure-initialized.js +133 -0
- package/dist/core/onboarding/marker.js +111 -0
- package/dist/core/onboarding/telemetry-state.js +108 -0
- package/dist/core/output-style/presets.js +176 -0
- package/dist/core/output-style/state.js +185 -0
- package/dist/core/path-security.js +287 -5
- package/dist/core/permission.js +82 -22
- package/dist/core/permissions/auto-classifier.js +124 -0
- package/dist/core/permissions/bash-parser.js +371 -0
- package/dist/core/permissions/circuit-breaker.js +83 -0
- package/dist/core/permissions/constrained-edit.js +91 -0
- package/dist/core/permissions/gate.js +278 -0
- package/dist/core/permissions/index.js +20 -0
- package/dist/core/permissions/mode.js +174 -0
- package/dist/core/permissions/network-egress.js +137 -0
- package/dist/core/permissions/state.js +241 -0
- package/dist/core/permissions/tool-class.js +93 -0
- package/dist/core/plan-mode/ui-state.js +51 -0
- package/dist/core/plans/plan-artifact.js +721 -0
- package/dist/core/policy-limits/etag-store.js +122 -0
- package/dist/core/prd-check/parser.js +215 -0
- package/dist/core/prd-check/reporter.js +127 -0
- package/dist/core/prd-check/session-review.js +557 -0
- package/dist/core/prd-check/verifiers.js +223 -0
- package/dist/core/prompt-cache/client-cache.js +99 -0
- package/dist/core/prompts/assembly.js +29 -0
- package/dist/core/prompts/registry.js +364 -0
- package/dist/core/pugi-md/cc-compat-rules.js +735 -0
- package/dist/core/pugi-md/context-injector.js +76 -0
- package/dist/core/pugi-md/walk-up.js +207 -0
- package/dist/core/python/uv-installer.js +270 -0
- package/dist/core/python/uv-resolver.js +83 -0
- package/dist/core/rate-limit/narrator.js +146 -0
- package/dist/core/recipes/cli-types.js +20 -0
- package/dist/core/recipes/loader.js +103 -0
- package/dist/core/recipes/runner.js +345 -0
- package/dist/core/recipes/schema.js +587 -0
- package/dist/core/release-notes/parser.js +241 -0
- package/dist/core/release-notes/state.js +116 -0
- package/dist/core/repl/ask.js +37 -37
- package/dist/core/repl/cancellation.js +26 -26
- package/dist/core/repl/cap-warning.js +4 -4
- package/dist/core/repl/clipboard-read.js +11 -11
- package/dist/core/repl/dispatch-fsm.js +12 -12
- package/dist/core/repl/history-search.js +15 -15
- package/dist/core/repl/history.js +28 -18
- package/dist/core/repl/kill-ring.js +5 -5
- package/dist/core/repl/model-pricing.js +135 -0
- package/dist/core/repl/privacy-banner.js +22 -22
- package/dist/core/repl/session.js +2157 -214
- package/dist/core/repl/slash-commands.js +533 -40
- package/dist/core/repl/store/index.js +1 -1
- package/dist/core/repl/store/jsonl-log.js +22 -22
- package/dist/core/repl/store/lockfile.js +10 -10
- package/dist/core/repl/store/session-store.js +136 -107
- package/dist/core/repl/store/types.js +15 -15
- package/dist/core/repl/store/uuid-v7.js +12 -12
- package/dist/core/repl/workspace-context.js +43 -21
- package/dist/core/repo-map/build.js +125 -0
- package/dist/core/repo-map/cache.js +185 -0
- package/dist/core/repo-map/extractor.js +254 -0
- package/dist/core/repo-map/formatter.js +145 -0
- package/dist/core/repo-map/page-rank.js +105 -0
- package/dist/core/repo-map/scanner.js +211 -0
- package/dist/core/retry-budget/budget.js +284 -0
- package/dist/core/retry-budget/index.js +5 -0
- package/dist/core/retry-budget/retry-cap.js +74 -0
- package/dist/core/routing/lead-worker.js +43 -0
- package/dist/core/routing/pre-flight-estimator.js +108 -0
- package/dist/core/runs/run-tree.js +103 -0
- package/dist/core/security/injection-scanner.js +367 -0
- package/dist/core/security/output-filter.js +418 -0
- package/dist/core/session/env-file.js +105 -0
- package/dist/core/session/section-budgets.js +140 -0
- package/dist/core/session.js +92 -0
- package/dist/core/settings.js +286 -5
- package/dist/core/share/formatter.js +271 -0
- package/dist/core/share/redactor.js +221 -0
- package/dist/core/share/uploader.js +267 -0
- package/dist/core/skills/defaults.js +457 -0
- package/dist/core/skills/loader.js +22 -22
- package/dist/core/skills/sources.js +27 -27
- package/dist/core/smoke/headless-driver.js +174 -0
- package/dist/core/smoke/orchestrator.js +194 -0
- package/dist/core/smoke/runner.js +238 -0
- package/dist/core/smoke/scenario-parser.js +316 -0
- package/dist/core/statusline.js +99 -0
- package/dist/core/subagents/dispatcher-real.js +600 -0
- package/dist/core/subagents/dispatcher.js +132 -43
- package/dist/core/subagents/index.js +19 -6
- package/dist/core/subagents/isolation-matrix.js +213 -0
- package/dist/core/subagents/spawn.js +19 -4
- package/dist/core/telemetry/emitter.js +229 -0
- package/dist/core/telemetry/queue.js +251 -0
- package/dist/core/theme/context.js +91 -0
- package/dist/core/theme/presets.js +228 -0
- package/dist/core/theme/state.js +181 -0
- package/dist/core/todos/invariant.js +10 -0
- package/dist/core/todos/state.js +177 -0
- package/dist/core/tool-schema/compressor.js +89 -0
- package/dist/core/transport/version-interceptor.js +166 -0
- package/dist/core/trust.js +2 -2
- package/dist/core/tui/thinking-block.js +64 -0
- package/dist/core/vim/keymap.js +288 -0
- package/dist/core/vim/state.js +92 -0
- package/dist/core/watch-markers/marker-watcher.js +133 -0
- package/dist/core/worktree-manager/cleanup.js +123 -0
- package/dist/core/worktree-manager/manager.js +303 -0
- package/dist/index.js +28 -0
- package/dist/runtime/bootstrap.js +190 -0
- package/dist/runtime/cli.js +4162 -488
- package/dist/runtime/commands/agents.js +30 -30
- package/dist/runtime/commands/budget.js +5 -5
- package/dist/runtime/commands/cancel.js +231 -0
- package/dist/runtime/commands/chain.js +489 -0
- package/dist/runtime/commands/codegraph-status.js +227 -0
- package/dist/runtime/commands/compact.js +297 -0
- package/dist/runtime/commands/config.js +32 -32
- package/dist/runtime/commands/cost.js +199 -0
- package/dist/runtime/commands/delegate.js +244 -13
- package/dist/runtime/commands/dispatch.js +126 -0
- package/dist/runtime/commands/doctor.js +579 -0
- package/dist/runtime/commands/feedback.js +184 -0
- package/dist/runtime/commands/hooks.js +184 -0
- package/dist/runtime/commands/init.js +254 -0
- package/dist/runtime/commands/lsp.js +368 -0
- package/dist/runtime/commands/mcp.js +879 -0
- package/dist/runtime/commands/memory.js +582 -0
- package/dist/runtime/commands/model.js +237 -0
- package/dist/runtime/commands/onboarding.js +275 -0
- package/dist/runtime/commands/patch.js +128 -0
- package/dist/runtime/commands/permissions.js +112 -0
- package/dist/runtime/commands/plan.js +143 -0
- package/dist/runtime/commands/prd-check.js +285 -0
- package/dist/runtime/commands/privacy.js +17 -17
- package/dist/runtime/commands/recipe.js +325 -0
- package/dist/runtime/commands/redo-blob-store.js +92 -0
- package/dist/runtime/commands/redo.js +361 -0
- package/dist/runtime/commands/release-notes.js +229 -0
- package/dist/runtime/commands/repo-map.js +95 -0
- package/dist/runtime/commands/report.js +299 -0
- package/dist/runtime/commands/resume.js +118 -0
- package/dist/runtime/commands/review-consensus.js +68 -53
- package/dist/runtime/commands/rewind.js +333 -0
- package/dist/runtime/commands/roster.js +14 -14
- package/dist/runtime/commands/sessions.js +163 -0
- package/dist/runtime/commands/share.js +316 -0
- package/dist/runtime/commands/skills.js +31 -31
- package/dist/runtime/commands/status.js +186 -0
- package/dist/runtime/commands/stickers.js +82 -0
- package/dist/runtime/commands/style.js +194 -0
- package/dist/runtime/commands/theme.js +196 -0
- package/dist/runtime/commands/undo.js +54 -22
- package/dist/runtime/commands/update.js +289 -0
- package/dist/runtime/commands/vim.js +140 -0
- package/dist/runtime/commands/worktree.js +177 -0
- package/dist/runtime/commands/worktrees.js +155 -0
- package/dist/runtime/headless-repl.js +195 -0
- package/dist/runtime/headless.js +543 -0
- package/dist/runtime/load-hooks-or-exit.js +71 -0
- package/dist/runtime/plan-decompose.js +531 -0
- package/dist/runtime/update-check.js +28 -28
- package/dist/runtime/version.js +65 -0
- package/dist/skills/bundled/batch.js +617 -0
- package/dist/skills/bundled/index.js +45 -0
- package/dist/skills/bundled/loop.js +358 -0
- package/dist/skills/bundled/remember.js +383 -0
- package/dist/skills/bundled/simplify.js +289 -0
- package/dist/skills/bundled/skillify.js +373 -0
- package/dist/skills/bundled/stuck.js +558 -0
- package/dist/skills/bundled/verify.js +439 -0
- package/dist/testing/vcr.js +486 -0
- package/dist/tools/agent-tool.js +229 -0
- package/dist/tools/apply-patch.js +556 -0
- package/dist/tools/ask-user-question.js +222 -0
- package/dist/tools/ask-user.js +115 -0
- package/dist/tools/bash.js +623 -45
- package/dist/tools/brief.js +224 -0
- package/dist/tools/enter-worktree.js +250 -0
- package/dist/tools/exit-worktree.js +147 -0
- package/dist/tools/file-tools.js +161 -44
- package/dist/tools/lsp-tools.js +189 -0
- package/dist/tools/mcp-tool.js +260 -0
- package/dist/tools/multi-edit.js +361 -0
- package/dist/tools/powershell.js +268 -0
- package/dist/tools/registry.js +85 -0
- package/dist/tools/skill-tool.js +96 -0
- package/dist/tools/sleep.js +99 -0
- package/dist/tools/synthetic-output.js +133 -0
- package/dist/tools/tasks.js +208 -0
- package/dist/tools/todo-write.js +184 -0
- package/dist/tools/verify-plan-execution.js +295 -0
- package/dist/tools/web-fetch-injection-scanner.js +207 -0
- package/dist/tools/web-fetch.js +195 -10
- package/dist/tools/web-search.js +458 -0
- package/dist/tui/agent-progress-card.js +111 -0
- package/dist/tui/agent-tree.js +11 -1
- package/dist/tui/ask-modal.js +14 -14
- package/dist/tui/ask-user-question-prompt.js +203 -0
- package/dist/tui/compact-banner.js +81 -0
- package/dist/tui/conversation-pane.js +85 -11
- package/dist/tui/cost-table.js +111 -0
- package/dist/tui/device-flow.js +2 -2
- package/dist/tui/doctor-table.js +46 -0
- package/dist/tui/feedback-prompt.js +156 -0
- package/dist/tui/input-box.js +247 -32
- package/dist/tui/login-picker.js +3 -3
- package/dist/tui/markdown-render.js +6 -6
- package/dist/tui/onboarding-wizard.js +240 -0
- package/dist/tui/permissions-picker.js +86 -0
- package/dist/tui/render.js +35 -0
- package/dist/tui/repl-render.js +332 -54
- package/dist/tui/repl-splash-art.js +16 -16
- package/dist/tui/repl-splash-mascot.js +48 -24
- package/dist/tui/repl-splash.js +22 -22
- package/dist/tui/repl.js +124 -44
- package/dist/tui/slash-palette.js +6 -6
- package/dist/tui/splash.js +2 -2
- package/dist/tui/status-bar.js +109 -31
- package/dist/tui/status-table.js +7 -0
- package/dist/tui/stickers-art.js +136 -0
- package/dist/tui/style-table.js +28 -0
- package/dist/tui/theme-table.js +29 -0
- package/dist/tui/thinking-spinner.js +123 -0
- package/dist/tui/tool-stream-pane.js +53 -4
- package/dist/tui/update-banner.js +27 -2
- package/dist/tui/vim-input.js +267 -0
- package/dist/tui/welcome-banner.js +107 -0
- package/dist/tui/welcome-data.js +293 -0
- package/dist/tui/workspace-context.js +2 -2
- package/docs/examples/codegraph.mcp.json +10 -0
- package/package.json +23 -6
- package/test/scenarios/codegen-create-file.scenario.txt +13 -0
- package/test/scenarios/compact-force.scenario.txt +11 -0
- package/test/scenarios/identity.scenario.txt +11 -0
- package/test/scenarios/persona-handoff.scenario.txt +11 -0
- package/test/scenarios/walkback.scenario.txt +12 -0
- package/dist/core/engine/compaction-hook.js +0 -154
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt registry primitive — versioned storage + hot-swap + rollback.
|
|
3
|
+
*
|
|
4
|
+
* Gap-analysis primitive #5 (final) from
|
|
5
|
+
* docs/research/2026-05-31-9-layer-rag-architecture-.md.
|
|
6
|
+
*
|
|
7
|
+
* Layout on disk (per slug):
|
|
8
|
+
*
|
|
9
|
+
* <baseDir>/
|
|
10
|
+
* <slug>/
|
|
11
|
+
* 0.1.0.md // semver-named version files
|
|
12
|
+
* 0.2.0.md
|
|
13
|
+
* 1.0.0.md
|
|
14
|
+
* current.md // POSIX symlink (or copy on win32) -> active version
|
|
15
|
+
* .history.jsonl // append-only audit log: { ts, action, version, prev? }
|
|
16
|
+
*
|
|
17
|
+
* Operations:
|
|
18
|
+
*
|
|
19
|
+
* loadPrompt — load a specific version or the active one
|
|
20
|
+
* listVersions — enumerate version files, sorted semver desc
|
|
21
|
+
* getCurrentVersion — read the active version (null when uninitialised)
|
|
22
|
+
* activateVersion — atomic hot-swap of `current.md` to a new version,
|
|
23
|
+
* append a history entry, never leave a gap
|
|
24
|
+
* rollbackPrompt — restore the previous active version from .history.jsonl
|
|
25
|
+
*
|
|
26
|
+
* Atomic-write contract: every `current.md` mutation lands via tmp + rename
|
|
27
|
+
* on the same filesystem so a concurrent reader never observes a torn state.
|
|
28
|
+
* Mirrors the agent-progress writer pattern (see
|
|
29
|
+
* apps/pugi-cli/src/core/agent-progress/writer.ts).
|
|
30
|
+
*
|
|
31
|
+
* Out of scope (separate PRs):
|
|
32
|
+
* - admin-api consumer wire-up
|
|
33
|
+
* - A/B tenant-subset pinning
|
|
34
|
+
* - cache invalidation hook on activate
|
|
35
|
+
* - migration of existing PUGI.md to versioned format
|
|
36
|
+
*/
|
|
37
|
+
import { appendFile, copyFile, lstat, mkdir, readFile, readdir, readlink, rename, rm, stat, symlink, writeFile, } from 'node:fs/promises';
|
|
38
|
+
import { tmpdir } from 'node:os';
|
|
39
|
+
import { dirname, join } from 'node:path';
|
|
40
|
+
import semver from 'semver';
|
|
41
|
+
/* ---------------------------------------------------------------------- */
|
|
42
|
+
/* Helpers */
|
|
43
|
+
/* ---------------------------------------------------------------------- */
|
|
44
|
+
const CURRENT_FILENAME = 'current.md';
|
|
45
|
+
const HISTORY_FILENAME = '.history.jsonl';
|
|
46
|
+
const IS_WINDOWS = process.platform === 'win32';
|
|
47
|
+
let writeSeq = 0;
|
|
48
|
+
function slugDir(baseDir, slug) {
|
|
49
|
+
if (!slug || typeof slug !== 'string') {
|
|
50
|
+
throw new TypeError(`prompt-registry: slug must be a non-empty string`);
|
|
51
|
+
}
|
|
52
|
+
return join(baseDir, slug);
|
|
53
|
+
}
|
|
54
|
+
function versionFile(baseDir, slug, version) {
|
|
55
|
+
return join(slugDir(baseDir, slug), `${version}.md`);
|
|
56
|
+
}
|
|
57
|
+
function currentFile(baseDir, slug) {
|
|
58
|
+
return join(slugDir(baseDir, slug), CURRENT_FILENAME);
|
|
59
|
+
}
|
|
60
|
+
function historyFile(baseDir, slug) {
|
|
61
|
+
return join(slugDir(baseDir, slug), HISTORY_FILENAME);
|
|
62
|
+
}
|
|
63
|
+
async function pathExists(p) {
|
|
64
|
+
try {
|
|
65
|
+
await lstat(p);
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
if (err.code === 'ENOENT')
|
|
70
|
+
return false;
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function nextTmpName(target) {
|
|
75
|
+
writeSeq += 1;
|
|
76
|
+
return `${target}.tmp-${process.pid}-${writeSeq}-${Date.now()}`;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Atomically write/replace `current.md` to point at `<version>.md`.
|
|
80
|
+
*
|
|
81
|
+
* POSIX path: write a symlink to a tmp name, then rename(tmp, current) so the
|
|
82
|
+
* swap is atomic — no reader ever observes the file missing.
|
|
83
|
+
*
|
|
84
|
+
* Windows path: symlinks require elevated privileges, so we copy the version
|
|
85
|
+
* bytes to a tmp file and rename. Still atomic on the same volume; the only
|
|
86
|
+
* cost is duplicated bytes.
|
|
87
|
+
*/
|
|
88
|
+
async function writeCurrentAtomically(dir, version) {
|
|
89
|
+
const finalPath = join(dir, CURRENT_FILENAME);
|
|
90
|
+
const tmp = nextTmpName(finalPath);
|
|
91
|
+
const targetVersionFile = `${version}.md`;
|
|
92
|
+
try {
|
|
93
|
+
if (IS_WINDOWS) {
|
|
94
|
+
await copyFile(join(dir, targetVersionFile), tmp);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// Relative symlink — survives moving the slug directory as long as
|
|
98
|
+
// the version siblings come along.
|
|
99
|
+
await symlink(targetVersionFile, tmp);
|
|
100
|
+
}
|
|
101
|
+
// Replace any existing current.md. rename() is atomic on the same fs.
|
|
102
|
+
await rename(tmp, finalPath);
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
try {
|
|
106
|
+
await rm(tmp, { force: true });
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// best-effort
|
|
110
|
+
}
|
|
111
|
+
throw err;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function appendHistory(baseDir, slug, entry) {
|
|
115
|
+
const line = `${JSON.stringify(entry)}\n`;
|
|
116
|
+
await appendFile(historyFile(baseDir, slug), line, 'utf8');
|
|
117
|
+
}
|
|
118
|
+
async function readHistory(baseDir, slug) {
|
|
119
|
+
const path = historyFile(baseDir, slug);
|
|
120
|
+
try {
|
|
121
|
+
const raw = await readFile(path, 'utf8');
|
|
122
|
+
const out = [];
|
|
123
|
+
for (const line of raw.split('\n')) {
|
|
124
|
+
const trimmed = line.trim();
|
|
125
|
+
if (!trimmed)
|
|
126
|
+
continue;
|
|
127
|
+
try {
|
|
128
|
+
out.push(JSON.parse(trimmed));
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// Skip malformed lines — append-only log should never have them, but
|
|
132
|
+
// tolerate corruption rather than poisoning the whole registry.
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
if (err.code === 'ENOENT')
|
|
139
|
+
return [];
|
|
140
|
+
throw err;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Resolve the active version slug by inspecting `current.md`.
|
|
145
|
+
*
|
|
146
|
+
* On POSIX we read the symlink target (e.g. `1.2.0.md` -> `1.2.0`).
|
|
147
|
+
* On Windows we have to fall back to the most recent activate entry in
|
|
148
|
+
* .history.jsonl because the file is a content copy, not a link.
|
|
149
|
+
*/
|
|
150
|
+
async function resolveActiveVersion(baseDir, slug) {
|
|
151
|
+
const path = currentFile(baseDir, slug);
|
|
152
|
+
if (!(await pathExists(path)))
|
|
153
|
+
return null;
|
|
154
|
+
if (!IS_WINDOWS) {
|
|
155
|
+
try {
|
|
156
|
+
const target = await readlink(path);
|
|
157
|
+
// Strip directory (defensive — we always write relative names) + `.md`.
|
|
158
|
+
const base = target.split(/[\\/]/).pop() ?? target;
|
|
159
|
+
return base.endsWith('.md') ? base.slice(0, -3) : base;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// Fall through to history lookup if current.md is a regular file
|
|
163
|
+
// (e.g. a manual operator override on POSIX).
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const history = await readHistory(baseDir, slug);
|
|
167
|
+
for (let i = history.length - 1; i >= 0; i -= 1) {
|
|
168
|
+
const entry = history[i];
|
|
169
|
+
if (entry && (entry.action === 'activate' || entry.action === 'rollback')) {
|
|
170
|
+
return entry.version;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
/* ---------------------------------------------------------------------- */
|
|
176
|
+
/* Public API */
|
|
177
|
+
/* ---------------------------------------------------------------------- */
|
|
178
|
+
/**
|
|
179
|
+
* Load a prompt body + metadata.
|
|
180
|
+
*
|
|
181
|
+
* Resolution rules:
|
|
182
|
+
* - `version` set → read `<slug>/<version>.md` directly
|
|
183
|
+
* - `version` omitted → read `<slug>/current.md` (follows symlink on POSIX,
|
|
184
|
+
* or reads the copied content on Windows)
|
|
185
|
+
*
|
|
186
|
+
* Throws:
|
|
187
|
+
* - TypeError when the slug directory does not exist
|
|
188
|
+
* - RangeError when the requested version file or `current.md` is missing
|
|
189
|
+
*/
|
|
190
|
+
export async function loadPrompt(opts) {
|
|
191
|
+
const dir = slugDir(opts.baseDir, opts.slug);
|
|
192
|
+
if (!(await pathExists(dir))) {
|
|
193
|
+
throw new TypeError(`prompt-registry: slug directory not found: ${opts.slug} (looked in ${opts.baseDir})`);
|
|
194
|
+
}
|
|
195
|
+
let resolvedVersion = opts.version;
|
|
196
|
+
let path;
|
|
197
|
+
if (resolvedVersion) {
|
|
198
|
+
path = versionFile(opts.baseDir, opts.slug, resolvedVersion);
|
|
199
|
+
if (!(await pathExists(path))) {
|
|
200
|
+
throw new RangeError(`prompt-registry: version not found: ${opts.slug}@${resolvedVersion}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
path = currentFile(opts.baseDir, opts.slug);
|
|
205
|
+
if (!(await pathExists(path))) {
|
|
206
|
+
throw new RangeError(`prompt-registry: no current version set for slug: ${opts.slug}`);
|
|
207
|
+
}
|
|
208
|
+
const active = await resolveActiveVersion(opts.baseDir, opts.slug);
|
|
209
|
+
if (!active) {
|
|
210
|
+
throw new RangeError(`prompt-registry: current.md exists but version cannot be resolved for slug: ${opts.slug}`);
|
|
211
|
+
}
|
|
212
|
+
resolvedVersion = active;
|
|
213
|
+
}
|
|
214
|
+
const body = await readFile(path, 'utf8');
|
|
215
|
+
return {
|
|
216
|
+
slug: opts.slug,
|
|
217
|
+
version: resolvedVersion,
|
|
218
|
+
body,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* List all version files for a slug, sorted semver descending.
|
|
223
|
+
* Excludes `current.md` and `.history.jsonl`. Returns `[]` for slugs
|
|
224
|
+
* that have no version files yet (and for slugs that do not exist).
|
|
225
|
+
*/
|
|
226
|
+
export async function listVersions(opts) {
|
|
227
|
+
const dir = slugDir(opts.baseDir, opts.slug);
|
|
228
|
+
let entries;
|
|
229
|
+
try {
|
|
230
|
+
entries = await readdir(dir);
|
|
231
|
+
}
|
|
232
|
+
catch (err) {
|
|
233
|
+
if (err.code === 'ENOENT')
|
|
234
|
+
return [];
|
|
235
|
+
throw err;
|
|
236
|
+
}
|
|
237
|
+
const versions = [];
|
|
238
|
+
for (const name of entries) {
|
|
239
|
+
if (name === CURRENT_FILENAME || name === HISTORY_FILENAME)
|
|
240
|
+
continue;
|
|
241
|
+
if (!name.endsWith('.md'))
|
|
242
|
+
continue;
|
|
243
|
+
const v = name.slice(0, -3);
|
|
244
|
+
if (semver.valid(v))
|
|
245
|
+
versions.push(v);
|
|
246
|
+
}
|
|
247
|
+
versions.sort((a, b) => semver.rcompare(a, b));
|
|
248
|
+
const out = [];
|
|
249
|
+
for (const v of versions) {
|
|
250
|
+
const body = await readFile(versionFile(opts.baseDir, opts.slug, v), 'utf8');
|
|
251
|
+
out.push({ slug: opts.slug, version: v, body });
|
|
252
|
+
}
|
|
253
|
+
return out;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Return the active version slug, or `null` when no `current.md` is set
|
|
257
|
+
* (or when the slug/baseDir does not exist).
|
|
258
|
+
*/
|
|
259
|
+
export async function getCurrentVersion(opts) {
|
|
260
|
+
if (!(await pathExists(opts.baseDir)))
|
|
261
|
+
return null;
|
|
262
|
+
if (!(await pathExists(slugDir(opts.baseDir, opts.slug))))
|
|
263
|
+
return null;
|
|
264
|
+
return resolveActiveVersion(opts.baseDir, opts.slug);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Promote `version` to be the active version. Writes `current.md` atomically
|
|
268
|
+
* (symlink+rename on POSIX, copy+rename on Windows) and appends an audit
|
|
269
|
+
* entry to `.history.jsonl`.
|
|
270
|
+
*
|
|
271
|
+
* Throws:
|
|
272
|
+
* - RangeError when `version` is not valid semver
|
|
273
|
+
* - RangeError when `<version>.md` does not exist on disk
|
|
274
|
+
*/
|
|
275
|
+
export async function activateVersion(opts) {
|
|
276
|
+
if (!semver.valid(opts.version)) {
|
|
277
|
+
throw new RangeError(`prompt-registry: version must be valid semver, got: ${opts.version}`);
|
|
278
|
+
}
|
|
279
|
+
const dir = slugDir(opts.baseDir, opts.slug);
|
|
280
|
+
const target = versionFile(opts.baseDir, opts.slug, opts.version);
|
|
281
|
+
if (!(await pathExists(target))) {
|
|
282
|
+
throw new RangeError(`prompt-registry: version file does not exist: ${opts.slug}@${opts.version}`);
|
|
283
|
+
}
|
|
284
|
+
// Capture prior active version BEFORE swapping so the history entry is
|
|
285
|
+
// complete even if a concurrent activate races us.
|
|
286
|
+
const prev = (await resolveActiveVersion(opts.baseDir, opts.slug)) ?? undefined;
|
|
287
|
+
if (!(await pathExists(dir))) {
|
|
288
|
+
await mkdir(dir, { recursive: true });
|
|
289
|
+
}
|
|
290
|
+
await writeCurrentAtomically(dir, opts.version);
|
|
291
|
+
const entry = {
|
|
292
|
+
ts: new Date().toISOString(),
|
|
293
|
+
action: 'activate',
|
|
294
|
+
version: opts.version,
|
|
295
|
+
...(prev !== undefined ? { prev } : {}),
|
|
296
|
+
};
|
|
297
|
+
await appendHistory(opts.baseDir, opts.slug, entry);
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Restore the previous active version from `.history.jsonl`. Idempotent
|
|
301
|
+
* across repeated calls: each call walks one step further back through the
|
|
302
|
+
* history to find a non-current target.
|
|
303
|
+
*
|
|
304
|
+
* Throws RangeError when there is no prior version to restore.
|
|
305
|
+
*/
|
|
306
|
+
export async function rollbackPrompt(opts) {
|
|
307
|
+
const history = await readHistory(opts.baseDir, opts.slug);
|
|
308
|
+
if (history.length === 0) {
|
|
309
|
+
throw new RangeError(`prompt-registry: no history to roll back for slug: ${opts.slug}`);
|
|
310
|
+
}
|
|
311
|
+
// Find the most recent entry that has a `prev` field — that's the version
|
|
312
|
+
// we want to restore. Entries without `prev` were the first activate, so
|
|
313
|
+
// there's nothing prior to fall back to from them.
|
|
314
|
+
let target;
|
|
315
|
+
for (let i = history.length - 1; i >= 0; i -= 1) {
|
|
316
|
+
const entry = history[i];
|
|
317
|
+
if (entry && entry.prev) {
|
|
318
|
+
target = entry.prev;
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (!target) {
|
|
323
|
+
throw new RangeError(`prompt-registry: no prior version recorded in history for slug: ${opts.slug}`);
|
|
324
|
+
}
|
|
325
|
+
const dir = slugDir(opts.baseDir, opts.slug);
|
|
326
|
+
const file = versionFile(opts.baseDir, opts.slug, target);
|
|
327
|
+
if (!(await pathExists(file))) {
|
|
328
|
+
throw new RangeError(`prompt-registry: prior version file missing on disk: ${opts.slug}@${target}`);
|
|
329
|
+
}
|
|
330
|
+
const prev = (await resolveActiveVersion(opts.baseDir, opts.slug)) ?? undefined;
|
|
331
|
+
await writeCurrentAtomically(dir, target);
|
|
332
|
+
const entry = {
|
|
333
|
+
ts: new Date().toISOString(),
|
|
334
|
+
action: 'rollback',
|
|
335
|
+
version: target,
|
|
336
|
+
...(prev !== undefined ? { prev } : {}),
|
|
337
|
+
};
|
|
338
|
+
await appendHistory(opts.baseDir, opts.slug, entry);
|
|
339
|
+
const body = await readFile(file, 'utf8');
|
|
340
|
+
return { slug: opts.slug, version: target, body };
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Convenience helper for callers that need an OS-level temp dir for tests
|
|
344
|
+
* or scratch operations. Exported so the spec file can share the same
|
|
345
|
+
* idiom as the implementation without duplicating logic.
|
|
346
|
+
*/
|
|
347
|
+
export function defaultTmpRoot(prefix = 'pugi-prompts-') {
|
|
348
|
+
return join(tmpdir(), prefix);
|
|
349
|
+
}
|
|
350
|
+
// Re-export the path helpers under a namespace for tests + downstream
|
|
351
|
+
// consumers that want to introspect the layout without re-deriving paths.
|
|
352
|
+
export const paths = {
|
|
353
|
+
slugDir,
|
|
354
|
+
versionFile,
|
|
355
|
+
currentFile,
|
|
356
|
+
historyFile,
|
|
357
|
+
};
|
|
358
|
+
// Silence "stat is unused" — kept in the import list because future
|
|
359
|
+
// consumers (cache-invalidation hook, follow-up PR) will need it and the
|
|
360
|
+
// import block is the single canonical place for fs/promises pulls.
|
|
361
|
+
void stat;
|
|
362
|
+
void dirname;
|
|
363
|
+
void writeFile;
|
|
364
|
+
//# sourceMappingURL=registry.js.map
|