akm-cli 0.9.30 → 0.10.26101001-alpha
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 +282 -0
- package/README.md +10 -10
- package/ROADMAP.md +112 -0
- package/STABILITY.md +79 -53
- package/dist/api-node.mjs +20 -0
- package/dist/api.d.ts +23 -0
- package/dist/api.js +45 -0
- package/dist/assets/hints/cli-hints-full.md +13 -27
- package/dist/assets/hints/cli-hints-short.md +1 -1
- package/dist/assets/improve-strategies/default.json +3 -3
- package/dist/assets/prompts/consolidate-pair.md +9 -7
- package/dist/cli/shared.js +31 -48
- package/dist/cli/unknown-flags.js +6 -43
- package/dist/cli.js +8 -34
- package/dist/commands/agent/contribute-cli.js +1 -1
- package/dist/commands/completions.js +1 -2
- package/dist/commands/config-cli.js +5 -28
- package/dist/commands/env/env-cli.js +5 -5
- package/dist/commands/env/secret-cli.js +2 -2
- package/dist/commands/health/checks.js +41 -16
- package/dist/commands/health/plugin-staleness.js +52 -22
- package/dist/commands/improve/autonomy-gate.js +5 -3
- package/dist/commands/improve/consolidate/pair-pass.js +7 -1
- package/dist/commands/improve/consolidate.js +6 -35
- package/dist/commands/improve/distill/lesson-update.js +91 -0
- package/dist/commands/improve/distill-guards.js +1 -43
- package/dist/commands/improve/distill.js +140 -20
- package/dist/commands/improve/extract-cli.js +1 -1
- package/dist/commands/improve/improve-cli.js +120 -114
- package/dist/commands/improve/improve-strategies.js +8 -12
- package/dist/commands/improve/loop-stages.js +0 -1
- package/dist/commands/improve/planner.js +0 -1
- package/dist/commands/improve/preparation.js +40 -99
- package/dist/commands/improve/proactive-maintenance.js +2 -2
- package/dist/commands/improve/reflect-noise.js +3 -55
- package/dist/commands/improve/reflect.js +3 -8
- package/dist/commands/improve/retrieval-scope.js +2 -2
- package/dist/commands/improve/salience.js +2 -9
- package/dist/commands/improve/stage.js +14 -9
- package/dist/commands/lint/index.js +5 -20
- package/dist/commands/observability-cli.js +3 -9
- package/dist/commands/proposal/drain.js +39 -6
- package/dist/commands/proposal/proposal-cli.js +10 -30
- package/dist/commands/proposal/repository.js +3 -3
- package/dist/commands/read/knowledge.js +3 -3
- package/dist/commands/read/remember-cli.js +0 -13
- package/dist/commands/read/search-cli.js +29 -97
- package/dist/commands/read/search.js +0 -11
- package/dist/commands/read/show.js +0 -19
- package/dist/commands/sources/plugin-upgrade.js +164 -101
- package/dist/commands/sources/sources-cli.js +1 -15
- package/dist/commands/sources/stash-cli.js +4 -12
- package/dist/commands/tasks/tasks-cli.js +4 -115
- package/dist/commands/workflow/plan.js +11 -33
- package/dist/commands/workflow-cli.js +2 -7
- package/dist/core/adapter/adapter-ids.js +1 -2
- package/dist/core/adapter/adapters/agent-skills-adapter.js +0 -4
- package/dist/core/adapter/adapters/akm-adapter.js +9 -48
- package/dist/core/adapter/adapters/akm-lint.js +10 -26
- package/dist/core/adapter/adapters/akm-metadata.js +2 -72
- package/dist/core/adapter/adapters/akm-task-adapter.js +0 -5
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +9 -28
- package/dist/core/adapter/adapters/dotenv-adapter.js +1 -20
- package/dist/core/adapter/adapters/generic-files-adapter.js +0 -10
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +0 -4
- package/dist/core/adapter/adapters/tool-dir-shared.js +0 -24
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -4
- package/dist/core/adapter/recognize-match.js +3 -4
- package/dist/core/common.js +18 -2
- package/dist/core/config/config-schema.js +12 -15
- package/dist/core/config/config-types.js +6 -15
- package/dist/core/config/config-walker.js +6 -23
- package/dist/core/config/config.js +17 -80
- package/dist/core/config/engine-semantics.js +7 -3
- package/dist/core/config/schema/engines.js +7 -7
- package/dist/core/config/schema/improve-processes.js +8 -61
- package/dist/core/config/schema/improve.js +2 -21
- package/dist/core/config/schema/index-config.js +6 -39
- package/dist/core/env-secret-ref.js +2 -45
- package/dist/core/errors.js +1 -2
- package/dist/core/extra-params.js +2 -86
- package/dist/core/file-lock.js +10 -1
- package/dist/core/improve-result.js +11 -1
- package/dist/core/mutation-target.js +2 -2
- package/dist/core/recognition-util.js +4 -5
- package/dist/core/warn.js +21 -4
- package/dist/core/write-source.js +3 -3
- package/dist/execution/target-ref.js +1 -2
- package/dist/indexer/search/db-search.js +4 -19
- package/dist/indexer/search/fts-query.js +0 -19
- package/dist/integrations/agent/engine-resolution.js +5 -2
- package/dist/integrations/agent/index.js +1 -1
- package/dist/integrations/agent/profiles.js +41 -98
- package/dist/integrations/agent/request-lowering.js +3 -3
- package/dist/integrations/harnesses/aider/index.js +5 -0
- package/dist/integrations/harnesses/amazonq/index.js +1 -0
- package/dist/integrations/harnesses/claude/config-import.js +2 -3
- package/dist/integrations/harnesses/claude/index.js +5 -0
- package/dist/integrations/harnesses/codex/index.js +1 -0
- package/dist/integrations/harnesses/copilot/index.js +1 -0
- package/dist/integrations/harnesses/gemini/index.js +1 -0
- package/dist/integrations/harnesses/index.js +18 -39
- package/dist/integrations/harnesses/opencode/agent-builder.js +60 -35
- package/dist/integrations/harnesses/opencode/config-import.js +121 -52
- package/dist/integrations/harnesses/opencode/index.js +22 -0
- package/dist/integrations/harnesses/opencode/plugin-layout.js +68 -0
- package/dist/integrations/harnesses/opencode/session-log-types.js +12 -0
- package/dist/integrations/harnesses/opencode/session-log-v1.js +72 -0
- package/dist/integrations/harnesses/opencode/session-log-v2.js +140 -0
- package/dist/integrations/harnesses/opencode/session-log.js +104 -115
- package/dist/integrations/harnesses/opencode/version.js +98 -0
- package/dist/integrations/harnesses/opencode-sdk/{harness.js → index.js} +5 -17
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +105 -206
- package/dist/integrations/harnesses/opencode-sdk/v1-adapter.js +199 -0
- package/dist/integrations/harnesses/opencode-sdk/v2-adapter.js +228 -0
- package/dist/integrations/harnesses/opencode-sdk/wire.js +66 -0
- package/dist/integrations/harnesses/openhands/index.js +1 -0
- package/dist/integrations/harnesses/pi/index.js +1 -0
- package/dist/integrations/harnesses/shared.js +2 -0
- package/dist/integrations/harnesses/types.js +2 -0
- package/dist/llm/client.js +74 -14
- package/dist/output/cli-hints.js +1 -1
- package/dist/output/context.js +10 -19
- package/dist/output/format-exempt.js +5 -8
- package/dist/output/shapes/helpers.js +1 -22
- package/dist/output/shapes/passthrough.js +0 -1
- package/dist/output/shapes.js +3 -31
- package/dist/output/stdout.js +5 -1
- package/dist/output/text/command-format.js +6 -1
- package/dist/output/text/workflow-format.js +4 -22
- package/dist/scripts/akm-migrate-node.js +15746 -16706
- package/dist/scripts/akm-migrate.js +16319 -17275
- package/dist/setup/detect.js +10 -4
- package/dist/setup/harness-config-import.js +5 -14
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/tasks.js +12 -13
- package/dist/storage/repositories/task-history-repository.js +0 -5
- package/dist/storage/repositories/workflow-runs-repository.js +5 -15
- package/dist/tasks/embedded.js +2 -1
- package/dist/tasks/run/task-history.js +0 -2
- package/dist/tasks/schedule-gates.js +39 -0
- package/dist/tasks/scheduler-binding.js +0 -16
- package/dist/tasks/scheduler-sync.js +3 -97
- package/dist/tasks/source/bounded-document.js +1 -1
- package/dist/tasks/source/task-source-v4.js +2 -12
- package/dist/tasks/task-run-reserved-flags.js +2 -31
- package/dist/workflows/authoring/authoring.js +6 -6
- package/dist/workflows/compile.js +8 -65
- package/dist/workflows/exec/child-workflow.js +22 -3
- package/dist/workflows/exec/native-executor.js +12 -147
- package/dist/workflows/exec/run-workflow.js +31 -103
- package/dist/workflows/exec/step-work.js +9 -178
- package/dist/workflows/exec/unit-dispatch.js +0 -10
- package/dist/workflows/freeze/environment.js +3 -44
- package/dist/workflows/freeze/freeze.js +8 -41
- package/dist/workflows/freeze/targets/child-workflow.js +27 -63
- package/dist/workflows/freeze/targets/command.js +2 -16
- package/dist/workflows/freeze/targets/shell.js +0 -2
- package/dist/workflows/freeze/task-bindings.js +2 -28
- package/dist/workflows/ir/params.js +12 -2
- package/dist/workflows/parser.js +82 -232
- package/dist/workflows/plan.js +1 -1
- package/dist/workflows/renderer.js +10 -19
- package/dist/workflows/runtime/run-plan.js +11 -53
- package/dist/workflows/runtime/runs.js +4 -27
- package/dist/workflows/source-files.js +12 -16
- package/dist/workflows/source-semantics.js +1 -93
- package/docs/README.md +43 -42
- package/docs/agents/README.md +15 -0
- package/docs/agents/agent-install.md +14 -0
- package/docs/architecture/README.md +67 -0
- package/docs/architecture/adapters.md +258 -0
- package/docs/architecture/akm-architecture-decision-history.md +2055 -0
- package/docs/architecture/akm-core-principles.md +126 -0
- package/docs/architecture/architecture.md +578 -0
- package/docs/architecture/comparisons/claude-code-vs-akm-workflows-full.md +550 -0
- package/docs/architecture/decisions/0001-no-interpolation-attached-structured-context.md +72 -0
- package/docs/architecture/decisions/0002-unit-reuse-and-input-hash-scope.md +142 -0
- package/docs/architecture/decisions/0003-child-env-allowlist-and-provenance.md +189 -0
- package/docs/architecture/decisions/0004-task-input-contract-and-flag-coercion.md +97 -0
- package/docs/architecture/decisions/0005-task-result-vocabulary-and-legacy-read-mapping.md +108 -0
- package/docs/architecture/decisions/0006-task-source-version-routing.md +124 -0
- package/docs/architecture/decisions/0007-workflow-composition-bounds.md +83 -0
- package/docs/architecture/decisions/0008-task-binding-normalization.md +88 -0
- package/docs/architecture/decisions/0009-child-run-publication-column-parity.md +80 -0
- package/docs/architecture/decisions/0010-driver-protocol-cut.md +36 -0
- package/docs/architecture/decisions/0011-engine-run-loop-invariants.md +125 -0
- package/docs/architecture/decisions/README.md +62 -0
- package/docs/architecture/improvement.md +471 -0
- package/docs/architecture/internals/classification.md +154 -0
- package/docs/architecture/internals/fresh-host-rebuild-runbook.md +40 -0
- package/docs/architecture/internals/functional-contract-patterns.md +340 -0
- package/docs/architecture/internals/health-advisories.md +110 -0
- package/docs/architecture/internals/improve-workflow.md +720 -0
- package/docs/architecture/internals/indexing.md +498 -0
- package/docs/architecture/internals/registry-network-boundary.md +57 -0
- package/docs/architecture/internals/search.md +248 -0
- package/docs/architecture/internals/storage-locations.md +943 -0
- package/docs/architecture/persisted-data-compat.md +68 -0
- package/docs/architecture/reviews/env-secret-access.md +319 -0
- package/docs/architecture/runtime-boundary-design.md +153 -0
- package/docs/architecture/specs/0.9.0-close-out-plan.md +361 -0
- package/docs/architecture/specs/0.9.0-decisions.md +532 -0
- package/docs/architecture/specs/0.9.0-docs-code-drift-register.md +745 -0
- package/docs/architecture/specs/0.9.0-open-items-register.md +135 -0
- package/docs/architecture/specs/0.9.0-public-api-issue-backlog.md +740 -0
- package/docs/architecture/specs/0.9.0-release-surface-review.md +636 -0
- package/docs/architecture/specs/agent-command-engine-model-design.md +513 -0
- package/docs/architecture/specs/akm-0.9.0-bundle-adapter-spec.md +501 -0
- package/docs/architecture/specs/akm-0.9.0-ref-grammar-decision.md +291 -0
- package/docs/architecture/specs/akm-format-neutral-bundle-workspace-spec.md +2059 -0
- package/docs/architecture/specs/di-seams-plan.md +539 -0
- package/docs/architecture/specs/driver-protocol-keep-or-cut.md +275 -0
- package/docs/architecture/specs/fact-asset-type.md +142 -0
- package/docs/architecture/specs/improve-collapse-churn-detector-design.md +634 -0
- package/docs/architecture/specs/okf-support.md +148 -0
- package/docs/architecture/specs/ref.md +329 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/README.md +72 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/findings-consolidated.md +152 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/library-eval-alternatives.md +352 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/library-eval-bunqueue.md +300 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/library-eval-openworkflow.md +402 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/opus-review-1.md +758 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/opus-review-2.md +645 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/opus-review-3.md +602 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-adversarial.md +27 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-architecture.md +27 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-coverage.md +74 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-security.md +15 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r2-adversarial.md +132 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r2-verification.md +147 -0
- package/docs/architecture/specs/stash-conventions-code-spec.md +275 -0
- package/docs/architecture/specs/stash-organization-conventions.md +377 -0
- package/docs/architecture/specs/task-workflow-format-unification.md +721 -0
- package/docs/architecture/specs/workflow-engine-buy-vs-build.md +231 -0
- package/docs/architecture/specs/workflow-format-unification.md +432 -0
- package/docs/architecture/testing/manual-testing-checklist.md +3411 -0
- package/docs/architecture/testing/okf-v0.2-conformance-runbook.md +924 -0
- package/docs/architecture/testing/task-workflow-manual-tests.md +1720 -0
- package/docs/architecture/testing/testing-workflow.md +446 -0
- package/docs/architecture/workflow-engine.md +223 -0
- package/docs/guides/README.md +33 -0
- package/docs/guides/author-bundles.md +310 -0
- package/docs/guides/author-workflows.md +490 -0
- package/docs/guides/bundles.md +321 -0
- package/docs/guides/capture-knowledge.md +136 -0
- package/docs/guides/claude-code-vs-akm-workflows.md +29 -0
- package/docs/guides/concepts.md +93 -0
- package/docs/guides/discover-and-load.md +158 -0
- package/docs/guides/getting-started.md +240 -0
- package/docs/guides/improve-the-library.md +246 -0
- package/docs/guides/knowledge-management.md +18 -0
- package/docs/guides/recipes/headless-install.md +97 -0
- package/docs/guides/recipes/website-source.md +28 -0
- package/docs/guides/run-workflows.md +279 -0
- package/docs/guides/scheduling.md +123 -0
- package/docs/guides/use-with-any-agent.md +174 -0
- package/docs/guides/wikis.md +81 -0
- package/docs/integration/bundling-akm.md +7 -6
- package/docs/maintainers/README.md +19 -0
- package/docs/maintainers/curate-workmap.md +335 -0
- package/docs/maintainers/eval.md +3 -0
- package/docs/maintainers/local-development.md +108 -0
- package/docs/maintainers/release-checklist.md +132 -0
- package/docs/migration/README.md +2 -2
- package/docs/migration/release-notes/0.0.13.md +1 -1
- package/docs/migration/release-notes/0.1.0.md +1 -1
- package/docs/migration/release-notes/0.2.0.md +1 -1
- package/docs/migration/release-notes/0.3.0.md +1 -1
- package/docs/migration/release-notes/0.5.0.md +1 -1
- package/docs/migration/release-notes/0.6.0.md +1 -1
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.7.3.md +2 -2
- package/docs/migration/release-notes/0.7.4.md +1 -1
- package/docs/migration/release-notes/0.7.5.md +1 -1
- package/docs/migration/release-notes/0.8.0.md +1 -1
- package/docs/migration/release-notes/0.9.0.md +1 -1
- package/docs/migration/release-notes/0.9.14.md +1 -1
- package/docs/migration/release-notes/0.9.15.md +1 -1
- package/docs/migration/release-notes/0.9.16.md +1 -1
- package/docs/migration/release-notes/0.9.17.md +1 -1
- package/docs/migration/release-notes/0.9.19.md +1 -1
- package/docs/migration/release-notes/0.9.2.md +1 -1
- package/docs/migration/release-notes/README.md +27 -31
- package/docs/migration/v0.5-to-v0.6.md +759 -0
- package/docs/migration/v0.8-to-v0.9.md +1 -1
- package/docs/plans/0.9.2-agent-command-workflow-plan.md +423 -0
- package/docs/posts/README.md +30 -0
- package/docs/posts/akm-command-tour.md +495 -0
- package/docs/posts/akm-logo-sized.webp +0 -0
- package/docs/posts/belief-aware-memory-12.md +139 -0
- package/docs/posts/config-v2-lessons-16.md +191 -0
- package/docs/posts/improve-loop-autonomous-10.md +225 -0
- package/docs/posts/improve-pipeline-debugging-0803.md +179 -0
- package/docs/posts/intro-part-01.md +76 -0
- package/docs/posts/intro-part-02.md +221 -0
- package/docs/posts/intro-part-03.md +190 -0
- package/docs/posts/intro-part-04.md +221 -0
- package/docs/posts/llm-mode-reflect-13.md +166 -0
- package/docs/posts/openpalm-built-on-akm.md +86 -0
- package/docs/posts/progressive-disclosure-06.md +129 -0
- package/docs/posts/proposal-safety-net-15.md +112 -0
- package/docs/posts/release-0.5.0.md +247 -0
- package/docs/posts/release-0.6.0.md +117 -0
- package/docs/posts/release-0.7.0.md +159 -0
- package/docs/posts/release-0.8.0.md +332 -0
- package/docs/posts/scattered-skills-05.md +100 -0
- package/docs/posts/task-assets-persistent-workflows-11.md +222 -0
- package/docs/posts/team-skills-07.md +198 -0
- package/docs/posts/wiki-agent-knowledge-08.md +223 -0
- package/docs/posts/workflows-vaults-09.md +321 -0
- package/docs/product-surface.md +54 -0
- package/docs/reference/README.md +2 -2
- package/docs/reference/api.md +65 -0
- package/docs/reference/asset-types.md +166 -0
- package/docs/reference/bundle-types.md +2 -4
- package/docs/reference/cli.md +129 -175
- package/docs/reference/configuration.md +84 -19
- package/docs/reference/data-and-telemetry.md +1 -1
- package/docs/reference/env-and-secrets.md +126 -0
- package/docs/reference/memory.md +124 -0
- package/docs/reference/refs.md +156 -0
- package/docs/reference/registry.md +489 -0
- package/docs/reference/supported-formats.md +3 -4
- package/docs/reference/tasks.md +12 -19
- package/docs/reference/website-sources.md +141 -0
- package/docs/reference/workflow-schema.md +89 -317
- package/docs/reference/workflows.md +20 -48
- package/package.json +23 -16
- package/schemas/akm-config.json +2 -243
- package/schemas/akm-task.json +2 -3
- package/schemas/akm-workflow.json +47 -84
- package/dist/assets/improve-strategies/catchup.json +0 -41
- package/dist/assets/improve-strategies/proactive-maintenance.json +0 -24
- package/dist/assets/improve-strategies/quick.json +0 -17
- package/dist/assets/improve-strategies/reflect-distill.json +0 -29
- package/dist/assets/improve-strategies/thorough.json +0 -49
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +0 -6
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +0 -4
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +0 -4
- package/dist/cli/retired-commands.js +0 -118
- package/dist/commands/tasks/validate.js +0 -176
- package/dist/core/config/legacy-source-shape-shim.js +0 -117
- package/dist/integrations/harnesses/ids.js +0 -28
- package/dist/tasks/source/task-source-v3-frozen.js +0 -424
- package/dist/tasks/source/task-to-v4.js +0 -863
- package/dist/workflows/exec/worktree.js +0 -372
- package/dist/workflows/freeze/child-output-references.js +0 -83
- package/dist/workflows/freeze/identity.js +0 -22
- package/dist/workflows/freeze/targets/script.js +0 -50
- package/dist/workflows/freeze/targets/task.js +0 -124
- package/dist/workflows/github-yaml.js +0 -506
- package/dist/workflows/runtime/run-outputs.js +0 -80
|
@@ -52,6 +52,7 @@ import { spawnSync } from "node:child_process";
|
|
|
52
52
|
import fs from "node:fs";
|
|
53
53
|
import os from "node:os";
|
|
54
54
|
import path from "node:path";
|
|
55
|
+
import { OPENCODE_V1, OPENCODE_V2, readPackageVersion, } from "../../integrations/harnesses/opencode/plugin-layout.js";
|
|
55
56
|
import { isExactSemver, isSemverRange, maxSatisfying, satisfiesRange } from "../../registry/semver.js";
|
|
56
57
|
/**
|
|
57
58
|
* Root directory holding Claude Code's plugin cache + marketplace clones.
|
|
@@ -66,41 +67,69 @@ function claudePluginsDir() {
|
|
|
66
67
|
/**
|
|
67
68
|
* Root of OpenCode's package cache, same override pattern as
|
|
68
69
|
* {@link claudePluginsDir} (`AKM_OPENCODE_CACHE_DIR` for tests).
|
|
69
|
-
* The OpenCode
|
|
70
|
-
* `
|
|
70
|
+
* The OpenCode plugins (`akm-opencode`, and `akm-opencode-v2` for OpenCode 2) bundle their own `akm-cli`
|
|
71
|
+
* (layout: `integrations/harnesses/opencode/plugin-layout.ts`) and run it in-process,
|
|
71
72
|
* sharing the host's databases with every other akm install — its version
|
|
72
73
|
* lag matters the same way a stale Claude plugin's does.
|
|
73
74
|
*/
|
|
74
75
|
function opencodeCacheDir() {
|
|
75
76
|
return process.env.AKM_OPENCODE_CACHE_DIR ?? path.join(os.homedir(), ".cache", "opencode");
|
|
76
77
|
}
|
|
78
|
+
const OPENCODE_PLUGIN_LOCATIONS = [
|
|
79
|
+
{
|
|
80
|
+
// OpenCode 1: <cache>/packages/akm-opencode/node_modules/akm-cli (the folder read since this check began).
|
|
81
|
+
flavor: OPENCODE_V1,
|
|
82
|
+
advisory: "opencode-plugin-version",
|
|
83
|
+
label: "OpenCode's",
|
|
84
|
+
remedy: "update the OpenCode plugin",
|
|
85
|
+
folders: (cacheRoot) => [{ folder: path.join(cacheRoot, OPENCODE_V1.cacheFolder(OPENCODE_V1.pkg)) }],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
// OpenCode 2: <cache>/npm/akm-opencode-v2@<spec>/<epoch-ms>/node_modules/akm-cli, newest build per spec.
|
|
89
|
+
flavor: OPENCODE_V2,
|
|
90
|
+
advisory: "opencode-v2-plugin-version",
|
|
91
|
+
label: "OpenCode 2's akm-opencode-v2",
|
|
92
|
+
remedy: "update the akm-opencode-v2 plugin",
|
|
93
|
+
folders: (cacheRoot) => {
|
|
94
|
+
const npmDir = path.join(cacheRoot, OPENCODE_V2.cacheFolder(""));
|
|
95
|
+
let specs;
|
|
96
|
+
try {
|
|
97
|
+
specs = fs.readdirSync(npmDir).filter((name) => name.startsWith(`${OPENCODE_V2.pkg}@`));
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
return specs.sort().map((spec) => ({ spec, folder: path.join(npmDir, spec) }));
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
];
|
|
77
106
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* {@link detectInstalledPlugins}'s manifest read. `undefined` when the
|
|
81
|
-
* OpenCode plugin, or its bundled akm-cli, is not installed.
|
|
107
|
+
* The bundled `akm-cli` of each cached copy of `plugin`: its `package.json` is read, never executed,
|
|
108
|
+
* mirroring {@link detectInstalledPlugins}'s manifest read. Copies with no bundled akm-cli are skipped.
|
|
82
109
|
*/
|
|
83
|
-
function
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return undefined;
|
|
110
|
+
function findBundledAkm(plugin, cacheRoot) {
|
|
111
|
+
const found = [];
|
|
112
|
+
for (const { spec, folder } of plugin.folders(cacheRoot)) {
|
|
113
|
+
const nodeModules = plugin.flavor.nodeModulesDir(folder);
|
|
114
|
+
const version = nodeModules && readPackageVersion(path.join(nodeModules, "akm-cli", "package.json"));
|
|
115
|
+
if (version !== undefined)
|
|
116
|
+
found.push(spec ? { spec, version } : { version });
|
|
91
117
|
}
|
|
118
|
+
return found;
|
|
92
119
|
}
|
|
93
|
-
function buildOpencodeBundledAdvisory(
|
|
120
|
+
function buildOpencodeBundledAdvisory(plugin, bundled, cliVersion) {
|
|
121
|
+
const bundledVersion = bundled.version;
|
|
94
122
|
const stale = bundledVersion !== cliVersion;
|
|
123
|
+
const where = bundled.spec ? ` (${bundled.spec})` : "";
|
|
95
124
|
return {
|
|
96
|
-
name:
|
|
125
|
+
name: plugin.advisory,
|
|
97
126
|
kind: "deterministic",
|
|
98
127
|
status: stale ? "warn" : "pass",
|
|
99
128
|
confidence: "high",
|
|
100
129
|
message: stale
|
|
101
|
-
?
|
|
102
|
-
:
|
|
103
|
-
evidence: { bundledVersion, cliVersion },
|
|
130
|
+
? `${plugin.label} bundled akm-cli${where} is v${bundledVersion}, but the running CLI is v${cliVersion} — it shares the same databases in-process; ${plugin.remedy}.`
|
|
131
|
+
: `${plugin.label} bundled akm-cli${where} matches the running v${cliVersion}.`,
|
|
132
|
+
evidence: bundled.spec ? { bundledVersion, cliVersion, spec: bundled.spec } : { bundledVersion, cliVersion },
|
|
104
133
|
};
|
|
105
134
|
}
|
|
106
135
|
/**
|
|
@@ -218,9 +247,10 @@ export function collectPluginStalenessAdvisories(options) {
|
|
|
218
247
|
const plugins = detectInstalledPlugins(pluginsRoot);
|
|
219
248
|
const results = plugins.map((plugin) => buildAdvisory(plugin, options.cliVersion, pluginsRoot, listRemoteTags));
|
|
220
249
|
const opencodeCacheRoot = options.opencodeCacheRoot ?? opencodeCacheDir();
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
250
|
+
for (const plugin of OPENCODE_PLUGIN_LOCATIONS) {
|
|
251
|
+
for (const bundled of findBundledAkm(plugin, opencodeCacheRoot)) {
|
|
252
|
+
results.push(buildOpencodeBundledAdvisory(plugin, bundled, options.cliVersion));
|
|
253
|
+
}
|
|
224
254
|
}
|
|
225
255
|
return results;
|
|
226
256
|
}
|
|
@@ -6,7 +6,7 @@ import { IMPROVE_AUTONOMY_CONFIG_KEY, isImproveAutonomyEnabled, } from "../../co
|
|
|
6
6
|
export const AUTONOMY_LANES = ["memoryInference", "triagePromote", "memoryCleanup"];
|
|
7
7
|
const LANE_REASONS = {
|
|
8
8
|
memoryInference: "writes derived memory children and rewrites parent frontmatter",
|
|
9
|
-
triagePromote: "auto-accepts queued proposals
|
|
9
|
+
triagePromote: "auto-accepts queued proposals the triage judgment tier approves (downgraded to queue)",
|
|
10
10
|
memoryCleanup: "rewrites belief-state frontmatter and moves files into the cleanup archive",
|
|
11
11
|
};
|
|
12
12
|
/**
|
|
@@ -59,8 +59,10 @@ export function applyAutonomyGate(strategy, config) {
|
|
|
59
59
|
}
|
|
60
60
|
// Triage stays ENABLED — queued proposals are still triaged, they just are not
|
|
61
61
|
// auto-accepted. Disabling it would remove review work the user asked for,
|
|
62
|
-
// which is the opposite of what a review-first default should do.
|
|
63
|
-
|
|
62
|
+
// which is the opposite of what a review-first default should do. Only a
|
|
63
|
+
// promote that runs the judgment tier is gated (#1143); a deterministic-only
|
|
64
|
+
// promote is the default and needs no opt-in.
|
|
65
|
+
if (processes.triage?.applyMode === "promote" && processes.triage.judgment?.enabled === true) {
|
|
64
66
|
processes.triage = { ...processes.triage, applyMode: "queue" };
|
|
65
67
|
gated.push(gatedLane("triagePromote"));
|
|
66
68
|
}
|
|
@@ -465,7 +465,13 @@ function orderByAge(x, y) {
|
|
|
465
465
|
}
|
|
466
466
|
/** The user message: dates decide "A (older)" / "B (newer)" (plan Appendix A), matching the calibration sample's own ordering. */
|
|
467
467
|
export function buildPairUserPrompt(older, newer) {
|
|
468
|
-
|
|
468
|
+
// Equal dates (a batch added in one commit, notes from one file time) make neither side newer: say so, or the judge
|
|
469
|
+
// reads "B (newer)" as proof that B replaces A (#1134).
|
|
470
|
+
const tied = Date.parse(older.createdIso) === Date.parse(newer.createdIso);
|
|
471
|
+
return [
|
|
472
|
+
...sideSection(tied ? "A (same age as B)" : "A (older)", older),
|
|
473
|
+
...sideSection(tied ? "B (same age as A)" : "B (newer)", newer),
|
|
474
|
+
].join("\n");
|
|
469
475
|
}
|
|
470
476
|
/** The tombstone-vocabulary reason a judge label maps to (`supersedes` -> `superseded`; the rest unchanged). */
|
|
471
477
|
export function tombstoneReason(label) {
|
|
@@ -28,7 +28,7 @@ import { detectAdapterId } from "../../core/adapter/detect-adapter.js";
|
|
|
28
28
|
import { assembleAssetFromString, serializeFrontmatter } from "../../core/asset/asset-serialize.js";
|
|
29
29
|
import { parseFrontmatter } from "../../core/asset/frontmatter.js";
|
|
30
30
|
import { conceptIdFromTypeName, displayRef, parseRefInput } from "../../core/asset/resolve-ref.js";
|
|
31
|
-
import {
|
|
31
|
+
import { loadConfig } from "../../core/config/config.js";
|
|
32
32
|
import { parseEmbeddedJsonResponse } from "../../core/parse.js";
|
|
33
33
|
import { openStateDatabase } from "../../core/state-db.js";
|
|
34
34
|
import { warn, warnVerbose } from "../../core/warn.js";
|
|
@@ -210,36 +210,6 @@ async function clusterMemoriesBySimilarity(memories, config, stateDb, signal) {
|
|
|
210
210
|
}
|
|
211
211
|
return { ordered, embedTelemetry: telemetry };
|
|
212
212
|
}
|
|
213
|
-
/**
|
|
214
|
-
* Anti-collapse (default on, `antiCollapse.enabled: false` opts out): a small
|
|
215
|
-
* deterministic sample of the pool is spread through the similarity order so
|
|
216
|
-
* consolidation is not purely similarity-driven.
|
|
217
|
-
*/
|
|
218
|
-
function injectRandomClusterMembers(memories, profile, warnings) {
|
|
219
|
-
const config = getImproveProcessConfig("consolidate", profile)?.antiCollapse ?? {};
|
|
220
|
-
if (config.enabled === false || memories.length <= 2)
|
|
221
|
-
return memories;
|
|
222
|
-
const fraction = config.randomClusterFraction ?? 0.05;
|
|
223
|
-
const randomCount = Math.max(1, Math.floor(memories.length * fraction));
|
|
224
|
-
const sample = [...memories]
|
|
225
|
-
.sort((a, b) => contentHash(a.name).localeCompare(contentHash(b.name)))
|
|
226
|
-
.slice(0, randomCount);
|
|
227
|
-
const sampled = new Set(sample.map((m) => m.name));
|
|
228
|
-
const interval = Math.max(2, Math.floor(memories.length / randomCount));
|
|
229
|
-
const out = [];
|
|
230
|
-
let next = 0;
|
|
231
|
-
for (let i = 0; i < memories.length; i++) {
|
|
232
|
-
const m = memories[i];
|
|
233
|
-
if (m && !sampled.has(m.name))
|
|
234
|
-
out.push(m);
|
|
235
|
-
if (i > 0 && i % interval === 0 && next < sample.length)
|
|
236
|
-
out.push(sample[next++]);
|
|
237
|
-
}
|
|
238
|
-
while (next < sample.length)
|
|
239
|
-
out.push(sample[next++]);
|
|
240
|
-
warnings.push(`Anti-collapse: injected ${randomCount} random (non-similarity-driven) cluster member(s) into consolidation pool (fraction=${fraction}).`);
|
|
241
|
-
return out;
|
|
242
|
-
}
|
|
243
213
|
/** Body hashes of pending consolidate proposals, so the prompt can mark memories already queued. */
|
|
244
214
|
function loadPendingConsolidateProposalHashes(stashDir, proposalsCtx) {
|
|
245
215
|
const hashes = new Set();
|
|
@@ -670,11 +640,12 @@ async function judgeConsolidationChunks(args) {
|
|
|
670
640
|
}
|
|
671
641
|
return planned;
|
|
672
642
|
}
|
|
643
|
+
/** Assumed wall-clock seconds per consolidation chunk for the up-front budget cap. */
|
|
644
|
+
const CHUNK_SECONDS_ESTIMATE = 30;
|
|
673
645
|
/**
|
|
674
646
|
* The model's plan for the narrowed pool: chunk size from the context window,
|
|
675
647
|
* an up-front cap when the remaining budget cannot cover every chunk (oldest
|
|
676
|
-
* first, the rest deferred), similarity clustering,
|
|
677
|
-
* chunk loop.
|
|
648
|
+
* first, the rest deferred), similarity clustering, then the chunk loop.
|
|
678
649
|
*/
|
|
679
650
|
async function planConsolidation(opts, config, stashDir, memories, warnings, stateDb, acc) {
|
|
680
651
|
const llmRunner = opts.llmRunner ?? undefined;
|
|
@@ -685,7 +656,7 @@ async function planConsolidation(opts, config, stashDir, memories, warnings, sta
|
|
|
685
656
|
let budgeted = memories;
|
|
686
657
|
const budgetMs = opts.signal?.remainingBudgetMs;
|
|
687
658
|
if (opts.signal && budgetMs !== undefined) {
|
|
688
|
-
const safeChunks = Math.max(0, Math.floor((Math.max(0, budgetMs) / 1000 /
|
|
659
|
+
const safeChunks = Math.max(0, Math.floor((Math.max(0, budgetMs) / 1000 / CHUNK_SECONDS_ESTIMATE) * 0.6));
|
|
689
660
|
if (safeChunks * chunkSize < memories.length) {
|
|
690
661
|
budgeted = [...memories]
|
|
691
662
|
.sort((a, b) => mtimeMsOf(a) - mtimeMsOf(b) || a.name.localeCompare(b.name))
|
|
@@ -700,7 +671,7 @@ async function planConsolidation(opts, config, stashDir, memories, warnings, sta
|
|
|
700
671
|
if (llmRunner && willDispatch)
|
|
701
672
|
assertRunnerCredentials(llmRunner);
|
|
702
673
|
const { ordered, embedTelemetry } = await clusterMemoriesBySimilarity(budgeted, config, stateDb, opts.signal);
|
|
703
|
-
const chunks = slice(
|
|
674
|
+
const chunks = slice(ordered);
|
|
704
675
|
const pendingProposalBodyHashes = loadPendingConsolidateProposalHashes(stashDir, opts.proposalsCtx);
|
|
705
676
|
warn(`[consolidate] ${budgeted.length} memories / ${chunks.length} chunk(s) / chunk_size=${chunkSize}` +
|
|
706
677
|
` / pending-proposal hashes: ${pendingProposalBodyHashes.size}`);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* Distill's lesson update (#1090): when a memory's lesson repeats a lesson the library already holds, the writer
|
|
6
|
+
* is asked to extend that lesson with what the memory adds instead of the duplicate being thrown away. These are
|
|
7
|
+
* the prompt, the reply schema and the two deterministic checks; the call, the judge and the proposal are in
|
|
8
|
+
* `../distill`.
|
|
9
|
+
*/
|
|
10
|
+
import { parseEmbeddedJsonResponse } from "../../../core/parse.js";
|
|
11
|
+
export const DISTILL_LESSON_UPDATE_JSON_SCHEMA = {
|
|
12
|
+
type: "object",
|
|
13
|
+
required: ["reason", "new_facts", "decision", "ref", "body"],
|
|
14
|
+
additionalProperties: false,
|
|
15
|
+
properties: {
|
|
16
|
+
reason: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "One sentence: which listed lesson states the rule and what the memory adds to it, or why nothing.",
|
|
19
|
+
},
|
|
20
|
+
new_facts: {
|
|
21
|
+
type: "array",
|
|
22
|
+
items: { type: "string" },
|
|
23
|
+
description: "Written before the body: each cause, step, number, limit or consequence the memory states and the lesson lacks, one short phrase each. Empty when there is none.",
|
|
24
|
+
},
|
|
25
|
+
decision: {
|
|
26
|
+
type: "string",
|
|
27
|
+
enum: ["update", "none"],
|
|
28
|
+
description: "`none` when no listed lesson states the memory's rule, or `new_facts` is empty: leave `ref` and `body` empty. Otherwise `update`.",
|
|
29
|
+
},
|
|
30
|
+
ref: { type: "string", description: "The `Existing lesson ref:` of the lesson to extend. Empty for `none`." },
|
|
31
|
+
body: {
|
|
32
|
+
type: "string",
|
|
33
|
+
description: "That lesson's whole body with the new lines added, every existing line unchanged. No frontmatter. Empty for `none`.",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
export function buildLessonUpdatePrompt(memory, feedback, candidates) {
|
|
38
|
+
const lines = [
|
|
39
|
+
"A lesson written from the memory below would repeat a lesson the library already holds. Do not write a new lesson. Extend the existing one with what the memory adds.",
|
|
40
|
+
"",
|
|
41
|
+
"Memory:",
|
|
42
|
+
"```",
|
|
43
|
+
memory.slice(0, 3000),
|
|
44
|
+
"```",
|
|
45
|
+
];
|
|
46
|
+
if (feedback.length > 0)
|
|
47
|
+
lines.push("", "Feedback recorded about the memory:", "```", feedback.join("\n").slice(0, 1500), "```");
|
|
48
|
+
lines.push("", "Existing lessons, nearest first (they may be on another subject):");
|
|
49
|
+
for (const candidate of candidates) {
|
|
50
|
+
lines.push(`\nExisting lesson ref: ${candidate.ref}`, "```", candidate.body.slice(0, 3000), "```");
|
|
51
|
+
}
|
|
52
|
+
lines.push("", "Pick the one existing lesson that already states the rule the memory gives. Write its whole body again with the new lines added:", "- Copy every existing line unchanged, in its place. Do not reword, shorten, reorder or drop any of them.", "First list in new_facts each cause, step, number, limit or consequence the memory states and the lesson lacks, one short phrase each: check every sentence of the memory against the lesson. The memory's own incident retold (what happened in one test, project or day) is not a new fact unless it carries one of these. Then add one line to the body for each new fact, and say nothing the memory does not say.", "- If the memory contradicts a line of the lesson, answer none.", "- If no listed lesson states the memory's rule, or the memory adds nothing the lesson lacks, answer none.", "", 'Return ONLY valid JSON, no prose: {"reason": "...", "new_facts": ["..."], "decision": "update" | "none", "ref": "<the existing lesson ref>", "body": "<the whole body>"}');
|
|
53
|
+
return lines.join("\n");
|
|
54
|
+
}
|
|
55
|
+
/** The writer's chosen update, or `null` for `none` and for any reply that names no listed lesson, no new fact or no body. */
|
|
56
|
+
export function parseLessonUpdate(raw, candidates) {
|
|
57
|
+
const payload = parseEmbeddedJsonResponse(raw);
|
|
58
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload) || payload.decision !== "update")
|
|
59
|
+
return null;
|
|
60
|
+
const ref = typeof payload.ref === "string" ? payload.ref.trim() : "";
|
|
61
|
+
const body = typeof payload.body === "string" ? payload.body.trim() : "";
|
|
62
|
+
const candidate = candidates.find((c) => c.ref === ref);
|
|
63
|
+
const facts = Array.isArray(payload.new_facts)
|
|
64
|
+
? payload.new_facts.filter((f) => typeof f === "string" && f.trim() !== "")
|
|
65
|
+
: [];
|
|
66
|
+
if (!candidate || !body || facts.length === 0)
|
|
67
|
+
return null;
|
|
68
|
+
return { candidate, body, reason: typeof payload.reason === "string" ? payload.reason.trim() : "" };
|
|
69
|
+
}
|
|
70
|
+
const normalizeLine = (line) => line.trim().replace(/\s+/g, " ").toLowerCase();
|
|
71
|
+
const bodyLines = (body) => body
|
|
72
|
+
.split("\n")
|
|
73
|
+
.map(normalizeLine)
|
|
74
|
+
.filter((line) => line !== "");
|
|
75
|
+
/**
|
|
76
|
+
* What an update does to a lesson's body: the existing lines it no longer holds (compared by words, in any case
|
|
77
|
+
* and spacing, anywhere in the new body) and the lines it adds.
|
|
78
|
+
*/
|
|
79
|
+
export function diffLessonBody(existingBody, mergedBody) {
|
|
80
|
+
const existing = bodyLines(existingBody);
|
|
81
|
+
const merged = bodyLines(mergedBody);
|
|
82
|
+
const kept = new Set(merged);
|
|
83
|
+
const had = new Set(existing);
|
|
84
|
+
return {
|
|
85
|
+
dropped: existing.filter((line) => !kept.has(line)),
|
|
86
|
+
added: mergedBody
|
|
87
|
+
.split("\n")
|
|
88
|
+
.filter((line) => line.trim() !== "" && !had.has(normalizeLine(line)))
|
|
89
|
+
.map((line) => line.trim()),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
/**
|
|
5
5
|
* Distill guards: the related lessons, knowledge notes and skills shown to the
|
|
6
|
-
* writer so it does not repeat or overwrite them (CLS context)
|
|
7
|
-
* that a proposal does not contradict the memories it came from.
|
|
6
|
+
* writer so it does not repeat or overwrite them (CLS context).
|
|
8
7
|
*/
|
|
9
8
|
export const DEFAULT_CLS_ADJACENT_COUNT = 3;
|
|
10
9
|
/** The CLS prompt section (each entry capped at 600 chars); empty when disabled (on unless `enabled: false`) or nothing is related. */
|
|
@@ -22,44 +21,3 @@ export function buildClsContext(adjacentItems, config) {
|
|
|
22
21
|
lines.push(`### ${item.ref}`, item.content.trim().slice(0, 600), "");
|
|
23
22
|
return lines.join("\n");
|
|
24
23
|
}
|
|
25
|
-
/**
|
|
26
|
-
* Flag a proposal whose "always/must X" (or "never/must not X") claim meets
|
|
27
|
-
* the opposite claim about X in a source. Deliberately conservative: a flag
|
|
28
|
-
* only costs a human review, while a model call per proposal is expensive.
|
|
29
|
-
*/
|
|
30
|
-
export function checkDistillFidelity(proposalBody, sourceBodies, config) {
|
|
31
|
-
if (!config.enabled || sourceBodies.length === 0)
|
|
32
|
-
return { contradictionDetected: false };
|
|
33
|
-
const strongClaims = extractStrongClaims(proposalBody.toLowerCase());
|
|
34
|
-
for (const sourceBody of sourceBodies) {
|
|
35
|
-
const sourceLow = sourceBody.toLowerCase();
|
|
36
|
-
for (const { polarity, term } of strongClaims) {
|
|
37
|
-
if (hasStrongClaim(sourceLow, term, polarity === "positive" ? "negative" : "positive")) {
|
|
38
|
-
return {
|
|
39
|
-
contradictionDetected: true,
|
|
40
|
-
reason: `Proposal makes a ${polarity} strong claim about "${term}" that conflicts with an opposing claim in a cited source. Route to human review.`,
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return { contradictionDetected: false };
|
|
46
|
-
}
|
|
47
|
-
const CLAIM_PATTERNS = [
|
|
48
|
-
{ polarity: "positive", re: /\b(?:always|must)\s+(\w+)/g },
|
|
49
|
-
{ polarity: "negative", re: /\b(?:never|must\s+not|should\s+not)\s+(\w+)/g },
|
|
50
|
-
];
|
|
51
|
-
function extractStrongClaims(text) {
|
|
52
|
-
const claims = [];
|
|
53
|
-
for (const { polarity, re } of CLAIM_PATTERNS) {
|
|
54
|
-
for (const m of text.matchAll(re)) {
|
|
55
|
-
const term = m[1];
|
|
56
|
-
if (term && term.length > 2)
|
|
57
|
-
claims.push({ polarity, term });
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return claims;
|
|
61
|
-
}
|
|
62
|
-
function hasStrongClaim(text, term, polarity) {
|
|
63
|
-
const marker = polarity === "positive" ? /\b(?:always|must)\s/ : /\b(?:never|must\s+not|should\s+not)\s/;
|
|
64
|
-
return marker.test(text) && text.includes(term);
|
|
65
|
-
}
|
|
@@ -40,15 +40,15 @@ import { getAllEntries } from "../../storage/repositories/index-entries-reposito
|
|
|
40
40
|
import { listProposals } from "../proposal/repository.js";
|
|
41
41
|
import { detectDoubleFrontmatter, isValidDescription } from "../proposal/validators/proposal-quality-validators.js";
|
|
42
42
|
import { akmSearch } from "../read/search.js";
|
|
43
|
-
import { stripFrontmatterBody } from "./content-hash.js";
|
|
44
43
|
import { autoRepairLessonFrontmatter, autoSwapDescriptionWhenToUse, collectLessonQualityFindings, repairLessonDescriptionTruncation, } from "./distill/content-repair.js";
|
|
45
|
-
import {
|
|
44
|
+
import { buildLessonUpdatePrompt, DISTILL_LESSON_UPDATE_JSON_SCHEMA, diffLessonBody, parseLessonUpdate, } from "./distill/lesson-update.js";
|
|
45
|
+
import { buildClsContext, DEFAULT_CLS_ADJACENT_COUNT } from "./distill-guards.js";
|
|
46
46
|
import { assessMemoryKnowledgePromotionCandidate, deriveKnowledgeRef } from "./distill-promotion-policy.js";
|
|
47
47
|
import { buildRefVocabulary, scoreEncodingSalience } from "./encoding-salience.js";
|
|
48
48
|
import { resolveImproveStrategy, resolveProcessEnabled } from "./improve-strategies.js";
|
|
49
49
|
import { recordLedgerAttempt } from "./ledger.js";
|
|
50
50
|
import { computeSalience, upsertAssetSalience } from "./salience.js";
|
|
51
|
-
import { callStage, mintProposal, noticeSet, rejectedProposalContext, resolveQualityGateJudge, runLessonQualityJudge, stageRunner, } from "./stage.js";
|
|
51
|
+
import { callStage, LESSON_REJECT_MAX_SCORE, mintProposal, noticeSet, rejectedProposalContext, resolveQualityGateJudge, runLessonQualityJudge, stageRunner, } from "./stage.js";
|
|
52
52
|
/**
|
|
53
53
|
* Input types distill structurally refuses: a lesson is the distilled form
|
|
54
54
|
* (distilling one would mint `lessons/lesson-…-lesson`), and env/secret bytes
|
|
@@ -361,7 +361,6 @@ export async function akmDistill(options) {
|
|
|
361
361
|
eligMeta,
|
|
362
362
|
asset,
|
|
363
363
|
vocabulary: loadRefVocabulary(),
|
|
364
|
-
outcomeWeightEnabled: config.improve?.salience?.outcomeWeightEnabled !== false,
|
|
365
364
|
related: options.fetchRelatedFn ?? fetchRelatedAssets,
|
|
366
365
|
lookup,
|
|
367
366
|
};
|
|
@@ -436,6 +435,12 @@ async function distill(run, targetKind, kind, outputRef, feedbackEvents) {
|
|
|
436
435
|
}
|
|
437
436
|
const none = answeredNone(call.raw);
|
|
438
437
|
if (none) {
|
|
438
|
+
// The writer may have answered NONE because a lesson already states the rule (the related list says to).
|
|
439
|
+
if (kind === "lesson") {
|
|
440
|
+
const update = await proposeLessonUpdate(run, feedbackLines(feedback), none.reason);
|
|
441
|
+
if (update)
|
|
442
|
+
return update;
|
|
443
|
+
}
|
|
439
444
|
return skipDistill(run, outputRef, kind, "nothing_reusable", `The writer found no lesson in ${run.inputRef}${none.reason ? `: ${none.reason}` : "."}`);
|
|
440
445
|
}
|
|
441
446
|
const assembled = assembleDistilledContent(run, call.raw, kind, outputRef);
|
|
@@ -537,6 +542,13 @@ async function judgeAndQueue(run, out) {
|
|
|
537
542
|
onNotices: run.notices.add,
|
|
538
543
|
});
|
|
539
544
|
if (!verdict.pass) {
|
|
545
|
+
if (out.kind === "lesson" &&
|
|
546
|
+
!out.promotion &&
|
|
547
|
+
(verdict.criteria?.nonRedundancy ?? 5) <= LESSON_REJECT_MAX_SCORE) {
|
|
548
|
+
const update = await proposeLessonUpdate(run, out.feedback ?? [], verdict.reason, related);
|
|
549
|
+
if (update)
|
|
550
|
+
return update;
|
|
551
|
+
}
|
|
540
552
|
return rejectDistilled(run, out.ref, content, verdict.score, verdict.reason, {
|
|
541
553
|
...(verdict.reviewNeeded ? { reviewNeeded: true } : {}),
|
|
542
554
|
...(verdict.criteria ? { criteria: verdict.criteria } : {}),
|
|
@@ -553,19 +565,6 @@ async function judgeAndQueue(run, out) {
|
|
|
553
565
|
frontmatter = data;
|
|
554
566
|
}
|
|
555
567
|
else {
|
|
556
|
-
// Optional check against the cited source; a contradiction goes to a human.
|
|
557
|
-
const fidelity = getImproveProcessConfig("distill", run.profile)?.fidelityCheck ?? {};
|
|
558
|
-
if (fidelity.enabled && out.source) {
|
|
559
|
-
try {
|
|
560
|
-
const verdict = checkDistillFidelity(stripFrontmatterBody(content), [stripFrontmatterBody(out.source)], fidelity);
|
|
561
|
-
if (verdict.contradictionDetected) {
|
|
562
|
-
return rejectDistilled(run, out.ref, content, 2.0, verdict.reason ?? "Proposal may contradict cited source memories.", { reviewNeeded: true, fidelityContradiction: true });
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
catch {
|
|
566
|
-
// The fidelity check is supplemental.
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
568
|
// Canonical provenance goes into the content promotion writes.
|
|
570
569
|
const parsed = parseFrontmatter(content);
|
|
571
570
|
const xrefs = Array.isArray(parsed.data.xrefs) ? parsed.data.xrefs.map(String) : [];
|
|
@@ -622,6 +621,129 @@ async function judgeAndQueue(run, out) {
|
|
|
622
621
|
...swapped,
|
|
623
622
|
};
|
|
624
623
|
}
|
|
624
|
+
/**
|
|
625
|
+
* A memory whose lesson repeats a lesson the library holds extends that lesson instead (#1090): the writer returns
|
|
626
|
+
* the lesson's body with the memory's new lines added, and the update is proposed on the lesson's own ref, for a
|
|
627
|
+
* person to review (never staged for the drain). `finding` is what the writer (its NONE reason) or the judge said
|
|
628
|
+
* about the repeat: the related lessons it names are the candidates, or the only related lesson when it names none,
|
|
629
|
+
* so a lesson that merely sits among several near the memory costs no call. Nothing is proposed, and `undefined`
|
|
630
|
+
* returned, when no lesson qualifies, the writer finds the memory adds
|
|
631
|
+
* nothing or contradicts the lesson, the body drops or rewords a line, the judge does not pass the extended lesson,
|
|
632
|
+
* or the lesson changed meanwhile. Needs the quality gate: the judge is what holds the added lines to the memory.
|
|
633
|
+
*/
|
|
634
|
+
async function proposeLessonUpdate(run, feedback, finding, related) {
|
|
635
|
+
const memory = run.asset.content ? parseFrontmatter(run.asset.content).content.trim() : "";
|
|
636
|
+
const said = finding.toLowerCase();
|
|
637
|
+
if (!run.runner || !qualityGateEnabled(run) || !memory)
|
|
638
|
+
return undefined;
|
|
639
|
+
const lessons = [];
|
|
640
|
+
for (const asset of related ?? (await run.related(memory.slice(0, 500), RELATED_COUNT))) {
|
|
641
|
+
const parsed = parseRefInput(asset.ref);
|
|
642
|
+
if (parsed.type !== "lesson" || !asset.path || !isWithin(asset.path, run.stash))
|
|
643
|
+
continue;
|
|
644
|
+
try {
|
|
645
|
+
const content = fs.readFileSync(asset.path, "utf8");
|
|
646
|
+
const ref = conceptIdFromTypeName("lesson", parsed.name);
|
|
647
|
+
lessons.push({ ref, path: asset.path, content, body: parseFrontmatter(content).content.trim() });
|
|
648
|
+
}
|
|
649
|
+
catch {
|
|
650
|
+
// A lesson that cannot be read cannot be extended.
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
const named = lessons.filter((l) => said.includes(l.ref.toLowerCase()) || said.includes(l.ref.split("/").slice(1).join("/").toLowerCase()));
|
|
654
|
+
const candidates = named.length > 0 ? named : lessons.length === 1 ? lessons : [];
|
|
655
|
+
if (candidates.length === 0)
|
|
656
|
+
return undefined;
|
|
657
|
+
const call = await callStage({
|
|
658
|
+
feature: "distill",
|
|
659
|
+
runner: run.runner,
|
|
660
|
+
system: "Return only valid JSON. No prose.",
|
|
661
|
+
prompt: buildLessonUpdatePrompt(memory, feedback, candidates),
|
|
662
|
+
gate: { config: run.config, enabled: true },
|
|
663
|
+
request: {
|
|
664
|
+
...(run.options.chat === undefined
|
|
665
|
+
? { responseSchema: DISTILL_LESSON_UPDATE_JSON_SCHEMA }
|
|
666
|
+
: { chat: run.options.chat }),
|
|
667
|
+
...(run.options.signal ? { signal: run.options.signal } : {}),
|
|
668
|
+
},
|
|
669
|
+
onNotices: run.notices.add,
|
|
670
|
+
});
|
|
671
|
+
const update = call.ok ? parseLessonUpdate(call.raw, candidates) : null;
|
|
672
|
+
if (!update)
|
|
673
|
+
return undefined;
|
|
674
|
+
const target = update.candidate;
|
|
675
|
+
const { dropped, added } = diffLessonBody(target.body, update.body);
|
|
676
|
+
if (dropped.length > 0) {
|
|
677
|
+
warnVerbose(`[akm] distill update of ${target.ref} dropped or reworded ${dropped.length} line(s); not proposed`);
|
|
678
|
+
return undefined;
|
|
679
|
+
}
|
|
680
|
+
if (added.length === 0)
|
|
681
|
+
return undefined;
|
|
682
|
+
// An edit is judged as the lesson it makes: the existing lesson with the lines added, as one lesson (reusable,
|
|
683
|
+
// grounded in the memory or the lesson), whose added lines must each say something the lesson did not (one call).
|
|
684
|
+
const extended = assembleAsset(parseFrontmatter(target.content).data, update.body);
|
|
685
|
+
const verdict = await runLessonQualityJudge(run.config, extended, memory, run.options.chat, {
|
|
686
|
+
extending: target.body,
|
|
687
|
+
...(feedback.length > 0 ? { feedback } : {}),
|
|
688
|
+
...((run.judgeRunner ?? run.runner) ? { llmRunner: run.judgeRunner ?? run.runner } : {}),
|
|
689
|
+
...(run.options.signal ? { signal: run.options.signal } : {}),
|
|
690
|
+
onNotices: run.notices.add,
|
|
691
|
+
});
|
|
692
|
+
if (!verdict.pass) {
|
|
693
|
+
warnVerbose(`[akm] distill update of ${target.ref} not proposed: ${verdict.reason}`);
|
|
694
|
+
return undefined;
|
|
695
|
+
}
|
|
696
|
+
// The proposal's before-hash is taken at mint: a lesson edited since it was read is not what the writer extended.
|
|
697
|
+
let proposal;
|
|
698
|
+
try {
|
|
699
|
+
if (fs.readFileSync(target.path, "utf8") !== target.content)
|
|
700
|
+
return undefined;
|
|
701
|
+
const existing = parseFrontmatter(target.content);
|
|
702
|
+
const xrefs = Array.isArray(existing.data.xrefs) ? existing.data.xrefs.map(String) : [];
|
|
703
|
+
const frontmatter = { ...existing.data, xrefs: [...new Set([...xrefs, run.inputRef])] };
|
|
704
|
+
proposal = mintProposal(run.stash, run.options.ctx, {
|
|
705
|
+
ref: target.ref,
|
|
706
|
+
source: "distill",
|
|
707
|
+
...(run.options.sourceRun !== undefined ? { sourceRun: run.options.sourceRun } : {}),
|
|
708
|
+
payload: { content: assembleAsset(frontmatter, update.body), frontmatter },
|
|
709
|
+
...(verdict.score > 0 ? { confidence: verdict.score / 5 } : {}),
|
|
710
|
+
...(run.options.eligibilitySource ? { eligibilitySource: run.options.eligibilitySource } : {}),
|
|
711
|
+
attemptedRefs: [run.ledgerRef],
|
|
712
|
+
}, {
|
|
713
|
+
review: {
|
|
714
|
+
reason: "distill-update",
|
|
715
|
+
gate: "quality-gate",
|
|
716
|
+
...(verdict.criteria ? { scores: verdict.criteria } : {}),
|
|
717
|
+
judgeReason: verdict.reason,
|
|
718
|
+
},
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
catch (error) {
|
|
722
|
+
warnVerbose(`[akm] distill update of ${target.ref} not proposed: ${error instanceof Error ? error.message : error}`);
|
|
723
|
+
return undefined;
|
|
724
|
+
}
|
|
725
|
+
emitDistill(run, {
|
|
726
|
+
outcome: "queued",
|
|
727
|
+
proposalRef: target.ref,
|
|
728
|
+
proposalKind: "lesson",
|
|
729
|
+
proposalId: proposal.id,
|
|
730
|
+
updatesExisting: true,
|
|
731
|
+
...(run.options.sourceRun !== undefined ? { sourceRun: run.options.sourceRun } : {}),
|
|
732
|
+
...exclusionMeta(run, false),
|
|
733
|
+
});
|
|
734
|
+
return {
|
|
735
|
+
schemaVersion: 1,
|
|
736
|
+
ok: true,
|
|
737
|
+
outcome: "queued",
|
|
738
|
+
inputRef: run.inputRef,
|
|
739
|
+
proposalRef: target.ref,
|
|
740
|
+
proposalKind: "lesson",
|
|
741
|
+
proposalId: proposal.id,
|
|
742
|
+
proposal,
|
|
743
|
+
updatesExisting: true,
|
|
744
|
+
...exclusionMeta(run, true),
|
|
745
|
+
};
|
|
746
|
+
}
|
|
625
747
|
function rejectDistilled(run, proposalRef, content, score, reason, meta) {
|
|
626
748
|
return writeQualityRejection({
|
|
627
749
|
stash: run.stash,
|
|
@@ -863,7 +985,6 @@ function stampInputSalience(run) {
|
|
|
863
985
|
type,
|
|
864
986
|
retrievalFreq: 0,
|
|
865
987
|
encodingSalience: scored.score,
|
|
866
|
-
outcomeWeightEnabled: run.outcomeWeightEnabled,
|
|
867
988
|
})));
|
|
868
989
|
}
|
|
869
990
|
catch {
|
|
@@ -887,7 +1008,6 @@ function persistOutputEncodingSalience(run, ref, body) {
|
|
|
887
1008
|
type,
|
|
888
1009
|
retrievalFreq: 0,
|
|
889
1010
|
encodingSalience: scored.score,
|
|
890
|
-
outcomeWeightEnabled: run.outcomeWeightEnabled,
|
|
891
1011
|
})));
|
|
892
1012
|
}
|
|
893
1013
|
catch {
|
|
@@ -974,7 +1094,7 @@ async function fetchRelatedAssets(query, n) {
|
|
|
974
1094
|
catch {
|
|
975
1095
|
// best-effort
|
|
976
1096
|
}
|
|
977
|
-
return { ref: h.ref, content };
|
|
1097
|
+
return { ref: h.ref, content, ...(h.path ? { path: h.path } : {}) };
|
|
978
1098
|
});
|
|
979
1099
|
}
|
|
980
1100
|
catch {
|
|
@@ -26,7 +26,7 @@ import { akmExtract, resolveStandaloneExtractPlan } from "./extract.js";
|
|
|
26
26
|
export const extractCommand = defineJsonCommand({
|
|
27
27
|
meta: {
|
|
28
28
|
name: "extract",
|
|
29
|
-
description: "
|
|
29
|
+
description: "Extract durable insights from native session files (claude, codex, opencode) and queue them as proposals.",
|
|
30
30
|
},
|
|
31
31
|
args: {
|
|
32
32
|
type: {
|