@grainulation/grainulation 1.0.0 → 1.0.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/lib/server.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+
2
3
  /**
3
4
  * grainulation serve — local HTTP server for the ecosystem control center
4
5
  *
@@ -10,12 +11,21 @@
10
11
  * grainulation serve [--port 9098]
11
12
  */
12
13
 
13
- import { createServer } from 'node:http';
14
- import { readFileSync, existsSync, readdirSync, statSync, mkdirSync, writeFileSync, renameSync, watchFile } from 'node:fs';
15
- import { join, resolve, extname, dirname } from 'node:path';
16
- import { fileURLToPath } from 'node:url';
17
14
  import { execSync } from 'node:child_process';
15
+ import {
16
+ existsSync,
17
+ mkdirSync,
18
+ readdirSync,
19
+ readFileSync,
20
+ renameSync,
21
+ statSync,
22
+ watchFile,
23
+ writeFileSync,
24
+ } from 'node:fs';
25
+ import { createServer } from 'node:http';
18
26
  import { createRequire } from 'node:module';
27
+ import { dirname, extname, join, resolve } from 'node:path';
28
+ import { fileURLToPath } from 'node:url';
19
29
 
20
30
  const __dirname = dirname(fileURLToPath(import.meta.url));
21
31
  const require = createRequire(import.meta.url);
@@ -72,19 +82,75 @@ const ROUTES = [
72
82
  // ── Tool registry ─────────────────────────────────────────────────────────────
73
83
 
74
84
  const TOOLS = [
75
- { name: 'wheat', pkg: '@grainulation/wheat', port: 9091, accent: '#fbbf24', role: 'Research sprint engine', category: 'core' },
76
- { name: 'farmer', pkg: '@grainulation/farmer', port: 9090, accent: '#3b82f6', role: 'Permission dashboard', category: 'core' },
77
- { name: 'barn', pkg: '@grainulation/barn', port: 9093, accent: '#f43f5e', role: 'Design system & templates', category: 'foundation' },
78
- { name: 'mill', pkg: '@grainulation/mill', port: 9094, accent: '#a78bfa', role: 'Export & publish engine', category: 'output' },
79
- { name: 'silo', pkg: '@grainulation/silo', port: 9095, accent: '#6ee7b7', role: 'Reusable claim libraries', category: 'storage' },
80
- { name: 'harvest', pkg: '@grainulation/harvest', port: 9096, accent: '#fb923c', role: 'Analytics & retrospectives', category: 'analytics' },
81
- { name: 'orchard', pkg: '@grainulation/orchard', port: 9097, accent: '#14b8a6', role: 'Multi-sprint orchestrator', category: 'orchestration' },
82
- { name: 'grainulation', pkg: '@grainulation/grainulation', port: 9098, accent: '#9ca3af', role: 'Ecosystem entry point', category: 'meta' },
85
+ {
86
+ name: 'wheat',
87
+ pkg: '@grainulation/wheat',
88
+ port: 9091,
89
+ accent: '#fbbf24',
90
+ role: 'Research sprint engine',
91
+ category: 'core',
92
+ },
93
+ {
94
+ name: 'farmer',
95
+ pkg: '@grainulation/farmer',
96
+ port: 9090,
97
+ accent: '#3b82f6',
98
+ role: 'Permission dashboard',
99
+ category: 'core',
100
+ },
101
+ {
102
+ name: 'barn',
103
+ pkg: '@grainulation/barn',
104
+ port: 9093,
105
+ accent: '#f43f5e',
106
+ role: 'Design system & templates',
107
+ category: 'foundation',
108
+ },
109
+ {
110
+ name: 'mill',
111
+ pkg: '@grainulation/mill',
112
+ port: 9094,
113
+ accent: '#a78bfa',
114
+ role: 'Export & publish engine',
115
+ category: 'output',
116
+ },
117
+ {
118
+ name: 'silo',
119
+ pkg: '@grainulation/silo',
120
+ port: 9095,
121
+ accent: '#6ee7b7',
122
+ role: 'Reusable claim libraries',
123
+ category: 'storage',
124
+ },
125
+ {
126
+ name: 'harvest',
127
+ pkg: '@grainulation/harvest',
128
+ port: 9096,
129
+ accent: '#fb923c',
130
+ role: 'Analytics & retrospectives',
131
+ category: 'analytics',
132
+ },
133
+ {
134
+ name: 'orchard',
135
+ pkg: '@grainulation/orchard',
136
+ port: 9097,
137
+ accent: '#14b8a6',
138
+ role: 'Multi-sprint orchestrator',
139
+ category: 'orchestration',
140
+ },
141
+ {
142
+ name: 'grainulation',
143
+ pkg: '@grainulation/grainulation',
144
+ port: 9098,
145
+ accent: '#9ca3af',
146
+ role: 'Ecosystem entry point',
147
+ category: 'meta',
148
+ },
83
149
  ];
84
150
 
85
151
  // ── State ─────────────────────────────────────────────────────────────────────
86
152
 
87
- let state = {
153
+ const state = {
88
154
  ecosystem: [],
89
155
  doctorResults: null,
90
156
  lastCheck: null,
@@ -95,7 +161,11 @@ const sseClients = new Set();
95
161
  function broadcast(event) {
96
162
  const data = `data: ${JSON.stringify(event)}\n\n`;
97
163
  for (const res of sseClients) {
98
- try { res.write(data); } catch { sseClients.delete(res); }
164
+ try {
165
+ res.write(data);
166
+ } catch {
167
+ sseClients.delete(res);
168
+ }
99
169
  }
100
170
  }
101
171
 
@@ -118,7 +188,9 @@ function detectTool(pkg) {
118
188
  const out = execSync(`npm list -g ${pkg} --depth=0 2>/dev/null`, { stdio: 'pipe', encoding: 'utf-8' });
119
189
  const match = out.match(new RegExp(escapeRegex(pkg) + '@(\\S+)'));
120
190
  if (match) return { installed: true, version: match[1], method: 'global' };
121
- } catch { /* not found */ }
191
+ } catch {
192
+ /* not found */
193
+ }
122
194
 
123
195
  // 2. npx cache
124
196
  try {
@@ -139,7 +211,9 @@ function detectTool(pkg) {
139
211
  }
140
212
  }
141
213
  }
142
- } catch { /* not found */ }
214
+ } catch {
215
+ /* not found */
216
+ }
143
217
 
