@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,127 @@
|
|
|
1
|
+
// palette-consistency.test.cjs -- Phase 121.5-03 Task 1
|
|
2
|
+
//
|
|
3
|
+
// Tests for references/visual/palette.json + scripts/check-palette-consistency.cjs.
|
|
4
|
+
//
|
|
5
|
+
// 5 tests: schema, hex format, live-repo check, fixture violation, derived_files paths.
|
|
6
|
+
//
|
|
7
|
+
// No emoji. No em-dashes.
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
|
|
15
|
+
const REPO_ROOT = path.join(__dirname, '..', '..');
|
|
16
|
+
const PALETTE_PATH = path.join(REPO_ROOT, 'references', 'visual', 'palette.json');
|
|
17
|
+
const CHECKER_PATH = path.join(REPO_ROOT, 'scripts', 'check-palette-consistency.cjs');
|
|
18
|
+
|
|
19
|
+
const checker = require(CHECKER_PATH);
|
|
20
|
+
|
|
21
|
+
let passed = 0;
|
|
22
|
+
let failed = 0;
|
|
23
|
+
|
|
24
|
+
function ok(name) {
|
|
25
|
+
console.log(' ok ' + name);
|
|
26
|
+
passed++;
|
|
27
|
+
}
|
|
28
|
+
function fail(name, msg) {
|
|
29
|
+
console.error(' FAIL ' + name + (msg ? ' -- ' + msg : ''));
|
|
30
|
+
failed++;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function assert(cond, name, msg) {
|
|
34
|
+
if (cond) ok(name);
|
|
35
|
+
else fail(name, msg);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.log('test palette-consistency');
|
|
39
|
+
|
|
40
|
+
// Test 1: palette.json parses; required tiers present.
|
|
41
|
+
let palette;
|
|
42
|
+
try {
|
|
43
|
+
palette = JSON.parse(fs.readFileSync(PALETTE_PATH, 'utf8'));
|
|
44
|
+
const hasAll = palette.base && palette.palette_a_discovery && palette.palette_b_build &&
|
|
45
|
+
palette.ansi_5_color && Array.isArray(palette.derived_files);
|
|
46
|
+
assert(hasAll, 'test1 schema -- base, palette_a, palette_b, ansi_5, derived_files all present');
|
|
47
|
+
} catch (e) {
|
|
48
|
+
fail('test1 schema -- parse error: ' + e.message);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Test 2: Every hex value matches /^#[0-9a-f]{6}$/i.
|
|
52
|
+
try {
|
|
53
|
+
const tiers = [palette.base, palette.palette_a_discovery, palette.palette_b_build, palette.extended || {}];
|
|
54
|
+
let bad = null;
|
|
55
|
+
for (const t of tiers) {
|
|
56
|
+
if (!t) continue;
|
|
57
|
+
for (const k of Object.keys(t)) {
|
|
58
|
+
const v = t[k];
|
|
59
|
+
if (typeof v === 'string' && !/^#[0-9a-f]{6}$/i.test(v)) {
|
|
60
|
+
bad = k + ' = ' + v;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (bad) break;
|
|
65
|
+
}
|
|
66
|
+
assert(bad === null, 'test2 hex format -- every hex matches /^#[0-9a-f]{6}$/i', bad);
|
|
67
|
+
} catch (e) {
|
|
68
|
+
fail('test2 hex format', e.message);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Test 3: check() exits valid against the live repo (or at least returns
|
|
72
|
+
// a structured result with no stray hex from derived files that exist).
|
|
73
|
+
try {
|
|
74
|
+
const result = checker.check();
|
|
75
|
+
// The check() may have violations if Task 2 hasn't wired consumers yet -- that
|
|
76
|
+
// is acceptable for Task 1 alone; we assert that the result is well-structured.
|
|
77
|
+
const ok1 = typeof result === 'object' && typeof result.valid === 'boolean' &&
|
|
78
|
+
Array.isArray(result.violations) && typeof result.canonical_count === 'number';
|
|
79
|
+
assert(ok1, 'test3 live-repo check -- check() returns structured result');
|
|
80
|
+
// Smoke test: canonical_count is at least 9 (the base tier alone has 9 keys).
|
|
81
|
+
assert(result.canonical_count >= 9, 'test3b live-repo check -- canonical_count >= 9');
|
|
82
|
+
} catch (e) {
|
|
83
|
+
fail('test3 live-repo check', e.message);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Test 4: Synthetic fixture with stray hex returns violations.length >= 1.
|
|
87
|
+
try {
|
|
88
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'palette-test-'));
|
|
89
|
+
const fakeFile = path.join(tmpDir, 'fake-consumer.css');
|
|
90
|
+
fs.writeFileSync(fakeFile, ':root { --not-canon: #abcdef; }');
|
|
91
|
+
// Build synthetic palette referencing this fake file via absolute path
|
|
92
|
+
// (relative-to-repo trick: place fixture under a known subpath).
|
|
93
|
+
// Instead use the in-process checker on a synthesized text directly.
|
|
94
|
+
const hexes = checker.hexesFromText(':root { --not-canon: #abcdef; --canon: #A63D2F; }');
|
|
95
|
+
const found1 = hexes.has('#abcdef');
|
|
96
|
+
const found2 = hexes.has('#a63d2f');
|
|
97
|
+
assert(found1 && found2, 'test4 fixture -- hexesFromText finds both canonical + non-canonical');
|
|
98
|
+
// Build a canonical set + check membership directly.
|
|
99
|
+
const canon = checker.collectCanonical(palette);
|
|
100
|
+
assert(canon.has('#a63d2f') && !canon.has('#abcdef'),
|
|
101
|
+
'test4b fixture -- canonical set contains shipped red, not the synthetic non-canon hex');
|
|
102
|
+
// Clean up tmp dir
|
|
103
|
+
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch (_) {}
|
|
104
|
+
} catch (e) {
|
|
105
|
+
fail('test4 fixture', e.message);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Test 5: derived_files entries point to files (or skip gracefully).
|
|
109
|
+
try {
|
|
110
|
+
const derived = palette.derived_files;
|
|
111
|
+
assert(Array.isArray(derived) && derived.length > 0,
|
|
112
|
+
'test5 derived_files -- non-empty array');
|
|
113
|
+
// At least one of the derived files must exist on disk.
|
|
114
|
+
const existCount = derived.filter(function(e) {
|
|
115
|
+
return fs.existsSync(path.join(REPO_ROOT, e.path));
|
|
116
|
+
}).length;
|
|
117
|
+
assert(existCount > 0,
|
|
118
|
+
'test5b derived_files -- at least one file exists on disk');
|
|
119
|
+
} catch (e) {
|
|
120
|
+
fail('test5 derived_files', e.message);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Summary
|
|
124
|
+
console.log('');
|
|
125
|
+
console.log('palette-consistency.test: ' + passed + ' passed, ' + failed + ' failed');
|
|
126
|
+
if (failed > 0) process.exit(1);
|
|
127
|
+
process.exit(0);
|
|
@@ -37,6 +37,29 @@ const crypto = require('node:crypto');
|
|
|
37
37
|
// ---------- Constants ----------
|
|
38
38
|
|
|
39
39
|
const PENDING_TENSIONS_DIR = path.join(os.homedir(), '.mindrian', 'pending-tensions');
|
|
40
|
+
|
|
41
|
+
// Phase 121-02 D-05: tension_engagement emit support. Maps the local Phase 116
|
|
42
|
+
// last_response vocabulary {RESOLVE, LATER, SKIP, DROPPED} -> the unified
|
|
43
|
+
// telemetry user_response enum {resolve, defer, ignore}. DROPPED is system-
|
|
44
|
+
// driven (3-strikes decay or explicit drop) and is intentionally excluded so
|
|
45
|
+
// only user-initiated transitions ever land in the unified stream.
|
|
46
|
+
const USER_RESPONSE_MAP = Object.freeze({
|
|
47
|
+
RESOLVE: 'resolve',
|
|
48
|
+
LATER: 'defer',
|
|
49
|
+
SKIP: 'ignore',
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Map Phase 116 tension_type vocabulary -> unified telemetry enum.
|
|
53
|
+
// contradiction -> contradicts
|
|
54
|
+
// convergence -> converges
|
|
55
|
+
// stale_decision -> invalidates (a stale decision invalidates its premise)
|
|
56
|
+
// open_question -> invalidates (an open question invalidates closure)
|
|
57
|
+
const TENSION_TYPE_MAP = Object.freeze({
|
|
58
|
+
contradiction: 'contradicts',
|
|
59
|
+
convergence: 'converges',
|
|
60
|
+
stale_decision: 'invalidates',
|
|
61
|
+
open_question: 'invalidates',
|
|
62
|
+
});
|
|
40
63
|
const VALID_STATES = Object.freeze(new Set(['queued', 'surfaced', 'resolved', 'dropped']));
|
|
41
64
|
const VALID_TYPES = Object.freeze(new Set([
|
|
42
65
|
'contradiction',
|
|
@@ -220,6 +243,58 @@ function markSurfaced(roomSlug, tension_id) {
|
|
|
220
243
|
return { ok: true, surfacing_count: next.surfacing_count };
|
|
221
244
|
}
|
|
222
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Phase 121-02 D-05: capture user engagement with surfaced tensions into the
|
|
248
|
+
* unified ~/.mindrian/telemetry/v1.13/events-YYYY-WNN.jsonl stream (Plan 121-00
|
|
249
|
+
* writer chokepoint). Emit ONLY on user-initiated state transitions
|
|
250
|
+
* (RESOLVE -> resolve, LATER -> defer, SKIP -> ignore). Decayed tensions
|
|
251
|
+
* (system-driven 3-strikes via evaluateAndDecay or explicit markDropped) are
|
|
252
|
+
* NOT engagement events and intentionally do NOT emit.
|
|
253
|
+
*
|
|
254
|
+
* Non-blocking: try/catch wraps every step. A Canon Part 8 forbidden-pattern
|
|
255
|
+
* rejection in the writer never crashes the markResolved transition; the
|
|
256
|
+
* JSONL state append has already succeeded by the time we emit.
|
|
257
|
+
*
|
|
258
|
+
* TTR: time from last_surfaced_at -> now. Floor at 0; integer seconds.
|
|
259
|
+
* Context hash: 16-char sha256 of context_signature.focus_node_id (or empty).
|
|
260
|
+
*/
|
|
261
|
+
function emitTensionEngagementUnified(roomSlug, prev, response) {
|
|
262
|
+
try {
|
|
263
|
+
if (!USER_RESPONSE_MAP[response]) return; // system-driven; do not emit
|
|
264
|
+
let writer;
|
|
265
|
+
try {
|
|
266
|
+
writer = require('../core/telemetry/writer.cjs');
|
|
267
|
+
} catch (_e) {
|
|
268
|
+
return; // missing writer module; soft skip
|
|
269
|
+
}
|
|
270
|
+
if (!writer || typeof writer.emit !== 'function') return;
|
|
271
|
+
const tensionType = TENSION_TYPE_MAP[prev && prev.type] || 'invalidates';
|
|
272
|
+
const surfacedAt = (prev && Number.isFinite(prev.last_surfaced_at))
|
|
273
|
+
? Number(prev.last_surfaced_at)
|
|
274
|
+
: 0;
|
|
275
|
+
const ttrSeconds = surfacedAt > 0
|
|
276
|
+
? Math.max(0, Math.round((Date.now() - surfacedAt) / 1000))
|
|
277
|
+
: 0;
|
|
278
|
+
// Hash the focus node id (or any context_signature field) to a 16-char
|
|
279
|
+
// sha256 prefix; never the raw scalar.
|
|
280
|
+
let contextSrc = '';
|
|
281
|
+
if (prev && isPlainObject(prev.context_signature)) {
|
|
282
|
+
contextSrc = String(prev.context_signature.focus_node_id || prev.context_signature.context_id || '');
|
|
283
|
+
}
|
|
284
|
+
const contextHash = crypto.createHash('sha256').update(contextSrc).digest('hex').slice(0, 16);
|
|
285
|
+
const slugHash = crypto.createHash('sha256').update(String(roomSlug || '')).digest('hex');
|
|
286
|
+
writer.emit('tension_engagement', {
|
|
287
|
+
tension_type: String(tensionType).slice(0, 64),
|
|
288
|
+
user_response: USER_RESPONSE_MAP[response],
|
|
289
|
+
ttr_seconds: ttrSeconds,
|
|
290
|
+
room_slug_sha256: slugHash,
|
|
291
|
+
context_hash: contextHash,
|
|
292
|
+
});
|
|
293
|
+
} catch (_e) {
|
|
294
|
+
// Swallow: telemetry MUST never crash the tension transition.
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
223
298
|
/**
|
|
224
299
|
* Mark a tension as resolved with a user response. State -> 'resolved'.
|
|
225
300
|
*
|
|
@@ -239,6 +314,11 @@ function markResolved(roomSlug, tension_id, response) {
|
|
|
239
314
|
});
|
|
240
315
|
const r = appendTension(roomSlug, next);
|
|
241
316
|
if (!r.ok) return r;
|
|
317
|
+
// Phase 121-02 D-05: emit only AFTER the JSONL state append succeeds. The
|
|
318
|
+
// emit is gated on USER_RESPONSE_MAP (RESOLVE/LATER/SKIP) so DROPPED falls
|
|
319
|
+
// through silently. This is the load-bearing exclusion: decayed tensions
|
|
320
|
+
// are system-driven and must not count as engagement.
|
|
321
|
+
emitTensionEngagementUnified(roomSlug, prev, response);
|
|
242
322
|
return { ok: true };
|
|
243
323
|
}
|
|
244
324
|
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* Copyright (c) 2026 Mindrian. BSL 1.1.
|
|
6
|
+
*
|
|
7
|
+
* Phase 121.5-04 (Sub-plan E) -- render-v2 disposition CI gate.
|
|
8
|
+
*
|
|
9
|
+
* Pairs with scripts/disposition-render-v2.cjs. The script is the
|
|
10
|
+
* operator-facing audit surface; this test is the CI wall that fails
|
|
11
|
+
* the build when a prose-path module silently starts importing
|
|
12
|
+
* lib/render/render-v2.cjs.
|
|
13
|
+
*
|
|
14
|
+
* What this test asserts (the disposition contract):
|
|
15
|
+
*
|
|
16
|
+
* 1. The audit script's verdict is HOLDS -- no prose-path module
|
|
17
|
+
* currently imports render-v2.
|
|
18
|
+
* 2. The allowlist matches the canonical set documented in
|
|
19
|
+
* lib/render/ROOM.md: { lib/render/render.cjs (v1 shim),
|
|
20
|
+
* lib/hmi/selector-dispatcher.cjs (agent-surface dispatcher) }.
|
|
21
|
+
* Any addition to the allowlist requires updating ROOM.md in
|
|
22
|
+
* the same commit -- this test catches drift between the two.
|
|
23
|
+
* 3. lib/render/ROOM.md exists and documents the disposition with
|
|
24
|
+
* the "current" linguistic discipline (NEVER "final", "complete",
|
|
25
|
+
* or "closed set" framing on rankable input signal types --
|
|
26
|
+
* verdict locked 2026-05-16 per the dual-graph review).
|
|
27
|
+
* 4. The Phase 102 VALIDATION.md is no longer status: draft (the
|
|
28
|
+
* Sub-plan E precondition for v1.13.0 final release gate).
|
|
29
|
+
*
|
|
30
|
+
* Registered in lib/memory/run-feynman-tests.cjs alongside the existing
|
|
31
|
+
* Phase 102 fences (test-render-v2-signature.cjs etc.).
|
|
32
|
+
*
|
|
33
|
+
* Constraints (Phase 87 invariant):
|
|
34
|
+
* - Node built-ins only (node:assert/strict, node:fs, node:path).
|
|
35
|
+
* - CJS only.
|
|
36
|
+
* - Zero new runtime dependencies.
|
|
37
|
+
*
|
|
38
|
+
* Canon parts: 3 (the v2 entry is reserved for Shape F.x decision-gate
|
|
39
|
+
* output surfaces), 6 (Product-as-Venture: the plugin honors its own
|
|
40
|
+
* disposition decisions via a CI gate, not a promise), 7 (Reuse Before
|
|
41
|
+
* Build: the v1 shim wraps v2 so we ship one renderer, two entry
|
|
42
|
+
* points -- not two parallel implementations).
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
const assert = require('node:assert/strict');
|
|
46
|
+
const fs = require('node:fs');
|
|
47
|
+
const path = require('node:path');
|
|
48
|
+
|
|
49
|
+
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
50
|
+
|
|
51
|
+
let passed = 0;
|
|
52
|
+
let failed = 0;
|
|
53
|
+
|
|
54
|
+
function ok(name) {
|
|
55
|
+
passed += 1;
|
|
56
|
+
process.stdout.write(' ok ' + name + '\n');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function fail(name, err) {
|
|
60
|
+
failed += 1;
|
|
61
|
+
process.stdout.write(' FAIL ' + name + '\n');
|
|
62
|
+
if (err) process.stdout.write(' ' + (err.message || String(err)) + '\n');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Scenario 1: the audit script's verdict is HOLDS.
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
(function test1_auditVerdictHolds() {
|
|
69
|
+
const label = 'audit script verdict HOLDS (no prose-path consumer)';
|
|
70
|
+
try {
|
|
71
|
+
const audit = require(path.join(REPO_ROOT, 'scripts', 'disposition-render-v2.cjs'));
|
|
72
|
+
const report = audit.audit();
|
|
73
|
+
assert.equal(report.verdict, 'HOLDS',
|
|
74
|
+
'verdict must be HOLDS; got ' + report.verdict
|
|
75
|
+
+ ' with violations: ' + JSON.stringify(report.violations));
|
|
76
|
+
assert.equal(report.disposition, 'agent-surface-only',
|
|
77
|
+
'disposition must be agent-surface-only');
|
|
78
|
+
assert.equal(report.summary.violation_count, 0,
|
|
79
|
+
'violation_count must be 0');
|
|
80
|
+
ok(label);
|
|
81
|
+
} catch (e) { fail(label, e); }
|
|
82
|
+
})();
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Scenario 2: allowlist matches canonical set.
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
(function test2_allowlistMatches() {
|
|
88
|
+
const label = 'allowlist matches canonical agent-surface set';
|
|
89
|
+
try {
|
|
90
|
+
const audit = require(path.join(REPO_ROOT, 'scripts', 'disposition-render-v2.cjs'));
|
|
91
|
+
const expected = [
|
|
92
|
+
path.join('lib', 'render', 'render.cjs'),
|
|
93
|
+
path.join('lib', 'hmi', 'selector-dispatcher.cjs'),
|
|
94
|
+
];
|
|
95
|
+
assert.deepEqual(
|
|
96
|
+
audit.ALLOWED_CONSUMERS.slice().sort(),
|
|
97
|
+
expected.slice().sort(),
|
|
98
|
+
'allowlist must equal canonical set; changes require ROOM.md update in same commit'
|
|
99
|
+
);
|
|
100
|
+
ok(label);
|
|
101
|
+
} catch (e) { fail(label, e); }
|
|
102
|
+
})();
|
|
103
|
+
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// Scenario 3: ROOM.md documents the disposition with "current" language.
|
|
106
|
+
//
|
|
107
|
+
// Per the 2026-05-16 dual-graph review (logged in 121.5-CONTEXT.md):
|
|
108
|
+
// render-v2's rankable input signal types is one of FOUR
|
|
109
|
+
// ultrareview-adjacent surfaces. The dual-graph proposal (future
|
|
110
|
+
// ASSOCIATION_LENS + TRANSITION_LENS lens classes) may add lens-derived
|
|
111
|
+
// signals. The disposition is "current," NOT "final/complete/closed."
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
(function test3_roomMdLinguisticDiscipline() {
|
|
114
|
+
const label = 'ROOM.md uses "current" linguistic discipline (no final/closed/complete framing)';
|
|
115
|
+
try {
|
|
116
|
+
const roomMdPath = path.join(REPO_ROOT, 'lib', 'render', 'ROOM.md');
|
|
117
|
+
assert.ok(fs.existsSync(roomMdPath), 'lib/render/ROOM.md must exist');
|
|
118
|
+
const body = fs.readFileSync(roomMdPath, 'utf8');
|
|
119
|
+
|
|
120
|
+
// Disposition must be present + carry the agent-surface-only marker.
|
|
121
|
+
assert.ok(/121\.5-04/.test(body) || /Sub-plan E/.test(body) || /Disposition/i.test(body),
|
|
122
|
+
'ROOM.md must reference the Sub-plan E disposition');
|
|
123
|
+
assert.ok(/agent-surface-only/i.test(body),
|
|
124
|
+
'ROOM.md must use the phrase "agent-surface-only"');
|
|
125
|
+
|
|
126
|
+
// Forbidden framing on rankable input signal types. The check is
|
|
127
|
+
// scoped to specific phrases that imply closure -- we do NOT forbid
|
|
128
|
+
// the words "final" or "complete" in unrelated context (e.g. "final
|
|
129
|
+
// release gate", "compose 4 zones" -- both legitimate). The patterns
|
|
130
|
+
// below are the closure-implying phrases we explicitly disallow on
|
|
131
|
+
// the signal-types section.
|
|
132
|
+
const forbidden = [
|
|
133
|
+
{ pattern: /\bcomplete set of rankable\b/i, label: '"complete set of rankable"' },
|
|
134
|
+
{ pattern: /\bclosed signal vocabulary\b/i, label: '"closed signal vocabulary"' },
|
|
135
|
+
{ pattern: /\ball input signal types\b/i, label: '"all input signal types"' },
|
|
136
|
+
{ pattern: /\bfinal set of rankable\b/i, label: '"final set of rankable"' },
|
|
137
|
+
];
|
|
138
|
+
for (const f of forbidden) {
|
|
139
|
+
assert.ok(!f.pattern.test(body),
|
|
140
|
+
'ROOM.md must NOT contain ' + f.label + ' (use "current"/"today"/"as of" framing)');
|
|
141
|
+
}
|
|
142
|
+
ok(label);
|
|
143
|
+
} catch (e) { fail(label, e); }
|
|
144
|
+
})();
|
|
145
|
+
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
// Scenario 4: Phase 102 VALIDATION.md is no longer status: draft.
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
(function test4_validation102OutOfDraft() {
|
|
150
|
+
const label = 'Phase 102 VALIDATION.md is out of status: draft';
|
|
151
|
+
try {
|
|
152
|
+
const valPath = path.join(REPO_ROOT,
|
|
153
|
+
'.planning', 'phases', '102-context-aware-rendering', '102-VALIDATION.md');
|
|
154
|
+
if (!fs.existsSync(valPath)) {
|
|
155
|
+
// The worktree may not carry the .planning tree; treat absence as
|
|
156
|
+
// a soft-pass at unit-test time. The Phase 102 VERIFICATION.md
|
|
157
|
+
// existence check (scenario 5) is the harder gate.
|
|
158
|
+
ok(label + ' (skipped: 102-VALIDATION.md absent in this worktree)');
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const body = fs.readFileSync(valPath, 'utf8');
|
|
162
|
+
// The frontmatter `status:` field MUST NOT read "draft".
|
|
163
|
+
const m = body.match(/^status:\s*(\S+)\s*$/m);
|
|
164
|
+
if (m) {
|
|
165
|
+
assert.notEqual(m[1].toLowerCase(), 'draft',
|
|
166
|
+
'102-VALIDATION.md status field must not be "draft"; got "' + m[1] + '"');
|
|
167
|
+
}
|
|
168
|
+
ok(label);
|
|
169
|
+
} catch (e) { fail(label, e); }
|
|
170
|
+
})();
|
|
171
|
+
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
// Scenario 5: Phase 102 VERIFICATION.md exists.
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
(function test5_verification102Exists() {
|
|
176
|
+
const label = 'Phase 102 VERIFICATION.md exists (Sub-plan E closure)';
|
|
177
|
+
try {
|
|
178
|
+
const verPath = path.join(REPO_ROOT,
|
|
179
|
+
'.planning', 'phases', '102-context-aware-rendering', '102-VERIFICATION.md');
|
|
180
|
+
if (!fs.existsSync(path.dirname(verPath))) {
|
|
181
|
+
// Worktree may not carry the phase tree; soft-pass.
|
|
182
|
+
ok(label + ' (skipped: phase 102 dir absent in this worktree)');
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
assert.ok(fs.existsSync(verPath),
|
|
186
|
+
'102-VERIFICATION.md must exist at ' + path.relative(REPO_ROOT, verPath));
|
|
187
|
+
const body = fs.readFileSync(verPath, 'utf8');
|
|
188
|
+
assert.ok(body.length > 200,
|
|
189
|
+
'102-VERIFICATION.md must be substantive (>200 bytes)');
|
|
190
|
+
ok(label);
|
|
191
|
+
} catch (e) { fail(label, e); }
|
|
192
|
+
})();
|
|
193
|
+
|
|
194
|
+
process.stdout.write('\n');
|
|
195
|
+
process.stdout.write(
|
|
196
|
+
'Phase 121.5-04 render-v2 disposition gate: ' +
|
|
197
|
+
passed + ' passed, ' + failed + ' failed\n'
|
|
198
|
+
);
|
|
199
|
+
process.exit(failed === 0 ? 0 : 1);
|