ashlr-plugin 1.36.4
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/.agents/plugins/marketplace.json +20 -0
- package/.claude-plugin/marketplace.json +39 -0
- package/.claude-plugin/plugin.json +36 -0
- package/.codex/agents/README.md +9 -0
- package/.codex/agents/ashlr-explorer.md +36 -0
- package/.codex/agents/ashlr-worker.md +38 -0
- package/.codex-plugin/plugin.json +45 -0
- package/.mcp.json +14 -0
- package/LICENSE +21 -0
- package/README.md +311 -0
- package/agents/ashlr-code.md +69 -0
- package/agents/ashlr-explore.md +44 -0
- package/agents/ashlr-plan.md +54 -0
- package/bench/fixtures/notebook-large.ipynb +660 -0
- package/bench/fixtures/notebook-small.ipynb +39 -0
- package/bench/fixtures/tasklist-closed.json +62 -0
- package/bench/fixtures/tasklist-large-mixed.json +27 -0
- package/bench/fixtures/tasklist-sprint-open.json +79 -0
- package/bench/fixtures/websearch-bun-runtime.json +32 -0
- package/bench/fixtures/websearch-react-hooks-patterns.json +32 -0
- package/bench/fixtures/websearch-typescript-generics.json +38 -0
- package/bench/refs/node-sdk/.refrev +1 -0
- package/bench/refs/node-sdk/README.md +17 -0
- package/bench/refs/node-sdk/src/agent/agent.ts +163 -0
- package/bench/refs/node-sdk/src/agent/create-agent-ui-stream.ts +83 -0
- package/bench/refs/node-sdk/src/agent/tool-loop-agent.ts +238 -0
- package/bench/refs/node-sdk/src/embed/embed-many.ts +409 -0
- package/bench/refs/node-sdk/src/embed/embed-result.ts +50 -0
- package/bench/refs/node-sdk/src/embed/embed.ts +257 -0
- package/bench/refs/node-sdk/src/error/invalid-argument-error.ts +34 -0
- package/bench/refs/node-sdk/src/error/invalid-tool-input-error.ts +33 -0
- package/bench/refs/node-sdk/src/error/no-object-generated-error.ts +70 -0
- package/bench/refs/node-sdk/src/generate-image/generate-image-result.ts +42 -0
- package/bench/refs/node-sdk/src/generate-image/generate-image.ts +362 -0
- package/bench/refs/node-sdk/src/generate-object/generate-object-result.ts +67 -0
- package/bench/refs/node-sdk/src/generate-object/generate-object.ts +501 -0
- package/bench/refs/node-sdk/src/generate-object/inject-json-instruction.ts +30 -0
- package/bench/refs/node-sdk/src/generate-object/output-strategy.ts +415 -0
- package/bench/refs/node-sdk/src/generate-object/repair-text.ts +12 -0
- package/bench/refs/node-sdk/src/generate-object/stream-object.ts +960 -0
- package/bench/refs/node-sdk/src/generate-speech/generate-speech.ts +191 -0
- package/bench/refs/node-sdk/src/generate-text/generate-text.ts +1484 -0
- package/bench/refs/node-sdk/src/generate-text/stream-text.ts +2708 -0
- package/bench/refs/node-sdk/src/generate-text/tool-call.ts +59 -0
- package/bench/refs/node-sdk/src/generate-text/tool-result.ts +36 -0
- package/bench/refs/node-sdk/src/telemetry/create-telemetry-dispatcher.ts +146 -0
- package/bench/refs/node-sdk/src/telemetry/telemetry-registry.ts +15 -0
- package/bench/refs/node-sdk/src/telemetry/telemetry.ts +223 -0
- package/bench/refs/node-sdk/src/util/async-iterable-stream.ts +94 -0
- package/bench/refs/node-sdk/src/util/consume-stream.ts +31 -0
- package/bench/refs/node-sdk/src/util/create-stitchable-stream.ts +112 -0
- package/bench/refs/node-sdk/src/util/download/download.ts +74 -0
- package/bench/refs/node-sdk/src/util/error-handler.ts +1 -0
- package/bench/refs/node-sdk/src/util/fix-json.ts +401 -0
- package/bench/refs/node-sdk/src/util/merge-objects.ts +79 -0
- package/bench/refs/python-lib/.refrev +1 -0
- package/bench/refs/python-lib/README.md +18 -0
- package/bench/refs/python-lib/pandas/core/_numba/executor.py +249 -0
- package/bench/refs/python-lib/pandas/core/_numba/extensions.py +586 -0
- package/bench/refs/python-lib/pandas/core/_numba/kernels/mean_.py +198 -0
- package/bench/refs/python-lib/pandas/core/_numba/kernels/min_max_.py +178 -0
- package/bench/refs/python-lib/pandas/core/_numba/kernels/sum_.py +255 -0
- package/bench/refs/python-lib/pandas/core/_numba/kernels/var_.py +251 -0
- package/bench/refs/python-lib/pandas/core/accessor.py +520 -0
- package/bench/refs/python-lib/pandas/core/api.py +138 -0
- package/bench/refs/python-lib/pandas/core/array_algos/masked_accumulations.py +97 -0
- package/bench/refs/python-lib/pandas/core/array_algos/masked_reductions.py +252 -0
- package/bench/refs/python-lib/pandas/core/array_algos/putmask.py +150 -0
- package/bench/refs/python-lib/pandas/core/array_algos/quantile.py +232 -0
- package/bench/refs/python-lib/pandas/core/array_algos/replace.py +159 -0
- package/bench/refs/python-lib/pandas/core/array_algos/take.py +609 -0
- package/bench/refs/python-lib/pandas/core/arraylike.py +534 -0
- package/bench/refs/python-lib/pandas/core/arrays/_arrow_string_mixins.py +434 -0
- package/bench/refs/python-lib/pandas/core/arrays/_mixins.py +630 -0
- package/bench/refs/python-lib/pandas/core/arrays/_ranges.py +276 -0
- package/bench/refs/python-lib/pandas/core/arrays/_utils.py +77 -0
- package/bench/refs/python-lib/pandas/core/arrays/arrow/accessors.py +521 -0
- package/bench/refs/python-lib/pandas/core/arrays/arrow/extension_types.py +174 -0
- package/bench/refs/python-lib/pandas/core/arrays/boolean.py +454 -0
- package/bench/refs/python-lib/pandas/core/arrays/floating.py +192 -0
- package/bench/refs/python-lib/pandas/core/arrays/integer.py +296 -0
- package/bench/refs/python-lib/pandas/core/arrays/masked.py +2140 -0
- package/bench/refs/python-lib/pandas/core/arrays/numeric.py +305 -0
- package/bench/refs/python-lib/pandas/core/arrays/numpy_.py +701 -0
- package/bench/refs/python-lib/pandas/core/arrays/period.py +1586 -0
- package/bench/refs/python-lib/pandas/core/arrays/sparse/accessor.py +505 -0
- package/bench/refs/python-lib/pandas/core/arrays/sparse/scipy_sparse.py +208 -0
- package/bench/refs/rust-project/.refrev +1 -0
- package/bench/refs/rust-project/README.md +17 -0
- package/bench/refs/rust-project/tokio/src/doc/os.rs +68 -0
- package/bench/refs/rust-project/tokio/src/fs/dir_builder.rs +129 -0
- package/bench/refs/rust-project/tokio/src/fs/file.rs +1113 -0
- package/bench/refs/rust-project/tokio/src/fs/mocks.rs +176 -0
- package/bench/refs/rust-project/tokio/src/fs/mod.rs +324 -0
- package/bench/refs/rust-project/tokio/src/fs/open_options.rs +856 -0
- package/bench/refs/rust-project/tokio/src/fs/read.rs +80 -0
- package/bench/refs/rust-project/tokio/src/fs/read_dir.rs +357 -0
- package/bench/refs/rust-project/tokio/src/fs/write.rs +99 -0
- package/bench/refs/rust-project/tokio/src/future/maybe_done.rs +123 -0
- package/bench/refs/rust-project/tokio/src/future/try_join.rs +82 -0
- package/bench/refs/rust-project/tokio/src/io/async_buf_read.rs +117 -0
- package/bench/refs/rust-project/tokio/src/io/async_read.rs +133 -0
- package/bench/refs/rust-project/tokio/src/io/async_seek.rs +96 -0
- package/bench/refs/rust-project/tokio/src/io/async_write.rs +402 -0
- package/bench/refs/rust-project/tokio/src/io/blocking.rs +339 -0
- package/bench/refs/rust-project/tokio/src/io/interest.rs +345 -0
- package/bench/refs/rust-project/tokio/src/io/join.rs +130 -0
- package/bench/refs/rust-project/tokio/src/io/mod.rs +308 -0
- package/bench/refs/rust-project/tokio/src/io/poll_evented.rs +319 -0
- package/bench/refs/rust-project/tokio/src/io/read_buf.rs +344 -0
- package/bench/refs/rust-project/tokio/src/io/ready.rs +322 -0
- package/bench/refs/rust-project/tokio/src/io/split.rs +166 -0
- package/bench/refs/rust-project/tokio/src/io/stderr.rs +148 -0
- package/bench/refs/rust-project/tokio/src/io/stdin.rs +98 -0
- package/bench/refs/rust-project/tokio/src/io/stdio_common.rs +222 -0
- package/bench/refs/rust-project/tokio/src/io/stdout.rs +197 -0
- package/bench/refs/rust-project/tokio/src/io/util/buf_reader.rs +311 -0
- package/bench/refs/rust-project/tokio/src/io/util/buf_stream.rs +207 -0
- package/bench/refs/rust-project/tokio/src/io/util/buf_writer.rs +310 -0
- package/bench/refs/rust-project/tokio/src/io/util/chain.rs +144 -0
- package/commands/_spawn-patterns.json +32 -0
- package/commands/ashlr-allow.md +21 -0
- package/commands/ashlr-benchmark.md +53 -0
- package/commands/ashlr-brief.md +97 -0
- package/commands/ashlr-budget.md +76 -0
- package/commands/ashlr-compact.md +54 -0
- package/commands/ashlr-dashboard.md +37 -0
- package/commands/ashlr-doctor.md +56 -0
- package/commands/ashlr-eco-mode.md +92 -0
- package/commands/ashlr-genome-init.md +24 -0
- package/commands/ashlr-genome-keygen.md +36 -0
- package/commands/ashlr-genome-loop.md +118 -0
- package/commands/ashlr-genome-push.md +36 -0
- package/commands/ashlr-genome-rewrap.md +48 -0
- package/commands/ashlr-genome-team-init.md +67 -0
- package/commands/ashlr-handoff.md +23 -0
- package/commands/ashlr-help.md +86 -0
- package/commands/ashlr-hook-timings.md +30 -0
- package/commands/ashlr-leaderboard.md +50 -0
- package/commands/ashlr-ollama-setup.md +39 -0
- package/commands/ashlr-orchestrate-status.md +72 -0
- package/commands/ashlr-orchestrate.md +83 -0
- package/commands/ashlr-parallelize.md +63 -0
- package/commands/ashlr-report-crash.md +33 -0
- package/commands/ashlr-resume.md +72 -0
- package/commands/ashlr-savings.md +24 -0
- package/commands/ashlr-settings.md +50 -0
- package/commands/ashlr-spawn.md +61 -0
- package/commands/ashlr-start.md +60 -0
- package/commands/ashlr-status.md +64 -0
- package/commands/ashlr-team-invite.md +47 -0
- package/commands/ashlr-tier.md +122 -0
- package/commands/ashlr-tour.md +60 -0
- package/commands/ashlr-update.md +155 -0
- package/commands/ashlr-upgrade.md +96 -0
- package/commands/goal.md +58 -0
- package/commands/loop.md +40 -0
- package/docs/CNAME +1 -0
- package/docs/PRO_TIER.md +104 -0
- package/docs/README.md +21 -0
- package/docs/architecture.md +415 -0
- package/docs/ashlr-pipe.md +161 -0
- package/docs/assets/logo.svg +19 -0
- package/docs/assets/og.png +0 -0
- package/docs/assets/og.svg +41 -0
- package/docs/benchmarks-v2.json +355 -0
- package/docs/benchmarks.json +102 -0
- package/docs/benchmarks.md +211 -0
- package/docs/billing.md +185 -0
- package/docs/cloud-genome.md +155 -0
- package/docs/competitive-wozcode.md +92 -0
- package/docs/deploy.md +325 -0
- package/docs/email-templates.md +113 -0
- package/docs/embed-tuning.md +103 -0
- package/docs/genome.md +120 -0
- package/docs/github-oauth-onboarding.md +127 -0
- package/docs/hero-video-script.md +129 -0
- package/docs/index.html +2262 -0
- package/docs/install-prompt.md +78 -0
- package/docs/install-windows.md +132 -0
- package/docs/install.ps1 +171 -0
- package/docs/install.sh +146 -0
- package/docs/launch/README.md +114 -0
- package/docs/launch/hn.md +49 -0
- package/docs/launch/reddit.md +45 -0
- package/docs/launch/x.md +65 -0
- package/docs/launch-post.md +112 -0
- package/docs/legal.md +78 -0
- package/docs/llm-providers.md +99 -0
- package/docs/multi-host-mcp.md +217 -0
- package/docs/multi-turn-architecture.md +108 -0
- package/docs/operations/deployment.md +108 -0
- package/docs/operations/error-monitoring.md +70 -0
- package/docs/operations/weekly-digest.md +70 -0
- package/docs/operations.md +494 -0
- package/docs/platform-support.md +133 -0
- package/docs/policy-packs.md +188 -0
- package/docs/press-kit/fact-sheet.md +78 -0
- package/docs/press-kit/founder-bio.md +3 -0
- package/docs/press-kit/logo.svg +33 -0
- package/docs/press-kit/screenshots.md +146 -0
- package/docs/pricing.md +251 -0
- package/docs/pro-backend-architecture.md +523 -0
- package/docs/producthunt.md +52 -0
- package/docs/router-migration.md +110 -0
- package/docs/session-log-schema.md +68 -0
- package/docs/social.md +95 -0
- package/docs/team-genome.md +528 -0
- package/docs/telemetry.md +194 -0
- package/docs/test-isolation.md +100 -0
- package/docs/upgrade.md +159 -0
- package/hooks/_hook-errors.ts +87 -0
- package/hooks/_hook-perf-emit.ts +113 -0
- package/hooks/_hook-timing-ledger.ts +1133 -0
- package/hooks/_nudge-throttle.ts +127 -0
- package/hooks/_recent-blocks.ts +127 -0
- package/hooks/audit-upload.ts +86 -0
- package/hooks/codex-hooks.json +171 -0
- package/hooks/commit-attribution.ts +179 -0
- package/hooks/edit-batching-nudge.ts +154 -0
- package/hooks/genome-scribe-hook.ts +219 -0
- package/hooks/hooks.json +294 -0
- package/hooks/orient-nudge-hook.ts +242 -0
- package/hooks/policy-enforce.ts +214 -0
- package/hooks/post-tool-use-embedding.ts +93 -0
- package/hooks/post-tool-use-genome.sh +37 -0
- package/hooks/post-tool-use-genome.ts +69 -0
- package/hooks/posttooluse-correlate.ts +122 -0
- package/hooks/posttooluse-dedupe-output.ts +241 -0
- package/hooks/posttooluse-genome-refresh.ts +223 -0
- package/hooks/posttooluse-native-nudge.ts +205 -0
- package/hooks/posttooluse-stale-result.ts +277 -0
- package/hooks/pretooluse-bash.ts +63 -0
- package/hooks/pretooluse-budget-guard.ts +318 -0
- package/hooks/pretooluse-common.ts +985 -0
- package/hooks/pretooluse-eco-router.ts +196 -0
- package/hooks/pretooluse-edit.sh +93 -0
- package/hooks/pretooluse-edit.ts +197 -0
- package/hooks/pretooluse-glob.ts +70 -0
- package/hooks/pretooluse-grep.sh +85 -0
- package/hooks/pretooluse-grep.ts +96 -0
- package/hooks/pretooluse-notebookedit.ts +121 -0
- package/hooks/pretooluse-read.sh +118 -0
- package/hooks/pretooluse-read.ts +118 -0
- package/hooks/pretooluse-task.ts +130 -0
- package/hooks/pretooluse-webfetch.ts +66 -0
- package/hooks/pretooluse-websearch.ts +85 -0
- package/hooks/pretooluse-write.ts +128 -0
- package/hooks/pulse-emit.ts +197 -0
- package/hooks/session-end-consolidate.sh +35 -0
- package/hooks/session-end-consolidate.ts +188 -0
- package/hooks/session-end-stats-gc.ts +80 -0
- package/hooks/session-log-append.sh +94 -0
- package/hooks/session-log-append.ts +116 -0
- package/hooks/session-start.ts +993 -0
- package/hooks/sessionend-hook-health-nudge.ts +451 -0
- package/hooks/sessionend-savings-nudge.ts +318 -0
- package/hooks/sessionend-status-line-nudge.ts +296 -0
- package/hooks/sessionstart-brief.ts +140 -0
- package/hooks/sessionstart-cost-refactor.ts +95 -0
- package/hooks/sessionstart-efficient.ts +110 -0
- package/hooks/sessionstart-genome-author.ts +93 -0
- package/hooks/sessionstart-lean-tools.ts +98 -0
- package/hooks/sessionstart-search.ts +97 -0
- package/hooks/stop-accounting.ts +152 -0
- package/hooks/subagent-stop-rollup.ts +162 -0
- package/hooks/userpromptsubmit-brief-trigger.ts +156 -0
- package/package.json +56 -0
- package/ports/README.md +108 -0
- package/ports/cursor/mcp.json +11 -0
- package/ports/goose/recipe.yaml +26 -0
- package/scripts/ashlr-mcp.ts +40 -0
- package/scripts/auto-update.ts +177 -0
- package/scripts/baseline-scan.ts +831 -0
- package/scripts/bench-orchestrate.ts +377 -0
- package/scripts/benchmark-refs.ts +292 -0
- package/scripts/bootstrap.mjs +100 -0
- package/scripts/brief-eval.ts +234 -0
- package/scripts/bump-version.ts +82 -0
- package/scripts/bun-resolve.d.mts +11 -0
- package/scripts/bun-resolve.mjs +59 -0
- package/scripts/calibrate-grep.ts +467 -0
- package/scripts/capture-grep-workload.ts +260 -0
- package/scripts/check-tier.ts +61 -0
- package/scripts/check-version-sync.ts +97 -0
- package/scripts/cli-orchestrate-status.ts +386 -0
- package/scripts/cli-orchestrate.ts +303 -0
- package/scripts/cli.ts +478 -0
- package/scripts/cloud-smoke-test.ts +313 -0
- package/scripts/coach-report.ts +373 -0
- package/scripts/context-db-migrate.ts +137 -0
- package/scripts/context-status.ts +43 -0
- package/scripts/demo-run.ts +255 -0
- package/scripts/deploy-smoke.sh +140 -0
- package/scripts/doctor.ts +992 -0
- package/scripts/embed-file-worker.ts +104 -0
- package/scripts/embed-tune.ts +308 -0
- package/scripts/errors-report.ts +428 -0
- package/scripts/find-test-leak.ts +69 -0
- package/scripts/generate-badge.ts +357 -0
- package/scripts/genome-auto-consolidate.ts +533 -0
- package/scripts/genome-auto-propose.ts +578 -0
- package/scripts/genome-cloud-pull.ts +363 -0
- package/scripts/genome-cloud-push.ts +382 -0
- package/scripts/genome-cloud-sync.ts +587 -0
- package/scripts/genome-commit-watcher.ts +295 -0
- package/scripts/genome-init.ts +986 -0
- package/scripts/genome-key.ts +233 -0
- package/scripts/genome-keygen.ts +168 -0
- package/scripts/genome-link.ts +59 -0
- package/scripts/genome-refresh-worker.ts +364 -0
- package/scripts/genome-rewrap.ts +90 -0
- package/scripts/genome-synthesizer.ts +679 -0
- package/scripts/genome-team-init.ts +312 -0
- package/scripts/handoff-pack.ts +438 -0
- package/scripts/hook-bootstrap.mjs +75 -0
- package/scripts/hook-timing-reader.ts +371 -0
- package/scripts/hook-timings-report.ts +554 -0
- package/scripts/install-genome-hooks.ts +196 -0
- package/scripts/install-onnx-model.ts +154 -0
- package/scripts/install-permissions.ts +276 -0
- package/scripts/install-status-line.ts +150 -0
- package/scripts/mcp-entrypoint.sh +78 -0
- package/scripts/mcp-entrypoint.ts +130 -0
- package/scripts/mdx-angle-bracket-lint.ts +152 -0
- package/scripts/measure-cold-start.ts +126 -0
- package/scripts/migrate-stats-to-sqlite.ts +86 -0
- package/scripts/ollama-setup.ts +498 -0
- package/scripts/onboarding-wizard.ts +1691 -0
- package/scripts/orchestrate-emit-telemetry.ts +177 -0
- package/scripts/orchestrate-expand.ts +517 -0
- package/scripts/orchestrate-run.ts +744 -0
- package/scripts/print-legend.ts +50 -0
- package/scripts/product-metadata.ts +38 -0
- package/scripts/prune-old-memories.ts +208 -0
- package/scripts/publish.sh +88 -0
- package/scripts/read-calibration.ts +122 -0
- package/scripts/report-crash.ts +226 -0
- package/scripts/run-benchmark.ts +750 -0
- package/scripts/run-root-tests.ts +28 -0
- package/scripts/savings-dashboard.ts +1258 -0
- package/scripts/savings-report-extras.ts +447 -0
- package/scripts/savings-status-line.ts +873 -0
- package/scripts/session-event-emit.ts +266 -0
- package/scripts/session-greet.ts +686 -0
- package/scripts/session-log-report.ts +551 -0
- package/scripts/session-resume.ts +614 -0
- package/scripts/set-hook-mode.ts +83 -0
- package/scripts/smoke-cross-platform.ts +117 -0
- package/scripts/smoke-realtime.ts +257 -0
- package/scripts/smoke-tools.ts +21 -0
- package/scripts/stats-cloud-pull.ts +213 -0
- package/scripts/stats-cloud-sync.ts +261 -0
- package/scripts/telemetry-flush.ts +160 -0
- package/scripts/telemetry-status.ts +266 -0
- package/scripts/timing-budget.ts +35 -0
- package/scripts/ui-animation.ts +120 -0
- package/scripts/ui.ts +431 -0
- package/scripts/upgrade-flow.ts +775 -0
- package/scripts/weekly-digest.ts +394 -0
- package/servers/_accounting.ts +78 -0
- package/servers/_ask-router.ts +84 -0
- package/servers/_ast-chunker.ts +434 -0
- package/servers/_ast-helpers.ts +183 -0
- package/servers/_ast-languages.ts +153 -0
- package/servers/_ast-refactor/_shared.ts +221 -0
- package/servers/_ast-refactor/cross-file-rename.ts +560 -0
- package/servers/_ast-refactor/extract-function.ts +357 -0
- package/servers/_ast-refactor/file-local-rename.ts +172 -0
- package/servers/_ast-refactor.ts +41 -0
- package/servers/_ast-skeleton.ts +107 -0
- package/servers/_bash-summarizers-registry.ts +1096 -0
- package/servers/_crash-dump.ts +210 -0
- package/servers/_cwd-clamp.ts +342 -0
- package/servers/_edit-log.ts +28 -0
- package/servers/_edit-match.ts +236 -0
- package/servers/_edit-validate.ts +196 -0
- package/servers/_embed-calibration.ts +123 -0
- package/servers/_embedding-cache.ts +401 -0
- package/servers/_embedding-model.ts +329 -0
- package/servers/_events.ts +113 -0
- package/servers/_genome-cache.ts +154 -0
- package/servers/_genome-commits.ts +172 -0
- package/servers/_genome-crypto-v2.ts +242 -0
- package/servers/_genome-crypto.ts +259 -0
- package/servers/_genome-discoveries.ts +140 -0
- package/servers/_genome-embed-populator.ts +216 -0
- package/servers/_genome-freshness.ts +134 -0
- package/servers/_genome-issues.ts +171 -0
- package/servers/_genome-live.ts +263 -0
- package/servers/_genome-prs.ts +188 -0
- package/servers/_genome-search.ts +479 -0
- package/servers/_genome-sync.ts +297 -0
- package/servers/_history-tracker.ts +279 -0
- package/servers/_http-helpers.ts +151 -0
- package/servers/_identity-hash.ts +151 -0
- package/servers/_llm-providers/anthropic.ts +132 -0
- package/servers/_llm-providers/cloud.ts +195 -0
- package/servers/_llm-providers/index.ts +350 -0
- package/servers/_llm-providers/local.ts +127 -0
- package/servers/_llm-providers/onnx.ts +393 -0
- package/servers/_llm-providers/types.ts +27 -0
- package/servers/_manifest-v2.ts +447 -0
- package/servers/_mcp-host.ts +93 -0
- package/servers/_nudge-events.ts +451 -0
- package/servers/_orchestrate-executor.ts +316 -0
- package/servers/_prefetch.ts +435 -0
- package/servers/_pricing.ts +256 -0
- package/servers/_pro.ts +381 -0
- package/servers/_read-cache.ts +44 -0
- package/servers/_router-handlers.ts +46 -0
- package/servers/_router.ts +55 -0
- package/servers/_run-with-timeout.ts +173 -0
- package/servers/_savings-render.ts +214 -0
- package/servers/_stats-sqlite.ts +640 -0
- package/servers/_stats.ts +1183 -0
- package/servers/_status-line-cells.ts +469 -0
- package/servers/_streaks.ts +162 -0
- package/servers/_summarize.ts +208 -0
- package/servers/_task-graph-render.ts +217 -0
- package/servers/_task-graph.ts +545 -0
- package/servers/_telemetry.ts +719 -0
- package/servers/_test-parsers.ts +530 -0
- package/servers/_test-watch.ts +626 -0
- package/servers/_text-helpers.ts +37 -0
- package/servers/_token-measure.ts +160 -0
- package/servers/_tool-base.ts +248 -0
- package/servers/_weekly-digest.ts +391 -0
- package/servers/ask-server-handlers.ts +49 -0
- package/servers/ask-server.ts +174 -0
- package/servers/bash-server-handlers.ts +138 -0
- package/servers/bash-server.ts +1136 -0
- package/servers/bench.ts +186 -0
- package/servers/diff-semantic-server-handlers.ts +39 -0
- package/servers/diff-semantic-server.ts +566 -0
- package/servers/diff-server-handlers.ts +317 -0
- package/servers/diff-server.ts +20 -0
- package/servers/edit-server-handlers.ts +31 -0
- package/servers/edit-server.ts +209 -0
- package/servers/edit-structural-server-handlers.ts +322 -0
- package/servers/efficiency-server-handlers.ts +161 -0
- package/servers/efficiency-server.ts +1135 -0
- package/servers/flush-server-handlers.ts +21 -0
- package/servers/flush-server.ts +19 -0
- package/servers/genome-server-handlers.ts +118 -0
- package/servers/genome-server.ts +397 -0
- package/servers/github-server-handlers.ts +187 -0
- package/servers/github-server.ts +856 -0
- package/servers/glob-server-handlers.ts +233 -0
- package/servers/glob-server.ts +20 -0
- package/servers/grep-server-handlers.ts +61 -0
- package/servers/grep-server.ts +564 -0
- package/servers/http-server-handlers.ts +36 -0
- package/servers/http-server.ts +149 -0
- package/servers/logs-server-handlers.ts +42 -0
- package/servers/logs-server.ts +359 -0
- package/servers/ls-server-handlers.ts +166 -0
- package/servers/ls-server.ts +20 -0
- package/servers/multi-edit-server-handlers.ts +51 -0
- package/servers/multi-edit-server.ts +422 -0
- package/servers/notebook-edit-server-handlers.ts +34 -0
- package/servers/notebook-edit-server.ts +181 -0
- package/servers/orient-server-handlers.ts +52 -0
- package/servers/orient-server.ts +436 -0
- package/servers/pipe-server-handlers.ts +68 -0
- package/servers/pipe-server.ts +300 -0
- package/servers/read-server-handlers.ts +42 -0
- package/servers/read-server.ts +206 -0
- package/servers/rename-file-server-handlers.ts +67 -0
- package/servers/rename-file-server.ts +699 -0
- package/servers/savings-server-handlers.ts +27 -0
- package/servers/savings-server.ts +54 -0
- package/servers/search-replace-regex-server-handlers.ts +96 -0
- package/servers/search-replace-regex-server.ts +751 -0
- package/servers/sql-server-handlers.ts +58 -0
- package/servers/sql-server.ts +505 -0
- package/servers/task-server-handlers.ts +358 -0
- package/servers/task-server.ts +24 -0
- package/servers/test-server-handlers.ts +351 -0
- package/servers/test-server.ts +20 -0
- package/servers/tree-server-handlers.ts +503 -0
- package/servers/tree-server.ts +20 -0
- package/servers/webfetch-server-handlers.ts +166 -0
- package/servers/webfetch-server.ts +20 -0
- package/servers/websearch-server-handlers.ts +215 -0
- package/servers/websearch-server.ts +19 -0
- package/servers/write-server-handlers.ts +27 -0
- package/servers/write-server.ts +97 -0
- package/skills/ashlr-brief/SKILL.md +80 -0
- package/skills/ashlr-codex-doctor/SKILL.md +21 -0
- package/skills/ashlr-codex-efficient-tools/SKILL.md +17 -0
- package/skills/ashlr-codex-genome-refresh/SKILL.md +20 -0
- package/skills/ashlr-codex-parallel-explore/SKILL.md +20 -0
- package/skills/ashlr-codex-savings/SKILL.md +17 -0
- package/skills/ashlr-cost-refactor/SKILL.md +50 -0
- package/skills/ashlr-efficient/SKILL.md +64 -0
- package/skills/ashlr-genome-author/SKILL.md +43 -0
- package/skills/ashlr-lean-tools/SKILL.md +43 -0
- package/skills/ashlr-search/SKILL.md +46 -0
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* _task-graph — Q1 2027 Distributed Orchestration: schema + validation + YAML I/O.
|
|
3
|
+
*
|
|
4
|
+
* This file is the CONTRACT between the three orchestration tracks:
|
|
5
|
+
* - Track A (this file + scripts/orchestrate-expand.ts): types + auto-expander.
|
|
6
|
+
* - Track B (runner + renderer): consumes TaskGraph to dispatch nodes and
|
|
7
|
+
* pretty-print the DAG for `--dry-run` confirmation.
|
|
8
|
+
* - Track C (/ashlr-orchestrate slash command): parses CLI args into ExpandOptions
|
|
9
|
+
* and hands the resulting TaskGraph to Track B.
|
|
10
|
+
*
|
|
11
|
+
* Anything exported below is part of the contract and MUST NOT change shape without
|
|
12
|
+
* a coordinated update to the sibling tracks. See plans/distributed-orchestration-design.md
|
|
13
|
+
* sections 3 (data model) and 6 (sequencing).
|
|
14
|
+
*
|
|
15
|
+
* No external dependencies — `js-yaml` is intentionally NOT pulled in. The
|
|
16
|
+
* `toYaml` / `fromYaml` helpers below are a minimal fit-for-purpose serializer
|
|
17
|
+
* covering only the shapes exported here.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Types — the contract
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
export type AgentKind = "refactorer" | "test-writer" | "doc-writer" | "reviewer" | "generic";
|
|
25
|
+
|
|
26
|
+
const VALID_AGENT_KINDS: ReadonlySet<AgentKind> = new Set<AgentKind>([
|
|
27
|
+
"refactorer",
|
|
28
|
+
"test-writer",
|
|
29
|
+
"doc-writer",
|
|
30
|
+
"reviewer",
|
|
31
|
+
"generic",
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
export interface TaskNode {
|
|
35
|
+
id: string; // stable, e.g. "node-auth-refactor"
|
|
36
|
+
agentKind: AgentKind;
|
|
37
|
+
goal: string; // 1-2 sentence task for this node
|
|
38
|
+
scope: string[]; // file/dir paths this node operates on
|
|
39
|
+
deps: string[]; // node ids that must complete before this one starts
|
|
40
|
+
estimatedTokens: number; // rough budget for sequencing + soft-throttle
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface TaskHandoff {
|
|
44
|
+
fromNode: string;
|
|
45
|
+
toNode: string;
|
|
46
|
+
contextSummary: string; // <=200 words — what the receiver needs to know
|
|
47
|
+
payloadJson?: unknown; // optional structured handoff
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface TaskGraph {
|
|
51
|
+
id: string; // uuid v4
|
|
52
|
+
goal: string; // the user's top-level intent
|
|
53
|
+
scope: string; // root path the orchestration operates within
|
|
54
|
+
tier: "pro" | "team"; // free is gated before we ever get here
|
|
55
|
+
createdAt: string; // ISO
|
|
56
|
+
nodes: TaskNode[];
|
|
57
|
+
handoffs: TaskHandoff[];
|
|
58
|
+
metadata: {
|
|
59
|
+
autoExpanded: boolean;
|
|
60
|
+
sourceYaml?: string; // present when generated from --yaml input
|
|
61
|
+
totalTokenBudget: number;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Validation
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Validate the shape of a TaskGraph candidate.
|
|
71
|
+
*
|
|
72
|
+
* Checks performed:
|
|
73
|
+
* - every required field exists with the right primitive type;
|
|
74
|
+
* - every TaskNode.agentKind is in VALID_AGENT_KINDS;
|
|
75
|
+
* - every dep references a node id that exists in `nodes`;
|
|
76
|
+
* - the dep graph is acyclic (DFS with grey/black coloring). On cycle, the
|
|
77
|
+
* returned error is `"cycle: A -> B -> A"` (arrows ASCII so YAML round-trips
|
|
78
|
+
* are not muddied);
|
|
79
|
+
* - handoff endpoints reference real node ids;
|
|
80
|
+
* - metadata.totalTokenBudget is a finite non-negative number.
|
|
81
|
+
*
|
|
82
|
+
* Returns a discriminated union so callers can pattern-match: on `ok: true` the
|
|
83
|
+
* `graph` field is narrowed to `TaskGraph`; on `ok: false` `errors` lists every
|
|
84
|
+
* problem found (validation does not short-circuit on the first error).
|
|
85
|
+
*/
|
|
86
|
+
export function validateTaskGraph(g: unknown): { ok: true; graph: TaskGraph } | { ok: false; errors: string[] } {
|
|
87
|
+
const errors: string[] = [];
|
|
88
|
+
|
|
89
|
+
if (g === null || typeof g !== "object" || Array.isArray(g)) {
|
|
90
|
+
return { ok: false, errors: ["root: expected object"] };
|
|
91
|
+
}
|
|
92
|
+
const obj = g as Record<string, unknown>;
|
|
93
|
+
|
|
94
|
+
// -- Top-level scalar checks ------------------------------------------------
|
|
95
|
+
if (typeof obj.id !== "string" || obj.id.length === 0) errors.push("id: missing or not a non-empty string");
|
|
96
|
+
if (typeof obj.goal !== "string" || obj.goal.length === 0) errors.push("goal: missing or not a non-empty string");
|
|
97
|
+
if (typeof obj.scope !== "string") errors.push("scope: missing or not a string");
|
|
98
|
+
if (obj.tier !== "pro" && obj.tier !== "team") errors.push("tier: must be 'pro' or 'team'");
|
|
99
|
+
if (typeof obj.createdAt !== "string") errors.push("createdAt: missing or not a string");
|
|
100
|
+
|
|
101
|
+
// -- nodes ------------------------------------------------------------------
|
|
102
|
+
const nodes: TaskNode[] = [];
|
|
103
|
+
const nodeIds = new Set<string>();
|
|
104
|
+
if (!Array.isArray(obj.nodes)) {
|
|
105
|
+
errors.push("nodes: missing or not an array");
|
|
106
|
+
} else {
|
|
107
|
+
obj.nodes.forEach((rawNode, i) => {
|
|
108
|
+
if (rawNode === null || typeof rawNode !== "object" || Array.isArray(rawNode)) {
|
|
109
|
+
errors.push(`nodes[${i}]: expected object`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const n = rawNode as Record<string, unknown>;
|
|
113
|
+
const tag = `nodes[${i}]`;
|
|
114
|
+
const id = typeof n.id === "string" ? n.id : null;
|
|
115
|
+
const goal = typeof n.goal === "string" ? n.goal : null;
|
|
116
|
+
const agentKind = typeof n.agentKind === "string" ? (n.agentKind as AgentKind) : null;
|
|
117
|
+
const estimatedTokens = typeof n.estimatedTokens === "number" ? n.estimatedTokens : null;
|
|
118
|
+
|
|
119
|
+
if (!id) errors.push(`${tag}.id: missing or not a string`);
|
|
120
|
+
if (!goal) errors.push(`${tag}.goal: missing or not a string`);
|
|
121
|
+
if (!agentKind) {
|
|
122
|
+
errors.push(`${tag}.agentKind: missing or not a string`);
|
|
123
|
+
} else if (!VALID_AGENT_KINDS.has(agentKind)) {
|
|
124
|
+
errors.push(`${tag}.agentKind: unknown '${agentKind}'`);
|
|
125
|
+
}
|
|
126
|
+
if (estimatedTokens === null || !Number.isFinite(estimatedTokens) || estimatedTokens < 0) {
|
|
127
|
+
errors.push(`${tag}.estimatedTokens: must be a finite non-negative number`);
|
|
128
|
+
}
|
|
129
|
+
if (!Array.isArray(n.scope) || !n.scope.every((s) => typeof s === "string")) {
|
|
130
|
+
errors.push(`${tag}.scope: must be an array of strings`);
|
|
131
|
+
}
|
|
132
|
+
if (!Array.isArray(n.deps) || !n.deps.every((s) => typeof s === "string")) {
|
|
133
|
+
errors.push(`${tag}.deps: must be an array of strings`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (id && agentKind && VALID_AGENT_KINDS.has(agentKind) && Array.isArray(n.scope) && Array.isArray(n.deps)) {
|
|
137
|
+
const node: TaskNode = {
|
|
138
|
+
id,
|
|
139
|
+
agentKind,
|
|
140
|
+
goal: goal ?? "",
|
|
141
|
+
scope: n.scope as string[],
|
|
142
|
+
deps: n.deps as string[],
|
|
143
|
+
estimatedTokens: estimatedTokens ?? 0,
|
|
144
|
+
};
|
|
145
|
+
nodes.push(node);
|
|
146
|
+
if (nodeIds.has(id)) errors.push(`${tag}.id: duplicate '${id}'`);
|
|
147
|
+
nodeIds.add(id);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// -- handoffs ---------------------------------------------------------------
|
|
153
|
+
const handoffs: TaskHandoff[] = [];
|
|
154
|
+
if (obj.handoffs !== undefined) {
|
|
155
|
+
if (!Array.isArray(obj.handoffs)) {
|
|
156
|
+
errors.push("handoffs: must be an array");
|
|
157
|
+
} else {
|
|
158
|
+
obj.handoffs.forEach((rawH, i) => {
|
|
159
|
+
if (rawH === null || typeof rawH !== "object" || Array.isArray(rawH)) {
|
|
160
|
+
errors.push(`handoffs[${i}]: expected object`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const h = rawH as Record<string, unknown>;
|
|
164
|
+
const tag = `handoffs[${i}]`;
|
|
165
|
+
if (typeof h.fromNode !== "string") errors.push(`${tag}.fromNode: missing or not a string`);
|
|
166
|
+
if (typeof h.toNode !== "string") errors.push(`${tag}.toNode: missing or not a string`);
|
|
167
|
+
if (typeof h.contextSummary !== "string") errors.push(`${tag}.contextSummary: missing or not a string`);
|
|
168
|
+
if (typeof h.fromNode === "string" && typeof h.toNode === "string" && typeof h.contextSummary === "string") {
|
|
169
|
+
if (!nodeIds.has(h.fromNode)) errors.push(`${tag}.fromNode: references unknown node '${h.fromNode}'`);
|
|
170
|
+
if (!nodeIds.has(h.toNode)) errors.push(`${tag}.toNode: references unknown node '${h.toNode}'`);
|
|
171
|
+
handoffs.push({
|
|
172
|
+
fromNode: h.fromNode,
|
|
173
|
+
toNode: h.toNode,
|
|
174
|
+
contextSummary: h.contextSummary,
|
|
175
|
+
payloadJson: h.payloadJson,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// -- metadata ---------------------------------------------------------------
|
|
183
|
+
let metadata: TaskGraph["metadata"] | null = null;
|
|
184
|
+
if (obj.metadata === undefined || obj.metadata === null || typeof obj.metadata !== "object" || Array.isArray(obj.metadata)) {
|
|
185
|
+
errors.push("metadata: missing or not an object");
|
|
186
|
+
} else {
|
|
187
|
+
const m = obj.metadata as Record<string, unknown>;
|
|
188
|
+
if (typeof m.autoExpanded !== "boolean") errors.push("metadata.autoExpanded: must be a boolean");
|
|
189
|
+
if (typeof m.totalTokenBudget !== "number" || !Number.isFinite(m.totalTokenBudget) || m.totalTokenBudget < 0) {
|
|
190
|
+
errors.push("metadata.totalTokenBudget: must be a finite non-negative number");
|
|
191
|
+
}
|
|
192
|
+
if (m.sourceYaml !== undefined && typeof m.sourceYaml !== "string") {
|
|
193
|
+
errors.push("metadata.sourceYaml: must be a string when present");
|
|
194
|
+
}
|
|
195
|
+
if (typeof m.autoExpanded === "boolean" && typeof m.totalTokenBudget === "number") {
|
|
196
|
+
metadata = {
|
|
197
|
+
autoExpanded: m.autoExpanded,
|
|
198
|
+
totalTokenBudget: m.totalTokenBudget,
|
|
199
|
+
sourceYaml: typeof m.sourceYaml === "string" ? m.sourceYaml : undefined,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// -- dep references + cycle detection --------------------------------------
|
|
205
|
+
// (only run when we have a clean nodes set; otherwise dep errors compound)
|
|
206
|
+
for (const node of nodes) {
|
|
207
|
+
for (const dep of node.deps) {
|
|
208
|
+
if (!nodeIds.has(dep)) errors.push(`nodes[${node.id}].deps: references unknown node '${dep}'`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (errors.length === 0) {
|
|
213
|
+
const cycle = findCycle(nodes);
|
|
214
|
+
if (cycle) errors.push(`cycle: ${cycle.join(" -> ")}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (errors.length > 0) return { ok: false, errors };
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
ok: true,
|
|
221
|
+
graph: {
|
|
222
|
+
id: obj.id as string,
|
|
223
|
+
goal: obj.goal as string,
|
|
224
|
+
scope: obj.scope as string,
|
|
225
|
+
tier: obj.tier as "pro" | "team",
|
|
226
|
+
createdAt: obj.createdAt as string,
|
|
227
|
+
nodes,
|
|
228
|
+
handoffs,
|
|
229
|
+
metadata: metadata as TaskGraph["metadata"],
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Cycle detection via DFS with grey/black coloring.
|
|
236
|
+
*
|
|
237
|
+
* Returns the cycle as a list of node ids `[A, B, A]` so the caller can render
|
|
238
|
+
* "cycle: A -> B -> A". Returns null when no cycle exists.
|
|
239
|
+
*
|
|
240
|
+
* Complexity: O(V + E). We bail on the first cycle found — multi-cycle reporting
|
|
241
|
+
* is not worth the complexity for an ergonomics-focused message.
|
|
242
|
+
*/
|
|
243
|
+
function findCycle(nodes: TaskNode[]): string[] | null {
|
|
244
|
+
const byId = new Map<string, TaskNode>();
|
|
245
|
+
for (const n of nodes) byId.set(n.id, n);
|
|
246
|
+
|
|
247
|
+
const WHITE = 0;
|
|
248
|
+
const GREY = 1;
|
|
249
|
+
const BLACK = 2;
|
|
250
|
+
const color = new Map<string, number>();
|
|
251
|
+
for (const n of nodes) color.set(n.id, WHITE);
|
|
252
|
+
|
|
253
|
+
const stack: string[] = [];
|
|
254
|
+
|
|
255
|
+
function dfs(id: string): string[] | null {
|
|
256
|
+
color.set(id, GREY);
|
|
257
|
+
stack.push(id);
|
|
258
|
+
const node = byId.get(id);
|
|
259
|
+
if (node) {
|
|
260
|
+
for (const dep of node.deps) {
|
|
261
|
+
const c = color.get(dep);
|
|
262
|
+
if (c === GREY) {
|
|
263
|
+
// Found a back-edge. Slice the cycle out of the stack.
|
|
264
|
+
const start = stack.indexOf(dep);
|
|
265
|
+
return [...stack.slice(start), dep];
|
|
266
|
+
}
|
|
267
|
+
if (c === WHITE) {
|
|
268
|
+
const found = dfs(dep);
|
|
269
|
+
if (found) return found;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
color.set(id, BLACK);
|
|
274
|
+
stack.pop();
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
for (const n of nodes) {
|
|
279
|
+
if (color.get(n.id) === WHITE) {
|
|
280
|
+
const found = dfs(n.id);
|
|
281
|
+
if (found) return found;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
// YAML serialization (minimal, fit-for-purpose)
|
|
289
|
+
// ---------------------------------------------------------------------------
|
|
290
|
+
//
|
|
291
|
+
// We support only the exact TaskGraph shape above:
|
|
292
|
+
// - scalars: strings, numbers, booleans, null;
|
|
293
|
+
// - sequences (arrays) of scalars or objects;
|
|
294
|
+
// - mappings (objects) keyed by identifier-safe strings.
|
|
295
|
+
//
|
|
296
|
+
// Strings are ALWAYS double-quoted with JSON-safe escaping, which is a valid
|
|
297
|
+
// (if pedestrian) YAML 1.2 syntax. This sidesteps the YAML quoting subtleties
|
|
298
|
+
// that bite hand-rolled emitters (colon-in-string, leading-dash, etc.).
|
|
299
|
+
//
|
|
300
|
+
// Multiline strings, anchors, tags, flow style, and other YAML niceties are
|
|
301
|
+
// deliberately out of scope. If a future contract field needs them, add a
|
|
302
|
+
// dedicated test and extend here — or pull in js-yaml.
|
|
303
|
+
|
|
304
|
+
/** Emit a TaskGraph as YAML text. */
|
|
305
|
+
export function toYaml(g: TaskGraph): string {
|
|
306
|
+
return emitMapping(g as unknown as Record<string, unknown>, 0) + "\n";
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function emitMapping(obj: Record<string, unknown>, indent: number): string {
|
|
310
|
+
const pad = " ".repeat(indent);
|
|
311
|
+
const keys = Object.keys(obj);
|
|
312
|
+
if (keys.length === 0) return `${pad}{}`;
|
|
313
|
+
const lines: string[] = [];
|
|
314
|
+
for (const k of keys) {
|
|
315
|
+
const v = obj[k];
|
|
316
|
+
if (v === undefined) continue;
|
|
317
|
+
lines.push(`${pad}${k}:${emitValueInline(v, indent)}`);
|
|
318
|
+
}
|
|
319
|
+
return lines.join("\n");
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function emitValueInline(v: unknown, indent: number): string {
|
|
323
|
+
if (v === null) return " null";
|
|
324
|
+
if (typeof v === "string") return ` ${encodeScalarString(v)}`;
|
|
325
|
+
if (typeof v === "number" || typeof v === "boolean") return ` ${String(v)}`;
|
|
326
|
+
if (Array.isArray(v)) {
|
|
327
|
+
if (v.length === 0) return " []";
|
|
328
|
+
const pad = " ".repeat(indent + 1);
|
|
329
|
+
const items = v.map((item) => {
|
|
330
|
+
if (item !== null && typeof item === "object" && !Array.isArray(item)) {
|
|
331
|
+
const inner = emitMapping(item as Record<string, unknown>, indent + 2);
|
|
332
|
+
// First key sits on the dash line; rest live one level deeper.
|
|
333
|
+
const innerLines = inner.split("\n");
|
|
334
|
+
if (innerLines.length === 0) return `${pad}- {}`;
|
|
335
|
+
const first = innerLines[0]?.trimStart() ?? "";
|
|
336
|
+
const rest = innerLines.slice(1).join("\n");
|
|
337
|
+
return rest ? `${pad}- ${first}\n${rest}` : `${pad}- ${first}`;
|
|
338
|
+
}
|
|
339
|
+
return `${pad}-${emitValueInline(item, indent + 1)}`;
|
|
340
|
+
});
|
|
341
|
+
return "\n" + items.join("\n");
|
|
342
|
+
}
|
|
343
|
+
if (typeof v === "object") {
|
|
344
|
+
const inner = emitMapping(v as Record<string, unknown>, indent + 1);
|
|
345
|
+
return "\n" + inner;
|
|
346
|
+
}
|
|
347
|
+
// Fallback: treat unknown types as JSON.
|
|
348
|
+
return ` ${JSON.stringify(v)}`;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function encodeScalarString(s: string): string {
|
|
352
|
+
// JSON.stringify gives us a perfectly valid YAML double-quoted scalar.
|
|
353
|
+
return JSON.stringify(s);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** Parse YAML text back into a TaskGraph. Re-runs `validateTaskGraph`. */
|
|
357
|
+
export function fromYaml(s: string): { ok: true; graph: TaskGraph } | { ok: false; errors: string[] } {
|
|
358
|
+
let parsed: unknown;
|
|
359
|
+
try {
|
|
360
|
+
parsed = parseYaml(s);
|
|
361
|
+
} catch (e) {
|
|
362
|
+
return { ok: false, errors: [`yaml-parse: ${(e as Error).message}`] };
|
|
363
|
+
}
|
|
364
|
+
return validateTaskGraph(parsed);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// ---------------------------------------------------------------------------
|
|
368
|
+
// Minimal YAML parser (indent-driven)
|
|
369
|
+
// ---------------------------------------------------------------------------
|
|
370
|
+
//
|
|
371
|
+
// Supports exactly what `toYaml` emits:
|
|
372
|
+
// - 2-space indentation;
|
|
373
|
+
// - `key: value` lines (value may be a JSON-quoted scalar, number, bool,
|
|
374
|
+
// null, `[]`, or `{}`, or absent if the value is a nested block);
|
|
375
|
+
// - `- value` sequence items (value may be a scalar OR an inline `key: value`
|
|
376
|
+
// starting a new mapping continued on indented lines below).
|
|
377
|
+
//
|
|
378
|
+
// Round-trips with toYaml. NOT a general-purpose YAML 1.2 parser.
|
|
379
|
+
|
|
380
|
+
interface YamlLine {
|
|
381
|
+
indent: number;
|
|
382
|
+
text: string;
|
|
383
|
+
raw: string;
|
|
384
|
+
lineNo: number;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function parseYaml(input: string): unknown {
|
|
388
|
+
const rawLines = input.split(/\r?\n/);
|
|
389
|
+
const lines: YamlLine[] = [];
|
|
390
|
+
rawLines.forEach((raw, idx) => {
|
|
391
|
+
// Strip comments (only when `#` is preceded by whitespace or at SOL — same
|
|
392
|
+
// rule as YAML proper). Quoted strings cannot contain raw `#` since we
|
|
393
|
+
// double-quote everything.
|
|
394
|
+
const stripped = stripComment(raw);
|
|
395
|
+
if (stripped.trim().length === 0) return;
|
|
396
|
+
const indent = countLeadingSpaces(stripped);
|
|
397
|
+
lines.push({ indent, text: stripped.trim(), raw, lineNo: idx + 1 });
|
|
398
|
+
});
|
|
399
|
+
if (lines.length === 0) return {};
|
|
400
|
+
const { value } = parseBlock(lines, 0, lines[0]!.indent);
|
|
401
|
+
return value;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function stripComment(line: string): string {
|
|
405
|
+
let inString = false;
|
|
406
|
+
for (let i = 0; i < line.length; i++) {
|
|
407
|
+
const ch = line[i];
|
|
408
|
+
if (ch === '"' && line[i - 1] !== "\\") inString = !inString;
|
|
409
|
+
if (ch === "#" && !inString && (i === 0 || /\s/.test(line[i - 1] ?? ""))) {
|
|
410
|
+
return line.slice(0, i);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return line;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function countLeadingSpaces(s: string): number {
|
|
417
|
+
let i = 0;
|
|
418
|
+
while (i < s.length && s[i] === " ") i++;
|
|
419
|
+
return i;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Parse a block of lines starting at `start` whose indent equals `indent`.
|
|
424
|
+
* Returns the parsed value and the index of the first line that is NOT part
|
|
425
|
+
* of this block (i.e. has a smaller indent than `indent`).
|
|
426
|
+
*/
|
|
427
|
+
function parseBlock(lines: YamlLine[], start: number, indent: number): { value: unknown; next: number } {
|
|
428
|
+
if (start >= lines.length) return { value: null, next: start };
|
|
429
|
+
const first = lines[start]!;
|
|
430
|
+
if (first.text.startsWith("- ")) return parseSequence(lines, start, indent);
|
|
431
|
+
return parseMapping(lines, start, indent);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function parseMapping(lines: YamlLine[], start: number, indent: number): { value: Record<string, unknown>; next: number } {
|
|
435
|
+
const obj: Record<string, unknown> = {};
|
|
436
|
+
let i = start;
|
|
437
|
+
while (i < lines.length) {
|
|
438
|
+
const line = lines[i]!;
|
|
439
|
+
if (line.indent < indent) break;
|
|
440
|
+
if (line.indent > indent) throw new Error(`unexpected indent at line ${line.lineNo}`);
|
|
441
|
+
const sepIdx = findKeySeparator(line.text);
|
|
442
|
+
if (sepIdx < 0) throw new Error(`mapping line missing ':' at line ${line.lineNo}: ${line.raw}`);
|
|
443
|
+
const key = line.text.slice(0, sepIdx).trim();
|
|
444
|
+
const rest = line.text.slice(sepIdx + 1).trim();
|
|
445
|
+
if (rest.length > 0) {
|
|
446
|
+
// Inline scalar / flow-style.
|
|
447
|
+
obj[key] = parseScalar(rest);
|
|
448
|
+
i++;
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
// Block value on subsequent indented lines.
|
|
452
|
+
if (i + 1 >= lines.length || lines[i + 1]!.indent <= indent) {
|
|
453
|
+
obj[key] = null;
|
|
454
|
+
i++;
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
const childIndent = lines[i + 1]!.indent;
|
|
458
|
+
const { value: child, next } = parseBlock(lines, i + 1, childIndent);
|
|
459
|
+
obj[key] = child;
|
|
460
|
+
i = next;
|
|
461
|
+
}
|
|
462
|
+
return { value: obj, next: i };
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function findKeySeparator(text: string): number {
|
|
466
|
+
// Skip past any JSON-quoted key region (we never emit quoted keys, but be safe).
|
|
467
|
+
let inString = false;
|
|
468
|
+
for (let i = 0; i < text.length; i++) {
|
|
469
|
+
const ch = text[i];
|
|
470
|
+
if (ch === '"' && text[i - 1] !== "\\") inString = !inString;
|
|
471
|
+
if (ch === ":" && !inString) return i;
|
|
472
|
+
}
|
|
473
|
+
return -1;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function parseSequence(lines: YamlLine[], start: number, indent: number): { value: unknown[]; next: number } {
|
|
477
|
+
const arr: unknown[] = [];
|
|
478
|
+
let i = start;
|
|
479
|
+
while (i < lines.length) {
|
|
480
|
+
const line = lines[i]!;
|
|
481
|
+
if (line.indent < indent) break;
|
|
482
|
+
if (line.indent > indent) throw new Error(`unexpected indent in sequence at line ${line.lineNo}`);
|
|
483
|
+
if (!line.text.startsWith("- ") && line.text !== "-") {
|
|
484
|
+
throw new Error(`expected sequence item at line ${line.lineNo}: ${line.raw}`);
|
|
485
|
+
}
|
|
486
|
+
const after = line.text === "-" ? "" : line.text.slice(2).trim();
|
|
487
|
+
if (after.length === 0) {
|
|
488
|
+
// The block lives entirely on the following lines.
|
|
489
|
+
if (i + 1 >= lines.length || lines[i + 1]!.indent <= indent) {
|
|
490
|
+
arr.push(null);
|
|
491
|
+
i++;
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
const childIndent = lines[i + 1]!.indent;
|
|
495
|
+
const { value: child, next } = parseBlock(lines, i + 1, childIndent);
|
|
496
|
+
arr.push(child);
|
|
497
|
+
i = next;
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
// Check whether the dash-line itself opens a mapping (e.g. `- id: foo`).
|
|
501
|
+
const sepIdx = findKeySeparator(after);
|
|
502
|
+
if (sepIdx >= 0) {
|
|
503
|
+
// Reconstruct a virtual mapping block: this line is the first key, and
|
|
504
|
+
// continuation lines (indented two more) belong to the same mapping.
|
|
505
|
+
const virtualIndent = indent + 2;
|
|
506
|
+
const virtualLines: YamlLine[] = [];
|
|
507
|
+
virtualLines.push({ indent: virtualIndent, text: after, raw: line.raw, lineNo: line.lineNo });
|
|
508
|
+
let j = i + 1;
|
|
509
|
+
while (j < lines.length && lines[j]!.indent >= virtualIndent && !(lines[j]!.indent === indent && lines[j]!.text.startsWith("- "))) {
|
|
510
|
+
virtualLines.push(lines[j]!);
|
|
511
|
+
j++;
|
|
512
|
+
}
|
|
513
|
+
const { value: child } = parseMapping(virtualLines, 0, virtualIndent);
|
|
514
|
+
arr.push(child);
|
|
515
|
+
i = j;
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
// Inline scalar sequence item.
|
|
519
|
+
arr.push(parseScalar(after));
|
|
520
|
+
i++;
|
|
521
|
+
}
|
|
522
|
+
return { value: arr, next: i };
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function parseScalar(text: string): unknown {
|
|
526
|
+
const t = text.trim();
|
|
527
|
+
if (t === "null" || t === "~") return null;
|
|
528
|
+
if (t === "true") return true;
|
|
529
|
+
if (t === "false") return false;
|
|
530
|
+
if (t === "[]") return [];
|
|
531
|
+
if (t === "{}") return {};
|
|
532
|
+
if (t.startsWith('"')) {
|
|
533
|
+
// JSON-quoted string.
|
|
534
|
+
try {
|
|
535
|
+
return JSON.parse(t);
|
|
536
|
+
} catch {
|
|
537
|
+
throw new Error(`invalid quoted string: ${t}`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
if (/^-?\d+(?:\.\d+)?(?:[eE][-+]?\d+)?$/.test(t)) {
|
|
541
|
+
return Number(t);
|
|
542
|
+
}
|
|
543
|
+
// Plain unquoted string fallback.
|
|
544
|
+
return t;
|
|
545
|
+
}
|