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
|
@@ -1,14 +1,16 @@
|
|
|
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 { randomBytes } from "node:crypto";
|
|
4
5
|
import fs from "node:fs";
|
|
5
6
|
import path from "node:path";
|
|
6
7
|
import { akmAdapter } from "../../core/adapter/adapters/akm-adapter.js";
|
|
7
8
|
import { stashDirNames } from "../../core/asset/asset-placement.js";
|
|
8
9
|
import { resolveStashDir } from "../../core/common.js";
|
|
9
|
-
import { getSources, loadConfig } from "../../core/config/config.js";
|
|
10
|
-
import { UsageError } from "../../core/errors.js";
|
|
10
|
+
import { getSources, loadConfig, resolveConfiguredSources } from "../../core/config/config.js";
|
|
11
|
+
import { NotFoundError, UsageError } from "../../core/errors.js";
|
|
11
12
|
import { sanitizeCommitMessage } from "../../core/git-message.js";
|
|
13
|
+
import { lockContentRootFor } from "../../integrations/lockfile.js";
|
|
12
14
|
import { runGit } from "./git-install.js";
|
|
13
15
|
import { getCachePaths, parseGitRepoUrl } from "./git-provider.js";
|
|
14
16
|
/**
|
|
@@ -44,15 +46,25 @@ export function listGitChangedPaths(repoDir) {
|
|
|
44
46
|
}
|
|
45
47
|
return paths;
|
|
46
48
|
}
|
|
49
|
+
export class GitStashPushError extends Error {
|
|
50
|
+
commit;
|
|
51
|
+
constructor(message, commit) {
|
|
52
|
+
super(message);
|
|
53
|
+
this.name = "GitStashPushError";
|
|
54
|
+
this.commit = commit;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const GIT_PUSH_TIMEOUT_MS = 120_000;
|
|
58
|
+
const ZERO_OID = "0000000000000000000000000000000000000000";
|
|
47
59
|
/**
|
|
48
|
-
* Resolve the writable
|
|
49
|
-
*
|
|
50
|
-
* primary stash writable, otherwise `undefined` (leave the per-stash default
|
|
51
|
-
* untouched). Extracted so `akm sync`, `akm improve`'s end-of-run sync, and the
|
|
52
|
-
* CLI body all derive this identically instead of re-copying the expression.
|
|
60
|
+
* Resolve the writable flag for an end-of-run / `akm sync` commit from the
|
|
61
|
+
* configured default bundle.
|
|
53
62
|
*/
|
|
54
63
|
export function resolveWritableOverride(config) {
|
|
55
|
-
|
|
64
|
+
const source = resolveConfiguredSources(config).find((entry) => entry.name === config.defaultBundle);
|
|
65
|
+
if (!source)
|
|
66
|
+
return undefined;
|
|
67
|
+
return (source.writable ?? source.type === "filesystem") ? true : undefined;
|
|
56
68
|
}
|
|
57
69
|
/**
|
|
58
70
|
* Commit (and optionally push) local changes in a git-backed stash.
|
|
@@ -71,7 +83,7 @@ export function resolveWritableOverride(config) {
|
|
|
71
83
|
* primary stash dir (e.g. `akm improve`'s end-of-run sync, whose pre-commit
|
|
72
84
|
* gate validates that exact directory) pass it here so the gate and the commit
|
|
73
85
|
* operate on the SAME directory instead of independently calling
|
|
74
|
-
* `resolveStashDir(
|
|
86
|
+
* `resolveStashDir()`. When absent, behaviour is unchanged.
|
|
75
87
|
*/
|
|
76
88
|
export function saveGitStash(name, message, writableOverride, options) {
|
|
77
89
|
// `push: false` (from `akm sync --no-push`) commits but never pushes, even
|
|
@@ -84,27 +96,45 @@ export function saveGitStash(name, message, writableOverride, options) {
|
|
|
84
96
|
// Empty result falls back to the timestamped default.
|
|
85
97
|
const sanitized = message ? sanitizeCommitMessage(message) : "";
|
|
86
98
|
const commitMessage = sanitized || `akm save ${timestamp}`;
|
|
99
|
+
const transactionId = options?.transactionId;
|
|
100
|
+
if (transactionId !== undefined && !/^[A-Za-z0-9._-]{1,128}$/.test(transactionId)) {
|
|
101
|
+
throw new Error("Invalid Git transaction identifier.");
|
|
102
|
+
}
|
|
87
103
|
let repoDir;
|
|
88
104
|
let writable = false;
|
|
105
|
+
let managedContentRoot;
|
|
89
106
|
if (name) {
|
|
90
107
|
const config = loadConfig();
|
|
91
108
|
const stash = findGitStashByTarget(getSources(config), name);
|
|
109
|
+
// NotFoundError (exit 1), not UsageError (exit 2): the argument is
|
|
110
|
+
// well-formed, the bundle just isn't configured.
|
|
92
111
|
if (!stash)
|
|
93
|
-
throw new
|
|
112
|
+
throw new NotFoundError(`No git bundle found with name "${name}"`, "SOURCE_NOT_FOUND");
|
|
94
113
|
if (stash.type !== "git") {
|
|
95
114
|
throw new UsageError(`Stash "${name}" is not a git stash (type: ${stash.type})`);
|
|
96
115
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
116
|
+
const lockedRoot = lockContentRootFor(stash.name, stash.type);
|
|
117
|
+
if (lockedRoot) {
|
|
118
|
+
const topLevel = runGit(["-C", lockedRoot, "rev-parse", "--show-toplevel"]);
|
|
119
|
+
if (topLevel.status !== 0 || !topLevel.stdout.trim()) {
|
|
120
|
+
throw new UsageError(`Managed Git stash "${name}" is not a checkout at ${lockedRoot}`);
|
|
121
|
+
}
|
|
122
|
+
repoDir = path.resolve(topLevel.stdout.trim());
|
|
123
|
+
managedContentRoot = path.resolve(lockedRoot);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
if (!stash.url)
|
|
127
|
+
throw new UsageError(`Stash "${name}" has no URL configured`);
|
|
128
|
+
const repo = parseGitRepoUrl(stash.url);
|
|
129
|
+
repoDir = getCachePaths(repo.canonicalUrl).repoDir;
|
|
130
|
+
}
|
|
101
131
|
writable = stash.writable === true;
|
|
102
132
|
}
|
|
103
133
|
else {
|
|
104
134
|
// Honour an explicit primary-stash dir override (keeps the improve gate and
|
|
105
135
|
// the commit on the same directory); otherwise resolve the default.
|
|
106
|
-
repoDir = options?.repoDir ?? resolveStashDir(
|
|
107
|
-
// Allow caller to
|
|
136
|
+
repoDir = options?.repoDir ?? resolveStashDir();
|
|
137
|
+
// Allow the caller to pass the configured default bundle's writability.
|
|
108
138
|
if (writableOverride !== undefined) {
|
|
109
139
|
writable = writableOverride;
|
|
110
140
|
}
|
|
@@ -113,12 +143,11 @@ export function saveGitStash(name, message, writableOverride, options) {
|
|
|
113
143
|
if (!isGitBackedStash(repoDir)) {
|
|
114
144
|
return { committed: false, pushed: false, skipped: true, reason: "not a git repository", output: "" };
|
|
115
145
|
}
|
|
116
|
-
// Nothing to commit?
|
|
117
146
|
const statusResult = runGit(["-C", repoDir, "status", "--porcelain"]);
|
|
118
147
|
if (statusResult.error || statusResult.status !== 0) {
|
|
119
148
|
throw new Error(`git status failed: ${statusResult.error?.message || statusResult.stderr?.trim() || "unknown error"}`);
|
|
120
149
|
}
|
|
121
|
-
if (!statusResult.stdout.trim()) {
|
|
150
|
+
if (!statusResult.stdout.trim() && options?.paths === undefined) {
|
|
122
151
|
return { committed: false, pushed: false, skipped: false, output: "nothing to commit, working tree clean" };
|
|
123
152
|
}
|
|
124
153
|
// Scoped staging (#476 + the auto-sync incident): NEVER refuse akm's commit
|
|
@@ -129,123 +158,383 @@ export function saveGitStash(name, message, writableOverride, options) {
|
|
|
129
158
|
//
|
|
130
159
|
// Precedence:
|
|
131
160
|
// 1. Explicit modified-file list (`options.paths`) — stage exactly those.
|
|
132
|
-
// 2.
|
|
133
|
-
//
|
|
134
|
-
// WIP. This preserves the #476 protection WITHOUT refusing.
|
|
161
|
+
// 2. A lock-backed content root, or managed adapter pathspecs for an
|
|
162
|
+
// ordinary stash. Both stay below AKM's resolved content boundary.
|
|
135
163
|
// 3. No resolvable managed path — no commit. Broad staging is never safe
|
|
136
164
|
// because it can absorb unrelated work already present in the index.
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const stagedResult = runGit([
|
|
147
|
-
"-C",
|
|
148
|
-
repoDir,
|
|
149
|
-
"diff",
|
|
150
|
-
"--cached",
|
|
151
|
-
"--quiet",
|
|
152
|
-
...(staged.pathspecs ? ["--", ...staged.pathspecs] : []),
|
|
153
|
-
]);
|
|
154
|
-
if (stagedResult.status === 0) {
|
|
165
|
+
const managedRelativeRoot = managedContentRoot
|
|
166
|
+
? path.relative(repoDir, managedContentRoot).replaceAll(path.sep, "/")
|
|
167
|
+
: "";
|
|
168
|
+
const changedPaths = listGitChangedPaths(repoDir);
|
|
169
|
+
const requestedPaths = normalizeExactPaths(repoDir, options?.paths ??
|
|
170
|
+
(managedRelativeRoot && managedRelativeRoot !== ".." && !managedRelativeRoot.startsWith("../")
|
|
171
|
+
? changedPaths.filter((changedPath) => changedPath === managedRelativeRoot || changedPath.startsWith(`${managedRelativeRoot}/`))
|
|
172
|
+
: managedFallbackPaths(repoDir, changedPaths)));
|
|
173
|
+
if (requestedPaths.length === 0) {
|
|
155
174
|
return { committed: false, pushed: false, skipped: false, output: "nothing to commit" };
|
|
156
175
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
repoDir,
|
|
162
|
-
"-c",
|
|
163
|
-
"user.name=akm",
|
|
164
|
-
"-c",
|
|
165
|
-
"user.email=akm@local",
|
|
166
|
-
"commit",
|
|
167
|
-
"-m",
|
|
168
|
-
commitMessage,
|
|
169
|
-
...(staged.pathspecs ? ["--only", "--", ...staged.pathspecs] : []),
|
|
170
|
-
]);
|
|
171
|
-
if (commitResult.status !== 0) {
|
|
172
|
-
throw new Error(`git commit failed: ${commitResult.stderr?.trim() || "unknown error"}`);
|
|
176
|
+
assertNoIgnoredExactPaths(repoDir, requestedPaths);
|
|
177
|
+
const baseHead = readOptionalHead(repoDir);
|
|
178
|
+
if (options?.expectedBaseHead !== undefined && (baseHead ?? null) !== options.expectedBaseHead) {
|
|
179
|
+
throw new Error(`Git target advanced before its exact-path commit could be created.`);
|
|
173
180
|
}
|
|
174
|
-
// Push only when there is a remote AND the stash is marked writable
|
|
175
181
|
const remoteResult = runGit(["-C", repoDir, "remote"]);
|
|
176
182
|
if (remoteResult.status !== 0) {
|
|
177
183
|
throw new Error(`git remote failed: ${remoteResult.stderr?.trim() || "unknown error"}`);
|
|
178
184
|
}
|
|
179
185
|
const hasRemote = remoteResult.stdout.trim().length > 0;
|
|
180
|
-
|
|
181
|
-
|
|
186
|
+
const pushTarget = hasRemote && writable && allowPush ? readActualUpstream(repoDir, baseHead) : undefined;
|
|
187
|
+
const exactCommit = createExactPathCommit(repoDir, {
|
|
188
|
+
baseHead,
|
|
189
|
+
commitMessage,
|
|
190
|
+
paths: requestedPaths,
|
|
191
|
+
transactionId,
|
|
192
|
+
expectedSnapshots: options?.expectedSnapshots,
|
|
193
|
+
});
|
|
194
|
+
if (!exactCommit) {
|
|
195
|
+
return { committed: false, pushed: false, skipped: false, output: "nothing to commit" };
|
|
196
|
+
}
|
|
197
|
+
if (!pushTarget) {
|
|
198
|
+
return {
|
|
199
|
+
committed: true,
|
|
200
|
+
pushed: false,
|
|
201
|
+
skipped: false,
|
|
202
|
+
output: `commit ${exactCommit}`,
|
|
203
|
+
commit: exactCommit,
|
|
204
|
+
};
|
|
182
205
|
}
|
|
183
|
-
const pushResult = runGit([
|
|
206
|
+
const pushResult = runGit([
|
|
207
|
+
"-C",
|
|
208
|
+
repoDir,
|
|
209
|
+
"push",
|
|
210
|
+
`--force-with-lease=${pushTarget.mergeRef}:${pushTarget.upstreamHead}`,
|
|
211
|
+
pushTarget.remote,
|
|
212
|
+
`${exactCommit}:${pushTarget.mergeRef}`,
|
|
213
|
+
], { timeout: GIT_PUSH_TIMEOUT_MS });
|
|
184
214
|
if (pushResult.status !== 0) {
|
|
185
|
-
throw new
|
|
215
|
+
throw new GitStashPushError(`git push failed: ${pushResult.stderr?.trim() || "unknown error"}`, exactCommit);
|
|
186
216
|
}
|
|
187
217
|
return {
|
|
188
218
|
committed: true,
|
|
189
219
|
pushed: true,
|
|
190
220
|
skipped: false,
|
|
191
|
-
output:
|
|
221
|
+
output: pushResult.stdout.trim() || "changes committed and pushed",
|
|
222
|
+
commit: exactCommit,
|
|
192
223
|
};
|
|
193
224
|
}
|
|
194
|
-
|
|
195
|
-
* Stage akm's changes in `repoDir` using the scoped-staging precedence
|
|
196
|
-
* documented at the call site (#476). The returned pathspecs are reduced to
|
|
197
|
-
* exact staged files so `git commit --only` cannot absorb unrelated index state.
|
|
198
|
-
*
|
|
199
|
-
* @param paths Optional explicit repo-relative paths akm wrote this run. When
|
|
200
|
-
* provided and non-empty, exactly those are staged (chunked to stay under
|
|
201
|
-
* argv length limits). Otherwise we fall back to the managed pathspecs, and
|
|
202
|
-
* skip the commit when no managed pathspec exists on disk.
|
|
203
|
-
*/
|
|
204
|
-
function stageScopedChanges(repoDir, paths) {
|
|
205
|
-
if (paths !== undefined && paths.length === 0)
|
|
206
|
-
return { ok: true, pathspecs: [] };
|
|
207
|
-
// Precedence 1: explicit modified-file list.
|
|
208
|
-
const explicit = (paths ?? []).filter((p) => typeof p === "string" && p.length > 0);
|
|
209
|
-
if (explicit.length > 0) {
|
|
210
|
-
const ok = addPathspecsChunked(repoDir, explicit);
|
|
211
|
-
return { ok, pathspecs: ok ? listStagedPaths(repoDir, explicit) : [] };
|
|
212
|
-
}
|
|
213
|
-
// Precedence 2: managed pathspecs that exist on disk (adapter-owned stash
|
|
214
|
-
// subdirs + `.akm`). WI-3.1: the owned subdirs are now sourced from the `akm`
|
|
215
|
-
// adapter's `directoryList()` — behavior-identical to the placement
|
|
216
|
-
// stash-subdir set, with `stashDirNames()` kept live as the fallback.
|
|
225
|
+
function managedFallbackPaths(repoDir, changedPaths) {
|
|
217
226
|
const ownedDirs = akmAdapter.directoryList?.({ id: "akm", adapter: "akm", root: repoDir, writable: false }) ?? stashDirNames();
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
227
|
+
const prefixes = [...ownedDirs, ".akm"];
|
|
228
|
+
return changedPaths.filter((changedPath) => prefixes.some((prefix) => changedPath === prefix || changedPath.startsWith(`${prefix}/`)));
|
|
229
|
+
}
|
|
230
|
+
function normalizeExactPaths(repoDir, paths) {
|
|
231
|
+
const normalized = [...new Set(paths.map((value) => value.replaceAll(path.sep, "/")))];
|
|
232
|
+
for (const filePath of normalized) {
|
|
233
|
+
const absolute = path.resolve(repoDir, filePath);
|
|
234
|
+
const relative = path.relative(repoDir, absolute).replaceAll(path.sep, "/");
|
|
235
|
+
if (!filePath ||
|
|
236
|
+
filePath.includes("\0") ||
|
|
237
|
+
path.isAbsolute(filePath) ||
|
|
238
|
+
relative !== filePath ||
|
|
239
|
+
relative === ".." ||
|
|
240
|
+
relative.startsWith("../")) {
|
|
241
|
+
throw new Error(`Unsafe exact Git path: ${filePath || "<empty>"}`);
|
|
242
|
+
}
|
|
222
243
|
}
|
|
223
|
-
|
|
224
|
-
return { ok: true, pathspecs: [] };
|
|
244
|
+
return normalized;
|
|
225
245
|
}
|
|
226
|
-
function
|
|
227
|
-
const
|
|
228
|
-
|
|
246
|
+
function runExactPathChunks(repoDir, command, paths, options = {}) {
|
|
247
|
+
const CHUNK = 500;
|
|
248
|
+
const results = [];
|
|
249
|
+
for (let i = 0; i < paths.length; i += CHUNK) {
|
|
250
|
+
results.push(runGit(["--literal-pathspecs", "-C", repoDir, ...command, "--", ...paths.slice(i, i + CHUNK)], {
|
|
251
|
+
env: options.env,
|
|
252
|
+
}));
|
|
253
|
+
}
|
|
254
|
+
return results;
|
|
255
|
+
}
|
|
256
|
+
export function listIgnoredExactPaths(repoDir, paths) {
|
|
257
|
+
if (paths.length === 0)
|
|
229
258
|
return [];
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
259
|
+
const input = `${paths.join("\0")}\0`;
|
|
260
|
+
const result = runGit(["-C", repoDir, "check-ignore", "-z", "--stdin"], { input });
|
|
261
|
+
if (result.status === 1)
|
|
262
|
+
return [];
|
|
263
|
+
if (result.status !== 0) {
|
|
264
|
+
throw new Error(`git check-ignore failed: ${result.stderr.trim() || "unknown error"}`);
|
|
265
|
+
}
|
|
266
|
+
return result.stdout.split("\0").filter(Boolean);
|
|
234
267
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
268
|
+
export function assertNoIgnoredExactPaths(repoDir, paths) {
|
|
269
|
+
const ignored = listIgnoredExactPaths(repoDir, normalizeExactPaths(repoDir, paths));
|
|
270
|
+
if (ignored.length > 0) {
|
|
271
|
+
throw new UsageError(`Exact Git publication path is ignored: ${ignored[0]}. Update .gitignore or choose a tracked destination before writing.`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/** Reject exact staged/unstaged paths before an AKM filesystem mutation. */
|
|
275
|
+
export function assertGitExactPathsClean(repoDir, paths) {
|
|
276
|
+
const normalized = normalizeExactPaths(repoDir, paths);
|
|
277
|
+
assertNoIgnoredExactPaths(repoDir, normalized);
|
|
278
|
+
for (const result of runExactPathChunks(repoDir, ["status", "--porcelain=v1", "-z", "--untracked-files=all"], normalized)) {
|
|
279
|
+
if (result.status !== 0) {
|
|
280
|
+
throw new Error(`git status failed: ${result.stderr.trim() || "unknown error"}`);
|
|
281
|
+
}
|
|
282
|
+
if (result.stdout.length > 0) {
|
|
283
|
+
const record = result.stdout.split("\0").find(Boolean) ?? "";
|
|
284
|
+
throw new UsageError(`Exact Git operation path has staged or unstaged work: ${record.slice(3)}. Commit, stash, or discard that path before retrying.`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function readOptionalHead(repoDir) {
|
|
289
|
+
const result = runGit(["-C", repoDir, "rev-parse", "--verify", "HEAD"]);
|
|
290
|
+
return result.status === 0 && result.stdout.trim() ? result.stdout.trim() : undefined;
|
|
291
|
+
}
|
|
292
|
+
function readBranchRef(repoDir) {
|
|
293
|
+
const result = runGit(["-C", repoDir, "symbolic-ref", "--quiet", "HEAD"]);
|
|
294
|
+
if (result.status !== 0 || !result.stdout.trim()) {
|
|
295
|
+
throw new UsageError(`Writable Git target at ${repoDir} is detached from a branch.`);
|
|
296
|
+
}
|
|
297
|
+
return result.stdout.trim();
|
|
298
|
+
}
|
|
299
|
+
function readActualUpstream(repoDir, baseHead) {
|
|
300
|
+
if (!baseHead)
|
|
301
|
+
throw new UsageError(`Writable Git target at ${repoDir} has no commit to publish.`);
|
|
302
|
+
const branchRef = readBranchRef(repoDir);
|
|
303
|
+
const branch = branchRef.replace(/^refs\/heads\//, "");
|
|
304
|
+
const remote = runGit(["-C", repoDir, "config", "--get", `branch.${branch}.remote`]);
|
|
305
|
+
const merge = runGit(["-C", repoDir, "config", "--get", `branch.${branch}.merge`]);
|
|
306
|
+
const upstream = runGit(["-C", repoDir, "rev-parse", "--verify", "@{u}"]);
|
|
307
|
+
if (remote.status !== 0 || merge.status !== 0 || upstream.status !== 0) {
|
|
308
|
+
throw new UsageError(`Writable Git target at ${repoDir} has no configured upstream branch.`);
|
|
309
|
+
}
|
|
310
|
+
const upstreamHead = upstream.stdout.trim();
|
|
311
|
+
if (!upstreamHead || upstreamHead !== baseHead) {
|
|
312
|
+
throw new UsageError(`Writable Git target at ${repoDir} is not synchronized with its actual upstream.`);
|
|
313
|
+
}
|
|
314
|
+
return { remote: remote.stdout.trim(), mergeRef: merge.stdout.trim(), upstreamHead };
|
|
315
|
+
}
|
|
316
|
+
function readIndexEntries(repoDir, paths) {
|
|
317
|
+
const entries = new Map(paths.map((filePath) => [filePath, []]));
|
|
318
|
+
for (const result of runExactPathChunks(repoDir, ["ls-files", "--stage", "-z"], paths)) {
|
|
319
|
+
if (result.status !== 0)
|
|
320
|
+
throw new Error(`Cannot inspect Git index: ${result.stderr.trim() || "unknown error"}`);
|
|
321
|
+
for (const record of result.stdout.split("\0")) {
|
|
322
|
+
if (!record)
|
|
323
|
+
continue;
|
|
324
|
+
const tab = record.indexOf("\t");
|
|
325
|
+
const match = record.slice(0, tab).match(/^(\d+) ([0-9a-f]+) (\d+)$/);
|
|
326
|
+
if (tab < 0 || !match)
|
|
327
|
+
throw new Error(`Cannot parse Git index entry.`);
|
|
328
|
+
const filePath = record.slice(tab + 1);
|
|
329
|
+
entries.get(filePath)?.push({ mode: match[1], oid: match[2], stage: Number(match[3]) });
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return entries;
|
|
333
|
+
}
|
|
334
|
+
function readTreeStates(repoDir, treeish, paths) {
|
|
335
|
+
const states = new Map(paths.map((filePath) => [filePath, null]));
|
|
336
|
+
if (!treeish)
|
|
337
|
+
return states;
|
|
338
|
+
for (const result of runExactPathChunks(repoDir, ["ls-tree", "-z", treeish], paths)) {
|
|
244
339
|
if (result.status !== 0)
|
|
340
|
+
throw new Error(`Cannot inspect Git tree: ${result.stderr.trim() || "unknown error"}`);
|
|
341
|
+
for (const record of result.stdout.split("\0")) {
|
|
342
|
+
if (!record)
|
|
343
|
+
continue;
|
|
344
|
+
const tab = record.indexOf("\t");
|
|
345
|
+
const match = record.slice(0, tab).match(/^(\d+) blob ([0-9a-f]+)$/);
|
|
346
|
+
if (tab < 0 || !match)
|
|
347
|
+
throw new Error(`Exact Git path is not a blob.`);
|
|
348
|
+
const mode = match[1];
|
|
349
|
+
if (mode !== "100644" && mode !== "100755" && mode !== "120000") {
|
|
350
|
+
throw new Error(`Unsupported Git mode ${mode}.`);
|
|
351
|
+
}
|
|
352
|
+
states.set(record.slice(tab + 1), { mode, oid: match[2] });
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return states;
|
|
356
|
+
}
|
|
357
|
+
function assertIndexMatchesBase(paths, indexEntries, baseStates) {
|
|
358
|
+
if (indexMatchesTree(paths, indexEntries, baseStates))
|
|
359
|
+
return;
|
|
360
|
+
const dirtyPath = paths.find((filePath) => {
|
|
361
|
+
const entries = indexEntries.get(filePath) ?? [];
|
|
362
|
+
const base = baseStates.get(filePath) ?? null;
|
|
363
|
+
return !(base === null
|
|
364
|
+
? entries.length === 0
|
|
365
|
+
: entries.length === 1 &&
|
|
366
|
+
entries[0]?.stage === 0 &&
|
|
367
|
+
entries[0]?.mode === base.mode &&
|
|
368
|
+
entries[0]?.oid === base.oid);
|
|
369
|
+
});
|
|
370
|
+
throw new UsageError(`Exact Git operation path has staged work: ${dirtyPath ?? paths[0]}. Commit, stash, or discard that path before retrying.`);
|
|
371
|
+
}
|
|
372
|
+
function indexMatchesTree(paths, indexEntries, treeStates) {
|
|
373
|
+
for (const filePath of paths) {
|
|
374
|
+
const entries = indexEntries.get(filePath) ?? [];
|
|
375
|
+
const state = treeStates.get(filePath) ?? null;
|
|
376
|
+
if (!(state === null
|
|
377
|
+
? entries.length === 0
|
|
378
|
+
: entries.length === 1 &&
|
|
379
|
+
entries[0]?.stage === 0 &&
|
|
380
|
+
entries[0]?.mode === state.mode &&
|
|
381
|
+
entries[0]?.oid === state.oid)) {
|
|
245
382
|
return false;
|
|
383
|
+
}
|
|
246
384
|
}
|
|
247
385
|
return true;
|
|
248
386
|
}
|
|
387
|
+
/** Repair only the stale base index left by a crash after an exact commit CAS. */
|
|
388
|
+
export function reconcileGitExactPathIndex(repoDir, baseCommit, commit, paths) {
|
|
389
|
+
const normalized = normalizeExactPaths(repoDir, paths);
|
|
390
|
+
const current = readIndexEntries(repoDir, normalized);
|
|
391
|
+
const committedStates = readTreeStates(repoDir, commit, normalized);
|
|
392
|
+
if (indexMatchesTree(normalized, current, committedStates))
|
|
393
|
+
return;
|
|
394
|
+
const baseStates = readTreeStates(repoDir, baseCommit, normalized);
|
|
395
|
+
if (!indexMatchesTree(normalized, current, baseStates)) {
|
|
396
|
+
throw new UsageError(`Exact Git operation path has staged work after transaction recovery. Commit, stash, or discard it before retrying.`);
|
|
397
|
+
}
|
|
398
|
+
for (const result of runExactPathChunks(repoDir, ["reset", "--quiet", commit], normalized)) {
|
|
399
|
+
if (result.status !== 0)
|
|
400
|
+
throw new Error(`git index cleanup failed during exact transaction recovery`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
function captureWorktreeState(repoDir, filePath) {
|
|
404
|
+
const absolutePath = path.join(repoDir, filePath);
|
|
405
|
+
let stat;
|
|
406
|
+
try {
|
|
407
|
+
stat = fs.lstatSync(absolutePath);
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
if (error.code === "ENOENT")
|
|
411
|
+
return null;
|
|
412
|
+
throw error;
|
|
413
|
+
}
|
|
414
|
+
let mode;
|
|
415
|
+
let result;
|
|
416
|
+
if (stat.isSymbolicLink()) {
|
|
417
|
+
mode = "120000";
|
|
418
|
+
result = runGit(["-C", repoDir, "hash-object", "--stdin"], { input: fs.readlinkSync(absolutePath) });
|
|
419
|
+
}
|
|
420
|
+
else if (stat.isFile()) {
|
|
421
|
+
mode = stat.mode & 0o111 ? "100755" : "100644";
|
|
422
|
+
result = runGit(["-C", repoDir, "hash-object", `--path=${filePath}`, "--", absolutePath]);
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
throw new Error(`Exact Git publication path is not a file: ${filePath}`);
|
|
426
|
+
}
|
|
427
|
+
if (result.status !== 0 || !result.stdout.trim()) {
|
|
428
|
+
throw new Error(`Cannot hash exact Git publication path: ${filePath}`);
|
|
429
|
+
}
|
|
430
|
+
return { oid: result.stdout.trim(), mode };
|
|
431
|
+
}
|
|
432
|
+
function samePathState(left, right) {
|
|
433
|
+
return left === null ? right === null : right !== null && left.oid === right.oid && left.mode === right.mode;
|
|
434
|
+
}
|
|
435
|
+
function assertWorktreeMatchesExpected(repoDir, paths, expected) {
|
|
436
|
+
for (const filePath of paths) {
|
|
437
|
+
if (!samePathState(captureWorktreeState(repoDir, filePath), expected.get(filePath) ?? null)) {
|
|
438
|
+
throw new Error(`Exact Git publication path changed while committing: ${filePath}`);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function sameIndexEntries(left, right) {
|
|
443
|
+
return JSON.stringify([...left]) === JSON.stringify([...right]);
|
|
444
|
+
}
|
|
445
|
+
function resetRealIndexPaths(repoDir, commit, paths, initial) {
|
|
446
|
+
const current = readIndexEntries(repoDir, paths);
|
|
447
|
+
if (!sameIndexEntries(initial, current))
|
|
448
|
+
return;
|
|
449
|
+
for (const result of runExactPathChunks(repoDir, ["reset", "--quiet", commit], paths)) {
|
|
450
|
+
if (result.status !== 0)
|
|
451
|
+
throw new Error(`git index cleanup failed after exact commit`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
function createExactPathCommit(repoDir, options) {
|
|
455
|
+
const branchRef = readBranchRef(repoDir);
|
|
456
|
+
const baseStates = readTreeStates(repoDir, options.baseHead, options.paths);
|
|
457
|
+
const initialIndex = readIndexEntries(repoDir, options.paths);
|
|
458
|
+
assertIndexMatchesBase(options.paths, initialIndex, baseStates);
|
|
459
|
+
const expected = new Map();
|
|
460
|
+
for (const filePath of options.paths) {
|
|
461
|
+
expected.set(filePath, options.expectedSnapshots && Object.hasOwn(options.expectedSnapshots, filePath)
|
|
462
|
+
? (options.expectedSnapshots[filePath] ?? null)
|
|
463
|
+
: captureWorktreeState(repoDir, filePath));
|
|
464
|
+
}
|
|
465
|
+
const gitIndex = runGit(["-C", repoDir, "rev-parse", "--git-path", "index"]);
|
|
466
|
+
if (gitIndex.status !== 0 || !gitIndex.stdout.trim())
|
|
467
|
+
throw new Error(`Cannot resolve Git index path.`);
|
|
468
|
+
const realIndexPath = path.resolve(repoDir, gitIndex.stdout.trim());
|
|
469
|
+
const temporaryIndex = `${realIndexPath}.akm-${process.pid}-${randomBytes(6).toString("hex")}`;
|
|
470
|
+
const env = { GIT_INDEX_FILE: temporaryIndex };
|
|
471
|
+
try {
|
|
472
|
+
const seeded = options.baseHead
|
|
473
|
+
? runGit(["-C", repoDir, "read-tree", options.baseHead], { env })
|
|
474
|
+
: runGit(["-C", repoDir, "read-tree", "--empty"], { env });
|
|
475
|
+
if (seeded.status !== 0)
|
|
476
|
+
throw new Error(`Cannot seed temporary Git index: ${seeded.stderr.trim()}`);
|
|
477
|
+
const stagePaths = options.paths.filter((filePath) => (expected.get(filePath) ?? null) !== null || (baseStates.get(filePath) ?? null) !== null);
|
|
478
|
+
for (const result of runExactPathChunks(repoDir, ["add", "-A"], stagePaths, { env })) {
|
|
479
|
+
if (result.status !== 0)
|
|
480
|
+
throw new Error(`Cannot stage exact Git paths: ${result.stderr.trim()}`);
|
|
481
|
+
}
|
|
482
|
+
const tree = runGit(["-C", repoDir, "write-tree"], { env });
|
|
483
|
+
if (tree.status !== 0 || !tree.stdout.trim())
|
|
484
|
+
throw new Error(`Cannot write exact Git tree.`);
|
|
485
|
+
const treeOid = tree.stdout.trim();
|
|
486
|
+
const committedStates = readTreeStates(repoDir, treeOid, options.paths);
|
|
487
|
+
for (const filePath of options.paths) {
|
|
488
|
+
if (!samePathState(committedStates.get(filePath) ?? null, expected.get(filePath) ?? null)) {
|
|
489
|
+
throw new Error(`Exact Git publication path changed while committing: ${filePath}`);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
assertWorktreeMatchesExpected(repoDir, options.paths, expected);
|
|
493
|
+
const baseTreeResult = options.baseHead
|
|
494
|
+
? runGit(["-C", repoDir, "rev-parse", `${options.baseHead}^{tree}`])
|
|
495
|
+
: undefined;
|
|
496
|
+
if (baseTreeResult && (baseTreeResult.status !== 0 || !baseTreeResult.stdout.trim())) {
|
|
497
|
+
throw new Error(`Cannot inspect base Git tree.`);
|
|
498
|
+
}
|
|
499
|
+
const baseTree = baseTreeResult?.stdout.trim() ?? "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
500
|
+
if (treeOid === baseTree)
|
|
501
|
+
return null;
|
|
502
|
+
const body = options.transactionId
|
|
503
|
+
? `${options.commitMessage}\n\nAKM-Transaction: ${options.transactionId}\n`
|
|
504
|
+
: `${options.commitMessage}\n`;
|
|
505
|
+
const commit = runGit([
|
|
506
|
+
"-C",
|
|
507
|
+
repoDir,
|
|
508
|
+
"-c",
|
|
509
|
+
"user.name=akm",
|
|
510
|
+
"-c",
|
|
511
|
+
"user.email=akm@local",
|
|
512
|
+
"commit-tree",
|
|
513
|
+
treeOid,
|
|
514
|
+
...(options.baseHead ? ["-p", options.baseHead] : []),
|
|
515
|
+
], { input: body });
|
|
516
|
+
if (commit.status !== 0 || !commit.stdout.trim()) {
|
|
517
|
+
throw new Error(`git commit-tree failed: ${commit.stderr.trim() || "unknown error"}`);
|
|
518
|
+
}
|
|
519
|
+
const commitOid = commit.stdout.trim();
|
|
520
|
+
const currentBranch = readBranchRef(repoDir);
|
|
521
|
+
const currentHead = readOptionalHead(repoDir);
|
|
522
|
+
if (currentBranch !== branchRef || currentHead !== options.baseHead) {
|
|
523
|
+
throw new Error(`Git target changed before its exact commit could be attached.`);
|
|
524
|
+
}
|
|
525
|
+
assertWorktreeMatchesExpected(repoDir, options.paths, expected);
|
|
526
|
+
const update = runGit(["-C", repoDir, "update-ref", branchRef, commitOid, options.baseHead ?? ZERO_OID]);
|
|
527
|
+
if (update.status !== 0) {
|
|
528
|
+
throw new Error(`Git target advanced before its exact commit could be attached.`);
|
|
529
|
+
}
|
|
530
|
+
resetRealIndexPaths(repoDir, commitOid, options.paths, initialIndex);
|
|
531
|
+
return commitOid;
|
|
532
|
+
}
|
|
533
|
+
finally {
|
|
534
|
+
fs.rmSync(temporaryIndex, { force: true });
|
|
535
|
+
fs.rmSync(`${temporaryIndex}.lock`, { force: true });
|
|
536
|
+
}
|
|
537
|
+
}
|
|
249
538
|
function findGitStashByTarget(stashes, target) {
|
|
250
539
|
return stashes.find((stash) => matchesGitStashTarget(stash, target));
|
|
251
540
|
}
|
|
@@ -8,6 +8,6 @@
|
|
|
8
8
|
//
|
|
9
9
|
// Re-exported here so every consumer (and every `spyOn(gitProvider, …)` test)
|
|
10
10
|
// keeps importing from a single module namespace.
|
|
11
|
-
export { classifyCloneFailure, cloneRepo,
|
|
11
|
+
export { classifyCloneFailure, cloneRepo, inspectGitUpstream, runGit, syncExistingWritableCheckout, } from "./git-install.js";
|
|
12
12
|
export { ensureGitMirror, GitSourceProvider, getCachePaths, parseGitRepoUrl, syncMirroredRepo, } from "./git-provider.js";
|
|
13
|
-
export { isGitBackedStash, listGitChangedPaths, resolveWritableOverride, saveGitStash, } from "./git-stash.js";
|
|
13
|
+
export { GitStashPushError, isGitBackedStash, listGitChangedPaths, resolveWritableOverride, saveGitStash, } from "./git-stash.js";
|