akm-cli 0.9.0-rc.9 → 0.9.1-beta.1
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 +1770 -44
- package/README.md +38 -47
- package/SECURITY.md +14 -1
- package/STABILITY.md +499 -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 +74 -89
- 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 +148 -88
- 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/llm-usage.js +2 -1
- 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 +4 -122
- package/dist/commands/health/task-runs.js +3 -67
- package/dist/commands/health/types-improve.js +7 -0
- package/dist/commands/health.js +160 -35
- 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 +11 -6
- package/dist/commands/improve/distill.js +58 -69
- package/dist/commands/improve/eligibility.js +101 -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 +254 -101
- package/dist/commands/improve/locks.js +7 -0
- 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 +14 -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/run-context.js +5 -0
- package/dist/commands/improve/salience.js +11 -122
- package/dist/commands/improve/session-asset.js +4 -0
- package/dist/commands/improve/source-identity.js +10 -38
- package/dist/commands/lint/base-linter.js +51 -131
- package/dist/commands/lint/env-key-rules.js +31 -47
- package/dist/commands/lint/index.js +444 -88
- package/dist/commands/lint/types.js +22 -1
- 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 +718 -279
- 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 +30 -7
- package/dist/commands/sources/init.js +12 -12
- package/dist/commands/sources/installed-stashes.js +387 -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 +284 -423
- package/dist/commands/workflow-cli.js +181 -450
- package/dist/core/abort-deadline.js +28 -0
- package/dist/core/adapter/adapters/agent-skills-adapter.js +67 -5
- package/dist/core/adapter/adapters/akm-adapter.js +60 -38
- package/dist/core/adapter/adapters/akm-lint.js +109 -42
- package/dist/core/adapter/adapters/akm-metadata.js +15 -44
- package/dist/core/adapter/adapters/akm-task-adapter.js +54 -31
- 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 +13 -6
- 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 +29 -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 +218 -44
- package/dist/core/concurrent.js +32 -0
- 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 +14 -3
- package/dist/core/env-secret-ref.js +76 -46
- package/dist/core/errors.js +43 -12
- package/dist/core/events.js +76 -152
- package/dist/core/file-change.js +6 -5
- package/dist/core/file-lock.js +7 -1
- package/dist/core/fs-txn.js +83 -7
- package/dist/core/git-message.js +2 -2
- package/dist/core/improve-result.js +6 -100
- package/dist/core/json-schema.js +327 -9
- package/dist/core/lesson-lint.js +1 -17
- package/dist/core/logs-db.js +2 -1
- package/dist/core/loopback.js +89 -0
- package/dist/core/migration-operation.js +33 -2
- package/dist/core/mutation-target.js +78 -0
- package/dist/core/parse.js +4 -1
- package/dist/core/path-access.js +107 -0
- package/dist/core/paths.js +17 -20
- package/dist/core/recognition-util.js +12 -14
- package/dist/core/redaction.js +98 -17
- package/dist/core/spawn-env.js +234 -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-scope.js +134 -0
- package/dist/core/state-db.js +5 -14
- package/dist/core/structured.js +1 -1
- package/dist/core/subprocess.js +182 -36
- 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-provenance.js +85 -0
- package/dist/core/write-source.js +778 -95
- package/dist/indexer/bundle-identity-guard.js +3 -2
- package/dist/indexer/db/graph-db.js +17 -30
- package/dist/indexer/ensure-index.js +11 -3
- 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 +33 -24
- package/dist/indexer/indexer.js +551 -253
- package/dist/indexer/installations.js +14 -96
- package/dist/indexer/passes/dir-staleness.js +16 -9
- package/dist/indexer/passes/memory-inference.js +15 -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 +107 -69
- 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/engine-resolution.js +24 -11
- package/dist/integrations/agent/model-aliases.js +1 -1
- package/dist/integrations/agent/profiles.js +23 -1
- 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 +20 -93
- 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 +132 -8
- 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 +56 -0
- package/dist/{migrate-storage-node.mjs → output/text/lint.js} +2 -5
- 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/{save.js → sync.js} +2 -2
- 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 +60972 -0
- package/dist/scripts/akm-migrate.js +60310 -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 +414 -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 +686 -282
- package/dist/sources/snapshot-fetchers/x.js +910 -0
- package/dist/storage/database.js +13 -1
- package/dist/storage/engines/sqlite-migrations.js +23 -111
- package/dist/storage/managed-db.js +21 -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 +44 -8
- package/dist/storage/repositories/index-entries-repository.js +157 -241
- 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 +118 -53
- 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/log-redaction.js +156 -0
- package/dist/tasks/parser.js +87 -160
- package/dist/tasks/resolve-akm-bin.js +137 -59
- package/dist/tasks/runner.js +209 -44
- package/dist/tasks/scheduler-invocation.js +220 -10
- package/dist/tasks/schema.js +89 -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/concurrency-policy.js +95 -1
- package/dist/workflows/exec/dispatch-redaction.js +114 -0
- package/dist/workflows/exec/exec-unit.js +542 -0
- package/dist/workflows/exec/frozen-judge.js +130 -32
- package/dist/workflows/exec/native-executor.js +513 -274
- package/dist/workflows/exec/param-secrets.js +12 -11
- package/dist/workflows/exec/run-workflow.js +496 -237
- package/dist/workflows/exec/step-work.js +616 -412
- package/dist/workflows/exec/unit-dispatch.js +34 -0
- package/dist/workflows/exec/unit-writer.js +53 -13
- package/dist/workflows/exec/worktree.js +454 -41
- package/dist/workflows/ir/compile.js +166 -271
- package/dist/workflows/ir/freeze.js +119 -42
- package/dist/workflows/ir/params.js +135 -11
- package/dist/workflows/ir/plan-hash.js +1 -1
- package/dist/workflows/ir/schema.js +129 -45
- package/dist/workflows/parser.js +1070 -307
- package/dist/workflows/program/expressions.js +20 -208
- package/dist/workflows/program/schema.js +31 -10
- package/dist/workflows/renderer.js +123 -68
- package/dist/workflows/resource-limits.js +184 -0
- package/dist/workflows/runtime/checkin.js +3 -3
- package/dist/workflows/runtime/plan-classifier.js +16 -75
- package/dist/workflows/runtime/runs.js +331 -132
- 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 +47 -38
- package/dist/workflows/validator.js +21 -62
- package/docs/README.md +110 -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 +209 -27
- package/docs/migration/v0.7-to-v0.8.md +48 -48
- package/docs/migration/v0.8-to-v0.9.md +567 -209
- package/docs/migration/v0.9.0-troubleshooting.md +561 -0
- package/docs/reference/README.md +19 -0
- package/docs/reference/bundle-types.md +19 -0
- package/docs/reference/cli.md +2342 -0
- package/docs/reference/configuration.md +371 -0
- package/docs/reference/data-and-telemetry.md +129 -46
- package/docs/reference/supported-formats.md +50 -0
- package/docs/reference/workflow-schema.md +1014 -0
- package/docs/reference/workflows.md +51 -0
- package/package.json +31 -13
- package/schemas/akm-asset-envelope.json +93 -0
- package/schemas/akm-config.json +89 -128
- package/schemas/akm-task.json +27 -5
- package/schemas/akm-workflow.json +157 -81
- 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);
|
|
@@ -38,8 +40,12 @@ export async function akmTasksAdd(input, deps = {}) {
|
|
|
38
40
|
if (targetCount !== 1) {
|
|
39
41
|
throw new UsageError("Pass exactly one of --workflow <ref>, --prompt <asset-ref|./file.md|text>, or --command <shell-command>.", "INVALID_FLAG_VALUE");
|
|
40
42
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
// `--timeout-ms` IS valid on a workflow task: it is the whole-run bound the
|
|
44
|
+
// task runner turns into an abort signal (issue 11), the same one
|
|
45
|
+
// `akm workflow run --timeout` applies interactively. Engine and model stay
|
|
46
|
+
// prompt-only — a workflow's engines come from its frozen plan.
|
|
47
|
+
if (input.workflow && (input.engine !== undefined || input.model !== undefined)) {
|
|
48
|
+
throw new UsageError("Workflow tasks accept --params and --timeout-ms; engine and model are prompt-task fields.", "INVALID_FLAG_VALUE");
|
|
43
49
|
}
|
|
44
50
|
if (hasCommand && (input.engine !== undefined || input.model !== undefined)) {
|
|
45
51
|
throw new UsageError("Command tasks accept --timeout-ms but not --engine or --model.", "INVALID_FLAG_VALUE");
|
|
@@ -49,15 +55,22 @@ export async function akmTasksAdd(input, deps = {}) {
|
|
|
49
55
|
// from `deps.backend` when present — retiring the `_setBackendsForTests` seam.
|
|
50
56
|
const backend = deps.backend?.name ?? backendNameForPlatform();
|
|
51
57
|
parseSchedule(input.schedule, backend);
|
|
52
|
-
const
|
|
53
|
-
const
|
|
58
|
+
const bundle = resolveTaskBundle(input.target, { requireWritable: true });
|
|
59
|
+
const writeTarget = bundle.resolved;
|
|
60
|
+
const stashDir = bundle.stashDir;
|
|
61
|
+
const installOpts = bundle.installTarget !== undefined ? { target: bundle.installTarget } : undefined;
|
|
54
62
|
const typeRoot = path.join(stashDir, "tasks");
|
|
55
63
|
const assetPath = assetPathForName("task", typeRoot, id);
|
|
56
64
|
if (!isWithin(assetPath, typeRoot)) {
|
|
57
65
|
throw new UsageError(`Resolved task path escapes the stash: "${id}".`, "PATH_ESCAPE_VIOLATION");
|
|
58
66
|
}
|
|
59
|
-
|
|
60
|
-
|
|
67
|
+
// Pre-0.8.0 tasks were markdown; the 0.8.0 cutover moved them to pure YAML
|
|
68
|
+
// (see the tasks dir rule in src/indexer/walk/matchers.ts). A leftover
|
|
69
|
+
// `<id>.md` still names the same task, so creating `<id>.yml` beside it
|
|
70
|
+
// must collide loudly rather than silently minting a duplicate.
|
|
71
|
+
const legacyAssetPath = path.join(typeRoot, `${id}.md`);
|
|
72
|
+
if ((fs.existsSync(assetPath) || fs.existsSync(legacyAssetPath)) && !input.force) {
|
|
73
|
+
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
74
|
}
|
|
62
75
|
const yaml = renderTaskYaml({
|
|
63
76
|
id,
|
|
@@ -77,9 +90,6 @@ export async function akmTasksAdd(input, deps = {}) {
|
|
|
77
90
|
});
|
|
78
91
|
const task = parseTaskDocument({ yaml, filePath: assetPath, id });
|
|
79
92
|
await validateTaskDocument(task, { backend, stashDir });
|
|
80
|
-
const obsoleteReason = obsoleteBackupTaskReason(task);
|
|
81
|
-
if (obsoleteReason)
|
|
82
|
-
throw new UsageError(obsoleteReason, "INVALID_FLAG_VALUE");
|
|
83
93
|
const ref = taskAssetRef(id);
|
|
84
94
|
const previousYaml = fs.existsSync(assetPath) ? fs.readFileSync(assetPath, "utf8") : undefined;
|
|
85
95
|
let previousTask;
|
|
@@ -96,15 +106,19 @@ export async function akmTasksAdd(input, deps = {}) {
|
|
|
96
106
|
const writeAsset = deps.writeAsset ?? writeAssetToSource;
|
|
97
107
|
const deleteAsset = deps.deleteAsset ?? deleteAssetFromSource;
|
|
98
108
|
const commitBoundary = deps.commitBoundary ?? commitWriteTargetBoundary;
|
|
99
|
-
const
|
|
109
|
+
const installedEntries = await sched.list();
|
|
110
|
+
assertNoForeignSchedule(installedEntries, id, bundle.installTarget);
|
|
111
|
+
const wasInstalled = previousYaml !== undefined && installedEntries.some((entry) => entry.id === id);
|
|
112
|
+
const installedEntry = installedEntries.find((entry) => entry.id === id);
|
|
113
|
+
const runtimeOpts = schedulerInstallOptions(installOpts, installedEntry, deps, installedEntry ? false : input.rebind === true, `create scheduler entry for task "${id}"`);
|
|
100
114
|
let sourceRestoreArmed = false;
|
|
101
115
|
let installSucceeded = false;
|
|
102
116
|
try {
|
|
103
117
|
sourceRestoreArmed = true;
|
|
104
|
-
await writeAsset(
|
|
105
|
-
await sched.install(task);
|
|
118
|
+
await writeAsset(writeTarget.source, writeTarget.config, ref, yaml);
|
|
119
|
+
await sched.install(task, runtimeOpts);
|
|
106
120
|
installSucceeded = true;
|
|
107
|
-
commitBoundary(
|
|
121
|
+
commitBoundary(writeTarget, `Update tasks/${id}`);
|
|
108
122
|
}
|
|
109
123
|
catch (err) {
|
|
110
124
|
const rollbackErrors = [];
|
|
@@ -113,12 +127,12 @@ export async function akmTasksAdd(input, deps = {}) {
|
|
|
113
127
|
try {
|
|
114
128
|
if (previousYaml === undefined) {
|
|
115
129
|
if (fs.existsSync(assetPath)) {
|
|
116
|
-
await deleteAsset(
|
|
130
|
+
await deleteAsset(writeTarget.source, writeTarget.config, ref);
|
|
117
131
|
sourceRestored = true;
|
|
118
132
|
}
|
|
119
133
|
}
|
|
120
134
|
else {
|
|
121
|
-
await restoreTaskSourceBytes(writeAsset,
|
|
135
|
+
await restoreTaskSourceBytes(writeAsset, writeTarget.source, writeTarget.config, ref, assetPath, previousYaml);
|
|
122
136
|
sourceRestored = true;
|
|
123
137
|
}
|
|
124
138
|
}
|
|
@@ -136,7 +150,7 @@ export async function akmTasksAdd(input, deps = {}) {
|
|
|
136
150
|
}
|
|
137
151
|
else if (installSucceeded && previousTask) {
|
|
138
152
|
try {
|
|
139
|
-
await sched.install(previousTask);
|
|
153
|
+
await sched.install(previousTask, runtimeOpts);
|
|
140
154
|
}
|
|
141
155
|
catch (rollbackError) {
|
|
142
156
|
rollbackErrors.push(rollbackError);
|
|
@@ -162,7 +176,7 @@ export async function akmTasksAdd(input, deps = {}) {
|
|
|
162
176
|
}
|
|
163
177
|
if (sourceRestored) {
|
|
164
178
|
try {
|
|
165
|
-
commitBoundary(
|
|
179
|
+
commitBoundary(writeTarget, `Restore tasks/${id}`);
|
|
166
180
|
}
|
|
167
181
|
catch (rollbackError) {
|
|
168
182
|
rollbackErrors.push(rollbackError);
|
|
@@ -178,252 +192,13 @@ export async function akmTasksAdd(input, deps = {}) {
|
|
|
178
192
|
id,
|
|
179
193
|
ref: conceptIdFromTypeName("task", id),
|
|
180
194
|
path: assetPath,
|
|
181
|
-
stashDir,
|
|
195
|
+
bundleDir: stashDir,
|
|
182
196
|
schedule: task.schedule,
|
|
183
197
|
enabled: task.enabled,
|
|
184
198
|
backend,
|
|
185
199
|
target: task.target,
|
|
186
200
|
};
|
|
187
201
|
}
|
|
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
202
|
export async function akmTasksRun(id, options = {}) {
|
|
428
203
|
const startedAt = new Date();
|
|
429
204
|
let normalised;
|
|
@@ -441,7 +216,13 @@ export async function akmTasksRun(id, options = {}) {
|
|
|
441
216
|
}
|
|
442
217
|
let stashDir;
|
|
443
218
|
try {
|
|
444
|
-
|
|
219
|
+
// No --bundle uses the primary stash. With --bundle, resolve (read-only)
|
|
220
|
+
// the named bundle so the task file and
|
|
221
|
+
// its relative asset refs load from that bundle's path.
|
|
222
|
+
stashDir =
|
|
223
|
+
options.target !== undefined
|
|
224
|
+
? resolveWriteTarget(loadConfig(), options.target, { requireWritable: false }).source.path
|
|
225
|
+
: resolveStashDir();
|
|
445
226
|
}
|
|
446
227
|
catch (failure) {
|
|
447
228
|
recordTaskAttemptFailure({
|
|
@@ -452,9 +233,6 @@ export async function akmTasksRun(id, options = {}) {
|
|
|
452
233
|
});
|
|
453
234
|
throw failure;
|
|
454
235
|
}
|
|
455
|
-
const typeRoot = path.join(stashDir, "tasks");
|
|
456
|
-
if (fs.existsSync(typeRoot))
|
|
457
|
-
warnLegacyMdTaskFiles(typeRoot);
|
|
458
236
|
const result = await runTask(normalised, { stashDir, scheduled: options.scheduled === true });
|
|
459
237
|
const exitCode = result.status === "failed" && result.target.kind === "command" && result.detail?.exitCode === 78
|
|
460
238
|
? 78
|
|
@@ -468,26 +246,32 @@ export async function akmTasksRun(id, options = {}) {
|
|
|
468
246
|
export async function akmTasksHistory(input) {
|
|
469
247
|
const limit = input.limit !== undefined && input.limit > 0 ? input.limit : 50;
|
|
470
248
|
const id = input.id ? normaliseTaskId(input.id) : undefined;
|
|
471
|
-
|
|
472
|
-
const typeRoot = path.join(stashDir, "tasks");
|
|
473
|
-
if (fs.existsSync(typeRoot))
|
|
474
|
-
warnLegacyMdTaskFiles(typeRoot);
|
|
249
|
+
// History rows are keyed by task id in state.db, not per bundle.
|
|
475
250
|
return { rows: readTaskHistory({ id, limit }) };
|
|
476
251
|
}
|
|
477
252
|
/**
|
|
478
|
-
* Reconcile the on-disk task files with the OS scheduler.
|
|
253
|
+
* Reconcile the on-disk task files of ONE bundle with the OS scheduler.
|
|
479
254
|
* • install missing tasks (after validating them — invalid files are
|
|
480
255
|
* skipped with a per-task reason rather than aborting the whole sync)
|
|
481
256
|
* • reinstall tasks whose schedule or enabled state changed in the .yml
|
|
482
257
|
* (drift detected by comparing the backend's installed signature against
|
|
483
258
|
* the signature the current definition would produce)
|
|
484
259
|
* • remove orphan scheduler entries that no longer have a backing file
|
|
260
|
+
*
|
|
261
|
+
* `--bundle <bundle>` scopes the reconciliation to that bundle: the file set is
|
|
262
|
+
* the bundle's `tasks/*.yml` and — crucially — the scheduler entries considered
|
|
263
|
+
* are ONLY those attributed to the same bundle (parsed from the installed
|
|
264
|
+
* `--bundle` token; absent ⇒ primary). This is the security boundary that keeps
|
|
265
|
+
* "registering a bundle never activates code": a plain (primary) sync never
|
|
266
|
+
* installs from, updates, or removes another bundle's entries, and sync never
|
|
267
|
+
* scans all bundles. Activation happens only through explicit `add --bundle`
|
|
268
|
+
* (or `sync --bundle` on a bundle whose task files are already present).
|
|
485
269
|
*/
|
|
486
|
-
export async function akmTasksSync(deps = {}) {
|
|
487
|
-
const stashDir =
|
|
270
|
+
export async function akmTasksSync(deps = {}, bundleTarget, options = {}) {
|
|
271
|
+
const stashDir = resolveTaskInspectDir(bundleTarget);
|
|
272
|
+
// Primary-bundle scheduler entries omit --bundle; other bundles carry it.
|
|
273
|
+
const syncTarget = bundleTarget !== undefined && !isPrimaryStashPath(stashDir) ? bundleTarget : undefined;
|
|
488
274
|
const typeRoot = path.join(stashDir, "tasks");
|
|
489
|
-
if (fs.existsSync(typeRoot))
|
|
490
|
-
warnLegacyMdTaskFiles(typeRoot);
|
|
491
275
|
const fileIds = fs.existsSync(typeRoot)
|
|
492
276
|
? fs
|
|
493
277
|
.readdirSync(typeRoot)
|
|
@@ -496,22 +280,30 @@ export async function akmTasksSync(deps = {}) {
|
|
|
496
280
|
: [];
|
|
497
281
|
const sched = deps.backend ?? selectBackend();
|
|
498
282
|
const backend = sched.name;
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
283
|
+
const installOpts = syncTarget !== undefined ? { target: syncTarget } : undefined;
|
|
284
|
+
const allEntries = options.rebind && sched.listForRebind ? await sched.listForRebind() : await sched.list();
|
|
285
|
+
// Attribution filter: only entries installed from THIS bundle are reconciled
|
|
286
|
+
// here. Entries carrying a `--bundle` for a different bundle are invisible to
|
|
287
|
+
// this sync — never removed, never touched.
|
|
288
|
+
const present = new Map(allEntries.filter((t) => sameBundle(t.target, syncTarget)).map((t) => [t.id, t]));
|
|
502
289
|
const installed = [];
|
|
503
290
|
const updated = [];
|
|
504
291
|
const unchanged = [];
|
|
505
292
|
const skipped = [];
|
|
293
|
+
const warnings = [];
|
|
506
294
|
for (const id of fileIds) {
|
|
507
295
|
const filePath = path.join(typeRoot, `${id}.yml`);
|
|
508
296
|
let task;
|
|
509
297
|
try {
|
|
510
298
|
task = parseTaskDocument({ yaml: fs.readFileSync(filePath, "utf8"), filePath, id });
|
|
511
299
|
await validateTaskDocument(task, { backend, stashDir });
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
300
|
+
// A bare id can only be scheduled from ONE bundle at a time (scheduler ids
|
|
301
|
+
// are never namespaced). If this id is already scheduled from a different
|
|
302
|
+
// bundle, refuse rather than clobber it — surface it as a per-task skip so
|
|
303
|
+
// the rest of the sync still proceeds.
|
|
304
|
+
const foreign = allEntries.find((e) => e.id === id && !sameBundle(e.target, syncTarget));
|
|
305
|
+
if (foreign)
|
|
306
|
+
throw new UsageError(foreignScheduleMessage(id, foreign.target), "RESOURCE_ALREADY_EXISTS");
|
|
515
307
|
}
|
|
516
308
|
catch (err) {
|
|
517
309
|
skipped.push({ id, reason: err instanceof Error ? err.message : String(err) });
|
|
@@ -531,8 +323,14 @@ export async function akmTasksSync(deps = {}) {
|
|
|
531
323
|
continue;
|
|
532
324
|
}
|
|
533
325
|
if (!present.has(id)) {
|
|
534
|
-
|
|
535
|
-
|
|
326
|
+
try {
|
|
327
|
+
const runtimeOpts = schedulerInstallOptions(installOpts, undefined, deps, options.rebind === true, `create scheduler entry for task "${id}"`, warnings);
|
|
328
|
+
await sched.install(task, runtimeOpts);
|
|
329
|
+
installed.push(id);
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
skipped.push({ id, reason: error instanceof Error ? error.message : String(error) });
|
|
333
|
+
}
|
|
536
334
|
continue;
|
|
537
335
|
}
|
|
538
336
|
// Already installed — reconcile against the current definition. Compare the
|
|
@@ -540,13 +338,15 @@ export async function akmTasksSync(deps = {}) {
|
|
|
540
338
|
// When the backend can't produce a signature (no expectedSignature, or it
|
|
541
339
|
// didn't record one), reinstall unconditionally — install() is idempotent,
|
|
542
340
|
// so the cost is one crontab write and correctness is guaranteed.
|
|
543
|
-
const
|
|
544
|
-
const
|
|
341
|
+
const installedEntry = present.get(id);
|
|
342
|
+
const runtimeOpts = schedulerInstallOptions(installOpts, options.rebind ? undefined : installedEntry, deps, options.rebind === true, `rebind scheduler entry for task "${id}"`, warnings);
|
|
343
|
+
const installedSig = installedEntry.signature;
|
|
344
|
+
const expectedSig = sched.expectedSignature?.(task, runtimeOpts);
|
|
545
345
|
if (installedSig !== undefined && expectedSig !== undefined && installedSig === expectedSig) {
|
|
546
346
|
unchanged.push(id);
|
|
547
347
|
}
|
|
548
348
|
else {
|
|
549
|
-
await sched.install(task);
|
|
349
|
+
await sched.install(task, runtimeOpts);
|
|
550
350
|
updated.push(id);
|
|
551
351
|
}
|
|
552
352
|
}
|
|
@@ -557,48 +357,69 @@ export async function akmTasksSync(deps = {}) {
|
|
|
557
357
|
removed.push(installedId);
|
|
558
358
|
}
|
|
559
359
|
}
|
|
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.");
|
|
360
|
+
return {
|
|
361
|
+
installed,
|
|
362
|
+
updated,
|
|
363
|
+
removed,
|
|
364
|
+
unchanged,
|
|
365
|
+
skipped,
|
|
366
|
+
backend: sched.name,
|
|
367
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
368
|
+
};
|
|
574
369
|
}
|
|
575
|
-
export async function akmTasksDoctor() {
|
|
370
|
+
export async function akmTasksDoctor(deps = {}) {
|
|
576
371
|
const warnings = [];
|
|
577
|
-
let invocation = {
|
|
372
|
+
let invocation = {
|
|
373
|
+
argv: [],
|
|
374
|
+
via: "unresolved",
|
|
375
|
+
};
|
|
578
376
|
try {
|
|
579
|
-
const r = resolveAkmInvocation();
|
|
580
|
-
invocation = { argv: r.argv, via: r.via };
|
|
377
|
+
const r = (deps.resolveInvocation ?? resolveAkmInvocation)();
|
|
378
|
+
invocation = { argv: r.argv, via: r.via, kind: r.kind, eligible: r.eligible };
|
|
581
379
|
}
|
|
582
380
|
catch (err) {
|
|
583
381
|
warnings.push(err instanceof Error ? err.message : String(err));
|
|
584
382
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
383
|
+
const skipNativeInspection = process.env.BUN_TEST === "1" && !deps.backend;
|
|
384
|
+
const sched = deps.backend ?? (skipNativeInspection ? undefined : selectBackend());
|
|
385
|
+
const backend = sched?.name ?? backendNameForPlatform();
|
|
386
|
+
let installed = [];
|
|
387
|
+
if (skipNativeInspection) {
|
|
388
|
+
warnings.push("Native scheduler inspection is skipped inside the bun test harness.");
|
|
590
389
|
}
|
|
591
|
-
|
|
592
|
-
|
|
390
|
+
else {
|
|
391
|
+
try {
|
|
392
|
+
installed = await sched.list();
|
|
393
|
+
}
|
|
394
|
+
catch (error) {
|
|
395
|
+
warnings.push(`Unable to inspect installed ${backend} definitions: ${error instanceof Error ? error.message : String(error)}`);
|
|
396
|
+
}
|
|
593
397
|
}
|
|
594
|
-
const
|
|
595
|
-
|
|
398
|
+
const bindings = groupInstalledBindings(installed, invocation);
|
|
399
|
+
// Report the EFFECTIVE engine view — the same one the runner resolves —
|
|
400
|
+
// so doctor never says "no engine" on an install where tasks actually run.
|
|
401
|
+
const { config } = withEngineFallback(loadConfig());
|
|
596
402
|
const defaultEngine = config.defaults?.engine;
|
|
597
403
|
const engines = Object.keys(config.engines ?? {});
|
|
598
404
|
// §6.1: surface the effective triage settings for the default improve
|
|
599
405
|
// strategy. The struct is a fixed shape, so this is a deliberate addition.
|
|
600
406
|
const improveStrategyName = typeof config.defaults?.improveStrategy === "string" ? config.defaults.improveStrategy : "default";
|
|
601
|
-
|
|
407
|
+
// D8 — report the EFFECTIVE strategy, not the raw one. Resolving the raw
|
|
408
|
+
// strategy here would report `applyMode: "promote"` for a promote strategy
|
|
409
|
+
// under a review-first config, while the run actually uses "queue" — a doctor
|
|
410
|
+
// command lying about the thing it exists to diagnose.
|
|
411
|
+
const rawStrategy = resolveImproveStrategy(config.defaults?.improveStrategy, config).config;
|
|
412
|
+
const { config: effectiveStrategy, gated } = applyAutonomyGate(rawStrategy, config);
|
|
413
|
+
const autonomyEnabled = isImproveAutonomyEnabled(config);
|
|
414
|
+
// Memory cleanup has no strategy flag to downgrade, so add that direct lane
|
|
415
|
+
// to the strategy-derived gate report.
|
|
416
|
+
const allGated = autonomyEnabled ? [] : [...gated, ...describeGatedLanes(configuredDirectAutonomyLanes())];
|
|
417
|
+
const improveAutonomy = {
|
|
418
|
+
enabled: autonomyEnabled,
|
|
419
|
+
configKey: IMPROVE_AUTONOMY_CONFIG_KEY,
|
|
420
|
+
gatedLanes: allGated.map((entry) => ({ lane: entry.lane, reason: entry.reason })),
|
|
421
|
+
};
|
|
422
|
+
const triage = effectiveStrategy.processes?.triage;
|
|
602
423
|
const improveTriage = triage
|
|
603
424
|
? {
|
|
604
425
|
defaultStrategy: improveStrategyName,
|
|
@@ -610,17 +431,111 @@ export async function akmTasksDoctor() {
|
|
|
610
431
|
return {
|
|
611
432
|
backend,
|
|
612
433
|
akm: invocation,
|
|
434
|
+
caller: invocation,
|
|
435
|
+
bindings,
|
|
436
|
+
...(bindings.some((binding) => !binding.status.includes("ok"))
|
|
437
|
+
? { remediation: "akm task sync --rebind" }
|
|
438
|
+
: {}),
|
|
613
439
|
logDir: getTaskLogDir(),
|
|
614
440
|
historyDir: getTaskHistoryDir(),
|
|
615
441
|
engine: { defaultEngine, available: engines },
|
|
616
|
-
stale: collectStaleTaskIds(),
|
|
617
|
-
staleGeneratedCommands: collectStaleGeneratedCommands(),
|
|
618
442
|
scheduleSubset: SCHEDULE_SUPPORTED_SUBSET_HINT,
|
|
619
443
|
warnings,
|
|
444
|
+
improveAutonomy,
|
|
620
445
|
...(improveTriage ? { improveTriage } : {}),
|
|
621
446
|
};
|
|
622
447
|
}
|
|
623
448
|
// ── helpers ─────────────────────────────────────────────────────────────────
|
|
449
|
+
function schedulerInstallOptions(base, installed, deps, explicitRebind, operation, warnings = []) {
|
|
450
|
+
if (installed && !explicitRebind) {
|
|
451
|
+
return {
|
|
452
|
+
...base,
|
|
453
|
+
binding: installed.binding,
|
|
454
|
+
contextPath: installed.contextPath,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
// Injected backends can own their default runtime unless a resolver is supplied.
|
|
458
|
+
if (deps.backend && !deps.schedulerRuntime)
|
|
459
|
+
return base;
|
|
460
|
+
const runtime = deps.schedulerRuntime?.() ?? prepareSchedulerRuntime(explicitRebind, operation);
|
|
461
|
+
// --rebind bypasses the eligibility refusal in prepareSchedulerRuntime; warn once
|
|
462
|
+
// per sync run rather than silently writing a mutable/unproven binary into cron.
|
|
463
|
+
if (explicitRebind && runtime.eligible === false && warnings.length === 0) {
|
|
464
|
+
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\`.`);
|
|
465
|
+
}
|
|
466
|
+
return { ...base, binding: runtime.binding, contextPath: runtime.contextPath };
|
|
467
|
+
}
|
|
468
|
+
export function prepareSchedulerRuntime(explicitRebind, operation, deps = {}) {
|
|
469
|
+
const invocation = (deps.resolveInvocation ?? resolveAkmInvocation)();
|
|
470
|
+
if (!invocation.eligible && !explicitRebind) {
|
|
471
|
+
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.");
|
|
472
|
+
}
|
|
473
|
+
const contextPath = (deps.writeDescriptor ?? writeSchedulerContextDescriptor)(schedulerContextDescriptor());
|
|
474
|
+
return { binding: invocation.argv, contextPath, eligible: invocation.eligible, kind: invocation.kind };
|
|
475
|
+
}
|
|
476
|
+
function groupInstalledBindings(entries, invocation) {
|
|
477
|
+
const groups = new Map();
|
|
478
|
+
for (const entry of entries) {
|
|
479
|
+
const argv = entry.binding;
|
|
480
|
+
const status = inspectInstalledBinding(entry, invocation);
|
|
481
|
+
const key = JSON.stringify([argv, entry.contextPath, status]);
|
|
482
|
+
const existing = groups.get(key);
|
|
483
|
+
if (existing) {
|
|
484
|
+
existing.taskIds.push(entry.id);
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
groups.set(key, {
|
|
488
|
+
argv,
|
|
489
|
+
contextPath: entry.contextPath,
|
|
490
|
+
taskIds: [entry.id],
|
|
491
|
+
status,
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
return [...groups.values()].map((group) => ({ ...group, taskIds: group.taskIds.sort() }));
|
|
495
|
+
}
|
|
496
|
+
function inspectInstalledBinding(entry, invocation) {
|
|
497
|
+
const status = [];
|
|
498
|
+
const binding = entry.binding;
|
|
499
|
+
if (!(invocation.eligible === true && sameArgv(binding, invocation.argv)) &&
|
|
500
|
+
binding.some((part) => /(?:^|[\\/])src[\\/]cli\.ts$|(?:^|[\\/])dist[\\/](?:cli\.js|cli-node\.mjs)$/i.test(part) ||
|
|
501
|
+
(path.isAbsolute(part) && hasGitAncestor(part)))) {
|
|
502
|
+
status.push("checkout");
|
|
503
|
+
}
|
|
504
|
+
if (binding.some((part) => part === "akm" || part === "bun" || part === "node"))
|
|
505
|
+
status.push("path-selected");
|
|
506
|
+
try {
|
|
507
|
+
validateSchedulerContextDescriptor(entry.contextPath);
|
|
508
|
+
}
|
|
509
|
+
catch {
|
|
510
|
+
status.push("invalid-context");
|
|
511
|
+
}
|
|
512
|
+
const absolutePaths = [...binding.filter((part) => path.isAbsolute(part)), entry.contextPath];
|
|
513
|
+
if (absolutePaths.some((part) => !fs.existsSync(part)))
|
|
514
|
+
status.push("missing-path");
|
|
515
|
+
if (status.length === 0)
|
|
516
|
+
status.push("ok");
|
|
517
|
+
return status;
|
|
518
|
+
}
|
|
519
|
+
function sameArgv(left, right) {
|
|
520
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
521
|
+
}
|
|
522
|
+
function hasGitAncestor(file) {
|
|
523
|
+
let current;
|
|
524
|
+
try {
|
|
525
|
+
current = path.dirname(fs.realpathSync(file));
|
|
526
|
+
}
|
|
527
|
+
catch {
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
for (;;) {
|
|
531
|
+
if (fs.existsSync(path.join(current, ".git")))
|
|
532
|
+
return true;
|
|
533
|
+
const parent = path.dirname(current);
|
|
534
|
+
if (parent === current)
|
|
535
|
+
return false;
|
|
536
|
+
current = parent;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
624
539
|
function taskAssetRef(id) {
|
|
625
540
|
return { type: "task", name: id };
|
|
626
541
|
}
|
|
@@ -629,8 +544,55 @@ async function restoreTaskSourceBytes(writeAsset, source, config, ref, filePath,
|
|
|
629
544
|
// The normal write path adds a trailing newline; rollback restores the raw snapshot exactly.
|
|
630
545
|
fs.writeFileSync(filePath, yaml, "utf8");
|
|
631
546
|
}
|
|
632
|
-
|
|
633
|
-
|
|
547
|
+
/**
|
|
548
|
+
* Resolve the bundle a mutating/run task command targets. Returns the resolved
|
|
549
|
+
* write/read target, its stash path, and the `--bundle <bundle>` token to embed
|
|
550
|
+
* in scheduled invocations. The primary bundle uses the target-less form.
|
|
551
|
+
*/
|
|
552
|
+
function resolveTaskBundle(target, opts) {
|
|
553
|
+
const selected = resolveWriteTarget(loadConfig(), target, { requireWritable: opts.requireWritable });
|
|
554
|
+
const resolved = opts.requireWritable ? prepareWriteTargetForMutation(selected) : selected;
|
|
555
|
+
const stashDir = resolved.source.path;
|
|
556
|
+
const installTarget = isPrimaryStashPath(stashDir) ? undefined : (resolved.selector ?? resolved.source.name);
|
|
557
|
+
return { resolved, stashDir, installTarget };
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Resolve the tasks/ directory a read/inspect command operates on. No
|
|
561
|
+
* `--bundle` uses the primary stash; `--bundle X` resolves bundle X read-only.
|
|
562
|
+
*/
|
|
563
|
+
function resolveTaskInspectDir(target) {
|
|
564
|
+
if (target === undefined)
|
|
565
|
+
return resolveStashDir();
|
|
566
|
+
return resolveWriteTarget(loadConfig(), target, { requireWritable: false }).source.path;
|
|
567
|
+
}
|
|
568
|
+
/** True when `candidate` resolves to the same directory as the primary stash. */
|
|
569
|
+
function isPrimaryStashPath(candidate) {
|
|
570
|
+
let primary;
|
|
571
|
+
try {
|
|
572
|
+
primary = path.resolve(resolveStashDir());
|
|
573
|
+
}
|
|
574
|
+
catch {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
return path.resolve(candidate) === primary;
|
|
578
|
+
}
|
|
579
|
+
/** Two bundle attributions match when both are the primary (undefined) or equal names. */
|
|
580
|
+
function sameBundle(a, b) {
|
|
581
|
+
return (a ?? undefined) === (b ?? undefined);
|
|
582
|
+
}
|
|
583
|
+
function foreignScheduleMessage(id, existingTarget) {
|
|
584
|
+
const where = existingTarget === undefined ? "the default bundle" : `bundle "${existingTarget}"`;
|
|
585
|
+
return `Task id "${id}" is already scheduled from ${where}; rename the task or disable the existing one first.`;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Refuse to schedule an id already installed from a DIFFERENT bundle. Scheduler
|
|
589
|
+
* ids are the bare task id (never namespaced), so a single id can be active from
|
|
590
|
+
* only one bundle at a time — a collision is a hard error, not an auto-rename.
|
|
591
|
+
*/
|
|
592
|
+
function assertNoForeignSchedule(entries, id, installTarget) {
|
|
593
|
+
const foreign = entries.find((entry) => entry.id === id && !sameBundle(entry.target, installTarget));
|
|
594
|
+
if (foreign)
|
|
595
|
+
throw new UsageError(foreignScheduleMessage(id, foreign.target), "RESOURCE_ALREADY_EXISTS");
|
|
634
596
|
}
|
|
635
597
|
function renderTaskYaml(input) {
|
|
636
598
|
const obj = { version: 2, schedule: input.schedule, enabled: input.enabled };
|
|
@@ -639,6 +601,8 @@ function renderTaskYaml(input) {
|
|
|
639
601
|
if (input.params) {
|
|
640
602
|
obj.params = parseJsonObjectArg(input.params);
|
|
641
603
|
}
|
|
604
|
+
if (input.timeoutMs !== undefined)
|
|
605
|
+
obj.timeoutMs = input.timeoutMs;
|
|
642
606
|
}
|
|
643
607
|
else if (input.prompt) {
|
|
644
608
|
obj.prompt = input.prompt;
|
|
@@ -667,101 +631,6 @@ function renderTaskYaml(input) {
|
|
|
667
631
|
obj.tags = input.tags;
|
|
668
632
|
return yamlStringify(obj);
|
|
669
633
|
}
|
|
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
634
|
function parseJsonObjectArg(raw) {
|
|
766
635
|
let parsed;
|
|
767
636
|
try {
|
|
@@ -799,11 +668,9 @@ export function setEnabledInYaml(yaml, enabled) {
|
|
|
799
668
|
// Re-exported so tests can verify the validator path directly.
|
|
800
669
|
// Re-export error classes consumed by callers that want to instanceof-check.
|
|
801
670
|
// Re-export this so the CLI can decide what process exit code to use after
|
|
802
|
-
// `akm
|
|
671
|
+
// `akm task run` completes.
|
|
803
672
|
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.
|
|
673
|
+
// Accept a bare task id or the canonical `[bundle//]tasks/<id>` ref.
|
|
807
674
|
export function parseTaskRef(input) {
|
|
808
675
|
const trimmed = input.trim();
|
|
809
676
|
// Canonical conceptId form: `[bundle//]tasks/<id>`. A `/` unambiguously marks
|
|
@@ -821,11 +688,5 @@ export function parseTaskRef(input) {
|
|
|
821
688
|
}
|
|
822
689
|
throw new UsageError(`Expected a task id or tasks/<id> ref, got "${input}".`, "INVALID_FLAG_VALUE");
|
|
823
690
|
}
|
|
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
691
|
return { id: normaliseTaskId(trimmed) };
|
|
831
692
|
}
|