maxsimcli 4.8.0 → 4.10.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 (162) hide show
  1. package/README.md +180 -202
  2. package/dist/assets/CHANGELOG.md +61 -0
  3. package/dist/assets/hooks/maxsim-check-update.cjs +38 -0
  4. package/dist/assets/hooks/maxsim-check-update.cjs.map +1 -1
  5. package/dist/assets/hooks/maxsim-statusline.cjs +116 -48
  6. package/dist/assets/hooks/maxsim-statusline.cjs.map +1 -1
  7. package/dist/assets/hooks/maxsim-sync-reminder.cjs +117 -0
  8. package/dist/assets/hooks/maxsim-sync-reminder.cjs.map +1 -0
  9. package/dist/assets/templates/agents/AGENTS.md +78 -106
  10. package/dist/assets/templates/agents/executor.md +101 -0
  11. package/dist/assets/templates/agents/planner.md +86 -0
  12. package/dist/assets/templates/agents/researcher.md +71 -0
  13. package/dist/assets/templates/agents/verifier.md +88 -0
  14. package/dist/assets/templates/commands/maxsim/debug.md +7 -7
  15. package/dist/assets/templates/commands/maxsim/execute.md +45 -0
  16. package/dist/assets/templates/commands/maxsim/go.md +29 -0
  17. package/dist/assets/templates/commands/maxsim/help.md +2 -2
  18. package/dist/assets/templates/commands/maxsim/init.md +52 -0
  19. package/dist/assets/templates/commands/maxsim/plan.md +50 -0
  20. package/dist/assets/templates/commands/maxsim/progress.md +4 -3
  21. package/dist/assets/templates/commands/maxsim/quick.md +6 -4
  22. package/dist/assets/templates/commands/maxsim/settings.md +4 -3
  23. package/dist/assets/templates/references/continuation-format.md +16 -16
  24. package/dist/assets/templates/references/model-profile-resolution.md +1 -1
  25. package/dist/assets/templates/references/model-profiles.md +12 -19
  26. package/dist/assets/templates/rules/conventions.md +51 -0
  27. package/dist/assets/templates/rules/verification-protocol.md +57 -0
  28. package/dist/assets/templates/skills/agent-system-map/SKILL.md +92 -0
  29. package/dist/assets/templates/skills/brainstorming/SKILL.md +48 -36
  30. package/dist/assets/templates/skills/code-review/SKILL.md +40 -61
  31. package/dist/assets/templates/skills/commit-conventions/SKILL.md +75 -0
  32. package/dist/assets/templates/skills/evidence-collection/SKILL.md +87 -0
  33. package/dist/assets/templates/skills/handoff-contract/SKILL.md +70 -0
  34. package/dist/assets/templates/skills/input-validation/SKILL.md +51 -0
  35. package/dist/assets/templates/skills/maxsim-batch/SKILL.md +41 -45
  36. package/dist/assets/templates/skills/maxsim-simplify/SKILL.md +37 -90
  37. package/dist/assets/templates/skills/memory-management/SKILL.md +32 -67
  38. package/dist/assets/templates/skills/research-methodology/SKILL.md +137 -0
  39. package/dist/assets/templates/skills/roadmap-writing/SKILL.md +40 -58
  40. package/dist/assets/templates/skills/sdd/SKILL.md +34 -69
  41. package/dist/assets/templates/skills/systematic-debugging/SKILL.md +20 -26
  42. package/dist/assets/templates/skills/tdd/SKILL.md +25 -33
  43. package/dist/assets/templates/skills/tool-priority-guide/SKILL.md +80 -0
  44. package/dist/assets/templates/skills/using-maxsim/SKILL.md +42 -73
  45. package/dist/assets/templates/skills/verification-before-completion/SKILL.md +12 -24
  46. package/dist/assets/templates/skills/verification-gates/SKILL.md +169 -0
  47. package/dist/assets/templates/templates/UAT.md +3 -3
  48. package/dist/assets/templates/templates/VALIDATION.md +1 -1
  49. package/dist/assets/templates/templates/context.md +4 -4
  50. package/dist/assets/templates/templates/debug-subagent-prompt.md +3 -3
  51. package/dist/assets/templates/templates/discovery.md +2 -2
  52. package/dist/assets/templates/templates/phase-prompt.md +2 -2
  53. package/dist/assets/templates/templates/planner-subagent-prompt.md +7 -7
  54. package/dist/assets/templates/templates/project.md +1 -1
  55. package/dist/assets/templates/templates/research.md +1 -1
  56. package/dist/assets/templates/templates/state.md +2 -2
  57. package/dist/assets/templates/templates/summary.md +41 -0
  58. package/dist/assets/templates/workflows/batch.md +5 -5
  59. package/dist/assets/templates/workflows/diagnose-issues.md +2 -2
  60. package/dist/assets/templates/workflows/discovery-phase.md +3 -3
  61. package/dist/assets/templates/workflows/discuss-phase.md +11 -11
  62. package/dist/assets/templates/workflows/execute-phase.md +205 -11
  63. package/dist/assets/templates/workflows/execute-plan.md +299 -34
  64. package/dist/assets/templates/workflows/execute.md +421 -0
  65. package/dist/assets/templates/workflows/go.md +250 -0
  66. package/dist/assets/templates/workflows/health.md +5 -5
  67. package/dist/assets/templates/workflows/help.md +165 -435
  68. package/dist/assets/templates/workflows/init-existing.md +23 -23
  69. package/dist/assets/templates/workflows/init.md +205 -0
  70. package/dist/assets/templates/workflows/new-milestone.md +9 -9
  71. package/dist/assets/templates/workflows/new-project.md +26 -26
  72. package/dist/assets/templates/workflows/plan-create.md +298 -0
  73. package/dist/assets/templates/workflows/plan-discuss.md +347 -0
  74. package/dist/assets/templates/workflows/plan-phase.md +29 -29
  75. package/dist/assets/templates/workflows/plan-research.md +177 -0
  76. package/dist/assets/templates/workflows/plan.md +231 -0
  77. package/dist/assets/templates/workflows/progress.md +46 -42
  78. package/dist/assets/templates/workflows/quick.md +195 -14
  79. package/dist/assets/templates/workflows/research-phase.md +5 -5
  80. package/dist/assets/templates/workflows/sdd.md +20 -12
  81. package/dist/assets/templates/workflows/settings.md +18 -14
  82. package/dist/assets/templates/workflows/verify-phase.md +1 -1
  83. package/dist/assets/templates/workflows/verify-work.md +16 -16
  84. package/dist/cli.cjs +4589 -229
  85. package/dist/cli.cjs.map +1 -1
  86. package/dist/core-D5zUr9cb.cjs.map +1 -1
  87. package/dist/install.cjs +234 -17
  88. package/dist/install.cjs.map +1 -1
  89. package/dist/mcp-server.cjs +298 -20
  90. package/dist/mcp-server.cjs.map +1 -1
  91. package/dist/skills-CjFWZIGM.cjs.map +1 -1
  92. package/package.json +1 -1
  93. package/dist/assets/hooks/maxsim-context-monitor.cjs +0 -121
  94. package/dist/assets/hooks/maxsim-context-monitor.cjs.map +0 -1
  95. package/dist/assets/templates/agents/maxsim-code-reviewer.md +0 -239
  96. package/dist/assets/templates/agents/maxsim-codebase-mapper.md +0 -214
  97. package/dist/assets/templates/agents/maxsim-debugger.md +0 -572
  98. package/dist/assets/templates/agents/maxsim-drift-checker.md +0 -522
  99. package/dist/assets/templates/agents/maxsim-executor.md +0 -504
  100. package/dist/assets/templates/agents/maxsim-integration-checker.md +0 -273
  101. package/dist/assets/templates/agents/maxsim-phase-researcher.md +0 -305
  102. package/dist/assets/templates/agents/maxsim-plan-checker.md +0 -343
  103. package/dist/assets/templates/agents/maxsim-planner.md +0 -610
  104. package/dist/assets/templates/agents/maxsim-project-researcher.md +0 -359
  105. package/dist/assets/templates/agents/maxsim-research-synthesizer.md +0 -263
  106. package/dist/assets/templates/agents/maxsim-roadmapper.md +0 -324
  107. package/dist/assets/templates/agents/maxsim-spec-reviewer.md +0 -245
  108. package/dist/assets/templates/agents/maxsim-verifier.md +0 -393
  109. package/dist/assets/templates/commands/maxsim/add-phase.md +0 -43
  110. package/dist/assets/templates/commands/maxsim/add-tests.md +0 -41
  111. package/dist/assets/templates/commands/maxsim/add-todo.md +0 -57
  112. package/dist/assets/templates/commands/maxsim/artefakte.md +0 -122
  113. package/dist/assets/templates/commands/maxsim/audit-milestone.md +0 -36
  114. package/dist/assets/templates/commands/maxsim/batch.md +0 -42
  115. package/dist/assets/templates/commands/maxsim/check-drift.md +0 -56
  116. package/dist/assets/templates/commands/maxsim/check-todos.md +0 -46
  117. package/dist/assets/templates/commands/maxsim/cleanup.md +0 -18
  118. package/dist/assets/templates/commands/maxsim/complete-milestone.md +0 -136
  119. package/dist/assets/templates/commands/maxsim/discuss-phase.md +0 -87
  120. package/dist/assets/templates/commands/maxsim/discuss.md +0 -70
  121. package/dist/assets/templates/commands/maxsim/execute-phase.md +0 -41
  122. package/dist/assets/templates/commands/maxsim/health.md +0 -22
  123. package/dist/assets/templates/commands/maxsim/init-existing.md +0 -46
  124. package/dist/assets/templates/commands/maxsim/insert-phase.md +0 -32
  125. package/dist/assets/templates/commands/maxsim/list-phase-assumptions.md +0 -46
  126. package/dist/assets/templates/commands/maxsim/map-codebase.md +0 -71
  127. package/dist/assets/templates/commands/maxsim/new-milestone.md +0 -44
  128. package/dist/assets/templates/commands/maxsim/new-project.md +0 -46
  129. package/dist/assets/templates/commands/maxsim/pause-work.md +0 -38
  130. package/dist/assets/templates/commands/maxsim/plan-milestone-gaps.md +0 -34
  131. package/dist/assets/templates/commands/maxsim/plan-phase.md +0 -44
  132. package/dist/assets/templates/commands/maxsim/realign.md +0 -39
  133. package/dist/assets/templates/commands/maxsim/reapply-patches.md +0 -110
  134. package/dist/assets/templates/commands/maxsim/remove-phase.md +0 -31
  135. package/dist/assets/templates/commands/maxsim/research-phase.md +0 -189
  136. package/dist/assets/templates/commands/maxsim/resume-work.md +0 -40
  137. package/dist/assets/templates/commands/maxsim/roadmap.md +0 -19
  138. package/dist/assets/templates/commands/maxsim/sdd.md +0 -39
  139. package/dist/assets/templates/commands/maxsim/set-profile.md +0 -34
  140. package/dist/assets/templates/commands/maxsim/update.md +0 -37
  141. package/dist/assets/templates/commands/maxsim/verify-work.md +0 -38
  142. package/dist/assets/templates/workflows/add-phase.md +0 -111
  143. package/dist/assets/templates/workflows/add-tests.md +0 -351
  144. package/dist/assets/templates/workflows/add-todo.md +0 -247
  145. package/dist/assets/templates/workflows/audit-milestone.md +0 -297
  146. package/dist/assets/templates/workflows/check-drift.md +0 -248
  147. package/dist/assets/templates/workflows/check-todos.md +0 -261
  148. package/dist/assets/templates/workflows/cleanup.md +0 -153
  149. package/dist/assets/templates/workflows/complete-milestone.md +0 -701
  150. package/dist/assets/templates/workflows/discuss.md +0 -343
  151. package/dist/assets/templates/workflows/insert-phase.md +0 -129
  152. package/dist/assets/templates/workflows/list-phase-assumptions.md +0 -178
  153. package/dist/assets/templates/workflows/map-codebase.md +0 -315
  154. package/dist/assets/templates/workflows/pause-work.md +0 -122
  155. package/dist/assets/templates/workflows/plan-milestone-gaps.md +0 -274
  156. package/dist/assets/templates/workflows/realign.md +0 -288
  157. package/dist/assets/templates/workflows/remove-phase.md +0 -154
  158. package/dist/assets/templates/workflows/resume-project.md +0 -306
  159. package/dist/assets/templates/workflows/roadmap.md +0 -130
  160. package/dist/assets/templates/workflows/set-profile.md +0 -81
  161. package/dist/assets/templates/workflows/transition.md +0 -544
  162. package/dist/assets/templates/workflows/update.md +0 -220
