@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/lib/packs.js
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
* a manifest, claims, and optional templates.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
const fs = require(
|
|
10
|
-
const path = require(
|
|
11
|
-
const crypto = require(
|
|
12
|
-
const { Store } = require(
|
|
9
|
+
const fs = require("node:fs");
|
|
10
|
+
const path = require("node:path");
|
|
11
|
+
const crypto = require("node:crypto");
|
|
12
|
+
const { Store } = require("./store.js");
|
|
13
13
|
|
|
14
|
-
const BUILT_IN_PACKS_DIR = path.join(__dirname,
|
|
14
|
+
const BUILT_IN_PACKS_DIR = path.join(__dirname, "..", "packs");
|
|
15
15
|
|
|
16
16
|
class Packs {
|
|
17
17
|
constructor(store) {
|
|
@@ -25,18 +25,18 @@ class Packs {
|
|
|
25
25
|
// Built-in packs
|
|
26
26
|
if (fs.existsSync(BUILT_IN_PACKS_DIR)) {
|
|
27
27
|
for (const file of fs.readdirSync(BUILT_IN_PACKS_DIR)) {
|
|
28
|
-
if (!file.endsWith(
|
|
28
|
+
if (!file.endsWith(".json")) continue;
|
|
29
29
|
try {
|
|
30
30
|
const data = JSON.parse(
|
|
31
|
-
fs.readFileSync(path.join(BUILT_IN_PACKS_DIR, file),
|
|
31
|
+
fs.readFileSync(path.join(BUILT_IN_PACKS_DIR, file), "utf-8"),
|
|
32
32
|
);
|
|
33
33
|
packs.push({
|
|
34
|
-
id: file.replace(
|
|
34
|
+
id: file.replace(".json", ""),
|
|
35
35
|
name: data.name,
|
|
36
36
|
description: data.description,
|
|
37
37
|
claimCount: (data.claims || []).length,
|
|
38
|
-
version: data.version ||
|
|
39
|
-
source:
|
|
38
|
+
version: data.version || "1.0.0",
|
|
39
|
+
source: "built-in",
|
|
40
40
|
});
|
|
41
41
|
} catch {
|
|
42
42
|
// skip malformed
|
|
@@ -48,18 +48,18 @@ class Packs {
|
|
|
48
48
|
const localDir = this.store.packsDir;
|
|
49
49
|
if (fs.existsSync(localDir)) {
|
|
50
50
|
for (const file of fs.readdirSync(localDir)) {
|
|
51
|
-
if (!file.endsWith(
|
|
51
|
+
if (!file.endsWith(".json")) continue;
|
|
52
52
|
try {
|
|
53
53
|
const data = JSON.parse(
|
|
54
|
-
fs.readFileSync(path.join(localDir, file),
|
|
54
|
+
fs.readFileSync(path.join(localDir, file), "utf-8"),
|
|
55
55
|
);
|
|
56
56
|
packs.push({
|
|
57
|
-
id: file.replace(
|
|
57
|
+
id: file.replace(".json", ""),
|
|
58
58
|
name: data.name,
|
|
59
59
|
description: data.description,
|
|
60
60
|
claimCount: (data.claims || []).length,
|
|
61
|
-
version: data.version ||
|
|
62
|
-
source:
|
|
61
|
+
version: data.version || "1.0.0",
|
|
62
|
+
source: "local",
|
|
63
63
|
});
|
|
64
64
|
} catch {
|
|
65
65
|
// skip malformed
|
|
@@ -74,12 +74,12 @@ class Packs {
|
|
|
74
74
|
get(id) {
|
|
75
75
|
const builtIn = path.join(BUILT_IN_PACKS_DIR, `${id}.json`);
|
|
76
76
|
if (fs.existsSync(builtIn)) {
|
|
77
|
-
return JSON.parse(fs.readFileSync(builtIn,
|
|
77
|
+
return JSON.parse(fs.readFileSync(builtIn, "utf-8"));
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
const local = path.join(this.store.packsDir, `${id}.json`);
|
|
81
81
|
if (fs.existsSync(local)) {
|
|
82
|
-
return JSON.parse(fs.readFileSync(local,
|
|
82
|
+
return JSON.parse(fs.readFileSync(local, "utf-8"));
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
return null;
|
|
@@ -105,19 +105,25 @@ class Packs {
|
|
|
105
105
|
|
|
106
106
|
const pack = {
|
|
107
107
|
name,
|
|
108
|
-
description: meta.description ||
|
|
109
|
-
version: meta.version ||
|
|
110
|
-
author: meta.author ||
|
|
108
|
+
description: meta.description || "",
|
|
109
|
+
version: meta.version || "1.0.0",
|
|
110
|
+
author: meta.author || "",
|
|
111
111
|
createdAt: new Date().toISOString(),
|
|
112
|
-
hash: crypto
|
|
112
|
+
hash: crypto
|
|
113
|
+
.createHash("sha256")
|
|
114
|
+
.update(JSON.stringify(allClaims))
|
|
115
|
+
.digest("hex"),
|
|
113
116
|
sources: collectionIds,
|
|
114
117
|
claims: allClaims,
|
|
115
118
|
};
|
|
116
119
|
|
|
117
|
-
const slug = name
|
|
120
|
+
const slug = name
|
|
121
|
+
.toLowerCase()
|
|
122
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
123
|
+
.replace(/^-|-$/g, "");
|
|
118
124
|
const packPath = path.join(this.store.packsDir, `${slug}.json`);
|
|
119
|
-
const tmp = packPath +
|
|
120
|
-
fs.writeFileSync(tmp, JSON.stringify(pack, null, 2) +
|
|
125
|
+
const tmp = packPath + ".tmp." + process.pid;
|
|
126
|
+
fs.writeFileSync(tmp, JSON.stringify(pack, null, 2) + "\n", "utf-8");
|
|
121
127
|
fs.renameSync(tmp, packPath);
|
|
122
128
|
|
|
123
129
|
return { id: slug, path: packPath, claimCount: allClaims.length };
|
|
@@ -133,19 +139,24 @@ class Packs {
|
|
|
133
139
|
throw new Error(`Pack file not found: ${filePath}`);
|
|
134
140
|
}
|
|
135
141
|
|
|
136
|
-
const pack = JSON.parse(fs.readFileSync(filePath,
|
|
137
|
-
const slug = (pack.name || path.basename(filePath,
|
|
142
|
+
const pack = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
143
|
+
const slug = (pack.name || path.basename(filePath, ".json"))
|
|
138
144
|
.toLowerCase()
|
|
139
|
-
.replace(/[^a-z0-9]+/g,
|
|
140
|
-
.replace(/^-|-$/g,
|
|
145
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
146
|
+
.replace(/^-|-$/g, "");
|
|
141
147
|
|
|
142
148
|
// Verify pack integrity if hash present
|
|
143
149
|
if (pack.hash && pack.claims) {
|
|
144
|
-
const actual = crypto
|
|
150
|
+
const actual = crypto
|
|
151
|
+
.createHash("sha256")
|
|
152
|
+
.update(JSON.stringify(pack.claims))
|
|
153
|
+
.digest("hex");
|
|
145
154
|
// Support both old 12-char and new 64-char hashes
|
|
146
155
|
if (pack.hash !== actual && !actual.startsWith(pack.hash)) {
|
|
147
156
|
if (!options.force) {
|
|
148
|
-
throw new Error(
|
|
157
|
+
throw new Error(
|
|
158
|
+
`Pack integrity check failed: hash mismatch. Use --force to install anyway.`,
|
|
159
|
+
);
|
|
149
160
|
}
|
|
150
161
|
}
|
|
151
162
|
}
|
|
@@ -155,13 +166,26 @@ class Packs {
|
|
|
155
166
|
|
|
156
167
|
// Version comparison if pack already exists
|
|
157
168
|
if (fs.existsSync(dest) && !options.force) {
|
|
158
|
-
const existing = JSON.parse(fs.readFileSync(dest,
|
|
159
|
-
const cmp = _compareSemver(
|
|
169
|
+
const existing = JSON.parse(fs.readFileSync(dest, "utf-8"));
|
|
170
|
+
const cmp = _compareSemver(
|
|
171
|
+
pack.version || "0.0.0",
|
|
172
|
+
existing.version || "0.0.0",
|
|
173
|
+
);
|
|
160
174
|
if (cmp === 0) {
|
|
161
|
-
return {
|
|
175
|
+
return {
|
|
176
|
+
id: slug,
|
|
177
|
+
claimCount: (pack.claims || []).length,
|
|
178
|
+
skipped: true,
|
|
179
|
+
reason: "same version",
|
|
180
|
+
};
|
|
162
181
|
}
|
|
163
182
|
if (cmp < 0) {
|
|
164
|
-
return {
|
|
183
|
+
return {
|
|
184
|
+
id: slug,
|
|
185
|
+
claimCount: (pack.claims || []).length,
|
|
186
|
+
skipped: true,
|
|
187
|
+
reason: `downgrade (${existing.version} → ${pack.version}). Use --force to override.`,
|
|
188
|
+
};
|
|
165
189
|
}
|
|
166
190
|
}
|
|
167
191
|
|
|
@@ -172,8 +196,8 @@ class Packs {
|
|
|
172
196
|
|
|
173
197
|
/** Compare two semver strings. Returns -1, 0, or 1. */
|
|
174
198
|
function _compareSemver(a, b) {
|
|
175
|
-
const pa = (a ||
|
|
176
|
-
const pb = (b ||
|
|
199
|
+
const pa = (a || "0.0.0").split(".").map(Number);
|
|
200
|
+
const pb = (b || "0.0.0").split(".").map(Number);
|
|
177
201
|
for (let i = 0; i < 3; i++) {
|
|
178
202
|
if ((pa[i] || 0) > (pb[i] || 0)) return 1;
|
|
179
203
|
if ((pa[i] || 0) < (pb[i] || 0)) return -1;
|
package/lib/search.js
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* rank by term frequency. No external deps.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
const fs = require(
|
|
9
|
-
const path = require(
|
|
10
|
-
const { Store } = require(
|
|
8
|
+
const fs = require("node:fs");
|
|
9
|
+
const path = require("node:path");
|
|
10
|
+
const { Store } = require("./store.js");
|
|
11
11
|
|
|
12
12
|
class Search {
|
|
13
13
|
constructor(store) {
|
|
@@ -35,18 +35,18 @@ class Search {
|
|
|
35
35
|
|
|
36
36
|
if (!fs.existsSync(claimsDir)) return [];
|
|
37
37
|
|
|
38
|
-
const files = fs.readdirSync(claimsDir).filter((f) => f.endsWith(
|
|
38
|
+
const files = fs.readdirSync(claimsDir).filter((f) => f.endsWith(".json"));
|
|
39
39
|
|
|
40
40
|
for (const file of files) {
|
|
41
41
|
const filePath = path.join(claimsDir, file);
|
|
42
42
|
let data;
|
|
43
43
|
try {
|
|
44
|
-
data = JSON.parse(fs.readFileSync(filePath,
|
|
44
|
+
data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
45
45
|
} catch {
|
|
46
46
|
continue;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
const collectionName = data.meta?.name || file.replace(
|
|
49
|
+
const collectionName = data.meta?.name || file.replace(".json", "");
|
|
50
50
|
const claims = data.claims || [];
|
|
51
51
|
|
|
52
52
|
for (const claim of claims) {
|
|
@@ -56,17 +56,20 @@ class Search {
|
|
|
56
56
|
if (evidenceFilter && claimEvidence !== evidenceFilter) continue;
|
|
57
57
|
|
|
58
58
|
// Score by token matches across searchable fields (support both content and legacy text)
|
|
59
|
-
const sourceStr =
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
const sourceStr =
|
|
60
|
+
typeof claim.source === "object"
|
|
61
|
+
? [claim.source?.origin, claim.source?.artifact]
|
|
62
|
+
.filter(Boolean)
|
|
63
|
+
.join(" ")
|
|
64
|
+
: claim.source || "";
|
|
62
65
|
const searchable = [
|
|
63
|
-
claim.content || claim.text ||
|
|
64
|
-
claim.type ||
|
|
65
|
-
claim.tags?.join(
|
|
66
|
+
claim.content || claim.text || "",
|
|
67
|
+
claim.type || "",
|
|
68
|
+
claim.tags?.join(" ") || "",
|
|
66
69
|
sourceStr,
|
|
67
70
|
collectionName,
|
|
68
71
|
]
|
|
69
|
-
.join(
|
|
72
|
+
.join(" ")
|
|
70
73
|
.toLowerCase();
|
|
71
74
|
|
|
72
75
|
let score = 0;
|
|
@@ -75,7 +78,10 @@ class Search {
|
|
|
75
78
|
if (idx !== -1) {
|
|
76
79
|
score += 1;
|
|
77
80
|
// Bonus for exact word match
|
|
78
|
-
if (
|
|
81
|
+
if (
|
|
82
|
+
searchable.includes(` ${token} `) ||
|
|
83
|
+
searchable.startsWith(`${token} `)
|
|
84
|
+
) {
|
|
79
85
|
score += 0.5;
|
|
80
86
|
}
|
|
81
87
|
}
|
|
@@ -101,10 +107,12 @@ class Search {
|
|
|
101
107
|
const claimsDir = this.store.claimsDir;
|
|
102
108
|
if (!fs.existsSync(claimsDir)) return [];
|
|
103
109
|
|
|
104
|
-
const files = fs.readdirSync(claimsDir).filter((f) => f.endsWith(
|
|
110
|
+
const files = fs.readdirSync(claimsDir).filter((f) => f.endsWith(".json"));
|
|
105
111
|
for (const file of files) {
|
|
106
112
|
try {
|
|
107
|
-
const data = JSON.parse(
|
|
113
|
+
const data = JSON.parse(
|
|
114
|
+
fs.readFileSync(path.join(claimsDir, file), "utf-8"),
|
|
115
|
+
);
|
|
108
116
|
for (const claim of data.claims || []) {
|
|
109
117
|
for (const tag of claim.tags || []) {
|
|
110
118
|
tagSet.add(tag);
|