@relipa/ai-flow-kit 0.0.4 → 0.0.5-beta.1

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 (41) hide show
  1. package/{docs/README.md → README.md} +25 -18
  2. package/bin/aiflow.js +77 -7
  3. package/custom/skills/{validate-ticket → read-study-requirement}/SKILL.md +27 -17
  4. package/custom/skills/review-plan/SKILL.md +1 -1
  5. package/custom/templates/shared/gate-workflow.md +88 -75
  6. package/custom/templates/tools/claude.md +1 -1
  7. package/custom/templates/tools/copilot.md +1 -1
  8. package/custom/templates/tools/cursor.md +1 -1
  9. package/custom/templates/tools/gemini.md +1 -1
  10. package/custom/templates/tools/generic.md +1 -1
  11. package/docs/{AIFLOW.md → common/AIFLOW.md} +462 -458
  12. package/docs/{CHANGELOG.md → common/CHANGELOG.md} +132 -100
  13. package/docs/{cli-reference.md → common/cli-reference.md} +98 -28
  14. package/docs/{troubleshooting.md → common/troubleshooting.md} +15 -0
  15. package/docs/project/ARCHITECTURE.md +28 -0
  16. package/package.json +7 -5
  17. package/scripts/context.js +1 -1
  18. package/scripts/guide.js +16 -0
  19. package/scripts/hooks/session-start.js +145 -141
  20. package/scripts/init.js +168 -44
  21. package/scripts/prompt.js +431 -402
  22. package/scripts/telemetry/cli.js +243 -0
  23. package/scripts/telemetry/config.js +91 -0
  24. package/scripts/telemetry/crypto.js +20 -0
  25. package/scripts/telemetry/flush.js +162 -0
  26. package/scripts/telemetry/record.js +138 -0
  27. package/scripts/use.js +74 -31
  28. package/upstream/skills/using-superpowers/SKILL.md +14 -0
  29. package/docs/IMPLEMENTATION_SUMMARY.md +0 -330
  30. package/docs/architecture.md +0 -394
  31. package/docs/developer-overview.md +0 -126
  32. package/upstream/tests/brainstorm-server/package-lock.json +0 -36
  33. /package/docs/{QUICK_START.md → common/QUICK_START.md} +0 -0
  34. /package/docs/{ai-integration.md → common/ai-integration.md} +0 -0
  35. /package/docs/{configuration.md → common/configuration.md} +0 -0
  36. /package/docs/{getting-started.md → common/getting-started.md} +0 -0
  37. /package/docs/{workflows → common/workflows}/bug-fix.md +0 -0
  38. /package/docs/{workflows → common/workflows}/feature.md +0 -0
  39. /package/docs/{workflows → common/workflows}/impact-analysis.md +0 -0
  40. /package/docs/{workflows → common/workflows}/investigation.md +0 -0
  41. /package/docs/{workflows → common/workflows}/refactor.md +0 -0
