akm-cli 0.9.13 → 0.9.14

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.
@@ -9467,6 +9467,53 @@ function projectMarkdownContent(body, truncationInfo) {
9467
9467
  truncationInfo.truncated = text.length > MARKDOWN_CONTENT_MAX_CHARS;
9468
9468
  return truncateUnicodeSafe(text, MARKDOWN_CONTENT_MAX_CHARS);
9469
9469
  }
9470
+ function setMarkdownFragmentContent(entry, content) {
9471
+ markdownFragmentProjectionEntries.add(entry);
9472
+ if (content)
9473
+ markdownFragmentContentByEntry.set(entry, content);
9474
+ }
9475
+ function getMarkdownFragmentContent(entry) {
9476
+ return markdownFragmentContentByEntry.get(entry);
9477
+ }
9478
+ function hasMarkdownFragmentContent(entry) {
9479
+ return markdownFragmentProjectionEntries.has(entry);
9480
+ }
9481
+ function projectMarkdownFragmentContent(raw) {
9482
+ const lines = raw.split(/\r?\n/);
9483
+ const parsed = parseFrontmatter(raw);
9484
+ const start = parsed.frontmatter ? parsed.bodyStartLine - 1 : 0;
9485
+ const projected = lines.map(() => "");
9486
+ let fence;
9487
+ const htmlComment = { inComment: false };
9488
+ for (let index = start;index < lines.length; index++) {
9489
+ const rawLine = lines[index];
9490
+ if (fence) {
9491
+ if (isMarkdownFenceClosing(rawLine, fence))
9492
+ fence = undefined;
9493
+ continue;
9494
+ }
9495
+ if (!htmlComment.inComment) {
9496
+ const opening2 = parseMarkdownFenceOpening(rawLine);
9497
+ if (opening2) {
9498
+ fence = opening2;
9499
+ continue;
9500
+ }
9501
+ }
9502
+ let safe = stripMarkdownHtmlComments(rawLine, htmlComment);
9503
+ const opening = parseMarkdownFenceOpening(safe.trim());
9504
+ if (opening) {
9505
+ fence = opening;
9506
+ continue;
9507
+ }
9508
+ if (/^\s*\[[^\]]+\]:\s*\S+/.test(safe) || /^\s*<[^>]+>\s*$/.test(safe))
9509
+ continue;
9510
+ safe = stripMarkdownLinkDestinations(safe).replace(/<[^>]+>/g, " ");
9511
+ projected[index] = safe.replace(/[ \t]+$/g, "");
9512
+ }
9513
+ const text = projected.join(`
9514
+ `);
9515
+ return text.trim() ? text : undefined;
9516
+ }
9470
9517
  function applyPreContributorFields(entry, file, ctx, pkgMeta) {
9471
9518
  const ext = path19.extname(file).toLowerCase();
9472
9519
  if (pkgMeta) {
@@ -9487,7 +9534,9 @@ function applyPreContributorFields(entry, file, ctx, pkgMeta) {
9487
9534
  entry.parameters = fmParams;
9488
9535
  applyWikiFrontmatter(entry, parsed.data);
9489
9536
  applyProvenanceFrontmatter(entry, parsed.data);
9490
- if (entry.type !== "env" && entry.type !== "session" && !hasSessionMemoryMarker(parsed.data, parsed.content)) {
9537
+ const safeForFragments = entry.type !== "env" && entry.type !== "session" && !hasSessionMemoryMarker(parsed.data, parsed.content);
9538
+ setMarkdownFragmentContent(entry, safeForFragments ? projectMarkdownFragmentContent(content) : undefined);
9539
+ if (safeForFragments) {
9491
9540
  const truncationInfo = { truncated: false };
9492
9541
  const contentProjection = projectMarkdownContent(parsed.content, truncationInfo);
9493
9542
  if (contentProjection) {
@@ -9605,7 +9654,7 @@ function extractDirTagsFromName(name) {
9605
9654
  }
9606
9655
  return Array.from(tags);
9607
9656
  }
9608
- var SCOPE_KEYS, KNOWN_QUALITY_VALUES, warnedUnknownQualityValues, MARKDOWN_CONTENT_MAX_CHARS = 1e6, MAX_MARKDOWN_LINK_NESTING = 32;
9657
+ var SCOPE_KEYS, KNOWN_QUALITY_VALUES, warnedUnknownQualityValues, MARKDOWN_CONTENT_MAX_CHARS = 1e6, MAX_MARKDOWN_LINK_NESTING = 32, markdownFragmentContentByEntry, markdownFragmentProjectionEntries;
9609
9658
  var init_metadata = __esm(() => {
9610
9659
  init_asset_ref();
9611
9660
  init_frontmatter();
@@ -9614,6 +9663,8 @@ var init_metadata = __esm(() => {
9614
9663
  SCOPE_KEYS = ["user", "agent", "run", "channel"];
9615
9664
  KNOWN_QUALITY_VALUES = new Set(["generated", "curated", "enriched", "proposed"]);
9616
9665
  warnedUnknownQualityValues = new Set;
9666
+ markdownFragmentContentByEntry = new WeakMap;
9667
+ markdownFragmentProjectionEntries = new WeakSet;
9617
9668
  });
9618
9669
 
9619
9670
  // src/execution/record.ts
@@ -12187,6 +12238,9 @@ var init_schema2 = __esm(() => {
12187
12238
  });
12188
12239
 
12189
12240
  // src/core/asset/markdown.ts
12241
+ function markdownHeadingSlug(heading) {
12242
+ return heading.trim().toLowerCase().replace(/<[^>]*>/g, "").replace(/[^\p{L}\p{N}\s_-]+/gu, "-").replace(/[\s_]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
12243
+ }
12190
12244
  function parseMarkdownToc(content) {
12191
12245
  const lines = content.split(/\r?\n/);
12192
12246
  const headings = [];
@@ -36292,7 +36346,7 @@ var require_libvips = __commonJS((exports, module) => {
36292
36346
  SPDX-License-Identifier: Apache-2.0
36293
36347
  */
36294
36348
  var { spawnSync: spawnSync6 } = __require("node:child_process");
36295
- var { createHash: createHash9 } = __require("node:crypto");
36349
+ var { createHash: createHash10 } = __require("node:crypto");
36296
36350
  var semverCoerce = require_coerce2();
36297
36351
  var semverGreaterThanOrEqualTo = require_gte2();
36298
36352
  var semverSatisfies = require_satisfies2();
@@ -36380,7 +36434,7 @@ var require_libvips = __commonJS((exports, module) => {
36380
36434
  }
36381
36435
  return false;
36382
36436
  };
36383
- var sha512 = (s) => createHash9("sha512").update(s).digest("hex");
36437
+ var sha512 = (s) => createHash10("sha512").update(s).digest("hex");
36384
36438
  var yarnLocator = () => {
36385
36439
  try {
36386
36440
  const identHash = sha512(`imgsharp-libvips-${buildPlatformArch()}`);
@@ -50672,10 +50726,10 @@ var import_sharp, __dirname = "/home/runner/work/akm/akm/node_modules/@huggingfa
50672
50726
  if (cached) {
50673
50727
  return cached.text();
50674
50728
  }
50675
- const hash3 = await this._getLfsFileHash(url2);
50676
- if (hash3) {
50677
- await hashCache.put(url2, new Response(hash3));
50678
- return hash3;
50729
+ const hash4 = await this._getLfsFileHash(url2);
50730
+ if (hash4) {
50731
+ await hashCache.put(url2, new Response(hash4));
50732
+ return hash4;
50679
50733
  }
50680
50734
  return null;
50681
50735
  } catch {
@@ -79284,7 +79338,7 @@ function listTxnJournalsTolerant(predicate) {
79284
79338
  }
79285
79339
 
79286
79340
  // src/commands/proposal/repository.ts
79287
- import { createHash as createHash9, randomUUID as randomUUID6 } from "node:crypto";
79341
+ import { createHash as createHash10, randomUUID as randomUUID6 } from "node:crypto";
79288
79342
  import fs49 from "node:fs";
79289
79343
  init_dist();
79290
79344
  import path61 from "node:path";
@@ -80192,20 +80246,41 @@ function buildWorkflowAction(ref) {
80192
80246
  return `Start or resume execution with \`akm workflow run ${shellQuote(ref)}\`.`;
80193
80247
  }
80194
80248
  var TYPE_PRESENTATION = {
80195
- skill: { label: "Skill", renderer: "skill-md", action: (ref) => `akm show ${ref} -> follow the instructions` },
80249
+ skill: {
80250
+ label: "Skill",
80251
+ renderer: "skill-md",
80252
+ action: (ref) => `akm show ${ref} -> follow the instructions`,
80253
+ fragmentRef: false
80254
+ },
80196
80255
  command: {
80197
80256
  label: "Command",
80198
80257
  renderer: "command-md",
80199
- action: (ref) => `akm show ${ref} -> fill placeholders and dispatch`
80258
+ action: (ref) => `akm show ${ref} -> fill placeholders and dispatch`,
80259
+ fragmentRef: false
80260
+ },
80261
+ agent: {
80262
+ label: "Agent",
80263
+ renderer: "agent-md",
80264
+ action: (ref) => `akm show ${ref} -> dispatch with full prompt`,
80265
+ fragmentRef: false
80200
80266
  },
80201
- agent: { label: "Agent", renderer: "agent-md", action: (ref) => `akm show ${ref} -> dispatch with full prompt` },
80202
80267
  knowledge: {
80203
80268
  label: "Knowledge",
80204
80269
  renderer: "knowledge-md",
80205
80270
  action: (ref) => `akm show ${ref} -> read reference material`
80206
80271
  },
80207
- workflow: { label: "Workflow", renderer: "workflow-md", action: (ref) => buildWorkflowAction(ref) },
80208
- script: { label: "Script", renderer: "script-source", action: (ref) => `akm show ${ref} -> execute the run command` },
80272
+ workflow: {
80273
+ label: "Workflow",
80274
+ renderer: "workflow-md",
80275
+ action: (ref) => buildWorkflowAction(ref),
80276
+ fragmentRef: false
80277
+ },
80278
+ script: {
80279
+ label: "Script",
80280
+ renderer: "script-source",
80281
+ action: (ref) => `akm show ${ref} -> execute the run command`,
80282
+ fragmentRef: false
80283
+ },
80209
80284
  memory: { label: "Memory", renderer: "memory-md", action: (ref) => `akm show ${ref} -> recall context` },
80210
80285
  env: {
80211
80286
  label: "Env",
@@ -80225,7 +80300,8 @@ var TYPE_PRESENTATION = {
80225
80300
  task: {
80226
80301
  label: "Task",
80227
80302
  renderer: "task-yaml",
80228
- action: (ref) => `akm show ${ref} -> inspect; akm task run <id> -> run now; edit the file + akm task sync -> unschedule`
80303
+ action: (ref) => `akm show ${ref} -> inspect; akm task run <id> -> run now; edit the file + akm task sync -> unschedule`,
80304
+ fragmentRef: false
80229
80305
  },
80230
80306
  session: {
80231
80307
  label: "Session",
@@ -80240,7 +80316,8 @@ var TYPE_PRESENTATION = {
80240
80316
  instruction: {
80241
80317
  label: "Instruction",
80242
80318
  renderer: "knowledge-md",
80243
- action: (ref) => `akm show ${ref} -> read the project instructions`
80319
+ action: (ref) => `akm show ${ref} -> read the project instructions`,
80320
+ fragmentRef: false
80244
80321
  }
80245
80322
  };
80246
80323
  var DEFAULT_PRESENTATION = { label: "Asset" };
@@ -83166,6 +83243,8 @@ function indexDocumentFromEntry(entry, base3, rendererName) {
83166
83243
  doc.lessonStrength = entry.lessonStrength;
83167
83244
  if (entry.derivedFrom !== undefined)
83168
83245
  doc.derivedFrom = entry.derivedFrom;
83246
+ if (hasMarkdownFragmentContent(entry))
83247
+ setMarkdownFragmentContent(doc, getMarkdownFragmentContent(entry));
83169
83248
  return doc;
83170
83249
  }
83171
83250
  function conceptIdForRecognizedType(root, filePath, type) {
@@ -92716,7 +92795,7 @@ init_paths();
92716
92795
  init_warn();
92717
92796
 
92718
92797
  // src/storage/repositories/index-entry-schema.ts
92719
- var CANONICAL_INDEX_DB_VERSION = 22;
92798
+ var CANONICAL_INDEX_DB_VERSION = 23;
92720
92799
  var CANONICAL_ENTRY_SCHEMA_FINGERPRINT = {
92721
92800
  tableSql: "CREATE TABLE entries ( id INTEGER PRIMARY KEY AUTOINCREMENT, item_ref TEXT NOT NULL UNIQUE, bundle_id TEXT NOT NULL, component_id TEXT NOT NULL, concept_id TEXT NOT NULL, adapter_id TEXT NOT NULL, type TEXT NOT NULL, file_path TEXT NOT NULL, content_hash TEXT, document_json TEXT NOT NULL, search_text TEXT NOT NULL, derived_from TEXT )",
92722
92801
  sqliteSequenceTable: true,
@@ -92866,7 +92945,12 @@ var CANONICAL_ENTRY_SCHEMA_FINGERPRINT = {
92866
92945
  { sequence: 1, cid: -1, name: null, descending: 0, collation: "BINARY", key: 0 }
92867
92946
  ]
92868
92947
  }
92869
- ]
92948
+ ],
92949
+ searchSurfaces: {
92950
+ entriesFtsSql: "CREATE VIRTUAL TABLE entries_fts USING fts5( entry_id UNINDEXED, name, description, tags, hints, content, tokenize='porter unicode61' )",
92951
+ fragmentSourceSql: "CREATE TABLE entry_fragments ( entry_id INTEGER PRIMARY KEY REFERENCES entries(id) ON DELETE CASCADE, safe_markdown TEXT NOT NULL )",
92952
+ fragmentsFtsSql: "CREATE VIRTUAL TABLE entry_fragments_fts USING fts5( entry_id UNINDEXED, fragment_id UNINDEXED, fragment_ordinal UNINDEXED, content, tokenize='porter unicode61' )"
92953
+ }
92870
92954
  };
92871
92955
  function sqlString(value) {
92872
92956
  return `'${value.replaceAll("'", "''")}'`;
@@ -92876,8 +92960,11 @@ function normalizeSchemaSql(value) {
92876
92960
  return null;
92877
92961
  return value.replace(/\s+/g, " ").trim();
92878
92962
  }
92963
+ function readNamedTableSql(db, name) {
92964
+ const row = db.prepare(`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ${sqlString(name)}`).get();
92965
+ return normalizeSchemaSql(row?.sql);
92966
+ }
92879
92967
  function readEntrySchemaFingerprint(db) {
92880
- const tableRow = db.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'entries'").get();
92881
92968
  const sqliteSequenceTable = db.prepare("SELECT 1 AS present FROM sqlite_master WHERE type = 'table' AND name = 'sqlite_sequence'").get() != null;
92882
92969
  const maxId = Number(db.prepare("SELECT COALESCE(MAX(id), 0) AS maxId FROM entries").get().maxId);
92883
92970
  const sequenceRow = sqliteSequenceTable ? db.prepare("SELECT seq FROM sqlite_sequence WHERE name = 'entries'").get() : undefined;
@@ -92906,11 +92993,16 @@ function readEntrySchemaFingerprint(db) {
92906
92993
  }))
92907
92994
  })).sort((left, right) => left.name.localeCompare(right.name));
92908
92995
  return {
92909
- tableSql: normalizeSchemaSql(tableRow?.sql),
92996
+ tableSql: readNamedTableSql(db, "entries"),
92910
92997
  sqliteSequenceTable,
92911
92998
  sqliteSequenceValid,
92912
92999
  columns,
92913
- indexes
93000
+ indexes,
93001
+ searchSurfaces: {
93002
+ entriesFtsSql: readNamedTableSql(db, "entries_fts"),
93003
+ fragmentSourceSql: readNamedTableSql(db, "entry_fragments"),
93004
+ fragmentsFtsSql: readNamedTableSql(db, "entry_fragments_fts")
93005
+ }
92914
93006
  };
92915
93007
  }
92916
93008
  function hasCanonicalEntrySchema(db) {
@@ -93113,20 +93205,30 @@ function openExistingDatabase(dbPath) {
93113
93205
  if (classifyPathAccess(resolvedPath).access === "absent") {
93114
93206
  throw new Error(`Index database not found at ${resolvedPath}. Run 'akm index' to build it.`);
93115
93207
  }
93116
- return openManagedDatabase({
93208
+ const db = openManagedDatabase({
93117
93209
  path: resolvedPath,
93118
- init: (db) => {
93119
- loadVecExtension(db);
93120
- warnIfNonCanonicalIndexGeneration(db, resolvedPath);
93210
+ init: (db2) => {
93211
+ loadVecExtension(db2);
93121
93212
  },
93122
93213
  create: false
93123
93214
  });
93215
+ try {
93216
+ assertCanonicalIndexGeneration(db, resolvedPath);
93217
+ return db;
93218
+ } catch (error2) {
93219
+ db.close();
93220
+ throw error2;
93221
+ }
93124
93222
  }
93125
- function warnIfNonCanonicalIndexGeneration(db, resolvedPath) {
93223
+ function assertCanonicalIndexGeneration(db, resolvedPath) {
93126
93224
  if (isCanonicalIndexGeneration(db))
93127
93225
  return;
93128
93226
  const classification = classifyIndexGeneration(db);
93129
- warnOnce(`index-read-noncanonical:${resolvedPath}`, `Index database at ${resolvedPath} does not match this akm's derived schema (stored generation ` + `${classification.storedVersion ?? "unknown"}; this binary understands ${CANONICAL_INDEX_DB_VERSION}). ` + "Reading it as-is; a query that needs a table or column this generation lacks will fail on its own. " + "Run 'akm index' to rebuild it for this binary.");
93227
+ const stored = classification.storedVersion ?? "unknown";
93228
+ if (classification.status === "newer") {
93229
+ throw new ConfigError(`Index database at ${resolvedPath} was built by a newer akm (stored generation ${stored}; ` + `this binary understands ${CANONICAL_INDEX_DB_VERSION}). Upgrade akm to use this index.`, "INDEX_SCHEMA_INCOMPATIBLE", "Upgrade akm to a version that understands this index generation.");
93230
+ }
93231
+ throw new ConfigError(`Index database at ${resolvedPath} is not usable with this akm's derived schema (stored generation ${stored}; ` + `this binary understands ${CANONICAL_INDEX_DB_VERSION}). Run 'akm index' to rebuild it.`, "INDEX_SCHEMA_INCOMPATIBLE", "Run `akm index` to rebuild the derived index from the currently materialized sources.");
93130
93232
  }
93131
93233
  function assertIndexPathReadable(resolvedPath) {
93132
93234
  const { access, code } = classifyPathAccess(resolvedPath);
@@ -93143,6 +93245,7 @@ import fs40 from "node:fs";
93143
93245
  init_asset_ref();
93144
93246
  init_resolve_ref();
93145
93247
  init_warn();
93248
+ init_metadata();
93146
93249
 
93147
93250
  // src/indexer/search/search-fields.ts
93148
93251
  function buildSearchFields(entry) {
@@ -93206,9 +93309,129 @@ function buildSearchText(entry) {
93206
93309
  // src/storage/repositories/index-entry-mapper.ts
93207
93310
  init_warn();
93208
93311
 
93312
+ // src/core/asset/markdown-fragments.ts
93313
+ init_markdown();
93314
+ import { createHash as createHash9 } from "node:crypto";
93315
+ var MARKDOWN_FRAGMENT_MAX_CHARS = 1600;
93316
+ var MARKDOWN_FRAGMENT_PREFIX = "akm-fragment-";
93317
+ function hash3(text) {
93318
+ return createHash9("sha256").update(text).digest("hex");
93319
+ }
93320
+ function uniqueSlugs(body) {
93321
+ const out = new Map;
93322
+ const seen = new Set;
93323
+ for (const heading of parseMarkdownToc(body).headings) {
93324
+ const base3 = markdownHeadingSlug(heading.text);
93325
+ if (!base3)
93326
+ continue;
93327
+ let slug = base3;
93328
+ for (let suffix = 1;seen.has(slug); suffix++)
93329
+ slug = `${base3}-${suffix}`;
93330
+ seen.add(slug);
93331
+ out.set(heading.line, slug);
93332
+ }
93333
+ return out;
93334
+ }
93335
+ function textOf(lines) {
93336
+ return lines.join(`
93337
+ `).trim();
93338
+ }
93339
+ function splitPiece(piece, maxChars) {
93340
+ if (textOf(piece.lines).length <= maxChars)
93341
+ return [piece];
93342
+ const pieces = [];
93343
+ let start = 0;
93344
+ while (start < piece.lines.length) {
93345
+ let end = start;
93346
+ let chars = 0;
93347
+ while (end < piece.lines.length) {
93348
+ const next = piece.lines[end];
93349
+ if (end === start && next.length > maxChars)
93350
+ break;
93351
+ if (end > start && chars + next.length + 1 > maxChars)
93352
+ break;
93353
+ chars += next.length + (end > start ? 1 : 0);
93354
+ end++;
93355
+ }
93356
+ if (end === start) {
93357
+ const line = piece.lines[start];
93358
+ let offset = 0;
93359
+ while (offset < line.length) {
93360
+ let cut = Math.min(offset + maxChars, line.length);
93361
+ if (cut < line.length) {
93362
+ const space = line.lastIndexOf(" ", cut);
93363
+ if (space > offset + Math.floor(maxChars * 0.55))
93364
+ cut = space;
93365
+ }
93366
+ pieces.push({ lines: [line.slice(offset, cut).trim()], startLine: piece.startLine + start });
93367
+ offset = cut;
93368
+ while (line[offset] === " ")
93369
+ offset++;
93370
+ }
93371
+ start++;
93372
+ continue;
93373
+ }
93374
+ let preferred = -1;
93375
+ for (let i = start + 1;i < end; i++)
93376
+ if (!piece.lines[i].trim())
93377
+ preferred = i;
93378
+ if (preferred > start)
93379
+ end = preferred;
93380
+ pieces.push({ lines: piece.lines.slice(start, end), startLine: piece.startLine + start });
93381
+ start = end;
93382
+ while (start < piece.lines.length && !piece.lines[start].trim())
93383
+ start++;
93384
+ }
93385
+ return pieces.filter((candidate) => textOf(candidate.lines));
93386
+ }
93387
+ function splitMarkdownFragmentStats(body, maxChars = MARKDOWN_FRAGMENT_MAX_CHARS) {
93388
+ const lines = body.split(/\r?\n/);
93389
+ const headings = parseMarkdownToc(body).headings;
93390
+ const boundaries = [1, ...headings.map((heading) => heading.line), lines.length + 1].filter((line, index, all) => index === 0 || line !== all[index - 1]).sort((left, right) => left - right);
93391
+ const slugs = uniqueSlugs(body);
93392
+ const pieces = [];
93393
+ let sectionCount = 0;
93394
+ for (let i = 0;i < boundaries.length - 1; i++) {
93395
+ const startLine = boundaries[i];
93396
+ const end = boundaries[i + 1] - 1;
93397
+ const section = { lines: lines.slice(startLine - 1, end), startLine, headingSlug: slugs.get(startLine) };
93398
+ if (textOf(section.lines)) {
93399
+ sectionCount++;
93400
+ pieces.push(...splitPiece(section, maxChars));
93401
+ }
93402
+ }
93403
+ const piecesPerHeading = new Map;
93404
+ for (const piece of pieces) {
93405
+ if (piece.headingSlug)
93406
+ piecesPerHeading.set(piece.headingSlug, (piecesPerHeading.get(piece.headingSlug) ?? 0) + 1);
93407
+ }
93408
+ const fragments = pieces.map((piece, ordinal) => {
93409
+ const text = textOf(piece.lines);
93410
+ const contentLines = piece.lines.map((line, index) => ({ line, index })).filter(({ line }) => line.trim());
93411
+ const first = contentLines[0]?.index ?? 0;
93412
+ const last = contentLines.at(-1)?.index ?? 0;
93413
+ const digest = hash3(text);
93414
+ const unsplitHeading = piece.headingSlug && piecesPerHeading.get(piece.headingSlug) === 1;
93415
+ return {
93416
+ fragmentId: `${MARKDOWN_FRAGMENT_PREFIX}${ordinal + 1}-${digest.slice(0, 12)}`,
93417
+ ordinal,
93418
+ startLine: piece.startLine + first,
93419
+ endLine: piece.startLine + last,
93420
+ ...unsplitHeading ? { headingSlug: piece.headingSlug } : {},
93421
+ text,
93422
+ hash: digest
93423
+ };
93424
+ });
93425
+ return { fragments, hardSplitCount: Math.max(0, pieces.length - sectionCount) };
93426
+ }
93427
+ function splitMarkdownFragments(body, maxChars = MARKDOWN_FRAGMENT_MAX_CHARS) {
93428
+ return splitMarkdownFragmentStats(body, maxChars).fragments;
93429
+ }
93430
+
93209
93431
  // src/storage/repositories/index-fts-repository.ts
93210
93432
  init_warn();
93211
93433
  var INSERT_FTS_SQL = "INSERT INTO entries_fts (entry_id, name, description, tags, hints, content) VALUES (?, ?, ?, ?, ?, ?)";
93434
+ var INSERT_FRAGMENT_SQL = "INSERT INTO entry_fragments_fts (entry_id, fragment_id, fragment_ordinal, content) VALUES (?, ?, ?, ?)";
93212
93435
  var ftsMutationStatementsByDb = new WeakMap;
93213
93436
  function getFtsMutationStatements(db) {
93214
93437
  const existing = ftsMutationStatementsByDb.get(db);
@@ -93216,22 +93439,39 @@ function getFtsMutationStatements(db) {
93216
93439
  return existing;
93217
93440
  const statements = {
93218
93441
  deleteOne: db.prepare("DELETE FROM entries_fts WHERE entry_id = ?"),
93219
- insert: db.prepare(INSERT_FTS_SQL)
93442
+ insert: db.prepare(INSERT_FTS_SQL),
93443
+ deleteFragments: db.prepare("DELETE FROM entry_fragments_fts WHERE entry_id = ?"),
93444
+ upsertFragmentSource: db.prepare("INSERT INTO entry_fragments (entry_id, safe_markdown) VALUES (?, ?) ON CONFLICT(entry_id) DO UPDATE SET safe_markdown = excluded.safe_markdown"),
93445
+ deleteFragmentSource: db.prepare("DELETE FROM entry_fragments WHERE entry_id = ?"),
93446
+ insertFragment: db.prepare(INSERT_FRAGMENT_SQL)
93220
93447
  };
93221
93448
  ftsMutationStatementsByDb.set(db, statements);
93222
93449
  return statements;
93223
93450
  }
93224
- function replaceFtsEntry(db, entryId, entry) {
93451
+ function replaceFtsEntry(db, entryId, entry, fragmentContent) {
93225
93452
  const fields = buildSearchFields(entry);
93226
93453
  const statements = getFtsMutationStatements(db);
93227
93454
  statements.deleteOne.run(entryId);
93228
93455
  statements.insert.run(entryId, fields.name, fields.description, fields.tags, fields.hints, fields.content);
93456
+ if (fragmentContent === undefined) {
93457
+ return;
93458
+ }
93459
+ statements.deleteFragments.run(entryId);
93460
+ statements.deleteFragmentSource.run(entryId);
93461
+ if (!fragmentContent)
93462
+ return;
93463
+ statements.upsertFragmentSource.run(entryId, fragmentContent);
93464
+ for (const fragment of splitMarkdownFragments(fragmentContent)) {
93465
+ statements.insertFragment.run(entryId, fragment.fragmentId, fragment.ordinal, fragment.text.toLowerCase());
93466
+ }
93229
93467
  }
93230
93468
  function deleteFtsEntries(db, entryIds) {
93231
93469
  for (let i = 0;i < entryIds.length; i += SQLITE_CHUNK_SIZE) {
93232
93470
  const chunk = entryIds.slice(i, i + SQLITE_CHUNK_SIZE);
93233
93471
  const placeholders = chunk.map(() => "?").join(",");
93234
93472
  db.prepare(`DELETE FROM entries_fts WHERE entry_id IN (${placeholders})`).run(...chunk);
93473
+ db.prepare(`DELETE FROM entry_fragments_fts WHERE entry_id IN (${placeholders})`).run(...chunk);
93474
+ db.prepare(`DELETE FROM entry_fragments WHERE entry_id IN (${placeholders})`).run(...chunk);
93235
93475
  }
93236
93476
  }
93237
93477
 
@@ -93246,7 +93486,7 @@ function upsertEntry(db, filePath, entry, searchText, provenance, contentHash) {
93246
93486
  throw new Error("upsertEntry: item_ref not found after upsert");
93247
93487
  if (previous?.id === result.id && previous.search_text !== searchText)
93248
93488
  deleteEntryVectors(db, result.id);
93249
- replaceFtsEntry(db, result.id, entry);
93489
+ replaceFtsEntry(db, result.id, entry, hasMarkdownFragmentContent(entry) ? getMarkdownFragmentContent(entry) ?? null : undefined);
93250
93490
  return result.id;
93251
93491
  };
93252
93492
  return db.transaction(apply)();
@@ -94260,6 +94500,9 @@ function publishTargetedEmbeddingMeta(db, config) {
94260
94500
  setMeta(db, "hasEmbeddings", ready ? "1" : "0");
94261
94501
  }
94262
94502
 
94503
+ // src/indexer/index-written-assets.ts
94504
+ init_metadata();
94505
+
94263
94506
  // src/indexer/scan/drain-dir.ts
94264
94507
  import path58 from "node:path";
94265
94508
  init_common();
@@ -94322,52 +94565,56 @@ function workflowNameForSourcePath(sourceRoot, adapterId, sourcePath) {
94322
94565
  return;
94323
94566
  return ownedPath;
94324
94567
  }
94325
- function listWorkflowSourceFiles(sourceRoot, adapterId, name) {
94568
+ function resolveWorkflowSourceDomains(sourceRoot, adapterId, sourcePaths) {
94326
94569
  if (adapterId !== "akm" && adapterId !== "akm-workflow")
94327
94570
  return [];
94328
- const canonicalName = canonicalizeWorkflowName(normalizeName2(name));
94329
- if (!isSafeRelativeName(canonicalName)) {
94330
- throw new UsageError("Workflow ref resolves outside the bundle root.", "PATH_ESCAPE_VIOLATION");
94331
- }
94332
- let realRoot;
94333
94571
  const authoredRoot = path55.resolve(sourceRoot);
94572
+ let realRoot;
94334
94573
  try {
94335
94574
  realRoot = fs46.realpathSync(authoredRoot);
94336
94575
  } catch {
94337
94576
  return [];
94338
94577
  }
94339
- const ownershipRoot = adapterId === "akm" ? path55.join(authoredRoot, "workflows") : authoredRoot;
94340
- const parent = path55.join(ownershipRoot, path55.dirname(canonicalName));
94341
- if (!isWithinResolved(parent, authoredRoot)) {
94342
- throw new UsageError("Workflow ref resolves outside the bundle root.", "PATH_ESCAPE_VIOLATION");
94343
- }
94344
- let entries;
94345
- try {
94346
- entries = fs46.readdirSync(parent, { withFileTypes: true });
94347
- } catch {
94348
- return [];
94349
- }
94350
- const basename = path55.basename(canonicalName);
94351
- const candidates = [];
94352
- for (const entry of entries) {
94353
- if (!entry.isFile() && !entry.isSymbolicLink())
94578
+ const candidatesByName = new Map;
94579
+ const seenAuthoredPaths = new Set;
94580
+ for (const sourcePath of sourcePaths) {
94581
+ const normalizedSourcePath = path55.resolve(sourcePath);
94582
+ if (seenAuthoredPaths.has(normalizedSourcePath))
94583
+ continue;
94584
+ seenAuthoredPaths.add(normalizedSourcePath);
94585
+ const authoredName = workflowNameForSourcePath(authoredRoot, adapterId, normalizedSourcePath);
94586
+ if (authoredName === undefined)
94354
94587
  continue;
94355
- const extension = path55.extname(entry.name);
94588
+ const canonicalName = canonicalizeWorkflowName(authoredName);
94589
+ if (!isSafeRelativeName(canonicalName))
94590
+ continue;
94591
+ const extension = path55.extname(normalizedSourcePath);
94356
94592
  const lowerExtension = extension.toLowerCase();
94357
94593
  if (!WORKFLOW_EXTENSIONS.includes(lowerExtension))
94358
94594
  continue;
94359
- if (entry.name.slice(0, -extension.length) !== basename)
94360
- continue;
94361
- const candidatePath = path55.join(parent, entry.name);
94362
- candidates.push({
94363
- path: candidatePath,
94364
- relativePath: toPosix(path55.relative(authoredRoot, candidatePath)),
94595
+ const candidate = {
94596
+ path: normalizedSourcePath,
94597
+ relativePath: toPosix(path55.relative(authoredRoot, normalizedSourcePath)),
94365
94598
  lowerExtension,
94366
- extensionlessStem: entry.name.slice(0, -extension.length)
94599
+ extensionlessStem: path55.basename(normalizedSourcePath).slice(0, -extension.length)
94600
+ };
94601
+ const domain = candidatesByName.get(canonicalName) ?? [];
94602
+ domain.push(candidate);
94603
+ candidatesByName.set(canonicalName, domain);
94604
+ }
94605
+ const resolutions = [];
94606
+ for (const canonicalName of [...candidatesByName.keys()].sort(compareCodePoints)) {
94607
+ const candidates = candidatesByName.get(canonicalName) ?? [];
94608
+ candidates.sort((left, right) => compareCodePoints(left.relativePath, right.relativePath));
94609
+ const sources = inspectWorkflowSourceDomain(candidates, canonicalName, realRoot);
94610
+ const sourcePaths2 = candidates.map((candidate) => candidate.relativePath);
94611
+ resolutions.push({
94612
+ canonicalName,
94613
+ sourcePaths: sourcePaths2,
94614
+ source: pickWorkflowSource(adapterId, canonicalName, sources)
94367
94615
  });
94368
94616
  }
94369
- candidates.sort((left, right) => compareCodePoints(left.relativePath, right.relativePath));
94370
- return inspectWorkflowSourceDomain(candidates, canonicalName, realRoot);
94617
+ return resolutions;
94371
94618
  }
94372
94619
  function inspectWorkflowSourceDomain(candidates, canonicalName, realRoot) {
94373
94620
  const sources = [];
@@ -94441,14 +94688,6 @@ function inspectWorkflowSourceCandidate(candidate, canonicalName, realRoot) {
94441
94688
  }
94442
94689
  };
94443
94690
  }
94444
- function resolveUniqueWorkflowSource(sourceRoot, adapterId, name) {
94445
- const sources = listWorkflowSourceFiles(sourceRoot, adapterId, name);
94446
- const canonicalName = sources[0]?.canonicalName ?? canonicalizeWorkflowName(normalizeName2(name));
94447
- return pickWorkflowSource(adapterId, canonicalName, sources);
94448
- }
94449
- function normalizeName2(name) {
94450
- return name.replaceAll("\\", "/");
94451
- }
94452
94691
  function isSafeRelativeName(name) {
94453
94692
  return name.length > 0 && !path55.posix.isAbsolute(name) && name !== ".." && !name.startsWith("../") && path55.posix.normalize(name) === name;
94454
94693
  }
@@ -94463,6 +94702,7 @@ init_metadata();
94463
94702
  init_file_context();
94464
94703
 
94465
94704
  // src/indexer/scan/doc-to-entry.ts
94705
+ init_metadata();
94466
94706
  import path57 from "node:path";
94467
94707
  function indexDocumentToStashEntry(doc) {
94468
94708
  const dj = doc.documentJson ?? {};
@@ -94479,6 +94719,8 @@ function indexDocumentToStashEntry(doc) {
94479
94719
  entry.content = doc.content;
94480
94720
  if (doc.contentTruncated !== undefined)
94481
94721
  entry.contentTruncated = doc.contentTruncated;
94722
+ if (hasMarkdownFragmentContent(doc))
94723
+ setMarkdownFragmentContent(entry, getMarkdownFragmentContent(doc));
94482
94724
  if (doc.ownsPresentation !== undefined)
94483
94725
  entry.ownsPresentation = doc.ownsPresentation;
94484
94726
  if (doc.updated !== undefined)
@@ -94572,31 +94814,28 @@ function drainDirDocuments(adapter, component, fileContexts) {
94572
94814
  const conceptIdByFile = new Map;
94573
94815
  const rejectedPaths = new Set;
94574
94816
  const rejectedConceptIds = new Set;
94575
- const workflowLookups = new Map;
94576
- for (const file of fileContexts) {
94817
+ const workflowOwnerPathByCanonicalName = new Map(resolveWorkflowSourceDomains(component.root, adapter.id, fileContexts.map((file) => file.absPath)).filter((resolution) => resolution.source !== undefined).map((resolution) => [resolution.canonicalName, path58.resolve(resolution.source.path)]));
94818
+ const invalidWorkflowOwnerNames = new Set;
94819
+ const orderedFileContexts = [...fileContexts].sort((left, right) => {
94820
+ const leftName = workflowNameForSourcePath(component.root, adapter.id, left.absPath);
94821
+ const rightName = workflowNameForSourcePath(component.root, adapter.id, right.absPath);
94822
+ const leftOwner = leftName !== undefined && workflowOwnerPathByCanonicalName.get(canonicalizeWorkflowName(leftName)) === path58.resolve(left.absPath);
94823
+ const rightOwner = rightName !== undefined && workflowOwnerPathByCanonicalName.get(canonicalizeWorkflowName(rightName)) === path58.resolve(right.absPath);
94824
+ if (leftOwner !== rightOwner)
94825
+ return leftOwner ? -1 : 1;
94826
+ return compareCodePoints(left.absPath, right.absPath);
94827
+ });
94828
+ for (const file of orderedFileContexts) {
94829
+ if (rejectedPaths.has(file.absPath))
94830
+ continue;
94577
94831
  const workflowName = workflowNameForSourcePath(component.root, adapter.id, file.absPath);
94578
94832
  if (workflowName !== undefined) {
94579
94833
  const canonicalName = canonicalizeWorkflowName(workflowName);
94580
- if (!workflowLookups.has(canonicalName))
94581
- workflowLookups.set(canonicalName, workflowName);
94582
- }
94583
- }
94584
- for (const [canonicalName, workflowName] of [...workflowLookups].sort(([left], [right]) => compareCodePoints(left, right))) {
94585
- try {
94586
- resolveUniqueWorkflowSource(component.root, adapter.id, workflowName);
94587
- } catch (error2) {
94588
- if (!(error2 instanceof WorkflowSourceRejectionError))
94589
- throw error2;
94590
- rejectedConceptIds.add(adapter.id === "akm" ? `workflows/${canonicalName}` : canonicalName);
94591
- for (const relativePath of error2.sourcePaths) {
94592
- rejectedPaths.add(path58.join(component.root, relativePath));
94834
+ const ownerPath = workflowOwnerPathByCanonicalName.get(canonicalName);
94835
+ if (ownerPath !== undefined && ownerPath !== path58.resolve(file.absPath) && !invalidWorkflowOwnerNames.has(canonicalName)) {
94836
+ continue;
94593
94837
  }
94594
- warnings.push(error2.message);
94595
94838
  }
94596
- }
94597
- for (const file of fileContexts) {
94598
- if (rejectedPaths.has(file.absPath))
94599
- continue;
94600
94839
  const doc = adapter.recognize(component, file);
94601
94840
  if (doc === null)
94602
94841
  continue;
@@ -94608,6 +94847,8 @@ function drainDirDocuments(adapter, component, fileContexts) {
94608
94847
  const dropWarning = handleWorkflowDoc(doc, file, component.root);
94609
94848
  if (dropWarning !== null) {
94610
94849
  warnings.push(dropWarning);
94850
+ if (workflowName !== undefined)
94851
+ invalidWorkflowOwnerNames.add(canonicalizeWorkflowName(workflowName));
94611
94852
  continue;
94612
94853
  }
94613
94854
  if (doc.hash !== undefined)
@@ -94720,6 +94961,9 @@ async function indexWrittenAssets(stashDir, filePaths, options = {}) {
94720
94961
  let entryWithSize = entry;
94721
94962
  try {
94722
94963
  entryWithSize = { ...entry, fileSize: fs48.statSync(file).size };
94964
+ if (hasMarkdownFragmentContent(entry)) {
94965
+ setMarkdownFragmentContent(entryWithSize, getMarkdownFragmentContent(entry));
94966
+ }
94723
94967
  } catch {}
94724
94968
  const provenance = deriveEntryProvenance({ bundleId: component.id, componentId: component.id, adapterId: component.adapter }, entry.type, entry.name, conceptId);
94725
94969
  const supersededIds = db.prepare("SELECT id FROM entries WHERE file_path = ? AND item_ref <> ?").all(file, provenance.itemRef);
@@ -95579,7 +95823,7 @@ var PROPOSAL_TXN_PHASES = [
95579
95823
  "committed"
95580
95824
  ];
95581
95825
  function proposalHash(content) {
95582
- return createHash9("sha256").update(content).digest("hex");
95826
+ return createHash10("sha256").update(content).digest("hex");
95583
95827
  }
95584
95828
  function proposalFileHash(filePath) {
95585
95829
  return proposalHash(fs49.readFileSync(filePath));