akm-cli 0.9.0-rc.9 → 0.9.0
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 +1063 -44
- package/README.md +51 -25
- package/SECURITY.md +14 -1
- package/STABILITY.md +497 -0
- package/dist/akm +148 -35
- package/dist/{akm-migrate-storage → akm-migrate} +6 -9
- package/dist/assets/hints/cli-hints-full.md +223 -95
- package/dist/assets/hints/cli-hints-short.md +85 -22
- package/dist/assets/improve-strategies/default.json +1 -1
- package/dist/assets/improve-strategies/reflect-distill.json +1 -1
- package/dist/assets/prompts/memory-infer-user.md +2 -3
- package/dist/assets/stash-skeleton/README.md +6 -5
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/organization.md +20 -9
- package/dist/assets/tasks/core/extract.yml +1 -1
- package/dist/assets/tasks/core/version-check.yml +1 -1
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +5 -0
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +8 -0
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +5 -0
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +5 -0
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +5 -0
- package/dist/assets/templates/html/health.html +1 -3
- package/dist/assets/workflows/workflow-template.md +32 -15
- package/dist/cli/invocation.js +40 -15
- package/dist/cli/parse-args.js +0 -22
- package/dist/cli/retired-commands.js +121 -0
- package/dist/cli/shared.js +154 -22
- package/dist/cli/unknown-flags.js +236 -0
- package/dist/cli-node.mjs +2 -1
- package/dist/cli.js +696 -258
- package/dist/commands/agent/agent-dispatch.js +14 -3
- package/dist/commands/agent/contribute-cli.js +73 -88
- package/dist/commands/completions.js +79 -22
- package/dist/commands/config-cli.js +17 -150
- package/dist/commands/env/env-cli.js +59 -143
- package/dist/commands/env/env.js +12 -163
- package/dist/commands/env/marker-path.js +6 -0
- package/dist/commands/env/secret-cli.js +36 -66
- package/dist/commands/env/secret.js +24 -57
- package/dist/commands/feedback-cli.js +141 -87
- package/dist/commands/health/accept-rate.js +58 -0
- package/dist/commands/health/advisories.js +3 -4
- package/dist/commands/health/checks.js +85 -23
- package/dist/commands/health/html-report.js +7 -10
- package/dist/commands/health/improve-metrics.js +25 -83
- package/dist/commands/health/md-report.js +5 -9
- package/dist/commands/health/metrics.js +62 -20
- package/dist/commands/health/renderers.js +47 -0
- package/dist/commands/health/report-view-model.js +4 -5
- package/dist/commands/health/stash-exposure.js +1 -1
- package/dist/commands/health/surfaces.js +3 -48
- package/dist/commands/health/task-runs.js +3 -67
- package/dist/commands/health/types-improve.js +7 -0
- package/dist/commands/health.js +99 -28
- package/dist/commands/improve/anti-collapse.js +2 -2
- package/dist/commands/improve/autonomy-gate.js +68 -0
- package/dist/commands/improve/collapse-detector.js +41 -40
- package/dist/commands/improve/consolidate/eligibility.js +1 -23
- package/dist/commands/improve/consolidate/merge.js +4 -0
- package/dist/commands/improve/consolidate.js +140 -1000
- package/dist/commands/improve/distill/promote-memory.js +12 -12
- package/dist/commands/improve/distill/quality-gate.js +6 -6
- package/dist/commands/improve/distill.js +58 -69
- package/dist/commands/improve/eligibility.js +95 -55
- package/dist/commands/improve/extract-cli.js +14 -133
- package/dist/commands/improve/improve-cli.js +98 -114
- package/dist/commands/improve/improve-result-file.js +1 -28
- package/dist/commands/improve/improve-strategies.js +8 -5
- package/dist/commands/improve/improve.js +128 -91
- package/dist/commands/improve/loop-stages.js +182 -20
- package/dist/commands/improve/memory/derived-ref.js +45 -43
- package/dist/commands/improve/memory/memory-belief.js +1 -1
- package/dist/commands/improve/memory/memory-contradiction-detect.js +4 -12
- package/dist/commands/improve/memory/memory-improve.js +6 -5
- package/dist/commands/improve/outcome-loop.js +22 -65
- package/dist/commands/improve/preparation.js +114 -123
- package/dist/commands/improve/proactive-maintenance.js +2 -5
- package/dist/commands/improve/reflect.js +56 -160
- package/dist/commands/improve/salience.js +11 -122
- package/dist/commands/improve/source-identity.js +10 -38
- package/dist/commands/lint/base-linter.js +20 -124
- package/dist/commands/lint/env-key-rules.js +31 -47
- package/dist/commands/lint/index.js +249 -43
- package/dist/commands/{events.js → log.js} +33 -38
- package/dist/commands/migrate-cli.js +92 -12
- package/dist/commands/migration-tool.js +46 -0
- package/dist/commands/observability-cli.js +70 -209
- package/dist/commands/proposal/drain.js +101 -29
- package/dist/commands/proposal/proposal-cli.js +76 -48
- package/dist/commands/proposal/proposal.js +54 -18
- package/dist/commands/proposal/propose-cli.js +88 -0
- package/dist/commands/proposal/propose.js +23 -15
- package/dist/commands/proposal/repository.js +701 -278
- package/dist/commands/proposal/validators/proposal-quality-validators.js +2 -8
- package/dist/commands/proposal/validators/proposal-validators.js +55 -7
- package/dist/commands/proposal/validators/proposals.js +4 -7
- package/dist/commands/read/curate.js +34 -53
- package/dist/commands/read/knowledge.js +150 -95
- package/dist/commands/read/registry-search.js +2 -2
- package/dist/commands/read/remember-cli.js +42 -15
- package/dist/commands/read/search-cli.js +180 -78
- package/dist/commands/read/search.js +58 -43
- package/dist/commands/read/show.js +197 -141
- package/dist/commands/registry-cli.js +12 -51
- package/dist/commands/remember.js +14 -57
- package/dist/commands/sources/add-cli.js +100 -31
- package/dist/commands/sources/bundle-cli.js +166 -0
- package/dist/commands/sources/bundle-config-ops.js +7 -2
- package/dist/commands/sources/info.js +18 -5
- package/dist/commands/sources/init.js +12 -12
- package/dist/commands/sources/installed-stashes.js +382 -98
- package/dist/commands/sources/schema-repair.js +3 -2
- package/dist/commands/sources/self-update.js +131 -38
- package/dist/commands/sources/source-add.js +72 -17
- package/dist/commands/sources/source-clone.js +129 -45
- package/dist/commands/sources/source-manage.js +43 -23
- package/dist/commands/sources/sources-cli.js +57 -208
- package/dist/commands/sources/stash-cli.js +46 -53
- package/dist/commands/tasks/tasks-cli.js +91 -97
- package/dist/commands/tasks/tasks.js +276 -421
- package/dist/commands/workflow-cli.js +175 -450
- package/dist/core/adapter/adapters/akm-adapter.js +47 -28
- package/dist/core/adapter/adapters/akm-lint.js +42 -27
- package/dist/core/adapter/adapters/akm-metadata.js +15 -44
- package/dist/core/adapter/adapters/akm-task-adapter.js +15 -13
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +55 -71
- package/dist/core/adapter/adapters/dotenv-adapter.js +1 -1
- package/dist/core/adapter/adapters/generic-files-adapter.js +2 -0
- package/dist/core/adapter/adapters/index.js +6 -6
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +14 -8
- package/dist/core/adapter/adapters/okf-adapter.js +187 -19
- package/dist/core/adapter/adapters/shared.js +3 -19
- package/dist/core/adapter/adapters/tool-dir-shared.js +8 -3
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +1 -0
- package/dist/core/adapter/detect-adapter.js +17 -0
- package/dist/core/adapter/recognize-match.js +6 -4
- package/dist/core/adapter/validate-context.js +214 -0
- package/dist/core/asset/akm-markdown.js +63 -0
- package/dist/core/asset/asset-placement.js +20 -6
- package/dist/core/asset/asset-ref.js +11 -9
- package/dist/core/asset/frontmatter-lint.js +30 -0
- package/dist/core/asset/frontmatter.js +25 -9
- package/dist/core/asset/markdown.js +40 -51
- package/dist/core/asset/resolve-ref.js +89 -18
- package/dist/core/asset/stash-meta.js +1 -1
- package/dist/core/bundle-id.js +51 -0
- package/dist/core/common.js +152 -38
- package/dist/core/config/config-io.js +12 -1
- package/dist/core/config/config-schema.js +35 -8
- package/dist/core/config/config-sources.js +55 -11
- package/dist/core/config/config-walker.js +25 -9
- package/dist/core/config/config.js +9 -48
- package/dist/core/config/experimental.js +21 -0
- package/dist/core/config/schema/embedding.js +5 -1
- package/dist/core/config/schema/experimental.js +30 -0
- package/dist/core/config/schema/improve-processes.js +0 -6
- package/dist/core/config/schema/improve.js +21 -3
- package/dist/core/config/schema/index-config.js +8 -15
- package/dist/core/config/schema/output.js +4 -1
- package/dist/core/config/schema/setup.js +9 -18
- package/dist/core/config/schema/sources-bundles.js +49 -33
- package/dist/core/config/schema/workflow.js +3 -3
- package/dist/core/env-secret-ref.js +76 -46
- package/dist/core/errors.js +18 -12
- package/dist/core/events.js +46 -128
- package/dist/core/file-change.js +6 -5
- package/dist/core/fs-txn.js +83 -7
- package/dist/core/git-message.js +2 -2
- package/dist/core/improve-result.js +1 -100
- package/dist/core/lesson-lint.js +1 -17
- package/dist/core/logs-db.js +2 -1
- package/dist/core/migration-operation.js +16 -0
- package/dist/core/mutation-target.js +78 -0
- package/dist/core/parse.js +4 -1
- package/dist/core/paths.js +17 -20
- package/dist/core/recognition-util.js +12 -14
- package/dist/core/redaction.js +34 -0
- package/dist/core/standards/resolve-standards-context.js +2 -14
- package/dist/core/standards/resolve-stash-standards.js +2 -2
- package/dist/core/standards/resolve-type-conventions.js +2 -2
- package/dist/core/state/migrations.js +41 -18
- package/dist/core/state-db.js +5 -14
- package/dist/core/structured.js +1 -1
- package/dist/core/subprocess.js +6 -4
- package/dist/core/text-truncation.js +9 -5
- package/dist/core/type-presentation.js +3 -3
- package/dist/core/warn.js +0 -3
- package/dist/core/write-source.js +771 -95
- package/dist/indexer/bundle-identity-guard.js +3 -2
- package/dist/indexer/db/graph-db.js +0 -24
- package/dist/indexer/ensure-index.js +1 -0
- package/dist/indexer/graph/graph-boost.js +9 -34
- package/dist/indexer/graph/graph-extraction.js +8 -5
- package/dist/indexer/index-writer-lock.js +53 -17
- package/dist/indexer/index-written-assets.js +16 -22
- package/dist/indexer/indexer.js +497 -239
- package/dist/indexer/installations.js +14 -96
- package/dist/indexer/passes/dir-staleness.js +16 -9
- package/dist/indexer/passes/memory-inference.js +11 -9
- package/dist/indexer/passes/metadata.js +113 -47
- package/dist/indexer/scan/doc-to-entry.js +38 -1
- package/dist/indexer/scan/drain-dir.js +13 -23
- package/dist/indexer/search/db-search.js +99 -54
- package/dist/indexer/search/fts-query.js +47 -24
- package/dist/indexer/search/ranking-contributors.js +42 -20
- package/dist/indexer/search/ranking.js +18 -99
- package/dist/indexer/search/search-fields.js +7 -2
- package/dist/indexer/search/search-source.js +82 -93
- package/dist/indexer/usage/usage-events.js +0 -89
- package/dist/indexer/walk/file-context.js +2 -1
- package/dist/indexer/walk/matchers.js +30 -43
- package/dist/indexer/walk/path-resolver.js +7 -2
- package/dist/indexer/walk/walker.js +38 -12
- package/dist/integrations/agent/builders.js +0 -6
- package/dist/integrations/agent/config.js +2 -2
- package/dist/integrations/agent/detect.js +49 -19
- package/dist/integrations/agent/engine-fallback.js +76 -0
- package/dist/integrations/agent/profiles.js +14 -0
- package/dist/integrations/agent/prompts.js +12 -8
- package/dist/integrations/agent/runner-dispatch.js +4 -2
- package/dist/integrations/agent/runner.js +0 -1
- package/dist/integrations/agent/spawn.js +5 -6
- package/dist/integrations/github.js +1 -1
- package/dist/integrations/harnesses/aider/agent-builder.js +6 -4
- package/dist/integrations/harnesses/amazonq/agent-builder.js +7 -4
- package/dist/integrations/harnesses/claude/session-log.js +0 -10
- package/dist/integrations/harnesses/codex/agent-builder.js +5 -2
- package/dist/integrations/harnesses/copilot/agent-builder.js +5 -3
- package/dist/integrations/harnesses/gemini/agent-builder.js +5 -3
- package/dist/integrations/harnesses/index.js +3 -7
- package/dist/integrations/harnesses/opencode/agent-builder.js +21 -2
- package/dist/integrations/harnesses/opencode/session-log.js +0 -15
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +13 -4
- package/dist/integrations/harnesses/openhands/agent-builder.js +9 -6
- package/dist/integrations/harnesses/pi/agent-builder.js +6 -4
- package/dist/integrations/lockfile.js +88 -7
- package/dist/integrations/session-logs/index.js +3 -28
- package/dist/llm/client.js +136 -100
- package/dist/llm/embedders/remote.js +13 -5
- package/dist/llm/feature-gate.js +4 -12
- package/dist/llm/graph-extract.js +5 -11
- package/dist/llm/memory-infer.js +144 -1
- package/dist/llm/metadata-enhance.js +5 -7
- package/dist/llm/structured-call.js +1 -1
- package/dist/llm/usage-persist.js +26 -5
- package/dist/llm/usage-telemetry.js +25 -2
- package/dist/output/cli-hints.js +1 -2
- package/dist/output/context.js +22 -7
- package/dist/output/format-exempt.js +80 -0
- package/dist/output/generic-render.js +259 -0
- package/dist/output/render-registry.js +57 -0
- package/dist/output/renderers.js +14 -36
- package/dist/output/shapes/curate.js +10 -1
- package/dist/output/shapes/events.js +12 -7
- package/dist/output/shapes/helpers.js +56 -83
- package/dist/output/shapes/migrate.js +8 -0
- package/dist/output/shapes/passthrough.js +7 -41
- package/dist/output/shapes/proposal/producer.js +15 -7
- package/dist/output/shapes.js +2 -9
- package/dist/output/text/{init.js → bundle-create.js} +3 -1
- package/dist/output/text/bundle-show.js +7 -0
- package/dist/output/text/command-format.js +164 -96
- package/dist/output/text/env.js +1 -3
- package/dist/output/text/events.js +8 -7
- package/dist/output/text/health-format.js +103 -0
- package/dist/output/text/health.js +7 -0
- package/dist/output/text/helpers.js +10 -8
- package/dist/output/text/lint-format.js +43 -0
- package/dist/output/text/{save.js → lint.js} +2 -2
- package/dist/output/text/migrate.js +88 -0
- package/dist/output/text/proposal/producer.js +4 -2
- package/dist/output/text/proposal-format.js +44 -72
- package/dist/output/text/registry-commands.js +1 -2
- package/dist/output/text/show-directives.js +15 -7
- package/dist/output/text/status-list.js +32 -0
- package/dist/output/text/sync.js +5 -0
- package/dist/output/text/workflow-format.js +24 -203
- package/dist/output/text/workflow.js +1 -7
- package/dist/output/text.js +16 -17
- package/dist/registry/factory.js +4 -6
- package/dist/registry/origin-resolve.js +16 -27
- package/dist/registry/providers/skills-sh.js +3 -3
- package/dist/registry/providers/static-index.js +13 -23
- package/dist/registry/resolve.js +42 -7
- package/dist/registry/semver.js +34 -84
- package/dist/runtime.js +2 -23
- package/dist/scripts/akm-migrate-node.js +60290 -0
- package/dist/scripts/akm-migrate.js +59628 -0
- package/dist/setup/detect.js +42 -15
- package/dist/setup/registry-stash-loader.js +2 -2
- package/dist/setup/setup.js +236 -136
- package/dist/setup/steps/connection.js +7 -9
- package/dist/setup/steps/platforms.js +9 -9
- package/dist/setup/steps/semantic.js +15 -3
- package/dist/setup/steps/sources.js +12 -13
- package/dist/setup/steps/stashdir.js +2 -3
- package/dist/setup/steps/tasks.js +237 -120
- package/dist/sources/freshness.js +1 -1
- package/dist/sources/provider-factory.js +11 -17
- package/dist/sources/providers/filesystem.js +2 -3
- package/dist/sources/providers/git-install.js +278 -34
- package/dist/sources/providers/git-provider.js +25 -23
- package/dist/sources/providers/git-stash.js +395 -106
- package/dist/sources/providers/git.js +2 -2
- package/dist/sources/providers/npm.js +16 -19
- package/dist/sources/providers/provider-utils.js +7 -4
- package/dist/sources/providers/sync-from-ref.js +3 -9
- package/dist/sources/providers/website.js +6 -1
- package/dist/sources/resolve.js +6 -5
- package/dist/sources/snapshot-fetchers/bluesky.js +146 -0
- package/dist/sources/snapshot-fetchers/content-extract.js +566 -0
- package/dist/sources/snapshot-fetchers/fetcher-util.js +41 -0
- package/dist/sources/snapshot-fetchers/github.js +100 -0
- package/dist/sources/snapshot-fetchers/host-guard.js +291 -0
- package/dist/sources/snapshot-fetchers/registry.js +17 -1
- package/dist/sources/snapshot-fetchers/robots.js +348 -0
- package/dist/sources/snapshot-fetchers/rss.js +282 -0
- package/dist/sources/snapshot-fetchers/secret-seam.js +42 -0
- package/dist/sources/snapshot-fetchers/website-ingest.js +566 -268
- package/dist/sources/snapshot-fetchers/x.js +910 -0
- package/dist/storage/database.js +7 -0
- package/dist/storage/engines/sqlite-migrations.js +23 -111
- package/dist/storage/managed-db.js +2 -2
- package/dist/storage/repositories/canaries-repository.js +1 -1
- package/dist/storage/repositories/events-repository.js +27 -11
- package/dist/storage/repositories/improve-runs-repository.js +6 -12
- package/dist/storage/repositories/index-connection.js +17 -6
- package/dist/storage/repositories/index-entries-repository.js +151 -240
- package/dist/storage/repositories/index-entry-mapper.js +15 -11
- package/dist/storage/repositories/index-fts-repository.js +5 -2
- package/dist/storage/repositories/index-llm-cache-repository.js +0 -1
- package/dist/storage/repositories/index-meta-repository.js +2 -3
- package/dist/storage/repositories/index-schema.js +10 -25
- package/dist/storage/repositories/index-utility-repository.js +15 -28
- package/dist/storage/repositories/index-vec-repository.js +6 -1
- package/dist/storage/repositories/outcome-repository.js +119 -0
- package/dist/storage/repositories/proposals-repository.js +296 -59
- package/dist/storage/repositories/registry-cache.js +19 -0
- package/dist/storage/repositories/salience-repository.js +172 -0
- package/dist/storage/repositories/task-history-repository.js +15 -13
- package/dist/storage/repositories/workflow-runs-repository.js +52 -40
- package/dist/tasks/backends/cron.js +105 -15
- package/dist/tasks/backends/index.js +1 -1
- package/dist/tasks/backends/launchd.js +85 -38
- package/dist/tasks/backends/schtasks.js +135 -15
- package/dist/tasks/embedded.js +56 -40
- package/dist/tasks/parser.js +7 -157
- package/dist/tasks/resolve-akm-bin.js +137 -59
- package/dist/tasks/runner.js +79 -42
- package/dist/tasks/scheduler-invocation.js +220 -10
- package/dist/tasks/schema.js +24 -1
- package/dist/tasks/task-id.js +1 -3
- package/dist/tasks/validator.js +20 -6
- package/dist/workflows/authoring/authoring.js +94 -143
- package/dist/workflows/authoring/scope-key.js +1 -1
- package/dist/workflows/exec/frozen-judge.js +28 -2
- package/dist/workflows/exec/native-executor.js +77 -57
- package/dist/workflows/exec/param-secrets.js +9 -9
- package/dist/workflows/exec/run-workflow.js +133 -79
- package/dist/workflows/exec/step-work.js +219 -346
- package/dist/{migrate-storage-node.mjs → workflows/exec/unit-dispatch.js} +1 -5
- package/dist/workflows/ir/compile.js +141 -270
- package/dist/workflows/ir/freeze.js +40 -30
- package/dist/workflows/ir/params.js +135 -11
- package/dist/workflows/ir/plan-hash.js +1 -1
- package/dist/workflows/ir/schema.js +25 -26
- package/dist/workflows/parser.js +872 -307
- package/dist/workflows/program/expressions.js +20 -208
- package/dist/workflows/program/schema.js +7 -10
- package/dist/workflows/renderer.js +95 -68
- package/dist/workflows/resource-limits.js +2 -0
- package/dist/workflows/runtime/checkin.js +3 -3
- package/dist/workflows/runtime/plan-classifier.js +16 -75
- package/dist/workflows/runtime/runs.js +186 -127
- package/dist/workflows/runtime/unit-checkin.js +1 -1
- package/dist/workflows/runtime/unit-phases.js +2 -2
- package/dist/workflows/runtime/workflow-asset-loader.js +232 -83
- package/dist/workflows/schema.js +1 -11
- package/dist/workflows/validate-summary.js +30 -36
- package/dist/workflows/validator.js +21 -62
- package/docs/README.md +68 -0
- package/docs/migration/README.md +8 -0
- package/docs/migration/release-notes/0.7.0.md +11 -11
- package/docs/migration/release-notes/0.9.0.md +208 -27
- package/docs/migration/v0.7-to-v0.8.md +46 -47
- package/docs/migration/v0.8-to-v0.9.md +564 -208
- package/docs/migration/v0.9.0-troubleshooting.md +561 -0
- package/docs/reference/README.md +12 -0
- package/docs/reference/cli.md +2253 -0
- package/docs/reference/configuration.md +358 -0
- package/docs/reference/data-and-telemetry.md +105 -42
- package/docs/reference/workflows.md +647 -0
- package/package.json +22 -11
- package/schemas/akm-asset-envelope.json +93 -0
- package/schemas/akm-config.json +81 -128
- package/schemas/akm-workflow.json +74 -73
- package/dist/assets/tasks/core/backup.yml +0 -5
- package/dist/assets/tasks/graph-refresh-weekly.yml +0 -10
- package/dist/cli/config-migrate.js +0 -1878
- package/dist/cli/config-validate.js +0 -41
- package/dist/commands/backup-cli.js +0 -56
- package/dist/commands/bundle/bundle-cli.js +0 -68
- package/dist/commands/bundle/bundle.js +0 -219
- package/dist/commands/graph/graph-cli.js +0 -124
- package/dist/commands/graph/graph.js +0 -489
- package/dist/commands/improve/extract-watch.js +0 -140
- package/dist/commands/mv-cli.js +0 -1221
- package/dist/commands/sources/history.js +0 -201
- package/dist/commands/tasks/default-tasks.js +0 -186
- package/dist/core/migration-backup.js +0 -1231
- package/dist/indexer/usage/unmigrated-vaults-guard.js +0 -95
- package/dist/llm/memory-infer-impl.js +0 -138
- package/dist/migrate/legacy/config-source-migration.js +0 -257
- package/dist/migrate/legacy/content-migration.js +0 -350
- package/dist/migrate/legacy/legacy-layout.js +0 -779
- package/dist/migrate/legacy/legacy-paths.js +0 -25
- package/dist/migrate/legacy/legacy-stash-json.js +0 -80
- package/dist/migrate/legacy/proposal-fs-import.js +0 -168
- package/dist/migrate/legacy/task-target-ref-migration.js +0 -278
- package/dist/migrate/legacy/three-db-cutover.js +0 -845
- package/dist/migrate/legacy/workflow-migrations-bodies.js +0 -52
- package/dist/migrate/legacy/workflow-migrations-frozen.js +0 -21
- package/dist/migrate/legacy-ref-grammar.js +0 -214
- package/dist/output/shapes/distill.js +0 -14
- package/dist/output/shapes/history.js +0 -11
- package/dist/output/text/distill.js +0 -6
- package/dist/output/text/enable-disable.js +0 -8
- package/dist/output/text/history.js +0 -6
- package/dist/registry/build-index.js +0 -382
- package/dist/schemas/akm-config.json +0 -4704
- package/dist/schemas/akm-task.json +0 -87
- package/dist/schemas/akm-workflow.json +0 -372
- package/dist/scripts/migrate-storage.js +0 -3816
- package/dist/workflows/authoring/workflow-program-template.yaml +0 -31
- package/dist/workflows/cli.js +0 -53
- package/dist/workflows/exec/brief.js +0 -481
- package/dist/workflows/exec/report.js +0 -1460
- package/dist/workflows/exec/watch.js +0 -116
- package/dist/workflows/program/parser.js +0 -813
- package/dist/workflows/program/project.js +0 -104
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
/**
|
|
5
|
-
* `akm
|
|
5
|
+
* `akm task` — register, inspect, run, and remove scheduled task assets.
|
|
6
6
|
*
|
|
7
7
|
* Each handler exported here is a pure function that performs the real work;
|
|
8
8
|
* `src/cli.ts` wraps these in citty `defineCommand`s and shapes their return
|
|
@@ -15,18 +15,20 @@ import { assetPathForName } from "../../core/asset/asset-placement.js";
|
|
|
15
15
|
import { conceptIdFromTypeName, parseRefInput } from "../../core/asset/resolve-ref.js";
|
|
16
16
|
import { isWithin, resolveStashDir } from "../../core/common.js";
|
|
17
17
|
import { loadConfig } from "../../core/config/config.js";
|
|
18
|
+
import { IMPROVE_AUTONOMY_CONFIG_KEY, isImproveAutonomyEnabled } from "../../core/config/experimental.js";
|
|
18
19
|
import { ConfigError, NotFoundError, UsageError } from "../../core/errors.js";
|
|
19
20
|
import { getTaskHistoryDir, getTaskLogDir } from "../../core/paths.js";
|
|
20
|
-
import { commitWriteTargetBoundary, deleteAssetFromSource, resolveWriteTarget, writeAssetToSource, } from "../../core/write-source.js";
|
|
21
|
-
import {
|
|
21
|
+
import { commitWriteTargetBoundary, deleteAssetFromSource, prepareWriteTargetForMutation, resolveWriteTarget, writeAssetToSource, } from "../../core/write-source.js";
|
|
22
|
+
import { withEngineFallback } from "../../integrations/agent/engine-fallback.js";
|
|
22
23
|
import { backendNameForPlatform, selectBackend } from "../../tasks/backends/index.js";
|
|
23
|
-
import { findBareAkmExecutableIndex } from "../../tasks/command-executable.js";
|
|
24
24
|
import { parseTaskDocument } from "../../tasks/parser.js";
|
|
25
25
|
import { resolveAkmInvocation } from "../../tasks/resolve-akm-bin.js";
|
|
26
26
|
import { exitCodeForStatus, INVALID_TASK_ATTEMPT_ID, readTaskHistory, recordTaskAttemptFailure, runTask, } from "../../tasks/runner.js";
|
|
27
|
-
import { parseSchedule, SCHEDULE_SUPPORTED_SUBSET_HINT
|
|
27
|
+
import { parseSchedule, SCHEDULE_SUPPORTED_SUBSET_HINT } from "../../tasks/schedule.js";
|
|
28
|
+
import { schedulerContextDescriptor, validateSchedulerContextDescriptor, writeSchedulerContextDescriptor, } from "../../tasks/scheduler-invocation.js";
|
|
28
29
|
import { normaliseTaskId } from "../../tasks/task-id.js";
|
|
29
30
|
import { validateTaskDocument } from "../../tasks/validator.js";
|
|
31
|
+
import { applyAutonomyGate, configuredDirectAutonomyLanes, describeGatedLanes } from "../improve/autonomy-gate.js";
|
|
30
32
|
import { resolveImproveStrategy } from "../improve/improve-strategies.js";
|
|
31
33
|
export async function akmTasksAdd(input, deps = {}) {
|
|
32
34
|
const id = normaliseTaskId(input.id);
|
|
@@ -49,15 +51,22 @@ export async function akmTasksAdd(input, deps = {}) {
|
|
|
49
51
|
// from `deps.backend` when present — retiring the `_setBackendsForTests` seam.
|
|
50
52
|
const backend = deps.backend?.name ?? backendNameForPlatform();
|
|
51
53
|
parseSchedule(input.schedule, backend);
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
+
const bundle = resolveTaskBundle(input.target, { requireWritable: true });
|
|
55
|
+
const writeTarget = bundle.resolved;
|
|
56
|
+
const stashDir = bundle.stashDir;
|
|
57
|
+
const installOpts = bundle.installTarget !== undefined ? { target: bundle.installTarget } : undefined;
|
|
54
58
|
const typeRoot = path.join(stashDir, "tasks");
|
|
55
59
|
const assetPath = assetPathForName("task", typeRoot, id);
|
|
56
60
|
if (!isWithin(assetPath, typeRoot)) {
|
|
57
61
|
throw new UsageError(`Resolved task path escapes the stash: "${id}".`, "PATH_ESCAPE_VIOLATION");
|
|
58
62
|
}
|
|
59
|
-
|
|
60
|
-
|
|
63
|
+
// Pre-0.8.0 tasks were markdown; the 0.8.0 cutover moved them to pure YAML
|
|
64
|
+
// (see the tasks dir rule in src/indexer/walk/matchers.ts). A leftover
|
|
65
|
+
// `<id>.md` still names the same task, so creating `<id>.yml` beside it
|
|
66
|
+
// must collide loudly rather than silently minting a duplicate.
|
|
67
|
+
const legacyAssetPath = path.join(typeRoot, `${id}.md`);
|
|
68
|
+
if ((fs.existsSync(assetPath) || fs.existsSync(legacyAssetPath)) && !input.force) {
|
|
69
|
+
throw new UsageError(`Task "${id}" already exists. Pass --force to overwrite, or delete its file and run \`akm task sync\` first.`, "RESOURCE_ALREADY_EXISTS");
|
|
61
70
|
}
|
|
62
71
|
const yaml = renderTaskYaml({
|
|
63
72
|
id,
|
|
@@ -77,9 +86,6 @@ export async function akmTasksAdd(input, deps = {}) {
|
|
|
77
86
|
});
|
|
78
87
|
const task = parseTaskDocument({ yaml, filePath: assetPath, id });
|
|
79
88
|
await validateTaskDocument(task, { backend, stashDir });
|
|
80
|
-
const obsoleteReason = obsoleteBackupTaskReason(task);
|
|
81
|
-
if (obsoleteReason)
|
|
82
|
-
throw new UsageError(obsoleteReason, "INVALID_FLAG_VALUE");
|
|
83
89
|
const ref = taskAssetRef(id);
|
|
84
90
|
const previousYaml = fs.existsSync(assetPath) ? fs.readFileSync(assetPath, "utf8") : undefined;
|
|
85
91
|
let previousTask;
|
|
@@ -96,15 +102,19 @@ export async function akmTasksAdd(input, deps = {}) {
|
|
|
96
102
|
const writeAsset = deps.writeAsset ?? writeAssetToSource;
|
|
97
103
|
const deleteAsset = deps.deleteAsset ?? deleteAssetFromSource;
|
|
98
104
|
const commitBoundary = deps.commitBoundary ?? commitWriteTargetBoundary;
|
|
99
|
-
const
|
|
105
|
+
const installedEntries = await sched.list();
|
|
106
|
+
assertNoForeignSchedule(installedEntries, id, bundle.installTarget);
|
|
107
|
+
const wasInstalled = previousYaml !== undefined && installedEntries.some((entry) => entry.id === id);
|
|
108
|
+
const installedEntry = installedEntries.find((entry) => entry.id === id);
|
|
109
|
+
const runtimeOpts = schedulerInstallOptions(installOpts, installedEntry, deps, installedEntry ? false : input.rebind === true, `create scheduler entry for task "${id}"`);
|
|
100
110
|
let sourceRestoreArmed = false;
|
|
101
111
|
let installSucceeded = false;
|
|
102
112
|
try {
|
|
103
113
|
sourceRestoreArmed = true;
|
|
104
|
-
await writeAsset(
|
|
105
|
-
await sched.install(task);
|
|
114
|
+
await writeAsset(writeTarget.source, writeTarget.config, ref, yaml);
|
|
115
|
+
await sched.install(task, runtimeOpts);
|
|
106
116
|
installSucceeded = true;
|
|
107
|
-
commitBoundary(
|
|
117
|
+
commitBoundary(writeTarget, `Update tasks/${id}`);
|
|
108
118
|
}
|
|
109
119
|
catch (err) {
|
|
110
120
|
const rollbackErrors = [];
|
|
@@ -113,12 +123,12 @@ export async function akmTasksAdd(input, deps = {}) {
|
|
|
113
123
|
try {
|
|
114
124
|
if (previousYaml === undefined) {
|
|
115
125
|
if (fs.existsSync(assetPath)) {
|
|
116
|
-
await deleteAsset(
|
|
126
|
+
await deleteAsset(writeTarget.source, writeTarget.config, ref);
|
|
117
127
|
sourceRestored = true;
|
|
118
128
|
}
|
|
119
129
|
}
|
|
120
130
|
else {
|
|
121
|
-
await restoreTaskSourceBytes(writeAsset,
|
|
131
|
+
await restoreTaskSourceBytes(writeAsset, writeTarget.source, writeTarget.config, ref, assetPath, previousYaml);
|
|
122
132
|
sourceRestored = true;
|
|
123
133
|
}
|
|
124
134
|
}
|
|
@@ -136,7 +146,7 @@ export async function akmTasksAdd(input, deps = {}) {
|
|
|
136
146
|
}
|
|
137
147
|
else if (installSucceeded && previousTask) {
|
|
138
148
|
try {
|
|
139
|
-
await sched.install(previousTask);
|
|
149
|
+
await sched.install(previousTask, runtimeOpts);
|
|
140
150
|
}
|
|
141
151
|
catch (rollbackError) {
|
|
142
152
|
rollbackErrors.push(rollbackError);
|
|
@@ -162,7 +172,7 @@ export async function akmTasksAdd(input, deps = {}) {
|
|
|
162
172
|
}
|
|
163
173
|
if (sourceRestored) {
|
|
164
174
|
try {
|
|
165
|
-
commitBoundary(
|
|
175
|
+
commitBoundary(writeTarget, `Restore tasks/${id}`);
|
|
166
176
|
}
|
|
167
177
|
catch (rollbackError) {
|
|
168
178
|
rollbackErrors.push(rollbackError);
|
|
@@ -178,252 +188,13 @@ export async function akmTasksAdd(input, deps = {}) {
|
|
|
178
188
|
id,
|
|
179
189
|
ref: conceptIdFromTypeName("task", id),
|
|
180
190
|
path: assetPath,
|
|
181
|
-
stashDir,
|
|
191
|
+
bundleDir: stashDir,
|
|
182
192
|
schedule: task.schedule,
|
|
183
193
|
enabled: task.enabled,
|
|
184
194
|
backend,
|
|
185
195
|
target: task.target,
|
|
186
196
|
};
|
|
187
197
|
}
|
|
188
|
-
/**
|
|
189
|
-
* Emit a single grouped stderr warning for legacy `.md` task files in the
|
|
190
|
-
* tasks directory. 0.8.0 requires task definitions to be pure `.yml`; any
|
|
191
|
-
* leftover `.md` files from 0.7.x would otherwise be silently skipped, which
|
|
192
|
-
* makes scheduled tasks vanish without operator notice. We do NOT auto-migrate
|
|
193
|
-
* — that is a separate workstream — but operators must see the affected files.
|
|
194
|
-
*
|
|
195
|
-
* `seen` is module-level so the warning is emitted at most once per process,
|
|
196
|
-
* even when both `akm tasks list` and `akm tasks sync` are invoked in the same
|
|
197
|
-
* akm run.
|
|
198
|
-
*/
|
|
199
|
-
const warnedLegacyMdDirs = new Set();
|
|
200
|
-
function warnLegacyMdTaskFiles(typeRoot) {
|
|
201
|
-
if (warnedLegacyMdDirs.has(typeRoot))
|
|
202
|
-
return;
|
|
203
|
-
let mdFiles;
|
|
204
|
-
try {
|
|
205
|
-
mdFiles = fs.readdirSync(typeRoot).filter((f) => f.endsWith(".md"));
|
|
206
|
-
}
|
|
207
|
-
catch {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
if (mdFiles.length === 0)
|
|
211
|
-
return;
|
|
212
|
-
warnedLegacyMdDirs.add(typeRoot);
|
|
213
|
-
const affected = mdFiles.map((f) => `tasks/${f}`).join(", ");
|
|
214
|
-
process.stderr.write(`WARNING: ${mdFiles.length} task file(s) use the legacy .md format and were ignored.\n` +
|
|
215
|
-
` AKM 0.8.0 requires tasks as pure .yml. See docs/migration/v0.7-to-v0.8.md#task-definition-files-mdfrontmatter--yml.\n` +
|
|
216
|
-
` Affected: ${affected}\n`);
|
|
217
|
-
}
|
|
218
|
-
/**
|
|
219
|
-
* Reset the legacy `.md` task warning de-duplication state. Test-only escape
|
|
220
|
-
* hatch — production code should never call this.
|
|
221
|
-
*/
|
|
222
|
-
export function _resetLegacyMdTaskWarningStateForTests() {
|
|
223
|
-
warnedLegacyMdDirs.clear();
|
|
224
|
-
}
|
|
225
|
-
export async function akmTasksList() {
|
|
226
|
-
const stashDir = resolveStashDir();
|
|
227
|
-
const typeRoot = path.join(stashDir, "tasks");
|
|
228
|
-
if (!fs.existsSync(typeRoot))
|
|
229
|
-
return { tasks: [], stale: [] };
|
|
230
|
-
const entries = fs.readdirSync(typeRoot);
|
|
231
|
-
warnLegacyMdTaskFiles(typeRoot);
|
|
232
|
-
const files = entries.filter((f) => f.endsWith(".yml"));
|
|
233
|
-
const tasks = [];
|
|
234
|
-
const stale = [];
|
|
235
|
-
for (const file of files) {
|
|
236
|
-
const id = file.slice(0, -4);
|
|
237
|
-
const filePath = path.join(typeRoot, file);
|
|
238
|
-
let task;
|
|
239
|
-
try {
|
|
240
|
-
task = parseTaskDocument({ yaml: fs.readFileSync(filePath, "utf8"), filePath, id });
|
|
241
|
-
}
|
|
242
|
-
catch (err) {
|
|
243
|
-
if (isStaleTaskError(err))
|
|
244
|
-
stale.push(id);
|
|
245
|
-
continue; // skip malformed files; `akm tasks show <id>` will surface the error
|
|
246
|
-
}
|
|
247
|
-
tasks.push({
|
|
248
|
-
id: task.id,
|
|
249
|
-
ref: conceptIdFromTypeName("task", task.id),
|
|
250
|
-
path: filePath,
|
|
251
|
-
schedule: task.schedule,
|
|
252
|
-
enabled: task.enabled,
|
|
253
|
-
target: task.target,
|
|
254
|
-
name: task.name,
|
|
255
|
-
description: task.description,
|
|
256
|
-
when_to_use: task.when_to_use,
|
|
257
|
-
tags: task.tags,
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
if (stale.length > 0)
|
|
261
|
-
warnStaleTaskFiles(stale);
|
|
262
|
-
return { tasks, stale };
|
|
263
|
-
}
|
|
264
|
-
export async function akmTasksShow(id) {
|
|
265
|
-
const normalised = normaliseTaskId(id);
|
|
266
|
-
const stashDir = resolveStashDir();
|
|
267
|
-
const typeRoot = path.join(stashDir, "tasks");
|
|
268
|
-
if (fs.existsSync(typeRoot))
|
|
269
|
-
warnLegacyMdTaskFiles(typeRoot);
|
|
270
|
-
const filePath = await resolveAssetPath(stashDir, "task", normalised);
|
|
271
|
-
const task = parseTaskDocument({
|
|
272
|
-
yaml: fs.readFileSync(filePath, "utf8"),
|
|
273
|
-
filePath,
|
|
274
|
-
id: normalised,
|
|
275
|
-
});
|
|
276
|
-
const spec = parseSchedule(task.schedule, backendNameForPlatform());
|
|
277
|
-
return {
|
|
278
|
-
id: task.id,
|
|
279
|
-
ref: conceptIdFromTypeName("task", task.id),
|
|
280
|
-
path: filePath,
|
|
281
|
-
schedule: task.schedule,
|
|
282
|
-
cron: translateToCron(spec),
|
|
283
|
-
enabled: task.enabled,
|
|
284
|
-
target: task.target,
|
|
285
|
-
name: task.name,
|
|
286
|
-
description: task.description,
|
|
287
|
-
when_to_use: task.when_to_use,
|
|
288
|
-
tags: task.tags,
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
export async function akmTasksRemove(id, deps = {}) {
|
|
292
|
-
const normalised = normaliseTaskId(id);
|
|
293
|
-
const target = resolveTaskWriteTarget();
|
|
294
|
-
const stashDir = target.source.path;
|
|
295
|
-
const typeRoot = path.join(stashDir, "tasks");
|
|
296
|
-
if (fs.existsSync(typeRoot))
|
|
297
|
-
warnLegacyMdTaskFiles(typeRoot);
|
|
298
|
-
const filePath = await resolveAssetPath(stashDir, "task", normalised);
|
|
299
|
-
const yaml = fs.readFileSync(filePath, "utf8");
|
|
300
|
-
const ref = taskAssetRef(normalised);
|
|
301
|
-
const sched = deps.backend ?? selectBackend();
|
|
302
|
-
const writeAsset = deps.writeAsset ?? writeAssetToSource;
|
|
303
|
-
const deleteAsset = deps.deleteAsset ?? deleteAssetFromSource;
|
|
304
|
-
const commitBoundary = deps.commitBoundary ?? commitWriteTargetBoundary;
|
|
305
|
-
const wasInstalled = (await sched.list()).some((entry) => entry.id === normalised);
|
|
306
|
-
const previousTask = wasInstalled ? parseTaskDocument({ yaml, filePath, id: normalised }) : undefined;
|
|
307
|
-
let uninstallAttempted = false;
|
|
308
|
-
let deleteAttempted = false;
|
|
309
|
-
try {
|
|
310
|
-
uninstallAttempted = true;
|
|
311
|
-
await sched.uninstall(normalised);
|
|
312
|
-
deleteAttempted = true;
|
|
313
|
-
await deleteAsset(target.source, target.config, ref);
|
|
314
|
-
commitBoundary(target, `Remove tasks/${normalised}`);
|
|
315
|
-
}
|
|
316
|
-
catch (err) {
|
|
317
|
-
const rollbackErrors = [];
|
|
318
|
-
let sourceRestored = false;
|
|
319
|
-
if (deleteAttempted) {
|
|
320
|
-
try {
|
|
321
|
-
await restoreTaskSourceBytes(writeAsset, target.source, target.config, ref, filePath, yaml);
|
|
322
|
-
sourceRestored = true;
|
|
323
|
-
}
|
|
324
|
-
catch (rollbackError) {
|
|
325
|
-
rollbackErrors.push(rollbackError);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
if (uninstallAttempted && previousTask) {
|
|
329
|
-
try {
|
|
330
|
-
await sched.install(previousTask);
|
|
331
|
-
}
|
|
332
|
-
catch (rollbackError) {
|
|
333
|
-
rollbackErrors.push(rollbackError);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
if (sourceRestored) {
|
|
337
|
-
try {
|
|
338
|
-
commitBoundary(target, `Restore tasks/${normalised}`);
|
|
339
|
-
}
|
|
340
|
-
catch (rollbackError) {
|
|
341
|
-
rollbackErrors.push(rollbackError);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
if (rollbackErrors.length > 0) {
|
|
345
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
346
|
-
throw new AggregateError([err, ...rollbackErrors], `${message}; rollback for task "${normalised}" was incomplete.`);
|
|
347
|
-
}
|
|
348
|
-
throw err;
|
|
349
|
-
}
|
|
350
|
-
return { id: normalised, removed: true, backend: sched.name };
|
|
351
|
-
}
|
|
352
|
-
export async function akmTasksSetEnabled(id, enabled, deps = {}) {
|
|
353
|
-
const normalised = normaliseTaskId(id);
|
|
354
|
-
const target = resolveTaskWriteTarget();
|
|
355
|
-
const stashDir = target.source.path;
|
|
356
|
-
const typeRoot = path.join(stashDir, "tasks");
|
|
357
|
-
if (fs.existsSync(typeRoot))
|
|
358
|
-
warnLegacyMdTaskFiles(typeRoot);
|
|
359
|
-
const filePath = await resolveAssetPath(stashDir, "task", normalised);
|
|
360
|
-
const yaml = fs.readFileSync(filePath, "utf8");
|
|
361
|
-
// Parse before writing so unsupported tasks are diagnosed without changing
|
|
362
|
-
// the source file or its installed scheduler entry.
|
|
363
|
-
const previousTask = parseTaskDocument({ yaml, filePath, id: normalised });
|
|
364
|
-
const updated = setEnabledInYaml(yaml, enabled);
|
|
365
|
-
const task = parseTaskDocument({ yaml: updated, filePath, id: normalised });
|
|
366
|
-
const obsoleteReason = obsoleteBackupTaskReason(task);
|
|
367
|
-
if (obsoleteReason)
|
|
368
|
-
throw new UsageError(obsoleteReason, "INVALID_FLAG_VALUE");
|
|
369
|
-
const ref = taskAssetRef(normalised);
|
|
370
|
-
const sched = deps.backend ?? selectBackend();
|
|
371
|
-
const writeAsset = deps.writeAsset ?? writeAssetToSource;
|
|
372
|
-
const commitBoundary = deps.commitBoundary ?? commitWriteTargetBoundary;
|
|
373
|
-
const wasInstalled = (await sched.list()).some((entry) => entry.id === normalised);
|
|
374
|
-
let sourceRestoreArmed = false;
|
|
375
|
-
let installSucceeded = false;
|
|
376
|
-
try {
|
|
377
|
-
sourceRestoreArmed = true;
|
|
378
|
-
await writeAsset(target.source, target.config, ref, updated);
|
|
379
|
-
// Reinstall from the (just-updated) definition rather than only toggling
|
|
380
|
-
// the comment. A plain toggle leaves a stale schedule in place if the
|
|
381
|
-
// .yml's `schedule:` changed while the task was disabled — re-enabling
|
|
382
|
-
// would silently keep the old cron line. install() renders the block with
|
|
383
|
-
// both the current schedule and the new enabled state, and is idempotent.
|
|
384
|
-
await sched.install(task);
|
|
385
|
-
installSucceeded = true;
|
|
386
|
-
commitBoundary(target, `Update tasks/${normalised}`);
|
|
387
|
-
}
|
|
388
|
-
catch (err) {
|
|
389
|
-
const rollbackErrors = [];
|
|
390
|
-
let sourceRestored = false;
|
|
391
|
-
if (sourceRestoreArmed) {
|
|
392
|
-
try {
|
|
393
|
-
await restoreTaskSourceBytes(writeAsset, target.source, target.config, ref, filePath, yaml);
|
|
394
|
-
sourceRestored = true;
|
|
395
|
-
}
|
|
396
|
-
catch (rollbackError) {
|
|
397
|
-
rollbackErrors.push(rollbackError);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
if (installSucceeded) {
|
|
401
|
-
try {
|
|
402
|
-
if (wasInstalled)
|
|
403
|
-
await sched.install(previousTask);
|
|
404
|
-
else
|
|
405
|
-
await sched.uninstall(normalised);
|
|
406
|
-
}
|
|
407
|
-
catch (rollbackError) {
|
|
408
|
-
rollbackErrors.push(rollbackError);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
if (sourceRestored) {
|
|
412
|
-
try {
|
|
413
|
-
commitBoundary(target, `Restore tasks/${normalised}`);
|
|
414
|
-
}
|
|
415
|
-
catch (rollbackError) {
|
|
416
|
-
rollbackErrors.push(rollbackError);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
if (rollbackErrors.length > 0) {
|
|
420
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
421
|
-
throw new AggregateError([err, ...rollbackErrors], `${message}; rollback for task "${normalised}" was incomplete.`);
|
|
422
|
-
}
|
|
423
|
-
throw err;
|
|
424
|
-
}
|
|
425
|
-
return { id: normalised, enabled, backend: sched.name };
|
|
426
|
-
}
|
|
427
198
|
export async function akmTasksRun(id, options = {}) {
|
|
428
199
|
const startedAt = new Date();
|
|
429
200
|
let normalised;
|
|
@@ -441,7 +212,13 @@ export async function akmTasksRun(id, options = {}) {
|
|
|
441
212
|
}
|
|
442
213
|
let stashDir;
|
|
443
214
|
try {
|
|
444
|
-
|
|
215
|
+
// No --bundle uses the primary stash. With --bundle, resolve (read-only)
|
|
216
|
+
// the named bundle so the task file and
|
|
217
|
+
// its relative asset refs load from that bundle's path.
|
|
218
|
+
stashDir =
|
|
219
|
+
options.target !== undefined
|
|
220
|
+
? resolveWriteTarget(loadConfig(), options.target, { requireWritable: false }).source.path
|
|
221
|
+
: resolveStashDir();
|
|
445
222
|
}
|
|
446
223
|
catch (failure) {
|
|
447
224
|
recordTaskAttemptFailure({
|
|
@@ -452,9 +229,6 @@ export async function akmTasksRun(id, options = {}) {
|
|
|
452
229
|
});
|
|
453
230
|
throw failure;
|
|
454
231
|
}
|
|
455
|
-
const typeRoot = path.join(stashDir, "tasks");
|
|
456
|
-
if (fs.existsSync(typeRoot))
|
|
457
|
-
warnLegacyMdTaskFiles(typeRoot);
|
|
458
232
|
const result = await runTask(normalised, { stashDir, scheduled: options.scheduled === true });
|
|
459
233
|
const exitCode = result.status === "failed" && result.target.kind === "command" && result.detail?.exitCode === 78
|
|
460
234
|
? 78
|
|
@@ -468,26 +242,32 @@ export async function akmTasksRun(id, options = {}) {
|
|
|
468
242
|
export async function akmTasksHistory(input) {
|
|
469
243
|
const limit = input.limit !== undefined && input.limit > 0 ? input.limit : 50;
|
|
470
244
|
const id = input.id ? normaliseTaskId(input.id) : undefined;
|
|
471
|
-
|
|
472
|
-
const typeRoot = path.join(stashDir, "tasks");
|
|
473
|
-
if (fs.existsSync(typeRoot))
|
|
474
|
-
warnLegacyMdTaskFiles(typeRoot);
|
|
245
|
+
// History rows are keyed by task id in state.db, not per bundle.
|
|
475
246
|
return { rows: readTaskHistory({ id, limit }) };
|
|
476
247
|
}
|
|
477
248
|
/**
|
|
478
|
-
* Reconcile the on-disk task files with the OS scheduler.
|
|
249
|
+
* Reconcile the on-disk task files of ONE bundle with the OS scheduler.
|
|
479
250
|
* • install missing tasks (after validating them — invalid files are
|
|
480
251
|
* skipped with a per-task reason rather than aborting the whole sync)
|
|
481
252
|
* • reinstall tasks whose schedule or enabled state changed in the .yml
|
|
482
253
|
* (drift detected by comparing the backend's installed signature against
|
|
483
254
|
* the signature the current definition would produce)
|
|
484
255
|
* • remove orphan scheduler entries that no longer have a backing file
|
|
256
|
+
*
|
|
257
|
+
* `--bundle <bundle>` scopes the reconciliation to that bundle: the file set is
|
|
258
|
+
* the bundle's `tasks/*.yml` and — crucially — the scheduler entries considered
|
|
259
|
+
* are ONLY those attributed to the same bundle (parsed from the installed
|
|
260
|
+
* `--bundle` token; absent ⇒ primary). This is the security boundary that keeps
|
|
261
|
+
* "registering a bundle never activates code": a plain (primary) sync never
|
|
262
|
+
* installs from, updates, or removes another bundle's entries, and sync never
|
|
263
|
+
* scans all bundles. Activation happens only through explicit `add --bundle`
|
|
264
|
+
* (or `sync --bundle` on a bundle whose task files are already present).
|
|
485
265
|
*/
|
|
486
|
-
export async function akmTasksSync(deps = {}) {
|
|
487
|
-
const stashDir =
|
|
266
|
+
export async function akmTasksSync(deps = {}, bundleTarget, options = {}) {
|
|
267
|
+
const stashDir = resolveTaskInspectDir(bundleTarget);
|
|
268
|
+
// Primary-bundle scheduler entries omit --bundle; other bundles carry it.
|
|
269
|
+
const syncTarget = bundleTarget !== undefined && !isPrimaryStashPath(stashDir) ? bundleTarget : undefined;
|
|
488
270
|
const typeRoot = path.join(stashDir, "tasks");
|
|
489
|
-
if (fs.existsSync(typeRoot))
|
|
490
|
-
warnLegacyMdTaskFiles(typeRoot);
|
|
491
271
|
const fileIds = fs.existsSync(typeRoot)
|
|
492
272
|
? fs
|
|
493
273
|
.readdirSync(typeRoot)
|
|
@@ -496,22 +276,30 @@ export async function akmTasksSync(deps = {}) {
|
|
|
496
276
|
: [];
|
|
497
277
|
const sched = deps.backend ?? selectBackend();
|
|
498
278
|
const backend = sched.name;
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
279
|
+
const installOpts = syncTarget !== undefined ? { target: syncTarget } : undefined;
|
|
280
|
+
const allEntries = options.rebind && sched.listForRebind ? await sched.listForRebind() : await sched.list();
|
|
281
|
+
// Attribution filter: only entries installed from THIS bundle are reconciled
|
|
282
|
+
// here. Entries carrying a `--bundle` for a different bundle are invisible to
|
|
283
|
+
// this sync — never removed, never touched.
|
|
284
|
+
const present = new Map(allEntries.filter((t) => sameBundle(t.target, syncTarget)).map((t) => [t.id, t]));
|
|
502
285
|
const installed = [];
|
|
503
286
|
const updated = [];
|
|
504
287
|
const unchanged = [];
|
|
505
288
|
const skipped = [];
|
|
289
|
+
const warnings = [];
|
|
506
290
|
for (const id of fileIds) {
|
|
507
291
|
const filePath = path.join(typeRoot, `${id}.yml`);
|
|
508
292
|
let task;
|
|
509
293
|
try {
|
|
510
294
|
task = parseTaskDocument({ yaml: fs.readFileSync(filePath, "utf8"), filePath, id });
|
|
511
295
|
await validateTaskDocument(task, { backend, stashDir });
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
296
|
+
// A bare id can only be scheduled from ONE bundle at a time (scheduler ids
|
|
297
|
+
// are never namespaced). If this id is already scheduled from a different
|
|
298
|
+
// bundle, refuse rather than clobber it — surface it as a per-task skip so
|
|
299
|
+
// the rest of the sync still proceeds.
|
|
300
|
+
const foreign = allEntries.find((e) => e.id === id && !sameBundle(e.target, syncTarget));
|
|
301
|
+
if (foreign)
|
|
302
|
+
throw new UsageError(foreignScheduleMessage(id, foreign.target), "RESOURCE_ALREADY_EXISTS");
|
|
515
303
|
}
|
|
516
304
|
catch (err) {
|
|
517
305
|
skipped.push({ id, reason: err instanceof Error ? err.message : String(err) });
|
|
@@ -531,8 +319,14 @@ export async function akmTasksSync(deps = {}) {
|
|
|
531
319
|
continue;
|
|
532
320
|
}
|
|
533
321
|
if (!present.has(id)) {
|
|
534
|
-
|
|
535
|
-
|
|
322
|
+
try {
|
|
323
|
+
const runtimeOpts = schedulerInstallOptions(installOpts, undefined, deps, options.rebind === true, `create scheduler entry for task "${id}"`, warnings);
|
|
324
|
+
await sched.install(task, runtimeOpts);
|
|
325
|
+
installed.push(id);
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
skipped.push({ id, reason: error instanceof Error ? error.message : String(error) });
|
|
329
|
+
}
|
|
536
330
|
continue;
|
|
537
331
|
}
|
|
538
332
|
// Already installed — reconcile against the current definition. Compare the
|
|
@@ -540,13 +334,15 @@ export async function akmTasksSync(deps = {}) {
|
|
|
540
334
|
// When the backend can't produce a signature (no expectedSignature, or it
|
|
541
335
|
// didn't record one), reinstall unconditionally — install() is idempotent,
|
|
542
336
|
// so the cost is one crontab write and correctness is guaranteed.
|
|
543
|
-
const
|
|
544
|
-
const
|
|
337
|
+
const installedEntry = present.get(id);
|
|
338
|
+
const runtimeOpts = schedulerInstallOptions(installOpts, options.rebind ? undefined : installedEntry, deps, options.rebind === true, `rebind scheduler entry for task "${id}"`, warnings);
|
|
339
|
+
const installedSig = installedEntry.signature;
|
|
340
|
+
const expectedSig = sched.expectedSignature?.(task, runtimeOpts);
|
|
545
341
|
if (installedSig !== undefined && expectedSig !== undefined && installedSig === expectedSig) {
|
|
546
342
|
unchanged.push(id);
|
|
547
343
|
}
|
|
548
344
|
else {
|
|
549
|
-
await sched.install(task);
|
|
345
|
+
await sched.install(task, runtimeOpts);
|
|
550
346
|
updated.push(id);
|
|
551
347
|
}
|
|
552
348
|
}
|
|
@@ -557,48 +353,69 @@ export async function akmTasksSync(deps = {}) {
|
|
|
557
353
|
removed.push(installedId);
|
|
558
354
|
}
|
|
559
355
|
}
|
|
560
|
-
return {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
if (args.length !== 2 || args[0] !== "db" || args[1] !== "backups")
|
|
570
|
-
return undefined;
|
|
571
|
-
return (`Task "${task.id}" invokes obsolete \`akm db backups\`, which only listed legacy backups and is not a 0.9 backup command. ` +
|
|
572
|
-
"AKM will not install or enable it; sync will keep any existing scheduler entry disabled, and the task file is unchanged. " +
|
|
573
|
-
"Use `akm backup create --for 0.9.0` for an explicit migration recovery snapshot.");
|
|
356
|
+
return {
|
|
357
|
+
installed,
|
|
358
|
+
updated,
|
|
359
|
+
removed,
|
|
360
|
+
unchanged,
|
|
361
|
+
skipped,
|
|
362
|
+
backend: sched.name,
|
|
363
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
364
|
+
};
|
|
574
365
|
}
|
|
575
|
-
export async function akmTasksDoctor() {
|
|
366
|
+
export async function akmTasksDoctor(deps = {}) {
|
|
576
367
|
const warnings = [];
|
|
577
|
-
let invocation = {
|
|
368
|
+
let invocation = {
|
|
369
|
+
argv: [],
|
|
370
|
+
via: "unresolved",
|
|
371
|
+
};
|
|
578
372
|
try {
|
|
579
|
-
const r = resolveAkmInvocation();
|
|
580
|
-
invocation = { argv: r.argv, via: r.via };
|
|
373
|
+
const r = (deps.resolveInvocation ?? resolveAkmInvocation)();
|
|
374
|
+
invocation = { argv: r.argv, via: r.via, kind: r.kind, eligible: r.eligible };
|
|
581
375
|
}
|
|
582
376
|
catch (err) {
|
|
583
377
|
warnings.push(err instanceof Error ? err.message : String(err));
|
|
584
378
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
379
|
+
const skipNativeInspection = process.env.BUN_TEST === "1" && !deps.backend;
|
|
380
|
+
const sched = deps.backend ?? (skipNativeInspection ? undefined : selectBackend());
|
|
381
|
+
const backend = sched?.name ?? backendNameForPlatform();
|
|
382
|
+
let installed = [];
|
|
383
|
+
if (skipNativeInspection) {
|
|
384
|
+
warnings.push("Native scheduler inspection is skipped inside the bun test harness.");
|
|
590
385
|
}
|
|
591
|
-
|
|
592
|
-
|
|
386
|
+
else {
|
|
387
|
+
try {
|
|
388
|
+
installed = await sched.list();
|
|
389
|
+
}
|
|
390
|
+
catch (error) {
|
|
391
|
+
warnings.push(`Unable to inspect installed ${backend} definitions: ${error instanceof Error ? error.message : String(error)}`);
|
|
392
|
+
}
|
|
593
393
|
}
|
|
594
|
-
const
|
|
595
|
-
|
|
394
|
+
const bindings = groupInstalledBindings(installed, invocation);
|
|
395
|
+
// Report the EFFECTIVE engine view — the same one the runner resolves —
|
|
396
|
+
// so doctor never says "no engine" on an install where tasks actually run.
|
|
397
|
+
const { config } = withEngineFallback(loadConfig());
|
|
596
398
|
const defaultEngine = config.defaults?.engine;
|
|
597
399
|
const engines = Object.keys(config.engines ?? {});
|
|
598
400
|
// §6.1: surface the effective triage settings for the default improve
|
|
599
401
|
// strategy. The struct is a fixed shape, so this is a deliberate addition.
|
|
600
402
|
const improveStrategyName = typeof config.defaults?.improveStrategy === "string" ? config.defaults.improveStrategy : "default";
|
|
601
|
-
|
|
403
|
+
// D8 — report the EFFECTIVE strategy, not the raw one. Resolving the raw
|
|
404
|
+
// strategy here would report `applyMode: "promote"` for a promote strategy
|
|
405
|
+
// under a review-first config, while the run actually uses "queue" — a doctor
|
|
406
|
+
// command lying about the thing it exists to diagnose.
|
|
407
|
+
const rawStrategy = resolveImproveStrategy(config.defaults?.improveStrategy, config).config;
|
|
408
|
+
const { config: effectiveStrategy, gated } = applyAutonomyGate(rawStrategy, config);
|
|
409
|
+
const autonomyEnabled = isImproveAutonomyEnabled(config);
|
|
410
|
+
// Memory cleanup has no strategy flag to downgrade, so add that direct lane
|
|
411
|
+
// to the strategy-derived gate report.
|
|
412
|
+
const allGated = autonomyEnabled ? [] : [...gated, ...describeGatedLanes(configuredDirectAutonomyLanes())];
|
|
413
|
+
const improveAutonomy = {
|
|
414
|
+
enabled: autonomyEnabled,
|
|
415
|
+
configKey: IMPROVE_AUTONOMY_CONFIG_KEY,
|
|
416
|
+
gatedLanes: allGated.map((entry) => ({ lane: entry.lane, reason: entry.reason })),
|
|
417
|
+
};
|
|
418
|
+
const triage = effectiveStrategy.processes?.triage;
|
|
602
419
|
const improveTriage = triage
|
|
603
420
|
? {
|
|
604
421
|
defaultStrategy: improveStrategyName,
|
|
@@ -610,17 +427,111 @@ export async function akmTasksDoctor() {
|
|
|
610
427
|
return {
|
|
611
428
|
backend,
|
|
612
429
|
akm: invocation,
|
|
430
|
+
caller: invocation,
|
|
431
|
+
bindings,
|
|
432
|
+
...(bindings.some((binding) => !binding.status.includes("ok"))
|
|
433
|
+
? { remediation: "akm task sync --rebind" }
|
|
434
|
+
: {}),
|
|
613
435
|
logDir: getTaskLogDir(),
|
|
614
436
|
historyDir: getTaskHistoryDir(),
|
|
615
437
|
engine: { defaultEngine, available: engines },
|
|
616
|
-
stale: collectStaleTaskIds(),
|
|
617
|
-
staleGeneratedCommands: collectStaleGeneratedCommands(),
|
|
618
438
|
scheduleSubset: SCHEDULE_SUPPORTED_SUBSET_HINT,
|
|
619
439
|
warnings,
|
|
440
|
+
improveAutonomy,
|
|
620
441
|
...(improveTriage ? { improveTriage } : {}),
|
|
621
442
|
};
|
|
622
443
|
}
|
|
623
444
|
// ── helpers ─────────────────────────────────────────────────────────────────
|
|
445
|
+
function schedulerInstallOptions(base, installed, deps, explicitRebind, operation, warnings = []) {
|
|
446
|
+
if (installed && !explicitRebind) {
|
|
447
|
+
return {
|
|
448
|
+
...base,
|
|
449
|
+
binding: installed.binding,
|
|
450
|
+
contextPath: installed.contextPath,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
// Injected backends can own their default runtime unless a resolver is supplied.
|
|
454
|
+
if (deps.backend && !deps.schedulerRuntime)
|
|
455
|
+
return base;
|
|
456
|
+
const runtime = deps.schedulerRuntime?.() ?? prepareSchedulerRuntime(explicitRebind, operation);
|
|
457
|
+
// --rebind bypasses the eligibility refusal in prepareSchedulerRuntime; warn once
|
|
458
|
+
// per sync run rather than silently writing a mutable/unproven binary into cron.
|
|
459
|
+
if (explicitRebind && runtime.eligible === false && warnings.length === 0) {
|
|
460
|
+
warnings.push(`--rebind bound scheduled tasks to an ineligible ${runtime.kind ?? "unknown"} invocation (${runtime.binding.join(" ")}); scheduled runs will invoke a mutable, unproven binary. Install akm via \`npm install --global akm-cli\` or a standalone release, then re-run \`akm task sync --rebind\`.`);
|
|
461
|
+
}
|
|
462
|
+
return { ...base, binding: runtime.binding, contextPath: runtime.contextPath };
|
|
463
|
+
}
|
|
464
|
+
export function prepareSchedulerRuntime(explicitRebind, operation, deps = {}) {
|
|
465
|
+
const invocation = (deps.resolveInvocation ?? resolveAkmInvocation)();
|
|
466
|
+
if (!invocation.eligible && !explicitRebind) {
|
|
467
|
+
throw new UsageError(`Refusing to ${operation} from an ineligible ${invocation.kind ?? "unknown"} invocation (${invocation.argv.join(" ")}).`, "INVALID_FLAG_VALUE", "npm-global ownership could not be verified. Run `npm install --global akm-cli` and use that launcher, use a standalone installation, or explicitly repeat the operation with --rebind.");
|
|
468
|
+
}
|
|
469
|
+
const contextPath = (deps.writeDescriptor ?? writeSchedulerContextDescriptor)(schedulerContextDescriptor());
|
|
470
|
+
return { binding: invocation.argv, contextPath, eligible: invocation.eligible, kind: invocation.kind };
|
|
471
|
+
}
|
|
472
|
+
function groupInstalledBindings(entries, invocation) {
|
|
473
|
+
const groups = new Map();
|
|
474
|
+
for (const entry of entries) {
|
|
475
|
+
const argv = entry.binding;
|
|
476
|
+
const status = inspectInstalledBinding(entry, invocation);
|
|
477
|
+
const key = JSON.stringify([argv, entry.contextPath, status]);
|
|
478
|
+
const existing = groups.get(key);
|
|
479
|
+
if (existing) {
|
|
480
|
+
existing.taskIds.push(entry.id);
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
groups.set(key, {
|
|
484
|
+
argv,
|
|
485
|
+
contextPath: entry.contextPath,
|
|
486
|
+
taskIds: [entry.id],
|
|
487
|
+
status,
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
return [...groups.values()].map((group) => ({ ...group, taskIds: group.taskIds.sort() }));
|
|
491
|
+
}
|
|
492
|
+
function inspectInstalledBinding(entry, invocation) {
|
|
493
|
+
const status = [];
|
|
494
|
+
const binding = entry.binding;
|
|
495
|
+
if (!(invocation.eligible === true && sameArgv(binding, invocation.argv)) &&
|
|
496
|
+
binding.some((part) => /(?:^|[\\/])src[\\/]cli\.ts$|(?:^|[\\/])dist[\\/](?:cli\.js|cli-node\.mjs)$/i.test(part) ||
|
|
497
|
+
(path.isAbsolute(part) && hasGitAncestor(part)))) {
|
|
498
|
+
status.push("checkout");
|
|
499
|
+
}
|
|
500
|
+
if (binding.some((part) => part === "akm" || part === "bun" || part === "node"))
|
|
501
|
+
status.push("path-selected");
|
|
502
|
+
try {
|
|
503
|
+
validateSchedulerContextDescriptor(entry.contextPath);
|
|
504
|
+
}
|
|
505
|
+
catch {
|
|
506
|
+
status.push("invalid-context");
|
|
507
|
+
}
|
|
508
|
+
const absolutePaths = [...binding.filter((part) => path.isAbsolute(part)), entry.contextPath];
|
|
509
|
+
if (absolutePaths.some((part) => !fs.existsSync(part)))
|
|
510
|
+
status.push("missing-path");
|
|
511
|
+
if (status.length === 0)
|
|
512
|
+
status.push("ok");
|
|
513
|
+
return status;
|
|
514
|
+
}
|
|
515
|
+
function sameArgv(left, right) {
|
|
516
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
517
|
+
}
|
|
518
|
+
function hasGitAncestor(file) {
|
|
519
|
+
let current;
|
|
520
|
+
try {
|
|
521
|
+
current = path.dirname(fs.realpathSync(file));
|
|
522
|
+
}
|
|
523
|
+
catch {
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
for (;;) {
|
|
527
|
+
if (fs.existsSync(path.join(current, ".git")))
|
|
528
|
+
return true;
|
|
529
|
+
const parent = path.dirname(current);
|
|
530
|
+
if (parent === current)
|
|
531
|
+
return false;
|
|
532
|
+
current = parent;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
624
535
|
function taskAssetRef(id) {
|
|
625
536
|
return { type: "task", name: id };
|
|
626
537
|
}
|
|
@@ -629,8 +540,55 @@ async function restoreTaskSourceBytes(writeAsset, source, config, ref, filePath,
|
|
|
629
540
|
// The normal write path adds a trailing newline; rollback restores the raw snapshot exactly.
|
|
630
541
|
fs.writeFileSync(filePath, yaml, "utf8");
|
|
631
542
|
}
|
|
632
|
-
|
|
633
|
-
|
|
543
|
+
/**
|
|
544
|
+
* Resolve the bundle a mutating/run task command targets. Returns the resolved
|
|
545
|
+
* write/read target, its stash path, and the `--bundle <bundle>` token to embed
|
|
546
|
+
* in scheduled invocations. The primary bundle uses the target-less form.
|
|
547
|
+
*/
|
|
548
|
+
function resolveTaskBundle(target, opts) {
|
|
549
|
+
const selected = resolveWriteTarget(loadConfig(), target, { requireWritable: opts.requireWritable });
|
|
550
|
+
const resolved = opts.requireWritable ? prepareWriteTargetForMutation(selected) : selected;
|
|
551
|
+
const stashDir = resolved.source.path;
|
|
552
|
+
const installTarget = isPrimaryStashPath(stashDir) ? undefined : (resolved.selector ?? resolved.source.name);
|
|
553
|
+
return { resolved, stashDir, installTarget };
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Resolve the tasks/ directory a read/inspect command operates on. No
|
|
557
|
+
* `--bundle` uses the primary stash; `--bundle X` resolves bundle X read-only.
|
|
558
|
+
*/
|
|
559
|
+
function resolveTaskInspectDir(target) {
|
|
560
|
+
if (target === undefined)
|
|
561
|
+
return resolveStashDir();
|
|
562
|
+
return resolveWriteTarget(loadConfig(), target, { requireWritable: false }).source.path;
|
|
563
|
+
}
|
|
564
|
+
/** True when `candidate` resolves to the same directory as the primary stash. */
|
|
565
|
+
function isPrimaryStashPath(candidate) {
|
|
566
|
+
let primary;
|
|
567
|
+
try {
|
|
568
|
+
primary = path.resolve(resolveStashDir());
|
|
569
|
+
}
|
|
570
|
+
catch {
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
return path.resolve(candidate) === primary;
|
|
574
|
+
}
|
|
575
|
+
/** Two bundle attributions match when both are the primary (undefined) or equal names. */
|
|
576
|
+
function sameBundle(a, b) {
|
|
577
|
+
return (a ?? undefined) === (b ?? undefined);
|
|
578
|
+
}
|
|
579
|
+
function foreignScheduleMessage(id, existingTarget) {
|
|
580
|
+
const where = existingTarget === undefined ? "the default bundle" : `bundle "${existingTarget}"`;
|
|
581
|
+
return `Task id "${id}" is already scheduled from ${where}; rename the task or disable the existing one first.`;
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Refuse to schedule an id already installed from a DIFFERENT bundle. Scheduler
|
|
585
|
+
* ids are the bare task id (never namespaced), so a single id can be active from
|
|
586
|
+
* only one bundle at a time — a collision is a hard error, not an auto-rename.
|
|
587
|
+
*/
|
|
588
|
+
function assertNoForeignSchedule(entries, id, installTarget) {
|
|
589
|
+
const foreign = entries.find((entry) => entry.id === id && !sameBundle(entry.target, installTarget));
|
|
590
|
+
if (foreign)
|
|
591
|
+
throw new UsageError(foreignScheduleMessage(id, foreign.target), "RESOURCE_ALREADY_EXISTS");
|
|
634
592
|
}
|
|
635
593
|
function renderTaskYaml(input) {
|
|
636
594
|
const obj = { version: 2, schedule: input.schedule, enabled: input.enabled };
|
|
@@ -667,101 +625,6 @@ function renderTaskYaml(input) {
|
|
|
667
625
|
obj.tags = input.tags;
|
|
668
626
|
return yamlStringify(obj);
|
|
669
627
|
}
|
|
670
|
-
function isStaleTaskError(err) {
|
|
671
|
-
return err instanceof UsageError && err.code === "TASK_SCHEMA_VERSION_UNSUPPORTED";
|
|
672
|
-
}
|
|
673
|
-
function warnStaleTaskFiles(ids) {
|
|
674
|
-
process.stderr.write(`WARNING: ${ids.length} task file(s) use an unsupported task schema version and were not loaded.\n` +
|
|
675
|
-
` Use version: 2. See docs/migration/v0.8-to-v0.9.md#engine-and-task-assets.\n` +
|
|
676
|
-
` Affected: ${ids.map((id) => `tasks/${id}.yml`).join(", ")}\n`);
|
|
677
|
-
}
|
|
678
|
-
function collectStaleTaskIds() {
|
|
679
|
-
const typeRoot = path.join(resolveStashDir(), "tasks");
|
|
680
|
-
if (!fs.existsSync(typeRoot))
|
|
681
|
-
return [];
|
|
682
|
-
const stale = [];
|
|
683
|
-
for (const file of fs.readdirSync(typeRoot)) {
|
|
684
|
-
if (!file.endsWith(".yml"))
|
|
685
|
-
continue;
|
|
686
|
-
const id = file.slice(0, -4);
|
|
687
|
-
try {
|
|
688
|
-
parseTaskDocument({
|
|
689
|
-
yaml: fs.readFileSync(path.join(typeRoot, file), "utf8"),
|
|
690
|
-
filePath: path.join(typeRoot, file),
|
|
691
|
-
id,
|
|
692
|
-
});
|
|
693
|
-
}
|
|
694
|
-
catch (err) {
|
|
695
|
-
if (isStaleTaskError(err))
|
|
696
|
-
stale.push(id);
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
return stale;
|
|
700
|
-
}
|
|
701
|
-
// Old-spelling match keys for the default-task commands whose retired
|
|
702
|
-
// `--auto-accept safe` flag is dropped in the `replacement`. Each id lists BOTH
|
|
703
|
-
// deprecated spellings that can survive in a v2 task file: the original
|
|
704
|
-
// `--profile X --auto-accept safe` and the intermediate `--strategy X
|
|
705
|
-
// --auto-accept safe` (what the 0.8→0.9 migration writes — parser normalization
|
|
706
|
-
// only rewrites `--profile`→`--strategy` for legacy v1 files, so a stored v2
|
|
707
|
-
// file keeps whichever spelling it was minted with). Without the `--strategy`
|
|
708
|
-
// variant, migrated default tasks match no key and warn on every run until 0.10
|
|
709
|
-
// (chunk-6 ledger residue).
|
|
710
|
-
const STALE_GENERATED_COMMANDS = {
|
|
711
|
-
"akm-improve-frequent": {
|
|
712
|
-
commands: [
|
|
713
|
-
"akm improve --profile frequent --auto-accept safe",
|
|
714
|
-
"akm improve --strategy frequent --auto-accept safe",
|
|
715
|
-
],
|
|
716
|
-
replacement: "akm improve --strategy frequent",
|
|
717
|
-
},
|
|
718
|
-
"akm-improve-consolidate": {
|
|
719
|
-
commands: [
|
|
720
|
-
"akm improve --profile consolidate --auto-accept safe",
|
|
721
|
-
"akm improve --strategy consolidate --auto-accept safe",
|
|
722
|
-
],
|
|
723
|
-
replacement: "akm improve --strategy consolidate",
|
|
724
|
-
},
|
|
725
|
-
"akm-improve-nightly": {
|
|
726
|
-
commands: [
|
|
727
|
-
"akm improve --profile thorough --auto-accept safe",
|
|
728
|
-
"akm improve --strategy thorough --auto-accept safe",
|
|
729
|
-
],
|
|
730
|
-
replacement: "akm improve --strategy thorough",
|
|
731
|
-
},
|
|
732
|
-
"akm-improve-catchup": {
|
|
733
|
-
commands: ["akm improve --profile catchup --auto-accept safe", "akm improve --strategy catchup --auto-accept safe"],
|
|
734
|
-
replacement: "akm improve --strategy catchup",
|
|
735
|
-
},
|
|
736
|
-
"akm-graph-refresh-weekly": {
|
|
737
|
-
commands: [
|
|
738
|
-
"akm improve --profile graph-refresh --auto-accept safe",
|
|
739
|
-
"akm improve --strategy graph-refresh --auto-accept safe",
|
|
740
|
-
],
|
|
741
|
-
replacement: "akm improve --strategy graph-refresh",
|
|
742
|
-
},
|
|
743
|
-
};
|
|
744
|
-
function collectStaleGeneratedCommands() {
|
|
745
|
-
const typeRoot = path.join(resolveStashDir(), "tasks");
|
|
746
|
-
if (!fs.existsSync(typeRoot))
|
|
747
|
-
return [];
|
|
748
|
-
const stale = [];
|
|
749
|
-
for (const [id, expected] of Object.entries(STALE_GENERATED_COMMANDS)) {
|
|
750
|
-
const filePath = path.join(typeRoot, `${id}.yml`);
|
|
751
|
-
if (!fs.existsSync(filePath))
|
|
752
|
-
continue;
|
|
753
|
-
try {
|
|
754
|
-
const task = parseTaskDocument({ yaml: fs.readFileSync(filePath, "utf8"), filePath, id });
|
|
755
|
-
if (task.target.kind === "command" && expected.commands.includes(task.target.cmd.join(" "))) {
|
|
756
|
-
stale.push({ id, replacement: expected.replacement });
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
catch {
|
|
760
|
-
// Stale-schema reporting owns files that cannot be parsed.
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
return stale;
|
|
764
|
-
}
|
|
765
628
|
function parseJsonObjectArg(raw) {
|
|
766
629
|
let parsed;
|
|
767
630
|
try {
|
|
@@ -799,11 +662,9 @@ export function setEnabledInYaml(yaml, enabled) {
|
|
|
799
662
|
// Re-exported so tests can verify the validator path directly.
|
|
800
663
|
// Re-export error classes consumed by callers that want to instanceof-check.
|
|
801
664
|
// Re-export this so the CLI can decide what process exit code to use after
|
|
802
|
-
// `akm
|
|
665
|
+
// `akm task run` completes.
|
|
803
666
|
export { ConfigError, exitCodeForStatus, NotFoundError, parseTaskDocument, UsageError };
|
|
804
|
-
// Accept a bare task id or the canonical
|
|
805
|
-
// (ref-grammar decision D-R3). The pre-0.9.0 `task:<id>` colon grammar is
|
|
806
|
-
// retired and rejected loudly — it appears NOWHERE after the flip.
|
|
667
|
+
// Accept a bare task id or the canonical `[bundle//]tasks/<id>` ref.
|
|
807
668
|
export function parseTaskRef(input) {
|
|
808
669
|
const trimmed = input.trim();
|
|
809
670
|
// Canonical conceptId form: `[bundle//]tasks/<id>`. A `/` unambiguously marks
|
|
@@ -821,11 +682,5 @@ export function parseTaskRef(input) {
|
|
|
821
682
|
}
|
|
822
683
|
throw new UsageError(`Expected a task id or tasks/<id> ref, got "${input}".`, "INVALID_FLAG_VALUE");
|
|
823
684
|
}
|
|
824
|
-
// Legacy `task:<id>` grammar is gone in 0.9.0 (D-R3) — reject it with a typed
|
|
825
|
-
// error that names the new form so muscle-memory callers get a clear fix.
|
|
826
|
-
if (trimmed.includes(":")) {
|
|
827
|
-
const legacyName = trimmed.slice(trimmed.indexOf(":") + 1);
|
|
828
|
-
throw new UsageError(`The \`task:<id>\` ref grammar was removed in 0.9.0 — use the bare id or \`tasks/${legacyName || "<id>"}\`.`, "INVALID_FLAG_VALUE");
|
|
829
|
-
}
|
|
830
685
|
return { id: normaliseTaskId(trimmed) };
|
|
831
686
|
}
|