@mindrian_os/install 1.13.0-beta.11 → 1.13.0-beta.13

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.
Files changed (33) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +68 -3
  3. package/bin/cli.js +114 -57
  4. package/commands/act.md +16 -2
  5. package/commands/auto-explore.md +1 -0
  6. package/commands/doctor.md +1 -1
  7. package/commands/operator.md +1 -1
  8. package/commands/pipeline.md +16 -1
  9. package/commands/setup.md +7 -3
  10. package/commands/suggest-next.md +17 -3
  11. package/lib/core/active-plugin-root.cjs +207 -0
  12. package/lib/core/brain-client.cjs +451 -36
  13. package/lib/core/cache-prune.cjs +208 -0
  14. package/lib/core/framework-chain-composer.cjs +156 -43
  15. package/lib/core/migrations/phase-109-nodes-provenance.cjs +47 -0
  16. package/lib/core/navigation/memory-events.cjs +17 -1
  17. package/lib/core/navigation/neighborhood.cjs +5 -4
  18. package/lib/core/navigation/packet.cjs +87 -1
  19. package/lib/core/navigation.cjs +6 -0
  20. package/lib/core/resolve-brain-key.cjs +201 -0
  21. package/lib/hmi/jtbd-taxonomy.json +2 -1
  22. package/lib/memory/framework-chain-composer.test.cjs +54 -20
  23. package/lib/memory/navigation-hook-resolver.test.cjs +177 -0
  24. package/lib/memory/run-feynman-tests.cjs +102 -0
  25. package/lib/memory/security-trifecta.test.cjs +23 -6
  26. package/lib/memory/suggest-next-workflow.test.cjs +176 -0
  27. package/lib/memory/workflow-layer-e2e.test.cjs +262 -0
  28. package/lib/workflow/ROOM.md +1 -1
  29. package/package.json +4 -1
  30. package/references/brain/command-triggers-schema.md +10 -221
  31. package/references/methodology/index.md +11 -74
  32. package/skills/brain-connector/SKILL.md +12 -8
  33. package/skills/pws-methodology/SKILL.md +7 -5
