@grainulation/silo 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/CODE_OF_CONDUCT.md +25 -0
- package/CONTRIBUTING.md +103 -0
- package/README.md +67 -59
- package/bin/silo.js +212 -86
- package/lib/analytics.js +26 -11
- package/lib/confluence.js +343 -0
- package/lib/graph.js +414 -0
- package/lib/import-export.js +29 -24
- package/lib/index.js +15 -9
- package/lib/packs.js +60 -36
- package/lib/search.js +24 -16
- package/lib/serve-mcp.js +391 -95
- package/lib/server.js +205 -110
- package/lib/store.js +34 -18
- package/lib/templates.js +28 -17
- package/package.json +7 -3
- package/packs/adr.json +219 -0
- package/packs/api-design.json +67 -14
- package/packs/architecture-decision.json +152 -0
- package/packs/architecture.json +45 -9
- package/packs/ci-cd.json +51 -11
- package/packs/compliance.json +70 -14
- package/packs/data-engineering.json +57 -12
- package/packs/frontend.json +56 -12
- package/packs/hackathon-best-ai.json +179 -0
- package/packs/hackathon-business-impact.json +180 -0
- package/packs/hackathon-innovation.json +210 -0
- package/packs/hackathon-most-innovative.json +179 -0
- package/packs/hackathon-most-rigorous.json +179 -0
- package/packs/hackathon-sprint-boost.json +173 -0
- package/packs/incident-postmortem.json +219 -0
- package/packs/migration.json +45 -9
- package/packs/observability.json +57 -12
- package/packs/security.json +61 -13
- package/packs/team-process.json +64 -13
- package/packs/testing.json +20 -4
- package/packs/vendor-eval.json +219 -0
- package/packs/vendor-evaluation.json +148 -0
package/bin/silo.js
CHANGED
|
@@ -16,24 +16,29 @@
|
|
|
16
16
|
* silo serve-mcp Start the MCP server on stdio
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
const { Store } = require(
|
|
20
|
-
const { Search } = require(
|
|
21
|
-
const { ImportExport } = require(
|
|
22
|
-
const { Templates } = require(
|
|
23
|
-
const { Packs } = require(
|
|
19
|
+
const { Store } = require("../lib/store.js");
|
|
20
|
+
const { Search } = require("../lib/search.js");
|
|
21
|
+
const { ImportExport } = require("../lib/import-export.js");
|
|
22
|
+
const { Templates } = require("../lib/templates.js");
|
|
23
|
+
const { Packs } = require("../lib/packs.js");
|
|
24
|
+
const { Graph } = require("../lib/graph.js");
|
|
24
25
|
|
|
25
26
|
// ── --version / -v (before verbose check) ──
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
process.
|
|
27
|
+
if (
|
|
28
|
+
process.argv.includes("--version") ||
|
|
29
|
+
(process.argv.includes("-v") && process.argv.length === 3)
|
|
30
|
+
) {
|
|
31
|
+
const pkg = require("../package.json");
|
|
32
|
+
process.stdout.write(pkg.version + "\n");
|
|
29
33
|
process.exit(0);
|
|
30
34
|
}
|
|
31
35
|
|
|
32
|
-
const verbose =
|
|
36
|
+
const verbose =
|
|
37
|
+
process.argv.includes("--verbose") || process.argv.includes("-v");
|
|
33
38
|
function vlog(...a) {
|
|
34
39
|
if (!verbose) return;
|
|
35
40
|
const ts = new Date().toISOString();
|
|
36
|
-
process.stderr.write(`[${ts}] silo: ${a.join(
|
|
41
|
+
process.stderr.write(`[${ts}] silo: ${a.join(" ")}\n`);
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
const store = new Store();
|
|
@@ -45,9 +50,9 @@ const packs = new Packs(store);
|
|
|
45
50
|
const args = process.argv.slice(2);
|
|
46
51
|
const command = args[0];
|
|
47
52
|
|
|
48
|
-
vlog(
|
|
53
|
+
vlog("startup", `command=${command || "(none)"}`, `cwd=${process.cwd()}`);
|
|
49
54
|
|
|
50
|
-
const jsonMode = args.includes(
|
|
55
|
+
const jsonMode = args.includes("--json");
|
|
51
56
|
|
|
52
57
|
function flag(name) {
|
|
53
58
|
const idx = args.indexOf(`--${name}`);
|
|
@@ -60,17 +65,17 @@ function flagList(name) {
|
|
|
60
65
|
if (idx === -1) return [];
|
|
61
66
|
const values = [];
|
|
62
67
|
for (let i = idx + 1; i < args.length; i++) {
|
|
63
|
-
if (args[i].startsWith(
|
|
68
|
+
if (args[i].startsWith("--")) break;
|
|
64
69
|
values.push(args[i]);
|
|
65
70
|
}
|
|
66
71
|
return values;
|
|
67
72
|
}
|
|
68
73
|
|
|
69
74
|
function print(obj) {
|
|
70
|
-
if (typeof obj ===
|
|
71
|
-
process.stdout.write(obj +
|
|
75
|
+
if (typeof obj === "string") {
|
|
76
|
+
process.stdout.write(obj + "\n");
|
|
72
77
|
} else {
|
|
73
|
-
process.stdout.write(JSON.stringify(obj, null, 2) +
|
|
78
|
+
process.stdout.write(JSON.stringify(obj, null, 2) + "\n");
|
|
74
79
|
}
|
|
75
80
|
}
|
|
76
81
|
|
|
@@ -86,6 +91,7 @@ Commands:
|
|
|
86
91
|
packs List available knowledge packs
|
|
87
92
|
templates List available sprint templates
|
|
88
93
|
install <file> Install a pack from a file
|
|
94
|
+
graph [stats|related|topic|clusters|export] Knowledge graph operations
|
|
89
95
|
analyze Run cross-library analytics (requires harvest)
|
|
90
96
|
serve [--port 9095] Start the knowledge browser UI
|
|
91
97
|
serve-mcp Start the MCP server on stdio
|
|
@@ -100,135 +106,160 @@ Examples:
|
|
|
100
106
|
|
|
101
107
|
try {
|
|
102
108
|
switch (command) {
|
|
103
|
-
case
|
|
109
|
+
case "list": {
|
|
104
110
|
const collections = store.list();
|
|
105
111
|
if (jsonMode) {
|
|
106
112
|
print(JSON.stringify(collections));
|
|
107
113
|
break;
|
|
108
114
|
}
|
|
109
115
|
if (collections.length === 0) {
|
|
110
|
-
print(
|
|
116
|
+
print(
|
|
117
|
+
'No stored collections. Use "silo store" to save claims or "silo packs" to see built-in packs.',
|
|
118
|
+
);
|
|
111
119
|
} else {
|
|
112
|
-
print(
|
|
120
|
+
print("Stored collections:\n");
|
|
113
121
|
for (const c of collections) {
|
|
114
|
-
print(
|
|
122
|
+
print(
|
|
123
|
+
` ${c.id} (${c.type || "claims"}, ${c.claimCount} claims) ${c.storedAt || ""}`,
|
|
124
|
+
);
|
|
115
125
|
}
|
|
116
126
|
}
|
|
117
127
|
break;
|
|
118
128
|
}
|
|
119
129
|
|
|
120
|
-
case
|
|
130
|
+
case "pull": {
|
|
121
131
|
const source = args[1];
|
|
122
|
-
const into = flag(
|
|
132
|
+
const into = flag("into");
|
|
123
133
|
if (!source || !into) {
|
|
124
|
-
print(
|
|
134
|
+
print("Usage: silo pull <pack> --into <file>");
|
|
125
135
|
process.exit(1);
|
|
126
136
|
}
|
|
127
|
-
const dryRun = args.includes(
|
|
128
|
-
const types = flagList(
|
|
129
|
-
const filterIds = flag(
|
|
130
|
-
const ids = filterIds
|
|
131
|
-
|
|
137
|
+
const dryRun = args.includes("--dry-run");
|
|
138
|
+
const types = flagList("type");
|
|
139
|
+
const filterIds = flag("filter");
|
|
140
|
+
const ids = filterIds
|
|
141
|
+
? filterIds.split(",").map((s) => s.trim())
|
|
142
|
+
: undefined;
|
|
143
|
+
const result = io.pull(source, into, {
|
|
144
|
+
types: types.length ? types : undefined,
|
|
145
|
+
ids,
|
|
146
|
+
dryRun,
|
|
147
|
+
});
|
|
132
148
|
if (dryRun) {
|
|
133
149
|
print(`Dry run: would import ${result.wouldImport} claims`);
|
|
134
150
|
print(result.claims);
|
|
135
151
|
} else {
|
|
136
|
-
print(
|
|
152
|
+
print(
|
|
153
|
+
`Imported ${result.imported} claims into ${into} (${result.skippedDuplicates} duplicates skipped, ${result.totalClaims} total)`,
|
|
154
|
+
);
|
|
137
155
|
}
|
|
138
156
|
break;
|
|
139
157
|
}
|
|
140
158
|
|
|
141
|
-
case
|
|
159
|
+
case "store": {
|
|
142
160
|
const name = args[1];
|
|
143
|
-
const from = flag(
|
|
161
|
+
const from = flag("from");
|
|
144
162
|
if (!name || !from) {
|
|
145
|
-
print(
|
|
163
|
+
print("Usage: silo store <name> --from <file>");
|
|
146
164
|
process.exit(1);
|
|
147
165
|
}
|
|
148
166
|
const result = io.push(from, name);
|
|
149
|
-
print(
|
|
167
|
+
print(
|
|
168
|
+
`Stored "${name}" (${result.claimCount} claims, hash: ${result.hash})`,
|
|
169
|
+
);
|
|
150
170
|
break;
|
|
151
171
|
}
|
|
152
172
|
|
|
153
|
-
case
|
|
154
|
-
const query = args
|
|
173
|
+
case "search": {
|
|
174
|
+
const query = args
|
|
175
|
+
.slice(1)
|
|
176
|
+
.filter((a) => !a.startsWith("--"))
|
|
177
|
+
.join(" ");
|
|
155
178
|
if (!query) {
|
|
156
|
-
print(
|
|
179
|
+
print("Usage: silo search <query> [--type <type>] [--evidence <tier>]");
|
|
157
180
|
process.exit(1);
|
|
158
181
|
}
|
|
159
|
-
const type = flag(
|
|
160
|
-
const tier = flag(
|
|
182
|
+
const type = flag("type");
|
|
183
|
+
const tier = flag("tier");
|
|
161
184
|
const results = search.query(query, { type, tier });
|
|
162
185
|
if (jsonMode) {
|
|
163
186
|
print(JSON.stringify(results));
|
|
164
187
|
break;
|
|
165
188
|
}
|
|
166
189
|
if (results.length === 0) {
|
|
167
|
-
print(
|
|
190
|
+
print("No matches found.");
|
|
168
191
|
} else {
|
|
169
192
|
print(`${results.length} result(s):\n`);
|
|
170
193
|
for (const r of results) {
|
|
171
|
-
const text = r.claim.content || r.claim.text ||
|
|
172
|
-
const tier = r.claim.evidence || r.claim.tier ||
|
|
173
|
-
print(
|
|
194
|
+
const text = r.claim.content || r.claim.text || "";
|
|
195
|
+
const tier = r.claim.evidence || r.claim.tier || "unknown";
|
|
196
|
+
print(
|
|
197
|
+
` [${r.claim.id}] (${r.claim.type}, ${tier}) ${text.slice(0, 120)}${text.length > 120 ? "..." : ""}`,
|
|
198
|
+
);
|
|
174
199
|
print(` from: ${r.collection} score: ${r.score}\n`);
|
|
175
200
|
}
|
|
176
201
|
}
|
|
177
202
|
break;
|
|
178
203
|
}
|
|
179
204
|
|
|
180
|
-
case
|
|
205
|
+
case "publish": {
|
|
181
206
|
const name = args[1];
|
|
182
|
-
const collections = flagList(
|
|
207
|
+
const collections = flagList("collections");
|
|
183
208
|
if (!name || collections.length === 0) {
|
|
184
|
-
print(
|
|
209
|
+
print("Usage: silo publish <name> --collections <id1> <id2> ...");
|
|
185
210
|
process.exit(1);
|
|
186
211
|
}
|
|
187
|
-
const desc = flag(
|
|
212
|
+
const desc = flag("description") || "";
|
|
188
213
|
const result = packs.bundle(name, collections, { description: desc });
|
|
189
|
-
print(
|
|
214
|
+
print(
|
|
215
|
+
`Published pack "${name}" (${result.claimCount} claims) -> ${result.path}`,
|
|
216
|
+
);
|
|
190
217
|
break;
|
|
191
218
|
}
|
|
192
219
|
|
|
193
|
-
case
|
|
220
|
+
case "packs": {
|
|
194
221
|
const allPacks = packs.list();
|
|
195
222
|
if (jsonMode) {
|
|
196
223
|
print(JSON.stringify(allPacks));
|
|
197
224
|
break;
|
|
198
225
|
}
|
|
199
226
|
if (allPacks.length === 0) {
|
|
200
|
-
print(
|
|
227
|
+
print("No packs available.");
|
|
201
228
|
} else {
|
|
202
|
-
print(
|
|
229
|
+
print("Available packs:\n");
|
|
203
230
|
for (const p of allPacks) {
|
|
204
|
-
print(
|
|
231
|
+
print(
|
|
232
|
+
` ${p.id} ${p.name} (${p.claimCount} claims, v${p.version}, ${p.source})`,
|
|
233
|
+
);
|
|
205
234
|
if (p.description) print(` ${p.description.slice(0, 100)}`);
|
|
206
|
-
print(
|
|
235
|
+
print("");
|
|
207
236
|
}
|
|
208
237
|
}
|
|
209
238
|
break;
|
|
210
239
|
}
|
|
211
240
|
|
|
212
|
-
case
|
|
241
|
+
case "templates": {
|
|
213
242
|
const allTemplates = templates.list();
|
|
214
243
|
if (jsonMode) {
|
|
215
244
|
print(JSON.stringify(allTemplates));
|
|
216
245
|
break;
|
|
217
246
|
}
|
|
218
247
|
if (allTemplates.length === 0) {
|
|
219
|
-
print(
|
|
248
|
+
print("No templates saved yet. Use the Templates API to create them.");
|
|
220
249
|
} else {
|
|
221
250
|
for (const t of allTemplates) {
|
|
222
|
-
print(
|
|
251
|
+
print(
|
|
252
|
+
` ${t.id} "${t.question || t.name}" (${t.seedClaims} seed claims) [${t.tags.join(", ")}]`,
|
|
253
|
+
);
|
|
223
254
|
}
|
|
224
255
|
}
|
|
225
256
|
break;
|
|
226
257
|
}
|
|
227
258
|
|
|
228
|
-
case
|
|
259
|
+
case "install": {
|
|
229
260
|
const filePath = args[1];
|
|
230
261
|
if (!filePath) {
|
|
231
|
-
print(
|
|
262
|
+
print("Usage: silo install <pack-file.json>");
|
|
232
263
|
process.exit(1);
|
|
233
264
|
}
|
|
234
265
|
const result = packs.install(filePath);
|
|
@@ -236,41 +267,45 @@ try {
|
|
|
236
267
|
break;
|
|
237
268
|
}
|
|
238
269
|
|
|
239
|
-
case
|
|
240
|
-
const { analyzeLibrary } = require(
|
|
270
|
+
case "analyze": {
|
|
271
|
+
const { analyzeLibrary } = require("../lib/analytics.js");
|
|
241
272
|
const result = analyzeLibrary(store);
|
|
242
273
|
if (jsonMode) {
|
|
243
274
|
print(JSON.stringify(result));
|
|
244
275
|
break;
|
|
245
276
|
}
|
|
246
277
|
if (!result.available) {
|
|
247
|
-
print(
|
|
278
|
+
print(
|
|
279
|
+
`silo analyze: harvest not found.\n\nThe analyze command requires @grainulation/harvest in a sibling directory.\nExpected locations:\n ../harvest/lib/analyzer.js\n\nInstall harvest alongside silo and try again.`,
|
|
280
|
+
);
|
|
248
281
|
process.exit(1);
|
|
249
282
|
}
|
|
250
283
|
if (!result.analysis) {
|
|
251
|
-
print(`silo analyze: ${result.reason ||
|
|
284
|
+
print(`silo analyze: ${result.reason || "no data to analyze"}`);
|
|
252
285
|
break;
|
|
253
286
|
}
|
|
254
|
-
print(
|
|
287
|
+
print(
|
|
288
|
+
`Cross-library analytics (${result.collectionCount} collection(s)):\n`,
|
|
289
|
+
);
|
|
255
290
|
const analysis = result.analysis;
|
|
256
291
|
if (analysis.typeDistribution) {
|
|
257
|
-
print(
|
|
292
|
+
print("Type distribution:");
|
|
258
293
|
for (const [type, count] of Object.entries(analysis.typeDistribution)) {
|
|
259
294
|
print(` ${type}: ${count}`);
|
|
260
295
|
}
|
|
261
|
-
print(
|
|
296
|
+
print("");
|
|
262
297
|
}
|
|
263
298
|
if (analysis.evidenceQuality) {
|
|
264
|
-
print(
|
|
299
|
+
print("Evidence quality:");
|
|
265
300
|
for (const [tier, count] of Object.entries(analysis.evidenceQuality)) {
|
|
266
301
|
print(` ${tier}: ${count}`);
|
|
267
302
|
}
|
|
268
|
-
print(
|
|
303
|
+
print("");
|
|
269
304
|
}
|
|
270
305
|
// Print any other top-level keys from the analysis
|
|
271
306
|
for (const [key, value] of Object.entries(analysis)) {
|
|
272
|
-
if (key ===
|
|
273
|
-
if (typeof value ===
|
|
307
|
+
if (key === "typeDistribution" || key === "evidenceQuality") continue;
|
|
308
|
+
if (typeof value === "object") {
|
|
274
309
|
print(`${key}: ${JSON.stringify(value, null, 2)}`);
|
|
275
310
|
} else {
|
|
276
311
|
print(`${key}: ${value}`);
|
|
@@ -279,39 +314,130 @@ try {
|
|
|
279
314
|
break;
|
|
280
315
|
}
|
|
281
316
|
|
|
282
|
-
case
|
|
283
|
-
const
|
|
317
|
+
case "graph": {
|
|
318
|
+
const graph = new Graph(store);
|
|
319
|
+
const action = args[1] || "stats";
|
|
320
|
+
const stats = graph.build();
|
|
321
|
+
|
|
322
|
+
if (action === "stats") {
|
|
323
|
+
if (jsonMode) {
|
|
324
|
+
print(JSON.stringify(stats));
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
print(
|
|
328
|
+
`Knowledge graph: ${stats.nodes} nodes, ${stats.edges} edges, ${stats.sources} sources, ${stats.topics} topics, ${stats.tags} tags`,
|
|
329
|
+
);
|
|
330
|
+
} else if (action === "related") {
|
|
331
|
+
const claimId = args[2];
|
|
332
|
+
if (!claimId) {
|
|
333
|
+
print("Usage: silo graph related <claimId>");
|
|
334
|
+
process.exit(1);
|
|
335
|
+
}
|
|
336
|
+
const results = graph.related(claimId, { limit: 20 });
|
|
337
|
+
if (jsonMode) {
|
|
338
|
+
print(JSON.stringify(results));
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
if (results.length === 0) {
|
|
342
|
+
print("No related claims found.");
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
print(`${results.length} related claim(s):\n`);
|
|
346
|
+
for (const r of results) {
|
|
347
|
+
print(
|
|
348
|
+
` [${r.claim.id}] (${r.relation}, w=${r.weight}) ${(r.claim.content || "").slice(0, 100)}`,
|
|
349
|
+
);
|
|
350
|
+
print(` from: ${r.source}\n`);
|
|
351
|
+
}
|
|
352
|
+
} else if (action === "topic") {
|
|
353
|
+
const topic = args
|
|
354
|
+
.slice(2)
|
|
355
|
+
.filter((a) => !a.startsWith("--"))
|
|
356
|
+
.join(" ");
|
|
357
|
+
if (!topic) {
|
|
358
|
+
print("Usage: silo graph topic <topic>");
|
|
359
|
+
process.exit(1);
|
|
360
|
+
}
|
|
361
|
+
const results = graph.byTopic(topic);
|
|
362
|
+
if (jsonMode) {
|
|
363
|
+
print(JSON.stringify(results));
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
if (results.length === 0) {
|
|
367
|
+
print("No claims found for this topic.");
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
print(`${results.length} claim(s) for topic "${topic}":\n`);
|
|
371
|
+
for (const r of results) {
|
|
372
|
+
print(
|
|
373
|
+
` [${r.claim.id}] (${r.claim.type}) ${(r.claim.content || "").slice(0, 100)}`,
|
|
374
|
+
);
|
|
375
|
+
print(` from: ${r.source}\n`);
|
|
376
|
+
}
|
|
377
|
+
} else if (action === "clusters") {
|
|
378
|
+
const clusters = graph.clusters(parseInt(flag("min-size")) || 3);
|
|
379
|
+
if (jsonMode) {
|
|
380
|
+
print(JSON.stringify(clusters));
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
if (clusters.length === 0) {
|
|
384
|
+
print("No clusters found.");
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
print(`${clusters.length} cluster(s):\n`);
|
|
388
|
+
for (const c of clusters.slice(0, 20)) {
|
|
389
|
+
print(
|
|
390
|
+
` "${c.topic}" — ${c.claimCount} claims, ${c.edgeCount} edges`,
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
} else if (action === "export") {
|
|
394
|
+
print(JSON.stringify(graph.toJSON(), null, 2));
|
|
395
|
+
} else {
|
|
396
|
+
print(
|
|
397
|
+
`Unknown graph action: ${action}. Use: stats, related, topic, clusters, export`,
|
|
398
|
+
);
|
|
399
|
+
process.exit(1);
|
|
400
|
+
}
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
case "serve-mcp": {
|
|
405
|
+
const serveMcp = require("../lib/serve-mcp.js");
|
|
284
406
|
serveMcp.run(process.cwd());
|
|
285
407
|
break;
|
|
286
408
|
}
|
|
287
409
|
|
|
288
|
-
case
|
|
410
|
+
case "serve": {
|
|
289
411
|
// Dynamic import for ESM server module -- use fork() for proper stdio
|
|
290
|
-
const port = flag(
|
|
291
|
-
const root = flag(
|
|
412
|
+
const port = flag("port") || "9095";
|
|
413
|
+
const root = flag("root") || process.cwd();
|
|
292
414
|
const serverArgs = [];
|
|
293
|
-
if (flag(
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
415
|
+
if (flag("port")) {
|
|
416
|
+
serverArgs.push("--port", port);
|
|
417
|
+
}
|
|
418
|
+
if (flag("root")) {
|
|
419
|
+
serverArgs.push("--root", root);
|
|
420
|
+
}
|
|
421
|
+
const { fork } = require("node:child_process");
|
|
422
|
+
const path = require("node:path");
|
|
423
|
+
const serverPath = path.join(__dirname, "..", "lib", "server.js");
|
|
298
424
|
const child = fork(serverPath, serverArgs, {
|
|
299
|
-
stdio:
|
|
425
|
+
stdio: "inherit",
|
|
300
426
|
env: process.env,
|
|
301
427
|
});
|
|
302
|
-
child.on(
|
|
428
|
+
child.on("error", (err) => {
|
|
303
429
|
process.stderr.write(`silo: error starting server: ${err.message}\n`);
|
|
304
430
|
process.exit(1);
|
|
305
431
|
});
|
|
306
|
-
child.on(
|
|
307
|
-
process.on(
|
|
308
|
-
process.on(
|
|
432
|
+
child.on("exit", (code) => process.exit(code || 0));
|
|
433
|
+
process.on("SIGTERM", () => child.kill("SIGTERM"));
|
|
434
|
+
process.on("SIGINT", () => child.kill("SIGINT"));
|
|
309
435
|
break;
|
|
310
436
|
}
|
|
311
437
|
|
|
312
|
-
case
|
|
313
|
-
case
|
|
314
|
-
case
|
|
438
|
+
case "help":
|
|
439
|
+
case "--help":
|
|
440
|
+
case "-h":
|
|
315
441
|
case undefined:
|
|
316
442
|
usage();
|
|
317
443
|
break;
|
package/lib/analytics.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* silo -> harvest edge: cross-library analytics.
|
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
* is not available.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
const fs = require(
|
|
13
|
-
const path = require(
|
|
12
|
+
const fs = require("node:fs");
|
|
13
|
+
const path = require("node:path");
|
|
14
14
|
|
|
15
15
|
const HARVEST_SIBLINGS = [
|
|
16
|
-
path.join(__dirname,
|
|
17
|
-
path.join(__dirname,
|
|
16
|
+
path.join(__dirname, "..", "..", "harvest"),
|
|
17
|
+
path.join(__dirname, "..", "..", "..", "harvest"),
|
|
18
18
|
];
|
|
19
19
|
|
|
20
20
|
/**
|
|
@@ -23,12 +23,14 @@ const HARVEST_SIBLINGS = [
|
|
|
23
23
|
*/
|
|
24
24
|
function loadHarvestAnalyzer() {
|
|
25
25
|
for (const dir of HARVEST_SIBLINGS) {
|
|
26
|
-
const analyzerPath = path.join(dir,
|
|
26
|
+
const analyzerPath = path.join(dir, "lib", "analyzer.js");
|
|
27
27
|
if (fs.existsSync(analyzerPath)) {
|
|
28
28
|
try {
|
|
29
29
|
const mod = require(analyzerPath);
|
|
30
|
-
if (typeof mod.analyze ===
|
|
31
|
-
} catch {
|
|
30
|
+
if (typeof mod.analyze === "function") return mod.analyze;
|
|
31
|
+
} catch {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
return null;
|
|
@@ -42,12 +44,20 @@ function loadHarvestAnalyzer() {
|
|
|
42
44
|
function analyzeLibrary(store) {
|
|
43
45
|
const analyze = loadHarvestAnalyzer();
|
|
44
46
|
if (!analyze) {
|
|
45
|
-
return {
|
|
47
|
+
return {
|
|
48
|
+
available: false,
|
|
49
|
+
reason: "harvest analyzer not found in sibling directories",
|
|
50
|
+
};
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
const collections = store.list();
|
|
49
54
|
if (collections.length === 0) {
|
|
50
|
-
return {
|
|
55
|
+
return {
|
|
56
|
+
available: true,
|
|
57
|
+
analysis: null,
|
|
58
|
+
collectionCount: 0,
|
|
59
|
+
reason: "no collections stored",
|
|
60
|
+
};
|
|
51
61
|
}
|
|
52
62
|
|
|
53
63
|
// Convert silo collections into the sprint format harvest expects
|
|
@@ -62,7 +72,12 @@ function analyzeLibrary(store) {
|
|
|
62
72
|
}
|
|
63
73
|
|
|
64
74
|
if (sprints.length === 0) {
|
|
65
|
-
return {
|
|
75
|
+
return {
|
|
76
|
+
available: true,
|
|
77
|
+
analysis: null,
|
|
78
|
+
collectionCount: 0,
|
|
79
|
+
reason: "no valid claim data",
|
|
80
|
+
};
|
|
66
81
|
}
|
|
67
82
|
|
|
68
83
|
const analysis = analyze(sprints);
|