@grainulation/grainulation 1.0.1 → 1.1.0
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/README.md +1 -1
- package/bin/grainulation.js +13 -14
- package/lib/doctor.js +67 -104
- package/lib/ecosystem.js +57 -65
- package/lib/pm.js +44 -69
- package/lib/router.js +179 -215
- package/lib/server.mjs +23 -34
- package/lib/setup.js +42 -51
- package/package.json +1 -2
- package/public/index.html +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<a href="https://www.npmjs.com/package/@grainulation/grainulation"><img src="https://img.shields.io/npm/v/@grainulation/grainulation" alt="npm version"></a>
|
|
6
|
+
<a href="https://www.npmjs.com/package/@grainulation/grainulation"><img src="https://img.shields.io/npm/v/@grainulation/grainulation?label=%40grainulation%2Fgrainulation" alt="npm version"></a>
|
|
7
7
|
<a href="https://www.npmjs.com/package/@grainulation/grainulation"><img src="https://img.shields.io/npm/dm/@grainulation/grainulation" alt="npm downloads"></a>
|
|
8
8
|
<a href="https://github.com/grainulation/grainulation/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="license"></a>
|
|
9
9
|
<a href="https://nodejs.org"><img src="https://img.shields.io/node/v/@grainulation/grainulation" alt="node"></a>
|
package/bin/grainulation.js
CHANGED
|
@@ -14,38 +14,37 @@
|
|
|
14
14
|
* grainulation <tool> [args] Delegate to a grainulation tool
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
const verbose =
|
|
18
|
-
|
|
19
|
-
const jsonMode = process.argv.includes("--json");
|
|
17
|
+
const verbose = process.argv.includes('--verbose') || process.argv.includes('-v');
|
|
18
|
+
const jsonMode = process.argv.includes('--json');
|
|
20
19
|
function vlog(...a) {
|
|
21
20
|
if (!verbose) return;
|
|
22
21
|
const ts = new Date().toISOString();
|
|
23
|
-
process.stderr.write(`[${ts}] grainulation: ${a.join(
|
|
22
|
+
process.stderr.write(`[${ts}] grainulation: ${a.join(' ')}\n`);
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
const command = process.argv[2];
|
|
27
|
-
vlog(
|
|
26
|
+
vlog('startup', `command=${command || '(none)'}`, `cwd=${process.cwd()}`);
|
|
28
27
|
|
|
29
28
|
// Serve command — start the HTTP server (ESM module)
|
|
30
|
-
if (command ===
|
|
31
|
-
const path = require(
|
|
32
|
-
const { spawn } = require(
|
|
33
|
-
const serverPath = path.join(__dirname,
|
|
29
|
+
if (command === 'serve') {
|
|
30
|
+
const path = require('node:path');
|
|
31
|
+
const { spawn } = require('node:child_process');
|
|
32
|
+
const serverPath = path.join(__dirname, '..', 'lib', 'server.mjs');
|
|
34
33
|
|
|
35
34
|
// Forward remaining args to the server
|
|
36
35
|
const serverArgs = process.argv.slice(3);
|
|
37
36
|
const child = spawn(process.execPath, [serverPath, ...serverArgs], {
|
|
38
|
-
stdio:
|
|
37
|
+
stdio: 'inherit',
|
|
39
38
|
});
|
|
40
39
|
|
|
41
|
-
child.on(
|
|
42
|
-
child.on(
|
|
40
|
+
child.on('close', (code) => process.exit(code ?? 0));
|
|
41
|
+
child.on('error', (err) => {
|
|
43
42
|
console.error(`grainulation: failed to start server: ${err.message}`);
|
|
44
43
|
process.exit(1);
|
|
45
44
|
});
|
|
46
45
|
} else {
|
|
47
|
-
const { route } = require(
|
|
46
|
+
const { route } = require('../lib/router');
|
|
48
47
|
// Strip --json from args before routing (it's handled as a mode flag)
|
|
49
|
-
const args = process.argv.slice(2).filter((a) => a !==
|
|
48
|
+
const args = process.argv.slice(2).filter((a) => a !== '--json');
|
|
50
49
|
route(args, { json: jsonMode });
|
|
51
50
|
}
|
package/lib/doctor.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
const { execSync } = require(
|
|
2
|
-
const { existsSync } = require(
|
|
3
|
-
const path = require(
|
|
4
|
-
const { getInstallable } = require(
|
|
1
|
+
const { execSync } = require('node:child_process');
|
|
2
|
+
const { existsSync } = require('node:fs');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { getInstallable } = require('./ecosystem');
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Health check.
|
|
@@ -18,12 +18,12 @@ const { getInstallable } = require("./ecosystem");
|
|
|
18
18
|
function checkGlobal(packageName) {
|
|
19
19
|
try {
|
|
20
20
|
const out = execSync(`npm list -g ${packageName} --depth=0 2>/dev/null`, {
|
|
21
|
-
stdio:
|
|
22
|
-
encoding:
|
|
21
|
+
stdio: 'pipe',
|
|
22
|
+
encoding: 'utf-8',
|
|
23
23
|
timeout: 5000,
|
|
24
24
|
});
|
|
25
25
|
const match = out.match(new RegExp(`${escapeRegex(packageName)}@(\\S+)`));
|
|
26
|
-
return match ? { version: match[1], method:
|
|
26
|
+
return match ? { version: match[1], method: 'global' } : null;
|
|
27
27
|
} catch {
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
@@ -35,34 +35,26 @@ function checkGlobal(packageName) {
|
|
|
35
35
|
*/
|
|
36
36
|
function checkNpxCache(packageName) {
|
|
37
37
|
try {
|
|
38
|
-
const prefix = execSync(
|
|
39
|
-
stdio:
|
|
40
|
-
encoding:
|
|
38
|
+
const prefix = execSync('npm config get cache', {
|
|
39
|
+
stdio: 'pipe',
|
|
40
|
+
encoding: 'utf-8',
|
|
41
41
|
timeout: 5000,
|
|
42
42
|
}).trim();
|
|
43
|
-
const npxDir = path.join(prefix,
|
|
43
|
+
const npxDir = path.join(prefix, '_npx');
|
|
44
44
|
if (!existsSync(npxDir)) return null;
|
|
45
45
|
|
|
46
46
|
// npx cache has hash-named directories, each with node_modules
|
|
47
|
-
const { readdirSync } = require(
|
|
47
|
+
const { readdirSync } = require('node:fs');
|
|
48
48
|
const entries = readdirSync(npxDir, { withFileTypes: true });
|
|
49
49
|
for (const entry of entries) {
|
|
50
50
|
if (!entry.isDirectory()) continue;
|
|
51
|
-
const pkgJson = path.join(
|
|
52
|
-
npxDir,
|
|
53
|
-
entry.name,
|
|
54
|
-
"node_modules",
|
|
55
|
-
packageName,
|
|
56
|
-
"package.json",
|
|
57
|
-
);
|
|
51
|
+
const pkgJson = path.join(npxDir, entry.name, 'node_modules', packageName, 'package.json');
|
|
58
52
|
if (existsSync(pkgJson)) {
|
|
59
53
|
try {
|
|
60
|
-
const pkg = JSON.parse(
|
|
61
|
-
|
|
62
|
-
);
|
|
63
|
-
return { version: pkg.version || "installed", method: "npx cache" };
|
|
54
|
+
const pkg = JSON.parse(require('node:fs').readFileSync(pkgJson, 'utf-8'));
|
|
55
|
+
return { version: pkg.version || 'installed', method: 'npx cache' };
|
|
64
56
|
} catch {
|
|
65
|
-
return { version:
|
|
57
|
+
return { version: 'installed', method: 'npx cache' };
|
|
66
58
|
}
|
|
67
59
|
}
|
|
68
60
|
}
|
|
@@ -78,15 +70,10 @@ function checkNpxCache(packageName) {
|
|
|
78
70
|
*/
|
|
79
71
|
function checkLocal(packageName) {
|
|
80
72
|
try {
|
|
81
|
-
const pkgJson = path.join(
|
|
82
|
-
process.cwd(),
|
|
83
|
-
"node_modules",
|
|
84
|
-
packageName,
|
|
85
|
-
"package.json",
|
|
86
|
-
);
|
|
73
|
+
const pkgJson = path.join(process.cwd(), 'node_modules', packageName, 'package.json');
|
|
87
74
|
if (existsSync(pkgJson)) {
|
|
88
|
-
const pkg = JSON.parse(require(
|
|
89
|
-
return { version: pkg.version ||
|
|
75
|
+
const pkg = JSON.parse(require('node:fs').readFileSync(pkgJson, 'utf-8'));
|
|
76
|
+
return { version: pkg.version || 'installed', method: 'local' };
|
|
90
77
|
}
|
|
91
78
|
return null;
|
|
92
79
|
} catch {
|
|
@@ -102,27 +89,18 @@ function checkLocal(packageName) {
|
|
|
102
89
|
function checkSource(packageName) {
|
|
103
90
|
const candidates = [
|
|
104
91
|
// Sibling directory (monorepo or co-located checkouts)
|
|
105
|
-
path.join(process.cwd(),
|
|
106
|
-
path.join(
|
|
107
|
-
process.cwd(),
|
|
108
|
-
"..",
|
|
109
|
-
packageName.replace(/^@[^/]+\//, ""),
|
|
110
|
-
"package.json",
|
|
111
|
-
),
|
|
92
|
+
path.join(process.cwd(), '..', packageName.replace(/^@[^/]+\//, '')),
|
|
93
|
+
path.join(process.cwd(), '..', packageName.replace(/^@[^/]+\//, ''), 'package.json'),
|
|
112
94
|
// Packages dir (monorepo)
|
|
113
|
-
path.join(process.cwd(),
|
|
95
|
+
path.join(process.cwd(), 'packages', packageName.replace(/^@[^/]+\//, '')),
|
|
114
96
|
];
|
|
115
97
|
for (const candidate of candidates) {
|
|
116
|
-
const pkgJson = candidate.endsWith(
|
|
117
|
-
? candidate
|
|
118
|
-
: path.join(candidate, "package.json");
|
|
98
|
+
const pkgJson = candidate.endsWith('package.json') ? candidate : path.join(candidate, 'package.json');
|
|
119
99
|
if (existsSync(pkgJson)) {
|
|
120
100
|
try {
|
|
121
|
-
const pkg = JSON.parse(
|
|
122
|
-
require("node:fs").readFileSync(pkgJson, "utf-8"),
|
|
123
|
-
);
|
|
101
|
+
const pkg = JSON.parse(require('node:fs').readFileSync(pkgJson, 'utf-8'));
|
|
124
102
|
if (pkg.name === packageName) {
|
|
125
|
-
return { version: pkg.version ||
|
|
103
|
+
return { version: pkg.version || 'installed', method: 'source' };
|
|
126
104
|
}
|
|
127
105
|
} catch {
|
|
128
106
|
// not a match, continue
|
|
@@ -139,24 +117,21 @@ function checkSource(packageName) {
|
|
|
139
117
|
*/
|
|
140
118
|
function checkNpxNoInstall(packageName) {
|
|
141
119
|
try {
|
|
142
|
-
const out = execSync(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
timeout: 5000,
|
|
148
|
-
},
|
|
149
|
-
).trim();
|
|
120
|
+
const out = execSync(`npx --no-install ${packageName} --version 2>/dev/null`, {
|
|
121
|
+
stdio: 'pipe',
|
|
122
|
+
encoding: 'utf-8',
|
|
123
|
+
timeout: 5000,
|
|
124
|
+
}).trim();
|
|
150
125
|
// Expect a version-like string
|
|
151
126
|
const match = out.match(/v?(\d+\.\d+\.\d+\S*)/);
|
|
152
|
-
return match ? { version: match[1], method:
|
|
127
|
+
return match ? { version: match[1], method: 'npx' } : null;
|
|
153
128
|
} catch {
|
|
154
129
|
return null;
|
|
155
130
|
}
|
|
156
131
|
}
|
|
157
132
|
|
|
158
133
|
function escapeRegex(str) {
|
|
159
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g,
|
|
134
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
160
135
|
}
|
|
161
136
|
|
|
162
137
|
/**
|
|
@@ -188,21 +163,21 @@ function getNodeVersion() {
|
|
|
188
163
|
|
|
189
164
|
function getNpmVersion() {
|
|
190
165
|
try {
|
|
191
|
-
return execSync(
|
|
192
|
-
stdio:
|
|
193
|
-
encoding:
|
|
166
|
+
return execSync('npm --version', {
|
|
167
|
+
stdio: 'pipe',
|
|
168
|
+
encoding: 'utf-8',
|
|
194
169
|
timeout: 5000,
|
|
195
170
|
}).trim();
|
|
196
171
|
} catch {
|
|
197
|
-
return
|
|
172
|
+
return 'not found';
|
|
198
173
|
}
|
|
199
174
|
}
|
|
200
175
|
|
|
201
176
|
function getPnpmVersion() {
|
|
202
177
|
try {
|
|
203
|
-
return execSync(
|
|
204
|
-
stdio:
|
|
205
|
-
encoding:
|
|
178
|
+
return execSync('pnpm --version', {
|
|
179
|
+
stdio: 'pipe',
|
|
180
|
+
encoding: 'utf-8',
|
|
206
181
|
timeout: 5000,
|
|
207
182
|
}).trim();
|
|
208
183
|
} catch {
|
|
@@ -212,9 +187,9 @@ function getPnpmVersion() {
|
|
|
212
187
|
|
|
213
188
|
function getBiomeVersion() {
|
|
214
189
|
try {
|
|
215
|
-
const out = execSync(
|
|
216
|
-
stdio:
|
|
217
|
-
encoding:
|
|
190
|
+
const out = execSync('npx biome --version', {
|
|
191
|
+
stdio: 'pipe',
|
|
192
|
+
encoding: 'utf-8',
|
|
218
193
|
timeout: 5000,
|
|
219
194
|
}).trim();
|
|
220
195
|
const match = out.match(/(\d+\.\d+\.\d+\S*)/);
|
|
@@ -226,9 +201,9 @@ function getBiomeVersion() {
|
|
|
226
201
|
|
|
227
202
|
function getHooksPath() {
|
|
228
203
|
try {
|
|
229
|
-
return execSync(
|
|
230
|
-
stdio:
|
|
231
|
-
encoding:
|
|
204
|
+
return execSync('git config core.hooksPath', {
|
|
205
|
+
stdio: 'pipe',
|
|
206
|
+
encoding: 'utf-8',
|
|
232
207
|
timeout: 5000,
|
|
233
208
|
}).trim();
|
|
234
209
|
} catch {
|
|
@@ -237,7 +212,7 @@ function getHooksPath() {
|
|
|
237
212
|
}
|
|
238
213
|
|
|
239
214
|
function run(opts) {
|
|
240
|
-
const json = opts
|
|
215
|
+
const json = opts?.json;
|
|
241
216
|
const tools = getInstallable();
|
|
242
217
|
|
|
243
218
|
const pnpmVersion = getPnpmVersion();
|
|
@@ -276,76 +251,64 @@ function run(opts) {
|
|
|
276
251
|
let installed = 0;
|
|
277
252
|
let missing = 0;
|
|
278
253
|
|
|
279
|
-
console.log(
|
|
280
|
-
console.log(
|
|
281
|
-
console.log(
|
|
282
|
-
console.log(
|
|
254
|
+
console.log('');
|
|
255
|
+
console.log(' \x1b[1;33mgrainulation doctor\x1b[0m');
|
|
256
|
+
console.log(' Checking ecosystem health...');
|
|
257
|
+
console.log('');
|
|
283
258
|
|
|
284
259
|
// Environment
|
|
285
|
-
console.log(
|
|
260
|
+
console.log(' \x1b[2mEnvironment:\x1b[0m');
|
|
286
261
|
console.log(` Node ${getNodeVersion()}`);
|
|
287
262
|
console.log(` npm v${getNpmVersion()}`);
|
|
288
263
|
if (pnpmVersion) {
|
|
289
264
|
console.log(` pnpm v${pnpmVersion}`);
|
|
290
265
|
} else {
|
|
291
|
-
console.log(
|
|
266
|
+
console.log(' pnpm \x1b[2mnot found\x1b[0m');
|
|
292
267
|
}
|
|
293
|
-
console.log(
|
|
268
|
+
console.log('');
|
|
294
269
|
|
|
295
270
|
// DX tooling
|
|
296
|
-
console.log(
|
|
271
|
+
console.log(' \x1b[2mDX tooling:\x1b[0m');
|
|
297
272
|
if (biomeVersion) {
|
|
298
273
|
console.log(` \x1b[32m\u2713\x1b[0m Biome v${biomeVersion}`);
|
|
299
274
|
} else {
|
|
300
|
-
console.log(
|
|
301
|
-
" \x1b[2m\u2717 Biome not found (pnpm install to set up)\x1b[0m",
|
|
302
|
-
);
|
|
275
|
+
console.log(' \x1b[2m\u2717 Biome not found (pnpm install to set up)\x1b[0m');
|
|
303
276
|
}
|
|
304
277
|
if (hooksPath) {
|
|
305
278
|
console.log(` \x1b[32m\u2713\x1b[0m Git hooks ${hooksPath}`);
|
|
306
279
|
} else {
|
|
307
|
-
console.log(
|
|
308
|
-
" \x1b[2m\u2717 Git hooks not configured (run: git config core.hooksPath .githooks)\x1b[0m",
|
|
309
|
-
);
|
|
280
|
+
console.log(' \x1b[2m\u2717 Git hooks not configured (run: git config core.hooksPath .githooks)\x1b[0m');
|
|
310
281
|
}
|
|
311
|
-
console.log(
|
|
282
|
+
console.log('');
|
|
312
283
|
|
|
313
284
|
// Tools
|
|
314
|
-
console.log(
|
|
285
|
+
console.log(' \x1b[2mTools:\x1b[0m');
|
|
315
286
|
for (const tool of tools) {
|
|
316
287
|
const result = detect(tool.package);
|
|
317
288
|
if (result) {
|
|
318
289
|
installed++;
|
|
319
290
|
const ver = `v${result.version}`.padEnd(10);
|
|
320
|
-
console.log(
|
|
321
|
-
` \x1b[32m\u2713\x1b[0m ${tool.name.padEnd(12)} ${ver} \x1b[2m(${result.method})\x1b[0m`,
|
|
322
|
-
);
|
|
291
|
+
console.log(` \x1b[32m\u2713\x1b[0m ${tool.name.padEnd(12)} ${ver} \x1b[2m(${result.method})\x1b[0m`);
|
|
323
292
|
} else {
|
|
324
293
|
missing++;
|
|
325
|
-
console.log(
|
|
326
|
-
` \x1b[2m\u2717 ${tool.name.padEnd(12)} -- (not found)\x1b[0m`,
|
|
327
|
-
);
|
|
294
|
+
console.log(` \x1b[2m\u2717 ${tool.name.padEnd(12)} -- (not found)\x1b[0m`);
|
|
328
295
|
}
|
|
329
296
|
}
|
|
330
297
|
|
|
331
|
-
console.log(
|
|
298
|
+
console.log('');
|
|
332
299
|
|
|
333
300
|
// Summary
|
|
334
301
|
if (missing === tools.length) {
|
|
335
|
-
console.log(
|
|
336
|
-
console.log(
|
|
302
|
+
console.log(' \x1b[33mNo grainulation tools found.\x1b[0m');
|
|
303
|
+
console.log(' Start with: npx @grainulation/wheat init');
|
|
337
304
|
} else if (missing > 0) {
|
|
338
|
-
console.log(
|
|
339
|
-
|
|
340
|
-
);
|
|
341
|
-
console.log(
|
|
342
|
-
" Run \x1b[1mgrainulation setup\x1b[0m to install what you need.",
|
|
343
|
-
);
|
|
305
|
+
console.log(` \x1b[32m${installed} found\x1b[0m, \x1b[2m${missing} not found\x1b[0m`);
|
|
306
|
+
console.log(' Run \x1b[1mgrainulation setup\x1b[0m to install what you need.');
|
|
344
307
|
} else {
|
|
345
|
-
console.log(
|
|
308
|
+
console.log(' \x1b[32mAll tools found. Full ecosystem ready.\x1b[0m');
|
|
346
309
|
}
|
|
347
310
|
|
|
348
|
-
console.log(
|
|
311
|
+
console.log('');
|
|
349
312
|
}
|
|
350
313
|
|
|
351
314
|
module.exports = {
|
package/lib/ecosystem.js
CHANGED
|
@@ -7,99 +7,91 @@
|
|
|
7
7
|
|
|
8
8
|
const TOOLS = [
|
|
9
9
|
{
|
|
10
|
-
name:
|
|
11
|
-
package:
|
|
12
|
-
icon:
|
|
13
|
-
role:
|
|
14
|
-
description:
|
|
15
|
-
|
|
16
|
-
category: "core",
|
|
10
|
+
name: 'wheat',
|
|
11
|
+
package: '@grainulation/wheat',
|
|
12
|
+
icon: 'W',
|
|
13
|
+
role: 'Grows evidence',
|
|
14
|
+
description: 'Research sprint engine. Ask a question, grow claims, compile a brief.',
|
|
15
|
+
category: 'core',
|
|
17
16
|
port: 9091,
|
|
18
|
-
serveCmd: [
|
|
17
|
+
serveCmd: ['serve'],
|
|
19
18
|
entryPoint: true,
|
|
20
19
|
},
|
|
21
20
|
{
|
|
22
|
-
name:
|
|
23
|
-
package:
|
|
24
|
-
icon:
|
|
25
|
-
role:
|
|
26
|
-
description:
|
|
27
|
-
|
|
28
|
-
category: "core",
|
|
21
|
+
name: 'farmer',
|
|
22
|
+
package: '@grainulation/farmer',
|
|
23
|
+
icon: 'F',
|
|
24
|
+
role: 'Permission dashboard',
|
|
25
|
+
description: 'Permission dashboard. Approve tool calls, review AI actions in real time.',
|
|
26
|
+
category: 'core',
|
|
29
27
|
port: 9090,
|
|
30
|
-
serveCmd: [
|
|
28
|
+
serveCmd: ['start'],
|
|
31
29
|
entryPoint: false,
|
|
32
30
|
},
|
|
33
31
|
{
|
|
34
|
-
name:
|
|
35
|
-
package:
|
|
36
|
-
icon:
|
|
37
|
-
role:
|
|
38
|
-
description:
|
|
39
|
-
|
|
40
|
-
category: "foundation",
|
|
32
|
+
name: 'barn',
|
|
33
|
+
package: '@grainulation/barn',
|
|
34
|
+
icon: 'B',
|
|
35
|
+
role: 'Shared tools',
|
|
36
|
+
description: 'Public utilities. Claim schemas, HTML templates, shared validators.',
|
|
37
|
+
category: 'foundation',
|
|
41
38
|
port: 9093,
|
|
42
|
-
serveCmd: [
|
|
39
|
+
serveCmd: ['serve'],
|
|
43
40
|
entryPoint: false,
|
|
44
41
|
},
|
|
45
42
|
{
|
|
46
|
-
name:
|
|
47
|
-
package:
|
|
48
|
-
icon:
|
|
49
|
-
role:
|
|
50
|
-
description:
|
|
51
|
-
|
|
52
|
-
category: "output",
|
|
43
|
+
name: 'mill',
|
|
44
|
+
package: '@grainulation/mill',
|
|
45
|
+
icon: 'M',
|
|
46
|
+
role: 'Processes output',
|
|
47
|
+
description: 'Export and publish. Turn compiled research into PDFs, slides, wikis.',
|
|
48
|
+
category: 'output',
|
|
53
49
|
port: 9094,
|
|
54
|
-
serveCmd: [
|
|
50
|
+
serveCmd: ['serve'],
|
|
55
51
|
entryPoint: false,
|
|
56
52
|
},
|
|
57
53
|
{
|
|
58
|
-
name:
|
|
59
|
-
package:
|
|
60
|
-
icon:
|
|
61
|
-
role:
|
|
62
|
-
description:
|
|
63
|
-
|
|
64
|
-
category: "storage",
|
|
54
|
+
name: 'silo',
|
|
55
|
+
package: '@grainulation/silo',
|
|
56
|
+
icon: 'S',
|
|
57
|
+
role: 'Stores knowledge',
|
|
58
|
+
description: 'Reusable claim libraries. Share vetted claims across sprints and teams.',
|
|
59
|
+
category: 'storage',
|
|
65
60
|
port: 9095,
|
|
66
|
-
serveCmd: [
|
|
61
|
+
serveCmd: ['serve'],
|
|
67
62
|
entryPoint: false,
|
|
68
63
|
},
|
|
69
64
|
{
|
|
70
|
-
name:
|
|
71
|
-
package:
|
|
72
|
-
icon:
|
|
73
|
-
role:
|
|
74
|
-
description:
|
|
75
|
-
|
|
76
|
-
category: "analytics",
|
|
65
|
+
name: 'harvest',
|
|
66
|
+
package: '@grainulation/harvest',
|
|
67
|
+
icon: 'H',
|
|
68
|
+
role: 'Analytics & retrospectives',
|
|
69
|
+
description: 'Cross-sprint learning. Track prediction accuracy, find blind spots over time.',
|
|
70
|
+
category: 'analytics',
|
|
77
71
|
port: 9096,
|
|
78
|
-
serveCmd: [
|
|
72
|
+
serveCmd: ['serve'],
|
|
79
73
|
entryPoint: false,
|
|
80
74
|
},
|
|
81
75
|
{
|
|
82
|
-
name:
|
|
83
|
-
package:
|
|
84
|
-
icon:
|
|
85
|
-
role:
|
|
86
|
-
description:
|
|
87
|
-
|
|
88
|
-
category: "orchestration",
|
|
76
|
+
name: 'orchard',
|
|
77
|
+
package: '@grainulation/orchard',
|
|
78
|
+
icon: 'O',
|
|
79
|
+
role: 'Orchestration',
|
|
80
|
+
description: 'Multi-sprint coordination. Run parallel research tracks, merge results.',
|
|
81
|
+
category: 'orchestration',
|
|
89
82
|
port: 9097,
|
|
90
|
-
serveCmd: [
|
|
83
|
+
serveCmd: ['serve'],
|
|
91
84
|
entryPoint: false,
|
|
92
85
|
},
|
|
93
86
|
{
|
|
94
|
-
name:
|
|
95
|
-
package:
|
|
96
|
-
icon:
|
|
97
|
-
role:
|
|
98
|
-
description:
|
|
99
|
-
|
|
100
|
-
category: "meta",
|
|
87
|
+
name: 'grainulation',
|
|
88
|
+
package: '@grainulation/grainulation',
|
|
89
|
+
icon: 'G',
|
|
90
|
+
role: 'The machine',
|
|
91
|
+
description: 'Process manager and ecosystem hub. Start, stop, and monitor all tools.',
|
|
92
|
+
category: 'meta',
|
|
101
93
|
port: 9098,
|
|
102
|
-
serveCmd: [
|
|
94
|
+
serveCmd: ['serve'],
|
|
103
95
|
entryPoint: false,
|
|
104
96
|
},
|
|
105
97
|
];
|
|
@@ -113,7 +105,7 @@ function getByName(name) {
|
|
|
113
105
|
}
|
|
114
106
|
|
|
115
107
|
function getInstallable() {
|
|
116
|
-
return TOOLS.filter((t) => t.name !==
|
|
108
|
+
return TOOLS.filter((t) => t.name !== 'grainulation');
|
|
117
109
|
}
|
|
118
110
|
|
|
119
111
|
function getCategories() {
|