akm-opencode 0.9.6202608312025 → 0.9.8202609020939
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 -4
- package/index.ts +22 -8
- package/package.json +2 -2
- package/shared/akm-version.ts +13 -9
- package/shared/recall-policy.ts +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# akm-opencode
|
|
2
2
|
|
|
3
|
-
OpenCode plugin for [AKM](https://github.com/itlackey/akm) `^0.9.
|
|
3
|
+
OpenCode plugin for [AKM](https://github.com/itlackey/akm) `^0.9.8`. It exposes exactly five public tools and uses lifecycle hooks to bring relevant AKM context into a session.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -18,13 +18,13 @@ Add the plugin to `opencode.json`:
|
|
|
18
18
|
| --- | --- |
|
|
19
19
|
| `akm_search` | Search configured bundles or registries. `source` accepts `local`, `registry`, `all`, or a configured bundle name. |
|
|
20
20
|
| `akm_show` | Show a concept by `[bundle//]conceptId[#fragment]`. A fragment selects a Markdown section. |
|
|
21
|
-
| `akm_curate` | Return ranked concepts for a task or topic. |
|
|
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. |
|
|
24
24
|
|
|
25
25
|
`akm_search`, `akm_show`, and `akm_curate` call the bundled AKM read APIs in process. `akm_feedback` and `akm_remember` use the compatible AKM CLI because they mutate AKM state. Failures return structured results and are logged through OpenCode app logging.
|
|
26
26
|
|
|
27
|
-
Use the `ref` returned by search or curate directly with show or feedback. Concept IDs look like `skills/code-review`, `memories/release-retro`, or `team-playbook//knowledge/deploy#Rollback`.
|
|
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
29
|
## Lifecycle Hooks
|
|
30
30
|
|
|
@@ -113,7 +113,7 @@ These three are read once, when the plugin module is imported, so they must be s
|
|
|
113
113
|
|
|
114
114
|
## Usage
|
|
115
115
|
|
|
116
|
-
1. Start with `akm_curate` for task-oriented discovery.
|
|
116
|
+
1. Start with `akm_curate` for task-oriented discovery; set `pack` when you want the selected local content immediately.
|
|
117
117
|
2. Use `akm_search` when you know the concept name and need its exact ID.
|
|
118
118
|
3. Fetch the full concept with `akm_show` before relying on it.
|
|
119
119
|
4. Record the outcome with `akm_feedback`.
|
package/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Plugin, tool } from "@opencode-ai/plugin"
|
|
2
2
|
// @ts-expect-error akm-cli does not publish declarations for this in-process entrypoint.
|
|
3
|
-
import { akmCurate } from "akm-cli/dist/commands/read/curate.js"
|
|
3
|
+
import { akmCurate, packCuratedHits } from "akm-cli/dist/commands/read/curate.js"
|
|
4
4
|
// @ts-expect-error akm-cli does not publish declarations for this in-process entrypoint.
|
|
5
5
|
import { akmSearch } from "akm-cli/dist/commands/read/search.js"
|
|
6
6
|
// @ts-expect-error akm-cli does not publish declarations for this in-process entrypoint.
|
|
@@ -48,7 +48,7 @@ const AKM_REQUIRED_VERSION_RANGE = AKM_VERSION_RANGE
|
|
|
48
48
|
// The consent banner's package specification is kept explicit so it remains a
|
|
49
49
|
// valid npm install target even if the shared compatibility range later grows
|
|
50
50
|
// extra clauses. Keep it in sync with the minimum supported stable release.
|
|
51
|
-
const AKM_RECOMMENDED_INSTALL_REF = "akm-cli@^0.9.
|
|
51
|
+
const AKM_RECOMMENDED_INSTALL_REF = "akm-cli@^0.9.8"
|
|
52
52
|
|
|
53
53
|
const AKM_AUTO_FEEDBACK = (process.env.AKM_AUTO_FEEDBACK ?? "1") !== "0"
|
|
54
54
|
const AKM_AUTO_CURATE = (process.env.AKM_AUTO_CURATE ?? "1") !== "0"
|
|
@@ -194,7 +194,7 @@ const akmVersionProbeCache = new Map<string, string | null>()
|
|
|
194
194
|
// whitespace-token extractor and is the single source of truth here.
|
|
195
195
|
const PROPOSED_QUALITY_WARNING = "Do not treat proposed assets as curated until accepted."
|
|
196
196
|
const AKM_WORKFLOW_INSTRUCTION = [
|
|
197
|
-
"# AKM workflow (v0.9.
|
|
197
|
+
"# AKM workflow (v0.9.7)",
|
|
198
198
|
"",
|
|
199
199
|
"Use AKM as a reusable knowledge and workflow bundle.",
|
|
200
200
|
"",
|
|
@@ -820,7 +820,7 @@ async function getPendingProposalCount(client: LogCapableClient, sessionID?: str
|
|
|
820
820
|
const command = resolveAkmCommand()
|
|
821
821
|
if (typeof command === "object" && "ok" in command) return { count: 0, unsupported: true }
|
|
822
822
|
try {
|
|
823
|
-
// AKM 0.9.
|
|
823
|
+
// AKM 0.9.7 canonical proposal-queue listing path: `akm proposal list`.
|
|
824
824
|
const stdout = execResolvedAkm(command, ["proposal", "list", "--status", "pending", "--format", "json"], {
|
|
825
825
|
encoding: "utf8",
|
|
826
826
|
timeout: AKM_PENDING_PROPOSAL_TIMEOUT_MS,
|
|
@@ -2662,11 +2662,24 @@ async function runInProcess(
|
|
|
2662
2662
|
meta: CliLogMeta,
|
|
2663
2663
|
): Promise<string> {
|
|
2664
2664
|
try {
|
|
2665
|
+
if (
|
|
2666
|
+
operation === "curate"
|
|
2667
|
+
&& input.pack !== undefined
|
|
2668
|
+
&& (typeof input.pack !== "number" || !Number.isInteger(input.pack) || input.pack <= 0)
|
|
2669
|
+
) {
|
|
2670
|
+
throw new Error("pack must be a positive integer token budget")
|
|
2671
|
+
}
|
|
2665
2672
|
const result = operation === "search"
|
|
2666
2673
|
? await akmSearch(input as Parameters<typeof akmSearch>[0])
|
|
2667
2674
|
: operation === "show"
|
|
2668
2675
|
? await akmShowUnified(input as Parameters<typeof akmShowUnified>[0])
|
|
2669
|
-
: await
|
|
2676
|
+
: await (async () => {
|
|
2677
|
+
const { pack, ...curateInput } = input
|
|
2678
|
+
const curated = await akmCurate(curateInput as Parameters<typeof akmCurate>[0])
|
|
2679
|
+
return typeof pack === "number"
|
|
2680
|
+
? packCuratedHits(curated, pack)
|
|
2681
|
+
: curated
|
|
2682
|
+
})()
|
|
2670
2683
|
const output = JSON.stringify(result)
|
|
2671
2684
|
const refs = extractAkmRefsFromString(output)
|
|
2672
2685
|
noteRecentRefs(meta.sessionID, refs)
|
|
@@ -3580,18 +3593,19 @@ const akmPlugin: Plugin = async ({ client, worktree, directory }) => {
|
|
|
3580
3593
|
// zero akm_* calls while curation was demonstrably available (#95).
|
|
3581
3594
|
// Leading with the decision — when to reach for this instead of just
|
|
3582
3595
|
// reading the file — is what it has to win on.
|
|
3583
|
-
description: "Reach for this 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 — including a file already present in the workspace, since having read a file does not mean you know its schema. PRIMARY discovery entry point for the bundle: describe the task in natural language and this returns the top matches as a ranked list.
|
|
3596
|
+
description: "Reach for this 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 — including a file already present in the workspace, since having read a file does not mean you know its schema. PRIMARY discovery entry point for the bundle: describe the task in natural language and this returns the top matches as a ranked list. Set pack to a token budget when you need the selected local assets' full content in one response; otherwise pass a hit's ref to akm_show before relying on it. Record akm_feedback once the result is known.",
|
|
3584
3597
|
args: {
|
|
3585
3598
|
query: tool.schema.string().describe("Task, topic, or natural-language description of what you want to do."),
|
|
3586
3599
|
type: tool.schema.enum(ASSET_TYPES as unknown as [string, ...string[]]).optional().describe("Optional asset type filter."),
|
|
3587
3600
|
limit: tool.schema.number().optional().describe("Maximum number of curated matches to return. Defaults to 4."),
|
|
3588
3601
|
source: tool.schema.string().optional().describe("Search source: 'local', 'registry', 'all', or a configured bundle name."),
|
|
3602
|
+
pack: tool.schema.number().optional().describe("Optional positive token budget for packing ranked local assets' full content into this response. Registry hits are never packed."),
|
|
3589
3603
|
},
|
|
3590
|
-
async execute({ query, type, limit, source }, context) {
|
|
3604
|
+
async execute({ query, type, limit, source, pack }, context) {
|
|
3591
3605
|
return runInProcess(
|
|
3592
3606
|
client as unknown as LogCapableClient,
|
|
3593
3607
|
"curate",
|
|
3594
|
-
{ query, type: type === "any" ? undefined : type, limit, source },
|
|
3608
|
+
{ query, type: type === "any" ? undefined : type, limit, source, pack },
|
|
3595
3609
|
{ toolName: "akm_curate", sessionID: context.sessionID, directory: context.directory },
|
|
3596
3610
|
)
|
|
3597
3611
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akm-opencode",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.8202609020939",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenCode plugin for AKM 0.9 with in-process search, show, and curate tools plus feedback and remember.",
|
|
6
6
|
"keywords": [
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@opencode-ai/plugin": "^1.18.14",
|
|
42
|
-
"akm-cli": "^0.9.
|
|
42
|
+
"akm-cli": "^0.9.8"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/node": "^26.1.1",
|
package/shared/akm-version.ts
CHANGED
|
@@ -13,27 +13,31 @@
|
|
|
13
13
|
// same code path runs on both sides.
|
|
14
14
|
//
|
|
15
15
|
// A single caret clause anchored at the stable release covers the whole
|
|
16
|
-
// supported line: `^0.9.
|
|
17
|
-
// 0.9.
|
|
18
|
-
//
|
|
19
|
-
// these plugins
|
|
16
|
+
// supported line: `^0.9.8` admits stable 0.9.8 and later 0.9.x releases.
|
|
17
|
+
// 0.9.8 is the compatibility floor, and the reason is a one-way one: 0.9.8
|
|
18
|
+
// adds state migrations 025 and 026, so once any 0.9.8 command opens
|
|
19
|
+
// `state.db` an older CLI refuses to open it at all. A home these plugins
|
|
20
|
+
// have driven is therefore a 0.9.8+ home, and admitting 0.9.7 would point
|
|
21
|
+
// the gate at a CLI that cannot read it. Its ref grammar, progressive-search
|
|
22
|
+
// metadata, token-budgeted curate packing, workflow lifecycle, and
|
|
23
|
+
// task/workflow wire contracts are the ones these plugins implement.
|
|
20
24
|
//
|
|
21
|
-
// KNOWN GAP: NO prerelease satisfies this range — not 0.9.
|
|
22
|
-
// *future* line such as 0.9.
|
|
25
|
+
// KNOWN GAP: NO prerelease satisfies this range — not 0.9.8-rc.1, and not a
|
|
26
|
+
// *future* line such as 0.9.9-rc.1. That is node-semver's documented behavior
|
|
23
27
|
// and the vendored matcher reproduces it: a prerelease only satisfies a range
|
|
24
28
|
// whose lower bound is a prerelease with the same major.minor.patch. Admitting
|
|
25
29
|
// a prerelease line again is an explicit one-clause edit here
|
|
26
|
-
// (`^0.9.
|
|
30
|
+
// (`^0.9.8 || ^0.9.9-rc.1`) when such a build actually needs testing — a
|
|
27
31
|
// deliberate opt-in rather than a range that silently accepts untested
|
|
28
32
|
// prereleases.
|
|
29
33
|
//
|
|
30
34
|
// Earlier 0.9 releases are deliberately excluded as well: accepting them
|
|
31
35
|
// would silently pass the version gate onto a CLI with retired ref and
|
|
32
|
-
// workflow contracts.
|
|
36
|
+
// workflow contracts, or one that cannot open a migrated state.db.
|
|
33
37
|
|
|
34
38
|
import { satisfies } from "./vendor-semver"
|
|
35
39
|
|
|
36
|
-
export const AKM_VERSION_RANGE = "^0.9.
|
|
40
|
+
export const AKM_VERSION_RANGE = "^0.9.8"
|
|
37
41
|
|
|
38
42
|
/**
|
|
39
43
|
* True when `version` is a valid semver string that satisfies
|
package/shared/recall-policy.ts
CHANGED
|
@@ -36,7 +36,7 @@ export function shouldRecall(prompt: string, options?: { activeWorkflow?: boolea
|
|
|
36
36
|
return { shouldRecall: false, reason: "skip-chitchat", query: text, scopeHints }
|
|
37
37
|
}
|
|
38
38
|
// Reuse the resolver-facing parser rather than carrying a second, stale
|
|
39
|
-
// grammar here. AKM 0.9.
|
|
39
|
+
// grammar here. AKM 0.9.7 refs are [bundle//]conceptId[#fragment]; retired
|
|
40
40
|
// type:name strings must not turn an otherwise low-signal prompt into an
|
|
41
41
|
// explicit AKM recall.
|
|
42
42
|
if (/\bakm\b|\bbundle\b/.test(lower) || extractAkmRefsFromString(text).length > 0) {
|