@10kdevs/matha 0.1.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/README.md +222 -0
- package/dist/analysis/contract-matcher.js +223 -0
- package/dist/analysis/git-analyser.js +261 -0
- package/dist/analysis/stability-classifier.js +122 -0
- package/dist/brain/cortex.js +258 -0
- package/dist/brain/dopamine.js +184 -0
- package/dist/brain/frontal-lobe.js +219 -0
- package/dist/brain/hippocampus.js +134 -0
- package/dist/commands/after.js +334 -0
- package/dist/commands/before.js +328 -0
- package/dist/commands/init.js +266 -0
- package/dist/commands/migrate.js +16 -0
- package/dist/index.js +114 -0
- package/dist/mcp/server.js +305 -0
- package/dist/mcp/tools.js +379 -0
- package/dist/storage/reader.js +29 -0
- package/dist/storage/writer.js +111 -0
- package/dist/utils/markdown-parser.js +173 -0
- package/dist/utils/schema-version.js +91 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bhupesh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# MATHA
|
|
2
|
+
|
|
3
|
+
**The persistent cognitive layer for AI-assisted development.**
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You gave the AI your entire codebase. You explained the business logic for two hours. You did a Q&A, walked through every edge case, corrected every misunderstanding.
|
|
8
|
+
|
|
9
|
+
Then you closed the session.
|
|
10
|
+
|
|
11
|
+
The next day you opened a new one. And it remembered nothing.
|
|
12
|
+
|
|
13
|
+
Not the HWM calculation that took three sessions to get right. Not the rule about deposit events not triggering profit cycles. Not the assumption that broke everything in V1 and cost you twelve days to fix. Gone. Cold start. Again.
|
|
14
|
+
|
|
15
|
+
This is not an AI intelligence problem. Every tool — Cursor, Copilot, Claude Code, Windsurf — is stateless by design. They are powerful and amnesiac in equal measure. The larger your project, the more painful that amnesia becomes.
|
|
16
|
+
|
|
17
|
+
MATHA fixes this.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## What MATHA Does
|
|
22
|
+
|
|
23
|
+
MATHA is a local MCP server that runs alongside your project and gives any AI agent the context that currently only exists inside a senior engineer's head.
|
|
24
|
+
|
|
25
|
+
It captures three things that no markdown file can hold:
|
|
26
|
+
|
|
27
|
+
**Intent** — why the project exists, what the non-negotiable rules are, what it explicitly does not do. Not features. The reasoning behind them.
|
|
28
|
+
|
|
29
|
+
**Decisions** — every assumption that broke, every correction that was made, every "no that's wrong" moment that cost you days. Captured automatically from the work itself, not from humans writing docs after the fact.
|
|
30
|
+
|
|
31
|
+
**Behaviour contracts** — what the system must do, written before code is written. Machine-verifiable. Validated after every session. Violations recorded and surfaced automatically next time.
|
|
32
|
+
|
|
33
|
+
Every session writes back what it learned. Every session starts warmer than the last. The brain stays on.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## How It Works
|
|
38
|
+
|
|
39
|
+
Three commands. That's the entire interface.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Once per project — seeds the brain from your project's intent and git history
|
|
43
|
+
matha init
|
|
44
|
+
|
|
45
|
+
# Before every AI session — surfaces what the brain knows, fires warnings,
|
|
46
|
+
# writes behaviour contract before a single line of code is written
|
|
47
|
+
matha before
|
|
48
|
+
|
|
49
|
+
# After every AI session — captures what was learned, records violations,
|
|
50
|
+
# updates the brain so the next session starts with full context
|
|
51
|
+
matha after
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The session brief produced by `matha before` is copy-pasteable directly into any AI agent. It tells the agent what it needs to know before it touches anything — danger zones, prior decisions, frozen files, behaviour contract.
|
|
55
|
+
|
|
56
|
+
The write-back from `matha after` means that correction never has to be made twice.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Installation
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm install -g matha
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Zero-install first run:
|
|
67
|
+
```bash
|
|
68
|
+
npx matha init
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Requires Node.js 20+. No API key. No cloud dependency. All data stays in your repository.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Connecting To Your IDE
|
|
76
|
+
|
|
77
|
+
After `matha init`, connect MATHA to your IDE via MCP.
|
|
78
|
+
|
|
79
|
+
The init command writes `.matha/mcp-config.json` with the exact config for your machine. Add it to your IDE's MCP configuration.
|
|
80
|
+
|
|
81
|
+
**Claude Code:**
|
|
82
|
+
```bash
|
|
83
|
+
# .matha/mcp-config.json contains the correct config
|
|
84
|
+
# Add to your Claude Code MCP settings
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Cursor:**
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"mcpServers": {
|
|
91
|
+
"matha": {
|
|
92
|
+
"command": "node",
|
|
93
|
+
"args": ["/path/to/your/project/node_modules/.bin/matha", "serve"]
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Once connected, your AI agent can call `matha_brief()` as its first action in any session — receiving the full project context before writing a line.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## The Eight Gates
|
|
104
|
+
|
|
105
|
+
`matha before` runs eight structured gates before allowing the AI to build. Not as a prompt. As enforced infrastructure.
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
GATE 01 UNDERSTAND What is the WHY of this change?
|
|
109
|
+
GATE 02 BOUND What are the non-negotiable rules?
|
|
110
|
+
GATE 03 ORIENT What exists? What is stable, frozen, volatile?
|
|
111
|
+
GATE 04 SURFACE DANGER Any prior failures in this area?
|
|
112
|
+
GATE 05 CONTRACT What must be true after? Written before code.
|
|
113
|
+
GATE 06 COST CHECK What model tier? What token budget?
|
|
114
|
+
GATE 07 BUILD AI is now allowed to generate code.
|
|
115
|
+
GATE 08 WRITE BACK What was learned? Captured. Never lost.
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Gate 07 does not open until Gates 01 through 05 are complete.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## The Brain
|
|
123
|
+
|
|
124
|
+
MATHA's knowledge lives in `.matha/` in your repository. Committed to version control. Owned by your team. Never sent anywhere.
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
.matha/
|
|
128
|
+
├── hippocampus/ intent, rules, decisions, danger zones
|
|
129
|
+
├── cerebellum/ behaviour contracts, violation log
|
|
130
|
+
├── cortex/ stability map, co-change graph, boundaries
|
|
131
|
+
├── dopamine/ session history, routing rules, deltas
|
|
132
|
+
└── sessions/ session briefs
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The cortex builds itself from git history. Files that change together are linked. Files with low churn and high connectivity are classified frozen — AI agents are warned before touching them.
|
|
136
|
+
|
|
137
|
+
The dopamine loop learns from every session. If business logic changes in your project consistently burn three times the predicted token budget, MATHA adjusts its recommendation automatically. It tells you why.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## MCP Tools
|
|
142
|
+
|
|
143
|
+
AI agents connected via MCP have access to:
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
matha_brief(scope?, directory?) Full session context
|
|
147
|
+
matha_get_rules() Non-negotiable business rules
|
|
148
|
+
matha_get_danger_zones(context?) Known failure patterns
|
|
149
|
+
matha_get_decisions(component?) Decision history
|
|
150
|
+
matha_get_stability(files[]) Stability classification per file
|
|
151
|
+
matha_match(scope, intent) Full cerebellum match — what does
|
|
152
|
+
the brain know about this operation?
|
|
153
|
+
matha_record_decision(...) Write a decision back to the brain
|
|
154
|
+
matha_record_danger(...) Flag a new danger zone
|
|
155
|
+
matha_record_contract(...) Store a behaviour contract
|
|
156
|
+
matha_refresh_cortex() Rebuild from git history
|
|
157
|
+
matha_get_routing(operationType?) Learned model routing rules
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Initialising From An Existing Document
|
|
163
|
+
|
|
164
|
+
If your project already has a BRD, spec, or requirements document:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
matha init --from requirements.md
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
MATHA parses business rules, boundaries, and intent from the document and pre-fills the init prompts. You review and confirm. Nothing is written without your sign-off.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## What MATHA Is Not
|
|
175
|
+
|
|
176
|
+
MATHA does not generate code.
|
|
177
|
+
|
|
178
|
+
MATHA does not replace your IDE, your AI model, or your version control.
|
|
179
|
+
|
|
180
|
+
MATHA does not require a specific model, a specific IDE, or a specific language. If your tool supports MCP, it connects to MATHA on day one.
|
|
181
|
+
|
|
182
|
+
MATHA does not send your data anywhere. The brain lives in your repository.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## The Real Problem It Solves
|
|
187
|
+
|
|
188
|
+
There is a moment every developer who uses AI tools eventually hits.
|
|
189
|
+
|
|
190
|
+
The project is large enough that you cannot hold all of it in your head. The AI cannot hold all of it in its context window. You find yourself explaining the same decision three times across three sessions. You find a bug and you are not confident which part of the codebase is responsible. You realise you have forgotten the core logic you wrote four weeks ago.
|
|
191
|
+
|
|
192
|
+
At that moment, the AI is not the problem. The absence of accumulated understanding is the problem.
|
|
193
|
+
|
|
194
|
+
MATHA is the accumulated understanding.
|
|
195
|
+
|
|
196
|
+
It is what the senior engineer carries in their head after three years on a project — the why behind every decision, the scars from every production incident, the rules that cannot be broken and the code that cannot be touched without knowing why it works the way it does.
|
|
197
|
+
|
|
198
|
+
That knowledge survives sessions. It survives team changes. It survives the moment you come back to a project six months after you last touched it.
|
|
199
|
+
|
|
200
|
+
The code gets better. The context never resets. The brain stays on.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Case Study
|
|
205
|
+
|
|
206
|
+
[How MATHA would have saved 12 days on a real PAMM trading platform →](docs/pamm-case-study.md)
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Contributing
|
|
211
|
+
|
|
212
|
+
[CONTRIBUTING.md](CONTRIBUTING.md)
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## License
|
|
217
|
+
|
|
218
|
+
MIT — owned by nobody, usable by everyone.
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
*Built because the problem was real.*
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import * as fs from 'fs/promises';
|
|
3
|
+
import { getDangerZones, getDecisions } from '../brain/hippocampus.js';
|
|
4
|
+
import { getSnapshot } from '../brain/cortex.js';
|
|
5
|
+
const STOP_WORDS = new Set([
|
|
6
|
+
'should', 'never', 'always', 'before', 'after', 'every', 'which',
|
|
7
|
+
'their', 'there', 'where', 'when', 'would', 'could', 'might',
|
|
8
|
+
'using', 'being', 'having'
|
|
9
|
+
]);
|
|
10
|
+
export function extractKeywords(text) {
|
|
11
|
+
const words = text
|
|
12
|
+
.toLowerCase()
|
|
13
|
+
.replace(/[^\w\s]/g, ' ')
|
|
14
|
+
.split(/\s+/);
|
|
15
|
+
return words.filter(word => word.length > 4 && !STOP_WORDS.has(word));
|
|
16
|
+
}
|
|
17
|
+
export function matchDangerZones(context, dangerZones) {
|
|
18
|
+
const results = [];
|
|
19
|
+
const scopeLower = context.scope.toLowerCase();
|
|
20
|
+
const intentLower = context.intent.toLowerCase();
|
|
21
|
+
for (const zone of dangerZones) {
|
|
22
|
+
const compLower = (zone.component || '').toLowerCase();
|
|
23
|
+
// Check scope match
|
|
24
|
+
let matched = scopeLower.includes(compLower);
|
|
25
|
+
// Check intent match
|
|
26
|
+
if (!matched) {
|
|
27
|
+
matched = intentLower.includes(compLower);
|
|
28
|
+
}
|
|
29
|
+
// Check keyword match
|
|
30
|
+
if (!matched && zone.description) {
|
|
31
|
+
const zoneKeywords = extractKeywords(zone.description);
|
|
32
|
+
matched = zoneKeywords.some(kw => intentLower.includes(kw));
|
|
33
|
+
}
|
|
34
|
+
if (matched) {
|
|
35
|
+
results.push({
|
|
36
|
+
matchType: 'danger_zone',
|
|
37
|
+
severity: 'critical',
|
|
38
|
+
title: `Danger Zone: ${zone.component}`,
|
|
39
|
+
description: zone.description || '',
|
|
40
|
+
source: 'danger-zones.json',
|
|
41
|
+
component: zone.component,
|
|
42
|
+
recommendation: 'Review danger zone before proceeding. Consider matha_record_decision after session.',
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return results;
|
|
47
|
+
}
|
|
48
|
+
export function matchContracts(context, contracts) {
|
|
49
|
+
const results = [];
|
|
50
|
+
const scopeLower = context.scope.toLowerCase();
|
|
51
|
+
for (const [key, contract] of Object.entries(contracts)) {
|
|
52
|
+
const compLower = (contract.component || '').toLowerCase();
|
|
53
|
+
if (scopeLower.includes(compLower)) {
|
|
54
|
+
const violations = (contract.assertions || []).filter((a) => a.violation_count > 0);
|
|
55
|
+
const hasViolations = violations.length > 0;
|
|
56
|
+
results.push({
|
|
57
|
+
matchType: 'contract',
|
|
58
|
+
severity: hasViolations ? 'critical' : 'info',
|
|
59
|
+
title: `Contract: ${contract.component}`,
|
|
60
|
+
description: hasViolations
|
|
61
|
+
? `Previously violated ${violations.length} times.`
|
|
62
|
+
: 'Contract is currently clean.',
|
|
63
|
+
source: 'contracts',
|
|
64
|
+
component: contract.component,
|
|
65
|
+
recommendation: `Verify all ${(contract.assertions || []).length} contract assertions pass after changes.`,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return results;
|
|
70
|
+
}
|
|
71
|
+
export function matchFrozenFiles(context, stabilityRecords) {
|
|
72
|
+
const results = [];
|
|
73
|
+
// Create a map of frozen files for quick lookup
|
|
74
|
+
const frozenFiles = stabilityRecords.filter(r => r.stability === 'frozen');
|
|
75
|
+
if (context.filepaths && context.filepaths.length > 0) {
|
|
76
|
+
for (const filepath of context.filepaths) {
|
|
77
|
+
// Find matching frozen record by exact path
|
|
78
|
+
const record = frozenFiles.find(r => r.filepath === filepath);
|
|
79
|
+
if (record) {
|
|
80
|
+
results.push({
|
|
81
|
+
matchType: 'frozen_file',
|
|
82
|
+
severity: 'critical',
|
|
83
|
+
title: `Frozen File: ${filepath}`,
|
|
84
|
+
description: record.reason || 'No reason provided',
|
|
85
|
+
source: 'cortex/stability.json',
|
|
86
|
+
component: filepath,
|
|
87
|
+
recommendation: 'This file is classified FROZEN. Confirm owner approval before modifying.',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
// Fall back to scope substring match
|
|
94
|
+
const scopeLower = context.scope.toLowerCase();
|
|
95
|
+
const scopeParts = scopeLower.split(',').map(s => s.trim()).filter(Boolean);
|
|
96
|
+
for (const record of frozenFiles) {
|
|
97
|
+
const recordFpLower = record.filepath.toLowerCase();
|
|
98
|
+
// Match if the scope part is a substring of the filepath, OR if filepath is a substring of the scope part
|
|
99
|
+
const isMatched = scopeParts.some(part => recordFpLower.includes(part) || part.includes(recordFpLower));
|
|
100
|
+
if (isMatched) {
|
|
101
|
+
results.push({
|
|
102
|
+
matchType: 'frozen_file',
|
|
103
|
+
severity: 'critical',
|
|
104
|
+
title: `Frozen File: ${record.filepath}`,
|
|
105
|
+
description: record.reason || 'No reason provided',
|
|
106
|
+
source: 'cortex/stability.json',
|
|
107
|
+
component: record.filepath,
|
|
108
|
+
recommendation: 'This file is classified FROZEN. Confirm owner approval before modifying.',
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return results;
|
|
114
|
+
}
|
|
115
|
+
export function matchDecisionPatterns(context, decisions) {
|
|
116
|
+
const results = [];
|
|
117
|
+
const scopeLower = context.scope.toLowerCase();
|
|
118
|
+
// Sort decisions by timestamp descending
|
|
119
|
+
const sortedDecisions = [...decisions].sort((a, b) => {
|
|
120
|
+
return new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime();
|
|
121
|
+
});
|
|
122
|
+
// Filter only active decisions
|
|
123
|
+
const activeDecisions = sortedDecisions.filter(d => d.status === 'active');
|
|
124
|
+
let matchCount = 0;
|
|
125
|
+
for (const decision of activeDecisions) {
|
|
126
|
+
if (matchCount >= 3)
|
|
127
|
+
break;
|
|
128
|
+
const compLower = (decision.component || '').toLowerCase();
|
|
129
|
+
if (scopeLower.includes(compLower)) {
|
|
130
|
+
results.push({
|
|
131
|
+
matchType: 'decision_pattern',
|
|
132
|
+
severity: 'warning',
|
|
133
|
+
title: `Prior Decision: ${decision.component}`,
|
|
134
|
+
description: `Previous assumption: ${decision.previous_assumption}. Correction: ${decision.correction}.`,
|
|
135
|
+
source: 'hippocampus/decisions',
|
|
136
|
+
component: decision.component,
|
|
137
|
+
recommendation: 'Be aware of this prior correction when working in this area.',
|
|
138
|
+
});
|
|
139
|
+
matchCount++;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return results;
|
|
143
|
+
}
|
|
144
|
+
// Helper to determine sort order
|
|
145
|
+
const SEVERITY_ORDER = {
|
|
146
|
+
critical: 0,
|
|
147
|
+
warning: 1,
|
|
148
|
+
info: 2,
|
|
149
|
+
};
|
|
150
|
+
export async function matchAll(context, mathaDir) {
|
|
151
|
+
try {
|
|
152
|
+
const allResults = [];
|
|
153
|
+
// 1. Fetch Danger Zones
|
|
154
|
+
try {
|
|
155
|
+
const dangerZones = await getDangerZones(mathaDir).catch(() => []);
|
|
156
|
+
allResults.push(...matchDangerZones(context, dangerZones));
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// Ignore errors for danger zones
|
|
160
|
+
}
|
|
161
|
+
// 2. Fetch Contracts
|
|
162
|
+
try {
|
|
163
|
+
const contractsDir = path.join(mathaDir, 'cerebellum', 'contracts');
|
|
164
|
+
const contractsFiles = await fs.readdir(contractsDir).catch(() => []);
|
|
165
|
+
const contracts = {};
|
|
166
|
+
for (const file of contractsFiles) {
|
|
167
|
+
if (file.endsWith('.json')) {
|
|
168
|
+
try {
|
|
169
|
+
const content = await fs.readFile(path.join(contractsDir, file), 'utf-8');
|
|
170
|
+
const parsed = JSON.parse(content);
|
|
171
|
+
if (parsed.component) {
|
|
172
|
+
contracts[parsed.component] = parsed;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
// ignore bad files
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
allResults.push(...matchContracts(context, contracts));
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
// Ignore errors for contracts
|
|
184
|
+
}
|
|
185
|
+
// 3. Fetch Frozen Files
|
|
186
|
+
try {
|
|
187
|
+
const snapshot = await getSnapshot(mathaDir).catch(() => null);
|
|
188
|
+
if (snapshot && snapshot.stability) {
|
|
189
|
+
allResults.push(...matchFrozenFiles(context, snapshot.stability));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// Ignore errors for frozen files
|
|
194
|
+
}
|
|
195
|
+
// 4. Fetch Decisions
|
|
196
|
+
try {
|
|
197
|
+
const decisions = await getDecisions(mathaDir).catch(() => []);
|
|
198
|
+
allResults.push(...matchDecisionPatterns(context, decisions));
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// Ignore errors for decisions
|
|
202
|
+
}
|
|
203
|
+
// Deduplicate by matchType + component
|
|
204
|
+
const seen = new Set();
|
|
205
|
+
const deduplicated = [];
|
|
206
|
+
for (const result of allResults) {
|
|
207
|
+
const key = `${result.matchType}:${result.component.toLowerCase()}`;
|
|
208
|
+
if (!seen.has(key)) {
|
|
209
|
+
seen.add(key);
|
|
210
|
+
deduplicated.push(result);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Sort by severity (critical -> warning -> info)
|
|
214
|
+
deduplicated.sort((a, b) => {
|
|
215
|
+
return SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity];
|
|
216
|
+
});
|
|
217
|
+
return deduplicated;
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
// Never throw - return empty array on catastrophic failure
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
}
|