@grainulation/grainulation 1.0.1 → 1.1.1

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