akm-cli 0.9.31 → 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 +253 -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 +87 -102
- 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/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 +41 -11
- 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 +13049 -14013
- package/dist/scripts/akm-migrate.js +16316 -17276
- 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 +1 -1
- package/docs/agents/README.md +3 -3
- package/docs/agents/agent-install.md +1 -1
- package/docs/architecture/adapters.md +17 -31
- package/docs/architecture/architecture.md +4 -5
- package/docs/architecture/improvement.md +52 -21
- package/docs/architecture/internals/improve-workflow.md +15 -8
- package/docs/architecture/internals/indexing.md +1 -1
- package/docs/architecture/internals/search.md +1 -2
- package/docs/architecture/internals/storage-locations.md +5 -5
- package/docs/architecture/persisted-data-compat.md +13 -4
- package/docs/architecture/specs/0.9.0-decisions.md +6 -2
- package/docs/architecture/specs/improve-collapse-churn-detector-design.md +2 -2
- package/docs/architecture/specs/workflow-format-unification.md +1 -6
- package/docs/architecture/testing/manual-testing-checklist.md +20 -29
- package/docs/architecture/testing/task-workflow-manual-tests.md +106 -203
- package/docs/architecture/testing/testing-workflow.md +1 -1
- package/docs/architecture/workflow-engine.md +5 -56
- package/docs/guides/author-workflows.md +39 -69
- package/docs/guides/capture-knowledge.md +1 -1
- package/docs/guides/claude-code-vs-akm-workflows.md +1 -2
- package/docs/guides/discover-and-load.md +4 -4
- package/docs/guides/getting-started.md +1 -1
- package/docs/guides/improve-the-library.md +7 -7
- package/docs/guides/recipes/headless-install.md +2 -2
- package/docs/guides/run-workflows.md +1 -1
- package/docs/guides/use-with-any-agent.md +49 -4
- package/docs/integration/bundling-akm.md +7 -6
- package/docs/maintainers/curate-workmap.md +1 -2
- package/docs/maintainers/release-checklist.md +30 -1
- package/docs/posts/akm-command-tour.md +3 -3
- package/docs/product-surface.md +1 -1
- package/docs/reference/README.md +1 -0
- package/docs/reference/api.md +65 -0
- package/docs/reference/asset-types.md +3 -5
- package/docs/reference/bundle-types.md +2 -4
- package/docs/reference/cli.md +126 -171
- package/docs/reference/configuration.md +84 -19
- package/docs/reference/data-and-telemetry.md +1 -1
- package/docs/reference/env-and-secrets.md +2 -2
- package/docs/reference/supported-formats.md +3 -4
- package/docs/reference/tasks.md +12 -19
- package/docs/reference/website-sources.md +1 -1
- package/docs/reference/workflow-schema.md +89 -317
- package/docs/reference/workflows.md +20 -48
- package/package.json +12 -2
- 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
|
@@ -11,6 +11,8 @@ import { withEngineFallback } from "../../integrations/agent/engine-fallback.js"
|
|
|
11
11
|
import { isLlmCredentialAvailable, resolveEngine, } from "../../integrations/agent/engine-resolution.js";
|
|
12
12
|
import { executionEngineDefinitionsFromConfig } from "../../integrations/agent/execution.js";
|
|
13
13
|
import { loadModelMap, mergeModelMapLayers, parseModelMapLayer, readInstalledModelMapText, resolveModelMapAlias, userModelMapPath, } from "../../integrations/agent/model-map.js";
|
|
14
|
+
import { detectOpencodeMajor, resolveOpencodeBin, } from "../../integrations/harnesses/opencode/version.js";
|
|
15
|
+
import { OPENCODE_CLIENT_PACKAGE } from "../../integrations/harnesses/opencode-sdk/wire.js";
|
|
14
16
|
import { probeEndpointOnce } from "../../llm/client.js";
|
|
15
17
|
import { STATE_DB_FREELIST_WARN_RATIO, } from "../../storage/state-db-integrity.js";
|
|
16
18
|
import { listKeys } from "../env/env.js";
|
|
@@ -116,20 +118,15 @@ async function runConfiguredEngineProbe(checkName, engineName, config, deps, rea
|
|
|
116
118
|
const env = deps.env ?? process.env;
|
|
117
119
|
const configuredEngine = config.engines?.[engineName];
|
|
118
120
|
if (configuredEngine?.kind === "agent" && configuredEngine.platform === "opencode-sdk") {
|
|
119
|
-
|
|
121
|
+
const binary = resolveOpencodeBin(configuredEngine.bin);
|
|
122
|
+
let detected;
|
|
120
123
|
try {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
const binary = configuredEngine.bin ?? "opencode";
|
|
129
|
-
let binaryAvailable;
|
|
130
|
-
try {
|
|
131
|
-
const version = (deps.spawnSync ?? spawnSync)(binary, ["--version"], { encoding: "utf8", timeout: 5_000 });
|
|
132
|
-
binaryAvailable = (version.status ?? 1) === 0;
|
|
124
|
+
detected = detectOpencodeMajor(binary, {
|
|
125
|
+
probe: (bin) => {
|
|
126
|
+
const run = (deps.spawnSync ?? spawnSync)(bin, ["--version"], { encoding: "utf8", timeout: 5_000 });
|
|
127
|
+
return (run.status ?? 1) === 0 ? String(run.stdout ?? "").trim() : undefined;
|
|
128
|
+
},
|
|
129
|
+
});
|
|
133
130
|
}
|
|
134
131
|
catch {
|
|
135
132
|
return {
|
|
@@ -141,6 +138,18 @@ async function runConfiguredEngineProbe(checkName, engineName, config, deps, rea
|
|
|
141
138
|
evidence: { engine: engineName, runtimeKind: "sdk", binaryAvailable: false },
|
|
142
139
|
};
|
|
143
140
|
}
|
|
141
|
+
const binaryAvailable = detected.runnable;
|
|
142
|
+
// The client package of the OpenCode major the binary reports; never the other major's.
|
|
143
|
+
const clientPackage = OPENCODE_CLIENT_PACKAGE[detected.major];
|
|
144
|
+
let packageAvailable = false;
|
|
145
|
+
try {
|
|
146
|
+
const resolvePackage = deps.resolvePackage ?? ((name) => import.meta.resolve(name));
|
|
147
|
+
resolvePackage(clientPackage);
|
|
148
|
+
packageAvailable = true;
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
packageAvailable = false;
|
|
152
|
+
}
|
|
144
153
|
const fallbackEngine = configuredEngine.llmEngine;
|
|
145
154
|
let fallback;
|
|
146
155
|
let fallbackCredential;
|
|
@@ -183,7 +192,7 @@ async function runConfiguredEngineProbe(checkName, engineName, config, deps, rea
|
|
|
183
192
|
? findSuppliedByEnvAsset(fallbackCredential, deps)
|
|
184
193
|
: null;
|
|
185
194
|
const missing = [
|
|
186
|
-
!packageAvailable ?
|
|
195
|
+
!packageAvailable ? `${clientPackage} package` : undefined,
|
|
187
196
|
!binaryAvailable ? `${binary} binary` : undefined,
|
|
188
197
|
fallbackEngine && !fallback ? "configured fallback LLM connection" : undefined,
|
|
189
198
|
!fallbackCredentialAvailable
|
|
@@ -198,7 +207,9 @@ async function runConfiguredEngineProbe(checkName, engineName, config, deps, rea
|
|
|
198
207
|
runtimeKind: "sdk",
|
|
199
208
|
binary,
|
|
200
209
|
binaryAvailable,
|
|
201
|
-
package:
|
|
210
|
+
package: clientPackage,
|
|
211
|
+
detectedVersion: detected.version || null,
|
|
212
|
+
detectedMajor: detected.major,
|
|
202
213
|
packageAvailable,
|
|
203
214
|
model: effectiveModel ?? null,
|
|
204
215
|
configuredModel: configuredModel ?? null,
|
|
@@ -533,7 +544,21 @@ export function probeActiveImproveStrategy(deps = {}) {
|
|
|
533
544
|
const requiredModelProcessNames = modelProcessNames.filter((name) => plan.processes[name].enabled || plan.engineUnavailable.some((item) => item.process === name));
|
|
534
545
|
const availableModelProcessNames = modelProcessNames.filter((name) => plan.processes[name].enabled);
|
|
535
546
|
const allRequiredUnavailable = requiredModelProcessNames.length > 0 && availableModelProcessNames.length === 0;
|
|
536
|
-
|
|
547
|
+
// No engine configured at all (no unavailable item carries a resolved engine)
|
|
548
|
+
// is "not set up yet", not a broken credential: it keeps the unknown/warn
|
|
549
|
+
// result it had when the plan threw for it. Engine-free deterministic triage
|
|
550
|
+
// (#1143) now lets such a plan resolve, so this has to be explicit.
|
|
551
|
+
const noEngineConfigured = plan.engineUnavailable.length > 0 && plan.engineUnavailable.every((i) => !i.engine);
|
|
552
|
+
const explicitlyConfigured = config.defaults?.improveStrategy !== undefined || Object.keys(config.improve?.strategies ?? {}).length > 0;
|
|
553
|
+
const status = unavailableProcesses.length === 0
|
|
554
|
+
? "pass"
|
|
555
|
+
: allRequiredUnavailable
|
|
556
|
+
? noEngineConfigured
|
|
557
|
+
? explicitlyConfigured
|
|
558
|
+
? "warn"
|
|
559
|
+
: "unknown"
|
|
560
|
+
: "fail"
|
|
561
|
+
: "warn";
|
|
537
562
|
return {
|
|
538
563
|
check: {
|
|
539
564
|
name: "active-improve-strategy",
|
|
@@ -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
|
-
}
|