akm-cli 0.9.0-beta.2 → 0.9.0-beta.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 (36) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/dist/assets/templates/html/default.html +78 -0
  3. package/dist/assets/templates/html/health.html +560 -0
  4. package/dist/assets/templates/html/vendor/echarts.min.js +45 -0
  5. package/dist/cli/shared.js +21 -5
  6. package/dist/cli.js +36 -5
  7. package/dist/commands/health/html-report.js +448 -0
  8. package/dist/commands/health.js +97 -6
  9. package/dist/commands/improve/extract.js +38 -2
  10. package/dist/commands/improve/improve-auto-accept.js +27 -1
  11. package/dist/commands/improve/improve.js +167 -53
  12. package/dist/commands/improve/reflect-noise.js +0 -0
  13. package/dist/commands/improve/reflect.js +25 -0
  14. package/dist/commands/proposal/drain.js +73 -6
  15. package/dist/commands/proposal/proposal-cli.js +22 -10
  16. package/dist/commands/proposal/proposal.js +12 -1
  17. package/dist/commands/proposal/validators/proposals.js +361 -338
  18. package/dist/commands/remember.js +6 -2
  19. package/dist/core/config/config-schema.js +5 -0
  20. package/dist/core/logs-db.js +304 -0
  21. package/dist/core/state-db.js +107 -14
  22. package/dist/indexer/db/db.js +2 -2
  23. package/dist/indexer/passes/memory-inference.js +61 -22
  24. package/dist/integrations/harnesses/claude/session-log.js +16 -4
  25. package/dist/llm/client.js +15 -0
  26. package/dist/llm/usage-persist.js +77 -0
  27. package/dist/llm/usage-telemetry.js +103 -0
  28. package/dist/output/context.js +3 -2
  29. package/dist/output/html-render.js +73 -0
  30. package/dist/output/shapes/helpers.js +17 -1
  31. package/dist/output/text/helpers.js +69 -1
  32. package/dist/scripts/migrate-storage.js +65 -14
  33. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +14 -2
  34. package/dist/tasks/runner.js +99 -16
  35. package/dist/workflows/db.js +4 -0
  36. package/package.json +1 -1
@@ -8631,7 +8631,7 @@ function openStateDatabase(dbPath) {
8631
8631
  const db = openDatabase(resolvedPath);
8632
8632
  db.exec("PRAGMA journal_mode = WAL");
8633
8633
  db.exec("PRAGMA foreign_keys = ON");
8634
- db.exec("PRAGMA busy_timeout = 5000");
8634
+ db.exec("PRAGMA busy_timeout = 30000");
8635
8635
  runMigrations2(db);
8636
8636
  return db;
8637
8637
  }
