akm-cli 0.10.261016-beta → 0.10.261017-beta

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 (104) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/README.md +8 -10
  3. package/SECURITY.md +7 -99
  4. package/STABILITY.md +57 -98
  5. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +2 -2
  6. package/dist/assets/stash-skeleton/facts/conventions/organization.md +11 -17
  7. package/dist/cli.js +32 -41
  8. package/dist/commands/feedback-cli.js +5 -4
  9. package/dist/commands/health.js +15 -16
  10. package/dist/commands/proposal/proposal-cli.js +1 -1
  11. package/dist/commands/read/registry-search.js +4 -1
  12. package/dist/commands/read/search.js +6 -6
  13. package/dist/commands/sources/self-update.js +9 -5
  14. package/dist/commands/tasks/tasks.js +12 -13
  15. package/dist/core/adapter/adapters/agent-skills-adapter.js +7 -7
  16. package/dist/core/adapter/adapters/akm-task-adapter.js +1 -1
  17. package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
  18. package/dist/core/adapter/adapters/claude-adapter.js +1 -1
  19. package/dist/core/adapter/adapters/dotenv-adapter.js +1 -1
  20. package/dist/core/adapter/adapters/generic-files-adapter.js +1 -1
  21. package/dist/core/adapter/adapters/llm-wiki-adapter.js +1 -1
  22. package/dist/core/adapter/adapters/opencode-adapter.js +1 -1
  23. package/dist/core/adapter/adapters/website-snapshot-adapter.js +1 -1
  24. package/dist/core/bundle-rename.js +17 -7
  25. package/dist/indexer/bundle-identity-guard.js +6 -6
  26. package/dist/indexer/index-written-assets.js +3 -3
  27. package/dist/indexer/indexer.js +12 -8
  28. package/dist/indexer/materialize-embeddings.js +12 -2
  29. package/dist/indexer/passes/metadata.js +103 -46
  30. package/dist/indexer/search/ranking.js +7 -3
  31. package/dist/indexer/search/search-fields.js +1 -1
  32. package/dist/indexer/usage/usage-events.js +30 -8
  33. package/dist/indexer/walk/walker.js +1 -1
  34. package/dist/output/shapes/helpers.js +3 -3
  35. package/dist/output/text/command-format.js +5 -6
  36. package/dist/scripts/akm-migrate-node.js +31 -3
  37. package/dist/scripts/akm-migrate.js +31 -3
  38. package/dist/sources/providers/git-provider.js +2 -4
  39. package/dist/sources/providers/provider-utils.js +8 -13
  40. package/dist/sources/snapshot-fetchers/website-ingest.js +6 -7
  41. package/dist/storage/repositories/events-repository.js +15 -0
  42. package/dist/storage/repositories/improve-ledger-repository.js +13 -0
  43. package/dist/storage/repositories/index-entries-repository.js +43 -5
  44. package/dist/storage/repositories/outcome-repository.js +12 -0
  45. package/dist/storage/repositories/proposals-repository.js +5 -5
  46. package/dist/storage/repositories/salience-repository.js +13 -0
  47. package/dist/storage/repositories/task-history-repository.js +8 -7
  48. package/dist/storage/repositories/workflow-runs-repository.js +10 -8
  49. package/docs/README.md +24 -60
  50. package/docs/agents/README.md +3 -8
  51. package/docs/architecture/README.md +18 -20
  52. package/docs/architecture/adapters.md +40 -43
  53. package/docs/architecture/architecture.md +171 -151
  54. package/docs/architecture/improvement.md +95 -91
  55. package/docs/architecture/internals/classification.md +101 -119
  56. package/docs/architecture/internals/fresh-host-rebuild-runbook.md +33 -35
  57. package/docs/architecture/internals/health-advisories.md +59 -46
  58. package/docs/architecture/internals/improve-workflow.md +166 -693
  59. package/docs/architecture/internals/indexing.md +65 -465
  60. package/docs/architecture/internals/storage-locations.md +301 -848
  61. package/docs/architecture/persisted-data-compat.md +6 -6
  62. package/docs/architecture/specs/okf-support.md +67 -95
  63. package/docs/architecture/specs/ref.md +67 -125
  64. package/docs/architecture/testing/testing-workflow.md +107 -167
  65. package/docs/architecture/workflow-engine.md +34 -33
  66. package/docs/guides/README.md +5 -5
  67. package/docs/guides/author-bundles.md +72 -98
  68. package/docs/guides/author-workflows.md +11 -18
  69. package/docs/guides/bundles.md +91 -123
  70. package/docs/guides/capture-knowledge.md +10 -9
  71. package/docs/guides/concepts.md +45 -30
  72. package/docs/guides/discover-and-load.md +39 -32
  73. package/docs/guides/getting-started.md +24 -24
  74. package/docs/guides/improve-the-library.md +98 -80
  75. package/docs/guides/recipes/headless-install.md +4 -4
  76. package/docs/guides/recipes/website-source.md +9 -5
  77. package/docs/guides/run-workflows.md +10 -11
  78. package/docs/guides/scheduling.md +11 -17
  79. package/docs/guides/use-with-any-agent.md +15 -14
  80. package/docs/guides/wikis.md +6 -9
  81. package/docs/integration/bundling-akm.md +21 -26
  82. package/docs/maintainers/README.md +2 -2
  83. package/docs/maintainers/local-development.md +3 -16
  84. package/docs/migration/README.md +2 -1
  85. package/docs/migration/release-notes/0.10.0.md +167 -22
  86. package/docs/migration/release-notes/README.md +2 -2
  87. package/docs/migration/v0.9-to-v0.10.md +47 -7
  88. package/docs/product-surface.md +10 -8
  89. package/docs/reference/README.md +26 -15
  90. package/docs/reference/asset-types.md +11 -12
  91. package/docs/reference/bundle-types.md +6 -5
  92. package/docs/reference/cli.md +52 -28
  93. package/docs/reference/configuration.md +46 -60
  94. package/docs/reference/data-and-telemetry.md +204 -421
  95. package/docs/reference/env-and-secrets.md +80 -83
  96. package/docs/reference/memory.md +1 -1
  97. package/docs/reference/refs.md +23 -36
  98. package/docs/reference/registry.md +138 -348
  99. package/docs/reference/supported-formats.md +8 -8
  100. package/docs/reference/tasks.md +13 -19
  101. package/docs/reference/website-sources.md +23 -15
  102. package/docs/reference/workflow-schema.md +28 -47
  103. package/docs/reference/workflows.md +33 -37
  104. package/package.json +1 -1
