@mainahq/core 0.2.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.
Files changed (156) hide show
  1. package/README.md +31 -0
  2. package/package.json +37 -0
  3. package/src/ai/__tests__/ai.test.ts +207 -0
  4. package/src/ai/__tests__/design-approaches.test.ts +192 -0
  5. package/src/ai/__tests__/spec-questions.test.ts +191 -0
  6. package/src/ai/__tests__/tiers.test.ts +110 -0
  7. package/src/ai/commit-msg.ts +28 -0
  8. package/src/ai/design-approaches.ts +76 -0
  9. package/src/ai/index.ts +205 -0
  10. package/src/ai/pr-summary.ts +60 -0
  11. package/src/ai/spec-questions.ts +74 -0
  12. package/src/ai/tiers.ts +52 -0
  13. package/src/ai/try-generate.ts +89 -0
  14. package/src/ai/validate.ts +66 -0
  15. package/src/benchmark/__tests__/reporter.test.ts +525 -0
  16. package/src/benchmark/__tests__/runner.test.ts +113 -0
  17. package/src/benchmark/__tests__/story-loader.test.ts +152 -0
  18. package/src/benchmark/reporter.ts +332 -0
  19. package/src/benchmark/runner.ts +91 -0
  20. package/src/benchmark/story-loader.ts +88 -0
  21. package/src/benchmark/types.ts +95 -0
  22. package/src/cache/__tests__/keys.test.ts +97 -0
  23. package/src/cache/__tests__/manager.test.ts +312 -0
  24. package/src/cache/__tests__/ttl.test.ts +94 -0
  25. package/src/cache/keys.ts +44 -0
  26. package/src/cache/manager.ts +231 -0
  27. package/src/cache/ttl.ts +77 -0
  28. package/src/config/__tests__/config.test.ts +376 -0
  29. package/src/config/index.ts +198 -0
  30. package/src/context/__tests__/budget.test.ts +179 -0
  31. package/src/context/__tests__/engine.test.ts +163 -0
  32. package/src/context/__tests__/episodic.test.ts +291 -0
  33. package/src/context/__tests__/relevance.test.ts +323 -0
  34. package/src/context/__tests__/retrieval.test.ts +143 -0
  35. package/src/context/__tests__/selector.test.ts +174 -0
  36. package/src/context/__tests__/semantic.test.ts +252 -0
  37. package/src/context/__tests__/treesitter.test.ts +229 -0
  38. package/src/context/__tests__/working.test.ts +236 -0
  39. package/src/context/budget.ts +130 -0
  40. package/src/context/engine.ts +394 -0
  41. package/src/context/episodic.ts +251 -0
  42. package/src/context/relevance.ts +325 -0
  43. package/src/context/retrieval.ts +325 -0
  44. package/src/context/selector.ts +93 -0
  45. package/src/context/semantic.ts +331 -0
  46. package/src/context/treesitter.ts +216 -0
  47. package/src/context/working.ts +192 -0
  48. package/src/db/__tests__/db.test.ts +151 -0
  49. package/src/db/index.ts +211 -0
  50. package/src/db/schema.ts +84 -0
  51. package/src/design/__tests__/design.test.ts +310 -0
  52. package/src/design/__tests__/generate-hld-lld.test.ts +109 -0
  53. package/src/design/__tests__/review.test.ts +561 -0
  54. package/src/design/index.ts +297 -0
  55. package/src/design/review.ts +327 -0
  56. package/src/explain/__tests__/explain.test.ts +173 -0
  57. package/src/explain/index.ts +181 -0
  58. package/src/features/__tests__/analyzer.test.ts +358 -0
  59. package/src/features/__tests__/checklist.test.ts +454 -0
  60. package/src/features/__tests__/numbering.test.ts +319 -0
  61. package/src/features/__tests__/quality.test.ts +295 -0
  62. package/src/features/__tests__/traceability.test.ts +147 -0
  63. package/src/features/analyzer.ts +445 -0
  64. package/src/features/checklist.ts +366 -0
  65. package/src/features/index.ts +18 -0
  66. package/src/features/numbering.ts +404 -0
  67. package/src/features/quality.ts +349 -0
  68. package/src/features/test-stubs.ts +157 -0
  69. package/src/features/traceability.ts +260 -0
  70. package/src/feedback/__tests__/async-feedback.test.ts +52 -0
  71. package/src/feedback/__tests__/collector.test.ts +219 -0
  72. package/src/feedback/__tests__/compress.test.ts +150 -0
  73. package/src/feedback/__tests__/preferences.test.ts +169 -0
  74. package/src/feedback/collector.ts +135 -0
  75. package/src/feedback/compress.ts +92 -0
  76. package/src/feedback/preferences.ts +108 -0
  77. package/src/git/__tests__/git.test.ts +62 -0
  78. package/src/git/index.ts +110 -0
  79. package/src/hooks/__tests__/runner.test.ts +266 -0
  80. package/src/hooks/index.ts +8 -0
  81. package/src/hooks/runner.ts +130 -0
  82. package/src/index.ts +356 -0
  83. package/src/init/__tests__/init.test.ts +228 -0
  84. package/src/init/index.ts +364 -0
  85. package/src/language/__tests__/detect.test.ts +77 -0
  86. package/src/language/__tests__/profile.test.ts +51 -0
  87. package/src/language/detect.ts +70 -0
  88. package/src/language/profile.ts +110 -0
  89. package/src/prompts/__tests__/defaults.test.ts +52 -0
  90. package/src/prompts/__tests__/engine.test.ts +183 -0
  91. package/src/prompts/__tests__/evolution-resolve.test.ts +169 -0
  92. package/src/prompts/__tests__/evolution.test.ts +187 -0
  93. package/src/prompts/__tests__/loader.test.ts +105 -0
  94. package/src/prompts/candidates/review-v2.md +55 -0
  95. package/src/prompts/defaults/ai-review.md +49 -0
  96. package/src/prompts/defaults/commit.md +30 -0
  97. package/src/prompts/defaults/context.md +26 -0
  98. package/src/prompts/defaults/design-approaches.md +57 -0
  99. package/src/prompts/defaults/design-hld-lld.md +55 -0
  100. package/src/prompts/defaults/design.md +53 -0
  101. package/src/prompts/defaults/explain.md +31 -0
  102. package/src/prompts/defaults/fix.md +32 -0
  103. package/src/prompts/defaults/index.ts +38 -0
  104. package/src/prompts/defaults/review.md +41 -0
  105. package/src/prompts/defaults/spec-questions.md +59 -0
  106. package/src/prompts/defaults/tests.md +72 -0
  107. package/src/prompts/engine.ts +137 -0
  108. package/src/prompts/evolution.ts +409 -0
  109. package/src/prompts/loader.ts +71 -0
  110. package/src/review/__tests__/review.test.ts +288 -0
  111. package/src/review/comprehensive.ts +362 -0
  112. package/src/review/index.ts +417 -0
  113. package/src/stats/__tests__/tracker.test.ts +323 -0
  114. package/src/stats/index.ts +11 -0
  115. package/src/stats/tracker.ts +492 -0
  116. package/src/ticket/__tests__/ticket.test.ts +273 -0
  117. package/src/ticket/index.ts +185 -0
  118. package/src/utils.ts +87 -0
  119. package/src/verify/__tests__/ai-review.test.ts +242 -0
  120. package/src/verify/__tests__/coverage.test.ts +83 -0
  121. package/src/verify/__tests__/detect.test.ts +175 -0
  122. package/src/verify/__tests__/diff-filter.test.ts +338 -0
  123. package/src/verify/__tests__/fix.test.ts +478 -0
  124. package/src/verify/__tests__/linters/clippy.test.ts +45 -0
  125. package/src/verify/__tests__/linters/go-vet.test.ts +27 -0
  126. package/src/verify/__tests__/linters/ruff.test.ts +64 -0
  127. package/src/verify/__tests__/mutation.test.ts +141 -0
  128. package/src/verify/__tests__/pipeline.test.ts +553 -0
  129. package/src/verify/__tests__/proof.test.ts +97 -0
  130. package/src/verify/__tests__/secretlint.test.ts +190 -0
  131. package/src/verify/__tests__/semgrep.test.ts +217 -0
  132. package/src/verify/__tests__/slop.test.ts +366 -0
  133. package/src/verify/__tests__/sonar.test.ts +113 -0
  134. package/src/verify/__tests__/syntax-guard.test.ts +227 -0
  135. package/src/verify/__tests__/trivy.test.ts +191 -0
  136. package/src/verify/__tests__/visual.test.ts +139 -0
  137. package/src/verify/ai-review.ts +276 -0
  138. package/src/verify/coverage.ts +134 -0
  139. package/src/verify/detect.ts +171 -0
  140. package/src/verify/diff-filter.ts +183 -0
  141. package/src/verify/fix.ts +317 -0
  142. package/src/verify/linters/clippy.ts +52 -0
  143. package/src/verify/linters/go-vet.ts +32 -0
  144. package/src/verify/linters/ruff.ts +47 -0
  145. package/src/verify/mutation.ts +143 -0
  146. package/src/verify/pipeline.ts +328 -0
  147. package/src/verify/proof.ts +277 -0
  148. package/src/verify/secretlint.ts +168 -0
  149. package/src/verify/semgrep.ts +170 -0
  150. package/src/verify/slop.ts +493 -0
  151. package/src/verify/sonar.ts +146 -0
  152. package/src/verify/syntax-guard.ts +251 -0
  153. package/src/verify/trivy.ts +161 -0
  154. package/src/verify/visual.ts +460 -0
  155. package/src/workflow/__tests__/context.test.ts +110 -0
  156. package/src/workflow/context.ts +81 -0
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Secretlint Integration for the Verify Engine.
3
+ *
4
+ * Runs Secretlint for secrets detection in source files.
5
+ * Parses JSON output into the unified Finding type.
6
+ * Gracefully skips if secretlint is not installed.
7
+ */
8
+
9
+ import { isToolAvailable } from "./detect";
10
+ import type { Finding } from "./diff-filter";
11
+
12
+ // ─── Types ────────────────────────────────────────────────────────────────
13
+
14
+ export interface SecretlintOptions {
15
+ files?: string[];
16
+ cwd?: string;
17
+ /** Pre-resolved availability — skips redundant detection if provided. */
18
+ available?: boolean;
19
+ }
20
+
21
+ export interface SecretlintResult {
22
+ findings: Finding[];
23
+ skipped: boolean;
24
+ }
25
+
26
+ // ─── JSON Parsing ─────────────────────────────────────────────────────────
27
+
28
+ /**
29
+ * Map secretlint numeric severity to unified severity.
30
+ * secretlint uses: 0 = info, 1 = warning, 2 = error
31
+ */
32
+ function mapSecretlintSeverity(severity: number): "error" | "warning" | "info" {
33
+ switch (severity) {
34
+ case 2:
35
+ return "error";
36
+ case 1:
37
+ return "warning";
38
+ default:
39
+ return "info";
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Parse secretlint JSON output into Finding[].
45
+ *
46
+ * Secretlint JSON output is an array of file results:
47
+ * ```json
48
+ * [{
49
+ * "filePath": "src/config.ts",
50
+ * "messages": [{
51
+ * "ruleId": "@secretlint/secretlint-rule-preset-recommend",
52
+ * "message": "Found AWS Access Key ID",
53
+ * "loc": {
54
+ * "start": { "line": 5, "column": 10 },
55
+ * "end": { "line": 5, "column": 30 }
56
+ * },
57
+ * "severity": 2
58
+ * }]
59
+ * }]
60
+ * ```
61
+ *
62
+ * Handles malformed JSON and unexpected structures gracefully.
63
+ */
64
+ export function parseSecretlintOutput(output: string): Finding[] {
65
+ if (!output.trim()) {
66
+ return [];
67
+ }
68
+
69
+ let parsed: unknown;
70
+ try {
71
+ parsed = JSON.parse(output);
72
+ } catch {
73
+ return [];
74
+ }
75
+
76
+ if (!Array.isArray(parsed)) {
77
+ return [];
78
+ }
79
+
80
+ const findings: Finding[] = [];
81
+
82
+ for (const fileResult of parsed) {
83
+ const fr = fileResult as Record<string, unknown>;
84
+ const filePath = (fr.filePath as string) ?? "";
85
+ const messages = fr.messages;
86
+
87
+ if (!Array.isArray(messages)) {
88
+ continue;
89
+ }
90
+
91
+ for (const msg of messages) {
92
+ const m = msg as Record<string, unknown>;
93
+ const ruleId = (m.ruleId as string) ?? undefined;
94
+ const message = (m.message as string) ?? "";
95
+ const severity = (m.severity as number) ?? 0;
96
+
97
+ const loc = m.loc as Record<string, unknown> | undefined;
98
+ let line = 0;
99
+ let column: number | undefined;
100
+
101
+ if (loc) {
102
+ const start = loc.start as Record<string, unknown> | undefined;
103
+ if (start) {
104
+ line = (start.line as number) ?? 0;
105
+ const col = start.column as number | undefined;
106
+ column = col ?? undefined;
107
+ }
108
+ }
109
+
110
+ findings.push({
111
+ tool: "secretlint",
112
+ file: filePath,
113
+ line,
114
+ column,
115
+ message,
116
+ severity: mapSecretlintSeverity(severity),
117
+ ruleId,
118
+ });
119
+ }
120
+ }
121
+
122
+ return findings;
123
+ }
124
+
125
+ // ─── Runner ───────────────────────────────────────────────────────────────
126
+
127
+ /**
128
+ * Run Secretlint and return parsed findings.
129
+ *
130
+ * If secretlint is not installed, returns `{ findings: [], skipped: true }`.
131
+ * If secretlint fails, returns `{ findings: [], skipped: false }`.
132
+ */
133
+ export async function runSecretlint(
134
+ options?: SecretlintOptions,
135
+ ): Promise<SecretlintResult> {
136
+ const toolAvailable =
137
+ options?.available ?? (await isToolAvailable("secretlint"));
138
+ if (!toolAvailable) {
139
+ return { findings: [], skipped: true };
140
+ }
141
+
142
+ const cwd = options?.cwd ?? process.cwd();
143
+
144
+ const args = ["secretlint", "--format", "json"];
145
+
146
+ if (options?.files && options.files.length > 0) {
147
+ args.push(...options.files);
148
+ } else {
149
+ args.push("**/*");
150
+ }
151
+
152
+ try {
153
+ const proc = Bun.spawn(args, {
154
+ cwd,
155
+ stdout: "pipe",
156
+ stderr: "pipe",
157
+ });
158
+
159
+ const stdout = await new Response(proc.stdout).text();
160
+ await new Response(proc.stderr).text();
161
+ await proc.exited;
162
+
163
+ const findings = parseSecretlintOutput(stdout);
164
+ return { findings, skipped: false };
165
+ } catch {
166
+ return { findings: [], skipped: false };
167
+ }
168
+ }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Semgrep Integration for the Verify Engine.
3
+ *
4
+ * Runs Semgrep with auto rules + optional custom rules directory.
5
+ * Parses SARIF output into the unified Finding type.
6
+ * Gracefully skips if semgrep is not installed.
7
+ */
8
+
9
+ import { isToolAvailable } from "./detect";
10
+ import type { Finding } from "./diff-filter";
11
+
12
+ // ─── Types ────────────────────────────────────────────────────────────────
13
+
14
+ export interface SemgrepOptions {
15
+ files?: string[];
16
+ rulesDir?: string;
17
+ config?: string;
18
+ cwd?: string;
19
+ /** Pre-resolved availability — skips redundant detection if provided. */
20
+ available?: boolean;
21
+ }
22
+
23
+ export interface SemgrepResult {
24
+ findings: Finding[];
25
+ skipped: boolean;
26
+ }
27
+
28
+ // ─── SARIF Parsing ────────────────────────────────────────────────────────
29
+
30
+ /**
31
+ * Map SARIF level to unified severity.
32
+ */
33
+ function mapSarifLevel(level: string): "error" | "warning" | "info" {
34
+ switch (level) {
35
+ case "error":
36
+ return "error";
37
+ case "warning":
38
+ return "warning";
39
+ case "note":
40
+ case "none":
41
+ return "info";
42
+ default:
43
+ return "warning";
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Parse SARIF JSON output (from semgrep --sarif) into Finding[].
49
+ *
50
+ * Handles malformed JSON and unexpected structures gracefully by
51
+ * returning an empty array.
52
+ */
53
+ export function parseSarif(sarifJson: string): Finding[] {
54
+ let sarif: Record<string, unknown>;
55
+ try {
56
+ sarif = JSON.parse(sarifJson) as Record<string, unknown>;
57
+ } catch {
58
+ return [];
59
+ }
60
+
61
+ const runs = sarif.runs;
62
+ if (!Array.isArray(runs)) {
63
+ return [];
64
+ }
65
+
66
+ const findings: Finding[] = [];
67
+
68
+ for (const run of runs) {
69
+ const results = (run as Record<string, unknown>).results;
70
+ if (!Array.isArray(results)) {
71
+ continue;
72
+ }
73
+
74
+ for (const result of results) {
75
+ const r = result as Record<string, unknown>;
76
+ const ruleId = (r.ruleId as string) ?? undefined;
77
+ const messageObj = r.message as Record<string, unknown> | undefined;
78
+ const message = (messageObj?.text as string) ?? "";
79
+ const level = (r.level as string) ?? "warning";
80
+
81
+ const locations = r.locations as Array<Record<string, unknown>>;
82
+ let file = "";
83
+ let line = 0;
84
+ let column: number | undefined;
85
+
86
+ if (Array.isArray(locations) && locations.length > 0) {
87
+ const loc = locations[0] as Record<string, unknown>;
88
+ const physicalLocation = loc?.physicalLocation as
89
+ | Record<string, unknown>
90
+ | undefined;
91
+
92
+ if (physicalLocation) {
93
+ const artifactLocation = physicalLocation.artifactLocation as
94
+ | Record<string, unknown>
95
+ | undefined;
96
+ file = (artifactLocation?.uri as string) ?? "";
97
+
98
+ const region = physicalLocation.region as
99
+ | Record<string, unknown>
100
+ | undefined;
101
+ if (region) {
102
+ line = (region.startLine as number) ?? 0;
103
+ const startColumn = region.startColumn as number | undefined;
104
+ column = startColumn ?? undefined;
105
+ }
106
+ }
107
+ }
108
+
109
+ findings.push({
110
+ tool: "semgrep",
111
+ file,
112
+ line,
113
+ column,
114
+ message,
115
+ severity: mapSarifLevel(level),
116
+ ruleId,
117
+ });
118
+ }
119
+ }
120
+
121
+ return findings;
122
+ }
123
+
124
+ // ─── Runner ───────────────────────────────────────────────────────────────
125
+
126
+ /**
127
+ * Run Semgrep and return parsed findings.
128
+ *
129
+ * If semgrep is not installed, returns `{ findings: [], skipped: true }`.
130
+ * If semgrep fails, returns `{ findings: [], skipped: false }`.
131
+ */
132
+ export async function runSemgrep(
133
+ options?: SemgrepOptions,
134
+ ): Promise<SemgrepResult> {
135
+ const toolAvailable =
136
+ options?.available ?? (await isToolAvailable("semgrep"));
137
+ if (!toolAvailable) {
138
+ return { findings: [], skipped: true };
139
+ }
140
+
141
+ const config = options?.config ?? "auto";
142
+ const cwd = options?.cwd ?? process.cwd();
143
+
144
+ const args = ["semgrep", "scan", "--sarif", `--config=${config}`];
145
+
146
+ if (options?.rulesDir) {
147
+ args.push(`--config=${options.rulesDir}`);
148
+ }
149
+
150
+ if (options?.files && options.files.length > 0) {
151
+ args.push(...options.files);
152
+ }
153
+
154
+ try {
155
+ const proc = Bun.spawn(args, {
156
+ cwd,
157
+ stdout: "pipe",
158
+ stderr: "pipe",
159
+ });
160
+
161
+ const stdout = await new Response(proc.stdout).text();
162
+ await new Response(proc.stderr).text();
163
+ await proc.exited;
164
+
165
+ const findings = parseSarif(stdout);
166
+ return { findings, skipped: false };
167
+ } catch {
168
+ return { findings: [], skipped: false };
169
+ }
170
+ }