akm-cli 0.9.16-alpha.1 → 0.9.16-alpha.2

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 (144) hide show
  1. package/CHANGELOG.md +40 -132
  2. package/dist/assets/hints/cli-hints-full.md +13 -6
  3. package/dist/assets/tasks/core/index-refresh.yml +1 -1
  4. package/dist/assets/tasks/improve/akm-improve-catchup.yml +3 -6
  5. package/dist/cli/retired-commands.js +0 -4
  6. package/dist/cli/unknown-flags.js +3 -36
  7. package/dist/commands/env/env-binding.js +4 -4
  8. package/dist/commands/env/env-cli.js +3 -3
  9. package/dist/commands/improve/collapse-detector.js +2 -2
  10. package/dist/commands/improve/consolidate.js +4 -6
  11. package/dist/commands/improve/improve-cli.js +20 -15
  12. package/dist/commands/improve/reflect.js +23 -2
  13. package/dist/commands/lint/base-linter.js +9 -0
  14. package/dist/commands/lint/env-key-rules.js +2 -2
  15. package/dist/commands/proposal/propose.js +15 -1
  16. package/dist/commands/proposal/repository.js +3 -12
  17. package/dist/commands/proposal/validators/proposal-quality-validators.js +40 -3
  18. package/dist/commands/proposal/validators/proposal-validators.js +5 -4
  19. package/dist/commands/read/curate.js +44 -34
  20. package/dist/commands/read/search.js +35 -54
  21. package/dist/commands/read/show.js +21 -2
  22. package/dist/commands/registry-cli.js +5 -5
  23. package/dist/commands/sources/add-cli.js +59 -16
  24. package/dist/commands/sources/bundle-cli.js +35 -11
  25. package/dist/commands/sources/bundle-config-ops.js +30 -0
  26. package/dist/commands/sources/dangerous-env-audit.js +4 -4
  27. package/dist/commands/sources/info.js +8 -8
  28. package/dist/commands/sources/installed-stashes.js +55 -61
  29. package/dist/commands/sources/source-add.js +39 -38
  30. package/dist/commands/sources/source-manage.js +34 -12
  31. package/dist/commands/sources/stash-cli.js +111 -119
  32. package/dist/commands/sources/stash-skeleton.js +6 -3
  33. package/dist/commands/tasks/explain.js +4 -1
  34. package/dist/commands/tasks/tasks-cli.js +31 -9
  35. package/dist/commands/tasks/tasks.js +239 -194
  36. package/dist/commands/tasks/validate.js +20 -32
  37. package/dist/core/activation-policy.js +4 -4
  38. package/dist/core/adapter/adapters/akm-adapter.js +8 -35
  39. package/dist/core/adapter/adapters/akm-metadata.js +1 -11
  40. package/dist/core/adapter/execution-source.js +10 -29
  41. package/dist/core/asset/asset-placement.js +0 -35
  42. package/dist/core/config/config-schema.js +64 -8
  43. package/dist/core/config/config-sources.js +96 -2
  44. package/dist/core/config/config.js +190 -24
  45. package/dist/core/config/legacy-source-shape-shim.js +9 -0
  46. package/dist/core/config/schema/embedding.js +30 -7
  47. package/dist/core/config/schema/execution.js +23 -0
  48. package/dist/core/config/schema/experimental.js +1 -1
  49. package/dist/core/config/schema/scheduler.js +20 -0
  50. package/dist/core/config/schema/search.js +10 -12
  51. package/dist/core/config/schema/sources-bundles.js +32 -1
  52. package/dist/core/content-safety.js +52 -0
  53. package/dist/core/errors.js +2 -5
  54. package/dist/core/maintenance-barrier.js +11 -13
  55. package/dist/core/paths.js +11 -0
  56. package/dist/core/run-lock.js +2 -5
  57. package/dist/core/state/migrations.js +1 -26
  58. package/dist/core/state-db.js +27 -63
  59. package/dist/core/type-presentation.js +1 -1
  60. package/dist/core/write-source.js +13 -8
  61. package/dist/indexer/bundle-identity-guard.js +45 -8
  62. package/dist/indexer/ensure-index.js +0 -5
  63. package/dist/indexer/index-db-contention.js +56 -0
  64. package/dist/indexer/index-rebuild-lock.js +73 -0
  65. package/dist/indexer/index-written-assets.js +171 -133
  66. package/dist/indexer/indexer.js +1621 -458
  67. package/dist/indexer/lookup/adapter-concept-owner.js +5 -19
  68. package/dist/indexer/materialize-embeddings.js +785 -0
  69. package/dist/indexer/passes/dir-staleness.js +161 -0
  70. package/dist/indexer/passes/metadata.js +1 -18
  71. package/dist/indexer/scan/drain-dir.js +70 -27
  72. package/dist/indexer/search/db-search.js +89 -373
  73. package/dist/indexer/search/ranking-contributors.js +16 -21
  74. package/dist/indexer/search/ranking.js +57 -135
  75. package/dist/indexer/search/search-source.js +29 -11
  76. package/dist/integrations/agent/execution-lowering.js +3 -2
  77. package/dist/integrations/agent/execution-preparation.js +32 -1
  78. package/dist/integrations/agent/prompts.js +1 -1
  79. package/dist/integrations/agent/request-lowering.js +3 -2
  80. package/dist/llm/client.js +3 -11
  81. package/dist/llm/embedder.js +3 -10
  82. package/dist/llm/embedders/remote.js +104 -133
  83. package/dist/llm/feature-gate.js +2 -4
  84. package/dist/llm/rerank-client.js +3 -3
  85. package/dist/output/shapes/passthrough.js +2 -1
  86. package/dist/output/text/command-format.js +13 -19
  87. package/dist/output/text/helpers.js +1 -1
  88. package/dist/output/text/index.js +2 -5
  89. package/dist/registry/resolve.js +37 -10
  90. package/dist/scripts/akm-migrate-node.js +15197 -11351
  91. package/dist/scripts/akm-migrate.js +15514 -11668
  92. package/dist/setup/semantic-assets.js +2 -2
  93. package/dist/setup/setup.js +3 -3
  94. package/dist/setup/steps/connection.js +2 -3
  95. package/dist/setup/steps/tasks.js +29 -36
  96. package/dist/sources/providers/git-install.js +17 -11
  97. package/dist/sources/providers/git-provider.js +12 -5
  98. package/dist/sources/providers/git-stash.js +38 -16
  99. package/dist/sources/snapshot-fetchers/website-ingest.js +3 -3
  100. package/dist/storage/repositories/embedding-salvage-repository.js +184 -0
  101. package/dist/storage/repositories/index-connection.js +3 -1
  102. package/dist/storage/repositories/index-entries-repository.js +68 -77
  103. package/dist/storage/repositories/index-entry-schema.js +25 -16
  104. package/dist/storage/repositories/index-fts-repository.js +263 -29
  105. package/dist/storage/repositories/index-meta-repository.js +29 -0
  106. package/dist/storage/repositories/index-schema.js +122 -115
  107. package/dist/storage/repositories/index-utility-repository.js +1 -1
  108. package/dist/storage/repositories/index-vec-repository.js +435 -22
  109. package/dist/tasks/activation-config.js +90 -0
  110. package/dist/tasks/backends/cron.js +9 -0
  111. package/dist/tasks/backends/launchd.js +1 -0
  112. package/dist/tasks/backends/schtasks.js +2 -0
  113. package/dist/tasks/embedded.js +4 -5
  114. package/dist/tasks/scheduler-binding.js +2 -2
  115. package/dist/tasks/scheduler-sync-preview.js +8 -1
  116. package/dist/tasks/scheduler-sync.js +19 -10
  117. package/dist/tasks/source/parse-task-source.js +10 -113
  118. package/dist/tasks/source/project-v4.js +2 -2
  119. package/dist/tasks/source/task-source-v4.js +4 -12
  120. package/dist/tasks/source/task-to-v3.js +4 -12
  121. package/dist/tasks/source/task-to-v4.js +40 -7
  122. package/docs/migration/README.md +1 -0
  123. package/docs/migration/release-notes/0.9.15.md +36 -34
  124. package/docs/migration/release-notes/0.9.16.md +60 -98
  125. package/docs/migration/release-notes/README.md +0 -5
  126. package/docs/migration/v0.9.1-to-v0.9.2.md +6 -9
  127. package/docs/reference/cli.md +124 -122
  128. package/docs/reference/configuration.md +137 -133
  129. package/docs/reference/data-and-telemetry.md +1 -2
  130. package/docs/reference/tasks.md +34 -29
  131. package/package.json +1 -1
  132. package/schemas/akm-config.json +170 -6
  133. package/schemas/akm-task.json +1 -2
  134. package/dist/commands/sources/index-status.js +0 -99
  135. package/dist/core/hash.js +0 -18
  136. package/dist/indexer/drain.js +0 -306
  137. package/dist/indexer/embedding-identity.js +0 -20
  138. package/dist/indexer/enrich.js +0 -260
  139. package/dist/indexer/reconcile.js +0 -890
  140. package/dist/indexer/scan/parse-file.js +0 -66
  141. package/dist/indexer/units/unit.js +0 -159
  142. package/dist/llm/embedders/provider-limits.js +0 -288
  143. package/dist/storage/repositories/files-repository.js +0 -181
  144. package/dist/storage/repositories/units-repository.js +0 -510
