akm-cli 0.9.0-rc.8 → 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 +105 -57
- 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 +37 -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 +101 -6
- 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 -1806
- 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 -1234
- 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 -223
- package/dist/migrate/legacy/content-migration.js +0 -305
- 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 -72
- package/dist/migrate/legacy/proposal-fs-import.js +0 -168
- package/dist/migrate/legacy/task-target-ref-migration.js +0 -272
- package/dist/migrate/legacy/three-db-cutover.js +0 -841
- 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
|
@@ -24,7 +24,7 @@ function validateDetail(value) {
|
|
|
24
24
|
metadataError("detail.exitCode must be a number or null");
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
-
/** Decode historical
|
|
27
|
+
/** Decode supported historical and current task-history metadata. */
|
|
28
28
|
export function decodeTaskHistoryMetadata(input) {
|
|
29
29
|
let parsed = input;
|
|
30
30
|
if (typeof input === "string") {
|
|
@@ -37,27 +37,26 @@ export function decodeTaskHistoryMetadata(input) {
|
|
|
37
37
|
}
|
|
38
38
|
if (!isRecord(parsed))
|
|
39
39
|
metadataError("root must be an object");
|
|
40
|
-
|
|
41
|
-
if (version === 1) {
|
|
40
|
+
if (parsed.metadataVersion === undefined || parsed.metadataVersion === 1) {
|
|
42
41
|
const allowed = new Set(["metadataVersion", "durationMs", "detail", "profile"]);
|
|
43
42
|
const unknown = Object.keys(parsed).filter((key) => !allowed.has(key));
|
|
44
43
|
if (unknown.length > 0)
|
|
45
44
|
metadataError(`unknown v1 fields: ${unknown.sort().join(", ")}`);
|
|
46
|
-
if (parsed.
|
|
47
|
-
metadataError("invalid v1 metadataVersion");
|
|
48
|
-
if (parsed.durationMs !== undefined && typeof parsed.durationMs !== "number")
|
|
45
|
+
if (parsed.durationMs !== undefined && typeof parsed.durationMs !== "number") {
|
|
49
46
|
metadataError("durationMs must be a number");
|
|
50
|
-
|
|
47
|
+
}
|
|
48
|
+
if (parsed.profile !== undefined && typeof parsed.profile !== "string") {
|
|
51
49
|
metadataError("profile must be a string");
|
|
50
|
+
}
|
|
52
51
|
validateDetail(parsed.detail);
|
|
53
52
|
return {
|
|
54
53
|
metadataVersion: 1,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
...(
|
|
54
|
+
durationMs: parsed.durationMs ?? 0,
|
|
55
|
+
detail: parsed.detail ?? null,
|
|
56
|
+
...(parsed.profile !== undefined ? { legacyProfile: parsed.profile } : {}),
|
|
58
57
|
};
|
|
59
58
|
}
|
|
60
|
-
if (
|
|
59
|
+
if (parsed.metadataVersion === 2) {
|
|
61
60
|
const allowed = new Set(["metadataVersion", "durationMs", "detail", "engine"]);
|
|
62
61
|
const unknown = Object.keys(parsed).filter((key) => !allowed.has(key));
|
|
63
62
|
if (unknown.length > 0)
|
|
@@ -77,7 +76,7 @@ export function decodeTaskHistoryMetadata(input) {
|
|
|
77
76
|
...(parsed.engine !== undefined ? { engine: parsed.engine } : {}),
|
|
78
77
|
};
|
|
79
78
|
}
|
|
80
|
-
metadataError(`unsupported metadataVersion: ${String(
|
|
79
|
+
metadataError(`unsupported metadataVersion: ${String(parsed.metadataVersion)}`);
|
|
81
80
|
}
|
|
82
81
|
/**
|
|
83
82
|
* Atomically reserve one task-attempt identity.
|
|
@@ -169,10 +168,13 @@ export function queryTaskHistory(db, options = {}) {
|
|
|
169
168
|
params.push(options.targetRef);
|
|
170
169
|
}
|
|
171
170
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
171
|
+
const limit = options.limit !== undefined ? "LIMIT ?" : "";
|
|
172
|
+
if (options.limit !== undefined)
|
|
173
|
+
params.push(options.limit);
|
|
172
174
|
return db
|
|
173
175
|
.prepare(`SELECT task_id, status, started_at, completed_at, failed_at, log_path,
|
|
174
176
|
target_kind, target_ref, metadata_json
|
|
175
|
-
FROM task_history ${where} ORDER BY started_at DESC, id DESC`)
|
|
177
|
+
FROM task_history ${where} ORDER BY started_at DESC, id DESC ${limit}`)
|
|
176
178
|
.all(...params);
|
|
177
179
|
}
|
|
178
180
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
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
|
-
import { openStateDatabase } from "../../core/state-db.js";
|
|
4
|
+
import { openStateDatabase, withImmediateTransaction } from "../../core/state-db.js";
|
|
5
5
|
import { resolveStorageLocations } from "../locations.js";
|
|
6
6
|
/**
|
|
7
7
|
* Repository owning every raw SQL statement against `workflow_runs` and
|
|
@@ -30,19 +30,28 @@ export class WorkflowRunsRepository {
|
|
|
30
30
|
transaction(fn) {
|
|
31
31
|
return this.db.transaction(fn)();
|
|
32
32
|
}
|
|
33
|
+
immediateTransaction(fn) {
|
|
34
|
+
return withImmediateTransaction(this.db, () => fn(this.db));
|
|
35
|
+
}
|
|
33
36
|
// ── reads (fully materialised) ─────────────────────────────────────────────
|
|
34
|
-
findActiveRunForScope(
|
|
37
|
+
findActiveRunForScope(workflowRefs, scopeKey) {
|
|
38
|
+
const refs = typeof workflowRefs === "string" ? [workflowRefs] : [...workflowRefs];
|
|
39
|
+
if (refs.length === 0)
|
|
40
|
+
return undefined;
|
|
35
41
|
return this.db
|
|
36
|
-
.prepare(
|
|
37
|
-
.get(
|
|
42
|
+
.prepare(`SELECT id, current_step_id FROM workflow_runs WHERE workflow_ref IN (${refs.map(() => "?").join(", ")}) AND scope_key = ? AND status = 'active' ORDER BY updated_at DESC, created_at DESC LIMIT 1`)
|
|
43
|
+
.get(...refs, scopeKey);
|
|
38
44
|
}
|
|
39
45
|
getRunById(runId) {
|
|
40
46
|
return this.db.prepare("SELECT * FROM workflow_runs WHERE id = ?").get(runId);
|
|
41
47
|
}
|
|
42
|
-
getActiveRunRowForScope(
|
|
48
|
+
getActiveRunRowForScope(workflowRefs, scopeKey) {
|
|
49
|
+
const refs = typeof workflowRefs === "string" ? [workflowRefs] : [...workflowRefs];
|
|
50
|
+
if (refs.length === 0)
|
|
51
|
+
return undefined;
|
|
43
52
|
return this.db
|
|
44
|
-
.prepare(
|
|
45
|
-
.get(
|
|
53
|
+
.prepare(`SELECT * FROM workflow_runs WHERE workflow_ref IN (${refs.map(() => "?").join(", ")}) AND scope_key = ? AND status = 'active' ORDER BY updated_at DESC, created_at DESC LIMIT 1`)
|
|
54
|
+
.get(...refs, scopeKey);
|
|
46
55
|
}
|
|
47
56
|
hasRun(runId) {
|
|
48
57
|
const row = this.db.prepare("SELECT 1 FROM workflow_runs WHERE id = ? LIMIT 1").get(runId);
|
|
@@ -57,6 +66,10 @@ export class WorkflowRunsRepository {
|
|
|
57
66
|
filters.push("workflow_ref = ?");
|
|
58
67
|
params.push(filter.workflowRef);
|
|
59
68
|
}
|
|
69
|
+
else if (filter.workflowRefs && filter.workflowRefs.length > 0) {
|
|
70
|
+
filters.push(`workflow_ref IN (${filter.workflowRefs.map(() => "?").join(", ")})`);
|
|
71
|
+
params.push(...filter.workflowRefs);
|
|
72
|
+
}
|
|
60
73
|
if (filter.activeOnly) {
|
|
61
74
|
// `activeOnly` means EXACTLY status='active' — a run currently
|
|
62
75
|
// executable. A `blocked` run is NOT active (it is parked awaiting a
|
|
@@ -113,7 +126,9 @@ export class WorkflowRunsRepository {
|
|
|
113
126
|
.run(runId, currentStepId);
|
|
114
127
|
}
|
|
115
128
|
markRunActive(runId, updatedAt) {
|
|
116
|
-
this.db
|
|
129
|
+
this.db
|
|
130
|
+
.prepare("UPDATE workflow_runs SET status = 'active', updated_at = ?, engine_lease_holder = NULL, engine_lease_until = NULL WHERE id = ?")
|
|
131
|
+
.run(updatedAt, runId);
|
|
117
132
|
}
|
|
118
133
|
updateStepCompletion(input) {
|
|
119
134
|
this.db
|
|
@@ -129,6 +144,14 @@ export class WorkflowRunsRepository {
|
|
|
129
144
|
WHERE id = ?`)
|
|
130
145
|
.run(input.status, input.currentStepId, input.updatedAt, input.completedAt, input.checkinArmedAt, input.runId);
|
|
131
146
|
}
|
|
147
|
+
markRunAbandoned(runId, updatedAt) {
|
|
148
|
+
const result = this.db
|
|
149
|
+
.prepare(`UPDATE workflow_runs
|
|
150
|
+
SET status = 'failed', updated_at = ?, completed_at = ?, checkin_armed_at = ?
|
|
151
|
+
WHERE id = ? AND status IN ('active', 'blocked')`)
|
|
152
|
+
.run(updatedAt, updatedAt, updatedAt, runId);
|
|
153
|
+
return Number(result.changes) === 1;
|
|
154
|
+
}
|
|
132
155
|
rearmCheckin(runId, checkinArmedAt) {
|
|
133
156
|
this.db.prepare("UPDATE workflow_runs SET checkin_armed_at = ? WHERE id = ?").run(checkinArmedAt, runId);
|
|
134
157
|
}
|
|
@@ -161,7 +184,8 @@ export class WorkflowRunsRepository {
|
|
|
161
184
|
const result = this.db
|
|
162
185
|
.prepare(`UPDATE workflow_runs
|
|
163
186
|
SET engine_lease_holder = ?, engine_lease_until = ?
|
|
164
|
-
WHERE id = ? AND
|
|
187
|
+
WHERE id = ? AND status = 'active'
|
|
188
|
+
AND (engine_lease_holder IS NULL OR engine_lease_until IS NULL OR engine_lease_until < ?)`)
|
|
165
189
|
.run(holder, until, runId, now);
|
|
166
190
|
return Number(result.changes) > 0;
|
|
167
191
|
}
|
|
@@ -172,18 +196,18 @@ export class WorkflowRunsRepository {
|
|
|
172
196
|
*/
|
|
173
197
|
renewEngineLease(runId, holder, until) {
|
|
174
198
|
const result = this.db
|
|
175
|
-
.prepare("UPDATE workflow_runs SET engine_lease_until = ? WHERE id = ? AND engine_lease_holder = ?")
|
|
199
|
+
.prepare("UPDATE workflow_runs SET engine_lease_until = ? WHERE id = ? AND engine_lease_holder = ? AND status = 'active'")
|
|
176
200
|
.run(until, runId, holder);
|
|
177
201
|
return Number(result.changes) > 0;
|
|
178
202
|
}
|
|
179
203
|
/**
|
|
180
|
-
* Clear the lease
|
|
181
|
-
*
|
|
182
|
-
*
|
|
204
|
+
* Clear the lease only while `holder` still owns a non-failed run. Failed
|
|
205
|
+
* runs retain the final holder/expiry for forensics until explicit resume.
|
|
206
|
+
* Releasing an already-lost lease is a harmless no-op.
|
|
183
207
|
*/
|
|
184
208
|
releaseEngineLease(runId, holder) {
|
|
185
209
|
this.db
|
|
186
|
-
.prepare("UPDATE workflow_runs SET engine_lease_holder = NULL, engine_lease_until = NULL WHERE id = ? AND engine_lease_holder = ?")
|
|
210
|
+
.prepare("UPDATE workflow_runs SET engine_lease_holder = NULL, engine_lease_until = NULL WHERE id = ? AND engine_lease_holder = ? AND status <> 'failed'")
|
|
187
211
|
.run(runId, holder);
|
|
188
212
|
}
|
|
189
213
|
// ── unit rows (migration 004) ──────────────────────────────────────────────
|
|
@@ -202,25 +226,11 @@ export class WorkflowRunsRepository {
|
|
|
202
226
|
.prepare("SELECT * FROM workflow_run_units WHERE run_id = ? AND step_id = ? ORDER BY started_at ASC, unit_id ASC")
|
|
203
227
|
.all(runId, stepId);
|
|
204
228
|
}
|
|
205
|
-
/** One unit row by primary key, or undefined. Used
|
|
206
|
-
*
|
|
229
|
+
/** One unit row by primary key, or undefined. Used for guarded read-then-write
|
|
230
|
+
* (idempotency / replay-divergence / claim checks). */
|
|
207
231
|
getUnit(runId, unitId) {
|
|
208
232
|
return this.db.prepare("SELECT * FROM workflow_run_units WHERE run_id = ? AND unit_id = ?").get(runId, unitId);
|
|
209
233
|
}
|
|
210
|
-
/**
|
|
211
|
-
* Stamp a unit-level check-in heartbeat (migration 007, R3 driver protocol):
|
|
212
|
-
* a driver executing a unit calls `report --status running` to claim (first
|
|
213
|
-
* call) or heartbeat (subsequent) it. Sets `status = 'running'` and
|
|
214
|
-
* `last_checkin_at`; NEVER touches `started_at` (the first-claim marker set by
|
|
215
|
-
* {@link insertUnit}) so the heartbeat window advances without resetting the
|
|
216
|
-
* claim time. The caller guards against re-claiming a terminal unit inside its
|
|
217
|
-
* transaction.
|
|
218
|
-
*/
|
|
219
|
-
updateUnitCheckin(runId, unitId, lastCheckinAt) {
|
|
220
|
-
this.db
|
|
221
|
-
.prepare("UPDATE workflow_run_units SET status = 'running', last_checkin_at = ? WHERE run_id = ? AND unit_id = ?")
|
|
222
|
-
.run(lastCheckinAt, runId, unitId);
|
|
223
|
-
}
|
|
224
234
|
/**
|
|
225
235
|
* Insert a unit row in `running` state (a dispatch is starting now).
|
|
226
236
|
*
|
|
@@ -267,20 +277,22 @@ export class WorkflowRunsRepository {
|
|
|
267
277
|
.run(input.runId, input.unitId, input.stepId, input.nodeId, input.parentUnitId, input.phase, input.runner, input.engine ?? null, input.model, input.inputHash, input.worktreePath ?? null, input.startedAt, input.claimHolder ?? null, input.claimExpiresAt ?? null);
|
|
268
278
|
}
|
|
269
279
|
/**
|
|
270
|
-
* Stamp a
|
|
271
|
-
*
|
|
272
|
-
* (`last_checkin_at`), and (re)writes the claim owner + expiry.
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
280
|
+
* Stamp a `running` claim + heartbeat on a unit row (migration 009, PR #714
|
|
281
|
+
* review round 2). Sets `status = 'running'`, refreshes the heartbeat
|
|
282
|
+
* (`last_checkin_at`), and (re)writes the claim owner + expiry. Must be called
|
|
283
|
+
* inside a transaction that has ALREADY validated the claim is free / expired
|
|
284
|
+
* / already held by this holder, so the write is the final step of a checked
|
|
285
|
+
* reclaim. Never touches `started_at` (the first-claim marker set by
|
|
286
|
+
* {@link insertUnit}).
|
|
277
287
|
*/
|
|
278
288
|
updateUnitClaim(runId, unitId, holder, expiresAt, lastCheckinAt) {
|
|
279
|
-
this.db
|
|
289
|
+
const result = this.db
|
|
280
290
|
.prepare(`UPDATE workflow_run_units
|
|
281
291
|
SET status = 'running', last_checkin_at = ?, claim_holder = ?, claim_expires_at = ?
|
|
282
|
-
WHERE run_id = ? AND unit_id =
|
|
283
|
-
|
|
292
|
+
WHERE run_id = ? AND unit_id = ?
|
|
293
|
+
AND EXISTS (SELECT 1 FROM workflow_runs WHERE id = ? AND status = 'active')`)
|
|
294
|
+
.run(lastCheckinAt, holder, expiresAt, runId, unitId, runId);
|
|
295
|
+
return Number(result.changes) === 1;
|
|
284
296
|
}
|
|
285
297
|
/**
|
|
286
298
|
* Record a unit's terminal state (completed / failed / skipped).
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
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
|
-
// crontab backend for `akm
|
|
4
|
+
// crontab backend for `akm task` (Linux default).
|
|
5
5
|
//
|
|
6
6
|
// Each akm-owned entry is wrapped in markers so a hand-edited crontab keeps
|
|
7
7
|
// its other lines untouched:
|
|
8
8
|
//
|
|
9
9
|
// # akm:task <id> BEGIN
|
|
10
|
-
// [SCHED] /abs/akm
|
|
10
|
+
// [SCHED] /abs/akm task run <id> >> /home/.../tasks/logs/<id>.log 2>&1
|
|
11
11
|
// # akm:task <id> END
|
|
12
12
|
//
|
|
13
13
|
// The backend reads/writes the user's crontab via `crontab -l` and
|
|
@@ -30,13 +30,14 @@ import { ConfigError } from "../../core/errors.js";
|
|
|
30
30
|
import { getTaskLogDir } from "../../core/paths.js";
|
|
31
31
|
import { resolveAkmInvocation } from "../resolve-akm-bin.js";
|
|
32
32
|
import { parseSchedule, translateToCron } from "../schedule.js";
|
|
33
|
-
import { buildScheduledTaskInvocation, resolveScheduledTaskContext, } from "../scheduler-invocation.js";
|
|
33
|
+
import { buildScheduledTaskInvocation, parseScheduledTaskArgv, resolveScheduledTaskContext, schedulerContextDescriptor, schedulerContextPath, } from "../scheduler-invocation.js";
|
|
34
34
|
import { nodeFs, throwIfNotOk } from "./exec-utils.js";
|
|
35
35
|
const BEGIN = (id) => `# akm:task ${assertCronValue(id)} BEGIN`;
|
|
36
36
|
const END = (id) => `# akm:task ${assertCronValue(id)} END`;
|
|
37
37
|
const DISABLED_PREFIX = "# akm:disabled ";
|
|
38
38
|
const BLOCK_RE = /^# akm:task ([\w.@:_-]+) BEGIN$/;
|
|
39
39
|
const BLOCK_END_RE = /^# akm:task ([\w.@:_-]+) END$/;
|
|
40
|
+
export const PORTABLE_CRON_LINE_LIMIT = 1000;
|
|
40
41
|
export function CRON_BACKEND(options = {}) {
|
|
41
42
|
const exec = options.exec ?? defaultCronExec();
|
|
42
43
|
const fsLike = options.fs ?? nodeFs();
|
|
@@ -44,14 +45,16 @@ export function CRON_BACKEND(options = {}) {
|
|
|
44
45
|
const akmArgv = options.akmArgv ?? resolveAkmInvocation().argv;
|
|
45
46
|
const envPath = options.envPath === false ? undefined : (options.envPath ?? process.env.PATH);
|
|
46
47
|
const scheduledContext = options.scheduledContext ?? resolveScheduledTaskContext();
|
|
48
|
+
const defaultContextPath = schedulerContextPath(schedulerContextDescriptor(scheduledContext, envPath ?? ""));
|
|
47
49
|
return {
|
|
48
50
|
name: "cron",
|
|
49
|
-
install(task) {
|
|
51
|
+
install(task, opts) {
|
|
50
52
|
// Create the log directory before writing the crontab line — cron
|
|
51
53
|
// appends with `>>` and the surrounding shell will fail the entire
|
|
52
54
|
// entry if the parent directory doesn't exist.
|
|
55
|
+
const cronLine = buildCronLine(task, [...(opts?.binding ?? akmArgv)], logDir, opts?.contextPath ?? defaultContextPath, opts?.target);
|
|
56
|
+
assertPortableCronLine(cronLine);
|
|
53
57
|
fsLike.ensureDir(logDir);
|
|
54
|
-
const cronLine = buildCronLine(task, akmArgv, logDir, envPath, scheduledContext);
|
|
55
58
|
const existing = readCrontab(exec);
|
|
56
59
|
const block = renderBlock(task.id, cronLine, task.enabled);
|
|
57
60
|
const next = upsertBlock(existing, task.id, block);
|
|
@@ -69,26 +72,54 @@ export function CRON_BACKEND(options = {}) {
|
|
|
69
72
|
},
|
|
70
73
|
list() {
|
|
71
74
|
const existing = readCrontab(exec);
|
|
72
|
-
|
|
75
|
+
const refs = [];
|
|
76
|
+
for (const { id, body } of listBlocks(existing)) {
|
|
77
|
+
const installed = extractCronInvocation(body);
|
|
78
|
+
// An invocation that no longer parses (e.g. a pre-0.9 `tasks run`
|
|
79
|
+
// entry surviving the scheduler-ABI respelling, or any other
|
|
80
|
+
// foreign/malformed line between our markers) is an orphan of its
|
|
81
|
+
// marker id, not a hard failure: omit it here so `akmTasksSync`
|
|
82
|
+
// treats the id as "not present" and reinstalls it from the current
|
|
83
|
+
// task file — going through the normal eligibility/`--rebind` gate
|
|
84
|
+
// exactly like a fresh install.
|
|
85
|
+
if (!installed)
|
|
86
|
+
continue;
|
|
87
|
+
refs.push({
|
|
88
|
+
id,
|
|
89
|
+
signature: normalizeSignature(body),
|
|
90
|
+
...(installed.target !== undefined ? { target: installed.target } : {}),
|
|
91
|
+
binding: installed.binding,
|
|
92
|
+
contextPath: installed.contextPath,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return refs;
|
|
96
|
+
},
|
|
97
|
+
listForRebind() {
|
|
98
|
+
const existing = readCrontab(exec);
|
|
99
|
+
return listBlocks(existing).map(({ id, body }) => {
|
|
100
|
+
const installed = extractCronInvocation(body);
|
|
101
|
+
return {
|
|
102
|
+
id,
|
|
103
|
+
signature: normalizeSignature(body),
|
|
104
|
+
...(installed?.target !== undefined ? { target: installed.target } : {}),
|
|
105
|
+
};
|
|
106
|
+
});
|
|
73
107
|
},
|
|
74
|
-
expectedSignature(task) {
|
|
75
|
-
const cronLine = buildCronLine(task, akmArgv, logDir,
|
|
108
|
+
expectedSignature(task, opts) {
|
|
109
|
+
const cronLine = buildCronLine(task, [...(opts?.binding ?? akmArgv)], logDir, opts?.contextPath ?? defaultContextPath, opts?.target);
|
|
110
|
+
assertPortableCronLine(cronLine);
|
|
76
111
|
return normalizeSignature(cronBlockBody(cronLine, task.enabled));
|
|
77
112
|
},
|
|
78
113
|
};
|
|
79
114
|
}
|
|
80
115
|
// ── helpers (exported for tests) ────────────────────────────────────────────
|
|
81
|
-
export function buildCronLine(task, akmArgv, logDir,
|
|
116
|
+
export function buildCronLine(task, akmArgv, logDir, contextPath, target) {
|
|
82
117
|
const spec = parseSchedule(task.schedule, "cron");
|
|
83
118
|
const cronExpr = translateToCron(spec);
|
|
84
119
|
const logPath = path.join(logDir, `${task.id}.log`);
|
|
85
|
-
const invocation = buildScheduledTaskInvocation(akmArgv, task.id,
|
|
120
|
+
const invocation = buildScheduledTaskInvocation(akmArgv, task.id, contextPath, target);
|
|
86
121
|
const cmd = invocation.argv.map((part) => quoteForCron(part)).join(" ");
|
|
87
|
-
|
|
88
|
-
.map(([key, value]) => `${key}=${quoteForCron(value)}`)
|
|
89
|
-
.join(" ");
|
|
90
|
-
const pathPrefix = envPath === undefined ? "" : `PATH=${quoteForCron(envPath)} `;
|
|
91
|
-
return `${cronExpr} ${contextPrefix} ${pathPrefix}${cmd} >> ${quoteForCron(logPath)} 2>&1`;
|
|
122
|
+
return `${cronExpr} ${cmd} >> ${quoteForCron(logPath)} 2>&1`;
|
|
92
123
|
}
|
|
93
124
|
/** The crontab line as it appears inside a block — commented when disabled. */
|
|
94
125
|
export function cronBlockBody(cronLine, enabled) {
|
|
@@ -141,6 +172,59 @@ function parseBlocks(existing) {
|
|
|
141
172
|
function malformedBlockError(id) {
|
|
142
173
|
return new ConfigError(`Crontab contains a malformed akm task block for "${id}"; refusing to modify it.`, "INVALID_CONFIG_FILE");
|
|
143
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Recover the bundle name from an installed cron block body by reading the
|
|
177
|
+
* `--bundle <bundle>` token embedded in the scheduled `akm task run …`
|
|
178
|
+
* invocation. Returns undefined for the byte-identical primary/default form
|
|
179
|
+
* (no `--bundle`). Bundle slugs never contain whitespace (config-schema
|
|
180
|
+
* `isBundleSlug`), so the quoted token is a single whitespace-delimited field
|
|
181
|
+
* even when cron-quoted — a plain field split recovers it, and
|
|
182
|
+
* the shell-word parser below reverses the quoting.
|
|
183
|
+
*/
|
|
184
|
+
export function extractInstalledTarget(body) {
|
|
185
|
+
return extractCronInvocation(body)?.target;
|
|
186
|
+
}
|
|
187
|
+
export function extractCronInvocation(body) {
|
|
188
|
+
const line = body.startsWith(DISABLED_PREFIX) ? body.slice(DISABLED_PREFIX.length) : body;
|
|
189
|
+
const fields = splitCronShellWords(line);
|
|
190
|
+
if (fields.length < 6)
|
|
191
|
+
return undefined;
|
|
192
|
+
let commandStart = 5;
|
|
193
|
+
while (commandStart < fields.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(fields[commandStart]))
|
|
194
|
+
commandStart += 1;
|
|
195
|
+
const redirectIndex = fields.indexOf(">>", commandStart);
|
|
196
|
+
if (redirectIndex === -1)
|
|
197
|
+
return undefined;
|
|
198
|
+
return parseScheduledTaskArgv(fields.slice(commandStart, redirectIndex));
|
|
199
|
+
}
|
|
200
|
+
/** Reverse {@link quoteForCron} for a single whitespace-free token. */
|
|
201
|
+
function splitCronShellWords(value) {
|
|
202
|
+
const words = [];
|
|
203
|
+
let current = "";
|
|
204
|
+
let quoted = false;
|
|
205
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
206
|
+
const char = value[index];
|
|
207
|
+
if (char === "'") {
|
|
208
|
+
quoted = !quoted;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (char === "\\" && index + 1 < value.length) {
|
|
212
|
+
current += value[index + 1];
|
|
213
|
+
index += 1;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (!quoted && /\s/.test(char)) {
|
|
217
|
+
if (current)
|
|
218
|
+
words.push(current);
|
|
219
|
+
current = "";
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
current += char;
|
|
223
|
+
}
|
|
224
|
+
if (current)
|
|
225
|
+
words.push(current);
|
|
226
|
+
return words;
|
|
227
|
+
}
|
|
144
228
|
/** Collapse incidental whitespace so signature comparison ignores it. */
|
|
145
229
|
function normalizeSignature(body) {
|
|
146
230
|
return body
|
|
@@ -218,6 +302,12 @@ function assertCronValue(value) {
|
|
|
218
302
|
}
|
|
219
303
|
return value;
|
|
220
304
|
}
|
|
305
|
+
function assertPortableCronLine(line) {
|
|
306
|
+
const bytes = Buffer.byteLength(line, "utf8");
|
|
307
|
+
if (bytes > PORTABLE_CRON_LINE_LIMIT) {
|
|
308
|
+
throw new ConfigError(`Generated cron definition is ${bytes} bytes; portable cron command lines are limited to ${PORTABLE_CRON_LINE_LIMIT} bytes.`, "INVALID_CONFIG_FILE");
|
|
309
|
+
}
|
|
310
|
+
}
|
|
221
311
|
function readCrontab(exec) {
|
|
222
312
|
const result = exec.read();
|
|
223
313
|
if (result.status === 0)
|
|
@@ -6,7 +6,7 @@ import { LAUNCHD_BACKEND } from "./launchd.js";
|
|
|
6
6
|
import { SCHTASKS_BACKEND } from "./schtasks.js";
|
|
7
7
|
// WI-9.10e: the former `_setBackendsForTests` module-mutation seam was retired.
|
|
8
8
|
// Tests inject a fake `TaskBackend` directly via the `deps.backend` parameter
|
|
9
|
-
// the `akm
|
|
9
|
+
// the `akm task` mutation entries already accept (the backend carries its own
|
|
10
10
|
// `name`), so no module-level override binding is needed. `selectBackend`'s
|
|
11
11
|
// `options.platform` covers the platform-steering the seam's second override
|
|
12
12
|
// used to provide.
|
|
@@ -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
|
-
* launchd backend for `akm
|
|
5
|
+
* launchd backend for `akm task` (macOS default).
|
|
6
6
|
*
|
|
7
7
|
* Each task is written as a per-user LaunchAgent plist at
|
|
8
8
|
* `~/Library/LaunchAgents/com.akm.task.<id>.plist` and registered via
|
|
@@ -29,7 +29,7 @@ import { ConfigError } from "../../core/errors.js";
|
|
|
29
29
|
import { getTaskLogDir } from "../../core/paths.js";
|
|
30
30
|
import { resolveAkmInvocation } from "../resolve-akm-bin.js";
|
|
31
31
|
import { parseSchedule, translateToLaunchd } from "../schedule.js";
|
|
32
|
-
import { buildScheduledTaskInvocation, resolveScheduledTaskContext, } from "../scheduler-invocation.js";
|
|
32
|
+
import { buildScheduledTaskInvocation, parseScheduledTaskArgv, resolveScheduledTaskContext, schedulerContextDescriptor, schedulerContextPath, } from "../scheduler-invocation.js";
|
|
33
33
|
import { escapeXml, nodeExec, nodeFs, runOrThrow } from "./exec-utils.js";
|
|
34
34
|
export const LAUNCHD_LABEL_PREFIX = "com.akm.task.";
|
|
35
35
|
export function LAUNCHD_BACKEND(options = {}) {
|
|
@@ -49,6 +49,7 @@ export function LAUNCHD_BACKEND(options = {}) {
|
|
|
49
49
|
return options.envPath;
|
|
50
50
|
return process.env.PATH ?? "";
|
|
51
51
|
};
|
|
52
|
+
const defaultContextPath = schedulerContextPath(schedulerContextDescriptor(scheduledContext, pathEnv() ?? ""));
|
|
52
53
|
const setEnableState = (id, enabled) => {
|
|
53
54
|
const verb = enabled ? "enable" : "disable";
|
|
54
55
|
runOrThrow(exec, ["launchctl", verb, target(id)], {
|
|
@@ -57,10 +58,10 @@ export function LAUNCHD_BACKEND(options = {}) {
|
|
|
57
58
|
};
|
|
58
59
|
return {
|
|
59
60
|
name: "launchd",
|
|
60
|
-
install(task) {
|
|
61
|
+
install(task, opts) {
|
|
61
62
|
// Capture PATH at install time so launchd (which strips the environment
|
|
62
63
|
// aggressively) can find the same binaries the user sees interactively.
|
|
63
|
-
const xml = buildPlistXml(task, akmArgv, logDir,
|
|
64
|
+
const xml = buildPlistXml(task, [...(opts?.binding ?? akmArgv)], logDir, opts?.contextPath ?? defaultContextPath, opts?.target);
|
|
64
65
|
const file = plistPath(task.id);
|
|
65
66
|
const previousPlist = fsLike.exists(file) ? fsLike.readFile(file) : undefined;
|
|
66
67
|
let previousEnabled = true;
|
|
@@ -201,55 +202,101 @@ export function LAUNCHD_BACKEND(options = {}) {
|
|
|
201
202
|
if (ids.length === 0)
|
|
202
203
|
return [];
|
|
203
204
|
const disabledLabels = readDisabledLabels(exec);
|
|
204
|
-
return ids
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
catch {
|
|
222
|
-
return { id };
|
|
223
|
-
}
|
|
224
|
-
});
|
|
205
|
+
return ids
|
|
206
|
+
.map((id) => inspectInstalledLaunchdTask(id, fsLike.readFile(plistPath(id)), disabledLabels, exec))
|
|
207
|
+
.filter((ref) => ref !== undefined);
|
|
208
|
+
},
|
|
209
|
+
listForRebind() {
|
|
210
|
+
if (!fsLike.exists(agentsDir))
|
|
211
|
+
return [];
|
|
212
|
+
const refs = [];
|
|
213
|
+
for (const file of fsLike.list(agentsDir)) {
|
|
214
|
+
if (!file.startsWith(LAUNCHD_LABEL_PREFIX) || !file.endsWith(".plist"))
|
|
215
|
+
continue;
|
|
216
|
+
const id = file.slice(LAUNCHD_LABEL_PREFIX.length, -".plist".length);
|
|
217
|
+
const installed = extractPlistInvocation(fsLike.readFile(plistPath(id)));
|
|
218
|
+
refs.push({ id, ...(installed?.target !== undefined ? { target: installed.target } : {}) });
|
|
219
|
+
}
|
|
220
|
+
return refs;
|
|
225
221
|
},
|
|
226
|
-
expectedSignature(task) {
|
|
227
|
-
return normalizeSignature(buildPlistXml(task, akmArgv, logDir,
|
|
222
|
+
expectedSignature(task, opts) {
|
|
223
|
+
return normalizeSignature(buildPlistXml(task, [...(opts?.binding ?? akmArgv)], logDir, opts?.contextPath ?? defaultContextPath, opts?.target));
|
|
228
224
|
},
|
|
229
225
|
};
|
|
230
226
|
}
|
|
227
|
+
function inspectInstalledLaunchdTask(id, raw, disabledLabels, exec) {
|
|
228
|
+
const installed = extractPlistInvocation(raw);
|
|
229
|
+
// An invocation that no longer parses (e.g. a pre-0.9 `tasks run` entry
|
|
230
|
+
// surviving the scheduler-ABI respelling) is an orphan of its marker id,
|
|
231
|
+
// not a hard failure: `list()` omits it so `akmTasksSync` treats the id as
|
|
232
|
+
// "not present" and reinstalls it from the current task file.
|
|
233
|
+
if (!installed)
|
|
234
|
+
return undefined;
|
|
235
|
+
const metadata = {
|
|
236
|
+
...(installed.target !== undefined ? { target: installed.target } : {}),
|
|
237
|
+
binding: installed.binding,
|
|
238
|
+
contextPath: installed.contextPath,
|
|
239
|
+
};
|
|
240
|
+
if (!disabledLabels)
|
|
241
|
+
return { id, ...metadata };
|
|
242
|
+
const jobLabel = `${LAUNCHD_LABEL_PREFIX}${id}`;
|
|
243
|
+
try {
|
|
244
|
+
const loaded = exec.run(["launchctl", "print", `gui/${exec.uid()}/${jobLabel}`]);
|
|
245
|
+
if (loaded.status !== 0)
|
|
246
|
+
return { id, ...metadata };
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return { id, ...metadata };
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
const xml = raw.replace(/<!-- akm-enabled:(?:true|false) -->/, `<!-- akm-enabled:${!disabledLabels.has(jobLabel)} -->`);
|
|
253
|
+
return {
|
|
254
|
+
id,
|
|
255
|
+
signature: normalizeSignature(xml),
|
|
256
|
+
...metadata,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
return { id, ...metadata };
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Recover the bundle name embedded as a `--bundle <bundle>` pair in a plist's
|
|
265
|
+
* `<ProgramArguments>`. Returns undefined for the primary/default form.
|
|
266
|
+
*/
|
|
267
|
+
export function extractPlistTarget(xml) {
|
|
268
|
+
return extractPlistInvocation(xml)?.target;
|
|
269
|
+
}
|
|
270
|
+
export function extractPlistInvocation(xml) {
|
|
271
|
+
const block = xml.match(/<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/);
|
|
272
|
+
if (!block)
|
|
273
|
+
return undefined;
|
|
274
|
+
const args = [...block[1].matchAll(/<string>([\s\S]*?)<\/string>/g)].map((m) => decodeXmlEntities(m[1]));
|
|
275
|
+
return parseScheduledTaskArgv(args);
|
|
276
|
+
}
|
|
277
|
+
function decodeXmlEntities(value) {
|
|
278
|
+
return value
|
|
279
|
+
.replaceAll("<", "<")
|
|
280
|
+
.replaceAll(">", ">")
|
|
281
|
+
.replaceAll(""", '"')
|
|
282
|
+
.replaceAll("'", "'")
|
|
283
|
+
.replaceAll("&", "&");
|
|
284
|
+
}
|
|
231
285
|
// ── XML builder (exported for tests) ────────────────────────────────────────
|
|
232
|
-
export function buildPlistXml(task, akmArgv, logDir,
|
|
286
|
+
export function buildPlistXml(task, akmArgv, logDir, contextPath, target) {
|
|
233
287
|
const spec = parseSchedule(task.schedule, "launchd");
|
|
234
288
|
const trigger = translateToLaunchd(spec);
|
|
235
|
-
const invocation = buildScheduledTaskInvocation(akmArgv, task.id,
|
|
289
|
+
const invocation = buildScheduledTaskInvocation(akmArgv, task.id, contextPath, target);
|
|
236
290
|
const argv = invocation.argv;
|
|
237
291
|
const programArgs = argv.map((a) => ` <string>${escapeXml(a)}</string>`).join("\n");
|
|
238
292
|
const logPath = path.join(logDir, `${task.id}.log`);
|
|
239
293
|
const triggerXml = renderLaunchdTrigger(trigger);
|
|
240
|
-
const environment = { ...invocation.environment };
|
|
241
|
-
if (pathEnv !== undefined)
|
|
242
|
-
environment.PATH = pathEnv;
|
|
243
|
-
const envEntries = Object.entries(environment)
|
|
244
|
-
.map(([key, value]) => ` <key>${key}</key>\n <string>${escapeXml(value)}</string>`)
|
|
245
|
-
.join("\n");
|
|
246
|
-
const envVarsXml = ` <key>EnvironmentVariables</key>\n <dict>\n${envEntries}\n </dict>\n`;
|
|
247
294
|
const xml = launchdTemplate
|
|
248
295
|
.replace("<dict>\n", `<dict>\n <!-- akm-enabled:${task.enabled} -->\n`)
|
|
249
296
|
.replace("{{LABEL}}", LAUNCHD_LABEL_PREFIX + escapeXml(task.id))
|
|
250
297
|
.replace("{{PROGRAM_ARGS}}", programArgs)
|
|
251
298
|
.replaceAll("{{LOG_PATH}}", escapeXml(logPath))
|
|
252
|
-
.replace("{{ENV_VARS}}",
|
|
299
|
+
.replace("{{ENV_VARS}}", "")
|
|
253
300
|
.replace("{{TRIGGER_XML}}", triggerXml);
|
|
254
301
|
for (const char of xml) {
|
|
255
302
|
const code = char.codePointAt(0) ?? 0;
|
|
@@ -330,7 +377,7 @@ function defaultAgentsDir() {
|
|
|
330
377
|
// relative path that would write somewhere unexpected.
|
|
331
378
|
const home = os.homedir();
|
|
332
379
|
if (!home) {
|
|
333
|
-
throw new ConfigError("Cannot determine user home directory; launchd backend requires HOME to locate ~/Library/LaunchAgents.", "INVALID_CONFIG_FILE", "Set $HOME (POSIX) or the equivalent before running `akm
|
|
380
|
+
throw new ConfigError("Cannot determine user home directory; launchd backend requires HOME to locate ~/Library/LaunchAgents.", "INVALID_CONFIG_FILE", "Set $HOME (POSIX) or the equivalent before running `akm task` on macOS.");
|
|
334
381
|
}
|
|
335
382
|
return path.join(home, "Library", "LaunchAgents");
|
|
336
383
|
}
|