sinapse-ai 1.6.1 → 1.8.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 (131) hide show
  1. package/.claude/CLAUDE.md +5 -11
  2. package/.claude/hooks/README.md +14 -1
  3. package/.claude/hooks/code-intel-pretool.cjs +115 -0
  4. package/.claude/hooks/enforce-delegation.cjs +31 -3
  5. package/.claude/hooks/enforce-framework-boundary.cjs +324 -0
  6. package/.claude/hooks/enforce-permission-mode.cjs +249 -0
  7. package/.claude/hooks/secret-scanning.cjs +34 -43
  8. package/.claude/hooks/synapse-engine.cjs +23 -23
  9. package/.claude/hooks/telemetry-post-tool.cjs +128 -0
  10. package/.claude/hooks/telemetry-stop.cjs +132 -0
  11. package/.claude/hooks/verify-packages.cjs +9 -2
  12. package/.claude/rules/documentation-first.md +1 -1
  13. package/.claude/rules/hook-governance.md +2 -0
  14. package/.sinapse-ai/cli/commands/health/index.js +24 -0
  15. package/.sinapse-ai/core/README.md +11 -0
  16. package/.sinapse-ai/core/config/config-loader.js +19 -0
  17. package/.sinapse-ai/core/config/merge-utils.js +8 -0
  18. package/.sinapse-ai/core/errors/constants.js +147 -0
  19. package/.sinapse-ai/core/errors/error-registry.js +176 -0
  20. package/.sinapse-ai/core/errors/index.js +50 -0
  21. package/.sinapse-ai/core/errors/serializer.js +147 -0
  22. package/.sinapse-ai/core/errors/sinapse-error.js +144 -0
  23. package/.sinapse-ai/core/errors/utils.js +187 -0
  24. package/.sinapse-ai/core/execution/build-orchestrator.js +47 -49
  25. package/.sinapse-ai/core/execution/build-state-manager.js +183 -31
  26. package/.sinapse-ai/core/execution/parallel-executor.js +7 -1
  27. package/.sinapse-ai/core/execution/semantic-merge-engine.js +26 -14
  28. package/.sinapse-ai/core/execution/subagent-dispatcher.js +201 -60
  29. package/.sinapse-ai/core/execution/wave-executor.js +4 -1
  30. package/.sinapse-ai/core/grounding/README.md +71 -11
  31. package/.sinapse-ai/core/health-check/checks/project/framework-config.js +38 -2
  32. package/.sinapse-ai/core/health-check/checks/project/package-json.js +47 -3
  33. package/.sinapse-ai/core/health-check/checks/services/gemini-cli.js +117 -0
  34. package/.sinapse-ai/core/health-check/checks/services/index.js +2 -0
  35. package/.sinapse-ai/core/health-check/healers/index.js +40 -3
  36. package/.sinapse-ai/core/ideation/ideation-engine.js +212 -107
  37. package/.sinapse-ai/core/ids/gate-evaluator.js +318 -0
  38. package/.sinapse-ai/core/ids/gates/g5-semantic-handshake.js +190 -0
  39. package/.sinapse-ai/core/ids/gates/g6-ci-integrity.js +162 -0
  40. package/.sinapse-ai/core/ids/index.js +30 -0
  41. package/.sinapse-ai/core/memory/__tests__/active-modules.verify.js +11 -0
  42. package/.sinapse-ai/core/memory/gotchas-memory.js +37 -2
  43. package/.sinapse-ai/core/orchestration/agent-invoker.js +29 -6
  44. package/.sinapse-ai/core/orchestration/brownfield-handler.js +36 -3
  45. package/.sinapse-ai/core/orchestration/condition-evaluator.js +57 -0
  46. package/.sinapse-ai/core/orchestration/executors/epic-3-executor.js +76 -5
  47. package/.sinapse-ai/core/orchestration/executors/epic-4-executor.js +63 -17
  48. package/.sinapse-ai/core/orchestration/executors/epic-6-executor.js +153 -41
  49. package/.sinapse-ai/core/orchestration/executors/epic-executor.js +40 -0
  50. package/.sinapse-ai/core/orchestration/greenfield-handler.js +87 -3
  51. package/.sinapse-ai/core/orchestration/master-orchestrator.js +150 -10
  52. package/.sinapse-ai/core/orchestration/parallel-executor.js +6 -1
  53. package/.sinapse-ai/core/orchestration/recovery-handler.js +81 -8
  54. package/.sinapse-ai/core/orchestration/workflow-executor.js +41 -0
  55. package/.sinapse-ai/core/registry/registry-loader.js +71 -5
  56. package/.sinapse-ai/core/registry/squad-agent-resolver.js +253 -0
  57. package/.sinapse-ai/core/synapse/context/context-tracker.js +104 -9
  58. package/.sinapse-ai/core/synapse/context/index.js +19 -0
  59. package/.sinapse-ai/core/synapse/context/semantic-handshake-engine.js +555 -0
  60. package/.sinapse-ai/core/synapse/diagnostics/collectors/pipeline-collector.js +4 -2
  61. package/.sinapse-ai/core/synapse/engine.js +43 -3
  62. package/.sinapse-ai/core/telemetry/ids-sink.js +188 -0
  63. package/.sinapse-ai/core/utils/output-formatter.js +8 -290
  64. package/.sinapse-ai/core/utils/spawn-safe.js +186 -0
  65. package/.sinapse-ai/core-config.yaml +68 -1
  66. package/.sinapse-ai/data/entity-registry.yaml +15082 -13618
  67. package/.sinapse-ai/data/registry-update-log.jsonl +143 -0
  68. package/.sinapse-ai/development/agents/developer.md +2 -0
  69. package/.sinapse-ai/development/agents/devops.md +9 -0
  70. package/.sinapse-ai/development/external-executors/README.md +18 -0
  71. package/.sinapse-ai/development/external-executors/codex.md +56 -0
  72. package/.sinapse-ai/development/scripts/populate-entity-registry.js +65 -9
  73. package/.sinapse-ai/development/scripts/squad/squad-downloader.js +169 -14
  74. package/.sinapse-ai/development/tasks/delegate-to-external-executor.md +152 -0
  75. package/.sinapse-ai/development/tasks/github-devops-pre-push-quality-gate.md +46 -29
  76. package/.sinapse-ai/development/tasks/update-sinapse.md +3 -3
  77. package/.sinapse-ai/hooks/sinapse-brand-grounding.cjs +4 -7
  78. package/.sinapse-ai/hooks/sinapse-ds-grounding.cjs +5 -8
  79. package/.sinapse-ai/hooks/sinapse-vault-grounding.cjs +6 -9
  80. package/.sinapse-ai/infrastructure/integrations/ai-providers/ai-provider-factory.js +4 -1
  81. package/.sinapse-ai/infrastructure/integrations/ai-providers/claude-provider.js +57 -55
  82. package/.sinapse-ai/infrastructure/integrations/pm-adapters/github-adapter.js +9 -7
  83. package/.sinapse-ai/infrastructure/scripts/ide-sync/gemini-commands.js +298 -0
  84. package/.sinapse-ai/infrastructure/scripts/ide-sync/index.js +127 -6
  85. package/.sinapse-ai/infrastructure/scripts/ide-sync/persona-renderer.js +97 -0
  86. package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/antigravity.js +121 -0
  87. package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/cursor.js +119 -0
  88. package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/github-copilot.js +191 -0
  89. package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/kimi.js +448 -0
  90. package/.sinapse-ai/install-manifest.yaml +218 -114
  91. package/.sinapse-ai/product/templates/engine/renderer.js +20 -1
  92. package/.sinapse-ai/scripts/pm.sh +18 -6
  93. package/bin/cli.js +17 -0
  94. package/bin/commands/agents.js +96 -0
  95. package/bin/commands/doctor.js +15 -0
  96. package/bin/commands/ideate.js +129 -0
  97. package/bin/commands/uninstall.js +40 -0
  98. package/bin/postinstall.js +50 -4
  99. package/bin/sinapse.js +146 -2
  100. package/bin/utils/secret-scanner-core.js +253 -0
  101. package/bin/utils/staged-secret-scan.js +106 -40
  102. package/docs/framework/collaboration-autonomy-plan.md +18 -18
  103. package/docs/guides/parallel-workflow.md +6 -6
  104. package/package.json +22 -5
  105. package/packages/installer/src/installer/git-hooks-installer.js +384 -0
  106. package/packages/installer/src/installer/sinapse-ai-installer.js +16 -0
  107. package/packages/installer/src/wizard/ide-config-generator.js +23 -0
  108. package/packages/installer/src/wizard/validators.js +38 -1
  109. package/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js +5 -1
  110. package/packages/installer/tests/unit/doctor/doctor-checks.test.js +44 -22
  111. package/packages/installer/tests/unit/git-hooks-installer.test.js +262 -0
  112. package/scripts/eval-runner.js +422 -0
  113. package/scripts/generate-install-manifest.js +13 -9
  114. package/scripts/generate-synapse-runtime.js +51 -0
  115. package/scripts/regenerate-orqx-stubs.ps1 +6 -5
  116. package/scripts/validate-all.js +1 -0
  117. package/scripts/validate-evals.js +466 -0
  118. package/scripts/validate-schemas.js +539 -0
  119. package/scripts/validate-squad-orqx.js +9 -2
  120. package/squads/claude-code-mastery/knowledge-base/memory-systems-reference.md +1 -1
  121. package/squads/squad-brand/templates/client-delivery-template.md +1 -1
  122. package/squads/squad-content/knowledge-base/social-compression-framework.md +1 -1
  123. package/squads/squad-council/knowledge-base/brand-strategy-models.md +1 -1
  124. package/.sinapse-ai/development/scripts/elicitation-engine.js +0 -385
  125. package/.sinapse-ai/development/scripts/elicitation-session-manager.js +0 -300
  126. package/.sinapse-ai/development/tasks/test-validation-task.md +0 -172
  127. package/docs/chrome-brain-upgrade-plan.md +0 -624
  128. package/docs/constitution-compliance.md +0 -87
  129. package/docs/mega-upgrade-orchestration-plan.md +0 -71
  130. package/docs/research-synthesis-for-upgrade.md +0 -511
  131. package/docs/security-audit-report.md +0 -306
