sinapse-ai 9.4.0 → 9.5.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 (266) hide show
  1. package/.claude/CLAUDE.md +10 -4
  2. package/.claude/hooks/enforce-architecture-first.py +197 -197
  3. package/.claude/hooks/enforce-git-push-authority.sh +25 -4
  4. package/.claude/hooks/mind-clone-governance.py +193 -193
  5. package/.claude/hooks/read-protection.py +152 -152
  6. package/.claude/hooks/sql-governance.py +183 -183
  7. package/.claude/hooks/verify-packages.cjs +83 -0
  8. package/.claude/hooks/write-path-validation.py +195 -195
  9. package/.claude/rules/hook-governance.md +1 -0
  10. package/.claude/rules/mandatory-delegation.md +24 -0
  11. package/.claude/rules/project-intelligence.md +63 -0
  12. package/.claude/rules/response-format.md +4 -0
  13. package/.claude/rules/safe-collaboration.md +4 -2
  14. package/.claude/rules/security-data-protection.md +18 -0
  15. package/.claude/rules/squad-awareness.md +93 -67
  16. package/.claude/rules/token-economy.md +148 -0
  17. package/.codex/agents/analyst.md +90 -0
  18. package/.codex/agents/architect.md +78 -0
  19. package/.codex/agents/data-engineer.md +38 -0
  20. package/.codex/agents/developer.md +97 -0
  21. package/.codex/agents/devops.md +121 -0
  22. package/.codex/agents/product-lead.md +27 -0
  23. package/.codex/agents/project-lead.md +28 -0
  24. package/.codex/agents/quality-gate.md +89 -0
  25. package/.codex/agents/sprint-lead.md +28 -0
  26. package/.codex/agents/squad-creator.md +58 -0
  27. package/.codex/agents/ux-design-expert.md +28 -0
  28. package/.sinapse-ai/core/code-intel/registry-syncer.js +56 -3
  29. package/.sinapse-ai/core/doctor/checks/agent-memory.js +5 -1
  30. package/.sinapse-ai/core/doctor/checks/claude-md.js +4 -1
  31. package/.sinapse-ai/core/doctor/checks/code-intel.js +5 -1
  32. package/.sinapse-ai/core/doctor/checks/commands-count.js +4 -1
  33. package/.sinapse-ai/core/doctor/checks/constitution-consistency.js +4 -1
  34. package/.sinapse-ai/core/doctor/checks/core-config.js +4 -1
  35. package/.sinapse-ai/core/doctor/checks/entity-registry.js +6 -1
  36. package/.sinapse-ai/core/doctor/checks/git-hooks.js +5 -1
  37. package/.sinapse-ai/core/doctor/checks/graph-dashboard.js +4 -1
  38. package/.sinapse-ai/core/doctor/checks/hooks-claude-count.js +5 -1
  39. package/.sinapse-ai/core/doctor/checks/ide-sync.js +4 -1
  40. package/.sinapse-ai/core/doctor/checks/node-version.js +4 -1
  41. package/.sinapse-ai/core/doctor/checks/npm-packages.js +4 -1
  42. package/.sinapse-ai/core/doctor/checks/rules-files.js +4 -1
  43. package/.sinapse-ai/core/doctor/checks/settings-json.js +4 -1
  44. package/.sinapse-ai/core/doctor/checks/skills-count.js +4 -1
  45. package/.sinapse-ai/core/doctor/index.js +157 -50
  46. package/.sinapse-ai/core/ids/registry-updater.js +6 -1
  47. package/.sinapse-ai/core/logger/index.js +319 -0
  48. package/.sinapse-ai/core/orchestration/terminal-spawner.js +2 -2
  49. package/.sinapse-ai/core/telemetry/index.js +247 -0
  50. package/.sinapse-ai/data/entity-registry.yaml +1384 -944
  51. package/.sinapse-ai/development/agents/architect.md +5 -0
  52. package/.sinapse-ai/development/agents/data-engineer.md +38 -0
  53. package/.sinapse-ai/development/agents/developer.md +28 -0
  54. package/.sinapse-ai/development/agents/devops.md +4 -0
  55. package/.sinapse-ai/development/agents/product-lead.md +27 -0
  56. package/.sinapse-ai/development/agents/project-lead.md +28 -0
  57. package/.sinapse-ai/development/agents/quality-gate.md +4 -0
  58. package/.sinapse-ai/development/agents/sprint-lead/MEMORY.md +8 -0
  59. package/.sinapse-ai/development/agents/sprint-lead.md +28 -0
  60. package/.sinapse-ai/development/agents/squad-creator.md +58 -0
  61. package/.sinapse-ai/development/agents/ux-design-expert.md +28 -0
  62. package/.sinapse-ai/development/knowledge-base/agent-communication-protocol.md +127 -0
  63. package/.sinapse-ai/development/knowledge-base/database-scaling-patterns.md +374 -0
  64. package/.sinapse-ai/development/knowledge-base/environment-deployment-patterns.md +353 -0
  65. package/.sinapse-ai/development/knowledge-base/gotchas-patterns.md +224 -0
  66. package/.sinapse-ai/development/knowledge-base/infrastructure-decision-framework.md +221 -0
  67. package/.sinapse-ai/development/knowledge-base/security-pre-deploy-checklist.md +410 -0
  68. package/.sinapse-ai/development/knowledge-base/software-architecture-patterns.md +299 -0
  69. package/.sinapse-ai/development/knowledge-base/token-economy-guide.md +198 -0
  70. package/.sinapse-ai/development/scripts/populate-entity-registry.js +5 -1
  71. package/.sinapse-ai/development/skills/captcha-handler.md +82 -0
  72. package/.sinapse-ai/development/skills/chrome-brain.md +81 -0
  73. package/.sinapse-ai/development/skills/deploy-readiness.md +93 -0
  74. package/.sinapse-ai/development/skills/model-router.md +92 -0
  75. package/.sinapse-ai/development/skills/sinapse-methodology.md +175 -0
  76. package/.sinapse-ai/development/skills/story-fast-track.md +71 -0
  77. package/.sinapse-ai/development/tasks/dev-develop-story.md +10 -0
  78. package/.sinapse-ai/development/tasks/environment-promotion-pipeline.md +582 -0
  79. package/.sinapse-ai/development/tasks/generate-agent-handoff.md +223 -0
  80. package/.sinapse-ai/development/tasks/infrastructure-assessment.md +432 -0
  81. package/.sinapse-ai/development/tasks/load-testing-setup.md +611 -0
  82. package/.sinapse-ai/development/tasks/observability-blueprint.md +562 -0
  83. package/.sinapse-ai/development/templates/legal/breach-notification-tmpl.md +113 -0
  84. package/.sinapse-ai/development/templates/legal/privacy-policy-tmpl.md +93 -0
  85. package/.sinapse-ai/development/templates/legal/terms-of-service-tmpl.md +85 -0
  86. package/.sinapse-ai/development/templates/service-template/README.md.hbs +159 -159
  87. package/.sinapse-ai/development/templates/service-template/__tests__/index.test.ts.hbs +238 -238
  88. package/.sinapse-ai/development/templates/service-template/client.ts.hbs +404 -404
  89. package/.sinapse-ai/development/templates/service-template/errors.ts.hbs +183 -183
  90. package/.sinapse-ai/development/templates/service-template/index.ts.hbs +121 -121
  91. package/.sinapse-ai/development/templates/service-template/package.json.hbs +88 -88
  92. package/.sinapse-ai/development/templates/service-template/types.ts.hbs +146 -146
  93. package/.sinapse-ai/development/templates/squad-template/LICENSE +22 -22
  94. package/.sinapse-ai/development/workflows/story-development-cycle.yaml +40 -1
  95. package/.sinapse-ai/hooks/ids-post-commit.js +22 -0
  96. package/.sinapse-ai/infrastructure/contracts/compatibility/README.md +42 -0
  97. package/.sinapse-ai/infrastructure/contracts/compatibility/sinapse-current.yaml +35 -0
  98. package/.sinapse-ai/infrastructure/scripts/llm-routing/templates/claude-free-tracked.cmd +127 -127
  99. package/.sinapse-ai/infrastructure/scripts/llm-routing/templates/deepseek-proxy.cmd +71 -71
  100. package/.sinapse-ai/infrastructure/scripts/llm-routing/templates/deepseek-usage.cmd +51 -51
  101. package/.sinapse-ai/infrastructure/scripts/pr-review-ai.js +16 -13
  102. package/.sinapse-ai/infrastructure/scripts/setup-project-infra.js +128 -0
  103. package/.sinapse-ai/infrastructure/scripts/test-discovery.js +8 -3
  104. package/.sinapse-ai/infrastructure/scripts/validate-manifest-parity.js +380 -0
  105. package/.sinapse-ai/infrastructure/scripts/validate-parity.js +76 -25
  106. package/.sinapse-ai/infrastructure/templates/coderabbit.yaml.template +280 -280
  107. package/.sinapse-ai/infrastructure/templates/config/env.example +16 -0
  108. package/.sinapse-ai/infrastructure/templates/config/gitignore-additions.tmpl +59 -0
  109. package/.sinapse-ai/infrastructure/templates/github/CODEOWNERS.template +12 -0
  110. package/.sinapse-ai/infrastructure/templates/github/PULL_REQUEST_TEMPLATE.md +29 -0
  111. package/.sinapse-ai/infrastructure/templates/github/ci-template.yml +77 -0
  112. package/.sinapse-ai/infrastructure/templates/github/issue-templates/bug_report.md +34 -0
  113. package/.sinapse-ai/infrastructure/templates/github/issue-templates/feature_request.md +19 -0
  114. package/.sinapse-ai/infrastructure/templates/github-workflows/ci.yml.template +170 -170
  115. package/.sinapse-ai/infrastructure/templates/github-workflows/pr-automation.yml.template +331 -331
  116. package/.sinapse-ai/infrastructure/templates/github-workflows/release.yml.template +197 -197
  117. package/.sinapse-ai/infrastructure/templates/gitignore/gitignore-brownfield-merge.tmpl +19 -19
  118. package/.sinapse-ai/infrastructure/templates/gitignore/gitignore-node.tmpl +86 -86
  119. package/.sinapse-ai/infrastructure/templates/gitignore/gitignore-python.tmpl +146 -146
  120. package/.sinapse-ai/infrastructure/templates/gitignore/gitignore-sinapse-base.tmpl +64 -64
  121. package/.sinapse-ai/infrastructure/templates/sinapse-sync.yaml.template +183 -183
  122. package/.sinapse-ai/install-manifest.yaml +275 -140
  123. package/.sinapse-ai/local-config.yaml.template +65 -65
  124. package/.sinapse-ai/monitor/hooks/lib/__init__.py +2 -2
  125. package/.sinapse-ai/monitor/hooks/lib/enrich.py +59 -59
  126. package/.sinapse-ai/monitor/hooks/lib/send_event.py +48 -48
  127. package/.sinapse-ai/monitor/hooks/notification.py +30 -30
  128. package/.sinapse-ai/monitor/hooks/post_tool_use.py +46 -46
  129. package/.sinapse-ai/monitor/hooks/pre_compact.py +30 -30
  130. package/.sinapse-ai/monitor/hooks/pre_tool_use.py +41 -41
  131. package/.sinapse-ai/monitor/hooks/stop.py +30 -30
  132. package/.sinapse-ai/monitor/hooks/subagent_stop.py +30 -30
  133. package/.sinapse-ai/monitor/hooks/user_prompt_submit.py +39 -39
  134. package/.sinapse-ai/product/templates/adr.hbs +126 -126
  135. package/.sinapse-ai/product/templates/dbdr.hbs +242 -242
  136. package/.sinapse-ai/product/templates/epic.hbs +213 -213
  137. package/.sinapse-ai/product/templates/pmdr.hbs +187 -187
  138. package/.sinapse-ai/product/templates/prd-v2.0.hbs +217 -217
  139. package/.sinapse-ai/product/templates/prd.hbs +202 -202
  140. package/.sinapse-ai/product/templates/story-tmpl.yaml +59 -0
  141. package/.sinapse-ai/product/templates/story.hbs +264 -264
  142. package/.sinapse-ai/product/templates/task.hbs +171 -171
  143. package/.sinapse-ai/product/templates/tmpl-comment-on-examples.sql +159 -159
  144. package/.sinapse-ai/product/templates/tmpl-migration-script.sql +92 -92
  145. package/.sinapse-ai/product/templates/tmpl-rls-granular-policies.sql +105 -105
  146. package/.sinapse-ai/product/templates/tmpl-rls-kiss-policy.sql +11 -11
  147. package/.sinapse-ai/product/templates/tmpl-rls-roles.sql +136 -136
  148. package/.sinapse-ai/product/templates/tmpl-rls-simple.sql +78 -78
  149. package/.sinapse-ai/product/templates/tmpl-rls-tenant.sql +153 -153
  150. package/.sinapse-ai/product/templates/tmpl-rollback-script.sql +78 -78
  151. package/.sinapse-ai/product/templates/tmpl-seed-data.sql +141 -141
  152. package/.sinapse-ai/product/templates/tmpl-smoke-test.sql +17 -17
  153. package/.sinapse-ai/product/templates/tmpl-staging-copy-merge.sql +140 -140
  154. package/.sinapse-ai/product/templates/tmpl-stored-proc.sql +141 -141
  155. package/.sinapse-ai/product/templates/tmpl-trigger.sql +153 -153
  156. package/.sinapse-ai/product/templates/tmpl-view-materialized.sql +134 -134
  157. package/.sinapse-ai/product/templates/tmpl-view.sql +178 -178
  158. package/.sinapse-ai/scripts/diagnostics/health-dashboard/package-lock.json +427 -355
  159. package/LICENSE +34 -34
  160. package/README.en.md +167 -20
  161. package/README.md +190 -22
  162. package/bin/cli.js +510 -196
  163. package/bin/postinstall.js +564 -0
  164. package/bin/sinapse-cli +283 -283
  165. package/bin/sinapse-graph.js +9 -0
  166. package/bin/sinapse-init.js +36 -4
  167. package/bin/sinapse-minimal.js +20 -9
  168. package/bin/sinapse.js +202 -122
  169. package/bin/utils/deprecation-warning.js +46 -0
  170. package/bin/utils/pre-push-safety.js +14 -0
  171. package/docs/TELEMETRY.md +131 -0
  172. package/docs/chrome-brain-upgrade-plan.md +624 -0
  173. package/docs/framework/orqx-plan.md +1 -1
  174. package/docs/installation/chrome-brain.md +17 -7
  175. package/docs/mega-upgrade-orchestration-plan.md +71 -0
  176. package/docs/pt/contributing.md +20 -0
  177. package/docs/research-synthesis-for-upgrade.md +511 -0
  178. package/docs/security-audit-report.md +306 -0
  179. package/package.json +20 -8
  180. package/packages/installer/src/config/configure-environment.js +19 -44
  181. package/packages/installer/src/detection/detect-project-type.js +181 -63
  182. package/packages/installer/src/installer/manifest-signature.js +32 -17
  183. package/packages/installer/src/wizard/i18n.js +12 -0
  184. package/packages/installer/src/wizard/ide-config-generator.js +8 -39
  185. package/packages/installer/src/wizard/index.js +119 -14
  186. package/packages/installer/src/wizard/questions.js +2 -3
  187. package/packages/installer/tests/integration/environment-configuration.test.js +7 -5
  188. package/packages/installer/tests/unit/detection/detect-project-type.test.js +138 -1
  189. package/packages/installer/tests/unit/doctor/doctor-orchestrator.test.js +3 -3
  190. package/packages/sinapse-install/bin/edmcp.js +0 -0
  191. package/packages/sinapse-install/bin/sinapse-install.js +0 -0
  192. package/packages/sinapse-pro-cli/bin/sinapse-pro.js +0 -0
  193. package/scripts/check-markdown-links.py +353 -353
  194. package/scripts/coverage-report-summary.js +169 -0
  195. package/scripts/generate-install-manifest.js +6 -2
  196. package/scripts/release-readiness.js +169 -0
  197. package/scripts/test-install-matrix-local.sh +153 -0
  198. package/scripts/validate-install-docs.js +394 -0
  199. package/scripts/validate-no-external-refs.js +376 -0
  200. package/scripts/validate-squad-orqx.js +302 -0
  201. package/scripts/validate-story-meta.js +263 -0
  202. package/squads/claude-code-mastery/CHANGELOG.md +1 -1
  203. package/squads/claude-code-mastery/README.md +2 -2
  204. package/squads/claude-code-mastery/squad.yaml +1 -1
  205. package/squads/squad-artdir/README.md +90 -0
  206. package/squads/squad-artdir/agents/accessibility-guardian.md +184 -0
  207. package/squads/squad-artdir/agents/artdir-orqx.md +145 -0
  208. package/squads/squad-artdir/agents/color-psychologist.md +166 -0
  209. package/squads/squad-artdir/agents/cro-persuasion.md +161 -0
  210. package/squads/squad-artdir/agents/design-system-architect.md +100 -0
  211. package/squads/squad-artdir/agents/ia-architect.md +169 -0
  212. package/squads/squad-artdir/agents/interaction-designer.md +162 -0
  213. package/squads/squad-artdir/agents/layout-engineer.md +163 -0
  214. package/squads/squad-artdir/agents/motion-architect.md +185 -0
  215. package/squads/squad-artdir/agents/platform-aesthetic-director.md +84 -0
  216. package/squads/squad-artdir/agents/premium-packaging-strategist.md +107 -0
  217. package/squads/squad-artdir/agents/product-surface-director.md +86 -0
  218. package/squads/squad-artdir/agents/type-systemist.md +138 -0
  219. package/squads/squad-artdir/agents/visual-strategist.md +127 -0
  220. package/squads/squad-artdir/checklists/seven-pillars-validation-checklist.md +172 -0
  221. package/squads/squad-artdir/knowledge-base/case-nyo-ia-reference.md +289 -0
  222. package/squads/squad-artdir/knowledge-base/deliverables-templates.md +457 -0
  223. package/squads/squad-artdir/knowledge-base/motion-technique-catalog.md +247 -0
  224. package/squads/squad-artdir/knowledge-base/premium-packaging-principles.md +133 -0
  225. package/squads/squad-artdir/knowledge-base/psychological-toolkit.md +229 -0
  226. package/squads/squad-artdir/knowledge-base/saas-art-direction-canon.md +242 -0
  227. package/squads/squad-artdir/knowledge-base/seven-pillars-framework.md +289 -0
  228. package/squads/squad-artdir/knowledge-base/ten-pillars-framework.md +221 -0
  229. package/squads/squad-artdir/package.json +20 -0
  230. package/squads/squad-artdir/squad.yaml +271 -0
  231. package/squads/squad-artdir/tasks/audit-conversion.md +97 -0
  232. package/squads/squad-artdir/tasks/audit-drift-multi-surface.md +55 -0
  233. package/squads/squad-artdir/tasks/consult-saas-canon.md +54 -0
  234. package/squads/squad-artdir/tasks/create-art-direction-brief.md +110 -0
  235. package/squads/squad-artdir/tasks/create-premium-packaging-brief.md +61 -0
  236. package/squads/squad-artdir/tasks/create-wireflow.md +84 -0
  237. package/squads/squad-artdir/tasks/design-color-system.md +81 -0
  238. package/squads/squad-artdir/tasks/design-product-surface.md +60 -0
  239. package/squads/squad-artdir/tasks/design-token-system.md +58 -0
  240. package/squads/squad-artdir/tasks/diagnose-visual-language.md +92 -0
  241. package/squads/squad-artdir/tasks/first-5-minutes-choreography.md +65 -0
  242. package/squads/squad-artdir/tasks/specify-motion-system.md +84 -0
  243. package/squads/squad-artdir/tasks/validate-against-pillars.md +143 -0
  244. package/squads/squad-artdir/templates/art-direction-brief-template.md +215 -0
  245. package/squads/squad-artdir/workflows/conversion-audit-cycle.yaml +78 -0
  246. package/squads/squad-artdir/workflows/full-art-direction-cycle.yaml +98 -0
  247. package/squads/squad-artdir/workflows/saas-platform-art-direction-cycle.yaml +174 -0
  248. package/squads/squad-brand/knowledge-base/ai-visual-generation-canon.md +234 -0
  249. package/squads/squad-brand/squad.yaml +20 -6
  250. package/squads/squad-claude/knowledge-base/context-window-optimization.md +1 -1
  251. package/squads/squad-claude/knowledge-base/swarm-orchestration-patterns.md +2 -2
  252. package/squads/squad-content/knowledge-base/ai-native-content-loop.md +220 -0
  253. package/squads/squad-content/knowledge-base/signal-intelligence-v2.md +234 -0
  254. package/squads/squad-content/knowledge-base/task-ownership-map.md +235 -0
  255. package/squads/squad-content/squad.yaml +187 -27
  256. package/squads/squad-copy/knowledge-base/ai-copy-human-loop-canon.md +235 -0
  257. package/squads/squad-copy/squad.yaml +19 -4
  258. package/squads/squad-design/knowledge-base/cross-surface-token-canon.md +209 -0
  259. package/squads/squad-design/squad.yaml +19 -4
  260. package/.sinapse-ai/core/registry/service-registry.json +0 -6346
  261. package/.sinapse-ai/data/registry-update-log.jsonl +0 -1323
  262. package/.sinapse-ai/manifests/agents.csv +0 -29
  263. package/.sinapse-ai/manifests/tasks.csv +0 -204
  264. package/.sinapse-ai/manifests/workers.csv +0 -196
  265. package/squads/squad-growth/tasks/calculate-sample-size.md +0 -121
  266. package/squads/squad-paidmedia/tasks/calculate-sample-size.md +0 -57
