akm-opencode 0.9.2202608290901 → 0.9.7202608312104
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 +23 -8
- package/package.json +2 -2
- package/shared/akm-version.ts +8 -7
- 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.7`. 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.7"
|
|
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)
|
|
@@ -2749,6 +2762,7 @@ type SearchHit = {
|
|
|
2749
2762
|
description?: string
|
|
2750
2763
|
score?: number
|
|
2751
2764
|
whyMatched?: string[]
|
|
2765
|
+
matchStage?: "exact" | "prefix" | "relaxed"
|
|
2752
2766
|
run?: string
|
|
2753
2767
|
origin?: string | null
|
|
2754
2768
|
size?: string
|
|
@@ -3579,18 +3593,19 @@ const akmPlugin: Plugin = async ({ client, worktree, directory }) => {
|
|
|
3579
3593
|
// zero akm_* calls while curation was demonstrably available (#95).
|
|
3580
3594
|
// Leading with the decision — when to reach for this instead of just
|
|
3581
3595
|
// reading the file — is what it has to win on.
|
|
3582
|
-
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.",
|
|
3583
3597
|
args: {
|
|
3584
3598
|
query: tool.schema.string().describe("Task, topic, or natural-language description of what you want to do."),
|
|
3585
3599
|
type: tool.schema.enum(ASSET_TYPES as unknown as [string, ...string[]]).optional().describe("Optional asset type filter."),
|
|
3586
3600
|
limit: tool.schema.number().optional().describe("Maximum number of curated matches to return. Defaults to 4."),
|
|
3587
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."),
|
|
3588
3603
|
},
|
|
3589
|
-
async execute({ query, type, limit, source }, context) {
|
|
3604
|
+
async execute({ query, type, limit, source, pack }, context) {
|
|
3590
3605
|
return runInProcess(
|
|
3591
3606
|
client as unknown as LogCapableClient,
|
|
3592
3607
|
"curate",
|
|
3593
|
-
{ query, type: type === "any" ? undefined : type, limit, source },
|
|
3608
|
+
{ query, type: type === "any" ? undefined : type, limit, source, pack },
|
|
3594
3609
|
{ toolName: "akm_curate", sessionID: context.sessionID, directory: context.directory },
|
|
3595
3610
|
)
|
|
3596
3611
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akm-opencode",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.7202608312104",
|
|
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.7"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/node": "^26.1.1",
|
package/shared/akm-version.ts
CHANGED
|
@@ -13,16 +13,17 @@
|
|
|
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
|
-
//
|
|
16
|
+
// supported line: `^0.9.7` admits stable 0.9.7 and later 0.9.x releases.
|
|
17
|
+
// 0.9.7 is the compatibility floor: its ref grammar, progressive-search
|
|
18
|
+
// metadata, token-budgeted curate packing, workflow lifecycle, and task/workflow
|
|
19
|
+
// wire contracts are the ones these plugins implement.
|
|
19
20
|
//
|
|
20
|
-
// KNOWN GAP: NO prerelease satisfies this range — not 0.9.
|
|
21
|
-
// *future* line such as 0.9.
|
|
21
|
+
// KNOWN GAP: NO prerelease satisfies this range — not 0.9.7-rc.1, and not a
|
|
22
|
+
// *future* line such as 0.9.8-rc.1. That is node-semver's documented behavior
|
|
22
23
|
// and the vendored matcher reproduces it: a prerelease only satisfies a range
|
|
23
24
|
// whose lower bound is a prerelease with the same major.minor.patch. Admitting
|
|
24
25
|
// a prerelease line again is an explicit one-clause edit here
|
|
25
|
-
// (`^0.9.
|
|
26
|
+
// (`^0.9.7 || ^0.9.8-rc.1`) when such a build actually needs testing — a
|
|
26
27
|
// deliberate opt-in rather than a range that silently accepts untested
|
|
27
28
|
// prereleases.
|
|
28
29
|
//
|
|
@@ -32,7 +33,7 @@
|
|
|
32
33
|
|
|
33
34
|
import { satisfies } from "./vendor-semver"
|
|
34
35
|
|
|
35
|
-
export const AKM_VERSION_RANGE = "^0.9.
|
|
36
|
+
export const AKM_VERSION_RANGE = "^0.9.7"
|
|
36
37
|
|
|
37
38
|
/**
|
|
38
39
|
* 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) {
|