144
218
  // 3. Local node_modules
145
219
  const localPkg = join(process.cwd(), 'node_modules', pkg, 'package.json');
@@ -147,7 +221,9 @@ function detectTool(pkg) {
147
221
  try {
148
222
  const p = JSON.parse(readFileSync(localPkg, 'utf8'));
149
223
  return { installed: true, version: p.version || 'installed', method: 'local' };
150
- } catch { /* ignore */ }
224
+ } catch {
225
+ /* ignore */
226
+ }
151
227
  }
152
228
 
153
229
  // 4. Sibling source directory
@@ -159,7 +235,9 @@ function detectTool(pkg) {
159
235
  if (p.name === pkg) {
160
236
  return { installed: true, version: p.version || 'source', method: 'source' };
161
237
  }
162
- } catch { /* ignore */ }
238
+ } catch {
239
+ /* ignore */
240
+ }
163
241
  }
164
242
 
165
243
  return { installed: false, version: null, method: null };
@@ -174,7 +252,9 @@ function runDoctor() {
174
252
  let npmVersion = 'unknown';
175
253
  try {
176
254
  npmVersion = execSync('npm --version', { stdio: 'pipe', encoding: 'utf-8' }).trim();
177
- } catch { /* ignore */ }
255
+ } catch {
256
+ /* ignore */
257
+ }
178
258
 
179
259
  const checks = [];
180
260
 
