akm-cli 0.9.0-beta.36 → 0.9.0-beta.38
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 +20 -0
- package/dist/commands/improve/improve.js +36 -1
- package/dist/commands/improve/salience.js +59 -7
- package/dist/core/standards/resolve-standards-context.js +33 -2
- package/dist/core/standards/resolve-stash-standards.js +12 -0
- package/dist/core/standards/resolve-type-conventions.js +66 -0
- package/dist/core/state-db.js +25 -0
- package/dist/scripts/migrate-storage.js +6 -0
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +6 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,26 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Per-type SOFT authoring conventions are now user-editable stash facts.** A
|
|
12
|
+
third authoring-guidance layer joins the hard rules (#645) and general stash
|
|
13
|
+
standards (#642): a stash owner can author
|
|
14
|
+
`facts/conventions/assets/<type>.md` (e.g. `…/skill.md`, `…/command.md`) to
|
|
15
|
+
capture soft, type-specific guidance — voice, structure, length *preference*,
|
|
16
|
+
naming style. When an agent authors a `skill:x`, the body of
|
|
17
|
+
`fact:conventions/assets/skill` is injected (type-scoped — authoring
|
|
18
|
+
`command:y` pulls the `command` convention, never the `skill` one), labeled
|
|
19
|
+
as soft guidance and kept separate from the validator-enforced hard rules.
|
|
20
|
+
The basename must be a `getAssetTypes()`-validated asset type; facts are read
|
|
21
|
+
straight from disk (no index rebuild) and degrade to empty safely. When no
|
|
22
|
+
per-type fact exists, the built-in `TYPE_HINTS` fallback is unchanged (no
|
|
23
|
+
regression). These facts carry soft conventions only and can never weaken the
|
|
24
|
+
authoring contract the gate enforces (`authoringRulesForType` remains the sole
|
|
25
|
+
source of validator-rejecting rules). The general convention/meta resolver now
|
|
26
|
+
excludes `facts/conventions/assets/*` so per-type guidance never leaks
|
|
27
|
+
un-type-scoped into other authoring flows. (#646)
|
|
28
|
+
|
|
9
29
|
## [0.9.0-beta.36] — 2026-06-22
|
|
10
30
|
|
|
11
31
|
### Added
|
|
@@ -54,7 +54,7 @@ import { DEFAULT_DUE_DAYS, DEFAULT_MAX_PER_RUN, filterProactiveDue, selectProact
|
|
|
54
54
|
import { akmProcedural } from "./procedural.js";
|
|
55
55
|
import { akmRecombine } from "./recombine.js";
|
|
56
56
|
import { akmReflect } from "./reflect.js";
|
|
57
|
-
import { buildRankChangeReport, computeSalience, getAllRankScores, getAssetSalience, getConsecutiveNoOps, getLastUseMsByRef, recordNoOp, resetConsecutiveNoOps, SALIENCE_NO_OP_DAMPEN_FACTOR, SALIENCE_NO_OP_DAMPEN_THRESHOLD, upsertAssetSalience, } from "./salience.js";
|
|
57
|
+
import { buildRankChangeReport, computeSalience, getAllRankScores, getAssetSalience, getConsecutiveNoOps, getLastUseMsByRef, isContentEncodingRow, recordNoOp, resetConsecutiveNoOps, SALIENCE_NO_OP_DAMPEN_FACTOR, SALIENCE_NO_OP_DAMPEN_THRESHOLD, upsertAssetSalience, } from "./salience.js";
|
|
58
58
|
// #607 Lock Decomposition: fine-grained per-process locks replace the single
|
|
59
59
|
// `improve.lock`. Three independent locks allow concurrent improve runs when
|
|
60
60
|
// they touch different subsystems (e.g. quick-shredder consolidate can run
|
|
@@ -2733,6 +2733,37 @@ async function runImprovePreparationStage(args) {
|
|
|
2733
2733
|
const outcomeWeightEnabled = salienceConfig?.outcomeWeightEnabled === true;
|
|
2734
2734
|
const salienceMap = new Map();
|
|
2735
2735
|
const nowForSalience = Date.now();
|
|
2736
|
+
// #644 — preserve content-derived encoding scores across runs.
|
|
2737
|
+
//
|
|
2738
|
+
// Before computing the salience vector, load each ref's stored encoding score
|
|
2739
|
+
// and its provenance. When the stored row carries a genuine content-derived
|
|
2740
|
+
// score (written by the distill path via `scoreEncodingSalience`), pass that
|
|
2741
|
+
// value back in as `inputs.encodingSalience` so `computeSalience` does NOT fall
|
|
2742
|
+
// back to the type-weight stub — keeping both the persisted `encoding_salience`
|
|
2743
|
+
// AND the derived `rank_score` keyed on real novelty/magnitude/prediction-error.
|
|
2744
|
+
// Refs that have never been content-scored keep the type-weight stub fallback.
|
|
2745
|
+
const storedEncodingByRef = new Map();
|
|
2746
|
+
{
|
|
2747
|
+
let dbForStoredEncoding;
|
|
2748
|
+
try {
|
|
2749
|
+
dbForStoredEncoding = openStateDatabase(eventsCtx?.dbPath);
|
|
2750
|
+
for (const r of mergedRefs) {
|
|
2751
|
+
const type = r.ref.includes(":") ? r.ref.slice(0, r.ref.indexOf(":")) : "";
|
|
2752
|
+
const row = getAssetSalience(dbForStoredEncoding, r.ref);
|
|
2753
|
+
if (row && isContentEncodingRow(row, type)) {
|
|
2754
|
+
storedEncodingByRef.set(r.ref, row.encoding_salience);
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
catch (err) {
|
|
2759
|
+
rethrowIfTestIsolationError(err);
|
|
2760
|
+
// best-effort: if DB unavailable, fall back to type-weight stub (prior behaviour)
|
|
2761
|
+
}
|
|
2762
|
+
finally {
|
|
2763
|
+
if (dbForStoredEncoding)
|
|
2764
|
+
dbForStoredEncoding.close();
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2736
2767
|
for (const r of mergedRefs) {
|
|
2737
2768
|
const type = r.ref.includes(":") ? r.ref.slice(0, r.ref.indexOf(":")) : "";
|
|
2738
2769
|
const sizeBytes = (() => {
|
|
@@ -2746,9 +2777,13 @@ async function runImprovePreparationStage(args) {
|
|
|
2746
2777
|
return undefined;
|
|
2747
2778
|
}
|
|
2748
2779
|
})();
|
|
2780
|
+
const storedEncoding = storedEncodingByRef.get(r.ref);
|
|
2749
2781
|
const vector = computeSalience({
|
|
2750
2782
|
ref: r.ref,
|
|
2751
2783
|
type,
|
|
2784
|
+
// #644: pass the stored content-derived score (if any) so the type-weight
|
|
2785
|
+
// stub is NOT re-asserted over a real distill-written encoding score.
|
|
2786
|
+
...(storedEncoding !== undefined ? { encodingSalience: storedEncoding } : {}),
|
|
2752
2787
|
retrievalFreq: retrievalCounts.get(r.ref) ?? 0,
|
|
2753
2788
|
lastUseMs: lastUseMsByRef.get(r.ref),
|
|
2754
2789
|
utilityScore: utilityMap.get(r.ref),
|
|
@@ -126,6 +126,7 @@ export function computeSalience(inputs) {
|
|
|
126
126
|
// Fall back to the type-importance stub only when the caller has not yet
|
|
127
127
|
// computed a content-based score (e.g. on older assets before the first
|
|
128
128
|
// staleness refresh runs).
|
|
129
|
+
const encodingSource = inputs.encodingSalience !== undefined ? "content" : "type-stub";
|
|
129
130
|
const encoding = inputs.encodingSalience !== undefined
|
|
130
131
|
? Math.min(1, Math.max(0, inputs.encodingSalience))
|
|
131
132
|
: (DEFAULT_TYPE_ENCODING_WEIGHTS[inputs.type] ?? DEFAULT_ENCODING_SALIENCE);
|
|
@@ -213,24 +214,75 @@ export function computeSalience(inputs) {
|
|
|
213
214
|
}
|
|
214
215
|
const rawRankScore = (we * encoding + wo * outcome + wr * retrieval) * sizePenalty;
|
|
215
216
|
const rankScore = Math.min(1, Math.max(0, rawRankScore));
|
|
216
|
-
return { encoding, outcome, retrieval, rankScore };
|
|
217
|
+
return { encoding, outcome, retrieval, rankScore, encodingSource };
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Does this row carry a genuine content-derived `encoding_salience` (#644)?
|
|
221
|
+
*
|
|
222
|
+
* Returns true when the provenance flag is `"content"`. For legacy rows
|
|
223
|
+
* (`encoding_source === null`, written before migration 015) we apply a
|
|
224
|
+
* conservative heuristic: treat the stored value as content-derived only when it
|
|
225
|
+
* does NOT equal the pure type-weight stub for the asset's type — because before
|
|
226
|
+
* the #644 fix every run overwrote real scores with the stub, a value that still
|
|
227
|
+
* differs from the stub must have been content-written and never re-clobbered.
|
|
228
|
+
* When the type cannot be determined (no `type` given) a null-provenance row is
|
|
229
|
+
* treated as a stub (the safe default).
|
|
230
|
+
*/
|
|
231
|
+
export function isContentEncodingRow(row, type) {
|
|
232
|
+
if (row.encoding_source === "content")
|
|
233
|
+
return true;
|
|
234
|
+
if (row.encoding_source === "type-stub")
|
|
235
|
+
return false;
|
|
236
|
+
// Legacy NULL provenance: differ-from-stub heuristic.
|
|
237
|
+
if (!type)
|
|
238
|
+
return false;
|
|
239
|
+
const stub = DEFAULT_TYPE_ENCODING_WEIGHTS[type] ?? DEFAULT_ENCODING_SALIENCE;
|
|
240
|
+
return Math.abs(row.encoding_salience - stub) > 1e-9;
|
|
217
241
|
}
|
|
218
242
|
/**
|
|
219
243
|
* Upsert salience scores for one asset into state.db.
|
|
220
244
|
*
|
|
221
|
-
* Idempotent: safe to call every run; updates
|
|
245
|
+
* Idempotent: safe to call every run; updates the outcome / retrieval / rank
|
|
246
|
+
* columns on conflict.
|
|
247
|
+
*
|
|
248
|
+
* #644 — encoding provenance guard: the `encoding_salience` + `encoding_source`
|
|
249
|
+
* columns are NOT lowered from a real content-derived score to a type-weight
|
|
250
|
+
* stub. When the stored row is `encoding_source = 'content'` and the incoming
|
|
251
|
+
* vector is a `type-stub` fallback, the stored encoding score and its provenance
|
|
252
|
+
* are preserved (only the other sub-scores and `rank_score` advance). A `content`
|
|
253
|
+
* write always wins; a `type-stub` write only seeds a row that has no content
|
|
254
|
+
* score yet. This stops the improve loop's type-weight fallback re-asserting the
|
|
255
|
+
* stub over a distill-written score on every run.
|
|
256
|
+
*
|
|
257
|
+
* NOTE: when the guard preserves the stored encoding score, the incoming
|
|
258
|
+
* `vector.rankScore` (computed from the stub encoding) is still written. Callers
|
|
259
|
+
* that want the rank_score to reflect the preserved content score should pass the
|
|
260
|
+
* stored content score back in as `inputs.encodingSalience` to `computeSalience`
|
|
261
|
+
* — which the improve loop does. The guard here is the defensive backstop.
|
|
222
262
|
*/
|
|
223
263
|
export function upsertAssetSalience(db, ref, vector, now) {
|
|
224
264
|
const ts = now ?? Date.now();
|
|
225
265
|
db.prepare(`INSERT INTO asset_salience
|
|
226
|
-
(asset_ref, encoding_salience, outcome_salience, retrieval_salience, rank_score, consecutive_no_ops, updated_at)
|
|
227
|
-
VALUES (?, ?, ?, ?, ?, 0, ?)
|
|
266
|
+
(asset_ref, encoding_salience, outcome_salience, retrieval_salience, rank_score, consecutive_no_ops, updated_at, encoding_source)
|
|
267
|
+
VALUES (?, ?, ?, ?, ?, 0, ?, ?)
|
|
228
268
|
ON CONFLICT(asset_ref) DO UPDATE SET
|
|
229
|
-
|
|
269
|
+
-- #644: never lower a real content-derived score to a type-weight stub.
|
|
270
|
+
-- Keep the stored encoding score + provenance when the stored row is
|
|
271
|
+
-- 'content' and the incoming write is a 'type-stub' fallback.
|
|
272
|
+
encoding_salience = CASE
|
|
273
|
+
WHEN asset_salience.encoding_source = 'content' AND excluded.encoding_source = 'type-stub'
|
|
274
|
+
THEN asset_salience.encoding_salience
|
|
275
|
+
ELSE excluded.encoding_salience
|
|
276
|
+
END,
|
|
277
|
+
encoding_source = CASE
|
|
278
|
+
WHEN asset_salience.encoding_source = 'content' AND excluded.encoding_source = 'type-stub'
|
|
279
|
+
THEN asset_salience.encoding_source
|
|
280
|
+
ELSE excluded.encoding_source
|
|
281
|
+
END,
|
|
230
282
|
outcome_salience = excluded.outcome_salience,
|
|
231
283
|
retrieval_salience = excluded.retrieval_salience,
|
|
232
284
|
rank_score = excluded.rank_score,
|
|
233
|
-
updated_at = excluded.updated_at`).run(ref, vector.encoding, vector.outcome, vector.retrieval, vector.rankScore, ts);
|
|
285
|
+
updated_at = excluded.updated_at`).run(ref, vector.encoding, vector.outcome, vector.retrieval, vector.rankScore, ts, vector.encodingSource ?? "type-stub");
|
|
234
286
|
}
|
|
235
287
|
/**
|
|
236
288
|
* Load the salience row for one asset, or undefined if not yet computed.
|
|
@@ -238,7 +290,7 @@ export function upsertAssetSalience(db, ref, vector, now) {
|
|
|
238
290
|
export function getAssetSalience(db, ref) {
|
|
239
291
|
const row = db
|
|
240
292
|
.prepare(`SELECT asset_ref, encoding_salience, outcome_salience, retrieval_salience,
|
|
241
|
-
rank_score, consecutive_no_ops, updated_at
|
|
293
|
+
rank_score, consecutive_no_ops, updated_at, encoding_source
|
|
242
294
|
FROM asset_salience WHERE asset_ref = ?`)
|
|
243
295
|
.get(ref);
|
|
244
296
|
// Bun SQLite returns null (not undefined) when no row found.
|
|
@@ -17,8 +17,24 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { extractWikiNameFromRef, INDEX_MD, LOG_MD, loadWikiSchema, SCHEMA_MD } from "../../wiki/wiki.js";
|
|
19
19
|
import { resolveStashStandards } from "./resolve-stash-standards.js";
|
|
20
|
+
import { resolveTypeConventions, typeConventionRef } from "./resolve-type-conventions.js";
|
|
20
21
|
/** Wiki infra files that are not authored pages (relative to the wiki root). */
|
|
21
22
|
const WIKI_INFRA_BASENAMES = new Set([SCHEMA_MD, INDEX_MD, LOG_MD]);
|
|
23
|
+
/**
|
|
24
|
+
* Extract the asset type from a canonical ref (`[origin//]type:name`) without
|
|
25
|
+
* throwing. Returns `undefined` for refs that have no `type:` prefix. Kept local
|
|
26
|
+
* and lenient — the per-type resolver validates the result against
|
|
27
|
+
* `getAssetTypes()`, so a bogus prefix here simply yields no convention.
|
|
28
|
+
*/
|
|
29
|
+
function refType(ref) {
|
|
30
|
+
if (!ref)
|
|
31
|
+
return undefined;
|
|
32
|
+
const body = ref.includes("//") ? ref.slice(ref.indexOf("//") + 2) : ref;
|
|
33
|
+
const colon = body.indexOf(":");
|
|
34
|
+
if (colon <= 0)
|
|
35
|
+
return undefined;
|
|
36
|
+
return body.slice(0, colon).trim() || undefined;
|
|
37
|
+
}
|
|
22
38
|
/**
|
|
23
39
|
* Resolve the standards context for a write target identified by its asset ref.
|
|
24
40
|
*
|
|
@@ -30,8 +46,23 @@ const WIKI_INFRA_BASENAMES = new Set([SCHEMA_MD, INDEX_MD, LOG_MD]);
|
|
|
30
46
|
export function resolveStandardsContext(ref, stashRoot) {
|
|
31
47
|
const wikiName = ref ? extractWikiNameFromRef(ref) : undefined;
|
|
32
48
|
if (!wikiName) {
|
|
33
|
-
// Non-wiki asset target → Feature B (stash
|
|
34
|
-
|
|
49
|
+
// Non-wiki asset target → Feature B (general stash standards) plus the
|
|
50
|
+
// per-type SOFT conventions layer (#646), type-scoped to the write target.
|
|
51
|
+
const general = resolveStashStandards(stashRoot);
|
|
52
|
+
const type = refType(ref);
|
|
53
|
+
// A non-empty body here guarantees `type` is a `getAssetTypes()`-validated
|
|
54
|
+
// string (the resolver returns "" otherwise).
|
|
55
|
+
const typeConventions = type ? resolveTypeConventions(stashRoot, type) : "";
|
|
56
|
+
if (!typeConventions || !type)
|
|
57
|
+
return general;
|
|
58
|
+
// Soft, type-scoped guidance — clearly labeled and kept separate from the
|
|
59
|
+
// HARD (validator-enforced) rules that `authoringRulesForType` injects
|
|
60
|
+
// downstream. These facts are advice only; they never weaken the gate.
|
|
61
|
+
const softSection = [
|
|
62
|
+
`# ${typeConventionRef(type)} (soft per-type conventions — guidance, not enforced)`,
|
|
63
|
+
typeConventions,
|
|
64
|
+
].join("\n");
|
|
65
|
+
return general ? `${general}\n\n${softSection}` : softSection;
|
|
35
66
|
}
|
|
36
67
|
// Wiki target. Extract the page path after `wiki:<name>/`.
|
|
37
68
|
const prefix = `wiki:${wikiName}/`;
|
|
@@ -21,6 +21,13 @@ import { parseFrontmatter } from "../asset/frontmatter.js";
|
|
|
21
21
|
const STANDARD_CATEGORIES = new Set(["convention", "meta"]);
|
|
22
22
|
/** Directory (under the stash root) where `fact` assets live. */
|
|
23
23
|
const FACTS_SUBDIR = "facts";
|
|
24
|
+
/**
|
|
25
|
+
* Per-type SOFT convention facts (`facts/conventions/assets/<type>.md`, #646)
|
|
26
|
+
* are surfaced **type-scoped** through `resolveTypeConventions`, so they must
|
|
27
|
+
* NOT leak into this un-type-scoped general layer (authoring a `command` must
|
|
28
|
+
* not pull the `skill` convention). Excluded by relative path (POSIX form).
|
|
29
|
+
*/
|
|
30
|
+
const TYPE_CONVENTIONS_REL = "conventions/assets/";
|
|
24
31
|
/**
|
|
25
32
|
* Recursively collect `.md` files under `dir` in stable (sorted) enumeration
|
|
26
33
|
* order. Returns absolute paths. Missing dir → `[]`.
|
|
@@ -59,6 +66,11 @@ export function resolveStashStandards(stashRoot) {
|
|
|
59
66
|
const factsRoot = path.join(stashRoot, FACTS_SUBDIR);
|
|
60
67
|
const sections = [];
|
|
61
68
|
for (const absPath of collectMarkdownFiles(factsRoot)) {
|
|
69
|
+
// Per-type SOFT conventions are delivered type-scoped (#646); skip them
|
|
70
|
+
// here so they never leak un-type-scoped into every authoring flow.
|
|
71
|
+
const relPosix = path.relative(factsRoot, absPath).split(path.sep).join("/");
|
|
72
|
+
if (relPosix.startsWith(TYPE_CONVENTIONS_REL))
|
|
73
|
+
continue;
|
|
62
74
|
let raw;
|
|
63
75
|
try {
|
|
64
76
|
raw = fs.readFileSync(absPath, "utf8");
|
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
/**
|
|
5
|
+
* Resolve **per-type SOFT authoring conventions** (#646) — the third and final
|
|
6
|
+
* authoring-guidance layer:
|
|
7
|
+
*
|
|
8
|
+
* 1. HARD rules (validator-rejecting, code-sourced) → `authoringRulesForType()`
|
|
9
|
+
* (`src/core/authoring-rules.ts`, #645). Never editable; the gate enforces them.
|
|
10
|
+
* 2. General stash standards (cross-type naming/tag conventions) →
|
|
11
|
+
* `resolveStashStandards()` `category: convention|meta` facts (#642).
|
|
12
|
+
* 3. **Per-type SOFT conventions** (voice, structure, length *preference* for
|
|
13
|
+
* *this* asset type) → user-editable `facts/conventions/assets/<type>.md`
|
|
14
|
+
* (THIS module). Augments the built-in `TYPE_HINTS` fallback for display.
|
|
15
|
+
*
|
|
16
|
+
* These facts are **soft only** — advice, not contract. They MUST NOT carry
|
|
17
|
+
* hard, validator-rejecting rules: a user editing or deleting one must never be
|
|
18
|
+
* able to weaken the authoring contract the gate enforces (#645 boundary).
|
|
19
|
+
*
|
|
20
|
+
* Selection is by a `getAssetTypes()`-validated basename: only
|
|
21
|
+
* `facts/conventions/assets/<known-type>.md` resolves. Read directly from disk
|
|
22
|
+
* (no index rebuild); any missing dir/file, unknown type, or read error degrades
|
|
23
|
+
* to `""` and never throws.
|
|
24
|
+
*/
|
|
25
|
+
import fs from "node:fs";
|
|
26
|
+
import path from "node:path";
|
|
27
|
+
import { getAssetTypes } from "../asset/asset-spec.js";
|
|
28
|
+
import { parseFrontmatter } from "../asset/frontmatter.js";
|
|
29
|
+
/** Sub-path (under the stash root) for per-type SOFT convention facts. */
|
|
30
|
+
export const TYPE_CONVENTIONS_SUBDIR = path.join("facts", "conventions", "assets");
|
|
31
|
+
/** The `fact:` ref prefix for a per-type convention, e.g. `fact:conventions/assets/skill`. */
|
|
32
|
+
export function typeConventionRef(type) {
|
|
33
|
+
return `fact:conventions/assets/${type}`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Read the SOFT authoring-convention body for asset type `type`, if a stash
|
|
37
|
+
* owner has authored `facts/conventions/assets/<type>.md`.
|
|
38
|
+
*
|
|
39
|
+
* @returns the trimmed markdown body (frontmatter stripped), or `""` when the
|
|
40
|
+
* type is unknown, the file is absent, or anything goes wrong.
|
|
41
|
+
*/
|
|
42
|
+
export function resolveTypeConventions(stashRoot, type) {
|
|
43
|
+
if (!stashRoot || !type)
|
|
44
|
+
return "";
|
|
45
|
+
// Basename MUST be a known asset type — never resolve an arbitrary file.
|
|
46
|
+
if (!getAssetTypes().includes(type))
|
|
47
|
+
return "";
|
|
48
|
+
const abs = path.join(stashRoot, TYPE_CONVENTIONS_SUBDIR, `${type}.md`);
|
|
49
|
+
let raw;
|
|
50
|
+
try {
|
|
51
|
+
raw = fs.readFileSync(abs, "utf8");
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return ""; // missing dir/file or read error → degrade to empty
|
|
55
|
+
}
|
|
56
|
+
let body = "";
|
|
57
|
+
try {
|
|
58
|
+
body = parseFrontmatter(raw).content;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Malformed frontmatter: fall back to the whole file (parseFrontmatter
|
|
62
|
+
// normally returns whole content as body, but guard defensively).
|
|
63
|
+
body = raw;
|
|
64
|
+
}
|
|
65
|
+
return body.trim();
|
|
66
|
+
}
|
package/dist/core/state-db.js
CHANGED
|
@@ -791,6 +791,31 @@ const MIGRATIONS = [
|
|
|
791
791
|
ON recombine_hypotheses(last_seen_at);
|
|
792
792
|
`,
|
|
793
793
|
},
|
|
794
|
+
// ── Migration 015 — asset_salience: encoding_source provenance column (#644) ──
|
|
795
|
+
//
|
|
796
|
+
// Records HOW the stored `encoding_salience` was derived so an improve run's
|
|
797
|
+
// type-weight fallback can no longer clobber a real content-derived score:
|
|
798
|
+
//
|
|
799
|
+
// "content" — written by the distill path from `scoreEncodingSalience`
|
|
800
|
+
// (novelty·0.40 + magnitude·0.35 + predictionError·0.25).
|
|
801
|
+
// "type-stub" — written by `computeSalience`'s `DEFAULT_TYPE_ENCODING_WEIGHTS`
|
|
802
|
+
// fallback (no content-based score available for this ref yet).
|
|
803
|
+
// NULL — legacy row written before this migration; provenance unknown.
|
|
804
|
+
// Treated as a stub by readers UNLESS its stored value differs
|
|
805
|
+
// from the pure type-weight (best-effort heuristic for old data).
|
|
806
|
+
//
|
|
807
|
+
// Why a column rather than inference: before #644, every improve run overwrote
|
|
808
|
+
// the distill-written content score with the type-weight stub, so the stored
|
|
809
|
+
// value alone cannot distinguish a real score from a stub. The provenance flag
|
|
810
|
+
// is the single source of truth going forward; `upsertAssetSalience` refuses to
|
|
811
|
+
// lower a "content" row to a "type-stub" so the high-salience gate (#608) keys
|
|
812
|
+
// on genuine novelty/magnitude/prediction-error, not the asset type.
|
|
813
|
+
{
|
|
814
|
+
id: "015-asset-salience-encoding-source",
|
|
815
|
+
up: `
|
|
816
|
+
ALTER TABLE asset_salience ADD COLUMN encoding_source TEXT DEFAULT NULL;
|
|
817
|
+
`,
|
|
818
|
+
},
|
|
794
819
|
];
|
|
795
820
|
/**
|
|
796
821
|
* Apply every pending migration in a single transaction per migration.
|
|
@@ -9978,6 +9978,12 @@ var init_state_db = __esm(() => {
|
|
|
9978
9978
|
CREATE INDEX IF NOT EXISTS idx_recombine_hypotheses_last_seen
|
|
9979
9979
|
ON recombine_hypotheses(last_seen_at);
|
|
9980
9980
|
`
|
|
9981
|
+
},
|
|
9982
|
+
{
|
|
9983
|
+
id: "015-asset-salience-encoding-source",
|
|
9984
|
+
up: `
|
|
9985
|
+
ALTER TABLE asset_salience ADD COLUMN encoding_source TEXT DEFAULT NULL;
|
|
9986
|
+
`
|
|
9981
9987
|
}
|
|
9982
9988
|
];
|
|
9983
9989
|
});
|
|
@@ -9108,6 +9108,12 @@ var MIGRATIONS = [
|
|
|
9108
9108
|
CREATE INDEX IF NOT EXISTS idx_recombine_hypotheses_last_seen
|
|
9109
9109
|
ON recombine_hypotheses(last_seen_at);
|
|
9110
9110
|
`
|
|
9111
|
+
},
|
|
9112
|
+
{
|
|
9113
|
+
id: "015-asset-salience-encoding-source",
|
|
9114
|
+
up: `
|
|
9115
|
+
ALTER TABLE asset_salience ADD COLUMN encoding_source TEXT DEFAULT NULL;
|
|
9116
|
+
`
|
|
9111
9117
|
}
|
|
9112
9118
|
];
|
|
9113
9119
|
function runMigrations2(db) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akm-cli",
|
|
3
|
-
"version": "0.9.0-beta.
|
|
3
|
+
"version": "0.9.0-beta.38",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "akm (Agent Knowledge Management) — A package manager for AI agent skills, commands, tools, and knowledge. Works with Claude Code, OpenCode, Cursor, and any AI coding assistant.",
|
|
6
6
|
"keywords": [
|