@@ -23,11 +23,10 @@ path is therefore the one facet you **cannot express twice and cannot change
23
23
  without breaking the ref** — so spend it deliberately, on exactly one axis.
24
24
 
25
25
  Retrieval is search, not browse: no one walks these folders at query time. A
26
- subdirectory buys you three things: its tokens are indexed as part of the
27
- asset's **name** (the highest-weighted search field), they are auto-added to
28
- `tags` (even when you set `tags` explicitly), and for scope-born types a slug
29
- matching the current repo's name earns an automatic in-project ranking boost.
30
- Every other facet belongs in frontmatter, where the index can actually read it.
26
+ subdirectory buys you two things: its tokens are indexed as part of the
27
+ asset's **name**, and they are auto-added to `tags` (even when you set `tags`
28
+ explicitly). Search weighs every indexed field equally. Every other facet
29
+ belongs in frontmatter, where the index can actually read it.
31
30
 
32
31
  ## Choose the partition axis by asset TYPE, not per-asset judgment
33
32
 
@@ -38,9 +37,8 @@ differently and bake the wrong guess into an immutable ref. Decide by type:
38
37
  `memory`, `lesson`, `task`, `env`, `secret`. These are born bound to the work
39
38
  in front of you, so the working context *is* the answer — no judgment needed.
40
39
  When the scope is a single repo, use its repo/package name as the slug (as
41
- `git remote`/package.json spell it) — ranking auto-boosts assets whose name
42
- or tags match the current repo. Client/team slugs get no such boost; just
43
- reuse the existing spelling.
40
+ `git remote`/package.json spell it). For a client or team slug, reuse the
41
+ existing spelling.
44
42
  - `memories/projectA/auth-token-refresh`
45
43
  - `lessons/clientX/migration-rollback-gotcha`
46
44
  - `secrets/clientX/api-key`
@@ -58,11 +56,8 @@ differently and bake the wrong guess into an immutable ref. Decide by type:
58
56
  the type root — if any single project would break when it changes, it is
59
57
  scope-born.
60
58
 
61
- **Why reuse types don't take the project axis:** project relevance is already
62
- recovered at query time — AKM blends a per-project usage signal into ranking
63
- (scoped utility, keyed off the current repo, not the asset's path). Free the
64
- scarce path segment for the domain — the *only* handle that co-locates
65
- cross-project reuse.
59
+ **Why reuse types don't take the project axis:** free the scarce path segment
60
+ for the domain — the *only* handle that co-locates cross-project reuse.
66
61
 
67
62
  ## Placement rules
68
63
 
@@ -86,16 +81,15 @@ cross-project reuse.
86
81
  (`acme` vs `acme-corp`) so the prefix does not fragment. Keep the domain
87
82
  vocabulary in `facts/conventions/domains`.
88
83
  - **Off-axis facets go in `tags:`, not a bare field.** The indexed FTS fields are
89
- name, description, tags, hints, and bounded low-weight body content — there is **no `project`
84
+ name, description, tags, hints, and bounded body content — there is **no `project`
90
85
  field**, so `project: projectA` in
91
86
  frontmatter is invisible to search. Put the off-axis facet in `tags` instead
92
87
  (a project-scoped memory adds `tags: [auth]`; a domain-scoped asset genuinely
93
88
  tied to a project adds `tags: [projectA]` sparingly).
94
89
  - **Directory tokens join `tags` on their own.** The scope/domain segments of
95
90
  the path are always auto-added to `tags` — even when you set `tags`
96
- explicitly — so there is no need to restate them; the tag-match ranking
97
- boost fires for the scope token either way. Filename tokens are auto-added
98
- only when `tags` is empty.
91
+ explicitly — so there is no need to restate them. Filename tokens are
92
+ auto-added only when `tags` is empty.
99
93
 
100
94
  ## Renames and evolution
101
95
 
package/dist/cli.js CHANGED
@@ -112,9 +112,6 @@ function applyEarlyStderrFlags(argv) {
112
112
  setVerbose(true);
113
113
  }
114
114
  }
