agenr 1.9.2 → 2.0.0

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.
package/dist/cli.js CHANGED
@@ -1,5 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import "./chunk-ETQPUJGS.js";
2
+ import {
3
+ composeProcedureRecallText,
4
+ computeProcedureRevisionHash,
5
+ computeProcedureSourceHash
6
+ } from "./chunk-ZYADFKX3.js";
3
7
  import {
4
8
  applyClaimExtractionResultToEntry,
5
9
  backfillEpisodeEmbeddings,
@@ -27,7 +31,7 @@ import {
27
31
  tokenizeGroundingText,
28
32
  validateEntriesWithIndexes,
29
33
  validateSupersessionRules
30
- } from "./chunk-I6A6DPNF.js";
34
+ } from "./chunk-XD3446YW.js";
31
35
  import {
32
36
  DEFAULT_CLAIM_EXTRACTION_CONCURRENCY,
33
37
  DEFAULT_SURGEON_CONTEXT_LIMIT,
@@ -66,6 +70,7 @@ import {
66
70
  mapEntryRow,
67
71
  mergeExplicitClaimKeyMetadata,
68
72
  normalizeManualClaimKeyUpdate,
73
+ parseAndNormalizeProcedureYaml,
69
74
  projectClaimCentricRecallEntry,
70
75
  readBoolean,
71
76
  readConfig,
@@ -86,7 +91,7 @@ import {
86
91
  updateEntry,
87
92
  validateTemporalValidityRange,
88
93
  writeConfig
89
- } from "./chunk-EMRMV2QR.js";
94
+ } from "./chunk-Y2BC7RCE.js";
90
95
  import {
91
96
  compactClaimKey,
92
97
  describeClaimKeyNormalizationFailure,
@@ -97,7 +102,7 @@ import {
97
102
  normalizeClaimKeySegment,
98
103
  recall,
99
104
  resolveClaimSlotPolicy
100
- } from "./chunk-GUDCFFRV.js";
105
+ } from "./chunk-MEHOGUZE.js";
101
106
 
102
107
  // src/cli/main.ts
103
108
  import { Command } from "commander";
@@ -230,8 +235,8 @@ function formatUnknownError(error) {
230
235
  }
231
236
 
232
237
  // src/cli/commands/ingest.ts
233
- import path8 from "path";
234
- import * as clack3 from "@clack/prompts";
238
+ import path10 from "path";
239
+ import * as clack4 from "@clack/prompts";
235
240
 
236
241
  // src/core/ingestion/parser.ts
237
242
  var IMPORTANCE_TIER_MAP = {
@@ -2965,20 +2970,20 @@ function registerIngestEpisodesCommand(parent) {
2965
2970
  embedding: embeddingSetup.port,
2966
2971
  createSummaryLlm: () => createEpisodeIngestSummaryLlm(provider, modelId, llmApiKey)
2967
2972
  };
2968
- const spinner5 = commandInput.verbose ? null : clack2.spinner();
2969
- spinner5?.start(`Generating episodes... (0/${plan.candidates.length})`);
2973
+ const spinner6 = commandInput.verbose ? null : clack2.spinner();
2974
+ spinner6?.start(`Generating episodes... (0/${plan.candidates.length})`);
2970
2975
  const execution = await executeEpisodeIngestPlan(plan, executionPorts, {
2971
2976
  concurrency: commandInput.concurrency,
2972
2977
  genVersion: CLI_EPISODE_GENERATOR_VERSION,
2973
2978
  onProgress: (completed, total, session) => {
2974
- if (spinner5) {
2975
- spinner5.message(`Generating episodes... (${completed}/${total})`);
2979
+ if (spinner6) {
2980
+ spinner6.message(`Generating episodes... (${completed}/${total})`);
2976
2981
  } else {
2977
2982
  reportEpisodeProgress(completed, total, session);
2978
2983
  }
2979
2984
  }
2980
2985
  });
2981
- spinner5?.stop("Episode ingest complete.");
2986
+ spinner6?.stop("Episode ingest complete.");
2982
2987
  if (!commandInput.verbose) {
2983
2988
  for (const session of execution.sessions.filter((result) => result.action === "failed")) {
2984
2989
  clack2.log.error(formatEpisodeProgressLine(void 0, void 0, session));
@@ -3054,19 +3059,19 @@ async function runEpisodeEmbeddingBackfill(params) {
3054
3059
  clack2.outro(`Dry run complete: ${missingEpisodes.length} ${pluralize(missingEpisodes.length, "episode")} need embeddings.`);
3055
3060
  return;
3056
3061
  }
3057
- const spinner5 = params.options.verbose ? null : clack2.spinner();
3058
- spinner5?.start(`Embedding episodes... (0/${missingEpisodes.length})`);
3062
+ const spinner6 = params.options.verbose ? null : clack2.spinner();
3063
+ spinner6?.start(`Embedding episodes... (0/${missingEpisodes.length})`);
3059
3064
  const result = await backfillEpisodeEmbeddings(ports, {
3060
3065
  concurrency: params.options.concurrency,
3061
3066
  onProgress: (completed, total, episode, status) => {
3062
- if (spinner5) {
3063
- spinner5.message(`Embedding episodes... (${completed}/${total})`);
3067
+ if (spinner6) {
3068
+ spinner6.message(`Embedding episodes... (${completed}/${total})`);
3064
3069
  } else {
3065
3070
  clack2.log.step(`${completed}/${total} ${episode.sourceId ?? episode.id}: ${status}`);
3066
3071
  }
3067
3072
  }
3068
3073
  });
3069
- spinner5?.stop("Episode embedding backfill complete.");
3074
+ spinner6?.stop("Episode embedding backfill complete.");
3070
3075
  const estimatedCost = estimateEpisodeEmbeddingCost(result.estimatedInputTokens, embeddingModel);
3071
3076
  const summaryParts = [`Done: ${result.embedded} ${pluralize(result.embedded, "episode")} embedded.`];
3072
3077
  if (result.failed > 0) {
@@ -3285,6 +3290,503 @@ function parseEpisodeIngestConcurrency(value) {
3285
3290
  return parseIntegerInRange(value, "Concurrency", MIN_EPISODE_INGEST_CONCURRENCY, MAX_EPISODE_INGEST_CONCURRENCY);
3286
3291
  }
3287
3292
 
3293
+ // src/cli/commands/ingest-procedures.ts
3294
+ import path9 from "path";
3295
+ import * as clack3 from "@clack/prompts";
3296
+ import "commander";
3297
+
3298
+ // src/adapters/files/procedure-files.ts
3299
+ import fs6 from "fs/promises";
3300
+ import path8 from "path";
3301
+ var PROCEDURE_FILE_PATTERN = /^.+\.ya?ml$/iu;
3302
+ async function discoverProcedureFiles(targetPath, options = {}) {
3303
+ const resolvedTargetPath = path8.resolve(targetPath);
3304
+ const stat = await fs6.stat(resolvedTargetPath);
3305
+ if (stat.isFile()) {
3306
+ return matchesProcedureFileName(path8.basename(resolvedTargetPath)) ? [resolvedTargetPath] : [];
3307
+ }
3308
+ const recursive = options.recursive ?? true;
3309
+ const entries = recursive ? await fs6.readdir(resolvedTargetPath, { recursive: true, withFileTypes: true }) : await fs6.readdir(resolvedTargetPath, { withFileTypes: true });
3310
+ return entries.filter((entry) => entry.isFile() && matchesProcedureFileName(entry.name)).map((entry) => path8.resolve(entry.parentPath ?? resolvedTargetPath, entry.name)).sort((left, right) => left.localeCompare(right));
3311
+ }
3312
+ async function readProcedureFile(filePath) {
3313
+ return fs6.readFile(filePath, "utf-8");
3314
+ }
3315
+ var LocalProcedureFiles = class {
3316
+ /**
3317
+ * Discovers procedure YAML files from a target file or directory path.
3318
+ *
3319
+ * @param targetPath - File or directory to inspect for procedure files.
3320
+ * @param options - Optional discovery flags.
3321
+ * @returns Sorted absolute procedure file paths.
3322
+ */
3323
+ async discoverFiles(targetPath, options) {
3324
+ return discoverProcedureFiles(targetPath, options);
3325
+ }
3326
+ /**
3327
+ * Reads one raw procedure YAML file from disk.
3328
+ *
3329
+ * @param filePath - Absolute procedure file path.
3330
+ * @returns Raw UTF-8 procedure source text.
3331
+ */
3332
+ async readFile(filePath) {
3333
+ return readProcedureFile(filePath);
3334
+ }
3335
+ };
3336
+ var localProcedureFiles = new LocalProcedureFiles();
3337
+ function matchesProcedureFileName(fileName) {
3338
+ return PROCEDURE_FILE_PATTERN.test(fileName.trim());
3339
+ }
3340
+
3341
+ // src/app/procedures/sync/service/execute.ts
3342
+ import { randomUUID } from "crypto";
3343
+ async function executeProcedureSync(plan, ports) {
3344
+ const invalidItems = plan.items.filter((item) => item.action === "invalid");
3345
+ if (invalidItems.length > 0) {
3346
+ throw new Error(formatInvalidPlanError(invalidItems));
3347
+ }
3348
+ const itemsNeedingEmbeddings = plan.items.filter(
3349
+ (item) => item.action === "create" || item.action === "supersede"
3350
+ );
3351
+ const embeddings = itemsNeedingEmbeddings.length > 0 ? await ports.embedding.embed(itemsNeedingEmbeddings.map((item) => item.candidate.recallText)) : [];
3352
+ if (embeddings.length !== itemsNeedingEmbeddings.length) {
3353
+ throw new Error(`Procedure embedding count mismatch: expected ${itemsNeedingEmbeddings.length}, received ${embeddings.length}.`);
3354
+ }
3355
+ const embeddingByFilePath = /* @__PURE__ */ new Map();
3356
+ itemsNeedingEmbeddings.forEach((item, index) => {
3357
+ const embedding = embeddings[index];
3358
+ if (!embedding) {
3359
+ throw new Error(`Missing embedding for procedure file ${item.candidate.filePath}.`);
3360
+ }
3361
+ embeddingByFilePath.set(item.candidate.filePath, embedding);
3362
+ });
3363
+ const items = await ports.db.withTransaction(async (db) => {
3364
+ const executionItems = [];
3365
+ for (const item of plan.items) {
3366
+ switch (item.action) {
3367
+ case "create": {
3368
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3369
+ const stored = await db.upsertProcedure(
3370
+ buildProcedureRecord({
3371
+ candidate: item.candidate,
3372
+ id: randomUUID(),
3373
+ createdAt: now,
3374
+ updatedAt: now,
3375
+ embedding: embeddingByFilePath.get(item.candidate.filePath),
3376
+ retired: false
3377
+ })
3378
+ );
3379
+ executionItems.push({
3380
+ action: "created",
3381
+ filePath: item.candidate.filePath,
3382
+ procedureKey: item.candidate.procedure.procedure_key,
3383
+ procedureId: stored.id
3384
+ });
3385
+ break;
3386
+ }
3387
+ case "update_source_only": {
3388
+ const stored = await db.upsertProcedure(
3389
+ buildProcedureRecord({
3390
+ candidate: item.candidate,
3391
+ existing: item.existing,
3392
+ id: item.existing.id,
3393
+ createdAt: item.existing.created_at,
3394
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3395
+ embedding: item.existing.embedding,
3396
+ retired: false
3397
+ })
3398
+ );
3399
+ executionItems.push({
3400
+ action: "updated_source_only",
3401
+ filePath: item.candidate.filePath,
3402
+ procedureKey: item.candidate.procedure.procedure_key,
3403
+ procedureId: stored.id
3404
+ });
3405
+ break;
3406
+ }
3407
+ case "supersede": {
3408
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3409
+ const replacementId = randomUUID();
3410
+ const staged = buildProcedureRecord({
3411
+ candidate: item.candidate,
3412
+ id: replacementId,
3413
+ createdAt: now,
3414
+ updatedAt: now,
3415
+ embedding: embeddingByFilePath.get(item.candidate.filePath),
3416
+ retired: true
3417
+ });
3418
+ await db.upsertProcedure(staged);
3419
+ const superseded = await db.supersedeProcedure(item.existing.id, replacementId, "procedure revision updated");
3420
+ if (!superseded) {
3421
+ throw new Error(`Failed to supersede active procedure ${item.existing.id} for ${item.candidate.procedure.procedure_key}.`);
3422
+ }
3423
+ const activated = await db.upsertProcedure({
3424
+ ...staged,
3425
+ retired: false,
3426
+ retired_at: void 0,
3427
+ retired_reason: void 0,
3428
+ superseded_by: void 0,
3429
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
3430
+ });
3431
+ executionItems.push({
3432
+ action: "superseded",
3433
+ filePath: item.candidate.filePath,
3434
+ procedureKey: item.candidate.procedure.procedure_key,
3435
+ procedureId: activated.id,
3436
+ previousProcedureId: item.existing.id
3437
+ });
3438
+ break;
3439
+ }
3440
+ case "unchanged":
3441
+ executionItems.push({
3442
+ action: "unchanged",
3443
+ filePath: item.candidate.filePath,
3444
+ procedureKey: item.candidate.procedure.procedure_key,
3445
+ procedureId: item.existing.id
3446
+ });
3447
+ break;
3448
+ case "invalid":
3449
+ throw new Error(`Invalid procedure plan item for ${item.filePath}: ${item.error}`);
3450
+ }
3451
+ }
3452
+ return executionItems;
3453
+ });
3454
+ return {
3455
+ plan,
3456
+ items,
3457
+ totals: summarizeExecution(items)
3458
+ };
3459
+ }
3460
+ function buildProcedureRecord(params) {
3461
+ const { candidate, existing } = params;
3462
+ return {
3463
+ id: params.id,
3464
+ ...candidate.procedure,
3465
+ recall_text: candidate.recallText,
3466
+ revision_hash: candidate.revisionHash,
3467
+ source_hash: candidate.sourceHash,
3468
+ source_file: candidate.filePath,
3469
+ embedding: params.embedding ?? existing?.embedding,
3470
+ retired: params.retired,
3471
+ retired_at: existing?.retired_at,
3472
+ retired_reason: existing?.retired_reason,
3473
+ superseded_by: existing?.superseded_by,
3474
+ created_at: params.createdAt,
3475
+ updated_at: params.updatedAt
3476
+ };
3477
+ }
3478
+ function summarizeExecution(items) {
3479
+ return items.reduce(
3480
+ (totals, item) => {
3481
+ switch (item.action) {
3482
+ case "created":
3483
+ totals.created += 1;
3484
+ break;
3485
+ case "updated_source_only":
3486
+ totals.updatedSourceOnly += 1;
3487
+ break;
3488
+ case "superseded":
3489
+ totals.superseded += 1;
3490
+ break;
3491
+ case "unchanged":
3492
+ totals.unchanged += 1;
3493
+ break;
3494
+ }
3495
+ return totals;
3496
+ },
3497
+ {
3498
+ created: 0,
3499
+ updatedSourceOnly: 0,
3500
+ superseded: 0,
3501
+ unchanged: 0
3502
+ }
3503
+ );
3504
+ }
3505
+ function formatInvalidPlanError(invalidItems) {
3506
+ const details = invalidItems.map((item) => `${item.filePath}: ${item.error}`).join(" | ");
3507
+ return `Procedure sync plan contains ${invalidItems.length} invalid file(s): ${details}`;
3508
+ }
3509
+
3510
+ // src/app/procedures/sync/service/prepare.ts
3511
+ async function prepareProcedureSync(targetPath, ports) {
3512
+ const files = await ports.files.discoverFiles(targetPath);
3513
+ const discoveredItems = await Promise.all(files.map((filePath) => prepareCandidateForFile(filePath, ports)));
3514
+ const duplicateKeys = collectDuplicateProcedureKeys(discoveredItems);
3515
+ const planItems = [];
3516
+ for (const discoveredItem of discoveredItems) {
3517
+ if ("action" in discoveredItem) {
3518
+ planItems.push(discoveredItem);
3519
+ continue;
3520
+ }
3521
+ const duplicates = duplicateKeys.get(discoveredItem.procedure.procedure_key);
3522
+ if (duplicates && duplicates.length > 1) {
3523
+ planItems.push({
3524
+ action: "invalid",
3525
+ filePath: discoveredItem.filePath,
3526
+ error: formatDuplicateProcedureKeyError(discoveredItem.procedure.procedure_key, duplicates)
3527
+ });
3528
+ continue;
3529
+ }
3530
+ const existing = await ports.db.findActiveProcedureByKey(discoveredItem.procedure.procedure_key);
3531
+ planItems.push(classifyProcedureCandidate(discoveredItem, existing));
3532
+ }
3533
+ return {
3534
+ targetPath,
3535
+ files,
3536
+ items: planItems,
3537
+ totals: summarizePlan(planItems, files.length)
3538
+ };
3539
+ }
3540
+ async function prepareCandidateForFile(filePath, ports) {
3541
+ try {
3542
+ const sourceText = await ports.files.readFile(filePath);
3543
+ const procedure = parseAndNormalizeProcedureYaml(sourceText, filePath);
3544
+ return {
3545
+ filePath,
3546
+ procedure,
3547
+ recallText: composeProcedureRecallText(procedure),
3548
+ revisionHash: computeProcedureRevisionHash(procedure),
3549
+ sourceHash: computeProcedureSourceHash(sourceText)
3550
+ };
3551
+ } catch (error) {
3552
+ return {
3553
+ action: "invalid",
3554
+ filePath,
3555
+ error: formatUnknownError3(error)
3556
+ };
3557
+ }
3558
+ }
3559
+ function collectDuplicateProcedureKeys(items) {
3560
+ const filesByProcedureKey = /* @__PURE__ */ new Map();
3561
+ for (const item of items) {
3562
+ if ("action" in item) {
3563
+ continue;
3564
+ }
3565
+ const files = filesByProcedureKey.get(item.procedure.procedure_key) ?? [];
3566
+ files.push(item.filePath);
3567
+ filesByProcedureKey.set(item.procedure.procedure_key, files);
3568
+ }
3569
+ return new Map(Array.from(filesByProcedureKey.entries()).filter(([, files]) => files.length > 1));
3570
+ }
3571
+ function classifyProcedureCandidate(candidate, existing) {
3572
+ if (!existing) {
3573
+ return {
3574
+ action: "create",
3575
+ candidate
3576
+ };
3577
+ }
3578
+ if (existing.revision_hash !== candidate.revisionHash) {
3579
+ return {
3580
+ action: "supersede",
3581
+ candidate,
3582
+ existing
3583
+ };
3584
+ }
3585
+ if (existing.source_hash !== candidate.sourceHash || existing.source_file !== candidate.filePath) {
3586
+ return {
3587
+ action: "update_source_only",
3588
+ candidate,
3589
+ existing
3590
+ };
3591
+ }
3592
+ return {
3593
+ action: "unchanged",
3594
+ candidate,
3595
+ existing
3596
+ };
3597
+ }
3598
+ function summarizePlan(items, discoveredCount) {
3599
+ return items.reduce(
3600
+ (totals, item) => {
3601
+ switch (item.action) {
3602
+ case "create":
3603
+ totals.create += 1;
3604
+ break;
3605
+ case "update_source_only":
3606
+ totals.updateSourceOnly += 1;
3607
+ break;
3608
+ case "supersede":
3609
+ totals.supersede += 1;
3610
+ break;
3611
+ case "unchanged":
3612
+ totals.unchanged += 1;
3613
+ break;
3614
+ case "invalid":
3615
+ totals.invalid += 1;
3616
+ break;
3617
+ }
3618
+ return totals;
3619
+ },
3620
+ {
3621
+ discovered: discoveredCount,
3622
+ create: 0,
3623
+ updateSourceOnly: 0,
3624
+ supersede: 0,
3625
+ unchanged: 0,
3626
+ invalid: 0
3627
+ }
3628
+ );
3629
+ }
3630
+ function formatDuplicateProcedureKeyError(procedureKey, filePaths) {
3631
+ return `Duplicate procedure_key "${procedureKey}" found in ${filePaths.join(", ")}.`;
3632
+ }
3633
+ function formatUnknownError3(error) {
3634
+ return error instanceof Error ? error.message : String(error);
3635
+ }
3636
+
3637
+ // src/cli/commands/ingest-procedures.ts
3638
+ var DEFAULT_PROCEDURE_SYNC_PATH = "procedures";
3639
+ function registerIngestProceduresCommand(parent) {
3640
+ parent.command("procedures [path]").description("Sync repo-authored procedure YAML files into the knowledge database").option("--dry-run", "Discover, validate, normalize, and diff without writing").option("--verbose", "Show detailed per-file planning and execution output").action(async (targetPath, options) => {
3641
+ let database = null;
3642
+ const commandInput = normalizeIngestProceduresCommand(targetPath, options);
3643
+ setVerbose(commandInput.verbose);
3644
+ clack3.intro(banner());
3645
+ try {
3646
+ const config = readConfig();
3647
+ const dbPath = config.dbPath;
3648
+ const resolvedTargetPath = path9.resolve(commandInput.targetPath);
3649
+ database = await createDatabase(dbPath);
3650
+ if (commandInput.verbose) {
3651
+ clack3.log.step(`Preparing procedure sync for ${resolvedTargetPath}...`);
3652
+ }
3653
+ const plan = await prepareProcedureSync(commandInput.targetPath, {
3654
+ files: localProcedureFiles,
3655
+ db: database
3656
+ });
3657
+ if (plan.files.length === 0) {
3658
+ clack3.log.warn(`No procedure files found at ${resolvedTargetPath}.`);
3659
+ clack3.outro("Nothing to sync.");
3660
+ return;
3661
+ }
3662
+ printProcedureSyncSummary({
3663
+ targetPath: resolvedTargetPath,
3664
+ dbPath,
3665
+ dryRun: commandInput.dryRun,
3666
+ plan
3667
+ });
3668
+ if (commandInput.verbose) {
3669
+ printVerboseProcedurePlan(plan.items);
3670
+ }
3671
+ if (commandInput.dryRun) {
3672
+ if (plan.totals.invalid > 0) {
3673
+ process.exitCode = 1;
3674
+ }
3675
+ clack3.outro(`Dry run complete: ${formatProcedurePlanTail(plan)}.`);
3676
+ return;
3677
+ }
3678
+ if (plan.totals.invalid > 0) {
3679
+ throw new Error(`Procedure sync blocked: ${plan.totals.invalid} invalid file(s) must be fixed before writing.`);
3680
+ }
3681
+ const embedding = createEmbeddingClient(resolveEmbeddingApiKey(config), resolveEmbeddingModel(config));
3682
+ const spinner6 = commandInput.verbose ? null : clack3.spinner();
3683
+ spinner6?.start(`Syncing procedures... (${countPlannedWrites(plan.items)} writes planned)`);
3684
+ const execution = await executeProcedureSync(plan, {
3685
+ db: database,
3686
+ embedding
3687
+ });
3688
+ spinner6?.stop("Procedure sync complete.");
3689
+ if (commandInput.verbose) {
3690
+ printVerboseProcedureExecution(execution.items);
3691
+ }
3692
+ clack3.outro(`Done: ${formatProcedureExecutionTail(execution)}.`);
3693
+ } catch (error) {
3694
+ process.exitCode = 1;
3695
+ clack3.log.error(formatUnknownError4(error));
3696
+ clack3.outro(ui.error("Procedure sync failed"));
3697
+ } finally {
3698
+ await database?.close();
3699
+ }
3700
+ });
3701
+ }
3702
+ function normalizeIngestProceduresCommand(targetPath, options) {
3703
+ return {
3704
+ targetPath: normalizeOptionalString3(targetPath) ?? DEFAULT_PROCEDURE_SYNC_PATH,
3705
+ verbose: options.verbose === true,
3706
+ dryRun: options.dryRun === true
3707
+ };
3708
+ }
3709
+ function printProcedureSyncSummary(params) {
3710
+ const lines = [
3711
+ formatLabel("Target", params.targetPath),
3712
+ formatLabel("Database", params.dbPath),
3713
+ formatLabel("Files", `${params.plan.totals.discovered} ${pluralize2(params.plan.totals.discovered, "file")} discovered`),
3714
+ formatLabel(
3715
+ "Plan",
3716
+ `${params.plan.totals.create} create | ${params.plan.totals.updateSourceOnly} source update | ${params.plan.totals.supersede} supersede | ${params.plan.totals.unchanged} unchanged | ${params.plan.totals.invalid} invalid`
3717
+ )
3718
+ ];
3719
+ if (params.dryRun) {
3720
+ lines.push(formatLabel("Mode", "dry run"));
3721
+ }
3722
+ clack3.log.info(lines.join("\n"));
3723
+ }
3724
+ function printVerboseProcedurePlan(items) {
3725
+ for (const item of items) {
3726
+ switch (item.action) {
3727
+ case "invalid":
3728
+ clack3.log.error(`[invalid] ${item.filePath}: ${item.error}`);
3729
+ break;
3730
+ case "create":
3731
+ clack3.log.step(`[create] ${item.candidate.procedure.procedure_key}: ${item.candidate.filePath}`);
3732
+ break;
3733
+ case "update_source_only":
3734
+ clack3.log.step(`[update_source_only] ${item.candidate.procedure.procedure_key}: ${item.candidate.filePath} -> reuse ${item.existing.id}`);
3735
+ break;
3736
+ case "supersede":
3737
+ clack3.log.step(`[supersede] ${item.candidate.procedure.procedure_key}: ${item.existing.id} -> new revision`);
3738
+ break;
3739
+ case "unchanged":
3740
+ clack3.log.step(`[unchanged] ${item.candidate.procedure.procedure_key}: ${item.candidate.filePath}`);
3741
+ break;
3742
+ }
3743
+ }
3744
+ }
3745
+ function printVerboseProcedureExecution(items) {
3746
+ for (const item of items) {
3747
+ switch (item.action) {
3748
+ case "created":
3749
+ clack3.log.step(`[created] ${item.procedureKey}: ${item.procedureId}`);
3750
+ break;
3751
+ case "updated_source_only":
3752
+ clack3.log.step(`[updated_source_only] ${item.procedureKey}: ${item.procedureId}`);
3753
+ break;
3754
+ case "superseded":
3755
+ clack3.log.step(`[superseded] ${item.procedureKey}: ${item.previousProcedureId} -> ${item.procedureId}`);
3756
+ break;
3757
+ case "unchanged":
3758
+ clack3.log.step(`[unchanged] ${item.procedureKey}: ${item.procedureId}`);
3759
+ break;
3760
+ }
3761
+ }
3762
+ }
3763
+ function countPlannedWrites(items) {
3764
+ return items.filter((item) => item.action === "create" || item.action === "update_source_only" || item.action === "supersede").length;
3765
+ }
3766
+ function formatProcedurePlanTail(plan) {
3767
+ return [
3768
+ `${plan.totals.create} create`,
3769
+ `${plan.totals.updateSourceOnly} source update`,
3770
+ `${plan.totals.supersede} supersede`,
3771
+ `${plan.totals.unchanged} unchanged`,
3772
+ `${plan.totals.invalid} invalid`
3773
+ ].join(", ");
3774
+ }
3775
+ function formatProcedureExecutionTail(execution) {
3776
+ return [
3777
+ `${execution.totals.created} created`,
3778
+ `${execution.totals.updatedSourceOnly} source updated`,
3779
+ `${execution.totals.superseded} superseded`,
3780
+ `${execution.totals.unchanged} unchanged`
3781
+ ].join(", ");
3782
+ }
3783
+ function pluralize2(value, singular, plural) {
3784
+ return value === 1 ? singular : plural ?? `${singular}s`;
3785
+ }
3786
+ function formatUnknownError4(error) {
3787
+ return error instanceof Error ? error.message : String(error);
3788
+ }
3789
+
3288
3790
  // src/cli/commands/ingest.ts
3289
3791
  var MIN_INGEST_CONCURRENCY = 1;
3290
3792
  var MAX_INGEST_CONCURRENCY = 50;
@@ -3292,6 +3794,7 @@ function registerIngestCommand(program2) {
3292
3794
  const ingestCommand = program2.command("ingest").description("Ingest OpenClaw transcripts and derived memory artifacts");
3293
3795
  registerIngestEntriesCommand(ingestCommand);
3294
3796
  registerIngestEpisodesCommand(ingestCommand);
3797
+ registerIngestProceduresCommand(ingestCommand);
3295
3798
  }
3296
3799
  function registerIngestEntriesCommand(parent) {
3297
3800
  const ingestCommand = parent.command("entries <path>", { isDefault: true }).description("Ingest OpenClaw session files into the knowledge database").option("--verbose", "Show detailed progress").option("--dry-run", "Parse and extract without storing").addOption(new Option2("--whole-file <mode>", "Whole-file mode: auto|force|never").choices(["auto", "force", "never"]).default("auto")).option("--skip-dedup", "Skip within-batch semantic dedup").addOption(new Option2("--concurrency <n>", "Max files to extract in parallel").argParser(parseConcurrency));
@@ -3300,7 +3803,7 @@ function registerIngestEntriesCommand(parent) {
3300
3803
  let db = null;
3301
3804
  const commandInput = normalizeIngestEntriesCommand(targetPath, options);
3302
3805
  setVerbose(commandInput.verbose);
3303
- clack3.intro(banner());
3806
+ clack4.intro(banner());
3304
3807
  try {
3305
3808
  const config = readConfig();
3306
3809
  const dbPath = config.dbPath;
@@ -3319,20 +3822,20 @@ function registerIngestEntriesCommand(parent) {
3319
3822
  const claimApiKey = claimModel ? resolveLlmApiKey(config, claimModel.provider) : void 0;
3320
3823
  const sharedEmbedding = createEmbeddingClient(resolveEmbeddingApiKey(config), resolveEmbeddingModel(config));
3321
3824
  if (commandInput.verbose) {
3322
- clack3.log.step(`Discovering transcript files in ${path8.resolve(commandInput.targetPath)}...`);
3825
+ clack4.log.step(`Discovering transcript files in ${path10.resolve(commandInput.targetPath)}...`);
3323
3826
  }
3324
3827
  const files = await localTranscriptFiles.discoverFiles(commandInput.targetPath);
3325
3828
  if (files.length === 0) {
3326
- clack3.log.warn(`No transcript files found at ${path8.resolve(commandInput.targetPath)}.`);
3327
- clack3.outro("Nothing to ingest.");
3829
+ clack4.log.warn(`No transcript files found at ${path10.resolve(commandInput.targetPath)}.`);
3830
+ clack4.outro("Nothing to ingest.");
3328
3831
  return;
3329
3832
  }
3330
- clack3.log.info(
3833
+ clack4.log.info(
3331
3834
  [
3332
3835
  formatLabel("Extraction model", `${provider}/${modelId}`),
3333
3836
  formatLabel("Dedup model", commandInput.skipDedup ? "skipped" : `${dedupProvider}/${dedupModelId}`),
3334
3837
  formatLabel("Database", dbPath),
3335
- formatLabel("Files", `${files.length} ${pluralize2(files.length, "file")} found`),
3838
+ formatLabel("Files", `${files.length} ${pluralize3(files.length, "file")} found`),
3336
3839
  formatLabel("Whole-file", commandInput.wholeFile),
3337
3840
  formatLabel("Within-batch dedup", commandInput.skipDedup ? "skipped" : "enabled"),
3338
3841
  formatLabel("Embeddings", "stored"),
@@ -3340,11 +3843,11 @@ function registerIngestEntriesCommand(parent) {
3340
3843
  ].join("\n")
3341
3844
  );
3342
3845
  if (commandInput.dryRun) {
3343
- clack3.log.warn("Dry run mode - no entries will be stored.");
3846
+ clack4.log.warn("Dry run mode - no entries will be stored.");
3344
3847
  }
3345
3848
  const useVerboseBulkWriteProgress = commandInput.verbose && !commandInput.dryRun;
3346
- const spinner5 = useVerboseBulkWriteProgress ? null : clack3.spinner();
3347
- spinner5?.start(`Processing transcripts... (0/${files.length} extracted)`);
3849
+ const spinner6 = useVerboseBulkWriteProgress ? null : clack4.spinner();
3850
+ spinner6?.start(`Processing transcripts... (0/${files.length} extracted)`);
3348
3851
  const ingestResult = await ingestDiscoveredFiles(
3349
3852
  files,
3350
3853
  {
@@ -3367,23 +3870,23 @@ function registerIngestEntriesCommand(parent) {
3367
3870
  skipDedup: commandInput.skipDedup,
3368
3871
  extractionContext: config.extractionContext,
3369
3872
  onExtractionProgress: (completed, total) => {
3370
- spinner5?.message(`Processing transcripts... (${completed}/${total} extracted)`);
3873
+ spinner6?.message(`Processing transcripts... (${completed}/${total} extracted)`);
3371
3874
  },
3372
3875
  onStageProgress: (event) => {
3373
- spinner5?.message(progressMessageForIngestStage(event));
3876
+ spinner6?.message(progressMessageForIngestStage(event));
3374
3877
  },
3375
3878
  onDedupProgress: (event) => {
3376
- spinner5?.message(progressMessageForDedup(event));
3879
+ spinner6?.message(progressMessageForDedup(event));
3377
3880
  },
3378
3881
  onClaimExtractionProgress: (event) => {
3379
- spinner5?.message(progressMessageForClaimExtraction(event));
3882
+ spinner6?.message(progressMessageForClaimExtraction(event));
3380
3883
  },
3381
3884
  onBulkWriteProgress: useVerboseBulkWriteProgress ? reportBulkWriteProgress : (event) => {
3382
- spinner5?.message(progressMessageForBulkWrite(event.phase));
3885
+ spinner6?.message(progressMessageForBulkWrite(event.phase));
3383
3886
  }
3384
3887
  }
3385
3888
  );
3386
- spinner5?.stop("Ingest pipeline complete.");
3889
+ spinner6?.stop("Ingest pipeline complete.");
3387
3890
  const extractionRuns = ingestResult.extractionRuns;
3388
3891
  const extractedResults = extractionRuns.map(({ result }) => result);
3389
3892
  const extractedSuccesses = extractedResults.filter((result) => result.skipped !== true && result.error === void 0);
@@ -3396,7 +3899,7 @@ function registerIngestEntriesCommand(parent) {
3396
3899
  printDedupSummary(dedupResult, taggedEntries, commandInput, dedupUsage.totalCost);
3397
3900
  }
3398
3901
  if (claimKeyHealth) {
3399
- clack3.log.info(formatClaimKeyHealthSummary(claimKeyHealth));
3902
+ clack4.log.info(formatClaimKeyHealthSummary(claimKeyHealth));
3400
3903
  }
3401
3904
  const totals = {
3402
3905
  stored: 0,
@@ -3419,9 +3922,9 @@ function registerIngestEntriesCommand(parent) {
3419
3922
  totals.skippedFiles += 1;
3420
3923
  if (commandInput.verbose) {
3421
3924
  printVerboseFileDetails(result, commandInput, usage);
3422
- clack3.log.step(buildSkippedMessage(path8.basename(result.file)));
3925
+ clack4.log.step(buildSkippedMessage(path10.basename(result.file)));
3423
3926
  } else {
3424
- clack3.log.step(buildSkippedMessage(path8.basename(result.file)));
3927
+ clack4.log.step(buildSkippedMessage(path10.basename(result.file)));
3425
3928
  }
3426
3929
  continue;
3427
3930
  }
@@ -3430,7 +3933,7 @@ function registerIngestEntriesCommand(parent) {
3430
3933
  if (commandInput.verbose) {
3431
3934
  printVerboseFileDetails(result, commandInput, usage);
3432
3935
  }
3433
- clack3.log.error(buildFailureMessage(path8.basename(result.file), result, commandInput, usage, index === 0));
3936
+ clack4.log.error(buildFailureMessage(path10.basename(result.file), result, commandInput, usage, index === 0));
3434
3937
  continue;
3435
3938
  }
3436
3939
  const storeResult = result.storeResult ?? emptyStoreResult2();
@@ -3440,23 +3943,23 @@ function registerIngestEntriesCommand(parent) {
3440
3943
  if (commandInput.verbose) {
3441
3944
  printVerboseFileDetails(result, commandInput, usage);
3442
3945
  }
3443
- clack3.log.step(buildSuccessMessage(path8.basename(result.file), result, commandInput, usage, index === 0));
3946
+ clack4.log.step(buildSuccessMessage(path10.basename(result.file), result, commandInput, usage, index === 0));
3444
3947
  }
3445
- const summaryParts = [`${totals.stored} ${pluralize2(totals.stored, "entry", "entries")} stored`, `${totals.deduped} deduped`];
3948
+ const summaryParts = [`${totals.stored} ${pluralize3(totals.stored, "entry", "entries")} stored`, `${totals.deduped} deduped`];
3446
3949
  if (totals.rejected > 0) {
3447
3950
  summaryParts.push(`${totals.rejected} rejected`);
3448
3951
  }
3449
3952
  if (totals.skippedFiles > 0) {
3450
- summaryParts.push(`${totals.skippedFiles} ${pluralize2(totals.skippedFiles, "file")} skipped`);
3953
+ summaryParts.push(`${totals.skippedFiles} ${pluralize3(totals.skippedFiles, "file")} skipped`);
3451
3954
  }
3452
3955
  if (totals.failedFiles > 0) {
3453
- summaryParts.push(`${totals.failedFiles} ${pluralize2(totals.failedFiles, "file")} failed`);
3956
+ summaryParts.push(`${totals.failedFiles} ${pluralize3(totals.failedFiles, "file")} failed`);
3454
3957
  }
3455
3958
  if (totals.warnings > 0) {
3456
- summaryParts.push(`${totals.warnings} ${pluralize2(totals.warnings, "warning")}`);
3959
+ summaryParts.push(`${totals.warnings} ${pluralize3(totals.warnings, "warning")}`);
3457
3960
  }
3458
3961
  if (usageTotals.calls > 0) {
3459
- clack3.log.info(
3962
+ clack4.log.info(
3460
3963
  [
3461
3964
  formatLabel(
3462
3965
  "Tokens",
@@ -3468,11 +3971,11 @@ function registerIngestEntriesCommand(parent) {
3468
3971
  );
3469
3972
  }
3470
3973
  const dryRunSuffix = commandInput.dryRun ? " Dry run only." : "";
3471
- clack3.outro(`Done: ${summaryParts.join(", ")}. (${formatCost2(usageTotals.totalCost)}, ${formatDurationMs2(Date.now() - startedAt)})${dryRunSuffix}`);
3974
+ clack4.outro(`Done: ${summaryParts.join(", ")}. (${formatCost2(usageTotals.totalCost)}, ${formatDurationMs2(Date.now() - startedAt)})${dryRunSuffix}`);
3472
3975
  } catch (error) {
3473
3976
  process.exitCode = 1;
3474
- clack3.log.error(formatUnknownError3(error));
3475
- clack3.outro(ui.error("Ingest failed"));
3977
+ clack4.log.error(formatUnknownError5(error));
3978
+ clack4.outro(ui.error("Ingest failed"));
3476
3979
  } finally {
3477
3980
  await db?.close();
3478
3981
  }
@@ -3544,15 +4047,15 @@ function printVerboseFileDetails(result, options, usage) {
3544
4047
  }
3545
4048
  const fileLabel = result.file;
3546
4049
  if (result.skipped) {
3547
- clack3.log.step(`${fileLabel}: skipped because the ingest hash matched.`);
4050
+ clack4.log.step(`${fileLabel}: skipped because the ingest hash matched.`);
3548
4051
  return;
3549
4052
  }
3550
4053
  if (result.error) {
3551
4054
  for (const chunkDetail of result.chunkDetails) {
3552
- clack3.log.step(formatChunkDetail(result.file, chunkDetail));
4055
+ clack4.log.step(formatChunkDetail(result.file, chunkDetail));
3553
4056
  }
3554
4057
  const lines2 = [
3555
- `${fileLabel}: ${result.messageCount} ${pluralize2(result.messageCount, "message")} parsed before failure`,
4058
+ `${fileLabel}: ${result.messageCount} ${pluralize3(result.messageCount, "message")} parsed before failure`,
3556
4059
  `${fileLabel}: extraction ${result.successfulChunks}/${result.chunkCount} chunks succeeded`
3557
4060
  ];
3558
4061
  const usageLine2 = formatVerboseUsageLine(fileLabel, usage);
@@ -3560,19 +4063,19 @@ function printVerboseFileDetails(result, options, usage) {
3560
4063
  lines2.push(usageLine2);
3561
4064
  }
3562
4065
  lines2.push(`${fileLabel}: duration ${formatDurationMs2(result.durationMs)}`);
3563
- clack3.log.step(lines2.join("\n"));
4066
+ clack4.log.step(lines2.join("\n"));
3564
4067
  for (const warning of result.warnings) {
3565
4068
  if (warning !== result.error) {
3566
- clack3.log.warn(`${fileLabel}: ${warning}`);
4069
+ clack4.log.warn(`${fileLabel}: ${warning}`);
3567
4070
  }
3568
4071
  }
3569
4072
  return;
3570
4073
  }
3571
4074
  for (const chunkDetail of result.chunkDetails) {
3572
- clack3.log.step(formatChunkDetail(result.file, chunkDetail));
4075
+ clack4.log.step(formatChunkDetail(result.file, chunkDetail));
3573
4076
  }
3574
4077
  const lines = [
3575
- `${fileLabel}: ${result.messageCount} ${pluralize2(result.messageCount, "message")} parsed`,
4078
+ `${fileLabel}: ${result.messageCount} ${pluralize3(result.messageCount, "message")} parsed`,
3576
4079
  `${fileLabel}: extraction ${result.successfulChunks}/${result.chunkCount} chunks succeeded`,
3577
4080
  `${fileLabel}: store ${formatStoreSummary(result)}`
3578
4081
  ];
@@ -3581,15 +4084,15 @@ function printVerboseFileDetails(result, options, usage) {
3581
4084
  lines.push(usageLine);
3582
4085
  }
3583
4086
  lines.push(`${fileLabel}: duration ${formatDurationMs2(result.durationMs)}`);
3584
- clack3.log.step(lines.join("\n"));
4087
+ clack4.log.step(lines.join("\n"));
3585
4088
  for (const warning of result.warnings) {
3586
- clack3.log.warn(`${fileLabel}: ${warning}`);
4089
+ clack4.log.warn(`${fileLabel}: ${warning}`);
3587
4090
  }
3588
4091
  }
3589
4092
  function buildSuccessMessage(fileLabel, result, options, usage, isFirstFile) {
3590
4093
  const storeResult = result.storeResult ?? emptyStoreResult2();
3591
4094
  const details = [
3592
- `${result.messageCount} ${pluralize2(result.messageCount, "message")}`,
4095
+ `${result.messageCount} ${pluralize3(result.messageCount, "message")}`,
3593
4096
  `${result.entriesExtracted} extracted`,
3594
4097
  `${storeResult.stored} stored`
3595
4098
  ];
@@ -3600,7 +4103,7 @@ function buildSuccessMessage(fileLabel, result, options, usage, isFirstFile) {
3600
4103
  details.push(`${storeResult.rejected} rejected`);
3601
4104
  }
3602
4105
  if (result.failedChunks > 0) {
3603
- details.push(`${result.failedChunks} chunk ${pluralize2(result.failedChunks, "failure")}`);
4106
+ details.push(`${result.failedChunks} chunk ${pluralize3(result.failedChunks, "failure")}`);
3604
4107
  }
3605
4108
  if (options.dryRun === true) {
3606
4109
  details.push("dry run");
@@ -3625,7 +4128,7 @@ function formatStoreSummary(result) {
3625
4128
  const storeResult = result.storeResult ?? emptyStoreResult2();
3626
4129
  const parts = [`${storeResult.stored} stored`, `${storeResult.skipped} deduped`, `${storeResult.rejected} rejected`];
3627
4130
  if (result.failedChunks > 0) {
3628
- parts.push(`${result.failedChunks} chunk ${pluralize2(result.failedChunks, "failure")}`);
4131
+ parts.push(`${result.failedChunks} chunk ${pluralize3(result.failedChunks, "failure")}`);
3629
4132
  }
3630
4133
  return parts.join(", ");
3631
4134
  }
@@ -3652,12 +4155,12 @@ function formatVerboseUsageLine(fileLabel, usage) {
3652
4155
  if (usage.fileCost === 0 && usage.fileCalls === 0) {
3653
4156
  return void 0;
3654
4157
  }
3655
- return `${fileLabel}: cost ${formatCost2(usage.fileCost)} (${usage.fileCalls} ${pluralize2(usage.fileCalls, "LLM call")})`;
4158
+ return `${fileLabel}: cost ${formatCost2(usage.fileCost)} (${usage.fileCalls} ${pluralize3(usage.fileCalls, "LLM call")})`;
3656
4159
  }
3657
- function formatUnknownError3(error) {
4160
+ function formatUnknownError5(error) {
3658
4161
  return error instanceof Error ? error.message : String(error);
3659
4162
  }
3660
- function pluralize2(value, singular, plural) {
4163
+ function pluralize3(value, singular, plural) {
3661
4164
  return value === 1 ? singular : plural ?? `${singular}s`;
3662
4165
  }
3663
4166
  function getDisplayResult(result, storeResults) {
@@ -3683,33 +4186,33 @@ function collectTaggedEntries2(results) {
3683
4186
  }
3684
4187
  function printDedupSummary(dedupResult, taggedEntries, options, dedupCost) {
3685
4188
  if (taggedEntries.length === 0) {
3686
- clack3.log.step("Dedup: 0 entries extracted, nothing to arbitrate.");
4189
+ clack4.log.step("Dedup: 0 entries extracted, nothing to arbitrate.");
3687
4190
  return;
3688
4191
  }
3689
4192
  if (options.skipDedup === true) {
3690
- clack3.log.step(`Dedup: skipped (--skip-dedup), ${taggedEntries.length} ${pluralize2(taggedEntries.length, "entry", "entries")} passed through.`);
4193
+ clack4.log.step(`Dedup: skipped (--skip-dedup), ${taggedEntries.length} ${pluralize3(taggedEntries.length, "entry", "entries")} passed through.`);
3691
4194
  return;
3692
4195
  }
3693
- clack3.log.step(
3694
- `Dedup: ${dedupResult.inputCount} ${pluralize2(dedupResult.inputCount, "entry", "entries")} -> ${dedupResult.clustersArbitrated} similar ${pluralize2(dedupResult.clustersArbitrated, "cluster")} found (similarity > ${formatThreshold(dedupResult.similarityThreshold)})`
4196
+ clack4.log.step(
4197
+ `Dedup: ${dedupResult.inputCount} ${pluralize3(dedupResult.inputCount, "entry", "entries")} -> ${dedupResult.clustersArbitrated} similar ${pluralize3(dedupResult.clustersArbitrated, "cluster")} found (similarity > ${formatThreshold(dedupResult.similarityThreshold)})`
3695
4198
  );
3696
- clack3.log.step(
3697
- `Dedup: ${dedupResult.clustersArbitrated} ${pluralize2(dedupResult.clustersArbitrated, "cluster")} arbitrated (${dedupResult.llmCalls} ${pluralize2(dedupResult.llmCalls, "LLM call")})`
4199
+ clack4.log.step(
4200
+ `Dedup: ${dedupResult.clustersArbitrated} ${pluralize3(dedupResult.clustersArbitrated, "cluster")} arbitrated (${dedupResult.llmCalls} ${pluralize3(dedupResult.llmCalls, "LLM call")})`
3698
4201
  );
3699
- clack3.log.step(
3700
- `Dedup: ${dedupResult.survivors.length} ${pluralize2(dedupResult.survivors.length, "entry", "entries")} survived, ${dedupResult.removedCount} removed (${formatCost2(dedupCost)})`
4202
+ clack4.log.step(
4203
+ `Dedup: ${dedupResult.survivors.length} ${pluralize3(dedupResult.survivors.length, "entry", "entries")} survived, ${dedupResult.removedCount} removed (${formatCost2(dedupCost)})`
3701
4204
  );
3702
4205
  for (const warning of dedupResult.warnings) {
3703
- clack3.log.warn(`Dedup: ${warning}`);
4206
+ clack4.log.warn(`Dedup: ${warning}`);
3704
4207
  }
3705
4208
  if (options.verbose !== true) {
3706
4209
  return;
3707
4210
  }
3708
4211
  for (const [clusterIndex, detail] of dedupResult.clusterDetails.entries()) {
3709
- clack3.log.step(formatDedupClusterDetail(clusterIndex, detail, taggedEntries));
4212
+ clack4.log.step(formatDedupClusterDetail(clusterIndex, detail, taggedEntries));
3710
4213
  }
3711
- clack3.log.step(
3712
- `Dedup: ${dedupResult.singletonsPassedThrough} ${pluralize2(dedupResult.singletonsPassedThrough, "singleton")} passed through (no similar neighbors)`
4214
+ clack4.log.step(
4215
+ `Dedup: ${dedupResult.singletonsPassedThrough} ${pluralize3(dedupResult.singletonsPassedThrough, "singleton")} passed through (no similar neighbors)`
3713
4216
  );
3714
4217
  }
3715
4218
  function formatDedupClusterDetail(clusterIndex, detail, taggedEntries) {
@@ -3718,7 +4221,7 @@ function formatDedupClusterDetail(clusterIndex, detail, taggedEntries) {
3718
4221
  localIndexByOriginal.set(entryIndex, localIndex);
3719
4222
  });
3720
4223
  const lines = [
3721
- `Dedup cluster ${clusterIndex + 1} (${detail.entryIndices.length} ${pluralize2(detail.entryIndices.length, "entry", "entries")}, max similarity ${detail.maxSimilarity.toFixed(2)}):`
4224
+ `Dedup cluster ${clusterIndex + 1} (${detail.entryIndices.length} ${pluralize3(detail.entryIndices.length, "entry", "entries")}, max similarity ${detail.maxSimilarity.toFixed(2)}):`
3722
4225
  ];
3723
4226
  for (const [localIndex, originalIndex] of detail.entryIndices.entries()) {
3724
4227
  const taggedEntry = taggedEntries.find((entry2) => entry2.originalIndex === originalIndex);
@@ -3778,11 +4281,11 @@ function progressMessageForIngestStage(event) {
3778
4281
  case "claim_extraction_start":
3779
4282
  return "Extracting claim keys...";
3780
4283
  case "store_start":
3781
- return `Running store pipeline for ${event.totalEntries} ${pluralize2(event.totalEntries, "entry", "entries")}...`;
4284
+ return `Running store pipeline for ${event.totalEntries} ${pluralize3(event.totalEntries, "entry", "entries")}...`;
3782
4285
  }
3783
4286
  }
3784
4287
  function progressMessageForDedup(event) {
3785
- return `Deduplicating entries... ${event.completedClusters}/${event.totalClusters} ${pluralize2(event.totalClusters, "cluster")} arbitrated (${event.completedEntries}/${event.totalEntries} entries covered)`;
4288
+ return `Deduplicating entries... ${event.completedClusters}/${event.totalClusters} ${pluralize3(event.totalClusters, "cluster")} arbitrated (${event.completedEntries}/${event.totalEntries} entries covered)`;
3786
4289
  }
3787
4290
  function progressMessageForClaimExtraction(event) {
3788
4291
  switch (event.phase) {
@@ -3807,16 +4310,16 @@ function progressMessageForBulkWrite(phase) {
3807
4310
  function reportBulkWriteProgress(event) {
3808
4311
  switch (event.phase) {
3809
4312
  case "prepare_start":
3810
- clack3.log.step("Store: dropping FTS triggers and vector index for bulk writes...");
4313
+ clack4.log.step("Store: dropping FTS triggers and vector index for bulk writes...");
3811
4314
  break;
3812
4315
  case "store_complete":
3813
- clack3.log.step("Store phase complete.");
4316
+ clack4.log.step("Store phase complete.");
3814
4317
  break;
3815
4318
  case "finalize_start":
3816
- clack3.log.step("Store: rebuilding FTS and vector index...");
4319
+ clack4.log.step("Store: rebuilding FTS and vector index...");
3817
4320
  break;
3818
4321
  case "finalize_complete":
3819
- clack3.log.step(`Store: indexes rebuilt (${formatDurationMs2(event.durationMs ?? 0)}).`);
4322
+ clack4.log.step(`Store: indexes rebuilt (${formatDurationMs2(event.durationMs ?? 0)}).`);
3820
4323
  break;
3821
4324
  }
3822
4325
  }
@@ -3870,33 +4373,33 @@ import { getModels } from "@mariozechner/pi-ai";
3870
4373
 
3871
4374
  // src/cli/ui.ts
3872
4375
  import os2 from "os";
3873
- import path9 from "path";
3874
- import * as clack4 from "@clack/prompts";
4376
+ import path11 from "path";
4377
+ import * as clack5 from "@clack/prompts";
3875
4378
  function createCliPrompts() {
3876
4379
  return {
3877
- intro: (message) => clack4.intro(message),
3878
- outro: (message) => clack4.outro(message),
3879
- note: (message, title) => clack4.note(message, title),
3880
- cancel: (message) => clack4.cancel(message),
3881
- isCancel: clack4.isCancel,
4380
+ intro: (message) => clack5.intro(message),
4381
+ outro: (message) => clack5.outro(message),
4382
+ note: (message, title) => clack5.note(message, title),
4383
+ cancel: (message) => clack5.cancel(message),
4384
+ isCancel: clack5.isCancel,
3882
4385
  select: async (options) => {
3883
- return await clack4.select({
4386
+ return await clack5.select({
3884
4387
  message: options.message,
3885
4388
  options: options.options,
3886
4389
  ...options.initialValue !== void 0 ? { initialValue: options.initialValue } : {}
3887
4390
  });
3888
4391
  },
3889
4392
  confirm: async (options) => {
3890
- return await clack4.confirm(options);
4393
+ return await clack5.confirm(options);
3891
4394
  },
3892
4395
  password: async (options) => {
3893
- return await clack4.password(options);
4396
+ return await clack5.password(options);
3894
4397
  },
3895
4398
  text: async (options) => {
3896
- return await clack4.text(options);
4399
+ return await clack5.text(options);
3897
4400
  },
3898
- spinner: () => clack4.spinner(),
3899
- log: clack4.log
4401
+ spinner: () => clack5.spinner(),
4402
+ log: clack5.log
3900
4403
  };
3901
4404
  }
3902
4405
  var cliPrompts = createCliPrompts();
@@ -3909,12 +4412,12 @@ function resolveUserPath2(value) {
3909
4412
  return os2.homedir();
3910
4413
  }
3911
4414
  if (trimmed.startsWith("~/")) {
3912
- return path9.join(os2.homedir(), trimmed.slice(2));
4415
+ return path11.join(os2.homedir(), trimmed.slice(2));
3913
4416
  }
3914
4417
  if (trimmed.startsWith("~\\")) {
3915
- return path9.join(os2.homedir(), trimmed.slice(2));
4418
+ return path11.join(os2.homedir(), trimmed.slice(2));
3916
4419
  }
3917
- return path9.resolve(trimmed);
4420
+ return path11.resolve(trimmed);
3918
4421
  }
3919
4422
  function formatPathForDisplay(filePath) {
3920
4423
  const home = os2.homedir();
@@ -3999,7 +4502,7 @@ async function withTimeout(promise, timeoutMs, message) {
3999
4502
  });
4000
4503
  return await Promise.race([promise, timeout]);
4001
4504
  }
4002
- function formatUnknownError4(error) {
4505
+ function formatUnknownError6(error) {
4003
4506
  return error instanceof Error ? error.message : String(error);
4004
4507
  }
4005
4508
 
@@ -4285,7 +4788,7 @@ function getSetupReadiness(config, env = process.env) {
4285
4788
  } catch (error) {
4286
4789
  return {
4287
4790
  ready: false,
4288
- guidance: formatUnknownError4(error)
4791
+ guidance: formatUnknownError6(error)
4289
4792
  };
4290
4793
  }
4291
4794
  }
@@ -4629,14 +5132,14 @@ async function selectAuthModel(prompts, runtime, auth, existingModel) {
4629
5132
  async function verifyManualPrimaryCredential(prompts, runtime, auth, provider, initialApiKey, probeModel) {
4630
5133
  let apiKey = initialApiKey;
4631
5134
  while (true) {
4632
- const spinner5 = prompts.spinner();
4633
- spinner5.start(`Testing ${describeAuthMethod(auth)}...`);
5135
+ const spinner6 = prompts.spinner();
5136
+ spinner6.start(`Testing ${describeAuthMethod(auth)}...`);
4634
5137
  const result = await runtime.testLlmConnection(provider, probeModel, apiKey);
4635
5138
  if (result.ok) {
4636
- spinner5.stop(ui.success("Connection verified"));
5139
+ spinner6.stop(ui.success("Connection verified"));
4637
5140
  return { apiKey, verified: true };
4638
5141
  }
4639
- spinner5.stop(ui.error(`Connection failed: ${result.error ?? "unknown error"}`));
5142
+ spinner6.stop(ui.error(`Connection failed: ${result.error ?? "unknown error"}`));
4640
5143
  const action = await prompts.select({
4641
5144
  message: "The provider connection test failed. What do you want to do?",
4642
5145
  options: [
@@ -4667,14 +5170,14 @@ async function verifyManualPrimaryCredential(prompts, runtime, auth, provider, i
4667
5170
  }
4668
5171
  async function verifyDetectedPrimaryCredential(prompts, runtime, auth, provider, apiKey, probeModel) {
4669
5172
  while (true) {
4670
- const spinner5 = prompts.spinner();
4671
- spinner5.start(`Testing ${describeAuthMethod(auth)}...`);
5173
+ const spinner6 = prompts.spinner();
5174
+ spinner6.start(`Testing ${describeAuthMethod(auth)}...`);
4672
5175
  const result = await runtime.testLlmConnection(provider, probeModel, apiKey);
4673
5176
  if (result.ok) {
4674
- spinner5.stop(ui.success("Connection verified"));
5177
+ spinner6.stop(ui.success("Connection verified"));
4675
5178
  return { apiKey, verified: true };
4676
5179
  }
4677
- spinner5.stop(ui.error(`Connection failed: ${result.error ?? "unknown error"}`));
5180
+ spinner6.stop(ui.error(`Connection failed: ${result.error ?? "unknown error"}`));
4678
5181
  const action = await prompts.select({
4679
5182
  message: "The provider connection test failed. What do you want to do?",
4680
5183
  options: [
@@ -4693,14 +5196,14 @@ async function verifyDetectedPrimaryCredential(prompts, runtime, auth, provider,
4693
5196
  }
4694
5197
  async function verifySharedEmbeddingKey(prompts, runtime, apiKey) {
4695
5198
  while (true) {
4696
- const spinner5 = prompts.spinner();
4697
- spinner5.start(`Testing embeddings with OpenAI ${EMBEDDING_MODEL}...`);
5199
+ const spinner6 = prompts.spinner();
5200
+ spinner6.start(`Testing embeddings with OpenAI ${EMBEDDING_MODEL}...`);
4698
5201
  const result = await runtime.testEmbeddingConnection(apiKey, EMBEDDING_MODEL);
4699
5202
  if (result.ok) {
4700
- spinner5.stop(ui.success("Embeddings verified"));
5203
+ spinner6.stop(ui.success("Embeddings verified"));
4701
5204
  return { verified: true };
4702
5205
  }
4703
- spinner5.stop(ui.error(`Embeddings test failed: ${result.error ?? "unknown error"}`));
5206
+ spinner6.stop(ui.error(`Embeddings test failed: ${result.error ?? "unknown error"}`));
4704
5207
  const action = await prompts.select({
4705
5208
  message: "The embeddings test failed. What do you want to do?",
4706
5209
  options: [
@@ -4720,14 +5223,14 @@ async function verifySharedEmbeddingKey(prompts, runtime, apiKey) {
4720
5223
  async function verifySeparateEmbeddingKey(prompts, runtime, initialApiKey) {
4721
5224
  let apiKey = initialApiKey;
4722
5225
  while (true) {
4723
- const spinner5 = prompts.spinner();
4724
- spinner5.start(`Testing embeddings with OpenAI ${EMBEDDING_MODEL}...`);
5226
+ const spinner6 = prompts.spinner();
5227
+ spinner6.start(`Testing embeddings with OpenAI ${EMBEDDING_MODEL}...`);
4725
5228
  const result = await runtime.testEmbeddingConnection(apiKey, EMBEDDING_MODEL);
4726
5229
  if (result.ok) {
4727
- spinner5.stop(ui.success("Embeddings verified"));
5230
+ spinner6.stop(ui.success("Embeddings verified"));
4728
5231
  return { apiKey, verified: true };
4729
5232
  }
4730
- spinner5.stop(ui.error(`Embeddings test failed: ${result.error ?? "unknown error"}`));
5233
+ spinner6.stop(ui.error(`Embeddings test failed: ${result.error ?? "unknown error"}`));
4731
5234
  const action = await prompts.select({
4732
5235
  message: "The embeddings test failed. What do you want to do?",
4733
5236
  options: [
@@ -4865,7 +5368,7 @@ var defaultSetupRuntime = {
4865
5368
  } catch (error) {
4866
5369
  return {
4867
5370
  ok: false,
4868
- error: formatUnknownError4(error)
5371
+ error: formatUnknownError6(error)
4869
5372
  };
4870
5373
  }
4871
5374
  },
@@ -4880,7 +5383,7 @@ var defaultSetupRuntime = {
4880
5383
  } catch (error) {
4881
5384
  return {
4882
5385
  ok: false,
4883
- error: formatUnknownError4(error)
5386
+ error: formatUnknownError6(error)
4884
5387
  };
4885
5388
  }
4886
5389
  },
@@ -4935,7 +5438,7 @@ async function runSetupCommand() {
4935
5438
  prompts.outro(`Next: ${ui.bold('agenr recall "test"')} or ${ui.bold("agenr ingest <path>")}`);
4936
5439
  } catch (error) {
4937
5440
  process.exitCode = 1;
4938
- prompts.log.error(formatUnknownError4(error));
5441
+ prompts.log.error(formatUnknownError6(error));
4939
5442
  prompts.outro(ui.error("Setup failed"));
4940
5443
  }
4941
5444
  }
@@ -4987,8 +5490,8 @@ function formatTokenCount(tokens) {
4987
5490
 
4988
5491
  // src/cli/commands/init/external-commands.ts
4989
5492
  import { execFile, execFileSync } from "child_process";
4990
- import fs6 from "fs/promises";
4991
- import path10 from "path";
5493
+ import fs7 from "fs/promises";
5494
+ import path12 from "path";
4992
5495
  var OPENCLAW_PLUGIN_PACKAGE = "@agenr/agenr-plugin";
4993
5496
  function execAsync(command, args, options) {
4994
5497
  return new Promise((resolve, reject) => {
@@ -5085,10 +5588,10 @@ async function restartOpenClawGateway() {
5085
5588
  }
5086
5589
  }
5087
5590
  async function writeOpenClawPluginConfig(stateDir, config) {
5088
- const openclawConfigPath = path10.join(stateDir, "openclaw.json");
5591
+ const openclawConfigPath = path12.join(stateDir, "openclaw.json");
5089
5592
  let root = {};
5090
5593
  try {
5091
- const raw = await fs6.readFile(openclawConfigPath, "utf8");
5594
+ const raw = await fs7.readFile(openclawConfigPath, "utf8");
5092
5595
  const parsed = JSON.parse(raw);
5093
5596
  if (isRecord(parsed)) {
5094
5597
  root = parsed;
@@ -5114,13 +5617,13 @@ async function writeOpenClawPluginConfig(stateDir, config) {
5114
5617
  } else {
5115
5618
  delete entryConfig.configPath;
5116
5619
  }
5117
- await fs6.mkdir(stateDir, { recursive: true });
5118
- await fs6.writeFile(openclawConfigPath, `${JSON.stringify(root, null, 2)}
5620
+ await fs7.mkdir(stateDir, { recursive: true });
5621
+ await fs7.writeFile(openclawConfigPath, `${JSON.stringify(root, null, 2)}
5119
5622
  `, "utf8");
5120
5623
  return openclawConfigPath;
5121
5624
  }
5122
5625
  function shouldPersistPluginConfigPath(dbPath, configPath) {
5123
- return path10.dirname(dbPath) !== path10.dirname(configPath);
5626
+ return path12.dirname(dbPath) !== path12.dirname(configPath);
5124
5627
  }
5125
5628
  function isRecord(value) {
5126
5629
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -5147,21 +5650,21 @@ function ensureArrayOfStrings(target, key) {
5147
5650
  }
5148
5651
 
5149
5652
  // src/cli/commands/init/openclaw-detect.ts
5150
- import fs7 from "fs";
5653
+ import fs8 from "fs";
5151
5654
  import os3 from "os";
5152
- import path11 from "path";
5655
+ import path13 from "path";
5153
5656
  function resolveDefaultOpenClawStateDir() {
5154
- return path11.join(os3.homedir(), ".openclaw");
5657
+ return path13.join(os3.homedir(), ".openclaw");
5155
5658
  }
5156
- function detectOpenClawInstallation(env = process.env, existsSyncFn = fs7.existsSync) {
5659
+ function detectOpenClawInstallation(env = process.env, existsSyncFn = fs8.existsSync) {
5157
5660
  const envStateDir = normalizeOptionalString5(env.OPENCLAW_STATE_DIR) ?? normalizeOptionalString5(env.OPENCLAW_HOME);
5158
5661
  const source = envStateDir ? "environment" : "default";
5159
5662
  const stateDir = envStateDir ? resolveUserPath2(envStateDir) : resolveDefaultOpenClawStateDir();
5160
5663
  return {
5161
5664
  detected: envStateDir !== void 0 || existsSyncFn(stateDir),
5162
5665
  stateDir,
5163
- configPath: path11.join(stateDir, "openclaw.json"),
5164
- sessionsRoot: path11.join(stateDir, "agents"),
5666
+ configPath: path13.join(stateDir, "openclaw.json"),
5667
+ sessionsRoot: path13.join(stateDir, "agents"),
5165
5668
  source
5166
5669
  };
5167
5670
  }
@@ -5171,8 +5674,8 @@ function normalizeOptionalString5(value) {
5171
5674
  }
5172
5675
 
5173
5676
  // src/cli/commands/init/session-scanner.ts
5174
- import fs8 from "fs/promises";
5175
- import path12 from "path";
5677
+ import fs9 from "fs/promises";
5678
+ import path14 from "path";
5176
5679
  async function scanSessionFiles(sessionsRoot, recentDays = 7) {
5177
5680
  const result = {
5178
5681
  totalFiles: 0,
@@ -5197,7 +5700,7 @@ async function scanSessionFiles(sessionsRoot, recentDays = 7) {
5197
5700
  }
5198
5701
  let stat;
5199
5702
  try {
5200
- stat = await fs8.stat(filePath);
5703
+ stat = await fs9.stat(filePath);
5201
5704
  } catch (error) {
5202
5705
  if (isMissingPathError(error)) {
5203
5706
  continue;
@@ -5215,7 +5718,7 @@ async function scanSessionFiles(sessionsRoot, recentDays = 7) {
5215
5718
  return result;
5216
5719
  }
5217
5720
  function isSessionTranscriptPath(filePath) {
5218
- return filePath.split(path12.sep).includes("sessions");
5721
+ return filePath.split(path14.sep).includes("sessions");
5219
5722
  }
5220
5723
  function isMissingPathError(error) {
5221
5724
  return typeof error === "object" && error !== null && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
@@ -5341,7 +5844,7 @@ async function runInitWizard(options = {}) {
5341
5844
  });
5342
5845
  prompts.log.info(`Updated ${formatPathForDisplay(openclawConfigPath)} to enable agenr as the active memory plugin.`);
5343
5846
  } catch (error) {
5344
- const message = formatUnknownError5(error);
5847
+ const message = formatUnknownError7(error);
5345
5848
  pluginStatus = `Plugin installed, but OpenClaw config update failed: ${message}`;
5346
5849
  prompts.log.warn(`OpenClaw config update failed: ${message}`);
5347
5850
  }
@@ -5358,18 +5861,18 @@ async function runInitWizard(options = {}) {
5358
5861
  scanSpinner.start("Scanning existing OpenClaw sessions...");
5359
5862
  sessionScan = await runtime.scanSessionFiles(detection.sessionsRoot);
5360
5863
  scanSpinner.stop(
5361
- `Found ${sessionScan.totalFiles} ${pluralize2(sessionScan.totalFiles, "session")} under ${formatPathForDisplay(detection.sessionsRoot)}.`
5864
+ `Found ${sessionScan.totalFiles} ${pluralize3(sessionScan.totalFiles, "session")} under ${formatPathForDisplay(detection.sessionsRoot)}.`
5362
5865
  );
5363
5866
  if (sessionScan.totalFiles === 0) {
5364
5867
  sessionStatus = "No sessions found";
5365
5868
  ingestStatus = "Skipped - no sessions found";
5366
5869
  prompts.log.info("No existing OpenClaw session transcripts were found. You can ingest later once sessions exist.");
5367
5870
  } else if (!setupReady) {
5368
- sessionStatus = `${sessionScan.totalFiles} ${pluralize2(sessionScan.totalFiles, "session")} found (${sessionScan.recentFiles.length} from last 7 days)`;
5871
+ sessionStatus = `${sessionScan.totalFiles} ${pluralize3(sessionScan.totalFiles, "session")} found (${sessionScan.recentFiles.length} from last 7 days)`;
5369
5872
  ingestStatus = "Skipped - current auth still needs credentials";
5370
5873
  prompts.log.warn(setupReadiness.guidance ?? "Skipping bulk ingest until agenr can resolve working LLM credentials for the selected auth profile.");
5371
5874
  } else {
5372
- sessionStatus = `${sessionScan.totalFiles} ${pluralize2(sessionScan.totalFiles, "session")} found (${sessionScan.recentFiles.length} from last 7 days)`;
5875
+ sessionStatus = `${sessionScan.totalFiles} ${pluralize3(sessionScan.totalFiles, "session")} found (${sessionScan.recentFiles.length} from last 7 days)`;
5373
5876
  const { provider: extractionProvider, modelId } = resolveModel(activeConfig, "extraction");
5374
5877
  const providerForCost = normalizeSetupProvider(extractionProvider);
5375
5878
  const showUsdEstimate = hasMeteredIngestCost(activeConfig.auth);
@@ -5389,11 +5892,11 @@ async function runInitWizard(options = {}) {
5389
5892
  } else {
5390
5893
  const filesToIngest = ingestChoice === "recent" ? sessionScan.recentFiles : sessionScan.allFiles;
5391
5894
  const ingestResult = await runtime.runBulkIngest(filesToIngest, activeConfig, prompts);
5392
- ingestStatus = `${ingestResult.filesProcessed} ${pluralize2(ingestResult.filesProcessed, "session")} processed, ${ingestResult.storedEntries} ${pluralize2(ingestResult.storedEntries, "entry", "entries")} stored`;
5895
+ ingestStatus = `${ingestResult.filesProcessed} ${pluralize3(ingestResult.filesProcessed, "session")} processed, ${ingestResult.storedEntries} ${pluralize3(ingestResult.storedEntries, "entry", "entries")} stored`;
5393
5896
  prompts.log.info(
5394
5897
  [
5395
- formatLabel("Ingested", `${ingestResult.filesProcessed} ${pluralize2(ingestResult.filesProcessed, "session")}`),
5396
- formatLabel("Stored", `${ingestResult.storedEntries} ${pluralize2(ingestResult.storedEntries, "entry", "entries")}`),
5898
+ formatLabel("Ingested", `${ingestResult.filesProcessed} ${pluralize3(ingestResult.filesProcessed, "session")}`),
5899
+ formatLabel("Stored", `${ingestResult.storedEntries} ${pluralize3(ingestResult.storedEntries, "entry", "entries")}`),
5397
5900
  formatLabel("Failures", `${ingestResult.failedFiles}`),
5398
5901
  formatLabel("Cost", formatCostUsd(ingestResult.totalCostUsd))
5399
5902
  ].join("\n")
@@ -5422,14 +5925,14 @@ async function runInitWizard(options = {}) {
5422
5925
  prompts.outro(buildNextSteps(detection, pluginStatus, gatewayStatus, sessionScan));
5423
5926
  } catch (error) {
5424
5927
  process.exitCode = 1;
5425
- prompts.log.error(formatUnknownError5(error));
5928
+ prompts.log.error(formatUnknownError7(error));
5426
5929
  prompts.outro(ui.error("Init failed"));
5427
5930
  }
5428
5931
  }
5429
5932
  async function runBulkIngest(files, config, prompts) {
5430
5933
  let database = null;
5431
- const spinner5 = prompts.spinner();
5432
- spinner5.start(`Ingesting ${files.length} ${pluralize2(files.length, "session")}... (0/${files.length} extracted)`);
5934
+ const spinner6 = prompts.spinner();
5935
+ spinner6.start(`Ingesting ${files.length} ${pluralize3(files.length, "session")}... (0/${files.length} extracted)`);
5433
5936
  try {
5434
5937
  database = await createDatabase(config.dbPath ?? resolveDbPath(config));
5435
5938
  const { provider, modelId } = resolveModel(config, "extraction");
@@ -5457,17 +5960,17 @@ async function runBulkIngest(files, config, prompts) {
5457
5960
  claimExtractionConfig,
5458
5961
  extractionContext: config.extractionContext,
5459
5962
  onExtractionProgress: (completed, total) => {
5460
- spinner5.message(`Ingesting sessions... (${completed}/${total} extracted)`);
5963
+ spinner6.message(`Ingesting sessions... (${completed}/${total} extracted)`);
5461
5964
  },
5462
5965
  onBulkWriteProgress: (event) => {
5463
- spinner5.message(progressMessageForBulkWrite2(event.phase));
5966
+ spinner6.message(progressMessageForBulkWrite2(event.phase));
5464
5967
  }
5465
5968
  }
5466
5969
  );
5467
5970
  const storedEntries = Array.from(result.storeResults.values()).reduce((total, fileResult) => total + (fileResult.storeResult?.stored ?? 0), 0);
5468
5971
  const failedFiles = result.extractionRuns.filter((run) => run.result.error !== void 0).length;
5469
5972
  const totalCostUsd = result.extractionRuns.reduce((total, run) => total + run.usage.totalCost, 0) + result.dedupUsage.totalCost;
5470
- spinner5.stop(`Ingest complete: ${storedEntries} ${pluralize2(storedEntries, "entry", "entries")} stored.`);
5973
+ spinner6.stop(`Ingest complete: ${storedEntries} ${pluralize3(storedEntries, "entry", "entries")} stored.`);
5471
5974
  return {
5472
5975
  filesProcessed: files.length,
5473
5976
  storedEntries,
@@ -5480,7 +5983,7 @@ async function runBulkIngest(files, config, prompts) {
5480
5983
  }
5481
5984
  function buildIngestChoiceMessage(scan, modelId, recentCost, fullCost, showUsdEstimate) {
5482
5985
  const lines = [
5483
- `Found ${scan.totalFiles} ${pluralize2(scan.totalFiles, "session")} (${scan.recentFiles.length} from last 7 days).`,
5986
+ `Found ${scan.totalFiles} ${pluralize3(scan.totalFiles, "session")} (${scan.recentFiles.length} from last 7 days).`,
5484
5987
  "",
5485
5988
  showUsdEstimate ? `Estimated extraction cost with ${modelId}:` : `Estimated transcript volume with ${modelId}:`
5486
5989
  ];
@@ -5571,7 +6074,7 @@ function progressMessageForBulkWrite2(phase) {
5571
6074
  return "Bulk ingest finalization complete...";
5572
6075
  }
5573
6076
  }
5574
- function formatUnknownError5(error) {
6077
+ function formatUnknownError7(error) {
5575
6078
  return error instanceof Error ? error.message : String(error);
5576
6079
  }
5577
6080
 
@@ -5583,11 +6086,11 @@ function registerInitCommand(program2) {
5583
6086
  }
5584
6087
 
5585
6088
  // src/cli/commands/recall.ts
5586
- import * as clack5 from "@clack/prompts";
6089
+ import * as clack6 from "@clack/prompts";
5587
6090
  import { InvalidArgumentError as InvalidArgumentError4, Option as Option3 } from "commander";
5588
6091
  function registerRecallCommand(program2) {
5589
6092
  program2.command("recall <query>").description("Search the knowledge database with the v1 hybrid recall pipeline").addOption(new Option3("--limit <n>", "Max results").argParser(parsePositiveInteger).default(10)).addOption(new Option3("--threshold <n>", "Minimum score cutoff").argParser(parseUnitInterval).default(0)).addOption(new Option3("--budget <n>", "Max token budget").argParser(parsePositiveInteger)).addOption(new Option3("--types <types>", "Comma-separated entry types").argParser(parseEntryTypes)).addOption(new Option3("--tags <tags>", "Comma-separated tags").argParser(parseCsvList)).option("--since <date>", "Only entries after this date (ISO or relative like 7d)").option("--until <date>", "Only entries before this date").option("--around <date>", "Bias results toward this date").option("--as-of <date>", "Resolve current vs prior state at this reference time").addOption(new Option3("--around-radius <n>", "Gaussian radius in days").argParser(parsePositiveNumber).default(14)).option("--verbose", "Show score breakdowns").action(async (query, options) => {
5590
- clack5.intro(banner());
6093
+ clack6.intro(banner());
5591
6094
  let db = null;
5592
6095
  try {
5593
6096
  const commandInput = normalizeRecallCommand(query, options);
@@ -5597,8 +6100,8 @@ function registerRecallCommand(program2) {
5597
6100
  db = await createDatabase(dbPath);
5598
6101
  const adapter = createRecallAdapter(db, embeddingClient);
5599
6102
  let lastTraceSummary;
5600
- const spinner5 = clack5.spinner();
5601
- spinner5.start("Searching knowledge...");
6103
+ const spinner6 = clack6.spinner();
6104
+ spinner6.start("Searching knowledge...");
5602
6105
  const results = await recall(commandInput.request, adapter, {
5603
6106
  trace: {
5604
6107
  reportSummary(summary) {
@@ -5606,24 +6109,24 @@ function registerRecallCommand(program2) {
5606
6109
  }
5607
6110
  }
5608
6111
  });
5609
- spinner5.stop(`Found ${results.length} ${pluralize3(results.length, "result")}.`);
6112
+ spinner6.stop(`Found ${results.length} ${pluralize4(results.length, "result")}.`);
5610
6113
  if (lastTraceSummary?.degraded.active) {
5611
6114
  for (const notice of lastTraceSummary.degraded.notices) {
5612
- clack5.log.warn(notice);
6115
+ clack6.log.warn(notice);
5613
6116
  }
5614
6117
  }
5615
6118
  if (results.length === 0) {
5616
- clack5.outro("No matching entries found.");
6119
+ clack6.outro("No matching entries found.");
5617
6120
  return;
5618
6121
  }
5619
6122
  for (const result of results) {
5620
- clack5.log.step(formatResult(result, commandInput.verbose, commandInput.request.asOf));
6123
+ clack6.log.step(formatResult(result, commandInput.verbose, commandInput.request.asOf));
5621
6124
  }
5622
- clack5.outro(`Recall complete: ${results.length} ${pluralize3(results.length, "result")}.`);
6125
+ clack6.outro(`Recall complete: ${results.length} ${pluralize4(results.length, "result")}.`);
5623
6126
  } catch (error) {
5624
6127
  process.exitCode = 1;
5625
- clack5.log.error(formatUnknownError6(error));
5626
- clack5.outro(ui.error("Recall failed"));
6128
+ clack6.log.error(formatUnknownError8(error));
6129
+ clack6.outro(ui.error("Recall failed"));
5627
6130
  } finally {
5628
6131
  await db?.close();
5629
6132
  }
@@ -5710,10 +6213,10 @@ function formatProvenance(projected) {
5710
6213
  ].filter((value) => value !== void 0);
5711
6214
  return parts.join(" ");
5712
6215
  }
5713
- function pluralize3(value, singular, plural) {
6216
+ function pluralize4(value, singular, plural) {
5714
6217
  return value === 1 ? singular : plural ?? `${singular}s`;
5715
6218
  }
5716
- function formatUnknownError6(error) {
6219
+ function formatUnknownError8(error) {
5717
6220
  return error instanceof Error ? error.message : String(error);
5718
6221
  }
5719
6222
 
@@ -5721,13 +6224,13 @@ function formatUnknownError6(error) {
5721
6224
  import { InvalidArgumentError as InvalidArgumentError5, Option as Option4 } from "commander";
5722
6225
 
5723
6226
  // src/app/scenarios/claim-keys/runtime.ts
5724
- import { randomUUID as randomUUID10 } from "crypto";
6227
+ import { randomUUID as randomUUID11 } from "crypto";
5725
6228
  import { mkdir as mkdir2, writeFile } from "fs/promises";
5726
- import path19 from "path";
6229
+ import path21 from "path";
5727
6230
  import { getModel as getModel2 } from "@mariozechner/pi-ai";
5728
6231
 
5729
6232
  // src/adapters/db/surgeon-run-log.ts
5730
- import { randomUUID } from "crypto";
6233
+ import { randomUUID as randomUUID2 } from "crypto";
5731
6234
 
5732
6235
  // src/core/surgeon/domain/pass-types.ts
5733
6236
  var SURGEON_PASS_TYPES = ["claim_key_quality", "proposal_resolution", "retirement", "supersession"];
@@ -5766,7 +6269,7 @@ function resolveSurgeonProposalApplyTarget(proposal) {
5766
6269
  var SURGEON_RUN_STATUSES = ["running", "completed", "failed", "aborted", "budget_exhausted", "cost_capped", "no_work", "stalled"];
5767
6270
  var SURGEON_PROPOSAL_REVIEW_STATUSES = ["open", "applied", "rejected"];
5768
6271
  async function createSurgeonRun(executor, run) {
5769
- const id = randomUUID();
6272
+ const id = randomUUID2();
5770
6273
  const startedAt = normalizeTimestamp(run.startedAt) ?? (/* @__PURE__ */ new Date()).toISOString();
5771
6274
  await executor.execute({
5772
6275
  sql: `
@@ -5844,7 +6347,7 @@ async function logSurgeonAction(executor, action) {
5844
6347
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
5845
6348
  `,
5846
6349
  args: [
5847
- action.id.trim().length > 0 ? action.id.trim() : randomUUID(),
6350
+ action.id.trim().length > 0 ? action.id.trim() : randomUUID2(),
5848
6351
  action.runId.trim(),
5849
6352
  action.actionType,
5850
6353
  entryIds[0] ?? null,
@@ -5943,7 +6446,7 @@ async function logSurgeonProposal(executor, proposal) {
5943
6446
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5944
6447
  `,
5945
6448
  args: [
5946
- proposal.id.trim().length > 0 ? proposal.id.trim() : randomUUID(),
6449
+ proposal.id.trim().length > 0 ? proposal.id.trim() : randomUUID2(),
5947
6450
  proposal.runId.trim(),
5948
6451
  logicalIssue.groupId,
5949
6452
  logicalIssue.issueKind,
@@ -7209,9 +7712,9 @@ function createSurgeonPort(executor) {
7209
7712
  }
7210
7713
 
7211
7714
  // src/app/surgeon/service.ts
7212
- import { randomUUID as randomUUID8 } from "crypto";
7213
- import fs10 from "fs";
7214
- import path14 from "path";
7715
+ import { randomUUID as randomUUID9 } from "crypto";
7716
+ import fs11 from "fs";
7717
+ import path16 from "path";
7215
7718
  import { runAgentLoop } from "@mariozechner/pi-agent-core";
7216
7719
 
7217
7720
  // src/core/surgeon/domain/run-presets.ts
@@ -7269,7 +7772,7 @@ function normalizeUsageAmount(value) {
7269
7772
  }
7270
7773
 
7271
7774
  // src/app/surgeon/claim-key-quality.ts
7272
- import { randomUUID as randomUUID2 } from "crypto";
7775
+ import { randomUUID as randomUUID3 } from "crypto";
7273
7776
 
7274
7777
  // src/core/claim-key-slot-resonance.ts
7275
7778
  var FAMILY_GENERIC_TOKEN_MIN_COUNT = 3;
@@ -8445,7 +8948,7 @@ async function runClaimKeyQualityPass(options, deps) {
8445
8948
  }
8446
8949
  applyClaimKeyLifecycle(actual, lifecycle);
8447
8950
  await deps.port.logRunAction({
8448
- id: randomUUID2(),
8951
+ id: randomUUID3(),
8449
8952
  runId: options.runId,
8450
8953
  actionType: "update_entry",
8451
8954
  entryIds: [entryId],
@@ -8472,7 +8975,7 @@ async function runClaimKeyQualityPass(options, deps) {
8472
8975
  async function persistProposal(proposal, audit) {
8473
8976
  await deps.port.logRunProposal(proposal);
8474
8977
  await deps.port.logRunAction({
8475
- id: randomUUID2(),
8978
+ id: randomUUID3(),
8476
8979
  runId: options.runId,
8477
8980
  actionType: "flag_review",
8478
8981
  entryIds: proposal.entryIds,
@@ -9357,7 +9860,7 @@ function restoreClaimKeyLifecycle(entry, snapshot) {
9357
9860
  }
9358
9861
  function createProposal(input) {
9359
9862
  return {
9360
- id: randomUUID2(),
9863
+ id: randomUUID3(),
9361
9864
  ...input,
9362
9865
  entryIds: normalizeStringArray3(input.entryIds),
9363
9866
  currentClaimKeys: normalizeStringArray3(input.currentClaimKeys),
@@ -9689,9 +10192,9 @@ function createSupersessionReviewTracker(input) {
9689
10192
  progress = createEmptySupersessionProgress(input);
9690
10193
  entryToClusterKeys = /* @__PURE__ */ new Map();
9691
10194
  },
9692
- recordPage({ scope, claimKeyTotal, subjectTotal, clusters }) {
9693
- const normalizedClaimKeyRemaining = normalizeCount(claimKeyTotal);
9694
- const normalizedSubjectRemaining = normalizeCount(subjectTotal);
10195
+ recordPage({ scope, claimKeyRemaining, subjectRemaining, clusters }) {
10196
+ const normalizedClaimKeyRemaining = normalizeCount(claimKeyRemaining);
10197
+ const normalizedSubjectRemaining = normalizeCount(subjectRemaining);
9695
10198
  const nextClaimKeyViewed = new Set(progress.claimKeyViewedKeys);
9696
10199
  const nextSubjectViewed = new Set(progress.subjectViewedKeys);
9697
10200
  const nextEntryMap = cloneEntryClusterMap(entryToClusterKeys);
@@ -9873,7 +10376,7 @@ function normalizeOptionalCount(value) {
9873
10376
  }
9874
10377
 
9875
10378
  // src/app/surgeon/proposal-review.ts
9876
- import { randomUUID as randomUUID3 } from "crypto";
10379
+ import { randomUUID as randomUUID4 } from "crypto";
9877
10380
  async function loadActiveProposalEntries(proposal, getEntry2) {
9878
10381
  const activeEntries = [];
9879
10382
  const inactiveEntryIds = [];
@@ -9913,7 +10416,7 @@ async function applyProposalToEntries(input, deps) {
9913
10416
  }
9914
10417
  if (updatedEntryIds.length > 0) {
9915
10418
  await deps.logRunAction({
9916
- id: randomUUID3(),
10419
+ id: randomUUID4(),
9917
10420
  runId: input.actionRunId ?? input.proposal.runId,
9918
10421
  actionType: "update_entry",
9919
10422
  entryIds: updatedEntryIds,
@@ -9937,8 +10440,8 @@ async function applyProposalToEntries(input, deps) {
9937
10440
  }
9938
10441
 
9939
10442
  // src/app/surgeon/trace-logger.ts
9940
- import fs9 from "fs";
9941
- import path13 from "path";
10443
+ import fs10 from "fs";
10444
+ import path15 from "path";
9942
10445
  var TRACE_MAX_STRING_LENGTH = 320;
9943
10446
  var TRACE_MAX_ARRAY_ITEMS = 12;
9944
10447
  var TRACE_MAX_OBJECT_KEYS = 20;
@@ -10146,8 +10649,8 @@ function appendTrace(tracePath, payload, logger) {
10146
10649
  return;
10147
10650
  }
10148
10651
  try {
10149
- fs9.mkdirSync(path13.dirname(tracePath), { recursive: true });
10150
- fs9.appendFileSync(tracePath, `${JSON.stringify({ timestamp: (/* @__PURE__ */ new Date()).toISOString(), ...payload })}
10652
+ fs10.mkdirSync(path15.dirname(tracePath), { recursive: true });
10653
+ fs10.appendFileSync(tracePath, `${JSON.stringify({ timestamp: (/* @__PURE__ */ new Date()).toISOString(), ...payload })}
10151
10654
  `, "utf8");
10152
10655
  } catch (error) {
10153
10656
  logger.warn(`failed to write trace file ${tracePath}: ${formatError2(error)}`);
@@ -10557,7 +11060,7 @@ function getSurgeonSupersessionPassPrompt() {
10557
11060
  }
10558
11061
 
10559
11062
  // src/app/surgeon/tools/complete.ts
10560
- import { randomUUID as randomUUID4 } from "crypto";
11063
+ import { randomUUID as randomUUID5 } from "crypto";
10561
11064
  import { Type } from "@sinclair/typebox";
10562
11065
 
10563
11066
  // src/app/surgeon/tools/shared.ts
@@ -10616,7 +11119,7 @@ function createCompletePassTool(deps) {
10616
11119
  deps.completionGuards?.supersession.markAdjudicated([entryId]);
10617
11120
  }
10618
11121
  await deps.recordRunAction({
10619
- id: randomUUID4(),
11122
+ id: randomUUID5(),
10620
11123
  runId: deps.runId,
10621
11124
  actionType: "skip",
10622
11125
  entryIds: entryId ? [entryId] : [],
@@ -11207,7 +11710,7 @@ function normalizeOptionalString10(value) {
11207
11710
  }
11208
11711
 
11209
11712
  // src/app/surgeon/tools/supersession-claim.ts
11210
- import { randomUUID as randomUUID5 } from "crypto";
11713
+ import { randomUUID as randomUUID6 } from "crypto";
11211
11714
  import { Type as Type7 } from "@sinclair/typebox";
11212
11715
  var ASSIGN_CLAIM_KEY_SCHEMA = Type7.Object({
11213
11716
  entry_id: Type7.String({ minLength: 1 }),
@@ -11286,7 +11789,7 @@ function createAssignClaimKeyTool(deps) {
11286
11789
  if (updated) {
11287
11790
  deps.completionGuards?.supersession.markAdjudicated([entry.id]);
11288
11791
  await deps.recordRunAction({
11289
- id: randomUUID5(),
11792
+ id: randomUUID6(),
11290
11793
  runId: deps.runId,
11291
11794
  actionType: "update_entry",
11292
11795
  entryIds: [entry.id],
@@ -11307,7 +11810,7 @@ function createAssignClaimKeyTool(deps) {
11307
11810
  }
11308
11811
 
11309
11812
  // src/app/surgeon/tools/supersession-link.ts
11310
- import { randomUUID as randomUUID6 } from "crypto";
11813
+ import { randomUUID as randomUUID7 } from "crypto";
11311
11814
  import { Type as Type8 } from "@sinclair/typebox";
11312
11815
  var LINK_SUPERSESSION_SCHEMA = Type8.Object({
11313
11816
  old_entry_id: Type8.String({ minLength: 1, description: "Entry being superseded." }),
@@ -11418,7 +11921,7 @@ function createLinkSupersessionTool(deps) {
11418
11921
  }
11419
11922
  deps.completionGuards?.supersession.markAdjudicated([oldEntryId, newEntryId]);
11420
11923
  await deps.recordRunAction({
11421
- id: randomUUID6(),
11924
+ id: randomUUID7(),
11422
11925
  runId: deps.runId,
11423
11926
  actionType: "resolve_conflict",
11424
11927
  entryIds: [oldEntryId, newEntryId],
@@ -11519,10 +12022,13 @@ function createQuerySupersessionCandidatesTool(deps) {
11519
12022
  const subjectClusterCount = scope === "claim_key" ? progress?.subjectClustersRemaining ?? counts.subjectCount : pendingSubjectClusters.length;
11520
12023
  const allClusters = scope === "claim_key" ? pendingClaimKeyClusters : scope === "subject" ? pendingSubjectClusters : [...pendingClaimKeyClusters, ...pendingSubjectClusters];
11521
12024
  const clusters = allClusters.slice(offset, offset + limit);
12025
+ const remainingClusters = allClusters.slice(Math.min(allClusters.length, offset + clusters.length));
12026
+ const claimKeyRemaining = scope === "subject" ? claimKeyClusterCount : countClustersByGrouping(remainingClusters, "claim_key");
12027
+ const subjectRemaining = scope === "claim_key" ? subjectClusterCount : countClustersByGrouping(remainingClusters, "subject");
11522
12028
  deps.completionGuards?.supersession.recordPage({
11523
12029
  scope,
11524
- claimKeyTotal: claimKeyClusterCount,
11525
- subjectTotal: subjectClusterCount,
12030
+ claimKeyRemaining,
12031
+ subjectRemaining,
11526
12032
  clusters
11527
12033
  });
11528
12034
  if (clusters.length === 0) {
@@ -11590,9 +12096,18 @@ function normalizeOptionalString11(value) {
11590
12096
  const normalized = value?.trim();
11591
12097
  return normalized ? normalized : void 0;
11592
12098
  }
12099
+ function countClustersByGrouping(clusters, groupedBy) {
12100
+ let count = 0;
12101
+ for (const cluster of clusters) {
12102
+ if (cluster.groupedBy === groupedBy) {
12103
+ count += 1;
12104
+ }
12105
+ }
12106
+ return count;
12107
+ }
11593
12108
 
11594
12109
  // src/app/surgeon/tools/supersession-validity.ts
11595
- import { randomUUID as randomUUID7 } from "crypto";
12110
+ import { randomUUID as randomUUID8 } from "crypto";
11596
12111
  import { Type as Type10 } from "@sinclair/typebox";
11597
12112
  var SET_VALIDITY_SCHEMA = Type10.Object({
11598
12113
  entry_id: Type10.String({ minLength: 1 }),
@@ -11701,7 +12216,7 @@ function createSetValidityTool(deps) {
11701
12216
  if (updated) {
11702
12217
  deps.completionGuards?.supersession.markAdjudicated([entry.id]);
11703
12218
  await deps.recordRunAction({
11704
- id: randomUUID7(),
12219
+ id: randomUUID8(),
11705
12220
  runId: deps.runId,
11706
12221
  actionType: "update_entry",
11707
12222
  entryIds: [entry.id],
@@ -12561,7 +13076,7 @@ async function runSurgeon(options, deps) {
12561
13076
  const entryIds = extractEntryIds(context.args);
12562
13077
  const reasoning = extractActionReasoning(context.assistantMessage, context.args, context.result.details, context.toolCall.name);
12563
13078
  const action = {
12564
- id: randomUUID8(),
13079
+ id: randomUUID9(),
12565
13080
  runId,
12566
13081
  actionType,
12567
13082
  entryIds,
@@ -12684,8 +13199,8 @@ function resolveTracePath(tracePath, passType, runId) {
12684
13199
  return void 0;
12685
13200
  }
12686
13201
  try {
12687
- if (fs10.statSync(tracePath).isDirectory()) {
12688
- return path14.join(tracePath, `surgeon-${passType}-${runId}.jsonl`);
13202
+ if (fs11.statSync(tracePath).isDirectory()) {
13203
+ return path16.join(tracePath, `surgeon-${passType}-${runId}.jsonl`);
12689
13204
  }
12690
13205
  } catch {
12691
13206
  return tracePath;
@@ -12901,7 +13416,7 @@ async function runProposalResolutionPass(input, deps) {
12901
13416
  }
12902
13417
  if (!input.apply) {
12903
13418
  await deps.recordRunAction({
12904
- id: randomUUID8(),
13419
+ id: randomUUID9(),
12905
13420
  runId: input.runId,
12906
13421
  actionType: "update_entry",
12907
13422
  entryIds: proposalEntryIds,
@@ -13687,7 +14202,7 @@ function isFixtureError(value) {
13687
14202
 
13688
14203
  // src/app/scenarios/claim-keys/fixture-loader.ts
13689
14204
  import { readFile } from "fs/promises";
13690
- import path16 from "path";
14205
+ import path18 from "path";
13691
14206
 
13692
14207
  // src/app/scenarios/claim-keys/validation/shared.ts
13693
14208
  var SUPPORTED_PROPOSAL_SCOPES = ["single_entry", "cluster"];
@@ -14017,12 +14532,12 @@ function readRequiredTrue(value, label, filePath) {
14017
14532
 
14018
14533
  // src/app/scenarios/claim-keys/validation/scenario-root.ts
14019
14534
  import { existsSync } from "fs";
14020
- import path15 from "path";
14535
+ import path17 from "path";
14021
14536
  import { fileURLToPath as fileURLToPath2 } from "url";
14022
14537
  var SCENARIO_ROOT_SEGMENTS = ["tests", "scenarios", "claim-keys"];
14023
14538
  function getDefaultClaimKeyScenarioRoot(options = {}) {
14024
- const moduleDirectory = path15.dirname(fileURLToPath2(options.moduleUrl ?? import.meta.url));
14025
- const startDirectories = Array.from(/* @__PURE__ */ new Set([path15.resolve(options.cwd ?? process.cwd()), moduleDirectory]));
14539
+ const moduleDirectory = path17.dirname(fileURLToPath2(options.moduleUrl ?? import.meta.url));
14540
+ const startDirectories = Array.from(/* @__PURE__ */ new Set([path17.resolve(options.cwd ?? process.cwd()), moduleDirectory]));
14026
14541
  for (const startDirectory of startDirectories) {
14027
14542
  const discovered = findScenarioRootFrom(startDirectory);
14028
14543
  if (discovered) {
@@ -14044,24 +14559,24 @@ function readOptionalRelativeFixturePath(value, label, filePath, rootDir) {
14044
14559
  return readRelativeFixturePath(value, label, filePath, rootDir);
14045
14560
  }
14046
14561
  function normalizeFixturePath(relativePath, rootDir, filePath, label) {
14047
- if (path15.isAbsolute(relativePath)) {
14562
+ if (path17.isAbsolute(relativePath)) {
14048
14563
  throw new Error(`Invalid scenario ${filePath}: ${label} must be relative to the scenario root.`);
14049
14564
  }
14050
- const resolved = path15.resolve(rootDir, relativePath);
14051
- const relative = path15.relative(rootDir, resolved);
14052
- if (relative.startsWith("..") || path15.isAbsolute(relative)) {
14565
+ const resolved = path17.resolve(rootDir, relativePath);
14566
+ const relative = path17.relative(rootDir, resolved);
14567
+ if (relative.startsWith("..") || path17.isAbsolute(relative)) {
14053
14568
  throw new Error(`Invalid scenario ${filePath}: ${label} must stay inside the scenario root.`);
14054
14569
  }
14055
- return relative.split(path15.sep).join("/");
14570
+ return relative.split(path17.sep).join("/");
14056
14571
  }
14057
14572
  function findScenarioRootFrom(startDirectory) {
14058
- let currentDirectory = path15.resolve(startDirectory);
14573
+ let currentDirectory = path17.resolve(startDirectory);
14059
14574
  while (true) {
14060
- const candidate = path15.join(currentDirectory, ...SCENARIO_ROOT_SEGMENTS);
14575
+ const candidate = path17.join(currentDirectory, ...SCENARIO_ROOT_SEGMENTS);
14061
14576
  if (existsSync(candidate)) {
14062
14577
  return candidate;
14063
14578
  }
14064
- const parentDirectory = path15.dirname(currentDirectory);
14579
+ const parentDirectory = path17.dirname(currentDirectory);
14065
14580
  if (parentDirectory === currentDirectory) {
14066
14581
  return void 0;
14067
14582
  }
@@ -14404,7 +14919,7 @@ async function loadSeedFixtureEntries(rootDir, relativePath) {
14404
14919
  if (!relativePath) {
14405
14920
  return null;
14406
14921
  }
14407
- const parsed = await readJsonFile(path16.join(rootDir, relativePath));
14922
+ const parsed = await readJsonFile(path18.join(rootDir, relativePath));
14408
14923
  const seedEntries = readSeedEntries(parsed, relativePath);
14409
14924
  if (!seedEntries) {
14410
14925
  throw new Error(`Seed fixture ${relativePath} must contain an array.`);
@@ -14467,7 +14982,7 @@ async function readArrayFixture(rootDir, relativePath) {
14467
14982
  if (!relativePath) {
14468
14983
  return null;
14469
14984
  }
14470
- const parsed = await readJsonFile(path16.join(rootDir, relativePath));
14985
+ const parsed = await readJsonFile(path18.join(rootDir, relativePath));
14471
14986
  if (!Array.isArray(parsed)) {
14472
14987
  throw new Error(`Fixture file ${relativePath} must contain a JSON array.`);
14473
14988
  }
@@ -14521,7 +15036,7 @@ function readExtractionImportance(value, label, filePath) {
14521
15036
 
14522
15037
  // src/app/scenarios/claim-keys/load-scenarios.ts
14523
15038
  import { readdir, readFile as readFile2 } from "fs/promises";
14524
- import path17 from "path";
15039
+ import path19 from "path";
14525
15040
 
14526
15041
  // src/app/scenarios/claim-keys/validation/expectations.ts
14527
15042
  var EXPECTATION_KEYS = /* @__PURE__ */ new Set(["warnings", "rows", "rowCount", "proposals", "storeResult", "surgeonSummary"]);
@@ -14783,13 +15298,13 @@ function validateClaimKeyScenario(input, filePath, rootDir = getDefaultClaimKeyS
14783
15298
  async function discoverScenarioFiles(rootDir) {
14784
15299
  const files = [];
14785
15300
  for (const kind of SUPPORTED_KINDS2) {
14786
- const directory = path17.join(rootDir, kind);
15301
+ const directory = path19.join(rootDir, kind);
14787
15302
  const entries = await readdir(directory, { withFileTypes: true });
14788
15303
  for (const entry of entries) {
14789
15304
  if (!entry.isFile() || !entry.name.endsWith(".json")) {
14790
15305
  continue;
14791
15306
  }
14792
- files.push(path17.join(directory, entry.name));
15307
+ files.push(path19.join(directory, entry.name));
14793
15308
  }
14794
15309
  }
14795
15310
  return files.sort();
@@ -14807,11 +15322,11 @@ async function readScenarioJsonFile(filePath) {
14807
15322
 
14808
15323
  // src/app/scenarios/claim-keys/sandbox.ts
14809
15324
  import { mkdir, rm } from "fs/promises";
14810
- import path18 from "path";
15325
+ import path20 from "path";
14811
15326
  var SANDBOX_DB_FILENAME = "knowledge.db";
14812
15327
  async function createClaimKeyScenarioSandbox(root) {
14813
- const resolvedRoot = path18.resolve(root);
14814
- const dbPath = path18.join(resolvedRoot, SANDBOX_DB_FILENAME);
15328
+ const resolvedRoot = path20.resolve(root);
15329
+ const dbPath = path20.join(resolvedRoot, SANDBOX_DB_FILENAME);
14815
15330
  await mkdir(resolvedRoot, { recursive: true });
14816
15331
  await removeDatabaseFiles(dbPath);
14817
15332
  const database = await createDatabase(dbPath);
@@ -14833,7 +15348,7 @@ async function removeDatabaseFiles(dbPath) {
14833
15348
  }
14834
15349
 
14835
15350
  // src/app/scenarios/claim-keys/seed.ts
14836
- import { randomUUID as randomUUID9 } from "crypto";
15351
+ import { randomUUID as randomUUID10 } from "crypto";
14837
15352
  var DEFAULT_SCENARIO_CREATED_AT = "2026-04-01T10:00:00.000Z";
14838
15353
  function buildClaimKeyScenarioSeedEntry(seedEntry) {
14839
15354
  const seedClaimKey = normalizeOptionalString12(seedEntry.claim_key);
@@ -14843,7 +15358,7 @@ function buildClaimKeyScenarioSeedEntry(seedEntry) {
14843
15358
  const createdAt = seedEntry.created_at?.trim() || validatedInput.created_at || DEFAULT_SCENARIO_CREATED_AT;
14844
15359
  const updatedAt = seedEntry.updated_at?.trim() || createdAt;
14845
15360
  return {
14846
- id: seedEntry.id?.trim() || randomUUID9(),
15361
+ id: seedEntry.id?.trim() || randomUUID10(),
14847
15362
  type: validatedInput.type,
14848
15363
  subject: validatedInput.subject,
14849
15364
  content: validatedInput.content,
@@ -15002,7 +15517,7 @@ async function listClaimKeyScenariosRuntime(options = {}) {
15002
15517
  }
15003
15518
  async function runClaimKeyScenariosRuntime(options = {}) {
15004
15519
  const runId = buildRunId();
15005
- const artifactRoot = path19.resolve(DEFAULT_ARTIFACT_ROOT, runId);
15520
+ const artifactRoot = path21.resolve(DEFAULT_ARTIFACT_ROOT, runId);
15006
15521
  await mkdir2(artifactRoot, { recursive: true });
15007
15522
  let scenarios;
15008
15523
  try {
@@ -15054,11 +15569,11 @@ function filterClaimKeyScenarios(scenarios, options) {
15054
15569
  }
15055
15570
  async function runOneClaimKeyScenario(scenario, options) {
15056
15571
  const startedAt = Date.now();
15057
- const scenarioArtifactRoot = path19.join(options.artifactRoot, scenario.id);
15058
- const sandboxRoot = path19.join(scenarioArtifactRoot, "sandbox");
15572
+ const scenarioArtifactRoot = path21.join(options.artifactRoot, scenario.id);
15573
+ const sandboxRoot = path21.join(scenarioArtifactRoot, "sandbox");
15059
15574
  const warnings = [];
15060
15575
  await mkdir2(scenarioArtifactRoot, { recursive: true });
15061
- await writeJson(path19.join(scenarioArtifactRoot, "scenario.json"), scenario);
15576
+ await writeJson(path21.join(scenarioArtifactRoot, "scenario.json"), scenario);
15062
15577
  let actual = {
15063
15578
  warnings: [],
15064
15579
  rows: [],
@@ -15171,7 +15686,7 @@ async function runIngestScenario(scenario, database, warnings, rootDir) {
15171
15686
  throw new Error(`Scenario ${scenario.id} is missing extraction fixture responses.`);
15172
15687
  }
15173
15688
  const result = await ingestPath(
15174
- path19.join(rootDir, scenario.input.transcriptFile),
15689
+ path21.join(rootDir, scenario.input.transcriptFile),
15175
15690
  {
15176
15691
  files: localTranscriptFiles,
15177
15692
  transcript: openClawTranscriptParser,
@@ -15313,20 +15828,20 @@ async function loadLatestSurgeonProposals(database, runId) {
15313
15828
  }));
15314
15829
  }
15315
15830
  async function writeScenarioArtifacts(scenarioArtifactRoot, actual, assertionResults, diffSummary) {
15316
- await writeJson(path19.join(scenarioArtifactRoot, "actual.json"), actual);
15317
- await writeJson(path19.join(scenarioArtifactRoot, "diff.json"), {
15831
+ await writeJson(path21.join(scenarioArtifactRoot, "actual.json"), actual);
15832
+ await writeJson(path21.join(scenarioArtifactRoot, "diff.json"), {
15318
15833
  assertions: assertionResults,
15319
15834
  diffSummary
15320
15835
  });
15321
- await writeJson(path19.join(scenarioArtifactRoot, "warnings.json"), actual.warnings);
15836
+ await writeJson(path21.join(scenarioArtifactRoot, "warnings.json"), actual.warnings);
15322
15837
  if (actual.storeResult) {
15323
- await writeJson(path19.join(scenarioArtifactRoot, "store-result.json"), actual.storeResult);
15838
+ await writeJson(path21.join(scenarioArtifactRoot, "store-result.json"), actual.storeResult);
15324
15839
  }
15325
15840
  if (actual.surgeonSummary) {
15326
- await writeJson(path19.join(scenarioArtifactRoot, "surgeon-summary.json"), actual.surgeonSummary);
15841
+ await writeJson(path21.join(scenarioArtifactRoot, "surgeon-summary.json"), actual.surgeonSummary);
15327
15842
  }
15328
15843
  if (actual.proposals.length > 0) {
15329
- await writeJson(path19.join(scenarioArtifactRoot, "proposals.json"), actual.proposals);
15844
+ await writeJson(path21.join(scenarioArtifactRoot, "proposals.json"), actual.proposals);
15330
15845
  }
15331
15846
  }
15332
15847
  async function writeJson(filePath, value) {
@@ -15334,7 +15849,7 @@ async function writeJson(filePath, value) {
15334
15849
  `, "utf8");
15335
15850
  }
15336
15851
  function buildRunId() {
15337
- return `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/gu, "-")}-${randomUUID10().slice(0, 8)}`;
15852
+ return `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/gu, "-")}-${randomUUID11().slice(0, 8)}`;
15338
15853
  }
15339
15854
  function toConfigurationError(error) {
15340
15855
  if (error instanceof ClaimKeyScenarioConfigurationError) {
@@ -15361,7 +15876,7 @@ function registerScenariosCommand(program2, deps = {}) {
15361
15876
  ` : renderScenarioList(scenarios));
15362
15877
  } catch (error) {
15363
15878
  process.exitCode = error instanceof ClaimKeyScenarioConfigurationError ? 2 : 1;
15364
- stderr.write(`Scenario list failed: ${formatUnknownError7(error)}
15879
+ stderr.write(`Scenario list failed: ${formatUnknownError9(error)}
15365
15880
  `);
15366
15881
  }
15367
15882
  });
@@ -15384,7 +15899,7 @@ function registerScenariosCommand(program2, deps = {}) {
15384
15899
  ` : renderScenarioRunSummary(summary));
15385
15900
  } catch (error) {
15386
15901
  process.exitCode = error instanceof ClaimKeyScenarioConfigurationError ? 2 : 1;
15387
- stderr.write(`Scenario run failed: ${formatUnknownError7(error)}
15902
+ stderr.write(`Scenario run failed: ${formatUnknownError9(error)}
15388
15903
  `);
15389
15904
  }
15390
15905
  });
@@ -15449,7 +15964,7 @@ function parseScenarioKind(value) {
15449
15964
  }
15450
15965
  return normalized;
15451
15966
  }
15452
- function formatUnknownError7(error) {
15967
+ function formatUnknownError9(error) {
15453
15968
  return error instanceof Error ? error.message : String(error);
15454
15969
  }
15455
15970
 
@@ -15458,7 +15973,7 @@ import { InvalidArgumentError as InvalidArgumentError6, Option as Option5 } from
15458
15973
 
15459
15974
  // src/app/surgeon/runtime.ts
15460
15975
  import { copyFile, mkdir as mkdir3 } from "fs/promises";
15461
- import path20 from "path";
15976
+ import path22 from "path";
15462
15977
  import { fileURLToPath as fileURLToPath3 } from "url";
15463
15978
  import { getModel as getModel3 } from "@mariozechner/pi-ai";
15464
15979
  var DEFAULT_SURGEON_PROVIDER = "openai";
@@ -15703,7 +16218,7 @@ async function backupDatabaseFile(dbPath) {
15703
16218
  }
15704
16219
  const sourcePath = resolveFilesystemPath(dbPath);
15705
16220
  const backupPath = `${sourcePath}.surgeon-backup-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-").replaceAll(".", "-")}`;
15706
- await mkdir3(path20.dirname(backupPath), { recursive: true });
16221
+ await mkdir3(path22.dirname(backupPath), { recursive: true });
15707
16222
  await copyFile(sourcePath, backupPath);
15708
16223
  await copySidecarIfPresent(`${sourcePath}-wal`, `${backupPath}-wal`);
15709
16224
  await copySidecarIfPresent(`${sourcePath}-shm`, `${backupPath}-shm`);
@@ -15721,12 +16236,12 @@ async function copySidecarIfPresent(sourcePath, targetPath) {
15721
16236
  }
15722
16237
  function resolveFilesystemPath(value) {
15723
16238
  if (!value.startsWith("file:")) {
15724
- return path20.resolve(value);
16239
+ return path22.resolve(value);
15725
16240
  }
15726
16241
  try {
15727
16242
  return fileURLToPath3(value);
15728
16243
  } catch {
15729
- return path20.resolve(value.slice("file:".length));
16244
+ return path22.resolve(value.slice("file:".length));
15730
16245
  }
15731
16246
  }
15732
16247
  function isMissingFileError2(error) {
@@ -15778,7 +16293,7 @@ function registerSurgeonCommand(program2) {
15778
16293
  ` : renderRunResult(result, commandInput.apply));
15779
16294
  } catch (error) {
15780
16295
  process.exitCode = 1;
15781
- process.stderr.write(`Surgeon run failed: ${formatUnknownError8(error)}
16296
+ process.stderr.write(`Surgeon run failed: ${formatUnknownError10(error)}
15782
16297
  `);
15783
16298
  } finally {
15784
16299
  display?.dispose();
@@ -15793,7 +16308,7 @@ function registerSurgeonCommand(program2) {
15793
16308
  process.stdout.write(renderStatus(result));
15794
16309
  } catch (error) {
15795
16310
  process.exitCode = 1;
15796
- process.stderr.write(`Failed to load surgeon status: ${formatUnknownError8(error)}
16311
+ process.stderr.write(`Failed to load surgeon status: ${formatUnknownError10(error)}
15797
16312
  `);
15798
16313
  }
15799
16314
  });
@@ -15807,7 +16322,7 @@ function registerSurgeonCommand(program2) {
15807
16322
  process.stdout.write(renderHistory(runs, limit));
15808
16323
  } catch (error) {
15809
16324
  process.exitCode = 1;
15810
- process.stderr.write(`Failed to load surgeon history: ${formatUnknownError8(error)}
16325
+ process.stderr.write(`Failed to load surgeon history: ${formatUnknownError10(error)}
15811
16326
  `);
15812
16327
  }
15813
16328
  });
@@ -15834,7 +16349,7 @@ function registerSurgeonCommand(program2) {
15834
16349
  );
15835
16350
  } catch (error) {
15836
16351
  process.exitCode = 1;
15837
- process.stderr.write(`Failed to load surgeon backlog: ${formatUnknownError8(error)}
16352
+ process.stderr.write(`Failed to load surgeon backlog: ${formatUnknownError10(error)}
15838
16353
  `);
15839
16354
  }
15840
16355
  });
@@ -15847,7 +16362,7 @@ function registerSurgeonCommand(program2) {
15847
16362
  process.stdout.write(renderActions(runId, actions));
15848
16363
  } catch (error) {
15849
16364
  process.exitCode = 1;
15850
- process.stderr.write(`Failed to load surgeon actions: ${formatUnknownError8(error)}
16365
+ process.stderr.write(`Failed to load surgeon actions: ${formatUnknownError10(error)}
15851
16366
  `);
15852
16367
  }
15853
16368
  });
@@ -15860,7 +16375,7 @@ function registerSurgeonCommand(program2) {
15860
16375
  process.stdout.write(renderProposals(runId, proposals));
15861
16376
  } catch (error) {
15862
16377
  process.exitCode = 1;
15863
- process.stderr.write(`Failed to load surgeon proposals: ${formatUnknownError8(error)}
16378
+ process.stderr.write(`Failed to load surgeon proposals: ${formatUnknownError10(error)}
15864
16379
  `);
15865
16380
  }
15866
16381
  });
@@ -15879,7 +16394,7 @@ function registerSurgeonCommand(program2) {
15879
16394
  process.stdout.write(renderProposalReviewResult(result));
15880
16395
  } catch (error) {
15881
16396
  process.exitCode = 1;
15882
- process.stderr.write(`Failed to review surgeon proposal: ${formatUnknownError8(error)}
16397
+ process.stderr.write(`Failed to review surgeon proposal: ${formatUnknownError10(error)}
15883
16398
  `);
15884
16399
  }
15885
16400
  });
@@ -16004,7 +16519,7 @@ function aggregatePasses(passes) {
16004
16519
  return order.map((passType) => aggregates.get(passType)).filter((value) => value !== void 0);
16005
16520
  }
16006
16521
  function formatPassAggregateLine(aggregate) {
16007
- const metricParts = [`${aggregate.runs} ${pluralize4(aggregate.runs, "pass")}`, `${aggregate.actionsTaken} ${pluralize4(aggregate.actionsTaken, "action")}`];
16522
+ const metricParts = [`${aggregate.runs} ${pluralize5(aggregate.runs, "pass")}`, `${aggregate.actionsTaken} ${pluralize5(aggregate.actionsTaken, "action")}`];
16008
16523
  if (aggregate.entriesRetired > 0) {
16009
16524
  metricParts.push(`${aggregate.entriesRetired} retired`);
16010
16525
  }
@@ -16022,7 +16537,7 @@ function extractPassSummarySnippet(summary) {
16022
16537
  const candidate = lines.find((line) => !line.endsWith(":") && !line.startsWith("- ")) ?? lines.find((line) => line.startsWith("- "))?.slice(2) ?? lines[0] ?? null;
16023
16538
  return candidate ? truncateDisplayText(candidate, 100) : null;
16024
16539
  }
16025
- function pluralize4(count, singular, plural) {
16540
+ function pluralize5(count, singular, plural) {
16026
16541
  if (count === 1) {
16027
16542
  return singular;
16028
16543
  }
@@ -16588,7 +17103,7 @@ function formatElapsed(elapsedMs2) {
16588
17103
  function formatUsd2(value) {
16589
17104
  return `$${value.toFixed(4)}`;
16590
17105
  }
16591
- function formatUnknownError8(error) {
17106
+ function formatUnknownError10(error) {
16592
17107
  return error instanceof Error ? error.message : String(error);
16593
17108
  }
16594
17109
  function normalizeOptionalBudget(value) {
@@ -16599,15 +17114,15 @@ function formatOptionalCount(value) {
16599
17114
  }
16600
17115
  function formatPassContextSummary(event) {
16601
17116
  const activeEntryCount = formatOptionalCount(event.workingSetSize);
16602
- const activeEntriesSummary = `${activeEntryCount} active ${pluralize4(activeEntryCount, "entry", "entries")}`;
17117
+ const activeEntriesSummary = `${activeEntryCount} active ${pluralize5(activeEntryCount, "entry", "entries")}`;
16603
17118
  if (event.passType === "proposal_resolution") {
16604
17119
  const eligibleProposalCount = formatOptionalCount(event.eligibleProposalBacklogCount);
16605
- return `${activeEntriesSummary} | Proposal backlog: ${eligibleProposalCount} eligible ${pluralize4(eligibleProposalCount, "proposal")}`;
17120
+ return `${activeEntriesSummary} | Proposal backlog: ${eligibleProposalCount} eligible ${pluralize5(eligibleProposalCount, "proposal")}`;
16606
17121
  }
16607
17122
  if (event.passType === "supersession") {
16608
17123
  const claimKeyClusterCount = formatOptionalCount(event.supersessionClaimKeyCount);
16609
17124
  const subjectClusterCount = formatOptionalCount(event.supersessionSubjectCount);
16610
- return `${activeEntriesSummary} | Supersession remaining: ${claimKeyClusterCount} claim_key ${pluralize4(claimKeyClusterCount, "cluster")}, ${subjectClusterCount} subject ${pluralize4(subjectClusterCount, "cluster")}`;
17125
+ return `${activeEntriesSummary} | Supersession remaining: ${claimKeyClusterCount} claim_key ${pluralize5(claimKeyClusterCount, "cluster")}, ${subjectClusterCount} subject ${pluralize5(subjectClusterCount, "cluster")}`;
16611
17126
  }
16612
17127
  if (event.passType === "retirement") {
16613
17128
  const actionableCount = formatOptionalCount(event.retirementAvailableActionableCount);
@@ -16693,7 +17208,7 @@ function registerTraceCommand(program2) {
16693
17208
  process.stdout.write(options.json === true ? renderTraceJson(trace) : renderTrace(trace));
16694
17209
  } catch (error) {
16695
17210
  process.exitCode = 1;
16696
- process.stderr.write(`Failed to load trace: ${formatUnknownError9(error)}
17211
+ process.stderr.write(`Failed to load trace: ${formatUnknownError11(error)}
16697
17212
  `);
16698
17213
  }
16699
17214
  });
@@ -16851,7 +17366,7 @@ function truncate2(value, maxChars) {
16851
17366
  }
16852
17367
  return `${value.slice(0, maxChars - 3).trimEnd()}...`;
16853
17368
  }
16854
- function formatUnknownError9(error) {
17369
+ function formatUnknownError11(error) {
16855
17370
  return error instanceof Error ? error.message : String(error);
16856
17371
  }
16857
17372