@sigloch/se-engine 1.0.0
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/LICENSE +21 -0
- package/dist/fix-templates.d.ts +24 -0
- package/dist/fix-templates.js +192 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +13 -0
- package/dist/layer.d.ts +29 -0
- package/dist/layer.js +36 -0
- package/dist/metrics.d.ts +53 -0
- package/dist/metrics.js +97 -0
- package/dist/readiness-compute.d.ts +27 -0
- package/dist/readiness-compute.js +119 -0
- package/dist/rule-apply.d.ts +39 -0
- package/dist/rule-apply.js +136 -0
- package/dist/rule-classify.d.ts +43 -0
- package/dist/rule-classify.js +163 -0
- package/dist/suggest.d.ts +31 -0
- package/dist/suggest.js +63 -0
- package/dist/topology.d.ts +119 -0
- package/dist/topology.js +383 -0
- package/package.json +49 -0
package/dist/topology.js
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/** Locale-independent id order — the canonical walk order of every primitive here. */
|
|
2
|
+
const byIdCanonical = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
3
|
+
/**
|
|
4
|
+
* Build adjacency maps from a graph. Traces referencing unknown nodes are
|
|
5
|
+
* dropped. `nodeWeight` (e.g. LOC per node, see layer.ts `weightNodes`) sets
|
|
6
|
+
* node masses; edge weight = mean of endpoint masses.
|
|
7
|
+
*
|
|
8
|
+
* ORDER-INVARIANT (CR-SM-240): `nodes` and every neighbour set come back in
|
|
9
|
+
* canonical id order, so the SAME graph in a different element/trace order
|
|
10
|
+
* yields a bit-identical Adjacency — and therefore bit-identical metrics.
|
|
11
|
+
*/
|
|
12
|
+
export function buildAdjacency(graph, nodeWeight) {
|
|
13
|
+
// CR-SM-240: CANONICAL order, not the caller's element order. Every primitive
|
|
14
|
+
// in this file walks `nodes` and the neighbour sets, so the caller's ordering
|
|
15
|
+
// used to leak into (a) the community ids and the CNM tie-break — moving
|
|
16
|
+
// `modifiability` and `coherence` by ~1e-2 on a real graph — and (b) the float
|
|
17
|
+
// summation order of degrees/betweenness, worth ~1e-15 in `scalability`.
|
|
18
|
+
// Sorting HERE fixes both at the single place where the order enters, instead
|
|
19
|
+
// of at each of the five consumers.
|
|
20
|
+
//
|
|
21
|
+
// Plain `<`/`>` on purpose, NOT `localeCompare`: the comparison must not depend
|
|
22
|
+
// on the runtime's locale — an invariant that holds only on the developer's
|
|
23
|
+
// machine is not an invariant.
|
|
24
|
+
const nodes = graph.elements.map((e) => e.id).sort(byIdCanonical);
|
|
25
|
+
const known = new Set(nodes);
|
|
26
|
+
const undirected = new Map();
|
|
27
|
+
const out = new Map();
|
|
28
|
+
const inn = new Map();
|
|
29
|
+
const w = new Map();
|
|
30
|
+
const mass = new Map(nodes.map((id) => [id, nodeWeight?.get(id) ?? 1]));
|
|
31
|
+
for (const id of nodes) {
|
|
32
|
+
undirected.set(id, new Set());
|
|
33
|
+
out.set(id, new Set());
|
|
34
|
+
inn.set(id, new Set());
|
|
35
|
+
w.set(id, new Map());
|
|
36
|
+
}
|
|
37
|
+
for (const t of graph.traces) {
|
|
38
|
+
if (!known.has(t.source) || !known.has(t.target) || t.source === t.target)
|
|
39
|
+
continue;
|
|
40
|
+
out.get(t.source).add(t.target);
|
|
41
|
+
inn.get(t.target).add(t.source);
|
|
42
|
+
undirected.get(t.source).add(t.target);
|
|
43
|
+
undirected.get(t.target).add(t.source);
|
|
44
|
+
const ew = (mass.get(t.source) + mass.get(t.target)) / 2;
|
|
45
|
+
w.get(t.source).set(t.target, ew);
|
|
46
|
+
w.get(t.target).set(t.source, ew);
|
|
47
|
+
}
|
|
48
|
+
// The sets were filled in TRACE order; re-lay them in canonical id order so the
|
|
49
|
+
// neighbour walks (BFS in betweenness, the CNM aggregate build) are invariant too.
|
|
50
|
+
// Sorting only `nodes` would leave the second half of the leak open.
|
|
51
|
+
for (const id of nodes) {
|
|
52
|
+
undirected.set(id, new Set([...undirected.get(id)].sort(byIdCanonical)));
|
|
53
|
+
out.set(id, new Set([...out.get(id)].sort(byIdCanonical)));
|
|
54
|
+
inn.set(id, new Set([...inn.get(id)].sort(byIdCanonical)));
|
|
55
|
+
}
|
|
56
|
+
return { nodes, undirected, out, in: inn, w, mass };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Betweenness centrality (Brandes' algorithm, unweighted undirected), normalized
|
|
60
|
+
* to [0,1] by the number of ordered node pairs (n-1)(n-2). Returns 0 for all
|
|
61
|
+
* nodes when n < 3. Higher = more of the shortest paths route through the node
|
|
62
|
+
* (structural bottleneck).
|
|
63
|
+
*
|
|
64
|
+
* CR-SM-228: index-based typed arrays instead of a fresh Map<string,X> per BFS
|
|
65
|
+
* source. The algorithm and its output are unchanged (bit-identical to the
|
|
66
|
+
* prior Map-based version) -- only the per-source scratch state changes from
|
|
67
|
+
* "allocate 4 new Maps" (O(V) allocation repeated V times = O(V^2) overhead
|
|
68
|
+
* on top of Brandes' own O(V*E), dominant on sparse graphs) to "reset 4
|
|
69
|
+
* preallocated typed arrays via .fill()" (same O(V) work, without the
|
|
70
|
+
* allocation/hashing cost). Measured A/B (1910-node graph, 3915 edges,
|
|
71
|
+
* cloned from graphcode's own SSOT):
|
|
72
|
+
* OLD per-source Map allocation : ~610 ms
|
|
73
|
+
* NEW indexed typed arrays : ~93 ms (6.6x faster, max diff 0)
|
|
74
|
+
* Root cause + measurement: sigloch-modules/docs/cr/open/CR-SM-228-*.md.
|
|
75
|
+
*/
|
|
76
|
+
export function betweenness(adj) {
|
|
77
|
+
const { nodes, undirected } = adj;
|
|
78
|
+
const n = nodes.length;
|
|
79
|
+
const cb = new Map(nodes.map((id) => [id, 0]));
|
|
80
|
+
if (n < 3)
|
|
81
|
+
return cb;
|
|
82
|
+
const indexOf = new Map(nodes.map((id, i) => [id, i]));
|
|
83
|
+
// Neighbour lists as index arrays, built once (not per BFS source).
|
|
84
|
+
const neighbours = nodes.map((id) => Int32Array.from(undirected.get(id), (w) => indexOf.get(w)));
|
|
85
|
+
const dist = new Int32Array(n);
|
|
86
|
+
const sigma = new Float64Array(n);
|
|
87
|
+
const delta = new Float64Array(n);
|
|
88
|
+
const queue = new Int32Array(n);
|
|
89
|
+
const stack = new Int32Array(n);
|
|
90
|
+
const cbArr = new Float64Array(n);
|
|
91
|
+
// Predecessor lists, reused across sources (length reset to 0, not reallocated).
|
|
92
|
+
const pred = Array.from({ length: n }, () => []);
|
|
93
|
+
for (let s = 0; s < n; s++) {
|
|
94
|
+
dist.fill(-1);
|
|
95
|
+
sigma.fill(0);
|
|
96
|
+
delta.fill(0);
|
|
97
|
+
for (let i = 0; i < n; i++)
|
|
98
|
+
pred[i].length = 0;
|
|
99
|
+
dist[s] = 0;
|
|
100
|
+
sigma[s] = 1;
|
|
101
|
+
let qHead = 0;
|
|
102
|
+
let qTail = 0;
|
|
103
|
+
let sTop = 0;
|
|
104
|
+
queue[qTail++] = s;
|
|
105
|
+
while (qHead < qTail) {
|
|
106
|
+
const v = queue[qHead++];
|
|
107
|
+
stack[sTop++] = v;
|
|
108
|
+
const dv = dist[v];
|
|
109
|
+
for (const w2 of neighbours[v]) {
|
|
110
|
+
if (dist[w2] < 0) {
|
|
111
|
+
dist[w2] = dv + 1;
|
|
112
|
+
queue[qTail++] = w2;
|
|
113
|
+
}
|
|
114
|
+
if (dist[w2] === dv + 1) {
|
|
115
|
+
sigma[w2] += sigma[v];
|
|
116
|
+
pred[w2].push(v);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
while (sTop > 0) {
|
|
121
|
+
const w2 = stack[--sTop];
|
|
122
|
+
for (const v of pred[w2]) {
|
|
123
|
+
delta[v] += (sigma[v] / sigma[w2]) * (1 + delta[w2]);
|
|
124
|
+
}
|
|
125
|
+
if (w2 !== s)
|
|
126
|
+
cbArr[w2] += delta[w2];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Each shortest path counted twice on an undirected graph → divide by 2,
|
|
130
|
+
// then normalize by the number of ordered pairs (n-1)(n-2).
|
|
131
|
+
const norm = (n - 1) * (n - 2);
|
|
132
|
+
nodes.forEach((id, i) => cb.set(id, cbArr[i] / norm));
|
|
133
|
+
return cb;
|
|
134
|
+
}
|
|
135
|
+
/** Max betweenness over all nodes (the worst bottleneck), in [0,1]. */
|
|
136
|
+
export function maxBetweenness(adj) {
|
|
137
|
+
let max = 0;
|
|
138
|
+
for (const v of betweenness(adj).values())
|
|
139
|
+
if (v > max)
|
|
140
|
+
max = v;
|
|
141
|
+
return max;
|
|
142
|
+
}
|
|
143
|
+
/** Weighted degree (strength) per node and 2m (sum of strengths). */
|
|
144
|
+
function degrees(adj) {
|
|
145
|
+
const deg = new Map();
|
|
146
|
+
let twoM = 0;
|
|
147
|
+
for (const id of adj.nodes) {
|
|
148
|
+
let s = 0;
|
|
149
|
+
for (const ww of adj.w.get(id).values())
|
|
150
|
+
s += ww;
|
|
151
|
+
deg.set(id, s);
|
|
152
|
+
twoM += s;
|
|
153
|
+
}
|
|
154
|
+
return { deg, twoM };
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Newman modularity Q for a given community assignment, over weighted edges.
|
|
158
|
+
* Q = (1/2m) Σ_ij [A_ij − k_i k_j / 2m] δ(c_i,c_j) ∈ [-0.5, 1]. 0 when edgeless.
|
|
159
|
+
* Unweighted graphs (all edge weights 1) reproduce the classic unweighted Q.
|
|
160
|
+
*/
|
|
161
|
+
export function modularityOf(adj, community) {
|
|
162
|
+
const { nodes, undirected } = adj;
|
|
163
|
+
const { deg, twoM } = degrees(adj);
|
|
164
|
+
if (twoM === 0)
|
|
165
|
+
return 0;
|
|
166
|
+
let intra = 0; // Σ A_ij over same-community ordered pairs (= 2 × internal edge weight)
|
|
167
|
+
for (const v of nodes) {
|
|
168
|
+
for (const w2 of undirected.get(v)) {
|
|
169
|
+
if (community.get(v) === community.get(w2))
|
|
170
|
+
intra += adj.w.get(v).get(w2);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const sumDegByComm = new Map();
|
|
174
|
+
for (const v of nodes) {
|
|
175
|
+
const c = community.get(v);
|
|
176
|
+
sumDegByComm.set(c, (sumDegByComm.get(c) ?? 0) + deg.get(v));
|
|
177
|
+
}
|
|
178
|
+
let degTerm = 0;
|
|
179
|
+
for (const s of sumDegByComm.values())
|
|
180
|
+
degTerm += (s / twoM) * (s / twoM);
|
|
181
|
+
return intra / twoM - degTerm;
|
|
182
|
+
}
|
|
183
|
+
/** Newman modularity Q of the graph under greedy CNM communities. */
|
|
184
|
+
export function modularityQ(adj) {
|
|
185
|
+
return modularityOf(adj, detectCommunities(adj));
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Greedy agglomerative community detection (Clauset–Newman–Moore, weighted):
|
|
189
|
+
* start each node in its own community, repeatedly merge the edge-linked pair
|
|
190
|
+
* that most increases Q, until no merge helps. Returns node→community-id.
|
|
191
|
+
* Edgeless graph → singletons.
|
|
192
|
+
*
|
|
193
|
+
* DETERMINISM (CR-SM-240) — the claim used to be "ties broken by community-id
|
|
194
|
+
* order", which was only half true: the ids are `buildAdjacency`'s node indices,
|
|
195
|
+
* and those were the CALLER's element order. A tie then went to whoever came
|
|
196
|
+
* first in the input, so a permuted graph produced a different partition (and
|
|
197
|
+
* with it different `modifiability`/`coherence`). The ids are canonical since
|
|
198
|
+
* CR-SM-240, so the sentence now holds: `alive` iterates ascending (built 0..n-1,
|
|
199
|
+
* deletions preserve order), each `e[i]` row was filled in canonical node order,
|
|
200
|
+
* and the strict `dq > best` therefore keeps the FIRST maximum in ascending
|
|
201
|
+
* (i, k) order. The invariant lives in `buildAdjacency`; do not re-derive it here.
|
|
202
|
+
*/
|
|
203
|
+
export function detectCommunities(adj) {
|
|
204
|
+
const { nodes, undirected } = adj;
|
|
205
|
+
const community = new Map(nodes.map((id, i) => [id, i]));
|
|
206
|
+
const { deg, twoM } = degrees(adj);
|
|
207
|
+
if (twoM === 0)
|
|
208
|
+
return community;
|
|
209
|
+
// Incremental CNM state (CR-231): each merge selects the pair with maximum
|
|
210
|
+
// modularity gain ΔQ = 2·(e_ij − a_i·a_j), computed in O(1) from maintained
|
|
211
|
+
// aggregates instead of re-scoring the whole partition.
|
|
212
|
+
// a[c] = fraction of edge-endpoint weight in community c (Σ strength / 2m)
|
|
213
|
+
// e[c][k] = fraction of edge weight between communities c and k
|
|
214
|
+
const a = new Map();
|
|
215
|
+
const members = new Map();
|
|
216
|
+
const e = new Map();
|
|
217
|
+
nodes.forEach((id, i) => {
|
|
218
|
+
a.set(i, deg.get(id) / twoM);
|
|
219
|
+
members.set(i, [id]);
|
|
220
|
+
e.set(i, new Map());
|
|
221
|
+
});
|
|
222
|
+
for (const v of nodes) {
|
|
223
|
+
const cv = community.get(v);
|
|
224
|
+
for (const w2 of undirected.get(v)) {
|
|
225
|
+
const cw = community.get(w2);
|
|
226
|
+
if (cv === cw)
|
|
227
|
+
continue;
|
|
228
|
+
const row = e.get(cv);
|
|
229
|
+
row.set(cw, (row.get(cw) ?? 0) + adj.w.get(v).get(w2) / twoM); // each direction adds w/2m → edge total w/m
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const alive = new Set(members.keys());
|
|
233
|
+
for (;;) {
|
|
234
|
+
// Best merge = max ΔQ over adjacent community pairs (ascending id order → deterministic).
|
|
235
|
+
let best = 1e-12;
|
|
236
|
+
let bi = -1;
|
|
237
|
+
let bj = -1;
|
|
238
|
+
for (const i of alive) {
|
|
239
|
+
const ai = a.get(i);
|
|
240
|
+
for (const [k, eik] of e.get(i)) {
|
|
241
|
+
if (k <= i)
|
|
242
|
+
continue; // unordered pair once
|
|
243
|
+
const dq = 2 * (eik - ai * a.get(k));
|
|
244
|
+
if (dq > best) {
|
|
245
|
+
best = dq;
|
|
246
|
+
bi = i;
|
|
247
|
+
bj = k;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (bi < 0)
|
|
252
|
+
break;
|
|
253
|
+
// Merge bj into bi (keep the lower id).
|
|
254
|
+
const ei = e.get(bi);
|
|
255
|
+
const ej = e.get(bj);
|
|
256
|
+
a.set(bi, a.get(bi) + a.get(bj));
|
|
257
|
+
for (const [k, ejk] of ej) {
|
|
258
|
+
if (k === bi)
|
|
259
|
+
continue;
|
|
260
|
+
ei.set(k, (ei.get(k) ?? 0) + ejk);
|
|
261
|
+
const ek = e.get(k);
|
|
262
|
+
ek.set(bi, (ek.get(bi) ?? 0) + ejk);
|
|
263
|
+
ek.delete(bj);
|
|
264
|
+
}
|
|
265
|
+
ei.delete(bj);
|
|
266
|
+
e.delete(bj);
|
|
267
|
+
for (const id of members.get(bj))
|
|
268
|
+
community.set(id, bi);
|
|
269
|
+
members.get(bi).push(...members.get(bj));
|
|
270
|
+
members.delete(bj);
|
|
271
|
+
a.delete(bj);
|
|
272
|
+
alive.delete(bj);
|
|
273
|
+
}
|
|
274
|
+
return community;
|
|
275
|
+
}
|
|
276
|
+
/** Weakly-connected components (undirected reachability), as node-id groups. */
|
|
277
|
+
export function components(adj) {
|
|
278
|
+
const { nodes, undirected } = adj;
|
|
279
|
+
const seen = new Set();
|
|
280
|
+
const groups = [];
|
|
281
|
+
for (const start of nodes) {
|
|
282
|
+
if (seen.has(start))
|
|
283
|
+
continue;
|
|
284
|
+
const group = [];
|
|
285
|
+
const stack = [start];
|
|
286
|
+
seen.add(start);
|
|
287
|
+
while (stack.length) {
|
|
288
|
+
const v = stack.pop();
|
|
289
|
+
group.push(v);
|
|
290
|
+
for (const w2 of undirected.get(v))
|
|
291
|
+
if (!seen.has(w2)) {
|
|
292
|
+
seen.add(w2);
|
|
293
|
+
stack.push(w2);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
groups.push(group);
|
|
297
|
+
}
|
|
298
|
+
return groups;
|
|
299
|
+
}
|
|
300
|
+
/** Weakly-connected component sizes (node counts). */
|
|
301
|
+
export function componentSizes(adj) {
|
|
302
|
+
return components(adj).map((g) => g.length);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Cyclomatic redundancy density = (m − n + c) / n, where m = undirected edges,
|
|
306
|
+
* n = nodes, c = components. Counts independent cycles per node — the redundant
|
|
307
|
+
* paths that survive a single edge/node failure. 0 for a forest. Unweighted.
|
|
308
|
+
*/
|
|
309
|
+
export function redundancyDensity(adj) {
|
|
310
|
+
const { nodes, undirected } = adj;
|
|
311
|
+
const n = nodes.length;
|
|
312
|
+
if (n === 0)
|
|
313
|
+
return 0;
|
|
314
|
+
let m = 0;
|
|
315
|
+
for (const nb of undirected.values())
|
|
316
|
+
m += nb.size;
|
|
317
|
+
m /= 2;
|
|
318
|
+
const c = components(adj).length;
|
|
319
|
+
return (m - n + c) / n;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Coupling/cohesion: the fraction of edge WEIGHT that stays INSIDE a community
|
|
323
|
+
* of the given partition (vs crossing between communities). ∈ [0,1]; higher =
|
|
324
|
+
* more cohesive / less coupled. Community-based, so non-trivial on the
|
|
325
|
+
* near-bipartite, layered SE ontology graph (CR-229). 0 when edgeless.
|
|
326
|
+
*/
|
|
327
|
+
export function intraEdgeFraction(adj, community) {
|
|
328
|
+
const { nodes, undirected } = adj;
|
|
329
|
+
let internal = 0;
|
|
330
|
+
let total = 0;
|
|
331
|
+
for (const v of nodes) {
|
|
332
|
+
for (const w2 of undirected.get(v)) {
|
|
333
|
+
// Each undirected edge is counted twice (v→w and w→v); the ratio is unaffected.
|
|
334
|
+
const ww = adj.w.get(v).get(w2);
|
|
335
|
+
total += ww;
|
|
336
|
+
if (community.get(v) === community.get(w2))
|
|
337
|
+
internal += ww;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return total === 0 ? 0 : internal / total;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Mean shortest directed path length from sources (in-degree 0) to sinks
|
|
344
|
+
* (out-degree 0), over reachable source→sink pairs only. Returns
|
|
345
|
+
* { meanLength, reachableFraction }. When no source→sink pair is reachable,
|
|
346
|
+
* meanLength is 0 and reachableFraction is 0. Unweighted.
|
|
347
|
+
*/
|
|
348
|
+
export function sourceSinkPaths(adj) {
|
|
349
|
+
const { nodes, out, in: inn } = adj;
|
|
350
|
+
const sources = nodes.filter((id) => inn.get(id).size === 0 && out.get(id).size > 0);
|
|
351
|
+
const sinks = new Set(nodes.filter((id) => out.get(id).size === 0 && inn.get(id).size > 0));
|
|
352
|
+
if (sources.length === 0 || sinks.size === 0)
|
|
353
|
+
return { meanLength: 0, reachableFraction: 0 };
|
|
354
|
+
let total = 0;
|
|
355
|
+
let lengthSum = 0;
|
|
356
|
+
let reached = 0;
|
|
357
|
+
for (const s of sources) {
|
|
358
|
+
// BFS shortest directed path lengths from s.
|
|
359
|
+
const dist = new Map([[s, 0]]);
|
|
360
|
+
const queue = [s];
|
|
361
|
+
while (queue.length) {
|
|
362
|
+
const v = queue.shift();
|
|
363
|
+
for (const w2 of out.get(v)) {
|
|
364
|
+
if (!dist.has(w2)) {
|
|
365
|
+
dist.set(w2, dist.get(v) + 1);
|
|
366
|
+
queue.push(w2);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
for (const sink of sinks) {
|
|
371
|
+
total += 1;
|
|
372
|
+
const d = dist.get(sink);
|
|
373
|
+
if (d !== undefined && d > 0) {
|
|
374
|
+
lengthSum += d;
|
|
375
|
+
reached += 1;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return {
|
|
380
|
+
meanLength: reached === 0 ? 0 : lengthSum / reached,
|
|
381
|
+
reachableFraction: total === 0 ? 0 : reached / total,
|
|
382
|
+
};
|
|
383
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sigloch/se-engine",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"LICENSE"
|
|
10
|
+
],
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "rm -rf dist && tsc",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"prepublishOnly": "npm run build && npm run test"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"zod": "^4.3.6"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"description": "SE analysis over a governed graph — metric vector, layer projection, rule apply/suggest, readiness computation",
|
|
27
|
+
"author": "sigloch-consulting",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/andreassigloch/sigloch-modules.git",
|
|
31
|
+
"directory": "packages/se-engine"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/andreassigloch/sigloch-modules#readme",
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/andreassigloch/sigloch-modules/issues"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=22"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"@sigloch/contracts": ">=5 <6"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@sigloch/contracts": "^5.0.1"
|
|
48
|
+
}
|
|
49
|
+
}
|