@relipa/ai-flow-kit 0.0.5-beta.0 → 0.0.5
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/README.md +23 -17
- 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 -88
- 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 -463
- package/docs/{CHANGELOG.md → common/CHANGELOG.md} +132 -128
- package/docs/{cli-reference.md → common/cli-reference.md} +1 -1
- package/docs/project/ARCHITECTURE.md +28 -0
- package/package.json +3 -2
- package/scripts/context.js +1 -1
- package/scripts/hooks/session-start.js +145 -145
- package/scripts/init.js +154 -47
- package/scripts/prompt.js +431 -402
- package/scripts/telemetry/cli.js +6 -12
- package/scripts/telemetry/config.js +1 -4
- package/scripts/telemetry/flush.js +16 -13
- package/scripts/use.js +74 -31
- package/docs/IMPLEMENTATION_SUMMARY.md +0 -330
- package/docs/architecture.md +0 -394
- package/docs/developer-overview.md +0 -126
- /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/{troubleshooting.md → common/troubleshooting.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
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,43 +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
|
-
|
|
38
|
-
// Ensure README.md is pulled into docs folder for developer visibility
|
|
39
|
-
const srcReadme = path.join(PKG_DIR, 'README.md');
|
|
40
|
-
if (await fs.pathExists(srcReadme)) {
|
|
41
|
-
await fs.copy(srcReadme, path.join(aiflowDocsDir, 'README.md'), { overwrite: true });
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
console.log(chalk.green(`✓ Documentation folder copied to .aiflow/docs`));
|
|
45
|
-
}
|
|
34
|
+
const srcCommonDir = path.join(PKG_DIR, 'docs', 'common');
|
|
35
|
+
if (!(await fs.pathExists(srcCommonDir))) return;
|
|
46
36
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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';
|
|
57
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';
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
58
79
|
}
|
|
59
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
|
+
|
|
60
93
|
async function copyAssetsToVersion(versionDir, framework) {
|
|
61
94
|
// ── Skills: merge upstream + custom ──────────────────────────────
|
|
62
95
|
const upstreamSkills = path.join(PKG_DIR, 'upstream', 'skills');
|
|
@@ -217,6 +250,9 @@ async function setupFramework(projectDir, framework, multi = false, selectedTool
|
|
|
217
250
|
const separator = `\n\n---\n\n`;
|
|
218
251
|
const skillRegistry = await generateSkillRegistry(projectDir);
|
|
219
252
|
|
|
253
|
+
const written = [];
|
|
254
|
+
const skipped = [];
|
|
255
|
+
|
|
220
256
|
for (const tool of selectedTools) {
|
|
221
257
|
const targetPath = path.join(projectDir, AI_TOOL_FILES[tool]);
|
|
222
258
|
if (!targetPath) continue;
|
|
@@ -240,18 +276,51 @@ async function setupFramework(projectDir, framework, multi = false, selectedTool
|
|
|
240
276
|
|
|
241
277
|
finalContent += frameworkContent;
|
|
242
278
|
|
|
279
|
+
const fileExists = await fs.pathExists(targetPath);
|
|
280
|
+
|
|
243
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
|
+
}
|
|
244
306
|
await fs.writeFile(targetPath, finalContent);
|
|
307
|
+
written.push(AI_TOOL_FILES[tool]);
|
|
245
308
|
} else {
|
|
246
|
-
if (
|
|
309
|
+
if (fileExists) {
|
|
247
310
|
await fs.appendFile(targetPath, separator + frameworkContent);
|
|
248
311
|
} else {
|
|
249
312
|
await fs.writeFile(targetPath, finalContent);
|
|
250
313
|
}
|
|
314
|
+
written.push(AI_TOOL_FILES[tool]);
|
|
251
315
|
}
|
|
252
316
|
}
|
|
253
317
|
|
|
254
|
-
|
|
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
|
+
}
|
|
255
324
|
}
|
|
256
325
|
|
|
257
326
|
async function setupAdapter(projectDir, adapter) {
|
|
@@ -266,10 +335,10 @@ async function setupAdapter(projectDir, adapter) {
|
|
|
266
335
|
const serverKey = Object.keys(mcpServers)[0];
|
|
267
336
|
const serverConfig = mcpServers[serverKey];
|
|
268
337
|
|
|
269
|
-
const credsFile = path.join(projectDir, '.aiflow', 'credentials.json');
|
|
270
338
|
let existingCreds = {};
|
|
271
|
-
if (await fs.pathExists(
|
|
272
|
-
|
|
339
|
+
if (await fs.pathExists(GLOBAL_CREDENTIALS_PATH)) {
|
|
340
|
+
const globalData = await fs.readJson(GLOBAL_CREDENTIALS_PATH).catch(() => ({}));
|
|
341
|
+
existingCreds = globalData.mcp || {};
|
|
273
342
|
}
|
|
274
343
|
|
|
275
344
|
console.log(chalk.cyan(`\nSetup MCP adapter for ${adapter}:`));
|
|
@@ -328,8 +397,13 @@ async function setupAdapter(projectDir, adapter) {
|
|
|
328
397
|
{ spaces: 2 }
|
|
329
398
|
);
|
|
330
399
|
|
|
331
|
-
|
|
332
|
-
|
|
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 });
|
|
333
407
|
await ensureCredentialsGitignored(projectDir);
|
|
334
408
|
|
|
335
409
|
console.log(chalk.green(`✓ MCP configuration saved (global + local + credentials).`));
|
|
@@ -435,7 +509,7 @@ function verifyFigma(credentials) {
|
|
|
435
509
|
|
|
436
510
|
async function ensureCredentialsGitignored(projectDir) {
|
|
437
511
|
const gitignorePath = path.join(projectDir, '.gitignore');
|
|
438
|
-
const entries = ['.aiflow/
|
|
512
|
+
const entries = ['.aiflow/no-telemetry'];
|
|
439
513
|
let content = '';
|
|
440
514
|
if (await fs.pathExists(gitignorePath)) {
|
|
441
515
|
content = await fs.readFile(gitignorePath, 'utf-8');
|
|
@@ -465,13 +539,41 @@ function maskSecret(value) {
|
|
|
465
539
|
async function init(options) {
|
|
466
540
|
const projectDir = process.cwd();
|
|
467
541
|
const aiflowDir = path.join(projectDir, '.aiflow');
|
|
468
|
-
|
|
469
|
-
const adapters
|
|
470
|
-
const primaryFramework = frameworks[0] || null;
|
|
471
|
-
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] : []);
|
|
472
544
|
|
|
473
545
|
try {
|
|
474
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
|
+
|
|
475
577
|
if (frameworks.length) console.log(chalk.gray(` Frameworks: ${frameworks.join(', ')}`));
|
|
476
578
|
if (adapters.length) console.log(chalk.gray(` Adapters: ${adapters.join(', ')}`));
|
|
477
579
|
|
|
@@ -479,8 +581,6 @@ async function init(options) {
|
|
|
479
581
|
await copyAssetsToVersion(versionDir, primaryFramework);
|
|
480
582
|
await verifySuperpowersSkills(versionDir);
|
|
481
583
|
await updateSymlinks(projectDir, versionDir);
|
|
482
|
-
|
|
483
|
-
// --- Added: Copy docs to project ---
|
|
484
584
|
await copyDocsToProject(projectDir);
|
|
485
585
|
|
|
486
586
|
const selectedTools = options.aiTools || Object.keys(AI_TOOL_FILES);
|
|
@@ -503,11 +603,18 @@ async function init(options) {
|
|
|
503
603
|
console.log(chalk.green('\n✨ Initialized AI Flow Kit successfully!'));
|
|
504
604
|
console.log(chalk.blue('\n' + '─'.repeat(62)));
|
|
505
605
|
console.log(chalk.bold.white(' Next steps:'));
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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')}`);
|
|
509
616
|
console.log(chalk.blue('─'.repeat(62)));
|
|
510
|
-
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')}`));
|
|
511
618
|
console.log(chalk.blue('─'.repeat(62)) + '\n');
|
|
512
619
|
} catch (err) {
|
|
513
620
|
console.error(chalk.red(`Error during initialization: ${err.message}`));
|