akm-cli 0.7.4 → 0.8.0-rc.3

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 (162) hide show
  1. package/{CHANGELOG.md → .github/CHANGELOG.md} +34 -1
  2. package/.github/LICENSE +374 -0
  3. package/dist/cli/parse-args.js +86 -0
  4. package/dist/cli.js +1223 -650
  5. package/dist/commands/agent-dispatch.js +107 -0
  6. package/dist/commands/agent-support.js +62 -0
  7. package/dist/commands/config-cli.js +68 -84
  8. package/dist/commands/consolidate.js +812 -0
  9. package/dist/commands/curate.js +1 -0
  10. package/dist/commands/distill-promotion-policy.js +658 -0
  11. package/dist/commands/distill.js +224 -39
  12. package/dist/commands/eval-cases.js +40 -0
  13. package/dist/commands/events.js +12 -24
  14. package/dist/commands/graph.js +222 -0
  15. package/dist/commands/health.js +376 -0
  16. package/dist/commands/help/help-accept.md +9 -0
  17. package/dist/commands/help/help-improve.md +53 -0
  18. package/dist/commands/help/help-proposals.md +15 -0
  19. package/dist/commands/help/help-propose.md +17 -0
  20. package/dist/commands/help/help-reject.md +8 -0
  21. package/dist/commands/history.js +3 -30
  22. package/dist/commands/improve.js +1161 -0
  23. package/dist/commands/info.js +2 -2
  24. package/dist/commands/init.js +2 -2
  25. package/dist/commands/install-audit.js +5 -1
  26. package/dist/commands/installed-stashes.js +118 -138
  27. package/dist/commands/knowledge.js +133 -0
  28. package/dist/commands/lint/agent-linter.js +46 -0
  29. package/dist/commands/lint/base-linter.js +291 -0
  30. package/dist/commands/lint/command-linter.js +46 -0
  31. package/dist/commands/lint/default-linter.js +13 -0
  32. package/dist/commands/lint/index.js +145 -0
  33. package/dist/commands/lint/knowledge-linter.js +13 -0
  34. package/dist/commands/lint/memory-linter.js +58 -0
  35. package/dist/commands/lint/registry.js +33 -0
  36. package/dist/commands/lint/skill-linter.js +42 -0
  37. package/dist/commands/lint/task-linter.js +47 -0
  38. package/dist/commands/lint/types.js +1 -0
  39. package/dist/commands/lint/vault-key-rules.js +67 -0
  40. package/dist/commands/lint/workflow-linter.js +53 -0
  41. package/dist/commands/lint.js +1 -0
  42. package/dist/commands/migration-help.js +2 -2
  43. package/dist/commands/proposal.js +8 -7
  44. package/dist/commands/propose.js +106 -43
  45. package/dist/commands/reflect.js +167 -41
  46. package/dist/commands/registry-search.js +2 -2
  47. package/dist/commands/remember.js +55 -1
  48. package/dist/commands/schema-repair.js +130 -0
  49. package/dist/commands/search.js +21 -5
  50. package/dist/commands/show.js +135 -55
  51. package/dist/commands/source-add.js +10 -10
  52. package/dist/commands/source-manage.js +11 -19
  53. package/dist/commands/tasks.js +385 -0
  54. package/dist/commands/url-checker.js +39 -0
  55. package/dist/commands/vault.js +173 -87
  56. package/dist/core/action-contributors.js +25 -0
  57. package/dist/core/asset-ref.js +4 -0
  58. package/dist/core/asset-registry.js +5 -17
  59. package/dist/core/asset-spec.js +11 -1
  60. package/dist/core/common.js +100 -0
  61. package/dist/core/concurrent.js +22 -0
  62. package/dist/core/config.js +240 -127
  63. package/dist/core/events.js +87 -123
  64. package/dist/core/frontmatter.js +0 -6
  65. package/dist/core/markdown.js +17 -0
  66. package/dist/core/memory-improve.js +678 -0
  67. package/dist/core/parse.js +155 -0
  68. package/dist/core/paths.js +101 -3
  69. package/dist/core/proposal-validators.js +61 -0
  70. package/dist/core/proposals.js +49 -38
  71. package/dist/core/state-db.js +731 -0
  72. package/dist/core/time.js +51 -0
  73. package/dist/core/warn.js +59 -1
  74. package/dist/indexer/db-search.js +86 -472
  75. package/dist/indexer/db.js +418 -59
  76. package/dist/indexer/ensure-index.js +133 -0
  77. package/dist/indexer/graph-boost.js +247 -94
  78. package/dist/indexer/graph-db.js +201 -0
  79. package/dist/indexer/graph-dedup.js +99 -0
  80. package/dist/indexer/graph-extraction.js +417 -74
  81. package/dist/indexer/index-context.js +10 -0
  82. package/dist/indexer/indexer.js +480 -298
  83. package/dist/indexer/llm-cache.js +47 -0
  84. package/dist/indexer/matchers.js +124 -160
  85. package/dist/indexer/memory-inference.js +63 -29
  86. package/dist/indexer/metadata-contributors.js +26 -0
  87. package/dist/indexer/metadata.js +196 -197
  88. package/dist/indexer/path-resolver.js +89 -0
  89. package/dist/indexer/ranking-contributors.js +204 -0
  90. package/dist/indexer/ranking.js +74 -0
  91. package/dist/indexer/search-hit-enrichers.js +22 -0
  92. package/dist/indexer/search-source.js +24 -9
  93. package/dist/indexer/semantic-status.js +2 -16
  94. package/dist/indexer/walker.js +25 -0
  95. package/dist/integrations/agent/builders.js +109 -0
  96. package/dist/integrations/agent/config.js +203 -3
  97. package/dist/integrations/agent/index.js +5 -2
  98. package/dist/integrations/agent/model-aliases.js +63 -0
  99. package/dist/integrations/agent/profiles.js +67 -5
  100. package/dist/integrations/agent/prompts.js +114 -29
  101. package/dist/integrations/agent/sdk-runner.js +120 -0
  102. package/dist/integrations/agent/spawn.js +158 -34
  103. package/dist/integrations/lockfile.js +10 -18
  104. package/dist/integrations/session-logs/index.js +65 -0
  105. package/dist/integrations/session-logs/providers/claude-code.js +56 -0
  106. package/dist/integrations/session-logs/providers/opencode.js +52 -0
  107. package/dist/integrations/session-logs/types.js +1 -0
  108. package/dist/llm/call-ai.js +74 -0
  109. package/dist/llm/client.js +63 -86
  110. package/dist/llm/feature-gate.js +27 -16
  111. package/dist/llm/graph-extract.js +297 -64
  112. package/dist/llm/memory-infer.js +52 -71
  113. package/dist/llm/metadata-enhance.js +39 -22
  114. package/dist/llm/prompts/graph-extract-user-prompt.md +12 -0
  115. package/dist/output/cli-hints-full.md +277 -0
  116. package/dist/output/cli-hints-short.md +65 -0
  117. package/dist/output/cli-hints.js +2 -309
  118. package/dist/output/renderers.js +226 -257
  119. package/dist/output/shapes.js +109 -96
  120. package/dist/output/text.js +274 -36
  121. package/dist/registry/providers/skills-sh.js +61 -49
  122. package/dist/registry/providers/static-index.js +44 -48
  123. package/dist/registry/resolve.js +8 -16
  124. package/dist/setup/setup.js +510 -11
  125. package/dist/sources/provider-factory.js +2 -1
  126. package/dist/sources/providers/filesystem.js +16 -23
  127. package/dist/sources/providers/git.js +45 -4
  128. package/dist/sources/providers/website.js +15 -22
  129. package/dist/sources/website-ingest.js +4 -0
  130. package/dist/tasks/backends/cron.js +200 -0
  131. package/dist/tasks/backends/exec-utils.js +25 -0
  132. package/dist/tasks/backends/index.js +32 -0
  133. package/dist/tasks/backends/launchd-template.xml +19 -0
  134. package/dist/tasks/backends/launchd.js +184 -0
  135. package/dist/tasks/backends/schtasks-template.xml +29 -0
  136. package/dist/tasks/backends/schtasks.js +212 -0
  137. package/dist/tasks/parser.js +198 -0
  138. package/dist/tasks/resolveAkmBin.js +84 -0
  139. package/dist/tasks/runner.js +432 -0
  140. package/dist/tasks/schedule.js +208 -0
  141. package/dist/tasks/schema.js +13 -0
  142. package/dist/tasks/validator.js +59 -0
  143. package/dist/wiki/index-template.md +12 -0
  144. package/dist/wiki/ingest-workflow-template.md +54 -0
  145. package/dist/wiki/log-template.md +8 -0
  146. package/dist/wiki/schema-template.md +61 -0
  147. package/dist/wiki/wiki-templates.js +12 -0
  148. package/dist/wiki/wiki.js +10 -61
  149. package/dist/workflows/authoring.js +5 -25
  150. package/dist/workflows/db.js +9 -0
  151. package/dist/workflows/renderer.js +8 -3
  152. package/dist/workflows/runs.js +73 -88
  153. package/dist/workflows/scope-key.js +76 -0
  154. package/dist/workflows/validator.js +1 -1
  155. package/dist/workflows/workflow-template.md +24 -0
  156. package/docs/README.md +5 -2
  157. package/docs/migration/release-notes/0.7.0.md +1 -1
  158. package/docs/migration/release-notes/0.7.4.md +1 -1
  159. package/docs/migration/release-notes/0.7.5.md +20 -0
  160. package/docs/migration/release-notes/0.8.0.md +43 -0
  161. package/package.json +4 -3
  162. package/dist/templates/wiki-templates.js +0 -100
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Unified AI call adapter: prefers `config.agent` (agent CLI shell-out),
3
+ * falls back to `config.llm` (HTTP chat-completions).
4
+ *
5
+ * NOT for use by background indexer passes — those call `chatCompletion`
6
+ * directly to avoid the agent-CLI overhead and to stay on the HTTP path that
7
+ * the indexer was designed around.
8
+ */
9
+ import { warn } from "../core/warn";
10
+ import { resolveAgentProfile, runAgent } from "../integrations/agent";
11
+ import { chatCompletion } from "./client";
12
+ /**
13
+ * Unified AI call: prefers `config.agent` (agent CLI), falls back to
14
+ * `config.llm` (HTTP). When neither is configured, returns a structured
15
+ * error pointing the user at `akm setup`.
16
+ *
17
+ * NOT for use by background indexer passes — those call `chatCompletion`
18
+ * directly.
19
+ */
20
+ export async function callAi(config, prompt, opts = {}) {
21
+ if (config.agent) {
22
+ try {
23
+ const defaultName = config.agent.default;
24
+ if (!defaultName) {
25
+ return {
26
+ ok: false,
27
+ error: "No default agent profile configured. Set `agent.default` in config.json or run `akm setup`.",
28
+ };
29
+ }
30
+ const profile = resolveAgentProfile(defaultName, config.agent.profiles?.[defaultName]);
31
+ if (!profile) {
32
+ return {
33
+ ok: false,
34
+ error: `Agent profile "${defaultName}" is not built-in and has no \`bin\` override.`,
35
+ };
36
+ }
37
+ const result = await runAgent(profile, prompt, {
38
+ stdio: opts.draftFilePath ? "interactive" : "captured",
39
+ parseOutput: "text",
40
+ timeoutMs: opts.timeoutMs,
41
+ });
42
+ if (!result.ok)
43
+ return { ok: false, error: result.error ?? result.reason ?? "agent failed" };
44
+ return { ok: true, content: result.stdout ?? "", path: "agent-cli" };
45
+ }
46
+ catch (e) {
47
+ return { ok: false, error: String(e) };
48
+ }
49
+ }
50
+ if (config.llm) {
51
+ if (opts.draftFilePath) {
52
+ warn("[akm] No agent CLI configured — falling back to LLM API. " +
53
+ "File-write contract unavailable; expecting JSON in stdout. " +
54
+ "Install an agent CLI and run `akm setup` for full functionality.");
55
+ }
56
+ const messages = [];
57
+ if (opts.systemPrompt)
58
+ messages.push({ role: "system", content: opts.systemPrompt });
59
+ messages.push({ role: "user", content: prompt });
60
+ try {
61
+ const content = await chatCompletion(config.llm, messages, {
62
+ ...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
63
+ });
64
+ return { ok: true, content, path: "llm-http" };
65
+ }
66
+ catch (e) {
67
+ return { ok: false, error: String(e) };
68
+ }
69
+ }
70
+ return {
71
+ ok: false,
72
+ error: "No AI connection configured. Run `akm setup` or set `agent` or `llm` in your config.",
73
+ };
74
+ }
@@ -8,6 +8,10 @@
8
8
  * `llm.ts` re-exports everything from this module for backward compatibility.
