codebakers 3.3.0 → 4.3.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.
- package/dist/index.js +1417 -1442
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,9 +6,10 @@ import {
|
|
|
6
6
|
} from "./chunk-HOWR3YTF.js";
|
|
7
7
|
|
|
8
8
|
// src/index.ts
|
|
9
|
-
import * as
|
|
9
|
+
import * as p7 from "@clack/prompts";
|
|
10
10
|
import ora3 from "ora";
|
|
11
|
-
import
|
|
11
|
+
import fs17 from "fs-extra";
|
|
12
|
+
import path18 from "path";
|
|
12
13
|
|
|
13
14
|
// src/core/config.ts
|
|
14
15
|
import Conf from "conf";
|
|
@@ -749,7 +750,7 @@ import Anthropic from "@anthropic-ai/sdk";
|
|
|
749
750
|
var CORE_PATTERNS = `
|
|
750
751
|
## RULES (You MUST follow these)
|
|
751
752
|
|
|
752
|
-
### BANNED (never do these)
|
|
753
|
+
### BANNED (never do these) - 15 Rules
|
|
753
754
|
- \u274C \`any\` type \u2192 use proper TypeScript types
|
|
754
755
|
- \u274C \`@ts-ignore\` or \`@ts-nocheck\` \u2192 fix the actual type error
|
|
755
756
|
- \u274C \`console.log\` \u2192 remove or use proper logger
|
|
@@ -760,226 +761,468 @@ var CORE_PATTERNS = `
|
|
|
760
761
|
- \u274C \`throw new Error('Not implemented')\` \u2192 implement it
|
|
761
762
|
- \u274C \`debugger\` statements \u2192 remove before commit
|
|
762
763
|
- \u274C hardcoded secrets/keys \u2192 use environment variables
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
- \
|
|
766
|
-
- \
|
|
767
|
-
- \
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
- \u2705
|
|
771
|
-
- \u2705
|
|
772
|
-
- \u2705
|
|
773
|
-
- \u2705
|
|
774
|
-
- \u2705
|
|
775
|
-
- \u2705
|
|
776
|
-
- \u2705
|
|
777
|
-
- \u2705
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
useEffect(() => {
|
|
846
|
-
async function fetchData() {
|
|
847
|
-
try {
|
|
848
|
-
const response = await fetch('/api/data');
|
|
849
|
-
if (!response.ok) throw new Error('Failed to fetch');
|
|
850
|
-
const result = await response.json();
|
|
851
|
-
setData(result);
|
|
852
|
-
} catch (err) {
|
|
853
|
-
setError(err instanceof Error ? err.message : 'Failed to load');
|
|
854
|
-
} finally {
|
|
855
|
-
setIsLoading(false);
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
fetchData();
|
|
859
|
-
}, []);
|
|
860
|
-
|
|
861
|
-
if (isLoading) return <Skeleton />;
|
|
862
|
-
if (error) return <ErrorState message={error} onRetry={() => window.location.reload()} />;
|
|
863
|
-
if (!data) return <EmptyState message="No data found" />;
|
|
864
|
-
|
|
865
|
-
return <DataDisplay data={data} />;
|
|
866
|
-
}
|
|
867
|
-
\`\`\`
|
|
868
|
-
|
|
869
|
-
#### API Route
|
|
870
|
-
\`\`\`tsx
|
|
871
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
872
|
-
import { z } from 'zod';
|
|
873
|
-
import { createClient } from '@/lib/supabase/server';
|
|
874
|
-
|
|
875
|
-
const requestSchema = z.object({
|
|
876
|
-
name: z.string().min(1),
|
|
877
|
-
email: z.string().email(),
|
|
878
|
-
});
|
|
879
|
-
|
|
880
|
-
export async function POST(request: NextRequest) {
|
|
881
|
-
try {
|
|
882
|
-
// 1. Auth check
|
|
883
|
-
const supabase = createClient();
|
|
884
|
-
const { data: { user }, error: authError } = await supabase.auth.getUser();
|
|
885
|
-
|
|
886
|
-
if (authError || !user) {
|
|
887
|
-
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
// 2. Validate input
|
|
891
|
-
const body = await request.json();
|
|
892
|
-
const result = requestSchema.safeParse(body);
|
|
893
|
-
|
|
894
|
-
if (!result.success) {
|
|
895
|
-
return NextResponse.json(
|
|
896
|
-
{ error: 'Validation failed', details: result.error.flatten() },
|
|
897
|
-
{ status: 400 }
|
|
898
|
-
);
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
// 3. Business logic
|
|
902
|
-
const data = result.data;
|
|
903
|
-
// ... do something
|
|
904
|
-
|
|
905
|
-
// 4. Return success
|
|
906
|
-
return NextResponse.json({ data }, { status: 201 });
|
|
907
|
-
|
|
908
|
-
} catch (error) {
|
|
909
|
-
console.error('[API] Error:', error);
|
|
910
|
-
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
\`\`\`
|
|
764
|
+
- \u274C \`innerHTML\` or \`dangerouslySetInnerHTML\` without sanitization
|
|
765
|
+
- \u274C \`document.write\` \u2192 use React/DOM methods
|
|
766
|
+
- \u274C synchronous \`localStorage\` in render \u2192 use useEffect
|
|
767
|
+
- \u274C \`var\` keyword \u2192 use const/let
|
|
768
|
+
- \u274C nested ternaries \u2192 use if/else or early returns
|
|
769
|
+
|
|
770
|
+
### REACT PATTERNS - 12 Rules
|
|
771
|
+
- \u2705 Components must have TypeScript props interface
|
|
772
|
+
- \u2705 useState must have explicit type: \`useState<Type>()\`
|
|
773
|
+
- \u2705 useEffect must have dependency array
|
|
774
|
+
- \u2705 useEffect cleanup for subscriptions/timers
|
|
775
|
+
- \u2705 useMemo/useCallback for expensive computations
|
|
776
|
+
- \u2705 Keys in lists must be stable IDs (not index)
|
|
777
|
+
- \u2705 Error boundaries for component trees
|
|
778
|
+
- \u2705 Suspense boundaries for lazy components
|
|
779
|
+
- \u2705 forwardRef for components accepting refs
|
|
780
|
+
- \u2705 displayName for HOCs and forwardRef
|
|
781
|
+
- \u2705 Prop drilling > 2 levels \u2192 use Context
|
|
782
|
+
- \u2705 Custom hooks for reusable stateful logic
|
|
783
|
+
|
|
784
|
+
### FORM PATTERNS - 8 Rules
|
|
785
|
+
- \u2705 Forms must have Zod validation schema
|
|
786
|
+
- \u2705 Forms must show field-level error messages
|
|
787
|
+
- \u2705 Forms must disable submit while submitting
|
|
788
|
+
- \u2705 Forms must show loading state on submit button
|
|
789
|
+
- \u2705 Forms must handle server errors gracefully
|
|
790
|
+
- \u2705 Forms must prevent double-submission
|
|
791
|
+
- \u2705 File inputs must validate file type/size
|
|
792
|
+
- \u2705 Forms must have accessible labels
|
|
793
|
+
|
|
794
|
+
### ASYNC/DATA PATTERNS - 10 Rules
|
|
795
|
+
- \u2705 Async operations must have try/catch
|
|
796
|
+
- \u2705 Async operations must have loading state
|
|
797
|
+
- \u2705 Lists must have loading state (skeleton/spinner)
|
|
798
|
+
- \u2705 Lists must have empty state
|
|
799
|
+
- \u2705 Lists must have error state with retry
|
|
800
|
+
- \u2705 Fetch must check response.ok
|
|
801
|
+
- \u2705 Fetch must have timeout handling
|
|
802
|
+
- \u2705 Pagination for lists > 20 items
|
|
803
|
+
- \u2705 Optimistic updates for better UX
|
|
804
|
+
- \u2705 Debounce for search/filter inputs
|
|
805
|
+
|
|
806
|
+
### API ROUTE PATTERNS - 8 Rules
|
|
807
|
+
- \u2705 API routes must check authentication
|
|
808
|
+
- \u2705 API routes must validate input with Zod
|
|
809
|
+
- \u2705 API routes must return proper HTTP status codes
|
|
810
|
+
- \u2705 API routes must log errors
|
|
811
|
+
- \u2705 API routes must sanitize user input
|
|
812
|
+
- \u2705 API routes must rate limit sensitive endpoints
|
|
813
|
+
- \u2705 API routes must validate content-type
|
|
814
|
+
- \u2705 API routes must handle CORS properly
|
|
815
|
+
|
|
816
|
+
### SECURITY PATTERNS - 8 Rules
|
|
817
|
+
- \u2705 Sanitize all user input before display
|
|
818
|
+
- \u2705 Use parameterized queries (no SQL injection)
|
|
819
|
+
- \u2705 CSRF tokens for state-changing operations
|
|
820
|
+
- \u2705 Secure cookies (httpOnly, secure, sameSite)
|
|
821
|
+
- \u2705 Validate redirect URLs
|
|
822
|
+
- \u2705 Use Content-Security-Policy headers
|
|
823
|
+
- \u2705 Hash passwords with bcrypt/argon2
|
|
824
|
+
- \u2705 Rate limit authentication endpoints
|
|
825
|
+
|
|
826
|
+
### ACCESSIBILITY PATTERNS - 6 Rules
|
|
827
|
+
- \u2705 Images must have alt text
|
|
828
|
+
- \u2705 Interactive elements must be keyboard accessible
|
|
829
|
+
- \u2705 Form inputs must have labels
|
|
830
|
+
- \u2705 Color contrast must meet WCAG AA
|
|
831
|
+
- \u2705 Focus indicators must be visible
|
|
832
|
+
- \u2705 ARIA labels for icon-only buttons
|
|
833
|
+
|
|
834
|
+
### TYPESCRIPT PATTERNS - 5 Rules
|
|
835
|
+
- \u2705 All exports must have explicit types
|
|
836
|
+
- \u2705 Prefer interfaces over type aliases for objects
|
|
837
|
+
- \u2705 Use discriminated unions for state
|
|
838
|
+
- \u2705 Avoid type assertions (as) where possible
|
|
839
|
+
- \u2705 Use strict mode
|
|
840
|
+
|
|
841
|
+
### TESTING PATTERNS - 4 Rules
|
|
842
|
+
- \u2705 Test files must be co-located with source
|
|
843
|
+
- \u2705 Test user behavior, not implementation
|
|
844
|
+
- \u2705 Mock external dependencies
|
|
845
|
+
- \u2705 Use data-testid for test selectors
|
|
914
846
|
`;
|
|
915
847
|
var BANNED_PATTERNS = [
|
|
848
|
+
// === BANNED PATTERNS (15) ===
|
|
916
849
|
{
|
|
917
850
|
id: "no-any",
|
|
918
851
|
regex: /:\s*any\b|<any>|as\s+any/g,
|
|
919
852
|
rule: "No `any` type",
|
|
920
853
|
fix: "Use proper TypeScript types",
|
|
921
|
-
severity: "error"
|
|
854
|
+
severity: "error",
|
|
855
|
+
category: "banned"
|
|
922
856
|
},
|
|
923
857
|
{
|
|
924
858
|
id: "no-ts-ignore",
|
|
925
859
|
regex: /@ts-ignore|@ts-nocheck/g,
|
|
926
860
|
rule: "No @ts-ignore",
|
|
927
861
|
fix: "Fix the actual type error",
|
|
928
|
-
severity: "error"
|
|
862
|
+
severity: "error",
|
|
863
|
+
category: "banned"
|
|
929
864
|
},
|
|
930
865
|
{
|
|
931
866
|
id: "no-console",
|
|
932
867
|
regex: /console\.(log|debug|info)\(/g,
|
|
933
868
|
rule: "No console.log",
|
|
934
869
|
fix: "Remove or use proper logger",
|
|
935
|
-
severity: "
|
|
870
|
+
severity: "warning",
|
|
871
|
+
category: "banned"
|
|
936
872
|
},
|
|
937
873
|
{
|
|
938
874
|
id: "no-todo",
|
|
939
875
|
regex: /\/\/\s*(TODO|FIXME|HACK|XXX):/gi,
|
|
940
876
|
rule: "No TODO/FIXME",
|
|
941
877
|
fix: "Complete the implementation now",
|
|
942
|
-
severity: "error"
|
|
878
|
+
severity: "error",
|
|
879
|
+
category: "banned"
|
|
943
880
|
},
|
|
944
881
|
{
|
|
945
882
|
id: "no-eval",
|
|
946
883
|
regex: /\beval\s*\(/g,
|
|
947
884
|
rule: "No eval()",
|
|
948
885
|
fix: "Use safer alternatives",
|
|
949
|
-
severity: "error"
|
|
886
|
+
severity: "error",
|
|
887
|
+
category: "security"
|
|
950
888
|
},
|
|
951
889
|
{
|
|
952
890
|
id: "no-empty-handler",
|
|
953
891
|
regex: /on\w+\s*=\s*\{\s*\(\)\s*=>\s*\{\s*\}\s*\}/g,
|
|
954
892
|
rule: "No empty event handlers",
|
|
955
893
|
fix: "Implement the handler",
|
|
956
|
-
severity: "error"
|
|
894
|
+
severity: "error",
|
|
895
|
+
category: "banned"
|
|
957
896
|
},
|
|
958
897
|
{
|
|
959
898
|
id: "no-debugger",
|
|
960
899
|
regex: /\bdebugger\b/g,
|
|
961
900
|
rule: "No debugger statements",
|
|
962
901
|
fix: "Remove debugger",
|
|
963
|
-
severity: "error"
|
|
902
|
+
severity: "error",
|
|
903
|
+
category: "banned"
|
|
964
904
|
},
|
|
965
905
|
{
|
|
966
906
|
id: "no-placeholder",
|
|
967
907
|
regex: /\/\/\s*\.\.\.|\/\*\s*\.\.\.\s*\*\//g,
|
|
968
908
|
rule: "No placeholder comments",
|
|
969
909
|
fix: "Write actual code",
|
|
970
|
-
severity: "error"
|
|
910
|
+
severity: "error",
|
|
911
|
+
category: "banned"
|
|
971
912
|
},
|
|
972
913
|
{
|
|
973
914
|
id: "no-not-implemented",
|
|
974
915
|
regex: /throw\s+new\s+Error\s*\(\s*['"`]not\s+implemented/gi,
|
|
975
916
|
rule: 'No "not implemented" errors',
|
|
976
917
|
fix: "Implement the function",
|
|
977
|
-
severity: "error"
|
|
918
|
+
severity: "error",
|
|
919
|
+
category: "banned"
|
|
920
|
+
},
|
|
921
|
+
{
|
|
922
|
+
id: "no-var",
|
|
923
|
+
regex: /\bvar\s+\w+/g,
|
|
924
|
+
rule: "No var keyword",
|
|
925
|
+
fix: "Use const or let",
|
|
926
|
+
severity: "error",
|
|
927
|
+
category: "banned"
|
|
928
|
+
},
|
|
929
|
+
{
|
|
930
|
+
id: "no-innerhtml",
|
|
931
|
+
regex: /\.innerHTML\s*=|dangerouslySetInnerHTML/g,
|
|
932
|
+
rule: "No innerHTML without sanitization",
|
|
933
|
+
fix: "Sanitize content or use React methods",
|
|
934
|
+
severity: "error",
|
|
935
|
+
category: "security"
|
|
936
|
+
},
|
|
937
|
+
{
|
|
938
|
+
id: "no-document-write",
|
|
939
|
+
regex: /document\.write\s*\(/g,
|
|
940
|
+
rule: "No document.write",
|
|
941
|
+
fix: "Use DOM methods or React",
|
|
942
|
+
severity: "error",
|
|
943
|
+
category: "banned"
|
|
944
|
+
},
|
|
945
|
+
{
|
|
946
|
+
id: "no-hardcoded-secret",
|
|
947
|
+
regex: /(api[_-]?key|secret|password|token)\s*[:=]\s*['"`][^'"`]{8,}/gi,
|
|
948
|
+
rule: "No hardcoded secrets",
|
|
949
|
+
fix: "Use environment variables",
|
|
950
|
+
severity: "error",
|
|
951
|
+
category: "security"
|
|
952
|
+
},
|
|
953
|
+
{
|
|
954
|
+
id: "no-nested-ternary",
|
|
955
|
+
regex: /\?\s*[^:]+\?\s*[^:]+:/g,
|
|
956
|
+
rule: "No nested ternaries",
|
|
957
|
+
fix: "Use if/else or early returns",
|
|
958
|
+
severity: "warning",
|
|
959
|
+
category: "banned"
|
|
960
|
+
},
|
|
961
|
+
{
|
|
962
|
+
id: "no-alert",
|
|
963
|
+
regex: /\balert\s*\(|\bconfirm\s*\(|\bprompt\s*\(/g,
|
|
964
|
+
rule: "No browser alerts",
|
|
965
|
+
fix: "Use proper UI components",
|
|
966
|
+
severity: "warning",
|
|
967
|
+
category: "banned"
|
|
968
|
+
},
|
|
969
|
+
// === REACT PATTERNS (12) ===
|
|
970
|
+
{
|
|
971
|
+
id: "react-no-index-key",
|
|
972
|
+
regex: /key\s*=\s*\{?\s*(index|i|idx)\s*\}?/g,
|
|
973
|
+
rule: "No array index as key",
|
|
974
|
+
fix: "Use stable unique ID",
|
|
975
|
+
severity: "warning",
|
|
976
|
+
category: "react"
|
|
977
|
+
},
|
|
978
|
+
{
|
|
979
|
+
id: "react-missing-deps",
|
|
980
|
+
regex: /useEffect\s*\(\s*\(\)\s*=>\s*\{[^}]*\}\s*\)/g,
|
|
981
|
+
rule: "useEffect missing dependency array",
|
|
982
|
+
fix: "Add dependency array []",
|
|
983
|
+
severity: "error",
|
|
984
|
+
category: "react"
|
|
985
|
+
},
|
|
986
|
+
{
|
|
987
|
+
id: "react-setstate-in-render",
|
|
988
|
+
regex: /function\s+\w+\s*\([^)]*\)\s*\{[^}]*\bset\w+\s*\([^)]+\)[^}]*return\s*\(/gs,
|
|
989
|
+
rule: "setState called during render",
|
|
990
|
+
fix: "Move to useEffect or event handler",
|
|
991
|
+
severity: "error",
|
|
992
|
+
category: "react"
|
|
993
|
+
},
|
|
994
|
+
// === SECURITY PATTERNS (8) ===
|
|
995
|
+
{
|
|
996
|
+
id: "security-sql-injection",
|
|
997
|
+
regex: /\$\{[^}]+\}.*(?:SELECT|INSERT|UPDATE|DELETE|FROM|WHERE)/gi,
|
|
998
|
+
rule: "Potential SQL injection",
|
|
999
|
+
fix: "Use parameterized queries",
|
|
1000
|
+
severity: "error",
|
|
1001
|
+
category: "security"
|
|
1002
|
+
},
|
|
1003
|
+
{
|
|
1004
|
+
id: "security-open-redirect",
|
|
1005
|
+
regex: /redirect\s*\(\s*(?:req\.|request\.)/gi,
|
|
1006
|
+
rule: "Potential open redirect",
|
|
1007
|
+
fix: "Validate redirect URL",
|
|
1008
|
+
severity: "error",
|
|
1009
|
+
category: "security"
|
|
1010
|
+
},
|
|
1011
|
+
{
|
|
1012
|
+
id: "security-exec",
|
|
1013
|
+
regex: /\bexec\s*\(|\bexecSync\s*\(|\bspawn\s*\(/g,
|
|
1014
|
+
rule: "Command execution detected",
|
|
1015
|
+
fix: "Sanitize input or avoid shell commands",
|
|
1016
|
+
severity: "warning",
|
|
1017
|
+
category: "security"
|
|
1018
|
+
},
|
|
1019
|
+
// === ACCESSIBILITY PATTERNS (6) ===
|
|
1020
|
+
{
|
|
1021
|
+
id: "a11y-img-alt",
|
|
1022
|
+
regex: /<img[^>]+(?!alt\s*=)[^>]*>/gi,
|
|
1023
|
+
rule: "Image missing alt attribute",
|
|
1024
|
+
fix: 'Add alt="description"',
|
|
1025
|
+
severity: "warning",
|
|
1026
|
+
category: "a11y"
|
|
1027
|
+
},
|
|
1028
|
+
{
|
|
1029
|
+
id: "a11y-button-type",
|
|
1030
|
+
regex: /<button(?![^>]*type\s*=)[^>]*>/gi,
|
|
1031
|
+
rule: "Button missing type attribute",
|
|
1032
|
+
fix: 'Add type="button" or type="submit"',
|
|
1033
|
+
severity: "warning",
|
|
1034
|
+
category: "a11y"
|
|
1035
|
+
},
|
|
1036
|
+
{
|
|
1037
|
+
id: "a11y-anchor-blank",
|
|
1038
|
+
regex: /target\s*=\s*['"]_blank['"](?![^>]*rel\s*=)/gi,
|
|
1039
|
+
rule: "External link missing rel attribute",
|
|
1040
|
+
fix: 'Add rel="noopener noreferrer"',
|
|
1041
|
+
severity: "warning",
|
|
1042
|
+
category: "a11y"
|
|
1043
|
+
},
|
|
1044
|
+
// === ASYNC PATTERNS (10) ===
|
|
1045
|
+
{
|
|
1046
|
+
id: "async-no-await",
|
|
1047
|
+
regex: /async\s+(?:function|\([^)]*\)\s*=>)[^{]*\{(?:(?!await)[^}])*\}/g,
|
|
1048
|
+
rule: "Async function without await",
|
|
1049
|
+
fix: "Add await or remove async",
|
|
1050
|
+
severity: "warning",
|
|
1051
|
+
category: "async"
|
|
1052
|
+
},
|
|
1053
|
+
{
|
|
1054
|
+
id: "async-promise-no-catch",
|
|
1055
|
+
regex: /\.then\s*\([^)]+\)(?!\s*\.catch)/g,
|
|
1056
|
+
rule: "Promise without catch",
|
|
1057
|
+
fix: "Add .catch() handler",
|
|
1058
|
+
severity: "warning",
|
|
1059
|
+
category: "async"
|
|
1060
|
+
},
|
|
1061
|
+
// === TYPESCRIPT PATTERNS (5) ===
|
|
1062
|
+
{
|
|
1063
|
+
id: "ts-non-null-assertion",
|
|
1064
|
+
regex: /\w+!/g,
|
|
1065
|
+
rule: "Non-null assertion used",
|
|
1066
|
+
fix: "Use optional chaining or null check",
|
|
1067
|
+
severity: "warning",
|
|
1068
|
+
category: "typescript"
|
|
1069
|
+
},
|
|
1070
|
+
{
|
|
1071
|
+
id: "ts-explicit-any-return",
|
|
1072
|
+
regex: /\):\s*any\s*\{/g,
|
|
1073
|
+
rule: "Function returns any",
|
|
1074
|
+
fix: "Add explicit return type",
|
|
1075
|
+
severity: "error",
|
|
1076
|
+
category: "typescript"
|
|
1077
|
+
},
|
|
1078
|
+
{
|
|
1079
|
+
id: "ts-object-type",
|
|
1080
|
+
regex: /:\s*object\b/g,
|
|
1081
|
+
rule: "Generic object type used",
|
|
1082
|
+
fix: "Use specific interface or Record<K,V>",
|
|
1083
|
+
severity: "warning",
|
|
1084
|
+
category: "typescript"
|
|
1085
|
+
},
|
|
1086
|
+
// === MORE REACT PATTERNS ===
|
|
1087
|
+
{
|
|
1088
|
+
id: "react-no-bind-in-render",
|
|
1089
|
+
regex: /onClick\s*=\s*\{[^}]*\.bind\s*\(/g,
|
|
1090
|
+
rule: "No .bind() in render",
|
|
1091
|
+
fix: "Use arrow function or useCallback",
|
|
1092
|
+
severity: "warning",
|
|
1093
|
+
category: "react"
|
|
1094
|
+
},
|
|
1095
|
+
{
|
|
1096
|
+
id: "react-no-arrow-in-render",
|
|
1097
|
+
regex: /onClick\s*=\s*\{\s*\(\)\s*=>\s*\w+\([^)]*\)\s*\}/g,
|
|
1098
|
+
rule: "Arrow function in render creates new function",
|
|
1099
|
+
fix: "Extract handler or use useCallback",
|
|
1100
|
+
severity: "warning",
|
|
1101
|
+
category: "react"
|
|
1102
|
+
},
|
|
1103
|
+
{
|
|
1104
|
+
id: "react-missing-error-boundary",
|
|
1105
|
+
regex: /throw\s+new\s+Error\s*\(/g,
|
|
1106
|
+
rule: "Throwing error without boundary",
|
|
1107
|
+
fix: "Wrap in Error Boundary",
|
|
1108
|
+
severity: "warning",
|
|
1109
|
+
category: "react"
|
|
1110
|
+
},
|
|
1111
|
+
{
|
|
1112
|
+
id: "react-direct-dom",
|
|
1113
|
+
regex: /document\.getElementById|document\.querySelector|document\.getElement/g,
|
|
1114
|
+
rule: "Direct DOM manipulation in React",
|
|
1115
|
+
fix: "Use refs or React state",
|
|
1116
|
+
severity: "warning",
|
|
1117
|
+
category: "react"
|
|
1118
|
+
},
|
|
1119
|
+
// === MORE SECURITY PATTERNS ===
|
|
1120
|
+
{
|
|
1121
|
+
id: "security-jwt-secret",
|
|
1122
|
+
regex: /jwt\.sign\s*\([^)]*['"`][^'"`]{8,}['"`]/gi,
|
|
1123
|
+
rule: "Hardcoded JWT secret",
|
|
1124
|
+
fix: "Use environment variable",
|
|
1125
|
+
severity: "error",
|
|
1126
|
+
category: "security"
|
|
1127
|
+
},
|
|
1128
|
+
{
|
|
1129
|
+
id: "security-md5",
|
|
1130
|
+
regex: /\bmd5\s*\(|createHash\s*\(\s*['"]md5['"]/gi,
|
|
1131
|
+
rule: "MD5 is insecure for passwords",
|
|
1132
|
+
fix: "Use bcrypt or argon2",
|
|
1133
|
+
severity: "error",
|
|
1134
|
+
category: "security"
|
|
1135
|
+
},
|
|
1136
|
+
{
|
|
1137
|
+
id: "security-sha1",
|
|
1138
|
+
regex: /createHash\s*\(\s*['"]sha1['"]/gi,
|
|
1139
|
+
rule: "SHA1 is deprecated",
|
|
1140
|
+
fix: "Use SHA256 or bcrypt",
|
|
1141
|
+
severity: "warning",
|
|
1142
|
+
category: "security"
|
|
1143
|
+
},
|
|
1144
|
+
{
|
|
1145
|
+
id: "security-cors-star",
|
|
1146
|
+
regex: /Access-Control-Allow-Origin['":\s]+\*/gi,
|
|
1147
|
+
rule: "CORS allows all origins",
|
|
1148
|
+
fix: "Specify allowed origins",
|
|
1149
|
+
severity: "warning",
|
|
1150
|
+
category: "security"
|
|
1151
|
+
},
|
|
1152
|
+
// === MORE ASYNC PATTERNS ===
|
|
1153
|
+
{
|
|
1154
|
+
id: "async-floating-promise",
|
|
1155
|
+
regex: /(?<!await\s)(?<!return\s)\w+\s*\.\s*then\s*\(/g,
|
|
1156
|
+
rule: "Floating promise (not awaited)",
|
|
1157
|
+
fix: "Add await or handle promise",
|
|
1158
|
+
severity: "warning",
|
|
1159
|
+
category: "async"
|
|
1160
|
+
},
|
|
1161
|
+
{
|
|
1162
|
+
id: "async-foreach-async",
|
|
1163
|
+
regex: /\.forEach\s*\(\s*async/g,
|
|
1164
|
+
rule: "async in forEach does not wait",
|
|
1165
|
+
fix: "Use for...of or Promise.all with map",
|
|
1166
|
+
severity: "error",
|
|
1167
|
+
category: "async"
|
|
1168
|
+
},
|
|
1169
|
+
// === MORE ACCESSIBILITY PATTERNS ===
|
|
1170
|
+
{
|
|
1171
|
+
id: "a11y-no-autofocus",
|
|
1172
|
+
regex: /autoFocus|autofocus/g,
|
|
1173
|
+
rule: "Autofocus can confuse screen readers",
|
|
1174
|
+
fix: "Remove or use carefully",
|
|
1175
|
+
severity: "warning",
|
|
1176
|
+
category: "a11y"
|
|
1177
|
+
},
|
|
1178
|
+
{
|
|
1179
|
+
id: "a11y-positive-tabindex",
|
|
1180
|
+
regex: /tabIndex\s*=\s*\{?\s*[1-9]/g,
|
|
1181
|
+
rule: "Positive tabindex disrupts focus order",
|
|
1182
|
+
fix: "Use tabIndex={0} or tabIndex={-1}",
|
|
1183
|
+
severity: "warning",
|
|
1184
|
+
category: "a11y"
|
|
1185
|
+
},
|
|
1186
|
+
{
|
|
1187
|
+
id: "a11y-onclick-no-keyboard",
|
|
1188
|
+
regex: /onClick\s*=\s*\{[^}]+\}(?![^>]*onKeyDown|onKeyPress|onKeyUp)/g,
|
|
1189
|
+
rule: "Click handler without keyboard support",
|
|
1190
|
+
fix: "Add onKeyDown for Enter/Space",
|
|
1191
|
+
severity: "warning",
|
|
1192
|
+
category: "a11y"
|
|
1193
|
+
},
|
|
1194
|
+
// === FORM PATTERNS ===
|
|
1195
|
+
{
|
|
1196
|
+
id: "form-no-autocomplete",
|
|
1197
|
+
regex: /<input[^>]+type\s*=\s*['"]password['"][^>]*(?!autocomplete)/gi,
|
|
1198
|
+
rule: "Password field missing autocomplete",
|
|
1199
|
+
fix: 'Add autocomplete="current-password" or "new-password"',
|
|
1200
|
+
severity: "warning",
|
|
1201
|
+
category: "form"
|
|
1202
|
+
},
|
|
1203
|
+
// === PERFORMANCE PATTERNS ===
|
|
1204
|
+
{
|
|
1205
|
+
id: "perf-console-in-loop",
|
|
1206
|
+
regex: /(?:for|while|\.forEach|\.map)\s*\([^)]*\)\s*\{[^}]*console\./g,
|
|
1207
|
+
rule: "Console in loop impacts performance",
|
|
1208
|
+
fix: "Remove or move outside loop",
|
|
1209
|
+
severity: "warning",
|
|
1210
|
+
category: "banned"
|
|
1211
|
+
},
|
|
1212
|
+
{
|
|
1213
|
+
id: "perf-await-in-loop",
|
|
1214
|
+
regex: /(?:for|while)\s*\([^)]*\)\s*\{[^}]*await\s/g,
|
|
1215
|
+
rule: "await in loop is sequential",
|
|
1216
|
+
fix: "Use Promise.all for parallel execution",
|
|
1217
|
+
severity: "warning",
|
|
1218
|
+
category: "async"
|
|
978
1219
|
}
|
|
979
1220
|
];
|
|
980
1221
|
var REQUIRED_CHECKS = [
|
|
1222
|
+
// === BUTTON CHECKS ===
|
|
981
1223
|
{
|
|
982
1224
|
id: "button-loading",
|
|
1225
|
+
category: "form",
|
|
983
1226
|
check: (code) => {
|
|
984
1227
|
const hasButton = /<Button|<button/i.test(code);
|
|
985
1228
|
if (!hasButton) return { pass: true };
|
|
@@ -987,17 +1230,31 @@ var REQUIRED_CHECKS = [
|
|
|
987
1230
|
return { pass: hasDisabled, rule: "Buttons must have disabled={isLoading}", fix: "Add loading state" };
|
|
988
1231
|
}
|
|
989
1232
|
},
|
|
1233
|
+
// === FORM CHECKS ===
|
|
990
1234
|
{
|
|
991
1235
|
id: "form-validation",
|
|
1236
|
+
category: "form",
|
|
992
1237
|
check: (code) => {
|
|
993
1238
|
const hasForm = /<form|useForm|handleSubmit/i.test(code);
|
|
994
1239
|
if (!hasForm) return { pass: true };
|
|
995
|
-
const hasValidation = /zod|z\.|zodResolver|schema/i.test(code);
|
|
996
|
-
return { pass: hasValidation, rule: "Forms must have
|
|
1240
|
+
const hasValidation = /zod|z\.|zodResolver|schema|yup|validate/i.test(code);
|
|
1241
|
+
return { pass: hasValidation, rule: "Forms must have validation", fix: "Add Zod/Yup schema" };
|
|
1242
|
+
}
|
|
1243
|
+
},
|
|
1244
|
+
{
|
|
1245
|
+
id: "form-error-display",
|
|
1246
|
+
category: "form",
|
|
1247
|
+
check: (code) => {
|
|
1248
|
+
const hasForm = /<form|useForm/i.test(code);
|
|
1249
|
+
if (!hasForm) return { pass: true };
|
|
1250
|
+
const hasErrors = /errors\.|error\s*&&|formState.*errors|\.message/i.test(code);
|
|
1251
|
+
return { pass: hasErrors, rule: "Forms must display errors", fix: "Add error messages" };
|
|
997
1252
|
}
|
|
998
1253
|
},
|
|
1254
|
+
// === ASYNC CHECKS ===
|
|
999
1255
|
{
|
|
1000
1256
|
id: "async-error-handling",
|
|
1257
|
+
category: "async",
|
|
1001
1258
|
check: (code) => {
|
|
1002
1259
|
const hasAsync = /async\s+|await\s+/i.test(code);
|
|
1003
1260
|
if (!hasAsync) return { pass: true };
|
|
@@ -1005,14 +1262,102 @@ var REQUIRED_CHECKS = [
|
|
|
1005
1262
|
return { pass: hasTryCatch, rule: "Async operations must have try/catch", fix: "Add error handling" };
|
|
1006
1263
|
}
|
|
1007
1264
|
},
|
|
1265
|
+
{
|
|
1266
|
+
id: "async-loading-state",
|
|
1267
|
+
category: "async",
|
|
1268
|
+
check: (code) => {
|
|
1269
|
+
const hasAsync = /async\s+|await\s+|\.then\s*\(/i.test(code);
|
|
1270
|
+
if (!hasAsync) return { pass: true };
|
|
1271
|
+
const hasLoading = /loading|isLoading|pending|fetching|submitting/i.test(code);
|
|
1272
|
+
return { pass: hasLoading, rule: "Async operations must have loading state", fix: "Add isLoading state" };
|
|
1273
|
+
}
|
|
1274
|
+
},
|
|
1275
|
+
// === API ROUTE CHECKS ===
|
|
1008
1276
|
{
|
|
1009
1277
|
id: "api-auth-check",
|
|
1278
|
+
category: "api",
|
|
1010
1279
|
check: (code) => {
|
|
1011
1280
|
const isApiRoute = /export\s+(async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH)/i.test(code);
|
|
1012
1281
|
if (!isApiRoute) return { pass: true };
|
|
1013
|
-
const hasAuth = /getUser|getSession|auth\(\)|authenticate|unauthorized|401/i.test(code);
|
|
1282
|
+
const hasAuth = /getUser|getSession|auth\(\)|authenticate|unauthorized|401|getServerSession/i.test(code);
|
|
1014
1283
|
return { pass: hasAuth, rule: "API routes must check authentication", fix: "Add auth check" };
|
|
1015
1284
|
}
|
|
1285
|
+
},
|
|
1286
|
+
{
|
|
1287
|
+
id: "api-input-validation",
|
|
1288
|
+
category: "api",
|
|
1289
|
+
check: (code) => {
|
|
1290
|
+
const isApiRoute = /export\s+(async\s+)?function\s+(POST|PUT|PATCH)/i.test(code);
|
|
1291
|
+
if (!isApiRoute) return { pass: true };
|
|
1292
|
+
const hasValidation = /safeParse|parse\(|validate|schema|zod|z\./i.test(code);
|
|
1293
|
+
return { pass: hasValidation, rule: "API routes must validate input", fix: "Add Zod validation" };
|
|
1294
|
+
}
|
|
1295
|
+
},
|
|
1296
|
+
{
|
|
1297
|
+
id: "api-status-codes",
|
|
1298
|
+
category: "api",
|
|
1299
|
+
check: (code) => {
|
|
1300
|
+
const isApiRoute = /export\s+(async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH)/i.test(code);
|
|
1301
|
+
if (!isApiRoute) return { pass: true };
|
|
1302
|
+
const hasStatusCodes = /status:\s*\d{3}|\{\s*status:\s*\d/i.test(code);
|
|
1303
|
+
return { pass: hasStatusCodes, rule: "API routes must return proper status codes", fix: "Add status codes" };
|
|
1304
|
+
}
|
|
1305
|
+
},
|
|
1306
|
+
// === LIST/DATA CHECKS ===
|
|
1307
|
+
{
|
|
1308
|
+
id: "list-loading-state",
|
|
1309
|
+
category: "async",
|
|
1310
|
+
check: (code) => {
|
|
1311
|
+
const hasList = /\.map\s*\(|Array\.from|forEach/i.test(code);
|
|
1312
|
+
const hasFetch = /fetch|useQuery|useSWR|axios/i.test(code);
|
|
1313
|
+
if (!hasList || !hasFetch) return { pass: true };
|
|
1314
|
+
const hasLoading = /Skeleton|Spinner|Loading|isLoading|loading/i.test(code);
|
|
1315
|
+
return { pass: hasLoading, rule: "Lists must have loading state", fix: "Add skeleton/spinner" };
|
|
1316
|
+
}
|
|
1317
|
+
},
|
|
1318
|
+
{
|
|
1319
|
+
id: "list-empty-state",
|
|
1320
|
+
category: "async",
|
|
1321
|
+
check: (code) => {
|
|
1322
|
+
const hasList = /\.map\s*\(/i.test(code);
|
|
1323
|
+
const hasFetch = /fetch|useQuery|useSWR|axios/i.test(code);
|
|
1324
|
+
if (!hasList || !hasFetch) return { pass: true };
|
|
1325
|
+
const hasEmpty = /length\s*===?\s*0|!.*\.length|empty|no\s+\w+\s+found/i.test(code);
|
|
1326
|
+
return { pass: hasEmpty, rule: "Lists must have empty state", fix: "Add empty state UI" };
|
|
1327
|
+
}
|
|
1328
|
+
},
|
|
1329
|
+
// === SECURITY CHECKS ===
|
|
1330
|
+
{
|
|
1331
|
+
id: "security-env-secrets",
|
|
1332
|
+
category: "security",
|
|
1333
|
+
check: (code) => {
|
|
1334
|
+
const hasSecret = /api[_-]?key|secret|password|token/i.test(code);
|
|
1335
|
+
if (!hasSecret) return { pass: true };
|
|
1336
|
+
const usesEnv = /process\.env|import\.meta\.env/i.test(code);
|
|
1337
|
+
return { pass: usesEnv, rule: "Secrets must use environment variables", fix: "Move to .env" };
|
|
1338
|
+
}
|
|
1339
|
+
},
|
|
1340
|
+
// === TYPESCRIPT CHECKS ===
|
|
1341
|
+
{
|
|
1342
|
+
id: "ts-export-types",
|
|
1343
|
+
category: "typescript",
|
|
1344
|
+
check: (code) => {
|
|
1345
|
+
const hasExport = /export\s+(const|function|class)\s+\w+/i.test(code);
|
|
1346
|
+
if (!hasExport) return { pass: true };
|
|
1347
|
+
const hasTypes = /:\s*\w+|<\w+>|interface\s+|type\s+/i.test(code);
|
|
1348
|
+
return { pass: hasTypes, rule: "Exports must have TypeScript types", fix: "Add type annotations" };
|
|
1349
|
+
}
|
|
1350
|
+
},
|
|
1351
|
+
// === ACCESSIBILITY CHECKS ===
|
|
1352
|
+
{
|
|
1353
|
+
id: "a11y-form-labels",
|
|
1354
|
+
category: "a11y",
|
|
1355
|
+
check: (code) => {
|
|
1356
|
+
const hasInput = /<input|<select|<textarea/i.test(code);
|
|
1357
|
+
if (!hasInput) return { pass: true };
|
|
1358
|
+
const hasLabel = /<label|aria-label|aria-labelledby|id=.*htmlFor/i.test(code);
|
|
1359
|
+
return { pass: hasLabel, rule: "Form inputs must have labels", fix: "Add <label> or aria-label" };
|
|
1360
|
+
}
|
|
1016
1361
|
}
|
|
1017
1362
|
];
|
|
1018
1363
|
function validateCode(code) {
|
|
@@ -1029,7 +1374,8 @@ function validateCode(code) {
|
|
|
1029
1374
|
line: lineNumber,
|
|
1030
1375
|
code: match[0],
|
|
1031
1376
|
fix: pattern.fix,
|
|
1032
|
-
severity: pattern.severity
|
|
1377
|
+
severity: pattern.severity,
|
|
1378
|
+
category: pattern.category
|
|
1033
1379
|
});
|
|
1034
1380
|
}
|
|
1035
1381
|
}
|
|
@@ -1039,7 +1385,8 @@ function validateCode(code) {
|
|
|
1039
1385
|
violations.push({
|
|
1040
1386
|
rule: result.rule,
|
|
1041
1387
|
fix: result.fix || "See pattern documentation",
|
|
1042
|
-
severity: "error"
|
|
1388
|
+
severity: "error",
|
|
1389
|
+
category: check.category
|
|
1043
1390
|
});
|
|
1044
1391
|
}
|
|
1045
1392
|
}
|
|
@@ -1285,7 +1632,7 @@ ${originalRequest}`;
|
|
|
1285
1632
|
}
|
|
1286
1633
|
const pathMatch = request.match(/['"`]([^'"`]+)['"`]/g);
|
|
1287
1634
|
if (pathMatch) {
|
|
1288
|
-
keywords.push(...pathMatch.map((
|
|
1635
|
+
keywords.push(...pathMatch.map((p8) => p8.replace(/['"`]/g, "")));
|
|
1289
1636
|
}
|
|
1290
1637
|
return keywords;
|
|
1291
1638
|
}
|
|
@@ -1499,11 +1846,11 @@ Format each as a single line. Be brief.`;
|
|
|
1499
1846
|
} else if (line.toLowerCase().includes("suggestion") || line.toLowerCase().includes("improvement")) {
|
|
1500
1847
|
currentSection = "suggestions";
|
|
1501
1848
|
} else if (line.startsWith("-") || line.startsWith("\u2022") || /^\d+\./.test(line)) {
|
|
1502
|
-
const
|
|
1503
|
-
if (
|
|
1504
|
-
issues.push(
|
|
1505
|
-
} else if (
|
|
1506
|
-
suggestions.push(
|
|
1849
|
+
const text5 = line.replace(/^[-•\d.]\s*/, "").trim();
|
|
1850
|
+
if (text5 && currentSection === "issues") {
|
|
1851
|
+
issues.push(text5);
|
|
1852
|
+
} else if (text5 && currentSection === "suggestions") {
|
|
1853
|
+
suggestions.push(text5);
|
|
1507
1854
|
}
|
|
1508
1855
|
}
|
|
1509
1856
|
}
|
|
@@ -1572,8 +1919,8 @@ Format each as a single line. Be brief.`;
|
|
|
1572
1919
|
if (this.ai) {
|
|
1573
1920
|
onProgress?.("Running AI deep analysis...", 60);
|
|
1574
1921
|
const filesForAI = await Promise.all(
|
|
1575
|
-
filePaths.slice(0, 50).map(async (
|
|
1576
|
-
const f = await this.loadFile(
|
|
1922
|
+
filePaths.slice(0, 50).map(async (p8) => {
|
|
1923
|
+
const f = await this.loadFile(p8);
|
|
1577
1924
|
return f ? { path: f.path, content: f.content } : null;
|
|
1578
1925
|
})
|
|
1579
1926
|
);
|
|
@@ -1730,57 +2077,67 @@ import * as p from "@clack/prompts";
|
|
|
1730
2077
|
import chalk from "chalk";
|
|
1731
2078
|
var colors = {
|
|
1732
2079
|
primary: chalk.hex("#7fff00"),
|
|
1733
|
-
// Lime green (
|
|
2080
|
+
// Lime green (brand)
|
|
1734
2081
|
secondary: chalk.hex("#888888"),
|
|
1735
2082
|
// Gray text
|
|
1736
2083
|
muted: chalk.hex("#555555"),
|
|
1737
2084
|
// Dimmed text
|
|
1738
|
-
success: chalk.hex("#
|
|
1739
|
-
//
|
|
1740
|
-
warning: chalk.hex("#
|
|
1741
|
-
//
|
|
2085
|
+
success: chalk.hex("#2ecc71"),
|
|
2086
|
+
// Bright green
|
|
2087
|
+
warning: chalk.hex("#f1c40f"),
|
|
2088
|
+
// Yellow
|
|
1742
2089
|
error: chalk.hex("#e74c3c"),
|
|
1743
2090
|
// Red
|
|
1744
2091
|
info: chalk.hex("#3498db"),
|
|
1745
2092
|
// Blue
|
|
1746
|
-
white: chalk.hex("#
|
|
1747
|
-
dim: chalk.hex("#666666")
|
|
2093
|
+
white: chalk.hex("#ffffff"),
|
|
2094
|
+
dim: chalk.hex("#666666"),
|
|
2095
|
+
accent: chalk.hex("#9b59b6")
|
|
2096
|
+
// Purple for highlights
|
|
2097
|
+
};
|
|
2098
|
+
var box = {
|
|
2099
|
+
topLeft: "\u256D",
|
|
2100
|
+
topRight: "\u256E",
|
|
2101
|
+
bottomLeft: "\u2570",
|
|
2102
|
+
bottomRight: "\u256F",
|
|
2103
|
+
horizontal: "\u2500",
|
|
2104
|
+
vertical: "\u2502",
|
|
2105
|
+
teeRight: "\u251C",
|
|
2106
|
+
teeLeft: "\u2524"
|
|
1748
2107
|
};
|
|
1749
2108
|
var sym = {
|
|
1750
|
-
check:
|
|
1751
|
-
cross:
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
2109
|
+
check: colors.success("\u2713"),
|
|
2110
|
+
cross: colors.error("\u2717"),
|
|
2111
|
+
bullet: colors.dim("\u2022"),
|
|
2112
|
+
arrow: colors.primary("\u2192"),
|
|
2113
|
+
arrowRight: "\u203A",
|
|
2114
|
+
spinner: "\u27F3",
|
|
2115
|
+
star: "\u2605",
|
|
2116
|
+
lightning: "\u26A1",
|
|
2117
|
+
folder: "\u{1F4C1}",
|
|
2118
|
+
file: "\u{1F4C4}",
|
|
2119
|
+
gear: "\u2699",
|
|
2120
|
+
rocket: "\u{1F680}",
|
|
2121
|
+
sparkles: "\u2728",
|
|
2122
|
+
warning: "\u26A0",
|
|
2123
|
+
info: "\u2139"
|
|
1757
2124
|
};
|
|
1758
|
-
function showHeader() {
|
|
1759
|
-
console.clear();
|
|
1760
|
-
console.log("");
|
|
1761
|
-
console.log(colors.muted(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
1762
|
-
console.log(colors.muted(" \u2502") + colors.white(" \u{1F4E6} C O D E B A K E R S ") + colors.muted("\u2502"));
|
|
1763
|
-
console.log(colors.muted(" \u2502") + colors.dim(" ") + colors.muted("\u2502"));
|
|
1764
|
-
console.log(colors.muted(" \u2502") + colors.secondary(" AI Dev Team That Follows The Rules ") + colors.muted("\u2502"));
|
|
1765
|
-
console.log(colors.muted(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
1766
|
-
console.log("");
|
|
1767
|
-
}
|
|
1768
2125
|
function showSuccess(message) {
|
|
1769
|
-
console.log(` ${sym.check} ${colors.
|
|
2126
|
+
console.log(` ${sym.check} ${colors.success(message)}`);
|
|
1770
2127
|
}
|
|
1771
2128
|
function showError(message) {
|
|
1772
2129
|
console.log(` ${sym.cross} ${colors.error(message)}`);
|
|
1773
2130
|
}
|
|
1774
2131
|
function showWarning(message) {
|
|
1775
|
-
console.log(` ${colors.warning(
|
|
2132
|
+
console.log(` ${colors.warning(sym.warning)} ${colors.warning(message)}`);
|
|
1776
2133
|
}
|
|
1777
2134
|
function showInfo(message) {
|
|
1778
|
-
console.log(colors.
|
|
2135
|
+
console.log(` ${colors.info(sym.info)} ${colors.info(message)}`);
|
|
1779
2136
|
}
|
|
1780
2137
|
function divider() {
|
|
1781
|
-
console.log(colors.muted(
|
|
2138
|
+
console.log(colors.muted(` ${box.horizontal.repeat(50)}`));
|
|
1782
2139
|
}
|
|
1783
|
-
var promptSymbol = colors.primary("\
|
|
2140
|
+
var promptSymbol = colors.primary("\u203A");
|
|
1784
2141
|
|
|
1785
2142
|
// src/core/nlp.ts
|
|
1786
2143
|
var INTENT_PATTERNS = [
|
|
@@ -1820,6 +2177,24 @@ var INTENT_PATTERNS = [
|
|
|
1820
2177
|
weight: 8
|
|
1821
2178
|
},
|
|
1822
2179
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
2180
|
+
// WEBSITE REVIEW - Audit a website URL
|
|
2181
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2182
|
+
{
|
|
2183
|
+
intent: "website-review",
|
|
2184
|
+
patterns: [
|
|
2185
|
+
/\b(review|audit|check|analyze|scan)\b.*\b(website|site|url|page|webpage)\b/i,
|
|
2186
|
+
/\b(website|site|url|page)\b.*\b(review|audit|check|issues|seo|accessibility)\b/i,
|
|
2187
|
+
/\bseo\s*(audit|check|review|analysis)\b/i,
|
|
2188
|
+
/\baccessibility\s*(audit|check|review)\b/i,
|
|
2189
|
+
/\bcheck\s*(this\s*)?(url|site|website)\b/i,
|
|
2190
|
+
/\bwhat('s| is)\s*wrong\s*with\s*(this\s*)?(website|site|url)\b/i,
|
|
2191
|
+
/\breview\s+(https?:\/\/|www\.)/i,
|
|
2192
|
+
/\baudit\s+(https?:\/\/|www\.)/i
|
|
2193
|
+
],
|
|
2194
|
+
keywords: ["review website", "audit website", "check site", "seo audit", "accessibility check", "website issues", "scan url"],
|
|
2195
|
+
weight: 9
|
|
2196
|
+
},
|
|
2197
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1823
2198
|
// PATTERNS - Pattern rules
|
|
1824
2199
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1825
2200
|
{
|
|
@@ -2503,12 +2878,12 @@ var NLPInterpreter = class {
|
|
|
2503
2878
|
deploy: "This will deploy your code. Continue?"
|
|
2504
2879
|
};
|
|
2505
2880
|
console.log("");
|
|
2506
|
-
const
|
|
2881
|
+
const confirm7 = await p.confirm({
|
|
2507
2882
|
message: colors.warning(messages[intent.type] || "Are you sure?"),
|
|
2508
2883
|
initialValue: intent.type !== "deploy"
|
|
2509
2884
|
// Default no for deploy
|
|
2510
2885
|
});
|
|
2511
|
-
return !p.isCancel(
|
|
2886
|
+
return !p.isCancel(confirm7) && confirm7 === true;
|
|
2512
2887
|
}
|
|
2513
2888
|
};
|
|
2514
2889
|
var nlp = new NLPInterpreter();
|
|
@@ -2736,22 +3111,22 @@ Please provide ONLY the corrected file content with the path. No explanation nee
|
|
|
2736
3111
|
// HELPERS
|
|
2737
3112
|
// ============================================================================
|
|
2738
3113
|
async findMissingPackages(packages) {
|
|
2739
|
-
const
|
|
3114
|
+
const fs18 = await import("fs-extra");
|
|
2740
3115
|
const path19 = await import("path");
|
|
2741
3116
|
const pkgPath = path19.join(this.scanner["projectPath"], "package.json");
|
|
2742
|
-
if (!await
|
|
2743
|
-
const pkg = await
|
|
3117
|
+
if (!await fs18.pathExists(pkgPath)) return packages;
|
|
3118
|
+
const pkg = await fs18.readJson(pkgPath);
|
|
2744
3119
|
const installed = {
|
|
2745
3120
|
...pkg.dependencies,
|
|
2746
3121
|
...pkg.devDependencies
|
|
2747
3122
|
};
|
|
2748
|
-
return packages.filter((
|
|
3123
|
+
return packages.filter((p8) => !installed[p8]);
|
|
2749
3124
|
}
|
|
2750
|
-
spin(
|
|
3125
|
+
spin(text5) {
|
|
2751
3126
|
if (this.spinner) {
|
|
2752
|
-
this.spinner.text =
|
|
3127
|
+
this.spinner.text = text5 + " (ESC to cancel)";
|
|
2753
3128
|
} else {
|
|
2754
|
-
this.spinner = ora(
|
|
3129
|
+
this.spinner = ora(text5 + " (ESC to cancel)").start();
|
|
2755
3130
|
}
|
|
2756
3131
|
}
|
|
2757
3132
|
stop() {
|
|
@@ -4410,570 +4785,42 @@ function getModeManager() {
|
|
|
4410
4785
|
|
|
4411
4786
|
// src/core/plan-executor.ts
|
|
4412
4787
|
import Anthropic3 from "@anthropic-ai/sdk";
|
|
4413
|
-
|
|
4788
|
+
|
|
4789
|
+
// src/core/question-executor.ts
|
|
4790
|
+
import * as p2 from "@clack/prompts";
|
|
4791
|
+
import Anthropic4 from "@anthropic-ai/sdk";
|
|
4792
|
+
|
|
4793
|
+
// src/core/agent-executor.ts
|
|
4794
|
+
import ora2 from "ora";
|
|
4795
|
+
|
|
4796
|
+
// src/core/input-handler.ts
|
|
4797
|
+
import fs9 from "fs-extra";
|
|
4798
|
+
import path9 from "path";
|
|
4799
|
+
|
|
4800
|
+
// src/core/prd-parser.ts
|
|
4801
|
+
import Anthropic5 from "@anthropic-ai/sdk";
|
|
4802
|
+
var PRDParser = class {
|
|
4414
4803
|
anthropic;
|
|
4415
4804
|
memory;
|
|
4416
|
-
|
|
4417
|
-
constructor(config, memory, scanner) {
|
|
4805
|
+
constructor(config, memory) {
|
|
4418
4806
|
const apiKey = config.getAnthropicKey();
|
|
4419
4807
|
if (!apiKey) throw new Error("API key not configured");
|
|
4420
|
-
this.anthropic = new
|
|
4808
|
+
this.anthropic = new Anthropic5({ apiKey });
|
|
4421
4809
|
this.memory = memory;
|
|
4422
|
-
this.scanner = scanner;
|
|
4423
4810
|
}
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
Create a detailed plan. Respond with ONLY valid JSON:
|
|
4435
|
-
{
|
|
4436
|
-
"description": "Brief summary of what will be built",
|
|
4437
|
-
"filesToCreate": ["src/path/file.ts", ...],
|
|
4438
|
-
"filesToModify": ["src/existing/file.ts", ...],
|
|
4439
|
-
"packagesToInstall": ["package-name", ...],
|
|
4440
|
-
"commandsToRun": ["npm install", "npm run build", ...],
|
|
4441
|
-
"estimatedTime": "2-3 minutes"
|
|
4442
|
-
}
|
|
4443
|
-
|
|
4444
|
-
Be specific about file paths. List ALL files that will be created or modified.`;
|
|
4445
|
-
const response = await this.anthropic.messages.create({
|
|
4446
|
-
model: "claude-sonnet-4-20250514",
|
|
4447
|
-
max_tokens: 2048,
|
|
4448
|
-
messages: [{ role: "user", content: prompt }]
|
|
4449
|
-
});
|
|
4450
|
-
const content = response.content[0]?.type === "text" ? response.content[0].text : "";
|
|
4451
|
-
try {
|
|
4452
|
-
const jsonStr = content.replace(/```json\n?|\n?```/g, "").trim();
|
|
4453
|
-
return JSON.parse(jsonStr);
|
|
4454
|
-
} catch {
|
|
4455
|
-
return {
|
|
4456
|
-
description: "Unable to parse plan",
|
|
4457
|
-
filesToCreate: [],
|
|
4458
|
-
filesToModify: [],
|
|
4459
|
-
packagesToInstall: [],
|
|
4460
|
-
commandsToRun: [],
|
|
4461
|
-
estimatedTime: "Unknown"
|
|
4462
|
-
};
|
|
4811
|
+
// ============================================================================
|
|
4812
|
+
// PARSE PRD
|
|
4813
|
+
// ============================================================================
|
|
4814
|
+
async parse(prdContent) {
|
|
4815
|
+
const chunks = this.chunkContent(prdContent, 5e4);
|
|
4816
|
+
let analysis;
|
|
4817
|
+
if (chunks.length === 1) {
|
|
4818
|
+
analysis = await this.analyzeChunk(chunks[0], true);
|
|
4819
|
+
} else {
|
|
4820
|
+
analysis = await this.analyzeMultipleChunks(chunks);
|
|
4463
4821
|
}
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
let output = "\n";
|
|
4467
|
-
output += colors.white(" \u{1F4CB} PLAN") + colors.muted(" (not executed yet)") + "\n\n";
|
|
4468
|
-
output += colors.muted(" " + plan.description) + "\n\n";
|
|
4469
|
-
if (plan.filesToCreate.length > 0) {
|
|
4470
|
-
output += colors.white(" Files to create:\n");
|
|
4471
|
-
for (const file of plan.filesToCreate) {
|
|
4472
|
-
output += colors.success(` + ${file}`) + "\n";
|
|
4473
|
-
}
|
|
4474
|
-
output += "\n";
|
|
4475
|
-
}
|
|
4476
|
-
if (plan.filesToModify.length > 0) {
|
|
4477
|
-
output += colors.white(" Files to modify:\n");
|
|
4478
|
-
for (const file of plan.filesToModify) {
|
|
4479
|
-
output += colors.warning(` ~ ${file}`) + "\n";
|
|
4480
|
-
}
|
|
4481
|
-
output += "\n";
|
|
4482
|
-
}
|
|
4483
|
-
if (plan.packagesToInstall.length > 0) {
|
|
4484
|
-
output += colors.white(" Packages to install:\n");
|
|
4485
|
-
for (const pkg of plan.packagesToInstall) {
|
|
4486
|
-
output += colors.muted(` + ${pkg}`) + "\n";
|
|
4487
|
-
}
|
|
4488
|
-
output += "\n";
|
|
4489
|
-
}
|
|
4490
|
-
if (plan.commandsToRun.length > 0) {
|
|
4491
|
-
output += colors.white(" Commands to run:\n");
|
|
4492
|
-
for (const cmd of plan.commandsToRun) {
|
|
4493
|
-
output += colors.muted(` $ ${cmd}`) + "\n";
|
|
4494
|
-
}
|
|
4495
|
-
output += "\n";
|
|
4496
|
-
}
|
|
4497
|
-
output += colors.muted(` Estimated time: ${plan.estimatedTime}`) + "\n";
|
|
4498
|
-
return output;
|
|
4499
|
-
}
|
|
4500
|
-
};
|
|
4501
|
-
|
|
4502
|
-
// src/core/question-executor.ts
|
|
4503
|
-
import * as p2 from "@clack/prompts";
|
|
4504
|
-
import Anthropic4 from "@anthropic-ai/sdk";
|
|
4505
|
-
var QuestionExecutor = class {
|
|
4506
|
-
anthropic;
|
|
4507
|
-
memory;
|
|
4508
|
-
scanner;
|
|
4509
|
-
constructor(config, memory, scanner) {
|
|
4510
|
-
const apiKey = config.getAnthropicKey();
|
|
4511
|
-
if (!apiKey) throw new Error("API key not configured");
|
|
4512
|
-
this.anthropic = new Anthropic4({ apiKey });
|
|
4513
|
-
this.memory = memory;
|
|
4514
|
-
this.scanner = scanner;
|
|
4515
|
-
}
|
|
4516
|
-
async generateQuestions(request) {
|
|
4517
|
-
const projectContext = await this.scanner.getContextString([]);
|
|
4518
|
-
const memoryContext = this.memory.getFullContext();
|
|
4519
|
-
const prompt = `You are about to build something. Before starting, identify what clarifying questions would help you build exactly what the user wants.
|
|
4520
|
-
|
|
4521
|
-
PROJECT CONTEXT:
|
|
4522
|
-
${projectContext}
|
|
4523
|
-
|
|
4524
|
-
MEMORY (what you already know):
|
|
4525
|
-
${memoryContext}
|
|
4526
|
-
|
|
4527
|
-
USER REQUEST:
|
|
4528
|
-
${request}
|
|
4529
|
-
|
|
4530
|
-
Generate 2-4 clarifying questions. Only ask questions where the answer would significantly change what you build. Skip questions if the answer is obvious from context or memory.
|
|
4531
|
-
|
|
4532
|
-
Respond with ONLY valid JSON:
|
|
4533
|
-
{
|
|
4534
|
-
"questions": [
|
|
4535
|
-
{
|
|
4536
|
-
"id": "auth_type",
|
|
4537
|
-
"text": "What type of authentication do you need?",
|
|
4538
|
-
"type": "select",
|
|
4539
|
-
"options": [
|
|
4540
|
-
{ "value": "email", "label": "Email/password only" },
|
|
4541
|
-
{ "value": "oauth", "label": "OAuth (Google, GitHub)" },
|
|
4542
|
-
{ "value": "both", "label": "Both email and OAuth" }
|
|
4543
|
-
]
|
|
4544
|
-
},
|
|
4545
|
-
{
|
|
4546
|
-
"id": "need_reset",
|
|
4547
|
-
"text": "Include password reset flow?",
|
|
4548
|
-
"type": "confirm",
|
|
4549
|
-
"default": "yes"
|
|
4550
|
-
}
|
|
4551
|
-
]
|
|
4552
|
-
}
|
|
4553
|
-
|
|
4554
|
-
Question types:
|
|
4555
|
-
- "select": Multiple choice (provide options)
|
|
4556
|
-
- "confirm": Yes/no question
|
|
4557
|
-
- "text": Free text input
|
|
4558
|
-
|
|
4559
|
-
Only ask ESSENTIAL questions. If you can make a reasonable assumption, do so.`;
|
|
4560
|
-
const response = await this.anthropic.messages.create({
|
|
4561
|
-
model: "claude-sonnet-4-20250514",
|
|
4562
|
-
max_tokens: 2048,
|
|
4563
|
-
messages: [{ role: "user", content: prompt }]
|
|
4564
|
-
});
|
|
4565
|
-
const content = response.content[0]?.type === "text" ? response.content[0].text : "";
|
|
4566
|
-
try {
|
|
4567
|
-
const jsonStr = content.replace(/```json\n?|\n?```/g, "").trim();
|
|
4568
|
-
const parsed = JSON.parse(jsonStr);
|
|
4569
|
-
return parsed.questions || [];
|
|
4570
|
-
} catch {
|
|
4571
|
-
return [];
|
|
4572
|
-
}
|
|
4573
|
-
}
|
|
4574
|
-
async askQuestions(questions) {
|
|
4575
|
-
const answers = {};
|
|
4576
|
-
if (questions.length === 0) {
|
|
4577
|
-
console.log(colors.muted("\n No clarification needed - proceeding with build.\n"));
|
|
4578
|
-
return { questions: [], answers: {} };
|
|
4579
|
-
}
|
|
4580
|
-
console.log("\n" + colors.secondary(" \u{1F914} A few questions first:") + "\n");
|
|
4581
|
-
for (const question of questions) {
|
|
4582
|
-
let answer;
|
|
4583
|
-
switch (question.type) {
|
|
4584
|
-
case "select":
|
|
4585
|
-
answer = await p2.select({
|
|
4586
|
-
message: question.text,
|
|
4587
|
-
options: question.options?.map((opt) => ({
|
|
4588
|
-
value: opt.value,
|
|
4589
|
-
label: opt.label
|
|
4590
|
-
})) || []
|
|
4591
|
-
});
|
|
4592
|
-
break;
|
|
4593
|
-
case "confirm":
|
|
4594
|
-
answer = await p2.confirm({
|
|
4595
|
-
message: question.text,
|
|
4596
|
-
initialValue: question.default === "yes"
|
|
4597
|
-
});
|
|
4598
|
-
break;
|
|
4599
|
-
case "text":
|
|
4600
|
-
default:
|
|
4601
|
-
answer = await p2.text({
|
|
4602
|
-
message: question.text,
|
|
4603
|
-
placeholder: question.default
|
|
4604
|
-
});
|
|
4605
|
-
break;
|
|
4606
|
-
}
|
|
4607
|
-
if (p2.isCancel(answer)) {
|
|
4608
|
-
return { questions, answers };
|
|
4609
|
-
}
|
|
4610
|
-
answers[question.id] = String(answer);
|
|
4611
|
-
}
|
|
4612
|
-
return { questions, answers };
|
|
4613
|
-
}
|
|
4614
|
-
formatAnswersForPrompt(result) {
|
|
4615
|
-
if (Object.keys(result.answers).length === 0) {
|
|
4616
|
-
return "";
|
|
4617
|
-
}
|
|
4618
|
-
let context = "\n\nUSER PREFERENCES (from clarifying questions):\n";
|
|
4619
|
-
for (const question of result.questions) {
|
|
4620
|
-
const answer = result.answers[question.id];
|
|
4621
|
-
if (answer) {
|
|
4622
|
-
context += `- ${question.text}: ${answer}
|
|
4623
|
-
`;
|
|
4624
|
-
}
|
|
4625
|
-
}
|
|
4626
|
-
return context;
|
|
4627
|
-
}
|
|
4628
|
-
showSummary(result) {
|
|
4629
|
-
if (Object.keys(result.answers).length === 0) return;
|
|
4630
|
-
console.log("\n" + colors.muted(" Got it! Building with:"));
|
|
4631
|
-
for (const question of result.questions) {
|
|
4632
|
-
const answer = result.answers[question.id];
|
|
4633
|
-
if (answer) {
|
|
4634
|
-
let displayAnswer = answer;
|
|
4635
|
-
if (question.type === "select" && question.options) {
|
|
4636
|
-
const option = question.options.find((o) => o.value === answer);
|
|
4637
|
-
if (option) displayAnswer = option.label;
|
|
4638
|
-
} else if (question.type === "confirm") {
|
|
4639
|
-
displayAnswer = answer === "true" ? "Yes" : "No";
|
|
4640
|
-
}
|
|
4641
|
-
console.log(colors.muted(` \u2022 ${displayAnswer}`));
|
|
4642
|
-
}
|
|
4643
|
-
}
|
|
4644
|
-
console.log("");
|
|
4645
|
-
}
|
|
4646
|
-
};
|
|
4647
|
-
|
|
4648
|
-
// src/core/agent-executor.ts
|
|
4649
|
-
import ora2 from "ora";
|
|
4650
|
-
var AgentExecutor = class {
|
|
4651
|
-
ai;
|
|
4652
|
-
config;
|
|
4653
|
-
memory;
|
|
4654
|
-
scanner;
|
|
4655
|
-
editor;
|
|
4656
|
-
spinner = null;
|
|
4657
|
-
paused = false;
|
|
4658
|
-
cancelled = false;
|
|
4659
|
-
constructor(config, ai, memory, scanner, editor) {
|
|
4660
|
-
this.config = config;
|
|
4661
|
-
this.ai = ai;
|
|
4662
|
-
this.memory = memory;
|
|
4663
|
-
this.scanner = scanner;
|
|
4664
|
-
this.editor = editor;
|
|
4665
|
-
}
|
|
4666
|
-
// ============================================================================
|
|
4667
|
-
// MAIN EXECUTION
|
|
4668
|
-
// ============================================================================
|
|
4669
|
-
async execute(request, options = {}) {
|
|
4670
|
-
const startTime = Date.now();
|
|
4671
|
-
this.paused = false;
|
|
4672
|
-
this.cancelled = false;
|
|
4673
|
-
const result = {
|
|
4674
|
-
success: false,
|
|
4675
|
-
filesCreated: [],
|
|
4676
|
-
filesModified: [],
|
|
4677
|
-
packagesInstalled: [],
|
|
4678
|
-
commandsRun: [],
|
|
4679
|
-
errors: [],
|
|
4680
|
-
duration: 0
|
|
4681
|
-
};
|
|
4682
|
-
const checkCancelled = () => {
|
|
4683
|
-
if (options.checkCancelled?.() || this.cancelled) {
|
|
4684
|
-
return true;
|
|
4685
|
-
}
|
|
4686
|
-
return false;
|
|
4687
|
-
};
|
|
4688
|
-
console.log("");
|
|
4689
|
-
console.log(colors.primary(" \u{1F916} AGENT MODE") + colors.muted(" - Running autonomously"));
|
|
4690
|
-
console.log(colors.muted(" Press ESC to pause at any checkpoint"));
|
|
4691
|
-
console.log("");
|
|
4692
|
-
try {
|
|
4693
|
-
const isComplex = this.isComplexRequest(request);
|
|
4694
|
-
if (isComplex) {
|
|
4695
|
-
return await this.executeParallel(request, options, checkCancelled);
|
|
4696
|
-
}
|
|
4697
|
-
const orchestrator = new Orchestrator(this.ai, this.editor, this.scanner, this.memory);
|
|
4698
|
-
this.spin("Generating code...");
|
|
4699
|
-
const genResult = await this.ai.generateStream(
|
|
4700
|
-
request,
|
|
4701
|
-
{
|
|
4702
|
-
onToken: (token) => {
|
|
4703
|
-
}
|
|
4704
|
-
},
|
|
4705
|
-
checkCancelled
|
|
4706
|
-
);
|
|
4707
|
-
if (checkCancelled()) {
|
|
4708
|
-
this.stop();
|
|
4709
|
-
return { ...result, cancelled: true, duration: Date.now() - startTime };
|
|
4710
|
-
}
|
|
4711
|
-
if (this.paused) {
|
|
4712
|
-
this.stop();
|
|
4713
|
-
return { ...result, paused: true, duration: Date.now() - startTime };
|
|
4714
|
-
}
|
|
4715
|
-
this.spin("Applying changes...");
|
|
4716
|
-
this.editor.parseResponse(genResult.content);
|
|
4717
|
-
const changes = this.editor.getPendingChanges();
|
|
4718
|
-
for (const change of changes) {
|
|
4719
|
-
if (checkCancelled()) {
|
|
4720
|
-
this.stop();
|
|
4721
|
-
return { ...result, cancelled: true, duration: Date.now() - startTime };
|
|
4722
|
-
}
|
|
4723
|
-
options.onFile?.(change.path, change.type === "create" ? "create" : "modify");
|
|
4724
|
-
if (change.type === "create") {
|
|
4725
|
-
result.filesCreated.push(change.path);
|
|
4726
|
-
} else {
|
|
4727
|
-
result.filesModified.push(change.path);
|
|
4728
|
-
}
|
|
4729
|
-
this.logStep(`${change.type === "create" ? "Created" : "Modified"} ${change.path}`);
|
|
4730
|
-
}
|
|
4731
|
-
await this.editor.apply();
|
|
4732
|
-
const allContent = changes.map((c) => c.content || "").join("\n");
|
|
4733
|
-
const packages = this.detectPackages(allContent);
|
|
4734
|
-
if (packages.length > 0 && !checkCancelled()) {
|
|
4735
|
-
this.spin("Installing packages...");
|
|
4736
|
-
const { Terminal: Terminal2 } = await import("./terminal-6ZQVP6R7.js");
|
|
4737
|
-
const terminal = new Terminal2();
|
|
4738
|
-
for (const pkg of packages) {
|
|
4739
|
-
this.logStep(`Installing ${pkg}...`);
|
|
4740
|
-
}
|
|
4741
|
-
const installResult = await terminal.npmInstall(packages);
|
|
4742
|
-
if (installResult.success) {
|
|
4743
|
-
result.packagesInstalled = packages;
|
|
4744
|
-
result.commandsRun.push(`npm install ${packages.join(" ")}`);
|
|
4745
|
-
}
|
|
4746
|
-
}
|
|
4747
|
-
if (!checkCancelled()) {
|
|
4748
|
-
this.spin("Type checking...");
|
|
4749
|
-
const { Terminal: Terminal2 } = await import("./terminal-6ZQVP6R7.js");
|
|
4750
|
-
const terminal = new Terminal2();
|
|
4751
|
-
const typeResult = await terminal.typecheck();
|
|
4752
|
-
result.commandsRun.push("tsc --noEmit");
|
|
4753
|
-
if (!typeResult.success) {
|
|
4754
|
-
this.logStep("Fixing type errors...");
|
|
4755
|
-
}
|
|
4756
|
-
}
|
|
4757
|
-
this.stop();
|
|
4758
|
-
result.success = result.errors.length === 0;
|
|
4759
|
-
result.duration = Date.now() - startTime;
|
|
4760
|
-
this.showSummary(result);
|
|
4761
|
-
for (const file of [...result.filesCreated, ...result.filesModified]) {
|
|
4762
|
-
this.memory.addModifiedFile(file);
|
|
4763
|
-
}
|
|
4764
|
-
await this.memory.save();
|
|
4765
|
-
return result;
|
|
4766
|
-
} catch (error) {
|
|
4767
|
-
this.stop();
|
|
4768
|
-
result.errors.push(error instanceof Error ? error.message : "Unknown error");
|
|
4769
|
-
result.duration = Date.now() - startTime;
|
|
4770
|
-
return result;
|
|
4771
|
-
}
|
|
4772
|
-
}
|
|
4773
|
-
// ============================================================================
|
|
4774
|
-
// PARALLEL EXECUTION
|
|
4775
|
-
// ============================================================================
|
|
4776
|
-
async executeParallel(request, options, checkCancelled) {
|
|
4777
|
-
const startTime = Date.now();
|
|
4778
|
-
const result = {
|
|
4779
|
-
success: false,
|
|
4780
|
-
filesCreated: [],
|
|
4781
|
-
filesModified: [],
|
|
4782
|
-
packagesInstalled: [],
|
|
4783
|
-
commandsRun: [],
|
|
4784
|
-
errors: [],
|
|
4785
|
-
duration: 0
|
|
4786
|
-
};
|
|
4787
|
-
console.log(colors.muted(" Using parallel agents for faster build..."));
|
|
4788
|
-
console.log("");
|
|
4789
|
-
const parallelSystem = new ParallelAgentSystem(
|
|
4790
|
-
this.config,
|
|
4791
|
-
this.memory,
|
|
4792
|
-
this.scanner,
|
|
4793
|
-
this.editor
|
|
4794
|
-
);
|
|
4795
|
-
const parallelResult = await parallelSystem.build(
|
|
4796
|
-
request,
|
|
4797
|
-
{
|
|
4798
|
-
onTaskPlan: (tasks) => {
|
|
4799
|
-
console.log(colors.muted(` \u{1F4CB} ${tasks.length} tasks planned`));
|
|
4800
|
-
},
|
|
4801
|
-
onAgentStart: (taskId, name) => {
|
|
4802
|
-
console.log(colors.muted(` \u25CF [${name}] Starting...`));
|
|
4803
|
-
},
|
|
4804
|
-
onAgentComplete: (taskId, agentResult) => {
|
|
4805
|
-
if (agentResult.success) {
|
|
4806
|
-
console.log(colors.success(` \u2713 [${taskId}] Complete (${agentResult.files.length} files)`));
|
|
4807
|
-
} else {
|
|
4808
|
-
console.log(colors.error(` \u2717 [${taskId}] Failed`));
|
|
4809
|
-
}
|
|
4810
|
-
},
|
|
4811
|
-
onMergeStart: () => {
|
|
4812
|
-
console.log(colors.muted(" \u{1F504} Merging results..."));
|
|
4813
|
-
},
|
|
4814
|
-
onMergeComplete: (conflicts) => {
|
|
4815
|
-
if (conflicts.length === 0) {
|
|
4816
|
-
console.log(colors.success(" \u2713 No conflicts"));
|
|
4817
|
-
}
|
|
4818
|
-
}
|
|
4819
|
-
},
|
|
4820
|
-
checkCancelled
|
|
4821
|
-
);
|
|
4822
|
-
if (parallelResult.cancelled) {
|
|
4823
|
-
return { ...result, cancelled: true, duration: Date.now() - startTime };
|
|
4824
|
-
}
|
|
4825
|
-
const changes = this.editor.getPendingChanges();
|
|
4826
|
-
await this.editor.apply();
|
|
4827
|
-
for (const change of changes) {
|
|
4828
|
-
if (change.type === "create") {
|
|
4829
|
-
result.filesCreated.push(change.path);
|
|
4830
|
-
} else {
|
|
4831
|
-
result.filesModified.push(change.path);
|
|
4832
|
-
}
|
|
4833
|
-
}
|
|
4834
|
-
result.success = parallelResult.success;
|
|
4835
|
-
result.duration = Date.now() - startTime;
|
|
4836
|
-
this.showSummary(result);
|
|
4837
|
-
return result;
|
|
4838
|
-
}
|
|
4839
|
-
// ============================================================================
|
|
4840
|
-
// HELPERS
|
|
4841
|
-
// ============================================================================
|
|
4842
|
-
isComplexRequest(request) {
|
|
4843
|
-
const lower = request.toLowerCase();
|
|
4844
|
-
const complexIndicators = [
|
|
4845
|
-
" and ",
|
|
4846
|
-
" with ",
|
|
4847
|
-
", ",
|
|
4848
|
-
" + ",
|
|
4849
|
-
"full",
|
|
4850
|
-
"complete",
|
|
4851
|
-
"entire",
|
|
4852
|
-
"saas",
|
|
4853
|
-
"dashboard",
|
|
4854
|
-
"auth",
|
|
4855
|
-
"billing",
|
|
4856
|
-
"payment"
|
|
4857
|
-
];
|
|
4858
|
-
let score = 0;
|
|
4859
|
-
for (const indicator of complexIndicators) {
|
|
4860
|
-
if (lower.includes(indicator)) score++;
|
|
4861
|
-
}
|
|
4862
|
-
return score >= 3;
|
|
4863
|
-
}
|
|
4864
|
-
detectPackages(content) {
|
|
4865
|
-
const packages = /* @__PURE__ */ new Set();
|
|
4866
|
-
const importRegex = /import\s+.*?from\s+['"]([^'"./][^'"]*)['"]/g;
|
|
4867
|
-
let match;
|
|
4868
|
-
while ((match = importRegex.exec(content)) !== null) {
|
|
4869
|
-
let pkg = match[1];
|
|
4870
|
-
if (pkg.startsWith("@")) {
|
|
4871
|
-
pkg = pkg.split("/").slice(0, 2).join("/");
|
|
4872
|
-
} else {
|
|
4873
|
-
pkg = pkg.split("/")[0];
|
|
4874
|
-
}
|
|
4875
|
-
const builtins = ["fs", "path", "http", "https", "crypto", "stream", "events", "os", "util", "url"];
|
|
4876
|
-
if (!builtins.includes(pkg)) {
|
|
4877
|
-
packages.add(pkg);
|
|
4878
|
-
}
|
|
4879
|
-
}
|
|
4880
|
-
return Array.from(packages);
|
|
4881
|
-
}
|
|
4882
|
-
spin(text6) {
|
|
4883
|
-
if (this.spinner) {
|
|
4884
|
-
this.spinner.text = text6;
|
|
4885
|
-
} else {
|
|
4886
|
-
this.spinner = ora2(text6).start();
|
|
4887
|
-
}
|
|
4888
|
-
}
|
|
4889
|
-
stop() {
|
|
4890
|
-
if (this.spinner) {
|
|
4891
|
-
this.spinner.stop();
|
|
4892
|
-
this.spinner = null;
|
|
4893
|
-
}
|
|
4894
|
-
}
|
|
4895
|
-
logStep(message) {
|
|
4896
|
-
this.stop();
|
|
4897
|
-
console.log(colors.muted(` \u25CF ${message}`));
|
|
4898
|
-
}
|
|
4899
|
-
showSummary(result) {
|
|
4900
|
-
console.log("");
|
|
4901
|
-
console.log(" " + "\u2550".repeat(50));
|
|
4902
|
-
if (result.success) {
|
|
4903
|
-
console.log(colors.success(" \u{1F389} AUTONOMOUS BUILD COMPLETE"));
|
|
4904
|
-
} else if (result.cancelled) {
|
|
4905
|
-
console.log(colors.warning(" \u26A0\uFE0F BUILD CANCELLED"));
|
|
4906
|
-
} else if (result.paused) {
|
|
4907
|
-
console.log(colors.warning(" \u23F8\uFE0F BUILD PAUSED"));
|
|
4908
|
-
} else {
|
|
4909
|
-
console.log(colors.error(" \u274C BUILD FAILED"));
|
|
4910
|
-
}
|
|
4911
|
-
console.log("");
|
|
4912
|
-
if (result.filesCreated.length > 0) {
|
|
4913
|
-
console.log(colors.muted(` Created: ${result.filesCreated.length} files`));
|
|
4914
|
-
}
|
|
4915
|
-
if (result.filesModified.length > 0) {
|
|
4916
|
-
console.log(colors.muted(` Modified: ${result.filesModified.length} files`));
|
|
4917
|
-
}
|
|
4918
|
-
if (result.packagesInstalled.length > 0) {
|
|
4919
|
-
console.log(colors.muted(` Installed: ${result.packagesInstalled.join(", ")}`));
|
|
4920
|
-
}
|
|
4921
|
-
const seconds = (result.duration / 1e3).toFixed(1);
|
|
4922
|
-
console.log(colors.muted(` Time: ${seconds}s`));
|
|
4923
|
-
if (result.errors.length > 0) {
|
|
4924
|
-
console.log("");
|
|
4925
|
-
console.log(colors.error(" Errors:"));
|
|
4926
|
-
for (const error of result.errors) {
|
|
4927
|
-
console.log(colors.error(` \u2022 ${error}`));
|
|
4928
|
-
}
|
|
4929
|
-
}
|
|
4930
|
-
console.log("");
|
|
4931
|
-
console.log(colors.muted(" All changes saved. Use /undo to rollback."));
|
|
4932
|
-
console.log(" " + "\u2550".repeat(50));
|
|
4933
|
-
console.log("");
|
|
4934
|
-
}
|
|
4935
|
-
// ============================================================================
|
|
4936
|
-
// CONTROL
|
|
4937
|
-
// ============================================================================
|
|
4938
|
-
pause() {
|
|
4939
|
-
this.paused = true;
|
|
4940
|
-
}
|
|
4941
|
-
cancel() {
|
|
4942
|
-
this.cancelled = true;
|
|
4943
|
-
}
|
|
4944
|
-
resume() {
|
|
4945
|
-
this.paused = false;
|
|
4946
|
-
}
|
|
4947
|
-
};
|
|
4948
|
-
|
|
4949
|
-
// src/core/input-handler.ts
|
|
4950
|
-
import fs9 from "fs-extra";
|
|
4951
|
-
import path9 from "path";
|
|
4952
|
-
|
|
4953
|
-
// src/core/prd-parser.ts
|
|
4954
|
-
import Anthropic5 from "@anthropic-ai/sdk";
|
|
4955
|
-
var PRDParser = class {
|
|
4956
|
-
anthropic;
|
|
4957
|
-
memory;
|
|
4958
|
-
constructor(config, memory) {
|
|
4959
|
-
const apiKey = config.getAnthropicKey();
|
|
4960
|
-
if (!apiKey) throw new Error("API key not configured");
|
|
4961
|
-
this.anthropic = new Anthropic5({ apiKey });
|
|
4962
|
-
this.memory = memory;
|
|
4963
|
-
}
|
|
4964
|
-
// ============================================================================
|
|
4965
|
-
// PARSE PRD
|
|
4966
|
-
// ============================================================================
|
|
4967
|
-
async parse(prdContent) {
|
|
4968
|
-
const chunks = this.chunkContent(prdContent, 5e4);
|
|
4969
|
-
let analysis;
|
|
4970
|
-
if (chunks.length === 1) {
|
|
4971
|
-
analysis = await this.analyzeChunk(chunks[0], true);
|
|
4972
|
-
} else {
|
|
4973
|
-
analysis = await this.analyzeMultipleChunks(chunks);
|
|
4974
|
-
}
|
|
4975
|
-
analysis.phases = this.createPhases(analysis.features);
|
|
4976
|
-
return analysis;
|
|
4822
|
+
analysis.phases = this.createPhases(analysis.features);
|
|
4823
|
+
return analysis;
|
|
4977
4824
|
}
|
|
4978
4825
|
chunkContent(content, maxSize) {
|
|
4979
4826
|
if (content.length <= maxSize) {
|
|
@@ -5188,7 +5035,7 @@ ${chunks[i]}`,
|
|
|
5188
5035
|
projectName: analysis.projectName,
|
|
5189
5036
|
totalFeatures: analysis.features.length,
|
|
5190
5037
|
completedFeatures: analysis.features.filter((f) => f.status === "complete").length,
|
|
5191
|
-
currentPhase: analysis.phases.find((
|
|
5038
|
+
currentPhase: analysis.phases.find((p8) => p8.status === "in_progress")?.id || analysis.phases[0]?.id || "",
|
|
5192
5039
|
currentFeature: analysis.features.find((f) => f.status === "in_progress")?.id || "",
|
|
5193
5040
|
features: analysis.features.map((f) => ({ id: f.id, status: f.status })),
|
|
5194
5041
|
startedAt: Date.now(),
|
|
@@ -5394,42 +5241,197 @@ Provide the complete component code.`;
|
|
|
5394
5241
|
suggestedApproach: "URL could not be accessed"
|
|
5395
5242
|
};
|
|
5396
5243
|
}
|
|
5397
|
-
const prompt = `Analyze this website HTML to understand its structure for recreation.
|
|
5244
|
+
const prompt = `Analyze this website HTML to understand its structure for recreation.
|
|
5245
|
+
|
|
5246
|
+
URL: ${url}
|
|
5247
|
+
|
|
5248
|
+
HTML (truncated):
|
|
5249
|
+
${html.slice(0, 3e4)}
|
|
5250
|
+
|
|
5251
|
+
Respond with ONLY valid JSON:
|
|
5252
|
+
{
|
|
5253
|
+
"title": "Website/page title",
|
|
5254
|
+
"description": "What this page does",
|
|
5255
|
+
"components": ["Key UI components", "Header", "Hero section", "Pricing cards", etc],
|
|
5256
|
+
"features": ["Interactive features", "Animations", "Forms", etc],
|
|
5257
|
+
"techStack": ["Detected technologies"],
|
|
5258
|
+
"suggestedApproach": "How to recreate this step by step"
|
|
5259
|
+
}`;
|
|
5260
|
+
const response = await this.anthropic.messages.create({
|
|
5261
|
+
model: "claude-sonnet-4-20250514",
|
|
5262
|
+
max_tokens: 2048,
|
|
5263
|
+
messages: [{ role: "user", content: prompt }]
|
|
5264
|
+
});
|
|
5265
|
+
const content = response.content[0]?.type === "text" ? response.content[0].text : "";
|
|
5266
|
+
try {
|
|
5267
|
+
const jsonStr = content.replace(/```json\n?|\n?```/g, "").trim();
|
|
5268
|
+
return JSON.parse(jsonStr);
|
|
5269
|
+
} catch {
|
|
5270
|
+
return {
|
|
5271
|
+
title: "Unable to analyze",
|
|
5272
|
+
description: "",
|
|
5273
|
+
components: [],
|
|
5274
|
+
features: [],
|
|
5275
|
+
techStack: [],
|
|
5276
|
+
suggestedApproach: "Manual analysis needed"
|
|
5277
|
+
};
|
|
5278
|
+
}
|
|
5279
|
+
}
|
|
5280
|
+
// ============================================================================
|
|
5281
|
+
// WEBSITE REVIEW/AUDIT
|
|
5282
|
+
// ============================================================================
|
|
5283
|
+
async reviewWebsite(url) {
|
|
5284
|
+
const html = await this.fetchURL(url);
|
|
5285
|
+
if (!html) {
|
|
5286
|
+
return {
|
|
5287
|
+
url,
|
|
5288
|
+
score: 0,
|
|
5289
|
+
issues: [{ category: "error", severity: "error", message: "Unable to fetch URL" }],
|
|
5290
|
+
suggestions: [],
|
|
5291
|
+
seoAnalysis: null,
|
|
5292
|
+
accessibilityAnalysis: null,
|
|
5293
|
+
performanceHints: []
|
|
5294
|
+
};
|
|
5295
|
+
}
|
|
5296
|
+
const prompt = `Review this website HTML for issues, best practices, SEO, and accessibility.
|
|
5398
5297
|
|
|
5399
5298
|
URL: ${url}
|
|
5400
5299
|
|
|
5401
|
-
HTML
|
|
5402
|
-
${html.slice(0,
|
|
5300
|
+
HTML:
|
|
5301
|
+
${html.slice(0, 4e4)}
|
|
5403
5302
|
|
|
5404
|
-
|
|
5303
|
+
Analyze and respond with ONLY valid JSON:
|
|
5405
5304
|
{
|
|
5406
|
-
"
|
|
5407
|
-
"
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5305
|
+
"score": 85,
|
|
5306
|
+
"issues": [
|
|
5307
|
+
{"category": "seo", "severity": "warning", "message": "Missing meta description", "fix": "Add <meta name='description'>"},
|
|
5308
|
+
{"category": "accessibility", "severity": "error", "message": "Images missing alt text", "fix": "Add alt attributes to all images"},
|
|
5309
|
+
{"category": "performance", "severity": "warning", "message": "Large inline styles", "fix": "Move to external CSS"},
|
|
5310
|
+
{"category": "security", "severity": "error", "message": "Mixed content (http in https)", "fix": "Use https for all resources"},
|
|
5311
|
+
{"category": "html", "severity": "warning", "message": "Invalid HTML structure", "fix": "Fix nesting issues"}
|
|
5312
|
+
],
|
|
5313
|
+
"suggestions": [
|
|
5314
|
+
"Add Open Graph tags for social sharing",
|
|
5315
|
+
"Implement lazy loading for images",
|
|
5316
|
+
"Add structured data markup"
|
|
5317
|
+
],
|
|
5318
|
+
"seoAnalysis": {
|
|
5319
|
+
"title": "Page title here",
|
|
5320
|
+
"titleLength": 55,
|
|
5321
|
+
"hasMetaDescription": true,
|
|
5322
|
+
"metaDescriptionLength": 150,
|
|
5323
|
+
"hasCanonical": false,
|
|
5324
|
+
"hasOpenGraph": false,
|
|
5325
|
+
"headingStructure": "H1 > H2 > H3 (good)",
|
|
5326
|
+
"keywordDensity": "moderate"
|
|
5327
|
+
},
|
|
5328
|
+
"accessibilityAnalysis": {
|
|
5329
|
+
"hasSkipLink": false,
|
|
5330
|
+
"imagesWithAlt": 8,
|
|
5331
|
+
"imagesWithoutAlt": 3,
|
|
5332
|
+
"formLabels": "all labeled",
|
|
5333
|
+
"colorContrast": "needs review",
|
|
5334
|
+
"keyboardNavigation": "partial"
|
|
5335
|
+
},
|
|
5336
|
+
"performanceHints": [
|
|
5337
|
+
"Compress images",
|
|
5338
|
+
"Minify CSS/JS",
|
|
5339
|
+
"Enable caching headers"
|
|
5340
|
+
]
|
|
5412
5341
|
}`;
|
|
5413
5342
|
const response = await this.anthropic.messages.create({
|
|
5414
5343
|
model: "claude-sonnet-4-20250514",
|
|
5415
|
-
max_tokens:
|
|
5344
|
+
max_tokens: 4096,
|
|
5416
5345
|
messages: [{ role: "user", content: prompt }]
|
|
5417
5346
|
});
|
|
5418
5347
|
const content = response.content[0]?.type === "text" ? response.content[0].text : "";
|
|
5419
5348
|
try {
|
|
5420
5349
|
const jsonStr = content.replace(/```json\n?|\n?```/g, "").trim();
|
|
5421
|
-
return JSON.parse(jsonStr);
|
|
5350
|
+
return { url, ...JSON.parse(jsonStr) };
|
|
5422
5351
|
} catch {
|
|
5423
5352
|
return {
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5353
|
+
url,
|
|
5354
|
+
score: 0,
|
|
5355
|
+
issues: [{ category: "error", severity: "error", message: "Unable to analyze website" }],
|
|
5356
|
+
suggestions: [],
|
|
5357
|
+
seoAnalysis: null,
|
|
5358
|
+
accessibilityAnalysis: null,
|
|
5359
|
+
performanceHints: []
|
|
5430
5360
|
};
|
|
5431
5361
|
}
|
|
5432
5362
|
}
|
|
5363
|
+
formatWebsiteReview(review) {
|
|
5364
|
+
let output = "\n";
|
|
5365
|
+
output += colors.muted(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E\n");
|
|
5366
|
+
output += colors.muted(" \u2502") + colors.white(" \u{1F310} Website Review ") + colors.muted("\u2502\n");
|
|
5367
|
+
output += colors.muted(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F\n\n");
|
|
5368
|
+
output += colors.muted(` URL: ${review.url}
|
|
5369
|
+
|
|
5370
|
+
`);
|
|
5371
|
+
const scoreColor = review.score >= 80 ? colors.success : review.score >= 60 ? colors.warning : colors.error;
|
|
5372
|
+
output += colors.white(" Score: ") + scoreColor(`${review.score}/100
|
|
5373
|
+
|
|
5374
|
+
`);
|
|
5375
|
+
if (review.issues && review.issues.length > 0) {
|
|
5376
|
+
output += colors.white(" Issues Found:\n");
|
|
5377
|
+
const categories = ["error", "security", "accessibility", "seo", "performance", "html"];
|
|
5378
|
+
for (const cat of categories) {
|
|
5379
|
+
const catIssues = review.issues.filter((i) => i.category === cat || i.severity === cat);
|
|
5380
|
+
if (catIssues.length > 0) {
|
|
5381
|
+
for (const issue of catIssues.slice(0, 3)) {
|
|
5382
|
+
const icon = issue.severity === "error" ? colors.error("\u2717") : colors.warning("\u26A0");
|
|
5383
|
+
output += ` ${icon} ${colors.muted(`[${issue.category}]`)} ${issue.message}
|
|
5384
|
+
`;
|
|
5385
|
+
if (issue.fix) {
|
|
5386
|
+
output += colors.dim(` \u2192 ${issue.fix}
|
|
5387
|
+
`);
|
|
5388
|
+
}
|
|
5389
|
+
}
|
|
5390
|
+
}
|
|
5391
|
+
}
|
|
5392
|
+
output += "\n";
|
|
5393
|
+
}
|
|
5394
|
+
if (review.seoAnalysis) {
|
|
5395
|
+
output += colors.white(" SEO:\n");
|
|
5396
|
+
const seo = review.seoAnalysis;
|
|
5397
|
+
output += colors.muted(` Title: ${seo.title || "Missing"} (${seo.titleLength || 0} chars)
|
|
5398
|
+
`);
|
|
5399
|
+
output += colors.muted(` Meta Description: ${seo.hasMetaDescription ? "\u2713" : "\u2717"}
|
|
5400
|
+
`);
|
|
5401
|
+
output += colors.muted(` Open Graph: ${seo.hasOpenGraph ? "\u2713" : "\u2717"}
|
|
5402
|
+
`);
|
|
5403
|
+
output += colors.muted(` Headings: ${seo.headingStructure || "Unknown"}
|
|
5404
|
+
|
|
5405
|
+
`);
|
|
5406
|
+
}
|
|
5407
|
+
if (review.accessibilityAnalysis) {
|
|
5408
|
+
output += colors.white(" Accessibility:\n");
|
|
5409
|
+
const a11y = review.accessibilityAnalysis;
|
|
5410
|
+
output += colors.muted(` Images with alt: ${a11y.imagesWithAlt || 0}/${(a11y.imagesWithAlt || 0) + (a11y.imagesWithoutAlt || 0)}
|
|
5411
|
+
`);
|
|
5412
|
+
output += colors.muted(` Skip link: ${a11y.hasSkipLink ? "\u2713" : "\u2717"}
|
|
5413
|
+
`);
|
|
5414
|
+
output += colors.muted(` Form labels: ${a11y.formLabels || "Unknown"}
|
|
5415
|
+
|
|
5416
|
+
`);
|
|
5417
|
+
}
|
|
5418
|
+
if (review.suggestions && review.suggestions.length > 0) {
|
|
5419
|
+
output += colors.white(" Suggestions:\n");
|
|
5420
|
+
for (const suggestion of review.suggestions.slice(0, 5)) {
|
|
5421
|
+
output += colors.dim(` \u2022 ${suggestion}
|
|
5422
|
+
`);
|
|
5423
|
+
}
|
|
5424
|
+
output += "\n";
|
|
5425
|
+
}
|
|
5426
|
+
if (review.performanceHints && review.performanceHints.length > 0) {
|
|
5427
|
+
output += colors.white(" Performance Tips:\n");
|
|
5428
|
+
for (const hint of review.performanceHints.slice(0, 3)) {
|
|
5429
|
+
output += colors.dim(` \u2022 ${hint}
|
|
5430
|
+
`);
|
|
5431
|
+
}
|
|
5432
|
+
}
|
|
5433
|
+
return output;
|
|
5434
|
+
}
|
|
5433
5435
|
async buildFromURL(url, specificSection) {
|
|
5434
5436
|
const html = await this.fetchURL(url);
|
|
5435
5437
|
if (!html) {
|
|
@@ -6684,8 +6686,8 @@ var SmartGitManager = class {
|
|
|
6684
6686
|
const pr = await this.createPR(phaseBranch, baseBranch);
|
|
6685
6687
|
return pr;
|
|
6686
6688
|
}
|
|
6687
|
-
slugify(
|
|
6688
|
-
return
|
|
6689
|
+
slugify(text5) {
|
|
6690
|
+
return text5.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 30);
|
|
6689
6691
|
}
|
|
6690
6692
|
// ============================================================================
|
|
6691
6693
|
// SMART COMMITS
|
|
@@ -6964,7 +6966,7 @@ var ProjectBuilder = class {
|
|
|
6964
6966
|
totalDuration: 0
|
|
6965
6967
|
};
|
|
6966
6968
|
try {
|
|
6967
|
-
const phaseNames = spec.buildPhases.map((
|
|
6969
|
+
const phaseNames = spec.buildPhases.map((p8) => p8.name.replace(/Phase \d+:\s*/i, ""));
|
|
6968
6970
|
const strategy = await this.git.createProjectStrategy(spec.name, phaseNames);
|
|
6969
6971
|
console.log(this.git.formatStrategy(strategy));
|
|
6970
6972
|
const proceed = await p4.confirm({
|
|
@@ -7671,7 +7673,7 @@ Return ONLY the fixed code, no explanations. Keep all other code exactly the sam
|
|
|
7671
7673
|
return this.patterns;
|
|
7672
7674
|
}
|
|
7673
7675
|
enablePattern(id) {
|
|
7674
|
-
const pattern = this.patterns.find((
|
|
7676
|
+
const pattern = this.patterns.find((p8) => p8.id === id);
|
|
7675
7677
|
if (pattern) {
|
|
7676
7678
|
pattern.enabled = true;
|
|
7677
7679
|
return true;
|
|
@@ -7679,7 +7681,7 @@ Return ONLY the fixed code, no explanations. Keep all other code exactly the sam
|
|
|
7679
7681
|
return false;
|
|
7680
7682
|
}
|
|
7681
7683
|
disablePattern(id) {
|
|
7682
|
-
const pattern = this.patterns.find((
|
|
7684
|
+
const pattern = this.patterns.find((p8) => p8.id === id);
|
|
7683
7685
|
if (pattern) {
|
|
7684
7686
|
pattern.enabled = false;
|
|
7685
7687
|
return true;
|
|
@@ -7738,8 +7740,8 @@ Return ONLY the fixed code, no explanations. Keep all other code exactly the sam
|
|
|
7738
7740
|
formatPatterns() {
|
|
7739
7741
|
let output = "\n";
|
|
7740
7742
|
output += colors.white(" \u{1F4CB} Pattern Rules") + "\n\n";
|
|
7741
|
-
const enabled = this.patterns.filter((
|
|
7742
|
-
const disabled = this.patterns.filter((
|
|
7743
|
+
const enabled = this.patterns.filter((p8) => p8.enabled);
|
|
7744
|
+
const disabled = this.patterns.filter((p8) => !p8.enabled);
|
|
7743
7745
|
output += colors.success(" ENABLED") + "\n";
|
|
7744
7746
|
for (const pattern of enabled) {
|
|
7745
7747
|
const severityIcon = pattern.severity === "critical" ? "\u{1F534}" : pattern.severity === "warning" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
@@ -7764,7 +7766,7 @@ Return ONLY the fixed code, no explanations. Keep all other code exactly the sam
|
|
|
7764
7766
|
return grouped;
|
|
7765
7767
|
}
|
|
7766
7768
|
getPatternName(id) {
|
|
7767
|
-
const pattern = this.patterns.find((
|
|
7769
|
+
const pattern = this.patterns.find((p8) => p8.id === id);
|
|
7768
7770
|
return pattern?.name || id;
|
|
7769
7771
|
}
|
|
7770
7772
|
// ============================================================================
|
|
@@ -7970,7 +7972,7 @@ var SmartSuggestions = class {
|
|
|
7970
7972
|
/platform\s*(that|which|for)/,
|
|
7971
7973
|
/saas\s*(for|that|which)/
|
|
7972
7974
|
];
|
|
7973
|
-
return ideaPatterns.some((
|
|
7975
|
+
return ideaPatterns.some((p8) => p8.test(lower));
|
|
7974
7976
|
}
|
|
7975
7977
|
looksLikeComplexRequest(input) {
|
|
7976
7978
|
const lower = input.toLowerCase();
|
|
@@ -8000,7 +8002,7 @@ var SmartSuggestions = class {
|
|
|
8000
8002
|
/clean\s*up/,
|
|
8001
8003
|
/improve\s*(the\s*)?(code|quality)/
|
|
8002
8004
|
];
|
|
8003
|
-
return patterns.some((
|
|
8005
|
+
return patterns.some((p8) => p8.test(lower));
|
|
8004
8006
|
}
|
|
8005
8007
|
// ============================================================================
|
|
8006
8008
|
// DISPLAY
|
|
@@ -8863,7 +8865,7 @@ ${issues.map((i) => ` \u2022 ${i}`).join("\n")}` : "\n\nEverything looks good!"
|
|
|
8863
8865
|
/^(ugh|argh|damn|shit|fuck)/i
|
|
8864
8866
|
// Frustration words
|
|
8865
8867
|
];
|
|
8866
|
-
return frustrationPatterns.some((
|
|
8868
|
+
return frustrationPatterns.some((p8) => p8.test(input));
|
|
8867
8869
|
}
|
|
8868
8870
|
// ============================================================================
|
|
8869
8871
|
// RATE LIMITING (Don't be annoying)
|
|
@@ -9125,8 +9127,8 @@ ${chunk.content.slice(0, 2e3)}`
|
|
|
9125
9127
|
chunk.embedding = this.simpleEmbed(chunk.content + " " + chunk.summary);
|
|
9126
9128
|
}
|
|
9127
9129
|
}
|
|
9128
|
-
simpleEmbed(
|
|
9129
|
-
const words =
|
|
9130
|
+
simpleEmbed(text5) {
|
|
9131
|
+
const words = text5.toLowerCase().split(/\W+/).filter((w) => w.length > 2);
|
|
9130
9132
|
const embedding = new Array(256).fill(0);
|
|
9131
9133
|
for (const word of words) {
|
|
9132
9134
|
const hash = this.hashString(word);
|
|
@@ -9587,7 +9589,7 @@ ${featureList}`
|
|
|
9587
9589
|
"Redux": ["@reduxjs/toolkit", "redux"]
|
|
9588
9590
|
};
|
|
9589
9591
|
for (const [name, packages] of Object.entries(categories)) {
|
|
9590
|
-
if (packages.some((
|
|
9592
|
+
if (packages.some((p8) => deps.has(p8))) {
|
|
9591
9593
|
stack.push(name);
|
|
9592
9594
|
}
|
|
9593
9595
|
}
|
|
@@ -9719,279 +9721,11 @@ ${featureList}`
|
|
|
9719
9721
|
}
|
|
9720
9722
|
};
|
|
9721
9723
|
|
|
9722
|
-
// src/core/onboarding.ts
|
|
9723
|
-
import * as p6 from "@clack/prompts";
|
|
9724
|
-
import fs16 from "fs-extra";
|
|
9725
|
-
import path16 from "path";
|
|
9726
|
-
function showWelcomeScreen() {
|
|
9727
|
-
console.clear();
|
|
9728
|
-
console.log("");
|
|
9729
|
-
console.log(colors.primary(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
9730
|
-
console.log(colors.primary(" \u2502 \u2502"));
|
|
9731
|
-
console.log(colors.primary(" \u2502") + colors.white(" Welcome to CodeBakers! ") + colors.primary("\u2502"));
|
|
9732
|
-
console.log(colors.primary(" \u2502") + colors.muted(" AI Dev Team That Follows The Rules ") + colors.primary("\u2502"));
|
|
9733
|
-
console.log(colors.primary(" \u2502 \u2502"));
|
|
9734
|
-
console.log(colors.primary(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
9735
|
-
console.log("");
|
|
9736
|
-
console.log(colors.white(" Let's get you set up in 2 quick steps:"));
|
|
9737
|
-
console.log("");
|
|
9738
|
-
console.log(colors.muted(" 1. Connect your account (or use API key)"));
|
|
9739
|
-
console.log(colors.muted(" 2. Initialize your project"));
|
|
9740
|
-
console.log("");
|
|
9741
|
-
console.log(colors.dim(" Press ESC at any time to go back"));
|
|
9742
|
-
console.log("");
|
|
9743
|
-
}
|
|
9744
|
-
async function showAuthScreen() {
|
|
9745
|
-
console.clear();
|
|
9746
|
-
console.log("");
|
|
9747
|
-
console.log(colors.white(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
9748
|
-
console.log(colors.white(" \u2502") + colors.primary(" Step 1 of 2: ") + colors.white("Connect Your Account ") + colors.white("\u2502"));
|
|
9749
|
-
console.log(colors.white(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
9750
|
-
console.log("");
|
|
9751
|
-
const choice = await p6.select({
|
|
9752
|
-
message: "How would you like to connect?",
|
|
9753
|
-
options: [
|
|
9754
|
-
{
|
|
9755
|
-
value: "browser",
|
|
9756
|
-
label: "\u{1F310} Login with browser",
|
|
9757
|
-
hint: "Google or GitHub - recommended"
|
|
9758
|
-
},
|
|
9759
|
-
{
|
|
9760
|
-
value: "code",
|
|
9761
|
-
label: "\u{1F511} Enter admin/beta code",
|
|
9762
|
-
hint: "If you have a special code"
|
|
9763
|
-
},
|
|
9764
|
-
{
|
|
9765
|
-
value: "apikey",
|
|
9766
|
-
label: "\u{1F527} Use my own API key",
|
|
9767
|
-
hint: "No account needed, limited features"
|
|
9768
|
-
},
|
|
9769
|
-
{
|
|
9770
|
-
value: "skip",
|
|
9771
|
-
label: "\u23ED\uFE0F Skip for now",
|
|
9772
|
-
hint: "You can login later with /login"
|
|
9773
|
-
}
|
|
9774
|
-
]
|
|
9775
|
-
});
|
|
9776
|
-
if (p6.isCancel(choice)) {
|
|
9777
|
-
return "back";
|
|
9778
|
-
}
|
|
9779
|
-
return choice;
|
|
9780
|
-
}
|
|
9781
|
-
async function showApiKeyScreen(config) {
|
|
9782
|
-
console.clear();
|
|
9783
|
-
console.log("");
|
|
9784
|
-
console.log(colors.white(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
9785
|
-
console.log(colors.white(" \u2502") + colors.primary(" Step 1 of 2: ") + colors.white("Enter Your API Key ") + colors.white("\u2502"));
|
|
9786
|
-
console.log(colors.white(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
9787
|
-
console.log("");
|
|
9788
|
-
console.log(colors.muted(" Get your API key from: https://console.anthropic.com/"));
|
|
9789
|
-
console.log("");
|
|
9790
|
-
const apiKey = await p6.text({
|
|
9791
|
-
message: "Anthropic API Key:",
|
|
9792
|
-
placeholder: "sk-ant-api...",
|
|
9793
|
-
validate: (value) => {
|
|
9794
|
-
if (!value) return "API key is required";
|
|
9795
|
-
if (!value.startsWith("sk-ant-")) return "API key should start with sk-ant-";
|
|
9796
|
-
return void 0;
|
|
9797
|
-
}
|
|
9798
|
-
});
|
|
9799
|
-
if (p6.isCancel(apiKey)) {
|
|
9800
|
-
return "back";
|
|
9801
|
-
}
|
|
9802
|
-
config.setAnthropicKey(apiKey);
|
|
9803
|
-
console.log("");
|
|
9804
|
-
console.log(colors.success(" \u2713 API key saved"));
|
|
9805
|
-
await sleep(500);
|
|
9806
|
-
return "done";
|
|
9807
|
-
}
|
|
9808
|
-
async function showProjectScreen(memory) {
|
|
9809
|
-
console.clear();
|
|
9810
|
-
console.log("");
|
|
9811
|
-
console.log(colors.white(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
9812
|
-
console.log(colors.white(" \u2502") + colors.primary(" Step 2 of 2: ") + colors.white("Initialize Your Project ") + colors.white("\u2502"));
|
|
9813
|
-
console.log(colors.white(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
9814
|
-
console.log("");
|
|
9815
|
-
const cwd = process.cwd();
|
|
9816
|
-
const dirName = path16.basename(cwd);
|
|
9817
|
-
const hasPackageJson = await fs16.pathExists(path16.join(cwd, "package.json"));
|
|
9818
|
-
if (hasPackageJson) {
|
|
9819
|
-
console.log(colors.muted(` \u{1F4C1} Detected project: ${dirName}`));
|
|
9820
|
-
console.log("");
|
|
9821
|
-
} else {
|
|
9822
|
-
console.log(colors.muted(` \u{1F4C1} Current folder: ${dirName}`));
|
|
9823
|
-
console.log("");
|
|
9824
|
-
}
|
|
9825
|
-
const action = await p6.select({
|
|
9826
|
-
message: "What would you like to do?",
|
|
9827
|
-
options: [
|
|
9828
|
-
{
|
|
9829
|
-
value: "init",
|
|
9830
|
-
label: "\u2728 Initialize this folder as a CB project",
|
|
9831
|
-
hint: "CB will remember context about this project"
|
|
9832
|
-
},
|
|
9833
|
-
{
|
|
9834
|
-
value: "skip",
|
|
9835
|
-
label: "\u23ED\uFE0F Skip for now",
|
|
9836
|
-
hint: "You can initialize later with /init"
|
|
9837
|
-
}
|
|
9838
|
-
]
|
|
9839
|
-
});
|
|
9840
|
-
if (p6.isCancel(action)) {
|
|
9841
|
-
return "back";
|
|
9842
|
-
}
|
|
9843
|
-
if (action === "skip") {
|
|
9844
|
-
return "skip";
|
|
9845
|
-
}
|
|
9846
|
-
const projectName = await p6.text({
|
|
9847
|
-
message: "Project name:",
|
|
9848
|
-
initialValue: dirName,
|
|
9849
|
-
placeholder: dirName
|
|
9850
|
-
});
|
|
9851
|
-
if (p6.isCancel(projectName)) {
|
|
9852
|
-
return "back";
|
|
9853
|
-
}
|
|
9854
|
-
const description = await p6.text({
|
|
9855
|
-
message: "Brief description (optional):",
|
|
9856
|
-
placeholder: "A web app that..."
|
|
9857
|
-
});
|
|
9858
|
-
if (p6.isCancel(description)) {
|
|
9859
|
-
return "back";
|
|
9860
|
-
}
|
|
9861
|
-
await memory.init();
|
|
9862
|
-
memory.setIdentity(projectName, description || "");
|
|
9863
|
-
console.log("");
|
|
9864
|
-
console.log(colors.success(` \u2713 Project "${projectName}" initialized`));
|
|
9865
|
-
console.log(colors.muted(" CB will now remember context about this project"));
|
|
9866
|
-
await sleep(500);
|
|
9867
|
-
return "done";
|
|
9868
|
-
}
|
|
9869
|
-
function showCompleteScreen() {
|
|
9870
|
-
console.clear();
|
|
9871
|
-
console.log("");
|
|
9872
|
-
console.log(colors.primary(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
9873
|
-
console.log(colors.primary(" \u2502") + colors.white(" ") + colors.primary("\u2502"));
|
|
9874
|
-
console.log(colors.primary(" \u2502") + colors.success(" \u2713 You're all set! ") + colors.primary("\u2502"));
|
|
9875
|
-
console.log(colors.primary(" \u2502") + colors.white(" ") + colors.primary("\u2502"));
|
|
9876
|
-
console.log(colors.primary(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
9877
|
-
console.log("");
|
|
9878
|
-
console.log(colors.white(" Quick start:"));
|
|
9879
|
-
console.log("");
|
|
9880
|
-
console.log(colors.muted(" Just type what you want:"));
|
|
9881
|
-
console.log(colors.white(' "Add a login page"'));
|
|
9882
|
-
console.log(colors.white(' "Fix the bug in navbar"'));
|
|
9883
|
-
console.log(colors.white(' "Create an API for users"'));
|
|
9884
|
-
console.log("");
|
|
9885
|
-
console.log(colors.muted(" Or use commands:"));
|
|
9886
|
-
console.log(colors.dim(" /help ") + colors.muted("Show all commands"));
|
|
9887
|
-
console.log(colors.dim(" /audit ") + colors.muted("Check code quality"));
|
|
9888
|
-
console.log(colors.dim(" /spec ") + colors.muted("Generate a product spec"));
|
|
9889
|
-
console.log("");
|
|
9890
|
-
}
|
|
9891
|
-
async function runOnboarding(config, memory) {
|
|
9892
|
-
let currentStep = "welcome";
|
|
9893
|
-
const hasApiKey = config.isConfigured();
|
|
9894
|
-
const hasAuth = auth.isLoggedIn();
|
|
9895
|
-
const hasProject = memory.getIdentity().name !== "";
|
|
9896
|
-
if (hasAuth || hasApiKey) {
|
|
9897
|
-
if (hasProject) {
|
|
9898
|
-
return true;
|
|
9899
|
-
}
|
|
9900
|
-
currentStep = "project";
|
|
9901
|
-
}
|
|
9902
|
-
while (true) {
|
|
9903
|
-
switch (currentStep) {
|
|
9904
|
-
case "welcome": {
|
|
9905
|
-
showWelcomeScreen();
|
|
9906
|
-
const proceed = await p6.confirm({
|
|
9907
|
-
message: "Ready to begin?",
|
|
9908
|
-
initialValue: true
|
|
9909
|
-
});
|
|
9910
|
-
if (p6.isCancel(proceed) || !proceed) {
|
|
9911
|
-
return false;
|
|
9912
|
-
}
|
|
9913
|
-
currentStep = "auth";
|
|
9914
|
-
break;
|
|
9915
|
-
}
|
|
9916
|
-
case "auth": {
|
|
9917
|
-
const authChoice = await showAuthScreen();
|
|
9918
|
-
if (authChoice === "back") {
|
|
9919
|
-
currentStep = "welcome";
|
|
9920
|
-
break;
|
|
9921
|
-
}
|
|
9922
|
-
if (authChoice === "skip") {
|
|
9923
|
-
if (!config.isConfigured()) {
|
|
9924
|
-
console.log("");
|
|
9925
|
-
console.log(colors.warning(" \u26A0 You need either an account or API key to use CB"));
|
|
9926
|
-
console.log("");
|
|
9927
|
-
await sleep(1500);
|
|
9928
|
-
break;
|
|
9929
|
-
}
|
|
9930
|
-
currentStep = "project";
|
|
9931
|
-
break;
|
|
9932
|
-
}
|
|
9933
|
-
if (authChoice === "apikey") {
|
|
9934
|
-
currentStep = "api_key";
|
|
9935
|
-
break;
|
|
9936
|
-
}
|
|
9937
|
-
if (authChoice === "browser" || authChoice === "code") {
|
|
9938
|
-
const success = authChoice === "browser" ? await auth.loginWithBrowser() : await auth.loginWithCode();
|
|
9939
|
-
if (success) {
|
|
9940
|
-
currentStep = "project";
|
|
9941
|
-
}
|
|
9942
|
-
break;
|
|
9943
|
-
}
|
|
9944
|
-
break;
|
|
9945
|
-
}
|
|
9946
|
-
case "api_key": {
|
|
9947
|
-
const result = await showApiKeyScreen(config);
|
|
9948
|
-
if (result === "back") {
|
|
9949
|
-
currentStep = "auth";
|
|
9950
|
-
break;
|
|
9951
|
-
}
|
|
9952
|
-
currentStep = "project";
|
|
9953
|
-
break;
|
|
9954
|
-
}
|
|
9955
|
-
case "project": {
|
|
9956
|
-
const result = await showProjectScreen(memory);
|
|
9957
|
-
if (result === "back") {
|
|
9958
|
-
if (!config.isConfigured() && !auth.isLoggedIn()) {
|
|
9959
|
-
currentStep = "auth";
|
|
9960
|
-
}
|
|
9961
|
-
break;
|
|
9962
|
-
}
|
|
9963
|
-
currentStep = "complete";
|
|
9964
|
-
break;
|
|
9965
|
-
}
|
|
9966
|
-
case "complete": {
|
|
9967
|
-
showCompleteScreen();
|
|
9968
|
-
const ready = await p6.confirm({
|
|
9969
|
-
message: "Start using CodeBakers?",
|
|
9970
|
-
initialValue: true
|
|
9971
|
-
});
|
|
9972
|
-
if (p6.isCancel(ready)) {
|
|
9973
|
-
currentStep = "project";
|
|
9974
|
-
break;
|
|
9975
|
-
}
|
|
9976
|
-
return true;
|
|
9977
|
-
}
|
|
9978
|
-
}
|
|
9979
|
-
}
|
|
9980
|
-
}
|
|
9981
|
-
function needsOnboarding(config, memory) {
|
|
9982
|
-
const hasApiKey = config.isConfigured();
|
|
9983
|
-
const hasAuth = auth.isLoggedIn();
|
|
9984
|
-
return !hasApiKey && !hasAuth;
|
|
9985
|
-
}
|
|
9986
|
-
function sleep(ms) {
|
|
9987
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9988
|
-
}
|
|
9989
|
-
|
|
9990
9724
|
// src/utils/folder-picker.ts
|
|
9991
9725
|
import { execSync } from "child_process";
|
|
9992
|
-
import
|
|
9726
|
+
import path16 from "path";
|
|
9993
9727
|
import os2 from "os";
|
|
9994
|
-
import * as
|
|
9728
|
+
import * as p6 from "@clack/prompts";
|
|
9995
9729
|
async function pickFolder(message = "Select a folder") {
|
|
9996
9730
|
const platform = os2.platform();
|
|
9997
9731
|
try {
|
|
@@ -10084,24 +9818,24 @@ async function manualFolderEntry(message) {
|
|
|
10084
9818
|
const commonPaths = [
|
|
10085
9819
|
{ label: "\u{1F4C1} Current folder", value: process.cwd() },
|
|
10086
9820
|
{ label: "\u{1F3E0} Home", value: homeDir },
|
|
10087
|
-
{ label: "\u{1F4C2} Desktop", value:
|
|
10088
|
-
{ label: "\u{1F4C2} Documents", value:
|
|
10089
|
-
{ label: "\u{1F4C2} Downloads", value:
|
|
9821
|
+
{ label: "\u{1F4C2} Desktop", value: path16.join(homeDir, "Desktop") },
|
|
9822
|
+
{ label: "\u{1F4C2} Documents", value: path16.join(homeDir, "Documents") },
|
|
9823
|
+
{ label: "\u{1F4C2} Downloads", value: path16.join(homeDir, "Downloads") },
|
|
10090
9824
|
{ label: "\u270F\uFE0F Type custom path", value: "__custom__" }
|
|
10091
9825
|
];
|
|
10092
|
-
const choice = await
|
|
9826
|
+
const choice = await p6.select({
|
|
10093
9827
|
message,
|
|
10094
|
-
options: commonPaths.map((
|
|
10095
|
-
value:
|
|
10096
|
-
label:
|
|
10097
|
-
hint:
|
|
9828
|
+
options: commonPaths.map((p8) => ({
|
|
9829
|
+
value: p8.value,
|
|
9830
|
+
label: p8.label,
|
|
9831
|
+
hint: p8.value !== "__custom__" ? p8.value : void 0
|
|
10098
9832
|
}))
|
|
10099
9833
|
});
|
|
10100
|
-
if (
|
|
9834
|
+
if (p6.isCancel(choice)) {
|
|
10101
9835
|
return null;
|
|
10102
9836
|
}
|
|
10103
9837
|
if (choice === "__custom__") {
|
|
10104
|
-
const customPath = await
|
|
9838
|
+
const customPath = await p6.text({
|
|
10105
9839
|
message: "Enter folder path:",
|
|
10106
9840
|
placeholder: "/path/to/folder",
|
|
10107
9841
|
validate: (value) => {
|
|
@@ -10109,7 +9843,7 @@ async function manualFolderEntry(message) {
|
|
|
10109
9843
|
return void 0;
|
|
10110
9844
|
}
|
|
10111
9845
|
});
|
|
10112
|
-
if (
|
|
9846
|
+
if (p6.isCancel(customPath)) {
|
|
10113
9847
|
return null;
|
|
10114
9848
|
}
|
|
10115
9849
|
let cleanPath = customPath.trim();
|
|
@@ -10122,8 +9856,8 @@ async function manualFolderEntry(message) {
|
|
|
10122
9856
|
|
|
10123
9857
|
// src/services/git.ts
|
|
10124
9858
|
import simpleGit from "simple-git";
|
|
10125
|
-
import
|
|
10126
|
-
import
|
|
9859
|
+
import fs16 from "fs-extra";
|
|
9860
|
+
import path17 from "path";
|
|
10127
9861
|
var GitService = class {
|
|
10128
9862
|
git;
|
|
10129
9863
|
projectPath;
|
|
@@ -10132,7 +9866,7 @@ var GitService = class {
|
|
|
10132
9866
|
this.git = simpleGit(projectPath);
|
|
10133
9867
|
}
|
|
10134
9868
|
async isRepo() {
|
|
10135
|
-
return
|
|
9869
|
+
return fs16.pathExists(path17.join(this.projectPath, ".git"));
|
|
10136
9870
|
}
|
|
10137
9871
|
async init() {
|
|
10138
9872
|
await this.git.init();
|
|
@@ -10243,7 +9977,32 @@ var VercelService = class {
|
|
|
10243
9977
|
};
|
|
10244
9978
|
|
|
10245
9979
|
// src/index.ts
|
|
10246
|
-
|
|
9980
|
+
import * as readline from "readline";
|
|
9981
|
+
var VERSION = "4.3.0";
|
|
9982
|
+
async function showChatInput() {
|
|
9983
|
+
return new Promise((resolve) => {
|
|
9984
|
+
const width = Math.min(process.stdout.columns || 80, 70);
|
|
9985
|
+
const innerWidth = width - 4;
|
|
9986
|
+
console.log("");
|
|
9987
|
+
console.log(colors.primary(` \u256D${"\u2500".repeat(innerWidth)}\u256E`));
|
|
9988
|
+
const rl = readline.createInterface({
|
|
9989
|
+
input: process.stdin,
|
|
9990
|
+
output: process.stdout,
|
|
9991
|
+
terminal: true
|
|
9992
|
+
});
|
|
9993
|
+
rl.on("SIGINT", () => {
|
|
9994
|
+
rl.close();
|
|
9995
|
+
console.log(colors.primary(` \u2570${"\u2500".repeat(innerWidth)}\u256F`));
|
|
9996
|
+
resolve(null);
|
|
9997
|
+
});
|
|
9998
|
+
process.stdout.write(colors.primary(" \u2502 ") + colors.dim("\u203A "));
|
|
9999
|
+
rl.question("", (answer) => {
|
|
10000
|
+
rl.close();
|
|
10001
|
+
console.log(colors.primary(` \u2570${"\u2500".repeat(innerWidth)}\u256F`));
|
|
10002
|
+
resolve(answer);
|
|
10003
|
+
});
|
|
10004
|
+
});
|
|
10005
|
+
}
|
|
10247
10006
|
async function main() {
|
|
10248
10007
|
const config = new Config();
|
|
10249
10008
|
const args = process.argv.slice(2);
|
|
@@ -10255,76 +10014,100 @@ async function main() {
|
|
|
10255
10014
|
showHelp();
|
|
10256
10015
|
return;
|
|
10257
10016
|
}
|
|
10258
|
-
if (args[0] === "setup") {
|
|
10259
|
-
await runSetup(config);
|
|
10260
|
-
return;
|
|
10261
|
-
}
|
|
10262
10017
|
if (args[0] === "login") {
|
|
10263
10018
|
await auth.login();
|
|
10264
10019
|
return;
|
|
10265
10020
|
}
|
|
10021
|
+
if (args[0] === "logout") {
|
|
10022
|
+
auth.logout();
|
|
10023
|
+
return;
|
|
10024
|
+
}
|
|
10266
10025
|
const memory = new Memory();
|
|
10267
10026
|
await memory.init();
|
|
10268
|
-
if (needsOnboarding(config, memory)) {
|
|
10269
|
-
const completed = await runOnboarding(config, memory);
|
|
10270
|
-
if (!completed) {
|
|
10271
|
-
console.log("");
|
|
10272
|
-
console.log(colors.muted(" Run `cb` again when you're ready."));
|
|
10273
|
-
console.log("");
|
|
10274
|
-
return;
|
|
10275
|
-
}
|
|
10276
|
-
}
|
|
10277
|
-
showHeader();
|
|
10278
|
-
console.log(colors.muted(` v${VERSION}`));
|
|
10279
|
-
console.log("");
|
|
10280
10027
|
const scanner = new ProjectScanner();
|
|
10281
10028
|
const structure = await scanner.detectProject();
|
|
10282
10029
|
const editor = new FileEditor();
|
|
10283
10030
|
const git = new GitService();
|
|
10284
|
-
|
|
10285
|
-
|
|
10286
|
-
console.log(colors.white(
|
|
10287
|
-
|
|
10288
|
-
console.log(colors.
|
|
10289
|
-
console.log(
|
|
10031
|
+
if (!config.isConfigured() && !auth.isLoggedIn()) {
|
|
10032
|
+
console.log("");
|
|
10033
|
+
console.log(colors.white(" Welcome to CodeBakers"));
|
|
10034
|
+
console.log("");
|
|
10035
|
+
console.log(colors.muted(" You need an API key to continue."));
|
|
10036
|
+
console.log("");
|
|
10037
|
+
const apiKey = await p7.text({
|
|
10038
|
+
message: "Anthropic API Key:",
|
|
10039
|
+
placeholder: "sk-ant-api..."
|
|
10040
|
+
});
|
|
10041
|
+
if (p7.isCancel(apiKey) || !apiKey) {
|
|
10042
|
+
console.log("");
|
|
10043
|
+
console.log(colors.muted(" Get your key at: https://console.anthropic.com"));
|
|
10044
|
+
console.log(colors.muted(" Then run: cb"));
|
|
10045
|
+
console.log("");
|
|
10046
|
+
return;
|
|
10047
|
+
}
|
|
10048
|
+
config.setAnthropicKey(apiKey);
|
|
10049
|
+
console.log(colors.success(" \u2713 API key saved"));
|
|
10050
|
+
console.log("");
|
|
10290
10051
|
}
|
|
10291
|
-
console.log("");
|
|
10292
10052
|
let ai;
|
|
10293
10053
|
try {
|
|
10294
10054
|
ai = new AIEngine(config, memory, scanner);
|
|
10295
10055
|
} catch (error) {
|
|
10296
|
-
|
|
10056
|
+
console.log("");
|
|
10057
|
+
console.log(colors.error(` \u2717 ${error instanceof Error ? error.message : "Failed to start"}`));
|
|
10058
|
+
console.log("");
|
|
10059
|
+
console.log(colors.muted(" Check your API key with: cb login"));
|
|
10060
|
+
console.log("");
|
|
10297
10061
|
return;
|
|
10298
10062
|
}
|
|
10299
10063
|
const nlp2 = new NLPInterpreter();
|
|
10300
10064
|
const modeManager = getModeManager();
|
|
10301
10065
|
const suggestions = new SmartSuggestions(memory, scanner);
|
|
10302
10066
|
const proactive = new ProactiveAssistant(memory, scanner, config);
|
|
10303
|
-
console.
|
|
10304
|
-
console.log(
|
|
10067
|
+
console.clear();
|
|
10068
|
+
console.log("");
|
|
10069
|
+
console.log(colors.muted(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
|
|
10070
|
+
console.log(colors.muted(" \u2502") + colors.primary(" \u2B21 C O D E B A K E R S ") + colors.muted("\u2502"));
|
|
10071
|
+
console.log(colors.muted(" \u2502") + colors.dim(` v${VERSION} \u2022 58 patterns \u2022 auto-parallel `) + colors.muted("\u2502"));
|
|
10072
|
+
console.log(colors.muted(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
|
|
10073
|
+
console.log("");
|
|
10074
|
+
console.log(colors.muted(" \u{1F4C1} ") + colors.white(structure.name) + (structure.framework ? colors.dim(` (${structure.framework})`) : ""));
|
|
10075
|
+
console.log("");
|
|
10076
|
+
console.log(colors.dim(" Just tell me what you want to build or do."));
|
|
10077
|
+
console.log(colors.dim(" Type /help to see examples."));
|
|
10305
10078
|
console.log("");
|
|
10306
10079
|
let lastAction;
|
|
10307
10080
|
let running = true;
|
|
10308
10081
|
while (running) {
|
|
10309
|
-
const
|
|
10310
|
-
|
|
10311
|
-
message: modePrompt,
|
|
10312
|
-
placeholder: "What would you like to build? (Shift+Tab to change mode)"
|
|
10313
|
-
});
|
|
10314
|
-
if (p8.isCancel(input)) {
|
|
10082
|
+
const input = await showChatInput();
|
|
10083
|
+
if (input === null) {
|
|
10315
10084
|
running = false;
|
|
10316
|
-
|
|
10085
|
+
console.log("");
|
|
10086
|
+
console.log(colors.muted(" Goodbye! \u{1F44B}"));
|
|
10087
|
+
console.log("");
|
|
10317
10088
|
break;
|
|
10318
10089
|
}
|
|
10319
10090
|
const cmd = input.trim();
|
|
10320
10091
|
if (!cmd) continue;
|
|
10321
|
-
const
|
|
10322
|
-
if (
|
|
10323
|
-
console.log(suggestions.formatSuggestion(
|
|
10092
|
+
const smartSuggestion = await suggestions.checkForSuggestions(cmd, lastAction);
|
|
10093
|
+
if (smartSuggestion) {
|
|
10094
|
+
console.log(suggestions.formatSuggestion(smartSuggestion));
|
|
10324
10095
|
}
|
|
10325
10096
|
const result = await nlp2.interpret(cmd);
|
|
10326
10097
|
if (result.type === "unclear") {
|
|
10327
|
-
|
|
10098
|
+
console.log("");
|
|
10099
|
+
console.log(colors.muted(" I'm not sure what you want to do."));
|
|
10100
|
+
console.log("");
|
|
10101
|
+
const cmdSuggestions = getSuggestions(cmd);
|
|
10102
|
+
if (cmdSuggestions.length > 0) {
|
|
10103
|
+
console.log(colors.muted(" Did you mean:"));
|
|
10104
|
+
cmdSuggestions.forEach((s) => {
|
|
10105
|
+
console.log(colors.dim(` \u2022 ${s}`));
|
|
10106
|
+
});
|
|
10107
|
+
console.log("");
|
|
10108
|
+
}
|
|
10109
|
+
console.log(colors.dim(" Type /help for a list of commands"));
|
|
10110
|
+
console.log("");
|
|
10328
10111
|
continue;
|
|
10329
10112
|
}
|
|
10330
10113
|
if (["undo", "clear", "deploy"].includes(result.type)) {
|
|
@@ -10335,7 +10118,9 @@ async function main() {
|
|
|
10335
10118
|
switch (result.type) {
|
|
10336
10119
|
case "quit":
|
|
10337
10120
|
running = false;
|
|
10338
|
-
|
|
10121
|
+
console.log("");
|
|
10122
|
+
console.log(colors.muted(" Goodbye! \u{1F44B}"));
|
|
10123
|
+
console.log("");
|
|
10339
10124
|
break;
|
|
10340
10125
|
case "help":
|
|
10341
10126
|
showCommandHelp();
|
|
@@ -10368,9 +10153,13 @@ async function main() {
|
|
|
10368
10153
|
await memory.save();
|
|
10369
10154
|
showSuccess(`Decision recorded: ${result.params.content}`);
|
|
10370
10155
|
} else {
|
|
10371
|
-
|
|
10372
|
-
|
|
10373
|
-
|
|
10156
|
+
const decision = await p7.text({
|
|
10157
|
+
message: "What decision would you like to record?",
|
|
10158
|
+
placeholder: "e.g., Use Supabase for auth"
|
|
10159
|
+
});
|
|
10160
|
+
if (p7.isCancel(decision)) {
|
|
10161
|
+
console.log(colors.muted(" Cancelled"));
|
|
10162
|
+
} else if (decision) {
|
|
10374
10163
|
memory.addDecision(decision);
|
|
10375
10164
|
await memory.save();
|
|
10376
10165
|
showSuccess(`Decision recorded: ${decision}`);
|
|
@@ -10383,9 +10172,13 @@ async function main() {
|
|
|
10383
10172
|
await memory.save();
|
|
10384
10173
|
showSuccess(`Rule added: ${result.params.content}`);
|
|
10385
10174
|
} else {
|
|
10386
|
-
|
|
10387
|
-
|
|
10388
|
-
|
|
10175
|
+
const rule = await p7.text({
|
|
10176
|
+
message: "What rule would you like to add?",
|
|
10177
|
+
placeholder: "e.g., Always use TypeScript strict mode"
|
|
10178
|
+
});
|
|
10179
|
+
if (p7.isCancel(rule)) {
|
|
10180
|
+
console.log(colors.muted(" Cancelled"));
|
|
10181
|
+
} else if (rule) {
|
|
10389
10182
|
memory.addCustomRule(rule);
|
|
10390
10183
|
await memory.save();
|
|
10391
10184
|
showSuccess(`Rule added: ${rule}`);
|
|
@@ -10401,8 +10194,10 @@ async function main() {
|
|
|
10401
10194
|
if (result.params?.command) {
|
|
10402
10195
|
await handleRunCommand(result.params.command, ctx);
|
|
10403
10196
|
} else {
|
|
10404
|
-
|
|
10405
|
-
|
|
10197
|
+
console.log("");
|
|
10198
|
+
console.log(colors.muted(" Usage: /run <command>"));
|
|
10199
|
+
console.log(colors.dim(" Example: /run npm install lodash"));
|
|
10200
|
+
console.log("");
|
|
10406
10201
|
}
|
|
10407
10202
|
break;
|
|
10408
10203
|
case "create":
|
|
@@ -10435,6 +10230,9 @@ async function main() {
|
|
|
10435
10230
|
case "audit":
|
|
10436
10231
|
await handleAudit(result.params?.args, ctx);
|
|
10437
10232
|
break;
|
|
10233
|
+
case "website-review":
|
|
10234
|
+
await handleWebsiteReview(cmd, ctx);
|
|
10235
|
+
break;
|
|
10438
10236
|
case "patterns":
|
|
10439
10237
|
await handlePatterns(result.params?.args, ctx);
|
|
10440
10238
|
break;
|
|
@@ -10458,68 +10256,167 @@ async function main() {
|
|
|
10458
10256
|
const inputHandler = new InputHandler(config, memory, scanner);
|
|
10459
10257
|
const detected = await inputHandler.detectAndProcess(cmd);
|
|
10460
10258
|
if (detected.type !== "text") {
|
|
10461
|
-
console.log(inputHandler.formatDetection(detected));
|
|
10462
|
-
const proceed = await
|
|
10463
|
-
message: `Process this ${detected.type}?`,
|
|
10464
|
-
initialValue: true
|
|
10465
|
-
});
|
|
10466
|
-
if (!proceed ||
|
|
10467
|
-
|
|
10468
|
-
|
|
10469
|
-
break;
|
|
10470
|
-
}
|
|
10471
|
-
const currentMode = getModeManager().getMode();
|
|
10472
|
-
if (currentMode === "plan") {
|
|
10473
|
-
await handlePlanMode(cmd, ctx);
|
|
10474
|
-
} else if (currentMode === "question") {
|
|
10475
|
-
await handleQuestionMode(cmd, ctx);
|
|
10476
|
-
} else if (currentMode === "agent") {
|
|
10477
|
-
await handleAgentMode(cmd, ctx);
|
|
10478
|
-
} else {
|
|
10479
|
-
const isComplex = detectComplexRequest(cmd);
|
|
10480
|
-
if (isComplex) {
|
|
10481
|
-
console.log("");
|
|
10482
|
-
console.log(colors.secondary(" \u{1F4A1} This looks like a complex request."));
|
|
10483
|
-
console.log(colors.muted(" Parallel build could be ~3x faster."));
|
|
10484
|
-
console.log("");
|
|
10485
|
-
const useParallel = await p8.confirm({
|
|
10486
|
-
message: "Use parallel build with 3 agents?",
|
|
10487
|
-
initialValue: true
|
|
10488
|
-
});
|
|
10489
|
-
if (useParallel && !p8.isCancel(useParallel)) {
|
|
10490
|
-
await handleParallelBuild(cmd, ctx);
|
|
10491
|
-
break;
|
|
10492
|
-
}
|
|
10493
|
-
}
|
|
10494
|
-
const templateManager = new TemplateManager();
|
|
10495
|
-
const suggestedTemplate = templateManager.suggestTemplate(cmd);
|
|
10496
|
-
if (suggestedTemplate) {
|
|
10497
|
-
console.log("");
|
|
10498
|
-
console.log(colors.secondary(` \u{1F4A1} I recommend the "${suggestedTemplate.name}" template.`));
|
|
10499
|
-
console.log(colors.muted(` ${suggestedTemplate.description}`));
|
|
10500
|
-
console.log("");
|
|
10501
|
-
const useTemplate = await p8.confirm({
|
|
10502
|
-
message: "Install this template first?",
|
|
10503
|
-
initialValue: true
|
|
10504
|
-
});
|
|
10505
|
-
if (useTemplate && !p8.isCancel(useTemplate)) {
|
|
10506
|
-
await handleInstallTemplate(suggestedTemplate.id, ctx);
|
|
10507
|
-
showSuccess("Template installed! Now customizing...");
|
|
10508
|
-
}
|
|
10509
|
-
}
|
|
10510
|
-
await handleGeneration(cmd, ctx);
|
|
10511
|
-
const filesCreated = editor.getPendingChanges().length;
|
|
10512
|
-
const followUp = await proactive.onAfterAction(cmd, filesCreated);
|
|
10513
|
-
if (followUp) {
|
|
10514
|
-
console.log(proactive.formatAction(followUp));
|
|
10515
|
-
proactive.markSuggestionShown();
|
|
10259
|
+
console.log(inputHandler.formatDetection(detected));
|
|
10260
|
+
const proceed = await p7.confirm({
|
|
10261
|
+
message: `Process this ${detected.type}?`,
|
|
10262
|
+
initialValue: true
|
|
10263
|
+
});
|
|
10264
|
+
if (!proceed || p7.isCancel(proceed)) {
|
|
10265
|
+
console.log(colors.muted(" Cancelled"));
|
|
10266
|
+
break;
|
|
10516
10267
|
}
|
|
10268
|
+
const enhancedCmd = inputHandler.enhancePrompt(detected, cmd);
|
|
10269
|
+
await handleAutomaticBuild(enhancedCmd, ctx);
|
|
10270
|
+
break;
|
|
10517
10271
|
}
|
|
10272
|
+
await handleAutomaticBuild(cmd, ctx);
|
|
10273
|
+
lastAction = cmd;
|
|
10274
|
+
break;
|
|
10518
10275
|
break;
|
|
10519
10276
|
}
|
|
10520
10277
|
proactive.updateLastActivity();
|
|
10521
10278
|
}
|
|
10522
10279
|
}
|
|
10280
|
+
async function handleAutomaticBuild(request, ctx) {
|
|
10281
|
+
const { config, memory, scanner, editor, ai } = ctx;
|
|
10282
|
+
memory.addMessage("user", request);
|
|
10283
|
+
memory.setActiveTask(request);
|
|
10284
|
+
await memory.createSnapshot(`Before: ${request.slice(0, 50)}`);
|
|
10285
|
+
const tasks = analyzeAndSplitTasks(request);
|
|
10286
|
+
if (tasks.length > 1) {
|
|
10287
|
+
console.log("");
|
|
10288
|
+
console.log(colors.muted(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
|
|
10289
|
+
console.log(colors.muted(" \u2502") + colors.primary(" \u26A1 Parallel Build ") + colors.muted("\u2502"));
|
|
10290
|
+
console.log(colors.muted(" \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"));
|
|
10291
|
+
tasks.forEach((task, i) => {
|
|
10292
|
+
const taskText = task.slice(0, 43);
|
|
10293
|
+
console.log(colors.muted(" \u2502") + colors.dim(` ${i + 1}. ${taskText}`.padEnd(47)) + colors.muted("\u2502"));
|
|
10294
|
+
});
|
|
10295
|
+
console.log(colors.muted(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
|
|
10296
|
+
console.log("");
|
|
10297
|
+
const hasParallel = await auth.checkFeature(FEATURES.parallel);
|
|
10298
|
+
if (hasParallel) {
|
|
10299
|
+
await runParallelBuild(tasks, ctx);
|
|
10300
|
+
} else {
|
|
10301
|
+
console.log(colors.dim(" Running sequentially (parallel requires Pro)..."));
|
|
10302
|
+
console.log("");
|
|
10303
|
+
for (const task of tasks) {
|
|
10304
|
+
await handleGeneration(task, ctx);
|
|
10305
|
+
}
|
|
10306
|
+
}
|
|
10307
|
+
} else {
|
|
10308
|
+
await handleGeneration(request, ctx);
|
|
10309
|
+
}
|
|
10310
|
+
}
|
|
10311
|
+
function analyzeAndSplitTasks(request) {
|
|
10312
|
+
const lowered = request.toLowerCase();
|
|
10313
|
+
const multiIndicators = [
|
|
10314
|
+
/\band\b/i,
|
|
10315
|
+
// "login and dashboard"
|
|
10316
|
+
/\+/,
|
|
10317
|
+
// "login + dashboard"
|
|
10318
|
+
/,\s*(?:and\s+)?/,
|
|
10319
|
+
// "login, dashboard, settings"
|
|
10320
|
+
/\bthen\b/i,
|
|
10321
|
+
// "login then dashboard"
|
|
10322
|
+
/\balso\b/i,
|
|
10323
|
+
// "also add settings"
|
|
10324
|
+
/\bplus\b/i
|
|
10325
|
+
// "plus a settings page"
|
|
10326
|
+
];
|
|
10327
|
+
const hasMultiIndicator = multiIndicators.some((r) => r.test(request));
|
|
10328
|
+
if (!hasMultiIndicator) {
|
|
10329
|
+
return [request];
|
|
10330
|
+
}
|
|
10331
|
+
let tasks = request.split(/(?:\band\b|\+|,\s*(?:and\s+)?|\bthen\b|\balso\b|\bplus\b)/i).map((t) => t.trim()).filter((t) => t.length > 3);
|
|
10332
|
+
if (tasks.length <= 1) {
|
|
10333
|
+
return [request];
|
|
10334
|
+
}
|
|
10335
|
+
if (tasks.length > 4) {
|
|
10336
|
+
const first3 = tasks.slice(0, 3);
|
|
10337
|
+
const rest = tasks.slice(3).join(" and ");
|
|
10338
|
+
tasks = [...first3, rest];
|
|
10339
|
+
}
|
|
10340
|
+
return tasks;
|
|
10341
|
+
}
|
|
10342
|
+
async function runParallelBuild(tasks, ctx) {
|
|
10343
|
+
const { config, memory, scanner, editor } = ctx;
|
|
10344
|
+
if (!await auth.checkFeature(FEATURES.parallel)) {
|
|
10345
|
+
return;
|
|
10346
|
+
}
|
|
10347
|
+
const parallelSystem = new ParallelAgentSystem(config, memory, scanner, editor);
|
|
10348
|
+
let cancelled = false;
|
|
10349
|
+
const checkCancelled = () => cancelled;
|
|
10350
|
+
const onKeypress = (key) => {
|
|
10351
|
+
const str = key.toString();
|
|
10352
|
+
if (str === "\x1B" || str === "\x1B") {
|
|
10353
|
+
cancelled = true;
|
|
10354
|
+
parallelSystem.cancel();
|
|
10355
|
+
}
|
|
10356
|
+
};
|
|
10357
|
+
if (process.stdin.isTTY) {
|
|
10358
|
+
process.stdin.setRawMode(true);
|
|
10359
|
+
}
|
|
10360
|
+
process.stdin.resume();
|
|
10361
|
+
process.stdin.on("data", onKeypress);
|
|
10362
|
+
console.log(colors.muted(" Press ESC to cancel"));
|
|
10363
|
+
console.log("");
|
|
10364
|
+
try {
|
|
10365
|
+
const startTime = Date.now();
|
|
10366
|
+
const combinedRequest = tasks.join(" AND ");
|
|
10367
|
+
const result = await parallelSystem.build(
|
|
10368
|
+
combinedRequest,
|
|
10369
|
+
{
|
|
10370
|
+
onTaskPlan: (plannedTasks) => {
|
|
10371
|
+
console.log(colors.muted(` Planned ${plannedTasks.length} parallel tasks`));
|
|
10372
|
+
},
|
|
10373
|
+
onAgentStart: (taskId, name) => {
|
|
10374
|
+
console.log(colors.dim(` \u2192 Starting: ${name}`));
|
|
10375
|
+
},
|
|
10376
|
+
onAgentComplete: (taskId, agentResult) => {
|
|
10377
|
+
const status = agentResult.success ? colors.success("\u2713") : colors.error("\u2717");
|
|
10378
|
+
console.log(` ${status} ${taskId}: ${agentResult.files.length} files`);
|
|
10379
|
+
}
|
|
10380
|
+
},
|
|
10381
|
+
checkCancelled
|
|
10382
|
+
);
|
|
10383
|
+
process.stdin.removeListener("data", onKeypress);
|
|
10384
|
+
if (process.stdin.isTTY) {
|
|
10385
|
+
process.stdin.setRawMode(false);
|
|
10386
|
+
}
|
|
10387
|
+
const duration = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
10388
|
+
console.log("");
|
|
10389
|
+
if (result.cancelled) {
|
|
10390
|
+
console.log(colors.muted(" Cancelled"));
|
|
10391
|
+
} else if (result.success) {
|
|
10392
|
+
console.log(colors.success(` \u2713 Completed in ${duration}s`));
|
|
10393
|
+
console.log(colors.muted(` Files: ${result.totalFiles}`));
|
|
10394
|
+
if (result.mergeConflicts.length > 0) {
|
|
10395
|
+
console.log(colors.warning(` Conflicts: ${result.mergeConflicts.length}`));
|
|
10396
|
+
}
|
|
10397
|
+
} else {
|
|
10398
|
+
console.log(colors.error(" Build failed"));
|
|
10399
|
+
result.results.forEach((r) => {
|
|
10400
|
+
if (!r.success && r.errors.length > 0) {
|
|
10401
|
+
r.errors.forEach((e) => console.log(colors.dim(` \u2022 ${e}`)));
|
|
10402
|
+
}
|
|
10403
|
+
});
|
|
10404
|
+
}
|
|
10405
|
+
console.log("");
|
|
10406
|
+
} catch (error) {
|
|
10407
|
+
process.stdin.removeListener("data", onKeypress);
|
|
10408
|
+
if (process.stdin.isTTY) {
|
|
10409
|
+
process.stdin.setRawMode(false);
|
|
10410
|
+
}
|
|
10411
|
+
if (cancelled) {
|
|
10412
|
+
console.log("");
|
|
10413
|
+
console.log(colors.muted(" Cancelled"));
|
|
10414
|
+
console.log("");
|
|
10415
|
+
return;
|
|
10416
|
+
}
|
|
10417
|
+
showError(error instanceof Error ? error.message : "Parallel build failed");
|
|
10418
|
+
}
|
|
10419
|
+
}
|
|
10523
10420
|
async function handleGeneration(request, ctx) {
|
|
10524
10421
|
const { memory, editor, ai, scanner } = ctx;
|
|
10525
10422
|
memory.addMessage("user", request);
|
|
@@ -10559,31 +10456,46 @@ async function handleGeneration(request, ctx) {
|
|
|
10559
10456
|
return;
|
|
10560
10457
|
}
|
|
10561
10458
|
console.log("");
|
|
10459
|
+
console.log(colors.muted(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
|
|
10562
10460
|
if (result.filesCreated.length > 0) {
|
|
10563
|
-
console.log(colors.success(`
|
|
10564
|
-
for (const f of result.filesCreated) {
|
|
10565
|
-
console.log(colors.muted(`
|
|
10461
|
+
console.log(colors.muted(" \u2502") + colors.success(` \u2713 Created ${result.filesCreated.length} file(s)`.padEnd(47)) + colors.muted("\u2502"));
|
|
10462
|
+
for (const f of result.filesCreated.slice(0, 5)) {
|
|
10463
|
+
console.log(colors.muted(" \u2502") + colors.dim(` + ${f}`.padEnd(47)) + colors.muted("\u2502"));
|
|
10464
|
+
}
|
|
10465
|
+
if (result.filesCreated.length > 5) {
|
|
10466
|
+
console.log(colors.muted(" \u2502") + colors.dim(` ... and ${result.filesCreated.length - 5} more`.padEnd(47)) + colors.muted("\u2502"));
|
|
10566
10467
|
}
|
|
10567
10468
|
}
|
|
10568
10469
|
if (result.filesModified.length > 0) {
|
|
10569
|
-
console.log(colors.
|
|
10570
|
-
for (const f of result.filesModified) {
|
|
10571
|
-
console.log(colors.muted(`
|
|
10470
|
+
console.log(colors.muted(" \u2502") + colors.warning(` ~ Modified ${result.filesModified.length} file(s)`.padEnd(47)) + colors.muted("\u2502"));
|
|
10471
|
+
for (const f of result.filesModified.slice(0, 5)) {
|
|
10472
|
+
console.log(colors.muted(" \u2502") + colors.dim(` ~ ${f}`.padEnd(47)) + colors.muted("\u2502"));
|
|
10473
|
+
}
|
|
10474
|
+
if (result.filesModified.length > 5) {
|
|
10475
|
+
console.log(colors.muted(" \u2502") + colors.dim(` ... and ${result.filesModified.length - 5} more`.padEnd(47)) + colors.muted("\u2502"));
|
|
10572
10476
|
}
|
|
10573
10477
|
}
|
|
10574
10478
|
if (result.commandsRun.length > 0) {
|
|
10575
|
-
console.log(colors.muted(
|
|
10479
|
+
console.log(colors.muted(" \u2502") + colors.dim(` \u26A1 Ran: ${result.commandsRun.join(", ")}`.padEnd(47)) + colors.muted("\u2502"));
|
|
10576
10480
|
}
|
|
10577
10481
|
if (result.errors.length > 0) {
|
|
10578
|
-
console.log("");
|
|
10579
|
-
|
|
10580
|
-
|
|
10581
|
-
console.log(colors.error(` \u2022 ${e}`));
|
|
10482
|
+
console.log(colors.muted(" \u2502") + colors.error(` \u2717 ${result.errors.length} error(s)`.padEnd(47)) + colors.muted("\u2502"));
|
|
10483
|
+
for (const e of result.errors.slice(0, 3)) {
|
|
10484
|
+
console.log(colors.muted(" \u2502") + colors.dim(` ${e.slice(0, 43)}`.padEnd(47)) + colors.muted("\u2502"));
|
|
10582
10485
|
}
|
|
10583
10486
|
}
|
|
10487
|
+
console.log(colors.muted(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
|
|
10584
10488
|
if (result.success) {
|
|
10585
10489
|
console.log("");
|
|
10586
|
-
|
|
10490
|
+
console.log(colors.success(" \u2713 Done!"));
|
|
10491
|
+
const totalFiles = result.filesCreated.length + result.filesModified.length;
|
|
10492
|
+
if (totalFiles > 0) {
|
|
10493
|
+
console.log("");
|
|
10494
|
+
console.log(colors.dim(" What's next?"));
|
|
10495
|
+
console.log(colors.dim(' \u2022 "Check my code for issues"'));
|
|
10496
|
+
console.log(colors.dim(' \u2022 "Generate tests"'));
|
|
10497
|
+
console.log(colors.dim(' \u2022 "Commit my changes"'));
|
|
10498
|
+
}
|
|
10587
10499
|
}
|
|
10588
10500
|
console.log("");
|
|
10589
10501
|
memory.addMessage("assistant", `Created: ${result.filesCreated.join(", ")}. Modified: ${result.filesModified.join(", ")}`);
|
|
@@ -10641,7 +10553,7 @@ async function handleCreate(name, ctx) {
|
|
|
10641
10553
|
const { config } = ctx;
|
|
10642
10554
|
let projectName = name;
|
|
10643
10555
|
if (!projectName) {
|
|
10644
|
-
const nameInput = await
|
|
10556
|
+
const nameInput = await p7.text({
|
|
10645
10557
|
message: "Project name:",
|
|
10646
10558
|
placeholder: "my-awesome-app",
|
|
10647
10559
|
validate: (value) => {
|
|
@@ -10649,23 +10561,23 @@ async function handleCreate(name, ctx) {
|
|
|
10649
10561
|
if (!/^[a-z0-9-]+$/.test(value)) return "Use lowercase letters, numbers, and hyphens only";
|
|
10650
10562
|
}
|
|
10651
10563
|
});
|
|
10652
|
-
if (
|
|
10564
|
+
if (p7.isCancel(nameInput)) return;
|
|
10653
10565
|
projectName = nameInput;
|
|
10654
10566
|
}
|
|
10655
10567
|
console.log("");
|
|
10656
|
-
const github = await
|
|
10568
|
+
const github = await p7.confirm({
|
|
10657
10569
|
message: "Create GitHub repository?",
|
|
10658
10570
|
initialValue: !!config.getGithubToken()
|
|
10659
10571
|
});
|
|
10660
|
-
const supabase = await
|
|
10572
|
+
const supabase = await p7.confirm({
|
|
10661
10573
|
message: "Create Supabase project?",
|
|
10662
10574
|
initialValue: !!config.getSupabaseToken()
|
|
10663
10575
|
});
|
|
10664
|
-
const vercel = await
|
|
10576
|
+
const vercel = await p7.confirm({
|
|
10665
10577
|
message: "Deploy to Vercel?",
|
|
10666
10578
|
initialValue: !!config.getVercelToken()
|
|
10667
10579
|
});
|
|
10668
|
-
if (
|
|
10580
|
+
if (p7.isCancel(github) || p7.isCancel(supabase) || p7.isCancel(vercel)) return;
|
|
10669
10581
|
const creator = new ProjectCreator(config);
|
|
10670
10582
|
const spinner = ora3("Creating project...").start();
|
|
10671
10583
|
const result = await creator.create(
|
|
@@ -10726,14 +10638,17 @@ async function handleInstallTemplate(templateId, ctx) {
|
|
|
10726
10638
|
let id = templateId;
|
|
10727
10639
|
if (!id) {
|
|
10728
10640
|
const templates = templateManager.listTemplates();
|
|
10729
|
-
const choice = await
|
|
10641
|
+
const choice = await p7.select({
|
|
10730
10642
|
message: "Choose a template:",
|
|
10731
10643
|
options: templates.map((t) => ({
|
|
10732
10644
|
value: t.id,
|
|
10733
10645
|
label: `${t.name} - ${t.description}`
|
|
10734
10646
|
}))
|
|
10735
10647
|
});
|
|
10736
|
-
if (
|
|
10648
|
+
if (p7.isCancel(choice)) {
|
|
10649
|
+
console.log(colors.muted(" Cancelled"));
|
|
10650
|
+
return;
|
|
10651
|
+
}
|
|
10737
10652
|
id = choice;
|
|
10738
10653
|
}
|
|
10739
10654
|
const spinner = ora3(`Installing ${id}...`).start();
|
|
@@ -10809,11 +10724,11 @@ async function handleParallelBuild(request, ctx) {
|
|
|
10809
10724
|
console.log(colors.muted(` + ${change.path}`));
|
|
10810
10725
|
}
|
|
10811
10726
|
console.log("");
|
|
10812
|
-
const apply = await
|
|
10727
|
+
const apply = await p7.confirm({
|
|
10813
10728
|
message: "Apply all files?",
|
|
10814
10729
|
initialValue: true
|
|
10815
10730
|
});
|
|
10816
|
-
if (apply && !
|
|
10731
|
+
if (apply && !p7.isCancel(apply)) {
|
|
10817
10732
|
const applied = await editor.apply();
|
|
10818
10733
|
showSuccess(`Applied ${applied.length} files`);
|
|
10819
10734
|
for (const file of applied) {
|
|
@@ -10839,124 +10754,18 @@ async function handleParallelBuild(request, ctx) {
|
|
|
10839
10754
|
}
|
|
10840
10755
|
console.log("");
|
|
10841
10756
|
}
|
|
10842
|
-
function detectComplexRequest(request) {
|
|
10843
|
-
const lower = request.toLowerCase();
|
|
10844
|
-
const features = [
|
|
10845
|
-
"auth",
|
|
10846
|
-
"login",
|
|
10847
|
-
"signup",
|
|
10848
|
-
"authentication",
|
|
10849
|
-
"dashboard",
|
|
10850
|
-
"admin",
|
|
10851
|
-
"settings",
|
|
10852
|
-
"profile",
|
|
10853
|
-
"billing",
|
|
10854
|
-
"payment",
|
|
10855
|
-
"stripe",
|
|
10856
|
-
"subscription",
|
|
10857
|
-
"analytics",
|
|
10858
|
-
"charts",
|
|
10859
|
-
"users",
|
|
10860
|
-
"user management",
|
|
10861
|
-
"api",
|
|
10862
|
-
"backend",
|
|
10863
|
-
"database",
|
|
10864
|
-
"crud"
|
|
10865
|
-
];
|
|
10866
|
-
let featureCount = 0;
|
|
10867
|
-
for (const feature of features) {
|
|
10868
|
-
if (lower.includes(feature)) {
|
|
10869
|
-
featureCount++;
|
|
10870
|
-
}
|
|
10871
|
-
}
|
|
10872
|
-
const hasMultiple = lower.includes(" and ") || lower.includes(" with ") || lower.includes(", ") || lower.includes(" + ");
|
|
10873
|
-
const scopeKeywords = ["full", "complete", "entire", "whole", "saas", "app", "application", "system"];
|
|
10874
|
-
const hasScope = scopeKeywords.some((k) => lower.includes(k));
|
|
10875
|
-
return featureCount >= 3 || featureCount >= 2 && hasMultiple || hasScope && featureCount >= 2;
|
|
10876
|
-
}
|
|
10877
|
-
async function handlePlanMode(request, ctx) {
|
|
10878
|
-
const { config, memory, scanner } = ctx;
|
|
10879
|
-
console.log("");
|
|
10880
|
-
console.log(colors.primary(" \u{1F4CB} PLAN MODE") + colors.muted(" - Showing what will happen"));
|
|
10881
|
-
console.log("");
|
|
10882
|
-
const planExecutor = new PlanExecutor(config, memory, scanner);
|
|
10883
|
-
const spinner = ora3("Creating plan...").start();
|
|
10884
|
-
try {
|
|
10885
|
-
const plan = await planExecutor.createPlan(request);
|
|
10886
|
-
spinner.stop();
|
|
10887
|
-
console.log(planExecutor.formatPlan(plan));
|
|
10888
|
-
const execute = await p8.confirm({
|
|
10889
|
-
message: "Execute this plan?",
|
|
10890
|
-
initialValue: true
|
|
10891
|
-
});
|
|
10892
|
-
if (execute && !p8.isCancel(execute)) {
|
|
10893
|
-
await handleGeneration(request, ctx);
|
|
10894
|
-
}
|
|
10895
|
-
} catch (error) {
|
|
10896
|
-
spinner.stop();
|
|
10897
|
-
showError(error instanceof Error ? error.message : "Plan failed");
|
|
10898
|
-
}
|
|
10899
|
-
}
|
|
10900
|
-
async function handleQuestionMode(request, ctx) {
|
|
10901
|
-
const { config, memory, scanner } = ctx;
|
|
10902
|
-
const questionExecutor = new QuestionExecutor(config, memory, scanner);
|
|
10903
|
-
try {
|
|
10904
|
-
const questions = await questionExecutor.generateQuestions(request);
|
|
10905
|
-
const result = await questionExecutor.askQuestions(questions);
|
|
10906
|
-
if (Object.keys(result.answers).length > 0) {
|
|
10907
|
-
questionExecutor.showSummary(result);
|
|
10908
|
-
const enhancedRequest = request + questionExecutor.formatAnswersForPrompt(result);
|
|
10909
|
-
await handleGeneration(enhancedRequest, ctx);
|
|
10910
|
-
} else {
|
|
10911
|
-
await handleGeneration(request, ctx);
|
|
10912
|
-
}
|
|
10913
|
-
} catch (error) {
|
|
10914
|
-
showError(error instanceof Error ? error.message : "Question mode failed");
|
|
10915
|
-
}
|
|
10916
|
-
}
|
|
10917
|
-
async function handleAgentMode(request, ctx) {
|
|
10918
|
-
const { config, ai, memory, scanner, editor } = ctx;
|
|
10919
|
-
await memory.createSnapshot(`Before agent: ${request.slice(0, 50)}`);
|
|
10920
|
-
let cancelled = false;
|
|
10921
|
-
const checkCancelled = () => cancelled;
|
|
10922
|
-
const onKeypress = (key) => {
|
|
10923
|
-
const str = key.toString();
|
|
10924
|
-
if (str === "\x1B" || str === "\x1B") {
|
|
10925
|
-
cancelled = true;
|
|
10926
|
-
}
|
|
10927
|
-
};
|
|
10928
|
-
if (process.stdin.isTTY) {
|
|
10929
|
-
process.stdin.setRawMode(true);
|
|
10930
|
-
}
|
|
10931
|
-
process.stdin.resume();
|
|
10932
|
-
process.stdin.on("data", onKeypress);
|
|
10933
|
-
const agentExecutor = new AgentExecutor(config, ai, memory, scanner, editor);
|
|
10934
|
-
try {
|
|
10935
|
-
const result = await agentExecutor.execute(request, { checkCancelled });
|
|
10936
|
-
process.stdin.removeListener("data", onKeypress);
|
|
10937
|
-
if (process.stdin.isTTY) {
|
|
10938
|
-
process.stdin.setRawMode(false);
|
|
10939
|
-
}
|
|
10940
|
-
if (result.cancelled) {
|
|
10941
|
-
showWarning("Cancelled.");
|
|
10942
|
-
}
|
|
10943
|
-
} catch (error) {
|
|
10944
|
-
process.stdin.removeListener("data", onKeypress);
|
|
10945
|
-
if (process.stdin.isTTY) {
|
|
10946
|
-
process.stdin.setRawMode(false);
|
|
10947
|
-
}
|
|
10948
|
-
showError(error instanceof Error ? error.message : "Agent mode failed");
|
|
10949
|
-
}
|
|
10950
|
-
}
|
|
10951
10757
|
async function handleTestGeneration(filePath, ctx) {
|
|
10952
10758
|
const { config, scanner, editor } = ctx;
|
|
10953
10759
|
const testGen = new TestGenerator(config, scanner);
|
|
10954
10760
|
if (!filePath) {
|
|
10955
|
-
const file = await
|
|
10761
|
+
const file = await p7.text({
|
|
10956
10762
|
message: "File to generate tests for:",
|
|
10957
10763
|
placeholder: "src/components/Button.tsx"
|
|
10958
10764
|
});
|
|
10959
|
-
if (
|
|
10765
|
+
if (p7.isCancel(file) || !file) {
|
|
10766
|
+
console.log(colors.muted(" Cancelled"));
|
|
10767
|
+
return;
|
|
10768
|
+
}
|
|
10960
10769
|
filePath = file;
|
|
10961
10770
|
}
|
|
10962
10771
|
const spinner = ora3(`Generating tests for ${filePath}...`).start();
|
|
@@ -10973,13 +10782,17 @@ async function handleTestGeneration(filePath, ctx) {
|
|
|
10973
10782
|
console.log(colors.muted(" " + test.content.split("\n").slice(0, 10).join("\n ")));
|
|
10974
10783
|
console.log(colors.muted(" ..."));
|
|
10975
10784
|
console.log("");
|
|
10976
|
-
const save = await
|
|
10785
|
+
const save = await p7.confirm({
|
|
10977
10786
|
message: `Save to ${test.testPath}?`,
|
|
10978
10787
|
initialValue: true
|
|
10979
10788
|
});
|
|
10980
|
-
if (
|
|
10789
|
+
if (p7.isCancel(save)) {
|
|
10790
|
+
console.log(colors.muted(" Test not saved"));
|
|
10791
|
+
} else if (save) {
|
|
10981
10792
|
await testGen.writeTest(test);
|
|
10982
10793
|
showSuccess(`Test saved to ${test.testPath}`);
|
|
10794
|
+
} else {
|
|
10795
|
+
console.log(colors.muted(" Test not saved"));
|
|
10983
10796
|
}
|
|
10984
10797
|
} catch (error) {
|
|
10985
10798
|
spinner.stop();
|
|
@@ -11015,11 +10828,11 @@ async function handleLivePreview(ctx) {
|
|
|
11015
10828
|
const { scanner } = ctx;
|
|
11016
10829
|
const preview = new LivePreview(scanner);
|
|
11017
10830
|
if (preview.isRunning()) {
|
|
11018
|
-
const stop = await
|
|
10831
|
+
const stop = await p7.confirm({
|
|
11019
10832
|
message: "Preview is running. Stop it?",
|
|
11020
10833
|
initialValue: false
|
|
11021
10834
|
});
|
|
11022
|
-
if (stop && !
|
|
10835
|
+
if (stop && !p7.isCancel(stop)) {
|
|
11023
10836
|
await preview.stop();
|
|
11024
10837
|
showSuccess("Preview stopped");
|
|
11025
10838
|
} else {
|
|
@@ -11047,28 +10860,38 @@ async function handleSpecGeneration(input, ctx) {
|
|
|
11047
10860
|
return;
|
|
11048
10861
|
}
|
|
11049
10862
|
const { config, memory, scanner, editor, ai } = ctx;
|
|
10863
|
+
console.log("");
|
|
10864
|
+
console.log(colors.white(" \u{1F4DD} Spec Generator"));
|
|
10865
|
+
console.log(colors.muted(" \u2500".repeat(35)));
|
|
10866
|
+
console.log("");
|
|
11050
10867
|
let description = input.replace(/^\/spec\s*/i, "").replace(/^\/prd\s*/i, "").replace(/^\/plan\s*/i, "").trim();
|
|
11051
10868
|
if (!description) {
|
|
11052
|
-
const desc = await
|
|
10869
|
+
const desc = await p7.text({
|
|
11053
10870
|
message: "What do you want to build?",
|
|
11054
10871
|
placeholder: "An Uber for dog walkers"
|
|
11055
10872
|
});
|
|
11056
|
-
if (
|
|
10873
|
+
if (p7.isCancel(desc) || !desc) {
|
|
10874
|
+
console.log(colors.muted(" Spec generation cancelled"));
|
|
10875
|
+
console.log("");
|
|
10876
|
+
return;
|
|
10877
|
+
}
|
|
11057
10878
|
description = desc;
|
|
11058
10879
|
}
|
|
11059
10880
|
const specGen = new SpecGenerator(config);
|
|
11060
10881
|
try {
|
|
11061
10882
|
const spec = await specGen.generate(description);
|
|
11062
10883
|
if (!spec) {
|
|
11063
|
-
|
|
10884
|
+
console.log("");
|
|
10885
|
+
console.log(colors.muted(" Spec generation cancelled"));
|
|
10886
|
+
console.log("");
|
|
11064
10887
|
return;
|
|
11065
10888
|
}
|
|
11066
10889
|
console.log(specGen.formatSpec(spec));
|
|
11067
|
-
const buildNow = await
|
|
10890
|
+
const buildNow = await p7.confirm({
|
|
11068
10891
|
message: "Build from this spec?",
|
|
11069
10892
|
initialValue: true
|
|
11070
10893
|
});
|
|
11071
|
-
if (buildNow && !
|
|
10894
|
+
if (buildNow && !p7.isCancel(buildNow)) {
|
|
11072
10895
|
const builder = new ProjectBuilder(config, memory, scanner, editor, ai);
|
|
11073
10896
|
let cancelled = false;
|
|
11074
10897
|
const checkCancelled = () => cancelled;
|
|
@@ -11195,30 +11018,47 @@ async function handleAudit(args, ctx) {
|
|
|
11195
11018
|
const auditor = new CodebaseAuditor(config);
|
|
11196
11019
|
const git = new SmartGitManager(config);
|
|
11197
11020
|
const subcommand = args?.split(" ")[0] || "";
|
|
11021
|
+
console.log("");
|
|
11022
|
+
console.log(colors.white(" \u{1F50D} Codebase Audit"));
|
|
11023
|
+
console.log(colors.muted(" \u2500".repeat(35)));
|
|
11198
11024
|
switch (subcommand) {
|
|
11199
|
-
case "fix":
|
|
11025
|
+
case "fix":
|
|
11026
|
+
case "--fix": {
|
|
11027
|
+
console.log("");
|
|
11200
11028
|
const spinner = ora3("Scanning codebase...").start();
|
|
11201
11029
|
const report = await auditor.audit((file, current, total) => {
|
|
11202
11030
|
spinner.text = `Scanning ${current}/${total}: ${file}`;
|
|
11203
11031
|
});
|
|
11204
|
-
spinner.
|
|
11032
|
+
spinner.succeed("Scan complete");
|
|
11205
11033
|
console.log(auditor.formatReport(report));
|
|
11206
11034
|
if (report.totalIssues === 0) {
|
|
11207
|
-
|
|
11035
|
+
console.log("");
|
|
11036
|
+
showSuccess("No issues found! Your code is clean.");
|
|
11037
|
+
console.log("");
|
|
11038
|
+
console.log(colors.muted(" What you can do next:"));
|
|
11039
|
+
console.log(colors.dim(" \u2022 /review Get a full codebase review"));
|
|
11040
|
+
console.log(colors.dim(" \u2022 /spec Generate a product spec"));
|
|
11041
|
+
console.log("");
|
|
11208
11042
|
return;
|
|
11209
11043
|
}
|
|
11210
11044
|
const fixType = args?.includes("critical") ? "critical" : args?.includes("all") ? "all" : null;
|
|
11211
11045
|
let issuesToFix = report.critical;
|
|
11212
11046
|
if (!fixType) {
|
|
11213
|
-
const choice = await
|
|
11047
|
+
const choice = await p7.select({
|
|
11214
11048
|
message: "What would you like to fix?",
|
|
11215
11049
|
options: [
|
|
11216
11050
|
{ value: "critical", label: `Critical issues only (${report.critical.length})` },
|
|
11217
11051
|
{ value: "warnings", label: `Critical + Warnings (${report.critical.length + report.warnings.length})` },
|
|
11218
|
-
{ value: "all", label: `Everything (${report.totalIssues})` }
|
|
11052
|
+
{ value: "all", label: `Everything (${report.totalIssues})` },
|
|
11053
|
+
{ value: "cancel", label: "\u2190 Back", hint: "Fix later" }
|
|
11219
11054
|
]
|
|
11220
11055
|
});
|
|
11221
|
-
if (
|
|
11056
|
+
if (p7.isCancel(choice) || choice === "cancel") {
|
|
11057
|
+
console.log("");
|
|
11058
|
+
console.log(colors.muted(" Auto-fix cancelled. You can run /audit fix anytime."));
|
|
11059
|
+
console.log("");
|
|
11060
|
+
return;
|
|
11061
|
+
}
|
|
11222
11062
|
if (choice === "warnings") {
|
|
11223
11063
|
issuesToFix = [...report.critical, ...report.warnings];
|
|
11224
11064
|
} else if (choice === "all") {
|
|
@@ -11249,11 +11089,11 @@ async function handleAudit(args, ctx) {
|
|
|
11249
11089
|
const files = result.changes.map((c) => c.file);
|
|
11250
11090
|
await git.commitFiles(files, "fix: resolve codebase audit issues");
|
|
11251
11091
|
console.log("");
|
|
11252
|
-
const push = await
|
|
11092
|
+
const push = await p7.confirm({
|
|
11253
11093
|
message: "Push and create PR?",
|
|
11254
11094
|
initialValue: true
|
|
11255
11095
|
});
|
|
11256
|
-
if (push && !
|
|
11096
|
+
if (push && !p7.isCancel(push)) {
|
|
11257
11097
|
await git.push();
|
|
11258
11098
|
const pr = await git.createPR(branchName, await git.getMainBranch(), "Fix: Codebase Audit Issues");
|
|
11259
11099
|
if (pr?.url) {
|
|
@@ -11271,25 +11111,69 @@ async function handleAudit(args, ctx) {
|
|
|
11271
11111
|
spinner.stop();
|
|
11272
11112
|
const markdown = auditor.exportMarkdown(report);
|
|
11273
11113
|
const reportPath = "AUDIT_REPORT.md";
|
|
11274
|
-
await
|
|
11114
|
+
await fs17.writeFile(reportPath, markdown);
|
|
11275
11115
|
console.log(auditor.formatReport(report));
|
|
11276
11116
|
showSuccess(`Report saved to ${reportPath}`);
|
|
11277
11117
|
break;
|
|
11278
11118
|
}
|
|
11279
11119
|
default: {
|
|
11120
|
+
console.log("");
|
|
11280
11121
|
const spinner = ora3("Scanning codebase...").start();
|
|
11281
11122
|
const report = await auditor.audit((file, current, total) => {
|
|
11282
11123
|
spinner.text = `Scanning ${current}/${total}: ${file}`;
|
|
11283
11124
|
});
|
|
11284
|
-
spinner.
|
|
11125
|
+
spinner.succeed("Scan complete");
|
|
11285
11126
|
console.log(auditor.formatReport(report));
|
|
11286
|
-
if (report.totalIssues
|
|
11287
|
-
console.log(
|
|
11288
|
-
|
|
11127
|
+
if (report.totalIssues === 0) {
|
|
11128
|
+
console.log("");
|
|
11129
|
+
showSuccess("No issues found! Your code is clean.");
|
|
11130
|
+
console.log("");
|
|
11131
|
+
} else {
|
|
11132
|
+
console.log("");
|
|
11133
|
+
console.log(colors.muted(" What you can do:"));
|
|
11134
|
+
console.log(colors.dim(" /audit fix Auto-fix issues"));
|
|
11135
|
+
console.log(colors.dim(" /audit report Export markdown report"));
|
|
11136
|
+
console.log("");
|
|
11289
11137
|
}
|
|
11290
11138
|
}
|
|
11291
11139
|
}
|
|
11292
11140
|
}
|
|
11141
|
+
async function handleWebsiteReview(input, ctx) {
|
|
11142
|
+
const { config } = ctx;
|
|
11143
|
+
const urlMatch = input.match(/(https?:\/\/[^\s]+)|(www\.[^\s]+)/i);
|
|
11144
|
+
let url = urlMatch ? urlMatch[0] : "";
|
|
11145
|
+
if (!url) {
|
|
11146
|
+
const urlInput = await p7.text({
|
|
11147
|
+
message: "Website URL to review:",
|
|
11148
|
+
placeholder: "https://example.com"
|
|
11149
|
+
});
|
|
11150
|
+
if (p7.isCancel(urlInput) || !urlInput) {
|
|
11151
|
+
console.log(colors.muted(" Cancelled"));
|
|
11152
|
+
return;
|
|
11153
|
+
}
|
|
11154
|
+
url = urlInput;
|
|
11155
|
+
}
|
|
11156
|
+
if (!url.startsWith("http")) {
|
|
11157
|
+
url = "https://" + url;
|
|
11158
|
+
}
|
|
11159
|
+
console.log("");
|
|
11160
|
+
const spinner = ora3(`Reviewing ${url}...`).start();
|
|
11161
|
+
try {
|
|
11162
|
+
const visualHandler = new VisualInputHandler(config);
|
|
11163
|
+
const review = await visualHandler.reviewWebsite(url);
|
|
11164
|
+
spinner.stop();
|
|
11165
|
+
console.log(visualHandler.formatWebsiteReview(review));
|
|
11166
|
+
console.log("");
|
|
11167
|
+
console.log(colors.muted(" What you can do:"));
|
|
11168
|
+
console.log(colors.dim(' "Build a better version of this"'));
|
|
11169
|
+
console.log(colors.dim(' "Fix the accessibility issues"'));
|
|
11170
|
+
console.log(colors.dim(' "Create an SEO-optimized version"'));
|
|
11171
|
+
console.log("");
|
|
11172
|
+
} catch (error) {
|
|
11173
|
+
spinner.stop();
|
|
11174
|
+
showError(error instanceof Error ? error.message : "Failed to review website");
|
|
11175
|
+
}
|
|
11176
|
+
}
|
|
11293
11177
|
async function handlePatterns(args, ctx) {
|
|
11294
11178
|
const { config } = ctx;
|
|
11295
11179
|
const auditor = new CodebaseAuditor(config);
|
|
@@ -11375,7 +11259,7 @@ async function handleFolderChange() {
|
|
|
11375
11259
|
console.log(colors.muted(" Cancelled"));
|
|
11376
11260
|
return;
|
|
11377
11261
|
}
|
|
11378
|
-
if (!await
|
|
11262
|
+
if (!await fs17.pathExists(folder)) {
|
|
11379
11263
|
showError(`Folder not found: ${folder}`);
|
|
11380
11264
|
return;
|
|
11381
11265
|
}
|
|
@@ -11393,17 +11277,29 @@ async function initProject(ctx) {
|
|
|
11393
11277
|
const { memory, scanner } = ctx;
|
|
11394
11278
|
const structure = await scanner.detectProject();
|
|
11395
11279
|
console.log("");
|
|
11396
|
-
|
|
11280
|
+
console.log(colors.white(" \u{1F4C1} Initialize Project"));
|
|
11281
|
+
console.log(colors.muted(" \u2500".repeat(35)));
|
|
11282
|
+
console.log("");
|
|
11283
|
+
const name = await p7.text({
|
|
11397
11284
|
message: "Project name:",
|
|
11398
|
-
initialValue: structure.name
|
|
11285
|
+
initialValue: structure.name,
|
|
11286
|
+
placeholder: structure.name
|
|
11399
11287
|
});
|
|
11400
|
-
if (
|
|
11401
|
-
|
|
11402
|
-
|
|
11288
|
+
if (p7.isCancel(name)) {
|
|
11289
|
+
console.log(colors.muted(" Cancelled"));
|
|
11290
|
+
console.log("");
|
|
11291
|
+
return;
|
|
11292
|
+
}
|
|
11293
|
+
const description = await p7.text({
|
|
11294
|
+
message: "Description (optional):",
|
|
11403
11295
|
placeholder: "A brief description of your project"
|
|
11404
11296
|
});
|
|
11405
|
-
if (
|
|
11406
|
-
|
|
11297
|
+
if (p7.isCancel(description)) {
|
|
11298
|
+
console.log(colors.muted(" Cancelled"));
|
|
11299
|
+
console.log("");
|
|
11300
|
+
return;
|
|
11301
|
+
}
|
|
11302
|
+
const stack = await p7.text({
|
|
11407
11303
|
message: "Tech stack (comma-separated):",
|
|
11408
11304
|
initialValue: [
|
|
11409
11305
|
structure.framework,
|
|
@@ -11412,7 +11308,11 @@ async function initProject(ctx) {
|
|
|
11412
11308
|
"Tailwind"
|
|
11413
11309
|
].filter(Boolean).join(", ")
|
|
11414
11310
|
});
|
|
11415
|
-
if (
|
|
11311
|
+
if (p7.isCancel(stack)) {
|
|
11312
|
+
console.log(colors.muted(" Cancelled"));
|
|
11313
|
+
console.log("");
|
|
11314
|
+
return;
|
|
11315
|
+
}
|
|
11416
11316
|
memory.setIdentity(
|
|
11417
11317
|
name,
|
|
11418
11318
|
description,
|
|
@@ -11455,11 +11355,11 @@ async function undoChanges(ctx, steps) {
|
|
|
11455
11355
|
const snapshot = snapshots[targetIndex];
|
|
11456
11356
|
console.log("");
|
|
11457
11357
|
console.log(colors.white(` Rolling back to: ${snapshot.description}`));
|
|
11458
|
-
const
|
|
11358
|
+
const confirm7 = await p7.confirm({
|
|
11459
11359
|
message: "Proceed with rollback?",
|
|
11460
11360
|
initialValue: false
|
|
11461
11361
|
});
|
|
11462
|
-
if (
|
|
11362
|
+
if (confirm7 && !p7.isCancel(confirm7)) {
|
|
11463
11363
|
const restored = await memory.rollback(snapshot.id);
|
|
11464
11364
|
showSuccess(`Restored ${restored.length} file${restored.length > 1 ? "s" : ""}`);
|
|
11465
11365
|
}
|
|
@@ -11467,11 +11367,11 @@ async function undoChanges(ctx, steps) {
|
|
|
11467
11367
|
async function commitChanges(ctx, message) {
|
|
11468
11368
|
const { git, memory } = ctx;
|
|
11469
11369
|
if (!await git.isRepo()) {
|
|
11470
|
-
const init = await
|
|
11370
|
+
const init = await p7.confirm({
|
|
11471
11371
|
message: "No git repo found. Initialize one?",
|
|
11472
11372
|
initialValue: true
|
|
11473
11373
|
});
|
|
11474
|
-
if (init && !
|
|
11374
|
+
if (init && !p7.isCancel(init)) {
|
|
11475
11375
|
await git.init();
|
|
11476
11376
|
showSuccess("Git repository initialized");
|
|
11477
11377
|
} else {
|
|
@@ -11496,13 +11396,13 @@ async function deployProject(ctx) {
|
|
|
11496
11396
|
showError("Vercel CLI not installed. Run: npm i -g vercel");
|
|
11497
11397
|
return;
|
|
11498
11398
|
}
|
|
11499
|
-
const prod = await
|
|
11399
|
+
const prod = await p7.confirm({
|
|
11500
11400
|
message: "Deploy to production?",
|
|
11501
11401
|
initialValue: false
|
|
11502
11402
|
});
|
|
11503
11403
|
const spinner = ora3("Deploying...").start();
|
|
11504
11404
|
try {
|
|
11505
|
-
const result = await vercel.deploy(process.cwd(), prod && !
|
|
11405
|
+
const result = await vercel.deploy(process.cwd(), prod && !p7.isCancel(prod));
|
|
11506
11406
|
spinner.stop();
|
|
11507
11407
|
showSuccess(`Deployed: ${result.url}`);
|
|
11508
11408
|
} catch (error) {
|
|
@@ -11511,78 +11411,108 @@ async function deployProject(ctx) {
|
|
|
11511
11411
|
}
|
|
11512
11412
|
}
|
|
11513
11413
|
async function reviewCodebase(ctx) {
|
|
11514
|
-
const { ai } = ctx;
|
|
11515
|
-
|
|
11414
|
+
const { ai, memory } = ctx;
|
|
11415
|
+
console.log("");
|
|
11416
|
+
console.log(colors.white(" \u{1F4CA} Codebase Review"));
|
|
11417
|
+
console.log(colors.muted(" \u2500".repeat(35)));
|
|
11418
|
+
console.log("");
|
|
11419
|
+
const includeAI = await p7.confirm({
|
|
11516
11420
|
message: "Include AI deep analysis? (slower but more thorough)",
|
|
11517
11421
|
initialValue: true
|
|
11518
11422
|
});
|
|
11519
|
-
if (
|
|
11423
|
+
if (p7.isCancel(includeAI)) {
|
|
11424
|
+
console.log(colors.muted(" Review cancelled"));
|
|
11425
|
+
console.log("");
|
|
11426
|
+
return;
|
|
11427
|
+
}
|
|
11520
11428
|
const analyzer = new CodebaseAnalyzer(
|
|
11521
11429
|
process.cwd(),
|
|
11522
11430
|
includeAI ? ai : void 0
|
|
11523
11431
|
);
|
|
11524
|
-
|
|
11432
|
+
console.log("");
|
|
11433
|
+
const spinner = ora3({
|
|
11434
|
+
text: "Starting codebase review...",
|
|
11435
|
+
spinner: "dots"
|
|
11436
|
+
}).start();
|
|
11525
11437
|
try {
|
|
11526
11438
|
const report = await analyzer.fullReview((step, progress) => {
|
|
11527
11439
|
spinner.text = `${step} (${Math.round(progress)}%)`;
|
|
11528
11440
|
});
|
|
11529
|
-
spinner.
|
|
11441
|
+
spinner.succeed("Review complete!");
|
|
11442
|
+
console.log("");
|
|
11530
11443
|
const formatted = analyzer.formatReport(report);
|
|
11531
11444
|
console.log(formatted);
|
|
11532
|
-
|
|
11533
|
-
|
|
11534
|
-
|
|
11535
|
-
|
|
11445
|
+
console.log(colors.muted(" \u2500".repeat(35)));
|
|
11446
|
+
console.log("");
|
|
11447
|
+
const hasIssues = report.summary.totalViolations > 0 || report.summary.totalIssues > 0;
|
|
11448
|
+
if (!hasIssues) {
|
|
11449
|
+
console.log(colors.success(" \u2713 Your codebase looks healthy!"));
|
|
11450
|
+
console.log("");
|
|
11451
|
+
console.log(colors.muted(" What you can do next:"));
|
|
11452
|
+
console.log(colors.dim(" \u2022 /audit Run a deeper audit with auto-fix"));
|
|
11453
|
+
console.log(colors.dim(" \u2022 /spec Generate a product spec"));
|
|
11454
|
+
console.log(colors.dim(" \u2022 /git Check your git status"));
|
|
11455
|
+
console.log("");
|
|
11456
|
+
} else {
|
|
11457
|
+
console.log(colors.warning(` Found ${report.summary.totalViolations + report.summary.totalIssues} issues to address`));
|
|
11458
|
+
console.log("");
|
|
11459
|
+
const action = await p7.select({
|
|
11460
|
+
message: "What would you like to do?",
|
|
11461
|
+
options: [
|
|
11462
|
+
{
|
|
11463
|
+
value: "fix",
|
|
11464
|
+
label: "\u{1F527} Auto-fix pattern violations",
|
|
11465
|
+
hint: `Fix ${report.byCategory.patterns.length} violations`
|
|
11466
|
+
},
|
|
11467
|
+
{
|
|
11468
|
+
value: "audit",
|
|
11469
|
+
label: "\u{1F50D} Run detailed audit",
|
|
11470
|
+
hint: "Get line-by-line fixes"
|
|
11471
|
+
},
|
|
11472
|
+
{
|
|
11473
|
+
value: "export",
|
|
11474
|
+
label: "\u{1F4C4} Export report",
|
|
11475
|
+
hint: "Save to file"
|
|
11476
|
+
},
|
|
11477
|
+
{
|
|
11478
|
+
value: "skip",
|
|
11479
|
+
label: "\u2190 Back to main",
|
|
11480
|
+
hint: "Address later"
|
|
11481
|
+
}
|
|
11482
|
+
]
|
|
11536
11483
|
});
|
|
11537
|
-
if (
|
|
11538
|
-
|
|
11484
|
+
if (p7.isCancel(action) || action === "skip") {
|
|
11485
|
+
console.log("");
|
|
11486
|
+
console.log(colors.muted(" You can run /audit anytime to fix issues"));
|
|
11487
|
+
console.log("");
|
|
11488
|
+
return;
|
|
11489
|
+
}
|
|
11490
|
+
if (action === "fix") {
|
|
11491
|
+
console.log("");
|
|
11492
|
+
console.log(colors.muted(" Starting auto-fix..."));
|
|
11493
|
+
await handleAudit("--fix", ctx);
|
|
11494
|
+
} else if (action === "audit") {
|
|
11495
|
+
await handleAudit("", ctx);
|
|
11496
|
+
} else if (action === "export") {
|
|
11497
|
+
const reportPath = path18.join(process.cwd(), "codebase-review.md");
|
|
11498
|
+
await fs17.writeFile(reportPath, formatted.replace(/\x1b\[[0-9;]*m/g, ""));
|
|
11499
|
+
console.log("");
|
|
11500
|
+
showSuccess(`Report saved to ${reportPath}`);
|
|
11539
11501
|
}
|
|
11540
11502
|
}
|
|
11503
|
+
memory.addDecision(`Codebase reviewed: ${report.summary.healthScore}/100 health score, ${report.summary.totalViolations} violations`);
|
|
11504
|
+
await memory.save();
|
|
11541
11505
|
} catch (error) {
|
|
11542
|
-
spinner.
|
|
11543
|
-
|
|
11544
|
-
|
|
11545
|
-
|
|
11546
|
-
|
|
11547
|
-
|
|
11548
|
-
|
|
11549
|
-
|
|
11550
|
-
|
|
11551
|
-
message: "Anthropic API key:",
|
|
11552
|
-
placeholder: "sk-ant-...",
|
|
11553
|
-
validate: (value) => {
|
|
11554
|
-
if (!value) return "Required";
|
|
11555
|
-
if (!value.startsWith("sk-ant-")) return "Invalid format";
|
|
11556
|
-
}
|
|
11557
|
-
});
|
|
11558
|
-
if (p8.isCancel(apiKey)) return;
|
|
11559
|
-
config.setAnthropicKey(apiKey);
|
|
11560
|
-
const setupGithub = await p8.confirm({
|
|
11561
|
-
message: "Set up GitHub token? (optional)",
|
|
11562
|
-
initialValue: false
|
|
11563
|
-
});
|
|
11564
|
-
if (setupGithub && !p8.isCancel(setupGithub)) {
|
|
11565
|
-
const token = await p8.text({
|
|
11566
|
-
message: "GitHub token:",
|
|
11567
|
-
placeholder: "ghp_..."
|
|
11568
|
-
});
|
|
11569
|
-
if (!p8.isCancel(token) && token) {
|
|
11570
|
-
config.setGithubToken(token);
|
|
11571
|
-
}
|
|
11572
|
-
}
|
|
11573
|
-
const setupVercel = await p8.confirm({
|
|
11574
|
-
message: "Set up Vercel token? (optional)",
|
|
11575
|
-
initialValue: false
|
|
11576
|
-
});
|
|
11577
|
-
if (setupVercel && !p8.isCancel(setupVercel)) {
|
|
11578
|
-
const token = await p8.text({
|
|
11579
|
-
message: "Vercel token:"
|
|
11580
|
-
});
|
|
11581
|
-
if (!p8.isCancel(token) && token) {
|
|
11582
|
-
config.setVercelToken(token);
|
|
11583
|
-
}
|
|
11506
|
+
spinner.fail("Review failed");
|
|
11507
|
+
console.log("");
|
|
11508
|
+
showError(error instanceof Error ? error.message : "Unknown error");
|
|
11509
|
+
console.log("");
|
|
11510
|
+
console.log(colors.muted(" Try:"));
|
|
11511
|
+
console.log(colors.dim(" \u2022 Check if you're in a project directory"));
|
|
11512
|
+
console.log(colors.dim(" \u2022 Run /status to verify your setup"));
|
|
11513
|
+
console.log(colors.dim(' \u2022 Run without AI: answer "no" to deep analysis'));
|
|
11514
|
+
console.log("");
|
|
11584
11515
|
}
|
|
11585
|
-
showSuccess("Setup complete!");
|
|
11586
11516
|
}
|
|
11587
11517
|
function showHelp() {
|
|
11588
11518
|
console.log(`
|
|
@@ -11602,33 +11532,78 @@ function showHelp() {
|
|
|
11602
11532
|
`);
|
|
11603
11533
|
}
|
|
11604
11534
|
function showCommandHelp() {
|
|
11605
|
-
console.log(
|
|
11606
|
-
|
|
11607
|
-
|
|
11608
|
-
|
|
11609
|
-
|
|
11610
|
-
|
|
11611
|
-
|
|
11612
|
-
|
|
11613
|
-
|
|
11614
|
-
|
|
11615
|
-
|
|
11616
|
-
|
|
11617
|
-
|
|
11618
|
-
|
|
11619
|
-
|
|
11620
|
-
|
|
11621
|
-
|
|
11622
|
-
|
|
11623
|
-
|
|
11624
|
-
|
|
11625
|
-
|
|
11626
|
-
|
|
11627
|
-
|
|
11628
|
-
|
|
11629
|
-
|
|
11630
|
-
|
|
11631
|
-
|
|
11632
|
-
|
|
11535
|
+
console.log("");
|
|
11536
|
+
console.log(colors.muted(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
|
|
11537
|
+
console.log(colors.muted(" \u2502") + colors.white(" Just tell me what you want ") + colors.muted("\u2502"));
|
|
11538
|
+
console.log(colors.muted(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
|
|
11539
|
+
console.log("");
|
|
11540
|
+
console.log(colors.primary(" Build & Create"));
|
|
11541
|
+
console.log(colors.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
11542
|
+
console.log(colors.white(' "Add a login page with Google auth"'));
|
|
11543
|
+
console.log(colors.white(' "Build login, dashboard, and settings"'));
|
|
11544
|
+
console.log(colors.white(' "I want to build an Uber for dog walking"'));
|
|
11545
|
+
console.log("");
|
|
11546
|
+
console.log(colors.primary(" Review & Fix"));
|
|
11547
|
+
console.log(colors.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
11548
|
+
console.log(colors.white(' "Check my code for issues"'));
|
|
11549
|
+
console.log(colors.white(` "What's wrong with this project?"`));
|
|
11550
|
+
console.log(colors.white(' "Fix the problems you find"'));
|
|
11551
|
+
console.log("");
|
|
11552
|
+
console.log(colors.primary(" Git & Deploy"));
|
|
11553
|
+
console.log(colors.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
11554
|
+
console.log(colors.white(' "Commit my changes"'));
|
|
11555
|
+
console.log(colors.white(' "Push to GitHub"'));
|
|
11556
|
+
console.log(colors.white(' "Deploy to Vercel"'));
|
|
11557
|
+
console.log("");
|
|
11558
|
+
console.log(colors.primary(" Other"));
|
|
11559
|
+
console.log(colors.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
11560
|
+
console.log(colors.white(' "Generate tests"') + colors.dim(' "Start dev server"'));
|
|
11561
|
+
console.log(colors.white(' "Create a PR"') + colors.dim(' "Undo that"'));
|
|
11562
|
+
console.log("");
|
|
11563
|
+
console.log(colors.dim(" Shortcuts: /help /quit /status /login"));
|
|
11564
|
+
console.log("");
|
|
11565
|
+
}
|
|
11566
|
+
function getSuggestions(input) {
|
|
11567
|
+
const lowered = input.toLowerCase();
|
|
11568
|
+
const suggestions = [];
|
|
11569
|
+
const mappings = {
|
|
11570
|
+
"reveiw": ["/review - Review your codebase"],
|
|
11571
|
+
"reviw": ["/review - Review your codebase"],
|
|
11572
|
+
"aduit": ["/audit - Audit code quality"],
|
|
11573
|
+
"auidt": ["/audit - Audit code quality"],
|
|
11574
|
+
"hlep": ["/help - Show all commands"],
|
|
11575
|
+
"helo": ["/help - Show all commands"],
|
|
11576
|
+
"init": ["/init - Initialize project"],
|
|
11577
|
+
"start": ["/preview - Start dev server", "/init - Initialize project"],
|
|
11578
|
+
"run": ["/run <command> - Run a terminal command"],
|
|
11579
|
+
"fix": ["/audit fix - Auto-fix issues"],
|
|
11580
|
+
"check": ["/audit - Check code quality", "/review - Review codebase"],
|
|
11581
|
+
"test": ["/test - Generate tests"],
|
|
11582
|
+
"build": ['"Build a login page" - Describe what you want'],
|
|
11583
|
+
"make": ['"Make a navbar" - Describe what you want'],
|
|
11584
|
+
"create": ["/create - Create new project", '"Create a dashboard" - Describe it'],
|
|
11585
|
+
"git": ["/git status - Check git", "/git commit - Commit changes"],
|
|
11586
|
+
"commit": ["/commit - Commit changes", '/git commit -m "message"'],
|
|
11587
|
+
"push": ["/git push - Push to remote"],
|
|
11588
|
+
"status": ["/status - Project status", "/git status - Git status"],
|
|
11589
|
+
"login": ["/login - Login to CodeBakers"],
|
|
11590
|
+
"logout": ["/logout - Logout"]
|
|
11591
|
+
};
|
|
11592
|
+
for (const [key, vals] of Object.entries(mappings)) {
|
|
11593
|
+
if (lowered.includes(key) || key.includes(lowered)) {
|
|
11594
|
+
suggestions.push(...vals);
|
|
11595
|
+
}
|
|
11596
|
+
}
|
|
11597
|
+
if (suggestions.length === 0) {
|
|
11598
|
+
if (lowered.length < 3) {
|
|
11599
|
+
suggestions.push("/help - Show all commands");
|
|
11600
|
+
} else {
|
|
11601
|
+
suggestions.push(
|
|
11602
|
+
'"' + input + '" - Try describing what you want to build',
|
|
11603
|
+
"/help - Show all commands"
|
|
11604
|
+
);
|
|
11605
|
+
}
|
|
11606
|
+
}
|
|
11607
|
+
return [...new Set(suggestions)].slice(0, 4);
|
|
11633
11608
|
}
|
|
11634
11609
|
main().catch(console.error);
|