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
@@ -58,5 +58,8 @@ async function run(context) {
58
58
  };
59
59
  }
60
60
 
61
- module.exports = { name, run, EXPECTED_RULES };
61
+ // Story A.3: rules files ship with the framework; exceptions indicate real damage.
62
+ const onError = 'fail';
63
+
64
+ module.exports = { name, run, EXPECTED_RULES, onError };
62
65
 
@@ -118,5 +118,8 @@ async function run(context) {
118
118
  };
119
119
  }
120
120
 
121
- module.exports = { name, run };
121
+ // Story A.3: .claude/settings.json is a hard requirement. Exceptions are FAIL.
122
+ const onError = 'fail';
123
+
124
+ module.exports = { name, run, onError };
122
125
 
@@ -69,5 +69,8 @@ async function run(context) {
69
69
  };
70
70
  }
71
71
 
72
- module.exports = { name, run };
72
+ // Story A.3: skills dir ships with the framework.
73
+ const onError = 'fail';
74
+
75
+ module.exports = { name, run, onError };
73
76
 
@@ -1,12 +1,23 @@
1
1
  /**
2
2
  * SINAPSE Doctor — Environment Health Check Orchestrator
3
3
  *
4
- * Runs 12 modular checks against the SINAPSE environment and returns
4
+ * Runs modular checks against the SINAPSE environment and returns
5
5
  * structured results with optional --fix, --json, and --dry-run support.
6
6
  *
7
+ * Exception Classification (Story A.3):
8
+ * Each check module may export an `onError` field with one of:
9
+ * - 'fail' (default) — thrown exceptions map to a FAIL verdict
10
+ * - 'warn' — thrown exceptions map to a WARN verdict
11
+ * - 'skip' — thrown exceptions cause the check to be excluded
12
+ * from results entirely
13
+ *
14
+ * This replaces the previous behavior where the generic catch block
15
+ * marked every exception as FAIL, producing false alarms on fresh
16
+ * installs (no git repo, empty registry, missing agent memory dirs).
17
+ *
7
18
  * @module sinapse-ai/doctor
8
- * @version 2.0.0
9
- * @story INS-4.1
19
+ * @version 2.1.0
20
+ * @story INS-4.1, A.3
10
21
  */
11
22
 
12
23
  const path = require('path');
@@ -15,7 +26,46 @@ const { formatText } = require('./formatters/text');
15
26
  const { formatJson } = require('./formatters/json');
16
27
  const { applyFixes } = require('./fix-handler');
17
28
 
18
- const DOCTOR_VERSION = '2.0.0';
29
+ const DOCTOR_VERSION = '2.1.0';
30
+
31
+ const VALID_ON_ERROR = new Set(['fail', 'warn', 'skip']);
32
+
33
+ /**
34
+ * Map a check module's `onError` field to the verdict produced when
35
+ * the check itself throws. Falls back to 'fail' for safety.
36
+ *
37
+ * @param {Object} checkModule
38
+ * @returns {'fail' | 'warn' | 'skip'}
39
+ */
40
+ function resolveOnError(checkModule) {
41
+ const declared = checkModule && checkModule.onError;
42
+ if (declared && VALID_ON_ERROR.has(declared)) {
43
+ return declared;
44
+ }
45
+ return 'fail';
46
+ }
47
+
48
+ /**
49
+ * Build a structured result entry for a thrown exception, based on
50
+ * the check's declared error policy. Returns null if the policy is
51
+ * 'skip' (excluded from results).
52
+ *
53
+ * @param {Object} checkModule
54
+ * @param {Error} error
55
+ * @returns {Object|null}
56
+ */
57
+ function buildErrorResult(checkModule, error) {
58
+ const policy = resolveOnError(checkModule);
59
+ if (policy === 'skip') {
60
+ return null;
61
+ }
62
+ return {
63
+ check: checkModule.name || 'unknown',
64
+ status: policy === 'warn' ? 'WARN' : 'FAIL',
65
+ message: `Check threw error: ${error.message}`,
66
+ fixCommand: null,
67
+ };
68
+ }
19
69
 
20
70
  /**
21
71
  * Run all doctor checks
@@ -26,7 +76,7 @@ const DOCTOR_VERSION = '2.0.0';
26
76
  * @param {boolean} [options.dryRun=false] - Show what --fix would do
27
77
  * @param {boolean} [options.quiet=false] - Minimal output
28
78
  * @param {string} [options.projectRoot] - Project root (defaults to cwd)
29
- * @returns {Promise<Object>} Doctor results
79
+ * @returns {Promise<Object>} Doctor results (includes `internalError` when the runner itself crashes)
30
80
  */
31
81
  async function runDoctorChecks(options = {}) {
32
82
  const {
@@ -38,58 +88,115 @@ async function runDoctorChecks(options = {}) {
38
88
  projectRoot = process.cwd(),
39
89
  } = options;
40
90
 
41
- const context = {
42
- projectRoot,
43
- frameworkRoot: path.resolve(__dirname, '..', '..', '..'),
44
- options: { fix, json, dryRun, quiet, deep },
45
- };
91
+ try {
92
+ const context = {
93
+ projectRoot,
94
+ frameworkRoot: path.resolve(__dirname, '..', '..', '..'),
95
+ options: { fix, json, dryRun, quiet, deep },
96
+ };
97
+
98
+ // Load and run all checks (deep checks only with --deep flag)
99
+ const checks = loadChecks({ deep });
100
+ const results = [];
46
101
 
47
- // Load and run all checks (deep checks only with --deep flag)
48
- const checks = loadChecks({ deep });
49
- const results = [];
50
-
51
- for (const checkModule of checks) {
52
- try {
53
- const result = await checkModule.run(context);
54
- results.push(result);
55
- } catch (error) {
56
- results.push({
57
- check: checkModule.name || 'unknown',
58
- status: 'FAIL',
59
- message: `Check threw error: ${error.message}`,
60
- fixCommand: null,
61
- });
102
+ for (const checkModule of checks) {
103
+ try {
104
+ const result = await checkModule.run(context);
105
+ results.push(result);
106
+ } catch (error) {
107
+ const errorResult = buildErrorResult(checkModule, error);
108
+ if (errorResult !== null) {
109
+ results.push(errorResult);
110
+ }
111
+ // policy === 'skip' → silently exclude
112
+ }
62
113
  }
63
- }
64
114
 
65
- // Apply fixes if requested
66
- let fixResults = null;
67
- if (fix || dryRun) {
68
- fixResults = await applyFixes(results, context);
69
- }
115
+ // Apply fixes if requested
116
+ let fixResults = null;
117
+ if (fix || dryRun) {
118
+ fixResults = await applyFixes(results, context);
119
+ }
70
120
 
71
- // Build summary
72
- const summary = {
73
- pass: results.filter((r) => r.status === 'PASS').length,
74
- warn: results.filter((r) => r.status === 'WARN').length,
75
- fail: results.filter((r) => r.status === 'FAIL').length,
76
- info: results.filter((r) => r.status === 'INFO').length,
77
- };
121
+ // Build summary
122
+ const summary = {
123
+ pass: results.filter((r) => r.status === 'PASS').length,
124
+ warn: results.filter((r) => r.status === 'WARN').length,
125
+ fail: results.filter((r) => r.status === 'FAIL').length,
126
+ info: results.filter((r) => r.status === 'INFO').length,
127
+ };
78
128
 
79
- const output = {
80
- version: DOCTOR_VERSION,
81
- timestamp: new Date().toISOString(),
82
- summary,
83
- checks: results,
84
- fixResults,
85
- };
129
+ const output = {
130
+ version: DOCTOR_VERSION,
131
+ timestamp: new Date().toISOString(),
132
+ summary,
133
+ checks: results,
134
+ fixResults,
135
+ internalError: null,
136
+ };
137
+
138
+ // Format output
139
+ if (json) {
140
+ return { formatted: formatJson(output), data: output };
141
+ }
142
+
143
+ return { formatted: formatText(output, { quiet }), data: output };
144
+ } catch (runnerError) {
145
+ // Runner itself crashed (not a check throwing). Surface as exit code 3.
146
+ const summary = { pass: 0, warn: 0, fail: 0, info: 0 };
147
+ const errorPayload = {
148
+ message: runnerError.message,
149
+ stack: runnerError.stack,
150
+ };
151
+ const output = {
152
+ version: DOCTOR_VERSION,
153
+ timestamp: new Date().toISOString(),
154
+ summary,
155
+ checks: [],
156
+ fixResults: null,
157
+ internalError: errorPayload,
158
+ };
159
+
160
+ const formatted = json
161
+ ? formatJson(output)
162
+ : `SINAPSE Doctor v${DOCTOR_VERSION} — internal error: ${runnerError.message}`;
86
163
 
87
- // Format output
88
- if (json) {
89
- return { formatted: formatJson(output), data: output };
164
+ return { formatted, data: output };
90
165
  }
166
+ }
91
167
 
92
- return { formatted: formatText(output, { quiet }), data: output };
168
+ /**
169
+ * Resolve a doctor result object into a canonical exit code.
170
+ *
171
+ * Story A.3 exit code mapping:
172
+ * 0 — PASS (no FAILs, no WARNs)
173
+ * 1 — WARN only (no FAILs)
174
+ * 2 — at least one FAIL
175
+ * 3 — internal runner error (runDoctorChecks itself crashed)
176
+ *
177
+ * @param {Object} result - Return value from runDoctorChecks()
178
+ * @returns {0 | 1 | 2 | 3}
179
+ */
180
+ function resolveExitCode(result) {
181
+ if (!result || !result.data) {
182
+ return 3;
183
+ }
184
+ if (result.data.internalError) {
185
+ return 3;
186
+ }
187
+ const summary = result.data.summary || {};
188
+ if ((summary.fail || 0) > 0) {
189
+ return 2;
190
+ }
191
+ if ((summary.warn || 0) > 0) {
192
+ return 1;
193
+ }
194
+ return 0;
93
195
  }
94
196
 
95
- module.exports = { runDoctorChecks, DOCTOR_VERSION };
197
+ module.exports = {
198
+ runDoctorChecks,
199
+ resolveExitCode,
200
+ resolveOnError,
201
+ DOCTOR_VERSION,
202
+ };
@@ -483,10 +483,15 @@ class RegistryUpdater {
483
483
  }
484
484
 
485
485
  _writeRegistry(registryData) {
486
+ // Story 10.24 — sortKeys: true makes the YAML output deterministic.
487
+ // Without it, key order follows JS object insertion order, which means
488
+ // semantically-identical writes can produce textually-different files
489
+ // and cause endless `M entity-registry.yaml` churn in `git status`.
490
+ // The registry is machine-managed so alphabetical key order is fine.
486
491
  const yamlStr = yaml.dump(registryData, {
487
492
  lineWidth: 120,
488
493
  noRefs: true,
489
- sortKeys: false,
494
+ sortKeys: true,
490
495
  });
491
496
 
492
497
  const dir = path.dirname(this._registryPath);
@@ -0,0 +1,319 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * SINAPSE Logger — unified logging abstraction
5
+ * @story A.2 - Logger & Verbose Flag Refactor
6
+ *
7
+ * Levels:
8
+ * error = 0 (always shown, except nothing is shown in --json mode until flush)
9
+ * warn = 1 (default floor — shown unless --quiet)
10
+ * info = 2 (shown on --verbose)
11
+ * debug = 3 (shown on --debug)
12
+ *
13
+ * Flag resolution priority (highest wins):
14
+ * --quiet > --debug > --verbose > default(warn)
15
+ *
16
+ * Extra modes:
17
+ * --json Structured output: all human text is suppressed; logger accumulates a
18
+ * JSON summary which is flushed at process exit (flush() also callable).
19
+ *
20
+ * First-run detection:
21
+ * ASCII art header is gated by level (info+) OR the absence of
22
+ * ~/.sinapse/.first-run-done. The first run creates this flag file.
23
+ *
24
+ * Design constraints (from story Dev Notes):
25
+ * - Zero external deps (pure Node stdlib).
26
+ * - Safe on repeated require() from bin/cli.js and bin/sinapse.js in same process.
27
+ * - Tests can call createLogger({ level, json }) to build isolated instances.
28
+ */
29
+
30
+ const fs = require('fs');
31
+ const os = require('os');
32
+ const path = require('path');
33
+
34
+ const LEVELS = Object.freeze({
35
+ error: 0,
36
+ warn: 1,
37
+ info: 2,
38
+ debug: 3,
39
+ });
40
+
41
+ const LEVEL_NAMES = Object.freeze(['error', 'warn', 'info', 'debug']);
42
+
43
+ const DEFAULT_LEVEL = 'warn';
44
+
45
+ const SINAPSE_HOME = path.join(os.homedir(), '.sinapse');
46
+ const FIRST_RUN_FLAG = path.join(SINAPSE_HOME, '.first-run-done');
47
+
48
+ /**
49
+ * Resolve level from parsed flags. Higher verbosity wins unless --quiet is set.
50
+ * --quiet beats everything else (error only).
51
+ * @param {{ quiet?: boolean, debug?: boolean, verbose?: boolean }} flags
52
+ * @returns {'error'|'warn'|'info'|'debug'}
53
+ */
54
+ function resolveLevel(flags = {}) {
55
+ if (flags.quiet) return 'error';
56
+ if (flags.debug) return 'debug';
57
+ if (flags.verbose) return 'info';
58
+ return DEFAULT_LEVEL;
59
+ }
60
+
61
+ /**
62
+ * Parse verbosity/output flags from an argv-like array (process.argv.slice(2)).
63
+ * Non-destructive — returns a plain object, does not mutate the input.
64
+ * @param {string[]} argv
65
+ */
66
+ function parseFlags(argv) {
67
+ const a = Array.isArray(argv) ? argv : [];
68
+ const has = (name) => a.includes(name);
69
+ return {
70
+ quiet: has('--quiet') || has('-q'),
71
+ verbose: has('--verbose') || has('-v'),
72
+ debug: has('--debug'),
73
+ json: has('--json'),
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Detect whether this is a first run. True when the flag file does NOT exist.
79
+ * Any filesystem error is treated as "not first run" to avoid breaking headless
80
+ * environments (safer default: stay quiet).
81
+ */
82
+ function isFirstRun() {
83
+ try {
84
+ return !fs.existsSync(FIRST_RUN_FLAG);
85
+ } catch {
86
+ return false;
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Mark first run as done by touching the flag file. Idempotent and failure-tolerant.
92
+ */
93
+ function markFirstRunDone() {
94
+ try {
95
+ if (!fs.existsSync(SINAPSE_HOME)) {
96
+ fs.mkdirSync(SINAPSE_HOME, { recursive: true });
97
+ }
98
+ if (!fs.existsSync(FIRST_RUN_FLAG)) {
99
+ fs.writeFileSync(FIRST_RUN_FLAG, `${new Date().toISOString()}\n`, 'utf8');
100
+ }
101
+ } catch {
102
+ /* non-fatal — first-run detection will just keep asking */
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Build a logger instance.
108
+ * @param {object} [opts]
109
+ * @param {'error'|'warn'|'info'|'debug'} [opts.level] explicit level (overrides flags)
110
+ * @param {boolean} [opts.json] json mode
111
+ * @param {NodeJS.WritableStream} [opts.stdout] stdout sink (testable)
112
+ * @param {NodeJS.WritableStream} [opts.stderr] stderr sink (testable)
113
+ */
114
+ function createLogger(opts = {}) {
115
+ const level = opts.level && LEVELS[opts.level] !== undefined ? opts.level : DEFAULT_LEVEL;
116
+ const threshold = LEVELS[level];
117
+ const json = Boolean(opts.json);
118
+ const stdout = opts.stdout || process.stdout;
119
+ const stderr = opts.stderr || process.stderr;
120
+
121
+ // JSON-mode accumulator. Flushed as a single JSON object on flush() or process exit.
122
+ const jsonState = {
123
+ status: 'ok',
124
+ version: null,
125
+ agents: [],
126
+ warnings: [],
127
+ errors: [],
128
+ messages: [],
129
+ };
130
+
131
+ function emit(channel, lvl, args) {
132
+ if (json) {
133
+ const text = args
134
+ .map((a) => (a instanceof Error ? a.stack || a.message : typeof a === 'object' ? safeStringify(a) : String(a)))
135
+ .join(' ');
136
+ if (lvl === 'error') jsonState.errors.push(text);
137
+ else if (lvl === 'warn') jsonState.warnings.push(text);
138
+ else jsonState.messages.push({ level: lvl, text });
139
+ if (lvl === 'error') jsonState.status = 'error';
140
+ else if (lvl === 'warn' && jsonState.status === 'ok') jsonState.status = 'warn';
141
+ return;
142
+ }
143
+ const stream = channel === 'stderr' ? stderr : stdout;
144
+ try {
145
+ stream.write(`${formatArgs(args)}\n`);
146
+ } catch {
147
+ /* broken pipe / closed stream — swallow */
148
+ }
149
+ }
150
+
151
+ function formatArgs(args) {
152
+ return args
153
+ .map((a) => {
154
+ if (a instanceof Error) return a.stack || a.message;
155
+ if (typeof a === 'object' && a !== null) return safeStringify(a);
156
+ return String(a);
157
+ })
158
+ .join(' ');
159
+ }
160
+
161
+ const api = {
162
+ level,
163
+ threshold,
164
+ json,
165
+
166
+ isEnabled(lvl) {
167
+ return LEVELS[lvl] !== undefined && LEVELS[lvl] <= threshold;
168
+ },
169
+
170
+ error(...args) {
171
+ // error is always captured (level 0)
172
+ emit('stderr', 'error', args);
173
+ },
174
+
175
+ warn(...args) {
176
+ if (threshold >= LEVELS.warn) emit('stderr', 'warn', args);
177
+ },
178
+
179
+ info(...args) {
180
+ if (threshold >= LEVELS.info) emit('stdout', 'info', args);
181
+ },
182
+
183
+ debug(...args) {
184
+ if (threshold >= LEVELS.debug) emit('stdout', 'debug', args);
185
+ },
186
+
187
+ /**
188
+ * Write raw text to stdout at info-or-higher. Used by commands that need
189
+ * to render tables / long output that should NOT appear on default runs.
190
+ */
191
+ print(...args) {
192
+ if (json) {
193
+ jsonState.messages.push({ level: 'info', text: formatArgs(args) });
194
+ return;
195
+ }
196
+ if (threshold >= LEVELS.info) {
197
+ try {
198
+ stdout.write(`${formatArgs(args)}\n`);
199
+ } catch { /* ignore */ }
200
+ }
201
+ },
202
+
203
+ /**
204
+ * Always-on stdout write. For output that MUST appear at default level
205
+ * (e.g. one-line status: "SINAPSE ready."). Respects --json and --quiet.
206
+ * --quiet: suppressed unless this is a direct error.
207
+ */
208
+ always(...args) {
209
+ if (json) {
210
+ jsonState.messages.push({ level: 'always', text: formatArgs(args) });
211
+ return;
212
+ }
213
+ if (threshold === LEVELS.error) return; // --quiet
214
+ try {
215
+ stdout.write(`${formatArgs(args)}\n`);
216
+ } catch { /* ignore */ }
217
+ },
218
+
219
+ /**
220
+ * Set the package version on the JSON state. Harmless in non-json mode.
221
+ */
222
+ setVersion(version) {
223
+ jsonState.version = version;
224
+ },
225
+
226
+ /**
227
+ * Push an agent entry into the JSON state. Harmless in non-json mode.
228
+ */
229
+ addAgent(name, meta = {}) {
230
+ jsonState.agents.push({ name, ...meta });
231
+ },
232
+
233
+ /**
234
+ * Flush the JSON state to stdout as a single structured object.
235
+ * Safe to call multiple times — no-op after the first call.
236
+ */
237
+ flush() {
238
+ if (!json || api._flushed) return;
239
+ api._flushed = true;
240
+ try {
241
+ stdout.write(`${JSON.stringify(jsonState)}\n`);
242
+ } catch { /* ignore */ }
243
+ },
244
+
245
+ _jsonState: jsonState,
246
+ _flushed: false,
247
+ };
248
+
249
+ return api;
250
+ }
251
+
252
+ /**
253
+ * Build the process-wide logger from process.argv. Cached: repeated calls return
254
+ * the same instance so `bin/cli.js` and `bin/sinapse.js` share state in a single run.
255
+ */
256
+ let _singleton = null;
257
+
258
+ function getLogger(argvOverride) {
259
+ if (_singleton) return _singleton;
260
+ const argv = argvOverride || process.argv.slice(2);
261
+ const flags = parseFlags(argv);
262
+ _singleton = createLogger({
263
+ level: resolveLevel(flags),
264
+ json: flags.json,
265
+ });
266
+ _singleton._flags = flags;
267
+
268
+ // Ensure JSON state is flushed on process exit so --json is reliable.
269
+ if (flags.json) {
270
+ const flush = () => { try { _singleton.flush(); } catch { /* noop */ } };
271
+ process.on('exit', flush);
272
+ process.on('beforeExit', flush);
273
+ }
274
+ return _singleton;
275
+ }
276
+
277
+ /** Reset the singleton (tests only). */
278
+ function _resetLogger() {
279
+ _singleton = null;
280
+ }
281
+
282
+ /**
283
+ * Decide whether the ASCII art header should be shown.
284
+ *
285
+ * show if: level >= info (i.e. --verbose or --debug)
286
+ * OR first run (no flag file)
287
+ *
288
+ * never show in --json mode or --quiet mode.
289
+ */
290
+ function shouldShowHeader(logger) {
291
+ if (!logger) return false;
292
+ if (logger.json) return false;
293
+ if (logger.threshold === LEVELS.error) return false; // --quiet
294
+ if (logger.threshold >= LEVELS.info) return true; // --verbose / --debug
295
+ return isFirstRun();
296
+ }
297
+
298
+ function safeStringify(value) {
299
+ try {
300
+ return JSON.stringify(value);
301
+ } catch {
302
+ return '[unserializable]';
303
+ }
304
+ }
305
+
306
+ module.exports = {
307
+ LEVELS,
308
+ LEVEL_NAMES,
309
+ DEFAULT_LEVEL,
310
+ FIRST_RUN_FLAG,
311
+ createLogger,
312
+ getLogger,
313
+ parseFlags,
314
+ resolveLevel,
315
+ isFirstRun,
316
+ markFirstRunDone,
317
+ shouldShowHeader,
318
+ _resetLogger,
319
+ };
@@ -12,7 +12,7 @@
12
12
 
13
13
  'use strict';
14
14
 
15
- const { spawn, execSync } = require('child_process');
15
+ const { spawn, execSync, execFileSync } = require('child_process');
16
16
  const fs = require('fs').promises;
17
17
  const fsSync = require('fs');
18
18
  const path = require('path');
@@ -554,7 +554,7 @@ async function spawnAgent(agent, task, options = {}) {
554
554
  SINAPSE_OUTPUT_DIR: opts.outputDir,
555
555
  };
556
556
 
557
- const result = execSync(`bash "${scriptPath}" ${args.join(' ')}`, {
557
+ const result = execFileSync('bash', [scriptPath, ...args], {
558
558
  encoding: 'utf8',
559
559
  timeout: opts.timeout,
560
560
  env,