@agenr/agenr-plugin 1.9.3 → 2.0.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.
@@ -1,12 +1,14 @@
1
1
  import {
2
2
  buildLexicalPlan,
3
+ combinedRelevance,
4
+ computeLexicalScore,
3
5
  cosineSimilarity,
4
6
  describeClaimKeyNormalizationFailure,
5
7
  normalizeClaimKey,
6
8
  recall,
7
9
  resolveClaimSlotPolicy,
8
10
  tokenize
9
- } from "./chunk-GUDCFFRV.js";
11
+ } from "./chunk-MEHOGUZE.js";
10
12
 
11
13
  // src/adapters/db/client.ts
12
14
  import fs from "fs/promises";
@@ -31,6 +33,9 @@ var CLAIM_KEY_SOURCES = [
31
33
  ];
32
34
  var CLAIM_SUPPORT_MODES = ["explicit", "normalized", "inferred"];
33
35
  var EPISODE_ACTIVITY_LEVELS = ["substantial", "minimal", "none"];
36
+ var PROCEDURE_STEP_KINDS = ["run_command", "read_reference", "inspect_state", "edit_file", "ask_user", "invoke_tool", "verify"];
37
+ var PROCEDURE_CONDITION_KINDS = ["harness_is", "tool_available", "file_exists", "path_exists", "env_flag", "repo_state", "user_confirmed"];
38
+ var PROCEDURE_SOURCE_KINDS = ["skill", "doc", "entry", "episode", "repo_file", "manual"];
34
39
 
35
40
  // src/core/claim-key-lifecycle.ts
36
41
  var PRECOMPUTED_LIFECYCLE_FIELDS = ["claim_key_status", "claim_key_source", "claim_key_confidence", "claim_key_rationale"];
@@ -1136,8 +1141,817 @@ function toNullableInteger(value) {
1136
1141
  return value ?? null;
1137
1142
  }
1138
1143
 
