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
@@ -205,6 +205,23 @@ class TemplateRenderer {
205
205
  this.customHelpers = new Map();
206
206
  registerDefaultHelpers(this.handlebars);
207
207
 
208
+ // HTML-escape policy for compiled templates.
209
+ //
210
+ // Default is `noEscape: true` (HTML escaping OFF) BY DESIGN. This renderer
211
+ // produces Markdown / plain-text documents (ADR, PRD, story, epic, task —
212
+ // see SUPPORTED_TYPES in index.js, all saved as `.md` under docs/). Markdown
213
+ // legitimately contains raw `<`, `>`, `&`, quotes (headings, tables,
214
+ // comparison operators, code blocks, HTML-in-markdown). Turning escaping ON
215
+ // would corrupt that output (`&` -> `&amp;`, `<` -> `&lt;`), so it stays OFF.
216
+ //
217
+ // SECURITY CONTRACT: this renderer must ONLY receive TRUSTED context
218
+ // (framework-elicited template variables) and its output is consumed as
219
+ // Markdown/CLI/docs — NEVER served as HTML to a browser. If you ever reuse
220
+ // this engine to emit HTML for a web response, construct it with
221
+ // `{ noEscape: false }` so Handlebars escapes interpolated values and
222
+ // mitigates XSS. Do not feed untrusted user input through the trusted path.
223
+ this.noEscape = options.noEscape !== false;
224
+
208
225
  // Register any custom helpers from options
209
226
  if (options.helpers) {
210
227
  for (const [name, fn] of Object.entries(options.helpers)) {
@@ -242,9 +259,11 @@ class TemplateRenderer {
242
259
  const templateBody = typeof template === 'string' ? template : template.body;
243
260
 
244
261
  try {
262
+ // noEscape default = true (Markdown/docs output, trusted context). See the
263
+ // SECURITY CONTRACT note in the constructor before changing this.
245
264
  const compiledTemplate = this.handlebars.compile(templateBody, {
246
265
  strict: false,
247
- noEscape: true,
266
+ noEscape: this.noEscape,
248
267
  });
249
268
 
250
269
  // Add metadata to context if available
@@ -381,24 +381,36 @@ spawn_terminal() {
381
381
  # Add agent activation
382
382
  agent_cmd="${agent_cmd} --print-only" # Just for testing, real impl would use actual claude flags
383
383
 
384
+ # SECURITY (SHELL-INJECTION-PM-SH): AGENT/TASK/PARAMS/CONTEXT_FILE come from
385
+ # CLI args and are interpolated into a command string run by a shell in the
386
+ # spawned terminal. Escape every interpolated value with `printf %q` so shell
387
+ # metacharacters (`;`, `$()`, backticks, quotes) cannot break out and execute.
388
+ local q_agent q_task q_params q_context q_output q_lock
389
+ printf -v q_agent '%q' "$AGENT"
390
+ printf -v q_task '%q' "$TASK"
391
+ printf -v q_params '%q' "$PARAMS"
392
+ printf -v q_context '%q' "$CONTEXT_FILE"
393
+ printf -v q_output '%q' "$OUTPUT_FILE"
394
+ printf -v q_lock '%q' "$LOCK_FILE"
395
+
384
396
  # For now, we'll create a simpler command that demonstrates the concept
385
397
  # The actual claude CLI integration will depend on how claude accepts agent/task args
386
398
  local full_cmd="echo '=== SINAPSE Agent Session ===' && "
387
- full_cmd+="echo 'Agent: ${AGENT}' && "
388
- full_cmd+="echo 'Task: ${TASK}' && "
389
- [[ -n "$PARAMS" ]] && full_cmd+="echo 'Params: ${PARAMS}' && "
390
- [[ -n "$CONTEXT_FILE" ]] && full_cmd+="echo 'Context: ${CONTEXT_FILE}' && "
399
+ full_cmd+="echo Agent: ${q_agent} && "
400
+ full_cmd+="echo Task: ${q_task} && "
401
+ [[ -n "$PARAMS" ]] && full_cmd+="echo Params: ${q_params} && "
402
+ [[ -n "$CONTEXT_FILE" ]] && full_cmd+="echo Context: ${q_context} && "
391
403
  full_cmd+="echo '' && "
392
404
 
393
405
  # Actual execution would be something like:
394
406
  # full_cmd+="${CLAUDE_CMD} @${AGENT} *${TASK} ${PARAMS}"
395
407
  # For now, simulate the output
396
- full_cmd+="echo 'Executing: @${AGENT} *${TASK} ${PARAMS}' && "
408
+ full_cmd+="echo Executing: @${q_agent} '*'${q_task} ${q_params} && "
397
409
  full_cmd+="echo 'Agent execution would happen here...' && "
398
410
  full_cmd+="echo '=== Session Complete ===' "
399
411
 
400
412
  # Redirect output to file and remove lock when done
401
- full_cmd+=" > '${OUTPUT_FILE}' 2>&1; rm -f '${LOCK_FILE}'"
413
+ full_cmd+=" > ${q_output} 2>&1; rm -f ${q_lock}"
402
414
 
403
415
  # Spawn based on OS
404
416
  case "$os" in
package/bin/cli.js CHANGED
@@ -26,6 +26,8 @@ const KNOWN_COMMANDS = [
26
26
  'list',
27
27
  'status',
28
28
  'doctor',
29
+ 'ideate',
30
+ 'agents',
29
31
  'chrome-brain',
30
32
  'help',
31
33
  ];
@@ -134,6 +136,21 @@ function runRouter() {
134
136
  // eslint-disable-next-line no-fallthrough -- process.exit above terminates; Story 10.45 piggyback fix.
135
137
  case 'list': cmdList(); break;
136
138
  case 'status': cmdStatus(); break;
139
+ case 'ideate': {
140
+ // Wires the IdeationEngine (self-improvement analyzers) into the CLI.
141
+ const { cmdIdeate } = require('./commands/ideate');
142
+ cmdIdeate({ argv: args.slice(1) })
143
+ .then((code) => { if (code) process.exitCode = code; })
144
+ .catch((e) => { logger.error(`${RED}Erro no ideate:${NC} ${e.message}`); process.exit(1); });
145
+ break;
146
+ }
147
+ case 'agents': {
148
+ // Lists the full agent roster with uniform derived metadata (SCHEMA-001).
149
+ const { cmdAgents } = require('./commands/agents');
150
+ try { process.exitCode = cmdAgents({ argv: args.slice(1) }) || 0; }
151
+ catch (e) { logger.error(`${RED}Erro no agents:${NC} ${e.message}`); process.exit(1); }
152
+ break;
153
+ }
137
154
  case 'doctor': {
138
155
  // Story 10.21 — wires the modular doctor into the canonical CLI
139
156
  const doctorArgs = args.slice(1);
@@ -0,0 +1,96 @@
1
+ // bin/commands/agents.js — `sinapse agents` command.
2
+ //
3
+ // Lists the full agent roster with uniform, derived metadata (id, name, squad,
4
+ // type) via SquadAgentResolver.list(). This is the consumer that makes the
5
+ // unified agent schema (SCHEMA-001) useful: one place to discover every agent
6
+ // regardless of how its source file is structured.
7
+
8
+ 'use strict';
9
+
10
+ const { getLogger } = require('../../.sinapse-ai/core/logger');
11
+ const { CYAN, GREEN, YELLOW, BOLD, DIM, RED, NC } = require('../lib/constants');
12
+
13
+ /**
14
+ * Parse `agents` CLI args.
15
+ * @param {string[]} argv
16
+ * @returns {{help:boolean, json:boolean, squad:string|undefined, type:string|undefined}}
17
+ */
18
+ function parseAgentsArgs(argv = []) {
19
+ const o = { help: false, json: false, squad: undefined, type: undefined };
20
+ for (let i = 0; i < argv.length; i++) {
21
+ const a = argv[i];
22
+ if (a === '--help' || a === '-h') o.help = true;
23
+ else if (a === '--json') o.json = true;
24
+ else if (a === '--squad') { o.squad = argv[++i]; }
25
+ else if (a.startsWith('--squad=')) o.squad = a.slice('--squad='.length);
26
+ else if (a === '--type') { o.type = argv[++i]; }
27
+ else if (a.startsWith('--type=')) o.type = a.slice('--type='.length);
28
+ }
29
+ return o;
30
+ }
31
+
32
+ function printHelp(logger) {
33
+ logger.always(`${BOLD}sinapse agents${NC} — lista os agentes do framework\n`);
34
+ logger.always(`${BOLD}Uso:${NC}`);
35
+ logger.always(` ${CYAN}sinapse agents${NC} todos os agentes, agrupados por squad`);
36
+ logger.always(` ${CYAN}sinapse agents --squad squad-copy${NC} só de um squad`);
37
+ logger.always(` ${CYAN}sinapse agents --type orchestrator${NC} só orquestradores`);
38
+ logger.always(` ${CYAN}sinapse agents --json${NC} saída JSON (pra tooling)`);
39
+ }
40
+
41
+ /**
42
+ * Run the agents listing.
43
+ * @param {object} [opts]
44
+ * @param {string[]} [opts.argv]
45
+ * @returns {number} exit code
46
+ */
47
+ function cmdAgents(opts = {}) {
48
+ const logger = getLogger();
49
+ const parsed = parseAgentsArgs(opts.argv || []);
50
+ if (parsed.help) {
51
+ printHelp(logger);
52
+ return 0;
53
+ }
54
+
55
+ let SquadAgentResolver;
56
+ try {
57
+ SquadAgentResolver = require('../../.sinapse-ai/core/registry/squad-agent-resolver');
58
+ } catch (e) {
59
+ logger.error(`${RED}Resolver de agentes indisponível:${NC} ${e.message}`);
60
+ return 1;
61
+ }
62
+
63
+ const resolver = new SquadAgentResolver(process.cwd());
64
+ let agents = resolver.list();
65
+
66
+ if (parsed.squad) agents = agents.filter((a) => a.squad === parsed.squad);
67
+ if (parsed.type) agents = agents.filter((a) => a.type === parsed.type);
68
+
69
+ if (parsed.json) {
70
+ logger.always(JSON.stringify(agents, null, 2));
71
+ return 0;
72
+ }
73
+
74
+ if (agents.length === 0) {
75
+ logger.always(`${YELLOW}Nenhum agente encontrado com esse filtro.${NC}`);
76
+ return 0;
77
+ }
78
+
79
+ // Group by squad for a scannable view.
80
+ const bySquad = {};
81
+ for (const a of agents) (bySquad[a.squad] ||= []).push(a);
82
+ const squads = Object.keys(bySquad).sort();
83
+
84
+ logger.always(`${BOLD}${agents.length} agentes${NC} em ${squads.length} grupos\n`);
85
+ for (const squad of squads) {
86
+ logger.always(`${CYAN}${squad}${NC} ${DIM}(${bySquad[squad].length})${NC}`);
87
+ for (const a of bySquad[squad].sort((x, y) => x.id.localeCompare(y.id))) {
88
+ const tag = a.type === 'orchestrator' ? `${GREEN}◆${NC}` : `${DIM}·${NC}`;
89
+ logger.always(` ${tag} ${a.id} ${DIM}— ${a.name}${NC}`);
90
+ }
91
+ logger.always('');
92
+ }
93
+ return 0;
94
+ }
95
+
96
+ module.exports = { cmdAgents, parseAgentsArgs };
@@ -48,6 +48,21 @@ Exit codes (Story A.3):
48
48
  logger.always(result.formatted);
49
49
  }
50
50
 
51
+ // --deep also surfaces the SYNAPSE context-engine diagnostics, which until now
52
+ // were only reachable via the diagnose-synapse skill (not the public CLI).
53
+ // Defensive: the collectors already degrade gracefully, but guard the require
54
+ // so a missing module never breaks `doctor`.
55
+ if (opts.deep) {
56
+ try {
57
+ const {
58
+ runDiagnostics,
59
+ } = require('../../.sinapse-ai/core/synapse/diagnostics/synapse-diagnostics');
60
+ logger.always('\n' + runDiagnostics(process.cwd()));
61
+ } catch (err) {
62
+ logger.always(`\n(SYNAPSE diagnostics unavailable: ${err.message})`);
63
+ }
64
+ }
65
+
51
66
  // Story A.3 — precise exit code mapping:
52
67
  // 0 PASS | 1 WARN only | 2 FAIL | 3 internal runner error
53
68
  // Fall back to resolveExitCode when available (module may be mocked in tests
@@ -0,0 +1,129 @@
1
+ // bin/commands/ideate.js — `sinapse ideate` command.
2
+ //
3
+ // Wires the IdeationEngine (.sinapse-ai/core/ideation) into the CLI so the
4
+ // framework's self-improvement analyzers (performance, security, code quality,
5
+ // UX, architecture) are actually reachable. Before this, the engine had zero
6
+ // consumers (VAPORWARE-1, audit 2026-06-11). Per "potentiate, don't cut", the
7
+ // fix is a real entry point, not deletion.
8
+
9
+ 'use strict';
10
+
11
+ const path = require('path');
12
+ const { getLogger } = require('../../.sinapse-ai/core/logger');
13
+ const { CYAN, GREEN, YELLOW, RED, BOLD, NC } = require('../lib/constants');
14
+
15
+ const VALID_AREAS = ['performance', 'security', 'codeQuality', 'ux', 'architecture'];
16
+
17
+ /**
18
+ * Parse `ideate` CLI args into options.
19
+ * @param {string[]} argv - args after the `ideate` token
20
+ * @returns {{ help: boolean, json: boolean, save: boolean, focus: string[]|undefined }}
21
+ */
22
+ function parseIdeateArgs(argv = []) {
23
+ const opts = { help: false, json: false, save: true, focus: undefined };
24
+ for (let i = 0; i < argv.length; i++) {
25
+ const a = argv[i];
26
+ if (a === '--help' || a === '-h') opts.help = true;
27
+ else if (a === '--json') opts.json = true;
28
+ else if (a === '--no-save') opts.save = false;
29
+ else if (a === '--focus' || a === '-f') {
30
+ const val = argv[i + 1];
31
+ if (val && !val.startsWith('-')) {
32
+ opts.focus = val.split(',').map((s) => s.trim()).filter(Boolean);
33
+ i++;
34
+ }
35
+ } else if (a.startsWith('--focus=')) {
36
+ opts.focus = a.slice('--focus='.length).split(',').map((s) => s.trim()).filter(Boolean);
37
+ }
38
+ }
39
+ return opts;
40
+ }
41
+
42
+ /** Print usage. */
43
+ function printHelp(logger) {
44
+ logger.always(`${BOLD}sinapse ideate${NC} — análise de melhorias do projeto\n`);
45
+ logger.always('Analisa o código e sugere melhorias priorizadas (quick wins primeiro)');
46
+ logger.always('nas áreas: performance, security, codeQuality, ux, architecture.\n');
47
+ logger.always(`${BOLD}Uso:${NC}`);
48
+ logger.always(` ${CYAN}sinapse ideate${NC} analisa todas as áreas`);
49
+ logger.always(` ${CYAN}sinapse ideate --focus security${NC} só uma área (ou lista: perf,security)`);
50
+ logger.always(` ${CYAN}sinapse ideate --no-save${NC} não grava o relatório em disco`);
51
+ logger.always(` ${CYAN}sinapse ideate --json${NC} saída JSON (pra pipelines)\n`);
52
+ logger.always(`Relatório salvo em ${CYAN}.sinapse/ideation/${NC} (suggestions.json + .md).`);
53
+ }
54
+
55
+ /**
56
+ * Run the ideation analysis and present results.
57
+ * @param {object} [opts]
58
+ * @param {string[]} [opts.argv] - raw args after `ideate`
59
+ * @returns {Promise<number>} exit code
60
+ */
61
+ async function cmdIdeate(opts = {}) {
62
+ const logger = getLogger();
63
+ const parsed = parseIdeateArgs(opts.argv || []);
64
+
65
+ if (parsed.help) {
66
+ printHelp(logger);
67
+ return 0;
68
+ }
69
+
70
+ // Validate focus areas early with a friendly message.
71
+ if (parsed.focus) {
72
+ const invalid = parsed.focus.filter((a) => !VALID_AREAS.includes(a));
73
+ if (invalid.length) {
74
+ logger.error(`${RED}Área inválida:${NC} ${invalid.join(', ')}`);
75
+ logger.error(`Áreas válidas: ${VALID_AREAS.join(', ')}`);
76
+ return 1;
77
+ }
78
+ }
79
+
80
+ let IdeationEngine;
81
+ try {
82
+ IdeationEngine = require('../../.sinapse-ai/core/ideation/ideation-engine');
83
+ } catch (e) {
84
+ logger.error(`${RED}Motor de ideação indisponível:${NC} ${e.message}`);
85
+ return 1;
86
+ }
87
+
88
+ const engine = new IdeationEngine({ rootPath: process.cwd() });
89
+
90
+ if (!parsed.json) {
91
+ logger.always(`${CYAN}›${NC} Analisando o projeto${parsed.focus ? ` (${parsed.focus.join(', ')})` : ''}...`);
92
+ }
93
+
94
+ let result;
95
+ try {
96
+ result = await engine.ideate({ focus: parsed.focus, save: parsed.save });
97
+ } catch (e) {
98
+ logger.error(`${RED}Falha na análise:${NC} ${e.message}`);
99
+ return 1;
100
+ }
101
+
102
+ if (parsed.json) {
103
+ logger.always(JSON.stringify(result, null, 2));
104
+ return 0;
105
+ }
106
+
107
+ const { summary } = result;
108
+ logger.always('');
109
+ logger.always(`${BOLD}${summary.totalSuggestions} sugestões${NC} · ${GREEN}${summary.quickWins} quick wins${NC} · ${YELLOW}${summary.highImpact} alto impacto${NC}`);
110
+
111
+ const top = result.allSuggestions.slice(0, 8);
112
+ if (top.length) {
113
+ logger.always('');
114
+ for (const s of top) {
115
+ const tag = s.category === 'quick-win' ? `${GREEN}[quick win]${NC}` : `${YELLOW}[${s.area}]${NC}`;
116
+ logger.always(` ${tag} ${s.title || s.description}`);
117
+ }
118
+ } else {
119
+ logger.always(`\n${GREEN}Nenhuma sugestão pendente — o código está limpo nas áreas analisadas.${NC}`);
120
+ }
121
+
122
+ if (parsed.save) {
123
+ const out = path.join('.sinapse', 'ideation');
124
+ logger.always(`\nRelatório completo salvo em ${CYAN}${out}/${NC} (suggestions.md)`);
125
+ }
126
+ return 0;
127
+ }
128
+
129
+ module.exports = { cmdIdeate, parseIdeateArgs, VALID_AREAS };
@@ -18,6 +18,35 @@ const {
18
18
  const { header } = require('../lib/header');
19
19
  const { rmDirSync } = require('../lib/fs-utils');
20
20
  const { confirmUninstall } = require('../lib/prompts');
21
+ const { runSafe } = require('../../.sinapse-ai/core/utils/spawn-safe');
22
+
23
+ // The installer sets git `core.hooksPath` to this managed dir. On uninstall we
24
+ // must unset it — otherwise git keeps pointing at removed hooks and every future
25
+ // commit fails with "cannot run hook" (UNINSTALL-GIT-HOOKS, audit 2026-06-11).
26
+ const MANAGED_HOOKS_MARKER = path.join('.sinapse-ai', 'git-hooks');
27
+
28
+ /**
29
+ * Unset git core.hooksPath IF it points at the SINAPSE-managed hooks dir.
30
+ * Only touches our own config — a user's custom hooksPath is left untouched.
31
+ * @param {string} projectDir - Git project directory (default cwd)
32
+ * @returns {Promise<{unset: boolean, value: string|null}>}
33
+ */
34
+ async function removeGitHooksConfig(projectDir = process.cwd()) {
35
+ try {
36
+ const get = await runSafe('git', ['-C', projectDir, 'config', '--get', 'core.hooksPath']);
37
+ const value = (get.stdout || '').trim();
38
+ if (!get.success || !value) return { unset: false, value: null };
39
+ // Normalize separators so the marker matches on Windows and POSIX.
40
+ const normalized = value.replace(/\\/g, '/');
41
+ if (!normalized.includes(MANAGED_HOOKS_MARKER.replace(/\\/g, '/'))) {
42
+ return { unset: false, value }; // not ours — leave it alone
43
+ }
44
+ const unset = await runSafe('git', ['-C', projectDir, 'config', '--unset', 'core.hooksPath']);
45
+ return { unset: unset.success, value };
46
+ } catch {
47
+ return { unset: false, value: null };
48
+ }
49
+ }
21
50
 
22
51
  // Story 10.40 — Remove SINAPSE-authored orqx agents from a global agents dir.
23
52
  // Returns { removed: N } for reporting. Only touches files matching *-orqx.md
@@ -205,6 +234,16 @@ async function cmdUninstall(opts = {}) {
205
234
  logger.always(` ${YELLOW}-${NC} ~/.claude/settings.json (no SINAPSE keys found)`);
206
235
  }
207
236
 
237
+ // UNINSTALL-GIT-HOOKS — reset git hooks config so commits keep working.
238
+ const hooksResult = await removeGitHooksConfig(process.cwd());
239
+ if (hooksResult.unset) {
240
+ logger.always(` ${GREEN}✓${NC} Reset git core.hooksPath (was SINAPSE-managed)`);
241
+ } else if (hooksResult.value) {
242
+ logger.always(` ${YELLOW}-${NC} git core.hooksPath kept (custom, not SINAPSE-managed)`);
243
+ } else {
244
+ logger.always(` ${YELLOW}-${NC} git core.hooksPath (not set)`);
245
+ }
246
+
208
247
  logger.always(`\n${GREEN}Sinapse uninstalled.${NC}`);
209
248
  logger.always(`${YELLOW}Note:${NC} PATH entry in shell RC files was not removed. Clean up manually if desired.\n`);
210
249
  }
@@ -216,5 +255,6 @@ module.exports = {
216
255
  removeInstalledAgentsFrom,
217
256
  removeOrqxAgentsFrom,
218
257
  cleanClaudeSettingsJson,
258
+ removeGitHooksConfig,
219
259
  INSTALLED_AGENTS_MANIFEST,
220
260
  };
@@ -1,11 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * SINAPSE Postinstall Orchestrator
5
- * @story A.1 - Postinstall Script & Runtime Dirs
4
+ * SINAPSE Setup Orchestrator (formerly the npm `postinstall` lifecycle hook)
5
+ * @story A.1 - Setup Script & Runtime Dirs
6
6
  * @story B.1 - Minimalist Install Output Design
7
+ * @security 2026-06 - NO LONGER wired as an npm `postinstall` hook. Auto-running
8
+ * code on `npm install` is a supply-chain surface: a compromised publish would
9
+ * execute on every consumer's machine without any explicit action. Setup is now
10
+ * EXPLICIT — it runs only via `npm run setup` or as part of `npx sinapse-ai install`,
11
+ * never automatically. The module's behavior is otherwise unchanged.
7
12
  *
8
- * Runs automatically after `npm install` (global or local) unless:
13
+ * When invoked, it still skips itself when:
9
14
  * - SINAPSE_SKIP_POSTINSTALL=1 is set (explicit opt-out)
10
15
  * - A known CI env var is present (GITHUB_ACTIONS, CI=true, etc.)
11
16
  * - npm was invoked with --ignore-scripts (native npm behavior, nothing to do here)
@@ -350,6 +355,43 @@ function stepCreateRuntimeDirs() {
350
355
  return { ok: softFailures === 0, critical: false };
351
356
  }
352
357
 
358
+ /**
359
+ * Step: generate the SYNAPSE context-engine runtime (.synapse/ domain files).
360
+ * Compiles the L0 constitution domain from .sinapse-ai/constitution.md so the
361
+ * UserPromptSubmit context engine actually injects rules. Without this, the
362
+ * engine is inert (the hook silently emits no context). Tolerant + non-critical:
363
+ * the wrapper always exits 0 and the engine degrades gracefully if absent.
364
+ */
365
+ function stepGenerateSynapse() {
366
+ if (isGlobalInstall()) {
367
+ return { ok: true, critical: false, skipped: true };
368
+ }
369
+ verboseLog(`${c.cyan}›${c.reset} Gerando runtime do motor de contexto (.synapse/)...`);
370
+ const script = path.join(PROJECT_ROOT, 'scripts', 'generate-synapse-runtime.js');
371
+ if (!fs.existsSync(script)) {
372
+ return { ok: true, critical: false, skipped: true };
373
+ }
374
+ // Run in-process (not a subprocess) — faster, and keeps the shared run()
375
+ // sequence (sync:ide → doctor) intact for callers/tests. The wrapper's
376
+ // generate() is tolerant and never throws; preserve our own exit code since
377
+ // the underlying generator sets process.exitCode=1 on a miss.
378
+ const savedExit = process.exitCode;
379
+ let ok = false;
380
+ try {
381
+ const { generate } = require(script);
382
+ ok = generate();
383
+ } catch (err) {
384
+ warn(`Geração do .synapse/ falhou: ${err.message} — não-crítico (motor degrada).`);
385
+ } finally {
386
+ process.exitCode = savedExit;
387
+ }
388
+ if (ok) {
389
+ verboseLog(`${c.green}✓${c.reset} Runtime do motor de contexto pronto (.synapse/constitution)`);
390
+ }
391
+ // Always non-critical: the engine degrades gracefully without domains.
392
+ return { ok: true, critical: false };
393
+ }
394
+
353
395
  /**
354
396
  * Step 4: sinapse doctor --quiet.
355
397
  * Exit code semantics (per Story A.1 Dev Notes + Story A.3):
@@ -541,6 +583,9 @@ function main(argvOverride) {
541
583
  return 2;
542
584
  }
543
585
 
586
+ // Compile the SYNAPSE context-engine runtime (.synapse/). Non-critical.
587
+ const synapseGen = stepGenerateSynapse();
588
+
544
589
  const doctor = stepDoctor();
545
590
  if (doctor.critical) {
546
591
  renderPartialInstallMessage();
@@ -565,7 +610,7 @@ function main(argvOverride) {
565
610
  // so `npm install` does not report `command failed`. Critical failures have
566
611
  // already returned 2 above. The `--json` output still carries `status: warn`
567
612
  // for pipelines that want to act on it. [Story 10.39]
568
- if (!syncIde.ok || !runtimeDirs.ok || !doctor.ok) {
613
+ if (!syncIde.ok || !runtimeDirs.ok || !synapseGen.ok || !doctor.ok) {
569
614
  renderPartialInstallMessage();
570
615
  if (FLAGS.json) {
571
616
  if (jsonState.status === 'success') jsonState.status = 'warn';
@@ -587,6 +632,7 @@ module.exports = {
587
632
  isValidInstallRoot,
588
633
  stepSyncIde,
589
634
  stepCreateRuntimeDirs,
635
+ stepGenerateSynapse,
590
636
  stepDoctor,
591
637
  renderFinalSummary,
592
638
  renderPartialInstallMessage,