@@ -18,7 +18,7 @@ import fs from "node:fs";
18
18
  import path from "node:path";
19
19
  import { detectAdapterId } from "../../core/adapter/detect-adapter.js";
20
20
  import { isWithin, resolveStashDir } from "../../core/common.js";
21
- import { acquireConfigReadFence, bundleComponentConfig, getSources, loadConfig } from "../../core/config/config.js";
21
+ import { acquireConfigReadFence, bundleComponentConfig, getSources, loadConfig, resolveSecret, } from "../../core/config/config.js";
22
22
  import { AkmError, ConfigError, NotFoundError, UsageError } from "../../core/errors.js";
23
23
  import { isPathAbsent } from "../../core/path-access.js";
24
24
  import { getDbPath, getRegistryCacheDir } from "../../core/paths.js";
@@ -26,7 +26,7 @@ import { beginImmediateTransaction, getStateDbPath, openStateDatabase } from "..
26
26
  import { warn } from "../../core/warn.js";
27
27
  import { resolveGitContentRoot } from "../../core/write-source.js";
28
28
  import { withAssetMutationLease } from "../../indexer/index-writer-lock.js";
29
- import { akmIndex, reclassifyIndexDbContention, runEmbeddingPass } from "../../indexer/indexer.js";
29
+ import { akmIndex, runEmbeddingPass } from "../../indexer/indexer.js";
30
30
  import { compareAndSwapLockfileSnapshot, publishLockfileUpdate, readLockfile, readLockfileForUpdate, } from "../../integrations/lockfile.js";
31
31
  import { parseRegistryRef } from "../../registry/resolve.js";
32
32
  import { sha256Hex } from "../../runtime.js";
@@ -106,6 +106,7 @@ function listManagedInstalls(config) {
106
106
  componentRoot,
107
107
  requiredRoots: lock.localRoot ? [path.resolve(lock.localRoot, componentRoot)] : [],
108
108
  auditConfigGeneration: bundleAuditGeneration(bundle),
109
+ ...(bundle.credential !== undefined ? { credential: bundle.credential } : {}),
109
110
  });
110
111
  }
