@mainahq/core 1.0.3 → 1.1.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.
Files changed (71) hide show
  1. package/package.json +1 -1
  2. package/src/ai/__tests__/delegation.test.ts +55 -1
  3. package/src/ai/delegation.ts +5 -3
  4. package/src/context/__tests__/budget.test.ts +29 -6
  5. package/src/context/__tests__/engine.test.ts +1 -0
  6. package/src/context/__tests__/selector.test.ts +23 -3
  7. package/src/context/__tests__/wiki.test.ts +349 -0
  8. package/src/context/budget.ts +12 -8
  9. package/src/context/engine.ts +37 -0
  10. package/src/context/selector.ts +30 -4
  11. package/src/context/wiki.ts +296 -0
  12. package/src/db/index.ts +12 -0
  13. package/src/feedback/__tests__/capture.test.ts +166 -0
  14. package/src/feedback/__tests__/signals.test.ts +144 -0
  15. package/src/feedback/__tests__/tmp-capture-1775575256633-lah0etnzlj/feedback.db +0 -0
  16. package/src/feedback/__tests__/tmp-capture-1775575256640-2xmjme4qraa/feedback.db +0 -0
  17. package/src/feedback/capture.ts +102 -0
  18. package/src/feedback/signals.ts +68 -0
  19. package/src/index.ts +104 -0
  20. package/src/init/__tests__/init.test.ts +400 -3
  21. package/src/init/index.ts +368 -12
  22. package/src/language/__tests__/__fixtures__/detect/composer.lock +1 -0
  23. package/src/prompts/defaults/index.ts +3 -1
  24. package/src/prompts/defaults/wiki-compile.md +20 -0
  25. package/src/prompts/defaults/wiki-query.md +18 -0
  26. package/src/stats/__tests__/tool-usage.test.ts +133 -0
  27. package/src/stats/tracker.ts +92 -0
  28. package/src/verify/__tests__/pipeline.test.ts +11 -8
  29. package/src/verify/pipeline.ts +13 -1
  30. package/src/verify/tools/__tests__/wiki-lint.test.ts +784 -0
  31. package/src/verify/tools/wiki-lint-runner.ts +38 -0
  32. package/src/verify/tools/wiki-lint.ts +898 -0
  33. package/src/wiki/__tests__/compiler.test.ts +389 -0
  34. package/src/wiki/__tests__/extractors/code.test.ts +99 -0
  35. package/src/wiki/__tests__/extractors/decision.test.ts +323 -0
  36. package/src/wiki/__tests__/extractors/feature.test.ts +186 -0
  37. package/src/wiki/__tests__/extractors/workflow.test.ts +131 -0
  38. package/src/wiki/__tests__/graph.test.ts +344 -0
  39. package/src/wiki/__tests__/hooks.test.ts +119 -0
  40. package/src/wiki/__tests__/indexer.test.ts +285 -0
  41. package/src/wiki/__tests__/linker.test.ts +230 -0
  42. package/src/wiki/__tests__/louvain.test.ts +229 -0
  43. package/src/wiki/__tests__/query.test.ts +316 -0
  44. package/src/wiki/__tests__/schema.test.ts +114 -0
  45. package/src/wiki/__tests__/signals.test.ts +474 -0
  46. package/src/wiki/__tests__/state.test.ts +168 -0
  47. package/src/wiki/__tests__/tracking.test.ts +118 -0
  48. package/src/wiki/__tests__/types.test.ts +387 -0
  49. package/src/wiki/compiler.ts +1075 -0
  50. package/src/wiki/extractors/code.ts +90 -0
  51. package/src/wiki/extractors/decision.ts +217 -0
  52. package/src/wiki/extractors/feature.ts +206 -0
  53. package/src/wiki/extractors/workflow.ts +112 -0
  54. package/src/wiki/graph.ts +445 -0
  55. package/src/wiki/hooks.ts +49 -0
  56. package/src/wiki/indexer.ts +105 -0
  57. package/src/wiki/linker.ts +117 -0
  58. package/src/wiki/louvain.ts +190 -0
  59. package/src/wiki/prompts/compile-architecture.md +59 -0
  60. package/src/wiki/prompts/compile-decision.md +66 -0
  61. package/src/wiki/prompts/compile-entity.md +56 -0
  62. package/src/wiki/prompts/compile-feature.md +60 -0
  63. package/src/wiki/prompts/compile-module.md +42 -0
  64. package/src/wiki/prompts/wiki-query.md +25 -0
  65. package/src/wiki/query.ts +338 -0
  66. package/src/wiki/schema.ts +111 -0
  67. package/src/wiki/signals.ts +368 -0
  68. package/src/wiki/state.ts +89 -0
  69. package/src/wiki/tracking.ts +30 -0
  70. package/src/wiki/types.ts +169 -0
  71. package/src/workflow/context.ts +26 -0
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Louvain Community Detection — identifies module boundaries in the knowledge graph.
3
+ *
4
+ * Implements the Louvain method for community detection:
5
+ * 1. Start each node in its own community
6
+ * 2. For each node, try moving to a neighbor's community if it improves modularity
7
+ * 3. Repeat until no improvement (convergence)
8
+ * 4. Return communities map + modularity score
9
+ *
10
+ * Deterministic: nodes are sorted before iteration. Handles disconnected components.
11
+ * Scales to 1000+ nodes in < 1 second.
12
+ */
13
+
14
+ // ─── Types ───────────────────────────────────────────────────────────────
15
+
16
+ export interface LouvainNode {
17
+ id: string;
18
+ community: number;
19
+ }
20
+
21
+ export interface LouvainResult {
22
+ communities: Map<number, string[]>;
23
+ modularity: number;
24
+ }
25
+
26
+ // ─── Modularity Helpers ─────────────────────────────────────────────────
27
+
28
+ /**
29
+ * Total number of edges (each undirected edge counted once).
30
+ */
31
+ function totalEdges(adjacency: Map<string, Set<string>>): number {
32
+ let count = 0;
33
+ for (const neighbors of adjacency.values()) {
34
+ count += neighbors.size;
35
+ }
36
+ // Each edge is counted twice in an undirected adjacency list
37
+ return count / 2;
38
+ }
39
+
40
+ /**
41
+ * Compute modularity for the current community assignment.
42
+ * Q = (1/2m) * sum_ij [ A_ij - (k_i * k_j) / 2m ] * delta(c_i, c_j)
43
+ */
44
+ function computeModularity(
45
+ adjacency: Map<string, Set<string>>,
46
+ communityOf: Map<string, number>,
47
+ m: number,
48
+ ): number {
49
+ if (m === 0) return 0;
50
+
51
+ let q = 0;
52
+ const twoM = 2 * m;
53
+
54
+ for (const [nodeI, neighbors] of adjacency) {
55
+ const ci = communityOf.get(nodeI) ?? -1;
56
+ const ki = neighbors.size;
57
+
58
+ for (const nodeJ of neighbors) {
59
+ const cj = communityOf.get(nodeJ) ?? -1;
60
+ if (ci !== cj) continue;
61
+
62
+ const kj = adjacency.get(nodeJ)?.size ?? 0;
63
+ // A_ij = 1 since nodeJ is in neighbors of nodeI
64
+ q += 1 - (ki * kj) / twoM;
65
+ }
66
+ }
67
+
68
+ return q / twoM;
69
+ }
70
+
71
+ // ─── Public API ──────────────────────────────────────────────────────────
72
+
73
+ /**
74
+ * Detect communities using the Louvain method.
75
+ * Input: adjacency list (node -> Set<neighbor>).
76
+ * Output: communities map (community ID -> node IDs) + modularity score.
77
+ *
78
+ * Optimized with pre-computed degree map and running community degree totals
79
+ * to avoid O(N) scan per modularity gain computation.
80
+ */
81
+ export function detectCommunities(
82
+ adjacency: Map<string, Set<string>>,
83
+ ): LouvainResult {
84
+ const nodes = [...adjacency.keys()].sort();
85
+
86
+ if (nodes.length === 0) {
87
+ return { communities: new Map(), modularity: 0 };
88
+ }
89
+
90
+ const m = totalEdges(adjacency);
91
+
92
+ // Pre-compute degrees
93
+ const deg = new Map<string, number>();
94
+ for (const [node, neighbors] of adjacency) {
95
+ deg.set(node, neighbors.size);
96
+ }
97
+
98
+ // Phase 1: Assign each node to its own community
99
+ const communityOf = new Map<string, number>();
100
+ // Track sum of degrees per community for O(1) lookup
101
+ const commSumTot = new Map<number, number>();
102
+
103
+ for (let i = 0; i < nodes.length; i++) {
104
+ const node = nodes[i] ?? "";
105
+ communityOf.set(node, i);
106
+ commSumTot.set(i, deg.get(node) ?? 0);
107
+ }
108
+
109
+ // Phase 2: Iteratively move nodes to improve modularity
110
+ const maxIterations = 100;
111
+ for (let iter = 0; iter < maxIterations; iter++) {
112
+ let improved = false;
113
+
114
+ for (const nodeId of nodes) {
115
+ const currentComm = communityOf.get(nodeId) ?? 0;
116
+ const neighbors = adjacency.get(nodeId) ?? new Set<string>();
117
+ const ki = deg.get(nodeId) ?? 0;
118
+
119
+ if (ki === 0) continue;
120
+
121
+ // Find neighbor communities and count edges into each
122
+ const neighborCommEdges = new Map<number, number>();
123
+ for (const neighbor of neighbors) {
124
+ const nc = communityOf.get(neighbor) ?? -1;
125
+ neighborCommEdges.set(nc, (neighborCommEdges.get(nc) ?? 0) + 1);
126
+ }
127
+
128
+ // Current community edges (k_in for current)
129
+ const kInCurrent = neighborCommEdges.get(currentComm) ?? 0;
130
+ // sumTot for current community minus this node's own degree
131
+ const sumTotCurrent = (commSumTot.get(currentComm) ?? 0) - ki;
132
+
133
+ let bestGain = 0;
134
+ let bestComm = currentComm;
135
+
136
+ // Evaluate moving to each neighbor community
137
+ const candidateComms = [...neighborCommEdges.keys()]
138
+ .filter((c) => c !== currentComm)
139
+ .sort((a, b) => a - b);
140
+
141
+ for (const candidateComm of candidateComms) {
142
+ const kInCandidate = neighborCommEdges.get(candidateComm) ?? 0;
143
+ const sumTotCandidate = commSumTot.get(candidateComm) ?? 0;
144
+
145
+ // delta_Q = [k_in_new / m - ki * sumTot_new / (2m^2)]
146
+ // - [k_in_old / m - ki * sumTot_old / (2m^2)]
147
+ const gainNew = kInCandidate / m - (ki * sumTotCandidate) / (2 * m * m);
148
+ const lossCurrent = kInCurrent / m - (ki * sumTotCurrent) / (2 * m * m);
149
+ const netGain = gainNew - lossCurrent;
150
+
151
+ if (netGain > bestGain) {
152
+ bestGain = netGain;
153
+ bestComm = candidateComm;
154
+ }
155
+ }
156
+
157
+ if (bestComm !== currentComm) {
158
+ // Update community assignment and running totals
159
+ commSumTot.set(currentComm, (commSumTot.get(currentComm) ?? 0) - ki);
160
+ commSumTot.set(bestComm, (commSumTot.get(bestComm) ?? 0) + ki);
161
+ communityOf.set(nodeId, bestComm);
162
+ improved = true;
163
+ }
164
+ }
165
+
166
+ if (!improved) break;
167
+ }
168
+
169
+ // Phase 3: Build communities map, renumbering to be contiguous
170
+ const rawCommunities = new Map<number, string[]>();
171
+ for (const [node, comm] of communityOf) {
172
+ const list = rawCommunities.get(comm) ?? [];
173
+ list.push(node);
174
+ rawCommunities.set(comm, list);
175
+ }
176
+
177
+ // Renumber communities to 0..N-1
178
+ const communities = new Map<number, string[]>();
179
+ let idx = 0;
180
+ for (const [, members] of [...rawCommunities.entries()].sort(
181
+ ([a], [b]) => a - b,
182
+ )) {
183
+ communities.set(idx, members.sort());
184
+ idx++;
185
+ }
186
+
187
+ const modularity = computeModularity(adjacency, communityOf, m);
188
+
189
+ return { communities, modularity };
190
+ }
@@ -0,0 +1,59 @@
1
+ ---
2
+ task: compile-architecture
3
+ tier: mechanical
4
+ version: 1
5
+ ---
6
+
7
+ # Architecture Article Compilation
8
+
9
+ Given the following system overview:
10
+
11
+ Modules:
12
+ {module_list}
13
+
14
+ Module dependency graph:
15
+ {dependency_graph}
16
+
17
+ Community clusters (Louvain):
18
+ {communities}
19
+
20
+ Key decisions:
21
+ {key_decisions}
22
+
23
+ Cross-cutting concerns:
24
+ {cross_cutting}
25
+
26
+ Generate an architecture article that:
27
+ 1. Provides a high-level overview of the system structure
28
+ 2. Describes the module dependency graph and layering
29
+ 3. Identifies community clusters and their purposes
30
+ 4. Lists key architectural decisions that shaped the system
31
+ 5. Notes cross-cutting concerns (error handling, logging, auth patterns)
32
+ 6. Highlights potential architectural risks or debt
33
+
34
+ ## Output Format
35
+
36
+ ```markdown
37
+ # Architecture Overview
38
+
39
+ ## System Structure
40
+ {high-level description of how the system is organized}
41
+
42
+ ## Module Map
43
+ {list of modules with brief descriptions and layer assignments}
44
+
45
+ ## Dependency Graph
46
+ {description of how modules depend on each other}
47
+
48
+ ## Community Clusters
49
+ {groups of tightly-coupled modules with shared purpose}
50
+
51
+ ## Key Decisions
52
+ {links to important architectural decisions}
53
+
54
+ ## Cross-Cutting Concerns
55
+ {patterns that span multiple modules}
56
+
57
+ ## Risks
58
+ {architectural debt or areas needing attention}
59
+ ```
@@ -0,0 +1,66 @@
1
+ ---
2
+ task: compile-decision
3
+ tier: mechanical
4
+ version: 1
5
+ ---
6
+
7
+ # Decision Article Compilation
8
+
9
+ Given the following decision record:
10
+ - ID: {decision_id}
11
+ - Title: {decision_title}
12
+ - Status: {status}
13
+
14
+ Context:
15
+ {context}
16
+
17
+ Decision:
18
+ {decision}
19
+
20
+ Rationale:
21
+ {rationale}
22
+
23
+ Alternatives rejected:
24
+ {alternatives_rejected}
25
+
26
+ Entities affected:
27
+ {entity_mentions}
28
+
29
+ Constitution alignment:
30
+ {constitution_alignment}
31
+
32
+ Generate a decision article that:
33
+ 1. Clearly states the decision and its current status
34
+ 2. Explains the context that motivated the decision
35
+ 3. Documents the rationale with specific reasons
36
+ 4. Lists alternatives that were considered and why they were rejected
37
+ 5. Links to entities and modules affected by this decision
38
+ 6. Notes alignment with project constitution
39
+
40
+ ## Output Format
41
+
42
+ ```markdown
43
+ # {decision_title}
44
+
45
+ **Status:** {proposed|accepted|deprecated|superseded}
46
+ **ID:** {decision_id}
47
+
48
+ ## Context
49
+ {what problem or situation prompted this decision}
50
+
51
+ ## Decision
52
+ {the actual decision made}
53
+
54
+ ## Rationale
55
+ {why this decision was chosen over alternatives}
56
+
57
+ ## Alternatives Considered
58
+ {list of rejected alternatives with reasons}
59
+
60
+ ## Impact
61
+ - Entities affected: {links to entity articles}
62
+ - Modules affected: {links to module articles}
63
+
64
+ ## Constitution Alignment
65
+ {how this decision aligns with project principles}
66
+ ```
@@ -0,0 +1,56 @@
1
+ ---
2
+ task: compile-entity
3
+ tier: mechanical
4
+ version: 1
5
+ ---
6
+
7
+ # Entity Article Compilation
8
+
9
+ Given the following entity definition:
10
+ {entity_definition}
11
+
12
+ Located in module `{module_name}` at `{file_path}:{line_number}`:
13
+ {source_context}
14
+
15
+ With the following lifecycle context:
16
+ - Features that modified this entity: {features}
17
+ - Decisions that affect this entity: {decisions}
18
+ - Dependents (who uses this): {dependents}
19
+ - Dependencies (what this uses): {dependencies}
20
+
21
+ Generate an entity article that:
22
+ 1. Describes what the entity is and what it does
23
+ 2. Shows its type signature or interface
24
+ 3. Lists which features introduced or modified it
25
+ 4. References relevant architectural decisions
26
+ 5. Notes breaking change risk based on dependent count
27
+
28
+ ## Output Format
29
+
30
+ ```markdown
31
+ # {entity_name}
32
+
33
+ **Kind:** {function|class|interface|type|const|enum}
34
+ **File:** `{file_path}:{line_number}`
35
+ **Module:** [[modules/{module_name}.md]]
36
+
37
+ ## Description
38
+ {what this entity does and why it exists}
39
+
40
+ ## Signature
41
+ ```typescript
42
+ {type_signature_or_definition}
43
+ ```
44
+
45
+ ## Lifecycle
46
+ - Introduced by: [[features/{feature}.md]]
47
+ - Modified by: {list of feature links}
48
+ - Governed by: {list of decision links}
49
+
50
+ ## Dependencies
51
+ - Uses: {list of entity links}
52
+ - Used by: {list of entity links}
53
+
54
+ ## Change Risk
55
+ {low|medium|high} — {N} dependents
56
+ ```
@@ -0,0 +1,60 @@
1
+ ---
2
+ task: compile-feature
3
+ tier: mechanical
4
+ version: 1
5
+ ---
6
+
7
+ # Feature Article Compilation
8
+
9
+ Given the following feature metadata:
10
+ - ID: {feature_id}
11
+ - Title: {feature_title}
12
+ - Branch: {branch}
13
+ - PR: {pr_number}
14
+ - Merged: {merged}
15
+
16
+ Spec quality score: {spec_quality_score}
17
+ Spec assertions: {spec_assertions}
18
+
19
+ Tasks:
20
+ {task_list}
21
+
22
+ Entities modified:
23
+ {entities_modified}
24
+
25
+ Decisions created:
26
+ {decisions_created}
27
+
28
+ Generate a feature history article that:
29
+ 1. Summarizes what the feature does
30
+ 2. Lists acceptance criteria from the spec
31
+ 3. Shows implementation progress (completed tasks vs total)
32
+ 4. Links to entities that were added or modified
33
+ 5. Links to decisions that were made during implementation
34
+ 6. Notes the branch and PR for traceability
35
+
36
+ ## Output Format
37
+
38
+ ```markdown
39
+ # {feature_title}
40
+
41
+ **Feature:** {feature_id}
42
+ **Status:** {merged|in-progress|planned}
43
+ **Branch:** `{branch}`
44
+ **PR:** #{pr_number}
45
+
46
+ ## Summary
47
+ {what this feature does and why}
48
+
49
+ ## Acceptance Criteria
50
+ {list of spec assertions}
51
+
52
+ ## Implementation
53
+ {task_list with completion status}
54
+
55
+ ## Entities Modified
56
+ {links to entity articles}
57
+
58
+ ## Decisions
59
+ {links to decision articles created during this feature}
60
+ ```
@@ -0,0 +1,42 @@
1
+ ---
2
+ task: compile-module
3
+ tier: mechanical
4
+ version: 1
5
+ ---
6
+
7
+ # Module Article Compilation
8
+
9
+ Given the following entities in module `{module_name}`:
10
+ {entity_list}
11
+
12
+ And the following dependency relationships:
13
+ {dependencies}
14
+
15
+ Generate a clear, concise module overview article that:
16
+ 1. Describes what this module does in 1-2 sentences
17
+ 2. Lists key entities grouped by purpose (exports, types, internal helpers)
18
+ 3. Shows dependency relationships as a list of imports/exports
19
+ 4. Notes which other modules depend on this one (consumers)
20
+ 5. Links to related features and decisions using [[path]] notation
21
+
22
+ ## Output Format
23
+
24
+ ```markdown
25
+ # {module_name}
26
+
27
+ {one_line_description}
28
+
29
+ ## Purpose
30
+ {2-3 sentences explaining the module's role in the system}
31
+
32
+ ## Key Entities
33
+ {grouped list of functions, types, classes with brief descriptions}
34
+
35
+ ## Dependencies
36
+ - **Imports from:** {list of modules this depends on}
37
+ - **Exported to:** {list of modules that import from this}
38
+
39
+ ## Related
40
+ - Features: {links to feature articles}
41
+ - Decisions: {links to decision articles}
42
+ ```
@@ -0,0 +1,25 @@
1
+ ---
2
+ task: wiki-query
3
+ tier: mechanical
4
+ version: 1
5
+ ---
6
+
7
+ # Wiki Query Synthesis
8
+
9
+ You are answering a question about a codebase using wiki documentation.
10
+
11
+ Question: {question}
12
+
13
+ Here are relevant wiki articles:
14
+
15
+ {articles}
16
+
17
+ ## Instructions
18
+
19
+ 1. Provide a clear, concise answer that directly addresses the question
20
+ 2. Cite relevant articles using [[article_path]] notation (e.g., [[modules/core.md]])
21
+ 3. If the articles don't contain enough information, say so explicitly
22
+ 4. Do NOT guess or hallucinate information not present in the articles
23
+ 5. If anything is ambiguous, use [NEEDS CLARIFICATION: specific question]
24
+ 6. Prioritize accuracy over completeness
25
+ 7. Keep the answer focused — avoid restating the entire article content