akm-cli 0.9.26-alpha.2 → 0.9.26
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 +169 -0
- package/dist/assets/hints/cli-hints-full.md +16 -10
- package/dist/assets/hints/cli-hints-short.md +5 -5
- package/dist/assets/stash-skeleton/README.md +4 -3
- package/dist/commands/feedback-cli.js +244 -43
- package/dist/commands/improve/distill.js +61 -2
- package/dist/commands/improve/loop-stages.js +16 -1
- package/dist/commands/improve/memory/memory-belief.js +1 -1
- package/dist/commands/improve/preparation.js +46 -0
- package/dist/commands/improve/reflect.js +15 -4
- package/dist/commands/proposal/repository.js +22 -6
- package/dist/core/asset/akm-markdown.js +40 -16
- package/dist/core/asset/frontmatter.js +67 -7
- package/dist/core/config/config-schema.js +1 -1
- package/dist/core/config/config.js +0 -4
- package/dist/core/config/schema/feedback.js +2 -19
- package/dist/scripts/akm-migrate-node.js +1 -2
- package/dist/scripts/akm-migrate.js +1 -2
- package/docs/reference/cli.md +35 -17
- package/docs/reference/configuration.md +7 -4
- package/docs/reference/data-and-telemetry.md +3 -3
- package/package.json +1 -1
- package/schemas/akm-config.json +0 -14
|
@@ -38,6 +38,7 @@ import { isJsonSchemaKnownUnsupported, LlmCallError } from "../../llm/client.js"
|
|
|
38
38
|
import { baseFailureFields, enoentHintMessage, isEnoentFailure } from "../agent/agent-support.js";
|
|
39
39
|
import { isValidDescription } from "../proposal/validators/proposal-quality-validators.js";
|
|
40
40
|
import { CHARS_PER_TOKEN, DEFAULT_CONTEXT_LENGTH_TOKENS } from "./consolidate/chunking.js";
|
|
41
|
+
import { contentHash } from "./content-hash.js";
|
|
41
42
|
import { findAssetFilePath } from "./eligibility.js";
|
|
42
43
|
import { resolveImproveExecution } from "./execution.js";
|
|
43
44
|
import { recordLedgerAttempt } from "./ledger.js";
|
|
@@ -46,19 +47,29 @@ import { loadRetrievalQueries, runRetrievalRegressionGate } from "./retrieval-ga
|
|
|
46
47
|
import { callStageOnce, errMessage, mintProposal, noticeSet, rejectedProposalContext, resolveQualityGateJudge, runReflectQualityJudge, } from "./stage.js";
|
|
47
48
|
const MAX_FEEDBACK_LINES = 10;
|
|
48
49
|
const MAX_GLOBAL_FEEDBACK_LINES = 20;
|
|
50
|
+
/** Ends a feedback line whose event judged text the asset no longer has. */
|
|
51
|
+
const EARLIER_TEXT_MARK = " (given on an earlier version of the text)";
|
|
49
52
|
function readOnlyEventsContext(ctx) {
|
|
50
53
|
return ctx?.db ? ctx : { ...(ctx ?? {}), readOnly: true };
|
|
51
54
|
}
|
|
52
|
-
/**
|
|
53
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Recent `feedback` lines for `ref` (or across all assets without one). Given the
|
|
57
|
+
* asset's current content, a line whose event recorded the hash of a different
|
|
58
|
+
* body is marked. Best-effort.
|
|
59
|
+
*/
|
|
60
|
+
function readRecentFeedback(ref, eventsCtx, assetContent) {
|
|
54
61
|
try {
|
|
55
62
|
const events = readEvents({ type: "feedback", ...(ref ? { ref } : {}) }, readOnlyEventsContext(eventsCtx)).events;
|
|
63
|
+
const bodyHash = assetContent === undefined ? undefined : contentHash(assetContent, "body");
|
|
56
64
|
return events.slice(-(ref ? MAX_FEEDBACK_LINES : MAX_GLOBAL_FEEDBACK_LINES)).map((event) => {
|
|
57
65
|
const md = event.metadata ?? {};
|
|
58
66
|
const signal = typeof md.signal === "string" ? md.signal : "?";
|
|
59
67
|
const note = typeof md.reason === "string" ? md.reason : typeof md.note === "string" ? md.note : "";
|
|
60
68
|
const details = note ? `[${signal}] ${note}` : `[${signal}]`;
|
|
61
|
-
|
|
69
|
+
const line = !ref && event.ref ? `${event.ref} ${details}` : details;
|
|
70
|
+
return bodyHash !== undefined && typeof md.contentHash === "string" && md.contentHash !== bodyHash
|
|
71
|
+
? `${line}${EARLIER_TEXT_MARK}`
|
|
72
|
+
: line;
|
|
62
73
|
});
|
|
63
74
|
}
|
|
64
75
|
catch {
|
|
@@ -636,7 +647,7 @@ function computeReflectContentBudgetChars(promptInput, runnerSpec) {
|
|
|
636
647
|
/** Every read-only prompt input, shared by dispatch and `--show-prompt`. */
|
|
637
648
|
function gatherReflectPromptSources(options, stash, parsedRef, assetContent) {
|
|
638
649
|
return {
|
|
639
|
-
feedback: readRecentFeedback(options.ref ? (options.itemRef ?? options.ref) : undefined, options.eventsCtx),
|
|
650
|
+
feedback: readRecentFeedback(options.ref ? (options.itemRef ?? options.ref) : undefined, options.eventsCtx, assetContent),
|
|
640
651
|
schemaHints: buildSchemaHints(parsedRef?.type ?? "", assetContent),
|
|
641
652
|
rejectedProposals: rejectedProposalContext(stash, options.ref, options.ctx, options.eventsCtx),
|
|
642
653
|
standardsContext: resolveStandardsContext(options.ref, stash),
|
|
@@ -13,12 +13,13 @@ import { randomUUID } from "node:crypto";
|
|
|
13
13
|
import fs from "node:fs";
|
|
14
14
|
import os from "node:os";
|
|
15
15
|
import path from "node:path";
|
|
16
|
+
import { isDeepStrictEqual } from "node:util";
|
|
16
17
|
import { parse as parseYaml } from "yaml";
|
|
17
18
|
import { ensureAkmMarkdownType } from "../../core/asset/akm-markdown.js";
|
|
18
19
|
import { assetPathForName, placementTypes, stashDirFor } from "../../core/asset/asset-placement.js";
|
|
19
20
|
import { isBundleSlug, parseBundleRef } from "../../core/asset/asset-ref.js";
|
|
20
21
|
import { assembleAsset, serializeFrontmatter } from "../../core/asset/asset-serialize.js";
|
|
21
|
-
import { carryForwardBookkeepingFrontmatter, parseFrontmatter } from "../../core/asset/frontmatter.js";
|
|
22
|
+
import { carryForwardBookkeepingFrontmatter, parseFrontmatter, replaceFrontmatterBlocks, } from "../../core/asset/frontmatter.js";
|
|
22
23
|
import { conceptIdFromTypeName, parseRefInput } from "../../core/asset/resolve-ref.js";
|
|
23
24
|
import { loadConfig } from "../../core/config/config.js";
|
|
24
25
|
import { ConfigError, NotFoundError, rethrowIfTestIsolationError, UsageError } from "../../core/errors.js";
|
|
@@ -883,8 +884,17 @@ function isPlainRecord(value) {
|
|
|
883
884
|
* Stamp provenance onto a promoted asset's frontmatter: bare top-level
|
|
884
885
|
* `generated` and `verified` (as OKF v0.2 spells them; `verified` accumulates),
|
|
885
886
|
* and `sources` under `provenance:`, since a bare `sources:` is the wiki
|
|
886
|
-
* citation-string convention.
|
|
887
|
-
*
|
|
887
|
+
* citation-string convention.
|
|
888
|
+
*
|
|
889
|
+
* Source preservation: an existing frontmatter block is edited as text and not
|
|
890
|
+
* written out again through the YAML serializer, which rewraps long values,
|
|
891
|
+
* requotes and drops comments in lines the stamp never touched. The blocks of
|
|
892
|
+
* the keys the stamp sets (`generated`, `verified`, `provenance`) are removed
|
|
893
|
+
* wherever they were and written again, serialized, as the last lines of the
|
|
894
|
+
* frontmatter; every other byte, the body included, stays as written. The
|
|
895
|
+
* edited text is parsed back to confirm it holds exactly the intended mapping;
|
|
896
|
+
* when it does not (an indented `---` inside a block scalar), the frontmatter is
|
|
897
|
+
* written out again instead, as it always used to be.
|
|
888
898
|
*/
|
|
889
899
|
function stampProposalProvenance(content, proposal, gateDecision, ctx, nowIsoStr) {
|
|
890
900
|
const parsed = parseFrontmatter(content);
|
|
@@ -917,9 +927,15 @@ function stampProposalProvenance(content, proposal, gateDecision, ctx, nowIsoStr
|
|
|
917
927
|
fm.provenance = provenance;
|
|
918
928
|
else
|
|
919
929
|
delete fm.provenance;
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
930
|
+
if (parsed.frontmatter === null)
|
|
931
|
+
return assembleAsset(fm, parsed.content);
|
|
932
|
+
const stamped = { generated: fm.generated, verified: fm.verified };
|
|
933
|
+
if (fm.provenance !== undefined)
|
|
934
|
+
stamped.provenance = fm.provenance;
|
|
935
|
+
const edited = replaceFrontmatterBlocks(content, ["generated", "verified", "provenance"], serializeFrontmatter(stamped).split("\n"));
|
|
936
|
+
if (edited !== null && isDeepStrictEqual(parseFrontmatter(edited).data, fm))
|
|
937
|
+
return edited;
|
|
938
|
+
return `---\n${serializeFrontmatter(fm)}\n---\n${parsed.content}`;
|
|
923
939
|
}
|
|
924
940
|
/**
|
|
925
941
|
* Validate, stamp and write an accepted proposal into its bound target, then
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { isDeepStrictEqual } from "node:util";
|
|
4
5
|
import { parse as parseYaml } from "yaml";
|
|
5
6
|
import { localDateStamp } from "../common.js";
|
|
6
7
|
import { UsageError } from "../errors.js";
|
|
7
8
|
import { serializeFrontmatter } from "./asset-serialize.js";
|
|
8
|
-
import { parseFrontmatterBlock, spliceFrontmatterLine } from "./frontmatter.js";
|
|
9
|
+
import { parseFrontmatterBlock, replaceFrontmatterLine, spliceFrontmatterLine } from "./frontmatter.js";
|
|
9
10
|
/**
|
|
10
11
|
* Ensure an AKM-authored Markdown concept is also a conformant OKF concept.
|
|
11
12
|
*
|
|
@@ -20,12 +21,18 @@ import { parseFrontmatterBlock, spliceFrontmatterLine } from "./frontmatter.js";
|
|
|
20
21
|
* re-stamp on every write (which would churn timestamps and manufacture
|
|
21
22
|
* needless diffs in git-backed bundles).
|
|
22
23
|
*
|
|
23
|
-
* Source preservation:
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
24
|
+
* Source preservation: whenever the frontmatter block parses, it is edited as
|
|
25
|
+
* text and never round-tripped through the YAML serializer, which rewraps long
|
|
26
|
+
* values, reorders keys and drops comments — changes nobody made, shown to the
|
|
27
|
+
* reviewer of what may be a one-line correction. A missing `type` and a
|
|
28
|
+
* missing `updated` are each added as one line before the closing `---`, in
|
|
29
|
+
* that order; a wrong `type` is replaced on its own line. Every other byte —
|
|
30
|
+
* comments, wrapping, quoting, key order, line endings, the body — is kept as
|
|
31
|
+
* written. The edited text is parsed back to confirm it holds exactly the
|
|
32
|
+
* intended mapping; when it does not (a `type` value that spans several lines,
|
|
33
|
+
* a flow-style `{…}` block), the document is re-serialized instead, as it
|
|
34
|
+
* always used to be. A document with no frontmatter block gets a new one;
|
|
35
|
+
* malformed YAML throws.
|
|
29
36
|
*/
|
|
30
37
|
export function ensureAkmMarkdownType(content, type, now = new Date()) {
|
|
31
38
|
const block = parseFrontmatterBlock(content);
|
|
@@ -45,19 +52,36 @@ export function ensureAkmMarkdownType(content, type, now = new Date()) {
|
|
|
45
52
|
throw new UsageError("AKM Markdown frontmatter must be a YAML mapping.", "INVALID_FLAG_VALUE");
|
|
46
53
|
}
|
|
47
54
|
const data = parsed;
|
|
55
|
+
const updated = localDateStamp(now);
|
|
48
56
|
const needsUpdated = !("updated" in data);
|
|
49
|
-
if (data.type === type)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
if (data.type === type && !needsUpdated)
|
|
58
|
+
return content;
|
|
59
|
+
let edited = content;
|
|
60
|
+
if (data.type !== type) {
|
|
61
|
+
edited =
|
|
62
|
+
"type" in data
|
|
63
|
+
? replaceFrontmatterLine(edited, "type", `type: ${type}`)
|
|
64
|
+
: spliceFrontmatterLine(edited, `type: ${type}`);
|
|
57
65
|
}
|
|
66
|
+
if (edited !== null && needsUpdated)
|
|
67
|
+
edited = spliceFrontmatterLine(edited, `updated: ${updated}`);
|
|
68
|
+
const intended = needsUpdated ? { ...data, type, updated } : { ...data, type };
|
|
69
|
+
if (edited !== null && parsesTo(edited, intended))
|
|
70
|
+
return edited;
|
|
71
|
+
// The text edit could not be done safely, but a re-serialized document
|
|
72
|
+
// beats a non-conformant one.
|
|
58
73
|
const { type: _priorType, ...rest } = data;
|
|
59
74
|
const next = { type, ...rest };
|
|
60
75
|
if (needsUpdated)
|
|
61
|
-
next.updated =
|
|
76
|
+
next.updated = updated;
|
|
62
77
|
return `---\n${serializeFrontmatter(next)}\n---\n${block.content}`;
|
|
63
78
|
}
|
|
79
|
+
/** True when the frontmatter of `text` parses to exactly `intended`. */
|
|
80
|
+
function parsesTo(text, intended) {
|
|
81
|
+
try {
|
|
82
|
+
return isDeepStrictEqual(parseYaml(parseFrontmatterBlock(text)?.frontmatter ?? ""), intended);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -196,17 +196,77 @@ function countLines(text) {
|
|
|
196
196
|
* frontmatter: round-tripping the mapping through the YAML serializer drops
|
|
197
197
|
* comments and normalizes formatting, which is unacceptable for a write that
|
|
198
198
|
* only needs to contribute one line. Shared by `ensureAkmMarkdownType`
|
|
199
|
-
* (
|
|
199
|
+
* (adding `type:` and `updated:` on write) and lint's `--fix` for
|
|
200
|
+
* `missing-updated`. The new line takes the block's own line ending.
|
|
200
201
|
*/
|
|
201
202
|
export function spliceFrontmatterLine(raw, line) {
|
|
202
|
-
|
|
203
|
-
|
|
203
|
+
// Alternating [line, eol, line, eol, …, line]: joining the parts back
|
|
204
|
+
// reproduces `raw` byte for byte, CRLF and all.
|
|
205
|
+
const parts = raw.split(/(\r?\n)/);
|
|
206
|
+
if (parts[0]?.trim() !== "---")
|
|
204
207
|
return null;
|
|
205
|
-
const
|
|
206
|
-
if (
|
|
208
|
+
const closeAt = parts.findIndex((part, i) => i > 0 && part.trim() === "---");
|
|
209
|
+
if (closeAt === -1)
|
|
210
|
+
return null;
|
|
211
|
+
parts.splice(closeAt, 0, line, parts[1]);
|
|
212
|
+
return parts.join("");
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Replace the top-level `key:` line of an existing frontmatter block with
|
|
216
|
+
* `line`, leaving every other byte untouched — the counterpart to
|
|
217
|
+
* {@link spliceFrontmatterLine} for correcting a field's value in place.
|
|
218
|
+
*
|
|
219
|
+
* Only that one physical line is replaced, so a value that continues onto
|
|
220
|
+
* further lines (a block sequence, a folded scalar) would be left dangling:
|
|
221
|
+
* the caller must check that the result still parses to what it meant. Returns
|
|
222
|
+
* null when `raw` has no well-formed block or no such line.
|
|
223
|
+
*/
|
|
224
|
+
export function replaceFrontmatterLine(raw, key, line) {
|
|
225
|
+
const parts = raw.split(/(\r?\n)/);
|
|
226
|
+
if (parts[0]?.trim() !== "---")
|
|
227
|
+
return null;
|
|
228
|
+
const closeAt = parts.findIndex((part, i) => i > 0 && part.trim() === "---");
|
|
229
|
+
if (closeAt === -1)
|
|
207
230
|
return null;
|
|
208
|
-
|
|
209
|
-
|
|
231
|
+
const at = parts.findIndex((part, i) => i > 0 && i < closeAt && part.match(/^(\w[\w-]*):(?:[ \t]|$)/)?.[1] === key);
|
|
232
|
+
if (at === -1)
|
|
233
|
+
return null;
|
|
234
|
+
parts[at] = line;
|
|
235
|
+
return parts.join("");
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Replace the top-level blocks of `keys` in an existing frontmatter block with
|
|
239
|
+
* `lines`, leaving every other byte untouched — the counterpart to
|
|
240
|
+
* {@link spliceFrontmatterLine} for a write that owns whole keys, such as the
|
|
241
|
+
* provenance stamp's `generated`, `verified` and `provenance`.
|
|
242
|
+
*
|
|
243
|
+
* A key's block is its `key:` line and the lines directly under it that
|
|
244
|
+
* continue it: indented lines and `- ` items. Every such block is removed
|
|
245
|
+
* wherever it sits, and `lines` are added just before the closing `---`, in the
|
|
246
|
+
* block's own line ending. A block with a blank line or a comment inside it is
|
|
247
|
+
* not removed whole, so the caller must check that the result still parses to
|
|
248
|
+
* what it meant. Returns null when `raw` has no well-formed block.
|
|
249
|
+
*/
|
|
250
|
+
export function replaceFrontmatterBlocks(raw, keys, lines) {
|
|
251
|
+
const parts = raw.split(/(\r?\n)/);
|
|
252
|
+
if (parts[0]?.trim() !== "---")
|
|
253
|
+
return null;
|
|
254
|
+
const closeAt = parts.findIndex((part, i) => i > 0 && part.trim() === "---");
|
|
255
|
+
if (closeAt === -1)
|
|
256
|
+
return null;
|
|
257
|
+
const kept = [];
|
|
258
|
+
for (let i = 2; i < closeAt; i += 2) {
|
|
259
|
+
const key = parts[i].match(/^(\w[\w-]*):(?:[ \t]|$)/)?.[1];
|
|
260
|
+
if (key === undefined || !keys.includes(key)) {
|
|
261
|
+
kept.push(parts[i], parts[i + 1]);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
// One of the keys: drop its line and the lines that continue it.
|
|
265
|
+
while (i + 2 < closeAt && /^(?:[ \t]|-(?:[ \t]|$))/.test(parts[i + 2]))
|
|
266
|
+
i += 2;
|
|
267
|
+
}
|
|
268
|
+
const added = lines.flatMap((line) => [line, parts[1]]);
|
|
269
|
+
return [parts[0], parts[1], ...kept, ...added, ...parts.slice(closeAt)].join("");
|
|
210
270
|
}
|
|
211
271
|
/**
|
|
212
272
|
* Strip one layer of matching quotes — frontmatter list items are often quoted
|
|
@@ -59,7 +59,7 @@ import { WorkflowConfigSchema } from "./schema/workflow.js";
|
|
|
59
59
|
export { EmbeddingConnectionConfigSchema } from "./schema/embedding.js";
|
|
60
60
|
export { EngineConfigSchema, EnginesSchema, LlmConnectionConfigSchema, LlmProfileConfigSchema } from "./schema/engines.js";
|
|
61
61
|
export { ExperimentalConfigSchema } from "./schema/experimental.js";
|
|
62
|
-
export {
|
|
62
|
+
export { FeedbackConfigSchema } from "./schema/feedback.js";
|
|
63
63
|
export { ImproveConfigSchema } from "./schema/improve.js";
|
|
64
64
|
export { ConsolidateProcessConfigSchema, DistillProcessConfigSchema, ExtractProcessConfigSchema, ImproveProcessConfigSchema, ImproveProfileConfigSchema, MemoryInferenceProcessConfigSchema, ProactiveMaintenanceProcessConfigSchema, ReflectProcessConfigSchema, TriageProcessConfigSchema, ValidationProcessConfigSchema, } from "./schema/improve-processes.js";
|
|
65
65
|
export { IndexConfigSchema, IndexPassConfigSchema } from "./schema/index-config.js";
|
|
@@ -23,10 +23,6 @@ import { getConfigPath } from "../paths.js";
|
|
|
23
23
|
import { warn, warnOnce } from "../warn.js";
|
|
24
24
|
// Canonical harness-id source of truth (#565) — runtime value re-export.
|
|
25
25
|
export { VALID_HARNESS_IDS } from "./config-types.js";
|
|
26
|
-
// ── Feedback failure-mode constants (F-3 / #384) ────────────────────────────
|
|
27
|
-
// Canonical taxonomy lives in the schema/validator layer; re-exported here so
|
|
28
|
-
// existing `../core/config/config` import sites keep working.
|
|
29
|
-
export { FEEDBACK_FAILURE_MODES } from "./config-schema.js";
|
|
30
26
|
// ── Defaults ────────────────────────────────────────────────────────────────
|
|
31
27
|
export const DEFAULT_CONFIG = {
|
|
32
28
|
configVersion: "0.9.0",
|
|
@@ -2,30 +2,13 @@
|
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* `feedback` config section. The retired `allowedFailureModes` key (it went
|
|
6
|
+
* with `akm feedback --failure-mode`) is tolerated as an unknown key.
|
|
7
7
|
*/
|
|
8
8
|
import { z } from "zod";
|
|
9
|
-
import { nonEmptyString } from "./primitives.js";
|
|
10
|
-
// ── Feedback failure modes (F-3 / #384) ─────────────────────────────────────
|
|
11
|
-
/**
|
|
12
|
-
* Curated taxonomy of failure modes for negative feedback.
|
|
13
|
-
*
|
|
14
|
-
* Structured failure modes enable aggregation across feedback events so the
|
|
15
|
-
* distill pipeline can detect that "5 assets failed for the same reason" and
|
|
16
|
-
* act on it — free-text strings about the same issue are not aggregatable.
|
|
17
|
-
*/
|
|
18
|
-
export const FEEDBACK_FAILURE_MODES = [
|
|
19
|
-
"incorrect", // Factually wrong or logically flawed content
|
|
20
|
-
"outdated", // Correct at some point but now stale
|
|
21
|
-
"dangerous", // Could cause harm if followed (security, safety)
|
|
22
|
-
"incomplete", // Missing key steps, context, or caveats
|
|
23
|
-
"redundant", // Duplicates another asset without adding value
|
|
24
|
-
];
|
|
25
9
|
// ── Feedback ────────────────────────────────────────────────────────────────
|
|
26
10
|
export const FeedbackConfigSchema = z
|
|
27
11
|
.object({
|
|
28
12
|
requireReason: z.boolean().optional(),
|
|
29
|
-
allowedFailureModes: z.array(nonEmptyString).optional(),
|
|
30
13
|
})
|
|
31
14
|
.passthrough();
|
|
@@ -35845,8 +35845,7 @@ var ExperimentalConfigSchema = exports_external.object({
|
|
|
35845
35845
|
|
|
35846
35846
|
// src/core/config/schema/feedback.ts
|
|
35847
35847
|
var FeedbackConfigSchema = exports_external.object({
|
|
35848
|
-
requireReason: exports_external.boolean().optional()
|
|
35849
|
-
allowedFailureModes: exports_external.array(nonEmptyString).optional()
|
|
35848
|
+
requireReason: exports_external.boolean().optional()
|
|
35850
35849
|
}).passthrough();
|
|
35851
35850
|
|
|
35852
35851
|
// src/core/config/schema/improve-processes.ts
|
|
@@ -35173,8 +35173,7 @@ var ExperimentalConfigSchema = exports_external.object({
|
|
|
35173
35173
|
|
|
35174
35174
|
// src/core/config/schema/feedback.ts
|
|
35175
35175
|
var FeedbackConfigSchema = exports_external.object({
|
|
35176
|
-
requireReason: exports_external.boolean().optional()
|
|
35177
|
-
allowedFailureModes: exports_external.array(nonEmptyString).optional()
|
|
35176
|
+
requireReason: exports_external.boolean().optional()
|
|
35178
35177
|
}).passthrough();
|
|
35179
35178
|
|
|
35180
35179
|
// src/core/config/schema/improve-processes.ts
|
package/docs/reference/cli.md
CHANGED
|
@@ -1587,17 +1587,27 @@ preserves it byte-for-byte.
|
|
|
1587
1587
|
|
|
1588
1588
|
### feedback
|
|
1589
1589
|
|
|
1590
|
-
Record positive or negative feedback for any indexed bundle asset.
|
|
1590
|
+
Record positive or negative feedback for any indexed bundle asset. Record
|
|
1591
|
+
`--negative` only when the asset's content is wrong or stale, and say what is
|
|
1592
|
+
wrong and what it should say; a note that simply did not fit your task is not
|
|
1593
|
+
negative feedback, so record nothing for it.
|
|
1591
1594
|
`akm feedback <ref> --negative --reason "<what is wrong and what should change>"`
|
|
1592
1595
|
flags the asset: it ranks lower right away, and the next improve run may repair
|
|
1593
1596
|
its description, title or `when_to_use` from your reason. Improve does not
|
|
1594
|
-
rewrite an asset's text.
|
|
1595
|
-
with `--replace`, `--with` and `--source`: akm checks that each
|
|
1596
|
-
text appears exactly once and that the frontmatter still parses,
|
|
1597
|
-
if either check fails, and queues the edit as a `feedback`
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1597
|
+
rewrite an asset's text. Once you have verified the correct fact, attach the
|
|
1598
|
+
exact fix with `--replace`, `--with` and `--source`: akm checks that each
|
|
1599
|
+
`--replace` text appears exactly once and that the frontmatter still parses,
|
|
1600
|
+
records nothing if either check fails, and queues the edit as a `feedback`
|
|
1601
|
+
proposal for review.
|
|
1602
|
+
To mark the asset's history, with or without a text fix, add `--superseded-by
|
|
1603
|
+
<ref>` (another asset replaces it) or `--outdated` (it describes a past state
|
|
1604
|
+
and no single asset replaces it), with `--reason` and `--source`. The same single
|
|
1605
|
+
proposal sets the asset's `beliefState` (`superseded`, or `deprecated`) and, for
|
|
1606
|
+
`--superseded-by`, adds the successor's ref to its `supersededBy` list, by
|
|
1607
|
+
editing only those lines of the frontmatter. `--positive` records that an asset
|
|
1608
|
+
helped (it raises its ranking) and does not trigger a rewrite. Both signals
|
|
1609
|
+
update the asset's utility score right away, so highly-rated assets rank higher
|
|
1610
|
+
in search results.
|
|
1601
1611
|
|
|
1602
1612
|
```sh
|
|
1603
1613
|
akm feedback scripts/deploy.sh --positive
|
|
@@ -1605,20 +1615,23 @@ akm feedback agents/reviewer --negative
|
|
|
1605
1615
|
akm feedback memories/deployment-notes --positive
|
|
1606
1616
|
akm feedback env/prod --positive
|
|
1607
1617
|
akm feedback skills/code-review --positive --reason "Worked perfectly for PR reviews"
|
|
1608
|
-
akm feedback skills/code-review --negative --
|
|
1618
|
+
akm feedback skills/code-review --negative --reason "references a removed flag"
|
|
1609
1619
|
akm feedback skills/code-review --negative --reason "flaky" --tag slice:train --tag team:platform
|
|
1610
1620
|
akm feedback knowledge/opencode-server --negative --reason "the default port is 4096, not 8000" --replace "port 8000" --with "port 4096" --source "https://opencode.ai/docs/server/"
|
|
1621
|
+
akm feedback knowledge/setup-v1 --negative --reason "the v2 guide replaces it" --superseded-by knowledge/setup-v2 --source "knowledge/setup-v2"
|
|
1622
|
+
akm feedback knowledge/api-v1 --negative --reason "describes the retired v1 API" --outdated --source "https://example.com/changelog"
|
|
1611
1623
|
```
|
|
1612
1624
|
|
|
1613
1625
|
| Flag | Description |
|
|
1614
1626
|
| --- | --- |
|
|
1615
1627
|
| `--positive` | Record that an asset helped: it raises its ranking and does not trigger a rewrite |
|
|
1616
1628
|
| `--negative` | Flag the asset: it ranks lower right away, and the next improve run may repair its frontmatter from `--reason` |
|
|
1617
|
-
| `--reason` | What is wrong with the asset's content and what should change; not for `akm` command errors. Attached to the feedback event (required for negative feedback by default, and always with `--replace`) |
|
|
1629
|
+
| `--reason` | What is wrong with the asset's content and what should change; not for `akm` command errors. Attached to the feedback event (required for negative feedback by default, and always with a fix: `--replace`, `--superseded-by` or `--outdated`) |
|
|
1618
1630
|
| `--replace <text>` | Exact text to correct, copied verbatim from the asset file; it must appear exactly once. Repeatable, each paired in order with a `--with`. Negative feedback only |
|
|
1619
1631
|
| `--with <text>` | The corrected text for the matching `--replace`. Use `--with=<text>` for a value that starts with `-` |
|
|
1620
|
-
| `--source <where>` | The URL, command or file that shows the correct fact. Required with `--replace`; shown to the reviewer with the proposal |
|
|
1621
|
-
| `--
|
|
1632
|
+
| `--source <where>` | The URL, command or file that shows the correct fact. Required with `--replace`, `--superseded-by` and `--outdated`; shown to the reviewer with the proposal |
|
|
1633
|
+
| `--superseded-by <ref>` | The ref of the asset that replaces this one. The proposal sets `beliefState: superseded` and adds the ref, as its `bundle//conceptId`, to `supersededBy`; `contradicted` and `archived` stay, a ref already listed is not added again, and a scalar `supersededBy` becomes a list. The ref must be indexed and must not be the asset itself, or nothing is recorded; nor is anything when the asset already says all this (the fix changes nothing). Negative feedback only; may be combined with `--replace`/`--with`, not with `--outdated`; markdown assets only |
|
|
1634
|
+
| `--outdated` | The asset describes a past state and no single asset replaces it. The proposal sets `beliefState: deprecated`, unless the asset already says `superseded`, `contradicted` or `archived`. Negative feedback only; may be combined with `--replace`/`--with`, not with `--superseded-by`; markdown assets only |
|
|
1622
1635
|
| `--tag` | Tag to attach to the feedback (repeatable, e.g. `--tag slice:train --tag team:platform`) |
|
|
1623
1636
|
| `--applied-to <ref>` | Credit a `lessons/<name>` lesson that helped resolve this task. When combined with `--positive`, appends this feedback ref to the target lesson's `lessonStrength[]` frontmatter array (dedup, idempotent). A non-lesson target, or a missing `--positive`, produces a warning rather than silently doing nothing. |
|
|
1624
1637
|
|
|
@@ -2522,8 +2535,12 @@ feedback in the last 30 days newer than the stage's last ledger attempt, or for
|
|
|
2522
2535
|
an explicit ref scope. A positive or note-only signal never plans one, so
|
|
2523
2536
|
improve does not rewrite an asset from a positive signal. Distill keeps its own
|
|
2524
2537
|
trigger: a memory with feedback of any kind (a signal or a note) in that window,
|
|
2525
|
-
newer than distill's last attempt.
|
|
2526
|
-
feedback
|
|
2538
|
+
newer than distill's last attempt. It skips a memory flagged wrong and not
|
|
2539
|
+
edited since (a negative feedback in that window judged the body it still has,
|
|
2540
|
+
or, recorded without that body's hash, is newer than the file's last write),
|
|
2541
|
+
and a memory whose only feedback in that window is positive with no reason or
|
|
2542
|
+
note, unless the ref is explicit. Two fallback lanes pick refs
|
|
2543
|
+
with no such feedback: high salience (content-scored refs at or above
|
|
2527
2544
|
`improve.salience.salienceThreshold`, default `0.75`, that were never reflected,
|
|
2528
2545
|
capped at 10% of the limit, at least one ref) and, in a strategy that enables
|
|
2529
2546
|
`proactiveMaintenance`, refs due for a revisit. They only select and score refs
|
|
@@ -3081,9 +3098,10 @@ passed on its current content is accepted (unless its target changed since it
|
|
|
3081
3098
|
was minted — that one is auto-rejected as `stale-target`); an empty diff is
|
|
3082
3099
|
rejected; a proposal that reflect or distill deferred for review is left for a
|
|
3083
3100
|
person; everything else goes to the judgment tier when one is enabled, and
|
|
3084
|
-
is otherwise left for review. A reflect revision that changes the body
|
|
3085
|
-
|
|
3086
|
-
decisions (queue mode); pass
|
|
3101
|
+
is otherwise left for review. A reflect revision that changes the body, and
|
|
3102
|
+
every distill lesson or knowledge promotion, is deferred for review even when
|
|
3103
|
+
its judge passes it. Default mode stages decisions (queue mode); pass
|
|
3104
|
+
`--promote` to actually accept.
|
|
3087
3105
|
|
|
3088
3106
|
```sh
|
|
3089
3107
|
akm proposal drain --dry-run # Preview without writing
|
|
@@ -467,7 +467,11 @@ guidance. When enabled, engine selection is judgment → triage → strategy →
|
|
|
467
467
|
|
|
468
468
|
`processes.reflect.qualityGate` and `processes.distill.qualityGate` control
|
|
469
469
|
each process's LLM-as-judge quality gate. Each is on unless it sets
|
|
470
|
-
`enabled: false`, and each follows only its own switch.
|
|
470
|
+
`enabled: false`, and each follows only its own switch. A reflect revision the
|
|
471
|
+
judge passes is staged for the triage drain to accept; a distill lesson it
|
|
472
|
+
passes is deferred for a person (reason `distill-review`), which the drain and
|
|
473
|
+
its judgment tier leave alone. With the distill gate off nothing is judged, and
|
|
474
|
+
the drain decides. The judge is the
|
|
471
475
|
process's own engine when that is an LLM engine, or the `defaults.llmEngine`
|
|
472
476
|
engine when an agent generates. `engine`, `model`, `timeoutMs` and `llm` give the gate a judge of its own,
|
|
473
477
|
resolved over the process's settings the way `triage.judgment` resolves over
|
|
@@ -745,12 +749,11 @@ or malformed response keeps the fused order.
|
|
|
745
749
|
|
|
746
750
|
## Feedback
|
|
747
751
|
|
|
748
|
-
`feedback`
|
|
752
|
+
`feedback` configures `akm feedback`:
|
|
749
753
|
|
|
750
754
|
| Key | Purpose |
|
|
751
755
|
| --- | --- |
|
|
752
|
-
| `feedback.requireReason` | Whether `akm feedback --negative` without `--reason
|
|
753
|
-
| `feedback.allowedFailureModes` | Restrict `--failure-mode` values accepted by `akm feedback`. Curated set (also the default when unset): `incorrect`, `outdated`, `dangerous`, `incomplete`, `redundant` |
|
|
756
|
+
| `feedback.requireReason` | Whether `akm feedback --negative` without `--reason` is a hard error. **Defaults to `true`** when unset — set `false` to downgrade the check to a warning instead |
|
|
754
757
|
|
|
755
758
|
## Bundles and write target
|
|
756
759
|
|
|
@@ -159,7 +159,7 @@ the set of types the code actually emits at HEAD (verified against every
|
|
|
159
159
|
| `curate` | `akm curate <prompt>` | `query`, `itemCount`, `itemRefs` |
|
|
160
160
|
| `show` | `akm show <ref>` | `ref`, `type`, `name` |
|
|
161
161
|
| `select` | `akm show` after a search returning the same ref | `ref`, `query`, `searchTs`, `rankPosition` |
|
|
162
|
-
| `feedback` | `akm feedback <ref>` | `signal` (positive/negative), `reason`, `
|
|
162
|
+
| `feedback` | `akm feedback <ref>` | `signal` (positive/negative), `reason`, `tags`, `fix` (`source`, the number of replacements and, for `--superseded-by` or `--outdated`, the `beliefState` the proposal leaves and the `supersededBy` ref, when a fix was attached), `contentHash` (sha256 of the asset's body, without its frontmatter, as it stood when the feedback was given: it lets reflect mark feedback given on an earlier version of the text, and the loop's distill pass tell that a memory flagged wrong still has it; left out for an env or secret file and when the file cannot be read) |
|
|
163
163
|
| `sync` | `akm sync` | `name`, `message`, `ok` |
|
|
164
164
|
| `index_db_vacuumed` | `akm index` VACUUMed index.db, after an index layout migration or because more than half its pages were free | `pagesBefore`, `pagesAfter`, `freelistRatioBefore` |
|
|
165
165
|
| `stash_synced` | `akm improve`'s internal auto-sync pass (the `sync.push` feature), **distinct from** the `akm sync` command above | `committed`, `pushed`, `skipped`, `reason`, `attributed` (paths the run wrote and staged), `unattributed` (in-scope paths that went dirty during the run without the run writing them — left for their author) |
|
|
@@ -188,14 +188,14 @@ the set of types the code actually emits at HEAD (verified against every
|
|
|
188
188
|
| `improve_invoked` | Start of an `akm improve` run | `ref` (scope); `strategy`, `scope`, `dryRun`, `eligibleCount` |
|
|
189
189
|
| `improve_completed` | `akm improve` run finished | run stats |
|
|
190
190
|
| `improve_failed` | `akm improve` run errored | error |
|
|
191
|
-
| `improve_skipped` | `akm improve` left a ref, a lane, or a group of refs out | `reason` (`no_new_signal`, `not_retrieved`, `distill_no_new_signal`, `budget_exhausted`, `budget_exhausted_batch`, `asset_missing_on_disk`, `strategy_filtered_all_passes`, `autonomy_gated`, `engine_unavailable`, `pool_below_min_size`, `consolidation_no_memory_updates`, `below_min_new_sessions`, `derived_memory_reflect_skipped`, `memory_distill_requires_feedback`); `count`, `remaining`, `strategy`, `lane` or `configKey` where they apply |
|
|
191
|
+
| `improve_skipped` | `akm improve` left a ref, a lane, or a group of refs out | `reason` (`no_new_signal`, `not_retrieved`, `distill_no_new_signal`, `budget_exhausted`, `budget_exhausted_batch`, `asset_missing_on_disk`, `strategy_filtered_all_passes`, `autonomy_gated`, `engine_unavailable`, `pool_below_min_size`, `consolidation_no_memory_updates`, `below_min_new_sessions`, `derived_memory_reflect_skipped`, `memory_distill_requires_feedback`, `distill_flagged_wrong`, `distill_positive_without_reason`); `count`, `remaining`, `strategy`, `lane` or `configKey` where they apply |
|
|
192
192
|
| `improve_lock_recovered` | Stale improve lock cleared at startup | |
|
|
193
193
|
| `improve_review_needed` | `akm feedback` pushed a high-utility asset's utility below the review threshold — a review-needed escalation is recorded (not a proposal, so it can't accidentally overwrite the asset) | `ref`, `previousUtility`, `nextUtility` |
|
|
194
194
|
| `reflect_invoked` | Start of reflect phase in `akm improve` | `ref`, engine |
|
|
195
195
|
| `reflect_completed` | Reflect phase produced a proposal | `ref` |
|
|
196
196
|
| `improve_reflect_outcome` | Per-asset reflect result | `ref`, `ok`, `durationMs`, `reason` |
|
|
197
197
|
| `propose_invoked` | `akm proposal new` | `ref` |
|
|
198
|
-
| `distill_invoked` | Distill phase inside the `akm improve`/`akm proposal new` pipeline. **`akm distill` is not a CLI command** — there is no standalone verb by that name | `ref`, outcome |
|
|
198
|
+
| `distill_invoked` | Distill phase inside the `akm improve`/`akm proposal new` pipeline. **`akm distill` is not a CLI command** — there is no standalone verb by that name | `ref`, outcome (`queued`, `skipped` with a `skipReason` such as `lesson_exists` or `conflict_noop`, `llm_failed`, `validation_failed`, `quality_rejected`, `review_needed`) |
|
|
199
199
|
| `extract_invoked` | `akm proposal extract --type <harness>` / `--auto`, or improve-stage session extraction | `outcome`, `sessionId`, `harness` |
|
|
200
200
|
| `extract_triaged` | The pre-LLM extract triage gate evaluated at least one session | `evaluated`, `passed`, `triagedOut`, `sourceRun` (aggregated) |
|
|
201
201
|
| `schema_repair_invoked` | The schema-repair pass inside `akm improve` (`runSchemaRepairPass`) attempts to patch missing frontmatter on an asset that failed schema validation. **There is no `akm lint --repair` flag** — `lint` has `--fix`/`--auto-fix`, unrelated to this event | `ref`, outcome |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akm-cli",
|
|
3
|
-
"version": "0.9.26
|
|
3
|
+
"version": "0.9.26",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cursor.",
|
|
6
6
|
"keywords": [
|
package/schemas/akm-config.json
CHANGED
|
@@ -595,13 +595,6 @@
|
|
|
595
595
|
"properties": {
|
|
596
596
|
"requireReason": {
|
|
597
597
|
"type": "boolean"
|
|
598
|
-
},
|
|
599
|
-
"allowedFailureModes": {
|
|
600
|
-
"type": "array",
|
|
601
|
-
"items": {
|
|
602
|
-
"type": "string",
|
|
603
|
-
"minLength": 1
|
|
604
|
-
}
|
|
605
598
|
}
|
|
606
599
|
},
|
|
607
600
|
"additionalProperties": true
|
|
@@ -2274,13 +2267,6 @@
|
|
|
2274
2267
|
"properties": {
|
|
2275
2268
|
"requireReason": {
|
|
2276
2269
|
"type": "boolean"
|
|
2277
|
-
},
|
|
2278
|
-
"allowedFailureModes": {
|
|
2279
|
-
"type": "array",
|
|
2280
|
-
"items": {
|
|
2281
|
-
"type": "string",
|
|
2282
|
-
"minLength": 1
|
|
2283
|
-
}
|
|
2284
2270
|
}
|
|
2285
2271
|
},
|
|
2286
2272
|
"additionalProperties": true
|