theslopmachine 0.6.2 → 0.7.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 (76) hide show
  1. package/MANUAL.md +21 -6
  2. package/README.md +55 -7
  3. package/RELEASE.md +15 -0
  4. package/assets/agents/developer.md +41 -1
  5. package/assets/agents/slopmachine-claude.md +100 -60
  6. package/assets/agents/slopmachine.md +40 -17
  7. package/assets/claude/agents/developer.md +42 -5
  8. package/assets/skills/clarification-gate/SKILL.md +25 -5
  9. package/assets/skills/claude-worker-management/SKILL.md +280 -57
  10. package/assets/skills/developer-session-lifecycle/SKILL.md +81 -37
  11. package/assets/skills/development-guidance/SKILL.md +21 -1
  12. package/assets/skills/evaluation-triage/SKILL.md +32 -23
  13. package/assets/skills/final-evaluation-orchestration/SKILL.md +86 -50
  14. package/assets/skills/hardening-gate/SKILL.md +17 -3
  15. package/assets/skills/integrated-verification/SKILL.md +3 -3
  16. package/assets/skills/planning-gate/SKILL.md +32 -3
  17. package/assets/skills/planning-guidance/SKILL.md +72 -13
  18. package/assets/skills/retrospective-analysis/SKILL.md +2 -2
  19. package/assets/skills/scaffold-guidance/SKILL.md +129 -124
  20. package/assets/skills/submission-packaging/SKILL.md +33 -27
  21. package/assets/skills/verification-gates/SKILL.md +44 -14
  22. package/assets/slopmachine/backend-evaluation-prompt.md +1 -1
  23. package/assets/slopmachine/frontend-evaluation-prompt.md +5 -5
  24. package/assets/slopmachine/scaffold-playbooks/android-kotlin-compose.md +81 -0
  25. package/assets/slopmachine/scaffold-playbooks/android-kotlin-views.md +191 -0
  26. package/assets/slopmachine/scaffold-playbooks/android-native-java.md +203 -0
  27. package/assets/slopmachine/scaffold-playbooks/angular-default.md +181 -0
  28. package/assets/slopmachine/scaffold-playbooks/backend-baseline.md +142 -0
  29. package/assets/slopmachine/scaffold-playbooks/backend-family-matrix.md +80 -0
  30. package/assets/slopmachine/scaffold-playbooks/database-module-matrix.md +80 -0
  31. package/assets/slopmachine/scaffold-playbooks/django-default.md +166 -0
  32. package/assets/slopmachine/scaffold-playbooks/docker-baseline.md +189 -0
  33. package/assets/slopmachine/scaffold-playbooks/docker-shared-contract.md +334 -0
  34. package/assets/slopmachine/scaffold-playbooks/electron-vite-default.md +124 -0
  35. package/assets/slopmachine/scaffold-playbooks/expo-react-native-default.md +73 -0
  36. package/assets/slopmachine/scaffold-playbooks/fastapi-default.md +134 -0
  37. package/assets/slopmachine/scaffold-playbooks/frontend-baseline.md +160 -0
  38. package/assets/slopmachine/scaffold-playbooks/frontend-family-matrix.md +134 -0
  39. package/assets/slopmachine/scaffold-playbooks/generic-unknown-tech-guide.md +136 -0
  40. package/assets/slopmachine/scaffold-playbooks/go-chi-default.md +160 -0
  41. package/assets/slopmachine/scaffold-playbooks/ios-linux-portable.md +93 -0
  42. package/assets/slopmachine/scaffold-playbooks/ios-native-objective-c.md +151 -0
  43. package/assets/slopmachine/scaffold-playbooks/ios-native-swift.md +188 -0
  44. package/assets/slopmachine/scaffold-playbooks/laravel-default.md +216 -0
  45. package/assets/slopmachine/scaffold-playbooks/livewire-default.md +265 -0
  46. package/assets/slopmachine/scaffold-playbooks/overlay-module-matrix.md +130 -0
  47. package/assets/slopmachine/scaffold-playbooks/platform-family-matrix.md +79 -0
  48. package/assets/slopmachine/scaffold-playbooks/selection-matrix.md +72 -0
  49. package/assets/slopmachine/scaffold-playbooks/spring-boot-default.md +182 -0
  50. package/assets/slopmachine/scaffold-playbooks/tauri-default.md +80 -0
  51. package/assets/slopmachine/scaffold-playbooks/vue-vite-default.md +162 -0
  52. package/assets/slopmachine/scaffold-playbooks/web-default.md +96 -0
  53. package/assets/slopmachine/templates/AGENTS.md +41 -3
  54. package/assets/slopmachine/templates/CLAUDE.md +111 -0
  55. package/assets/slopmachine/utils/claude_create_session.mjs +1 -0
  56. package/assets/slopmachine/utils/claude_live_channel.mjs +188 -0
  57. package/assets/slopmachine/utils/claude_live_common.mjs +406 -0
  58. package/assets/slopmachine/utils/claude_live_hook.py +47 -0
  59. package/assets/slopmachine/utils/claude_live_launch.mjs +181 -0
  60. package/assets/slopmachine/utils/claude_live_status.mjs +25 -0
  61. package/assets/slopmachine/utils/claude_live_stop.mjs +45 -0
  62. package/assets/slopmachine/utils/claude_live_turn.mjs +250 -0
  63. package/assets/slopmachine/utils/claude_resume_session.mjs +1 -0
  64. package/assets/slopmachine/utils/claude_wait_for_rate_limit_reset.mjs +23 -0
  65. package/assets/slopmachine/utils/claude_wait_for_rate_limit_reset.sh +5 -0
  66. package/assets/slopmachine/utils/claude_worker_common.mjs +224 -4
  67. package/assets/slopmachine/utils/cleanup_delivery_artifacts.py +4 -0
  68. package/assets/slopmachine/utils/export_ai_session.mjs +1 -1
  69. package/assets/slopmachine/utils/normalize_claude_session.py +153 -0
  70. package/assets/slopmachine/utils/package_claude_session.mjs +96 -0
  71. package/assets/slopmachine/utils/prepare_strict_audit_workspace.mjs +65 -0
  72. package/package.json +1 -1
  73. package/src/constants.js +42 -3
  74. package/src/init.js +173 -28
  75. package/src/install.js +75 -0
  76. package/src/send-data.js +56 -57
