theslopmachine 0.3.7 → 0.4.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 (44) hide show
  1. package/MANUAL.md +13 -9
  2. package/README.md +163 -3
  3. package/RELEASE.md +11 -3
  4. package/assets/agents/developer-v2.md +86 -0
  5. package/assets/agents/developer.md +21 -23
  6. package/assets/agents/slopmachine-v2.md +219 -0
  7. package/assets/agents/slopmachine.md +56 -38
  8. package/assets/skills/beads-operations/SKILL.md +32 -31
  9. package/assets/skills/beads-operations-v2/SKILL.md +82 -0
  10. package/assets/skills/clarification-gate/SKILL.md +8 -1
  11. package/assets/skills/clarification-gate-v2/SKILL.md +74 -0
  12. package/assets/skills/developer-session-lifecycle/SKILL.md +45 -14
  13. package/assets/skills/developer-session-lifecycle-v2/SKILL.md +148 -0
  14. package/assets/skills/development-guidance-v2/SKILL.md +60 -0
  15. package/assets/skills/evaluation-triage-v2/SKILL.md +38 -0
  16. package/assets/skills/final-evaluation-orchestration/SKILL.md +9 -11
  17. package/assets/skills/final-evaluation-orchestration-v2/SKILL.md +57 -0
  18. package/assets/skills/get-overlays/SKILL.md +77 -6
  19. package/assets/skills/hardening-gate-v2/SKILL.md +64 -0
  20. package/assets/skills/integrated-verification-v2/SKILL.md +47 -0
  21. package/assets/skills/owner-evidence-discipline-v2/SKILL.md +15 -0
  22. package/assets/skills/planning-gate/SKILL.md +6 -4
  23. package/assets/skills/planning-gate-v2/SKILL.md +91 -0
  24. package/assets/skills/planning-guidance-v2/SKILL.md +100 -0
  25. package/assets/skills/remediation-guidance-v2/SKILL.md +31 -0
  26. package/assets/skills/report-output-discipline-v2/SKILL.md +15 -0
  27. package/assets/skills/scaffold-guidance-v2/SKILL.md +57 -0
  28. package/assets/skills/session-rollover-v2/SKILL.md +41 -0
  29. package/assets/skills/submission-packaging/SKILL.md +147 -115
  30. package/assets/skills/submission-packaging-v2/SKILL.md +142 -0
  31. package/assets/skills/verification-gates/SKILL.md +44 -16
  32. package/assets/skills/verification-gates-v2/SKILL.md +102 -0
  33. package/assets/slopmachine/backend-evaluation-prompt.md +9 -2
  34. package/assets/slopmachine/frontend-evaluation-prompt.md +9 -2
  35. package/assets/slopmachine/templates/AGENTS-v2.md +55 -0
  36. package/assets/slopmachine/templates/AGENTS.md +20 -17
  37. package/assets/slopmachine/tracker-init.js +104 -0
  38. package/assets/slopmachine/workflow-init-v2.js +99 -0
  39. package/package.json +1 -1
  40. package/src/constants.js +22 -3
  41. package/src/init.js +33 -28
  42. package/src/install.js +186 -140
  43. package/src/utils.js +19 -0
  44. package/assets/slopmachine/beads-init.js +0 -439
package/src/install.js CHANGED
@@ -3,7 +3,7 @@ import os from 'node:os'
3
3
  import path from 'node:path'
4
4
 
