akm-cli 0.9.8 → 0.9.9

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 (34) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/dist/commands/health/checks.js +40 -0
  3. package/dist/commands/health.js +57 -31
  4. package/dist/commands/migrate-cli.js +29 -189
  5. package/dist/commands/sources/add-cli.js +7 -0
  6. package/dist/commands/sources/installed-stashes.js +36 -8
  7. package/dist/commands/sources/self-update.js +104 -62
  8. package/dist/commands/sources/source-add.js +6 -5
  9. package/dist/commands/sources/sources-cli.js +7 -18
  10. package/dist/commands/tasks/tasks-cli.js +4 -3
  11. package/dist/commands/tasks/tasks.js +13 -6
  12. package/dist/core/adapter/adapter-ids.js +35 -0
  13. package/dist/core/adapter/adapters/index.js +29 -0
  14. package/dist/core/adapter/detect-adapter.js +91 -3
  15. package/dist/core/config/config.js +1 -1
  16. package/dist/core/config/schema/sources-bundles.js +23 -0
  17. package/dist/core/extra-params.js +1 -1
  18. package/dist/core/state/migrations.js +2 -4
  19. package/dist/core/state-db.js +31 -8
  20. package/dist/indexer/indexer.js +64 -1
  21. package/dist/scripts/akm-migrate-node.js +86534 -20434
  22. package/dist/scripts/akm-migrate.js +86415 -20280
  23. package/dist/tasks/backends/cron.js +21 -6
  24. package/dist/tasks/resolve-akm-bin.js +1 -1
  25. package/docs/README.md +1 -0
  26. package/docs/integration/bundling-akm.md +276 -0
  27. package/docs/migration/v0.9.0-troubleshooting.md +10 -14
  28. package/docs/migration/v0.9.1-to-v0.9.2.md +12 -16
  29. package/docs/reference/cli.md +59 -31
  30. package/docs/reference/tasks.md +4 -10
  31. package/package.json +2 -1
  32. package/dist/commands/migrate/config-extra-params.js +0 -61
  33. package/dist/commands/migrate/dead-residue.js +0 -113
  34. package/dist/commands/migrate/stale-txn.js +0 -49
@@ -151,7 +151,7 @@ export function parseAndValidateConfigText(text, sourcePath) {
151
151
  // rewritten onto the first-class engine field they now shadow. This used
152
152
  // to happen silently, in memory, on every load; that ran forever and never
153
153
  // converged. The lift itself is now `akm migrate apply`'s job (see
154
- // src/commands/migrate/config-extra-params.ts) and persists to disk, so a
154
+ // scripts/akm-migrate/migrate/config-extra-params.ts) and persists to disk, so a
155
155
  // config that has not been migrated yet fails closed here instead of
156
156
  // silently drifting from what's on disk.
157
157
  const where = sourcePath ? ` at ${sourcePath}` : "";
@@ -7,9 +7,15 @@
7
7
  * change.
8
8
  */
9
9
  import { z } from "zod";
10
+ // Dependency-free mirror of the adapter registry's id list (#909) — see
11
+ // adapter-ids.ts's header for why config imports this leaf rather than
12
+ // `core/adapter/registry.ts` (which would pull in all 11 concrete adapters
13
+ // and, transitively, the indexer modules they delegate to).
14
+ import { VALID_ADAPTER_IDS } from "../../adapter/adapter-ids.js";
10
15
  import { isBundleSlug } from "../../asset/asset-ref.js";
11
16
  import { hasRegistryUrlCredentials, REGISTRY_CREDENTIALS_UNSUPPORTED } from "../../registry-url.js";
12
17
  import { httpUrl, nonEmptyString, positiveInt } from "./primitives.js";
18
+ const VALID_ADAPTER_IDS_SET = new Set(VALID_ADAPTER_IDS);
13
19
  // ── Sources / registries / installed ────────────────────────────────────────
14
20
  const SourceConfigEntryOptionsSchema = z.record(z.unknown());
15
21
  export const SourceConfigEntrySchema = z
@@ -165,6 +171,23 @@ export const BundleConfigEntrySchema = z
165
171
  message: "writable: true is only supported on path and git bundle sources",
166
172
  });
