akm-cli 0.9.0-rc.0 → 0.9.0-rc.2
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 +339 -2
- package/SECURITY.md +23 -24
- package/dist/assets/help/help-improve.md +10 -10
- package/dist/assets/hints/cli-hints-full.md +44 -10
- package/dist/assets/hints/cli-hints-short.md +6 -2
- package/dist/assets/{profiles → improve-strategies}/default.json +1 -0
- package/dist/assets/{profiles → improve-strategies}/graph-refresh.json +1 -1
- package/dist/assets/{profiles → improve-strategies}/proactive-maintenance.json +2 -3
- package/dist/assets/{profiles → improve-strategies}/reflect-distill.json +3 -4
- package/dist/assets/prompts/workflow-unit-preamble.md +26 -0
- package/dist/assets/stash-skeleton/README.md +28 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +12 -1
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +11 -1
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +9 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +7 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +7 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +98 -0
- package/dist/assets/stash-skeleton/facts/conventions/domains.md +63 -0
- package/dist/assets/stash-skeleton/facts/conventions/organization.md +127 -0
- package/dist/assets/tasks/core/backup.yml +1 -0
- package/dist/assets/tasks/core/extract.yml +1 -0
- package/dist/assets/tasks/core/improve.yml +1 -0
- package/dist/assets/tasks/core/index-refresh.yml +1 -0
- package/dist/assets/tasks/core/sync.yml +1 -0
- package/dist/assets/tasks/core/version-check.yml +1 -0
- package/dist/assets/tasks/graph-refresh-weekly.yml +4 -4
- package/dist/assets/templates/html/health.html +5 -1
- package/dist/cli/config-migrate.js +31 -138
- package/dist/cli/config-validate.js +10 -8
- package/dist/cli.js +48 -14
- package/dist/commands/agent/agent-dispatch.js +17 -16
- package/dist/commands/agent/agent-support.js +0 -24
- package/dist/commands/agent/contribute-cli.js +5 -15
- package/dist/commands/backup-cli.js +54 -0
- package/dist/commands/config-cli.js +45 -159
- package/dist/commands/env/env-binding.js +95 -0
- package/dist/commands/env/env-cli.js +8 -65
- package/dist/commands/env/secret.js +8 -5
- package/dist/commands/health/checks.js +130 -83
- package/dist/commands/health/html-report.js +4 -0
- package/dist/commands/health/improve-metrics.js +30 -32
- package/dist/commands/health/llm-usage.js +19 -19
- package/dist/commands/health/md-report.js +4 -0
- package/dist/commands/health/metrics.js +2 -1
- package/dist/commands/health/surfaces.js +5 -4
- package/dist/commands/health.js +1 -1
- package/dist/commands/improve/consolidate/chunking.js +2 -2
- package/dist/commands/improve/consolidate.js +28 -25
- package/dist/commands/improve/distill/promote-memory.js +5 -12
- package/dist/commands/improve/distill/quality-gate.js +5 -7
- package/dist/commands/improve/distill.js +16 -5
- package/dist/commands/improve/eligibility.js +22 -12
- package/dist/commands/improve/extract-cli.js +47 -19
- package/dist/commands/improve/extract.js +110 -62
- package/dist/commands/improve/improve-cli.js +38 -16
- package/dist/commands/improve/improve-result-file.js +30 -24
- package/dist/commands/improve/improve-strategies.js +137 -0
- package/dist/commands/improve/improve.js +60 -30
- package/dist/commands/improve/locks.js +66 -45
- package/dist/commands/improve/loop-stages.js +75 -33
- package/dist/commands/improve/memory/memory-belief.js +79 -7
- package/dist/commands/improve/memory/memory-contradiction-detect.js +12 -4
- package/dist/commands/improve/preparation.js +71 -73
- package/dist/commands/improve/procedural.js +3 -2
- package/dist/commands/improve/recombine.js +2 -1
- package/dist/commands/improve/reflect.js +119 -214
- package/dist/commands/improve/shared.js +11 -5
- package/dist/commands/lint/base-linter.js +152 -42
- package/dist/commands/mv-cli.js +809 -0
- package/dist/commands/proposal/proposal-cli.js +18 -8
- package/dist/commands/proposal/propose.js +64 -69
- package/dist/commands/read/knowledge.js +436 -4
- package/dist/commands/read/remember-cli.js +39 -2
- package/dist/commands/read/search-cli.js +6 -1
- package/dist/commands/registry-cli.js +29 -14
- package/dist/commands/remember.js +2 -0
- package/dist/commands/sources/init.js +13 -14
- package/dist/commands/sources/migration-help.js +7 -4
- package/dist/commands/sources/schema-repair.js +2 -4
- package/dist/commands/sources/source-add.js +62 -73
- package/dist/commands/sources/source-manage.js +50 -46
- package/dist/commands/sources/stash-cli.js +41 -4
- package/dist/commands/tasks/default-tasks.js +12 -12
- package/dist/commands/tasks/tasks-cli.js +7 -3
- package/dist/commands/tasks/tasks.js +113 -18
- package/dist/commands/wiki-cli.js +9 -10
- package/dist/commands/workflow-cli.js +276 -12
- package/dist/core/asset/asset-spec.js +58 -1
- package/dist/core/asset/frontmatter.js +12 -2
- package/dist/core/common.js +5 -3
- package/dist/core/config/config-io.js +28 -17
- package/dist/core/config/config-schema.js +379 -66
- package/dist/core/config/config-types.js +3 -3
- package/dist/core/config/config-version.js +29 -0
- package/dist/core/config/config-walker.js +98 -27
- package/dist/core/config/config.js +132 -266
- package/dist/core/config/deep-merge.js +41 -0
- package/dist/core/config/engine-semantics.js +32 -0
- package/dist/core/errors.js +2 -2
- package/dist/core/extra-params.js +61 -0
- package/dist/core/file-lock.js +201 -56
- package/dist/core/improve-result.js +178 -0
- package/dist/core/json-schema.js +142 -0
- package/dist/core/maintenance-barrier.js +119 -0
- package/dist/core/migration-backup.js +416 -0
- package/dist/core/paths.js +3 -0
- package/dist/core/redaction.js +358 -0
- package/dist/core/state/migrations.js +17 -2
- package/dist/core/state-db.js +44 -1
- package/dist/indexer/db/db.js +118 -2
- package/dist/indexer/graph/graph-extraction.js +28 -16
- package/dist/indexer/index-writer-lock.js +31 -24
- package/dist/indexer/index-written-assets.js +15 -6
- package/dist/indexer/indexer.js +47 -2
- package/dist/indexer/passes/memory-inference.js +10 -6
- package/dist/indexer/passes/metadata.js +250 -0
- package/dist/indexer/search/db-search.js +111 -44
- package/dist/indexer/search/fts-query.js +41 -0
- package/dist/indexer/search/ranking-contributors.js +48 -0
- package/dist/indexer/search/ranking.js +36 -23
- package/dist/indexer/search/search-fields.js +11 -1
- package/dist/indexer/walk/matchers.js +39 -0
- package/dist/integrations/agent/builder-shared.js +7 -0
- package/dist/integrations/agent/builders.js +5 -50
- package/dist/integrations/agent/config.js +3 -143
- package/dist/integrations/agent/detect.js +17 -2
- package/dist/integrations/agent/engine-resolution.js +202 -0
- package/dist/integrations/agent/index.js +1 -2
- package/dist/integrations/agent/model-aliases.js +16 -2
- package/dist/integrations/agent/profiles.js +36 -62
- package/dist/integrations/agent/runner-dispatch.js +91 -4
- package/dist/integrations/agent/runner.js +76 -207
- package/dist/integrations/agent/spawn.js +141 -20
- package/dist/integrations/harnesses/aider/agent-builder.js +112 -0
- package/dist/integrations/harnesses/aider/index.js +57 -0
- package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
- package/dist/integrations/harnesses/amazonq/agent-builder.js +152 -0
- package/dist/integrations/harnesses/amazonq/index.js +58 -0
- package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
- package/dist/integrations/harnesses/claude/agent-builder.js +46 -8
- package/dist/integrations/harnesses/claude/index.js +25 -25
- package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
- package/dist/integrations/harnesses/codex/agent-builder.js +136 -0
- package/dist/integrations/harnesses/codex/index.js +62 -0
- package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
- package/dist/integrations/harnesses/copilot/agent-builder.js +121 -0
- package/dist/integrations/harnesses/copilot/index.js +59 -0
- package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
- package/dist/integrations/harnesses/gemini/agent-builder.js +120 -0
- package/dist/integrations/harnesses/gemini/index.js +59 -0
- package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
- package/dist/integrations/harnesses/index.js +27 -28
- package/dist/integrations/harnesses/opencode/agent-builder.js +2 -3
- package/dist/integrations/harnesses/opencode/index.js +15 -22
- package/dist/integrations/harnesses/opencode-sdk/harness.js +60 -0
- package/dist/integrations/harnesses/opencode-sdk/index.js +8 -32
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +681 -108
- package/dist/integrations/harnesses/openhands/agent-builder.js +125 -0
- package/dist/integrations/harnesses/openhands/index.js +57 -0
- package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
- package/dist/integrations/harnesses/pi/agent-builder.js +103 -0
- package/dist/integrations/harnesses/pi/index.js +57 -0
- package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
- package/dist/integrations/harnesses/types.js +8 -32
- package/dist/integrations/lockfile.js +32 -21
- package/dist/integrations/session-logs/index.js +24 -11
- package/dist/llm/client.js +48 -14
- package/dist/llm/feature-gate.js +15 -47
- package/dist/llm/graph-extract.js +1 -1
- package/dist/llm/index-passes.js +8 -42
- package/dist/llm/memory-infer-impl.js +1 -1
- package/dist/llm/usage-persist.js +4 -0
- package/dist/llm/usage-telemetry.js +35 -5
- package/dist/output/renderers.js +3 -2
- package/dist/output/shapes/helpers.js +2 -1
- package/dist/output/shapes/passthrough.js +6 -0
- package/dist/output/text/helpers.js +215 -2
- package/dist/output/text/workflow.js +3 -1
- package/dist/schemas/akm-config.json +16638 -0
- package/dist/schemas/akm-task.json +87 -0
- package/dist/schemas/akm-workflow.json +372 -0
- package/dist/scripts/migrate-storage.js +10944 -8801
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9247 -350
- package/dist/setup/detected-engines.js +142 -0
- package/dist/setup/engine-config.js +89 -0
- package/dist/setup/setup.js +236 -132
- package/dist/setup/steps/connection.js +61 -32
- package/dist/setup/steps/platforms.js +4 -4
- package/dist/setup/steps.js +3 -2
- package/dist/storage/database.js +13 -1
- package/dist/storage/engines/sqlite-migrations.js +1 -0
- package/dist/storage/repositories/improve-runs-repository.js +5 -5
- package/dist/storage/repositories/task-history-repository.js +78 -0
- package/dist/storage/repositories/workflow-runs-repository.js +190 -1
- package/dist/tasks/parser.js +138 -52
- package/dist/tasks/runner.js +71 -75
- package/dist/tasks/schema.js +1 -1
- package/dist/tasks/validator.js +11 -6
- package/dist/text-import-hook.mjs +1 -1
- package/dist/wiki/wiki.js +9 -8
- package/dist/workflows/authoring/authoring.js +123 -10
- package/dist/workflows/authoring/workflow-program-template.yaml +31 -0
- package/dist/workflows/cli.js +4 -0
- package/dist/workflows/concurrency-policy.js +15 -0
- package/dist/workflows/db.js +200 -13
- package/dist/workflows/exec/brief.js +478 -0
- package/dist/workflows/exec/frozen-judge.js +47 -0
- package/dist/workflows/exec/native-executor.js +1034 -0
- package/dist/workflows/exec/param-secrets.js +115 -0
- package/dist/workflows/exec/report.js +1355 -0
- package/dist/workflows/exec/run-workflow.js +609 -0
- package/dist/workflows/exec/scheduler.js +71 -0
- package/dist/workflows/exec/step-work.js +1212 -0
- package/dist/workflows/exec/unit-writer.js +23 -0
- package/dist/workflows/exec/watch.js +116 -0
- package/dist/workflows/exec/worktree.js +171 -0
- package/dist/workflows/ir/compile.js +375 -0
- package/dist/workflows/ir/freeze.js +243 -0
- package/dist/workflows/ir/params.js +54 -0
- package/dist/workflows/ir/plan-hash.js +68 -0
- package/dist/workflows/ir/schema.js +545 -0
- package/dist/workflows/parser.js +10 -1
- package/dist/workflows/program/expressions.js +369 -0
- package/dist/workflows/program/parser.js +869 -0
- package/dist/workflows/program/project.js +104 -0
- package/dist/workflows/program/schema.js +54 -0
- package/dist/workflows/renderer.js +82 -5
- package/dist/workflows/resource-limits.js +20 -0
- package/dist/workflows/runtime/agent-identity.js +59 -14
- package/dist/workflows/runtime/plan-classifier.js +187 -0
- package/dist/workflows/runtime/runs.js +246 -69
- package/dist/workflows/runtime/unit-checkin.js +45 -0
- package/dist/workflows/runtime/workflow-asset-loader.js +42 -1
- package/dist/workflows/validate-summary.js +24 -3
- package/dist/workflows/validator.js +26 -1
- package/docs/data-and-telemetry.md +4 -3
- package/docs/migration/release-notes/0.6.0.md +1 -1
- package/docs/migration/release-notes/0.7.0.md +5 -4
- package/docs/migration/release-notes/0.9.0-beta.60.md +19 -0
- package/docs/migration/v0.8-to-v0.9.md +401 -0
- package/package.json +4 -2
- package/schemas/akm-config.json +16638 -0
- package/schemas/akm-task.json +87 -0
- package/schemas/akm-workflow.json +372 -0
- package/dist/commands/improve/improve-profiles.js +0 -168
- package/dist/core/config/config-migration.js +0 -602
- package/dist/core/deep-merge.js +0 -38
- package/dist/llm/call-ai.js +0 -62
- package/dist/setup/legacy-config.js +0 -106
- package/docs/README.md +0 -104
- /package/dist/assets/{profiles → improve-strategies}/catchup.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/consolidate.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/frequent.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/memory-focus.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/quick.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/recombine-only.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/synthesize.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/thorough.json +0 -0
|
@@ -29,12 +29,13 @@ import { openStateDatabase, withStateDb } from "../../core/state-db.js";
|
|
|
29
29
|
import { warn } from "../../core/warn.js";
|
|
30
30
|
import { commitWriteTargetBoundary, deleteAssetFromSource, resolveWriteTarget, writeAssetToSource, } from "../../core/write-source.js";
|
|
31
31
|
import { closeDatabase, findEntryIdByRef, getAllEntries, getEntryById, getNeighborsByEntryId, openExistingDatabase, } from "../../indexer/db/db.js";
|
|
32
|
-
import {
|
|
32
|
+
import { materializeLlmRunnerConnection, resolveImproveProcessRunner } from "../../integrations/agent/runner.js";
|
|
33
33
|
import { chatCompletion } from "../../llm/client.js";
|
|
34
34
|
import { cosineSimilarity, embedBatch, resolveEmbeddingModelId } from "../../llm/embedder.js";
|
|
35
|
-
import {
|
|
35
|
+
import { tryLlmFeature } from "../../llm/feature-gate.js";
|
|
36
36
|
import { getConsolidationJudgedMap, upsertConsolidationJudged, } from "../../storage/repositories/consolidation-repository.js";
|
|
37
37
|
import { getBodyEmbeddings, upsertBodyEmbeddings } from "../../storage/repositories/embeddings-repository.js";
|
|
38
|
+
import { resolveImproveStrategy, resolveProcessEnabled } from "./improve-strategies.js";
|
|
38
39
|
// Chunk sizing + per-chunk prompt assembly live in ./consolidate/chunking.
|
|
39
40
|
// Imported for internal use by the orchestrator and re-exported for importers.
|
|
40
41
|
import { buildChunkPrompt, computeSafeChunkSize, DEFAULT_CONTEXT_LENGTH_TOKENS } from "./consolidate/chunking.js";
|
|
@@ -493,13 +494,13 @@ function archiveMemory(filePath, stashDir, ref, reason, opIndex, supersededBy, w
|
|
|
493
494
|
*
|
|
494
495
|
* Priority order (mirrors extract / reflect / distill — see
|
|
495
496
|
* `src/commands/extract.ts:421-438` and the canonical
|
|
496
|
-
* `
|
|
497
|
+
* `resolveImproveProcessRunner` pattern):
|
|
497
498
|
*
|
|
498
|
-
* 1. `
|
|
499
|
-
* via {@link
|
|
499
|
+
* 1. `improve.strategies.<name>.processes.consolidate.engine`
|
|
500
|
+
* via {@link resolveImproveProcessRunner}. Lets the user pin
|
|
500
501
|
* a dedicated model (e.g. `ministral-3b`) for consolidation instead of
|
|
501
|
-
* whatever `defaults.
|
|
502
|
-
* 2. `getDefaultLlmConfig(config)` — the baseline default LLM
|
|
502
|
+
* whatever `defaults.llmEngine` happens to be.
|
|
503
|
+
* 2. `getDefaultLlmConfig(config)` — the baseline default LLM engine.
|
|
503
504
|
*
|
|
504
505
|
* Regression guard (2026-05-26): before this resolver, `akmConsolidate`
|
|
505
506
|
* called `getDefaultLlmConfig` directly and silently ignored a configured
|
|
@@ -509,13 +510,9 @@ function archiveMemory(filePath, stashDir, ref, reason, opIndex, supersededBy, w
|
|
|
509
510
|
* `/tmp/akm-health-investigations/consolidation-no-op.md`.
|
|
510
511
|
*/
|
|
511
512
|
function resolveConsolidateLlmConfig(config, activeProfile) {
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
return runnerSpec.connection;
|
|
516
|
-
}
|
|
517
|
-
// Non-LLM runner modes (agent/sdk) don't apply to consolidate's HTTP path;
|
|
518
|
-
// fall back to the default LLM profile rather than disabling the pass.
|
|
513
|
+
const runnerSpec = resolveImproveProcessRunner(activeProfile, "consolidate", config);
|
|
514
|
+
if (runnerSpec)
|
|
515
|
+
return materializeLlmRunnerConnection(runnerSpec);
|
|
519
516
|
return getDefaultLlmConfig(config);
|
|
520
517
|
}
|
|
521
518
|
// ── Judged-state cache (#581) ────────────────────────────────────────────────
|
|
@@ -578,8 +575,9 @@ export async function akmConsolidate(opts = {}) {
|
|
|
578
575
|
// standalone `akm consolidate` gets a self-contained token.
|
|
579
576
|
const sourceRun = opts.sourceRun ?? `consolidate-${startMs}`;
|
|
580
577
|
const config = opts.config ?? loadConfig();
|
|
578
|
+
opts = { ...opts, improveProfile: opts.improveProfile ?? resolveImproveStrategy(undefined, config).config };
|
|
581
579
|
const stashDir = opts.stashDir ?? resolveStashDir();
|
|
582
|
-
if (!
|
|
580
|
+
if (!resolveProcessEnabled("consolidate", opts.improveProfile ?? resolveImproveStrategy(undefined, config).config)) {
|
|
583
581
|
return makeConsolidateResult({
|
|
584
582
|
dryRun: opts.dryRun ?? false,
|
|
585
583
|
target: opts.target ?? stashDir,
|
|
@@ -868,9 +866,11 @@ async function planConsolidation(opts, config, stashDir, startMs, memories, warn
|
|
|
868
866
|
// CLI. The agent CLI is for interactive agent sessions (reflect, propose);
|
|
869
867
|
// structured JSON generation works better and faster via HTTP.
|
|
870
868
|
//
|
|
871
|
-
//
|
|
872
|
-
//
|
|
873
|
-
const llmConfig =
|
|
869
|
+
// Improve supplies a frozen connection; standalone consolidate resolves its
|
|
870
|
+
// selected strategy/default engine here.
|
|
871
|
+
const llmConfig = Object.hasOwn(opts, "llmConfig")
|
|
872
|
+
? (opts.llmConfig ?? undefined)
|
|
873
|
+
: resolveConsolidateLlmConfig(config, opts.improveProfile);
|
|
874
874
|
const isHttpPath = !!llmConfig;
|
|
875
875
|
// Chunk sizing: derive a safe chunk size from the configured model context
|
|
876
876
|
// window so that the full prompt (system prompt + chunk user prompt) never
|
|
@@ -1087,7 +1087,7 @@ async function planConsolidation(opts, config, stashDir, startMs, memories, warn
|
|
|
1087
1087
|
catch (e) {
|
|
1088
1088
|
return { ok: false, error: String(e) };
|
|
1089
1089
|
}
|
|
1090
|
-
}, { ok: false, error: fallbackError });
|
|
1090
|
+
}, { ok: false, error: fallbackError }, { enabled: true });
|
|
1091
1091
|
let raw = await callChunkLlm(`chunk ${chunkIdx + 1} failed`);
|
|
1092
1092
|
if (!raw.ok) {
|
|
1093
1093
|
// Single retry with 2s backoff before recording chunk as lost.
|
|
@@ -1230,7 +1230,7 @@ async function planConsolidation(opts, config, stashDir, startMs, memories, warn
|
|
|
1230
1230
|
* the op-handlers' `pushSkipReason`. Behavior-identical to the former inlined
|
|
1231
1231
|
* write block. Never invoked on the dry-run or aborted-confirm paths.
|
|
1232
1232
|
*/
|
|
1233
|
-
async function applyConsolidationPlan(config, stashDir, sourceRun, memories, warnings, allOps, accounting, dedupCollapsed, activeProfile) {
|
|
1233
|
+
async function applyConsolidationPlan(config, stashDir, sourceRun, memories, warnings, allOps, accounting, dedupCollapsed, activeProfile, llmConfig) {
|
|
1234
1234
|
// -- Phase B + writes -------------------------------------------------------
|
|
1235
1235
|
const target = resolveWriteTarget(config);
|
|
1236
1236
|
const timestamp = timestampForFilename();
|
|
@@ -1258,6 +1258,7 @@ async function applyConsolidationPlan(config, stashDir, sourceRun, memories, war
|
|
|
1258
1258
|
const opCtx = {
|
|
1259
1259
|
config,
|
|
1260
1260
|
improveProfile: activeProfile,
|
|
1261
|
+
llmConfig: llmConfig ?? null,
|
|
1261
1262
|
stashDir,
|
|
1262
1263
|
sourceRun,
|
|
1263
1264
|
target,
|
|
@@ -1380,7 +1381,9 @@ async function akmConsolidateInner(opts, config, stashDir, startMs, sourceRun, w
|
|
|
1380
1381
|
}
|
|
1381
1382
|
}
|
|
1382
1383
|
// -- Pass 3: execute the plan against the filesystem ------------------------
|
|
1383
|
-
const { merged, deleted, contradicted, mergeFloorViolations, mergedSecondaries, promoted } = await applyConsolidationPlan(config, stashDir, sourceRun, memories, warnings, allOps, accounting, dedupCollapsed, opts.improveProfile)
|
|
1384
|
+
const { merged, deleted, contradicted, mergeFloorViolations, mergedSecondaries, promoted } = await applyConsolidationPlan(config, stashDir, sourceRun, memories, warnings, allOps, accounting, dedupCollapsed, opts.improveProfile, Object.hasOwn(opts, "llmConfig")
|
|
1385
|
+
? (opts.llmConfig ?? null)
|
|
1386
|
+
: (resolveConsolidateLlmConfig(config, opts.improveProfile) ?? null));
|
|
1384
1387
|
const runDurationMs = Date.now() - startMs;
|
|
1385
1388
|
const budgetFraction = opts.runBudgetMs !== undefined && opts.runBudgetMs > 0 ? runDurationMs / opts.runBudgetMs : undefined;
|
|
1386
1389
|
return {
|
|
@@ -1502,7 +1505,7 @@ export async function handleMergeOp(op, opIndex, ctx) {
|
|
|
1502
1505
|
emitMergeFailureSkips("merge_read_failed");
|
|
1503
1506
|
return;
|
|
1504
1507
|
}
|
|
1505
|
-
const mergeResult = await generateMergedContent(config, op.primary, primaryBody, op.secondaries, memoryByRef, ctx.improveProfile);
|
|
1508
|
+
const mergeResult = await generateMergedContent(config, op.primary, primaryBody, op.secondaries, memoryByRef, ctx.improveProfile, ctx.llmConfig);
|
|
1506
1509
|
if ("error" in mergeResult) {
|
|
1507
1510
|
warnings.push(`Merge: ${mergeResult.error} for ${mergeResult.detail}.`);
|
|
1508
1511
|
emitMergeFailureSkips(mergeResult.error);
|
|
@@ -2147,7 +2150,7 @@ function loadMemoriesForSource(source, stashDir, warnings) {
|
|
|
2147
2150
|
}
|
|
2148
2151
|
return memories;
|
|
2149
2152
|
}
|
|
2150
|
-
async function generateMergedContent(config, primaryRef, primaryBody, secondaryRefs, memoryByRef, activeProfile) {
|
|
2153
|
+
async function generateMergedContent(config, primaryRef, primaryBody, secondaryRefs, memoryByRef, activeProfile, resolvedLlmConfig) {
|
|
2151
2154
|
// Only handle single-secondary merges per design (one call per merge op)
|
|
2152
2155
|
const secRef = secondaryRefs[0];
|
|
2153
2156
|
const secEntry = memoryByRef.get(secRef);
|
|
@@ -2191,7 +2194,7 @@ async function generateMergedContent(config, primaryRef, primaryBody, secondaryR
|
|
|
2191
2194
|
.join("\n");
|
|
2192
2195
|
// Use the same per-process profile resolution as the chunk-plan call above
|
|
2193
2196
|
// so the merge generation step doesn't silently revert to the default LLM.
|
|
2194
|
-
const llmConfig = resolveConsolidateLlmConfig(config, activeProfile);
|
|
2197
|
+
const llmConfig = resolvedLlmConfig === null ? undefined : (resolvedLlmConfig ?? resolveConsolidateLlmConfig(config, activeProfile));
|
|
2195
2198
|
const result = await tryLlmFeature("memory_consolidation", config, async () => {
|
|
2196
2199
|
if (!llmConfig)
|
|
2197
2200
|
return { ok: false, error: "No LLM configured for consolidation" };
|
|
@@ -2204,7 +2207,7 @@ async function generateMergedContent(config, primaryRef, primaryBody, secondaryR
|
|
|
2204
2207
|
catch (e) {
|
|
2205
2208
|
return { ok: false, error: String(e) };
|
|
2206
2209
|
}
|
|
2207
|
-
}, { ok: false, error: `merge content generation failed for ${primaryRef}` });
|
|
2210
|
+
}, { ok: false, error: `merge content generation failed for ${primaryRef}` }, { enabled: true });
|
|
2208
2211
|
if (!result.ok) {
|
|
2209
2212
|
return {
|
|
2210
2213
|
error: "merge_transport_failed",
|
|
@@ -18,11 +18,8 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import fs from "node:fs";
|
|
20
20
|
import { parseFrontmatter } from "../../../core/asset/frontmatter.js";
|
|
21
|
-
import { getDefaultLlmConfig } from "../../../core/config/config.js";
|
|
22
|
-
import { ConfigError } from "../../../core/errors.js";
|
|
23
21
|
import { appendEvent } from "../../../core/events.js";
|
|
24
22
|
import { parseEmbeddedJsonResponse } from "../../../llm/client.js";
|
|
25
|
-
import { isLlmFeatureEnabled } from "../../../llm/feature-gate.js";
|
|
26
23
|
import { createProposal, isProposalSkipped } from "../../proposal/repository.js";
|
|
27
24
|
import { assessMemoryKnowledgePromotionCandidate } from "../distill-promotion-policy.js";
|
|
28
25
|
import { persistOutputEncodingSalience, runLessonQualityJudge, writeQualityRejection } from "./quality-gate.js";
|
|
@@ -62,7 +59,7 @@ export async function promoteMemoryToKnowledge(ctx) {
|
|
|
62
59
|
}
|
|
63
60
|
})()
|
|
64
61
|
: null;
|
|
65
|
-
if (existingKnowledgeContent &&
|
|
62
|
+
if (existingKnowledgeContent && ctx.llmConfig) {
|
|
66
63
|
// Existing content found: call LLM for contradiction-resolution merge.
|
|
67
64
|
const mergePrompt = [
|
|
68
65
|
"You are merging two versions of a knowledge document.",
|
|
@@ -81,11 +78,7 @@ export async function promoteMemoryToKnowledge(ctx) {
|
|
|
81
78
|
"```",
|
|
82
79
|
].join("\n");
|
|
83
80
|
try {
|
|
84
|
-
const
|
|
85
|
-
if (!mergeLlm) {
|
|
86
|
-
throw new ConfigError("LLM is not configured for distillation merge.", "LLM_NOT_CONFIGURED");
|
|
87
|
-
}
|
|
88
|
-
const mergeResponse = await chat(mergeLlm, [
|
|
81
|
+
const mergeResponse = await chat(ctx.llmConfig, [
|
|
89
82
|
{ role: "system", content: "Return only valid JSON. No prose." },
|
|
90
83
|
{ role: "user", content: mergePrompt },
|
|
91
84
|
]);
|
|
@@ -122,7 +115,7 @@ export async function promoteMemoryToKnowledge(ctx) {
|
|
|
122
115
|
// The reviewer will see both versions in the proposal diff.
|
|
123
116
|
}
|
|
124
117
|
}
|
|
125
|
-
else if (existingKnowledgeContent
|
|
118
|
+
else if (existingKnowledgeContent) {
|
|
126
119
|
// No LLM configured: include existing content as context in the proposal
|
|
127
120
|
// so the reviewer can do the contradiction resolution manually.
|
|
128
121
|
resolvedPromotionContent = [
|
|
@@ -141,10 +134,10 @@ export async function promoteMemoryToKnowledge(ctx) {
|
|
|
141
134
|
// D-5 / #388: Three-band system — review_needed band queues to proposal
|
|
142
135
|
// queue with review_needed outcome rather than auto-rejecting.
|
|
143
136
|
let knowledgeJudgeConfidence;
|
|
144
|
-
if (
|
|
137
|
+
if (ctx.strategy?.processes?.distill?.qualityGate?.enabled ?? true) {
|
|
145
138
|
// D-4 / #390: retrieve top-3 similar lessons for dedup check in judge.
|
|
146
139
|
const similarLessons = await fetchSimilarLessonsFn(resolvedPromotionContent.slice(0, 500), 3);
|
|
147
|
-
const judgeResult = await runLessonQualityJudge(config, resolvedPromotionContent, assetContent ?? "", chat, similarLessons.length > 0 ? similarLessons : undefined);
|
|
140
|
+
const judgeResult = await runLessonQualityJudge(config, resolvedPromotionContent, assetContent ?? "", chat, similarLessons.length > 0 ? similarLessons : undefined, ctx.llmConfig);
|
|
148
141
|
if (!judgeResult.pass) {
|
|
149
142
|
if (judgeResult.reviewNeeded) {
|
|
150
143
|
// Uncertainty band (2.5–3.5): queue as review_needed instead of rejecting.
|
|
@@ -102,10 +102,8 @@ export function buildJudgePrompt(lessonContent, sourceContent, similarLessons) {
|
|
|
102
102
|
* Run the LLM-as-judge quality gate on a proposal's content.
|
|
103
103
|
*
|
|
104
104
|
* Exported so reflect.ts can apply the same gate to reflect proposals (R-5 / #374).
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* `profiles.improve.default.processes.distill.qualityGate.enabled` (and the
|
|
108
|
-
* corresponding `.reflect.qualityGate.enabled` for proposals).
|
|
105
|
+
* The selected strategy's distill/reflect quality-gate setting is resolved by
|
|
106
|
+
* the caller before this function runs.
|
|
109
107
|
*
|
|
110
108
|
* Fail-CLOSED (07 P0-2): returns `pass: false` (score -1) on timeout, parse
|
|
111
109
|
* failure, or missing LLM. Minted content that cannot be judged is rejected,
|
|
@@ -114,12 +112,12 @@ export function buildJudgePrompt(lessonContent, sourceContent, similarLessons) {
|
|
|
114
112
|
*/
|
|
115
113
|
export async function runLessonQualityJudge(config, lessonContent, sourceContent, chat,
|
|
116
114
|
/** D-4 / #390: top-3 similar existing lessons for dedup check. */
|
|
117
|
-
similarLessons) {
|
|
118
|
-
const llmConfig = getDefaultLlmConfig(config);
|
|
115
|
+
similarLessons, llmConfigOverride) {
|
|
116
|
+
const llmConfig = llmConfigOverride ?? getDefaultLlmConfig(config);
|
|
119
117
|
if (!llmConfig) {
|
|
120
118
|
return { pass: false, score: -1, reason: "no LLM configured — cannot judge, failing closed" };
|
|
121
119
|
}
|
|
122
|
-
const judgeLlmConfig = llmConfig
|
|
120
|
+
const judgeLlmConfig = llmConfig;
|
|
123
121
|
const JUDGE_TIMEOUT_MS = 8_000;
|
|
124
122
|
try {
|
|
125
123
|
const raw = await Promise.race([
|
|
@@ -69,8 +69,9 @@ import { withStateDb } from "../../core/state-db.js";
|
|
|
69
69
|
import { warnVerbose } from "../../core/warn.js";
|
|
70
70
|
import { closeDatabase, getAllEntries, openIndexDatabase } from "../../indexer/db/db.js";
|
|
71
71
|
import { resolveAssetPath } from "../../indexer/walk/path-resolver.js";
|
|
72
|
+
import { materializeLlmRunnerConnection, resolveImproveProcessRunner } from "../../integrations/agent/runner.js";
|
|
72
73
|
import { chatCompletion, parseEmbeddedJsonResponse } from "../../llm/client.js";
|
|
73
|
-
import {
|
|
74
|
+
import { tryLlmFeature } from "../../llm/feature-gate.js";
|
|
74
75
|
import { createProposal, isProposalSkipped, listProposals, } from "../proposal/repository.js";
|
|
75
76
|
import { stripFrontmatterBody as stripBodyForFidelity } from "./dedup.js";
|
|
76
77
|
import { autoRepairLessonFrontmatter, autoSwapDescriptionWhenToUse, collectLessonQualityFindings, repairLessonDescriptionTruncation, } from "./distill/content-repair.js";
|
|
@@ -79,6 +80,7 @@ import { fetchTopSimilarLessons, persistOutputEncodingSalience, runLessonQuality
|
|
|
79
80
|
import { buildClsContext, checkDistillFidelity } from "./distill-guards.js";
|
|
80
81
|
import { deriveKnowledgeRef } from "./distill-promotion-policy.js";
|
|
81
82
|
import { buildRefVocabulary, scoreEncodingSalience } from "./encoding-salience.js";
|
|
83
|
+
import { resolveImproveStrategy, resolveProcessEnabled } from "./improve-strategies.js";
|
|
82
84
|
import { computeSalience, upsertAssetSalience } from "./salience.js";
|
|
83
85
|
// Re-exported for `reflect.ts`, which applies the same LLM-as-judge gate to
|
|
84
86
|
// reflect proposals (R-5 / #374).
|
|
@@ -474,8 +476,15 @@ export async function akmDistill(options) {
|
|
|
474
476
|
};
|
|
475
477
|
}
|
|
476
478
|
const config = options.config ?? loadConfig();
|
|
479
|
+
options = { ...options, improveProfile: options.improveProfile ?? resolveImproveStrategy(undefined, config).config };
|
|
477
480
|
const stash = options.stashDir ?? resolveStashDir();
|
|
478
481
|
const chat = options.chat ?? chatCompletion;
|
|
482
|
+
const distillLlm = Object.hasOwn(options, "llmConfig")
|
|
483
|
+
? (options.llmConfig ?? undefined)
|
|
484
|
+
: (() => {
|
|
485
|
+
const runner = resolveImproveProcessRunner(options.improveProfile, "distill", config);
|
|
486
|
+
return runner ? materializeLlmRunnerConnection(runner) : getDefaultLlmConfig(config);
|
|
487
|
+
})();
|
|
479
488
|
const lookup = options.lookupFn ?? defaultLookup;
|
|
480
489
|
const readEventsImpl = options.readEventsFn ?? readEvents;
|
|
481
490
|
// R1 opt-out must flow into every computeSalience call this command makes so
|
|
@@ -602,6 +611,8 @@ export async function akmDistill(options) {
|
|
|
602
611
|
assetContent,
|
|
603
612
|
filteredEvents,
|
|
604
613
|
config,
|
|
614
|
+
strategy: options.improveProfile,
|
|
615
|
+
llmConfig: distillLlm,
|
|
605
616
|
chat,
|
|
606
617
|
stash,
|
|
607
618
|
lookup,
|
|
@@ -684,12 +695,11 @@ export async function akmDistill(options) {
|
|
|
684
695
|
const distillSchema = effectiveProposalKind === "knowledge" ? DISTILL_KNOWLEDGE_JSON_SCHEMA : DISTILL_LESSON_JSON_SCHEMA;
|
|
685
696
|
let fallbackReason;
|
|
686
697
|
const raw = await tryLlmFeature("distill", config, async () => {
|
|
687
|
-
const distillLlm = getDefaultLlmConfig(config);
|
|
688
698
|
if (!distillLlm) {
|
|
689
699
|
// No LLM connection configured — treat as gate-disabled. Throwing
|
|
690
700
|
// here lets `tryLlmFeature` route us through the "error" fallback,
|
|
691
701
|
// which is the same graceful skipped path.
|
|
692
|
-
throw new ConfigError("No LLM
|
|
702
|
+
throw new ConfigError("No LLM engine configured. Set defaults.llmEngine or improve.strategies.<name>.processes.distill.engine.", "LLM_NOT_CONFIGURED");
|
|
693
703
|
}
|
|
694
704
|
// Production path: pass the JSON schema so providers that honour
|
|
695
705
|
// `response_format: json_schema` enforce shape upstream. Providers that
|
|
@@ -701,6 +711,7 @@ export async function akmDistill(options) {
|
|
|
701
711
|
// functions (which return markdown strings) continue to work.
|
|
702
712
|
return chat(distillLlm, messages);
|
|
703
713
|
}, null, {
|
|
714
|
+
enabled: resolveProcessEnabled("distill", options.improveProfile ?? resolveImproveStrategy(undefined, config).config),
|
|
704
715
|
onFallback: (evt) => {
|
|
705
716
|
fallbackReason = evt.reason;
|
|
706
717
|
// Log the fallback reason; the caller (raw === null path) handles
|
|
@@ -822,10 +833,10 @@ export async function akmDistill(options) {
|
|
|
822
833
|
// D-5 / #388: Three-band system — review_needed band queues a proposal
|
|
823
834
|
// with review_needed outcome rather than auto-rejecting.
|
|
824
835
|
let lessonJudgeConfidence;
|
|
825
|
-
if (
|
|
836
|
+
if (options.improveProfile?.processes?.distill?.qualityGate?.enabled ?? true) {
|
|
826
837
|
// D-4 / #390: retrieve top-3 similar lessons for dedup check in judge.
|
|
827
838
|
const similarLessons = await fetchSimilarLessonsFn(content.slice(0, 500), 3);
|
|
828
|
-
const judgeResult = await runLessonQualityJudge(config, content, assetContent ?? "", chat, similarLessons.length > 0 ? similarLessons : undefined);
|
|
839
|
+
const judgeResult = await runLessonQualityJudge(config, content, assetContent ?? "", chat, similarLessons.length > 0 ? similarLessons : undefined, distillLlm);
|
|
829
840
|
if (!judgeResult.pass) {
|
|
830
841
|
if (judgeResult.reviewNeeded) {
|
|
831
842
|
return writeQualityRejection(stash, inputRef, effectiveLessonRef, content, judgeResult.score, judgeResult.reason, {
|
|
@@ -8,11 +8,11 @@ import { parseFrontmatter } from "../../core/asset/frontmatter.js";
|
|
|
8
8
|
import { isAssetType } from "../../core/common.js";
|
|
9
9
|
import { NotFoundError, rethrowIfTestIsolationError, UsageError } from "../../core/errors.js";
|
|
10
10
|
import { readEvents } from "../../core/events.js";
|
|
11
|
-
import { closeDatabase, getAllEntries, getUtilityScoresByIds, openExistingDatabase } from "../../indexer/db/db.js";
|
|
11
|
+
import { closeDatabase, getAllEntries, getUtilityScoresByIds, openExistingDatabase, openReadonlyExistingDatabase, } from "../../indexer/db/db.js";
|
|
12
12
|
import { getWritableStashDirs, resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
13
13
|
import { resolveAssetPath } from "../../indexer/walk/path-resolver.js";
|
|
14
14
|
import { isDistillRefusedInputType } from "./distill.js";
|
|
15
|
-
import {
|
|
15
|
+
import { isStrategyFilteredForAllPasses } from "./improve-strategies.js";
|
|
16
16
|
// Eligibility / candidate-selection predicates for improve. Free functions
|
|
17
17
|
// (no akmImprove closure state) extracted from improve.ts to shrink the
|
|
18
18
|
// orchestrator and make candidate selection independently testable.
|
|
@@ -49,6 +49,13 @@ export function dedupeRefs(refs) {
|
|
|
49
49
|
return out;
|
|
50
50
|
}
|
|
51
51
|
export async function collectEligibleRefs(scope, stashDir, improveProfile) {
|
|
52
|
+
return collectEligibleRefsFromIndex(scope, stashDir, improveProfile, false);
|
|
53
|
+
}
|
|
54
|
+
/** Dry-run planner path: query an existing index without creating or mutating it. */
|
|
55
|
+
export async function collectEligibleRefsReadOnly(scope, stashDir, improveProfile) {
|
|
56
|
+
return collectEligibleRefsFromIndex(scope, stashDir, improveProfile, true);
|
|
57
|
+
}
|
|
58
|
+
async function collectEligibleRefsFromIndex(scope, stashDir, improveProfile, readOnly) {
|
|
52
59
|
if (scope.mode === "ref" && scope.value) {
|
|
53
60
|
const parsed = parseAssetRef(scope.value);
|
|
54
61
|
const writableDirs = new Set(getWritableStashDirs(stashDir).map((dir) => path.resolve(dir)));
|
|
@@ -57,7 +64,7 @@ export async function collectEligibleRefs(scope, stashDir, improveProfile) {
|
|
|
57
64
|
return {
|
|
58
65
|
plannedRefs: [],
|
|
59
66
|
memorySummary: { eligible: 0, derived: 0 },
|
|
60
|
-
|
|
67
|
+
strategyFilteredRefs: [],
|
|
61
68
|
};
|
|
62
69
|
}
|
|
63
70
|
return {
|
|
@@ -66,7 +73,7 @@ export async function collectEligibleRefs(scope, stashDir, improveProfile) {
|
|
|
66
73
|
eligible: parsed.type === "memory" ? 1 : 0,
|
|
67
74
|
derived: parsed.type === "memory" && parsed.name.endsWith(".derived") ? 1 : 0,
|
|
68
75
|
},
|
|
69
|
-
|
|
76
|
+
strategyFilteredRefs: [],
|
|
70
77
|
};
|
|
71
78
|
}
|
|
72
79
|
let sources;
|
|
@@ -74,10 +81,10 @@ export async function collectEligibleRefs(scope, stashDir, improveProfile) {
|
|
|
74
81
|
sources = resolveSourceEntries(stashDir);
|
|
75
82
|
}
|
|
76
83
|
catch {
|
|
77
|
-
return { plannedRefs: [], memorySummary: { eligible: 0, derived: 0 },
|
|
84
|
+
return { plannedRefs: [], memorySummary: { eligible: 0, derived: 0 }, strategyFilteredRefs: [] };
|
|
78
85
|
}
|
|
79
86
|
if (sources.length === 0) {
|
|
80
|
-
return { plannedRefs: [], memorySummary: { eligible: 0, derived: 0 },
|
|
87
|
+
return { plannedRefs: [], memorySummary: { eligible: 0, derived: 0 }, strategyFilteredRefs: [] };
|
|
81
88
|
}
|
|
82
89
|
// Only operate on writable sources — never mutate read-only registry caches
|
|
83
90
|
// or remote stashes that the user did not mark writable.
|
|
@@ -91,7 +98,10 @@ export async function collectEligibleRefs(scope, stashDir, improveProfile) {
|
|
|
91
98
|
const writableDirSet = new Set(writableDirs.map((d) => path.resolve(d)));
|
|
92
99
|
let db;
|
|
93
100
|
try {
|
|
94
|
-
db = openExistingDatabase();
|
|
101
|
+
db = readOnly ? openReadonlyExistingDatabase() : openExistingDatabase();
|
|
102
|
+
if (!db) {
|
|
103
|
+
return { plannedRefs: [], memorySummary: { eligible: 0, derived: 0 }, strategyFilteredRefs: [] };
|
|
104
|
+
}
|
|
95
105
|
const entries = getAllEntries(db, scope.mode === "type" ? scope.value : undefined).filter((indexed) => {
|
|
96
106
|
// First apply the existing stashDir-scope filter (no-op when stashDir is unset).
|
|
97
107
|
if (!isEntryInScope(indexed.stashDir, indexed.filePath, stashDir))
|
|
@@ -117,14 +127,14 @@ export async function collectEligibleRefs(scope, stashDir, improveProfile) {
|
|
|
117
127
|
// 2026-05-27: extend the .derived precedent to profile-incompatible
|
|
118
128
|
// refs. If every per-ref pass (reflect + distill) on the active
|
|
119
129
|
// profile would refuse this ref, drop it from `plannedRefs`. The
|
|
120
|
-
// caller emits `improve_skipped { reason:
|
|
130
|
+
// caller emits `improve_skipped { reason: strategy_filtered_all_passes }`
|
|
121
131
|
// once `eventsCtx` is available so the audit trail is preserved in a
|
|
122
132
|
// single event per ref instead of 2× synthetic actions per run.
|
|
123
133
|
// Background: see /tmp/akm-health-investigations/planner-profile-metrics-deep-analysis.md
|
|
124
|
-
if (improveProfile &&
|
|
134
|
+
if (improveProfile && isStrategyFilteredForAllPasses(ref, improveProfile)) {
|
|
125
135
|
profileFiltered.set(ref, {
|
|
126
136
|
ref,
|
|
127
|
-
reason: "
|
|
137
|
+
reason: "strategy_filtered_all_passes",
|
|
128
138
|
filePath: indexed.filePath,
|
|
129
139
|
});
|
|
130
140
|
}
|
|
@@ -145,14 +155,14 @@ export async function collectEligibleRefs(scope, stashDir, improveProfile) {
|
|
|
145
155
|
return {
|
|
146
156
|
plannedRefs: [...planned.values()],
|
|
147
157
|
memorySummary: { eligible: memoryEligible, derived: memoryDerived },
|
|
148
|
-
|
|
158
|
+
strategyFilteredRefs: [...profileFiltered.values()],
|
|
149
159
|
};
|
|
150
160
|
}
|
|
151
161
|
catch (error) {
|
|
152
162
|
// The bun-test isolation guard must never be downgraded to "empty plan".
|
|
153
163
|
rethrowIfTestIsolationError(error);
|
|
154
164
|
if (error instanceof NotFoundError || error instanceof Error) {
|
|
155
|
-
return { plannedRefs: [], memorySummary: { eligible: 0, derived: 0 },
|
|
165
|
+
return { plannedRefs: [], memorySummary: { eligible: 0, derived: 0 }, strategyFilteredRefs: [] };
|
|
156
166
|
}
|
|
157
167
|
throw error;
|
|
158
168
|
}
|
|
@@ -18,9 +18,10 @@ import fs from "node:fs";
|
|
|
18
18
|
import path from "node:path";
|
|
19
19
|
import { getStringArg } from "../../cli/parse-args.js";
|
|
20
20
|
import { defineJsonCommand, EXIT_CODES, output } from "../../cli/shared.js";
|
|
21
|
+
import { loadConfig } from "../../core/config/config.js";
|
|
21
22
|
import { UsageError } from "../../core/errors.js";
|
|
22
23
|
import { getAvailableHarnesses, getWatchTargets } from "../../integrations/session-logs/index.js";
|
|
23
|
-
import { akmExtract } from "./extract.js";
|
|
24
|
+
import { akmExtract, resolveStandaloneExtractPlan, } from "./extract.js";
|
|
24
25
|
import { akmExtractWatch } from "./extract-watch.js";
|
|
25
26
|
export const extractCommand = defineJsonCommand({
|
|
26
27
|
meta: {
|
|
@@ -61,7 +62,15 @@ export const extractCommand = defineJsonCommand({
|
|
|
61
62
|
},
|
|
62
63
|
"timeout-ms": {
|
|
63
64
|
type: "string",
|
|
64
|
-
description: "Per-session LLM timeout in ms (default
|
|
65
|
+
description: "Per-session LLM timeout in ms (default 600000).",
|
|
66
|
+
},
|
|
67
|
+
engine: {
|
|
68
|
+
type: "string",
|
|
69
|
+
description: "Named LLM engine for this invocation. Mutually exclusive with --strategy.",
|
|
70
|
+
},
|
|
71
|
+
strategy: {
|
|
72
|
+
type: "string",
|
|
73
|
+
description: "Improve strategy supplying extract behavior and engine. Mutually exclusive with --engine.",
|
|
65
74
|
},
|
|
66
75
|
watch: {
|
|
67
76
|
type: "boolean",
|
|
@@ -81,12 +90,17 @@ export const extractCommand = defineJsonCommand({
|
|
|
81
90
|
const auto = args.auto === true;
|
|
82
91
|
const dryRun = args["dry-run"] === true;
|
|
83
92
|
const force = args.force === true;
|
|
93
|
+
const engine = getStringArg(args, "engine");
|
|
94
|
+
const strategy = getStringArg(args, "strategy");
|
|
84
95
|
const timeoutMs = typeof args["timeout-ms"] === "string" && args["timeout-ms"] !== ""
|
|
85
96
|
? Number.parseInt(args["timeout-ms"], 10)
|
|
86
97
|
: undefined;
|
|
87
98
|
if (timeoutMs !== undefined && (!Number.isFinite(timeoutMs) || timeoutMs <= 0)) {
|
|
88
99
|
throw new UsageError(`--timeout-ms must be a positive integer (got "${args["timeout-ms"]}").`, "INVALID_FLAG_VALUE");
|
|
89
100
|
}
|
|
101
|
+
if (engine && strategy) {
|
|
102
|
+
throw new UsageError("--engine and --strategy are mutually exclusive. Pick one.", "INVALID_FLAG_VALUE");
|
|
103
|
+
}
|
|
90
104
|
const watch = args.watch === true;
|
|
91
105
|
const debounceMs = typeof args["debounce-ms"] === "string" && args["debounce-ms"] !== ""
|
|
92
106
|
? Number.parseInt(args["debounce-ms"], 10)
|
|
@@ -94,24 +108,31 @@ export const extractCommand = defineJsonCommand({
|
|
|
94
108
|
if (watch && (!Number.isFinite(debounceMs) || debounceMs <= 0)) {
|
|
95
109
|
throw new UsageError(`--debounce-ms must be a positive integer (got "${args["debounce-ms"]}").`, "INVALID_FLAG_VALUE");
|
|
96
110
|
}
|
|
97
|
-
if (watch) {
|
|
98
|
-
await runWatchMode({ debounceMs, dryRun, force, ...(since ? { since } : {}) });
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
if (auto && type) {
|
|
111
|
+
if (!watch && auto && type) {
|
|
102
112
|
throw new UsageError("--auto and --type are mutually exclusive. Pick one.", "INVALID_FLAG_VALUE");
|
|
103
113
|
}
|
|
104
|
-
if (!auto && !type) {
|
|
114
|
+
if (!watch && !auto && !type) {
|
|
105
115
|
throw new UsageError("--type is required (or pass --auto to try every available harness).", "MISSING_REQUIRED_ARGUMENT");
|
|
106
116
|
}
|
|
107
|
-
const
|
|
117
|
+
const config = loadConfig();
|
|
118
|
+
const resolvedPlan = resolveStandaloneExtractPlan(config, {
|
|
119
|
+
...(engine ? { engine } : {}),
|
|
120
|
+
...(strategy ? { strategy } : {}),
|
|
121
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
122
|
+
});
|
|
123
|
+
if (watch) {
|
|
124
|
+
await runWatchMode({ debounceMs, dryRun, force, config, resolvedPlan, ...(since ? { since } : {}) });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const commonOptions = Object.freeze({
|
|
108
128
|
...(sessionId ? { sessionId } : {}),
|
|
109
129
|
...(location ? { location } : {}),
|
|
110
130
|
...(since ? { since } : {}),
|
|
111
131
|
dryRun,
|
|
112
132
|
force,
|
|
113
|
-
|
|
114
|
-
|
|
133
|
+
config,
|
|
134
|
+
resolvedPlan,
|
|
135
|
+
});
|
|
115
136
|
if (auto) {
|
|
116
137
|
const harnesses = getAvailableHarnesses();
|
|
117
138
|
if (harnesses.length === 0) {
|
|
@@ -196,6 +217,13 @@ function createFsWatchEventSource(roots) {
|
|
|
196
217
|
},
|
|
197
218
|
};
|
|
198
219
|
}
|
|
220
|
+
/** Snapshot the CLI-resolved watch options once and reuse them for every debounced trigger. */
|
|
221
|
+
export function createExtractWatchTrigger(options, extractFn = akmExtract) {
|
|
222
|
+
const snapshot = Object.freeze({ ...options });
|
|
223
|
+
return async (harnessName) => {
|
|
224
|
+
await extractFn({ type: harnessName, ...snapshot });
|
|
225
|
+
};
|
|
226
|
+
}
|
|
199
227
|
/**
|
|
200
228
|
* Run `akm extract --watch`: watch every available harness's session-log
|
|
201
229
|
* roots and run extract (debounced, per-harness) on change. Stays alive until
|
|
@@ -216,18 +244,18 @@ async function runWatchMode(opts) {
|
|
|
216
244
|
}
|
|
217
245
|
const allRoots = targets.flatMap((t) => t.roots);
|
|
218
246
|
const eventSource = createFsWatchEventSource(allRoots);
|
|
247
|
+
const onTrigger = createExtractWatchTrigger({
|
|
248
|
+
dryRun: opts.dryRun,
|
|
249
|
+
force: opts.force,
|
|
250
|
+
config: opts.config,
|
|
251
|
+
resolvedPlan: opts.resolvedPlan,
|
|
252
|
+
...(opts.since ? { since: opts.since } : {}),
|
|
253
|
+
});
|
|
219
254
|
const handle = akmExtractWatch({
|
|
220
255
|
roots: targets,
|
|
221
256
|
eventSource,
|
|
222
257
|
debounceMs: opts.debounceMs,
|
|
223
|
-
onTrigger
|
|
224
|
-
await akmExtract({
|
|
225
|
-
type: harnessName,
|
|
226
|
-
dryRun: opts.dryRun,
|
|
227
|
-
force: opts.force,
|
|
228
|
-
...(opts.since ? { since: opts.since } : {}),
|
|
229
|
-
});
|
|
230
|
-
},
|
|
258
|
+
onTrigger,
|
|
231
259
|
});
|
|
232
260
|
output("extract", {
|
|
233
261
|
schemaVersion: 1,
|