@@ -0,0 +1,207 @@
1
+ 'use strict';
2
+ /*
3
+ * lib/core/active-plugin-root.cjs -- the ONE resolver for "where is the active
4
+ * MindrianOS plugin install on this machine?"
5
+ *
6
+ * Background: before this module, three different places guessed independently
7
+ * (bin/cli.js hardcoded a legacy path; scripts/statusline-mos did `ls cache |
8
+ * sort -V` with a pure-semver regex that rejected `-beta.N`; context-monitor
9
+ * inherited whatever those picked). Three guessers, three failure modes -- a box
10
+ * with 1.12.0 + 1.13.0-beta.9 in the cache ended up rendering the statusline,
11
+ * the version banner, and MINDRIAN_OS_ROOT from the wrong (older) version. This
12
+ * collapses them into one source of truth.
13
+ *
14
+ * Precedence (first hit wins):
15
+ * 1. MINDRIAN_OS_ROOT env var -- tests, dev boxes, hand clones.
16
+ * 2. ~/.claude/plugins/installed_plugins.json
17
+ * -- Claude Code's own registry of the
18
+ * ACTIVE install of mos@mindrian-marketplace.
19
+ * Temporal truth: it knows which one is
20
+ * current even when "highest semver" is not
21
+ * the active one (e.g. a pinned older version).
22
+ * 3. ~/.claude/plugins/cache/<marketplace>/mos/<version>/
23
+ * -- newest valid version dir; pre-release
24
+ * tolerant (`sort -V` ordering). Fallback
25
+ * for when installed_plugins.json is missing
26
+ * or unparseable.
27
+ * 4. ~/.claude/plugins/mindrian-os/ -- legacy hand-clone layout.
28
+ * 5. null -- not installed.
29
+ *
30
+ * Use as a module:
31
+ * const { resolveActivePluginRoot } = require('<...>/lib/core/active-plugin-root.cjs');
32
+ * const { root, source } = resolveActivePluginRoot(); // root may be null
33
+ *
34
+ * Use as a CLI (so a bash wrapper -- e.g. scripts/statusline-mos -- can shell out):
35
+ * node active-plugin-root.cjs -> prints the resolved path, or nothing; exit 0 if found, 1 if not
36
+ * node active-plugin-root.cjs --json -> prints {"root": "<path|null>", "source": "<...>"}
37
+ *
38
+ * Canon Part 8: this reads LOCAL files only (~/.claude/plugins/). Zero network.
39
+ */
40
+
41
+ const fs = require('node:fs');
42
+ const path = require('node:path');
43
+ const os = require('node:os');
44
+
45
+ const PLUGIN_KEYS = ['mos', 'mindrian-os']; // accept either marketplace plugin name
46
+
47
+ // A valid plugin install dir has a .claude-plugin/plugin.json.
48
+ function isPluginDir(dir) {
49
+ try {
50
+ return !!dir && fs.statSync(dir).isDirectory() && fs.existsSync(path.join(dir, '.claude-plugin', 'plugin.json'));
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+
56
+ function fromInstalledPlugins(home) {
57
+ // Shape (Claude Code 2.x):
58
+ // { "version": N, "plugins": { "mos@mindrian-marketplace": [ { "installPath": "...", ... } ], ... } }
59
+ // Defensive: the key might be just "mos"; the value might be an object not a
60
+ // 1-element array; the path field might be installPath / path / dir.
61
+ const file = path.join(home, '.claude', 'plugins', 'installed_plugins.json');
62
+ let data;
63
+ try {
64
+ data = JSON.parse(fs.readFileSync(file, 'utf8'));
65
+ } catch {
66
+ return null;
67
+ }
68
+ const plugins = (data && (data.plugins || data)) || {};
69
+ for (const key of Object.keys(plugins)) {
70
+ const name = String(key).split('@')[0];
71
+ if (!PLUGIN_KEYS.includes(name)) continue;
72
+ let entry = plugins[key];
73
+ if (Array.isArray(entry)) entry = entry[0];
74
+ if (!entry || typeof entry !== 'object') continue;
75
+ const p = entry.installPath || entry.path || entry.dir;
76
+ if (p && isPluginDir(p)) return p;
77
+ }
78
+ return null;
79
+ }
80
+
81
+ function fromMarketplaceCache(home) {
82
+ const cacheBase = path.join(home, '.claude', 'plugins', 'cache');
83
+ let marketplaces;
84
+ try {
85
+ marketplaces = fs.readdirSync(cacheBase, { withFileTypes: true });
86
+ } catch {
87
+ return null;
88
+ }
89
+ const found = [];
90
+ for (const mk of marketplaces) {
91
+ if (!mk.isDirectory()) continue;
92
+ for (const pluginName of PLUGIN_KEYS) {
93
+ const pluginDir = path.join(cacheBase, mk.name, pluginName);
94
+ let versions;
95
+ try {
96
+ versions = fs.readdirSync(pluginDir, { withFileTypes: true });
97
+ } catch {
98
+ continue;
99
+ }
100
+ for (const v of versions) {
101
+ if (!v.isDirectory()) continue;
102
+ const candidate = path.join(pluginDir, v.name);
103
+ if (isPluginDir(candidate)) found.push({ dir: candidate, ver: v.name });
104
+ }
105
+ }
106
+ }
107
+ if (!found.length) return null;
108
+ // `localeCompare` numeric ordering matches `sort -V` for the cases we care
109
+ // about: 1.12.0 < 1.12.5.1 < 1.13.0-beta.9 < 1.13.0-beta.11 < 1.13.0.
110
+ found.sort((a, b) => String(a.ver).localeCompare(String(b.ver), undefined, { numeric: true }));
111
+ return found[found.length - 1].dir;
112
+ }
113
+
114
+ function fromLegacyClone(home) {
115
+ const legacy = path.join(home, '.claude', 'plugins', 'mindrian-os');
116
+ return isPluginDir(legacy) ? legacy : null;
117
+ }
118
+
119
+ // Phase 123 Plan-02 (HARNESS-123-05): classify the resolved plugin root into one
120
+ // of the 4 known topologies. RESEARCH Pitfall 4: a legacy-clone dir can carry an
121
+ // unrelated origin remote (the dogfood box's ~/.claude/plugins/mindrian-os/ has
122
+ // its origin pointed at mindrian-agno-backend.git) so a naive "has origin ->
123
+ // dev-clone" heuristic mis-classifies it. The order below is precedence-locked:
124
+ // env override and explicit legacy path win before the structural git probe.
125
+ //
126
+ // Returns one of: 'marketplace-cache' | 'dev-clone' | 'legacy' | 'not-found'.
127
+ function classifyTopology(root, source) {
128
+ if (!root) return 'not-found';
129
+ // env override -> always treated as a dev clone (tests, dev boxes, hand clones).
130
+ if (source === 'MINDRIAN_OS_ROOT') return 'dev-clone';
131
+
132
+ const home = os.homedir();
133
+ // Explicit legacy path wins before the structural check -- some legacy clones
134
+ // carry an unrelated origin remote (Pitfall 4).
135
+ try {
136
+ if (path.resolve(root) === path.resolve(home, '.claude', 'plugins', 'mindrian-os')) {
137
+ return 'legacy';
138
+ }
139
+ } catch { /* ignore */ }
140
+
141
+ // Marketplace cache: under ~/.claude/plugins/cache/<mp>/{mos|mindrian-os}/<version>/.
142
+ try {
143
+ const cacheBase = path.resolve(home, '.claude', 'plugins', 'cache');
144
+ const resolved = path.resolve(root);
145
+ if (resolved.startsWith(cacheBase + path.sep)) {
146
+ return 'marketplace-cache';
147
+ }
148
+ } catch { /* ignore */ }
149
+
150
+ // Dev clone structural probe: a .git + install.sh + an origin remote URL that
151
+ // mentions the plugin repo name. execSync is wrapped in try/catch so a missing
152
+ // git binary or a non-git dir falls through cleanly.
153
+ try {
154
+ if (fs.existsSync(path.join(root, '.git')) && fs.existsSync(path.join(root, 'install.sh'))) {
155
+ const cp = require('node:child_process');
156
+ let originUrl = '';
157
+ try {
158
+ originUrl = cp.execSync('git -C ' + JSON.stringify(root) + ' remote get-url origin', {
159
+ stdio: ['ignore', 'pipe', 'ignore'],
160
+ encoding: 'utf8',
161
+ timeout: 1500,
162
+ }).trim();
163
+ } catch { /* no origin remote, swallow */ }
164
+ if (/mindrian-os-plugin/i.test(originUrl)) return 'dev-clone';
165
+ }
166
+ } catch { /* ignore */ }
167
+
168
+ // Defensive defaults: respect the resolver's source classification.
169
+ if (source === 'legacy-clone') return 'legacy';
170
+ // A resolved plugin dir that is not under a recognized cache path and not the
171
+ // legacy fixed path and does not pass the dev-clone probe -- treat as
172
+ // marketplace-cache (the bin/cli.js + installed_plugins.json path).
173
+ return 'marketplace-cache';
174
+ }
175
+
176
+ function resolveActivePluginRoot() {
177
+ const envRoot = process.env.MINDRIAN_OS_ROOT;
178
+ if (envRoot) {
179
+ return { root: envRoot, source: 'MINDRIAN_OS_ROOT', topology: classifyTopology(envRoot, 'MINDRIAN_OS_ROOT') };
180
+ }
181
+
182
+ const home = os.homedir();
183
+ let r;
184
+ if ((r = fromInstalledPlugins(home))) {
185
+ return { root: r, source: 'installed_plugins.json', topology: classifyTopology(r, 'installed_plugins.json') };
186
+ }
187
+ if ((r = fromMarketplaceCache(home))) {
188
+ return { root: r, source: 'marketplace-cache', topology: classifyTopology(r, 'marketplace-cache') };
189
+ }
190
+ if ((r = fromLegacyClone(home))) {
191
+ return { root: r, source: 'legacy-clone', topology: classifyTopology(r, 'legacy-clone') };
192
+ }
193
+ return { root: null, source: 'not-found', topology: 'not-found' };
194
+ }
195
+
196
+ module.exports = { resolveActivePluginRoot, classifyTopology };
197
+
198
+ // CLI entry point.
199
+ if (require.main === module) {
200
+ const out = resolveActivePluginRoot();
201
+ if (process.argv.includes('--json')) {
202
+ process.stdout.write(JSON.stringify(out) + '\n');
203
+ } else if (out.root) {
204
+ process.stdout.write(out.root + '\n');
205
+ }
206
+ process.exit(out.root ? 0 : 1);
207
+ }