Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

claude-config.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.

claude-config.test.tsBlame163 lines · 1 contributor
3b99c5fTest User1/**
2 * Block W2 — Claude harness configuration.
3 *
4 * Coverage:
5 * - .claude/settings.json is valid JSON
6 * - It declares the `gluecron` MCP server with the expected URL pattern
7 * - It denies the canonical 11 GitHub-write tool names
8 * - CLAUDE.md contains the "Source of truth: Gluecron" section
9 * - Each .claude/skills/<name>/SKILL.md exists with the expected
10 * frontmatter shape (name, description, tools)
11 */
12
13import { describe, it, expect } from "bun:test";
14import { readFile } from "node:fs/promises";
15import { join } from "node:path";
16
17const REPO_ROOT = join(import.meta.dir, "..", "..");
18const SETTINGS_PATH = join(REPO_ROOT, ".claude", "settings.json");
19const CLAUDE_MD = join(REPO_ROOT, "CLAUDE.md");
20const SKILLS_DIR = join(REPO_ROOT, ".claude", "skills");
21
22const SKILLS = ["gluecron-pr", "gluecron-issue", "gluecron-review"] as const;
23
24const DENIED_GITHUB_WRITE_TOOLS = [
25 "mcp__github__create_pull_request",
26 "mcp__github__merge_pull_request",
27 "mcp__github__create_or_update_file",
28 "mcp__github__push_files",
29 "mcp__github__delete_file",
30 "mcp__github__create_branch",
31 "mcp__github__create_repository",
32 "mcp__github__create_pull_request_with_copilot",
33 "mcp__github__update_pull_request",
34 "mcp__github__update_pull_request_branch",
35 "mcp__github__merge_pull_request_with_copilot",
36];
37
38// ---------------------------------------------------------------------------
39// .claude/settings.json shape
40// ---------------------------------------------------------------------------
41
42describe(".claude/settings.json", () => {
43 it("is valid JSON", async () => {
44 const raw = await readFile(SETTINGS_PATH, "utf8");
45 expect(() => JSON.parse(raw)).not.toThrow();
46 });
47
48 it("declares the gluecron MCP server", async () => {
49 const raw = await readFile(SETTINGS_PATH, "utf8");
50 const cfg = JSON.parse(raw) as {
51 mcpServers?: Record<string, { transport?: string; url?: string }>;
52 };
53 expect(cfg.mcpServers).toBeDefined();
54 expect(cfg.mcpServers!.gluecron).toBeDefined();
55 const entry = cfg.mcpServers!.gluecron!;
56 expect(entry.transport).toBe("http");
57 // URL ends with /mcp on a gluecron host.
58 expect(entry.url).toMatch(/gluecron[^\s]*\/mcp$/);
59 });
60
61 it("passes Authorization via env var (PAT never in file)", async () => {
62 const raw = await readFile(SETTINGS_PATH, "utf8");
63 const cfg = JSON.parse(raw) as {
64 mcpServers?: Record<
65 string,
66 { headers?: Record<string, string> }
67 >;
68 };
69 const auth = cfg.mcpServers?.gluecron?.headers?.Authorization;
70 expect(auth).toBeDefined();
71 expect(auth).toContain("${env:GLUECRON_PAT}");
72 expect(auth).toMatch(/^Bearer\s/);
73 });
74
75 it(`denies all ${DENIED_GITHUB_WRITE_TOOLS.length} canonical GitHub write tools`, async () => {
76 const raw = await readFile(SETTINGS_PATH, "utf8");
77 const cfg = JSON.parse(raw) as {
78 permissions?: { deny?: string[] };
79 };
80 expect(cfg.permissions?.deny).toBeDefined();
81 const deny = cfg.permissions!.deny!;
82 for (const tool of DENIED_GITHUB_WRITE_TOOLS) {
83 expect(deny).toContain(tool);
84 }
85 });
86});
87
88// ---------------------------------------------------------------------------
89// CLAUDE.md "Source of truth: Gluecron" section
90// ---------------------------------------------------------------------------
91
92describe("CLAUDE.md", () => {
93 it("contains the 'Source of truth: Gluecron' section", async () => {
94 const raw = await readFile(CLAUDE_MD, "utf8");
95 expect(raw).toContain("## Source of truth: Gluecron (not GitHub)");
96 });
97
98 it("references .claude/settings.json and the mcp-tools module", async () => {
99 const raw = await readFile(CLAUDE_MD, "utf8");
100 expect(raw).toContain(".claude/settings.json");
101 expect(raw).toContain("src/lib/mcp-tools.ts");
102 });
103
104 it("warns against calling mcp__github__* write tools", async () => {
105 const raw = await readFile(CLAUDE_MD, "utf8");
106 expect(raw).toContain("mcp__github__");
107 });
108
109 it("mentions the GLUECRON_PAT env var", async () => {
110 const raw = await readFile(CLAUDE_MD, "utf8");
111 expect(raw).toContain("GLUECRON_PAT");
112 });
113});
114
115// ---------------------------------------------------------------------------
116// Each .claude/skills/<name>/SKILL.md exists and has the expected frontmatter
117// ---------------------------------------------------------------------------
118
119function parseFrontmatter(src: string): Record<string, string> {
120 if (!src.startsWith("---")) {
121 throw new Error("SKILL.md missing frontmatter");
122 }
123 const end = src.indexOf("\n---", 3);
124 if (end < 0) throw new Error("SKILL.md frontmatter not terminated");
125 const raw = src.slice(3, end).trim();
126 const fields: Record<string, string> = {};
127 let currentKey = "";
128 let buffer: string[] = [];
129 const flush = () => {
130 if (currentKey) {
131 fields[currentKey] = buffer.join("\n").trim();
132 }
133 buffer = [];
134 };
135 for (const line of raw.split("\n")) {
136 const m = /^([a-zA-Z_][a-zA-Z0-9_-]*):\s*(.*)$/.exec(line);
137 if (m && !line.startsWith(" ") && !line.startsWith("\t")) {
138 flush();
139 currentKey = m[1];
140 buffer = [m[2]];
141 } else {
142 buffer.push(line);
143 }
144 }
145 flush();
146 return fields;
147}
148
149describe(".claude/skills/* SKILL.md shape", () => {
150 for (const name of SKILLS) {
151 it(`${name}/SKILL.md exists and has name+description+tools`, async () => {
152 const path = join(SKILLS_DIR, name, "SKILL.md");
153 const raw = await readFile(path, "utf8");
154 const fields = parseFrontmatter(raw);
155 expect(fields.name).toBe(name);
156 expect(fields.description).toBeDefined();
157 expect(fields.description.length).toBeGreaterThan(0);
158 expect(fields.description.toLowerCase()).toContain("gluecron");
159 expect(fields.tools).toBeDefined();
160 expect(fields.tools.length).toBeGreaterThan(0);
161 });
162 }
163});