@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 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 { 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: "pipe",
22
- encoding: "utf-8",
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: "global" } : null;
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("npm config get cache", {
39
- stdio: "pipe",
40
- encoding: "utf-8",
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, "_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");
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
- require("node:fs").readFileSync(pkgJson, "utf-8"),
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: "installed", method: "npx cache" };
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("node:fs").readFileSync(pkgJson, "utf-8"));
89
- return { version: pkg.version || "installed", method: "local" };
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(), "..", packageName.replace(/^@[^/]+\//, "")),
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(), "packages", packageName.replace(/^@[^/]+\//, "")),
95
+ path.join(process.cwd(), 'packages', packageName.replace(/^@[^/]+\//, '')),
114
96
  ];
115
97
  for (const candidate of candidates) {
116
- const pkgJson = candidate.endsWith("package.json")
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 || "installed", method: "source" };
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
- `npx --no-install ${packageName} --version 2>/dev/null`,
144
- {
145
- stdio: "pipe",
146
- encoding: "utf-8",
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: "npx" } : null;
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("npm --version", {
192
- stdio: "pipe",
193
- encoding: "utf-8",
166
+ return execSync('npm --version', {
167
+ stdio: 'pipe',
168
+ encoding: 'utf-8',
194
169
  timeout: 5000,
195
170
  }).trim();
196
171
  } catch {
197
- return "not found";
172
+ return 'not found';
198
173
  }
199
174
  }
200
175
 
201
176
  function getPnpmVersion() {
202
177
  try {
203
- return execSync("pnpm --version", {
204
- stdio: "pipe",
205
- encoding: "utf-8",
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("npx biome --version", {
216
- stdio: "pipe",
217
- encoding: "utf-8",
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("git config core.hooksPath", {
230
- stdio: "pipe",
231
- encoding: "utf-8",
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 && opts.json;
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(" \x1b[1;33mgrainulation doctor\x1b[0m");
281
- console.log(" Checking ecosystem health...");
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(" \x1b[2mEnvironment:\x1b[0m");
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(" pnpm \x1b[2mnot found\x1b[0m");
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(" \x1b[2mDX tooling:\x1b[0m");
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(" \x1b[2mTools:\x1b[0m");
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(" \x1b[33mNo grainulation tools found.\x1b[0m");
336
- console.log(" Start with: npx @grainulation/wheat init");
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
- ` \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
- );
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(" \x1b[32mAll tools found. Full ecosystem ready.\x1b[0m");
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: "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() {