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
|
@@ -10,13 +10,15 @@ import { rethrowIfTestIsolationError } from "../../core/errors.js";
|
|
|
10
10
|
import { appendEvent, readEvents } from "../../core/events.js";
|
|
11
11
|
import { classifyImproveAction, foldDistillSkipped } from "../../core/improve-types.js";
|
|
12
12
|
import { getDbPath, getStateDbPathInDataDir } from "../../core/paths.js";
|
|
13
|
+
import { redactSensitiveText } from "../../core/redaction.js";
|
|
13
14
|
import { openStateDatabase } from "../../core/state-db.js";
|
|
14
15
|
import { info, warn } from "../../core/warn.js";
|
|
15
16
|
import { closeDatabase, getEntryCount, openExistingDatabase } from "../../indexer/db/db.js";
|
|
16
17
|
import { ensureIndex } from "../../indexer/ensure-index.js";
|
|
17
18
|
import { akmIndex } from "../../indexer/indexer.js";
|
|
18
19
|
import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
19
|
-
import {
|
|
20
|
+
import { collectEngineCredentialValues } from "../../integrations/agent/engine-resolution.js";
|
|
21
|
+
import { materializeLlmRunnerConnection } from "../../integrations/agent/runner.js";
|
|
20
22
|
import { installLlmUsagePersistence } from "../../llm/usage-persist.js";
|
|
21
23
|
import { withLlmStage } from "../../llm/usage-telemetry.js";
|
|
22
24
|
import { isGitBackedStash, resolveWritableOverride, saveGitStash } from "../../sources/providers/git.js";
|
|
@@ -24,9 +26,9 @@ import { drainProposals } from "../proposal/drain.js";
|
|
|
24
26
|
import { resolveDrainPolicy } from "../proposal/drain-policies.js";
|
|
25
27
|
import { akmDistill } from "./distill.js";
|
|
26
28
|
// Eligibility / candidate-selection predicates live in ./eligibility.
|
|
27
|
-
import { buildLatestProposalTsMap, collectEligibleRefs, memoryCleanupParentRef, resolveImproveScope, shouldAnalyzeMemoryCleanup, } from "./eligibility.js";
|
|
29
|
+
import { buildLatestProposalTsMap, collectEligibleRefs, collectEligibleRefsReadOnly, memoryCleanupParentRef, resolveImproveScope, shouldAnalyzeMemoryCleanup, } from "./eligibility.js";
|
|
28
30
|
import { countEvalCases } from "./eval-cases.js";
|
|
29
|
-
import {
|
|
31
|
+
import { resolveImprovePlan } from "./improve-strategies.js";
|
|
30
32
|
// #607 per-process lock primitives live in ./locks. Imported for internal use;
|
|
31
33
|
// resetHeldProcessLocks is re-exported (the test seam imports it from here).
|
|
32
34
|
import { PROCESS_LOCK_DEFS, processLockPath, releaseAllProcessLocks, releaseHeldLocksIfOwned, releaseProcessLock, tryAcquireProcessLock, withOptionalProcessLock, } from "./locks.js";
|
|
@@ -91,19 +93,28 @@ export async function akmImprove(options = {}) {
|
|
|
91
93
|
const reindexFn = options.reindexFn ?? akmIndex;
|
|
92
94
|
const drainProposalsFn = options.drainProposalsFn ?? drainProposals;
|
|
93
95
|
// #616 multi-cycle test seams. Default to the real module-local fns.
|
|
94
|
-
const collectEligibleRefsImpl = options.collectEligibleRefsFn ?? collectEligibleRefs;
|
|
96
|
+
const collectEligibleRefsImpl = options.collectEligibleRefsFn ?? (options.dryRun ? collectEligibleRefsReadOnly : collectEligibleRefs);
|
|
95
97
|
const runImprovePreparationStageImpl = options.runImprovePreparationStageFn ?? runImprovePreparationStage;
|
|
96
98
|
const runImproveLoopStageImpl = options.runImproveLoopStageFn ?? runImproveLoopStage;
|
|
97
99
|
const runImprovePostLoopStageImpl = options.runImprovePostLoopStageFn ?? runImprovePostLoopStage;
|
|
98
100
|
// Resolve the improve profile for this run. Profile drives type filtering,
|
|
99
101
|
// process gating, and default autoAccept/limit values.
|
|
100
102
|
const _earlyConfig = options.config ?? loadConfig();
|
|
101
|
-
const
|
|
103
|
+
const resolvedPlan = options.resolvedPlan ??
|
|
104
|
+
resolveImprovePlan(options.strategy, _earlyConfig, {
|
|
105
|
+
repairValidationFailures: options.repairValidationFailures,
|
|
106
|
+
});
|
|
107
|
+
const selectedStrategy = resolvedPlan.strategy;
|
|
108
|
+
const improveSensitiveValues = collectEngineCredentialValues(_earlyConfig);
|
|
109
|
+
const improveProfile = selectedStrategy.config;
|
|
102
110
|
// Apply profile defaults — CLI flags take precedence over profile defaults.
|
|
103
111
|
// Rebuild options with effective values so all downstream stage functions
|
|
104
112
|
// automatically pick up the profile-driven defaults.
|
|
105
113
|
options = {
|
|
106
114
|
...options,
|
|
115
|
+
// Pin nested calls and quality gates to the same config snapshot as the
|
|
116
|
+
// invocation plan. They must never reload a changed config mid-run.
|
|
117
|
+
config: _earlyConfig,
|
|
107
118
|
autoAccept: options.autoAccept ?? improveProfile.autoAccept,
|
|
108
119
|
// Profile-level limit, then process-level reflect.limit as fallback.
|
|
109
120
|
// CLI --limit takes precedence over both.
|
|
@@ -161,7 +172,7 @@ export async function akmImprove(options = {}) {
|
|
|
161
172
|
// (cycle 1, in the first try) always overwrites them before any read.
|
|
162
173
|
let plannedRefs = [];
|
|
163
174
|
let memorySummary = { eligible: 0, derived: 0 };
|
|
164
|
-
let
|
|
175
|
+
let strategyFilteredRefs = [];
|
|
165
176
|
let memoryCleanupPlan;
|
|
166
177
|
let guidance;
|
|
167
178
|
let triageDrain;
|
|
@@ -170,7 +181,7 @@ export async function akmImprove(options = {}) {
|
|
|
170
181
|
// first try) and is re-run at the top of each subsequent multi-cycle cycle.
|
|
171
182
|
// Re-running ensureIndex between cycles makes cycle N's gate-promoted
|
|
172
183
|
// proposals visible to cycle N+1's collectEligibleRefs. Mutates the
|
|
173
|
-
// outer-scope plannedRefs/memorySummary/
|
|
184
|
+
// outer-scope plannedRefs/memorySummary/strategyFilteredRefs/memoryCleanupPlan/
|
|
174
185
|
// guidance so for maxCycles:1 the body is byte-identical to pre-#616.
|
|
175
186
|
const runIndexAndCollect = async () => {
|
|
176
187
|
// #339 fix: ensureIndex MUST run BEFORE collectEligibleRefs. The eligible-ref
|
|
@@ -178,7 +189,7 @@ export async function akmImprove(options = {}) {
|
|
|
178
189
|
// table (or the index is otherwise empty), the prior run order silently
|
|
179
190
|
// returned plannedRefs=[] and the improve loop no-op'd. Hoisting the call
|
|
180
191
|
// here repopulates the index first so the subsequent query sees fresh data.
|
|
181
|
-
if (primaryStashDir) {
|
|
192
|
+
if (primaryStashDir && !options.dryRun) {
|
|
182
193
|
// Probe pre-ensureIndex entry count to drive the loud-fail warning below.
|
|
183
194
|
// Best-effort: a missing DB / unreadable schema is the fresh-install case
|
|
184
195
|
// and not a bug — we silently skip the probe.
|
|
@@ -230,15 +241,24 @@ export async function akmImprove(options = {}) {
|
|
|
230
241
|
}
|
|
231
242
|
}
|
|
232
243
|
}
|
|
233
|
-
({
|
|
244
|
+
({
|
|
245
|
+
plannedRefs,
|
|
246
|
+
memorySummary,
|
|
247
|
+
strategyFilteredRefs = [],
|
|
248
|
+
} = await collectEligibleRefsImpl(scope, options.stashDir, improveProfile));
|
|
234
249
|
const cleanupParentRef = memoryCleanupParentRef(scope, options.stashDir);
|
|
235
250
|
// M-1 (#367): Run contradiction-detection BEFORE analyzeMemoryCleanup so
|
|
236
251
|
// the SCC resolver in resolveFamilyContradictions has edges to work on.
|
|
237
252
|
// Best-effort: failures are warnings, never fatal.
|
|
238
|
-
if (
|
|
253
|
+
if (!options.dryRun &&
|
|
254
|
+
primaryStashDir &&
|
|
255
|
+
shouldAnalyzeMemoryCleanup(scope, memorySummary.eligible, primaryStashDir)) {
|
|
239
256
|
try {
|
|
240
257
|
// Reuse the config resolved at the top of the run instead of a second load.
|
|
241
|
-
|
|
258
|
+
const contradictionDetectionFn = options.contradictionDetectionFn ?? detectAndWriteContradictions;
|
|
259
|
+
await withLlmStage("memory-contradiction", () => contradictionDetectionFn(primaryStashDir, _earlyConfig, undefined, improveProfile, resolvedPlan.processes.consolidate.runner
|
|
260
|
+
? materializeLlmRunnerConnection(resolvedPlan.processes.consolidate.runner)
|
|
261
|
+
: null), { engine: resolvedPlan.processes.consolidate.runner?.engine, process: "consolidate" });
|
|
242
262
|
}
|
|
243
263
|
catch (err) {
|
|
244
264
|
// Non-fatal: contradiction detection is a best-effort pass.
|
|
@@ -264,7 +284,7 @@ export async function akmImprove(options = {}) {
|
|
|
264
284
|
if (!options.dryRun) {
|
|
265
285
|
// Backstop release on process.exit() (signal handler / budget watchdog),
|
|
266
286
|
// which skips the finally below. Removed in that finally on the normal path.
|
|
267
|
-
const releaseAllOnExit = () => releaseHeldLocksIfOwned(
|
|
287
|
+
const releaseAllOnExit = () => releaseHeldLocksIfOwned();
|
|
268
288
|
exitBackstop = releaseAllOnExit;
|
|
269
289
|
process.on("exit", releaseAllOnExit);
|
|
270
290
|
// #607 triage pre-pass: acquire triage.lock, drain the standing pending
|
|
@@ -272,14 +292,14 @@ export async function akmImprove(options = {}) {
|
|
|
272
292
|
// a cleared queue (no `duplicate_pending` collisions) and ensureIndex
|
|
273
293
|
// absorbs triage's promotions for free. Release immediately after —
|
|
274
294
|
// triage.lock is not needed again until the next improve run.
|
|
275
|
-
if (primaryStashDir &&
|
|
295
|
+
if (primaryStashDir && resolvedPlan.processes.triage.enabled) {
|
|
276
296
|
if (scope.mode === "ref") {
|
|
277
297
|
warn("[improve] triage pre-pass skipped (single-ref scope never drains the whole queue)");
|
|
278
298
|
}
|
|
279
299
|
else {
|
|
280
300
|
const triageLPath = processLockPath(lockBaseDir, "triage");
|
|
281
301
|
const triageResult = tryAcquireProcessLock(triageLPath, PROCESS_LOCK_DEFS.triage.staleAfterMs, options.skipIfLocked, "triage");
|
|
282
|
-
if (triageResult === "skipped") {
|
|
302
|
+
if (triageResult.state === "skipped") {
|
|
283
303
|
triageDrain = undefined;
|
|
284
304
|
}
|
|
285
305
|
else {
|
|
@@ -288,9 +308,6 @@ export async function akmImprove(options = {}) {
|
|
|
288
308
|
const policy = resolveDrainPolicy(triageConfig?.policy);
|
|
289
309
|
const applyMode = triageConfig?.applyMode ?? "queue";
|
|
290
310
|
const maxAccepts = triageConfig?.maxAcceptsPerRun ?? 25;
|
|
291
|
-
const judgment = triageConfig?.judgment
|
|
292
|
-
? resolveTriageJudgmentRunner(triageConfig.judgment, _earlyConfig)
|
|
293
|
-
: null;
|
|
294
311
|
triageDrain = await drainProposalsFn({
|
|
295
312
|
stashDir: primaryStashDir,
|
|
296
313
|
policy,
|
|
@@ -299,14 +316,14 @@ export async function akmImprove(options = {}) {
|
|
|
299
316
|
dryRun: false,
|
|
300
317
|
excludeIds: new Set(),
|
|
301
318
|
...(triageConfig?.maxDiffLines !== undefined ? { maxDiffLines: triageConfig.maxDiffLines } : {}),
|
|
302
|
-
judgment,
|
|
319
|
+
judgment: resolvedPlan.triageJudgment,
|
|
303
320
|
});
|
|
304
321
|
}
|
|
305
322
|
catch (err) {
|
|
306
323
|
warn(`[improve] triage pre-pass failed (non-fatal): ${errMessage(err)}`);
|
|
307
324
|
}
|
|
308
325
|
finally {
|
|
309
|
-
releaseProcessLock(
|
|
326
|
+
releaseProcessLock(triageResult.ownership);
|
|
310
327
|
}
|
|
311
328
|
}
|
|
312
329
|
}
|
|
@@ -318,15 +335,16 @@ export async function akmImprove(options = {}) {
|
|
|
318
335
|
await runIndexAndCollect();
|
|
319
336
|
if (options.dryRun) {
|
|
320
337
|
const result = {
|
|
321
|
-
schemaVersion:
|
|
338
|
+
schemaVersion: 2,
|
|
322
339
|
ok: true,
|
|
340
|
+
strategy: selectedStrategy.name,
|
|
323
341
|
scope,
|
|
324
342
|
dryRun: true,
|
|
325
343
|
...(guidance ? { guidance } : {}),
|
|
326
344
|
memorySummary,
|
|
327
345
|
...(memoryCleanupPlan ? { memoryCleanup: shapeMemoryCleanup(memoryCleanupPlan) } : {}),
|
|
328
346
|
plannedRefs,
|
|
329
|
-
...(
|
|
347
|
+
...(strategyFilteredRefs.length > 0 ? { strategyFilteredRefs } : {}),
|
|
330
348
|
};
|
|
331
349
|
return result;
|
|
332
350
|
}
|
|
@@ -459,14 +477,15 @@ export async function akmImprove(options = {}) {
|
|
|
459
477
|
// event per ref (#592) — the per-ref loop caused O(n) sequential state.db
|
|
460
478
|
// writes that consumed ~500 s on a 9 000-ref stash. No downstream consumer
|
|
461
479
|
// needs the per-ref audit trail: health's skip histogram reads the
|
|
462
|
-
// `
|
|
463
|
-
if (
|
|
480
|
+
// `strategy_filtered_all_passes` counters from `improve_completed` metadata.
|
|
481
|
+
if (strategyFilteredRefs.length > 0) {
|
|
464
482
|
appendEvent({
|
|
465
483
|
eventType: "improve_skipped",
|
|
466
484
|
ref: undefined,
|
|
467
485
|
metadata: {
|
|
468
|
-
|
|
469
|
-
|
|
486
|
+
strategy: selectedStrategy.name,
|
|
487
|
+
reason: "strategy_filtered_all_passes",
|
|
488
|
+
count: strategyFilteredRefs.length,
|
|
470
489
|
},
|
|
471
490
|
}, eventsCtx);
|
|
472
491
|
}
|
|
@@ -534,11 +553,15 @@ export async function akmImprove(options = {}) {
|
|
|
534
553
|
if (cycleIndex > 0) {
|
|
535
554
|
await runIndexAndCollect();
|
|
536
555
|
// Re-emit the profile-filtered audit summary for this cycle's selection.
|
|
537
|
-
if (
|
|
556
|
+
if (strategyFilteredRefs.length > 0) {
|
|
538
557
|
appendEvent({
|
|
539
558
|
eventType: "improve_skipped",
|
|
540
559
|
ref: undefined,
|
|
541
|
-
metadata: {
|
|
560
|
+
metadata: {
|
|
561
|
+
strategy: selectedStrategy.name,
|
|
562
|
+
reason: "strategy_filtered_all_passes",
|
|
563
|
+
count: strategyFilteredRefs.length,
|
|
564
|
+
},
|
|
542
565
|
}, eventsCtx);
|
|
543
566
|
}
|
|
544
567
|
}
|
|
@@ -563,6 +586,8 @@ export async function akmImprove(options = {}) {
|
|
|
563
586
|
eventsCtx,
|
|
564
587
|
initialCleanupWarnings: preEnsureCleanupWarnings,
|
|
565
588
|
improveProfile,
|
|
589
|
+
resolvedPlan,
|
|
590
|
+
strategyName: selectedStrategy.name,
|
|
566
591
|
budgetSignal: budgetAbortController.signal,
|
|
567
592
|
}));
|
|
568
593
|
prepGateCount += preparation.gateAutoAcceptedCount;
|
|
@@ -624,6 +649,7 @@ export async function akmImprove(options = {}) {
|
|
|
624
649
|
budgetMs,
|
|
625
650
|
eventsCtx,
|
|
626
651
|
improveProfile,
|
|
652
|
+
resolvedPlan,
|
|
627
653
|
budgetSignal: budgetAbortController.signal,
|
|
628
654
|
});
|
|
629
655
|
});
|
|
@@ -655,6 +681,7 @@ export async function akmImprove(options = {}) {
|
|
|
655
681
|
eventsCtx,
|
|
656
682
|
budgetSignal: budgetAbortController.signal,
|
|
657
683
|
improveProfile,
|
|
684
|
+
resolvedPlan,
|
|
658
685
|
consolidationRan: preparation.consolidationRan,
|
|
659
686
|
// R5: floor violations from this run's consolidate pass + the
|
|
660
687
|
// auto-accepted volume so far (prep + loop gates) for churn detection.
|
|
@@ -709,8 +736,9 @@ export async function akmImprove(options = {}) {
|
|
|
709
736
|
// below still read `finalActions` (reflect skips are not folded).
|
|
710
737
|
const { actions: persistedActions, aggregate: distillSkippedAggregate } = foldDistillSkipped(finalActions);
|
|
711
738
|
const result = {
|
|
712
|
-
schemaVersion:
|
|
739
|
+
schemaVersion: 2,
|
|
713
740
|
ok: true,
|
|
741
|
+
strategy: selectedStrategy.name,
|
|
714
742
|
scope,
|
|
715
743
|
dryRun: false,
|
|
716
744
|
...(guidance ? { guidance } : {}),
|
|
@@ -737,7 +765,7 @@ export async function akmImprove(options = {}) {
|
|
|
737
765
|
}
|
|
738
766
|
: {}),
|
|
739
767
|
plannedRefs: preparation.actionableRefs,
|
|
740
|
-
...(
|
|
768
|
+
...(strategyFilteredRefs.length > 0 ? { strategyFilteredRefs } : {}),
|
|
741
769
|
actions: persistedActions,
|
|
742
770
|
...(distillSkippedAggregate ? { distillSkipped: distillSkippedAggregate } : {}),
|
|
743
771
|
...(preparation.validationFailures.length > 0 ? { validationFailures: preparation.validationFailures } : {}),
|
|
@@ -826,7 +854,8 @@ export async function akmImprove(options = {}) {
|
|
|
826
854
|
eventType: "improve_failed",
|
|
827
855
|
ref: scope.mode === "ref" ? scope.value : `improve:${scope.mode}:${scope.value ?? "all"}`,
|
|
828
856
|
metadata: {
|
|
829
|
-
|
|
857
|
+
strategy: selectedStrategy.name,
|
|
858
|
+
error: redactSensitiveText(errMessage(err), improveSensitiveValues),
|
|
830
859
|
durationMs: Date.now() - startMs,
|
|
831
860
|
},
|
|
832
861
|
}, eventsCtx);
|
|
@@ -942,6 +971,7 @@ function emitImproveCompletedEvent(result, durations, eventsCtx) {
|
|
|
942
971
|
? result.scope.value
|
|
943
972
|
: `improve:${result.scope.mode}:${result.scope.value ?? "all"}`,
|
|
944
973
|
metadata: {
|
|
974
|
+
strategy: result.strategy,
|
|
945
975
|
plannedRefs: result.plannedRefs.length,
|
|
946
976
|
reflectActions: actionCounts.reflect,
|
|
947
977
|
distillActions: actionCounts.distill,
|
|
@@ -5,7 +5,8 @@ import fs from "node:fs";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { ConfigError } from "../../core/errors.js";
|
|
7
7
|
import { appendEvent } from "../../core/events.js";
|
|
8
|
-
import { probeLock,
|
|
8
|
+
import { createLockPayload, probeLock, reclaimStaleLock, releaseLock, tryAcquireLockSync, } from "../../core/file-lock.js";
|
|
9
|
+
import { withMaintenanceStartBarrier } from "../../core/maintenance-barrier.js";
|
|
9
10
|
import { warn } from "../../core/warn.js";
|
|
10
11
|
// #607 Lock Decomposition: fine-grained per-process locks replace the single
|
|
11
12
|
// `improve.lock`. Three independent locks allow concurrent improve runs when
|
|
@@ -32,11 +33,27 @@ export function processLockPath(lockBaseDir, lockName) {
|
|
|
32
33
|
return path.join(lockBaseDir, PROCESS_LOCK_DEFS[lockName].fileName);
|
|
33
34
|
}
|
|
34
35
|
export function tryAcquireProcessLock(lockPath, staleAfterMs, skipIfLocked, lockLabel) {
|
|
36
|
+
let recoveryEvent;
|
|
37
|
+
const result = withMaintenanceStartBarrier(() => tryAcquireProcessLockUnlocked(lockPath, staleAfterMs, skipIfLocked, lockLabel, (event) => {
|
|
38
|
+
recoveryEvent = event;
|
|
39
|
+
}));
|
|
40
|
+
if (recoveryEvent) {
|
|
41
|
+
try {
|
|
42
|
+
appendEvent(recoveryEvent);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
/* event emission is best-effort; never block lock recovery */
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
function tryAcquireProcessLockUnlocked(lockPath, staleAfterMs, skipIfLocked, lockLabel, onRecovered) {
|
|
35
51
|
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
36
|
-
const lockPayload = () =>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
52
|
+
const lockPayload = () => createLockPayload({ startedAt: new Date().toISOString() });
|
|
53
|
+
let ownership = tryAcquireLockSync(lockPath, lockPayload());
|
|
54
|
+
if (ownership) {
|
|
55
|
+
heldProcessLocks.add(ownership);
|
|
56
|
+
return { state: "acquired", ownership };
|
|
40
57
|
}
|
|
41
58
|
const probe = probeLock(lockPath, { staleAfterMs });
|
|
42
59
|
// Race: the holder released the lock between our failed `tryAcquireLockSync`
|
|
@@ -45,9 +62,10 @@ export function tryAcquireProcessLock(lockPath, staleAfterMs, skipIfLocked, lock
|
|
|
45
62
|
// would warn/throw with a null PID for a lock that nobody actually holds.
|
|
46
63
|
// (Mirrors the absent/stale reclaim-and-retry in `acquireExtractSessionLock`.)
|
|
47
64
|
if (probe.state === "absent") {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
65
|
+
ownership = tryAcquireLockSync(lockPath, lockPayload());
|
|
66
|
+
if (ownership) {
|
|
67
|
+
heldProcessLocks.add(ownership);
|
|
68
|
+
return { state: "acquired", ownership };
|
|
51
69
|
}
|
|
52
70
|
// Re-grabbed by another racer in the window — fall through and treat as held.
|
|
53
71
|
}
|
|
@@ -63,52 +81,55 @@ export function tryAcquireProcessLock(lockPath, staleAfterMs, skipIfLocked, lock
|
|
|
63
81
|
})()
|
|
64
82
|
: null;
|
|
65
83
|
if (probe.state === "stale") {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
lockedAt: lock?.startedAt ?? null,
|
|
73
|
-
recoveredAt: new Date().toISOString(),
|
|
74
|
-
lockAgeMs: probe.ageMs ?? null,
|
|
75
|
-
reason: probe.reason === "pid_dead" ? "pid_not_alive" : probe.reason,
|
|
76
|
-
},
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
/* event emission is best-effort; never block lock recovery */
|
|
84
|
+
if (!reclaimStaleLock(lockPath, probe)) {
|
|
85
|
+
if (skipIfLocked) {
|
|
86
|
+
warn(`[improve] ${lockLabel} lock changed ownership during stale recovery; skipping (--skip-if-locked)`);
|
|
87
|
+
return { state: "skipped" };
|
|
88
|
+
}
|
|
89
|
+
throw new ConfigError(`akm improve ${lockLabel} is already running. Delete ${lockPath} to force.`, "INVALID_CONFIG_FILE");
|
|
81
90
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
91
|
+
onRecovered({
|
|
92
|
+
eventType: "improve_lock_recovered",
|
|
93
|
+
metadata: {
|
|
94
|
+
lockName: lockLabel,
|
|
95
|
+
stalePid: lock?.pid ?? null,
|
|
96
|
+
lockedAt: lock?.startedAt ?? null,
|
|
97
|
+
recoveredAt: new Date().toISOString(),
|
|
98
|
+
lockAgeMs: probe.ageMs ?? null,
|
|
99
|
+
reason: probe.reason === "pid_dead" ? "pid_not_alive" : probe.reason,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
ownership = tryAcquireLockSync(lockPath, lockPayload());
|
|
103
|
+
if (ownership) {
|
|
104
|
+
heldProcessLocks.add(ownership);
|
|
105
|
+
return { state: "acquired", ownership };
|
|
86
106
|
}
|
|
87
107
|
if (skipIfLocked) {
|
|
88
108
|
warn(`[improve] ${lockLabel} lock acquired by another run during stale recovery; skipping (--skip-if-locked)`);
|
|
89
|
-
return "skipped";
|
|
109
|
+
return { state: "skipped" };
|
|
90
110
|
}
|
|
91
111
|
throw new ConfigError(`akm improve ${lockLabel} is already running. Delete ${lockPath} to force.`, "INVALID_CONFIG_FILE");
|
|
92
112
|
}
|
|
93
113
|
if (skipIfLocked) {
|
|
94
114
|
warn(`[improve] ${lockLabel} lock held by another run (PID ${lock?.pid}, started ${lock?.startedAt}); skipping (--skip-if-locked)`);
|
|
95
|
-
return "skipped";
|
|
115
|
+
return { state: "skipped" };
|
|
96
116
|
}
|
|
97
117
|
throw new ConfigError(`akm improve ${lockLabel} is already running (PID ${lock?.pid}, started ${lock?.startedAt}). Delete ${lockPath} to force.`, "INVALID_CONFIG_FILE");
|
|
98
118
|
}
|
|
99
|
-
export function releaseProcessLock(
|
|
119
|
+
export function releaseProcessLock(ownership) {
|
|
120
|
+
if (!heldProcessLocks.has(ownership))
|
|
121
|
+
return;
|
|
100
122
|
try {
|
|
101
|
-
|
|
123
|
+
releaseLock(ownership);
|
|
102
124
|
}
|
|
103
|
-
|
|
104
|
-
|
|
125
|
+
finally {
|
|
126
|
+
heldProcessLocks.delete(ownership);
|
|
105
127
|
}
|
|
106
|
-
heldProcessLocks.delete(lockPath);
|
|
107
128
|
}
|
|
108
129
|
export function releaseAllProcessLocks() {
|
|
109
|
-
for (const
|
|
130
|
+
for (const ownership of heldProcessLocks) {
|
|
110
131
|
try {
|
|
111
|
-
|
|
132
|
+
releaseLock(ownership);
|
|
112
133
|
}
|
|
113
134
|
catch {
|
|
114
135
|
// ignore
|
|
@@ -119,12 +140,12 @@ export function releaseAllProcessLocks() {
|
|
|
119
140
|
/**
|
|
120
141
|
* Ownership-safe release of every currently-held lock, for the `process.on("exit")`
|
|
121
142
|
* backstop (signal handler / budget watchdog paths that skip the normal finally).
|
|
122
|
-
* Uses
|
|
123
|
-
*
|
|
143
|
+
* Uses exact ownership handles so a lock legitimately re-acquired after stale
|
|
144
|
+
* recovery is never deleted. Does NOT clear the Set (the process is exiting).
|
|
124
145
|
*/
|
|
125
|
-
export function releaseHeldLocksIfOwned(
|
|
126
|
-
for (const
|
|
127
|
-
|
|
146
|
+
export function releaseHeldLocksIfOwned() {
|
|
147
|
+
for (const ownership of heldProcessLocks) {
|
|
148
|
+
releaseLock(ownership);
|
|
128
149
|
}
|
|
129
150
|
}
|
|
130
151
|
/**
|
|
@@ -136,19 +157,19 @@ export function releaseHeldLocksIfOwned(pid) {
|
|
|
136
157
|
* Behaviour matches the hand-rolled `acquired = tryAcquire(...) === "acquired";
|
|
137
158
|
* …run stage…; if (acquired) release` idiom it replaces:
|
|
138
159
|
* - When the lock is held and `skipIfLocked` is set, `tryAcquireProcessLock`
|
|
139
|
-
* returns "skipped" → the stage still runs (unlocked), nothing to release.
|
|
160
|
+
* returns `state: "skipped"` → the stage still runs (unlocked), nothing to release.
|
|
140
161
|
* - When the lock is held and `skipIfLocked` is NOT set, `tryAcquireProcessLock`
|
|
141
162
|
* throws (propagated here before `body` runs; nothing acquired, nothing released).
|
|
142
163
|
* - The process-exit backstop (`releaseHeldLocksIfOwned`) still covers a
|
|
143
164
|
* `process.exit` that skips this `finally`.
|
|
144
165
|
*/
|
|
145
166
|
export async function withOptionalProcessLock(opts, body) {
|
|
146
|
-
const
|
|
167
|
+
const acquisition = tryAcquireProcessLock(opts.lockPath, opts.staleAfterMs, opts.skipIfLocked, opts.label);
|
|
147
168
|
try {
|
|
148
169
|
return await body();
|
|
149
170
|
}
|
|
150
171
|
finally {
|
|
151
|
-
if (acquired)
|
|
152
|
-
releaseProcessLock(
|
|
172
|
+
if (acquisition.state === "acquired")
|
|
173
|
+
releaseProcessLock(acquisition.ownership);
|
|
153
174
|
}
|
|
154
175
|
}
|