buildflow-dev 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +483 -157
- package/bin/buildflow.js +10 -0
- package/package.json +1 -1
- package/src/commands/fix.js +368 -0
- package/src/commands/init.js +390 -17
- package/src/commands/install.js +120 -17
- package/src/commands/status.js +36 -1
- package/src/commands/update.js +16 -4
- package/src/index.js +1 -0
- package/src/utils/checkVersion.js +72 -0
- package/src/utils/welcome.js +24 -1
- package/templates/CLAUDE.md +14 -0
package/src/commands/install.js
CHANGED
|
@@ -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 })
|
|
@@ -59,6 +62,9 @@ const TOOLS = {
|
|
|
59
62
|
try { which.sync('gemini'); return true } catch { return false }
|
|
60
63
|
},
|
|
61
64
|
|
|
65
|
+
isInstalledLocal() { return existsSync(join(process.cwd(), '.gemini', 'commands', 'start.md')) },
|
|
66
|
+
isInstalledGlobal() { return existsSync(join(homedir(), '.gemini', 'commands', 'start.md')) },
|
|
67
|
+
|
|
62
68
|
installGlobal(commandFiles) {
|
|
63
69
|
const dir = join(homedir(), '.gemini', 'commands')
|
|
64
70
|
mkdirSync(dir, { recursive: true })
|
|
@@ -101,27 +107,34 @@ const TOOLS = {
|
|
|
101
107
|
try { which.sync('codex'); return true } catch { return false }
|
|
102
108
|
},
|
|
103
109
|
|
|
110
|
+
isInstalledLocal() { return existsSync(join(process.cwd(), '.codex', 'instructions', 'buildflow-start.md')) },
|
|
111
|
+
isInstalledGlobal() { return existsSync(join(homedir(), '.codex', 'instructions', 'buildflow-start.md')) },
|
|
112
|
+
|
|
104
113
|
installGlobal(commandFiles) {
|
|
105
114
|
const dir = join(homedir(), '.codex', 'instructions')
|
|
115
|
+
const skillsDir = join(homedir(), '.codex', 'skills')
|
|
106
116
|
mkdirSync(dir, { recursive: true })
|
|
107
117
|
for (const [name, content] of Object.entries(commandFiles)) {
|
|
108
118
|
writeFileSync(join(dir, `buildflow-${name}.md`), content)
|
|
119
|
+
writeCodexSkill(skillsDir, name, content)
|
|
109
120
|
}
|
|
110
121
|
patchAgentsMd(join(homedir(), '.codex', 'AGENTS.md'), 'global')
|
|
111
|
-
return dir
|
|
122
|
+
return `${dir} + ${skillsDir}`
|
|
112
123
|
},
|
|
113
124
|
|
|
114
125
|
installLocal(commandFiles) {
|
|
115
126
|
const dir = join(process.cwd(), '.codex', 'instructions')
|
|
127
|
+
const skillsDir = join(process.cwd(), '.codex', 'skills')
|
|
116
128
|
mkdirSync(dir, { recursive: true })
|
|
117
129
|
for (const [name, content] of Object.entries(commandFiles)) {
|
|
118
130
|
writeFileSync(join(dir, `buildflow-${name}.md`), content)
|
|
131
|
+
writeCodexSkill(skillsDir, name, content)
|
|
119
132
|
}
|
|
120
133
|
patchAgentsMd(join(process.cwd(), 'AGENTS.md'), 'local')
|
|
121
|
-
return dir
|
|
134
|
+
return `${dir} + ${skillsDir}`
|
|
122
135
|
},
|
|
123
136
|
|
|
124
|
-
triggerNote: 'In Codex CLI, say "use
|
|
137
|
+
triggerNote: 'In Codex CLI, use $buildflow-start or say "use buildflow-start". Slash menu commands are not exposed by Codex.',
|
|
125
138
|
},
|
|
126
139
|
|
|
127
140
|
cursor: {
|
|
@@ -139,6 +152,9 @@ const TOOLS = {
|
|
|
139
152
|
return hasCursorApp || hasCursorDir
|
|
140
153
|
},
|
|
141
154
|
|
|
155
|
+
isInstalledLocal() { return existsSync(join(process.cwd(), '.cursor', 'rules', 'buildflow.mdc')) },
|
|
156
|
+
isInstalledGlobal() { return this.isInstalledLocal() },
|
|
157
|
+
|
|
142
158
|
installGlobal(commandFiles) {
|
|
143
159
|
return this.installLocal(commandFiles)
|
|
144
160
|
},
|
|
@@ -171,6 +187,12 @@ const TOOLS = {
|
|
|
171
187
|
)
|
|
172
188
|
},
|
|
173
189
|
|
|
190
|
+
isInstalledLocal() {
|
|
191
|
+
const p = join(process.cwd(), '.clinerules')
|
|
192
|
+
return existsSync(p) && readFileSync(p, 'utf8').includes('BuildFlow')
|
|
193
|
+
},
|
|
194
|
+
isInstalledGlobal() { return this.isInstalledLocal() },
|
|
195
|
+
|
|
174
196
|
installGlobal(commandFiles) {
|
|
175
197
|
return this.installLocal(commandFiles)
|
|
176
198
|
},
|
|
@@ -194,6 +216,9 @@ const TOOLS = {
|
|
|
194
216
|
return existsSync(join(homedir(), '.continue', 'config.json'))
|
|
195
217
|
},
|
|
196
218
|
|
|
219
|
+
isInstalledLocal() { return existsSync(join(process.cwd(), '.continue', 'buildflow', 'start.md')) },
|
|
220
|
+
isInstalledGlobal() { return existsSync(join(homedir(), '.continue', 'buildflow', 'start.md')) },
|
|
221
|
+
|
|
197
222
|
installGlobal(commandFiles) {
|
|
198
223
|
const dir = join(homedir(), '.continue', 'buildflow')
|
|
199
224
|
mkdirSync(dir, { recursive: true })
|
|
@@ -218,21 +243,58 @@ const TOOLS = {
|
|
|
218
243
|
|
|
219
244
|
}
|
|
220
245
|
|
|
246
|
+
const UPDATE_CHECK_INSTRUCTION = `
|
|
247
|
+
## BuildFlow Update Check (Run Every Session)
|
|
248
|
+
|
|
249
|
+
At the very start of every session, before anything else:
|
|
250
|
+
1. Check if \`.buildflow/core/UPDATE.md\` exists
|
|
251
|
+
2. If it does, show the user this notice:
|
|
252
|
+
> "BuildFlow update available — run \`npx buildflow-dev update\` in your terminal to upgrade."
|
|
253
|
+
Then show the contents of UPDATE.md so the user sees the version details.
|
|
254
|
+
3. If the file does not exist, proceed silently.
|
|
255
|
+
`
|
|
256
|
+
|
|
221
257
|
function geminiContextBlock(commandFiles) {
|
|
222
258
|
const commandList = Object.keys(commandFiles)
|
|
223
259
|
.map(name => `- \`/buildflow-${name}\`: see .gemini/commands/${name}.md`)
|
|
224
260
|
.join('\n')
|
|
225
|
-
return `## BuildFlow Commands
|
|
261
|
+
return `## BuildFlow Commands
|
|
262
|
+
|
|
263
|
+
When the user types a /buildflow-* command, load and execute the corresponding file from .gemini/commands/.
|
|
264
|
+
|
|
265
|
+
${commandList}
|
|
266
|
+
${UPDATE_CHECK_INSTRUCTION}`
|
|
226
267
|
}
|
|
227
268
|
|
|
228
269
|
function patchAgentsMd(filePath, scope) {
|
|
229
270
|
const existing = existsSync(filePath) ? readFileSync(filePath, 'utf8') : ''
|
|
230
271
|
if (existing.includes('BuildFlow')) return
|
|
231
272
|
const dir = scope === 'global' ? '~/.codex/instructions/' : '.codex/instructions/'
|
|
232
|
-
const block = `\n\n## BuildFlow Instructions\n\nWhen the user types /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`
|
|
273
|
+
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${UPDATE_CHECK_INSTRUCTION}`
|
|
233
274
|
writeFileSync(filePath, existing + block)
|
|
234
275
|
}
|
|
235
276
|
|
|
277
|
+
function writeCodexSkill(skillsDir, name, commandContent) {
|
|
278
|
+
const skillName = `buildflow-${name}`
|
|
279
|
+
const skillDir = join(skillsDir, skillName)
|
|
280
|
+
mkdirSync(skillDir, { recursive: true })
|
|
281
|
+
writeFileSync(join(skillDir, 'SKILL.md'), codexSkillContent(skillName, commandContent))
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function codexSkillContent(skillName, commandContent) {
|
|
285
|
+
const description = extractFrontmatterValue(commandContent, 'description') || `Run ${skillName}`
|
|
286
|
+
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`
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function extractFrontmatterValue(content, key) {
|
|
290
|
+
const match = content.match(new RegExp(`^${key}:\\s*(.+)$`, 'm'))
|
|
291
|
+
return match?.[1]?.trim().replace(/^["']|["']$/g, '')
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function escapeYamlString(value) {
|
|
295
|
+
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
|
|
296
|
+
}
|
|
297
|
+
|
|
236
298
|
function cursorRulesContent(commandFiles) {
|
|
237
299
|
const commandDescriptions = Object.keys(commandFiles)
|
|
238
300
|
.map(name => `- @buildflow-${name}`)
|
|
@@ -246,7 +308,7 @@ alwaysApply: false
|
|
|
246
308
|
# BuildFlow v3.0
|
|
247
309
|
|
|
248
310
|
You are integrated with BuildFlow, an adaptive development orchestration system.
|
|
249
|
-
|
|
311
|
+
${UPDATE_CHECK_INSTRUCTION}
|
|
250
312
|
## Available Commands
|
|
251
313
|
|
|
252
314
|
When the user types @buildflow-<command> or references a buildflow command, execute the corresponding workflow:
|
|
@@ -283,7 +345,7 @@ function clineRulesContent(commandFiles) {
|
|
|
283
345
|
.map(name => `- /buildflow-${name}`)
|
|
284
346
|
.join('\n')
|
|
285
347
|
return `# BuildFlow v3.0 Rules for Cline
|
|
286
|
-
|
|
348
|
+
${UPDATE_CHECK_INSTRUCTION}
|
|
287
349
|
## Slash Commands
|
|
288
350
|
|
|
289
351
|
When the user types any of the following commands, load the corresponding instruction file from .buildflow/commands/:
|
|
@@ -359,6 +421,17 @@ function detectAppName() {
|
|
|
359
421
|
return process.cwd().split(/[/\\]/).pop() || 'my-project'
|
|
360
422
|
}
|
|
361
423
|
|
|
424
|
+
export function getToolStatus() {
|
|
425
|
+
return Object.values(TOOLS).map(tool => ({
|
|
426
|
+
id: tool.id,
|
|
427
|
+
name: tool.name,
|
|
428
|
+
icon: tool.icon,
|
|
429
|
+
detected: tool.detect(),
|
|
430
|
+
installedLocal: tool.isInstalledLocal(),
|
|
431
|
+
installedGlobal: tool.isInstalledGlobal(),
|
|
432
|
+
}))
|
|
433
|
+
}
|
|
434
|
+
|
|
362
435
|
function loadCommandTemplates() {
|
|
363
436
|
const templatesDir = join(__dirname, '../../templates/commands')
|
|
364
437
|
const commands = {}
|
|
@@ -393,17 +466,30 @@ export async function run(opts = {}) {
|
|
|
393
466
|
const detectedList = Object.entries(detected).filter(([, found]) => found)
|
|
394
467
|
const notFoundList = Object.entries(detected).filter(([, found]) => !found)
|
|
395
468
|
|
|
469
|
+
// Tools detected on the system but BuildFlow not yet installed into them
|
|
470
|
+
const newlyDetected = detectedList.filter(([id]) => {
|
|
471
|
+
const t = TOOLS[id]
|
|
472
|
+
return !t.isInstalledLocal() && !t.isInstalledGlobal()
|
|
473
|
+
})
|
|
474
|
+
|
|
396
475
|
if (detectedList.length > 0) {
|
|
397
476
|
console.log(chalk.green(' ✓ Detected on your system:'))
|
|
398
477
|
for (const [id] of detectedList) {
|
|
399
478
|
const t = TOOLS[id]
|
|
400
|
-
|
|
479
|
+
const installed = t.isInstalledLocal() || t.isInstalledGlobal()
|
|
480
|
+
const badge = installed ? chalk.dim(' (already installed)') : chalk.yellow(' (not yet installed)')
|
|
481
|
+
console.log(chalk.green(` ${t.icon} ${t.name}`) + badge)
|
|
401
482
|
}
|
|
402
483
|
} else {
|
|
403
484
|
console.log(chalk.yellow(' ⚠ No AI tools detected automatically.'))
|
|
404
485
|
console.log(chalk.dim(' You can still install for tools not on this list.\n'))
|
|
405
486
|
}
|
|
406
487
|
|
|
488
|
+
if (newlyDetected.length > 0) {
|
|
489
|
+
console.log(chalk.yellow(`\n ${newlyDetected.length} tool(s) detected but BuildFlow not installed yet.`))
|
|
490
|
+
console.log(chalk.dim(' They are pre-selected below.\n'))
|
|
491
|
+
}
|
|
492
|
+
|
|
407
493
|
if (notFoundList.length > 0) {
|
|
408
494
|
console.log(chalk.dim('\n Not detected (can still install):'))
|
|
409
495
|
for (const [id] of notFoundList) {
|
|
@@ -428,11 +514,19 @@ export async function run(opts = {}) {
|
|
|
428
514
|
return
|
|
429
515
|
}
|
|
430
516
|
} else {
|
|
431
|
-
const choices = Object.entries(TOOLS).map(([id, tool]) =>
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
517
|
+
const choices = Object.entries(TOOLS).map(([id, tool]) => {
|
|
518
|
+
const isInstalled = tool.isInstalledLocal() || tool.isInstalledGlobal()
|
|
519
|
+
const statusBadge = !detected[id]
|
|
520
|
+
? ''
|
|
521
|
+
: isInstalled
|
|
522
|
+
? chalk.dim(' (reinstall)')
|
|
523
|
+
: chalk.yellow(' ← new')
|
|
524
|
+
return {
|
|
525
|
+
name: id,
|
|
526
|
+
message: `${tool.icon} ${tool.name}${statusBadge}`,
|
|
527
|
+
hint: tool.description,
|
|
528
|
+
}
|
|
529
|
+
})
|
|
436
530
|
|
|
437
531
|
const { tools } = await prompt({
|
|
438
532
|
type: 'multiselect',
|
|
@@ -440,7 +534,10 @@ export async function run(opts = {}) {
|
|
|
440
534
|
message: 'Which AI tools do you want to install BuildFlow into?',
|
|
441
535
|
hint: '(Space to select, Enter to confirm)',
|
|
442
536
|
choices,
|
|
443
|
-
|
|
537
|
+
// Pre-select newly detected tools (detected but not yet installed)
|
|
538
|
+
initial: newlyDetected.length > 0
|
|
539
|
+
? newlyDetected.map(([id]) => id)
|
|
540
|
+
: detectedList.map(([id]) => id),
|
|
444
541
|
})
|
|
445
542
|
|
|
446
543
|
if (!tools || tools.length === 0) {
|
|
@@ -539,9 +636,15 @@ export async function run(opts = {}) {
|
|
|
539
636
|
if (succeeded.length > 0) {
|
|
540
637
|
console.log(chalk.bold('\n Next steps:\n'))
|
|
541
638
|
console.log(chalk.white(' 1. Open your AI tool in this project'))
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
639
|
+
if (succeeded.every(({ tool }) => tool.id === 'codex')) {
|
|
640
|
+
console.log(chalk.white(' 2. Restart Codex CLI so new skills are loaded'))
|
|
641
|
+
console.log(chalk.white(' 3. Start with: ') + chalk.cyan('$buildflow-start'))
|
|
642
|
+
console.log(chalk.white(' Or for existing projects: ') + chalk.cyan('$buildflow-onboard'))
|
|
643
|
+
} else {
|
|
644
|
+
console.log(chalk.white(' 2. Type "/" to see BuildFlow commands'))
|
|
645
|
+
console.log(chalk.white(' 3. Start with: ') + chalk.cyan('/buildflow-start'))
|
|
646
|
+
console.log(chalk.white(' Or for existing projects: ') + chalk.cyan('/buildflow-onboard'))
|
|
647
|
+
}
|
|
545
648
|
console.log('')
|
|
546
649
|
}
|
|
547
650
|
}
|
package/src/commands/status.js
CHANGED
|
@@ -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('
|
|
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 = ' ') => {
|
package/src/commands/update.js
CHANGED
|
@@ -4,6 +4,7 @@ import { existsSync, readFileSync } from 'fs'
|
|
|
4
4
|
import { join, dirname } from 'path'
|
|
5
5
|
import { fileURLToPath } from 'url'
|
|
6
6
|
import { run as runInstall } 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,12 +16,20 @@ 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
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
|
@@ -30,6 +39,9 @@ export async function run(opts = {}) {
|
|
|
30
39
|
await new Promise(r => setTimeout(r, 400))
|
|
31
40
|
sp.succeed(chalk.green(' ✓ Command files updated'))
|
|
32
41
|
|
|
42
|
+
// Clear the update notice now that we've updated
|
|
43
|
+
clearUpdateNotice()
|
|
44
|
+
|
|
33
45
|
console.log('')
|
|
34
46
|
await runInstall({ yes: opts.yes })
|
|
35
47
|
}
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { run as init } from './commands/init.js'
|
|
2
2
|
export { run as install } from './commands/install.js'
|
|
3
3
|
export { run as audit } from './commands/audit.js'
|
|
4
|
+
export { run as fix } from './commands/fix.js'
|
|
4
5
|
export { run as status } from './commands/status.js'
|
|
5
6
|
export { run as update } from './commands/update.js'
|
|
@@ -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
|
+
}
|
package/src/utils/welcome.js
CHANGED
|
@@ -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'))
|
package/templates/CLAUDE.md
CHANGED
|
@@ -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:
|