@productbrain/mcp 0.0.1-beta.66 → 0.0.1-beta.68

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.
@@ -31,6 +31,7 @@ import {
31
31
  registerSmartCaptureTools,
32
32
  requireActiveSession,
33
33
  requireWriteAccess,
34
+ resolveCollection,
34
35
  runWithToolContext,
35
36
  setSessionOriented,
36
37
  startAgentSession,
@@ -42,7 +43,7 @@ import {
42
43
  unknownAction,
43
44
  validationResult,
44
45
  withEnvelope
45
- } from "./chunk-UWILSX73.js";
46
+ } from "./chunk-TH5AUVVM.js";
46
47
  import {
47
48
  trackKnowledgeGap,
48
49
  trackQualityCheck,
@@ -237,7 +238,7 @@ ${formatted}` }],
237
238
  },
238
239
  withEnvelope(async ({ entryId }) => {
239
240
  requireWriteAccess();
240
- const { runContradictionCheck } = await import("./smart-capture-EGTUM4XP.js");
241
+ const { runContradictionCheck } = await import("./smart-capture-Q64ZXK65.js");
241
242
  const entry = await mcpQuery("chain.getEntry", { entryId });
242
243
  if (!entry) {
243
244
  return notFoundResult(entryId, `Entry '${entryId}' not found. Try search to find the right ID.`);
@@ -364,14 +365,15 @@ function sanitizeEntryData(data) {
364
365
  );
365
366
  return Object.keys(filtered).length > 0 ? filtered : void 0;
366
367
  }
367
- var ENTRIES_ACTIONS = ["list", "get", "batch", "search"];
368
+ var ENTRIES_ACTIONS = ["list", "get", "batch", "search", "move"];
368
369
  var entriesSchema = z2.object({
369
370
  action: z2.enum(ENTRIES_ACTIONS).describe(
370
- "'list': browse entries with filters. 'get': fetch one entry by ID. 'batch': fetch multiple entries. 'search': full-text search."
371
+ "'list': browse entries with filters. 'get': fetch one entry by ID. 'batch': fetch multiple entries. 'search': full-text search. 'move': move entry to a different collection."
371
372
  ),
372
- entryId: z2.string().optional().describe("Entry ID for get action, e.g. 'T-SUPPLIER', 'BR-001'"),
373
+ entryId: z2.string().optional().describe("Entry ID for get/move action, e.g. 'DEC-42', 'BR-001'"),
373
374
  entryIds: z2.array(z2.string()).min(1).max(20).optional().describe("Entry IDs for batch action, e.g. ['TYPE-strategy', 'STR-jljeg7']"),
374
375
  collection: z2.string().optional().describe("Collection slug for list/search, e.g. 'glossary', 'tracking-events', 'business-rules'"),
376
+ toCollection: z2.string().optional().describe("Target collection slug for move action, e.g. 'decisions', 'architecture'"),
375
377
  status: z2.string().optional().describe("Filter: draft | active | deprecated | archived"),
376
378
  tag: z2.string().optional().describe("Filter by internal tag for list"),
377
379
  label: z2.string().optional().describe("Filter by label slug for list \u2014 matches entries across all collections"),
@@ -426,14 +428,14 @@ function registerEntriesTools(server) {
426
428
  "entries",
427
429
  {
428
430
  title: "Entries",
429
- description: 'Read entries from the Chain. One tool for all entry reading.\n\n- **list**: Browse entries with optional filters (collection, status, tag, label). Use collections action=list first to discover slugs.\n- **get**: Fetch a single entry by ID \u2014 full record with data, labels, relations, history.\n- **batch**: Fetch multiple entries (max 20) in one call. Same shape as get per entry.\n- **search**: Full-text search across entries. Scope by collection or filter by status.\n\nUse `entries action=get entryId="..."` to fetch one. Use `entries action=search query="..."` to discover.',
431
+ description: 'Read entries from the Chain. One tool for all entry reading.\n\n- **list**: Browse entries with optional filters (collection, status, tag, label). Use collections action=list first to discover slugs.\n- **get**: Fetch a single entry by ID \u2014 full record with data, labels, relations, history.\n- **batch**: Fetch multiple entries (max 20) in one call. Same shape as get per entry.\n- **search**: Full-text search across entries. Scope by collection or filter by status.\n- **move**: Move an entry to a different collection. Use when classifier misrouted or user wants to reclassify.\n\nUse `entries action=get entryId="..."` to fetch one. Use `entries action=search query="..."` to discover.',
430
432
  inputSchema: entriesSchema,
431
- annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
433
+ annotations: { idempotentHint: false, openWorldHint: false }
432
434
  },
433
435
  withEnvelope(async (args) => {
434
436
  const parsed = parseOrFail(entriesSchema, args);
435
437
  if (!parsed.ok) return parsed.result;
436
- const { action, entryId, entryIds, collection, status, tag, label, query } = parsed.data;
438
+ const { action, entryId, entryIds, collection, toCollection, status, tag, label, query } = parsed.data;
437
439
  return runWithToolContext({ tool: "entries", action }, async () => {
438
440
  if (action === "get") {
439
441
  if (!entryId) {
@@ -456,6 +458,11 @@ function registerEntriesTools(server) {
456
458
  if (action === "list") {
457
459
  return handleList(collection, status, tag, label);
458
460
  }
461
+ if (action === "move") {
462
+ if (!entryId) return validationResult("entryId is required when action is 'move'.");
463
+ if (!toCollection) return validationResult("toCollection is required when action is 'move'.");
464
+ return handleMove(entryId, toCollection);
465
+ }
459
466
  return unknownAction(action, ENTRIES_ACTIONS);
460
467
  });
461
468
  })
@@ -657,6 +664,41 @@ ${formatted}` }],
657
664
  structuredContent: success(`Found ${total} entries${scope}.`, { entries: structuredEntries, total })
