@theproductguy/create-mission-control 1.0.4 → 1.0.6
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.
- package/README.md +1 -1
- package/bin/cli.js +52 -3
- package/package.json +1 -1
- package/src/template/.env.example +28 -0
- package/src/template/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
- package/src/template/.github/ISSUE_TEMPLATE/feature_request.md +30 -0
- package/src/template/.github/PULL_REQUEST_TEMPLATE.md +33 -0
- package/src/template/.github/workflows/ci.yml +83 -0
- package/src/template/.husky/commit-msg +1 -0
- package/src/template/.husky/pre-commit +1 -0
- package/src/template/.prettierrc +11 -0
- package/src/template/README.md +78 -0
- package/src/template/agent-os/commands/create-constitution/create-constitution.md +89 -0
- package/src/template/agent-os/commands/research-tech/research-tech.md +93 -0
- package/src/template/agent-os/commands/shape-spec/shape-spec.md +2 -1
- package/src/template/agent-os/docs/context7.md +20 -0
- package/src/template/commitlint.config.js +25 -0
- package/src/template/control-center/backend/index.js +58 -1
- package/src/template/control-center/frontend/src/App.tsx +188 -78
- package/src/template/design-system/.gemini/hooks/ai-slop-guard.md +80 -0
- package/src/template/design-system/.gemini/hooks/on-design-complete.md +63 -0
- package/src/template/eslint.config.js +37 -0
- package/src/template/lint-staged.config.js +6 -0
- package/src/template/package.json +25 -2
- package/src/template/src/__tests__/example.test.ts +17 -0
- package/src/template/src/__tests__/setup.ts +14 -0
- package/src/template/vitest.config.ts +25 -0
|
@@ -136,6 +136,53 @@ app.get('/api/status', async (req, res) => {
|
|
|
136
136
|
const hasTokens = fs.existsSync(path.join(designDir, 'product/design-system/colors.json'));
|
|
137
137
|
const hasShell = fs.existsSync(path.join(designDir, 'product/shell/spec.md'));
|
|
138
138
|
|
|
139
|
+
// Implementation tracking
|
|
140
|
+
const appDir = path.join(PROJECT_ROOT, 'app');
|
|
141
|
+
const hasScaffold = fs.existsSync(path.join(APP_DIR, 'src/lib/utils.ts'));
|
|
142
|
+
|
|
143
|
+
// Check for test files
|
|
144
|
+
const testFiles = glob.sync('**/*.{test,spec}.{ts,tsx,js,jsx}', { cwd: appDir, ignore: ['node_modules/**'] });
|
|
145
|
+
const hasTests = testFiles.length > 0;
|
|
146
|
+
|
|
147
|
+
// Check for coverage report
|
|
148
|
+
const coverageDir = path.join(PROJECT_ROOT, 'coverage');
|
|
149
|
+
const coverageSummary = path.join(coverageDir, 'coverage-summary.json');
|
|
150
|
+
let coveragePercent = null;
|
|
151
|
+
if (fs.existsSync(coverageSummary)) {
|
|
152
|
+
try {
|
|
153
|
+
const coverage = JSON.parse(fs.readFileSync(coverageSummary, 'utf-8'));
|
|
154
|
+
if (coverage.total && coverage.total.lines) {
|
|
155
|
+
coveragePercent = Math.round(coverage.total.lines.pct);
|
|
156
|
+
}
|
|
157
|
+
} catch (e) {
|
|
158
|
+
// Ignore parsing errors
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Check git status
|
|
163
|
+
let gitInfo = { initialized: false, branch: null, uncommitted: 0, lastCommit: null };
|
|
164
|
+
const gitDir = path.join(PROJECT_ROOT, '.git');
|
|
165
|
+
if (fs.existsSync(gitDir)) {
|
|
166
|
+
gitInfo.initialized = true;
|
|
167
|
+
try {
|
|
168
|
+
const { execSync } = require('child_process');
|
|
169
|
+
gitInfo.branch = execSync('git rev-parse --abbrev-ref HEAD', { cwd: PROJECT_ROOT, encoding: 'utf-8' }).trim();
|
|
170
|
+
const status = execSync('git status --porcelain', { cwd: PROJECT_ROOT, encoding: 'utf-8' });
|
|
171
|
+
gitInfo.uncommitted = status.split('\n').filter(l => l.trim()).length;
|
|
172
|
+
try {
|
|
173
|
+
gitInfo.lastCommit = execSync('git log -1 --pretty=format:"%s"', { cwd: PROJECT_ROOT, encoding: 'utf-8' }).trim();
|
|
174
|
+
} catch (e) {
|
|
175
|
+
// No commits yet
|
|
176
|
+
}
|
|
177
|
+
} catch (e) {
|
|
178
|
+
// Git commands failed
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Check for linked specs
|
|
183
|
+
const specsWithTasks = specs.filter(s => s.tasks.exists && s.tasks.total > 0);
|
|
184
|
+
const specsCompleted = specs.filter(s => s.tasks.exists && s.tasks.completed === s.tasks.total && s.tasks.total > 0);
|
|
185
|
+
|
|
139
186
|
res.json({
|
|
140
187
|
product: productFiles,
|
|
141
188
|
services,
|
|
@@ -155,7 +202,17 @@ app.get('/api/status', async (req, res) => {
|
|
|
155
202
|
}
|
|
156
203
|
},
|
|
157
204
|
implementation: {
|
|
158
|
-
scaffolded:
|
|
205
|
+
scaffolded: hasScaffold,
|
|
206
|
+
tests: {
|
|
207
|
+
count: testFiles.length,
|
|
208
|
+
hasTests
|
|
209
|
+
},
|
|
210
|
+
coverage: coveragePercent,
|
|
211
|
+
specs: {
|
|
212
|
+
total: specsWithTasks.length,
|
|
213
|
+
completed: specsCompleted.length
|
|
214
|
+
},
|
|
215
|
+
git: gitInfo
|
|
159
216
|
},
|
|
160
217
|
specs,
|
|
161
218
|
projectRoot: PROJECT_ROOT
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
2
|
import axios from 'axios';
|
|
3
|
-
import { Layout, Layers, FileText, Code, CheckSquare, RefreshCw, ArrowRight, X, Plus, Trash2, WalletCards, LayoutDashboard, Copy, Package } from 'lucide-react';
|
|
3
|
+
import { Layout, Layers, FileText, Code, CheckSquare, RefreshCw, ArrowRight, X, Plus, Trash2, WalletCards, LayoutDashboard, Copy, Package, Play } from 'lucide-react';
|
|
4
4
|
import { useToast } from './components/ui/ToastContext';
|
|
5
5
|
import { ThemeToggle } from '@theproductguy/agent-os-ui';
|
|
6
6
|
import '@theproductguy/agent-os-ui/style.css';
|
|
@@ -46,6 +46,21 @@ interface ProjectState {
|
|
|
46
46
|
};
|
|
47
47
|
implementation: {
|
|
48
48
|
scaffolded: boolean;
|
|
49
|
+
tests?: {
|
|
50
|
+
count: number;
|
|
51
|
+
hasTests: boolean;
|
|
52
|
+
};
|
|
53
|
+
coverage?: number | null;
|
|
54
|
+
specs?: {
|
|
55
|
+
total: number;
|
|
56
|
+
completed: number;
|
|
57
|
+
};
|
|
58
|
+
git?: {
|
|
59
|
+
initialized: boolean;
|
|
60
|
+
branch: string | null;
|
|
61
|
+
uncommitted: number;
|
|
62
|
+
lastCommit: string | null;
|
|
63
|
+
};
|
|
49
64
|
};
|
|
50
65
|
specs: Spec[];
|
|
51
66
|
services?: {
|
|
@@ -130,19 +145,33 @@ function StatusItem({ label, status, icon, small, onClick }: any) {
|
|
|
130
145
|
}
|
|
131
146
|
|
|
132
147
|
function PromptButton({ label, prompt, onClick, small, primary }: any) {
|
|
148
|
+
const deepLink = `vscode://vscode.executeCommand/workbench.action.chat.open?query=${encodeURIComponent(prompt)}`;
|
|
149
|
+
|
|
133
150
|
return (
|
|
134
|
-
<
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
151
|
+
<div className={`flex gap-1 items-center ${!small ? 'w-full' : 'flex-1'}`}>
|
|
152
|
+
<button
|
|
153
|
+
onClick={() => onClick(prompt)}
|
|
154
|
+
className={`flex items-center justify-center gap-2 rounded-lg font-medium transition cursor-pointer flex-1
|
|
155
|
+
${small ? 'px-3 py-1.5 text-xs' : 'px-4 py-2 text-sm'}
|
|
156
|
+
${primary
|
|
157
|
+
? 'bg-primary hover:bg-primary/90 text-primary-foreground shadow-sm'
|
|
158
|
+
: 'bg-background hover:bg-secondary border border-border text-foreground hover:text-foreground'}
|
|
159
|
+
`}
|
|
160
|
+
>
|
|
161
|
+
<Copy size={small ? 12 : 14} />
|
|
162
|
+
{label}
|
|
163
|
+
</button>
|
|
164
|
+
|
|
165
|
+
<a
|
|
166
|
+
href={deepLink}
|
|
167
|
+
className={`flex items-center justify-center rounded-lg border border-border bg-secondary hover:bg-secondary/80 text-foreground transition
|
|
168
|
+
${small ? 'w-8 h-[30px]' : 'w-10 h-[38px]'}
|
|
169
|
+
`}
|
|
170
|
+
title="Run in IDE (Deep Link)"
|
|
171
|
+
>
|
|
172
|
+
<Play size={small ? 12 : 14} fill="currentColor" className="opacity-70" />
|
|
173
|
+
</a>
|
|
174
|
+
</div>
|
|
146
175
|
)
|
|
147
176
|
}
|
|
148
177
|
|
|
@@ -701,77 +730,14 @@ function App() {
|
|
|
701
730
|
</div>
|
|
702
731
|
</section>
|
|
703
732
|
|
|
704
|
-
{/* Phase 3:
|
|
705
|
-
<section className="bg-card border border-border rounded-xl p-5 shadow-sm h-full flex flex-col transition-all hover:shadow-md hover:border-border/80">
|
|
706
|
-
<div className="flex items-center gap-3 mb-5 pb-4 border-b border-border/50">
|
|
707
|
-
<div className="p-2 bg-sidebar-primary/10 text-sidebar-primary rounded-lg">
|
|
708
|
-
<Package size={20} />
|
|
709
|
-
</div>
|
|
710
|
-
<h2 className="text-lg font-semibold">3. Implementation</h2>
|
|
711
|
-
</div>
|
|
712
|
-
|
|
713
|
-
<div className="space-y-4 flex-1">
|
|
714
|
-
<div className="flex items-center justify-between p-3 bg-secondary/30 rounded-lg border border-border/50">
|
|
715
|
-
<span className="text-foreground font-medium text-sm flex items-center gap-2"><Layout size={16} className="text-muted-foreground" /> Scaffold</span>
|
|
716
|
-
{state?.implementation?.scaffolded ? (
|
|
717
|
-
<span className="text-emerald-600 bg-emerald-50 dark:bg-emerald-900/20 px-2 py-0.5 rounded text-xs font-medium border border-emerald-200 dark:border-emerald-800">Done</span>
|
|
718
|
-
) : (
|
|
719
|
-
<span className="text-muted-foreground bg-secondary px-2 py-0.5 rounded text-xs font-medium border border-border">Pending</span>
|
|
720
|
-
)}
|
|
721
|
-
</div>
|
|
722
|
-
</div>
|
|
723
|
-
|
|
724
|
-
<div className="mt-6">
|
|
725
|
-
{!state?.implementation?.scaffolded ? (
|
|
726
|
-
<PromptButton
|
|
727
|
-
label="Scaffold App"
|
|
728
|
-
prompt="Antigravity, scaffold the implementation. Read 'agent-os/commands/scaffold-implementation/scaffold-implementation.md'."
|
|
729
|
-
onClick={copyToClipboard}
|
|
730
|
-
/>
|
|
731
|
-
) : (
|
|
732
|
-
<div className="p-3 bg-secondary/30 rounded-lg border border-border/50 text-sm text-muted-foreground text-center italic mb-4">
|
|
733
|
-
App scaffolded. Ready for implementation.
|
|
734
|
-
</div>
|
|
735
|
-
)}
|
|
736
|
-
|
|
737
|
-
{state?.implementation?.scaffolded && state?.design?.exportPrompts?.oneShot && (
|
|
738
|
-
<div className="pt-3 border-t border-border/50">
|
|
739
|
-
<p className="text-xs text-muted-foreground mb-2 font-medium">Implementation Options:</p>
|
|
740
|
-
<div className="space-y-2">
|
|
741
|
-
<PromptButton
|
|
742
|
-
label="Option A: One-Shot (All)"
|
|
743
|
-
prompt={`Antigravity, implement the app. Read 'product-plan/prompts/one-shot-prompt.md'.`}
|
|
744
|
-
onClick={copyToClipboard}
|
|
745
|
-
small
|
|
746
|
-
primary
|
|
747
|
-
/>
|
|
748
|
-
{state?.design?.exportPrompts?.section && (
|
|
749
|
-
<div className="relative">
|
|
750
|
-
<PromptButton
|
|
751
|
-
label="Option B: Incremental (Section)"
|
|
752
|
-
prompt={`Antigravity, implement a section. Read 'product-plan/prompts/section-prompt.md'.`}
|
|
753
|
-
onClick={copyToClipboard}
|
|
754
|
-
small
|
|
755
|
-
/>
|
|
756
|
-
<p className="text-[10px] text-muted-foreground mt-1.5 text-center leading-tight">
|
|
757
|
-
Build feature-by-feature using the <strong>Feature Specs</strong> list →
|
|
758
|
-
</p>
|
|
759
|
-
</div>
|
|
760
|
-
)}
|
|
761
|
-
</div>
|
|
762
|
-
</div>
|
|
763
|
-
)}
|
|
764
|
-
</div>
|
|
765
|
-
</section>
|
|
766
|
-
|
|
767
|
-
{/* Phase 4: Specs & Implementation */}
|
|
733
|
+
{/* Phase 3: Feature Specs */}
|
|
768
734
|
<section className="bg-card border border-border rounded-xl p-5 shadow-sm col-span-1 md:col-span-2 lg:col-span-1 h-full flex flex-col transition-all hover:shadow-md hover:border-border/80">
|
|
769
735
|
<div className="flex items-center justify-between mb-5 pb-4 border-b border-border/50">
|
|
770
736
|
<div className="flex items-center gap-3">
|
|
771
737
|
<div className="p-2 bg-sidebar-primary/10 text-sidebar-primary rounded-lg">
|
|
772
738
|
<Code size={20} />
|
|
773
739
|
</div>
|
|
774
|
-
<h2 className="text-lg font-semibold">
|
|
740
|
+
<h2 className="text-lg font-semibold">3. Feature Specs</h2>
|
|
775
741
|
</div>
|
|
776
742
|
<button className="text-xs bg-secondary hover:bg-secondary/80 border border-border px-3 py-2 rounded-md flex items-center gap-1.5 font-medium transition cursor-pointer"
|
|
777
743
|
onClick={() => setCreatingSpec(true)}>
|
|
@@ -780,6 +746,24 @@ function App() {
|
|
|
780
746
|
</div>
|
|
781
747
|
|
|
782
748
|
<div className="space-y-3 flex-1 overflow-y-auto max-h-[300px] pr-1">
|
|
749
|
+
{/* Auto-generate from Roadmap after Design Export */}
|
|
750
|
+
{(state?.product?.roadmap?.items?.filter(item =>
|
|
751
|
+
!item.completed &&
|
|
752
|
+
!state?.product?.roadmap?.isBoilerplate
|
|
753
|
+
) ?? []).length > 0 && !state?.specs?.length && (
|
|
754
|
+
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 p-4 rounded-lg mb-3">
|
|
755
|
+
<p className="text-sm text-blue-700 dark:text-blue-300 mb-2">
|
|
756
|
+
Ready to generate specs from roadmap items?
|
|
757
|
+
</p>
|
|
758
|
+
<PromptButton
|
|
759
|
+
label="Generate All Specs"
|
|
760
|
+
prompt={`Antigravity, generate feature specs from the roadmap items. For each item in 'agent-os/product/roadmap.md' that is not yet completed, create a spec folder in 'agent-os/specs/[feature-name]/' with spec.md and tasks.md files. Context: 1. Read 'product-plan/product-overview.md' for data model/flows. 2. Read 'design-system/design-tokens.md' and 'design-system/app-shell.md' for UI/UX constraints. 3. Use the shape-spec command: Read 'agent-os/commands/shape-spec/shape-spec.md'.`}
|
|
761
|
+
onClick={copyToClipboard}
|
|
762
|
+
small
|
|
763
|
+
primary
|
|
764
|
+
/>
|
|
765
|
+
</div>
|
|
766
|
+
)}
|
|
783
767
|
{state?.specs?.map(spec => (
|
|
784
768
|
<div key={spec.name} className="bg-secondary/20 p-4 rounded-lg border border-border/50 hover:border-border transition-colors group relative">
|
|
785
769
|
<button
|
|
@@ -867,6 +851,132 @@ function App() {
|
|
|
867
851
|
</div>
|
|
868
852
|
</section>
|
|
869
853
|
|
|
854
|
+
{/* Phase 4: Implementation */}
|
|
855
|
+
<section className="bg-card border border-border rounded-xl p-5 shadow-sm h-full flex flex-col transition-all hover:shadow-md hover:border-border/80">
|
|
856
|
+
<div className="flex items-center gap-3 mb-5 pb-4 border-b border-border/50">
|
|
857
|
+
<div className="p-2 bg-sidebar-primary/10 text-sidebar-primary rounded-lg">
|
|
858
|
+
<Package size={20} />
|
|
859
|
+
</div>
|
|
860
|
+
<h2 className="text-lg font-semibold">4. Implementation</h2>
|
|
861
|
+
</div>
|
|
862
|
+
|
|
863
|
+
<div className="space-y-3 flex-1">
|
|
864
|
+
{/* Scaffold Status */}
|
|
865
|
+
<div className="flex items-center justify-between p-3 bg-secondary/30 rounded-lg border border-border/50">
|
|
866
|
+
<span className="text-foreground font-medium text-sm flex items-center gap-2"><Layout size={16} className="text-muted-foreground" /> Scaffold</span>
|
|
867
|
+
{state?.implementation?.scaffolded ? (
|
|
868
|
+
<span className="text-emerald-600 bg-emerald-50 dark:bg-emerald-900/20 px-2 py-0.5 rounded text-xs font-medium border border-emerald-200 dark:border-emerald-800">Done</span>
|
|
869
|
+
) : (
|
|
870
|
+
<span className="text-muted-foreground bg-secondary px-2 py-0.5 rounded text-xs font-medium border border-border">Pending</span>
|
|
871
|
+
)}
|
|
872
|
+
</div>
|
|
873
|
+
|
|
874
|
+
{/* Git Status */}
|
|
875
|
+
{state?.implementation?.git?.initialized && (
|
|
876
|
+
<div className="flex items-center justify-between p-3 bg-secondary/30 rounded-lg border border-border/50">
|
|
877
|
+
<span className="text-foreground font-medium text-sm flex items-center gap-2">
|
|
878
|
+
<svg className="w-4 h-4 text-muted-foreground" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" /></svg>
|
|
879
|
+
{state.implementation.git.branch || 'main'}
|
|
880
|
+
</span>
|
|
881
|
+
<div className="flex items-center gap-2">
|
|
882
|
+
{state.implementation.git.uncommitted > 0 && (
|
|
883
|
+
<span className="text-amber-600 bg-amber-50 dark:bg-amber-900/20 px-2 py-0.5 rounded text-xs font-medium border border-amber-200 dark:border-amber-800">
|
|
884
|
+
{state.implementation.git.uncommitted} uncommitted
|
|
885
|
+
</span>
|
|
886
|
+
)}
|
|
887
|
+
{state.implementation.git.uncommitted === 0 && (
|
|
888
|
+
<span className="text-emerald-600 bg-emerald-50 dark:bg-emerald-900/20 px-2 py-0.5 rounded text-xs font-medium border border-emerald-200 dark:border-emerald-800">Clean</span>
|
|
889
|
+
)}
|
|
890
|
+
</div>
|
|
891
|
+
</div>
|
|
892
|
+
)}
|
|
893
|
+
|
|
894
|
+
{/* Tests & Coverage */}
|
|
895
|
+
{state?.implementation?.scaffolded && (
|
|
896
|
+
<div className="flex items-center justify-between p-3 bg-secondary/30 rounded-lg border border-border/50">
|
|
897
|
+
<span className="text-foreground font-medium text-sm flex items-center gap-2">
|
|
898
|
+
<CheckSquare size={16} className="text-muted-foreground" /> Tests
|
|
899
|
+
</span>
|
|
900
|
+
<div className="flex items-center gap-2">
|
|
901
|
+
{state?.implementation?.tests?.hasTests ? (
|
|
902
|
+
<span className="text-emerald-600 bg-emerald-50 dark:bg-emerald-900/20 px-2 py-0.5 rounded text-xs font-medium border border-emerald-200 dark:border-emerald-800">
|
|
903
|
+
{state.implementation.tests.count} files
|
|
904
|
+
</span>
|
|
905
|
+
) : (
|
|
906
|
+
<span className="text-muted-foreground bg-secondary px-2 py-0.5 rounded text-xs font-medium border border-border">No tests</span>
|
|
907
|
+
)}
|
|
908
|
+
{state?.implementation?.coverage !== null && state?.implementation?.coverage !== undefined && (
|
|
909
|
+
<span className={`px-2 py-0.5 rounded text-xs font-medium border ${state.implementation.coverage >= 80
|
|
910
|
+
? 'text-emerald-600 bg-emerald-50 dark:bg-emerald-900/20 border-emerald-200 dark:border-emerald-800'
|
|
911
|
+
: state.implementation.coverage >= 50
|
|
912
|
+
? 'text-amber-600 bg-amber-50 dark:bg-amber-900/20 border-amber-200 dark:border-amber-800'
|
|
913
|
+
: 'text-red-600 bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800'
|
|
914
|
+
}`}>
|
|
915
|
+
{state.implementation.coverage}% coverage
|
|
916
|
+
</span>
|
|
917
|
+
)}
|
|
918
|
+
</div>
|
|
919
|
+
</div>
|
|
920
|
+
)}
|
|
921
|
+
|
|
922
|
+
{/* Spec Progress */}
|
|
923
|
+
{state?.implementation?.specs && state.implementation.specs.total > 0 && (
|
|
924
|
+
<div className="flex items-center justify-between p-3 bg-secondary/30 rounded-lg border border-border/50">
|
|
925
|
+
<span className="text-foreground font-medium text-sm flex items-center gap-2">
|
|
926
|
+
<FileText size={16} className="text-muted-foreground" /> Specs
|
|
927
|
+
</span>
|
|
928
|
+
<span className={`px-2 py-0.5 rounded text-xs font-medium border ${state.implementation.specs.completed === state.implementation.specs.total
|
|
929
|
+
? 'text-emerald-600 bg-emerald-50 dark:bg-emerald-900/20 border-emerald-200 dark:border-emerald-800'
|
|
930
|
+
: 'text-blue-600 bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800'
|
|
931
|
+
}`}>
|
|
932
|
+
{state.implementation.specs.completed}/{state.implementation.specs.total} complete
|
|
933
|
+
</span>
|
|
934
|
+
</div>
|
|
935
|
+
)}
|
|
936
|
+
</div>
|
|
937
|
+
|
|
938
|
+
<div className="mt-6">
|
|
939
|
+
{!state?.implementation?.scaffolded ? (
|
|
940
|
+
<PromptButton
|
|
941
|
+
label="Scaffold App"
|
|
942
|
+
prompt="Antigravity, scaffold the implementation. Read 'agent-os/commands/scaffold-implementation/scaffold-implementation.md'."
|
|
943
|
+
onClick={copyToClipboard}
|
|
944
|
+
/>
|
|
945
|
+
) : (
|
|
946
|
+
<div className="p-3 bg-secondary/30 rounded-lg border border-border/50 text-sm text-muted-foreground text-center italic mb-4">
|
|
947
|
+
App scaffolded. Ready for implementation.
|
|
948
|
+
</div>
|
|
949
|
+
)}
|
|
950
|
+
|
|
951
|
+
{state?.implementation?.scaffolded && state?.design?.exportPrompts?.oneShot && (
|
|
952
|
+
<div className="pt-3 border-t border-border/50">
|
|
953
|
+
<p className="text-xs text-muted-foreground mb-2 font-medium">Implementation Options:</p>
|
|
954
|
+
<div className="space-y-2">
|
|
955
|
+
<PromptButton
|
|
956
|
+
label="Option A: One-Shot (All)"
|
|
957
|
+
prompt={`Antigravity, implement the app. Read 'product-plan/prompts/one-shot-prompt.md'. If Context7 is configured, use it to verify library documentation.`}
|
|
958
|
+
onClick={copyToClipboard}
|
|
959
|
+
small
|
|
960
|
+
primary
|
|
961
|
+
/>
|
|
962
|
+
{state?.design?.exportPrompts?.section && (
|
|
963
|
+
<div className="relative">
|
|
964
|
+
<PromptButton
|
|
965
|
+
label="Option B: Incremental (Section)"
|
|
966
|
+
prompt={`Antigravity, implement a section. Read 'product-plan/prompts/section-prompt.md'. Use Context7 for latest docs if available.`}
|
|
967
|
+
onClick={copyToClipboard}
|
|
968
|
+
small
|
|
969
|
+
/>
|
|
970
|
+
<p className="text-[10px] text-muted-foreground mt-1.5 text-center leading-tight">
|
|
971
|
+
Build by spec from <strong>Feature Specs</strong> ←
|
|
972
|
+
</p>
|
|
973
|
+
</div>
|
|
974
|
+
)}
|
|
975
|
+
</div>
|
|
976
|
+
</div>
|
|
977
|
+
)}
|
|
978
|
+
</div>
|
|
979
|
+
</section>
|
|
870
980
|
|
|
871
981
|
</main>
|
|
872
982
|
</div>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Hook: AI Slop Guard
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
Warn when code or design patterns that indicate generic AI-generated output are detected.
|
|
5
|
+
|
|
6
|
+
## Trigger
|
|
7
|
+
- PreToolUse: Before writing to CSS/TSX/HTML files
|
|
8
|
+
- PreCommit: Before committing changes
|
|
9
|
+
|
|
10
|
+
## Patterns to Watch For
|
|
11
|
+
|
|
12
|
+
### Typography Slop
|
|
13
|
+
- ⚠️ `font-family: 'Inter'` — Generic AI default font
|
|
14
|
+
- ⚠️ `font-family: 'Roboto'` — Overused AI choice
|
|
15
|
+
- ⚠️ `font-family: 'Open Sans'` — Too common
|
|
16
|
+
- ⚠️ `font-family: 'Poppins'` — AI favorite
|
|
17
|
+
|
|
18
|
+
### Color Slop
|
|
19
|
+
- ⚠️ `#808080` or `gray` — Dead gray
|
|
20
|
+
- ⚠️ `#cccccc`, `#dddddd` — Lifeless neutral
|
|
21
|
+
- ⚠️ `#f5f5f5` — The AI background color
|
|
22
|
+
- ⚠️ Pure `#000000` or `#ffffff` — No warmth
|
|
23
|
+
- ⚠️ `#3b82f6` or `#2563eb` — Everyone's blue
|
|
24
|
+
|
|
25
|
+
### Layout Slop
|
|
26
|
+
- ⚠️ Cards in cards — Nested containers
|
|
27
|
+
- ⚠️ Hero with centered h1 + subtitle + CTA — The AI template
|
|
28
|
+
- ⚠️ 3-column feature grid — Overused pattern
|
|
29
|
+
- ⚠️ Centered everything — Lazy alignment
|
|
30
|
+
|
|
31
|
+
### Effect Slop
|
|
32
|
+
- ⚠️ `backdrop-filter: blur(10px)` — Glassmorphism default
|
|
33
|
+
- ⚠️ `border-radius: 12px` — The AI corner radius
|
|
34
|
+
- ⚠️ `box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1)` — Tailwind default
|
|
35
|
+
|
|
36
|
+
### Animation Slop
|
|
37
|
+
- ⚠️ `transition: all` — Lazy animation
|
|
38
|
+
- ⚠️ `animate-bounce` — Generic micro-interaction
|
|
39
|
+
- ⚠️ `animate-pulse` — Loading skeleton overuse
|
|
40
|
+
|
|
41
|
+
### Copy Slop
|
|
42
|
+
- ⚠️ "Streamline your workflow" — Generic SaaS copy
|
|
43
|
+
- ⚠️ "Unlock the power of" — AI cliché
|
|
44
|
+
- ⚠️ "Seamlessly integrate" — Meaningless buzzword
|
|
45
|
+
- ⚠️ "Best-in-class" — Tells nothing
|
|
46
|
+
|
|
47
|
+
## Response When Detected
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
⚠️ AI Slop Guard Warning
|
|
51
|
+
|
|
52
|
+
Detected potentially generic AI patterns:
|
|
53
|
+
- [Pattern name]: [File:Line]
|
|
54
|
+
|
|
55
|
+
Consider:
|
|
56
|
+
1. Running `/audit` to get specific recommendations
|
|
57
|
+
2. Consulting the frontend-design skill for alternatives
|
|
58
|
+
3. Using `/bolder` if design is too safe
|
|
59
|
+
|
|
60
|
+
Continue anyway? [y/N]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Configuration
|
|
64
|
+
Users can customize in `.gemini/settings.json`:
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"hooks": {
|
|
68
|
+
"ai-slop-guard": {
|
|
69
|
+
"enabled": true,
|
|
70
|
+
"strictness": "medium", // low, medium, high
|
|
71
|
+
"ignore": ["Inter"] // Patterns to ignore
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Notes
|
|
78
|
+
- This is a warning, not a blocker
|
|
79
|
+
- Some patterns are acceptable in context
|
|
80
|
+
- The goal is awareness, not restriction
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Hook: On Design Complete
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
Automatically run quality checks when a design phase is completed.
|
|
5
|
+
|
|
6
|
+
## Trigger
|
|
7
|
+
After completing:
|
|
8
|
+
- `/design-tokens`
|
|
9
|
+
- `/design-shell`
|
|
10
|
+
- `/design-screen`
|
|
11
|
+
|
|
12
|
+
## Actions
|
|
13
|
+
|
|
14
|
+
### 1. Run Auto-Audit
|
|
15
|
+
Execute a mini-audit focusing on the just-completed work:
|
|
16
|
+
|
|
17
|
+
**After `/design-tokens`:**
|
|
18
|
+
- Check for AI slop fonts
|
|
19
|
+
- Verify color hierarchy exists
|
|
20
|
+
- Ensure contrast ratios meet WCAG AA
|
|
21
|
+
- Validate color format consistency
|
|
22
|
+
|
|
23
|
+
**After `/design-shell`:**
|
|
24
|
+
- Check for AI slop layout patterns
|
|
25
|
+
- Verify touch targets (44x44 minimum)
|
|
26
|
+
- Check spacing consistency
|
|
27
|
+
- Validate responsive breakpoints
|
|
28
|
+
|
|
29
|
+
**After `/design-screen`:**
|
|
30
|
+
- Full component audit
|
|
31
|
+
- Accessibility check
|
|
32
|
+
- Performance impact assessment
|
|
33
|
+
|
|
34
|
+
### 2. Generate Report
|
|
35
|
+
Save audit results to `design-system/QA/[phase]-audit.md`
|
|
36
|
+
|
|
37
|
+
### 3. Suggest Next Steps
|
|
38
|
+
Based on audit results:
|
|
39
|
+
- If issues found: Suggest specific fix commands
|
|
40
|
+
- If clean: Suggest next phase
|
|
41
|
+
|
|
42
|
+
## Output Format
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
✅ Design phase completed!
|
|
46
|
+
|
|
47
|
+
Auto-audit results:
|
|
48
|
+
├── Accessibility: ✓ Pass
|
|
49
|
+
├── Typography: ⚠️ 1 warning (consider font alternatives)
|
|
50
|
+
├── Colors: ✓ Pass
|
|
51
|
+
└── Layout: ✓ Pass
|
|
52
|
+
|
|
53
|
+
Suggestions:
|
|
54
|
+
- Run `/critique` for UX evaluation
|
|
55
|
+
- Continue to `/design-shell` when ready
|
|
56
|
+
|
|
57
|
+
Full report: design-system/QA/tokens-audit.md
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Notes
|
|
61
|
+
- This hook runs automatically, no user action needed
|
|
62
|
+
- Reports accumulate, so you can see progress over time
|
|
63
|
+
- Hook can be disabled in settings if desired
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import js from '@eslint/js'
|
|
2
|
+
import globals from 'globals'
|
|
3
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
4
|
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
5
|
+
import tseslint from 'typescript-eslint'
|
|
6
|
+
|
|
7
|
+
export default tseslint.config(
|
|
8
|
+
{ ignores: ['dist', 'node_modules', 'coverage', '.next', 'build'] },
|
|
9
|
+
{
|
|
10
|
+
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
|
11
|
+
files: ['**/*.{ts,tsx}'],
|
|
12
|
+
languageOptions: {
|
|
13
|
+
ecmaVersion: 2020,
|
|
14
|
+
globals: globals.browser,
|
|
15
|
+
},
|
|
16
|
+
plugins: {
|
|
17
|
+
'react-hooks': reactHooks,
|
|
18
|
+
'react-refresh': reactRefresh,
|
|
19
|
+
},
|
|
20
|
+
rules: {
|
|
21
|
+
...reactHooks.configs.recommended.rules,
|
|
22
|
+
'react-refresh/only-export-components': [
|
|
23
|
+
'warn',
|
|
24
|
+
{ allowConstantExport: true },
|
|
25
|
+
],
|
|
26
|
+
// Strict TypeScript rules
|
|
27
|
+
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
|
28
|
+
'@typescript-eslint/no-explicit-any': 'warn',
|
|
29
|
+
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
30
|
+
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
31
|
+
// Best practices
|
|
32
|
+
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
|
33
|
+
'prefer-const': 'error',
|
|
34
|
+
'no-var': 'error',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
)
|
|
@@ -19,9 +19,32 @@
|
|
|
19
19
|
"start:control": "cd control-center/frontend && npm run dev -- --port 5401",
|
|
20
20
|
"start:app": "cd app && npm run dev -- --port 5402",
|
|
21
21
|
"build": "npm run build --workspaces",
|
|
22
|
-
"test": "
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:watch": "vitest",
|
|
24
|
+
"test:coverage": "vitest run --coverage",
|
|
25
|
+
"lint": "eslint . --ext .ts,.tsx,.js,.jsx",
|
|
26
|
+
"lint:fix": "eslint . --ext .ts,.tsx,.js,.jsx --fix",
|
|
27
|
+
"format": "prettier --write .",
|
|
28
|
+
"format:check": "prettier --check .",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"prepare": "husky"
|
|
23
31
|
},
|
|
24
32
|
"devDependencies": {
|
|
25
|
-
"
|
|
33
|
+
"@commitlint/cli": "^19.0.0",
|
|
34
|
+
"@commitlint/config-conventional": "^19.0.0",
|
|
35
|
+
"@eslint/js": "^9.0.0",
|
|
36
|
+
"@types/node": "^20.11.0",
|
|
37
|
+
"@vitest/coverage-v8": "^2.0.0",
|
|
38
|
+
"concurrently": "^8.2.2",
|
|
39
|
+
"eslint": "^9.0.0",
|
|
40
|
+
"eslint-plugin-react-hooks": "^5.0.0",
|
|
41
|
+
"eslint-plugin-react-refresh": "^0.4.0",
|
|
42
|
+
"globals": "^15.0.0",
|
|
43
|
+
"husky": "^9.0.0",
|
|
44
|
+
"lint-staged": "^15.0.0",
|
|
45
|
+
"prettier": "^3.2.0",
|
|
46
|
+
"prettier-plugin-tailwindcss": "^0.6.0",
|
|
47
|
+
"typescript-eslint": "^8.0.0",
|
|
48
|
+
"vitest": "^2.0.0"
|
|
26
49
|
}
|
|
27
50
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
|
|
3
|
+
describe('Example Test Suite', () => {
|
|
4
|
+
it('should pass a basic test', () => {
|
|
5
|
+
expect(1 + 1).toBe(2)
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
it('should handle strings', () => {
|
|
9
|
+
expect('hello world').toContain('world')
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('should handle arrays', () => {
|
|
13
|
+
const items = [1, 2, 3]
|
|
14
|
+
expect(items).toHaveLength(3)
|
|
15
|
+
expect(items).toContain(2)
|
|
16
|
+
})
|
|
17
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import '@testing-library/jest-dom/vitest'
|
|
2
|
+
|
|
3
|
+
// Global test setup
|
|
4
|
+
beforeAll(() => {
|
|
5
|
+
// Setup code that runs before all tests
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
// Cleanup after each test
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
afterAll(() => {
|
|
13
|
+
// Cleanup code that runs after all tests
|
|
14
|
+
})
|