@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/lib/server.mjs CHANGED
@@ -11,17 +11,8 @@
11
11
  * grainulation serve [--port 9098]
12
12
  */
13
13
 
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';
14
+ import { execFileSync } from 'node:child_process';
15
+ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
25
16
  import { createServer } from 'node:http';
26
17
  import { createRequire } from 'node:module';
27
18
  import { dirname, extname, join, resolve } from 'node:path';
@@ -185,8 +176,11 @@ function detectTool(pkg) {
185
176
 
186
177
  // 1. Global npm
187
178
  try {
188
- const out = execSync(`npm list -g ${pkg} --depth=0 2>/dev/null`, { stdio: 'pipe', encoding: 'utf-8' });
189
- const match = out.match(new RegExp(escapeRegex(pkg) + '@(\\S+)'));
179
+ const out = execFileSync('npm', ['list', '-g', pkg, '--depth=0'], {
180
+ stdio: ['pipe', 'pipe', 'ignore'],
181
+ encoding: 'utf-8',
182
+ });
183
+ const match = out.match(new RegExp(`${escapeRegex(pkg)}@(\\S+)`));
190
184
  if (match) return { installed: true, version: match[1], method: 'global' };
191
185
  } catch {
192
186
  /* not found */
@@ -194,7 +188,10 @@ function detectTool(pkg) {
194
188
 
195
189
  // 2. npx cache
196
190
  try {
197
- const prefix = execSync('npm config get cache', { stdio: 'pipe', encoding: 'utf-8' }).trim();
191
+ const prefix = execFileSync('npm', ['config', 'get', 'cache'], {
192
+ stdio: ['pipe', 'pipe', 'ignore'],
193
+ encoding: 'utf-8',
194
+ }).trim();
198
195
  const npxDir = join(prefix, '_npx');
199
196
  if (existsSync(npxDir)) {
200
197
  const entries = readdirSync(npxDir, { withFileTypes: true });
@@ -251,7 +248,7 @@ function runDoctor() {
251
248
  const nodeVersion = process.version;
252
249
  let npmVersion = 'unknown';
253
250
  try {
254
- npmVersion = execSync('npm --version', { stdio: 'pipe', encoding: 'utf-8' }).trim();
251
+ npmVersion = execFileSync('npm', ['--version'], { stdio: ['pipe', 'pipe', 'ignore'], encoding: 'utf-8' }).trim();
255
252
  } catch {
256
253
  /* ignore */
257
254
  }
@@ -261,7 +258,7 @@ function runDoctor() {
261
258
  // Environment checks
262
259
  checks.push({
263
260
  name: 'Node.js',
264
- status: parseInt(nodeVersion.slice(1)) >= 18 ? 'pass' : 'warning',
261
+ status: parseInt(nodeVersion.slice(1), 10) >= 18 ? 'pass' : 'warning',
265
262
  detail: nodeVersion,
266
263
  category: 'environment',
267
264
  });
@@ -334,16 +331,15 @@ function scaffold(targetDir, options = {}) {
334
331
  mkdirSync(dir, { recursive: true });
335
332
 
336
333
  // claims.json (atomic write-then-rename)
337
- const claimsData =
338
- JSON.stringify(
339
- {
340
- claims: [],
341
- meta: { created: new Date().toISOString(), tool: 'grainulation' },
342
- },
343
- null,
344
- 2,
345
- ) + '\n';
346
- const tmpClaims = join(dir, 'claims.json.tmp.' + process.pid);
334
+ const claimsData = `${JSON.stringify(
335
+ {
336
+ claims: [],
337
+ meta: { created: new Date().toISOString(), tool: 'grainulation' },
338
+ },
339
+ null,
340
+ 2,
341
+ )}\n`;
342
+ const tmpClaims = join(dir, `claims.json.tmp.${process.pid}`);
347
343
  writeFileSync(tmpClaims, claimsData);
348
344
  renameSync(tmpClaims, join(dir, 'claims.json'));
349
345
 
@@ -356,16 +352,15 @@ function scaffold(targetDir, options = {}) {
356
352
 
357
353
  // orchard.json (if multi-sprint, atomic write-then-rename)
358
354
  if (options.includeOrchard) {
359
- const orchardData =
360
- JSON.stringify(
361
- {
362
- sprints: [],
363
- settings: { sync_interval: 'manual' },
364
- },
365
- null,
366
- 2,
367
- ) + '\n';
368
- const tmpOrchard = join(dir, 'orchard.json.tmp.' + process.pid);
355
+ const orchardData = `${JSON.stringify(
356
+ {
357
+ sprints: [],
358
+ settings: { sync_interval: 'manual' },
359
+ },
360
+ null,
361
+ 2,
362
+ )}\n`;
363
+ const tmpOrchard = join(dir, `orchard.json.tmp.${process.pid}`);
369
364
  writeFileSync(tmpOrchard, orchardData);
370
365
  renameSync(tmpOrchard, join(dir, 'orchard.json'));
371
366
  }
@@ -443,7 +438,7 @@ table{width:100%;border-collapse:collapse}th,td{padding:8px 12px;border-bottom:1
443
438
  th{color:#9ca3af}code{background:#1e293b;padding:2px 6px;border-radius:4px;font-size:13px}</style></head>
444
439
  <body><h1>grainulation API</h1><p>${ROUTES.length} endpoints</p>
445
440
  <table><tr><th>Method</th><th>Path</th><th>Description</th></tr>
446
- ${ROUTES.map((r) => '<tr><td><code>' + r.method + '</code></td><td><code>' + r.path + '</code></td><td>' + r.description + '</td></tr>').join('')}
441
+ ${ROUTES.map((r) => `<tr><td><code>${r.method}</code></td><td><code>${r.path}</code></td><td>${r.description}</td></tr>`).join('')}
447
442
  </table></body></html>`;
448
443
  res.writeHead(200, { 'Content-Type': 'text/html' });
449
444
  res.end(html);
@@ -588,7 +583,7 @@ ${ROUTES.map((r) => '<tr><td><code>' + r.method + '</code></td><td><code>' + r.p
588
583
 
589
584
  // ── Static files ──
590
585
  const filePath = url.pathname === '/' ? '/index.html' : url.pathname;
591
- const resolved = resolve(PUBLIC_DIR, '.' + filePath);
586
+ const resolved = resolve(PUBLIC_DIR, `.${filePath}`);
592
587
 
593
588
  if (!resolved.startsWith(PUBLIC_DIR)) {
594
589
  res.writeHead(403);
package/lib/setup.js CHANGED
@@ -1,7 +1,7 @@
1
- const readline = require("node:readline");
2
- const { execSync } = require("node:child_process");
3
- const { getInstallable, getCategories } = require("./ecosystem");
4
- const { getVersion } = require("./doctor");
1
+ const readline = require('node:readline');
2
+ const { execFileSync } = require('node:child_process');
3
+ const { getInstallable, getCategories } = require('./ecosystem');
4
+ const { getVersion } = require('./doctor');
5
5
 
6
6
  /**
7
7
  * Interactive setup wizard.
@@ -12,24 +12,24 @@ const { getVersion } = require("./doctor");
12
12
 
13
13
  const ROLES = [
14
14
  {
15
- name: "Researcher",
16
- description: "Run research sprints, grow evidence, write briefs",
17
- tools: ["wheat"],
15
+ name: 'Researcher',
16
+ description: 'Run research sprints, grow evidence, write briefs',
17
+ tools: ['wheat'],
18
18
  },
19
19
  {
20
- name: "Researcher + Dashboard",
21
- description: "Research sprints with real-time permission dashboard",
22
- tools: ["wheat", "farmer"],
20
+ name: 'Researcher + Dashboard',
21
+ description: 'Research sprints with real-time permission dashboard',
22
+ tools: ['wheat', 'farmer'],
23
23
  },
24
24
  {
25
- name: "Team Lead",
26
- description: "Coordinate multiple sprints, review analytics",
27
- tools: ["wheat", "farmer", "orchard", "harvest"],
25
+ name: 'Team Lead',
26
+ description: 'Coordinate multiple sprints, review analytics',
27
+ tools: ['wheat', 'farmer', 'orchard', 'harvest'],
28
28
  },
29
29
  {
30
- name: "Full Ecosystem",
31
- description: "Everything. All 7 tools.",
32
- 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'],
33
33
  },
34
34
  ];
35
35
 
@@ -45,24 +45,24 @@ async function run() {
45
45
  output: process.stdout,
46
46
  });
47
47
 
48
- console.log("");
49
- console.log(" \x1b[1;33mgrainulation setup\x1b[0m");
50
- console.log(" What are you trying to do?");
51
- 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('');
52
52
 
53
53
  for (let i = 0; i < ROLES.length; i++) {
54
54
  const role = ROLES[i];
55
55
  console.log(` \x1b[1m${i + 1}.\x1b[0m ${role.name}`);
56
56
  console.log(` \x1b[2m${role.description}\x1b[0m`);
57
- console.log(` Tools: ${role.tools.join(", ")}`);
58
- console.log("");
57
+ console.log(` Tools: ${role.tools.join(', ')}`);
58
+ console.log('');
59
59
  }
60
60
 
61
- const answer = await ask(rl, " Choose (1-4): ");
61
+ const answer = await ask(rl, ' Choose (1-4): ');
62
62
  const choice = parseInt(answer, 10);
63
63
 
64
- if (choice < 1 || choice > ROLES.length || isNaN(choice)) {
65
- console.log("\n \x1b[31mInvalid choice.\x1b[0m\n");
64
+ if (choice < 1 || choice > ROLES.length || Number.isNaN(choice)) {
65
+ console.log('\n \x1b[31mInvalid choice.\x1b[0m\n');
66
66
  rl.close();
67
67
  return;
68
68
  }
@@ -70,9 +70,9 @@ async function run() {
70
70
  const role = ROLES[choice - 1];
71
71
  const toInstall = [];
72
72
 
73
- console.log("");
73
+ console.log('');
74
74
  console.log(` \x1b[1mSetting up: ${role.name}\x1b[0m`);
75
- console.log("");
75
+ console.log('');
76
76
 
77
77
  for (const toolName of role.tools) {
78
78
  const tool = getInstallable().find((t) => t.name === toolName);
@@ -80,9 +80,7 @@ async function run() {
80
80
 
81
81
  const version = getVersion(tool.package);
82
82
  if (version) {
83
- console.log(
84
- ` \x1b[32m\u2713\x1b[0m ${tool.name} already installed (${version})`,
85
- );
83
+ console.log(` \x1b[32m\u2713\x1b[0m ${tool.name} already installed (${version})`);
86
84
  } else {
87
85
  toInstall.push(tool);
88
86
  console.log(` \x1b[33m+\x1b[0m ${tool.name} will be installed`);
@@ -90,44 +88,37 @@ async function run() {
90
88
  }
91
89
 
92
90
  if (toInstall.length === 0) {
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
- );
91
+ console.log('');
92
+ console.log(' \x1b[32mEverything is already installed.\x1b[0m');
93
+ console.log(' Run \x1b[1mnpx @grainulation/wheat init\x1b[0m to start a research sprint.');
98
94
  rl.close();
99
95
  return;
100
96
  }
101
97
 
102
- console.log("");
103
- const confirm = await ask(
104
- rl,
105
- ` Install ${toInstall.length} package(s)? (y/N): `,
106
- );
98
+ console.log('');
99
+ const confirm = await ask(rl, ` Install ${toInstall.length} package(s)? (y/N): `);
107
100
 
108
- if (confirm.toLowerCase() !== "y") {
109
- console.log("\n \x1b[2mAborted.\x1b[0m\n");
101
+ if (confirm.toLowerCase() !== 'y') {
102
+ console.log('\n \x1b[2mAborted.\x1b[0m\n');
110
103
  rl.close();
111
104
  return;
112
105
  }
113
106
 
114
- console.log("");
107
+ console.log('');
115
108
  for (const tool of toInstall) {
116
109
  console.log(` Installing ${tool.package}...`);
117
110
  try {
118
- execSync(`npm install -g ${tool.package}`, { stdio: "pipe" });
111
+ execFileSync('npm', ['install', '-g', tool.package], { stdio: 'pipe' });
119
112
  console.log(` \x1b[32m\u2713\x1b[0m ${tool.name} installed`);
120
113
  } catch (err) {
121
- console.log(
122
- ` \x1b[31m\u2717\x1b[0m ${tool.name} failed: ${err.message}`,
123
- );
114
+ console.log(` \x1b[31m\u2717\x1b[0m ${tool.name} failed: ${err.message}`);
124
115
  }
125
116
  }
126
117
 
127
- console.log("");
128
- console.log(" \x1b[32mSetup complete.\x1b[0m");
129
- console.log(" Start with: npx @grainulation/wheat init");
130
- console.log("");
118
+ console.log('');
119
+ console.log(' \x1b[32mSetup complete.\x1b[0m');
120
+ console.log(' Start with: npx @grainulation/wheat init');
121
+ console.log('');
131
122
  rl.close();
132
123
  }
133
124
 
package/package.json CHANGED
@@ -1,9 +1,8 @@
1
1
  {
2
2
  "name": "@grainulation/grainulation",
3
- "version": "1.0.1",
3
+ "version": "1.1.1",
4
4
  "description": "Structured research for decisions that satisfice",
5
5
  "license": "MIT",
6
- "type": "module",
7
6
  "workspaces": [
8
7
  "packages/*"
9
8
  ],
@@ -26,8 +25,7 @@
26
25
  "test": "node test/basic.test.js",
27
26
  "lint": "biome ci .",
28
27
  "format": "biome check --write .",
29
- "start": "node bin/grainulation.js",
30
- "postinstall": "git config core.hooksPath .githooks || true"
28
+ "start": "node bin/grainulation.js"
31
29
  },
32
30
  "keywords": [
33
31
  "research",
package/public/index.html CHANGED
@@ -440,7 +440,7 @@ body {
440
440
  </main>
441
441
  </div>
442
442
  <footer class="footer">
443
- <span>grainulation v1.0.0 -- @grainulation/grainulation</span>
443
+ <span>grainulation v1.0.2 -- @grainulation/grainulation</span>
444
444
  <div class="footer-links">
445
445
  <a href="http://localhost:9091">wheat</a>
446
446
  <a href="http://localhost:9090">farmer</a>