@assistkick/create 1.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 (178) hide show
  1. package/dist/bin/create.d.ts +2 -0
  2. package/dist/bin/create.js +25 -0
  3. package/dist/bin/create.js.map +1 -0
  4. package/dist/src/scaffolder.d.ts +22 -0
  5. package/dist/src/scaffolder.js +120 -0
  6. package/dist/src/scaffolder.js.map +1 -0
  7. package/package.json +24 -0
  8. package/templates/product-system/.env.example +8 -0
  9. package/templates/product-system/CLAUDE.md +45 -0
  10. package/templates/product-system/package.json +32 -0
  11. package/templates/product-system/packages/backend/package.json +37 -0
  12. package/templates/product-system/packages/backend/src/middleware/auth_middleware.test.ts +86 -0
  13. package/templates/product-system/packages/backend/src/middleware/auth_middleware.ts +35 -0
  14. package/templates/product-system/packages/backend/src/routes/auth.ts +463 -0
  15. package/templates/product-system/packages/backend/src/routes/coherence.ts +187 -0
  16. package/templates/product-system/packages/backend/src/routes/graph.ts +67 -0
  17. package/templates/product-system/packages/backend/src/routes/kanban.ts +201 -0
  18. package/templates/product-system/packages/backend/src/routes/pipeline.ts +41 -0
  19. package/templates/product-system/packages/backend/src/routes/projects.ts +122 -0
  20. package/templates/product-system/packages/backend/src/routes/users.ts +97 -0
  21. package/templates/product-system/packages/backend/src/server.ts +159 -0
  22. package/templates/product-system/packages/backend/src/services/auth_service.test.ts +115 -0
  23. package/templates/product-system/packages/backend/src/services/auth_service.ts +82 -0
  24. package/templates/product-system/packages/backend/src/services/coherence-review.ts +339 -0
  25. package/templates/product-system/packages/backend/src/services/email_service.ts +75 -0
  26. package/templates/product-system/packages/backend/src/services/init.ts +80 -0
  27. package/templates/product-system/packages/backend/src/services/invitation_service.test.ts +235 -0
  28. package/templates/product-system/packages/backend/src/services/invitation_service.ts +193 -0
  29. package/templates/product-system/packages/backend/src/services/password_reset_service.test.ts +151 -0
  30. package/templates/product-system/packages/backend/src/services/password_reset_service.ts +135 -0
  31. package/templates/product-system/packages/backend/src/services/project_service.test.ts +215 -0
  32. package/templates/product-system/packages/backend/src/services/project_service.ts +171 -0
  33. package/templates/product-system/packages/backend/src/services/pty_session_manager.test.ts +88 -0
  34. package/templates/product-system/packages/backend/src/services/pty_session_manager.ts +279 -0
  35. package/templates/product-system/packages/backend/src/services/terminal_ws_handler.ts +133 -0
  36. package/templates/product-system/packages/backend/src/services/user_management_service.test.ts +158 -0
  37. package/templates/product-system/packages/backend/src/services/user_management_service.ts +128 -0
  38. package/templates/product-system/packages/backend/tsconfig.json +22 -0
  39. package/templates/product-system/packages/frontend/index.html +13 -0
  40. package/templates/product-system/packages/frontend/package-lock.json +2666 -0
  41. package/templates/product-system/packages/frontend/package.json +30 -0
  42. package/templates/product-system/packages/frontend/public/favicon.svg +16 -0
  43. package/templates/product-system/packages/frontend/src/App.tsx +29 -0
  44. package/templates/product-system/packages/frontend/src/api/client.ts +386 -0
  45. package/templates/product-system/packages/frontend/src/api/client_projects.test.ts +104 -0
  46. package/templates/product-system/packages/frontend/src/api/client_refresh.test.ts +145 -0
  47. package/templates/product-system/packages/frontend/src/components/CoherenceView.tsx +414 -0
  48. package/templates/product-system/packages/frontend/src/components/GraphLegend.tsx +124 -0
  49. package/templates/product-system/packages/frontend/src/components/GraphSettings.tsx +112 -0
  50. package/templates/product-system/packages/frontend/src/components/GraphView.tsx +370 -0
  51. package/templates/product-system/packages/frontend/src/components/InviteUserDialog.tsx +85 -0
  52. package/templates/product-system/packages/frontend/src/components/KanbanView.tsx +470 -0
  53. package/templates/product-system/packages/frontend/src/components/LoginPage.tsx +116 -0
  54. package/templates/product-system/packages/frontend/src/components/ProjectSelector.tsx +187 -0
  55. package/templates/product-system/packages/frontend/src/components/QaIssueSheet.tsx +192 -0
  56. package/templates/product-system/packages/frontend/src/components/SidePanel.tsx +231 -0
  57. package/templates/product-system/packages/frontend/src/components/TerminalView.tsx +200 -0
  58. package/templates/product-system/packages/frontend/src/components/Toolbar.tsx +84 -0
  59. package/templates/product-system/packages/frontend/src/components/UsersView.tsx +249 -0
  60. package/templates/product-system/packages/frontend/src/constants/graph.ts +191 -0
  61. package/templates/product-system/packages/frontend/src/hooks/useAuth.tsx +54 -0
  62. package/templates/product-system/packages/frontend/src/hooks/useGraph.ts +27 -0
  63. package/templates/product-system/packages/frontend/src/hooks/useKanban.ts +21 -0
  64. package/templates/product-system/packages/frontend/src/hooks/useProjects.ts +86 -0
  65. package/templates/product-system/packages/frontend/src/hooks/useTheme.ts +26 -0
  66. package/templates/product-system/packages/frontend/src/hooks/useToast.tsx +62 -0
  67. package/templates/product-system/packages/frontend/src/hooks/use_projects_logic.test.ts +61 -0
  68. package/templates/product-system/packages/frontend/src/main.tsx +12 -0
  69. package/templates/product-system/packages/frontend/src/pages/accept_invitation_page.tsx +167 -0
  70. package/templates/product-system/packages/frontend/src/pages/forgot_password_page.tsx +100 -0
  71. package/templates/product-system/packages/frontend/src/pages/register_page.tsx +137 -0
  72. package/templates/product-system/packages/frontend/src/pages/reset_password_page.tsx +146 -0
  73. package/templates/product-system/packages/frontend/src/routes/ProtectedRoute.tsx +12 -0
  74. package/templates/product-system/packages/frontend/src/routes/accept_invitation.tsx +14 -0
  75. package/templates/product-system/packages/frontend/src/routes/dashboard.tsx +221 -0
  76. package/templates/product-system/packages/frontend/src/routes/forgot_password.tsx +13 -0
  77. package/templates/product-system/packages/frontend/src/routes/login.tsx +14 -0
  78. package/templates/product-system/packages/frontend/src/routes/register.tsx +14 -0
  79. package/templates/product-system/packages/frontend/src/routes/reset_password.tsx +13 -0
  80. package/templates/product-system/packages/frontend/src/styles/index.css +3358 -0
  81. package/templates/product-system/packages/frontend/src/utils/auth_validation.test.ts +51 -0
  82. package/templates/product-system/packages/frontend/src/utils/auth_validation.ts +19 -0
  83. package/templates/product-system/packages/frontend/src/utils/login_validation.test.ts +61 -0
  84. package/templates/product-system/packages/frontend/src/utils/login_validation.ts +24 -0
  85. package/templates/product-system/packages/frontend/src/utils/logout.test.ts +63 -0
  86. package/templates/product-system/packages/frontend/src/utils/node_sizing.test.ts +62 -0
  87. package/templates/product-system/packages/frontend/src/utils/node_sizing.ts +24 -0
  88. package/templates/product-system/packages/frontend/src/utils/task_status.test.ts +53 -0
  89. package/templates/product-system/packages/frontend/src/utils/task_status.ts +14 -0
  90. package/templates/product-system/packages/frontend/tsconfig.json +21 -0
  91. package/templates/product-system/packages/frontend/vite.config.ts +20 -0
  92. package/templates/product-system/packages/shared/.env.example +3 -0
  93. package/templates/product-system/packages/shared/README.md +1 -0
  94. package/templates/product-system/packages/shared/db/migrate.ts +32 -0
  95. package/templates/product-system/packages/shared/db/migrations/0000_dashing_gorgon.sql +128 -0
  96. package/templates/product-system/packages/shared/db/migrations/meta/0000_snapshot.json +819 -0
  97. package/templates/product-system/packages/shared/db/migrations/meta/_journal.json +13 -0
  98. package/templates/product-system/packages/shared/db/schema.ts +137 -0
  99. package/templates/product-system/packages/shared/drizzle.config.js +14 -0
  100. package/templates/product-system/packages/shared/lib/claude-service.ts +215 -0
  101. package/templates/product-system/packages/shared/lib/coherence.ts +278 -0
  102. package/templates/product-system/packages/shared/lib/completeness.ts +30 -0
  103. package/templates/product-system/packages/shared/lib/constants.ts +327 -0
  104. package/templates/product-system/packages/shared/lib/db.ts +81 -0
  105. package/templates/product-system/packages/shared/lib/git_workflow.ts +110 -0
  106. package/templates/product-system/packages/shared/lib/graph.ts +186 -0
  107. package/templates/product-system/packages/shared/lib/kanban.ts +161 -0
  108. package/templates/product-system/packages/shared/lib/markdown.ts +205 -0
  109. package/templates/product-system/packages/shared/lib/pipeline-state-store.ts +124 -0
  110. package/templates/product-system/packages/shared/lib/pipeline.ts +489 -0
  111. package/templates/product-system/packages/shared/lib/prompt_builder.ts +170 -0
  112. package/templates/product-system/packages/shared/lib/relevance_search.ts +159 -0
  113. package/templates/product-system/packages/shared/lib/session.ts +152 -0
  114. package/templates/product-system/packages/shared/lib/validator.ts +117 -0
  115. package/templates/product-system/packages/shared/lib/work_summary_parser.ts +130 -0
  116. package/templates/product-system/packages/shared/package.json +30 -0
  117. package/templates/product-system/packages/shared/scripts/assign-project.ts +52 -0
  118. package/templates/product-system/packages/shared/tools/add_edge.ts +61 -0
  119. package/templates/product-system/packages/shared/tools/add_node.ts +101 -0
  120. package/templates/product-system/packages/shared/tools/end_session.ts +87 -0
  121. package/templates/product-system/packages/shared/tools/get_gaps.ts +87 -0
  122. package/templates/product-system/packages/shared/tools/get_kanban.ts +125 -0
  123. package/templates/product-system/packages/shared/tools/get_node.ts +78 -0
  124. package/templates/product-system/packages/shared/tools/get_status.ts +98 -0
  125. package/templates/product-system/packages/shared/tools/migrate_to_turso.ts +385 -0
  126. package/templates/product-system/packages/shared/tools/move_card.ts +143 -0
  127. package/templates/product-system/packages/shared/tools/rebuild_index.ts +77 -0
  128. package/templates/product-system/packages/shared/tools/remove_edge.ts +59 -0
  129. package/templates/product-system/packages/shared/tools/remove_node.ts +96 -0
  130. package/templates/product-system/packages/shared/tools/resolve_question.ts +75 -0
  131. package/templates/product-system/packages/shared/tools/search_nodes.ts +106 -0
  132. package/templates/product-system/packages/shared/tools/start_session.ts +144 -0
  133. package/templates/product-system/packages/shared/tools/update_node.ts +133 -0
  134. package/templates/product-system/packages/shared/tsconfig.json +24 -0
  135. package/templates/product-system/pnpm-workspace.yaml +2 -0
  136. package/templates/product-system/smoke_test.ts +219 -0
  137. package/templates/product-system/tests/coherence_review.test.ts +562 -0
  138. package/templates/product-system/tests/db_sqlite_fallback.test.ts +75 -0
  139. package/templates/product-system/tests/edge_type_color_coding.test.ts +147 -0
  140. package/templates/product-system/tests/emit-tool-use-events.test.ts +85 -0
  141. package/templates/product-system/tests/feature_kind.test.ts +139 -0
  142. package/templates/product-system/tests/gap_indicators.test.ts +199 -0
  143. package/templates/product-system/tests/graceful_init.test.ts +142 -0
  144. package/templates/product-system/tests/graph_legend.test.ts +314 -0
  145. package/templates/product-system/tests/graph_settings_sheet.test.ts +804 -0
  146. package/templates/product-system/tests/hide_defined_filter.test.ts +205 -0
  147. package/templates/product-system/tests/kanban.test.ts +529 -0
  148. package/templates/product-system/tests/neighborhood_focus.test.ts +132 -0
  149. package/templates/product-system/tests/node_search.test.ts +340 -0
  150. package/templates/product-system/tests/node_sizing.test.ts +170 -0
  151. package/templates/product-system/tests/node_type_toggle_filters.test.ts +285 -0
  152. package/templates/product-system/tests/node_type_visual_encoding.test.ts +103 -0
  153. package/templates/product-system/tests/pipeline-state-store.test.ts +268 -0
  154. package/templates/product-system/tests/pipeline-unit.test.ts +593 -0
  155. package/templates/product-system/tests/pipeline.test.ts +195 -0
  156. package/templates/product-system/tests/pipeline_stats_all_cards.test.ts +193 -0
  157. package/templates/product-system/tests/play_all.test.ts +296 -0
  158. package/templates/product-system/tests/qa_issue_sheet.test.ts +464 -0
  159. package/templates/product-system/tests/relevance_search.test.ts +186 -0
  160. package/templates/product-system/tests/search_reorder.test.ts +88 -0
  161. package/templates/product-system/tests/serve_ui.test.ts +281 -0
  162. package/templates/product-system/tests/serve_ui_drizzle.test.ts +114 -0
  163. package/templates/product-system/tests/session_context_recall.test.ts +135 -0
  164. package/templates/product-system/tests/side_panel.test.ts +345 -0
  165. package/templates/product-system/tests/spec_completeness_label.test.ts +69 -0
  166. package/templates/product-system/tests/url_routing_test.ts +122 -0
  167. package/templates/product-system/tests/user_login.test.ts +150 -0
  168. package/templates/product-system/tests/user_registration.test.ts +205 -0
  169. package/templates/product-system/tests/web_terminal.test.ts +572 -0
  170. package/templates/product-system/tests/work_summary.test.ts +211 -0
  171. package/templates/product-system/tests/zoom_pan.test.ts +43 -0
  172. package/templates/product-system/tsconfig.json +24 -0
  173. package/templates/skills/product-bootstrap/SKILL.md +312 -0
  174. package/templates/skills/product-code-reviewer/SKILL.md +147 -0
  175. package/templates/skills/product-debugger/SKILL.md +206 -0
  176. package/templates/skills/product-debugger/references/agent-browser.md +1156 -0
  177. package/templates/skills/product-developer/SKILL.md +182 -0
  178. package/templates/skills/product-interview/SKILL.md +220 -0
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { existsSync } from 'node:fs';
5
+ import { Scaffolder } from '../src/scaffolder.js';
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
8
+ // Resolve templates directory:
9
+ // - Compiled (dist/bin/create.js): go up 2 levels to package root, then templates/
10
+ // - Dev via tsx (bin/create.ts): go up 1 level to package root, then templates/
11
+ const isCompiled = __dirname.includes('/dist/') || __dirname.includes('\\dist\\');
12
+ const packageRoot = isCompiled ? join(__dirname, '..', '..') : join(__dirname, '..');
13
+ const templatesDir = join(packageRoot, 'templates');
14
+ if (!existsSync(templatesDir)) {
15
+ console.error('Error: templates directory not found at', templatesDir);
16
+ console.error('Run `pnpm prepare_templates` from the create/ package directory first.');
17
+ process.exit(1);
18
+ }
19
+ const targetDir = process.cwd();
20
+ const scaffolder = new Scaffolder(targetDir, templatesDir);
21
+ scaffolder.run().catch((err) => {
22
+ console.error('Error:', err.message);
23
+ process.exit(1);
24
+ });
25
+ //# sourceMappingURL=create.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create.js","sourceRoot":"","sources":["../../bin/create.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC,+BAA+B;AAC/B,mFAAmF;AACnF,gFAAgF;AAChF,MAAM,UAAU,GAAG,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;AAClF,MAAM,WAAW,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;AACrF,MAAM,YAAY,GAAG,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;AAEpD,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;IAC9B,OAAO,CAAC,KAAK,CAAC,yCAAyC,EAAE,YAAY,CAAC,CAAC;IACvE,OAAO,CAAC,KAAK,CAAC,wEAAwE,CAAC,CAAC;IACxF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;AAEhC,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;AAC3D,UAAU,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,GAAU,EAAE,EAAE;IACpC,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,22 @@
1
+ type DbConfig = {
2
+ type: 'local';
3
+ } | {
4
+ type: 'turso';
5
+ url: string;
6
+ authToken: string;
7
+ };
8
+ export type PromptFn = (question: string) => Promise<string>;
9
+ export declare class Scaffolder {
10
+ private readonly targetDir;
11
+ private readonly templatesDir;
12
+ private readonly prompt;
13
+ constructor(targetDir: string, templatesDir: string, prompt?: PromptFn);
14
+ run: () => Promise<void>;
15
+ private checkPnpm;
16
+ private promptDbConfig;
17
+ private scaffoldProductSystem;
18
+ private scaffoldSkills;
19
+ skillHasModifications: (sourceDir: string, targetDir: string) => boolean;
20
+ generateEnv: (productSystemDir: string, dbConfig: DbConfig) => void;
21
+ }
22
+ export {};
@@ -0,0 +1,120 @@
1
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync, } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { createInterface } from 'node:readline';
4
+ import { execSync } from 'node:child_process';
5
+ const createReadlinePrompt = () => {
6
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
7
+ return (question) => new Promise(resolve => rl.question(question, answer => resolve(answer)));
8
+ };
9
+ export class Scaffolder {
10
+ targetDir;
11
+ templatesDir;
12
+ prompt;
13
+ constructor(targetDir, templatesDir, prompt) {
14
+ this.targetDir = targetDir;
15
+ this.templatesDir = templatesDir;
16
+ this.prompt = prompt ?? createReadlinePrompt();
17
+ }
18
+ run = async () => {
19
+ this.checkPnpm();
20
+ const productSystemTarget = join(this.targetDir, 'product-system');
21
+ const skillsTarget = join(this.targetDir, '.claude', 'skills');
22
+ const isRerun = existsSync(productSystemTarget);
23
+ let dbConfig = null;
24
+ if (isRerun) {
25
+ console.log('product-system/ already exists. Updating code, preserving data/ and .env...');
26
+ }
27
+ else {
28
+ dbConfig = await this.promptDbConfig();
29
+ }
30
+ this.scaffoldProductSystem(productSystemTarget);
31
+ await this.scaffoldSkills(skillsTarget, isRerun);
32
+ if (!isRerun && dbConfig) {
33
+ this.generateEnv(productSystemTarget, dbConfig);
34
+ }
35
+ console.log('\nRunning pnpm install...');
36
+ execSync('pnpm install', { cwd: productSystemTarget, stdio: 'inherit' });
37
+ console.log('\nRunning db:migrate...');
38
+ execSync('pnpm db:migrate', { cwd: productSystemTarget, stdio: 'inherit' });
39
+ console.log('\nproduct-system is ready!');
40
+ console.log(' cd product-system && pnpm dev');
41
+ };
42
+ checkPnpm = () => {
43
+ try {
44
+ execSync('pnpm --version', { stdio: 'pipe' });
45
+ }
46
+ catch {
47
+ console.error('Error: pnpm is required but not found.');
48
+ console.error('Install it from: https://pnpm.io/installation');
49
+ process.exit(1);
50
+ }
51
+ };
52
+ promptDbConfig = async () => {
53
+ console.log('\nDatabase configuration:');
54
+ console.log(' 1. Local SQLite (default — zero config, start immediately)');
55
+ console.log(' 2. Turso cloud (cross-device sync, team collaboration)');
56
+ const choice = await this.prompt('\nSelect [1/2, default=1]: ');
57
+ if (choice.trim() === '2') {
58
+ const url = await this.prompt('TURSO_DATABASE_URL: ');
59
+ const authToken = await this.prompt('TURSO_AUTH_TOKEN: ');
60
+ return { type: 'turso', url: url.trim(), authToken: authToken.trim() };
61
+ }
62
+ return { type: 'local' };
63
+ };
64
+ scaffoldProductSystem = (targetDir) => {
65
+ const sourceDir = join(this.templatesDir, 'product-system');
66
+ mkdirSync(targetDir, { recursive: true });
67
+ cpSync(sourceDir, targetDir, {
68
+ recursive: true,
69
+ force: true,
70
+ filter: (src) => {
71
+ const base = src.split('/').pop() ?? '';
72
+ return base !== 'node_modules';
73
+ },
74
+ });
75
+ console.log('Scaffolded product-system/');
76
+ };
77
+ scaffoldSkills = async (targetDir, isRerun) => {
78
+ const sourceSkillsDir = join(this.templatesDir, 'skills');
79
+ if (!existsSync(sourceSkillsDir))
80
+ return;
81
+ mkdirSync(targetDir, { recursive: true });
82
+ const skillNames = readdirSync(sourceSkillsDir).filter(name => statSync(join(sourceSkillsDir, name)).isDirectory());
83
+ for (const skillName of skillNames) {
84
+ const sourceSkillDir = join(sourceSkillsDir, skillName);
85
+ const targetSkillDir = join(targetDir, skillName);
86
+ if (isRerun && existsSync(targetSkillDir)) {
87
+ const hasModifications = this.skillHasModifications(sourceSkillDir, targetSkillDir);
88
+ if (hasModifications) {
89
+ const answer = await this.prompt(`Skill "${skillName}" has local modifications. Overwrite? [y/N]: `);
90
+ if (answer.trim().toLowerCase() !== 'y') {
91
+ console.log(` Kept local: ${skillName}`);
92
+ continue;
93
+ }
94
+ }
95
+ }
96
+ mkdirSync(targetSkillDir, { recursive: true });
97
+ cpSync(sourceSkillDir, targetSkillDir, { recursive: true, force: true });
98
+ console.log(` ${isRerun ? 'Updated' : 'Installed'}: ${skillName}`);
99
+ }
100
+ };
101
+ skillHasModifications = (sourceDir, targetDir) => {
102
+ const sourceFile = join(sourceDir, 'SKILL.md');
103
+ const targetFile = join(targetDir, 'SKILL.md');
104
+ if (!existsSync(targetFile) || !existsSync(sourceFile))
105
+ return false;
106
+ return readFileSync(sourceFile, 'utf-8') !== readFileSync(targetFile, 'utf-8');
107
+ };
108
+ generateEnv = (productSystemDir, dbConfig) => {
109
+ const lines = [];
110
+ if (dbConfig.type === 'local') {
111
+ lines.push('# Database — local SQLite', '# The database file is created automatically at data/local.db', '# To upgrade to Turso, uncomment and fill in the variables below:', '#TURSO_DATABASE_URL=libsql://your-db.turso.io', '#TURSO_AUTH_TOKEN=your-token', '');
112
+ }
113
+ else {
114
+ lines.push('# Database — Turso cloud', `TURSO_DATABASE_URL=${dbConfig.url}`, `TURSO_AUTH_TOKEN=${dbConfig.authToken}`, '');
115
+ }
116
+ writeFileSync(join(productSystemDir, '.env'), lines.join('\n'));
117
+ console.log('Generated .env');
118
+ };
119
+ }
120
+ //# sourceMappingURL=scaffolder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffolder.js","sourceRoot":"","sources":["../../src/scaffolder.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@assistkick/create",
3
+ "version": "1.0.0",
4
+ "description": "Scaffold the product-system into any project",
5
+ "type": "module",
6
+ "bin": {
7
+ "@assistkick/create": "./dist/bin/create.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "templates"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "prepare_templates": "bash scripts/prepare_templates.sh",
16
+ "prepublishOnly": "bash scripts/prepare_templates.sh && pnpm build",
17
+ "test": "tsx --test tests/**/*.test.ts"
18
+ },
19
+ "devDependencies": {
20
+ "@types/node": "^25.3.3",
21
+ "tsx": "^4.21.0",
22
+ "typescript": "^5.9.3"
23
+ }
24
+ }
@@ -0,0 +1,8 @@
1
+ # Turso database credentials
2
+ TURSO_DATABASE_URL=libsql://your-database.turso.io
3
+ TURSO_AUTH_TOKEN=your-auth-token
4
+ JWT_SECRET=your-jwt-secret
5
+ NODE_ENV=development
6
+ RESEND_API_KEY=your-resend-api-key
7
+ APP_BASE_URL=http://localhost:3000
8
+ EMAIL_FROM=noreply@yourdomain.com
@@ -0,0 +1,45 @@
1
+ # Interview System
2
+
3
+ AI requirements interview system that builds a specification knowledge graph through structured interviews.
4
+
5
+ ## Tech Stack
6
+
7
+ - **Runtime:** Node.js with TypeScript (ESM)
8
+ - **Package manager:** pnpm (monorepo with workspaces)
9
+ - **Backend:** Express 5, tsx (dev/run), WebSockets (ws), node-pty
10
+ - **Frontend:** React 19, Vite, React Router, D3, xterm.js
11
+ - **Database:** Turso (libSQL), Drizzle ORM
12
+ - **Auth:** jose (JWT), bcryptjs
13
+ - **Email:** Resend
14
+
15
+ ## Project Structure
16
+
17
+ ```
18
+ product-system/
19
+ packages/
20
+ backend/ — Express API server (@interview-system/backend)
21
+ frontend/ — React SPA (@interview-system/frontend)
22
+ shared/ — DB schema, migrations, shared types (@interview-system/shared)
23
+ ```
24
+
25
+ ## Commands
26
+
27
+ - `pnpm dev` — run backend + frontend concurrently
28
+ - `pnpm build` — build all packages
29
+ - `pnpm test` — run tests
30
+
31
+ ## Database Migrations
32
+
33
+ Schema lives in `packages/shared/db/schema.ts`. Drizzle config is in `packages/shared/drizzle.config.js`.
34
+
35
+ To apply schema changes:
36
+
37
+ 1. Edit the schema in `packages/shared/db/schema.ts`
38
+ 2. Generate a migration: `pnpm db:generate`
39
+ 3. Apply the migration: `pnpm db:migrate`
40
+
41
+ **Rules:**
42
+ - Always use `pnpm db:generate` and `pnpm db:migrate` — never write SQL migration files by hand
43
+ - Never write JS/TS scripts that execute raw SQL for migrations
44
+ - Never use `--force` or `--push` with drizzle-kit
45
+ - Let Drizzle manage the migration lifecycle entirely
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "interview-system",
3
+ "version": "1.0.0",
4
+ "description": "AI requirements interview system — builds a specification knowledge graph through structured interviews",
5
+ "private": true,
6
+ "type": "module",
7
+ "scripts": {
8
+ "dev": "concurrently -n backend,frontend -c blue,green \"pnpm --filter @interview-system/backend dev\" \"pnpm --filter @interview-system/frontend dev\"",
9
+ "build": "pnpm -r build",
10
+ "start": "tsx packages/backend/src/server.ts",
11
+ "test": "tsx --test tests/**/*.test.ts",
12
+ "clean": "pnpm -r clean",
13
+ "db:migrate": "pnpm --filter @interview-system/shared db:migrate",
14
+ "db:rollback": "pnpm --filter @interview-system/shared db:rollback",
15
+ "db:generate": "pnpm --filter @interview-system/shared db:generate"
16
+ },
17
+ "pnpm": {
18
+ "onlyBuiltDependencies": ["esbuild", "node-pty"]
19
+ },
20
+ "devDependencies": {
21
+ "@libsql/client": "^0.17.0",
22
+ "@types/bcryptjs": "^3.0.0",
23
+ "@types/node": "^25.3.3",
24
+ "bcryptjs": "^3.0.3",
25
+ "concurrently": "^9.1.2",
26
+ "drizzle-orm": "^0.45.1",
27
+ "gray-matter": "^4.0.3",
28
+ "jose": "^6.1.3",
29
+ "tsx": "^4.21.0",
30
+ "typescript": "^5.9.3"
31
+ }
32
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@interview-system/backend",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "tsx watch src/server.ts --verbose",
8
+ "build": "echo 'Backend runs via tsx — no build step needed'",
9
+ "start": "tsx src/server.ts",
10
+ "clean": "echo 'No build artifacts'",
11
+ "postinstall": "chmod +x node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper 2>/dev/null || true"
12
+ },
13
+ "dependencies": {
14
+ "@interview-system/shared": "workspace:*",
15
+ "bcryptjs": "^3.0.3",
16
+ "cookie-parser": "^1.4.7",
17
+ "cors": "^2.8.5",
18
+ "dotenv": "^17.3.1",
19
+ "drizzle-orm": "^0.45.1",
20
+ "express": "^5.1.0",
21
+ "gray-matter": "^4.0.3",
22
+ "jose": "^6.1.3",
23
+ "node-pty": "^1.1.0",
24
+ "resend": "^6.9.3",
25
+ "ws": "^8.19.0"
26
+ },
27
+ "devDependencies": {
28
+ "@types/bcryptjs": "^3.0.0",
29
+ "@types/cookie-parser": "^1.4.10",
30
+ "@types/cors": "^2.8.17",
31
+ "@types/express": "^5.0.0",
32
+ "@types/node": "^25.3.3",
33
+ "@types/ws": "^8.18.1",
34
+ "tsx": "^4.21.0",
35
+ "typescript": "^5.9.3"
36
+ }
37
+ }
@@ -0,0 +1,86 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { AuthMiddleware } from './auth_middleware.ts';
4
+ import { AuthService } from '../services/auth_service.ts';
5
+
6
+ const createAuthService = () => new AuthService({
7
+ jwtSecret: 'test-secret-at-least-32-chars-long!!',
8
+ isProduction: false,
9
+ });
10
+
11
+ const createMockRes = () => {
12
+ let statusCode = 200;
13
+ let body: any = null;
14
+ return {
15
+ status: (code: number) => {
16
+ statusCode = code;
17
+ return { json: (data: any) => { body = data; } };
18
+ },
19
+ json: (data: any) => { body = data; },
20
+ getStatus: () => statusCode,
21
+ getBody: () => body,
22
+ };
23
+ };
24
+
25
+ describe('AuthMiddleware', () => {
26
+ describe('requireAuth', () => {
27
+ it('calls next when a valid access token is present', async () => {
28
+ const authService = createAuthService();
29
+ const middleware = new AuthMiddleware({ authService });
30
+ const token = await authService.generateAccessToken('user-1', 'test@example.com', 'admin');
31
+
32
+ let nextCalled = false;
33
+ const req = { cookies: { access_token: token } } as any;
34
+ const res = createMockRes() as any;
35
+
36
+ await middleware.requireAuth(req, res, () => { nextCalled = true; });
37
+
38
+ assert.equal(nextCalled, true);
39
+ assert.deepEqual(req.user, { id: 'user-1', email: 'test@example.com', role: 'admin' });
40
+ });
41
+
42
+ it('returns 401 when no access token cookie is present', async () => {
43
+ const authService = createAuthService();
44
+ const middleware = new AuthMiddleware({ authService });
45
+
46
+ let nextCalled = false;
47
+ const req = { cookies: {} } as any;
48
+ const res = createMockRes() as any;
49
+
50
+ await middleware.requireAuth(req, res, () => { nextCalled = true; });
51
+
52
+ assert.equal(nextCalled, false);
53
+ assert.equal(res.getStatus(), 401);
54
+ assert.deepEqual(res.getBody(), { error: 'Not authenticated' });
55
+ });
56
+
57
+ it('returns 401 when access token is invalid', async () => {
58
+ const authService = createAuthService();
59
+ const middleware = new AuthMiddleware({ authService });
60
+
61
+ let nextCalled = false;
62
+ const req = { cookies: { access_token: 'invalid-token' } } as any;
63
+ const res = createMockRes() as any;
64
+
65
+ await middleware.requireAuth(req, res, () => { nextCalled = true; });
66
+
67
+ assert.equal(nextCalled, false);
68
+ assert.equal(res.getStatus(), 401);
69
+ assert.deepEqual(res.getBody(), { error: 'Invalid or expired token' });
70
+ });
71
+
72
+ it('returns 401 when cookies object is missing', async () => {
73
+ const authService = createAuthService();
74
+ const middleware = new AuthMiddleware({ authService });
75
+
76
+ let nextCalled = false;
77
+ const req = {} as any;
78
+ const res = createMockRes() as any;
79
+
80
+ await middleware.requireAuth(req, res, () => { nextCalled = true; });
81
+
82
+ assert.equal(nextCalled, false);
83
+ assert.equal(res.getStatus(), 401);
84
+ });
85
+ });
86
+ });
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Auth middleware — verifies JWT access token from HTTP-only cookie.
3
+ * Returns 401 for unauthenticated requests to protected API routes.
4
+ */
5
+
6
+ import type { Request, Response, NextFunction } from 'express';
7
+ import type { AuthService } from '../services/auth_service.js';
8
+
9
+ interface AuthMiddlewareDeps {
10
+ authService: AuthService;
11
+ }
12
+
13
+ export class AuthMiddleware {
14
+ private readonly authService: AuthService;
15
+
16
+ constructor({ authService }: AuthMiddlewareDeps) {
17
+ this.authService = authService;
18
+ }
19
+
20
+ requireAuth = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
21
+ const token = (req as any).cookies?.['access_token'];
22
+ if (!token) {
23
+ res.status(401).json({ error: 'Not authenticated' });
24
+ return;
25
+ }
26
+
27
+ try {
28
+ const payload = await this.authService.verifyToken(token);
29
+ (req as any).user = { id: payload.sub, email: payload.email, role: payload.role };
30
+ next();
31
+ } catch {
32
+ res.status(401).json({ error: 'Invalid or expired token' });
33
+ }
34
+ };
35
+ }