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
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
* Architectural notes:
|
|
14
14
|
* - Stateless. All file/LLM access goes through injectable seams so tests
|
|
15
15
|
* never touch a real platform.
|
|
16
|
-
* - Bounded LLM call
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* - Bounded LLM call routed through `callStructured`. Improve-stage
|
|
17
|
+
* enablement comes from the active strategy; explicit `akm extract` always
|
|
18
|
+
* runs regardless of that stage toggle.
|
|
19
19
|
* - Proposals routed via `createProposal({ source: "extract", ... })` — the
|
|
20
20
|
* same review queue as reflect / distill / consolidate. Never direct-write.
|
|
21
21
|
* - Per-candidate body assembly merges description (+ when_to_use for lessons)
|
|
@@ -26,30 +26,32 @@
|
|
|
26
26
|
import fs from "node:fs";
|
|
27
27
|
import path from "node:path";
|
|
28
28
|
import { assembleAsset } from "../../core/asset/asset-serialize.js";
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
29
|
+
import { timestampForFilename } from "../../core/common.js";
|
|
30
|
+
import { getImproveProcessConfig, loadConfig } from "../../core/config/config.js";
|
|
31
31
|
import { ConfigError, UsageError } from "../../core/errors.js";
|
|
32
32
|
import { appendEvent } from "../../core/events.js";
|
|
33
|
-
import { probeLock, releaseLock, tryAcquireLockSync } from "../../core/file-lock.js";
|
|
33
|
+
import { createLockPayload, probeLock, reclaimStaleLock, releaseLock, tryAcquireLockSync, } from "../../core/file-lock.js";
|
|
34
|
+
import { tryAcquireMaintenanceBarrier } from "../../core/maintenance-barrier.js";
|
|
34
35
|
import { resolveStashStandards } from "../../core/standards/resolve-stash-standards.js";
|
|
36
|
+
import { resolveTypeConventions, typeConventionRef } from "../../core/standards/resolve-type-conventions.js";
|
|
35
37
|
import { getStateDbPath, openStateDatabase, withStateDb } from "../../core/state-db.js";
|
|
36
38
|
import { repairTruncatedDescription } from "../../core/text-truncation.js";
|
|
39
|
+
import { DURATION_UNITS, parseDuration } from "../../core/time.js";
|
|
37
40
|
import { warn } from "../../core/warn.js";
|
|
38
41
|
import { indexWrittenAssets } from "../../indexer/index-written-assets.js";
|
|
39
|
-
import {
|
|
42
|
+
import { resolveLlmEngineUse } from "../../integrations/agent/engine-resolution.js";
|
|
43
|
+
import { materializeLlmRunnerConnection, resolveImproveProcessRunner, } from "../../integrations/agent/runner.js";
|
|
40
44
|
import { normalizeHarnessId } from "../../integrations/harnesses/index.js";
|
|
41
45
|
import { getAvailableHarnesses } from "../../integrations/session-logs/index.js";
|
|
42
46
|
import { preFilterSession } from "../../integrations/session-logs/pre-filter.js";
|
|
43
|
-
import {
|
|
44
|
-
import { embed } from "../../llm/embedder.js";
|
|
45
|
-
import { tryLlmFeature } from "../../llm/feature-gate.js";
|
|
47
|
+
import { callStructured } from "../../llm/structured-call.js";
|
|
46
48
|
import { sha256Hex } from "../../runtime.js";
|
|
47
49
|
import { getExtractedSessionsMap, getLastExtractRunAt, shouldSkipAlreadyExtractedSession, upsertExtractedSession, } from "../../storage/repositories/extract-sessions-repository.js";
|
|
48
|
-
import {
|
|
50
|
+
import { isProposalSkipped } from "../proposal/repository.js";
|
|
49
51
|
import { buildExtractPrompt, EXTRACT_JSON_SCHEMA, parseExtractPayload } from "./extract-prompt.js";
|
|
50
|
-
import {
|
|
51
|
-
import {
|
|
52
|
-
import {
|
|
52
|
+
import { resolveImproveStrategy, resolveProcessEnabled } from "./improve-strategies.js";
|
|
53
|
+
import { emitProposal } from "./proposal-envelope.js";
|
|
54
|
+
import { createRunContext, resolveRunStashDir } from "./run-context.js";
|
|
53
55
|
import { buildSessionSummaryPrompt, parseSessionSummary, SESSION_SUMMARY_JSON_SCHEMA, sessionMeetsDurationGate, writeSessionAsset, } from "./session-asset.js";
|
|
54
56
|
import { resolveTriageConfig, scoreSessionTriage } from "./triage.js";
|
|
55
57
|
/** Default minimum session duration (minutes) for session indexing (#561). */
|
|
@@ -123,20 +125,75 @@ function getExtractSessionLockPath(harness, sessionId, stateDbPath) {
|
|
|
123
125
|
* extraction outright.
|
|
124
126
|
*/
|
|
125
127
|
function acquireExtractSessionLock(lockPath) {
|
|
128
|
+
const releaseBarrier = tryAcquireMaintenanceBarrier();
|
|
129
|
+
if (!releaseBarrier)
|
|
130
|
+
return { proceed: false };
|
|
126
131
|
try {
|
|
127
132
|
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
128
|
-
|
|
129
|
-
|
|
133
|
+
let ownership = tryAcquireLockSync(lockPath, createLockPayload());
|
|
134
|
+
if (ownership)
|
|
135
|
+
return { proceed: true, ownership };
|
|
130
136
|
const probe = probeLock(lockPath, { staleAfterMs: EXTRACT_SESSION_LOCK_STALE_MS });
|
|
131
137
|
if (probe.state === "held")
|
|
132
|
-
return false;
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
138
|
+
return { proceed: false };
|
|
139
|
+
// Absent (released between attempt + probe) or successfully reclaimed stale lock → retry once.
|
|
140
|
+
if (probe.state === "stale" && !reclaimStaleLock(lockPath, probe))
|
|
141
|
+
return { proceed: false };
|
|
142
|
+
ownership = tryAcquireLockSync(lockPath, createLockPayload());
|
|
143
|
+
return ownership ? { proceed: true, ownership } : { proceed: false };
|
|
136
144
|
}
|
|
137
145
|
catch {
|
|
138
|
-
return true;
|
|
146
|
+
return { proceed: true };
|
|
139
147
|
}
|
|
148
|
+
finally {
|
|
149
|
+
releaseBarrier();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function cloneAndFreeze(value) {
|
|
153
|
+
const clone = structuredClone(value);
|
|
154
|
+
const freeze = (item) => {
|
|
155
|
+
if (typeof item !== "object" || item === null || Object.isFrozen(item))
|
|
156
|
+
return;
|
|
157
|
+
for (const child of Object.values(item))
|
|
158
|
+
freeze(child);
|
|
159
|
+
Object.freeze(item);
|
|
160
|
+
};
|
|
161
|
+
freeze(clone);
|
|
162
|
+
return clone;
|
|
163
|
+
}
|
|
164
|
+
/** Resolve standalone extract selection once before discovery, auto iteration, or watch startup. */
|
|
165
|
+
export function resolveStandaloneExtractPlan(config, selection) {
|
|
166
|
+
if (selection.engine && selection.strategy) {
|
|
167
|
+
throw new UsageError("--engine and --strategy are mutually exclusive. Pick one.", "INVALID_FLAG_VALUE");
|
|
168
|
+
}
|
|
169
|
+
const selected = resolveImproveStrategy(selection.strategy, config);
|
|
170
|
+
const process = cloneAndFreeze(getImproveProcessConfig("extract", selected.config) ?? {});
|
|
171
|
+
const invocation = {
|
|
172
|
+
...(selection.engine ? { engine: selection.engine } : {}),
|
|
173
|
+
...(Object.hasOwn(selection, "timeoutMs") ? { timeoutMs: selection.timeoutMs ?? null } : {}),
|
|
174
|
+
};
|
|
175
|
+
const resolved = resolveLlmEngineUse(config, [selected.config, process, invocation], { optional: true });
|
|
176
|
+
if (!resolved) {
|
|
177
|
+
throw new ConfigError("No LLM engine configured for extract. Set defaults.llmEngine, pass --engine, or select an improve strategy with processes.extract.engine.", "LLM_NOT_CONFIGURED");
|
|
178
|
+
}
|
|
179
|
+
const runner = {
|
|
180
|
+
kind: "llm",
|
|
181
|
+
engine: resolved.engine,
|
|
182
|
+
connection: resolved.connection,
|
|
183
|
+
...(resolved.credential ? { credential: resolved.credential } : {}),
|
|
184
|
+
timeoutMs: resolved.timeoutMs,
|
|
185
|
+
};
|
|
186
|
+
return Object.freeze({
|
|
187
|
+
strategy: selected.name,
|
|
188
|
+
engine: resolved.engine,
|
|
189
|
+
// `akm extract` is an explicit operation. The strategy supplies behavior,
|
|
190
|
+
// but its improve-stage enablement gate does not disable this command.
|
|
191
|
+
enabled: true,
|
|
192
|
+
process,
|
|
193
|
+
runner: cloneAndFreeze(runner),
|
|
194
|
+
timeoutMs: resolved.timeoutMs,
|
|
195
|
+
embeddingConfig: cloneAndFreeze(config.embedding),
|
|
196
|
+
});
|
|
140
197
|
}
|
|
141
198
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
142
199
|
/**
|
|
@@ -146,6 +203,12 @@ function acquireExtractSessionLock(lockPath) {
|
|
|
146
203
|
*
|
|
147
204
|
* Throws UsageError on unparseable input so the CLI surfaces a clear error
|
|
148
205
|
* rather than silently defaulting.
|
|
206
|
+
*
|
|
207
|
+
* The recognizer is deliberately CASE-INSENSITIVE and whitespace-tolerant —
|
|
208
|
+
* `5M` means 5 MINUTES here, diverging from the core grammar's case-sensitive
|
|
209
|
+
* `M`=months (pinned by tests/commands/goldens-duration-flags.test.ts); only
|
|
210
|
+
* the unit arithmetic is delegated to the canonical {@link DURATION_UNITS}
|
|
211
|
+
* table via {@link parseDuration}.
|
|
149
212
|
*/
|
|
150
213
|
export function parseSinceArg(value, now = Date.now()) {
|
|
151
214
|
if (!value || value.trim() === "") {
|
|
@@ -154,10 +217,9 @@ export function parseSinceArg(value, now = Date.now()) {
|
|
|
154
217
|
const trimmed = value.trim();
|
|
155
218
|
const relMatch = trimmed.match(/^(\d+)\s*([mhd])$/i);
|
|
156
219
|
if (relMatch) {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return now - ms;
|
|
220
|
+
const ms = parseDuration(`${relMatch[1] ?? "0"}${(relMatch[2] ?? "h").toLowerCase()}`, DURATION_UNITS);
|
|
221
|
+
if (ms !== null)
|
|
222
|
+
return now - ms;
|
|
161
223
|
}
|
|
162
224
|
const iso = Date.parse(trimmed);
|
|
163
225
|
if (!Number.isNaN(iso))
|
|
@@ -187,31 +249,53 @@ function resolveHarness(type, harnesses) {
|
|
|
187
249
|
* so the accept-time descriptionQualityValidator passes — same pattern as
|
|
188
250
|
* the consolidate-writer fix at consolidate.ts.
|
|
189
251
|
*/
|
|
190
|
-
function buildCandidateProposal(candidate, sourceRef) {
|
|
191
|
-
const ref =
|
|
252
|
+
function buildCandidateProposal(candidate, sourceRef, sessionAssetRef) {
|
|
253
|
+
const ref = deriveExtractCandidateRef(candidate, sourceRef);
|
|
192
254
|
// Post-generation repair pass (#556): deterministically complete a
|
|
193
255
|
// description the LLM sliced mid-sentence before it reaches the
|
|
194
256
|
// auto-accept validators. No-op (byte-identical) for valid descriptions.
|
|
195
257
|
const description = repairTruncatedDescription(candidate.description, candidate.body);
|
|
196
258
|
const fm = {
|
|
197
259
|
description,
|
|
198
|
-
|
|
260
|
+
...(sessionAssetRef ? { xrefs: [sessionAssetRef] } : {}),
|
|
199
261
|
};
|
|
200
262
|
if (candidate.type === "lesson" && candidate.when_to_use) {
|
|
201
263
|
fm.when_to_use = candidate.when_to_use;
|
|
202
264
|
}
|
|
203
|
-
// #615 WS-0: preserve ordered-action + outcome data in frontmatter so the data
|
|
204
|
-
// survives even if source transcripts are not re-extractable later. The
|
|
205
|
-
// procedural-compilation feature (detection/compilation) is deferred to 0.10+.
|
|
206
|
-
if (candidate.orderedActions && candidate.orderedActions.length > 0) {
|
|
207
|
-
fm.orderedActions = candidate.orderedActions;
|
|
208
|
-
if (candidate.outcomeData) {
|
|
209
|
-
fm.outcomeData = candidate.outcomeData;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
265
|
const content = assembleAsset(fm, candidate.body);
|
|
213
266
|
return { ref, content, description };
|
|
214
267
|
}
|
|
268
|
+
function canonicalSegment(value) {
|
|
269
|
+
return value
|
|
270
|
+
.trim()
|
|
271
|
+
.toLowerCase()
|
|
272
|
+
.replace(/[^a-z0-9-]+/g, "-")
|
|
273
|
+
.replace(/-+/g, "-")
|
|
274
|
+
.replace(/^-|-$/g, "");
|
|
275
|
+
}
|
|
276
|
+
export function deriveExtractCandidateRef(candidate, sourceRef) {
|
|
277
|
+
const candidateParts = candidate.name.split("/").map(canonicalSegment).filter(Boolean);
|
|
278
|
+
const leaf = candidateParts.at(-1) ?? "extracted-insight";
|
|
279
|
+
if (candidate.type === "memory" || candidate.type === "lesson") {
|
|
280
|
+
const projectName = sourceRef.projectHint?.split(/[\\/]/).filter(Boolean).at(-1);
|
|
281
|
+
const scope = projectName ? canonicalSegment(projectName) : "";
|
|
282
|
+
const subdir = candidate.type === "memory" ? "memories" : "lessons";
|
|
283
|
+
return `${subdir}/${scope ? `${scope}/` : ""}${leaf}`;
|
|
284
|
+
}
|
|
285
|
+
return `knowledge/${leaf}`;
|
|
286
|
+
}
|
|
287
|
+
function resolveExtractStandards(stashDir) {
|
|
288
|
+
const sections = [];
|
|
289
|
+
const general = resolveStashStandards(stashDir);
|
|
290
|
+
if (general)
|
|
291
|
+
sections.push(general);
|
|
292
|
+
for (const type of ["memory", "lesson", "knowledge"]) {
|
|
293
|
+
const body = resolveTypeConventions(stashDir, type);
|
|
294
|
+
if (body)
|
|
295
|
+
sections.push(`# ${typeConventionRef(type)} (soft per-type conventions — guidance, not enforced)\n${body}`);
|
|
296
|
+
}
|
|
297
|
+
return sections.join("\n\n");
|
|
298
|
+
}
|
|
215
299
|
/**
|
|
216
300
|
* Canonicalize a session's content into a single deterministic string for
|
|
217
301
|
* hashing (#602). Each event is rendered `<role>\n<text>` and events are joined
|
|
@@ -245,36 +329,32 @@ export function hashSessionContent(data) {
|
|
|
245
329
|
* proposal validation failure) the session result records a warning and
|
|
246
330
|
* keeps going — one session's bad luck never aborts a multi-session run.
|
|
247
331
|
*/
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
prior, force,
|
|
259
|
-
// Stash authoring standards (convention/meta fact bodies) for non-wiki
|
|
260
|
-
// output. Resolved ONCE per run by the caller and threaded in so facts are
|
|
261
|
-
// not re-read per session. Empty string when none exist.
|
|
262
|
-
standardsContext) {
|
|
263
|
-
const warnings = [];
|
|
332
|
+
/**
|
|
333
|
+
* The zero-LLM pre-flight gates for one session: read, the #602 content-hash
|
|
334
|
+
* already-extracted skip, the #595/#596 minContentChars floor, and the #626
|
|
335
|
+
* heuristic triage gate. Returns a terminal skip result, or the read `data` +
|
|
336
|
+
* pre-filtered events + content hash to carry into the extraction prompt.
|
|
337
|
+
* Extracted verbatim from `processSession` — every skip shape/reason is
|
|
338
|
+
* byte-identical.
|
|
339
|
+
*/
|
|
340
|
+
function runPreLlmSessionGates(args) {
|
|
341
|
+
const { harness, sessionRef, prior, force, maxTotalChars, minContentChars, triage } = args;
|
|
264
342
|
let data;
|
|
265
343
|
try {
|
|
266
344
|
data = harness.readSession(sessionRef);
|
|
267
345
|
}
|
|
268
346
|
catch (err) {
|
|
269
347
|
return {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
348
|
+
skip: {
|
|
349
|
+
sessionId: sessionRef.sessionId,
|
|
350
|
+
harness: harness.name,
|
|
351
|
+
candidateCount: 0,
|
|
352
|
+
proposalIds: [],
|
|
353
|
+
preFilter: { inputCount: 0, outputCount: 0, truncatedCount: 0 },
|
|
354
|
+
warnings: [`readSession failed: ${err instanceof Error ? err.message : String(err)}`],
|
|
355
|
+
skipped: true,
|
|
356
|
+
skipReason: "read_failed",
|
|
357
|
+
},
|
|
278
358
|
};
|
|
279
359
|
}
|
|
280
360
|
// #602 — content-hash skip. Computed on the RAW event stream immediately after
|
|
@@ -287,15 +367,17 @@ standardsContext) {
|
|
|
287
367
|
const contentHash = hashSessionContent(data);
|
|
288
368
|
if (!force && shouldSkipAlreadyExtractedSession(prior, contentHash)) {
|
|
289
369
|
return {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
370
|
+
skip: {
|
|
371
|
+
sessionId: sessionRef.sessionId,
|
|
372
|
+
harness: harness.name,
|
|
373
|
+
candidateCount: 0,
|
|
374
|
+
proposalIds: [],
|
|
375
|
+
preFilter: { inputCount: 0, outputCount: 0, truncatedCount: 0 },
|
|
376
|
+
warnings: [`already extracted (content unchanged) at ${prior?.processed_at}; pass --force to re-process`],
|
|
377
|
+
skipped: true,
|
|
378
|
+
skipReason: "already_extracted",
|
|
379
|
+
contentHash,
|
|
380
|
+
},
|
|
299
381
|
};
|
|
300
382
|
}
|
|
301
383
|
const filtered = preFilterSession(data, {
|
|
@@ -312,32 +394,7 @@ standardsContext) {
|
|
|
312
394
|
const rawContentChars = data.events.reduce((sum, event) => sum + event.text.length, 0);
|
|
313
395
|
if (minContentChars > 0 && rawContentChars < minContentChars) {
|
|
314
396
|
return {
|
|
315
|
-
|
|
316
|
-
harness: harness.name,
|
|
317
|
-
candidateCount: 0,
|
|
318
|
-
proposalIds: [],
|
|
319
|
-
preFilter: {
|
|
320
|
-
inputCount: filtered.stats.inputCount,
|
|
321
|
-
outputCount: filtered.stats.outputCount,
|
|
322
|
-
truncatedCount: filtered.stats.truncatedCount,
|
|
323
|
-
},
|
|
324
|
-
warnings: [],
|
|
325
|
-
skipped: true,
|
|
326
|
-
skipReason: "too_short",
|
|
327
|
-
contentHash,
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
// #626 — pre-LLM heuristic triage gate. Runs AFTER minContentChars + the
|
|
331
|
-
// already-extracted skip check (both in the caller / above), BEFORE the
|
|
332
|
-
// extraction prompt and the session-asset write. When the session scores below
|
|
333
|
-
// the configured threshold we triage it out: no chat() call, no session asset,
|
|
334
|
-
// no proposals. Pure-heuristic — zero added LLM cost. Default-off → skipped.
|
|
335
|
-
if (triage.enabled) {
|
|
336
|
-
const t = scoreSessionTriage(data, triage.minScore, {
|
|
337
|
-
proceduralAwareFloor: triage.proceduralAwareFloor,
|
|
338
|
-
});
|
|
339
|
-
if (!t.pass) {
|
|
340
|
-
return {
|
|
397
|
+
skip: {
|
|
341
398
|
sessionId: sessionRef.sessionId,
|
|
342
399
|
harness: harness.name,
|
|
343
400
|
candidateCount: 0,
|
|
@@ -349,11 +406,91 @@ standardsContext) {
|
|
|
349
406
|
},
|
|
350
407
|
warnings: [],
|
|
351
408
|
skipped: true,
|
|
352
|
-
skipReason: "
|
|
409
|
+
skipReason: "too_short",
|
|
353
410
|
contentHash,
|
|
411
|
+
},
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
// #626 — pre-LLM heuristic triage gate. Runs AFTER minContentChars + the
|
|
415
|
+
// already-extracted skip check (both in the caller / above), BEFORE the
|
|
416
|
+
// extraction prompt and the session-asset write. When the session scores below
|
|
417
|
+
// the configured threshold we triage it out: no chat() call, no session asset,
|
|
418
|
+
// no proposals. Pure-heuristic — zero added LLM cost. Default-off → skipped.
|
|
419
|
+
if (triage.enabled) {
|
|
420
|
+
const t = scoreSessionTriage(data, triage.minScore);
|
|
421
|
+
if (!t.pass) {
|
|
422
|
+
return {
|
|
423
|
+
skip: {
|
|
424
|
+
sessionId: sessionRef.sessionId,
|
|
425
|
+
harness: harness.name,
|
|
426
|
+
candidateCount: 0,
|
|
427
|
+
proposalIds: [],
|
|
428
|
+
preFilter: {
|
|
429
|
+
inputCount: filtered.stats.inputCount,
|
|
430
|
+
outputCount: filtered.stats.outputCount,
|
|
431
|
+
truncatedCount: filtered.stats.truncatedCount,
|
|
432
|
+
},
|
|
433
|
+
warnings: [],
|
|
434
|
+
skipped: true,
|
|
435
|
+
skipReason: "triaged_out",
|
|
436
|
+
contentHash,
|
|
437
|
+
},
|
|
354
438
|
};
|
|
355
439
|
}
|
|
356
440
|
}
|
|
441
|
+
return { data, filtered, contentHash };
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* The bounded per-session extraction LLM call. Resolves the connection with
|
|
445
|
+
* the same fail-open contract the gated fn had (a `getLlmConfig()` throw —
|
|
446
|
+
* `materializeLlmConnection` can raise ConfigError — takes the skipped path,
|
|
447
|
+
* never propagates), then routes through `callStructured` under the
|
|
448
|
+
* `session_extraction` gate. Returns the seam result plus the `llmRaw`
|
|
449
|
+
* side-channel value that distinguishes fallback-took-over from a
|
|
450
|
+
* genuinely-empty response.
|
|
451
|
+
*/
|
|
452
|
+
async function runSessionExtractionLlmCall(args) {
|
|
453
|
+
const { config, getLlmConfig, chat, prompt, timeoutMs, signal } = args;
|
|
454
|
+
let extractLlm;
|
|
455
|
+
try {
|
|
456
|
+
extractLlm = getLlmConfig();
|
|
457
|
+
}
|
|
458
|
+
catch {
|
|
459
|
+
extractLlm = undefined;
|
|
460
|
+
}
|
|
461
|
+
let llmRaw = "";
|
|
462
|
+
const llmResult = extractLlm === undefined
|
|
463
|
+
? ""
|
|
464
|
+
: await callStructured({
|
|
465
|
+
feature: "session_extraction",
|
|
466
|
+
akmConfig: config,
|
|
467
|
+
config: extractLlm,
|
|
468
|
+
messages: [{ role: "user", content: prompt }],
|
|
469
|
+
request: {
|
|
470
|
+
timeoutMs,
|
|
471
|
+
responseSchema: EXTRACT_JSON_SCHEMA,
|
|
472
|
+
...(signal ? { signal } : {}),
|
|
473
|
+
...(chat ? { chat } : {}),
|
|
474
|
+
},
|
|
475
|
+
parse: (raw) => {
|
|
476
|
+
llmRaw = raw ?? "";
|
|
477
|
+
return llmRaw;
|
|
478
|
+
},
|
|
479
|
+
// A transport throw takes the "" fallback with llmRaw left unset —
|
|
480
|
+
// the same skipped path the gated-fn throw produced before.
|
|
481
|
+
onError: () => "",
|
|
482
|
+
fallback: "",
|
|
483
|
+
});
|
|
484
|
+
return { llmResult, llmRaw };
|
|
485
|
+
}
|
|
486
|
+
async function processSession(runCtx, session) {
|
|
487
|
+
const { harness, stashDir, config, getLlmConfig, chat, ctx, eventsCtx, sourceRun, dryRun, timeoutMs, maxTotalChars, minContentChars, triage, sessionIndexing, signal, standardsContext, } = runCtx;
|
|
488
|
+
const { sessionRef, prior, force } = session;
|
|
489
|
+
const warnings = [];
|
|
490
|
+
const gate = runPreLlmSessionGates({ harness, sessionRef, prior, force, maxTotalChars, minContentChars, triage });
|
|
491
|
+
if ("skip" in gate)
|
|
492
|
+
return gate.skip;
|
|
493
|
+
const { data, filtered, contentHash } = gate;
|
|
357
494
|
const prompt = buildExtractPrompt({
|
|
358
495
|
data,
|
|
359
496
|
events: filtered.events,
|
|
@@ -388,16 +525,16 @@ standardsContext) {
|
|
|
388
525
|
}
|
|
389
526
|
return {};
|
|
390
527
|
};
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
}
|
|
528
|
+
const { llmResult, llmRaw } = await runSessionExtractionLlmCall({
|
|
529
|
+
config,
|
|
530
|
+
getLlmConfig,
|
|
531
|
+
chat,
|
|
532
|
+
prompt,
|
|
533
|
+
timeoutMs,
|
|
534
|
+
signal,
|
|
535
|
+
});
|
|
399
536
|
if (llmResult === "" && !llmRaw) {
|
|
400
|
-
//
|
|
537
|
+
// The seam took the fallback path (disabled / timeout / error). Return skipped.
|
|
401
538
|
return {
|
|
402
539
|
sessionId: sessionRef.sessionId,
|
|
403
540
|
harness: harness.name,
|
|
@@ -416,10 +553,12 @@ standardsContext) {
|
|
|
416
553
|
}
|
|
417
554
|
const payload = parseExtractPayload(llmRaw);
|
|
418
555
|
const proposalIds = [];
|
|
556
|
+
// Provenance refs are added only after the cited session asset exists.
|
|
557
|
+
const sessionAsset = await maybeWriteSessionAsset();
|
|
419
558
|
if (payload.candidates.length === 0) {
|
|
420
559
|
appendEvent({
|
|
421
560
|
eventType: "extract_invoked",
|
|
422
|
-
ref:
|
|
561
|
+
...(sessionAsset.sessionAssetRef ? { ref: sessionAsset.sessionAssetRef } : {}),
|
|
423
562
|
metadata: {
|
|
424
563
|
outcome: "no_candidates",
|
|
425
564
|
sessionId: sessionRef.sessionId,
|
|
@@ -429,8 +568,7 @@ standardsContext) {
|
|
|
429
568
|
preFilterInput: filtered.stats.inputCount,
|
|
430
569
|
preFilterOutput: filtered.stats.outputCount,
|
|
431
570
|
},
|
|
432
|
-
},
|
|
433
|
-
const sessionAsset = await maybeWriteSessionAsset();
|
|
571
|
+
}, eventsCtx);
|
|
434
572
|
return {
|
|
435
573
|
sessionId: sessionRef.sessionId,
|
|
436
574
|
harness: harness.name,
|
|
@@ -447,63 +585,41 @@ standardsContext) {
|
|
|
447
585
|
...sessionAsset,
|
|
448
586
|
};
|
|
449
587
|
}
|
|
450
|
-
//
|
|
451
|
-
//
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
const hotProbationEnabled = getImproveProcessConfig(config, "extract")?.hotProbation?.enabled === true;
|
|
588
|
+
// §23.6 fingerprint model-id term: the profile resolved for this session's
|
|
589
|
+
// LLM call (best-effort — an unconfigured profile leaves the term empty).
|
|
590
|
+
let extractModelId;
|
|
591
|
+
try {
|
|
592
|
+
extractModelId = runCtx.getLlmConfig().model;
|
|
593
|
+
}
|
|
594
|
+
catch {
|
|
595
|
+
extractModelId = undefined;
|
|
596
|
+
}
|
|
460
597
|
for (const candidate of payload.candidates) {
|
|
598
|
+
const built = buildCandidateProposal(candidate, data.ref, sessionAsset.sessionAssetRef);
|
|
461
599
|
if (dryRun) {
|
|
462
|
-
proposalIds.push(`dry-run:${
|
|
600
|
+
proposalIds.push(`dry-run:${built.ref}`);
|
|
463
601
|
continue;
|
|
464
602
|
}
|
|
465
603
|
try {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
// existing derived-layer node, down-prioritize by multiplying confidence by
|
|
469
|
-
// the penalty. PARITY: schemaSimilarityCtx is null when the flag is off →
|
|
470
|
-
// applySchemaSimilarityPenalty returns the original confidence untouched and
|
|
471
|
-
// never embeds. (Logic lives in schema-similarity-gate.ts so it is unit-testable.)
|
|
472
|
-
const gateResult = await applySchemaSimilarityPenalty(candidate, schemaSimilarityCtx, (text) => schemaSimilarityCtx?.embedFn
|
|
473
|
-
? schemaSimilarityCtx.embedFn(text)
|
|
474
|
-
: embed(text, schemaSimilarityCtx?.embeddingConfig));
|
|
475
|
-
const effectiveConfidence = gateResult.effectiveConfidence;
|
|
476
|
-
if (gateResult.warning)
|
|
477
|
-
warn(gateResult.warning);
|
|
478
|
-
const { ref, content, description } = buildCandidateProposal(candidate, sessionRef);
|
|
479
|
-
const result = createProposal(stashDir, {
|
|
604
|
+
const { ref, content, description } = built;
|
|
605
|
+
const result = emitProposal({ stashDir, proposalsCtx: ctx }, {
|
|
480
606
|
ref,
|
|
481
607
|
source: "extract",
|
|
482
608
|
sourceRun,
|
|
609
|
+
// §23.6 fingerprint model-id term (WI-6.4). The LLM already ran for
|
|
610
|
+
// this session, so the profile is resolvable; guard anyway.
|
|
611
|
+
...(extractModelId ? { modelId: extractModelId } : {}),
|
|
483
612
|
payload: {
|
|
484
613
|
content,
|
|
485
614
|
frontmatter: {
|
|
486
615
|
description,
|
|
487
616
|
...(candidate.when_to_use ? { when_to_use: candidate.when_to_use } : {}),
|
|
488
|
-
|
|
489
|
-
|
|
617
|
+
confidence: candidate.confidence,
|
|
618
|
+
...(sessionAsset.sessionAssetRef ? { xrefs: [sessionAsset.sessionAssetRef] } : {}),
|
|
490
619
|
evidence: candidate.evidence,
|
|
491
|
-
// #615 WS-0: mirror ordered-action + outcome data in the proposal
|
|
492
|
-
// frontmatter record so downstream tooling can read it without
|
|
493
|
-
// re-parsing the content body. Omitted when not present.
|
|
494
|
-
...(candidate.orderedActions && candidate.orderedActions.length > 0
|
|
495
|
-
? { orderedActions: candidate.orderedActions }
|
|
496
|
-
: {}),
|
|
497
|
-
...(candidate.outcomeData ? { outcomeData: candidate.outcomeData } : {}),
|
|
498
|
-
// WS-3b step 0c: tag system-generated extractions as hot-probation
|
|
499
|
-
// when the feature is enabled. The consolidation pass will exclude
|
|
500
|
-
// them from the LLM merge pool until the intake dedup+quality pass
|
|
501
|
-
// runs against them. User-explicit `akm remember` (captureMode: hot)
|
|
502
|
-
// is unaffected — this only applies to extract-generated proposals.
|
|
503
|
-
...(hotProbationEnabled ? buildHotProbationFrontmatter() : {}),
|
|
504
620
|
},
|
|
505
621
|
},
|
|
506
|
-
}
|
|
622
|
+
});
|
|
507
623
|
if (isProposalSkipped(result)) {
|
|
508
624
|
warnings.push(`candidate ${candidate.type}:${candidate.name} skipped: ${result.reason}: ${result.message}`);
|
|
509
625
|
}
|
|
@@ -517,7 +633,7 @@ standardsContext) {
|
|
|
517
633
|
}
|
|
518
634
|
appendEvent({
|
|
519
635
|
eventType: "extract_invoked",
|
|
520
|
-
ref:
|
|
636
|
+
...(sessionAsset.sessionAssetRef ? { ref: sessionAsset.sessionAssetRef } : {}),
|
|
521
637
|
metadata: {
|
|
522
638
|
outcome: "candidates_queued",
|
|
523
639
|
sessionId: sessionRef.sessionId,
|
|
@@ -528,8 +644,7 @@ standardsContext) {
|
|
|
528
644
|
preFilterInput: filtered.stats.inputCount,
|
|
529
645
|
preFilterOutput: filtered.stats.outputCount,
|
|
530
646
|
},
|
|
531
|
-
},
|
|
532
|
-
const sessionAsset = await maybeWriteSessionAsset();
|
|
647
|
+
}, eventsCtx);
|
|
533
648
|
return {
|
|
534
649
|
sessionId: sessionRef.sessionId,
|
|
535
650
|
harness: harness.name,
|
|
@@ -545,191 +660,34 @@ standardsContext) {
|
|
|
545
660
|
...sessionAsset,
|
|
546
661
|
};
|
|
547
662
|
}
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
const
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
schemaVersion: 1,
|
|
576
|
-
ok: true,
|
|
577
|
-
shape: "extract-result",
|
|
578
|
-
dryRun,
|
|
579
|
-
type: options.type,
|
|
580
|
-
sessionsProcessed: 0,
|
|
581
|
-
sessionsSkipped: 0,
|
|
582
|
-
candidatesCreated: 0,
|
|
583
|
-
proposals: [],
|
|
584
|
-
sessions: [],
|
|
585
|
-
warnings: [
|
|
586
|
-
"session_extraction feature disabled — set profiles.improve.default.processes.extract.enabled: true to use",
|
|
587
|
-
],
|
|
588
|
-
durationMs: Date.now() - startMs,
|
|
589
|
-
};
|
|
590
|
-
}
|
|
591
|
-
// Resolve the LLM connection. Priority order:
|
|
592
|
-
// 1. Options.config.profiles.improve.default.processes.extract.profile
|
|
593
|
-
// (per-process override, matches reflect/distill/consolidate)
|
|
594
|
-
// 2. config.defaults.llm (the default LLM profile)
|
|
595
|
-
// 3. throw — extract requires an LLM.
|
|
596
|
-
let llmConfig;
|
|
597
|
-
const runnerSpec = resolveImproveProcessRunnerFromProfile(extractProcess, config);
|
|
598
|
-
if (runnerSpec) {
|
|
599
|
-
if (!runnerIsLlm(runnerSpec)) {
|
|
600
|
-
throw new ConfigError(`Extract only supports mode: "llm" (in-tree LLM call). Got mode: "${runnerSpec.kind}" from profiles.improve.default.processes.extract — change it to "llm" or remove the override.`, "INVALID_CONFIG_FILE");
|
|
601
|
-
}
|
|
602
|
-
llmConfig = runnerSpec.connection;
|
|
603
|
-
}
|
|
604
|
-
else {
|
|
605
|
-
llmConfig = getDefaultLlmConfig(config) ?? undefined;
|
|
606
|
-
}
|
|
607
|
-
if (!llmConfig) {
|
|
608
|
-
throw new ConfigError("No LLM connection configured for extract. Set profiles.llm + defaults.llm, or set profiles.improve.default.processes.extract.profile to a configured LLM profile.");
|
|
609
|
-
}
|
|
610
|
-
// Honor per-process timeoutMs override; fall back to options.timeoutMs; then 60s.
|
|
611
|
-
const timeoutMs = options.timeoutMs ??
|
|
612
|
-
(typeof extractProcess?.timeoutMs === "number" ? extractProcess.timeoutMs : undefined) ??
|
|
613
|
-
60_000;
|
|
614
|
-
// Pre-filter budget — process config can raise it for large-context models.
|
|
615
|
-
const maxTotalChars = typeof extractProcess?.maxTotalChars === "number" ? extractProcess.maxTotalChars : undefined;
|
|
616
|
-
// #595/#596 — minimum raw session size; sessions below it skip the LLM call
|
|
617
|
-
// entirely. Set `processes.extract.minContentChars: 0` to disable the gate.
|
|
618
|
-
const minContentChars = typeof extractProcess?.minContentChars === "number" ? extractProcess.minContentChars : DEFAULT_MIN_CONTENT_CHARS;
|
|
619
|
-
// Cap on NEW sessions LLM-processed per run; 0 disables. Absent = default.
|
|
620
|
-
// Bounds per-run wall time / LLM cost so a backlog can't push a run past its
|
|
621
|
-
// task timeout — the overflow stays unseen and is picked up by later runs.
|
|
622
|
-
const maxSessionsPerRun = typeof extractProcess?.maxSessionsPerRun === "number"
|
|
623
|
-
? extractProcess.maxSessionsPerRun
|
|
624
|
-
: DEFAULT_MAX_SESSIONS_PER_RUN;
|
|
625
|
-
// Default discovery window — process config can override the built-in 24h.
|
|
626
|
-
const effectiveSince = options.since ?? extractProcess?.defaultSince;
|
|
627
|
-
// #626 — resolve the triage gate config once per run. Default-off → the
|
|
628
|
-
// per-session path never calls the scorer and emits no telemetry.
|
|
629
|
-
const triage = resolveTriageConfig(extractProcess);
|
|
630
|
-
// #561 — resolve session-indexing config. Default ON: we only reach this code
|
|
631
|
-
// when `session_extraction` is enabled AND an LLM is configured (both checked
|
|
632
|
-
// above), so defaulting on costs nothing offline (the summary call fails open)
|
|
633
|
-
// while making sessions searchable in the common LLM-configured case. Set
|
|
634
|
-
// `processes.extract.indexSessions: false` for byte-identical legacy behaviour.
|
|
635
|
-
const sessionIndexingEnabled = extractProcess?.indexSessions ?? true;
|
|
636
|
-
const minSessionDuration = typeof extractProcess?.minSessionDuration === "number"
|
|
637
|
-
? extractProcess.minSessionDuration
|
|
638
|
-
: DEFAULT_MIN_SESSION_DURATION_MINUTES;
|
|
639
|
-
// Production summary generator: a bounded in-tree LLM call wrapped in the same
|
|
640
|
-
// fail-open `tryLlmFeature` seam as the rest of extract. Returns `undefined`
|
|
641
|
-
// on disablement / timeout / error so no asset is written. Tests inject a fake.
|
|
642
|
-
const chatForSummary = options.chat ?? chatCompletion;
|
|
643
|
-
const defaultSessionSummaryGenerator = async (data) => {
|
|
644
|
-
let raw = "";
|
|
645
|
-
await tryLlmFeature("session_extraction", config, async () => {
|
|
646
|
-
raw = await chatForSummary(llmConfig, [{ role: "user", content: buildSessionSummaryPrompt(data) }], {
|
|
647
|
-
timeoutMs,
|
|
648
|
-
responseSchema: SESSION_SUMMARY_JSON_SCHEMA,
|
|
649
|
-
});
|
|
650
|
-
return raw;
|
|
651
|
-
}, "", { timeoutMs });
|
|
652
|
-
return parseSessionSummary(raw);
|
|
653
|
-
};
|
|
654
|
-
const sessionIndexing = {
|
|
655
|
-
enabled: sessionIndexingEnabled,
|
|
656
|
-
minDurationMinutes: minSessionDuration,
|
|
657
|
-
generate: options.generateSessionSummary ?? defaultSessionSummaryGenerator,
|
|
663
|
+
/**
|
|
664
|
+
* Iterate the discovered candidate sessions: enforce the per-run cap, take the
|
|
665
|
+
* per-session cross-process lock, dispatch to {@link processSession}, aggregate
|
|
666
|
+
* the #626 triage counters, and persist each seen-row outcome. Extracted verbatim
|
|
667
|
+
* from `akmExtract` — the maxSessionsPerRun break, lock/skip accounting, triage
|
|
668
|
+
* aggregation, and seen-row upsert are byte-identical.
|
|
669
|
+
*/
|
|
670
|
+
async function runExtractSessionLoop(args) {
|
|
671
|
+
const { candidates, options, harness, seenMap, stateDb, trackingEnabled, dryRun, stashDir, config, getLlmConfig, chat, sourceRun, timeoutMs, maxTotalChars, minContentChars, maxSessionsPerRun, triage, sessionIndexing, extractStandardsContext, topLevelWarnings, } = args;
|
|
672
|
+
// WI-7.7 §2: run-scoped processSession inputs, resolved once per run.
|
|
673
|
+
const sessionRunCtx = {
|
|
674
|
+
harness,
|
|
675
|
+
stashDir,
|
|
676
|
+
config,
|
|
677
|
+
getLlmConfig,
|
|
678
|
+
chat,
|
|
679
|
+
ctx: options.ctx,
|
|
680
|
+
eventsCtx: options.eventsCtx,
|
|
681
|
+
sourceRun,
|
|
682
|
+
dryRun,
|
|
683
|
+
timeoutMs,
|
|
684
|
+
maxTotalChars,
|
|
685
|
+
minContentChars,
|
|
686
|
+
triage,
|
|
687
|
+
sessionIndexing,
|
|
688
|
+
signal: options.signal,
|
|
689
|
+
standardsContext: extractStandardsContext,
|
|
658
690
|
};
|
|
659
|
-
const harness = resolveHarness(options.type, options.harnesses);
|
|
660
|
-
if (!harness) {
|
|
661
|
-
return {
|
|
662
|
-
schemaVersion: 1,
|
|
663
|
-
ok: false,
|
|
664
|
-
shape: "extract-result",
|
|
665
|
-
dryRun,
|
|
666
|
-
type: options.type,
|
|
667
|
-
sessionsProcessed: 0,
|
|
668
|
-
sessionsSkipped: 0,
|
|
669
|
-
candidatesCreated: 0,
|
|
670
|
-
proposals: [],
|
|
671
|
-
sessions: [],
|
|
672
|
-
warnings: [`no available harness matches type "${options.type}" (check that the platform is installed)`],
|
|
673
|
-
durationMs: Date.now() - startMs,
|
|
674
|
-
};
|
|
675
|
-
}
|
|
676
|
-
if (!harness.isAvailable()) {
|
|
677
|
-
return {
|
|
678
|
-
schemaVersion: 1,
|
|
679
|
-
ok: false,
|
|
680
|
-
shape: "extract-result",
|
|
681
|
-
dryRun,
|
|
682
|
-
type: options.type,
|
|
683
|
-
sessionsProcessed: 0,
|
|
684
|
-
sessionsSkipped: 0,
|
|
685
|
-
candidatesCreated: 0,
|
|
686
|
-
proposals: [],
|
|
687
|
-
sessions: [],
|
|
688
|
-
warnings: [`harness ${options.type} is registered but reports not-available (no session data on this machine)`],
|
|
689
|
-
durationMs: Date.now() - startMs,
|
|
690
|
-
};
|
|
691
|
-
}
|
|
692
|
-
// Decide which sessions to process: explicit sessionId OR discovery via since.
|
|
693
|
-
let candidates;
|
|
694
|
-
if (options.sessionId) {
|
|
695
|
-
const all = harness.listSessions({
|
|
696
|
-
...(options.location ? { location: options.location } : {}),
|
|
697
|
-
});
|
|
698
|
-
const target = all.find((s) => s.sessionId === options.sessionId);
|
|
699
|
-
if (!target) {
|
|
700
|
-
return {
|
|
701
|
-
schemaVersion: 1,
|
|
702
|
-
ok: false,
|
|
703
|
-
shape: "extract-result",
|
|
704
|
-
dryRun,
|
|
705
|
-
type: options.type,
|
|
706
|
-
sessionsProcessed: 0,
|
|
707
|
-
sessionsSkipped: 0,
|
|
708
|
-
candidatesCreated: 0,
|
|
709
|
-
proposals: [],
|
|
710
|
-
sessions: [],
|
|
711
|
-
warnings: [`session ${options.sessionId} not found for harness ${options.type}`],
|
|
712
|
-
durationMs: Date.now() - startMs,
|
|
713
|
-
};
|
|
714
|
-
}
|
|
715
|
-
candidates = [target];
|
|
716
|
-
}
|
|
717
|
-
else {
|
|
718
|
-
// No explicit `--since`/`defaultSince` → default to "since the last run"
|
|
719
|
-
// (floored at 48h) so an intermittently-online host doesn't lose sessions
|
|
720
|
-
// that ended while it was off. See {@link resolveDefaultSinceMs}.
|
|
721
|
-
const sinceMs = effectiveSince
|
|
722
|
-
? parseSinceArg(effectiveSince)
|
|
723
|
-
: resolveDefaultSinceMs(harness.name, startMs, {
|
|
724
|
-
...(options.stateDb ? { stateDb: options.stateDb } : {}),
|
|
725
|
-
...(options.stateDbPath ? { stateDbPath: options.stateDbPath } : {}),
|
|
726
|
-
...(options.skipTracking ? { skipTracking: options.skipTracking } : {}),
|
|
727
|
-
});
|
|
728
|
-
candidates = harness.listSessions({
|
|
729
|
-
sinceMs,
|
|
730
|
-
...(options.location ? { location: options.location } : {}),
|
|
731
|
-
});
|
|
732
|
-
}
|
|
733
691
|
const sessions = [];
|
|
734
692
|
let processedCount = 0;
|
|
735
693
|
let skippedCount = 0;
|
|
@@ -738,51 +696,9 @@ export async function akmExtract(options) {
|
|
|
738
696
|
let triagePassed = 0;
|
|
739
697
|
let triagedOut = 0;
|
|
740
698
|
const allProposalIds = [];
|
|
741
|
-
const topLevelWarnings = [];
|
|
742
|
-
const chat = options.chat ?? chatCompletion;
|
|
743
|
-
// Open state.db once for the run and bulk-load seen-rows for the candidate
|
|
744
|
-
// set so we can decide skip/process in O(1) per session. Tracking is opt-out
|
|
745
|
-
// via options.skipTracking (used by tests + one-shot debug calls).
|
|
746
|
-
const trackingEnabled = options.skipTracking !== true;
|
|
747
|
-
let stateDb;
|
|
748
|
-
let seenMap = new Map();
|
|
749
|
-
if (trackingEnabled && candidates.length > 0) {
|
|
750
|
-
try {
|
|
751
|
-
stateDb = options.stateDb ?? openStateDatabase(options.stateDbPath);
|
|
752
|
-
seenMap = getExtractedSessionsMap(stateDb, harness.name, candidates.map((c) => c.sessionId));
|
|
753
|
-
}
|
|
754
|
-
catch (err) {
|
|
755
|
-
// state.db open is best-effort — log and proceed without skip-tracking
|
|
756
|
-
// so a transient sqlite error never blocks the actual extraction.
|
|
757
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
758
|
-
warn(`[extract] state.db unavailable, processing without skip-tracking: ${msg}`);
|
|
759
|
-
topLevelWarnings.push(`state.db unavailable: ${msg}`);
|
|
760
|
-
stateDb = undefined;
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
// WS-3b Step-0b: schema-similarity intake gate.
|
|
764
|
-
// DEFAULT ON since R3 (docs/design/improve-self-learning-analysis.md G5):
|
|
765
|
-
// extract is the highest-volume acquisition path with no LLM judge, so the
|
|
766
|
-
// cheap embedding-dedup check (one embed per lesson/knowledge candidate,
|
|
767
|
-
// fail-open) is the intake quality gate. Opt out via
|
|
768
|
-
// processes.extract.schemaSimilarity.enabled: false. The gate is inert in
|
|
769
|
-
// practice when no derived-layer embeddings exist (empty ctx → no penalty).
|
|
770
|
-
const schemaSimilarityCfg = extractProcess?.schemaSimilarity;
|
|
771
|
-
let schemaSimilarityCtx = null;
|
|
772
|
-
if (schemaSimilarityCfg?.enabled !== false) {
|
|
773
|
-
const derivedEmbeddings = options.schemaSimilarityEmbeddings ?? loadDerivedLayerEmbeddings();
|
|
774
|
-
schemaSimilarityCtx = {
|
|
775
|
-
config: { ...schemaSimilarityCfg, enabled: true },
|
|
776
|
-
derivedEmbeddings,
|
|
777
|
-
embeddingConfig: config.embedding,
|
|
778
|
-
embedFn: options.schemaSimilarityEmbedFn,
|
|
779
|
-
};
|
|
780
|
-
}
|
|
781
|
-
// Stash authoring standards (convention/meta fact bodies) for non-wiki
|
|
782
|
-
// extract output. Resolved ONCE per run and threaded into each session's
|
|
783
|
-
// prompt so facts are not re-read per session.
|
|
784
|
-
const extractStandardsContext = resolveStashStandards(stashDir);
|
|
785
699
|
for (const summary of candidates) {
|
|
700
|
+
if (options.signal?.aborted)
|
|
701
|
+
break;
|
|
786
702
|
// #602 — the already-extracted skip moved INTO processSession (the content
|
|
787
703
|
// hash needs the session body, only available after readSession). The prior
|
|
788
704
|
// row + bypass flags are threaded through; an unchanged session returns
|
|
@@ -802,10 +718,11 @@ export async function akmExtract(options) {
|
|
|
802
718
|
// runs (those that open their own state.db): dry-run is read-only, an
|
|
803
719
|
// injected `stateDb` handle is an in-process/test scenario with no cross-
|
|
804
720
|
// process race, and skip-tracking-off opts out entirely.
|
|
805
|
-
let
|
|
721
|
+
let sessionLockOwnership;
|
|
806
722
|
if (trackingEnabled && !dryRun && !options.stateDb) {
|
|
807
|
-
sessionLockPath = getExtractSessionLockPath(harness.name, summary.sessionId, options.stateDbPath ?? getStateDbPath());
|
|
808
|
-
|
|
723
|
+
const sessionLockPath = getExtractSessionLockPath(harness.name, summary.sessionId, options.stateDbPath ?? getStateDbPath());
|
|
724
|
+
const sessionLock = acquireExtractSessionLock(sessionLockPath);
|
|
725
|
+
if (!sessionLock.proceed) {
|
|
809
726
|
sessions.push({
|
|
810
727
|
sessionId: summary.sessionId,
|
|
811
728
|
harness: harness.name,
|
|
@@ -819,9 +736,14 @@ export async function akmExtract(options) {
|
|
|
819
736
|
skippedCount += 1;
|
|
820
737
|
continue;
|
|
821
738
|
}
|
|
739
|
+
sessionLockOwnership = sessionLock.ownership;
|
|
822
740
|
}
|
|
823
741
|
try {
|
|
824
|
-
const result = await processSession(
|
|
742
|
+
const result = await processSession(sessionRunCtx, {
|
|
743
|
+
sessionRef: summary,
|
|
744
|
+
prior,
|
|
745
|
+
force: options.force === true,
|
|
746
|
+
});
|
|
825
747
|
sessions.push(result);
|
|
826
748
|
// #626 — triage aggregation. A session reached the triage gate only when it
|
|
827
749
|
// was NOT already preempted by an earlier skip (read_failed / too_short /
|
|
@@ -916,10 +838,332 @@ export async function akmExtract(options) {
|
|
|
916
838
|
skippedCount += 1;
|
|
917
839
|
}
|
|
918
840
|
finally {
|
|
919
|
-
if (
|
|
920
|
-
releaseLock(
|
|
841
|
+
if (sessionLockOwnership)
|
|
842
|
+
releaseLock(sessionLockOwnership);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
return { sessions, processedCount, skippedCount, triageEvaluated, triagePassed, triagedOut, allProposalIds };
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Resolve the run-scoped LLM/engine, budget, triage, and session-indexing
|
|
849
|
+
* settings for one extract invocation (throwing when no engine is configured).
|
|
850
|
+
* Extracted verbatim from `akmExtract` — the timeout precedence chain, the
|
|
851
|
+
* session-summary generator seam, and the default resolutions are byte-identical.
|
|
852
|
+
*/
|
|
853
|
+
function resolveExtractRunConfig(options, config, extractProcess, activeProfile) {
|
|
854
|
+
// Improve supplies its invocation-owned connection. Standalone extract
|
|
855
|
+
// resolves the selected process engine, then defaults.llmEngine.
|
|
856
|
+
const runnerSpec = options.resolvedPlan
|
|
857
|
+
? options.resolvedPlan.runner
|
|
858
|
+
: resolveImproveProcessRunner(activeProfile, "extract", config);
|
|
859
|
+
const fixedLlmConfig = options.resolvedPlan ? undefined : options.llmConfig;
|
|
860
|
+
if (!runnerSpec && !fixedLlmConfig) {
|
|
861
|
+
throw new ConfigError("No LLM engine configured for extract. Set defaults.llmEngine or improve.strategies.<name>.processes.extract.engine.", "LLM_NOT_CONFIGURED");
|
|
862
|
+
}
|
|
863
|
+
const timeoutMs = options.resolvedPlan
|
|
864
|
+
? options.resolvedPlan.timeoutMs
|
|
865
|
+
: Object.hasOwn(options, "timeoutMs")
|
|
866
|
+
? (options.timeoutMs ?? null)
|
|
867
|
+
: runnerSpec?.timeoutMs !== undefined
|
|
868
|
+
? runnerSpec.timeoutMs
|
|
869
|
+
: fixedLlmConfig && Object.hasOwn(fixedLlmConfig, "timeoutMs")
|
|
870
|
+
? (fixedLlmConfig.timeoutMs ?? null)
|
|
871
|
+
: 600_000;
|
|
872
|
+
const getLlmConfig = () => runnerSpec ? materializeLlmRunnerConnection(runnerSpec) : fixedLlmConfig;
|
|
873
|
+
// Pre-filter budget — process config can raise it for large-context models.
|
|
874
|
+
const maxTotalChars = typeof extractProcess?.maxTotalChars === "number" ? extractProcess.maxTotalChars : undefined;
|
|
875
|
+
// #595/#596 — minimum raw session size; sessions below it skip the LLM call
|
|
876
|
+
// entirely. Set `processes.extract.minContentChars: 0` to disable the gate.
|
|
877
|
+
const minContentChars = typeof extractProcess?.minContentChars === "number" ? extractProcess.minContentChars : DEFAULT_MIN_CONTENT_CHARS;
|
|
878
|
+
// Cap on NEW sessions LLM-processed per run; 0 disables. Absent = default.
|
|
879
|
+
// Bounds per-run wall time / LLM cost so a backlog can't push a run past its
|
|
880
|
+
// task timeout — the overflow stays unseen and is picked up by later runs.
|
|
881
|
+
const maxSessionsPerRun = typeof extractProcess?.maxSessionsPerRun === "number"
|
|
882
|
+
? extractProcess.maxSessionsPerRun
|
|
883
|
+
: DEFAULT_MAX_SESSIONS_PER_RUN;
|
|
884
|
+
// Default discovery window — process config can override the built-in 24h.
|
|
885
|
+
const effectiveSince = options.since ?? extractProcess?.defaultSince;
|
|
886
|
+
// #626 — resolve the triage gate config once per run. Default-off → the
|
|
887
|
+
// per-session path never calls the scorer and emits no telemetry.
|
|
888
|
+
const triage = resolveTriageConfig(extractProcess);
|
|
889
|
+
// #561 — resolve session-indexing config. Default ON: we only reach this code
|
|
890
|
+
// when `session_extraction` is enabled AND an LLM is configured (both checked
|
|
891
|
+
// above), so defaulting on costs nothing offline (the summary call fails open)
|
|
892
|
+
// while making sessions searchable in the common LLM-configured case. Set
|
|
893
|
+
// `processes.extract.indexSessions: false` for byte-identical legacy behaviour.
|
|
894
|
+
const sessionIndexingEnabled = extractProcess?.indexSessions ?? true;
|
|
895
|
+
const minSessionDuration = typeof extractProcess?.minSessionDuration === "number"
|
|
896
|
+
? extractProcess.minSessionDuration
|
|
897
|
+
: DEFAULT_MIN_SESSION_DURATION_MINUTES;
|
|
898
|
+
// Production summary generator: a bounded in-tree LLM call wrapped in the
|
|
899
|
+
// same fail-open `callStructured` seam as the rest of extract. Returns
|
|
900
|
+
// `undefined` on disablement / timeout / error so no asset is written.
|
|
901
|
+
// Tests inject a fake.
|
|
902
|
+
const defaultSessionSummaryGenerator = async (data) => {
|
|
903
|
+
// Same fail-open contract as the per-session call: a getLlmConfig()
|
|
904
|
+
// throw takes the "" fallback rather than propagating.
|
|
905
|
+
let summaryLlm;
|
|
906
|
+
try {
|
|
907
|
+
summaryLlm = getLlmConfig();
|
|
908
|
+
}
|
|
909
|
+
catch {
|
|
910
|
+
summaryLlm = undefined;
|
|
911
|
+
}
|
|
912
|
+
let raw = "";
|
|
913
|
+
if (summaryLlm !== undefined) {
|
|
914
|
+
await callStructured({
|
|
915
|
+
feature: "session_extraction",
|
|
916
|
+
akmConfig: config,
|
|
917
|
+
config: summaryLlm,
|
|
918
|
+
messages: [{ role: "user", content: buildSessionSummaryPrompt(data) }],
|
|
919
|
+
request: {
|
|
920
|
+
timeoutMs,
|
|
921
|
+
responseSchema: SESSION_SUMMARY_JSON_SCHEMA,
|
|
922
|
+
...(options.chat ? { chat: options.chat } : {}),
|
|
923
|
+
},
|
|
924
|
+
parse: (r) => {
|
|
925
|
+
raw = r ?? "";
|
|
926
|
+
return raw;
|
|
927
|
+
},
|
|
928
|
+
onError: () => "",
|
|
929
|
+
fallback: "",
|
|
930
|
+
});
|
|
921
931
|
}
|
|
932
|
+
return parseSessionSummary(raw);
|
|
933
|
+
};
|
|
934
|
+
const sessionIndexing = {
|
|
935
|
+
enabled: sessionIndexingEnabled,
|
|
936
|
+
minDurationMinutes: minSessionDuration,
|
|
937
|
+
generate: options.generateSessionSummary ?? defaultSessionSummaryGenerator,
|
|
938
|
+
};
|
|
939
|
+
return {
|
|
940
|
+
timeoutMs,
|
|
941
|
+
getLlmConfig,
|
|
942
|
+
maxTotalChars,
|
|
943
|
+
minContentChars,
|
|
944
|
+
maxSessionsPerRun,
|
|
945
|
+
effectiveSince,
|
|
946
|
+
triage,
|
|
947
|
+
sessionIndexing,
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Resolve the session set to process: the single `--session-id` target (or a
|
|
952
|
+
* not-found envelope) or the discovery-window listing. Extracted verbatim from
|
|
953
|
+
* `akmExtract`; the 48h default-since floor and location filter are unchanged.
|
|
954
|
+
*/
|
|
955
|
+
function discoverExtractCandidates(options, harness, effectiveSince, startMs, dryRun) {
|
|
956
|
+
if (options.sessionId) {
|
|
957
|
+
const all = harness.listSessions({
|
|
958
|
+
...(options.location ? { location: options.location } : {}),
|
|
959
|
+
});
|
|
960
|
+
const target = all.find((s) => s.sessionId === options.sessionId);
|
|
961
|
+
if (!target) {
|
|
962
|
+
return {
|
|
963
|
+
notFound: {
|
|
964
|
+
schemaVersion: 1,
|
|
965
|
+
ok: false,
|
|
966
|
+
shape: "extract-result",
|
|
967
|
+
dryRun,
|
|
968
|
+
type: options.type,
|
|
969
|
+
sessionsProcessed: 0,
|
|
970
|
+
sessionsSkipped: 0,
|
|
971
|
+
candidatesCreated: 0,
|
|
972
|
+
proposals: [],
|
|
973
|
+
sessions: [],
|
|
974
|
+
warnings: [`session ${options.sessionId} not found for harness ${options.type}`],
|
|
975
|
+
durationMs: Date.now() - startMs,
|
|
976
|
+
},
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
return { candidates: [target] };
|
|
922
980
|
}
|
|
981
|
+
// No explicit `--since`/`defaultSince` → default to "since the last run"
|
|
982
|
+
// (floored at 48h) so an intermittently-online host doesn't lose sessions
|
|
983
|
+
// that ended while it was off. See {@link resolveDefaultSinceMs}.
|
|
984
|
+
const sinceMs = effectiveSince
|
|
985
|
+
? parseSinceArg(effectiveSince)
|
|
986
|
+
: resolveDefaultSinceMs(harness.name, startMs, {
|
|
987
|
+
...(options.stateDb ? { stateDb: options.stateDb } : {}),
|
|
988
|
+
...(options.stateDbPath ? { stateDbPath: options.stateDbPath } : {}),
|
|
989
|
+
...(options.skipTracking ? { skipTracking: options.skipTracking } : {}),
|
|
990
|
+
});
|
|
991
|
+
return {
|
|
992
|
+
candidates: harness.listSessions({
|
|
993
|
+
sinceMs,
|
|
994
|
+
...(options.location ? { location: options.location } : {}),
|
|
995
|
+
}),
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
// ── Public entrypoint ────────────────────────────────────────────────────────
|
|
999
|
+
/**
|
|
1000
|
+
* WI-9.10: build one `akm extract` run's {@link RunContext} from values
|
|
1001
|
+
* `akmExtract` has already resolved by the time it calls this (config,
|
|
1002
|
+
* stashDir, dryRun, sourceRun, and `resolveExtractRunConfig`'s own
|
|
1003
|
+
* `getLlmConfig`) — no second config load, no new db handle.
|
|
1004
|
+
*
|
|
1005
|
+
* `RunContext.getLlmConfig` is typed `() => LlmConnectionConfig | null`, but
|
|
1006
|
+
* extract's own resolved `getLlmConfig` returns `LlmProfileConfig` (a
|
|
1007
|
+
* superset — `supportsJsonSchema` — of `LlmConnectionConfig`) and, per its
|
|
1008
|
+
* documented fail-open contract, MAY THROW (`materializeLlmConnection` can
|
|
1009
|
+
* raise ConfigError) rather than return null; every existing caller in this
|
|
1010
|
+
* file wraps it in try/catch for exactly that reason. The thin closure below
|
|
1011
|
+
* adapts at the boundary: it derives from the SAME already-resolved
|
|
1012
|
+
* runner/profile (this doesn't widen `RunContext.getLlmConfig`'s type), and —
|
|
1013
|
+
* matching the file's own fail-open contract — coalesces a throw to `null`
|
|
1014
|
+
* instead of propagating.
|
|
1015
|
+
*/
|
|
1016
|
+
function buildExtractRunContext(args) {
|
|
1017
|
+
const { options, config, stashDir, dryRun, sourceRun, getLlmConfig } = args;
|
|
1018
|
+
const getRunContextLlmConfig = () => {
|
|
1019
|
+
try {
|
|
1020
|
+
return getLlmConfig();
|
|
1021
|
+
}
|
|
1022
|
+
catch {
|
|
1023
|
+
return null;
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
return createRunContext({
|
|
1027
|
+
stashDir,
|
|
1028
|
+
config,
|
|
1029
|
+
eventsCtx: options.eventsCtx ?? {},
|
|
1030
|
+
// Not yet wired into any proposal call site this stage (mirrors
|
|
1031
|
+
// buildImproveRunContext's proposalsCtx comment in improve.ts).
|
|
1032
|
+
proposalsCtx: options.ctx ?? {},
|
|
1033
|
+
getLlmConfig: getRunContextLlmConfig,
|
|
1034
|
+
sourceRun,
|
|
1035
|
+
dryRun,
|
|
1036
|
+
signal: options.signal,
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
export async function akmExtract(options) {
|
|
1040
|
+
const startMs = Date.now();
|
|
1041
|
+
if (!options.type || options.type.trim() === "") {
|
|
1042
|
+
throw new UsageError("--type is required. Pass a harness name (e.g. --type claude-code).", "MISSING_REQUIRED_ARGUMENT");
|
|
1043
|
+
}
|
|
1044
|
+
const config = options.config ?? loadConfig();
|
|
1045
|
+
const stashDir = resolveRunStashDir(options.stashDir);
|
|
1046
|
+
const dryRun = options.dryRun ?? false;
|
|
1047
|
+
const sourceRun = options.sourceRun ?? `extract-${timestampForFilename()}`;
|
|
1048
|
+
// Read process behavior from the frozen standalone plan or the active improve
|
|
1049
|
+
// strategy. This prevents config changes during watch mode from changing later
|
|
1050
|
+
// triggers and prevents one improve strategy from overriding another.
|
|
1051
|
+
const activeProfile = options.improveProfile ?? (options.resolvedPlan ? undefined : resolveImproveStrategy(undefined, config).config);
|
|
1052
|
+
const extractProcess = options.resolvedPlan?.process ?? getImproveProcessConfig("extract", activeProfile);
|
|
1053
|
+
// The `extract.enabled` process toggle gates extract as a STAGE of `akm improve`
|
|
1054
|
+
// (the activeProfile path) — consistent with #593/#594 where the active profile,
|
|
1055
|
+
// not `default`, is the source of truth. An EXPLICIT `akm extract` invocation
|
|
1056
|
+
// (no activeProfile) is a direct user/cron action and always runs; gating it on
|
|
1057
|
+
// the default improve profile's stage toggle was a footgun — dropping extract
|
|
1058
|
+
// from the daily improve profile would silently disable the standalone command.
|
|
1059
|
+
const extractEnabled = options.resolvedPlan?.enabled ??
|
|
1060
|
+
(options.improveProfile ? resolveProcessEnabled("extract", options.improveProfile) : true);
|
|
1061
|
+
// Feature-gate early so we get a clean "skipped because disabled" envelope.
|
|
1062
|
+
if (!extractEnabled) {
|
|
1063
|
+
return {
|
|
1064
|
+
schemaVersion: 1,
|
|
1065
|
+
ok: true,
|
|
1066
|
+
shape: "extract-result",
|
|
1067
|
+
dryRun,
|
|
1068
|
+
type: options.type,
|
|
1069
|
+
sessionsProcessed: 0,
|
|
1070
|
+
sessionsSkipped: 0,
|
|
1071
|
+
candidatesCreated: 0,
|
|
1072
|
+
proposals: [],
|
|
1073
|
+
sessions: [],
|
|
1074
|
+
warnings: ["extract is disabled by the selected improve strategy"],
|
|
1075
|
+
durationMs: Date.now() - startMs,
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
const { timeoutMs, getLlmConfig, maxTotalChars, minContentChars, maxSessionsPerRun, effectiveSince, triage, sessionIndexing, } = resolveExtractRunConfig(options, config, extractProcess, activeProfile);
|
|
1079
|
+
// WI-9.10: construct this run's RunContext (extracted to
|
|
1080
|
+
// buildExtractRunContext to keep akmExtract under the fn-size bar — R31).
|
|
1081
|
+
const ctx = buildExtractRunContext({ options, config, stashDir, dryRun, sourceRun, getLlmConfig });
|
|
1082
|
+
const harness = resolveHarness(options.type, options.harnesses);
|
|
1083
|
+
if (!harness) {
|
|
1084
|
+
return {
|
|
1085
|
+
schemaVersion: 1,
|
|
1086
|
+
ok: false,
|
|
1087
|
+
shape: "extract-result",
|
|
1088
|
+
dryRun,
|
|
1089
|
+
type: options.type,
|
|
1090
|
+
sessionsProcessed: 0,
|
|
1091
|
+
sessionsSkipped: 0,
|
|
1092
|
+
candidatesCreated: 0,
|
|
1093
|
+
proposals: [],
|
|
1094
|
+
sessions: [],
|
|
1095
|
+
warnings: [`no available harness matches type "${options.type}" (check that the platform is installed)`],
|
|
1096
|
+
durationMs: Date.now() - startMs,
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
if (!harness.isAvailable()) {
|
|
1100
|
+
return {
|
|
1101
|
+
schemaVersion: 1,
|
|
1102
|
+
ok: false,
|
|
1103
|
+
shape: "extract-result",
|
|
1104
|
+
dryRun,
|
|
1105
|
+
type: options.type,
|
|
1106
|
+
sessionsProcessed: 0,
|
|
1107
|
+
sessionsSkipped: 0,
|
|
1108
|
+
candidatesCreated: 0,
|
|
1109
|
+
proposals: [],
|
|
1110
|
+
sessions: [],
|
|
1111
|
+
warnings: [`harness ${options.type} is registered but reports not-available (no session data on this machine)`],
|
|
1112
|
+
durationMs: Date.now() - startMs,
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
// Decide which sessions to process: explicit sessionId OR discovery via since.
|
|
1116
|
+
const discovery = discoverExtractCandidates(options, harness, effectiveSince, startMs, dryRun);
|
|
1117
|
+
if ("notFound" in discovery)
|
|
1118
|
+
return discovery.notFound;
|
|
1119
|
+
const candidates = discovery.candidates;
|
|
1120
|
+
const topLevelWarnings = [];
|
|
1121
|
+
// Open state.db once for the run and bulk-load seen-rows for the candidate
|
|
1122
|
+
// set so we can decide skip/process in O(1) per session. Tracking is opt-out
|
|
1123
|
+
// via options.skipTracking (used by tests + one-shot debug calls).
|
|
1124
|
+
const trackingEnabled = options.skipTracking !== true;
|
|
1125
|
+
let stateDb;
|
|
1126
|
+
let seenMap = new Map();
|
|
1127
|
+
if (trackingEnabled && candidates.length > 0) {
|
|
1128
|
+
try {
|
|
1129
|
+
stateDb = options.stateDb ?? openStateDatabase(options.stateDbPath);
|
|
1130
|
+
seenMap = getExtractedSessionsMap(stateDb, harness.name, candidates.map((c) => c.sessionId));
|
|
1131
|
+
}
|
|
1132
|
+
catch (err) {
|
|
1133
|
+
// state.db open is best-effort — log and proceed without skip-tracking
|
|
1134
|
+
// so a transient sqlite error never blocks the actual extraction.
|
|
1135
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1136
|
+
warn(`[extract] state.db unavailable, processing without skip-tracking: ${msg}`);
|
|
1137
|
+
topLevelWarnings.push(`state.db unavailable: ${msg}`);
|
|
1138
|
+
stateDb = undefined;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
// Stash authoring standards (convention/meta fact bodies) for non-wiki
|
|
1142
|
+
// extract output. Resolved ONCE per run and threaded into each session's
|
|
1143
|
+
// prompt so facts are not re-read per session.
|
|
1144
|
+
const extractStandardsContext = resolveExtractStandards(stashDir);
|
|
1145
|
+
const { sessions, processedCount, skippedCount, triageEvaluated, triagePassed, triagedOut, allProposalIds } = await runExtractSessionLoop({
|
|
1146
|
+
candidates,
|
|
1147
|
+
options,
|
|
1148
|
+
harness,
|
|
1149
|
+
seenMap,
|
|
1150
|
+
stateDb,
|
|
1151
|
+
trackingEnabled,
|
|
1152
|
+
dryRun,
|
|
1153
|
+
stashDir,
|
|
1154
|
+
config,
|
|
1155
|
+
getLlmConfig,
|
|
1156
|
+
chat: options.chat,
|
|
1157
|
+
sourceRun,
|
|
1158
|
+
timeoutMs,
|
|
1159
|
+
maxTotalChars,
|
|
1160
|
+
minContentChars,
|
|
1161
|
+
maxSessionsPerRun,
|
|
1162
|
+
triage,
|
|
1163
|
+
sessionIndexing,
|
|
1164
|
+
extractStandardsContext,
|
|
1165
|
+
topLevelWarnings,
|
|
1166
|
+
});
|
|
923
1167
|
// Close the state.db connection we opened. Callers that injected stateDb
|
|
924
1168
|
// via the test seam own its lifecycle.
|
|
925
1169
|
if (stateDb && !options.stateDb) {
|
|
@@ -942,13 +1186,18 @@ export async function akmExtract(options) {
|
|
|
942
1186
|
triagedOut,
|
|
943
1187
|
sourceRun,
|
|
944
1188
|
},
|
|
945
|
-
}, options.
|
|
1189
|
+
}, options.eventsCtx);
|
|
946
1190
|
}
|
|
947
1191
|
return {
|
|
948
1192
|
schemaVersion: 1,
|
|
949
1193
|
ok: true,
|
|
950
1194
|
shape: "extract-result",
|
|
951
|
-
dryRun
|
|
1195
|
+
// Sourced from ctx (identical value to the local `dryRun` — see the
|
|
1196
|
+
// RunContext construction above) so the constructed RunContext has a
|
|
1197
|
+
// genuine downstream reference in this verb, which currently has no
|
|
1198
|
+
// content-read site to route through ctx.readAsset (see the WI-9.10c
|
|
1199
|
+
// report).
|
|
1200
|
+
dryRun: ctx.dryRun,
|
|
952
1201
|
type: options.type,
|
|
953
1202
|
sessionsProcessed: processedCount,
|
|
954
1203
|
sessionsSkipped: skippedCount,
|
|
@@ -975,8 +1224,8 @@ export async function akmExtract(options) {
|
|
|
975
1224
|
* over-/under-count here only affects whether the pass RUNS, never whether a
|
|
976
1225
|
* changed session is actually re-processed.
|
|
977
1226
|
*/
|
|
978
|
-
export function countNewExtractCandidates(
|
|
979
|
-
const extractProcess = getImproveProcessConfig(
|
|
1227
|
+
export function countNewExtractCandidates(_config, options = {}) {
|
|
1228
|
+
const extractProcess = getImproveProcessConfig("extract", options.improveProfile);
|
|
980
1229
|
const effectiveSince = options.since ?? extractProcess?.defaultSince;
|
|
981
1230
|
// Mirror akmExtract: when no explicit window is set, default per-harness to
|
|
982
1231
|
// "since the last run" (floored at 48h) instead of a fixed 24h. Keeps this
|