@@ -197,7 +277,7 @@ function runDoctor() {
197
277
  const toolStatuses = [];
198
278
  for (const tool of TOOLS) {
199
279
  const result = detectTool(tool.pkg);
200
- const status = result.installed ? 'pass' : (tool.category === 'core' ? 'warning' : 'info');
280
+ const status = result.installed ? 'pass' : tool.category === 'core' ? 'warning' : 'info';
201
281
 
202
282
  checks.push({
203
283
  name: tool.name,
@@ -214,9 +294,9 @@ function runDoctor() {
214
294
  });
215
295
  }
216
296
 
217
- const passCount = checks.filter(c => c.status === 'pass').length;
218
- const warnCount = checks.filter(c => c.status === 'warning').length;
219
- const failCount = checks.filter(c => c.status === 'fail').length;
297
+ const passCount = checks.filter((c) => c.status === 'pass').length;
298
+ const warnCount = checks.filter((c) => c.status === 'warning').length;
299
+ const failCount = checks.filter((c) => c.status === 'fail').length;
220
300
 
221
301
  return {
222
302
  checks,
@@ -231,7 +311,7 @@ function runDoctor() {
231
311
  // ── Ecosystem — aggregate status ──────────────────────────────────────────────
232
312
 
233
313
  function getEcosystem() {
234
- return TOOLS.map(tool => {
314
+ return TOOLS.map((tool) => {
235
315
  const result = detectTool(tool.pkg);
236
316
  return {
237
317
  ...tool,
@@ -254,24 +334,37 @@ function scaffold(targetDir, options = {}) {
254
334
  mkdirSync(dir, { recursive: true });
255
335
 
256
336
  // claims.json (atomic write-then-rename)
257
- const claimsData = JSON.stringify({
258
- claims: [],
259
- meta: { created: new Date().toISOString(), tool: 'grainulation' }
260
- }, null, 2) + '\n';
337
+ const claimsData =
338
+ JSON.stringify(
339
+ {
340
+ claims: [],
341
+ meta: { created: new Date().toISOString(), tool: 'grainulation' },
342
+ },
343
+ null,
344
+ 2,
345
+ ) + '\n';
261
346
  const tmpClaims = join(dir, 'claims.json.tmp.' + process.pid);
262
347
  writeFileSync(tmpClaims, claimsData);
263
348
  renameSync(tmpClaims, join(dir, 'claims.json'));
264
349
 
265
350
  // CLAUDE.md
266
351
  const question = options.question || 'What should we build?';
267
- writeFileSync(join(dir, 'CLAUDE.md'), `# Sprint\n\n**Question:** ${question}\n\n**Constraints:**\n- (add constraints here)\n\n**Done looks like:** (describe the output)\n`);
352
+ writeFileSync(
353
+ join(dir, 'CLAUDE.md'),
354
+ `# Sprint\n\n**Question:** ${question}\n\n**Constraints:**\n- (add constraints here)\n\n**Done looks like:** (describe the output)\n`,
355
+ );
268
356
 
269
357
  // orchard.json (if multi-sprint, atomic write-then-rename)
270
358
  if (options.includeOrchard) {
271
- const orchardData = JSON.stringify({
272
- sprints: [],
273
- settings: { sync_interval: 'manual' }
274
- }, null, 2) + '\n';
359
+ const orchardData =
360
+ JSON.stringify(
361
+ {
362
+ sprints: [],
363
+ settings: { sync_interval: 'manual' },
364
+ },
365
+ null,
366
+ 2,
367
+ ) + '\n';
275
368
  const tmpOrchard = join(dir, 'orchard.json.tmp.' + process.pid);
276
369
  writeFileSync(tmpOrchard, orchardData);
277
370
  renameSync(tmpOrchard, join(dir, 'orchard.json'));
@@ -310,10 +403,13 @@ function json(res, data, status = 200) {
310
403
  function readBody(req) {
311
404
  return new Promise((resolve, reject) => {
312
405
  const chunks = [];
313
- req.on('data', c => chunks.push(c));
406
+ req.on('data', (c) => chunks.push(c));
314
407
  req.on('end', () => {
315
- try { resolve(JSON.parse(Buffer.concat(chunks).toString())); }
316
- catch { resolve({}); }
408
+ try {
409
+ resolve(JSON.parse(Buffer.concat(chunks).toString()));
410
+ } catch {
411
+ resolve({});
412
+ }
317
413
  });
318
414
  req.on('error', reject);
319
415
  });
@@ -347,7 +443,7 @@ table{width:100%;border-collapse:collapse}th,td{padding:8px 12px;border-bottom:1
347
443
  th{color:#9ca3af}code{background:#1e293b;padding:2px 6px;border-radius:4px;font-size:13px}</style></head>
348
444
  <body><h1>grainulation API</h1><p>${ROUTES.length} endpoints</p>
349
445
  <table><tr><th>Method</th><th>Path</th><th>Description</th></tr>
350
- ${ROUTES.map(r => '<tr><td><code>'+r.method+'</code></td><td><code>'+r.path+'</code></td><td>'+r.description+'</td></tr>').join('')}
446
+ ${ROUTES.map((r) => '<tr><td><code>' + r.method + '</code></td><td><code>' + r.path + '</code></td><td>' + r.description + '</td></tr>').join('')}
351
447
  </table></body></html>`;
352
448
  res.writeHead(200, { 'Content-Type': 'text/html' });
353
449
  res.end(html);
@@ -359,15 +455,23 @@ ${ROUTES.map(r => '<tr><td><code>'+r.method+'</code></td><td><code>'+r.path+'</c
359
455
  res.writeHead(200, {
360
456
  'Content-Type': 'text/event-stream',
361
457
  'Cache-Control': 'no-cache',
362
- 'Connection': 'keep-alive',
458
+ Connection: 'keep-alive',
363
459
  });
364
460
  res.write(`data: ${JSON.stringify({ type: 'state', data: state })}\n\n`);
365
461
  const heartbeat = setInterval(() => {
366
- try { res.write(': heartbeat\n\n'); } catch { clearInterval(heartbeat); }
462
+ try {
463
+ res.write(': heartbeat\n\n');
464
+ } catch {
465
+ clearInterval(heartbeat);
466
+ }
367
467
  }, 15000);
368
468
  sseClients.add(res);
369
469
  vlog('sse', `client connected (${sseClients.size} total)`);
370
- req.on('close', () => { clearInterval(heartbeat); sseClients.delete(res); vlog('sse', `client disconnected (${sseClients.size} total)`); });
470
+ req.on('close', () => {
471
+ clearInterval(heartbeat);
472
+ sseClients.delete(res);
473
+ vlog('sse', `client disconnected (${sseClients.size} total)`);
474
+ });
371
475
  return;
372
476
  }
373
477
 
@@ -388,7 +492,7 @@ ${ROUTES.map(r => '<tr><td><code>'+r.method+'</code></td><td><code>'+r.path+'</c
388
492
  // ── API: tool detail ──
389
493
  if (req.method === 'GET' && url.pathname.startsWith('/api/tools/')) {
390
494
  const name = url.pathname.split('/').pop();
391
- const tool = state.ecosystem.find(t => t.name === name);
495
+ const tool = state.ecosystem.find((t) => t.name === name);
392
496
  if (!tool) {
393
497
  json(res, { error: 'Tool not found' }, 404);
394
498
  return;
@@ -401,9 +505,12 @@ ${ROUTES.map(r => '<tr><td><code>'+r.method+'</code></td><td><code>'+r.path+'</c
401
505
  if (req.method === 'POST' && url.pathname === '/api/pm/start') {
402
506
  const body = await readBody(req);
403
507
  const toolName = body.tool;
404
- if (!toolName) { json(res, { error: 'Missing tool name' }, 400); return; }
508
+ if (!toolName) {
509
+ json(res, { error: 'Missing tool name' }, 400);
510
+ return;
511
+ }
405
512
  try {
406
- const rootArgs = (body.root || SPRINT_ROOT) ? ['--root', resolve(body.root || SPRINT_ROOT)] : [];
513
+ const rootArgs = body.root || SPRINT_ROOT ? ['--root', resolve(body.root || SPRINT_ROOT)] : [];
407
514
  const result = pm.startTool(toolName, rootArgs);
408
515
  json(res, { ok: true, ...result });
409
516
  setTimeout(refreshState, 1500);
@@ -417,7 +524,10 @@ ${ROUTES.map(r => '<tr><td><code>'+r.method+'</code></td><td><code>'+r.path+'</c
417
524
  if (req.method === 'POST' && url.pathname === '/api/pm/stop') {
418
525
  const body = await readBody(req);
419
526
  const toolName = body.tool;
420
- if (!toolName) { json(res, { error: 'Missing tool name' }, 400); return; }
527
+ if (!toolName) {
528
+ json(res, { error: 'Missing tool name' }, 400);
529
+ return;
530
+ }
421
531
  try {
422
532
  const result = pm.stopTool(toolName);
423
533
  json(res, { ok: true, ...result });
@@ -477,7 +587,7 @@ ${ROUTES.map(r => '<tr><td><code>'+r.method+'</code></td><td><code>'+r.path+'</c
477
587
  }
478
588
 
479
589
  // ── Static files ──
480
- let filePath = url.pathname === '/' ? '/index.html' : url.pathname;
590
+ const filePath = url.pathname === '/' ? '/index.html' : url.pathname;
481
591
  const resolved = resolve(PUBLIC_DIR, '.' + filePath);
482
592
 
483
593
  if (!resolved.startsWith(PUBLIC_DIR)) {
@@ -488,7 +598,7 @@ ${ROUTES.map(r => '<tr><td><code>'+r.method+'</code></td><td><code>'+r.path+'</c
488
598
 
489
599
  if (existsSync(resolved) && statSync(resolved).isFile()) {
490
600
  const ext = extname(resolved);
491
- let content = readFileSync(resolved);
601
+ const content = readFileSync(resolved);
492
602
  res.writeHead(200, { 'Content-Type': MIME[ext] || 'application/octet-stream' });
493
603
  res.end(content);
494
604
  return;
@@ -501,7 +611,11 @@ ${ROUTES.map(r => '<tr><td><code>'+r.method+'</code></td><td><code>'+r.path+'</c
501
611
  // ── Graceful shutdown ─────────────────────────────────────────────────────────
502
612
  const shutdown = (signal) => {
503
613
  console.log(`\ngrainulation: ${signal} received, shutting down...`);
504
- for (const res of sseClients) { try { res.end(); } catch {} }
614
+ for (const res of sseClients) {
615
+ try {
616
+ res.end();
617
+ } catch {}
618
+ }
505
619
  sseClients.clear();
506
620
  server.close(() => process.exit(0));
507
621
  setTimeout(() => process.exit(1), 5000);
@@ -515,8 +629,10 @@ refreshState();
515
629
 
516
630
  server.listen(PORT, '127.0.0.1', () => {
517
631
  vlog('listen', `port=${PORT}`);
518
- const installed = state.ecosystem.filter(t => t.installed).length;
632
+ const installed = state.ecosystem.filter((t) => t.installed).length;
519
633
  console.log(`grainulation: serving on http://localhost:${PORT}`);
520
634
  console.log(` tools: ${installed}/${TOOLS.length} installed`);
521
- console.log(` doctor: ${state.doctorResults.summary.pass} pass, ${state.doctorResults.summary.warning} warn, ${state.doctorResults.summary.fail} fail`);
635
+ console.log(
636
+ ` doctor: ${state.doctorResults.summary.pass} pass, ${state.doctorResults.summary.warning} warn, ${state.doctorResults.summary.fail} fail`,
637
+ );
522
638
  });
package/lib/setup.js CHANGED
@@ -1,9 +1,7 @@
1
- 'use strict';
2
-
3
- const readline = require('node:readline');
4
- const { execSync } = require('node:child_process');
5
- const { getInstallable, getCategories } = require('./ecosystem');
6
- const { getVersion } = require('./doctor');
1
+ const readline = require("node:readline");
2
+ const { execSync } = require("node:child_process");
3
+ const { getInstallable, getCategories } = require("./ecosystem");
4
+ const { getVersion } = require("./doctor");
7
5
 
8
6
  /**
9
7
  * Interactive setup wizard.
@@ -14,24 +12,24 @@ const { getVersion } = require('./doctor');
14
12
 
15
13
  const ROLES = [
16
14
  {
17
- name: 'Researcher',
18
- description: 'Run research sprints, grow evidence, write briefs',
19
- tools: ['wheat'],
15
+ name: "Researcher",
16
+ description: "Run research sprints, grow evidence, write briefs",
17
+ tools: ["wheat"],
20
18
  },
21
19
  {
22
- name: 'Researcher + Dashboard',
23
- description: 'Research sprints with real-time permission dashboard',
24
- tools: ['wheat', 'farmer'],
20
+ name: "Researcher + Dashboard",
21
+ description: "Research sprints with real-time permission dashboard",
22
+ tools: ["wheat", "farmer"],
25
23
  },
26
24
  {
27
- name: 'Team Lead',
28
- description: 'Coordinate multiple sprints, review analytics',
29
- tools: ['wheat', 'farmer', 'orchard', 'harvest'],
25
+ name: "Team Lead",
26
+ description: "Coordinate multiple sprints, review analytics",
27
+ tools: ["wheat", "farmer", "orchard", "harvest"],
30
28
  },
31
29
  {
32
- name: 'Full Ecosystem',
33
- description: 'Everything. All 7 tools.',
34
- tools: ['wheat', 'farmer', 'barn', 'mill', 'silo', 'harvest', 'orchard'],
30
+ name: "Full Ecosystem",
31
+ description: "Everything. All 7 tools.",
32
+ tools: ["wheat", "farmer", "barn", "mill", "silo", "harvest", "orchard"],
35
33
  },
36
34
  ];
37
35
 
@@ -47,24 +45,24 @@ async function run() {
47
45
  output: process.stdout,
48
46
  });
49
47
 
50
- console.log('');
51
- console.log(' \x1b[1;33mgrainulation setup\x1b[0m');
52
- console.log(' What are you trying to do?');
53
- console.log('');
48
+ console.log("");
49
+ console.log(" \x1b[1;33mgrainulation setup\x1b[0m");
50
+ console.log(" What are you trying to do?");
51
+ console.log("");
54
52
 
55
53
  for (let i = 0; i < ROLES.length; i++) {
56
54
  const role = ROLES[i];
57
55
  console.log(` \x1b[1m${i + 1}.\x1b[0m ${role.name}`);
58
56
  console.log(` \x1b[2m${role.description}\x1b[0m`);
59
- console.log(` Tools: ${role.tools.join(', ')}`);
60
- console.log('');
57
+ console.log(` Tools: ${role.tools.join(", ")}`);
58
+ console.log("");
61
59
  }
62
60
 
63
- const answer = await ask(rl, ' Choose (1-4): ');
61
+ const answer = await ask(rl, " Choose (1-4): ");
64
62
  const choice = parseInt(answer, 10);
65
63
 
66
64
  if (choice < 1 || choice > ROLES.length || isNaN(choice)) {
67
- console.log('\n \x1b[31mInvalid choice.\x1b[0m\n');
65
+ console.log("\n \x1b[31mInvalid choice.\x1b[0m\n");
68
66
  rl.close();
69
67
  return;
70
68
  }
@@ -72,9 +70,9 @@ async function run() {
72
70
  const role = ROLES[choice - 1];
73
71
  const toInstall = [];
74
72
 
75
- console.log('');
73
+ console.log("");
76
74
  console.log(` \x1b[1mSetting up: ${role.name}\x1b[0m`);
77
- console.log('');
75
+ console.log("");
78
76
 
79
77
  for (const toolName of role.tools) {
80
78
  const tool = getInstallable().find((t) => t.name === toolName);
@@ -82,7 +80,9 @@ async function run() {
82
80
 
83
81
  const version = getVersion(tool.package);
84
82
  if (version) {
85
- console.log(` \x1b[32m\u2713\x1b[0m ${tool.name} already installed (${version})`);
83
+ console.log(
84
+ ` \x1b[32m\u2713\x1b[0m ${tool.name} already installed (${version})`,
85
+ );
86
86
  } else {
87
87
  toInstall.push(tool);
88
88
  console.log(` \x1b[33m+\x1b[0m ${tool.name} will be installed`);
@@ -90,40 +90,44 @@ async function run() {
90
90
  }
91
91
 
92
92
  if (toInstall.length === 0) {
93
- console.log('');
94
- console.log(' \x1b[32mEverything is already installed.\x1b[0m');
95
- console.log(' Run \x1b[1mnpx @grainulation/wheat init\x1b[0m to start a research sprint.');
93
+ console.log("");
94
+ console.log(" \x1b[32mEverything is already installed.\x1b[0m");
95
+ console.log(
96
+ " Run \x1b[1mnpx @grainulation/wheat init\x1b[0m to start a research sprint.",
97
+ );
96
98
  rl.close();
97
99
  return;
98
100
  }
99
101
 
100
- console.log('');
102
+ console.log("");
101
103
  const confirm = await ask(
102
104
  rl,
103
- ` Install ${toInstall.length} package(s)? (y/N): `
105
+ ` Install ${toInstall.length} package(s)? (y/N): `,
104
106
  );
105
107
 
106
- if (confirm.toLowerCase() !== 'y') {
107
- console.log('\n \x1b[2mAborted.\x1b[0m\n');
108
+ if (confirm.toLowerCase() !== "y") {
109
+ console.log("\n \x1b[2mAborted.\x1b[0m\n");
108
110
  rl.close();
109
111
  return;
110
112
  }
111
113
 
112
- console.log('');
114
+ console.log("");
113
115
  for (const tool of toInstall) {
114
116
  console.log(` Installing ${tool.package}...`);
115
117
  try {
116
- execSync(`npm install -g ${tool.package}`, { stdio: 'pipe' });
118
+ execSync(`npm install -g ${tool.package}`, { stdio: "pipe" });
117
119
  console.log(` \x1b[32m\u2713\x1b[0m ${tool.name} installed`);
118
120
  } catch (err) {
119
- console.log(` \x1b[31m\u2717\x1b[0m ${tool.name} failed: ${err.message}`);
121
+ console.log(
122
+ ` \x1b[31m\u2717\x1b[0m ${tool.name} failed: ${err.message}`,
123
+ );
120
124
  }
121
125
  }
122
126
 
123
- console.log('');
124
- console.log(' \x1b[32mSetup complete.\x1b[0m');
125
- console.log(' Start with: npx @grainulation/wheat init');
126
- console.log('');
127
+ console.log("");
128
+ console.log(" \x1b[32mSetup complete.\x1b[0m");
129
+ console.log(" Start with: npx @grainulation/wheat init");
130
+ console.log("");
127
131
  rl.close();
128
132
  }
129
133
 
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "@grainulation/grainulation",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Structured research for decisions that satisfice",
5
5
  "license": "MIT",
6
+ "type": "module",
7
+ "workspaces": [
8
+ "packages/*"
9
+ ],
6
10
  "bin": {
7
11
  "grainulation": "bin/grainulation.js"
8
12
  },
@@ -20,7 +24,10 @@
20
24
  ],
21
25
  "scripts": {
22
26
  "test": "node test/basic.test.js",
23
- "start": "node bin/grainulation.js"
27
+ "lint": "biome ci .",
28
+ "format": "biome check --write .",
29
+ "start": "node bin/grainulation.js",
30
+ "postinstall": "git config core.hooksPath .githooks || true"
24
31
  },
25
32
  "keywords": [
26
33
  "research",
@@ -38,7 +45,11 @@
38
45
  },
39
46
  "homepage": "https://grainulation.com",
40
47
  "engines": {
41
- "node": ">=18"
48
+ "node": ">=20"
42
49
  },
43
- "author": "grainulation contributors"
50
+ "packageManager": "pnpm@10.30.0",
51
+ "author": "grainulation contributors",
52
+ "devDependencies": {
53
+ "@biomejs/biome": "2.4.8"
54
+ }
44
55
  }