akm-opencode 0.9.21202610030351 → 0.9.21202610040615
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/README.md +4 -10
- package/dist/index.js +11 -73
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ Add the plugin to `opencode.json`:
|
|
|
17
17
|
| Tool | Description |
|
|
18
18
|
| --- | --- |
|
|
19
19
|
| `akm_search` | Search configured bundles or registries. `source` accepts `local`, `registry`, `all`, or a configured bundle name. |
|
|
20
|
-
| `akm_show` | Show a concept by `[bundle//]conceptId[#fragment]`.
|
|
20
|
+
| `akm_show` | Show a concept by `[bundle//]conceptId[#fragment]`. |
|
|
21
21
|
| `akm_curate` | Return ranked concepts for a task or topic. Optional `pack` is a token budget that returns the selected local assets' full content in one response; registry hits are omitted from packed items. |
|
|
22
22
|
| `akm_feedback` | Record positive or negative feedback for a concept. |
|
|
23
23
|
| `akm_remember` | Save durable knowledge as a searchable memory. |
|
|
@@ -26,13 +26,7 @@ Add the plugin to `opencode.json`:
|
|
|
26
26
|
|
|
27
27
|
Use the `ref` returned by search or curate directly with show or feedback. When `akm_curate.pack` is set, the response already includes packed local content, so a separate show call is only needed for omitted or registry-only hits. Concept IDs look like `skills/code-review`, `memories/release-retro`, or `team-playbook//knowledge/deploy#Rollback`.
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
preserves the existing selected-fragment behavior. For an opaque fragment ref
|
|
31
|
-
returned by search, lead context puts the indexed-safe first fragment before
|
|
32
|
-
an explicitly labelled selected match and uses a 3,200-character default hard
|
|
33
|
-
bound. Friendly authored heading selectors retain their existing source-live
|
|
34
|
-
behavior. Set either `max_tokens` (estimated at four characters per token) or
|
|
35
|
-
`max_chars`, never both. Search and show
|
|
29
|
+
Search and show
|
|
36
30
|
responses preserve canonical `ref` while exposing `selectedRef`, `parentRef`,
|
|
37
31
|
fragment ordinal/count and lines, previous/next refs, separate fragment/parent
|
|
38
32
|
estimates, and context truncation metadata.
|
|
@@ -56,7 +50,7 @@ The plugin subscribes to OpenCode lifecycle events. Hook failures are logged thr
|
|
|
56
50
|
|
|
57
51
|
The session observation buffer that retrospective feedback reads from survives every non-terminal event: it is bounded by `AKM_SESSION_BUFFER_MAX_ENTRIES` and dropped only on `session.deleted`. Discarding it at `session.idle` would empty it between turns, so "thanks, that worked" would credit nothing in exactly the sessions that used the most assets.
|
|
58
52
|
|
|
59
|
-
Automatic feedback skips references that AKM reports as ineligible, and no `akm_*` tool call submits feedback on its own. A *successful* `akm_show` / `akm_search` / `akm_curate` only inspected a concept, which is not evidence that it helped, so on OpenCode the positive signal comes from a retrospective confirmation instead. A *failed* call (an error result, a timeout, an ambiguous ref) is not feedback on the asset either, and is only logged: its ref is not remembered as used, so a later "thanks, that worked" does not credit it and a later correction does not blame it. Same rules as the Claude plugin, which applies them to the `akm` subcommand of a Bash invocation.
|
|
53
|
+
Automatic feedback skips references that AKM reports as ineligible, and no `akm_*` tool call submits feedback on its own. A *successful* `akm_show` / `akm_search` / `akm_curate` only inspected a concept, which is not evidence that it helped, so on OpenCode the positive signal comes from a retrospective confirmation instead. A *failed* call (an error result, a timeout, an ambiguous ref) is not feedback on the asset either, and is only logged, as a warning that names its ref and the error: its ref is not remembered as used, so a later "thanks, that worked" does not credit it and a later correction does not blame it. Same rules as the Claude plugin, which applies them to the `akm` subcommand of a Bash invocation.
|
|
60
54
|
|
|
61
55
|
### Automatic learning proposals
|
|
62
56
|
|
|
@@ -137,7 +131,7 @@ These three are read once, when the plugin module is imported, so they must be s
|
|
|
137
131
|
|
|
138
132
|
1. Start with `akm_curate` for task-oriented discovery; set `pack` when you want the selected local content immediately.
|
|
139
133
|
2. Use `akm_search` when you know the concept name and need its exact ID.
|
|
140
|
-
3. Fetch a concept with `akm_show` before relying on it
|
|
134
|
+
3. Fetch a concept with `akm_show` before relying on it.
|
|
141
135
|
4. Record whether the concept helped with `akm_feedback`; a failed akm call is not feedback on the asset.
|
|
142
136
|
5. Use `akm_remember` for durable knowledge that should be available to future sessions.
|
|
143
137
|
|
package/dist/index.js
CHANGED
|
@@ -1163,54 +1163,6 @@ function shouldRecall(prompt, options) {
|
|
|
1163
1163
|
return { shouldRecall: false, reason: "skip-low-signal", query: text, scopeHints };
|
|
1164
1164
|
}
|
|
1165
1165
|
|
|
1166
|
-
// fragment-context.ts
|
|
1167
|
-
function positiveSafeInteger(value, field) {
|
|
1168
|
-
if (value === undefined)
|
|
1169
|
-
return;
|
|
1170
|
-
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
|
|
1171
|
-
throw new Error(`${field} must be a positive integer`);
|
|
1172
|
-
}
|
|
1173
|
-
return value;
|
|
1174
|
-
}
|
|
1175
|
-
function supportsFragmentLeadContext(version) {
|
|
1176
|
-
const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version.trim());
|
|
1177
|
-
if (!match)
|
|
1178
|
-
return false;
|
|
1179
|
-
const major = Number(match[1]);
|
|
1180
|
-
const minor = Number(match[2]);
|
|
1181
|
-
const patch = Number(match[3]);
|
|
1182
|
-
return major > 0 || minor > 9 || minor === 9 && patch >= 15;
|
|
1183
|
-
}
|
|
1184
|
-
function normalizeFragmentShowInput(input, loadedAkmApiVersion) {
|
|
1185
|
-
const {
|
|
1186
|
-
context,
|
|
1187
|
-
max_tokens: rawMaxTokens,
|
|
1188
|
-
max_chars: rawMaxChars,
|
|
1189
|
-
...showInput
|
|
1190
|
-
} = input;
|
|
1191
|
-
const maxTokens = positiveSafeInteger(rawMaxTokens, "max_tokens");
|
|
1192
|
-
const maxChars = positiveSafeInteger(rawMaxChars, "max_chars");
|
|
1193
|
-
if (maxTokens !== undefined && maxChars !== undefined) {
|
|
1194
|
-
throw new Error("max_tokens and max_chars are mutually exclusive");
|
|
1195
|
-
}
|
|
1196
|
-
const contextMode = context;
|
|
1197
|
-
if (contextMode === "lead" && !supportsFragmentLeadContext(loadedAkmApiVersion)) {
|
|
1198
|
-
throw new Error(`context='lead' requires akm-cli >=0.9.15; this plugin bundles ${loadedAkmApiVersion}. ` + "Install the plugin release whose exact akm-cli pin is 0.9.15 or newer.");
|
|
1199
|
-
}
|
|
1200
|
-
const maxContextChars = maxChars ?? (maxTokens !== undefined ? maxTokens * 4 : undefined);
|
|
1201
|
-
if (maxContextChars !== undefined && !Number.isSafeInteger(maxContextChars)) {
|
|
1202
|
-
throw new Error("fragment context budget is too large");
|
|
1203
|
-
}
|
|
1204
|
-
if (maxContextChars !== undefined && contextMode !== "lead") {
|
|
1205
|
-
throw new Error("max_tokens and max_chars require context='lead'");
|
|
1206
|
-
}
|
|
1207
|
-
return {
|
|
1208
|
-
...showInput,
|
|
1209
|
-
...contextMode !== undefined ? { contextMode } : {},
|
|
1210
|
-
...maxContextChars !== undefined ? { maxContextChars } : {}
|
|
1211
|
-
};
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
1166
|
// index.ts
|
|
1215
1167
|
var moduleDir = path4.dirname(fileURLToPath(import.meta.url));
|
|
1216
1168
|
var AKM_AUTO_FEEDBACK = (process.env.AKM_AUTO_FEEDBACK ?? "1") !== "0";
|
|
@@ -1790,11 +1742,10 @@ async function recordRetrospectiveFeedback(client, sessionID, text) {
|
|
|
1790
1742
|
}
|
|
1791
1743
|
retrospectiveState.set(sessionID, { recentRefs });
|
|
1792
1744
|
}
|
|
1793
|
-
function queueFeedback(client, ref,
|
|
1794
|
-
|
|
1795
|
-
if (dedupe?.has(dedupeKey))
|
|
1745
|
+
function queueFeedback(client, ref, note, meta, dedupe) {
|
|
1746
|
+
if (dedupe?.has(ref))
|
|
1796
1747
|
return true;
|
|
1797
|
-
dedupe?.add(
|
|
1748
|
+
dedupe?.add(ref);
|
|
1798
1749
|
const command = resolveAkmCommand();
|
|
1799
1750
|
if (typeof command === "object" && "ok" in command) {
|
|
1800
1751
|
writePluginLog(client, "warn", "AKM auto-feedback skipped", {
|
|
@@ -1803,7 +1754,6 @@ function queueFeedback(client, ref, sentiment, note, meta, dedupe) {
|
|
|
1803
1754
|
sessionID: meta.sessionID,
|
|
1804
1755
|
directory: meta.directory,
|
|
1805
1756
|
ref,
|
|
1806
|
-
sentiment,
|
|
1807
1757
|
error: command.error
|
|
1808
1758
|
});
|
|
1809
1759
|
return false;
|
|
@@ -1813,7 +1763,7 @@ function queueFeedback(client, ref, sentiment, note, meta, dedupe) {
|
|
|
1813
1763
|
...command.argsPrefix,
|
|
1814
1764
|
"feedback",
|
|
1815
1765
|
ref,
|
|
1816
|
-
|
|
1766
|
+
"--positive",
|
|
1817
1767
|
"--reason",
|
|
1818
1768
|
note,
|
|
1819
1769
|
"--format",
|
|
@@ -1831,7 +1781,6 @@ function queueFeedback(client, ref, sentiment, note, meta, dedupe) {
|
|
|
1831
1781
|
sessionID: meta.sessionID,
|
|
1832
1782
|
directory: meta.directory,
|
|
1833
1783
|
ref,
|
|
1834
|
-
sentiment,
|
|
1835
1784
|
error: signal ? `akm feedback exited via signal ${signal}` : `akm feedback exited with code ${code}`
|
|
1836
1785
|
});
|
|
1837
1786
|
}
|
|
@@ -1843,7 +1792,6 @@ function queueFeedback(client, ref, sentiment, note, meta, dedupe) {
|
|
|
1843
1792
|
sessionID: meta.sessionID,
|
|
1844
1793
|
directory: meta.directory,
|
|
1845
1794
|
ref,
|
|
1846
|
-
sentiment,
|
|
1847
1795
|
error: formatCliError(error)
|
|
1848
1796
|
});
|
|
1849
1797
|
});
|
|
@@ -1856,7 +1804,6 @@ function queueFeedback(client, ref, sentiment, note, meta, dedupe) {
|
|
|
1856
1804
|
sessionID: meta.sessionID,
|
|
1857
1805
|
directory: meta.directory,
|
|
1858
1806
|
ref,
|
|
1859
|
-
sentiment,
|
|
1860
1807
|
error: formatCliError(error)
|
|
1861
1808
|
});
|
|
1862
1809
|
return false;
|
|
@@ -2898,8 +2845,7 @@ async function runInProcess(client, operation, input, meta) {
|
|
|
2898
2845
|
if (operation === "curate" && input.pack !== undefined && (typeof input.pack !== "number" || !Number.isInteger(input.pack) || input.pack <= 0)) {
|
|
2899
2846
|
throw new Error("pack must be a positive integer token budget");
|
|
2900
2847
|
}
|
|
2901
|
-
const
|
|
2902
|
-
const result = operation === "search" ? await akmSearch(normalizedInput) : operation === "show" ? await akmShowUnified(normalizedInput) : await (async () => {
|
|
2848
|
+
const result = operation === "search" ? await akmSearch(input) : operation === "show" ? await akmShowUnified(input) : await (async () => {
|
|
2903
2849
|
const { pack, ...curateInput } = input;
|
|
2904
2850
|
const curated = await akmCurate(curateInput);
|
|
2905
2851
|
return typeof pack === "number" ? packCuratedHits(curated, pack) : curated;
|
|
@@ -3262,7 +3208,7 @@ ${hint}` : hint;
|
|
|
3262
3208
|
});
|
|
3263
3209
|
if (!shouldSubmitAutomaticFeedback(signal))
|
|
3264
3210
|
continue;
|
|
3265
|
-
queueFeedback(logClient, ref,
|
|
3211
|
+
queueFeedback(logClient, ref, signal.note, {
|
|
3266
3212
|
toolName: "chat.message",
|
|
3267
3213
|
sessionID: input.sessionID,
|
|
3268
3214
|
agent: input.agent
|
|
@@ -3347,6 +3293,7 @@ ${hint}` : hint;
|
|
|
3347
3293
|
sessionID: input.sessionID,
|
|
3348
3294
|
callID: input.callID,
|
|
3349
3295
|
title: output.title,
|
|
3296
|
+
refs: allRefs,
|
|
3350
3297
|
error: typeof parsed.error === "string" ? parsed.error : undefined
|
|
3351
3298
|
});
|
|
3352
3299
|
}
|
|
@@ -3417,22 +3364,13 @@ ${hint}` : hint;
|
|
|
3417
3364
|
}
|
|
3418
3365
|
}),
|
|
3419
3366
|
akm_show: tool({
|
|
3420
|
-
description: "Show an AKM asset by [bundle//]conceptId[#fragment].
|
|
3367
|
+
description: "Show an AKM asset by [bundle//]conceptId[#fragment]. Read an asset this way before relying on it, then record akm_feedback once you know whether it helped.",
|
|
3421
3368
|
args: {
|
|
3422
3369
|
ref: tool.schema.string().describe("Asset ref returned by akm_curate or akm_search, optionally with a #fragment \u2014 e.g. `skills/code-review` or `local//knowledge/deploy#Rollback`."),
|
|
3423
|
-
detail: tool.schema.enum(["brief", "summary", "normal", "full"]).optional().describe("Response detail level. Defaults to 'normal'.")
|
|
3424
|
-
context: tool.schema.enum(["exact", "lead"]).optional().describe("Fragment context mode. Defaults to exact. For an opaque search fragment, use lead to prepend indexed-safe lead context while keeping the selected match last."),
|
|
3425
|
-
max_tokens: tool.schema.number().optional().describe("Positive token budget for context='lead', estimated as four characters per token. Mutually exclusive with max_chars."),
|
|
3426
|
-
max_chars: tool.schema.number().optional().describe("Positive character budget for context='lead'. Mutually exclusive with max_tokens. Lead defaults to 3200 characters.")
|
|
3370
|
+
detail: tool.schema.enum(["brief", "summary", "normal", "full"]).optional().describe("Response detail level. Defaults to 'normal'.")
|
|
3427
3371
|
},
|
|
3428
|
-
async execute({ ref, detail
|
|
3429
|
-
return runInProcess(client, "show", {
|
|
3430
|
-
ref,
|
|
3431
|
-
detail,
|
|
3432
|
-
context,
|
|
3433
|
-
max_tokens,
|
|
3434
|
-
max_chars
|
|
3435
|
-
}, { toolName: "akm_show", sessionID: toolContext.sessionID, directory: toolContext.directory });
|
|
3372
|
+
async execute({ ref, detail }, toolContext) {
|
|
3373
|
+
return runInProcess(client, "show", { ref, detail }, { toolName: "akm_show", sessionID: toolContext.sessionID, directory: toolContext.directory });
|
|
3436
3374
|
}
|
|
3437
3375
|
}),
|
|
3438
3376
|
akm_remember: tool({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akm-opencode",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.21202610040615",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenCode plugin for AKM with discovery tools, feedback, memory, and automatic learning proposals from corrections and recurring workflows.",
|
|
6
6
|
"keywords": [
|