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,126 @@
1
+ /**
2
+ * GET/POST /api/settings – structured presets + check toggles.
3
+ * Reads/writes .shimwrappercheck-presets.json and syncs .shimwrappercheckrc.
4
+ */
5
+ import { NextRequest, NextResponse } from "next/server";
6
+ import path from "path";
7
+ import fs from "fs";
8
+ import { getProjectRoot } from "@/lib/projectRoot";
9
+ import {
10
+ type SettingsData,
11
+ type Preset,
12
+ DEFAULT_SETTINGS,
13
+ DEFAULT_VIBE_CODE_PRESET,
14
+ buildRcContent,
15
+ SUPABASE_COMMAND_IDS,
16
+ GIT_COMMAND_IDS,
17
+ } from "@/lib/presets";
18
+
19
+ const PRESETS_FILE = ".shimwrappercheck-presets.json";
20
+ const RC_FILE = ".shimwrappercheckrc";
21
+
22
+ function getPresetsPath(): string {
23
+ return path.join(getProjectRoot(), PRESETS_FILE);
24
+ }
25
+ function getRcPath(): string {
26
+ return path.join(getProjectRoot(), RC_FILE);
27
+ }
28
+
29
+ function parseRcToSettings(rawRc: string): Partial<SettingsData> {
30
+ const checkToggles = { frontend: true, backend: true, aiReview: true };
31
+ const argsMatch = rawRc.match(/SHIM_CHECKS_ARGS="([^"]*)"/);
32
+ if (argsMatch) {
33
+ const args = argsMatch[1];
34
+ if (args.includes("--no-frontend")) checkToggles.frontend = false;
35
+ if (args.includes("--no-backend")) checkToggles.backend = false;
36
+ if (args.includes("--no-ai-review")) checkToggles.aiReview = false;
37
+ }
38
+ const enforceMatch = rawRc.match(/SHIM_ENFORCE_COMMANDS="([^"]*)"/);
39
+ const hookMatch = rawRc.match(/SHIM_HOOK_COMMANDS="([^"]*)"/);
40
+ const gitMatch = rawRc.match(/SHIM_GIT_ENFORCE_COMMANDS="([^"]*)"/);
41
+ const enforce = enforceMatch ? enforceMatch[1].split(",").map((s) => s.trim()) : [];
42
+ const hook = hookMatch ? hookMatch[1].split(",").map((s) => s.trim()) : [];
43
+ const gitEnforce = gitMatch ? gitMatch[1].split(",").map((s) => s.trim()) : [];
44
+ const supabaseEnforce = enforce.filter((c) => (SUPABASE_COMMAND_IDS as readonly string[]).includes(c));
45
+ const supabaseHook = hook.filter((c) => (SUPABASE_COMMAND_IDS as readonly string[]).includes(c));
46
+ const gitEnforceList = gitEnforce.filter((c) => (GIT_COMMAND_IDS as readonly string[]).includes(c));
47
+ const preset: Preset = {
48
+ ...DEFAULT_VIBE_CODE_PRESET,
49
+ supabase: { enforce: supabaseEnforce as any, hook: supabaseHook as any },
50
+ git: { enforce: gitEnforceList as any },
51
+ };
52
+ return {
53
+ presets: [preset],
54
+ activePresetId: DEFAULT_VIBE_CODE_PRESET.id,
55
+ checkToggles,
56
+ };
57
+ }
58
+
59
+ export async function GET() {
60
+ try {
61
+ const root = getProjectRoot();
62
+ const presetsPath = getPresetsPath();
63
+ const rcPath = getRcPath();
64
+
65
+ let settings: SettingsData = { ...DEFAULT_SETTINGS };
66
+
67
+ if (fs.existsSync(presetsPath)) {
68
+ try {
69
+ const raw = fs.readFileSync(presetsPath, "utf8");
70
+ const parsed = JSON.parse(raw) as SettingsData;
71
+ if (parsed.presets?.length) settings.presets = parsed.presets;
72
+ if (parsed.activePresetId) settings.activePresetId = parsed.activePresetId;
73
+ if (parsed.checkToggles) settings.checkToggles = { ...DEFAULT_SETTINGS.checkToggles, ...parsed.checkToggles };
74
+ } catch {
75
+ // use defaults
76
+ }
77
+ }
78
+
79
+ if (fs.existsSync(rcPath) && !fs.existsSync(presetsPath)) {
80
+ const rawRc = fs.readFileSync(rcPath, "utf8");
81
+ const fromRc = parseRcToSettings(rawRc);
82
+ if (fromRc.checkToggles) settings.checkToggles = fromRc.checkToggles;
83
+ if (fromRc.presets?.length) settings.presets = fromRc.presets;
84
+ if (fromRc.activePresetId) settings.activePresetId = fromRc.activePresetId;
85
+ }
86
+
87
+ return NextResponse.json(settings);
88
+ } catch (err) {
89
+ console.error("settings get error:", err);
90
+ return NextResponse.json(
91
+ { error: err instanceof Error ? err.message : "Unknown error" },
92
+ { status: 500 }
93
+ );
94
+ }
95
+ }
96
+
97
+ export async function POST(request: NextRequest) {
98
+ try {
99
+ const body = (await request.json()) as SettingsData;
100
+ if (!body || !Array.isArray(body.presets)) {
101
+ return NextResponse.json({ error: "presets array required" }, { status: 400 });
102
+ }
103
+
104
+ const settings: SettingsData = {
105
+ presets: body.presets,
106
+ activePresetId: body.activePresetId ?? DEFAULT_SETTINGS.activePresetId,
107
+ checkToggles: { ...DEFAULT_SETTINGS.checkToggles, ...body.checkToggles },
108
+ };
109
+
110
+ const root = getProjectRoot();
111
+ const presetsPath = getPresetsPath();
112
+ if (!fs.existsSync(root)) fs.mkdirSync(root, { recursive: true });
113
+
114
+ fs.writeFileSync(presetsPath, JSON.stringify(settings, null, 2), "utf8");
115
+ const rcContent = buildRcContent(settings);
116
+ fs.writeFileSync(getRcPath(), rcContent, "utf8");
117
+
118
+ return NextResponse.json({ ok: true });
119
+ } catch (err) {
120
+ console.error("settings post error:", err);
121
+ return NextResponse.json(
122
+ { error: err instanceof Error ? err.message : "Unknown error" },
123
+ { status: 500 }
124
+ );
125
+ }
126
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * GET /api/status – project status for dashboard (checks script, config, AGENTS.md, husky).
3
+ * Vercel-compatible; uses SHIM_PROJECT_ROOT when deployed.
4
+ */
5
+ import { NextResponse } from "next/server";
6
+ import path from "path";
7
+ import fs from "fs";
8
+ import { getProjectRoot } from "@/lib/projectRoot";
9
+
10
+ export async function GET() {
11
+ try {
12
+ const root = getProjectRoot();
13
+ const hasRc = fs.existsSync(path.join(root, ".shimwrappercheckrc"));
14
+ const hasPresets = fs.existsSync(path.join(root, ".shimwrappercheck-presets.json"));
15
+ const hasAgents = fs.existsSync(path.join(root, "AGENTS.md"));
16
+ const hasRunChecks = fs.existsSync(path.join(root, "scripts", "run-checks.sh"));
17
+ const hasHusky = fs.existsSync(path.join(root, ".husky", "pre-push"));
18
+ const hasGitHook = fs.existsSync(path.join(root, ".git", "hooks", "pre-push"));
19
+ const hasSupabase = fs.existsSync(path.join(root, "supabase", "config.toml"));
20
+
21
+ return NextResponse.json({
22
+ projectRoot: root,
23
+ config: hasRc,
24
+ presetsFile: hasPresets,
25
+ agentsMd: hasAgents,
26
+ runChecksScript: hasRunChecks,
27
+ prePushHusky: hasHusky,
28
+ prePushGit: hasGitHook,
29
+ supabase: hasSupabase,
30
+ });
31
+ } catch (err) {
32
+ console.error("status error:", err);
33
+ return NextResponse.json(
34
+ { error: err instanceof Error ? err.message : "Unknown error" },
35
+ { status: 500 }
36
+ );
37
+ }
38
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Config page: edit .shimwrappercheckrc (raw text).
3
+ * Location: app/config/page.tsx
4
+ */
5
+ "use client";
6
+
7
+ import { useEffect, useState } from "react";
8
+ export default function ConfigPage() {
9
+ const [raw, setRaw] = useState("");
10
+ const [loading, setLoading] = useState(true);
11
+ const [saving, setSaving] = useState(false);
12
+ const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null);
13
+
14
+ useEffect(() => {
15
+ fetch("/api/config")
16
+ .then((r) => r.json())
17
+ .then((data) => {
18
+ setRaw(data.raw ?? "");
19
+ setLoading(false);
20
+ })
21
+ .catch(() => setLoading(false));
22
+ }, []);
23
+
24
+ const save = () => {
25
+ setSaving(true);
26
+ setMessage(null);
27
+ fetch("/api/config", {
28
+ method: "POST",
29
+ headers: { "Content-Type": "application/json" },
30
+ body: JSON.stringify({ raw }),
31
+ })
32
+ .then((r) => r.json())
33
+ .then((data) => {
34
+ setSaving(false);
35
+ if (data.error) setMessage({ type: "error", text: data.error });
36
+ else setMessage({ type: "success", text: "Config gespeichert." });
37
+ })
38
+ .catch(() => {
39
+ setSaving(false);
40
+ setMessage({ type: "error", text: "Speichern fehlgeschlagen." });
41
+ });
42
+ };
43
+
44
+ if (loading) {
45
+ return (
46
+ <div className="flex justify-center py-12">
47
+ <span className="loading loading-spinner loading-lg" />
48
+ </div>
49
+ );
50
+ }
51
+
52
+ return (
53
+ <div className="space-y-6">
54
+ <h1 className="text-3xl font-bold">Config (.shimwrappercheckrc)</h1>
55
+ <p className="text-base-content/80">
56
+ Shell-Variablen und Kommentare. Wird beim nächsten Aufruf des Shims verwendet.
57
+ </p>
58
+ <textarea
59
+ className="textarea textarea-bordered w-full font-mono text-sm min-h-[320px]"
60
+ value={raw}
61
+ onChange={(e) => setRaw(e.target.value)}
62
+ placeholder="# shimwrappercheck config\nSHIM_ENFORCE_COMMANDS=\"functions,db,migration\"\n..."
63
+ spellCheck={false}
64
+ />
65
+ <div className="flex gap-4 items-center">
66
+ <button type="button" className="btn btn-primary" onClick={save} disabled={saving}>
67
+ {saving ? "Speichern…" : "Speichern"}
68
+ </button>
69
+ {message && (
70
+ <span className={message.type === "success" ? "text-success" : "text-error"}>
71
+ {message.text}
72
+ </span>
73
+ )}
74
+ </div>
75
+ </div>
76
+ );
77
+ }
@@ -0,0 +1,20 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ :root {
6
+ --background: #f8fafc;
7
+ --foreground: #0f172a;
8
+ }
9
+
10
+ @media (prefers-color-scheme: dark) {
11
+ :root {
12
+ --background: #0f172a;
13
+ --foreground: #f8fafc;
14
+ }
15
+ }
16
+
17
+ body {
18
+ color: var(--foreground);
19
+ background: var(--background);
20
+ }
@@ -0,0 +1,23 @@
1
+ import type { Metadata } from "next";
2
+ import "./globals.css";
3
+ import Nav from "@/components/Nav";
4
+
5
+ export const metadata: Metadata = {
6
+ title: "shimwrappercheck Dashboard",
7
+ description: "Config & AGENTS.md für shimwrappercheck",
8
+ };
9
+
10
+ export default function RootLayout({
11
+ children,
12
+ }: Readonly<{
13
+ children: React.ReactNode;
14
+ }>) {
15
+ return (
16
+ <html lang="de" data-theme="light">
17
+ <body className="min-h-screen bg-base-200">
18
+ <Nav />
19
+ <main className="container mx-auto px-4 py-6">{children}</main>
20
+ </body>
21
+ </html>
22
+ );
23
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Dashboard home: status, quick actions (run checks), links to Config and AGENTS.md.
3
+ * Location: app/page.tsx
4
+ */
5
+ "use client";
6
+
7
+ import { useEffect, useState } from "react";
8
+ import Link from "next/link";
9
+ import StatusCard from "@/components/StatusCard";
10
+
11
+ type Status = {
12
+ projectRoot?: string;
13
+ config?: boolean;
14
+ presetsFile?: boolean;
15
+ agentsMd?: boolean;
16
+ runChecksScript?: boolean;
17
+ prePushHusky?: boolean;
18
+ prePushGit?: boolean;
19
+ supabase?: boolean;
20
+ };
21
+
22
+ export default function DashboardPage() {
23
+ const [status, setStatus] = useState<Status | null>(null);
24
+ const [loading, setLoading] = useState(true);
25
+ const [runResult, setRunResult] = useState<{ stdout: string; stderr: string; code: number } | null>(null);
26
+ const [running, setRunning] = useState(false);
27
+
28
+ useEffect(() => {
29
+ fetch("/api/status")
30
+ .then((r) => r.json())
31
+ .then((data) => {
32
+ setStatus(data);
33
+ setLoading(false);
34
+ })
35
+ .catch(() => setLoading(false));
36
+ }, []);
37
+
38
+ const runChecks = () => {
39
+ setRunning(true);
40
+ setRunResult(null);
41
+ fetch("/api/run-checks", { method: "POST" })
42
+ .then((r) => r.json())
43
+ .then((data) => {
44
+ setRunResult({ stdout: data.stdout ?? "", stderr: data.stderr ?? "", code: data.code ?? 1 });
45
+ setRunning(false);
46
+ })
47
+ .catch(() => {
48
+ setRunResult({ stdout: "", stderr: "Request failed", code: 1 });
49
+ setRunning(false);
50
+ });
51
+ };
52
+
53
+ if (loading || !status) {
54
+ return (
55
+ <div className="flex justify-center py-12">
56
+ <span className="loading loading-spinner loading-lg" />
57
+ </div>
58
+ );
59
+ }
60
+
61
+ return (
62
+ <div className="space-y-8">
63
+ <h1 className="text-3xl font-bold">Dashboard</h1>
64
+ <p className="text-base-content/80">
65
+ Status und Aktionen für shimwrappercheck. AGENTS.md und Config können hier bearbeitet werden (auch für Agents).
66
+ </p>
67
+
68
+ <div>
69
+ <h2 className="text-xl font-semibold mb-4">Status</h2>
70
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
71
+ <StatusCard label=".shimwrappercheckrc" ok={!!status.config} />
72
+ <StatusCard label="Presets (.shimwrappercheck-presets.json)" ok={!!status.presetsFile} detail="Presets & Check-Toggles (Einstellungen)" />
73
+ <StatusCard label="AGENTS.md" ok={!!status.agentsMd} detail="Agent-Anweisungen (über GUI bearbeitbar)" />
74
+ <StatusCard label="scripts/run-checks.sh" ok={!!status.runChecksScript} />
75
+ <StatusCard label="Husky pre-push" ok={!!status.prePushHusky} />
76
+ <StatusCard label="Git pre-push Hook" ok={!!status.prePushGit} />
77
+ <StatusCard label="Supabase" ok={!!status.supabase} />
78
+ </div>
79
+ {status.projectRoot && (
80
+ <p className="mt-2 text-sm text-base-content/70">Projekt-Root: {status.projectRoot}</p>
81
+ )}
82
+ </div>
83
+
84
+ <div>
85
+ <h2 className="text-xl font-semibold mb-4">Aktionen</h2>
86
+ <div className="flex flex-wrap gap-4">
87
+ <button
88
+ type="button"
89
+ className="btn btn-primary"
90
+ onClick={runChecks}
91
+ disabled={running || !status.runChecksScript}
92
+ >
93
+ {running ? "Läuft…" : "Nur Checks ausführen"}
94
+ </button>
95
+ <Link href="/settings" className="btn btn-outline">
96
+ Einstellungen (Presets & Checks)
97
+ </Link>
98
+ <Link href="/config" className="btn btn-outline">
99
+ Config (Raw)
100
+ </Link>
101
+ <Link href="/agents" className="btn btn-outline">
102
+ AGENTS.md bearbeiten
103
+ </Link>
104
+ </div>
105
+ </div>
106
+
107
+ {runResult && (
108
+ <div className="card bg-base-100 shadow-md">
109
+ <div className="card-body">
110
+ <h3 className="card-title">
111
+ Letzte Check-Ausgabe {runResult.code === 0 ? "(OK)" : "(Fehler)"}
112
+ </h3>
113
+ <pre className="bg-base-200 p-4 rounded-lg text-sm overflow-auto max-h-64 whitespace-pre-wrap">
114
+ {runResult.stdout || "(keine Ausgabe)"}
115
+ {runResult.stderr ? `\n${runResult.stderr}` : ""}
116
+ </pre>
117
+ </div>
118
+ </div>
119
+ )}
120
+ </div>
121
+ );
122
+ }