@mindrian_os/cli 2.0.0-beta.33 → 2.0.0-beta.35
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 +56 -0
- package/bin/mindrian-brain-mcp-client.cjs +14 -0
- package/commands/doctor.md +2 -2
- package/data/brain-census.generated.json +2827 -1523
- package/lib/core/brain-client.cjs +47 -3
- package/lib/core/brain-prewarm.cjs +164 -0
- package/lib/core/doctor/class-m-brain-smoke.cjs +160 -26
- package/lib/core/doctor/class-m-brain-smoke.test.cjs +441 -82
- package/lib/core/integration-registry.cjs +96 -20
- package/lib/mcp/brain-composition-census.cjs +22 -6
- package/lib/mcp/brain-route-bound.cjs +66 -0
- package/lib/mcp/brain-router.cjs +25 -10
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/scripts/build-brain-census.cjs +321 -4
- package/scripts/doctor.cjs +22 -7
- package/scripts/session-start +22 -0
- package/skills/doctor/SKILL.md +2 -2
|
@@ -463,6 +463,15 @@ function renderMarkdown(census) {
|
|
|
463
463
|
lines.push('> ' + GENERATED_NOTE + '. This file is rendered by scripts/build-brain-census.cjs. Edit the source lane data, not this file.');
|
|
464
464
|
lines.push('');
|
|
465
465
|
|
|
466
|
+
// Quick task 260911-axz: name the origin and run date in prose near the
|
|
467
|
+
// top, not only in the Census Meta table below, so a reader who lands
|
|
468
|
+
// mid-file cannot mistake a Theo census for the incumbent one. Additive
|
|
469
|
+
// and gated on the Theo shape; the incumbent render carries no such line.
|
|
470
|
+
if ((laneB && laneB.theo_shape) || _isTheoStatsShape(meta.brain_stats)) {
|
|
471
|
+
lines.push('**This census describes the Theo origin `' + _fmtVal(meta.brain_url) + '` as of ' + _fmtVal(meta.census_date) + '.**');
|
|
472
|
+
lines.push('');
|
|
473
|
+
}
|
|
474
|
+
|
|
466
475
|
// ---- Census Meta ----
|
|
467
476
|
lines.push('## Census Meta');
|
|
468
477
|
lines.push('');
|
|
@@ -470,7 +479,11 @@ function renderMarkdown(census) {
|
|
|
470
479
|
lines.push('| ----- | ----- |');
|
|
471
480
|
lines.push('| Census date | ' + _fmtVal(meta.census_date) + ' |');
|
|
472
481
|
lines.push('| Brain URL | ' + _fmtVal(meta.brain_url) + ' |');
|
|
473
|
-
if (meta.brain_stats) {
|
|
482
|
+
if (meta.brain_stats && _isTheoStatsShape(meta.brain_stats)) {
|
|
483
|
+
lines.push('| brain_stats nodes | ' + _fmtVal(meta.brain_stats.nodes) + ' |');
|
|
484
|
+
lines.push('| brain_stats relationships | ' + _fmtVal(meta.brain_stats.relationships) + ' |');
|
|
485
|
+
lines.push('| brain_stats labels | ' + _fmtVal(Array.isArray(meta.brain_stats.labels) ? meta.brain_stats.labels.length : null) + ' |');
|
|
486
|
+
} else if (meta.brain_stats) {
|
|
474
487
|
lines.push('| brain_stats backend | ' + _fmtVal(meta.brain_stats.backend) + ' |');
|
|
475
488
|
lines.push('| brain_stats totalRecordCount | ' + _fmtVal(meta.brain_stats.totalRecordCount) + ' |');
|
|
476
489
|
lines.push('| brain_stats relationshipCount | ' + _fmtVal(meta.brain_stats.relationshipCount) + ' |');
|
|
@@ -505,6 +518,124 @@ function renderMarkdown(census) {
|
|
|
505
518
|
'PENDING - Lane B aggregate Cypher not yet run (admin key not supplied as of ' +
|
|
506
519
|
_fmtVal(meta.census_date) + '); Lane A below is complete and citable.'
|
|
507
520
|
);
|
|
521
|
+
} else if (laneB.theo_shape) {
|
|
522
|
+
// Quick task 260911-axz: the Theo lane. Sourced from brain_stats (C1,
|
|
523
|
+
// C5) and brain_schema (C6-equivalent) rather than the whole-graph
|
|
524
|
+
// Cypher those incumbent queries need; C2, C2a-d, C3, C4, C7, C8, C9 are
|
|
525
|
+
// still attempted and any refusal is rendered as a refusal, never a
|
|
526
|
+
// blank section.
|
|
527
|
+
const r = laneB.results || {};
|
|
528
|
+
lines.push('Source: Theo live `brain_stats` + `brain_schema`. Theo\'s read allow-list rejects a whole-graph ' +
|
|
529
|
+
'AllNodesScan plan (the C1/C5 queries share it), so those two are answered directly from `brain_stats` ' +
|
|
530
|
+
'instead of Cypher; every other query below was still attempted and any refusal is recorded honestly.');
|
|
531
|
+
lines.push('');
|
|
532
|
+
lines.push('**C1 Framework totals (source: brain_stats.labels, not a Cypher count):** ' + _fmtVal(r.C1 && r.C1.frameworks) + ' Framework nodes.');
|
|
533
|
+
lines.push('');
|
|
534
|
+
if (r.C2 && r.C2.refused) {
|
|
535
|
+
lines.push('**C2/C3 HAS_* structure coverage:** refused by Theo -- ' + _fmtVal(r.C2.error));
|
|
536
|
+
} else if (Array.isArray(r.C2)) {
|
|
537
|
+
lines.push('**C2/C3 HAS_* structure coverage (grouped alternation):**');
|
|
538
|
+
lines.push('');
|
|
539
|
+
lines.push('| Edge type | Edges | Frameworks with edge |');
|
|
540
|
+
lines.push('| --------- | ----- | --------------------- |');
|
|
541
|
+
for (const row of r.C2) {
|
|
542
|
+
lines.push('| ' + _fmtVal(row.t) + ' | ' + _fmtVal(row.edges) + ' | ' + _fmtVal(row.frameworks_with_t) + ' |');
|
|
543
|
+
}
|
|
544
|
+
} else if (r.C2a || r.C2b || r.C2c || r.C2d) {
|
|
545
|
+
lines.push('**C2/C3 HAS_* structure coverage (single-type fallback used, grouped alternation refused):**');
|
|
546
|
+
lines.push('');
|
|
547
|
+
lines.push('| Edge type | Edges | Frameworks with edge |');
|
|
548
|
+
lines.push('| --------- | ----- | --------------------- |');
|
|
549
|
+
for (const key of ['C2a', 'C2b', 'C2c', 'C2d']) {
|
|
550
|
+
const row = r[key];
|
|
551
|
+
if (!row) continue;
|
|
552
|
+
if (row.refused) {
|
|
553
|
+
lines.push('| ' + key + ' | refused | ' + _fmtVal(row.error) + ' |');
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
const rr = Array.isArray(row) ? row[0] : row;
|
|
557
|
+
if (!rr) continue;
|
|
558
|
+
lines.push('| ' + _fmtVal(rr.t) + ' | ' + _fmtVal(rr.edges) + ' | ' + _fmtVal(rr.frameworks_with_t) + ' |');
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
lines.push('');
|
|
562
|
+
if (r.C3 && r.C3.refused) {
|
|
563
|
+
lines.push('C3 distinct frameworks with any structure: refused by Theo -- ' + _fmtVal(r.C3.error));
|
|
564
|
+
} else {
|
|
565
|
+
lines.push('C3 distinct frameworks with any structure: ' + _fmtVal(r.C3 && r.C3.structured_frameworks) + '.');
|
|
566
|
+
}
|
|
567
|
+
lines.push('');
|
|
568
|
+
{
|
|
569
|
+
const c4 = r.C4 || {};
|
|
570
|
+
const c4str = (sub) => {
|
|
571
|
+
const v = c4[sub];
|
|
572
|
+
if (v && typeof v === 'object' && v.refused) return 'refused (' + _fmtVal(v.error) + ')';
|
|
573
|
+
return _fmtVal(v);
|
|
574
|
+
};
|
|
575
|
+
lines.push('**C4 flow edge counts:** FEEDS_INTO=' + c4str('feeds_into') + ', LEADS_TO=' + c4str('leads_to') + ', ALIAS_OF=' + c4str('alias_of') + '.');
|
|
576
|
+
}
|
|
577
|
+
lines.push('');
|
|
578
|
+
lines.push('**C5 label census** (source: `brain_stats.labels`, a per-label count Theo already provides; ' +
|
|
579
|
+
'the incumbent\'s "counts sum to more than the node count" UNWIND caveat does not apply here and is not repeated):');
|
|
580
|
+
lines.push('');
|
|
581
|
+
lines.push('| Label | Count |');
|
|
582
|
+
lines.push('| ----- | ----- |');
|
|
583
|
+
for (const row of Array.isArray(r.C5) ? r.C5 : []) {
|
|
584
|
+
lines.push('| ' + _fmtVal(row.label) + ' | ' + _fmtVal(row.c) + ' |');
|
|
585
|
+
}
|
|
586
|
+
lines.push('');
|
|
587
|
+
lines.push('**C6 relationship-type census** (source: `brain_schema`; Theo reports relationship TYPE NAMES ' +
|
|
588
|
+
'without per-type counts, so the count column is n/a - never fabricated, never silently zero):');
|
|
589
|
+
lines.push('');
|
|
590
|
+
if (r.C6 && r.C6.refused) {
|
|
591
|
+
lines.push('refused by Theo -- ' + _fmtVal(r.C6.error));
|
|
592
|
+
} else {
|
|
593
|
+
lines.push('| Relationship type | Count |');
|
|
594
|
+
lines.push('| ------------------ | ----- |');
|
|
595
|
+
for (const t of (r.C6 && r.C6.relationship_types) || []) {
|
|
596
|
+
lines.push('| ' + _fmtVal(t) + ' | n/a |');
|
|
597
|
+
}
|
|
598
|
+
lines.push('');
|
|
599
|
+
lines.push('property_keys count: ' + _fmtVal(r.C6 && r.C6.property_keys_count) + '.');
|
|
600
|
+
}
|
|
601
|
+
lines.push('');
|
|
602
|
+
lines.push('**C7 duplicate-name scan:**');
|
|
603
|
+
lines.push('');
|
|
604
|
+
if (r.C7 && r.C7.refused) {
|
|
605
|
+
lines.push('refused by Theo -- ' + _fmtVal(r.C7.error));
|
|
606
|
+
} else if (!Array.isArray(r.C7) || r.C7.length === 0) {
|
|
607
|
+
lines.push('No duplicate Framework names found.');
|
|
608
|
+
} else {
|
|
609
|
+
lines.push('| Normalized key | Names | Count |');
|
|
610
|
+
lines.push('| -------------- | ----- | ----- |');
|
|
611
|
+
for (const row of r.C7) {
|
|
612
|
+
lines.push('| ' + _fmtVal(row.k) + ' | ' + _fmtVal(Array.isArray(row.names) ? row.names.join(', ') : row.names) + ' | ' + _fmtVal(row.c) + ' |');
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
lines.push('');
|
|
616
|
+
lines.push('**C8 named-absence probes (TRIZ/SCAMPER/Five Whys):**');
|
|
617
|
+
lines.push('');
|
|
618
|
+
if (r.C8 && r.C8.refused) {
|
|
619
|
+
lines.push('refused by Theo -- ' + _fmtVal(r.C8.error));
|
|
620
|
+
} else if (!Array.isArray(r.C8) || r.C8.length === 0) {
|
|
621
|
+
lines.push('Zero rows returned - TRIZ, SCAMPER, and Five Whys are absent as Framework nodes.');
|
|
622
|
+
} else {
|
|
623
|
+
lines.push('Rows returned (NOT absent): ' + r.C8.map((row) => row['f.name'] || row.name).join(', '));
|
|
624
|
+
}
|
|
625
|
+
lines.push('');
|
|
626
|
+
lines.push('**C9 JTBD alias enumeration:**');
|
|
627
|
+
lines.push('');
|
|
628
|
+
if (r.C9 && r.C9.refused) {
|
|
629
|
+
lines.push('refused by Theo -- ' + _fmtVal(r.C9.error));
|
|
630
|
+
} else if (!Array.isArray(r.C9) || r.C9.length === 0) {
|
|
631
|
+
lines.push('Zero rows returned.');
|
|
632
|
+
} else {
|
|
633
|
+
lines.push('| Name |');
|
|
634
|
+
lines.push('| ---- |');
|
|
635
|
+
for (const row of r.C9) {
|
|
636
|
+
lines.push('| ' + _fmtVal(row['f.name'] || row.name) + ' |');
|
|
637
|
+
}
|
|
638
|
+
}
|
|
508
639
|
} else {
|
|
509
640
|
if (laneB.drift_caveat) {
|
|
510
641
|
lines.push(
|
|
@@ -821,6 +952,159 @@ async function _runLaneB(key) {
|
|
|
821
952
|
return { results, usedFallback };
|
|
822
953
|
}
|
|
823
954
|
|
|
955
|
+
// ---------------------------------------------------------------------------
|
|
956
|
+
// _isTheoStatsShape(stats) - the same dual-shape discipline
|
|
957
|
+
// lib/core/doctor/class-m-brain-smoke.cjs's _layer6 already uses: recognize
|
|
958
|
+
// the incumbent's key (totalRecordCount) FIRST implicitly by requiring its
|
|
959
|
+
// absence here, so the incumbent path can never be misclassified as Theo.
|
|
960
|
+
// True when stats is an object carrying a finite `nodes` number and an
|
|
961
|
+
// array `labels`, and no `totalRecordCount` at all (quick task 260911-axz).
|
|
962
|
+
// ---------------------------------------------------------------------------
|
|
963
|
+
function _isTheoStatsShape(stats) {
|
|
964
|
+
return !!stats
|
|
965
|
+
&& typeof stats === 'object'
|
|
966
|
+
&& typeof stats.nodes === 'number'
|
|
967
|
+
&& Number.isFinite(stats.nodes)
|
|
968
|
+
&& Array.isArray(stats.labels)
|
|
969
|
+
&& !('totalRecordCount' in stats);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// ---------------------------------------------------------------------------
|
|
973
|
+
// _looksLikePlanRejection(result) - Theo's read allow-list refusal does NOT
|
|
974
|
+
// surface as an HTTP error or a JSON-RPC `error` field (both of which
|
|
975
|
+
// brainCall() already turns into `{ ok: false, bodyText }`): it surfaces as
|
|
976
|
+
// an ok:true tool result whose content is the plain-text string
|
|
977
|
+
// "PLAN_REJECTED: plan operator `X` is not on the read allow-list", which
|
|
978
|
+
// brainCall()'s JSON.parse failure wraps as `{ text: <that string> }`
|
|
979
|
+
// (found live, quick task 260911-axz, running C2/C3 against Theo -- both
|
|
980
|
+
// use `count(DISTINCT f)`, which Theo's allow-list rejects). Without this
|
|
981
|
+
// check, a caller reading `r.ok` alone would treat a refusal as a success
|
|
982
|
+
// and either render a blank section or, for C4, silently drop every field
|
|
983
|
+
// (`JSON.stringify` omits `undefined`-valued keys), which is exactly the
|
|
984
|
+
// "blank section" and silent-loss failure this generator exists to avoid.
|
|
985
|
+
// ---------------------------------------------------------------------------
|
|
986
|
+
function _looksLikePlanRejection(result) {
|
|
987
|
+
return !!result && typeof result === 'object' && typeof result.text === 'string'
|
|
988
|
+
&& /PLAN_REJECTED|not on the (read )?allow-?list/i.test(result.text);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// ---------------------------------------------------------------------------
|
|
992
|
+
// _theoQueryOutcome(r) - normalizes a brainCall() outcome into either the
|
|
993
|
+
// raw result or a `{ refused: true, error }` object, covering BOTH refusal
|
|
994
|
+
// shapes Theo can hand back: an HTTP/transport failure (r.ok === false) and
|
|
995
|
+
// the soft PLAN_REJECTED text payload above (r.ok === true). Every call site
|
|
996
|
+
// in _runLaneBTheo routes through this one function so a refusal is never
|
|
997
|
+
// classified two different ways in two different places.
|
|
998
|
+
// ---------------------------------------------------------------------------
|
|
999
|
+
function _theoQueryOutcome(r) {
|
|
1000
|
+
if (!r.ok) return { refused: true, error: r.bodyText };
|
|
1001
|
+
if (_looksLikePlanRejection(r.result)) return { refused: true, error: r.result.text };
|
|
1002
|
+
return r.result;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
function _isRefused(value) {
|
|
1006
|
+
return !!value && typeof value === 'object' && value.refused === true;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// ---------------------------------------------------------------------------
|
|
1010
|
+
// _runLaneBTheo(key, brainStats) - Theo lane B (quick task 260911-axz).
|
|
1011
|
+
// Additive only: never edits CENSUS_QUERIES or the incumbent _runLaneB
|
|
1012
|
+
// above. Sources C1 and C5 directly from the already-fetched brainStats
|
|
1013
|
+
// (Theo's read allow-list rejects the whole-graph AllNodesScan plan both of
|
|
1014
|
+
// those Cypher queries share), sources C6 from brain_schema (type NAMES
|
|
1015
|
+
// without per-type counts -- honestly recorded as n/a, never fabricated or
|
|
1016
|
+
// silently zero), and STILL ATTEMPTS C2, C2a-d, C3, C4, C7, C8, C9 through
|
|
1017
|
+
// brainCall('brain_query', ...), recording { refused: true, error } for any
|
|
1018
|
+
// id Theo refuses (either refusal shape, via _theoQueryOutcome) instead of
|
|
1019
|
+
// throwing (tests/test-246-census-guard.cjs pins all 13 query ids as Part 8
|
|
1020
|
+
// allow-classified; nothing here removes or rewords a query, only how a
|
|
1021
|
+
// refusal is recorded).
|
|
1022
|
+
// ---------------------------------------------------------------------------
|
|
1023
|
+
async function _runLaneBTheo(key, brainStats) {
|
|
1024
|
+
const results = {};
|
|
1025
|
+
|
|
1026
|
+
// C1: Framework total from brain_stats.labels, not a Cypher count.
|
|
1027
|
+
const frameworkEntry = (brainStats.labels || []).find((l) => l && l.label === 'Framework');
|
|
1028
|
+
results.C1 = {
|
|
1029
|
+
frameworks: frameworkEntry ? frameworkEntry.count : null,
|
|
1030
|
+
source: 'brain_stats.labels (not a Cypher count; Theo refuses the whole-graph plan C1 shares)',
|
|
1031
|
+
};
|
|
1032
|
+
|
|
1033
|
+
// C5-equivalent: brain_stats.labels IS already a per-label census; no
|
|
1034
|
+
// UNWIND is needed or attempted.
|
|
1035
|
+
results.C5 = (brainStats.labels || []).map((l) => ({ label: l && l.label, c: l && l.count }));
|
|
1036
|
+
|
|
1037
|
+
// C6-equivalent: brain_schema's relationship TYPE NAMES, no per-type
|
|
1038
|
+
// counts, plus a property_keys count.
|
|
1039
|
+
const schemaRes = await brainCall('brain_schema', {}, key);
|
|
1040
|
+
const schemaOutcome = _theoQueryOutcome(schemaRes);
|
|
1041
|
+
if (_isRefused(schemaOutcome)) {
|
|
1042
|
+
results.C6 = schemaOutcome;
|
|
1043
|
+
} else {
|
|
1044
|
+
const schemaResult = schemaOutcome || {};
|
|
1045
|
+
results.C6 = {
|
|
1046
|
+
relationship_types: Array.isArray(schemaResult.relationship_types) ? schemaResult.relationship_types : [],
|
|
1047
|
+
property_keys_count: Array.isArray(schemaResult.property_keys) ? schemaResult.property_keys.length : null,
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
const c2 = CENSUS_QUERIES.find((q) => q.id === 'C2');
|
|
1052
|
+
const r2 = await brainCall('brain_query', { cypher: c2.cypher }, key);
|
|
1053
|
+
const c2Outcome = _theoQueryOutcome(r2);
|
|
1054
|
+
results.C2 = c2Outcome;
|
|
1055
|
+
if (_isRefused(c2Outcome)) {
|
|
1056
|
+
for (const subId of ['C2a', 'C2b', 'C2c', 'C2d']) {
|
|
1057
|
+
const q = CENSUS_QUERIES.find((qq) => qq.id === subId);
|
|
1058
|
+
const r = await brainCall('brain_query', { cypher: q.cypher }, key);
|
|
1059
|
+
results[subId] = _theoQueryOutcome(r);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
const c3 = CENSUS_QUERIES.find((q) => q.id === 'C3');
|
|
1064
|
+
const r3 = await brainCall('brain_query', { cypher: c3.cypher }, key);
|
|
1065
|
+
const c3Outcome = _theoQueryOutcome(r3);
|
|
1066
|
+
results.C3 = _isRefused(c3Outcome) ? c3Outcome : (Array.isArray(c3Outcome) ? c3Outcome[0] : c3Outcome);
|
|
1067
|
+
|
|
1068
|
+
const c4entries = CENSUS_QUERIES.filter((q) => q.id === 'C4');
|
|
1069
|
+
const c4merged = {};
|
|
1070
|
+
for (const q of c4entries) {
|
|
1071
|
+
const r = await brainCall('brain_query', { cypher: q.cypher }, key);
|
|
1072
|
+
const outcome = _theoQueryOutcome(r);
|
|
1073
|
+
if (_isRefused(outcome)) {
|
|
1074
|
+
c4merged[q.sub] = outcome;
|
|
1075
|
+
continue;
|
|
1076
|
+
}
|
|
1077
|
+
const row = Array.isArray(outcome) ? outcome[0] : outcome;
|
|
1078
|
+
c4merged[q.sub] = row ? (q.sub in row ? row[q.sub] : null) : null;
|
|
1079
|
+
}
|
|
1080
|
+
results.C4 = c4merged;
|
|
1081
|
+
|
|
1082
|
+
for (const id of ['C7', 'C8', 'C9']) {
|
|
1083
|
+
const q = CENSUS_QUERIES.find((qq) => qq.id === id);
|
|
1084
|
+
const r = await brainCall('brain_query', { cypher: q.cypher }, key);
|
|
1085
|
+
results[id] = _theoQueryOutcome(r);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
return { results, usedFallback: false, theo_shape: true };
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
// ---------------------------------------------------------------------------
|
|
1092
|
+
// _isColdStartLikeFailure(callResult) - true when a brainCall() result looks
|
|
1093
|
+
// like Theo has not woken up yet (Render cold start) rather than a real,
|
|
1094
|
+
// persistent outage: a fetch failure (httpStatus 0), a client-side timeout
|
|
1095
|
+
// (errorKind 'timeout'), or a 502/503 from the edge. Quick task 260911-axz's
|
|
1096
|
+
// cold-start-one-retry rule (--lane-a) keys on this predicate so a genuine
|
|
1097
|
+
// outage (still failing after the 30s warm-up) is never mistaken for a
|
|
1098
|
+
// cold start and retried forever.
|
|
1099
|
+
// ---------------------------------------------------------------------------
|
|
1100
|
+
function _isColdStartLikeFailure(callResult) {
|
|
1101
|
+
if (!callResult || callResult.ok) return false;
|
|
1102
|
+
if (callResult.errorKind === 'timeout') return true;
|
|
1103
|
+
if (callResult.httpStatus === 0) return true;
|
|
1104
|
+
if (callResult.httpStatus === 502 || callResult.httpStatus === 503) return true;
|
|
1105
|
+
return false;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
824
1108
|
// ---------------------------------------------------------------------------
|
|
825
1109
|
// main() - four-mode CLI.
|
|
826
1110
|
// ---------------------------------------------------------------------------
|
|
@@ -854,6 +1138,26 @@ async function main() {
|
|
|
854
1138
|
console.error('Brain key unavailable: ' + r.reason);
|
|
855
1139
|
process.exit(1);
|
|
856
1140
|
}
|
|
1141
|
+
|
|
1142
|
+
// Cold start, one retry (quick task 260911-axz): Theo runs on Render and
|
|
1143
|
+
// sleeps. A reachability probe up front, separate from _runLaneA's own
|
|
1144
|
+
// per-framework loop, lets a cold-start failure retry ONCE after a 30s
|
|
1145
|
+
// warm-up window before any real probing begins. Only a SECOND failure
|
|
1146
|
+
// after the warm-up concludes a real outage; this generator then stops
|
|
1147
|
+
// and reports rather than writing a partial artifact.
|
|
1148
|
+
let reachProbe = await brainCall('brain_stats', {}, r.key);
|
|
1149
|
+
if (_isColdStartLikeFailure(reachProbe)) {
|
|
1150
|
+
console.error('[census] Theo unreachable on first attempt (' + (reachProbe.httpStatus || 'no status')
|
|
1151
|
+
+ ' ' + reachProbe.bodyText + '); this may be a Render cold start. Waiting 30s and retrying ONCE.');
|
|
1152
|
+
await new Promise((resolve) => setTimeout(resolve, 30000));
|
|
1153
|
+
reachProbe = await brainCall('brain_stats', {}, r.key);
|
|
1154
|
+
if (_isColdStartLikeFailure(reachProbe)) {
|
|
1155
|
+
console.error('[census] Theo still unreachable after the 30s retry: ' + (reachProbe.httpStatus || 'no status')
|
|
1156
|
+
+ ' ' + reachProbe.bodyText + '. Concluding a real outage, not a cold start. Refusing to write a partial artifact.');
|
|
1157
|
+
process.exit(1);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
857
1161
|
const scanned = scanMethodologyCommands();
|
|
858
1162
|
const laneAResult = await _runLaneA(r.key);
|
|
859
1163
|
const census = loadExisting();
|
|
@@ -888,18 +1192,31 @@ async function main() {
|
|
|
888
1192
|
console.error('Brain key unavailable: ' + r.reason);
|
|
889
1193
|
process.exit(1);
|
|
890
1194
|
}
|
|
891
|
-
|
|
1195
|
+
// Shape detection (quick task 260911-axz): a brain_stats call selects
|
|
1196
|
+
// the Theo lane (_runLaneBTheo) when its shape matches; otherwise
|
|
1197
|
+
// _runLaneB runs exactly as today, unedited. Recognizing the
|
|
1198
|
+
// incumbent's shape first (via the absence check inside
|
|
1199
|
+
// _isTheoStatsShape) is the proof of inertness: the incumbent path
|
|
1200
|
+
// cannot change.
|
|
1201
|
+
const statsProbe = await brainCall('brain_stats', {}, r.key);
|
|
1202
|
+
const brainStats = statsProbe.ok ? statsProbe.result : null;
|
|
1203
|
+
const isTheoShape = _isTheoStatsShape(brainStats);
|
|
1204
|
+
const laneB = isTheoShape ? await _runLaneBTheo(r.key, brainStats) : await _runLaneB(r.key);
|
|
1205
|
+
const laneBSource = isTheoShape
|
|
1206
|
+
? 'theo-live brain_stats + brain_schema (whole-graph Cypher refused by Theo read allow-list)'
|
|
1207
|
+
: 'render-live';
|
|
892
1208
|
const census = loadExisting();
|
|
893
1209
|
census.lane_b = {
|
|
894
1210
|
results: laneB.results,
|
|
895
|
-
lane_b_source:
|
|
1211
|
+
lane_b_source: laneBSource,
|
|
896
1212
|
key_tier_lane_b: 'admin',
|
|
897
1213
|
drift_caveat: false,
|
|
898
1214
|
used_c2_fallback: laneB.usedFallback,
|
|
899
1215
|
};
|
|
1216
|
+
if (isTheoShape) census.lane_b.theo_shape = true;
|
|
900
1217
|
census.meta = Object.assign({}, census.meta, {
|
|
901
1218
|
key_tier_lane_b: 'admin',
|
|
902
|
-
lane_b_source:
|
|
1219
|
+
lane_b_source: laneBSource,
|
|
903
1220
|
});
|
|
904
1221
|
writeOut(census);
|
|
905
1222
|
console.log('Lane B complete: results for ' + Object.keys(laneB.results).join(', '));
|
package/scripts/doctor.cjs
CHANGED
|
@@ -371,9 +371,10 @@ Class flags (combine freely; --all activates them all):
|
|
|
371
371
|
transcripts at ~/.claude/projects/.../*.jsonl for /mos:<deprecated>
|
|
372
372
|
patterns; surfaces a per-command "use /mos:<new> instead" hint.
|
|
373
373
|
Phase 121.5-08 Sub-plan J. LOCAL-only, zero network.)
|
|
374
|
-
--brain-smoke class M (Brain end-to-end smoke:
|
|
375
|
-
|
|
376
|
-
|
|
374
|
+
--brain-smoke class M (Brain end-to-end smoke: 7-layer probe -- origin and
|
|
375
|
+
shadow connector (quick 260911-axz), plugin root, key resolver,
|
|
376
|
+
HTTPS schema, MCP stdio handshake, e2e brain_schema, store identity
|
|
377
|
+
(stale-replica detection, quick 260819-c9b).
|
|
377
378
|
Diagnostic-only; reports the exact failing layer. Phase 127-02.)
|
|
378
379
|
--eureka-smoke class S (Eureka local-embedding-stack smoke: 4-layer probe --
|
|
379
380
|
deps present, vec backend, model cache, graceful degrade.
|
|
@@ -4198,15 +4199,17 @@ async function classMBrainSmoke(flags) {
|
|
|
4198
4199
|
if (flags.json) {
|
|
4199
4200
|
console.log(JSON.stringify(Object.assign({ class: 'M' }, result), null, 2));
|
|
4200
4201
|
} else {
|
|
4201
|
-
console.log('Class M -- Brain end-to-end smoke (
|
|
4202
|
+
console.log('Class M -- Brain end-to-end smoke (7-layer probe)');
|
|
4202
4203
|
console.log(' Overall: ' + (result.ok ? 'PASS' : 'FAIL') + ' (' + result.overall_ms + 'ms)');
|
|
4203
4204
|
for (const layer of result.layers) {
|
|
4204
4205
|
const marker = layer.ok ? 'PASS' : 'FAIL';
|
|
4205
4206
|
console.log(' [' + marker + '] ' + layer.name + ' -- ' + layer.reason + ' (' + layer.ms + 'ms)');
|
|
4206
4207
|
// Handoff section 7 item g: report which endpoint the wire resolved
|
|
4207
|
-
// to and whether it is canon.
|
|
4208
|
-
//
|
|
4209
|
-
|
|
4208
|
+
// to and whether it is canon. Branches on layer.id so the pre-existing
|
|
4209
|
+
// store_identity shape stays byte-identical and the new origin_shadow
|
|
4210
|
+
// shape (quick task 260911-axz) gets its own rendering instead of
|
|
4211
|
+
// printing "endpoint=undefined node_count=undefined canon=undefined".
|
|
4212
|
+
if (layer.payload && layer.id === 'store_identity') {
|
|
4210
4213
|
const p = layer.payload;
|
|
4211
4214
|
console.log(' endpoint=' + p.endpoint + ' node_count=' + p.node_count
|
|
4212
4215
|
+ ' canon=' + p.canon + (p.override ? ' override=true' : ''));
|
|
@@ -4217,6 +4220,18 @@ async function classMBrainSmoke(flags) {
|
|
|
4217
4220
|
if (p.stamp.refreshed_at != null) stampParts.push('refreshed_at=' + p.stamp.refreshed_at);
|
|
4218
4221
|
console.log(' GraphRagMeta stamp: ' + stampParts.join(' '));
|
|
4219
4222
|
}
|
|
4223
|
+
} else if (layer.payload && layer.id === 'origin_shadow') {
|
|
4224
|
+
const p = layer.payload;
|
|
4225
|
+
console.log(' origin=' + p.resolved_origin + ' theo=' + p.is_theo
|
|
4226
|
+
+ (p.override ? ' override=true' : ''));
|
|
4227
|
+
if (p.theo_health) {
|
|
4228
|
+
console.log(' theo_health mode=' + p.theo_health.mode
|
|
4229
|
+
+ (p.theo_health.build_sha != null ? ' sha=' + p.theo_health.build_sha : ''));
|
|
4230
|
+
}
|
|
4231
|
+
for (const s of (p.shadows || [])) {
|
|
4232
|
+
console.log(' shadow: scope=' + s.scope + ' host=' + s.url_host
|
|
4233
|
+
+ ' fix=`claude mcp remove mindrian-brain -s ' + s.scope + '`');
|
|
4234
|
+
}
|
|
4220
4235
|
}
|
|
4221
4236
|
}
|
|
4222
4237
|
}
|
package/scripts/session-start
CHANGED
|
@@ -1910,6 +1910,28 @@ fi
|
|
|
1910
1910
|
unset _BRAIN_KEY_JSON _BRAIN_AVAILABLE _BRAIN_REASON
|
|
1911
1911
|
# --- END Brain status ---
|
|
1912
1912
|
|
|
1913
|
+
# ---------------------------------------------------------------------------
|
|
1914
|
+
# Quick 260911-ddd (DDD-02): CLI-only pre-warm extra, never the only path.
|
|
1915
|
+
#
|
|
1916
|
+
# The MCP shim (bin/mindrian-brain-mcp-client.cjs) already fires this same
|
|
1917
|
+
# probe at startup on all three surfaces (CLI, Desktop, Cowork) because
|
|
1918
|
+
# .mcp.json registers it with alwaysLoad: true -- that is the one
|
|
1919
|
+
# surface-neutral pre-warm point. This spawn is a CLI-only belt-and-braces
|
|
1920
|
+
# extra: session-start fires earlier in the CLI turn than the shim
|
|
1921
|
+
# necessarily connects, so firing it here too shortens the CLI cold window
|
|
1922
|
+
# further without duplicating any state (both calls write the same marker
|
|
1923
|
+
# idempotently).
|
|
1924
|
+
#
|
|
1925
|
+
# This is NOT the egress quick 260910-h32 removed: that was a brain_write of
|
|
1926
|
+
# room data. This is a content-free theo_health READ with no arguments and
|
|
1927
|
+
# no room bytes -- it reintroduces no Brain write. Detached, guarded idiom
|
|
1928
|
+
# (matches the existing `( node ... >/dev/null 2>&1 || true ) &` pattern
|
|
1929
|
+
# used elsewhere in this file, e.g. the feynman-minto-guardian spawn below).
|
|
1930
|
+
# Deliberately NOT inside the `if [ -d "$ROOM_DIR" ]` guard: pre-warm has
|
|
1931
|
+
# nothing to do with a room.
|
|
1932
|
+
# ---------------------------------------------------------------------------
|
|
1933
|
+
( node "${PLUGIN_ROOT}/lib/core/brain-prewarm.cjs" >/dev/null 2>&1 || true ) &
|
|
1934
|
+
|
|
1913
1935
|
# ---------------------------------------------------------------------------
|
|
1914
1936
|
# 260517-dcw dogfood-bridge drain (Canon Part 6 Product-as-Venture)
|
|
1915
1937
|
# ---------------------------------------------------------------------------
|
package/skills/doctor/SKILL.md
CHANGED
|
@@ -76,7 +76,7 @@ Every check has a stable class letter (or a registry id). The class flag that ac
|
|
|
76
76
|
- **class J -- deployment-surfaces** (shares `--install-state`; `--fix` supported) -- reconciles every owned surface in `data/deployment-surfaces.json` against disk. Reads topology / active-root / active-version from class I's same-invocation result (self-derives via `shared.cjs` when absent). `--fix` re-stamps `ok:false` session-start-owned surfaces and prunes the marketplace cache.
|
|
77
77
|
- **class K -- stale-first-touch-copy** (`--stale-first-touch`; check-only) -- greeting surfaces declared by `data/first-touch-surfaces.json` (banner, splash, onboard, sessionstart, operator-update, larry-extended) scanned for stale version literals and U+2014 em-dash violations. SEED-007 absorption. Also activated by `--all`.
|
|
78
78
|
- **class L -- deprecated-usage** (`--deprecated-usage`; check-only) -- scans the last 7 days of `~/.claude/projects/.../*.jsonl` session transcripts for `/mos:<deprecated>` patterns and surfaces a per-command "use `/mos:<new>` instead" hint. Pure LOCAL scan; zero network, zero Brain. Also activated by `--all`.
|
|
79
|
-
- **class M -- brain-smoke** (`--brain-smoke`; check-only) --
|
|
79
|
+
- **class M -- brain-smoke** (`--brain-smoke`; check-only) -- 7-layer Brain end-to-end probe: layer 0 origin and shadow connector (quick task 260911-axz -- resolved Brain origin, Theo status, `MINDRIAN_BRAIN_URL` override, Theo's live mode/build sha, and any Claude Code `~/.claude.json` entry shadowing the plugin's own `mindrian-brain` shim), then plugin root resolver, key resolver, HTTPS schema, MCP stdio handshake, e2e brain_schema via the bundled shim, and store identity (stale-replica detection). Diagnostic-only; reports the exact failing layer. This is an async runner and stays special-cased (carve-out, below). Activated by `--all`.
|
|
80
80
|
- **class N -- plugin-enabled-state** (no flag; bare run + `--all`; check-only) -- the silent-disable watchdog (DRIFT-12). Reads `~/.claude/settings.json` enabledPlugins + installed_plugins.json; installed && enabled===false is CRITICAL with a re-enable hint. It runs on a bare run and under `--all` precisely because a DISABLED plugin cannot fire its own SessionStart hooks to report itself. LOCAL read only; never writes settings.json.
|
|
81
81
|
- **class P / Q / R -- drift** (`--drift`; opt-in, NOT in `--all`) -- class P is prose-vs-code drift (skill-vs-code + first-touch, report-only even under `--fix`), class Q is gsd-record drift (shells out to `gsd-tools validate health`, parses W007 ROADMAP gaps + I001 missing SUMMARYs; `--fix` writes the DRIFT.md baseline and stubs missing SUMMARYs), class R is runtime-reachability drift (FAILS NON-ZERO when a capability is WIRED in the connector registry but UNREACHABLE by `decide()` at runtime). LOCAL-only, zero network.
|
|
82
82
|
- **class S -- eureka-smoke** (`--eureka-smoke`; check-only) -- 4-layer Eureka local-embedding-stack probe (deps present, vec backend, model cache, graceful degrade). Non-cascading; never downloads a model unless `MINDRIAN_EUREKA_SMOKE_ALLOW_DOWNLOAD=1`; rolls into `--acceptance`. Another async carve-out (below). `--fix eureka` installs the local embedding stack into `~/.mindrian/eureka-deps/` (one-time, about 380 MB), the same action as `/mos:eureka enable`.
|
|
@@ -102,7 +102,7 @@ Look at the user's invocation:
|
|
|
102
102
|
- `/mos:doctor --install-state` -> class I (install-state + topology + 6-way version-of-record) + class J (deployment-surface reconciliation)
|
|
103
103
|
- `/mos:doctor --stale-first-touch` -> class K
|
|
104
104
|
- `/mos:doctor --deprecated-usage` -> class L
|
|
105
|
-
- `/mos:doctor --brain-smoke` -> class M (
|
|
105
|
+
- `/mos:doctor --brain-smoke` -> class M (7-layer Brain probe)
|
|
106
106
|
- `/mos:doctor --eureka-smoke` -> class S (4-layer Eureka probe; opt-in)
|
|
107
107
|
- `/mos:doctor --drift` -> class P + class Q + class R (opt-in; NOT in `--all`)
|
|
108
108
|
- `/mos:doctor --report-registration-bug` -> READ-ONLY escalation reporter (below). NOT a class flag, NOT part of `--all`, NOT a `--fix`.
|