@@ -13,9 +13,9 @@ The previous "safe collaboration" suggestions should be **partially approved**,
13
13
 
14
14
  ### Approved now
15
15
 
16
- - Standardize how Caio and Soier work in parallel inside the same repository
16
+ - Standardize how two collaborators work in parallel inside the same repository
17
17
  - Create a clear contributor operating model for framework changes
18
- - Define which changes Soier can make autonomously without waiting for Caio
18
+ - Define which changes a contributor can make autonomously without waiting for the maintainer
19
19
  - Reuse the existing SINAPSE infrastructure already present in the repository
20
20
 
21
21
  ### Not approved now
@@ -33,8 +33,8 @@ The core need is **not only Git safety**.
33
33
 
34
34
  The real requirement is:
35
35
 
36
- 1. Caio and Soier must be able to work in sync without overwriting each other.
37
- 2. Soier must be able to add framework features without depending on Caio to explain where things go or how to wire them in.
36
+ 1. Two collaborators must be able to work in sync without overwriting each other.
37
+ 2. A contributor must be able to add framework features without depending on the maintainer to explain where things go or how to wire them in.
38
38
 
39
39
  The repository already contains strong building blocks for this:
40
40
 
@@ -59,14 +59,14 @@ What is missing is a **single operating model** that tells a collaborator:
59
59
 
