shimwrappercheck 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 (48) hide show
  1. package/AGENTS.md +21 -0
  2. package/README.md +286 -0
  3. package/bin/git +5 -0
  4. package/bin/shim +5 -0
  5. package/bin/shimwrappercheck +2 -0
  6. package/bin/supabase +5 -0
  7. package/dashboard/.next/cache/config.json +7 -0
  8. package/dashboard/.next/package.json +1 -0
  9. package/dashboard/.next/routes-manifest.json +1 -0
  10. package/dashboard/.next/trace +1 -0
  11. package/dashboard/README.md +32 -0
  12. package/dashboard/app/agents/page.tsx +88 -0
  13. package/dashboard/app/api/agents-md/route.ts +68 -0
  14. package/dashboard/app/api/config/route.ts +51 -0
  15. package/dashboard/app/api/run-checks/route.ts +54 -0
  16. package/dashboard/app/api/settings/route.ts +126 -0
  17. package/dashboard/app/api/status/route.ts +38 -0
  18. package/dashboard/app/config/page.tsx +77 -0
  19. package/dashboard/app/globals.css +20 -0
  20. package/dashboard/app/layout.tsx +23 -0
  21. package/dashboard/app/page.tsx +122 -0
  22. package/dashboard/app/settings/page.tsx +422 -0
  23. package/dashboard/components/Nav.tsx +33 -0
  24. package/dashboard/components/StatusCard.tsx +27 -0
  25. package/dashboard/lib/presets.ts +97 -0
  26. package/dashboard/lib/projectRoot.ts +25 -0
  27. package/dashboard/next.config.js +6 -0
  28. package/dashboard/package-lock.json +5307 -0
  29. package/dashboard/package.json +28 -0
  30. package/dashboard/postcss.config.js +6 -0
  31. package/dashboard/tailwind.config.js +14 -0
  32. package/dashboard/tsconfig.json +20 -0
  33. package/docs/SHIM_WRAPPER_CONCEPT.md +79 -0
  34. package/docs/WORKFLOW_AND_GAP_ANALYSIS.md +159 -0
  35. package/package.json +24 -0
  36. package/scripts/cli-checked.sh +307 -0
  37. package/scripts/cli.js +23 -0
  38. package/scripts/fetch-edge-logs.sh +96 -0
  39. package/scripts/git-checked.sh +194 -0
  40. package/scripts/init.js +341 -0
  41. package/scripts/install.js +303 -0
  42. package/scripts/ping-edge-health.sh +113 -0
  43. package/scripts/setup.js +55 -0
  44. package/scripts/supabase-checked.sh +330 -0
  45. package/templates/ai-code-review.sh +217 -0
  46. package/templates/git-pre-push +41 -0
  47. package/templates/husky-pre-push +46 -0
  48. package/templates/run-checks.sh +67 -0
