@mainahq/core 1.0.2 → 1.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/package.json +1 -1
- package/src/ai/__tests__/availability.test.ts +131 -0
- package/src/ai/__tests__/delegation.test.ts +55 -1
- package/src/ai/availability.ts +23 -0
- package/src/ai/delegation.ts +5 -3
- package/src/context/__tests__/budget.test.ts +29 -6
- package/src/context/__tests__/engine.test.ts +1 -0
- package/src/context/__tests__/selector.test.ts +23 -3
- package/src/context/__tests__/wiki.test.ts +349 -0
- package/src/context/budget.ts +12 -8
- package/src/context/engine.ts +37 -0
- package/src/context/selector.ts +30 -4
- package/src/context/wiki.ts +296 -0
- package/src/db/index.ts +12 -0
- package/src/feedback/__tests__/capture.test.ts +166 -0
- package/src/feedback/__tests__/signals.test.ts +144 -0
- package/src/feedback/__tests__/tmp-capture-1775575256633-lah0etnzlj/feedback.db +0 -0
- package/src/feedback/__tests__/tmp-capture-1775575256640-2xmjme4qraa/feedback.db +0 -0
- package/src/feedback/capture.ts +102 -0
- package/src/feedback/signals.ts +68 -0
- package/src/index.ts +108 -1
- package/src/init/__tests__/init.test.ts +477 -18
- package/src/init/index.ts +419 -13
- package/src/language/__tests__/__fixtures__/detect/composer.lock +1 -0
- package/src/prompts/defaults/index.ts +3 -1
- package/src/prompts/defaults/wiki-compile.md +20 -0
- package/src/prompts/defaults/wiki-query.md +18 -0
- package/src/stats/__tests__/tool-usage.test.ts +133 -0
- package/src/stats/tracker.ts +92 -0
- package/src/verify/__tests__/builtin.test.ts +270 -0
- package/src/verify/__tests__/pipeline.test.ts +11 -8
- package/src/verify/builtin.ts +350 -0
- package/src/verify/pipeline.ts +32 -2
- package/src/verify/tools/__tests__/wiki-lint.test.ts +784 -0
- package/src/verify/tools/wiki-lint-runner.ts +38 -0
- package/src/verify/tools/wiki-lint.ts +898 -0
- package/src/wiki/__tests__/compiler.test.ts +389 -0
- package/src/wiki/__tests__/extractors/code.test.ts +99 -0
- package/src/wiki/__tests__/extractors/decision.test.ts +323 -0
- package/src/wiki/__tests__/extractors/feature.test.ts +186 -0
- package/src/wiki/__tests__/extractors/workflow.test.ts +131 -0
- package/src/wiki/__tests__/graph.test.ts +344 -0
- package/src/wiki/__tests__/hooks.test.ts +119 -0
- package/src/wiki/__tests__/indexer.test.ts +285 -0
- package/src/wiki/__tests__/linker.test.ts +230 -0
- package/src/wiki/__tests__/louvain.test.ts +229 -0
- package/src/wiki/__tests__/query.test.ts +316 -0
- package/src/wiki/__tests__/schema.test.ts +114 -0
- package/src/wiki/__tests__/signals.test.ts +474 -0
- package/src/wiki/__tests__/state.test.ts +168 -0
- package/src/wiki/__tests__/tracking.test.ts +118 -0
- package/src/wiki/__tests__/types.test.ts +387 -0
- package/src/wiki/compiler.ts +1075 -0
- package/src/wiki/extractors/code.ts +90 -0
- package/src/wiki/extractors/decision.ts +217 -0
- package/src/wiki/extractors/feature.ts +206 -0
- package/src/wiki/extractors/workflow.ts +112 -0
- package/src/wiki/graph.ts +445 -0
- package/src/wiki/hooks.ts +49 -0
- package/src/wiki/indexer.ts +105 -0
- package/src/wiki/linker.ts +117 -0
- package/src/wiki/louvain.ts +190 -0
- package/src/wiki/prompts/compile-architecture.md +59 -0
- package/src/wiki/prompts/compile-decision.md +66 -0
- package/src/wiki/prompts/compile-entity.md +56 -0
- package/src/wiki/prompts/compile-feature.md +60 -0
- package/src/wiki/prompts/compile-module.md +42 -0
- package/src/wiki/prompts/wiki-query.md +25 -0
- package/src/wiki/query.ts +338 -0
- package/src/wiki/schema.ts +111 -0
- package/src/wiki/signals.ts +368 -0
- package/src/wiki/state.ts +89 -0
- package/src/wiki/tracking.ts +30 -0
- package/src/wiki/types.ts +169 -0
- package/src/workflow/context.ts +26 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in Verify Checks — pure-function checks that always run.
|
|
3
|
+
*
|
|
4
|
+
* These provide baseline verification without requiring external linters.
|
|
5
|
+
* Each check is a pure function: (filePath, content) => Finding[].
|
|
6
|
+
* No I/O, no side effects, no subprocess spawns.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Finding } from "./diff-filter";
|
|
10
|
+
|
|
11
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
function isTestFile(filePath: string): boolean {
|
|
14
|
+
return (
|
|
15
|
+
filePath.endsWith(".test.ts") ||
|
|
16
|
+
filePath.endsWith(".test.tsx") ||
|
|
17
|
+
filePath.endsWith(".test.js") ||
|
|
18
|
+
filePath.endsWith(".test.jsx") ||
|
|
19
|
+
filePath.endsWith(".spec.ts") ||
|
|
20
|
+
filePath.endsWith(".spec.tsx") ||
|
|
21
|
+
filePath.endsWith(".spec.js") ||
|
|
22
|
+
filePath.endsWith(".spec.jsx") ||
|
|
23
|
+
filePath.includes("__tests__/")
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isDeclarationFile(filePath: string): boolean {
|
|
28
|
+
return filePath.endsWith(".d.ts");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isTypeScriptFile(filePath: string): boolean {
|
|
32
|
+
return (
|
|
33
|
+
filePath.endsWith(".ts") ||
|
|
34
|
+
filePath.endsWith(".tsx") ||
|
|
35
|
+
filePath.endsWith(".mts") ||
|
|
36
|
+
filePath.endsWith(".cts")
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ─── Check 1: console.log in non-test files ─────────────────────────────
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Detect console.log/warn/error/debug/info calls in production code.
|
|
44
|
+
* Test files are excluded since console usage is acceptable there.
|
|
45
|
+
*/
|
|
46
|
+
export function checkConsoleLogs(filePath: string, content: string): Finding[] {
|
|
47
|
+
if (isTestFile(filePath)) return [];
|
|
48
|
+
|
|
49
|
+
const findings: Finding[] = [];
|
|
50
|
+
const lines = content.split("\n");
|
|
51
|
+
const consolePattern = /\bconsole\.(log|warn|error|debug|info)\s*\(/;
|
|
52
|
+
|
|
53
|
+
for (const [i, line] of lines.entries()) {
|
|
54
|
+
if (consolePattern.test(line)) {
|
|
55
|
+
findings.push({
|
|
56
|
+
tool: "builtin",
|
|
57
|
+
file: filePath,
|
|
58
|
+
line: i + 1,
|
|
59
|
+
message: `console.${line.match(consolePattern)?.[1]} found in production code`,
|
|
60
|
+
severity: "warning",
|
|
61
|
+
ruleId: "no-console-log",
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return findings;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ─── Check 2: Unused imports ─────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Best-effort regex check for unused named imports.
|
|
73
|
+
* Looks for `import { X, Y }` where identifiers don't appear
|
|
74
|
+
* elsewhere in the file. Prefers false negatives over false positives.
|
|
75
|
+
*/
|
|
76
|
+
export function checkUnusedImports(
|
|
77
|
+
filePath: string,
|
|
78
|
+
content: string,
|
|
79
|
+
): Finding[] {
|
|
80
|
+
const findings: Finding[] = [];
|
|
81
|
+
const lines = content.split("\n");
|
|
82
|
+
|
|
83
|
+
// Match named imports: import { A, B } from "..." or import type { A } from "..."
|
|
84
|
+
const importLinePattern =
|
|
85
|
+
/^import\s+(?:type\s+)?{([^}]+)}\s+from\s+["'][^"']+["'];?\s*$/;
|
|
86
|
+
|
|
87
|
+
for (const [i, line] of lines.entries()) {
|
|
88
|
+
const match = line.match(importLinePattern);
|
|
89
|
+
if (!match) continue;
|
|
90
|
+
|
|
91
|
+
// Check if this is a type-only import (import type { ... })
|
|
92
|
+
const isTypeImport = /^import\s+type\s+\{/.test(line);
|
|
93
|
+
|
|
94
|
+
const rawNames = match[1]?.split(",") ?? [];
|
|
95
|
+
const importedNames: string[] = [];
|
|
96
|
+
for (const raw of rawNames) {
|
|
97
|
+
// Handle "X as Y" — the local name is Y
|
|
98
|
+
const parts = raw.trim().split(/\s+as\s+/);
|
|
99
|
+
const resolved = (parts.length > 1 ? parts[1] : parts[0])?.trim() ?? "";
|
|
100
|
+
if (resolved.length > 0) {
|
|
101
|
+
importedNames.push(resolved);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Get the rest of the file content (excluding import lines)
|
|
106
|
+
const restOfFile = lines
|
|
107
|
+
.filter((l) => !importLinePattern.test(l))
|
|
108
|
+
.join("\n");
|
|
109
|
+
|
|
110
|
+
for (const name of importedNames) {
|
|
111
|
+
// Check if the identifier appears in the rest of the file
|
|
112
|
+
// Use word boundary to avoid matching substrings
|
|
113
|
+
const usagePattern = new RegExp(`\\b${escapeRegex(name)}\\b`);
|
|
114
|
+
if (!usagePattern.test(restOfFile)) {
|
|
115
|
+
findings.push({
|
|
116
|
+
tool: "builtin",
|
|
117
|
+
file: filePath,
|
|
118
|
+
line: i + 1,
|
|
119
|
+
message: `Import '${name}' appears unused${isTypeImport ? " (type import)" : ""}`,
|
|
120
|
+
severity: "warning",
|
|
121
|
+
ruleId: "unused-import",
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return findings;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function escapeRegex(str: string): string {
|
|
131
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ─── Check 3: TODO/FIXME/HACK comments ──────────────────────────────────
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Count and report TODO, FIXME, and HACK markers.
|
|
138
|
+
* These are informational — they don't block verification.
|
|
139
|
+
*/
|
|
140
|
+
export function checkTodoComments(
|
|
141
|
+
filePath: string,
|
|
142
|
+
content: string,
|
|
143
|
+
): Finding[] {
|
|
144
|
+
const findings: Finding[] = [];
|
|
145
|
+
const lines = content.split("\n");
|
|
146
|
+
const todoPattern = /\b(TODO|FIXME|HACK)\b/;
|
|
147
|
+
|
|
148
|
+
for (const [i, line] of lines.entries()) {
|
|
149
|
+
const match = line.match(todoPattern);
|
|
150
|
+
if (match) {
|
|
151
|
+
findings.push({
|
|
152
|
+
tool: "builtin",
|
|
153
|
+
file: filePath,
|
|
154
|
+
line: i + 1,
|
|
155
|
+
message: `${match[1]} comment found: ${line.trim()}`,
|
|
156
|
+
severity: "info",
|
|
157
|
+
ruleId: "todo-comment",
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return findings;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ─── Check 4: File size ──────────────────────────────────────────────────
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Flag files exceeding 500 lines. Large files are harder to review
|
|
169
|
+
* and maintain — consider splitting them.
|
|
170
|
+
*/
|
|
171
|
+
export function checkFileSize(filePath: string, content: string): Finding[] {
|
|
172
|
+
const lineCount = content.split("\n").length;
|
|
173
|
+
|
|
174
|
+
if (lineCount > 500) {
|
|
175
|
+
return [
|
|
176
|
+
{
|
|
177
|
+
tool: "builtin",
|
|
178
|
+
file: filePath,
|
|
179
|
+
line: 1,
|
|
180
|
+
message: `File has ${lineCount} lines (exceeds 500 line limit). Consider splitting.`,
|
|
181
|
+
severity: "warning",
|
|
182
|
+
ruleId: "file-too-long",
|
|
183
|
+
},
|
|
184
|
+
];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ─── Check 5: Secrets patterns ──────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Detect hardcoded secrets: password=, secret=, token=, api_key=
|
|
194
|
+
* followed by a quoted or literal non-empty value (not a variable reference).
|
|
195
|
+
*/
|
|
196
|
+
export function checkSecrets(filePath: string, content: string): Finding[] {
|
|
197
|
+
const findings: Finding[] = [];
|
|
198
|
+
const lines = content.split("\n");
|
|
199
|
+
|
|
200
|
+
// Patterns: key followed by = and a hardcoded value (quoted string or bare literal)
|
|
201
|
+
// Does NOT match variable references like process.env.X, ${VAR}, etc.
|
|
202
|
+
const secretPattern =
|
|
203
|
+
/\b(password|secret|token|api_key|apikey|api_secret|private_key|auth_token)\s*[=:]\s*["'`]([^"'`\s$]{2,})["'`]/i;
|
|
204
|
+
|
|
205
|
+
for (const [i, line] of lines.entries()) {
|
|
206
|
+
const match = line.match(secretPattern);
|
|
207
|
+
if (match) {
|
|
208
|
+
findings.push({
|
|
209
|
+
tool: "builtin",
|
|
210
|
+
file: filePath,
|
|
211
|
+
line: i + 1,
|
|
212
|
+
message: `Possible hardcoded ${match[1]} detected`,
|
|
213
|
+
severity: "error",
|
|
214
|
+
ruleId: "hardcoded-secret",
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return findings;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ─── Check 6: Empty catch blocks ─────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Detect empty catch blocks (no statements, no comments).
|
|
226
|
+
* A catch with only whitespace is still flagged.
|
|
227
|
+
* A catch with a comment is considered intentional and allowed.
|
|
228
|
+
*/
|
|
229
|
+
export function checkEmptyCatch(filePath: string, content: string): Finding[] {
|
|
230
|
+
const findings: Finding[] = [];
|
|
231
|
+
const lines = content.split("\n");
|
|
232
|
+
|
|
233
|
+
for (const [i, line] of lines.entries()) {
|
|
234
|
+
// Match catch on same line: catch (e) {}
|
|
235
|
+
// or catch (e) { } (with just whitespace)
|
|
236
|
+
const inlineMatch = line.match(/\bcatch\s*\([^)]*\)\s*\{\s*\}\s*$/);
|
|
237
|
+
if (inlineMatch) {
|
|
238
|
+
findings.push({
|
|
239
|
+
tool: "builtin",
|
|
240
|
+
file: filePath,
|
|
241
|
+
line: i + 1,
|
|
242
|
+
message: "Empty catch block — errors are silently swallowed",
|
|
243
|
+
severity: "warning",
|
|
244
|
+
ruleId: "empty-catch",
|
|
245
|
+
});
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Multi-line catch: catch (e) { on this line, } on a later line
|
|
250
|
+
const catchOpenMatch = line.match(/\bcatch\s*\([^)]*\)\s*\{\s*$/);
|
|
251
|
+
if (catchOpenMatch) {
|
|
252
|
+
// Look ahead for the closing brace
|
|
253
|
+
let blockContent = "";
|
|
254
|
+
let closingLine = -1;
|
|
255
|
+
for (let j = i + 1; j < lines.length && j < i + 20; j++) {
|
|
256
|
+
const nextLine = lines[j] ?? "";
|
|
257
|
+
if (nextLine.trim() === "}") {
|
|
258
|
+
closingLine = j;
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
blockContent += nextLine;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (closingLine !== -1) {
|
|
265
|
+
const trimmed = blockContent.trim();
|
|
266
|
+
// Empty or whitespace-only is flagged
|
|
267
|
+
// Comments are intentional — not flagged
|
|
268
|
+
if (trimmed === "") {
|
|
269
|
+
findings.push({
|
|
270
|
+
tool: "builtin",
|
|
271
|
+
file: filePath,
|
|
272
|
+
line: i + 1,
|
|
273
|
+
message: "Empty catch block — errors are silently swallowed",
|
|
274
|
+
severity: "warning",
|
|
275
|
+
ruleId: "empty-catch",
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
// If it contains a comment (// or /* or *), it's intentional
|
|
279
|
+
// If it contains actual code, it's not empty
|
|
280
|
+
// Either way, no finding needed
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return findings;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ─── Check 7: `any` type usage ───────────────────────────────────────────
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Detect `any` type annotations in TypeScript files.
|
|
292
|
+
* Skips .d.ts files where `any` is sometimes necessary.
|
|
293
|
+
* Avoids false positives on words containing "any" (e.g., "many", "company")
|
|
294
|
+
* and on comments/strings.
|
|
295
|
+
*/
|
|
296
|
+
export function checkAnyType(filePath: string, content: string): Finding[] {
|
|
297
|
+
if (!isTypeScriptFile(filePath)) return [];
|
|
298
|
+
if (isDeclarationFile(filePath)) return [];
|
|
299
|
+
|
|
300
|
+
const findings: Finding[] = [];
|
|
301
|
+
const lines = content.split("\n");
|
|
302
|
+
|
|
303
|
+
// Match `: any`, `as any`, `<any>`, `any[]`, `any,`, `any)`, `any;`
|
|
304
|
+
// — basically `any` used as a type annotation, not as a substring in identifiers
|
|
305
|
+
const anyTypePattern =
|
|
306
|
+
/(?::\s*any\b|(?:as|extends|implements)\s+any\b|<any\b|any\s*[[\]>,);|&])/;
|
|
307
|
+
|
|
308
|
+
for (const [i, line] of lines.entries()) {
|
|
309
|
+
// Skip comment lines
|
|
310
|
+
const trimmed = line.trim();
|
|
311
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("*")) continue;
|
|
312
|
+
|
|
313
|
+
// Skip lines where 'any' only appears in a string literal
|
|
314
|
+
// Simple heuristic: remove string contents and check again
|
|
315
|
+
const withoutStrings = line
|
|
316
|
+
.replace(/"(?:[^"\\]|\\.)*"/g, '""')
|
|
317
|
+
.replace(/'(?:[^'\\]|\\.)*'/g, "''")
|
|
318
|
+
.replace(/`(?:[^`\\]|\\.)*`/g, "``");
|
|
319
|
+
|
|
320
|
+
if (anyTypePattern.test(withoutStrings)) {
|
|
321
|
+
findings.push({
|
|
322
|
+
tool: "builtin",
|
|
323
|
+
file: filePath,
|
|
324
|
+
line: i + 1,
|
|
325
|
+
message: "Usage of 'any' type — prefer explicit types or 'unknown'",
|
|
326
|
+
severity: "warning",
|
|
327
|
+
ruleId: "no-any-type",
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return findings;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ─── Aggregator ──────────────────────────────────────────────────────────
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Run all built-in checks on a single file and return aggregated findings.
|
|
339
|
+
*/
|
|
340
|
+
export function runBuiltinChecks(filePath: string, content: string): Finding[] {
|
|
341
|
+
return [
|
|
342
|
+
...checkConsoleLogs(filePath, content),
|
|
343
|
+
...checkUnusedImports(filePath, content),
|
|
344
|
+
...checkTodoComments(filePath, content),
|
|
345
|
+
...checkFileSize(filePath, content),
|
|
346
|
+
...checkSecrets(filePath, content),
|
|
347
|
+
...checkEmptyCatch(filePath, content),
|
|
348
|
+
...checkAnyType(filePath, content),
|
|
349
|
+
];
|
|
350
|
+
}
|
package/src/verify/pipeline.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 1. Get files to check (staged files, or provided list)
|
|
6
6
|
* 2. Run syntax guard FIRST — abort immediately if it fails
|
|
7
7
|
* 3. Auto-detect available tools
|
|
8
|
-
* 4. Run all available tools in PARALLEL (slop, semgrep, trivy, secretlint)
|
|
8
|
+
* 4. Run all available tools in PARALLEL (slop, builtin, semgrep, trivy, secretlint)
|
|
9
9
|
* 5. Collect all findings
|
|
10
10
|
* 6. Apply diff-only filter (unless diffOnly === false)
|
|
11
11
|
* 7. Determine pass/fail: passed = no error-severity findings
|
|
@@ -19,6 +19,7 @@ import { detectLanguages } from "../language/detect";
|
|
|
19
19
|
import type { LanguageId } from "../language/profile";
|
|
20
20
|
import { getProfile } from "../language/profile";
|
|
21
21
|
import { type AIReviewResult, runAIReview } from "./ai-review";
|
|
22
|
+
import { runBuiltinChecks } from "./builtin";
|
|
22
23
|
import { checkConsistency } from "./consistency";
|
|
23
24
|
import { runCoverage } from "./coverage";
|
|
24
25
|
import type { DetectedTool } from "./detect";
|
|
@@ -32,6 +33,7 @@ import { detectSlop } from "./slop";
|
|
|
32
33
|
import { runSonar } from "./sonar";
|
|
33
34
|
import type { SyntaxDiagnostic } from "./syntax-guard";
|
|
34
35
|
import { syntaxGuard } from "./syntax-guard";
|
|
36
|
+
import { runWikiLintTool } from "./tools/wiki-lint-runner";
|
|
35
37
|
import { runTrivy } from "./trivy";
|
|
36
38
|
import { runTypecheck } from "./typecheck";
|
|
37
39
|
|
|
@@ -240,10 +242,38 @@ export async function runPipeline(
|
|
|
240
242
|
}),
|
|
241
243
|
);
|
|
242
244
|
|
|
245
|
+
// Built-in checks (always run, pure functions, no external dependencies)
|
|
246
|
+
toolPromises.push(
|
|
247
|
+
runToolWithTiming("builtin", async () => {
|
|
248
|
+
const findings: Finding[] = [];
|
|
249
|
+
for (const file of files) {
|
|
250
|
+
try {
|
|
251
|
+
const fullPath = file.startsWith("/") ? file : `${cwd}/${file}`;
|
|
252
|
+
const text = await Bun.file(fullPath).text();
|
|
253
|
+
findings.push(...runBuiltinChecks(file, text));
|
|
254
|
+
} catch {
|
|
255
|
+
// File read failure should not block pipeline
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return { findings, skipped: false };
|
|
259
|
+
}),
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
// Wiki lint — only runs if .maina/wiki/ exists (auto-skips otherwise)
|
|
263
|
+
toolPromises.push(
|
|
264
|
+
runToolWithTiming("wiki-lint", () => runWikiLintTool({ cwd, mainaDir })),
|
|
265
|
+
);
|
|
266
|
+
|
|
243
267
|
const toolReports = await Promise.all(toolPromises);
|
|
244
268
|
|
|
245
269
|
// ── Step 4b: Warn if all external tools were skipped ─────────────────
|
|
246
|
-
const builtInTools = new Set([
|
|
270
|
+
const builtInTools = new Set([
|
|
271
|
+
"slop",
|
|
272
|
+
"typecheck",
|
|
273
|
+
"consistency",
|
|
274
|
+
"builtin",
|
|
275
|
+
"wiki-lint",
|
|
276
|
+
]);
|
|
247
277
|
const externalTools = toolReports.filter((r) => !builtInTools.has(r.tool));
|
|
248
278
|
const allExternalSkipped =
|
|
249
279
|
externalTools.length > 0 && externalTools.every((r) => r.skipped);
|