akm-cli 0.9.0-beta.5 → 0.9.0-beta.51
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 +711 -0
- package/README.md +12 -4
- package/dist/akm +38 -0
- package/dist/akm-migrate-storage +38 -0
- package/dist/assets/profiles/default.json +9 -4
- package/dist/assets/profiles/frequent.json +1 -1
- package/dist/assets/profiles/memory-focus.json +1 -1
- package/dist/assets/profiles/quick.json +1 -1
- package/dist/assets/profiles/synthesize.json +15 -0
- package/dist/assets/profiles/thorough.json +1 -1
- package/dist/assets/prompts/consolidate-system.md +23 -0
- package/dist/assets/prompts/contradiction-judge.md +33 -0
- package/dist/assets/prompts/distill-knowledge-system.md +22 -0
- package/dist/assets/prompts/distill-lesson-system.md +36 -0
- package/dist/assets/prompts/extract-session.md +6 -2
- package/dist/assets/prompts/graph-extract-system.md +1 -0
- package/dist/assets/prompts/graph-extract-user-prompt.md +1 -1
- package/dist/assets/prompts/memory-infer-system.md +1 -0
- package/dist/assets/prompts/memory-infer-user.md +5 -0
- package/dist/assets/prompts/metadata-enhance-system.md +1 -0
- package/dist/assets/prompts/procedural-system.md +44 -0
- package/dist/assets/prompts/recombine-system.md +40 -0
- package/dist/assets/prompts/staleness-detect-system.md +6 -0
- package/dist/assets/prompts/validate-summary-judge.md +1 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +39 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +43 -0
- package/dist/assets/templates/html/health.html +281 -111
- package/dist/assets/wiki/ingest-workflow-template.md +38 -10
- package/dist/cli/parse-args.js +46 -1
- package/dist/cli/shared.js +28 -0
- package/dist/cli.js +27 -11
- package/dist/commands/agent/agent-dispatch.js +2 -2
- package/dist/commands/agent/agent-support.js +0 -7
- package/dist/commands/agent/contribute-cli.js +17 -4
- package/dist/commands/config-cli.js +18 -2
- package/dist/commands/env/child-env.js +47 -0
- package/dist/commands/env/env-cli.js +33 -26
- package/dist/commands/env/secret-cli.js +36 -22
- package/dist/commands/feedback-cli.js +15 -6
- package/dist/commands/graph/graph-cli.js +5 -13
- package/dist/commands/graph/graph.js +76 -72
- package/dist/commands/health/checks.js +49 -1
- package/dist/commands/health/html-report.js +422 -80
- package/dist/commands/health.js +386 -9
- package/dist/commands/improve/calibration.js +161 -0
- package/dist/commands/improve/consolidate/chunking.js +141 -0
- package/dist/commands/improve/consolidate/eligibility.js +81 -0
- package/dist/commands/improve/consolidate/merge.js +145 -0
- package/dist/commands/improve/consolidate/sanitize.js +231 -0
- package/dist/commands/{lint.js → improve/consolidate/types.js} +1 -1
- package/dist/commands/improve/consolidate.js +635 -660
- package/dist/commands/improve/dedup.js +482 -0
- package/dist/commands/improve/distill.js +159 -69
- package/dist/commands/improve/eligibility.js +434 -0
- package/dist/commands/improve/encoding-salience.js +205 -0
- package/dist/commands/improve/extract-cli.js +124 -2
- package/dist/commands/improve/extract-prompt.js +39 -2
- package/dist/commands/improve/extract-watch.js +140 -0
- package/dist/commands/improve/extract.js +389 -40
- package/dist/commands/improve/feedback-valence.js +54 -0
- package/dist/commands/improve/homeostatic.js +467 -0
- package/dist/commands/improve/improve-auto-accept.js +138 -7
- package/dist/commands/improve/improve-cli.js +36 -61
- package/dist/commands/improve/improve-profiles.js +14 -0
- package/dist/commands/improve/improve-result-file.js +14 -25
- package/dist/commands/improve/improve-session.js +58 -0
- package/dist/commands/improve/improve.js +485 -2498
- package/dist/commands/improve/locks.js +154 -0
- package/dist/commands/improve/loop-stages.js +1083 -0
- package/dist/commands/improve/memory/memory-contradiction-detect.js +23 -28
- package/dist/commands/improve/outcome-loop.js +256 -0
- package/dist/commands/improve/preparation.js +1966 -0
- package/dist/commands/improve/proactive-maintenance.js +115 -0
- package/dist/commands/improve/procedural.js +418 -0
- package/dist/commands/improve/recombine.js +850 -0
- package/dist/commands/improve/reflect-noise.js +0 -0
- package/dist/commands/improve/reflect.js +183 -40
- package/dist/commands/improve/salience.js +438 -0
- package/dist/commands/improve/triage.js +93 -0
- package/dist/commands/lint/agent-linter.js +19 -24
- package/dist/commands/lint/base-linter.js +173 -60
- package/dist/commands/lint/command-linter.js +19 -24
- package/dist/commands/lint/env-key-rules.js +38 -1
- package/dist/commands/lint/fact-linter.js +39 -0
- package/dist/commands/lint/index.js +31 -13
- package/dist/commands/lint/memory-linter.js +1 -1
- package/dist/commands/lint/registry.js +7 -2
- package/dist/commands/lint/task-linter.js +3 -3
- package/dist/commands/lint/workflow-linter.js +26 -1
- package/dist/commands/proposal/drain-policies.js +5 -0
- package/dist/commands/proposal/drain.js +43 -50
- package/dist/commands/proposal/proposal-cli.js +21 -31
- package/dist/commands/proposal/proposal.js +5 -0
- package/dist/commands/proposal/propose.js +7 -2
- package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
- package/dist/commands/proposal/validators/proposals.js +189 -63
- package/dist/commands/read/curate.js +414 -94
- package/dist/commands/read/knowledge.js +6 -3
- package/dist/commands/read/search-cli.js +9 -4
- package/dist/commands/read/search.js +10 -6
- package/dist/commands/read/show.js +86 -7
- package/dist/commands/sources/init.js +49 -17
- package/dist/commands/sources/installed-stashes.js +11 -3
- package/dist/commands/sources/schema-repair.js +43 -45
- package/dist/commands/sources/self-update.js +2 -2
- package/dist/commands/sources/source-add.js +7 -3
- package/dist/commands/sources/stash-cli.js +28 -40
- package/dist/commands/sources/stash-skeleton.js +23 -8
- package/dist/commands/tasks/tasks-cli.js +19 -27
- package/dist/commands/tasks/tasks.js +39 -11
- package/dist/commands/wiki-cli.js +21 -35
- package/dist/core/asset/asset-registry.js +3 -1
- package/dist/core/asset/asset-spec.js +18 -2
- package/dist/core/asset/frontmatter.js +166 -167
- package/dist/core/asset/markdown.js +8 -0
- package/dist/core/authoring-rules.js +92 -0
- package/dist/core/common.js +0 -5
- package/dist/core/config/config-migration.js +12 -11
- package/dist/core/config/config-schema.js +340 -56
- package/dist/core/config/config-types.js +3 -3
- package/dist/core/config/config.js +28 -7
- package/dist/core/events.js +3 -7
- package/dist/core/improve-types.js +11 -8
- package/dist/core/logs-db.js +10 -66
- package/dist/core/parse.js +36 -16
- package/dist/core/paths.js +3 -0
- package/dist/core/standards/resolve-standards-context.js +87 -0
- package/dist/core/standards/resolve-stash-standards.js +99 -0
- package/dist/core/standards/resolve-type-conventions.js +66 -0
- package/dist/core/state/migrations.js +714 -0
- package/dist/core/state-db.js +525 -474
- package/dist/indexer/db/db.js +439 -247
- package/dist/indexer/db/graph-db.js +129 -86
- package/dist/indexer/ensure-index.js +152 -17
- package/dist/indexer/graph/graph-boost.js +51 -41
- package/dist/indexer/graph/graph-extraction.js +218 -4
- package/dist/indexer/index-writer-lock.js +99 -0
- package/dist/indexer/indexer.js +123 -221
- package/dist/indexer/passes/dir-staleness.js +114 -0
- package/dist/indexer/passes/memory-inference.js +13 -5
- package/dist/indexer/passes/staleness-detect.js +2 -5
- package/dist/indexer/search/db-search.js +19 -6
- package/dist/indexer/search/ranking-contributors.js +22 -0
- package/dist/indexer/search/ranking.js +4 -0
- package/dist/indexer/search/search-source.js +17 -18
- package/dist/indexer/search/semantic-status.js +4 -0
- package/dist/indexer/walk/matchers.js +9 -0
- package/dist/integrations/agent/config.js +6 -53
- package/dist/integrations/agent/index.js +2 -18
- package/dist/integrations/agent/prompts.js +75 -9
- package/dist/integrations/agent/runner-dispatch.js +59 -0
- package/dist/integrations/harnesses/claude/session-log.js +11 -1
- package/dist/integrations/harnesses/index.js +2 -3
- package/dist/integrations/harnesses/opencode/session-log.js +173 -3
- package/dist/integrations/harnesses/opencode-sdk/index.js +2 -2
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +0 -2
- package/dist/integrations/session-logs/index.js +16 -0
- package/dist/llm/client.js +45 -15
- package/dist/llm/embedder.js +42 -3
- package/dist/llm/embedders/deterministic.js +66 -0
- package/dist/llm/embedders/local.js +66 -2
- package/dist/llm/feature-gate.js +8 -4
- package/dist/llm/graph-extract.js +67 -44
- package/dist/llm/memory-infer-impl.js +138 -0
- package/dist/llm/memory-infer.js +1 -127
- package/dist/llm/metadata-enhance.js +44 -31
- package/dist/llm/structured-call.js +49 -0
- package/dist/migrate-storage-node.mjs +8 -0
- package/dist/output/context.js +5 -5
- package/dist/output/renderers.js +74 -2
- package/dist/output/shapes/curate.js +14 -2
- package/dist/output/shapes/passthrough.js +0 -1
- package/dist/output/text/helpers.js +16 -1
- package/dist/registry/providers/skills-sh.js +21 -147
- package/dist/registry/providers/static-index.js +15 -157
- package/dist/registry/resolve.js +22 -9
- package/dist/runtime.js +25 -1
- package/dist/scripts/migrate-storage.js +2617 -1961
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +759 -510
- package/dist/setup/setup.js +29 -8
- package/dist/sources/include.js +6 -2
- package/dist/sources/providers/filesystem.js +0 -1
- package/dist/sources/providers/git-install.js +210 -0
- package/dist/sources/providers/git-provider.js +234 -0
- package/dist/sources/providers/git-stash.js +248 -0
- package/dist/sources/providers/git.js +10 -661
- package/dist/sources/providers/npm.js +2 -6
- package/dist/sources/providers/provider-utils.js +13 -7
- package/dist/sources/providers/sync-from-ref.js +9 -1
- package/dist/sources/providers/tar-utils.js +16 -8
- package/dist/sources/providers/website.js +9 -5
- package/dist/sources/website-ingest.js +187 -29
- package/dist/sources/wiki-fetchers/registry.js +53 -0
- package/dist/sources/wiki-fetchers/youtube.js +239 -0
- package/dist/storage/database.js +45 -10
- package/dist/storage/managed-db.js +82 -0
- package/dist/storage/repositories/registry-cache.js +92 -0
- package/dist/storage/sqlite-pragmas.js +146 -0
- package/dist/tasks/backends/cron.js +1 -1
- package/dist/tasks/backends/launchd.js +1 -1
- package/dist/tasks/backends/schtasks.js +1 -1
- package/dist/tasks/{resolveAkmBin.js → resolve-akm-bin.js} +2 -2
- package/dist/tasks/runner.js +5 -13
- package/dist/text-import-hook.mjs +0 -0
- package/dist/wiki/wiki.js +37 -0
- package/dist/workflows/db.js +3 -4
- package/dist/workflows/runtime/runs.js +1 -117
- package/dist/workflows/runtime/workflow-asset-loader.js +125 -0
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/data-and-telemetry.md +3 -2
- package/docs/migration/release-notes/0.9.0.md +39 -0
- package/package.json +13 -11
- package/dist/commands/db-cli.js +0 -23
- package/dist/indexer/db/db-backup.js +0 -376
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
*/
|
|
36
36
|
import fs from "node:fs";
|
|
37
37
|
import path from "node:path";
|
|
38
|
+
import contradictionJudgeTemplate from "../../../assets/prompts/contradiction-judge.md" with { type: "text" };
|
|
38
39
|
import { assembleAsset } from "../../../core/asset/asset-serialize.js";
|
|
39
40
|
import { parseFrontmatter } from "../../../core/asset/frontmatter.js";
|
|
40
41
|
import { getDefaultLlmConfig } from "../../../core/config/config.js";
|
|
@@ -51,6 +52,13 @@ const MAX_FAMILY_SIZE = 8;
|
|
|
51
52
|
* families. Prevents runaway LLM usage on stashes with many memories.
|
|
52
53
|
*/
|
|
53
54
|
const MAX_PAIRS_PER_RUN = 20;
|
|
55
|
+
/**
|
|
56
|
+
* Minimum confidence required to write a contradiction edge. Below this
|
|
57
|
+
* threshold the LLM may be flagging topic-overlap rather than genuine logical
|
|
58
|
+
* exclusivity (investigation 2026-06-18). Absent confidence fields default to
|
|
59
|
+
* 1.0 for backward compatibility with older judge responses.
|
|
60
|
+
*/
|
|
61
|
+
const CONTRADICT_CONFIDENCE_THRESHOLD = 0.92;
|
|
54
62
|
/**
|
|
55
63
|
* Truncation limit for memory body content sent to the LLM judge.
|
|
56
64
|
* Keeps prompts compact while preserving the key factual claims.
|
|
@@ -58,34 +66,13 @@ const MAX_PAIRS_PER_RUN = 20;
|
|
|
58
66
|
const BODY_TRUNCATION = 800;
|
|
59
67
|
// ── Prompt builder ────────────────────────────────────────────────────────────
|
|
60
68
|
function buildContradictionJudgePrompt(a, b) {
|
|
61
|
-
return
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"",
|
|
65
|
-
"
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
"Content:",
|
|
69
|
-
"```",
|
|
70
|
-
a.body.slice(0, BODY_TRUNCATION),
|
|
71
|
-
"```",
|
|
72
|
-
"",
|
|
73
|
-
"Memory B:",
|
|
74
|
-
`Ref: ${b.ref}`,
|
|
75
|
-
`Description: ${b.description || "(none)"}`,
|
|
76
|
-
"Content:",
|
|
77
|
-
"```",
|
|
78
|
-
b.body.slice(0, BODY_TRUNCATION),
|
|
79
|
-
"```",
|
|
80
|
-
"",
|
|
81
|
-
"Answer ONLY with valid JSON — no prose, no code fences:",
|
|
82
|
-
'{"contradicts": true|false, "reason": "<one sentence explaining why or why not>"}',
|
|
83
|
-
"",
|
|
84
|
-
"A contradiction means the memories make mutually exclusive factual claims about the",
|
|
85
|
-
"same topic (e.g. Memory A says 'always use VPN' while Memory B says 'VPN is optional').",
|
|
86
|
-
"If the memories are complementary, about different topics, or one supersedes the other",
|
|
87
|
-
"without direct conflict, return false.",
|
|
88
|
-
].join("\n");
|
|
69
|
+
return contradictionJudgeTemplate
|
|
70
|
+
.replace("{{A_REF}}", a.ref)
|
|
71
|
+
.replace("{{A_DESCRIPTION}}", a.description || "(none)")
|
|
72
|
+
.replace("{{A_BODY}}", a.body.slice(0, BODY_TRUNCATION))
|
|
73
|
+
.replace("{{B_REF}}", b.ref)
|
|
74
|
+
.replace("{{B_DESCRIPTION}}", b.description || "(none)")
|
|
75
|
+
.replace("{{B_BODY}}", b.body.slice(0, BODY_TRUNCATION));
|
|
89
76
|
}
|
|
90
77
|
// ── Filesystem helpers ────────────────────────────────────────────────────────
|
|
91
78
|
function* walkMarkdownFilesLocal(root) {
|
|
@@ -256,6 +243,14 @@ export async function detectAndWriteContradictions(stashDir, config, chat = chat
|
|
|
256
243
|
}
|
|
257
244
|
if (!parsed?.contradicts)
|
|
258
245
|
continue;
|
|
246
|
+
// Confidence gate: absent field defaults to 1.0 (backward compat with
|
|
247
|
+
// pre-confidence responses). Do NOT default to 0 — that would silently
|
|
248
|
+
// disable all detection during the rollout period.
|
|
249
|
+
const confidence = typeof parsed.confidence === "number" ? parsed.confidence : 1.0;
|
|
250
|
+
if (confidence < CONTRADICT_CONFIDENCE_THRESHOLD) {
|
|
251
|
+
result.warnings.push(`Pair ${a.ref} / ${b.ref}: confidence ${confidence.toFixed(2)} below ${CONTRADICT_CONFIDENCE_THRESHOLD} threshold — skipped.`);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
259
254
|
// Write contradiction edges: both members get contradictedBy pointing to each other.
|
|
260
255
|
try {
|
|
261
256
|
const wroteA = writeContradictedByEdge(a.filePath, b.ref);
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
5
|
+
/**
|
|
6
|
+
* Weight on the "retrieved-but-never-improved" penalty term. Setting this to
|
|
7
|
+
* 0 degrades to a pure prediction-error score (no quality filter); setting it
|
|
8
|
+
* to 1 heavily penalises assets whose retrievals never led to accepted changes.
|
|
9
|
+
*/
|
|
10
|
+
export const OUTCOME_PENALTY_WEIGHT = 0.3;
|
|
11
|
+
/**
|
|
12
|
+
* EMA decay factor for the expected-retrieval rolling mean (α).
|
|
13
|
+
* New expected = α × new_count + (1−α) × old_expected.
|
|
14
|
+
* At α = 0.3 the window is ≈ 3 cycles.
|
|
15
|
+
*/
|
|
16
|
+
export const OUTCOME_EMA_ALPHA = 0.3;
|
|
17
|
+
/**
|
|
18
|
+
* Maximum K improve cycles for the eligibility-trace window. Retrievals older
|
|
19
|
+
* than K cycles contribute via the EMA decay naturally (they are already baked
|
|
20
|
+
* into `expected_retrieval_rate`).
|
|
21
|
+
*/
|
|
22
|
+
export const OUTCOME_TRACE_CYCLES = 5;
|
|
23
|
+
/**
|
|
24
|
+
* Warm-start cap: the maximum `outcome_score` a brand-new row can be seeded with
|
|
25
|
+
* (from the utility EMA). Prevents a `[0,1]`-range utility value from generating
|
|
26
|
+
* a score that the first negative delta could catastrophically invert. Clipped to
|
|
27
|
+
* the maximum plausible first differential update: `max(0, 1 − 0) = 1`, so cap
|
|
28
|
+
* at a modest 0.3 to avoid spurious rank-flip on first cycle.
|
|
29
|
+
*/
|
|
30
|
+
export const WARM_START_CAP = 0.3;
|
|
31
|
+
/**
|
|
32
|
+
* Penalty cap: the minimum outcome_score is capped at this value so a single
|
|
33
|
+
* very-negative run can't send the score to −∞.
|
|
34
|
+
*/
|
|
35
|
+
export const OUTCOME_SCORE_MIN = -1.0;
|
|
36
|
+
/**
|
|
37
|
+
* Diversity floor: `outcomeSalience` for any asset is at least this fraction
|
|
38
|
+
* of the maximum observed `outcome_score` in the table, so rare-but-correct
|
|
39
|
+
* assets cannot be permanently outcompeted. 0 = disabled (pure competition).
|
|
40
|
+
*/
|
|
41
|
+
export const DIVERSITY_FLOOR_FRACTION = 0.1;
|
|
42
|
+
/**
|
|
43
|
+
* Default review_pressure increment per negative-feedback retrieval cycle.
|
|
44
|
+
* Each cycle where negative_feedback_count > 0, pressure increases by this
|
|
45
|
+
* much (and decreases by REVIEW_PRESSURE_DECAY if no new negatives).
|
|
46
|
+
*/
|
|
47
|
+
export const REVIEW_PRESSURE_INCREMENT = 1;
|
|
48
|
+
/**
|
|
49
|
+
* Decay per non-negative cycle. Keeps review_pressure from accumulating forever
|
|
50
|
+
* when an asset's quality problems are addressed and feedback turns positive.
|
|
51
|
+
*/
|
|
52
|
+
export const REVIEW_PRESSURE_DECAY = 1;
|
|
53
|
+
/**
|
|
54
|
+
* Upsert one asset's outcome row.
|
|
55
|
+
*
|
|
56
|
+
* On first call for a ref (no prior row): warm-starts from `utilityScore`.
|
|
57
|
+
* On subsequent calls: applies the differential update formula.
|
|
58
|
+
*
|
|
59
|
+
* Returns the resulting `outcome_score` and `review_pressure` so the caller
|
|
60
|
+
* can pass them to `computeSalience` without a second read.
|
|
61
|
+
*/
|
|
62
|
+
export function updateAssetOutcome(db, inputs) {
|
|
63
|
+
const now = inputs.now ?? Date.now();
|
|
64
|
+
const valence = inputs.valence ?? 0;
|
|
65
|
+
// Fetch existing row (or undefined for first insert).
|
|
66
|
+
const existing = getAssetOutcome(db, inputs.ref);
|
|
67
|
+
const isNewRow = existing === undefined;
|
|
68
|
+
let outcomeScore;
|
|
69
|
+
let reviewPressure;
|
|
70
|
+
let expectedRetrievalRate;
|
|
71
|
+
if (isNewRow) {
|
|
72
|
+
// ── Warm-start ─────────────────────────────────────────────────────────
|
|
73
|
+
// Seed from utility EMA, clipped to [0, WARM_START_CAP].
|
|
74
|
+
// The warm-start is a non-negative seed; the first real differential update
|
|
75
|
+
// will adjust it up or down from there.
|
|
76
|
+
const seedScore = Math.min(WARM_START_CAP, Math.max(0, inputs.utilityScore ?? 0));
|
|
77
|
+
outcomeScore = seedScore;
|
|
78
|
+
reviewPressure = 0;
|
|
79
|
+
// Seed expected_retrieval_rate = 0 (no delta history yet).
|
|
80
|
+
// Seeding with currentRetrievalCount would produce a large spurious negative
|
|
81
|
+
// prediction error on the first real cycle (delta ≪ cumulative count).
|
|
82
|
+
expectedRetrievalRate = 0;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// ── Differential update ────────────────────────────────────────────────
|
|
86
|
+
//
|
|
87
|
+
// retrieval_delta = current − stored (non-negative — we never go backwards)
|
|
88
|
+
const retrievalDelta = Math.max(0, inputs.currentRetrievalCount - existing.retrieval_count);
|
|
89
|
+
// accepted_change_rate = accepted_count / max(1, retrieval_count)
|
|
90
|
+
const acceptedChangeRate = inputs.acceptedChangeCount / Math.max(1, inputs.currentRetrievalCount);
|
|
91
|
+
// Differential prediction-error term:
|
|
92
|
+
// outcome = (retrieval_delta − expected_delta)
|
|
93
|
+
// − PENALTY × retrieval_delta × (1 − accepted_change_rate)
|
|
94
|
+
// + valence
|
|
95
|
+
//
|
|
96
|
+
// Prediction error is computed against the PRIOR stored EMA (before folding
|
|
97
|
+
// in this cycle's observation), so the current delta cannot leak into its own
|
|
98
|
+
// expectation. Negative values are intentional — they signal below-average cycles.
|
|
99
|
+
const expectedDelta = existing.expected_retrieval_rate;
|
|
100
|
+
const predictionError = retrievalDelta - expectedDelta;
|
|
101
|
+
// Advance the EMA over the OBSERVED delta (not the cumulative count).
|
|
102
|
+
// expected' = α × delta + (1−α) × prior_expected
|
|
103
|
+
expectedRetrievalRate =
|
|
104
|
+
OUTCOME_EMA_ALPHA * retrievalDelta + (1 - OUTCOME_EMA_ALPHA) * existing.expected_retrieval_rate;
|
|
105
|
+
const penalty = OUTCOME_PENALTY_WEIGHT * retrievalDelta * (1 - acceptedChangeRate);
|
|
106
|
+
// Running sum (EMA approach): new score = α × update + (1−α) × old
|
|
107
|
+
// so the score tracks the moving signal, not the cumulative sum.
|
|
108
|
+
const rawUpdate = predictionError - penalty + valence;
|
|
109
|
+
const newScore = OUTCOME_EMA_ALPHA * rawUpdate + (1 - OUTCOME_EMA_ALPHA) * existing.outcome_score;
|
|
110
|
+
// Clip to [OUTCOME_SCORE_MIN, +Infinity) — no upper cap so that very-active
|
|
111
|
+
// useful assets can accumulate a high positive score.
|
|
112
|
+
outcomeScore = Math.max(OUTCOME_SCORE_MIN, newScore);
|
|
113
|
+
// ── review_pressure (#613) ─────────────────────────────────────────────
|
|
114
|
+
// New negatives this cycle.
|
|
115
|
+
const newNegatives = Math.max(0, inputs.negativeFeedbackCount - existing.negative_feedback_count);
|
|
116
|
+
if (newNegatives > 0) {
|
|
117
|
+
reviewPressure = Math.min(10, existing.review_pressure + REVIEW_PRESSURE_INCREMENT * newNegatives);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// Decay when no new negatives.
|
|
121
|
+
reviewPressure = Math.max(0, existing.review_pressure - REVIEW_PRESSURE_DECAY);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Upsert the row.
|
|
125
|
+
db.prepare(`INSERT INTO asset_outcome
|
|
126
|
+
(asset_ref, last_retrieved_at, retrieval_count, expected_retrieval_rate,
|
|
127
|
+
negative_feedback_count, accepted_change_count, review_pressure,
|
|
128
|
+
outcome_score, updated_at)
|
|
129
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
130
|
+
ON CONFLICT(asset_ref) DO UPDATE SET
|
|
131
|
+
last_retrieved_at = excluded.last_retrieved_at,
|
|
132
|
+
retrieval_count = excluded.retrieval_count,
|
|
133
|
+
expected_retrieval_rate= excluded.expected_retrieval_rate,
|
|
134
|
+
negative_feedback_count= excluded.negative_feedback_count,
|
|
135
|
+
accepted_change_count = excluded.accepted_change_count,
|
|
136
|
+
review_pressure = excluded.review_pressure,
|
|
137
|
+
outcome_score = excluded.outcome_score,
|
|
138
|
+
updated_at = excluded.updated_at`).run(inputs.ref, inputs.lastRetrievedAt, inputs.currentRetrievalCount, expectedRetrievalRate, inputs.negativeFeedbackCount, inputs.acceptedChangeCount, reviewPressure, outcomeScore, now);
|
|
139
|
+
return { outcomeScore, reviewPressure, isNewRow };
|
|
140
|
+
}
|
|
141
|
+
// ── Reader ────────────────────────────────────────────────────────────────────
|
|
142
|
+
/**
|
|
143
|
+
* Load the outcome row for one asset, or `undefined` if not yet written.
|
|
144
|
+
*/
|
|
145
|
+
export function getAssetOutcome(db, ref) {
|
|
146
|
+
const row = db
|
|
147
|
+
.prepare(`SELECT asset_ref, last_retrieved_at, retrieval_count, expected_retrieval_rate,
|
|
148
|
+
negative_feedback_count, accepted_change_count, review_pressure,
|
|
149
|
+
outcome_score, updated_at
|
|
150
|
+
FROM asset_outcome WHERE asset_ref = ?`)
|
|
151
|
+
.get(ref);
|
|
152
|
+
return row == null ? undefined : row;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Load ALL asset_outcome rows. Used for the proxy-adequacy tripwire computation.
|
|
156
|
+
*/
|
|
157
|
+
export function getAllAssetOutcomes(db) {
|
|
158
|
+
return db
|
|
159
|
+
.prepare(`SELECT asset_ref, last_retrieved_at, retrieval_count, expected_retrieval_rate,
|
|
160
|
+
negative_feedback_count, accepted_change_count, review_pressure,
|
|
161
|
+
outcome_score, updated_at
|
|
162
|
+
FROM asset_outcome ORDER BY asset_ref`)
|
|
163
|
+
.all();
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Build a Map<ref, outcome_score> for a set of refs in one query.
|
|
167
|
+
* Used by `salience.ts` to populate `outcomeSalience`.
|
|
168
|
+
*/
|
|
169
|
+
export function getOutcomeScoresByRef(db, refs) {
|
|
170
|
+
const result = new Map();
|
|
171
|
+
if (refs.length === 0)
|
|
172
|
+
return result;
|
|
173
|
+
const CHUNK = 500;
|
|
174
|
+
for (let i = 0; i < refs.length; i += CHUNK) {
|
|
175
|
+
const chunk = refs.slice(i, i + CHUNK);
|
|
176
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
177
|
+
const rows = db
|
|
178
|
+
.prepare(`SELECT asset_ref, outcome_score FROM asset_outcome WHERE asset_ref IN (${placeholders})`)
|
|
179
|
+
.all(...chunk);
|
|
180
|
+
for (const row of rows) {
|
|
181
|
+
result.set(row.asset_ref, row.outcome_score);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
// ── outcomeSalience projection ────────────────────────────────────────────────
|
|
187
|
+
/**
|
|
188
|
+
* Convert a raw `outcome_score` (differential, may be negative) to a
|
|
189
|
+
* `outcomeSalience` value in [0, 1] suitable for use in the salience projection.
|
|
190
|
+
*
|
|
191
|
+
* Approach:
|
|
192
|
+
* 1. Clip negative scores to 0 (a negative outcome just means "below expected",
|
|
193
|
+
* not "irrelevant"; it should not harm the base retrieval/encoding ranking).
|
|
194
|
+
* 2. Apply the diversity floor so rare-but-correct assets always retain a
|
|
195
|
+
* minimum `outcomeSalience` relative to the stash-wide maximum.
|
|
196
|
+
* 3. Normalise by `maxScore` (the stash-wide max outcome_score) so the term
|
|
197
|
+
* lives in [0, 1]. When maxScore ≤ 0 (all seeds, nothing observed yet),
|
|
198
|
+
* return the floor (or 0 if DIVERSITY_FLOOR_FRACTION = 0).
|
|
199
|
+
*
|
|
200
|
+
* @param outcomeScore - Raw outcome_score from asset_outcome.
|
|
201
|
+
* @param maxScore - Maximum outcome_score across ALL rows (≥ 0 after clip).
|
|
202
|
+
* Callers must compute this once per batch and pass it in.
|
|
203
|
+
*/
|
|
204
|
+
export function outcomeScoreToSalience(outcomeScore, maxScore) {
|
|
205
|
+
const clipped = Math.max(0, outcomeScore);
|
|
206
|
+
if (maxScore <= 0) {
|
|
207
|
+
// No positive scores observed yet — return diversity floor.
|
|
208
|
+
return DIVERSITY_FLOOR_FRACTION;
|
|
209
|
+
}
|
|
210
|
+
const normalised = clipped / maxScore;
|
|
211
|
+
// Apply diversity floor.
|
|
212
|
+
return Math.max(DIVERSITY_FLOOR_FRACTION, normalised);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Compute `corr(outcome_score, accepted_change_rate)` across all asset_outcome
|
|
216
|
+
* rows. Returns `{correlation: NaN, n, isInverted: false}` when there is
|
|
217
|
+
* insufficient data (fewer than 3 rows or zero variance in either variable).
|
|
218
|
+
*
|
|
219
|
+
* A NEGATIVE correlation means: assets with a HIGH outcome_score have a LOW
|
|
220
|
+
* accepted_change_rate — i.e. the assets the proxy rates as "doing well"
|
|
221
|
+
* (frequently retrieved) are precisely the ones that rarely yield an accepted
|
|
222
|
+
* improvement. That inverts the proxy: high outcome_score ≠ "doing well", so the
|
|
223
|
+
* coarse retrieval-delta signal is no longer trustworthy and the 0.10+ rich
|
|
224
|
+
* signal is due. (`isInverted = correlation < -0.3`.)
|
|
225
|
+
*/
|
|
226
|
+
export function computeProxyAdequacy(rows) {
|
|
227
|
+
const n = rows.length;
|
|
228
|
+
if (n < 3)
|
|
229
|
+
return { correlation: Number.NaN, n, isInverted: false };
|
|
230
|
+
// accepted_change_rate per row.
|
|
231
|
+
const xs = rows.map((r) => r.outcome_score);
|
|
232
|
+
const ys = rows.map((r) => r.accepted_change_count / Math.max(1, r.retrieval_count));
|
|
233
|
+
const meanX = xs.reduce((a, b) => a + b, 0) / n;
|
|
234
|
+
const meanY = ys.reduce((a, b) => a + b, 0) / n;
|
|
235
|
+
let covXY = 0;
|
|
236
|
+
let varX = 0;
|
|
237
|
+
let varY = 0;
|
|
238
|
+
for (let i = 0; i < n; i++) {
|
|
239
|
+
const dx = xs[i] - meanX;
|
|
240
|
+
const dy = ys[i] - meanY;
|
|
241
|
+
covXY += dx * dy;
|
|
242
|
+
varX += dx * dx;
|
|
243
|
+
varY += dy * dy;
|
|
244
|
+
}
|
|
245
|
+
covXY /= n;
|
|
246
|
+
varX /= n;
|
|
247
|
+
varY /= n;
|
|
248
|
+
const denom = Math.sqrt(varX) * Math.sqrt(varY);
|
|
249
|
+
if (denom < 1e-12)
|
|
250
|
+
return { correlation: Number.NaN, n, isInverted: false };
|
|
251
|
+
const correlation = covXY / denom;
|
|
252
|
+
// Inverted proxy: negative correlation between outcome and accepted_change_rate
|
|
253
|
+
// means high-outcome assets are also high-need — the opposite of "useful".
|
|
254
|
+
const isInverted = correlation < -0.3;
|
|
255
|
+
return { correlation, n, isInverted };
|
|
256
|
+
}
|