akm-cli 0.9.0-beta.52 → 0.9.0-beta.54

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 (66) hide show
  1. package/dist/assets/hints/cli-hints-full.md +6 -5
  2. package/dist/cli/clack.js +56 -0
  3. package/dist/cli/confirm.js +1 -1
  4. package/dist/cli.js +0 -7
  5. package/dist/commands/env/env-cli.js +3 -2
  6. package/dist/commands/env/env.js +14 -67
  7. package/dist/commands/health/checks.js +28 -15
  8. package/dist/commands/health/html-report.js +33 -10
  9. package/dist/commands/health.js +222 -22
  10. package/dist/commands/improve/collapse-detector.js +419 -0
  11. package/dist/commands/improve/consolidate.js +72 -54
  12. package/dist/commands/improve/distill.js +79 -13
  13. package/dist/commands/improve/extract.js +13 -6
  14. package/dist/commands/improve/homeostatic.js +109 -79
  15. package/dist/commands/improve/improve-cli.js +67 -1
  16. package/dist/commands/improve/improve.js +10 -0
  17. package/dist/commands/improve/loop-stages.js +39 -1
  18. package/dist/commands/improve/outcome-loop.js +33 -19
  19. package/dist/commands/improve/preparation.js +36 -11
  20. package/dist/commands/improve/salience.js +49 -32
  21. package/dist/commands/read/curate.js +9 -13
  22. package/dist/commands/read/knowledge.js +4 -0
  23. package/dist/commands/read/search-cli.js +6 -4
  24. package/dist/commands/read/search.js +12 -5
  25. package/dist/commands/read/show.js +6 -8
  26. package/dist/commands/sources/add-cli.js +1 -1
  27. package/dist/commands/sources/init.js +12 -0
  28. package/dist/commands/sources/stash-cli.js +1 -1
  29. package/dist/commands/tasks/default-tasks.js +12 -0
  30. package/dist/core/asset/asset-spec.js +3 -2
  31. package/dist/core/config/config-schema.js +39 -17
  32. package/dist/core/config/config.js +12 -0
  33. package/dist/core/eval/rank-metrics.js +113 -0
  34. package/dist/core/state/migrations.js +56 -0
  35. package/dist/core/state-db.js +146 -19
  36. package/dist/core/warn.js +21 -0
  37. package/dist/indexer/db/db.js +6 -0
  38. package/dist/indexer/ensure-index.js +36 -92
  39. package/dist/indexer/index-writer-lock.js +9 -11
  40. package/dist/indexer/index-written-assets.js +105 -0
  41. package/dist/indexer/indexer.js +16 -4
  42. package/dist/indexer/passes/metadata.js +20 -0
  43. package/dist/indexer/read-preflight.js +23 -0
  44. package/dist/indexer/search/db-search.js +29 -1
  45. package/dist/indexer/search/ranking-contributors.js +33 -1
  46. package/dist/indexer/search/ranking.js +66 -0
  47. package/dist/indexer/search/search-fields.js +6 -0
  48. package/dist/indexer/walk/walker.js +21 -13
  49. package/dist/integrations/agent/detect.js +9 -0
  50. package/dist/integrations/agent/index.js +1 -1
  51. package/dist/llm/client.js +12 -0
  52. package/dist/llm/embedder.js +26 -2
  53. package/dist/llm/embedders/local.js +7 -1
  54. package/dist/llm/feature-gate.js +6 -2
  55. package/dist/output/renderers.js +8 -13
  56. package/dist/output/shapes/helpers.js +0 -3
  57. package/dist/output/shapes/passthrough.js +1 -0
  58. package/dist/scripts/migrate-storage.js +178 -35
  59. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +46 -19
  60. package/dist/setup/detect.js +9 -0
  61. package/dist/setup/registry-stash-loader.js +12 -0
  62. package/dist/setup/setup.js +1 -1
  63. package/dist/storage/repositories/index-db.js +10 -1
  64. package/dist/tasks/backends/index.js +9 -0
  65. package/dist/tasks/runner.js +9 -0
  66. package/package.json +2 -4
