@grainulation/orchard 1.0.1 → 1.0.2

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/CONTRIBUTING.md CHANGED
@@ -15,16 +15,20 @@ No `npm install` needed -- orchard has zero dependencies.
15
15
  ## How to contribute
16
16
 
17
17
  ### Report a bug
18
+
18
19
  Open an issue with:
20
+
19
21
  - What you expected
20
22
  - What happened instead
21
23
  - Your Node version (`node --version`)
22
24
  - Steps to reproduce
23
25
 
24
26
  ### Suggest a feature
27
+
25
28
  Open an issue describing the use case, not just the solution. "I need X because Y" is more useful than "add X."
26
29
 
27
30
  ### Submit a PR
31
+
28
32
  1. Fork the repo
29
33
  2. Create a branch (`git checkout -b fix/description`)
30
34
  3. Make your changes
@@ -74,11 +78,13 @@ Tests use Node's built-in test runner. No test framework dependencies.
74
78
  ## Commit messages
75
79
 
76
80
  Follow the existing pattern:
81
+
77
82
  ```
78
83
  orchard: <what changed>
79
84
  ```
80
85
 
81
86
  Examples:
87
+
82
88
  ```
83
89
  orchard: add cross-sprint dependency graph
84
90
  orchard: fix timeline calculation for overlapping sprints
package/README.md CHANGED
@@ -3,7 +3,8 @@
3
3
  </p>
4
4
 
5
5
  <p align="center">
6
- <a href="https://www.npmjs.com/package/@grainulation/orchard"><img src="https://img.shields.io/npm/v/@grainulation/orchard" alt="npm version"></a> <a href="https://www.npmjs.com/package/@grainulation/orchard"><img src="https://img.shields.io/npm/dm/@grainulation/orchard" alt="npm downloads"></a> <a href="https://github.com/grainulation/orchard/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@grainulation/orchard" alt="license"></a> <a href="https://nodejs.org"><img src="https://img.shields.io/node/v/@grainulation/orchard" alt="node"></a> <a href="https://github.com/grainulation/orchard/actions"><img src="https://github.com/grainulation/orchard/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
6
+ <a href="https://www.npmjs.com/package/@grainulation/orchard"><img src="https://img.shields.io/npm/v/@grainulation/orchard" alt="npm version"></a> <a href="https://www.npmjs.com/package/@grainulation/orchard"><img src="https://img.shields.io/npm/dm/@grainulation/orchard" alt="npm downloads"></a> <a href="https://github.com/grainulation/orchard/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="license"></a> <a href="https://nodejs.org"><img src="https://img.shields.io/node/v/@grainulation/orchard" alt="node"></a> <a href="https://github.com/grainulation/orchard/actions"><img src="https://github.com/grainulation/orchard/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
7
+ <a href="https://deepwiki.com/grainulation/orchard"><img src="https://deepwiki.com/badge.svg" alt="Explore on DeepWiki"></a>
7
8
  </p>
8
9
 
9
10
  <p align="center"><strong>Multi-sprint orchestration and dependency tracking.</strong></p>
@@ -69,15 +70,15 @@ orchard dashboard # Generate unified HTML dashboard
69
70
 
70
71
  ## CLI
71
72
 
72
- | Command | Description |
73
- |---------|-------------|
74
- | `orchard plan` | Show sprint dependency graph |
75
- | `orchard status` | Show status of all tracked sprints |
76
- | `orchard assign <path> <person>` | Assign a person to a sprint |
77
- | `orchard sync` | Sync sprint states from directories |
78
- | `orchard dashboard [outfile]` | Generate unified HTML dashboard |
79
- | `orchard init` | Initialize orchard.json |
80
- | `orchard serve` | Start the portfolio dashboard web server |
73
+ | Command | Description |
74
+ | -------------------------------- | ---------------------------------------- |
75
+ | `orchard plan` | Show sprint dependency graph |
76
+ | `orchard status` | Show status of all tracked sprints |
77
+ | `orchard assign <path> <person>` | Assign a person to a sprint |
78
+ | `orchard sync` | Sync sprint states from directories |
79
+ | `orchard dashboard [outfile]` | Generate unified HTML dashboard |
80
+ | `orchard init` | Initialize orchard.json |
81
+ | `orchard serve` | Start the portfolio dashboard web server |
81
82
 
82
83
  ## Conflict detection
83
84
 
@@ -92,16 +93,16 @@ Node built-in modules only.
92
93
 
93
94
  ## Part of the grainulation ecosystem
94
95
 
95
- | Tool | Role |
96
- |------|------|
97
- | [wheat](https://github.com/grainulation/wheat) | Research engine -- grow structured evidence |
98
- | [farmer](https://github.com/grainulation/farmer) | Permission dashboard -- approve AI actions in real time |
99
- | [barn](https://github.com/grainulation/barn) | Shared tools -- templates, validators, sprint detection |
100
- | [mill](https://github.com/grainulation/mill) | Format conversion -- export to PDF, CSV, slides, 24 formats |
101
- | [silo](https://github.com/grainulation/silo) | Knowledge storage -- reusable claim libraries and packs |
102
- | [harvest](https://github.com/grainulation/harvest) | Analytics -- cross-sprint patterns and prediction scoring |
103
- | **orchard** | Orchestration -- multi-sprint coordination and dependencies |
104
- | [grainulation](https://github.com/grainulation/grainulation) | Unified CLI -- single entry point to the ecosystem |
96
+ | Tool | Role |
97
+ | ------------------------------------------------------------ | ----------------------------------------------------------- |
98
+ | [wheat](https://github.com/grainulation/wheat) | Research engine -- grow structured evidence |
99
+ | [farmer](https://github.com/grainulation/farmer) | Permission dashboard -- approve AI actions in real time |
100
+ | [barn](https://github.com/grainulation/barn) | Shared tools -- templates, validators, sprint detection |
101
+ | [mill](https://github.com/grainulation/mill) | Format conversion -- export to PDF, CSV, slides, 24 formats |
102
+ | [silo](https://github.com/grainulation/silo) | Knowledge storage -- reusable claim libraries and packs |
103
+ | [harvest](https://github.com/grainulation/harvest) | Analytics -- cross-sprint patterns and prediction scoring |
104
+ | **orchard** | Orchestration -- multi-sprint coordination and dependencies |
105
+ | [grainulation](https://github.com/grainulation/grainulation) | Unified CLI -- single entry point to the ecosystem |
105
106
 
106
107
  ## License
107
108
 
package/bin/orchard.js CHANGED
@@ -1,74 +1,78 @@
1
1
  #!/usr/bin/env node
2
- 'use strict';
2
+ "use strict";
3
3
 
4
- const path = require('node:path');
5
- const fs = require('node:fs');
4
+ const path = require("node:path");
5
+ const fs = require("node:fs");
6
6
 
7
- const verbose = process.argv.includes('--verbose') || process.argv.includes('-v');
7
+ const verbose =
8
+ process.argv.includes("--verbose") || process.argv.includes("-v");
8
9
  function vlog(...a) {
9
10
  if (!verbose) return;
10
11
  const ts = new Date().toISOString();
11
- process.stderr.write(`[${ts}] orchard: ${a.join(' ')}\n`);
12
+ process.stderr.write(`[${ts}] orchard: ${a.join(" ")}\n`);
12
13
  }
13
14
 
14
15
  const COMMANDS = {
15
- init: 'Initialize orchard.json in the current directory',
16
- plan: 'Show sprint dependency graph as ASCII',
17
- status: 'Show status of all tracked sprints',
18
- assign: 'Assign a person to a sprint',
19
- sync: 'Sync sprint states from their directories',
20
- dashboard: 'Generate unified HTML dashboard',
21
- serve: 'Start the portfolio dashboard web server',
22
- connect: 'Connect to a farmer instance',
23
- doctor: 'Check health of orchard setup',
24
- help: 'Show this help message',
16
+ init: "Initialize orchard.json in the current directory",
17
+ plan: "Show sprint dependency graph (--format mermaid|ascii, --mermaid)",
18
+ status: "Show status of all tracked sprints",
19
+ conflicts: "Show cross-sprint conflicts (--severity critical|warning|info)",
20
+ assign: "Assign a person to a sprint",
21
+ sync: "Sync sprint states from their directories",
22
+ decompose: "Auto-decompose a question into sub-sprints",
23
+ hackathon: "Hackathon coordinator (init|team|status|end)",
24
+ dashboard: "Generate unified HTML dashboard",
25
+ serve: "Start the portfolio dashboard web server",
26
+ connect: "Connect to a farmer instance",
27
+ doctor: "Check health of orchard setup",
28
+ help: "Show this help message",
25
29
  };
26
30
 
27
31
  function loadConfig(dir) {
28
- const configPath = path.join(dir, 'orchard.json');
32
+ const configPath = path.join(dir, "orchard.json");
29
33
  if (!fs.existsSync(configPath)) {
30
34
  return null;
31
35
  }
32
- return JSON.parse(fs.readFileSync(configPath, 'utf8'));
36
+ return JSON.parse(fs.readFileSync(configPath, "utf8"));
33
37
  }
34
38
 
35
39
  function findOrchardRoot() {
36
40
  let dir = process.cwd();
37
41
  while (dir !== path.dirname(dir)) {
38
- if (fs.existsSync(path.join(dir, 'orchard.json'))) return dir;
42
+ if (fs.existsSync(path.join(dir, "orchard.json"))) return dir;
39
43
  dir = path.dirname(dir);
40
44
  }
41
45
  return null;
42
46
  }
43
47
 
44
48
  function printHelp() {
45
- console.log('');
46
- console.log(' orchard - Multi-sprint research orchestrator');
47
- console.log('');
48
- console.log(' Usage: orchard <command> [options]');
49
- console.log('');
50
- console.log(' Commands:');
49
+ console.log("");
50
+ console.log(" orchard - Multi-sprint research orchestrator");
51
+ console.log("");
52
+ console.log(" Usage: orchard <command> [options]");
53
+ console.log("");
54
+ console.log(" Commands:");
51
55
  for (const [cmd, desc] of Object.entries(COMMANDS)) {
52
56
  console.log(` ${cmd.padEnd(12)} ${desc}`);
53
57
  }
54
- console.log('');
55
- console.log(' Serve options:');
56
- console.log(' --port 9097 Port for the web server (default: 9097)');
57
- console.log(' --root <dir> Root directory to scan for sprints');
58
- console.log('');
59
- console.log(' Connect:');
60
- console.log(' orchard connect farmer --url http://localhost:9090');
61
- console.log('');
62
- console.log(' Config: orchard.json in project root');
63
- console.log('');
58
+ console.log("");
59
+ console.log(" Serve options:");
60
+ console.log(" --port 9097 Port for the web server (default: 9097)");
61
+ console.log(" --root <dir> Root directory to scan for sprints");
62
+ console.log("");
63
+ console.log(" Connect:");
64
+ console.log(" orchard connect farmer --url http://localhost:9090");
65
+ console.log("");
66
+ console.log(" Config: orchard.json in project root");
67
+ console.log("");
64
68
  }
65
69
 
66
70
  async function main() {
67
71
  const args = process.argv.slice(2);
68
- const command = args[0] || 'help';
69
- vlog('startup', `command=${command}`, `cwd=${process.cwd()}`);
72
+ const command = args[0] || "help";
73
+ vlog("startup", `command=${command}`, `cwd=${process.cwd()}`);
70
74
 
71
- if (command === 'help' || command === '--help' || command === '-h') {
75
+ if (command === "help" || command === "--help" || command === "-h") {
72
76
  printHelp();
73
77
  process.exit(0);
74
78
  }
@@ -80,30 +84,30 @@ async function main() {
80
84
  }
81
85
 
82
86
  // Serve command — start the HTTP server (ESM module)
83
- if (command === 'serve') {
84
- const serverPath = path.join(__dirname, '..', 'lib', 'server.js');
85
- const { spawn } = require('node:child_process');
87
+ if (command === "serve") {
88
+ const serverPath = path.join(__dirname, "..", "lib", "server.js");
89
+ const { spawn } = require("node:child_process");
86
90
 
87
91
  // Forward remaining args to the server
88
92
  const serverArgs = args.slice(1);
89
93
  const child = spawn(process.execPath, [serverPath, ...serverArgs], {
90
- stdio: 'inherit',
94
+ stdio: "inherit",
91
95
  });
92
96
 
93
- child.on('close', (code) => process.exit(code ?? 0));
94
- child.on('error', (err) => {
97
+ child.on("close", (code) => process.exit(code ?? 0));
98
+ child.on("error", (err) => {
95
99
  console.error(`orchard: failed to start server: ${err.message}`);
96
100
  process.exit(1);
97
101
  });
98
- process.on('SIGTERM', () => child.kill('SIGTERM'));
99
- process.on('SIGINT', () => child.kill('SIGINT'));
102
+ process.on("SIGTERM", () => child.kill("SIGTERM"));
103
+ process.on("SIGINT", () => child.kill("SIGINT"));
100
104
  return;
101
105
  }
102
106
 
103
- if (command === 'connect') {
104
- const { connect: farmerConnect } = require('../lib/farmer.js');
105
- const connectArgs = process.argv.slice(process.argv.indexOf('connect') + 1);
106
- const rootIdx = connectArgs.indexOf('--root');
107
+ if (command === "connect") {
108
+ const { connect: farmerConnect } = require("../lib/farmer.js");
109
+ const connectArgs = process.argv.slice(process.argv.indexOf("connect") + 1);
110
+ const rootIdx = connectArgs.indexOf("--root");
107
111
  let targetDir = process.cwd();
108
112
  if (rootIdx !== -1 && connectArgs[rootIdx + 1]) {
109
113
  targetDir = path.resolve(connectArgs[rootIdx + 1]);
@@ -112,85 +116,228 @@ async function main() {
112
116
  return;
113
117
  }
114
118
 
115
- if (command === 'init') {
116
- const { parseArgs } = require('node:util');
119
+ if (command === "init") {
120
+ const { parseArgs } = require("node:util");
117
121
  let rootDir = process.cwd();
118
122
  try {
119
- const { values } = parseArgs({ args: process.argv.slice(3), options: { root: { type: 'string' } }, allowPositionals: true });
123
+ const { values } = parseArgs({
124
+ args: process.argv.slice(3),
125
+ options: { root: { type: "string" } },
126
+ allowPositionals: true,
127
+ });
120
128
  if (values.root) rootDir = path.resolve(values.root);
121
- } catch (_) { /* ignore parse errors for init */ }
122
- const configPath = path.join(rootDir, 'orchard.json');
129
+ } catch (_) {
130
+ /* ignore parse errors for init */
131
+ }
132
+ const configPath = path.join(rootDir, "orchard.json");
123
133
  if (fs.existsSync(configPath)) {
124
- console.error('orchard: orchard.json already exists');
134
+ console.error("orchard: orchard.json already exists");
125
135
  process.exit(1);
126
136
  }
127
- const defaultConfig = { sprints: [], settings: { sync_interval: 'manual' } };
128
- fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2) + '\n', 'utf8');
129
- console.log('Initialized orchard.json add sprints with `orchard plan`');
137
+ const defaultConfig = {
138
+ sprints: [],
139
+ settings: { sync_interval: "manual" },
140
+ };
141
+ fs.writeFileSync(
142
+ configPath,
143
+ JSON.stringify(defaultConfig, null, 2) + "\n",
144
+ "utf8",
145
+ );
146
+ console.log("Initialized orchard.json — add sprints with `orchard plan`");
130
147
  process.exit(0);
131
148
  }
132
149
 
133
150
  const root = findOrchardRoot();
134
- if (!root && command !== 'help' && command !== 'doctor') {
135
- console.error('orchard: no orchard.json found. Run from a directory with orchard.json or a subdirectory.');
136
- console.error('');
137
- console.error('Create one:');
151
+ if (!root && command !== "help" && command !== "doctor") {
152
+ console.error(
153
+ "orchard: no orchard.json found. Run from a directory with orchard.json or a subdirectory.",
154
+ );
155
+ console.error("");
156
+ console.error("Create one:");
138
157
  console.error(' { "sprints": [] }');
139
158
  process.exit(1);
140
159
  }
141
160
 
142
161
  const config = root ? loadConfig(root) : { sprints: [] };
143
- const jsonMode = args.includes('--json');
162
+ const jsonMode = args.includes("--json");
144
163
 
145
164
  switch (command) {
146
- case 'plan': {
165
+ case "plan": {
147
166
  if (jsonMode) {
148
- const { buildGraph, topoSort, detectCycles } = require('../lib/planner.js');
167
+ const {
168
+ buildGraph,
169
+ topoSort,
170
+ detectCycles,
171
+ } = require("../lib/planner.js");
149
172
  const graph = buildGraph(config);
150
173
  const order = topoSort(config);
151
174
  const cycles = detectCycles(config);
152
175
  console.log(JSON.stringify({ graph, order, cycles }, null, 2));
153
176
  break;
154
177
  }
155
- const { printDependencyGraph } = require('../lib/planner.js');
156
- printDependencyGraph(config, root);
178
+ const fmtIdx = args.indexOf("--format");
179
+ const planFormat =
180
+ fmtIdx !== -1 && args[fmtIdx + 1] ? args[fmtIdx + 1] : null;
181
+ if (args.includes("--mermaid") || planFormat === "mermaid") {
182
+ const { generateMermaid } = require("../lib/planner.js");
183
+ console.log(generateMermaid(config));
184
+ break;
185
+ }
186
+ if (planFormat === "ascii" || !planFormat) {
187
+ const { printDependencyGraph } = require("../lib/planner.js");
188
+ printDependencyGraph(config, root);
189
+ break;
190
+ }
191
+ console.error(
192
+ `orchard: unknown plan format: ${planFormat}. Supported: ascii, mermaid`,
193
+ );
194
+ process.exit(1);
157
195
  break;
158
196
  }
159
- case 'status': {
197
+ case "status": {
160
198
  if (jsonMode) {
161
- const { getStatusData } = require('../lib/tracker.js');
199
+ const { getStatusData } = require("../lib/tracker.js");
162
200
  const data = getStatusData(config, root);
163
201
  console.log(JSON.stringify(data, null, 2));
164
202
  break;
165
203
  }
166
- const { printStatus } = require('../lib/tracker.js');
204
+ const { printStatus } = require("../lib/tracker.js");
167
205
  printStatus(config, root);
168
206
  break;
169
207
  }
170
- case 'assign': {
208
+ case "assign": {
171
209
  const sprintPath = args[1];
172
210
  const person = args[2];
173
211
  if (!sprintPath || !person) {
174
- console.error('orchard: usage: orchard assign <sprint-path> <person>');
212
+ console.error("orchard: usage: orchard assign <sprint-path> <person>");
175
213
  process.exit(1);
176
214
  }
177
- const { assignSprint } = require('../lib/assignments.js');
215
+ const { assignSprint } = require("../lib/assignments.js");
178
216
  assignSprint(config, root, sprintPath, person);
179
217
  break;
180
218
  }
181
- case 'sync': {
182
- const { syncAll } = require('../lib/sync.js');
219
+ case "sync": {
220
+ const { syncAll } = require("../lib/sync.js");
183
221
  syncAll(config, root);
184
222
  break;
185
223
  }
186
- case 'dashboard': {
187
- const { generateDashboard } = require('../lib/dashboard.js');
188
- const outPath = args[1] || path.join(root, 'orchard-dashboard.html');
224
+ case "dashboard": {
225
+ const { generateDashboard } = require("../lib/dashboard.js");
226
+ const outPath = args[1] || path.join(root, "orchard-dashboard.html");
189
227
  generateDashboard(config, root, outPath);
190
228
  break;
191
229
  }
192
- case 'doctor': {
193
- const { runChecks, printReport } = require('../lib/doctor.js');
230
+ case "conflicts": {
231
+ const {
232
+ detectConflicts,
233
+ filterBySeverity,
234
+ printConflicts,
235
+ } = require("../lib/conflicts.js");
236
+ const sevIdx = args.indexOf("--severity");
237
+ const severity =
238
+ sevIdx !== -1 && args[sevIdx + 1] ? args[sevIdx + 1] : "info";
239
+ if (jsonMode) {
240
+ const all = detectConflicts(config, root);
241
+ const filtered = filterBySeverity(all, severity);
242
+ console.log(
243
+ JSON.stringify(
244
+ { conflicts: filtered, count: filtered.length },
245
+ null,
246
+ 2,
247
+ ),
248
+ );
249
+ break;
250
+ }
251
+ printConflicts(config, root, { severity });
252
+ break;
253
+ }
254
+ case "decompose": {
255
+ const question = args
256
+ .filter((a) => !a.startsWith("--"))
257
+ .slice(1)
258
+ .join(" ");
259
+ if (!question) {
260
+ console.error(
261
+ 'orchard: usage: orchard decompose "<question>" [--apply] [--max <n>]',
262
+ );
263
+ process.exit(1);
264
+ }
265
+ const maxIdx = args.indexOf("--max");
266
+ const maxSprints =
267
+ maxIdx !== -1 && args[maxIdx + 1] ? parseInt(args[maxIdx + 1], 10) : 5;
268
+ if (args.includes("--apply")) {
269
+ const { applyDecomposition } = require("../lib/decompose.js");
270
+ const sprints = applyDecomposition(root, question, { maxSprints });
271
+ console.log(`Created ${sprints.length} sub-sprints for: "${question}"`);
272
+ for (const s of sprints) {
273
+ console.log(` ${s.path}`);
274
+ }
275
+ } else {
276
+ const { printDecomposition } = require("../lib/decompose.js");
277
+ printDecomposition(question, { maxSprints });
278
+ }
279
+ break;
280
+ }
281
+ case "hackathon": {
282
+ const sub = args[1];
283
+ const hack = require("../lib/hackathon.js");
284
+ switch (sub) {
285
+ case "init": {
286
+ const nameIdx = args.indexOf("--name");
287
+ const durIdx = args.indexOf("--duration");
288
+ const name =
289
+ nameIdx !== -1 && args[nameIdx + 1] ? args[nameIdx + 1] : undefined;
290
+ const duration =
291
+ durIdx !== -1 && args[durIdx + 1]
292
+ ? parseInt(args[durIdx + 1], 10)
293
+ : undefined;
294
+ const h = hack.initHackathon(root, { name, duration });
295
+ console.log(`Hackathon "${h.name}" started — ends at ${h.endTime}`);
296
+ break;
297
+ }
298
+ case "team": {
299
+ const teamName = args[2];
300
+ const qIdx = args.indexOf("--question");
301
+ const question =
302
+ qIdx !== -1 ? args.slice(qIdx + 1).join(" ") : undefined;
303
+ if (!teamName) {
304
+ console.error(
305
+ 'orchard: usage: orchard hackathon team <name> [--question "..."]',
306
+ );
307
+ process.exit(1);
308
+ }
309
+ const result = hack.addTeam(root, teamName, question);
310
+ console.log(
311
+ `Team "${result.teamName}" added — sprint: ${result.sprintPath}`,
312
+ );
313
+ break;
314
+ }
315
+ case "end": {
316
+ const board = hack.endHackathon(root);
317
+ console.log("Hackathon ended! Final leaderboard:");
318
+ for (let i = 0; i < board.length; i++) {
319
+ const t = board[i];
320
+ console.log(
321
+ ` ${i + 1}. ${t.team} — ${t.score}pts (${t.claimCount} claims)`,
322
+ );
323
+ }
324
+ break;
325
+ }
326
+ default: {
327
+ if (jsonMode) {
328
+ const timer = hack.timerStatus(root);
329
+ const board = hack.leaderboard(root);
330
+ console.log(JSON.stringify({ timer, leaderboard: board }, null, 2));
331
+ } else {
332
+ hack.printHackathon(root);
333
+ }
334
+ break;
335
+ }
336
+ }
337
+ break;
338
+ }
339
+ case "doctor": {
340
+ const { runChecks, printReport } = require("../lib/doctor.js");
194
341
  const result = runChecks(root || process.cwd());
195
342
  if (jsonMode) {
196
343
  console.log(JSON.stringify(result, null, 2));
@@ -1,7 +1,7 @@
1
- 'use strict';
1
+ "use strict";
2
2
 
3
- const fs = require('node:fs');
4
- const path = require('node:path');
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
5
 
6
6
  /**
7
7
  * Assign a person to a sprint. Updates orchard.json.
@@ -11,7 +11,7 @@ function assignSprint(config, root, sprintPath, person) {
11
11
 
12
12
  if (!sprint) {
13
13
  console.error(`Sprint not found: ${sprintPath}`);
14
- console.error('Available sprints:');
14
+ console.error("Available sprints:");
15
15
  for (const s of config.sprints || []) {
16
16
  console.error(` ${s.path}`);
17
17
  }
@@ -21,13 +21,15 @@ function assignSprint(config, root, sprintPath, person) {
21
21
  const prev = sprint.assigned_to;
22
22
  sprint.assigned_to = person;
23
23
 
24
- const configPath = path.join(root, 'orchard.json');
25
- const tmp = configPath + '.tmp.' + process.pid;
26
- fs.writeFileSync(tmp, JSON.stringify(config, null, 2) + '\n');
24
+ const configPath = path.join(root, "orchard.json");
25
+ const tmp = configPath + ".tmp." + process.pid;
26
+ fs.writeFileSync(tmp, JSON.stringify(config, null, 2) + "\n");
27
27
  fs.renameSync(tmp, configPath);
28
28
 
29
29
  if (prev) {
30
- console.log(`Reassigned ${path.basename(sprintPath)}: ${prev} -> ${person}`);
30
+ console.log(
31
+ `Reassigned ${path.basename(sprintPath)}: ${prev} -> ${person}`,
32
+ );
31
33
  } else {
32
34
  console.log(`Assigned ${path.basename(sprintPath)} to ${person}`);
33
35
  }
@@ -40,7 +42,7 @@ function getWorkload(config) {
40
42
  const workload = new Map();
41
43
 
42
44
  for (const sprint of config.sprints || []) {
43
- const person = sprint.assigned_to || 'unassigned';
45
+ const person = sprint.assigned_to || "unassigned";
44
46
  if (!workload.has(person)) {
45
47
  workload.set(person, []);
46
48
  }
@@ -56,20 +58,20 @@ function getWorkload(config) {
56
58
  function printWorkload(config) {
57
59
  const workload = getWorkload(config);
58
60
 
59
- console.log('');
60
- console.log(' Workload Distribution');
61
- console.log(' ' + '-'.repeat(40));
61
+ console.log("");
62
+ console.log(" Workload Distribution");
63
+ console.log(" " + "-".repeat(40));
62
64
 
63
65
  for (const [person, sprints] of workload) {
64
- const active = sprints.filter((s) => s.status !== 'done').length;
66
+ const active = sprints.filter((s) => s.status !== "done").length;
65
67
  console.log(` ${person}: ${sprints.length} sprints (${active} active)`);
66
68
  for (const s of sprints) {
67
- const status = s.status || 'unknown';
69
+ const status = s.status || "unknown";
68
70
  console.log(` - ${path.basename(s.path)} [${status}]`);
69
71
  }
70
72
  }
71
73
 
72
- console.log('');
74
+ console.log("");
73
75
  }
74
76
 
75
77
  /**
@@ -80,8 +82,8 @@ function findOverloaded(config, maxLoad = 3) {
80
82
  const overloaded = [];
81
83
 
82
84
  for (const [person, sprints] of workload) {
83
- if (person === 'unassigned') continue;
84
- const active = sprints.filter((s) => s.status !== 'done').length;
85
+ if (person === "unassigned") continue;
86
+ const active = sprints.filter((s) => s.status !== "done").length;
85
87
  if (active > maxLoad) {
86
88
  overloaded.push({ person, active, sprints });
87
89
  }