@pugi/cli 0.1.0-beta.10 → 0.1.0-beta.100
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/README.md +53 -11
- 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/retro.js +210 -0
- package/dist/commands/smoke.js +133 -0
- package/dist/core/agent-progress/cleanup.js +134 -0
- package/dist/core/agent-progress/schema.js +144 -0
- package/dist/core/agent-progress/writer.js +101 -0
- package/dist/core/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/db.js +506 -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/codegraph/parser.js +71 -0
- package/dist/core/codegraph/types.js +34 -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 +13 -13
- 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 +72 -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 +333 -7
- package/dist/core/edits/format-detector.js +260 -0
- package/dist/core/edits/format-matrix.js +26 -0
- package/dist/core/edits/fuzzy-ladder.js +650 -0
- package/dist/core/edits/index.js +5 -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 +29 -29
- package/dist/core/engine/anvil-client.js +214 -26
- package/dist/core/engine/auto-compact.js +247 -0
- package/dist/core/engine/budgets.js +220 -0
- package/dist/core/engine/compact-llm-summarizer.js +124 -0
- package/dist/core/engine/context-prefix.js +155 -0
- package/dist/core/engine/index.js +1 -1
- package/dist/core/engine/intensity.js +163 -0
- package/dist/core/engine/intent.js +260 -0
- package/dist/core/engine/native-pugi.js +1559 -227
- package/dist/core/engine/prompts.js +187 -19
- package/dist/core/engine/strip-internal-fields.js +124 -0
- package/dist/core/engine/tool-bridge.js +1887 -59
- package/dist/core/engine/verification-patterns.js +195 -0
- 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 +46 -0
- package/dist/core/hooks/index.js +15 -0
- package/dist/core/hooks/registry.js +216 -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/hooks/worktree-events.js +158 -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 +551 -41
- package/dist/core/lsp/language-detect.js +66 -0
- package/dist/core/lsp/post-edit-diagnostics.js +171 -0
- package/dist/core/lsp/server-detect.js +173 -0
- package/dist/core/lsp/symbol-cache.js +162 -0
- package/dist/core/lsp/symbol-tools.js +664 -0
- package/dist/core/mcp/client.js +97 -28
- package/dist/core/mcp/http-server.js +553 -0
- package/dist/core/mcp/orchestrator-config.js +192 -0
- package/dist/core/mcp/orchestrator-tools.js +806 -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/notes/notes-paths.js +113 -0
- package/dist/core/notes/notes-recorder.js +140 -0
- package/dist/core/notes/notes-writer.js +53 -0
- package/dist/core/notes/renderers.js +0 -0
- package/dist/core/notes/slug.js +105 -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 +107 -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-gitignore.js +52 -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/engine-bridge.js +303 -0
- 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 +2690 -229
- package/dist/core/repl/slash-commands.js +540 -41
- 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/tool-route.js +382 -0
- 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/retro/git-collector.js +251 -0
- package/dist/core/retro/health-card.js +25 -0
- package/dist/core/retro/metrics.js +342 -0
- package/dist/core/retro/narrative.js +249 -0
- package/dist/core/retro/plane-collector.js +274 -0
- package/dist/core/retro/pr-issue-link.js +65 -0
- package/dist/core/retro/types.js +16 -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/sandboxing/adapter.js +29 -0
- package/dist/core/sandboxing/index.js +49 -0
- package/dist/core/sandboxing/none.js +19 -0
- package/dist/core/sandboxing/seatbelt.js +183 -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 +119 -0
- package/dist/core/settings.js +378 -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 +30 -30
- 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 +146 -52
- 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/include-parser.js +249 -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 +4345 -561
- package/dist/runtime/commands/agents.js +31 -31
- 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 +74 -40
- package/dist/runtime/commands/cost.js +199 -0
- package/dist/runtime/commands/delegate.js +27 -4
- 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 +187 -0
- package/dist/runtime/commands/index-cmd.js +353 -0
- package/dist/runtime/commands/init.js +254 -0
- package/dist/runtime/commands/lsp.js +200 -38
- package/dist/runtime/commands/mcp.js +935 -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 +12 -12
- 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/servers.js +236 -0
- 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 +8 -8
- package/dist/runtime/commands/worktrees.js +155 -0
- package/dist/runtime/deprecation-warning.js +69 -0
- package/dist/runtime/engine-exit-code.js +50 -0
- package/dist/runtime/headless-repl.js +195 -0
- package/dist/runtime/headless.js +548 -0
- package/dist/runtime/load-hooks-or-exit.js +71 -0
- package/dist/runtime/plan-decompose.js +22 -22
- package/dist/runtime/sigint-guard.js +272 -0
- package/dist/runtime/stream-renderer.js +195 -0
- package/dist/runtime/update-check.js +28 -28
- package/dist/runtime/version.js +65 -0
- package/dist/runtime/worktree-bootstrap.js +579 -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 +89 -28
- package/dist/tools/ask-user-question.js +337 -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/cron.js +433 -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/http-request.js +336 -0
- package/dist/tools/lsp-tools.js +377 -1
- 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 +120 -5
- package/dist/tools/server-tools.js +892 -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 +22 -1
- package/dist/tui/ask-modal.js +14 -14
- package/dist/tui/ask-user-question-chips.js +315 -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/multi-file-diff-approval.js +375 -0
- package/dist/tui/onboarding-wizard.js +240 -0
- package/dist/tui/permissions-picker.js +86 -0
- package/dist/tui/render.js +36 -1
- package/dist/tui/repl-render.js +239 -25
- 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 +125 -45
- 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/package.json +21 -5
- package/test/scenarios/codegen-create-file.scenario.txt +13 -0
- package/test/scenarios/compact-force.scenario.txt +12 -0
- package/test/scenarios/identity.scenario.txt +11 -0
- package/test/scenarios/persona-handoff.scenario.txt +12 -0
- package/test/scenarios/walkback.scenario.txt +12 -0
- package/dist/core/engine/compaction-hook.js +0 -154
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PR F (2026-06-05): Obsidian-style notes — path resolution.
|
|
3
|
+
*
|
|
4
|
+
* Three on-disk surfaces, all rooted at `<workspaceRoot>/.pugi/`:
|
|
5
|
+
*
|
|
6
|
+
* 1. Session notes — `.pugi/notes/sessions/<session-id>.md`
|
|
7
|
+
* One markdown file per REPL dispatch. Frontmatter (id, persona,
|
|
8
|
+
* command, brief, status, files, timing) + body summary.
|
|
9
|
+
*
|
|
10
|
+
* 2. File concept notes — `.pugi/notes/files/<slug>.md`
|
|
11
|
+
* One markdown file per touched repo file. Append-only history
|
|
12
|
+
* of which sessions touched it. Cross-linked via wiki-links from
|
|
13
|
+
* session notes (`[[files/snake-html]]`).
|
|
14
|
+
*
|
|
15
|
+
* 3. Daily journal — `.pugi/journal/YYYY-MM-DD.md`
|
|
16
|
+
* One markdown file per UTC day. Append-only one-paragraph
|
|
17
|
+
* entry per dispatch. Provides a chronological "what shipped
|
|
18
|
+
* today" view operators read directly in Obsidian or `cat`.
|
|
19
|
+
*
|
|
20
|
+
* The on-disk schema is OPEN by design: operators are encouraged to
|
|
21
|
+
* point their Obsidian vault at `.pugi/notes` and link freely. We
|
|
22
|
+
* avoid hidden state files (`.json` indexes, `.db` caches) so the
|
|
23
|
+
* tree stays grep-able and CI-friendly.
|
|
24
|
+
*
|
|
25
|
+
* All path helpers are pure — no fs side effects. The writer module
|
|
26
|
+
* owns `mkdir -p` + atomic write.
|
|
27
|
+
*/
|
|
28
|
+
import { join, basename, resolve, isAbsolute, sep } from 'node:path';
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the per-workspace notes root. Mirrors the existing
|
|
31
|
+
* `.pugi/audit/`, `.pugi/sessions/`, `.pugi/runs/` conventions —
|
|
32
|
+
* a customer who already pins `.pugi/` in `.gitignore` (most do)
|
|
33
|
+
* automatically picks up notes without touching their VCS hygiene.
|
|
34
|
+
*/
|
|
35
|
+
export function resolveNotesRoot(workspaceRoot) {
|
|
36
|
+
return join(workspaceRoot, '.pugi', 'notes');
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Resolve the per-workspace daily-journal root.
|
|
40
|
+
*
|
|
41
|
+
* Journal lives ALONGSIDE notes/ rather than under it because the
|
|
42
|
+
* journal is a chronological surface (one entry per day), distinct
|
|
43
|
+
* from the topical surface (notes by session/file). Keeping them
|
|
44
|
+
* sibling dirs matches how Obsidian users typically organise
|
|
45
|
+
* "Daily Notes" plugin output vs concept notes.
|
|
46
|
+
*/
|
|
47
|
+
export function resolveJournalRoot(workspaceRoot) {
|
|
48
|
+
return join(workspaceRoot, '.pugi', 'journal');
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Resolve the session-note path for a given session id. Returns an
|
|
52
|
+
* absolute path under the workspace.
|
|
53
|
+
*
|
|
54
|
+
* Session ids are already filesystem-safe (the engine adapter mints
|
|
55
|
+
* them via crypto.randomUUID + slug normalization) so we pass them
|
|
56
|
+
* through verbatim. If a future caller hands a session id with path
|
|
57
|
+
* separators we strip them defensively to prevent path traversal
|
|
58
|
+
* (callers should never do this, but cheap guard).
|
|
59
|
+
*/
|
|
60
|
+
export function resolveSessionNotePath(workspaceRoot, sessionId) {
|
|
61
|
+
const safe = sessionId.replace(/[\\/]+/g, '-');
|
|
62
|
+
return join(resolveNotesRoot(workspaceRoot), 'sessions', `${safe}.md`);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Resolve the file-concept note path for a given repo-relative file
|
|
66
|
+
* path. The slug is derived in `slug.ts` so the same file always
|
|
67
|
+
* maps to the same note across sessions.
|
|
68
|
+
*/
|
|
69
|
+
export function resolveFileNotePath(workspaceRoot, fileSlug) {
|
|
70
|
+
return join(resolveNotesRoot(workspaceRoot), 'files', `${fileSlug}.md`);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Resolve the daily-journal path for a given UTC date. We use UTC
|
|
74
|
+
* intentionally so journal files line up across operator timezone
|
|
75
|
+
* shifts (laptop in SF, desktop in EU, CI in UTC — all converge on
|
|
76
|
+
* the same `YYYY-MM-DD.md` for a given session).
|
|
77
|
+
*/
|
|
78
|
+
export function resolveJournalPath(workspaceRoot, utcDate) {
|
|
79
|
+
const y = utcDate.getUTCFullYear();
|
|
80
|
+
const m = String(utcDate.getUTCMonth() + 1).padStart(2, '0');
|
|
81
|
+
const d = String(utcDate.getUTCDate()).padStart(2, '0');
|
|
82
|
+
return join(resolveJournalRoot(workspaceRoot), `${y}-${m}-${d}.md`);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Resolve a file path to its repo-relative form for slug derivation.
|
|
86
|
+
* Returns null when the path is outside the workspace (defensive —
|
|
87
|
+
* the slug derivation cannot produce a meaningful note for an
|
|
88
|
+
* absolute path outside the tree).
|
|
89
|
+
*
|
|
90
|
+
* Symlinks are not followed; we operate on the lexical path. The
|
|
91
|
+
* audit-trail module uses the same convention (resolve before
|
|
92
|
+
* hashing) so cross-feature paths stay consistent.
|
|
93
|
+
*/
|
|
94
|
+
export function toWorkspaceRelative(workspaceRoot, filePath) {
|
|
95
|
+
const absRoot = resolve(workspaceRoot);
|
|
96
|
+
const abs = isAbsolute(filePath) ? resolve(filePath) : resolve(absRoot, filePath);
|
|
97
|
+
if (abs === absRoot)
|
|
98
|
+
return '.';
|
|
99
|
+
const prefix = absRoot + sep;
|
|
100
|
+
if (!abs.startsWith(prefix))
|
|
101
|
+
return null;
|
|
102
|
+
return abs.slice(prefix.length);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Strip the basename to a workspace-relative hint useful when the
|
|
106
|
+
* caller has nothing better. Lets the journal renderer fall back
|
|
107
|
+
* gracefully on file paths it cannot resolve against the workspace
|
|
108
|
+
* (e.g. agent emitted an absolute /tmp path for a scratch file).
|
|
109
|
+
*/
|
|
110
|
+
export function fallbackPathHint(filePath) {
|
|
111
|
+
return basename(filePath);
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=notes-paths.js.map
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PR F (2026-06-05): Notes recorder — orchestrates session/file/daily
|
|
3
|
+
* markdown writes at dispatch end.
|
|
4
|
+
*
|
|
5
|
+
* Called from `native-pugi.ts` immediately after the `dispatch_end`
|
|
6
|
+
* audit event fires. The recorder:
|
|
7
|
+
* 1. Renders the per-session note (`writeNoteAtomic`).
|
|
8
|
+
* 2. Renders the daily-journal entry (`appendNoteLine`).
|
|
9
|
+
* 3. For each touched file, renders the file-concept note header
|
|
10
|
+
* (atomic, only when the file is empty) and appends a history
|
|
11
|
+
* line.
|
|
12
|
+
*
|
|
13
|
+
* Side effects gated by `enabled` flag from settings + env var
|
|
14
|
+
* (`PUGI_NOTES_DISABLE=1` overrides). Every call is wrapped in a
|
|
15
|
+
* try/catch — notes are a convenience surface, not a correctness
|
|
16
|
+
* guarantee. The audit-trail JSONL remains the authoritative log.
|
|
17
|
+
*/
|
|
18
|
+
import { statSync, readFileSync } from 'node:fs';
|
|
19
|
+
import { resolveSessionNotePath, resolveFileNotePath, resolveJournalPath, toWorkspaceRelative, fallbackPathHint, } from './notes-paths.js';
|
|
20
|
+
import { fileSlugFor } from './slug.js';
|
|
21
|
+
import { writeNoteAtomic, appendNoteLine } from './notes-writer.js';
|
|
22
|
+
import { renderSessionNote, renderFileNoteHeader, renderFileNoteHistoryLine, renderJournalHeader, renderJournalEntry, } from './renderers.js';
|
|
23
|
+
export const PUGI_NOTES_DISABLE_VAR = 'PUGI_NOTES_DISABLE';
|
|
24
|
+
export function isNotesDisabledByEnv(env = process.env) {
|
|
25
|
+
const raw = env[PUGI_NOTES_DISABLE_VAR]?.trim().toLowerCase();
|
|
26
|
+
if (!raw)
|
|
27
|
+
return false;
|
|
28
|
+
return raw === '1' || raw === 'true' || raw === 'yes';
|
|
29
|
+
}
|
|
30
|
+
function fileExists(path) {
|
|
31
|
+
try {
|
|
32
|
+
statSync(path);
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function fileEmptyOrMissing(path) {
|
|
40
|
+
try {
|
|
41
|
+
const buf = readFileSync(path, 'utf8');
|
|
42
|
+
return buf.trim().length === 0;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Compute (slug, relative path) pairs for every touched file. Paths
|
|
50
|
+
* outside the workspace fall back to a basename-only slug so the
|
|
51
|
+
* concept note still lands somewhere readable.
|
|
52
|
+
*/
|
|
53
|
+
function deriveFileSlugs(workspaceRoot, filesChanged) {
|
|
54
|
+
const out = [];
|
|
55
|
+
for (const f of filesChanged) {
|
|
56
|
+
const rel = toWorkspaceRelative(workspaceRoot, f) ?? fallbackPathHint(f);
|
|
57
|
+
const slug = fileSlugFor(rel);
|
|
58
|
+
if (slug.length > 0) {
|
|
59
|
+
out.push({ slug, relativePath: rel });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
function durationSeconds(startedAt, endedAt) {
|
|
65
|
+
const s = Date.parse(startedAt);
|
|
66
|
+
const e = Date.parse(endedAt);
|
|
67
|
+
if (Number.isNaN(s) || Number.isNaN(e) || e < s)
|
|
68
|
+
return 0;
|
|
69
|
+
return Math.round((e - s) / 1000);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Single entry point — called from `native-pugi.ts` at dispatch end.
|
|
73
|
+
* Never throws.
|
|
74
|
+
*/
|
|
75
|
+
export function recordSessionNotes(input) {
|
|
76
|
+
try {
|
|
77
|
+
if (!input.config.enabled)
|
|
78
|
+
return;
|
|
79
|
+
if (isNotesDisabledByEnv(input.env ?? process.env))
|
|
80
|
+
return;
|
|
81
|
+
const fileSlugs = deriveFileSlugs(input.workspaceRoot, input.filesChanged);
|
|
82
|
+
const snap = {
|
|
83
|
+
sessionId: input.sessionId,
|
|
84
|
+
command: input.command,
|
|
85
|
+
persona: input.persona,
|
|
86
|
+
brief: input.brief,
|
|
87
|
+
status: input.status,
|
|
88
|
+
toolCallCount: input.toolCallCount,
|
|
89
|
+
turnsUsed: input.turnsUsed,
|
|
90
|
+
tokensUsed: input.tokensUsed,
|
|
91
|
+
filesChanged: input.filesChanged,
|
|
92
|
+
fileSlugs,
|
|
93
|
+
startedAt: input.startedAt,
|
|
94
|
+
endedAt: input.endedAt,
|
|
95
|
+
durationSeconds: durationSeconds(input.startedAt, input.endedAt),
|
|
96
|
+
reason: input.reason,
|
|
97
|
+
};
|
|
98
|
+
// (1) Session note — atomic write.
|
|
99
|
+
writeNoteAtomic(resolveSessionNotePath(input.workspaceRoot, input.sessionId), renderSessionNote(snap));
|
|
100
|
+
// (2) Daily journal — header on first write per day, then entry.
|
|
101
|
+
const endedAtDate = new Date(input.endedAt);
|
|
102
|
+
const journalPath = resolveJournalPath(input.workspaceRoot, endedAtDate);
|
|
103
|
+
if (!fileExists(journalPath) || fileEmptyOrMissing(journalPath)) {
|
|
104
|
+
appendNoteLine(journalPath, renderJournalHeader(endedAtDate));
|
|
105
|
+
}
|
|
106
|
+
const journalEntry = {
|
|
107
|
+
sessionId: input.sessionId,
|
|
108
|
+
endedAt: input.endedAt,
|
|
109
|
+
command: input.command,
|
|
110
|
+
persona: input.persona,
|
|
111
|
+
brief: input.brief,
|
|
112
|
+
status: input.status,
|
|
113
|
+
filesChanged: input.filesChanged,
|
|
114
|
+
};
|
|
115
|
+
appendNoteLine(journalPath, renderJournalEntry(journalEntry));
|
|
116
|
+
// (3) File-concept notes — header on first touch, history line
|
|
117
|
+
// every touch. Both writes are best-effort.
|
|
118
|
+
for (const f of fileSlugs) {
|
|
119
|
+
const path = resolveFileNotePath(input.workspaceRoot, f.slug);
|
|
120
|
+
if (!fileExists(path) || fileEmptyOrMissing(path)) {
|
|
121
|
+
appendNoteLine(path, renderFileNoteHeader(f.slug, f.relativePath));
|
|
122
|
+
}
|
|
123
|
+
const entry = {
|
|
124
|
+
sessionId: input.sessionId,
|
|
125
|
+
slug: f.slug,
|
|
126
|
+
relativePath: f.relativePath,
|
|
127
|
+
endedAt: input.endedAt,
|
|
128
|
+
status: input.status,
|
|
129
|
+
brief: input.brief,
|
|
130
|
+
};
|
|
131
|
+
appendNoteLine(path, renderFileNoteHistoryLine(entry));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// Notes are best-effort. Failure here MUST NOT propagate to the
|
|
136
|
+
// dispatch — the customer's primary work is already captured in
|
|
137
|
+
// the audit-trail JSONL and the per-session events mirror.
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=notes-recorder.js.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PR F (2026-06-05): Atomic + append-only file writers for notes.
|
|
3
|
+
*
|
|
4
|
+
* Two primitives:
|
|
5
|
+
* - `writeNoteAtomic` — full-file replace via tmp + rename.
|
|
6
|
+
* Used for session notes (one file per dispatch, complete).
|
|
7
|
+
* - `appendNoteLine` — `O_APPEND` append. Used for daily journal
|
|
8
|
+
* and file-concept history sections.
|
|
9
|
+
*
|
|
10
|
+
* Both NEVER throw — failure inside the notes layer must NOT break
|
|
11
|
+
* a customer dispatch. The audit-trail module follows the same
|
|
12
|
+
* contract (`writeAuditEvent` catches everything). Notes are
|
|
13
|
+
* convenience surface, not load-bearing.
|
|
14
|
+
*
|
|
15
|
+
* `mkdir -p` runs every call (cheap when dir exists). Mode `0o755`
|
|
16
|
+
* because operators expect to read these in Obsidian without sudo,
|
|
17
|
+
* unlike audit trails (`0o700`) which carry tenant boundary.
|
|
18
|
+
*/
|
|
19
|
+
import { mkdirSync, writeFileSync, renameSync, appendFileSync, unlinkSync } from 'node:fs';
|
|
20
|
+
import { dirname } from 'node:path';
|
|
21
|
+
import { randomBytes } from 'node:crypto';
|
|
22
|
+
export function writeNoteAtomic(targetPath, content) {
|
|
23
|
+
try {
|
|
24
|
+
mkdirSync(dirname(targetPath), { recursive: true, mode: 0o755 });
|
|
25
|
+
const tmp = `${targetPath}.${randomBytes(6).toString('hex')}.tmp`;
|
|
26
|
+
try {
|
|
27
|
+
writeFileSync(tmp, content, { encoding: 'utf8', mode: 0o644 });
|
|
28
|
+
renameSync(tmp, targetPath);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
try {
|
|
32
|
+
unlinkSync(tmp);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// tmp may not exist (write failed before fd flushed)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Notes are best-effort. Swallow.
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function appendNoteLine(targetPath, line) {
|
|
44
|
+
try {
|
|
45
|
+
mkdirSync(dirname(targetPath), { recursive: true, mode: 0o755 });
|
|
46
|
+
const ensureNewline = line.endsWith('\n') ? line : `${line}\n`;
|
|
47
|
+
appendFileSync(targetPath, ensureNewline, { encoding: 'utf8', mode: 0o644 });
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Notes are best-effort. Swallow.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=notes-writer.js.map
|
|
Binary file
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PR F (2026-06-05): Path-to-slug normalization for file concept notes.
|
|
3
|
+
*
|
|
4
|
+
* A repo file path like `src/components/Header.tsx` becomes the slug
|
|
5
|
+
* `src-components-header-tsx`, which maps к
|
|
6
|
+
* `.pugi/notes/files/src-components-header-tsx.md`. The mapping is:
|
|
7
|
+
*
|
|
8
|
+
* - deterministic (same path → same slug across sessions, so the
|
|
9
|
+
* append-only history line lands in the same note);
|
|
10
|
+
* - filesystem-safe (no path separators that would create subdirs
|
|
11
|
+
* in `.pugi/notes/files/`);
|
|
12
|
+
* - Obsidian-link-friendly (lowercase + hyphens, no spaces or
|
|
13
|
+
* other characters that need escaping in `[[wiki-links]]`);
|
|
14
|
+
* - reversible-enough — the original path is also written into the
|
|
15
|
+
* note frontmatter (`path: src/components/Header.tsx`) so tooling
|
|
16
|
+
* can recover the canonical form without parsing the slug.
|
|
17
|
+
*
|
|
18
|
+
* Why one flat `files/` dir instead of mirroring the repo tree under
|
|
19
|
+
* `.pugi/notes/files/src/components/Header.md`: Obsidian's `[[link]]`
|
|
20
|
+
* resolver works best when notes live at a single depth. A flat dir
|
|
21
|
+
* also keeps the wiki-link grammar simple — `[[files/header-tsx]]`
|
|
22
|
+
* instead of `[[files/src/components/Header.tsx]]` (the latter is
|
|
23
|
+
* also a legal Obsidian link but harder to grep + harder to rename
|
|
24
|
+
* file paths без breaking links).
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Maximum slug length. Empirically the longest file paths в monorepos
|
|
28
|
+
* are ~120 chars (deep apps/admin-api/src/modules/.../something.ts).
|
|
29
|
+
* Cap at 200 to leave headroom and keep `.md` filenames reasonable
|
|
30
|
+
* across filesystems (Windows MAX_PATH = 260 historically, modern
|
|
31
|
+
* ext4 = 255 на single component).
|
|
32
|
+
*/
|
|
33
|
+
const MAX_SLUG_CHARS = 200;
|
|
34
|
+
/**
|
|
35
|
+
* Convert a workspace-relative path to a filesystem-safe slug.
|
|
36
|
+
*
|
|
37
|
+
* Examples:
|
|
38
|
+
* src/App.jsx → src-app-jsx
|
|
39
|
+
* apps/admin-api/main.ts → apps-admin-api-main-ts
|
|
40
|
+
* README.md → readme-md
|
|
41
|
+
* .pugi/notes/sessions/x.md → pugi-notes-sessions-x-md (defensive)
|
|
42
|
+
* docs/2026-06-05-foo.md → docs-2026-06-05-foo-md
|
|
43
|
+
*
|
|
44
|
+
* Edge cases:
|
|
45
|
+
* empty / null / undefined → `'untitled'` fallback (never throws)
|
|
46
|
+
* path with unicode → unicode preserved when filesystem-safe;
|
|
47
|
+
* separators + control chars normalized
|
|
48
|
+
* trailing/leading hyphens → trimmed
|
|
49
|
+
* collapsed multi-hyphens → single `-`
|
|
50
|
+
*/
|
|
51
|
+
export function fileSlugFor(relativePath) {
|
|
52
|
+
if (typeof relativePath !== 'string' || relativePath.length === 0) {
|
|
53
|
+
return 'untitled';
|
|
54
|
+
}
|
|
55
|
+
let s = relativePath
|
|
56
|
+
.toLowerCase()
|
|
57
|
+
// Path separators + dots → hyphen so the slug stays flat.
|
|
58
|
+
.replace(/[\\/.]+/g, '-')
|
|
59
|
+
// Strip anything that is not alphanumeric, hyphen, or non-ASCII
|
|
60
|
+
// letter (allow CJK / Cyrillic for international repos).
|
|
61
|
+
.replace(/[^\p{L}\p{N}-]+/gu, '-')
|
|
62
|
+
// Collapse consecutive hyphens.
|
|
63
|
+
.replace(/-+/g, '-')
|
|
64
|
+
// Trim hyphens off the ends.
|
|
65
|
+
.replace(/^-+|-+$/g, '');
|
|
66
|
+
if (s.length === 0)
|
|
67
|
+
return 'untitled';
|
|
68
|
+
if (s.length > MAX_SLUG_CHARS) {
|
|
69
|
+
s = s.slice(0, MAX_SLUG_CHARS).replace(/-+$/g, '');
|
|
70
|
+
}
|
|
71
|
+
return s;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Convert an arbitrary string (e.g. a brief like "сделай snake.html")
|
|
75
|
+
* to a concept slug for the wiki-link grammar. Currently the recorder
|
|
76
|
+
* doesn't mint these (file slugs cover the v1 surface) but keeping
|
|
77
|
+
* the helper here keeps the slug rules centralized for PR G's
|
|
78
|
+
* graph-extraction work.
|
|
79
|
+
*
|
|
80
|
+
* Returns `null` rather than `'untitled'` when there is nothing to
|
|
81
|
+
* slug — callers should suppress the link instead of emitting a
|
|
82
|
+
* placeholder. Different fallback semantics than `fileSlugFor`
|
|
83
|
+
* because file slugs ALWAYS have a path to lean on, whereas concept
|
|
84
|
+
* slugs degrade silently when the input is unusable.
|
|
85
|
+
*/
|
|
86
|
+
export function conceptSlugFor(input) {
|
|
87
|
+
if (typeof input !== 'string')
|
|
88
|
+
return null;
|
|
89
|
+
const trimmed = input.trim();
|
|
90
|
+
if (trimmed.length === 0)
|
|
91
|
+
return null;
|
|
92
|
+
let s = trimmed
|
|
93
|
+
.toLowerCase()
|
|
94
|
+
.replace(/[\\/.]+/g, '-')
|
|
95
|
+
.replace(/[^\p{L}\p{N}-]+/gu, '-')
|
|
96
|
+
.replace(/-+/g, '-')
|
|
97
|
+
.replace(/^-+|-+$/g, '');
|
|
98
|
+
if (s.length === 0)
|
|
99
|
+
return null;
|
|
100
|
+
if (s.length > MAX_SLUG_CHARS) {
|
|
101
|
+
s = s.slice(0, MAX_SLUG_CHARS).replace(/-+$/g, '');
|
|
102
|
+
}
|
|
103
|
+
return s;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=slug.js.map
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UX — `ensureInitialized` helper.
|
|
3
|
+
*
|
|
4
|
+
* Auto-init pre-flight for every Pugi command. Before this helper landed,
|
|
5
|
+
* the only entry points that exercised the init flow were:
|
|
6
|
+
*
|
|
7
|
+
* 1. The explicit `pugi init` CLI subcommand.
|
|
8
|
+
* 2. The REPL's `/init` slash (β1a r1).
|
|
9
|
+
* 3. Engine commands (`pugi code`, `pugi build`, `pugi sync`) which
|
|
10
|
+
* called the legacy `ensureInitialized` in `cli.ts` and threw
|
|
11
|
+
* `Error('Run pugi init first')` if the operator ran them in a
|
|
12
|
+
* directory without `.pugi/`.
|
|
13
|
+
*
|
|
14
|
+
* Read-only commands (`pugi explain`, `pugi review`, `pugi plan`,
|
|
15
|
+
* `pugi smoke`, `pugi chain new`, ...) silently no-op'd the `.pugi/`
|
|
16
|
+
* mirror inside the engine adapter, which made early dogfooding
|
|
17
|
+
* confusing — the operator saw a successful command but no session
|
|
18
|
+
* artifacts on disk and no idea why.
|
|
19
|
+
*
|
|
20
|
+
* Auto-init contract (matches CEO directive ):
|
|
21
|
+
*
|
|
22
|
+
* - `.pugi/` already exists → return `{ status: 'already' }` silently.
|
|
23
|
+
* - Interactive TTY + no `.pugi/` → prompt
|
|
24
|
+
* "No Pugi workspace found here. Initialize? (Y/n)".
|
|
25
|
+
* Default Y. On Y: run `scaffoldPugiWorkspace`, return `{ status:
|
|
26
|
+
* 'initialized' }`. On n: return `{ status: 'declined' }` so the
|
|
27
|
+
* caller can bail with a helpful message.
|
|
28
|
+
* - Non-interactive (CI / pipe / --json / --no-tty) + no `.pugi/`:
|
|
29
|
+
* default behaviour is conservative — return `{ status: 'declined',
|
|
30
|
+
* reason: 'non_interactive' }`. The caller decides how to surface
|
|
31
|
+
* this (engine commands bail with a clean error; read-only
|
|
32
|
+
* commands MAY continue with degraded semantics).
|
|
33
|
+
* - `--no-init` flag forces conservative posture even on interactive
|
|
34
|
+
* terminals (operator wants to fail fast).
|
|
35
|
+
*
|
|
36
|
+
* Session cache: a command pre-flight that already prompted for and
|
|
37
|
+
* scaffolded `.pugi/` MUST NOT re-prompt for the same workspace in the
|
|
38
|
+
* same process. The cache key is the absolute workspace root path. The
|
|
39
|
+
* cache is process-local (Map) — it does not persist across `pugi`
|
|
40
|
+
* invocations (a second `pugi code` in the same shell starts fresh and
|
|
41
|
+
* re-checks the filesystem).
|
|
42
|
+
*
|
|
43
|
+
* This module is intentionally framework-free: no Ink, no React, no
|
|
44
|
+
* readline. The prompt reader is injected via the `prompt` callback so
|
|
45
|
+
* the spec can drive the helper deterministically and the CLI can
|
|
46
|
+
* forward to its existing stdin-reader (`readSingleChoice` in cli.ts).
|
|
47
|
+
*/
|
|
48
|
+
import { existsSync, statSync } from 'node:fs';
|
|
49
|
+
import { resolve } from 'node:path';
|
|
50
|
+
/**
|
|
51
|
+
* Process-local cache of workspaces that already passed the pre-flight
|
|
52
|
+
* gate. Keyed by absolute root path. The cache is intentionally
|
|
53
|
+
* additive-only — there is no eviction. A long-running REPL session
|
|
54
|
+
* stays in one workspace and we never want to re-prompt within it.
|
|
55
|
+
*/
|
|
56
|
+
const initialisedCache = new Set();
|
|
57
|
+
/**
|
|
58
|
+
* Reset the cache. Exported for spec teardown — production callers
|
|
59
|
+
* never need this.
|
|
60
|
+
*/
|
|
61
|
+
export function resetInitializedCache() {
|
|
62
|
+
initialisedCache.clear();
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Detect `.pugi/` at `root`. Pure filesystem read; swallows permission
|
|
66
|
+
* errors (returns false). Exported so the spec can assert the same
|
|
67
|
+
* detection the helper uses without re-implementing the check.
|
|
68
|
+
*/
|
|
69
|
+
export function hasPugiWorkspace(root) {
|
|
70
|
+
const path = resolve(root, '.pugi');
|
|
71
|
+
try {
|
|
72
|
+
if (!existsSync(path))
|
|
73
|
+
return false;
|
|
74
|
+
return statSync(path).isDirectory();
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Auto-init pre-flight. Idempotent and process-cache aware — calling
|
|
82
|
+
* twice in the same process for the same workspace returns `already`
|
|
83
|
+
* the second time even if the filesystem state changed underneath.
|
|
84
|
+
*
|
|
85
|
+
* Implementation notes:
|
|
86
|
+
*
|
|
87
|
+
* - Returns `{ status: 'already' }` when `.pugi/` exists OR the cache
|
|
88
|
+
* remembers this workspace. The cache short-circuit means a second
|
|
89
|
+
* command in the same process never blocks on the prompt.
|
|
90
|
+
* - Interactive + missing → prompt. The default answer (empty input
|
|
91
|
+
* OR a leading `y` / `yes`) maps to scaffold. Anything else
|
|
92
|
+
* (`n`, `no`, `cancel`, whitespace + non-y) maps to declined.
|
|
93
|
+
* - Scaffolder failures propagate to the caller; the helper does
|
|
94
|
+
* NOT swallow them because a failed scaffold means the operator's
|
|
95
|
+
* command cannot continue anyway. Tests assert this.
|
|
96
|
+
*/
|
|
97
|
+
export async function ensureInitialized(opts) {
|
|
98
|
+
const root = resolve(opts.cwd ?? process.cwd());
|
|
99
|
+
if (initialisedCache.has(root)) {
|
|
100
|
+
return { status: 'already', root };
|
|
101
|
+
}
|
|
102
|
+
if (hasPugiWorkspace(root)) {
|
|
103
|
+
initialisedCache.add(root);
|
|
104
|
+
return { status: 'already', root };
|
|
105
|
+
}
|
|
106
|
+
if (opts.skip) {
|
|
107
|
+
return { status: 'declined', root, reason: 'disabled' };
|
|
108
|
+
}
|
|
109
|
+
if (!opts.interactive) {
|
|
110
|
+
return { status: 'declined', root, reason: 'non_interactive' };
|
|
111
|
+
}
|
|
112
|
+
if (!opts.prompt) {
|
|
113
|
+
// Defensive — an interactive caller forgot к wire the prompt
|
|
114
|
+
// reader. Treat the same as non-interactive rather than throwing
|
|
115
|
+
// so the surrounding command can degrade gracefully.
|
|
116
|
+
return { status: 'declined', root, reason: 'non_interactive' };
|
|
117
|
+
}
|
|
118
|
+
const write = opts.write ?? ((line) => process.stderr.write(line));
|
|
119
|
+
write(`No Pugi workspace found at ${root}.\n`);
|
|
120
|
+
const answer = (await opts.prompt('Initialize a new Pugi workspace here? (Y/n) ')).trim().toLowerCase();
|
|
121
|
+
// Default = yes (empty input OR leading 'y'). Anything else = no.
|
|
122
|
+
// Mirrors the gh CLI / the upstream prompt convention where the upper-
|
|
123
|
+
// case option in `(Y/n)` is the default-on-Enter answer.
|
|
124
|
+
const acceptedShort = answer === '' || answer === 'y' || answer === 'yes';
|
|
125
|
+
if (!acceptedShort) {
|
|
126
|
+
write('Initialization declined.\n');
|
|
127
|
+
return { status: 'declined', root, reason: 'user_declined' };
|
|
128
|
+
}
|
|
129
|
+
await opts.scaffold({ cwd: root });
|
|
130
|
+
initialisedCache.add(root);
|
|
131
|
+
return { status: 'initialized', root };
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=ensure-initialized.js.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* — Onboarding marker file.
|
|
3
|
+
*
|
|
4
|
+
* `~/.pugi/.onboarded` is a single, contentless marker. Its existence
|
|
5
|
+
* tells the bare-invocation hint check that the operator has already
|
|
6
|
+
* walked the `/onboarding` wizard at least once, so we no longer print
|
|
7
|
+
* the "Tip: run `pugi onboarding` to configure defaults" line on
|
|
8
|
+
* cold-start. The wizard re-runs cleanly — idempotency lives in the
|
|
9
|
+
* wizard itself, not in the marker.
|
|
10
|
+
*
|
|
11
|
+
* Why a marker file (and not just `~/.pugi/config.json`'s existence)?
|
|
12
|
+
*
|
|
13
|
+
* - The config file is touched the moment ANY surface writes a
|
|
14
|
+
* default — `pugi style terse --persist`, `pugi permissions ask`,
|
|
15
|
+
* `pugi config set …`. Using "config exists" as the proxy for
|
|
16
|
+
* "operator has onboarded" would silence the first-run hint for
|
|
17
|
+
* operators who never saw the wizard.
|
|
18
|
+
*
|
|
19
|
+
* - The marker is explicit: it is written ONLY by the wizard's exit
|
|
20
|
+
* step (or `pugi onboarding --mark-only` for the upgrade-path
|
|
21
|
+
* where we want to suppress the hint without forcing a re-walk).
|
|
22
|
+
*
|
|
23
|
+
* - Removing the marker (`rm ~/.pugi/.onboarded`) re-arms the hint
|
|
24
|
+
* without nuking the operator's accumulated config — useful for
|
|
25
|
+
* QA, support flows, and demo-machine resets.
|
|
26
|
+
*
|
|
27
|
+
* Path resolution mirrors the L6/L18 convention: `PUGI_HOME` env wins,
|
|
28
|
+
* else `~/.pugi`. The marker is touched as an empty file (no JSON, no
|
|
29
|
+
* timestamp payload) — readers MUST treat existence as the only signal
|
|
30
|
+
* so a future change to mtime semantics does not break us.
|
|
31
|
+
*
|
|
32
|
+
* IO contract:
|
|
33
|
+
* - `isOnboarded(env)` — pure read; never throws, returns false on
|
|
34
|
+
* any fs error so a corrupted home dir cannot hide the hint.
|
|
35
|
+
* - `markOnboarded(env)` — best-effort write; creates `<home>/.pugi/`
|
|
36
|
+
* if missing, mode 0o600 on the marker so it never lands in a
|
|
37
|
+
* world-readable backup.
|
|
38
|
+
* - `clearOnboarded(env)` — best-effort delete; absent file is a
|
|
39
|
+
* no-op (not an error). Used by `pugi onboarding --reset` and the
|
|
40
|
+
* spec teardown.
|
|
41
|
+
*/
|
|
42
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync, } from 'node:fs';
|
|
43
|
+
import { homedir } from 'node:os';
|
|
44
|
+
import { resolve } from 'node:path';
|
|
45
|
+
/**
|
|
46
|
+
* Env override for `~/.pugi`. Same convention as L6 / L18 — spec
|
|
47
|
+
* fixtures point this at a temp dir so a real developer machine never
|
|
48
|
+
* lands a stray marker.
|
|
49
|
+
*/
|
|
50
|
+
export const PUGI_HOME_ENV = 'PUGI_HOME';
|
|
51
|
+
/**
|
|
52
|
+
* Marker basename. Hidden (leading dot) so it does not clutter `ls`
|
|
53
|
+
* inside `~/.pugi/` next to `config.json` / `session.json`.
|
|
54
|
+
*/
|
|
55
|
+
const MARKER_BASENAME = '.onboarded';
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the absolute path to the onboarding marker. Exported for the
|
|
58
|
+
* spec; production callers go through `isOnboarded` / `markOnboarded`.
|
|
59
|
+
*/
|
|
60
|
+
export function onboardingMarkerPath(env = process.env) {
|
|
61
|
+
const home = env[PUGI_HOME_ENV] ?? resolve(homedir(), '.pugi');
|
|
62
|
+
return resolve(home, MARKER_BASENAME);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* True when the marker exists. Pure read. Defensive: any fs error
|
|
66
|
+
* (race with deletion, permission flip) degrades to `false` — printing
|
|
67
|
+
* the hint twice is harmless, silently swallowing the wizard would
|
|
68
|
+
* surprise the operator.
|
|
69
|
+
*/
|
|
70
|
+
export function isOnboarded(env = process.env) {
|
|
71
|
+
try {
|
|
72
|
+
return existsSync(onboardingMarkerPath(env));
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Touch the marker. Creates `<home>/.pugi/` if missing. Idempotent —
|
|
80
|
+
* re-touching an existing marker is a no-op for the consumer (the file
|
|
81
|
+
* was already there; the hint was already suppressed).
|
|
82
|
+
*
|
|
83
|
+
* Best-effort: a write failure is swallowed because the wizard already
|
|
84
|
+
* completed its real work (mode + style + telemetry were persisted).
|
|
85
|
+
* The worst case is a redundant hint on the next `pugi` invocation —
|
|
86
|
+
* preferable to crashing the freshly-completed wizard with a stat EIO.
|
|
87
|
+
*/
|
|
88
|
+
export function markOnboarded(env = process.env) {
|
|
89
|
+
const path = onboardingMarkerPath(env);
|
|
90
|
+
try {
|
|
91
|
+
mkdirSync(resolve(path, '..'), { recursive: true });
|
|
92
|
+
writeFileSync(path, '', { encoding: 'utf8', mode: 0o600 });
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// intentionally swallowed — see header
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Remove the marker. Used by `pugi onboarding --reset` (and the spec
|
|
100
|
+
* teardown). Absent file is a no-op; any other fs error is swallowed
|
|
101
|
+
* so a permission glitch never leaks out of the reset surface.
|
|
102
|
+
*/
|
|
103
|
+
export function clearOnboarded(env = process.env) {
|
|
104
|
+
try {
|
|
105
|
+
rmSync(onboardingMarkerPath(env), { force: true });
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// intentionally swallowed — see header
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=marker.js.map
|