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.
- package/.claude/CLAUDE.md +5 -11
- package/.claude/hooks/README.md +14 -1
- package/.claude/hooks/code-intel-pretool.cjs +115 -0
- package/.claude/hooks/enforce-delegation.cjs +31 -3
- package/.claude/hooks/enforce-framework-boundary.cjs +324 -0
- package/.claude/hooks/enforce-permission-mode.cjs +249 -0
- package/.claude/hooks/secret-scanning.cjs +34 -43
- package/.claude/hooks/synapse-engine.cjs +23 -23
- package/.claude/hooks/telemetry-post-tool.cjs +128 -0
- package/.claude/hooks/telemetry-stop.cjs +132 -0
- package/.claude/hooks/verify-packages.cjs +9 -2
- package/.claude/rules/documentation-first.md +1 -1
- package/.claude/rules/hook-governance.md +2 -0
- package/.sinapse-ai/cli/commands/health/index.js +24 -0
- package/.sinapse-ai/core/README.md +11 -0
- package/.sinapse-ai/core/config/config-loader.js +19 -0
- package/.sinapse-ai/core/config/merge-utils.js +8 -0
- package/.sinapse-ai/core/errors/constants.js +147 -0
- package/.sinapse-ai/core/errors/error-registry.js +176 -0
- package/.sinapse-ai/core/errors/index.js +50 -0
- package/.sinapse-ai/core/errors/serializer.js +147 -0
- package/.sinapse-ai/core/errors/sinapse-error.js +144 -0
- package/.sinapse-ai/core/errors/utils.js +187 -0
- package/.sinapse-ai/core/execution/build-orchestrator.js +47 -49
- package/.sinapse-ai/core/execution/build-state-manager.js +183 -31
- package/.sinapse-ai/core/execution/parallel-executor.js +7 -1
- package/.sinapse-ai/core/execution/semantic-merge-engine.js +26 -14
- package/.sinapse-ai/core/execution/subagent-dispatcher.js +201 -60
- package/.sinapse-ai/core/execution/wave-executor.js +4 -1
- package/.sinapse-ai/core/grounding/README.md +71 -11
- package/.sinapse-ai/core/health-check/checks/project/framework-config.js +38 -2
- package/.sinapse-ai/core/health-check/checks/project/package-json.js +47 -3
- package/.sinapse-ai/core/health-check/checks/services/gemini-cli.js +117 -0
- package/.sinapse-ai/core/health-check/checks/services/index.js +2 -0
- package/.sinapse-ai/core/health-check/healers/index.js +40 -3
- package/.sinapse-ai/core/ideation/ideation-engine.js +212 -107
- package/.sinapse-ai/core/ids/gate-evaluator.js +318 -0
- package/.sinapse-ai/core/ids/gates/g5-semantic-handshake.js +190 -0
- package/.sinapse-ai/core/ids/gates/g6-ci-integrity.js +162 -0
- package/.sinapse-ai/core/ids/index.js +30 -0
- package/.sinapse-ai/core/memory/__tests__/active-modules.verify.js +11 -0
- package/.sinapse-ai/core/memory/gotchas-memory.js +37 -2
- package/.sinapse-ai/core/orchestration/agent-invoker.js +29 -6
- package/.sinapse-ai/core/orchestration/brownfield-handler.js +36 -3
- package/.sinapse-ai/core/orchestration/condition-evaluator.js +57 -0
- package/.sinapse-ai/core/orchestration/executors/epic-3-executor.js +76 -5
- package/.sinapse-ai/core/orchestration/executors/epic-4-executor.js +63 -17
- package/.sinapse-ai/core/orchestration/executors/epic-6-executor.js +153 -41
- package/.sinapse-ai/core/orchestration/executors/epic-executor.js +40 -0
- package/.sinapse-ai/core/orchestration/greenfield-handler.js +87 -3
- package/.sinapse-ai/core/orchestration/master-orchestrator.js +150 -10
- package/.sinapse-ai/core/orchestration/parallel-executor.js +6 -1
- package/.sinapse-ai/core/orchestration/recovery-handler.js +81 -8
- package/.sinapse-ai/core/orchestration/workflow-executor.js +41 -0
- package/.sinapse-ai/core/registry/registry-loader.js +71 -5
- package/.sinapse-ai/core/registry/squad-agent-resolver.js +253 -0
- package/.sinapse-ai/core/synapse/context/context-tracker.js +104 -9
- package/.sinapse-ai/core/synapse/context/index.js +19 -0
- package/.sinapse-ai/core/synapse/context/semantic-handshake-engine.js +555 -0
- package/.sinapse-ai/core/synapse/diagnostics/collectors/pipeline-collector.js +4 -2
- package/.sinapse-ai/core/synapse/engine.js +43 -3
- package/.sinapse-ai/core/telemetry/ids-sink.js +188 -0
- package/.sinapse-ai/core/utils/output-formatter.js +8 -290
- package/.sinapse-ai/core/utils/spawn-safe.js +186 -0
- package/.sinapse-ai/core-config.yaml +68 -1
- package/.sinapse-ai/data/entity-registry.yaml +15082 -13618
- package/.sinapse-ai/data/registry-update-log.jsonl +143 -0
- package/.sinapse-ai/development/agents/developer.md +2 -0
- package/.sinapse-ai/development/agents/devops.md +9 -0
- package/.sinapse-ai/development/external-executors/README.md +18 -0
- package/.sinapse-ai/development/external-executors/codex.md +56 -0
- package/.sinapse-ai/development/scripts/populate-entity-registry.js +65 -9
- package/.sinapse-ai/development/scripts/squad/squad-downloader.js +169 -14
- package/.sinapse-ai/development/tasks/delegate-to-external-executor.md +152 -0
- package/.sinapse-ai/development/tasks/github-devops-pre-push-quality-gate.md +46 -29
- package/.sinapse-ai/development/tasks/update-sinapse.md +3 -3
- package/.sinapse-ai/hooks/sinapse-brand-grounding.cjs +4 -7
- package/.sinapse-ai/hooks/sinapse-ds-grounding.cjs +5 -8
- package/.sinapse-ai/hooks/sinapse-vault-grounding.cjs +6 -9
- package/.sinapse-ai/infrastructure/integrations/ai-providers/ai-provider-factory.js +4 -1
- package/.sinapse-ai/infrastructure/integrations/ai-providers/claude-provider.js +57 -55
- package/.sinapse-ai/infrastructure/integrations/pm-adapters/github-adapter.js +9 -7
- package/.sinapse-ai/infrastructure/scripts/ide-sync/gemini-commands.js +298 -0
- package/.sinapse-ai/infrastructure/scripts/ide-sync/index.js +127 -6
- package/.sinapse-ai/infrastructure/scripts/ide-sync/persona-renderer.js +97 -0
- package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/antigravity.js +121 -0
- package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/cursor.js +119 -0
- package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/github-copilot.js +191 -0
- package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/kimi.js +448 -0
- package/.sinapse-ai/install-manifest.yaml +218 -114
- package/.sinapse-ai/product/templates/engine/renderer.js +20 -1
- package/.sinapse-ai/scripts/pm.sh +18 -6
- package/bin/cli.js +17 -0
- package/bin/commands/agents.js +96 -0
- package/bin/commands/doctor.js +15 -0
- package/bin/commands/ideate.js +129 -0
- package/bin/commands/uninstall.js +40 -0
- package/bin/postinstall.js +50 -4
- package/bin/sinapse.js +146 -2
- package/bin/utils/secret-scanner-core.js +253 -0
- package/bin/utils/staged-secret-scan.js +106 -40
- package/docs/framework/collaboration-autonomy-plan.md +18 -18
- package/docs/guides/parallel-workflow.md +6 -6
- package/package.json +22 -5
- package/packages/installer/src/installer/git-hooks-installer.js +384 -0
- package/packages/installer/src/installer/sinapse-ai-installer.js +16 -0
- package/packages/installer/src/wizard/ide-config-generator.js +23 -0
- package/packages/installer/src/wizard/validators.js +38 -1
- package/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js +5 -1
- package/packages/installer/tests/unit/doctor/doctor-checks.test.js +44 -22
- package/packages/installer/tests/unit/git-hooks-installer.test.js +262 -0
- package/scripts/eval-runner.js +422 -0
- package/scripts/generate-install-manifest.js +13 -9
- package/scripts/generate-synapse-runtime.js +51 -0
- package/scripts/regenerate-orqx-stubs.ps1 +6 -5
- package/scripts/validate-all.js +1 -0
- package/scripts/validate-evals.js +466 -0
- package/scripts/validate-schemas.js +539 -0
- package/scripts/validate-squad-orqx.js +9 -2
- package/squads/claude-code-mastery/knowledge-base/memory-systems-reference.md +1 -1
- package/squads/squad-brand/templates/client-delivery-template.md +1 -1
- package/squads/squad-content/knowledge-base/social-compression-framework.md +1 -1
- package/squads/squad-council/knowledge-base/brand-strategy-models.md +1 -1
- package/.sinapse-ai/development/scripts/elicitation-engine.js +0 -385
- package/.sinapse-ai/development/scripts/elicitation-session-manager.js +0 -300
- package/.sinapse-ai/development/tasks/test-validation-task.md +0 -172
- package/docs/chrome-brain-upgrade-plan.md +0 -624
- package/docs/constitution-compliance.md +0 -87
- package/docs/mega-upgrade-orchestration-plan.md +0 -71
- package/docs/research-synthesis-for-upgrade.md +0 -511
- 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
|
|
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
|
|
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.
|
|
37
|
-
2.
|
|
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**:
|
|
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
|
|
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 `
|
|
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
|
-
- "
|
|
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
|
|
105
|
+
Create a **framework contributor mode** for two collaborators with these outcomes:
|
|
106
106
|
|
|
107
|
-
-
|
|
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
|
-
-
|
|
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
|
|
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
|
-
-
|
|
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
|
|
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
|
|
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
|
-
-
|
|
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
|
|
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
|
|
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
|
|
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 |
|
|
46
|
-
| Squads, agents, tasks |
|
|
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
|
|
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
|
|
101
|
+
| "envia pro outro revisar" | push + criar PR |
|
|
102
102
|
| "atualiza meu projeto" | git fetch + pull + merge |
|
|
103
|
-
| "o que o
|
|
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.
|
|
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
|
|
47
|
-
"
|
|
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
|
-
"
|
|
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.
|
|
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
|
|