@@ -8706,7 +8706,9 @@ var MIGRATIONS = [
8706
8706
  --
8707
8707
  -- Extensible (metadata_json) columns:
8708
8708
  -- metadata_json TEXT \u2014 JSON object for future proposal fields.
8709
- -- Current fields stored here: sourceRun, review.
8709
+ -- Current fields stored here: sourceRun,
8710
+ -- review, confidence, gateDecision (#577),
8711
+ -- backupContent.
8710
8712
  --
8711
8713
  -- ADD COLUMN extension points (future migrations):
8712
8714
  -- ALTER TABLE proposals ADD COLUMN source_run TEXT DEFAULT NULL;
@@ -8893,6 +8895,16 @@ var MIGRATIONS = [
8893
8895
  CREATE INDEX IF NOT EXISTS idx_extract_sessions_processed
8894
8896
  ON extract_sessions_seen(processed_at);
8895
8897
  `
8898
+ },
8899
+ {
8900
+ id: "005-proposal-fs-imports",
8901
+ up: `
8902
+ CREATE TABLE IF NOT EXISTS proposal_fs_imports (
8903
+ stash_dir TEXT PRIMARY KEY,
8904
+ imported_at TEXT NOT NULL,
8905
+ imported_count INTEGER NOT NULL DEFAULT 0
8906
+ );
8907
+ `
8896
8908
  }
8897
8909
  ];
8898
8910
  function runMigrations2(db) {
@@ -16,7 +16,9 @@
16
16
  * 4. Dispatch by target kind:
17
17
  * • workflow → `startWorkflowRun(ref, params)`
18
18
  * • prompt → `runAgent(profile, prompt, { stdio: "captured" })`
19
- * 5. Capture stdout / stderr to `<cacheDir>/tasks/logs/<id>/<ts>.log`.
19
+ * 5. Capture stdout / stderr as structured rows in logs.db (task_logs) and,
20
+ * transitionally, as a flat text tail at `<cacheDir>/tasks/logs/<id>/<ts>.log`
21
+ * (see docs/technical/logs-audit.md).
20
22
  * 6. Write a history row to state.db task_history table.
21
23
  *
22
24
  * Returns a structured result so the CLI handler can shape it for `output()`
@@ -29,6 +31,7 @@ import { parseAssetRef } from "../core/asset/asset-ref.js";
29
31
  import { resolveStashDir } from "../core/common.js";
30
32
  import { loadConfig } from "../core/config/config.js";
31
33
  import { NotFoundError, rethrowIfTestIsolationError } from "../core/errors.js";
34
+ import { buildTaskRunId, insertTaskLogLines, openLogsDatabase, } from "../core/logs-db.js";
32
35
  import { getTaskLogDir } from "../core/paths.js";
33
36
  import { getTaskHistory, openStateDatabase, queryTaskHistory, upsertTaskHistory } from "../core/state-db.js";
34
37
  import { error } from "../core/warn.js";
@@ -70,7 +73,15 @@ export async function runTask(id, options = {}) {
70
73
  log: logPath,
71
74
  target: disabledTarget,
72
75
  };
73
- fs.writeFileSync(logPath, `[akm tasks] task "${id}" is disabled — skipping run.\n`);
76
+ const disabledLine = `[akm tasks] task "${id}" is disabled — skipping run.`;
77
+ persistRunLog({
78
+ taskId: id,
79
+ startedAtIso: startedIso,
80
+ finishedAtIso: result.finishedAt,
81
+ logPath,
82
+ fileText: `${disabledLine}\n`,
83
+ dbLines: [{ line: disabledLine }],
84
+ });
74
85
  appendHistory(result);
75
86
  return result;
76
87
  }
@@ -108,7 +119,9 @@ async function runCommandTask(input) {
108
119
  throw new Error("invariant: command target");
109
120
  const { cmd } = task.target;
110
121
  const timeoutMs = task.timeoutMs !== undefined ? task.timeoutMs : null;
111
- const logLines = [`[akm tasks] task=${task.id} kind=command cmd=${cmd.join(" ")}`];
122
+ const header = `[akm tasks] task=${task.id} kind=command cmd=${cmd.join(" ")}`;
123
+ const logLines = [header];
124
+ const dbLines = [{ line: header }];
112
125
  let stdout = "";
113
126
  let stderr = "";
114
127
  let exitCode = null;
@@ -144,24 +157,36 @@ async function runCommandTask(input) {
144
157
  exitCode = proc.exitCode ?? (timedOut ? 143 : 1);
145
158
  if (timedOut) {
146
159
  logLines.push(`timed_out=true timeout_ms=${timeoutMs}`);
160
+ dbLines.push({ level: "error", line: `timed_out=true timeout_ms=${timeoutMs}` });
147
161
  }
148
162
  logLines.push(`exit_code=${exitCode}`);
163
+ dbLines.push({ level: exitCode === 0 ? "info" : "error", line: `exit_code=${exitCode}` });
149
164
  if (stdout) {
150
165
  logLines.push("--- stdout ---");
151
166
  logLines.push(stdout);
167
+ dbLines.push(...streamLines(stdout, "stdout", "info"));
152
168
  }
153
169
  if (stderr) {
154
170
  logLines.push("--- stderr ---");
155
171
  logLines.push(stderr);
172
+ dbLines.push(...streamLines(stderr, "stderr", "error"));
156
173
  }
157
174
  }
158
175
  catch (e) {
159
176
  const msg = e instanceof Error ? e.message : String(e);
160
177
  logLines.push(`spawn_error=${msg}`);
178
+ dbLines.push({ level: "error", line: `spawn_error=${msg}` });
161
179
  exitCode = 1;
162
180
  }
163
- fs.writeFileSync(logPath, `${logLines.join("\n")}\n`);
164
181
  const finishedAt = now();
182
+ persistRunLog({
183
+ taskId: task.id,
184
+ startedAtIso: startedAt.toISOString(),
185
+ finishedAtIso: finishedAt.toISOString(),
186
+ logPath,
187
+ fileText: `${logLines.join("\n")}\n`,
188
+ dbLines,
189
+ });
165
190
  const status = exitCode === 0 ? "completed" : "failed";
166
191
  const result = {
167
192
  id: task.id,
@@ -196,7 +221,14 @@ async function runWorkflowTask(input) {
196
221
  const finishedAt = now();
197
222
  const status = error ? "failed" : mapWorkflowStatus(detail?.run.status);
198
223
  const log = renderWorkflowLog({ task, detail, error });
199
- fs.writeFileSync(logPath, log);
224
+ persistRunLog({
225
+ taskId: task.id,
226
+ startedAtIso: startedAt.toISOString(),
227
+ finishedAtIso: finishedAt.toISOString(),
228
+ logPath,
229
+ fileText: log.fileText,
230
+ dbLines: log.dbLines,
231
+ });
200
232
  const result = {
201
233
  id: task.id,
202
234
  status,
@@ -248,16 +280,17 @@ function mapWorkflowStatus(status) {
248
280
  }
249
281
  }
250
282
  function renderWorkflowLog(input) {
251
- const lines = [];
252
- lines.push(`[akm tasks] task=${input.task.id} kind=workflow ref=${input.task.target.ref}`);
283
+ const dbLines = [
284
+ { line: `[akm tasks] task=${input.task.id} kind=workflow ref=${input.task.target.ref}` },
285
+ ];
253
286
  if (input.detail) {
254
- lines.push(`run_id=${input.detail.run.id} status=${input.detail.run.status}`);
255
- lines.push(`workflow_title=${input.detail.run.workflowTitle}`);
287
+ dbLines.push({ line: `run_id=${input.detail.run.id} status=${input.detail.run.status}` });
288
+ dbLines.push({ line: `workflow_title=${input.detail.run.workflowTitle}` });
256
289
  }
257
290
  if (input.error) {
258
- lines.push(`error=${input.error.message}`);
291
+ dbLines.push({ level: "error", line: `error=${input.error.message}` });
259
292
  }
260
- return `${lines.join("\n")}\n`;
293
+ return { fileText: `${dbLines.map((entry) => entry.line).join("\n")}\n`, dbLines };
261
294
  }
262
295
  // ── prompt target ───────────────────────────────────────────────────────────
263
296
  async function runPromptTask(input) {
@@ -321,7 +354,14 @@ async function runPromptTask(input) {
321
354
  });
322
355
  const finishedAt = now();
323
356
  const log = renderPromptLog({ task, profileName: profile.name, result });
324
- fs.writeFileSync(logPath, log);
357
+ persistRunLog({
358
+ taskId: task.id,
359
+ startedAtIso: startedAt.toISOString(),
360
+ finishedAtIso: finishedAt.toISOString(),
361
+ logPath,
362
+ fileText: log.fileText,
363
+ dbLines: log.dbLines,
364
+ });
325
365
  const status = result.ok ? "completed" : "failed";
326
366
  const out = {
327
367
  id: task.id,
@@ -359,20 +399,63 @@ async function resolvePromptText(task, stashDir) {
359
399
  }
360
400
  function renderPromptLog(input) {
361
401
  const lines = [];
362
- lines.push(`[akm tasks] task=${input.task.id} kind=prompt profile=${input.profileName}`);
363
- lines.push(`ok=${input.result.ok} exit_code=${input.result.exitCode ?? "null"} duration_ms=${input.result.durationMs}`);
402
+ const dbLines = [];
403
+ const header = `[akm tasks] task=${input.task.id} kind=prompt profile=${input.profileName}`;
404
+ const summary = `ok=${input.result.ok} exit_code=${input.result.exitCode ?? "null"} duration_ms=${input.result.durationMs}`;
405
+ lines.push(header, summary);
406
+ dbLines.push({ line: header }, { level: input.result.ok ? "info" : "error", line: summary });
364
407
  if (!input.result.ok) {
365
- lines.push(`reason=${input.result.reason ?? ""} error=${input.result.error ?? ""}`);
408
+ const failure = `reason=${input.result.reason ?? ""} error=${input.result.error ?? ""}`;
409
+ lines.push(failure);
410
+ dbLines.push({ level: "error", line: failure });
366
411
  }
367
412
  if (input.result.stdout) {
368
413
  lines.push("--- agent stdout ---");
369
414
  lines.push(input.result.stdout);
415
+ dbLines.push(...streamLines(input.result.stdout, "stdout", "info"));
370
416
  }
371
417
  if (input.result.stderr) {
372
418
  lines.push("--- agent stderr ---");
373
419
  lines.push(input.result.stderr);
420
+ dbLines.push(...streamLines(input.result.stderr, "stderr", "error"));
421
+ }
422
+ return { fileText: `${lines.join("\n")}\n`, dbLines };
423
+ }
424
+ /** Split captured pipe output into per-line logs.db rows (blank lines dropped). */
425
+ function streamLines(text, stream, level) {
426
+ return text
427
+ .split("\n")
428
+ .filter((line) => line.length > 0)
429
+ .map((line) => ({ stream, level, line }));
430
+ }
431
+ /**
432
+ * Persist a finished run's log: the flat text file (so `log_path` in
433
+ * task_history keeps resolving for humans and older consumers) plus
434
+ * structured rows in logs.db keyed by `buildTaskRunId(taskId, startedAt)`.
435
+ *
436
+ * The DB write is best-effort, mirroring {@link appendHistory}: an unwritable
437
+ * logs.db must never fail a task run.
438
+ */
439
+ function persistRunLog(input) {
440
+ fs.writeFileSync(input.logPath, input.fileText);
441
+ try {
442
+ const db = openLogsDatabase();
443
+ try {
444
+ insertTaskLogLines(db, {
445
+ taskId: input.taskId,
446
+ runId: buildTaskRunId(input.taskId, input.startedAtIso),
447
+ ts: input.finishedAtIso,
448
+ lines: input.dbLines,
449
+ });
450
+ }
451
+ finally {
452
+ db.close();
453
+ }
454
+ }
455
+ catch (err) {
456
+ rethrowIfTestIsolationError(err);
457
+ error(`[akm] task log DB write failed: ${String(err)}`);
374
458
  }
375
- return `${lines.join("\n")}\n`;
376
459
  }
377
460
  // ── history ─────────────────────────────────────────────────────────────────
378
461
  function appendHistory(result) {
@@ -47,6 +47,10 @@ export function openWorkflowDatabase(dbPath = getWorkflowDbPath()) {
47
47
  }
48
48
  const db = openDatabase(dbPath);
49
49
  db.exec("PRAGMA journal_mode = WAL");
50
+ // #589: 30 s busy timeout, matching index.db / state.db. Without it the
51
+ // default is 0 ms, so any concurrent writer fails immediately with
52
+ // SQLITE_BUSY.
53
+ db.exec("PRAGMA busy_timeout = 30000");
50
54
  db.exec("PRAGMA foreign_keys = ON");
51
55
  ensureBaseSchema(db);
52
56
  runMigrations(db);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akm-cli",
3
- "version": "0.9.0-beta.2",
3
+ "version": "0.9.0-beta.3",
4
4
  "type": "module",
5
5
  "description": "akm (Agent Knowledge Management) — A package manager for AI agent skills, commands, tools, and knowledge. Works with Claude Code, OpenCode, Cursor, and any AI coding assistant.",
6
6
  "keywords": [