@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.
- package/dist/bin/create.d.ts +2 -0
- package/dist/bin/create.js +25 -0
- package/dist/bin/create.js.map +1 -0
- package/dist/src/scaffolder.d.ts +22 -0
- package/dist/src/scaffolder.js +120 -0
- package/dist/src/scaffolder.js.map +1 -0
- package/package.json +24 -0
- package/templates/product-system/.env.example +8 -0
- package/templates/product-system/CLAUDE.md +45 -0
- package/templates/product-system/package.json +32 -0
- package/templates/product-system/packages/backend/package.json +37 -0
- package/templates/product-system/packages/backend/src/middleware/auth_middleware.test.ts +86 -0
- package/templates/product-system/packages/backend/src/middleware/auth_middleware.ts +35 -0
- package/templates/product-system/packages/backend/src/routes/auth.ts +463 -0
- package/templates/product-system/packages/backend/src/routes/coherence.ts +187 -0
- package/templates/product-system/packages/backend/src/routes/graph.ts +67 -0
- package/templates/product-system/packages/backend/src/routes/kanban.ts +201 -0
- package/templates/product-system/packages/backend/src/routes/pipeline.ts +41 -0
- package/templates/product-system/packages/backend/src/routes/projects.ts +122 -0
- package/templates/product-system/packages/backend/src/routes/users.ts +97 -0
- package/templates/product-system/packages/backend/src/server.ts +159 -0
- package/templates/product-system/packages/backend/src/services/auth_service.test.ts +115 -0
- package/templates/product-system/packages/backend/src/services/auth_service.ts +82 -0
- package/templates/product-system/packages/backend/src/services/coherence-review.ts +339 -0
- package/templates/product-system/packages/backend/src/services/email_service.ts +75 -0
- package/templates/product-system/packages/backend/src/services/init.ts +80 -0
- package/templates/product-system/packages/backend/src/services/invitation_service.test.ts +235 -0
- package/templates/product-system/packages/backend/src/services/invitation_service.ts +193 -0
- package/templates/product-system/packages/backend/src/services/password_reset_service.test.ts +151 -0
- package/templates/product-system/packages/backend/src/services/password_reset_service.ts +135 -0
- package/templates/product-system/packages/backend/src/services/project_service.test.ts +215 -0
- package/templates/product-system/packages/backend/src/services/project_service.ts +171 -0
- package/templates/product-system/packages/backend/src/services/pty_session_manager.test.ts +88 -0
- package/templates/product-system/packages/backend/src/services/pty_session_manager.ts +279 -0
- package/templates/product-system/packages/backend/src/services/terminal_ws_handler.ts +133 -0
- package/templates/product-system/packages/backend/src/services/user_management_service.test.ts +158 -0
- package/templates/product-system/packages/backend/src/services/user_management_service.ts +128 -0
- package/templates/product-system/packages/backend/tsconfig.json +22 -0
- package/templates/product-system/packages/frontend/index.html +13 -0
- package/templates/product-system/packages/frontend/package-lock.json +2666 -0
- package/templates/product-system/packages/frontend/package.json +30 -0
- package/templates/product-system/packages/frontend/public/favicon.svg +16 -0
- package/templates/product-system/packages/frontend/src/App.tsx +29 -0
- package/templates/product-system/packages/frontend/src/api/client.ts +386 -0
- package/templates/product-system/packages/frontend/src/api/client_projects.test.ts +104 -0
- package/templates/product-system/packages/frontend/src/api/client_refresh.test.ts +145 -0
- package/templates/product-system/packages/frontend/src/components/CoherenceView.tsx +414 -0
- package/templates/product-system/packages/frontend/src/components/GraphLegend.tsx +124 -0
- package/templates/product-system/packages/frontend/src/components/GraphSettings.tsx +112 -0
- package/templates/product-system/packages/frontend/src/components/GraphView.tsx +370 -0
- package/templates/product-system/packages/frontend/src/components/InviteUserDialog.tsx +85 -0
- package/templates/product-system/packages/frontend/src/components/KanbanView.tsx +470 -0
- package/templates/product-system/packages/frontend/src/components/LoginPage.tsx +116 -0
- package/templates/product-system/packages/frontend/src/components/ProjectSelector.tsx +187 -0
- package/templates/product-system/packages/frontend/src/components/QaIssueSheet.tsx +192 -0
- package/templates/product-system/packages/frontend/src/components/SidePanel.tsx +231 -0
- package/templates/product-system/packages/frontend/src/components/TerminalView.tsx +200 -0
- package/templates/product-system/packages/frontend/src/components/Toolbar.tsx +84 -0
- package/templates/product-system/packages/frontend/src/components/UsersView.tsx +249 -0
- package/templates/product-system/packages/frontend/src/constants/graph.ts +191 -0
- package/templates/product-system/packages/frontend/src/hooks/useAuth.tsx +54 -0
- package/templates/product-system/packages/frontend/src/hooks/useGraph.ts +27 -0
- package/templates/product-system/packages/frontend/src/hooks/useKanban.ts +21 -0
- package/templates/product-system/packages/frontend/src/hooks/useProjects.ts +86 -0
- package/templates/product-system/packages/frontend/src/hooks/useTheme.ts +26 -0
- package/templates/product-system/packages/frontend/src/hooks/useToast.tsx +62 -0
- package/templates/product-system/packages/frontend/src/hooks/use_projects_logic.test.ts +61 -0
- package/templates/product-system/packages/frontend/src/main.tsx +12 -0
- package/templates/product-system/packages/frontend/src/pages/accept_invitation_page.tsx +167 -0
- package/templates/product-system/packages/frontend/src/pages/forgot_password_page.tsx +100 -0
- package/templates/product-system/packages/frontend/src/pages/register_page.tsx +137 -0
- package/templates/product-system/packages/frontend/src/pages/reset_password_page.tsx +146 -0
- package/templates/product-system/packages/frontend/src/routes/ProtectedRoute.tsx +12 -0
- package/templates/product-system/packages/frontend/src/routes/accept_invitation.tsx +14 -0
- package/templates/product-system/packages/frontend/src/routes/dashboard.tsx +221 -0
- package/templates/product-system/packages/frontend/src/routes/forgot_password.tsx +13 -0
- package/templates/product-system/packages/frontend/src/routes/login.tsx +14 -0
- package/templates/product-system/packages/frontend/src/routes/register.tsx +14 -0
- package/templates/product-system/packages/frontend/src/routes/reset_password.tsx +13 -0
- package/templates/product-system/packages/frontend/src/styles/index.css +3358 -0
- package/templates/product-system/packages/frontend/src/utils/auth_validation.test.ts +51 -0
- package/templates/product-system/packages/frontend/src/utils/auth_validation.ts +19 -0
- package/templates/product-system/packages/frontend/src/utils/login_validation.test.ts +61 -0
- package/templates/product-system/packages/frontend/src/utils/login_validation.ts +24 -0
- package/templates/product-system/packages/frontend/src/utils/logout.test.ts +63 -0
- package/templates/product-system/packages/frontend/src/utils/node_sizing.test.ts +62 -0
- package/templates/product-system/packages/frontend/src/utils/node_sizing.ts +24 -0
- package/templates/product-system/packages/frontend/src/utils/task_status.test.ts +53 -0
- package/templates/product-system/packages/frontend/src/utils/task_status.ts +14 -0
- package/templates/product-system/packages/frontend/tsconfig.json +21 -0
- package/templates/product-system/packages/frontend/vite.config.ts +20 -0
- package/templates/product-system/packages/shared/.env.example +3 -0
- package/templates/product-system/packages/shared/README.md +1 -0
- package/templates/product-system/packages/shared/db/migrate.ts +32 -0
- package/templates/product-system/packages/shared/db/migrations/0000_dashing_gorgon.sql +128 -0
- package/templates/product-system/packages/shared/db/migrations/meta/0000_snapshot.json +819 -0
- package/templates/product-system/packages/shared/db/migrations/meta/_journal.json +13 -0
- package/templates/product-system/packages/shared/db/schema.ts +137 -0
- package/templates/product-system/packages/shared/drizzle.config.js +14 -0
- package/templates/product-system/packages/shared/lib/claude-service.ts +215 -0
- package/templates/product-system/packages/shared/lib/coherence.ts +278 -0
- package/templates/product-system/packages/shared/lib/completeness.ts +30 -0
- package/templates/product-system/packages/shared/lib/constants.ts +327 -0
- package/templates/product-system/packages/shared/lib/db.ts +81 -0
- package/templates/product-system/packages/shared/lib/git_workflow.ts +110 -0
- package/templates/product-system/packages/shared/lib/graph.ts +186 -0
- package/templates/product-system/packages/shared/lib/kanban.ts +161 -0
- package/templates/product-system/packages/shared/lib/markdown.ts +205 -0
- package/templates/product-system/packages/shared/lib/pipeline-state-store.ts +124 -0
- package/templates/product-system/packages/shared/lib/pipeline.ts +489 -0
- package/templates/product-system/packages/shared/lib/prompt_builder.ts +170 -0
- package/templates/product-system/packages/shared/lib/relevance_search.ts +159 -0
- package/templates/product-system/packages/shared/lib/session.ts +152 -0
- package/templates/product-system/packages/shared/lib/validator.ts +117 -0
- package/templates/product-system/packages/shared/lib/work_summary_parser.ts +130 -0
- package/templates/product-system/packages/shared/package.json +30 -0
- package/templates/product-system/packages/shared/scripts/assign-project.ts +52 -0
- package/templates/product-system/packages/shared/tools/add_edge.ts +61 -0
- package/templates/product-system/packages/shared/tools/add_node.ts +101 -0
- package/templates/product-system/packages/shared/tools/end_session.ts +87 -0
- package/templates/product-system/packages/shared/tools/get_gaps.ts +87 -0
- package/templates/product-system/packages/shared/tools/get_kanban.ts +125 -0
- package/templates/product-system/packages/shared/tools/get_node.ts +78 -0
- package/templates/product-system/packages/shared/tools/get_status.ts +98 -0
- package/templates/product-system/packages/shared/tools/migrate_to_turso.ts +385 -0
- package/templates/product-system/packages/shared/tools/move_card.ts +143 -0
- package/templates/product-system/packages/shared/tools/rebuild_index.ts +77 -0
- package/templates/product-system/packages/shared/tools/remove_edge.ts +59 -0
- package/templates/product-system/packages/shared/tools/remove_node.ts +96 -0
- package/templates/product-system/packages/shared/tools/resolve_question.ts +75 -0
- package/templates/product-system/packages/shared/tools/search_nodes.ts +106 -0
- package/templates/product-system/packages/shared/tools/start_session.ts +144 -0
- package/templates/product-system/packages/shared/tools/update_node.ts +133 -0
- package/templates/product-system/packages/shared/tsconfig.json +24 -0
- package/templates/product-system/pnpm-workspace.yaml +2 -0
- package/templates/product-system/smoke_test.ts +219 -0
- package/templates/product-system/tests/coherence_review.test.ts +562 -0
- package/templates/product-system/tests/db_sqlite_fallback.test.ts +75 -0
- package/templates/product-system/tests/edge_type_color_coding.test.ts +147 -0
- package/templates/product-system/tests/emit-tool-use-events.test.ts +85 -0
- package/templates/product-system/tests/feature_kind.test.ts +139 -0
- package/templates/product-system/tests/gap_indicators.test.ts +199 -0
- package/templates/product-system/tests/graceful_init.test.ts +142 -0
- package/templates/product-system/tests/graph_legend.test.ts +314 -0
- package/templates/product-system/tests/graph_settings_sheet.test.ts +804 -0
- package/templates/product-system/tests/hide_defined_filter.test.ts +205 -0
- package/templates/product-system/tests/kanban.test.ts +529 -0
- package/templates/product-system/tests/neighborhood_focus.test.ts +132 -0
- package/templates/product-system/tests/node_search.test.ts +340 -0
- package/templates/product-system/tests/node_sizing.test.ts +170 -0
- package/templates/product-system/tests/node_type_toggle_filters.test.ts +285 -0
- package/templates/product-system/tests/node_type_visual_encoding.test.ts +103 -0
- package/templates/product-system/tests/pipeline-state-store.test.ts +268 -0
- package/templates/product-system/tests/pipeline-unit.test.ts +593 -0
- package/templates/product-system/tests/pipeline.test.ts +195 -0
- package/templates/product-system/tests/pipeline_stats_all_cards.test.ts +193 -0
- package/templates/product-system/tests/play_all.test.ts +296 -0
- package/templates/product-system/tests/qa_issue_sheet.test.ts +464 -0
- package/templates/product-system/tests/relevance_search.test.ts +186 -0
- package/templates/product-system/tests/search_reorder.test.ts +88 -0
- package/templates/product-system/tests/serve_ui.test.ts +281 -0
- package/templates/product-system/tests/serve_ui_drizzle.test.ts +114 -0
- package/templates/product-system/tests/session_context_recall.test.ts +135 -0
- package/templates/product-system/tests/side_panel.test.ts +345 -0
- package/templates/product-system/tests/spec_completeness_label.test.ts +69 -0
- package/templates/product-system/tests/url_routing_test.ts +122 -0
- package/templates/product-system/tests/user_login.test.ts +150 -0
- package/templates/product-system/tests/user_registration.test.ts +205 -0
- package/templates/product-system/tests/web_terminal.test.ts +572 -0
- package/templates/product-system/tests/work_summary.test.ts +211 -0
- package/templates/product-system/tests/zoom_pan.test.ts +43 -0
- package/templates/product-system/tsconfig.json +24 -0
- package/templates/skills/product-bootstrap/SKILL.md +312 -0
- package/templates/skills/product-code-reviewer/SKILL.md +147 -0
- package/templates/skills/product-debugger/SKILL.md +206 -0
- package/templates/skills/product-debugger/references/agent-browser.md +1156 -0
- package/templates/skills/product-developer/SKILL.md +182 -0
- package/templates/skills/product-interview/SKILL.md +220 -0
|
@@ -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
|
+
}
|