@@ -148,20 +148,28 @@ function isInsideGitRepo(dir) {
148
148
  * read (e.g. permission errors).
149
149
  */
150
150
  export function* walkMarkdownFiles(root) {
151
- let entries;
152
- try {
153
- entries = fs.readdirSync(root, { withFileTypes: true });
154
- }
155
- catch {
156
- return;
157
- }
158
- for (const entry of entries) {
159
- const full = path.join(root, entry.name);
160
- if (entry.isDirectory()) {
161
- yield* walkMarkdownFiles(full);
151
+ const stack = [root];
152
+ while (stack.length > 0) {
153
+ const current = stack.pop();
154
+ if (!current)
155
+ continue;
156
+ let entries;
157
+ try {
158
+ entries = fs.readdirSync(current, { withFileTypes: true });
162
159
  }
163
- else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
164
- yield full;
160
+ catch {
161
+ continue;
162
+ }
163
+ for (const entry of entries) {
164
+ const full = path.join(current, entry.name);
165
+ if (entry.isSymbolicLink())
166
+ continue;
167
+ if (entry.isDirectory()) {
168
+ stack.push(full);
169
+ }
170
+ else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
171
+ yield full;
172
+ }
165
173
  }
166
174
  }
167
175
  }
@@ -55,6 +55,11 @@ export function defaultWhich(bin, envSource = process.env) {
55
55
  }
56
56
  return undefined;
57
57
  }
58
+ let detectOverrides;
59
+ /** TEST-ONLY. Swap the detection implementations; pass undefined to restore. */
60
+ export function _setAgentDetectForTests(fakes) {
61
+ detectOverrides = fakes;
62
+ }
58
63
  /**
59
64
  * Probe every resolvable agent profile (built-ins plus user overrides)
60
65
  * for an installed CLI.
@@ -64,6 +69,8 @@ export function defaultWhich(bin, envSource = process.env) {
64
69
  * @param whichFn Binary lookup. Tests should inject a stub.
65
70
  */
66
71
  export function detectAgentCliProfiles(agent, whichFn = defaultWhich) {
72
+ if (detectOverrides?.detectAgentCliProfiles)
73
+ return detectOverrides.detectAgentCliProfiles(agent, whichFn);
67
74
  const profiles = listResolvedAgentProfiles(agent);
68
75
  return profiles.map((profile) => probeProfile(profile, whichFn));
69
76
  }
@@ -87,6 +94,8 @@ function probeProfile(profile, whichFn) {
87
94
  * writing `agent.default`.
88
95
  */
89
96
  export function pickDefaultAgentProfile(results, existingDefault) {
97
+ if (detectOverrides?.pickDefaultAgentProfile)
98
+ return detectOverrides.pickDefaultAgentProfile(results, existingDefault);
90
99
  if (existingDefault) {
91
100
  const match = results.find((r) => r.name === existingDefault && r.available);
92
101
  if (match)
@@ -3,7 +3,7 @@
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  export { getCommandBuilder } from "./builders.js";
5
5
  export { DEFAULT_AGENT_TIMEOUT_MS, listAgentProfileNames, listResolvedAgentProfiles, requireAgentProfile, resolveAgentProfile, resolveDefaultProfileName, resolveProfileFromConfig, } from "./config.js";
6
- export { defaultWhich, detectAgentCliProfiles, pickDefaultAgentProfile } from "./detect.js";
6
+ export { _setAgentDetectForTests, defaultWhich, detectAgentCliProfiles, pickDefaultAgentProfile } from "./detect.js";
7
7
  export { listBuiltinModelAliases, resolveModel } from "./model-aliases.js";
8
8
  export { BUILTIN_AGENT_PROFILE_NAMES, getBuiltinAgentProfile, listBuiltinAgentProfiles, } from "./profiles.js";
9
9
  export { buildProposePrompt, buildReflectPrompt, buildSchemaRepairPrompt, extractDraftConfidence, parseAgentProposalPayload, } from "./prompts.js";
@@ -165,7 +165,19 @@ function isRetryable(err) {
165
165
  }
166
166
  return false;
167
167
  }
168
+ // ── Test seam ────────────────────────────────────────────────────────────────
169
+ // Swap-and-restore override. Inert in production; only tests call the setter.
170
+ let chatCompletionOverride;
171
+ /** TEST-ONLY. Swap the implementation of `chatCompletion`; pass undefined to restore. */
172
+ export function _setChatCompletionForTests(fake) {
173
+ chatCompletionOverride = fake;
174
+ }
168
175
  export async function chatCompletion(config, messages, options) {
176
+ if (chatCompletionOverride)
177
+ return chatCompletionOverride(config, messages, options);
178
+ return chatCompletionReal(config, messages, options);
179
+ }
180
+ async function chatCompletionReal(config, messages, options) {
169
181
  const effectiveTimeoutMs = options?.timeoutMs ?? config.timeoutMs ?? 120_000;
170
182
  const started = Date.now();
171
183
  try {
@@ -3,11 +3,26 @@
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  import { embedCacheKey, getCachedEmbedding, setCachedEmbedding } from "./embedders/cache.js";
5
5
  import { DETERMINISTIC_EMBED_MODEL_ID, deterministicEmbed, isDeterministicEmbedEnabled, } from "./embedders/deterministic.js";
6
- import { DEFAULT_LOCAL_MODEL, isTransformersAvailable, LocalEmbedder } from "./embedders/local.js";
6
+ import { DEFAULT_LOCAL_MODEL, isTransformersAvailable as isTransformersAvailableReal, LocalEmbedder, } from "./embedders/local.js";
7
7
  import { hasRemoteEndpoint, RemoteEmbedder } from "./embedders/remote.js";
8
8
  // ── Re-exports (public API) ─────────────────────────────────────────────────
9
9
  export { clearEmbeddingCache } from "./embedders/cache.js";
10
- export { DEFAULT_LOCAL_MODEL, isTransformersAvailable } from "./embedders/local.js";
10
+ export { _setTransformersLoaderForTests, DEFAULT_LOCAL_MODEL } from "./embedders/local.js";
11
+ let embedderOverrides;
12
+ /** TEST-ONLY. Swap embedder implementations; pass undefined to restore. */
13
+ export function _setEmbedderForTests(fakes) {
14
+ embedderOverrides = fakes;
15
+ }
16
+ /**
17
+ * Check whether the @huggingface/transformers package is importable.
18
+ * Delegating wrapper around `./embedders/local`'s probe so tests can swap it
19
+ * via {@link _setEmbedderForTests}.
20
+ */
21
+ export function isTransformersAvailable() {
22
+ if (embedderOverrides?.isTransformersAvailable)
23
+ return embedderOverrides.isTransformersAvailable();
24
+ return isTransformersAvailableReal();
25
+ }
11
26
  // ── Singleton local embedder ────────────────────────────────────────────────
12
27
  // `_localEmbedder` is an intentional module-level singleton but constructed
13
28
  // lazily on first use. The underlying @huggingface/transformers pipeline is
@@ -40,6 +55,8 @@ export function resetLocalEmbedder() {
40
55
  * and embedding config. Repeated identical queries return the cached vector.
41
56
  */
42
57
  export async function embed(text, embeddingConfig, signal) {
58
+ if (embedderOverrides?.embed)
59
+ return embedderOverrides.embed(text, embeddingConfig, signal);
43
60
  // Deterministic mode (env-gated, test/bench only): model-free, stable.
44
61
  if (isDeterministicEmbedEnabled()) {
45
62
  return deterministicEmbed(text);
@@ -61,6 +78,8 @@ export async function embed(text, embeddingConfig, signal) {
61
78
  * which processes texts in chunks of 32 for genuine batched inference.
62
79
  */
63
80
  export async function embedBatch(texts, embeddingConfig, signal) {
81
+ if (embedderOverrides?.embedBatch)
82
+ return embedderOverrides.embedBatch(texts, embeddingConfig, signal);
64
83
  if (texts.length === 0)
65
84
  return [];
66
85
  // Deterministic mode (env-gated, test/bench only): model-free, stable.
@@ -104,6 +123,8 @@ export { cosineSimilarity } from "./embedders/types.js";
104
123
  * - No config: use `DEFAULT_LOCAL_MODEL` (the shared singleton model).
105
124
  */
106
125
  export function resolveEmbeddingModelId(embeddingConfig) {
126
+ if (embedderOverrides?.resolveEmbeddingModelId)
127
+ return embedderOverrides.resolveEmbeddingModelId(embeddingConfig);
107
128
  if (isDeterministicEmbedEnabled())
108
129
  return DETERMINISTIC_EMBED_MODEL_ID;
109
130
  if (!embeddingConfig)
@@ -117,6 +138,9 @@ export function resolveEmbeddingModelId(embeddingConfig) {
117
138
  * Check whether embedding is available with a detailed reason on failure.
118
139
  */
119
140
  export async function checkEmbeddingAvailability(embeddingConfig) {
141
+ if (embedderOverrides?.checkEmbeddingAvailability) {
142
+ return embedderOverrides.checkEmbeddingAvailability(embeddingConfig);
143
+ }
120
144
  // Deterministic mode (env-gated): always available — no model, no network.
121
145
  if (isDeterministicEmbedEnabled()) {
122
146
  return { available: true };
@@ -28,6 +28,12 @@ function isBatchTensor(v) {
28
28
  Array.isArray(v.dims) &&
29
29
  v.dims.length >= 2);
30
30
  }
31
+ const realTransformersLoader = () => import("@huggingface/transformers");
32
+ let transformersLoader = realTransformersLoader;
33
+ /** TEST-ONLY. Swap the transformers module loader; pass undefined to restore. */
34
+ export function _setTransformersLoaderForTests(fake) {
35
+ transformersLoader = fake ?? realTransformersLoader;
36
+ }
31
37
  const LOCAL_EMBEDDER_DTYPE = "fp32";
32
38
  const LOCAL_EMBEDDER_FALLBACK_DTYPE = "auto";
33
39
  /**
@@ -180,7 +186,7 @@ export class LocalEmbedder {
180
186
  }
181
187
  let pipeline;
182
188
  try {
183
- const mod = await import("@huggingface/transformers");
189
+ const mod = await transformersLoader();
184
190
  pipeline = mod.pipeline;
185
191
  }
186
192
  catch (importError) {
@@ -24,8 +24,12 @@ const FEATURE_LOCATION = {
24
24
  metadata_enhance: (cfg) => cfg.index?.metadataEnhance?.enabled ?? false,
25
25
  // Legacy default: false
26
26
  curate_rerank: (cfg) => cfg.search?.curateRerank?.enabled ?? false,
27
- // Legacy default: false
28
- lesson_quality_gate: (cfg) => cfg.profiles?.improve?.default?.processes?.distill?.qualityGate?.enabled ?? false,
27
+ // Default ON since R3 (docs/design/improve-self-learning-analysis.md G5):
28
+ // distill is a primary acquisition path and the judge fails open (no LLM /
29
+ // timeout / parse failure all pass through), so the gate only ever filters
30
+ // when a judge verdict actually exists. Opt out via
31
+ // profiles.improve.default.processes.distill.qualityGate.enabled: false.
32
+ lesson_quality_gate: (cfg) => cfg.profiles?.improve?.default?.processes?.distill?.qualityGate?.enabled ?? true,
29
33
  // Legacy default: false
30
34
  proposal_quality_gate: (cfg) => cfg.profiles?.improve?.default?.processes?.reflect?.qualityGate?.enabled ?? false,
31
35
  // Legacy default: false
@@ -364,23 +364,21 @@ const scriptSourceRenderer = {
364
364
  };
365
365
  // ── 8. env-file ───────────────────────────────────────────────────────────────
366
366
  /**
367
- * Env renderer. Returns ONLY key names and start-of-line comments never
368
- * values. Deliberately omits content/template/prompt so env values cannot leak
369
- * through `akm show`.
367
+ * Env renderer. Returns ONLY key names never values, and never comment
368
+ * text (comments routinely contain commented-out credentials). Deliberately
369
+ * omits content/template/prompt so env values cannot leak through `akm show`.
370
370
  */
371
371
  const envFileRenderer = {
372
372
  name: "env-file",
373
373
  buildShowResponse(ctx) {
374
374
  const name = deriveName(ctx);
375
- const { keys, comments } = listVaultKeys(ctx.absPath);
375
+ const { keys } = listVaultKeys(ctx.absPath);
376
376
  return {
377
377
  type: "env",
378
378
  name,
379
379
  path: ctx.absPath,
380
- action: "Environment — keys + comments only. Use `akm env run <ref> -- <command>` to run with the whole .env injected; prefer `--clean` to minimize inherited parent env. AKM itself does not print values, but child stdout/stderr is not redacted. `akm env export <ref> --out <file>` writes a sourceable script to a file. Never `source` the raw file. Values stay on disk and are never written to akm's stdout.",
381
- description: comments.length > 0 ? comments.join("\n") : undefined,
380
+ action: "Environment — key names only. Use `akm env run <ref> -- <command>` to run with the whole .env injected; prefer `--clean` to minimize inherited parent env. AKM itself does not print values, but child stdout/stderr is not redacted. `akm env export <ref> --out <file>` writes a sourceable script to a file. Never `source` the raw file. Values stay on disk and are never written to akm's stdout.",
382
381
  keys,
383
- comments,
384
382
  };
385
383
  },
386
384
  enrichSearchHit(hit, _stashDir) {
@@ -647,12 +645,9 @@ function applyScriptMetadata(entry, ctx) {
647
645
  }
648
646
  }
649
647
  function applyEnvMetadata(entry, ctx) {
650
- const { keys, comments } = listVaultKeys(ctx.absPath);
651
- if (comments.length > 0 && !entry.description) {
652
- entry.description = comments.join(" ").slice(0, 500);
653
- entry.source = "comments";
654
- entry.confidence = 0.7;
655
- }
648
+ // Key names only comment text must never reach description/search_text
649
+ // (comments routinely contain commented-out credentials).
650
+ const { keys } = listVaultKeys(ctx.absPath);
656
651
  if (keys.length > 0) {
657
652
  entry.searchHints = keys;
658
653
  }
@@ -369,7 +369,6 @@ export function shapeShowOutput(result, detail, shape = "human") {
369
369
  "workflowParameters",
370
370
  "steps",
371
371
  "keys",
372
- "comments",
373
372
  "related",
374
373
  ]);
375
374
  }
@@ -385,7 +384,6 @@ export function shapeShowOutput(result, detail, shape = "human") {
385
384
  "run",
386
385
  "origin",
387
386
  "keys",
388
- "comments",
389
387
  "related",
390
388
  ]);
391
389
  }
@@ -411,7 +409,6 @@ export function shapeShowOutput(result, detail, shape = "human") {
411
409
  "cwd",
412
410
  "activeRun",
413
411
  "keys",
414
- "comments",
415
412
  "related",
416
413
  // path and editable are always projected so JSON consumers can locate and
417
414
  // edit the asset without needing --detail full (QA #7).
@@ -42,6 +42,7 @@ const PASSTHROUGH_COMMANDS = [
42
42
  "extract",
43
43
  "health",
44
44
  "improve",
45
+ "improve-canary",
45
46
  "lessons-coverage",
46
47
  "import",
47
48
  "index",