115
- // resolveHelpMigrateVersionArg moved to ./cli/invocation (chunk-9 WI-9.9
116
- // argv-normalization fold — it re-scanned process.argv, same as
117
- // findCittyTopLevelCommand and parseAllFlagValues below).
118
115
  /**
119
116
  * Stderr-only human-friendly hint after a non-interactive `setup` invocation.
120
117
  * Default --format is `json`, so a CI or piped consumer sees only the JSON on
@@ -155,10 +152,9 @@ const setupCommand = defineCommand({
155
152
  description: "Interactive configuration wizard. Enables agent programs (Claude Code, Codex, OpenCode, ...), sets up a model connection and embeddings, and picks bundle sources and registries. Flags answer individual questions; --yes takes the detected default for the rest, for scripting.",
156
153
  },
157
154
  args: {
158
- // R-051/S11: `setup` is a raw `defineCommand` (not `defineJsonCommand`),
159
- // so it does not get `GLOBAL_OUTPUT_ARGS` for free — same gap `health`
160
- // already had. Three of its four run branches call `output()`; spreading
161
- // this in is a `--help` visibility fix, not a behavior change.
155
+ // `setup` is a raw `defineCommand` (not `defineJsonCommand`), so it does
156
+ // not get `GLOBAL_OUTPUT_ARGS` for free. Three of its four run branches
157
+ // call `output()`; spreading this in is a `--help` visibility fix.
162
158
  ...GLOBAL_OUTPUT_ARGS,
163
159
  endpoint: {
164
160
  type: "string",
@@ -285,11 +281,11 @@ const healthCommand = defineCommand({
285
281
  description: "Check akm runtime health, artifacts, and improve metrics",
286
282
  },
287
283
  args: {
288
- // R-051: `health` is a raw `defineCommand` (not `defineJsonCommand`), so
289
- // it does not get `GLOBAL_OUTPUT_ARGS` for free. `--format`/`--detail`/
290
- // `--output` already parsed correctly here (this command has
284
+ // `health` is a raw `defineCommand` (not `defineJsonCommand`), so it does
285
+ // not get `GLOBAL_OUTPUT_ARGS` for free. `--format`/`--detail`/
286
+ // `--output` parse correctly here regardless (this command has
291
287
  // no positional for a stray value to fall into), so this is purely a
292
- // `--help` visibility / consistency fix, not a behavior change.
288
+ // `--help` visibility / consistency measure.
293
289
  ...GLOBAL_OUTPUT_ARGS,
294
290
  since: {
295
291
  type: "string",
@@ -389,8 +385,8 @@ const healthCommand = defineCommand({
389
385
  }
390
386
  output("health", base);
391
387
  });
392
- // R-067: `emitJsonError` (src/cli/shared.ts) no longer force-exits on the
393
- // error path — it sets `process.exitCode` and returns, so a `--report`
388
+ // `emitJsonError` (src/cli/shared.ts) does not force-exit on the error
389
+ // path — it sets `process.exitCode` and returns, so a `--report`
394
390
  // failure thrown AFTER `resultStatus` was already assigned (e.g. the
395
391
  // proposal-queue read above) would otherwise leave `resultStatus`
396
392
  // populated here too. Skip the status-derived exit entirely once
@@ -443,14 +439,12 @@ const completionsCommand = defineCommand({
443
439
  },
444
440
  },
445
441
  run({ args }) {
446
- // R-052(b): this was a bare `run()` throwing directly, so an unsupported
447
- // `--shell` value escaped straight to citty's top-level error handling
448
- // instead of the standard JSON envelope — exit 1 with a raw stack trace
449
- // instead of the classified exit-2 usage error every other command
450
- // produces (`completions` is format-exempt, so it stays a raw
451
- // `defineCommand` rather than `defineJsonCommand`, but still needs the
452
- // same error-classification wrapper other bare `defineCommand`s in this
453
- // file use, e.g. `help migrate` below).
442
+ // An unsupported `--shell` value must reach the standard JSON envelope as
443
+ // the classified exit-2 usage error, not escape to citty's top-level error
444
+ // handling as a raw stack trace (`completions` is format-exempt, so it
445
+ // stays a raw `defineCommand` rather than `defineJsonCommand`, but still
446
+ // needs the same error-classification wrapper other bare `defineCommand`s
447
+ // in this file use, e.g. `help migrate` below).
454
448
  return runWithJsonErrors(() => {
455
449
  if (args.shell !== "bash") {
456
450
  throw new UsageError(`Unsupported shell: ${args.shell}. Only bash is supported.`);
@@ -678,7 +672,7 @@ const EXIT_GENERAL = EXIT_CODES.GENERAL;
678
672
  const EXIT_HEALTH_WARN = EXIT_CODES.HEALTH_WARN;
679
673
  // ── Top-level driver (replaces citty's `runMain`) ───────────────────────────
680
674
  //
681
- // R-032: citty's own `runMain` catches EVERY error escaping `runCommand` —
675
+ // citty's own `runMain` catches EVERY error escaping `runCommand` —
682
676
  // including its unexported `CLIError`, thrown for "Unknown command …", "No
683
677
  // command specified.", "Missing required argument/positional …", and invalid
684
678
  // enum values — and unconditionally calls `process.exit(1)`, regardless of
@@ -692,7 +686,7 @@ const EXIT_HEALTH_WARN = EXIT_CODES.HEALTH_WARN;
692
686
  // of `runMain`, replicating `runMain`'s `--help` / `--version`
693
687
  // short-circuits and its CLIError → usage-banner rendering, but classifying
694
688
  // a CLIError as USAGE (2) instead of GENERAL (1). Every other error escaping
695
- // this boundary keeps the previous GENERAL (1) mapping — this only
689
+ // this boundary keeps the GENERAL (1) mapping — this only
696
690
  // reclassifies the one error family citty itself throws before any of our
697
691
  // own command bodies (and their `runWithJsonErrors` / `emitJsonError`
698
692
  // classification) ever run.
@@ -762,7 +756,7 @@ function resolveCittyCommandPath(cmd, rawArgs, path = []) {
762
756
  * dropping the `akm ` root prefix every top-level command's `--help` already
763
757
  * shows. Building a synthetic parent whose `meta.name` is the full prefix
764
758
  * (`akm task`) fixes it for any depth without patching the vendored
765
- * dependency (S11 item 4).
759
+ * dependency.
766
760
  */
767
761
  function buildUsageParentForPath(path) {
768
762
  // `version` carries through too — citty's renderUsage falls back to
@@ -776,7 +770,7 @@ function buildUsageParentForPath(path) {
776
770
  },
777
771
  };
778
772
  }
779
- // ── Sectioned root help (S11) ────────────────────────────────────────────────
773
+ // ── Sectioned root help ────────────────────────────────────────────────
780
774
  //
