@mindrian_os/install 1.13.0-beta.14 → 1.13.0-beta.17
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 +15 -0
- package/commands/file-meeting.md +2 -0
- package/commands/grade.md +2 -0
- package/commands/mva-brief.md +56 -0
- package/commands/mva-option.md +89 -0
- package/commands/new-project.md +2 -0
- package/commands/onboard.md +2 -0
- package/hooks/hooks.json +9 -0
- package/lib/agents/mva/brain-classic-traps.cjs +77 -0
- package/lib/agents/mva/brain-cross-domain.cjs +79 -0
- package/lib/agents/mva/brain-similar-ventures.cjs +93 -0
- package/lib/agents/mva/dashboard-graph-neighborhood.cjs +72 -0
- package/lib/agents/mva/index.cjs +42 -0
- package/lib/agents/mva/six-hats-red-black.cjs +137 -0
- package/lib/agents/mva/tavily-funding-scan.cjs +147 -0
- package/lib/agents/mva/test-all-six-agents.cjs +467 -0
- package/lib/conversation/operator.cjs +64 -0
- package/lib/conversation/operator.test.cjs +160 -0
- package/lib/core/cache-prune.cjs +114 -8
- package/lib/core/install-state.cjs +242 -0
- package/lib/core/mva-agent-contract.cjs +170 -0
- package/lib/core/mva-agent-contract.test.cjs +169 -0
- package/lib/core/mva-budget.cjs +75 -0
- package/lib/core/mva-budget.test.cjs +68 -0
- package/lib/core/mva-classifier.cjs +370 -0
- package/lib/core/mva-classifier.test.cjs +248 -0
- package/lib/core/mva-deck-builder.cjs +452 -0
- package/lib/core/mva-deck-builder.test.cjs +287 -0
- package/lib/core/mva-detect.smoke.test.cjs +197 -0
- package/lib/core/mva-dispatcher.cjs +110 -0
- package/lib/core/mva-dispatcher.test.cjs +216 -0
- package/lib/core/mva-option-router.cjs +292 -0
- package/lib/core/mva-option-router.test.cjs +483 -0
- package/lib/core/mva-orchestrator.cjs +324 -0
- package/lib/core/mva-orchestrator.test.cjs +908 -0
- package/lib/core/mva-progressive-renderer.cjs +194 -0
- package/lib/core/mva-progressive-renderer.test.cjs +157 -0
- package/lib/core/mva-rule-linter.cjs +213 -0
- package/lib/core/mva-rule-linter.test.cjs +336 -0
- package/lib/core/mva-state.cjs +159 -0
- package/lib/core/mva-telemetry.cjs +170 -0
- package/lib/core/mva-telemetry.test.cjs +196 -0
- package/lib/core/mva-vercel-deploy.cjs +168 -0
- package/lib/core/mva-vercel-deploy.test.cjs +239 -0
- package/lib/core/navigation/dashboard-helpers.cjs +145 -0
- package/lib/core/navigation.cjs +11 -0
- package/lib/core/resolve-vercel-key.cjs +107 -0
- package/lib/core/resolve-vercel-key.test.cjs +137 -0
- package/lib/memory/run-feynman-tests.cjs +27 -0
- package/package.json +1 -1
- package/skills/mva-pipeline/SKILL.md +129 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/*
|
|
3
|
+
* Phase 118 Plan 02 -- dashboard-helpers: thin wrappers used by the Plan
|
|
4
|
+
* 118-02 dashboard-graph-neighborhood MVA agent (Agent 6 of 6 in B1).
|
|
5
|
+
*
|
|
6
|
+
* Two wrappers, both kept under lib/core/navigation/ so the chokepoint module
|
|
7
|
+
* (lib/core/navigation.cjs) re-exports them as the navigator-facing API. The
|
|
8
|
+
* dashboard MVA agent goes through navigation.cjs only -- the Phase 109 D-06
|
|
9
|
+
* chokepoint invariant (Canon Part 9).
|
|
10
|
+
*
|
|
11
|
+
* Wrappers:
|
|
12
|
+
* detectActiveRoom() -> { roomDir, hasRoomDb } | null
|
|
13
|
+
* Resolves the active room via MindrianRooms/.rooms/registry.json (the
|
|
14
|
+
* scripts/brain-derive-command.cjs precedent at line 142). Returns null if
|
|
15
|
+
* no registry, no active slug, or the resolved path is not a directory.
|
|
16
|
+
*
|
|
17
|
+
* getRecentDecisionNeighborhood(roomDir, opts) -> { nodes:[], edges:[] }
|
|
18
|
+
* Opens room.db via lib/core/room-db.cjs (the SOLE owner of better-sqlite3
|
|
19
|
+
* instantiation), picks the most recent 'decision' node, calls the
|
|
20
|
+
* existing getNeighborhood() chokepoint, returns the typed neighborhood.
|
|
21
|
+
* Returns { nodes:[], edges:[] } when room.db has no decisions yet.
|
|
22
|
+
*
|
|
23
|
+
* Canon Part 9 (Memory Locality + Interpretation): this helper IS a navigation
|
|
24
|
+
* surface; agents that need room-graph reads go through here.
|
|
25
|
+
*
|
|
26
|
+
* Canon Part 8: zero network surface. Pure LOCAL filesystem + SQLite reads.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const fs = require('node:fs');
|
|
30
|
+
const path = require('node:path');
|
|
31
|
+
const os = require('node:os');
|
|
32
|
+
const roomDbMod = require('../room-db.cjs');
|
|
33
|
+
const neighborhoodMod = require('./neighborhood.cjs');
|
|
34
|
+
|
|
35
|
+
function _safeIsFile(p) {
|
|
36
|
+
try { return fs.statSync(p).isFile(); } catch (_e) { return false; }
|
|
37
|
+
}
|
|
38
|
+
function _safeIsDir(p) {
|
|
39
|
+
try { return fs.statSync(p).isDirectory(); } catch (_e) { return false; }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Resolve the active room via the MindrianRooms registry. Mirrors
|
|
44
|
+
* scripts/brain-derive-command.cjs::resolveActiveRoom verbatim so the same
|
|
45
|
+
* detection rules apply across CLI commands and MVA agents.
|
|
46
|
+
*
|
|
47
|
+
* @returns {{ roomDir: string, hasRoomDb: boolean } | null}
|
|
48
|
+
*/
|
|
49
|
+
function detectActiveRoom() {
|
|
50
|
+
const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
51
|
+
const roomsRoot = process.env.MINDRIAN_ROOMS_ROOT
|
|
52
|
+
|| process.env.MINDRIAN_ROOMS_HOME
|
|
53
|
+
|| path.join(home, 'MindrianRooms');
|
|
54
|
+
const registryPath = path.join(roomsRoot, '.rooms', 'registry.json');
|
|
55
|
+
if (!_safeIsFile(registryPath)) return null;
|
|
56
|
+
let reg;
|
|
57
|
+
try {
|
|
58
|
+
reg = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
59
|
+
} catch (_e) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
if (!reg || typeof reg !== 'object') return null;
|
|
63
|
+
const activeSlug = reg.active;
|
|
64
|
+
if (!activeSlug || typeof activeSlug !== 'string') return null;
|
|
65
|
+
const entry = reg.rooms && reg.rooms[activeSlug];
|
|
66
|
+
if (!entry || typeof entry !== 'object') return null;
|
|
67
|
+
const relPath = entry.path || activeSlug;
|
|
68
|
+
const absPath = path.resolve(roomsRoot, relPath);
|
|
69
|
+
if (!_safeIsDir(absPath)) return null;
|
|
70
|
+
const roomDbPath = path.join(absPath, '.mindrian', 'room.db');
|
|
71
|
+
const hasRoomDb = _safeIsFile(roomDbPath);
|
|
72
|
+
return { roomDir: absPath, hasRoomDb: hasRoomDb };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Return a 1-2 hop neighborhood snapshot of the most-recent Decision node in
|
|
77
|
+
* the room graph. The dashboard MVA agent renders this as the "your room
|
|
78
|
+
* already has N related decision nodes" surface.
|
|
79
|
+
*
|
|
80
|
+
* @param {string} roomDir absolute path to the active room
|
|
81
|
+
* @param {{ hops?: number, limit?: number }} [opts]
|
|
82
|
+
* @returns {{ nodes: Array, edges: Array }}
|
|
83
|
+
*/
|
|
84
|
+
function getRecentDecisionNeighborhood(roomDir, opts) {
|
|
85
|
+
const options = opts || {};
|
|
86
|
+
const hops = Number.isInteger(options.hops) && options.hops >= 1 ? options.hops : 1;
|
|
87
|
+
const limit = Number.isInteger(options.limit) && options.limit > 0 ? options.limit : 5;
|
|
88
|
+
|
|
89
|
+
let db;
|
|
90
|
+
try {
|
|
91
|
+
db = roomDbMod.openRoomDb(roomDir);
|
|
92
|
+
} catch (_e) {
|
|
93
|
+
return { nodes: [], edges: [] };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let nodes = [];
|
|
97
|
+
let edges = [];
|
|
98
|
+
try {
|
|
99
|
+
// Pick the most recent decision node (or any node if no decisions) as focus.
|
|
100
|
+
const focusRow = db.prepare(
|
|
101
|
+
"SELECT id FROM nodes WHERE type = 'decision' ORDER BY created_at DESC LIMIT 1"
|
|
102
|
+
).get();
|
|
103
|
+
if (!focusRow) {
|
|
104
|
+
return { nodes: [], edges: [] };
|
|
105
|
+
}
|
|
106
|
+
const focusNodeId = focusRow.id;
|
|
107
|
+
// Use the existing chokepoint to get the neighborhood. The depth + topK
|
|
108
|
+
// map onto Plan 118-02's "1-2 hop / 5-node snapshot" contract.
|
|
109
|
+
const neighborhood = neighborhoodMod.getNeighborhood(db, focusNodeId, {
|
|
110
|
+
maxDepth: hops,
|
|
111
|
+
topK: limit,
|
|
112
|
+
});
|
|
113
|
+
// Always include the focus node itself as the first node in the returned set.
|
|
114
|
+
nodes = [{ id: focusNodeId, type: 'decision' }].concat(neighborhood.map((n) => ({
|
|
115
|
+
id: n.id,
|
|
116
|
+
type: n.type,
|
|
117
|
+
score: n.score,
|
|
118
|
+
edgeTypeIn: n.edgeTypeIn,
|
|
119
|
+
depth: n.depth,
|
|
120
|
+
})));
|
|
121
|
+
// The closed neighborhood query also returns edge_path; surface a flat
|
|
122
|
+
// edges array of (source, target, type) tuples derived from the path
|
|
123
|
+
// metadata so the deck renderer (Plan 118-03) can draw the graph.
|
|
124
|
+
for (const n of neighborhood) {
|
|
125
|
+
if (n.edgePath && n.edgePath.length >= 2) {
|
|
126
|
+
const pathArr = n.edgePath;
|
|
127
|
+
for (let i = 0; i + 1 < pathArr.length; i++) {
|
|
128
|
+
edges.push({
|
|
129
|
+
source: pathArr[i],
|
|
130
|
+
target: pathArr[i + 1],
|
|
131
|
+
type: i + 1 === pathArr.length - 1 ? (n.edgeTypeIn || 'unknown') : 'path',
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
} catch (_e) {
|
|
137
|
+
nodes = [];
|
|
138
|
+
edges = [];
|
|
139
|
+
} finally {
|
|
140
|
+
try { roomDbMod.closeRoomDb(db); } catch (_e) { /* tolerant */ }
|
|
141
|
+
}
|
|
142
|
+
return { nodes, edges };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
module.exports = { detectActiveRoom, getRecentDecisionNeighborhood };
|
package/lib/core/navigation.cjs
CHANGED
|
@@ -30,6 +30,7 @@ const packet = require('./navigation/packet.cjs');
|
|
|
30
30
|
const ingestion = require('./navigation/ingestion.cjs');
|
|
31
31
|
const roomHome = require('./navigation/room-home.cjs');
|
|
32
32
|
const edges = require('./navigation/edges.cjs');
|
|
33
|
+
const dashboardHelpers = require('./navigation/dashboard-helpers.cjs');
|
|
33
34
|
|
|
34
35
|
function notImplementedYet(name, plan) {
|
|
35
36
|
return function () {
|
|
@@ -88,4 +89,14 @@ module.exports = {
|
|
|
88
89
|
// 116/117/118 will extend the allowlist additively for tension / auto-explore /
|
|
89
90
|
// MVA edges. Same additive-re-export pattern as logMemoryEvent + firstCapturedLastTouchedBySection.)
|
|
90
91
|
writeEdge: edges.writeEdge,
|
|
92
|
+
|
|
93
|
+
// Dashboard MVA helpers (Phase 118 Plan 02 -- additive re-export so the
|
|
94
|
+
// lib/agents/mva/dashboard-graph-neighborhood.cjs agent goes through the
|
|
95
|
+
// navigation chokepoint per Canon Part 9 D-06 invariant. Same additive-
|
|
96
|
+
// re-export pattern as logMemoryEvent / firstCapturedLastTouchedBySection /
|
|
97
|
+
// writeEdge. detectActiveRoom mirrors scripts/brain-derive-command.cjs
|
|
98
|
+
// line 142 verbatim; getRecentDecisionNeighborhood is a thin wrapper that
|
|
99
|
+
// resolves the focus node + calls the existing getNeighborhood chokepoint.)
|
|
100
|
+
detectActiveRoom: dashboardHelpers.detectActiveRoom,
|
|
101
|
+
getRecentDecisionNeighborhood: dashboardHelpers.getRecentDecisionNeighborhood,
|
|
91
102
|
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Mindrian. BSL 1.1.
|
|
3
|
+
*
|
|
4
|
+
* Phase 118-04 Plan 04 Task 1 -- resolve-vercel-key.
|
|
5
|
+
*
|
|
6
|
+
* The ONE resolver for "where is the Vercel deploy token on this machine?".
|
|
7
|
+
* Mirrors lib/core/resolve-brain-key.cjs (Phase 95.6 / Phase 123 Plan-07).
|
|
8
|
+
*
|
|
9
|
+
* Precedence per LD2 (Locked Decision 2 in 118-CONTEXT.md):
|
|
10
|
+
* 1. process.env.VERCEL_TOKEN -- explicit operator intent, highest.
|
|
11
|
+
* 2. <home>/.mindrian.env -- global backup, persists across CWDs.
|
|
12
|
+
* 3. <cwd>/.env -- project-local override.
|
|
13
|
+
* 4. null -- triggers local-file fallback path.
|
|
14
|
+
*
|
|
15
|
+
* Quote-stripping: handles both `VERCEL_TOKEN="abc"` (double-quoted) and
|
|
16
|
+
* `VERCEL_TOKEN=abc` (raw) per feedback_gmail_qp_env_var_corruption.md.
|
|
17
|
+
*
|
|
18
|
+
* Returns:
|
|
19
|
+
* resolveVercelKey({home?, cwd?}) -> string | null
|
|
20
|
+
*
|
|
21
|
+
* Constants:
|
|
22
|
+
* VERCEL_PROJECT_NAME = 'mindrianos-briefs'
|
|
23
|
+
*
|
|
24
|
+
* Canon Part 8: this module reads LOCAL files and env only. Zero network
|
|
25
|
+
* surface. The Vercel API call is mva-vercel-deploy.cjs's job; this module
|
|
26
|
+
* only DISCOVERS whether a token exists.
|
|
27
|
+
*
|
|
28
|
+
* No transitive runtime dependencies; node built-ins only.
|
|
29
|
+
*/
|
|
30
|
+
'use strict';
|
|
31
|
+
|
|
32
|
+
const fs = require('node:fs');
|
|
33
|
+
const path = require('node:path');
|
|
34
|
+
const os = require('node:os');
|
|
35
|
+
|
|
36
|
+
/** @type {string} The project name on Vercel where deck deploys live. */
|
|
37
|
+
const VERCEL_PROJECT_NAME = 'mindrianos-briefs';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Parse a single VERCEL_TOKEN=... line out of a (possibly multi-line) env-file
|
|
41
|
+
* body. Returns the trimmed value with surrounding double-quotes stripped, or
|
|
42
|
+
* null if the line is absent or the value is empty.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} body
|
|
45
|
+
* @returns {string|null}
|
|
46
|
+
*/
|
|
47
|
+
function _parseKey(body) {
|
|
48
|
+
const m = body.match(/^VERCEL_TOKEN\s*=\s*(.+?)\s*$/m);
|
|
49
|
+
if (!m) return null;
|
|
50
|
+
let v = m[1].trim();
|
|
51
|
+
// Strip surrounding double-quotes if present
|
|
52
|
+
if (v.length >= 2 && v.startsWith('"') && v.endsWith('"')) {
|
|
53
|
+
v = v.slice(1, -1);
|
|
54
|
+
}
|
|
55
|
+
return v.length > 0 ? v : null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Resolve the Vercel deploy token from the standard precedence chain.
|
|
60
|
+
*
|
|
61
|
+
* The `home` and `cwd` parameters exist as test seams (mirrors
|
|
62
|
+
* resolve-brain-key.cjs pattern). Env-aware home resolution so hermetic tests
|
|
63
|
+
* overriding process.env.HOME work on Linux/POSIX, where os.homedir() reads
|
|
64
|
+
* /etc/passwd and ignores the env override.
|
|
65
|
+
*
|
|
66
|
+
* @param {{home?: string, cwd?: string}} [opts]
|
|
67
|
+
* @returns {string|null}
|
|
68
|
+
*/
|
|
69
|
+
function resolveVercelKey(opts) {
|
|
70
|
+
const o = opts || {};
|
|
71
|
+
const home = o.home || process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
72
|
+
const cwd = o.cwd || process.cwd();
|
|
73
|
+
|
|
74
|
+
// (1) Env var wins.
|
|
75
|
+
if (process.env.VERCEL_TOKEN) {
|
|
76
|
+
const v = String(process.env.VERCEL_TOKEN).trim();
|
|
77
|
+
if (v.length > 0) return v;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// (2) <home>/.mindrian.env
|
|
81
|
+
const mindrianEnvPath = path.join(home, '.mindrian.env');
|
|
82
|
+
if (fs.existsSync(mindrianEnvPath)) {
|
|
83
|
+
try {
|
|
84
|
+
const body = fs.readFileSync(mindrianEnvPath, 'utf8');
|
|
85
|
+
const v = _parseKey(body);
|
|
86
|
+
if (v) return v;
|
|
87
|
+
} catch (_e) { /* fall through */ }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// (3) <cwd>/.env
|
|
91
|
+
const cwdEnvPath = path.join(cwd, '.env');
|
|
92
|
+
if (fs.existsSync(cwdEnvPath)) {
|
|
93
|
+
try {
|
|
94
|
+
const body = fs.readFileSync(cwdEnvPath, 'utf8');
|
|
95
|
+
const v = _parseKey(body);
|
|
96
|
+
if (v) return v;
|
|
97
|
+
} catch (_e) { /* fall through */ }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// (4) not-found
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
resolveVercelKey,
|
|
106
|
+
VERCEL_PROJECT_NAME,
|
|
107
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Mindrian. BSL 1.1.
|
|
3
|
+
*
|
|
4
|
+
* Phase 118-04 Plan 04 Task 1 -- resolve-vercel-key tests.
|
|
5
|
+
*
|
|
6
|
+
* Mirrors test patterns from lib/core/resolve-brain-key.cjs tests.
|
|
7
|
+
* Precedence per LD2: process.env.VERCEL_TOKEN -> <home>/.mindrian.env ->
|
|
8
|
+
* <cwd>/.env -> null.
|
|
9
|
+
*
|
|
10
|
+
* Quote-stripping: handles both `VERCEL_TOKEN="abc"` (double-quoted) and
|
|
11
|
+
* `VERCEL_TOKEN=abc` (raw) per feedback_gmail_qp_env_var_corruption.md.
|
|
12
|
+
*
|
|
13
|
+
* VERCEL_PROJECT_NAME is exported as the constant 'mindrianos-briefs'.
|
|
14
|
+
*/
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const test = require('node:test');
|
|
18
|
+
const assert = require('node:assert/strict');
|
|
19
|
+
const fs = require('node:fs');
|
|
20
|
+
const os = require('node:os');
|
|
21
|
+
const path = require('node:path');
|
|
22
|
+
|
|
23
|
+
function _mkTmpHome() {
|
|
24
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'mva-vercel-key-test-'));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function _cleanup(dir) {
|
|
28
|
+
try { fs.rmSync(dir, { recursive: true, force: true }); } catch (_e) { /* ignore */ }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Force-reload module after env mutation
|
|
32
|
+
function _freshRequire() {
|
|
33
|
+
const p = require.resolve('./resolve-vercel-key.cjs');
|
|
34
|
+
delete require.cache[p];
|
|
35
|
+
return require('./resolve-vercel-key.cjs');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
test('Test 1: process.env.VERCEL_TOKEN takes precedence over file', () => {
|
|
39
|
+
const home = _mkTmpHome();
|
|
40
|
+
try {
|
|
41
|
+
fs.writeFileSync(path.join(home, '.mindrian.env'),
|
|
42
|
+
'VERCEL_TOKEN="from-file"\n', { mode: 0o600 });
|
|
43
|
+
const prev = process.env.VERCEL_TOKEN;
|
|
44
|
+
process.env.VERCEL_TOKEN = 'from-env';
|
|
45
|
+
try {
|
|
46
|
+
const { resolveVercelKey } = _freshRequire();
|
|
47
|
+
const r = resolveVercelKey({ home });
|
|
48
|
+
assert.equal(r, 'from-env');
|
|
49
|
+
} finally {
|
|
50
|
+
if (prev === undefined) { delete process.env.VERCEL_TOKEN; }
|
|
51
|
+
else { process.env.VERCEL_TOKEN = prev; }
|
|
52
|
+
}
|
|
53
|
+
} finally {
|
|
54
|
+
_cleanup(home);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('Test 2: ~/.mindrian.env with quoted VERCEL_TOKEN resolves correctly', () => {
|
|
59
|
+
const home = _mkTmpHome();
|
|
60
|
+
try {
|
|
61
|
+
fs.writeFileSync(path.join(home, '.mindrian.env'),
|
|
62
|
+
'OTHER_VAR=ignore\nVERCEL_TOKEN="abc123"\n', { mode: 0o600 });
|
|
63
|
+
const prev = process.env.VERCEL_TOKEN;
|
|
64
|
+
delete process.env.VERCEL_TOKEN;
|
|
65
|
+
try {
|
|
66
|
+
const { resolveVercelKey } = _freshRequire();
|
|
67
|
+
const r = resolveVercelKey({ home, cwd: home });
|
|
68
|
+
assert.equal(r, 'abc123');
|
|
69
|
+
} finally {
|
|
70
|
+
if (prev !== undefined) process.env.VERCEL_TOKEN = prev;
|
|
71
|
+
}
|
|
72
|
+
} finally {
|
|
73
|
+
_cleanup(home);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('Test 3: no env + no file = null', () => {
|
|
78
|
+
const home = _mkTmpHome();
|
|
79
|
+
try {
|
|
80
|
+
const prev = process.env.VERCEL_TOKEN;
|
|
81
|
+
delete process.env.VERCEL_TOKEN;
|
|
82
|
+
try {
|
|
83
|
+
const { resolveVercelKey } = _freshRequire();
|
|
84
|
+
const r = resolveVercelKey({ home, cwd: home });
|
|
85
|
+
assert.equal(r, null);
|
|
86
|
+
} finally {
|
|
87
|
+
if (prev !== undefined) process.env.VERCEL_TOKEN = prev;
|
|
88
|
+
}
|
|
89
|
+
} finally {
|
|
90
|
+
_cleanup(home);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('Test 4: ~/.mindrian.env with unquoted VERCEL_TOKEN resolves correctly', () => {
|
|
95
|
+
const home = _mkTmpHome();
|
|
96
|
+
try {
|
|
97
|
+
fs.writeFileSync(path.join(home, '.mindrian.env'),
|
|
98
|
+
'VERCEL_TOKEN=raw-no-quotes\n', { mode: 0o600 });
|
|
99
|
+
const prev = process.env.VERCEL_TOKEN;
|
|
100
|
+
delete process.env.VERCEL_TOKEN;
|
|
101
|
+
try {
|
|
102
|
+
const { resolveVercelKey } = _freshRequire();
|
|
103
|
+
const r = resolveVercelKey({ home, cwd: home });
|
|
104
|
+
assert.equal(r, 'raw-no-quotes');
|
|
105
|
+
} finally {
|
|
106
|
+
if (prev !== undefined) process.env.VERCEL_TOKEN = prev;
|
|
107
|
+
}
|
|
108
|
+
} finally {
|
|
109
|
+
_cleanup(home);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('Test 5: VERCEL_PROJECT_NAME exported constant === mindrianos-briefs', () => {
|
|
114
|
+
const { VERCEL_PROJECT_NAME } = _freshRequire();
|
|
115
|
+
assert.equal(VERCEL_PROJECT_NAME, 'mindrianos-briefs');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('Test 5b: CWD .env fallback when ~/.mindrian.env absent', () => {
|
|
119
|
+
const home = _mkTmpHome();
|
|
120
|
+
const cwd = _mkTmpHome();
|
|
121
|
+
try {
|
|
122
|
+
fs.writeFileSync(path.join(cwd, '.env'),
|
|
123
|
+
'VERCEL_TOKEN="from-cwd-env"\n', { mode: 0o600 });
|
|
124
|
+
const prev = process.env.VERCEL_TOKEN;
|
|
125
|
+
delete process.env.VERCEL_TOKEN;
|
|
126
|
+
try {
|
|
127
|
+
const { resolveVercelKey } = _freshRequire();
|
|
128
|
+
const r = resolveVercelKey({ home, cwd });
|
|
129
|
+
assert.equal(r, 'from-cwd-env');
|
|
130
|
+
} finally {
|
|
131
|
+
if (prev !== undefined) process.env.VERCEL_TOKEN = prev;
|
|
132
|
+
}
|
|
133
|
+
} finally {
|
|
134
|
+
_cleanup(home);
|
|
135
|
+
_cleanup(cwd);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
@@ -1346,6 +1346,33 @@ const TEST_FILES = [
|
|
|
1346
1346
|
// .planning/phases/104.1-per-command-teaching-content/104.1-01-PLAN.md.
|
|
1347
1347
|
path.join(REPO_ROOT, 'lib', 'memory', 'per-command-teaching.test.cjs'),
|
|
1348
1348
|
path.join(REPO_ROOT, 'lib', 'memory', 'per-command-jtbd-derivation.test.cjs'),
|
|
1349
|
+
// ---------- Phase 118 ----------
|
|
1350
|
+
// 30-Second MVA + Reward-Before-Investment Rule. Plans 00-06 ship the
|
|
1351
|
+
// pipeline; Plan 06 ships the rule + linter + Dror 2.0 harness.
|
|
1352
|
+
// Aggregator: tests/run-all-118.sh.
|
|
1353
|
+
// Plan 118-00 (UserPromptSubmit detection):
|
|
1354
|
+
path.join(REPO_ROOT, 'lib', 'core', 'mva-classifier.test.cjs'),
|
|
1355
|
+
path.join(REPO_ROOT, 'lib', 'core', 'mva-detect.smoke.test.cjs'),
|
|
1356
|
+
// Plan 118-01 (dispatch architecture):
|
|
1357
|
+
path.join(REPO_ROOT, 'lib', 'core', 'mva-agent-contract.test.cjs'),
|
|
1358
|
+
path.join(REPO_ROOT, 'lib', 'core', 'mva-budget.test.cjs'),
|
|
1359
|
+
path.join(REPO_ROOT, 'lib', 'core', 'mva-dispatcher.test.cjs'),
|
|
1360
|
+
// Plan 118-02 (six MVA agents -- aggregate runner for all 6):
|
|
1361
|
+
path.join(REPO_ROOT, 'lib', 'agents', 'mva', 'test-all-six-agents.cjs'),
|
|
1362
|
+
// Plan 118-03 (progressive streaming + orchestrator + telemetry):
|
|
1363
|
+
path.join(REPO_ROOT, 'lib', 'core', 'mva-progressive-renderer.test.cjs'),
|
|
1364
|
+
path.join(REPO_ROOT, 'lib', 'core', 'mva-telemetry.test.cjs'),
|
|
1365
|
+
path.join(REPO_ROOT, 'lib', 'core', 'mva-orchestrator.test.cjs'),
|
|
1366
|
+
// Plan 118-04 (Feynman deck + Vercel deploy):
|
|
1367
|
+
path.join(REPO_ROOT, 'lib', 'core', 'resolve-vercel-key.test.cjs'),
|
|
1368
|
+
path.join(REPO_ROOT, 'lib', 'core', 'mva-vercel-deploy.test.cjs'),
|
|
1369
|
+
path.join(REPO_ROOT, 'lib', 'core', 'mva-deck-builder.test.cjs'),
|
|
1370
|
+
// Plan 118-05 (footer routing + operator helper):
|
|
1371
|
+
path.join(REPO_ROOT, 'lib', 'conversation', 'operator.test.cjs'),
|
|
1372
|
+
path.join(REPO_ROOT, 'lib', 'core', 'mva-option-router.test.cjs'),
|
|
1373
|
+
// Plan 118-06 (rule-linter + Dror 2.0 acceptance harness):
|
|
1374
|
+
path.join(REPO_ROOT, 'lib', 'core', 'mva-rule-linter.test.cjs'),
|
|
1375
|
+
path.join(REPO_ROOT, 'tests', 'test-mva-dror-harness.cjs'),
|
|
1349
1376
|
];
|
|
1350
1377
|
|
|
1351
1378
|
// Exit code convention for child tests:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mindrian_os/install",
|
|
3
|
-
"version": "1.13.0-beta.
|
|
3
|
+
"version": "1.13.0-beta.17",
|
|
4
4
|
"description": "Install MindrianOS into Claude Code with one command -- `npx @mindrian_os/install`. Ships the MindrianOS plugin (Larry + PWS methodology + Data Room) plus a setup/diagnostics CLI (install/doctor/update).",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"mcp": "node bin/mindrian-mcp-server.cjs",
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mva-pipeline
|
|
3
|
+
description: Auto-activates when UserPromptSubmit detection classifies the user's prompt as a venture sentence; relays the 30-second MVA brief in Larry's voice
|
|
4
|
+
auto-activate: state-file
|
|
5
|
+
state-file: ~/.mindrian/mva/<session-id>.json
|
|
6
|
+
state-condition: pending && !running
|
|
7
|
+
interactive_first_reward: instant_brief
|
|
8
|
+
canon_parts: [Part 2, Part 8, Part 10]
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# The 30-second MVA skill
|
|
12
|
+
|
|
13
|
+
## When this activates
|
|
14
|
+
|
|
15
|
+
When Plan 118-00's UserPromptSubmit detection writes a pending state with
|
|
16
|
+
`pipeline_status: 'pending'` (or `hebrew_refusal: true`), this skill fires on
|
|
17
|
+
the NEXT model turn. The skill is the bridge between Plan 118-00's classifier
|
|
18
|
+
and Plan 118-03's orchestrator: it makes Larry the GUIDED narrator of the
|
|
19
|
+
brief without baking the orchestrator into Claude Code's hook protocol.
|
|
20
|
+
|
|
21
|
+
## What to do
|
|
22
|
+
|
|
23
|
+
1. Run `/mos:mva-brief` (or invoke `node scripts/mva-run.cjs` via Bash directly).
|
|
24
|
+
2. Relay the stdout to the user VERBATIM, in your normal Larry voice (no extra
|
|
25
|
+
framing). The renderer already speaks in Larry's GUIDED voice; double-voicing
|
|
26
|
+
breaks the pedagogical contract.
|
|
27
|
+
3. Wait for the user's option selection (1, 2, 3, or free-text).
|
|
28
|
+
4. Route per the footer behavior:
|
|
29
|
+
- 1 -> stay in JUST_TALK; the brief stays visible; user can ask follow-ups
|
|
30
|
+
about any cell ("tell me more about the Honeydue case", "what was the
|
|
31
|
+
Gottman analogy?", etc.)
|
|
32
|
+
- 2 -> invoke /mos:new-project (Phase 119 wrapper; in v1.13.0 this surfaces
|
|
33
|
+
a stub message that the full room-build flow lands in beta.18)
|
|
34
|
+
- 3 -> invoke /mos:challenge-assumptions against the brief
|
|
35
|
+
|
|
36
|
+
## What NOT to do (per feedback_larry_pedagogical_guided_first.md)
|
|
37
|
+
|
|
38
|
+
- Do NOT add commentary or "I noticed..." preamble before the rendered output.
|
|
39
|
+
The renderer already opens with "Scanning for precedents..." in Larry's
|
|
40
|
+
GUIDED voice.
|
|
41
|
+
- Do NOT interpret findings autonomously ("This means you should..."). The
|
|
42
|
+
rendered output IS GUIDED -- it asks the user to think; the renderer
|
|
43
|
+
surfaces what's in the graph and lets the navigator chew on it.
|
|
44
|
+
- Do NOT skip the 3-option footer. Even if all agents failed, the
|
|
45
|
+
sharp-question fallback substitutes for the footer (per binding decision B7);
|
|
46
|
+
the renderer handles this -- you just relay verbatim.
|
|
47
|
+
- Do NOT pre-pick an option. The user picks. That's the decision gate.
|
|
48
|
+
|
|
49
|
+
## Canon parts implemented
|
|
50
|
+
|
|
51
|
+
- Part 2 (team around navigator -- 6 agents as a parallel team, with the skill
|
|
52
|
+
being the surface Larry uses to relay the team's output)
|
|
53
|
+
- Part 8 (boundary -- the agents send ONLY generic handles to Brain and
|
|
54
|
+
Tavily; the renderer + skill never re-introduce user content)
|
|
55
|
+
- Part 10 sub-claim 3 (room as receipt -- the brief IS the reward delivered
|
|
56
|
+
BEFORE the user is asked to invest in setting up a room; option 2 is the
|
|
57
|
+
ask-for-investment that comes AFTER the reward, per Hooked sequencing)
|
|
58
|
+
|
|
59
|
+
## State file contract
|
|
60
|
+
|
|
61
|
+
Read from `~/.mindrian/mva/<session-id>.json` (the Plan 118-00 wire):
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
{
|
|
65
|
+
"sentence_sha256": "<64-hex>", // NEVER the raw sentence
|
|
66
|
+
"classified_at": <epoch_ms>,
|
|
67
|
+
"classifier_source": "heuristic" | "heuristic_fallback" | "haiku-4-5",
|
|
68
|
+
"classifier_confidence": "high" | "medium" | "low",
|
|
69
|
+
"locale": "en" | "he",
|
|
70
|
+
"hebrew_refusal": true, // optional, set on LD1 short-circuit
|
|
71
|
+
"pipeline_status": "pending" | "running" | "complete"
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
On `hebrew_refusal:true`, the orchestrator renders the bilingual refusal block
|
|
76
|
+
and DOES NOT fire the dispatcher (per LD1 in 118-CONTEXT.md).
|
|
77
|
+
|
|
78
|
+
## Routing the 3-option footer (after the brief renders)
|
|
79
|
+
|
|
80
|
+
Once `/mos:mva-brief` (or the orchestrator) has rendered the brief to the user,
|
|
81
|
+
the user's next message is most likely an option selection. Recognition rule:
|
|
82
|
+
|
|
83
|
+
- User types exactly `1`, `2`, or `3` -> invoke `/mos:mva-option <N>` (no sha8
|
|
84
|
+
argument needed; the command auto-resolves via `resolveCurrentSha8()`)
|
|
85
|
+
- User types `/mos:mva-option N` explicitly -> invoke `/mos:mva-option <N>`
|
|
86
|
+
(sha8 still optional; auto-resolved when omitted)
|
|
87
|
+
- User types anything else -> handle as a normal conversation turn (do NOT
|
|
88
|
+
route through `mva-option`)
|
|
89
|
+
|
|
90
|
+
The sha8 argument is OPTIONAL because the router auto-discovers the most
|
|
91
|
+
recent brief via `resolveCurrentSha8()` -> `~/.mindrian/mva/state.json` (the
|
|
92
|
+
manifest written by Plan 118-03's orchestrator after `mva_brief_rendered`).
|
|
93
|
+
|
|
94
|
+
### Per-option behavior
|
|
95
|
+
|
|
96
|
+
Option 1 -- "Just tell me what's new" (stay in tell-me mode):
|
|
97
|
+
- Acknowledgment: "Keeping the brief visible. Ask me anything about what you just saw."
|
|
98
|
+
- Operator: transitions to `JUST_TALK`
|
|
99
|
+
- Brief stays in scrollback; follow-up questions about any of the 6 cells are welcome
|
|
100
|
+
|
|
101
|
+
Option 2 -- "Build a room around this" (invest, deferred):
|
|
102
|
+
- Show the stub message verbatim from `STUB_MESSAGE_119`:
|
|
103
|
+
"Building a room around this is the next layer; shipping in beta.18 (Phase 119). For now, press option 1 to keep this brief visible, or option 3 to go deeper."
|
|
104
|
+
- Operator: no transition (option 2 is stubbed for v1.13.0)
|
|
105
|
+
- In v1.13.0-beta.18 (Phase 119), this routes to `/mos:new-project --from-brief <sha8>`
|
|
106
|
+
|
|
107
|
+
Option 3 -- "Challenge me -- Devil's Advocate" (go deeper):
|
|
108
|
+
- Bridge text: "Going deeper. Pulling the brief into a Devil's Advocate pass."
|
|
109
|
+
- Operator: transitions to `METHODOLOGY`
|
|
110
|
+
- Invoke: `/mos:challenge-assumptions --from-brief <sha8>`
|
|
111
|
+
|
|
112
|
+
### Edge cases
|
|
113
|
+
|
|
114
|
+
- Brief data expired (side-file missing): tell the user the brief expired and
|
|
115
|
+
offer to re-run by typing their venture sentence again.
|
|
116
|
+
- Brief is still rendering (`mva_brief_rendered` event not yet emitted): hold
|
|
117
|
+
the option, tell the user "Brief is still rendering -- options will activate
|
|
118
|
+
when it completes".
|
|
119
|
+
- No `state.json` exists (fresh install or Hebrew refusal path): tell the user
|
|
120
|
+
"No recent brief found. Type your venture sentence to fire the pipeline."
|
|
121
|
+
- Invalid option (`4`, `99`, etc.): treat as free-text and route normally.
|
|
122
|
+
|
|
123
|
+
### Do NOT
|
|
124
|
+
|
|
125
|
+
- Do NOT autonomously pick an option for the user.
|
|
126
|
+
- Do NOT pre-summarize what option 2 "would do" -- the stub message says it.
|
|
127
|
+
- Do NOT add em-dashes to any rendered option text -- use `--` only.
|
|
128
|
+
- Do NOT invoke `/mos:new-project` for option 2 in v1.13.0 -- that wiring
|
|
129
|
+
lands in Phase 119 (beta.18).
|