akm-cli 0.9.26 → 0.9.27-alpha.1

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.
@@ -5,7 +5,7 @@ import fs from "node:fs";
5
5
  import path from "node:path";
6
6
  import { detectAdapterId } from "../core/adapter/detect-adapter.js";
7
7
  import { adapterForId } from "../core/adapter/registry.js";
8
- import { isHttpUrl } from "../core/common.js";
8
+ import { compareCodePoints, isHttpUrl } from "../core/common.js";
9
9
  import { classifyPathAccess, describeInaccessiblePath } from "../core/path-access.js";
10
10
  import { getDbPath } from "../core/paths.js";
11
11
  import { SCRIPT_EXTENSIONS } from "../core/recognition-util.js";
@@ -14,6 +14,7 @@ import { isVerbose, warn, warnOnce, warnVerbose } from "../core/warn.js";
14
14
  import { resolveSourcesForOrigin } from "../registry/origin-resolve.js";
15
15
  import { closeDatabase, openExistingDatabase, openIndexDatabase, openReadonlyExistingDatabase, } from "../storage/repositories/index-connection.js";
16
16
  import { deleteEntriesByBundle, deleteEntriesByDirAndBundle, deleteEntriesByDirExceptRefs, deleteEntriesByIds, deleteUsageEventsByEntryIds, findEntryIdByRef, getAllEntries, getEmbeddableEntryCount, getEntryCount, getIndexedBundleIdsByDir, getIndexedDirPathsByBundleId, relinkUsageEvents, upsertEntry, } from "../storage/repositories/index-entries-repository.js";
17
+ import { rebuildFtsIfTotalsStale } from "../storage/repositories/index-fts-repository.js";
17
18
  import { clearStaleCacheEntries } from "../storage/repositories/index-llm-cache-repository.js";
18
19
  import { deleteIndexDirState, deleteMeta, getIndexDirState, getMeta, setMeta, upsertIndexDirState, } from "../storage/repositories/index-meta-repository.js";
19
20
  import { VACUUM_PENDING_META } from "../storage/repositories/index-schema.js";
