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
|
@@ -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 (`&` -> `&`, `<` -> `<`), 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:
|
|
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
|
|
388
|
-
full_cmd+="echo
|
|
389
|
-
[[ -n "$PARAMS" ]] && full_cmd+="echo
|
|
390
|
-
[[ -n "$CONTEXT_FILE" ]] && full_cmd+="echo
|
|
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
|
|
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+=" >
|
|
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 };
|
package/bin/commands/doctor.js
CHANGED
|
@@ -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
|
};
|
package/bin/postinstall.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* SINAPSE
|
|
5
|
-
* @story A.1 -
|
|
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
|
-
*
|
|
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,
|