9
9
  */
10
10
  import { fetchWithTimeout } from "../core/common";
11
+ import { escapeJsonStringControls, parseJsonResponse, stripCodeFences, stripThinkBlocks } from "../core/parse";
12
+ // Re-export shared parse utilities so existing importers of `client.ts` continue
13
+ // to resolve `parseJsonResponse` and `parseEmbeddedJsonResponse` from this module.
14
+ export { escapeJsonStringControls, parseEmbeddedJsonResponse, parseJsonResponse, stripCodeFences, stripThinkBlocks, } from "../core/parse";
11
15
  /** Maximum length of an LLM error response body included in thrown errors. */
12
16
  const ERROR_BODY_MAX_LEN = 200;
13
17
  /**
@@ -38,105 +42,78 @@ export function redactErrorBody(input) {
38
42
  }
39
43
  return out;
40
44
  }
45
+ export class LlmCallError extends Error {
46
+ code;
47
+ statusCode;
48
+ constructor(message, code, statusCode) {
49
+ super(message);
50
+ this.code = code;
51
+ this.statusCode = statusCode;
52
+ this.name = "LlmCallError";
53
+ }
54
+ }
41
55
  export async function chatCompletion(config, messages, options) {
56
+ const timeoutMs = options?.timeoutMs ?? config.timeoutMs ?? 120_000;
42
57
  const headers = { "Content-Type": "application/json" };
43
58
  if (config.apiKey) {
44
59
  headers.Authorization = `Bearer ${config.apiKey}`;
45
60
  }
46
- const response = await fetchWithTimeout(config.endpoint, {
47
- method: "POST",
48
- headers,
49
- body: JSON.stringify({
50
- model: config.model,
51
- messages,
52
- temperature: options?.temperature ?? config.temperature ?? 0.3,
53
- max_tokens: options?.maxTokens ?? config.maxTokens ?? 512,
54
- ...config.extraParams,
55
- }),
56
- }, 30_000, options?.signal);
61
+ // Only include max_tokens when explicitly set. The model/API knows its own
62
+ // limits; a hardcoded default creates silent truncation failures when the
63
+ // guess is wrong. Users who need a cap can set llm.maxTokens in config.
64
+ const resolvedMaxTokens = options?.maxTokens ?? config.maxTokens;
65
+ let response;
66
+ try {
67
+ response = await fetchWithTimeout(config.endpoint, {
68
+ method: "POST",
69
+ headers,
70
+ body: JSON.stringify({
71
+ model: config.model,
72
+ messages,
73
+ temperature: options?.temperature ?? config.temperature ?? 0.3,
74
+ ...(resolvedMaxTokens !== undefined ? { max_tokens: resolvedMaxTokens } : {}),
75
+ ...config.extraParams,
76
+ }),
77
+ }, timeoutMs, options?.signal);
78
+ }
79
+ catch (err) {
80
+ // fetchWithTimeout throws a plain Error with a message containing
81
+ // "timed out" for AbortController-driven timeouts, or "aborted" for
82
+ // caller-driven cancellations. Map both to typed LlmCallError.
83
+ const msg = err instanceof Error ? err.message : String(err);
84
+ if (err instanceof DOMException && err.name === "AbortError") {
85
+ throw new LlmCallError(`Request timed out after ${timeoutMs}ms`, "timeout");
86
+ }
87
+ if (msg.includes("timed out")) {
88
+ throw new LlmCallError(`Request timed out after ${timeoutMs}ms`, "timeout");
89
+ }
90
+ throw new LlmCallError(`Network error: ${msg}`, "network_error");
91
+ }
57
92
  if (!response.ok) {
58
93
  const rawBody = await response.text().catch(() => "");
59
94
  const safeBody = redactErrorBody(rawBody);
60
- throw new Error(`LLM request failed (${response.status}) ${config.endpoint}: ${safeBody}`);
95
+ const status = response.status;
96
+ if (status === 429) {
97
+ throw new LlmCallError(`LLM request rate limited (429) ${config.endpoint}: ${safeBody}`, "rate_limited", status);
98
+ }
99
+ if (status >= 500) {
100
+ throw new LlmCallError(`LLM provider error (${status}) ${config.endpoint}: ${safeBody}`, "provider_error", status);
101
+ }
102
+ throw new LlmCallError(`LLM request failed (${status}) ${config.endpoint}: ${safeBody}`, "provider_error", status);
61
103
  }
62
104
  const json = (await response.json());
63
- return json.choices?.[0]?.message?.content?.trim() ?? "";
64
- }
65
- /** Strip leading/trailing markdown code fences from an LLM response. */
66
- export function stripJsonFences(raw) {
67
- return raw
68
- .trim()
69
- .replace(/<think>[\s\S]*?<\/think>/gi, "")
70
- .replace(/^```(?:json)?\s*\n?/i, "")
71
- .replace(/\n?```\s*$/i, "")
72
- .trim();
73
- }
74
- /** Parse a possibly-fenced JSON response. Returns undefined if invalid. */
75
- export function parseJsonResponse(raw) {
76
- try {
77
- return JSON.parse(stripJsonFences(raw));
78
- }
79
- catch {
80
- return undefined;
81
- }
105
+ const content = (json.choices?.[0]?.message?.content ?? "").trim();
106
+ const reasoning = (json.choices?.[0]?.message?.reasoning_content ?? "").trim();
107
+ return content || reasoning;
82
108
  }
