@relipa/ai-flow-kit 0.0.6 → 0.0.7-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.
Files changed (38) hide show
  1. package/README.md +33 -13
  2. package/bin/aiflow.js +58 -2
  3. package/custom/mcp-presets/gitnexus.json +8 -0
  4. package/custom/skills/generate-spec/SKILL.md +7 -0
  5. package/custom/skills/impact-analysis/SKILL.md +10 -0
  6. package/custom/skills/read-study-requirement/SKILL.md +11 -0
  7. package/custom/skills/review-plan/SKILL.md +15 -0
  8. package/docs/common/AIFLOW.md +12 -6
  9. package/docs/common/CHANGELOG.md +49 -5
  10. package/docs/common/QUICK_START.md +13 -11
  11. package/docs/common/cli-reference.md +23 -0
  12. package/package.json +2 -7
  13. package/scripts/checkpoint.js +46 -0
  14. package/scripts/doctor.js +111 -8
  15. package/scripts/gitnexus-worker.js +94 -0
  16. package/scripts/guide.js +42 -51
  17. package/scripts/hooks/session-start.js +35 -5
  18. package/scripts/hooks/session-stop.js +55 -0
  19. package/scripts/init.js +293 -18
  20. package/scripts/prompt.js +2 -2
  21. package/scripts/remove.js +54 -0
  22. package/scripts/task.js +62 -0
  23. package/scripts/update.js +14 -4
  24. package/scripts/use.js +41 -0
  25. package/upstream/.claude-plugin/marketplace.json +4 -4
  26. package/upstream/.claude-plugin/plugin.json +2 -4
  27. package/upstream/.cursor-plugin/plugin.json +2 -4
  28. package/upstream/docs/plans/2025-11-22-opencode-support-design.md +1 -1
  29. package/upstream/docs/plans/2025-11-28-skills-improvements-from-user-feedback.md +2 -2
  30. package/upstream/docs/testing.md +2 -2
  31. package/upstream/skills/subagent-driven-development/SKILL.md +5 -6
  32. package/upstream/skills/subagent-driven-development/implementer-prompt.md +2 -3
  33. package/upstream/skills/systematic-debugging/CREATION-LOG.md +1 -1
  34. package/upstream/skills/systematic-debugging/root-cause-tracing.md +1 -1
  35. package/upstream/skills/tdd-lean/SKILL.md +127 -0
  36. package/upstream/skills/using-git-worktrees/SKILL.md +4 -5
  37. package/upstream/skills/writing-plans/SKILL.md +3 -9
  38. package/upstream/tests/brainstorm-server/package-lock.json +36 -0
package/scripts/init.js CHANGED
@@ -8,6 +8,18 @@ const PKG_DIR = path.join(__dirname, '..');
8
8
  const GLOBAL_AIFLOW_DIR = path.join(os.homedir(), '.aiflow');
9
9
  const GLOBAL_CREDENTIALS_PATH = path.join(GLOBAL_AIFLOW_DIR, 'credentials.json');
10
10
  const PKG_VERSION = require('../package.json').version;
11
+ const { execSync, spawn } = require('child_process');
12
+
13
+ function commandExists(cmd) {
14
+ try {
15
+ const isWin = process.platform === 'win32';
16
+ const checkCmd = isWin ? `where ${cmd}` : `which ${cmd}`;
17
+ execSync(checkCmd, { stdio: 'ignore' });
18
+ return true;
19
+ } catch (_) {
20
+ return false;
21
+ }
22
+ }
11
23
 
12
24
  // Map framework → language family for picking the right rules
