@pugi/cli 0.1.0-beta.10 → 0.1.0-beta.101
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 +55 -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 +598 -0
- package/dist/core/codegraph/queries/go.scm +57 -0
- package/dist/core/codegraph/queries/javascript.scm +56 -0
- package/dist/core/codegraph/queries/python.scm +55 -0
- package/dist/core/codegraph/queries/rust.scm +63 -0
- package/dist/core/codegraph/queries/typescript.scm +91 -0
- package/dist/core/codegraph/reindex.js +218 -0
- package/dist/core/codegraph/resolve-edges.js +107 -0
- package/dist/core/codegraph/types.js +34 -0
- package/dist/core/codegraph/watcher.js +440 -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 +67 -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 +219 -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/eval/v1/ledger.js +83 -0
- package/dist/core/eval/v1/runner.js +280 -0
- package/dist/core/eval/v1/scoring.js +68 -0
- package/dist/core/eval/v1/task-loader.js +191 -0
- package/dist/core/eval/v1/types.js +14 -0
- package/dist/core/eval/v1/verifier.js +176 -0
- package/dist/core/eval/v1/yaml-parser.js +250 -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 +43 -0
- package/dist/core/sandboxing/bubblewrap.js +209 -0
- package/dist/core/sandboxing/index.js +78 -0
- package/dist/core/sandboxing/none.js +19 -0
- package/dist/core/sandboxing/policy.js +97 -0
- package/dist/core/sandboxing/seatbelt.js +231 -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 +402 -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 +4403 -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/eval-v1.js +266 -0
- package/dist/runtime/commands/feedback.js +184 -0
- package/dist/runtime/commands/hooks.js +187 -0
- package/dist/runtime/commands/index-cmd.js +459 -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-cli.js +182 -0
- 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 +811 -49
- 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 +29 -6
- 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,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task loader for pugi-eval-v1.
|
|
3
|
+
*
|
|
4
|
+
* Reads `<root>/eval/v1/tasks/<NN>-<slug>.task.yml`, parses via the
|
|
5
|
+
* embedded minimal YAML parser, validates via Zod, then asserts the
|
|
6
|
+
* filename matches the parsed `id` so a refactor cannot silently
|
|
7
|
+
* decouple the basename from the schema id.
|
|
8
|
+
*
|
|
9
|
+
* The loader also exposes `loadTaskManifest` which is the spec-side
|
|
10
|
+
* gate that pins task content via sha256 and refuses mismatches (the
|
|
11
|
+
* frozen-benchmark invariant from backlog #120).
|
|
12
|
+
*/
|
|
13
|
+
import { createHash } from 'node:crypto';
|
|
14
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
15
|
+
import { basename, join, resolve } from 'node:path';
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { parseTaskYaml } from './yaml-parser.js';
|
|
18
|
+
const TASK_ID_RE = /^\d{2}-[a-z][a-z0-9-]*$/;
|
|
19
|
+
const TASK_FILENAME_RE = /^(\d{2}-[a-z][a-z0-9-]*)\.task\.yml$/;
|
|
20
|
+
const verificationSchema = z.discriminatedUnion('kind', [
|
|
21
|
+
z
|
|
22
|
+
.object({ kind: z.literal('file_exists'), path: z.string().min(1) })
|
|
23
|
+
.strict(),
|
|
24
|
+
z
|
|
25
|
+
.object({
|
|
26
|
+
kind: z.literal('file_contains'),
|
|
27
|
+
path: z.string().min(1),
|
|
28
|
+
pattern: z.string().min(1),
|
|
29
|
+
mode: z.enum(['literal', 'regex']).optional(),
|
|
30
|
+
})
|
|
31
|
+
.strict(),
|
|
32
|
+
z
|
|
33
|
+
.object({
|
|
34
|
+
kind: z.literal('output_contains'),
|
|
35
|
+
pattern: z.string().min(1),
|
|
36
|
+
mode: z.enum(['literal', 'regex']).optional(),
|
|
37
|
+
})
|
|
38
|
+
.strict(),
|
|
39
|
+
z
|
|
40
|
+
.object({
|
|
41
|
+
kind: z.literal('command_exit_code'),
|
|
42
|
+
command: z.string().min(1),
|
|
43
|
+
expectedExitCode: z.number().int(),
|
|
44
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
45
|
+
})
|
|
46
|
+
.strict(),
|
|
47
|
+
]);
|
|
48
|
+
const taskSchema = z
|
|
49
|
+
.object({
|
|
50
|
+
id: z.string().regex(TASK_ID_RE, 'id must match <NN>-<slug>'),
|
|
51
|
+
difficulty: z.enum(['simple', 'medium', 'hard']),
|
|
52
|
+
intensity: z.enum(['quick', 'standard', 'deep', 'marathon']),
|
|
53
|
+
command: z.enum(['code', 'fix', 'explain', 'plan', 'build']),
|
|
54
|
+
brief: z.string().min(1),
|
|
55
|
+
fixture: z.record(z.string(), z.string()).optional(),
|
|
56
|
+
verification: z.array(verificationSchema).min(1),
|
|
57
|
+
maxTokens: z.number().int().positive(),
|
|
58
|
+
maxTurns: z.number().int().positive(),
|
|
59
|
+
timeoutMs: z.number().int().positive(),
|
|
60
|
+
})
|
|
61
|
+
.strict();
|
|
62
|
+
/**
|
|
63
|
+
* Default tasks directory relative to the @pugi/cli workspace root.
|
|
64
|
+
* Tests inject a different directory; production resolves it through
|
|
65
|
+
* `defaultTasksDir`.
|
|
66
|
+
*/
|
|
67
|
+
export function defaultTasksDir(packageRoot) {
|
|
68
|
+
return resolve(packageRoot, 'eval', 'v1', 'tasks');
|
|
69
|
+
}
|
|
70
|
+
export function defaultManifestPath(packageRoot) {
|
|
71
|
+
return resolve(packageRoot, 'eval', 'v1', 'manifest.json');
|
|
72
|
+
}
|
|
73
|
+
export function defaultLedgerPath(packageRoot) {
|
|
74
|
+
return resolve(packageRoot, 'eval', 'v1', 'results.tsv');
|
|
75
|
+
}
|
|
76
|
+
export function listTaskFiles(tasksDir) {
|
|
77
|
+
let entries;
|
|
78
|
+
try {
|
|
79
|
+
entries = readdirSync(tasksDir);
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
throw new Error(`eval-v1 tasks directory not found at ${tasksDir}: ${err.message}`);
|
|
83
|
+
}
|
|
84
|
+
const files = entries.filter((e) => TASK_FILENAME_RE.test(e)).sort();
|
|
85
|
+
return files.map((f) => join(tasksDir, f));
|
|
86
|
+
}
|
|
87
|
+
export function loadTaskFile(path) {
|
|
88
|
+
const raw = readFileSync(path, 'utf8');
|
|
89
|
+
const filenameMatch = TASK_FILENAME_RE.exec(basename(path));
|
|
90
|
+
if (!filenameMatch) {
|
|
91
|
+
throw new Error(`eval-v1: filename ${basename(path)} does not match <NN>-<slug>.task.yml`);
|
|
92
|
+
}
|
|
93
|
+
const expectedId = filenameMatch[1];
|
|
94
|
+
let parsed;
|
|
95
|
+
try {
|
|
96
|
+
parsed = parseTaskYaml(raw);
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
throw new Error(`eval-v1 task ${basename(path)} failed YAML parse: ${err.message}`);
|
|
100
|
+
}
|
|
101
|
+
const result = taskSchema.safeParse(parsed);
|
|
102
|
+
if (!result.success) {
|
|
103
|
+
throw new Error(`eval-v1 task ${basename(path)} failed schema validation: ${result.error.message}`);
|
|
104
|
+
}
|
|
105
|
+
if (result.data.id !== expectedId) {
|
|
106
|
+
throw new Error(`eval-v1 task ${basename(path)} id field ${result.data.id} does not match filename ${expectedId}`);
|
|
107
|
+
}
|
|
108
|
+
return { path, raw, spec: result.data };
|
|
109
|
+
}
|
|
110
|
+
export function loadAllTasks(tasksDir) {
|
|
111
|
+
const files = listTaskFiles(tasksDir);
|
|
112
|
+
const loaded = files.map((f) => loadTaskFile(f));
|
|
113
|
+
// Detect duplicate ids that survived (cannot happen given filename
|
|
114
|
+
// regex but defensive against future refactors).
|
|
115
|
+
const seen = new Set();
|
|
116
|
+
for (const entry of loaded) {
|
|
117
|
+
if (seen.has(entry.spec.id)) {
|
|
118
|
+
throw new Error(`eval-v1: duplicate task id ${entry.spec.id}`);
|
|
119
|
+
}
|
|
120
|
+
seen.add(entry.spec.id);
|
|
121
|
+
}
|
|
122
|
+
return loaded;
|
|
123
|
+
}
|
|
124
|
+
export function manifestEntryFor(path, raw) {
|
|
125
|
+
const filenameMatch = TASK_FILENAME_RE.exec(basename(path));
|
|
126
|
+
if (!filenameMatch) {
|
|
127
|
+
throw new Error(`cannot derive manifest entry: filename ${basename(path)} does not match`);
|
|
128
|
+
}
|
|
129
|
+
const id = filenameMatch[1];
|
|
130
|
+
const sha = createHash('sha256').update(raw).digest('hex');
|
|
131
|
+
return { id, sha256: sha, byteLength: Buffer.byteLength(raw, 'utf8') };
|
|
132
|
+
}
|
|
133
|
+
export function readManifest(manifestPath) {
|
|
134
|
+
const raw = readFileSync(manifestPath, 'utf8');
|
|
135
|
+
const parsed = JSON.parse(raw);
|
|
136
|
+
const schema = z
|
|
137
|
+
.object({
|
|
138
|
+
schemaVersion: z.literal(1),
|
|
139
|
+
generatedAt: z.string(),
|
|
140
|
+
entries: z
|
|
141
|
+
.array(z
|
|
142
|
+
.object({
|
|
143
|
+
id: z.string().regex(TASK_ID_RE),
|
|
144
|
+
sha256: z.string().regex(/^[0-9a-f]{64}$/),
|
|
145
|
+
byteLength: z.number().int().nonnegative(),
|
|
146
|
+
})
|
|
147
|
+
.strict())
|
|
148
|
+
.min(1),
|
|
149
|
+
})
|
|
150
|
+
.strict();
|
|
151
|
+
const result = schema.safeParse(parsed);
|
|
152
|
+
if (!result.success) {
|
|
153
|
+
throw new Error(`eval-v1 manifest ${manifestPath} invalid: ${result.error.message}`);
|
|
154
|
+
}
|
|
155
|
+
return result.data;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Compare the on-disk task files against the committed manifest. Used
|
|
159
|
+
* by the meta-spec to enforce the frozen-benchmark invariant.
|
|
160
|
+
*/
|
|
161
|
+
export function diffManifest(tasks, manifest) {
|
|
162
|
+
const reasons = [];
|
|
163
|
+
const computed = new Map();
|
|
164
|
+
for (const t of tasks) {
|
|
165
|
+
const entry = manifestEntryFor(t.path, t.raw);
|
|
166
|
+
computed.set(entry.id, entry);
|
|
167
|
+
}
|
|
168
|
+
const declared = new Map();
|
|
169
|
+
for (const e of manifest.entries)
|
|
170
|
+
declared.set(e.id, e);
|
|
171
|
+
for (const [id, entry] of computed) {
|
|
172
|
+
const decl = declared.get(id);
|
|
173
|
+
if (!decl) {
|
|
174
|
+
reasons.push(`task ${id} present on disk but missing from manifest`);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (decl.sha256 !== entry.sha256) {
|
|
178
|
+
reasons.push(`task ${id} sha256 mismatch (disk=${entry.sha256.slice(0, 12)} manifest=${decl.sha256.slice(0, 12)})`);
|
|
179
|
+
}
|
|
180
|
+
if (decl.byteLength !== entry.byteLength) {
|
|
181
|
+
reasons.push(`task ${id} byteLength mismatch (disk=${entry.byteLength} manifest=${decl.byteLength})`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
for (const [id] of declared) {
|
|
185
|
+
if (!computed.has(id)) {
|
|
186
|
+
reasons.push(`task ${id} declared in manifest but missing on disk`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return { ok: reasons.length === 0, reasons };
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=task-loader.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pugi-eval-v1 type definitions (backlog #120, Reviewer foundation).
|
|
3
|
+
*
|
|
4
|
+
* Frozen benchmark harness types. The shapes here are stable: every
|
|
5
|
+
* field added later must preserve backward compatibility with the v1
|
|
6
|
+
* `results.tsv` ledger columns and the v1 task YAML schema. Breaking
|
|
7
|
+
* changes ship as `eval-v2`.
|
|
8
|
+
*
|
|
9
|
+
* Why types live in `core/eval/v1/` and not next to the CLI command:
|
|
10
|
+
* the meta-spec, ledger, scoring, and verifier all consume them. CLI
|
|
11
|
+
* command modules stay thin wrappers per the project convention.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verification check executor for pugi-eval-v1.
|
|
3
|
+
*
|
|
4
|
+
* Each check kind has a pure implementation. The runner calls
|
|
5
|
+
* `runVerifications` after the CLI subprocess exits, passing the
|
|
6
|
+
* post-run workspace root + the final stdout text the engine emitted.
|
|
7
|
+
*
|
|
8
|
+
* Path safety: `file_exists` and `file_contains` resolve the path
|
|
9
|
+
* relative to the workspace root and refuse anything that escapes the
|
|
10
|
+
* root (`..` traversal, absolute paths, symlinks). A task spec that
|
|
11
|
+
* tries to peek outside the workspace fails the check rather than
|
|
12
|
+
* leaking host state.
|
|
13
|
+
*/
|
|
14
|
+
import { spawnSync } from 'node:child_process';
|
|
15
|
+
import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
16
|
+
import { isAbsolute, relative, resolve } from 'node:path';
|
|
17
|
+
const DEFAULT_COMMAND_TIMEOUT_MS = 60_000;
|
|
18
|
+
function withinRoot(rootReal, candidateReal) {
|
|
19
|
+
if (candidateReal === rootReal)
|
|
20
|
+
return true;
|
|
21
|
+
const rel = relative(rootReal, candidateReal);
|
|
22
|
+
return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolve `workspaceRoot` to its realpath once so symlinked tmp dirs
|
|
26
|
+
* (macOS `/tmp` -> `/private/tmp`) compare correctly against any
|
|
27
|
+
* resolved child path. Falls back к the raw root when the directory
|
|
28
|
+
* does not yet exist (defensive - production callers always create
|
|
29
|
+
* the tmp dir before invoking).
|
|
30
|
+
*/
|
|
31
|
+
function realRoot(workspaceRoot) {
|
|
32
|
+
try {
|
|
33
|
+
return realpathSync(workspaceRoot);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return workspaceRoot;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function resolveWorkspacePath(workspaceRoot, path) {
|
|
40
|
+
if (isAbsolute(path)) {
|
|
41
|
+
return { ok: false, reason: `absolute paths refused (${path})` };
|
|
42
|
+
}
|
|
43
|
+
if (path.split(/[\\/]/).includes('..')) {
|
|
44
|
+
return { ok: false, reason: `path traversal refused (${path})` };
|
|
45
|
+
}
|
|
46
|
+
const root = realRoot(workspaceRoot);
|
|
47
|
+
const absolute = resolve(root, path);
|
|
48
|
+
if (!withinRoot(root, absolute)) {
|
|
49
|
+
return { ok: false, reason: `path escapes workspace root (${path})` };
|
|
50
|
+
}
|
|
51
|
+
// realpath check: if the file exists, the realpath must also be
|
|
52
|
+
// inside the workspace root - symlinks that point outside are a
|
|
53
|
+
// refusal even when the entry itself is inside.
|
|
54
|
+
if (existsSync(absolute)) {
|
|
55
|
+
try {
|
|
56
|
+
const real = realpathSync(absolute);
|
|
57
|
+
if (!withinRoot(root, real)) {
|
|
58
|
+
return {
|
|
59
|
+
ok: false,
|
|
60
|
+
reason: `symlink target escapes workspace root (${path})`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Stat failure during realpath: surface as missing rather than
|
|
66
|
+
// throwing - the file_exists check will record the failure.
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return { ok: true, absolute };
|
|
70
|
+
}
|
|
71
|
+
function matches(haystack, pattern, mode) {
|
|
72
|
+
if (mode === 'regex') {
|
|
73
|
+
let re;
|
|
74
|
+
try {
|
|
75
|
+
re = new RegExp(pattern);
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
return {
|
|
79
|
+
ok: false,
|
|
80
|
+
reason: `invalid regex ${pattern}: ${err.message}`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return re.test(haystack)
|
|
84
|
+
? { ok: true, reason: '' }
|
|
85
|
+
: { ok: false, reason: `regex ${pattern} did not match` };
|
|
86
|
+
}
|
|
87
|
+
return haystack.includes(pattern)
|
|
88
|
+
? { ok: true, reason: '' }
|
|
89
|
+
: { ok: false, reason: `literal ${JSON.stringify(pattern)} not found` };
|
|
90
|
+
}
|
|
91
|
+
export function runVerification(check, ctx) {
|
|
92
|
+
switch (check.kind) {
|
|
93
|
+
case 'file_exists': {
|
|
94
|
+
const r = resolveWorkspacePath(ctx.workspaceRoot, check.path);
|
|
95
|
+
if (!r.ok)
|
|
96
|
+
return { kind: check.kind, passed: false, detail: r.reason };
|
|
97
|
+
try {
|
|
98
|
+
const stat = statSync(r.absolute);
|
|
99
|
+
if (!stat.isFile()) {
|
|
100
|
+
return {
|
|
101
|
+
kind: check.kind,
|
|
102
|
+
passed: false,
|
|
103
|
+
detail: `${check.path} exists but is not a regular file`,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return { kind: check.kind, passed: true, detail: '' };
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return {
|
|
110
|
+
kind: check.kind,
|
|
111
|
+
passed: false,
|
|
112
|
+
detail: `${check.path} not found`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
case 'file_contains': {
|
|
117
|
+
const r = resolveWorkspacePath(ctx.workspaceRoot, check.path);
|
|
118
|
+
if (!r.ok)
|
|
119
|
+
return { kind: check.kind, passed: false, detail: r.reason };
|
|
120
|
+
let body;
|
|
121
|
+
try {
|
|
122
|
+
body = readFileSync(r.absolute, 'utf8');
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return {
|
|
126
|
+
kind: check.kind,
|
|
127
|
+
passed: false,
|
|
128
|
+
detail: `${check.path} not readable`,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const m = matches(body, check.pattern, check.mode);
|
|
132
|
+
return {
|
|
133
|
+
kind: check.kind,
|
|
134
|
+
passed: m.ok,
|
|
135
|
+
detail: m.ok ? '' : `${check.path}: ${m.reason}`,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
case 'output_contains': {
|
|
139
|
+
const m = matches(ctx.finalText, check.pattern, check.mode);
|
|
140
|
+
return {
|
|
141
|
+
kind: check.kind,
|
|
142
|
+
passed: m.ok,
|
|
143
|
+
detail: m.ok ? '' : `final output: ${m.reason}`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
case 'command_exit_code': {
|
|
147
|
+
const timeout = check.timeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS;
|
|
148
|
+
const result = spawnSync('bash', ['-lc', check.command], {
|
|
149
|
+
cwd: ctx.workspaceRoot,
|
|
150
|
+
timeout,
|
|
151
|
+
encoding: 'utf8',
|
|
152
|
+
env: { ...process.env, CI: '1' },
|
|
153
|
+
});
|
|
154
|
+
if (result.error) {
|
|
155
|
+
return {
|
|
156
|
+
kind: check.kind,
|
|
157
|
+
passed: false,
|
|
158
|
+
detail: `${check.command}: ${result.error.message}`,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const exit = result.status ?? -1;
|
|
162
|
+
if (exit !== check.expectedExitCode) {
|
|
163
|
+
return {
|
|
164
|
+
kind: check.kind,
|
|
165
|
+
passed: false,
|
|
166
|
+
detail: `${check.command}: exit ${exit} expected ${check.expectedExitCode}`,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return { kind: check.kind, passed: true, detail: '' };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
export function runVerifications(checks, ctx) {
|
|
174
|
+
return checks.map((c) => runVerification(c, ctx));
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=verifier.js.map
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal YAML parser for pugi-eval-v1 task files.
|
|
3
|
+
*
|
|
4
|
+
* Same rationale as `core/recipes/schema.ts`: pulling in `js-yaml`
|
|
5
|
+
* is ~70 KB of installed weight for a schema we control. The shape
|
|
6
|
+
* here is intentionally narrow:
|
|
7
|
+
*
|
|
8
|
+
* - Top-level scalars (string / number / boolean).
|
|
9
|
+
* - Block sequences of scalars (e.g. plain string lists).
|
|
10
|
+
* - Block sequences of maps (verification entries).
|
|
11
|
+
* - Nested block maps (fixture file map).
|
|
12
|
+
* - Multi-line block scalars via `|` (literal) and `>` (folded).
|
|
13
|
+
* - `#` line comments.
|
|
14
|
+
*
|
|
15
|
+
* Unsupported YAML features: flow collections, anchors, tags,
|
|
16
|
+
* directives, document separators. The parser throws on anything
|
|
17
|
+
* outside the supported subset so a malformed task file fails fast
|
|
18
|
+
* with a clear line number.
|
|
19
|
+
*
|
|
20
|
+
* The parsed value is `unknown`; downstream loader code applies a Zod
|
|
21
|
+
* schema to narrow it. The parser deliberately does NOT do schema
|
|
22
|
+
* validation - it only enforces YAML well-formedness.
|
|
23
|
+
*/
|
|
24
|
+
export class TaskYamlParseError extends Error {
|
|
25
|
+
line;
|
|
26
|
+
constructor(message, line) {
|
|
27
|
+
super(`task YAML parse error at line ${line}: ${message}`);
|
|
28
|
+
this.line = line;
|
|
29
|
+
this.name = 'TaskYamlParseError';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Tokenise the source into non-blank, non-comment lines + their
|
|
34
|
+
* indentation. Tab characters are rejected (YAML spec) so the indent
|
|
35
|
+
* count is unambiguous.
|
|
36
|
+
*/
|
|
37
|
+
function tokenise(source) {
|
|
38
|
+
const physical = source.split(/\r?\n/);
|
|
39
|
+
const out = [];
|
|
40
|
+
for (let i = 0; i < physical.length; i += 1) {
|
|
41
|
+
const raw = physical[i] ?? '';
|
|
42
|
+
const lineNo = i + 1;
|
|
43
|
+
if (raw.includes('\t')) {
|
|
44
|
+
throw new TaskYamlParseError('tab characters are not allowed; use spaces for indentation', lineNo);
|
|
45
|
+
}
|
|
46
|
+
const stripped = raw.replace(/\s*#.*$/, '');
|
|
47
|
+
if (stripped.trim() === '')
|
|
48
|
+
continue;
|
|
49
|
+
const indent = stripped.length - stripped.replace(/^ +/, '').length;
|
|
50
|
+
out.push({ lineNo, indent, body: stripped.slice(indent) });
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Convert a literal scalar (after the `:` or after a `-` marker) into
|
|
56
|
+
* a JS value. Recognises `true`/`false`, integers, floats, single-
|
|
57
|
+
* and double-quoted strings; everything else is returned verbatim as
|
|
58
|
+
* an unquoted string.
|
|
59
|
+
*/
|
|
60
|
+
function coerceScalar(raw, lineNo) {
|
|
61
|
+
const trimmed = raw.trim();
|
|
62
|
+
if (trimmed === '')
|
|
63
|
+
return '';
|
|
64
|
+
if (trimmed === 'true')
|
|
65
|
+
return true;
|
|
66
|
+
if (trimmed === 'false')
|
|
67
|
+
return false;
|
|
68
|
+
if (trimmed === 'null' || trimmed === '~')
|
|
69
|
+
return '';
|
|
70
|
+
if (/^-?\d+$/.test(trimmed))
|
|
71
|
+
return Number(trimmed);
|
|
72
|
+
if (/^-?\d+\.\d+$/.test(trimmed))
|
|
73
|
+
return Number(trimmed);
|
|
74
|
+
const doubleQuoted = /^"((?:\\.|[^"\\])*)"$/.exec(trimmed);
|
|
75
|
+
if (doubleQuoted) {
|
|
76
|
+
// Decode common escape sequences. This subset matches what the
|
|
77
|
+
// task spec needs (newlines inside regex patterns, escaped
|
|
78
|
+
// backslashes).
|
|
79
|
+
return doubleQuoted[1].replace(/\\(.)/g, (_, c) => {
|
|
80
|
+
if (c === 'n')
|
|
81
|
+
return '\n';
|
|
82
|
+
if (c === 't')
|
|
83
|
+
return '\t';
|
|
84
|
+
if (c === 'r')
|
|
85
|
+
return '\r';
|
|
86
|
+
if (c === '"')
|
|
87
|
+
return '"';
|
|
88
|
+
if (c === '\\')
|
|
89
|
+
return '\\';
|
|
90
|
+
throw new TaskYamlParseError(`unknown escape \\${c} in double-quoted string`, lineNo);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
const singleQuoted = /^'((?:''|[^'])*)'$/.exec(trimmed);
|
|
94
|
+
if (singleQuoted) {
|
|
95
|
+
return singleQuoted[1].replace(/''/g, "'");
|
|
96
|
+
}
|
|
97
|
+
return trimmed;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Block scalar reader for `|` (literal, preserves newlines) and `>`
|
|
101
|
+
* (folded, newlines collapse to spaces). Joins continuation lines
|
|
102
|
+
* whose indent is strictly greater than the parent map's indent.
|
|
103
|
+
*/
|
|
104
|
+
function readBlockScalar(marker, parentIndent, lines, cursor) {
|
|
105
|
+
const collected = [];
|
|
106
|
+
let baseIndent = -1;
|
|
107
|
+
while (cursor.i < lines.length) {
|
|
108
|
+
const next = lines[cursor.i];
|
|
109
|
+
if (next.indent <= parentIndent)
|
|
110
|
+
break;
|
|
111
|
+
if (baseIndent < 0)
|
|
112
|
+
baseIndent = next.indent;
|
|
113
|
+
const slice = ' '.repeat(Math.max(0, next.indent - baseIndent)) + next.body;
|
|
114
|
+
collected.push(slice);
|
|
115
|
+
cursor.i += 1;
|
|
116
|
+
}
|
|
117
|
+
if (marker === '|')
|
|
118
|
+
return collected.join('\n');
|
|
119
|
+
return collected.join(' ');
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Recursively parse a block beginning at `cursor.i` whose body lives
|
|
123
|
+
* strictly deeper than `parentIndent`. The return value is either a
|
|
124
|
+
* map (object) or a sequence (array); the caller decides which based
|
|
125
|
+
* on the first child line.
|
|
126
|
+
*/
|
|
127
|
+
function parseBlock(lines, cursor, parentIndent) {
|
|
128
|
+
if (cursor.i >= lines.length)
|
|
129
|
+
return {};
|
|
130
|
+
const first = lines[cursor.i];
|
|
131
|
+
if (first.indent <= parentIndent)
|
|
132
|
+
return {};
|
|
133
|
+
const blockIndent = first.indent;
|
|
134
|
+
const isSequence = first.body.startsWith('- ') || first.body === '-';
|
|
135
|
+
if (isSequence) {
|
|
136
|
+
const arr = [];
|
|
137
|
+
while (cursor.i < lines.length) {
|
|
138
|
+
const line = lines[cursor.i];
|
|
139
|
+
if (line.indent < blockIndent)
|
|
140
|
+
break;
|
|
141
|
+
if (line.indent > blockIndent) {
|
|
142
|
+
throw new TaskYamlParseError(`unexpected indent ${line.indent} in sequence (expected ${blockIndent})`, line.lineNo);
|
|
143
|
+
}
|
|
144
|
+
if (!line.body.startsWith('-')) {
|
|
145
|
+
throw new TaskYamlParseError(`expected sequence marker '-' but found ${line.body.slice(0, 10)}`, line.lineNo);
|
|
146
|
+
}
|
|
147
|
+
const after = line.body.replace(/^-\s*/, '');
|
|
148
|
+
cursor.i += 1;
|
|
149
|
+
if (after === '') {
|
|
150
|
+
arr.push(parseBlock(lines, cursor, blockIndent));
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
// Inline `- key: value` first entry; treat the rest of the line
|
|
154
|
+
// as a single-key map entry, then continue collecting deeper
|
|
155
|
+
// siblings into the same map.
|
|
156
|
+
const colonIdx = after.indexOf(':');
|
|
157
|
+
if (colonIdx === -1) {
|
|
158
|
+
arr.push(coerceScalar(after, line.lineNo));
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const key = after.slice(0, colonIdx).trim();
|
|
162
|
+
const valRaw = after.slice(colonIdx + 1).trim();
|
|
163
|
+
const entry = {};
|
|
164
|
+
if (valRaw === '' || valRaw === '|' || valRaw === '>') {
|
|
165
|
+
if (valRaw === '|' || valRaw === '>') {
|
|
166
|
+
entry[key] = readBlockScalar(valRaw, blockIndent, lines, cursor);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
entry[key] = parseBlock(lines, cursor, blockIndent);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
entry[key] = coerceScalar(valRaw, line.lineNo);
|
|
174
|
+
}
|
|
175
|
+
// Continue absorbing deeper siblings of this synthetic map.
|
|
176
|
+
while (cursor.i < lines.length) {
|
|
177
|
+
const sibling = lines[cursor.i];
|
|
178
|
+
// Sibling map keys live at the same indent as the inline
|
|
179
|
+
// `key: value` text (which is at `blockIndent + 2` relative to
|
|
180
|
+
// the `-` marker). Anything at blockIndent or shallower
|
|
181
|
+
// belongs to the parent.
|
|
182
|
+
const childIndent = blockIndent + 2;
|
|
183
|
+
if (sibling.indent < childIndent)
|
|
184
|
+
break;
|
|
185
|
+
if (sibling.indent > childIndent) {
|
|
186
|
+
throw new TaskYamlParseError(`unexpected indent ${sibling.indent} (expected ${childIndent})`, sibling.lineNo);
|
|
187
|
+
}
|
|
188
|
+
const sibColon = sibling.body.indexOf(':');
|
|
189
|
+
if (sibColon === -1) {
|
|
190
|
+
throw new TaskYamlParseError(`expected key:value in map but found ${sibling.body.slice(0, 20)}`, sibling.lineNo);
|
|
191
|
+
}
|
|
192
|
+
const sibKey = sibling.body.slice(0, sibColon).trim();
|
|
193
|
+
const sibVal = sibling.body.slice(sibColon + 1).trim();
|
|
194
|
+
cursor.i += 1;
|
|
195
|
+
if (sibVal === '' || sibVal === '|' || sibVal === '>') {
|
|
196
|
+
if (sibVal === '|' || sibVal === '>') {
|
|
197
|
+
entry[sibKey] = readBlockScalar(sibVal, childIndent, lines, cursor);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
entry[sibKey] = parseBlock(lines, cursor, childIndent);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
entry[sibKey] = coerceScalar(sibVal, sibling.lineNo);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
arr.push(entry);
|
|
208
|
+
}
|
|
209
|
+
return arr;
|
|
210
|
+
}
|
|
211
|
+
const map = {};
|
|
212
|
+
while (cursor.i < lines.length) {
|
|
213
|
+
const line = lines[cursor.i];
|
|
214
|
+
if (line.indent < blockIndent)
|
|
215
|
+
break;
|
|
216
|
+
if (line.indent > blockIndent) {
|
|
217
|
+
throw new TaskYamlParseError(`unexpected indent ${line.indent} (expected ${blockIndent})`, line.lineNo);
|
|
218
|
+
}
|
|
219
|
+
const colonIdx = line.body.indexOf(':');
|
|
220
|
+
if (colonIdx === -1) {
|
|
221
|
+
throw new TaskYamlParseError(`expected key:value but found ${line.body.slice(0, 20)}`, line.lineNo);
|
|
222
|
+
}
|
|
223
|
+
const key = line.body.slice(0, colonIdx).trim();
|
|
224
|
+
const valRaw = line.body.slice(colonIdx + 1).trim();
|
|
225
|
+
cursor.i += 1;
|
|
226
|
+
if (valRaw === '') {
|
|
227
|
+
map[key] = parseBlock(lines, cursor, blockIndent);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (valRaw === '|' || valRaw === '>') {
|
|
231
|
+
map[key] = readBlockScalar(valRaw, blockIndent, lines, cursor);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
map[key] = coerceScalar(valRaw, line.lineNo);
|
|
235
|
+
}
|
|
236
|
+
return map;
|
|
237
|
+
}
|
|
238
|
+
export function parseTaskYaml(source) {
|
|
239
|
+
const lines = tokenise(source);
|
|
240
|
+
if (lines.length === 0)
|
|
241
|
+
return {};
|
|
242
|
+
const cursor = { i: 0 };
|
|
243
|
+
const result = parseBlock(lines, cursor, -1);
|
|
244
|
+
if (cursor.i !== lines.length) {
|
|
245
|
+
const stray = lines[cursor.i];
|
|
246
|
+
throw new TaskYamlParseError(`stray content after document body`, stray.lineNo);
|
|
247
|
+
}
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=yaml-parser.js.map
|