akm-cli 0.9.26-alpha.1 → 0.9.26-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/dist/assets/hints/cli-hints-full.md +7 -3
- package/dist/assets/hints/cli-hints-short.md +3 -2
- package/dist/assets/stash-skeleton/README.md +5 -2
- package/dist/commands/feedback-cli.js +130 -8
- package/dist/commands/proposal/repository.js +1 -0
- package/dist/output/shapes/helpers.js +2 -0
- package/dist/output/text/proposal-format.js +5 -0
- package/dist/scripts/akm-migrate-node.js +7 -0
- package/dist/scripts/akm-migrate.js +7 -0
- package/dist/storage/repositories/proposals-repository.js +9 -0
- package/docs/reference/cli.md +16 -7
- package/docs/reference/data-and-telemetry.md +1 -1
- 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
|
+
## [0.9.26-alpha.2] - 2026-10-05
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **`akm feedback --negative` can carry an exact fix of the asset's text:**
|
|
14
|
+
`--replace "<exact current text>" --with "<corrected text>" --source "<URL,
|
|
15
|
+
command or file>"`, repeatable for several edits. akm checks at once that each
|
|
16
|
+
`--replace` text appears exactly once and that the frontmatter still parses,
|
|
17
|
+
records nothing if a check fails (so the caller can correct it and retry), and
|
|
18
|
+
queues the edit as a `feedback` proposal that shows the reason and source to
|
|
19
|
+
the reviewer. Improve has edited only an asset's frontmatter since 0.9.25, so
|
|
20
|
+
this is how a wrong fact in a note's text gets corrected: by the agent that
|
|
21
|
+
found it, with its evidence, instead of by a nightly rewrite.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **`akm feedback` help, the shipped hints and the docs no longer promise that
|
|
26
|
+
improve proposes a fix of the text from a negative reason;** they say it may
|
|
27
|
+
repair the frontmatter, and point to the exact-fix flags.
|
|
28
|
+
|
|
9
29
|
## [0.9.26-alpha.1] - 2026-10-05
|
|
10
30
|
|
|
11
31
|
### Changed
|
|
@@ -95,7 +95,8 @@ akm workflow create ship-release # Create a workflow asset in the
|
|
|
95
95
|
akm lint --type workflows # Parse and compile every .md/.yml workflow source; list every error
|
|
96
96
|
akm workflow run workflows/ship-release # Start or resume and execute the workflow
|
|
97
97
|
akm feedback skills/code-review --positive # Record that an asset helped (ranks it higher; no rewrite)
|
|
98
|
-
akm feedback agents/reviewer --negative --reason "wrong framework" # Flag it
|
|
98
|
+
akm feedback agents/reviewer --negative --reason "wrong framework" # Flag it: lowers its ranking; improve may repair its frontmatter
|
|
99
|
+
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/" # Queue an exact fix
|
|
99
100
|
akm feedback memories/deployment-notes --positive # Works for memories too
|
|
100
101
|
akm feedback env/prod --positive # Records env feedback without surfacing values
|
|
101
102
|
```
|
|
@@ -103,8 +104,11 @@ akm feedback env/prod --positive # Records env feedback without su
|
|
|
103
104
|
Use `akm feedback` whenever an asset's content materially helps, or proves wrong,
|
|
104
105
|
stale or unhelpful, so future search ranking can learn from actual usage.
|
|
105
106
|
`akm feedback <ref> --negative --reason "<what is wrong and what should change>"`
|
|
106
|
-
flags the asset
|
|
107
|
-
|
|
107
|
+
flags the asset: it ranks lower right away, and the next improve run may repair
|
|
108
|
+
its description, title or `when_to_use` from your reason. Improve does not
|
|
109
|
+
rewrite an asset's text: to correct a wrong fact there, attach the exact fix
|
|
110
|
+
with `--replace "<exact current text>" --with "<corrected text>" --source "<URL,
|
|
111
|
+
command or file>"`, which akm checks and queues as a proposal. `--positive` records that an asset helped (it raises its
|
|
108
112
|
ranking) and does not trigger a rewrite; improve no longer rewrites assets from
|
|
109
113
|
positive signals or on a proactive cadence. An akm command that fails says
|
|
110
114
|
nothing about the asset; don't record it as feedback.
|
|
@@ -8,7 +8,7 @@ For any task, follow this loop:
|
|
|
8
8
|
1. `akm curate "<task>"` — find the best matching asset
|
|
9
9
|
2. `akm show <ref>` — read the schema (field names and structure)
|
|
10
10
|
3. Edit the workspace file using schema field names + task-specific values from your README
|
|
11
|
-
4. `akm feedback <ref> --positive` — record that the asset helped (it raises its ranking and does not trigger a rewrite); when its content was wrong, stale or unhelpful, `akm feedback <ref> --negative --reason "<what is wrong and what should change>"` flags it
|
|
11
|
+
4. `akm feedback <ref> --positive` — record that the asset helped (it raises its ranking and does not trigger a rewrite); when its content was wrong, stale or unhelpful, `akm feedback <ref> --negative --reason "<what is wrong and what should change>"` flags it: the next improve run may repair its description, title or `when_to_use` from your reason, but not its text. To correct a wrong fact in the text, add the exact fix: `--replace "<exact current text>" --with "<corrected text>" --source "<URL, command or file>"`. A failed akm command (e.g. `akm show` erroring) is not feedback on the asset — don't record it.
|
|
12
12
|
|
|
13
13
|
For workflow tasks:
|
|
14
14
|
1. `akm show workflows/<name>` — inspect the procedure before executing it
|
|
@@ -40,7 +40,8 @@ akm proposal diff skills/akm-dream # Diff proposal by ref, UUID, or 8
|
|
|
40
40
|
akm proposal accept 7c115132 # Accept by UUID prefix
|
|
41
41
|
akm proposal reject skills/my-skill --reason "..." # Reject by ref
|
|
42
42
|
akm feedback <ref> --positive # Record that an asset helped (ranks it higher; no rewrite)
|
|
43
|
-
akm feedback <ref> --negative --reason "..." # Flag it
|
|
43
|
+
akm feedback <ref> --negative --reason "..." # Flag it: lowers its ranking; improve may repair its frontmatter
|
|
44
|
+
akm feedback <ref> --negative --reason "..." --replace "<exact text>" --with "<fix>" --source "<url|cmd|file>" # Queue an exact fix of its text
|
|
44
45
|
akm bundle add <ref> # Add a source (npm, GitHub, git, local dir)
|
|
45
46
|
akm clone <ref> # Copy an asset to the working bundle (optional --dest arg to clone to specific location)
|
|
46
47
|
akm sync # Commit (and push if writable remote) changes in the primary bundle (--no-push to commit only)
|
|
@@ -80,10 +80,13 @@ akm search "<query>" --type skill
|
|
|
80
80
|
# Mark an asset as helpful (raises its ranking; does not trigger a rewrite)
|
|
81
81
|
akm feedback <ref> --positive
|
|
82
82
|
|
|
83
|
-
# Flag an asset
|
|
84
|
-
#
|
|
83
|
+
# Flag an asset: it ranks lower, and the next improve run may repair its
|
|
84
|
+
# description, title or when_to_use from your reason
|
|
85
85
|
akm feedback <ref> --negative --reason "<what is wrong and what should change>"
|
|
86
86
|
|
|
87
|
+
# Correct a wrong fact in an asset's text: the exact fix is checked and queued for review
|
|
88
|
+
akm feedback <ref> --negative --reason "<what is wrong>" --replace "<exact current text>" --with "<corrected text>" --source "<URL, command or file>"
|
|
89
|
+
|
|
87
90
|
# Capture a durable lesson or memory from the current session
|
|
88
91
|
akm remember "<fact or lesson>"
|
|
89
92
|
```
|
|
@@ -2,12 +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
|
import fs from "node:fs";
|
|
5
|
+
import { parse as yamlParse } from "yaml";
|
|
5
6
|
import { defineJsonCommand, output, parseAllFlagValues } from "../cli/shared.js";
|
|
6
7
|
import { makeBundleRef, parseBundleRef } from "../core/asset/asset-ref.js";
|
|
7
8
|
import { assembleAsset } from "../core/asset/asset-serialize.js";
|
|
8
9
|
import { parseFrontmatter, parseFrontmatterBlock } from "../core/asset/frontmatter.js";
|
|
9
10
|
import { conceptIdFromTypeName, parseRefInput } from "../core/asset/resolve-ref.js";
|
|
10
|
-
import { isWithin, writeFileAtomic } from "../core/common.js";
|
|
11
|
+
import { isWithin, resolveStashDir, writeFileAtomic } from "../core/common.js";
|
|
11
12
|
import { FEEDBACK_FAILURE_MODES, loadConfig } from "../core/config/config.js";
|
|
12
13
|
import { NotFoundError, UsageError } from "../core/errors.js";
|
|
13
14
|
import { appendEvent } from "../core/events.js";
|
|
@@ -23,6 +24,7 @@ import { resolveSourcesForOrigin } from "../registry/origin-resolve.js";
|
|
|
23
24
|
import { closeDatabase, openExistingDatabase } from "../storage/repositories/index-connection.js";
|
|
24
25
|
import { findEntryIdByRef, getEntryFilePathById, getItemRefById, } from "../storage/repositories/index-entries-repository.js";
|
|
25
26
|
import { applyFeedbackToUtilityScore } from "../storage/repositories/index-utility-repository.js";
|
|
27
|
+
import { createProposal } from "./proposal/repository.js";
|
|
26
28
|
// ── Tag validation ────────────────────────────────────────────────────────────
|
|
27
29
|
const TAG_KEY_RE = /^[a-z_][a-z0-9_]*$/;
|
|
28
30
|
function validateFeedbackTags(raw) {
|
|
@@ -185,15 +187,74 @@ function recordFeedbackUsage(indexDb, entryId, durableEntryRef, signal, metadata
|
|
|
185
187
|
});
|
|
186
188
|
return { utilityResult, rankingUpdateApplied, rankingUpdateSkippedReason };
|
|
187
189
|
}
|
|
190
|
+
// ── Exact fixes ──────────────────────────────────────────────────────────────
|
|
191
|
+
/** 1-based numbers of the lines on which `needle` starts. */
|
|
192
|
+
function startLines(text, needle) {
|
|
193
|
+
const lines = [];
|
|
194
|
+
for (let at = text.indexOf(needle); at !== -1; at = text.indexOf(needle, at + 1)) {
|
|
195
|
+
lines.push(text.slice(0, at).split("\n").length);
|
|
196
|
+
}
|
|
197
|
+
return lines;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Apply `--replace`/`--with` pairs in order. Each `--replace` text must appear
|
|
201
|
+
* exactly once in the text as it stands at that point, so the edit can only
|
|
202
|
+
* land where the caller meant it.
|
|
203
|
+
*/
|
|
204
|
+
export function applyExactReplacements(text, pairs, file) {
|
|
205
|
+
let out = text;
|
|
206
|
+
pairs.forEach((pair, i) => {
|
|
207
|
+
const label = `--replace #${i + 1}`;
|
|
208
|
+
if (!pair.old)
|
|
209
|
+
throw new UsageError(`${label} is empty.`, "INVALID_FLAG_VALUE");
|
|
210
|
+
const lines = startLines(out, pair.old);
|
|
211
|
+
if (lines.length === 0) {
|
|
212
|
+
throw new UsageError(`${label} was not found in ${file}.`, "INVALID_FLAG_VALUE", "Copy the text verbatim from the file, including whitespace and punctuation.");
|
|
213
|
+
}
|
|
214
|
+
if (lines.length > 1) {
|
|
215
|
+
throw new UsageError(`${label} appears ${lines.length} times in ${file} (lines ${lines.join(", ")}).`, "INVALID_FLAG_VALUE", "Include more of the surrounding text so it appears once.");
|
|
216
|
+
}
|
|
217
|
+
const at = out.indexOf(pair.old);
|
|
218
|
+
out = out.slice(0, at) + pair.new + out.slice(at + pair.old.length);
|
|
219
|
+
});
|
|
220
|
+
return out;
|
|
221
|
+
}
|
|
222
|
+
/** A fix may change the frontmatter's values, but it must still parse as YAML. */
|
|
223
|
+
function assertFrontmatterStillParses(before, after, file) {
|
|
224
|
+
if (!parseFrontmatterBlock(before))
|
|
225
|
+
return;
|
|
226
|
+
const block = parseFrontmatterBlock(after);
|
|
227
|
+
let problem;
|
|
228
|
+
if (!block) {
|
|
229
|
+
problem = "the frontmatter block is gone";
|
|
230
|
+
}
|
|
231
|
+
else if (block.frontmatter.trim()) {
|
|
232
|
+
try {
|
|
233
|
+
const data = yamlParse(block.frontmatter);
|
|
234
|
+
if (typeof data !== "object" || data === null || Array.isArray(data))
|
|
235
|
+
problem = "it is no longer a mapping";
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
problem = (err instanceof Error ? err.message : String(err)).split("\n")[0];
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (problem) {
|
|
242
|
+
throw new UsageError(`The fix breaks the frontmatter of ${file}: ${problem}.`, "INVALID_FLAG_VALUE", 'Quote a value that contains ": ", or leave the frontmatter alone.');
|
|
243
|
+
}
|
|
244
|
+
}
|
|
188
245
|
// ── Command definition ────────────────────────────────────────────────────────
|
|
189
246
|
export const feedbackCommand = defineJsonCommand({
|
|
190
247
|
meta: {
|
|
191
248
|
name: "feedback",
|
|
192
249
|
description: "Record positive or negative feedback for any indexed bundle asset.\n\n" +
|
|
193
250
|
'`akm feedback <ref> --negative --reason "<what is wrong and what should change>"` flags\n' +
|
|
194
|
-
"the asset
|
|
195
|
-
"
|
|
196
|
-
"
|
|
251
|
+
"the asset: the next improve run may repair its description, title or when_to_use from\n" +
|
|
252
|
+
"your reason, but it does not rewrite the text. To correct a wrong fact in the text, attach\n" +
|
|
253
|
+
'the exact fix: --replace "<exact current text>" --with "<corrected text>" --source "<URL,\n' +
|
|
254
|
+
'command or file that shows it>" (repeat --replace/--with for several edits; use --with=...\n' +
|
|
255
|
+
"for a value that starts with -). akm checks that each --replace text appears exactly once\n" +
|
|
256
|
+
"and queues the edit as a proposal for review. `--positive` records that an asset helped\n" +
|
|
257
|
+
"(it raises its ranking) and does not trigger a rewrite.\n\n" +
|
|
197
258
|
"Both signals adjust the asset's usefulness score right away, in the same\n" +
|
|
198
259
|
"process: positive feedback raises it, negative lowers it, and recent\n" +
|
|
199
260
|
"feedback counts for more than old feedback. No reindex is needed — the new\n" +
|
|
@@ -211,12 +272,24 @@ export const feedbackCommand = defineJsonCommand({
|
|
|
211
272
|
},
|
|
212
273
|
negative: {
|
|
213
274
|
type: "boolean",
|
|
214
|
-
description: "Flag the asset
|
|
275
|
+
description: "Flag the asset: lowers its ranking immediately (no reindex needed), and the next improve run may repair its frontmatter from --reason. Attach --replace/--with/--source to correct its text.",
|
|
215
276
|
default: false,
|
|
216
277
|
},
|
|
217
278
|
reason: {
|
|
218
279
|
type: "string",
|
|
219
|
-
description: "What is wrong with the asset's content and what should change
|
|
280
|
+
description: "What is wrong with the asset's content and what should change, specifically (required for negative feedback by default). Not for akm command errors.",
|
|
281
|
+
},
|
|
282
|
+
replace: {
|
|
283
|
+
type: "string",
|
|
284
|
+
description: "Exact text to correct, copied verbatim from the asset file; it must appear exactly once (repeatable, each paired with a --with in order). Negative feedback only.",
|
|
285
|
+
},
|
|
286
|
+
with: {
|
|
287
|
+
type: "string",
|
|
288
|
+
description: "Corrected text for the matching --replace (repeatable, in the same order).",
|
|
289
|
+
},
|
|
290
|
+
source: {
|
|
291
|
+
type: "string",
|
|
292
|
+
description: "Where the correct fact comes from: a URL, command or file. Required with --replace.",
|
|
220
293
|
},
|
|
221
294
|
"failure-mode": {
|
|
222
295
|
type: "string",
|
|
@@ -262,18 +335,37 @@ export const feedbackCommand = defineJsonCommand({
|
|
|
262
335
|
throw new UsageError(`Invalid --failure-mode "${failureMode}". Accepted values: ${allowedModes.join(", ")}.`, "INVALID_FLAG_VALUE", `Use one of: ${allowedModes.join(", ")}`);
|
|
263
336
|
}
|
|
264
337
|
}
|
|
338
|
+
// An exact fix for the asset's text: each --replace pairs with a --with, in order.
|
|
339
|
+
const replaces = parseAllFlagValues("--replace");
|
|
340
|
+
const withs = parseAllFlagValues("--with");
|
|
341
|
+
const fixSource = args.source?.trim() || undefined;
|
|
342
|
+
const fixPairs = replaces.map((old, i) => ({ old, new: withs[i] ?? "" }));
|
|
343
|
+
if (replaces.length > 0 || withs.length > 0 || fixSource !== undefined) {
|
|
344
|
+
if (!args.negative) {
|
|
345
|
+
throw new UsageError("--replace, --with and --source are only for negative feedback.", "INVALID_FLAG_VALUE");
|
|
346
|
+
}
|
|
347
|
+
if (replaces.length === 0 || replaces.length !== withs.length) {
|
|
348
|
+
throw new UsageError(`Each --replace needs one --with (got ${replaces.length} --replace and ${withs.length} --with).`, "INVALID_FLAG_VALUE");
|
|
349
|
+
}
|
|
350
|
+
if (!fixSource) {
|
|
351
|
+
throw new UsageError("A fix needs --source: the URL, command or file that shows the correct fact.", "MISSING_REQUIRED_ARGUMENT");
|
|
352
|
+
}
|
|
353
|
+
if (!reason?.trim()) {
|
|
354
|
+
throw new UsageError("A fix needs --reason: say what is wrong.", "MISSING_REQUIRED_ARGUMENT");
|
|
355
|
+
}
|
|
356
|
+
}
|
|
265
357
|
if (args.negative === true && !reason?.trim()) {
|
|
266
358
|
// F-3 / #384: Default requireReason is now true. Load config to allow
|
|
267
359
|
// operators to opt out via feedback.requireReason: false in akm.json.
|
|
268
360
|
const cfg = loadConfig();
|
|
269
361
|
const requireReason = cfg.feedback?.requireReason ?? true; // Default: true (F-3 / #384)
|
|
270
362
|
if (requireReason) {
|
|
271
|
-
throw new UsageError("Negative feedback requires --reason:
|
|
363
|
+
throw new UsageError("Negative feedback requires --reason: say what is wrong and what should change. " +
|
|
272
364
|
"Use --failure-mode for a curated taxonomy or --reason for free text. " +
|
|
273
365
|
"Set feedback.requireReason: false in akm.json to downgrade to a warning.", "MISSING_REQUIRED_ARGUMENT", `Hint: akm feedback ${ref} --negative --reason "<what is wrong and what should change>" [--failure-mode incorrect|outdated|dangerous|incomplete|redundant]`);
|
|
274
366
|
}
|
|
275
367
|
else {
|
|
276
|
-
warn("Warning: negative feedback without --reason
|
|
368
|
+
warn("Warning: negative feedback without --reason says nothing about what is wrong.");
|
|
277
369
|
}
|
|
278
370
|
}
|
|
279
371
|
const rawTags = parseAllFlagValues("--tag");
|
|
@@ -283,6 +375,7 @@ export const feedbackCommand = defineJsonCommand({
|
|
|
283
375
|
...(reason?.trim() ? { reason: reason.trim() } : {}),
|
|
284
376
|
...(failureMode ? { failureMode } : {}),
|
|
285
377
|
...(validatedTags.length > 0 ? { tags: validatedTags } : {}),
|
|
378
|
+
...(fixPairs.length > 0 ? { fix: { source: fixSource, replacements: fixPairs.length } } : {}),
|
|
286
379
|
};
|
|
287
380
|
const metadataStr = Object.keys(metadataObj).length > 1 ? JSON.stringify(metadataObj) : undefined;
|
|
288
381
|
// Feedback only needs the index to exist, not to be current. A stale index
|
|
@@ -309,6 +402,7 @@ export const feedbackCommand = defineJsonCommand({
|
|
|
309
402
|
let rankingUpdateApplied = false;
|
|
310
403
|
let rankingUpdateSkippedReason;
|
|
311
404
|
let durableRef = ref;
|
|
405
|
+
let fix;
|
|
312
406
|
const db = openExistingDatabase();
|
|
313
407
|
try {
|
|
314
408
|
const config = loadConfig();
|
|
@@ -338,6 +432,23 @@ export const feedbackCommand = defineJsonCommand({
|
|
|
338
432
|
if (!itemRef)
|
|
339
433
|
throw new UsageError(`Indexed ref "${ref}" has no durable item ref.`, "INVALID_PROPOSAL");
|
|
340
434
|
durableRef = itemRef;
|
|
435
|
+
if (fixPairs.length > 0) {
|
|
436
|
+
// Checked before anything is recorded, so a fix that does not apply leaves no trace.
|
|
437
|
+
const filePath = getEntryFilePathById(db, entryId);
|
|
438
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
439
|
+
throw new NotFoundError(`The file for ${itemRef} is missing on disk.`, "ASSET_NOT_FOUND");
|
|
440
|
+
}
|
|
441
|
+
const resolved = resolveMutationTarget(config, parseRefInput(itemRef), undefined, { requireWritable: true });
|
|
442
|
+
if (!isWithin(filePath, resolved.target.source.path)) {
|
|
443
|
+
throw new UsageError(`${itemRef} is outside bundle "${resolved.target.source.name}".`);
|
|
444
|
+
}
|
|
445
|
+
const before = fs.readFileSync(filePath, "utf8");
|
|
446
|
+
const after = applyExactReplacements(before, fixPairs, filePath);
|
|
447
|
+
if (after === before)
|
|
448
|
+
throw new UsageError("The fix changes nothing.", "INVALID_FLAG_VALUE");
|
|
449
|
+
assertFrontmatterStillParses(before, after, filePath);
|
|
450
|
+
fix = { content: after, target: { source: resolved.target.source.name, root: resolved.target.source.path } };
|
|
451
|
+
}
|
|
341
452
|
const recordResult = recordFeedbackUsage(db, entryId, itemRef, signal, metadataStr);
|
|
342
453
|
utilityResult = recordResult.utilityResult;
|
|
343
454
|
rankingUpdateApplied = recordResult.rankingUpdateApplied;
|
|
@@ -351,6 +462,16 @@ export const feedbackCommand = defineJsonCommand({
|
|
|
351
462
|
ref: durableRef,
|
|
352
463
|
metadata: metadataObj,
|
|
353
464
|
});
|
|
465
|
+
const fixProposal = fix && reason?.trim() && fixSource
|
|
466
|
+
? createProposal(resolveStashDir(), {
|
|
467
|
+
ref: durableRef,
|
|
468
|
+
itemRef: durableRef,
|
|
469
|
+
target: fix.target,
|
|
470
|
+
source: "feedback",
|
|
471
|
+
payload: { content: fix.content },
|
|
472
|
+
feedback: { reason: reason.trim(), source: fixSource },
|
|
473
|
+
})
|
|
474
|
+
: undefined;
|
|
354
475
|
// F-5 / #386: When a high-utility asset crosses below the review threshold,
|
|
355
476
|
// auto-create a review-needed escalation proposal so a human can confirm
|
|
356
477
|
// whether the negative feedback is valid before the asset falls out of
|
|
@@ -419,6 +540,7 @@ export const feedbackCommand = defineJsonCommand({
|
|
|
419
540
|
rankingUpdate: rankingUpdateApplied
|
|
420
541
|
? { applied: true }
|
|
421
542
|
: { applied: false, reason: rankingUpdateSkippedReason ?? "unknown" },
|
|
543
|
+
...(fixProposal ? { fix: { proposalId: fixProposal.id, replacements: fixPairs.length, source: fixSource } } : {}),
|
|
422
544
|
...(appliedToResult
|
|
423
545
|
? { appliedTo: { ref: appliedToResult.lessonRef, lessonStrength: appliedToResult.strength } }
|
|
424
546
|
: {}),
|
|
@@ -287,6 +287,7 @@ export function createProposal(stashDir, input, ctx) {
|
|
|
287
287
|
...(input.eligibilitySource !== undefined ? { eligibilitySource: input.eligibilitySource } : {}),
|
|
288
288
|
...(input.promotionSource !== undefined ? { promotionSource: input.promotionSource } : {}),
|
|
289
289
|
...(input.promotionSourceHash !== undefined ? { promotionSourceHash: input.promotionSourceHash } : {}),
|
|
290
|
+
...(input.feedback !== undefined ? { feedback: input.feedback } : {}),
|
|
290
291
|
};
|
|
291
292
|
upsertProposal(db, proposal, stashDir);
|
|
292
293
|
for (const ref of input.attemptedRefs ?? [normalizedRef]) {
|
|
@@ -85,6 +85,7 @@ export function shapeProposalEntry(entry, detail) {
|
|
|
85
85
|
"updatedAt",
|
|
86
86
|
"confidence",
|
|
87
87
|
"gateDecision",
|
|
88
|
+
"feedback",
|
|
88
89
|
"review",
|
|
89
90
|
"reviewHistory",
|
|
90
91
|
"retirement",
|
|
@@ -101,6 +102,7 @@ export function shapeProposalEntry(entry, detail) {
|
|
|
101
102
|
"updatedAt",
|
|
102
103
|
"confidence",
|
|
103
104
|
"gateDecision",
|
|
105
|
+
"feedback",
|
|
104
106
|
"payload",
|
|
105
107
|
"review",
|
|
106
108
|
"reviewHistory",
|
|
@@ -183,6 +183,11 @@ export function formatProposalShowPlain(r) {
|
|
|
183
183
|
if (gate.decidedAt)
|
|
184
184
|
lines.push(`gate.decidedAt: ${String(gate.decidedAt)}`);
|
|
185
185
|
}
|
|
186
|
+
const fix = p.feedback;
|
|
187
|
+
if (fix) {
|
|
188
|
+
lines.push(`feedback.reason: ${String(fix.reason)}`);
|
|
189
|
+
lines.push(`feedback.source: ${String(fix.source)}`);
|
|
190
|
+
}
|
|
186
191
|
const review = p.review;
|
|
187
192
|
if (review) {
|
|
188
193
|
lines.push(`review.outcome: ${String(review.outcome ?? "?")}`);
|
|
@@ -28539,6 +28539,7 @@ function shapeProposalEntry(entry, detail) {
|
|
|
28539
28539
|
"updatedAt",
|
|
28540
28540
|
"confidence",
|
|
28541
28541
|
"gateDecision",
|
|
28542
|
+
"feedback",
|
|
28542
28543
|
"review",
|
|
28543
28544
|
"reviewHistory",
|
|
28544
28545
|
"retirement"
|
|
@@ -28554,6 +28555,7 @@ function shapeProposalEntry(entry, detail) {
|
|
|
28554
28555
|
"updatedAt",
|
|
28555
28556
|
"confidence",
|
|
28556
28557
|
"gateDecision",
|
|
28558
|
+
"feedback",
|
|
28557
28559
|
"payload",
|
|
28558
28560
|
"review",
|
|
28559
28561
|
"reviewHistory",
|
|
@@ -30491,6 +30493,11 @@ function formatProposalShowPlain(r) {
|
|
|
30491
30493
|
if (gate.decidedAt)
|
|
30492
30494
|
lines.push(`gate.decidedAt: ${String(gate.decidedAt)}`);
|
|
30493
30495
|
}
|
|
30496
|
+
const fix = p.feedback;
|
|
30497
|
+
if (fix) {
|
|
30498
|
+
lines.push(`feedback.reason: ${String(fix.reason)}`);
|
|
30499
|
+
lines.push(`feedback.source: ${String(fix.source)}`);
|
|
30500
|
+
}
|
|
30494
30501
|
const review = p.review;
|
|
30495
30502
|
if (review) {
|
|
30496
30503
|
lines.push(`review.outcome: ${String(review.outcome ?? "?")}`);
|
|
@@ -27867,6 +27867,7 @@ function shapeProposalEntry(entry, detail) {
|
|
|
27867
27867
|
"updatedAt",
|
|
27868
27868
|
"confidence",
|
|
27869
27869
|
"gateDecision",
|
|
27870
|
+
"feedback",
|
|
27870
27871
|
"review",
|
|
27871
27872
|
"reviewHistory",
|
|
27872
27873
|
"retirement"
|
|
@@ -27882,6 +27883,7 @@ function shapeProposalEntry(entry, detail) {
|
|
|
27882
27883
|
"updatedAt",
|
|
27883
27884
|
"confidence",
|
|
27884
27885
|
"gateDecision",
|
|
27886
|
+
"feedback",
|
|
27885
27887
|
"payload",
|
|
27886
27888
|
"review",
|
|
27887
27889
|
"reviewHistory",
|
|
@@ -29819,6 +29821,11 @@ function formatProposalShowPlain(r) {
|
|
|
29819
29821
|
if (gate.decidedAt)
|
|
29820
29822
|
lines.push(`gate.decidedAt: ${String(gate.decidedAt)}`);
|
|
29821
29823
|
}
|
|
29824
|
+
const fix = p.feedback;
|
|
29825
|
+
if (fix) {
|
|
29826
|
+
lines.push(`feedback.reason: ${String(fix.reason)}`);
|
|
29827
|
+
lines.push(`feedback.source: ${String(fix.source)}`);
|
|
29828
|
+
}
|
|
29822
29829
|
const review = p.review;
|
|
29823
29830
|
if (review) {
|
|
29824
29831
|
lines.push(`review.outcome: ${String(review.outcome ?? "?")}`);
|
|
@@ -171,6 +171,12 @@ function validatePresentMetadata(meta) {
|
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
|
+
if (Object.hasOwn(meta, "feedback")) {
|
|
175
|
+
const fix = meta.feedback;
|
|
176
|
+
if (!isRecord(fix) || typeof fix.reason !== "string" || typeof fix.source !== "string") {
|
|
177
|
+
invalidPresentField("feedback");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
174
180
|
if (Object.hasOwn(meta, "acceptedTarget")) {
|
|
175
181
|
const target = meta.acceptedTarget;
|
|
176
182
|
if (typeof target !== "object" ||
|
|
@@ -293,6 +299,7 @@ export function proposalRowToProposal(row) {
|
|
|
293
299
|
...(meta.reviewHistory !== undefined ? { reviewHistory: meta.reviewHistory } : {}),
|
|
294
300
|
...(typeof meta.confidence === "number" ? { confidence: meta.confidence } : {}),
|
|
295
301
|
...(meta.gateDecision !== undefined ? { gateDecision: meta.gateDecision } : {}),
|
|
302
|
+
...(meta.feedback !== undefined ? { feedback: meta.feedback } : {}),
|
|
296
303
|
...(typeof meta.backupContent === "string" ? { backupContent: meta.backupContent } : {}),
|
|
297
304
|
...(meta.acceptedTarget !== undefined ? { acceptedTarget: meta.acceptedTarget } : {}),
|
|
298
305
|
...(typeof meta.eligibilitySource === "string"
|
|
@@ -359,6 +366,8 @@ export function proposalToRowValues(proposal, stashDir) {
|
|
|
359
366
|
metaObj.confidence = proposal.confidence;
|
|
360
367
|
if (proposal.gateDecision !== undefined)
|
|
361
368
|
metaObj.gateDecision = proposal.gateDecision;
|
|
369
|
+
if (proposal.feedback !== undefined)
|
|
370
|
+
metaObj.feedback = proposal.feedback;
|
|
362
371
|
if (proposal.backupContent !== undefined)
|
|
363
372
|
metaObj.backupContent = proposal.backupContent;
|
|
364
373
|
if (proposal.acceptedTarget !== undefined)
|
package/docs/reference/cli.md
CHANGED
|
@@ -1589,9 +1589,14 @@ preserves it byte-for-byte.
|
|
|
1589
1589
|
|
|
1590
1590
|
Record positive or negative feedback for any indexed bundle asset.
|
|
1591
1591
|
`akm feedback <ref> --negative --reason "<what is wrong and what should change>"`
|
|
1592
|
-
flags the asset
|
|
1593
|
-
|
|
1594
|
-
|
|
1592
|
+
flags the asset: it ranks lower right away, and the next improve run may repair
|
|
1593
|
+
its description, title or `when_to_use` from your reason. Improve does not
|
|
1594
|
+
rewrite an asset's text. To correct a wrong fact there, attach the exact fix
|
|
1595
|
+
with `--replace`, `--with` and `--source`: akm checks that each `--replace`
|
|
1596
|
+
text appears exactly once and that the frontmatter still parses, records nothing
|
|
1597
|
+
if either check fails, and queues the edit as a `feedback` proposal for review.
|
|
1598
|
+
`--positive` records that an asset helped (it raises its ranking) and does not
|
|
1599
|
+
trigger a rewrite. Both signals update the asset's
|
|
1595
1600
|
utility score right away, so highly-rated assets rank higher in search results.
|
|
1596
1601
|
|
|
1597
1602
|
```sh
|
|
@@ -1602,13 +1607,17 @@ akm feedback env/prod --positive
|
|
|
1602
1607
|
akm feedback skills/code-review --positive --reason "Worked perfectly for PR reviews"
|
|
1603
1608
|
akm feedback skills/code-review --negative --failure-mode outdated --reason "references a removed flag"
|
|
1604
1609
|
akm feedback skills/code-review --negative --reason "flaky" --tag slice:train --tag team:platform
|
|
1610
|
+
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/"
|
|
1605
1611
|
```
|
|
1606
1612
|
|
|
1607
1613
|
| Flag | Description |
|
|
1608
1614
|
| --- | --- |
|
|
1609
1615
|
| `--positive` | Record that an asset helped: it raises its ranking and does not trigger a rewrite |
|
|
1610
|
-
| `--negative` | Flag the asset
|
|
1611
|
-
| `--reason` | What is wrong with the asset's content and what should change; not for `akm` command errors. Attached to the feedback event
|
|
1616
|
+
| `--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`) |
|
|
1618
|
+
| `--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
|
+
| `--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 |
|
|
1612
1621
|
| `--failure-mode` | Structured failure-mode taxonomy for negative feedback: `incorrect`, `outdated`, `dangerous`, `incomplete`, `redundant`. Stored alongside `--reason` in event metadata for the distill pipeline. |
|
|
1613
1622
|
| `--tag` | Tag to attach to the feedback (repeatable, e.g. `--tag slice:train --tag team:platform`) |
|
|
1614
1623
|
| `--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. |
|
|
@@ -3097,8 +3106,8 @@ akm proposal drain --strategy default --promote -y # Read the triage block from
|
|
|
3097
3106
|
|
|
3098
3107
|
`akm feedback` accepts an optional `--reason <text>` flag whose value is
|
|
3099
3108
|
forwarded into feedback metadata and consumed by improve/distill proposal
|
|
3100
|
-
prompts. Negative feedback requires a reason by default:
|
|
3101
|
-
|
|
3109
|
+
prompts. Negative feedback requires a reason by default: say what is wrong and
|
|
3110
|
+
what should change.
|
|
3102
3111
|
|
|
3103
3112
|
Write the reason about the asset's content. Reflect treats it as an unverified
|
|
3104
3113
|
report to investigate, not a fact to insert, and is told to leave the section
|
|
@@ -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`, `failureMode`, `tags` |
|
|
162
|
+
| `feedback` | `akm feedback <ref>` | `signal` (positive/negative), `reason`, `failureMode`, `tags`, `fix` (`source` and the number of replacements, when an exact fix was attached) |
|
|
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) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akm-cli",
|
|
3
|
-
"version": "0.9.26-alpha.
|
|
3
|
+
"version": "0.9.26-alpha.2",
|
|
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": [
|