akm-cli 0.9.7 → 0.9.8-beta.1

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 (117) hide show
  1. package/CHANGELOG.md +218 -0
  2. package/dist/commands/env/env.js +2 -21
  3. package/dist/commands/health/checks.js +0 -25
  4. package/dist/commands/health/improve-metrics.js +8 -34
  5. package/dist/commands/health/windows.js +0 -4
  6. package/dist/commands/health.js +1 -35
  7. package/dist/commands/improve/consolidate/eligibility.js +11 -5
  8. package/dist/commands/improve/extract.js +36 -32
  9. package/dist/commands/improve/memory/memory-belief.js +15 -5
  10. package/dist/commands/improve/memory/memory-contradiction-detect.js +16 -25
  11. package/dist/commands/improve/memory/memory-improve.js +7 -19
  12. package/dist/commands/improve/preparation.js +52 -36
  13. package/dist/commands/improve/reflect.js +13 -17
  14. package/dist/commands/lint/base-linter.js +42 -2
  15. package/dist/commands/lint/index.js +4 -5
  16. package/dist/commands/migrate/config-extra-params.js +61 -0
  17. package/dist/commands/migrate/dead-residue.js +113 -0
  18. package/dist/commands/migrate/stale-txn.js +49 -0
  19. package/dist/commands/migrate-cli.js +42 -1
  20. package/dist/commands/proposal/proposal.js +1 -21
  21. package/dist/commands/proposal/repository.js +0 -4
  22. package/dist/commands/proposal/validators/proposal-quality-validators.js +0 -6
  23. package/dist/commands/read/curate.js +4 -18
  24. package/dist/commands/read/show.js +2 -1
  25. package/dist/commands/sources/installed-stashes.js +5 -1
  26. package/dist/commands/sources/self-update.js +38 -1
  27. package/dist/commands/sources/sources-cli.js +17 -1
  28. package/dist/commands/tasks/tasks.js +0 -8
  29. package/dist/commands/url-checker.js +20 -28
  30. package/dist/core/adapter/adapters/agent-skills-adapter.js +2 -6
  31. package/dist/core/adapter/adapters/akm-adapter.js +2 -3
  32. package/dist/core/adapter/adapters/akm-lint.js +2 -20
  33. package/dist/core/adapter/adapters/akm-task-adapter.js +2 -6
  34. package/dist/core/adapter/adapters/akm-workflow-adapter.js +2 -6
  35. package/dist/core/adapter/adapters/dotenv-adapter.js +2 -22
  36. package/dist/core/adapter/adapters/generic-files-adapter.js +3 -9
  37. package/dist/core/adapter/adapters/llm-wiki-adapter.js +2 -6
  38. package/dist/core/adapter/adapters/okf-adapter.js +7 -18
  39. package/dist/core/adapter/adapters/shared.js +2 -0
  40. package/dist/core/adapter/adapters/tool-dir-shared.js +3 -9
  41. package/dist/core/adapter/adapters/website-snapshot-adapter.js +2 -6
  42. package/dist/core/adapter/validate-context.js +1 -3
  43. package/dist/core/asset/asset-placement.js +1 -3
  44. package/dist/core/asset/frontmatter.js +96 -0
  45. package/dist/core/common.js +66 -2
  46. package/dist/core/config/config-walker.js +6 -10
  47. package/dist/core/config/config.js +11 -8
  48. package/dist/core/extra-params.js +17 -13
  49. package/dist/core/improve-result.js +1 -3
  50. package/dist/core/json-schema.js +9 -11
  51. package/dist/core/state/migrations.js +52 -2
  52. package/dist/core/state-db.js +2 -1
  53. package/dist/execution/executable-identity.js +1 -3
  54. package/dist/execution/guarded-source.js +1 -6
  55. package/dist/indexer/bundle-identity-guard.js +6 -1
  56. package/dist/indexer/db/graph-db.js +139 -154
  57. package/dist/indexer/ensure-index.js +11 -19
  58. package/dist/indexer/graph/graph-boost.js +23 -34
  59. package/dist/indexer/graph/graph-extraction.js +12 -2
  60. package/dist/indexer/indexer.js +1 -1
  61. package/dist/indexer/lookup/adapter-concept-owner.js +12 -9
  62. package/dist/indexer/passes/memory-inference.js +7 -2
  63. package/dist/indexer/scan/drain-dir.js +2 -4
  64. package/dist/indexer/search/db-search.js +3 -3
  65. package/dist/indexer/search/fts-query.js +10 -15
  66. package/dist/indexer/search/search-source.js +0 -13
  67. package/dist/indexer/usage/usage-events.js +9 -1
  68. package/dist/indexer/walk/walker.js +11 -6
  69. package/dist/integrations/harnesses/claude/result-extractor.js +30 -15
  70. package/dist/integrations/harnesses/codex/result-extractor.js +43 -6
  71. package/dist/integrations/harnesses/copilot/result-extractor.js +39 -12
  72. package/dist/integrations/harnesses/gemini/result-extractor.js +40 -12
  73. package/dist/integrations/harnesses/index.js +0 -4
  74. package/dist/integrations/harnesses/openhands/result-extractor.js +51 -19
  75. package/dist/integrations/harnesses/pi/result-extractor.js +45 -12
  76. package/dist/integrations/lockfile.js +0 -14
  77. package/dist/integrations/session-logs/index.js +0 -81
  78. package/dist/llm/client.js +0 -12
  79. package/dist/llm/memory-infer.js +1 -3
  80. package/dist/llm/usage-telemetry.js +1 -3
  81. package/dist/registry/create-provider-registry.js +4 -0
  82. package/dist/registry/factory.js +4 -0
  83. package/dist/registry/resolve.js +23 -8
  84. package/dist/runtime.js +0 -12
  85. package/dist/scripts/akm-migrate-node.js +142 -244
  86. package/dist/scripts/akm-migrate.js +142 -244
  87. package/dist/setup/setup.js +6 -12
  88. package/dist/sources/providers/git-install.js +7 -2
  89. package/dist/sources/providers/tar-utils.js +1 -7
  90. package/dist/sources/snapshot-fetchers/content-extract.js +0 -17
  91. package/dist/storage/managed-db.js +20 -7
  92. package/dist/storage/repositories/events-repository.js +0 -81
  93. package/dist/storage/repositories/index-entries-repository.js +0 -17
  94. package/dist/storage/repositories/index-fts-repository.js +24 -30
  95. package/dist/storage/repositories/index-utility-repository.js +0 -57
  96. package/dist/storage/repositories/index-vec-repository.js +25 -27
  97. package/dist/storage/repositories/task-history-repository.js +9 -3
  98. package/dist/tasks/backends/launchd.js +8 -18
  99. package/dist/tasks/run/task-history.js +21 -31
  100. package/dist/tasks/scheduler-sync.js +1 -6
  101. package/dist/tasks/source/bounded-document.js +1 -14
  102. package/dist/workflows/exec/child-workflow.js +1 -1
  103. package/dist/workflows/exec/native-executor.js +2 -2
  104. package/dist/workflows/exec/step-work.js +5 -17
  105. package/dist/workflows/exec/worktree.js +40 -6
  106. package/dist/workflows/freeze/task-bindings.js +2 -4
  107. package/dist/workflows/ir/compile.js +3 -14
  108. package/dist/workflows/ir/schema-v4.js +4 -6
  109. package/dist/workflows/ir/schema.js +2 -5
  110. package/dist/workflows/parser.js +23 -26
  111. package/dist/workflows/source-files.js +8 -13
  112. package/dist/workflows/source-ir/schema.js +1 -19
  113. package/docs/migration/v0.9.0-troubleshooting.md +12 -2
  114. package/docs/reference/cli.md +2 -0
  115. package/docs/reference/workflow-schema.md +24 -0
  116. package/package.json +2 -5
  117. package/dist/workflows/source-ir/compare.js +0 -17