package/bin/cli.js CHANGED
@@ -5,6 +5,15 @@ const path = require('path');
5
5
  const fs = require('fs');
6
6
  const os = require('os');
7
7
 
8
+ // Story A.2 — unified logger. Levels: error/warn/info/debug.
9
+ // Flags: --verbose, --debug, --quiet, --json. Default level: warn.
10
+ const {
11
+ getLogger,
12
+ shouldShowHeader,
13
+ markFirstRunDone,
14
+ } = require('../.sinapse-ai/core/logger');
15
+ const logger = getLogger();
16
+
8
17
  // ══════════════════════════════════════════════════════════════════════════════
9
18
  // Sinapse CLI — Global + Local installer for Claude Code agent squads
10
19
  // ══════════════════════════════════════════════════════════════════════════════
@@ -29,18 +38,31 @@ const NC = '\x1b[0m';
29
38
  // ── Helpers ──────────────────────────────────────────────────────────────────
30
39
 
31
40
  function header() {
41
+ // Story A.2 AC 7 — ASCII art only on --verbose / --debug OR first-run.
42
+ // Never in --quiet or --json mode.
43
+ if (!shouldShowHeader(logger)) return;
32
44
  const W = `${WHITE}${BOLD}`;
33
- console.log('');
34
- console.log(`${W} ███████╗██╗███╗ ██╗ █████╗ ██████╗ ███████╗███████╗ █████╗ ██╗${NC}`);
35
- console.log(`${W} ██╔════╝██║████╗ ██║██╔══██╗██╔══██╗██╔════╝██╔════╝ ██╔══██╗██║${NC}`);
36
- console.log(`${W} ███████╗██║██╔██╗ ██║███████║██████╔╝███████╗█████╗ ███████║██║${NC}`);
37
- console.log(`${W} ╚════██║██║██║╚██╗██║██╔══██║██╔═══╝ ╚════██║██╔══╝ ██╔══██║██║${NC}`);
38
- console.log(`${W} ███████║██║██║ ╚████║██║ ██║██║ ███████║███████╗ ██║ ██║██║${NC}`);
39
- console.log(`${W} ╚══════╝╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚══════╝╚══════╝ ╚═╝ ╚═╝╚═╝${NC}`);
40
- console.log('');
41
- console.log(`${DIM} Seu copiloto de inteligencia artificial${NC}`);
42
- console.log(`${DIM} v${VERSION}${NC}`);
43
- console.log('');
45
+ const lines = [
46
+ '',
47
+ `${W} ███████╗██╗███╗ ██╗ █████╗ ██████╗ ███████╗███████╗ █████╗ ██╗${NC}`,
48
+ `${W} ██╔════╝██║████╗ ██║██╔══██╗██╔══██╗██╔════╝██╔════╝ ██╔══██╗██║${NC}`,
49
+ `${W} ███████╗██║██╔██╗ ██║███████║██████╔╝███████╗█████╗ ███████║██║${NC}`,
50
+ `${W} ╚════██║██║██║╚██╗██║██╔══██║██╔═══╝ ╚════██║██╔══╝ ██╔══██║██║${NC}`,
51
+ `${W} ███████║██║██║ ╚████║██║ ██║██║ ███████║███████╗ ██║ ██║██║${NC}`,
52
+ `${W} ╚══════╝╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚══════╝╚══════╝ ╚═╝ ╚═╝╚═╝${NC}`,
53
+ '',
54
+ `${DIM} Seu copiloto de inteligencia artificial${NC}`,
55
+ `${DIM} v${VERSION}${NC}`,
56
+ '',
57
+ ];
58
+ // Write directly to stdout so the header still shows on first-run even when
59
+ // the logger level is `warn` (default). `shouldShowHeader` is the single
60
+ // gate that decides whether we get here at all.
61
+ for (const line of lines) {
62
+ try { process.stdout.write(`${line}\n`); } catch { /* ignore */ }
63
+ }
64
+ // Mark first-run done so subsequent runs stay clean unless --verbose.
65
+ markFirstRunDone();
44
66
  }
45
67
 
46
68
  function getSquads(baseDir) {
@@ -144,6 +166,89 @@ function rmDirSync(dir) {
144
166
  }
145
167
  }
146
168
 
169
+ // syncDirSync — Story 10.20
170
+ // Copies src -> dest, only writing files whose mtime+size differ. Returns a
171
+ // {added, updated, unchanged, removed} delta. Removes orphaned files in dest
172
+ // that are no longer present in src. Used by install upsert mode to avoid
173
+ // the rmDir+copyDir destruction of user-customized files.
174
+ function syncDirSync(src, dest, delta = { added: 0, updated: 0, unchanged: 0, removed: 0 }) {
175
+ if (!fs.existsSync(src)) return delta;
176
+ fs.mkdirSync(dest, { recursive: true });
177
+ const srcEntries = new Set();
178
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
179
+ srcEntries.add(entry.name);
180
+ const sp = path.join(src, entry.name);
181
+ const dp = path.join(dest, entry.name);
182
+ if (entry.isDirectory()) {
183
+ syncDirSync(sp, dp, delta);
184
+ } else {
185
+ const srcStat = fs.statSync(sp);
186
+ let needsWrite = true;
187
+ if (fs.existsSync(dp)) {
188
+ try {
189
+ const dstStat = fs.statSync(dp);
190
+ if (dstStat.size === srcStat.size && dstStat.mtimeMs >= srcStat.mtimeMs) {
191
+ needsWrite = false;
192
+ delta.unchanged += 1;
193
+ } else {
194
+ delta.updated += 1;
195
+ }
196
+ } catch {
197
+ delta.updated += 1;
198
+ }
199
+ } else {
200
+ delta.added += 1;
201
+ }
202
+ if (needsWrite) fs.copyFileSync(sp, dp);
203
+ }
204
+ }
205
+ if (fs.existsSync(dest)) {
206
+ for (const name of fs.readdirSync(dest)) {
207
+ if (!srcEntries.has(name)) {
208
+ const orphan = path.join(dest, name);
209
+ try {
210
+ const stat = fs.statSync(orphan);
211
+ if (stat.isDirectory()) rmDirSync(orphan);
212
+ else fs.unlinkSync(orphan);
213
+ delta.removed += 1;
214
+ } catch { /* skip */ }
215
+ }
216
+ }
217
+ }
218
+ return delta;
219
+ }
220
+
221
+ // detectExistingInstall — Story 10.20
222
+ // Returns { upsert: true, prevMeta, language, llm } when ~/.sinapse/metadata.json
223
+ // exists, or { upsert: false } otherwise. Reads ~/.claude/settings.json for
224
+ // language reuse.
225
+ function detectExistingInstall() {
226
+ const metaPath = path.join(SINAPSE_HOME, 'metadata.json');
227
+ if (!fs.existsSync(metaPath)) return { upsert: false };
228
+ let prevMeta = null;
229
+ try {
230
+ prevMeta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
231
+ } catch {
232
+ return { upsert: false };
233
+ }
234
+ let language = null;
235
+ try {
236
+ const settingsPath = path.join(HOME, '.claude', 'settings.json');
237
+ if (fs.existsSync(settingsPath)) {
238
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
239
+ if (settings && typeof settings.language === 'string') {
240
+ language = settings.language === 'portuguese' ? 'pt' : (settings.language === 'english' ? 'en' : null);
241
+ }
242
+ }
243
+ } catch { /* fall through */ }
244
+ return {
245
+ upsert: true,
246
+ prevMeta,
247
+ language,
248
+ llm: prevMeta && typeof prevMeta.llm === 'string' ? prevMeta.llm : null,
249
+ };
250
+ }
251
+
147
252
  function toForwardSlash(p) {
148
253
  return p.replace(/\\/g, '/');
149
254
  }
@@ -153,6 +258,10 @@ function toForwardSlash(p) {
153
258
  * @returns {Promise<string>} 'claude-code' | 'codex' | 'both'
154
259
  */
155
260
  async function promptLlmChoice() {
261
+ // Non-TTY (CI, Docker, scripts, agents): skip prompt, default to claude-code.
262
+ if (!process.stdin.isTTY) {
263
+ return 'claude-code';
264
+ }
156
265
  try {
157
266
  const inquirer = require('inquirer');
158
267
  const { llms } = await inquirer.prompt([{
@@ -172,11 +281,11 @@ async function promptLlmChoice() {
172
281
  const readline = require('readline');
173
282
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
174
283
  return new Promise((resolve) => {
175
- console.log(`${CYAN} Escolha sua LLM:${NC}`);
176
- console.log(` ${GREEN}1${NC}) Claude Code ${DIM}(Recomendado)${NC}`);
177
- console.log(` ${GREEN}2${NC}) Codex CLI`);
178
- console.log(` ${GREEN}3${NC}) Ambos`);
179
- console.log('');
284
+ logger.always(`${CYAN} Escolha sua LLM:${NC}`);
285
+ logger.always(` ${GREEN}1${NC}) Claude Code ${DIM}(Recomendado)${NC}`);
286
+ logger.always(` ${GREEN}2${NC}) Codex CLI`);
287
+ logger.always(` ${GREEN}3${NC}) Ambos`);
288
+ logger.always('');
180
289
  rl.question(` ${BOLD}Opcao [1/2/3]:${NC} `, (answer) => {
181
290
  rl.close();
182
291
  const choice = (answer || '1').trim();
@@ -190,40 +299,63 @@ async function promptLlmChoice() {
190
299
 
191
300
  // ── Global Install ───────────────────────────────────────────────────────────
192
301
 
193
- async function cmdInstallGlobal() {
302
+ async function cmdInstallGlobal(opts = {}) {
194
303
  header();
195
- console.log(`${BOLD} Bem-vindo ao SINAPSE AI!${NC}`);
196
- console.log(`${DIM} Vamos configurar seu copiloto de inteligencia artificial.${NC}`);
197
- console.log('');
198
304
 
199
- // Language selection (always show for UX clarity)
200
- let language = 'pt';
201
- try {
202
- const inquirer = require('inquirer');
203
- const langAnswer = await inquirer.prompt([{
204
- type: 'list',
205
- name: 'language',
206
- message: 'Language / Idioma:',
207
- choices: [
208
- { name: 'Portugues', value: 'pt' },
209
- { name: 'English', value: 'en' },
210
- ],
211
- default: 'pt',
212
- }]);
213
- language = langAnswer.language;
214
- } catch {
215
- // Fallback: readline
216
- const readline = require('readline');
217
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
218
- language = await new Promise((resolve) => {
219
- console.log(` ${CYAN}Language / Idioma:${NC}`);
220
- console.log(` ${GREEN}1${NC}) Portugues`);
221
- console.log(` ${GREEN}2${NC}) English`);
222
- rl.question(` ${BOLD}[1/2]:${NC} `, (answer) => {
223
- rl.close();
224
- resolve((answer || '1').trim() === '2' ? 'en' : 'pt');
225
- });
226
- });
305
+ // Story 10.20 Upsert detection
306
+ const force = Boolean(opts.force);
307
+ const reconfigure = Boolean(opts.reconfigure);
308
+ const existing = force ? { upsert: false } : detectExistingInstall();
309
+ const isUpsert = existing.upsert;
310
+
311
+ if (force && fs.existsSync(path.join(SINAPSE_HOME, 'metadata.json'))) {
312
+ logger.always(`${YELLOW}--force flag detected: running fresh install (overwriting any existing install).${NC}`);
313
+ logger.always('');
314
+ } else if (isUpsert) {
315
+ const prevVer = existing.prevMeta && existing.prevMeta.version ? existing.prevMeta.version : 'unknown';
316
+ logger.always(`${BOLD} Detected existing install (v${prevVer}). Refreshing in place...${NC}`);
317
+ logger.always(`${DIM} Use --force to wipe and reinstall fresh. Use --reconfigure to re-prompt language/LLM.${NC}`);
318
+ logger.always('');
319
+ } else {
320
+ logger.always(`${BOLD} Bem-vindo ao SINAPSE AI!${NC}`);
321
+ logger.always(`${DIM} Vamos configurar seu copiloto de inteligencia artificial.${NC}`);
322
+ logger.always('');
323
+ }
324
+
325
+ // Language selection (skipped in upsert mode if already known, or non-TTY)
326
+ // Story 10.35: --reconfigure forces prompt even in upsert mode
327
+ let language = (isUpsert && !reconfigure && existing.language) ? existing.language : null;
328
+ if (!language) {
329
+ language = 'pt';
330
+ if (process.stdin.isTTY) {
331
+ try {
332
+ const inquirer = require('inquirer');
333
+ const langAnswer = await inquirer.prompt([{
334
+ type: 'list',
335
+ name: 'language',
336
+ message: 'Language / Idioma:',
337
+ choices: [
338
+ { name: 'Portugues', value: 'pt' },
339
+ { name: 'English', value: 'en' },
340
+ ],
341
+ default: 'pt',
342
+ }]);
343
+ language = langAnswer.language;
344
+ } catch {
345
+ // Fallback: readline
346
+ const readline = require('readline');
347
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
348
+ language = await new Promise((resolve) => {
349
+ logger.always(` ${CYAN}Language / Idioma:${NC}`);
350
+ logger.always(` ${GREEN}1${NC}) Portugues`);
351
+ logger.always(` ${GREEN}2${NC}) English`);
352
+ rl.question(` ${BOLD}[1/2]:${NC} `, (answer) => {
353
+ rl.close();
354
+ resolve((answer || '1').trim() === '2' ? 'en' : 'pt');
355
+ });
356
+ });
357
+ }
358
+ }
227
359
  }
228
360
 
229
361
  // Save language to ~/.claude/settings.json
@@ -239,48 +371,71 @@ async function cmdInstallGlobal() {
239
371
  fs.writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 2) + '\n');
240
372
  } catch { /* non-critical */ }
241
373
 
242
- // LLM selection (inquirer checkbox with readline fallback)
243
- const llmChoice = await promptLlmChoice();
374
+ // LLM selection (skipped in upsert mode if previous llm known)
375
+ // Story 10.35: --reconfigure forces prompt even in upsert mode
376
+ const llmChoice = (isUpsert && !reconfigure && existing.llm) ? existing.llm : await promptLlmChoice();
244
377
 
245
- console.log('');
246
- console.log(`${BOLD}Installing Sinapse globally...${NC}\n`);
378
+ logger.always('');
379
+ logger.always(`${BOLD}Installing Sinapse globally...${NC}\n`);
247
380
 
248
381
  // Validate package — squads live in squads/ subdirectory
249
382
  const squadsDir = path.join(ROOT, 'squads');
250
383
  const squads = getSquads(fs.existsSync(squadsDir) ? squadsDir : ROOT);
251
384
  if (squads.length === 0) {
252
- console.error(`${RED}ERROR: No squad directories found in package.${NC}`);
385
+ logger.error(`${RED}ERROR: No squad directories found in package.${NC}`);
253
386
  process.exit(1);
254
387
  }
255
388
 
256
389
  // Phase 1: Copy squads to ~/.sinapse/
257
- console.log(`${CYAN}Phase 1:${NC} Copying squads to ~/.sinapse/`);
390
+ logger.always(`${CYAN}Phase 1:${NC} ${isUpsert ? 'Refreshing' : 'Copying'} squads to ~/.sinapse/`);
258
391
  fs.mkdirSync(SINAPSE_HOME, { recursive: true });
259
392
 
260
393
  const squadsSrcBase = fs.existsSync(squadsDir) ? squadsDir : ROOT;
261
394
  let totalAgents = 0;
395
+ const totalDelta = { added: 0, updated: 0, unchanged: 0, removed: 0 };
396
+ let squadsRefreshed = 0;
397
+ let squadsAdded = 0;
262
398
  for (const squad of squads) {
263
399
  const src = path.join(squadsSrcBase, squad.name);
264
400
  const dest = path.join(SINAPSE_HOME, squad.name);
265
- rmDirSync(dest);
266
- copyDirSync(src, dest);
401
+ if (isUpsert) {
402
+ const existedBefore = fs.existsSync(dest);
403
+ const delta = syncDirSync(src, dest);
404
+ totalDelta.added += delta.added;
405
+ totalDelta.updated += delta.updated;
406
+ totalDelta.unchanged += delta.unchanged;
407
+ totalDelta.removed += delta.removed;
408
+ if (existedBefore) squadsRefreshed += 1; else squadsAdded += 1;
409
+ logger.always(` ${GREEN}OK${NC} ${squad.name} (${delta.added} added, ${delta.updated} updated, ${delta.unchanged} unchanged${delta.removed ? ', ' + delta.removed + ' removed' : ''})`);
410
+ } else {
411
+ rmDirSync(dest);
412
+ copyDirSync(src, dest);
413
+ logger.always(` ${GREEN}OK${NC} ${squad.name} (${squad.agents} agents)`);
414
+ }
267
415
  totalAgents += squad.agents;
268
- console.log(` ${GREEN}OK${NC} ${squad.name} (${squad.agents} agents)`);
269
416
  }
270
417
 
271
418
  // Copy sinapse/ orqx squad
272
419
  const sinapseMasterSrc = path.join(ROOT, 'sinapse');
273
420
  const sinapseMasterDest = path.join(SINAPSE_HOME, 'sinapse');
274
421
  if (fs.existsSync(sinapseMasterSrc)) {
275
- rmDirSync(sinapseMasterDest);
276
- copyDirSync(sinapseMasterSrc, sinapseMasterDest);
422
+ if (isUpsert) {
423
+ const delta = syncDirSync(sinapseMasterSrc, sinapseMasterDest);
424
+ totalDelta.added += delta.added;
425
+ totalDelta.updated += delta.updated;
426
+ totalDelta.unchanged += delta.unchanged;
427
+ totalDelta.removed += delta.removed;
428
+ } else {
429
+ rmDirSync(sinapseMasterDest);
430
+ copyDirSync(sinapseMasterSrc, sinapseMasterDest);
431
+ }
277
432
  const masterAgents = getAgentFiles(sinapseMasterDest).length;
278
433
  totalAgents += masterAgents;
279
- console.log(` ${GREEN}OK${NC} sinapse (master, ${masterAgents} agents)`);
434
+ logger.always(` ${GREEN}OK${NC} sinapse (master, ${masterAgents} agents)`);
280
435
  }
281
436
 
282
437
  // Phase 2: Generate orqx commands
283
- console.log(`\n${CYAN}Phase 2:${NC} Generating agent commands`);
438
+ logger.always(`\n${CYAN}Phase 2:${NC} Generating agent commands`);
284
439
  fs.mkdirSync(CLAUDE_COMMANDS_DIR, { recursive: true });
285
440
 
286
441
  // Clear old commands
@@ -324,7 +479,7 @@ async function cmdInstallGlobal() {
324
479
  }
325
480
  }
326
481
  }
327
- console.log(` ${GREEN}OK${NC} ${writtenAgents.size} total command files`);
482
+ logger.always(` ${GREEN}OK${NC} ${writtenAgents.size} total command files`);
328
483
 
329
484
  // Phase 2b: Install global agents based on LLM choice
330
485
  if (llmChoice === 'claude-code' || llmChoice === 'both') {
@@ -333,7 +488,7 @@ async function cmdInstallGlobal() {
333
488
  for (const f of fs.readdirSync(CLAUDE_COMMANDS_DIR).filter(f => f.endsWith('.md'))) {
334
489
  fs.copyFileSync(path.join(CLAUDE_COMMANDS_DIR, f), path.join(globalAgentsDir, f));
335
490
  }
336
- console.log(` ${GREEN}OK${NC} Claude Code global agents (${writtenAgents.size})`);
491
+ logger.always(` ${GREEN}OK${NC} Claude Code global agents (${writtenAgents.size})`);
337
492
  }
338
493
 
339
494
  if (llmChoice === 'codex' || llmChoice === 'both') {
@@ -342,57 +497,76 @@ async function cmdInstallGlobal() {
342
497
  for (const f of fs.readdirSync(CLAUDE_COMMANDS_DIR).filter(f => f.endsWith('.md'))) {
343
498
  fs.copyFileSync(path.join(CLAUDE_COMMANDS_DIR, f), path.join(codexAgentsDir, f));
344
499
  }
345
- console.log(` ${GREEN}OK${NC} Codex global agents (${writtenAgents.size})`);
500
+ logger.always(` ${GREEN}OK${NC} Codex global agents (${writtenAgents.size})`);
346
501
  }
347
502
 
348
503
  // Phase 3: Generate squad-awareness.md
349
- console.log(`\n${CYAN}Phase 3:${NC} Generating squad-awareness rules`);
504
+ logger.always(`\n${CYAN}Phase 3:${NC} Generating squad-awareness rules`);
350
505
  generateSquadAwareness(SINAPSE_HOME, squads);
351
- console.log(` ${GREEN}OK${NC} squad-awareness.md`);
506
+ logger.always(` ${GREEN}OK${NC} squad-awareness.md`);
352
507
 
353
508
  // Phase 4: Create launcher
354
- console.log(`\n${CYAN}Phase 4:${NC} Creating launcher`);
509
+ logger.always(`\n${CYAN}Phase 4:${NC} Creating launcher`);
355
510
  createLauncher();
356
511
 
357
512
  // Phase 5: PATH management
358
- console.log(`\n${CYAN}Phase 5:${NC} Configuring PATH`);
513
+ logger.always(`\n${CYAN}Phase 5:${NC} Configuring PATH`);
359
514
  ensurePath();
360
515
 
361
- // Phase 6: Write metadata
516
+ // Phase 6: Write metadata (Story 10.20 — preserve installedAt on upsert)
517
+ const nowIso = new Date().toISOString();
362
518
  const meta = {
363
519
  version: VERSION,
364
- installedAt: new Date().toISOString(),
520
+ installedAt: isUpsert && existing.prevMeta && existing.prevMeta.installedAt
521
+ ? existing.prevMeta.installedAt
522
+ : nowIso,
365
523
  squads: squads.length,
366
524
  agents: totalAgents,
367
525
  commands: writtenAgents.size,
368
526
  llm: llmChoice,
369
527
  platform: process.platform,
370
528
  };
529
+ if (isUpsert) {
530
+ meta.updatedAt = nowIso;
531
+ }
371
532
  fs.writeFileSync(path.join(SINAPSE_HOME, 'metadata.json'), JSON.stringify(meta, null, 2));
372
533
 
534
+ // Story 10.20 — Upsert summary block
535
+ if (isUpsert) {
536
+ const prevVer = existing.prevMeta && existing.prevMeta.version ? existing.prevMeta.version : 'unknown';
537
+ logger.always('');
538
+ logger.always(`${BOLD}Upsert complete:${NC}`);
539
+ logger.always(` ${CYAN}Mode:${NC} upsert (--force to reinstall)`);
540
+ logger.always(` ${CYAN}Version:${NC} ${prevVer} -> ${VERSION}`);
541
+ logger.always(` ${CYAN}Squads:${NC} ${squadsRefreshed} refreshed${squadsAdded ? ', ' + squadsAdded + ' added' : ''}`);
542
+ logger.always(` ${CYAN}Files:${NC} ${totalDelta.added} added, ${totalDelta.updated} updated, ${totalDelta.unchanged} unchanged${totalDelta.removed ? ', ' + totalDelta.removed + ' removed' : ''}`);
543
+ logger.always(` ${CYAN}First installed:${NC} ${meta.installedAt}`);
544
+ logger.always(` ${CYAN}Last updated:${NC} ${meta.updatedAt}`);
545
+ }
546
+
373
547
  // Chrome Brain: Auto-install browser automation
374
548
  if (llmChoice === 'claude-code' || llmChoice === 'both') {
375
549
  try {
376
550
  const { detectChrome, detectPlatform, installScripts, installHooks, installMcp, installKnowledgeBase } = require('./modules/chrome-brain-installer');
377
551
  const chromePath = detectChrome();
378
552
  if (chromePath) {
379
- console.log(`\n${CYAN}Phase 7:${NC} Chrome Brain (browser automation)`);
553
+ logger.always(`\n${CYAN}Phase 7:${NC} Chrome Brain (browser automation)`);
380
554
  const platform = detectPlatform();
381
555
  installScripts(chromePath, platform);
382
556
  installHooks();
383
557
  installMcp(platform);
384
558
  installKnowledgeBase();
385
- console.log(` ${GREEN}OK${NC} Chrome Brain installed — all agents can control Chrome`);
559
+ logger.always(` ${GREEN}OK${NC} Chrome Brain installed — all agents can control Chrome`);
386
560
  } else {
387
- console.log(`\n${YELLOW}SKIP${NC} Chrome Brain — Chrome not found (install Chrome and run: sinapse chrome-brain install)`);
561
+ logger.always(`\n${YELLOW}SKIP${NC} Chrome Brain — Chrome not found (install Chrome and run: npx sinapse-ai chrome-brain install)`);
388
562
  }
389
563
  } catch (error) {
390
- console.log(`\n${YELLOW}SKIP${NC} Chrome Brain: ${error.message}`);
564
+ logger.always(`\n${YELLOW}SKIP${NC} Chrome Brain: ${error.message}`);
391
565
  }
392
566
  }
393
567
 
394
568
  // Phase 8: Install project-local files (.sinapse-ai/, .claude/, .env)
395
- console.log(`\n${CYAN}Phase 8:${NC} Installing project files in current directory`);
569
+ logger.always(`\n${CYAN}Phase 8:${NC} Installing project files in current directory`);
396
570
  try {
397
571
  const wizardPath = path.join(ROOT, 'packages', 'installer', 'src', 'wizard', 'index.js');
398
572
  if (fs.existsSync(wizardPath)) {
@@ -402,17 +576,17 @@ async function cmdInstallGlobal() {
402
576
  language: language,
403
577
  selectedLLM: llmChoice,
404
578
  });
405
- console.log(` ${GREEN}OK${NC} Project files installed (.sinapse-ai/, .claude/)`);
579
+ logger.always(` ${GREEN}OK${NC} Project files installed (.sinapse-ai/, .claude/)`);
406
580
  } else {
407
- console.log(` ${YELLOW}SKIP${NC} Project installer not available`);
581
+ logger.always(` ${YELLOW}SKIP${NC} Project installer not available`);
408
582
  }
409
583
  } catch (error) {
410
- console.log(` ${YELLOW}WARN${NC} Project files: ${error.message}`);
411
- console.log(` ${DIM}Run 'sinapse install' in your project later to complete setup${NC}`);
584
+ logger.always(` ${YELLOW}WARN${NC} Project files: ${error.message}`);
585
+ logger.always(` ${DIM}Run 'npx sinapse-ai install' in your project later to complete setup${NC}`);
412
586
  }
413
587
 
414
588
  // Verify
415
- console.log(`\n${CYAN}Verification:${NC}`);
589
+ logger.always(`\n${CYAN}Verification:${NC}`);
416
590
  verifyInstall();
417
591
 
418
592
  // Success message
@@ -421,18 +595,18 @@ async function cmdInstallGlobal() {
421
595
  else if (llmChoice === 'both') startCmd = `Run ${CYAN}sinapse${NC} or ${CYAN}codex${NC} to start`;
422
596
  else startCmd = `Run ${CYAN}sinapse${NC} to start Claude Code with all agents`;
423
597
 
424
- console.log('');
425
- console.log(`${GREEN}══════════════════════════════════════════════════════════════${NC}`);
426
- console.log(`${GREEN} Sinapse installed successfully!${NC}`);
427
- console.log(`${GREEN}══════════════════════════════════════════════════════════════${NC}`);
428
- console.log('');
429
- console.log(` ${BOLD}${squads.length} squads${NC} | ${BOLD}${totalAgents} agents${NC} | ${BOLD}${writtenAgents.size} orqx commands${NC}`);
430
- console.log(` ${startCmd}`);
431
- console.log('');
432
- console.log(` ${BOLD}Try an agent:${NC}`);
433
- console.log(` ${CYAN}/SINAPSE:agents:sinapse-orqx${NC}`);
434
- console.log(` ${CYAN}/SINAPSE:agents:brand-orqx${NC}`);
435
- console.log('');
598
+ logger.always('');
599
+ logger.always(`${GREEN}══════════════════════════════════════════════════════════════${NC}`);
600
+ logger.always(`${GREEN} Sinapse installed successfully!${NC}`);
601
+ logger.always(`${GREEN}══════════════════════════════════════════════════════════════${NC}`);
602
+ logger.always('');
603
+ logger.always(` ${BOLD}${squads.length} squads${NC} | ${BOLD}${totalAgents} agents${NC} | ${BOLD}${writtenAgents.size} orqx commands${NC}`);
604
+ logger.always(` ${startCmd}`);
605
+ logger.always('');
606
+ logger.always(` ${BOLD}Try an agent:${NC}`);
607
+ logger.always(` ${CYAN}/SINAPSE:agents:sinapse-orqx${NC}`);
608
+ logger.always(` ${CYAN}/SINAPSE:agents:brand-orqx${NC}`);
609
+ logger.always('');
436
610
  }
437
611
 
438
612
  function generateCommandMd(agentId, agentName, agentIcon, squadName, squadPath, agentFile) {
@@ -622,13 +796,13 @@ exec claude --add-dir "${sinapsePathForBash}" --agent sinapse-orqx "$@"
622
796
  const bashPath = path.join(BIN_DIR, 'sinapse');
623
797
  fs.writeFileSync(bashPath, bashLauncher);
624
798
  try { fs.chmodSync(bashPath, 0o755); } catch {}
625
- console.log(` ${GREEN}OK${NC} ~/bin/sinapse`);
799
+ logger.always(` ${GREEN}OK${NC} ~/bin/sinapse`);
626
800
 
627
801
  // Windows CMD launcher
628
802
  if (IS_WIN) {
629
803
  const cmdLauncher = `@echo off\r\nclaude --add-dir "%USERPROFILE%\\.sinapse" --agent sinapse-orqx %*\r\n`;
630
804
  fs.writeFileSync(path.join(BIN_DIR, 'sinapse.cmd'), cmdLauncher);
631
- console.log(` ${GREEN}OK${NC} ~/bin/sinapse.cmd`);
805
+ logger.always(` ${GREEN}OK${NC} ~/bin/sinapse.cmd`);
632
806
  }
633
807
  }
634
808
 
@@ -638,7 +812,7 @@ function ensurePath() {
638
812
  const alreadyInPath = pathDirs.some(p => path.normalize(p) === binNorm);
639
813
 
640
814
  if (alreadyInPath) {
641
- console.log(` ${YELLOW}SKIP${NC} ~/bin already in PATH`);
815
+ logger.always(` ${YELLOW}SKIP${NC} ~/bin already in PATH`);
642
816
  return;
643
817
  }
644
818
 
@@ -648,7 +822,7 @@ function ensurePath() {
648
822
  ensurePathUnix();
649
823
  }
650
824
 
651
- console.log(` ${YELLOW}NOTE${NC} Restart your terminal for PATH changes`);
825
+ logger.always(` ${YELLOW}NOTE${NC} Restart your terminal for PATH changes`);
652
826
  }
653
827
 
654
828
  function ensurePathUnix() {
@@ -662,13 +836,13 @@ function ensurePathUnix() {
662
836
  const content = fs.readFileSync(rcPath, 'utf8');
663
837
  if (content.includes(marker) || content.includes(exportLine)) continue;
664
838
  fs.appendFileSync(rcPath, `\n${marker}\n${exportLine}\n`);
665
- console.log(` ${GREEN}OK${NC} Updated ~/${rc}`);
839
+ logger.always(` ${GREEN}OK${NC} Updated ~/${rc}`);
666
840
  return;
667
841
  }
668
842
 
669
843
  // If no RC file found, create .profile
670
844
  fs.writeFileSync(path.join(HOME, '.profile'), `${marker}\n${exportLine}\n`, { flag: 'a' });
671
- console.log(` ${GREEN}OK${NC} Created ~/.profile`);
845
+ logger.always(` ${GREEN}OK${NC} Created ~/.profile`);
672
846
  }
673
847
 
674
848
  function ensurePathWindows() {
@@ -678,24 +852,24 @@ function ensurePathWindows() {
678
852
  const currentPath = match ? match[1].trim() : '';
679
853
 
680
854
  if (currentPath.includes('%USERPROFILE%\\bin') || currentPath.includes(BIN_DIR.replace(/\//g, '\\'))) {
681
- console.log(` ${YELLOW}SKIP${NC} ~/bin already in Windows PATH`);
855
+ logger.always(` ${YELLOW}SKIP${NC} ~/bin already in Windows PATH`);
682
856
  return;
683
857
  }
684
858
 
685
859
  const newPath = currentPath ? `${currentPath};%USERPROFILE%\\bin` : '%USERPROFILE%\\bin';
686
860
  if (newPath.length > 900) {
687
- console.log(` ${YELLOW}WARN${NC} PATH too long for setx. Add manually: ${BIN_DIR}`);
861
+ logger.always(` ${YELLOW}WARN${NC} PATH too long for setx. Add manually: ${BIN_DIR}`);
688
862
  return;
689
863
  }
690
864
 
691
865
  execSync(`setx PATH "${newPath}"`, { encoding: 'utf8', stdio: 'pipe' });
692
- console.log(` ${GREEN}OK${NC} Added ~/bin to Windows User PATH`);
866
+ logger.always(` ${GREEN}OK${NC} Added ~/bin to Windows User PATH`);
693
867
  } catch {
694
868
  try {
695
869
  execSync(`setx PATH "%USERPROFILE%\\bin"`, { encoding: 'utf8', stdio: 'pipe' });
696
- console.log(` ${GREEN}OK${NC} Created Windows User PATH with ~/bin`);
870
+ logger.always(` ${GREEN}OK${NC} Created Windows User PATH with ~/bin`);
697
871
  } catch {
698
- console.log(` ${YELLOW}WARN${NC} Could not modify PATH. Add manually: ${BIN_DIR}`);
872
+ logger.always(` ${YELLOW}WARN${NC} Could not modify PATH. Add manually: ${BIN_DIR}`);
699
873
  }
700
874
  }
701
875
  }
@@ -709,7 +883,7 @@ function verifyInstall() {
709
883
  ];
710
884
 
711
885
  for (const [ok, msg] of checks) {
712
- console.log(` ${ok ? GREEN + '✓' : RED + '✗'}${NC} ${msg}`);
886
+ logger.always(` ${ok ? GREEN + '✓' : RED + '✗'}${NC} ${msg}`);
713
887
  }
714
888
  }
715
889
 
@@ -719,44 +893,56 @@ async function cmdUpdateGlobal() {
719
893
  header();
720
894
 
721
895
  if (!fs.existsSync(path.join(SINAPSE_HOME, 'metadata.json'))) {
722
- console.log(`${YELLOW}Sinapse not installed globally. Run: npx sinapse install${NC}\n`);
896
+ logger.always(`${YELLOW}Sinapse not installed globally. Run: npx sinapse-ai install${NC}\n`);
723
897
  process.exit(1);
724
898
  }
725
899
 
900
+ // Story 10.22 — reuse settings from existing install
901
+ const existing = detectExistingInstall();
902
+ const prevVer = existing.prevMeta && existing.prevMeta.version ? existing.prevMeta.version : 'unknown';
903
+
726
904
  // Welcome back screen
727
- console.log(`${BOLD} Que bom que voce voltou!${NC}`);
728
- console.log(`${DIM} Vamos atualizar seu SINAPSE AI para a v${VERSION}.${NC}`);
729
- console.log('');
905
+ logger.always(`${BOLD} Que bom que voce voltou!${NC}`);
906
+ logger.always(`${DIM} Atualizando SINAPSE AI: v${prevVer} -> v${VERSION}${NC}`);
907
+ logger.always('');
730
908
 
731
- // LLM selection (inquirer checkbox with readline fallback)
732
- const llmChoice = await promptLlmChoice();
909
+ // Story 10.22 — skip LLM prompt when previous llm known. To re-prompt,
910
+ // run `npx sinapse-ai install --force`.
911
+ const llmChoice = existing.llm || await promptLlmChoice();
733
912
 
734
- console.log('');
735
- console.log(`${BOLD}Atualizando SINAPSE AI...${NC}\n`);
913
+ logger.always('');
914
+ logger.always(`${BOLD}Atualizando SINAPSE AI...${NC}\n`);
736
915
 
737
916
  const squadsDir = path.join(ROOT, 'squads');
738
917
  const squadsSrcBase = fs.existsSync(squadsDir) ? squadsDir : ROOT;
739
918
  const squads = getSquads(squadsSrcBase);
740
919
 
741
- // Phase 1: Re-copy squads
742
- console.log(`${CYAN}Phase 1:${NC} Updating squads`);
920
+ // Phase 1: Sync squads (Story 10.22 — replaces rmDir+copy with syncDirSync)
921
+ logger.always(`${CYAN}Phase 1:${NC} Refreshing squads`);
922
+ const totalDelta = { added: 0, updated: 0, unchanged: 0, removed: 0 };
743
923
  for (const squad of squads) {
744
924
  const src = path.join(squadsSrcBase, squad.name);
745
925
  const dest = path.join(SINAPSE_HOME, squad.name);
746
- rmDirSync(dest);
747
- copyDirSync(src, dest);
748
- console.log(` ${GREEN}OK${NC} ${squad.name}`);
926
+ const delta = syncDirSync(src, dest);
927
+ totalDelta.added += delta.added;
928
+ totalDelta.updated += delta.updated;
929
+ totalDelta.unchanged += delta.unchanged;
930
+ totalDelta.removed += delta.removed;
931
+ logger.always(` ${GREEN}OK${NC} ${squad.name} (${delta.added} added, ${delta.updated} updated, ${delta.unchanged} unchanged${delta.removed ? ', ' + delta.removed + ' removed' : ''})`);
749
932
  }
750
933
 
751
934
  const sinapseMasterSrc = path.join(ROOT, 'sinapse');
752
935
  if (fs.existsSync(sinapseMasterSrc)) {
753
- rmDirSync(path.join(SINAPSE_HOME, 'sinapse'));
754
- copyDirSync(sinapseMasterSrc, path.join(SINAPSE_HOME, 'sinapse'));
755
- console.log(` ${GREEN}OK${NC} sinapse (orqx)`);
936
+ const delta = syncDirSync(sinapseMasterSrc, path.join(SINAPSE_HOME, 'sinapse'));
937
+ totalDelta.added += delta.added;
938
+ totalDelta.updated += delta.updated;
939
+ totalDelta.unchanged += delta.unchanged;
940
+ totalDelta.removed += delta.removed;
941
+ logger.always(` ${GREEN}OK${NC} sinapse (orqx)`);
756
942
  }
757
943
 
758
944
  // Phase 2: Regenerate commands (reuse install logic)
759
- console.log(`\n${CYAN}Phase 2:${NC} Regenerating commands`);
945
+ logger.always(`\n${CYAN}Phase 2:${NC} Regenerating commands`);
760
946
  // Clear and regenerate
761
947
  rmDirSync(CLAUDE_COMMANDS_DIR);
762
948
  fs.mkdirSync(CLAUDE_COMMANDS_DIR, { recursive: true });
@@ -795,7 +981,7 @@ async function cmdUpdateGlobal() {
795
981
  writtenAgents.add(file);
796
982
  }
797
983
  }
798
- console.log(` ${GREEN}OK${NC} ${writtenAgents.size} command files (${totalAgents} agents total)`);
984
+ logger.always(` ${GREEN}OK${NC} ${writtenAgents.size} command files (${totalAgents} agents total)`);
799
985
 
800
986
  // Phase 2b: Install global agents based on LLM choice
801
987
  if (llmChoice === 'claude-code' || llmChoice === 'both') {
@@ -805,7 +991,7 @@ async function cmdUpdateGlobal() {
805
991
  for (const f of fs.readdirSync(CLAUDE_COMMANDS_DIR).filter(f => f.endsWith('.md'))) {
806
992
  fs.copyFileSync(path.join(CLAUDE_COMMANDS_DIR, f), path.join(globalAgentsDir, f));
807
993
  }
808
- console.log(` ${GREEN}OK${NC} Claude Code global agents (${writtenAgents.size})`);
994
+ logger.always(` ${GREEN}OK${NC} Claude Code global agents (${writtenAgents.size})`);
809
995
  }
810
996
 
811
997
  if (llmChoice === 'codex' || llmChoice === 'both') {
@@ -815,24 +1001,36 @@ async function cmdUpdateGlobal() {
815
1001
  for (const f of fs.readdirSync(CLAUDE_COMMANDS_DIR).filter(f => f.endsWith('.md'))) {
816
1002
  fs.copyFileSync(path.join(CLAUDE_COMMANDS_DIR, f), path.join(codexAgentsDir, f));
817
1003
  }
818
- console.log(` ${GREEN}OK${NC} Codex global agents (${writtenAgents.size})`);
1004
+ logger.always(` ${GREEN}OK${NC} Codex global agents (${writtenAgents.size})`);
819
1005
  }
820
1006
 
821
1007
  // Phase 3: Regenerate awareness
822
- console.log(`\n${CYAN}Phase 3:${NC} Updating squad-awareness`);
1008
+ logger.always(`\n${CYAN}Phase 3:${NC} Updating squad-awareness`);
823
1009
  generateSquadAwareness(SINAPSE_HOME, squads);
824
- console.log(` ${GREEN}OK${NC} squad-awareness.md`);
1010
+ logger.always(` ${GREEN}OK${NC} squad-awareness.md`);
825
1011
 
826
- // Update metadata
1012
+ // Update metadata (Story 10.22 — preserve installedAt, bump version + updatedAt)
827
1013
  const metaPath = path.join(SINAPSE_HOME, 'metadata.json');
828
1014
  const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
1015
+ // installedAt preserved by NOT touching it
829
1016
  meta.updatedAt = new Date().toISOString();
1017
+ meta.version = VERSION;
830
1018
  meta.squads = squads.length;
831
1019
  meta.commands = writtenAgents.size;
1020
+ meta.llm = llmChoice;
832
1021
  fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2));
833
1022
 
1023
+ // Story 10.22 — Update summary block (mirrors Story 10.20 install upsert)
1024
+ logger.always('');
1025
+ logger.always(`${BOLD}Update complete:${NC}`);
1026
+ logger.always(` ${CYAN}Version:${NC} ${prevVer} -> ${VERSION}`);
1027
+ logger.always(` ${CYAN}Squads:${NC} ${squads.length} refreshed`);
1028
+ logger.always(` ${CYAN}Files:${NC} ${totalDelta.added} added, ${totalDelta.updated} updated, ${totalDelta.unchanged} unchanged${totalDelta.removed ? ', ' + totalDelta.removed + ' removed' : ''}`);
1029
+ logger.always(` ${CYAN}First installed:${NC} ${meta.installedAt}`);
1030
+ logger.always(` ${CYAN}Last updated:${NC} ${meta.updatedAt}`);
1031
+
834
1032
  // Phase 4: Update project-local files (.sinapse-ai/, .claude/)
835
- console.log(`\n${CYAN}Phase 4:${NC} Updating project files in current directory`);
1033
+ logger.always(`\n${CYAN}Phase 4:${NC} Updating project files in current directory`);
836
1034
  try {
837
1035
  const wizardPath = path.join(ROOT, 'packages', 'installer', 'src', 'wizard', 'index.js');
838
1036
  if (fs.existsSync(wizardPath)) {
@@ -842,13 +1040,13 @@ async function cmdUpdateGlobal() {
842
1040
  language: meta.language || 'pt',
843
1041
  selectedLLM: llmChoice,
844
1042
  });
845
- console.log(` ${GREEN}OK${NC} Project files updated (.sinapse-ai/, .claude/)`);
1043
+ logger.always(` ${GREEN}OK${NC} Project files updated (.sinapse-ai/, .claude/)`);
846
1044
  } else {
847
- console.log(` ${YELLOW}SKIP${NC} Project installer not available`);
1045
+ logger.always(` ${YELLOW}SKIP${NC} Project installer not available`);
848
1046
  }
849
1047
  } catch (error) {
850
- console.log(` ${YELLOW}WARN${NC} Project files: ${error.message}`);
851
- console.log(` ${DIM}Run 'sinapse install' in your project later to complete update${NC}`);
1048
+ logger.always(` ${YELLOW}WARN${NC} Project files: ${error.message}`);
1049
+ logger.always(` ${DIM}Run 'npx sinapse-ai install' in your project later to complete update${NC}`);
852
1050
  }
853
1051
 
854
1052
  let startCmd;
@@ -856,21 +1054,21 @@ async function cmdUpdateGlobal() {
856
1054
  else if (llmChoice === 'both') startCmd = `Digite ${CYAN}sinapse${NC} ou ${CYAN}codex${NC} para comecar`;
857
1055
  else startCmd = `Digite ${CYAN}sinapse${NC} para comecar`;
858
1056
 
859
- console.log('');
860
- console.log(`${GREEN}══════════════════════════════════════════════════════════════${NC}`);
861
- console.log(`${GREEN} SINAPSE AI atualizado para v${VERSION}!${NC}`);
862
- console.log(`${GREEN}══════════════════════════════════════════════════════════════${NC}`);
863
- console.log('');
864
- console.log(` ${BOLD}${squads.length} squads${NC} | ${BOLD}${totalAgents} agents${NC} | ${BOLD}${writtenAgents.size} orqx commands${NC}`);
865
- console.log(` ${startCmd}`);
866
- console.log('');
1057
+ logger.always('');
1058
+ logger.always(`${GREEN}══════════════════════════════════════════════════════════════${NC}`);
1059
+ logger.always(`${GREEN} SINAPSE AI atualizado para v${VERSION}!${NC}`);
1060
+ logger.always(`${GREEN}══════════════════════════════════════════════════════════════${NC}`);
1061
+ logger.always('');
1062
+ logger.always(` ${BOLD}${squads.length} squads${NC} | ${BOLD}${totalAgents} agents${NC} | ${BOLD}${writtenAgents.size} orqx commands${NC}`);
1063
+ logger.always(` ${startCmd}`);
1064
+ logger.always('');
867
1065
  }
868
1066
 
869
1067
  // ── Uninstall ────────────────────────────────────────────────────────────────
870
1068
 
871
1069
  function cmdUninstall() {
872
1070
  header();
873
- console.log(`${BOLD}Uninstalling Sinapse...${NC}\n`);
1071
+ logger.always(`${BOLD}Uninstalling Sinapse...${NC}\n`);
874
1072
 
875
1073
  const items = [
876
1074
  [SINAPSE_HOME, '~/.sinapse/'],
@@ -887,14 +1085,14 @@ function cmdUninstall() {
887
1085
  } else {
888
1086
  fs.unlinkSync(p);
889
1087
  }
890
- console.log(` ${GREEN}✓${NC} Removed ${label}`);
1088
+ logger.always(` ${GREEN}✓${NC} Removed ${label}`);
891
1089
  } else {
892
- console.log(` ${YELLOW}-${NC} ${label} (not found)`);
1090
+ logger.always(` ${YELLOW}-${NC} ${label} (not found)`);
893
1091
  }
894
1092
  }
895
1093
 
896
- console.log(`\n${GREEN}Sinapse uninstalled.${NC}`);
897
- console.log(`${YELLOW}Note:${NC} PATH entry in shell RC files was not removed. Clean up manually if desired.\n`);
1094
+ logger.always(`\n${GREEN}Sinapse uninstalled.${NC}`);
1095
+ logger.always(`${YELLOW}Note:${NC} PATH entry in shell RC files was not removed. Clean up manually if desired.\n`);
898
1096
  }
899
1097
 
900
1098
  // ── Local Install (existing behavior) ────────────────────────────────────────
@@ -909,7 +1107,7 @@ function toPosixPath(p) {
909
1107
  function runBash(script) {
910
1108
  const scriptPath = path.join(ROOT, script);
911
1109
  if (!fs.existsSync(scriptPath)) {
912
- console.error(`${RED}Script not found: ${script}${NC}`);
1110
+ logger.error(`${RED}Script not found: ${script}${NC}`);
913
1111
  process.exit(1);
914
1112
  }
915
1113
 
@@ -941,13 +1139,13 @@ function runBash(script) {
941
1139
 
942
1140
  function cmdInstallLocal() {
943
1141
  header();
944
- console.log(`${CYAN}▸ Installing squads in current project...${NC}\n`);
1142
+ logger.always(`${CYAN}▸ Installing squads in current project...${NC}\n`);
945
1143
  runBash('scripts/install-squads.sh');
946
1144
  }
947
1145
 
948
1146
  function cmdUpdateLocal() {
949
1147
  header();
950
- console.log(`${CYAN}▸ Updating squads in current project...${NC}\n`);
1148
+ logger.always(`${CYAN}▸ Updating squads in current project...${NC}\n`);
951
1149
  runBash('scripts/update-squads.sh');
952
1150
  }
953
1151
 
@@ -959,81 +1157,197 @@ function cmdList() {
959
1157
  const squads = getSquads(baseDir);
960
1158
  let totalAgents = 0, totalTasks = 0;
961
1159
 
962
- console.log(`${BOLD}Squads available:${NC}\n`);
1160
+ logger.always(`${BOLD}Squads available:${NC}\n`);
963
1161
 
964
1162
  for (const s of squads) {
965
1163
  totalAgents += s.agents;
966
1164
  totalTasks += s.tasks;
967
1165
  const agents = `${s.agents} agents`.padStart(10);
968
1166
  const tasks = `${s.tasks} tasks`.padStart(10);
969
- console.log(` ${CYAN}${s.name.padEnd(25)}${NC} ${GREEN}${agents}${NC} ${YELLOW}${tasks}${NC} ${s.desc}`);
1167
+ logger.always(` ${CYAN}${s.name.padEnd(25)}${NC} ${GREEN}${agents}${NC} ${YELLOW}${tasks}${NC} ${s.desc}`);
970
1168
  }
971
1169
 
972
- console.log('');
973
- console.log(` ${BOLD}Total: ${GREEN}${totalAgents} agents${NC} | ${YELLOW}${totalTasks} tasks${NC}`);
974
- console.log(` ${BOLD}Invoke: ${CYAN}/SINAPSE:agents:{agent-id}${NC}`);
975
- console.log('');
1170
+ logger.always('');
1171
+ logger.always(` ${BOLD}Total: ${GREEN}${totalAgents} agents${NC} | ${YELLOW}${totalTasks} tasks${NC}`);
1172
+ logger.always(` ${BOLD}Invoke: ${CYAN}/SINAPSE:agents:{agent-id}${NC}`);
1173
+ logger.always('');
976
1174
  }
977
1175
 
978
1176
  function cmdStatus() {
979
1177
  header();
980
1178
 
981
1179
  // Check global install
982
- console.log(`${BOLD}Global Install:${NC}\n`);
1180
+ logger.always(`${BOLD}Global Install:${NC}\n`);
983
1181
 
984
1182
  if (fs.existsSync(path.join(SINAPSE_HOME, 'metadata.json'))) {
985
1183
  const meta = JSON.parse(fs.readFileSync(path.join(SINAPSE_HOME, 'metadata.json'), 'utf8'));
986
- console.log(` ${GREEN}✓${NC} Installed (v${meta.version})`);
987
- console.log(` ${GREEN}✓${NC} ${meta.squads} squads, ${meta.commands || '?'} agents`);
988
- console.log(` ${GREEN}✓${NC} Installed: ${meta.installedAt}`);
989
- if (meta.updatedAt) console.log(` ${GREEN}✓${NC} Updated: ${meta.updatedAt}`);
1184
+ logger.always(` ${GREEN}✓${NC} Installed (v${meta.version})`);
1185
+ logger.always(` ${GREEN}✓${NC} ${meta.squads} squads, ${meta.commands || '?'} agents`);
1186
+ logger.always(` ${GREEN}✓${NC} Installed: ${meta.installedAt}`);
1187
+ if (meta.updatedAt) logger.always(` ${GREEN}✓${NC} Updated: ${meta.updatedAt}`);
990
1188
  } else {
991
- console.log(` ${RED}✗${NC} Not installed globally`);
992
- console.log(` ${YELLOW}Run:${NC} npx sinapse install`);
1189
+ logger.always(` ${RED}✗${NC} Not installed globally`);
1190
+ logger.always(` ${YELLOW}Run:${NC} npx sinapse-ai install`);
993
1191
  }
994
1192
 
995
1193
  verifyInstall();
996
- console.log('');
1194
+ logger.always('');
1195
+ }
1196
+
1197
+ // cmdDoctor — Story 10.21
1198
+ // Wires the existing modular doctor (.sinapse-ai/core/doctor) into the
1199
+ // canonical sinapse-ai CLI. Mirrors bin/sinapse.js runDoctor() but uses
1200
+ // process.exitCode (rather than process.exit) to let stdout flush cleanly.
1201
+ async function cmdDoctor(opts = {}) {
1202
+ if (opts.help) {
1203
+ logger.always(`Usage: npx sinapse-ai doctor [options]
1204
+
1205
+ Run health checks against the SINAPSE environment.
1206
+
1207
+ Options:
1208
+ --fix Auto-correct fixable issues
1209
+ --dry-run Show what --fix would do without applying
1210
+ --json Output as JSON (machine-readable)
1211
+ --quiet Minimal output
1212
+ --deep Run deep checks too (slower)
1213
+ --help, -h Show this help
1214
+
1215
+ Exit codes (Story A.3):
1216
+ 0 PASS — all checks passed, no warnings
1217
+ 1 WARN only — non-blocking issues detected
1218
+ 2 FAIL — at least one blocking check failed
1219
+ 3 Internal error — doctor runner crashed
1220
+ `);
1221
+ return { ok: true, formatted: '', data: null };
1222
+ }
1223
+
1224
+ const doctorModulePath = path.join(__dirname, '..', '.sinapse-ai', 'core', 'doctor');
1225
+ // eslint-disable-next-line global-require
1226
+ const { runDoctorChecks, resolveExitCode } = require(doctorModulePath);
1227
+ const result = await runDoctorChecks({
1228
+ fix: Boolean(opts.fix),
1229
+ json: Boolean(opts.json),
1230
+ dryRun: Boolean(opts.dryRun),
1231
+ quiet: Boolean(opts.quiet),
1232
+ deep: Boolean(opts.deep),
1233
+ projectRoot: process.cwd(),
1234
+ });
1235
+
1236
+ if (result && result.formatted) {
1237
+ logger.always(result.formatted);
1238
+ }
1239
+
1240
+ // Story A.3 — precise exit code mapping:
1241
+ // 0 PASS | 1 WARN only | 2 FAIL | 3 internal runner error
1242
+ // Fall back to resolveExitCode when available (module may be mocked in tests
1243
+ // that pre-date A.3; in that case, keep the legacy 0/1 behavior).
1244
+ if (typeof resolveExitCode === 'function') {
1245
+ const code = resolveExitCode(result);
1246
+ if (code !== 0) {
1247
+ process.exitCode = code;
1248
+ }
1249
+ } else if (result && result.data && result.data.summary && result.data.summary.fail > 0) {
1250
+ process.exitCode = 1;
1251
+ }
1252
+
1253
+ return result;
997
1254
  }
998
1255
 
999
1256
  function cmdHelp() {
1000
1257
  header();
1001
- console.log(`${BOLD}Commands:${NC}\n`);
1002
- console.log(` ${CYAN}npx sinapse install${NC} Global setup (recommended)`);
1003
- console.log(` ${CYAN}npx sinapse install --local${NC} Install in current project`);
1004
- console.log(` ${CYAN}npx sinapse update${NC} Update global squads`);
1005
- console.log(` ${CYAN}npx sinapse uninstall${NC} Remove global install`);
1006
- console.log(` ${CYAN}npx sinapse list${NC} List all squads and agents`);
1007
- console.log(` ${CYAN}npx sinapse status${NC} Check installation status`);
1008
- console.log(` ${CYAN}npx sinapse help${NC} Show this help`);
1009
- console.log('');
1010
- console.log(`${BOLD}After install:${NC}\n`);
1011
- console.log(` ${CYAN}sinapse${NC} Start Claude Code with all agents`);
1012
- console.log(` ${CYAN}sinapse --continue${NC} Continue last session`);
1013
- console.log('');
1014
- console.log(`${BOLD}Agents:${NC}\n`);
1015
- console.log(` All agents use: ${CYAN}/SINAPSE:agents:{agent-id}${NC}`);
1016
- console.log(` Example: ${CYAN}/SINAPSE:agents:brand-orqx${NC}`);
1017
- console.log('');
1258
+ logger.always(`${BOLD}Commands:${NC}\n`);
1259
+ logger.always(` ${CYAN}npx sinapse-ai install${NC} Install SINAPSE (idempotent — re-runs are upserts)`);
1260
+ logger.always(` ${CYAN}npx sinapse-ai install --force${NC} Wipe and reinstall fresh, even if already installed`);
1261
+ logger.always(` ${CYAN}npx sinapse-ai install --reconfigure${NC} Re-prompt language/LLM without wiping existing install`);
1262
+ logger.always(` ${CYAN}npx sinapse-ai update${NC} Update SINAPSE to the latest version`);
1263
+ logger.always(` ${CYAN}npx sinapse-ai uninstall${NC} Remove SINAPSE from current project`);
1264
+ logger.always('');
1265
+ logger.always(` ${DIM}Works in CI / non-interactive environments (uses sensible defaults).${NC}`);
1266
+ logger.always('');
1267
+ logger.always(`${BOLD}Diagnostics:${NC}\n`);
1268
+ logger.always(` ${CYAN}npx sinapse-ai status${NC} Check installation status`);
1269
+ logger.always(` ${CYAN}npx sinapse-ai doctor${NC} Run health checks (--fix --dry-run --json --deep)`);
1270
+ logger.always(` ${CYAN}npx sinapse-ai list${NC} List all squads and agents`);
1271
+ logger.always(` ${CYAN}npx sinapse-ai help${NC} Show this help`);
1272
+ logger.always('');
1273
+ logger.always(`${BOLD}After install:${NC}\n`);
1274
+ logger.always(` ${CYAN}sinapse${NC} Start Claude Code with all agents`);
1275
+ logger.always(` ${CYAN}sinapse --continue${NC} Continue last session`);
1276
+ logger.always('');
1277
+ logger.always(`${BOLD}Agents:${NC}\n`);
1278
+ logger.always(` All agents use: ${CYAN}/SINAPSE:agents:{agent-id}${NC}`);
1279
+ logger.always(` Example: ${CYAN}/SINAPSE:agents:brand-orqx${NC}`);
1280
+ logger.always('');
1018
1281
  }
1019
1282
 
1020
1283
  // ── Router ───────────────────────────────────────────────────────────────────
1021
1284
 
1022
- const args = process.argv.slice(2);
1023
- const command = args[0] || 'help';
1024
- const isLocal = args.includes('--local');
1025
-
1026
- switch (command) {
1027
- case 'install': isLocal ? cmdInstallLocal() : cmdInstallGlobal().catch(e => { console.error(e.message); process.exit(1); }); break;
1028
- case 'update': isLocal ? cmdUpdateLocal() : cmdUpdateGlobal().catch(e => { console.error(e.message); process.exit(1); }); break;
1029
- case 'uninstall': cmdUninstall(); break;
1030
- case 'list': cmdList(); break;
1031
- case 'status': cmdStatus(); break;
1032
- case 'help':
1033
- case '--help':
1034
- case '-h': cmdHelp(); break;
1035
- default:
1036
- console.error(`${RED}Unknown command:${NC} ${command}`);
1037
- console.error(`Run ${CYAN}npx sinapse help${NC}`);
1038
- process.exit(1);
1285
+ // Story 10.20 — Export helpers for unit tests. Router only runs when this
1286
+ // file is invoked directly by Node; importing it from a test gets the
1287
+ // helpers without triggering the switch statement.
1288
+ module.exports = {
1289
+ syncDirSync,
1290
+ detectExistingInstall,
1291
+ cmdDoctor,
1292
+ promptLlmChoice,
1293
+ SINAPSE_HOME,
1294
+ HOME,
1295
+ };
1296
+
1297
+ if (require.main === module) {
1298
+ runRouter();
1299
+ }
1300
+
1301
+ function runRouter() {
1302
+ const args = process.argv.slice(2);
1303
+ const command = args[0] || 'help';
1304
+ const isLocal = args.includes('--local');
1305
+ const isForce = args.includes('--force');
1306
+ const isReconfigure = args.includes('--reconfigure');
1307
+
1308
+ switch (command) {
1309
+ case 'install': isLocal ? cmdInstallLocal() : cmdInstallGlobal({ force: isForce, reconfigure: isReconfigure }).catch(e => { logger.error(e.message); process.exit(1); }); break;
1310
+ case 'update': isLocal ? cmdUpdateLocal() : cmdUpdateGlobal().catch(e => { logger.error(e.message); process.exit(1); }); break;
1311
+ case 'uninstall': cmdUninstall(); break;
1312
+ case 'list': cmdList(); break;
1313
+ case 'status': cmdStatus(); break;
1314
+ case 'doctor': {
1315
+ // Story 10.21 — wires the modular doctor into the canonical CLI
1316
+ const doctorArgs = args.slice(1);
1317
+ const doctorOpts = {
1318
+ help: doctorArgs.includes('--help') || doctorArgs.includes('-h'),
1319
+ fix: doctorArgs.includes('--fix'),
1320
+ json: doctorArgs.includes('--json'),
1321
+ dryRun: doctorArgs.includes('--dry-run'),
1322
+ quiet: doctorArgs.includes('--quiet'),
1323
+ deep: doctorArgs.includes('--deep'),
1324
+ };
1325
+ cmdDoctor(doctorOpts).catch(e => { logger.error(`${RED}doctor error:${NC} ${e.message}`); process.exit(1); });
1326
+ break;
1327
+ }
1328
+ case 'chrome-brain': {
1329
+ // Story 10.13 — chrome-brain is the canonical sub-capability for browser
1330
+ // automation. Delegating to the shared chrome-brain-installer module keeps
1331
+ // `npx sinapse-ai chrome-brain <install|uninstall|status>` working without
1332
+ // requiring users to fall back to legacy binaries.
1333
+ const chromeBrainArgs = args.slice(1);
1334
+ (async () => {
1335
+ try {
1336
+ const { runChromeBrain } = require('./modules/chrome-brain-installer');
1337
+ await runChromeBrain(chromeBrainArgs);
1338
+ } catch (e) {
1339
+ logger.error(`${RED}chrome-brain error:${NC} ${e.message}`);
1340
+ process.exit(1);
1341
+ }
1342
+ })();
1343
+ break;
1344
+ }
1345
+ case 'help':
1346
+ case '--help':
1347
+ case '-h': cmdHelp(); break;
1348
+ default:
1349
+ logger.error(`${RED}Unknown command:${NC} ${command}`);
1350
+ logger.error(`Run ${CYAN}npx sinapse-ai help${NC}`);
1351
+ process.exit(1);
1352
+ }
1039
1353
  }