1144
+ // src/adapters/db/procedure-queries.ts
1145
+ import { randomUUID as randomUUID2 } from "crypto";
1146
+
1147
+ // src/core/procedures/validation.ts
1148
+ import { parseDocument } from "yaml";
1149
+ function parseProcedureYaml(sourceText, filePath) {
1150
+ const document = parseDocument(sourceText, {
1151
+ merge: false,
1152
+ prettyErrors: true,
1153
+ uniqueKeys: true
1154
+ });
1155
+ if (document.errors.length > 0) {
1156
+ const [firstError] = document.errors;
1157
+ throw new Error(`Invalid procedure ${filePath}: ${firstError?.message ?? "YAML parsing failed."}`);
1158
+ }
1159
+ return document.toJS({ maxAliasCount: 0 });
1160
+ }
1161
+ function readProcedureRecord(value, label, filePath) {
1162
+ if (!isRecord(value)) {
1163
+ throw new Error(`Invalid procedure ${filePath}: ${label} must be an object.`);
1164
+ }
1165
+ return value;
1166
+ }
1167
+ function rejectUnexpectedProcedureFields(record, allowedKeys, label, filePath) {
1168
+ const unexpected = Object.keys(record).filter((key) => !allowedKeys.has(key));
1169
+ if (unexpected.length === 0) {
1170
+ return;
1171
+ }
1172
+ throw new Error(`Invalid procedure ${filePath}: ${label} contains unsupported field "${unexpected[0]}".`);
1173
+ }
1174
+ function readRequiredProcedureString(value, label, filePath) {
1175
+ if (typeof value !== "string") {
1176
+ throw new Error(`Invalid procedure ${filePath}: ${label} must be a non-empty string.`);
1177
+ }
1178
+ const normalized = normalizeWhitespace(value);
1179
+ if (normalized.length === 0) {
1180
+ throw new Error(`Invalid procedure ${filePath}: ${label} must be a non-empty string.`);
1181
+ }
1182
+ return normalized;
1183
+ }
1184
+ function readOptionalProcedureString(value, label, filePath) {
1185
+ if (value === void 0) {
1186
+ return void 0;
1187
+ }
1188
+ return readRequiredProcedureString(value, label, filePath);
1189
+ }
1190
+ function readProcedureStringArray(value, label, filePath, options = {}) {
1191
+ if (value === void 0) {
1192
+ if (options.required) {
1193
+ throw new Error(`Invalid procedure ${filePath}: ${label} must be an array.`);
1194
+ }
1195
+ return [];
1196
+ }
1197
+ if (!Array.isArray(value)) {
1198
+ throw new Error(`Invalid procedure ${filePath}: ${label} must be an array.`);
1199
+ }
1200
+ const normalized = value.map((item, index) => readRequiredProcedureString(item, `${label}[${index}]`, filePath));
1201
+ if (options.minItems !== void 0 && normalized.length < options.minItems) {
1202
+ throw new Error(`Invalid procedure ${filePath}: ${label} must contain at least ${options.minItems} item(s).`);
1203
+ }
1204
+ return normalized;
1205
+ }
1206
+ function readProcedureRecordArray(value, label, filePath, options = {}) {
1207
+ if (value === void 0) {
1208
+ if (options.required) {
1209
+ throw new Error(`Invalid procedure ${filePath}: ${label} must be an array.`);
1210
+ }
1211
+ return [];
1212
+ }
1213
+ if (!Array.isArray(value)) {
1214
+ throw new Error(`Invalid procedure ${filePath}: ${label} must be an array.`);
1215
+ }
1216
+ const records = value.map((item, index) => readProcedureRecord(item, `${label}[${index}]`, filePath));
1217
+ if (options.minItems !== void 0 && records.length < options.minItems) {
1218
+ throw new Error(`Invalid procedure ${filePath}: ${label} must contain at least ${options.minItems} item(s).`);
1219
+ }
1220
+ return records;
1221
+ }
1222
+ function readProcedureStepKind(value, label, filePath, supportedKinds) {
1223
+ const normalized = readRequiredProcedureString(value, label, filePath);
1224
+ if (!supportedKinds.includes(normalized)) {
1225
+ throw new Error(`Invalid procedure ${filePath}: ${label} has unsupported step kind "${normalized}".`);
1226
+ }
1227
+ return normalized;
1228
+ }
1229
+ function readProcedureConditionKind(value, label, filePath, supportedKinds) {
1230
+ const normalized = readRequiredProcedureString(value, label, filePath);
1231
+ if (!supportedKinds.includes(normalized)) {
1232
+ throw new Error(`Invalid procedure ${filePath}: ${label} has unsupported condition kind "${normalized}".`);
1233
+ }
1234
+ return normalized;
1235
+ }
1236
+ function readProcedureSourceKind(value, label, filePath, supportedKinds) {
1237
+ const normalized = readRequiredProcedureString(value, label, filePath);
1238
+ if (!supportedKinds.includes(normalized)) {
1239
+ throw new Error(`Invalid procedure ${filePath}: ${label} has unsupported source kind "${normalized}".`);
1240
+ }
1241
+ return normalized;
1242
+ }
1243
+ function normalizeProcedureToolArgumentValue(value, label, filePath) {
1244
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
1245
+ return value;
1246
+ }
1247
+ if (typeof value === "number") {
1248
+ if (!Number.isFinite(value)) {
1249
+ throw new Error(`Invalid procedure ${filePath}: ${label} must not contain non-finite numbers.`);
1250
+ }
1251
+ return value;
1252
+ }
1253
+ if (Array.isArray(value)) {
1254
+ return value.map((item, index) => normalizeProcedureToolArgumentValue(item, `${label}[${index}]`, filePath));
1255
+ }
1256
+ if (!isRecord(value)) {
1257
+ throw new Error(`Invalid procedure ${filePath}: ${label} must be JSON-like data.`);
1258
+ }
1259
+ const normalizedEntries = Object.entries(value).map(([key, entryValue]) => {
1260
+ const normalizedKey = key.trim();
1261
+ if (normalizedKey.length === 0) {
1262
+ throw new Error(`Invalid procedure ${filePath}: ${label} must not contain blank object keys.`);
1263
+ }
1264
+ return [normalizedKey, normalizeProcedureToolArgumentValue(entryValue, `${label}.${normalizedKey}`, filePath)];
1265
+ }).sort(([left], [right]) => left.localeCompare(right));
1266
+ return Object.fromEntries(normalizedEntries);
1267
+ }
1268
+ function isRecord(value) {
1269
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1270
+ }
1271
+ function normalizeWhitespace(value) {
1272
+ return value.replace(/\s+/gu, " ").trim();
1273
+ }
1274
+
1275
+ // src/core/procedures/normalization.ts
1276
+ var PROCEDURE_KEYS = /* @__PURE__ */ new Set([
1277
+ "procedure_key",
1278
+ "title",
1279
+ "goal",
1280
+ "when_to_use",
1281
+ "when_not_to_use",
1282
+ "prerequisites",
1283
+ "steps",
1284
+ "verification",
1285
+ "failure_modes",
1286
+ "sources"
1287
+ ]);
1288
+ var STEP_BASE_KEYS = /* @__PURE__ */ new Set(["id", "kind", "instruction", "conditions", "stop_if"]);
1289
+ var READ_REFERENCE_STEP_KEYS = /* @__PURE__ */ new Set([...STEP_BASE_KEYS, "ref"]);
1290
+ var RUN_COMMAND_STEP_KEYS = /* @__PURE__ */ new Set([...STEP_BASE_KEYS, "command"]);
1291
+ var INSPECT_STATE_STEP_KEYS = /* @__PURE__ */ new Set([...STEP_BASE_KEYS, "target", "query"]);
1292
+ var EDIT_FILE_STEP_KEYS = /* @__PURE__ */ new Set([...STEP_BASE_KEYS, "path", "edit"]);
1293
+ var ASK_USER_STEP_KEYS = /* @__PURE__ */ new Set([...STEP_BASE_KEYS, "prompt"]);
1294
+ var INVOKE_TOOL_STEP_KEYS = /* @__PURE__ */ new Set([...STEP_BASE_KEYS, "tool", "arguments"]);
1295
+ var VERIFY_STEP_KEYS = /* @__PURE__ */ new Set([...STEP_BASE_KEYS, "checks"]);
1296
+ var SOURCE_KEYS = /* @__PURE__ */ new Set(["kind", "path", "locator", "label"]);
1297
+ var HARNESS_CONDITION_KEYS = /* @__PURE__ */ new Set(["kind", "value"]);
1298
+ var TOOL_AVAILABLE_CONDITION_KEYS = /* @__PURE__ */ new Set(["kind", "value"]);
1299
+ var FILE_EXISTS_CONDITION_KEYS = /* @__PURE__ */ new Set(["kind", "path"]);
1300
+ var PATH_EXISTS_CONDITION_KEYS = /* @__PURE__ */ new Set(["kind", "path"]);
1301
+ var ENV_FLAG_CONDITION_KEYS = /* @__PURE__ */ new Set(["kind", "name", "value"]);
1302
+ var REPO_STATE_CONDITION_KEYS = /* @__PURE__ */ new Set(["kind", "value"]);
1303
+ var USER_CONFIRMED_CONDITION_KEYS = /* @__PURE__ */ new Set(["kind", "value"]);
1304
+ function parseAndNormalizeProcedureYaml(sourceText, filePath) {
1305
+ return normalizeProcedureDefinition(parseProcedureYaml(sourceText, filePath), filePath);
1306
+ }
1307
+ function normalizeProcedureDefinition(value, filePath) {
1308
+ const record = readProcedureRecord(value, "Procedure root", filePath);
1309
+ rejectUnexpectedProcedureFields(record, PROCEDURE_KEYS, "Procedure root", filePath);
1310
+ return {
1311
+ procedure_key: normalizeProcedureKey(record.procedure_key, filePath),
1312
+ title: readRequiredProcedureString(record.title, "title", filePath),
1313
+ goal: readRequiredProcedureString(record.goal, "goal", filePath),
1314
+ when_to_use: readProcedureStringArray(record.when_to_use, "when_to_use", filePath),
1315
+ when_not_to_use: readProcedureStringArray(record.when_not_to_use, "when_not_to_use", filePath),
1316
+ prerequisites: readProcedureStringArray(record.prerequisites, "prerequisites", filePath),
1317
+ steps: normalizeProcedureSteps(record.steps, filePath),
1318
+ verification: readProcedureStringArray(record.verification, "verification", filePath, { required: true, minItems: 1 }),
1319
+ failure_modes: readProcedureStringArray(record.failure_modes, "failure_modes", filePath, { required: true, minItems: 1 }),
1320
+ sources: normalizeProcedureSources(record.sources, "sources", filePath, { required: true, minItems: 1 })
1321
+ };
1322
+ }
1323
+ function normalizeProcedureKey(value, filePath) {
1324
+ const normalized = readRequiredProcedureString(value, "procedure_key", filePath).toLowerCase();
1325
+ if (!normalized.includes("/") || !/^[a-z0-9][a-z0-9._/-]*$/u.test(normalized)) {
1326
+ throw new Error(`Invalid procedure ${filePath}: procedure_key must be a lowercase slash-delimited identifier like "agenr/release".`);
1327
+ }
1328
+ return normalized;
1329
+ }
1330
+ function normalizeProcedureSteps(value, filePath) {
1331
+ const records = readProcedureRecordArray(value, "steps", filePath, { required: true, minItems: 1 });
1332
+ const steps = records.map((record, index) => normalizeProcedureStep(record, `steps[${index}]`, filePath));
1333
+ const stepIds = /* @__PURE__ */ new Set();
1334
+ for (const step of steps) {
1335
+ if (stepIds.has(step.id)) {
1336
+ throw new Error(`Invalid procedure ${filePath}: steps must not contain duplicate id "${step.id}".`);
1337
+ }
1338
+ stepIds.add(step.id);
1339
+ }
1340
+ return steps;
1341
+ }
1342
+ function normalizeProcedureStep(record, label, filePath) {
1343
+ const kind = readProcedureStepKind(record.kind, `${label}.kind`, filePath, PROCEDURE_STEP_KINDS);
1344
+ const base = normalizeProcedureStepBase(record, label, filePath);
1345
+ switch (kind) {
1346
+ case "run_command":
1347
+ rejectUnexpectedProcedureFields(record, RUN_COMMAND_STEP_KEYS, label, filePath);
1348
+ return {
1349
+ ...base,
1350
+ kind,
1351
+ command: readRequiredProcedureString(record.command, `${label}.command`, filePath)
1352
+ };
1353
+ case "read_reference":
1354
+ rejectUnexpectedProcedureFields(record, READ_REFERENCE_STEP_KEYS, label, filePath);
1355
+ return {
1356
+ ...base,
1357
+ kind,
1358
+ ref: normalizeProcedureSource(readProcedureRecord(record.ref, `${label}.ref`, filePath), `${label}.ref`, filePath)
1359
+ };
1360
+ case "inspect_state": {
1361
+ rejectUnexpectedProcedureFields(record, INSPECT_STATE_STEP_KEYS, label, filePath);
1362
+ const target = readOptionalProcedureString(record.target, `${label}.target`, filePath);
1363
+ const query = readOptionalProcedureString(record.query, `${label}.query`, filePath);
1364
+ if (!target && !query) {
1365
+ throw new Error(`Invalid procedure ${filePath}: ${label} must define target, query, or both.`);
1366
+ }
1367
+ return {
1368
+ ...base,
1369
+ kind,
1370
+ ...target ? { target } : {},
1371
+ ...query ? { query } : {}
1372
+ };
1373
+ }
1374
+ case "edit_file":
1375
+ rejectUnexpectedProcedureFields(record, EDIT_FILE_STEP_KEYS, label, filePath);
1376
+ return {
1377
+ ...base,
1378
+ kind,
1379
+ path: readRequiredProcedureString(record.path, `${label}.path`, filePath),
1380
+ edit: readRequiredProcedureString(record.edit, `${label}.edit`, filePath)
1381
+ };
1382
+ case "ask_user":
1383
+ rejectUnexpectedProcedureFields(record, ASK_USER_STEP_KEYS, label, filePath);
1384
+ return {
1385
+ ...base,
1386
+ kind,
1387
+ prompt: readRequiredProcedureString(record.prompt, `${label}.prompt`, filePath)
1388
+ };
1389
+ case "invoke_tool":
1390
+ rejectUnexpectedProcedureFields(record, INVOKE_TOOL_STEP_KEYS, label, filePath);
1391
+ return {
1392
+ ...base,
1393
+ kind,
1394
+ tool: readRequiredProcedureString(record.tool, `${label}.tool`, filePath),
1395
+ ...record.arguments !== void 0 ? {
1396
+ arguments: normalizeProcedureArguments(record.arguments, `${label}.arguments`, filePath)
1397
+ } : {}
1398
+ };
1399
+ case "verify":
1400
+ rejectUnexpectedProcedureFields(record, VERIFY_STEP_KEYS, label, filePath);
1401
+ return {
1402
+ ...base,
1403
+ kind,
1404
+ checks: readProcedureStringArray(record.checks, `${label}.checks`, filePath, { required: true, minItems: 1 })
1405
+ };
1406
+ }
1407
+ }
1408
+ function normalizeProcedureStepBase(record, label, filePath) {
1409
+ return {
1410
+ id: readRequiredProcedureString(record.id, `${label}.id`, filePath),
1411
+ instruction: readRequiredProcedureString(record.instruction, `${label}.instruction`, filePath),
1412
+ ...record.conditions !== void 0 ? { conditions: normalizeProcedureConditions(record.conditions, `${label}.conditions`, filePath) } : {},
1413
+ ...record.stop_if !== void 0 ? { stop_if: normalizeProcedureConditions(record.stop_if, `${label}.stop_if`, filePath) } : {}
1414
+ };
1415
+ }
1416
+ function normalizeProcedureArguments(value, label, filePath) {
1417
+ const record = readProcedureRecord(value, label, filePath);
1418
+ return normalizeProcedureToolArgumentValue(record, label, filePath);
1419
+ }
1420
+ function normalizeProcedureConditions(value, label, filePath) {
1421
+ return readProcedureRecordArray(value, label, filePath, { required: true, minItems: 1 }).map(
1422
+ (record, index) => normalizeProcedureCondition(record, `${label}[${index}]`, filePath)
1423
+ );
1424
+ }
1425
+ function normalizeProcedureCondition(record, label, filePath) {
1426
+ const kind = readProcedureConditionKind(record.kind, `${label}.kind`, filePath, PROCEDURE_CONDITION_KINDS);
1427
+ switch (kind) {
1428
+ case "harness_is":
1429
+ rejectUnexpectedProcedureFields(record, HARNESS_CONDITION_KEYS, label, filePath);
1430
+ return {
1431
+ kind,
1432
+ value: readRequiredProcedureString(record.value, `${label}.value`, filePath)
1433
+ };
1434
+ case "tool_available":
1435
+ rejectUnexpectedProcedureFields(record, TOOL_AVAILABLE_CONDITION_KEYS, label, filePath);
1436
+ return {
1437
+ kind,
1438
+ value: readRequiredProcedureString(record.value, `${label}.value`, filePath)
1439
+ };
1440
+ case "file_exists":
1441
+ rejectUnexpectedProcedureFields(record, FILE_EXISTS_CONDITION_KEYS, label, filePath);
1442
+ return {
1443
+ kind,
1444
+ path: readRequiredProcedureString(record.path, `${label}.path`, filePath)
1445
+ };
1446
+ case "path_exists":
1447
+ rejectUnexpectedProcedureFields(record, PATH_EXISTS_CONDITION_KEYS, label, filePath);
1448
+ return {
1449
+ kind,
1450
+ path: readRequiredProcedureString(record.path, `${label}.path`, filePath)
1451
+ };
1452
+ case "env_flag":
1453
+ rejectUnexpectedProcedureFields(record, ENV_FLAG_CONDITION_KEYS, label, filePath);
1454
+ return {
1455
+ kind,
1456
+ name: readRequiredProcedureString(record.name, `${label}.name`, filePath),
1457
+ ...record.value !== void 0 ? { value: readRequiredProcedureString(record.value, `${label}.value`, filePath) } : {}
1458
+ };
1459
+ case "repo_state":
1460
+ rejectUnexpectedProcedureFields(record, REPO_STATE_CONDITION_KEYS, label, filePath);
1461
+ return {
1462
+ kind,
1463
+ value: readRequiredProcedureString(record.value, `${label}.value`, filePath)
1464
+ };
1465
+ case "user_confirmed":
1466
+ rejectUnexpectedProcedureFields(record, USER_CONFIRMED_CONDITION_KEYS, label, filePath);
1467
+ return {
1468
+ kind,
1469
+ value: readRequiredProcedureString(record.value, `${label}.value`, filePath)
1470
+ };
1471
+ }
1472
+ }
1473
+ function normalizeProcedureSources(value, label, filePath, options = {}) {
1474
+ return readProcedureRecordArray(value, label, filePath, options).map((record, index) => normalizeProcedureSource(record, `${label}[${index}]`, filePath));
1475
+ }
1476
+ function normalizeProcedureSource(record, label, filePath) {
1477
+ rejectUnexpectedProcedureFields(record, SOURCE_KEYS, label, filePath);
1478
+ const kind = readProcedureSourceKind(record.kind, `${label}.kind`, filePath, PROCEDURE_SOURCE_KINDS);
1479
+ const path3 = readOptionalProcedureString(record.path, `${label}.path`, filePath);
1480
+ const locator = readOptionalProcedureString(record.locator, `${label}.locator`, filePath);
1481
+ const sourceLabel = readOptionalProcedureString(record.label, `${label}.label`, filePath);
1482
+ switch (kind) {
1483
+ case "skill":
1484
+ case "doc":
1485
+ case "repo_file":
1486
+ if (!path3) {
1487
+ throw new Error(`Invalid procedure ${filePath}: ${label}.${kind} sources require a path.`);
1488
+ }
1489
+ break;
1490
+ case "entry":
1491
+ case "episode":
1492
+ if (!locator) {
1493
+ throw new Error(`Invalid procedure ${filePath}: ${label}.${kind} sources require a locator.`);
1494
+ }
1495
+ break;
1496
+ case "manual":
1497
+ if (!sourceLabel && !locator) {
1498
+ throw new Error(`Invalid procedure ${filePath}: ${label}.manual sources require a label or locator.`);
1499
+ }
1500
+ break;
1501
+ }
1502
+ return {
1503
+ kind,
1504
+ ...path3 ? { path: path3 } : {},
1505
+ ...locator ? { locator } : {},
1506
+ ...sourceLabel ? { label: sourceLabel } : {}
1507
+ };
1508
+ }
1509
+
1510
+ // src/adapters/db/procedure-row-mapping.ts
1511
+ var ACTIVE_PROCEDURE_CLAUSE = "retired = 0 AND superseded_by IS NULL";
1512
+ var PROCEDURE_SELECT_COLUMNS = `
1513
+ id,
1514
+ procedure_key,
1515
+ title,
1516
+ goal,
1517
+ body_json,
1518
+ recall_text,
1519
+ source_file,
1520
+ source_hash,
1521
+ revision_hash,
1522
+ embedding,
1523
+ retired,
1524
+ retired_at,
1525
+ retired_reason,
1526
+ superseded_by,
1527
+ created_at,
1528
+ updated_at
1529
+ `;
1530
+ function buildActiveProcedureClause(alias) {
1531
+ if (!alias) {
1532
+ return ACTIVE_PROCEDURE_CLAUSE;
1533
+ }
1534
+ return `${alias}.retired = 0 AND ${alias}.superseded_by IS NULL`;
1535
+ }
1536
+ function serializeProcedureBody(procedure) {
1537
+ return JSON.stringify(procedure);
1538
+ }
1539
+ function mapProcedureRow(row) {
1540
+ const definition = parseProcedureBody(readRequiredString(row, "body_json"));
1541
+ return {
1542
+ id: readRequiredString(row, "id"),
1543
+ procedure_key: readRequiredString(row, "procedure_key"),
1544
+ title: readRequiredString(row, "title"),
1545
+ goal: readRequiredString(row, "goal"),
1546
+ when_to_use: definition.when_to_use,
1547
+ when_not_to_use: definition.when_not_to_use,
1548
+ prerequisites: definition.prerequisites,
1549
+ steps: definition.steps,
1550
+ verification: definition.verification,
1551
+ failure_modes: definition.failure_modes,
1552
+ sources: definition.sources,
1553
+ recall_text: readRequiredString(row, "recall_text"),
1554
+ revision_hash: readRequiredString(row, "revision_hash"),
1555
+ source_hash: readRequiredString(row, "source_hash"),
1556
+ source_file: readOptionalString(row, "source_file"),
1557
+ embedding: readEmbedding(row, "embedding"),
1558
+ retired: readBoolean(row, "retired"),
1559
+ retired_at: readOptionalString(row, "retired_at"),
1560
+ retired_reason: readOptionalString(row, "retired_reason"),
1561
+ superseded_by: readOptionalString(row, "superseded_by"),
1562
+ created_at: readRequiredString(row, "created_at"),
1563
+ updated_at: readRequiredString(row, "updated_at")
1564
+ };
1565
+ }
1566
+ function parseProcedureBody(bodyJson) {
1567
+ let parsed;
1568
+ try {
1569
+ parsed = JSON.parse(bodyJson);
1570
+ } catch (error) {
1571
+ const message = error instanceof Error ? error.message : String(error);
1572
+ throw new Error(`Invalid procedures.body_json payload: ${message}`, {
1573
+ cause: error
1574
+ });
1575
+ }
1576
+ return normalizeProcedureDefinition(parsed, "procedures.body_json");
1577
+ }
1578
+
1579
+ // src/adapters/db/procedure-queries.ts
1580
+ var LOOKUP_CHUNK_SIZE = 100;
1581
+ var FTS_TIERS = ["exact", "all_tokens", "any_tokens"];
1582
+ async function upsertProcedure(executor, procedure) {
1583
+ const payload = normalizeStoredProcedure(procedure);
1584
+ const id = payload.id.trim().length > 0 ? payload.id.trim() : randomUUID2();
1585
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1586
+ const createdAt = normalizeTimestamp(payload.created_at) ?? now;
1587
+ const updatedAt = normalizeTimestamp(payload.updated_at) ?? now;
1588
+ const vectorJson = serializeEmbeddingForVector(payload.embedding ?? []);
1589
+ await executor.execute({
1590
+ sql: `
1591
+ INSERT INTO procedures (
1592
+ id,
1593
+ procedure_key,
1594
+ title,
1595
+ goal,
1596
+ body_json,
1597
+ recall_text,
1598
+ source_file,
1599
+ source_hash,
1600
+ revision_hash,
1601
+ embedding,
1602
+ retired,
1603
+ retired_at,
1604
+ retired_reason,
1605
+ superseded_by,
1606
+ created_at,
1607
+ updated_at
1608
+ )
1609
+ VALUES (
1610
+ ?, ?, ?, ?, ?, ?, ?, ?, ?,
1611
+ CASE WHEN ? IS NULL THEN NULL ELSE vector32(?) END,
1612
+ ?, ?, ?, ?, ?, ?
1613
+ )
1614
+ ON CONFLICT(id) DO UPDATE SET
1615
+ procedure_key = excluded.procedure_key,
1616
+ title = excluded.title,
1617
+ goal = excluded.goal,
1618
+ body_json = excluded.body_json,
1619
+ recall_text = excluded.recall_text,
1620
+ source_file = excluded.source_file,
1621
+ source_hash = excluded.source_hash,
1622
+ revision_hash = excluded.revision_hash,
1623
+ embedding = excluded.embedding,
1624
+ retired = excluded.retired,
1625
+ retired_at = excluded.retired_at,
1626
+ retired_reason = excluded.retired_reason,
1627
+ superseded_by = excluded.superseded_by,
1628
+ updated_at = excluded.updated_at
1629
+ `,
1630
+ args: [
1631
+ id,
1632
+ payload.procedure_key,
1633
+ payload.title,
1634
+ payload.goal,
1635
+ serializeProcedureBody(payload.body),
1636
+ payload.recall_text,
1637
+ toNullableString2(payload.source_file),
1638
+ payload.source_hash,
1639
+ payload.revision_hash,
1640
+ vectorJson,
1641
+ vectorJson,
1642
+ payload.retired ? 1 : 0,
1643
+ toNullableString2(payload.retired_at),
1644
+ toNullableString2(payload.retired_reason),
1645
+ toNullableString2(payload.superseded_by),
1646
+ createdAt,
1647
+ updatedAt
1648
+ ]
1649
+ });
1650
+ return getProcedureById(executor, id);
1651
+ }
1652
+ async function getProcedure(executor, id) {
1653
+ const [procedure] = await hydrateProcedures(executor, [id]);
1654
+ return procedure ?? null;
1655
+ }
1656
+ async function hydrateProcedures(executor, ids) {
1657
+ const normalizedIds = dedupeStrings(ids);
1658
+ if (normalizedIds.length === 0) {
1659
+ return [];
1660
+ }
1661
+ const byId = /* @__PURE__ */ new Map();
1662
+ for (const chunk of chunkValues(normalizedIds, LOOKUP_CHUNK_SIZE)) {
1663
+ const placeholders = chunk.map(() => "?").join(", ");
1664
+ const result = await executor.execute({
1665
+ sql: `
1666
+ SELECT
1667
+ ${PROCEDURE_SELECT_COLUMNS}
1668
+ FROM procedures
1669
+ WHERE id IN (${placeholders})
1670
+ AND ${ACTIVE_PROCEDURE_CLAUSE}
1671
+ `,
1672
+ args: chunk
1673
+ });
1674
+ for (const row of result.rows) {
1675
+ const procedure = mapProcedureRow(row);
1676
+ byId.set(procedure.id, procedure);
1677
+ }
1678
+ }
1679
+ return ids.map((id) => byId.get(id.trim())).filter((procedure) => procedure !== void 0);
1680
+ }
1681
+ async function findActiveProcedureByKey(executor, procedureKey) {
1682
+ const normalizedKey = normalizeOptionalString3(procedureKey);
1683
+ if (!normalizedKey) {
1684
+ return null;
1685
+ }
1686
+ const result = await executor.execute({
1687
+ sql: `
1688
+ SELECT ${PROCEDURE_SELECT_COLUMNS}
1689
+ FROM procedures
1690
+ WHERE procedure_key = ?
1691
+ AND ${ACTIVE_PROCEDURE_CLAUSE}
1692
+ LIMIT 1
1693
+ `,
1694
+ args: [normalizedKey]
1695
+ });
1696
+ const row = result.rows[0];
1697
+ return row ? mapProcedureRow(row) : null;
1698
+ }
1699
+ async function procedureVectorSearch(executor, params) {
1700
+ if (params.limit <= 0 || params.embedding.length === 0) {
1701
+ return [];
1702
+ }
1703
+ const serializedEmbedding = serializeEmbeddingForVector(params.embedding);
1704
+ if (!serializedEmbedding) {
1705
+ return [];
1706
+ }
1707
+ let result;
1708
+ try {
1709
+ result = await executor.execute({
1710
+ sql: `
1711
+ SELECT ${prefixColumns2(PROCEDURE_SELECT_COLUMNS, "p")}
1712
+ FROM vector_top_k('idx_procedures_embedding', vector32(?), ?) AS v
1713
+ JOIN procedures AS p ON p.rowid = v.id
1714
+ WHERE ${buildActiveProcedureClause("p")}
1715
+ LIMIT ?
1716
+ `,
1717
+ args: [serializedEmbedding, params.limit, params.limit]
1718
+ });
1719
+ } catch (error) {
1720
+ throw wrapProcedureVectorError(error);
1721
+ }
1722
+ return result.rows.map((row) => {
1723
+ const procedure = mapProcedureRow(row);
1724
+ return {
1725
+ procedure,
1726
+ vectorSim: cosineSimilarity(params.embedding, procedure.embedding ?? [])
1727
+ };
1728
+ }).filter((candidate) => candidate.vectorSim > 0).sort((left, right) => right.vectorSim - left.vectorSim).slice(0, params.limit);
1729
+ }
1730
+ async function procedureFtsSearch(executor, params) {
1731
+ const query = normalizeOptionalString3(params.text);
1732
+ if (!query || params.limit <= 0) {
1733
+ return [];
1734
+ }
1735
+ const plan = buildLexicalPlan(query);
1736
+ if (plan.length === 0) {
1737
+ return [];
1738
+ }
1739
+ const matches = /* @__PURE__ */ new Map();
1740
+ for (const tier of plan) {
1741
+ let result;
1742
+ try {
1743
+ result = await executor.execute({
1744
+ sql: `
1745
+ SELECT
1746
+ ${prefixColumns2(PROCEDURE_SELECT_COLUMNS, "p")},
1747
+ bm25(procedures_fts, 1.0, 2.0) AS rank
1748
+ FROM procedures_fts
1749
+ JOIN procedures AS p ON p.rowid = procedures_fts.rowid
1750
+ WHERE procedures_fts MATCH ?
1751
+ AND ${buildActiveProcedureClause("p")}
1752
+ ORDER BY bm25(procedures_fts, 1.0, 2.0)
1753
+ LIMIT ?
1754
+ `,
1755
+ args: [compileLexicalTier(tier), params.limit]
1756
+ });
1757
+ } catch {
1758
+ continue;
1759
+ }
1760
+ for (const row of result.rows) {
1761
+ const procedure = mapProcedureRow(row);
1762
+ if (matches.has(procedure.id)) {
1763
+ continue;
1764
+ }
1765
+ matches.set(procedure.id, {
1766
+ procedure,
1767
+ rank: readNumber(row, "rank", Number.POSITIVE_INFINITY),
1768
+ tier: tier.tier
1769
+ });
1770
+ }
1771
+ }
1772
+ return Array.from(matches.values()).sort(compareProcedureFtsMatches).slice(0, params.limit).map(({ procedure, rank }) => ({
1773
+ procedure,
1774
+ rank
1775
+ }));
1776
+ }
1777
+ async function listProceduresWithoutEmbeddings(executor, limit) {
1778
+ const normalizedLimit = normalizePositiveInteger2(limit);
1779
+ const result = await executor.execute({
1780
+ sql: `
1781
+ SELECT ${PROCEDURE_SELECT_COLUMNS}
1782
+ FROM procedures
1783
+ WHERE ${ACTIVE_PROCEDURE_CLAUSE}
1784
+ AND embedding IS NULL
1785
+ ORDER BY created_at DESC, id ASC
1786
+ ${normalizedLimit ? "LIMIT ?" : ""}
1787
+ `,
1788
+ args: normalizedLimit ? [normalizedLimit] : []
1789
+ });
1790
+ return result.rows.map((row) => mapProcedureRow(row));
1791
+ }
1792
+ async function updateProcedureEmbedding(executor, id, embedding) {
1793
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1794
+ const vectorJson = serializeEmbeddingForVector(embedding);
1795
+ await executor.execute({
1796
+ sql: `
1797
+ UPDATE procedures
1798
+ SET embedding = CASE WHEN ? IS NULL THEN NULL ELSE vector32(?) END,
1799
+ updated_at = ?
1800
+ WHERE id = ?
1801
+ `,
1802
+ args: [vectorJson, vectorJson, now, id]
1803
+ });
1804
+ }
1805
+ async function retireProcedure(executor, id, reason) {
1806
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1807
+ const result = await executor.execute({
1808
+ sql: `
1809
+ UPDATE procedures
1810
+ SET retired = 1,
1811
+ retired_at = ?,
1812
+ retired_reason = ?,
1813
+ updated_at = ?
1814
+ WHERE id = ?
1815
+ AND ${ACTIVE_PROCEDURE_CLAUSE}
1816
+ `,
1817
+ args: [now, toNullableString2(normalizeOptionalString3(reason)), now, id]
1818
+ });
1819
+ return result.rowsAffected > 0;
1820
+ }
1821
+ async function supersedeProcedure(executor, oldId, newId, reason) {
1822
+ const normalizedOldId = normalizeOptionalString3(oldId);
1823
+ const normalizedNewId = normalizeOptionalString3(newId);
1824
+ if (!normalizedOldId || !normalizedNewId || normalizedOldId === normalizedNewId) {
1825
+ return false;
1826
+ }
1827
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1828
+ const result = await executor.execute({
1829
+ sql: `
1830
+ UPDATE procedures
1831
+ SET superseded_by = ?,
1832
+ retired_reason = COALESCE(?, retired_reason),
1833
+ updated_at = ?
1834
+ WHERE id = ?
1835
+ AND ${ACTIVE_PROCEDURE_CLAUSE}
1836
+ `,
1837
+ args: [normalizedNewId, toNullableString2(normalizeOptionalString3(reason)), now, normalizedOldId]
1838
+ });
1839
+ return result.rowsAffected > 0;
1840
+ }
1841
+ async function getProcedureById(executor, id) {
1842
+ const result = await executor.execute({
1843
+ sql: `
1844
+ SELECT ${PROCEDURE_SELECT_COLUMNS}
1845
+ FROM procedures
1846
+ WHERE id = ?
1847
+ LIMIT 1
1848
+ `,
1849
+ args: [id]
1850
+ });
1851
+ const row = result.rows[0];
1852
+ if (!row) {
1853
+ throw new Error(`Procedure ${id} was not found after persistence.`);
1854
+ }
1855
+ return mapProcedureRow(row);
1856
+ }
1857
+ function normalizeStoredProcedure(procedure) {
1858
+ const body = normalizeProcedureDefinition(
1859
+ {
1860
+ procedure_key: procedure.procedure_key,
1861
+ title: procedure.title,
1862
+ goal: procedure.goal,
1863
+ when_to_use: procedure.when_to_use,
1864
+ when_not_to_use: procedure.when_not_to_use,
1865
+ prerequisites: procedure.prerequisites,
1866
+ steps: procedure.steps,
1867
+ verification: procedure.verification,
1868
+ failure_modes: procedure.failure_modes,
1869
+ sources: procedure.sources
1870
+ },
1871
+ "procedure write payload"
1872
+ );
1873
+ return {
1874
+ ...procedure,
1875
+ ...body,
1876
+ body,
1877
+ recall_text: normalizeRequiredText(procedure.recall_text, "recall_text"),
1878
+ revision_hash: normalizeRequiredText(procedure.revision_hash, "revision_hash"),
1879
+ source_hash: normalizeRequiredText(procedure.source_hash, "source_hash"),
1880
+ source_file: normalizeOptionalString3(procedure.source_file),
1881
+ retired_at: normalizeOptionalString3(procedure.retired_at),
1882
+ retired_reason: normalizeOptionalString3(procedure.retired_reason),
1883
+ superseded_by: normalizeOptionalString3(procedure.superseded_by),
1884
+ embedding: normalizeEmbedding2(procedure.embedding)
1885
+ };
1886
+ }
1887
+ function normalizeRequiredText(value, label) {
1888
+ const normalized = normalizeOptionalString3(value);
1889
+ if (!normalized) {
1890
+ throw new Error(`Procedure ${label} must be a non-empty string.`);
1891
+ }
1892
+ return normalized;
1893
+ }
1894
+ function normalizeOptionalString3(value) {
1895
+ const trimmed = value?.trim();
1896
+ return trimmed ? trimmed : void 0;
1897
+ }
1898
+ function normalizeTimestamp(value) {
1899
+ return normalizeOptionalString3(value);
1900
+ }
1901
+ function normalizeEmbedding2(embedding) {
1902
+ if (!embedding || embedding.length === 0) {
1903
+ return void 0;
1904
+ }
1905
+ return embedding.map((value) => Number.isFinite(value) ? value : 0);
1906
+ }
1907
+ function normalizePositiveInteger2(value) {
1908
+ if (value === void 0 || !Number.isFinite(value) || value <= 0) {
1909
+ return void 0;
1910
+ }
1911
+ return Math.trunc(value);
1912
+ }
1913
+ function chunkValues(values, size) {
1914
+ const chunks = [];
1915
+ for (let index = 0; index < values.length; index += size) {
1916
+ chunks.push(values.slice(index, index + size));
1917
+ }
1918
+ return chunks;
1919
+ }
1920
+ function dedupeStrings(values) {
1921
+ return Array.from(new Set(values.map((value) => value.trim()).filter((value) => value.length > 0)));
1922
+ }
1923
+ function prefixColumns2(columns, alias) {
1924
+ return columns.split(",").map((column) => column.trim()).filter((column) => column.length > 0).map((column) => `${alias}.${column}`).join(", ");
1925
+ }
1926
+ function compareProcedureFtsMatches(left, right) {
1927
+ const tierDelta = ftsTierPriority(left.tier) - ftsTierPriority(right.tier);
1928
+ if (tierDelta !== 0) {
1929
+ return tierDelta;
1930
+ }
1931
+ if (left.rank !== right.rank) {
1932
+ return left.rank - right.rank;
1933
+ }
1934
+ return left.procedure.procedure_key.localeCompare(right.procedure.procedure_key);
1935
+ }
1936
+ function ftsTierPriority(tier) {
1937
+ return FTS_TIERS.indexOf(tier);
1938
+ }
1939
+ function compileLexicalTier(tier) {
1940
+ if (tier.tier === "exact") {
1941
+ return `"${tier.text.replaceAll('"', '""')}"`;
1942
+ }
1943
+ return tier.tier === "all_tokens" ? tier.tokens.join(" ") : tier.tokens.join(" OR ");
1944
+ }
1945
+ function wrapProcedureVectorError(error) {
1946
+ const message = error instanceof Error ? error.message : String(error);
1947
+ return new Error(`Procedure vector search is unavailable: ${message}`);
1948
+ }
1949
+ function toNullableString2(value) {
1950
+ return value ?? null;
1951
+ }
1952
+
1139
1953
  // src/adapters/db/queries.ts
