CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
intelligence.test.ts
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| 2c34075 | 1 | import { describe, it, expect, beforeAll, afterAll } from "bun:test"; |
| 2 | import { join } from "path"; | |
| 3 | import { rm, mkdir } from "fs/promises"; | |
| 4 | import { initBareRepo, getRepoPath } from "../git/repository"; | |
| 5 | import { | |
| 6 | computeHealthScore, | |
| 7 | detectCIConfig, | |
| 8 | analyzePush, | |
| 9 | } from "../lib/intelligence"; | |
| 10 | import { autoRepair } from "../lib/autorepair"; | |
| 11 | ||
| 12 | const TEST_REPOS = join(import.meta.dir, "../../.test-repos-intel-" + Date.now()); | |
| 13 | ||
| 14 | beforeAll(async () => { | |
| 15 | await rm(TEST_REPOS, { recursive: true, force: true }); | |
| 16 | await mkdir(TEST_REPOS, { recursive: true }); | |
| 17 | process.env.GIT_REPOS_PATH = TEST_REPOS; | |
| 18 | ||
| 19 | // Create repo with various files | |
| 20 | await initBareRepo("dev", "myapp"); | |
| 21 | const cloneDir = join(TEST_REPOS, "_clone"); | |
| 22 | const workDir = join(cloneDir, "work"); | |
| 23 | ||
| 24 | const run = async (cmd: string[], cwd: string) => { | |
| 25 | const proc = Bun.spawn(cmd, { cwd, stdout: "pipe", stderr: "pipe" }); | |
| 26 | await proc.exited; | |
| 27 | }; | |
| 28 | ||
| 29 | await run(["git", "clone", getRepoPath("dev", "myapp"), workDir], TEST_REPOS); | |
| 30 | await run(["git", "config", "user.email", "dev@test.com"], workDir); | |
| 31 | await run(["git", "config", "user.name", "Dev"], workDir); | |
| 32 | ||
| 33 | await mkdir(join(workDir, "src"), { recursive: true }); | |
| 34 | await mkdir(join(workDir, "src/__tests__"), { recursive: true }); | |
| 35 | ||
| 36 | // package.json | |
| 37 | await Bun.write( | |
| 38 | join(workDir, "package.json"), | |
| 39 | JSON.stringify( | |
| 40 | { | |
| 41 | name: "myapp", | |
| 42 | version: "1.0.0", | |
| 43 | scripts: { test: "bun test", lint: "eslint .", build: "tsc" }, | |
| 44 | dependencies: { hono: "^4.0.0" }, | |
| 45 | devDependencies: { typescript: "^5.0.0", "@types/bun": "^1.0.0" }, | |
| 46 | }, | |
| 47 | null, | |
| 48 | 2 | |
| 49 | ) | |
| 50 | ); | |
| 51 | ||
| 52 | // tsconfig | |
| 53 | await Bun.write( | |
| 54 | join(workDir, "tsconfig.json"), | |
| 55 | JSON.stringify({ compilerOptions: { strict: true } }, null, 2) | |
| 56 | ); | |
| 57 | ||
| 58 | // README | |
| 59 | await Bun.write(join(workDir, "README.md"), "# My App\n\nA test project.\n"); | |
| 60 | ||
| 61 | // Source files | |
| 62 | await Bun.write( | |
| 63 | join(workDir, "src/index.ts"), | |
| 64 | 'const greeting = "hello world";\nconsole.log(greeting);\n' | |
| 65 | ); | |
| 66 | await Bun.write( | |
| 67 | join(workDir, "src/util.ts"), | |
| 68 | 'export function add(a: number, b: number): number {\n return a + b;\n}\n' | |
| 69 | ); | |
| 70 | ||
| 71 | // Test file | |
| 72 | await Bun.write( | |
| 73 | join(workDir, "src/__tests__/util.test.ts"), | |
| 74 | 'import { expect, test } from "bun:test";\nimport { add } from "../util";\n\ntest("add", () => {\n expect(add(1, 2)).toBe(3);\n});\n' | |
| 75 | ); | |
| 76 | ||
| 77 | // Trailing whitespace in a file (for auto-repair test) | |
| 78 | await Bun.write( | |
| 79 | join(workDir, "src/messy.ts"), | |
| 80 | 'const x = 1; \nconst y = 2;\t\t\n// no newline at end' | |
| 81 | ); | |
| 82 | ||
| 83 | // bun.lock (just a dummy) | |
| 84 | await Bun.write(join(workDir, "bun.lock"), "{}"); | |
| 85 | ||
| 86 | // .gitignore with some entries | |
| 87 | await Bun.write(join(workDir, ".gitignore"), "node_modules/\ndist/\n"); | |
| 88 | ||
| 89 | await run(["git", "add", "-A"], workDir); | |
| 90 | await run(["git", "commit", "-m", "Initial commit"], workDir); | |
| 91 | await run(["git", "branch", "-M", "main"], workDir); | |
| 92 | await run(["git", "push", "-u", "origin", "main"], workDir); | |
| 93 | ||
| 94 | await rm(cloneDir, { recursive: true, force: true }); | |
| 95 | }); | |
| 96 | ||
| 97 | afterAll(async () => { | |
| 98 | await rm(TEST_REPOS, { recursive: true, force: true }); | |
| 99 | }); | |
| 100 | ||
| 101 | describe("health score", () => { | |
| 102 | it("computes a health report", async () => { | |
| 103 | const report = await computeHealthScore("dev", "myapp"); | |
| 104 | ||
| 105 | expect(report.score).toBeGreaterThan(0); | |
| 106 | expect(report.score).toBeLessThanOrEqual(100); | |
| 107 | expect(["A+", "A", "B", "C", "D", "F"]).toContain(report.grade); | |
| 108 | expect(report.breakdown.security).toBeDefined(); | |
| 109 | expect(report.breakdown.testing).toBeDefined(); | |
| 110 | expect(report.breakdown.complexity).toBeDefined(); | |
| 111 | expect(report.breakdown.dependencies).toBeDefined(); | |
| 112 | expect(report.breakdown.documentation).toBeDefined(); | |
| 113 | expect(report.breakdown.activity).toBeDefined(); | |
| 114 | expect(report.insights.length).toBeGreaterThan(0); | |
| 115 | }); | |
| 116 | ||
| 117 | it("detects tests exist", async () => { | |
| 118 | const report = await computeHealthScore("dev", "myapp"); | |
| 119 | expect(report.breakdown.testing.hasTests).toBe(true); | |
| 120 | expect(report.breakdown.testing.testFileCount).toBeGreaterThan(0); | |
| 121 | }); | |
| 122 | ||
| 123 | it("detects README", async () => { | |
| 124 | const report = await computeHealthScore("dev", "myapp"); | |
| 125 | expect(report.breakdown.documentation.hasReadme).toBe(true); | |
| 126 | }); | |
| 127 | ||
| 128 | it("detects dependencies", async () => { | |
| 129 | const report = await computeHealthScore("dev", "myapp"); | |
| 130 | expect(report.breakdown.dependencies.total).toBeGreaterThan(0); | |
| 131 | expect(report.breakdown.dependencies.lockfileExists).toBe(true); | |
| 132 | }); | |
| 133 | ||
| 134 | it("has at least 1 contributor", async () => { | |
| 135 | const report = await computeHealthScore("dev", "myapp"); | |
| 136 | expect(report.breakdown.activity.uniqueContributors).toBeGreaterThanOrEqual(1); | |
| 137 | }); | |
| 138 | }); | |
| 139 | ||
| 140 | describe("zero-config CI detection", () => { | |
| 141 | it("detects Bun + TypeScript project", async () => { | |
| 142 | const ci = await detectCIConfig("dev", "myapp", "main"); | |
| 143 | ||
| 144 | expect(ci.projectType).toBe("typescript"); | |
| 145 | expect(ci.runtime).toBe("bun"); | |
| 146 | expect(ci.detected).toContain("Bun project detected"); | |
| 147 | expect(ci.detected).toContain("TypeScript detected"); | |
| 148 | }); | |
| 149 | ||
| 150 | it("detects test, lint, and build commands", async () => { | |
| 151 | const ci = await detectCIConfig("dev", "myapp", "main"); | |
| 152 | ||
| 153 | const names = ci.commands.map((c) => c.name); | |
| 154 | expect(names).toContain("Test"); | |
| 155 | expect(names).toContain("Lint"); | |
| 156 | }); | |
| 157 | ||
| 158 | it("detects Hono framework", async () => { | |
| 159 | const ci = await detectCIConfig("dev", "myapp", "main"); | |
| 160 | expect(ci.detected).toContain("Hono framework"); | |
| 161 | }); | |
| 162 | }); | |
| 163 | ||
| 164 | describe("auto-repair", () => { | |
| 165 | it("fixes whitespace issues", async () => { | |
| 166 | const result = await autoRepair("dev", "myapp", "main"); | |
| 167 | ||
| 168 | expect(result.repaired).toBe(true); | |
| 169 | expect(result.repairs.length).toBeGreaterThan(0); | |
| 170 | ||
| 171 | // Should have fixed trailing whitespace | |
| 172 | const whitespaceRepairs = result.repairs.filter( | |
| 173 | (r) => r.type === "whitespace" | |
| 174 | ); | |
| 175 | expect(whitespaceRepairs.length).toBeGreaterThan(0); | |
| 176 | }); | |
| 177 | ||
| 178 | it("adds missing .gitignore entries", async () => { | |
| 179 | const result = await autoRepair("dev", "myapp", "main"); | |
| 180 | ||
| 181 | const gitignoreRepairs = result.repairs.filter( | |
| 182 | (r) => r.type === "gitignore" | |
| 183 | ); | |
| 184 | // May or may not need repair depending on current state | |
| 185 | // (first run may have already fixed it) | |
| 186 | expect(result.repaired).toBeDefined(); | |
| 187 | }); | |
| 188 | ||
| 189 | it("subsequent runs have fewer repairs", async () => { | |
| 190 | // Run once to fix everything | |
| 191 | const first = await autoRepair("dev", "myapp", "main"); | |
| 192 | // Run again — should have fewer or no repairs | |
| 193 | const second = await autoRepair("dev", "myapp", "main"); | |
| 194 | expect(second.repairs.length).toBeLessThanOrEqual(first.repairs.length); | |
| 195 | }); | |
| 196 | }); | |
| 197 | ||
| 198 | describe("health dashboard route", () => { | |
| 199 | it("GET /:owner/:repo/health returns health page", async () => { | |
| 200 | const app = (await import("../app")).default; | |
| 201 | const res = await app.request("/dev/myapp/health"); | |
| 202 | expect(res.status).toBe(200); | |
| 203 | const html = await res.text(); | |
| 204 | expect(html).toContain("Health Score"); | |
| 205 | expect(html).toContain("Security"); | |
| 206 | expect(html).toContain("Testing"); | |
| 207 | expect(html).toContain("Zero-Config CI"); | |
| 208 | }); | |
| 209 | }); |