@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
package/src/index.ts ADDED
@@ -0,0 +1,356 @@
1
+ export const VERSION = "0.1.0";
2
+
3
+ // AI
4
+ export { generateCommitMessage } from "./ai/commit-msg";
5
+ export {
6
+ type DesignApproach,
7
+ generateDesignApproaches,
8
+ } from "./ai/design-approaches";
9
+ export { generate } from "./ai/index";
10
+ export { generatePrSummary } from "./ai/pr-summary";
11
+ export {
12
+ generateSpecQuestions,
13
+ type SpecQuestion,
14
+ } from "./ai/spec-questions";
15
+ export {
16
+ type DelegationPrompt,
17
+ type TryAIResult,
18
+ tryAIGenerate,
19
+ } from "./ai/try-generate";
20
+ // AI validation
21
+ export { type AIValidationResult, validateAIOutput } from "./ai/validate";
22
+ export {
23
+ buildReport,
24
+ buildTier3Report,
25
+ formatComparison,
26
+ formatTier3Comparison,
27
+ } from "./benchmark/reporter";
28
+ export { parseTestOutput, runBenchmark } from "./benchmark/runner";
29
+ export { listStories, loadStory } from "./benchmark/story-loader";
30
+ // Benchmark
31
+ export type {
32
+ BenchmarkMetrics,
33
+ BenchmarkReport,
34
+ LoadedStory,
35
+ StepMetrics,
36
+ StoryConfig,
37
+ Tier3Results,
38
+ Tier3Totals,
39
+ } from "./benchmark/types";
40
+ // Cache
41
+ export {
42
+ type CacheManager,
43
+ type CacheStats,
44
+ createCacheManager,
45
+ } from "./cache/manager";
46
+ // Config
47
+ export { getApiKey, isHostMode, shouldDelegateToHost } from "./config/index";
48
+ export { calculateTokens } from "./context/budget";
49
+ export {
50
+ type AssembledContext,
51
+ assembleContext,
52
+ type ContextOptions,
53
+ type LayerReport,
54
+ } from "./context/engine";
55
+ // Context — episodic
56
+ export { addEntry as addEpisodicEntry } from "./context/episodic";
57
+ export type { MainaCommand } from "./context/selector";
58
+ // Context — working
59
+ export {
60
+ loadWorkingContext,
61
+ saveWorkingContext,
62
+ setVerificationResult,
63
+ trackFile,
64
+ } from "./context/working";
65
+ // DB (Result type)
66
+ export type { Result } from "./db/index";
67
+ // Design (ADR)
68
+ export {
69
+ type AdrSummary,
70
+ generateHldLld,
71
+ getNextAdrNumber,
72
+ listAdrs,
73
+ scaffoldAdr,
74
+ } from "./design/index";
75
+ // Design Review
76
+ export {
77
+ buildReviewContext,
78
+ findAdrByNumber,
79
+ type ReviewContext,
80
+ type ReviewFinding,
81
+ type ReviewOptions,
82
+ type ReviewResult,
83
+ reviewDesign,
84
+ } from "./design/review";
85
+ // Explain
86
+ export {
87
+ type DiagramOptions,
88
+ generateDependencyDiagram,
89
+ generateModuleSummary,
90
+ type ModuleSummary,
91
+ } from "./explain/index";
92
+ // Features
93
+ export {
94
+ type AnalysisFinding,
95
+ type AnalysisReport,
96
+ analyze,
97
+ } from "./features/analyzer";
98
+ export {
99
+ type CheckResult,
100
+ type VerificationReport,
101
+ verifyPlan,
102
+ } from "./features/checklist";
103
+ export {
104
+ createFeatureDir,
105
+ type DesignChoices,
106
+ getNextFeatureNumber,
107
+ scaffoldFeature,
108
+ scaffoldFeatureWithContext,
109
+ } from "./features/numbering";
110
+ export { type QualityScore, scoreSpec } from "./features/quality";
111
+ export { generateTestStubs } from "./features/test-stubs";
112
+ export type {
113
+ TaskTrace,
114
+ TraceabilityReport,
115
+ TraceDeps,
116
+ } from "./features/traceability";
117
+ export { traceFeature } from "./features/traceability";
118
+ // Feedback
119
+ export {
120
+ type FeedbackRecord,
121
+ getFeedbackSummary,
122
+ getWorkflowId,
123
+ recordFeedback,
124
+ recordFeedbackAsync,
125
+ recordFeedbackWithCompression,
126
+ } from "./feedback/collector";
127
+ export {
128
+ compressReview,
129
+ storeCompressedReview,
130
+ } from "./feedback/compress";
131
+ export {
132
+ acknowledgeFinding,
133
+ dismissFinding,
134
+ getNoisyRules,
135
+ loadPreferences,
136
+ type Preferences,
137
+ type RulePreference,
138
+ savePreferences,
139
+ } from "./feedback/preferences";
140
+ // Git
141
+ export {
142
+ type Commit,
143
+ getBranchName,
144
+ getChangedFiles,
145
+ getCurrentBranch,
146
+ getDiff,
147
+ getRecentCommits,
148
+ getRepoRoot,
149
+ getStagedFiles,
150
+ getTrackedFiles,
151
+ } from "./git/index";
152
+ // Hooks
153
+ export {
154
+ executeHook,
155
+ type HookContext,
156
+ type HookEvent,
157
+ type HookResult,
158
+ runHooks,
159
+ scanHooks,
160
+ } from "./hooks/index";
161
+ // Init
162
+ export {
163
+ bootstrap,
164
+ type DetectedStack,
165
+ type InitOptions,
166
+ type InitReport,
167
+ } from "./init/index";
168
+ // Language
169
+ export {
170
+ detectLanguages,
171
+ getPrimaryLanguage,
172
+ } from "./language/detect";
173
+ export {
174
+ GO_PROFILE,
175
+ getProfile,
176
+ getSupportedLanguages,
177
+ type LanguageId,
178
+ type LanguageProfile,
179
+ PYTHON_PROFILE,
180
+ RUST_PROFILE,
181
+ TYPESCRIPT_PROFILE,
182
+ } from "./language/profile";
183
+ export { loadDefault, type PromptTask } from "./prompts/defaults/index";
184
+ // Prompts
185
+ export {
186
+ type BuiltPrompt,
187
+ buildSystemPrompt,
188
+ type FeedbackOutcome,
189
+ getPromptStats,
190
+ type PromptStat,
191
+ recordOutcome,
192
+ } from "./prompts/engine";
193
+ export {
194
+ type ABResolution,
195
+ abTest,
196
+ analyseFeedback,
197
+ analyseWorkflowFeedback,
198
+ analyseWorkflowRuns,
199
+ type CandidatePrompt,
200
+ createCandidate,
201
+ type FeedbackAnalysis,
202
+ promote,
203
+ resolveABTests,
204
+ retire,
205
+ type WorkflowRunSummary,
206
+ type WorkflowStepAnalysis,
207
+ } from "./prompts/evolution";
208
+ // Comprehensive Review (Superpowers-style)
209
+ export {
210
+ type ComprehensiveReviewFinding,
211
+ type ComprehensiveReviewOptions,
212
+ type ComprehensiveReviewResult,
213
+ comprehensiveReview,
214
+ type ReviewSeverity,
215
+ } from "./review/comprehensive";
216
+ // PR Review (two-stage)
217
+ export {
218
+ type ReviewFinding as PrReviewFinding,
219
+ type ReviewOptions as PrReviewOptions,
220
+ type ReviewResult as PrReviewResult,
221
+ type ReviewStageResult,
222
+ reviewCodeQuality,
223
+ reviewCodeQualityWithAI,
224
+ reviewSpecCompliance,
225
+ runTwoStageReview,
226
+ } from "./review/index";
227
+ // Stats
228
+ export {
229
+ type CommitSnapshot,
230
+ type ComparisonReport,
231
+ getComparison,
232
+ getLatest,
233
+ getSkipRate,
234
+ getStats,
235
+ getTrends,
236
+ recordSnapshot,
237
+ type SnapshotInput,
238
+ type StatsReport,
239
+ type TrendDirection,
240
+ type TrendsReport,
241
+ } from "./stats/tracker";
242
+ // Ticket
243
+ export {
244
+ buildIssueBody,
245
+ createTicket,
246
+ detectModules,
247
+ type SpawnDeps,
248
+ type TicketOptions,
249
+ type TicketResult,
250
+ } from "./ticket/index";
251
+ // Utils
252
+ export { toKebabCase } from "./utils";
253
+ // Verify — AI Review
254
+ export {
255
+ type AIReviewOptions,
256
+ type AIReviewResult,
257
+ type EntityWithBody,
258
+ type ReferencedFunction,
259
+ resolveReferencedFunctions,
260
+ runAIReview,
261
+ } from "./verify/ai-review";
262
+ // Verify — Coverage
263
+ export {
264
+ type CoverageOptions,
265
+ type CoverageResult,
266
+ parseDiffCoverJson,
267
+ runCoverage,
268
+ } from "./verify/coverage";
269
+ export {
270
+ type DetectedTool,
271
+ detectTool,
272
+ detectTools,
273
+ isToolAvailable,
274
+ TOOL_REGISTRY,
275
+ type ToolName,
276
+ } from "./verify/detect";
277
+ export {
278
+ type DiffFilterResult,
279
+ type Finding,
280
+ filterByDiff,
281
+ filterByDiffWithMap,
282
+ parseChangedLines,
283
+ } from "./verify/diff-filter";
284
+ export {
285
+ type FixOptions,
286
+ type FixResult,
287
+ type FixSuggestion,
288
+ generateFixes,
289
+ hashFinding,
290
+ parseFixResponse,
291
+ } from "./verify/fix";
292
+ // Verify — Mutation
293
+ export {
294
+ type MutationOptions,
295
+ type MutationResult,
296
+ parseStrykerReport,
297
+ runMutation,
298
+ } from "./verify/mutation";
299
+ // Verify — Pipeline
300
+ export {
301
+ type PipelineOptions,
302
+ type PipelineResult,
303
+ runPipeline,
304
+ type ToolReport,
305
+ } from "./verify/pipeline";
306
+ // Verify — Proof
307
+ export {
308
+ formatVerificationProof,
309
+ gatherVerificationProof,
310
+ type ProofOptions,
311
+ type ToolProof,
312
+ type VerificationProof,
313
+ } from "./verify/proof";
314
+ export {
315
+ detectCommentedCode,
316
+ detectConsoleLogs,
317
+ detectEmptyBodies,
318
+ detectHallucinatedImports,
319
+ detectSlop,
320
+ detectTodosWithoutTickets,
321
+ type SlopResult,
322
+ type SlopRule,
323
+ } from "./verify/slop";
324
+ // Verify — SonarQube
325
+ export {
326
+ parseSonarReport,
327
+ runSonar,
328
+ type SonarOptions,
329
+ type SonarResult,
330
+ } from "./verify/sonar";
331
+ export {
332
+ parseBiomeOutput,
333
+ type SyntaxDiagnostic,
334
+ type SyntaxGuardResult,
335
+ syntaxGuard,
336
+ } from "./verify/syntax-guard";
337
+ // Verify — Visual
338
+ export {
339
+ captureScreenshot,
340
+ compareImages,
341
+ detectWebProject,
342
+ loadVisualConfig,
343
+ runVisualVerification,
344
+ type ScreenshotOptions,
345
+ type ScreenshotResult,
346
+ updateBaselines,
347
+ type VisualConfig,
348
+ type VisualDiffResult,
349
+ type VisualVerifyResult,
350
+ } from "./verify/visual";
351
+ // Workflow
352
+ export {
353
+ appendWorkflowStep,
354
+ loadWorkflowContext,
355
+ resetWorkflowContext,
356
+ } from "./workflow/context";
@@ -0,0 +1,228 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import {
3
+ existsSync,
4
+ mkdirSync,
5
+ readFileSync,
6
+ rmSync,
7
+ writeFileSync,
8
+ } from "node:fs";
9
+ import { tmpdir } from "node:os";
10
+ import { join } from "node:path";
11
+ import { bootstrap } from "../index";
12
+
13
+ function makeTmpDir(): string {
14
+ const dir = join(
15
+ tmpdir(),
16
+ `maina-init-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
17
+ );
18
+ mkdirSync(dir, { recursive: true });
19
+ return dir;
20
+ }
21
+
22
+ describe("bootstrap", () => {
23
+ let tmpDir: string;
24
+
25
+ beforeEach(() => {
26
+ tmpDir = makeTmpDir();
27
+ });
28
+
29
+ afterEach(() => {
30
+ rmSync(tmpDir, { recursive: true, force: true });
31
+ });
32
+
33
+ test("creates .maina/ directory", async () => {
34
+ const result = await bootstrap(tmpDir);
35
+ expect(result.ok).toBe(true);
36
+ if (result.ok) {
37
+ expect(existsSync(join(tmpDir, ".maina"))).toBe(true);
38
+ expect(result.value.directory).toBe(join(tmpDir, ".maina"));
39
+ }
40
+ });
41
+
42
+ test("creates constitution.md with template", async () => {
43
+ const result = await bootstrap(tmpDir);
44
+ expect(result.ok).toBe(true);
45
+
46
+ const constitutionPath = join(tmpDir, ".maina", "constitution.md");
47
+ expect(existsSync(constitutionPath)).toBe(true);
48
+
49
+ const content = readFileSync(constitutionPath, "utf-8");
50
+ expect(content).toContain("# Project Constitution");
51
+ expect(content).toContain("## Stack");
52
+ expect(content).toContain("## Architecture");
53
+ expect(content).toContain("## Verification");
54
+ expect(content).toContain("[NEEDS CLARIFICATION]");
55
+ });
56
+
57
+ test("creates AGENTS.md at repo root", async () => {
58
+ const result = await bootstrap(tmpDir);
59
+ expect(result.ok).toBe(true);
60
+
61
+ const agentsPath = join(tmpDir, "AGENTS.md");
62
+ expect(existsSync(agentsPath)).toBe(true);
63
+
64
+ const content = readFileSync(agentsPath, "utf-8");
65
+ expect(content).toContain("# AGENTS.md");
66
+ expect(content).toContain("maina verify");
67
+ expect(content).toContain("maina commit");
68
+ expect(content).toContain("maina context");
69
+ expect(content).toContain("maina doctor");
70
+ expect(content).toContain("constitution.md");
71
+ });
72
+
73
+ test("creates CI workflow", async () => {
74
+ const result = await bootstrap(tmpDir);
75
+ expect(result.ok).toBe(true);
76
+
77
+ const ciPath = join(tmpDir, ".github", "workflows", "maina-ci.yml");
78
+ expect(existsSync(ciPath)).toBe(true);
79
+
80
+ const content = readFileSync(ciPath, "utf-8");
81
+ expect(content).toContain("name: Maina CI");
82
+ expect(content).toContain("actions/checkout@v4");
83
+ });
84
+
85
+ test("detects bun stack from package.json", async () => {
86
+ // Create a Bun project
87
+ writeFileSync(
88
+ join(tmpDir, "package.json"),
89
+ JSON.stringify({ devDependencies: { "@types/bun": "latest" } }),
90
+ );
91
+ writeFileSync(join(tmpDir, "tsconfig.json"), "{}");
92
+
93
+ const result = await bootstrap(tmpDir);
94
+ expect(result.ok).toBe(true);
95
+ if (result.ok) {
96
+ expect(result.value.detectedStack.runtime).toBe("bun");
97
+ expect(result.value.detectedStack.language).toBe("typescript");
98
+
99
+ const ci = readFileSync(
100
+ join(tmpDir, ".github", "workflows", "maina-ci.yml"),
101
+ "utf-8",
102
+ );
103
+ expect(ci).toContain("oven-sh/setup-bun@v2");
104
+ expect(ci).toContain("bun install");
105
+ }
106
+ });
107
+
108
+ test("creates prompts directory with defaults", async () => {
109
+ const result = await bootstrap(tmpDir);
110
+ expect(result.ok).toBe(true);
111
+
112
+ const promptsDir = join(tmpDir, ".maina", "prompts");
113
+ expect(existsSync(promptsDir)).toBe(true);
114
+
115
+ const reviewPath = join(promptsDir, "review.md");
116
+ expect(existsSync(reviewPath)).toBe(true);
117
+ const reviewContent = readFileSync(reviewPath, "utf-8");
118
+ expect(reviewContent.length).toBeGreaterThan(0);
119
+
120
+ const commitPath = join(promptsDir, "commit.md");
121
+ expect(existsSync(commitPath)).toBe(true);
122
+ const commitContent = readFileSync(commitPath, "utf-8");
123
+ expect(commitContent.length).toBeGreaterThan(0);
124
+ });
125
+
126
+ test("creates hooks directory", async () => {
127
+ const result = await bootstrap(tmpDir);
128
+ expect(result.ok).toBe(true);
129
+
130
+ const hooksDir = join(tmpDir, ".maina", "hooks");
131
+ expect(existsSync(hooksDir)).toBe(true);
132
+ });
133
+
134
+ test("skips existing files (no overwrite)", async () => {
135
+ // Pre-create constitution.md with custom content
136
+ const mainaDir = join(tmpDir, ".maina");
137
+ mkdirSync(mainaDir, { recursive: true });
138
+ const constitutionPath = join(mainaDir, "constitution.md");
139
+ writeFileSync(constitutionPath, "# My Custom Constitution\n");
140
+
141
+ // Pre-create AGENTS.md with custom content
142
+ const agentsPath = join(tmpDir, "AGENTS.md");
143
+ writeFileSync(agentsPath, "# My Custom Agents\n");
144
+
145
+ const result = await bootstrap(tmpDir);
146
+ expect(result.ok).toBe(true);
147
+ if (result.ok) {
148
+ expect(result.value.skipped).toContain(".maina/constitution.md");
149
+ expect(result.value.skipped).toContain("AGENTS.md");
150
+
151
+ // Content should NOT have been overwritten
152
+ const content = readFileSync(constitutionPath, "utf-8");
153
+ expect(content).toBe("# My Custom Constitution\n");
154
+
155
+ const agentsContent = readFileSync(agentsPath, "utf-8");
156
+ expect(agentsContent).toBe("# My Custom Agents\n");
157
+ }
158
+ });
159
+
160
+ test("force flag overwrites existing", async () => {
161
+ // Pre-create constitution.md with custom content
162
+ const mainaDir = join(tmpDir, ".maina");
163
+ mkdirSync(mainaDir, { recursive: true });
164
+ const constitutionPath = join(mainaDir, "constitution.md");
165
+ writeFileSync(constitutionPath, "# My Custom Constitution\n");
166
+
167
+ const result = await bootstrap(tmpDir, { force: true });
168
+ expect(result.ok).toBe(true);
169
+ if (result.ok) {
170
+ expect(result.value.created).toContain(".maina/constitution.md");
171
+ expect(result.value.skipped).not.toContain(".maina/constitution.md");
172
+
173
+ // Content should have been overwritten with template
174
+ const content = readFileSync(constitutionPath, "utf-8");
175
+ expect(content).toContain("# Project Constitution");
176
+ }
177
+ });
178
+
179
+ test("returns correct created/skipped lists", async () => {
180
+ // Pre-create one file
181
+ const mainaDir = join(tmpDir, ".maina");
182
+ mkdirSync(mainaDir, { recursive: true });
183
+ writeFileSync(join(mainaDir, "constitution.md"), "existing\n");
184
+
185
+ const result = await bootstrap(tmpDir);
186
+ expect(result.ok).toBe(true);
187
+ if (result.ok) {
188
+ // constitution.md should be skipped
189
+ expect(result.value.skipped).toContain(".maina/constitution.md");
190
+ expect(result.value.created).not.toContain(".maina/constitution.md");
191
+
192
+ // Other files should be created
193
+ expect(result.value.created).toContain("AGENTS.md");
194
+ expect(result.value.created).toContain(".github/workflows/maina-ci.yml");
195
+ expect(result.value.created).toContain(".maina/prompts/review.md");
196
+ expect(result.value.created).toContain(".maina/prompts/commit.md");
197
+
198
+ // Total files should add up
199
+ const total = result.value.created.length + result.value.skipped.length;
200
+ expect(total).toBeGreaterThanOrEqual(5);
201
+ }
202
+ });
203
+
204
+ test("works on empty directory", async () => {
205
+ // tmpDir is already empty
206
+ const result = await bootstrap(tmpDir);
207
+ expect(result.ok).toBe(true);
208
+ if (result.ok) {
209
+ expect(result.value.created.length).toBeGreaterThanOrEqual(5);
210
+ expect(result.value.skipped.length).toBe(0);
211
+ expect(result.value.directory).toBe(join(tmpDir, ".maina"));
212
+ }
213
+ });
214
+
215
+ test("detects available verification tools in report", async () => {
216
+ const result = await bootstrap(tmpDir);
217
+ expect(result.ok).toBe(true);
218
+ if (result.ok) {
219
+ expect(result.value.detectedTools).toBeDefined();
220
+ expect(Array.isArray(result.value.detectedTools)).toBe(true);
221
+ // Each tool should have name and available flag
222
+ for (const tool of result.value.detectedTools) {
223
+ expect(typeof tool.name).toBe("string");
224
+ expect(typeof tool.available).toBe("boolean");
225
+ }
226
+ }
227
+ });
228
+ });