658
665
  };
659
666
  }
667
+ async function handleMove(entryId, toCollection) {
668
+ try {
669
+ const result = await mcpMutation("chain.moveToCollection", { entryId, toCollectionSlug: toCollection });
670
+ const lines = [
671
+ `## Move Result`,
672
+ "",
673
+ `Moved **${result.entryId}** from \`${result.fromCollection}\` \u2192 \`${result.toCollection}\``
674
+ ];
675
+ if (result.workflowStatusReset) {
676
+ lines.push(``, `\u26A0\uFE0F Workflow status \`${result.previousWorkflowStatus}\` was reset (incompatible with target collection).`);
677
+ }
678
+ return {
679
+ content: [{ type: "text", text: lines.join("\n") }],
680
+ structuredContent: success(
681
+ `Moved ${result.entryId} from ${result.fromCollection} to ${result.toCollection}.`,
682
+ result,
683
+ [
684
+ { tool: "entries", description: "View updated entry", parameters: { action: "get", entryId } },
685
+ { tool: "quality", description: "Re-check quality", parameters: { action: "check", entryId } }
686
+ ]
687
+ )
688
+ };
689
+ } catch (error) {
690
+ const msg = error instanceof Error ? error.message : String(error);
691
+ const isNotFound = msg.includes("not found");
692
+ if (isNotFound) return notFoundResult(entryId, `Could not move entry: ${msg}`);
693
+ return failureResult(
694
+ `Could not move **${entryId}** to \`${toCollection}\`: ${msg}`,
695
+ "MOVE_FAILED",
696
+ msg,
697
+ "Check the entry ID and target collection slug, then retry.",
698
+ [{ tool: "entries", description: "Search for entry", parameters: { action: "search", query: entryId } }]
699
+ );
700
+ }
701
+ }
660
702
  async function handleSearch(server, query, collection, status) {
661
703
  const scope = collection ? ` in \`${collection}\`` : "";
662
704
  await server.sendLoggingMessage({
@@ -3053,7 +3095,7 @@ If any facilitate call fails:
3053
3095
  label: "De-risk \u2014 Rabbit Holes & No-Gos",
3054
3096
  type: "open",
3055
3097
  instruction: "What could go wrong? What are we NOT building? Walk through the solution slowly and find the risks.",
3056
- facilitatorGuidance: "**Investigate first.** Read the investigationBrief. Spawn sub-agents to search the codebase for fragile code, tight coupling, missing tests, performance-sensitive paths, and external dependencies near the affected modules. Propose risks with evidence: 'I found that X module has no tests and is tightly coupled to Y \u2014 this is a rabbit hole.' Every confirmed risk MUST be captured with its own respond call: `facilitate action=respond betEntryId='...' dimension='risks' source='user_reaction' userInput='...' capture={type:'risk', name:'Risk Name', description:'What could go wrong', theme:'implementation'}`. Each becomes a tensions entry linked to the bet via constrains. Push for mitigations \u2014 every risk needs a patch or explicit acceptance. Then declare no-gos: 'Based on what I've seen, I'd suggest these no-gos.' Every no-go MUST use capture: `facilitate action=respond betEntryId='...' dimension='boundaries' source='user_reaction' userInput='...' capture={type:'noGo', name:'No-Go Title', description:'Why we are not doing this'}`. Without the `capture` parameter on each call, the item is NOT saved \u2014 you will get a WARNING. When risks score 6+ and boundaries score 4+, present Shape Go gate: 'Is this buildable within the appetite?'",
3098
+ facilitatorGuidance: "**Investigate first.** Read the investigationBrief. Spawn sub-agents to search the codebase for fragile code, tight coupling, missing tests, performance-sensitive paths, and external dependencies near the affected modules. Propose risks with evidence: 'I found that X module has no tests and is tightly coupled to Y \u2014 this is a rabbit hole.' Every confirmed risk MUST be captured with its own respond call: `facilitate action=respond betEntryId='...' dimension='risks' source='user_reaction' userInput='...' capture={type:'risk', name:'Risk Name', description:'What could go wrong', theme:'implementation'}`. Each becomes a tensions entry linked to the bet via constrains. Push for mitigations \u2014 every risk needs a patch or explicit acceptance. Then declare no-gos: 'Based on what I've seen, I'd suggest these no-gos.' Every no-go MUST use capture: `facilitate action=respond betEntryId='...' dimension='boundaries' source='user_reaction' userInput='...' capture={type:'noGo', name:'No-Go Title', description:'Why we are not doing this'}`. Without the `capture` parameter on each call, the item is NOT saved \u2014 you will get a WARNING. **Counter-Metric Mandate (STD-19):** If the derisking references any quantitative gates or metrics (accuracy targets, performance budgets, adoption thresholds), stress-test them: 'What's the denominator? What gets excluded? What counter-metric would expose a false positive?' A metric gate that can be gamed by abstaining or cherry-picking the population is not a gate \u2014 name the compound metric that can't be gamed. When risks score 6+ and boundaries score 4+, present Shape Go gate: 'Is this buildable within the appetite?'",
3057
3099
  questions: [
3058
3100
  {
3059
3101
  id: "shape-go",
@@ -3079,7 +3121,7 @@ If any facilitate call fails:
3079
3121
  label: "Validate & Build Contract",
3080
3122
  type: "synthesis",
3081
3123
  instruction: "Check completeness. Generate the build contract. Review the full scorecard.",
3082
- facilitatorGuidance: "Call `facilitate action=score` to get the full scorecard. Present it as a table. Check: does the response include a buildContract? If captureReady is true, it should. Verify the 5 core ingredients: Problem, Appetite, Solution elements, Rabbit Holes, No-Gos. For Big Batch: also verify Architecture, narrative hook, and value-flow annotations. Present the build contract. Ask: 'Anything to adjust before we capture to the Chain?'",
3124
+ facilitatorGuidance: "Call `facilitate action=score` to get the full scorecard. Present it as a table. Check: does the response include a buildContract? If captureReady is true, it should. Verify the 5 core ingredients: Problem, Appetite, Solution elements, Rabbit Holes, No-Gos. For Big Batch: also verify Architecture, narrative hook, and value-flow annotations. **Counter-Metric Mandate (STD-19):** If the scorecard or build contract contains metrics or gates, verify each has a denominator, a coverage rate, and a counter-metric. Flag any gate that can be satisfied by abstaining or excluding the hard cases. Present the build contract. Ask: 'Anything to adjust before we capture to the Chain?'",
3083
3125
  outputSchema: {
3084
3126
  field: "validation",
3085
3127
  description: "Completeness check result and build contract",
@@ -3194,7 +3236,18 @@ var IMPLEMENTATION_REVIEW_WORKFLOW_DESCRIPTOR = {
3194
3236
 
3195
3237
  ## Sub-Agent Classification (for test failures)
3196
3238
 
3197
- When reviewing test results: (a) test staleness \u2014 code changed, test not updated; (b) regression \u2014 code broke behavior; (c) flaky/bad test \u2014 harness or timing issue.`,
3239
+ When reviewing test results: (a) test staleness \u2014 code changed, test not updated; (b) regression \u2014 code broke behavior; (c) flaky/bad test \u2014 harness or timing issue.
3240
+
3241
+ ## Structured Verdict (CRITICAL)
3242
+
3243
+ Round 05 MUST end with a structured verdict block containing exactly these five items:
3244
+ 1. **Verdict:** Y (ship) or N (do not ship) \u2014 one word, no hedging.
3245
+ 2. **What shipped:** One sentence describing what was built.
3246
+ 3. **Why it matters:** One sentence on the problem this solves.
3247
+ 4. **User benefit:** One sentence on the value for users.
3248
+ 5. **How to validate:** One sentence on how to verify it works as intended.
3249
+
3250
+ This block is the final deliverable of the review. Everything else is supporting evidence.`,
3198
3251
  rounds: [
3199
3252
  {
3200
3253
  id: "orient",
@@ -3230,7 +3283,7 @@ When reviewing test results: (a) test staleness \u2014 code changed, test not up
3230
3283
  label: "Code & Test Honesty",
3231
3284
  type: "open",
3232
3285
  instruction: "Spawn sub-agents to review implementation and tests for the stated scope only. Did we meet high standards? Are tests validating real behavior or did we edit them just to pass?",
3233
- facilitatorGuidance: "Restrict all review to the scope stated in Round 01 (by default, work in this conversation only). Spawn 1\u20132 sub-agents in parallel: (1) explore agent \u2014 code review, architecture boundaries, type-safety for scoped files only. (2) generalPurpose agent \u2014 test review for scoped code: are tests asserting real behavior or were they edited to pass? Pass the scope (files/BET) explicitly to sub-agents so they do not touch other work. Use Context7 (query-docs) for relevant testing and framework best practices if needed. Classify any test failures: staleness, regression, or flaky. Synthesize: implementation grade, test honesty verdict, refactoring suggestions (within scope only).",
3286
+ facilitatorGuidance: "Restrict all review to the scope stated in Round 01 (by default, work in this conversation only). Spawn 1\u20132 sub-agents in parallel: (1) explore agent \u2014 code review, architecture boundaries, type-safety for scoped files only. (2) generalPurpose agent \u2014 test review for scoped code: are tests asserting real behavior or were they edited to pass? Pass the scope (files/BET) explicitly to sub-agents so they do not touch other work. Use Context7 (query-docs) for relevant testing and framework best practices if needed. Classify any test failures: staleness, regression, or flaky. **Counter-Metric Mandate (STD-19):** When reporting test coverage, accuracy, or any quantitative result, include the denominator, what was excluded, and at least one counter-metric. Never report accuracy without recall. Never report pass rate without coverage. Synthesize: implementation grade, test honesty verdict, refactoring suggestions (within scope only).",
3234
3287
  outputSchema: {
3235
3288
  field: "codeAndTests",
3236
3289
  description: "Implementation grade, test honesty, refactor suggestions",
@@ -3258,10 +3311,10 @@ When reviewing test results: (a) test staleness \u2014 code changed, test not up
3258
3311
  label: "Synthesis & Sign-Off",
3259
3312
  type: "commit",
3260
3313
  instruction: "Summarize the review. Answer: Does this bring us closer to our vision and make our code and architecture cleaner? Refactoring needed? Ship or conditional? End with BET/chain IDs.",
3261
- facilitatorGuidance: "Present the full synthesis: standards pass/fail, code grade, test honesty, chain validation. Explicitly answer: Does this bring us closer to our vision and make our code and architecture cleaner? Recommend: ship, ship with conditions, or do not ship. CRITICAL: The output MUST end with one sentence: 'BET/feature IDs reviewed: [IDs]. [One-line summary].' Example: 'BET-xxx, STD-xxx reviewed. Conditional ship \u2014 fix [specific issues] and add [missing tests].'",
3314
+ facilitatorGuidance: "Present the full synthesis: standards pass/fail, code grade, test honesty, chain validation. Explicitly answer: Does this bring us closer to our vision and make our code and architecture cleaner? **Counter-Metric Mandate (STD-19):** For EVERY quantitative result you present, you MUST include: (1) the denominator \u2014 what is the total population measured, (2) the coverage/abstention rate \u2014 what was excluded, (3) at least one counter-metric that tells the opposite story (recall vs precision, error rate vs success rate), (4) the 'student exam' reframe \u2014 score on the full exam, not just questions answered. **Red-team your own synthesis:** Before presenting, ask yourself: 'What's the worst honest interpretation of these numbers? What metric looks good but hides a real problem? What would a skeptic challenge?' Present that alongside the headline. Recommend: ship, ship with conditions, or do not ship. CRITICAL: The output MUST end with one sentence: 'BET/feature IDs reviewed: [IDs]. [One-line summary].' Example: 'BET-xxx, STD-xxx reviewed. Conditional ship \u2014 fix [specific issues] and add [missing tests].' **Structured Verdict Block (REQUIRED):** After the full synthesis, present the verdict block with exactly 5 items: (1) Verdict: Y or N \u2014 one word. (2) What shipped: one sentence. (3) Why it matters: one sentence on the problem solved. (4) User benefit: one sentence on value for users. (5) How to validate: one sentence on verification. This block is the final deliverable. Everything else is supporting evidence.",
3262
3315
  outputSchema: {
3263
3316
  field: "synthesis",
3264
- description: "Full review synthesis with BET/chain IDs",
3317
+ description: "Full review synthesis with BET/chain IDs and structured verdict block (verdict Y/N, what shipped, why it matters, user benefit, how to validate)",
3265
3318
  format: "structured"
3266
3319
  },
3267
3320
  maxDurationHint: "5 min"
@@ -3272,7 +3325,7 @@ When reviewing test results: (a) test staleness \u2014 code changed, test not up
3272
3325
  label: "Capture & Close",
3273
3326
  type: "close",
3274
3327
  instruction: "Finalize the review as an insight draft. Use session-wrapup. Thank the participant.",
3275
- facilitatorGuidance: "Summarize the review for finalization. Complete the terminal round through `workflows action=checkpoint` with `isFinal=true` so the workflow substrate creates the draft insight record. Include: BET/feature IDs, verdict, key findings. Run session-wrapup before closing. Thank the participant. Remind them to commit-entry when ready.",
3328
+ facilitatorGuidance: "Summarize the review for finalization. Complete the terminal round through `workflows action=checkpoint` with `isFinal=true` so the workflow substrate creates the draft insight record. Include: BET/feature IDs, verdict, key findings, and the structured verdict block from Round 05. The insight description MUST include the 5-item verdict block (verdict Y/N, what shipped, why it matters, user benefit, how to validate). Run session-wrapup before closing. Thank the participant. Remind them to commit-entry when ready.",
3276
3329
  outputSchema: {
3277
3330
  field: "capture",
3278
3331
  description: "Insight entry created, BET IDs referenced",
@@ -3853,6 +3906,7 @@ This checkpoint was NOT saved. The conversation context is preserved \u2014 cont
3853
3906
  runId
3854
3907
  );
3855
3908
  }
3909
+ const resolvedSummaryCapture = summaryCapture;
3856
3910
  const existingRun = await mcpQuery(
3857
3911
  "chainwork.getLatestWorkflowRun",
3858
3912
  {
@@ -3866,6 +3920,15 @@ This checkpoint was NOT saved. The conversation context is preserved \u2014 cont
3866
3920
  );
3867
3921
  }
3868
3922
  try {
3923
+ let resolvedCollectionSlug;
3924
+ if (resolvedSummaryCapture.routing.mode === "classifier") {
3925
+ const classifyName = summaryName ?? wf.name;
3926
+ const classifyDesc = summaryDescription ?? outputPreview;
3927
+ const resolved = await resolveCollection({ name: classifyName, description: classifyDesc });
3928
+ if (resolved && resolved.tier !== "low") {
3929
+ resolvedCollectionSlug = resolved.collection;
3930
+ }
3931
+ }
3869
3932
  const result = await mcpMutation("chainwork.finalizeWorkflowRun", {
3870
3933
  agentSessionId,
3871
3934
  workflowId: wf.id,
@@ -3883,22 +3946,23 @@ This checkpoint was NOT saved. The conversation context is preserved \u2014 cont
3883
3946
  finalRoundId: roundId,
3884
3947
  finalOutput: normalizedOutput,
3885
3948
  summaryCapture: {
3886
- routing: summaryCapture.routing.mode === "fixed" ? {
3949
+ routing: resolvedSummaryCapture.routing.mode === "fixed" ? {
3887
3950
  mode: "fixed",
3888
- collection: summaryCapture.routing.collection
3951
+ collection: resolvedSummaryCapture.routing.collection
3889
3952
  } : {
3890
3953
  mode: "classifier"
3891
3954
  },
3892
- nameTemplate: summaryCapture.nameTemplate,
3893
- descriptionField: summaryCapture.descriptionField,
3894
- descriptionSource: summaryCapture.descriptionSource
3955
+ nameTemplate: resolvedSummaryCapture.nameTemplate,
3956
+ descriptionField: resolvedSummaryCapture.descriptionField,
3957
+ descriptionSource: resolvedSummaryCapture.descriptionSource
3895
3958
  },
3896
3959
  summaryDescription,
3897
- summaryName
3960
+ summaryName,
3961
+ resolvedCollectionSlug
3898
3962
  });
3899
3963
  lines.push(
3900
3964
  result.summary.created ? `**Draft Created**: \`${result.summary.entryId}\`` : `**Draft Already Exists**: \`${result.summary.entryId}\``,
3901
- `Collection: \`${result.summary.collection ?? (summaryCapture.routing.mode === "fixed" ? summaryCapture.routing.collection : "classifier-routed")}\``,
3965
+ `Collection: \`${result.summary.collection ?? (resolvedSummaryCapture.routing.mode === "fixed" ? resolvedSummaryCapture.routing.collection : "classifier-routed")}\``,
3902
3966
  `Name: ${result.summary.name}`,
3903
3967
  "",
3904
3968
  `The summary is captured as a draft. Use \`commit-entry entryId="${result.summary.entryId}"\` to promote it to the Chain.`,
@@ -3912,7 +3976,7 @@ This checkpoint was NOT saved. The conversation context is preserved \u2014 cont
3912
3976
  entryId: result.summary.entryId,
3913
3977
  workflowId,
3914
3978
  roundId,
3915
- collection: result.summary.collection ?? (summaryCapture.routing.mode === "fixed" ? summaryCapture.routing.collection : null),
3979
+ collection: result.summary.collection ?? (resolvedSummaryCapture.routing.mode === "fixed" ? resolvedSummaryCapture.routing.collection : null),
3916
3980
  isFinal: true,
3917
3981
  runId: result.runId,
3918
3982
  created: result.summary.created
@@ -3938,8 +4002,8 @@ This checkpoint was NOT saved. The conversation context is preserved \u2014 cont
3938
4002
  "",
3939
4003
  `The workflow output is preserved in this conversation. `,
3940
4004
  `You can manually create the entry later using \`capture\` with:`,
3941
- `- Collection: \`${summaryCapture.routing.mode === "fixed" ? summaryCapture.routing.collection : "classifier-routed"}\``,
3942
- `- Name: ${summaryName ?? summaryCapture.nameTemplate}`,
4005
+ `- Collection: \`${resolvedSummaryCapture.routing.mode === "fixed" ? resolvedSummaryCapture.routing.collection : "classifier-routed"}\``,
4006
+ `- Name: ${summaryName ?? resolvedSummaryCapture.nameTemplate}`,
3943
4007
  `- Description: ${summaryDescription ?? outputPreview}`
3944
4008
  );
3945
4009
  return {
@@ -5490,11 +5554,12 @@ async function processCaptures(opts) {
5490
5554
  const entriesCreated = [];
5491
5555
  let relationsCreated = 0;
5492
5556
  const capAgentId = getAgentSessionId();
5493
- const collectionMap = {
5494
- element: { slug: "features", dataField: "description", relationType: "part_of" },
5495
- risk: { slug: "tensions", dataField: "description", relationType: "constrains" },
5496
- decision: { slug: "decisions", dataField: "rationale", relationType: "informs" }
5557
+ const CAPTURE_DEFAULTS = {
5558
+ features: { dataField: "description", relationType: "part_of" },
5559
+ tensions: { dataField: "description", relationType: "constrains" },
5560
+ decisions: { dataField: "rationale", relationType: "informs" }
5497
5561
  };
5562
+ const FALLBACK_DEFAULTS = { dataField: "description", relationType: "related_to" };
5498
5563
  let runningBetData = { ...betData };
5499
5564
  for (const item of captureItems) {
5500
5565
  if (item.type === "noGo") {
@@ -5518,17 +5583,28 @@ async function processCaptures(opts) {
5518
5583
  }
5519
5584
  continue;
5520
5585
  }
5521
- const mapping = collectionMap[item.type];
5522
- if (!mapping) continue;
5586
+ const resolved = await resolveCollection({
5587
+ name: item.name,
5588
+ description: item.description,
5589
+ typeHint: item.type
5590
+ });
5591
+ if (!resolved) {
5592
+ captureErrors.push({
5593
+ operation: "classify",
5594
+ detail: `Could not resolve collection for ${item.type} "${item.name}" \u2014 skipped.`
5595
+ });
5596
+ continue;
5597
+ }
5598
+ const { dataField, relationType } = CAPTURE_DEFAULTS[resolved.collection] ?? FALLBACK_DEFAULTS;
5523
5599
  let capturedEntryId = null;
5524
5600
  try {
5525
5601
  const result = await mcpMutation(
5526
5602
  "chain.createEntry",
5527
5603
  {
5528
- collectionSlug: mapping.slug,
5604
+ collectionSlug: resolved.collection,
5529
5605
  name: item.name,
5530
5606
  status: "draft",
5531
- data: { [mapping.dataField]: item.description },
5607
+ data: { [dataField]: item.description },
5532
5608
  createdBy: capAgentId ? `agent:${capAgentId}` : "facilitate",
5533
5609
  sessionId: capAgentId ?? void 0
5534
5610
  }
@@ -5540,9 +5616,9 @@ async function processCaptures(opts) {
5540
5616
  if (msg.includes("Duplicate entry") || msg.includes("already exists")) {
5541
5617
  const fallback = await findAndLinkExisting(
5542
5618
  item.name,
5543
- mapping.slug,
5619
+ resolved.collection,
5544
5620
  betEntryId,
5545
- mapping.relationType,
5621
+ relationType,
5546
5622
  capAgentId
5547
5623
  );
5548
5624
  if (fallback) {
@@ -5551,7 +5627,7 @@ async function processCaptures(opts) {
5551
5627
  if (fallback.linked) relationsCreated++;
5552
5628
  captureErrors.push({
5553
5629
  operation: "info",
5554
- detail: `Linked existing ${mapping.slug} entry \`${fallback.entryId}\` instead of creating duplicate.`
5630
+ detail: `Linked existing ${resolved.collection} entry \`${fallback.entryId}\` instead of creating duplicate.`
5555
5631
  });
5556
5632
  } else {
5557
5633
  captureErrors.push({ operation: "capture", detail: `${item.type}: ${msg}` });
@@ -5565,7 +5641,7 @@ async function processCaptures(opts) {
5565
5641
  await mcpMutation("chain.createEntryRelation", {
5566
5642
  fromEntryId: capturedEntryId,
5567
5643
  toEntryId: betEntryId,
5568
- type: mapping.relationType,
5644
+ type: relationType,
5569
5645
  sessionId: capAgentId ?? void 0
5570
5646
  });
5571
5647
  relationsCreated++;
@@ -5574,7 +5650,7 @@ async function processCaptures(opts) {
5574
5650
  if (!msg.includes("already exists") && !msg.includes("Duplicate")) {
5575
5651
  captureErrors.push({
5576
5652
  operation: "relation",
5577
- detail: `${mapping.relationType} from ${capturedEntryId} to ${betEntryId}: ${msg}`
5653
+ detail: `${relationType} from ${capturedEntryId} to ${betEntryId}: ${msg}`
5578
5654
  });
5579
5655
  }
5580
5656
  }
@@ -6273,7 +6349,7 @@ async function handleCommitConstellation(args) {
6273
6349
  }
6274
6350
  let contradictionWarnings = [];
6275
6351
  try {
6276
- const { runContradictionCheck } = await import("./smart-capture-EGTUM4XP.js");
6352
+ const { runContradictionCheck } = await import("./smart-capture-Q64ZXK65.js");
6277
6353
  const descField = betData.problem ?? betData.description ?? "";
6278
6354
  contradictionWarnings = await runContradictionCheck(
6279
6355
  betEntry.name ?? betId,
@@ -9407,6 +9483,7 @@ var CALL_CATEGORIES = {
9407
9483
  "chain.searchEntries": "search",
9408
9484
  "chain.createEntry": "write",
9409
9485
  "chain.updateEntry": "write",
9486
+ "chain.moveToCollection": "write",
9410
9487
  "chain.createEntryRelation": "write",
9411
9488
  "chain.applyLabel": "label",
9412
9489
  "chain.removeLabel": "label",
@@ -11675,4 +11752,4 @@ export {
11675
11752
  SERVER_VERSION,
11676
11753
  createProductBrainServer
11677
11754
  };
11678
- //# sourceMappingURL=chunk-4IQ7A5R4.js.map
11755
+ //# sourceMappingURL=chunk-OI3Q6G7U.js.map