@@ -39,7 +39,6 @@ for (const provider of HARNESSES) {
39
39
  throw new Error(`[akm] session-log provider "${provider.name}" is not registered as a sessionLogs harness in HARNESS_REGISTRY (src/integrations/harnesses). Add it there.`);
40
40
  }
41
41
  }
42
- const ERROR_PATTERNS = /error|failed|exception|cannot|undefined|null pointer|ENOENT|timeout/i;
43
42
  /**
44
43
  * Returns all available session log harnesses for the current machine.
45
44
  * Add new harnesses to HARNESSES to support additional agent runtimes.
@@ -53,83 +52,3 @@ export function normalizeSessionTopic(text) {
53
52
  return undefined;
54
53
  return normalized.slice(0, 60);
55
54
  }
56
- export function aggregateSessionEvents(events) {
57
- const counts = new Map();
58
- for (const event of events) {
59
- const topic = normalizeSessionTopic(event.text);
60
- if (!topic)
61
- continue;
62
- const isFailurePattern = ERROR_PATTERNS.test(topic);
63
- if (!isFailurePattern)
64
- continue;
65
- const existing = counts.get(topic) ?? {
66
- count: 0,
67
- isFailurePattern,
68
- sources: new Set(),
69
- topic,
70
- };
71
- existing.count += 1;
72
- existing.isFailurePattern = existing.isFailurePattern || isFailurePattern;
73
- existing.sources.add(event.harness);
74
- counts.set(topic, existing);
75
- }
76
- return [...counts.values()]
77
- .filter((entry) => entry.count >= 2)
78
- .sort((a, b) => b.count - a.count || a.topic.localeCompare(b.topic))
79
- .slice(0, 15)
80
- .map((entry) => ({
81
- topic: entry.topic,
82
- frequency: entry.count,
83
- source: [...entry.sources].sort().join(","),
84
- isFailurePattern: entry.isFailurePattern,
85
- }));
86
- }
87
- /**
88
- * Collect normalized session events from a set of harnesses for the health
89
- * candidate scan (#568).
90
- *
91
- * Pipeline selection per harness:
92
- * - Drive the richer `listSessions()` + `readSession()` pipeline. `readSession` flattens
93
- * structured content — tool calls, assistant content blocks, thinking,
94
- * tool_result — into event text (e.g. ClaudeCodeProvider's `parseClaudeEvent`
95
- * surfaces `[tool:*]` / `[tool_result]` blocks that the legacy flat
96
- * `readEvents` scan drops entirely). This is what lets health advisories see
97
- * repeated tool failures / long runs that the flat scan hid.
98
- *
99
- * Extracted as a pure function (harnesses injected) so it is unit-testable
100
- * without touching the real on-disk session-log locations.
101
- *
102
- * `maxSessionsPerHarness` bounds the path: `readSession()` reads each
103
- * session file in full. On a machine with a
104
- * deep `~/.claude/projects` history a 30-day window can hold hundreds of
105
- * multi-MB session files, and reading+parsing every one in full made the
106
- * health command (`akm health`, which calls this synchronously) blow past its
107
- * latency budget. `listSessions()` returns summaries sorted newest-first, so
108
- * capping to the most-recent N sessions per harness keeps the richer signal
109
- * for what actually matters (recent activity) while bounding cost.
110
- */
111
- const DEFAULT_MAX_SESSIONS_PER_HARNESS = 50;
112
- export function collectSessionEvents(harnesses, sinceMs, maxSessionsPerHarness = DEFAULT_MAX_SESSIONS_PER_HARNESS) {
113
- const events = [];
114
- for (const harness of harnesses) {
115
- try {
116
- // Enumerate sessions cheaply, then read each one's full structured event
117
- // stream. There is no parallel flat-log parser.
118
- const summaries = harness.listSessions({ sinceMs });
119
- // summaries are newest-first; bound the full-file reads (see doc above).
120
- for (const summary of summaries.slice(0, maxSessionsPerHarness)) {
121
- try {
122
- const session = harness.readSession(summary);
123
- events.push(...session.events);
124
- }
125
- catch {
126
- // a single unreadable session is non-fatal
127
- }
128
- }
129
- }
130
- catch {
131
- // individual harness failures are non-fatal
132
- }
133
- }
134
- return events;
135
- }
@@ -423,18 +423,6 @@ async function chatCompletionAttemptOnce(config, messages, options, timeoutMs, i
423
423
  }
