@relipa/ai-flow-kit 0.0.7 → 0.0.8-beta.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/README.md +7 -6
- package/bin/aiflow.js +400 -360
- package/bin/ak.js +4 -0
- package/custom/templates/php-plain.md +261 -261
- package/custom/templates/php.md +261 -0
- package/custom/templates/python.md +79 -0
- package/docs/common/CHANGELOG.md +43 -1
- package/docs/common/QUICK_START.md +57 -50
- package/docs/common/cli-reference.md +161 -196
- package/docs/common/getting-started.md +3 -3
- package/docs/common/troubleshooting.md +7 -7
- package/package.json +2 -1
- package/scripts/checkpoint.js +46 -46
- package/scripts/gitnexus-worker.js +94 -94
- package/scripts/hooks/session-stop.js +55 -55
- package/scripts/init.js +7 -4
- package/scripts/task.js +21 -0
- package/scripts/use.js +880 -625
package/scripts/checkpoint.js
CHANGED
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
const fs = require('fs-extra');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const chalk = require('chalk');
|
|
4
|
-
|
|
5
|
-
const PROJECT_DIR = process.cwd();
|
|
6
|
-
const AIFLOW_DIR = path.join(PROJECT_DIR, '.aiflow');
|
|
7
|
-
const CONTEXT_FILE = path.join(AIFLOW_DIR, 'context', 'current.json');
|
|
8
|
-
|
|
9
|
-
const GATE_LABELS = { 1: 'Analyze', 2: 'Plan', 3: 'Code', 4: 'Review', 5: 'PR' };
|
|
10
|
-
|
|
11
|
-
module.exports = async function checkpoint(options = {}) {
|
|
12
|
-
const { gate, step, tokens, ticket: ticketOpt } = options;
|
|
13
|
-
|
|
14
|
-
if (!gate || !step || tokens === undefined) {
|
|
15
|
-
console.log(chalk.yellow('Usage: aiflow checkpoint --gate <N> --step <name> --tokens <N>'));
|
|
16
|
-
console.log(chalk.gray(' Example: aiflow checkpoint --gate 3 --step "tests-written" --tokens 3200'));
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Resolve ticket ID
|
|
21
|
-
let ticketId = ticketOpt;
|
|
22
|
-
if (!ticketId) {
|
|
23
|
-
const ctx = await fs.readJson(CONTEXT_FILE).catch(() => null);
|
|
24
|
-
ticketId = ctx && ctx.taskId ? ctx.taskId : 'unknown';
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const taskDir = path.join(AIFLOW_DIR, 'tasks', ticketId);
|
|
28
|
-
await fs.ensureDir(taskDir);
|
|
29
|
-
|
|
30
|
-
const checkpointPath = path.join(taskDir, 'checkpoints.json');
|
|
31
|
-
const existing = (await fs.pathExists(checkpointPath))
|
|
32
|
-
? await fs.readJson(checkpointPath).catch(() => ({ checkpoints: [] }))
|
|
33
|
-
: { checkpoints: [] };
|
|
34
|
-
|
|
35
|
-
const entry = {
|
|
36
|
-
gate: parseInt(gate),
|
|
37
|
-
step,
|
|
38
|
-
tokens: parseInt(tokens),
|
|
39
|
-
timestamp: new Date().toISOString(),
|
|
40
|
-
};
|
|
41
|
-
existing.checkpoints.push(entry);
|
|
42
|
-
await fs.writeJson(checkpointPath, existing, { spaces: 2 });
|
|
43
|
-
|
|
44
|
-
const label = GATE_LABELS[parseInt(gate)] || `Gate ${gate}`;
|
|
45
|
-
console.log(chalk.green(`✓ Checkpoint: Gate ${gate} (${label}) — ${step} — ~${parseInt(tokens).toLocaleString()} tokens`));
|
|
46
|
-
};
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
|
|
5
|
+
const PROJECT_DIR = process.cwd();
|
|
6
|
+
const AIFLOW_DIR = path.join(PROJECT_DIR, '.aiflow');
|
|
7
|
+
const CONTEXT_FILE = path.join(AIFLOW_DIR, 'context', 'current.json');
|
|
8
|
+
|
|
9
|
+
const GATE_LABELS = { 1: 'Analyze', 2: 'Plan', 3: 'Code', 4: 'Review', 5: 'PR' };
|
|
10
|
+
|
|
11
|
+
module.exports = async function checkpoint(options = {}) {
|
|
12
|
+
const { gate, step, tokens, ticket: ticketOpt } = options;
|
|
13
|
+
|
|
14
|
+
if (!gate || !step || tokens === undefined) {
|
|
15
|
+
console.log(chalk.yellow('Usage: aiflow checkpoint --gate <N> --step <name> --tokens <N>'));
|
|
16
|
+
console.log(chalk.gray(' Example: aiflow checkpoint --gate 3 --step "tests-written" --tokens 3200'));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Resolve ticket ID
|
|
21
|
+
let ticketId = ticketOpt;
|
|
22
|
+
if (!ticketId) {
|
|
23
|
+
const ctx = await fs.readJson(CONTEXT_FILE).catch(() => null);
|
|
24
|
+
ticketId = ctx && ctx.taskId ? ctx.taskId : 'unknown';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const taskDir = path.join(AIFLOW_DIR, 'tasks', ticketId);
|
|
28
|
+
await fs.ensureDir(taskDir);
|
|
29
|
+
|
|
30
|
+
const checkpointPath = path.join(taskDir, 'checkpoints.json');
|
|
31
|
+
const existing = (await fs.pathExists(checkpointPath))
|
|
32
|
+
? await fs.readJson(checkpointPath).catch(() => ({ checkpoints: [] }))
|
|
33
|
+
: { checkpoints: [] };
|
|
34
|
+
|
|
35
|
+
const entry = {
|
|
36
|
+
gate: parseInt(gate),
|
|
37
|
+
step,
|
|
38
|
+
tokens: parseInt(tokens),
|
|
39
|
+
timestamp: new Date().toISOString(),
|
|
40
|
+
};
|
|
41
|
+
existing.checkpoints.push(entry);
|
|
42
|
+
await fs.writeJson(checkpointPath, existing, { spaces: 2 });
|
|
43
|
+
|
|
44
|
+
const label = GATE_LABELS[parseInt(gate)] || `Gate ${gate}`;
|
|
45
|
+
console.log(chalk.green(`✓ Checkpoint: Gate ${gate} (${label}) — ${step} — ~${parseInt(tokens).toLocaleString()} tokens`));
|
|
46
|
+
};
|
|
@@ -1,94 +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
|
-
});
|
|
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
|
+
});
|
|
@@ -1,55 +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);
|
|
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);
|
package/scripts/init.js
CHANGED
|
@@ -24,7 +24,7 @@ function commandExists(cmd) {
|
|
|
24
24
|
// Map framework → language family for picking the right rules
|
|
25
25
|
const FRAMEWORK_LANGUAGE = {
|
|
26
26
|
'laravel': 'php',
|
|
27
|
-
'php
|
|
27
|
+
'php': 'php',
|
|
28
28
|
'spring-boot': 'java',
|
|
29
29
|
'reactjs': 'javascript',
|
|
30
30
|
'nextjs': 'javascript',
|
|
@@ -80,7 +80,7 @@ async function detectFramework(projectDir) {
|
|
|
80
80
|
if (await fs.pathExists(composerPath)) {
|
|
81
81
|
const composer = await fs.readJson(composerPath).catch(() => ({}));
|
|
82
82
|
if ((composer.require || {})['laravel/framework']) return 'laravel';
|
|
83
|
-
return 'php
|
|
83
|
+
return 'php';
|
|
84
84
|
}
|
|
85
85
|
// Python
|
|
86
86
|
if (await fs.pathExists(path.join(projectDir, 'manage.py'))) return 'python-django';
|
|
@@ -89,7 +89,9 @@ async function detectFramework(projectDir) {
|
|
|
89
89
|
const reqs = (await fs.readFile(reqPath, 'utf-8').catch(() => '')).toLowerCase();
|
|
90
90
|
if (reqs.includes('fastapi')) return 'python-fastapi';
|
|
91
91
|
if (reqs.includes('django')) return 'python-django';
|
|
92
|
+
return 'python';
|
|
92
93
|
}
|
|
94
|
+
if (await fs.pathExists(path.join(projectDir, 'pyproject.toml'))) return 'python';
|
|
93
95
|
return null;
|
|
94
96
|
}
|
|
95
97
|
|
|
@@ -99,9 +101,10 @@ const FRAMEWORK_CHOICES = [
|
|
|
99
101
|
{ name: 'Next.js', value: 'nextjs' },
|
|
100
102
|
{ name: 'Vue / Nuxt', value: 'vue-nuxt' },
|
|
101
103
|
{ name: 'Laravel (PHP)', value: 'laravel' },
|
|
102
|
-
{ name: 'PHP
|
|
104
|
+
{ name: 'PHP', value: 'php' },
|
|
103
105
|
{ name: 'NestJS', value: 'nestjs' },
|
|
104
106
|
{ name: 'Node.js / Express', value: 'nodejs-express' },
|
|
107
|
+
{ name: 'Python', value: 'python' },
|
|
105
108
|
{ name: 'Python Django', value: 'python-django' },
|
|
106
109
|
{ name: 'Python FastAPI', value: 'python-fastapi' },
|
|
107
110
|
];
|
|
@@ -468,7 +471,7 @@ async function setupAdapter(projectDir, adapter) {
|
|
|
468
471
|
}
|
|
469
472
|
globalData.mcp = { ...(globalData.mcp || {}), ...credentials };
|
|
470
473
|
await fs.writeJson(GLOBAL_CREDENTIALS_PATH, globalData, { spaces: 2 });
|
|
471
|
-
await
|
|
474
|
+
await ensureAiflowGitignored(projectDir);
|
|
472
475
|
|
|
473
476
|
console.log(chalk.green(`✓ MCP configuration saved (global + local + credentials).`));
|
|
474
477
|
}
|
package/scripts/task.js
CHANGED
|
@@ -178,6 +178,9 @@ async function resumeTask(taskId) {
|
|
|
178
178
|
await fs.ensureDir(CONTEXT_DIR);
|
|
179
179
|
await fs.writeJson(CURRENT_FILE, ctx, { spaces: 2 });
|
|
180
180
|
|
|
181
|
+
// Deactivate any other tasks before marking this one active
|
|
182
|
+
await deactivateOtherTasks(taskId);
|
|
183
|
+
|
|
181
184
|
// Mark as active
|
|
182
185
|
const now = new Date().toISOString();
|
|
183
186
|
taskState.status = 'active';
|
|
@@ -369,6 +372,21 @@ async function nextGate(taskId) {
|
|
|
369
372
|
// Internal helpers
|
|
370
373
|
// ──────────────────────────────────────────────────────────────
|
|
371
374
|
|
|
375
|
+
async function deactivateOtherTasks(exceptTaskId) {
|
|
376
|
+
if (!(await fs.pathExists(TASKS_DIR))) return;
|
|
377
|
+
const entries = await fs.readdir(TASKS_DIR, { withFileTypes: true });
|
|
378
|
+
for (const entry of entries) {
|
|
379
|
+
if (!entry.isDirectory() || entry.name === exceptTaskId) continue;
|
|
380
|
+
const statePath = path.join(TASKS_DIR, entry.name, 'task-state.json');
|
|
381
|
+
if (!(await fs.pathExists(statePath))) continue;
|
|
382
|
+
const state = await fs.readJson(statePath).catch(() => null);
|
|
383
|
+
if (state && state.status === 'active') {
|
|
384
|
+
const now = new Date().toISOString();
|
|
385
|
+
await fs.writeJson(statePath, { ...state, status: 'pending', updatedAt: now, pausedAt: now }, { spaces: 2 });
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
372
390
|
async function loadAllTaskStates() {
|
|
373
391
|
if (!(await fs.pathExists(TASKS_DIR))) return [];
|
|
374
392
|
const entries = await fs.readdir(TASKS_DIR, { withFileTypes: true });
|
|
@@ -467,6 +485,9 @@ module.exports.createOrActivateTaskState = async function createOrActivateTaskSt
|
|
|
467
485
|
// Detect current gate
|
|
468
486
|
const currentGate = await detectCurrentGate(taskId);
|
|
469
487
|
|
|
488
|
+
// Deactivate any other tasks before marking this one active
|
|
489
|
+
await deactivateOtherTasks(taskId);
|
|
490
|
+
|
|
470
491
|
const existing = (await fs.pathExists(statePath)) ? await fs.readJson(statePath).catch(() => ({})) : {};
|
|
471
492
|
const now = new Date().toISOString();
|
|
472
493
|
const taskState = {
|