akm-opencode 0.9.21202610012143 → 0.9.21202610040152

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.
Files changed (3) hide show
  1. package/README.md +3 -10
  2. package/dist/index.js +9 -91
  3. 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]`. Reads are exact by default; for an opaque search fragment, `context: "lead"` adds bounded indexed-safe lead context. |
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
- For `akm_show`, `context` accepts `exact` or `lead`. Exact is the default and
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.
@@ -85,7 +79,6 @@ Every kill switch below is opt-out and reads the same way: only the literal `0`
85
79
  | `AKM_AUTO_CURATE` | `1` | Set to `0` to disable automatic prompt curation. |
86
80
  | `AKM_AUTO_FEEDBACK` | `1` | Set to `0` to disable automatic feedback: the retrospective ("that worked") confirmation. |
87
81
  | `AKM_AUTO_LEARNING` | `1` | Set to `0` to disable prompt-signal capture and automatic learning/skill proposal submission. Native session extraction is controlled separately by `AKM_AUTO_MEMORY`. |
88
- | `AKM_AUTO_HINTS` | `1` | Set to `0` to skip the per-session `akm hints` call. The missing-bundle warning is deliberately not gated on this: it explains why the bundle is empty in the first place. |
89
82
  | `AKM_AUTO_MEMORY` | `1` | Set to `0` to disable interval-gated native-session extraction through `akm proposal extract`. The Claude plugin honours the same variable for its `SessionEnd` extraction. |
90
83
  | `AKM_INDEX_ON_SESSION_END` | `1` | Set to `0` to skip the `akm index` refresh. It runs only on `session.deleted` — never on `session.idle`, which fires after every turn. |
91
84
  | `AKM_WRITE_GATE` | `observe` | The format-declaration write gate (#99). When a file the session **read** declares a format your bundle documents — an `apiVersion:` namespace, a `yaml-language-server` pragma, a `$schema` key, an XML root namespace — and that asset has not been opened this session, the FIRST `edit`/`write` to that file is blocked once and the model is told which ref to read. It ships in `observe`: everything runs and the would-fire count lands in the ledger, but nothing is blocked. Set `enforce` to block, or `off`/`0` to disable it entirely. An unrecognized value is a configuration error — it logs one error per process and the gate refuses to run rather than guessing a default. It never fires on a file this session created: the create is recorded when it happens — a `write` to a path this session has not read, or an `edit` with an empty `oldString` — and that path stays insulated for the rest of the session, so reading back the model's own output does not re-arm it (ledger reason `session-created`). It is inert on `apply_patch` (which carries no file path) — that case logs one warning per process rather than failing quietly. |
@@ -138,7 +131,7 @@ These three are read once, when the plugin module is imported, so they must be s
138
131
 
139
132
  1. Start with `akm_curate` for task-oriented discovery; set `pack` when you want the selected local content immediately.
140
133
  2. Use `akm_search` when you know the concept name and need its exact ID.
141
- 3. Fetch a concept with `akm_show` before relying on it; opt into bounded lead context only when a selected fragment needs it.
134
+ 3. Fetch a concept with `akm_show` before relying on it.
142
135
  4. Record whether the concept helped with `akm_feedback`; a failed akm call is not feedback on the asset.
143
136
  5. Use `akm_remember` for durable knowledge that should be available to future sessions.
144
137
 
package/dist/index.js CHANGED
@@ -1163,61 +1163,12 @@ 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";
1217
1169
  var AKM_AUTO_LEARNING = (process.env.AKM_AUTO_LEARNING ?? "1") !== "0";
1218
1170
  var AKM_AUTO_SKILL_PROPOSALS = (process.env.AKM_AUTO_SKILL_PROPOSALS ?? "1") !== "0";
1219
1171
  var AKM_AUTO_CURATE = (process.env.AKM_AUTO_CURATE ?? "1") !== "0";
1220
- var AKM_AUTO_HINTS = (process.env.AKM_AUTO_HINTS ?? "1") !== "0";
1221
1172
  var AKM_PENDING_PROPOSAL_TIMEOUT_MS = Math.max(500, (Number(process.env.AKM_PENDING_PROPOSAL_TIMEOUT ?? "2") || 2) * 1000);
1222
1173
  var AKM_CURATE_LIMIT = Math.max(1, Number(process.env.AKM_CURATE_LIMIT ?? "5") || 5);
1223
1174
  var AKM_CURATE_MIN_CHARS = Math.max(1, Number(process.env.AKM_CURATE_MIN_CHARS ?? "16") || 16);
@@ -1275,19 +1226,6 @@ var pendingProposalSummaryCache = new Map;
1275
1226
  var retrospectiveState = new Map;
1276
1227
  var cachedAkmBundleDir;
1277
1228
  var PROPOSED_QUALITY_WARNING = "Do not treat proposed assets as curated until accepted.";
1278
- var AKM_WORKFLOW_INSTRUCTION = [
1279
- "# AKM workflow (v0.9.21)",
1280
- "",
1281
- "Use AKM as a reusable knowledge and workflow bundle.",
1282
- "",
1283
- "Before writing or editing anything whose exact syntax or keys you are not certain of (a file already in the workspace included):",
1284
- "1. Use `akm_curate` with a query that includes the current project name/domain (primary discovery). Fall back to `akm_search` only when you already know an asset exists and need its exact ref.",
1285
- "2. Use `akm_show <ref>` before relying on an asset.",
1286
- "3. Record `akm_feedback` after the result is known.",
1287
- "4. Use `akm_remember` to preserve durable project knowledge.",
1288
- `5. ${PROPOSED_QUALITY_WARNING}`
1289
- ].join(`
1290
- `);
1291
1229
  function readPackageVersion() {
1292
1230
  try {
1293
1231
  const raw = readFileSync3(path4.join(moduleDir, "package.json"), "utf8");
@@ -2420,19 +2358,9 @@ function warnIfWriteGateInert(client) {
2420
2358
  var AKM_HINTS_PREFIX = [
2421
2359
  "# AKM is available in this session",
2422
2360
  "",
2423
- "You have an AKM bundle on this machine. Before writing **or editing** a config file, manifest, schema, or command for any tool, format, or API whose exact syntax or keys you are not certain of, call `akm_curate` with a task description to find relevant assets with LLM-reranked relevance scores. A file already being present in the workspace is not evidence that you know its schema \u2014 the values may be given to you while the key names and nesting are not, so check the bundle for that format's conventions before you edit it.",
2424
- "",
2425
- "**Choosing the right lookup command:**",
2426
- "",
2427
- "- **`akm_curate`** \u2014 use this when starting any new task, looking for patterns, docs, skills, or workflows. This is the PRIMARY lookup command. akm automatically boosts assets that match the current project (cwd-anchored project-context ranking), so an explicit project name in the query is not required for ranking \u2014 but it still helps the reranker frame intent.",
2428
- ' - Good: `akm_curate("akm CLI improve command performance analysis")` (explicit framing, still ideal)',
2429
- ' - Bad: `akm_curate("improve performance analysis")` (too generic \u2014 the reranker has less to work with even with auto-boost)',
2430
- '- **`akm_search` (known name)** \u2014 use ONLY when you already know an asset exists (e.g. after `akm_show` returned "not found") and need to locate its exact ref. Do not use as a discovery tool.',
2431
- "- **`akm_show <bundle>//meta`** \u2014 when working in or with an unfamiliar bundle, read its optional `.meta/` orientation (purpose, key assets, conventions, maintainer) before diving in. `akm_show meta` reads your working bundle's `.meta/index.md`; `akm_show meta:<name>` reads other `.meta/` docs (e.g. `meta:about`). These docs are direct-read and never appear in `akm_search`.",
2432
- "",
2433
- "When an asset's content is wrong, stale or incomplete, call `akm_feedback <ref> negative` with a note saying <what is wrong and what should change>: negative feedback is what triggers a review and fix of the asset, so make the note specific. Call it with `positive` when an asset materially helped (it only improves ranking). A failed akm call is not feedback on the asset. Use `akm_remember` to persist durable learnings so future sessions inherit them.",
2434
- "",
2435
- AKM_WORKFLOW_INSTRUCTION
2361
+ "- Assets for this project (skills, knowledge, memories, workflows) live in the AKM bundle. Before writing or editing a file whose format or keys you are not sure of, find them with `akm_curate` (by task) or `akm_search` (by name), and read one with `akm_show` before relying on it.",
2362
+ "- Record `akm_feedback` on an asset once you know whether it helped; keep durable project knowledge with `akm_remember`.",
2363
+ "- `akm hints` has this bundle's conventions; `akm help` has the CLI."
2436
2364
  ].join(`
2437
2365
  `);
2438
2366
  var AKM_CURATED_TAIL = "\n\nTip: call `akm_show <ref>` to fetch full content. If an asset is wrong, stale or incomplete, call `akm_feedback <ref> negative` with a note saying <what is wrong and what should change>: that queues it for review and a fix. Use `positive` when it helped.";
@@ -2922,8 +2850,7 @@ async function runInProcess(client, operation, input, meta) {
2922
2850
  if (operation === "curate" && input.pack !== undefined && (typeof input.pack !== "number" || !Number.isInteger(input.pack) || input.pack <= 0)) {
2923
2851
  throw new Error("pack must be a positive integer token budget");
2924
2852
  }
2925
- const normalizedInput = operation === "show" ? normalizeFragmentShowInput(input, BUNDLED_AKM_API_VERSION) : input;
2926
- const result = operation === "search" ? await akmSearch(normalizedInput) : operation === "show" ? await akmShowUnified(normalizedInput) : await (async () => {
2853
+ const result = operation === "search" ? await akmSearch(input) : operation === "show" ? await akmShowUnified(input) : await (async () => {
2927
2854
  const { pack, ...curateInput } = input;
2928
2855
  const curated = await akmCurate(curateInput);
2929
2856
  return typeof pack === "number" ? packCuratedHits(curated, pack) : curated;
@@ -3110,7 +3037,7 @@ var akmPlugin = async ({ client, worktree, directory }) => {
3110
3037
  }
3111
3038
  if (!sessionHints.has(sid)) {
3112
3039
  const bundleWarning = await getAkmBundleWarning(logClient);
3113
- const hints = AKM_AUTO_HINTS ? await runHintsForSession(logClient, sid) : null;
3040
+ const hints = await runHintsForSession(logClient, sid);
3114
3041
  const body = [bundleWarning, hints].filter(Boolean).join(`
3115
3042
 
3116
3043
  `);
@@ -3441,22 +3368,13 @@ ${hint}` : hint;
3441
3368
  }
3442
3369
  }),
3443
3370
  akm_show: tool({
3444
- description: "Show an AKM asset by [bundle//]conceptId[#fragment]. Reads stay exact by default. For an opaque fragment ref returned by search, context='lead' prepends bounded indexed-safe lead context and labels the selected match last; authored heading selectors retain their existing source-live behavior. Read an asset this way before relying on it, then record akm_feedback once you know whether it helped.",
3371
+ 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.",
3445
3372
  args: {
3446
3373
  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`."),
3447
- detail: tool.schema.enum(["brief", "summary", "normal", "full"]).optional().describe("Response detail level. Defaults to 'normal'."),
3448
- 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."),
3449
- max_tokens: tool.schema.number().optional().describe("Positive token budget for context='lead', estimated as four characters per token. Mutually exclusive with max_chars."),
3450
- max_chars: tool.schema.number().optional().describe("Positive character budget for context='lead'. Mutually exclusive with max_tokens. Lead defaults to 3200 characters.")
3374
+ detail: tool.schema.enum(["brief", "summary", "normal", "full"]).optional().describe("Response detail level. Defaults to 'normal'.")
3451
3375
  },
3452
- async execute({ ref, detail, context, max_tokens, max_chars }, toolContext) {
3453
- return runInProcess(client, "show", {
3454
- ref,
3455
- detail,
3456
- context,
3457
- max_tokens,
3458
- max_chars
3459
- }, { toolName: "akm_show", sessionID: toolContext.sessionID, directory: toolContext.directory });
3376
+ async execute({ ref, detail }, toolContext) {
3377
+ return runInProcess(client, "show", { ref, detail }, { toolName: "akm_show", sessionID: toolContext.sessionID, directory: toolContext.directory });
3460
3378
  }
3461
3379
  }),
3462
3380
  akm_remember: tool({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akm-opencode",
3
- "version": "0.9.21202610012143",
3
+ "version": "0.9.21202610040152",
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": [