akm-cli 0.9.0-rc.1 → 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 +223 -9
- 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/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/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/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/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 +358 -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/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 +116 -1
- 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/integrations/agent/builder-shared.js +7 -0
- package/dist/integrations/agent/builders.js +5 -52
- 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 +7 -2
- package/dist/integrations/agent/profiles.js +6 -99
- package/dist/integrations/agent/runner-dispatch.js +76 -13
- package/dist/integrations/agent/runner.js +76 -207
- package/dist/integrations/agent/spawn.js +4 -6
- package/dist/integrations/harnesses/aider/agent-builder.js +2 -3
- package/dist/integrations/harnesses/aider/index.js +0 -1
- package/dist/integrations/harnesses/amazonq/agent-builder.js +2 -3
- package/dist/integrations/harnesses/amazonq/index.js +0 -1
- package/dist/integrations/harnesses/claude/agent-builder.js +2 -3
- package/dist/integrations/harnesses/claude/index.js +0 -2
- package/dist/integrations/harnesses/codex/agent-builder.js +2 -3
- package/dist/integrations/harnesses/codex/index.js +0 -1
- package/dist/integrations/harnesses/copilot/agent-builder.js +2 -3
- package/dist/integrations/harnesses/copilot/index.js +0 -1
- package/dist/integrations/harnesses/gemini/agent-builder.js +2 -3
- package/dist/integrations/harnesses/gemini/index.js +0 -1
- package/dist/integrations/harnesses/index.js +1 -24
- package/dist/integrations/harnesses/opencode/agent-builder.js +2 -3
- package/dist/integrations/harnesses/opencode/index.js +0 -6
- package/dist/integrations/harnesses/opencode-sdk/harness.js +1 -6
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +274 -191
- package/dist/integrations/harnesses/openhands/agent-builder.js +2 -3
- package/dist/integrations/harnesses/openhands/index.js +0 -1
- package/dist/integrations/harnesses/pi/agent-builder.js +2 -3
- package/dist/integrations/harnesses/pi/index.js +0 -1
- package/dist/integrations/harnesses/types.js +1 -32
- package/dist/integrations/lockfile.js +32 -21
- 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/shapes/helpers.js +2 -1
- package/dist/output/shapes/passthrough.js +2 -0
- package/dist/output/text/helpers.js +3 -1
- package/dist/schemas/akm-config.json +11013 -8600
- package/dist/schemas/akm-task.json +87 -0
- package/dist/schemas/akm-workflow.json +57 -13
- package/dist/scripts/migrate-storage.js +8591 -509
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +8623 -484
- 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 +9 -8
- 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/wiki/wiki.js +9 -8
- package/dist/workflows/authoring/workflow-program-template.yaml +3 -3
- package/dist/workflows/concurrency-policy.js +15 -0
- package/dist/workflows/db.js +65 -13
- package/dist/workflows/exec/brief.js +18 -24
- package/dist/workflows/exec/frozen-judge.js +47 -0
- package/dist/workflows/exec/native-executor.js +124 -65
- package/dist/workflows/exec/report.js +93 -33
- package/dist/workflows/exec/run-workflow.js +38 -25
- package/dist/workflows/exec/scheduler.js +12 -41
- package/dist/workflows/exec/step-work.js +91 -35
- package/dist/workflows/ir/compile.js +13 -26
- package/dist/workflows/ir/freeze.js +243 -0
- package/dist/workflows/ir/plan-hash.js +40 -5
- package/dist/workflows/ir/schema.js +542 -1
- package/dist/workflows/parser.js +7 -0
- package/dist/workflows/program/parser.js +132 -23
- package/dist/workflows/program/project.js +3 -4
- package/dist/workflows/program/schema.js +2 -2
- package/dist/workflows/resource-limits.js +20 -0
- package/dist/workflows/runtime/plan-classifier.js +187 -0
- package/dist/workflows/runtime/runs.js +54 -47
- package/dist/workflows/runtime/workflow-asset-loader.js +0 -22
- package/dist/workflows/validator.js +25 -0
- package/docs/data-and-telemetry.md +2 -2
- 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/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
|
@@ -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,
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* never touch a real platform.
|
|
16
16
|
* - Bounded LLM call wrapped by {@link tryLlmFeature} under the
|
|
17
17
|
* `session_extraction` gate (default-on; opt out via
|
|
18
|
-
* `
|
|
18
|
+
* `improve.strategies.<name>.processes.extract.enabled: false`).
|
|
19
19
|
* - Proposals routed via `createProposal({ source: "extract", ... })` — the
|
|
20
20
|
* same review queue as reflect / distill / consolidate. Never direct-write.
|
|
21
21
|
* - Per-candidate body assembly merges description (+ when_to_use for lessons)
|
|
@@ -27,16 +27,18 @@ import fs from "node:fs";
|
|
|
27
27
|
import path from "node:path";
|
|
28
28
|
import { assembleAsset } from "../../core/asset/asset-serialize.js";
|
|
29
29
|
import { resolveStashDir, timestampForFilename } from "../../core/common.js";
|
|
30
|
-
import {
|
|
30
|
+
import { getImproveProcessConfig, loadConfig } from "../../core/config/config.js";
|
|
31
31
|
import { ConfigError, UsageError } from "../../core/errors.js";
|
|
32
32
|
import { appendEvent } from "../../core/events.js";
|
|
33
|
-
import { probeLock, releaseLock, tryAcquireLockSync } from "../../core/file-lock.js";
|
|
33
|
+
import { createLockPayload, probeLock, reclaimStaleLock, releaseLock, tryAcquireLockSync, } from "../../core/file-lock.js";
|
|
34
|
+
import { tryAcquireMaintenanceBarrier } from "../../core/maintenance-barrier.js";
|
|
34
35
|
import { resolveStashStandards } from "../../core/standards/resolve-stash-standards.js";
|
|
35
36
|
import { getStateDbPath, openStateDatabase, withStateDb } from "../../core/state-db.js";
|
|
36
37
|
import { repairTruncatedDescription } from "../../core/text-truncation.js";
|
|
37
38
|
import { warn } from "../../core/warn.js";
|
|
38
39
|
import { indexWrittenAssets } from "../../indexer/index-written-assets.js";
|
|
39
|
-
import {
|
|
40
|
+
import { resolveLlmEngineUse } from "../../integrations/agent/engine-resolution.js";
|
|
41
|
+
import { materializeLlmRunnerConnection, resolveImproveProcessRunner, } from "../../integrations/agent/runner.js";
|
|
40
42
|
import { normalizeHarnessId } from "../../integrations/harnesses/index.js";
|
|
41
43
|
import { getAvailableHarnesses } from "../../integrations/session-logs/index.js";
|
|
42
44
|
import { preFilterSession } from "../../integrations/session-logs/pre-filter.js";
|
|
@@ -48,7 +50,7 @@ import { getExtractedSessionsMap, getLastExtractRunAt, shouldSkipAlreadyExtracte
|
|
|
48
50
|
import { createProposal, isProposalSkipped } from "../proposal/repository.js";
|
|
49
51
|
import { buildExtractPrompt, EXTRACT_JSON_SCHEMA, parseExtractPayload } from "./extract-prompt.js";
|
|
50
52
|
import { buildHotProbationFrontmatter } from "./hot-probation.js";
|
|
51
|
-
import { resolveProcessEnabled } from "./improve-
|
|
53
|
+
import { resolveImproveStrategy, resolveProcessEnabled } from "./improve-strategies.js";
|
|
52
54
|
import { applySchemaSimilarityPenalty, loadDerivedLayerEmbeddings, } from "./schema-similarity-gate.js";
|
|
53
55
|
import { buildSessionSummaryPrompt, parseSessionSummary, SESSION_SUMMARY_JSON_SCHEMA, sessionMeetsDurationGate, writeSessionAsset, } from "./session-asset.js";
|
|
54
56
|
import { resolveTriageConfig, scoreSessionTriage } from "./triage.js";
|
|
@@ -123,20 +125,75 @@ function getExtractSessionLockPath(harness, sessionId, stateDbPath) {
|
|
|
123
125
|
* extraction outright.
|
|
124
126
|
*/
|
|
125
127
|
function acquireExtractSessionLock(lockPath) {
|
|
128
|
+
const releaseBarrier = tryAcquireMaintenanceBarrier();
|
|
129
|
+
if (!releaseBarrier)
|
|
130
|
+
return { proceed: false };
|
|
126
131
|
try {
|
|
127
132
|
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
128
|
-
|
|
129
|
-
|
|
133
|
+
let ownership = tryAcquireLockSync(lockPath, createLockPayload());
|
|
134
|
+
if (ownership)
|
|
135
|
+
return { proceed: true, ownership };
|
|
130
136
|
const probe = probeLock(lockPath, { staleAfterMs: EXTRACT_SESSION_LOCK_STALE_MS });
|
|
131
137
|
if (probe.state === "held")
|
|
132
|
-
return false;
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
138
|
+
return { proceed: false };
|
|
139
|
+
// Absent (released between attempt + probe) or successfully reclaimed stale lock → retry once.
|
|
140
|
+
if (probe.state === "stale" && !reclaimStaleLock(lockPath, probe))
|
|
141
|
+
return { proceed: false };
|
|
142
|
+
ownership = tryAcquireLockSync(lockPath, createLockPayload());
|
|
143
|
+
return ownership ? { proceed: true, ownership } : { proceed: false };
|
|
136
144
|
}
|
|
137
145
|
catch {
|
|
138
|
-
return true;
|
|
146
|
+
return { proceed: true };
|
|
139
147
|
}
|
|
148
|
+
finally {
|
|
149
|
+
releaseBarrier();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function cloneAndFreeze(value) {
|
|
153
|
+
const clone = structuredClone(value);
|
|
154
|
+
const freeze = (item) => {
|
|
155
|
+
if (typeof item !== "object" || item === null || Object.isFrozen(item))
|
|
156
|
+
return;
|
|
157
|
+
for (const child of Object.values(item))
|
|
158
|
+
freeze(child);
|
|
159
|
+
Object.freeze(item);
|
|
160
|
+
};
|
|
161
|
+
freeze(clone);
|
|
162
|
+
return clone;
|
|
163
|
+
}
|
|
164
|
+
/** Resolve standalone extract selection once before discovery, auto iteration, or watch startup. */
|
|
165
|
+
export function resolveStandaloneExtractPlan(config, selection) {
|
|
166
|
+
if (selection.engine && selection.strategy) {
|
|
167
|
+
throw new UsageError("--engine and --strategy are mutually exclusive. Pick one.", "INVALID_FLAG_VALUE");
|
|
168
|
+
}
|
|
169
|
+
const selected = resolveImproveStrategy(selection.strategy, config);
|
|
170
|
+
const process = cloneAndFreeze(getImproveProcessConfig(config, "extract", selected.config) ?? {});
|
|
171
|
+
const invocation = {
|
|
172
|
+
...(selection.engine ? { engine: selection.engine } : {}),
|
|
173
|
+
...(Object.hasOwn(selection, "timeoutMs") ? { timeoutMs: selection.timeoutMs ?? null } : {}),
|
|
174
|
+
};
|
|
175
|
+
const resolved = resolveLlmEngineUse(config, [selected.config, process, invocation], { optional: true });
|
|
176
|
+
if (!resolved) {
|
|
177
|
+
throw new ConfigError("No LLM engine configured for extract. Set defaults.llmEngine, pass --engine, or select an improve strategy with processes.extract.engine.", "LLM_NOT_CONFIGURED");
|
|
178
|
+
}
|
|
179
|
+
const runner = {
|
|
180
|
+
kind: "llm",
|
|
181
|
+
engine: resolved.engine,
|
|
182
|
+
connection: resolved.connection,
|
|
183
|
+
...(resolved.credential ? { credential: resolved.credential } : {}),
|
|
184
|
+
timeoutMs: resolved.timeoutMs,
|
|
185
|
+
};
|
|
186
|
+
return Object.freeze({
|
|
187
|
+
strategy: selected.name,
|
|
188
|
+
engine: resolved.engine,
|
|
189
|
+
// `akm extract` is an explicit operation. The strategy supplies behavior,
|
|
190
|
+
// but its improve-stage enablement gate does not disable this command.
|
|
191
|
+
enabled: true,
|
|
192
|
+
process,
|
|
193
|
+
runner: cloneAndFreeze(runner),
|
|
194
|
+
timeoutMs: resolved.timeoutMs,
|
|
195
|
+
embeddingConfig: cloneAndFreeze(config.embedding),
|
|
196
|
+
});
|
|
140
197
|
}
|
|
141
198
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
142
199
|
/**
|
|
@@ -245,11 +302,11 @@ export function hashSessionContent(data) {
|
|
|
245
302
|
* proposal validation failure) the session result records a warning and
|
|
246
303
|
* keeps going — one session's bad luck never aborts a multi-session run.
|
|
247
304
|
*/
|
|
248
|
-
async function processSession(harness, sessionRef, stashDir, config,
|
|
305
|
+
async function processSession(harness, sessionRef, stashDir, config, getLlmConfig, chat, ctx, sourceRun, dryRun, timeoutMs, maxTotalChars, minContentChars,
|
|
249
306
|
// #626 — pre-LLM heuristic triage gate. Default-off (enabled:false) takes the
|
|
250
307
|
// exact pre-change path (no scorer call, no new skipReason).
|
|
251
308
|
// #641 — proceduralAwareFloor is opt-in, DEFAULT OFF.
|
|
252
|
-
triage, sessionIndexing, schemaSimilarityCtx,
|
|
309
|
+
triage, sessionIndexing, schemaSimilarityCtx, hotProbationEnabled,
|
|
253
310
|
// #602 — already-extracted skip moved INSIDE processSession: the content hash
|
|
254
311
|
// can only be computed after readSession, so the skip decision lives here. The
|
|
255
312
|
// prior row + bypass flags are threaded in from the caller. Skipping here still
|
|
@@ -390,7 +447,7 @@ standardsContext) {
|
|
|
390
447
|
};
|
|
391
448
|
let llmRaw = "";
|
|
392
449
|
const llmResult = await tryLlmFeature("session_extraction", config, async () => {
|
|
393
|
-
llmRaw = await chat(
|
|
450
|
+
llmRaw = await chat(getLlmConfig(), [{ role: "user", content: prompt }], {
|
|
394
451
|
timeoutMs,
|
|
395
452
|
responseSchema: EXTRACT_JSON_SCHEMA,
|
|
396
453
|
});
|
|
@@ -451,12 +508,7 @@ standardsContext) {
|
|
|
451
508
|
// When enabled, system-generated extractions enter captureMode: hot-probation
|
|
452
509
|
// so they spend ONE consolidation cycle in probation before the deterministic
|
|
453
510
|
// dedup+quality pass promotes them. Default OFF.
|
|
454
|
-
//
|
|
455
|
-
// flag lives inside the 18-arg `processSession`, which has no handle to the
|
|
456
|
-
// active profile, and threading a 19th positional arg for a DEFAULT-OFF flag
|
|
457
|
-
// isn't worth the param bloat. The extract STAGE toggle (in `akmExtract`,
|
|
458
|
-
// below) already honors `--profile`.
|
|
459
|
-
const hotProbationEnabled = getImproveProcessConfig(config, "extract")?.hotProbation?.enabled === true;
|
|
511
|
+
// The invocation boundary resolves this from the frozen extract process.
|
|
460
512
|
for (const candidate of payload.candidates) {
|
|
461
513
|
if (dryRun) {
|
|
462
514
|
proposalIds.push(`dry-run:${candidate.type}:${candidate.name}`);
|
|
@@ -555,20 +607,19 @@ export async function akmExtract(options) {
|
|
|
555
607
|
const stashDir = options.stashDir ?? resolveStashDir();
|
|
556
608
|
const dryRun = options.dryRun ?? false;
|
|
557
609
|
const sourceRun = options.sourceRun ?? `extract-${timestampForFilename()}`;
|
|
558
|
-
// Read
|
|
559
|
-
//
|
|
560
|
-
//
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
const activeProfile = options.improveProfile;
|
|
564
|
-
const extractProcess = activeProfile ? activeProfile.processes?.extract : getImproveProcessConfig(config, "extract");
|
|
610
|
+
// Read process behavior from the frozen standalone plan or the active improve
|
|
611
|
+
// strategy. This prevents config changes during watch mode from changing later
|
|
612
|
+
// triggers and prevents one improve strategy from overriding another.
|
|
613
|
+
const activeProfile = options.improveProfile ?? (options.resolvedPlan ? undefined : resolveImproveStrategy(undefined, config).config);
|
|
614
|
+
const extractProcess = options.resolvedPlan?.process ?? getImproveProcessConfig(config, "extract", activeProfile);
|
|
565
615
|
// The `extract.enabled` process toggle gates extract as a STAGE of `akm improve`
|
|
566
616
|
// (the activeProfile path) — consistent with #593/#594 where the active profile,
|
|
567
617
|
// not `default`, is the source of truth. An EXPLICIT `akm extract` invocation
|
|
568
618
|
// (no activeProfile) is a direct user/cron action and always runs; gating it on
|
|
569
619
|
// the default improve profile's stage toggle was a footgun — dropping extract
|
|
570
620
|
// from the daily improve profile would silently disable the standalone command.
|
|
571
|
-
const extractEnabled =
|
|
621
|
+
const extractEnabled = options.resolvedPlan?.enabled ??
|
|
622
|
+
(options.improveProfile ? resolveProcessEnabled("extract", options.improveProfile) : true);
|
|
572
623
|
// Feature-gate early so we get a clean "skipped because disabled" envelope.
|
|
573
624
|
if (!extractEnabled) {
|
|
574
625
|
return {
|
|
@@ -582,35 +633,29 @@ export async function akmExtract(options) {
|
|
|
582
633
|
candidatesCreated: 0,
|
|
583
634
|
proposals: [],
|
|
584
635
|
sessions: [],
|
|
585
|
-
warnings: [
|
|
586
|
-
"session_extraction feature disabled — set profiles.improve.default.processes.extract.enabled: true to use",
|
|
587
|
-
],
|
|
636
|
+
warnings: ["extract is disabled by the selected improve strategy"],
|
|
588
637
|
durationMs: Date.now() - startMs,
|
|
589
638
|
};
|
|
590
639
|
}
|
|
591
|
-
//
|
|
592
|
-
//
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
if (!runnerIsLlm(runnerSpec)) {
|
|
600
|
-
throw new ConfigError(`Extract only supports mode: "llm" (in-tree LLM call). Got mode: "${runnerSpec.kind}" from profiles.improve.default.processes.extract — change it to "llm" or remove the override.`, "INVALID_CONFIG_FILE");
|
|
601
|
-
}
|
|
602
|
-
llmConfig = runnerSpec.connection;
|
|
603
|
-
}
|
|
604
|
-
else {
|
|
605
|
-
llmConfig = getDefaultLlmConfig(config) ?? undefined;
|
|
606
|
-
}
|
|
607
|
-
if (!llmConfig) {
|
|
608
|
-
throw new ConfigError("No LLM connection configured for extract. Set profiles.llm + defaults.llm, or set profiles.improve.default.processes.extract.profile to a configured LLM profile.");
|
|
640
|
+
// Improve supplies its invocation-owned connection. Standalone extract
|
|
641
|
+
// resolves the selected process engine, then defaults.llmEngine.
|
|
642
|
+
const runnerSpec = options.resolvedPlan
|
|
643
|
+
? options.resolvedPlan.runner
|
|
644
|
+
: resolveImproveProcessRunner(activeProfile, "extract", config);
|
|
645
|
+
const fixedLlmConfig = options.resolvedPlan ? undefined : options.llmConfig;
|
|
646
|
+
if (!runnerSpec && !fixedLlmConfig) {
|
|
647
|
+
throw new ConfigError("No LLM engine configured for extract. Set defaults.llmEngine or improve.strategies.<name>.processes.extract.engine.", "LLM_NOT_CONFIGURED");
|
|
609
648
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
(
|
|
613
|
-
|
|
649
|
+
const timeoutMs = options.resolvedPlan
|
|
650
|
+
? options.resolvedPlan.timeoutMs
|
|
651
|
+
: Object.hasOwn(options, "timeoutMs")
|
|
652
|
+
? (options.timeoutMs ?? null)
|
|
653
|
+
: runnerSpec?.timeoutMs !== undefined
|
|
654
|
+
? runnerSpec.timeoutMs
|
|
655
|
+
: fixedLlmConfig && Object.hasOwn(fixedLlmConfig, "timeoutMs")
|
|
656
|
+
? (fixedLlmConfig.timeoutMs ?? null)
|
|
657
|
+
: 600_000;
|
|
658
|
+
const getLlmConfig = () => runnerSpec ? materializeLlmRunnerConnection(runnerSpec) : fixedLlmConfig;
|
|
614
659
|
// Pre-filter budget — process config can raise it for large-context models.
|
|
615
660
|
const maxTotalChars = typeof extractProcess?.maxTotalChars === "number" ? extractProcess.maxTotalChars : undefined;
|
|
616
661
|
// #595/#596 — minimum raw session size; sessions below it skip the LLM call
|
|
@@ -643,7 +688,7 @@ export async function akmExtract(options) {
|
|
|
643
688
|
const defaultSessionSummaryGenerator = async (data) => {
|
|
644
689
|
let raw = "";
|
|
645
690
|
await tryLlmFeature("session_extraction", config, async () => {
|
|
646
|
-
raw = await chatForSummary(
|
|
691
|
+
raw = await chatForSummary(getLlmConfig(), [{ role: "user", content: buildSessionSummaryPrompt(data) }], {
|
|
647
692
|
timeoutMs,
|
|
648
693
|
responseSchema: SESSION_SUMMARY_JSON_SCHEMA,
|
|
649
694
|
});
|
|
@@ -768,13 +813,14 @@ export async function akmExtract(options) {
|
|
|
768
813
|
// processes.extract.schemaSimilarity.enabled: false. The gate is inert in
|
|
769
814
|
// practice when no derived-layer embeddings exist (empty ctx → no penalty).
|
|
770
815
|
const schemaSimilarityCfg = extractProcess?.schemaSimilarity;
|
|
816
|
+
const hotProbationEnabled = extractProcess?.hotProbation?.enabled === true;
|
|
771
817
|
let schemaSimilarityCtx = null;
|
|
772
818
|
if (schemaSimilarityCfg?.enabled !== false) {
|
|
773
819
|
const derivedEmbeddings = options.schemaSimilarityEmbeddings ?? loadDerivedLayerEmbeddings();
|
|
774
820
|
schemaSimilarityCtx = {
|
|
775
821
|
config: { ...schemaSimilarityCfg, enabled: true },
|
|
776
822
|
derivedEmbeddings,
|
|
777
|
-
embeddingConfig: config.embedding,
|
|
823
|
+
embeddingConfig: options.resolvedPlan?.embeddingConfig ?? config.embedding,
|
|
778
824
|
embedFn: options.schemaSimilarityEmbedFn,
|
|
779
825
|
};
|
|
780
826
|
}
|
|
@@ -802,10 +848,11 @@ export async function akmExtract(options) {
|
|
|
802
848
|
// runs (those that open their own state.db): dry-run is read-only, an
|
|
803
849
|
// injected `stateDb` handle is an in-process/test scenario with no cross-
|
|
804
850
|
// process race, and skip-tracking-off opts out entirely.
|
|
805
|
-
let
|
|
851
|
+
let sessionLockOwnership;
|
|
806
852
|
if (trackingEnabled && !dryRun && !options.stateDb) {
|
|
807
|
-
sessionLockPath = getExtractSessionLockPath(harness.name, summary.sessionId, options.stateDbPath ?? getStateDbPath());
|
|
808
|
-
|
|
853
|
+
const sessionLockPath = getExtractSessionLockPath(harness.name, summary.sessionId, options.stateDbPath ?? getStateDbPath());
|
|
854
|
+
const sessionLock = acquireExtractSessionLock(sessionLockPath);
|
|
855
|
+
if (!sessionLock.proceed) {
|
|
809
856
|
sessions.push({
|
|
810
857
|
sessionId: summary.sessionId,
|
|
811
858
|
harness: harness.name,
|
|
@@ -819,9 +866,10 @@ export async function akmExtract(options) {
|
|
|
819
866
|
skippedCount += 1;
|
|
820
867
|
continue;
|
|
821
868
|
}
|
|
869
|
+
sessionLockOwnership = sessionLock.ownership;
|
|
822
870
|
}
|
|
823
871
|
try {
|
|
824
|
-
const result = await processSession(harness, summary, stashDir, config,
|
|
872
|
+
const result = await processSession(harness, summary, stashDir, config, getLlmConfig, chat, options.ctx, sourceRun, dryRun, timeoutMs, maxTotalChars, minContentChars, triage, sessionIndexing, schemaSimilarityCtx, hotProbationEnabled, prior, options.force === true, extractStandardsContext);
|
|
825
873
|
sessions.push(result);
|
|
826
874
|
// #626 — triage aggregation. A session reached the triage gate only when it
|
|
827
875
|
// was NOT already preempted by an earlier skip (read_failed / too_short /
|
|
@@ -916,8 +964,8 @@ export async function akmExtract(options) {
|
|
|
916
964
|
skippedCount += 1;
|
|
917
965
|
}
|
|
918
966
|
finally {
|
|
919
|
-
if (
|
|
920
|
-
releaseLock(
|
|
967
|
+
if (sessionLockOwnership)
|
|
968
|
+
releaseLock(sessionLockOwnership);
|
|
921
969
|
}
|
|
922
970
|
}
|
|
923
971
|
// Close the state.db connection we opened. Callers that injected stateDb
|