@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,190 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2026 Mindrian. BSL 1.1.
|
|
4
|
+
*
|
|
5
|
+
* lib/core/stale-copy-scanner.cjs
|
|
6
|
+
* -------------------------------
|
|
7
|
+
* Phase 121.5-05 Sub-plan F Task 2 (SEED-007 absorption).
|
|
8
|
+
*
|
|
9
|
+
* Pure CJS module that scans the first-touch surfaces declared in
|
|
10
|
+
* data/first-touch-surfaces.json for two regression patterns:
|
|
11
|
+
*
|
|
12
|
+
* - stale_version_hardcode a literal `vX.Y.Z` (or `X.Y.Z`) in greeting
|
|
13
|
+
* copy that is OLDER than the running plugin
|
|
14
|
+
* version (semver compare). Source finding
|
|
15
|
+
* (SEED-007, 2026-05-09): a v1.12.0 greeting
|
|
16
|
+
* was rendering on a stale Windows install that
|
|
17
|
+
* had been bumped to v1.13.0.
|
|
18
|
+
* - em_dash_violation any U+2014 EM DASH in a surface that has
|
|
19
|
+
* em_dash_check: true. Enforces the
|
|
20
|
+
* feedback_no_emdashes hard rule.
|
|
21
|
+
*
|
|
22
|
+
* Per-surface invariants come from data/first-touch-surfaces.json:
|
|
23
|
+
* - version_stamp_required: false -> skip stale_version_hardcode
|
|
24
|
+
* - em_dash_check: false -> skip em_dash_violation
|
|
25
|
+
* - allowed_hardcoded_versions[] -> whitelist of strings exempt from
|
|
26
|
+
* stale_version_hardcode (template
|
|
27
|
+
* tokens, doc examples)
|
|
28
|
+
*
|
|
29
|
+
* Wired into scripts/doctor.cjs as class K (stale-first-touch-copy).
|
|
30
|
+
*
|
|
31
|
+
* Canon Part 7 (Reuse Before Build): consolidates the SEED-007 detection
|
|
32
|
+
* responsibility previously scattered across scripts/check-first-touch-drift.cjs
|
|
33
|
+
* (which exists but is partial / not exposed in /mos:doctor) and the manual
|
|
34
|
+
* audit pattern. ZERO new commands; class K extends the existing class A-J
|
|
35
|
+
* doctor pattern.
|
|
36
|
+
*
|
|
37
|
+
* Canon Part 8: local-only; no fetch, no Brain, no telemetry egress.
|
|
38
|
+
*
|
|
39
|
+
* Note: this plan was scoped against class H in 121.5-05-PLAN.md, but class
|
|
40
|
+
* H was already taken (install-incomplete: missing statusLine block + halted
|
|
41
|
+
* .install-receipt.json) at the time of execution. Renamed to class K (next
|
|
42
|
+
* available after I + J) per deviation Rule 3 (auto-fix blocking issue:
|
|
43
|
+
* naming collision). The behavior contract from the plan is preserved
|
|
44
|
+
* byte-identical; only the class letter changed.
|
|
45
|
+
*
|
|
46
|
+
* No emoji. No em-dashes. ASCII-clean source.
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
'use strict';
|
|
50
|
+
|
|
51
|
+
const fs = require('fs');
|
|
52
|
+
const path = require('path');
|
|
53
|
+
const {
|
|
54
|
+
readSurfaces,
|
|
55
|
+
resolveCurrentVersion,
|
|
56
|
+
hasEmDash,
|
|
57
|
+
} = require('./first-touch-version-stamper.cjs');
|
|
58
|
+
|
|
59
|
+
const REPO_ROOT = path.join(__dirname, '..', '..');
|
|
60
|
+
|
|
61
|
+
// vX.Y.Z or X.Y.Z (optional -<prerelease> tail). Captures the X.Y.Z core in
|
|
62
|
+
// group 1 (without the leading v) for semver compare. Matches:
|
|
63
|
+
// v1.13.0 -> 1.13.0
|
|
64
|
+
// 1.13.0 -> 1.13.0
|
|
65
|
+
// v1.13.0-beta.15 -> 1.13.0-beta.15
|
|
66
|
+
const VERSION_LITERAL_RE = /\bv?(\d+\.\d+\.\d+(?:-[a-zA-Z0-9.]+)?)\b/g;
|
|
67
|
+
|
|
68
|
+
// compareSemver(a, b) returns negative if a < b, positive if a > b, 0 if
|
|
69
|
+
// equal. Loose splitter (numeric-aware for X.Y.Z core; lexical for the
|
|
70
|
+
// prerelease tail). Sufficient for the SEED-007 detection pattern (older
|
|
71
|
+
// X.Y.Z than current); does not aim to match the full semver 2.0 spec.
|
|
72
|
+
function compareSemver(a, b) {
|
|
73
|
+
const parseLoose = (s) => String(s)
|
|
74
|
+
.replace(/^v/, '')
|
|
75
|
+
.split(/[.-]/)
|
|
76
|
+
.map((p) => (/^\d+$/.test(p) ? parseInt(p, 10) : p));
|
|
77
|
+
const A = parseLoose(a);
|
|
78
|
+
const B = parseLoose(b);
|
|
79
|
+
const len = Math.max(A.length, B.length);
|
|
80
|
+
for (let i = 0; i < len; i++) {
|
|
81
|
+
const x = A[i];
|
|
82
|
+
const y = B[i];
|
|
83
|
+
if (x === undefined) return -1;
|
|
84
|
+
if (y === undefined) return 1;
|
|
85
|
+
if (typeof x === 'number' && typeof y === 'number') {
|
|
86
|
+
if (x !== y) return x - y;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
// Mixed-type or string compare: stringwise.
|
|
90
|
+
const sx = String(x);
|
|
91
|
+
const sy = String(y);
|
|
92
|
+
if (sx !== sy) return sx < sy ? -1 : 1;
|
|
93
|
+
}
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// scanSurface(surface, currentVersion, opts?) -> violations[]
|
|
98
|
+
// opts.repoRoot can override the file root for tests / fixtures.
|
|
99
|
+
function scanSurface(surface, currentVersion, opts) {
|
|
100
|
+
const violations = [];
|
|
101
|
+
const repoRoot = (opts && opts.repoRoot) || REPO_ROOT;
|
|
102
|
+
const filePath = path.isAbsolute(surface.file)
|
|
103
|
+
? surface.file
|
|
104
|
+
: path.join(repoRoot, surface.file);
|
|
105
|
+
let content;
|
|
106
|
+
try {
|
|
107
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
108
|
+
} catch (_e) {
|
|
109
|
+
// Missing surface file is NOT a violation (per scripts/check-first-touch-
|
|
110
|
+
// drift.cjs convention -- missing is warn-only, not hard fail). Tests
|
|
111
|
+
// explicitly cover the present-but-stale + present-but-em-dashed paths.
|
|
112
|
+
return violations;
|
|
113
|
+
}
|
|
114
|
+
if (surface.em_dash_check && hasEmDash(content)) {
|
|
115
|
+
violations.push({
|
|
116
|
+
surface: surface.id,
|
|
117
|
+
file: surface.file,
|
|
118
|
+
kind: 'em_dash_violation',
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (surface.version_stamp_required !== false) {
|
|
122
|
+
const allowed = new Set(surface.allowed_hardcoded_versions || []);
|
|
123
|
+
VERSION_LITERAL_RE.lastIndex = 0;
|
|
124
|
+
let m;
|
|
125
|
+
while ((m = VERSION_LITERAL_RE.exec(content)) !== null) {
|
|
126
|
+
const found = m[1];
|
|
127
|
+
// Allowlist check: exact match against the captured X.Y.Z(-prerelease)
|
|
128
|
+
// core OR against the original literal token (with optional 'v' prefix).
|
|
129
|
+
if (allowed.has(found) || allowed.has('v' + found) || allowed.has(m[0])) continue;
|
|
130
|
+
// Anything OLDER than the running plugin version is stale -- that is
|
|
131
|
+
// the SEED-007 trigger. Equal or newer is fine.
|
|
132
|
+
if (compareSemver(found, currentVersion) < 0) {
|
|
133
|
+
violations.push({
|
|
134
|
+
surface: surface.id,
|
|
135
|
+
file: surface.file,
|
|
136
|
+
kind: 'stale_version_hardcode',
|
|
137
|
+
found,
|
|
138
|
+
current: currentVersion,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return violations;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// scanForStaleCopy(opts?) -> {valid, violations, scanned_count, current_version}
|
|
147
|
+
// opts.surfaces overrides the default surfaces[] from data/first-touch-surfaces.json.
|
|
148
|
+
// opts.currentVersion overrides the auto-resolved plugin.json version.
|
|
149
|
+
// opts.repoRoot routes relative paths against an override (for tests).
|
|
150
|
+
function scanForStaleCopy(opts) {
|
|
151
|
+
opts = opts || {};
|
|
152
|
+
const surfaces = opts.surfaces ? opts.surfaces : readSurfaces().surfaces;
|
|
153
|
+
const currentVersion = opts.currentVersion || resolveCurrentVersion();
|
|
154
|
+
const violations = [];
|
|
155
|
+
for (const s of surfaces) {
|
|
156
|
+
violations.push(...scanSurface(s, currentVersion, { repoRoot: opts.repoRoot }));
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
valid: violations.length === 0,
|
|
160
|
+
violations,
|
|
161
|
+
scanned_count: surfaces.length,
|
|
162
|
+
current_version: currentVersion,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
module.exports = {
|
|
167
|
+
scanForStaleCopy,
|
|
168
|
+
scanSurface,
|
|
169
|
+
compareSemver,
|
|
170
|
+
VERSION_LITERAL_RE,
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// CLI invocation: `node lib/core/stale-copy-scanner.cjs` runs the scanner
|
|
174
|
+
// against the live repo and prints a summary. Exit 0 clean / Exit 1 violations.
|
|
175
|
+
if (require.main === module) {
|
|
176
|
+
const result = scanForStaleCopy();
|
|
177
|
+
if (result.valid) {
|
|
178
|
+
process.stdout.write('stale-copy-scanner: clean (' + result.scanned_count + ' surfaces scanned, current v' + result.current_version + ')\n');
|
|
179
|
+
process.exit(0);
|
|
180
|
+
}
|
|
181
|
+
process.stdout.write('stale-copy-scanner: ' + result.violations.length + ' violation(s) (current v' + result.current_version + ')\n');
|
|
182
|
+
for (const v of result.violations) {
|
|
183
|
+
if (v.kind === 'stale_version_hardcode') {
|
|
184
|
+
process.stdout.write(' ' + v.surface + ' (' + v.file + ') -- stale version literal "' + v.found + '" (current ' + v.current + ')\n');
|
|
185
|
+
} else if (v.kind === 'em_dash_violation') {
|
|
186
|
+
process.stdout.write(' ' + v.surface + ' (' + v.file + ') -- em-dash (U+2014) in greeting surface\n');
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Mindrian. BSL 1.1.
|
|
3
|
+
* Phase 121.5-08 (Sub-plan J) -- /mos:mos state-aware router.
|
|
4
|
+
*
|
|
5
|
+
* D-10 LOCKED: /mos:mos picks the right surface based on the navigator's
|
|
6
|
+
* current room state. The user types /mos:mos and the router meets them
|
|
7
|
+
* where they are.
|
|
8
|
+
*
|
|
9
|
+
* No room exists -> /mos:onboard
|
|
10
|
+
* Room exists but mostly empty -> /mos:status (+ suggest next move)
|
|
11
|
+
* ("mostly empty" = sectionsCount == 0 OR stage matches /Pre-Opportunity/i)
|
|
12
|
+
* Room populated (sections >= 1, stage past Pre-Opportunity)
|
|
13
|
+
* -> /mos:suggest-next
|
|
14
|
+
*
|
|
15
|
+
* The router is a PURE function. Reading STATE.md / the registry is done
|
|
16
|
+
* upstream by commands/mos.md; the router operates on the resolved
|
|
17
|
+
* roomState object only. This keeps the routing logic hermetic and
|
|
18
|
+
* unit-testable without any fs / spawn.
|
|
19
|
+
*
|
|
20
|
+
* Canon Part 3: /mos:mos is itself a Decision Gate at the meta level --
|
|
21
|
+
* the router writes a typed (navigator) -[ROUTED_VIA {target, reason}]->
|
|
22
|
+
* (next-surface) edge upstream when the routing decision is committed
|
|
23
|
+
* by commands/mos.md. The router function here only RESOLVES the target;
|
|
24
|
+
* edge emission is the caller's responsibility.
|
|
25
|
+
*
|
|
26
|
+
* Canon Part 7: ONE function replaces the prior "pick the right /mos:*
|
|
27
|
+
* surface for me" mental load on the user. NO new command behaviors
|
|
28
|
+
* are added -- /mos:mos delegates to /mos:onboard / /mos:status /
|
|
29
|
+
* /mos:suggest-next which already exist.
|
|
30
|
+
*
|
|
31
|
+
* Canon Part 8: zero Brain calls, zero network, zero side-channel writes.
|
|
32
|
+
*/
|
|
33
|
+
'use strict';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Resolve which canonical surface to route to given the current room state.
|
|
37
|
+
*
|
|
38
|
+
* @param {{
|
|
39
|
+
* roomState?: {
|
|
40
|
+
* exists?: boolean, // does the room directory exist?
|
|
41
|
+
* sectionsCount?: number, // how many section folders are populated?
|
|
42
|
+
* stage?: string, // venture stage from STATE.md (or empty)
|
|
43
|
+
* }
|
|
44
|
+
* }} opts
|
|
45
|
+
* @returns {{
|
|
46
|
+
* route: string, // '/mos:onboard' | '/mos:status' | '/mos:suggest-next'
|
|
47
|
+
* reason: string, // 'no_room' | 'mostly_empty' | 'populated'
|
|
48
|
+
* addendum: string | null, // optional follow-on hint (e.g. 'suggest next move')
|
|
49
|
+
* }}
|
|
50
|
+
*/
|
|
51
|
+
function resolveNextSurface(opts) {
|
|
52
|
+
const rs = (opts && opts.roomState) || {};
|
|
53
|
+
|
|
54
|
+
// Branch 1: no room exists at all -- the new navigator path.
|
|
55
|
+
if (!rs.exists) {
|
|
56
|
+
return { route: '/mos:onboard', reason: 'no_room', addendum: null };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Branch 2: room exists but is mostly empty. Two signals:
|
|
60
|
+
// (a) sectionsCount == 0 -- the navigator has not populated any section.
|
|
61
|
+
// (b) stage matches Pre-Opportunity (case-insensitive) -- the venture
|
|
62
|
+
// has not even crossed the Opportunity threshold yet.
|
|
63
|
+
// Either signal routes to /mos:status with a next-move addendum.
|
|
64
|
+
const sections = typeof rs.sectionsCount === 'number' ? rs.sectionsCount : 0;
|
|
65
|
+
const stage = typeof rs.stage === 'string' ? rs.stage : '';
|
|
66
|
+
if (sections === 0 || /Pre-Opportunity/i.test(stage)) {
|
|
67
|
+
return {
|
|
68
|
+
route: '/mos:status',
|
|
69
|
+
reason: 'mostly_empty',
|
|
70
|
+
addendum: 'suggest next move',
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Branch 3: room is populated -- the navigator wants the next move.
|
|
75
|
+
return { route: '/mos:suggest-next', reason: 'populated', addendum: null };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = { resolveNextSurface };
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Mindrian. BSL 1.1.
|
|
3
|
+
*
|
|
4
|
+
* Phase 121-00 -- frozen v1 telemetry schema.
|
|
5
|
+
*
|
|
6
|
+
* Source of truth for the unified trajectory-telemetry stream at
|
|
7
|
+
* ~/.mindrian/telemetry/v1.13/events-YYYY-WNN.jsonl
|
|
8
|
+
*
|
|
9
|
+
* Per Canon D-10 (frozen v1 schema with per-row schema_version):
|
|
10
|
+
* - EVENT_TYPES is Object.frozen and locked at 15 entries for v1.
|
|
11
|
+
* - ALLOWED_FIELDS is a frozen, per-event whitelist of scalar field names.
|
|
12
|
+
* - SCHEMA_VERSION is a literal Number (not String) so consumers can
|
|
13
|
+
* dispatch on version with a strict-equal check.
|
|
14
|
+
* - Future structural changes ship as SCHEMA_VERSION 2 events that
|
|
15
|
+
* coexist in the same JSONL stream; v1 stays additive-only.
|
|
16
|
+
*
|
|
17
|
+
* Per Canon Part 8 (Graph Boundary):
|
|
18
|
+
* - String-length caps prevent raw artifact bytes from sneaking through
|
|
19
|
+
* allowed fields. emit() applies them; this module declares them.
|
|
20
|
+
* - The 9 net-new event types (D-04..D-09) only allow scalar fields:
|
|
21
|
+
* hashes (sha256), enum strings, counts, scores, timestamps.
|
|
22
|
+
* There is no field on any event that intentionally carries free-text.
|
|
23
|
+
*
|
|
24
|
+
* Inheritance: the 6 mva.* event types mirror lib/core/mva-telemetry.cjs
|
|
25
|
+
* ALLOWED_FIELDS verbatim. Per D-08, mva-telemetry.cjs becomes a thin shim
|
|
26
|
+
* delegating to the unified writer in a downstream plan; v1 keeps both
|
|
27
|
+
* call paths byte-identical on the wire so the shim is a no-op cut-over.
|
|
28
|
+
*
|
|
29
|
+
* Pure CJS, node built-ins only. Zero runtime deps. Zero network surface.
|
|
30
|
+
*/
|
|
31
|
+
'use strict';
|
|
32
|
+
|
|
33
|
+
// ---------- Frozen v1 schema version ----------
|
|
34
|
+
|
|
35
|
+
const SCHEMA_VERSION = 1;
|
|
36
|
+
|
|
37
|
+
// ---------- Frozen invariants (15 event types) ----------
|
|
38
|
+
|
|
39
|
+
const EVENT_TYPES = Object.freeze([
|
|
40
|
+
// 6 inherited from lib/core/mva-telemetry.cjs (Phase 118):
|
|
41
|
+
'mva_pipeline_started',
|
|
42
|
+
'mva_agent_returned',
|
|
43
|
+
'mva_brief_rendered',
|
|
44
|
+
'mva_option_selected',
|
|
45
|
+
'mva_brief_deployed',
|
|
46
|
+
'mva_pipeline_failed',
|
|
47
|
+
// 9 net-new for the unified Phase 121 surface (D-04..D-09):
|
|
48
|
+
'selector_pick', // D-04: F-shape selector picks (88.2 + 125 ranker)
|
|
49
|
+
'tension_engagement', // D-05: tension hook user response (116)
|
|
50
|
+
'auto_explore_decision', // D-06: auto-explore acceptance (117)
|
|
51
|
+
'breakthrough_dismissed',// D-07: F.7 + ethics-tier dismissal (120)
|
|
52
|
+
'hooked_axis_score', // D-08: Hooked axis re-score (117 scripts)
|
|
53
|
+
'empathy_observation', // D-09: empathy audit harness observation
|
|
54
|
+
'room_receipt_written', // D-09: room-as-receipt write (119)
|
|
55
|
+
'command_invocation', // D-09: PostToolUse /mos:* sweep
|
|
56
|
+
'nav_bypass', // D-09: navigation.cjs bypass migration source
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
// ---------- Per-event ALLOWED_FIELDS (frozen arrays) ----------
|
|
60
|
+
//
|
|
61
|
+
// Every entry is a closed whitelist. emit() rejects any payload key that is
|
|
62
|
+
// not in this list. Adding a field to v1 requires:
|
|
63
|
+
// 1. amending this list (additive only)
|
|
64
|
+
// 2. a Canon Part 8 review of the new field
|
|
65
|
+
// 3. matching frontmatter update in the consuming plan's CONTEXT.md
|
|
66
|
+
// Structural changes (renaming, removing, re-typing a field) require a
|
|
67
|
+
// SCHEMA_VERSION bump to 2 + a separate plan that introduces the v2 event.
|
|
68
|
+
//
|
|
69
|
+
// Field-naming convention:
|
|
70
|
+
// *_sha256 -- 64-char hex hash, exempt from raw-hex detector
|
|
71
|
+
// *_hash -- shorter hash (8-32 chars), also hash-class
|
|
72
|
+
// *_score -- numeric, no length cap concern
|
|
73
|
+
// ttr_seconds -- numeric (time-to-response in seconds)
|
|
74
|
+
// ranker_* -- 125-ranker outputs (numeric)
|
|
75
|
+
// *_response -- enum string (kept / redid / ignored / resolve / defer / ignore)
|
|
76
|
+
// *_count -- non-negative integer
|
|
77
|
+
|
|
78
|
+
const ALLOWED_FIELDS = Object.freeze({
|
|
79
|
+
// ----- 6 inherited mva.* (must mirror lib/core/mva-telemetry.cjs verbatim) -----
|
|
80
|
+
mva_pipeline_started: Object.freeze(['sentence_sha256']),
|
|
81
|
+
mva_agent_returned: Object.freeze(['sentence_sha256', 'agent_id', 'duration_ms', 'status', 'error_short']),
|
|
82
|
+
mva_brief_rendered: Object.freeze(['sentence_sha256', 'total_duration_ms', 'agent_count_ok', 'agent_count_failed']),
|
|
83
|
+
mva_option_selected: Object.freeze(['sentence_sha256', 'option_id', 'time_to_click_ms']),
|
|
84
|
+
mva_brief_deployed: Object.freeze(['sentence_sha256', 'vercel_subdomain_hash', 'deploy_duration_ms', 'status', 'error_short']),
|
|
85
|
+
mva_pipeline_failed: Object.freeze(['sentence_sha256', 'total_duration_ms', 'error_short']),
|
|
86
|
+
|
|
87
|
+
// ----- 9 net-new (D-04..D-09) -----
|
|
88
|
+
selector_pick: Object.freeze([
|
|
89
|
+
'sub_shape', // enum: 'F.0' | 'F.1' | 'F.2' | 'F.3' | 'F.4' | 'F.5' | 'F.6' | 'F.7'
|
|
90
|
+
'mode', // enum: 'A' (Brain reachable) | 'B' (Local Only) | 'Tier 0'
|
|
91
|
+
'ranker_confidence', // number 0..1 (125-ranker confidence for picked option)
|
|
92
|
+
'recommended_rendered', // boolean (RECOMMENDED marker was shown)
|
|
93
|
+
'options_count', // integer
|
|
94
|
+
'room_slug_sha256', // 64-char sha256
|
|
95
|
+
'verb_chosen', // enum (closed verb vocab, one of the 10 canonical verbs)
|
|
96
|
+
]),
|
|
97
|
+
tension_engagement: Object.freeze([
|
|
98
|
+
'tension_type', // enum: 'contradicts' | 'converges' | 'invalidates'
|
|
99
|
+
'user_response', // enum: 'resolve' | 'defer' | 'ignore'
|
|
100
|
+
'ttr_seconds', // number (time-to-response)
|
|
101
|
+
'room_slug_sha256', // 64-char sha256
|
|
102
|
+
'context_hash', // shorter hash (16 chars) tying back to surfaced context
|
|
103
|
+
]),
|
|
104
|
+
auto_explore_decision: Object.freeze([
|
|
105
|
+
'finding_type', // enum: 'whitespace' | 'reverse_salient' | 'cross_domain_match'
|
|
106
|
+
'user_response', // enum: 'kept' | 'redid' | 'ignored'
|
|
107
|
+
'domain_match_score', // number 0..1
|
|
108
|
+
'room_slug_sha256',
|
|
109
|
+
]),
|
|
110
|
+
breakthrough_dismissed: Object.freeze([
|
|
111
|
+
'detector_type', // enum (per Phase 120 D-19 detector taxonomy)
|
|
112
|
+
'verb_chosen', // enum (F.7 verb pick or Free-Text)
|
|
113
|
+
'ethics_tier', // enum: 'HARD_FLOOR' | 'SOFT_BAND' | 'NEUTRAL' | 'GREEN'
|
|
114
|
+
'voice_audit_pass', // boolean
|
|
115
|
+
'room_slug_sha256',
|
|
116
|
+
]),
|
|
117
|
+
hooked_axis_score: Object.freeze([
|
|
118
|
+
'axis_name', // enum: 'investment' | 'reward' | 'trigger' | 'action'
|
|
119
|
+
'score_value', // number
|
|
120
|
+
'room_slug_sha256',
|
|
121
|
+
'window_iso_week', // string 'YYYY-WNN' (ISO week the score window covers)
|
|
122
|
+
]),
|
|
123
|
+
empathy_observation: Object.freeze([
|
|
124
|
+
'engaged_past_15m', // boolean
|
|
125
|
+
'handed_back_material', // boolean
|
|
126
|
+
'returned_within_48h', // boolean
|
|
127
|
+
'ttr_seconds', // number
|
|
128
|
+
'tester_id_hash', // hash (NOT the tester's real ID; per Canon Part 8)
|
|
129
|
+
]),
|
|
130
|
+
room_receipt_written: Object.freeze([
|
|
131
|
+
'room_slug_sha256',
|
|
132
|
+
'conversation_id_hash', // hash
|
|
133
|
+
'generated_at_ts', // number (epoch ms) or ISO string
|
|
134
|
+
]),
|
|
135
|
+
command_invocation: Object.freeze([
|
|
136
|
+
'command', // enum string (e.g. '/mos:whitespace', '/mos:think-hats')
|
|
137
|
+
'outcome', // enum: 'success' | 'error' | 'aborted'
|
|
138
|
+
'duration_ms', // number
|
|
139
|
+
'context_hash', // hash
|
|
140
|
+
]),
|
|
141
|
+
nav_bypass: Object.freeze([
|
|
142
|
+
'op', // enum: 'read' | 'write' | 'query'
|
|
143
|
+
'reason', // enum: 'legacy_path' | 'startup_bootstrap' | 'tooling_only'
|
|
144
|
+
'caller_hash', // hash (stack-frame-derived identifier; NOT a file path)
|
|
145
|
+
'room_slug_sha256',
|
|
146
|
+
]),
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// ---------- String-length caps ----------
|
|
150
|
+
//
|
|
151
|
+
// MAX_STRING_LEN is the default cap on any string payload value. The two
|
|
152
|
+
// other constants are special-cased: sha256 fields must be exactly 64 hex
|
|
153
|
+
// characters; error_short is a short error description with its own cap.
|
|
154
|
+
|
|
155
|
+
const MAX_STRING_LEN = 64;
|
|
156
|
+
const MAX_ERROR_SHORT_LEN = 60;
|
|
157
|
+
const SHA256_LEN = 64;
|
|
158
|
+
const MAX_CONTEXT_HASH_LEN = 16;
|
|
159
|
+
|
|
160
|
+
module.exports = {
|
|
161
|
+
EVENT_TYPES,
|
|
162
|
+
ALLOWED_FIELDS,
|
|
163
|
+
SCHEMA_VERSION,
|
|
164
|
+
MAX_STRING_LEN,
|
|
165
|
+
MAX_ERROR_SHORT_LEN,
|
|
166
|
+
SHA256_LEN,
|
|
167
|
+
MAX_CONTEXT_HASH_LEN,
|
|
168
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* Copyright (c) 2026 Mindrian. BSL 1.1.
|
|
6
|
+
*
|
|
7
|
+
* Phase 121-00 -- frozen v1 telemetry schema acceptance tests.
|
|
8
|
+
*
|
|
9
|
+
* Verifies EVENT_TYPES + ALLOWED_FIELDS contracts from
|
|
10
|
+
* lib/core/telemetry/schema.cjs. The schema is the source of truth that
|
|
11
|
+
* lib/core/telemetry/validator.cjs and lib/core/telemetry/writer.cjs
|
|
12
|
+
* consume. Per Canon Part 8 + D-10 (frozen v1 schema), this contract is
|
|
13
|
+
* locked for the v1 wire forever; future evolution lives in v2 events
|
|
14
|
+
* that coexist in the same JSONL stream (per-row schema_version).
|
|
15
|
+
*
|
|
16
|
+
* Test map (2 cases, one-to-one with the PLAN <behavior> block for Task 1):
|
|
17
|
+
* 1. EVENT_TYPES is Object.freeze'd array of exactly 15 strings
|
|
18
|
+
* (6 inherited mva.* + 9 net-new; superset of mva-telemetry.cjs).
|
|
19
|
+
* 2. ALLOWED_FIELDS keys === EVENT_TYPES set; every value is frozen array.
|
|
20
|
+
*
|
|
21
|
+
* Registered in lib/memory/run-feynman-tests.cjs.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const assert = require('node:assert/strict');
|
|
25
|
+
const path = require('node:path');
|
|
26
|
+
|
|
27
|
+
const REPO = path.resolve(__dirname, '..', '..', '..');
|
|
28
|
+
const SCHEMA_PATH = path.join(REPO, 'lib/core/telemetry/schema.cjs');
|
|
29
|
+
|
|
30
|
+
try { delete require.cache[require.resolve(SCHEMA_PATH)]; } catch (_) {}
|
|
31
|
+
const schema = require(SCHEMA_PATH);
|
|
32
|
+
|
|
33
|
+
assert.equal(typeof schema, 'object', 'schema module must export an object');
|
|
34
|
+
assert.ok(Array.isArray(schema.EVENT_TYPES), 'schema must export EVENT_TYPES array');
|
|
35
|
+
assert.equal(typeof schema.ALLOWED_FIELDS, 'object', 'schema must export ALLOWED_FIELDS object');
|
|
36
|
+
assert.equal(typeof schema.SCHEMA_VERSION, 'number', 'schema must export numeric SCHEMA_VERSION');
|
|
37
|
+
|
|
38
|
+
// ---------- Test 1: EVENT_TYPES frozen + exactly 15 entries ----------
|
|
39
|
+
|
|
40
|
+
(function test1EventTypesFrozen() {
|
|
41
|
+
const expected = [
|
|
42
|
+
'mva_pipeline_started',
|
|
43
|
+
'mva_agent_returned',
|
|
44
|
+
'mva_brief_rendered',
|
|
45
|
+
'mva_option_selected',
|
|
46
|
+
'mva_brief_deployed',
|
|
47
|
+
'mva_pipeline_failed',
|
|
48
|
+
'selector_pick',
|
|
49
|
+
'tension_engagement',
|
|
50
|
+
'auto_explore_decision',
|
|
51
|
+
'breakthrough_dismissed',
|
|
52
|
+
'hooked_axis_score',
|
|
53
|
+
'empathy_observation',
|
|
54
|
+
'room_receipt_written',
|
|
55
|
+
'command_invocation',
|
|
56
|
+
'nav_bypass',
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
assert.equal(schema.EVENT_TYPES.length, 15,
|
|
60
|
+
'EVENT_TYPES must contain exactly 15 entries (6 mva.* + 9 net-new), got ' + schema.EVENT_TYPES.length);
|
|
61
|
+
assert.equal(Object.isFrozen(schema.EVENT_TYPES), true,
|
|
62
|
+
'EVENT_TYPES must be Object.freeze\'d');
|
|
63
|
+
|
|
64
|
+
for (const name of expected) {
|
|
65
|
+
assert.ok(schema.EVENT_TYPES.includes(name),
|
|
66
|
+
'EVENT_TYPES missing required event: ' + name);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// SCHEMA_VERSION must be the literal Number 1 (Canon D-10).
|
|
70
|
+
assert.equal(schema.SCHEMA_VERSION, 1,
|
|
71
|
+
'SCHEMA_VERSION must be Number 1, got ' + schema.SCHEMA_VERSION + ' (typeof=' + typeof schema.SCHEMA_VERSION + ')');
|
|
72
|
+
|
|
73
|
+
console.log('PASS test 1: EVENT_TYPES frozen array of 15 strings + SCHEMA_VERSION === 1');
|
|
74
|
+
})();
|
|
75
|
+
|
|
76
|
+
// ---------- Test 2: ALLOWED_FIELDS shape ----------
|
|
77
|
+
|
|
78
|
+
(function test2AllowedFieldsShape() {
|
|
79
|
+
const keys = Object.keys(schema.ALLOWED_FIELDS);
|
|
80
|
+
|
|
81
|
+
// Key parity with EVENT_TYPES.
|
|
82
|
+
assert.equal(keys.length, schema.EVENT_TYPES.length,
|
|
83
|
+
'ALLOWED_FIELDS key count must equal EVENT_TYPES length');
|
|
84
|
+
for (const evt of schema.EVENT_TYPES) {
|
|
85
|
+
assert.ok(keys.includes(evt),
|
|
86
|
+
'ALLOWED_FIELDS missing key for event type: ' + evt);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Every value must be a frozen array.
|
|
90
|
+
for (const evt of schema.EVENT_TYPES) {
|
|
91
|
+
const allowed = schema.ALLOWED_FIELDS[evt];
|
|
92
|
+
assert.ok(Array.isArray(allowed),
|
|
93
|
+
'ALLOWED_FIELDS[' + evt + '] must be an array');
|
|
94
|
+
assert.equal(Object.isFrozen(allowed), true,
|
|
95
|
+
'ALLOWED_FIELDS[' + evt + '] must be frozen');
|
|
96
|
+
assert.ok(allowed.length >= 1,
|
|
97
|
+
'ALLOWED_FIELDS[' + evt + '] must have at least 1 allowed field');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Spot-check the 9 net-new event types per the plan (D-04 through D-09).
|
|
101
|
+
assert.ok(schema.ALLOWED_FIELDS.selector_pick.includes('sub_shape'));
|
|
102
|
+
assert.ok(schema.ALLOWED_FIELDS.selector_pick.includes('ranker_confidence'));
|
|
103
|
+
assert.ok(schema.ALLOWED_FIELDS.selector_pick.includes('room_slug_sha256'));
|
|
104
|
+
assert.ok(schema.ALLOWED_FIELDS.tension_engagement.includes('ttr_seconds'));
|
|
105
|
+
assert.ok(schema.ALLOWED_FIELDS.auto_explore_decision.includes('domain_match_score'));
|
|
106
|
+
assert.ok(schema.ALLOWED_FIELDS.breakthrough_dismissed.includes('ethics_tier'));
|
|
107
|
+
assert.ok(schema.ALLOWED_FIELDS.hooked_axis_score.includes('axis_name'));
|
|
108
|
+
assert.ok(schema.ALLOWED_FIELDS.empathy_observation.includes('engaged_past_15m'));
|
|
109
|
+
assert.ok(schema.ALLOWED_FIELDS.room_receipt_written.includes('conversation_id_hash'));
|
|
110
|
+
assert.ok(schema.ALLOWED_FIELDS.command_invocation.includes('command'));
|
|
111
|
+
assert.ok(schema.ALLOWED_FIELDS.nav_bypass.includes('caller_hash'));
|
|
112
|
+
|
|
113
|
+
// 6 inherited mva.* event types (must mirror lib/core/mva-telemetry.cjs ALLOWED_FIELDS).
|
|
114
|
+
assert.ok(schema.ALLOWED_FIELDS.mva_pipeline_started.includes('sentence_sha256'));
|
|
115
|
+
assert.ok(schema.ALLOWED_FIELDS.mva_agent_returned.includes('duration_ms'));
|
|
116
|
+
assert.ok(schema.ALLOWED_FIELDS.mva_brief_rendered.includes('total_duration_ms'));
|
|
117
|
+
assert.ok(schema.ALLOWED_FIELDS.mva_option_selected.includes('option_id'));
|
|
118
|
+
assert.ok(schema.ALLOWED_FIELDS.mva_brief_deployed.includes('vercel_subdomain_hash'));
|
|
119
|
+
assert.ok(schema.ALLOWED_FIELDS.mva_pipeline_failed.includes('error_short'));
|
|
120
|
+
|
|
121
|
+
console.log('PASS test 2: ALLOWED_FIELDS shape (15 frozen arrays; mva.* + 9 net-new spot-checks pass)');
|
|
122
|
+
})();
|
|
123
|
+
|
|
124
|
+
console.log('\nschema.test.cjs: 2/2 tests passed');
|