424
424
  }
425
425
  // ── Availability check ──────────────────────────────────────────────────────
426
- /**
427
- * Check if the LLM endpoint is reachable.
428
- */
429
- export async function isLlmAvailable(config) {
430
- try {
431
- const result = await chatCompletion(config, [{ role: "user", content: "Respond with just the word: ok" }]);
432
- return result.length > 0;
433
- }
434
- catch {
435
- return false;
436
- }
437
- }
438
426
  // ── Reachability probe ──────────────────────────────────────────────────────
439
427
  /**
440
428
  * Best-effort reachability check with an error message, for setup's optional
@@ -22,8 +22,6 @@ import { toErrorMessage } from "../core/common.js";
22
22
  import { parseEmbeddedJsonResponse } from "../core/parse.js";
23
23
  import { warn } from "../core/warn.js";
24
24
  import { callStructured } from "./structured-call.js";
25
- /** Hard cap on body chars sent to the model — pragmatic and matches `runLlmEnrich`. */
26
- const MAX_BODY_CHARS = 4000;
27
25
  const SYSTEM_PROMPT = memoryInferSystemPrompt;
28
26
  const USER_PROMPT_PREFIX = memoryInferUserPrompt;
29
27
  const PROMPT_PLACEHOLDERS = new Set([
@@ -74,7 +72,7 @@ export async function compressMemoryToDerivedMemory(llmRunner, body, signal, akm
74
72
  const trimmedBody = body.trim();
75
73
  if (!trimmedBody)
76
74
  return undefined;
77
- const userPrompt = `${USER_PROMPT_PREFIX}${trimmedBody.slice(0, MAX_BODY_CHARS)}`;
75
+ const userPrompt = `${USER_PROMPT_PREFIX}${trimmedBody}`;
78
76
  // Memory inference is always gated: no config closes the gate instead of
79
77
  // taking callStructured's ungated path for direct callers.
80
78
  if (!akmConfig) {
@@ -25,6 +25,7 @@
25
25
  * `undefined` outside any `withLlmStage` scope.
26
26
  */
27
27
  import { AsyncLocalStorage } from "node:async_hooks";
28
+ import { asNonEmptyString } from "../core/common.js";
28
29
  const attributionStorage = new AsyncLocalStorage();
29
30
  let usageSink;
30
31
  /**
@@ -85,9 +86,6 @@ export function emitLlmUsage(record) {
85
86
  function asFiniteNonNegative(value) {
86
87
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
87
88
  }
88
- function asNonEmptyString(value) {
89
- return typeof value === "string" && value.length > 0 ? value : undefined;
90
- }
91
89
  function asLlmUsageErrorCode(value) {
92
90
  switch (value) {
93
91
  case "rate_limited":
@@ -17,6 +17,10 @@ export function createProviderRegistry() {
17
17
  resolve(type) {
18
18
  return map.get(type) ?? null;
19
19
  },
20
+ /** Removes a registration. No-op if `type` was never registered. */
21
+ unregister(type) {
22
+ map.delete(type);
23
+ },
20
24
  /** Snapshot of all registered keys. Iteration order matches insertion order. */
21
25
  list() {
22
26
  return [...map.keys()];
@@ -24,3 +24,7 @@ export function registerRegistryProvider(type, factory) {
24
24
  export function resolveRegistryProviderFactory(type) {
25
25
  return registry.resolve(type);
26
26
  }
27
+ /** Test-only seam: removes a registration made with {@link registerRegistryProvider}. */
28
+ export function unregisterRegistryProvider(type) {
29
+ registry.unregister(type);
30
+ }
@@ -298,14 +298,18 @@ export function trustedNpmTarballHosts() {
298
298
  const hosts = new Set([DEFAULT_NPM_REGISTRY_HOST]);
299
299
  const override = process.env.AKM_NPM_REGISTRY?.trim();
300
300
  if (override) {
301
+ // A malformed override must not be silently ignored (falling back to the
302
+ // public registry as though nothing was configured) — that would install
303
+ // from the wrong registry without telling the operator (R-035).
304
+ let overrideHost;
301
305
  try {
302
- const overrideHost = new URL(override).hostname.toLowerCase();
303
- if (overrideHost)
304
- hosts.add(overrideHost);
306
+ overrideHost = new URL(override).hostname.toLowerCase();
305
307
  }
306
308
  catch {
307
- // Ignore unparseable overrides; the default host still applies.
309
+ throw new UsageError(`AKM_NPM_REGISTRY is set to an invalid URL: ${override}`);
308
310
  }
311
+ if (overrideHost)
312
+ hosts.add(overrideHost);
309
313
  }
310
314
  return hosts;
311
315
  }
@@ -358,14 +362,18 @@ export function npmArtifactNetworkPolicy(artifact) {
358
362
  function npmMetadataRegistry() {
359
363
  const override = process.env.AKM_NPM_REGISTRY?.trim();
360
364
  if (override) {
365
+ // A malformed override must not be silently ignored (falling back to the
366
+ // public registry as though nothing was configured) — that would install
367
+ // from the wrong registry without telling the operator (R-035).
368
+ let url;
361
369
  try {
362
- const url = new URL(override);
363
- const base = `${url.origin}${url.pathname === "/" ? "" : url.pathname}`;
364
- return { baseUrl: base.replace(/\/+$/, ""), allowPrivateRegistryOrigin: true };
370
+ url = new URL(override);
365
371
  }
366
372
  catch {
367
- // Ignore unparseable overrides; fall back to the public registry.
373
+ throw new UsageError(`AKM_NPM_REGISTRY is set to an invalid URL: ${override}`);
368
374
  }
375
+ const base = `${url.origin}${url.pathname === "/" ? "" : url.pathname}`;
376
+ return { baseUrl: base.replace(/\/+$/, ""), allowPrivateRegistryOrigin: true };
369
377
  }
370
378
  return { baseUrl: `https://${DEFAULT_NPM_REGISTRY_HOST}`, allowPrivateRegistryOrigin: false };
371
379
  }
@@ -490,6 +498,13 @@ function resolveGitRevisionFromRemote(url, requestedRef) {
490
498
  }
491
499
  async function resolveGitArtifact(parsed) {
492
500
  const resolvedRevision = resolveGitRevisionFromRemote(parsed.url, parsed.requestedRef);
501
+ if (!resolvedRevision) {
502
+ // Unlike the GitHub path (which falls back to the REST API on a failed
503
+ // `git ls-remote`), a plain git source has no fallback resolver — an
504
+ // unresolved revision here would silently disable the post-clone
505
+ // revision-integrity check in `verifyClonedRevision` (R-011).
506
+ throw new Error(`Unable to resolve ${parsed.requestedRef ?? "HEAD"} for ${parsed.url} via 'git ls-remote'; refusing to install without a verifiable revision.`);
507
+ }
493
508
  return {
494
509
  id: parsed.id,
495
510
  source: parsed.source,
package/dist/runtime.js CHANGED
@@ -250,18 +250,6 @@ export function semverOrder(a, b) {
250
250
  const semver = nodeRequire("semver");
251
251
  return semver.compare(a, b);
252
252
  }
253
- // ── Module resolution ───────────────────────────────────────────────────────
254
- /**
255
- * Resolve a module specifier without loading it, relative to `from`. On Bun
256
- * uses `Bun.resolveSync`; on Node uses `require.resolve`. Throws when the
257
- * specifier cannot be resolved (mirroring both underlying APIs).
258
- */
259
- export function resolveModule(spec, from) {
260
- if (isBun) {
261
- return bunGlobal().resolveSync(spec, from);
262
- }
263
- return nodeRequire.resolve(spec, { paths: [from] });
264
- }
265
253
  // ── Filesystem paths ────────────────────────────────────────────────────────
266
254
  /**
267
255
  * The directory of the module identified by `importMetaUrl`. Replaces