@@ -125,7 +126,14 @@ export async function runEmbeddingPass(params) {
125
126
  */
126
127
  function finalizeIndex(args) {
127
128
  const { db, sources, sourceDirs, stashDir, signal, onProgress } = args;
128
- onProgress({ phase: "fts", message: "Full-text search index is current." });
129
+ // Rows replaced or removed since its BM25 totals were taken leave them off; recompute them (#1048).
130
+ const rebuilt = rebuildFtsIfTotalsStale(db);
131
+ onProgress({
132
+ phase: "fts",
133
+ message: rebuilt
134
+ ? "Rebuilt the full-text search index to recompute its totals."
135
+ : "Full-text search index is current.",
136
+ });
129
137
  const tFtsEnd = Date.now();
130
138
  // Re-link state.db usage events to the regenerated index and recompute the
131
139
  // derived utility cache. Stored refs already use the current item-ref grammar,
@@ -907,11 +915,6 @@ function requiresWorkflowSourcePreflight(ctxs) {
907
915
  }
908
916
  /** Phase 2 (sync): write all pre-generated scan records inside a single transaction. */
909
917
  function persistDirRecords(db, dirRecords, warnings, bundleByRoot) {
910
- // Per-source dedup: the same logical asset can appear more than once within
911
- // one owning source, where source order still makes the first occurrence win.
912
- // The owner is part of the key so identical concepts in different bundles
913
- // remain distinct indexed rows.
914
- const indexedAssetIdentities = new Set();
915
918
  const deletedUsageEntryIds = new Set();
916
919
  const findPersisted = db.prepare("SELECT id, content_hash, file_path, adapter_id FROM entries WHERE item_ref = ?");
917
920
  const insertTransaction = db.transaction(() => {
@@ -961,7 +964,7 @@ function persistDirRecords(db, dirRecords, warnings, bundleByRoot) {
961
964
  let persistedRows = 0;
962
965
  let dedupedRows = 0;
963
966
  if (stash) {
964
- const ownerIdentity = bundle.bundleId;
967
+ const sourceRoot = `${path.resolve(currentStashDir)}${path.sep}`;
965
968
  for (const entry of stash.entries) {
966
969
  const entryPath = entry.filename ? path.join(dirPath, entry.filename) : null;
967
970
  if (!entryPath) {
@@ -973,23 +976,36 @@ function persistDirRecords(db, dirRecords, warnings, bundleByRoot) {
973
976
  warn(`Skipping entry without adapter-owned concept identity: ${entryPath}`);
974
977
  continue;
975
978
  }
976
- // Adapter-owned concept identity is path-based and cannot be replaced
977
- // by presentation fields such as type/title.
978
- const identityKey = `${ownerIdentity}\0${adapterConceptId}`;
979
- if (indexedAssetIdentities.has(identityKey)) {
980
- dedupedRows++;
981
- continue;
982
- }
983
- indexedAssetIdentities.add(identityKey);
984
979
  // content_hash = doc.hash from the drain, keyed by the recognized
985
980
  // file's path. A missing hash preserves the existing value on upsert.
986
981
  const contentHash = hashByFile?.get(entryPath);
982
+ // Adapter-owned concept identity is path-based and cannot be replaced
983
+ // by presentation fields such as type/title.
987
984
  const provenance = deriveEntryProvenance(bundle, entry.type, entry.name, adapterConceptId);
985
+ // Two files of one source can claim one ref (a skill's references/a.md and
986
+ // knowledge/skills/x/references/a.md are both knowledge/skills/x/references/a). The file with
987
+ // the smaller path holds it, whichever directories this run drains and in whatever order the
988
+ // walk met them (#1050): an entry yields to a row held by a file with a smaller path and
989
+ // otherwise takes the ref over. The same concept in another bundle is a different row, and a
990
+ // holder whose file is gone is no claim.
991
+ const holder = findPersisted.get(provenance.itemRef) ?? undefined;
992
+ if (holder &&
993
+ holder.file_path !== entryPath &&
994
+ holder.file_path.startsWith(sourceRoot) &&
995
+ fs.existsSync(holder.file_path)) {
996
+ const yields = compareCodePoints(holder.file_path, entryPath) < 0;
997
+ const [indexed, skipped] = yields ? [holder.file_path, entryPath] : [entryPath, holder.file_path];
998
+ warnings.push(`Two files claim ${provenance.itemRef}: indexed ${indexed}, skipped ${skipped}.`);
999
+ if (yields) {
1000
+ dedupedRows++;
1001
+ continue;
1002
+ }
1003
+ // The directory the ref comes from now has a row fewer than its files: drain it again.
1004
+ deleteIndexDirState(db, path.dirname(holder.file_path));
1005
+ }
988
1006
  keptItemRefs.add(provenance.itemRef);
989
1007
  persistedRows++;
990
- const previous = sameVariant
991
- ? (findPersisted.get(provenance.itemRef) ?? undefined)
992
- : undefined;
1008
+ const previous = sameVariant ? holder : undefined;
993
1009
  const unchanged = previous !== undefined &&
994
1010
  contentHash !== undefined &&
995
1011
  previous.content_hash === contentHash &&
@@ -19,12 +19,13 @@
19
19
  * `./result-extractor.ts`. Always emitted, mirroring how the Claude builder
20
20
  * always emits `--print`: dispatch is the captured, non-interactive path.
21
21
  * - Codex is the NATIVE-SCHEMA tier (plan §"Structured-output
22
- * normalization"): `req.schema` is written to a temp file and passed via
23
- * `--output-schema <file>`. The file is tiny, uniquely named under the OS
24
- * temp dir, and intentionally NOT cleaned up here — `BuiltCommand` has no
25
- * post-run hook, and the spawned process reads the file after `build()`
26
- * returns. OS temp reaping owns the lifecycle. The engine still validates
27
- * the output defensively (the constrained output is trusted but verified).
22
+ * normalization"): `req.schema` is written to a file and passed via
23
+ * `--output-schema <file>`. The file is named by the hash of the schema
24
+ * and lives in akm's cache dir, so a schema is written once however many
25
+ * units dispatch with it: `BuiltCommand` has no post-run hook, and the
26
+ * spawned process reads the file after `build()` returns, so a file per
27
+ * dispatch could only leak (#1051). The engine still validates the output
28
+ * defensively (the constrained output is trusted but verified).
28
29
  * - `codex exec` has no system-prompt flag; `req.systemPrompt` is folded
29
30
  * into the prompt payload (system text first, blank line, then the task),
30
31
  * after the `--` end-of-options separator so it can never be parsed as a
@@ -52,22 +53,29 @@
52
53
  * that registry, so this builder is reachable under the `"codex"` platform
53
54
  * name without any further wiring.
54
55
  */
55
- import { mkdtempSync, writeFileSync } from "node:fs";
56
- import { tmpdir } from "node:os";
56
+ import { mkdirSync } from "node:fs";
57
57
  import { join } from "node:path";
58
+ import { writeFileAtomic } from "../../../core/common.js";
59
+ import { getCacheDir } from "../../../core/paths.js";
60
+ import { sha256Hex } from "../../../runtime.js";
58
61
  import { resolveDispatchModel } from "../../agent/builder-shared.js";
59
62
  import { createAgentRequestLowerer } from "../../agent/request-lowering.js";
60
63
  /**
61
- * Write a node's JSON Schema to a fresh temp file for `--output-schema`.
64
+ * Write a node's JSON Schema to a file for `--output-schema`, named by the hash
65
+ * of its text, in akm's cache dir. Returns the absolute file path (the value
66
+ * handed to the flag).
62
67
  *
63
- * A unique `mkdtemp` directory per build avoids collisions between concurrent
64
- * fan-out units dispatching in the same process. Returns the absolute file
65
- * path (the value handed to the flag).
68
+ * The same schema is the same file, so concurrent fan-out units share it, and
69
+ * the atomic rename makes a write of identical bytes safe under a reader: no
70
+ * unit can see a half-written schema, and none removes it from another. Nothing
71
+ * is cleaned up, because nothing accumulates but one file per distinct schema.
66
72
  */
67
73
  export function writeCodexOutputSchemaFile(schema) {
68
- const dir = mkdtempSync(join(tmpdir(), "akm-codex-schema-"));
69
- const file = join(dir, "output-schema.json");
70
- writeFileSync(file, `${JSON.stringify(schema, null, 2)}\n`, "utf8");
74
+ const text = `${JSON.stringify(schema, null, 2)}\n`;
75
+ const dir = join(getCacheDir(), "codex-output-schemas");
76
+ mkdirSync(dir, { recursive: true });
77
+ const file = join(dir, `${sha256Hex(text)}.json`);
78
+ writeFileAtomic(file, text);
71
79
  return file;
72
80
  }
73
81
  /**
@@ -222,12 +222,18 @@ async function chatCompletionReal(config, messages, options) {
222
222
  * a config/endpoint change can never leave a stale verdict in place.
223
223
  */
224
224
  const jsonSchemaUnsupportedConnections = new Set();
225
+ /**
226
+ * The same, for the two thinking-control fields (`chat_template_kwargs` and
227
+ * `enable_thinking`): a strict hosted API answers 400 `Unknown parameter`.
228
+ */
229
+ const thinkingControlRejectedConnections = new Set();
225
230
  function connectionKey(config) {
226
231
  return `${config.endpoint}|${config.model}`;
227
232
  }
228
- /** TEST-ONLY. Clear the in-memory json-schema-support tracker between tests. */
229
- export function _resetJsonSchemaSupportTrackerForTests() {
233
+ /** TEST-ONLY. Clear the in-memory endpoint-support trackers between tests. */
234
+ export function _resetEndpointSupportTrackersForTests() {
230
235
  jsonSchemaUnsupportedConnections.clear();
236
+ thinkingControlRejectedConnections.clear();
231
237
  }
232
238
  /**
233
239
  * Whether this connection has already been proven, this process, not to
@@ -239,6 +245,24 @@ export function _resetJsonSchemaSupportTrackerForTests() {
239
245
  export function isJsonSchemaKnownUnsupported(config) {
240
246
  return jsonSchemaUnsupportedConnections.has(connectionKey(config));
241
247
  }
248
+ /** Whether a real call this process showed this connection rejects the thinking-control fields (#1045). */
249
+ export function isThinkingControlKnownRejected(config) {
250
+ return thinkingControlRejectedConnections.has(connectionKey(config));
251
+ }
252
+ /**
253
+ * Whether `err` is a provider's 4xx (a rate limit is not one) that may be a
254
+ * verdict on the shape of this request rather than on the request's own content.
255
+ */
256
+ function isRejectedRequest(err) {
257
+ return (err instanceof LlmCallError &&
258
+ err.code === "provider_error" &&
259
+ typeof err.statusCode === "number" &&
260
+ err.statusCode >= 400 &&
261
+ err.statusCode < 500 &&
262
+ err.statusCode !== 429);
263
+ }
264
+ /** A strict API names the field it does not know: `Unknown parameter: 'chat_template_kwargs'.` */
265
+ const THINKING_FIELD_NAMED = /chat_template_kwargs|enable_thinking/;
242
266
  /**
243
267
  * A single chat-completion attempt: one HTTP request/response cycle, with an
244
268
  * inline fallback-once when a schema was requested and the provider 4xx's
@@ -248,29 +272,35 @@ export function isJsonSchemaKnownUnsupported(config) {
248
272
  * `config.supportsJsonSchema === false` is the only thing that skips it,
249
273
  * and that is a value a human (or workflow author) set explicitly, not one
250
274
  * a probe wrote automatically.
275
+ *
276
+ * The thinking-control fields get the same treatment (#1045): a 4xx that
277
+ * names `chat_template_kwargs` or `enable_thinking` is answered by one retry
278
+ * without them, which may in turn fall back without the schema.
251
279
  */
252
- async function chatCompletionAttempt(config, messages, options, timeoutMs) {
280
+ async function chatCompletionAttempt(config, messages, options, timeoutMs, includeThinking = (options?.enableThinking ?? config.enableThinking) !== undefined &&
281
+ !isThinkingControlKnownRejected(config)) {
253
282
  const wantsSchema = Boolean(options?.responseSchema) && config.supportsJsonSchema !== false && !isJsonSchemaKnownUnsupported(config);
254
283
  try {
255
- return await chatCompletionAttemptOnce(config, messages, options, timeoutMs, wantsSchema);
284
+ return await chatCompletionAttemptOnce(config, messages, options, timeoutMs, wantsSchema, includeThinking);
256
285
  }
257
286
  catch (err) {
258
- if (!wantsSchema ||
259
- !(err instanceof LlmCallError) ||
260
- err.code !== "provider_error" ||
261
- typeof err.statusCode !== "number" ||
262
- err.statusCode < 400 ||
263
- err.statusCode >= 500 ||
264
- err.statusCode === 429) {
287
+ if (!isRejectedRequest(err))
265
288
  throw err;
289
+ if (includeThinking && THINKING_FIELD_NAMED.test(err.message)) {
290
+ warnVerbose(`[akm] LLM rejected chat_template_kwargs/enable_thinking (${err.statusCode}); retrying once without them: ${err.message}`);
291
+ const fallback = await chatCompletionAttempt(config, messages, options, timeoutMs, false);
292
+ thinkingControlRejectedConnections.add(connectionKey(config));
293
+ return fallback;
266
294
  }
295
+ if (!wantsSchema)
296
+ throw err;
267
297
  warnVerbose(`[akm] LLM rejected response_format:json_schema (${err.statusCode}); retrying once without it: ${err.message}`);
268
- const fallback = await chatCompletionAttemptOnce(config, messages, options, timeoutMs, false);
298
+ const fallback = await chatCompletionAttemptOnce(config, messages, options, timeoutMs, false, includeThinking);
269
299
  jsonSchemaUnsupportedConnections.add(connectionKey(config));
270
300
  return fallback;
271
301
  }
272
302
  }
273
- async function chatCompletionAttemptOnce(config, messages, options, timeoutMs, includeSchema) {
303
+ async function chatCompletionAttemptOnce(config, messages, options, timeoutMs, includeSchema, includeThinking) {
274
304
  if (config.extraParams !== undefined) {
275
305
  const issue = validateExtraParams(config.extraParams)[0];
276
306
  if (issue)
@@ -309,7 +339,7 @@ async function chatCompletionAttemptOnce(config, messages, options, timeoutMs, i
309
339
  // (freellmapi, Bifrost) can drop either one depending on how it was built.
310
340
  // Send both whenever thinking is explicitly resolved so the same engine
311
341
  // block keeps working across a direct vhost or any gateway in front of it.
312
- const resolvedEnableThinking = options?.enableThinking ?? config.enableThinking;
342
+ const resolvedEnableThinking = includeThinking ? (options?.enableThinking ?? config.enableThinking) : undefined;
313
343
  const thinkingParams = resolvedEnableThinking === undefined
314
344
  ? {}
315
345
  : { chat_template_kwargs: { enable_thinking: resolvedEnableThinking }, enable_thinking: resolvedEnableThinking };
@@ -46,7 +46,7 @@ const PROMPT_PLACEHOLDERS = new Set([
46
46
  * — no more "incomplete derived memory payload from LLM; skipping memory"
47
47
  * for shape-only failures.
48
48
  */
49
- const DERIVED_MEMORY_JSON_SCHEMA = {
49
+ export const DERIVED_MEMORY_JSON_SCHEMA = {
50
50
  type: "object",
51
51
  properties: {
52
52
  title: { type: "string", minLength: 1 },
@@ -25639,6 +25639,7 @@ var $visitAsync = visit.visitAsync;
25639
25639
  import path7 from "node:path";
25640
25640
  // src/runtime.ts
25641
25641
  import { spawn as nodeSpawn, spawnSync as nodeSpawnSync } from "node:child_process";
25642
+ import { createHash } from "node:crypto";
25642
25643
  import { createWriteStream, statfsSync } from "node:fs";
25643
25644
  import { createRequire as createRequire2 } from "node:module";
25644
25645
  import path6 from "node:path";
@@ -26803,6 +26804,9 @@ async function writeResponseToFileCapped(filePath, res, options) {
26803
26804
  reader.releaseLock?.();
26804
26805
  }
26805
26806
  }
26807
+ function sha256Hex(data) {
26808
+ return createHash("sha256").update(data).digest("hex");
26809
+ }
26806
26810
  function getDirname(importMetaUrl) {
26807
26811
  return path6.dirname(fileURLToPath(importMetaUrl));
26808
26812
  }
@@ -36849,7 +36853,7 @@ function withConfigLock(fn) {
36849
36853
  }
36850
36854
 
36851
36855
  // src/core/config/config-sources.ts
36852
- import { createHash } from "node:crypto";
36856
+ import { createHash as createHash2 } from "node:crypto";
36853
36857
  import fs9 from "node:fs";
36854
36858
  import path13 from "node:path";
36855
36859
  function bundleComponentConfig(bundle) {
@@ -36912,7 +36916,7 @@ function filesystemBundleSourceId(contentRoot) {
36912
36916
  });
36913
36917
  }
36914
36918
  function hashBundleSourceIdentity(identity3) {
36915
- return `sha256:${createHash("sha256").update("akm.bundle-source\x00v1\x00").update(JSON.stringify(identity3)).digest("hex")}`;
36919
+ return `sha256:${createHash2("sha256").update("akm.bundle-source\x00v1\x00").update(JSON.stringify(identity3)).digest("hex")}`;
36916
36920
  }
36917
36921
  function bundleContentRoots(config) {
36918
36922
  const bundles = config.bundles ?? {};
@@ -38374,7 +38378,7 @@ async function tryFetchJson(url, headers) {
38374
38378
  }
38375
38379
 
38376
38380
  // src/sources/providers/provider-utils.ts
38377
- import { createHash as createHash4, randomUUID as randomUUID2 } from "node:crypto";
38381
+ import { createHash as createHash5, randomUUID as randomUUID2 } from "node:crypto";
38378
38382
  import fs27 from "node:fs";
38379
38383
  import path34 from "node:path";
38380
38384
 
@@ -38469,7 +38473,7 @@ function countLines(text) {
38469
38473
  }
38470
38474
 
38471
38475
  // src/core/adapter/adapters/shared.ts
38472
- import { createHash as createHash2 } from "node:crypto";
38476
+ import { createHash as createHash3 } from "node:crypto";
38473
38477
 
38474
38478
  // src/core/asset/frontmatter-lint.ts
38475
38479
  function checkUnquotedDescriptionColon(frontmatterText) {
@@ -38491,7 +38495,7 @@ function checkUnquotedDescriptionColon(frontmatterText) {
38491
38495
 
38492
38496
  // src/core/adapter/adapters/shared.ts
38493
38497
  function hashContent(content) {
38494
- return createHash2("sha256").update(content, "utf8").digest("hex");
38498
+ return createHash3("sha256").update(content, "utf8").digest("hex");
38495
38499
  }
38496
38500
  function nonEmptyString2(value) {
38497
38501
  if (typeof value !== "string")
@@ -39674,7 +39678,7 @@ function extractDirTagsFromName(name) {
39674
39678
  }
39675
39679
 
39676
39680
  // src/core/adapter/execution-source.ts
39677
- import { createHash as createHash3 } from "node:crypto";
39681
+ import { createHash as createHash4 } from "node:crypto";
39678
39682
 
39679
39683
  // src/execution/source.ts
39680
39684
  var EXECUTION_SOURCE_SCHEMA_VERSION = 1;
@@ -40012,7 +40016,7 @@ function renderMarkdownExecutionSource(input) {
40012
40016
  bundle: identity3.bundle,
40013
40017
  adapter: identity3.adapter,
40014
40018
  file: identity3.file,
40015
- hash: createHash3("sha256").update(raw, "utf8").digest("hex")
40019
+ hash: createHash4("sha256").update(raw, "utf8").digest("hex")
40016
40020
  },
40017
40021
  ...hasDefaults ? { defaults } : {},
40018
40022
  ...extensions === undefined ? {} : { extensions }
@@ -46567,7 +46571,7 @@ function uniqueSlug() {
46567
46571
  }
46568
46572
  function buildInstallCacheDir(cacheRootDir, source, id, version) {
46569
46573
  const safeId = id.replace(/[^a-zA-Z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "");
46570
- const identitySuffix = version === "writable" ? `-${createHash4("sha256").update(`${source}:${id}`).digest("hex").slice(0, 12)}` : "";
46574
+ const identitySuffix = version === "writable" ? `-${createHash5("sha256").update(`${source}:${id}`).digest("hex").slice(0, 12)}` : "";
46571
46575
  const slug = `${source}-${safeId}${identitySuffix}`;
46572
46576
  const versionSlug = source === "local" ? uniqueSlug() : version?.replace(/[^a-zA-Z0-9_.-]+/g, "-") ?? uniqueSlug();
46573
46577
  return path34.join(cacheRootDir, slug || source, versionSlug);
@@ -46588,7 +46592,7 @@ async function downloadArchive(url, destination) {
46588
46592
  }
46589
46593
  async function computeFileHash(filePath) {
46590
46594
  const data = fs27.readFileSync(filePath);
46591
- const hash = createHash4("sha256").update(data).digest("hex");
46595
+ const hash = createHash5("sha256").update(data).digest("hex");
46592
46596
  return `sha256:${hash}`;
46593
46597
  }
46594
46598
  function isDirectory(target) {
@@ -46763,7 +46767,7 @@ function redactUrlUserinfo(text) {
46763
46767
  return text.replace(/\b([A-Za-z][A-Za-z0-9+.-]*:\/\/)([^\s/@]+)@/g, "$1[REDACTED]@");
46764
46768
  }
46765
46769
  // src/sources/providers/git-provider.ts
46766
- import { createHash as createHash5 } from "node:crypto";
46770
+ import { createHash as createHash6 } from "node:crypto";
46767
46771
  import fs30 from "node:fs";
46768
46772
  import path36 from "node:path";
46769
46773
 
@@ -46824,7 +46828,7 @@ function resolveGitContentDir(config) {
46824
46828
  throw new ConfigError("git source entry must have either `path` or `url`");
46825
46829
  }
46826
46830
  function getCachePaths(repoUrl, cacheRootOverride) {
46827
- const key = createHash5("sha256").update(repoUrl).digest("hex").slice(0, 16);
46831
+ const key = createHash6("sha256").update(repoUrl).digest("hex").slice(0, 16);
46828
46832
  const cacheRoot = cacheRootOverride ?? getRegistryIndexCacheDir();
46829
46833
  const rootDir = path36.join(cacheRoot, `git-${key}`);
46830
46834
  return {
@@ -47045,7 +47049,7 @@ import fs33 from "node:fs";
47045
47049
  import path39 from "node:path";
47046
47050
 
47047
47051
  // src/sources/providers/tar-utils.ts
47048
- import { createHash as createHash6 } from "node:crypto";
47052
+ import { createHash as createHash7 } from "node:crypto";
47049
47053
  import fs32 from "node:fs";
47050
47054
  import path38 from "node:path";
47051
47055
  function verifyArchiveIntegrity(archivePath, expected, source) {
@@ -47058,7 +47062,7 @@ function verifyArchiveIntegrity(archivePath, expected, source) {
47058
47062
  const dashIndex = expected.indexOf("-");
47059
47063
  const algorithm = expected.slice(0, dashIndex);
47060
47064
  const expectedBase64 = expected.slice(dashIndex + 1);
47061
- const actualBase64 = createHash6(algorithm).update(fileBuffer).digest("base64");
47065
+ const actualBase64 = createHash7(algorithm).update(fileBuffer).digest("base64");
47062
47066
  if (actualBase64 !== expectedBase64) {
47063
47067
  fs32.unlinkSync(archivePath);
47064
47068
  throw new Error(`Integrity check failed for ${archivePath}: expected ${algorithm} digest ${expectedBase64}, got ${actualBase64}`);
@@ -47066,7 +47070,7 @@ function verifyArchiveIntegrity(archivePath, expected, source) {
47066
47070
  return;
47067
47071
  }
47068
47072
  if (/^[0-9a-f]{40}$/i.test(expected)) {
47069
- const actualHex = createHash6("sha1").update(fileBuffer).digest("hex");
47073
+ const actualHex = createHash7("sha1").update(fileBuffer).digest("hex");
47070
47074
  if (actualHex.toLowerCase() !== expected.toLowerCase()) {
47071
47075
  fs32.unlinkSync(archivePath);
47072
47076
  throw new Error(`Integrity check failed for ${archivePath}: expected sha1 ${expected}, got ${actualHex}`);
@@ -51669,14 +51673,15 @@ class ClaudeHarness extends BaseHarness {
51669
51673
  }
51670
51674
 
51671
51675
  // src/integrations/harnesses/codex/agent-builder.ts
51672
- import { mkdtempSync, writeFileSync } from "node:fs";
51673
- import { tmpdir } from "node:os";
51676
+ import { mkdirSync } from "node:fs";
51674
51677
  import { join } from "node:path";
51675
51678
  function writeCodexOutputSchemaFile(schema) {
51676
- const dir = mkdtempSync(join(tmpdir(), "akm-codex-schema-"));
51677
- const file = join(dir, "output-schema.json");
51678
- writeFileSync(file, `${JSON.stringify(schema, null, 2)}
51679
- `, "utf8");
51679
+ const text = `${JSON.stringify(schema, null, 2)}
51680
+ `;
51681
+ const dir = join(getCacheDir(), "codex-output-schemas");
51682
+ mkdirSync(dir, { recursive: true });
51683
+ const file = join(dir, `${sha256Hex(text)}.json`);
51684
+ writeFileAtomic(file, text);
51680
51685
  return file;
51681
51686
  }
51682
51687
  function ensureSandboxFlags(base) {
@@ -24967,6 +24967,7 @@ var $visitAsync = visit.visitAsync;
24967
24967
  import path7 from "path";
24968
24968
  // src/runtime.ts
24969
24969
  import { spawn as nodeSpawn, spawnSync as nodeSpawnSync } from "child_process";
24970
+ import { createHash } from "crypto";
24970
24971
  import { createWriteStream, statfsSync } from "fs";
24971
24972
  import { createRequire } from "module";
24972
24973
  import path6 from "path";
@@ -26131,6 +26132,9 @@ async function writeResponseToFileCapped(filePath, res, options) {
26131
26132
  reader.releaseLock?.();
26132
26133
  }
26133
26134
  }
26135
+ function sha256Hex(data) {
26136
+ return createHash("sha256").update(data).digest("hex");
26137
+ }
26134
26138
  function getDirname(importMetaUrl) {
26135
26139
  return path6.dirname(fileURLToPath(importMetaUrl));
26136
26140
  }
@@ -36177,7 +36181,7 @@ function withConfigLock(fn) {
36177
36181
  }
36178
36182
 
36179
36183
  // src/core/config/config-sources.ts
36180
- import { createHash } from "crypto";
36184
+ import { createHash as createHash2 } from "crypto";
36181
36185
  import fs9 from "fs";
36182
36186
  import path13 from "path";
36183
36187
  function bundleComponentConfig(bundle) {
@@ -36240,7 +36244,7 @@ function filesystemBundleSourceId(contentRoot) {
36240
36244
  });
36241
36245
  }
36242
36246
  function hashBundleSourceIdentity(identity3) {
36243
- return `sha256:${createHash("sha256").update("akm.bundle-source\x00v1\x00").update(JSON.stringify(identity3)).digest("hex")}`;
36247
+ return `sha256:${createHash2("sha256").update("akm.bundle-source\x00v1\x00").update(JSON.stringify(identity3)).digest("hex")}`;
36244
36248
  }
36245
36249
  function bundleContentRoots(config) {
36246
36250
  const bundles = config.bundles ?? {};
@@ -37702,7 +37706,7 @@ async function tryFetchJson(url, headers) {
37702
37706
  }
37703
37707
 
37704
37708
  // src/sources/providers/provider-utils.ts
37705
- import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
37709
+ import { createHash as createHash5, randomUUID as randomUUID2 } from "crypto";
37706
37710
  import fs27 from "fs";
37707
37711
  import path34 from "path";
37708
37712
 
@@ -37797,7 +37801,7 @@ function countLines(text) {
37797
37801
  }
37798
37802
 
37799
37803
  // src/core/adapter/adapters/shared.ts
37800
- import { createHash as createHash2 } from "crypto";
37804
+ import { createHash as createHash3 } from "crypto";
37801
37805
 
37802
37806
  // src/core/asset/frontmatter-lint.ts
37803
37807
  function checkUnquotedDescriptionColon(frontmatterText) {
@@ -37819,7 +37823,7 @@ function checkUnquotedDescriptionColon(frontmatterText) {
37819
37823
 
37820
37824
  // src/core/adapter/adapters/shared.ts
37821
37825
  function hashContent(content) {
37822
- return createHash2("sha256").update(content, "utf8").digest("hex");
37826
+ return createHash3("sha256").update(content, "utf8").digest("hex");
37823
37827
  }
37824
37828
  function nonEmptyString2(value) {
37825
37829
  if (typeof value !== "string")
@@ -39002,7 +39006,7 @@ function extractDirTagsFromName(name) {
39002
39006
  }
39003
39007
 
39004
39008
  // src/core/adapter/execution-source.ts
39005
- import { createHash as createHash3 } from "crypto";
39009
+ import { createHash as createHash4 } from "crypto";
39006
39010
 
39007
39011
  // src/execution/source.ts
39008
39012
  var EXECUTION_SOURCE_SCHEMA_VERSION = 1;
@@ -39340,7 +39344,7 @@ function renderMarkdownExecutionSource(input) {
39340
39344
  bundle: identity3.bundle,
39341
39345
  adapter: identity3.adapter,
39342
39346
  file: identity3.file,
39343
- hash: createHash3("sha256").update(raw, "utf8").digest("hex")
39347
+ hash: createHash4("sha256").update(raw, "utf8").digest("hex")
39344
39348
  },
39345
39349
  ...hasDefaults ? { defaults } : {},
39346
39350
  ...extensions === undefined ? {} : { extensions }
@@ -45895,7 +45899,7 @@ function uniqueSlug() {
45895
45899
  }
45896
45900
  function buildInstallCacheDir(cacheRootDir, source, id, version) {
45897
45901
  const safeId = id.replace(/[^a-zA-Z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "");
45898
- const identitySuffix = version === "writable" ? `-${createHash4("sha256").update(`${source}:${id}`).digest("hex").slice(0, 12)}` : "";
45902
+ const identitySuffix = version === "writable" ? `-${createHash5("sha256").update(`${source}:${id}`).digest("hex").slice(0, 12)}` : "";
45899
45903
  const slug = `${source}-${safeId}${identitySuffix}`;
45900
45904
  const versionSlug = source === "local" ? uniqueSlug() : version?.replace(/[^a-zA-Z0-9_.-]+/g, "-") ?? uniqueSlug();
45901
45905
  return path34.join(cacheRootDir, slug || source, versionSlug);
@@ -45916,7 +45920,7 @@ async function downloadArchive(url, destination) {
45916
45920
  }
45917
45921
  async function computeFileHash(filePath) {
45918
45922
  const data = fs27.readFileSync(filePath);
45919
- const hash = createHash4("sha256").update(data).digest("hex");
45923
+ const hash = createHash5("sha256").update(data).digest("hex");
45920
45924
  return `sha256:${hash}`;
45921
45925
  }
45922
45926
  function isDirectory(target) {
@@ -46091,7 +46095,7 @@ function redactUrlUserinfo(text) {
46091
46095
  return text.replace(/\b([A-Za-z][A-Za-z0-9+.-]*:\/\/)([^\s/@]+)@/g, "$1[REDACTED]@");
46092
46096
  }
46093
46097
  // src/sources/providers/git-provider.ts
46094
- import { createHash as createHash5 } from "crypto";
46098
+ import { createHash as createHash6 } from "crypto";
46095
46099
  import fs30 from "fs";
46096
46100
  import path36 from "path";
46097
46101
 
@@ -46152,7 +46156,7 @@ function resolveGitContentDir(config) {
46152
46156
  throw new ConfigError("git source entry must have either `path` or `url`");
46153
46157
  }
46154
46158
  function getCachePaths(repoUrl, cacheRootOverride) {
46155
- const key = createHash5("sha256").update(repoUrl).digest("hex").slice(0, 16);
46159
+ const key = createHash6("sha256").update(repoUrl).digest("hex").slice(0, 16);
46156
46160
  const cacheRoot = cacheRootOverride ?? getRegistryIndexCacheDir();
46157
46161
  const rootDir = path36.join(cacheRoot, `git-${key}`);
46158
46162
  return {
@@ -46373,7 +46377,7 @@ import fs33 from "fs";
46373
46377
  import path39 from "path";
46374
46378
 
46375
46379
  // src/sources/providers/tar-utils.ts
46376
- import { createHash as createHash6 } from "crypto";
46380
+ import { createHash as createHash7 } from "crypto";
46377
46381
  import fs32 from "fs";
46378
46382
  import path38 from "path";
46379
46383
  function verifyArchiveIntegrity(archivePath, expected, source) {
@@ -46386,7 +46390,7 @@ function verifyArchiveIntegrity(archivePath, expected, source) {
46386
46390
  const dashIndex = expected.indexOf("-");
46387
46391
  const algorithm = expected.slice(0, dashIndex);
46388
46392
  const expectedBase64 = expected.slice(dashIndex + 1);
46389
- const actualBase64 = createHash6(algorithm).update(fileBuffer).digest("base64");
46393
+ const actualBase64 = createHash7(algorithm).update(fileBuffer).digest("base64");
46390
46394
  if (actualBase64 !== expectedBase64) {
46391
46395
  fs32.unlinkSync(archivePath);
46392
46396
  throw new Error(`Integrity check failed for ${archivePath}: expected ${algorithm} digest ${expectedBase64}, got ${actualBase64}`);
@@ -46394,7 +46398,7 @@ function verifyArchiveIntegrity(archivePath, expected, source) {
46394
46398
  return;
46395
46399
  }
46396
46400
  if (/^[0-9a-f]{40}$/i.test(expected)) {
46397
- const actualHex = createHash6("sha1").update(fileBuffer).digest("hex");
46401
+ const actualHex = createHash7("sha1").update(fileBuffer).digest("hex");
46398
46402
  if (actualHex.toLowerCase() !== expected.toLowerCase()) {
46399
46403
  fs32.unlinkSync(archivePath);
46400
46404
  throw new Error(`Integrity check failed for ${archivePath}: expected sha1 ${expected}, got ${actualHex}`);
@@ -51664,14 +51668,15 @@ class ClaudeHarness extends BaseHarness {
51664
51668
  }
51665
51669
 
51666
51670
  // src/integrations/harnesses/codex/agent-builder.ts
51667
- import { mkdtempSync, writeFileSync } from "fs";
51668
- import { tmpdir } from "os";
51671
+ import { mkdirSync } from "fs";
51669
51672
  import { join as join2 } from "path";
51670
51673
  function writeCodexOutputSchemaFile(schema) {
51671
- const dir = mkdtempSync(join2(tmpdir(), "akm-codex-schema-"));
51672
- const file = join2(dir, "output-schema.json");
51673
- writeFileSync(file, `${JSON.stringify(schema, null, 2)}
51674
- `, "utf8");
51674
+ const text = `${JSON.stringify(schema, null, 2)}
51675
+ `;
51676
+ const dir = join2(getCacheDir(), "codex-output-schemas");
51677
+ mkdirSync(dir, { recursive: true });
51678
+ const file = join2(dir, `${sha256Hex(text)}.json`);
51679
+ writeFileAtomic(file, text);
51675
51680
  return file;
51676
51681
  }
51677
51682
  function ensureSandboxFlags(base) {
@@ -120,20 +120,36 @@ const REQUIRED_ENTRY_COLUMNS = [
120
120
  "document_json",
121
121
  "derived_from",
122
122
  ];
123
+ /**
124
+ * The transitional columns layout 20 and earlier kept beside the current ones
125
+ * and layout 21 removed (`entry_key` was the key). They are NOT NULL, so a
126
+ * table that still has one takes none of this release's inserts, and its
127
+ * `document_json` is empty (the entry sat in `entry_json`), so none of its rows
128
+ * can be read either. A 0.9.1 index is such a table.
129
+ */
130
+ const RETIRED_ENTRY_COLUMNS = ["entry_key", "dir_path", "stash_dir", "entry_json", "entry_type"];
131
+ function entryColumnNames(db) {
132
+ return new Set(db.prepare("PRAGMA table_info(entries)").all().map((c) => c.name));
133
+ }
123
134
  /** Required `entries` columns the table lacks; every column when there is no `entries` table. */
124
135
  export function missingEntryColumns(db) {
125
- const present = new Set(db.prepare("PRAGMA table_info(entries)").all().map((c) => c.name));
136
+ const present = entryColumnNames(db);
126
137
  return REQUIRED_ENTRY_COLUMNS.filter((column) => !present.has(column));
127
138
  }
139
+ /** Retired `entries` columns the table still has: it was written by layout 20 or earlier. */
140
+ export function retiredEntryColumns(db) {
141
+ const present = entryColumnNames(db);
142
+ return RETIRED_ENTRY_COLUMNS.filter((column) => present.has(column));
143
+ }
128
144
  /**
129
145
  * Whether an index database has an `entries` table this release can read. A
130
146
  * fresh or empty file has none; an index older than layout 21 has one keyed
131
- * by columns this release no longer reads, and serves nothing until the next
132
- * `akm index` recreates it.
147
+ * by columns this release no longer reads (or lacks the ones it does), and
148
+ * serves nothing until the next `akm index` recreates it.
133
149
  */
134
150
  export function hasCurrentEntriesTable(db) {
135
151
  try {
136
- return missingEntryColumns(db).length === 0;
152
+ return missingEntryColumns(db).length === 0 && retiredEntryColumns(db).length === 0;
137
153
  }
138
154
  catch {
139
155
  return false;