60
60
  ## Why Scope Reduction Is Correct
61
61
 
62
- If the team prioritizes generic template hardening first, it will improve portability but **not solve the immediate bottleneck**: Soier still may not know when a framework change is autonomous, coordinated, or blocked.
62
+ If the team prioritizes generic template hardening first, it will improve portability but **not solve the immediate bottleneck**: a contributor still may not know when a framework change is autonomous, coordinated, or blocked.
63
63
 
64
64
  If the team prioritizes contributor autonomy first, the outcome is immediately useful inside the active repository and can later be extracted into a stronger reusable template.
65
65
 
66
66
  Therefore the correct order is:
67
67
 
68
68
  1. Fix the operating model inside `sinapse-ai`
69
- 2. Prove it with Caio + Soier
69
+ 2. Prove it with both collaborators
70
70
  3. Only then generalize it into the reusable template
71
71
 
72
72
  ---
@@ -77,7 +77,7 @@ Therefore the correct order is:
77
77
 
78
78
  There are two branch models in the repository context:
79
79
 
80
- - Safe collaboration rule suggests human prefixes like `caio/feat/...` and `soier/fix/...`
80
+ - Safe collaboration rule suggests human prefixes like `alice/feat/...` and `bob/fix/...`
81
81
  - Worktree infrastructure currently creates `auto-claude/{storyId}`
82
82
 
83
83
  This divergence creates ambiguity and weakens adoption.
@@ -86,7 +86,7 @@ This divergence creates ambiguity and weakens adoption.
86
86
 
87
87
  The repo has generators, tasks, workflows, and standards, but there is no short operational guide saying:
88
88
 
89
- - "Soier can do these classes of framework changes alone"
89
+ - "A contributor can do these classes of framework changes alone"
90
90
  - "These paths require coordination first"
91
91
  - "After this type of change, run these sync commands"
92
92
 