13
25
  const FRAMEWORK_LANGUAGE = {
@@ -157,9 +169,13 @@ async function setupSuperpowersHook(_projectDir, claudeDir) {
157
169
  const hooksDir = path.join(claudeDir, 'hooks');
158
170
  await fs.ensureDir(hooksDir);
159
171
 
160
- const hookSrc = path.join(PKG_DIR, 'scripts', 'hooks', 'session-start.js');
161
- const hookDest = path.join(hooksDir, 'session-start.js');
162
- await fs.copy(hookSrc, hookDest, { overwrite: true });
172
+ const hookStartSrc = path.join(PKG_DIR, 'scripts', 'hooks', 'session-start.js');
173
+ const hookStartDest = path.join(hooksDir, 'session-start.js');
174
+ await fs.copy(hookStartSrc, hookStartDest, { overwrite: true });
175
+
176
+ const hookStopSrc = path.join(PKG_DIR, 'scripts', 'hooks', 'session-stop.js');
177
+ const hookStopDest = path.join(hooksDir, 'session-stop.js');
178
+ await fs.copy(hookStopSrc, hookStopDest, { overwrite: true });
163
179
 
164
180
  const settingsFile = path.join(claudeDir, 'settings.json');
165
181
  let settings = {};
@@ -169,6 +185,7 @@ async function setupSuperpowersHook(_projectDir, claudeDir) {
169
185
 
170
186
  if (!settings.hooks) settings.hooks = {};
171
187
  if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
188
+ if (!settings.hooks.SessionStop) settings.hooks.SessionStop = [];
172
189
 
173
190
  settings.hooks.SessionStart = settings.hooks.SessionStart.filter(
174
191
  h => !(h._aiflowKit)
@@ -178,13 +195,26 @@ async function setupSuperpowersHook(_projectDir, claudeDir) {
178
195
  hooks: [
179
196
  {
180
197
  type: 'command',
181
- command: `node "${hookDest.replace(/\\/g, '/')}"`,
198
+ command: `node "${hookStartDest.replace(/\\/g, '/')}"`,
199
+ }
200
+ ]
201
+ });
202
+
203
+ settings.hooks.SessionStop = settings.hooks.SessionStop.filter(
204
+ h => !(h._aiflowKit)
205
+ );
206
+ settings.hooks.SessionStop.push({
207
+ _aiflowKit: true,
208
+ hooks: [
209
+ {
210
+ type: 'command',
211
+ command: `node "${hookStopDest.replace(/\\/g, '/')}"`,
182
212
  }
183
213
  ]
184
214
  });
185
215
 
186
216
  await fs.writeJson(settingsFile, settings, { spaces: 2 });
187
- console.log(chalk.green(`✓ Superpowers SessionStart hook configured`));
217
+ console.log(chalk.green(`✓ Superpowers hooks (SessionStart, SessionStop) configured`));
188
218
  }
189
219
 
190
220
  async function verifySuperpowersSkills(versionDir) {
@@ -273,24 +303,66 @@ async function setupFramework(projectDir, framework, multi = false, selectedTool
273
303
  finalContent += workflowContent + '\n\n';
274
304
  }
275
305
 
276
- finalContent += frameworkContent;
306
+ const markerStart = '<!-- aiflow-kit-start -->';
307
+ const markerEnd = '<!-- aiflow-kit-end -->';
308
+
309
+ finalContent = `${markerStart}\n${finalContent.trim()}\n${markerEnd}`;
277
310
 
278
311
  const fileExists = await fs.pathExists(targetPath);
279
312
 
280
313
  if (!multi) {
281
314
  const relPath = path.relative(projectDir, targetPath).replace(/\\/g, '/');
282
315
  if (fileExists) {
283
- // File already exists — save aiflow template to .aiflow/reference/ for comparison.
284
- // Developer owns the file in root; we never overwrite without explicit action.
285
- const refPath = path.join(projectDir, '.aiflow', 'reference', path.basename(targetPath));
286
- await fs.ensureDir(path.dirname(refPath));
287
- await fs.writeFile(refPath, finalContent);
288
- const refRel = path.relative(projectDir, refPath).replace(/\\/g, '/');
289
- console.log(chalk.gray(` ${relPath} exists aiflow template saved to ${refRel}`));
290
- skipped.push(relPath);
316
+ const existingContent = await fs.readFile(targetPath, 'utf-8');
317
+ const hasStart = existingContent.includes(markerStart);
318
+ const hasEnd = existingContent.includes(markerEnd);
319
+
320
+ if (hasStart && hasEnd) {
321
+ const doUpdate = await confirm({
322
+ message: `File ${relPath} already contains an AI Flow block. Do you want to update this block? (Your custom rules will not be affected)`,
323
+ default: true
324
+ });
325
+ if (doUpdate) {
326
+ const regex = new RegExp(`${markerStart}[\\s\\S]*?${markerEnd}`, 'g');
327
+ const updatedContent = existingContent.replace(regex, finalContent);
328
+ await fs.writeFile(targetPath, updatedContent);
329
+ console.log(chalk.green(` ✓ Updated AI Flow block in ${relPath}`));
330
+ written.push(AI_TOOL_FILES[tool]);
331
+ } else {
332
+ const refPath = path.join(projectDir, '.aiflow', 'reference', path.basename(targetPath));
333
+ await fs.ensureDir(path.dirname(refPath));
334
+ await fs.writeFile(refPath, finalContent);
335
+ const refRel = path.relative(projectDir, refPath).replace(/\\/g, '/');
336
+ console.log(chalk.gray(` ✓ Skipped ${relPath} — aiflow template saved to ${refRel}`));
337
+ skipped.push(relPath);
338
+ }
339
+ } else {
340
+ const doOverwrite = await confirm({
341
+ message: `File ${relPath} is missing the new AI Flow marker block. Do you want to OVERWRITE it completely with the latest template?`,
342
+ default: true
343
+ });
344
+ if (doOverwrite) {
345
+ await fs.writeFile(targetPath, finalContent);
346
+ console.log(chalk.green(` ✓ Overwrote ${relPath} with latest AI Flow template`));
347
+ written.push(AI_TOOL_FILES[tool]);
348
+ } else {
349
+ const refPath = path.join(projectDir, '.aiflow', 'reference', path.basename(targetPath));
350
+ await fs.ensureDir(path.dirname(refPath));
351
+ await fs.writeFile(refPath, finalContent);
352
+ const refRel = path.relative(projectDir, refPath).replace(/\\/g, '/');
353
+ console.log(chalk.gray(` ✓ Skipped ${relPath} — aiflow template saved to ${refRel}`));
354
+ skipped.push(relPath);
355
+ }
356
+ }
291
357
  } else {
292
- await fs.writeFile(targetPath, finalContent);
293
- written.push(AI_TOOL_FILES[tool]);
358
+ const doCreate = await confirm({
359
+ message: `File ${relPath} does not exist. Do you want to create it using the latest AI Flow template?`,
360
+ default: true
361
+ });
362
+ if (doCreate) {
363
+ await fs.writeFile(targetPath, finalContent);
364
+ written.push(AI_TOOL_FILES[tool]);
365
+ }
294
366
  }
295
367
  } else {
296
368
  if (fileExists) {
@@ -494,9 +566,19 @@ function verifyFigma(credentials) {
494
566
  });
495
567
  }
496
568
 
497
- async function ensureCredentialsGitignored(projectDir) {
569
+ async function ensureAiflowGitignored(projectDir) {
498
570
  const gitignorePath = path.join(projectDir, '.gitignore');
499
- const entries = ['.aiflow/no-telemetry'];
571
+ const entries = [
572
+ '.aiflow/',
573
+ 'plan/',
574
+ '.claude/',
575
+ '.rules/',
576
+ '.mcp.json',
577
+ 'CLAUDE.md',
578
+ 'GEMINI.md',
579
+ '.cursorrules',
580
+ '.github/copilot-instructions.md'
581
+ ];
500
582
  let content = '';
501
583
  if (await fs.pathExists(gitignorePath)) {
502
584
  content = await fs.readFile(gitignorePath, 'utf-8');
@@ -523,6 +605,190 @@ function maskSecret(value) {
523
605
  return `${value.slice(0, 4)}***${value.slice(-4)}`;
524
606
  }
525
607
 
608
+ // ── GitNexus (code intelligence) helpers ─────────────────────────
609
+
610
+ const IGNORE_DIRS = new Set([
611
+ '.git', 'node_modules', 'target', 'build', '.aiflow',
612
+ 'dist', 'out', '.next', '.nuxt', 'vendor', 'coverage', '.gradle',
613
+ ]);
614
+ const SOURCE_EXT = /\.(java|kt|groovy|js|ts|jsx|tsx|mjs|cjs|php|py|go|rb|cs|cpp|c|h|rs|vue|svelte|xml|yml|yaml|sql|properties)$/i;
615
+ async function countSourceFiles(dir) {
616
+ let count = 0;
617
+ const scan = async (d) => {
618
+ let entries;
619
+ try { entries = await fs.readdir(d, { withFileTypes: true }); } catch (_) { return; }
620
+ for (const e of entries) {
621
+ if (e.isDirectory()) {
622
+ if (!IGNORE_DIRS.has(e.name) && !e.name.startsWith('.')) await scan(path.join(d, e.name));
623
+ } else if (SOURCE_EXT.test(e.name)) {
624
+ count++;
625
+ }
626
+ }
627
+ };
628
+ await scan(dir);
629
+ return count;
630
+ }
631
+
632
+ async function isGitNexusConfigured(projectDir) {
633
+ const mcpPath = path.join(projectDir, '.mcp.json');
634
+ if (!(await fs.pathExists(mcpPath))) return false;
635
+ const mcp = await fs.readJson(mcpPath).catch(() => ({}));
636
+ return !!(mcp.mcpServers && mcp.mcpServers.gitnexus);
637
+ }
638
+
639
+ async function setupGitNexus(projectDir, wait = false) {
640
+ const { spawn } = require('child_process');
641
+ // On Windows, use shell:true so npm scripts (.cmd) resolve correctly — avoids EINVAL
642
+ const isWin = process.platform === 'win32';
643
+ const npxCmd = 'npx';
644
+ const spawnOpts = (extra = {}) => ({ shell: isWin, windowsHide: true, ...extra });
645
+
646
+ // 1. Inject MCP server config into .mcp.json (merge, don't overwrite)
647
+ const mcpPath = path.join(projectDir, '.mcp.json');
648
+ let existing = {};
649
+ if (await fs.pathExists(mcpPath)) {
650
+ existing = await fs.readJson(mcpPath).catch(() => ({}));
651
+ }
652
+ if (!existing.mcpServers) existing.mcpServers = {};
653
+ const mcpAlreadyPresent = !!(existing.mcpServers.gitnexus);
654
+
655
+ // 3. Detect if gitnexus is already available in PATH (preferred over npx)
656
+ const hasGlobalGitNexus = commandExists('gitnexus');
657
+ if (hasGlobalGitNexus) {
658
+ existing.mcpServers.gitnexus = { command: 'gitnexus', args: ['mcp'] };
659
+ console.log(chalk.gray(' ✓ Global gitnexus found — using it for MCP and indexing.'));
660
+ } else {
661
+ existing.mcpServers.gitnexus = { command: 'npx', args: ['-y', 'gitnexus@latest', 'mcp'] };
662
+ }
663
+ await fs.writeJson(mcpPath, existing, { spaces: 2 });
664
+ if (!mcpAlreadyPresent) {
665
+ console.log(chalk.green('✓ GitNexus MCP server configured (.mcp.json)'));
666
+ }
667
+
668
+ const nodeMajor = parseInt(process.version.slice(1).split('.')[0], 10);
669
+ if (nodeMajor < 20) {
670
+ console.log(chalk.yellow(`⚠ GitNexus indexing skipped — requires Node.js >=20.0.0 (current: ${process.version}).`));
671
+ console.log(chalk.gray(' MCP config saved. After upgrading Node.js, run: npx gitnexus analyze'));
672
+ return;
673
+ }
674
+
675
+ const aiflowDir = path.join(projectDir, '.aiflow');
676
+ await fs.ensureDir(aiflowDir);
677
+ const statusPath = path.join(aiflowDir, 'gitnexus-status.json');
678
+ const startedAt = new Date().toISOString();
679
+ await fs.writeJson(statusPath, { status: 'running', startedAt });
680
+
681
+ if (!wait) {
682
+ const workerPath = path.join(__dirname, 'gitnexus-worker.js');
683
+ const child = spawn(process.execPath, [workerPath, projectDir],
684
+ spawnOpts({ detached: true, stdio: 'ignore' }));
685
+ child.unref();
686
+ console.log(chalk.cyan('⟳ GitNexus: indexing codebase in background...'));
687
+ console.log(chalk.gray(' Notification will appear when you run `aiflow use <TICKET>` next time.'));
688
+ return;
689
+ }
690
+
691
+ const fileCount = await countSourceFiles(projectDir);
692
+ console.log(chalk.cyan(`⟳ GitNexus: indexing ${fileCount} source files (this may take 10–30 min on first run)...`));
693
+ console.log(chalk.gray(' Remove --wait to index in background instead.'));
694
+
695
+ await new Promise((resolve) => {
696
+ const runIndexing = (withMoreRam = false) => {
697
+ const currentCmd = hasGlobalGitNexus ? 'gitnexus' : 'npx';
698
+ const currentArgs = hasGlobalGitNexus ? ['analyze'] : ['-y', 'gitnexus@latest', 'analyze'];
699
+ const env = { ...process.env };
700
+ if (withMoreRam) {
701
+ const ramSize = withMoreRam === '8g' ? '8192' : '4096';
702
+ env.NODE_OPTIONS = `--max-old-space-size=${ramSize}`;
703
+ }
704
+
705
+ const child = spawn(currentCmd, currentArgs,
706
+ spawnOpts({ cwd: projectDir, stdio: ['ignore', 'inherit', 'inherit'], env }));
707
+
708
+ child.on('close', async (code) => {
709
+ const completedAt = new Date().toISOString();
710
+ if (code === 0) {
711
+ await fs.writeJson(statusPath, { status: 'done', startedAt, completedAt, notified: true });
712
+ console.log(chalk.green(`✓ GitNexus: indexed ${fileCount} files — code intelligence is ready.`));
713
+ resolve();
714
+ } else {
715
+ console.log(chalk.red(`✗ GitNexus: indexing failed (exit ${code}).`));
716
+
717
+ // Detection of Segmentation Fault / Crash (139 on Linux/Mac, 3221225477 on Windows)
718
+ const isCrash = (code === 139 || code === 3221225477);
719
+ if (isCrash) {
720
+ console.log(chalk.yellow(`\n⚠ Indexing crashed (Segmentation Fault) at ${withMoreRam ? (withMoreRam === '8g' ? '8GB' : '4GB') : 'default'} RAM.`));
721
+
722
+ const nextRam = !withMoreRam ? '4g' : (withMoreRam === '4g' ? '8g' : null);
723
+ if (nextRam) {
724
+ const retry = await confirm({
725
+ message: `Would you like to retry indexing with increased memory (${nextRam.toUpperCase()})?`,
726
+ default: true
727
+ });
728
+ if (retry) {
729
+ return runIndexing(nextRam); // Recursion, don't resolve yet
730
+ }
731
+ } else {
732
+ console.log(chalk.red('⚠ Already tried with 8GB RAM. Please check if there are any extremely large files or corrupted database.'));
733
+ console.log(chalk.gray(' Try: rm -rf .aiflow/ladybug.db (if exists) or check your .gitignore'));
734
+ }
735
+ }
736
+
737
+ // If npx failed and gitnexus isn't global, offer to install it global (only if not a crash or if retry was rejected)
738
+ if (!hasGlobalGitNexus && !isCrash) {
739
+ console.log(chalk.yellow('\nTip: npx failed. Installing gitnexus globally often fixes environment issues.'));
740
+ const install = await confirm({ message: 'Would you like me to try installing gitnexus globally for you?', default: true });
741
+ if (install) {
742
+ try {
743
+ console.log(chalk.blue('Running: npm install -g gitnexus ...'));
744
+ execSync('npm install -g gitnexus', { stdio: 'inherit' });
745
+ console.log(chalk.green('✓ GitNexus installed globally. Please run `aiflow init` again to retry indexing.'));
746
+ } catch (err) {
747
+ console.log(chalk.red('Failed to install globally. Please try manually: npm install -g gitnexus'));
748
+ }
749
+ }
750
+ }
751
+
752
+ await fs.writeJson(statusPath, { status: 'error', startedAt, completedAt, exitCode: code, notified: true });
753
+ resolve();
754
+ }
755
+ });
756
+
757
+ child.on('error', async (err) => {
758
+ await fs.writeJson(statusPath, {
759
+ status: 'error', startedAt, completedAt: new Date().toISOString(),
760
+ exitCode: null, error: err.message, notified: true,
761
+ });
762
+ console.log(chalk.red(`✗ GitNexus: failed to start — ${err.message}`));
763
+ resolve();
764
+ });
765
+ };
766
+
767
+ runIndexing();
768
+ });
769
+ }
770
+
771
+ async function maybeSetupGitNexus(projectDir, withGitNexus, wait = false) {
772
+ if (!withGitNexus) return;
773
+
774
+ // Check if indexing has already completed successfully (not stale)
775
+ const STALE_MS = 3 * 60 * 60 * 1000;
776
+ const statusPath = path.join(projectDir, '.aiflow', 'gitnexus-status.json');
777
+ let indexDone = false;
778
+ try {
779
+ const gn = await fs.readJson(statusPath);
780
+ const age = gn.startedAt ? Date.now() - new Date(gn.startedAt).getTime() : Infinity;
781
+ const staleRunning = gn.status === 'running' && age > STALE_MS;
782
+ indexDone = gn.status === 'done' && !staleRunning;
783
+ } catch (_) {}
784
+
785
+ if ((await isGitNexusConfigured(projectDir)) && indexDone) {
786
+ console.log(chalk.gray(' GitNexus already configured and indexed — skipping.'));
787
+ return;
788
+ }
789
+ await setupGitNexus(projectDir, wait);
790
+ }
791
+
526
792
  // ── RTK (token compression) helpers ──────────────────────────────
527
793
 
528
794
  async function detectRtk() {
@@ -657,6 +923,12 @@ async function init(options) {
657
923
  await maybeSetupRtk(projectDir, options.withRtk || false);
658
924
  }
659
925
 
926
+ // ── Optional: GitNexus code intelligence ─────────────────────
927
+ await maybeSetupGitNexus(projectDir, options.withGitnexus || false, options.wait || false);
928
+
929
+ // ── Ensure all aiflow files are gitignored ───────────────────
930
+ await ensureAiflowGitignored(projectDir);
931
+
660
932
  console.log(chalk.green('\n✨ Initialized AI Flow Kit successfully!'));
661
933
  console.log(chalk.blue('\n' + '─'.repeat(62)));
662
934
  console.log(chalk.bold.white(' Next steps:'));
@@ -682,3 +954,6 @@ module.exports = init;
682
954
  module.exports.AI_TOOL_FILES = AI_TOOL_FILES;
683
955
  module.exports.detectRtk = detectRtk;
684
956
  module.exports.isRtkHookConfigured = isRtkHookConfigured;
957
+ module.exports.isGitNexusConfigured = isGitNexusConfigured;
958
+ module.exports.setupFramework = setupFramework;
959
+ module.exports.ensureAiflowGitignored = ensureAiflowGitignored;
package/scripts/prompt.js CHANGED
@@ -96,7 +96,7 @@ AI must:
96
96
  Only runs after Gate 2 APPROVED.
97
97
  1. Complex feature (3+ files): **INVOKE:** \`superpowers:subagent-driven-development\`
98
98
  2. **INVOKE:** \`superpowers:test-driven-development\` — write failing test first
99
- 3. Implement to make tests pass — commit each small step
99
+ 3. Implement to make tests pass — verify each small step
100
100
 
101
101
  ### GATE 4 — AI Self-Review (wait for APPROVED)
102
102
  **INVOKE:** \`review-plan\` skill
@@ -174,7 +174,7 @@ AI must:
174
174
  Only runs after Gate 2 APPROVED.
175
175
  1. **INVOKE:** \`superpowers:test-driven-development\` — ensure tests cover CURRENT behavior
176
176
  2. Refactor incrementally — keep tests green at each step
177
- 3. Commit each step
177
+ 3. Commit after manual verification
178
178
 
179
179
  ### GATE 4 — AI Self-Review (wait for APPROVED)
180
180
  **INVOKE:** \`review-plan\` skill
package/scripts/remove.js CHANGED
@@ -89,6 +89,60 @@ async function removeFromProject() {
89
89
  console.log(chalk.green('✓ Removed .rules/'));
90
90
  }
91
91
 
92
+ // Clean up AI Tool files (CLAUDE.md, .cursorrules, etc.)
93
+ const { AI_TOOL_FILES } = require('./init');
94
+ const markerStart = '<!-- aiflow-kit-start -->';
95
+ const markerEnd = '<!-- aiflow-kit-end -->';
96
+
97
+ for (const tool in AI_TOOL_FILES) {
98
+ const filePath = path.join(PROJECT_DIR, AI_TOOL_FILES[tool]);
99
+ if (!(await fs.pathExists(filePath))) continue;
100
+
101
+ let content = await fs.readFile(filePath, 'utf-8');
102
+ let cleaned = false;
103
+
104
+ if (content.includes(markerStart) && content.includes(markerEnd)) {
105
+ // Precise removal using markers
106
+ const startIndex = content.indexOf(markerStart);
107
+ const endIndex = content.indexOf(markerEnd) + markerEnd.length;
108
+ content = content.slice(0, startIndex).trim() + '\n' + content.slice(endIndex).trim();
109
+ content = content.trim();
110
+ cleaned = true;
111
+ } else if (content.includes('## AI Skill Registry (Superpowers)')) {
112
+ // Fallback for legacy installs: remove sections by headers
113
+ console.log(chalk.yellow(` ⚠ Found legacy aiflow sections in ${AI_TOOL_FILES[tool]}`));
114
+ const ok = await confirm({ message: `Remove ai-flow-kit sections from ${AI_TOOL_FILES[tool]}?`, default: true });
115
+ if (ok) {
116
+ // Heuristic: remove from "## AI Skill Registry" or "# Claude AI System Instructions"
117
+ // This is simple and covers most cases. For complex cases, user should manual clean.
118
+ const headers = [
119
+ '# Claude AI System Instructions',
120
+ '## AI Skill Registry (Superpowers)',
121
+ '## MANDATORY: Strict Gate Workflow'
122
+ ];
123
+ let minIndex = content.length;
124
+ for (const h of headers) {
125
+ const idx = content.indexOf(h);
126
+ if (idx !== -1 && idx < minIndex) minIndex = idx;
127
+ }
128
+ if (minIndex < content.length) {
129
+ content = content.slice(0, minIndex).trim();
130
+ cleaned = true;
131
+ }
132
+ }
133
+ }
134
+
135
+ if (cleaned) {
136
+ if (content.length === 0) {
137
+ await fs.remove(filePath);
138
+ console.log(chalk.green(`✓ Deleted ${AI_TOOL_FILES[tool]} (it was aiflow-only)`));
139
+ } else {
140
+ await fs.writeFile(filePath, content);
141
+ console.log(chalk.green(`✓ Cleaned up ai-flow-kit instructions from ${AI_TOOL_FILES[tool]}`));
142
+ }
143
+ }
144
+ }
145
+
92
146
  console.log(chalk.green('\n✨ ai-flow-kit removed from project.'));
93
147
  console.log(chalk.gray('To reinstall: aiflow init'));
94
148
  }
package/scripts/task.js CHANGED
@@ -22,6 +22,7 @@ module.exports = async function task(action, options = {}) {
22
22
  if (action === 'resume' && options.taskId) return await resumeTask(options.taskId);
23
23
  if (action === 'reset' && options.taskId) return await resetTask(options.taskId);
24
24
  if (action === 'remove' && options.taskId) return await removeTask(options.taskId);
25
+ if (action === 'next') return await nextGate(options.taskId);
25
26
 
26
27
  console.log(chalk.yellow('Usage:'));
27
28
  console.log(chalk.gray(' aiflow task status — show active task and pending list'));
@@ -32,6 +33,8 @@ module.exports = async function task(action, options = {}) {
32
33
  console.log(chalk.gray(' aiflow task resume <ticket-id> — resume a paused task'));
33
34
  console.log(chalk.gray(' aiflow task reset <ticket-id> — reset task to Gate 1 (keeps context)'));
34
35
  console.log(chalk.gray(' aiflow task remove <ticket-id> — permanently delete all task data'));
36
+ console.log(chalk.gray(' aiflow task next — approve current gate + prep next session'));
37
+ console.log(chalk.gray(' aiflow task next --ticket <id> — same, specify ticket explicitly'));
35
38
  } catch (err) {
36
39
  console.error(chalk.red(`Error: ${err.message}`));
37
40
  }
@@ -293,6 +296,65 @@ async function removeTask(taskId) {
293
296
  console.log(chalk.green(`✓ Task ${taskId} removed.`));
294
297
  }
295
298
 
299
+ async function nextGate(taskId) {
300
+ // Resolve ticket ID
301
+ let resolvedId = taskId;
302
+ if (!resolvedId) {
303
+ const ctx = await fs.readJson(CURRENT_FILE).catch(() => null);
304
+ resolvedId = ctx && ctx.taskId ? ctx.taskId : null;
305
+ }
306
+ if (!resolvedId) {
307
+ console.log(chalk.yellow('No ticket ID provided and no active task found.'));
308
+ console.log(chalk.gray(' Usage: aiflow task next --ticket PROJ-123'));
309
+ return;
310
+ }
311
+
312
+ const taskDir = path.join(TASKS_DIR, resolvedId);
313
+ const statePath = path.join(taskDir, 'task-state.json');
314
+ await fs.ensureDir(taskDir);
315
+
316
+ // Load or create task state
317
+ const existing = (await fs.pathExists(statePath)) ? await fs.readJson(statePath).catch(() => ({})) : {};
318
+ const currentGate = existing.currentGate || await detectCurrentGate(resolvedId);
319
+ const now = new Date().toISOString();
320
+
321
+ // Record gate approval
322
+ const gateApprovals = existing.gateApprovals || {};
323
+ gateApprovals[String(currentGate)] = now;
324
+ const nextGateNum = currentGate + 1;
325
+
326
+ // Save context snapshot if current task is active
327
+ if (await fs.pathExists(CURRENT_FILE)) {
328
+ const ctx = await fs.readJson(CURRENT_FILE).catch(() => null);
329
+ if (ctx && ctx.taskId === resolvedId) {
330
+ await fs.writeJson(path.join(taskDir, 'context.json'), ctx, { spaces: 2 });
331
+ }
332
+ }
333
+
334
+ const taskState = {
335
+ ...existing,
336
+ taskId: resolvedId,
337
+ status: 'pending',
338
+ updatedAt: now,
339
+ pausedAt: now,
340
+ currentGate: nextGateNum,
341
+ gateApprovals,
342
+ };
343
+ await fs.writeJson(statePath, taskState, { spaces: 2 });
344
+
345
+ // Clear current context so no task is "active"
346
+ await fs.remove(CURRENT_FILE);
347
+
348
+ const gateLabels = { 1: 'Analyze', 2: 'Plan', 3: 'Code', 4: 'Review', 5: 'PR' };
349
+ console.log(chalk.green(`✓ Gate ${currentGate} approved for ${resolvedId}.`));
350
+ console.log(chalk.white(`\n Next: Gate ${nextGateNum} — ${gateLabels[nextGateNum] || 'Done'}`));
351
+ console.log(chalk.cyan('\n To start Gate ' + nextGateNum + ' in a fresh session:'));
352
+ console.log(chalk.gray(' 1. Open a new terminal or chat session'));
353
+ console.log(chalk.gray(` 2. Run: aiflow task resume ${resolvedId}`));
354
+ console.log(chalk.gray(` 3. Claude/AI will automatically start Gate ${nextGateNum} with clean context`));
355
+ console.log();
356
+ }
357
+
296
358
  // ──────────────────────────────────────────────────────────────
297
359
  // Internal helpers
298
360
  // ──────────────────────────────────────────────────────────────
package/scripts/update.js CHANGED
@@ -29,12 +29,11 @@ module.exports = async function update(options = {}) {
29
29
  const currentVersion = state.current_version;
30
30
 
31
31
  if (currentVersion === PKG_VERSION && !options.force) {
32
- console.log(chalk.green(`Already on v${PKG_VERSION}. Use --force to re-copy assets anyway.`));
33
- return;
32
+ console.log(chalk.cyan(`You are already on v${PKG_VERSION}. Syncing latest assets and instruction files...`));
33
+ } else {
34
+ console.log(chalk.blue(`Updating from v${currentVersion} to v${PKG_VERSION}...`));
34
35
  }
35
36
 
36
- console.log(chalk.blue(`Updating from v${currentVersion} to v${PKG_VERSION}...`));
37
-
38
37
  const newVersionDir = path.join(aiflowDir, 'versions', PKG_VERSION);
39
38
  await fs.ensureDir(newVersionDir);
40
39
 
@@ -107,5 +106,16 @@ module.exports = async function update(options = {}) {
107
106
  state.current_version = PKG_VERSION;
108
107
  await fs.writeJson(stateFile, state);
109
108
 
109
+ // Sync instruction files with the updated skills
110
+ const { setupFramework, AI_TOOL_FILES, ensureAiflowGitignored } = require('./init');
111
+ const frameworks = state.frameworks || [];
112
+ const selectedTools = state.aiTools || Object.keys(AI_TOOL_FILES);
113
+ for (const fw of frameworks) {
114
+ await setupFramework(projectDir, fw, frameworks.length > 1, selectedTools);
115
+ }
116
+
117
+ // Ensure all aiflow files are gitignored (especially for projects upgrading from older versions)
118
+ await ensureAiflowGitignored(projectDir);
119
+
110
120
  console.log(chalk.green(`\n✨ Update completed! Your project is now on v${PKG_VERSION}.`));
111
121
  };
package/scripts/use.js CHANGED
@@ -20,6 +20,9 @@ module.exports = async function use(target, options = {}) {
20
20
  return;
21
21
  }
22
22
 
23
+ // ── GitNexus index notification (all AI tools see this) ──────
24
+ await checkGitNexusIndexStatus();
25
+
23
26
  // ── Manual entry ──
24
27
  if (options.manual) {
25
28
  return await manualContext();
@@ -692,6 +695,44 @@ function printContextSummary(context) {
692
695
  console.log();
693
696
  }
694
697
 
698
+ // ── GitNexus index status notification ───────────────────────────
699
+ // Called at the top of use() so ALL AI tools (Claude/Gemini/Cursor/Copilot)
700
+ // see the notification when developer runs `aiflow use TICKET`.
701
+ const GITNEXUS_STALE_MS = 3 * 60 * 60 * 1000; // 3 hours — if still "running" after this, process died
702
+
703
+ async function checkGitNexusIndexStatus() {
704
+ const statusPath = path.join(AIFLOW_DIR, 'gitnexus-status.json');
705
+ try {
706
+ if (!(await fs.pathExists(statusPath))) return;
707
+ const gn = await fs.readJson(statusPath);
708
+ if (gn.notified) return;
709
+
710
+ // Detect stale "running" — process was killed without updating status
711
+ if (gn.status === 'running' && gn.startedAt) {
712
+ const age = Date.now() - new Date(gn.startedAt).getTime();
713
+ if (age > GITNEXUS_STALE_MS) {
714
+ gn.status = 'error';
715
+ gn.error = 'timed out — process may have been killed or ran out of memory';
716
+ gn.notified = false;
717
+ await fs.writeJson(statusPath, gn, { spaces: 2 });
718
+ }
719
+ }
720
+
721
+ if (gn.status === 'done') {
722
+ console.log(chalk.green('✓ GitNexus indexing complete — code intelligence tools are ready.'));
723
+ gn.notified = true;
724
+ await fs.writeJson(statusPath, gn, { spaces: 2 });
725
+ } else if (gn.status === 'error') {
726
+ const detail = gn.error ? ` (${gn.error})` : gn.exitCode ? ` (exit ${gn.exitCode})` : '';
727
+ console.log(chalk.red(`✗ GitNexus indexing failed${detail}.`));
728
+ // console.log(chalk.gray(' Retry: aiflow init --with-gitnexus'));
729
+ gn.notified = true;
730
+ await fs.writeJson(statusPath, gn, { spaces: 2 });
731
+ }
732
+ // status === 'running' and not yet stale: still in progress, stay silent
733
+ } catch (_) {}
734
+ }
735
+
695
736
  async function suggestNextStep() {
696
737
  let aiTools = ['claude']; // Default
697
738
  try {
@@ -2,8 +2,8 @@
2
2
  "name": "superpowers-dev",
3
3
  "description": "Development marketplace for Superpowers core skills library",
4
4
  "owner": {
5
- "name": "Jesse Vincent",
6
- "email": "jesse@fsck.com"
5
+ "name": "Example Team",
6
+ "email": "dev@example.com"
7
7
  },
8
8
  "plugins": [
9
9
  {
@@ -12,8 +12,8 @@
12
12
  "version": "5.0.7",
13
13
  "source": "./",
14
14
  "author": {
15
- "name": "Jesse Vincent",
16
- "email": "jesse@fsck.com"
15
+ "name": "Example Developer",
16
+ "email": "dev@example.com"
17
17
  }
18
18
  }
19
19
  ]
@@ -3,11 +3,9 @@
3
3
  "description": "Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques",
4
4
  "version": "5.0.7",
5
5
  "author": {
6
- "name": "Jesse Vincent",
7
- "email": "jesse@fsck.com"
6
+ "name": "Example Developer",
7
+ "email": "dev@example.com"
8
8
  },
9
- "homepage": "https://github.com/obra/superpowers",
10
- "repository": "https://github.com/obra/superpowers",
11
9
  "license": "MIT",
12
10
  "keywords": [
13
11
  "skills",