package/src/install.js CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  OPCODE_VERSION,
12
12
  PACKAGE_ROOT,
13
13
  REQUIRED_SKILL_DIRS,
14
+ REQUIRED_SLOPMACHINE_ROOT_FILES,
14
15
  REQUIRED_SLOPMACHINE_FILES,
15
16
  } from './constants.js'
16
17
  import {
@@ -376,6 +377,30 @@ async function tryInstallCoreDependency(name) {
376
377
  winget: ['winget', ['install', '--id', 'Python.Python.3.11', '-e']],
377
378
  choco: ['choco', ['install', 'python', '--version=3.11.7', '-y']],
378
379
  },
380
+ bash: {
381
+ brew: ['brew', ['install', 'bash']],
382
+ apt: ['sudo', ['apt-get', 'install', '-y', 'bash']],
383
+ dnf: ['sudo', ['dnf', 'install', '-y', 'bash']],
384
+ },
385
+ curl: {
386
+ brew: ['brew', ['install', 'curl']],
387
+ apt: ['sudo', ['apt-get', 'install', '-y', 'curl']],
388
+ dnf: ['sudo', ['dnf', 'install', '-y', 'curl']],
389
+ },
390
+ tar: {
391
+ apt: ['sudo', ['apt-get', 'install', '-y', 'tar']],
392
+ dnf: ['sudo', ['dnf', 'install', '-y', 'tar']],
393
+ },
394
+ tmux: {
395
+ brew: ['brew', ['install', 'tmux']],
396
+ apt: ['sudo', ['apt-get', 'install', '-y', 'tmux']],
397
+ dnf: ['sudo', ['dnf', 'install', '-y', 'tmux']],
398
+ },
399
+ zip: {
400
+ brew: ['brew', ['install', 'zip']],
401
+ apt: ['sudo', ['apt-get', 'install', '-y', 'zip']],
402
+ dnf: ['sudo', ['dnf', 'install', '-y', 'zip']],
403
+ },
379
404
  }