@@ -0,0 +1,422 @@
1
+ /**
2
+ * Settings page: presets (Vibe Code + custom), Supabase/Git command toggles, check toggles.
3
+ * Location: app/settings/page.tsx
4
+ */
5
+ "use client";
6
+
7
+ import { useEffect, useState, useCallback } from "react";
8
+ import type { SettingsData, Preset, ProviderId, SupabaseCommandId, GitCommandId } from "@/lib/presets";
9
+ import {
10
+ DEFAULT_VIBE_CODE_PRESET,
11
+ DEFAULT_CHECK_TOGGLES,
12
+ SUPABASE_COMMAND_IDS,
13
+ GIT_COMMAND_IDS,
14
+ } from "@/lib/presets";
15
+
16
+ export default function SettingsPage() {
17
+ const [settings, setSettings] = useState<SettingsData | null>(null);
18
+ const [loading, setLoading] = useState(true);
19
+ const [saving, setSaving] = useState(false);
20
+ const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null);
21
+ const [newPresetName, setNewPresetName] = useState("");
22
+ const [showNewPreset, setShowNewPreset] = useState(false);
23
+
24
+ const load = useCallback(() => {
25
+ fetch("/api/settings")
26
+ .then((r) => r.json())
27
+ .then((data) => {
28
+ setSettings(data);
29
+ setLoading(false);
30
+ })
31
+ .catch(() => setLoading(false));
32
+ }, []);
33
+
34
+ useEffect(() => {
35
+ load();
36
+ }, [load]);
37
+
38
+ const save = () => {
39
+ if (!settings) return;
40
+ setSaving(true);
41
+ setMessage(null);
42
+ fetch("/api/settings", {
43
+ method: "POST",
44
+ headers: { "Content-Type": "application/json" },
45
+ body: JSON.stringify(settings),
46
+ })
47
+ .then((r) => r.json())
48
+ .then((data) => {
49
+ setSaving(false);
50
+ if (data.error) setMessage({ type: "error", text: data.error });
51
+ else setMessage({ type: "success", text: "Einstellungen gespeichert." });
52
+ })
53
+ .catch(() => {
54
+ setSaving(false);
55
+ setMessage({ type: "error", text: "Speichern fehlgeschlagen." });
56
+ });
57
+ };
58
+
59
+ const activePreset = settings?.presets?.find((p) => p.id === settings.activePresetId) ?? DEFAULT_VIBE_CODE_PRESET;
60
+
61
+ const setActivePresetId = (id: string) => {
62
+ if (!settings) return;
63
+ setSettings({ ...settings, activePresetId: id });
64
+ };
65
+
66
+ const toggleSupabaseEnforce = (cmd: SupabaseCommandId) => {
67
+ if (!settings || !activePreset.supabase) return;
68
+ const presets = settings.presets.map((p) =>
69
+ p.id === activePreset.id
70
+ ? {
71
+ ...p,
72
+ supabase: {
73
+ ...p.supabase!,
74
+ enforce: p.supabase!.enforce.includes(cmd)
75
+ ? p.supabase!.enforce.filter((c) => c !== cmd)
76
+ : [...p.supabase!.enforce, cmd],
77
+ },
78
+ }
79
+ : p
80
+ );
81
+ setSettings({ ...settings, presets });
82
+ };
83
+
84
+ const toggleSupabaseHook = (cmd: SupabaseCommandId) => {
85
+ if (!settings || !activePreset.supabase) return;
86
+ const presets = settings.presets.map((p) =>
87
+ p.id === activePreset.id
88
+ ? {
89
+ ...p,
90
+ supabase: {
91
+ ...p.supabase!,
92
+ hook: p.supabase!.hook.includes(cmd)
93
+ ? p.supabase!.hook.filter((c) => c !== cmd)
94
+ : [...p.supabase!.hook, cmd],
95
+ },
96
+ }
97
+ : p
98
+ );
99
+ setSettings({ ...settings, presets });
100
+ };
101
+
102
+ const toggleGitEnforce = (cmd: GitCommandId) => {
103
+ if (!settings || !activePreset.git) return;
104
+ const presets = settings.presets.map((p) =>
105
+ p.id === activePreset.id
106
+ ? {
107
+ ...p,
108
+ git: {
109
+ ...p.git!,
110
+ enforce: p.git!.enforce.includes(cmd)
111
+ ? p.git!.enforce.filter((c) => c !== cmd)
112
+ : [...p.git!.enforce, cmd],
113
+ },
114
+ }
115
+ : p
116
+ );
117
+ setSettings({ ...settings, presets });
118
+ };
119
+
120
+ const setAutoPush = (v: boolean) => {
121
+ if (!settings) return;
122
+ const presets = settings.presets.map((p) =>
123
+ p.id === activePreset.id ? { ...p, autoPush: v } : p
124
+ );
125
+ setSettings({ ...settings, presets });
126
+ };
127
+
128
+ const setCheckToggles = (key: keyof typeof DEFAULT_CHECK_TOGGLES, value: boolean) => {
129
+ if (!settings) return;
130
+ setSettings({
131
+ ...settings,
132
+ checkToggles: { ...settings.checkToggles, [key]: value },
133
+ });
134
+ };
135
+
136
+ const addCustomPreset = () => {
137
+ if (!newPresetName.trim() || !settings) return;
138
+ const id = "preset-" + Date.now();
139
+ const newPreset: Preset = {
140
+ id,
141
+ name: newPresetName.trim(),
142
+ providers: [],
143
+ autoPush: false,
144
+ };
145
+ setSettings({
146
+ ...settings,
147
+ presets: [...settings.presets, newPreset],
148
+ activePresetId: id,
149
+ });
150
+ setNewPresetName("");
151
+ setShowNewPreset(false);
152
+ };
153
+
154
+ const addProviderToPreset = (provider: ProviderId) => {
155
+ if (!settings || activePreset.providers.includes(provider)) return;
156
+ const presets = settings.presets.map((p) => {
157
+ if (p.id !== activePreset.id) return p;
158
+ const providers = [...p.providers, provider];
159
+ return {
160
+ ...p,
161
+ providers,
162
+ supabase:
163
+ provider === "supabase"
164
+ ? { enforce: [...SUPABASE_COMMAND_IDS], hook: [...SUPABASE_COMMAND_IDS] }
165
+ : p.supabase,
166
+ git: provider === "git" ? { enforce: ["push"] } : p.git,
167
+ };
168
+ });
169
+ setSettings({ ...settings, presets });
170
+ };
171
+
172
+ const removeProviderFromPreset = (provider: ProviderId) => {
173
+ if (!settings) return;
174
+ const presets = settings.presets.map((p) =>
175
+ p.id === activePreset.id
176
+ ? {
177
+ ...p,
178
+ providers: p.providers.filter((pr) => pr !== provider),
179
+ supabase: provider === "supabase" ? undefined : p.supabase,
180
+ git: provider === "git" ? undefined : p.git,
181
+ }
182
+ : p
183
+ );
184
+ setSettings({ ...settings, presets });
185
+ };
186
+
187
+ const deletePreset = (id: string) => {
188
+ if (!settings || id === DEFAULT_VIBE_CODE_PRESET.id) return;
189
+ const presets = settings.presets.filter((p) => p.id !== id);
190
+ setSettings({
191
+ ...settings,
192
+ presets,
193
+ activePresetId: settings.activePresetId === id ? DEFAULT_VIBE_CODE_PRESET.id : settings.activePresetId,
194
+ });
195
+ };
196
+
197
+ if (loading || !settings) {
198
+ return (
199
+ <div className="flex justify-center py-12">
200
+ <span className="loading loading-spinner loading-lg" />
201
+ </div>
202
+ );
203
+ }
204
+
205
+ return (
206
+ <div className="space-y-8">
207
+ <h1 className="text-3xl font-bold">Einstellungen</h1>
208
+ <p className="text-base-content/80">
209
+ Preset wählen, Befehle (Supabase / GitHub) und Checks ein-/ausschalten. Speichern schreibt .shimwrappercheckrc.
210
+ </p>
211
+
212
+ {/* Preset selector */}
213
+ <div className="card bg-base-100 shadow-md">
214
+ <div className="card-body">
215
+ <h2 className="card-title">Preset</h2>
216
+ <div className="flex flex-wrap gap-2 items-center">
217
+ {settings.presets.map((p) => (
218
+ <div key={p.id} className="flex items-center gap-1">
219
+ <button
220
+ type="button"
221
+ className={`btn btn-sm ${p.id === settings.activePresetId ? "btn-primary" : "btn-ghost"}`}
222
+ onClick={() => setActivePresetId(p.id)}
223
+ >
224
+ {p.name}
225
+ </button>
226
+ {p.id !== DEFAULT_VIBE_CODE_PRESET.id && (
227
+ <button
228
+ type="button"
229
+ className="btn btn-ghost btn-sm btn-circle"
230
+ onClick={() => deletePreset(p.id)}
231
+ title="Preset löschen"
232
+ >
233
+ ×
234
+ </button>
235
+ )}
236
+ </div>
237
+ ))}
238
+ {!showNewPreset ? (
239
+ <button type="button" className="btn btn-outline btn-sm" onClick={() => setShowNewPreset(true)}>
240
+ + Neues Preset
241
+ </button>
242
+ ) : (
243
+ <div className="flex gap-2 items-center">
244
+ <input
245
+ type="text"
246
+ className="input input-bordered input-sm w-40"
247
+ placeholder="Name"
248
+ value={newPresetName}
249
+ onChange={(e) => setNewPresetName(e.target.value)}
250
+ />
251
+ <button type="button" className="btn btn-primary btn-sm" onClick={addCustomPreset}>
252
+ Anlegen
253
+ </button>
254
+ <button type="button" className="btn btn-ghost btn-sm" onClick={() => setShowNewPreset(false)}>
255
+ Abbrechen
256
+ </button>
257
+ </div>
258
+ )}
259
+ </div>
260
+ </div>
261
+ </div>
262
+
263
+ {/* Active preset: providers (for custom) + command toggles */}
264
+ <div className="space-y-6">
265
+ {activePreset.id !== DEFAULT_VIBE_CODE_PRESET.id && (
266
+ <div className="card bg-base-100 shadow-md">
267
+ <div className="card-body">
268
+ <h2 className="card-title">Provider in diesem Preset</h2>
269
+ <p className="text-sm text-base-content/70">Provider hinzufügen (z. B. GitHub, Supabase).</p>
270
+ <div className="flex gap-2 flex-wrap">
271
+ {(["supabase", "git"] as const).map((prov) => (
272
+ <div key={prov} className="flex items-center gap-1">
273
+ {activePreset.providers.includes(prov) ? (
274
+ <>
275
+ <span className="badge badge-primary">{prov === "git" ? "GitHub" : "Supabase"}</span>
276
+ <button
277
+ type="button"
278
+ className="btn btn-ghost btn-xs"
279
+ onClick={() => removeProviderFromPreset(prov)}
280
+ >
281
+ entfernen
282
+ </button>
283
+ </>
284
+ ) : (
285
+ <button type="button" className="btn btn-outline btn-sm" onClick={() => addProviderToPreset(prov)}>
286
+ + {prov === "git" ? "GitHub" : "Supabase"}
287
+ </button>
288
+ )}
289
+ </div>
290
+ ))}
291
+ </div>
292
+ </div>
293
+ </div>
294
+ )}
295
+
296
+ {/* Supabase */}
297
+ {activePreset.providers.includes("supabase") && (
298
+ <div className="card bg-base-100 shadow-md">
299
+ <div className="card-body">
300
+ <h2 className="card-title">Supabase</h2>
301
+ <p className="text-sm text-base-content/70">Für welche Befehle Checks und Hooks laufen.</p>
302
+ <div className="overflow-x-auto">
303
+ <table className="table table-sm">
304
+ <thead>
305
+ <tr>
306
+ <th>Befehl</th>
307
+ <th>Checks</th>
308
+ <th>Hooks (nach Deploy)</th>
309
+ </tr>
310
+ </thead>
311
+ <tbody>
312
+ {SUPABASE_COMMAND_IDS.map((cmd) => (
313
+ <tr key={cmd}>
314
+ <td>{cmd}</td>
315
+ <td>
316
+ <input
317
+ type="checkbox"
318
+ className="toggle toggle-sm"
319
+ checked={activePreset.supabase?.enforce.includes(cmd) ?? false}
320
+ onChange={() => toggleSupabaseEnforce(cmd)}
321
+ />
322
+ </td>
323
+ <td>
324
+ <input
325
+ type="checkbox"
326
+ className="toggle toggle-sm"
327
+ checked={activePreset.supabase?.hook.includes(cmd) ?? false}
328
+ onChange={() => toggleSupabaseHook(cmd)}
329
+ />
330
+ </td>
331
+ </tr>
332
+ ))}
333
+ </tbody>
334
+ </table>
335
+ </div>
336
+ <div className="form-control">
337
+ <label className="label cursor-pointer justify-start gap-2">
338
+ <input
339
+ type="checkbox"
340
+ className="checkbox checkbox-sm"
341
+ checked={activePreset.autoPush}
342
+ onChange={(e) => setAutoPush(e.target.checked)}
343
+ />
344
+ <span className="label-text">Nach Erfolg automatisch git push</span>
345
+ </label>
346
+ </div>
347
+ </div>
348
+ </div>
349
+ )}
350
+
351
+ {/* Git */}
352
+ {activePreset.providers.includes("git") && (
353
+ <div className="card bg-base-100 shadow-md">
354
+ <div className="card-body">
355
+ <h2 className="card-title">GitHub (Git)</h2>
356
+ <p className="text-sm text-base-content/70">Für welche Git-Befehle Checks laufen.</p>
357
+ <div className="flex flex-wrap gap-4">
358
+ {GIT_COMMAND_IDS.map((cmd) => (
359
+ <label key={cmd} className="flex items-center gap-2 cursor-pointer">
360
+ <input
361
+ type="checkbox"
362
+ className="toggle toggle-sm"
363
+ checked={activePreset.git?.enforce.includes(cmd) ?? false}
364
+ onChange={() => toggleGitEnforce(cmd)}
365
+ />
366
+ <span>{cmd}</span>
367
+ </label>
368
+ ))}
369
+ </div>
370
+ </div>
371
+ </div>
372
+ )}
373
+ </div>
374
+
375
+ {/* Check toggles */}
376
+ <div className="card bg-base-100 shadow-md">
377
+ <div className="card-body">
378
+ <h2 className="card-title">Checks (run-checks.sh)</h2>
379
+ <p className="text-sm text-base-content/70">Welche Schritte beim Check-Lauf ausgeführt werden.</p>
380
+ <div className="flex flex-wrap gap-6">
381
+ <label className="flex items-center gap-2 cursor-pointer">
382
+ <input
383
+ type="checkbox"
384
+ className="toggle toggle-sm"
385
+ checked={settings.checkToggles.frontend}
386
+ onChange={(e) => setCheckToggles("frontend", e.target.checked)}
387
+ />
388
+ <span>Frontend (Lint, Build, Test, npm audit)</span>
389
+ </label>
390
+ <label className="flex items-center gap-2 cursor-pointer">
391
+ <input
392
+ type="checkbox"
393
+ className="toggle toggle-sm"
394
+ checked={settings.checkToggles.backend}
395
+ onChange={(e) => setCheckToggles("backend", e.target.checked)}
396
+ />
397
+ <span>Backend (deno fmt/lint/audit)</span>
398
+ </label>
399
+ <label className="flex items-center gap-2 cursor-pointer">
400
+ <input
401
+ type="checkbox"
402
+ className="toggle toggle-sm"
403
+ checked={settings.checkToggles.aiReview}
404
+ onChange={(e) => setCheckToggles("aiReview", e.target.checked)}
405
+ />
406
+ <span>AI Review (Codex/Cursor)</span>
407
+ </label>
408
+ </div>
409
+ </div>
410
+ </div>
411
+
412
+ <div className="flex gap-4 items-center">
413
+ <button type="button" className="btn btn-primary" onClick={save} disabled={saving}>
414
+ {saving ? "Speichern…" : "Speichern"}
415
+ </button>
416
+ {message && (
417
+ <span className={message.type === "success" ? "text-success" : "text-error"}>{message.text}</span>
418
+ )}
419
+ </div>
420
+ </div>
421
+ );
422
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Top navigation for shimwrappercheck dashboard.
3
+ * Location: /components/Nav.tsx
4
+ */
5
+ "use client";
6
+
7
+ import Link from "next/link";
8
+
9
+ export default function Nav() {
10
+ return (
11
+ <div className="navbar bg-base-100 shadow-lg">
12
+ <div className="flex-1">
13
+ <Link href="/" className="btn btn-ghost text-xl">
14
+ shimwrappercheck
15
+ </Link>
16
+ </div>
17
+ <div className="flex-none gap-2">
18
+ <Link href="/" className="btn btn-ghost btn-sm">
19
+ Dashboard
20
+ </Link>
21
+ <Link href="/settings" className="btn btn-ghost btn-sm">
22
+ Einstellungen
23
+ </Link>
24
+ <Link href="/config" className="btn btn-ghost btn-sm">
25
+ Config
26
+ </Link>
27
+ <Link href="/agents" className="btn btn-ghost btn-sm">
28
+ AGENTS.md
29
+ </Link>
30
+ </div>
31
+ </div>
32
+ );
33
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Status card for dashboard: shows one check (label + ok/missing).
3
+ * Location: /components/StatusCard.tsx
4
+ */
5
+ "use client";
6
+
7
+ export default function StatusCard({
8
+ label,
9
+ ok,
10
+ detail,
11
+ }: {
12
+ label: string;
13
+ ok: boolean;
14
+ detail?: string;
15
+ }) {
16
+ return (
17
+ <div className="card bg-base-100 shadow-md">
18
+ <div className="card-body p-4">
19
+ <h3 className="card-title text-sm">{label}</h3>
20
+ <p className={ok ? "text-success" : "text-warning"}>
21
+ {ok ? "✓ Vorhanden" : "— Nicht gefunden"}
22
+ </p>
23
+ {detail && <p className="text-xs opacity-80">{detail}</p>}
24
+ </div>
25
+ </div>
26
+ );
27
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Presets data model and default "Vibe Code" preset.
3
+ * Maps to .shimwrappercheckrc (SHIM_ENFORCE_COMMANDS, SHIM_HOOK_COMMANDS, etc.).
4
+ */
5
+
6
+ export type ProviderId = "supabase" | "git";
7
+
8
+ export const SUPABASE_COMMAND_IDS = [
9
+ "functions",
10
+ "db",
11
+ "migration",
12
+ "push",
13
+ ] as const;
14
+ export const GIT_COMMAND_IDS = ["push", "commit", "merge", "rebase"] as const;
15
+
16
+ export type SupabaseCommandId = (typeof SUPABASE_COMMAND_IDS)[number];
17
+ export type GitCommandId = (typeof GIT_COMMAND_IDS)[number];
18
+
19
+ export interface SupabaseCommands {
20
+ enforce: SupabaseCommandId[];
21
+ hook: SupabaseCommandId[];
22
+ }
23
+ export interface GitCommands {
24
+ enforce: GitCommandId[];
25
+ }
26
+
27
+ export interface Preset {
28
+ id: string;
29
+ name: string;
30
+ providers: ProviderId[];
31
+ supabase?: SupabaseCommands;
32
+ git?: GitCommands;
33
+ autoPush: boolean;
34
+ }
35
+
36
+ export interface CheckToggles {
37
+ frontend: boolean;
38
+ backend: boolean;
39
+ aiReview: boolean;
40
+ }
41
+
42
+ export interface SettingsData {
43
+ presets: Preset[];
44
+ activePresetId: string;
45
+ checkToggles: CheckToggles;
46
+ }
47
+
48
+ const VIBE_CODE_ID = "vibe-code";
49
+
50
+ export const DEFAULT_CHECK_TOGGLES: CheckToggles = {
51
+ frontend: true,
52
+ backend: true,
53
+ aiReview: true,
54
+ };
55
+
56
+ export const DEFAULT_VIBE_CODE_PRESET: Preset = {
57
+ id: VIBE_CODE_ID,
58
+ name: "Vibe Code",
59
+ providers: ["supabase", "git"],
60
+ supabase: {
61
+ enforce: ["functions", "db", "migration"],
62
+ hook: ["functions", "db", "migration"],
63
+ },
64
+ git: {
65
+ enforce: ["push"],
66
+ },
67
+ autoPush: true,
68
+ };
69
+
70
+ export const DEFAULT_SETTINGS: SettingsData = {
71
+ presets: [DEFAULT_VIBE_CODE_PRESET],
72
+ activePresetId: VIBE_CODE_ID,
73
+ checkToggles: DEFAULT_CHECK_TOGGLES,
74
+ };
75
+
76
+ /** Build .shimwrappercheckrc content from active preset + check toggles */
77
+ export function buildRcContent(settings: SettingsData): string {
78
+ const preset = settings.presets.find((p) => p.id === settings.activePresetId) ?? DEFAULT_VIBE_CODE_PRESET;
79
+ const lines: string[] = ["# shimwrappercheck config (generated by dashboard)"];
80
+
81
+ if (preset.providers.includes("supabase") && preset.supabase) {
82
+ lines.push(`SHIM_ENFORCE_COMMANDS="${preset.supabase.enforce.join(",")}"`);
83
+ lines.push(`SHIM_HOOK_COMMANDS="${preset.supabase.hook.join(",")}"`);
84
+ lines.push(`SHIM_AUTO_PUSH=${preset.autoPush ? 1 : 0}`);
85
+ }
86
+ if (preset.providers.includes("git") && preset.git) {
87
+ lines.push(`SHIM_GIT_ENFORCE_COMMANDS="${preset.git.enforce.join(",")}"`);
88
+ }
89
+
90
+ const args: string[] = [];
91
+ if (!settings.checkToggles.frontend) args.push("--no-frontend");
92
+ if (!settings.checkToggles.backend) args.push("--no-backend");
93
+ if (!settings.checkToggles.aiReview) args.push("--no-ai-review");
94
+ if (args.length) lines.push(`SHIM_CHECKS_ARGS="${args.join(" ")}"`);
95
+
96
+ return lines.join("\n") + "\n";
97
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Resolves the project root (shimwrappercheck repo root) for reading/writing
3
+ * .shimwrappercheckrc and AGENTS.md. When running from dashboard/, parent is repo root.
4
+ * Override with SHIM_PROJECT_ROOT for Vercel or custom setups.
5
+ */
6
+ import path from "path";
7
+ import fs from "fs";
8
+
9
+ export function getProjectRoot(): string {
10
+ const envRoot = process.env.SHIM_PROJECT_ROOT;
11
+ if (envRoot && fs.existsSync(envRoot)) return path.resolve(envRoot);
12
+
13
+ const cwd = process.cwd();
14
+ const name = path.basename(cwd);
15
+ if (name === "dashboard") return path.resolve(cwd, "..");
16
+
17
+ const parent = path.resolve(cwd, "..");
18
+ const hasRc = fs.existsSync(path.join(cwd, ".shimwrappercheckrc"));
19
+ const hasAgents = fs.existsSync(path.join(cwd, "AGENTS.md"));
20
+ if (hasRc || hasAgents) return cwd;
21
+ if (fs.existsSync(path.join(parent, ".shimwrappercheckrc")) || fs.existsSync(path.join(parent, "AGENTS.md")))
22
+ return parent;
23
+
24
+ return cwd;
25
+ }
@@ -0,0 +1,6 @@
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ reactStrictMode: true,
4
+ };
5
+
6
+ module.exports = nextConfig;