1140
- import { randomUUID as randomUUID2 } from "crypto";
1954
+ import { randomUUID as randomUUID3 } from "crypto";
1141
1955
 
1142
1956
  // src/core/temporal-validity.ts
1143
1957
  function validateTemporalValidityRange(validFrom, validTo) {
@@ -1189,13 +2003,13 @@ function normalizeOptionalTimestamp(value) {
1189
2003
  }
1190
2004
 
1191
2005
  // src/adapters/db/queries.ts
1192
- var LOOKUP_CHUNK_SIZE = 100;
2006
+ var LOOKUP_CHUNK_SIZE2 = 100;
1193
2007
  var DEFAULT_QUALITY_SCORE2 = 0.5;
1194
2008
  async function insertEntry(executor, entry, embedding, contentHash) {
1195
2009
  const now = (/* @__PURE__ */ new Date()).toISOString();
1196
- const id = entry.id.trim().length > 0 ? entry.id.trim() : randomUUID2();
1197
- const createdAt = normalizeTimestamp(entry.created_at) ?? now;
1198
- const updatedAt = normalizeTimestamp(entry.updated_at) ?? now;
2010
+ const id = entry.id.trim().length > 0 ? entry.id.trim() : randomUUID3();
2011
+ const createdAt = normalizeTimestamp2(entry.created_at) ?? now;
2012
+ const updatedAt = normalizeTimestamp2(entry.updated_at) ?? now;
1199
2013
  const vectorJson = serializeEmbeddingForVector(embedding);
1200
2014
  await executor.execute({
1201
2015
  sql: `
@@ -1254,37 +2068,37 @@ async function insertEntry(executor, entry, embedding, contentHash) {
1254
2068
  normalizeInteger(entry.importance, 0),
1255
2069
  entry.expiry,
1256
2070
  serializeTags(entry.tags),
1257
- normalizeOptionalString3(entry.source_file),
1258
- normalizeOptionalString3(entry.source_context),
2071
+ normalizeOptionalString4(entry.source_file),
2072
+ normalizeOptionalString4(entry.source_context),
1259
2073
  vectorJson,
1260
2074
  vectorJson,
1261
2075
  contentHash.trim(),
1262
- normalizeOptionalString3(entry.norm_content_hash),
2076
+ normalizeOptionalString4(entry.norm_content_hash),
1263
2077
  null,
1264
2078
  normalizeNumber(entry.quality_score, DEFAULT_QUALITY_SCORE2),
1265
2079
  normalizeInteger(entry.recall_count, 0),
1266
- normalizeOptionalString3(entry.last_recalled_at),
1267
- normalizeOptionalString3(entry.superseded_by),
1268
- normalizeOptionalString3(entry.valid_from),
1269
- normalizeOptionalString3(entry.valid_to),
1270
- normalizeOptionalString3(entry.claim_key),
1271
- normalizeOptionalString3(entry.claim_key_raw),
1272
- normalizeOptionalString3(entry.claim_key_status),
1273
- normalizeOptionalString3(entry.claim_key_source),
2080
+ normalizeOptionalString4(entry.last_recalled_at),
2081
+ normalizeOptionalString4(entry.superseded_by),
2082
+ normalizeOptionalString4(entry.valid_from),
2083
+ normalizeOptionalString4(entry.valid_to),
2084
+ normalizeOptionalString4(entry.claim_key),
2085
+ normalizeOptionalString4(entry.claim_key_raw),
2086
+ normalizeOptionalString4(entry.claim_key_status),
2087
+ normalizeOptionalString4(entry.claim_key_source),
1274
2088
  normalizeOptionalNumber(entry.claim_key_confidence),
1275
- normalizeOptionalString3(entry.claim_key_rationale),
1276
- normalizeOptionalString3(entry.claim_support_source_kind),
1277
- normalizeOptionalString3(entry.claim_support_locator),
1278
- normalizeOptionalString3(entry.claim_support_observed_at),
1279
- normalizeOptionalString3(entry.claim_support_mode),
1280
- normalizeOptionalString3(entry.supersession_kind),
1281
- normalizeOptionalString3(entry.supersession_reason),
1282
- normalizeOptionalString3(entry.cluster_id),
1283
- normalizeOptionalString3(entry.user_id),
1284
- normalizeOptionalString3(entry.project),
2089
+ normalizeOptionalString4(entry.claim_key_rationale),
2090
+ normalizeOptionalString4(entry.claim_support_source_kind),
2091
+ normalizeOptionalString4(entry.claim_support_locator),
2092
+ normalizeOptionalString4(entry.claim_support_observed_at),
2093
+ normalizeOptionalString4(entry.claim_support_mode),
2094
+ normalizeOptionalString4(entry.supersession_kind),
2095
+ normalizeOptionalString4(entry.supersession_reason),
2096
+ normalizeOptionalString4(entry.cluster_id),
2097
+ normalizeOptionalString4(entry.user_id),
2098
+ normalizeOptionalString4(entry.project),
1285
2099
  entry.retired ? 1 : 0,
1286
- normalizeOptionalString3(entry.retired_at),
1287
- normalizeOptionalString3(entry.retired_reason),
2100
+ normalizeOptionalString4(entry.retired_at),
2101
+ normalizeOptionalString4(entry.retired_reason),
1288
2102
  createdAt,
1289
2103
  updatedAt
1290
2104
  ]
@@ -1292,12 +2106,12 @@ async function insertEntry(executor, entry, embedding, contentHash) {
1292
2106
  return id;
1293
2107
  }
1294
2108
  async function getEntries(executor, ids) {
1295
- const normalizedIds = dedupeStrings(ids);
2109
+ const normalizedIds = dedupeStrings2(ids);
1296
2110
  if (normalizedIds.length === 0) {
1297
2111
  return [];
1298
2112
  }
1299
2113
  const byId = /* @__PURE__ */ new Map();
1300
- for (const chunk of chunkValues(normalizedIds, LOOKUP_CHUNK_SIZE)) {
2114
+ for (const chunk of chunkValues2(normalizedIds, LOOKUP_CHUNK_SIZE2)) {
1301
2115
  const placeholders = chunk.map(() => "?").join(", ");
1302
2116
  const result = await executor.execute({
1303
2117
  sql: `
@@ -1321,12 +2135,12 @@ async function getEntry(executor, id) {
1321
2135
  return entry ?? null;
1322
2136
  }
1323
2137
  async function findExistingHashes(executor, hashes) {
1324
- const normalizedHashes = dedupeStrings(hashes);
2138
+ const normalizedHashes = dedupeStrings2(hashes);
1325
2139
  if (normalizedHashes.length === 0) {
1326
2140
  return /* @__PURE__ */ new Set();
1327
2141
  }
1328
2142
  const matches = /* @__PURE__ */ new Set();
1329
- for (const chunk of chunkValues(normalizedHashes, LOOKUP_CHUNK_SIZE)) {
2143
+ for (const chunk of chunkValues2(normalizedHashes, LOOKUP_CHUNK_SIZE2)) {
1330
2144
  const placeholders = chunk.map(() => "?").join(", ");
1331
2145
  const result = await executor.execute({
1332
2146
  sql: `
@@ -1344,12 +2158,12 @@ async function findExistingHashes(executor, hashes) {
1344
2158
  return matches;
1345
2159
  }
1346
2160
  async function findExistingNormHashes(executor, hashes) {
1347
- const normalizedHashes = dedupeStrings(hashes);
2161
+ const normalizedHashes = dedupeStrings2(hashes);
1348
2162
  if (normalizedHashes.length === 0) {
1349
2163
  return /* @__PURE__ */ new Set();
1350
2164
  }
1351
2165
  const matches = /* @__PURE__ */ new Set();
1352
- for (const chunk of chunkValues(normalizedHashes, LOOKUP_CHUNK_SIZE)) {
2166
+ for (const chunk of chunkValues2(normalizedHashes, LOOKUP_CHUNK_SIZE2)) {
1353
2167
  const placeholders = chunk.map(() => "?").join(", ");
1354
2168
  const result = await executor.execute({
1355
2169
  sql: `
@@ -1378,7 +2192,7 @@ async function retireEntry(executor, id, reason) {
1378
2192
  WHERE id = ?
1379
2193
  AND ${ACTIVE_ENTRY_CLAUSE}
1380
2194
  `,
1381
- args: [now, normalizeOptionalString3(reason), now, id]
2195
+ args: [now, normalizeOptionalString4(reason), now, id]
1382
2196
  });
1383
2197
  return result.rowsAffected > 0;
1384
2198
  }
@@ -1412,7 +2226,7 @@ async function supersedeEntry(executor, oldId, newId, kind, reason) {
1412
2226
  WHERE id = ?
1413
2227
  AND ${ACTIVE_ENTRY_CLAUSE}
1414
2228
  `,
1415
- args: [normalizedNewId, normalizeOptionalString3(kind) ?? "update", normalizeOptionalString3(reason), now, normalizedOldId]
2229
+ args: [normalizedNewId, normalizeOptionalString4(kind) ?? "update", normalizeOptionalString4(reason), now, normalizedOldId]
1416
2230
  });
1417
2231
  return result.rowsAffected > 0;
1418
2232
  }
@@ -1552,25 +2366,25 @@ async function updateEntry(executor, id, fields, options) {
1552
2366
  assignments.push("claim_support_observed_at = ?");
1553
2367
  assignments.push("claim_support_mode = ?");
1554
2368
  args.push(
1555
- normalizeOptionalString3(lifecycleUpdate.claim_key),
1556
- normalizeOptionalString3(lifecycleUpdate.claim_key_raw),
1557
- normalizeOptionalString3(lifecycleUpdate.claim_key_status),
1558
- normalizeOptionalString3(lifecycleUpdate.claim_key_source),
2369
+ normalizeOptionalString4(lifecycleUpdate.claim_key),
2370
+ normalizeOptionalString4(lifecycleUpdate.claim_key_raw),
2371
+ normalizeOptionalString4(lifecycleUpdate.claim_key_status),
2372
+ normalizeOptionalString4(lifecycleUpdate.claim_key_source),
1559
2373
  normalizeOptionalNumber(lifecycleUpdate.claim_key_confidence),
1560
- normalizeOptionalString3(lifecycleUpdate.claim_key_rationale),
1561
- normalizeOptionalString3(lifecycleUpdate.claim_support_source_kind),
1562
- normalizeOptionalString3(lifecycleUpdate.claim_support_locator),
1563
- normalizeTimestamp(lifecycleUpdate.claim_support_observed_at),
1564
- normalizeOptionalString3(lifecycleUpdate.claim_support_mode)
2374
+ normalizeOptionalString4(lifecycleUpdate.claim_key_rationale),
2375
+ normalizeOptionalString4(lifecycleUpdate.claim_support_source_kind),
2376
+ normalizeOptionalString4(lifecycleUpdate.claim_support_locator),
2377
+ normalizeTimestamp2(lifecycleUpdate.claim_support_observed_at),
2378
+ normalizeOptionalString4(lifecycleUpdate.claim_support_mode)
1565
2379
  );
1566
2380
  }
1567
2381
  if (fields.valid_from !== void 0) {
1568
2382
  assignments.push("valid_from = ?");
1569
- args.push(normalizeOptionalString3(fields.valid_from));
2383
+ args.push(normalizeOptionalString4(fields.valid_from));
1570
2384
  }
1571
2385
  if (fields.valid_to !== void 0) {
1572
2386
  assignments.push("valid_to = ?");
1573
- args.push(normalizeOptionalString3(fields.valid_to));
2387
+ args.push(normalizeOptionalString4(fields.valid_to));
1574
2388
  }
1575
2389
  if (assignments.length === 0) {
1576
2390
  return false;
@@ -1636,7 +2450,7 @@ async function recordRecallEvent(executor, entryId, query, sessionKey) {
1636
2450
  )
1637
2451
  VALUES (?, ?, ?, ?, ?)
1638
2452
  `,
1639
- args: [randomUUID2(), entryId, query, normalizeOptionalString3(sessionKey), now]
2453
+ args: [randomUUID3(), entryId, query, normalizeOptionalString4(sessionKey), now]
1640
2454
  });
1641
2455
  }
1642
2456
  async function getIngestLogEntry(executor, filePath) {
@@ -1676,25 +2490,25 @@ async function insertIngestLogEntry(executor, filePath, fileHash, entryCount) {
1676
2490
  args: [filePath, fileHash, (/* @__PURE__ */ new Date()).toISOString(), normalizeInteger(entryCount, 0)]
1677
2491
  });
1678
2492
  }
1679
- function dedupeStrings(values) {
2493
+ function dedupeStrings2(values) {
1680
2494
  return Array.from(new Set(values.map((value) => value.trim()).filter((value) => value.length > 0)));
1681
2495
  }
1682
- function chunkValues(values, size) {
2496
+ function chunkValues2(values, size) {
1683
2497
  const chunks = [];
1684
2498
  for (let index = 0; index < values.length; index += size) {
1685
2499
  chunks.push(values.slice(index, index + size));
1686
2500
  }
1687
2501
  return chunks;
1688
2502
  }
1689
- function normalizeOptionalString3(value) {
2503
+ function normalizeOptionalString4(value) {
1690
2504
  if (value === void 0) {
1691
2505
  return null;
1692
2506
  }
1693
2507
  const trimmed = value.trim();
1694
2508
  return trimmed.length > 0 ? trimmed : null;
1695
2509
  }
1696
- function normalizeTimestamp(value) {
1697
- return normalizeOptionalString3(value);
2510
+ function normalizeTimestamp2(value) {
2511
+ return normalizeOptionalString4(value);
1698
2512
  }
1699
2513
  function normalizeOptionalNumber(value) {
1700
2514
  return typeof value === "number" && Number.isFinite(value) ? value : null;
@@ -1721,9 +2535,10 @@ function normalizeInteger(value, fallback) {
1721
2535
  }
1722
2536
 
1723
2537
  // src/adapters/db/schema.ts
1724
- var SCHEMA_VERSION = "9";
2538
+ var SCHEMA_VERSION = "10";
1725
2539
  var VECTOR_INDEX_NAME = "idx_entries_embedding";
1726
2540
  var EPISODE_VECTOR_INDEX_NAME = "idx_episodes_embedding";
2541
+ var PROCEDURE_VECTOR_INDEX_NAME = "idx_procedures_embedding";
1727
2542
  var BULK_WRITE_STATE_META_KEY = "bulk_write_state";
1728
2543
  var LAST_BULK_INGEST_META_KEY = "last_bulk_ingest_at";
1729
2544
  var CREATE_ENTRIES_TABLE_SQL = `
@@ -1839,6 +2654,59 @@ var CREATE_EPISODES_TABLE_SQL = `
1839
2654
  updated_at TEXT NOT NULL
1840
2655
  )
1841
2656
  `;
2657
+ var CREATE_PROCEDURES_TABLE_SQL = `
2658
+ CREATE TABLE IF NOT EXISTS procedures (
2659
+ id TEXT PRIMARY KEY,
2660
+ procedure_key TEXT NOT NULL,
2661
+ title TEXT NOT NULL,
2662
+ goal TEXT NOT NULL,
2663
+ body_json TEXT NOT NULL,
2664
+ recall_text TEXT NOT NULL,
2665
+ source_file TEXT,
2666
+ source_hash TEXT NOT NULL,
2667
+ revision_hash TEXT NOT NULL,
2668
+ embedding F32_BLOB(1024),
2669
+ retired INTEGER NOT NULL DEFAULT 0,
2670
+ retired_at TEXT,
2671
+ retired_reason TEXT,
2672
+ superseded_by TEXT REFERENCES procedures(id),
2673
+ created_at TEXT NOT NULL,
2674
+ updated_at TEXT NOT NULL
2675
+ )
2676
+ `;
2677
+ var CREATE_PROCEDURES_FTS_TABLE_SQL = `
2678
+ CREATE VIRTUAL TABLE IF NOT EXISTS procedures_fts USING fts5(
2679
+ title,
2680
+ recall_text,
2681
+ content=procedures,
2682
+ content_rowid=rowid
2683
+ )
2684
+ `;
2685
+ var CREATE_PROCEDURES_FTS_INSERT_TRIGGER_SQL = `
2686
+ CREATE TRIGGER IF NOT EXISTS procedures_ai AFTER INSERT ON procedures
2687
+ WHEN new.retired = 0 AND new.superseded_by IS NULL BEGIN
2688
+ INSERT INTO procedures_fts(rowid, title, recall_text)
2689
+ VALUES (new.rowid, new.title, new.recall_text);
2690
+ END
2691
+ `;
2692
+ var CREATE_PROCEDURES_FTS_DELETE_TRIGGER_SQL = `
2693
+ CREATE TRIGGER IF NOT EXISTS procedures_ad AFTER DELETE ON procedures
2694
+ WHEN old.retired = 0 AND old.superseded_by IS NULL BEGIN
2695
+ INSERT INTO procedures_fts(procedures_fts, rowid, title, recall_text)
2696
+ VALUES ('delete', old.rowid, old.title, old.recall_text);
2697
+ END
2698
+ `;
2699
+ var CREATE_PROCEDURES_FTS_UPDATE_TRIGGER_SQL = `
2700
+ CREATE TRIGGER IF NOT EXISTS procedures_au AFTER UPDATE ON procedures BEGIN
2701
+ INSERT INTO procedures_fts(procedures_fts, rowid, title, recall_text)
2702
+ SELECT 'delete', old.rowid, old.title, old.recall_text
2703
+ WHERE old.retired = 0 AND old.superseded_by IS NULL;
2704
+
2705
+ INSERT INTO procedures_fts(rowid, title, recall_text)
2706
+ SELECT new.rowid, new.title, new.recall_text
2707
+ WHERE new.retired = 0 AND new.superseded_by IS NULL;
2708
+ END
2709
+ `;
1842
2710
  var CREATE_RECALL_EVENTS_TABLE_SQL = `
1843
2711
  CREATE TABLE IF NOT EXISTS recall_events (
1844
2712
  id TEXT PRIMARY KEY,
@@ -2006,6 +2874,32 @@ var CREATE_EPISODES_SOURCE_SOURCE_ID_UNIQUE_INDEX_SQL = `
2006
2874
  ON episodes(source, source_id)
2007
2875
  WHERE source_id IS NOT NULL
2008
2876
  `;
2877
+ var CREATE_PROCEDURES_PROCEDURE_KEY_INDEX_SQL = `
2878
+ CREATE INDEX IF NOT EXISTS idx_procedures_procedure_key
2879
+ ON procedures(procedure_key)
2880
+ `;
2881
+ var CREATE_PROCEDURES_REVISION_HASH_INDEX_SQL = `
2882
+ CREATE INDEX IF NOT EXISTS idx_procedures_revision_hash
2883
+ ON procedures(revision_hash)
2884
+ `;
2885
+ var CREATE_PROCEDURES_SOURCE_HASH_INDEX_SQL = `
2886
+ CREATE INDEX IF NOT EXISTS idx_procedures_source_hash
2887
+ ON procedures(source_hash)
2888
+ `;
2889
+ var CREATE_PROCEDURES_RETIRED_INDEX_SQL = `
2890
+ CREATE INDEX IF NOT EXISTS idx_procedures_retired
2891
+ ON procedures(retired)
2892
+ `;
2893
+ var CREATE_PROCEDURES_CREATED_AT_INDEX_SQL = `
2894
+ CREATE INDEX IF NOT EXISTS idx_procedures_created_at
2895
+ ON procedures(created_at)
2896
+ `;
2897
+ var CREATE_PROCEDURES_ACTIVE_KEY_UNIQUE_INDEX_SQL = `
2898
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_procedures_active_procedure_key
2899
+ ON procedures(procedure_key)
2900
+ WHERE retired = 0
2901
+ AND superseded_by IS NULL
2902
+ `;
2009
2903
  var CREATE_RECALL_EVENTS_ENTRY_ID_INDEX_SQL = `
2010
2904
  CREATE INDEX IF NOT EXISTS idx_recall_events_entry_id
2011
2905
  ON recall_events(entry_id)
@@ -2040,6 +2934,19 @@ var CREATE_EPISODES_EMBEDDING_INDEX_SQL = `
2040
2934
  AND retired = 0
2041
2935
  AND superseded_by IS NULL
2042
2936
  `;
2937
+ var CREATE_PROCEDURES_EMBEDDING_INDEX_SQL = `
2938
+ CREATE INDEX IF NOT EXISTS idx_procedures_embedding ON procedures (
2939
+ libsql_vector_idx(
2940
+ embedding,
2941
+ 'metric=cosine',
2942
+ 'compress_neighbors=float8',
2943
+ 'max_neighbors=50'
2944
+ )
2945
+ )
2946
+ WHERE embedding IS NOT NULL
2947
+ AND retired = 0
2948
+ AND superseded_by IS NULL
2949
+ `;
2043
2950
  var SCHEMA_STATEMENTS = [
2044
2951
  CREATE_ENTRIES_TABLE_SQL,
2045
2952
  CREATE_ENTRIES_FTS_TABLE_SQL,
@@ -2048,6 +2955,11 @@ var SCHEMA_STATEMENTS = [
2048
2955
  CREATE_ENTRIES_FTS_UPDATE_TRIGGER_SQL,
2049
2956
  CREATE_INGEST_LOG_TABLE_SQL,
2050
2957
  CREATE_EPISODES_TABLE_SQL,
2958
+ CREATE_PROCEDURES_TABLE_SQL,
2959
+ CREATE_PROCEDURES_FTS_TABLE_SQL,
2960
+ CREATE_PROCEDURES_FTS_INSERT_TRIGGER_SQL,
2961
+ CREATE_PROCEDURES_FTS_DELETE_TRIGGER_SQL,
2962
+ CREATE_PROCEDURES_FTS_UPDATE_TRIGGER_SQL,
2051
2963
  CREATE_RECALL_EVENTS_TABLE_SQL,
2052
2964
  CREATE_SURGEON_RUNS_TABLE_SQL,
2053
2965
  CREATE_SURGEON_RUN_ACTIONS_TABLE_SQL,
@@ -2076,6 +2988,12 @@ var SCHEMA_STATEMENTS = [
2076
2988
  CREATE_EPISODES_SOURCE_ID_INDEX_SQL,
2077
2989
  CREATE_EPISODES_RETIRED_INDEX_SQL,
2078
2990
  CREATE_EPISODES_SOURCE_SOURCE_ID_UNIQUE_INDEX_SQL,
2991
+ CREATE_PROCEDURES_PROCEDURE_KEY_INDEX_SQL,
2992
+ CREATE_PROCEDURES_REVISION_HASH_INDEX_SQL,
2993
+ CREATE_PROCEDURES_SOURCE_HASH_INDEX_SQL,
2994
+ CREATE_PROCEDURES_RETIRED_INDEX_SQL,
2995
+ CREATE_PROCEDURES_CREATED_AT_INDEX_SQL,
2996
+ CREATE_PROCEDURES_ACTIVE_KEY_UNIQUE_INDEX_SQL,
2079
2997
  CREATE_RECALL_EVENTS_ENTRY_ID_INDEX_SQL,
2080
2998
  CREATE_RECALL_EVENTS_RECALLED_AT_INDEX_SQL
2081
2999
  ];
@@ -2099,7 +3017,12 @@ async function initSchema(db) {
2099
3017
  await migrateV8ToV9(db);
2100
3018
  currentVersion = "9";
2101
3019
  }
3020
+ if (currentVersion === "9") {
3021
+ await migrateV9ToV10(db);
3022
+ currentVersion = "10";
3023
+ }
2102
3024
  const hadEntriesFts = await tableExists(db, "entries_fts");
3025
+ const hadProceduresFts = await tableExists(db, "procedures_fts");
2103
3026
  for (const statement of SCHEMA_STATEMENTS) {
2104
3027
  await db.execute(statement);
2105
3028
  }
@@ -2115,13 +3038,13 @@ async function initSchema(db) {
2115
3038
  await finalizeBulkWrites(db);
2116
3039
  return;
2117
3040
  }
2118
- if (currentVersion !== SCHEMA_VERSION || !hadEntriesFts) {
3041
+ if (currentVersion !== SCHEMA_VERSION || !hadEntriesFts || !hadProceduresFts) {
2119
3042
  await rebuildFts(db);
2120
3043
  }
2121
3044
  await ensureVectorIndexes(db);
2122
3045
  }
2123
3046
  async function assertSupportedSchemaState(db, currentVersion) {
2124
- if (currentVersion && currentVersion !== "5" && currentVersion !== "6" && currentVersion !== "7" && currentVersion !== "8" && currentVersion !== SCHEMA_VERSION) {
3047
+ if (currentVersion && currentVersion !== "5" && currentVersion !== "6" && currentVersion !== "7" && currentVersion !== "8" && currentVersion !== "9" && currentVersion !== SCHEMA_VERSION) {
2125
3048
  throw new Error(
2126
3049
  `Unsupported agenr database schema version "${currentVersion}". This build only supports schema version ${SCHEMA_VERSION}. Create a fresh database with \`agenr db reset\` or manually migrate the data into a new database.`
2127
3050
  );
@@ -2207,8 +3130,22 @@ async function migrateV8ToV9(db) {
2207
3130
  }
2208
3131
  await db.execute(CREATE_SURGEON_RUN_PROPOSALS_REVIEW_STATUS_INDEX_SQL);
2209
3132
  }
3133
+ async function migrateV9ToV10(db) {
3134
+ await db.execute(CREATE_PROCEDURES_TABLE_SQL);
3135
+ await db.execute(CREATE_PROCEDURES_FTS_TABLE_SQL);
3136
+ await db.execute(CREATE_PROCEDURES_FTS_INSERT_TRIGGER_SQL);
3137
+ await db.execute(CREATE_PROCEDURES_FTS_DELETE_TRIGGER_SQL);
3138
+ await db.execute(CREATE_PROCEDURES_FTS_UPDATE_TRIGGER_SQL);
3139
+ await db.execute(CREATE_PROCEDURES_PROCEDURE_KEY_INDEX_SQL);
3140
+ await db.execute(CREATE_PROCEDURES_REVISION_HASH_INDEX_SQL);
3141
+ await db.execute(CREATE_PROCEDURES_SOURCE_HASH_INDEX_SQL);
3142
+ await db.execute(CREATE_PROCEDURES_RETIRED_INDEX_SQL);
3143
+ await db.execute(CREATE_PROCEDURES_CREATED_AT_INDEX_SQL);
3144
+ await db.execute(CREATE_PROCEDURES_ACTIVE_KEY_UNIQUE_INDEX_SQL);
3145
+ }
2210
3146
  async function rebuildFts(db) {
2211
3147
  await db.execute("INSERT INTO entries_fts(entries_fts) VALUES ('rebuild')");
3148
+ await db.execute("INSERT INTO procedures_fts(procedures_fts) VALUES ('rebuild')");
2212
3149
  }
2213
3150
  async function prepareBulkWrites(db) {
2214
3151
  await runImmediateTransaction(db, async () => {
@@ -2223,6 +3160,9 @@ async function prepareBulkWrites(db) {
2223
3160
  await db.execute("DROP TRIGGER IF EXISTS entries_ai");
2224
3161
  await db.execute("DROP TRIGGER IF EXISTS entries_ad");
2225
3162
  await db.execute("DROP TRIGGER IF EXISTS entries_au");
3163
+ await db.execute("DROP TRIGGER IF EXISTS procedures_ai");
3164
+ await db.execute("DROP TRIGGER IF EXISTS procedures_ad");
3165
+ await db.execute("DROP TRIGGER IF EXISTS procedures_au");
2226
3166
  await dropVectorIndexes(db);
2227
3167
  });
2228
3168
  }
@@ -2231,6 +3171,9 @@ async function finalizeBulkWrites(db) {
2231
3171
  await db.execute(CREATE_ENTRIES_FTS_INSERT_TRIGGER_SQL);
2232
3172
  await db.execute(CREATE_ENTRIES_FTS_DELETE_TRIGGER_SQL);
2233
3173
  await db.execute(CREATE_ENTRIES_FTS_UPDATE_TRIGGER_SQL);
3174
+ await db.execute(CREATE_PROCEDURES_FTS_INSERT_TRIGGER_SQL);
3175
+ await db.execute(CREATE_PROCEDURES_FTS_DELETE_TRIGGER_SQL);
3176
+ await db.execute(CREATE_PROCEDURES_FTS_UPDATE_TRIGGER_SQL);
2234
3177
  await rebuildFts(db);
2235
3178
  await ensureVectorIndexes(db);
2236
3179
  await db.execute({
@@ -2316,6 +3259,7 @@ async function ensureVectorIndexes(db) {
2316
3259
  try {
2317
3260
  await db.execute(CREATE_ENTRIES_EMBEDDING_INDEX_SQL);
2318
3261
  await db.execute(CREATE_EPISODES_EMBEDDING_INDEX_SQL);
3262
+ await db.execute(CREATE_PROCEDURES_EMBEDDING_INDEX_SQL);
2319
3263
  } catch (error) {
2320
3264
  if (!isVectorUnavailableError(error)) {
2321
3265
  throw error;
@@ -2326,6 +3270,7 @@ async function dropVectorIndexes(db) {
2326
3270
  try {
2327
3271
  await db.execute(`DROP INDEX IF EXISTS ${VECTOR_INDEX_NAME}`);
2328
3272
  await db.execute(`DROP INDEX IF EXISTS ${EPISODE_VECTOR_INDEX_NAME}`);
3273
+ await db.execute(`DROP INDEX IF EXISTS ${PROCEDURE_VECTOR_INDEX_NAME}`);
2329
3274
  } catch (error) {
2330
3275
  if (!isVectorUnavailableError(error)) {
2331
3276
  throw error;
@@ -2416,6 +3361,46 @@ var LibsqlDatabase = class _LibsqlDatabase {
2416
3361
  async updateEpisodeEmbedding(id, embedding) {
2417
3362
  await updateEpisodeEmbedding(this.executor, id, embedding);
2418
3363
  }
3364
+ /** Inserts or updates one procedure revision row. */
3365
+ async upsertProcedure(procedure) {
3366
+ return upsertProcedure(this.executor, procedure);
3367
+ }
3368
+ /** Loads one active procedure by primary key. */
3369
+ async getProcedure(id) {
3370
+ return getProcedure(this.executor, id);
3371
+ }
3372
+ /** Hydrates active procedures by ID while preserving caller order. */
3373
+ async hydrateProcedures(ids) {
3374
+ return hydrateProcedures(this.executor, ids);
3375
+ }
3376
+ /** Loads one active procedure revision by stable procedure key. */
3377
+ async findActiveProcedureByKey(procedureKey) {
3378
+ return findActiveProcedureByKey(this.executor, procedureKey);
3379
+ }
3380
+ /** Finds active procedures by vector similarity. */
3381
+ async procedureVectorSearch(params) {
3382
+ return procedureVectorSearch(this.executor, params);
3383
+ }
3384
+ /** Finds active procedures by lexical FTS search. */
3385
+ async procedureFtsSearch(params) {
3386
+ return procedureFtsSearch(this.executor, params);
3387
+ }
3388
+ /** Lists active procedures that still need embeddings. */
3389
+ async listProceduresWithoutEmbeddings(limit) {
3390
+ return listProceduresWithoutEmbeddings(this.executor, limit);
3391
+ }
3392
+ /** Updates only the embedding payload for one procedure row. */
3393
+ async updateProcedureEmbedding(id, embedding) {
3394
+ await updateProcedureEmbedding(this.executor, id, embedding);
3395
+ }
3396
+ /** Marks one active procedure revision as retired. */
3397
+ async retireProcedure(id, reason) {
3398
+ return retireProcedure(this.executor, id, reason);
3399
+ }
3400
+ /** Marks one active procedure revision as superseded by a newer revision. */
3401
+ async supersedeProcedure(oldId, newId, reason) {
3402
+ return supersedeProcedure(this.executor, oldId, newId, reason);
3403
+ }
2419
3404
  /** Finds which normalized content hashes already exist in storage. */
2420
3405
  async findExistingNormHashes(hashes) {
2421
3406
  return findExistingNormHashes(this.executor, hashes);
@@ -2530,7 +3515,7 @@ import path2 from "path";
2530
3515
  import { fileURLToPath } from "url";
2531
3516
 
2532
3517
  // src/adapters/shared/validation.ts
2533
- function isRecord(value) {
3518
+ function isRecord2(value) {
2534
3519
  return typeof value === "object" && value !== null && !Array.isArray(value);
2535
3520
  }
2536
3521
  function pushIssue(issues, path3, message) {
@@ -2765,7 +3750,7 @@ function normalizeAgenrConfig(value, options) {
2765
3750
  }
2766
3751
  };
2767
3752
  }
2768
- if (!isRecord(value)) {
3753
+ if (!isRecord2(value)) {
2769
3754
  return {
2770
3755
  ok: false,
2771
3756
  issues: [{ path: "config", message: "Expected a JSON object." }]
@@ -2884,7 +3869,7 @@ function parseCredentials(value, path3, issues) {
2884
3869
  if (value === void 0) {
2885
3870
  return void 0;
2886
3871
  }
2887
- if (!isRecord(value)) {
3872
+ if (!isRecord2(value)) {
2888
3873
  pushIssue(issues, path3, "Expected an object.");
2889
3874
  return void 0;
2890
3875
  }
@@ -2907,7 +3892,7 @@ function parseModelConfig(value, path3, issues) {
2907
3892
  if (value === void 0) {
2908
3893
  return void 0;
2909
3894
  }
2910
- if (!isRecord(value)) {
3895
+ if (!isRecord2(value)) {
2911
3896
  pushIssue(issues, path3, "Expected an object.");
2912
3897
  return void 0;
2913
3898
  }
@@ -2933,7 +3918,7 @@ function parseClaimExtractionConfig(value, path3, issues) {
2933
3918
  resolved: defaults
2934
3919
  };
2935
3920
  }
2936
- if (!isRecord(value)) {
3921
+ if (!isRecord2(value)) {
2937
3922
  pushIssue(issues, path3, "Expected an object.");
2938
3923
  return {
2939
3924
  resolved: defaults
@@ -2978,7 +3963,7 @@ function parseSurgeonConfig(value, path3, issues) {
2978
3963
  resolved: defaults
2979
3964
  };
2980
3965
  }
2981
- if (!isRecord(value)) {
3966
+ if (!isRecord2(value)) {
2982
3967
  pushIssue(issues, path3, "Expected an object.");
2983
3968
  return {
2984
3969
  resolved: defaults
@@ -3030,7 +4015,7 @@ function parseRetirementPassConfig(value, path3, issues) {
3030
4015
  resolved: defaults
3031
4016
  };
3032
4017
  }
3033
- if (!isRecord(value)) {
4018
+ if (!isRecord2(value)) {
3034
4019
  pushIssue(issues, path3, "Expected an object.");
3035
4020
  return {
3036
4021
  resolved: defaults
@@ -3047,7 +4032,7 @@ function parseRetirementPassConfig(value, path3, issues) {
3047
4032
  resolved: defaults
3048
4033
  };
3049
4034
  }
3050
- if (!isRecord(retirement)) {
4035
+ if (!isRecord2(retirement)) {
3051
4036
  pushIssue(issues, `${path3}.retirement`, "Expected an object.");
3052
4037
  return {
3053
4038
  resolved: defaults
@@ -3221,11 +4206,11 @@ function resolveConfigDir() {
3221
4206
  return process.env.AGENR_CONFIG_DIR ?? DEFAULT_CONFIG_DIR;
3222
4207
  }
3223
4208
  function resolveConfigPath(options = {}) {
3224
- const envConfigPath = normalizeOptionalString4(process.env.AGENR_CONFIG_PATH);
4209
+ const envConfigPath = normalizeOptionalString5(process.env.AGENR_CONFIG_PATH);
3225
4210
  if (envConfigPath) {
3226
4211
  return envConfigPath;
3227
4212
  }
3228
- const explicitConfigPath = normalizeOptionalString4(options.configPath);
4213
+ const explicitConfigPath = normalizeOptionalString5(options.configPath);
3229
4214
  if (explicitConfigPath) {
3230
4215
  return explicitConfigPath;
3231
4216
  }
@@ -3236,7 +4221,7 @@ function resolveConfigPath(options = {}) {
3236
4221
  return path2.join(resolveConfigDir(), "config.json");
3237
4222
  }
3238
4223
  function resolveDbPath(config) {
3239
- return normalizeOptionalString4(process.env.AGENR_DB_PATH) ?? normalizeOptionalString4(config?.dbPath) ?? resolvePersistedDefaultDbPath();
4224
+ return normalizeOptionalString5(process.env.AGENR_DB_PATH) ?? normalizeOptionalString5(config?.dbPath) ?? resolvePersistedDefaultDbPath();
3240
4225
  }
3241
4226
  function resolveClaimExtractionConfig(config) {
3242
4227
  if (config && "enabled" in (config.claimExtraction ?? {})) {
@@ -3327,7 +4312,7 @@ function writeConfig(config, options = {}) {
3327
4312
  }
3328
4313
  }
3329
4314
  function resolveAdjacentConfigPath(dbPath) {
3330
- const normalizedDbPath = normalizeOptionalString4(dbPath);
4315
+ const normalizedDbPath = normalizeOptionalString5(dbPath);
3331
4316
  if (!normalizedDbPath || normalizedDbPath === ":memory:") {
3332
4317
  return void 0;
3333
4318
  }
@@ -3340,7 +4325,7 @@ function resolveAdjacentConfigPath(dbPath) {
3340
4325
  }
3341
4326
  return path2.join(path2.dirname(normalizedDbPath), "config.json");
3342
4327
  }
3343
- function normalizeOptionalString4(value) {
4328
+ function normalizeOptionalString5(value) {
3344
4329
  const normalized = value?.trim();
3345
4330
  return normalized && normalized.length > 0 ? normalized : void 0;
3346
4331
  }
@@ -3358,7 +4343,7 @@ function resolvePersistedDefaultDbPath() {
3358
4343
  return path2.join(resolveConfigDir(), DEFAULT_DB_NAME);
3359
4344
  }
3360
4345
  function resolveReadDefaultDbPath(options) {
3361
- return normalizeOptionalString4(process.env.AGENR_DB_PATH) ?? normalizeOptionalString4(options.dbPath) ?? resolvePersistedDefaultDbPath();
4346
+ return normalizeOptionalString5(process.env.AGENR_DB_PATH) ?? normalizeOptionalString5(options.dbPath) ?? resolvePersistedDefaultDbPath();
3362
4347
  }
3363
4348
  function formatConfigValidationError(configPath, issues) {
3364
4349
  const details = issues.map((issue) => `${issue.path}: ${issue.message}`).join("; ");
@@ -3405,7 +4390,7 @@ async function embedTexts(texts, apiKey, model) {
3405
4390
  if (texts.length === 0) {
3406
4391
  return [];
3407
4392
  }
3408
- const chunks = chunkValues2(texts, EMBEDDING_BATCH_SIZE);
4393
+ const chunks = chunkValues3(texts, EMBEDDING_BATCH_SIZE);
3409
4394
  const chunkResults = new Array(chunks.length);
3410
4395
  let nextChunkIndex = 0;
3411
4396
  await Promise.all(
@@ -3525,7 +4510,7 @@ function getErrorSnippet(rawBody) {
3525
4510
  const maxLength = 200;
3526
4511
  return trimmed.length <= maxLength ? trimmed : `${trimmed.slice(0, maxLength)}...`;
3527
4512
  }
3528
- function chunkValues2(values, chunkSize) {
4513
+ function chunkValues3(values, chunkSize) {
3529
4514
  const chunks = [];
3530
4515
  for (let index = 0; index < values.length; index += chunkSize) {
3531
4516
  chunks.push(values.slice(index, index + chunkSize));
@@ -3558,7 +4543,7 @@ var RECALL_CANDIDATE_SELECT_COLUMNS = `
3558
4543
  e.retired,
3559
4544
  e.created_at
3560
4545
  `;
3561
- var FTS_TIERS = ["exact", "all_tokens", "any_tokens"];
4546
+ var FTS_TIERS2 = ["exact", "all_tokens", "any_tokens"];
3562
4547
  var PREDECESSOR_EXPANSION_LIMIT_PER_SEED = 8;
3563
4548
  var PREDECESSOR_EXPANSION_MAX_RESULTS = 40;
3564
4549
  function createRecallAdapter(executor, embeddingPort) {
@@ -3625,7 +4610,7 @@ var LibsqlRecallAdapter = class {
3625
4610
  const filters = buildEntryFilterClause(params.filters, "e");
3626
4611
  const matches = /* @__PURE__ */ new Map();
3627
4612
  for (const tier of plan) {
3628
- const query = compileLexicalTier(tier);
4613
+ const query = compileLexicalTier2(tier);
3629
4614
  let result;
3630
4615
  try {
3631
4616
  result = await this.executor.execute({
@@ -3814,16 +4799,16 @@ function normalizeStrings(values) {
3814
4799
  return Array.from(new Set(values.map((value) => value.trim()).filter((value) => value.length > 0)));
3815
4800
  }
3816
4801
  function compareFtsCandidates(left, right) {
3817
- const tierDelta = ftsTierPriority(left.tier) - ftsTierPriority(right.tier);
4802
+ const tierDelta = ftsTierPriority2(left.tier) - ftsTierPriority2(right.tier);
3818
4803
  if (tierDelta !== 0) {
3819
4804
  return tierDelta;
3820
4805
  }
3821
4806
  return left.rank - right.rank;
3822
4807
  }
3823
- function ftsTierPriority(tier) {
3824
- return FTS_TIERS.indexOf(tier);
4808
+ function ftsTierPriority2(tier) {
4809
+ return FTS_TIERS2.indexOf(tier);
3825
4810
  }
3826
- function compileLexicalTier(tier) {
4811
+ function compileLexicalTier2(tier) {
3827
4812
  if (tier.tier === "exact") {
3828
4813
  return `"${tier.text.replaceAll('"', '""')}"`;
3829
4814
  }
@@ -3893,7 +4878,7 @@ function flattenClaimCentricRecallFamilies(families) {
3893
4878
  }
3894
4879
  function projectClaimCentricRecallEntry(recall2, options = {}) {
3895
4880
  const entry = recall2.entry;
3896
- const claimKey = normalizeOptionalString5(entry.claim_key);
4881
+ const claimKey = normalizeOptionalString6(entry.claim_key);
3897
4882
  const familyKey = claimKey ?? `entry:${entry.id}`;
3898
4883
  const slotPolicy = resolveClaimSlotPolicy(claimKey, options.slotPolicyConfig).policy;
3899
4884
  const asOfResolution = buildAsOfResolution(recall2, options.asOf);
@@ -3919,38 +4904,38 @@ function resolveMemoryState(recall2, asOfResolution) {
3919
4904
  if (asOfResolution.relation === "active") {
3920
4905
  return "current";
3921
4906
  }
3922
- return normalizeOptionalString5(entry.superseded_by) ? "superseded" : "historical";
4907
+ return normalizeOptionalString6(entry.superseded_by) ? "superseded" : "historical";
3923
4908
  }
3924
4909
  if (asOfResolution.relation === "observed_after" || asOfResolution.relation === "created_after") {
3925
4910
  return "historical";
3926
4911
  }
3927
- if (normalizeOptionalString5(entry.superseded_by)) {
4912
+ if (normalizeOptionalString6(entry.superseded_by)) {
3928
4913
  return "superseded";
3929
4914
  }
3930
- if (entry.retired || normalizeOptionalString5(entry.valid_to)) {
4915
+ if (entry.retired || normalizeOptionalString6(entry.valid_to)) {
3931
4916
  return "historical";
3932
4917
  }
3933
4918
  return "current";
3934
4919
  }
3935
- if (normalizeOptionalString5(entry.superseded_by)) {
4920
+ if (normalizeOptionalString6(entry.superseded_by)) {
3936
4921
  return "superseded";
3937
4922
  }
3938
- if (entry.retired || normalizeOptionalString5(entry.valid_to)) {
4923
+ if (entry.retired || normalizeOptionalString6(entry.valid_to)) {
3939
4924
  return "historical";
3940
4925
  }
3941
4926
  return "current";
3942
4927
  }
3943
4928
  function resolveClaimStatus(recall2) {
3944
4929
  const entry = recall2.entry;
3945
- if (!normalizeOptionalString5(entry.claim_key)) {
4930
+ if (!normalizeOptionalString6(entry.claim_key)) {
3946
4931
  return "no_key";
3947
4932
  }
3948
4933
  return entry.claim_key_status ?? "legacy";
3949
4934
  }
3950
4935
  function buildFreshness(recall2, memoryState, asOfResolution) {
3951
4936
  const entry = recall2.entry;
3952
- const validFrom = normalizeOptionalString5(entry.valid_from);
3953
- const validTo = normalizeOptionalString5(entry.valid_to);
4937
+ const validFrom = normalizeOptionalString6(entry.valid_from);
4938
+ const validTo = normalizeOptionalString6(entry.valid_to);
3954
4939
  const createdAt = entry.created_at;
3955
4940
  const labelParts = [`created ${createdAt}`];
3956
4941
  if (validFrom || validTo) {
@@ -3978,12 +4963,12 @@ function buildFreshness(recall2, memoryState, asOfResolution) {
3978
4963
  function buildProvenance(recall2) {
3979
4964
  const entry = recall2.entry;
3980
4965
  return {
3981
- ...normalizeOptionalString5(entry.superseded_by) ? { supersededById: entry.superseded_by } : {},
3982
- ...normalizeOptionalString5(entry.supersession_kind) ? { supersessionKind: entry.supersession_kind } : {},
3983
- ...normalizeOptionalString5(entry.supersession_reason) ? { supersessionReason: entry.supersession_reason } : {},
3984
- ...normalizeOptionalString5(entry.claim_support_source_kind) ? { supportSourceKind: entry.claim_support_source_kind } : {},
3985
- ...normalizeOptionalString5(entry.claim_support_locator) ? { supportLocator: entry.claim_support_locator } : {},
3986
- ...normalizeOptionalString5(entry.claim_support_observed_at) ? { supportObservedAt: entry.claim_support_observed_at } : {},
4966
+ ...normalizeOptionalString6(entry.superseded_by) ? { supersededById: entry.superseded_by } : {},
4967
+ ...normalizeOptionalString6(entry.supersession_kind) ? { supersessionKind: entry.supersession_kind } : {},
4968
+ ...normalizeOptionalString6(entry.supersession_reason) ? { supersessionReason: entry.supersession_reason } : {},
4969
+ ...normalizeOptionalString6(entry.claim_support_source_kind) ? { supportSourceKind: entry.claim_support_source_kind } : {},
4970
+ ...normalizeOptionalString6(entry.claim_support_locator) ? { supportLocator: entry.claim_support_locator } : {},
4971
+ ...normalizeOptionalString6(entry.claim_support_observed_at) ? { supportObservedAt: entry.claim_support_observed_at } : {},
3987
4972
  ...entry.claim_support_mode ? { supportMode: entry.claim_support_mode } : {}
3988
4973
  };
3989
4974
  }
@@ -4019,12 +5004,12 @@ function buildWhySurfaced(recall2, asOfResolution) {
4019
5004
  function formatScore(value) {
4020
5005
  return value.toFixed(2);
4021
5006
  }
4022
- function normalizeOptionalString5(value) {
5007
+ function normalizeOptionalString6(value) {
4023
5008
  const normalized = value?.trim();
4024
5009
  return normalized && normalized.length > 0 ? normalized : void 0;
4025
5010
  }
4026
5011
  function buildAsOfResolution(recall2, asOf) {
4027
- const normalizedAsOf = normalizeOptionalString5(asOf);
5012
+ const normalizedAsOf = normalizeOptionalString6(asOf);
4028
5013
  if (!normalizedAsOf) {
4029
5014
  return void 0;
4030
5015
  }
@@ -4088,7 +5073,7 @@ function formatAsOfRelation(relation) {
4088
5073
  }
4089
5074
  }
4090
5075
  function parseTimestamp(value) {
4091
- const normalized = normalizeOptionalString5(value);
5076
+ const normalized = normalizeOptionalString6(value);
4092
5077
  if (!normalized) {
4093
5078
  return void 0;
4094
5079
  }
@@ -4585,7 +5570,7 @@ async function searchEpisodes(query, database, now = /* @__PURE__ */ new Date())
4585
5570
  if (limit === 0) {
4586
5571
  return [];
4587
5572
  }
4588
- const normalizedEmbedding = normalizeEmbedding2(query.embedding);
5573
+ const normalizedEmbedding = normalizeEmbedding3(query.embedding);
4589
5574
  const bounds = query.timeWindow ? resolveTemporalWindowBounds(query.timeWindow, now) : null;
4590
5575
  const hasTemporal = bounds !== null;
4591
5576
  const hasSemantic = normalizedEmbedding.length > 0;
@@ -4618,7 +5603,7 @@ function normalizeLimit(value) {
4618
5603
  function computeCandidateLimit(limit) {
4619
5604
  return Math.min(Math.max(limit * CANDIDATE_MULTIPLIER, MIN_CANDIDATE_LIMIT), MAX_CANDIDATE_LIMIT);
4620
5605
  }
4621
- function normalizeEmbedding2(embedding) {
5606
+ function normalizeEmbedding3(embedding) {
4622
5607
  if (!embedding || embedding.length === 0) {
4623
5608
  return [];
4624
5609
  }
@@ -4668,6 +5653,156 @@ function compareAscending2(left, right) {
4668
5653
  return left.localeCompare(right);
4669
5654
  }
4670
5655
 
5656
+ // src/app/procedures/recall/service.ts
5657
+ var DEFAULT_LIMIT2 = 5;
5658
+ var DEFAULT_CANONICAL_THRESHOLD = 0.55;
5659
+ var DEFAULT_CANONICAL_MARGIN = 0.08;
5660
+ var LEXICAL_CANDIDATE_MULTIPLIER = 3;
5661
+ var VECTOR_CANDIDATE_MULTIPLIER = 4;
5662
+ var VECTOR_ONLY_CANONICAL_FLOOR = 0.6;
5663
+ var LEXICAL_ONLY_NOTICE = "Semantic procedure search unavailable - using lexical-only procedure ranking.";
5664
+ var LEXICAL_ONLY_FALLBACK_NOTICE = "Semantic procedure search failed during procedure recall - using lexical-only procedure ranking.";
5665
+ async function runProcedureRecall(input, deps) {
5666
+ const text = input.text.trim();
5667
+ const limit = normalizeLimit2(input.limit);
5668
+ if (text.length === 0 || limit === 0) {
5669
+ return {
5670
+ candidates: [],
5671
+ notices: []
5672
+ };
5673
+ }
5674
+ const lexicalLimit = limit * LEXICAL_CANDIDATE_MULTIPLIER;
5675
+ const vectorLimit = limit * VECTOR_CANDIDATE_MULTIPLIER;
5676
+ const notices = [];
5677
+ const lexicalMatches = await deps.db.procedureFtsSearch({
5678
+ text,
5679
+ limit: lexicalLimit
5680
+ });
5681
+ const queryEmbedding = await maybeEmbedQuery(text, deps.embedQuery, notices);
5682
+ const vectorMatches = queryEmbedding.length > 0 ? await deps.db.procedureVectorSearch({
5683
+ embedding: queryEmbedding,
5684
+ limit: vectorLimit
5685
+ }).catch(() => {
5686
+ notices.push(LEXICAL_ONLY_FALLBACK_NOTICE);
5687
+ return [];
5688
+ }) : [];
5689
+ const ranked = rankProcedureCandidates(text, lexicalMatches, vectorMatches).slice(0, limit);
5690
+ const canonicalProcedure = selectCanonicalProcedure(ranked, input.threshold);
5691
+ return {
5692
+ ...canonicalProcedure ? { canonicalProcedure } : {},
5693
+ candidates: ranked,
5694
+ notices: dedupePreservingOrder(notices)
5695
+ };
5696
+ }
5697
+ async function maybeEmbedQuery(text, embedQuery, notices) {
5698
+ if (!embedQuery) {
5699
+ notices.push(LEXICAL_ONLY_NOTICE);
5700
+ return [];
5701
+ }
5702
+ try {
5703
+ const embedding = await embedQuery(text);
5704
+ if (embedding.length === 0) {
5705
+ notices.push(LEXICAL_ONLY_NOTICE);
5706
+ return [];
5707
+ }
5708
+ return embedding;
5709
+ } catch {
5710
+ notices.push(LEXICAL_ONLY_FALLBACK_NOTICE);
5711
+ return [];
5712
+ }
5713
+ }
5714
+ function rankProcedureCandidates(query, lexicalMatches, vectorMatches) {
5715
+ const merged = /* @__PURE__ */ new Map();
5716
+ for (const match of lexicalMatches) {
5717
+ const lexical = computeProcedureLexicalScore(query, match.procedure);
5718
+ merged.set(match.procedure.id, {
5719
+ procedure: match.procedure,
5720
+ lexical,
5721
+ vector: 0
5722
+ });
5723
+ }
5724
+ for (const match of vectorMatches) {
5725
+ const existing = merged.get(match.procedure.id);
5726
+ merged.set(match.procedure.id, {
5727
+ procedure: match.procedure,
5728
+ lexical: existing?.lexical ?? computeProcedureLexicalScore(query, match.procedure),
5729
+ vector: Math.max(existing?.vector ?? 0, match.vectorSim)
5730
+ });
5731
+ }
5732
+ return Array.from(merged.values()).map((candidate) => {
5733
+ const relevance = combinedRelevance(candidate.vector, candidate.lexical);
5734
+ return {
5735
+ procedure: candidate.procedure,
5736
+ score: relevance,
5737
+ scores: {
5738
+ relevance,
5739
+ lexical: candidate.lexical,
5740
+ vector: candidate.vector
5741
+ }
5742
+ };
5743
+ }).filter((candidate) => candidate.score > 0).sort(compareProcedureCandidates);
5744
+ }
5745
+ function computeProcedureLexicalScore(query, procedure) {
5746
+ return computeLexicalScore(query, procedure.title, procedure.recall_text);
5747
+ }
5748
+ function selectCanonicalProcedure(ranked, threshold) {
5749
+ const leader = ranked[0];
5750
+ if (!leader) {
5751
+ return void 0;
5752
+ }
5753
+ const minimumScore = normalizeThreshold(threshold);
5754
+ if (leader.score < minimumScore) {
5755
+ return void 0;
5756
+ }
5757
+ if (leader.scores.lexical === 0 && leader.scores.vector < VECTOR_ONLY_CANONICAL_FLOOR) {
5758
+ return void 0;
5759
+ }
5760
+ const runnerUp = ranked[1];
5761
+ if (runnerUp && leader.score - runnerUp.score < DEFAULT_CANONICAL_MARGIN) {
5762
+ return void 0;
5763
+ }
5764
+ return leader.procedure;
5765
+ }
5766
+ function compareProcedureCandidates(left, right) {
5767
+ if (left.score !== right.score) {
5768
+ return right.score - left.score;
5769
+ }
5770
+ if (left.scores.lexical !== right.scores.lexical) {
5771
+ return right.scores.lexical - left.scores.lexical;
5772
+ }
5773
+ if (left.scores.vector !== right.scores.vector) {
5774
+ return right.scores.vector - left.scores.vector;
5775
+ }
5776
+ if (left.procedure.updated_at !== right.procedure.updated_at) {
5777
+ return right.procedure.updated_at.localeCompare(left.procedure.updated_at);
5778
+ }
5779
+ return left.procedure.procedure_key.localeCompare(right.procedure.procedure_key);
5780
+ }
5781
+ function normalizeLimit2(value) {
5782
+ if (value === void 0 || !Number.isFinite(value)) {
5783
+ return DEFAULT_LIMIT2;
5784
+ }
5785
+ return Math.max(0, Math.trunc(value));
5786
+ }
5787
+ function normalizeThreshold(value) {
5788
+ if (value === void 0 || !Number.isFinite(value)) {
5789
+ return DEFAULT_CANONICAL_THRESHOLD;
5790
+ }
5791
+ return Math.min(1, Math.max(0, value));
5792
+ }
5793
+ function dedupePreservingOrder(values) {
5794
+ const seen = /* @__PURE__ */ new Set();
5795
+ const deduped = [];
5796
+ for (const value of values) {
5797
+ if (seen.has(value)) {
5798
+ continue;
5799
+ }
5800
+ seen.add(value);
5801
+ deduped.push(value);
5802
+ }
5803
+ return deduped;
5804
+ }
5805
+
4671
5806
  // src/app/recall/transitions.ts
4672
5807
  function buildClaimTransitionExplanations(params) {
4673
5808
  if (params.families.length === 0 || params.detectedIntent === "temporal_narrative") {
@@ -4754,7 +5889,7 @@ function selectEpisodeContext(family, episodes) {
4754
5889
  var EPISODE_FRESHNESS_NOTICE = "Episodes cover consolidated prior sessions only; the most recent completed session may not appear yet.";
4755
5890
  var EPISODE_SEMANTIC_FALLBACK_NOTICE = "Semantic episode search unavailable - showing temporal results only.";
4756
5891
  var EPISODE_SEMANTIC_UNAVAILABLE_NOTICE = "Semantic episode search unavailable - no semantic episode results could be returned.";
4757
- var ENTRY_FILTER_NOTICE = "Threshold, type filters, and tag filters were applied to entries only.";
5892
+ var ENTRY_FILTER_NOTICE = "Type and tag filters were applied to entries only.";
4758
5893
  var HISTORICAL_STATE_PATTERNS = [
4759
5894
  "what was the previous",
4760
5895
  "what was the earlier",
@@ -4775,6 +5910,35 @@ var HISTORICAL_STATE_REGEX_PATTERNS = [
4775
5910
  /\bwhat\b.*\bplan\b.*\bearlier\b/u,
4776
5911
  /\bwhat\b.*\bplan\b.*\bbefore\b/u
4777
5912
  ];
5913
+ var PROCEDURAL_PATTERNS = [
5914
+ "how do i",
5915
+ "how should i",
5916
+ "how can i",
5917
+ "what steps",
5918
+ "which steps",
5919
+ "walk me through",
5920
+ "guide me through",
5921
+ "step by step",
5922
+ "step-by-step",
5923
+ "checklist for",
5924
+ "process for",
5925
+ "procedure for",
5926
+ "method for",
5927
+ "instructions for",
5928
+ "best way to",
5929
+ "recommended way to"
5930
+ ];
5931
+ var PROCEDURAL_REGEX_PATTERNS = [
5932
+ /\bhow (?:do|should|can) (?:i|we)\b/u,
5933
+ /\bhow to\b/u,
5934
+ /\bwhat (?:are the )?steps\b/u,
5935
+ /\bwhich steps\b/u,
5936
+ /\bwalk me through\b/u,
5937
+ /\bguide me through\b/u,
5938
+ /\bstep(?: |-)?by(?: |-)?step\b/u,
5939
+ /\b(?:checklist|playbook|runbook|procedure|process|instructions?|workflow|method)\b.*\b(?:for|to)\b/u,
5940
+ /\bwhat(?:'s| is) the (?:best|recommended|right) way to\b/u
5941
+ ];
4778
5942
  async function runUnifiedRecall(input, deps) {
4779
5943
  const now = deps.now ?? /* @__PURE__ */ new Date();
4780
5944
  const requested = normalizeMode(input.mode);
@@ -4782,9 +5946,13 @@ async function runUnifiedRecall(input, deps) {
4782
5946
  const hasEntryFilters = hasEntryScopedFilters(input);
4783
5947
  const topicAnchor = hasTopicAnchor(input.text, hasEntryFilters);
4784
5948
  const historicalStatePattern = detectHistoricalStatePattern(input.text);
5949
+ const proceduralPattern = detectProceduralPattern(input.text);
4785
5950
  if (historicalStatePattern) {
4786
5951
  deps.debugLog?.(`[agenr] unified recall matched historical-state pattern=${JSON.stringify(historicalStatePattern)} query=${JSON.stringify(input.text)}`);
4787
5952
  }
5953
+ if (proceduralPattern) {
5954
+ deps.debugLog?.(`[agenr] unified recall matched procedural pattern=${JSON.stringify(proceduralPattern)} query=${JSON.stringify(input.text)}`);
5955
+ }
4788
5956
  const routing = routeRecall({
4789
5957
  requested,
4790
5958
  text: input.text,
@@ -4811,6 +5979,20 @@ async function runUnifiedRecall(input, deps) {
4811
5979
  if (routing.queried.includes("episodes") && hasEntryScopedFilters(input)) {
4812
5980
  notices.push(ENTRY_FILTER_NOTICE);
4813
5981
  }
5982
+ const procedureResults = routing.queried.includes("procedures") ? await runProcedureRecall(
5983
+ {
5984
+ text: input.text,
5985
+ ...input.limit !== void 0 ? { limit: input.limit } : {},
5986
+ ...input.threshold !== void 0 ? { threshold: input.threshold } : {}
5987
+ },
5988
+ {
5989
+ db: deps.procedures,
5990
+ ...deps.embedQuery ? { embedQuery: deps.embedQuery } : {}
5991
+ }
5992
+ ) : {
5993
+ candidates: [],
5994
+ notices: []
5995
+ };
4814
5996
  const entries = await maybeRunEntryRecall({
4815
5997
  input,
4816
5998
  deps,
@@ -4846,13 +6028,16 @@ async function runUnifiedRecall(input, deps) {
4846
6028
  }
4847
6029
  } : {},
4848
6030
  ...input.asOf ? { asOf: input.asOf.trim() } : {},
6031
+ ...procedureResults.canonicalProcedure ? { procedure: procedureResults.canonicalProcedure } : {},
6032
+ procedureCandidates: procedureResults.candidates,
6033
+ procedureNotices: dedupePreservingOrder2(procedureResults.notices),
4849
6034
  episodes,
4850
6035
  entries: rawEntries,
4851
6036
  projectedEntries,
4852
6037
  entryFamilies,
4853
6038
  claimTransitions,
4854
- notices: dedupePreservingOrder(notices),
4855
- count: episodes.length + rawEntries.length
6039
+ notices: dedupePreservingOrder2(notices),
6040
+ count: procedureResults.candidates.length + episodes.length + rawEntries.length
4856
6041
  };
4857
6042
  }
4858
6043
  function routeRecall(params) {
@@ -4860,6 +6045,7 @@ function routeRecall(params) {
4860
6045
  const factual = /^(when did|when was|what decision|what preference|what(?:'s| is) the default|which version|what threshold)\b/.test(lower);
4861
6046
  const narrative = /\b(what happened|what were we doing|what was going on|summarize|catch me up)\b/.test(lower);
4862
6047
  const historicalState = detectHistoricalStatePattern(params.text) !== void 0;
6048
+ const procedural = detectProceduralPattern(params.text) !== void 0;
4863
6049
  const topicAnchor = hasTopicAnchor(params.text, params.hasEntryFilters);
4864
6050
  if (params.requested === "entries") {
4865
6051
  return {
@@ -4877,20 +6063,60 @@ function routeRecall(params) {
4877
6063
  reason: params.parsedTimeWindow ? "Explicit mode=episodes override with a resolved time window." : "Explicit mode=episodes override without a resolved time window."
4878
6064
  };
4879
6065
  }
6066
+ if (params.requested === "procedures") {
6067
+ return {
6068
+ requested: params.requested,
6069
+ detectedIntent: "procedural",
6070
+ queried: ["procedures"],
6071
+ reason: "Explicit mode=procedures override."
6072
+ };
6073
+ }
4880
6074
  if (historicalState) {
4881
6075
  return {
4882
6076
  requested: params.requested,
4883
6077
  detectedIntent: "historical_state",
4884
- queried: ["entries", "episodes"],
4885
- reason: params.parsedTimeWindow ? "The query asks about a previous state or transition and includes a supported time expression, so both entries and episodes were queried." : "The query asks about a previous state or transition, so both entries and episodes were queried."
6078
+ queried: procedural ? ["procedures", "entries", "episodes"] : ["entries", "episodes"],
6079
+ reason: params.parsedTimeWindow ? procedural ? "The query asks for steps around a previous state or transition and includes a supported time expression, so procedures, entries, and episodes were queried." : "The query asks about a previous state or transition and includes a supported time expression, so both entries and episodes were queried." : procedural ? "The query asks for steps around a previous state or transition, so procedures, entries, and episodes were queried." : "The query asks about a previous state or transition, so both entries and episodes were queried."
4886
6080
  };
4887
6081
  }
4888
6082
  if (factual && params.parsedTimeWindow) {
4889
6083
  return {
4890
6084
  requested: params.requested,
4891
6085
  detectedIntent: "mixed",
4892
- queried: ["entries", "episodes"],
4893
- reason: "The query combines a factual phrase with a supported time expression, so both entries and episodes were queried."
6086
+ queried: procedural ? ["procedures", "entries", "episodes"] : ["entries", "episodes"],
6087
+ reason: procedural ? "The query combines a procedural ask with factual and time-based signals, so procedures, entries, and episodes were queried." : "The query combines a factual phrase with a supported time expression, so both entries and episodes were queried."
6088
+ };
6089
+ }
6090
+ if (procedural && params.parsedTimeWindow && topicAnchor) {
6091
+ return {
6092
+ requested: params.requested,
6093
+ detectedIntent: "mixed",
6094
+ queried: ["procedures", "episodes", "entries"],
6095
+ reason: "The query asks for steps, includes a supported time expression, and names a topic anchor, so procedures were queried first with supporting episodes and entries."
6096
+ };
6097
+ }
6098
+ if (procedural && params.parsedTimeWindow) {
6099
+ return {
6100
+ requested: params.requested,
6101
+ detectedIntent: "mixed",
6102
+ queried: ["procedures", "episodes"],
6103
+ reason: "The query asks for steps and includes a supported time expression, so procedures were queried first with supporting episodes."
6104
+ };
6105
+ }
6106
+ if (procedural && topicAnchor) {
6107
+ return {
6108
+ requested: params.requested,
6109
+ detectedIntent: "mixed",
6110
+ queried: ["procedures", "entries"],
6111
+ reason: "The query asks for steps and includes a topic anchor, so procedures were queried first with supporting entries."
6112
+ };
6113
+ }
6114
+ if (procedural) {
6115
+ return {
6116
+ requested: params.requested,
6117
+ detectedIntent: "procedural",
6118
+ queried: ["procedures"],
6119
+ reason: "The query asks how to do something or requests a step-by-step method, so procedure recall was used first."
4894
6120
  };
4895
6121
  }
4896
6122
  if (factual) {
@@ -5027,11 +6253,20 @@ function detectHistoricalStatePattern(text) {
5027
6253
  const regexPattern = HISTORICAL_STATE_REGEX_PATTERNS.find((pattern) => pattern.test(lower));
5028
6254
  return regexPattern?.source;
5029
6255
  }
6256
+ function detectProceduralPattern(text) {
6257
+ const lower = text.trim().toLowerCase();
6258
+ const explicitPattern = PROCEDURAL_PATTERNS.find((pattern) => lower.includes(pattern));
6259
+ if (explicitPattern) {
6260
+ return explicitPattern;
6261
+ }
6262
+ const regexPattern = PROCEDURAL_REGEX_PATTERNS.find((pattern) => pattern.test(lower));
6263
+ return regexPattern?.source;
6264
+ }
5030
6265
  function normalizeMode(value) {
5031
- return value === "entries" || value === "episodes" ? value : "auto";
6266
+ return value === "entries" || value === "episodes" || value === "procedures" ? value : "auto";
5032
6267
  }
5033
6268
  function hasEntryScopedFilters(input) {
5034
- return Boolean(input.threshold !== void 0 || hasNonEmptyArray(input.types) || hasNonEmptyArray(input.tags));
6269
+ return Boolean(hasNonEmptyArray(input.types) || hasNonEmptyArray(input.tags));
5035
6270
  }
5036
6271
  function hasNonEmptyArray(value) {
5037
6272
  return Array.isArray(value) && value.length > 0;
@@ -5051,7 +6286,7 @@ async function maybeEmbedEpisodeQuery(text, embedQuery) {
5051
6286
  return void 0;
5052
6287
  }
5053
6288
  }
5054
- function dedupePreservingOrder(values) {
6289
+ function dedupePreservingOrder2(values) {
5055
6290
  const seen = /* @__PURE__ */ new Set();
5056
6291
  const deduped = [];
5057
6292
  for (const value of values) {
@@ -5101,6 +6336,8 @@ export {
5101
6336
  readNumber,
5102
6337
  readBoolean,
5103
6338
  mapEntryRow,
6339
+ parseAndNormalizeProcedureYaml,
6340
+ normalizeProcedureDefinition,
5104
6341
  validateTemporalValidityRange,
5105
6342
  insertEntry,
5106
6343
  getEntry,
@@ -5110,7 +6347,7 @@ export {
5110
6347
  VECTOR_INDEX_NAME,
5111
6348
  getLastBulkIngestAt,
5112
6349
  createDatabase,
5113
- isRecord,
6350
+ isRecord2 as isRecord,
5114
6351
  pushIssue,
5115
6352
  pushUnexpectedFields,
5116
6353
  parseRequiredTrimmedString,