@@ -102,9 +102,9 @@ Reviewer auto-assignment is documented, but `auto_assign_reviewers` is still `fa
102
102
 
103
103
  ## Orchestration Objective
104
104
 
105
- Create a **framework contributor mode** for Caio and Soier with these outcomes:
105
+ Create a **framework contributor mode** for two collaborators with these outcomes:
106
106
 
107
- - Soier can add supported framework features end-to-end without waiting for Caio
107
+ - A contributor can add supported framework features end-to-end without waiting for the maintainer
108
108
  - Git collisions are minimized by isolation plus coordination rules
109
109
  - Core-risk edits are routed through a tighter review path
110
110
  - Sync steps are deterministic for agent, workflow, template, and manifest changes
@@ -128,7 +128,7 @@ Create a **framework contributor mode** for Caio and Soier with these outcomes:
128
128
 
129
129
  ### Exit Criteria
130
130
 
131
- - Caio and Soier can classify any proposed change in under 1 minute
131
+ - Two collaborators can classify any proposed change in under 1 minute
132
132
  - Both know whether they can proceed alone or need coordination
133
133
  - No feature work starts directly on `main`
134
134
 
@@ -142,7 +142,7 @@ Create a **framework contributor mode** for Caio and Soier with these outcomes:
142
142
 
143
143
  ### Autonomous Lane
144
144
 
145
- Changes Soier should be able to make without waiting for Caio, as long as the story and quality gates are respected:
145
+ Changes a contributor should be able to make without waiting for the maintainer, as long as the story and quality gates are respected:
146
146
 
147
147
  - new or updated agent definitions
148
148
  - new or updated tasks
@@ -172,7 +172,7 @@ Changes that should stay under explicit authority:
172
172
 
173
173
  ### Exit Criteria
174
174
 
175
- - Soier can add a framework feature from story to PR inside the Autonomous Lane without asking where things belong
175
+ - A contributor can add a framework feature from story to PR inside the Autonomous Lane without asking where things belong
176
176
  - Coordinated Lane is clearly documented and followed
177
177
 
178
178
  ---
@@ -186,7 +186,7 @@ Changes that should stay under explicit authority:
186
186
  ### Recommended hardening now
187
187
 
188
188
  - enforce "no work on main" as team habit and documented rule
189
- - make reviewer routing explicit for Caio/Soier handoff
189
+ - make reviewer routing explicit for the maintainer/contributor handoff
190
190
  - standardize when to use worktrees versus plain feature branches
191
191
  - ensure manifest and sync steps are part of the contribution workflow
192
192
 
@@ -200,7 +200,7 @@ Changes that should stay under explicit authority:
200
200
  ### Exit Criteria
201
201
 
202
202
  - daily collaboration no longer depends on ad hoc verbal coordination
203
- - PR handoff between Caio and Soier is predictable
203
+ - PR handoff between two collaborators is predictable
204
204
  - no accidental drift in agent/config sync for framework changes
205
205
 
206
206
  ---
@@ -219,9 +219,9 @@ Changes that should stay under explicit authority:
219
219
 
220
220
  ## Success Metrics
221
221
 
222
- - Soier can independently ship a framework feature inside the Autonomous Lane
222
+ - A contributor can independently ship a framework feature inside the Autonomous Lane
223
223
  - Fewer edits start on `main`
224
- - Fewer handoffs depend on Caio explaining structure manually
224
+ - Fewer handoffs depend on the maintainer explaining structure manually
225
225
  - Reduced accidental omissions in `sync:ide`, skills sync, and manifest updates
226
226
  - PR review becomes the coordination point instead of synchronous chat
227
227
 
@@ -233,7 +233,7 @@ Approve the initiative, but with this narrowed scope:
233
233
 
234
234
  **Do not start by polishing the generic safe-collab template.**
235
235
 
236
- Start by formalizing the contributor operating model inside `sinapse-ai`, because that is the shortest path to real autonomy for Soier and safe parallel optimization for both of you.
236
+ Start by formalizing the contributor operating model inside `sinapse-ai`, because that is the shortest path to real autonomy for contributors and safe parallel optimization for the team.
237
237
 
238
238
  ---
239
239
 
@@ -1,4 +1,4 @@
1
- # Trabalhando em Paralelo — Guia para Caio e Matheus
1
+ # Trabalhando em Paralelo — Guia para dois colaboradores
2
2
 
3
3
  ## TL;DR
4
4
 
@@ -42,12 +42,12 @@ Para evitar que os dois mexam no mesmo arquivo ao mesmo tempo:
42
42
 
43
43
  | Area | Responsavel |
44
44
  |------|-------------|
45
- | Installer, CLI, hooks | Caio |
46
- | Squads, agents, tasks | Matheus |
45
+ | Installer, CLI, hooks | Colaborador A |
46
+ | Squads, agents, tasks | Colaborador B |
47
47
  | Docs, stories | Quem estiver trabalhando naquela feature |
48
48
  | Tests | Quem escreveu o codigo |
49
49
 
50
- Se precisar mexer na area do outro: **avisem antes no WhatsApp/Discord**.
50
+ Se precisar mexer na area do outro: **avisem antes no canal combinado do time**.
51
51
 
52
52
  ## Regra #2: Mudancas pequenas e frequentes
53
53
 
@@ -98,7 +98,7 @@ Em vez de comandos tecnicos, fale normalmente:
98
98
  | Voce diz | O agente entende |
99
99
  |----------|------------------|
100
100
  | "salva meu trabalho" | git add + commit |
101
- | "envia pro Soier revisar" | push + criar PR |
101
+ | "envia pro outro revisar" | push + criar PR |
102
102
  | "atualiza meu projeto" | git fetch + pull + merge |
103
- | "o que o Caio mudou?" | git log + diff |
103
+ | "o que o outro mudou?" | git log + diff |
104
104
  | "desfaz isso" | git revert (seguro) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sinapse-ai",
3
- "version": "1.6.1",
3
+ "version": "1.8.0",
4
4
  "description": "SINAPSE AI: Framework de orquestracao de IA — 18 squads, 189 agentes especializados",
5
5
  "bin": {
6
6
  "sinapse": "bin/sinapse.js",
@@ -43,17 +43,28 @@
43
43
  "docs/framework/",
44
44
  "docs/en/",
45
45
  "docs/pt/",
46
- "docs/*.md",
47
- "CHROME-BRAIN-INSTALL.md",
46
+ "docs/README.md",
47
+ "docs/getting-started.md",
48
+ "docs/troubleshooting.md",
49
+ "docs/ide-integration.md",
50
+ "docs/agent-reference-guide.md",
51
+ "docs/codex-integration-process.md",
52
+ "docs/codex-parity-program.md",
53
+ "docs/TELEMETRY.md",
48
54
  "README.md",
49
55
  "LICENSE"
50
56
  ],
51
57
  "scripts": {
52
58
  "format": "prettier --write \"**/*.md\"",
59
+ "generate:synapse": "node scripts/generate-synapse-runtime.js",
60
+ "pretest": "node scripts/generate-synapse-runtime.js",
53
61
  "test": "jest",
54
62
  "test:watch": "jest --watch",
55
63
  "test:coverage": "jest --coverage",
56
64
  "test:health-check": "mocha tests/health-check/**/*.test.js --timeout 30000",
65
+ "eval": "node scripts/eval-runner.js",
66
+ "validate:evals": "node scripts/validate-evals.js",
67
+ "validate:schemas": "node scripts/validate-schemas.js",
57
68
  "lint": "eslint . --cache --cache-location .eslintcache",
58
69
  "typecheck": "tsc --noEmit",
59
70
  "release": "semantic-release",
@@ -74,6 +85,11 @@
74
85
  "sync:ide:claude": "node .sinapse-ai/infrastructure/scripts/ide-sync/index.js sync --ide claude-code",
75
86
  "sync:ide:codex": "node .sinapse-ai/infrastructure/scripts/sync-codex-local-first.js",
76
87
  "validate:claude-sync": "node .sinapse-ai/infrastructure/scripts/ide-sync/index.js validate --ide claude-code --strict",
88
+ "validate:gemini-sync": "node .sinapse-ai/infrastructure/scripts/ide-sync/index.js validate --ide gemini --strict",
89
+ "validate:cursor-sync": "node .sinapse-ai/infrastructure/scripts/ide-sync/index.js validate --ide cursor --strict",
90
+ "validate:antigravity-sync": "node .sinapse-ai/infrastructure/scripts/ide-sync/index.js validate --ide antigravity --strict",
91
+ "validate:copilot-sync": "node .sinapse-ai/infrastructure/scripts/ide-sync/index.js validate --ide github-copilot --strict",
92
+ "validate:kimi-sync": "node .sinapse-ai/infrastructure/scripts/ide-sync/index.js validate --ide kimi --strict",
77
93
  "validate:claude-integration": "node .sinapse-ai/infrastructure/scripts/validate-claude-integration.js",
78
94
  "validate:squad-schema": "node scripts/validate-squad-yaml.js",
79
95
  "validate:squad-schema:strict": "node scripts/validate-squad-yaml.js --strict",
@@ -106,7 +122,7 @@
106
122
  "validate:publish": "node bin/utils/validate-publish.js",
107
123
  "brand": "node scripts/sinapse-patch.js",
108
124
  "prepublishOnly": "node bin/utils/validate-publish.js && npm run generate:manifest && npm run validate:manifest",
109
- "postinstall": "node bin/postinstall.js",
125
+ "setup": "node bin/postinstall.js",
110
126
  "prepare": "husky"
111
127
  },
112
128
  "dependencies": {
@@ -118,6 +134,7 @@
118
134
  "chokidar": "^3.5.3",
119
135
  "cli-progress": "^3.12.0",
120
136
  "commander": "^12.1.0",
137
+ "cross-spawn": "^7.0.6",
121
138
  "execa": "^5.1.1",
122
139
  "fast-glob": "^3.3.3",
123
140
  "fs-extra": "^11.3.2",
@@ -198,7 +215,7 @@
198
215
  "diff": "^8.0.3",
199
216
  "serialize-javascript": "^7.0.5",
200
217
  "picomatch": "^4.0.4",
201
- "brace-expansion": "^5.0.5",
218
+ "brace-expansion": "^5.0.6",
202
219
  "fast-uri": "^3.1.2",
203
220
  "ip-address": "^10.1.1"
204
221
  }
@@ -0,0 +1,384 @@
1
+ /**
2
+ * SINAPSE Git Hooks Installer (Stream B — Frente 4.2)
3
+ *
4
+ * Propagates the SINAPSE secret-scan guard into every project the framework
5
+ * creates or clones. Instead of relying on husky (which the target project may
6
+ * not have), it uses git's native `core.hooksPath` mechanism: a managed hooks
7
+ * directory under `.sinapse-ai/git-hooks/` with Node `.js` hook scripts.
8
+ *
9
+ * Cross-platform by construction:
10
+ * - Hooks are `.js` files with `#!/usr/bin/env node` shebang. Git on every
11
+ * platform invokes the file as an executable; the shebang routes it to node.
12
+ * We NEVER emit `.sh` or `.py` (they would not run on Caio's Windows).
13
+ * - Execute bit is set via `fs.chmodSync(path, 0o755)` on unix; on win32 the
14
+ * filesystem has no execute bit so chmod is a tolerated no-op.
15
+ * - `core.hooksPath` is set via `runSafe` (cross-spawn, argv-based, never
16
+ * `shell: true`) — no shell metacharacter interpolation, injection-proof.
17
+ *
18
+ * Idempotent: running install twice does not duplicate hooks or chaining. The
19
+ * managed hook is overwritten with the canonical content each run, and the
20
+ * husky chain is detected at runtime (not baked in), so it always reflects the
21
+ * current state of the target project.
22
+ *
23
+ * Husky / existing-hooks safety: the generated pre-commit detects an existing
24
+ * `.husky/pre-commit` (or a prior `core.hooksPath` hook backed up during
25
+ * takeover) and CHAINS to it — it never silently discards the project's own
26
+ * hooks.
27
+ *
28
+ * Callers (Article XI — no orphan):
29
+ * - installSinapseCore() in sinapse-ai-installer.js (the `sinapse init` flow)
30
+ * - GreenfieldHandler._ensureGitHooks() in greenfield-handler.js (Phase 0)
31
+ *
32
+ * @module installer/git-hooks-installer
33
+ */
34
+
35
+ 'use strict';
36
+
37
+ const fs = require('fs');
38
+ const path = require('path');
39
+
40
+ // runSafe lives in the framework core. Resolve it relative to this file so the
41
+ // module works both inside the sinapse-ai repo and inside an installed package.
42
+ // packages/installer/src/installer -> repo root -> .sinapse-ai/core/utils
43
+ const { runSafe } = require('../../../../.sinapse-ai/core/utils/spawn-safe');
44
+
45
+ /**
46
+ * Name of the managed hooks directory, relative to the project root.
47
+ * Kept under `.sinapse-ai/` so it travels with the framework install and is
48
+ * obviously framework-owned.
49
+ * @constant {string}
50
+ */
51
+ const MANAGED_HOOKS_DIRNAME = path.join('.sinapse-ai', 'git-hooks');
52
+
53
+ /**
54
+ * Source scanner files copied into `<hooksDir>/lib/` so the generated hook can
55
+ * `require` them with a stable relative path regardless of where the project
56
+ * lives. `staged-secret-scan.js` is self-contained (only `child_process`).
57
+ * @constant {Array<{from: string, to: string}>}
58
+ */
59
+ const SCANNER_SOURCES = [
60
+ {
61
+ from: path.join(__dirname, '..', '..', '..', '..', 'bin', 'utils', 'staged-secret-scan.js'),
62
+ to: 'staged-secret-scan.js',
63
+ },
64
+ // staged-secret-scan.js requires secret-scanner-core.js via __dirname, so the
65
+ // core must travel with it into the lib/ bundle. Both files use only Node built-ins.
66
+ {
67
+ from: path.join(__dirname, '..', '..', '..', '..', 'bin', 'utils', 'secret-scanner-core.js'),
68
+ to: 'secret-scanner-core.js',
69
+ },
70
+ ];
71
+
72
+ /**
73
+ * Marker comment written into managed hooks so we can recognize (and safely
74
+ * overwrite) our own hooks without clobbering a user-authored one.
75
+ * @constant {string}
76
+ */
77
+ const MANAGED_MARKER = 'SINAPSE-MANAGED-GIT-HOOK';
78
+
79
+ /**
80
+ * Detect whether the target project already has a husky pre-commit hook.
81
+ *
82
+ * @param {string} projectDir - Absolute path to the project root.
83
+ * @returns {{ hasHusky: boolean, huskyPreCommit: string|null }}
84
+ */
85
+ function detectHusky(projectDir) {
86
+ const huskyPreCommit = path.join(projectDir, '.husky', 'pre-commit');
87
+ const hasHusky = fs.existsSync(huskyPreCommit);
88
+ return { hasHusky, huskyPreCommit: hasHusky ? huskyPreCommit : null };
89
+ }
90
+
91
+ /**
92
+ * Read the currently-configured `core.hooksPath` for a project (if any).
93
+ *
94
+ * @param {string} projectDir - Absolute path to the project root.
95
+ * @returns {Promise<string|null>} Configured value, or null if unset/error.
96
+ */
97
+ async function getCoreHooksPath(projectDir) {
98
+ try {
99
+ const result = await runSafe('git', ['-C', projectDir, 'config', '--get', 'core.hooksPath']);
100
+ if (result.success) {
101
+ const value = (result.stdout || '').trim();
102
+ return value || null;
103
+ }
104
+ } catch {
105
+ // git unavailable / not a repo — fall through to null.
106
+ }
107
+ return null;
108
+ }
109
+
110
+ /**
111
+ * Set `core.hooksPath` for a project using argv-safe spawning (no shell).
112
+ *
113
+ * @param {string} projectDir - Absolute path to the project root.
114
+ * @param {string} hooksPathValue - Path to write into core.hooksPath (relative
115
+ * to projectDir is preferred so the repo stays portable).
116
+ * @returns {Promise<{ success: boolean, error: (string|null) }>}
117
+ */
118
+ async function setCoreHooksPath(projectDir, hooksPathValue) {
119
+ try {
120
+ const result = await runSafe('git', ['-C', projectDir, 'config', 'core.hooksPath', hooksPathValue]);
121
+ if (result.success) {
122
+ return { success: true, error: null };
123
+ }
124
+ return { success: false, error: (result.stderr || '').trim() || `git exited with code ${result.code}` };
125
+ } catch (error) {
126
+ return { success: false, error: error.message };
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Write a managed hook file with the node shebang and set the execute bit.
132
+ *
133
+ * @param {string} hooksDir - Absolute path to the managed hooks directory.
134
+ * @param {string} hookName - Hook name (e.g. 'pre-commit').
135
+ * @param {string} scriptContent - Full file content (must start with shebang).
136
+ * @returns {string} Absolute path to the written hook.
137
+ */
138
+ function writeManagedHook(hooksDir, hookName, scriptContent) {
139
+ fs.mkdirSync(hooksDir, { recursive: true });
140
+ const hookPath = path.join(hooksDir, hookName);
141
+ fs.writeFileSync(hookPath, scriptContent, 'utf8');
142
+ try {
143
+ // 0o755 = rwxr-xr-x. No-op effect on win32 (no execute bit), tolerated.
144
+ fs.chmodSync(hookPath, 0o755);
145
+ } catch {
146
+ // chmod can fail on exotic filesystems; the shebang still routes to node
147
+ // and on Windows the bit is irrelevant. Non-fatal.
148
+ }
149
+ return hookPath;
150
+ }
151
+
152
+ /**
153
+ * Copy the scanner library files into `<hooksDir>/lib/` so the hook can require
154
+ * them with a stable relative path. Idempotent (overwrites).
155
+ *
156
+ * @param {string} hooksDir - Absolute path to the managed hooks directory.
157
+ * @returns {string[]} Relative names of the copied scanner files.
158
+ */
159
+ function copyScannerLib(hooksDir) {
160
+ const libDir = path.join(hooksDir, 'lib');
161
+ fs.mkdirSync(libDir, { recursive: true });
162
+
163
+ const copied = [];
164
+ for (const source of SCANNER_SOURCES) {
165
+ if (fs.existsSync(source.from)) {
166
+ fs.copyFileSync(source.from, path.join(libDir, source.to));
167
+ copied.push(source.to);
168
+ }
169
+ }
170
+ return copied;
171
+ }
172
+
173
+ /**
174
+ * Build the content of the managed `pre-commit` Node hook.
175
+ *
176
+ * The hook:
177
+ * 1. Runs the bundled staged-secret-scan against the staged index.
178
+ * 2. Blocks the commit (exit 1) on any finding, printing redacted reasons.
179
+ * 3. Fail-CLOSED: if the scanner cannot be loaded/run, the commit is blocked
180
+ * (a broken guard must never silently allow secrets through).
181
+ * 4. Chains to a pre-existing `.husky/pre-commit` (or a backed-up prior hook)
182
+ * so the project's own hooks still run.
183
+ *
184
+ * @param {Object} [options]
185
+ * @param {string|null} [options.chainHookRel] - Relative path (from project
186
+ * root) of a prior hook to chain to after the scan passes. When null, the
187
+ * hook auto-detects `.husky/pre-commit` at runtime.
188
+ * @returns {string} Hook file content.
189
+ */
190
+ function buildPreCommitHook(options = {}) {
191
+ const chainHookRel = options.chainHookRel || null;
192
+ const chainLiteral = chainHookRel ? JSON.stringify(chainHookRel) : 'null';
193
+
194
+ return `#!/usr/bin/env node
195
+ 'use strict';
196
+ /* ${MANAGED_MARKER} — Auto-generated by SINAPSE git-hooks-installer. Do not edit manually. */
197
+ /* Re-run \`sinapse init\` (or the greenfield bootstrap) to regenerate. */
198
+
199
+ const path = require('path');
200
+ const fs = require('fs');
201
+ const { spawnSync } = require('child_process');
202
+
203
+ const projectRoot = process.cwd();
204
+
205
+ // --- 1. SINAPSE staged secret scan (fail-CLOSED) ----------------------------
206
+ let scanStagedFiles;
207
+ let getStagedFiles;
208
+ try {
209
+ // The scanner is bundled next to this hook under ./lib so the require path is
210
+ // stable no matter where the project lives.
211
+ ({ scanStagedFiles, getStagedFiles } = require(path.join(__dirname, 'lib', 'staged-secret-scan.js')));
212
+ } catch (loadErr) {
213
+ process.stderr.write('SINAPSE Secret Scan: scanner could not be loaded — blocking commit (fail-closed).\\n');
214
+ process.stderr.write(' ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)) + '\\n');
215
+ process.exit(1);
216
+ }
217
+
218
+ try {
219
+ const files = getStagedFiles();
220
+ const findings = scanStagedFiles(files);
221
+ if (findings.length > 0) {
222
+ process.stderr.write('\\nSINAPSE Secret Scan: commit blocked.\\n\\n');
223
+ for (const f of findings) {
224
+ process.stderr.write(' - ' + f.filePath + ': ' + f.reason + '\\n');
225
+ }
226
+ process.stderr.write('\\nRemove the sensitive content before committing.\\n\\n');
227
+ process.exit(1);
228
+ }
229
+ } catch (scanErr) {
230
+ process.stderr.write('SINAPSE Secret Scan: scan failed — blocking commit (fail-closed).\\n');
231
+ process.stderr.write(' ' + (scanErr && scanErr.message ? scanErr.message : String(scanErr)) + '\\n');
232
+ process.exit(1);
233
+ }
234
+
235
+ // --- 2. Chain to a pre-existing hook (husky or backed-up prior hook) --------
236
+ const explicitChain = ${chainLiteral};
237
+ const candidates = [];
238
+ if (explicitChain) {
239
+ candidates.push(path.resolve(projectRoot, explicitChain));
240
+ }
241
+ candidates.push(path.join(projectRoot, '.husky', 'pre-commit'));
242
+
243
+ const selfPath = __filename;
244
+ for (const candidate of candidates) {
245
+ if (!candidate || !fs.existsSync(candidate)) continue;
246
+ if (path.resolve(candidate) === path.resolve(selfPath)) continue; // never recurse into self
247
+
248
+ const isJs = candidate.endsWith('.js') || candidate.endsWith('.cjs');
249
+ const cmd = isJs ? process.execPath : candidate;
250
+ const args = isJs ? [candidate] : [];
251
+ const res = spawnSync(cmd, args, { stdio: 'inherit', cwd: projectRoot });
252
+
253
+ if (res.error) {
254
+ // A shell-script hook (.husky uses sh) may not be directly executable via
255
+ // spawnSync on Windows. Try 'sh' as an interpreter before giving up.
256
+ if (!isJs) {
257
+ const shRes = spawnSync('sh', [candidate], { stdio: 'inherit', cwd: projectRoot });
258
+ if (!shRes.error) {
259
+ if (typeof shRes.status === 'number' && shRes.status !== 0) process.exit(shRes.status);
260
+ break;
261
+ }
262
+ }
263
+ process.stderr.write('SINAPSE pre-commit: failed to run chained hook ' + candidate + ': ' + res.error.message + '\\n');
264
+ process.exit(1);
265
+ }
266
+ if (typeof res.status === 'number' && res.status !== 0) {
267
+ process.exit(res.status);
268
+ }
269
+ break; // only chain to the first existing prior hook
270
+ }
271
+
272
+ process.exit(0);
273
+ `;
274
+ }
275
+
276
+ /**
277
+ * Install the SINAPSE git guard into a project.
278
+ *
279
+ * Steps:
280
+ * 1. Verify the target is a git repo (best-effort; we still write the hooks
281
+ * so a later `git init` picks them up via core.hooksPath).
282
+ * 2. If a non-SINAPSE `core.hooksPath` is already set, preserve it by chaining
283
+ * (we do not blow away a project's existing managed hooks dir).
284
+ * 3. Create `.sinapse-ai/git-hooks/`, bundle the scanner lib, write pre-commit.
285
+ * 4. Point `core.hooksPath` at the managed dir.
286
+ *
287
+ * Idempotent. Multiplatform. No shell.
288
+ *
289
+ * @param {Object} options
290
+ * @param {string} options.projectDir - Absolute path to the project root.
291
+ * @returns {Promise<{
292
+ * success: boolean,
293
+ * hooksDir: string,
294
+ * huskyDetected: boolean,
295
+ * chainedTo: (string|null),
296
+ * coreHooksPathSet: boolean,
297
+ * skipped: boolean,
298
+ * error: (string|null),
299
+ * }>}
300
+ */
301
+ async function installGitHooks(options = {}) {
302
+ const projectDir = options.projectDir;
303
+
304
+ const result = {
305
+ success: false,
306
+ hooksDir: null,
307
+ huskyDetected: false,
308
+ chainedTo: null,
309
+ coreHooksPathSet: false,
310
+ skipped: false,
311
+ error: null,
312
+ };
313
+
314
+ if (!projectDir || typeof projectDir !== 'string') {
315
+ result.error = 'installGitHooks requires options.projectDir (absolute string path)';
316
+ return result;
317
+ }
318
+
319
+ try {
320
+ const hooksDir = path.join(projectDir, MANAGED_HOOKS_DIRNAME);
321
+ result.hooksDir = hooksDir;
322
+
323
+ // Detect husky so we can chain to it (and report it).
324
+ const { hasHusky } = detectHusky(projectDir);
325
+ result.huskyDetected = hasHusky;
326
+ if (hasHusky) {
327
+ result.chainedTo = path.join('.husky', 'pre-commit');
328
+ }
329
+
330
+ // Detect a pre-existing non-SINAPSE core.hooksPath. If the project already
331
+ // routes hooks somewhere that ISN'T ours, chain to that dir's pre-commit so
332
+ // we never silently disable the project's existing guard.
333
+ const existingHooksPath = await getCoreHooksPath(projectDir);
334
+ let explicitChain = null;
335
+ if (existingHooksPath) {
336
+ const resolvedExisting = path.resolve(projectDir, existingHooksPath);
337
+ const resolvedOurs = path.resolve(hooksDir);
338
+ if (resolvedExisting !== resolvedOurs) {
339
+ const existingPreCommit = path.join(resolvedExisting, 'pre-commit');
340
+ if (fs.existsSync(existingPreCommit)) {
341
+ // Store as a project-relative path for portability.
342
+ explicitChain = path.relative(projectDir, existingPreCommit);
343
+ result.chainedTo = explicitChain;
344
+ }
345
+ }
346
+ }
347
+
348
+ // Bundle the scanner lib and write the managed pre-commit hook.
349
+ copyScannerLib(hooksDir);
350
+ writeManagedHook(hooksDir, 'pre-commit', buildPreCommitHook({ chainHookRel: explicitChain }));
351
+
352
+ // Point core.hooksPath at our managed dir (project-relative for portability).
353
+ const relHooksDir = MANAGED_HOOKS_DIRNAME.split(path.sep).join('/');
354
+ const set = await setCoreHooksPath(projectDir, relHooksDir);
355
+ result.coreHooksPathSet = set.success;
356
+
357
+ if (!set.success) {
358
+ // Hooks are on disk; only the git config wiring failed. Surface a soft
359
+ // error but keep the artifacts so a later retry / manual `git config`
360
+ // completes the wiring. Not fatal to the overall install.
361
+ result.error = set.error;
362
+ result.success = false;
363
+ return result;
364
+ }
365
+
366
+ result.success = true;
367
+ return result;
368
+ } catch (error) {
369
+ result.error = error.message;
370
+ return result;
371
+ }
372
+ }
373
+
374
+ module.exports = {
375
+ installGitHooks,
376
+ detectHusky,
377
+ getCoreHooksPath,
378
+ setCoreHooksPath,
379
+ writeManagedHook,
380
+ copyScannerLib,
381
+ buildPreCommitHook,
382
+ MANAGED_HOOKS_DIRNAME,
383
+ MANAGED_MARKER,
384
+ };
@@ -335,6 +335,22 @@ async function installSinapseCore(options = {}) {
335
335
  }
336
336
  }
337
337
 
338
+ // Stream B (Frente 4.2): propagate the SINAPSE secret-scan guard into the
339
+ // target project via git core.hooksPath + a managed Node pre-commit hook.
340
+ // Best-effort: a hooks-wiring failure must not abort a successful install.
341
+ spinner.text = 'Installing git secret-scan guard...';
342
+ result.gitHooksInstalled = false;
343
+ try {
344
+ const { installGitHooks } = require('./git-hooks-installer');
345
+ const hooksResult = await installGitHooks({ projectDir: targetDir });
346
+ result.gitHooksInstalled = hooksResult.success;
347
+ if (!hooksResult.success && hooksResult.error) {
348
+ result.errors.push(`Git hooks warning: ${hooksResult.error}`);
349
+ }
350
+ } catch (hooksError) {
351
+ result.errors.push(`Git hooks warning: ${hooksError.message}`);
352
+ }
353
+
338
354
  result.success = true;
339
355
  spinner.succeed(`SINAPSE core installed (${result.installedFiles.length} files)`);
340
356