5
5
  import {
6
- BEADS_VERSION,
6
+ BR_VERSION,
7
7
  buildPaths,
8
8
  MCP_ENTRIES,
9
9
  OPCODE_VERSION,
@@ -14,8 +14,8 @@ import {
14
14
  import {
15
15
  backupFile,
16
16
  commandExists,
17
- copyDirIfMissing,
18
- copyFileIfMissing,
17
+ copyDirReplacing,
18
+ copyFileReplacing,
19
19
  ensureDir,
20
20
  findBashExecutable,
21
21
  getGlobalNpmPackageVersion,
@@ -28,6 +28,7 @@ import {
28
28
  readJsonIfExists,
29
29
  resolveCommand,
30
30
  runCommand,
31
+ section,
31
32
  warn,
32
33
  writeJson,
33
34
  } from './utils.js'
@@ -44,26 +45,21 @@ function getUnixBeadsBinDirs() {
44
45
  const home = getHomeDir()
45
46
  return [
46
47
  path.join(home, '.local', 'bin'),
47
- path.join(home, 'go', 'bin'),
48
- path.join(home, '.linuxbrew', 'bin'),
48
+ path.join(home, '.cargo', 'bin'),
49
49
  '/opt/homebrew/bin',
50
50
  '/usr/local/bin',
51
- '/home/linuxbrew/.linuxbrew/bin',
52
51
  ]
53
52
  }
54
53
 
55
- function getLinuxBeadsBinDir() {
56
- return path.join(getHomeDir(), '.local', 'bin')
57
- }
58
-
59
54
  function getWindowsBeadsBinDirs() {
60
55
  if (process.platform !== 'win32') {
61
56
  return []
62
57
  }
63
58
 
64
59
  const dirs = [
65
- process.env.LOCALAPPDATA ? path.join(process.env.LOCALAPPDATA, 'Programs', 'bd') : null,
66
- process.env.USERPROFILE ? path.join(process.env.USERPROFILE, 'go', 'bin') : null,
60
+ process.env.LOCALAPPDATA ? path.join(process.env.LOCALAPPDATA, 'Programs', 'br') : null,
61
+ path.join(getHomeDir(), '.local', 'bin'),
62
+ path.join(getHomeDir(), '.cargo', 'bin'),
67
63
  ].filter(Boolean)
68
64
 
69
65
  return [...new Set(dirs)]
@@ -71,14 +67,14 @@ function getWindowsBeadsBinDirs() {
71
67
 
72
68
  function getBeadsCommandCandidates() {
73
69
  if (process.platform === 'win32') {
74
- return getWindowsBeadsBinDirs().map((dir) => path.join(dir, 'bd.exe'))
70
+ return getWindowsBeadsBinDirs().map((dir) => path.join(dir, 'br.exe'))
75
71
  }
76
72
 
77
- return getUnixBeadsBinDirs().map((dir) => path.join(dir, 'bd'))
73
+ return getUnixBeadsBinDirs().map((dir) => path.join(dir, 'br'))
78
74
  }
79
75
 
80
76
  async function resolveBeadsCommand() {
81
- return resolveCommand('bd', { additionalCandidates: getBeadsCommandCandidates(), preferCandidates: true })
77
+ return resolveCommand('br', { additionalCandidates: getBeadsCommandCandidates(), preferCandidates: true })
82
78
  }
83
79
 
84
80
  async function getBeadsVersion() {
@@ -99,14 +95,24 @@ async function probeBeadsRuntime(command) {
99
95
  const probeDir = await fs.mkdtemp(path.join(os.tmpdir(), 'slopmachine-beads-probe-'))
100
96
 
101
97
  try {
102
- const result = await runCommand(command, ['init', '--quiet', '--stealth', '--skip-hooks'], {
98
+ const result = await runCommand(command, ['init', '--quiet'], {
103
99
  cwd: probeDir,
104
100
  env: { ...process.env, CI: '1' },
105
101
  })
106
102
 
107
- const output = `${result.stdout}${result.stderr}`.trim()
103
+ if (result.code !== 0) {
104
+ const output = `${result.stdout}${result.stderr}`.trim()
105
+ return { ok: false, output }
106
+ }
107
+
108
+ const syncResult = await runCommand(command, ['sync', '--flush-only'], {
109
+ cwd: probeDir,
110
+ env: { ...process.env, CI: '1' },
111
+ })
112
+
113
+ const output = `${syncResult.stdout}${syncResult.stderr}`.trim()
108
114
  return {
109
- ok: result.code === 0,
115
+ ok: syncResult.code === 0,
110
116
  output,
111
117
  }
112
118
  } finally {
@@ -167,7 +173,7 @@ async function ensureUnixBeadsPath() {
167
173
  }
168
174
 
169
175
  for (const dir of getUnixBeadsBinDirs()) {
170
- if (!(await pathExists(path.join(dir, 'bd')))) {
176
+ if (!(await pathExists(path.join(dir, 'br')))) {
171
177
  continue
172
178
  }
173
179
 
@@ -175,122 +181,120 @@ async function ensureUnixBeadsPath() {
175
181
  }
176
182
  }
177
183
 
178
- async function installLinuxBeadsPrerequisites() {
179
- const managers = await detectPackageManagers()
180
-
181
- if (managers.apt) {
182
- log('Installing Linux Beads build dependencies via apt')
183
- const updateResult = await runCommand('sudo', ['apt-get', 'update'], { stdio: 'inherit' })
184
- if (updateResult.code !== 0) {
185
- return updateResult
186
- }
187
-
188
- return runCommand('sudo', [
189
- 'apt-get',
190
- 'install',
191
- '-y',
192
- 'build-essential',
193
- 'pkg-config',
194
- 'libicu-dev',
195
- 'libzstd-dev',
196
- 'golang-go',
197
- ], { stdio: 'inherit' })
198
- }
199
-
200
- if (managers.dnf) {
201
- log('Installing Linux Beads build dependencies via dnf')
202
- return runCommand('sudo', [
203
- 'dnf',
204
- 'install',
205
- '-y',
206
- 'gcc',
207
- 'gcc-c++',
208
- 'make',
209
- 'pkgconf-pkg-config',
210
- 'libicu-devel',
211
- 'libzstd-devel',
212
- 'golang',
213
- ], { stdio: 'inherit' })
214
- }
215
-
216
- return { code: 0, stdout: '', stderr: '' }
217
- }
218
-
219
184
  async function installBeadsOnWindows() {
220
185
  const shell = await resolveCommand('pwsh') || await resolveCommand('powershell')
221
186
  if (!shell) {
222
- return { code: 1, stdout: '', stderr: 'PowerShell is required to install Beads on Windows' }
187
+ return { code: 1, stdout: '', stderr: 'PowerShell is required to install beads_rust on Windows' }
188
+ }
189
+
190
+ if (process.arch !== 'x64') {
191
+ return { code: 1, stdout: '', stderr: `Unsupported Windows architecture for beads_rust: ${process.arch}` }
223
192
  }
224
193
 
225
- log('Installing Beads with the upstream Windows PowerShell installer')
226
- const result = await runCommand(shell, [
227
- '-NoProfile',
228
- '-ExecutionPolicy',
229
- 'Bypass',
230
- '-Command',
231
- 'irm https://raw.githubusercontent.com/steveyegge/beads/main/install.ps1 | iex',
232
- ], { stdio: 'inherit' })
194
+ const installDir = process.env.LOCALAPPDATA
195
+ ? path.join(process.env.LOCALAPPDATA, 'Programs', 'br')
196
+ : path.join(getHomeDir(), 'AppData', 'Local', 'Programs', 'br')
233
197
 
198
+ const zipName = `br-v${BR_VERSION}-windows_amd64.zip`
199
+ const downloadUrl = `https://github.com/Dicklesworthstone/beads_rust/releases/download/v${BR_VERSION}/${zipName}`
200
+ const tempZip = process.env.TEMP
201
+ ? path.join(process.env.TEMP, zipName)
202
+ : path.join(getHomeDir(), 'AppData', 'Local', 'Temp', zipName)
203
+
204
+ const ps = [
205
+ '$ErrorActionPreference = "Stop"',
206
+ `$zipUrl = ${quotePowerShell(downloadUrl)}`,
207
+ `$zipPath = ${quotePowerShell(tempZip)}`,
208
+ `$installDir = ${quotePowerShell(installDir)}`,
209
+ 'New-Item -ItemType Directory -Force -Path $installDir | Out-Null',
210
+ 'Invoke-WebRequest -Uri $zipUrl -OutFile $zipPath',
211
+ 'Expand-Archive -Path $zipPath -DestinationPath $installDir -Force',
212
+ 'Remove-Item -Path $zipPath -Force -ErrorAction SilentlyContinue',
213
+ ].join('; ')
214
+
215
+ log(`Installing beads_rust ${BR_VERSION} on Windows from release zip`)
216
+ const result = await runCommand(shell, ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', ps], { stdio: 'inherit' })
234
217
  await ensureWindowsBeadsPath()
235
218
  return result
236
219
  }
237
220
 
238
- async function installBeadsOnUnix() {
239
- if (process.platform === 'linux') {
240
- const depsResult = await installLinuxBeadsPrerequisites()
241
- if (depsResult.code !== 0) {
242
- return depsResult
243
- }
221
+ function getLinuxBrFallbackArchiveName() {
222
+ if (process.platform !== 'linux') {
223
+ return null
224
+ }
244
225
 
245
- if (!(await commandExists('go'))) {
246
- return { code: 1, stdout: '', stderr: 'Go is required to build a CGO-enabled Beads binary on Linux' }
247
- }
226
+ if (process.arch === 'x64') {
227
+ return `br-v${BR_VERSION}-linux_musl_amd64.tar.gz`
228
+ }
248
229
 
249
- const binDir = getLinuxBeadsBinDir()
250
- await ensureDir(binDir)
230
+ if (process.arch === 'arm64') {
231
+ return `br-v${BR_VERSION}-linux_arm64.tar.gz`
232
+ }
251
233
 
252
- log(`Installing Beads ${BEADS_VERSION} on Linux with CGO_ENABLED=1`)
253
- const result = await runCommand('go', ['install', `github.com/steveyegge/beads/cmd/bd@v${BEADS_VERSION}`], {
254
- stdio: 'inherit',
255
- env: {
256
- ...process.env,
257
- CGO_ENABLED: '1',
258
- GOBIN: binDir,
259
- },
260
- })
234
+ return null
235
+ }
261
236
 
262
- await ensureUnixBeadsPath()
263
- return result
237
+ async function downloadFile(url, outputPath) {
238
+ const response = await fetch(url)
239
+ if (!response.ok) {
240
+ throw new Error(`Download failed with status ${response.status}`)
264
241
  }
265
242
 
266
- const managers = await detectPackageManagers()
267
- if (managers.brew) {
268
- log('Installing Beads via Homebrew')
269
- const result = await runCommand('brew', ['install', 'beads'], { stdio: 'inherit' })
243
+ const buffer = Buffer.from(await response.arrayBuffer())
244
+ await fs.writeFile(outputPath, buffer)
245
+ }
246
+
247
+ async function installLinuxBrFallbackBinary() {
248
+ const archiveName = getLinuxBrFallbackArchiveName()
249
+ if (!archiveName) {
250
+ return { code: 1, stdout: '', stderr: `Unsupported Linux architecture for beads_rust fallback: ${process.arch}` }
251
+ }
252
+
253
+ const downloadUrl = `https://github.com/Dicklesworthstone/beads_rust/releases/download/v${BR_VERSION}/${archiveName}`
254
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'slopmachine-br-fallback-'))
255
+ const archivePath = path.join(tempDir, archiveName)
256
+ const binDir = path.join(getHomeDir(), '.local', 'bin')
257
+
258
+ try {
259
+ await ensureDir(binDir)
260
+ log(`Installing beads_rust fallback binary ${archiveName}`)
261
+ await downloadFile(downloadUrl, archivePath)
262
+
263
+ const extractResult = await runCommand('tar', ['-xzf', archivePath, '-C', tempDir])
264
+ if (extractResult.code !== 0) {
265
+ return extractResult
266
+ }
267
+
268
+ const sourceBinary = path.join(tempDir, 'br')
269
+ if (!(await pathExists(sourceBinary))) {
270
+ return { code: 1, stdout: '', stderr: 'Downloaded beads_rust archive did not contain br' }
271
+ }
272
+
273
+ await fs.copyFile(sourceBinary, path.join(binDir, 'br'))
274
+ await fs.chmod(path.join(binDir, 'br'), 0o755)
270
275
  await ensureUnixBeadsPath()
271
- return result
276
+ return { code: 0, stdout: '', stderr: '' }
277
+ } catch (error) {
278
+ return { code: 1, stdout: '', stderr: error instanceof Error ? error.message : String(error) }
279
+ } finally {
280
+ await fs.rm(tempDir, { recursive: true, force: true })
272
281
  }
282
+ }
273
283
 
284
+ async function installBeadsOnUnix() {
274
285
  const bashExecutable = await findBashExecutable()
275
286
  if (!bashExecutable) {
276
- return { code: 1, stdout: '', stderr: 'bash is required to run the Beads installer on this platform' }
287
+ return { code: 1, stdout: '', stderr: 'bash is required to run the beads_rust installer on this platform' }
277
288
  }
278
289
 
279
- if (await commandExists('curl')) {
280
- log('Installing Beads with the upstream Unix install script')
281
- const result = await runCommand(bashExecutable, ['-lc', 'curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash'], { stdio: 'inherit' })
282
- await ensureUnixBeadsPath()
283
- return result
290
+ if (!(await commandExists('curl'))) {
291
+ return { code: 1, stdout: '', stderr: 'curl is required to install beads_rust automatically on this platform' }
284
292
  }
285
293
 
286
- if (await commandExists('wget')) {
287
- log('Installing Beads with the upstream Unix install script')
288
- const result = await runCommand(bashExecutable, ['-lc', 'wget -qO- https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash'], { stdio: 'inherit' })
289
- await ensureUnixBeadsPath()
290
- return result
291
- }
292
-
293
- return { code: 1, stdout: '', stderr: 'curl or wget is required to install Beads on this platform' }
294
+ log('Installing beads_rust with the upstream Unix install script')
295
+ const result = await runCommand(bashExecutable, ['-lc', 'curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/beads_rust/main/install.sh?$(date +%s)" | bash'], { stdio: 'inherit' })
296
+ await ensureUnixBeadsPath()
297
+ return result
294
298
  }
295
299
 
296
300
  async function getCommandVersion(command, args = ['--version']) {
@@ -337,7 +341,7 @@ async function tryInstallCoreDependency(name) {
337
341
  log('Installing opencode-ai@latest globally via npm')
338
342
  return runCommand('npm', ['install', '-g', 'opencode-ai@latest'], { stdio: 'inherit' })
339
343
  }
340
- if (name === 'bd') {
344
+ if (name === 'br') {
341
345
  if (process.platform === 'win32') {
342
346
  return installBeadsOnWindows()
343
347
  }
@@ -419,7 +423,7 @@ async function ensureDependency({ name, checkCommand, requiredVersion, installab
419
423
  }
420
424
  }
421
425
 
422
- if (checkCommand === 'bd') {
426
+ if (checkCommand === 'br') {
423
427
  const beads = await getBeadsVersion()
424
428
  if (beads) {
425
429
  const probe = await probeBeadsRuntime(beads.command)
@@ -483,23 +487,59 @@ async function ensureDependency({ name, checkCommand, requiredVersion, installab
483
487
  warn('opencode-ai install completed, but the package could not be verified. Open a new terminal and run `opencode --version` before using `slopmachine init -o`.')
484
488
  }
485
489
 
486
- if (checkCommand === 'bd') {
490
+ if (checkCommand === 'br') {
487
491
  const beads = await getBeadsVersion()
488
492
  if (beads) {
489
493
  const probe = await probeBeadsRuntime(beads.command)
490
494
  if (probe.ok) {
491
495
  log(`Installed ${name} via ${beads.command}: ${beads.version}`)
492
- if (process.platform === 'win32' && !(await commandExists('bd'))) {
493
- warn('Beads was installed, but `bd` is not visible in this shell PATH yet. The installer added the common Windows Beads directories to PATH for future shells.')
496
+ if (process.platform === 'win32' && !(await commandExists('br'))) {
497
+ warn('beads_rust was installed, but `br` is not visible in this shell PATH yet. The installer added the common beads_rust directories to PATH for future shells.')
494
498
  }
495
499
  return
496
500
  }
497
501
 
502
+ if (process.platform === 'linux') {
503
+ warn(`${name} was installed, but the detected binary failed runtime verification. Trying Linux fallback binary.`)
504
+ const fallbackResult = await installLinuxBrFallbackBinary()
505
+ if (fallbackResult.code === 0) {
506
+ const fallbackVersion = await getBeadsVersion()
507
+ if (fallbackVersion) {
508
+ const fallbackProbe = await probeBeadsRuntime(fallbackVersion.command)
509
+ if (fallbackProbe.ok) {
510
+ log(`Installed ${name} via fallback binary at ${fallbackVersion.command}: ${fallbackVersion.version}`)
511
+ return
512
+ }
513
+ warn(`${name} fallback binary still failed runtime verification.${fallbackProbe.output ? ` ${fallbackProbe.output}` : ''}`)
514
+ }
515
+ } else {
516
+ warn(`Linux fallback binary installation failed.${fallbackResult.stderr ? ` ${fallbackResult.stderr}` : ''}`)
517
+ }
518
+ }
519
+
498
520
  warn(`${name} was installed at ${beads.command}, but it still failed a runtime probe.${probe.output ? ` ${probe.output}` : ''}`)
499
521
  return
500
522
  }
501
523
 
502
- warn('Beads installation completed, but the `bd` command could not be verified. Re-open PowerShell or Command Prompt and run `bd version`.')
524
+ if (process.platform === 'linux') {
525
+ warn(`${name} install completed, but no working br binary was detected. Trying Linux fallback binary.`)
526
+ const fallbackResult = await installLinuxBrFallbackBinary()
527
+ if (fallbackResult.code === 0) {
528
+ const fallbackVersion = await getBeadsVersion()
529
+ if (fallbackVersion) {
530
+ const fallbackProbe = await probeBeadsRuntime(fallbackVersion.command)
531
+ if (fallbackProbe.ok) {
532
+ log(`Installed ${name} via fallback binary at ${fallbackVersion.command}: ${fallbackVersion.version}`)
533
+ return
534
+ }
535
+ warn(`${name} fallback binary still failed runtime verification.${fallbackProbe.output ? ` ${fallbackProbe.output}` : ''}`)
536
+ }
537
+ } else {
538
+ warn(`Linux fallback binary installation failed.${fallbackResult.stderr ? ` ${fallbackResult.stderr}` : ''}`)
539
+ }
540
+ }
541
+
542
+ warn('beads_rust installation completed, but the `br` command could not be verified. Open a new terminal and run `br version`.')
503
543
  }
504
544
  }
505
545
 
@@ -529,16 +569,16 @@ async function checkInitShellSupport() {
529
569
  async function installAgents(paths) {
530
570
  const sourceAgents = path.join(assetsRoot(), 'agents')
531
571
  await ensureDir(paths.opencodeAgentsDir)
532
- const summary = { installed: [], skipped: [] }
572
+ const summary = { installed: [], refreshed: [] }
533
573
 
534
- for (const fileName of ['slopmachine.md', 'developer.md']) {
535
- const result = await copyFileIfMissing(path.join(sourceAgents, fileName), path.join(paths.opencodeAgentsDir, fileName))
536
- if (result.copied) {
574
+ for (const fileName of ['slopmachine.md', 'developer.md', 'slopmachine-v2.md', 'developer-v2.md']) {
575
+ const result = await copyFileReplacing(path.join(sourceAgents, fileName), path.join(paths.opencodeAgentsDir, fileName))
576
+ if (result.replaced) {
577
+ log(`Refreshed agent: ${fileName}`)
578
+ summary.refreshed.push(fileName)
579
+ } else {
537
580
  log(`Installed agent: ${fileName}`)
538
581
  summary.installed.push(fileName)
539
- } else {
540
- warn(`Skipped existing agent: ${fileName}`)
541
- summary.skipped.push(fileName)
542
582
  }
543
583
  }
544
584
 
@@ -548,16 +588,16 @@ async function installAgents(paths) {
548
588
  async function installSkills(paths) {
549
589
  const sourceSkills = path.join(assetsRoot(), 'skills')
550
590
  await ensureDir(paths.globalSkillsDir)
551
- const summary = { installed: [], skipped: [] }
591
+ const summary = { installed: [], refreshed: [] }
552
592
 
553
593
  for (const dirName of REQUIRED_SKILL_DIRS) {
554
- const result = await copyDirIfMissing(path.join(sourceSkills, dirName), path.join(paths.globalSkillsDir, dirName))
555
- if (result.copied) {
594
+ const result = await copyDirReplacing(path.join(sourceSkills, dirName), path.join(paths.globalSkillsDir, dirName))
595
+ if (result.replaced) {
596
+ log(`Refreshed skill: ${dirName}`)
597
+ summary.refreshed.push(dirName)
598
+ } else {
556
599
  log(`Installed skill: ${dirName}`)
557
600
  summary.installed.push(dirName)
558
- } else {
559
- warn(`Skipped existing skill: ${dirName}`)
560
- summary.skipped.push(dirName)
561
601
  }
562
602
  }
563
603
 
@@ -567,17 +607,18 @@ async function installSkills(paths) {
567
607
  async function installSlopmachineAssets(paths) {
568
608
  const source = path.join(assetsRoot(), 'slopmachine')
569
609
  await ensureDir(paths.slopmachineDir)
570
- const summary = { installed: [], skipped: [] }
610
+ const summary = { installed: [], refreshed: [] }
571
611
 
572
612
  for (const relativePath of REQUIRED_SLOPMACHINE_FILES) {
573
- const result = await copyFileIfMissing(path.join(source, relativePath), path.join(paths.slopmachineDir, relativePath))
574
- if (result.copied) {
575
- await makeExecutableIfShellScript(path.join(paths.slopmachineDir, relativePath))
613
+ const targetPath = path.join(paths.slopmachineDir, relativePath)
614
+ const result = await copyFileReplacing(path.join(source, relativePath), targetPath)
615
+ await makeExecutableIfShellScript(targetPath)
616
+ if (result.replaced) {
617
+ log(`Refreshed asset: ${relativePath}`)
618
+ summary.refreshed.push(relativePath)
619
+ } else {
576
620
  log(`Installed asset: ${relativePath}`)
577
621
  summary.installed.push(relativePath)
578
- } else {
579
- warn(`Skipped existing asset: ${relativePath}`)
580
- summary.skipped.push(relativePath)
581
622
  }
582
623
  }
583
624
 
@@ -668,31 +709,36 @@ async function collectApiKeys() {
668
709
 
669
710
  export async function runInstall() {
670
711
  const paths = buildPaths()
712
+ section('Setup')
671
713
  log(`Configuring theslopmachine in ${paths.home}`)
672
714
 
715
+ section('Dependencies')
673
716
  await ensureDependency({ name: 'git', checkCommand: 'git', installable: true })
674
717
  await ensureDependency({ name: 'python3', checkCommand: 'python3', installable: true })
675
718
  await ensureDependency({ name: 'opencode', checkCommand: 'opencode', requiredVersion: OPCODE_VERSION, installable: true })
676
- await ensureDependency({ name: 'Beads (bd)', checkCommand: 'bd', requiredVersion: BEADS_VERSION, installable: true })
719
+ await ensureDependency({ name: 'beads_rust (br)', checkCommand: 'br', requiredVersion: null, installable: true })
677
720
  await checkDocker()
678
721
  await checkInitShellSupport()
679
722
 
723
+ section('Package Assets')
680
724
  const agentSummary = await installAgents(paths)
681
725
  const skillSummary = await installSkills(paths)
682
726
  const assetSummary = await installSlopmachineAssets(paths)
683
727
 
728
+ section('OpenCode Config')
684
729
  await maybeInstallPluginBinary()
685
730
  const keys = await collectApiKeys()
686
731
  await mergeOpencodeConfig(paths, keys)
687
732
 
733
+ section('Summary')
688
734
  log('Setup phase completed.')
689
735
  console.log('\ntheslopmachine setup summary')
690
736
  console.log(`- Agents installed: ${agentSummary.installed.length}`)
691
- console.log(`- Agents skipped: ${agentSummary.skipped.length}`)
737
+ console.log(`- Agents refreshed: ${agentSummary.refreshed.length}`)
692
738
  console.log(`- Skills installed: ${skillSummary.installed.length}`)
693
- console.log(`- Skills skipped: ${skillSummary.skipped.length}`)
739
+ console.log(`- Skills refreshed: ${skillSummary.refreshed.length}`)
694
740
  console.log(`- SlopMachine assets installed: ${assetSummary.installed.length}`)
695
- console.log(`- SlopMachine assets skipped: ${assetSummary.skipped.length}`)
741
+ console.log(`- SlopMachine assets refreshed: ${assetSummary.refreshed.length}`)
696
742
  console.log(`- Agents directory: ${paths.opencodeAgentsDir}`)
697
743
  console.log(`- Skills directory: ${paths.globalSkillsDir}`)
698
744
  console.log(`- SlopMachine home: ${paths.slopmachineDir}`)
package/src/utils.js CHANGED
@@ -13,6 +13,10 @@ export function warn(message) {
13
13
  console.warn(`[slopmachine] WARN: ${message}`)
14
14
  }
15
15
 
16
+ export function section(title) {
17
+ console.log(`\n[slopmachine] ${title}`)
18
+ }
19
+
16
20
  export async function pathExists(targetPath) {
17
21
  try {
18
22
  await fs.access(targetPath, fsConstants.F_OK)
@@ -35,6 +39,13 @@ export async function copyFileIfMissing(sourcePath, targetPath) {
35
39
  return { copied: true, skipped: false }
36
40
  }
37
41
 
42
+ export async function copyFileReplacing(sourcePath, targetPath) {
43
+ const existed = await pathExists(targetPath)
44
+ await ensureDir(path.dirname(targetPath))
45
+ await fs.copyFile(sourcePath, targetPath)
46
+ return { copied: true, replaced: existed, skipped: false }
47
+ }
48
+
38
49
  export async function copyDirIfMissing(sourcePath, targetPath) {
39
50
  if (await pathExists(targetPath)) {
40
51
  return { copied: false, skipped: true }
@@ -44,6 +55,14 @@ export async function copyDirIfMissing(sourcePath, targetPath) {
44
55
  return { copied: true, skipped: false }
45
56
  }
46
57
 
58
+ export async function copyDirReplacing(sourcePath, targetPath) {
59
+ const existed = await pathExists(targetPath)
60
+ await ensureDir(path.dirname(targetPath))
61
+ await fs.rm(targetPath, { recursive: true, force: true })
62
+ await fs.cp(sourcePath, targetPath, { recursive: true })
63
+ return { copied: true, replaced: existed, skipped: false }
64
+ }
65
+
47
66
  export async function makeExecutableIfShellScript(targetPath) {
48
67
  if (targetPath.endsWith('.sh')) {
49
68
  await fs.chmod(targetPath, 0o755)