@@ -89,6 +89,43 @@ function checkForUpdate(options) {
89
89
  detached: true
90
90
  }).unref();
91
91
  }
92
+ /**
93
+ * Create a backup of the current MAXSIM installation before an update.
94
+ * Called by the installer (not by the SessionStart hook).
95
+ *
96
+ * @param cwd - The project working directory containing .claude/
97
+ * @returns The backup directory path on success, null on failure.
98
+ */
99
+ function createBackupBeforeUpdate(cwd) {
100
+ try {
101
+ const sourceDir = node_path.join(cwd, CLAUDE_DIR);
102
+ const backupDir = node_path.join(sourceDir, "maxsim-backup");
103
+ node_fs.mkdirSync(backupDir, { recursive: true });
104
+ for (const relDir of [
105
+ "commands/maxsim",
106
+ "maxsim",
107
+ "hooks",
108
+ "agents",
109
+ "skills"
110
+ ]) {
111
+ const src = node_path.join(sourceDir, relDir);
112
+ if (!node_fs.existsSync(src)) continue;
113
+ const dest = node_path.join(backupDir, relDir);
114
+ node_fs.mkdirSync(node_path.dirname(dest), { recursive: true });
115
+ node_fs.cpSync(src, dest, { recursive: true });
116
+ }
117
+ let version = "unknown";
118
+ const versionFile = node_path.join(sourceDir, "maxsim", "VERSION");
119
+ if (node_fs.existsSync(versionFile)) version = node_fs.readFileSync(versionFile, "utf8").trim();
120
+ node_fs.writeFileSync(node_path.join(backupDir, "backup-meta.json"), JSON.stringify({
121
+ created: (/* @__PURE__ */ new Date()).toISOString(),
122
+ version
123
+ }, null, 2));
124
+ return backupDir;
125
+ } catch {
126
+ return null;
127
+ }
128
+ }
92
129
  if (require.main === module) checkForUpdate({
93
130
  homeDir: node_os.homedir(),
94
131
  cwd: process.cwd()
@@ -96,4 +133,5 @@ if (require.main === module) checkForUpdate({
96
133
 
97
134
  //#endregion
98
135
  exports.checkForUpdate = checkForUpdate;
136
+ exports.createBackupBeforeUpdate = createBackupBeforeUpdate;
99
137
  //# sourceMappingURL=maxsim-check-update.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"maxsim-check-update.cjs","names":["path","fs","os"],"sources":["../../../src/hooks/shared.ts","../../../src/hooks/maxsim-check-update.ts"],"sourcesContent":["/**\n * Shared utilities for MAXSIM hooks.\n */\n\n/**\n * Read all stdin as a string, then invoke callback with parsed JSON.\n * Used by context-monitor and statusline hooks.\n */\nexport function readStdinJson<T>(callback: (data: T) => void): void {\n let input = '';\n process.stdin.setEncoding('utf8');\n process.stdin.on('data', (chunk: string) => (input += chunk));\n process.stdin.on('end', () => {\n try {\n const data = JSON.parse(input) as T;\n callback(data);\n } catch {\n // Silent fail -- never block hook execution\n process.exit(0);\n }\n });\n}\n\n/** The '.claude' path segment -- template marker replaced during install. */\nexport const CLAUDE_DIR = '.claude';\n","#!/usr/bin/env node\n/**\n * Check for MAXSIM updates in background, write result to cache.\n * Called by SessionStart hook - runs once per session.\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport * as os from 'node:os';\nimport { spawn } from 'node:child_process';\nimport { CLAUDE_DIR } from './shared';\n\nexport interface UpdateCheckResult {\n update_available: boolean;\n installed: string;\n latest: string;\n checked: number;\n}\n\nexport interface CheckForUpdateOptions {\n homeDir: string;\n cwd: string;\n}\n\nexport function checkForUpdate(options: CheckForUpdateOptions): void {\n const { homeDir, cwd } = options;\n const cacheDir = path.join(homeDir, CLAUDE_DIR, 'cache');\n const cacheFile = path.join(cacheDir, 'maxsim-update-check.json');\n\n // VERSION file locations (check project first, then global)\n const projectVersionFile = path.join(cwd, CLAUDE_DIR, 'maxsim', 'VERSION');\n const globalVersionFile = path.join(homeDir, CLAUDE_DIR, 'maxsim', 'VERSION');\n\n // Ensure cache directory exists\n if (!fs.existsSync(cacheDir)) {\n fs.mkdirSync(cacheDir, { recursive: true });\n }\n\n // Run check in background (spawn background process, windowsHide prevents console flash)\n const child = spawn(process.execPath, ['-e', `\n const fs = require('fs');\n const { execSync } = require('child_process');\n\n const cacheFile = ${JSON.stringify(cacheFile)};\n const projectVersionFile = ${JSON.stringify(projectVersionFile)};\n const globalVersionFile = ${JSON.stringify(globalVersionFile)};\n\n // Check project directory first (local install), then global\n let installed = '0.0.0';\n try {\n if (fs.existsSync(projectVersionFile)) {\n installed = fs.readFileSync(projectVersionFile, 'utf8').trim();\n } else if (fs.existsSync(globalVersionFile)) {\n installed = fs.readFileSync(globalVersionFile, 'utf8').trim();\n }\n } catch (e) {}\n\n let latest = null;\n try {\n latest = execSync('npm view maxsimcli version', { encoding: 'utf8', timeout: 10000, windowsHide: true }).trim();\n } catch (e) {}\n\n const result = {\n update_available: latest && installed !== latest,\n installed,\n latest: latest || 'unknown',\n checked: Math.floor(Date.now() / 1000)\n };\n\n fs.writeFileSync(cacheFile, JSON.stringify(result));\n`], {\n stdio: 'ignore',\n windowsHide: true,\n detached: true,\n });\n\n child.unref();\n}\n\n// Standalone entry\nif (require.main === module) {\n checkForUpdate({ homeDir: os.homedir(), cwd: process.cwd() });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwBA,MAAa,aAAa;;;;;;;;ACA1B,SAAgB,eAAe,SAAsC;CACnE,MAAM,EAAE,SAAS,QAAQ;CACzB,MAAM,WAAWA,UAAK,KAAK,SAAS,YAAY,QAAQ;CACxD,MAAM,YAAYA,UAAK,KAAK,UAAU,2BAA2B;CAGjE,MAAM,qBAAqBA,UAAK,KAAK,KAAK,YAAY,UAAU,UAAU;CAC1E,MAAM,oBAAoBA,UAAK,KAAK,SAAS,YAAY,UAAU,UAAU;AAG7E,KAAI,CAACC,QAAG,WAAW,SAAS,CAC1B,SAAG,UAAU,UAAU,EAAE,WAAW,MAAM,CAAC;AAyC7C,+BArCoB,QAAQ,UAAU,CAAC,MAAM;;;;sBAIzB,KAAK,UAAU,UAAU,CAAC;+BACjB,KAAK,UAAU,mBAAmB,CAAC;8BACpC,KAAK,UAAU,kBAAkB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;EAyB9D,EAAE;EACA,OAAO;EACP,aAAa;EACb,UAAU;EACX,CAAC,CAEI,OAAO;;AAIf,IAAI,QAAQ,SAAS,OACnB,gBAAe;CAAE,SAASC,QAAG,SAAS;CAAE,KAAK,QAAQ,KAAK;CAAE,CAAC"}
1
+ {"version":3,"file":"maxsim-check-update.cjs","names":["path","fs","os"],"sources":["../../../src/hooks/shared.ts","../../../src/hooks/maxsim-check-update.ts"],"sourcesContent":["/**\n * Shared utilities for MAXSIM hooks.\n */\n\n/**\n * Read all stdin as a string, then invoke callback with parsed JSON.\n * Used by statusline and sync-reminder hooks.\n */\nexport function readStdinJson<T>(callback: (data: T) => void): void {\n let input = '';\n process.stdin.setEncoding('utf8');\n process.stdin.on('data', (chunk: string) => (input += chunk));\n process.stdin.on('end', () => {\n try {\n const data = JSON.parse(input) as T;\n callback(data);\n } catch {\n // Silent fail -- never block hook execution\n process.exit(0);\n }\n });\n}\n\n/** The '.claude' path segment -- template marker replaced during install. */\nexport const CLAUDE_DIR = '.claude';\n","#!/usr/bin/env node\n/**\n * Check for MAXSIM updates in background, write result to cache.\n * Called by SessionStart hook - runs once per session.\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport * as os from 'node:os';\nimport { spawn } from 'node:child_process';\nimport { CLAUDE_DIR } from './shared';\n\nexport interface UpdateCheckResult {\n update_available: boolean;\n installed: string;\n latest: string;\n checked: number;\n}\n\nexport interface CheckForUpdateOptions {\n homeDir: string;\n cwd: string;\n}\n\nexport function checkForUpdate(options: CheckForUpdateOptions): void {\n const { homeDir, cwd } = options;\n const cacheDir = path.join(homeDir, CLAUDE_DIR, 'cache');\n const cacheFile = path.join(cacheDir, 'maxsim-update-check.json');\n\n // VERSION file locations (check project first, then global)\n const projectVersionFile = path.join(cwd, CLAUDE_DIR, 'maxsim', 'VERSION');\n const globalVersionFile = path.join(homeDir, CLAUDE_DIR, 'maxsim', 'VERSION');\n\n // Ensure cache directory exists\n if (!fs.existsSync(cacheDir)) {\n fs.mkdirSync(cacheDir, { recursive: true });\n }\n\n // Run check in background (spawn background process, windowsHide prevents console flash)\n const child = spawn(process.execPath, ['-e', `\n const fs = require('fs');\n const { execSync } = require('child_process');\n\n const cacheFile = ${JSON.stringify(cacheFile)};\n const projectVersionFile = ${JSON.stringify(projectVersionFile)};\n const globalVersionFile = ${JSON.stringify(globalVersionFile)};\n\n // Check project directory first (local install), then global\n let installed = '0.0.0';\n try {\n if (fs.existsSync(projectVersionFile)) {\n installed = fs.readFileSync(projectVersionFile, 'utf8').trim();\n } else if (fs.existsSync(globalVersionFile)) {\n installed = fs.readFileSync(globalVersionFile, 'utf8').trim();\n }\n } catch (e) {}\n\n let latest = null;\n try {\n latest = execSync('npm view maxsimcli version', { encoding: 'utf8', timeout: 10000, windowsHide: true }).trim();\n } catch (e) {}\n\n const result = {\n update_available: latest && installed !== latest,\n installed,\n latest: latest || 'unknown',\n checked: Math.floor(Date.now() / 1000)\n };\n\n fs.writeFileSync(cacheFile, JSON.stringify(result));\n`], {\n stdio: 'ignore',\n windowsHide: true,\n detached: true,\n });\n\n child.unref();\n}\n\n/**\n * Create a backup of the current MAXSIM installation before an update.\n * Called by the installer (not by the SessionStart hook).\n *\n * @param cwd - The project working directory containing .claude/\n * @returns The backup directory path on success, null on failure.\n */\nexport function createBackupBeforeUpdate(cwd: string): string | null {\n try {\n const sourceDir = path.join(cwd, CLAUDE_DIR);\n const backupDir = path.join(sourceDir, 'maxsim-backup');\n\n fs.mkdirSync(backupDir, { recursive: true });\n\n // Key directories to back up\n const dirsToBackup = [\n 'commands/maxsim',\n 'maxsim',\n 'hooks',\n 'agents',\n 'skills',\n ];\n\n for (const relDir of dirsToBackup) {\n const src = path.join(sourceDir, relDir);\n if (!fs.existsSync(src)) continue;\n\n const dest = path.join(backupDir, relDir);\n fs.mkdirSync(path.dirname(dest), { recursive: true });\n fs.cpSync(src, dest, { recursive: true });\n }\n\n // Write backup metadata\n let version = 'unknown';\n const versionFile = path.join(sourceDir, 'maxsim', 'VERSION');\n if (fs.existsSync(versionFile)) {\n version = fs.readFileSync(versionFile, 'utf8').trim();\n }\n\n fs.writeFileSync(\n path.join(backupDir, 'backup-meta.json'),\n JSON.stringify(\n { created: new Date().toISOString(), version },\n null,\n 2,\n ),\n );\n\n return backupDir;\n } catch {\n // Backup failure should not block the update\n return null;\n }\n}\n\n// Standalone entry\nif (require.main === module) {\n checkForUpdate({ homeDir: os.homedir(), cwd: process.cwd() });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwBA,MAAa,aAAa;;;;;;;;ACA1B,SAAgB,eAAe,SAAsC;CACnE,MAAM,EAAE,SAAS,QAAQ;CACzB,MAAM,WAAWA,UAAK,KAAK,SAAS,YAAY,QAAQ;CACxD,MAAM,YAAYA,UAAK,KAAK,UAAU,2BAA2B;CAGjE,MAAM,qBAAqBA,UAAK,KAAK,KAAK,YAAY,UAAU,UAAU;CAC1E,MAAM,oBAAoBA,UAAK,KAAK,SAAS,YAAY,UAAU,UAAU;AAG7E,KAAI,CAACC,QAAG,WAAW,SAAS,CAC1B,SAAG,UAAU,UAAU,EAAE,WAAW,MAAM,CAAC;AAyC7C,+BArCoB,QAAQ,UAAU,CAAC,MAAM;;;;sBAIzB,KAAK,UAAU,UAAU,CAAC;+BACjB,KAAK,UAAU,mBAAmB,CAAC;8BACpC,KAAK,UAAU,kBAAkB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;EAyB9D,EAAE;EACA,OAAO;EACP,aAAa;EACb,UAAU;EACX,CAAC,CAEI,OAAO;;;;;;;;;AAUf,SAAgB,yBAAyB,KAA4B;AACnE,KAAI;EACF,MAAM,YAAYD,UAAK,KAAK,KAAK,WAAW;EAC5C,MAAM,YAAYA,UAAK,KAAK,WAAW,gBAAgB;AAEvD,UAAG,UAAU,WAAW,EAAE,WAAW,MAAM,CAAC;AAW5C,OAAK,MAAM,UARU;GACnB;GACA;GACA;GACA;GACA;GACD,EAEkC;GACjC,MAAM,MAAMA,UAAK,KAAK,WAAW,OAAO;AACxC,OAAI,CAACC,QAAG,WAAW,IAAI,CAAE;GAEzB,MAAM,OAAOD,UAAK,KAAK,WAAW,OAAO;AACzC,WAAG,UAAUA,UAAK,QAAQ,KAAK,EAAE,EAAE,WAAW,MAAM,CAAC;AACrD,WAAG,OAAO,KAAK,MAAM,EAAE,WAAW,MAAM,CAAC;;EAI3C,IAAI,UAAU;EACd,MAAM,cAAcA,UAAK,KAAK,WAAW,UAAU,UAAU;AAC7D,MAAIC,QAAG,WAAW,YAAY,CAC5B,WAAUA,QAAG,aAAa,aAAa,OAAO,CAAC,MAAM;AAGvD,UAAG,cACDD,UAAK,KAAK,WAAW,mBAAmB,EACxC,KAAK,UACH;GAAE,0BAAS,IAAI,MAAM,EAAC,aAAa;GAAE;GAAS,EAC9C,MACA,EACD,CACF;AAED,SAAO;SACD;AAEN,SAAO;;;AAKX,IAAI,QAAQ,SAAS,OACnB,gBAAe;CAAE,SAASE,QAAG,SAAS;CAAE,KAAK,QAAQ,KAAK;CAAE,CAAC"}
@@ -31,8 +31,7 @@ let node_fs = require("node:fs");
31
31
  node_fs = __toESM(node_fs);
32
32
  let node_path = require("node:path");
33
33
  node_path = __toESM(node_path);
34
- let node_os = require("node:os");
35
- node_os = __toESM(node_os);
34
+ let node_child_process = require("node:child_process");
36
35
 
37
36
  //#region src/hooks/shared.ts
38
37
  /**
@@ -40,7 +39,7 @@ node_os = __toESM(node_os);
40
39
  */
41
40
  /**
42
41
  * Read all stdin as a string, then invoke callback with parsed JSON.
43
- * Used by context-monitor and statusline hooks.
42
+ * Used by statusline and sync-reminder hooks.
44
43
  */
45
44
  function readStdinJson(callback) {
46
45
  let input = "";
@@ -61,56 +60,125 @@ const CLAUDE_DIR = ".claude";
61
60
  //#region src/hooks/maxsim-statusline.ts
62
61
  /**
63
62
  * Claude Code Statusline - MAXSIM Edition
64
- * Shows: model | current task | directory | context usage
63
+ * Shows: [update] model | P{N} | v{M}: {pct}% | dirname
65
64
  */
65
+ const CACHE_TTL_SECONDS = 60;
66
+ /**
67
+ * Spawn a detached Node child process to refresh the progress cache in the background.
68
+ * The child runs gh CLI commands to detect owner/repo, find the first open milestone,
69
+ * compute progress, and find the current phase label.
70
+ */
71
+ function spawnBackgroundRefresh(cacheDir, cacheFile) {
72
+ try {
73
+ const script = `
74
+ const { execSync } = require('child_process');
75
+ const fs = require('fs');
76
+ const path = require('path');
77
+
78
+ try {
79
+ // Detect owner/repo
80
+ const nameWithOwner = execSync('gh repo view --json nameWithOwner -q .nameWithOwner', {
81
+ encoding: 'utf8',
82
+ timeout: 10000,
83
+ stdio: ['pipe', 'pipe', 'pipe'],
84
+ }).trim();
85
+
86
+ if (!nameWithOwner || !nameWithOwner.includes('/')) {
87
+ process.exit(0);
88
+ }
89
+
90
+ const [owner, repo] = nameWithOwner.split('/');
91
+
92
+ // Get milestones
93
+ let milestoneTitle = null;
94
+ let milestonePct = 0;
95
+ try {
96
+ const milestonesRaw = execSync(
97
+ 'gh api repos/' + owner + '/' + repo + '/milestones --jq "."',
98
+ { encoding: 'utf8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'] }
99
+ ).trim();
100
+ if (milestonesRaw) {
101
+ const milestones = JSON.parse(milestonesRaw);
102
+ const openMilestone = milestones.find(function(m) { return m.state === 'open'; });
103
+ if (openMilestone) {
104
+ milestoneTitle = openMilestone.title || null;
105
+ const total = (openMilestone.open_issues || 0) + (openMilestone.closed_issues || 0);
106
+ if (total > 0) {
107
+ milestonePct = Math.round(((openMilestone.closed_issues || 0) / total) * 100);
108
+ }
109
+ }
110
+ }
111
+ } catch (e) {
112
+ // gh api failed for milestones, continue with defaults
113
+ }
114
+
115
+ // Get current phase from labels on open issues
116
+ let phaseNumber = null;
117
+ try {
118
+ const phaseRaw = execSync(
119
+ 'gh api "repos/' + owner + '/' + repo + '/issues?state=open&labels=phase:&per_page=1&sort=created&direction=desc" --jq ".[0].labels[] | select(.name | startswith(\\"phase:\\")) | .name"',
120
+ { encoding: 'utf8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'] }
121
+ ).trim();
122
+ if (phaseRaw && phaseRaw.startsWith('phase:')) {
123
+ phaseNumber = phaseRaw.replace('phase:', '').trim();
124
+ }
125
+ } catch (e) {
126
+ // gh api failed for phase, continue with null
127
+ }
128
+
129
+ // Write cache
130
+ const cacheData = JSON.stringify({
131
+ phase_number: phaseNumber,
132
+ milestone_title: milestoneTitle,
133
+ milestone_pct: milestonePct,
134
+ updated: Math.floor(Date.now() / 1000),
135
+ });
136
+
137
+ const dir = ${JSON.stringify(cacheDir)};
138
+ fs.mkdirSync(dir, { recursive: true });
139
+ fs.writeFileSync(${JSON.stringify(cacheFile)}, cacheData);
140
+ } catch (e) {
141
+ // Silently degrade if gh not available
142
+ process.exit(0);
143
+ }
144
+ `;
145
+ (0, node_child_process.spawn)(process.execPath, ["-e", script], {
146
+ stdio: "ignore",
147
+ windowsHide: true,
148
+ detached: true
149
+ }).unref();
150
+ } catch {}
151
+ }
66
152
  function formatStatusline(data) {
67
153
  const model = data.model?.display_name || "Claude";
68
- const dir = data.workspace?.current_dir || process.cwd();
69
- const session = data.session_id || "";
70
- const remaining = data.context_window?.remaining_percentage;
71
- let ctx = "";
72
- if (remaining != null) {
73
- const rem = Math.round(remaining);
74
- const rawUsed = Math.max(0, Math.min(100, 100 - rem));
75
- const used = Math.min(100, Math.round(rawUsed / 80 * 100));
76
- if (session) try {
77
- const bridgePath = node_path.join(node_os.tmpdir(), `claude-ctx-${session}.json`);
78
- const bridgeData = JSON.stringify({
79
- session_id: session,
80
- remaining_percentage: remaining,
81
- used_pct: used,
82
- timestamp: Math.floor(Date.now() / 1e3)
83
- });
84
- node_fs.writeFileSync(bridgePath, bridgeData);
85
- } catch {}
86
- const filled = Math.floor(used / 10);
87
- const bar = "█".repeat(filled) + "░".repeat(10 - filled);
88
- if (used < 63) ctx = ` \x1b[32m${bar} ${used}%\x1b[0m`;
89
- else if (used < 81) ctx = ` \x1b[33m${bar} ${used}%\x1b[0m`;
90
- else if (used < 95) ctx = ` \x1b[38;5;208m${bar} ${used}%\x1b[0m`;
91
- else ctx = ` \x1b[5;31m\uD83D\uDC80 ${bar} ${used}%\x1b[0m`;
92
- }
93
- let task = "";
94
- const homeDir = node_os.homedir();
95
- const todosDir = node_path.join(homeDir, CLAUDE_DIR, "todos");
96
- if (session && node_fs.existsSync(todosDir)) try {
97
- const files = node_fs.readdirSync(todosDir).filter((f) => f.startsWith(session) && f.includes("-agent-") && f.endsWith(".json")).map((f) => ({
98
- name: f,
99
- mtime: node_fs.statSync(node_path.join(todosDir, f)).mtime
100
- })).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
101
- if (files.length > 0) try {
102
- const inProgress = JSON.parse(node_fs.readFileSync(node_path.join(todosDir, files[0].name), "utf8")).find((t) => t.status === "in_progress");
103
- if (inProgress) task = inProgress.activeForm || "";
104
- } catch {}
154
+ const dir = data.workspace?.project_dir || data.workspace?.current_dir || process.cwd();
155
+ const dirname = node_path.basename(dir);
156
+ const SEP = " │ ";
157
+ const DIM = "\x1B[2m";
158
+ const RESET = "\x1B[0m";
159
+ let updateIndicator = "";
160
+ const updateCacheFile = node_path.join(dir, CLAUDE_DIR, "cache", "maxsim-update-check.json");
161
+ if (node_fs.existsSync(updateCacheFile)) try {
162
+ if (JSON.parse(node_fs.readFileSync(updateCacheFile, "utf8")).update_available) updateIndicator = "\x1B[33m⬆\x1B[0m ";
105
163
  } catch {}
106
- let maxsimUpdate = "";
107
- const cacheFile = node_path.join(homeDir, CLAUDE_DIR, "cache", "maxsim-update-check.json");
164
+ const planningDir = node_path.join(dir, ".planning");
165
+ if (!node_fs.existsSync(planningDir)) return `${updateIndicator}${DIM}${model}${RESET}${SEP}${DIM}${dirname}${RESET}`;
166
+ const cacheDir = node_path.join(dir, CLAUDE_DIR, "cache");
167
+ const cacheFile = node_path.join(cacheDir, "maxsim-progress.json");
168
+ let cache = null;
169
+ let cacheAge = Infinity;
108
170
  if (node_fs.existsSync(cacheFile)) try {
109
- if (JSON.parse(node_fs.readFileSync(cacheFile, "utf8")).update_available) maxsimUpdate = "\x1B[33m⬆ /maxsim:update\x1B[0m │ ";
110
- } catch {}
111
- const dirname = node_path.basename(dir);
112
- if (task) return `${maxsimUpdate}\x1b[2m${model}\x1b[0m \u2502 \x1b[1m${task}\x1b[0m \u2502 \x1b[2m${dirname}\x1b[0m${ctx}`;
113
- else return `${maxsimUpdate}\x1b[2m${model}\x1b[0m \u2502 \x1b[2m${dirname}\x1b[0m${ctx}`;
171
+ cache = JSON.parse(node_fs.readFileSync(cacheFile, "utf8"));
172
+ cacheAge = Math.floor(Date.now() / 1e3) - (cache.updated || 0);
173
+ } catch {
174
+ cache = null;
175
+ }
176
+ if (cacheAge > CACHE_TTL_SECONDS) spawnBackgroundRefresh(cacheDir, cacheFile);
177
+ let phaseSegment = "";
178
+ if (cache?.phase_number) phaseSegment = `${SEP}${DIM}P${cache.phase_number}${RESET}`;
179
+ let milestoneSegment = "";
180
+ if (cache?.milestone_title) milestoneSegment = `${SEP}${DIM}${cache.milestone_title}: ${cache.milestone_pct}%${RESET}`;
181
+ return `${updateIndicator}${DIM}${model}${RESET}${phaseSegment}${milestoneSegment}${SEP}${DIM}${dirname}${RESET}`;
114
182
  }
115
183
  if (require.main === module) readStdinJson((data) => {
116
184
  process.stdout.write(formatStatusline(data));
@@ -1 +1 @@
1
- {"version":3,"file":"maxsim-statusline.cjs","names":["path","os","fs"],"sources":["../../../src/hooks/shared.ts","../../../src/hooks/maxsim-statusline.ts"],"sourcesContent":["/**\n * Shared utilities for MAXSIM hooks.\n */\n\n/**\n * Read all stdin as a string, then invoke callback with parsed JSON.\n * Used by context-monitor and statusline hooks.\n */\nexport function readStdinJson<T>(callback: (data: T) => void): void {\n let input = '';\n process.stdin.setEncoding('utf8');\n process.stdin.on('data', (chunk: string) => (input += chunk));\n process.stdin.on('end', () => {\n try {\n const data = JSON.parse(input) as T;\n callback(data);\n } catch {\n // Silent fail -- never block hook execution\n process.exit(0);\n }\n });\n}\n\n/** The '.claude' path segment -- template marker replaced during install. */\nexport const CLAUDE_DIR = '.claude';\n","#!/usr/bin/env node\n/**\n * Claude Code Statusline - MAXSIM Edition\n * Shows: model | current task | directory | context usage\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport * as os from 'node:os';\nimport { readStdinJson, CLAUDE_DIR } from './shared';\n\nexport interface StatuslineInput {\n model?: { display_name?: string };\n workspace?: { current_dir?: string };\n session_id?: string;\n context_window?: { remaining_percentage?: number };\n}\n\nexport function formatStatusline(data: StatuslineInput): string {\n const model = data.model?.display_name || 'Claude';\n const dir = data.workspace?.current_dir || process.cwd();\n const session = data.session_id || '';\n const remaining = data.context_window?.remaining_percentage;\n\n // Context window display (shows USED percentage scaled to 80% limit)\n let ctx = '';\n if (remaining != null) {\n const rem = Math.round(remaining);\n const rawUsed = Math.max(0, Math.min(100, 100 - rem));\n // Scale: 80% real usage = 100% displayed\n const used = Math.min(100, Math.round((rawUsed / 80) * 100));\n\n // Write context metrics to bridge file for the context-monitor PostToolUse hook.\n if (session) {\n try {\n const bridgePath = path.join(os.tmpdir(), `claude-ctx-${session}.json`);\n const bridgeData = JSON.stringify({\n session_id: session,\n remaining_percentage: remaining,\n used_pct: used,\n timestamp: Math.floor(Date.now() / 1000),\n });\n fs.writeFileSync(bridgePath, bridgeData);\n } catch {\n // Silent fail -- bridge is best-effort, don't break statusline\n }\n }\n\n // Build progress bar (10 segments)\n const filled = Math.floor(used / 10);\n const bar = '\\u2588'.repeat(filled) + '\\u2591'.repeat(10 - filled);\n\n // Color based on scaled usage\n if (used < 63) {\n ctx = ` \\x1b[32m${bar} ${used}%\\x1b[0m`;\n } else if (used < 81) {\n ctx = ` \\x1b[33m${bar} ${used}%\\x1b[0m`;\n } else if (used < 95) {\n ctx = ` \\x1b[38;5;208m${bar} ${used}%\\x1b[0m`;\n } else {\n ctx = ` \\x1b[5;31m\\uD83D\\uDC80 ${bar} ${used}%\\x1b[0m`;\n }\n }\n\n // Current task from todos\n let task = '';\n const homeDir = os.homedir();\n const todosDir = path.join(homeDir, CLAUDE_DIR, 'todos');\n if (session && fs.existsSync(todosDir)) {\n try {\n const files = fs.readdirSync(todosDir)\n .filter((f: string) => f.startsWith(session) && f.includes('-agent-') && f.endsWith('.json'))\n .map((f: string) => ({ name: f, mtime: fs.statSync(path.join(todosDir, f)).mtime }))\n .sort((a: { mtime: Date }, b: { mtime: Date }) => b.mtime.getTime() - a.mtime.getTime());\n\n if (files.length > 0) {\n try {\n const todos = JSON.parse(fs.readFileSync(path.join(todosDir, files[0].name), 'utf8'));\n const inProgress = todos.find((t: { status: string; activeForm?: string }) => t.status === 'in_progress');\n if (inProgress) task = inProgress.activeForm || '';\n } catch {\n // ignore\n }\n }\n } catch {\n // Silently fail on file system errors - don't break statusline\n }\n }\n\n // MAXSIM update available?\n let maxsimUpdate = '';\n const cacheFile = path.join(homeDir, CLAUDE_DIR, 'cache', 'maxsim-update-check.json');\n if (fs.existsSync(cacheFile)) {\n try {\n const cache = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));\n if (cache.update_available) {\n maxsimUpdate = '\\x1b[33m\\u2B06 /maxsim:update\\x1b[0m \\u2502 ';\n }\n } catch {\n // ignore\n }\n }\n\n // Output\n const dirname = path.basename(dir);\n if (task) {\n return `${maxsimUpdate}\\x1b[2m${model}\\x1b[0m \\u2502 \\x1b[1m${task}\\x1b[0m \\u2502 \\x1b[2m${dirname}\\x1b[0m${ctx}`;\n } else {\n return `${maxsimUpdate}\\x1b[2m${model}\\x1b[0m \\u2502 \\x1b[2m${dirname}\\x1b[0m${ctx}`;\n }\n}\n\n// Standalone entry\nif (require.main === module) {\n readStdinJson<StatuslineInput>((data) => {\n process.stdout.write(formatStatusline(data));\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAQA,SAAgB,cAAiB,UAAmC;CAClE,IAAI,QAAQ;AACZ,SAAQ,MAAM,YAAY,OAAO;AACjC,SAAQ,MAAM,GAAG,SAAS,UAAmB,SAAS,MAAO;AAC7D,SAAQ,MAAM,GAAG,aAAa;AAC5B,MAAI;AAEF,YADa,KAAK,MAAM,MAAM,CAChB;UACR;AAEN,WAAQ,KAAK,EAAE;;GAEjB;;;AAIJ,MAAa,aAAa;;;;;;;;ACN1B,SAAgB,iBAAiB,MAA+B;CAC9D,MAAM,QAAQ,KAAK,OAAO,gBAAgB;CAC1C,MAAM,MAAM,KAAK,WAAW,eAAe,QAAQ,KAAK;CACxD,MAAM,UAAU,KAAK,cAAc;CACnC,MAAM,YAAY,KAAK,gBAAgB;CAGvC,IAAI,MAAM;AACV,KAAI,aAAa,MAAM;EACrB,MAAM,MAAM,KAAK,MAAM,UAAU;EACjC,MAAM,UAAU,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;EAErD,MAAM,OAAO,KAAK,IAAI,KAAK,KAAK,MAAO,UAAU,KAAM,IAAI,CAAC;AAG5D,MAAI,QACF,KAAI;GACF,MAAM,aAAaA,UAAK,KAAKC,QAAG,QAAQ,EAAE,cAAc,QAAQ,OAAO;GACvE,MAAM,aAAa,KAAK,UAAU;IAChC,YAAY;IACZ,sBAAsB;IACtB,UAAU;IACV,WAAW,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;IACzC,CAAC;AACF,WAAG,cAAc,YAAY,WAAW;UAClC;EAMV,MAAM,SAAS,KAAK,MAAM,OAAO,GAAG;EACpC,MAAM,MAAM,IAAS,OAAO,OAAO,GAAG,IAAS,OAAO,KAAK,OAAO;AAGlE,MAAI,OAAO,GACT,OAAM,YAAY,IAAI,GAAG,KAAK;WACrB,OAAO,GAChB,OAAM,YAAY,IAAI,GAAG,KAAK;WACrB,OAAO,GAChB,OAAM,kBAAkB,IAAI,GAAG,KAAK;MAEpC,OAAM,2BAA2B,IAAI,GAAG,KAAK;;CAKjD,IAAI,OAAO;CACX,MAAM,UAAUA,QAAG,SAAS;CAC5B,MAAM,WAAWD,UAAK,KAAK,SAAS,YAAY,QAAQ;AACxD,KAAI,WAAWE,QAAG,WAAW,SAAS,CACpC,KAAI;EACF,MAAM,QAAQA,QAAG,YAAY,SAAS,CACnC,QAAQ,MAAc,EAAE,WAAW,QAAQ,IAAI,EAAE,SAAS,UAAU,IAAI,EAAE,SAAS,QAAQ,CAAC,CAC5F,KAAK,OAAe;GAAE,MAAM;GAAG,OAAOA,QAAG,SAASF,UAAK,KAAK,UAAU,EAAE,CAAC,CAAC;GAAO,EAAE,CACnF,MAAM,GAAoB,MAAuB,EAAE,MAAM,SAAS,GAAG,EAAE,MAAM,SAAS,CAAC;AAE1F,MAAI,MAAM,SAAS,EACjB,KAAI;GAEF,MAAM,aADQ,KAAK,MAAME,QAAG,aAAaF,UAAK,KAAK,UAAU,MAAM,GAAG,KAAK,EAAE,OAAO,CAAC,CAC5D,MAAM,MAA+C,EAAE,WAAW,cAAc;AACzG,OAAI,WAAY,QAAO,WAAW,cAAc;UAC1C;SAIJ;CAMV,IAAI,eAAe;CACnB,MAAM,YAAYA,UAAK,KAAK,SAAS,YAAY,SAAS,2BAA2B;AACrF,KAAIE,QAAG,WAAW,UAAU,CAC1B,KAAI;AAEF,MADc,KAAK,MAAMA,QAAG,aAAa,WAAW,OAAO,CAAC,CAClD,iBACR,gBAAe;SAEX;CAMV,MAAM,UAAUF,UAAK,SAAS,IAAI;AAClC,KAAI,KACF,QAAO,GAAG,aAAa,SAAS,MAAM,wBAAwB,KAAK,wBAAwB,QAAQ,SAAS;KAE5G,QAAO,GAAG,aAAa,SAAS,MAAM,wBAAwB,QAAQ,SAAS;;AAKnF,IAAI,QAAQ,SAAS,OACnB,gBAAgC,SAAS;AACvC,SAAQ,OAAO,MAAM,iBAAiB,KAAK,CAAC;EAC5C"}
1
+ {"version":3,"file":"maxsim-statusline.cjs","names":["path","fs"],"sources":["../../../src/hooks/shared.ts","../../../src/hooks/maxsim-statusline.ts"],"sourcesContent":["/**\n * Shared utilities for MAXSIM hooks.\n */\n\n/**\n * Read all stdin as a string, then invoke callback with parsed JSON.\n * Used by statusline and sync-reminder hooks.\n */\nexport function readStdinJson<T>(callback: (data: T) => void): void {\n let input = '';\n process.stdin.setEncoding('utf8');\n process.stdin.on('data', (chunk: string) => (input += chunk));\n process.stdin.on('end', () => {\n try {\n const data = JSON.parse(input) as T;\n callback(data);\n } catch {\n // Silent fail -- never block hook execution\n process.exit(0);\n }\n });\n}\n\n/** The '.claude' path segment -- template marker replaced during install. */\nexport const CLAUDE_DIR = '.claude';\n","#!/usr/bin/env node\n/**\n * Claude Code Statusline - MAXSIM Edition\n * Shows: [update] model | P{N} | v{M}: {pct}% | dirname\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { spawn } from 'node:child_process';\nimport { readStdinJson, CLAUDE_DIR } from './shared';\n\nexport interface StatuslineInput {\n model?: { display_name?: string };\n workspace?: { current_dir?: string; project_dir?: string };\n session_id?: string;\n}\n\nexport interface ProgressCache {\n phase_number: string | null;\n milestone_title: string | null;\n milestone_pct: number;\n updated: number;\n}\n\nconst CACHE_TTL_SECONDS = 60;\n\n/**\n * Spawn a detached Node child process to refresh the progress cache in the background.\n * The child runs gh CLI commands to detect owner/repo, find the first open milestone,\n * compute progress, and find the current phase label.\n */\nfunction spawnBackgroundRefresh(cacheDir: string, cacheFile: string): void {\n try {\n const script = `\nconst { execSync } = require('child_process');\nconst fs = require('fs');\nconst path = require('path');\n\ntry {\n // Detect owner/repo\n const nameWithOwner = execSync('gh repo view --json nameWithOwner -q .nameWithOwner', {\n encoding: 'utf8',\n timeout: 10000,\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n if (!nameWithOwner || !nameWithOwner.includes('/')) {\n process.exit(0);\n }\n\n const [owner, repo] = nameWithOwner.split('/');\n\n // Get milestones\n let milestoneTitle = null;\n let milestonePct = 0;\n try {\n const milestonesRaw = execSync(\n 'gh api repos/' + owner + '/' + repo + '/milestones --jq \".\"',\n { encoding: 'utf8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'] }\n ).trim();\n if (milestonesRaw) {\n const milestones = JSON.parse(milestonesRaw);\n const openMilestone = milestones.find(function(m) { return m.state === 'open'; });\n if (openMilestone) {\n milestoneTitle = openMilestone.title || null;\n const total = (openMilestone.open_issues || 0) + (openMilestone.closed_issues || 0);\n if (total > 0) {\n milestonePct = Math.round(((openMilestone.closed_issues || 0) / total) * 100);\n }\n }\n }\n } catch (e) {\n // gh api failed for milestones, continue with defaults\n }\n\n // Get current phase from labels on open issues\n let phaseNumber = null;\n try {\n const phaseRaw = execSync(\n 'gh api \"repos/' + owner + '/' + repo + '/issues?state=open&labels=phase:&per_page=1&sort=created&direction=desc\" --jq \".[0].labels[] | select(.name | startswith(\\\\\"phase:\\\\\")) | .name\"',\n { encoding: 'utf8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'] }\n ).trim();\n if (phaseRaw && phaseRaw.startsWith('phase:')) {\n phaseNumber = phaseRaw.replace('phase:', '').trim();\n }\n } catch (e) {\n // gh api failed for phase, continue with null\n }\n\n // Write cache\n const cacheData = JSON.stringify({\n phase_number: phaseNumber,\n milestone_title: milestoneTitle,\n milestone_pct: milestonePct,\n updated: Math.floor(Date.now() / 1000),\n });\n\n const dir = ${JSON.stringify(cacheDir)};\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(${JSON.stringify(cacheFile)}, cacheData);\n} catch (e) {\n // Silently degrade if gh not available\n process.exit(0);\n}\n`;\n\n const child = spawn(process.execPath, ['-e', script], {\n stdio: 'ignore',\n windowsHide: true,\n detached: true,\n });\n child.unref();\n } catch {\n // Silent fail -- never break statusline\n }\n}\n\nexport function formatStatusline(data: StatuslineInput): string {\n const model = data.model?.display_name || 'Claude';\n const dir = data.workspace?.project_dir || data.workspace?.current_dir || process.cwd();\n const dirname = path.basename(dir);\n\n const SEP = ' \\u2502 ';\n const DIM = '\\x1b[2m';\n const RESET = '\\x1b[0m';\n\n // MAXSIM update available?\n let updateIndicator = '';\n const updateCacheFile = path.join(dir, CLAUDE_DIR, 'cache', 'maxsim-update-check.json');\n if (fs.existsSync(updateCacheFile)) {\n try {\n const cache = JSON.parse(fs.readFileSync(updateCacheFile, 'utf8'));\n if (cache.update_available) {\n updateIndicator = '\\x1b[33m\\u2B06\\x1b[0m ';\n }\n } catch {\n // ignore\n }\n }\n\n // Check if this is a MAXSIM project\n const planningDir = path.join(dir, '.planning');\n const isMaxsimProject = fs.existsSync(planningDir);\n\n if (!isMaxsimProject) {\n return `${updateIndicator}${DIM}${model}${RESET}${SEP}${DIM}${dirname}${RESET}`;\n }\n\n // Read progress cache\n const cacheDir = path.join(dir, CLAUDE_DIR, 'cache');\n const cacheFile = path.join(cacheDir, 'maxsim-progress.json');\n let cache: ProgressCache | null = null;\n let cacheAge = Infinity;\n\n if (fs.existsSync(cacheFile)) {\n try {\n cache = JSON.parse(fs.readFileSync(cacheFile, 'utf8')) as ProgressCache;\n cacheAge = Math.floor(Date.now() / 1000) - (cache.updated || 0);\n } catch {\n cache = null;\n }\n }\n\n // Spawn background refresh if cache is stale or missing\n if (cacheAge > CACHE_TTL_SECONDS) {\n spawnBackgroundRefresh(cacheDir, cacheFile);\n }\n\n // Build phase segment\n let phaseSegment = '';\n if (cache?.phase_number) {\n phaseSegment = `${SEP}${DIM}P${cache.phase_number}${RESET}`;\n }\n\n // Build milestone segment\n let milestoneSegment = '';\n if (cache?.milestone_title) {\n milestoneSegment = `${SEP}${DIM}${cache.milestone_title}: ${cache.milestone_pct}%${RESET}`;\n }\n\n return `${updateIndicator}${DIM}${model}${RESET}${phaseSegment}${milestoneSegment}${SEP}${DIM}${dirname}${RESET}`;\n}\n\n// Standalone entry\nif (require.main === module) {\n readStdinJson<StatuslineInput>((data) => {\n process.stdout.write(formatStatusline(data));\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAQA,SAAgB,cAAiB,UAAmC;CAClE,IAAI,QAAQ;AACZ,SAAQ,MAAM,YAAY,OAAO;AACjC,SAAQ,MAAM,GAAG,SAAS,UAAmB,SAAS,MAAO;AAC7D,SAAQ,MAAM,GAAG,aAAa;AAC5B,MAAI;AAEF,YADa,KAAK,MAAM,MAAM,CAChB;UACR;AAEN,WAAQ,KAAK,EAAE;;GAEjB;;;AAIJ,MAAa,aAAa;;;;;;;;ACA1B,MAAM,oBAAoB;;;;;;AAO1B,SAAS,uBAAuB,UAAkB,WAAyB;AACzE,KAAI;EACF,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAgEH,KAAK,UAAU,SAAS,CAAC;;qBAEpB,KAAK,UAAU,UAAU,CAAC;;;;;;AAY3C,gCALoB,QAAQ,UAAU,CAAC,MAAM,OAAO,EAAE;GACpD,OAAO;GACP,aAAa;GACb,UAAU;GACX,CAAC,CACI,OAAO;SACP;;AAKV,SAAgB,iBAAiB,MAA+B;CAC9D,MAAM,QAAQ,KAAK,OAAO,gBAAgB;CAC1C,MAAM,MAAM,KAAK,WAAW,eAAe,KAAK,WAAW,eAAe,QAAQ,KAAK;CACvF,MAAM,UAAUA,UAAK,SAAS,IAAI;CAElC,MAAM,MAAM;CACZ,MAAM,MAAM;CACZ,MAAM,QAAQ;CAGd,IAAI,kBAAkB;CACtB,MAAM,kBAAkBA,UAAK,KAAK,KAAK,YAAY,SAAS,2BAA2B;AACvF,KAAIC,QAAG,WAAW,gBAAgB,CAChC,KAAI;AAEF,MADc,KAAK,MAAMA,QAAG,aAAa,iBAAiB,OAAO,CAAC,CACxD,iBACR,mBAAkB;SAEd;CAMV,MAAM,cAAcD,UAAK,KAAK,KAAK,YAAY;AAG/C,KAAI,CAFoBC,QAAG,WAAW,YAAY,CAGhD,QAAO,GAAG,kBAAkB,MAAM,QAAQ,QAAQ,MAAM,MAAM,UAAU;CAI1E,MAAM,WAAWD,UAAK,KAAK,KAAK,YAAY,QAAQ;CACpD,MAAM,YAAYA,UAAK,KAAK,UAAU,uBAAuB;CAC7D,IAAI,QAA8B;CAClC,IAAI,WAAW;AAEf,KAAIC,QAAG,WAAW,UAAU,CAC1B,KAAI;AACF,UAAQ,KAAK,MAAMA,QAAG,aAAa,WAAW,OAAO,CAAC;AACtD,aAAW,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK,IAAI,MAAM,WAAW;SACvD;AACN,UAAQ;;AAKZ,KAAI,WAAW,kBACb,wBAAuB,UAAU,UAAU;CAI7C,IAAI,eAAe;AACnB,KAAI,OAAO,aACT,gBAAe,GAAG,MAAM,IAAI,GAAG,MAAM,eAAe;CAItD,IAAI,mBAAmB;AACvB,KAAI,OAAO,gBACT,oBAAmB,GAAG,MAAM,MAAM,MAAM,gBAAgB,IAAI,MAAM,cAAc,GAAG;AAGrF,QAAO,GAAG,kBAAkB,MAAM,QAAQ,QAAQ,eAAe,mBAAmB,MAAM,MAAM,UAAU;;AAI5G,IAAI,QAAQ,SAAS,OACnB,gBAAgC,SAAS;AACvC,SAAQ,OAAO,MAAM,iBAAiB,KAAK,CAAC;EAC5C"}
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
3
+ //#region \0rolldown/runtime.js
4
+ var __create = Object.create;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getProtoOf = Object.getPrototypeOf;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
13
+ key = keys[i];
14
+ if (!__hasOwnProp.call(to, key) && key !== except) {
15
+ __defProp(to, key, {
16
+ get: ((k) => from[k]).bind(null, key),
17
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
18
+ });
19
+ }
20
+ }
21
+ }
22
+ return to;
23
+ };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
25
+ value: mod,
26
+ enumerable: true
27
+ }) : target, mod));
28
+
29
+ //#endregion
30
+ let node_fs = require("node:fs");
31
+ node_fs = __toESM(node_fs);
32
+ let node_os = require("node:os");
33
+ node_os = __toESM(node_os);
34
+ let node_path = require("node:path");
35
+ node_path = __toESM(node_path);
36
+
37
+ //#region src/hooks/shared.ts
38
+ /**
39
+ * Shared utilities for MAXSIM hooks.
40
+ */
41
+ /**
42
+ * Read all stdin as a string, then invoke callback with parsed JSON.
43
+ * Used by statusline and sync-reminder hooks.
44
+ */
45
+ function readStdinJson(callback) {
46
+ let input = "";
47
+ process.stdin.setEncoding("utf8");
48
+ process.stdin.on("data", (chunk) => input += chunk);
49
+ process.stdin.on("end", () => {
50
+ try {
51
+ callback(JSON.parse(input));
52
+ } catch {
53
+ process.exit(0);
54
+ }
55
+ });
56
+ }
57
+
58
+ //#endregion
59
+ //#region src/hooks/maxsim-sync-reminder.ts
60
+ /**
61
+ * Sync Reminder Hook — PostToolUse hook that detects .planning/ file writes
62
+ * and gently reminds the user to sync changes to GitHub Issues.
63
+ *
64
+ * Debounces reminders: fires on the first .planning/ write per session,
65
+ * then every DEBOUNCE_CALLS writes thereafter.
66
+ */
67
+ /** Number of .planning/ writes between repeated reminders. */
68
+ const DEBOUNCE_CALLS = 10;
69
+ const REMINDER_MESSAGE = ".planning/ files changed locally. Consider syncing to GitHub Issues when ready.";
70
+ function processSyncReminder(data) {
71
+ const sessionId = data.session_id;
72
+ const filePath = data.tool_input?.file_path;
73
+ if (!sessionId || !filePath) return null;
74
+ const normalized = node_path.normalize(filePath);
75
+ const planningSegment = `${node_path.sep}.planning${node_path.sep}`;
76
+ const planningEnd = `${node_path.sep}.planning`;
77
+ if (!normalized.includes(planningSegment) && !normalized.endsWith(planningEnd)) return null;
78
+ const stateFile = node_path.join(node_os.tmpdir(), `maxsim-sync-${sessionId}.json`);
79
+ let state;
80
+ try {
81
+ if (node_fs.existsSync(stateFile)) state = JSON.parse(node_fs.readFileSync(stateFile, "utf8"));
82
+ else state = {
83
+ callsSinceRemind: 0,
84
+ reminded: false
85
+ };
86
+ } catch {
87
+ state = {
88
+ callsSinceRemind: 0,
89
+ reminded: false
90
+ };
91
+ }
92
+ state.callsSinceRemind++;
93
+ if (!state.reminded || state.callsSinceRemind >= DEBOUNCE_CALLS) {
94
+ state.callsSinceRemind = 0;
95
+ state.reminded = true;
96
+ try {
97
+ node_fs.writeFileSync(stateFile, JSON.stringify(state));
98
+ } catch {}
99
+ return { hookSpecificOutput: {
100
+ hookEventName: "PostToolUse",
101
+ additionalContext: REMINDER_MESSAGE
102
+ } };
103
+ }
104
+ try {
105
+ node_fs.writeFileSync(stateFile, JSON.stringify(state));
106
+ } catch {}
107
+ return null;
108
+ }
109
+ if (require.main === module) readStdinJson((data) => {
110
+ const result = processSyncReminder(data);
111
+ if (result) process.stdout.write(JSON.stringify(result));
112
+ });
113
+
114
+ //#endregion
115
+ exports.DEBOUNCE_CALLS = DEBOUNCE_CALLS;
116
+ exports.processSyncReminder = processSyncReminder;
117
+ //# sourceMappingURL=maxsim-sync-reminder.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"maxsim-sync-reminder.cjs","names":["path","os","fs"],"sources":["../../../src/hooks/shared.ts","../../../src/hooks/maxsim-sync-reminder.ts"],"sourcesContent":["/**\n * Shared utilities for MAXSIM hooks.\n */\n\n/**\n * Read all stdin as a string, then invoke callback with parsed JSON.\n * Used by statusline and sync-reminder hooks.\n */\nexport function readStdinJson<T>(callback: (data: T) => void): void {\n let input = '';\n process.stdin.setEncoding('utf8');\n process.stdin.on('data', (chunk: string) => (input += chunk));\n process.stdin.on('end', () => {\n try {\n const data = JSON.parse(input) as T;\n callback(data);\n } catch {\n // Silent fail -- never block hook execution\n process.exit(0);\n }\n });\n}\n\n/** The '.claude' path segment -- template marker replaced during install. */\nexport const CLAUDE_DIR = '.claude';\n","#!/usr/bin/env node\n/**\n * Sync Reminder Hook — PostToolUse hook that detects .planning/ file writes\n * and gently reminds the user to sync changes to GitHub Issues.\n *\n * Debounces reminders: fires on the first .planning/ write per session,\n * then every DEBOUNCE_CALLS writes thereafter.\n */\n\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { readStdinJson } from './shared';\n\nexport interface SyncReminderInput {\n session_id?: string;\n cwd?: string;\n tool_input?: { file_path?: string };\n}\n\nexport interface SyncReminderOutput {\n hookSpecificOutput: {\n hookEventName: string;\n additionalContext: string;\n };\n}\n\ninterface DebounceState {\n callsSinceRemind: number;\n reminded: boolean;\n}\n\n/** Number of .planning/ writes between repeated reminders. */\nexport const DEBOUNCE_CALLS = 10;\n\nconst REMINDER_MESSAGE =\n '.planning/ files changed locally. Consider syncing to GitHub Issues when ready.';\n\nexport function processSyncReminder(\n data: SyncReminderInput,\n): SyncReminderOutput | null {\n const sessionId = data.session_id;\n const filePath = data.tool_input?.file_path;\n\n if (!sessionId || !filePath) {\n return null;\n }\n\n // Normalize path for cross-platform (Windows backslash handling)\n const normalized = path.normalize(filePath);\n\n // Check if the file is inside a .planning/ directory\n const planningSegment = `${path.sep}.planning${path.sep}`;\n const planningEnd = `${path.sep}.planning`;\n if (\n !normalized.includes(planningSegment) &&\n !normalized.endsWith(planningEnd)\n ) {\n return null;\n }\n\n // Load debounce state from temp file\n const stateFile = path.join(\n os.tmpdir(),\n `maxsim-sync-${sessionId}.json`,\n );\n\n let state: DebounceState;\n try {\n if (fs.existsSync(stateFile)) {\n state = JSON.parse(fs.readFileSync(stateFile, 'utf8')) as DebounceState;\n } else {\n state = { callsSinceRemind: 0, reminded: false };\n }\n } catch {\n state = { callsSinceRemind: 0, reminded: false };\n }\n\n state.callsSinceRemind++;\n\n // Fire reminder on first write OR after debounce interval expires\n if (!state.reminded || state.callsSinceRemind >= DEBOUNCE_CALLS) {\n state.callsSinceRemind = 0;\n state.reminded = true;\n\n try {\n fs.writeFileSync(stateFile, JSON.stringify(state));\n } catch {\n // Silent fail -- never block hook execution\n }\n\n return {\n hookSpecificOutput: {\n hookEventName: 'PostToolUse',\n additionalContext: REMINDER_MESSAGE,\n },\n };\n }\n\n // Not time for a reminder yet\n try {\n fs.writeFileSync(stateFile, JSON.stringify(state));\n } catch {\n // Silent fail -- never block hook execution\n }\n\n return null;\n}\n\n// Standalone entry\nif (require.main === module) {\n readStdinJson<SyncReminderInput>((data) => {\n const result = processSyncReminder(data);\n if (result) {\n process.stdout.write(JSON.stringify(result));\n }\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAQA,SAAgB,cAAiB,UAAmC;CAClE,IAAI,QAAQ;AACZ,SAAQ,MAAM,YAAY,OAAO;AACjC,SAAQ,MAAM,GAAG,SAAS,UAAmB,SAAS,MAAO;AAC7D,SAAQ,MAAM,GAAG,aAAa;AAC5B,MAAI;AAEF,YADa,KAAK,MAAM,MAAM,CAChB;UACR;AAEN,WAAQ,KAAK,EAAE;;GAEjB;;;;;;;;;;;;;ACaJ,MAAa,iBAAiB;AAE9B,MAAM,mBACJ;AAEF,SAAgB,oBACd,MAC2B;CAC3B,MAAM,YAAY,KAAK;CACvB,MAAM,WAAW,KAAK,YAAY;AAElC,KAAI,CAAC,aAAa,CAAC,SACjB,QAAO;CAIT,MAAM,aAAaA,UAAK,UAAU,SAAS;CAG3C,MAAM,kBAAkB,GAAGA,UAAK,IAAI,WAAWA,UAAK;CACpD,MAAM,cAAc,GAAGA,UAAK,IAAI;AAChC,KACE,CAAC,WAAW,SAAS,gBAAgB,IACrC,CAAC,WAAW,SAAS,YAAY,CAEjC,QAAO;CAIT,MAAM,YAAYA,UAAK,KACrBC,QAAG,QAAQ,EACX,eAAe,UAAU,OAC1B;CAED,IAAI;AACJ,KAAI;AACF,MAAIC,QAAG,WAAW,UAAU,CAC1B,SAAQ,KAAK,MAAMA,QAAG,aAAa,WAAW,OAAO,CAAC;MAEtD,SAAQ;GAAE,kBAAkB;GAAG,UAAU;GAAO;SAE5C;AACN,UAAQ;GAAE,kBAAkB;GAAG,UAAU;GAAO;;AAGlD,OAAM;AAGN,KAAI,CAAC,MAAM,YAAY,MAAM,oBAAoB,gBAAgB;AAC/D,QAAM,mBAAmB;AACzB,QAAM,WAAW;AAEjB,MAAI;AACF,WAAG,cAAc,WAAW,KAAK,UAAU,MAAM,CAAC;UAC5C;AAIR,SAAO,EACL,oBAAoB;GAClB,eAAe;GACf,mBAAmB;GACpB,EACF;;AAIH,KAAI;AACF,UAAG,cAAc,WAAW,KAAK,UAAU,MAAM,CAAC;SAC5C;AAIR,QAAO;;AAIT,IAAI,QAAQ,SAAS,OACnB,gBAAkC,SAAS;CACzC,MAAM,SAAS,oBAAoB,KAAK;AACxC,KAAI,OACF,SAAQ,OAAO,MAAM,KAAK,UAAU,OAAO,CAAC;EAE9C"}