111
112
  return out;
@@ -312,15 +313,11 @@ export async function akmRemove(input) {
312
313
  config: {
313
314
  sourceCount: getSources(updatedConfig).length,
314
315
  },
315
- // `IndexResponse.directoriesScanned`/`directoriesSkipped` were renamed/
316
- // removed (#index-redesign W6) — this response's own `index` shape is
317
- // unchanged, so bridge from the renamed source field;
318
- // `directoriesSkipped` was already always 0.
319
316
  index: {
320
317
  mode: index.mode,
321
318
  totalEntries: index.totalEntries,
322
- directoriesScanned: index.sourcesScanned,
323
- directoriesSkipped: 0,
319
+ directoriesScanned: index.directoriesScanned,
320
+ directoriesSkipped: index.directoriesSkipped,
324
321
  },
325
322
  };
326
323
  }
@@ -346,15 +343,11 @@ export async function akmRemove(input) {
346
343
  config: {
347
344
  sourceCount: getSources(updatedConfig).length,
348
345
  },
349
- // `IndexResponse.directoriesScanned`/`directoriesSkipped` were renamed/
350
- // removed (#index-redesign W6) — this response's own `index` shape is
351
- // unchanged, so bridge from the renamed source field;
352
- // `directoriesSkipped` was already always 0.
353
346
  index: {
354
347
  mode: index.mode,
355
348
  totalEntries: index.totalEntries,
356
- directoriesScanned: index.sourcesScanned,
357
- directoriesSkipped: 0,
349
+ directoriesScanned: index.directoriesScanned,
350
+ directoriesSkipped: index.directoriesSkipped,
358
351
  },
359
352
  };
360
353
  }