@@ -1,141 +1,145 @@
1
- #!/usr/bin/env node
2
- /**
3
- * SessionStart hook for ai-flow-kit
4
- * Injects:
5
- * 1. using-superpowers skill content
6
- * 2. Active ticket context + gate workflow (if context exists)
7
- * Cross-platform replacement for the bash session-start hook.
8
- */
9
-
10
- const fs = require('fs');
11
- const path = require('path');
12
-
13
- // This script lives at .claude/hooks/session-start.js
14
- // Project root is 3 levels up
15
- const projectRoot = path.resolve(__dirname, '..', '..', '..');
16
- const skillPath = path.join(projectRoot, '.claude', 'skills', 'using-superpowers', 'SKILL.md');
17
- const contextPath = path.join(projectRoot, '.aiflow', 'context', 'current.json');
18
-
19
- // ── 1. Load superpowers skill ──────────────────────────────────
20
- let skillContent = '';
21
- try {
22
- skillContent = fs.readFileSync(skillPath, 'utf-8');
23
- } catch (_) {
24
- // Skill file missing — continue without it
25
- }
26
-
27
- // ── 2. Load active ticket context ──────────────────────────────
28
- let contextBlock = '';
29
- try {
30
- if (fs.existsSync(contextPath)) {
31
- const ctx = JSON.parse(fs.readFileSync(contextPath, 'utf-8'));
32
- if (ctx.taskId && ctx.title) {
33
- contextBlock = buildContextPrompt(ctx);
34
- }
35
- }
36
- } catch (_) {
37
- // Context missing or invalid — continue without it
38
- }
39
-
40
- // ── 3. Combine and output ──────────────────────────────────────
41
- const parts = [];
42
-
43
- if (skillContent) {
44
- parts.push(`<EXTREMELY_IMPORTANT>\nYou have superpowers.\n\n**Below is the full content of your 'superpowers:using-superpowers' skill - your introduction to using skills. For all other skills, use the 'Skill' tool:**\n\n${skillContent}\n\n</EXTREMELY_IMPORTANT>`);
45
- }
46
-
47
- if (contextBlock) {
48
- parts.push(contextBlock);
49
- }
50
-
51
- const combined = parts.join('\n\n');
52
-
53
- // Escape for JSON string embedding
54
- const escaped = combined
55
- .replace(/\\/g, '\\\\')
56
- .replace(/"/g, '\\"')
57
- .replace(/\n/g, '\\n')
58
- .replace(/\r/g, '\\r')
59
- .replace(/\t/g, '\\t');
60
-
61
- process.stdout.write(JSON.stringify({
62
- hookSpecificOutput: {
63
- hookEventName: 'SessionStart',
64
- additionalContext: escaped
65
- }
66
- }));
67
-
68
- process.exit(0);
69
-
70
- // ── Helper ─────────────────────────────────────────────────────
71
-
72
- function buildContextPrompt(ctx) {
73
- const taskType = ctx.taskType || 'feature';
74
-
75
- const lines = [];
76
- lines.push('<ACTIVE_TASK>');
77
- lines.push(`**Active Ticket:** ${ctx.taskId} ${ctx.title}`);
78
- lines.push(`**Type:** ${taskType}`);
79
- lines.push(`**Status:** ${ctx.status || 'Unknown'}`);
80
- lines.push(`**Assignee:** ${ctx.assignee || 'Unknown'}`);
81
- lines.push('');
82
-
83
- if (ctx.description) {
84
- lines.push('**Description:**');
85
- lines.push(ctx.description.substring(0, 2000));
86
- lines.push('');
87
- }
88
-
89
- if (ctx.acceptanceCriteria && ctx.acceptanceCriteria.length > 0) {
90
- lines.push('**Acceptance Criteria:**');
91
- for (const c of ctx.acceptanceCriteria) {
92
- lines.push(`- ${c}`);
93
- }
94
- lines.push('');
95
- }
96
-
97
- if (ctx.comments && ctx.comments.length > 0) {
98
- lines.push(`**Comments (${ctx.comments.length}):**`);
99
- // Include last 5 comments to keep context manageable
100
- const recent = ctx.comments.slice(-5);
101
- for (const c of recent) {
102
- lines.push(c);
103
- lines.push('');
104
- }
105
- }
106
-
107
- if (ctx.context && ctx.context.files && ctx.context.files.length > 0) {
108
- lines.push('**Related Files:**');
109
- for (const f of ctx.context.files) {
110
- lines.push(`- \`${f}\``);
111
- }
112
- lines.push('');
113
- }
114
-
115
- // ── Gate workflow instruction ──
116
- lines.push('---');
117
- lines.push('');
118
- lines.push('**AUTO-START GATE 1: You MUST begin analyzing this ticket immediately.**');
119
- lines.push('');
120
- lines.push(`INVOKE the \`validate-ticket\` skill NOW for ticket ${ctx.taskId}.`);
121
- lines.push('');
122
- lines.push('Gate 1 process:');
123
- lines.push('1. Read the ticket context above');
124
- lines.push('2. Read CLAUDE.md understand project architecture and conventions');
125
- lines.push('3. Read source code — identify related files, data flow, patterns');
126
- lines.push('4. If anything is unclear — ask ONE question at a time');
127
- lines.push('5. Output plan/' + ctx.taskId + '/requirement.md with:');
128
- lines.push(' - Requirements summary');
129
- lines.push(' - Source code analysis');
130
- lines.push(' - Proposed solution and approach');
131
- lines.push(' - Impact analysis');
132
- lines.push(' - Effort estimate');
133
- lines.push(' - Testing plan');
134
- lines.push('6. Display GATE 1 prompt and wait for APPROVED');
135
- lines.push('');
136
- lines.push('DO NOT wait for the developer to ask. START NOW.');
137
- lines.push('If you have not started automatically, begin as soon as the developer types **"start"**.');
138
- lines.push('</ACTIVE_TASK>');
139
-
140
- return lines.join('\n');
141
- }
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SessionStart hook for ai-flow-kit
4
+ * Injects:
5
+ * 1. using-superpowers skill content
6
+ * 2. Active ticket context + gate workflow (if context exists)
7
+ * Cross-platform replacement for the bash session-start hook.
8
+ */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+ const { record } = require('../telemetry/record');
13
+
14
+ // Record session start telemetry
15
+ record('session.start');
16
+
17
+ // This script lives at .claude/hooks/session-start.js
18
+ // Project root is 2 levels up from __dirname (.claude/hooks → .claude → project root)
19
+ const projectRoot = path.resolve(__dirname, '..', '..');
20
+ const skillPath = path.join(projectRoot, '.claude', 'skills', 'using-superpowers', 'SKILL.md');
21
+ const contextPath = path.join(projectRoot, '.aiflow', 'context', 'current.json');
22
+
23
+ // ── 1. Load superpowers skill ──────────────────────────────────
24
+ let skillContent = '';
25
+ try {
26
+ skillContent = fs.readFileSync(skillPath, 'utf-8');
27
+ } catch (_) {
28
+ // Skill file missing — continue without it
29
+ }
30
+
31
+ // ── 2. Load active ticket context ──────────────────────────────
32
+ let contextBlock = '';
33
+ try {
34
+ if (fs.existsSync(contextPath)) {
35
+ const ctx = JSON.parse(fs.readFileSync(contextPath, 'utf-8'));
36
+ if (ctx.taskId && ctx.title) {
37
+ contextBlock = buildContextPrompt(ctx);
38
+ }
39
+ }
40
+ } catch (_) {
41
+ // Context missing or invalid — continue without it
42
+ }
43
+
44
+ // ── 3. Combine and output ──────────────────────────────────────
45
+ const parts = [];
46
+
47
+ if (skillContent) {
48
+ parts.push(`<EXTREMELY_IMPORTANT>\nYou have superpowers.\n\n**Below is the full content of your 'superpowers:using-superpowers' skill - your introduction to using skills. For all other skills, use the 'Skill' tool:**\n\n${skillContent}\n\n</EXTREMELY_IMPORTANT>`);
49
+ }
50
+
51
+ if (contextBlock) {
52
+ parts.push(contextBlock);
53
+ }
54
+
55
+ const combined = parts.join('\n\n');
56
+
57
+ // Escape for JSON string embedding
58
+ const escaped = combined
59
+ .replace(/\\/g, '\\\\')
60
+ .replace(/"/g, '\\"')
61
+ .replace(/\n/g, '\\n')
62
+ .replace(/\r/g, '\\r')
63
+ .replace(/\t/g, '\\t');
64
+
65
+ process.stdout.write(JSON.stringify({
66
+ hookSpecificOutput: {
67
+ hookEventName: 'SessionStart',
68
+ additionalContext: escaped
69
+ }
70
+ }));
71
+
72
+ process.exit(0);
73
+
74
+ // ── Helper ─────────────────────────────────────────────────────
75
+
76
+ function buildContextPrompt(ctx) {
77
+ const taskType = ctx.taskType || 'feature';
78
+
79
+ const lines = [];
80
+ lines.push('<ACTIVE_TASK>');
81
+ lines.push(`**Active Ticket:** ${ctx.taskId} — ${ctx.title}`);
82
+ lines.push(`**Type:** ${taskType}`);
83
+ lines.push(`**Status:** ${ctx.status || 'Unknown'}`);
84
+ lines.push(`**Assignee:** ${ctx.assignee || 'Unknown'}`);
85
+ lines.push('');
86
+
87
+ if (ctx.description) {
88
+ lines.push('**Description:**');
89
+ lines.push(ctx.description.substring(0, 2000));
90
+ lines.push('');
91
+ }
92
+
93
+ if (ctx.acceptanceCriteria && ctx.acceptanceCriteria.length > 0) {
94
+ lines.push('**Acceptance Criteria:**');
95
+ for (const c of ctx.acceptanceCriteria) {
96
+ lines.push(`- ${c}`);
97
+ }
98
+ lines.push('');
99
+ }
100
+
101
+ if (ctx.comments && ctx.comments.length > 0) {
102
+ lines.push(`**Comments (${ctx.comments.length}):**`);
103
+ // Include last 5 comments to keep context manageable
104
+ const recent = ctx.comments.slice(-5);
105
+ for (const c of recent) {
106
+ lines.push(c);
107
+ lines.push('');
108
+ }
109
+ }
110
+
111
+ if (ctx.context && ctx.context.files && ctx.context.files.length > 0) {
112
+ lines.push('**Related Files:**');
113
+ for (const f of ctx.context.files) {
114
+ lines.push(`- \`${f}\``);
115
+ }
116
+ lines.push('');
117
+ }
118
+
119
+ // ── Gate workflow instruction ──
120
+ lines.push('---');
121
+ lines.push('');
122
+ lines.push('**AUTO-START GATE 1: You MUST begin analyzing this ticket immediately.**');
123
+ lines.push('');
124
+ lines.push(`INVOKE the \`read-study-requirement\` skill NOW for ticket ${ctx.taskId}.`);
125
+ lines.push('');
126
+ lines.push('Gate 1 process:');
127
+ lines.push('1. Read the ticket context above');
128
+ lines.push('2. Read CLAUDE.md — understand project architecture and conventions');
129
+ lines.push('3. Read source code — identify related files, data flow, patterns');
130
+ lines.push('4. If anything is unclear — ask ONE question at a time');
131
+ lines.push('5. Output plan/' + ctx.taskId + '/requirement.md with:');
132
+ lines.push(' - Requirements summary');
133
+ lines.push(' - Source code analysis');
134
+ lines.push(' - Proposed solution and approach');
135
+ lines.push(' - Impact analysis');
136
+ lines.push(' - Effort estimate');
137
+ lines.push(' - Testing plan');
138
+ lines.push('6. Display GATE 1 prompt and wait for APPROVED');
139
+ lines.push('');
140
+ lines.push('DO NOT wait for the developer to ask. START NOW.');
141
+ lines.push('If you have not started automatically, begin as soon as the developer types **"start"**.');
142
+ lines.push('</ACTIVE_TASK>');
143
+
144
+ return lines.join('\n');
145
+ }
package/scripts/init.js CHANGED
@@ -1,9 +1,12 @@
1
1
  const fs = require('fs-extra');
2
2
  const path = require('path');
3
+ const os = require('os');
3
4
  const chalk = require('chalk');
4
- const { input } = require('@inquirer/prompts');
5
+ const { input, select, confirm } = require('@inquirer/prompts');
5
6
 
6
7
  const PKG_DIR = path.join(__dirname, '..');
8
+ const GLOBAL_AIFLOW_DIR = path.join(os.homedir(), '.aiflow');
9
+ const GLOBAL_CREDENTIALS_PATH = path.join(GLOBAL_AIFLOW_DIR, 'credentials.json');
7
10
  const PKG_VERSION = require('../package.json').version;
8
11
 
9
12
  // Map framework → language family for picking the right rules
@@ -20,36 +23,73 @@ const AI_TOOL_FILES = {
20
23
  'cursor': '.cursorrules',
21
24
  'gemini': 'GEMINI.md',
22
25
  'copilot': '.github/copilot-instructions.md',
23
- 'generic': 'AI_INSTRUCTIONS.md'
26
+ 'generic': '.aiflow/AI_INSTRUCTIONS.md'
24
27
  };
25
28
 
26
29
  /**
27
- * Copy documentation files and the docs/ folder to the project
30
+ * Copy only docs/common/ to .aiflow/docs in the project.
31
+ * Internal aiflow docs (changelog, architecture…) are never copied out.
28
32
  */
29
33
  async function copyDocsToProject(projectDir) {
30
- const aiflowDocsDir = path.join(projectDir, '.aiflow', 'docs');
31
- const srcDocsDir = path.join(PKG_DIR, 'docs');
32
-
33
- // 1. Copy the entire docs/ folder to .aiflow/docs
34
- if (await fs.pathExists(srcDocsDir)) {
35
- await fs.ensureDir(aiflowDocsDir);
36
- await fs.copy(srcDocsDir, aiflowDocsDir, { overwrite: true });
37
- console.log(chalk.green(`✓ Documentation folder copied to .aiflow/docs`));
38
- }
34
+ const srcCommonDir = path.join(PKG_DIR, 'docs', 'common');
35
+ if (!(await fs.pathExists(srcCommonDir))) return;
39
36
 
40
- // 2. Copy key markdown files to project root for easy access
41
- const keyFiles = ['QUICK_START.md', 'AIFLOW.md'];
42
- for (const file of keyFiles) {
43
- const srcPath = path.join(srcDocsDir, file);
44
- const destPath = path.join(projectDir, file);
45
- if (await fs.pathExists(srcPath)) {
46
- // We overwrite these to ensure they are up to date with the installed version
47
- await fs.copy(srcPath, destPath, { overwrite: true });
48
- console.log(chalk.gray(` ✓ ${file} updated in project root`));
49
- }
37
+ const destDir = path.join(projectDir, '.aiflow', 'docs');
38
+ await fs.ensureDir(destDir);
39
+ await fs.copy(srcCommonDir, destDir, { overwrite: true });
40
+ console.log(chalk.green(`✓ Documentation copied to .aiflow/docs`));
41
+ }
42
+
43
+ /**
44
+ * Scan the project directory to guess the framework.
45
+ * Returns a framework key or null if undetected.
46
+ */
47
+ async function detectFramework(projectDir) {
48
+ // JS / TS — check package.json dependencies
49
+ const pkgPath = path.join(projectDir, 'package.json');
50
+ if (await fs.pathExists(pkgPath)) {
51
+ const pkg = await fs.readJson(pkgPath).catch(() => ({}));
52
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
53
+ if (deps['next']) return 'nextjs';
54
+ if (deps['nuxt']) return 'vue-nuxt';
55
+ if (deps['vue']) return 'vue-nuxt';
56
+ if (deps['@nestjs/core']) return 'nodejs-express';
57
+ if (deps['react']) return 'reactjs';
58
+ if (deps['express'] || deps['fastify']) return 'nodejs-express';
59
+ }
60
+ // Java — pom.xml / Gradle
61
+ if (await fs.pathExists(path.join(projectDir, 'pom.xml'))) return 'spring-boot';
62
+ if (await fs.pathExists(path.join(projectDir, 'build.gradle'))) return 'spring-boot';
63
+ if (await fs.pathExists(path.join(projectDir, 'build.gradle.kts'))) return 'spring-boot';
64
+ // PHP — composer.json
65
+ const composerPath = path.join(projectDir, 'composer.json');
66
+ if (await fs.pathExists(composerPath)) {
67
+ const composer = await fs.readJson(composerPath).catch(() => ({}));
68
+ if ((composer.require || {})['laravel/framework']) return 'laravel';
69
+ }
70
+ // Python
71
+ if (await fs.pathExists(path.join(projectDir, 'manage.py'))) return 'python-django';
72
+ const reqPath = path.join(projectDir, 'requirements.txt');
73
+ if (await fs.pathExists(reqPath)) {
74
+ const reqs = (await fs.readFile(reqPath, 'utf-8').catch(() => '')).toLowerCase();
75
+ if (reqs.includes('fastapi')) return 'python-fastapi';
76
+ if (reqs.includes('django')) return 'python-django';
50
77
  }
78
+ return null;
51
79
  }
52
80
 
81
+ const FRAMEWORK_CHOICES = [
82
+ { name: 'Spring Boot (Java)', value: 'spring-boot' },
83
+ { name: 'React', value: 'reactjs' },
84
+ { name: 'Next.js', value: 'nextjs' },
85
+ { name: 'Vue / Nuxt', value: 'vue-nuxt' },
86
+ { name: 'Laravel (PHP)', value: 'laravel' },
87
+ { name: 'Node.js / Express', value: 'nodejs-express' },
88
+ { name: 'Python Django', value: 'python-django' },
89
+ { name: 'Python FastAPI', value: 'python-fastapi' },
90
+ { name: 'Skip (no framework)', value: null },
91
+ ];
92
+
53
93
  async function copyAssetsToVersion(versionDir, framework) {
54
94
  // ── Skills: merge upstream + custom ──────────────────────────────
55
95
  const upstreamSkills = path.join(PKG_DIR, 'upstream', 'skills');
@@ -210,6 +250,9 @@ async function setupFramework(projectDir, framework, multi = false, selectedTool
210
250
  const separator = `\n\n---\n\n`;
211
251
  const skillRegistry = await generateSkillRegistry(projectDir);
212
252
 
253
+ const written = [];
254
+ const skipped = [];
255
+
213
256
  for (const tool of selectedTools) {
214
257
  const targetPath = path.join(projectDir, AI_TOOL_FILES[tool]);
215
258
  if (!targetPath) continue;
@@ -233,18 +276,51 @@ async function setupFramework(projectDir, framework, multi = false, selectedTool
233
276
 
234
277
  finalContent += frameworkContent;
235
278
 
279
+ const fileExists = await fs.pathExists(targetPath);
280
+
236
281
  if (!multi) {
282
+ if (fileExists) {
283
+ const relPath = path.relative(projectDir, targetPath).replace(/\\/g, '/');
284
+ const overwrite = await confirm({
285
+ message: ` ${relPath} already exists — overwrite with aiflow template?`,
286
+ default: false
287
+ });
288
+
289
+ if (!overwrite) {
290
+ // Save aiflow template to .aiflow/reference/ so dev can compare/merge manually
291
+ const refPath = path.join(projectDir, '.aiflow', 'reference', path.basename(targetPath));
292
+ await fs.ensureDir(path.dirname(refPath));
293
+ await fs.writeFile(refPath, finalContent);
294
+ const refRel = path.relative(projectDir, refPath).replace(/\\/g, '/');
295
+ skipped.push(relPath);
296
+ console.log(chalk.gray(` aiflow template saved to ${refRel} for reference`));
297
+ continue;
298
+ }
299
+
300
+ // Backup existing file before overwriting
301
+ const backupPath = path.join(projectDir, '.aiflow', 'backup', path.basename(targetPath));
302
+ await fs.ensureDir(path.dirname(backupPath));
303
+ await fs.copy(targetPath, backupPath, { overwrite: true });
304
+ console.log(chalk.gray(` Backed up ${relPath} → .aiflow/backup/${path.basename(targetPath)}`));
305
+ }
237
306
  await fs.writeFile(targetPath, finalContent);
307
+ written.push(AI_TOOL_FILES[tool]);
238
308
  } else {
239
- if (await fs.pathExists(targetPath)) {
309
+ if (fileExists) {
240
310
  await fs.appendFile(targetPath, separator + frameworkContent);
241
311
  } else {
242
312
  await fs.writeFile(targetPath, finalContent);
243
313
  }
314
+ written.push(AI_TOOL_FILES[tool]);
244
315
  }
245
316
  }
246
317
 
247
- console.log(chalk.green(`✓ AI instruction files updated for framework: ${framework} (${selectedTools.join(', ')})`));
318
+ if (written.length) {
319
+ console.log(chalk.green(`✓ AI instruction files updated for framework: ${framework} (${written.join(', ')})`));
320
+ }
321
+ if (skipped.length) {
322
+ console.log(chalk.yellow(` Skipped (kept existing): ${skipped.join(', ')}`));
323
+ }
248
324
  }
249
325
 
250
326
  async function setupAdapter(projectDir, adapter) {
@@ -259,10 +335,10 @@ async function setupAdapter(projectDir, adapter) {
259
335
  const serverKey = Object.keys(mcpServers)[0];
260
336
  const serverConfig = mcpServers[serverKey];
261
337
 
262
- const credsFile = path.join(projectDir, '.aiflow', 'credentials.json');
263
338
  let existingCreds = {};
264
- if (await fs.pathExists(credsFile)) {
265
- existingCreds = await fs.readJson(credsFile).catch(() => ({}));
339
+ if (await fs.pathExists(GLOBAL_CREDENTIALS_PATH)) {
340
+ const globalData = await fs.readJson(GLOBAL_CREDENTIALS_PATH).catch(() => ({}));
341
+ existingCreds = globalData.mcp || {};
266
342
  }
267
343
 
268
344
  console.log(chalk.cyan(`\nSetup MCP adapter for ${adapter}:`));
@@ -321,8 +397,13 @@ async function setupAdapter(projectDir, adapter) {
321
397
  { spaces: 2 }
322
398
  );
323
399
 
324
- Object.assign(existingCreds, credentials);
325
- await fs.writeJson(credsFile, existingCreds, { spaces: 2 });
400
+ await fs.ensureDir(GLOBAL_AIFLOW_DIR);
401
+ let globalData = {};
402
+ if (await fs.pathExists(GLOBAL_CREDENTIALS_PATH)) {
403
+ globalData = await fs.readJson(GLOBAL_CREDENTIALS_PATH).catch(() => ({}));
404
+ }
405
+ globalData.mcp = { ...(globalData.mcp || {}), ...credentials };
406
+ await fs.writeJson(GLOBAL_CREDENTIALS_PATH, globalData, { spaces: 2 });
326
407
  await ensureCredentialsGitignored(projectDir);
327
408
 
328
409
  console.log(chalk.green(`✓ MCP configuration saved (global + local + credentials).`));
@@ -428,16 +509,26 @@ function verifyFigma(credentials) {
428
509
 
429
510
  async function ensureCredentialsGitignored(projectDir) {
430
511
  const gitignorePath = path.join(projectDir, '.gitignore');
431
- const entry = '.aiflow/credentials.json';
512
+ const entries = ['.aiflow/no-telemetry'];
432
513
  let content = '';
433
514
  if (await fs.pathExists(gitignorePath)) {
434
515
  content = await fs.readFile(gitignorePath, 'utf-8');
435
516
  }
436
517
  const lines = content.split('\n').map(l => l.trim());
437
- if (lines.includes(entry)) return;
438
- const separator = content.length > 0 && !content.endsWith('\n') ? '\n' : '';
439
- await fs.appendFile(gitignorePath, `${separator}${entry}\n`);
440
- console.log(chalk.yellow(` ⚠ Added ${entry} to .gitignore (contains API keys)`));
518
+ let changed = false;
519
+ let separator = content.length > 0 && !content.endsWith('\n') ? '\n' : '';
520
+
521
+ for (const entry of entries) {
522
+ if (!lines.includes(entry)) {
523
+ content += `${separator}${entry}\n`;
524
+ separator = '';
525
+ changed = true;
526
+ console.log(chalk.yellow(` ⚠ Added ${entry} to .gitignore`));
527
+ }
528
+ }
529
+ if (changed) {
530
+ await fs.writeFile(gitignorePath, content);
531
+ }
441
532
  }
442
533
 
443
534
  function maskSecret(value) {
@@ -448,13 +539,41 @@ function maskSecret(value) {
448
539
  async function init(options) {
449
540
  const projectDir = process.cwd();
450
541
  const aiflowDir = path.join(projectDir, '.aiflow');
451
- const frameworks = options.frameworks || (options.framework ? [options.framework] : []);
452
- const adapters = options.adapters || (options.adapter ? [options.adapter] : []);
453
- const primaryFramework = frameworks[0] || null;
454
- const versionDir = path.join(aiflowDir, 'versions', PKG_VERSION);
542
+ let frameworks = options.frameworks || (options.framework ? [options.framework] : []);
543
+ const adapters = options.adapters || (options.adapter ? [options.adapter] : []);
455
544
 
456
545
  try {
457
546
  console.log(chalk.blue(`Initializing ai-flow-kit (v${PKG_VERSION})...`));
547
+
548
+ // ── Auto-detect or prompt for framework if not supplied ──────
549
+ if (frameworks.length === 0) {
550
+ const detected = await detectFramework(projectDir);
551
+ if (detected) {
552
+ const label = FRAMEWORK_CHOICES.find(c => c.value === detected)?.name || detected;
553
+ console.log(chalk.cyan(`\n Detected framework: ${chalk.bold(label)}`));
554
+ const useDetected = await confirm({ message: `Use "${label}"?`, default: true });
555
+ if (useDetected) {
556
+ frameworks = [detected];
557
+ } else {
558
+ const chosen = await select({
559
+ message: 'Select framework:',
560
+ choices: FRAMEWORK_CHOICES,
561
+ });
562
+ if (chosen) frameworks = [chosen];
563
+ }
564
+ } else {
565
+ console.log(chalk.gray('\n No framework detected.'));
566
+ const chosen = await select({
567
+ message: 'Select framework (or skip):',
568
+ choices: FRAMEWORK_CHOICES,
569
+ });
570
+ if (chosen) frameworks = [chosen];
571
+ }
572
+ }
573
+
574
+ const primaryFramework = frameworks[0] || null;
575
+ const versionDir = path.join(aiflowDir, 'versions', PKG_VERSION);
576
+
458
577
  if (frameworks.length) console.log(chalk.gray(` Frameworks: ${frameworks.join(', ')}`));
459
578
  if (adapters.length) console.log(chalk.gray(` Adapters: ${adapters.join(', ')}`));
460
579
 
@@ -462,8 +581,6 @@ async function init(options) {
462
581
  await copyAssetsToVersion(versionDir, primaryFramework);
463
582
  await verifySuperpowersSkills(versionDir);
464
583
  await updateSymlinks(projectDir, versionDir);
465
-
466
- // --- Added: Copy docs to project ---
467
584
  await copyDocsToProject(projectDir);
468
585
 
469
586
  const selectedTools = options.aiTools || Object.keys(AI_TOOL_FILES);
@@ -486,11 +603,18 @@ async function init(options) {
486
603
  console.log(chalk.green('\n✨ Initialized AI Flow Kit successfully!'));
487
604
  console.log(chalk.blue('\n' + '─'.repeat(62)));
488
605
  console.log(chalk.bold.white(' Next steps:'));
489
- console.log(` ${chalk.green('aiflow use PROJ-33')} ${chalk.gray('← load ticket context')}`);
490
- console.log(` ${chalk.green('claude')} ${chalk.gray('← Use Claude Code CLI (recommended)')}`);
491
- console.log(` ${chalk.green('Cursor / Gemini')} ${chalk.gray('← Instructions loaded in .cursorrules / GEMINI.md')}`);
606
+
607
+ if (adapters.length > 0) {
608
+ console.log(` ${chalk.green('aiflow use PROJ-33')} ${chalk.gray('← load ticket from ' + adapters.join('/'))} `);
609
+ } else {
610
+ console.log(` ${chalk.green('aiflow use --manual')} ${chalk.gray('← input ticket content in terminal')}`);
611
+ console.log(` ${chalk.green('aiflow use --file req.md')} ${chalk.gray('← load from local file')}`);
612
+ }
613
+
614
+ console.log(` ${chalk.green('claude')} ${chalk.gray('← Claude Code CLI (recommended)')}`);
615
+ console.log(` ${chalk.green('Cursor / Gemini')} ${chalk.gray('← Instructions in .cursorrules / GEMINI.md')}`);
492
616
  console.log(chalk.blue('─'.repeat(62)));
493
- console.log(chalk.gray(` Full guide: ${chalk.green('aiflow guide')} | Command list: ${chalk.green('aiflow guide --commands')}`));
617
+ console.log(chalk.gray(` Full guide: ${chalk.green('aiflow guide')} | Commands: ${chalk.green('aiflow guide --commands')}`));
494
618
  console.log(chalk.blue('─'.repeat(62)) + '\n');
495
619
  } catch (err) {
496
620
  console.error(chalk.red(`Error during initialization: ${err.message}`));