83
109
  /**
84
- * Best-effort recovery for providers that wrap JSON in extra prose or fenced
85
- * blocks. Extracts the first balanced top-level object/array and parses it.
110
+ * Strip `<think>` blocks, code fences, and escape control characters in JSON
111
+ * strings. Thin wrapper kept for backward compatibility with call sites that
112
+ * import `stripJsonFences` from this module. New code should prefer the
113
+ * granular helpers from `../core/parse`.
86
114
  */
87
- export function parseEmbeddedJsonResponse(raw) {
88
- const direct = parseJsonResponse(raw);
89
- if (direct !== undefined)
90
- return direct;
91
- const text = stripJsonFences(raw);
92
- let arrayFallback;
93
- for (let start = 0; start < text.length; start++) {
94
- const opener = text[start];
95
- if (opener !== "{" && opener !== "[")
96
- continue;
97
- const closer = opener === "{" ? "}" : "]";
98
- let depth = 0;
99
- let inString = false;
100
- let escaped = false;
101
- for (let i = start; i < text.length; i++) {
102
- const ch = text[i];
103
- if (inString) {
104
- if (escaped) {
105
- escaped = false;
106
- }
107
- else if (ch === "\\") {
108
- escaped = true;
109
- }
110
- else if (ch === '"') {
111
- inString = false;
112
- }
113
- continue;
114
- }
115
- if (ch === '"') {
116
- inString = true;
117
- continue;
118
- }
119
- if (ch === opener)
120
- depth += 1;
121
- if (ch === closer) {
122
- depth -= 1;
123
- if (depth === 0) {
124
- try {
125
- const parsed = JSON.parse(text.slice(start, i + 1));
126
- if (!Array.isArray(parsed)) {
127
- return parsed;
128
- }
129
- arrayFallback ??= parsed;
130
- break;
131
- }
132
- catch {
133
- break;
134
- }
135
- }
136
- }
137
- }
138
- }
139
- return arrayFallback;
115
+ export function stripJsonFences(raw) {
116
+ return escapeJsonStringControls(stripCodeFences(stripThinkBlocks(raw)));
140
117
  }
141
118
  // ── Availability check ──────────────────────────────────────────────────────
142
119
  /**
@@ -8,16 +8,15 @@
8
8
  * The seam is intentionally tiny:
9
9
  *
10
10
  * - `isLlmFeatureEnabled(config, feature)` — pure predicate, no side
11
- * effects, no I/O. Returns `true` only when the feature flag is the
12
- * literal boolean `true` in config. Defaults are `false` per v1
13
- * spec §14 — adding a flag to the schema is a non-event until the user
14
- * opts in.
11
+ * effects, no I/O. Returns `true` when the feature flag is explicitly
12
+ * `true`, or when the feature has a non-false default (currently
13
+ * `graph_extraction`).
15
14
  * - `tryLlmFeature(feature, config, fn, fallback, opts?)` — single-call
16
15
  * wrapper that runs `fn()` only when the gate is open, enforces a hard
17
- * timeout (default 30s — overridable per call), and returns `fallback`
18
- * on disablement, throw, or timeout. The wrapper is referentially
19
- * transparent for any given (gate-state, fn-result) pair: no module
20
- * state is mutated.
16
+ * timeout (default 600s — overridable per call via `opts.timeoutMs`),
17
+ * and returns `fallback` on disablement, throw, or timeout. The wrapper
18
+ * is referentially transparent for any given (gate-state, fn-result)
19
+ * pair: no module state is mutated.
21
20
  *
22
21
  * Statelessness invariant (v1 spec §14.4): nothing in this module holds
23
22
  * state across calls. There are no caches, no module-level singletons, no
@@ -29,18 +28,30 @@
29
28
  /**
30
29
  * Pure predicate: is the named feature gate explicitly enabled in `config`?
31
30
  *
32
- * Returns `false` when:
33
- * - the LLM block is missing,
34
- * - the `features` block is missing,
35
- * - the key is absent (defaults are `false`),
36
- * - the key is set to `false`.
31
+ * Returns `false` only when the key is explicitly set to `false`, or when
32
+ * the key is absent and its default is `false`.
37
33
  */
34
+ const FEATURE_DEFAULTS = {
35
+ memory_inference: true,
36
+ graph_extraction: true,
37
+ };
38
38
  export function isLlmFeatureEnabled(config, feature) {
39
- if (!config?.llm?.features)
39
+ const configured = config?.llm?.features?.[feature];
40
+ if (configured === true)
41
+ return true;
42
+ if (configured === false)
40
43
  return false;
41
- return config.llm.features[feature] === true;
44
+ return FEATURE_DEFAULTS[feature] === true;
42
45
  }
43
- const DEFAULT_TIMEOUT_MS = 30_000;
46
+ /**
47
+ * Default hard timeout for every bounded in-tree LLM call. Set to 10 minutes
48
+ * (600 000 ms) — generous enough for a slow local model on a single-threaded
49
+ * server. Override per-call via `TryLlmFeatureOptions.timeoutMs`.
50
+ *
51
+ * Do NOT reduce this default without a documented user-facing reason — local
52
+ * model users need the headroom.
53
+ */
54
+ const DEFAULT_TIMEOUT_MS = 600_000;
44
55
  /**
45
56
  * Run `fn()` only if `isLlmFeatureEnabled(config, feature)` is `true`. On
46
57
  * disablement, throw, or timeout, return `fallback` (or — if it is a