@mindrian_os/install 1.13.0-beta.12 → 1.13.0-beta.14
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 +57 -10
- package/README.md +74 -572
- package/commands/act.md +1 -0
- package/commands/admin.md +1 -0
- package/commands/analyze-needs.md +1 -0
- package/commands/analyze-systems.md +1 -0
- package/commands/analyze-timing.md +1 -0
- package/commands/auto-explore.md +2 -0
- package/commands/beautiful-question.md +1 -0
- package/commands/brain-derive.md +1 -0
- package/commands/build-knowledge.md +1 -0
- package/commands/build-thesis.md +1 -0
- package/commands/causal.md +1 -0
- package/commands/challenge-assumptions.md +1 -0
- package/commands/compare-ventures.md +1 -0
- package/commands/dashboard.md +1 -0
- package/commands/deep-grade.md +1 -0
- package/commands/diagnose.md +1 -0
- package/commands/diagnostics.md +1 -0
- package/commands/doctor.md +2 -1
- package/commands/dominant-designs.md +1 -0
- package/commands/explain-decision.md +1 -0
- package/commands/explore-domains.md +1 -0
- package/commands/explore-futures.md +1 -0
- package/commands/explore-trends.md +1 -0
- package/commands/export.md +1 -0
- package/commands/feynman-timeline-refresh.md +78 -0
- package/commands/file-meeting.md +1 -0
- package/commands/find-analogies.md +1 -0
- package/commands/find-bottlenecks.md +1 -0
- package/commands/find-connections.md +1 -0
- package/commands/funding.md +1 -0
- package/commands/grade.md +1 -0
- package/commands/graph.md +1 -0
- package/commands/hat-briefing.md +1 -0
- package/commands/heal.md +1 -0
- package/commands/help.md +1 -0
- package/commands/hmi-status.md +1 -0
- package/commands/jtbd.md +1 -0
- package/commands/leadership.md +1 -0
- package/commands/lean-canvas.md +1 -0
- package/commands/macro-trends.md +1 -0
- package/commands/map-unknowns.md +1 -0
- package/commands/memory.md +1 -0
- package/commands/models.md +1 -0
- package/commands/mos-reason.md +1 -0
- package/commands/mullins.md +1 -0
- package/commands/new-project.md +1 -0
- package/commands/onboard.md +1 -0
- package/commands/operator.md +2 -1
- package/commands/opportunities.md +1 -0
- package/commands/organize.md +1 -0
- package/commands/persona.md +1 -0
- package/commands/pipeline.md +1 -0
- package/commands/present.md +1 -0
- package/commands/publish.md +1 -0
- package/commands/query.md +1 -0
- package/commands/radar.md +1 -0
- package/commands/reanalyze.md +1 -0
- package/commands/research.md +1 -0
- package/commands/room.md +1 -0
- package/commands/rooms.md +1 -0
- package/commands/root-cause.md +1 -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 +1 -0
- package/commands/scheduled-tasks.md +1 -0
- package/commands/score-innovation.md +1 -0
- package/commands/scout.md +1 -0
- package/commands/setup.md +8 -3
- package/commands/snapshot.md +1 -0
- package/commands/speakers.md +1 -0
- package/commands/splash.md +1 -0
- package/commands/status.md +1 -0
- package/commands/structure-argument.md +1 -0
- package/commands/suggest-next.md +1 -0
- package/commands/systems-thinking.md +1 -0
- package/commands/think-hats.md +1 -0
- package/commands/update.md +1 -0
- package/commands/user-needs.md +1 -0
- package/commands/validate.md +1 -0
- package/commands/value-proposition.md +1 -0
- package/commands/vault.md +1 -0
- package/commands/visualize.md +1 -0
- package/commands/whitespace.md +1 -0
- package/commands/wiki.md +1 -0
- package/lib/brain/framework-chain-slice.cjs +193 -0
- package/lib/core/active-plugin-root.cjs +71 -6
- package/lib/core/brain-client.cjs +451 -36
- package/lib/core/cache-prune.cjs +208 -0
- package/lib/core/feynman/ROOM.md +25 -0
- package/lib/core/feynman/timeline-renderer.cjs +197 -0
- package/lib/core/feynman/timeline-runner.cjs +281 -0
- package/lib/core/navigation/edges.cjs +86 -0
- package/lib/core/navigation/insights.cjs +37 -0
- package/lib/core/navigation/memory-events.cjs +56 -1
- package/lib/core/navigation/neighborhood.cjs +5 -4
- package/lib/core/navigation/packet.cjs +176 -10
- package/lib/core/navigation/projections.cjs +201 -0
- package/lib/core/navigation.cjs +31 -0
- package/lib/core/resolve-brain-key.cjs +201 -0
- package/lib/mcp/larry-server-instructions.md +1 -1
- package/lib/memory/brain-cypher-chain-slice.test.cjs +368 -0
- package/lib/memory/f-selector-ranker.test.cjs +593 -0
- package/lib/memory/navigation-projections.test.cjs +241 -0
- package/lib/memory/navigation-write-edge.test.cjs +206 -0
- package/lib/memory/packet-chain-hint.test.cjs +407 -0
- package/lib/memory/packet-schema-validation.test.cjs +317 -0
- package/lib/memory/per-command-jtbd-derivation.test.cjs +130 -0
- package/lib/memory/per-command-teaching.test.cjs +110 -0
- package/lib/memory/run-feynman-tests.cjs +121 -0
- package/lib/memory/security-trifecta.test.cjs +23 -6
- package/lib/memory/selector-decisions.test.cjs +417 -0
- package/lib/memory/selector-miss.test.cjs +290 -0
- package/lib/workflow/f-selector-ranker.cjs +420 -0
- package/lib/workflow/selector-decisions.cjs +368 -0
- package/package.json +4 -1
- package/references/design/email-template-standard.md +1 -1
- package/references/user-research/2026-04-05-leah-lawrence-session.md +3 -3
- package/skills/brain-connector/SKILL.md +9 -3
|
@@ -9,11 +9,80 @@
|
|
|
9
9
|
// Canon Part 9: builder produces the JS object; Phase 110 wraps with validateAndSendBrainPacket;
|
|
10
10
|
// Phase 109 honors the privacy: 'no_raw_artifact_text' field by default in constraints.
|
|
11
11
|
|
|
12
|
+
const fs = require('node:fs');
|
|
12
13
|
const path = require('node:path');
|
|
13
14
|
const crypto = require('node:crypto');
|
|
14
15
|
const { getNeighborhood } = require('./neighborhood.cjs');
|
|
15
16
|
const { findContradictions, findUnsupportedClaims, findRelevantOpportunities } = require('./insights.cjs');
|
|
16
17
|
const { findRecentChanges } = require('./memory-events.cjs');
|
|
18
|
+
// Phase 125-03 -- framework_chain_hint stitch. projections.cjs (Plan 01) gives
|
|
19
|
+
// us the active framework set + hop depth from roomState; framework-chain-slice
|
|
20
|
+
// (Plan 02) fetches the FEEDS_INTO Cypher slice from the Brain. Both modules
|
|
21
|
+
// are pure-or-graceful: projections never throws, slice fetcher returns a
|
|
22
|
+
// degraded envelope rather than throwing. Required at module top because the
|
|
23
|
+
// stitch is a hot path the helper does NOT lazy-import.
|
|
24
|
+
const projections = require('./projections.cjs');
|
|
25
|
+
const chainSliceMod = require('../../brain/framework-chain-slice.cjs');
|
|
26
|
+
|
|
27
|
+
// Phase 110-02 (D-03 + D-09): privacy-mode opt-up. local_summary_only is the default and
|
|
28
|
+
// the only mode any of the 12 shipped Brain jobs ever requests (every $def.in.properties.privacy_mode
|
|
29
|
+
// in data/brain-packet-schema.json is { const: 'local_summary_only' }). allow_filenames opts
|
|
30
|
+
// up via .config.json preferences.brain_privacy_mode (project-level) or opts.privacyMode
|
|
31
|
+
// (per-call); allow_excerpts ADDITIONALLY requires a Part-3 Decision Gate APPROVE-with-reason
|
|
32
|
+
// row on the room graph tagged brain_excerpts -- there is NO shipped consumer of allow_excerpts
|
|
33
|
+
// as of v1.13.0-beta.3, so it is a defined-but-unconsumed escape hatch. Config can only CAP
|
|
34
|
+
// the mode, never RAISE what a job sends -- the schema's per-job const enforces this for free
|
|
35
|
+
// in Phase 110-03 sendPacket.
|
|
36
|
+
const PRIVACY_MODES = ['local_summary_only', 'allow_filenames', 'allow_excerpts'];
|
|
37
|
+
|
|
38
|
+
function readRoomConfigPrivacyMode(roomDir) {
|
|
39
|
+
if (!roomDir) return null;
|
|
40
|
+
try {
|
|
41
|
+
const p = path.join(roomDir, '.config.json');
|
|
42
|
+
if (!fs.existsSync(p)) return null;
|
|
43
|
+
const cfg = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
44
|
+
const v = cfg && cfg.preferences && cfg.preferences.brain_privacy_mode;
|
|
45
|
+
return PRIVACY_MODES.includes(v) ? v : null;
|
|
46
|
+
} catch (_) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// The Part-3 Decision Gate APPROVE row (Canon Part 3 + Part 4). The F.0 selector at
|
|
52
|
+
// lib/hmi/shape-f0-renderer.cjs is the canonical render path that, when wired in a future
|
|
53
|
+
// plan, writes the brain_excerpts-tagged APPROVE decision row that this helper looks for.
|
|
54
|
+
// As of v1.13.0-beta.3 there is NO shipped consumer of allow_excerpts -- so this returns
|
|
55
|
+
// false until a Part-3 gate is wired and the user approves. The query is a single guarded
|
|
56
|
+
// SELECT against the local room graph (the same db handle buildBrainPacket already takes);
|
|
57
|
+
// any error or absent row returns false so the resolver caps down safely.
|
|
58
|
+
function roomHasExcerptApproval(db, roomDir) {
|
|
59
|
+
try {
|
|
60
|
+
if (!db) return false;
|
|
61
|
+
const row = db.prepare(
|
|
62
|
+
"SELECT 1 FROM nodes WHERE type IN ('decision','memory_event') " +
|
|
63
|
+
"AND review_status = 'confirmed' AND created_by = 'user' " +
|
|
64
|
+
"AND instr(properties, 'brain_excerpts') > 0 LIMIT 1"
|
|
65
|
+
).get();
|
|
66
|
+
return !!row;
|
|
67
|
+
} catch (_) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function resolvePrivacyMode(db, roomDir, opts) {
|
|
73
|
+
const options = opts || {};
|
|
74
|
+
const perCall = options.privacyMode;
|
|
75
|
+
const fromConfig = readRoomConfigPrivacyMode(roomDir);
|
|
76
|
+
const requested = (typeof perCall === 'string' && PRIVACY_MODES.includes(perCall))
|
|
77
|
+
? perCall
|
|
78
|
+
: (fromConfig || 'local_summary_only');
|
|
79
|
+
if (requested === 'allow_excerpts' && !roomHasExcerptApproval(db, roomDir)) {
|
|
80
|
+
// Cap down per "config caps, never raises". If config said allow_filenames, keep it;
|
|
81
|
+
// otherwise drop to local_summary_only.
|
|
82
|
+
return fromConfig === 'allow_filenames' ? 'allow_filenames' : 'local_summary_only';
|
|
83
|
+
}
|
|
84
|
+
return requested;
|
|
85
|
+
}
|
|
17
86
|
|
|
18
87
|
function loadJtbd(roomDir, mocks) {
|
|
19
88
|
if (mocks && mocks.jtbd) return mocks.jtbd;
|
|
@@ -117,10 +186,65 @@ function getFocusType(db, focusNodeId) {
|
|
|
117
186
|
return row ? row.type : null;
|
|
118
187
|
}
|
|
119
188
|
|
|
120
|
-
|
|
189
|
+
// Phase 125-03 -- stitch helper. Reads roomState via projections, fetches the
|
|
190
|
+
// 1-3 hop FEEDS_INTO slice via Plan 02 fetcher (or mock from opts._mocks),
|
|
191
|
+
// returns the framework_chain_hint shape per CONTEXT.md Scope IN section B
|
|
192
|
+
// item 5 -- OR undefined when the active set is empty (which is the "absent
|
|
193
|
+
// from packet" path per Plan 04 schema: framework_chain_hint is optional on
|
|
194
|
+
// LocalGraphSummary; absence is meaningful).
|
|
195
|
+
//
|
|
196
|
+
// Contract:
|
|
197
|
+
// - roomState null / not-an-object -> undefined (cold-start packet ships without hint)
|
|
198
|
+
// - active frameworks empty array -> undefined (no anchor to fetch a slice for)
|
|
199
|
+
// - active frameworks non-empty + Brain reachable -> hint object (5 fields)
|
|
200
|
+
// - active frameworks non-empty + Brain unreachable -> hint object with degraded
|
|
201
|
+
// shape per Plan 02 fetcher contract (edges:[], brain_snapshot_id:null,
|
|
202
|
+
// slice_rationale carrying 'brain_unreachable' or similar marker). The hint
|
|
203
|
+
// is STILL attached so the ranker can distinguish "active set non-empty but
|
|
204
|
+
// Brain failed" from "active set empty" (RESEARCH G-08 / Tier 0 invariant).
|
|
205
|
+
//
|
|
206
|
+
// Async because the fetcher is async. Pure read-only flow: no db writes, no fs
|
|
207
|
+
// writes, no Brain writes -- only the Plan 02 read primitive (Cypher MATCH).
|
|
208
|
+
async function _surfaceFrameworkChainHint(db, roomState, opts) {
|
|
209
|
+
if (!roomState || typeof roomState !== 'object') return undefined;
|
|
210
|
+
const active = projections.resolveActiveFrameworks(roomState);
|
|
211
|
+
if (!Array.isArray(active) || active.length === 0) return undefined;
|
|
212
|
+
const hopDepth = projections.resolveHopDepth(roomState);
|
|
213
|
+
const depth = (hopDepth && (hopDepth.depth === 1 || hopDepth.depth === 2 || hopDepth.depth === 3))
|
|
214
|
+
? hopDepth.depth
|
|
215
|
+
: 3;
|
|
216
|
+
const activeNames = active.map(function (a) { return a.name; });
|
|
217
|
+
// Test seam: opts._mocks.fetchFrameworkChainSlice replaces the live fetcher
|
|
218
|
+
// for hermetic tests (no Brain network). Falls through to the shipped Plan
|
|
219
|
+
// 02 module when no mock is provided.
|
|
220
|
+
const fetcher = (opts && opts._mocks && typeof opts._mocks.fetchFrameworkChainSlice === 'function')
|
|
221
|
+
? opts._mocks.fetchFrameworkChainSlice
|
|
222
|
+
: chainSliceMod.fetchFrameworkChainSlice;
|
|
223
|
+
// Per Plan 02 contract, fetcher NEVER throws; it returns a degraded envelope
|
|
224
|
+
// on any failure path (Brain unreachable, query throws, sanitizer rejects).
|
|
225
|
+
// Wrapping in try/catch is defensive belt-and-suspenders in case a future
|
|
226
|
+
// mock or replacement misbehaves.
|
|
227
|
+
let slice;
|
|
228
|
+
try {
|
|
229
|
+
slice = await fetcher({ active_frameworks: activeNames, max_hops: depth });
|
|
230
|
+
} catch (_e) {
|
|
231
|
+
// Defensive: even if the fetcher contract is violated, do NOT crash the
|
|
232
|
+
// packet builder. Return undefined -> hint absent for this call.
|
|
233
|
+
return undefined;
|
|
234
|
+
}
|
|
235
|
+
if (!slice || typeof slice !== 'object') return undefined;
|
|
236
|
+
return slice;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function buildBrainPacket(db, job, focusNodeId, opts) {
|
|
121
240
|
const options = opts || {};
|
|
122
241
|
const mocks = options._mocks;
|
|
123
242
|
const roomId = options.roomId || null;
|
|
243
|
+
const roomDir = options.roomDir || null;
|
|
244
|
+
|
|
245
|
+
// Phase 110-02: resolve the privacy mode BEFORE building the packet body so the resolved
|
|
246
|
+
// value is available on the top-level return object alongside packet_version and origin.
|
|
247
|
+
const privacyMode = resolvePrivacyMode(db, roomDir, options);
|
|
124
248
|
|
|
125
249
|
// Active context. Mocks override the require() calls for hermetic tests.
|
|
126
250
|
const jtbdMod = loadJtbd(null, mocks);
|
|
@@ -151,10 +275,29 @@ function buildBrainPacket(db, job, focusNodeId, opts) {
|
|
|
151
275
|
const recentChanges = findRecentChanges(db, Date.now() - 24 * 60 * 60 * 1000, { limit: 10 }).map(safeRecentChangeProjection);
|
|
152
276
|
const bankedOpportunities = surface_banked_opportunities(db, focusNodeId, mocks);
|
|
153
277
|
|
|
278
|
+
// Phase 125-03: framework_chain_hint stitch. Reads roomState from opts (callers
|
|
279
|
+
// pass it through; Plan 05 ranker passes its own roomState when calling
|
|
280
|
+
// buildBrainPacket explicitly). When opts.roomState is absent OR resolves to an
|
|
281
|
+
// empty active framework set, the hint is ABSENT from the packet per the
|
|
282
|
+
// CONTEXT.md "absent when active set empty" invariant (Plan 04 schema accepts
|
|
283
|
+
// both shapes since framework_chain_hint is NOT in LocalGraphSummary.required[]).
|
|
284
|
+
const roomState = (options && typeof options === 'object') ? options.roomState : null;
|
|
285
|
+
const frameworkChainHint = await _surfaceFrameworkChainHint(db, roomState, options || {});
|
|
286
|
+
|
|
154
287
|
return {
|
|
155
288
|
packet_version: '1.0',
|
|
156
289
|
job,
|
|
157
290
|
room_stage: getRoomStage(db),
|
|
291
|
+
// Phase 110-02 D-08 layer 1: stamp the navigation-API provenance origin. The schema's
|
|
292
|
+
// $defs.Origin enum constrains this to the closed set; brain-client.sendPacket (Phase
|
|
293
|
+
// 110-03) refuses any other value at wire time. defense-in-depth -- three layers (schema
|
|
294
|
+
// enum + pre-commit hook + sendPacket allowlist), no in-process nonce.
|
|
295
|
+
origin: 'navigation_api',
|
|
296
|
+
// Phase 110-02 D-09: top-level privacy_mode (one of D-03's 3 enum values; default
|
|
297
|
+
// local_summary_only). Separate from constraints.privacy (human-readable note) -- the
|
|
298
|
+
// schema's $defs.PrivacyMode enum + each job's $def.in.properties.privacy_mode const
|
|
299
|
+
// enforces "config caps, never raises" at the wire level in Phase 110-03 sendPacket.
|
|
300
|
+
privacy_mode: privacyMode,
|
|
158
301
|
active_context: {
|
|
159
302
|
jtbd: jtbdId,
|
|
160
303
|
operator,
|
|
@@ -164,14 +307,22 @@ function buildBrainPacket(db, job, focusNodeId, opts) {
|
|
|
164
307
|
summary: focusSummary,
|
|
165
308
|
},
|
|
166
309
|
},
|
|
167
|
-
local_graph_summary:
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
310
|
+
local_graph_summary: Object.assign(
|
|
311
|
+
{
|
|
312
|
+
nearest_claims: nearestClaims,
|
|
313
|
+
nearest_assumptions: nearestAssumptions,
|
|
314
|
+
contradictions,
|
|
315
|
+
unsupported_claims: unsupportedClaims,
|
|
316
|
+
recent_changes: recentChanges,
|
|
317
|
+
banked_opportunities: bankedOpportunities,
|
|
318
|
+
},
|
|
319
|
+
// Phase 125-03: conditionally include framework_chain_hint. Object.assign
|
|
320
|
+
// with an empty-object short-circuit guarantees the key is ABSENT (not
|
|
321
|
+
// present with an undefined value) when frameworkChainHint is undefined.
|
|
322
|
+
// This preserves the schema invariant in Plan 04 (the field is optional;
|
|
323
|
+
// absence is meaningful for the ranker in Plan 05).
|
|
324
|
+
(frameworkChainHint === undefined) ? {} : { framework_chain_hint: frameworkChainHint }
|
|
325
|
+
),
|
|
175
326
|
constraints: {
|
|
176
327
|
privacy: 'no_raw_artifact_text',
|
|
177
328
|
max_tokens: 1200,
|
|
@@ -179,4 +330,19 @@ function buildBrainPacket(db, job, focusNodeId, opts) {
|
|
|
179
330
|
};
|
|
180
331
|
}
|
|
181
332
|
|
|
182
|
-
module.exports = {
|
|
333
|
+
module.exports = {
|
|
334
|
+
buildBrainPacket,
|
|
335
|
+
surface_banked_opportunities,
|
|
336
|
+
shortText,
|
|
337
|
+
// Phase 110-02 (D-09): exported so Phase 110-05 round-trip tests, future callers, and
|
|
338
|
+
// brain-client.sendPacket (110-03) can introspect / reuse the resolution order.
|
|
339
|
+
resolvePrivacyMode,
|
|
340
|
+
readRoomConfigPrivacyMode,
|
|
341
|
+
roomHasExcerptApproval,
|
|
342
|
+
PRIVACY_MODES,
|
|
343
|
+
// Phase 125-03 test seam: surface helper exposed for fine-grained unit tests
|
|
344
|
+
// in lib/memory/packet-chain-hint.test.cjs. NOT part of the public API; Plan
|
|
345
|
+
// 05 ranker reads the assembled local_graph_summary.framework_chain_hint
|
|
346
|
+
// directly off the packet rather than calling the helper.
|
|
347
|
+
_test: { _surfaceFrameworkChainHint },
|
|
348
|
+
};
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Phase 125-01 -- pure projection helpers for f-selector-ranker (Plan 05 consumer).
|
|
3
|
+
// All three functions are pure + synchronous; they project roomState into the
|
|
4
|
+
// three signals D4's scoring formula consumes. No I/O. No Brain calls. No db
|
|
5
|
+
// writes. Database READS via existing navigation primitives only -- no direct
|
|
6
|
+
// room-db access.
|
|
7
|
+
//
|
|
8
|
+
// Canon Part 9: SQL remembers and navigates; these helpers are the "navigate"
|
|
9
|
+
// projections over the local mind that the ranker reads BEFORE making its
|
|
10
|
+
// recommendation. Canon Part 7: extension of navigation/, not modification of
|
|
11
|
+
// navigation.cjs's closed surface. Plan 05 may require these directly from
|
|
12
|
+
// projections.cjs OR via navigation.cjs re-export -- Plan 01 ships the
|
|
13
|
+
// projections; navigation.cjs re-export is OPTIONAL (deferred to Plan 05 if
|
|
14
|
+
// preferred to avoid the closed-surface widening discussion).
|
|
15
|
+
//
|
|
16
|
+
// Function signatures (LOCKED in 125-CONTEXT.md "Function signatures (LOCKED)"):
|
|
17
|
+
// resolveActiveFrameworks(roomState) -> Array<{name, weight, source}>
|
|
18
|
+
// resolveHopDepth(roomState) -> {depth: 1|2|3, rationale: string}
|
|
19
|
+
// computeInvestmentLevel(roomState) -> {level: number, label: string}
|
|
20
|
+
//
|
|
21
|
+
// roomState shape (extends the chain-recommender.cjs shape per RESEARCH G-03 + G-04):
|
|
22
|
+
// {
|
|
23
|
+
// problemType?: 'UDP'|'IDP'|'WDP'|string,
|
|
24
|
+
// governing_thought?: string,
|
|
25
|
+
// activeJtbd?: string,
|
|
26
|
+
// brainAnchors?: string[],
|
|
27
|
+
// framework_invocations?: number, // from COUNT(memory_event WHERE event_type='framework_invoked')
|
|
28
|
+
// feedsIntoEdges?: Edge[],
|
|
29
|
+
// brainSection?: object,
|
|
30
|
+
// brainSections?: object,
|
|
31
|
+
// recentFrameworks?: string[], // from findRecentChanges memory_event 'framework_invoked'
|
|
32
|
+
// }
|
|
33
|
+
|
|
34
|
+
// Common frameworks in the Brain teaching graph -- used as substring hints
|
|
35
|
+
// when extracting from governing_thought free-text. NOT exhaustive (the Brain
|
|
36
|
+
// has 100+ frameworks); just the high-traffic ones for early-room
|
|
37
|
+
// governing_thought parsing. Plan 05 may extend this list or replace with a
|
|
38
|
+
// registry-derived superset; the projection helper is just a substring scan.
|
|
39
|
+
const KNOWN_FRAMEWORK_HINTS = Object.freeze([
|
|
40
|
+
'Beautiful Question Framework', 'Mullins 7 Domains', 'SWOT', 'Porter Five Forces',
|
|
41
|
+
'JTBD', 'Jobs to be Done', 'Lean Canvas', 'Business Model Canvas',
|
|
42
|
+
'Six Thinking Hats', 'Cynefin', 'Root Cause Analysis', 'First Principles',
|
|
43
|
+
'Wardley Map', 'OKR', 'Scenario Planning', 'Design Thinking',
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
function _extractFrameworkFromThought(text) {
|
|
47
|
+
if (typeof text !== 'string' || text.length === 0) return null;
|
|
48
|
+
for (const fw of KNOWN_FRAMEWORK_HINTS) {
|
|
49
|
+
if (text.indexOf(fw) !== -1) return fw;
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Resolve a JTBD id to a framework name via:
|
|
55
|
+
// jtbd-taxonomy.json (entry.methodology_hooks[0]) -> command slug
|
|
56
|
+
// command-registry.json (commands[].command) -> first frameworks[]
|
|
57
|
+
// Synchronous, fs-only read. Returns null on any failure -- never throws.
|
|
58
|
+
// Mirrors lib/brain/chain-recommender.cjs::_jtbdToFramework (same fs pattern,
|
|
59
|
+
// same fail-soft contract). Plan 05 may share the registry via injection if
|
|
60
|
+
// it wants to centralize the cache; Plan 01 just reads.
|
|
61
|
+
function _jtbdToFramework(activeJtbd) {
|
|
62
|
+
if (typeof activeJtbd !== 'string' || activeJtbd.length === 0) return null;
|
|
63
|
+
const path = require('node:path');
|
|
64
|
+
const fs = require('node:fs');
|
|
65
|
+
const TAXONOMY_PATH = path.join(__dirname, '..', '..', 'hmi', 'jtbd-taxonomy.json');
|
|
66
|
+
const REGISTRY_PATH = path.join(__dirname, '..', '..', '..', 'data', 'command-registry.json');
|
|
67
|
+
let tax;
|
|
68
|
+
let reg;
|
|
69
|
+
try { tax = JSON.parse(fs.readFileSync(TAXONOMY_PATH, 'utf8')); } catch (_e) { return null; }
|
|
70
|
+
try { reg = JSON.parse(fs.readFileSync(REGISTRY_PATH, 'utf8')); } catch (_e) { return null; }
|
|
71
|
+
const entry = (tax.entries || []).find((e) => e && e.id === activeJtbd);
|
|
72
|
+
if (!entry || !Array.isArray(entry.methodology_hooks) || entry.methodology_hooks.length === 0) return null;
|
|
73
|
+
const firstHook = entry.methodology_hooks[0]; // e.g. '/mos:beautiful-question' or 'mos:beautiful-question'
|
|
74
|
+
if (typeof firstHook !== 'string') return null;
|
|
75
|
+
const parts = firstHook.split(':');
|
|
76
|
+
if (parts.length < 2) return null;
|
|
77
|
+
const slug = parts.slice(1).join(':');
|
|
78
|
+
const cmd = (reg.commands || []).find((c) => c && typeof c.command === 'string' && c.command.split(':').slice(1).join(':') === slug);
|
|
79
|
+
return (cmd && Array.isArray(cmd.frameworks) && cmd.frameworks.length > 0) ? cmd.frameworks[0] : null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// D1 -- Active-framework multi-signal projection.
|
|
83
|
+
// Precedence (highest -> lowest weight):
|
|
84
|
+
// 1.00 governing_thought (CONTEXT signal, Phase 90)
|
|
85
|
+
// 0.75 activeJtbd (INTENT signal, /mos:jtbd via taxonomy)
|
|
86
|
+
// 0.50 brainAnchors (MEMORY signal, BRAIN.md anchors per Phase 90)
|
|
87
|
+
// 0.25 recentFrameworks (TEMPORAL signal, memory_event 'framework_invoked' recency)
|
|
88
|
+
// First-write-wins per framework name (a framework already claimed by a higher
|
|
89
|
+
// signal does NOT also appear at a lower one). Empty array on empty roomState.
|
|
90
|
+
function resolveActiveFrameworks(roomState) {
|
|
91
|
+
if (!roomState || typeof roomState !== 'object') return [];
|
|
92
|
+
const out = new Map(); // name -> {name, weight, source}
|
|
93
|
+
|
|
94
|
+
// 1. governing_thought (CONTEXT signal, weight 1.0)
|
|
95
|
+
if (typeof roomState.governing_thought === 'string' && roomState.governing_thought.length > 0) {
|
|
96
|
+
const fw = _extractFrameworkFromThought(roomState.governing_thought);
|
|
97
|
+
if (fw) out.set(fw, { name: fw, weight: 1.0, source: 'governing_thought' });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 2. activeJtbd (INTENT signal, weight 0.75)
|
|
101
|
+
if (typeof roomState.activeJtbd === 'string' && roomState.activeJtbd.length > 0) {
|
|
102
|
+
const fw = _jtbdToFramework(roomState.activeJtbd);
|
|
103
|
+
if (fw && !out.has(fw)) {
|
|
104
|
+
out.set(fw, { name: fw, weight: 0.75, source: 'activeJtbd' });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 3. BRAIN.md anchors (MEMORY signal, weight 0.5)
|
|
109
|
+
if (Array.isArray(roomState.brainAnchors)) {
|
|
110
|
+
for (const a of roomState.brainAnchors) {
|
|
111
|
+
if (typeof a === 'string' && a.length > 0 && !out.has(a)) {
|
|
112
|
+
out.set(a, { name: a, weight: 0.5, source: 'brain_md' });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 4. memory_event recency (TEMPORAL signal, weight 0.25)
|
|
118
|
+
if (Array.isArray(roomState.recentFrameworks)) {
|
|
119
|
+
for (const a of roomState.recentFrameworks) {
|
|
120
|
+
if (typeof a === 'string' && a.length > 0 && !out.has(a)) {
|
|
121
|
+
out.set(a, { name: a, weight: 0.25, source: 'memory_event' });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return Array.from(out.values()).sort((a, b) => b.weight - a.weight);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// D2 -- Variable hop depth, context-driven.
|
|
130
|
+
// Defaults to depth 3 (WIDE) on ambiguity per CONTEXT.md.
|
|
131
|
+
// depth 1: WDP + strong governing_thought (execution mode)
|
|
132
|
+
// depth 2: IDP (exploratory mode); or WDP without governing_thought
|
|
133
|
+
// depth 3: wicked / undefined / no anchor (default WIDE)
|
|
134
|
+
function resolveHopDepth(roomState) {
|
|
135
|
+
if (!roomState || typeof roomState !== 'object') {
|
|
136
|
+
return { depth: 3, rationale: 'no roomState provided; defaulting WIDE on ambiguity' };
|
|
137
|
+
}
|
|
138
|
+
const problemType = roomState.problemType;
|
|
139
|
+
const hasGoverningThought =
|
|
140
|
+
typeof roomState.governing_thought === 'string' && roomState.governing_thought.length > 0;
|
|
141
|
+
if (problemType === 'WDP' && hasGoverningThought) {
|
|
142
|
+
return {
|
|
143
|
+
depth: 1,
|
|
144
|
+
rationale: 'well-defined state with strong governing_thought; execution mode',
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (problemType === 'IDP') {
|
|
148
|
+
return {
|
|
149
|
+
depth: 2,
|
|
150
|
+
rationale: 'ill-defined state; evolving governing_thought; exploratory mode',
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
if (problemType === 'WDP') {
|
|
154
|
+
return {
|
|
155
|
+
depth: 2,
|
|
156
|
+
rationale: 'well-defined problemType but no governing_thought; moderate slice',
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
depth: 3,
|
|
161
|
+
rationale: 'wicked or undefined state; no anchor; defaulting WIDE on ambiguity',
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// D3 -- Continuous investment gradient.
|
|
166
|
+
// level = min(1.0, max(0.0, framework_invocations / 10))
|
|
167
|
+
// level === 0 -> "fresh room, ranking with Brain priors"
|
|
168
|
+
// 0 < level < 0.5 -> "warming up: Brain + early local signal"
|
|
169
|
+
// 0.5 <= level < 1 -> "balanced: Brain + memory equal weight"
|
|
170
|
+
// level === 1.0 -> "full local scoring with Brain confidence"
|
|
171
|
+
// Caps at 1.0 for >= 10 invocations; floors at 0.0 for missing or negative
|
|
172
|
+
// counter. Drives D4's scoring formula:
|
|
173
|
+
// score = brain_confidence*0.40 + (1-recency_decay)*0.30*investment_level
|
|
174
|
+
// + problem_type_bind*0.30*investment_level
|
|
175
|
+
// (normalized to 0..1)
|
|
176
|
+
function computeInvestmentLevel(roomState) {
|
|
177
|
+
const raw =
|
|
178
|
+
roomState && typeof roomState === 'object' && typeof roomState.framework_invocations === 'number'
|
|
179
|
+
? roomState.framework_invocations
|
|
180
|
+
: 0;
|
|
181
|
+
const level = Math.min(1.0, Math.max(0.0, raw / 10));
|
|
182
|
+
let label;
|
|
183
|
+
if (level === 0) {
|
|
184
|
+
label = 'fresh room, ranking with Brain priors';
|
|
185
|
+
} else if (level < 0.5) {
|
|
186
|
+
label = 'warming up: Brain + early local signal';
|
|
187
|
+
} else if (level < 1.0) {
|
|
188
|
+
label = 'balanced: Brain + memory equal weight';
|
|
189
|
+
} else {
|
|
190
|
+
label = 'full local scoring with Brain confidence';
|
|
191
|
+
}
|
|
192
|
+
return { level, label };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
module.exports = {
|
|
196
|
+
resolveActiveFrameworks,
|
|
197
|
+
resolveHopDepth,
|
|
198
|
+
computeInvestmentLevel,
|
|
199
|
+
// Test seam (private; used by lib/memory/navigation-projections.test.cjs only).
|
|
200
|
+
_test: { _extractFrameworkFromThought, _jtbdToFramework, KNOWN_FRAMEWORK_HINTS },
|
|
201
|
+
};
|
package/lib/core/navigation.cjs
CHANGED
|
@@ -10,6 +10,16 @@
|
|
|
10
10
|
// re-exports those plus internal helpers as needed) requires canon amendment.
|
|
11
11
|
// Canon Part 9: navigation IS the local mind; this is the only module callers should
|
|
12
12
|
// require for graph reads, ranking, packet building, and truth-state promotion.
|
|
13
|
+
//
|
|
14
|
+
// Phase 125-00 amendment (Pass 3 GAP-2 resolution): writeEdge added as a thin
|
|
15
|
+
// re-export on the navigation surface. Additive extension per the Phase 110-03
|
|
16
|
+
// logMemoryEvent precedent (and the Phase 124-01 firstCapturedLastTouchedBySection
|
|
17
|
+
// precedent shipped between 110-03 and here). The closed DOCUMENTED 13-function
|
|
18
|
+
// API is unchanged in spirit -- additive re-exports of internal helpers are
|
|
19
|
+
// needed for the Plan 06 selector-decisions surface to write typed cascade edges
|
|
20
|
+
// (DEFERRED / REJECTED per CONTEXT.md D7) without bypassing the chokepoint.
|
|
21
|
+
// Canon Part 4 binding: every choice is graph data; writeEdge is the primitive
|
|
22
|
+
// that lets the F-selector ranker emit those choices as typed edges.
|
|
13
23
|
|
|
14
24
|
const focus = require('./navigation/focus.cjs');
|
|
15
25
|
const neighborhoodMod = require('./navigation/neighborhood.cjs');
|
|
@@ -19,6 +29,7 @@ const insights = require('./navigation/insights.cjs');
|
|
|
19
29
|
const packet = require('./navigation/packet.cjs');
|
|
20
30
|
const ingestion = require('./navigation/ingestion.cjs');
|
|
21
31
|
const roomHome = require('./navigation/room-home.cjs');
|
|
32
|
+
const edges = require('./navigation/edges.cjs');
|
|
22
33
|
|
|
23
34
|
function notImplementedYet(name, plan) {
|
|
24
35
|
return function () {
|
|
@@ -57,4 +68,24 @@ module.exports = {
|
|
|
57
68
|
|
|
58
69
|
// Truth-state chokepoint (Plan 109-04 LIVE).
|
|
59
70
|
promoteNodeStatus: transitions.promoteNodeStatus,
|
|
71
|
+
|
|
72
|
+
// Memory-event logging (Phase 110-03 -- a thin re-export so brain-client.cjs can log the
|
|
73
|
+
// brain_packet_rejected / brain_response_rejected / brain_legacy_path_used events without
|
|
74
|
+
// reaching into the internal navigation/memory-events.cjs. The closed navigation surface is
|
|
75
|
+
// the DOCUMENTED 13-function API; the implementation re-exports internal helpers as needed.)
|
|
76
|
+
logMemoryEvent: memoryEvents.logEvent,
|
|
77
|
+
|
|
78
|
+
// First-captured / last-touched scalars by section (Phase 124-01 -- a thin re-export so
|
|
79
|
+
// lib/core/feynman/timeline-renderer.cjs can compose its D-05 summary line over the
|
|
80
|
+
// memory_event log without reaching into the internal navigation/insights.cjs. The closed
|
|
81
|
+
// navigation surface is the DOCUMENTED 13-function API; the implementation re-exports
|
|
82
|
+
// internal helpers as needed -- same pattern as the Phase 110-03 logMemoryEvent re-export.)
|
|
83
|
+
firstCapturedLastTouchedBySection: insights.firstCapturedLastTouchedBySection,
|
|
84
|
+
|
|
85
|
+
// Edge-write primitive (Phase 125-00 -- Pass 3 GAP-2 resolution; D7 typed cascade edge
|
|
86
|
+
// surface for F.1 defer / F.2 reject. Allowlist gated on ALLOWED_EDGE_TYPES Set in
|
|
87
|
+
// navigation/edges.cjs. Plan 06 selector-decisions.cjs is the first consumer; Phase
|
|
88
|
+
// 116/117/118 will extend the allowlist additively for tension / auto-explore /
|
|
89
|
+
// MVA edges. Same additive-re-export pattern as logMemoryEvent + firstCapturedLastTouchedBySection.)
|
|
90
|
+
writeEdge: edges.writeEdge,
|
|
60
91
|
};
|