@productbrain/mcp 0.0.1-beta.2364 → 0.0.1-beta.2378

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.
@@ -805,33 +805,18 @@ async function populateHubCache() {
805
805
  function invalidateHubCacheForScope() {
806
806
  hubCache.delete(cacheScope());
807
807
  }
808
- function computeLinkConfidence(candidate, sourceName, sourceDescription, sourceCollection, candidateCollection, hubSlugs) {
809
- const text = `${sourceName} ${sourceDescription}`.toLowerCase();
810
- const candidateName = candidate.name.toLowerCase();
811
- let score = 0;
812
- const reasons = [];
813
- if (text.includes(candidateName) && candidateName.length > 3) {
814
- score += 40;
815
- reasons.push("name match");
816
- }
817
- const candidateWords = candidateName.split(/\s+/).filter((w) => w.length > 3);
818
- const matchingWords = candidateWords.filter((w) => text.includes(w));
819
- const wordScore = matchingWords.length / Math.max(candidateWords.length, 1) * 30;
820
- score += wordScore;
821
- if (matchingWords.length > 0) {
822
- reasons.push(`word overlap (${matchingWords.slice(0, 3).join(", ")})`);
823
- }
824
- if (hubSlugs.has(candidateCollection)) {
825
- score += 15;
826
- reasons.push("hub collection");
827
- }
828
- if (candidateCollection !== sourceCollection) {
829
- score += 10;
830
- reasons.push("cross-collection");
831
- }
832
- const finalScore = Math.min(score, 100);
833
- const reason = reasons.length > 0 ? reasons.join(" + ") : "low relevance";
834
- return { score: finalScore, reason };
808
+ function buildScoreLinkCandidatesJob(candidates, sourceName, sourceDescription, sourceCollection) {
809
+ return {
810
+ candidates: candidates.map((c) => ({ name: c.name, collectionId: c.collectionId })),
811
+ sourceName,
812
+ sourceDescription,
813
+ sourceCollection
814
+ };
815
+ }
816
+ var LINK_SCORE_CHUNK_MAX_ENTRIES = 10;
817
+ var LINK_SCORE_CHUNK_MAX_BYTES = 7e5;
818
+ function estimateJobBytes(job) {
819
+ return JSON.stringify(job).length;
835
820
  }
836
821
  function inferRelationType(_sourceCollection, _targetCollection, profile) {
837
822
  const type = profile.recommendedRelationTypes[0] ?? "related_to";
@@ -1727,15 +1712,40 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
1727
1712
  ]);
1728
1713
  const collMap = /* @__PURE__ */ new Map();
1729
1714
  for (const c of allCollections) collMap.set(c._id, c.slug);
1730
- const candidates = (searchResults ?? []).filter((r) => r.entryId !== finalEntryId && r._id !== internalId).map((r) => {
1731
- const conf = computeLinkConfidence(r, name, description, resolvedCollection, collMap.get(r.collectionId) ?? "unknown", hubSlugs);
1715
+ const candidatesFiltered = (searchResults ?? []).filter((r) => r.entryId !== finalEntryId && r._id !== internalId).map((r) => ({
1716
+ name: r.name,
1717
+ entryId: r.entryId,
1718
+ collectionId: r.collectionId,
1719
+ preview: extractPreview(r.data, 80)
1720
+ }));
1721
+ let scores = [];
1722
+ if (candidatesFiltered.length > 0) {
1723
+ try {
1724
+ const scoreJobResults = await kernelQuery("chain.scoreLinkCandidates", {
1725
+ collMap: Object.fromEntries(collMap),
1726
+ hubSlugs: [...hubSlugs],
1727
+ jobs: [
1728
+ buildScoreLinkCandidatesJob(
1729
+ candidatesFiltered.map((r) => ({ name: r.name, collectionId: r.collectionId })),
1730
+ name,
1731
+ description,
1732
+ resolvedCollection
1733
+ )
1734
+ ]
1735
+ });
1736
+ scores = scoreJobResults[0] ?? [];
1737
+ } catch {
1738
+ }
1739
+ }
1740
+ const candidates = scores.map((s) => {
1741
+ const r = candidatesFiltered[s.index];
1732
1742
  return {
1733
1743
  ...r,
1734
1744
  collSlug: collMap.get(r.collectionId) ?? "unknown",
1735
- confidence: conf.score,
1736
- confidenceReason: conf.reason
1745
+ confidence: s.score,
1746
+ confidenceReason: s.reason
1737
1747
  };
1738
- }).sort((a, b) => b.confidence - a.confidence);
1748
+ });
1739
1749
  let autoCount = 0;
1740
1750
  for (const c of candidates) {
1741
1751
  if (autoCount >= MAX_AUTO_LINKS) break;
@@ -1762,11 +1772,10 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
1762
1772
  const autoTargetIds = new Set(pendingRelations.map((r) => r.toEntryId));
1763
1773
  for (const c of candidates) {
1764
1774
  if (linksSuggested.length >= MAX_SUGGESTIONS) break;
1765
- if (autoTargetIds.has(c.entryId)) continue;
1775
+ if (c.entryId !== void 0 && autoTargetIds.has(c.entryId)) continue;
1766
1776
  if (c.confidence < 10) continue;
1767
- const preview2 = extractPreview(c.data, 80);
1768
1777
  const reason = c.confidence >= AUTO_LINK_CONFIDENCE_THRESHOLD ? "high relevance (already linked)" : `"${c.name.toLowerCase().split(/\s+/).filter((w) => `${name} ${description}`.toLowerCase().includes(w) && w.length > 3).slice(0, 2).join('", "')}" appears in content`;
1769
- linksSuggested.push({ entryId: c.entryId, name: c.name, collection: c.collSlug, reason, preview: preview2 });
1778
+ linksSuggested.push({ entryId: c.entryId, name: c.name, collection: c.collSlug, reason, preview: c.preview });
1770
1779
  }
1771
1780
  conflictCandidates = candidates.filter((c) => c.entryId && c.entryId !== finalEntryId).slice(0, 3).map((c) => ({ entryId: c.entryId, name: c.name, collection: c.collSlug }));
1772
1781
  }
@@ -2295,6 +2304,47 @@ async function handleBatchCapture(server, { entries, autoCommit, preview }) {
2295
2304
  for (const c of allCollections) collCache.set(c.slug, c);
2296
2305
  const collIdToSlug = /* @__PURE__ */ new Map();
2297
2306
  for (const c of allCollections) collIdToSlug.set(c._id, c.slug);
2307
+ async function flushPendingChunk(chunk) {
2308
+ if (chunk.length === 0) return;
2309
+ const jobsToScore = chunk.filter((p) => p.job !== null).map((p) => p.job);
2310
+ let scoreResultsByJob = [];
2311
+ if (jobsToScore.length > 0) {
2312
+ try {
2313
+ scoreResultsByJob = await kernelQuery("chain.scoreLinkCandidates", {
2314
+ collMap: Object.fromEntries(collIdToSlug),
2315
+ hubSlugs: [...hubSlugs],
2316
+ jobs: jobsToScore
2317
+ });
2318
+ } catch {
2319
+ scoreResultsByJob = jobsToScore.map(() => []);
2320
+ }
2321
+ }
2322
+ let scoreCursor = 0;
2323
+ for (const pending of chunk) {
2324
+ const scores = pending.job !== null ? scoreResultsByJob[scoreCursor++] ?? [] : [];
2325
+ try {
2326
+ await pending.finish(scores);
2327
+ } catch (error) {
2328
+ const msg = error instanceof Error ? error.message : String(error);
2329
+ results.push({
2330
+ entryIdx: pending.entryIdx,
2331
+ name: pending.entry.name,
2332
+ collection: pending.resolvedSlug,
2333
+ entryId: "",
2334
+ ok: false,
2335
+ autoLinks: 0,
2336
+ advisedLinks: 0,
2337
+ status: "draft",
2338
+ classifiedBy: pending.classifiedBy,
2339
+ confidence: pending.confidence,
2340
+ confidenceTier: pending.confidenceTier,
2341
+ error: msg
2342
+ });
2343
+ }
2344
+ }
2345
+ }
2346
+ let pendingFinish = [];
2347
+ let chunkBytes = 0;
2298
2348
  for (let entryIdx = 0; entryIdx < entries.length; entryIdx++) {
2299
2349
  const entry = entries[entryIdx];
2300
2350
  if (entryIdx > 0 && entryIdx % 5 === 0) {
@@ -2337,6 +2387,7 @@ async function handleBatchCapture(server, { entries, autoCommit, preview }) {
2337
2387
  const col = collCache.get(resolvedSlug);
2338
2388
  if (!col) {
2339
2389
  results.push({
2390
+ entryIdx,
2340
2391
  name: entry.name,
2341
2392
  collection: resolvedSlug,
2342
2393
  entryId: "",
@@ -2353,6 +2404,7 @@ async function handleBatchCapture(server, { entries, autoCommit, preview }) {
2353
2404
  }
2354
2405
  if (entry.entryId && resolvedSlug !== "business-rules" && resolvedSlug !== "standards") {
2355
2406
  results.push({
2407
+ entryIdx,
2356
2408
  name: entry.name,
2357
2409
  collection: resolvedSlug,
2358
2410
  entryId: "",
@@ -2467,149 +2519,189 @@ async function handleBatchCapture(server, { entries, autoCommit, preview }) {
2467
2519
  surface: "mcp_capture"
2468
2520
  });
2469
2521
  }
2470
- let autoLinkCount = 0;
2471
- let advisedLinkCount = 0;
2472
- let entryOverlapCount = 0;
2522
+ let candidatesFiltered = [];
2523
+ let job = null;
2473
2524
  const searchQuery = extractSearchTerms(entry.name, entry.description);
2474
2525
  if (searchQuery) {
2475
2526
  try {
2476
2527
  const searchResults = await kernelQuery("chain.searchEntries", { query: searchQuery });
2477
- const candidates = (searchResults ?? []).filter((r) => r.entryId !== finalEntryId).map((r) => {
2478
- const conf = computeLinkConfidence(r, entry.name, entry.description, resolvedSlug, collIdToSlug.get(r.collectionId) ?? "unknown", hubSlugs);
2479
- return { ...r, collSlug: collIdToSlug.get(r.collectionId) ?? "unknown", confidence: conf.score };
2480
- }).sort((a, b) => b.confidence - a.confidence);
2481
- entryOverlapCount = candidates.filter((c) => c.entryId).length;
2482
- const batchAutoLinks = [];
2483
- for (const c of candidates) {
2484
- if (batchAutoLinks.length >= MAX_AUTO_LINKS) break;
2485
- if (c.confidence < AUTO_LINK_CONFIDENCE_THRESHOLD) break;
2486
- if (!c.entryId) continue;
2487
- const { type: relationType } = inferRelationType(resolvedSlug, c.collSlug, profile);
2488
- batchAutoLinks.push({ fromEntryId: finalEntryId, toEntryId: c.entryId, type: relationType, proposedBy: "auto-link", confidence: c.confidence });
2489
- }
2490
- if (batchAutoLinks.length > 0 && preview) {
2491
- batchEntryWarnings.push(`${batchAutoLinks.length} auto-link(s) would be created (preview \u2014 no DB writes).`);
2492
- } else if (batchAutoLinks.length > 0) {
2493
- const batchRes = await kernelMutation("chain.createEntryRelations", {
2494
- relations: batchAutoLinks,
2495
- sessionId: agentId ?? void 0
2496
- });
2497
- autoLinkCount = batchRes.created;
2498
- advisedLinkCount = batchRes.advised ?? 0;
2499
- if (advisedLinkCount > 0) {
2500
- batchEntryWarnings.push(`${advisedLinkCount} auto-link(s) advised, not written (untypeable) \u2014 accept with \`relations action=create\` to promote.`);
2501
- }
2528
+ candidatesFiltered = (searchResults ?? []).filter((r) => r.entryId !== finalEntryId).map((r) => ({
2529
+ name: r.name,
2530
+ entryId: r.entryId,
2531
+ collectionId: r.collectionId
2532
+ }));
2533
+ if (candidatesFiltered.length > 0) {
2534
+ job = buildScoreLinkCandidatesJob(
2535
+ candidatesFiltered.map((r) => ({ name: r.name, collectionId: r.collectionId })),
2536
+ entry.name,
2537
+ entry.description,
2538
+ resolvedSlug
2539
+ );
2502
2540
  }
2503
2541
  } catch {
2504
2542
  }
2505
2543
  }
2506
- let relationsCreatedCount = 0;
2507
- let relationsPreviewedCount = 0;
2508
- let relationsProposedCount = 0;
2509
- const relationsFailedList = [];
2510
- if (entry.relations && entry.relations.length > 0) {
2511
- for (const rel of entry.relations) {
2512
- try {
2513
- const relResult = await kernelMutation(
2514
- "chain.createEntryRelation",
2515
- {
2516
- fromEntryId: finalEntryId,
2517
- toEntryId: rel.to,
2518
- type: rel.type,
2519
- proposedBy: "user",
2520
- sessionId: agentId ?? void 0,
2521
- ...preview ? { preview: true } : {}
2544
+ const prospectiveJobBytes = job ? estimateJobBytes(job) : 0;
2545
+ if (pendingFinish.length > 0 && (pendingFinish.length >= LINK_SCORE_CHUNK_MAX_ENTRIES || chunkBytes + prospectiveJobBytes > LINK_SCORE_CHUNK_MAX_BYTES)) {
2546
+ await flushPendingChunk(pendingFinish);
2547
+ pendingFinish = [];
2548
+ chunkBytes = 0;
2549
+ }
2550
+ pendingFinish.push({
2551
+ entryIdx,
2552
+ entry,
2553
+ resolvedSlug,
2554
+ classifiedBy,
2555
+ confidence,
2556
+ confidenceTier,
2557
+ job,
2558
+ finish: async (scores) => {
2559
+ let autoLinkCount = 0;
2560
+ let advisedLinkCount = 0;
2561
+ let entryOverlapCount = 0;
2562
+ if (candidatesFiltered.length > 0) {
2563
+ try {
2564
+ const candidates = scores.map((s) => {
2565
+ const r = candidatesFiltered[s.index];
2566
+ return { ...r, collSlug: collIdToSlug.get(r.collectionId) ?? "unknown", confidence: s.score };
2567
+ });
2568
+ entryOverlapCount = candidates.filter((c) => c.entryId).length;
2569
+ const batchAutoLinks = [];
2570
+ for (const c of candidates) {
2571
+ if (batchAutoLinks.length >= MAX_AUTO_LINKS) break;
2572
+ if (c.confidence < AUTO_LINK_CONFIDENCE_THRESHOLD) break;
2573
+ if (!c.entryId) continue;
2574
+ const { type: relationType } = inferRelationType(resolvedSlug, c.collSlug, profile);
2575
+ batchAutoLinks.push({ fromEntryId: finalEntryId, toEntryId: c.entryId, type: relationType, proposedBy: "auto-link", confidence: c.confidence });
2522
2576
  }
2523
- );
2524
- if (relResult?.preview) {
2525
- relationsPreviewedCount++;
2526
- } else if (relResult?.status === "agent_proposal_created") {
2527
- relationsProposedCount++;
2528
- } else {
2529
- relationsCreatedCount++;
2577
+ if (batchAutoLinks.length > 0 && preview) {
2578
+ batchEntryWarnings.push(`${batchAutoLinks.length} auto-link(s) would be created (preview \u2014 no DB writes).`);
2579
+ } else if (batchAutoLinks.length > 0) {
2580
+ const batchRes = await kernelMutation("chain.createEntryRelations", {
2581
+ relations: batchAutoLinks,
2582
+ sessionId: agentId ?? void 0
2583
+ });
2584
+ autoLinkCount = batchRes.created;
2585
+ advisedLinkCount = batchRes.advised ?? 0;
2586
+ if (advisedLinkCount > 0) {
2587
+ batchEntryWarnings.push(`${advisedLinkCount} auto-link(s) advised, not written (untypeable) \u2014 accept with \`relations action=create\` to promote.`);
2588
+ }
2589
+ }
2590
+ } catch {
2530
2591
  }
2531
- } catch (relErr) {
2532
- const relMsg = relErr instanceof Error ? relErr.message : String(relErr);
2533
- relationsFailedList.push(`${rel.to} (${rel.type}): ${relMsg}`);
2534
2592
  }
2535
- }
2536
- if (relationsFailedList.length > 0) {
2537
- batchEntryWarnings.push(`${relationsFailedList.length} inline relation(s) failed: ${relationsFailedList.join("; ")}`);
2538
- }
2539
- if (relationsPreviewedCount > 0) {
2540
- batchEntryWarnings.push(`${relationsPreviewedCount} inline relation(s) would be created (preview \u2014 no DB writes).`);
2541
- }
2542
- if (relationsProposedCount > 0) {
2543
- batchEntryWarnings.push(`${relationsProposedCount} inline relation(s) converted to an agent proposal (misuse pattern) \u2014 review in Cortex UI.`);
2544
- }
2545
- }
2546
- if (autoCommitApplied && !batchWasAutoCommittedServerSide) {
2547
- try {
2548
- const semanticConflicts = await discoverSemanticConflicts(entry.name, entry.description, resolvedSlug);
2549
- const commitResult = await kernelMutation("chain.commitEntry", {
2550
- entryId: finalEntryId,
2551
- author: agentId ? `agent:${agentId}` : void 0,
2552
- sessionId: agentId ?? void 0,
2553
- ...semanticConflicts.length > 0 ? { conflicts: semanticConflicts } : {}
2554
- });
2555
- const refusalReason = coherencyRefusalReason(commitResult);
2556
- if (refusalReason) {
2557
- commitError = `coherency gate refused this accept \u2014 ${refusalReason}`;
2558
- commitRefusal = commitResult?.refusal ?? void 0;
2559
- finalStatus = "draft_on_failure";
2560
- } else {
2561
- finalStatus = commitResult?.status === "proposal_created" ? "proposed" : "committed";
2562
- contradictionAdvisory = commitResult?.contradictionAdvisory ?? void 0;
2563
- if (finalStatus === "committed") {
2564
- await recordSessionActivity({ entryModified: internalId });
2565
- trackChainEntryCommitted(wsCtx.workspaceId, {
2566
- entry_id: finalEntryId,
2567
- collection: resolvedSlug ?? void 0,
2568
- commit_method: "auto",
2569
- surface: "mcp_capture"
2593
+ let relationsCreatedCount = 0;
2594
+ let relationsPreviewedCount = 0;
2595
+ let relationsProposedCount = 0;
2596
+ const relationsFailedList = [];
2597
+ if (entry.relations && entry.relations.length > 0) {
2598
+ for (const rel of entry.relations) {
2599
+ try {
2600
+ const relResult = await kernelMutation(
2601
+ "chain.createEntryRelation",
2602
+ {
2603
+ fromEntryId: finalEntryId,
2604
+ toEntryId: rel.to,
2605
+ type: rel.type,
2606
+ proposedBy: "user",
2607
+ sessionId: agentId ?? void 0,
2608
+ ...preview ? { preview: true } : {}
2609
+ }
2610
+ );
2611
+ if (relResult?.preview) {
2612
+ relationsPreviewedCount++;
2613
+ } else if (relResult?.status === "agent_proposal_created") {
2614
+ relationsProposedCount++;
2615
+ } else {
2616
+ relationsCreatedCount++;
2617
+ }
2618
+ } catch (relErr) {
2619
+ const relMsg = relErr instanceof Error ? relErr.message : String(relErr);
2620
+ relationsFailedList.push(`${rel.to} (${rel.type}): ${relMsg}`);
2621
+ }
2622
+ }
2623
+ if (relationsFailedList.length > 0) {
2624
+ batchEntryWarnings.push(`${relationsFailedList.length} inline relation(s) failed: ${relationsFailedList.join("; ")}`);
2625
+ }
2626
+ if (relationsPreviewedCount > 0) {
2627
+ batchEntryWarnings.push(`${relationsPreviewedCount} inline relation(s) would be created (preview \u2014 no DB writes).`);
2628
+ }
2629
+ if (relationsProposedCount > 0) {
2630
+ batchEntryWarnings.push(`${relationsProposedCount} inline relation(s) converted to an agent proposal (misuse pattern) \u2014 review in Cortex UI.`);
2631
+ }
2632
+ }
2633
+ if (autoCommitApplied && !batchWasAutoCommittedServerSide) {
2634
+ try {
2635
+ const semanticConflicts = await discoverSemanticConflicts(entry.name, entry.description, resolvedSlug);
2636
+ const commitResult = await kernelMutation("chain.commitEntry", {
2637
+ entryId: finalEntryId,
2638
+ author: agentId ? `agent:${agentId}` : void 0,
2639
+ sessionId: agentId ?? void 0,
2640
+ ...semanticConflicts.length > 0 ? { conflicts: semanticConflicts } : {}
2570
2641
  });
2642
+ const refusalReason = coherencyRefusalReason(commitResult);
2643
+ if (refusalReason) {
2644
+ commitError = `coherency gate refused this accept \u2014 ${refusalReason}`;
2645
+ commitRefusal = commitResult?.refusal ?? void 0;
2646
+ finalStatus = "draft_on_failure";
2647
+ } else {
2648
+ finalStatus = commitResult?.status === "proposal_created" ? "proposed" : "committed";
2649
+ contradictionAdvisory = commitResult?.contradictionAdvisory ?? void 0;
2650
+ if (finalStatus === "committed") {
2651
+ await recordSessionActivity({ entryModified: internalId });
2652
+ trackChainEntryCommitted(wsCtx.workspaceId, {
2653
+ entry_id: finalEntryId,
2654
+ collection: resolvedSlug ?? void 0,
2655
+ commit_method: "auto",
2656
+ surface: "mcp_capture"
2657
+ });
2658
+ }
2659
+ }
2660
+ } catch (error) {
2661
+ commitError = error instanceof Error ? error.message : String(error);
2662
+ finalStatus = "draft_on_failure";
2663
+ await recordCommitFailure({ entryId: finalEntryId, error, sessionId: agentId, server });
2571
2664
  }
2572
2665
  }
2573
- } catch (error) {
2574
- commitError = error instanceof Error ? error.message : String(error);
2575
- finalStatus = "draft_on_failure";
2576
- await recordCommitFailure({ entryId: finalEntryId, error, sessionId: agentId, server });
2666
+ const entryNorm = result2.normalization;
2667
+ results.push({
2668
+ entryIdx,
2669
+ name: entry.name,
2670
+ collection: resolvedSlug,
2671
+ entryId: finalEntryId,
2672
+ ok: true,
2673
+ autoLinks: autoLinkCount,
2674
+ advisedLinks: advisedLinkCount,
2675
+ status: finalStatus,
2676
+ classifiedBy,
2677
+ confidence,
2678
+ confidenceTier,
2679
+ ...commitError ? { commitError } : {},
2680
+ // WP-465 surface parity: thread full refusal payload for coherency-refused batch entries.
2681
+ ...commitRefusal ? { commitRefusal } : {},
2682
+ // WP-485 Slice 2b (R2, FEAT-1370): the server's contradiction advisory (DEC-1321 —
2683
+ // advisory only, never blocking) — visible, never a silent success.
2684
+ ...contradictionAdvisory ? { contradictionAdvisory } : {},
2685
+ ...batchEntryWarnings.length > 0 ? { warnings: batchEntryWarnings } : {},
2686
+ ...entryNorm && (Object.keys(entryNorm.remapped).length > 0 || entryNorm.rejected.length > 0) && {
2687
+ normalization: { remapped: entryNorm.remapped, rejected: entryNorm.rejected }
2688
+ },
2689
+ // BET-167: Track overlap count for conflict advisory
2690
+ ...entryOverlapCount > 0 ? { overlapCount: entryOverlapCount } : {},
2691
+ // TEN-2365: capture-time authority-domain proposal echo.
2692
+ ...result2.authorityDomain ? { authorityDomain: result2.authorityDomain } : {},
2693
+ // TEN-957 (WP-484 S2): inline relations created for this entry.
2694
+ ...relationsCreatedCount > 0 ? { relationsCreated: relationsCreatedCount } : {},
2695
+ // Finding #14: distinct from relationsCreated — never counted as a write.
2696
+ ...relationsProposedCount > 0 ? { relationsProposed: relationsProposedCount } : {}
2697
+ });
2577
2698
  }
2578
- }
2579
- const entryNorm = result2.normalization;
2580
- results.push({
2581
- name: entry.name,
2582
- collection: resolvedSlug,
2583
- entryId: finalEntryId,
2584
- ok: true,
2585
- autoLinks: autoLinkCount,
2586
- advisedLinks: advisedLinkCount,
2587
- status: finalStatus,
2588
- classifiedBy,
2589
- confidence,
2590
- confidenceTier,
2591
- ...commitError ? { commitError } : {},
2592
- // WP-465 surface parity: thread full refusal payload for coherency-refused batch entries.
2593
- ...commitRefusal ? { commitRefusal } : {},
2594
- // WP-485 Slice 2b (R2, FEAT-1370): the server's contradiction advisory (DEC-1321 —
2595
- // advisory only, never blocking) — visible, never a silent success.
2596
- ...contradictionAdvisory ? { contradictionAdvisory } : {},
2597
- ...batchEntryWarnings.length > 0 ? { warnings: batchEntryWarnings } : {},
2598
- ...entryNorm && (Object.keys(entryNorm.remapped).length > 0 || entryNorm.rejected.length > 0) && {
2599
- normalization: { remapped: entryNorm.remapped, rejected: entryNorm.rejected }
2600
- },
2601
- // BET-167: Track overlap count for conflict advisory
2602
- ...entryOverlapCount > 0 ? { overlapCount: entryOverlapCount } : {},
2603
- // TEN-2365: capture-time authority-domain proposal echo.
2604
- ...result2.authorityDomain ? { authorityDomain: result2.authorityDomain } : {},
2605
- // TEN-957 (WP-484 S2): inline relations created for this entry.
2606
- ...relationsCreatedCount > 0 ? { relationsCreated: relationsCreatedCount } : {},
2607
- // Finding #14: distinct from relationsCreated — never counted as a write.
2608
- ...relationsProposedCount > 0 ? { relationsProposed: relationsProposedCount } : {}
2609
2699
  });
2700
+ if (job) chunkBytes += prospectiveJobBytes;
2610
2701
  } catch (error) {
2611
2702
  const msg = error instanceof Error ? error.message : String(error);
2612
2703
  results.push({
2704
+ entryIdx,
2613
2705
  name: entry.name,
2614
2706
  collection: resolvedSlug,
2615
2707
  entryId: "",
@@ -2624,6 +2716,7 @@ async function handleBatchCapture(server, { entries, autoCommit, preview }) {
2624
2716
  });
2625
2717
  }
2626
2718
  }
2719
+ await flushPendingChunk(pendingFinish);
2627
2720
  const created = results.filter((r) => r.ok);
2628
2721
  const failed = results.filter((r) => !r.ok);
2629
2722
  const committed = created.filter((r) => r.status === "committed");
@@ -2835,8 +2928,13 @@ _Use \`entries action=move\` to correct any misclassified entries._`);
2835
2928
  }))
2836
2929
  },
2837
2930
  ...failed.length > 0 && {
2931
+ // PR #392 round 2 (Codex, finding 2): `r.entryIdx` is the ORIGINAL input
2932
+ // position, carried explicitly since the moment each result was built —
2933
+ // NEVER `results.indexOf(r)` (the chunked pipeline pushes results in a
2934
+ // different order than `entries`' input order, so that would silently
2935
+ // report the wrong index for a batch with a mix of early/late failures).
2838
2936
  failedEntries: failed.map((r) => ({
2839
- index: results.indexOf(r),
2937
+ index: r.entryIdx,
2840
2938
  collection: r.collection,
2841
2939
  name: r.name,
2842
2940
  error: r.error ?? "unknown error"
@@ -15548,4 +15646,4 @@ export {
15548
15646
  createProductBrainServer,
15549
15647
  initFeatureFlags
15550
15648
  };
15551
- //# sourceMappingURL=chunk-EPK7OSX3.js.map
15649
+ //# sourceMappingURL=chunk-Y52AWERD.js.map