380
405
 
381
406
  const platformKey = managers.brew
@@ -587,6 +612,31 @@ async function checkInitShellSupport() {
587
612
  }
588
613
  }
589
614
 
615
+ async function checkClaudeCli() {
616
+ const claudeVersion = await getCommandVersion('claude')
617
+ if (!claudeVersion) {
618
+ warn('Claude CLI is required for the `slopmachine-claude` backend and was not found in PATH. Install Claude Code before using the Claude-backed workflow.')
619
+ return
620
+ }
621
+
622
+ log(`claude detected: ${claudeVersion}`)
623
+ }
624
+
625
+ async function checkArchiveSupport() {
626
+ if (process.platform === 'win32') {
627
+ const shell = await resolveCommand('pwsh') || await resolveCommand('powershell')
628
+ if (!shell) {
629
+ warn('PowerShell is required on Windows for archive packaging paths such as send-data and Claude session packaging.')
630
+ return
631
+ }
632
+
633
+ log(`archive shell detected: ${shell}`)
634
+ return
635
+ }
636
+
637
+ await ensureDependency({ name: 'zip', checkCommand: 'zip', requiredVersion: null, installable: true })
638
+ }
639
+
590
640
  async function findLegacyInstallItems(paths) {
591
641
  const legacyItems = []
592
642
  const scanTargets = [
@@ -744,6 +794,18 @@ async function installSlopmachineAssets(paths) {
744
794
  }
745
795
  }
746
796
 
797
+ for (const relativePath of REQUIRED_SLOPMACHINE_ROOT_FILES) {
798
+ const targetPath = path.join(paths.slopmachineDir, relativePath)
799
+ const result = await copyFileReplacing(path.join(PACKAGE_ROOT, relativePath), targetPath)
800
+ if (result.replaced) {
801
+ log(`Refreshed asset: ${relativePath}`)
802
+ summary.refreshed.push(relativePath)
803
+ } else {
804
+ log(`Installed asset: ${relativePath}`)
805
+ summary.installed.push(relativePath)
806
+ }
807
+ }
808
+
747
809
  return summary
748
810
  }
749
811
 
