@grainulation/orchard 1.0.1 → 1.0.4
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 +7 -1
- package/README.md +21 -20
- package/bin/orchard.js +238 -80
- package/lib/assignments.js +19 -17
- package/lib/conflicts.js +177 -29
- package/lib/dashboard.js +100 -47
- package/lib/decompose.js +268 -0
- package/lib/doctor.js +48 -32
- package/lib/emit.js +72 -0
- package/lib/export.js +72 -44
- package/lib/farmer.js +126 -42
- package/lib/hackathon.js +349 -0
- package/lib/planner.js +150 -21
- package/lib/server.js +395 -165
- package/lib/sync.js +31 -25
- package/lib/tracker.js +52 -40
- package/package.json +7 -3
package/lib/sync.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
|
|
3
|
-
const fs = require(
|
|
4
|
-
const path = require(
|
|
5
|
-
const { readSprintState } = require(
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
const { readSprintState } = require("./tracker.js");
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Sync all sprint states from their directories into orchard.json.
|
|
@@ -12,9 +12,9 @@ function syncAll(config, root) {
|
|
|
12
12
|
const sprints = config.sprints || [];
|
|
13
13
|
let updated = 0;
|
|
14
14
|
|
|
15
|
-
console.log(
|
|
16
|
-
console.log(
|
|
17
|
-
console.log(
|
|
15
|
+
console.log("");
|
|
16
|
+
console.log(" Syncing sprint states...");
|
|
17
|
+
console.log("");
|
|
18
18
|
|
|
19
19
|
for (const sprint of sprints) {
|
|
20
20
|
const state = readSprintState(sprint.path, root);
|
|
@@ -30,25 +30,29 @@ function syncAll(config, root) {
|
|
|
30
30
|
if (oldStatus !== inferred) {
|
|
31
31
|
sprint.status = inferred;
|
|
32
32
|
updated++;
|
|
33
|
-
console.log(
|
|
33
|
+
console.log(
|
|
34
|
+
` [~] ${path.basename(sprint.path)}: ${oldStatus || "unknown"} -> ${inferred}`,
|
|
35
|
+
);
|
|
34
36
|
} else {
|
|
35
|
-
console.log(
|
|
37
|
+
console.log(
|
|
38
|
+
` [=] ${path.basename(sprint.path)}: ${inferred} (${state.claimsCount} claims)`,
|
|
39
|
+
);
|
|
36
40
|
}
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
if (updated > 0) {
|
|
40
|
-
const configPath = path.join(root,
|
|
41
|
-
const tmp = configPath +
|
|
42
|
-
fs.writeFileSync(tmp, JSON.stringify(config, null, 2) +
|
|
44
|
+
const configPath = path.join(root, "orchard.json");
|
|
45
|
+
const tmp = configPath + ".tmp." + process.pid;
|
|
46
|
+
fs.writeFileSync(tmp, JSON.stringify(config, null, 2) + "\n");
|
|
43
47
|
fs.renameSync(tmp, configPath);
|
|
44
|
-
console.log(
|
|
48
|
+
console.log("");
|
|
45
49
|
console.log(` Updated ${updated} sprint(s) in orchard.json.`);
|
|
46
50
|
} else {
|
|
47
|
-
console.log(
|
|
48
|
-
console.log(
|
|
51
|
+
console.log("");
|
|
52
|
+
console.log(" All sprints up to date.");
|
|
49
53
|
}
|
|
50
54
|
|
|
51
|
-
console.log(
|
|
55
|
+
console.log("");
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
/**
|
|
@@ -56,15 +60,15 @@ function syncAll(config, root) {
|
|
|
56
60
|
*/
|
|
57
61
|
function inferStatus(sprint, state) {
|
|
58
62
|
// If manually set to done, keep it
|
|
59
|
-
if (sprint.status ===
|
|
63
|
+
if (sprint.status === "done") return "done";
|
|
60
64
|
|
|
61
65
|
// If manually blocked, keep it
|
|
62
|
-
if (sprint.status ===
|
|
66
|
+
if (sprint.status === "blocked") return "blocked";
|
|
63
67
|
|
|
64
|
-
if (!state.exists) return
|
|
65
|
-
if (state.claimsCount === 0) return
|
|
66
|
-
if (state.hasCompilation) return
|
|
67
|
-
return
|
|
68
|
+
if (!state.exists) return "not-found";
|
|
69
|
+
if (state.claimsCount === 0) return "not-started";
|
|
70
|
+
if (state.hasCompilation) return "compiled";
|
|
71
|
+
return "active";
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
/**
|
|
@@ -75,15 +79,17 @@ function findReady(config, root) {
|
|
|
75
79
|
const statuses = new Map();
|
|
76
80
|
|
|
77
81
|
for (const s of sprints) {
|
|
78
|
-
statuses.set(s.path, s.status ||
|
|
82
|
+
statuses.set(s.path, s.status || "unknown");
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
const ready = [];
|
|
82
86
|
for (const s of sprints) {
|
|
83
|
-
if (s.status ===
|
|
87
|
+
if (s.status === "done" || s.status === "active") continue;
|
|
84
88
|
|
|
85
89
|
const deps = s.depends_on || [];
|
|
86
|
-
const allDone = deps.every(
|
|
90
|
+
const allDone = deps.every(
|
|
91
|
+
(d) => statuses.get(d) === "done" || statuses.get(d) === "compiled",
|
|
92
|
+
);
|
|
87
93
|
|
|
88
94
|
if (allDone) {
|
|
89
95
|
ready.push(s);
|
package/lib/tracker.js
CHANGED
|
@@ -1,31 +1,36 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
|
|
3
|
-
const fs = require(
|
|
4
|
-
const path = require(
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const path = require("node:path");
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Read sprint status from its directory.
|
|
8
8
|
* Looks for claims.json and compilation.json to determine state.
|
|
9
9
|
*/
|
|
10
10
|
function readSprintState(sprintPath, root) {
|
|
11
|
-
const absPath = path.isAbsolute(sprintPath)
|
|
11
|
+
const absPath = path.isAbsolute(sprintPath)
|
|
12
|
+
? sprintPath
|
|
13
|
+
: path.join(root, sprintPath);
|
|
12
14
|
const state = {
|
|
13
15
|
exists: false,
|
|
14
16
|
claimsCount: 0,
|
|
15
17
|
hasCompilation: false,
|
|
16
18
|
lastModified: null,
|
|
17
|
-
status:
|
|
19
|
+
status: "unknown",
|
|
18
20
|
};
|
|
19
21
|
|
|
20
22
|
if (!fs.existsSync(absPath)) return state;
|
|
21
23
|
state.exists = true;
|
|
22
24
|
|
|
23
|
-
const claimsPath = path.join(absPath,
|
|
25
|
+
const claimsPath = path.join(absPath, "claims.json");
|
|
24
26
|
if (fs.existsSync(claimsPath)) {
|
|
25
27
|
try {
|
|
26
|
-
const claims = JSON.parse(fs.readFileSync(claimsPath,
|
|
27
|
-
state.claimsCount = Array.isArray(claims)
|
|
28
|
-
|
|
28
|
+
const claims = JSON.parse(fs.readFileSync(claimsPath, "utf8"));
|
|
29
|
+
state.claimsCount = Array.isArray(claims)
|
|
30
|
+
? claims.length
|
|
31
|
+
: claims.claims
|
|
32
|
+
? claims.claims.length
|
|
33
|
+
: 0;
|
|
29
34
|
const stat = fs.statSync(claimsPath);
|
|
30
35
|
state.lastModified = stat.mtime;
|
|
31
36
|
} catch {
|
|
@@ -33,16 +38,16 @@ function readSprintState(sprintPath, root) {
|
|
|
33
38
|
}
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
const compilationPath = path.join(absPath,
|
|
41
|
+
const compilationPath = path.join(absPath, "compilation.json");
|
|
37
42
|
state.hasCompilation = fs.existsSync(compilationPath);
|
|
38
43
|
|
|
39
44
|
// Infer status
|
|
40
45
|
if (state.claimsCount === 0) {
|
|
41
|
-
state.status =
|
|
46
|
+
state.status = "not-started";
|
|
42
47
|
} else if (state.hasCompilation) {
|
|
43
|
-
state.status =
|
|
48
|
+
state.status = "compiled";
|
|
44
49
|
} else {
|
|
45
|
-
state.status =
|
|
50
|
+
state.status = "in-progress";
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
return state;
|
|
@@ -55,52 +60,59 @@ function printStatus(config, root) {
|
|
|
55
60
|
const sprints = config.sprints || [];
|
|
56
61
|
|
|
57
62
|
if (sprints.length === 0) {
|
|
58
|
-
console.log(
|
|
63
|
+
console.log("No sprints configured. Add sprints to orchard.json.");
|
|
59
64
|
return;
|
|
60
65
|
}
|
|
61
66
|
|
|
62
|
-
const active = sprints.filter(
|
|
63
|
-
|
|
67
|
+
const active = sprints.filter(
|
|
68
|
+
(s) => s.status === "active" || !s.status,
|
|
69
|
+
).length;
|
|
70
|
+
const done = sprints.filter((s) => s.status === "done").length;
|
|
64
71
|
|
|
65
|
-
console.log(
|
|
66
|
-
console.log(` ${sprints.length} sprints tracked. ${active} active, ${done} done.`);
|
|
67
|
-
console.log(' ' + '-'.repeat(70));
|
|
72
|
+
console.log("");
|
|
68
73
|
console.log(
|
|
69
|
-
|
|
70
|
-
'Sprint'.padEnd(20) +
|
|
71
|
-
'Status'.padEnd(14) +
|
|
72
|
-
'Claims'.padEnd(10) +
|
|
73
|
-
'Assigned'.padEnd(16) +
|
|
74
|
-
'Deadline'
|
|
74
|
+
` ${sprints.length} sprints tracked. ${active} active, ${done} done.`,
|
|
75
75
|
);
|
|
76
|
-
console.log(
|
|
76
|
+
console.log(" " + "-".repeat(70));
|
|
77
|
+
console.log(
|
|
78
|
+
" " +
|
|
79
|
+
"Sprint".padEnd(20) +
|
|
80
|
+
"Status".padEnd(14) +
|
|
81
|
+
"Claims".padEnd(10) +
|
|
82
|
+
"Assigned".padEnd(16) +
|
|
83
|
+
"Deadline",
|
|
84
|
+
);
|
|
85
|
+
console.log(" " + "-".repeat(70));
|
|
77
86
|
|
|
78
87
|
for (const sprint of sprints) {
|
|
79
88
|
const state = readSprintState(sprint.path, root);
|
|
80
89
|
const name = path.basename(sprint.path).substring(0, 18);
|
|
81
90
|
const status = sprint.status || state.status;
|
|
82
91
|
const claims = state.claimsCount.toString();
|
|
83
|
-
const assignee = (sprint.assigned_to ||
|
|
84
|
-
const deadline = sprint.deadline ||
|
|
92
|
+
const assignee = (sprint.assigned_to || "-").substring(0, 14);
|
|
93
|
+
const deadline = sprint.deadline || "-";
|
|
85
94
|
|
|
86
95
|
const statusDisplay =
|
|
87
|
-
status ===
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
96
|
+
status === "active"
|
|
97
|
+
? "* active"
|
|
98
|
+
: status === "done"
|
|
99
|
+
? "x done"
|
|
100
|
+
: status === "blocked"
|
|
101
|
+
? "! blocked"
|
|
102
|
+
: ` ${status}`;
|
|
91
103
|
|
|
92
104
|
console.log(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
105
|
+
" " +
|
|
106
|
+
name.padEnd(20) +
|
|
107
|
+
statusDisplay.padEnd(14) +
|
|
108
|
+
claims.padEnd(10) +
|
|
109
|
+
assignee.padEnd(16) +
|
|
110
|
+
deadline,
|
|
99
111
|
);
|
|
100
112
|
}
|
|
101
113
|
|
|
102
|
-
console.log(
|
|
103
|
-
console.log(
|
|
114
|
+
console.log(" " + "-".repeat(70));
|
|
115
|
+
console.log("");
|
|
104
116
|
}
|
|
105
117
|
|
|
106
118
|
/**
|
package/package.json
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grainulation/orchard",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Multi-sprint research orchestrator
|
|
3
|
+
"version": "1.0.4",
|
|
4
|
+
"description": "Multi-sprint research orchestrator — coordinate parallel research across teams",
|
|
5
5
|
"main": "lib/planner.js",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./lib/planner.js",
|
|
8
8
|
"./server": "./lib/server.js",
|
|
9
9
|
"./sync": "./lib/sync.js",
|
|
10
10
|
"./tracker": "./lib/tracker.js",
|
|
11
|
+
"./conflicts": "./lib/conflicts.js",
|
|
12
|
+
"./hackathon": "./lib/hackathon.js",
|
|
13
|
+
"./decompose": "./lib/decompose.js",
|
|
14
|
+
"./emit": "./lib/emit.js",
|
|
11
15
|
"./doctor": "./lib/doctor.js",
|
|
12
16
|
"./package.json": "./package.json"
|
|
13
17
|
},
|
|
@@ -47,6 +51,6 @@
|
|
|
47
51
|
},
|
|
48
52
|
"homepage": "https://orchard.grainulation.com",
|
|
49
53
|
"engines": {
|
|
50
|
-
"node": ">=
|
|
54
|
+
"node": ">=20"
|
|
51
55
|
}
|
|
52
56
|
}
|