781
775
  // citty's own generated COMMANDS list is a flat, unordered dump of every
782
776
  // top-level command — fine for a handful of commands, not for the ~28 this
@@ -853,7 +847,7 @@ function renderCommandSections() {
853
847
  return HELP_SECTIONS.map(({ title, commands }) => `${title}\n${formatCommandRows(commands)}`).join("\n\n");
854
848
  }
855
849
  /**
856
- * Sectioned replacement for citty's root COMMANDS list (S11 item 1). Reuses
850
+ * Sectioned replacement for citty's root COMMANDS list. Reuses
857
851
  * `renderUsage(main)` for the banner + exit-code table (kept verbatim) +
858
852
  * USAGE line + global OPTIONS, then cuts the string before citty's own
859
853
  * "COMMANDS" heading (present because `main` has `subCommands`) and appends
@@ -938,7 +932,7 @@ function cittyCliErrorUsageCode(error) {
938
932
  }
939
933
  /**
940
934
  * Route a citty `CLIError` through the standard JSON envelope instead of
941
- * citty's own usage-banner + raw `console.error(message)` (S11 item 3): the
935
+ * citty's own usage-banner + raw `console.error(message)`: the
942
936
  * one-line diagnosis is the error's own message (e.g. "Unknown command
943
937
  * foo"), and the hint is a short pointer — with a did-you-mean suggestion
944
938
  * prepended when the unresolved token is close to a real sibling command —
@@ -962,12 +956,10 @@ export function normalizeCittyCliError(error, rawArgs) {
962
956
  }
963
957
  /**
964
958
  * The CLI's real startup sequence, extracted into a function so error paths
965
- * can `return` early — top-level `return` is a syntax error in an ES module,
966
- * and this used to rely on `emitJsonError`'s `never` return type (a
967
- * synchronous `process.exit`) to stop execution instead. Now that
968
- * `emitJsonError` (src/cli/shared.ts, R-067) only records `process.exitCode`
969
- * and returns, every direct call site here needs its own explicit `return;`
970
- * to stop the rest of startup from running after a fatal early error.
959
+ * can `return` early — top-level `return` is a syntax error in an ES module.
960
+ * `emitJsonError` (src/cli/shared.ts) only records `process.exitCode` and
961
+ * returns, so every direct call site here needs its own explicit `return;` to
962
+ * stop the rest of startup from running after a fatal early error.
971
963
  */