@@ -369,13 +362,14 @@ export async function akmRemove(input) {
369
362
  function readCurrentIndexSummary() {
370
363
  const db = openReadonlyExistingDatabase(getDbPath());
371
364
  if (!db) {
372
- return { mode: "incremental", totalEntries: 0, sourcesScanned: 0 };
365
+ return { mode: "incremental", totalEntries: 0, directoriesScanned: 0, directoriesSkipped: 0 };
373
366
  }
374
367
  try {
375
368
  return {
376
369
  mode: "incremental",
377
370
  totalEntries: getAllEntries(db).length,
378
- sourcesScanned: 0,
371
+ directoriesScanned: 0,
372
+ directoriesSkipped: 0,
379
373
  };
380
374
  }
381
375
  finally {
@@ -397,15 +391,11 @@ function buildUpdateResponse(stashDir, target, all, processed, opts) {
397
391
  config: {
398
392
  sourceCount: getSources(finalConfig).length,
399
393
  },
400
- // `IndexResponse.directoriesScanned`/`directoriesSkipped` were renamed/
401
- // removed (#index-redesign W6) — `UpdateResponse["index"]`'s own shape is
402
- // unchanged, so bridge from the renamed source field;
403
- // `directoriesSkipped` was already always 0.
404
394
  index: {
405
395
  mode: index.mode,
406
396
  totalEntries: index.totalEntries,
407
- directoriesScanned: index.sourcesScanned,
408
- directoriesSkipped: 0,
397
+ directoriesScanned: index.directoriesScanned,
398
+ directoriesSkipped: index.directoriesSkipped,
409
399
  ...(index.scanComplete !== undefined ? { scanComplete: index.scanComplete } : {}),
410
400
  // A real embedding pass (`akmIndex`/`runEmbeddingPass`) reports its own
411
401
  // verified `semanticStatus`. When no pass ran this update (the
@@ -473,9 +463,7 @@ function openUnifiedUpdateTransaction() {
473
463
  candidate.exec(`ATTACH DATABASE ${sqliteStringLiteral(statePath)} AS "${UPDATE_STATE_SCHEMA}"`);
474
464
  // openIndexDatabase invokes this before ensureSchema, so the outer
475
465
  // transaction begins before the first update-owned index mutation.
476
- // `candidate` is the index.db connection, so contention here must
477
- // report as INDEX_DB_CONTENDED, not the state.db default.
478
- beginImmediateTransaction(candidate, "index");
466
+ beginImmediateTransaction(candidate);
479
467
  },
480
468
  });
481
469
  return {
@@ -553,15 +541,7 @@ async function runPostCommitEmbeddingPass(index) {
553
541
  return { ...index, verification };
554
542
  }
555
543
  catch (error) {
556
- // Run the failure through the same index.db contention classifier the
557
- // drain catch uses (indexer.ts's reclassifyIndexDbContention) before
558
- // building the message: this catch's first statement, openIndexDatabase
559
- // (init runs ensureSchema and writes), can throw the raw SQLite driver
560
- // error under contention, and this was the last surviving non-fatal
561
- // catch that printed that raw text verbatim to the operator instead of
562
- // reporting it as index.db contention.
563
- const reportedError = reclassifyIndexDbContention(error);
564
- const message = reportedError instanceof Error ? reportedError.message : String(reportedError);
544
+ const message = error instanceof Error ? error.message : String(error);
565
545
  warn(`[akm bundle update] post-commit embedding pass failed: ${message}`);
566
546
  return {
567
547
  ...index,
@@ -662,12 +642,12 @@ async function verifyPublishedAudit(prepared, id) {
662
642
  throw new ConfigError(`Writable source "${id}" changed while its audited Git target was materialized; refusing to activate unaudited bytes.`, "DANGEROUS_ENV_AUDIT_FAILED");
663
643
  }
664
644
  }
665
- async function auditPreparedUpdate(prepared, id, ref, allowInsecure) {
645
+ async function auditPreparedUpdate(prepared, id, ref, allowDangerousEnvKeys) {
666
646
  updateTransactionHook("staged", id);
667
647
  const decision = await auditStashForDangerousKeys({
668
648
  stashRoot: prepared.auditRoot,
669
649
  ref,
670
- allowDangerousKeys: allowInsecure,
650
+ allowDangerousKeys: allowDangerousEnvKeys,
671
651
  isTTY: process.stdin.isTTY === true,
672
652
  operation: "update",
673
653
  renderBlockedError: false,
@@ -734,6 +714,7 @@ function prepareWritableManagedUpdate(managed, force) {
734
714
  writable: true,
735
715
  writableRoot: stagedContentRoot,
736
716
  ...(stagedPhysicalRequiredRoots.length > 0 ? { writableRequiredRoots: stagedPhysicalRequiredRoots } : {}),
717
+ ...(managed.credential ? { credential: resolveSecret(managed.credential, storeSecretResolver.resolveSecret) } : {}),
737
718
  })
738
719
  .then((staged) => {
739
720
  const auditedTargetHead = gitHead(stagedRepo);
@@ -780,6 +761,9 @@ async function prepareManagedUpdate(managed, force) {
780
761
  force,
781
762
  writable: false,
782
763
  cacheRootDir: stagingParent,
764
+ ...(managed.credential
765
+ ? { credential: resolveSecret(managed.credential, storeSecretResolver.resolveSecret) }
766
+ : {}),
783
767
  });
784
768
  if (!pathAtOrBelow(staged.cacheDir, stagingParent)) {
785
769
  // Test/provider seams may return an already-isolated candidate. It remains
@@ -816,9 +800,9 @@ function resolveComponentAuditRoot(contentRoot, componentRoot, id) {
816
800
  }
817
801
  return resolved;
818
802
  }
819
- async function publishPreparedPlainUpdate(id, ref, prepared, stashDir, allowInsecure, full, expectedConfigGeneration) {
803
+ async function publishPreparedPlainUpdate(id, ref, prepared, stashDir, allowDangerousEnvKeys, full, expectedConfigGeneration) {
820
804
  try {
821
- await auditPreparedUpdate(prepared, id, ref, allowInsecure);
805
+ await auditPreparedUpdate(prepared, id, ref, allowDangerousEnvKeys);
822
806
  return await withAssetMutationLease("source-update", async () => {
823
807
  let transaction;
824
808
  let releaseConfigFence;
@@ -892,14 +876,18 @@ async function prepareGitPlainUpdate(gitSource, componentRoot, writable) {
892
876
  const stagingParent = createStagingParent(path.dirname(livePaths.rootDir));
893
877
  const stagedPaths = getCachePaths(repo.canonicalUrl, stagingParent);
894
878
  try {
895
- const expectedOldHead = writable ? gitHead(livePaths.repoDir) : undefined;
896
- if (writable && fs.existsSync(livePaths.rootDir)) {
879
+ const hasLiveCheckout = writable && fs.existsSync(livePaths.repoDir);
880
+ const expectedOldHead = hasLiveCheckout ? gitHead(livePaths.repoDir) : undefined;
881
+ if (hasLiveCheckout) {
897
882
  fs.cpSync(livePaths.rootDir, stagedPaths.rootDir, { recursive: true, preserveTimestamps: true });
898
883
  }
899
884
  const staged = await syncMirroredRepo(gitSource, {
900
885
  force: true,
901
886
  writable,
902
887
  cacheRootDir: stagingParent,
888
+ ...(gitSource.credential
889
+ ? { credential: resolveSecret(gitSource.credential, storeSecretResolver.resolveSecret) }
890
+ : {}),
903
891
  });
904
892
  const configuredStagedAuditRoot = resolveComponentAuditRoot(resolveGitContentRoot(staged.contentDir), componentRoot, gitSource.name ?? "git");
905
893
  if (!pathAtOrBelow(staged.cacheDir, stagingParent)) {
@@ -922,7 +910,13 @@ async function prepareGitPlainUpdate(gitSource, componentRoot, writable) {
922
910
  const physicalStagedRepo = canonicalWritablePath(stagedPaths.repoDir, "staged plain Git checkout");
923
911
  auditRoot = containedWritablePath(configuredStagedAuditRoot, physicalStagedRepo, `Post-sync component root for "${gitSource.name ?? "git"}"`);
924
912
  publishedAuditRoot = remapStagedPath(configuredStagedAuditRoot, staged.cacheDir, livePaths.rootDir);
925
- publishedAuditContainmentRoot = canonicalWritablePath(livePaths.repoDir, "plain Git checkout");
913
+ if (hasLiveCheckout) {
914
+ publishedAuditContainmentRoot = canonicalWritablePath(livePaths.repoDir, "plain Git checkout");
915
+ }
916
+ else {
917
+ const physicalCacheParent = canonicalWritablePath(path.dirname(livePaths.rootDir), "plain Git cache parent");
918
+ publishedAuditContainmentRoot = path.join(physicalCacheParent, path.basename(livePaths.rootDir), path.relative(livePaths.rootDir, livePaths.repoDir));
919
+ }
926
920
  publishedAuditExpectedPhysicalRoot = path.join(publishedAuditContainmentRoot, path.relative(physicalStagedRepo, auditRoot));
927
921
  }
928
922
  return {
@@ -930,7 +924,7 @@ async function prepareGitPlainUpdate(gitSource, componentRoot, writable) {
930
924
  ...(publishedAuditRoot ? { publishedAuditRoot } : {}),
931
925
  ...(publishedAuditContainmentRoot ? { publishedAuditContainmentRoot } : {}),
932
926
  ...(publishedAuditExpectedPhysicalRoot ? { publishedAuditExpectedPhysicalRoot } : {}),
933
- publication: writable
927
+ publication: writable && hasLiveCheckout
934
928
  ? writableGitPublication({
935
929
  stagedRepo: stagedPaths.repoDir,
936
930
  liveRepo: livePaths.repoDir,
@@ -945,14 +939,14 @@ async function prepareGitPlainUpdate(gitSource, componentRoot, writable) {
945
939
  throw error;
946
940
  }
947
941
  }
948
- async function syncGitPlainSource(gitSource, stashDir, allowInsecure) {
942
+ async function syncGitPlainSource(gitSource, stashDir, allowDangerousEnvKeys) {
949
943
  const id = gitSource.name ?? gitSource.url ?? "";
950
944
  const currentConfig = loadConfig();
951
945
  const currentSource = currentPlainSource(currentConfig, id, "git");
952
946
  const ref = currentSource.url ?? "";
953
947
  const fence = bundleAuditFence(currentConfig, id);
954
948
  const prepared = await prepareGitPlainUpdate(currentSource, fence.componentRoot, fence.writable);
955
- const index = await publishPreparedPlainUpdate(id, ref, prepared, stashDir, allowInsecure, true, fence.generation);
949
+ const index = await publishPreparedPlainUpdate(id, ref, prepared, stashDir, allowDangerousEnvKeys, true, fence.generation);
956
950
  return { item: { id, kind: "git", ref }, index };
957
951
  }
958
952
  async function prepareWebsitePlainUpdate(websiteSource, componentRoot) {
@@ -983,24 +977,24 @@ async function prepareWebsitePlainUpdate(websiteSource, componentRoot) {
983
977
  throw error;
984
978
  }
985
979
  }
986
- async function syncWebsitePlainSource(websiteSource, stashDir, allowInsecure) {
980
+ async function syncWebsitePlainSource(websiteSource, stashDir, allowDangerousEnvKeys) {
987
981
  const id = websiteSource.name ?? websiteSource.url ?? "";
988
982
  const currentConfig = loadConfig();
989
983
  const currentSource = currentPlainSource(currentConfig, id, "website");
990
984
  const ref = currentSource.url ?? "";
991
985
  const fence = bundleAuditFence(currentConfig, id);
992
986
  const prepared = await prepareWebsitePlainUpdate(currentSource, fence.componentRoot);
993
- const index = await publishPreparedPlainUpdate(id, ref, prepared, stashDir, allowInsecure, true, fence.generation);
987
+ const index = await publishPreparedPlainUpdate(id, ref, prepared, stashDir, allowDangerousEnvKeys, true, fence.generation);
994
988
  return { item: { id, kind: "website", ref }, index };
995
989
  }
996
990
  /** Sync a git-mirrored (plain) source and return an UpdateResponse (single-target path). */
997
- async function updateGitSource(stashDir, target, all, gitSource, allowInsecure) {
998
- const synced = await syncGitPlainSource(gitSource, stashDir, allowInsecure);
991
+ async function updateGitSource(stashDir, target, all, gitSource, allowDangerousEnvKeys) {
992
+ const synced = await syncGitPlainSource(gitSource, stashDir, allowDangerousEnvKeys);
999
993
  return buildUpdateResponse(stashDir, target, all, [], { plainSynced: [synced.item], index: synced.index });
1000
994
  }
1001
995
  /** Re-crawl a website (plain) source and return an UpdateResponse (single-target path). */
1002
- async function updateWebsiteSource(stashDir, target, all, websiteSource, allowInsecure) {
1003
- const synced = await syncWebsitePlainSource(websiteSource, stashDir, allowInsecure);
996
+ async function updateWebsiteSource(stashDir, target, all, websiteSource, allowDangerousEnvKeys) {
997
+ const synced = await syncWebsitePlainSource(websiteSource, stashDir, allowDangerousEnvKeys);
1004
998
  return buildUpdateResponse(stashDir, target, all, [], { plainSynced: [synced.item], index: synced.index });
1005
999
  }
1006
1000
  /** Reconcile a filesystem source's current bytes without provider hydration. */
@@ -1063,7 +1057,7 @@ function managedInstallViewOfPlainNpm(npmSource) {
1063
1057
  * scripts; only the branch that can `rm -rf` needs a gate, not the whole
1064
1058
  * command).
1065
1059
  */
1066
- async function updateManagedInstall(managed, force, yes, stashDir, allowInsecure) {
1060
+ async function updateManagedInstall(managed, force, yes, stashDir, allowDangerousEnvKeys) {
1067
1061
  // No pre-cleanup of the old root, even under --force: the providers already
1068
1062
  // re-materialize staging-first (git clones into a `.tmp-*` sibling and swaps
1069
1063
  // only on success), so destroying `managed.localRoot` BEFORE `syncFromRef`
@@ -1072,7 +1066,7 @@ async function updateManagedInstall(managed, force, yes, stashDir, allowInsecure
1072
1066
  // config/lock publication and reindexing both succeed.
1073
1067
  const prepared = await prepareManagedUpdate(managed, force);
1074
1068
  try {
1075
- await auditPreparedUpdate(prepared, managed.installId, managed.ref, allowInsecure);
1069
+ await auditPreparedUpdate(prepared, managed.installId, managed.ref, allowDangerousEnvKeys);
1076
1070
  const synced = prepared.synced;
1077
1071
  const installedEntry = {
1078
1072
  id: managed.installId,
@@ -1280,14 +1274,14 @@ export async function akmUpdate(input) {
1280
1274
  const all = input?.all === true;
1281
1275
  const force = input?.force === true;
1282
1276
  const yes = input?.yes === true;
1283
- const allowInsecure = input?.allowInsecure === true;
1277
+ const allowDangerousEnvKeys = input?.allowDangerousEnvKeys === true;
1284
1278
  const config = loadConfig();
1285
1279
  const managedInstalls = listManagedInstalls(config);
1286
1280
  if (target && !all) {
1287
1281
  // Registry-managed install (lock-backed) — re-download from its locator.
1288
1282
  const managed = resolveManagedTarget(config, target);
1289
1283
  if (managed) {
1290
- const updated = await updateManagedInstall(managed, force, yes, stashDir, allowInsecure);
1284
+ const updated = await updateManagedInstall(managed, force, yes, stashDir, allowDangerousEnvKeys);
1291
1285
  return buildUpdateResponse(stashDir, target, all, [updated.item], { index: updated.index });
1292
1286
  }
1293
1287
  // Plain git / website source (bundles without a lock) — provider re-sync.
@@ -1316,7 +1310,7 @@ export async function akmUpdate(input) {
1316
1310
  return false;
1317
1311
  });
1318
1312
  if (gitMatch)
1319
- return updateGitSource(stashDir, target, all, gitMatch, allowInsecure);
1313
+ return updateGitSource(stashDir, target, all, gitMatch, allowDangerousEnvKeys);
1320
1314
  const websiteMatch = stashes.find((s) => {
1321
1315
  if (s.type !== "website")
1322
1316
  return false;
@@ -1329,7 +1323,7 @@ export async function akmUpdate(input) {
1329
1323
  return false;
1330
1324
  });
1331
1325
  if (websiteMatch)
1332
- return updateWebsiteSource(stashDir, target, all, websiteMatch, allowInsecure);
1326
+ return updateWebsiteSource(stashDir, target, all, websiteMatch, allowDangerousEnvKeys);
1333
1327
  // Plain npm source (bundle without a lock) — sync via the registry
1334
1328
  // pipeline and promote to a managed install (see
1335
1329
  // managedInstallViewOfPlainNpm's doc comment for why npm can't stay
@@ -1344,7 +1338,7 @@ export async function akmUpdate(input) {
1344
1338
  return false;
1345
1339
  });
1346
1340
  if (npmMatch) {
1347
- const updated = await updateManagedInstall(managedInstallViewOfPlainNpm(npmMatch), force, yes, stashDir, allowInsecure);
1341
+ const updated = await updateManagedInstall(managedInstallViewOfPlainNpm(npmMatch), force, yes, stashDir, allowDangerousEnvKeys);
1348
1342
  return buildUpdateResponse(stashDir, target, all, [updated.item], { index: updated.index });
1349
1343
  }
1350
1344
  const filesystemMatch = stashes.find((source) => {
@@ -1367,7 +1361,7 @@ export async function akmUpdate(input) {
1367
1361
  let latestIndex;
1368
1362
  for (const managed of selected) {
1369
1363
  try {
1370
- const updated = await updateManagedInstall(managed, force, yes, stashDir, allowInsecure);
1364
+ const updated = await updateManagedInstall(managed, force, yes, stashDir, allowDangerousEnvKeys);
1371
1365
  processed.push(updated.item);
1372
1366
  latestIndex = updated.index;
1373
1367
  }
@@ -1392,17 +1386,17 @@ export async function akmUpdate(input) {
1392
1386
  const id = plain.name ?? plain.path ?? plain.url ?? "";
1393
1387
  try {
1394
1388
  if (plain.type === "git") {
1395
- const updated = await syncGitPlainSource(plain, stashDir, allowInsecure);
1389
+ const updated = await syncGitPlainSource(plain, stashDir, allowDangerousEnvKeys);
1396
1390
  plainSynced.push(updated.item);
1397
1391
  latestIndex = updated.index;
1398
1392
  }
1399
1393
  else if (plain.type === "npm") {
1400
- const updated = await updateManagedInstall(managedInstallViewOfPlainNpm(plain), force, yes, stashDir, allowInsecure);
1394
+ const updated = await updateManagedInstall(managedInstallViewOfPlainNpm(plain), force, yes, stashDir, allowDangerousEnvKeys);
1401
1395
  processed.push(updated.item);
1402
1396
  latestIndex = updated.index;
1403
1397
  }
1404
1398
  else if (plain.type === "website") {
1405
- const updated = await syncWebsitePlainSource(plain, stashDir, allowInsecure);
1399
+ const updated = await syncWebsitePlainSource(plain, stashDir, allowDangerousEnvKeys);
1406
1400
  plainSynced.push(updated.item);
1407
1401
  latestIndex = updated.index;
1408
1402
  }
@@ -6,7 +6,7 @@ import path from "node:path";
6
6
  import { detectAdapterId } from "../../core/adapter/detect-adapter.js";
7
7
  import { isBundleSlug } from "../../core/asset/asset-ref.js";
8
8
  import { isHttpUrl, resolveStashDir } from "../../core/common.js";
9
- import { bundleEntryToSourceEntry, getSources, installedSourceDescriptor, loadConfig, mutateConfig, } from "../../core/config/config.js";
9
+ import { bundleEntryToSourceEntry, getSources, installedSourceDescriptor, loadConfig, mutateConfig, resolveSecret, } from "../../core/config/config.js";
10
10
  import { ConfigError, UsageError } from "../../core/errors.js";
11
11
  import { akmIndex } from "../../indexer/indexer.js";
12
12
  import { deriveBundleId } from "../../indexer/installations.js";
@@ -16,7 +16,8 @@ import { detectStashRoot } from "../../sources/providers/provider-utils.js";
16
16
  import { syncFromRef } from "../../sources/providers/sync-from-ref.js";
17
17
  import { storeSecretResolver } from "../../sources/snapshot-fetchers/secret-seam.js";
18
18
  import { ensureWebsiteMirror, shouldAllowPrivateWebsiteUrlForTests, validateWebsiteInputUrl, } from "../../sources/snapshot-fetchers/website-ingest.js";
19
- import { bundleKeyForPath, bundleKeyForUrl, nextBundleKey } from "./bundle-config-ops.js";
19
+ import { revokeSchedulerActivationsForBundle } from "../../tasks/activation-config.js";
20
+ import { bundleKeyForPath, bundleKeyForUrl, nextBundleKey, placeBundle, } from "./bundle-config-ops.js";
20
21
  export async function akmAdd(input) {
21
22
  const ref = input.ref.trim();
22
23
  if (!ref)
@@ -24,23 +25,23 @@ export async function akmAdd(input) {
24
25
  "Examples: `akm bundle add @scope/stash`, `akm bundle add github:owner/repo`, `akm bundle add ./local/path`");
25
26
  const stashDir = resolveStashDir();
26
27
  if (shouldAddAsWebsiteUrl(ref)) {
27
- return addWebsiteSource(ref, stashDir, input.name, input.options);
28
+ return addWebsiteSource(ref, stashDir, input.name, input.options, input);
28
29
  }
29
30
  // Local directories become filesystem bundles; registry refs use the
30
31
  // registry-backed bundle installer below.
31
32
  try {
32
33
  const parsed = parseRegistryRef(ref);
33
34
  if (parsed.source === "local") {
34
- return addLocalSource(ref, parsed.sourcePath, stashDir, input.name, input.adapter);
35
+ return addLocalSource(ref, parsed.sourcePath, stashDir, input.name, input.adapter, input);
35
36
  }
36
37
  }
37
38
  catch {
38
39
  // Not a local ref — fall through to registry install
39
40
  }
40
- return addRegistryStash(ref, stashDir, input.writable);
41
+ return addRegistryStash(ref, stashDir, input.writable, input, input.credential);
41
42
  }
42
43
  /** Add a local directory as a filesystem bundle. */
43
- async function addLocalSource(ref, sourcePath, stashDir, explicitName, explicitAdapter) {
44
+ async function addLocalSource(ref, sourcePath, stashDir, explicitName, explicitAdapter, position = {}) {
44
45
  const stashRoot = detectStashRoot(sourcePath);
45
46
  const resolvedPath = path.resolve(stashRoot);
46
47
  const adapter = explicitAdapter ?? detectAdapterId(resolvedPath);
@@ -62,11 +63,11 @@ async function addLocalSource(ref, sourcePath, stashDir, explicitName, explicitA
62
63
  }
63
64
  const bundles = { ...(config.bundles ?? {}) };
64
65
  bundleKey = nextBundleKey(bundles, explicitName, resolvedPath);
65
- bundles[bundleKey] = {
66
+ const entry = {
66
67
  path: resolvedPath,
67
68
  components: { main: { root: ".", adapter } },
68
69
  };
69
- return { ...config, bundles };
70
+ return { ...config, bundles: placeBundle(bundles, bundleKey, entry, position) };
70
71
  });
71
72
  const index = await akmIndex({ stashDir });
72
73
  const updatedConfig = loadConfig();
@@ -86,18 +87,13 @@ async function addLocalSource(ref, sourcePath, stashDir, explicitName, explicitA
86
87
  index: {
87
88
  mode: index.mode,
88
89
  totalEntries: index.totalEntries,
89
- // `IndexResponse.directoriesScanned`/`directoriesSkipped` were renamed/
90
- // removed (#index-redesign W6: reconcile is a flat per-file stat walk,
91
- // never a directory walk) — this response's own `directoriesScanned`/
92
- // `directoriesSkipped` shape is unchanged, so bridge from the renamed
93
- // source field; `directoriesSkipped` was already always 0.
94
- directoriesScanned: index.sourcesScanned,
95
- directoriesSkipped: 0,
90
+ directoriesScanned: index.directoriesScanned,
91
+ directoriesSkipped: index.directoriesSkipped,
96
92
  ...(index.warnings?.length ? { warnings: index.warnings } : {}),
97
93
  },
98
94
  };
99
95
  }
100
- async function addWebsiteSource(ref, stashDir, name, options) {
96
+ async function addWebsiteSource(ref, stashDir, name, options, position = {}) {
101
97
  const allowPrivateHosts = shouldAllowPrivateWebsiteUrlForTests(ref);
102
98
  const normalizedUrl = validateWebsiteInputUrl(ref, { allowPrivateHosts });
103
99
  const numberOption = (value) => (typeof value === "number" ? value : undefined);
@@ -129,9 +125,9 @@ async function addWebsiteSource(ref, stashDir, name, options) {
129
125
  entry = bundleEntryToSourceEntry(key, bundles[key]);
130
126
  return config;
131
127
  }
132
- bundles[key] = nextBundle;
128
+ const nextBundles = placeBundle(bundles, key, nextBundle, position);
133
129
  entry = bundleEntryToSourceEntry(key, nextBundle);
134
- return { ...config, bundles };
130
+ return { ...config, bundles: nextBundles };
135
131
  });
136
132
  const cachePaths = await ensureWebsiteMirror(entry, {
137
133
  requireStashDir: true,
@@ -156,13 +152,8 @@ async function addWebsiteSource(ref, stashDir, name, options) {
156
152
  index: {
157
153
  mode: index.mode,
158
154
  totalEntries: index.totalEntries,
159
- // `IndexResponse.directoriesScanned`/`directoriesSkipped` were renamed/
160
- // removed (#index-redesign W6: reconcile is a flat per-file stat walk,
161
- // never a directory walk) — this response's own `directoriesScanned`/
162
- // `directoriesSkipped` shape is unchanged, so bridge from the renamed
163
- // source field; `directoriesSkipped` was already always 0.
164
- directoriesScanned: index.sourcesScanned,
165
- directoriesSkipped: 0,
155
+ directoriesScanned: index.directoriesScanned,
156
+ directoriesSkipped: index.directoriesSkipped,
166
157
  ...(index.warnings?.length ? { warnings: index.warnings } : {}),
167
158
  },
168
159
  };
@@ -171,11 +162,14 @@ async function addWebsiteSource(ref, stashDir, name, options) {
171
162
  * Install a stash from a registry (npm, github, git) by dispatching to the
172
163
  * matching syncable provider and persisting the lock entry.
173
164
  */
174
- async function addRegistryStash(ref, stashDir, writable) {
165
+ async function addRegistryStash(ref, stashDir, writable, position = {}, credentialRef) {
175
166
  const parsedRef = parseRegistryRef(ref);
176
167
  if (writable === true && parsedRef.source !== "git" && parsedRef.source !== "github") {
177
168
  throw new ConfigError("writable: true is only supported on filesystem and git sources", "INVALID_CONFIG_FILE");
178
169
  }
170
+ if (credentialRef && parsedRef.source !== "git" && parsedRef.source !== "github") {
171
+ throw new ConfigError("credential is only supported on git sources", "INVALID_CONFIG_FILE");
172
+ }
179
173
  const currentConfig = loadConfig();
180
174
  const existingBundleKey = findInstalledBundleKey(currentConfig.bundles ?? {}, parsedRef.id);
181
175
  const existingBundle = existingBundleKey ? currentConfig.bundles?.[existingBundleKey] : undefined;
@@ -189,6 +183,11 @@ async function addRegistryStash(ref, stashDir, writable) {
189
183
  writable: effectiveWritable,
190
184
  ...(effectiveWritable && priorLock?.localRoot ? { writableRoot: priorLock.localRoot } : {}),
191
185
  ...(requiredRoots.length > 0 ? { writableRequiredRoots: requiredRoots } : {}),
186
+ ...(credentialRef
187
+ ? { credential: resolveSecret(credentialRef, storeSecretResolver.resolveSecret) }
188
+ : existingBundle?.credential
189
+ ? { credential: resolveSecret(existingBundle.credential, storeSecretResolver.resolveSecret) }
190
+ : {}),
192
191
  });
193
192
  const { config: updatedConfig, bundleId } = upsertInstalledRegistryEntry({
194
193
  id: synced.id,
@@ -201,7 +200,7 @@ async function addRegistryStash(ref, stashDir, writable) {
201
200
  cacheDir: synced.cacheDir,
202
201
  installedAt: synced.syncedAt,
203
202
  writable: synced.writable,
204
- });
203
+ }, position, credentialRef);
205
204
  // The prior materialized root (if this is a re-install) — read BEFORE the lock
206
205
  // upsert overwrites it, so a moved cache root can be cleaned afterwards.
207
206
  const priorLocalRoot = priorLock?.localRoot;
@@ -248,13 +247,8 @@ async function addRegistryStash(ref, stashDir, writable) {
248
247
  index: {
249
248
  mode: index.mode,
250
249
  totalEntries: index.totalEntries,
251
- // `IndexResponse.directoriesScanned`/`directoriesSkipped` were renamed/
252
- // removed (#index-redesign W6: reconcile is a flat per-file stat walk,
253
- // never a directory walk) — this response's own `directoriesScanned`/
254
- // `directoriesSkipped` shape is unchanged, so bridge from the renamed
255
- // source field; `directoriesSkipped` was already always 0.
256
- directoriesScanned: index.sourcesScanned,
257
- directoriesSkipped: 0,
250
+ directoriesScanned: index.directoriesScanned,
251
+ directoriesSkipped: index.directoriesSkipped,
258
252
  ...(index.warnings?.length ? { warnings: index.warnings } : {}),
259
253
  },
260
254
  };
@@ -267,7 +261,7 @@ async function addRegistryStash(ref, stashDir, writable) {
267
261
  * via {@link upsertLockEntry} with the returned `bundleId`). Returns the config
268
262
  * plus the derived bundle id so the caller keys its lock entry identically.
269
263
  */
270
- export function upsertInstalledRegistryEntry(entry) {
264
+ export function upsertInstalledRegistryEntry(entry, position = {}, credential) {
271
265
  let bundleId = entry.id;
272
266
  const config = mutateConfig((current) => {
273
267
  const bundles = { ...(current.bundles ?? {}) };
@@ -286,13 +280,15 @@ export function upsertInstalledRegistryEntry(entry) {
286
280
  },
287
281
  };
288
282
  const descriptor = installedSourceDescriptor(entry.source, entry.ref, path.resolve(entry.stashRoot));
289
- bundles[bundleId] = {
283
+ const effectiveCredential = credential ?? bundles[bundleId]?.credential;
284
+ const nextEntry = {
290
285
  ...descriptor,
286
+ ...(effectiveCredential ? { credential: effectiveCredential } : {}),
291
287
  ...(entry.writable === true ? { writable: true } : {}),
292
288
  ...(entry.id !== bundleId ? { registryId: entry.id } : {}),
293
289
  components: components,
294
290
  };
295
- return { ...current, bundles };
291
+ return { ...current, bundles: placeBundle(bundles, bundleId, nextEntry, position) };
296
292
  }).config;
297
293
  return { config, bundleId };
298
294
  }
@@ -310,7 +306,12 @@ export async function removeInstalledRegistryEntry(id) {
310
306
  return current;
311
307
  removedKey = key;
312
308
  delete bundles[key];
313
- return { ...current, bundles: Object.keys(bundles).length > 0 ? bundles : undefined };
309
+ return revokeSchedulerActivationsForBundle({
310
+ ...current,
311
+ bundles: Object.keys(bundles).length > 0 ? bundles : undefined,
312
+ ...(current.defaultBundle === key ? { defaultBundle: undefined } : {}),
313
+ ...(current.defaultWriteTarget === key ? { defaultWriteTarget: undefined } : {}),
314
+ }, key);
314
315
  }).config;
315
316
  if (removedKey)
316
317
  await removeLockEntry(removedKey);