akm-cli 0.9.0-rc.0 → 0.9.0-rc.13
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 +1283 -22
- package/README.md +62 -37
- package/SECURITY.md +46 -31
- package/dist/akm +162 -38
- package/dist/akm-migrate +44 -0
- package/dist/assets/backends/schtasks-template.xml +2 -1
- package/dist/assets/hints/cli-hints-full.md +268 -118
- package/dist/assets/hints/cli-hints-short.md +87 -24
- package/dist/assets/{profiles → improve-strategies}/catchup.json +3 -1
- package/dist/assets/{profiles → improve-strategies}/consolidate.json +3 -1
- package/dist/assets/{profiles → improve-strategies}/default.json +6 -7
- package/dist/assets/improve-strategies/frequent.json +15 -0
- package/dist/assets/{profiles → improve-strategies}/graph-refresh.json +4 -2
- package/dist/assets/{profiles → improve-strategies}/memory-focus.json +4 -1
- package/dist/assets/{profiles → improve-strategies}/proactive-maintenance.json +5 -5
- package/dist/assets/{profiles → improve-strategies}/quick.json +4 -2
- package/dist/assets/improve-strategies/reflect-distill.json +30 -0
- package/dist/assets/{profiles → improve-strategies}/thorough.json +1 -1
- package/dist/assets/prompts/consolidate-system.md +5 -5
- package/dist/assets/prompts/extract-session.md +2 -6
- package/dist/assets/prompts/memory-infer-user.md +2 -3
- package/dist/assets/prompts/reflect-llm-framed-contract.md +11 -0
- package/dist/assets/prompts/reflect-llm-schema-contract.md +3 -0
- package/dist/assets/prompts/reflect-output-repair.md +3 -0
- package/dist/assets/prompts/workflow-unit-preamble.md +26 -0
- package/dist/assets/stash-skeleton/README.md +38 -10
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +8 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +8 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +14 -1
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +13 -1
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +9 -1
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +11 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +9 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +9 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +8 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +100 -0
- package/dist/assets/stash-skeleton/facts/conventions/domains.md +64 -0
- package/dist/assets/stash-skeleton/facts/conventions/organization.md +136 -0
- package/dist/assets/tasks/core/extract.yml +3 -2
- package/dist/assets/tasks/core/improve.yml +2 -1
- package/dist/assets/tasks/core/index-refresh.yml +1 -0
- package/dist/assets/tasks/core/sync.yml +1 -0
- package/dist/assets/tasks/core/version-check.yml +2 -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 +5 -4
- package/dist/assets/workflows/workflow-template.md +31 -15
- package/dist/cli/invocation.js +279 -0
- package/dist/cli/parse-args.js +5 -90
- package/dist/cli/retired-commands.js +78 -0
- package/dist/cli/shared.js +158 -48
- package/dist/cli-node.mjs +2 -1
- package/dist/cli.js +747 -293
- package/dist/commands/agent/agent-dispatch.js +19 -18
- package/dist/commands/agent/agent-support.js +0 -24
- package/dist/commands/agent/contribute-cli.js +43 -97
- package/dist/commands/completions.js +80 -23
- package/dist/commands/config-cli.js +44 -281
- package/dist/commands/env/env-binding.js +99 -0
- package/dist/commands/env/env-cli.js +84 -224
- package/dist/commands/env/env.js +12 -163
- package/dist/commands/env/marker-path.js +6 -0
- package/dist/commands/env/secret-cli.js +45 -61
- package/dist/commands/env/secret.js +32 -62
- package/dist/commands/feedback-cli.js +179 -85
- package/dist/commands/health/accept-rate.js +58 -0
- package/dist/commands/health/advisories.js +7 -8
- package/dist/commands/health/checks.js +279 -94
- package/dist/commands/health/html-report.js +197 -578
- package/dist/commands/health/improve-metrics.js +277 -246
- package/dist/commands/health/llm-usage.js +19 -19
- package/dist/commands/health/md-report.js +16 -7
- package/dist/commands/health/metrics.js +67 -32
- package/dist/commands/health/renderers.js +47 -0
- package/dist/commands/health/report-view-model.js +508 -0
- package/dist/commands/health/stash-exposure.js +1 -1
- package/dist/commands/health/surfaces.js +16 -56
- package/dist/commands/health/task-runs.js +3 -67
- package/dist/{migrate-storage-node.mjs → commands/health/types-checks.js} +1 -5
- package/dist/commands/health/types-improve.js +29 -0
- package/dist/{output/text/save.js → commands/health/types-metrics.js} +1 -2
- package/dist/commands/health/types-result.js +7 -0
- package/dist/commands/health/types-runs.js +4 -0
- package/dist/commands/health/types-session-log.js +4 -0
- package/dist/commands/health/types-windows.js +4 -0
- package/dist/commands/health/types.js +26 -21
- package/dist/commands/health/windows.js +2 -3
- package/dist/commands/health.js +296 -167
- package/dist/commands/improve/anti-collapse.js +5 -5
- package/dist/commands/improve/autonomy-gate.js +68 -0
- package/dist/commands/improve/collapse-detector.js +65 -52
- package/dist/commands/improve/consolidate/chunking.js +9 -7
- package/dist/commands/improve/consolidate/eligibility.js +1 -23
- package/dist/commands/improve/consolidate/merge.js +4 -0
- package/dist/commands/improve/consolidate.js +454 -1354
- package/dist/commands/improve/content-hash.js +39 -0
- package/dist/commands/improve/distill/content-repair.js +4 -10
- package/dist/commands/improve/distill/promote-memory.js +89 -64
- package/dist/commands/improve/distill/quality-gate.js +118 -42
- package/dist/commands/improve/distill-guards.js +1 -1
- package/dist/commands/improve/distill-promotion-policy.js +33 -888
- package/dist/commands/improve/distill.js +607 -363
- package/dist/commands/improve/eligibility.js +165 -79
- package/dist/commands/improve/extract-cli.js +35 -126
- package/dist/commands/improve/extract-prompt.js +6 -35
- package/dist/commands/improve/extract.js +640 -391
- package/dist/commands/improve/feedback-valence.js +2 -12
- package/dist/commands/improve/improve-cli.js +134 -135
- package/dist/commands/improve/improve-result-file.js +30 -50
- package/dist/commands/improve/improve-run-types.js +4 -0
- package/dist/commands/improve/improve-strategies.js +135 -0
- package/dist/commands/improve/improve.js +904 -701
- package/dist/commands/improve/locks.js +64 -111
- package/dist/commands/improve/loop-stages.js +1110 -923
- package/dist/commands/improve/memory/derived-ref.js +124 -0
- package/dist/commands/improve/memory/memory-belief.js +79 -7
- package/dist/commands/improve/memory/memory-contradiction-detect.js +49 -52
- package/dist/commands/improve/memory/memory-improve.js +25 -37
- package/dist/commands/improve/outcome-loop.js +25 -88
- package/dist/commands/improve/preparation.js +1034 -813
- package/dist/commands/improve/proactive-maintenance.js +34 -9
- package/dist/commands/improve/proposal-envelope.js +31 -0
- package/dist/commands/improve/reflect.js +983 -794
- package/dist/commands/improve/run-context.js +119 -0
- package/dist/commands/improve/salience.js +24 -127
- package/dist/commands/improve/session-asset.js +7 -3
- package/dist/commands/improve/shared.js +14 -34
- package/dist/commands/improve/source-identity.js +28 -0
- package/dist/commands/improve/triage.js +20 -17
- package/dist/commands/lint/base-linter.js +340 -313
- package/dist/commands/lint/env-key-rules.js +31 -47
- package/dist/commands/lint/index.js +185 -30
- package/dist/commands/{events.js → log.js} +28 -38
- package/dist/commands/migrate-cli.js +54 -0
- package/dist/commands/migration-tool.js +55 -0
- package/dist/commands/observability-cli.js +70 -208
- package/dist/commands/proposal/diff-format.js +50 -0
- package/dist/commands/proposal/drain-policies.js +0 -6
- package/dist/commands/proposal/drain.js +91 -40
- package/dist/commands/proposal/proposal-cli.js +134 -132
- package/dist/commands/proposal/proposal-types.js +56 -0
- package/dist/commands/proposal/proposal.js +83 -65
- package/dist/commands/proposal/propose-cli.js +88 -0
- package/dist/commands/proposal/propose.js +105 -88
- package/dist/commands/proposal/repository.js +1303 -278
- package/dist/commands/proposal/validators/proposal-quality-validators.js +16 -6
- package/dist/commands/proposal/validators/proposal-validators.js +61 -12
- package/dist/commands/proposal/validators/proposals.js +6 -8
- package/dist/commands/read/curate.js +78 -73
- package/dist/commands/read/knowledge.js +510 -13
- package/dist/commands/read/registry-search.js +2 -2
- package/dist/commands/read/remember-cli.js +84 -15
- package/dist/commands/read/search-cli.js +203 -96
- package/dist/commands/read/search.js +126 -94
- package/dist/commands/read/show.js +226 -250
- package/dist/commands/registry-cli.js +34 -60
- package/dist/commands/remember.js +18 -57
- package/dist/commands/sources/add-cli.js +104 -49
- package/dist/commands/sources/bundle-cli.js +166 -0
- package/dist/commands/sources/bundle-config-ops.js +63 -0
- package/dist/commands/sources/info.js +27 -15
- package/dist/commands/sources/init.js +30 -40
- package/dist/commands/sources/installed-stashes.js +469 -172
- package/dist/commands/sources/migration-help.js +7 -4
- package/dist/commands/sources/schema-repair.js +10 -9
- package/dist/commands/sources/self-update.js +182 -121
- package/dist/commands/sources/source-add.js +169 -178
- package/dist/commands/sources/source-clone.js +144 -41
- package/dist/commands/sources/source-manage.js +94 -59
- package/dist/commands/sources/sources-cli.js +64 -205
- package/dist/commands/sources/stash-cli.js +91 -54
- package/dist/commands/sources/stash-skeleton.js +1 -1
- package/dist/commands/tasks/tasks-cli.js +106 -104
- package/dist/commands/tasks/tasks.js +445 -262
- package/dist/commands/workflow-cli.js +232 -121
- package/dist/core/action-contributors.js +1 -1
- package/dist/core/activation-policy.js +49 -0
- package/dist/core/adapter/adapters/agent-skills-adapter.js +181 -0
- package/dist/core/adapter/adapters/akm-adapter.js +528 -0
- package/dist/core/adapter/adapters/akm-lint.js +392 -0
- package/dist/core/adapter/adapters/akm-metadata.js +387 -0
- package/dist/core/adapter/adapters/akm-task-adapter.js +149 -0
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +180 -0
- package/dist/core/adapter/adapters/claude-adapter.js +61 -0
- package/dist/core/adapter/adapters/dotenv-adapter.js +187 -0
- package/dist/core/adapter/adapters/generic-files-adapter.js +119 -0
- package/dist/core/adapter/adapters/index.js +80 -0
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +419 -0
- package/dist/core/adapter/adapters/okf-adapter.js +391 -0
- package/dist/core/adapter/adapters/opencode-adapter.js +68 -0
- package/dist/core/adapter/adapters/shared.js +286 -0
- package/dist/core/adapter/adapters/tool-dir-shared.js +217 -0
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +155 -0
- package/dist/core/adapter/bundle-adapter.js +4 -0
- package/dist/core/adapter/detect-adapter.js +17 -0
- package/dist/core/adapter/recognize-match.js +44 -0
- package/dist/core/adapter/registry.js +56 -0
- package/dist/core/adapter/types.js +4 -0
- package/dist/core/asset/akm-markdown.js +30 -0
- package/dist/core/asset/asset-placement.js +243 -0
- package/dist/core/asset/asset-ref.js +110 -79
- package/dist/core/asset/asset-serialize.js +20 -0
- package/dist/core/asset/frontmatter.js +28 -12
- package/dist/core/asset/markdown.js +40 -51
- package/dist/core/asset/resolve-ref.js +274 -0
- package/dist/core/asset/stash-meta.js +2 -2
- package/dist/core/bundle-id.js +51 -0
- package/dist/core/common.js +281 -86
- package/dist/core/config/config-io.js +42 -128
- package/dist/core/config/config-schema.js +233 -834
- package/dist/core/config/config-sources.js +162 -39
- package/dist/core/config/config-types.js +16 -11
- package/dist/core/config/config-version.js +29 -0
- package/dist/core/config/config-walker.js +126 -37
- package/dist/core/config/config.js +154 -331
- package/dist/core/config/deep-merge.js +41 -0
- package/dist/core/config/engine-semantics.js +28 -0
- package/dist/core/config/experimental.js +21 -0
- package/dist/core/config/schema/embedding.js +38 -0
- package/dist/core/config/schema/engines.js +116 -0
- package/dist/core/config/schema/experimental.js +47 -0
- package/dist/core/config/schema/feedback.js +31 -0
- package/dist/core/config/schema/improve-processes.js +389 -0
- package/dist/core/config/schema/improve.js +94 -0
- package/dist/core/config/schema/index-config.js +176 -0
- package/dist/core/config/schema/output.js +18 -0
- package/dist/core/config/schema/primitives.js +94 -0
- package/dist/core/config/schema/search.js +30 -0
- package/dist/core/config/schema/setup.js +18 -0
- package/dist/core/config/schema/sources-bundles.js +169 -0
- package/dist/core/config/schema/workflow.js +29 -0
- package/dist/core/env-secret-ref.js +155 -20
- package/dist/core/errors.js +17 -15
- package/dist/core/events-types.js +4 -0
- package/dist/core/events.js +46 -128
- package/dist/core/extra-params.js +62 -0
- package/dist/core/file-change.js +17 -0
- package/dist/core/file-lock.js +202 -57
- package/dist/core/fs-txn.js +392 -0
- package/dist/core/git-message.js +59 -0
- package/dist/core/improve-result.js +167 -0
- package/dist/core/json-schema.js +142 -0
- package/dist/core/lesson-lint.js +1 -17
- package/dist/core/logs-db.js +1 -1
- package/dist/core/maintenance-barrier.js +135 -0
- package/dist/core/migration-operation.js +44 -0
- package/dist/core/mutation-target.js +78 -0
- package/dist/core/paths.js +22 -25
- package/dist/core/platform.js +10 -0
- package/dist/core/recognition-util.js +128 -0
- package/dist/core/redaction.js +392 -0
- package/dist/core/standards/resolve-standards-context.js +36 -65
- package/dist/core/standards/resolve-stash-standards.js +2 -2
- package/dist/core/standards/resolve-type-conventions.js +5 -5
- package/dist/core/state/migrations.js +242 -11
- package/dist/core/state-db.js +98 -10
- package/dist/core/structured.js +1 -1
- package/dist/core/subprocess.js +303 -0
- package/dist/core/text-truncation.js +9 -5
- package/dist/core/time.js +20 -0
- package/dist/core/type-presentation.js +130 -0
- package/dist/core/warn.js +0 -3
- package/dist/core/write-source.js +834 -118
- package/dist/indexer/bundle-identity-guard.js +92 -0
- package/dist/indexer/db/graph-db.js +1 -25
- package/dist/indexer/db/llm-cache.js +1 -1
- package/dist/indexer/ensure-index.js +30 -9
- package/dist/indexer/graph/graph-boost.js +9 -30
- package/dist/indexer/graph/graph-extraction.js +41 -27
- package/dist/indexer/graph/graph-types.js +4 -0
- package/dist/indexer/index-writer-lock.js +93 -49
- package/dist/indexer/index-written-assets.js +100 -53
- package/dist/indexer/indexer.js +746 -329
- package/dist/indexer/init.js +18 -25
- package/dist/indexer/installations.js +142 -0
- package/dist/indexer/passes/dir-staleness.js +18 -10
- package/dist/indexer/passes/memory-inference.js +25 -15
- package/dist/indexer/passes/metadata.js +412 -243
- package/dist/indexer/scan/doc-to-entry.js +160 -0
- package/dist/indexer/scan/drain-dir.js +134 -0
- package/dist/indexer/search/db-search.js +292 -108
- package/dist/indexer/search/fts-query.js +64 -0
- package/dist/indexer/search/ranking-contributors.js +145 -25
- package/dist/indexer/search/ranking-types.js +4 -0
- package/dist/indexer/search/ranking.js +28 -71
- package/dist/indexer/search/search-attribution.js +67 -0
- package/dist/indexer/search/search-fields.js +18 -3
- package/dist/indexer/search/search-hit-enrichers.js +30 -40
- package/dist/indexer/search/search-source.js +157 -111
- package/dist/indexer/search/semantic-status.js +4 -1
- package/dist/indexer/usage/usage-events.js +10 -30
- package/dist/indexer/walk/file-context.js +3 -45
- package/dist/indexer/walk/matchers.js +42 -34
- package/dist/indexer/walk/path-resolver.js +11 -5
- package/dist/indexer/walk/walker.js +42 -14
- package/dist/integrations/agent/builder-shared.js +7 -0
- package/dist/integrations/agent/builders.js +5 -56
- package/dist/integrations/agent/config.js +3 -143
- package/dist/integrations/agent/detect.js +17 -2
- package/dist/integrations/agent/engine-resolution.js +231 -0
- package/dist/integrations/agent/index.js +1 -2
- package/dist/integrations/agent/model-aliases.js +16 -2
- package/dist/integrations/agent/profiles.js +36 -62
- package/dist/integrations/agent/prompts.js +46 -18
- package/dist/integrations/agent/runner-dispatch.js +93 -4
- package/dist/integrations/agent/runner.js +76 -208
- package/dist/integrations/agent/spawn.js +88 -196
- package/dist/integrations/harnesses/aider/agent-builder.js +114 -0
- package/dist/integrations/harnesses/aider/index.js +48 -0
- package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
- package/dist/integrations/harnesses/amazonq/agent-builder.js +147 -0
- package/dist/integrations/harnesses/amazonq/index.js +45 -0
- package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
- package/dist/integrations/harnesses/claude/agent-builder.js +46 -8
- package/dist/integrations/harnesses/claude/config-import.js +1 -3
- package/dist/integrations/harnesses/claude/index.js +24 -35
- package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
- package/dist/integrations/harnesses/claude/session-log.js +27 -75
- package/dist/integrations/harnesses/codex/agent-builder.js +138 -0
- package/dist/integrations/harnesses/codex/index.js +52 -0
- package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
- package/dist/integrations/harnesses/copilot/agent-builder.js +122 -0
- package/dist/integrations/harnesses/copilot/index.js +48 -0
- package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
- package/dist/integrations/harnesses/gemini/agent-builder.js +120 -0
- package/dist/integrations/harnesses/gemini/index.js +48 -0
- package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
- package/dist/integrations/harnesses/ids.js +24 -0
- package/dist/integrations/harnesses/index.js +54 -34
- package/dist/integrations/harnesses/opencode/agent-builder.js +23 -5
- package/dist/integrations/harnesses/opencode/config-import.js +1 -3
- package/dist/integrations/harnesses/opencode/index.js +14 -32
- package/dist/integrations/harnesses/opencode/session-log.js +67 -125
- package/dist/integrations/harnesses/opencode-sdk/harness.js +51 -0
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +681 -108
- package/dist/integrations/harnesses/openhands/agent-builder.js +128 -0
- package/dist/integrations/harnesses/openhands/index.js +48 -0
- package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
- package/dist/integrations/harnesses/pi/agent-builder.js +97 -0
- package/dist/integrations/harnesses/pi/index.js +45 -0
- package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
- package/dist/integrations/harnesses/shared.js +17 -0
- package/dist/integrations/harnesses/types.js +43 -32
- package/dist/integrations/lockfile.js +211 -24
- package/dist/integrations/session-logs/index.js +36 -39
- package/dist/integrations/session-logs/provider-base.js +113 -0
- package/dist/llm/client.js +182 -110
- package/dist/llm/embedders/deterministic.js +2 -2
- package/dist/llm/embedders/remote.js +21 -9
- package/dist/llm/feature-gate.js +17 -57
- package/dist/llm/graph-extract.js +12 -13
- package/dist/llm/index-passes.js +8 -42
- package/dist/llm/memory-infer.js +144 -1
- package/dist/llm/metadata-enhance.js +45 -30
- package/dist/llm/structured-call.js +16 -8
- package/dist/llm/usage-persist.js +30 -5
- package/dist/llm/usage-telemetry.js +59 -6
- package/dist/output/cli-hints.js +1 -2
- package/dist/output/command-registry.js +27 -0
- package/dist/output/context.js +22 -7
- package/dist/output/format-exempt.js +80 -0
- package/dist/output/generic-render.js +251 -0
- package/dist/output/html-render.js +11 -16
- package/dist/output/render-registry.js +57 -0
- package/dist/output/renderers.js +14 -279
- package/dist/output/shapes/curate.js +10 -1
- package/dist/output/shapes/events.js +12 -7
- package/dist/output/shapes/helpers.js +58 -84
- package/dist/output/shapes/passthrough.js +11 -39
- package/dist/output/shapes/proposal/producer.js +15 -7
- package/dist/output/shapes/registry.js +12 -6
- package/dist/output/shapes.js +0 -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 +562 -0
- package/dist/output/text/env.js +1 -3
- package/dist/output/text/events.js +8 -7
- package/dist/output/text/helpers.js +15 -1164
- package/dist/output/text/proposal/producer.js +4 -2
- package/dist/output/text/proposal-format.js +202 -0
- package/dist/output/text/registry-commands.js +1 -2
- package/dist/output/text/registry.js +12 -6
- package/dist/output/text/show-directives.js +117 -0
- package/dist/output/text/show-format.js +103 -0
- package/dist/output/text/sync.js +5 -0
- package/dist/output/text/workflow-format.js +332 -0
- package/dist/output/text/workflow.js +3 -2
- package/dist/output/text.js +10 -19
- 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 +15 -25
- package/dist/registry/resolve.js +43 -94
- package/dist/registry/semver.js +43 -0
- package/dist/runtime.js +81 -12
- package/dist/scripts/akm-migrate.js +35529 -0
- package/dist/setup/detect.js +5 -7
- package/dist/setup/detected-engines.js +136 -0
- package/dist/setup/engine-config.js +100 -0
- package/dist/setup/registry-stash-loader.js +3 -3
- package/dist/setup/semantic-assets.js +12 -9
- package/dist/setup/setup.js +444 -208
- package/dist/setup/steps/connection-shared.js +120 -0
- package/dist/setup/steps/connection.js +108 -305
- package/dist/setup/steps/platforms.js +13 -12
- package/dist/setup/steps/semantic.js +15 -3
- package/dist/setup/steps/sources.js +21 -15
- package/dist/setup/steps/stashdir.js +6 -4
- package/dist/setup/steps/tasks.js +236 -119
- package/dist/setup/steps.js +3 -2
- package/dist/sources/freshness.js +39 -0
- 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 +54 -56
- package/dist/sources/providers/git-stash.js +420 -91
- package/dist/sources/providers/git.js +2 -2
- package/dist/sources/providers/npm.js +16 -19
- package/dist/sources/providers/provider-utils.js +47 -22
- package/dist/sources/providers/sync-from-ref.js +3 -9
- package/dist/sources/providers/website.js +2 -2
- package/dist/sources/resolve.js +11 -10
- package/dist/sources/snapshot-fetchers/types.js +4 -0
- package/dist/sources/{website-ingest.js → snapshot-fetchers/website-ingest.js} +110 -41
- package/dist/storage/database.js +60 -4
- package/dist/storage/engines/sqlite-migrations.js +156 -5
- package/dist/storage/locations.js +1 -2
- package/dist/storage/repositories/canaries-repository.js +1 -1
- package/dist/storage/repositories/events-repository.js +51 -11
- package/dist/storage/repositories/improve-runs-repository.js +6 -32
- package/dist/storage/repositories/index-connection.js +79 -0
- package/dist/storage/repositories/index-db.js +4 -3
- package/dist/storage/repositories/index-entries-repository.js +863 -0
- package/dist/{indexer/db/entry-mapper.js → storage/repositories/index-entry-mapper.js} +19 -2
- package/dist/storage/repositories/index-entry-types.js +4 -0
- package/dist/storage/repositories/index-fts-repository.js +167 -0
- package/dist/storage/repositories/index-llm-cache-repository.js +108 -0
- package/dist/storage/repositories/index-meta-repository.js +49 -0
- package/dist/{indexer/db/schema.js → storage/repositories/index-schema.js} +226 -100
- package/dist/storage/repositories/index-sql.js +12 -0
- package/dist/storage/repositories/index-utility-repository.js +356 -0
- package/dist/storage/repositories/index-vec-repository.js +250 -0
- package/dist/storage/repositories/outcome-repository.js +119 -0
- package/dist/storage/repositories/proposals-repository.js +317 -75
- package/dist/storage/repositories/registry-cache.js +1 -1
- package/dist/storage/repositories/salience-repository.js +172 -0
- package/dist/storage/repositories/task-history-repository.js +110 -3
- package/dist/storage/repositories/workflow-runs-repository.js +240 -19
- package/dist/tasks/backends/cron.js +169 -46
- package/dist/tasks/backends/exec-utils.js +76 -3
- package/dist/tasks/backends/index.js +6 -9
- package/dist/tasks/backends/launchd.js +292 -55
- package/dist/tasks/backends/schtasks.js +557 -70
- package/dist/tasks/backends/types.js +4 -0
- package/dist/tasks/command-executable.js +93 -0
- package/dist/tasks/embedded.js +56 -38
- package/dist/tasks/parser.js +156 -64
- package/dist/tasks/resolve-akm-bin.js +144 -51
- package/dist/tasks/runner.js +377 -209
- package/dist/tasks/schedule.js +108 -19
- package/dist/tasks/scheduler-invocation.js +296 -0
- package/dist/tasks/schema.js +1 -1
- package/dist/tasks/task-id.js +35 -0
- package/dist/tasks/validator.js +30 -16
- package/dist/text-import-hook.mjs +1 -1
- package/dist/workflows/authoring/authoring.js +104 -43
- package/dist/workflows/authoring/scope-key.js +1 -1
- package/dist/workflows/cli.js +0 -16
- package/dist/workflows/concurrency-policy.js +15 -0
- package/dist/workflows/exec/brief.js +450 -0
- package/dist/workflows/exec/frozen-judge.js +47 -0
- package/dist/workflows/exec/native-executor.js +1038 -0
- package/dist/workflows/exec/param-secrets.js +115 -0
- package/dist/workflows/exec/report.js +1460 -0
- package/dist/workflows/exec/run-workflow.js +602 -0
- package/dist/workflows/exec/scheduler.js +71 -0
- package/dist/workflows/exec/step-work.js +1190 -0
- package/dist/workflows/exec/unit-writer.js +23 -0
- package/dist/workflows/exec/workflow-engine-gate.js +67 -0
- package/dist/workflows/exec/worktree.js +171 -0
- package/dist/workflows/ir/compile.js +246 -0
- package/dist/workflows/ir/freeze.js +233 -0
- package/dist/workflows/ir/params.js +54 -0
- package/dist/workflows/ir/plan-hash.js +68 -0
- package/dist/workflows/ir/schema.js +540 -0
- package/dist/workflows/parser.js +878 -304
- package/dist/workflows/program/expressions.js +181 -0
- package/dist/workflows/program/schema.js +51 -0
- package/dist/workflows/renderer.js +100 -45
- package/dist/workflows/resource-limits.js +22 -0
- package/dist/workflows/runtime/agent-identity.js +59 -14
- package/dist/workflows/runtime/checkin.js +1 -1
- package/dist/workflows/runtime/plan-classifier.js +131 -0
- package/dist/workflows/runtime/runs.js +376 -119
- package/dist/workflows/runtime/unit-checkin.js +45 -0
- package/dist/workflows/runtime/unit-phases.js +20 -0
- package/dist/workflows/runtime/workflow-asset-loader.js +241 -40
- package/dist/workflows/schema.js +1 -11
- package/dist/workflows/validate-summary.js +2 -3
- package/dist/workflows/validator.js +52 -30
- package/docs/README.md +42 -78
- package/docs/migration/README.md +8 -0
- package/docs/migration/release-notes/0.6.0.md +1 -1
- package/docs/migration/release-notes/0.7.0.md +9 -8
- package/docs/migration/release-notes/0.9.0.md +158 -14
- package/docs/migration/v0.7-to-v0.8.md +46 -47
- package/docs/migration/v0.8-to-v0.9.md +844 -0
- package/docs/reference/README.md +12 -0
- package/docs/reference/data-and-telemetry.md +333 -0
- package/package.json +21 -17
- package/schemas/akm-asset-envelope.json +93 -0
- package/schemas/akm-config.json +4636 -0
- package/schemas/akm-task.json +87 -0
- package/schemas/akm-workflow.json +373 -0
- package/dist/akm-migrate-storage +0 -38
- package/dist/assets/help/help-accept.md +0 -12
- package/dist/assets/help/help-improve.md +0 -84
- package/dist/assets/help/help-proposals.md +0 -17
- package/dist/assets/help/help-propose.md +0 -17
- package/dist/assets/help/help-reject.md +0 -11
- package/dist/assets/profiles/frequent.json +0 -13
- package/dist/assets/profiles/recombine-only.json +0 -21
- package/dist/assets/profiles/reflect-distill.json +0 -30
- package/dist/assets/profiles/synthesize.json +0 -15
- package/dist/assets/prompts/procedural-system.md +0 -44
- package/dist/assets/prompts/recombine-system.md +0 -40
- package/dist/assets/prompts/staleness-detect-system.md +0 -6
- package/dist/assets/tasks/core/backup.yml +0 -4
- package/dist/assets/tasks/graph-refresh-weekly.yml +0 -10
- package/dist/assets/templates/html/default.html +0 -78
- package/dist/assets/templates/html/vendor/echarts.min.js +0 -45
- package/dist/assets/wiki/index-template.md +0 -12
- package/dist/assets/wiki/ingest-workflow-template.md +0 -83
- package/dist/assets/wiki/log-template.md +0 -8
- package/dist/assets/wiki/schema-template.md +0 -61
- package/dist/cli/config-migrate.js +0 -150
- package/dist/cli/config-validate.js +0 -39
- package/dist/commands/graph/graph-cli.js +0 -124
- package/dist/commands/graph/graph.js +0 -487
- package/dist/commands/improve/calibration.js +0 -161
- package/dist/commands/improve/dedup.js +0 -482
- package/dist/commands/improve/extract-watch.js +0 -140
- package/dist/commands/improve/hot-probation.js +0 -45
- package/dist/commands/improve/improve-auto-accept.js +0 -276
- package/dist/commands/improve/improve-profiles.js +0 -168
- package/dist/commands/improve/procedural.js +0 -398
- package/dist/commands/improve/recombine.js +0 -818
- package/dist/commands/improve/schema-similarity-gate.js +0 -168
- package/dist/commands/lint/agent-linter.js +0 -44
- package/dist/commands/lint/command-linter.js +0 -44
- package/dist/commands/lint/default-linter.js +0 -16
- package/dist/commands/lint/fact-linter.js +0 -39
- package/dist/commands/lint/knowledge-linter.js +0 -16
- package/dist/commands/lint/memory-linter.js +0 -61
- package/dist/commands/lint/registry.js +0 -41
- package/dist/commands/lint/skill-linter.js +0 -45
- package/dist/commands/lint/task-linter.js +0 -50
- package/dist/commands/lint/workflow-linter.js +0 -81
- package/dist/commands/proposal/legacy-import.js +0 -115
- package/dist/commands/sources/history.js +0 -196
- package/dist/commands/tasks/default-tasks.js +0 -186
- package/dist/commands/wiki-cli.js +0 -292
- package/dist/core/asset/asset-registry.js +0 -76
- package/dist/core/asset/asset-spec.js +0 -259
- package/dist/core/config/config-migration.js +0 -602
- package/dist/core/deep-merge.js +0 -38
- package/dist/core/eval/rank-metrics.js +0 -113
- package/dist/core/ripgrep/install.js +0 -163
- package/dist/core/ripgrep/resolve.js +0 -81
- package/dist/indexer/db/db.js +0 -1413
- package/dist/indexer/manifest.js +0 -170
- package/dist/indexer/passes/metadata-contributors.js +0 -31
- package/dist/indexer/usage/unmigrated-vaults-guard.js +0 -94
- package/dist/integrations/harnesses/opencode-sdk/index.js +0 -49
- package/dist/llm/call-ai.js +0 -62
- package/dist/llm/memory-infer-impl.js +0 -138
- 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/output/text/wiki.js +0 -16
- package/dist/registry/build-index.js +0 -386
- package/dist/scripts/migrate-storage.js +0 -19108
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +0 -9411
- package/dist/scripts/migrations/v16-to-v17.js +0 -141
- package/dist/setup/legacy-config.js +0 -106
- package/dist/storage/repositories/consolidation-repository.js +0 -38
- package/dist/storage/repositories/recombine-repository.js +0 -213
- package/dist/wiki/wiki-templates.js +0 -15
- package/dist/wiki/wiki.js +0 -1012
- package/dist/workflows/db.js +0 -215
- package/docs/data-and-telemetry.md +0 -226
- /package/dist/sources/{wiki-fetchers → snapshot-fetchers}/registry.js +0 -0
- /package/dist/sources/{wiki-fetchers → snapshot-fetchers}/youtube.js +0 -0
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// `<type>:<slug>` -> on-disk-asset question and MUST agree on the set of
|
|
11
11
|
// reachable refs for any given stash layout.
|
|
12
12
|
//
|
|
13
|
-
// The lock is enforced by `tests/contracts/ref-resolver-contract.test.ts`,
|
|
13
|
+
// The lock is enforced by `tests/integration/contracts/ref-resolver-contract.test.ts`,
|
|
14
14
|
// which drives this implementation through a canonical fixture set. The
|
|
15
15
|
// akm-plugins repo ships an equivalent test that drives its copy through the
|
|
16
16
|
// SAME inputs and asserts identical outcomes. Any change to the resolver
|
|
@@ -19,27 +19,24 @@
|
|
|
19
19
|
//
|
|
20
20
|
// Cases the contract covers (see fixture in the contract test):
|
|
21
21
|
// - existing memory / knowledge / agent / workflow / skill refs
|
|
22
|
-
// - knowledge
|
|
22
|
+
// - namespaced knowledge paths (knowledge/<category>/<slug>.md)
|
|
23
23
|
// - skill multi-file layout (skills/<slug>/SKILL.md)
|
|
24
|
-
// - memory `.derived.md` sibling
|
|
25
24
|
// - namespaced slugs containing `/`
|
|
26
25
|
// - env (`env/.env`, `env/<name>.env`) and secret (`secrets/<name>`) refs
|
|
27
26
|
// - non-existent refs
|
|
28
|
-
// - script
|
|
27
|
+
// - script paths with explicit extensions
|
|
29
28
|
//
|
|
30
|
-
// As of 0.9 the
|
|
31
|
-
// `
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
// hand-listed `vault` type was removed from the registry in 0.9 (replaced by
|
|
35
|
-
// `env`); `vault:` refs are therefore no longer matched here. `env:`/`secret:`
|
|
36
|
-
// refs are now matched and path-resolved. `script` stays unresolvable and
|
|
37
|
-
// `task` keeps its legacy `.md` resolution (see refToRelPath for both).
|
|
29
|
+
// As of 0.9 the path mapping in `refToRelPath` is DERIVED FROM THE PLACEMENT
|
|
30
|
+
// SPECS (`assetPathForName` in `src/core/asset/asset-placement.ts`) rather than
|
|
31
|
+
// hand-encoded, so it can no longer drift from the placement layer. `env`/
|
|
32
|
+
// `secret`, `script`, and `task` refs are path-resolved.
|
|
38
33
|
// ----------------------------------------------------------------------------
|
|
39
34
|
import fs from "node:fs";
|
|
40
35
|
import path from "node:path";
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
36
|
+
import { assetPathForName, stashDirFor } from "../../core/asset/asset-placement.js";
|
|
37
|
+
import { BUNDLE_REF_RE } from "../../core/asset/asset-ref.js";
|
|
38
|
+
import { typeNameFromConceptId } from "../../core/asset/resolve-ref.js";
|
|
39
|
+
import { findFenceRegions } from "./markdown-insertion.js";
|
|
43
40
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
44
41
|
function formatDate(d) {
|
|
45
42
|
const y = d.getFullYear();
|
|
@@ -130,52 +127,24 @@ function stripFencedBlocks(body) {
|
|
|
130
127
|
return lines.join("\n");
|
|
131
128
|
}
|
|
132
129
|
// ── missing-ref helpers ───────────────────────────────────────────────────────
|
|
133
|
-
/**
|
|
134
|
-
* Type alternation for {@link REF_RE}, derived from the asset registry at
|
|
135
|
-
* module load so it can never drift from `ASSET_SPECS`. Longest-first ordering
|
|
136
|
-
* is defensive (no built-in type is a prefix of another, but a future custom
|
|
137
|
-
* `registerAssetType` one might be) so the alternation prefers the longest
|
|
138
|
-
* match. Regex metacharacters are escaped in case a custom type introduces one.
|
|
139
|
-
*/
|
|
140
|
-
function buildRefTypeAlternation() {
|
|
141
|
-
const types = [...getAssetTypes()].sort((a, b) => b.length - a.length);
|
|
142
|
-
return types.map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
143
|
-
}
|
|
144
|
-
// Only the TYPE alternation is registry-derived; the surrounding grammar
|
|
145
|
-
// (boundary prefix, capture group, slug charset) is byte-identical to the
|
|
146
|
-
// legacy hand-written pattern. Deriving the types from `getAssetTypes()` means
|
|
147
|
-
// `env`/`secret` (added in 0.9) are now matched, and the removed `vault` type
|
|
148
|
-
// is not — both follow the registry automatically.
|
|
149
|
-
const REF_RE = new RegExp(`(?:^|[\\s\`"'(])((${buildRefTypeAlternation()}):[^\\s"'\`)\\]>,\\n]+)`, "gm");
|
|
150
130
|
/**
|
|
151
131
|
* Map from ref type to relative path pattern within stashRoot. Returns null to
|
|
152
132
|
* skip (type is unresolvable by the slug walker).
|
|
153
133
|
*
|
|
154
|
-
* Path layout is owned by the
|
|
155
|
-
* `
|
|
156
|
-
* rest of the CLI agree on where an asset lives.
|
|
157
|
-
* preserved to keep pre-0.9 behaviour byte-identical:
|
|
158
|
-
* - `script`: returns null (scripts live in nested dirs with arbitrary
|
|
159
|
-
* extensions — unresolvable by the slug-based walker, as the contract pins).
|
|
160
|
-
* - `task`: M1 fix — tasks are stored as `<id>.yml` on disk, so resolve
|
|
161
|
-
* `task:` refs against `tasks/<id>.yml` to match actual on-disk layout.
|
|
134
|
+
* Path layout is owned by the placement layer: we resolve through
|
|
135
|
+
* `assetPathForName(type, stashDirFor(type), name)` so the linter and the
|
|
136
|
+
* rest of the CLI agree on where an asset lives.
|
|
162
137
|
*
|
|
163
138
|
* Exported for contract testing — see header CONTRACT block.
|
|
164
139
|
*/
|
|
165
140
|
export function refToRelPath(refType, refName) {
|
|
166
|
-
|
|
167
|
-
if (refType === "script")
|
|
168
|
-
return null;
|
|
169
|
-
// M1: tasks are stored as .yml on disk; resolve task: refs against tasks/<id>.yml.
|
|
170
|
-
if (refType === "task")
|
|
171
|
-
return path.join(TYPE_DIRS.task ?? "tasks", `${refName}.yml`);
|
|
172
|
-
const typeDir = TYPE_DIRS[refType];
|
|
141
|
+
const typeDir = stashDirFor(refType);
|
|
173
142
|
if (!typeDir)
|
|
174
143
|
return null; // unknown type — skip
|
|
175
|
-
//
|
|
144
|
+
// assetPathForName returns a path rooted at the type dir we pass in,
|
|
176
145
|
// i.e. "<typeDir>/<...>" — exactly the stash-relative path this helper has
|
|
177
146
|
// always returned.
|
|
178
|
-
return
|
|
147
|
+
return assetPathForName(refType, typeDir, refName);
|
|
179
148
|
}
|
|
180
149
|
/**
|
|
181
150
|
* Returns true if `relPath` resolves to a real file (or multi-file directory
|
|
@@ -185,114 +154,182 @@ export function refToRelPath(refType, refName) {
|
|
|
185
154
|
*/
|
|
186
155
|
export function refExistsInAnyStash(relPath, refType, refName, stashRoots) {
|
|
187
156
|
for (const root of stashRoots) {
|
|
188
|
-
|
|
189
|
-
if (fs.existsSync(absPath))
|
|
190
|
-
return true;
|
|
191
|
-
// Multi-file skill layout: directory containing SKILL.md
|
|
192
|
-
const bareDir = absPath.replace(/\.md$/, "");
|
|
193
|
-
if (fs.existsSync(bareDir) && fs.existsSync(path.join(bareDir, "SKILL.md")))
|
|
194
|
-
return true;
|
|
195
|
-
// .derived.md variant for memory refs
|
|
196
|
-
if (refType === "memory") {
|
|
197
|
-
const derivedPath = path.join(root, "memories", `${refName}.derived.md`);
|
|
198
|
-
if (fs.existsSync(derivedPath))
|
|
199
|
-
return true;
|
|
200
|
-
}
|
|
201
|
-
// Knowledge-specific: search subdirectories like knowledge/projects/, knowledge/tools/, etc.
|
|
202
|
-
if (refType === "knowledge") {
|
|
203
|
-
try {
|
|
204
|
-
const knowledgeDir = path.join(root, "knowledge");
|
|
205
|
-
if (fs.existsSync(knowledgeDir) && fs.statSync(knowledgeDir).isDirectory()) {
|
|
206
|
-
const entries = fs.readdirSync(knowledgeDir, { withFileTypes: true });
|
|
207
|
-
for (const entry of entries) {
|
|
208
|
-
if (!entry.isDirectory())
|
|
209
|
-
continue;
|
|
210
|
-
const subPath = path.join(knowledgeDir, entry.name, `${refName}.md`);
|
|
211
|
-
if (fs.existsSync(subPath))
|
|
212
|
-
return true;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
catch {
|
|
217
|
-
// Ignore errors reading directory
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
// Fallback: the refName may already encode the full stash-relative path
|
|
221
|
-
// (e.g. knowledge:skills/foo/references/bar where the file lives at
|
|
222
|
-
// <stash>/skills/foo/references/bar.md, not <stash>/knowledge/skills/...).
|
|
223
|
-
const directPath = path.join(root, `${refName}.md`);
|
|
224
|
-
if (fs.existsSync(directPath))
|
|
225
|
-
return true;
|
|
226
|
-
const directDir = path.join(root, refName);
|
|
227
|
-
if (fs.existsSync(directDir) && fs.existsSync(path.join(directDir, "SKILL.md")))
|
|
157
|
+
if (resolveRefPathInStash(relPath, refType, refName, root) !== null)
|
|
228
158
|
return true;
|
|
229
159
|
}
|
|
230
160
|
return false;
|
|
231
161
|
}
|
|
232
162
|
/**
|
|
233
|
-
*
|
|
234
|
-
*
|
|
163
|
+
* Resolve the on-disk primary file for a ref within a SINGLE stash root, using
|
|
164
|
+
* the same reachability rules (in the same order) as
|
|
165
|
+
* {@link refExistsInAnyStash}, which delegates here. Returns the absolute path
|
|
166
|
+
* of the file that makes the ref "exist" — for a multi-file skill directory
|
|
167
|
+
* that is its `SKILL.md` primary — or `null` when the ref does not resolve in
|
|
168
|
+
* this root.
|
|
235
169
|
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
170
|
+
* Extracted for SPEC-5 (`--supersedes` demotion): write commands need the
|
|
171
|
+
* superseded asset's actual file to mutate, and forking a second resolver
|
|
172
|
+
* would drift from lint's. NOT part of the akm-plugins ref-resolver contract
|
|
173
|
+
* (the contract pins `refToRelPath` + `refExistsInAnyStash`; this is the
|
|
174
|
+
* shared internal both build on).
|
|
240
175
|
*/
|
|
241
|
-
function
|
|
242
|
-
const
|
|
176
|
+
export function resolveRefPathInStash(relPath, _refType, _refName, root) {
|
|
177
|
+
const absPath = path.join(root, relPath);
|
|
178
|
+
if (fs.existsSync(absPath))
|
|
179
|
+
return absPath;
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* A `(refType, refName)` pair that is not a lint-checkable local asset ref —
|
|
184
|
+
* shared skip-guard for recognized `bundle//conceptId` refs. Filters the
|
|
185
|
+
* false-positive patterns:
|
|
186
|
+
* - Shell variables: memory:$(cmd) or knowledge:${VAR} (guarded by callers on
|
|
187
|
+
* the raw token, before it is split).
|
|
188
|
+
* - Empty names or names that look like absolute paths / home dirs / URLs.
|
|
189
|
+
* - Incomplete/placeholder refs: single-character slug or "**".
|
|
190
|
+
* - Template placeholder refs like skill:<name> / workflow:<my-workflow>.
|
|
191
|
+
*/
|
|
192
|
+
function isNonRefName(refName) {
|
|
193
|
+
if (!refName || refName.startsWith("/") || refName.startsWith("~") || refName.startsWith("http"))
|
|
194
|
+
return true;
|
|
195
|
+
if (refName.length <= 1 || refName === "**")
|
|
196
|
+
return true;
|
|
197
|
+
if (refName.startsWith("<") || refName.includes("<"))
|
|
198
|
+
return true;
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Resolve a `(refType, refName)` pair against `allRoots`. Returns the resolved
|
|
203
|
+
* stash-relative path when the ref is MISSING (no file under any root), or
|
|
204
|
+
* `null` when it resolves, is a skipped/unresolvable type, or is a
|
|
205
|
+
* non-ref-shaped name. The single existence check both grammars route through.
|
|
206
|
+
*/
|
|
207
|
+
function localRefMissingRelPath(refType, refName, allRoots) {
|
|
208
|
+
if (isNonRefName(refName))
|
|
209
|
+
return null;
|
|
210
|
+
const relPath = refToRelPath(refType, refName);
|
|
211
|
+
if (relPath === null)
|
|
212
|
+
return null; // type is skipped / unresolvable
|
|
213
|
+
return refExistsInAnyStash(relPath, refType, refName, allRoots) ? null : relPath;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* 0.9.0 grammar recognition: fully-qualified `bundle//conceptId` body-refs
|
|
217
|
+
* (`BUNDLE_REF_RE`, the anchored prose form — spec §11.1 / ref-grammar decision
|
|
218
|
+
* D-R3). The conceptId is reverse-translated to its legacy `type`/`name` via the
|
|
219
|
+
* D-R2 static table (`typeNameFromConceptId`) so the SAME on-disk existence
|
|
220
|
+
* check applies; a conceptId whose leading segment names no known stash-subdir
|
|
221
|
+
* is not a local asset ref and is skipped (foreign-adapter / cross-bundle prose).
|
|
222
|
+
*/
|
|
223
|
+
function scanBundleRefs(scanBody, allRoots) {
|
|
243
224
|
const missing = [];
|
|
225
|
+
const re = new RegExp(BUNDLE_REF_RE.source, BUNDLE_REF_RE.flags);
|
|
244
226
|
let match;
|
|
245
|
-
const re = new RegExp(REF_RE.source, REF_RE.flags);
|
|
246
|
-
// C1: Strip fenced code blocks so example refs inside ``` are not flagged.
|
|
247
|
-
const scanBody = stripFencedBlocks(body);
|
|
248
227
|
// biome-ignore lint/suspicious/noAssignInExpressions: idiomatic regex loop
|
|
249
228
|
while ((match = re.exec(scanBody)) !== null) {
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
if (fullRef.includes("$(") || fullRef.includes("${")) {
|
|
253
|
-
continue;
|
|
254
|
-
}
|
|
255
|
-
// Skip ACP type notation: agent::Type (double colons)
|
|
256
|
-
if (fullRef.includes("::")) {
|
|
257
|
-
continue;
|
|
258
|
-
}
|
|
259
|
-
// Strip leading "local//" prefix if present
|
|
260
|
-
let ref = fullRef;
|
|
261
|
-
if (ref.startsWith("local//")) {
|
|
262
|
-
ref = ref.slice("local//".length);
|
|
263
|
-
}
|
|
264
|
-
else if (fullRef.includes("//")) {
|
|
265
|
-
// Has a remote origin prefix (e.g. "npm:", "github:", "owner/repo//") — skip
|
|
229
|
+
const token = match[1]; // e.g. "core//memories/foo"
|
|
230
|
+
if (token.includes("$(") || token.includes("${") || token.includes("::"))
|
|
266
231
|
continue;
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
const colonIdx = ref.indexOf(":");
|
|
270
|
-
if (colonIdx === -1)
|
|
232
|
+
const boundary = token.indexOf("//");
|
|
233
|
+
if (boundary < 0)
|
|
271
234
|
continue;
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
235
|
+
const found = classifyConceptRef(token.slice(boundary + 2), allRoots);
|
|
236
|
+
if (found !== null)
|
|
237
|
+
missing.push({ ref: token, resolvedRelPath: found });
|
|
238
|
+
}
|
|
239
|
+
return missing;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Map a bare 0.9.0 conceptId (`<stash-subdir>/<name>`, e.g. `memories/foo`) to
|
|
243
|
+
* its legacy `type`/`name` and run the shared existence check. Returns the
|
|
244
|
+
* missing relPath, or `null` when it resolves or is not a known local
|
|
245
|
+
* asset-type prefix. Drops a trailing `#fragment` (export selector) before
|
|
246
|
+
* mapping.
|
|
247
|
+
*/
|
|
248
|
+
function classifyConceptRef(rawConceptId, allRoots) {
|
|
249
|
+
const conceptId = rawConceptId.split("#", 1)[0];
|
|
250
|
+
const parts = typeNameFromConceptId(conceptId);
|
|
251
|
+
if (parts === undefined)
|
|
252
|
+
return null; // foreign type / not a local asset ref
|
|
253
|
+
return localRefMissingRelPath(parts.type, parts.name, allRoots);
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Returns an array of {ref, resolvedRelPath} for every local AKM ref in the
|
|
257
|
+
* PROSE body that does not resolve to a real file under any of the provided
|
|
258
|
+
* stash roots. Recognizes the 0.9.0 fully-qualified `bundle//conceptId` grammar
|
|
259
|
+
* ({@link scanBundleRefs}). Bare short conceptIds are NOT refs in prose (D-R3) —
|
|
260
|
+
* those are recognized only in the ref-list channels
|
|
261
|
+
* ({@link checkMissingRefsInList}).
|
|
262
|
+
*/
|
|
263
|
+
function checkMissingRefs(body, stashRoot, extraStashRoots = []) {
|
|
264
|
+
const allRoots = [stashRoot, ...extraStashRoots];
|
|
265
|
+
// C1: Strip fenced code blocks so example refs inside ``` are not flagged.
|
|
266
|
+
const scanBody = stripFencedBlocks(body);
|
|
267
|
+
return dedupeMissing(scanBundleRefs(scanBody, allRoots));
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Missing-ref check for the REF-LIST channels (frontmatter `refs:` /
|
|
271
|
+
* `xrefs:` / `supersededBy:` / `contradictedBy:`) where EACH value is a whole
|
|
272
|
+
* ref, not prose. Unlike the body scan, a bare short conceptId (`memories/foo`)
|
|
273
|
+
* IS a ref here (the value's whole purpose is to name one asset), so the flipped
|
|
274
|
+
* short-conceptId frontmatter the 0.9.0 output emits is no longer invisible.
|
|
275
|
+
* Recognizes, per value:
|
|
276
|
+
* - fully-qualified `bundle//conceptId`;
|
|
277
|
+
* - bare short `conceptId` (`<stash-subdir>/<name>`).
|
|
278
|
+
*/
|
|
279
|
+
function checkMissingRefsInList(values, stashRoot, extraStashRoots = []) {
|
|
280
|
+
const allRoots = [stashRoot, ...extraStashRoots];
|
|
281
|
+
const missing = [];
|
|
282
|
+
for (const raw of values) {
|
|
283
|
+
const value = raw.trim();
|
|
284
|
+
if (!value || value.includes("$(") || value.includes("${") || value.includes("::"))
|
|
276
285
|
continue;
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
286
|
+
const boundary = value.indexOf("//");
|
|
287
|
+
if (boundary >= 0) {
|
|
288
|
+
// Qualified: `bundle//conceptId` (0.9.0). A colon in the tail marks a
|
|
289
|
+
// legacy/remote `origin//type:name` — not the new grammar, so skip it.
|
|
290
|
+
const tail = value.slice(boundary + 2);
|
|
291
|
+
if (tail.includes(":"))
|
|
292
|
+
continue;
|
|
293
|
+
const rel = classifyConceptRef(tail, allRoots);
|
|
294
|
+
if (rel !== null)
|
|
295
|
+
missing.push({ ref: value, resolvedRelPath: rel });
|
|
280
296
|
continue;
|
|
281
297
|
}
|
|
282
|
-
//
|
|
283
|
-
|
|
298
|
+
// Un-prefixed: a 0.9.0 short `conceptId`. (Post-Chunk-8 the durable
|
|
299
|
+
// frontmatter xref channel is conceptId-spelled — the legacy `type:name`
|
|
300
|
+
// ref-list arm is retired.)
|
|
301
|
+
const rel = classifyConceptRef(value, allRoots);
|
|
302
|
+
if (rel !== null)
|
|
303
|
+
missing.push({ ref: value, resolvedRelPath: rel });
|
|
304
|
+
}
|
|
305
|
+
return dedupeMissing(missing);
|
|
306
|
+
}
|
|
307
|
+
/** Dedupe missing-ref records by their `ref` token (both arms can flag one ref). */
|
|
308
|
+
function dedupeMissing(rows) {
|
|
309
|
+
const seen = new Set();
|
|
310
|
+
const out = [];
|
|
311
|
+
for (const row of rows) {
|
|
312
|
+
if (seen.has(row.ref))
|
|
284
313
|
continue;
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
if (relPath === null)
|
|
288
|
-
continue; // type is skipped
|
|
289
|
-
if (!refExistsInAnyStash(relPath, refType, refName, allRoots)) {
|
|
290
|
-
missing.push({ ref: fullRef, resolvedRelPath: relPath });
|
|
291
|
-
}
|
|
314
|
+
seen.add(row.ref);
|
|
315
|
+
out.push(row);
|
|
292
316
|
}
|
|
293
|
-
return
|
|
317
|
+
return out;
|
|
294
318
|
}
|
|
295
319
|
// ── frontmatter refs ─────────────────────────────────────────────────────────
|
|
320
|
+
/**
|
|
321
|
+
* Frontmatter keys that carry cross-reference lists per the stash
|
|
322
|
+
* organization conventions: `xrefs:` (provenance / associative links),
|
|
323
|
+
* `supersededBy:` and `contradictedBy:` (belief-state correction links).
|
|
324
|
+
* The missing-ref check validates each of these in ADDITION to the body /
|
|
325
|
+
* `refs:` scan — they are the channel the conventions mandate, and a rename
|
|
326
|
+
* would otherwise dangle them silently.
|
|
327
|
+
*
|
|
328
|
+
* `sources:` is deliberately excluded (non-wiki `sources:` was rejected as a
|
|
329
|
+
* typed channel; wiki `sources:` is checked by lintWiki). `evidenceSources:` is
|
|
330
|
+
* excluded because it can point at merged-away or pruned assets.
|
|
331
|
+
*/
|
|
332
|
+
const XREF_FRONTMATTER_KEYS = ["xrefs", "supersededBy", "contradictedBy"];
|
|
296
333
|
/**
|
|
297
334
|
* Return the `refs:` array from frontmatter when it is present and is an
|
|
298
335
|
* array of strings; otherwise return `null` to signal the caller should
|
|
@@ -342,6 +379,22 @@ function readRefsArray(value) {
|
|
|
342
379
|
}
|
|
343
380
|
return out;
|
|
344
381
|
}
|
|
382
|
+
/**
|
|
383
|
+
* Like {@link readRefsArray} but also accepts a single scalar string,
|
|
384
|
+
* normalizing it to a one-element list. The indexer's
|
|
385
|
+
* `normalizeNonEmptyStringList` treats `supersededBy: memory:x` and
|
|
386
|
+
* `supersededBy: [memory:x]` identically — both are live data — so the
|
|
387
|
+
* frontmatter xref-channel check must validate both shapes; the array-only
|
|
388
|
+
* reader silently skipped dangling scalar refs. Returns `null` for any other
|
|
389
|
+
* type (missing key, number, object) and for a blank scalar.
|
|
390
|
+
*/
|
|
391
|
+
function readRefStringOrArray(value) {
|
|
392
|
+
if (typeof value === "string") {
|
|
393
|
+
const trimmed = value.trim();
|
|
394
|
+
return trimmed ? [trimmed] : null;
|
|
395
|
+
}
|
|
396
|
+
return readRefsArray(value);
|
|
397
|
+
}
|
|
345
398
|
/**
|
|
346
399
|
* Detect a leading nested frontmatter block in `body` (i.e. a `---\n…\n---`
|
|
347
400
|
* pair that opens within the first few lines of the body). When present,
|
|
@@ -407,204 +460,178 @@ function parseInnerFrontmatterBlock(body) {
|
|
|
407
460
|
}
|
|
408
461
|
return data;
|
|
409
462
|
}
|
|
410
|
-
// ──
|
|
463
|
+
// ── Base checks ─────────────────────────────────────────────────────────────
|
|
411
464
|
/**
|
|
412
|
-
*
|
|
413
|
-
*
|
|
465
|
+
* The cross-type checks every asset linter runs first: `unquoted-colon`,
|
|
466
|
+
* `missing-updated`, `stale-path`, and `missing-ref`.
|
|
467
|
+
*
|
|
468
|
+
* akm 0.9.0 chunk-3 (plan §12): this was the `BaseLinter.runBaseChecks`
|
|
469
|
+
* protected method every per-type linter class inherited. Those classes are
|
|
470
|
+
* gone — the format-generic checks live here as ONE shared function (this), and
|
|
471
|
+
* the per-`type` extra rules moved to the `akm` adapter's `validate`
|
|
472
|
+
* (`core/adapter/adapters/akm-lint.ts`). The live `akmLint` command
|
|
473
|
+
* (`commands/lint/index.ts`) calls this directly, then appends the adapter's
|
|
474
|
+
* per-type findings.
|
|
414
475
|
*
|
|
415
|
-
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
* (in practice the base class updates `ctx.raw` in place when `fix` is true).
|
|
476
|
+
* File mutations triggered by the fixable base checks (`unquoted-colon`,
|
|
477
|
+
* `missing-updated`) are flushed to disk here when `ctx.fix` is set, and
|
|
478
|
+
* `ctx.raw` is updated in place so a caller can re-parse the post-fix content.
|
|
419
479
|
*/
|
|
420
|
-
export
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
}
|
|
438
|
-
/**
|
|
439
|
-
* Validate that the `type` field value is one of an allowed set.
|
|
440
|
-
*
|
|
441
|
-
* Returns a detail string if the value is present but invalid, `null` if valid or absent.
|
|
442
|
-
*/
|
|
443
|
-
checkInvalidTypeValue(data, allowedTypes) {
|
|
444
|
-
if (!("type" in data) || !data.type)
|
|
445
|
-
return null; // absent — covered by checkMissingNameOrType
|
|
446
|
-
const value = String(data.type);
|
|
447
|
-
if (allowedTypes.includes(value))
|
|
448
|
-
return null;
|
|
449
|
-
return `type field has invalid value '${value}'; expected one of: ${allowedTypes.join(", ")}`;
|
|
450
|
-
}
|
|
451
|
-
/**
|
|
452
|
-
* Derive a URL-safe slug from a file path.
|
|
453
|
-
*/
|
|
454
|
-
suggestSlug(filePath) {
|
|
455
|
-
return path
|
|
456
|
-
.basename(filePath, ".md")
|
|
457
|
-
.toLowerCase()
|
|
458
|
-
.replace(/[^a-z0-9-]+/g, "-")
|
|
459
|
-
.replace(/-+/g, "-")
|
|
460
|
-
.replace(/^-|-$/g, "");
|
|
461
|
-
}
|
|
462
|
-
/**
|
|
463
|
-
* Insert one or more lines into a markdown body at a safe location.
|
|
464
|
-
*
|
|
465
|
-
* "Safe" means: not inside a markdown table, HTML table, fenced code block,
|
|
466
|
-
* or indented code block. If `proposedLineNumber` falls inside one of those
|
|
467
|
-
* regions, the helper pushes the insertion to immediately after the region.
|
|
468
|
-
* This is a regression guard against the class of bug where an auto-fix
|
|
469
|
-
* splits a table fence by injecting a callout between the separator row
|
|
470
|
-
* and the first data row (broke `knowledge/akm-cli-reference.md` in 0.8.0).
|
|
471
|
-
*
|
|
472
|
-
* Subclasses that perform line-based body insertion MUST route through this
|
|
473
|
-
* helper instead of calling `splice` directly. Insertion fixers must NOT
|
|
474
|
-
* touch frontmatter — use `fixMissingUpdated` / `fixUnquotedColon` style
|
|
475
|
-
* regex edits for that case (those already operate inside the `---…---`
|
|
476
|
-
* fence and don't intersect with body line numbers).
|
|
477
|
-
*
|
|
478
|
-
* @param raw Full file contents (frontmatter + body).
|
|
479
|
-
* @param newLines Lines to insert (without trailing newlines).
|
|
480
|
-
* @param proposedLineNumber 0-based line index within `raw` where the
|
|
481
|
-
* caller wants the new content to appear.
|
|
482
|
-
* @returns The mutated file contents with `newLines` spliced at the
|
|
483
|
-
* adjusted safe position.
|
|
484
|
-
*/
|
|
485
|
-
insertLinesSafely(raw, newLines, proposedLineNumber) {
|
|
486
|
-
const lines = raw.split(/\r?\n/);
|
|
487
|
-
const safeIdx = findSafeInsertionPoint(lines, proposedLineNumber);
|
|
488
|
-
lines.splice(safeIdx, 0, ...newLines);
|
|
489
|
-
return lines.join("\n");
|
|
490
|
-
}
|
|
491
|
-
runBaseChecks(ctx) {
|
|
492
|
-
const issues = [];
|
|
493
|
-
let currentRaw = ctx.raw;
|
|
494
|
-
let modified = false;
|
|
495
|
-
// M8: Parse lint_skip from frontmatter for per-file rule suppression.
|
|
496
|
-
// Accept both an array (`lint_skip: [missing-ref, stale-path]`) and a
|
|
497
|
-
// single scalar (`lint_skip: missing-ref`). Non-string entries are coerced
|
|
498
|
-
// and trimmed so loosely-typed YAML still gates correctly.
|
|
499
|
-
const rawLintSkip = ctx.data?.lint_skip;
|
|
500
|
-
const lintSkip = (Array.isArray(rawLintSkip) ? rawLintSkip : rawLintSkip != null ? [rawLintSkip] : [])
|
|
501
|
-
.map((v) => String(v).trim())
|
|
502
|
-
.filter(Boolean);
|
|
503
|
-
const shouldRun = (issueType) => !lintSkip.includes(issueType);
|
|
504
|
-
// ── 1. unquoted-colon ──────────────────────────────────────────────────
|
|
505
|
-
if (shouldRun("unquoted-colon")) {
|
|
506
|
-
const unquotedColonDetail = checkUnquotedColon(ctx.frontmatter);
|
|
507
|
-
if (unquotedColonDetail) {
|
|
508
|
-
if (ctx.fix) {
|
|
509
|
-
currentRaw = fixUnquotedColon(currentRaw);
|
|
510
|
-
modified = true;
|
|
511
|
-
issues.push({
|
|
512
|
-
file: ctx.relPath,
|
|
513
|
-
issue: "unquoted-colon",
|
|
514
|
-
detail: unquotedColonDetail,
|
|
515
|
-
fixed: true,
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
else {
|
|
519
|
-
issues.push({
|
|
520
|
-
file: ctx.relPath,
|
|
521
|
-
issue: "unquoted-colon",
|
|
522
|
-
detail: unquotedColonDetail,
|
|
523
|
-
fixed: false,
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
} // end shouldRun("unquoted-colon")
|
|
528
|
-
// ── 2. missing-updated ─────────────────────────────────────────────────
|
|
529
|
-
if (shouldRun("missing-updated") && checkMissingUpdated(ctx.data, ctx.frontmatter)) {
|
|
480
|
+
export function runBaseChecks(ctx) {
|
|
481
|
+
const issues = [];
|
|
482
|
+
let currentRaw = ctx.raw;
|
|
483
|
+
let modified = false;
|
|
484
|
+
// M8: Parse lint_skip from frontmatter for per-file rule suppression.
|
|
485
|
+
// Accept both an array (`lint_skip: [missing-ref, stale-path]`) and a
|
|
486
|
+
// single scalar (`lint_skip: missing-ref`). Non-string entries are coerced
|
|
487
|
+
// and trimmed so loosely-typed YAML still gates correctly.
|
|
488
|
+
const rawLintSkip = ctx.data?.lint_skip;
|
|
489
|
+
const lintSkip = (Array.isArray(rawLintSkip) ? rawLintSkip : rawLintSkip != null ? [rawLintSkip] : [])
|
|
490
|
+
.map((v) => String(v).trim())
|
|
491
|
+
.filter(Boolean);
|
|
492
|
+
const shouldRun = (issueType) => !lintSkip.includes(issueType);
|
|
493
|
+
// ── 1. unquoted-colon ──────────────────────────────────────────────────
|
|
494
|
+
if (shouldRun("unquoted-colon")) {
|
|
495
|
+
const unquotedColonDetail = checkUnquotedColon(ctx.frontmatter);
|
|
496
|
+
if (unquotedColonDetail) {
|
|
530
497
|
if (ctx.fix) {
|
|
531
|
-
|
|
532
|
-
try {
|
|
533
|
-
mtime = fs.statSync(ctx.filePath).mtime;
|
|
534
|
-
}
|
|
535
|
-
catch {
|
|
536
|
-
mtime = new Date();
|
|
537
|
-
}
|
|
538
|
-
currentRaw = fixMissingUpdated(currentRaw, mtime);
|
|
498
|
+
currentRaw = fixUnquotedColon(currentRaw);
|
|
539
499
|
modified = true;
|
|
540
500
|
issues.push({
|
|
541
501
|
file: ctx.relPath,
|
|
542
|
-
issue: "
|
|
543
|
-
detail:
|
|
502
|
+
issue: "unquoted-colon",
|
|
503
|
+
detail: unquotedColonDetail,
|
|
544
504
|
fixed: true,
|
|
545
505
|
});
|
|
546
506
|
}
|
|
547
507
|
else {
|
|
548
508
|
issues.push({
|
|
549
509
|
file: ctx.relPath,
|
|
550
|
-
issue: "
|
|
551
|
-
detail:
|
|
510
|
+
issue: "unquoted-colon",
|
|
511
|
+
detail: unquotedColonDetail,
|
|
552
512
|
fixed: false,
|
|
553
513
|
});
|
|
554
514
|
}
|
|
555
515
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
// M4: Also scan ctx.frontmatter for stale paths (absolute paths in frontmatter).
|
|
564
|
-
if (shouldRun("stale-path")) {
|
|
565
|
-
const staleInBody = checkStalePath(ctx.body);
|
|
566
|
-
const staleInFrontmatter = ctx.frontmatter ? checkStalePath(ctx.frontmatter) : [];
|
|
567
|
-
for (const candidate of [...staleInBody, ...staleInFrontmatter]) {
|
|
568
|
-
// M4: Suggest portable replacement when path is under stashRoot.
|
|
569
|
-
const portableHint = candidate.startsWith(ctx.stashRoot)
|
|
570
|
-
? ` (portable form: $AKM_STASH_DIR${candidate.slice(ctx.stashRoot.length)})`
|
|
571
|
-
: "";
|
|
572
|
-
issues.push({
|
|
573
|
-
file: ctx.relPath,
|
|
574
|
-
issue: "stale-path",
|
|
575
|
-
detail: `nonexistent path: ${candidate}${portableHint}`,
|
|
576
|
-
fixed: false,
|
|
577
|
-
});
|
|
516
|
+
} // end shouldRun("unquoted-colon")
|
|
517
|
+
// ── 2. missing-updated ─────────────────────────────────────────────────
|
|
518
|
+
if (shouldRun("missing-updated") && checkMissingUpdated(ctx.data, ctx.frontmatter)) {
|
|
519
|
+
if (ctx.fix) {
|
|
520
|
+
let mtime;
|
|
521
|
+
try {
|
|
522
|
+
mtime = fs.statSync(ctx.filePath).mtime;
|
|
578
523
|
}
|
|
524
|
+
catch {
|
|
525
|
+
mtime = new Date();
|
|
526
|
+
}
|
|
527
|
+
currentRaw = fixMissingUpdated(currentRaw, mtime);
|
|
528
|
+
modified = true;
|
|
529
|
+
issues.push({
|
|
530
|
+
file: ctx.relPath,
|
|
531
|
+
issue: "missing-updated",
|
|
532
|
+
detail: `stamped updated: ${formatDate(mtime)}`,
|
|
533
|
+
fixed: true,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
issues.push({
|
|
538
|
+
file: ctx.relPath,
|
|
539
|
+
issue: "missing-updated",
|
|
540
|
+
detail: "no updated field in frontmatter",
|
|
541
|
+
fixed: false,
|
|
542
|
+
});
|
|
579
543
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
//
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
544
|
+
}
|
|
545
|
+
if (modified) {
|
|
546
|
+
fs.writeFileSync(ctx.filePath, currentRaw, "utf8");
|
|
547
|
+
// Propagate the mutated raw back so subclasses can re-parse if needed
|
|
548
|
+
ctx.raw = currentRaw;
|
|
549
|
+
}
|
|
550
|
+
// ── 3. stale-path ──────────────────────────────────────────────────────
|
|
551
|
+
// M3: checkStalePath returns all stale matches; push one issue per path.
|
|
552
|
+
// M4: Also scan ctx.frontmatter for stale paths (absolute paths in frontmatter).
|
|
553
|
+
if (shouldRun("stale-path")) {
|
|
554
|
+
const staleInBody = checkStalePath(ctx.body);
|
|
555
|
+
const staleInFrontmatter = ctx.frontmatter ? checkStalePath(ctx.frontmatter) : [];
|
|
556
|
+
for (const candidate of [...staleInBody, ...staleInFrontmatter]) {
|
|
557
|
+
// M4: Suggest portable replacement when path is under stashRoot.
|
|
558
|
+
const portableHint = candidate.startsWith(ctx.stashRoot)
|
|
559
|
+
? ` (portable form: $AKM_BUNDLE_DIR${candidate.slice(ctx.stashRoot.length)})`
|
|
560
|
+
: "";
|
|
561
|
+
issues.push({
|
|
562
|
+
file: ctx.relPath,
|
|
563
|
+
issue: "stale-path",
|
|
564
|
+
detail: `nonexistent path: ${candidate}${portableHint}`,
|
|
565
|
+
fixed: false,
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
// ── 4. missing-ref ─────────────────────────────────────────────────────
|
|
570
|
+
// Carve-out for assets that declare an explicit `refs:` array in
|
|
571
|
+
// frontmatter (e.g. session-checkpoint memories captured by the
|
|
572
|
+
// claude-code hook). The frontmatter array is the *authoritative*
|
|
573
|
+
// ref list — any ref-shaped tokens in the body are treated as
|
|
574
|
+
// literal strings (heredocs, grep patterns, JSON values, regex
|
|
575
|
+
// patterns embedded in tool transcripts). Without this carve-out
|
|
576
|
+
// every session capture produces a fresh batch of `missing-ref`
|
|
577
|
+
// flags on every literal `<type>:<slug>` token in a transcript.
|
|
578
|
+
//
|
|
579
|
+
// The producer guarantees that entries in `refs:` already resolve
|
|
580
|
+
// (it validates against the live stash before writing), so we
|
|
581
|
+
// still run `checkMissingRefs` against the array itself to catch
|
|
582
|
+
// refs that were valid at capture time but later removed from the
|
|
583
|
+
// stash.
|
|
584
|
+
if (shouldRun("missing-ref")) {
|
|
585
|
+
const explicitRefs = extractFrontmatterRefs(ctx.data, ctx.body);
|
|
586
|
+
// An explicit `refs:` array is a REF LIST (each value is a whole ref —
|
|
587
|
+
// short conceptIds included); a bare body is PROSE (anchored refs only).
|
|
588
|
+
const missingRefs = explicitRefs !== null
|
|
589
|
+
? checkMissingRefsInList(explicitRefs, ctx.stashRoot, ctx.extraStashRoots)
|
|
590
|
+
: checkMissingRefs(ctx.body, ctx.stashRoot, ctx.extraStashRoots);
|
|
591
|
+
for (const { ref, resolvedRelPath } of missingRefs) {
|
|
592
|
+
issues.push({
|
|
593
|
+
file: ctx.relPath,
|
|
594
|
+
issue: "missing-ref",
|
|
595
|
+
detail: `missing ref: ${ref} (resolved to ${resolvedRelPath})`,
|
|
596
|
+
fixed: false,
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
// Frontmatter xref channels (xrefs / supersededBy / contradictedBy).
|
|
600
|
+
// Runs regardless of the `refs:` body-scan carve-out above — that
|
|
601
|
+
// carve-out governs only the BODY scan (`refs: []` declares "no
|
|
602
|
+
// outbound refs in the body", not "skip my correction links").
|
|
603
|
+
// Non-ref-shaped values (URLs, `raw/<slug>`, `<placeholder>`
|
|
604
|
+
// templates, shell vars) fall out via checkMissingRefs' guards.
|
|
589
605
|
//
|
|
590
|
-
//
|
|
591
|
-
//
|
|
592
|
-
//
|
|
593
|
-
//
|
|
594
|
-
//
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
+
// Gate: runs when the file has a frontmatter block OR when an
|
|
607
|
+
// authoritative `refs:` list was extracted. On the task/YAML path
|
|
608
|
+
// (lint/index.ts) ctx.frontmatter is always null and the whole file
|
|
609
|
+
// IS the body (`body === raw`); the top-level YAML keys land in
|
|
610
|
+
// ctx.data. Without `refs:` the body scan above already catches ref
|
|
611
|
+
// values under these keys, so running the pass would double-report —
|
|
612
|
+
// skip it. With `refs:` present the body scan is suppressed
|
|
613
|
+
// (refSource is the refs list), so this pass is the ONLY thing that
|
|
614
|
+
// validates the xref keys — it must run or dangling task xrefs go
|
|
615
|
+
// unreported. The two cases are mutually exclusive, so no ref is
|
|
616
|
+
// ever double-reported. Md files without a frontmatter block and
|
|
617
|
+
// without `refs:` land in the skip branch with empty ctx.data, so
|
|
618
|
+
// nothing is lost for them either.
|
|
619
|
+
if (ctx.frontmatter !== null || explicitRefs !== null) {
|
|
620
|
+
for (const key of XREF_FRONTMATTER_KEYS) {
|
|
621
|
+
const values = readRefStringOrArray(ctx.data?.[key]);
|
|
622
|
+
if (values === null)
|
|
623
|
+
continue;
|
|
624
|
+
const missingXrefs = checkMissingRefsInList(values, ctx.stashRoot, ctx.extraStashRoots);
|
|
625
|
+
for (const { ref, resolvedRelPath } of missingXrefs) {
|
|
626
|
+
issues.push({
|
|
627
|
+
file: ctx.relPath,
|
|
628
|
+
issue: "missing-ref",
|
|
629
|
+
detail: `missing ref: ${ref} (frontmatter ${key}; resolved to ${resolvedRelPath})`,
|
|
630
|
+
fixed: false,
|
|
631
|
+
});
|
|
632
|
+
}
|
|
606
633
|
}
|
|
607
634
|
}
|
|
608
|
-
return issues;
|
|
609
635
|
}
|
|
636
|
+
return issues;
|
|
610
637
|
}
|