akm-cli 0.9.0-rc.2 → 0.9.0-rc.4
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 +40 -12
- package/README.md +7 -8
- package/SECURITY.md +5 -3
- package/dist/akm +44 -33
- package/dist/akm-migrate-storage +44 -33
- package/dist/assets/backends/schtasks-template.xml +2 -1
- package/dist/assets/improve-strategies/catchup.json +3 -1
- package/dist/assets/improve-strategies/consolidate.json +3 -1
- package/dist/assets/improve-strategies/frequent.json +3 -1
- package/dist/assets/improve-strategies/graph-refresh.json +3 -1
- package/dist/assets/improve-strategies/memory-focus.json +4 -1
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -0
- package/dist/assets/improve-strategies/quick.json +3 -1
- package/dist/assets/improve-strategies/recombine-only.json +2 -0
- package/dist/assets/improve-strategies/reflect-distill.json +1 -0
- package/dist/assets/improve-strategies/synthesize.json +2 -0
- package/dist/assets/tasks/core/backup.yml +2 -2
- package/dist/cli/config-migrate.js +554 -26
- package/dist/cli.js +28 -11
- package/dist/commands/backup-cli.js +6 -4
- package/dist/commands/config-cli.js +10 -3
- package/dist/commands/feedback-cli.js +24 -7
- package/dist/commands/health/checks.js +94 -15
- package/dist/commands/health/html-report.js +23 -2
- package/dist/commands/health/improve-metrics.js +45 -6
- package/dist/commands/health/md-report.js +10 -1
- package/dist/commands/health/surfaces.js +2 -1
- package/dist/commands/health/windows.js +1 -1
- package/dist/commands/health.js +1 -1
- package/dist/commands/improve/anti-collapse.js +3 -3
- package/dist/commands/improve/collapse-detector.js +5 -5
- package/dist/commands/improve/consolidate.js +123 -66
- package/dist/commands/improve/distill/promote-memory.js +8 -6
- package/dist/commands/improve/distill/quality-gate.js +1 -1
- package/dist/commands/improve/distill-guards.js +1 -1
- package/dist/commands/improve/distill-promotion-policy.js +32 -26
- package/dist/commands/improve/distill.js +52 -36
- package/dist/commands/improve/eligibility.js +9 -8
- package/dist/commands/improve/extract-prompt.js +2 -2
- package/dist/commands/improve/extract.js +43 -11
- package/dist/commands/improve/improve-auto-accept.js +14 -28
- package/dist/commands/improve/improve-cli.js +4 -2
- package/dist/commands/improve/improve-strategies.js +2 -1
- package/dist/commands/improve/improve.js +49 -8
- package/dist/commands/improve/loop-stages.js +11 -6
- package/dist/commands/improve/preparation.js +51 -17
- package/dist/commands/improve/procedural.js +10 -6
- package/dist/commands/improve/recombine.js +31 -7
- package/dist/commands/improve/reflect.js +24 -14
- package/dist/commands/improve/salience.js +5 -1
- package/dist/commands/improve/source-identity.js +43 -0
- package/dist/commands/migrate-cli.js +36 -0
- package/dist/commands/mv-cli.js +647 -258
- package/dist/commands/proposal/drain.js +16 -4
- package/dist/commands/proposal/proposal-cli.js +3 -2
- package/dist/commands/proposal/proposal.js +16 -55
- package/dist/commands/proposal/repository.js +664 -58
- package/dist/commands/read/curate.js +4 -2
- package/dist/commands/read/knowledge.js +4 -1
- package/dist/commands/read/search.js +6 -2
- package/dist/commands/read/show.js +8 -7
- package/dist/commands/sources/self-update.js +156 -112
- package/dist/commands/sources/sources-cli.js +7 -2
- package/dist/commands/tasks/default-tasks.js +5 -5
- package/dist/commands/tasks/tasks-cli.js +7 -3
- package/dist/commands/tasks/tasks.js +261 -68
- package/dist/core/common.js +36 -3
- package/dist/core/config/config-io.js +2 -2
- package/dist/core/config/config-schema.js +15 -3
- package/dist/core/config/config.js +14 -13
- package/dist/core/file-lock.js +2 -2
- package/dist/core/improve-result.js +87 -7
- package/dist/core/migration-backup.js +816 -171
- package/dist/core/migration-operation.js +44 -0
- package/dist/core/state/migrations.js +4 -7
- package/dist/core/state-db.js +23 -7
- package/dist/core/write-source.js +86 -18
- package/dist/indexer/db/db.js +109 -53
- package/dist/indexer/index-writer-lock.js +38 -37
- package/dist/indexer/index-written-assets.js +73 -56
- package/dist/indexer/indexer.js +5 -1
- package/dist/indexer/search/search-source.js +2 -2
- package/dist/indexer/usage/usage-events.js +8 -2
- package/dist/integrations/agent/engine-resolution.js +14 -7
- package/dist/scripts/migrate-storage.js +887 -992
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +494 -574
- package/dist/setup/detected-engines.js +7 -13
- package/dist/setup/engine-config.js +14 -4
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/connection.js +1 -1
- package/dist/setup/steps/tasks.js +1 -1
- package/dist/sources/providers/git-stash.js +58 -21
- package/dist/sources/providers/git.js +1 -1
- package/dist/storage/engines/sqlite-migrations.js +138 -3
- package/dist/storage/repositories/events-repository.js +24 -0
- package/dist/storage/repositories/proposals-repository.js +14 -0
- package/dist/storage/repositories/task-history-repository.js +30 -3
- package/dist/tasks/backends/cron.js +71 -38
- package/dist/tasks/backends/exec-utils.js +55 -3
- package/dist/tasks/backends/launchd.js +241 -50
- package/dist/tasks/backends/schtasks.js +434 -59
- package/dist/tasks/command-executable.js +93 -0
- package/dist/tasks/embedded.js +2 -0
- package/dist/tasks/parser.js +142 -6
- package/dist/tasks/resolve-akm-bin.js +19 -4
- package/dist/tasks/runner.js +258 -93
- package/dist/tasks/schedule.js +108 -19
- package/dist/tasks/scheduler-invocation.js +86 -0
- package/dist/tasks/task-id.js +37 -0
- package/dist/workflows/db.js +24 -13
- package/dist/workflows/validator.js +24 -2
- package/docs/README.md +103 -0
- package/docs/migration/release-notes/0.9.0.md +28 -9
- package/docs/migration/v0.8-to-v0.9.md +186 -26
- package/package.json +2 -3
|
@@ -33,6 +33,7 @@ import { appendEvent } from "../../core/events.js";
|
|
|
33
33
|
import { createLockPayload, probeLock, reclaimStaleLock, releaseLock, tryAcquireLockSync, } from "../../core/file-lock.js";
|
|
34
34
|
import { tryAcquireMaintenanceBarrier } from "../../core/maintenance-barrier.js";
|
|
35
35
|
import { resolveStashStandards } from "../../core/standards/resolve-stash-standards.js";
|
|
36
|
+
import { resolveTypeConventions, typeConventionRef } from "../../core/standards/resolve-type-conventions.js";
|
|
36
37
|
import { getStateDbPath, openStateDatabase, withStateDb } from "../../core/state-db.js";
|
|
37
38
|
import { repairTruncatedDescription } from "../../core/text-truncation.js";
|
|
38
39
|
import { warn } from "../../core/warn.js";
|
|
@@ -244,15 +245,15 @@ function resolveHarness(type, harnesses) {
|
|
|
244
245
|
* so the accept-time descriptionQualityValidator passes — same pattern as
|
|
245
246
|
* the consolidate-writer fix at consolidate.ts.
|
|
246
247
|
*/
|
|
247
|
-
function buildCandidateProposal(candidate, sourceRef) {
|
|
248
|
-
const ref =
|
|
248
|
+
function buildCandidateProposal(candidate, sourceRef, sessionAssetRef) {
|
|
249
|
+
const ref = deriveExtractCandidateRef(candidate, sourceRef);
|
|
249
250
|
// Post-generation repair pass (#556): deterministically complete a
|
|
250
251
|
// description the LLM sliced mid-sentence before it reaches the
|
|
251
252
|
// auto-accept validators. No-op (byte-identical) for valid descriptions.
|
|
252
253
|
const description = repairTruncatedDescription(candidate.description, candidate.body);
|
|
253
254
|
const fm = {
|
|
254
255
|
description,
|
|
255
|
-
|
|
256
|
+
...(sessionAssetRef ? { xrefs: [sessionAssetRef] } : {}),
|
|
256
257
|
};
|
|
257
258
|
if (candidate.type === "lesson" && candidate.when_to_use) {
|
|
258
259
|
fm.when_to_use = candidate.when_to_use;
|
|
@@ -269,6 +270,36 @@ function buildCandidateProposal(candidate, sourceRef) {
|
|
|
269
270
|
const content = assembleAsset(fm, candidate.body);
|
|
270
271
|
return { ref, content, description };
|
|
271
272
|
}
|
|
273
|
+
function canonicalSegment(value) {
|
|
274
|
+
return value
|
|
275
|
+
.trim()
|
|
276
|
+
.toLowerCase()
|
|
277
|
+
.replace(/[^a-z0-9-]+/g, "-")
|
|
278
|
+
.replace(/-+/g, "-")
|
|
279
|
+
.replace(/^-|-$/g, "");
|
|
280
|
+
}
|
|
281
|
+
export function deriveExtractCandidateRef(candidate, sourceRef) {
|
|
282
|
+
const candidateParts = candidate.name.split("/").map(canonicalSegment).filter(Boolean);
|
|
283
|
+
const leaf = candidateParts.at(-1) ?? "extracted-insight";
|
|
284
|
+
if (candidate.type === "memory" || candidate.type === "lesson") {
|
|
285
|
+
const projectName = sourceRef.projectHint?.split(/[\\/]/).filter(Boolean).at(-1);
|
|
286
|
+
const scope = projectName ? canonicalSegment(projectName) : "";
|
|
287
|
+
return `${candidate.type}:${scope ? `${scope}/` : ""}${leaf}`;
|
|
288
|
+
}
|
|
289
|
+
return `knowledge:${leaf}`;
|
|
290
|
+
}
|
|
291
|
+
function resolveExtractStandards(stashDir) {
|
|
292
|
+
const sections = [];
|
|
293
|
+
const general = resolveStashStandards(stashDir);
|
|
294
|
+
if (general)
|
|
295
|
+
sections.push(general);
|
|
296
|
+
for (const type of ["memory", "lesson", "knowledge"]) {
|
|
297
|
+
const body = resolveTypeConventions(stashDir, type);
|
|
298
|
+
if (body)
|
|
299
|
+
sections.push(`# ${typeConventionRef(type)} (soft per-type conventions — guidance, not enforced)\n${body}`);
|
|
300
|
+
}
|
|
301
|
+
return sections.join("\n\n");
|
|
302
|
+
}
|
|
272
303
|
/**
|
|
273
304
|
* Canonicalize a session's content into a single deterministic string for
|
|
274
305
|
* hashing (#602). Each event is rendered `<role>\n<text>` and events are joined
|
|
@@ -473,10 +504,12 @@ standardsContext) {
|
|
|
473
504
|
}
|
|
474
505
|
const payload = parseExtractPayload(llmRaw);
|
|
475
506
|
const proposalIds = [];
|
|
507
|
+
// Provenance refs are added only after the cited session asset exists.
|
|
508
|
+
const sessionAsset = await maybeWriteSessionAsset();
|
|
476
509
|
if (payload.candidates.length === 0) {
|
|
477
510
|
appendEvent({
|
|
478
511
|
eventType: "extract_invoked",
|
|
479
|
-
ref:
|
|
512
|
+
...(sessionAsset.sessionAssetRef ? { ref: sessionAsset.sessionAssetRef } : {}),
|
|
480
513
|
metadata: {
|
|
481
514
|
outcome: "no_candidates",
|
|
482
515
|
sessionId: sessionRef.sessionId,
|
|
@@ -487,7 +520,6 @@ standardsContext) {
|
|
|
487
520
|
preFilterOutput: filtered.stats.outputCount,
|
|
488
521
|
},
|
|
489
522
|
}, ctx);
|
|
490
|
-
const sessionAsset = await maybeWriteSessionAsset();
|
|
491
523
|
return {
|
|
492
524
|
sessionId: sessionRef.sessionId,
|
|
493
525
|
harness: harness.name,
|
|
@@ -510,8 +542,9 @@ standardsContext) {
|
|
|
510
542
|
// dedup+quality pass promotes them. Default OFF.
|
|
511
543
|
// The invocation boundary resolves this from the frozen extract process.
|
|
512
544
|
for (const candidate of payload.candidates) {
|
|
545
|
+
const built = buildCandidateProposal(candidate, data.ref, sessionAsset.sessionAssetRef);
|
|
513
546
|
if (dryRun) {
|
|
514
|
-
proposalIds.push(`dry-run:${
|
|
547
|
+
proposalIds.push(`dry-run:${built.ref}`);
|
|
515
548
|
continue;
|
|
516
549
|
}
|
|
517
550
|
try {
|
|
@@ -527,7 +560,7 @@ standardsContext) {
|
|
|
527
560
|
const effectiveConfidence = gateResult.effectiveConfidence;
|
|
528
561
|
if (gateResult.warning)
|
|
529
562
|
warn(gateResult.warning);
|
|
530
|
-
const { ref, content, description } =
|
|
563
|
+
const { ref, content, description } = built;
|
|
531
564
|
const result = createProposal(stashDir, {
|
|
532
565
|
ref,
|
|
533
566
|
source: "extract",
|
|
@@ -538,7 +571,7 @@ standardsContext) {
|
|
|
538
571
|
description,
|
|
539
572
|
...(candidate.when_to_use ? { when_to_use: candidate.when_to_use } : {}),
|
|
540
573
|
...(effectiveConfidence !== undefined ? { confidence: effectiveConfidence } : {}),
|
|
541
|
-
|
|
574
|
+
...(sessionAsset.sessionAssetRef ? { xrefs: [sessionAsset.sessionAssetRef] } : {}),
|
|
542
575
|
evidence: candidate.evidence,
|
|
543
576
|
// #615 WS-0: mirror ordered-action + outcome data in the proposal
|
|
544
577
|
// frontmatter record so downstream tooling can read it without
|
|
@@ -569,7 +602,7 @@ standardsContext) {
|
|
|
569
602
|
}
|
|
570
603
|
appendEvent({
|
|
571
604
|
eventType: "extract_invoked",
|
|
572
|
-
ref:
|
|
605
|
+
...(sessionAsset.sessionAssetRef ? { ref: sessionAsset.sessionAssetRef } : {}),
|
|
573
606
|
metadata: {
|
|
574
607
|
outcome: "candidates_queued",
|
|
575
608
|
sessionId: sessionRef.sessionId,
|
|
@@ -581,7 +614,6 @@ standardsContext) {
|
|
|
581
614
|
preFilterOutput: filtered.stats.outputCount,
|
|
582
615
|
},
|
|
583
616
|
}, ctx);
|
|
584
|
-
const sessionAsset = await maybeWriteSessionAsset();
|
|
585
617
|
return {
|
|
586
618
|
sessionId: sessionRef.sessionId,
|
|
587
619
|
harness: harness.name,
|
|
@@ -827,7 +859,7 @@ export async function akmExtract(options) {
|
|
|
827
859
|
// Stash authoring standards (convention/meta fact bodies) for non-wiki
|
|
828
860
|
// extract output. Resolved ONCE per run and threaded into each session's
|
|
829
861
|
// prompt so facts are not re-read per session.
|
|
830
|
-
const extractStandardsContext =
|
|
862
|
+
const extractStandardsContext = resolveExtractStandards(stashDir);
|
|
831
863
|
for (const summary of candidates) {
|
|
832
864
|
// #602 — the already-extracted skip moved INTO processSession (the content
|
|
833
865
|
// hash needs the session body, only available after readSession). The prior
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import { loadConfig } from "../../core/config/config.js";
|
|
5
5
|
import { UsageError } from "../../core/errors.js";
|
|
6
|
-
import { appendEvent } from "../../core/events.js";
|
|
7
6
|
import { withStateDb } from "../../core/state-db.js";
|
|
8
7
|
import { info, warn } from "../../core/warn.js";
|
|
9
8
|
import { getPhaseThreshold } from "../../storage/repositories/improve-runs-repository.js";
|
|
@@ -119,42 +118,29 @@ export async function runAutoAcceptGate(candidates, cfg, promoteFn = promoteProp
|
|
|
119
118
|
continue;
|
|
120
119
|
}
|
|
121
120
|
try {
|
|
122
|
-
const
|
|
123
|
-
stamp(promotion.proposal.id, {
|
|
124
|
-
outcome: "auto-accepted",
|
|
125
|
-
reason: promoteReason,
|
|
126
|
-
confidence,
|
|
127
|
-
thresholds: { autoAccept: effectiveThreshold },
|
|
128
|
-
...(currentContentHash !== undefined ? { contentHash: currentContentHash } : {}),
|
|
129
|
-
gate: gateLabel,
|
|
130
|
-
});
|
|
131
|
-
// Resolve the eligibilitySource: exploration-promoted proposals get
|
|
132
|
-
// eligibilitySource="exploration" (WS-4); normal auto-accepts carry
|
|
133
|
-
// whatever the proposal was tagged with at selection time.
|
|
121
|
+
const target = resolvedConfig.defaultWriteTarget;
|
|
134
122
|
const resolvedEligibilitySource = isExploration
|
|
135
123
|
? "exploration"
|
|
136
|
-
:
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
metadata: {
|
|
141
|
-
proposalId: promotion.proposal.id,
|
|
142
|
-
source: promotion.proposal.source,
|
|
143
|
-
...(promotion.proposal.sourceRun !== undefined ? { sourceRun: promotion.proposal.sourceRun } : {}),
|
|
144
|
-
assetPath: promotion.assetPath,
|
|
124
|
+
: currentProposal?.eligibilitySource;
|
|
125
|
+
const promotion = await promoteFn(cfg.stashDir, resolvedConfig, proposalId, {
|
|
126
|
+
...(target ? { target } : {}),
|
|
127
|
+
eventMetadata: {
|
|
145
128
|
autoAccept: true,
|
|
146
129
|
confidence,
|
|
147
130
|
threshold: effectiveThreshold,
|
|
148
131
|
phase: cfg.phase,
|
|
149
|
-
// Attribution tagging: carry the eligibility lane from the proposal
|
|
150
|
-
// record onto the auto-accept promoted event so the lane survives to
|
|
151
|
-
// accept time even when promotion happens in a later run.
|
|
152
132
|
...(resolvedEligibilitySource !== undefined ? { eligibilitySource: resolvedEligibilitySource } : {}),
|
|
153
|
-
// WS-4: mark exploration promotions so health/telemetry can
|
|
154
|
-
// distinguish them from calibration-signal promotions.
|
|
155
133
|
...(isExploration ? { explorationBudget: true } : {}),
|
|
156
134
|
},
|
|
157
|
-
},
|
|
135
|
+
}, undefined);
|
|
136
|
+
stamp(promotion.proposal.id, {
|
|
137
|
+
outcome: "auto-accepted",
|
|
138
|
+
reason: promoteReason,
|
|
139
|
+
confidence,
|
|
140
|
+
thresholds: { autoAccept: effectiveThreshold },
|
|
141
|
+
...(currentContentHash !== undefined ? { contentHash: currentContentHash } : {}),
|
|
142
|
+
gate: gateLabel,
|
|
143
|
+
});
|
|
158
144
|
if (isExploration) {
|
|
159
145
|
info(`[improve] exploration-accepted ${promotion.ref} (${cfg.phase}; confidence=${confidence.toFixed(2)}; budgetRemaining=${explorationRemaining})`);
|
|
160
146
|
}
|
|
@@ -11,8 +11,8 @@ import { getCacheDir } from "../../core/paths.js";
|
|
|
11
11
|
import { redactSensitiveText } from "../../core/redaction.js";
|
|
12
12
|
import { withStateDb } from "../../core/state-db.js";
|
|
13
13
|
import { clearLogFile, setLogFile } from "../../core/warn.js";
|
|
14
|
+
import { resolveWriteTarget } from "../../core/write-source.js";
|
|
14
15
|
import { closeDatabase, openExistingDatabase } from "../../indexer/db/db.js";
|
|
15
|
-
import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
16
16
|
import { collectEngineCredentialValues } from "../../integrations/agent/engine-resolution.js";
|
|
17
17
|
import { parseFlagValue } from "../../output/context.js";
|
|
18
18
|
import { getActiveCanaries, queryRecentCycleMetrics } from "../../storage/repositories/canaries-repository.js";
|
|
@@ -176,6 +176,7 @@ export const improveCommand = defineCommand({
|
|
|
176
176
|
const skipIfLocked = args["skip-if-locked"];
|
|
177
177
|
const strategyArg = getStringArg(args, "strategy");
|
|
178
178
|
const effectiveConfig = loadConfig();
|
|
179
|
+
const writeTarget = resolveWriteTarget(effectiveConfig, targetArg, { requireWritable: !dryRun });
|
|
179
180
|
// Resolve every enabled model-backed process before logging, signal
|
|
180
181
|
// lifecycle setup, or any filesystem/database side effect.
|
|
181
182
|
const resolvedPlan = resolveImprovePlan(strategyArg, effectiveConfig);
|
|
@@ -202,7 +203,7 @@ export const improveCommand = defineCommand({
|
|
|
202
203
|
// was minted at end-of-run, so SIGTERM'd runs (cron timeout) left no
|
|
203
204
|
// row in improve_runs and effectively disappeared from `akm health`.
|
|
204
205
|
const runId = buildImproveRunId();
|
|
205
|
-
const primaryStashDir =
|
|
206
|
+
const primaryStashDir = writeTarget.source.path;
|
|
206
207
|
const scopeArg = getStringArg(args, "scope");
|
|
207
208
|
const inferredScopeMode = (scopeArg ?? "").includes(":") ? "ref" : scopeArg ? "type" : "all";
|
|
208
209
|
// Signal handler + exception path both flow through this helper so
|
|
@@ -248,6 +249,7 @@ export const improveCommand = defineCommand({
|
|
|
248
249
|
dryRun,
|
|
249
250
|
resolvedPlan,
|
|
250
251
|
target: targetArg,
|
|
252
|
+
writeTarget,
|
|
251
253
|
autoAccept,
|
|
252
254
|
...(runId !== undefined ? { runId } : {}),
|
|
253
255
|
...(limitRaw !== undefined ? { limit: limitRaw } : {}),
|
|
@@ -67,7 +67,8 @@ export function resolveImproveStrategy(name, config) {
|
|
|
67
67
|
const valid = [...new Set([...Object.keys(BUILTIN_STRATEGIES), ...Object.keys(userStrategies)])].sort();
|
|
68
68
|
throw new ConfigError(`Improve strategy "${selectedName}" not found. Valid strategies: ${valid.join(", ")}.`, "UNKNOWN_IMPROVE_STRATEGY");
|
|
69
69
|
}
|
|
70
|
-
const
|
|
70
|
+
const selectedStrategy = BUILTIN_STRATEGIES[selectedName] ?? {};
|
|
71
|
+
const baseStrategy = deepMergeConfig(BUILTIN_STRATEGIES.default, selectedStrategy);
|
|
71
72
|
const resolved = deepMergeConfig(baseStrategy, (userStrategies[selectedName] ?? {}));
|
|
72
73
|
return { name: selectedName, config: resolved };
|
|
73
74
|
}
|
|
@@ -13,6 +13,7 @@ import { getDbPath, getStateDbPathInDataDir } from "../../core/paths.js";
|
|
|
13
13
|
import { redactSensitiveText } from "../../core/redaction.js";
|
|
14
14
|
import { openStateDatabase } from "../../core/state-db.js";
|
|
15
15
|
import { info, warn } from "../../core/warn.js";
|
|
16
|
+
import { resolveWritable, resolveWriteTarget } from "../../core/write-source.js";
|
|
16
17
|
import { closeDatabase, getEntryCount, openExistingDatabase } from "../../indexer/db/db.js";
|
|
17
18
|
import { ensureIndex } from "../../indexer/ensure-index.js";
|
|
18
19
|
import { akmIndex } from "../../indexer/indexer.js";
|
|
@@ -21,7 +22,7 @@ import { collectEngineCredentialValues } from "../../integrations/agent/engine-r
|
|
|
21
22
|
import { materializeLlmRunnerConnection } from "../../integrations/agent/runner.js";
|
|
22
23
|
import { installLlmUsagePersistence } from "../../llm/usage-persist.js";
|
|
23
24
|
import { withLlmStage } from "../../llm/usage-telemetry.js";
|
|
24
|
-
import { isGitBackedStash, resolveWritableOverride, saveGitStash } from "../../sources/providers/git.js";
|
|
25
|
+
import { isGitBackedStash, listGitChangedPaths, resolveWritableOverride, saveGitStash, } from "../../sources/providers/git.js";
|
|
25
26
|
import { drainProposals } from "../proposal/drain.js";
|
|
26
27
|
import { resolveDrainPolicy } from "../proposal/drain-policies.js";
|
|
27
28
|
import { akmDistill } from "./distill.js";
|
|
@@ -41,6 +42,7 @@ import { runImprovePreparationStage } from "./preparation.js";
|
|
|
41
42
|
import { DEFAULT_DUE_DAYS, filterProactiveDue } from "./proactive-maintenance.js";
|
|
42
43
|
import { akmReflect } from "./reflect.js";
|
|
43
44
|
import { errMessage } from "./shared.js";
|
|
45
|
+
import { shouldReadLegacyBareImproveState } from "./source-identity.js";
|
|
44
46
|
export { resetHeldProcessLocks } from "./locks.js";
|
|
45
47
|
// Re-exported from ./loop-stages for test importers (improve-db-locking).
|
|
46
48
|
export { runImproveMaintenancePasses } from "./loop-stages.js";
|
|
@@ -99,7 +101,14 @@ export async function akmImprove(options = {}) {
|
|
|
99
101
|
const runImprovePostLoopStageImpl = options.runImprovePostLoopStageFn ?? runImprovePostLoopStage;
|
|
100
102
|
// Resolve the improve profile for this run. Profile drives type filtering,
|
|
101
103
|
// process gating, and default autoAccept/limit values.
|
|
102
|
-
|
|
104
|
+
let _earlyConfig = options.config ?? loadConfig();
|
|
105
|
+
const writeTarget = options.writeTarget ??
|
|
106
|
+
(options.target
|
|
107
|
+
? resolveWriteTarget(_earlyConfig, options.target, { requireWritable: !options.dryRun })
|
|
108
|
+
: undefined);
|
|
109
|
+
if (writeTarget && options.target) {
|
|
110
|
+
_earlyConfig = { ..._earlyConfig, defaultWriteTarget: writeTarget.source.name };
|
|
111
|
+
}
|
|
103
112
|
const resolvedPlan = options.resolvedPlan ??
|
|
104
113
|
resolveImprovePlan(options.strategy, _earlyConfig, {
|
|
105
114
|
repairValidationFailures: options.repairValidationFailures,
|
|
@@ -115,6 +124,20 @@ export async function akmImprove(options = {}) {
|
|
|
115
124
|
// Pin nested calls and quality gates to the same config snapshot as the
|
|
116
125
|
// invocation plan. They must never reload a changed config mid-run.
|
|
117
126
|
config: _earlyConfig,
|
|
127
|
+
...(writeTarget
|
|
128
|
+
? {
|
|
129
|
+
target: writeTarget.source.name,
|
|
130
|
+
writeTarget,
|
|
131
|
+
stashDir: writeTarget.source.path,
|
|
132
|
+
consolidateOptions: {
|
|
133
|
+
...options.consolidateOptions,
|
|
134
|
+
target: writeTarget.source.name,
|
|
135
|
+
writeTarget,
|
|
136
|
+
},
|
|
137
|
+
}
|
|
138
|
+
: {}),
|
|
139
|
+
legacyBareState: options.legacyBareState ??
|
|
140
|
+
shouldReadLegacyBareImproveState(writeTarget?.source.name, writeTarget?.source.path, _earlyConfig),
|
|
118
141
|
autoAccept: options.autoAccept ?? improveProfile.autoAccept,
|
|
119
142
|
// Profile-level limit, then process-level reflect.limit as fallback.
|
|
120
143
|
// CLI --limit takes precedence over both.
|
|
@@ -130,6 +153,8 @@ export async function akmImprove(options = {}) {
|
|
|
130
153
|
catch {
|
|
131
154
|
primaryStashDir = undefined;
|
|
132
155
|
}
|
|
156
|
+
const syncRepoDir = writeTarget?.source.repoPath ?? primaryStashDir;
|
|
157
|
+
const initialGitPaths = syncRepoDir && isGitBackedStash(syncRepoDir) ? new Set(listGitChangedPaths(syncRepoDir)) : new Set();
|
|
133
158
|
// C2 (#553/#554/#499): resolve the state.db path ONCE, synchronously, at the
|
|
134
159
|
// command boundary — before the first `await` below. Every state.db open in
|
|
135
160
|
// this run (`openStateDatabase`, every default-path `appendEvent`) is pinned
|
|
@@ -310,6 +335,8 @@ export async function akmImprove(options = {}) {
|
|
|
310
335
|
const maxAccepts = triageConfig?.maxAcceptsPerRun ?? 25;
|
|
311
336
|
triageDrain = await drainProposalsFn({
|
|
312
337
|
stashDir: primaryStashDir,
|
|
338
|
+
...(options.target ? { target: options.target } : {}),
|
|
339
|
+
config: options.config,
|
|
313
340
|
policy,
|
|
314
341
|
applyMode,
|
|
315
342
|
maxAccepts,
|
|
@@ -412,15 +439,29 @@ export async function akmImprove(options = {}) {
|
|
|
412
439
|
// after the db-backed context is installed inside the try use the live handle.
|
|
413
440
|
const effectiveSync = { ...improveProfile.sync, ...options.sync };
|
|
414
441
|
const commitStashBatch = (messageContext) => {
|
|
415
|
-
|
|
442
|
+
const repoDir = writeTarget?.source.repoPath ?? primaryStashDir;
|
|
443
|
+
if (!primaryStashDir || !repoDir || effectiveSync.enabled === false || !isGitBackedStash(repoDir)) {
|
|
416
444
|
return undefined;
|
|
417
445
|
}
|
|
418
446
|
const saveGitStashFn = options.saveGitStashFn ?? saveGitStash;
|
|
419
|
-
const writableOverride = resolveWritableOverride(_earlyConfig);
|
|
420
|
-
const push =
|
|
447
|
+
const writableOverride = writeTarget ? resolveWritable(writeTarget.config) : resolveWritableOverride(_earlyConfig);
|
|
448
|
+
const push = options.sync?.push ?? writeTarget?.config.options?.pushOnCommit ?? improveProfile.sync?.push ?? true;
|
|
421
449
|
const message = renderSyncCommitMessage(effectiveSync.message ?? "akm improve auto-sync", messageContext, Date.now());
|
|
422
450
|
try {
|
|
423
|
-
const
|
|
451
|
+
const assetRoot = writeTarget?.source.path ?? primaryStashDir;
|
|
452
|
+
const assetPrefix = assetRoot ? path.relative(repoDir, assetRoot).replaceAll(path.sep, "/") : "";
|
|
453
|
+
const paths = listGitChangedPaths(repoDir).filter((changedPath) => {
|
|
454
|
+
if (initialGitPaths.has(changedPath))
|
|
455
|
+
return false;
|
|
456
|
+
if (path.basename(changedPath).includes(".lock"))
|
|
457
|
+
return false;
|
|
458
|
+
return !assetPrefix || changedPath === assetPrefix || changedPath.startsWith(`${assetPrefix}/`);
|
|
459
|
+
});
|
|
460
|
+
const syncResult = saveGitStashFn(undefined, message, writableOverride, {
|
|
461
|
+
push,
|
|
462
|
+
repoDir,
|
|
463
|
+
paths,
|
|
464
|
+
});
|
|
424
465
|
appendEvent({
|
|
425
466
|
eventType: "stash_synced",
|
|
426
467
|
metadata: {
|
|
@@ -621,8 +662,8 @@ export async function akmImprove(options = {}) {
|
|
|
621
662
|
let postLockLoopRefs = preparation.loopRefs;
|
|
622
663
|
if (proactiveLoopRefs.length > 0) {
|
|
623
664
|
const proactiveRefStrs = proactiveLoopRefs.map((r) => r.ref);
|
|
624
|
-
const freshReflectTs = buildLatestProposalTsMap(proactiveRefStrs, "reflect");
|
|
625
|
-
const freshDistillTs = buildLatestProposalTsMap(proactiveRefStrs, "distill");
|
|
665
|
+
const freshReflectTs = buildLatestProposalTsMap(proactiveRefStrs, "reflect", options.target, options.legacyBareState);
|
|
666
|
+
const freshDistillTs = buildLatestProposalTsMap(proactiveRefStrs, "distill", options.target, options.legacyBareState);
|
|
626
667
|
const pmDueDays = improveProfile.processes?.proactiveMaintenance?.dueDays ?? DEFAULT_DUE_DAYS;
|
|
627
668
|
const stillDue = new Set(filterProactiveDue(proactiveLoopRefs, freshReflectTs, freshDistillTs, pmDueDays, Date.now()).map((r) => r.ref));
|
|
628
669
|
const dropped = proactiveLoopRefs.filter((r) => !stillDue.has(r.ref));
|
|
@@ -38,6 +38,7 @@ import { akmProcedural } from "./procedural.js";
|
|
|
38
38
|
import { akmRecombine } from "./recombine.js";
|
|
39
39
|
import { recordNoOp, resetConsecutiveNoOps } from "./salience.js";
|
|
40
40
|
import { errMessage, refSlug } from "./shared.js";
|
|
41
|
+
import { durableImproveRef } from "./source-identity.js";
|
|
41
42
|
// ── improve loop / post-loop / maintenance stages ───────────────────
|
|
42
43
|
// The cycle stages run by akmImprove, extracted from improve.ts.
|
|
43
44
|
export async function runImproveLoopStage(args) {
|
|
@@ -210,11 +211,13 @@ export async function runImproveLoopStage(args) {
|
|
|
210
211
|
const reflectProfileRunner = resolvedPlan.processes.reflect.runner;
|
|
211
212
|
const reflectCallArgs = {
|
|
212
213
|
ref: planned.ref,
|
|
214
|
+
...(options.target ? { sourceName: options.target } : {}),
|
|
215
|
+
...(options.legacyBareState ? { legacyBareState: true } : {}),
|
|
213
216
|
task: options.task,
|
|
214
217
|
// Active strategy supplies non-engine process tuning.
|
|
215
218
|
...(improveProfile ? { improveProfile } : {}),
|
|
216
219
|
config: options.config,
|
|
217
|
-
...(
|
|
220
|
+
...(primaryStashDir ? { stashDir: primaryStashDir } : {}),
|
|
218
221
|
...(reflectErrors.length > 0 ? { avoidPatterns: [...reflectErrors] } : {}),
|
|
219
222
|
eventSource: "improve",
|
|
220
223
|
// #639 — resolve the low-value filter from the ACTIVE improve profile
|
|
@@ -347,7 +350,7 @@ export async function runImproveLoopStage(args) {
|
|
|
347
350
|
// the asset changed; reset the counter so the dampener lifts.
|
|
348
351
|
if (isNoChange && eventsCtx?.db) {
|
|
349
352
|
try {
|
|
350
|
-
recordNoOp(eventsCtx.db, planned.ref);
|
|
353
|
+
recordNoOp(eventsCtx.db, durableImproveRef(planned.ref, options.target));
|
|
351
354
|
}
|
|
352
355
|
catch {
|
|
353
356
|
// best-effort: plasticity counter failure never blocks the run
|
|
@@ -355,7 +358,7 @@ export async function runImproveLoopStage(args) {
|
|
|
355
358
|
}
|
|
356
359
|
else if (reflectResult.ok && eventsCtx?.db) {
|
|
357
360
|
try {
|
|
358
|
-
resetConsecutiveNoOps(eventsCtx.db, planned.ref);
|
|
361
|
+
resetConsecutiveNoOps(eventsCtx.db, durableImproveRef(planned.ref, options.target));
|
|
359
362
|
}
|
|
360
363
|
catch {
|
|
361
364
|
// best-effort
|
|
@@ -482,8 +485,10 @@ export async function runImproveLoopStage(args) {
|
|
|
482
485
|
}
|
|
483
486
|
const distillResult = await withLlmStage("distill", () => distillFn({
|
|
484
487
|
ref: planned.ref,
|
|
488
|
+
...(options.target ? { sourceName: options.target } : {}),
|
|
489
|
+
...(options.legacyBareState ? { legacyBareState: true } : {}),
|
|
485
490
|
...(parsedPlannedRef.type === "memory" ? { proposalKind: "auto" } : {}),
|
|
486
|
-
...(
|
|
491
|
+
...(primaryStashDir ? { stashDir: primaryStashDir } : {}),
|
|
487
492
|
// Active profile so distill's per-process reads honor `--profile`.
|
|
488
493
|
...(improveProfile ? { improveProfile } : {}),
|
|
489
494
|
config: options.config,
|
|
@@ -512,10 +517,10 @@ export async function runImproveLoopStage(args) {
|
|
|
512
517
|
if (eventsCtx?.db) {
|
|
513
518
|
try {
|
|
514
519
|
if (distillResult.outcome === "quality_rejected" || distillResult.outcome === "skipped") {
|
|
515
|
-
recordNoOp(eventsCtx.db, planned.ref);
|
|
520
|
+
recordNoOp(eventsCtx.db, durableImproveRef(planned.ref, options.target));
|
|
516
521
|
}
|
|
517
522
|
else if (distillResult.outcome === "queued") {
|
|
518
|
-
resetConsecutiveNoOps(eventsCtx.db, planned.ref);
|
|
523
|
+
resetConsecutiveNoOps(eventsCtx.db, durableImproveRef(planned.ref, options.target));
|
|
519
524
|
}
|
|
520
525
|
}
|
|
521
526
|
catch {
|
|
@@ -31,7 +31,19 @@ import { makeGateConfig, resolveExtractConfidence, runAutoAcceptGate } from "./i
|
|
|
31
31
|
import { applyMemoryCleanup } from "./memory/memory-improve.js";
|
|
32
32
|
import { computeProxyAdequacy, getAllAssetOutcomes, getOutcomeScoresByRef, OUTCOME_SCORE_MAX, outcomeScoreToSalience, updateAssetOutcome, } from "./outcome-loop.js";
|
|
33
33
|
import { DEFAULT_DUE_DAYS, DEFAULT_MAX_PER_RUN, selectProactiveMaintenanceRefs } from "./proactive-maintenance.js";
|
|
34
|
-
import { buildRankChangeReport, computeSalience, getAllRankScores, getAssetSalience,
|
|
34
|
+
import { buildRankChangeReport, computeSalience, getAllRankScores, getAssetSalience, getLastUseMsByRef, isContentEncodingRow, SALIENCE_NO_OP_DAMPEN_FACTOR, SALIENCE_NO_OP_DAMPEN_THRESHOLD, upsertAssetSalience, } from "./salience.js";
|
|
35
|
+
import { bareImproveRef, durableImproveRef, improveStateReadRefs } from "./source-identity.js";
|
|
36
|
+
function readAssetSalienceForImproveRef(db, ref, sourceName, includeLegacyBare = false) {
|
|
37
|
+
for (const key of improveStateReadRefs(ref, sourceName, includeLegacyBare)) {
|
|
38
|
+
const row = getAssetSalience(db, key);
|
|
39
|
+
if (row)
|
|
40
|
+
return row;
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
function readConsecutiveNoOpsForImproveRef(db, ref, sourceName, includeLegacyBare = false) {
|
|
45
|
+
return readAssetSalienceForImproveRef(db, ref, sourceName, includeLegacyBare)?.consecutive_no_ops ?? 0;
|
|
46
|
+
}
|
|
35
47
|
// ── improve preparation stage ───────────────────────
|
|
36
48
|
// The pre-loop preparation pipeline (consolidation, session-extract, validation/
|
|
37
49
|
// repair, eligibility partitioning, selectors) extracted from improve.ts.
|
|
@@ -814,9 +826,9 @@ export async function runImprovePreparationStage(args) {
|
|
|
814
826
|
// Per-ref queries would be N+1 and the planner is already the hottest path
|
|
815
827
|
// in `akm improve`.
|
|
816
828
|
const candidateRefs = postCleanupRefs.filter((r) => !validationFailureRefs.has(r.ref)).map((r) => r.ref);
|
|
817
|
-
const latestFeedbackTs = buildLatestFeedbackTsMap(candidateRefs, feedbackSinceCutoff);
|
|
818
|
-
const lastReflectProposalTs = buildLatestProposalTsMap(candidateRefs, "reflect");
|
|
819
|
-
const lastDistillProposalTs = buildLatestProposalTsMap(candidateRefs, "distill");
|
|
829
|
+
const latestFeedbackTs = buildLatestFeedbackTsMap(candidateRefs, feedbackSinceCutoff, options.target, options.legacyBareState);
|
|
830
|
+
const lastReflectProposalTs = buildLatestProposalTsMap(candidateRefs, "reflect", options.target, options.legacyBareState);
|
|
831
|
+
const lastDistillProposalTs = buildLatestProposalTsMap(candidateRefs, "distill", options.target, options.legacyBareState);
|
|
820
832
|
// Refs the distill signal-delta gate rejected at planning time. The main
|
|
821
833
|
// loop reads this to skip distill for these refs without re-checking
|
|
822
834
|
// eligibility per iteration.
|
|
@@ -951,15 +963,17 @@ export async function runImprovePreparationStage(args) {
|
|
|
951
963
|
// to feedbackSinceCutoff (same as the old inline `(e.ts ?? "") >= cutoff` guard).
|
|
952
964
|
const feedbackSummary = new Map();
|
|
953
965
|
{
|
|
954
|
-
const
|
|
966
|
+
const feedbackCandidateRefs = [...processableRefs, ...noFeedbackPool].map((r) => r.ref);
|
|
967
|
+
const feedbackCandidateSet = new Set(feedbackCandidateRefs);
|
|
968
|
+
const feedbackRefByDurableKey = new Map(feedbackCandidateRefs.flatMap((ref) => improveStateReadRefs(ref, options.target, options.legacyBareState).map((key) => [key, ref])));
|
|
955
969
|
if (feedbackCandidateSet.size > 0) {
|
|
956
970
|
// Fetch ALL feedback events in one query (no ref filter, no since filter =
|
|
957
971
|
// single full table scan). Filtering per-ref in memory avoids N sequential
|
|
958
972
|
// state.db opens — the dominant FD-leak path on large stashes.
|
|
959
973
|
const { events: allFeedbackEvents } = readEvents({ type: "feedback" }, eventsCtx);
|
|
960
974
|
for (const e of allFeedbackEvents) {
|
|
961
|
-
const ref = e.ref;
|
|
962
|
-
if (!ref
|
|
975
|
+
const ref = e.ref ? feedbackRefByDurableKey.get(e.ref) : undefined;
|
|
976
|
+
if (!ref)
|
|
963
977
|
continue;
|
|
964
978
|
const entry = feedbackSummary.get(ref) ?? { hasSignal: false, positive: 0, negative: 0 };
|
|
965
979
|
const meta = e.metadata;
|
|
@@ -1021,8 +1035,12 @@ export async function runImprovePreparationStage(args) {
|
|
|
1021
1035
|
// heavily-retrieved, one never-touched — would receive identical rankScores.
|
|
1022
1036
|
// Fix (WS-1 blocker 3): union the feedback pool into the lookup.
|
|
1023
1037
|
const allCandidateRefs = [...new Set([...signalFiltered, ...noFeedbackCandidates].map((r) => r.ref))];
|
|
1024
|
-
retrievalCounts = getRetrievalCounts(dbForRetrieval, allCandidateRefs
|
|
1025
|
-
|
|
1038
|
+
retrievalCounts = getRetrievalCounts(dbForRetrieval, allCandidateRefs, {
|
|
1039
|
+
sourceName: options.target,
|
|
1040
|
+
stashDir: primaryStashDir,
|
|
1041
|
+
includeLegacyBare: options.legacyBareState || !options.target,
|
|
1042
|
+
});
|
|
1043
|
+
lastUseMsForProactive = getLastUseMsByRef(dbForRetrieval, noFeedbackCandidates.map((r) => r.ref), primaryStashDir);
|
|
1026
1044
|
// High-retrieval signal-delta (simplified rule, 0.8.0): a no-feedback
|
|
1027
1045
|
// ref qualifies exactly once — when it has actually been retrieved
|
|
1028
1046
|
// (retrievalCount ≥ 1) AND retrievalCount ≥ threshold AND no prior reflect
|
|
@@ -1160,7 +1178,7 @@ export async function runImprovePreparationStage(args) {
|
|
|
1160
1178
|
// found later in the scan lost its slot to an earlier lower-scoring one.
|
|
1161
1179
|
const qualifying = [];
|
|
1162
1180
|
for (const r of candidates) {
|
|
1163
|
-
const row =
|
|
1181
|
+
const row = readAssetSalienceForImproveRef(dbForHighSalience, r.ref, options.target, options.legacyBareState);
|
|
1164
1182
|
if (row &&
|
|
1165
1183
|
isContentEncodingRow(row, parseAssetRef(r.ref).type) &&
|
|
1166
1184
|
row.encoding_salience >= salienceThreshold &&
|
|
@@ -1283,7 +1301,7 @@ export async function runImprovePreparationStage(args) {
|
|
|
1283
1301
|
let dbForSalience;
|
|
1284
1302
|
try {
|
|
1285
1303
|
dbForSalience = openExistingDatabase();
|
|
1286
|
-
lastUseMsByRef = getLastUseMsByRef(dbForSalience, mergedRefs.map((r) => r.ref));
|
|
1304
|
+
lastUseMsByRef = getLastUseMsByRef(dbForSalience, mergedRefs.map((r) => r.ref), primaryStashDir);
|
|
1287
1305
|
}
|
|
1288
1306
|
catch (err) {
|
|
1289
1307
|
rethrowIfTestIsolationError(err);
|
|
@@ -1439,7 +1457,7 @@ export async function runImprovePreparationStage(args) {
|
|
|
1439
1457
|
withStateDb((dbForStoredEncoding) => {
|
|
1440
1458
|
for (const r of mergedRefs) {
|
|
1441
1459
|
const type = r.ref.includes(":") ? r.ref.slice(0, r.ref.indexOf(":")) : "";
|
|
1442
|
-
const row =
|
|
1460
|
+
const row = readAssetSalienceForImproveRef(dbForStoredEncoding, r.ref, options.target, options.legacyBareState);
|
|
1443
1461
|
if (row && isContentEncodingRow(row, type)) {
|
|
1444
1462
|
storedEncodingByRef.set(r.ref, row.encoding_salience);
|
|
1445
1463
|
}
|
|
@@ -1532,7 +1550,15 @@ export async function runImprovePreparationStage(args) {
|
|
|
1532
1550
|
// Step 7: stash-wide rank-change report BEFORE overwriting the table.
|
|
1533
1551
|
//
|
|
1534
1552
|
// Load ALL existing rows so rank positions are stash-relative, not pool-relative.
|
|
1535
|
-
const
|
|
1553
|
+
const allStoredScores = getAllRankScores(stateDb);
|
|
1554
|
+
const existingAllScores = options.target
|
|
1555
|
+
? new Map([...allStoredScores].flatMap(([ref, score]) => {
|
|
1556
|
+
const parsed = parseAssetRef(ref);
|
|
1557
|
+
return parsed.origin === options.target || (options.legacyBareState && !parsed.origin)
|
|
1558
|
+
? [[bareImproveRef(ref), score]]
|
|
1559
|
+
: [];
|
|
1560
|
+
}))
|
|
1561
|
+
: allStoredScores;
|
|
1536
1562
|
if (existingAllScores.size === 0) {
|
|
1537
1563
|
// Scenario A: first WS-1 run — table empty.
|
|
1538
1564
|
//
|
|
@@ -1629,7 +1655,7 @@ export async function runImprovePreparationStage(args) {
|
|
|
1629
1655
|
}, eventsCtx);
|
|
1630
1656
|
}
|
|
1631
1657
|
for (const [ref, vector] of salienceMap) {
|
|
1632
|
-
upsertAssetSalience(stateDb, ref, vector, nowForSalience);
|
|
1658
|
+
upsertAssetSalience(stateDb, durableImproveRef(ref, options.target), vector, nowForSalience);
|
|
1633
1659
|
}
|
|
1634
1660
|
}, { path: eventsCtx?.dbPath });
|
|
1635
1661
|
}
|
|
@@ -1715,7 +1741,15 @@ export async function runImprovePreparationStage(args) {
|
|
|
1715
1741
|
try {
|
|
1716
1742
|
withStateDb((replayDb) => {
|
|
1717
1743
|
const alreadyInPool = new Set(mergedRefs.map((r) => r.ref));
|
|
1718
|
-
const
|
|
1744
|
+
const storedRankScores = getAllRankScores(replayDb);
|
|
1745
|
+
const allRankScores = options.target
|
|
1746
|
+
? new Map([...storedRankScores].flatMap(([ref, score]) => {
|
|
1747
|
+
const parsed = parseAssetRef(ref);
|
|
1748
|
+
return parsed.origin === options.target || (options.legacyBareState && !parsed.origin)
|
|
1749
|
+
? [[bareImproveRef(ref), score]]
|
|
1750
|
+
: [];
|
|
1751
|
+
}))
|
|
1752
|
+
: storedRankScores;
|
|
1719
1753
|
// Candidate universe = every salience row NOT already in the pool, ordered by
|
|
1720
1754
|
// rank_score desc with a deterministic ref-string tie-break (mirrors the main
|
|
1721
1755
|
// sort). Converged refs (consecutive_no_ops >= dampener threshold) are fully
|
|
@@ -1725,7 +1759,7 @@ export async function runImprovePreparationStage(args) {
|
|
|
1725
1759
|
for (const [ref, rankScore] of allRankScores) {
|
|
1726
1760
|
if (alreadyInPool.has(ref))
|
|
1727
1761
|
continue;
|
|
1728
|
-
const noOps =
|
|
1762
|
+
const noOps = readConsecutiveNoOpsForImproveRef(replayDb, ref, options.target, options.legacyBareState);
|
|
1729
1763
|
if (noOps >= SALIENCE_NO_OP_DAMPEN_THRESHOLD) {
|
|
1730
1764
|
convergedSkipped++;
|
|
1731
1765
|
continue;
|
|
@@ -1802,7 +1836,7 @@ export async function runImprovePreparationStage(args) {
|
|
|
1802
1836
|
const ownsNoOpDb = !eventsCtx?.db;
|
|
1803
1837
|
try {
|
|
1804
1838
|
for (const r of mergedRefs) {
|
|
1805
|
-
noOpMap.set(r.ref,
|
|
1839
|
+
noOpMap.set(r.ref, readConsecutiveNoOpsForImproveRef(noOpDb, r.ref, options.target, options.legacyBareState));
|
|
1806
1840
|
}
|
|
1807
1841
|
}
|
|
1808
1842
|
finally {
|