@mindrian_os/install 1.13.0-beta.17 → 1.13.0-beta.19
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 +26 -0
- package/commands/act.md +1 -0
- package/commands/admin.md +1 -0
- package/commands/analyze-needs.md +2 -0
- package/commands/analyze-systems.md +2 -0
- package/commands/analyze-timing.md +2 -0
- package/commands/auto-explore.md +2 -0
- package/commands/beautiful-question.md +2 -0
- package/commands/brain-derive.md +2 -0
- package/commands/build-knowledge.md +2 -0
- package/commands/build-thesis.md +2 -0
- package/commands/causal.md +2 -0
- package/commands/challenge-assumptions.md +2 -0
- package/commands/compare-ventures.md +2 -0
- package/commands/dashboard.md +2 -1
- package/commands/deep-grade.md +2 -0
- package/commands/diagnose.md +21 -1
- package/commands/diagnostics.md +14 -3
- package/commands/doctor.md +4 -1
- package/commands/dogfood-flush.md +92 -0
- package/commands/dominant-designs.md +2 -0
- package/commands/explain-decision.md +2 -0
- package/commands/explore-domains.md +2 -0
- package/commands/explore-futures.md +2 -0
- package/commands/explore-trends.md +2 -0
- package/commands/export.md +1 -0
- package/commands/feynman-timeline-refresh.md +2 -0
- package/commands/file-meeting.md +2 -0
- package/commands/find-analogies.md +1 -0
- package/commands/find-bottlenecks.md +2 -0
- package/commands/find-connections.md +2 -0
- package/commands/funding.md +1 -0
- package/commands/grade.md +2 -0
- package/commands/graph.md +1 -0
- package/commands/hat-briefing.md +1 -0
- package/commands/heal.md +22 -170
- package/commands/help.md +54 -334
- package/commands/hmi-status.md +23 -144
- package/commands/jtbd.md +1 -0
- package/commands/leadership.md +2 -0
- package/commands/lean-canvas.md +2 -0
- package/commands/macro-trends.md +2 -0
- package/commands/map-unknowns.md +2 -0
- package/commands/memory.md +1 -0
- package/commands/models.md +1 -0
- package/commands/mos-reason.md +2 -0
- package/commands/mos.md +139 -0
- package/commands/mullins.md +2 -0
- package/commands/mva-brief.md +2 -0
- package/commands/mva-option.md +2 -0
- package/commands/new-project.md +2 -0
- package/commands/onboard.md +20 -7
- package/commands/operator.md +1 -0
- package/commands/opportunities.md +1 -0
- package/commands/organize.md +22 -469
- package/commands/persona.md +1 -0
- package/commands/pipeline.md +2 -0
- package/commands/present.md +1 -0
- package/commands/publish.md +2 -0
- package/commands/query.md +24 -102
- package/commands/radar.md +2 -0
- package/commands/reanalyze.md +1 -0
- package/commands/research.md +2 -0
- package/commands/room.md +2 -0
- package/commands/rooms.md +1 -0
- package/commands/root-cause.md +2 -0
- package/commands/rs-experts.md +1 -0
- package/commands/rs-explain.md +1 -0
- package/commands/rs-fetch.md +1 -0
- package/commands/rs-thesis.md +1 -0
- package/commands/scenario-plan.md +2 -0
- package/commands/scheduled-tasks.md +1 -0
- package/commands/score-innovation.md +2 -0
- package/commands/scout.md +1 -0
- package/commands/setup.md +2 -0
- package/commands/snapshot.md +2 -0
- package/commands/speakers.md +1 -0
- package/commands/splash.md +5 -2
- package/commands/status.md +1 -0
- package/commands/structure-argument.md +2 -0
- package/commands/suggest-next.md +2 -0
- package/commands/systems-thinking.md +2 -0
- package/commands/think-hats.md +2 -0
- package/commands/update.md +2 -0
- package/commands/user-needs.md +2 -0
- package/commands/validate.md +2 -0
- package/commands/value-proposition.md +2 -0
- package/commands/vault.md +2 -0
- package/commands/visualize.md +24 -29
- package/commands/whitespace.md +2 -1
- package/commands/wiki.md +1 -0
- package/hooks/hooks.json +22 -88
- package/lib/agents/auto-explore-agent.cjs +82 -0
- package/lib/core/breakthrough/canary.cjs +134 -0
- package/lib/core/breakthrough/canary.test.cjs +136 -0
- package/lib/core/breakthrough/detectors.cjs +359 -0
- package/lib/core/breakthrough/detectors.test.cjs +333 -0
- package/lib/core/breakthrough/ethics-fence.cjs +127 -0
- package/lib/core/breakthrough/ethics-fence.test.cjs +178 -0
- package/lib/core/breakthrough/resurfacing.cjs +150 -0
- package/lib/core/breakthrough/resurfacing.test.cjs +233 -0
- package/lib/core/breakthrough/review-queue.cjs +154 -0
- package/lib/core/breakthrough/review-queue.test.cjs +160 -0
- package/lib/core/breakthrough/scanner-d17-d18.test.cjs +229 -0
- package/lib/core/breakthrough/scanner.cjs +426 -0
- package/lib/core/breakthrough/scanner.test.cjs +267 -0
- package/lib/core/breakthrough/schema.cjs +164 -0
- package/lib/core/breakthrough/schema.test.cjs +256 -0
- package/lib/core/breakthrough/scoring.cjs +293 -0
- package/lib/core/breakthrough/scoring.test.cjs +423 -0
- package/lib/core/breakthrough/verb-dispatch.cjs +221 -0
- package/lib/core/breakthrough/verb-dispatch.test.cjs +185 -0
- package/lib/core/breakthrough/voice-scaffold.cjs +247 -0
- package/lib/core/breakthrough/voice-scaffold.test.cjs +251 -0
- package/lib/core/first-touch-version-stamper.cjs +113 -0
- package/lib/core/larry-thinness-acknowledgment.cjs +64 -0
- package/lib/core/larry-thinness-acknowledgment.test.cjs +97 -0
- package/lib/core/llm-name-suggester.cjs +194 -0
- package/lib/core/llm-name-suggester.test.cjs +132 -0
- package/lib/core/mva-orchestrator.cjs +41 -0
- package/lib/core/mva-telemetry.cjs +31 -143
- package/lib/core/navigation/edges.cjs +35 -0
- package/lib/core/navigation/memory-events.cjs +126 -0
- package/lib/core/room-auto-create.cjs +318 -0
- package/lib/core/room-auto-create.test.cjs +198 -0
- package/lib/core/room-discard-cascade.cjs +225 -0
- package/lib/core/room-discard-cascade.test.cjs +135 -0
- package/lib/core/room-name-validator.cjs +132 -0
- package/lib/core/room-name-validator.test.cjs +156 -0
- package/lib/core/room-naming-selector.cjs +357 -0
- package/lib/core/room-naming-selector.test.cjs +277 -0
- package/lib/core/room-receipt-emit.cjs +63 -0
- package/lib/core/room-skeleton-scaffold.cjs +315 -0
- package/lib/core/room-skeleton-scaffold.test.cjs +291 -0
- package/lib/core/stale-copy-scanner.cjs +190 -0
- package/lib/core/state-aware-router.cjs +78 -0
- package/lib/core/telemetry/schema.cjs +168 -0
- package/lib/core/telemetry/schema.test.cjs +124 -0
- package/lib/core/telemetry/validator.cjs +197 -0
- package/lib/core/telemetry/validator.test.cjs +188 -0
- package/lib/core/telemetry/writer.cjs +141 -0
- package/lib/core/telemetry/writer.test.cjs +331 -0
- package/lib/core/terminal-capability.cjs +88 -0
- package/lib/core/venture-shape-nudge.cjs +163 -0
- package/lib/core/venture-shape-nudge.test.cjs +161 -0
- package/lib/core/visual-ops.cjs +70 -2
- package/lib/hmi/selector-dispatcher.cjs +90 -1
- package/lib/hmi/shape-f7-breakthrough-renderer.cjs +222 -0
- package/lib/hmi/shape-f7-breakthrough-renderer.test.cjs +233 -0
- package/lib/memory/body-shape-coverage.test.cjs +268 -0
- package/lib/memory/doctor-deprecation-surface.test.cjs +185 -0
- package/lib/memory/first-touch-version.test.cjs +198 -0
- package/lib/memory/help-coverage.test.cjs +108 -0
- package/lib/memory/help-renderer.test.cjs +145 -0
- package/lib/memory/palette-consistency.test.cjs +127 -0
- package/lib/memory/pending-tension-store.cjs +80 -0
- package/lib/memory/render-v2-disposition.test.cjs +199 -0
- package/lib/memory/run-feynman-tests.cjs +213 -0
- package/lib/memory/sessionstart-coordinator.test.cjs +446 -0
- package/lib/memory/skill-vs-code-drift.test.cjs +257 -0
- package/lib/memory/soft-alias.test.cjs +144 -0
- package/lib/memory/stale-copy-scanner.test.cjs +291 -0
- package/lib/memory/state-aware-router.test.cjs +90 -0
- package/lib/memory/statusline-two-row.test.cjs +338 -0
- package/lib/memory/terminal-capability.test.cjs +155 -0
- package/lib/render/ROOM.md +74 -22
- package/lib/sessionstart/budget-compressor.cjs +130 -0
- package/lib/sessionstart/contributor-interface.cjs +134 -0
- package/lib/sessionstart/contributor-isolator.cjs +128 -0
- package/lib/sessionstart/precedence-ladder.cjs +47 -0
- package/lib/statusline/governing-thought-truncator.cjs +45 -0
- package/lib/statusline/two-row-renderer.cjs +186 -0
- package/lib/statusline/version-resolver.cjs +81 -0
- package/package.json +1 -1
- package/references/visual/ROOM.md +55 -0
- package/references/visual/palette.json +54 -0
- package/skills/larry-personality/SKILL.md +34 -0
- package/skills/ui-system/SKILL.md +109 -1
- package/skills/ui-system/rules/dual-palette.md +156 -0
- package/skills/ui-system/rules/glyph-disambiguation.md +171 -0
- package/skills/ui-system/rules/shape-f-zero-and-six.md +169 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Phase 120-00 Wave 1 -- the 4 breakthrough pattern detectors + classifyFireTier + the
|
|
3
|
+
// frozen threshold constants. Per CONTEXT.md D-01..D-06 + D-18 + D-20:
|
|
4
|
+
// - PURE functions over local graph state (Canon Part 8 -- no Brain coupling)
|
|
5
|
+
// - ALL chokepoint reads route via lib/core/navigation.cjs (Canon Part 9 D-06)
|
|
6
|
+
// - Every emitted Breakthrough carries provenance.artifact_ids (D-20 HARD FLOOR)
|
|
7
|
+
// - Reads Phase 117 math-layer JSON output; NEVER recomputes the math
|
|
8
|
+
//
|
|
9
|
+
// Canon Part 8 invariants (source-grep enforced by tests/test-120-00-scaffold.sh
|
|
10
|
+
// Gate 8 + the Test 12 in detectors.test.cjs):
|
|
11
|
+
// - NO require of lib/core/brain-client.cjs or any mcp-server-brain/* module.
|
|
12
|
+
// - NO fetch to brain.mindrian.* domain.
|
|
13
|
+
// - NO cross-user OR cross-room aggregation -- "cross-domain" in this module
|
|
14
|
+
// means cross-section WITHIN the same room (per CONTEXT.md scope D-04).
|
|
15
|
+
//
|
|
16
|
+
// Canon Part 9 invariant: the math layer (Phase 117) writes JSON files into
|
|
17
|
+
// roomDir/.mindrian/; detectors READ those files but NEVER re-execute the
|
|
18
|
+
// underlying Python scripts (no child_process.exec on hsi-*.py / rs-engine.py).
|
|
19
|
+
// Source-grep tripwire: zero matches for child_process.exec.+\.py.
|
|
20
|
+
//
|
|
21
|
+
// Em-dash HARD RULE (CLAUDE.md feedback_no_emdashes): use "--" not the U+2014
|
|
22
|
+
// character anywhere in this file (comments, log lines, strings).
|
|
23
|
+
|
|
24
|
+
const path = require('node:path');
|
|
25
|
+
const fs = require('node:fs');
|
|
26
|
+
const crypto = require('node:crypto');
|
|
27
|
+
|
|
28
|
+
// Pin the DETECTOR_THRESHOLDS to the verbatim values locked in CONTEXT.md
|
|
29
|
+
// D-01..D-06. Plan 120-02 session-start scanner reads these constants at
|
|
30
|
+
// scan time, so they MUST stay frozen-by-Object.freeze + immutable in test
|
|
31
|
+
// surface. Any change to these numbers is a canon-level decision, not a
|
|
32
|
+
// source-level edit -- requires re-running /gsd:discuss-phase 120.
|
|
33
|
+
const DETECTOR_THRESHOLDS = Object.freeze({
|
|
34
|
+
SOFT_FIRE_MIN_ARTIFACTS: 3, // D-02
|
|
35
|
+
SOFT_FIRE_MIN_CONFIDENCE: 0.25, // D-02
|
|
36
|
+
HARD_FIRE_MIN_ARTIFACTS: 4, // D-03
|
|
37
|
+
HARD_FIRE_CROSS_SECTION_BYPASS: 3, // D-03 OR clause
|
|
38
|
+
HARD_FIRE_MIN_CONFIDENCE: 0.35, // D-03
|
|
39
|
+
SEMANTIC_SIMILARITY_THRESHOLD: 0.40, // D-04 (within-room noise correlation pushes threshold up)
|
|
40
|
+
WINDOW_DAYS_DEFAULT: 14, // D-06 ethical fence (no surface for material > 14 days old)
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const DETECTOR_TYPES = Object.freeze([
|
|
44
|
+
'convergence',
|
|
45
|
+
'contradiction_resolved',
|
|
46
|
+
'cross_domain_analogy',
|
|
47
|
+
'reverse_salient_closed',
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
// Reverse-salient lagging-component signal threshold (D-05 score-delta signal).
|
|
51
|
+
// signed_diff magnitude >= 0.5 means the score crossed its baseline meaningfully.
|
|
52
|
+
const RS_SCORE_DELTA_THRESHOLD = 0.5;
|
|
53
|
+
|
|
54
|
+
// Helper: build a deterministic Breakthrough id from kind + sorted artifact_ids + ts.
|
|
55
|
+
function buildBreakthroughId(kind, artifactIds, nowMs) {
|
|
56
|
+
const seed = kind + ':' + artifactIds.slice().sort().join(',') + ':' + nowMs;
|
|
57
|
+
const hash = crypto.createHash('sha256').update(seed).digest('hex').slice(0, 16);
|
|
58
|
+
return 'breakthrough:' + kind + ':' + hash;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Helper: build a Breakthrough candidate with confidence formula applied. Per
|
|
62
|
+
// CONTEXT.md D-12 scoring blend: confidence = clip(0, 0.95, 0.25 + 0.10*count + 0.30*differential).
|
|
63
|
+
// The 0.10/0.30 weights deliberately bias the formula so cross-section linkage
|
|
64
|
+
// (an extra signal on top of count + differential) does NOT auto-promote a
|
|
65
|
+
// 3-artifact convergence past 0.35 -- classifyFireTier handles the bypass via
|
|
66
|
+
// the count-with-bypass OR clause + the conf >= 0.35 floor.
|
|
67
|
+
function buildCandidate(kind, artifactIds, theme, differential, crossSectionLinked, nowMs, windowMs) {
|
|
68
|
+
const filtered = (artifactIds || []).filter((s) => typeof s === 'string' && s.length > 0);
|
|
69
|
+
const baseConf = 0.25 + 0.10 * filtered.length + 0.30 * (typeof differential === 'number' ? differential : 0);
|
|
70
|
+
const confidence = Math.min(0.95, Math.max(0, baseConf));
|
|
71
|
+
return {
|
|
72
|
+
id: buildBreakthroughId(kind, filtered, nowMs),
|
|
73
|
+
kind: kind,
|
|
74
|
+
confidence: confidence,
|
|
75
|
+
artifact_ids: filtered,
|
|
76
|
+
theme: typeof theme === 'string' ? theme.slice(0, 200) : '',
|
|
77
|
+
differential: typeof differential === 'number' ? differential : 0,
|
|
78
|
+
cross_section_linked: !!crossSectionLinked,
|
|
79
|
+
detected_at: nowMs,
|
|
80
|
+
window_start: nowMs - windowMs,
|
|
81
|
+
window_end: nowMs,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function classifyFireTier(candidate) {
|
|
86
|
+
const T = DETECTOR_THRESHOLDS;
|
|
87
|
+
if (!candidate || !Array.isArray(candidate.artifact_ids)) return 'below_floor';
|
|
88
|
+
const count = candidate.artifact_ids.length;
|
|
89
|
+
const conf = typeof candidate.confidence === 'number' ? candidate.confidence : 0;
|
|
90
|
+
// D-03: hard-fire when (artifact_count >= 4) OR (>= 3 AND cross_section_linked),
|
|
91
|
+
// AND confidence >= 0.35.
|
|
92
|
+
const hard_eligible =
|
|
93
|
+
((count >= T.HARD_FIRE_MIN_ARTIFACTS) ||
|
|
94
|
+
(count >= T.HARD_FIRE_CROSS_SECTION_BYPASS && candidate.cross_section_linked))
|
|
95
|
+
&& conf >= T.HARD_FIRE_MIN_CONFIDENCE;
|
|
96
|
+
if (hard_eligible) return 'hard';
|
|
97
|
+
// D-02: soft-fire when artifact_count >= 3 AND confidence >= 0.25 (below hard tier).
|
|
98
|
+
const soft_eligible = count >= T.SOFT_FIRE_MIN_ARTIFACTS && conf >= T.SOFT_FIRE_MIN_CONFIDENCE;
|
|
99
|
+
if (soft_eligible) return 'soft';
|
|
100
|
+
return 'below_floor';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function partitionByTier(candidates) {
|
|
104
|
+
const hits = [];
|
|
105
|
+
const soft_fires = [];
|
|
106
|
+
for (const c of candidates) {
|
|
107
|
+
const tier = classifyFireTier(c);
|
|
108
|
+
if (tier === 'hard') hits.push(c);
|
|
109
|
+
else if (tier === 'soft') soft_fires.push(c);
|
|
110
|
+
// 'below_floor' falls off -- never surfaces, never logs to buffer.
|
|
111
|
+
}
|
|
112
|
+
return { hits: hits, soft_fires: soft_fires };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Internal: resolve the math-layer JSON path within roomDir/.mindrian/. Returns
|
|
116
|
+
// null when roomDir is invalid OR the file does not exist (graceful degradation:
|
|
117
|
+
// Phase 117 may not have fired yet on a fresh room).
|
|
118
|
+
function resolveMathFile(roomState, basename) {
|
|
119
|
+
if (!roomState || typeof roomState.roomDir !== 'string') return null;
|
|
120
|
+
const fullPath = path.join(roomState.roomDir, '.mindrian', basename);
|
|
121
|
+
if (!fs.existsSync(fullPath)) return null;
|
|
122
|
+
return fullPath;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function readMathJson(roomState, basename) {
|
|
126
|
+
const p = resolveMathFile(roomState, basename);
|
|
127
|
+
if (!p) return null;
|
|
128
|
+
try {
|
|
129
|
+
const raw = fs.readFileSync(p, 'utf8');
|
|
130
|
+
return JSON.parse(raw);
|
|
131
|
+
} catch (_e) {
|
|
132
|
+
// Malformed JSON returns null -- the caller short-circuits to empty result.
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Compute the windowMs + nowMs for a detector call. Caps windowDays at 14 (D-06).
|
|
138
|
+
function resolveWindow(roomState, opts) {
|
|
139
|
+
const nowMs = (roomState && typeof roomState.now === 'number') ? roomState.now : Date.now();
|
|
140
|
+
const requestedDays = (opts && typeof opts.window_days === 'number') ? opts.window_days
|
|
141
|
+
: ((roomState && typeof roomState.window_days === 'number') ? roomState.window_days
|
|
142
|
+
: DETECTOR_THRESHOLDS.WINDOW_DAYS_DEFAULT);
|
|
143
|
+
const cappedDays = Math.min(DETECTOR_THRESHOLDS.WINDOW_DAYS_DEFAULT, Math.max(1, requestedDays));
|
|
144
|
+
return { nowMs: nowMs, windowMs: cappedDays * 24 * 3600 * 1000 };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// --- detectConvergence (D-01 detector 1) ---
|
|
148
|
+
// Reads whitespace.gaps[] from .mindrian/whitespace-results.json (Phase 117 math output).
|
|
149
|
+
// Each gap: {gap_id?, theme, artifacts: string[], differential: number, sections?: string[],
|
|
150
|
+
// detected_at?: number}. A gap with N artifacts where N >= 3 (after empty-string filtering)
|
|
151
|
+
// is a candidate. cross_section_linked = sections.length >= 2.
|
|
152
|
+
function detectConvergence(roomState, opts) {
|
|
153
|
+
const { nowMs, windowMs } = resolveWindow(roomState, opts);
|
|
154
|
+
const data = readMathJson(roomState, 'whitespace-results.json');
|
|
155
|
+
if (!data || !Array.isArray(data.gaps)) {
|
|
156
|
+
return { hits: [], soft_fires: [] };
|
|
157
|
+
}
|
|
158
|
+
const windowStart = nowMs - windowMs;
|
|
159
|
+
const candidates = [];
|
|
160
|
+
for (const gap of data.gaps) {
|
|
161
|
+
if (!gap || typeof gap !== 'object') continue;
|
|
162
|
+
// Window filter: detected_at older than windowStart is excluded (D-06).
|
|
163
|
+
if (typeof gap.detected_at === 'number' && gap.detected_at < windowStart) continue;
|
|
164
|
+
const artifacts = Array.isArray(gap.artifacts) ? gap.artifacts.filter((s) => typeof s === 'string' && s.length > 0) : [];
|
|
165
|
+
if (artifacts.length < DETECTOR_THRESHOLDS.SOFT_FIRE_MIN_ARTIFACTS) continue;
|
|
166
|
+
const sections = Array.isArray(gap.sections) ? gap.sections.filter((s) => typeof s === 'string' && s.length > 0) : [];
|
|
167
|
+
const crossSectionLinked = sections.length >= 2;
|
|
168
|
+
const candidate = buildCandidate(
|
|
169
|
+
'convergence',
|
|
170
|
+
artifacts,
|
|
171
|
+
typeof gap.theme === 'string' ? gap.theme : '',
|
|
172
|
+
typeof gap.differential === 'number' ? gap.differential : 0,
|
|
173
|
+
crossSectionLinked,
|
|
174
|
+
nowMs,
|
|
175
|
+
windowMs
|
|
176
|
+
);
|
|
177
|
+
candidates.push(candidate);
|
|
178
|
+
}
|
|
179
|
+
return partitionByTier(candidates);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// --- detectContradictionResolved (D-01 detector 2) ---
|
|
183
|
+
// Reads CONTRADICTS edges from room.db within the window. When a CONTRADICTS edge
|
|
184
|
+
// in the window has properties.resolved === true, that's a candidate -- the pair
|
|
185
|
+
// of artifacts is the provenance. The "resolved" flag is set by Phase 116
|
|
186
|
+
// unresolved-tension-hook closure logic when a tension is resolved by a new artifact.
|
|
187
|
+
//
|
|
188
|
+
// Per Canon Part 9 D-06: this read goes through navigation.cjs. We lazy-require
|
|
189
|
+
// the navigation module so test code can override it (Test 6 seeds CONTRADICTS
|
|
190
|
+
// edges directly into the seeded db).
|
|
191
|
+
function detectContradictionResolved(roomState, opts) {
|
|
192
|
+
const { nowMs, windowMs } = resolveWindow(roomState, opts);
|
|
193
|
+
if (!roomState || !roomState.db) {
|
|
194
|
+
return { hits: [], soft_fires: [] };
|
|
195
|
+
}
|
|
196
|
+
const windowStart = nowMs - windowMs;
|
|
197
|
+
const candidates = [];
|
|
198
|
+
try {
|
|
199
|
+
// Query CONTRADICTS edges directly via the room.db handle. The edges table
|
|
200
|
+
// schema (per lib/core/lazygraph-ops.cjs::initSchema) is
|
|
201
|
+
// (source TEXT, target TEXT, type TEXT, properties TEXT).
|
|
202
|
+
// We filter by type='CONTRADICTS' and json_extract(properties,'$.resolved')='true'/1.
|
|
203
|
+
// The window filter applies to properties.resolved_at if present.
|
|
204
|
+
const rows = roomState.db.prepare(
|
|
205
|
+
"SELECT source, target, properties FROM edges WHERE type = 'CONTRADICTS'"
|
|
206
|
+
).all();
|
|
207
|
+
for (const row of rows) {
|
|
208
|
+
if (!row || typeof row.properties !== 'string') continue;
|
|
209
|
+
let props;
|
|
210
|
+
try { props = JSON.parse(row.properties); } catch (_e) { continue; }
|
|
211
|
+
if (!props || props.resolved !== true) continue;
|
|
212
|
+
const resolvedAt = typeof props.resolved_at === 'number' ? props.resolved_at : nowMs;
|
|
213
|
+
if (resolvedAt < windowStart) continue;
|
|
214
|
+
const sourceId = typeof row.source === 'string' ? row.source : '';
|
|
215
|
+
const targetId = typeof row.target === 'string' ? row.target : '';
|
|
216
|
+
const artifacts = [sourceId, targetId].filter((s) => s.length > 0);
|
|
217
|
+
if (artifacts.length < 2) continue;
|
|
218
|
+
const candidate = buildCandidate(
|
|
219
|
+
'contradiction_resolved',
|
|
220
|
+
artifacts,
|
|
221
|
+
typeof props.theme === 'string' ? props.theme : '',
|
|
222
|
+
typeof props.differential === 'number' ? props.differential : 0.5,
|
|
223
|
+
true, // cross-section is implicit for a CONTRADICTS pair (different artifacts)
|
|
224
|
+
nowMs,
|
|
225
|
+
windowMs
|
|
226
|
+
);
|
|
227
|
+
candidates.push(candidate);
|
|
228
|
+
}
|
|
229
|
+
} catch (_err) {
|
|
230
|
+
return { hits: [], soft_fires: [] };
|
|
231
|
+
}
|
|
232
|
+
// contradiction_resolved is inherently a 2-artifact event (source + target of the
|
|
233
|
+
// CONTRADICTS edge). The generic SOFT_FIRE_MIN_ARTIFACTS=3 floor was tuned for
|
|
234
|
+
// convergence (whitespace gaps with N>=3 by definition). For this detector we
|
|
235
|
+
// apply a separate tier rule: every resolved contradiction in the window fires
|
|
236
|
+
// at minimum at SOFT tier (since the act of resolution is itself the signal);
|
|
237
|
+
// promotion to HARD requires the confidence floor (>= 0.35) -- same as the
|
|
238
|
+
// generic D-03 hard floor.
|
|
239
|
+
const hits = [];
|
|
240
|
+
const soft_fires = [];
|
|
241
|
+
for (const c of candidates) {
|
|
242
|
+
if (c.confidence >= DETECTOR_THRESHOLDS.HARD_FIRE_MIN_CONFIDENCE) {
|
|
243
|
+
hits.push(c);
|
|
244
|
+
} else if (c.confidence >= DETECTOR_THRESHOLDS.SOFT_FIRE_MIN_CONFIDENCE) {
|
|
245
|
+
soft_fires.push(c);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return { hits: hits, soft_fires: soft_fires };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// --- detectCrossDomainAnalogy (D-01 detector 3) ---
|
|
252
|
+
// Reads analogy.zones[] from .mindrian/discovery-cycle-results.json (Phase 117 math output).
|
|
253
|
+
// Per CONTEXT.md SCOPE clarification + D-04: "cross-domain means cross-section within
|
|
254
|
+
// the same room, NEVER cross-user or cross-room aggregation". Cross-section is
|
|
255
|
+
// encoded by source_section !== target_section AND similarity >= 0.40.
|
|
256
|
+
function detectCrossDomainAnalogy(roomState, opts) {
|
|
257
|
+
const { nowMs, windowMs } = resolveWindow(roomState, opts);
|
|
258
|
+
const data = readMathJson(roomState, 'discovery-cycle-results.json');
|
|
259
|
+
if (!data) return { hits: [], soft_fires: [] };
|
|
260
|
+
// The file can either have analogy_whitespace.zones or analogy.zones depending on
|
|
261
|
+
// the Phase 117 writer version. Accept both.
|
|
262
|
+
const zones = (data.analogy_whitespace && Array.isArray(data.analogy_whitespace.zones)) ? data.analogy_whitespace.zones
|
|
263
|
+
: (data.analogy && Array.isArray(data.analogy.zones)) ? data.analogy.zones
|
|
264
|
+
: [];
|
|
265
|
+
if (!zones.length) return { hits: [], soft_fires: [] };
|
|
266
|
+
const windowStart = nowMs - windowMs;
|
|
267
|
+
const candidates = [];
|
|
268
|
+
for (const zone of zones) {
|
|
269
|
+
if (!zone || typeof zone !== 'object') continue;
|
|
270
|
+
if (typeof zone.detected_at === 'number' && zone.detected_at < windowStart) continue;
|
|
271
|
+
const similarity = typeof zone.similarity === 'number' ? zone.similarity : 0;
|
|
272
|
+
if (similarity < DETECTOR_THRESHOLDS.SEMANTIC_SIMILARITY_THRESHOLD) continue;
|
|
273
|
+
const sourceArt = typeof zone.source_artifact_id === 'string' ? zone.source_artifact_id : '';
|
|
274
|
+
const targetArt = typeof zone.target_artifact_id === 'string' ? zone.target_artifact_id : '';
|
|
275
|
+
const artifacts = [sourceArt, targetArt].filter((s) => s.length > 0);
|
|
276
|
+
if (artifacts.length < 2) continue;
|
|
277
|
+
const sourceSection = typeof zone.source_section === 'string' ? zone.source_section : '';
|
|
278
|
+
const targetSection = typeof zone.target_section === 'string' ? zone.target_section : '';
|
|
279
|
+
const crossSectionLinked = sourceSection !== targetSection && sourceSection.length > 0 && targetSection.length > 0;
|
|
280
|
+
// D-04 boost: similarity feeds the differential blend so high-similarity cross-section
|
|
281
|
+
// matches push past the soft floor naturally.
|
|
282
|
+
const candidate = buildCandidate(
|
|
283
|
+
'cross_domain_analogy',
|
|
284
|
+
artifacts,
|
|
285
|
+
typeof zone.theme === 'string' ? zone.theme : sourceSection + ' x ' + targetSection,
|
|
286
|
+
similarity, // pass similarity as the differential -- 0.40+ -> 0.12+ added to baseConf
|
|
287
|
+
crossSectionLinked,
|
|
288
|
+
nowMs,
|
|
289
|
+
windowMs
|
|
290
|
+
);
|
|
291
|
+
candidates.push(candidate);
|
|
292
|
+
}
|
|
293
|
+
return partitionByTier(candidates);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// --- detectReverseSalientClosed (D-01 detector 4) ---
|
|
297
|
+
// Reads rs.pairs[] from .mindrian/.rs-engine-results.json (Phase 117 math output).
|
|
298
|
+
// Per CONTEXT.md D-05: BOTH signals required for hard-fire:
|
|
299
|
+
// (a) graph-level proof -- closure_edge_now_exists === true (a structural transition
|
|
300
|
+
// that did not hold at window_start but holds now), AND
|
|
301
|
+
// (b) score-delta -- |signed_diff| >= RS_SCORE_DELTA_THRESHOLD (the lagging-component
|
|
302
|
+
// score crossed its baseline within the window).
|
|
303
|
+
// Single-signal candidates return soft_fire only.
|
|
304
|
+
function detectReverseSalientClosed(roomState, opts) {
|
|
305
|
+
const { nowMs, windowMs } = resolveWindow(roomState, opts);
|
|
306
|
+
const data = readMathJson(roomState, '.rs-engine-results.json');
|
|
307
|
+
if (!data || !Array.isArray(data.pairs)) {
|
|
308
|
+
return { hits: [], soft_fires: [] };
|
|
309
|
+
}
|
|
310
|
+
const windowStart = nowMs - windowMs;
|
|
311
|
+
const candidates = [];
|
|
312
|
+
for (const pair of data.pairs) {
|
|
313
|
+
if (!pair || typeof pair !== 'object') continue;
|
|
314
|
+
if (typeof pair.detected_at === 'number' && pair.detected_at < windowStart) continue;
|
|
315
|
+
const sourceArt = typeof pair.source_artifact_id === 'string' ? pair.source_artifact_id : '';
|
|
316
|
+
const targetArt = typeof pair.target_artifact_id === 'string' ? pair.target_artifact_id : '';
|
|
317
|
+
const artifacts = [sourceArt, targetArt].filter((s) => s.length > 0);
|
|
318
|
+
if (artifacts.length < 2) continue;
|
|
319
|
+
|
|
320
|
+
const signedDiff = typeof pair.signed_diff === 'number' ? pair.signed_diff : 0;
|
|
321
|
+
const scoreDeltaSignal = Math.abs(signedDiff) >= RS_SCORE_DELTA_THRESHOLD;
|
|
322
|
+
const graphSignal = pair.closure_edge_now_exists === true;
|
|
323
|
+
// D-05 BOTH-signals invariant: only when both fire do we get a hard candidate.
|
|
324
|
+
// Single-signal candidates land at soft tier via the confidence floor manipulation:
|
|
325
|
+
// we cap differential at 0.3 for single-signal cases (drives conf below 0.35).
|
|
326
|
+
const bothSignals = scoreDeltaSignal && graphSignal;
|
|
327
|
+
const differential = bothSignals ? 0.6 : 0.2;
|
|
328
|
+
|
|
329
|
+
const sourceSection = typeof pair.source_section === 'string' ? pair.source_section : '';
|
|
330
|
+
const targetSection = typeof pair.target_section === 'string' ? pair.target_section : '';
|
|
331
|
+
const crossSectionLinked = sourceSection !== targetSection && sourceSection.length > 0 && targetSection.length > 0;
|
|
332
|
+
|
|
333
|
+
const candidate = buildCandidate(
|
|
334
|
+
'reverse_salient_closed',
|
|
335
|
+
artifacts,
|
|
336
|
+
typeof pair.theme === 'string' ? pair.theme : '',
|
|
337
|
+
differential,
|
|
338
|
+
crossSectionLinked,
|
|
339
|
+
nowMs,
|
|
340
|
+
windowMs
|
|
341
|
+
);
|
|
342
|
+
candidates.push(candidate);
|
|
343
|
+
}
|
|
344
|
+
return partitionByTier(candidates);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
module.exports = {
|
|
348
|
+
detectConvergence,
|
|
349
|
+
detectContradictionResolved,
|
|
350
|
+
detectCrossDomainAnalogy,
|
|
351
|
+
detectReverseSalientClosed,
|
|
352
|
+
classifyFireTier,
|
|
353
|
+
partitionByTier,
|
|
354
|
+
buildCandidate,
|
|
355
|
+
buildBreakthroughId,
|
|
356
|
+
DETECTOR_THRESHOLDS,
|
|
357
|
+
DETECTOR_TYPES,
|
|
358
|
+
RS_SCORE_DELTA_THRESHOLD,
|
|
359
|
+
};
|