akm-cli 0.7.4 → 0.8.0-rc.10
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 +224 -1
- package/README.md +22 -6
- package/SECURITY.md +93 -0
- package/dist/cli/config-migrate.js +144 -0
- package/dist/cli/config-validate.js +39 -0
- package/dist/cli/confirm.js +73 -0
- package/dist/cli/parse-args.js +133 -0
- package/dist/cli/shared.js +129 -0
- package/dist/cli.js +2631 -1440
- package/dist/commands/add-cli.js +279 -0
- package/dist/commands/agent-dispatch.js +110 -0
- package/dist/commands/agent-support.js +68 -0
- package/dist/commands/completions.js +3 -0
- package/dist/commands/config-cli.js +130 -534
- package/dist/commands/consolidate.js +2122 -0
- package/dist/commands/curate.js +45 -3
- package/dist/commands/db-cli.js +23 -0
- package/dist/commands/distill-promotion-policy.js +660 -0
- package/dist/commands/distill.js +1081 -73
- package/dist/commands/env.js +213 -0
- package/dist/commands/eval-cases.js +43 -0
- package/dist/commands/events.js +15 -24
- package/dist/commands/extract-cli.js +127 -0
- package/dist/commands/extract-prompt.js +204 -0
- package/dist/commands/extract.js +477 -0
- package/dist/commands/feedback-cli.js +331 -0
- package/dist/commands/graph.js +477 -0
- package/dist/commands/health.js +1302 -0
- package/dist/commands/help/help-accept.md +12 -0
- package/dist/commands/help/help-improve.md +69 -0
- package/dist/commands/help/help-proposals.md +18 -0
- package/dist/commands/help/help-propose.md +17 -0
- package/dist/commands/help/help-reject.md +11 -0
- package/dist/commands/history.js +54 -46
- package/dist/commands/improve-auto-accept.js +97 -0
- package/dist/commands/improve-cli.js +217 -0
- package/dist/commands/improve-profiles.js +166 -0
- package/dist/commands/improve-result-file.js +167 -0
- package/dist/commands/improve.js +2373 -0
- package/dist/commands/info.js +5 -2
- package/dist/commands/init.js +50 -2
- package/dist/commands/installed-stashes.js +102 -139
- package/dist/commands/knowledge.js +136 -0
- package/dist/commands/lint/agent-linter.js +49 -0
- package/dist/commands/lint/base-linter.js +479 -0
- package/dist/commands/lint/command-linter.js +49 -0
- package/dist/commands/lint/default-linter.js +16 -0
- package/dist/commands/lint/env-key-rules.js +154 -0
- package/dist/commands/lint/index.js +196 -0
- package/dist/commands/lint/knowledge-linter.js +16 -0
- package/dist/commands/lint/markdown-insertion.js +343 -0
- package/dist/commands/lint/memory-linter.js +61 -0
- package/dist/commands/lint/registry.js +36 -0
- package/dist/commands/lint/skill-linter.js +45 -0
- package/dist/commands/lint/task-linter.js +50 -0
- package/dist/commands/lint/types.js +4 -0
- package/dist/commands/lint/workflow-linter.js +56 -0
- package/dist/commands/lint.js +4 -0
- package/dist/commands/migration-help.js +3 -0
- package/dist/commands/proposal.js +67 -12
- package/dist/commands/propose.js +120 -45
- package/dist/commands/reflect.js +1104 -60
- package/dist/commands/registry-cli.js +150 -0
- package/dist/commands/registry-search.js +5 -2
- package/dist/commands/remember-cli.js +257 -0
- package/dist/commands/remember.js +70 -7
- package/dist/commands/schema-repair.js +203 -0
- package/dist/commands/search.js +115 -14
- package/dist/commands/secret.js +173 -0
- package/dist/commands/self-update.js +3 -0
- package/dist/commands/show.js +158 -60
- package/dist/commands/source-add.js +17 -45
- package/dist/commands/source-clone.js +3 -0
- package/dist/commands/source-manage.js +14 -19
- package/dist/commands/tasks.js +437 -0
- package/dist/commands/url-checker.js +42 -0
- package/dist/core/action-contributors.js +28 -0
- package/dist/core/asset-ref.js +17 -2
- package/dist/core/asset-registry.js +12 -17
- package/dist/core/asset-serialize.js +88 -0
- package/dist/core/asset-spec.js +67 -1
- package/dist/core/common.js +182 -0
- package/dist/core/concurrent.js +25 -0
- package/dist/core/config-io.js +347 -0
- package/dist/core/config-migration.js +622 -0
- package/dist/core/config-schema.js +534 -0
- package/dist/core/config-sources.js +108 -0
- package/dist/core/config-types.js +4 -0
- package/dist/core/config-walker.js +337 -0
- package/dist/core/config.js +364 -968
- package/dist/core/errors.js +42 -20
- package/dist/core/events.js +105 -135
- package/dist/core/file-lock.js +104 -0
- package/dist/core/frontmatter.js +75 -8
- package/dist/core/lesson-lint.js +3 -0
- package/dist/core/markdown.js +20 -0
- package/dist/core/memory-belief.js +62 -0
- package/dist/core/memory-contradiction-detect.js +274 -0
- package/dist/core/memory-improve.js +806 -0
- package/dist/core/parse.js +158 -0
- package/dist/core/paths.js +280 -14
- package/dist/core/proposal-quality-validators.js +380 -0
- package/dist/core/proposal-validators.js +69 -0
- package/dist/core/proposals.js +512 -42
- package/dist/core/state-db.js +1068 -0
- package/dist/core/text-truncation.js +107 -0
- package/dist/core/time.js +54 -0
- package/dist/core/tty.js +59 -0
- package/dist/core/warn.js +64 -1
- package/dist/core/write-source.js +3 -0
- package/dist/indexer/db-backup.js +391 -0
- package/dist/indexer/db-search.js +198 -489
- package/dist/indexer/db.js +990 -108
- package/dist/indexer/ensure-index.js +136 -0
- package/dist/indexer/file-context.js +3 -0
- package/dist/indexer/graph-boost.js +376 -101
- package/dist/indexer/graph-db.js +391 -0
- package/dist/indexer/graph-dedup.js +95 -0
- package/dist/indexer/graph-extraction.js +550 -114
- package/dist/indexer/index-context.js +4 -0
- package/dist/indexer/indexer.js +547 -309
- package/dist/indexer/llm-cache.js +52 -0
- package/dist/indexer/manifest.js +3 -0
- package/dist/indexer/matchers.js +167 -160
- package/dist/indexer/memory-inference.js +152 -74
- package/dist/indexer/metadata-contributors.js +29 -0
- package/dist/indexer/metadata.js +275 -196
- package/dist/indexer/path-resolver.js +92 -0
- package/dist/indexer/project-context.js +192 -0
- package/dist/indexer/ranking-contributors.js +331 -0
- package/dist/indexer/ranking.js +81 -0
- package/dist/indexer/search-fields.js +5 -9
- package/dist/indexer/search-hit-enrichers.js +111 -0
- package/dist/indexer/search-source.js +44 -10
- package/dist/indexer/semantic-status.js +6 -17
- package/dist/indexer/staleness-detect.js +447 -0
- package/dist/indexer/usage-events.js +12 -9
- package/dist/indexer/walker.js +28 -0
- package/dist/integrations/agent/builders.js +135 -0
- package/dist/integrations/agent/config.js +122 -230
- package/dist/integrations/agent/detect.js +3 -0
- package/dist/integrations/agent/index.js +7 -13
- package/dist/integrations/agent/model-aliases.js +55 -0
- package/dist/integrations/agent/profiles.js +70 -5
- package/dist/integrations/agent/prompts.js +250 -36
- package/dist/integrations/agent/runner.js +151 -0
- package/dist/integrations/agent/sdk-runner.js +126 -0
- package/dist/integrations/agent/spawn.js +183 -35
- package/dist/integrations/github.js +3 -0
- package/dist/integrations/lockfile.js +32 -69
- package/dist/integrations/session-logs/index.js +69 -0
- package/dist/integrations/session-logs/inline-refs.js +35 -0
- package/dist/integrations/session-logs/pre-filter.js +152 -0
- package/dist/integrations/session-logs/providers/claude-code.js +282 -0
- package/dist/integrations/session-logs/providers/opencode.js +258 -0
- package/dist/integrations/session-logs/types.js +4 -0
- package/dist/llm/call-ai.js +62 -0
- package/dist/llm/client.js +79 -88
- package/dist/llm/embedder.js +20 -29
- package/dist/llm/embedders/cache.js +3 -7
- package/dist/llm/embedders/local.js +42 -1
- package/dist/llm/embedders/remote.js +20 -8
- package/dist/llm/embedders/types.js +3 -7
- package/dist/llm/feature-gate.js +95 -48
- package/dist/llm/graph-extract.js +676 -72
- package/dist/llm/index-passes.js +44 -29
- package/dist/llm/memory-infer.js +80 -71
- package/dist/llm/metadata-enhance.js +42 -29
- package/dist/llm/prompts/extract-session.md +80 -0
- package/dist/llm/prompts/graph-extract-user-prompt.md +35 -0
- package/dist/output/cli-hints-full.md +292 -0
- package/dist/output/cli-hints-short.md +66 -0
- package/dist/output/cli-hints.js +7 -311
- package/dist/output/context.js +60 -8
- package/dist/output/renderers.js +306 -258
- package/dist/output/shapes/curate.js +56 -0
- package/dist/output/shapes/distill.js +10 -0
- package/dist/output/shapes/env-list.js +19 -0
- package/dist/output/shapes/events.js +11 -0
- package/dist/output/shapes/helpers.js +424 -0
- package/dist/output/shapes/history.js +7 -0
- package/dist/output/shapes/passthrough.js +102 -0
- package/dist/output/shapes/proposal-accept.js +7 -0
- package/dist/output/shapes/proposal-diff.js +7 -0
- package/dist/output/shapes/proposal-list.js +7 -0
- package/dist/output/shapes/proposal-producer.js +11 -0
- package/dist/output/shapes/proposal-reject.js +7 -0
- package/dist/output/shapes/proposal-show.js +7 -0
- package/dist/output/shapes/registry-search.js +6 -0
- package/dist/output/shapes/registry.js +30 -0
- package/dist/output/shapes/search.js +6 -0
- package/dist/output/shapes/secret-list.js +19 -0
- package/dist/output/shapes/show.js +6 -0
- package/dist/output/shapes/vault-list.js +19 -0
- package/dist/output/shapes.js +51 -511
- package/dist/output/text/add.js +6 -0
- package/dist/output/text/clone.js +6 -0
- package/dist/output/text/config.js +6 -0
- package/dist/output/text/curate.js +6 -0
- package/dist/output/text/distill.js +7 -0
- package/dist/output/text/enable-disable.js +7 -0
- package/dist/output/text/events.js +10 -0
- package/dist/output/text/feedback.js +6 -0
- package/dist/output/text/helpers.js +1039 -0
- package/dist/output/text/history.js +7 -0
- package/dist/output/text/import.js +6 -0
- package/dist/output/text/index.js +6 -0
- package/dist/output/text/info.js +6 -0
- package/dist/output/text/init.js +6 -0
- package/dist/output/text/list.js +6 -0
- package/dist/output/text/proposal-producer.js +8 -0
- package/dist/output/text/proposal.js +11 -0
- package/dist/output/text/registry-commands.js +11 -0
- package/dist/output/text/registry.js +30 -0
- package/dist/output/text/remember.js +6 -0
- package/dist/output/text/remove.js +6 -0
- package/dist/output/text/save.js +6 -0
- package/dist/output/text/search.js +6 -0
- package/dist/output/text/show.js +6 -0
- package/dist/output/text/update.js +6 -0
- package/dist/output/text/upgrade.js +6 -0
- package/dist/output/text/vault.js +16 -0
- package/dist/output/text/wiki.js +15 -0
- package/dist/output/text/workflow.js +14 -0
- package/dist/output/text.js +44 -1093
- package/dist/registry/build-index.js +3 -0
- package/dist/registry/create-provider-registry.js +3 -0
- package/dist/registry/factory.js +4 -1
- package/dist/registry/origin-resolve.js +3 -0
- package/dist/registry/providers/index.js +3 -0
- package/dist/registry/providers/skills-sh.js +71 -50
- package/dist/registry/providers/static-index.js +53 -48
- package/dist/registry/providers/types.js +3 -24
- package/dist/registry/resolve.js +11 -16
- package/dist/registry/types.js +3 -0
- package/dist/scripts/migrate-storage.js +17750 -0
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9031 -0
- package/dist/scripts/migrations/v16-to-v17.js +141 -0
- package/dist/setup/detect.js +3 -0
- package/dist/setup/ripgrep-install.js +3 -0
- package/dist/setup/ripgrep-resolve.js +3 -0
- package/dist/setup/setup.js +775 -37
- package/dist/setup/steps.js +3 -15
- package/dist/sources/include.js +3 -0
- package/dist/sources/provider-factory.js +5 -12
- package/dist/sources/provider.js +3 -20
- package/dist/sources/providers/filesystem.js +19 -23
- package/dist/sources/providers/git.js +179 -20
- package/dist/sources/providers/index.js +3 -0
- package/dist/sources/providers/install-types.js +3 -13
- package/dist/sources/providers/npm.js +3 -4
- package/dist/sources/providers/provider-utils.js +3 -0
- package/dist/sources/providers/sync-from-ref.js +3 -11
- package/dist/sources/providers/tar-utils.js +3 -0
- package/dist/sources/providers/website.js +18 -22
- package/dist/sources/resolve.js +3 -0
- package/dist/sources/types.js +3 -0
- package/dist/sources/website-ingest.js +7 -0
- package/dist/tasks/backends/cron.js +203 -0
- package/dist/tasks/backends/exec-utils.js +28 -0
- package/dist/tasks/backends/index.js +24 -0
- package/dist/tasks/backends/launchd-template.xml +19 -0
- package/dist/tasks/backends/launchd.js +187 -0
- package/dist/tasks/backends/schtasks-template.xml +29 -0
- package/dist/tasks/backends/schtasks.js +215 -0
- package/dist/tasks/parser.js +211 -0
- package/dist/tasks/resolveAkmBin.js +87 -0
- package/dist/tasks/runner.js +458 -0
- package/dist/tasks/schedule.js +227 -0
- package/dist/tasks/schema.js +15 -0
- package/dist/tasks/validator.js +62 -0
- package/dist/version.js +3 -0
- package/dist/wiki/index-template.md +12 -0
- package/dist/wiki/ingest-workflow-template.md +54 -0
- package/dist/wiki/log-template.md +8 -0
- package/dist/wiki/schema-template.md +61 -0
- package/dist/wiki/wiki-templates.js +15 -0
- package/dist/wiki/wiki.js +13 -61
- package/dist/workflows/authoring.js +8 -25
- package/dist/workflows/cli.js +3 -0
- package/dist/workflows/db.js +141 -2
- package/dist/workflows/document-cache.js +3 -10
- package/dist/workflows/parser.js +3 -0
- package/dist/workflows/renderer.js +11 -3
- package/dist/workflows/runs.js +91 -89
- package/dist/workflows/schema.js +3 -0
- package/dist/workflows/scope-key.js +79 -0
- package/dist/workflows/validator.js +4 -8
- package/dist/workflows/workflow-template.md +24 -0
- package/docs/README.md +10 -2
- package/docs/data-and-telemetry.md +225 -0
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.7.4.md +1 -1
- package/docs/migration/release-notes/0.7.5.md +20 -0
- package/docs/migration/release-notes/0.8.0.md +48 -0
- package/docs/migration/v0.7-to-v0.8.md +1307 -0
- package/package.json +29 -11
- package/dist/commands/install-audit.js +0 -381
- package/dist/commands/vault.js +0 -333
- package/dist/templates/wiki-templates.js +0 -100
|
@@ -0,0 +1,331 @@
|
|
|
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
|
+
import fs from "node:fs";
|
|
5
|
+
import { defineCommand } from "citty";
|
|
6
|
+
import { output, parseAllFlagValues, runWithJsonErrors } from "../cli/shared";
|
|
7
|
+
import { parseAssetRef } from "../core/asset-ref";
|
|
8
|
+
import { assembleAsset } from "../core/asset-serialize";
|
|
9
|
+
import { writeFileAtomic } from "../core/common";
|
|
10
|
+
import { FEEDBACK_FAILURE_MODES, loadConfig } from "../core/config";
|
|
11
|
+
import { UsageError } from "../core/errors";
|
|
12
|
+
import { appendEvent } from "../core/events";
|
|
13
|
+
import { parseFrontmatter, parseFrontmatterBlock } from "../core/frontmatter";
|
|
14
|
+
import { warn } from "../core/warn";
|
|
15
|
+
import { applyFeedbackToUtilityScore, closeDatabase, findEntryIdByRef, openExistingDatabase } from "../indexer/db";
|
|
16
|
+
import { ensureIndex } from "../indexer/ensure-index";
|
|
17
|
+
import { resolveSourceEntries } from "../indexer/search-source";
|
|
18
|
+
import { insertUsageEvent } from "../indexer/usage-events";
|
|
19
|
+
// ── Tag validation ────────────────────────────────────────────────────────────
|
|
20
|
+
const TAG_KEY_RE = /^[a-z_][a-z0-9_]*$/;
|
|
21
|
+
const MAX_FEEDBACK_TAGS = 10;
|
|
22
|
+
function validateFeedbackTags(raw) {
|
|
23
|
+
const seen = new Set();
|
|
24
|
+
const out = [];
|
|
25
|
+
for (const tag of raw) {
|
|
26
|
+
const parts = tag.split(":");
|
|
27
|
+
if (parts.length < 2 || parts[0] === "" || parts.slice(1).join("") === "") {
|
|
28
|
+
throw new UsageError(`Invalid tag "${tag}". Tags must be in key:value format where key matches [a-z_][a-z0-9_]* and value is non-empty.`, "INVALID_FLAG_VALUE");
|
|
29
|
+
}
|
|
30
|
+
const key = parts[0];
|
|
31
|
+
if (!TAG_KEY_RE.test(key)) {
|
|
32
|
+
throw new UsageError(`Invalid tag key "${key}" in "${tag}". Key must match [a-z_][a-z0-9_]*.`, "INVALID_FLAG_VALUE");
|
|
33
|
+
}
|
|
34
|
+
if (seen.has(tag))
|
|
35
|
+
continue;
|
|
36
|
+
seen.add(tag);
|
|
37
|
+
out.push(tag);
|
|
38
|
+
}
|
|
39
|
+
if (out.length > MAX_FEEDBACK_TAGS) {
|
|
40
|
+
throw new UsageError(`Too many tags: ${out.length}. Maximum is ${MAX_FEEDBACK_TAGS}.`, "INVALID_FLAG_VALUE");
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
// ── Lesson strength helper ────────────────────────────────────────────────────
|
|
45
|
+
/**
|
|
46
|
+
* Phase 7A: append a feedback ref to a lesson's `lessonStrength[]`
|
|
47
|
+
* frontmatter array. Returns `{ strength }` (post-update count) on success,
|
|
48
|
+
* or `null` when the lesson cannot be located. Idempotent: if the ref is
|
|
49
|
+
* already credited, no write occurs.
|
|
50
|
+
*
|
|
51
|
+
* The function looks up the lesson's file via the indexer DB so the write
|
|
52
|
+
* targets the canonical on-disk location. Frontmatter is rewritten in
|
|
53
|
+
* place (no asset-spec round-trip) because we're modifying a single key on
|
|
54
|
+
* an existing asset — the same pattern memory-inference uses for
|
|
55
|
+
* `inferenceProcessed`.
|
|
56
|
+
*/
|
|
57
|
+
function appendLessonStrength(type, name, feedbackRef) {
|
|
58
|
+
const ref = `${type}:${name}`;
|
|
59
|
+
let filePath;
|
|
60
|
+
const db = openExistingDatabase();
|
|
61
|
+
try {
|
|
62
|
+
const entryId = findEntryIdByRef(db, ref);
|
|
63
|
+
if (entryId === undefined) {
|
|
64
|
+
warn(`[feedback] --applied-to: lesson ${ref} is not in the index.`);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const row = db.prepare("SELECT file_path FROM entries WHERE id = ?").get(entryId);
|
|
68
|
+
if (!row?.file_path) {
|
|
69
|
+
warn(`[feedback] --applied-to: cannot resolve file path for ${ref}.`);
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
filePath = row.file_path;
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
closeDatabase(db);
|
|
76
|
+
}
|
|
77
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
78
|
+
warn(`[feedback] --applied-to: lesson file missing on disk for ${ref}.`);
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
82
|
+
const parsed = parseFrontmatter(raw);
|
|
83
|
+
const data = { ...parsed.data };
|
|
84
|
+
const existing = data.lessonStrength;
|
|
85
|
+
const strengthList = Array.isArray(existing)
|
|
86
|
+
? existing.filter((x) => typeof x === "string" && x.trim().length > 0).map((x) => x.trim())
|
|
87
|
+
: typeof existing === "string" && existing.trim().length > 0
|
|
88
|
+
? [existing.trim()]
|
|
89
|
+
: [];
|
|
90
|
+
if (strengthList.includes(feedbackRef)) {
|
|
91
|
+
// Already credited — idempotent no-op.
|
|
92
|
+
return { strength: strengthList.length };
|
|
93
|
+
}
|
|
94
|
+
strengthList.push(feedbackRef);
|
|
95
|
+
data.lessonStrength = strengthList;
|
|
96
|
+
const block = parseFrontmatterBlock(raw);
|
|
97
|
+
const body = block?.content ?? raw;
|
|
98
|
+
const next = assembleAsset(data, body);
|
|
99
|
+
try {
|
|
100
|
+
// Preserve the existing file's permission bits (markdown assets are
|
|
101
|
+
// typically 0o644); writeFileAtomic defaults to 0o600 otherwise.
|
|
102
|
+
const mode = fs.statSync(filePath).mode & 0o777;
|
|
103
|
+
writeFileAtomic(filePath, next, mode);
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
warn(`[feedback] --applied-to: failed to write ${filePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
return { strength: strengthList.length };
|
|
110
|
+
}
|
|
111
|
+
// ── Command definition ────────────────────────────────────────────────────────
|
|
112
|
+
export const feedbackCommand = defineCommand({
|
|
113
|
+
meta: {
|
|
114
|
+
name: "feedback",
|
|
115
|
+
description: "Record positive or negative feedback for any indexed stash asset.\n\n" +
|
|
116
|
+
"Positive feedback boosts an asset's EMA utility score, making it rank higher\n" +
|
|
117
|
+
"in future searches without requiring a full reindex.\n\n" +
|
|
118
|
+
"Negative feedback records a negative signal in usage_events and state.db events.\n" +
|
|
119
|
+
"It does NOT immediately lower the asset's ranking — the EMA utility score is\n" +
|
|
120
|
+
"updated the next time `akm index` runs (incremental or full). Run `akm index`\n" +
|
|
121
|
+
"after recording negative feedback to have it reflected in search results.",
|
|
122
|
+
},
|
|
123
|
+
args: {
|
|
124
|
+
// Optional in citty so run() is invoked even when omitted; we re-validate
|
|
125
|
+
// and throw a structured UsageError below so exit code is 2 (USAGE) rather
|
|
126
|
+
// than citty's default 0 (help banner).
|
|
127
|
+
ref: { type: "positional", description: "Asset ref (type:name)", required: false },
|
|
128
|
+
positive: { type: "boolean", description: "Record positive feedback (boosts ranking immediately)", default: false },
|
|
129
|
+
negative: {
|
|
130
|
+
type: "boolean",
|
|
131
|
+
description: "Record negative feedback (suppresses ranking after next `akm index`). " +
|
|
132
|
+
"Reindexing is required for the signal to affect search results.",
|
|
133
|
+
default: false,
|
|
134
|
+
},
|
|
135
|
+
reason: {
|
|
136
|
+
type: "string",
|
|
137
|
+
description: "Reason for the feedback (required for negative feedback by default; used by distillation)",
|
|
138
|
+
},
|
|
139
|
+
note: { type: "string", description: "Alias for --reason (backward-compatible, prefer --reason)" },
|
|
140
|
+
"failure-mode": {
|
|
141
|
+
type: "string",
|
|
142
|
+
description: `Structured failure-mode taxonomy for negative feedback (F-3 / #384). ` +
|
|
143
|
+
`Accepted values: ${FEEDBACK_FAILURE_MODES.join(", ")}. ` +
|
|
144
|
+
"Stored alongside --reason in event metadata for aggregation by the distill pipeline.",
|
|
145
|
+
},
|
|
146
|
+
tag: {
|
|
147
|
+
type: "string",
|
|
148
|
+
description: "Tag to attach to the feedback (repeatable, e.g. --tag slice:train --tag team:platform)",
|
|
149
|
+
},
|
|
150
|
+
"applied-to": {
|
|
151
|
+
type: "string",
|
|
152
|
+
description: "Credit a lesson that helped resolve this task. Accepts a `lesson:<name>` ref. " +
|
|
153
|
+
"When combined with --positive, appends this feedback ref to the target lesson's " +
|
|
154
|
+
"`lessonStrength[]` frontmatter array (dedup, idempotent). Ignored on non-lesson targets.",
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
run({ args }) {
|
|
158
|
+
return runWithJsonErrors(async () => {
|
|
159
|
+
const ref = (args.ref ?? "").trim();
|
|
160
|
+
if (!ref) {
|
|
161
|
+
throw new UsageError("Asset ref is required. Usage: akm feedback <ref> --positive|--negative", "MISSING_REQUIRED_ARGUMENT", "Pass a ref like `skill:deploy` and either --positive or --negative.");
|
|
162
|
+
}
|
|
163
|
+
parseAssetRef(ref);
|
|
164
|
+
if (args.positive && args.negative) {
|
|
165
|
+
throw new UsageError("Specify either --positive or --negative, not both.");
|
|
166
|
+
}
|
|
167
|
+
if (!args.positive && !args.negative) {
|
|
168
|
+
throw new UsageError("Specify --positive or --negative.");
|
|
169
|
+
}
|
|
170
|
+
const signal = args.positive ? "positive" : "negative";
|
|
171
|
+
// `--note` is a deprecated back-compat alias for `--reason` (removed in
|
|
172
|
+
// 0.9.0). Warn on stderr when it is used as the sole source (i.e. without
|
|
173
|
+
// an explicit `--reason`). Warnings go to stderr only so JSON stdout
|
|
174
|
+
// consumers are unaffected.
|
|
175
|
+
if (args.note !== undefined && args.reason === undefined) {
|
|
176
|
+
warn("warning: '--note' is deprecated for 'akm feedback'; use '--reason'. Removed in 0.9.0.");
|
|
177
|
+
}
|
|
178
|
+
const reason = args.reason ?? args.note;
|
|
179
|
+
// F-3 / #384: Validate --failure-mode against the curated enum.
|
|
180
|
+
const failureMode = args["failure-mode"]?.trim() || undefined;
|
|
181
|
+
if (failureMode) {
|
|
182
|
+
if (args.positive) {
|
|
183
|
+
throw new UsageError("--failure-mode is only valid for negative feedback.", "INVALID_FLAG_VALUE", "Remove --failure-mode or switch to --negative.");
|
|
184
|
+
}
|
|
185
|
+
const cfg = loadConfig();
|
|
186
|
+
const allowedModes = cfg.feedback?.allowedFailureModes ?? FEEDBACK_FAILURE_MODES;
|
|
187
|
+
if (allowedModes.length > 0 && !allowedModes.includes(failureMode)) {
|
|
188
|
+
throw new UsageError(`Invalid --failure-mode "${failureMode}". Accepted values: ${allowedModes.join(", ")}.`, "INVALID_FLAG_VALUE", `Use one of: ${allowedModes.join(", ")}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (args.negative === true && !reason?.trim()) {
|
|
192
|
+
// F-3 / #384: Default requireReason is now true. Load config to allow
|
|
193
|
+
// operators to opt out via feedback.requireReason: false in akm.json.
|
|
194
|
+
const cfg = loadConfig();
|
|
195
|
+
const requireReason = cfg.feedback?.requireReason ?? true; // Default: true (F-3 / #384)
|
|
196
|
+
if (requireReason) {
|
|
197
|
+
throw new UsageError("Negative feedback requires --reason (structured failure signals are needed for distillation). " +
|
|
198
|
+
"Use --failure-mode for a curated taxonomy or --reason for free text. " +
|
|
199
|
+
"Set feedback.requireReason: false in akm.json to downgrade to a warning.", "MISSING_REQUIRED_ARGUMENT", `Hint: akm feedback ${ref} --negative --reason "..." [--failure-mode incorrect|outdated|dangerous|incomplete|redundant]`);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
warn("Warning: negative feedback without --reason provides less distillation signal.");
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const rawTags = parseAllFlagValues("--tag");
|
|
206
|
+
const validatedTags = validateFeedbackTags(rawTags);
|
|
207
|
+
const metadataObj = {
|
|
208
|
+
signal,
|
|
209
|
+
...(reason?.trim() ? { reason: reason.trim() } : {}),
|
|
210
|
+
...(failureMode ? { failureMode } : {}),
|
|
211
|
+
...(validatedTags.length > 0 ? { tags: validatedTags } : {}),
|
|
212
|
+
};
|
|
213
|
+
const metadataStr = Object.keys(metadataObj).length > 1 ? JSON.stringify(metadataObj) : undefined;
|
|
214
|
+
// Auto-index when stale so the index is current before recording feedback.
|
|
215
|
+
const sources = resolveSourceEntries();
|
|
216
|
+
if (sources.length > 0) {
|
|
217
|
+
await ensureIndex(sources[0].path);
|
|
218
|
+
}
|
|
219
|
+
let utilityResult;
|
|
220
|
+
const db = openExistingDatabase();
|
|
221
|
+
try {
|
|
222
|
+
const entryId = findEntryIdByRef(db, ref);
|
|
223
|
+
if (entryId === undefined) {
|
|
224
|
+
throw new UsageError(`Ref "${ref}" is not in the index. ` +
|
|
225
|
+
"Run 'akm search' to verify the asset exists, then 'akm index' if it was recently added.");
|
|
226
|
+
}
|
|
227
|
+
// Persist the feedback signal into usage_events. For positive signals,
|
|
228
|
+
// the EMA utility score is updated immediately on the next read path.
|
|
229
|
+
// For negative signals, the score is adjusted the next time `akm index`
|
|
230
|
+
// runs — the signal is durable in the DB but does NOT suppress ranking
|
|
231
|
+
// in search results until after reindexing.
|
|
232
|
+
insertUsageEvent(db, {
|
|
233
|
+
event_type: "feedback",
|
|
234
|
+
entry_ref: ref,
|
|
235
|
+
entry_id: entryId,
|
|
236
|
+
signal,
|
|
237
|
+
metadata: metadataStr,
|
|
238
|
+
});
|
|
239
|
+
// Apply feedback-derived utility score adjustment immediately so that
|
|
240
|
+
// positive/negative signals influence search ranking without requiring
|
|
241
|
+
// a full reindex. We query the total accumulated feedback counts from
|
|
242
|
+
// usage_events so the delta reflects the entire signal history.
|
|
243
|
+
// Uses MemRL bounded-step EMA (F-5 / #386, arXiv:2601.03192).
|
|
244
|
+
try {
|
|
245
|
+
const counts = db
|
|
246
|
+
.prepare(`SELECT
|
|
247
|
+
SUM(CASE WHEN signal = 'positive' THEN 1 ELSE 0 END) AS pos,
|
|
248
|
+
SUM(CASE WHEN signal = 'negative' THEN 1 ELSE 0 END) AS neg
|
|
249
|
+
FROM usage_events
|
|
250
|
+
WHERE event_type = 'feedback' AND entry_id = ?`)
|
|
251
|
+
.get(entryId);
|
|
252
|
+
const pos = counts?.pos ?? 0;
|
|
253
|
+
const neg = counts?.neg ?? 0;
|
|
254
|
+
utilityResult = applyFeedbackToUtilityScore(db, entryId, pos, neg);
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
// best-effort — feedback recording succeeds even if utility update fails
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
finally {
|
|
261
|
+
closeDatabase(db);
|
|
262
|
+
}
|
|
263
|
+
appendEvent({
|
|
264
|
+
eventType: "feedback",
|
|
265
|
+
ref,
|
|
266
|
+
metadata: metadataObj,
|
|
267
|
+
});
|
|
268
|
+
// F-5 / #386: When a high-utility asset crosses below the review threshold,
|
|
269
|
+
// auto-create a review-needed escalation proposal so a human can confirm
|
|
270
|
+
// whether the negative feedback is valid before the asset falls out of
|
|
271
|
+
// the improve loop. Best-effort — failure is logged but does not fail the
|
|
272
|
+
// feedback command.
|
|
273
|
+
// Emit a structured event rather than a proposal so the review-needed
|
|
274
|
+
// signal is queryable via `akm events list --type improve_review_needed`
|
|
275
|
+
// without risking accidental asset overwrite if the proposal is accepted.
|
|
276
|
+
if (utilityResult?.crossedReviewThreshold) {
|
|
277
|
+
try {
|
|
278
|
+
appendEvent({
|
|
279
|
+
eventType: "improve_review_needed",
|
|
280
|
+
ref,
|
|
281
|
+
metadata: {
|
|
282
|
+
previousUtility: utilityResult.previousUtility,
|
|
283
|
+
nextUtility: utilityResult.nextUtility,
|
|
284
|
+
reason: reason?.trim() ?? null,
|
|
285
|
+
failureMode: failureMode ?? null,
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
catch (escalationErr) {
|
|
290
|
+
warn(`[feedback] Could not emit review-needed event for ${ref}: ${escalationErr instanceof Error ? escalationErr.message : String(escalationErr)}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// Phase 7A / Advantage D4b: --applied-to credits a lesson. When the
|
|
294
|
+
// target is a `lesson:<name>` ref and the signal is positive, append
|
|
295
|
+
// the feedback ref to the target lesson's `lessonStrength[]`
|
|
296
|
+
// frontmatter array (dedup, idempotent). Non-lesson targets are
|
|
297
|
+
// ignored. Failures here are warnings — feedback recording is the
|
|
298
|
+
// primary contract and must not regress on lesson-write errors.
|
|
299
|
+
const appliedToRaw = args["applied-to"]?.trim();
|
|
300
|
+
let appliedToResult = null;
|
|
301
|
+
if (appliedToRaw && signal === "positive") {
|
|
302
|
+
try {
|
|
303
|
+
const parsedApplied = parseAssetRef(appliedToRaw);
|
|
304
|
+
if (parsedApplied.type === "lesson") {
|
|
305
|
+
const updated = appendLessonStrength(parsedApplied.type, parsedApplied.name, ref);
|
|
306
|
+
if (updated) {
|
|
307
|
+
appliedToResult = { lessonRef: appliedToRaw, strength: updated.strength };
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
catch (err) {
|
|
312
|
+
warn(`[feedback] --applied-to failed for ${appliedToRaw}: ${err instanceof Error ? err.message : String(err)}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
else if (appliedToRaw && signal !== "positive") {
|
|
316
|
+
warn("[feedback] --applied-to is ignored without --positive; lesson credit is only recorded on positive signals.");
|
|
317
|
+
}
|
|
318
|
+
output("feedback", {
|
|
319
|
+
ok: true,
|
|
320
|
+
ref,
|
|
321
|
+
signal,
|
|
322
|
+
reason: reason?.trim() ?? null,
|
|
323
|
+
failureMode: failureMode ?? null,
|
|
324
|
+
tags: validatedTags,
|
|
325
|
+
...(appliedToResult
|
|
326
|
+
? { appliedTo: { ref: appliedToResult.lessonRef, lessonStrength: appliedToResult.strength } }
|
|
327
|
+
: {}),
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
},
|
|
331
|
+
});
|