@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,251 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/*
|
|
3
|
+
* Phase 120-03 Wave 2 Task 1 -- voice-scaffold unit tests (Tests 1-14).
|
|
4
|
+
*
|
|
5
|
+
* Per CONTEXT.md D-17 LOCKED VERBATIM 4-rule voice scaffold:
|
|
6
|
+
* Rule 1: Evidence requirement -- artifact ids OR graph edges cited
|
|
7
|
+
* Rule 2: Mechanism clause -- "by Y" user action
|
|
8
|
+
* Rule 3: Time anchor -- "in the last N hours/days" / "this week" / etc.
|
|
9
|
+
* Rule 4: No unbacked superlatives -- 6 forbidden + 3 frequency words guarded
|
|
10
|
+
*
|
|
11
|
+
* The auditor IS the structural enforcement (D-20 meta-principle).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const test = require('node:test');
|
|
15
|
+
const { strict: assert } = require('node:assert');
|
|
16
|
+
const fs = require('node:fs');
|
|
17
|
+
const path = require('node:path');
|
|
18
|
+
|
|
19
|
+
const SCAFFOLD_PATH = path.resolve(__dirname, 'voice-scaffold.cjs');
|
|
20
|
+
const voiceScaffold = require('./voice-scaffold.cjs');
|
|
21
|
+
const {
|
|
22
|
+
composeBreakthroughVoiceLine,
|
|
23
|
+
auditVoiceLine,
|
|
24
|
+
citeEvidence,
|
|
25
|
+
includeMechanism,
|
|
26
|
+
anchorTime,
|
|
27
|
+
noUnbackedSuperlatives,
|
|
28
|
+
formatTimeAnchor,
|
|
29
|
+
VOICE_RULE_NAMES,
|
|
30
|
+
FORBIDDEN_SUPERLATIVES,
|
|
31
|
+
FREQUENCY_WORDS,
|
|
32
|
+
KIND_DISPLAY,
|
|
33
|
+
} = voiceScaffold;
|
|
34
|
+
|
|
35
|
+
// --- T1: VOICE_RULE_NAMES verbatim ---
|
|
36
|
+
test('T1: VOICE_RULE_NAMES verbatim + Object.freeze invariant', () => {
|
|
37
|
+
assert.deepEqual(VOICE_RULE_NAMES, [
|
|
38
|
+
'evidence_requirement',
|
|
39
|
+
'mechanism_clause',
|
|
40
|
+
'time_anchor',
|
|
41
|
+
'no_unbacked_superlatives',
|
|
42
|
+
]);
|
|
43
|
+
assert.equal(VOICE_RULE_NAMES.length, 4);
|
|
44
|
+
// Object.freeze invariant
|
|
45
|
+
const before = VOICE_RULE_NAMES.slice();
|
|
46
|
+
try { VOICE_RULE_NAMES.push('extra'); } catch (_e) { /* strict mode throws */ }
|
|
47
|
+
assert.deepEqual(VOICE_RULE_NAMES.slice(0, 4), before);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// --- T2: FORBIDDEN_SUPERLATIVES verbatim ---
|
|
51
|
+
test('T2: FORBIDDEN_SUPERLATIVES contains the 6 D-17 rule 4 words verbatim', () => {
|
|
52
|
+
assert.deepEqual(FORBIDDEN_SUPERLATIVES, [
|
|
53
|
+
'breakthrough', 'biggest', 'first', 'unprecedented', 'major', 'massive',
|
|
54
|
+
]);
|
|
55
|
+
assert.equal(FORBIDDEN_SUPERLATIVES.length, 6);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// --- T3: composeBreakthroughVoiceLine happy path -- convergence ---
|
|
59
|
+
test('T3: composeBreakthroughVoiceLine produces D-17-compliant line for convergence', () => {
|
|
60
|
+
const twoDaysAgoMs = Date.now() - 2 * 86400 * 1000;
|
|
61
|
+
const bk = {
|
|
62
|
+
kind: 'convergence',
|
|
63
|
+
theme: 'operator-state machines',
|
|
64
|
+
artifact_ids: ['art:1', 'art:2', 'art:3', 'art:4'],
|
|
65
|
+
detected_at: twoDaysAgoMs,
|
|
66
|
+
};
|
|
67
|
+
const roomState = {
|
|
68
|
+
mechanism_phrase: 'by uploading the four notes on Mode A and Mode B',
|
|
69
|
+
};
|
|
70
|
+
const line = composeBreakthroughVoiceLine(bk, roomState);
|
|
71
|
+
assert.match(
|
|
72
|
+
line,
|
|
73
|
+
/^You're seeing a convergence on operator-state machines \(artifacts art:1, art:2, art:3, art:4\) -- by uploading the four notes on Mode A and Mode B -- in the last two days\.$/,
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// --- T4: composeBreakthroughVoiceLine default mechanism when roomState absent ---
|
|
78
|
+
test('T4: composeBreakthroughVoiceLine uses structural default mechanism when roomState absent', () => {
|
|
79
|
+
const bk = {
|
|
80
|
+
kind: 'convergence',
|
|
81
|
+
theme: 'X',
|
|
82
|
+
artifact_ids: ['a1', 'a2', 'a3'],
|
|
83
|
+
detected_at: Date.now(),
|
|
84
|
+
};
|
|
85
|
+
const line = composeBreakthroughVoiceLine(bk, null);
|
|
86
|
+
// Default: "by adding N artifacts to this section"
|
|
87
|
+
assert.ok(/by adding 3 artifacts/.test(line),
|
|
88
|
+
'expected default mechanism with artifact count, got: ' + line);
|
|
89
|
+
// The default must still pass auditor (rule 2 satisfied)
|
|
90
|
+
const audit = auditVoiceLine(line);
|
|
91
|
+
assert.equal(audit.ok, true, 'default-mechanism line must pass auditor, violations: ' + JSON.stringify(audit.violations));
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// --- T5: auditVoiceLine -- D-17 rule 1 violation -- no evidence cite ---
|
|
95
|
+
test('T5: auditVoiceLine catches D-17 rule 1 violation (no evidence cite)', () => {
|
|
96
|
+
const bad = "You're seeing a convergence on X -- by uploading -- in the last two days.";
|
|
97
|
+
const r = auditVoiceLine(bad);
|
|
98
|
+
assert.equal(r.ok, false);
|
|
99
|
+
assert.ok(r.violations.indexOf('evidence_requirement') >= 0,
|
|
100
|
+
'expected evidence_requirement violation, got: ' + JSON.stringify(r.violations));
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// --- T6: auditVoiceLine -- D-17 rule 2 violation -- no mechanism ---
|
|
104
|
+
test('T6: auditVoiceLine catches D-17 rule 2 violation (no mechanism)', () => {
|
|
105
|
+
const bad = "You're seeing a convergence on X (artifacts a1, a2, a3) -- in the last two days.";
|
|
106
|
+
const r = auditVoiceLine(bad);
|
|
107
|
+
assert.equal(r.ok, false);
|
|
108
|
+
assert.ok(r.violations.indexOf('mechanism_clause') >= 0,
|
|
109
|
+
'expected mechanism_clause violation, got: ' + JSON.stringify(r.violations));
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// --- T7: auditVoiceLine -- D-17 rule 3 violation -- no time anchor ---
|
|
113
|
+
test('T7: auditVoiceLine catches D-17 rule 3 violation (no time anchor)', () => {
|
|
114
|
+
const bad = "You're seeing a convergence on X (artifacts a1, a2, a3) -- by uploading the notes.";
|
|
115
|
+
const r = auditVoiceLine(bad);
|
|
116
|
+
assert.equal(r.ok, false);
|
|
117
|
+
assert.ok(r.violations.indexOf('time_anchor') >= 0,
|
|
118
|
+
'expected time_anchor violation, got: ' + JSON.stringify(r.violations));
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// --- T8: auditVoiceLine -- D-17 rule 4 violation -- unbacked superlative ---
|
|
122
|
+
test('T8: auditVoiceLine catches D-17 rule 4 violation (unbacked superlative)', () => {
|
|
123
|
+
const bad = "You're seeing the BIGGEST breakthrough on X (artifacts a1, a2, a3) -- by uploading -- in the last hour.";
|
|
124
|
+
const r = auditVoiceLine(bad);
|
|
125
|
+
assert.equal(r.ok, false);
|
|
126
|
+
assert.ok(r.violations.indexOf('no_unbacked_superlatives') >= 0,
|
|
127
|
+
'expected no_unbacked_superlatives violation, got: ' + JSON.stringify(r.violations));
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// --- T9: auditVoiceLine -- D-17 rule 4 SATISFIED with numeric backing ---
|
|
131
|
+
test('T9: auditVoiceLine accepts D-17 rule 4 when superlative has numeric backing', () => {
|
|
132
|
+
const good = "You're seeing a convergence on X (artifacts a1, a2, a3) -- by uploading -- in the last hour. This is the highest differential score (0.78) in this room's history.";
|
|
133
|
+
const r = auditVoiceLine(good);
|
|
134
|
+
assert.equal(r.ok, true,
|
|
135
|
+
'expected no violations, got: ' + JSON.stringify(r.violations));
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// --- T10: auditVoiceLine happy path passes all 4 rules ---
|
|
139
|
+
test('T10: auditVoiceLine passes for T3-composed convergence line', () => {
|
|
140
|
+
const twoDaysAgoMs = Date.now() - 2 * 86400 * 1000;
|
|
141
|
+
const bk = {
|
|
142
|
+
kind: 'convergence',
|
|
143
|
+
theme: 'operator-state machines',
|
|
144
|
+
artifact_ids: ['art:1', 'art:2', 'art:3', 'art:4'],
|
|
145
|
+
detected_at: twoDaysAgoMs,
|
|
146
|
+
};
|
|
147
|
+
const roomState = {
|
|
148
|
+
mechanism_phrase: 'by uploading the four notes on Mode A and Mode B',
|
|
149
|
+
};
|
|
150
|
+
const line = composeBreakthroughVoiceLine(bk, roomState);
|
|
151
|
+
const r = auditVoiceLine(line);
|
|
152
|
+
assert.equal(r.ok, true,
|
|
153
|
+
'composer + auditor must be aligned, violations: ' + JSON.stringify(r.violations));
|
|
154
|
+
assert.deepEqual(r.violations, []);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// --- T11: auditVoiceLine -- multiple violations reported ---
|
|
158
|
+
test('T11: auditVoiceLine reports multiple violations on maximally bad line', () => {
|
|
159
|
+
const veryBad = "You're seeing a convergence on X.";
|
|
160
|
+
const r = auditVoiceLine(veryBad);
|
|
161
|
+
assert.equal(r.ok, false);
|
|
162
|
+
// expect at least 3 violations: evidence, mechanism, time_anchor (no superlative present so rule 4 OK)
|
|
163
|
+
assert.ok(r.violations.indexOf('evidence_requirement') >= 0);
|
|
164
|
+
assert.ok(r.violations.indexOf('mechanism_clause') >= 0);
|
|
165
|
+
assert.ok(r.violations.indexOf('time_anchor') >= 0);
|
|
166
|
+
assert.ok(r.violations.length >= 3);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// --- T12: composeBreakthroughVoiceLine for EVERY kind passes auditor ---
|
|
170
|
+
test('T12: composeBreakthroughVoiceLine output passes auditor for every kind', () => {
|
|
171
|
+
const kinds = [
|
|
172
|
+
'convergence',
|
|
173
|
+
'contradiction_resolved',
|
|
174
|
+
'cross_domain_analogy',
|
|
175
|
+
'reverse_salient_closed',
|
|
176
|
+
];
|
|
177
|
+
for (const kind of kinds) {
|
|
178
|
+
const bk = {
|
|
179
|
+
kind: kind,
|
|
180
|
+
theme: 'sample-theme',
|
|
181
|
+
artifact_ids: ['art:1', 'art:2', 'art:3'],
|
|
182
|
+
detected_at: Date.now() - 6 * 3600 * 1000, // 6 hours ago
|
|
183
|
+
};
|
|
184
|
+
const line = composeBreakthroughVoiceLine(bk, null);
|
|
185
|
+
const r = auditVoiceLine(line);
|
|
186
|
+
assert.equal(r.ok, true,
|
|
187
|
+
'kind ' + kind + ': composer output failed audit, line: ' + line + ', violations: ' + JSON.stringify(r.violations));
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// --- T13: Canon Part 8 source-grep -- zero Brain coupling ---
|
|
192
|
+
test('T13: Canon Part 8 source-grep -- voice-scaffold.cjs has zero Brain coupling', () => {
|
|
193
|
+
const src = fs.readFileSync(SCAFFOLD_PATH, 'utf8');
|
|
194
|
+
// brain-client require
|
|
195
|
+
assert.ok(!/require\(.+brain-client/.test(src),
|
|
196
|
+
'voice-scaffold must not require brain-client');
|
|
197
|
+
// direct fetch to brain.mindrian
|
|
198
|
+
assert.ok(!/fetch.+brain\.mindrian/.test(src),
|
|
199
|
+
'voice-scaffold must not fetch brain.mindrian');
|
|
200
|
+
// cross-room aggregation
|
|
201
|
+
assert.ok(!/cross-room/i.test(src.split('\n').filter(l => !l.match(/^\s*\/\//)).join('\n')) ||
|
|
202
|
+
src.indexOf('Canon Part 8') >= 0,
|
|
203
|
+
'cross-room mentions require Canon Part 8 context comment');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// --- T14: em-dash invariant ---
|
|
207
|
+
test('T14: zero U+2014 em-dashes in voice-scaffold.cjs', () => {
|
|
208
|
+
const src = fs.readFileSync(SCAFFOLD_PATH, 'utf8');
|
|
209
|
+
const idx = src.indexOf('—');
|
|
210
|
+
assert.equal(idx, -1, 'em-dash found at index ' + idx);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// --- Bonus: formatTimeAnchor buckets ---
|
|
214
|
+
test('T15: formatTimeAnchor returns expected buckets', () => {
|
|
215
|
+
const now = Date.now();
|
|
216
|
+
assert.equal(formatTimeAnchor(now - 30 * 60 * 1000, now), 'in the last hour');
|
|
217
|
+
assert.equal(formatTimeAnchor(now - 4 * 3600 * 1000, now), 'in the last 4 hours');
|
|
218
|
+
assert.equal(formatTimeAnchor(now - 12 * 3600 * 1000, now), 'today');
|
|
219
|
+
assert.equal(formatTimeAnchor(now - 36 * 3600 * 1000, now), 'in the last day');
|
|
220
|
+
assert.equal(formatTimeAnchor(now - 2 * 86400 * 1000, now), 'in the last two days');
|
|
221
|
+
assert.equal(formatTimeAnchor(now - 5 * 86400 * 1000, now), 'this week');
|
|
222
|
+
assert.equal(formatTimeAnchor(now - 10 * 86400 * 1000, now), 'in the last two weeks');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// --- Bonus: rule predicates exposed ---
|
|
226
|
+
test('T16: rule predicates exposed individually for unit-level testing', () => {
|
|
227
|
+
assert.equal(typeof citeEvidence, 'function');
|
|
228
|
+
assert.equal(typeof includeMechanism, 'function');
|
|
229
|
+
assert.equal(typeof anchorTime, 'function');
|
|
230
|
+
assert.equal(typeof noUnbackedSuperlatives, 'function');
|
|
231
|
+
// citeEvidence positive cases
|
|
232
|
+
assert.equal(citeEvidence('foo (artifacts a1, a2)'), true);
|
|
233
|
+
assert.equal(citeEvidence('foo [[wiki-link]]'), true);
|
|
234
|
+
assert.equal(citeEvidence('foo (see edges X, Y)'), true);
|
|
235
|
+
// citeEvidence negative
|
|
236
|
+
assert.equal(citeEvidence('foo bar baz'), false);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// --- Bonus: FREQUENCY_WORDS exposed ---
|
|
240
|
+
test('T17: FREQUENCY_WORDS exposed verbatim', () => {
|
|
241
|
+
assert.deepEqual(FREQUENCY_WORDS, ['consistent', 'repeated', 'always']);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// --- Bonus: KIND_DISPLAY exposed ---
|
|
245
|
+
test('T18: KIND_DISPLAY exposed with the 4 detector kinds', () => {
|
|
246
|
+
assert.equal(typeof KIND_DISPLAY, 'object');
|
|
247
|
+
assert.ok(KIND_DISPLAY.convergence);
|
|
248
|
+
assert.ok(KIND_DISPLAY.contradiction_resolved);
|
|
249
|
+
assert.ok(KIND_DISPLAY.cross_domain_analogy);
|
|
250
|
+
assert.ok(KIND_DISPLAY.reverse_salient_closed);
|
|
251
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2026 Mindrian. BSL 1.1.
|
|
4
|
+
*
|
|
5
|
+
* first-touch-version-stamper.cjs
|
|
6
|
+
* --------------------------------
|
|
7
|
+
* Phase 121.5-05 Sub-plan F (SEED-007 absorption).
|
|
8
|
+
*
|
|
9
|
+
* Pure function module that returns the canonical version stamp for each
|
|
10
|
+
* first-touch surface (banner / splash / onboard / sessionstart /
|
|
11
|
+
* operator-update / larry-extended). The string the user sees -- "MindrianOS
|
|
12
|
+
* v<version>" -- is derived once, here, so the per-surface emission stays in
|
|
13
|
+
* lock-step with .claude-plugin/plugin.json.
|
|
14
|
+
*
|
|
15
|
+
* Source finding (SEED-007, 2026-05-09): a v1.12.0 "Three ways to do this"
|
|
16
|
+
* greeting was rendering on a stale Windows install with NO version-of-record
|
|
17
|
+
* visible. The user could not answer "what version am I running?" by LOOKING
|
|
18
|
+
* at the terminal. This module is the canonical answer to that question.
|
|
19
|
+
*
|
|
20
|
+
* Canon Part 7 (Reuse Before Build): the version-of-record source is
|
|
21
|
+
* .claude-plugin/plugin.json -- the same one scripts/banner already reads via
|
|
22
|
+
* lib/core/platform.cjs::readPluginJsonVersion. This module wraps that source
|
|
23
|
+
* with the per-surface rendering contract; it does NOT introduce a second
|
|
24
|
+
* version-of-record surface.
|
|
25
|
+
*
|
|
26
|
+
* Canon Part 8: local-only; no fetch, no Brain, no telemetry egress.
|
|
27
|
+
*
|
|
28
|
+
* No emoji. No em-dashes. ASCII-clean source.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
'use strict';
|
|
32
|
+
|
|
33
|
+
const fs = require('fs');
|
|
34
|
+
const path = require('path');
|
|
35
|
+
|
|
36
|
+
const SURFACES_PATH = path.join(__dirname, '..', '..', 'data', 'first-touch-surfaces.json');
|
|
37
|
+
const PLUGIN_JSON_PATH = path.join(__dirname, '..', '..', '.claude-plugin', 'plugin.json');
|
|
38
|
+
|
|
39
|
+
const KNOWN_SURFACES = new Set([
|
|
40
|
+
'banner',
|
|
41
|
+
'splash',
|
|
42
|
+
'onboard',
|
|
43
|
+
'sessionstart',
|
|
44
|
+
'operator-update',
|
|
45
|
+
'larry-extended',
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
function readSurfaces() {
|
|
49
|
+
return JSON.parse(fs.readFileSync(SURFACES_PATH, 'utf8'));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function resolveCurrentVersion() {
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(fs.readFileSync(PLUGIN_JSON_PATH, 'utf8')).version;
|
|
55
|
+
} catch (_e) {
|
|
56
|
+
return 'unknown';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function stampVersion(surfaceId, version) {
|
|
61
|
+
if (!KNOWN_SURFACES.has(surfaceId)) {
|
|
62
|
+
throw new Error('unknown_surface: ' + surfaceId);
|
|
63
|
+
}
|
|
64
|
+
const v = version || resolveCurrentVersion();
|
|
65
|
+
switch (surfaceId) {
|
|
66
|
+
case 'banner':
|
|
67
|
+
return 'MindrianOS v' + v;
|
|
68
|
+
case 'splash':
|
|
69
|
+
return 'MindrianOS v' + v + ' -- conversation as the product surface';
|
|
70
|
+
case 'onboard':
|
|
71
|
+
return 'Welcome to MindrianOS v' + v + '. Let me show you around.';
|
|
72
|
+
case 'sessionstart':
|
|
73
|
+
case 'operator-update':
|
|
74
|
+
case 'larry-extended':
|
|
75
|
+
return 'MindrianOS v' + v;
|
|
76
|
+
default:
|
|
77
|
+
// unreachable: KNOWN_SURFACES gates entry
|
|
78
|
+
throw new Error('unknown_surface: ' + surfaceId);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// U+2014 EM DASH detector -- the no-em-dash hard rule scanner uses this
|
|
83
|
+
// (CLAUDE.md / MEMORY feedback_no_emdashes). Written as an escape so this
|
|
84
|
+
// source file stays ASCII-clean.
|
|
85
|
+
const EM_DASH = '\u2014';
|
|
86
|
+
|
|
87
|
+
function hasEmDash(text) {
|
|
88
|
+
return typeof text === 'string' && text.indexOf(EM_DASH) >= 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = {
|
|
92
|
+
stampVersion,
|
|
93
|
+
resolveCurrentVersion,
|
|
94
|
+
readSurfaces,
|
|
95
|
+
hasEmDash,
|
|
96
|
+
KNOWN_SURFACES,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Allow CLI invocation: `node lib/core/first-touch-version-stamper.cjs banner`
|
|
100
|
+
if (require.main === module) {
|
|
101
|
+
const surfaceArg = process.argv[2];
|
|
102
|
+
if (!surfaceArg) {
|
|
103
|
+
process.stdout.write(stampVersion('banner') + '\n');
|
|
104
|
+
process.exit(0);
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
process.stdout.write(stampVersion(surfaceArg) + '\n');
|
|
108
|
+
process.exit(0);
|
|
109
|
+
} catch (e) {
|
|
110
|
+
process.stderr.write('first-touch-version-stamper: ' + e.message + '\n');
|
|
111
|
+
process.exit(2);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/*
|
|
3
|
+
* Phase 119-02 -- Larry thinness-acknowledgment voice + thin-finding detector.
|
|
4
|
+
*
|
|
5
|
+
* Per CONTEXT.md D-05: when Phase 117's composeAutoExploreFinding returns
|
|
6
|
+
* thin output (null OR <= 1 finding), the room is still scaffolded but
|
|
7
|
+
* Larry's voice acknowledges thinness honestly via the verbatim copy below.
|
|
8
|
+
*
|
|
9
|
+
* The verbatim copy is LOCKED per .planning/phases/119-room-as-receipt-invariant/
|
|
10
|
+
* 119-02-PLAN.md objective block (lines 89-93). Do NOT paraphrase.
|
|
11
|
+
*
|
|
12
|
+
* Em-dash HARD RULE: uses `--` (two ASCII hyphens) NEVER `-` (U+2014).
|
|
13
|
+
* Smart-quote BAN: uses ASCII apostrophe `'` (0x27) only -- never `’`.
|
|
14
|
+
*
|
|
15
|
+
* Canon Part 8 invariant: pure-local module; no Brain call, no telemetry
|
|
16
|
+
* egress; the voice line is a static string with zero outbound surface.
|
|
17
|
+
* Canon Part 10 sub-claim 1: Larry IS the product -- the voice line is
|
|
18
|
+
* the conversational anchor that makes "rooms are receipts" feel honest
|
|
19
|
+
* rather than apologetic when the source material is thin (D-05).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
// The verbatim D-05 voice line. Locked per the plan. Length: 152 chars (ASCII).
|
|
23
|
+
// Composed via two string literals (concat) for readability; the runtime
|
|
24
|
+
// value is one continuous string.
|
|
25
|
+
const THINNESS_VOICE_LINE =
|
|
26
|
+
"I made a room around this -- it's mostly empty until we have more to work with. " +
|
|
27
|
+
"Want to keep going and see what fills in?";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Decide whether the room should surface the thinness acknowledgment to the user.
|
|
31
|
+
*
|
|
32
|
+
* Per .planning/phases/119-room-as-receipt-invariant/119-02-PLAN.md Task 1
|
|
33
|
+
* <behavior> Test 6: D-05 threshold is "2+ findings = substantive; <=1 finding
|
|
34
|
+
* = thin". The detector treats null / undefined / non-object / missing
|
|
35
|
+
* findings array / empty findings array / single-finding all as thin.
|
|
36
|
+
*
|
|
37
|
+
* @param {object|null} autoExploreFinding the JSON shape Phase 117 writes
|
|
38
|
+
* to .mindrian/auto-explore-<material_id>.json
|
|
39
|
+
* (shape: { material_id, findings: [...], composed_at_ms })
|
|
40
|
+
* @returns {boolean} true if Larry should render the thinness voice line
|
|
41
|
+
*/
|
|
42
|
+
function shouldAcknowledgeThinness(autoExploreFinding) {
|
|
43
|
+
if (autoExploreFinding === null || autoExploreFinding === undefined) return true;
|
|
44
|
+
if (typeof autoExploreFinding !== 'object') return true;
|
|
45
|
+
const findings = Array.isArray(autoExploreFinding.findings) ? autoExploreFinding.findings : [];
|
|
46
|
+
return findings.length <= 1;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Return the verbatim D-05 thinness voice line. Single source of truth -- callers
|
|
51
|
+
* MUST use this function (or the THINNESS_VOICE_LINE constant) rather than
|
|
52
|
+
* inlining the string. This avoids cross-file string drift.
|
|
53
|
+
*
|
|
54
|
+
* @returns {string} the locked verbatim voice line
|
|
55
|
+
*/
|
|
56
|
+
function voiceLine() {
|
|
57
|
+
return THINNESS_VOICE_LINE;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = {
|
|
61
|
+
THINNESS_VOICE_LINE: THINNESS_VOICE_LINE,
|
|
62
|
+
shouldAcknowledgeThinness: shouldAcknowledgeThinness,
|
|
63
|
+
voiceLine: voiceLine,
|
|
64
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/*
|
|
3
|
+
* Phase 119-02 Task 1 -- test suite for larry-thinness-acknowledgment.cjs.
|
|
4
|
+
*
|
|
5
|
+
* Verifies (per CONTEXT.md D-05 verbatim copy lock + the 11 behavior tests in
|
|
6
|
+
* .planning/phases/119-room-as-receipt-invariant/119-02-PLAN.md Task 1):
|
|
7
|
+
* 1. THINNESS_VOICE_LINE returns exact verbatim string.
|
|
8
|
+
* 2. voiceLine() returns the same string as THINNESS_VOICE_LINE.
|
|
9
|
+
* 3. shouldAcknowledgeThinness(null) -> true.
|
|
10
|
+
* 4. shouldAcknowledgeThinness({findings:[]}) -> true.
|
|
11
|
+
* 5. shouldAcknowledgeThinness({findings:[a,b]}) -> false (substantive).
|
|
12
|
+
* 6. shouldAcknowledgeThinness({findings:[a]}) -> true (D-05 threshold 2+).
|
|
13
|
+
* 7-10. Template files exist + valid frontmatter (verified by Task 2 + bash gate).
|
|
14
|
+
* 11. No smart quotes / em-dashes in the verbatim string (ASCII only).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const assert = require('node:assert');
|
|
18
|
+
const test = require('node:test');
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
THINNESS_VOICE_LINE,
|
|
22
|
+
shouldAcknowledgeThinness,
|
|
23
|
+
voiceLine,
|
|
24
|
+
} = require('./larry-thinness-acknowledgment.cjs');
|
|
25
|
+
|
|
26
|
+
const EXPECTED =
|
|
27
|
+
"I made a room around this -- it's mostly empty until we have more to work with. " +
|
|
28
|
+
"Want to keep going and see what fills in?";
|
|
29
|
+
|
|
30
|
+
test('Test 1: THINNESS_VOICE_LINE is the verbatim D-05 string', () => {
|
|
31
|
+
assert.strictEqual(THINNESS_VOICE_LINE, EXPECTED);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('Test 2: voiceLine() returns same string as THINNESS_VOICE_LINE (single source of truth)', () => {
|
|
35
|
+
assert.strictEqual(voiceLine(), THINNESS_VOICE_LINE);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('Test 3: shouldAcknowledgeThinness(null) returns true', () => {
|
|
39
|
+
assert.strictEqual(shouldAcknowledgeThinness(null), true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('Test 4: shouldAcknowledgeThinness(undefined) returns true', () => {
|
|
43
|
+
assert.strictEqual(shouldAcknowledgeThinness(undefined), true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('Test 5: shouldAcknowledgeThinness({findings: []}) returns true (empty findings)', () => {
|
|
47
|
+
assert.strictEqual(shouldAcknowledgeThinness({ findings: [] }), true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('Test 6: shouldAcknowledgeThinness({findings: [a, b]}) returns false (substantive, 2+ findings)', () => {
|
|
51
|
+
const finding = {
|
|
52
|
+
findings: [
|
|
53
|
+
{ source_pipeline: 'whitespace', hsi_score: 0.7 },
|
|
54
|
+
{ source_pipeline: 'cross-domain', hsi_score: 0.6 },
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
assert.strictEqual(shouldAcknowledgeThinness(finding), false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('Test 7: shouldAcknowledgeThinness({findings: [a]}) returns true (1-finding edge case, D-05 threshold)', () => {
|
|
61
|
+
const finding = {
|
|
62
|
+
findings: [{ source_pipeline: 'whitespace', hsi_score: 0.3 }],
|
|
63
|
+
};
|
|
64
|
+
assert.strictEqual(shouldAcknowledgeThinness(finding), true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('Test 8: shouldAcknowledgeThinness on non-object returns true (defensive)', () => {
|
|
68
|
+
assert.strictEqual(shouldAcknowledgeThinness('not an object'), true);
|
|
69
|
+
assert.strictEqual(shouldAcknowledgeThinness(42), true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('Test 9: shouldAcknowledgeThinness on object without findings array returns true', () => {
|
|
73
|
+
assert.strictEqual(shouldAcknowledgeThinness({}), true);
|
|
74
|
+
assert.strictEqual(shouldAcknowledgeThinness({ findings: null }), true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('Test 10: THINNESS_VOICE_LINE has no em-dash (em-dash HARD RULE)', () => {
|
|
78
|
+
// Construct via charCode so this test source itself contains no literal
|
|
79
|
+
// em-dash (which would self-trip the shell-harness em-dash invariant).
|
|
80
|
+
const EMDASH = String.fromCharCode(0x2014);
|
|
81
|
+
assert.strictEqual(THINNESS_VOICE_LINE.indexOf(EMDASH), -1, 'em-dash U+2014 present');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('Test 11: THINNESS_VOICE_LINE has no smart-quote characters (ASCII only)', () => {
|
|
85
|
+
// Right single quote U+2019, left single quote U+2018, right double U+201D, left double U+201C
|
|
86
|
+
assert.strictEqual(THINNESS_VOICE_LINE.indexOf('’'), -1, 'right single quote U+2019 present');
|
|
87
|
+
assert.strictEqual(THINNESS_VOICE_LINE.indexOf('‘'), -1, 'left single quote U+2018 present');
|
|
88
|
+
assert.strictEqual(THINNESS_VOICE_LINE.indexOf('”'), -1, 'right double quote U+201D present');
|
|
89
|
+
assert.strictEqual(THINNESS_VOICE_LINE.indexOf('“'), -1, 'left double quote U+201C present');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('Test 12: THINNESS_VOICE_LINE contains the locked phrase fragments', () => {
|
|
93
|
+
assert.ok(THINNESS_VOICE_LINE.includes('I made a room around this'), 'opener missing');
|
|
94
|
+
assert.ok(THINNESS_VOICE_LINE.includes("it's mostly empty"), 'middle (ASCII apostrophe) missing');
|
|
95
|
+
assert.ok(THINNESS_VOICE_LINE.includes('Want to keep going'), 'continuation invite missing');
|
|
96
|
+
assert.ok(THINNESS_VOICE_LINE.includes('see what fills in?'), 'closing question missing');
|
|
97
|
+
});
|