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
package/dist/core/parse.js
CHANGED
|
@@ -23,7 +23,10 @@
|
|
|
23
23
|
* models). Also strips leading/trailing whitespace.
|
|
24
24
|
*/
|
|
25
25
|
export function stripThinkBlocks(raw) {
|
|
26
|
-
return raw
|
|
26
|
+
return raw
|
|
27
|
+
.replace(/<think>[\s\S]*?<\/think>/gi, "")
|
|
28
|
+
.replace(/^[\s\S]*?<\/think>/i, "")
|
|
29
|
+
.trim();
|
|
27
30
|
}
|
|
28
31
|
/**
|
|
29
32
|
* Strips markdown code fences (``` or ~~~, with optional language tag).
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* Tell "this path is not there" apart from "I am not allowed to look at it"
|
|
6
|
+
* (issue #791).
|
|
7
|
+
*
|
|
8
|
+
* # Why this exists
|
|
9
|
+
*
|
|
10
|
+
* `fs.existsSync()` answers `false` for BOTH cases: it swallows every error,
|
|
11
|
+
* so `ENOENT` (genuinely absent) and `EACCES` (present, unreadable) are
|
|
12
|
+
* indistinguishable. akm used `existsSync` as its "is there an index?" gate on
|
|
13
|
+
* the read path, so an index the caller could not read was reported as an index
|
|
14
|
+
* that did not exist — `akm search` and `akm curate` returned
|
|
15
|
+
* `hits: []` with the tip *"No search index available. Run 'akm index' to build
|
|
16
|
+
* one."* at exit 0, for a populated index sitting right there on disk.
|
|
17
|
+
*
|
|
18
|
+
* That is the worst possible shape for a failure. A non-zero exit is a problem
|
|
19
|
+
* the operator can see; an empty-but-successful result is a lie that a
|
|
20
|
+
* consuming agent will confidently relay to its user. In the report that
|
|
21
|
+
* prompted this module, exactly that happened: an agent told its user akm's
|
|
22
|
+
* "vector service is unavailable and its maintenance lock is read-only" — a
|
|
23
|
+
* story it invented to explain results that claimed to be fine.
|
|
24
|
+
*
|
|
25
|
+
* # Contract
|
|
26
|
+
*
|
|
27
|
+
* `absent` means the path (or a parent component) genuinely is not there, which
|
|
28
|
+
* is a legitimate first-run state every caller already handles. `inaccessible`
|
|
29
|
+
* means the path may well exist and this process cannot determine that or read
|
|
30
|
+
* it — a caller must NEVER degrade that to an empty-but-successful result.
|
|
31
|
+
*/
|
|
32
|
+
import fs from "node:fs";
|
|
33
|
+
function errnoOf(error) {
|
|
34
|
+
const code = error?.code;
|
|
35
|
+
return typeof code === "string" ? code : undefined;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Classify `target` as {@link PathAccess}.
|
|
39
|
+
*
|
|
40
|
+
* `ENOENT`/`ENOTDIR` are `absent` — the path cannot exist as named. Everything
|
|
41
|
+
* else that fails is `inaccessible`, carrying its errno: a permission error is
|
|
42
|
+
* the common case, but a symlink loop or an I/O error are equally "present as
|
|
43
|
+
* far as anyone knows, and unusable", and silently treating them as "no index"
|
|
44
|
+
* is the bug this module exists to prevent.
|
|
45
|
+
*
|
|
46
|
+
* Statting is not enough — a file can be `stat`-able through a searchable
|
|
47
|
+
* parent while being unreadable itself — so a successful stat is confirmed with
|
|
48
|
+
* an `R_OK` access check.
|
|
49
|
+
*/
|
|
50
|
+
export function classifyPathAccess(target) {
|
|
51
|
+
try {
|
|
52
|
+
fs.statSync(target);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
const code = errnoOf(error);
|
|
56
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
57
|
+
return { access: "absent" };
|
|
58
|
+
return { access: "inaccessible", ...(code ? { code } : {}) };
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
fs.accessSync(target, fs.constants.R_OK);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
const code = errnoOf(error);
|
|
65
|
+
return { access: "inaccessible", ...(code ? { code } : {}) };
|
|
66
|
+
}
|
|
67
|
+
return { access: "present" };
|
|
68
|
+
}
|
|
69
|
+
/** True when the path is genuinely absent — the ordinary "not built yet" state. */
|
|
70
|
+
export function isPathAbsent(target) {
|
|
71
|
+
return classifyPathAccess(target).access === "absent";
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* A diagnostic line naming everything an operator needs to fix a permission
|
|
75
|
+
* problem without a second round trip: the path, the errno, the mode and owner
|
|
76
|
+
* of whatever akm *could* stat along the way, and the uid actually running.
|
|
77
|
+
*
|
|
78
|
+
* Deliberately best-effort — this runs on an error path, so a failure to gather
|
|
79
|
+
* detail must never mask the error being described.
|
|
80
|
+
*/
|
|
81
|
+
export function describeInaccessiblePath(target, code) {
|
|
82
|
+
const parts = [target];
|
|
83
|
+
if (code)
|
|
84
|
+
parts.push(`(${code})`);
|
|
85
|
+
try {
|
|
86
|
+
const stat = fs.statSync(target);
|
|
87
|
+
parts.push(`mode ${(stat.mode & 0o777).toString(8).padStart(3, "0")}, owner uid ${stat.uid}`);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// Cannot stat the file itself — describe the closest parent we CAN see,
|
|
91
|
+
// which is usually where the missing permission actually is.
|
|
92
|
+
const parent = target.slice(0, Math.max(0, target.lastIndexOf("/")));
|
|
93
|
+
if (parent) {
|
|
94
|
+
try {
|
|
95
|
+
const stat = fs.statSync(parent);
|
|
96
|
+
parts.push(`parent ${parent} is mode ${(stat.mode & 0o777).toString(8).padStart(3, "0")}, owner uid ${stat.uid}`);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Nothing further to say.
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
104
|
+
if (uid !== undefined)
|
|
105
|
+
parts.push(`running as uid ${uid}`);
|
|
106
|
+
return parts.join("; ");
|
|
107
|
+
}
|
package/dist/core/paths.js
CHANGED
|
@@ -30,7 +30,7 @@ function isUnderBunTest(env) {
|
|
|
30
30
|
* Returns true when the given path is in a directory family the OS may
|
|
31
31
|
* reap (or that the user has clearly designated as a sandbox by virtue
|
|
32
32
|
* of placing it under `/tmp` or a macOS per-user temp dir). Used to
|
|
33
|
-
* decide whether `
|
|
33
|
+
* decide whether `AKM_BUNDLE_DIR=$tmpdir` should also isolate config +
|
|
34
34
|
* cache writes (so a test harness's `akm setup --yes --dir .` cannot
|
|
35
35
|
* silently clobber the user's `~/.config/akm/config.json`). See
|
|
36
36
|
* the 2026-05-23 setup-clobbers-user-config incident
|
|
@@ -67,7 +67,7 @@ export function getConfigDir(env = process.env, platform = process.platform) {
|
|
|
67
67
|
return override;
|
|
68
68
|
// Explicit XDG override wins next — tests and operators that pre-arrange
|
|
69
69
|
// an isolated config dir via XDG_CONFIG_HOME (or %APPDATA% on Windows)
|
|
70
|
-
// must be honored as set, so the
|
|
70
|
+
// must be honored as set, so the AKM_BUNDLE_DIR transient-isolation rule
|
|
71
71
|
// below does not silently move config away from where they pointed it.
|
|
72
72
|
if (platform === "win32") {
|
|
73
73
|
const appData = env.APPDATA?.trim();
|
|
@@ -79,16 +79,16 @@ export function getConfigDir(env = process.env, platform = process.platform) {
|
|
|
79
79
|
if (xdgConfigHome)
|
|
80
80
|
return path.join(xdgConfigHome, "akm");
|
|
81
81
|
}
|
|
82
|
-
// Isolation safety: when
|
|
82
|
+
// Isolation safety: when AKM_BUNDLE_DIR points at a transient/sandbox path
|
|
83
83
|
// (/tmp, /var/tmp, /private/var/folders) AND no explicit config dir
|
|
84
|
-
// override is set, route config writes into `${
|
|
84
|
+
// override is set, route config writes into `${AKM_BUNDLE_DIR}/.akm`
|
|
85
85
|
// instead of the user's host ~/.config/akm. This prevents the documented
|
|
86
86
|
// isolation pattern
|
|
87
|
-
// AKM_DATA_DIR=/tmp/x
|
|
87
|
+
// AKM_DATA_DIR=/tmp/x AKM_BUNDLE_DIR=/tmp/x akm setup --yes --dir .
|
|
88
88
|
// from silently clobbering the host config. See
|
|
89
89
|
// the 2026-05-23 setup-clobbers-user-config incident.
|
|
90
|
-
// Daily users with a persistent
|
|
91
|
-
const stashOverride = env.
|
|
90
|
+
// Daily users with a persistent AKM_BUNDLE_DIR=~/my-stash are unaffected.
|
|
91
|
+
const stashOverride = env.AKM_BUNDLE_DIR?.trim();
|
|
92
92
|
if (stashOverride && isTransientStashPath(stashOverride)) {
|
|
93
93
|
return path.join(stashOverride, ".akm");
|
|
94
94
|
}
|
|
@@ -122,7 +122,7 @@ export function getCacheDir(env = process.env) {
|
|
|
122
122
|
// Explicit XDG/platform overrides win before the transient-stash isolation
|
|
123
123
|
// rule below — tests and operators that pre-arrange XDG_CACHE_HOME (or
|
|
124
124
|
// %LOCALAPPDATA% / %USERPROFILE% / %APPDATA% on Windows) must be honored
|
|
125
|
-
// as set, so the
|
|
125
|
+
// as set, so the AKM_BUNDLE_DIR transient rule does not silently move cache
|
|
126
126
|
// writes away from where they pointed them.
|
|
127
127
|
if (IS_WINDOWS) {
|
|
128
128
|
const localAppData = env.LOCALAPPDATA?.trim();
|
|
@@ -145,12 +145,12 @@ export function getCacheDir(env = process.env) {
|
|
|
145
145
|
if (xdgCacheHome)
|
|
146
146
|
return path.join(xdgCacheHome, "akm");
|
|
147
147
|
}
|
|
148
|
-
// Isolation safety (mirrors getConfigDir): when
|
|
148
|
+
// Isolation safety (mirrors getConfigDir): when AKM_BUNDLE_DIR points at a
|
|
149
149
|
// transient path AND no explicit cache override is set, route cache writes
|
|
150
|
-
// into `${
|
|
150
|
+
// into `${AKM_BUNDLE_DIR}/.akm/cache` so that config backups, registry-index
|
|
151
151
|
// cache, and other regenerable artifacts do not pollute the user's host
|
|
152
152
|
// ~/.cache/akm directory.
|
|
153
|
-
const stashOverride = env.
|
|
153
|
+
const stashOverride = env.AKM_BUNDLE_DIR?.trim();
|
|
154
154
|
if (stashOverride && isTransientStashPath(stashOverride)) {
|
|
155
155
|
return path.join(stashOverride, ".akm", "cache");
|
|
156
156
|
}
|
|
@@ -184,7 +184,7 @@ export function getDataDir(env = process.env, platform = process.platform) {
|
|
|
184
184
|
// user's real $XDG_DATA_HOME / ~/.local/share/akm under any condition.
|
|
185
185
|
// Any test that needs a data dir must point it at a mktemp-d directory
|
|
186
186
|
// via XDG_DATA_HOME (or AKM_DATA_DIR). The previous carve-out that only
|
|
187
|
-
// fired when
|
|
187
|
+
// fired when AKM_BUNDLE_DIR was set was a loophole: tests calling
|
|
188
188
|
// openDatabase() or getDbPath() without overriding any env var silently
|
|
189
189
|
// wrote into ~/.local/share/akm/index.db (observed: 4,183-row
|
|
190
190
|
// registry-cache pollution). Item 5 of the 0.8.x critical-review plan.
|
|
@@ -225,9 +225,9 @@ export function getMaintenanceBarrierPath() {
|
|
|
225
225
|
export function getStateDbPathInDataDir() {
|
|
226
226
|
return path.join(getDataDir(), "state.db");
|
|
227
227
|
}
|
|
228
|
-
/**
|
|
229
|
-
export function
|
|
230
|
-
return path.join(getDataDir(), "tasks", "
|
|
228
|
+
/** Content-addressed scheduler runtime descriptors. */
|
|
229
|
+
export function getTaskContextDir(env = process.env) {
|
|
230
|
+
return path.join(getDataDir(env), "tasks", "context");
|
|
231
231
|
}
|
|
232
232
|
/** Path to the akm.lock file in $DATA. */
|
|
233
233
|
export function getLockfilePath() {
|
|
@@ -246,9 +246,6 @@ export function getRegistryCacheDir() {
|
|
|
246
246
|
export function getRegistryIndexCacheDir() {
|
|
247
247
|
return path.join(getCacheDir(), "registry-index");
|
|
248
248
|
}
|
|
249
|
-
export function getBinDir() {
|
|
250
|
-
return path.join(getCacheDir(), "bin");
|
|
251
|
-
}
|
|
252
249
|
// ── Scheduled-task runtime directories (logs + history) ──────────────────────
|
|
253
250
|
export function getTaskLogDir() {
|
|
254
251
|
return path.join(getCacheDir(), "tasks", "logs");
|
|
@@ -258,7 +255,7 @@ export function getTaskHistoryDir() {
|
|
|
258
255
|
}
|
|
259
256
|
// ── Default stash directory ──────────────────────────────────────────────────
|
|
260
257
|
export function getDefaultStashDir(env = process.env) {
|
|
261
|
-
const override = env.
|
|
258
|
+
const override = env.AKM_BUNDLE_DIR?.trim();
|
|
262
259
|
if (override)
|
|
263
260
|
return override;
|
|
264
261
|
if (IS_WINDOWS) {
|
|
@@ -269,7 +266,7 @@ export function getDefaultStashDir(env = process.env) {
|
|
|
269
266
|
}
|
|
270
267
|
const home = env.HOME?.trim();
|
|
271
268
|
if (!home) {
|
|
272
|
-
throw new ConfigError("Unable to determine default
|
|
269
|
+
throw new ConfigError("Unable to determine default bundle directory. Set HOME.", "STASH_DIR_NOT_FOUND");
|
|
273
270
|
}
|
|
274
271
|
return path.join(home, "akm");
|
|
275
272
|
}
|
|
@@ -34,22 +34,20 @@ export const SCRIPT_EXTENSIONS = new Set([
|
|
|
34
34
|
".kts",
|
|
35
35
|
]);
|
|
36
36
|
/**
|
|
37
|
-
* Recognized workflow asset extensions
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* (redesign addendum, R1). `workflows/<name>` refs resolve against this list.
|
|
37
|
+
* Recognized workflow asset extensions. One format now (workflow-format-
|
|
38
|
+
* unification): orchestration graph in frontmatter, prose in the body.
|
|
39
|
+
* `workflows/<name>` refs resolve against this list.
|
|
41
40
|
*/
|
|
42
|
-
export const WORKFLOW_EXTENSIONS = [".md"
|
|
41
|
+
export const WORKFLOW_EXTENSIONS = [".md"];
|
|
43
42
|
/**
|
|
44
|
-
* Strip
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* unchanged.
|
|
43
|
+
* Strip the recognized workflow extension (`.md`) from a workflow asset
|
|
44
|
+
* *name* so `foo` and `foo.md` collapse to one canonical identity — the same
|
|
45
|
+
* collapse `workflowSpec.toCanonicalName` performs on a resolved file path.
|
|
46
|
+
* Callers that turn a `workflows/<name>` ref into run identity (the
|
|
47
|
+
* active-run guard, list/status filters) MUST route the name through this so
|
|
48
|
+
* an aliased spelling (`workflows/foo.md`) and the canonical `workflows/foo`
|
|
49
|
+
* cannot start or hide parallel runs of the same workflow. Names without the
|
|
50
|
+
* extension pass through unchanged.
|
|
53
51
|
*/
|
|
54
52
|
export function canonicalizeWorkflowName(name) {
|
|
55
53
|
const lower = name.toLowerCase();
|
package/dist/core/redaction.js
CHANGED
|
@@ -298,8 +298,74 @@ function addMappedMatches(coverageDelta, haystack, needle) {
|
|
|
298
298
|
}
|
|
299
299
|
}
|
|
300
300
|
/**
|
|
301
|
-
*
|
|
302
|
-
*
|
|
301
|
+
* Mark every occurrence of `needle` in `text` — no encoding normalization, for
|
|
302
|
+
* text that contains neither `%` nor `+` and so cannot carry an encoded form.
|
|
303
|
+
*
|
|
304
|
+
* Matching against the ORIGINAL text (rather than an accumulator being rewritten
|
|
305
|
+
* in place) is the whole point: see {@link redactSensitiveText}.
|
|
306
|
+
*/
|
|
307
|
+
function addPlainMatches(coverageDelta, text, needle) {
|
|
308
|
+
if (!needle)
|
|
309
|
+
return;
|
|
310
|
+
let offset = 0;
|
|
311
|
+
while (offset <= text.length - needle.length) {
|
|
312
|
+
const match = text.indexOf(needle, offset);
|
|
313
|
+
if (match < 0)
|
|
314
|
+
break;
|
|
315
|
+
coverageDelta[match] = coverageDelta[match] + 1;
|
|
316
|
+
coverageDelta[match + needle.length] = coverageDelta[match + needle.length] - 1;
|
|
317
|
+
offset = match + needle.length;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Redact credential-shaped substrings from arbitrary text by pattern alone —
|
|
322
|
+
* unlike {@link redactSensitiveText}, which requires the exact secret value
|
|
323
|
+
* up front, this catches credentials no caller ever knew to list. No
|
|
324
|
+
* truncation is applied; callers that need a length cap (e.g.
|
|
325
|
+
* {@link redactErrorBody} in src/llm/client.ts) apply it themselves.
|
|
326
|
+
*
|
|
327
|
+
* Targets:
|
|
328
|
+
* - `Bearer <token>` headers echoed back by a provider
|
|
329
|
+
* - `sk-…` / `sk_…` style API keys (OpenAI / Anthropic-shaped)
|
|
330
|
+
* - `key-…` / `key_…` shorthand keys
|
|
331
|
+
* - `"api_key": "…"` / `"apiKey": "…"` JSON fields
|
|
332
|
+
* - Discord webhook URLs (`discord.com/api/webhooks/<id>/<token>`) — the id
|
|
333
|
+
* is kept, only the token segment is redacted
|
|
334
|
+
* - Slack incoming-webhook URLs (`hooks.slack.com/services/<team>/<channel>/<token>`)
|
|
335
|
+
* — the team/channel ids are kept, only the trailing token is redacted
|
|
336
|
+
*/
|
|
337
|
+
export function redactCredentialPatterns(input) {
|
|
338
|
+
if (!input)
|
|
339
|
+
return "";
|
|
340
|
+
return (input
|
|
341
|
+
// Bearer tokens (case-insensitive)
|
|
342
|
+
.replace(/\bBearer\s+[A-Za-z0-9._\-+/=]+/gi, "Bearer [REDACTED]")
|
|
343
|
+
// sk-/sk_ style keys
|
|
344
|
+
.replace(/\bsk[-_][A-Za-z0-9._-]{6,}/g, "[REDACTED]")
|
|
345
|
+
// key-/key_ shorthand keys
|
|
346
|
+
.replace(/\bkey[-_][A-Za-z0-9._-]{6,}/g, "[REDACTED]")
|
|
347
|
+
// JSON-style "api_key": "...", "apiKey": "...", "api-key": "..."
|
|
348
|
+
.replace(/("(?:api[_-]?key|apiKey|authorization|token)"\s*:\s*")([^"]*)(")/gi, "$1[REDACTED]$3")
|
|
349
|
+
// Discord webhook URLs: keep the webhook id, redact the token segment.
|
|
350
|
+
.replace(/(discord(?:app)?\.com\/api\/webhooks\/\d+\/)[A-Za-z0-9_-]+/gi, "$1[REDACTED]")
|
|
351
|
+
// Slack incoming-webhook URLs: keep the team/channel ids, redact the token.
|
|
352
|
+
.replace(/(hooks\.slack\.com\/services\/[A-Za-z0-9]+\/[A-Za-z0-9]+\/)[A-Za-z0-9]+/gi, "$1[REDACTED]"));
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Replace exact sensitive values in text.
|
|
356
|
+
*
|
|
357
|
+
* Every match is located against the ORIGINAL text and the result is emitted
|
|
358
|
+
* once, so overlapping matches merge into a single `[REDACTED]` and no needle
|
|
359
|
+
* can ever match inside a token an earlier needle produced.
|
|
360
|
+
*
|
|
361
|
+
* That last property is load-bearing. This function used to take a `replaceAll`
|
|
362
|
+
* fast path that chained over a *mutating* accumulator, which meant any needle
|
|
363
|
+
* drawn from the letters of `[REDACTED]` re-matched the tokens already injected
|
|
364
|
+
* and the output grew geometrically: `redactSensitiveText("a".repeat(50),
|
|
365
|
+
* ["a","E","D","T","C","R"])` returned 32,450 characters — 649x the input. On a
|
|
366
|
+
* path where the needle set is derived from configuration or the environment,
|
|
367
|
+
* that is a memory-exhaustion hazard reachable from ordinary command output.
|
|
368
|
+
* The encoded-form path never had the bug because it always worked this way.
|
|
303
369
|
*/
|
|
304
370
|
export function redactSensitiveText(text, sensitiveValues) {
|
|
305
371
|
const values = [...new Set(sensitiveValues)]
|
|
@@ -307,22 +373,24 @@ export function redactSensitiveText(text, sensitiveValues) {
|
|
|
307
373
|
.sort((a, b) => b.length - a.length || a.localeCompare(b));
|
|
308
374
|
if (values.length === 0)
|
|
309
375
|
return text;
|
|
310
|
-
|
|
311
|
-
|
|
376
|
+
const coverageDelta = new Int32Array(text.length + 1);
|
|
377
|
+
if (text.includes("%") || text.includes("+")) {
|
|
378
|
+
// Percent-/plus-encoded text: match needle and haystack in their decoded
|
|
379
|
+
// forms, mapping hits back to source offsets.
|
|
380
|
+
const addMatchesForMode = (plusAsSpace) => {
|
|
381
|
+
const haystack = normalizeEncodedText(text, plusAsSpace);
|
|
382
|
+
for (const value of values) {
|
|
383
|
+
addMappedMatches(coverageDelta, haystack, normalizeEncodedText(value, plusAsSpace).text);
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
addMatchesForMode(false);
|
|
387
|
+
if (text.includes("+"))
|
|
388
|
+
addMatchesForMode(true);
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
312
391
|
for (const value of values)
|
|
313
|
-
|
|
314
|
-
return redacted;
|
|
392
|
+
addPlainMatches(coverageDelta, text, value);
|
|
315
393
|
}
|
|
316
|
-
const coverageDelta = new Int32Array(text.length + 1);
|
|
317
|
-
const addMatchesForMode = (plusAsSpace) => {
|
|
318
|
-
const haystack = normalizeEncodedText(text, plusAsSpace);
|
|
319
|
-
for (const value of values) {
|
|
320
|
-
addMappedMatches(coverageDelta, haystack, normalizeEncodedText(value, plusAsSpace).text);
|
|
321
|
-
}
|
|
322
|
-
};
|
|
323
|
-
addMatchesForMode(false);
|
|
324
|
-
if (text.includes("+"))
|
|
325
|
-
addMatchesForMode(true);
|
|
326
394
|
let redacted = "";
|
|
327
395
|
let coverage = 0;
|
|
328
396
|
let offset = 0;
|
|
@@ -350,7 +418,20 @@ export function redactSensitiveValue(value, sensitiveValues) {
|
|
|
350
418
|
if (Array.isArray(entry))
|
|
351
419
|
return entry.map(redact);
|
|
352
420
|
if (entry && typeof entry === "object") {
|
|
353
|
-
|
|
421
|
+
const out = {};
|
|
422
|
+
for (const [key, child] of Object.entries(entry)) {
|
|
423
|
+
const redactedKey = redactSensitiveText(key, values);
|
|
424
|
+
// Two DISTINCT keys can redact to the same string (`{a, b, ab}` under
|
|
425
|
+
// needles `a`/`b` all collapse toward `[REDACTED]`). Building this with
|
|
426
|
+
// `Object.fromEntries` kept only the last of each colliding group, so a
|
|
427
|
+
// field was silently DROPPED rather than redacted — data loss disguised
|
|
428
|
+
// as redaction. Suffix instead: the value stays, the key stays hidden.
|
|
429
|
+
let finalKey = redactedKey;
|
|
430
|
+
for (let n = 2; Object.hasOwn(out, finalKey); n++)
|
|
431
|
+
finalKey = `${redactedKey} (${n})`;
|
|
432
|
+
out[finalKey] = redact(child);
|
|
433
|
+
}
|
|
434
|
+
return out;
|
|
354
435
|
}
|
|
355
436
|
return entry;
|
|
356
437
|
};
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* The ONE allowlist-based child-environment primitive.
|
|
6
|
+
*
|
|
7
|
+
* Two akm code paths spawn a child from an explicit list of environment
|
|
8
|
+
* variable NAMES — the agent-CLI spawn wrapper
|
|
9
|
+
* (`integrations/agent/spawn.ts`, `profile.envPassthrough`) and the workflow
|
|
10
|
+
* `exec` unit runner (`workflows/exec/exec-unit.ts`) — and both start from an
|
|
11
|
+
* EMPTY environment and copy through named entries with
|
|
12
|
+
* {@link collectAllowlistedEnv}. Keeping that in one leaf module is what makes
|
|
13
|
+
* "allowlist" a single reviewable mechanism instead of two implementations
|
|
14
|
+
* that drift apart.
|
|
15
|
+
*
|
|
16
|
+
* NOT covered: the opencode-sdk server spawn
|
|
17
|
+
* (`integrations/harnesses/opencode-sdk/sdk-runner.ts`) keeps its own
|
|
18
|
+
* hard-coded name list and does not route through here, so it gets neither the
|
|
19
|
+
* platform floor below nor PATH supplementation.
|
|
20
|
+
*
|
|
21
|
+
* A LEAF: node built-ins only, so both the integrations layer and the workflow
|
|
22
|
+
* engine can import it without opening a cycle.
|
|
23
|
+
*
|
|
24
|
+
* @module core/spawn-env
|
|
25
|
+
*/
|
|
26
|
+
import fs from "node:fs";
|
|
27
|
+
import os from "node:os";
|
|
28
|
+
import path from "node:path";
|
|
29
|
+
/**
|
|
30
|
+
* The baseline env names every allowlisted akm child receives regardless of
|
|
31
|
+
* what it runs: process identity (`HOME`, `USER`), tool resolution (`PATH`),
|
|
32
|
+
* locale (`LANG`, `LC_ALL`), terminal (`TERM`), scratch space (`TMPDIR`), and
|
|
33
|
+
* akm's own event provenance (`AKM_EVENT_SOURCE` — machine traffic, never a
|
|
34
|
+
* secret). BOTH allowlists extend it — the agent-CLI profiles'
|
|
35
|
+
* `COMMON_PASSTHROUGH` (`integrations/agent/profiles.ts`) and the workflow
|
|
36
|
+
* exec unit's `EXEC_DEFAULT_ENV_PASSTHROUGH` (`workflows/exec/exec-unit.ts`)
|
|
37
|
+
* — so a baseline name cannot drift into one child-spawn path but not the
|
|
38
|
+
* other. NOTE: profile `envPassthrough` is frozen into workflow engine
|
|
39
|
+
* snapshots, so growing this list changes frozen-plan content — extend
|
|
40
|
+
* deliberately.
|
|
41
|
+
*/
|
|
42
|
+
export const COMMON_SPAWN_ENV_PASSTHROUGH = [
|
|
43
|
+
"HOME",
|
|
44
|
+
"PATH",
|
|
45
|
+
"USER",
|
|
46
|
+
"LANG",
|
|
47
|
+
"LC_ALL",
|
|
48
|
+
"TERM",
|
|
49
|
+
"TMPDIR",
|
|
50
|
+
"AKM_EVENT_SOURCE",
|
|
51
|
+
];
|
|
52
|
+
/**
|
|
53
|
+
* The names Windows itself requires of ANY child, whatever the caller's
|
|
54
|
+
* allowlist says. Applied at build time rather than added to
|
|
55
|
+
* {@link COMMON_SPAWN_ENV_PASSTHROUGH} because profile `envPassthrough` is
|
|
56
|
+
* frozen into workflow engine snapshots: growing the shared list would change
|
|
57
|
+
* the bytes — and so the hashes — of every plan already on disk, to express
|
|
58
|
+
* something that is not a policy choice at all.
|
|
59
|
+
*
|
|
60
|
+
* `SystemRoot`, `SystemDrive` and `WINDIR` are what PROCESS CREATION reads;
|
|
61
|
+
* without them the loader cannot find system DLLs and the spawn fails before
|
|
62
|
+
* the command runs. Without `PATHEXT` Windows never tries `bun.exe`/`bun.cmd`,
|
|
63
|
+
* so a `bin: "bun"` profile is unresolvable, and `COMSPEC` is how `.bat`/`.cmd`
|
|
64
|
+
* targets resolve at all. The rest are the win32 analogues of baseline names
|
|
65
|
+
* the POSIX side already grants — `HOME` (`USERPROFILE`, `HOMEDRIVE`,
|
|
66
|
+
* `HOMEPATH`) and `TMPDIR` (`TEMP`, `TMP`). None is a secret.
|
|
67
|
+
*
|
|
68
|
+
* Config and install roots (`APPDATA`, `LOCALAPPDATA`, `ProgramFiles`, …) are
|
|
69
|
+
* deliberately NOT here: a child can be created and can resolve its command
|
|
70
|
+
* without them, so which allowlist wants them stays a per-caller decision.
|
|
71
|
+
*
|
|
72
|
+
* THE definition of the floor. The workflow exec allowlist
|
|
73
|
+
* (`EXEC_DEFAULT_ENV_PASSTHROUGH`) spreads this constant rather than
|
|
74
|
+
* re-spelling it, because that list is also consumed on POSIX — where
|
|
75
|
+
* {@link spawnEnvNamesFor} appends nothing — and the names still have to be
|
|
76
|
+
* requestable there for a win32 run of the same workflow.
|
|
77
|
+
*/
|
|
78
|
+
export const WIN32_SPAWN_ENV_FLOOR = [
|
|
79
|
+
"SystemRoot",
|
|
80
|
+
"SystemDrive",
|
|
81
|
+
"WINDIR",
|
|
82
|
+
"COMSPEC",
|
|
83
|
+
"PATHEXT",
|
|
84
|
+
"USERPROFILE",
|
|
85
|
+
"HOMEDRIVE",
|
|
86
|
+
"HOMEPATH",
|
|
87
|
+
"TEMP",
|
|
88
|
+
"TMP",
|
|
89
|
+
];
|
|
90
|
+
/**
|
|
91
|
+
* The effective allowlist for `platform`: the caller's names, plus any floor
|
|
92
|
+
* the operating system requires of every child regardless of allowlist.
|
|
93
|
+
* Exported for tests, which must be able to ask for a platform they are not
|
|
94
|
+
* running on.
|
|
95
|
+
*/
|
|
96
|
+
export function spawnEnvNamesFor(names, platform = process.platform) {
|
|
97
|
+
const effective = [...names];
|
|
98
|
+
if (platform !== "win32")
|
|
99
|
+
return effective;
|
|
100
|
+
// Deduped by EXACT spelling, never case-folded: {@link collectAllowlistedEnv}
|
|
101
|
+
// looks each surviving name up with exactly this case, and a `source` that is
|
|
102
|
+
// a plain object — the agent spawn's `envSource` seam — does not case-fold.
|
|
103
|
+
// Suppressing `SystemRoot` because the caller happened to write `SYSTEMROOT`
|
|
104
|
+
// would therefore drop the loader-critical variable the floor exists to
|
|
105
|
+
// guarantee. Keeping both spellings is harmless: the win32 environment is
|
|
106
|
+
// itself case-insensitive, so they resolve to the same value.
|
|
107
|
+
const present = new Set(effective);
|
|
108
|
+
for (const name of WIN32_SPAWN_ENV_FLOOR) {
|
|
109
|
+
if (!present.has(name))
|
|
110
|
+
effective.push(name);
|
|
111
|
+
}
|
|
112
|
+
return effective;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Build a child environment from an allowlist: start EMPTY and copy through
|
|
116
|
+
* exactly the named variables that exist in `source`. Names absent from the
|
|
117
|
+
* source are simply absent from the child (never an empty string, which many
|
|
118
|
+
* tools treat as "set but blank").
|
|
119
|
+
*
|
|
120
|
+
* `PATH`, when it comes through, is supplemented for scheduler contexts — see
|
|
121
|
+
* {@link supplementPathForSchedulerContext}. That happens here rather than in
|
|
122
|
+
* each caller so a child spawned from cron/launchd/Task Scheduler can find the
|
|
123
|
+
* user's toolchain no matter which spawn path reached it. On win32 the names
|
|
124
|
+
* in {@link WIN32_SPAWN_ENV_FLOOR} come through too, for the same reason: the
|
|
125
|
+
* spawn cannot succeed without them, whichever caller built the list.
|
|
126
|
+
*/
|
|
127
|
+
export function collectAllowlistedEnv(names, source = process.env) {
|
|
128
|
+
const env = {};
|
|
129
|
+
for (const name of spawnEnvNamesFor(names)) {
|
|
130
|
+
const value = source[name];
|
|
131
|
+
if (value !== undefined)
|
|
132
|
+
env[name] = value;
|
|
133
|
+
}
|
|
134
|
+
if (env.PATH !== undefined) {
|
|
135
|
+
env.PATH = supplementPathForSchedulerContext(env.PATH);
|
|
136
|
+
}
|
|
137
|
+
return env;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Answers already computed by {@link supplementPathForSchedulerContext}, keyed
|
|
141
|
+
* by the input PATH and the home directory it was computed against.
|
|
142
|
+
*
|
|
143
|
+
* Correctness envelope: the answer is a pure function of those two, the
|
|
144
|
+
* platform, and which candidate directories exist on disk. The platform cannot
|
|
145
|
+
* change under a running process, and the other two are in the key — so the one
|
|
146
|
+
* thing the memo assumes is that a candidate directory does not APPEAR while
|
|
147
|
+
* akm runs. Answering the rest of a run the way its start was answered is what
|
|
148
|
+
* a shell's own command-path caching already does, and the cost of not
|
|
149
|
+
* memoizing is paid on every child-env build: two PATH splits and up to seven
|
|
150
|
+
* SYNCHRONOUS `existsSync` probes, on the event loop a 10 000-unit fan-out
|
|
151
|
+
* shares with the run's lease heartbeat.
|
|
152
|
+
*
|
|
153
|
+
* Bounded so a caller that somehow varies its PATH cannot grow it without
|
|
154
|
+
* limit; a spawn path only ever sees a handful of distinct PATH strings, so
|
|
155
|
+
* the reset is effectively unreachable in practice.
|
|
156
|
+
*/
|
|
157
|
+
const supplementedPaths = new Map();
|
|
158
|
+
const SUPPLEMENTED_PATH_MEMO_MAX = 64;
|
|
159
|
+
/**
|
|
160
|
+
* Supplement `existingPath` with well-known user binary directories when
|
|
161
|
+
* running in a scheduler context (cron/launchd) where PATH is stripped.
|
|
162
|
+
*
|
|
163
|
+
* Detection heuristic: if the current PATH does not contain the user's home
|
|
164
|
+
* directory, we are likely in a stripped scheduler env. In an interactive
|
|
165
|
+
* shell the user's home almost always appears (e.g. ~/.bun/bin, ~/.cargo/bin).
|
|
166
|
+
*
|
|
167
|
+
* Only directories that actually exist on disk are prepended, and only if
|
|
168
|
+
* they are not already present, so interactive-shell PATH ordering is never
|
|
169
|
+
* disturbed.
|
|
170
|
+
*
|
|
171
|
+
* Memoized per input PATH — see {@link supplementedPaths} for what that
|
|
172
|
+
* assumes.
|
|
173
|
+
*/
|
|
174
|
+
export function supplementPathForSchedulerContext(existingPath) {
|
|
175
|
+
const home = os.homedir();
|
|
176
|
+
// NUL-joined so no pair of (home, PATH) can collide onto one key: no path
|
|
177
|
+
// component can contain a NUL.
|
|
178
|
+
const key = `${home}\u0000${existingPath}`;
|
|
179
|
+
const memoized = supplementedPaths.get(key);
|
|
180
|
+
if (memoized !== undefined)
|
|
181
|
+
return memoized;
|
|
182
|
+
const supplemented = computeSupplementedPath(existingPath, home);
|
|
183
|
+
if (supplementedPaths.size >= SUPPLEMENTED_PATH_MEMO_MAX)
|
|
184
|
+
supplementedPaths.clear();
|
|
185
|
+
supplementedPaths.set(key, supplemented);
|
|
186
|
+
return supplemented;
|
|
187
|
+
}
|
|
188
|
+
function computeSupplementedPath(existingPath, home) {
|
|
189
|
+
// A home of `/` (system crontab, launchd, service accounts) or of `""`
|
|
190
|
+
// prefixes EVERY entry, so a prefix test would read the most stripped
|
|
191
|
+
// environments there are as interactive and skip the repair they exist for.
|
|
192
|
+
const comparableHome = home === "" || home === path.sep ? undefined : home;
|
|
193
|
+
// If PATH already contains the home directory, we are in an interactive
|
|
194
|
+
// shell — skip supplementation entirely. Compared on a path boundary: a
|
|
195
|
+
// sibling home (`/home/alice` next to `/home/al`) is not this user's.
|
|
196
|
+
const isUnderHome = (dir) => comparableHome !== undefined && (dir === comparableHome || dir.startsWith(comparableHome + path.sep));
|
|
197
|
+
if (existingPath.split(path.delimiter).some(isUnderHome)) {
|
|
198
|
+
return existingPath;
|
|
199
|
+
}
|
|
200
|
+
const candidates = pathCandidatesForCurrentPlatform(home);
|
|
201
|
+
const existing = new Set(existingPath.split(path.delimiter).filter(Boolean));
|
|
202
|
+
const toAdd = candidates.filter((d) => !existing.has(d) && fs.existsSync(d));
|
|
203
|
+
if (toAdd.length === 0)
|
|
204
|
+
return existingPath;
|
|
205
|
+
return [...toAdd, existingPath].filter(Boolean).join(path.delimiter);
|
|
206
|
+
}
|
|
207
|
+
function pathCandidatesForCurrentPlatform(home) {
|
|
208
|
+
if (process.platform === "win32") {
|
|
209
|
+
// Windows: Bun + Cargo + Scoop + Chocolatey + system tools. Order favors
|
|
210
|
+
// user-local installs over machine-global so the user's chosen toolchain
|
|
211
|
+
// wins. These paths are commonly stripped from Task Scheduler / service
|
|
212
|
+
// environments, mirroring the cron/launchd problem on POSIX.
|
|
213
|
+
const localAppData = process.env.LOCALAPPDATA ?? path.join(home, "AppData", "Local");
|
|
214
|
+
const userProfile = process.env.USERPROFILE ?? home;
|
|
215
|
+
const programFiles = process.env.ProgramFiles ?? "C:\\Program Files";
|
|
216
|
+
return [
|
|
217
|
+
path.join(userProfile, ".bun", "bin"),
|
|
218
|
+
path.join(localAppData, "Programs", "bun"),
|
|
219
|
+
path.join(userProfile, ".cargo", "bin"),
|
|
220
|
+
path.join(localAppData, "Programs", "Git", "cmd"),
|
|
221
|
+
path.join(userProfile, "scoop", "shims"),
|
|
222
|
+
path.join(programFiles, "Git", "cmd"),
|
|
223
|
+
"C:\\ProgramData\\chocolatey\\bin",
|
|
224
|
+
];
|
|
225
|
+
}
|
|
226
|
+
return [
|
|
227
|
+
path.join(home, ".bun", "bin"),
|
|
228
|
+
path.join(home, ".cargo", "bin"),
|
|
229
|
+
path.join(home, ".local", "bin"),
|
|
230
|
+
"/opt/homebrew/bin",
|
|
231
|
+
"/opt/homebrew/sbin",
|
|
232
|
+
"/usr/local/bin",
|
|
233
|
+
];
|
|
234
|
+
}
|