@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/bin/aiflow.js CHANGED
@@ -1,360 +1,400 @@
1
- #!/usr/bin/env node
2
-
3
- const { program } = require('commander');
4
- const chalk = require('chalk');
5
- const packageJson = require('../package.json');
6
- const initCommand = require('../scripts/init');
7
- const updateCommand = require('../scripts/update');
8
- const doctorCommand = require('../scripts/doctor');
9
- const useCommand = require('../scripts/use');
10
- const TaskDetector = require('../scripts/detect');
11
- const validateCommand = require('../scripts/validate');
12
- const memoryCommand = require('../scripts/memory');
13
- const contextCommand = require('../scripts/context');
14
- const promptCommand = require('../scripts/prompt');
15
- const removeCommand = require('../scripts/remove');
16
- const guideCommand = require('../scripts/guide');
17
- const telemetryCommand= require('../scripts/telemetry/cli');
18
- const taskCommand = require('../scripts/task');
19
- const checkpointCommand = require('../scripts/checkpoint');
20
- const { record } = require('../scripts/telemetry/record');
21
-
22
- program
23
- .version(packageJson.version)
24
- .description('AI Flow Kit CLI - AI-powered development workflows for teams');
25
-
26
- // ── GLOBAL TELEMETRY HOOK ────────────────────────────────────
27
- // We intercept raw argv here so we can catch --version, --help,
28
- // and invalid commands *before* Commander exits the process.
29
- (() => {
30
- const args = process.argv.slice(2);
31
- let cmd = 'help';
32
- if (args.length > 0) {
33
- const first = args[0];
34
- if (['-V', '-v', '--version'].includes(first)) {
35
- cmd = 'version';
36
- } else if (['-h', '--help'].includes(first)) {
37
- cmd = 'help';
38
- } else if (first.startsWith('-')) {
39
- cmd = 'unknown_flag';
40
- if (args.includes('--help') || args.includes('-h')) cmd = 'help';
41
- } else if (first === 'memory') {
42
- const sec = args[1];
43
- cmd = (sec && !sec.startsWith('-')) ? `memory.${sec}` : 'memory';
44
- } else {
45
- cmd = first;
46
- if (args.includes('--help') || args.includes('-h')) {
47
- cmd = `${first}.help`;
48
- }
49
- }
50
- }
51
- // Record everything except telemetry and gate actions (gate records its own rich event)
52
- if (cmd !== 'telemetry' && !cmd.startsWith('telemetry.') && cmd !== 'gate') {
53
- record('command.invoked', { command: cmd });
54
- }
55
- })();
56
-
57
-
58
- // ── init ──────────────────────────────────────────────────────
59
- program
60
- .command('init')
61
- .description('Initialize AI Flow Kit in your project')
62
- .option('-f, --framework <types>', 'framework(s), comma-separated (e.g. spring-boot,reactjs)')
63
- .option('-a, --adapter <types>', 'adapter(s), comma-separated (e.g. backlog,jira)')
64
- .option('-e, --env <types>', 'AI environment(s)/tool(s), comma-separated (e.g. cursor,gemini,copilot)')
65
- .option('--with-rtk', 'force enable RTK token compression hook')
66
- .option('--no-rtk', 'skip RTK setup even if RTK is detected')
67
- // .option('--with-gitnexus', 'enable GitNexus code intelligence (indexes in background)')
68
- // .option('--wait', 'wait for GitNexus indexing to complete before exiting')
69
- .action((options) => {
70
- options.frameworks = options.framework
71
- ? options.framework.split(',').map(s => s.trim()).filter(Boolean)
72
- : [];
73
- options.adapters = options.adapter
74
- ? options.adapter.split(',').map(s => s.trim()).filter(Boolean)
75
- : [];
76
- options.aiTools = options.env
77
- ? options.env.split(',').map(s => s.trim()).filter(Boolean)
78
- : Object.keys(require('../scripts/init').AI_TOOL_FILES || {}).filter(t => t !== 'generic');
79
-
80
- record('command.invoked', { command: 'init' });
81
- initCommand(options);
82
- });
83
-
84
- // ── use ───────────────────────────────────────────────────────
85
- program
86
- .command('use [target]')
87
- .description('Load context from ticket, version, or file')
88
- .option('-m, --manual', 'manual context entry')
89
- .option('-f, --file <path>', 'load from file')
90
- .option('-s, --save <name>', 'save as named context')
91
- .option('--with-comments', 'load all comments (default: description only)')
92
- .option('--comments-last <n>', 'load only the last N comments', parseInt)
93
- .option('--comments-from <n>', 'load comments from index N onward (0-based)', parseInt)
94
- .option('--fast', 'fast mode: minimal Q&A, concise requirement doc (Default)')
95
- .option('--full', 'full mode: force complete analysis with Q&A (default)')
96
- .action((target, options) => {
97
- record('command.invoked', { command: 'use' });
98
- useCommand(target, options);
99
- });
100
-
101
- // ── prompt ────────────────────────────────────────────────────
102
- program
103
- .command('prompt [type]')
104
- .description('Generate AI prompt with context and rules')
105
- .option('-l, --list', 'list available prompt types')
106
- .option('-o, --output <file>', 'save to file')
107
- .option('--lang <lang>', 'language: english (default) or vietnamese')
108
- .option('--detail <level>', 'detail level: minimal | standard (default) | comprehensive')
109
- .action((type, options) => {
110
- record('command.invoked', { command: 'prompt' });
111
- promptCommand(type, options);
112
- });
113
-
114
- // ── detect ────────────────────────────────────────────────────
115
- program
116
- .command('detect [description]')
117
- .description('Auto-detect task type from description')
118
- .option('-v, --verbose', 'show detection details')
119
- .option('-t, --threshold <number>', 'confidence threshold (0-100)')
120
- .action((description, options) => {
121
- record('command.invoked', { command: 'detect' });
122
- const detector = new TaskDetector();
123
- const threshold = options.threshold ? parseInt(options.threshold) / 100 : 0.8;
124
- const result = detector.detect(description, threshold);
125
- console.log(chalk.cyan('\nTask Detection Results:\n'));
126
- console.log(detector.formatOutput(result, options.verbose));
127
- });
128
-
129
- // ── task ─────────────────────────────────────────────────────
130
- const taskCmd = program.command('task').description('Manage multiple tasks — pause, switch, resume');
131
-
132
- taskCmd
133
- .command('status')
134
- .description('Show active task and pending tasks')
135
- .action(() => { record('command.invoked', { command: 'task.status' }); taskCommand('status'); });
136
-
137
- taskCmd
138
- .command('list')
139
- .description('List all saved tasks')
140
- .action(() => { record('command.invoked', { command: 'task.list' }); taskCommand('list'); });
141
-
142
- taskCmd
143
- .command('pause')
144
- .description('Pause current task and save progress')
145
- .option('-n, --note <text>', 'note about why you are pausing')
146
- .action((opts) => { record('command.invoked', { command: 'task.pause' }); taskCommand('pause', { note: opts.note }); });
147
-
148
- taskCmd
149
- .command('switch <ticket-id>')
150
- .description('Pause current task and switch to another')
151
- .action((ticketId) => { record('command.invoked', { command: 'task.switch' }); taskCommand('switch', { taskId: ticketId }); });
152
-
153
- taskCmd
154
- .command('resume <ticket-id>')
155
- .description('Resume a paused task')
156
- .action((ticketId) => { record('command.invoked', { command: 'task.resume' }); taskCommand('resume', { taskId: ticketId }); });
157
-
158
- taskCmd
159
- .command('reset <ticket-id>')
160
- .description('Reset task to Gate 1 (keeps ticket context, clears gate progress)')
161
- .action((ticketId) => { record('command.invoked', { command: 'task.reset' }); taskCommand('reset', { taskId: ticketId }); });
162
-
163
- taskCmd
164
- .command('remove <ticket-id>')
165
- .description('Permanently delete all saved data for a task')
166
- .action((ticketId) => { record('command.invoked', { command: 'task.remove' }); taskCommand('remove', { taskId: ticketId }); });
167
-
168
- taskCmd
169
- .command('next')
170
- .description('Approve current gate and prepare next session (saves state for resume)')
171
- .option('-t, --ticket <id>', 'ticket ID (defaults to active task)')
172
- .action((opts) => {
173
- record('command.invoked', { command: 'task.next' });
174
- taskCommand('next', { taskId: opts.ticket });
175
- });
176
-
177
- // ── context ───────────────────────────────────────────────────
178
- program
179
- .command('context [action]')
180
- .description('Manage task contexts (list|show|save <name>|clear)')
181
- .option('-s, --save <name>', 'save current context with name')
182
- .option('--load <name>', 'load saved context by name')
183
- .option('-d, --delete <name>', 'delete saved context')
184
- .option('--clear', 'clear all saved contexts')
185
- .option('--show', 'show current context')
186
- .action((action, options) => {
187
- record('command.invoked', { command: 'context' });
188
- contextCommand(action, options);
189
- });
190
-
191
- // ── checkpoint ────────────────────────────────────────────────
192
- program
193
- .command('checkpoint')
194
- .description('Record token usage checkpoint (called by AI during gate work)')
195
- .requiredOption('--gate <n>', 'gate number (1–5)', parseInt)
196
- .requiredOption('--step <name>', 'step name (e.g. "tests-written")')
197
- .requiredOption('--tokens <n>', 'estimated token count', parseInt)
198
- .option('-t, --ticket <id>', 'ticket ID (defaults to active task)')
199
- .action((opts) => {
200
- record('command.invoked', { command: 'checkpoint' });
201
- checkpointCommand({ gate: opts.gate, step: opts.step, tokens: opts.tokens, ticket: opts.ticket });
202
- });
203
-
204
- // ── validate ──────────────────────────────────────────────────
205
- program
206
- .command('validate <file>')
207
- .description('Validate output against team rules')
208
- .option('-r, --ruleset <set>', 'rule set: default | strict | lenient', 'default')
209
- .option('-v, --verbose', 'detailed report')
210
- .option('--fix', 'auto-fix trailing whitespace')
211
- .action((file, options) => {
212
- record('command.invoked', { command: 'validate' });
213
- validateCommand(file, {
214
- ruleset: options.ruleset,
215
- verbose: options.verbose,
216
- fix: options.fix
217
- });
218
- });
219
-
220
- // ── memory ────────────────────────────────────────────────────
221
- // Sub-commands to avoid the ambiguous "memory <action> --save key" pattern
222
- const memCmd = program.command('memory').description('Manage team knowledge and memory');
223
-
224
- memCmd
225
- .command('save <key> <value>')
226
- .description('Save a memory entry')
227
- .action((key, value) => { record('command.invoked', { command: 'memory.save' }); memoryCommand('save', { key, value }); });
228
-
229
- memCmd
230
- .command('get <key>')
231
- .description('Retrieve a memory entry')
232
- .action((key) => { record('command.invoked', { command: 'memory.get' }); memoryCommand('get', { key }); });
233
-
234
- memCmd
235
- .command('list')
236
- .description('List all memories')
237
- .action(() => { record('command.invoked', { command: 'memory.list' }); memoryCommand('list'); });
238
-
239
- memCmd
240
- .command('search <query>')
241
- .description('Search memories by keyword')
242
- .action((query) => { record('command.invoked', { command: 'memory.search' }); memoryCommand('search', { query }); });
243
-
244
- memCmd
245
- .command('delete <key>')
246
- .description('Delete a memory entry')
247
- .action((key) => { record('command.invoked', { command: 'memory.delete' }); memoryCommand('delete', { key }); });
248
-
249
- memCmd
250
- .command('clear')
251
- .description('Clear all memories')
252
- .action(() => { record('command.invoked', { command: 'memory.clear' }); memoryCommand('clear'); });
253
-
254
- // ── guide ─────────────────────────────────────────────────────
255
- program
256
- .command('guide')
257
- .description('Show quickstart guide, architecture, and command reference')
258
- .option('--flow', 'show architecture & flow diagram only')
259
- .option('--commands', 'show command reference only')
260
- .action((options) => {
261
- record('command.invoked', { command: 'guide' });
262
- guideCommand(options);
263
- });
264
-
265
- // ── gate (telemetry for gate workflow events) ─────────────────
266
- program
267
- .command('gate <number> <action>')
268
- .description('Log a gate workflow event (start|approved) called by AI during gate workflow')
269
- .option('--ticket <id>', 'ticket ID')
270
- .option('--ai-tool <tool>', 'AI tool name')
271
- .action((number, action, options) => {
272
- const gateNum = parseInt(number);
273
- if (isNaN(gateNum) || gateNum < 1 || gateNum > 5) return;
274
- if (!['start', 'approved'].includes(action)) return;
275
- record(`gate.${action}`, {
276
- gate: gateNum,
277
- ticket_id: options.ticket || '',
278
- command: `gate${gateNum}.${action}`,
279
- ai_tool: options.aiTool || ''
280
- });
281
-
282
- if (action === 'approved') {
283
- console.log(chalk.cyan(`\n Gate ${gateNum} approved!`));
284
- console.log(chalk.yellow(` Pro-Tip: To avoid context pollution, it's recommended to start a fresh chat session.`));
285
- console.log(chalk.gray(` Run: aiflow task next${options.ticket ? ` --ticket ${options.ticket}` : ''} to save progress,`));
286
- console.log(chalk.gray(` then open a NEW chatbox and type "continue".\n`));
287
- }
288
- });
289
-
290
- // ── remove ────────────────────────────────────────────────────
291
- program
292
- .command('remove')
293
- .description('Remove ai-flow-kit from project, a cached version, or uninstall globally')
294
- .option('--version <ver>', 'remove a specific cached version from .aiflow/versions/')
295
- .option('--global', 'uninstall the global npm package (removes `aiflow` command)')
296
- .action((options) => {
297
- record('command.invoked', { command: 'remove' });
298
- removeCommand(options);
299
- });
300
-
301
- // ── update ────────────────────────────────────────────────────
302
- program
303
- .command('update')
304
- .description('Update to latest version')
305
- .option('--force', 'force update even if already on latest')
306
- .action((options) => {
307
- record('command.invoked', { command: 'update' });
308
- updateCommand(options);
309
- });
310
-
311
- // ── sync-skills ───────────────────────────────────────────────
312
- program
313
- .command('sync-skills')
314
- .description('Manually synchronize AI Instruction files with local custom skills')
315
- .action(async () => {
316
- record('command.invoked', { command: 'sync-skills' });
317
- const projectDir = process.cwd();
318
- const { setupFramework, AI_TOOL_FILES, ensureAiflowGitignored } = require('../scripts/init');
319
- const fs = require('fs-extra');
320
- const path = require('path');
321
- const chalk = require('chalk');
322
-
323
- const stateFile = path.join(projectDir, '.aiflow', 'state.json');
324
- if (!(await fs.pathExists(stateFile))) {
325
- console.log(chalk.red('Project is not initialized. Please run `aiflow init` first.'));
326
- return;
327
- }
328
-
329
- const state = await fs.readJson(stateFile);
330
- const frameworks = state.frameworks || [];
331
- const selectedTools = state.aiTools || Object.keys(AI_TOOL_FILES);
332
-
333
- console.log(chalk.cyan(' Syncing AI Instruction files...'));
334
- for (const fw of frameworks) {
335
- await setupFramework(projectDir, fw, frameworks.length > 1, selectedTools);
336
- }
337
- await ensureAiflowGitignored(projectDir);
338
- console.log(chalk.green('✨ Sync completed!'));
339
- });
340
-
341
- // ── doctor ────────────────────────────────────────────────────
342
- program
343
- .command('doctor')
344
- .description('Health check of AI Flow Kit setup')
345
- .option('-t, --ticket <id>', 'show token breakdown for specific ticket')
346
- .option('-v, --verbose', 'detailed output')
347
- .action((opts) => {
348
- record('command.invoked', { command: 'doctor' });
349
- doctorCommand({ ticket: opts.ticket, verbose: opts.verbose });
350
- });
351
-
352
- // ── telemetry ──────────────────────────────────────────────────
353
- program
354
- .command('telemetry [action]')
355
- .description('Manage telemetry logging (enable|disable|status)')
356
- .action((action) => {
357
- telemetryCommand(action || 'status');
358
- });
359
-
360
- program.parse(process.argv);
1
+ #!/usr/bin/env node
2
+
3
+ const { program } = require('commander');
4
+ const chalk = require('chalk');
5
+ const packageJson = require('../package.json');
6
+ const initCommand = require('../scripts/init');
7
+ const updateCommand = require('../scripts/update');
8
+ const doctorCommand = require('../scripts/doctor');
9
+ const useCommand = require('../scripts/use');
10
+ const TaskDetector = require('../scripts/detect');
11
+ const validateCommand = require('../scripts/validate');
12
+ const memoryCommand = require('../scripts/memory');
13
+ const contextCommand = require('../scripts/context');
14
+ const promptCommand = require('../scripts/prompt');
15
+ const removeCommand = require('../scripts/remove');
16
+ const guideCommand = require('../scripts/guide');
17
+ const telemetryCommand= require('../scripts/telemetry/cli');
18
+ const taskCommand = require('../scripts/task');
19
+ const checkpointCommand = require('../scripts/checkpoint');
20
+ const { record } = require('../scripts/telemetry/record');
21
+
22
+ // ── DEPRECATION WARNING ───────────────────────────────────────
23
+ if (!process.env.AIFLOW_IS_AK) {
24
+ console.warn(chalk.yellow('\n⚠ Warning: "aiflow" command is deprecated and will be removed in future versions.'));
25
+ console.warn(chalk.yellow('Please use the shorter "ak" command instead.\n'));
26
+ }
27
+
28
+ program
29
+ .version(packageJson.version)
30
+ .description('AI Flow Kit CLI - AI-powered development workflows for teams');
31
+
32
+ // ── GLOBAL TELEMETRY HOOK ────────────────────────────────────
33
+ // We intercept raw argv here so we can catch --version, --help,
34
+ // and invalid commands *before* Commander exits the process.
35
+ // Also alias `-v` to commander's `-V` at the root only — subcommands still
36
+ // keep `-v` as `--verbose`. And alias `--fw` to `--framework`.
37
+ (() => {
38
+ if (process.argv[2] === '-v') process.argv[2] = '-V';
39
+ for (let i = 3; i < process.argv.length; i++) {
40
+ if (process.argv[i] === '--fw') process.argv[i] = '--framework';
41
+ }
42
+ const args = process.argv.slice(2);
43
+ let cmd = 'help';
44
+ if (args.length > 0) {
45
+ const first = args[0];
46
+ if (['-V', '-v', '--version'].includes(first)) {
47
+ cmd = 'version';
48
+ } else if (['-h', '--help'].includes(first)) {
49
+ cmd = 'help';
50
+ } else if (first.startsWith('-')) {
51
+ cmd = 'unknown_flag';
52
+ if (args.includes('--help') || args.includes('-h')) cmd = 'help';
53
+ } else if (first === 'memory') {
54
+ const sec = args[1];
55
+ cmd = (sec && !sec.startsWith('-')) ? `memory.${sec}` : 'memory';
56
+ } else {
57
+ cmd = first;
58
+ if (args.includes('--help') || args.includes('-h')) {
59
+ cmd = `${first}.help`;
60
+ }
61
+ }
62
+ }
63
+ // Record everything except telemetry and gate actions (gate records its own rich event)
64
+ if (cmd !== 'telemetry' && !cmd.startsWith('telemetry.') && cmd !== 'gate') {
65
+ record('command.invoked', { command: cmd });
66
+ }
67
+ })();
68
+
69
+
70
+ // ── init ──────────────────────────────────────────────────────
71
+ program
72
+ .command('init')
73
+ .alias('i')
74
+ .description('Initialize AI Flow Kit in your project')
75
+ .option('-f, --framework <types>', 'framework(s), comma-separated (e.g. spring-boot,reactjs)')
76
+ .option('-a, --adapter <types>', 'adapter(s), comma-separated (e.g. backlog,jira)')
77
+ .option('-e, --env <types>', 'AI environment(s)/tool(s), comma-separated (e.g. cursor,gemini,copilot)')
78
+ .option('--with-rtk', 'force enable RTK token compression hook')
79
+ .option('--no-rtk', 'skip RTK setup even if RTK is detected')
80
+ .action((options) => {
81
+ options.frameworks = options.framework
82
+ ? options.framework.split(',').map(s => s.trim()).filter(Boolean)
83
+ : [];
84
+ options.adapters = options.adapter
85
+ ? options.adapter.split(',').map(s => s.trim()).filter(Boolean)
86
+ : [];
87
+ options.aiTools = options.env
88
+ ? options.env.split(',').map(s => s.trim()).filter(Boolean)
89
+ : Object.keys(require('../scripts/init').AI_TOOL_FILES || {}).filter(t => t !== 'generic');
90
+
91
+ record('command.invoked', { command: 'init' });
92
+ initCommand(options);
93
+ });
94
+
95
+ // ── use ───────────────────────────────────────────────────────
96
+ program
97
+ .command('use [target]')
98
+ .alias('u')
99
+ .description('Load context from ticket, version, or file')
100
+ .option('-m, --manual', 'manual context entry')
101
+ .option('-f, --file <path>', 'load from file')
102
+ .option('-s, --save <name>', 'save as named context')
103
+ .option('-c, --coms', 'load all comments (alias for --with-comments)')
104
+ .option('--with-comments', 'load all comments')
105
+ .option('--cid <id>', 'load a specific comment by ID')
106
+ .option('--clast <n>', 'load the last N comments', parseInt)
107
+ .option('--cfrom <id>', 'load comments from ID N onward', parseInt)
108
+ .option('--cto <id>', 'load comments up to ID N', parseInt)
109
+ .option('-F, --fast', 'fast mode: minimal Q&A, concise requirement doc (Default)')
110
+ .option('-U, --full', 'full mode: force complete analysis with Q&A (default)')
111
+ .action((target, options) => {
112
+ record('command.invoked', { command: 'use' });
113
+ useCommand(target, options);
114
+ });
115
+
116
+ // ── prompt ────────────────────────────────────────────────────
117
+ program
118
+ .command('prompt [type]')
119
+ .alias('p')
120
+ .description('Generate AI prompt with context and rules')
121
+ .option('-l, --list', 'list available prompt types')
122
+ .option('-o, --output <file>', 'save to file')
123
+ .option('-L, --lang <lang>', 'language: english (default) or vietnamese')
124
+ .option('-d, --detail <level>', 'detail level: minimal | standard (default) | comprehensive')
125
+ .action((type, options) => {
126
+ record('command.invoked', { command: 'prompt' });
127
+ promptCommand(type, options);
128
+ });
129
+
130
+ // ── detect ────────────────────────────────────────────────────
131
+ program
132
+ .command('detect [description]')
133
+ .alias('d')
134
+ .description('Auto-detect task type from description')
135
+ .option('-v, --verbose', 'show detection details')
136
+ .option('-t, --threshold <number>', 'confidence threshold (0-100)')
137
+ .action((description, options) => {
138
+ record('command.invoked', { command: 'detect' });
139
+ const detector = new TaskDetector();
140
+ const threshold = options.threshold ? parseInt(options.threshold) / 100 : 0.8;
141
+ const result = detector.detect(description, threshold);
142
+ console.log(chalk.cyan('\nTask Detection Results:\n'));
143
+ console.log(detector.formatOutput(result, options.verbose));
144
+ });
145
+
146
+ // ── task ─────────────────────────────────────────────────────
147
+ const taskCmd = program.command('task').alias('t').description('Manage multiple tasks — pause, switch, resume');
148
+
149
+ taskCmd
150
+ .command('status')
151
+ .alias('st')
152
+ .description('Show active task and pending tasks')
153
+ .action(() => { record('command.invoked', { command: 'task.status' }); taskCommand('status'); });
154
+
155
+ taskCmd
156
+ .command('list')
157
+ .alias('ls')
158
+ .description('List all saved tasks')
159
+ .action(() => { record('command.invoked', { command: 'task.list' }); taskCommand('list'); });
160
+
161
+ taskCmd
162
+ .command('pause')
163
+ .alias('p')
164
+ .description('Pause current task and save progress')
165
+ .option('-n, --note <text>', 'note about why you are pausing')
166
+ .action((opts) => { record('command.invoked', { command: 'task.pause' }); taskCommand('pause', { note: opts.note }); });
167
+
168
+ taskCmd
169
+ .command('switch <ticket-id>')
170
+ .alias('sw')
171
+ .description('Pause current task and switch to another')
172
+ .action((ticketId) => { record('command.invoked', { command: 'task.switch' }); taskCommand('switch', { taskId: ticketId }); });
173
+
174
+ taskCmd
175
+ .command('resume <ticket-id>')
176
+ .alias('r')
177
+ .description('Resume a paused task')
178
+ .action((ticketId) => { record('command.invoked', { command: 'task.resume' }); taskCommand('resume', { taskId: ticketId }); });
179
+
180
+ taskCmd
181
+ .command('reset <ticket-id>')
182
+ .alias('rst')
183
+ .description('Reset task to Gate 1 (keeps ticket context, clears gate progress)')
184
+ .action((ticketId) => { record('command.invoked', { command: 'task.reset' }); taskCommand('reset', { taskId: ticketId }); });
185
+
186
+ taskCmd
187
+ .command('remove <ticket-id>')
188
+ .alias('rm')
189
+ .description('Permanently delete all saved data for a task')
190
+ .action((ticketId) => { record('command.invoked', { command: 'task.remove' }); taskCommand('remove', { taskId: ticketId }); });
191
+
192
+ taskCmd
193
+ .command('next')
194
+ .alias('n')
195
+ .description('Approve current gate and prepare next session (saves state for resume)')
196
+ .option('-t, --ticket <id>', 'ticket ID (defaults to active task)')
197
+ .action((opts) => {
198
+ record('command.invoked', { command: 'task.next' });
199
+ taskCommand('next', { taskId: opts.ticket });
200
+ });
201
+
202
+ // ── context ───────────────────────────────────────────────────
203
+ program
204
+ .command('context [action]')
205
+ .alias('ctx')
206
+ .description('Manage task contexts (list|show|save <name>|clear)')
207
+ .option('-s, --save <name>', 'save current context with name')
208
+ .option('-l, --load <name>', 'load saved context by name')
209
+ .option('-d, --delete <name>', 'delete saved context')
210
+ .option('--clear', 'clear all saved contexts')
211
+ .option('--show', 'show current context')
212
+ .action((action, options) => {
213
+ record('command.invoked', { command: 'context' });
214
+ contextCommand(action, options);
215
+ });
216
+
217
+ // ── checkpoint ────────────────────────────────────────────────
218
+ program
219
+ .command('checkpoint')
220
+ .alias('cp')
221
+ .description('Record token usage checkpoint (called by AI during gate work)')
222
+ .requiredOption('-g, --gate <n>', 'gate number (1–5)', parseInt)
223
+ .requiredOption('-s, --step <name>', 'step name (e.g. "tests-written")')
224
+ .requiredOption('-n, --tokens <n>', 'estimated token count', parseInt)
225
+ .option('-t, --ticket <id>', 'ticket ID (defaults to active task)')
226
+ .action((opts) => {
227
+ record('command.invoked', { command: 'checkpoint' });
228
+ checkpointCommand({ gate: opts.gate, step: opts.step, tokens: opts.tokens, ticket: opts.ticket });
229
+ });
230
+
231
+ // ── validate ──────────────────────────────────────────────────
232
+ program
233
+ .command('validate <file>')
234
+ .alias('vl')
235
+ .description('Validate output against team rules')
236
+ .option('-r, --ruleset <set>', 'rule set: default | strict | lenient', 'default')
237
+ .option('-v, --verbose', 'detailed report')
238
+ .option('-x, --fix', 'auto-fix trailing whitespace')
239
+ .action((file, options) => {
240
+ record('command.invoked', { command: 'validate' });
241
+ validateCommand(file, {
242
+ ruleset: options.ruleset,
243
+ verbose: options.verbose,
244
+ fix: options.fix
245
+ });
246
+ });
247
+
248
+ // ── memory ────────────────────────────────────────────────────
249
+ // Sub-commands to avoid the ambiguous "memory <action> --save key" pattern
250
+ const memCmd = program.command('memory').alias('mem').description('Manage team knowledge and memory');
251
+
252
+ memCmd
253
+ .command('save <key> <value>')
254
+ .alias('s')
255
+ .description('Save a memory entry')
256
+ .action((key, value) => { record('command.invoked', { command: 'memory.save' }); memoryCommand('save', { key, value }); });
257
+
258
+ memCmd
259
+ .command('get <key>')
260
+ .alias('g')
261
+ .description('Retrieve a memory entry')
262
+ .action((key) => { record('command.invoked', { command: 'memory.get' }); memoryCommand('get', { key }); });
263
+
264
+ memCmd
265
+ .command('list')
266
+ .alias('ls')
267
+ .description('List all memories')
268
+ .action(() => { record('command.invoked', { command: 'memory.list' }); memoryCommand('list'); });
269
+
270
+ memCmd
271
+ .command('search <query>')
272
+ .alias('sr')
273
+ .description('Search memories by keyword')
274
+ .action((query) => { record('command.invoked', { command: 'memory.search' }); memoryCommand('search', { query }); });
275
+
276
+ memCmd
277
+ .command('delete <key>')
278
+ .alias('d')
279
+ .description('Delete a memory entry')
280
+ .action((key) => { record('command.invoked', { command: 'memory.delete' }); memoryCommand('delete', { key }); });
281
+
282
+ memCmd
283
+ .command('clear')
284
+ .alias('cl')
285
+ .description('Clear all memories')
286
+ .action(() => { record('command.invoked', { command: 'memory.clear' }); memoryCommand('clear'); });
287
+
288
+ // ── guide ─────────────────────────────────────────────────────
289
+ program
290
+ .command('guide')
291
+ .alias('g')
292
+ .description('Show quickstart guide, architecture, and command reference')
293
+ .option('-f, --flow', 'show architecture & flow diagram only')
294
+ .option('-c, --commands', 'show command reference only')
295
+ .action((options) => {
296
+ record('command.invoked', { command: 'guide' });
297
+ guideCommand(options);
298
+ });
299
+
300
+ // ── gate (telemetry for gate workflow events) ─────────────────
301
+ program
302
+ .command('gate <number> <action>')
303
+ .description('Log a gate workflow event (start|approved) — called by AI during gate workflow')
304
+ .option('--ticket <id>', 'ticket ID')
305
+ .option('--ai-tool <tool>', 'AI tool name')
306
+ .action((number, action, options) => {
307
+ const gateNum = parseInt(number);
308
+ if (isNaN(gateNum) || gateNum < 1 || gateNum > 5) return;
309
+ if (!['start', 'approved'].includes(action)) return;
310
+ record(`gate.${action}`, {
311
+ gate: gateNum,
312
+ ticket_id: options.ticket || '',
313
+ command: `gate${gateNum}.${action}`,
314
+ ai_tool: options.aiTool || ''
315
+ });
316
+
317
+ if (action === 'approved') {
318
+ console.log(chalk.cyan(`\n Gate ${gateNum} approved!`));
319
+ console.log(chalk.yellow(` Pro-Tip: To avoid context pollution, it's recommended to start a fresh chat session.`));
320
+ console.log(chalk.gray(` Run: aiflow task next${options.ticket ? ` --ticket ${options.ticket}` : ''} to save progress,`));
321
+ console.log(chalk.gray(` then open a NEW chatbox and type "continue".\n`));
322
+ }
323
+ });
324
+
325
+ // ── remove ────────────────────────────────────────────────────
326
+ program
327
+ .command('remove')
328
+ .alias('rm')
329
+ .description('Remove ai-flow-kit from project, a cached version, or uninstall globally')
330
+ .option('--version <ver>', 'remove a specific cached version from .aiflow/versions/')
331
+ .option('-g, --global', 'uninstall the global npm package (removes `aiflow` command)')
332
+ .action((options) => {
333
+ record('command.invoked', { command: 'remove' });
334
+ removeCommand(options);
335
+ });
336
+
337
+ // ── update ────────────────────────────────────────────────────
338
+ program
339
+ .command('update')
340
+ .alias('up')
341
+ .description('Update to latest version')
342
+ .option('-f, --force', 'force update even if already on latest')
343
+ .action((options) => {
344
+ record('command.invoked', { command: 'update' });
345
+ updateCommand(options);
346
+ });
347
+
348
+ // ── sync-skills ───────────────────────────────────────────────
349
+ program
350
+ .command('sync-skills')
351
+ .alias('sync')
352
+ .description('Manually synchronize AI Instruction files with local custom skills')
353
+ .action(async () => {
354
+ record('command.invoked', { command: 'sync-skills' });
355
+ const projectDir = process.cwd();
356
+ const { setupFramework, AI_TOOL_FILES, ensureAiflowGitignored } = require('../scripts/init');
357
+ const fs = require('fs-extra');
358
+ const path = require('path');
359
+ const chalk = require('chalk');
360
+
361
+ const stateFile = path.join(projectDir, '.aiflow', 'state.json');
362
+ if (!(await fs.pathExists(stateFile))) {
363
+ console.log(chalk.red('Project is not initialized. Please run `aiflow init` first.'));
364
+ return;
365
+ }
366
+
367
+ const state = await fs.readJson(stateFile);
368
+ const frameworks = state.frameworks || [];
369
+ const selectedTools = state.aiTools || Object.keys(AI_TOOL_FILES);
370
+
371
+ console.log(chalk.cyan('⟳ Syncing AI Instruction files...'));
372
+ for (const fw of frameworks) {
373
+ await setupFramework(projectDir, fw, frameworks.length > 1, selectedTools);
374
+ }
375
+ await ensureAiflowGitignored(projectDir);
376
+ console.log(chalk.green('✨ Sync completed!'));
377
+ });
378
+
379
+ // ── doctor ────────────────────────────────────────────────────
380
+ program
381
+ .command('doctor')
382
+ .alias('dr')
383
+ .description('Health check of AI Flow Kit setup')
384
+ .option('-t, --ticket <id>', 'show token breakdown for specific ticket')
385
+ .option('-v, --verbose', 'detailed output')
386
+ .action((opts) => {
387
+ record('command.invoked', { command: 'doctor' });
388
+ doctorCommand({ ticket: opts.ticket, verbose: opts.verbose });
389
+ });
390
+
391
+ // ── telemetry ──────────────────────────────────────────────────
392
+ program
393
+ .command('telemetry [action]')
394
+ .alias('tel')
395
+ .description('Manage telemetry logging (enable|disable|status)')
396
+ .action((action) => {
397
+ telemetryCommand(action || 'status');
398
+ });
399
+
400
+ program.parse(process.argv);