@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.
- package/{docs/README.md → README.md} +25 -18
- package/bin/aiflow.js +77 -7
- package/custom/skills/{validate-ticket → read-study-requirement}/SKILL.md +27 -17
- package/custom/skills/review-plan/SKILL.md +1 -1
- package/custom/templates/shared/gate-workflow.md +88 -75
- package/custom/templates/tools/claude.md +1 -1
- package/custom/templates/tools/copilot.md +1 -1
- package/custom/templates/tools/cursor.md +1 -1
- package/custom/templates/tools/gemini.md +1 -1
- package/custom/templates/tools/generic.md +1 -1
- package/docs/{AIFLOW.md → common/AIFLOW.md} +462 -458
- package/docs/{CHANGELOG.md → common/CHANGELOG.md} +132 -100
- package/docs/{cli-reference.md → common/cli-reference.md} +98 -28
- package/docs/{troubleshooting.md → common/troubleshooting.md} +15 -0
- package/docs/project/ARCHITECTURE.md +28 -0
- package/package.json +7 -5
- package/scripts/context.js +1 -1
- package/scripts/guide.js +16 -0
- package/scripts/hooks/session-start.js +145 -141
- package/scripts/init.js +168 -44
- package/scripts/prompt.js +431 -402
- package/scripts/telemetry/cli.js +243 -0
- package/scripts/telemetry/config.js +91 -0
- package/scripts/telemetry/crypto.js +20 -0
- package/scripts/telemetry/flush.js +162 -0
- package/scripts/telemetry/record.js +138 -0
- package/scripts/use.js +74 -31
- package/upstream/skills/using-superpowers/SKILL.md +14 -0
- package/docs/IMPLEMENTATION_SUMMARY.md +0 -330
- package/docs/architecture.md +0 -394
- package/docs/developer-overview.md +0 -126
- package/upstream/tests/brainstorm-server/package-lock.json +0 -36
- /package/docs/{QUICK_START.md → common/QUICK_START.md} +0 -0
- /package/docs/{ai-integration.md → common/ai-integration.md} +0 -0
- /package/docs/{configuration.md → common/configuration.md} +0 -0
- /package/docs/{getting-started.md → common/getting-started.md} +0 -0
- /package/docs/{workflows → common/workflows}/bug-fix.md +0 -0
- /package/docs/{workflows → common/workflows}/feature.md +0 -0
- /package/docs/{workflows → common/workflows}/impact-analysis.md +0 -0
- /package/docs/{workflows → common/workflows}/investigation.md +0 -0
- /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
|
-
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (
|
|
48
|
-
parts.push(
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
.replace(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
lines
|
|
80
|
-
lines.push(
|
|
81
|
-
lines.push(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
lines.push('
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
lines.push('');
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
lines.push(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
lines.push('');
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
lines.push(
|
|
121
|
-
lines.push('');
|
|
122
|
-
lines.push('
|
|
123
|
-
lines.push('
|
|
124
|
-
lines.push(
|
|
125
|
-
lines.push('
|
|
126
|
-
lines.push('
|
|
127
|
-
lines.push('
|
|
128
|
-
lines.push('
|
|
129
|
-
lines.push('
|
|
130
|
-
lines.push('
|
|
131
|
-
lines.push('
|
|
132
|
-
lines.push(' -
|
|
133
|
-
lines.push(' -
|
|
134
|
-
lines.push('
|
|
135
|
-
lines.push('');
|
|
136
|
-
lines.push('
|
|
137
|
-
lines.push('
|
|
138
|
-
lines.push('
|
|
139
|
-
|
|
140
|
-
|
|
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
|
|
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
|
|
31
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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 (
|
|
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
|
-
|
|
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(
|
|
265
|
-
|
|
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
|
-
|
|
325
|
-
|
|
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
|
|
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
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
-
|
|
452
|
-
const adapters
|
|
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
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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')} |
|
|
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}`));
|