akm-cli 0.7.0-rc1 → 0.7.0

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 (83) hide show
  1. package/dist/src/cli.js +100 -16
  2. package/dist/src/commands/config-cli.js +42 -0
  3. package/dist/src/commands/history.js +78 -7
  4. package/dist/src/commands/registry-search.js +69 -6
  5. package/dist/src/commands/search.js +30 -3
  6. package/dist/src/commands/show.js +29 -0
  7. package/dist/src/commands/source-add.js +5 -1
  8. package/dist/src/commands/source-manage.js +7 -1
  9. package/dist/src/core/config.js +28 -0
  10. package/dist/src/indexer/db-search.js +1 -0
  11. package/dist/src/indexer/indexer.js +16 -2
  12. package/dist/src/indexer/matchers.js +1 -1
  13. package/dist/src/indexer/search-source.js +4 -2
  14. package/dist/src/integrations/agent/profiles.js +1 -1
  15. package/dist/src/integrations/agent/spawn.js +67 -16
  16. package/dist/src/integrations/github.js +9 -3
  17. package/dist/src/llm/embedders/remote.js +37 -3
  18. package/dist/src/output/cli-hints.js +15 -2
  19. package/dist/src/output/renderers.js +3 -1
  20. package/dist/src/output/shapes.js +8 -1
  21. package/dist/src/output/text.js +156 -3
  22. package/dist/src/registry/build-index.js +5 -4
  23. package/dist/src/registry/providers/static-index.js +3 -1
  24. package/dist/src/setup/setup.js +9 -0
  25. package/dist/src/wiki/wiki.js +54 -6
  26. package/dist/src/workflows/runs.js +37 -3
  27. package/dist/tests/architecture/agent-no-llm-sdk-guard.test.js +1 -1
  28. package/dist/tests/bench/attribution.test.js +24 -23
  29. package/dist/tests/bench/cleanup.js +31 -0
  30. package/dist/tests/bench/cli.js +366 -31
  31. package/dist/tests/bench/cli.test.js +282 -14
  32. package/dist/tests/bench/corpus.js +3 -0
  33. package/dist/tests/bench/corpus.test.js +10 -10
  34. package/dist/tests/bench/doctor.js +525 -0
  35. package/dist/tests/bench/driver.js +77 -22
  36. package/dist/tests/bench/driver.test.js +142 -1
  37. package/dist/tests/bench/environment.js +233 -0
  38. package/dist/tests/bench/environment.test.js +199 -0
  39. package/dist/tests/bench/evolve.js +67 -0
  40. package/dist/tests/bench/evolve.test.js +12 -4
  41. package/dist/tests/bench/failure-modes.test.js +52 -3
  42. package/dist/tests/bench/feedback-integrity.test.js +3 -2
  43. package/dist/tests/bench/leakage.test.js +105 -2
  44. package/dist/tests/bench/learning-curve.test.js +3 -2
  45. package/dist/tests/bench/metrics.js +102 -26
  46. package/dist/tests/bench/metrics.test.js +10 -4
  47. package/dist/tests/bench/opencode-config.js +194 -0
  48. package/dist/tests/bench/opencode-config.test.js +370 -0
  49. package/dist/tests/bench/report.js +73 -9
  50. package/dist/tests/bench/report.test.js +59 -10
  51. package/dist/tests/bench/run-config.js +355 -0
  52. package/dist/tests/bench/run-config.test.js +298 -0
  53. package/dist/tests/bench/run-curate-test.js +32 -0
  54. package/dist/tests/bench/run-failing-tasks.js +56 -0
  55. package/dist/tests/bench/run-full-bench.js +51 -0
  56. package/dist/tests/bench/run-items36-targeted.js +69 -0
  57. package/dist/tests/bench/run-nano-quick.js +42 -0
  58. package/dist/tests/bench/run-waveg-targeted.js +62 -0
  59. package/dist/tests/bench/runner.js +257 -94
  60. package/dist/tests/bench/tmp.js +90 -0
  61. package/dist/tests/bench/trajectory.js +2 -2
  62. package/dist/tests/bench/verifier.js +6 -1
  63. package/dist/tests/bench/workflow-spec.js +11 -24
  64. package/dist/tests/bench/workflow-spec.test.js +1 -1
  65. package/dist/tests/bench/workflow-trace.js +34 -0
  66. package/dist/tests/cli-errors.test.js +1 -0
  67. package/dist/tests/commands/history.test.js +195 -0
  68. package/dist/tests/config.test.js +25 -0
  69. package/dist/tests/e2e.test.js +23 -2
  70. package/dist/tests/fixtures/stashes/load.js +1 -1
  71. package/dist/tests/fixtures/stashes/load.test.js +11 -2
  72. package/dist/tests/indexer.test.js +12 -1
  73. package/dist/tests/output-baseline.test.js +2 -1
  74. package/dist/tests/output-shapes-unit.test.js +3 -1
  75. package/dist/tests/registry-build-index.test.js +17 -1
  76. package/dist/tests/registry-providers/static-index.test.js +34 -0
  77. package/dist/tests/registry-search.test.js +200 -0
  78. package/dist/tests/remember-frontmatter.test.js +11 -13
  79. package/dist/tests/source-qa-fixes.test.js +18 -0
  80. package/dist/tests/source-registry.test.js +3 -3
  81. package/dist/tests/source-source.test.js +61 -1
  82. package/dist/tests/workflow-qa-fixes.test.js +18 -0
  83. package/package.json +1 -1
