buildflow-dev 1.0.2 → 1.0.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buildflow-dev",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Adaptive AI-powered development orchestration. Works with Claude Code, Gemini CLI, Codex CLI, Cursor, and more.",
5
5
  "keywords": [
6
6
  "ai",
@@ -26,6 +26,9 @@ const TOOLS = {
26
26
  return hasCli || hasClaudeDir
27
27
  },
28
28
 
29
+ isInstalledLocal() { return existsSync(join(process.cwd(), '.claude', 'commands', 'buildflow-start.md')) },
30
+ isInstalledGlobal() { return existsSync(join(homedir(), '.claude', 'commands', 'buildflow-start.md')) },
31
+
29
32
  installGlobal(commandFiles) {
30
33
  const dir = join(homedir(), '.claude', 'commands')
31
34
  mkdirSync(dir, { recursive: true })
@@ -56,17 +59,29 @@ const TOOLS = {
56
59
  docsUrl: 'https://github.com/google-gemini/gemini-cli',
57
60
 
58
61
  detect() {
59
- try { which.sync('gemini'); return true } catch { return false }
62
+ const hasCli = (() => { try { which.sync('gemini'); return true } catch { return false } })()
63
+ // Also check config dir — binary may not be on PATH in all shells
64
+ const hasGeminiDir = existsSync(join(homedir(), '.gemini'))
65
+ return hasCli || hasGeminiDir
66
+ },
67
+
68
+ isInstalledLocal() {
69
+ const cmdFile = existsSync(join(process.cwd(), '.gemini', 'commands', 'start.md'))
70
+ const ctxFile = existsSync(join(process.cwd(), 'GEMINI.md')) &&
71
+ readFileSafe(join(process.cwd(), 'GEMINI.md')).includes('BuildFlow')
72
+ return cmdFile || ctxFile
73
+ },
74
+ isInstalledGlobal() {
75
+ const cmdFile = existsSync(join(homedir(), '.gemini', 'commands', 'start.md'))
76
+ const ctxFile = existsSync(join(homedir(), '.gemini', 'GEMINI.md')) &&
77
+ readFileSafe(join(homedir(), '.gemini', 'GEMINI.md')).includes('BuildFlow')
78
+ return cmdFile || ctxFile
60
79
  },
61
80
 
62
81
  installGlobal(commandFiles) {
63
82
  const dir = join(homedir(), '.gemini', 'commands')
64
83
  mkdirSync(dir, { recursive: true })
65
- const contextPath = join(homedir(), '.gemini', 'GEMINI.md')
66
- const existingContent = existsSync(contextPath) ? readFileSync(contextPath, 'utf8') : ''
67
- if (!existingContent.includes('## BuildFlow Commands')) {
68
- writeFileSync(contextPath, existingContent + '\n\n' + geminiContextBlock(commandFiles))
69
- }
84
+ patchGeminiContext(join(homedir(), '.gemini', 'GEMINI.md'), commandFiles)
70
85
  for (const [name, content] of Object.entries(commandFiles)) {
71
86
  writeFileSync(join(dir, `${name}.md`), content)
72
87
  }
@@ -76,18 +91,14 @@ const TOOLS = {
76
91
  installLocal(commandFiles) {
77
92
  const dir = join(process.cwd(), '.gemini', 'commands')
78
93
  mkdirSync(dir, { recursive: true })
79
- const contextPath = join(process.cwd(), 'GEMINI.md')
80
- const existingContent = existsSync(contextPath) ? readFileSync(contextPath, 'utf8') : ''
81
- if (!existingContent.includes('## BuildFlow Commands')) {
82
- writeFileSync(contextPath, existingContent + '\n\n' + geminiContextBlock(commandFiles))
83
- }
94
+ patchGeminiContext(join(process.cwd(), 'GEMINI.md'), commandFiles)
84
95
  for (const [name, content] of Object.entries(commandFiles)) {
85
96
  writeFileSync(join(dir, `${name}.md`), content)
86
97
  }
87
98
  return dir
88
99
  },
89
100
 
90
- triggerNote: 'In Gemini CLI, type "/" or @buildflow to use commands',
101
+ triggerNote: 'In Gemini CLI, type "/buildflow-start" or ask Gemini to run a buildflow command',
91
102
  },
92
103
 
93
104
  codex: {
@@ -98,35 +109,40 @@ const TOOLS = {
98
109
  docsUrl: 'https://github.com/openai/codex',
99
110
 
100
111
  detect() {
101
- try { which.sync('codex'); return true } catch { return false }
112
+ const hasCli = (() => { try { which.sync('codex'); return true } catch { return false } })()
113
+ const hasCodexDir = existsSync(join(homedir(), '.codex'))
114
+ return hasCli || hasCodexDir
115
+ },
116
+
117
+ isInstalledLocal() { return existsSync(join(process.cwd(), '.codex', 'instructions', 'buildflow-start.md')) },
118
+ isInstalledGlobal() { return existsSync(join(homedir(), '.codex', 'instructions', 'buildflow-start.md')) },
119
+
120
+ installGlobal(commandFiles) {
121
+ const dir = join(homedir(), '.codex', 'instructions')
122
+ const skillsDir = join(homedir(), '.codex', 'skills')
123
+ mkdirSync(dir, { recursive: true })
124
+ for (const [name, content] of Object.entries(commandFiles)) {
125
+ writeFileSync(join(dir, `buildflow-${name}.md`), content)
126
+ writeCodexSkill(skillsDir, name, content)
127
+ }
128
+ patchAgentsMd(join(homedir(), '.codex', 'AGENTS.md'), 'global')
129
+ return `${dir} + ${skillsDir}`
130
+ },
131
+
132
+ installLocal(commandFiles) {
133
+ const dir = join(process.cwd(), '.codex', 'instructions')
134
+ const skillsDir = join(process.cwd(), '.codex', 'skills')
135
+ mkdirSync(dir, { recursive: true })
136
+ for (const [name, content] of Object.entries(commandFiles)) {
137
+ writeFileSync(join(dir, `buildflow-${name}.md`), content)
138
+ writeCodexSkill(skillsDir, name, content)
139
+ }
140
+ patchAgentsMd(join(process.cwd(), 'AGENTS.md'), 'local')
141
+ return `${dir} + ${skillsDir}`
102
142
  },
103
143
 
104
- installGlobal(commandFiles) {
105
- const dir = join(homedir(), '.codex', 'instructions')
106
- const skillsDir = join(homedir(), '.codex', 'skills')
107
- mkdirSync(dir, { recursive: true })
108
- for (const [name, content] of Object.entries(commandFiles)) {
109
- writeFileSync(join(dir, `buildflow-${name}.md`), content)
110
- writeCodexSkill(skillsDir, name, content)
111
- }
112
- patchAgentsMd(join(homedir(), '.codex', 'AGENTS.md'), 'global')
113
- return `${dir} + ${skillsDir}`
114
- },
115
-
116
- installLocal(commandFiles) {
117
- const dir = join(process.cwd(), '.codex', 'instructions')
118
- const skillsDir = join(process.cwd(), '.codex', 'skills')
119
- mkdirSync(dir, { recursive: true })
120
- for (const [name, content] of Object.entries(commandFiles)) {
121
- writeFileSync(join(dir, `buildflow-${name}.md`), content)
122
- writeCodexSkill(skillsDir, name, content)
123
- }
124
- patchAgentsMd(join(process.cwd(), 'AGENTS.md'), 'local')
125
- return `${dir} + ${skillsDir}`
126
- },
127
-
128
- triggerNote: 'In Codex CLI, use $buildflow-start or say "use buildflow-start". Slash menu commands are not exposed by Codex.',
129
- },
144
+ triggerNote: 'In Codex CLI, use $buildflow-start or say "use buildflow-start". Slash menu commands are not exposed by Codex.',
145
+ },
130
146
 
131
147
  cursor: {
132
148
  id: 'cursor',
@@ -143,6 +159,9 @@ const TOOLS = {
143
159
  return hasCursorApp || hasCursorDir
144
160
  },
145
161
 
162
+ isInstalledLocal() { return existsSync(join(process.cwd(), '.cursor', 'rules', 'buildflow.mdc')) },
163
+ isInstalledGlobal() { return this.isInstalledLocal() },
164
+
146
165
  installGlobal(commandFiles) {
147
166
  return this.installLocal(commandFiles)
148
167
  },
@@ -175,6 +194,11 @@ const TOOLS = {
175
194
  )
176
195
  },
177
196
 
197
+ isInstalledLocal() {
198
+ return readFileSafe(join(process.cwd(), '.clinerules')).includes('BuildFlow')
199
+ },
200
+ isInstalledGlobal() { return this.isInstalledLocal() },
201
+
178
202
  installGlobal(commandFiles) {
179
203
  return this.installLocal(commandFiles)
180
204
  },
@@ -198,6 +222,9 @@ const TOOLS = {
198
222
  return existsSync(join(homedir(), '.continue', 'config.json'))
199
223
  },
200
224
 
225
+ isInstalledLocal() { return existsSync(join(process.cwd(), '.continue', 'buildflow', 'start.md')) },
226
+ isInstalledGlobal() { return existsSync(join(homedir(), '.continue', 'buildflow', 'start.md')) },
227
+
201
228
  installGlobal(commandFiles) {
202
229
  const dir = join(homedir(), '.continue', 'buildflow')
203
230
  mkdirSync(dir, { recursive: true })
@@ -222,41 +249,64 @@ const TOOLS = {
222
249
 
223
250
  }
224
251
 
252
+ const UPDATE_CHECK_INSTRUCTION = `
253
+ ## BuildFlow Update Check (Run Every Session)
254
+
255
+ At the very start of every session, before anything else:
256
+ 1. Check if \`.buildflow/core/UPDATE.md\` exists
257
+ 2. If it does, show the user this notice:
258
+ > "BuildFlow update available — run \`npx buildflow-dev update\` in your terminal to upgrade."
259
+ Then show the contents of UPDATE.md so the user sees the version details.
260
+ 3. If the file does not exist, proceed silently.
261
+ `
262
+
225
263
  function geminiContextBlock(commandFiles) {
226
264
  const commandList = Object.keys(commandFiles)
227
265
  .map(name => `- \`/buildflow-${name}\`: see .gemini/commands/${name}.md`)
228
266
  .join('\n')
229
- return `## BuildFlow Commands\n\nWhen the user types a /buildflow-* command, load and execute the corresponding file from .gemini/commands/.\n\n${commandList}`
267
+ return `## BuildFlow Commands
268
+
269
+ When the user types a /buildflow-* command, load and execute the corresponding file from .gemini/commands/.
270
+
271
+ ${commandList}
272
+ ${UPDATE_CHECK_INSTRUCTION}`
230
273
  }
231
274
 
232
- function patchAgentsMd(filePath, scope) {
233
- const existing = existsSync(filePath) ? readFileSync(filePath, 'utf8') : ''
234
- if (existing.includes('BuildFlow')) return
235
- const dir = scope === 'global' ? '~/.codex/instructions/' : '.codex/instructions/'
236
- const block = `\n\n## BuildFlow Instructions\n\nWhen the user types $buildflow-<command> or /buildflow-<command>, load the matching file from ${dir} and follow those instructions.\n\nAvailable commands: start, think, plan, build, check, ship, onboard, modify, refactor, audit, status, explain, back, help\n`
237
- writeFileSync(filePath, existing + block)
238
- }
239
-
240
- function writeCodexSkill(skillsDir, name, commandContent) {
241
- const skillName = `buildflow-${name}`
242
- const skillDir = join(skillsDir, skillName)
243
- mkdirSync(skillDir, { recursive: true })
244
- writeFileSync(join(skillDir, 'SKILL.md'), codexSkillContent(skillName, commandContent))
245
- }
246
-
247
- function codexSkillContent(skillName, commandContent) {
248
- const description = extractFrontmatterValue(commandContent, 'description') || `Run ${skillName}`
249
- return `---\nname: "${skillName}"\ndescription: "${escapeYamlString(description)}"\nmetadata:\n short-description: "${escapeYamlString(description)}"\n---\n\n<objective>\nExecute the BuildFlow workflow below end-to-end.\nTreat any user text after $${skillName} as arguments for this workflow.\n</objective>\n\n<workflow>\n${commandContent}\n</workflow>\n`
250
- }
251
-
252
- function extractFrontmatterValue(content, key) {
253
- const match = content.match(new RegExp(`^${key}:\\s*(.+)$`, 'm'))
254
- return match?.[1]?.trim().replace(/^["']|["']$/g, '')
255
- }
256
-
257
- function escapeYamlString(value) {
258
- return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
259
- }
275
+ function patchAgentsMd(filePath, scope) {
276
+ const existing = readFileSafe(filePath)
277
+ const dir = scope === 'global' ? '~/.codex/instructions/' : '.codex/instructions/'
278
+ const block = `## BuildFlow Instructions\n\nWhen the user types $buildflow-<command> or /buildflow-<command>, load the matching file from ${dir} and follow those instructions.\n\nAvailable commands: start, think, plan, build, check, ship, onboard, modify, refactor, audit, status, explain, back, help\n${UPDATE_CHECK_INSTRUCTION}`
279
+ if (existing.includes('## BuildFlow Instructions')) {
280
+ const updated = existing.replace(
281
+ /## BuildFlow Instructions[\s\S]*?(?=\n## |\n# |$)/,
282
+ block
283
+ )
284
+ writeFileSync(filePath, updated)
285
+ } else {
286
+ writeFileSync(filePath, existing + (existing ? '\n\n' : '') + block)
287
+ }
288
+ }
289
+
290
+ function writeCodexSkill(skillsDir, name, commandContent) {
291
+ const skillName = `buildflow-${name}`
292
+ const skillDir = join(skillsDir, skillName)
293
+ mkdirSync(skillDir, { recursive: true })
294
+ writeFileSync(join(skillDir, 'SKILL.md'), codexSkillContent(skillName, commandContent))
295
+ }
296
+
297
+ function codexSkillContent(skillName, commandContent) {
298
+ const description = extractFrontmatterValue(commandContent, 'description') || `Run ${skillName}`
299
+ return `---\nname: "${skillName}"\ndescription: "${escapeYamlString(description)}"\nmetadata:\n short-description: "${escapeYamlString(description)}"\n---\n\n<objective>\nExecute the BuildFlow workflow below end-to-end.\nTreat any user text after $${skillName} as arguments for this workflow.\n</objective>\n\n<workflow>\n${commandContent}\n</workflow>\n`
300
+ }
301
+
302
+ function extractFrontmatterValue(content, key) {
303
+ const match = content.match(new RegExp(`^${key}:\\s*(.+)$`, 'm'))
304
+ return match?.[1]?.trim().replace(/^["']|["']$/g, '')
305
+ }
306
+
307
+ function escapeYamlString(value) {
308
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
309
+ }
260
310
 
261
311
  function cursorRulesContent(commandFiles) {
262
312
  const commandDescriptions = Object.keys(commandFiles)
@@ -271,7 +321,7 @@ alwaysApply: false
271
321
  # BuildFlow v3.0
272
322
 
273
323
  You are integrated with BuildFlow, an adaptive development orchestration system.
274
-
324
+ ${UPDATE_CHECK_INSTRUCTION}
275
325
  ## Available Commands
276
326
 
277
327
  When the user types @buildflow-<command> or references a buildflow command, execute the corresponding workflow:
@@ -308,7 +358,7 @@ function clineRulesContent(commandFiles) {
308
358
  .map(name => `- /buildflow-${name}`)
309
359
  .join('\n')
310
360
  return `# BuildFlow v3.0 Rules for Cline
311
-
361
+ ${UPDATE_CHECK_INSTRUCTION}
312
362
  ## Slash Commands
313
363
 
314
364
  When the user types any of the following commands, load the corresponding instruction file from .buildflow/commands/:
@@ -371,6 +421,27 @@ function readdirSafe(dir) {
371
421
  }
372
422
  }
373
423
 
424
+ function readFileSafe(filePath) {
425
+ try { return readFileSync(filePath, 'utf8') } catch { return '' }
426
+ }
427
+
428
+ // Writes or replaces the BuildFlow block in a GEMINI.md context file.
429
+ // Always overwrites the block so updates pick up new commands.
430
+ function patchGeminiContext(contextPath, commandFiles) {
431
+ const existing = readFileSafe(contextPath)
432
+ const block = geminiContextBlock(commandFiles)
433
+ if (existing.includes('## BuildFlow Commands')) {
434
+ // Replace old block — from the marker to the next top-level ## or end of file
435
+ const updated = existing.replace(
436
+ /## BuildFlow Commands[\s\S]*?(?=\n## |\n# |$)/,
437
+ block.trimStart()
438
+ )
439
+ writeFileSync(contextPath, updated)
440
+ } else {
441
+ writeFileSync(contextPath, existing + (existing ? '\n\n' : '') + block)
442
+ }
443
+ }
444
+
374
445
  function detectAppName() {
375
446
  const pkgPath = join(process.cwd(), 'package.json')
376
447
  if (existsSync(pkgPath)) {
@@ -384,6 +455,55 @@ function detectAppName() {
384
455
  return process.cwd().split(/[/\\]/).pop() || 'my-project'
385
456
  }
386
457
 
458
+ export async function refreshInstalledTools(opts = {}) {
459
+ const commandFiles = loadCommandTemplates()
460
+ const commandCount = Object.keys(commandFiles).length
461
+ const results = []
462
+
463
+ for (const tool of Object.values(TOOLS)) {
464
+ const local = tool.isInstalledLocal()
465
+ const global = tool.isInstalledGlobal()
466
+ if (!local && !global) continue
467
+
468
+ const sp = ora(` ${tool.icon} Refreshing ${tool.name}...`).start()
469
+ try {
470
+ if (local) tool.installLocal(commandFiles)
471
+ if (global && !local) tool.installGlobal(commandFiles)
472
+ sp.succeed(chalk.green(` ${tool.icon} ${tool.name}`) + chalk.dim(` — ${commandCount} commands refreshed`))
473
+ results.push({ tool, success: true })
474
+ } catch (err) {
475
+ sp.fail(chalk.red(` ${tool.icon} ${tool.name} — ${err.message}`))
476
+ results.push({ tool, success: false, error: err })
477
+ }
478
+ }
479
+
480
+ if (results.length === 0) {
481
+ console.log(chalk.yellow(' No previously installed tools found.'))
482
+ console.log(chalk.dim(' Run: buildflow install\n'))
483
+ } else {
484
+ const failed = results.filter(r => !r.success)
485
+ if (failed.length > 0) {
486
+ console.log(chalk.red('\n Some tools failed to update:'))
487
+ for (const { tool, error } of failed) {
488
+ console.log(chalk.red(` ✗ ${tool.name}: ${error.message}`))
489
+ }
490
+ }
491
+ }
492
+
493
+ return results
494
+ }
495
+
496
+ export function getToolStatus() {
497
+ return Object.values(TOOLS).map(tool => ({
498
+ id: tool.id,
499
+ name: tool.name,
500
+ icon: tool.icon,
501
+ detected: tool.detect(),
502
+ installedLocal: tool.isInstalledLocal(),
503
+ installedGlobal: tool.isInstalledGlobal(),
504
+ }))
505
+ }
506
+
387
507
  function loadCommandTemplates() {
388
508
  const templatesDir = join(__dirname, '../../templates/commands')
389
509
  const commands = {}
@@ -418,17 +538,30 @@ export async function run(opts = {}) {
418
538
  const detectedList = Object.entries(detected).filter(([, found]) => found)
419
539
  const notFoundList = Object.entries(detected).filter(([, found]) => !found)
420
540
 
541
+ // Tools detected on the system but BuildFlow not yet installed into them
542
+ const newlyDetected = detectedList.filter(([id]) => {
543
+ const t = TOOLS[id]
544
+ return !t.isInstalledLocal() && !t.isInstalledGlobal()
545
+ })
546
+
421
547
  if (detectedList.length > 0) {
422
548
  console.log(chalk.green(' ✓ Detected on your system:'))
423
549
  for (const [id] of detectedList) {
424
550
  const t = TOOLS[id]
425
- console.log(chalk.green(` ${t.icon} ${t.name}`) + chalk.dim(` ${t.description}`))
551
+ const installed = t.isInstalledLocal() || t.isInstalledGlobal()
552
+ const badge = installed ? chalk.dim(' (already installed)') : chalk.yellow(' (not yet installed)')
553
+ console.log(chalk.green(` ${t.icon} ${t.name}`) + badge)
426
554
  }
427
555
  } else {
428
556
  console.log(chalk.yellow(' ⚠ No AI tools detected automatically.'))
429
557
  console.log(chalk.dim(' You can still install for tools not on this list.\n'))
430
558
  }
431
559
 
560
+ if (newlyDetected.length > 0) {
561
+ console.log(chalk.yellow(`\n ${newlyDetected.length} tool(s) detected but BuildFlow not installed yet.`))
562
+ console.log(chalk.dim(' They are pre-selected below.\n'))
563
+ }
564
+
432
565
  if (notFoundList.length > 0) {
433
566
  console.log(chalk.dim('\n Not detected (can still install):'))
434
567
  for (const [id] of notFoundList) {
@@ -453,11 +586,19 @@ export async function run(opts = {}) {
453
586
  return
454
587
  }
455
588
  } else {
456
- const choices = Object.entries(TOOLS).map(([id, tool]) => ({
457
- name: id,
458
- message: `${tool.icon} ${tool.name}${detected[id] ? chalk.green(' ✓') : ''}`,
459
- hint: tool.description,
460
- }))
589
+ const choices = Object.entries(TOOLS).map(([id, tool]) => {
590
+ const isInstalled = tool.isInstalledLocal() || tool.isInstalledGlobal()
591
+ const statusBadge = !detected[id]
592
+ ? ''
593
+ : isInstalled
594
+ ? chalk.dim(' (reinstall)')
595
+ : chalk.yellow(' ← new')
596
+ return {
597
+ name: id,
598
+ message: `${tool.icon} ${tool.name}${statusBadge}`,
599
+ hint: tool.description,
600
+ }
601
+ })
461
602
 
462
603
  const { tools } = await prompt({
463
604
  type: 'multiselect',
@@ -465,7 +606,10 @@ export async function run(opts = {}) {
465
606
  message: 'Which AI tools do you want to install BuildFlow into?',
466
607
  hint: '(Space to select, Enter to confirm)',
467
608
  choices,
468
- initial: detectedList.map(([id]) => id),
609
+ // Pre-select newly detected tools (detected but not yet installed)
610
+ initial: newlyDetected.length > 0
611
+ ? newlyDetected.map(([id]) => id)
612
+ : detectedList.map(([id]) => id),
469
613
  })
470
614
 
471
615
  if (!tools || tools.length === 0) {
@@ -561,18 +705,18 @@ export async function run(opts = {}) {
561
705
  }
562
706
  }
563
707
 
564
- if (succeeded.length > 0) {
565
- console.log(chalk.bold('\n Next steps:\n'))
566
- console.log(chalk.white(' 1. Open your AI tool in this project'))
567
- if (succeeded.every(({ tool }) => tool.id === 'codex')) {
568
- console.log(chalk.white(' 2. Restart Codex CLI so new skills are loaded'))
569
- console.log(chalk.white(' 3. Start with: ') + chalk.cyan('$buildflow-start'))
570
- console.log(chalk.white(' Or for existing projects: ') + chalk.cyan('$buildflow-onboard'))
571
- } else {
572
- console.log(chalk.white(' 2. Type "/" to see BuildFlow commands'))
573
- console.log(chalk.white(' 3. Start with: ') + chalk.cyan('/buildflow-start'))
574
- console.log(chalk.white(' Or for existing projects: ') + chalk.cyan('/buildflow-onboard'))
575
- }
576
- console.log('')
577
- }
578
- }
708
+ if (succeeded.length > 0) {
709
+ console.log(chalk.bold('\n Next steps:\n'))
710
+ console.log(chalk.white(' 1. Open your AI tool in this project'))
711
+ if (succeeded.every(({ tool }) => tool.id === 'codex')) {
712
+ console.log(chalk.white(' 2. Restart Codex CLI so new skills are loaded'))
713
+ console.log(chalk.white(' 3. Start with: ') + chalk.cyan('$buildflow-start'))
714
+ console.log(chalk.white(' Or for existing projects: ') + chalk.cyan('$buildflow-onboard'))
715
+ } else {
716
+ console.log(chalk.white(' 2. Type "/" to see BuildFlow commands'))
717
+ console.log(chalk.white(' 3. Start with: ') + chalk.cyan('/buildflow-start'))
718
+ console.log(chalk.white(' Or for existing projects: ') + chalk.cyan('/buildflow-onboard'))
719
+ }
720
+ console.log('')
721
+ }
722
+ }
@@ -1,6 +1,7 @@
1
1
  import chalk from 'chalk'
2
2
  import { existsSync, readFileSync, readdirSync, statSync } from 'fs'
3
3
  import { join } from 'path'
4
+ import { getToolStatus } from './install.js'
4
5
 
5
6
  export async function run(opts = {}) {
6
7
  const base = join(process.cwd(), '.buildflow')
@@ -35,10 +36,44 @@ export async function run(opts = {}) {
35
36
  onboarded === 'n/a' ? chalk.dim('N/A (greenfield)') :
36
37
  chalk.yellow('No — run /buildflow-onboard')))
37
38
 
38
- const hasDebt = debt.includes('## Active') && !debt.includes('[None]')
39
+ const hasDebt = debt.includes('## Active') && !debt.includes('*None')
39
40
  console.log(chalk.dim(' Security debt: ') +
40
41
  (hasDebt ? chalk.red('⚠ Issues pending — see .buildflow/security/DEBT.md') : chalk.green('Clean')))
41
42
 
43
+ // ── AI Tool Status ──────────────────────────────────────────────────────────
44
+ console.log(chalk.dim('\n AI Tools:\n'))
45
+
46
+ const tools = getToolStatus()
47
+ const uninstalled = []
48
+
49
+ for (const t of tools) {
50
+ if (!t.detected) {
51
+ console.log(chalk.dim(` ${t.icon} ${t.name}`) + chalk.dim(' — not installed on this machine'))
52
+ continue
53
+ }
54
+
55
+ const local = t.installedLocal
56
+ const global = t.installedGlobal
57
+
58
+ if (local || global) {
59
+ const scope = local && global ? 'local + global' : local ? 'local' : 'global'
60
+ console.log(chalk.green(` ${t.icon} ${t.name}`) + chalk.dim(` ✓ BuildFlow installed (${scope})`))
61
+ } else {
62
+ console.log(chalk.yellow(` ${t.icon} ${t.name}`) + chalk.yellow(' ⚠ Detected but BuildFlow not installed'))
63
+ uninstalled.push(t)
64
+ }
65
+ }
66
+
67
+ if (uninstalled.length > 0) {
68
+ console.log('')
69
+ console.log(chalk.yellow(' Some tools are missing BuildFlow commands.'))
70
+ console.log(chalk.dim(' Run one of:'))
71
+ for (const t of uninstalled) {
72
+ console.log(chalk.white(` buildflow install --tool ${t.id}`))
73
+ }
74
+ console.log(chalk.dim(' Or: buildflow install (interactive)'))
75
+ }
76
+
42
77
  if (opts.verbose) {
43
78
  console.log(chalk.dim('\n .buildflow/ structure:'))
44
79
  const walk = (dir, prefix = ' ') => {
@@ -3,7 +3,8 @@ import ora from 'ora'
3
3
  import { existsSync, readFileSync } from 'fs'
4
4
  import { join, dirname } from 'path'
5
5
  import { fileURLToPath } from 'url'
6
- import { run as runInstall } from './install.js'
6
+ import { refreshInstalledTools } from './install.js'
7
+ import { checkVersion, clearUpdateNotice } from '../utils/checkVersion.js'
7
8
 
8
9
  const __dirname = dirname(fileURLToPath(import.meta.url))
9
10
 
@@ -15,21 +16,30 @@ export async function run(opts = {}) {
15
16
  return
16
17
  }
17
18
 
19
+ const pkg = JSON.parse(readFileSync(join(__dirname, '../../package.json'), 'utf8'))
20
+
18
21
  if (opts.check) {
19
22
  console.log('\n' + chalk.bold.white(' BuildFlow Update Check\n'))
20
- const pkgPath = join(__dirname, '../../package.json')
21
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
22
- console.log(chalk.dim(` Local version: ${pkg.version}`))
23
- console.log(chalk.dim(' Run: npx buildflow-dev update to update commands\n'))
23
+ const sp = ora('Checking npm registry...').start()
24
+ const update = await checkVersion(pkg.version)
25
+ sp.stop()
26
+
27
+ if (update) {
28
+ console.log(chalk.yellow(` Update available: ${update.current} → ${update.latest}`))
29
+ console.log(chalk.white(' Run: npx buildflow-dev update\n'))
30
+ } else {
31
+ console.log(chalk.green(` ✓ Up to date (v${pkg.version})\n`))
32
+ }
24
33
  return
25
34
  }
26
35
 
27
36
  console.log('\n' + chalk.bold.white(' Updating BuildFlow...\n'))
28
37
 
29
- const sp = ora('Updating command files...').start()
30
- await new Promise(r => setTimeout(r, 400))
31
- sp.succeed(chalk.green(' ✓ Command files updated'))
38
+ // Re-push latest command templates to all previously installed tools
39
+ await refreshInstalledTools()
40
+
41
+ // Clear the update notice now that we've updated
42
+ clearUpdateNotice()
32
43
 
33
- console.log('')
34
- await runInstall({ yes: opts.yes })
44
+ console.log(chalk.green(`\n ✓ BuildFlow updated to v${pkg.version}\n`))
35
45
  }
@@ -0,0 +1,72 @@
1
+ import { existsSync, writeFileSync, unlinkSync, mkdirSync } from 'fs'
2
+ import { join } from 'path'
3
+
4
+ const REGISTRY_URL = 'https://registry.npmjs.org/buildflow-dev/latest'
5
+ const UPDATE_FILE = '.buildflow/core/UPDATE.md'
6
+
7
+ export async function checkVersion(currentVersion) {
8
+ try {
9
+ const res = await fetch(REGISTRY_URL, {
10
+ signal: AbortSignal.timeout(3000),
11
+ headers: { Accept: 'application/json' },
12
+ })
13
+ if (!res.ok) return null
14
+
15
+ const { version: latest } = await res.json()
16
+ const updatePath = join(process.cwd(), UPDATE_FILE)
17
+
18
+ if (latest === currentVersion) {
19
+ // Remove stale notice if we're already up to date
20
+ if (existsSync(updatePath)) unlinkSync(updatePath)
21
+ return null
22
+ }
23
+
24
+ // Only write the file if .buildflow/ exists (i.e. project is initialized)
25
+ const coreDir = join(process.cwd(), '.buildflow', 'core')
26
+ if (!existsSync(coreDir)) return { current: currentVersion, latest }
27
+
28
+ mkdirSync(coreDir, { recursive: true })
29
+ writeFileSync(updatePath, buildUpdateNotice(currentVersion, latest))
30
+
31
+ return { current: currentVersion, latest }
32
+ } catch {
33
+ return null // Network timeout or offline — silently skip
34
+ }
35
+ }
36
+
37
+ export function clearUpdateNotice() {
38
+ const updatePath = join(process.cwd(), UPDATE_FILE)
39
+ if (existsSync(updatePath)) unlinkSync(updatePath)
40
+ }
41
+
42
+ function buildUpdateNotice(current, latest) {
43
+ return `# BuildFlow Update Available
44
+
45
+ > A new version of BuildFlow is available.
46
+ > Your AI tool detected this file at session start.
47
+
48
+ | | Version |
49
+ |---|---|
50
+ | **Installed** | ${current} |
51
+ | **Latest** | ${latest} |
52
+
53
+ ## How to Update
54
+
55
+ Run this in your terminal:
56
+
57
+ \`\`\`bash
58
+ npx buildflow-dev update
59
+ \`\`\`
60
+
61
+ Or if installed globally:
62
+
63
+ \`\`\`bash
64
+ npm install -g buildflow-dev@latest
65
+ \`\`\`
66
+
67
+ After updating, this notice will disappear automatically.
68
+
69
+ ---
70
+ *Generated by BuildFlow CLI · ${new Date().toISOString().split('T')[0]}*
71
+ `
72
+ }
@@ -1,8 +1,21 @@
1
1
  import chalk from 'chalk'
2
2
  import { existsSync, readFileSync } from 'fs'
3
- import { join } from 'path'
3
+ import { join, dirname } from 'path'
4
+ import { fileURLToPath } from 'url'
5
+ import { checkVersion } from './checkVersion.js'
6
+ import { getToolStatus } from '../commands/install.js'
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url))
4
9
 
5
10
  export async function showWelcome() {
11
+ const pkg = JSON.parse(readFileSync(join(__dirname, '../../package.json'), 'utf8'))
12
+ // Fire-and-forget version check — writes UPDATE.md if behind, doesn't block output
13
+ checkVersion(pkg.version).then(update => {
14
+ if (update) {
15
+ console.log(chalk.yellow(`\n Update available: ${update.current} → ${update.latest}`))
16
+ console.log(chalk.dim(' Run: npx buildflow-dev update\n'))
17
+ }
18
+ }).catch(() => {})
6
19
  const isInitialized = existsSync(join(process.cwd(), '.buildflow'))
7
20
 
8
21
  console.log('\n' + chalk.bold.white(' BuildFlow v3.0'))
@@ -56,6 +69,16 @@ export async function showWelcome() {
56
69
  console.log(chalk.white(' buildflow status ') + chalk.dim(' Show project status'))
57
70
  console.log(chalk.white(' buildflow audit ') + chalk.dim(' Run security audit from terminal'))
58
71
  console.log(chalk.white(' buildflow update ') + chalk.dim(' Update BuildFlow commands'))
72
+
73
+ // Surface any newly installed AI tools that don't have BuildFlow yet
74
+ const uninstalled = getToolStatus().filter(t => t.detected && !t.installedLocal && !t.installedGlobal)
75
+ if (uninstalled.length > 0) {
76
+ console.log('')
77
+ console.log(chalk.yellow(` New AI tool(s) detected — BuildFlow not installed yet:`))
78
+ for (const t of uninstalled) {
79
+ console.log(chalk.yellow(` ${t.icon} ${t.name}`) + chalk.dim(` → buildflow install --tool ${t.id}`))
80
+ }
81
+ }
59
82
  } else {
60
83
  console.log(chalk.yellow(' Not initialized in this directory.\n'))
61
84
  console.log(chalk.bold(' Get started:\n'))
@@ -2,6 +2,20 @@
2
2
 
3
3
  This project uses **BuildFlow v3.0** for adaptive AI-powered development orchestration.
4
4
 
5
+ ## Session Start Checklist (Run Every Time)
6
+
7
+ Before doing anything else at the start of every session:
8
+
9
+ 1. **Check for updates** — if `.buildflow/core/UPDATE.md` exists, show the user this notice:
10
+ > "BuildFlow update available — run `npx buildflow-dev update` in your terminal."
11
+ Then display the contents of UPDATE.md so the user sees the version details.
12
+ If the file does not exist, proceed silently.
13
+
14
+ 2. **Load memory** — read `.buildflow/memory/light.md` for project context
15
+ 3. **Load state** — read `.buildflow/core/state.md` for current phase and status
16
+
17
+ ---
18
+
5
19
  ## Quick Start
6
20
 
7
21
  Type `/` in Claude Code to see available commands: