@mindrian_os/install 1.13.0-beta.24 → 1.13.0-beta.26
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/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +16 -0
- package/agents/brain-query.md +12 -15
- package/agents/grading.md +14 -26
- package/agents/investor.md +6 -7
- package/agents/research.md +1 -2
- package/commands/act.md +8 -8
- package/commands/rs-experts.md +3 -1
- package/commands/rs-explain.md +2 -2
- package/commands/rs-thesis.md +3 -1
- package/lib/agents/mva/brain-classic-traps.cjs +29 -51
- package/lib/brain/chain-recommender.cjs +14 -8
- package/lib/brain/framework-chain-slice.cjs +89 -70
- package/lib/core/brain-client.cjs +54 -0
- package/lib/core/brain-derivation-prompts.cjs +15 -10
- package/lib/core/brain-derivation.cjs +16 -2
- package/lib/core/rs-chain-feeder.cjs +62 -30
- package/lib/core/rs-nl-to-query.cjs +16 -6
- package/lib/hmi/cross-room-memory.cjs +72 -29
- package/lib/mcp/brain-router.cjs +69 -55
- package/lib/memory/brain-cypher-chain-slice.test.cjs +143 -143
- package/lib/memory/brain-derivation.test.cjs +10 -5
- package/package.json +1 -1
- package/references/brain/query-patterns.md +29 -17
|
@@ -261,18 +261,35 @@ function buildBrainQueryContext(localContext) {
|
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
// ============================================================
|
|
264
|
-
// Brain
|
|
264
|
+
// Brain search -- SINGLE chokepoint (Tripwire #2).
|
|
265
265
|
// ============================================================
|
|
266
266
|
//
|
|
267
267
|
// The ONE AND ONLY function in this module that reaches Brain. Every
|
|
268
268
|
// other code path that wants Brain enrichment routes through here.
|
|
269
|
+
//
|
|
270
|
+
// BUG FIX 2026-05-22: the former implementation called client.query(payload)
|
|
271
|
+
// where payload was a plain OBJECT (assembled by tryBrainHints). brain_query
|
|
272
|
+
// requires a Cypher STRING; passing an object causes MCP input validation to
|
|
273
|
+
// fail silently, making Mode A cross-room enrichment dead for all users.
|
|
274
|
+
//
|
|
275
|
+
// Fix: route the JTBD co-occurrence hint lookup through the UNGATED
|
|
276
|
+
// brain_search (semantic) instead. We build a generic methodology query
|
|
277
|
+
// string from the allow-listed context keys and call client.search().
|
|
278
|
+
// brain_search accepts a free-text query string and returns semantic
|
|
279
|
+
// matches from the Pinecone index -- no admin gate, no Cypher string
|
|
280
|
+
// required, no user content (the query string is derived ONLY from
|
|
281
|
+
// allow-listed enums and slugs per Canon Part 8).
|
|
282
|
+
//
|
|
283
|
+
// Result shape from brain_search: { matches: Array<{title,score,...}> }
|
|
284
|
+
// or { records: Array<...> } depending on the server version. We adapt
|
|
285
|
+
// both shapes in tryBrainHints below.
|
|
269
286
|
|
|
270
|
-
async function
|
|
287
|
+
async function tryBrainSearch(queryText) {
|
|
271
288
|
const client = getBrainClient();
|
|
272
|
-
if (!client || typeof client.
|
|
289
|
+
if (!client || typeof client.search !== 'function') return null;
|
|
273
290
|
// The single Brain call site. (Tripwire #2: exactly one match for
|
|
274
|
-
// /(?:
|
|
275
|
-
return await client.
|
|
291
|
+
// /(?:tryBrainSearch|client\.search)\(/ in the entire module.)
|
|
292
|
+
return await client.search(queryText, { topK: 3 });
|
|
276
293
|
}
|
|
277
294
|
|
|
278
295
|
// ============================================================
|
|
@@ -411,32 +428,58 @@ async function tryBrainHints(byJtbd) {
|
|
|
411
428
|
const jtbds = Object.keys(byJtbd);
|
|
412
429
|
if (jtbds.length === 0) return null;
|
|
413
430
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
431
|
+
// Build a SINGLE generic methodology query string from the JTBD slugs.
|
|
432
|
+
// The query string is derived from allow-listed slug tokens only (no user
|
|
433
|
+
// prose, no artifact bodies, no room names). Canon Part 8 safe.
|
|
434
|
+
//
|
|
435
|
+
// We send ONE search call for all JTBDs together (rather than one per JTBD)
|
|
436
|
+
// to minimize Brain round-trips; the semantic results are generic methodology
|
|
437
|
+
// patterns applicable to any venture doing these jobs.
|
|
438
|
+
const safeJtbdList = jtbds
|
|
439
|
+
.filter(function (j) { return typeof j === 'string' && /^[a-z0-9-]{1,64}$/.test(j); })
|
|
440
|
+
.slice(0, 8);
|
|
441
|
+
|
|
442
|
+
if (safeJtbdList.length === 0) return null;
|
|
443
|
+
|
|
444
|
+
// Query string: fully generic methodology language (no user content).
|
|
445
|
+
// e.g. "methodology patterns for jtbd: prepare-pitch find-bottleneck"
|
|
446
|
+
const queryText = 'methodology patterns for jtbd: ' + safeJtbdList.join(' ');
|
|
447
|
+
|
|
448
|
+
// Tripwire #4: scan the outgoing query string before send.
|
|
449
|
+
const queryPayload = { q: queryText };
|
|
450
|
+
if (!payloadIsClean(queryPayload)) {
|
|
451
|
+
// Query audit failed; abort Mode A.
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
421
454
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
455
|
+
let r;
|
|
456
|
+
try {
|
|
457
|
+
r = await tryBrainSearch(queryText);
|
|
458
|
+
} catch (err) {
|
|
459
|
+
// Re-throw so outer aggregateAcrossRooms can record the warning
|
|
460
|
+
// row. This is the "read-only Brain failure mid-render" case --
|
|
461
|
+
// we degrade to Mode B but stamp a single warning row.
|
|
462
|
+
throw err;
|
|
463
|
+
}
|
|
427
464
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
465
|
+
// brain_search returns { matches: [...] } or { records: [...] }.
|
|
466
|
+
// Extract the pattern list and assign uniformly across all JTBDs.
|
|
467
|
+
// (The response is generic -- there are no per-JTBD buckets from semantic
|
|
468
|
+
// search; we attribute the same hint set to every JTBD in the batch.)
|
|
469
|
+
const matchItems = (r && Array.isArray(r.matches)) ? r.matches
|
|
470
|
+
: (r && Array.isArray(r.records)) ? r.records
|
|
471
|
+
: [];
|
|
472
|
+
|
|
473
|
+
if (matchItems.length === 0) return null;
|
|
474
|
+
|
|
475
|
+
// Map semantic matches to the patterns shape the caller expects.
|
|
476
|
+
const patterns = matchItems.slice(0, 5).map(function (m) {
|
|
477
|
+
return { title: m.title || m.name || 'methodology pattern', score: m.score || 0 };
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
const hints = {};
|
|
481
|
+
for (const jtbd of safeJtbdList) {
|
|
482
|
+
hints[jtbd] = patterns;
|
|
440
483
|
}
|
|
441
484
|
|
|
442
485
|
return Object.keys(hints).length > 0 ? hints : null;
|
package/lib/mcp/brain-router.cjs
CHANGED
|
@@ -254,7 +254,13 @@ function localRoute(roomDir, stateContent, intent) {
|
|
|
254
254
|
// ---------------------------------------------------------------------------
|
|
255
255
|
|
|
256
256
|
/**
|
|
257
|
-
* Call Brain API for framework recommendation.
|
|
257
|
+
* Call Brain API for framework recommendation via brain_ask (ungated).
|
|
258
|
+
* Replaced the former raw-Cypher brain_query path (admin-gated, BUG 2)
|
|
259
|
+
* with brain.ask(question) -- valid for all API keys.
|
|
260
|
+
* Reads next_gate.options[].framework for the ranked chain.
|
|
261
|
+
* Canon Part 8: the NL question carries only the generic problem-type enum,
|
|
262
|
+
* never user content or artifact text.
|
|
263
|
+
*
|
|
258
264
|
* @param {string} roomDir
|
|
259
265
|
* @param {string} stateContent
|
|
260
266
|
* @param {string} [intent]
|
|
@@ -270,65 +276,73 @@ async function brainRoute(roomDir, stateContent, intent) {
|
|
|
270
276
|
|
|
271
277
|
if (!brainClient.isAvailable()) return null;
|
|
272
278
|
|
|
273
|
-
//
|
|
279
|
+
// Build a generic NL question carrying only the problem-type enum.
|
|
280
|
+
// Canon Part 8: no user content, no artifact text, no proprietary numbers.
|
|
274
281
|
const { definition, complexity } = extractProblemType(stateContent);
|
|
282
|
+
const safeDefinition = definition.replace(/[^a-zA-Z-]/g, '') || 'undefined';
|
|
283
|
+
const safeComplexity = complexity.replace(/[^a-zA-Z-]/g, '') || 'complex';
|
|
284
|
+
const question = 'recommend a framework for a ' + safeDefinition + ' definition '
|
|
285
|
+
+ safeComplexity + ' problem';
|
|
286
|
+
|
|
287
|
+
let brainResult;
|
|
288
|
+
try {
|
|
289
|
+
if (typeof brainClient.ask !== 'function') return null;
|
|
290
|
+
brainResult = await brainClient.ask(question);
|
|
291
|
+
} catch (_e) {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (!brainResult) return null;
|
|
296
|
+
|
|
297
|
+
// Read next_gate.options[] for the ranked framework chain.
|
|
298
|
+
// Gracefully handle both presence and absence of next_gate.
|
|
299
|
+
const options = (brainResult.next_gate && Array.isArray(brainResult.next_gate.options))
|
|
300
|
+
? brainResult.next_gate.options
|
|
301
|
+
: [];
|
|
275
302
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
//
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
OPTIONAL MATCH (f)-[:FEEDS_INTO]->(f_next:Framework)
|
|
287
|
-
WITH f, pt, collect(DISTINCT f_next.name) AS feeds_chain
|
|
288
|
-
OPTIONAL MATCH (f)-[:CO_OCCURS]->(f_co:Framework)
|
|
289
|
-
WITH f, pt, feeds_chain, collect(DISTINCT f_co.name) AS co_chain
|
|
290
|
-
RETURN f.name AS primary,
|
|
291
|
-
CASE WHEN size(feeds_chain) > 0 THEN feeds_chain[..3]
|
|
292
|
-
ELSE co_chain[..3] END AS chain,
|
|
293
|
-
pt.name AS problem_type,
|
|
294
|
-
CASE WHEN size(feeds_chain) > 0 THEN 'feeds_into'
|
|
295
|
-
ELSE 'co_occurs' END AS chain_type
|
|
296
|
-
LIMIT 3
|
|
297
|
-
`;
|
|
298
|
-
|
|
299
|
-
const result = await brainClient.query(cypher);
|
|
300
|
-
|
|
301
|
-
if (result && result.records && result.records.length > 0) {
|
|
302
|
-
const rec = result.records[0];
|
|
303
|
-
const primary = rec.primary || rec[0];
|
|
304
|
-
const chainNames = rec.chain || rec[1] || [];
|
|
305
|
-
const fullChain = [primary, ...chainNames].filter(Boolean);
|
|
306
|
-
|
|
307
|
-
// Map Brain framework names to CLI command names
|
|
308
|
-
const mappedChain = fullChain
|
|
309
|
-
.map(name => {
|
|
310
|
-
const normalized = name.toLowerCase().replace(/[^a-z-]/g, '');
|
|
311
|
-
return KNOWN_METHODOLOGIES.find(m => m === normalized || normalized.includes(m))
|
|
312
|
-
|| normalized;
|
|
313
|
-
})
|
|
314
|
-
.filter(Boolean)
|
|
315
|
-
.slice(0, 4);
|
|
316
|
-
|
|
317
|
-
if (mappedChain.length > 0) {
|
|
318
|
-
const chainType = rec.chain_type || rec[3] || 'co_occurs';
|
|
319
|
-
return {
|
|
320
|
-
chain: mappedChain,
|
|
321
|
-
confidence: 0.85,
|
|
322
|
-
source: 'brain',
|
|
323
|
-
chain_type: chainType,
|
|
324
|
-
reasoning: `Brain recommends ${mappedChain.join(' -> ')} for ${definition} ${complexity} problem. ` +
|
|
325
|
-
`Chain derived from ${chainType === 'feeds_into' ? 'FEEDS_INTO (sequential)' : 'CO_OCCURS (complementary)'} relationships.`,
|
|
326
|
-
target_sections: []
|
|
327
|
-
};
|
|
303
|
+
const anchorFramework = (brainResult.directive && brainResult.directive.guided)
|
|
304
|
+
? (brainResult.directive.guided.framework || null)
|
|
305
|
+
: null;
|
|
306
|
+
|
|
307
|
+
// Build the chain: anchor first (if present + not already in options), then options[].framework.
|
|
308
|
+
const rawChain = [];
|
|
309
|
+
if (anchorFramework && typeof anchorFramework === 'string') rawChain.push(anchorFramework);
|
|
310
|
+
for (const opt of options) {
|
|
311
|
+
if (opt && typeof opt.framework === 'string' && opt.framework.length > 0) {
|
|
312
|
+
if (!rawChain.includes(opt.framework)) rawChain.push(opt.framework);
|
|
328
313
|
}
|
|
329
314
|
}
|
|
330
315
|
|
|
331
|
-
return null;
|
|
316
|
+
if (rawChain.length === 0) return null;
|
|
317
|
+
|
|
318
|
+
// Map Brain framework names to CLI methodology names (slug normalization).
|
|
319
|
+
// This mirrors the previous mapping in the old Cypher path.
|
|
320
|
+
const mappedChain = rawChain
|
|
321
|
+
.map(function (name) {
|
|
322
|
+
const normalized = name.toLowerCase().replace(/[^a-z-]/g, '');
|
|
323
|
+
return KNOWN_METHODOLOGIES.find(function (m) { return m === normalized || normalized.includes(m); })
|
|
324
|
+
|| normalized;
|
|
325
|
+
})
|
|
326
|
+
.filter(Boolean)
|
|
327
|
+
.slice(0, 4);
|
|
328
|
+
|
|
329
|
+
if (mappedChain.length === 0) return null;
|
|
330
|
+
|
|
331
|
+
// Derive top confidence from the options array (or default 0.8).
|
|
332
|
+
const topConf = (options.length > 0 && typeof options[0].confidence === 'number')
|
|
333
|
+
? options[0].confidence
|
|
334
|
+
: 0.8;
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
chain: mappedChain,
|
|
338
|
+
confidence: topConf,
|
|
339
|
+
source: 'brain',
|
|
340
|
+
chain_type: 'feeds_into',
|
|
341
|
+
reasoning: 'Brain recommends ' + mappedChain.join(' -> ') + ' for '
|
|
342
|
+
+ safeDefinition + ' ' + safeComplexity + ' problem. '
|
|
343
|
+
+ 'Chain derived from brain_ask FEEDS_INTO recommendations.',
|
|
344
|
+
target_sections: [],
|
|
345
|
+
};
|
|
332
346
|
}
|
|
333
347
|
|
|
334
348
|
// ---------------------------------------------------------------------------
|