@@ -7,6 +7,7 @@
7
7
  * failure mode we want to verify produces a valid §13.3 envelope.
8
8
  */
9
9
  import { describe, expect, test } from "bun:test";
10
+ import fs from "node:fs";
10
11
  import path from "node:path";
11
12
  const REPO_ROOT = path.resolve(__dirname, "..", "..");
12
13
  const CLI = path.join(REPO_ROOT, "tests", "bench", "cli.ts");
@@ -25,29 +26,60 @@ function run(args, env = {}) {
25
26
  };
26
27
  }
27
28
  describe("bench CLI", () => {
28
- test("`help` subcommand exits 0 and lists the four subcommands", () => {
29
+ test("`help` subcommand exits 0 and lists the five subcommands", () => {
29
30
  const r = run(["help"]);
30
31
  expect(r.exitCode).toBe(0);
31
32
  expect(r.stdout).toContain("utility");
32
33
  expect(r.stdout).toContain("evolve");
33
34
  expect(r.stdout).toContain("compare");
34
35
  expect(r.stdout).toContain("attribute");
36
+ expect(r.stdout).toContain("clean");
35
37
  });
36
- test("utility without BENCH_OPENCODE_MODEL exits 2", () => {
37
- // Strip out any inherited model env so the missing-model branch fires.
38
- const r = run(["utility", "--tasks", "train"], { BENCH_OPENCODE_MODEL: "" });
39
- expect(r.exitCode).toBe(2);
40
- expect(r.stderr).toContain("BENCH_OPENCODE_MODEL");
38
+ test("`clean` subcommand exits 0 and removes bench tmp root", () => {
39
+ const r = run(["clean"]);
40
+ expect(r.exitCode).toBe(0);
41
+ expect(r.stderr).toContain("bench clean:");
42
+ });
43
+ test("utility without BENCH_OPENCODE_MODEL exits 2 when no providers file supplies defaultModel", () => {
44
+ // When BENCH_OPENCODE_MODEL is unset AND the providers file has no
45
+ // defaultModel, exit 2 fires. We supply a minimal no-defaultModel file
46
+ // via --opencode-config to prevent the committed fixture's defaultModel
47
+ // from satisfying the model requirement.
48
+ const tmpDir = fs.mkdtempSync("/tmp/bench-cli-nomodel-test-");
49
+ const noModelPath = path.join(tmpDir, "no-default.json");
50
+ try {
51
+ fs.writeFileSync(noModelPath, JSON.stringify({ schemaVersion: 1, providers: { p: { npm: "@ai-sdk/openai-compatible" } } }));
52
+ const r = run(["utility", "--tasks", "train", "--opencode-config", noModelPath], {
53
+ BENCH_OPENCODE_MODEL: "",
54
+ });
55
+ expect(r.exitCode).toBe(2);
56
+ expect(r.stderr).toContain("BENCH_OPENCODE_MODEL");
57
+ }
58
+ finally {
59
+ fs.rmSync(tmpDir, { recursive: true, force: true });
60
+ }
41
61
  });
42
62
  test("evolve without --tasks exits 2 with usage error", () => {
43
63
  const r = run(["evolve"], { BENCH_OPENCODE_MODEL: "anthropic/claude-opus-4-7" });
44
64
  expect(r.exitCode).toBe(2);
45
65
  expect(r.stderr).toContain("--tasks");
46
66
  });
47
- test("evolve without BENCH_OPENCODE_MODEL exits 2", () => {
48
- const r = run(["evolve", "--tasks", "docker-homelab"], { BENCH_OPENCODE_MODEL: "" });
49
- expect(r.exitCode).toBe(2);
50
- expect(r.stderr).toContain("BENCH_OPENCODE_MODEL");
67
+ test("evolve without BENCH_OPENCODE_MODEL exits 2 when no providers file supplies defaultModel", () => {
68
+ // Same pattern as utility: supply a no-defaultModel providers file so the
69
+ // model-required check fires.
70
+ const tmpDir = fs.mkdtempSync("/tmp/bench-cli-nomodel-evolve-test-");
71
+ const noModelPath = path.join(tmpDir, "no-default.json");
72
+ try {
73
+ fs.writeFileSync(noModelPath, JSON.stringify({ schemaVersion: 1, providers: { p: { npm: "@ai-sdk/openai-compatible" } } }));
74
+ const r = run(["evolve", "--tasks", "docker-homelab", "--opencode-config", noModelPath], {
75
+ BENCH_OPENCODE_MODEL: "",
76
+ });
77
+ expect(r.exitCode).toBe(2);
78
+ expect(r.stderr).toContain("BENCH_OPENCODE_MODEL");
79
+ }
80
+ finally {
81
+ fs.rmSync(tmpDir, { recursive: true, force: true });
82
+ }
51
83
  });
52
84
  test("attribute without --base exits 2", () => {
53
85
  const r = run(["attribute"]);
@@ -113,7 +145,21 @@ describe("bench CLI", () => {
113
145
  expect(r.stderr).toContain("eval");
114
146
  });
115
147
  test("without --json: JSON still goes to stdout, markdown summary goes to stderr", () => {
116
- const r = run(["utility", "--tasks", "train", "--seeds", "1", "--budget-tokens", "1000", "--budget-wall-ms", "1000"], { BENCH_OPENCODE_MODEL: "anthropic/claude-opus-4-7" });
148
+ const r = run(
149
+ // --no-noakm to keep run count at 1 arm (faster), since this test is
150
+ // about the stdout/stderr contract, not the noakm arm.
151
+ [
152
+ "utility",
153
+ "--tasks",
154
+ "train",
155
+ "--seeds",
156
+ "1",
157
+ "--budget-tokens",
158
+ "1000",
159
+ "--budget-wall-ms",
160
+ "1000",
161
+ "--no-noakm",
162
+ ], { BENCH_OPENCODE_MODEL: "anthropic/claude-opus-4-7" });
117
163
  expect(r.exitCode).toBe(0);
118
164
  // stdout MUST be valid JSON (the bench's machine-readable contract).
119
165
  let parsed;
@@ -127,6 +173,44 @@ describe("bench CLI", () => {
127
173
  expect(r.stderr).toContain("## Aggregate");
128
174
  expect(r.stderr).toContain("tasks discovered:");
129
175
  }, 60_000);
176
+ // ── C2: noakm arm now default-on; --no-noakm opts out ────────────────────
177
+ test("utility help mentions --no-noakm flag", () => {
178
+ const r = run(["help"]);
179
+ expect(r.exitCode).toBe(0);
180
+ expect(r.stdout).toContain("--no-noakm");
181
+ });
182
+ test("utility default: aggregate includes noakm arm", () => {
183
+ const r = run(["utility", "--tasks", "train", "--seeds", "1", "--budget-tokens", "1000", "--budget-wall-ms", "1000", "--json"], { BENCH_OPENCODE_MODEL: "anthropic/claude-opus-4-7" });
184
+ expect(r.exitCode).toBe(0);
185
+ const parsed = JSON.parse(r.stdout);
186
+ const aggregate = parsed.aggregate;
187
+ expect(aggregate.noakm).toBeDefined();
188
+ expect(aggregate.akm).toBeDefined();
189
+ expect(aggregate.delta).toBeDefined();
190
+ }, 60_000);
191
+ test("utility --no-noakm: envelope is valid and contains akm arm", () => {
192
+ // When --no-noakm is passed the noakm arm does not run. The JSON envelope
193
+ // is still valid (aggregate.noakm exists but reflects zero runs); akm
194
+ // is present with real run data. Exit 0.
195
+ const r = run([
196
+ "utility",
197
+ "--tasks",
198
+ "train",
199
+ "--seeds",
200
+ "1",
201
+ "--budget-tokens",
202
+ "1000",
203
+ "--budget-wall-ms",
204
+ "1000",
205
+ "--no-noakm",
206
+ "--json",
207
+ ], { BENCH_OPENCODE_MODEL: "anthropic/claude-opus-4-7" });
208
+ expect(r.exitCode).toBe(0);
209
+ const parsed = JSON.parse(r.stdout);
210
+ expect(parsed.schemaVersion).toBe(1);
211
+ const aggregate = parsed.aggregate;
212
+ expect(aggregate.akm).toBeDefined();
213
+ }, 60_000);
130
214
  // ── #261: --include-synthetic flag ─────────────────────────────────────────
131
215
  test("utility help mentions --include-synthetic flag", () => {
132
216
  const r = run(["help"]);
@@ -144,6 +228,7 @@ describe("bench CLI", () => {
144
228
  "1000",
145
229
  "--budget-wall-ms",
146
230
  "1000",
231
+ "--no-noakm", // isolate: test is about the synthetic arm, not noakm
147
232
  "--include-synthetic",
148
233
  "--json",
149
234
  ], { BENCH_OPENCODE_MODEL: "anthropic/claude-opus-4-7" });
@@ -155,8 +240,20 @@ describe("bench CLI", () => {
155
240
  }, 60_000);
156
241
  test("utility WITHOUT --include-synthetic: aggregate has no synthetic / akm_over_synthetic_lift", () => {
157
242
  // Byte-identical default contract: no spurious 'synthetic' keys when the
158
- // flag is absent.
159
- const r = run(["utility", "--tasks", "train", "--seeds", "1", "--budget-tokens", "1000", "--budget-wall-ms", "1000", "--json"], { BENCH_OPENCODE_MODEL: "anthropic/claude-opus-4-7" });
243
+ // flag is absent. Use --no-noakm for speed — this test is about synthetic, not noakm.
244
+ const r = run([
245
+ "utility",
246
+ "--tasks",
247
+ "train",
248
+ "--seeds",
249
+ "1",
250
+ "--budget-tokens",
251
+ "1000",
252
+ "--budget-wall-ms",
253
+ "1000",
254
+ "--no-noakm",
255
+ "--json",
256
+ ], { BENCH_OPENCODE_MODEL: "anthropic/claude-opus-4-7" });
160
257
  expect(r.exitCode).toBe(0);
161
258
  const parsed = JSON.parse(r.stdout);
162
259
  const aggregate = parsed.aggregate;
@@ -164,7 +261,21 @@ describe("bench CLI", () => {
164
261
  expect("akm_over_synthetic_lift" in aggregate).toBe(false);
165
262
  }, 60_000);
166
263
  test("with --json: stderr carries no markdown summary", () => {
167
- const r = run(["utility", "--tasks", "train", "--seeds", "1", "--budget-tokens", "1000", "--budget-wall-ms", "1000", "--json"], { BENCH_OPENCODE_MODEL: "anthropic/claude-opus-4-7" });
264
+ const r = run(
265
+ // --no-noakm for speed — this test is about the json/stderr contract, not noakm.
266
+ [
267
+ "utility",
268
+ "--tasks",
269
+ "train",
270
+ "--seeds",
271
+ "1",
272
+ "--budget-tokens",
273
+ "1000",
274
+ "--budget-wall-ms",
275
+ "1000",
276
+ "--no-noakm",
277
+ "--json",
278
+ ], { BENCH_OPENCODE_MODEL: "anthropic/claude-opus-4-7" });
168
279
  expect(r.exitCode).toBe(0);
169
280
  // stdout is still JSON.
170
281
  expect(() => JSON.parse(r.stdout)).not.toThrow();
@@ -174,4 +285,161 @@ describe("bench CLI", () => {
174
285
  // The minor trace line is fine.
175
286
  expect(r.stderr).toContain("tasks discovered:");
176
287
  }, 60_000);
288
+ // ── --opencode-config tests ───────────────────────────────────────────────
289
+ test("utility help mentions --opencode-config and BENCH_OPENCODE_CONFIG", () => {
290
+ const r = run(["help"]);
291
+ expect(r.exitCode).toBe(0);
292
+ expect(r.stdout).toContain("--opencode-config");
293
+ expect(r.stdout).toContain("BENCH_OPENCODE_CONFIG");
294
+ });
295
+ test("--opencode-config <missing path> exits 2 (usage error — file not found)", () => {
296
+ const r = run(["utility", "--tasks", "train", "--opencode-config", "/nonexistent/providers.json"], {
297
+ BENCH_OPENCODE_MODEL: "test-model",
298
+ });
299
+ expect(r.exitCode).toBe(2);
300
+ expect(r.stderr).toContain("not found");
301
+ });
302
+ test("--opencode-config <invalid JSON file> exits 78 (config error)", () => {
303
+ // Write a temp file with bad JSON then pass its path.
304
+ const tmpDir = fs.mkdtempSync("/tmp/bench-cli-test-");
305
+ const badJsonPath = path.join(tmpDir, "bad.json");
306
+ try {
307
+ fs.writeFileSync(badJsonPath, "{ this is not valid json }");
308
+ const r = run(["utility", "--tasks", "train", "--opencode-config", badJsonPath], {
309
+ BENCH_OPENCODE_MODEL: "test-model",
310
+ });
311
+ expect(r.exitCode).toBe(78);
312
+ expect(r.stderr).toContain("JSON parse error");
313
+ }
314
+ finally {
315
+ fs.rmSync(tmpDir, { recursive: true, force: true });
316
+ }
317
+ });
318
+ test("BENCH_OPENCODE_CONFIG env var pointing to missing file exits 2", () => {
319
+ const r = run(["utility", "--tasks", "train"], {
320
+ BENCH_OPENCODE_MODEL: "test-model",
321
+ BENCH_OPENCODE_CONFIG: "/nonexistent/path.json",
322
+ });
323
+ expect(r.exitCode).toBe(2);
324
+ });
325
+ test("auto-discovers committed fixture when no flag or env var is set", () => {
326
+ // The committed fixture exists at tests/fixtures/bench/opencode-providers.json.
327
+ // With no --opencode-config and no BENCH_OPENCODE_CONFIG, the CLI should
328
+ // auto-discover it and NOT fail on provider loading. The model must match
329
+ // the fixture's provider prefix or we'd get a materialise error at runtime;
330
+ // but here we use a tiny budget so it all goes through harness_error anyway.
331
+ // The point is: exit code 0 (not 78) when the default fixture is auto-discovered.
332
+ const r = run(["utility", "--tasks", "train", "--seeds", "1", "--budget-tokens", "100", "--budget-wall-ms", "100", "--json"], {
333
+ BENCH_OPENCODE_MODEL: "anthropic/claude-opus-4-7",
334
+ BENCH_OPENCODE_CONFIG: "", // blank — let auto-discovery run
335
+ });
336
+ // Should exit 0 — the auto-discovered fixture is valid.
337
+ expect(r.exitCode).toBe(0);
338
+ }, 60_000);
339
+ });
340
+ describe("bench CLI — config-file dispatch", () => {
341
+ test("config-file dispatch loads tests/bench/configs/nano-quick.json and runs end-to-end", () => {
342
+ // Same structure as the legacy auto-discovery test above — the in-tree
343
+ // committed fixture supplies providers so this works without
344
+ // BENCH_OPENCODE_CONFIG set. We constrain to one task with the --tasks
345
+ // override and shrink the budget so failures terminate quickly.
346
+ const r = run([
347
+ "tests/bench/configs/nano-quick.json",
348
+ "--tasks",
349
+ "drillbit/backup-policy",
350
+ "--seeds",
351
+ "1",
352
+ "--parallel",
353
+ "1",
354
+ "--json",
355
+ ], {
356
+ BENCH_OPENCODE_MODEL: "anthropic/claude-opus-4-7",
357
+ });
358
+ expect(r.exitCode).toBe(0);
359
+ // JSON envelope carries the corpus block and the per-task array.
360
+ const envelope = JSON.parse(r.stdout);
361
+ expect(envelope.corpus).toBeDefined();
362
+ expect(Array.isArray(envelope.tasks)).toBe(true);
363
+ // Stderr trace line confirms the config-mode dispatch ran.
364
+ expect(r.stderr).toContain("config=nano-quick");
365
+ // No obsolete warnings — the new path doesn't trip them.
366
+ expect(r.stderr).not.toContain("[obsolete]");
367
+ }, 60_000);
368
+ test("config-file dispatch surfaces baseline_by_task_id when the config carries `baseline`", () => {
369
+ const r = run([
370
+ "tests/bench/configs/failing-tasks.json",
371
+ "--tasks",
372
+ "drillbit/backup-policy",
373
+ "--seeds",
374
+ "1",
375
+ "--parallel",
376
+ "1",
377
+ "--json",
378
+ ], {
379
+ BENCH_OPENCODE_MODEL: "anthropic/claude-opus-4-7",
380
+ });
381
+ expect(r.exitCode).toBe(0);
382
+ const envelope = JSON.parse(r.stdout);
383
+ expect(envelope.baseline_by_task_id).toBeDefined();
384
+ expect(typeof envelope.baseline_by_task_id["drillbit/backup-policy"]).toBe("number");
385
+ }, 60_000);
386
+ test("config-file dispatch with bogus --tasks override exits 2", () => {
387
+ const r = run(["tests/bench/configs/nano-quick.json", "--tasks", "drillbit/no-such-task", "--seeds", "1", "--json"], {
388
+ BENCH_OPENCODE_MODEL: "anthropic/claude-opus-4-7",
389
+ });
390
+ expect(r.exitCode).toBe(2);
391
+ expect(r.stderr).toContain("--tasks");
392
+ });
393
+ test("nonexistent config file falls through to subcommand parser", () => {
394
+ // A path that doesn't exist isn't routed to config mode — the subcommand
395
+ // parser sees an unknown name and exits 2.
396
+ const r = run(["does-not-exist.json"], {
397
+ BENCH_OPENCODE_MODEL: "anthropic/claude-opus-4-7",
398
+ });
399
+ expect(r.exitCode).toBe(2);
400
+ expect(r.stderr).toContain("unknown subcommand");
401
+ });
402
+ });
403
+ describe("bench CLI — obsolete flag warnings", () => {
404
+ test("--no-noakm in subcommand mode emits exactly one [obsolete] line for that flag", () => {
405
+ const r = run([
406
+ "utility",
407
+ "--tasks",
408
+ "train",
409
+ "--seeds",
410
+ "1",
411
+ "--budget-tokens",
412
+ "100",
413
+ "--budget-wall-ms",
414
+ "100",
415
+ "--no-noakm",
416
+ "--json",
417
+ ], { BENCH_OPENCODE_MODEL: "anthropic/claude-opus-4-7" });
418
+ expect(r.exitCode).toBe(0);
419
+ // The invocation also uses --budget-tokens / --budget-wall-ms which fire
420
+ // their own obsolete warnings; this test only asserts that --no-noakm
421
+ // emits exactly one line and is deduped.
422
+ const noNoakmLines = r.stderr.split("\n").filter((l) => l.includes("[obsolete] --no-noakm"));
423
+ expect(noNoakmLines.length).toBe(1);
424
+ }, 60_000);
425
+ test("--budget-tokens emits an [obsolete] warning that points at budgetTokens", () => {
426
+ const r = run(["utility", "--tasks", "train", "--seeds", "1", "--budget-tokens", "100", "--budget-wall-ms", "100", "--json"], { BENCH_OPENCODE_MODEL: "anthropic/claude-opus-4-7" });
427
+ expect(r.exitCode).toBe(0);
428
+ expect(r.stderr).toContain("[obsolete] --budget-tokens");
429
+ expect(r.stderr).toContain("budgetTokens");
430
+ }, 60_000);
431
+ test("config-file dispatch never emits obsolete warnings", () => {
432
+ const r = run([
433
+ "tests/bench/configs/nano-quick.json",
434
+ "--tasks",
435
+ "drillbit/backup-policy",
436
+ "--seeds",
437
+ "1",
438
+ "--parallel",
439
+ "1",
440
+ "--json",
441
+ ], { BENCH_OPENCODE_MODEL: "anthropic/claude-opus-4-7" });
442
+ expect(r.exitCode).toBe(0);
443
+ expect(r.stderr).not.toContain("[obsolete]");
444
+ }, 60_000);
177
445
  });
@@ -277,6 +277,9 @@ export function parseTaskYaml(text, taskDir) {
277
277
  const staleGuidanceCase = asBoolean(raw.stale_guidance_case);
278
278
  if (staleGuidanceCase !== undefined)
279
279
  meta.staleGuidanceCase = staleGuidanceCase;
280
+ const akmKeywords = asString(raw.akm_keywords);
281
+ if (akmKeywords !== undefined)
282
+ meta.akmKeywords = akmKeywords;
280
283
  return meta;
281
284
  }
282
285
  function asBoolean(value) {
@@ -37,22 +37,22 @@ describe("listTasks", () => {
37
37
  expect(sample?.budget.tokens).toBe(1000);
38
38
  expect(sample?.budget.wallMs).toBe(30_000);
39
39
  });
40
- test("seeds 23 hand-authored tasks across four domains (issues #237, #259)", () => {
40
+ test("seeds hand-authored tasks across all domains", () => {
41
41
  const tasks = listTasks();
42
- expect(tasks).toHaveLength(23);
42
+ expect(tasks).toHaveLength(40);
43
43
  const byDomain = new Map();
44
44
  for (const task of tasks) {
45
45
  const list = byDomain.get(task.domain) ?? [];
46
46
  list.push(task);
47
47
  byDomain.set(task.domain, list);
48
48
  }
49
- expect(new Set(byDomain.keys())).toEqual(new Set(["docker-homelab", "az-cli", "opencode", "workflow-compliance"]));
49
+ expect(new Set(byDomain.keys())).toEqual(new Set(["docker-homelab", "az-cli", "opencode", "workflow-compliance", "drillbit", "inkwell"]));
50
50
  expect(byDomain.get("docker-homelab")).toHaveLength(6);
51
51
  expect(byDomain.get("az-cli")).toHaveLength(6);
52
- expect(byDomain.get("opencode")).toHaveLength(5);
53
- // Workflow-compliance domain (#259): one task per failure category,
54
- // plus the matched pair for repeated-failure-reflection-trigger.
52
+ expect(byDomain.get("opencode")).toHaveLength(6);
55
53
  expect(byDomain.get("workflow-compliance")).toHaveLength(6);
54
+ expect(byDomain.get("drillbit")).toHaveLength(7);
55
+ expect(byDomain.get("inkwell")).toHaveLength(9);
56
56
  });
57
57
  test("every task validates against the §13.1 schema", () => {
58
58
  const ID_RE = /^[a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*$/;
@@ -76,10 +76,10 @@ describe("listTasks", () => {
76
76
  const evalTasks = listTasks({ slice: "eval" });
77
77
  expect(train.every((t) => t.slice === "train")).toBe(true);
78
78
  expect(evalTasks.every((t) => t.slice === "eval")).toBe(true);
79
- // The seeded corpus is split 13 train / 10 eval after the
80
- // workflow-compliance tasks landed (#259 added 4 train + 2 eval).
81
- expect(train).toHaveLength(13);
82
- expect(evalTasks).toHaveLength(10);
79
+ // 23 train (19 + 2 drillbit train + 2 inkwell train)
80
+ // 17 eval (15 prior + 2 new: inkwell/full-config + opencode/select-correct-skill)
81
+ expect(train).toHaveLength(23);
82
+ expect(evalTasks).toHaveLength(17);
83
83
  });
84
84
  });
85
85
  describe("loadTask", () => {