167
173
  }
174
+ // #909: an unrecognized `components.*.adapter` used to silently fall back
175
+ // to `akm` at detect-time with no error and no disclosure — a typo
176
+ // (`agent_skills`, `akm-native`, …) then silently changed which files got
177
+ // indexed. Reject it here instead, the same treatment other enum-valued
178
+ // config fields get, listing the accepted values (derived from the
179
+ // adapter registry, never hardcoded — see adapter-ids.ts).
180
+ if (componentEntry !== undefined) {
181
+ const [componentName, componentValue] = componentEntry;
182
+ const adapterValue = componentValue.adapter;
183
+ if (adapterValue !== undefined && !VALID_ADAPTER_IDS_SET.has(adapterValue)) {
184
+ ctx.addIssue({
185
+ code: z.ZodIssueCode.custom,
186
+ path: ["components", componentName, "adapter"],
187
+ message: `unrecognized adapter "${adapterValue}"; expected one of: ${VALID_ADAPTER_IDS.join(", ")}`,
188
+ });
189
+ }
190
+ }
168
191
  });
169
192
  /**
170
193
  * `bundles` map. Keys are workspace bundle slugs (spec §11.1 / D-R5 charset).
@@ -125,7 +125,7 @@ export function formatExtraParamsIssue(label, issue) {
125
125
  * (#852, following #815).
126
126
  *
127
127
  * Pure: never touches the filesystem. Two callers use this differently:
128
- * `akm migrate apply` (src/commands/migrate/config-extra-params.ts) uses the
128
+ * `akm migrate apply` (scripts/akm-migrate/migrate/config-extra-params.ts) uses the
129
129
  * returned `config` to persist the rewrite to disk, once; `parseAndValidateConfigText`
130
130
  * (src/core/config/config.ts) calls this only to detect whether a lift is
131
131
  * needed and discards `config` — an unmigrated config fails closed there
@@ -1187,8 +1187,7 @@ export function runMigrations(db, options) {
1187
1187
  }
1188
1188
  if (!options.allowHistoricalDestructiveStateUpgrade) {
1189
1189
  throw new Error("Refusing to migrate an existing unversioned state.db during an ordinary managed open. " +
1190
- "Run `akm upgrade --force` to snapshot it before migration 001, " +
1191
- "or `akm upgrade --state-only` where akm cannot reinstall itself (container/global install).");
1190
+ "Run `akm upgrade` (or `akm migrate apply`) to snapshot it before migration 001 and apply it deliberately.");
1192
1191
  }
1193
1192
  const ledger = assertMigrationLedger(lockedDb, STATE_MIGRATIONS);
1194
1193
  if (ledger.migrationIds.length !== 0) {
@@ -1218,8 +1217,7 @@ export function runMigrations(db, options) {
1218
1217
  assertMigrationLedger(lockedDb, STATE_MIGRATIONS);
1219
1218
  if (!options?.allowHistoricalDestructiveStateUpgrade) {
1220
1219
  throw new Error(`Refusing to apply historical destructive state migration ${migration.id} during an ordinary managed open. ` +
1221
- "Run `akm upgrade --force` to create a sibling state.db safety copy and apply it deliberately, " +
1222
- "or `akm upgrade --state-only` where akm cannot reinstall itself (container/global install).");
1220
+ "Run `akm upgrade` (or `akm migrate apply`) to create a sibling state.db safety copy and apply it deliberately.");
1223
1221
  }
1224
1222
  if (!options.beforeHistoricalDestructiveMigration) {
1225
1223
  throw new Error(`Historical destructive state migration ${migration.id} requires a verified safety-copy hook.`);
@@ -407,8 +407,8 @@ export function openStateDatabase(dbPath, options) {
407
407
  existingUnversionedDatabase = ledger.migrationIds.length === 0;
408
408
  if (existingUnversionedDatabase && !options?.allowHistoricalDestructiveStateUpgrade) {
409
409
  throw new Error("Refusing to migrate an existing unversioned state.db during an ordinary managed open. " +
410
- "Run `akm upgrade --force` to create a verified snapshot before migration 001, " +
411
- "or `akm upgrade --state-only` where akm cannot reinstall itself (container/global install).");
410
+ "Run `akm upgrade` (or `akm migrate apply`) to create a verified snapshot before migration 001 " +
411
+ "and apply it deliberately.");
412
412
  }
413
413
  }
414
414
  finally {
@@ -499,13 +499,36 @@ export function openStateDatabase(dbPath, options) {
499
499
  }
500
500
  }
501
501
  /**
502
- * Narrow state-schema step owned by `akm upgrade` after executable replacement.
503
- * Missing/current databases are no-ops. A pre-018 exact ledger is snapshotted
504
- * beside state.db and verified before the immutable released migration runs.
502
+ * Read-only: the state migration IDs the running akm would apply to `dbPath`,
503
+ * in ledger order. Empty when the database is missing or current. Throws on a
504
+ * ledger this akm cannot own (newer, or not an exact ordered prefix) -- the
505
+ * same refusal a managed open makes.
505
506
  */