@@ -880,11 +942,24 @@ export async function runInstall() {
880
942
  log(`Configuring theslopmachine in ${paths.home}`)
881
943
 
882
944
  section('Dependencies')
945
+ await ensureDependency({ name: 'npm', checkCommand: 'npm', requiredVersion: null, installable: false })
883
946
  await ensureDependency({ name: 'git', checkCommand: 'git', installable: true })
884
947
  await ensureDependency({ name: 'python3', checkCommand: 'python3', installable: true })
948
+ if (process.platform !== 'win32') {
949
+ await ensureDependency({ name: 'bash', checkCommand: 'bash', installable: true })
950
+ }
951
+ if (process.platform !== 'win32') {
952
+ await ensureDependency({ name: 'curl', checkCommand: 'curl', installable: true })
953
+ }
954
+ if (process.platform === 'linux') {
955
+ await ensureDependency({ name: 'tar', checkCommand: 'tar', installable: true })
956
+ }
885
957
  await ensureDependency({ name: 'opencode', checkCommand: 'opencode', requiredVersion: OPCODE_VERSION, installable: true })
886
958
  await ensureDependency({ name: 'beads_rust (br)', checkCommand: 'br', requiredVersion: null, installable: true })
887
959
  await checkDocker()
960
+ await ensureDependency({ name: 'tmux', checkCommand: 'tmux', requiredVersion: null, installable: process.platform !== 'win32' })
961
+ await checkClaudeCli()
962
+ await checkArchiveSupport()
888
963
  await checkInitShellSupport()
889
964
 
890
965
  section('Legacy Cleanup')
package/src/send-data.js CHANGED
@@ -23,7 +23,7 @@ const RETROSPECTIVE_FILENAMES = (runId) => [
23
23
  `improvement-actions-${runId}.md`,
24
24
  ]
25
25
 
26
- const SELF_TEST_REPORTS_DIR = 'self_test_reports'
26
+ const EVALUATION_REPORTS_DIR = '.tmp'
27
27
 
28
28
  function sha256Hex(buffer) {
29
29
  return createHash('sha256').update(buffer).digest('hex')
@@ -368,7 +368,7 @@ async function buildManifest(stagingDir, projectId, runId, label, workspaceRoot)
368
368
  project_id: projectId,
369
369
  run_id: runId,
370
370
  label,
371
- bundle_categories: ['sessions', 'self-test', 'retrospective', 'beads', 'metadata'],
371
+ bundle_categories: ['sessions', 'evaluation', 'retrospective', 'beads', 'metadata'],
372
372
  files,
373
373
  file_count: files.length,
374
374
  total_bytes: totalBytes,
@@ -386,67 +386,33 @@ async function buildManifest(stagingDir, projectId, runId, label, workspaceRoot)
386
386
  }
387
387
 
388
388
  async function exportDeveloperSessionArtifacts(session, workspaceRoot, stagingDir) {
389
+ if (session.backend === 'claude' || session.backend === 'claude-live') {
390
+ throw new Error(`Claude-backed sessions should be exported through the shared project archive, not per-session packaging (${session.label})`)
391
+ }
392
+
389
393
  const utilsDir = path.join(buildPaths().slopmachineDir, 'utils')
390
- const exportScript = path.join(utilsDir, 'export_ai_session.mjs')
391
394
  const stripScript = path.join(utilsDir, 'strip_session_parent.py')
392
- const convertScript = path.join(utilsDir, 'convert_exported_ai_session.mjs')
393
- const converterPythonScript = path.join(utilsDir, 'convert_ai_session.py')
394
395
 
395
- const exportNumber = session.order + 1
396
- const rootSessionFile = path.join(stagingDir, `session-${exportNumber}.json`)
397
- const convertedSessionFile = path.join(stagingDir, 'sessions', `${session.label}.json`)
396
+ const cleanedSessionFile = path.join(stagingDir, 'sessions', `${session.label}.json`)
398
397
  const rawSessionFile = path.join(stagingDir, `.session-export-${session.label}.raw`)
399
398
 
400
- await ensureDir(path.dirname(convertedSessionFile))
401
-
402
- if (session.backend === 'claude') {
403
- const exportResult = await runCommand(process.execPath, [
404
- exportScript,
405
- '--backend',
406
- 'claude',
407
- '--cwd',
408
- workspaceRoot,
409
- '--session-id',
410
- session.sessionId,
411
- '--output',
412
- rootSessionFile,
413
- ])
414
-
415
- if (exportResult.code !== 0) {
416
- throw new Error(`Failed to export session ${session.label}: ${(exportResult.stderr || exportResult.stdout).trim()}`)
417
- }
418
- } else {
419
- const opencodeCommand = await resolveCommand('opencode')
420
- if (!opencodeCommand) {
421
- throw new Error('Unable to find `opencode` for developer session export')
422
- }
423
-
424
- await exportOpenCodeSession(opencodeCommand, session.sessionId, rawSessionFile, workspaceRoot)
425
-
426
- const stripResult = await runCommand('python3', [
427
- stripScript,
428
- rawSessionFile,
429
- '--output',
430
- rootSessionFile,
431
- ])
432
-
433
- if (stripResult.code !== 0) {
434
- throw new Error(`Failed to clean exported session ${session.label}: ${(stripResult.stderr || stripResult.stdout).trim()}`)
435
- }
399
+ const opencodeCommand = await resolveCommand('opencode')
400
+ if (!opencodeCommand) {
401
+ throw new Error('Unable to find `opencode` for developer session export')
436
402
  }
437
403
 
438
- const convertResult = await runCommand(process.execPath, [
439
- convertScript,
440
- '--converter-script',
441
- converterPythonScript,
442
- '--input',
443
- rootSessionFile,
404
+ await ensureDir(path.dirname(cleanedSessionFile))
405
+ await exportOpenCodeSession(opencodeCommand, session.sessionId, rawSessionFile, workspaceRoot)
406
+
407
+ const stripResult = await runCommand('python3', [
408
+ stripScript,
409
+ rawSessionFile,
444
410
  '--output',
445
- convertedSessionFile,
411
+ cleanedSessionFile,
446
412
  ])
447
413
 
448
- if (convertResult.code !== 0) {
449
- throw new Error(`Failed to convert exported session ${session.label}: ${(convertResult.stderr || convertResult.stdout).trim()}`)
414
+ if (stripResult.code !== 0) {
415
+ throw new Error(`Failed to clean exported session ${session.label}: ${(stripResult.stderr || stripResult.stdout).trim()}`)
450
416
  }
451
417
 
452
418
  if (await pathExists(rawSessionFile)) {
@@ -454,6 +420,33 @@ async function exportDeveloperSessionArtifacts(session, workspaceRoot, stagingDi
454
420
  }
455
421
  }
456
422
 
423
+ async function exportClaudeProjectArtifacts(claudeSessions, workspaceRoot, stagingDir) {
424
+ if (claudeSessions.length === 0) {
425
+ return
426
+ }
427
+
428
+ const utilsDir = path.join(buildPaths().slopmachineDir, 'utils')
429
+ const packageClaudeSessionScript = path.join(utilsDir, 'package_claude_session.mjs')
430
+ const outputPath = path.join(stagingDir, 'claude-sessions.zip')
431
+ const anchorSession = claudeSessions[0]
432
+
433
+ const packageResult = await runCommand(process.execPath, [
434
+ packageClaudeSessionScript,
435
+ '--cwd',
436
+ workspaceRoot,
437
+ '--session-id',
438
+ anchorSession.sessionId,
439
+ '--label',
440
+ 'claude-sessions',
441
+ '--output',
442
+ outputPath,
443
+ ])
444
+
445
+ if (packageResult.code !== 0) {
446
+ throw new Error(`Failed to package Claude project sessions: ${(packageResult.stderr || packageResult.stdout).trim()}`)
447
+ }
448
+ }
449
+
457
450
  async function createZipArchive(stagingDir, outputPath) {
458
451
  await ensureDir(path.dirname(outputPath))
459
452
 
@@ -550,15 +543,21 @@ async function stageSendDataBundle({ workspaceRoot, repoPath, ownerSessionId, de
550
543
 
551
544
  await exportOpenCodeSession(opencodeCommand, ownerSessionId, path.join(stagingDir, 'owner-session.json'), workspaceRoot)
552
545
 
553
- for (const session of developerSessions.sort((left, right) => left.order - right.order)) {
546
+ const sortedSessions = developerSessions.sort((left, right) => left.order - right.order)
547
+ const claudeSessions = sortedSessions.filter((session) => session.backend === 'claude' || session.backend === 'claude-live')
548
+ const nonClaudeSessions = sortedSessions.filter((session) => session.backend !== 'claude' && session.backend !== 'claude-live')
549
+
550
+ for (const session of nonClaudeSessions) {
554
551
  await exportDeveloperSessionArtifacts(session, workspaceRoot, stagingDir)
555
552
  }
556
553
 
554
+ await exportClaudeProjectArtifacts(claudeSessions, workspaceRoot, stagingDir)
555
+
557
556
  await copyOptionalPath(
558
- path.join(workspaceRoot, SELF_TEST_REPORTS_DIR),
559
- path.join(stagingDir, SELF_TEST_REPORTS_DIR),
557
+ path.join(workspaceRoot, EVALUATION_REPORTS_DIR),
558
+ path.join(stagingDir, EVALUATION_REPORTS_DIR),
560
559
  warnings,
561
- SELF_TEST_REPORTS_DIR,
560
+ EVALUATION_REPORTS_DIR,
562
561
  )
563
562
 
564
563
  const retrospectiveDir = path.join(buildPaths().slopmachineDir, 'retrospectives')