972
964
  export async function runCli() {
973
965
  try {
@@ -978,7 +970,7 @@ export async function runCli() {
978
970
  return;
979
971
  }
980
972
  // Mint the ParsedInvocation singleton from the (normalized) argv — the ONE
981
- // place argv is parsed for the whole process (plan §10.7 / chunk-9 WI-9.9).
973
+ // place argv is parsed for the whole process.
982
974
  // Every out-of-cli.ts command module reads argv state through
983
975
  // `getParsedInvocation()` from here on instead of re-scanning process.argv.
984
976
  setParsedInvocation(process.argv);
@@ -1024,11 +1016,10 @@ export async function runCli() {
1024
1016
  return;
1025
1017
  }
1026
1018
  const commandPath = resolveCittyCommandPath(main, process.argv.slice(2));
1027
- // D7 — every command that renders through output() honours all six --format
1019
+ // Every command that renders through output() honours all six --format
1028
1020
  // values. The declared exempt set (src/output/format-exempt.ts) does not
1029
1021
  // render an envelope at all, so warn rather than pretend: silently ignoring
1030
- // the flag is what made the old md/html behaviour so hard to discover. A
1031
- // warning, not an error, because the flag is harmless here and scripts that
1022
+ // the flag makes the behaviour hard to discover. A warning, not an error, because the flag is harmless here and scripts that
1032
1023
  // pass --format globally to a mixed batch of commands should still work.
1033
1024
  const invocation = getParsedInvocation();
1034
1025
  if ((invocation.hasFlag("--format") || invocation.getFlagValue("--format") !== undefined) &&
@@ -1082,7 +1073,7 @@ export async function runCli() {
1082
1073
  if (HELP_FLAGS.some((flag) => ownArgs.includes(flag))) {
1083
1074
  const resolved = resolveDeepestCittyCommand(main, rawArgs);
1084
1075
  if (resolved === main) {
1085
- // Root `--help` (S11 item 1): the sectioned overview, not citty's
1076
+ // Root `--help`: the sectioned overview, not citty's
1086
1077
  // flat COMMANDS dump.
1087
1078
  process.stdout.write(`${await renderSectionedRootHelp()}\n`);
1088
1079
  }
@@ -1097,14 +1088,14 @@ export async function runCli() {
1097
1088
  return;
1098
1089
  }
1099
1090
  // citty's parseArgs (strict: false) silently ignores undeclared flags, so
1100
- // a typo used to run the command with defaults and exit 0. Checked here, after --help and
1101
- // --version, so those keep working on any command.
1091
+ // a typo would otherwise run the command with defaults and exit 0. Checked
1092
+ // here, after --help and --version, so those keep working on any command.
1102
1093
  assertKnownFlags(main, rawArgs);
1103
1094
  await runCommand(main, { rawArgs });
1104
1095
  }
1105
1096
  catch (error) {
1106
1097
  if (isCittyCliError(error)) {
1107
- // R-032/S11: reclassify citty's own "unknown command" / "no command
1098
+ // Reclassify citty's own "unknown command" / "no command
1108
1099
  // specified" / "missing required argument" family as USAGE (2) and
1109
1100
  // route it through the SAME JSON envelope every other command's
1110
1101
  // failure uses (`emitJsonError`), rather than citty's own usage-banner
@@ -435,10 +435,11 @@ export const feedbackCommand = defineJsonCommand({
435
435
  "next search; no reindex is needed. Each recorded positive raises the asset's\n" +
436
436
  "score and each negative lowers it, the same way for every asset: the more\n" +
437
437
  "positives, the higher it ranks (5 outrank 2), and each further vote counts\n" +
438
- "for a little less. The effect is small, so it reorders close matches and\n" +
439
- "does not override relevance. Only feedback recorded as the user counts, not\n" +
440
- "feedback from improve, task or audit runs. Feedback also moves the utility\n" +
441
- "score that improve and `akm metrics` read.",
438
+ "for a little less. The effect is small: in keyword-only search one vote\n" +
439
+ "moves a hit one place, and with embeddings it breaks only near-ties. Only\n" +
440
+ "feedback recorded as the user counts, not feedback from improve, task or\n" +
441
+ "audit runs. Feedback also moves the utility score that improve and\n" +
442
+ "`akm metrics` read.",
442
443
  },
443
444
  args: {
444
445
  // Optional in citty so run() is invoked even when omitted; we re-validate
@@ -72,21 +72,21 @@ function validateAkmHealthOptions(options) {
72
72
  }
73
73
  }
74
74
  }
75
- // ── akmHealth phase helpers (chunk-9 WI-9.5b; file-level decompose following
76
- // the function's natural gather/advise/check/assemble phases) ───────────────
75
+ // ── akmHealth phase helpers (the function's natural
76
+ // gather/advise/check/assemble phases) ──────────────────────────────────────
77
77
  /** Event type appended + read back by the state.db round-trip probe. */
78
78
  const HEALTH_PROBE_EVENT = "health_probe";
79
- /** Synthetic sentinel ref (ref-grammar decision D-R3): a colon-free
79
+ /** Synthetic sentinel ref: a colon-free
80
80
  * `<subsystem>/_<marker>` label. `health` has no asset stash-subdir, so
81
81
  * `health/_probe` names the subsystem. */
82
82
  const HEALTH_PROBE_REF = "health/_probe";
83
83
  /**
84
84
  * Verify state.db can accept a write and read it back — WITHOUT leaving any
85
- * permanent trace (R-030). Earlier versions appended a `health_probe` event
86
- * on every `akm health` invocation and never removed it: a read-only health
87
- * check ran on a cron would grow state.db without bound (the only purge is
88
- * `improve`'s retention pass, which a health-only user never runs). The probe
89
- * row inserted here is deleted again inside the SAME connection once the
85
+ * permanent trace. Leaving the `health_probe` event in place after every
86
+ * `akm health` invocation would grow state.db without bound for a read-only
87
+ * health check run on a cron (the only purge is `improve`'s retention pass,
88
+ * which a health-only user never runs). The probe row inserted here is
89
+ * deleted again inside the SAME connection once the
90
90
  * round trip is confirmed, so the net effect on the `events` table is always
91
91
  * zero rows — the round trip still genuinely exercises append + read against
92
92
  * the real table, it just doesn't accumulate.
@@ -132,7 +132,7 @@ export function probeStateDbRoundTrip(stateDbPath) {
132
132
  }
133
133
  }
134
134
  /**
135
- * Item 7: dedupe stuck-active rows by task_id (keeping the oldest/largest age
135
+ * Dedupe stuck-active rows by task_id (keeping the oldest/largest age
136
136
  * per id) so the `active-runs` check can name WHICH tasks are stuck instead of
137
137
  * just a count. No pid/liveness probing — purely a projection of the rows
138
138
  * already read.
@@ -148,7 +148,7 @@ function dedupeStuckActiveTasks(rows, now) {
148
148
  return [...byTask.entries()].map(([taskId, ageMs]) => ({ taskId, ageMs }));
149
149
  }
150
150
  /**
151
- * Item 6: group task_history rows by task_id and return the one with the
151
+ * Group task_history rows by task_id and return the one with the
152
152
  * highest fail rate among tasks with at least MIN_ROWS_FOR_WORST_TASK_FAIL_RATE
153
153
  * rows in the window — surfaces a consistently-failing task that a large,
154
154
  * mostly-healthy population would otherwise hide behind the aggregate rate.
@@ -184,7 +184,7 @@ function gatherTaskHistoryPhase(db, since, stateDbPath, now, readOnly) {
184
184
  const requiredTables = ["events", "proposals", "schema_migrations", "task_history"];
185
185
  const missingTables = requiredTables.filter((name) => !tableNames.includes(name));
186
186
  const probe = readOnly ? { ok: true, durationMs: null, skipped: true } : probeStateDbRoundTrip(stateDbPath);
187
- // R0: read-only, independent of the round-trip probe above — integrity_check
187
+ // Read-only, independent of the round-trip probe above — integrity_check
188
188
  // catches corruption a successful append/read cannot (out-of-order rowids,
189
189
  // bad index entry counts), and the freelist reading is purely informational.
190
190
  const stateDbIntegrity = runStateDbIntegrityCheck(stateDbPath);
@@ -251,7 +251,7 @@ function gatherAncillaryAdvisories(db, options, egressConfigView) {
251
251
  const indexStateMismatch = detectIndexStateGenerationMismatch(db);
252
252
  if (indexStateMismatch)
253
253
  advisories.push(indexStateMismatch);
254
- // 08-F1: surface a `stash-git-exposure` advisory when env/secret assets are
254
+ // Surface a `stash-git-exposure` advisory when env/secret assets are
255
255
  // git-tracked AND a remote is configured (the leak moment). Best-effort.
256
256
  // Cheap guard: only shell out to git when the stash has its OWN `.git` (or a
257
257
  // test injected a fake seam), so the hot path never spawns for a non-git
@@ -295,8 +295,7 @@ function gatherAncillaryAdvisories(db, options, egressConfigView) {
295
295
  catch {
296
296
  // Non-fatal.
297
297
  }
298
- // Item 4 (alpha.9 plan §5.4/§8 step 8): a bundle with no git history of its
299
- // own never gets the purge sweep, so its memory-cleanup archive only ever
298
+ // A bundle with no git history of its own never gets the purge sweep, so its memory-cleanup archive only ever
300
299
  // grows — report its size and file count instead. Best-effort — an
301
300
  // unreadable archive must not abort the health report.
302
301
  try {
@@ -425,7 +424,7 @@ function detectIndexStateGenerationMismatch(stateDb) {
425
424
  }
426
425
  }
427
426
  /**
428
- * Phase 3 — window-compare mode. Resolves `--window-compare`/`--windows` into
427
+ * Window-compare mode. Resolves `--window-compare`/`--windows` into
429
428
  * per-window bundles and computes deltas between the earliest and latest
430
429
  * window. Top-level metrics retain the primary `--since` query.
431
430
  */
@@ -655,7 +654,7 @@ export async function akmHealth(options = {}) {
655
654
  const hardFailure = hardChecks.some((check) => check.status === "fail");
656
655
  const deterministicWarnings = [...hardChecks, ...advisories].some((check) => check.status === "warn" && check.kind === "deterministic");
657
656
  const status = hardFailure ? "fail" : deterministicWarnings ? "warn" : "pass";
658
- // ── Per-run mode (Phase 2) ────────────────────────────────────────────
657
+ // ── Per-run mode ────────────────────────────────────────────
659
658
  const runs = options.groupBy === "run" ? perRunSummaries : undefined;
660
659
  return {
661
660
  schemaVersion: 3,
@@ -390,7 +390,7 @@ const proposalDrainCommand = defineJsonCommand({
390
390
  },
391
391
  promote: {
392
392
  type: "boolean",
393
- description: "Promote (accept) matching proposals. Default is queue mode (stage only, no writes to assets).",
393
+ description: "Promote (accept) matching proposals. Without it, the strategy's apply mode decides, and the default strategy's is promote (akm#1274).",
394
394
  default: false,
395
395
  },
396
396
  judgment: {
@@ -14,8 +14,11 @@ export async function searchRegistry(query, options) {
14
14
  return { query: "", hits: [], warnings: [] };
15
15
  }
16
16
  const limit = clampLimit(options?.limit);
17
+ // AKM_REGISTRY_URL outranks the caller's list: `akm search` passes
18
+ // `config.registries`, which would otherwise shadow the override.
19
+ const envOverride = process.env.AKM_REGISTRY_URL?.trim() ? resolveRegistries() : undefined;
17
20
  // resolveRegistries() already filters by enabled; explicit registries are filtered here
18
- const raw = options?.registries ?? resolveRegistries();
21
+ const raw = envOverride ?? options?.registries ?? resolveRegistries();
19
22
  const warnings = [];
20
23
  const entries = (options?.registries ? raw.filter((r) => r.enabled !== false) : raw).filter((entry) => {
21
24
  if (!hasRegistryUrlCredentials(entry.url))
@@ -5,7 +5,7 @@
5
5
  * `akm search` — entry point.
6
6
  *
7
7
  * Search consults the local FTS5 index. There is one query path
8
- * because there is one data store. Provider fan-out is gone.
8
+ * because there is one data store; there is no provider fan-out.
9
9
  *
10
10
  * The orchestration here is thin: build the FTS query, optionally interleave
11
11
  * a registry search behind `--from registry|all`, and log a usage event.
@@ -214,9 +214,9 @@ function resolveEntryIds(db, hits) {
214
214
  try {
215
215
  const entryId = getEntryIdByFilePath(db, hit.path);
216
216
  if (entryId !== undefined) {
217
- // F4c: persist the DURABLE fully-qualified `bundle//conceptId` spelling,
218
- // derived from the resolved entry row's `item_ref` (D-R3: durable keys
219
- // come from the resolved item, never raw input).
217
+ // Persist the DURABLE fully-qualified `bundle//conceptId` spelling,
218
+ // derived from the resolved entry row's `item_ref` (durable keys come
219
+ // from the resolved item, never raw input).
220
220
  const itemRef = getItemRefById(db, entryId);
221
221
  if (itemRef !== null) {
222
222
  results.push({ entryId, ref: itemRef, hit });
@@ -254,7 +254,7 @@ function logSearchEvent(rawQuery, response, mode = "keyword", eventSource = "use
254
254
  // Emit a structured event to events.jsonl so workflow-trace consumers
255
255
  // detect akm search invocations without relying on stdout scraping.
256
256
  const stashHits = response.hits.filter((h) => h.type !== "registry");
257
- // D8: include registry hit refs so a show following a registry-only search generates a select event
257
+ // Include registry hit refs so a show following a registry-only search generates a select event
258
258
  const registryHitRefs = (response.registryHits ?? []).map((h) => `registry:${h.id}`);
259
259
  const allResultRefs = [...stashHits.map((h) => h.ref), ...registryHitRefs];
260
260
  appendEvent({
@@ -267,7 +267,7 @@ function logSearchEvent(rawQuery, response, mode = "keyword", eventSource = "use
267
267
  // Under contention these usage hints are skipped, not waited for.
268
268
  withIndexDb((db) => {
269
269
  const resolved = resolveEntryIds(db, stashHits.slice(0, 50));
270
- // usage_events telemetry now writes to state.db (Chunk-8 WI-8.3);
270
+ // usage_events telemetry writes to state.db;
271
271
  // entry_id/entry_ref are resolved from index.db above and carried across.
272
272
  const stashHitCount = response.hits.length;
273
273
  const registryHitCount = Array.isArray(response.registryHits) ? response.registryHits.length : 0;
@@ -7,7 +7,7 @@ import fs from "node:fs";
7
7
  import path from "node:path";
8
8
  import { fetchWithRetry, IS_WINDOWS, ResponseTooLargeError, readBodyWithByteCap, readChunkWithDeadline, } from "../../core/common.js";
9
9
  import { ConfigError } from "../../core/errors.js";
10
- import { warn } from "../../core/warn.js";
10
+ import { isQuiet, warn } from "../../core/warn.js";
11
11
  import { githubHeaders } from "../../integrations/github.js";
12
12
  import { getDirname, mainPath, semverOrder } from "../../runtime.js";
13
13
  import { resolveNpmGlobalRoot } from "../../tasks/resolve-akm-bin.js";
@@ -428,7 +428,11 @@ async function runMigrationStep(runTool) {
428
428
  return { status: "failed", error: result.stderr.trim() || line || `akm-migrate exited ${result.status}` };
429
429
  }
430
430
  /**
431
- * Rebuild the derived index after a successful upgrade.
431
+ * Rebuild the derived index after a successful upgrade. The first index after
432
+ * an upgrade can re-embed part of the corpus and take minutes, so the child's
433
+ * stderr (its progress lines) goes straight to the terminal; its stdout, never a
434
+ * terminal here, is captured. `--format json` keeps the progress on stderr
435
+ * whatever output format the user has configured, and `--quiet` is passed on.
432
436
  */
433
437
  function runPostUpgradeTasks(akmBin, opts) {
434
438
  if (opts.skip) {
@@ -439,10 +443,10 @@ function runPostUpgradeTasks(akmBin, opts) {
439
443
  };
440
444
  }
441
445
  try {
442
- const result = childProcess.spawnSync(akmBin, ["index"], {
446
+ const result = childProcess.spawnSync(akmBin, ["index", "--format", "json", ...(isQuiet() ? ["--quiet"] : [])], {
443
447
  encoding: "utf8",
444
448
  env: process.env,
445
- stdio: "pipe",
449
+ stdio: ["pipe", "pipe", "inherit"],
446
450
  });
447
451
  if (result.error) {
448
452
  return {
@@ -452,7 +456,7 @@ function runPostUpgradeTasks(akmBin, opts) {
452
456
  };
453
457
  }
454
458
  if (result.status !== 0) {
455
- const detail = (result.stderr ?? "").trim() || (result.stdout ?? "").trim() || `exit code ${result.status}`;
459
+ const detail = (result.stdout ?? "").trim() || `exit code ${result.status}`;
456
460
  return {
457
461
  ok: false,
458
462
  skipped: false,
@@ -178,10 +178,10 @@ export async function akmTasksRun(id, options = {}) {
178
178
  const adapterId = bundle.source.adapterId ?? detectAdapterId(bundle.source.path);
179
179
  const resolvedId = taskIdForAdapter(parsed.id, adapterId);
180
180
  const scheduled = options.scheduled === true;
181
- // Built ONCE at this invocation boundary. eventSource is "task"
182
- // whether or not --scheduled was passed — scheduled stays a
183
- // separate field carrying its own pre-existing meaning (activation policy,
184
- // scheduler env), never selecting the event source.
181
+ // Built ONCE at this invocation boundary. eventSource is "task" whether or
182
+ // not --scheduled was passed — scheduled stays a separate field carrying its
183
+ // own meaning (activation policy, scheduler env), never selecting the event
184
+ // source.
185
185
  const provenance = createExecutionProvenanceContext(scheduled);
186
186
  // The raw, exact-name input flags `tasks-cli.ts`'s Stage 1 captures ride
187
187
  // through unchanged to `runTask` -> `loadPreparedTask`'s Stage 2 materializer,
@@ -206,8 +206,8 @@ export async function akmTasksRun(id, options = {}) {
206
206
  // projectability, and resolver failures therefore create no history row.
207
207
  const result = await runTask(resolvedId, runOptions);
208
208
  // "command" means the agent/LLM arm — the native shell/script arm reports
209
- // "shell" / "script" — so a shell/script task's process exit 78 still passes
210
- // through as CLI exit 78 (documented behavior, src/assets/hints/cli-hints-short.md).
209
+ // "shell" / "script". A shell/script task's process exit 78 passes through
210
+ // as CLI exit 78 (documented behavior, src/assets/hints/cli-hints-short.md).
211
211
  const exitCode = result.status === "failed" &&
212
212
  (result.target.kind === "shell" || result.target.kind === "script") &&
213
213
  result.detail?.exitCode === 78
@@ -597,10 +597,9 @@ export async function akmTasksDoctor(deps = {}) {
597
597
  const config = loadConfig();
598
598
  const defaultEngine = (defaultHarness(config) ?? defaultConnection(config))?.name;
599
599
  const engines = [...Object.keys(config.connections ?? {}), ...Object.keys(config.harnesses ?? {})];
600
- // §6.1: surface the effective triage settings for the default improve
601
- // strategy. The struct is a fixed shape, so this is a deliberate addition.
600
+ // Surface the effective triage settings for the default improve strategy.
602
601
  const improveStrategyName = config.improve?.strategy ?? "default";
603
- // D8 — report the EFFECTIVE strategy, not the raw one. Resolving the raw
602
+ // Report the EFFECTIVE strategy, not the raw one. Resolving the raw
604
603
  // strategy here would report `applyMode: "promote"` for a promote strategy
605
604
  // under a review-first config, while the run actually uses "queue" — a doctor
606
605
  // command lying about the thing it exists to diagnose.
@@ -753,7 +752,7 @@ function errorMessage(cause) {
753
752
  }
754
753
  /**
755
754
  * Infer a JSON-Schema-subset `type` keyword from one `--params` value's
756
- * runtime shape. `null` is not one of the five runtime types
755
+ * runtime shape. `null` is not one of the five usual runtime types
757
756
  * (string/number/boolean/object/array) but is a value JSON.parse can still
758
757
  * produce for a param; `src/core/json-schema.ts`'s subset validator accepts
759
758
  * `"null"` as a `type`, so it is handled rather than mis-typed.
@@ -768,9 +767,9 @@ function jsonSchemaTypeOf(value) {
768
767
  /**
769
768
  * Render `--params` as typed `inputs:` declarations, one per key, each
770
769
  * carrying a `default:` equal to the authored value and a `type:` inferred
771
- * from its own JSON runtime shape (row B-20). Never emitted as `with:` —
772
- * task source v4 accepts `with:` only on `uses: akm/command` (§3.2.6's
773
- * `parseTarget`/`checkTopLevelKeys`), and a workflow target's declared
770
+ * from its own JSON runtime shape. Never emitted as `with:` —
771
+ * task source v4 accepts `with:` only on `uses: akm/command`
772
+ * (`parseTarget`/`checkTopLevelKeys`), and a workflow target's declared
774
773
  * inputs are what `load-task.ts`'s existing v4 delivery override binds into
775
774
  * the child run's params.
776
775
  */
@@ -2,7 +2,7 @@
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  /**
5
- * The `agent-skills` adapter — akm 0.9.0 format-family work item (#46).
5
+ * The `agent-skills` adapter.
6
6
  *
7
7
  * Recognizes a COLLECTION of standalone SKILL.md packages (like
8
8
  * github.com/anthropics/skills): each `<name>/SKILL.md` is one `skill` item
@@ -38,7 +38,7 @@
38
38
  *
39
39
  * Conformance oracle (authored, DO NOT modify): fixture
40
40
  * `tests/fixtures/bundles/agent-skills/` + goldens
41
- * `tests/fixtures/format-family-goldens/agent-skills/{recognition,placement,lint,renderer}.json`.
41
+ * `tests/fixtures/format-family-goldens/agent-skills/{recognition,lint,renderer}.json`.
42
42
  */
43
43
  import fs from "node:fs";
44
44
  import path from "node:path";
@@ -52,7 +52,7 @@ const SKILL_MANIFEST = "SKILL.md";
52
52
  /** Agent Skills hard limits. */
53
53
  const NAME_MAX = 64;
54
54
  const DESCRIPTION_MAX = 1024;
55
- /** Agent Skills reserved words a name must not contain (real-world source). */
55
+ /** Agent Skills reserved words a name must not contain. */
56
56
  const RESERVED_NAME_WORDS = ["anthropic", "claude"];
57
57
  /** name charset/shape rule. */
58
58
  const NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
@@ -98,7 +98,7 @@ function recognize(c, file) {
98
98
  doc.tags = tags;
99
99
  return doc;
100
100
  }
101
- /** The Agent Skills §4.5 hard-rule checks for one SKILL.md (name + description). */
101
+ /** The Agent Skills hard-rule checks for one SKILL.md (name + description). */
102
102
  function skillFieldDiagnostics(relPath, dirName, data) {
103
103
  const diagnostics = [];
104
104
  const name = typeof data.name === "string" ? data.name.normalize("NFKC") : "";
@@ -176,8 +176,8 @@ async function scanPackageCandidate(dir, entries, ctx, depth) {
176
176
  *
177
177
  * `validate` walks CHANGES, and a change is always a file — so a package
178
178
  * directory carrying resources but no manifest contributes nothing the
179
- * change-loop can see, and the check the spec (§4.5) and the lint golden's
180
- * `missingSkillMd` edge case both name was unreachable. This scans the
179
+ * change-loop can see, and the check the lint golden's `missingSkillMd` edge
180
+ * case names would be unreachable. This scans the
181
181
  * component root through {@link ValidateContext.list} instead, so the case is
182
182
  * actually reported.
183
183
  *
@@ -230,7 +230,7 @@ export const agentSkillsAdapter = {
230
230
  readCandidates(c, conceptId) {
231
231
  return [{ path: path.join(c.root, conceptId, SKILL_MANIFEST), conceptId }];
232
232
  },
233
- /** Install-time probe (§1.2): a root that directly contains one or more `<name>/SKILL.md` skill packages. */
233
+ /** Install-time probe: a root that directly contains one or more `<name>/SKILL.md` skill packages. */
234
234
  looksLikeRoot(root) {
235
235
  let entries;
236
236
  try {
@@ -26,7 +26,7 @@
26
26
  *
27
27
  * Conformance oracle (authored, DO NOT modify): fixture
28
28
  * `tests/fixtures/bundles/akm-task/` + goldens
29
- * `tests/fixtures/format-family-goldens/akm-task/{recognition,placement,lint,renderer}.json`.
29
+ * `tests/fixtures/format-family-goldens/akm-task/{recognition,lint,renderer}.json`.
30
30
  */
31
31
  import fs from "node:fs";
32
32
  import path from "node:path";
@@ -15,7 +15,7 @@
15
15
  *
16
16
  * Conformance oracle (authored, DO NOT modify): fixture
17
17
  * `tests/fixtures/bundles/akm-workflow/` + goldens
18
- * `tests/fixtures/format-family-goldens/akm-workflow/{recognition,placement,lint,renderer}.json`.
18
+ * `tests/fixtures/format-family-goldens/akm-workflow/{recognition,lint,renderer}.json`.
19
19
  */
20
20
  import fs from "node:fs";
21
21
  import path from "node:path";
@@ -16,7 +16,7 @@
16
16
  *
17
17
  * Conformance oracle (authored, DO NOT modify): fixture
18
18
  * `tests/fixtures/bundles/claude/` + goldens
19
- * `tests/fixtures/format-family-goldens/claude/{recognition,placement,lint,renderer}.json`.
19
+ * `tests/fixtures/format-family-goldens/claude/{recognition,lint,renderer}.json`.
20
20
  */
21
21
  import fs from "node:fs";
22
22
  import path from "node:path";