akm-cli 0.9.16 → 0.9.17-alpha.10
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 +2101 -0
- package/STABILITY.md +11 -10
- package/dist/akm +124 -193
- package/dist/akm-migrate +38 -19
- package/dist/assets/hints/cli-hints-full.md +6 -7
- package/dist/assets/improve-strategies/catchup.json +0 -3
- package/dist/assets/improve-strategies/consolidate.json +0 -1
- package/dist/assets/improve-strategies/default.json +1 -2
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -2
- package/dist/assets/improve-strategies/quick.json +1 -2
- package/dist/assets/improve-strategies/reflect-distill.json +1 -2
- package/dist/assets/improve-strategies/thorough.json +0 -3
- package/dist/assets/prompts/consolidate-pair.md +20 -0
- package/dist/assets/prompts/consolidate-system.md +4 -11
- package/dist/assets/prompts/retrieval-relevance-judge.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +20 -20
- package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -2
- package/dist/assets/templates/html/health.html +3 -5
- package/dist/cli/retired-commands.js +1 -1
- package/dist/cli/shared.js +6 -2
- package/dist/cli/unknown-flags.js +24 -1
- package/dist/cli.js +68 -10
- package/dist/commands/agent/agent-dispatch.js +1 -1
- package/dist/commands/command/command-execution.js +24 -62
- package/dist/commands/feedback-cli.js +0 -1
- package/dist/commands/health/accept-rate.js +6 -0
- package/dist/commands/health/archive-usage.js +92 -0
- package/dist/commands/health/checks.js +83 -74
- package/dist/commands/health/config-skew.js +38 -0
- package/dist/commands/health/data-dir-usage.js +25 -13
- package/dist/commands/health/egress.js +54 -0
- package/dist/commands/health/html-report.js +1 -42
- package/dist/commands/health/improve-metrics.js +136 -591
- package/dist/commands/health/md-report.js +1 -6
- package/dist/commands/health/plugin-staleness.js +53 -3
- package/dist/commands/health/renderers.js +12 -4
- package/dist/commands/health/report-view-model.js +14 -120
- package/dist/commands/health/types-improve.js +4 -19
- package/dist/commands/health/windows.js +64 -74
- package/dist/commands/health.js +145 -143
- package/dist/commands/improve/consolidate/chunking.js +26 -117
- package/dist/commands/improve/consolidate/continuity-check.js +137 -0
- package/dist/commands/improve/consolidate/pair-pass.js +791 -0
- package/dist/commands/improve/consolidate/sanitize.js +54 -149
- package/dist/commands/improve/consolidate.js +589 -1127
- package/dist/commands/improve/content-hash.js +16 -24
- package/dist/commands/improve/distill/content-repair.js +18 -100
- package/dist/commands/improve/distill-guards.js +20 -81
- package/dist/commands/improve/distill-promotion-policy.js +23 -243
- package/dist/commands/improve/distill.js +608 -1041
- package/dist/commands/improve/eligibility.js +126 -390
- package/dist/commands/improve/execution.js +8 -10
- package/dist/commands/improve/extract-prompt.js +1 -2
- package/dist/commands/improve/extract.js +487 -1046
- package/dist/commands/improve/feedback-valence.js +0 -25
- package/dist/commands/improve/improve-cli.js +75 -169
- package/dist/commands/improve/improve-result-file.js +10 -66
- package/dist/commands/improve/improve-strategies.js +52 -4
- package/dist/commands/improve/improve-usage-report.js +18 -64
- package/dist/commands/improve/improve.js +480 -1074
- package/dist/commands/improve/ledger.js +119 -0
- package/dist/commands/improve/locks.js +2 -8
- package/dist/commands/improve/loop-stages.js +415 -1073
- package/dist/commands/improve/memory/derived-ref.js +12 -77
- package/dist/commands/improve/memory/memory-belief.js +16 -118
- package/dist/commands/improve/memory/memory-improve.js +266 -14
- package/dist/commands/improve/outcome-loop.js +28 -156
- package/dist/commands/improve/planner.js +5 -15
- package/dist/commands/improve/preparation.js +779 -2319
- package/dist/commands/improve/proactive-maintenance.js +34 -101
- package/dist/commands/improve/reflect-noise.js +104 -280
- package/dist/commands/improve/reflect.js +642 -1353
- package/dist/commands/improve/retrieval-gate.js +127 -0
- package/dist/commands/improve/retrieval-scope.js +92 -0
- package/dist/commands/improve/salience.js +41 -240
- package/dist/commands/improve/session-asset.js +19 -100
- package/dist/commands/improve/stage.js +322 -0
- package/dist/commands/lint/base-linter.js +37 -15
- package/dist/commands/proposal/drain.js +261 -578
- package/dist/commands/proposal/proposal-cli.js +19 -20
- package/dist/commands/proposal/proposal-types.js +31 -24
- package/dist/commands/proposal/proposal.js +38 -8
- package/dist/commands/proposal/propose.js +134 -160
- package/dist/commands/proposal/repository.js +1097 -1394
- package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
- package/dist/commands/proposal/validators/proposal-validators.js +1 -1
- package/dist/commands/proposal/validators/proposals.js +22 -89
- package/dist/commands/read/curate.js +105 -462
- package/dist/commands/read/knowledge.js +3 -2
- package/dist/commands/read/search-cli.js +16 -33
- package/dist/commands/read/search.js +17 -23
- package/dist/commands/read/show.js +57 -108
- package/dist/commands/sources/bundle-cli.js +25 -2
- package/dist/commands/sources/bundle-config-ops.js +4 -0
- package/dist/commands/sources/dangerous-env-audit.js +1 -2
- package/dist/commands/sources/info.js +127 -29
- package/dist/commands/sources/installed-stashes.js +197 -746
- package/dist/commands/sources/schema-repair.js +98 -129
- package/dist/commands/sources/source-add.js +62 -12
- package/dist/commands/sources/source-manage.js +9 -2
- package/dist/commands/sources/stash-cli.js +24 -4
- package/dist/commands/tasks/explain.js +10 -13
- package/dist/commands/tasks/tasks-cli.js +12 -13
- package/dist/commands/tasks/tasks.js +350 -936
- package/dist/commands/tasks/validate.js +26 -24
- package/dist/commands/workflow/plan.js +22 -29
- package/dist/commands/workflow-cli.js +4 -4
- package/dist/core/adapter/adapters/akm-adapter.js +2 -1
- package/dist/core/adapter/adapters/akm-lint.js +2 -3
- package/dist/core/adapter/adapters/akm-metadata.js +42 -12
- package/dist/core/adapter/adapters/akm-task-adapter.js +29 -8
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
- package/dist/core/adapter/execution-source.js +17 -29
- package/dist/core/asset/asset-placement.js +4 -13
- package/dist/core/asset/frontmatter.js +106 -1
- package/dist/core/asset/resolve-ref.js +1 -1
- package/dist/core/bundle-id.js +42 -5
- package/dist/core/bundle-rename.js +285 -0
- package/dist/core/config/config-io.js +1 -2
- package/dist/core/config/config-schema.js +9 -34
- package/dist/core/config/config-walker.js +1 -1
- package/dist/core/config/config.js +184 -111
- package/dist/core/config/engine-semantics.js +0 -2
- package/dist/core/config/legacy-source-shape-shim.js +38 -9
- package/dist/core/config/schema/embedding.js +20 -5
- package/dist/core/config/schema/engines.js +5 -0
- package/dist/core/config/schema/execution.js +1 -1
- package/dist/core/config/schema/experimental.js +1 -1
- package/dist/core/config/schema/improve-processes.js +54 -125
- package/dist/core/config/schema/improve.js +4 -42
- package/dist/core/config/schema/index-config.js +9 -48
- package/dist/core/config/schema/scheduler.js +12 -12
- package/dist/core/config/schema/search.js +6 -22
- package/dist/core/env-secret-ref.js +0 -1
- package/dist/core/errors.js +8 -9
- package/dist/core/file-change.js +13 -5
- package/dist/core/file-lock.js +76 -173
- package/dist/core/improve-result.js +35 -7
- package/dist/core/improve-types.js +0 -1
- package/dist/core/logs-db.js +2 -2
- package/dist/core/loopback.js +7 -12
- package/dist/core/non-task-input.js +20 -0
- package/dist/core/parse.js +13 -16
- package/dist/core/paths.js +0 -24
- package/dist/core/redaction.js +109 -2
- package/dist/core/run-lock.js +2 -5
- package/dist/core/spawn-env.js +1 -1
- package/dist/core/state/migrations.js +123 -61
- package/dist/core/state-db-scope.js +2 -4
- package/dist/core/state-db.js +126 -692
- package/dist/core/time.js +0 -20
- package/dist/core/type-presentation.js +1 -9
- package/dist/core/write-source.js +294 -1005
- package/dist/execution/input-contract.js +1 -1
- package/dist/execution/resolved-request.js +135 -689
- package/dist/execution/source.js +63 -257
- package/dist/execution/target-ref.js +1 -1
- package/dist/indexer/bundle-identity-guard.js +2 -2
- package/dist/indexer/db/llm-cache.js +2 -2
- package/dist/indexer/ensure-index.js +77 -73
- package/dist/indexer/index-rebuild-lock.js +3 -11
- package/dist/indexer/index-writer-lock.js +8 -17
- package/dist/indexer/index-written-assets.js +141 -154
- package/dist/indexer/indexer.js +400 -1124
- package/dist/indexer/links/declared-links.js +90 -0
- package/dist/indexer/materialize-embeddings.js +60 -397
- package/dist/indexer/passes/memory-inference.js +96 -90
- package/dist/indexer/passes/metadata.js +132 -219
- package/dist/indexer/read-preflight.js +0 -7
- package/dist/indexer/scan/doc-to-entry.js +2 -3
- package/dist/indexer/scan/drain-dir.js +1 -1
- package/dist/indexer/search/db-search.js +190 -590
- package/dist/indexer/search/fts-query.js +30 -41
- package/dist/indexer/search/ranking.js +28 -154
- package/dist/indexer/search/search-attribution.js +12 -32
- package/dist/indexer/search/search-fields.js +11 -15
- package/dist/indexer/search/search-hit-enrichers.js +54 -85
- package/dist/indexer/search/search-source.js +1 -4
- package/dist/indexer/usage/usage-events.js +36 -7
- package/dist/indexer/walk/walker.js +3 -4
- package/dist/integrations/agent/engine-fallback.js +23 -40
- package/dist/integrations/agent/engine-resolution.js +93 -183
- package/dist/integrations/agent/execution.js +507 -0
- package/dist/integrations/agent/model-map.js +28 -156
- package/dist/integrations/agent/request-lowering.js +66 -141
- package/dist/integrations/agent/runner-dispatch.js +143 -321
- package/dist/integrations/agent/runner.js +54 -14
- package/dist/integrations/lockfile.js +53 -101
- package/dist/llm/client.js +18 -6
- package/dist/llm/embedders/deterministic.js +2 -3
- package/dist/llm/embedders/profile.js +71 -0
- package/dist/llm/embedders/remote.js +11 -17
- package/dist/llm/feature-gate.js +0 -8
- package/dist/llm/index-passes.js +3 -5
- package/dist/llm/memory-infer.js +1 -2
- package/dist/llm/structured-call.js +5 -24
- package/dist/output/generic-render.js +23 -11
- package/dist/output/html-render.js +13 -10
- package/dist/output/render-registry.js +3 -32
- package/dist/output/shapes/helpers.js +25 -38
- package/dist/output/shapes/passthrough.js +1 -9
- package/dist/{indexer/graph/graph-types.js → output/text/bundle-rename.js} +4 -1
- package/dist/output/text/command-format.js +69 -31
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/migrate.js +5 -14
- package/dist/output/text/proposal-format.js +48 -3
- package/dist/output/text/show-format.js +13 -17
- package/dist/output/text/workflow-format.js +0 -32
- package/dist/output/text.js +2 -0
- package/dist/registry/factory.js +4 -19
- package/dist/registry/network.js +66 -220
- package/dist/registry/providers/index.js +0 -2
- package/dist/registry/providers/skills-sh.js +3 -14
- package/dist/registry/providers/static-index.js +24 -26
- package/dist/registry/resolve.js +55 -131
- package/dist/scripts/akm-migrate-node.js +42948 -92369
- package/dist/scripts/akm-migrate.js +42935 -92354
- package/dist/setup/registry-stash-loader.js +4 -13
- package/dist/setup/semantic-assets.js +3 -44
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/connection.js +5 -6
- package/dist/setup/steps/platforms.js +2 -2
- package/dist/setup/steps/tasks.js +25 -15
- package/dist/sources/provider-factory.js +17 -18
- package/dist/sources/providers/filesystem.js +2 -3
- package/dist/sources/providers/git-install.js +7 -1
- package/dist/sources/providers/git-provider.js +0 -3
- package/dist/sources/providers/git-stash.js +83 -21
- package/dist/sources/providers/npm.js +2 -4
- package/dist/sources/providers/provider-utils.js +5 -10
- package/dist/sources/providers/website.js +0 -2
- package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
- package/dist/sources/website-url.js +2 -2
- package/dist/storage/database.js +9 -35
- package/dist/storage/repositories/improve-ledger-repository.js +209 -0
- package/dist/storage/repositories/index-connection.js +39 -72
- package/dist/storage/repositories/index-entries-repository.js +131 -129
- package/dist/storage/repositories/index-entry-mapper.js +1 -2
- package/dist/storage/repositories/index-entry-schema.js +101 -268
- package/dist/storage/repositories/index-fts-repository.js +86 -256
- package/dist/storage/repositories/index-links-repository.js +143 -0
- package/dist/storage/repositories/index-llm-cache-repository.js +7 -9
- package/dist/storage/repositories/index-meta-repository.js +6 -4
- package/dist/storage/repositories/index-schema.js +257 -325
- package/dist/storage/repositories/index-utility-repository.js +8 -29
- package/dist/storage/repositories/index-vec-repository.js +133 -414
- package/dist/storage/repositories/outcome-repository.js +2 -1
- package/dist/storage/repositories/proposals-repository.js +104 -1
- package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
- package/dist/storage/repositories/salience-repository.js +1 -19
- package/dist/storage/repositories/task-history-repository.js +26 -4
- package/dist/storage/repositories/workflow-runs-repository.js +53 -244
- package/dist/storage/sqlite-migrations.js +136 -0
- package/dist/storage/sqlite-pragmas.js +11 -9
- package/dist/storage/sqlite-transaction.js +170 -0
- package/dist/storage/state-db-integrity.js +130 -0
- package/dist/tasks/activation-config.js +134 -62
- package/dist/tasks/backends/cron.js +191 -302
- package/dist/tasks/backends/exec-utils.js +2 -5
- package/dist/tasks/backends/launchd.js +141 -748
- package/dist/tasks/backends/schtasks.js +119 -623
- package/dist/tasks/prepare/prepare-support.js +5 -15
- package/dist/tasks/prepare/prepare.js +0 -2
- package/dist/tasks/resolve-akm-bin.js +20 -79
- package/dist/tasks/run/attempt-lifecycle.js +0 -1
- package/dist/tasks/run/load-task.js +1 -1
- package/dist/tasks/scheduler-binding.js +20 -238
- package/dist/tasks/scheduler-invocation.js +136 -244
- package/dist/tasks/scheduler-lock.js +53 -0
- package/dist/tasks/scheduler-sync.js +368 -679
- package/dist/tasks/source/parse-task-source.js +55 -9
- package/dist/tasks/source/task-source-v3-frozen.js +3 -4
- package/dist/tasks/source/task-to-v4.js +464 -88
- package/dist/workflows/authoring/authoring.js +3 -12
- package/dist/workflows/compile.js +211 -0
- package/dist/workflows/concurrency-policy.js +13 -74
- package/dist/workflows/exec/child-invocation.js +3 -17
- package/dist/workflows/exec/child-workflow.js +32 -141
- package/dist/workflows/exec/dispatch-redaction.js +13 -53
- package/dist/workflows/exec/environment.js +98 -0
- package/dist/workflows/exec/exec-unit.js +33 -140
- package/dist/workflows/exec/frozen-judge.js +7 -59
- package/dist/workflows/exec/native-executor.js +82 -341
- package/dist/workflows/exec/param-secrets.js +29 -47
- package/dist/workflows/exec/run-workflow.js +154 -387
- package/dist/workflows/exec/scheduler.js +9 -36
- package/dist/workflows/exec/step-work.js +127 -430
- package/dist/workflows/exec/unit-dispatch.js +11 -63
- package/dist/workflows/exec/unit-writer.js +8 -52
- package/dist/workflows/exec/worktree.js +39 -273
- package/dist/workflows/freeze/child-output-references.js +4 -15
- package/dist/workflows/freeze/environment.js +99 -92
- package/dist/workflows/freeze/freeze.js +172 -0
- package/dist/workflows/freeze/step-values.js +19 -21
- package/dist/workflows/freeze/targets/child-workflow.js +23 -92
- package/dist/workflows/freeze/targets/command.js +10 -33
- package/dist/workflows/freeze/targets/script.js +5 -12
- package/dist/workflows/freeze/targets/shell.js +3 -6
- package/dist/workflows/freeze/targets/task.js +25 -80
- package/dist/workflows/freeze/task-bindings.js +20 -67
- package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
- package/dist/workflows/ir/params.js +6 -51
- package/dist/workflows/ir/plan-hash.js +2 -34
- package/dist/workflows/parser.js +140 -43
- package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
- package/dist/workflows/renderer.js +36 -69
- package/dist/workflows/resource-limits.js +12 -120
- package/dist/workflows/runtime/agent-identity.js +8 -40
- package/dist/workflows/runtime/run-outputs.js +3 -6
- package/dist/workflows/runtime/run-plan.js +316 -0
- package/dist/workflows/runtime/runs.js +48 -200
- package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
- package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/integration/bundling-akm.md +49 -42
- package/docs/migration/README.md +1 -0
- package/docs/migration/release-notes/0.9.17.md +43 -0
- package/docs/migration/v0.9.1-to-v0.9.2.md +23 -7
- package/docs/reference/cli.md +232 -135
- package/docs/reference/configuration.md +71 -57
- package/docs/reference/data-and-telemetry.md +20 -21
- package/docs/reference/tasks.md +105 -39
- package/docs/reference/workflow-schema.md +14 -18
- package/docs/reference/workflows.md +6 -9
- package/package.json +1 -1
- package/schemas/akm-config.json +115 -738
- package/schemas/akm-workflow.json +1 -0
- package/dist/assets/improve-strategies/graph-refresh.json +0 -15
- package/dist/assets/prompts/contradiction-judge.md +0 -33
- package/dist/assets/prompts/graph-extract-system.md +0 -1
- package/dist/assets/prompts/graph-extract-user-prompt.md +0 -35
- package/dist/assets/prompts/metadata-enhance-system.md +0 -1
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +0 -4
- package/dist/commands/health/advisories.js +0 -150
- package/dist/commands/health/metrics.js +0 -329
- package/dist/commands/health/surfaces.js +0 -102
- package/dist/commands/improve/anti-collapse.js +0 -83
- package/dist/commands/improve/collapse-detector.js +0 -432
- package/dist/commands/improve/consolidate/eligibility.js +0 -48
- package/dist/commands/improve/consolidate/merge.js +0 -149
- package/dist/commands/improve/distill/promote-memory.js +0 -291
- package/dist/commands/improve/distill/quality-gate.js +0 -337
- package/dist/commands/improve/eval-cases.js +0 -52
- package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
- package/dist/commands/improve/proposal-envelope.js +0 -31
- package/dist/commands/improve/run-context.js +0 -123
- package/dist/commands/improve/shared.js +0 -31
- package/dist/commands/improve/source-identity.js +0 -28
- package/dist/commands/improve/triage.js +0 -96
- package/dist/commands/proposal/drain-policies.js +0 -151
- package/dist/commands/sources/update-transaction.js +0 -220
- package/dist/core/action-contributors.js +0 -28
- package/dist/core/config/config-version-shim.js +0 -101
- package/dist/core/fs-txn.js +0 -405
- package/dist/core/lexical-score.js +0 -25
- package/dist/core/maintenance-barrier.js +0 -167
- package/dist/execution/executable-identity.js +0 -105
- package/dist/execution/guarded-source.js +0 -427
- package/dist/indexer/db/graph-db.js +0 -444
- package/dist/indexer/graph/graph-boost.js +0 -427
- package/dist/indexer/graph/graph-dedup.js +0 -95
- package/dist/indexer/graph/graph-extraction.js +0 -1108
- package/dist/indexer/search/name-match.js +0 -35
- package/dist/indexer/search/ranking-contributors.js +0 -515
- package/dist/indexer/search/ranking-types.js +0 -4
- package/dist/indexer/walk/project-context.js +0 -192
- package/dist/integrations/agent/execution-cascade.js +0 -566
- package/dist/integrations/agent/execution-definitions.js +0 -202
- package/dist/integrations/agent/execution-lowering.js +0 -841
- package/dist/integrations/agent/execution-preparation.js +0 -98
- package/dist/integrations/agent/inline-execution.js +0 -74
- package/dist/llm/graph-extract.js +0 -728
- package/dist/llm/metadata-enhance.js +0 -96
- package/dist/registry/create-provider-registry.js +0 -29
- package/dist/registry/pinned-request-helper.js +0 -247
- package/dist/registry/pinned-transport.js +0 -717
- package/dist/sources/providers/index.js +0 -14
- package/dist/storage/engines/sqlite-migrations.js +0 -271
- package/dist/storage/repositories/canaries-repository.js +0 -107
- package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
- package/dist/storage/repositories/registry-cache.js +0 -113
- package/dist/tasks/scheduler-sync-preview.js +0 -52
- package/dist/tasks/source/task-to-v3.js +0 -507
- package/dist/workflows/freeze/resolve-steps.js +0 -86
- package/dist/workflows/freeze/source-freeze.js +0 -64
- package/dist/workflows/ir/compile.js +0 -321
- package/dist/workflows/ir/environment-v4.js +0 -330
- package/dist/workflows/ir/freeze-v4.js +0 -153
- package/dist/workflows/ir/schema-v4.js +0 -745
- package/dist/workflows/ir/schema.js +0 -354
- package/dist/workflows/program/schema.js +0 -77
- package/dist/workflows/runtime/checkin.js +0 -57
- package/dist/workflows/runtime/plan-classifier.js +0 -196
- package/dist/workflows/runtime/unit-checkin.js +0 -45
- package/dist/workflows/runtime/unit-phases.js +0 -20
- package/dist/workflows/schema.js +0 -4
- package/dist/workflows/source-ir/compile.js +0 -200
- package/dist/workflows/source-ir/program.js +0 -50
- package/dist/workflows/source-ir/result.js +0 -26
- package/dist/workflows/source-ir/schema.js +0 -786
- package/dist/workflows/source-ir/triggers.js +0 -79
- package/dist/workflows/source-ir/uses.js +0 -40
- package/dist/workflows/validator.js +0 -60
|
@@ -2,37 +2,18 @@
|
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
/**
|
|
5
|
-
* Session
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* — on any harness — can discover prior work via `akm search` / `akm curate`.
|
|
10
|
-
*
|
|
11
|
-
* Design constraints (see #561):
|
|
12
|
-
* - ADDITIVE + FAIL-OPEN + CONFIG-GATED. Disabled (or no LLM provider) →
|
|
13
|
-
* extract behaves EXACTLY as before. Nothing is written.
|
|
14
|
-
* - The LLM summary call routes through the injectable {@link SessionSummaryGenerator}
|
|
15
|
-
* seam so tests never touch a real provider, and so production wraps the
|
|
16
|
-
* call in the existing `tryLlmFeature` fail-open pattern.
|
|
17
|
-
* - The `log_path` + `access` frontmatter fields are the durable correlation
|
|
18
|
-
* key — they survive index rebuilds (the body is re-derived from disk).
|
|
19
|
-
*
|
|
20
|
-
* The asset is written to `sessions/<harness>/<session-id>.md`; the registered
|
|
21
|
-
* `session` asset type (see `asset-spec.ts`) makes the normal index pass pick it
|
|
22
|
-
* up for FTS + vector search with no special-casing.
|
|
5
|
+
* Session assets (#561): besides its memory proposals, extract writes each
|
|
6
|
+
* session to `sessions/<harness>/<session-id>.md` as a searchable `session`
|
|
7
|
+
* asset. Additive and fail-open — no summary means nothing is written — and
|
|
8
|
+
* `log_path` + `access` in the frontmatter tell any agent how to read the raw log.
|
|
23
9
|
*/
|
|
24
10
|
import fs from "node:fs";
|
|
25
11
|
import path from "node:path";
|
|
26
12
|
import { stashDirFor } from "../../core/asset/asset-placement.js";
|
|
27
13
|
import { assembleAsset } from "../../core/asset/asset-serialize.js";
|
|
28
14
|
import { conceptIdFromTypeName } from "../../core/asset/resolve-ref.js";
|
|
15
|
+
import { parseEmbeddedJsonResponse } from "../../core/parse.js";
|
|
29
16
|
import { recordWrittenPath } from "../../core/write-provenance.js";
|
|
30
|
-
/**
|
|
31
|
-
* JSON Schema for the session-summary LLM call. Strict so providers that
|
|
32
|
-
* support schema enforcement constrain the output upstream; the parser only
|
|
33
|
-
* has to handle the happy path. `additionalProperties: false` drops any
|
|
34
|
-
* hallucinated keys before parsing.
|
|
35
|
-
*/
|
|
36
17
|
export const SESSION_SUMMARY_JSON_SCHEMA = {
|
|
37
18
|
type: "object",
|
|
38
19
|
required: ["summary", "key_topics"],
|
|
@@ -43,11 +24,7 @@ export const SESSION_SUMMARY_JSON_SCHEMA = {
|
|
|
43
24
|
tags: { type: "array", items: { type: "string" } },
|
|
44
25
|
},
|
|
45
26
|
};
|
|
46
|
-
/**
|
|
47
|
-
* Render a compact transcript snippet from session events for the summary
|
|
48
|
-
* prompt. Mirrors the extract transcript format but caps total length so the
|
|
49
|
-
* summary prompt stays bounded regardless of session size.
|
|
50
|
-
*/
|
|
27
|
+
/** The transcript for the summary prompt, capped at `maxChars`. */
|
|
51
28
|
function renderTranscriptForSummary(events, maxChars = 12_000) {
|
|
52
29
|
if (events.length === 0)
|
|
53
30
|
return "(empty — no events)";
|
|
@@ -66,11 +43,6 @@ function renderTranscriptForSummary(events, maxChars = 12_000) {
|
|
|
66
43
|
}
|
|
67
44
|
return lines.join("\n\n") || "(empty — no textual events)";
|
|
68
45
|
}
|
|
69
|
-
/**
|
|
70
|
-
* Build the user prompt for the session-summary LLM call. Pure — no IO. The
|
|
71
|
-
* model is asked for a dense 2–4 sentence summary plus key topics, optimised
|
|
72
|
-
* for semantic search recall.
|
|
73
|
-
*/
|
|
74
46
|
export function buildSessionSummaryPrompt(data) {
|
|
75
47
|
const ref = data.ref;
|
|
76
48
|
const startedAt = isoOrUndefined(ref.startedAt) ?? "unknown";
|
|
@@ -92,33 +64,13 @@ export function buildSessionSummaryPrompt(data) {
|
|
|
92
64
|
'Respond as JSON: {"summary": string, "key_topics": string[], "tags"?: string[]}.',
|
|
93
65
|
].join("\n");
|
|
94
66
|
}
|
|
95
|
-
/**
|
|
96
|
-
* Parse the session-summary LLM response into a {@link SessionSummaryResult}.
|
|
97
|
-
* Defensive: tolerates prose preamble/postamble around the JSON, and returns
|
|
98
|
-
* `undefined` when nothing usable parses (fail-open: no asset is written).
|
|
99
|
-
*/
|
|
67
|
+
/** The summary JSON, tolerating prose around it; `undefined` when nothing usable parses. */
|
|
100
68
|
export function parseSessionSummary(raw) {
|
|
101
69
|
if (!raw || raw.trim().length === 0)
|
|
102
70
|
return undefined;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
parsed = JSON.parse(raw);
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
const start = raw.indexOf("{");
|
|
109
|
-
const end = raw.lastIndexOf("}");
|
|
110
|
-
if (start === -1 || end <= start)
|
|
111
|
-
return undefined;
|
|
112
|
-
try {
|
|
113
|
-
parsed = JSON.parse(raw.slice(start, end + 1));
|
|
114
|
-
}
|
|
115
|
-
catch {
|
|
116
|
-
return undefined;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
if (!parsed || typeof parsed !== "object")
|
|
71
|
+
const obj = parseEmbeddedJsonResponse(raw);
|
|
72
|
+
if (!obj || typeof obj !== "object" || Array.isArray(obj))
|
|
120
73
|
return undefined;
|
|
121
|
-
const obj = parsed;
|
|
122
74
|
const summary = typeof obj.summary === "string" ? obj.summary.trim() : "";
|
|
123
75
|
if (summary.length === 0)
|
|
124
76
|
return undefined;
|
|
@@ -130,62 +82,40 @@ export function parseSessionSummary(raw) {
|
|
|
130
82
|
: undefined;
|
|
131
83
|
return { summary, keyTopics, ...(tags && tags.length > 0 ? { tags } : {}) };
|
|
132
84
|
}
|
|
133
|
-
/**
|
|
134
|
-
* Decide whether a session is long enough to index. `minDurationMinutes <= 0`
|
|
135
|
-
* disables the gate. When either timestamp is missing we DON'T gate it out —
|
|
136
|
-
* fail-open toward indexing, since a missing timestamp is not evidence of a
|
|
137
|
-
* trivial session.
|
|
138
|
-
*/
|
|
85
|
+
/** Long enough to index (`<= 0` disables; a missing timestamp is no evidence of a trivial session). */
|
|
139
86
|
export function sessionMeetsDurationGate(data, minDurationMinutes) {
|
|
140
87
|
if (!Number.isFinite(minDurationMinutes) || minDurationMinutes <= 0)
|
|
141
88
|
return true;
|
|
142
89
|
const { startedAt, endedAt } = data.ref;
|
|
143
90
|
if (typeof startedAt !== "number" || typeof endedAt !== "number")
|
|
144
91
|
return true;
|
|
145
|
-
|
|
146
|
-
return durationMinutes >= minDurationMinutes;
|
|
92
|
+
return (endedAt - startedAt) / 60_000 >= minDurationMinutes;
|
|
147
93
|
}
|
|
148
|
-
/**
|
|
149
|
-
* Build per-harness `access` instructions for reading the raw session log.
|
|
150
|
-
*
|
|
151
|
-
* Documented convention (#561, checklist item "Document `access` field
|
|
152
|
-
* convention per harness"): the string tells a downstream agent exactly how to
|
|
153
|
-
* read and parse the source at `log_path`. New harnesses fall back to a generic
|
|
154
|
-
* `cat <log_path>` hint for file-backed logs.
|
|
155
|
-
*/
|
|
94
|
+
/** How an agent reads and parses the raw log at `log_path`, per harness (`cat` otherwise). */
|
|
156
95
|
export function buildSessionAccessInstructions(harness, logPath, sessionId) {
|
|
157
|
-
|
|
158
|
-
if (canonical === "claude") {
|
|
96
|
+
if (harness === "claude") {
|
|
159
97
|
return [
|
|
160
98
|
`Read with: cat ${logPath}`,
|
|
161
99
|
`Parse messages: jq -r 'select(.type=="message") | .message.content[]? | select(.type=="text") | .text' ${logPath}`,
|
|
162
100
|
].join("\n");
|
|
163
101
|
}
|
|
164
|
-
if (
|
|
102
|
+
if (harness === "opencode") {
|
|
165
103
|
return [
|
|
166
104
|
`Open the SQLite database at ${JSON.stringify(logPath)} in read-only mode.`,
|
|
167
105
|
"Query: SELECT m.data, p.data FROM message AS m JOIN part AS p ON p.message_id = m.id WHERE m.session_id = ? AND p.session_id = ? ORDER BY m.time_created, p.time_created;",
|
|
168
106
|
`Bind both parameters to ${JSON.stringify(sessionId)}.`,
|
|
169
107
|
].join("\n");
|
|
170
108
|
}
|
|
171
|
-
// Generic fallback — file-backed logs are always readable with cat.
|
|
172
109
|
return `Read with: cat ${logPath}`;
|
|
173
110
|
}
|
|
174
|
-
/** ISO-8601 (UTC) from a ms-epoch, or undefined when absent/non-finite. */
|
|
175
111
|
function isoOrUndefined(ms) {
|
|
176
112
|
return typeof ms === "number" && Number.isFinite(ms) ? new Date(ms).toISOString() : undefined;
|
|
177
113
|
}
|
|
178
114
|
/** Default session-name slug: `<harness>-session-<yyyy-mm-dd>-<shortId>`. */
|
|
179
115
|
export function buildSessionAssetName(harness, sessionId, startedAtMs) {
|
|
180
|
-
|
|
181
|
-
const datePart = isoOrUndefined(startedAtMs)?.slice(0, 10) ?? "unknown-date";
|
|
182
|
-
const shortId = sessionId.slice(0, 8);
|
|
183
|
-
return `${canonical}-session-${datePart}-${shortId}`;
|
|
116
|
+
return `${harness}-session-${isoOrUndefined(startedAtMs)?.slice(0, 10) ?? "unknown-date"}-${sessionId.slice(0, 8)}`;
|
|
184
117
|
}
|
|
185
|
-
/**
|
|
186
|
-
* Assemble the full session asset (frontmatter + `## Summary` / `## Key topics`).
|
|
187
|
-
* Pure — no IO. Returns the serialized markdown string.
|
|
188
|
-
*/
|
|
118
|
+
/** The session asset: frontmatter plus `## Summary` and `## Key topics`. */
|
|
189
119
|
export function buildSessionAssetContent(data, summary) {
|
|
190
120
|
const ref = data.ref;
|
|
191
121
|
const harness = ref.harness;
|
|
@@ -213,8 +143,7 @@ export function buildSessionAssetContent(data, summary) {
|
|
|
213
143
|
.map((t) => `- ${t.trim()}`)
|
|
214
144
|
.join("\n");
|
|
215
145
|
const body = `## Summary\n\n${summary.summary.trim()}\n\n## Key topics\n\n${topics || "- (none extracted)"}\n`;
|
|
216
|
-
//
|
|
217
|
-
// it without re-reading the body — matches how other content types behave.
|
|
146
|
+
// The summary doubles as the description, as for other types.
|
|
218
147
|
const content = assembleAsset({ ...frontmatter, description: summary.summary.trim() }, body);
|
|
219
148
|
return { name, frontmatter, content };
|
|
220
149
|
}
|
|
@@ -223,13 +152,7 @@ export function resolveSessionAssetPath(stashDir, harness, sessionId) {
|
|
|
223
152
|
const dir = stashDirFor("session") ?? "sessions";
|
|
224
153
|
return path.join(stashDir, dir, harness, `${sessionId}.md`);
|
|
225
154
|
}
|
|
226
|
-
/**
|
|
227
|
-
* Generate (via the injected summarizer) and write a session asset to the stash.
|
|
228
|
-
*
|
|
229
|
-
* FAIL-OPEN: when the summarizer returns `undefined` (disabled / no LLM /
|
|
230
|
-
* error), NOTHING is written and `{ written: false }` is returned. Any write
|
|
231
|
-
* error is swallowed by the caller — session indexing must NEVER break extract.
|
|
232
|
-
*/
|
|
155
|
+
/** Summarize and write a session asset; nothing without a summary. The caller swallows write errors. */
|
|
233
156
|
export async function writeSessionAsset(data, stashDir, generate) {
|
|
234
157
|
const summary = await generate(data);
|
|
235
158
|
if (!summary?.summary || summary.summary.trim().length === 0) {
|
|
@@ -241,15 +164,11 @@ export async function writeSessionAsset(data, stashDir, generate) {
|
|
|
241
164
|
const filePath = resolveSessionAssetPath(stashDir, harness, sessionId);
|
|
242
165
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
243
166
|
fs.writeFileSync(filePath, content, "utf8");
|
|
244
|
-
//
|
|
245
|
-
// journal it so the run's auto-sync stages it as one of its own writes.
|
|
167
|
+
// Written outside the proposal queue: journal it so auto-sync commits it (#652).
|
|
246
168
|
recordWrittenPath(filePath);
|
|
247
169
|
return {
|
|
248
170
|
written: true,
|
|
249
171
|
filePath,
|
|
250
|
-
// Canonical 0.9.0 conceptId (`sessions/<harness>/<id>`, D-R3) — the same
|
|
251
|
-
// spelling the xrefs / usage-event readers now expect. Historical
|
|
252
|
-
// `session:<harness>/<id>` rows persist un-migrated and are tolerated.
|
|
253
172
|
ref: conceptIdFromTypeName("session", `${harness}/${sessionId}`),
|
|
254
173
|
logPath: data.ref.filePath,
|
|
255
174
|
};
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { getImproveProcessConfig } from "../../core/config/config.js";
|
|
5
|
+
import { ConfigError } from "../../core/errors.js";
|
|
6
|
+
import { parseEmbeddedJsonResponse } from "../../core/parse.js";
|
|
7
|
+
import { warn } from "../../core/warn.js";
|
|
8
|
+
import { LlmCallError } from "../../llm/client.js";
|
|
9
|
+
import { callStructured } from "../../llm/structured-call.js";
|
|
10
|
+
import { withLlmStage } from "../../llm/usage-telemetry.js";
|
|
11
|
+
import { isProceduralRejection } from "../proposal/proposal-types.js";
|
|
12
|
+
import { createProposal, listProposalsReadOnly, proposalContentHash, recordGateDecision, } from "../proposal/repository.js";
|
|
13
|
+
import { resolveImproveLlmExecution } from "./execution.js";
|
|
14
|
+
/** Normalize an unknown thrown value to a message. */
|
|
15
|
+
export function errMessage(e) {
|
|
16
|
+
return e instanceof Error ? e.message : String(e);
|
|
17
|
+
}
|
|
18
|
+
/** The lowering notices a stage's dispatches emitted, each once. */
|
|
19
|
+
export function noticeSet(forward) {
|
|
20
|
+
const byKey = new Map();
|
|
21
|
+
const add = (notices) => {
|
|
22
|
+
for (const notice of notices)
|
|
23
|
+
byKey.set(JSON.stringify(notice), notice);
|
|
24
|
+
forward?.(notices);
|
|
25
|
+
};
|
|
26
|
+
const list = () => Object.freeze([...byKey.values()]);
|
|
27
|
+
return { add, list, fields: () => (byKey.size > 0 ? { notices: list() } : {}) };
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A stage's LLM runner: the one the improve plan froze for it (an own
|
|
31
|
+
* `llmRunner` key, `null` meaning "none"), else the process engine cascade.
|
|
32
|
+
*/
|
|
33
|
+
export function stageRunner(frozen, config, profile, processName, onNotices) {
|
|
34
|
+
if (Object.hasOwn(frozen, "llmRunner"))
|
|
35
|
+
return frozen.llmRunner ?? undefined;
|
|
36
|
+
const resolved = resolveImproveLlmExecution({
|
|
37
|
+
config,
|
|
38
|
+
profile,
|
|
39
|
+
process: getImproveProcessConfig(processName, profile),
|
|
40
|
+
processName,
|
|
41
|
+
});
|
|
42
|
+
if (resolved)
|
|
43
|
+
onNotices?.(resolved.notices);
|
|
44
|
+
return resolved?.runner;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* One model call. Provider trouble (transport error, timeout, a disabled
|
|
48
|
+
* feature) comes back as `{ ok: false }`; only a configuration failure throws.
|
|
49
|
+
*/
|
|
50
|
+
export async function callStage(call) {
|
|
51
|
+
const messages = [
|
|
52
|
+
...(call.system ? [{ role: "system", content: call.system }] : []),
|
|
53
|
+
...(call.history ?? []),
|
|
54
|
+
{ role: "user", content: call.prompt },
|
|
55
|
+
];
|
|
56
|
+
let failure;
|
|
57
|
+
try {
|
|
58
|
+
const raw = await callStructured({
|
|
59
|
+
feature: call.feature,
|
|
60
|
+
...(call.gate
|
|
61
|
+
? { akmConfig: call.gate.config, ...(call.gate.enabled !== undefined ? { enabled: call.gate.enabled } : {}) }
|
|
62
|
+
: {}),
|
|
63
|
+
runner: call.runner,
|
|
64
|
+
messages,
|
|
65
|
+
...(call.request ? { request: call.request } : {}),
|
|
66
|
+
...(call.onNotices ? { onNotices: call.onNotices } : {}),
|
|
67
|
+
parse: (r) => r ?? "",
|
|
68
|
+
onError: (_cls, err) => {
|
|
69
|
+
failure = { ok: false, reason: "error", error: errMessage(err) };
|
|
70
|
+
return undefined;
|
|
71
|
+
},
|
|
72
|
+
fallback: undefined,
|
|
73
|
+
onFallback: (event) => {
|
|
74
|
+
failure ??= { ok: false, reason: event.reason, ...(event.error ? { error: event.error.message } : {}) };
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
return raw === undefined ? (failure ?? { ok: false, reason: "error" }) : { ok: true, raw };
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
if (err instanceof ConfigError)
|
|
81
|
+
throw err;
|
|
82
|
+
const timedOut = err instanceof LlmCallError && err.code === "timeout";
|
|
83
|
+
return { ok: false, reason: timedOut ? "timeout" : "error", error: errMessage(err) };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** Attribute a stage's LLM calls to its process and planned engine (the usage report). */
|
|
87
|
+
const STAGE_LABELS = {
|
|
88
|
+
reflect: "reflect",
|
|
89
|
+
distill: "distill",
|
|
90
|
+
consolidate: "consolidate",
|
|
91
|
+
extract: "session-extraction",
|
|
92
|
+
memoryInference: "memory-inference",
|
|
93
|
+
validation: "validation",
|
|
94
|
+
};
|
|
95
|
+
export function attributeStage(plan, process, fn) {
|
|
96
|
+
return withLlmStage(STAGE_LABELS[process], fn, { engine: plan?.processes[process].runner?.engine, process });
|
|
97
|
+
}
|
|
98
|
+
/** How many prior rejected proposals are shown to the model as "don't repeat this". */
|
|
99
|
+
export const MAX_REJECTED_PROPOSALS = 3;
|
|
100
|
+
/**
|
|
101
|
+
* Reflexion context: the newest reviewer rejections for `ref`. Procedural
|
|
102
|
+
* refusals (expiry, stale target, missing asset) are not judgements on the
|
|
103
|
+
* content and are left out. Reads never create state.db.
|
|
104
|
+
*/
|
|
105
|
+
export function rejectedProposalContext(stash, ref, ctx) {
|
|
106
|
+
if (!ref)
|
|
107
|
+
return [];
|
|
108
|
+
return listProposalsReadOnly(stash, { ref, status: "rejected", includeArchive: true }, ctx)
|
|
109
|
+
.filter((p) => !isProceduralRejection(p))
|
|
110
|
+
.sort((a, b) => new Date(b.updatedAt ?? 0).getTime() - new Date(a.updatedAt ?? 0).getTime())
|
|
111
|
+
.slice(0, MAX_REJECTED_PROPOSALS)
|
|
112
|
+
.map((p) => ({
|
|
113
|
+
ref: p.ref,
|
|
114
|
+
reason: p.review?.reason ?? "no reason given",
|
|
115
|
+
// `payload.content` is populated on every row, including legacy ones.
|
|
116
|
+
contentPreview: p.payload.content.slice(0, 500),
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
// ── Mint ─────────────────────────────────────────────────────────────────────
|
|
120
|
+
/**
|
|
121
|
+
* Create a stage's proposal. `judged` stamps a `staged` gate decision with the
|
|
122
|
+
* judged content's hash (the triage drain accepts it while the content still
|
|
123
|
+
* matches); `review` leaves it `deferred` for a human (`review_needed` in the
|
|
124
|
+
* improve ledger).
|
|
125
|
+
*/
|
|
126
|
+
export function mintProposal(stash, proposalsCtx, input, verdict = {}) {
|
|
127
|
+
const proposal = createProposal(stash, input, proposalsCtx);
|
|
128
|
+
if (verdict.review) {
|
|
129
|
+
return recordGateDecision(stash, proposal.id, { outcome: "deferred", ...verdict.review }, proposalsCtx) ?? proposal;
|
|
130
|
+
}
|
|
131
|
+
return verdict.judged ? stageJudgedProposal(stash, proposal, proposalsCtx) : proposal;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Stamp a proposal the quality judge passed. Best-effort: a failed stamp only
|
|
135
|
+
* means the triage drain judges it again.
|
|
136
|
+
*/
|
|
137
|
+
export function stageJudgedProposal(stash, proposal, proposalsCtx) {
|
|
138
|
+
try {
|
|
139
|
+
return (recordGateDecision(stash, proposal.id, {
|
|
140
|
+
outcome: "staged",
|
|
141
|
+
reason: "quality-judge",
|
|
142
|
+
gate: "quality-gate",
|
|
143
|
+
contentHash: proposalContentHash(proposal),
|
|
144
|
+
}, proposalsCtx) ?? proposal);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
warn(`[akm] failed to record the quality-judge pass for ${proposal.id}: ${errMessage(error)}`);
|
|
148
|
+
return proposal;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/** Lesson judge prompt; similar existing lessons let it mark near-duplicates down. */
|
|
152
|
+
export function buildJudgePrompt(lessonContent, sourceContent, similarLessons) {
|
|
153
|
+
const lines = [
|
|
154
|
+
"You are evaluating a proposed lesson asset for an akm knowledge base.",
|
|
155
|
+
"",
|
|
156
|
+
"Score this lesson on each criterion from 1 (poor) to 5 (excellent):",
|
|
157
|
+
"1. NOVELTY: Does the lesson add information not already present in the source asset?",
|
|
158
|
+
"2. NON-REDUNDANCY: Is this lesson meaningfully different from what the source already says?",
|
|
159
|
+
"",
|
|
160
|
+
"Source asset content:",
|
|
161
|
+
"```",
|
|
162
|
+
sourceContent.slice(0, 2000),
|
|
163
|
+
"```",
|
|
164
|
+
];
|
|
165
|
+
if (similarLessons && similarLessons.length > 0) {
|
|
166
|
+
lines.push("", "Existing similar lessons (top-3 by similarity). Rate lower if the proposed lesson is substantially similar to any of these:");
|
|
167
|
+
for (const sl of similarLessons)
|
|
168
|
+
lines.push(`\nExisting lesson ref: ${sl.ref}`, "```", sl.content.slice(0, 500), "```");
|
|
169
|
+
}
|
|
170
|
+
lines.push("", "Proposed lesson content:", "```", lessonContent.slice(0, 1000), "```", "", 'Return ONLY valid JSON, no prose: {"scores": {"novelty": <1-5 integer>, "nonRedundancy": <1-5 integer>}, "reason": "<one sentence>"}');
|
|
171
|
+
return lines.join("\n");
|
|
172
|
+
}
|
|
173
|
+
function boundedDocument(content, maxChars = 6000) {
|
|
174
|
+
if (content.length <= maxChars)
|
|
175
|
+
return content;
|
|
176
|
+
const half = Math.floor((maxChars - 80) / 2);
|
|
177
|
+
return `${content.slice(0, half)}\n\n[... middle omitted for bounded judge context ...]\n\n${content.slice(-half)}`;
|
|
178
|
+
}
|
|
179
|
+
function buildChangedRegion(sourceContent, candidateContent) {
|
|
180
|
+
const source = sourceContent.split("\n");
|
|
181
|
+
const candidate = candidateContent.split("\n");
|
|
182
|
+
let prefix = 0;
|
|
183
|
+
while (prefix < source.length && prefix < candidate.length && source[prefix] === candidate[prefix])
|
|
184
|
+
prefix++;
|
|
185
|
+
let suffix = 0;
|
|
186
|
+
while (suffix < source.length - prefix &&
|
|
187
|
+
suffix < candidate.length - prefix &&
|
|
188
|
+
source[source.length - 1 - suffix] === candidate[candidate.length - 1 - suffix]) {
|
|
189
|
+
suffix++;
|
|
190
|
+
}
|
|
191
|
+
const removed = source.slice(prefix, source.length - suffix).join("\n");
|
|
192
|
+
const added = candidate.slice(prefix, candidate.length - suffix).join("\n");
|
|
193
|
+
return boundedDocument(`Removed or replaced:\n${removed || "(none)"}\n\nAdded or replacement:\n${added || "(none)"}`);
|
|
194
|
+
}
|
|
195
|
+
/** Judge prompt for an in-place revision (overlap with the source is expected). */
|
|
196
|
+
export function buildReflectJudgePrompt(candidateContent, sourceContent, feedback) {
|
|
197
|
+
return [
|
|
198
|
+
"You are evaluating a proposed revision to an existing akm asset.",
|
|
199
|
+
"",
|
|
200
|
+
"Score this revision on each criterion from 1 (poor) to 5 (excellent):",
|
|
201
|
+
"1. FEEDBACK ALIGNMENT: Does the revision address the supplied feedback or improve retrieval and clarity?",
|
|
202
|
+
"2. PRESERVATION: Does it retain the source's concrete facts, code, commands, examples, and structure without truncation?",
|
|
203
|
+
"3. QUALITY: Is the revision coherent, actionable, complete, and free of unsupported claims?",
|
|
204
|
+
"",
|
|
205
|
+
"Overlap with the source is expected and must not lower the score by itself; this is an in-place revision, not a new lesson.",
|
|
206
|
+
"",
|
|
207
|
+
"Feedback:",
|
|
208
|
+
"```",
|
|
209
|
+
(feedback.length > 0 ? feedback.join("\n") : "No explicit feedback supplied.").slice(0, 1000),
|
|
210
|
+
"```",
|
|
211
|
+
"",
|
|
212
|
+
"Source asset content:",
|
|
213
|
+
"```",
|
|
214
|
+
boundedDocument(sourceContent),
|
|
215
|
+
"```",
|
|
216
|
+
"",
|
|
217
|
+
"Proposed revision:",
|
|
218
|
+
"```",
|
|
219
|
+
boundedDocument(candidateContent),
|
|
220
|
+
"```",
|
|
221
|
+
"",
|
|
222
|
+
"Changed region:",
|
|
223
|
+
"```",
|
|
224
|
+
buildChangedRegion(sourceContent, candidateContent),
|
|
225
|
+
"```",
|
|
226
|
+
"",
|
|
227
|
+
'Return ONLY valid JSON, no prose: {"scores": {"feedbackAlignment": <1-5 integer>, "preservation": <1-5 integer>, "quality": <1-5 integer>}, "reason": "<one sentence>"}',
|
|
228
|
+
].join("\n");
|
|
229
|
+
}
|
|
230
|
+
const LESSON_JUDGE_CRITERIA = ["novelty", "nonRedundancy"];
|
|
231
|
+
const REFLECT_JUDGE_CRITERIA = ["feedbackAlignment", "preservation", "quality"];
|
|
232
|
+
/**
|
|
233
|
+
* Read a judge response: the per-criterion shape (averaged here) or the older
|
|
234
|
+
* `{"score"}` shape. Only the expected criteria are read; any missing or
|
|
235
|
+
* out-of-range (1..5) value is a parse failure, extra keys are ignored.
|
|
236
|
+
*/
|
|
237
|
+
function parseJudgeResponse(raw, keys) {
|
|
238
|
+
const parsed = parseEmbeddedJsonResponse(raw);
|
|
239
|
+
if (!parsed || typeof parsed.reason !== "string")
|
|
240
|
+
return undefined;
|
|
241
|
+
const reason = parsed.reason;
|
|
242
|
+
const inRange = (value) => typeof value === "number" && Number.isFinite(value) && value >= 1 && value <= 5;
|
|
243
|
+
if (parsed.scores !== undefined) {
|
|
244
|
+
if (typeof parsed.scores !== "object" || parsed.scores === null || Array.isArray(parsed.scores))
|
|
245
|
+
return undefined;
|
|
246
|
+
const scores = parsed.scores;
|
|
247
|
+
const criteria = {};
|
|
248
|
+
for (const key of keys) {
|
|
249
|
+
const value = scores[key];
|
|
250
|
+
if (!inRange(value))
|
|
251
|
+
return undefined;
|
|
252
|
+
criteria[key] = value;
|
|
253
|
+
}
|
|
254
|
+
return { score: Object.values(criteria).reduce((a, b) => a + b, 0) / keys.length, reason, criteria };
|
|
255
|
+
}
|
|
256
|
+
return inRange(parsed.score) ? { score: parsed.score, reason } : undefined;
|
|
257
|
+
}
|
|
258
|
+
function judgeResponseSchema(keys) {
|
|
259
|
+
return {
|
|
260
|
+
type: "object",
|
|
261
|
+
required: ["scores", "reason"],
|
|
262
|
+
additionalProperties: false,
|
|
263
|
+
properties: {
|
|
264
|
+
scores: {
|
|
265
|
+
type: "object",
|
|
266
|
+
required: [...keys],
|
|
267
|
+
additionalProperties: false,
|
|
268
|
+
properties: Object.fromEntries(keys.map((key) => [key, { type: "integer", minimum: 1, maximum: 5 }])),
|
|
269
|
+
},
|
|
270
|
+
reason: { type: "string" },
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* The quality judge. Fails closed: no runner, an unparseable verdict or a
|
|
276
|
+
* provider failure never passes content. Bands: >= 3.5 pass, 2.5-3.5 review,
|
|
277
|
+
* < 2.5 reject. Temperature is pinned to 0 so verdicts do not flip.
|
|
278
|
+
*/
|
|
279
|
+
async function runQualityJudge(feature, config, prompt, keys, chat, options) {
|
|
280
|
+
const resolved = !options.runnerSelectionFrozen && !options.llmRunner
|
|
281
|
+
? resolveImproveLlmExecution({ config, processName: `${feature}-judge` })
|
|
282
|
+
: null;
|
|
283
|
+
if (resolved)
|
|
284
|
+
options.onNotices?.(resolved.notices);
|
|
285
|
+
const runner = options.llmRunner ?? resolved?.runner;
|
|
286
|
+
if (!runner)
|
|
287
|
+
return { pass: false, score: -1, reason: "no LLM configured — cannot judge, failing closed" };
|
|
288
|
+
const outcome = await callStage({
|
|
289
|
+
feature,
|
|
290
|
+
runner,
|
|
291
|
+
system: "Return only valid JSON. No prose.",
|
|
292
|
+
prompt,
|
|
293
|
+
request: {
|
|
294
|
+
enableThinking: false,
|
|
295
|
+
temperature: 0,
|
|
296
|
+
responseSchema: judgeResponseSchema(keys),
|
|
297
|
+
...(Object.hasOwn(options, "timeoutMs") ? { timeoutMs: options.timeoutMs } : {}),
|
|
298
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
299
|
+
...(chat ? { chat } : {}),
|
|
300
|
+
},
|
|
301
|
+
...(options.onNotices ? { onNotices: options.onNotices } : {}),
|
|
302
|
+
});
|
|
303
|
+
if (!outcome.ok) {
|
|
304
|
+
return { pass: false, score: -1, reason: "judge timeout/error — routed to review", reviewNeeded: true };
|
|
305
|
+
}
|
|
306
|
+
const parsed = parseJudgeResponse(outcome.raw, keys);
|
|
307
|
+
if (!parsed)
|
|
308
|
+
return { pass: false, score: -1, reason: "judge parse failed — routed to review", reviewNeeded: true };
|
|
309
|
+
const { score, reason, criteria } = parsed;
|
|
310
|
+
const verdict = score >= 3.5 ? { pass: true } : score >= 2.5 ? { pass: false, reviewNeeded: true } : { pass: false };
|
|
311
|
+
return { ...verdict, score, reason, ...(criteria ? { criteria } : {}) };
|
|
312
|
+
}
|
|
313
|
+
/** Judge a proposed lesson (or knowledge promotion) against its source. */
|
|
314
|
+
export function runLessonQualityJudge(config, lessonContent, sourceContent, chat, options = {}) {
|
|
315
|
+
const prompt = buildJudgePrompt(lessonContent, sourceContent, options.similarLessons);
|
|
316
|
+
return runQualityJudge("lesson_quality_gate", config, prompt, LESSON_JUDGE_CRITERIA, chat, options);
|
|
317
|
+
}
|
|
318
|
+
/** Judge an in-place reflect revision without new-lesson novelty criteria. */
|
|
319
|
+
export function runReflectQualityJudge(config, candidateContent, sourceContent, feedback, chat, options = {}) {
|
|
320
|
+
const prompt = buildReflectJudgePrompt(candidateContent, sourceContent, feedback);
|
|
321
|
+
return runQualityJudge("proposal_quality_gate", config, prompt, REFLECT_JUDGE_CRITERIA, chat, options);
|
|
322
|
+
}
|
|
@@ -42,6 +42,7 @@ import { isArchivedRelPath } from "../../core/asset/memory-archive.js";
|
|
|
42
42
|
import { conceptIdFromTypeName, typeNameFromConceptId } from "../../core/asset/resolve-ref.js";
|
|
43
43
|
import { localDateStamp } from "../../core/common.js";
|
|
44
44
|
import { containsRedactedContent, REDACTED_CONTENT_MARKER } from "../../core/content-safety.js";
|
|
45
|
+
import { DERIVED_SUFFIX } from "../../core/recognition-util.js";
|
|
45
46
|
import { findFenceRegions } from "./markdown-insertion.js";
|
|
46
47
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
47
48
|
/** Fold physically wrapped prose the same way a YAML plain scalar does. */
|
|
@@ -203,12 +204,14 @@ export function refExistsInAnyStash(relPath, refType, refName, stashRoots) {
|
|
|
203
204
|
if (resolveRefPathInStash(relPath, refType, refName, root) !== null)
|
|
204
205
|
return true;
|
|
205
206
|
}
|
|
206
|
-
// #884:
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
//
|
|
207
|
+
// #884: an asset `analyzeMemoryCleanup` pruned, or (alpha.9) a consolidate
|
|
208
|
+
// pair-pass `retire` proposal or a promotion's source memory retired, was
|
|
209
|
+
// ARCHIVED, not deleted — its bytes and identity live on under
|
|
210
|
+
// `.akm/memory-cleanup/archive`. Inbound refs to it are satisfied, not
|
|
211
|
+
// dangling, so resolve the tombstone rather than reporting `missing-ref`.
|
|
212
|
+
// Checked only after every live location misses: a tombstone must never
|
|
213
|
+
// shadow a real file, and the scan then costs one directory read per root
|
|
214
|
+
// instead of one per ref.
|
|
212
215
|
//
|
|
213
216
|
// Existence ONLY. `resolveRefPathInStash` deliberately does NOT consult the
|
|
214
217
|
// archive: it hands back a path callers MUTATE (SPEC-5 `--supersedes`
|
|
@@ -217,17 +220,36 @@ export function refExistsInAnyStash(relPath, refType, refName, stashRoots) {
|
|
|
217
220
|
return memoryArchiveHasRef(refType, refName, stashRoots);
|
|
218
221
|
}
|
|
219
222
|
/**
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
+
* The stash-relative files that satisfy a ref, in preference order: its own
|
|
224
|
+
* placement spellings, then, for a memory, the `<name>.derived.md` child (#882),
|
|
225
|
+
* so an edge to a parent whose plain `.md` is gone still reaches the child it was
|
|
226
|
+
* distilled into. This is lint's reachability rule only; the child owns
|
|
227
|
+
* `memories/<name>.derived`, never `memories/<name>`.
|
|
228
|
+
*/
|
|
229
|
+
function refPathCandidates(refType, typeDir, refName) {
|
|
230
|
+
const candidates = assetPathCandidatesForName(refType, typeDir, refName);
|
|
231
|
+
if (refType !== "memory" || refName.endsWith(DERIVED_SUFFIX))
|
|
232
|
+
return candidates;
|
|
233
|
+
return [...candidates, assetPathForName(refType, typeDir, `${refName}${DERIVED_SUFFIX}`)];
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* True when `(refType, refName)` names an asset the cleanup archive holds a
|
|
237
|
+
* tombstone for, in any root. Mirrors `resolveRefPathInStash`'s candidate set
|
|
238
|
+
* so a memory ref that resolved through the `.derived.md` child (#882) still
|
|
239
|
+
* resolves once archived.
|
|
240
|
+
*
|
|
241
|
+
* Originally memory-only (#884: only `.derived` memories were ever pruned).
|
|
242
|
+
* 0.9.17-alpha.9 generalized `archiveCleanupCandidate` to any memory,
|
|
243
|
+
* knowledge or lesson file (a consolidate pair-pass `retire` proposal, or an
|
|
244
|
+
* accepted promotion's source memory), so this must check every type, not
|
|
245
|
+
* just `memory` — otherwise an xref to a retired knowledge or lesson asset
|
|
246
|
+
* reports `missing-ref` even though it resolves fine through the tombstone.
|
|
223
247
|
*/
|
|
224
248
|
function memoryArchiveHasRef(refType, refName, stashRoots) {
|
|
225
|
-
if (refType !== "memory")
|
|
226
|
-
return false; // only memories are ever archived
|
|
227
249
|
const typeDir = stashDirFor(refType);
|
|
228
250
|
if (typeDir === undefined)
|
|
229
251
|
return false;
|
|
230
|
-
const candidates =
|
|
252
|
+
const candidates = refPathCandidates(refType, typeDir, refName);
|
|
231
253
|
for (const root of stashRoots) {
|
|
232
254
|
for (const candidate of candidates) {
|
|
233
255
|
if (isArchivedRelPath(candidate, root))
|
|
@@ -241,8 +263,8 @@ function memoryArchiveHasRef(refType, refName, stashRoots) {
|
|
|
241
263
|
* the same reachability rules (in the same order) as
|
|
242
264
|
* {@link refExistsInAnyStash}, which delegates here. Returns the absolute path
|
|
243
265
|
* of the file that makes the ref "exist" — for a multi-file skill directory
|
|
244
|
-
* that is its `SKILL.md` primary, for a `memory` ref its `.derived.md`
|
|
245
|
-
* when the plain `.md` is absent (#882, see
|
|
266
|
+
* that is its `SKILL.md` primary, for a `memory` ref its `.derived.md` child
|
|
267
|
+
* when the plain `.md` is absent (#882, see {@link refPathCandidates}) —
|
|
246
268
|
* or `null` when the ref does not resolve in this root.
|
|
247
269
|
*
|
|
248
270
|
* Extracted for SPEC-5 (`--supersedes` demotion): write commands need the
|
|
@@ -253,7 +275,7 @@ function memoryArchiveHasRef(refType, refName, stashRoots) {
|
|
|
253
275
|
*/
|
|
254
276
|
export function resolveRefPathInStash(relPath, refType, refName, root) {
|
|
255
277
|
const typeDir = stashDirFor(refType);
|
|
256
|
-
const candidates = typeDir === undefined ? [relPath] :
|
|
278
|
+
const candidates = typeDir === undefined ? [relPath] : refPathCandidates(refType, typeDir, refName);
|
|
257
279
|
for (const candidate of candidates) {
|
|
258
280
|
const absPath = path.join(root, candidate);
|
|
259
281
|
if (fs.existsSync(absPath))
|