thrivekit 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/.claude/commands/explain.md +114 -0
  2. package/.claude/commands/idea.md +370 -0
  3. package/.claude/commands/my-dna.md +122 -0
  4. package/.claude/commands/prd.md +286 -0
  5. package/.claude/commands/review.md +167 -0
  6. package/.claude/commands/sign.md +32 -0
  7. package/.claude/commands/styleguide.md +450 -0
  8. package/.claude/commands/tour.md +301 -0
  9. package/.claude/commands/vibe-check.md +116 -0
  10. package/.claude/commands/vibe-help.md +47 -0
  11. package/.claude/commands/vibe-list.md +203 -0
  12. package/.claude/settings.json +75 -0
  13. package/.claude/settings.local.json +12 -0
  14. package/.pre-commit-hooks.yaml +102 -0
  15. package/LICENSE +21 -0
  16. package/README.md +214 -0
  17. package/bin/postinstall.sh +29 -0
  18. package/bin/ralph.sh +171 -0
  19. package/bin/thrivekit.sh +24 -0
  20. package/bin/vibe-check.js +19 -0
  21. package/dist/checks/check-any-types.d.ts +6 -0
  22. package/dist/checks/check-any-types.d.ts.map +1 -0
  23. package/dist/checks/check-any-types.js +73 -0
  24. package/dist/checks/check-any-types.js.map +1 -0
  25. package/dist/checks/check-commented-code.d.ts +6 -0
  26. package/dist/checks/check-commented-code.d.ts.map +1 -0
  27. package/dist/checks/check-commented-code.js +81 -0
  28. package/dist/checks/check-commented-code.js.map +1 -0
  29. package/dist/checks/check-console-error.d.ts +6 -0
  30. package/dist/checks/check-console-error.d.ts.map +1 -0
  31. package/dist/checks/check-console-error.js +41 -0
  32. package/dist/checks/check-console-error.js.map +1 -0
  33. package/dist/checks/check-debug-statements.d.ts +6 -0
  34. package/dist/checks/check-debug-statements.d.ts.map +1 -0
  35. package/dist/checks/check-debug-statements.js +120 -0
  36. package/dist/checks/check-debug-statements.js.map +1 -0
  37. package/dist/checks/check-deep-nesting.d.ts +6 -0
  38. package/dist/checks/check-deep-nesting.d.ts.map +1 -0
  39. package/dist/checks/check-deep-nesting.js +116 -0
  40. package/dist/checks/check-deep-nesting.js.map +1 -0
  41. package/dist/checks/check-docker-platform.d.ts +6 -0
  42. package/dist/checks/check-docker-platform.d.ts.map +1 -0
  43. package/dist/checks/check-docker-platform.js +42 -0
  44. package/dist/checks/check-docker-platform.js.map +1 -0
  45. package/dist/checks/check-dry-violations.d.ts +6 -0
  46. package/dist/checks/check-dry-violations.d.ts.map +1 -0
  47. package/dist/checks/check-dry-violations.js +124 -0
  48. package/dist/checks/check-dry-violations.js.map +1 -0
  49. package/dist/checks/check-empty-catch.d.ts +6 -0
  50. package/dist/checks/check-empty-catch.d.ts.map +1 -0
  51. package/dist/checks/check-empty-catch.js +111 -0
  52. package/dist/checks/check-empty-catch.js.map +1 -0
  53. package/dist/checks/check-function-length.d.ts +6 -0
  54. package/dist/checks/check-function-length.d.ts.map +1 -0
  55. package/dist/checks/check-function-length.js +152 -0
  56. package/dist/checks/check-function-length.js.map +1 -0
  57. package/dist/checks/check-hardcoded-ai-models.d.ts +10 -0
  58. package/dist/checks/check-hardcoded-ai-models.d.ts.map +1 -0
  59. package/dist/checks/check-hardcoded-ai-models.js +102 -0
  60. package/dist/checks/check-hardcoded-ai-models.js.map +1 -0
  61. package/dist/checks/check-hardcoded-urls.d.ts +6 -0
  62. package/dist/checks/check-hardcoded-urls.d.ts.map +1 -0
  63. package/dist/checks/check-hardcoded-urls.js +124 -0
  64. package/dist/checks/check-hardcoded-urls.js.map +1 -0
  65. package/dist/checks/check-magic-numbers.d.ts +6 -0
  66. package/dist/checks/check-magic-numbers.d.ts.map +1 -0
  67. package/dist/checks/check-magic-numbers.js +116 -0
  68. package/dist/checks/check-magic-numbers.js.map +1 -0
  69. package/dist/checks/check-secrets.d.ts +6 -0
  70. package/dist/checks/check-secrets.d.ts.map +1 -0
  71. package/dist/checks/check-secrets.js +138 -0
  72. package/dist/checks/check-secrets.js.map +1 -0
  73. package/dist/checks/check-snake-case-ts.d.ts +6 -0
  74. package/dist/checks/check-snake-case-ts.d.ts.map +1 -0
  75. package/dist/checks/check-snake-case-ts.js +78 -0
  76. package/dist/checks/check-snake-case-ts.js.map +1 -0
  77. package/dist/checks/check-todo-fixme.d.ts +6 -0
  78. package/dist/checks/check-todo-fixme.d.ts.map +1 -0
  79. package/dist/checks/check-todo-fixme.js +41 -0
  80. package/dist/checks/check-todo-fixme.js.map +1 -0
  81. package/dist/checks/check-unsafe-html.d.ts +6 -0
  82. package/dist/checks/check-unsafe-html.d.ts.map +1 -0
  83. package/dist/checks/check-unsafe-html.js +101 -0
  84. package/dist/checks/check-unsafe-html.js.map +1 -0
  85. package/dist/checks/index.d.ts +30 -0
  86. package/dist/checks/index.d.ts.map +1 -0
  87. package/dist/checks/index.js +57 -0
  88. package/dist/checks/index.js.map +1 -0
  89. package/dist/cli.d.ts +13 -0
  90. package/dist/cli.d.ts.map +1 -0
  91. package/dist/cli.js +206 -0
  92. package/dist/cli.js.map +1 -0
  93. package/dist/index.d.ts +9 -0
  94. package/dist/index.d.ts.map +1 -0
  95. package/dist/index.js +10 -0
  96. package/dist/index.js.map +1 -0
  97. package/dist/utils/file-reader.d.ts +24 -0
  98. package/dist/utils/file-reader.d.ts.map +1 -0
  99. package/dist/utils/file-reader.js +140 -0
  100. package/dist/utils/file-reader.js.map +1 -0
  101. package/dist/utils/patterns.d.ts +27 -0
  102. package/dist/utils/patterns.d.ts.map +1 -0
  103. package/dist/utils/patterns.js +84 -0
  104. package/dist/utils/patterns.js.map +1 -0
  105. package/dist/utils/reporters.d.ts +21 -0
  106. package/dist/utils/reporters.d.ts.map +1 -0
  107. package/dist/utils/reporters.js +115 -0
  108. package/dist/utils/reporters.js.map +1 -0
  109. package/dist/utils/types.d.ts +71 -0
  110. package/dist/utils/types.d.ts.map +1 -0
  111. package/dist/utils/types.js +5 -0
  112. package/dist/utils/types.js.map +1 -0
  113. package/package.json +82 -0
  114. package/ralph/api.sh +210 -0
  115. package/ralph/backup.sh +838 -0
  116. package/ralph/browser-verify/README.md +135 -0
  117. package/ralph/browser-verify/verify.ts +450 -0
  118. package/ralph/checks/check-fastapi-responses.py +155 -0
  119. package/ralph/hooks/hooks-config.json +72 -0
  120. package/ralph/hooks/inject-context.sh +44 -0
  121. package/ralph/hooks/install.sh +207 -0
  122. package/ralph/hooks/log-tools.sh +45 -0
  123. package/ralph/hooks/protect-prd.sh +27 -0
  124. package/ralph/hooks/save-learnings.sh +36 -0
  125. package/ralph/hooks/warn-debug.sh +54 -0
  126. package/ralph/hooks/warn-empty-catch.sh +63 -0
  127. package/ralph/hooks/warn-secrets.sh +89 -0
  128. package/ralph/hooks/warn-urls.sh +77 -0
  129. package/ralph/init.sh +388 -0
  130. package/ralph/loop.sh +570 -0
  131. package/ralph/playwright.sh +238 -0
  132. package/ralph/prd.sh +295 -0
  133. package/ralph/setup/feature-tour.sh +155 -0
  134. package/ralph/setup/quick-setup.sh +239 -0
  135. package/ralph/setup/tutorial.sh +159 -0
  136. package/ralph/setup/ui.sh +136 -0
  137. package/ralph/setup.sh +353 -0
  138. package/ralph/signs.sh +150 -0
  139. package/ralph/utils.sh +682 -0
  140. package/ralph/verify/browser.sh +324 -0
  141. package/ralph/verify/lint.sh +363 -0
  142. package/ralph/verify/review.sh +164 -0
  143. package/ralph/verify/tests.sh +81 -0
  144. package/ralph/verify.sh +224 -0
  145. package/templates/PROMPT.md +235 -0
  146. package/templates/config/fullstack.json +86 -0
  147. package/templates/config/go.json +81 -0
  148. package/templates/config/minimal.json +76 -0
  149. package/templates/config/node.json +81 -0
  150. package/templates/config/python.json +81 -0
  151. package/templates/config/rust.json +81 -0
  152. package/templates/examples/CLAUDE-django.md +174 -0
  153. package/templates/examples/CLAUDE-fastapi.md +270 -0
  154. package/templates/examples/CLAUDE-fastmcp.md +352 -0
  155. package/templates/examples/CLAUDE-fullstack.md +256 -0
  156. package/templates/examples/CLAUDE-node.md +246 -0
  157. package/templates/examples/CLAUDE-react.md +138 -0
  158. package/templates/optional/cursorrules.template +147 -0
  159. package/templates/optional/eslint.config.js +34 -0
  160. package/templates/optional/lint-staged.config.js +34 -0
  161. package/templates/optional/ruff.toml +125 -0
  162. package/templates/optional/vibe-check.yml +116 -0
  163. package/templates/optional/vscode-settings.json +127 -0
  164. package/templates/signs.json +46 -0
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Common regex patterns used across hooks
3
+ */
4
+ export declare const SECRET_PATTERNS: {
5
+ awsAccessKey: RegExp;
6
+ awsSecretKey: RegExp;
7
+ genericApiKey: RegExp;
8
+ genericSecret: RegExp;
9
+ stripeKey: RegExp;
10
+ githubToken: RegExp;
11
+ slackToken: RegExp;
12
+ twilioKey: RegExp;
13
+ sendgridKey: RegExp;
14
+ privateKey: RegExp;
15
+ jwt: RegExp;
16
+ };
17
+ export declare const URL_PATTERNS: {
18
+ localhost: RegExp;
19
+ localIp: RegExp;
20
+ hardcodedUrl: RegExp;
21
+ };
22
+ export declare const DEBUG_PATTERNS: Record<string, RegExp[]>;
23
+ export declare const LANGUAGE_EXTENSIONS: Record<string, string[]>;
24
+ export declare function getLanguage(extension: string): string | undefined;
25
+ export declare const PLACEHOLDER_PATTERNS: RegExp[];
26
+ export declare function isPlaceholder(value: string): boolean;
27
+ //# sourceMappingURL=patterns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.d.ts","sourceRoot":"","sources":["../../src/utils/patterns.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,eAAO,MAAM,eAAe;;;;;;;;;;;;CAqB3B,CAAC;AAGF,eAAO,MAAM,YAAY;;;;CAOxB,CAAC;AAGF,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAgBnD,CAAC;AAGF,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAQxD,CAAC;AAGF,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOjE;AAGD,eAAO,MAAM,oBAAoB,UAUhC,CAAC;AAGF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEpD"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Common regex patterns used across hooks
3
+ */
4
+ // Secret patterns
5
+ export const SECRET_PATTERNS = {
6
+ // AWS
7
+ awsAccessKey: /AKIA[0-9A-Z]{16}/,
8
+ awsSecretKey: /(?<![A-Za-z0-9/+=])[A-Za-z0-9/+=]{40}(?![A-Za-z0-9/+=])/,
9
+ // API keys (generic)
10
+ genericApiKey: /(?:api[_-]?key|apikey|api[_-]?secret)\s*[:=]\s*['"][a-zA-Z0-9_\-]{20,}['"]/i,
11
+ genericSecret: /(?:secret|password|passwd|pwd|token|auth)\s*[:=]\s*['"][^'"]{8,}['"]/i,
12
+ // Specific services
13
+ stripeKey: /sk_(?:live|test)_[0-9a-zA-Z]{24,}/,
14
+ githubToken: /gh[pousr]_[A-Za-z0-9_]{36,}/,
15
+ slackToken: /xox[baprs]-[0-9]{10,}-[0-9a-zA-Z]{24,}/,
16
+ twilioKey: /SK[0-9a-fA-F]{32}/,
17
+ sendgridKey: /SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}/,
18
+ // Private keys
19
+ privateKey: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/,
20
+ // JWT (only if it looks like a real token, not a placeholder)
21
+ jwt: /eyJ[a-zA-Z0-9_-]{10,}\.eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/,
22
+ };
23
+ // URL patterns
24
+ export const URL_PATTERNS = {
25
+ // Localhost URLs
26
+ localhost: /https?:\/\/localhost(?::\d+)?(?:\/[^\s'"]*)?/g,
27
+ localIp: /https?:\/\/127\.0\.0\.1(?::\d+)?(?:\/[^\s'"]*)?/g,
28
+ // Hardcoded production URLs (excluding common CDNs)
29
+ hardcodedUrl: /https?:\/\/(?!(?:cdn|fonts|unpkg|cdnjs|jsdelivr)\.)[a-zA-Z0-9][a-zA-Z0-9-]*\.[a-zA-Z]{2,}(?::\d+)?(?:\/[^\s'"]*)?/g,
30
+ };
31
+ // Debug statement patterns by language
32
+ export const DEBUG_PATTERNS = {
33
+ javascript: [
34
+ /console\.(log|debug|info|warn|error|trace|dir|table)\s*\(/,
35
+ /debugger\s*;?/,
36
+ /alert\s*\(/,
37
+ ],
38
+ typescript: [
39
+ /console\.(log|debug|info|warn|error|trace|dir|table)\s*\(/,
40
+ /debugger\s*;?/,
41
+ ],
42
+ python: [
43
+ /\bprint\s*\(/,
44
+ /\bbreakpoint\s*\(\s*\)/,
45
+ /\bpdb\.set_trace\s*\(\s*\)/,
46
+ /\bipdb\.set_trace\s*\(\s*\)/,
47
+ ],
48
+ };
49
+ // File extensions by language type
50
+ export const LANGUAGE_EXTENSIONS = {
51
+ javascript: ['js', 'jsx', 'mjs', 'cjs'],
52
+ typescript: ['ts', 'tsx', 'mts', 'cts'],
53
+ python: ['py', 'pyw'],
54
+ json: ['json', 'jsonc'],
55
+ yaml: ['yaml', 'yml'],
56
+ docker: ['dockerfile'],
57
+ html: ['html', 'htm'],
58
+ };
59
+ // Get language from file extension
60
+ export function getLanguage(extension) {
61
+ for (const [lang, exts] of Object.entries(LANGUAGE_EXTENSIONS)) {
62
+ if (exts.includes(extension.toLowerCase())) {
63
+ return lang;
64
+ }
65
+ }
66
+ return undefined;
67
+ }
68
+ // Common placeholder patterns (to ignore in secret detection)
69
+ export const PLACEHOLDER_PATTERNS = [
70
+ /example/i,
71
+ /placeholder/i,
72
+ /your[_-]?(?:api[_-]?)?key/i,
73
+ /xxx+/i,
74
+ /test/i,
75
+ /dummy/i,
76
+ /fake/i,
77
+ /sample/i,
78
+ /demo/i,
79
+ ];
80
+ // Check if a string looks like a placeholder
81
+ export function isPlaceholder(value) {
82
+ return PLACEHOLDER_PATTERNS.some((pattern) => pattern.test(value));
83
+ }
84
+ //# sourceMappingURL=patterns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.js","sourceRoot":"","sources":["../../src/utils/patterns.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,kBAAkB;AAClB,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,MAAM;IACN,YAAY,EAAE,kBAAkB;IAChC,YAAY,EAAE,yDAAyD;IAEvE,qBAAqB;IACrB,aAAa,EAAE,6EAA6E;IAC5F,aAAa,EAAE,uEAAuE;IAEtF,oBAAoB;IACpB,SAAS,EAAE,mCAAmC;IAC9C,WAAW,EAAE,6BAA6B;IAC1C,UAAU,EAAE,wCAAwC;IACpD,SAAS,EAAE,mBAAmB;IAC9B,WAAW,EAAE,0CAA0C;IAEvD,eAAe;IACf,UAAU,EAAE,wDAAwD;IAEpE,8DAA8D;IAC9D,GAAG,EAAE,kEAAkE;CACxE,CAAC;AAEF,eAAe;AACf,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,iBAAiB;IACjB,SAAS,EAAE,+CAA+C;IAC1D,OAAO,EAAE,kDAAkD;IAE3D,oDAAoD;IACpD,YAAY,EAAE,oHAAoH;CACnI,CAAC;AAEF,uCAAuC;AACvC,MAAM,CAAC,MAAM,cAAc,GAA6B;IACtD,UAAU,EAAE;QACV,2DAA2D;QAC3D,eAAe;QACf,YAAY;KACb;IACD,UAAU,EAAE;QACV,2DAA2D;QAC3D,eAAe;KAChB;IACD,MAAM,EAAE;QACN,cAAc;QACd,wBAAwB;QACxB,4BAA4B;QAC5B,6BAA6B;KAC9B;CACF,CAAC;AAEF,mCAAmC;AACnC,MAAM,CAAC,MAAM,mBAAmB,GAA6B;IAC3D,UAAU,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC;IACvC,UAAU,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC;IACvC,MAAM,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;IACvB,IAAI,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC;IACrB,MAAM,EAAE,CAAC,YAAY,CAAC;IACtB,IAAI,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC;CACtB,CAAC;AAEF,mCAAmC;AACnC,MAAM,UAAU,WAAW,CAAC,SAAiB;IAC3C,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,mBAAmB,CAAC,EAAE,CAAC;QAC/D,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YAC3C,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,8DAA8D;AAC9D,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,UAAU;IACV,cAAc;IACd,4BAA4B;IAC5B,OAAO;IACP,OAAO;IACP,QAAQ;IACR,OAAO;IACP,SAAS;IACT,OAAO;CACR,CAAC;AAEF,6CAA6C;AAC7C,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAO,oBAAoB,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;AACrE,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Output formatters for CLI results
3
+ */
4
+ import type { SummaryResult } from './types.js';
5
+ /**
6
+ * Pretty format - colored terminal output
7
+ */
8
+ export declare function formatPretty(summary: SummaryResult): string;
9
+ /**
10
+ * JSON format - machine-readable output
11
+ */
12
+ export declare function formatJson(summary: SummaryResult): string;
13
+ /**
14
+ * Compact format - one line per issue
15
+ */
16
+ export declare function formatCompact(summary: SummaryResult): string;
17
+ /**
18
+ * Format results based on output format option
19
+ */
20
+ export declare function formatResults(summary: SummaryResult, format: 'pretty' | 'json' | 'compact'): string;
21
+ //# sourceMappingURL=reporters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporters.d.ts","sourceRoot":"","sources":["../../src/utils/reporters.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAY,MAAM,YAAY,CAAC;AAoC1D;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAoD3D;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAEzD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAgB5D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,GACpC,MAAM,CASR"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Output formatters for CLI results
3
+ */
4
+ // ANSI color codes
5
+ const colors = {
6
+ reset: '\x1b[0m',
7
+ bold: '\x1b[1m',
8
+ dim: '\x1b[2m',
9
+ red: '\x1b[31m',
10
+ yellow: '\x1b[33m',
11
+ blue: '\x1b[34m',
12
+ cyan: '\x1b[36m',
13
+ gray: '\x1b[90m',
14
+ };
15
+ function severityColor(severity) {
16
+ switch (severity) {
17
+ case 'error':
18
+ return colors.red;
19
+ case 'warning':
20
+ return colors.yellow;
21
+ case 'info':
22
+ return colors.blue;
23
+ }
24
+ }
25
+ function severityIcon(severity) {
26
+ switch (severity) {
27
+ case 'error':
28
+ return '✖';
29
+ case 'warning':
30
+ return '⚠';
31
+ case 'info':
32
+ return 'ℹ';
33
+ }
34
+ }
35
+ /**
36
+ * Pretty format - colored terminal output
37
+ */
38
+ export function formatPretty(summary) {
39
+ const lines = [];
40
+ // Header
41
+ lines.push('');
42
+ lines.push(`${colors.bold}vibe-check${colors.reset}`);
43
+ lines.push('');
44
+ // Results by file
45
+ for (const result of summary.results) {
46
+ if (result.issues.length === 0)
47
+ continue;
48
+ lines.push(`${colors.cyan}${result.filePath}${colors.reset}`);
49
+ for (const issue of result.issues) {
50
+ const color = severityColor(issue.severity);
51
+ const icon = severityIcon(issue.severity);
52
+ const location = issue.column
53
+ ? `${issue.line}:${issue.column}`
54
+ : `${issue.line}`;
55
+ lines.push(` ${colors.gray}${location.padEnd(8)}${colors.reset}` +
56
+ `${color}${icon}${colors.reset} ` +
57
+ `${issue.message} ${colors.dim}${issue.ruleId}${colors.reset}`);
58
+ }
59
+ lines.push('');
60
+ }
61
+ // Summary
62
+ const problems = [];
63
+ if (summary.errorCount > 0) {
64
+ problems.push(`${colors.red}${summary.errorCount} error${summary.errorCount === 1 ? '' : 's'}${colors.reset}`);
65
+ }
66
+ if (summary.warningCount > 0) {
67
+ problems.push(`${colors.yellow}${summary.warningCount} warning${summary.warningCount === 1 ? '' : 's'}${colors.reset}`);
68
+ }
69
+ if (summary.infoCount > 0) {
70
+ problems.push(`${colors.blue}${summary.infoCount} info${colors.reset}`);
71
+ }
72
+ if (problems.length > 0) {
73
+ lines.push(`${problems.join(', ')} in ${summary.filesWithIssues} file${summary.filesWithIssues === 1 ? '' : 's'}`);
74
+ }
75
+ else {
76
+ lines.push(`${colors.bold}✓${colors.reset} ${summary.filesChecked} file${summary.filesChecked === 1 ? '' : 's'} checked - no issues found`);
77
+ }
78
+ lines.push('');
79
+ return lines.join('\n');
80
+ }
81
+ /**
82
+ * JSON format - machine-readable output
83
+ */
84
+ export function formatJson(summary) {
85
+ return JSON.stringify(summary, null, 2);
86
+ }
87
+ /**
88
+ * Compact format - one line per issue
89
+ */
90
+ export function formatCompact(summary) {
91
+ const lines = [];
92
+ for (const result of summary.results) {
93
+ for (const issue of result.issues) {
94
+ const location = issue.column
95
+ ? `${issue.line}:${issue.column}`
96
+ : `${issue.line}`;
97
+ lines.push(`${result.filePath}:${location}: ${issue.severity}: ${issue.message} [${issue.ruleId}]`);
98
+ }
99
+ }
100
+ return lines.join('\n');
101
+ }
102
+ /**
103
+ * Format results based on output format option
104
+ */
105
+ export function formatResults(summary, format) {
106
+ switch (format) {
107
+ case 'pretty':
108
+ return formatPretty(summary);
109
+ case 'json':
110
+ return formatJson(summary);
111
+ case 'compact':
112
+ return formatCompact(summary);
113
+ }
114
+ }
115
+ //# sourceMappingURL=reporters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporters.js","sourceRoot":"","sources":["../../src/utils/reporters.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Shared types for thrivekit
3
+ */
4
+ export type Severity = 'error' | 'warning' | 'info';
5
+ export interface FileContext {
6
+ /** Absolute path to the file */
7
+ filePath: string;
8
+ /** File content as string */
9
+ content: string;
10
+ /** File extension without dot (e.g., 'ts', 'py') */
11
+ extension: string;
12
+ }
13
+ export interface HookResult {
14
+ /** 1-based line number */
15
+ line: number;
16
+ /** 0-based column number */
17
+ column?: number;
18
+ /** Human-readable message */
19
+ message: string;
20
+ /** Severity level */
21
+ severity: Severity;
22
+ /** Rule identifier (e.g., 'secrets/aws-key') */
23
+ ruleId: string;
24
+ /** Optional fix suggestion */
25
+ fix?: string;
26
+ }
27
+ export interface Hook {
28
+ /** Unique identifier (e.g., 'secrets', 'debug-statements') */
29
+ id: string;
30
+ /** Human-readable name */
31
+ name: string;
32
+ /** Description of what this hook checks */
33
+ description: string;
34
+ /** Default severity level */
35
+ severity: Severity;
36
+ /** File extensions this hook applies to (without dots) */
37
+ fileTypes: string[];
38
+ /** Check function that returns issues found */
39
+ check: (context: FileContext) => HookResult[];
40
+ }
41
+ export interface CheckOptions {
42
+ /** Only run these hooks (by id) */
43
+ only?: string[];
44
+ /** Skip these hooks (by id) */
45
+ skip?: string[];
46
+ /** Minimum severity to fail on */
47
+ failOn?: Severity;
48
+ /** Output format */
49
+ format?: 'pretty' | 'json' | 'compact';
50
+ /** Auto-fix issues where possible */
51
+ fix?: boolean;
52
+ }
53
+ export interface CheckResult {
54
+ /** File path */
55
+ filePath: string;
56
+ /** All issues found in this file */
57
+ issues: HookResult[];
58
+ }
59
+ export interface SummaryResult {
60
+ /** Total files checked */
61
+ filesChecked: number;
62
+ /** Files with issues */
63
+ filesWithIssues: number;
64
+ /** Total issues by severity */
65
+ errorCount: number;
66
+ warningCount: number;
67
+ infoCount: number;
68
+ /** All results */
69
+ results: CheckResult[];
70
+ }
71
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/utils/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAEpD,MAAM,WAAW,WAAW;IAC1B,gCAAgC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,qBAAqB;IACrB,QAAQ,EAAE,QAAQ,CAAC;IACnB,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,IAAI;IACnB,8DAA8D;IAC9D,EAAE,EAAE,MAAM,CAAC;IACX,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAC;IACpB,6BAA6B;IAC7B,QAAQ,EAAE,QAAQ,CAAC;IACnB,0DAA0D;IAC1D,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,+CAA+C;IAC/C,KAAK,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,UAAU,EAAE,CAAC;CAC/C;AAED,MAAM,WAAW,YAAY;IAC3B,mCAAmC;IACnC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,+BAA+B;IAC/B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,kCAAkC;IAClC,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,oBAAoB;IACpB,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;IACvC,qCAAqC;IACrC,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,gBAAgB;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,0BAA0B;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,wBAAwB;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,+BAA+B;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB;IAClB,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Shared types for thrivekit
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/utils/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "thrivekit",
3
+ "version": "2.0.0",
4
+ "description": "Tools to thrive with agentic coding - RALPH autonomous loop, Claude Code hooks, PRD-driven development",
5
+ "author": "Allie Jones <allie@allthrive.ai>",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/allthriveai/thrivekit"
10
+ },
11
+ "homepage": "https://github.com/allthriveai/thrivekit#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/allthriveai/thrivekit/issues"
14
+ },
15
+ "keywords": [
16
+ "ai",
17
+ "agentic",
18
+ "claude",
19
+ "claude-code",
20
+ "autonomous",
21
+ "prd",
22
+ "ralph",
23
+ "pre-commit",
24
+ "code-quality"
25
+ ],
26
+ "type": "module",
27
+ "main": "dist/index.js",
28
+ "types": "dist/index.d.ts",
29
+ "bin": {
30
+ "thrivekit": "./bin/thrivekit.sh",
31
+ "vibe-check": "./bin/vibe-check.js"
32
+ },
33
+ "exports": {
34
+ ".": {
35
+ "types": "./dist/index.d.ts",
36
+ "import": "./dist/index.js"
37
+ }
38
+ },
39
+ "files": [
40
+ "dist",
41
+ "bin",
42
+ "ralph",
43
+ "templates",
44
+ ".claude",
45
+ ".pre-commit-hooks.yaml"
46
+ ],
47
+ "scripts": {
48
+ "build": "tsc",
49
+ "dev": "tsc --watch",
50
+ "test": "vitest",
51
+ "test:run": "vitest run",
52
+ "lint": "eslint src",
53
+ "typecheck": "tsc --noEmit",
54
+ "clean": "rm -rf dist",
55
+ "prepublishOnly": "npm run clean && npm run build",
56
+ "postinstall": "./bin/postinstall.sh",
57
+ "release": "npm version patch && git push && git push --tags && npm publish",
58
+ "release:minor": "npm version minor && git push && git push --tags && npm publish",
59
+ "release:major": "npm version major && git push && git push --tags && npm publish"
60
+ },
61
+ "engines": {
62
+ "node": ">=18.0.0"
63
+ },
64
+ "peerDependencies": {
65
+ "playwright": ">=1.40.0"
66
+ },
67
+ "peerDependenciesMeta": {
68
+ "playwright": {
69
+ "optional": true
70
+ }
71
+ },
72
+ "dependencies": {
73
+ "tsx": "^4.0.0"
74
+ },
75
+ "devDependencies": {
76
+ "@types/node": "^20.0.0",
77
+ "eslint": "^9.0.0",
78
+ "playwright": "^1.40.0",
79
+ "typescript": "^5.0.0",
80
+ "vitest": "^4.0.18"
81
+ }
82
+ }
package/ralph/api.sh ADDED
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env bash
2
+ # shellcheck shell=bash
3
+ # api.sh - API validation for backend stories
4
+
5
+ # Parse an endpoint string into method and path
6
+ # Usage: parse_endpoint "POST /api/users" "http://localhost:3000"
7
+ # Sets: ENDPOINT_METHOD, ENDPOINT_PATH, ENDPOINT_URL
8
+ parse_endpoint() {
9
+ local endpoint="$1"
10
+ local base_url="${2:-}"
11
+
12
+ # Defaults
13
+ ENDPOINT_METHOD="GET"
14
+ ENDPOINT_PATH="$endpoint"
15
+ ENDPOINT_URL=""
16
+
17
+ # Parse method if present (e.g., "POST /api/contact")
18
+ if [[ "$endpoint" =~ ^(GET|POST|PUT|PATCH|DELETE)[[:space:]]+(.*) ]]; then
19
+ ENDPOINT_METHOD="${BASH_REMATCH[1]}"
20
+ ENDPOINT_PATH="${BASH_REMATCH[2]}"
21
+ fi
22
+
23
+ # Build full URL
24
+ if [[ "$ENDPOINT_PATH" =~ ^https?:// ]]; then
25
+ ENDPOINT_URL="$ENDPOINT_PATH"
26
+ elif [[ -n "$base_url" ]]; then
27
+ ENDPOINT_URL="${base_url}${ENDPOINT_PATH}"
28
+ fi
29
+ }
30
+
31
+ # Check if endpoint is a WebSocket (can't be tested with HTTP)
32
+ is_websocket_endpoint() {
33
+ local endpoint="$1"
34
+ [[ "$endpoint" =~ ^wss?:// ]] || [[ "$endpoint" =~ ^(GET|POST|PUT|PATCH|DELETE)[[:space:]]+wss?:// ]]
35
+ }
36
+
37
+ # Validate API endpoints for a backend story
38
+ run_api_validation() {
39
+ local story="$1"
40
+
41
+ # Get API endpoints from story
42
+ local endpoints
43
+ endpoints=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .apiEndpoints[]?' "$RALPH_DIR/prd.json" 2>/dev/null)
44
+
45
+ if [[ -z "$endpoints" ]]; then
46
+ echo " (no apiEndpoints defined, skipping API validation)"
47
+ return 0
48
+ fi
49
+
50
+ # Get base URL from config or use default
51
+ local base_url
52
+ base_url=$(get_config '.api.baseUrl' "http://localhost:3000")
53
+
54
+ local failed=0
55
+
56
+ echo " Validating API endpoints..."
57
+
58
+ while IFS= read -r endpoint; do
59
+ [[ -z "$endpoint" ]] && continue
60
+
61
+ # Skip WebSocket endpoints - they can't be tested with HTTP curl
62
+ if is_websocket_endpoint "$endpoint"; then
63
+ echo " Skipping WebSocket endpoint: $endpoint (use integration tests)"
64
+ continue
65
+ fi
66
+
67
+ # Parse endpoint into method, path, and full URL
68
+ parse_endpoint "$endpoint" "$base_url"
69
+
70
+ echo -n " $ENDPOINT_METHOD $ENDPOINT_PATH... "
71
+
72
+ # Make the request
73
+ local response_code
74
+ response_code=$(curl -sf -m "$CURL_TIMEOUT_SECONDS" -o /dev/null -w "%{http_code}" -X "$ENDPOINT_METHOD" "$ENDPOINT_URL" 2>/dev/null)
75
+
76
+ if [[ "$response_code" =~ ^2[0-9][0-9]$ ]]; then
77
+ print_success "$response_code"
78
+ elif [[ "$response_code" == "000" ]]; then
79
+ print_error "connection failed"
80
+ failed=1
81
+ else
82
+ print_error "$response_code"
83
+ failed=1
84
+ fi
85
+ done <<< "$endpoints"
86
+
87
+ return $failed
88
+ }
89
+
90
+ # Run comprehensive API tests for a story
91
+ run_api_tests() {
92
+ local story="$1"
93
+
94
+ # Get test steps that look like API calls
95
+ local test_steps
96
+ test_steps=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .testSteps[]?' "$RALPH_DIR/prd.json" 2>/dev/null)
97
+
98
+ if [[ -z "$test_steps" ]]; then
99
+ return 0
100
+ fi
101
+
102
+ local failed=0
103
+ local log_file
104
+ log_file=$(create_temp_file ".log") || return 1
105
+
106
+ echo " Running API test steps..."
107
+
108
+ while IFS= read -r step; do
109
+ [[ -z "$step" ]] && continue
110
+
111
+ # Check if this looks like a curl command or API test
112
+ if [[ "$step" =~ ^curl ]]; then
113
+ echo -n " $step... "
114
+
115
+ if safe_exec "$step" "$log_file"; then
116
+ print_success "passed"
117
+ else
118
+ print_error "failed"
119
+ echo ""
120
+ echo " Response:"
121
+ tail -"$MAX_OUTPUT_PREVIEW_LINES" "$log_file" | sed 's/^/ /'
122
+ failed=1
123
+ fi
124
+ fi
125
+ done <<< "$test_steps"
126
+
127
+ rm -f "$log_file"
128
+ return $failed
129
+ }
130
+
131
+ # Validate error handling for API
132
+ run_api_error_tests() {
133
+ local story="$1"
134
+
135
+ # Get error handling requirements
136
+ local error_handling
137
+ error_handling=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .errorHandling[]?' "$RALPH_DIR/prd.json" 2>/dev/null)
138
+
139
+ if [[ -z "$error_handling" ]]; then
140
+ return 0
141
+ fi
142
+
143
+ # Get base URL and endpoints
144
+ local base_url
145
+ base_url=$(get_config '.api.baseUrl' "http://localhost:3000")
146
+
147
+ local endpoints
148
+ endpoints=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .apiEndpoints[0]?' "$RALPH_DIR/prd.json" 2>/dev/null)
149
+
150
+ if [[ -z "$endpoints" ]]; then
151
+ return 0
152
+ fi
153
+
154
+ # Skip WebSocket endpoints
155
+ if is_websocket_endpoint "$endpoints"; then
156
+ echo " Skipping error tests for WebSocket endpoint"
157
+ return 0
158
+ fi
159
+
160
+ # Parse endpoint (default to POST for error tests)
161
+ parse_endpoint "$endpoints" "$base_url"
162
+ [[ "$ENDPOINT_METHOD" == "GET" ]] && ENDPOINT_METHOD="POST"
163
+
164
+ local failed=0
165
+
166
+ echo " Testing API error handling..."
167
+
168
+ # Test common error cases
169
+ while IFS= read -r error_case; do
170
+ [[ -z "$error_case" ]] && continue
171
+
172
+ # Check for 400 tests (bad input)
173
+ if [[ "$error_case" =~ 400 ]]; then
174
+ echo -n " Testing 400 (bad request)... "
175
+
176
+ local response_code
177
+ response_code=$(curl -sf -m "$CURL_TIMEOUT_SECONDS" -o /dev/null -w "%{http_code}" \
178
+ -X "$ENDPOINT_METHOD" \
179
+ -H "Content-Type: application/json" \
180
+ -d '{}' \
181
+ "$ENDPOINT_URL" 2>/dev/null)
182
+
183
+ if [[ "$response_code" == "400" ]]; then
184
+ print_success "correctly returns 400"
185
+ else
186
+ print_warning "got $response_code (expected 400)"
187
+ failed=1
188
+ fi
189
+ fi
190
+
191
+ # Check for 401 tests (unauthorized)
192
+ if [[ "$error_case" =~ 401 ]]; then
193
+ echo -n " Testing 401 (unauthorized)... "
194
+
195
+ local response_code
196
+ response_code=$(curl -sf -m "$CURL_TIMEOUT_SECONDS" -o /dev/null -w "%{http_code}" \
197
+ -X "$ENDPOINT_METHOD" \
198
+ "$ENDPOINT_URL" 2>/dev/null)
199
+
200
+ if [[ "$response_code" == "401" ]]; then
201
+ print_success "correctly returns 401"
202
+ else
203
+ print_warning "got $response_code (expected 401)"
204
+ failed=1
205
+ fi
206
+ fi
207
+ done <<< "$error_handling"
208
+
209
+ return $failed
210
+ }