506
- export function upgradeHistoricalStateDatabase(dbPath = getStateDbPath()) {
507
+ export function listPendingStateMigrations(dbPath = getStateDbPath()) {
507
508
  if (!fs.existsSync(dbPath))
508
- return { upgraded: false };
509
+ return [];
510
+ const preflight = openDatabase(dbPath, { readonly: true });
511
+ try {
512
+ preflight.exec("PRAGMA busy_timeout = 30000");
513
+ const ledger = assertMigrationLedger(preflight, STATE_MIGRATIONS);
514
+ return STATE_MIGRATIONS.slice(ledger.migrationIds.length).map((migration) => migration.id);
515
+ }
516
+ finally {
517
+ preflight.close();
518
+ }
519
+ }
520
+ /**
521
+ * Apply every pending state migration, historical-destructive ones included:
522
+ * the one step `akm upgrade` and `akm migrate apply` share, and the only
523
+ * caller that may admit migration 018 (an ordinary managed open refuses it by
524
+ * design). Missing/current databases are no-ops. A pre-018 exact ledger, or an
525
+ * unversioned database, is snapshotted beside state.db and verified before the
526
+ * immutable released migration runs.
527
+ */
528
+ export function upgradeHistoricalStateDatabase(dbPath = getStateDbPath()) {
529
+ const pending = listPendingStateMigrations(dbPath);
530
+ if (pending.length === 0)
531
+ return { upgraded: false, applied: [] };
509
532
  let safetyCopyPath;
510
533
  try {
511
534
  const db = openStateDatabase(dbPath, {
@@ -521,7 +544,7 @@ export function upgradeHistoricalStateDatabase(dbPath = getStateDbPath()) {
521
544
  const recovery = safetyCopyPath ? ` Verified safety copy: ${safetyCopyPath}.` : "";
522
545
  throw new Error(`${detail}${recovery}`);
523
546
  }
524
- return safetyCopyPath ? { upgraded: true, safetyCopyPath } : { upgraded: false };
547
+ return safetyCopyPath ? { upgraded: true, applied: pending, safetyCopyPath } : { upgraded: true, applied: pending };
525
548
  }
526
549
  /**
527
550
  * Run `fn` against state.db, owning the handle unless one is borrowed. The loan
@@ -13,7 +13,7 @@ import { classifyPathAccess, describeInaccessiblePath } from "../core/path-acces
13
13
  import { getDbPath } from "../core/paths.js";
14
14
  import { SCRIPT_EXTENSIONS } from "../core/recognition-util.js";
15
15
  import { withStateDb } from "../core/state-db.js";
16
- import { isVerbose, warn, warnVerbose } from "../core/warn.js";
16
+ import { isVerbose, warn, warnOnce, warnVerbose } from "../core/warn.js";
17
17
  import { disposeLoweredExecutionDispatchLease, } from "../integrations/agent/execution-lowering.js";
18
18
  import { isLlmFeatureEnabled } from "../llm/feature-gate.js";
19
19
  import { resolveIndexPassExecution } from "../llm/index-passes.js";
@@ -750,6 +750,67 @@ function sourceSnapshotRemovals(db, currentStashDir, bundleId, currentDirs, allI
750
750
  reason: { kind: "not-in-source-snapshot" },
751
751
  }));
752
752
  }
753
+ /**
754
+ * Warn ONCE per process (#908) when the chosen adapter for a component
755
+ * entirely skips a top-level directory that holds files the `akm` adapter —
756
+ * the format-neutral superset — would have indexed. `detectAdapterId` now
757
+ * corrects this for AUTO-DETECTION (a mixed layout detects as `akm`); this
758
+ * covers the case detection cannot see, an EXPLICITLY configured narrow
759
+ * adapter (`components.<name>.adapter: "agent-skills"`, say) sitting next to
760
+ * ordinary akm content. One line for the whole process — not one per bundle,
761
+ * not one per directory — naming the count and the directories is enough to
762
+ * point an operator at the fix.
763
+ */
764
+ function warnIfAdapterSkipsAkmContent(component, files, adapter) {
765
+ if (adapter.id === "akm")
766
+ return;
767
+ const akm = adapterForId("akm");
768
+ if (!akm)
769
+ return;
770
+ const byTopDir = new Map();
771
+ for (const file of files) {
772
+ const top = file.ancestorDirs[0];
773
+ if (!top)
774
+ continue; // a root-level file is not a "skipped directory" concern
775
+ const group = byTopDir.get(top);
776
+ if (group)
777
+ group.push(file);
778
+ else
779
+ byTopDir.set(top, [file]);
780
+ }
781
+ const akmComponent = { ...component, adapter: "akm" };
782
+ let skippedCount = 0;
783
+ const skippedDirs = [];
784
+ for (const [dir, dirFiles] of byTopDir) {
785
+ const chosenRecognizesAny = dirFiles.some((file) => {
786
+ try {
787
+ return adapter.recognize(component, file) !== null;
788
+ }
789
+ catch {
790
+ return false;
791
+ }
792
+ });
793
+ if (chosenRecognizesAny)
794
+ continue; // the chosen adapter owns this dir; nothing skipped
795
+ const akmCandidates = dirFiles.filter((file) => {
796
+ try {
797
+ return akm.recognize(akmComponent, file) !== null;
798
+ }
799
+ catch {
800
+ return false;
801
+ }
802
+ });
803
+ if (akmCandidates.length === 0)
804
+ continue; // akm would drop it too — not a shadowing case
805
+ skippedCount += akmCandidates.length;
806
+ skippedDirs.push(dir);
807
+ }
808
+ if (skippedCount === 0)
809
+ return;
810
+ skippedDirs.sort();
811
+ warnOnce("adapter-skip-akm-content", `${adapter.id} adapter skipped ${skippedCount} file${skippedCount === 1 ? "" : "s"} in ` +
812
+ `${skippedDirs.map((dir) => `${dir}/`).join(", ")} — set components.<name>.adapter to "akm" to index them`);
813
+ }
753
814
  function buildSourceScanPlans(db, allSourceEntries, isIncremental, reconcileMissingDirs) {
754
815
  const componentBySource = buildComponentBySource(allSourceEntries);
755
816
  const handoffDirs = new Set();
@@ -775,6 +836,8 @@ function buildSourceScanPlans(db, allSourceEntries, isIncremental, reconcileMiss
775
836
  });
776
837
  const dirGroups = groupFileContextsByDir(walked.files);
777
838
  const adapter = adapterForId(component.adapter);
839
+ if (adapter)
840
+ warnIfAdapterSkipsAkmContent(component, walked.files, adapter);
778
841
  return {
779
842
  currentStashDir,
780
843
  component,