@relipa/ai-flow-kit 0.0.6 → 0.0.7-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/README.md +41 -16
- package/bin/aiflow.js +65 -2
- package/custom/mcp-presets/gitnexus.json +8 -0
- package/custom/skills/generate-spec/SKILL.md +7 -0
- package/custom/skills/impact-analysis/SKILL.md +10 -0
- package/custom/skills/investigate-bug/SKILL.md +17 -0
- package/custom/skills/read-study-requirement/SKILL.md +11 -0
- package/custom/skills/review-plan/SKILL.md +15 -0
- package/custom/templates/php-plain.md +261 -0
- package/docs/common/AIFLOW.md +17 -6
- package/docs/common/CHANGELOG.md +63 -5
- package/docs/common/QUICK_START.md +33 -13
- package/docs/common/cli-reference.md +23 -0
- package/package.json +2 -7
- package/scripts/checkpoint.js +46 -0
- package/scripts/doctor.js +111 -8
- package/scripts/gitnexus-worker.js +94 -0
- package/scripts/guide.js +42 -51
- package/scripts/hooks/session-start.js +35 -5
- package/scripts/hooks/session-stop.js +55 -0
- package/scripts/init.js +299 -19
- package/scripts/prompt.js +2 -2
- package/scripts/remove.js +54 -0
- package/scripts/task.js +101 -0
- package/scripts/update.js +14 -4
- package/scripts/use.js +78 -6
- package/upstream/.claude-plugin/marketplace.json +4 -4
- package/upstream/.claude-plugin/plugin.json +2 -4
- package/upstream/.cursor-plugin/plugin.json +2 -4
- package/upstream/docs/plans/2025-11-22-opencode-support-design.md +1 -1
- package/upstream/docs/plans/2025-11-28-skills-improvements-from-user-feedback.md +2 -2
- package/upstream/docs/testing.md +2 -2
- package/upstream/skills/subagent-driven-development/SKILL.md +5 -6
- package/upstream/skills/subagent-driven-development/implementer-prompt.md +2 -3
- package/upstream/skills/systematic-debugging/CREATION-LOG.md +1 -1
- package/upstream/skills/systematic-debugging/root-cause-tracing.md +1 -1
- package/upstream/skills/tdd-lean/SKILL.md +127 -0
- package/upstream/skills/using-git-worktrees/SKILL.md +4 -5
- package/upstream/skills/writing-plans/SKILL.md +3 -9
- package/upstream/tests/brainstorm-server/package-lock.json +36 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Background worker: runs `npx gitnexus analyze` and writes result to
|
|
4
|
+
* .aiflow/gitnexus-status.json so session-start.js / use.js can notify the developer.
|
|
5
|
+
*
|
|
6
|
+
* Spawned as a detached process by init.js — never run directly.
|
|
7
|
+
* Usage: node gitnexus-worker.js <projectDir>
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { spawn, execSync } = require('child_process');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
function commandExists(cmd) {
|
|
15
|
+
try {
|
|
16
|
+
const isWin = process.platform === 'win32';
|
|
17
|
+
const checkCmd = isWin ? `where ${cmd}` : `which ${cmd}`;
|
|
18
|
+
execSync(checkCmd, { stdio: 'ignore' });
|
|
19
|
+
return true;
|
|
20
|
+
} catch (_) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const projectDir = process.argv[2];
|
|
26
|
+
if (!projectDir) process.exit(1);
|
|
27
|
+
|
|
28
|
+
const statusPath = path.join(projectDir, '.aiflow', 'gitnexus-status.json');
|
|
29
|
+
const logPath = path.join(projectDir, '.aiflow', 'gitnexus-analyze.log');
|
|
30
|
+
|
|
31
|
+
// Preserve startedAt written by init.js
|
|
32
|
+
let startedAt = new Date().toISOString();
|
|
33
|
+
try {
|
|
34
|
+
if (fs.existsSync(statusPath)) {
|
|
35
|
+
const existing = JSON.parse(fs.readFileSync(statusPath, 'utf-8'));
|
|
36
|
+
if (existing.startedAt) startedAt = existing.startedAt;
|
|
37
|
+
}
|
|
38
|
+
} catch (_) {}
|
|
39
|
+
|
|
40
|
+
// gitnexus requires Node >=20
|
|
41
|
+
const nodeMajor = parseInt(process.version.slice(1).split('.')[0], 10);
|
|
42
|
+
if (nodeMajor < 20) {
|
|
43
|
+
try {
|
|
44
|
+
fs.writeFileSync(statusPath, JSON.stringify({
|
|
45
|
+
status: 'error',
|
|
46
|
+
startedAt,
|
|
47
|
+
completedAt: new Date().toISOString(),
|
|
48
|
+
error: `Node.js version >=20 required (current: ${process.version})`,
|
|
49
|
+
}, null, 2));
|
|
50
|
+
} catch (_) {}
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Status path and log path already defined above
|
|
55
|
+
|
|
56
|
+
const logStream = fs.createWriteStream(logPath, { flags: 'w' });
|
|
57
|
+
|
|
58
|
+
// On Windows use shell:true — avoids EINVAL when spawning .cmd scripts
|
|
59
|
+
const isWin = process.platform === 'win32';
|
|
60
|
+
|
|
61
|
+
const hasGlobalGitNexus = commandExists('gitnexus');
|
|
62
|
+
const cmd = hasGlobalGitNexus ? 'gitnexus' : 'npx';
|
|
63
|
+
const args = hasGlobalGitNexus ? ['analyze'] : ['-y', 'gitnexus@latest', 'analyze'];
|
|
64
|
+
|
|
65
|
+
const child = spawn(cmd, args, {
|
|
66
|
+
cwd: projectDir,
|
|
67
|
+
shell: isWin,
|
|
68
|
+
windowsHide: true,
|
|
69
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
child.stdout.pipe(logStream);
|
|
73
|
+
child.stderr.pipe(logStream);
|
|
74
|
+
|
|
75
|
+
child.on('close', (code) => {
|
|
76
|
+
const status = code === 0
|
|
77
|
+
? { status: 'done', startedAt, completedAt: new Date().toISOString() }
|
|
78
|
+
: { status: 'error', startedAt, completedAt: new Date().toISOString(), exitCode: code };
|
|
79
|
+
try {
|
|
80
|
+
fs.writeFileSync(statusPath, JSON.stringify(status, null, 2));
|
|
81
|
+
} catch (_) {}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
child.on('error', (err) => {
|
|
85
|
+
try {
|
|
86
|
+
fs.writeFileSync(statusPath, JSON.stringify({
|
|
87
|
+
status: 'error',
|
|
88
|
+
startedAt,
|
|
89
|
+
completedAt: new Date().toISOString(),
|
|
90
|
+
exitCode: null,
|
|
91
|
+
error: err.message,
|
|
92
|
+
}, null, 2));
|
|
93
|
+
} catch (_) {}
|
|
94
|
+
});
|
package/scripts/guide.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
1
3
|
const chalk = require('chalk');
|
|
2
4
|
|
|
3
5
|
// ──────────────────────────────────────────────────────────────
|
|
@@ -40,56 +42,42 @@ function showHeader() {
|
|
|
40
42
|
// ──────────────────────────────────────────────────────────────
|
|
41
43
|
|
|
42
44
|
function showFlow() {
|
|
43
|
-
console.log(chalk.bold.cyan('
|
|
45
|
+
console.log(chalk.bold.cyan(' ⛩️ WORKFLOW OVERVIEW (AIFLOW.md)\n'));
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
console.log(chalk.gray(' │'));
|
|
80
|
-
console.log(chalk.gray(' ┌──────────────────▼───────────────────┐'));
|
|
81
|
-
console.log(chalk.gray(' │') + chalk.bold.magenta(' Claude AI (automatic)') + chalk.gray(' │'));
|
|
82
|
-
console.log(chalk.gray(' │') + chalk.white(' │'));
|
|
83
|
-
console.log(chalk.gray(' │') + chalk.gray(' SessionStart hook injected superpowers │'));
|
|
84
|
-
console.log(chalk.gray(' │') + chalk.gray(' ↓ │'));
|
|
85
|
-
console.log(chalk.gray(' │') + chalk.gray(' Read CLAUDE.md (framework rules) │'));
|
|
86
|
-
console.log(chalk.gray(' │') + chalk.gray(' ↓ │'));
|
|
87
|
-
console.log(chalk.gray(' │') + chalk.gray(' Read context/current.json (ticket) │'));
|
|
88
|
-
console.log(chalk.gray(' │') + chalk.gray(' ↓ │'));
|
|
89
|
-
console.log(chalk.gray(' │') + chalk.gray(' Auto-detect task type → select skill │'));
|
|
90
|
-
console.log(chalk.gray(' │') + chalk.gray(' ↓ │'));
|
|
91
|
-
console.log(chalk.gray(' │') + chalk.gray(' Implement with team rules │'));
|
|
92
|
-
console.log(chalk.gray(' └────────────────────────────────────────┘'));
|
|
47
|
+
try {
|
|
48
|
+
const aiflowPath = path.join(__dirname, '../docs/common/AIFLOW.md');
|
|
49
|
+
if (!fs.existsSync(aiflowPath)) {
|
|
50
|
+
console.log(chalk.red(' Error: AIFLOW.md not found.'));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const content = fs.readFileSync(aiflowPath, 'utf8');
|
|
55
|
+
const lines = content.split('\n');
|
|
56
|
+
|
|
57
|
+
let inSection = false;
|
|
58
|
+
let sectionLines = [];
|
|
59
|
+
|
|
60
|
+
for (const line of lines) {
|
|
61
|
+
if (line.trim() === '## Workflow Overview') {
|
|
62
|
+
inSection = true;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (inSection) {
|
|
66
|
+
if (line.startsWith('## ') || line.trim() === '---') {
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
sectionLines.push(line);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (sectionLines.length > 0) {
|
|
74
|
+
console.log(chalk.white(sectionLines.join('\n')));
|
|
75
|
+
} else {
|
|
76
|
+
console.log(chalk.yellow(' (Workflow Overview section not found in AIFLOW.md)'));
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.log(chalk.red(' Error reading AIFLOW.md: ' + error.message));
|
|
80
|
+
}
|
|
93
81
|
console.log();
|
|
94
82
|
}
|
|
95
83
|
|
|
@@ -159,11 +147,14 @@ function showCommands() {
|
|
|
159
147
|
items: [
|
|
160
148
|
['aiflow init', '--framework <fw> --adapter <ad>', 'Initialize project'],
|
|
161
149
|
['aiflow init', '--env <tools>', 'AI tools: cursor,gemini,copilot'],
|
|
162
|
-
['aiflow init', '--with-rtk | --no-rtk', '
|
|
163
|
-
['aiflow
|
|
150
|
+
['aiflow init', '--with-rtk | --no-rtk', 'RTK bash compression (60–90% saving)'],
|
|
151
|
+
// ['aiflow init', '--with-gitnexus', 'GitNexus code intelligence (waits for index)'],
|
|
152
|
+
// ['aiflow init', '--with-gitnexus --no-wait', 'GitNexus: index in background'],
|
|
153
|
+
['aiflow doctor', '', 'Check setup health + token savings status'],
|
|
164
154
|
['aiflow doctor', '--verbose', 'Detailed health check output'],
|
|
165
155
|
['aiflow update', '', 'Update to the latest version'],
|
|
166
156
|
['aiflow update', '--force', 'Force update even if already latest'],
|
|
157
|
+
['aiflow sync-skills', '', 'Manually sync AI Instruction files'],
|
|
167
158
|
['aiflow remove', '', 'Remove ai-flow-kit from project'],
|
|
168
159
|
['aiflow remove', '--version <ver>', 'Remove a cached version'],
|
|
169
160
|
['aiflow remove', '--global', 'Uninstall global package'],
|
|
@@ -41,7 +41,37 @@ try {
|
|
|
41
41
|
// Context missing or invalid — continue without it
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
// ── 3.
|
|
44
|
+
// ── 3. Check GitNexus background indexing status ───────────────
|
|
45
|
+
const GITNEXUS_STALE_MS = 3 * 60 * 60 * 1000; // 3 hours
|
|
46
|
+
const gitNexusStatusPath = path.join(projectRoot, '.aiflow', 'gitnexus-status.json');
|
|
47
|
+
try {
|
|
48
|
+
if (fs.existsSync(gitNexusStatusPath)) {
|
|
49
|
+
const gn = JSON.parse(fs.readFileSync(gitNexusStatusPath, 'utf-8'));
|
|
50
|
+
if (!gn.notified) {
|
|
51
|
+
// Detect stale "running" — process was killed without updating status
|
|
52
|
+
if (gn.status === 'running' && gn.startedAt) {
|
|
53
|
+
const age = Date.now() - new Date(gn.startedAt).getTime();
|
|
54
|
+
if (age > GITNEXUS_STALE_MS) {
|
|
55
|
+
gn.status = 'error';
|
|
56
|
+
gn.error = 'timed out — process may have been killed or ran out of memory';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (gn.status === 'done') {
|
|
60
|
+
process.stderr.write('[aiflow] ✓ GitNexus indexing complete — code intelligence tools are ready.\n');
|
|
61
|
+
} else if (gn.status === 'error') {
|
|
62
|
+
const detail = gn.error ? ` (${gn.error})` : gn.exitCode != null ? ` (exit ${gn.exitCode})` : '';
|
|
63
|
+
process.stderr.write(`[aiflow] ✗ GitNexus indexing failed${detail}.\n`);
|
|
64
|
+
// process.stderr.write('[aiflow] Retry: aiflow init --with-gitnexus\n');
|
|
65
|
+
}
|
|
66
|
+
if (gn.status === 'done' || gn.status === 'error') {
|
|
67
|
+
gn.notified = true;
|
|
68
|
+
fs.writeFileSync(gitNexusStatusPath, JSON.stringify(gn, null, 2));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
} catch (_) {}
|
|
73
|
+
|
|
74
|
+
// ── 4. Combine and output ──────────────────────────────────────
|
|
45
75
|
const parts = [];
|
|
46
76
|
|
|
47
77
|
if (skillContent) {
|
|
@@ -222,12 +252,12 @@ function buildModeInstruction(mode, gate) {
|
|
|
222
252
|
],
|
|
223
253
|
3: [
|
|
224
254
|
'**MODE: fast** — Gate 3 fast track:',
|
|
255
|
+
'- INVOKE `tdd-lean` skill (NOT superpowers:test-driven-development)',
|
|
256
|
+
'- tdd-lean uses Batch Red-Green-Refactor: 2–3 test runs total, never per-test',
|
|
257
|
+
'- Test Output Discipline: keep only summary line after each run, discard stack traces',
|
|
225
258
|
'- DO NOT use `superpowers:subagent-driven-development`',
|
|
226
259
|
'- DO NOT spawn Spec Reviewer or Code Quality Reviewer subagents',
|
|
227
|
-
'-
|
|
228
|
-
'- Write tests and implementation in the same pass per task',
|
|
229
|
-
'- Run the test suite ONCE at the end of all tasks (not after each step)',
|
|
230
|
-
'- If tests fail: fix inline, do not spawn a subagent',
|
|
260
|
+
'- Call `aiflow checkpoint` at each milestone (tests-written, code-done, tests-pass)',
|
|
231
261
|
],
|
|
232
262
|
4: [
|
|
233
263
|
'**MODE: fast** — Gate 4 fast track:',
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* SessionStop hook for ai-flow-kit
|
|
4
|
+
* Reads checkpoints.json for the active task and prints a token usage summary.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { record } = require('../telemetry/record');
|
|
10
|
+
|
|
11
|
+
record('session.stop');
|
|
12
|
+
|
|
13
|
+
const projectRoot = path.resolve(__dirname, '..', '..');
|
|
14
|
+
const contextFile = path.join(projectRoot, '.aiflow', 'context', 'current.json');
|
|
15
|
+
const tasksDir = path.join(projectRoot, '.aiflow', 'tasks');
|
|
16
|
+
|
|
17
|
+
const GATE_LABELS = { 1: 'Analyze', 2: 'Plan', 3: 'Code', 4: 'Review', 5: 'PR' };
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
if (!fs.existsSync(contextFile)) process.exit(0);
|
|
21
|
+
const ctx = JSON.parse(fs.readFileSync(contextFile, 'utf-8'));
|
|
22
|
+
const ticketId = ctx && ctx.taskId;
|
|
23
|
+
if (!ticketId) process.exit(0);
|
|
24
|
+
|
|
25
|
+
const checkpointPath = path.join(tasksDir, ticketId, 'checkpoints.json');
|
|
26
|
+
if (!fs.existsSync(checkpointPath)) process.exit(0);
|
|
27
|
+
|
|
28
|
+
const data = JSON.parse(fs.readFileSync(checkpointPath, 'utf-8'));
|
|
29
|
+
const checkpoints = data.checkpoints || [];
|
|
30
|
+
if (checkpoints.length === 0) process.exit(0);
|
|
31
|
+
|
|
32
|
+
// Aggregate latest token value per gate
|
|
33
|
+
const gateTokens = {};
|
|
34
|
+
for (const cp of checkpoints) {
|
|
35
|
+
const g = cp.gate;
|
|
36
|
+
if (!gateTokens[g] || cp.tokens > gateTokens[g]) {
|
|
37
|
+
gateTokens[g] = cp.tokens;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const gates = Object.keys(gateTokens).sort();
|
|
42
|
+
const total = gates.reduce((sum, g) => sum + gateTokens[g], 0);
|
|
43
|
+
|
|
44
|
+
process.stderr.write('\n[aiflow] Token usage this session:\n');
|
|
45
|
+
for (const g of gates) {
|
|
46
|
+
const label = GATE_LABELS[parseInt(g)] || `Gate ${g}`;
|
|
47
|
+
const t = gateTokens[g];
|
|
48
|
+
const bar = '█'.repeat(Math.round((t / total) * 20)).padEnd(20, '░');
|
|
49
|
+
const pct = Math.round((t / total) * 100);
|
|
50
|
+
process.stderr.write(`[aiflow] Gate ${g} ${label.padEnd(8)} ~${t.toLocaleString().padStart(6)} ${bar} ${pct}%\n`);
|
|
51
|
+
}
|
|
52
|
+
process.stderr.write(`[aiflow] Total estimated: ~${total.toLocaleString()} tokens\n\n`);
|
|
53
|
+
} catch (_) {}
|
|
54
|
+
|
|
55
|
+
process.exit(0);
|