sinapse-ai 1.12.2 → 1.13.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 +2 -2
- package/.claude/rules/agent-memory-imports.md +2 -0
- package/.sinapse-ai/cli/commands/pro/index.js +8 -48
- package/.sinapse-ai/constitution.md +6 -2
- package/.sinapse-ai/data/entity-registry.yaml +12 -36
- package/.sinapse-ai/development/agents/architect/MEMORY.md +35 -0
- package/.sinapse-ai/development/agents/data-engineer/MEMORY.md +38 -0
- package/.sinapse-ai/development/agents/developer/MEMORY.md +36 -0
- package/.sinapse-ai/development/agents/devops/MEMORY.md +39 -0
- package/.sinapse-ai/development/agents/product-lead/MEMORY.md +36 -0
- package/.sinapse-ai/development/agents/project-lead/MEMORY.md +36 -0
- package/.sinapse-ai/development/agents/quality-gate/MEMORY.md +35 -0
- package/.sinapse-ai/development/agents/sprint-lead/MEMORY.md +36 -0
- package/.sinapse-ai/development/templates/chrome-brain/knowledge-base/chrome-brain.md +2 -2
- package/.sinapse-ai/infrastructure/scripts/ide-sync/index.js +15 -0
- package/.sinapse-ai/infrastructure/scripts/validate-agents.js +159 -8
- package/.sinapse-ai/install-manifest.yaml +12 -148
- package/AGENTS.md +13 -2
- package/CHANGELOG.md +122 -2
- package/README.en.md +1 -1
- package/README.md +6 -26
- package/bin/cli.js +2 -7
- package/bin/commands/install.js +19 -257
- package/bin/commands/update.js +18 -78
- package/bin/lib/command-generator.js +261 -0
- package/bin/lib/prompts.js +2 -65
- package/bin/modules/chrome-brain-installer.js +2 -1
- package/bin/postinstall.js +6 -6
- package/docs/getting-started.md +0 -1
- package/docs/pt/getting-started.md +1 -1
- package/docs/security/dependabot-triage.md +17 -17
- package/package.json +6 -3
- package/packages/installer/src/wizard/i18n.js +3 -204
- package/packages/installer/src/wizard/index.js +1 -1
- package/packages/installer/src/wizard/questions.js +5 -368
- package/packages/installer/src/wizard/wizard.js +0 -9
- package/scripts/generate-install-manifest.js +6 -0
- package/scripts/validate-changelog-tags.js +97 -0
- package/scripts/validate-no-personal-leaks.js +184 -28
- package/scripts/validate-schemas.js +55 -10
- package/scripts/validate-story-meta.js +25 -12
- package/squads/claude-code-mastery/agents/claude-mastery-chief.md +1 -1
- package/squads/claude-code-mastery/agents/hooks-architect.md +12 -20
- package/squads/claude-code-mastery/tasks/audit-setup.md +1 -1
- package/squads/squad-product/agents/product-orqx.md +0 -1
- package/squads/squad-product/agents/ps-client-product-manager.md +0 -1
- package/squads/squad-product/agents/ps-delivery-manager.md +0 -1
- package/squads/squad-product/agents/ps-discovery-lead.md +0 -1
- package/squads/squad-product/agents/ps-product-analyst.md +0 -1
- package/squads/squad-product/agents/ps-product-ops-specialist.md +0 -1
- package/squads/squad-product/agents/ps-product-strategist.md +0 -1
- package/.sinapse-ai/core/grounding/README.md +0 -83
- package/.sinapse-ai/core/grounding/brand.cjs +0 -29
- package/.sinapse-ai/core/grounding/config-loader.cjs +0 -52
- package/.sinapse-ai/core/grounding/design-system.cjs +0 -29
- package/.sinapse-ai/core/grounding/vault.cjs +0 -36
- package/.sinapse-ai/development/scripts/approval-workflow.js +0 -643
- package/.sinapse-ai/development/scripts/backup-manager.js +0 -607
- package/.sinapse-ai/development/scripts/branch-manager.js +0 -390
- package/.sinapse-ai/development/scripts/code-quality-improver.js +0 -1329
- package/.sinapse-ai/development/scripts/commit-message-generator.js +0 -850
- package/.sinapse-ai/development/scripts/conflict-resolver.js +0 -675
- package/.sinapse-ai/development/scripts/dependency-analyzer.js +0 -638
- package/.sinapse-ai/development/scripts/diff-generator.js +0 -352
- package/.sinapse-ai/development/scripts/git-wrapper.js +0 -462
- package/.sinapse-ai/development/scripts/modification-validator.js +0 -555
- package/.sinapse-ai/development/scripts/performance-analyzer.js +0 -758
- package/.sinapse-ai/development/scripts/refactoring-suggester.js +0 -1148
- package/.sinapse-ai/development/scripts/rollback-handler.js +0 -531
- package/.sinapse-ai/development/scripts/security-checker.js +0 -359
- package/.sinapse-ai/development/scripts/template-engine.js +0 -240
- package/.sinapse-ai/development/scripts/template-validator.js +0 -279
- package/.sinapse-ai/development/scripts/test-generator.js +0 -844
- package/.sinapse-ai/development/scripts/transaction-manager.js +0 -590
- package/.sinapse-ai/development/scripts/yaml-validator.js +0 -397
- package/.sinapse-ai/hooks/sinapse-brand-grounding.cjs +0 -162
- package/.sinapse-ai/hooks/sinapse-ds-grounding.cjs +0 -211
- package/.sinapse-ai/hooks/sinapse-vault-grounding.cjs +0 -194
- package/bin/lib/register-grounding-hooks.js +0 -145
- package/docs/guides/agents/traces/execution-traces.zip +0 -0
- package/docs/guides/grounding-setup.md +0 -154
- package/docs/guides/workflows/SINAPSE-WORKFLOWS.zip +0 -0
- package/packages/installer/src/pro/pro-scaffolder.js +0 -449
- package/packages/installer/src/wizard/grounding-config.js +0 -131
- package/packages/installer/src/wizard/pro-setup.js +0 -1439
- package/packages/installer/templates/README.md +0 -16
- package/packages/installer/templates/brand-routing.example.json +0 -12
- package/packages/installer/templates/ds-routing.example.json +0 -12
- package/packages/installer/templates/vault-routing.example.json +0 -12
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* SINAPSE Design System Grounding Hook — UserPromptSubmit (framework-shipped).
|
|
6
|
-
*
|
|
7
|
-
* @story GA-1.6
|
|
8
|
-
*
|
|
9
|
-
* Reads `~/.claude/sinapse-ai-config.yaml` (Story 10.47 schema) `designSystem`
|
|
10
|
-
* section. Activates only when the user prompt contains UI keywords
|
|
11
|
-
* (PT + EN). Injects the first 3000 chars of the DS law file as
|
|
12
|
-
* `<ds-grounding>` context.
|
|
13
|
-
*
|
|
14
|
-
* Coexistence: the user's personal `~/.claude/hooks/design-system-grounding.cjs` (if present)
|
|
15
|
-
* reads `ds-routing.json` and is unaffected by this file.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
const fs = require('fs');
|
|
19
|
-
const os = require('os');
|
|
20
|
-
const path = require('path');
|
|
21
|
-
|
|
22
|
-
const HOOK_TIMEOUT_MS = 3500;
|
|
23
|
-
const MAX_INJECTION_SIZE = 3000; // chars (story says 3000 for DS)
|
|
24
|
-
const MAX_LAW_PREVIEW = 3000;
|
|
25
|
-
const MIN_PROMPT_CHARS = 15;
|
|
26
|
-
const CONFIG_PATH = path.join(os.homedir(), '.claude', 'sinapse-ai-config.yaml');
|
|
27
|
-
|
|
28
|
-
const UI_KEYWORDS = [
|
|
29
|
-
// PT
|
|
30
|
-
'pagina', 'página', 'lp', 'landing', 'componente', 'interface', 'tela',
|
|
31
|
-
'layout', 'hero', 'dashboard', 'carrossel', 'slide', 'mockup',
|
|
32
|
-
'wireframe', 'prototipo', 'protótipo', 'design', 'visual', 'site',
|
|
33
|
-
'website', 'menu', 'navbar', 'footer', 'botao', 'botão', 'card', 'cards',
|
|
34
|
-
'secao', 'seção', 'frontend', 'front-end', 'figma', 'react', 'tailwind',
|
|
35
|
-
'nextjs', 'next.js', 'redesign', 'tipografia', 'paleta',
|
|
36
|
-
// EN
|
|
37
|
-
'page', 'component', 'screen', 'section', 'button', 'typography',
|
|
38
|
-
'palette', 'web app', 'ui ', ' ui',
|
|
39
|
-
];
|
|
40
|
-
|
|
41
|
-
// Candidate filenames to read as the DS law, in priority order.
|
|
42
|
-
const LAW_CANDIDATES = [
|
|
43
|
-
'0.0-guidelines.md',
|
|
44
|
-
'guidelines.md',
|
|
45
|
-
'principles.md',
|
|
46
|
-
'README.md',
|
|
47
|
-
'tokens.json',
|
|
48
|
-
'design-system.md',
|
|
49
|
-
];
|
|
50
|
-
|
|
51
|
-
function readStdin() {
|
|
52
|
-
return new Promise((resolve) => {
|
|
53
|
-
let data = '';
|
|
54
|
-
let settled = false;
|
|
55
|
-
const finish = (val) => { if (!settled) { settled = true; resolve(val); } };
|
|
56
|
-
try {
|
|
57
|
-
process.stdin.setEncoding('utf8');
|
|
58
|
-
process.stdin.on('error', () => finish(null));
|
|
59
|
-
process.stdin.on('data', (chunk) => { data += chunk; });
|
|
60
|
-
process.stdin.on('end', () => {
|
|
61
|
-
try { finish(JSON.parse(data)); } catch { finish(null); }
|
|
62
|
-
});
|
|
63
|
-
} catch {
|
|
64
|
-
finish(null);
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Story 10.47: delegate to the shared grounding config loader instead of
|
|
70
|
-
// duplicating the read+parse. The require is guarded so the hook stays
|
|
71
|
-
// fail-open even if the shared module is somehow absent at runtime.
|
|
72
|
-
function loadConfig() {
|
|
73
|
-
try {
|
|
74
|
-
return require('../core/grounding/config-loader.cjs').loadGroundingConfig(CONFIG_PATH);
|
|
75
|
-
} catch {
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function getSection(cfg, name) {
|
|
81
|
-
if (!cfg || !cfg.grounding) return null;
|
|
82
|
-
const s = cfg.grounding[name];
|
|
83
|
-
if (!s || typeof s !== 'object' || !s.enabled) return null;
|
|
84
|
-
return s;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function isUIPrompt(prompt) {
|
|
88
|
-
if (!prompt || typeof prompt !== 'string') return false;
|
|
89
|
-
if (prompt.trim().length < MIN_PROMPT_CHARS) return false;
|
|
90
|
-
const lower = prompt.toLowerCase();
|
|
91
|
-
return UI_KEYWORDS.some((k) => lower.includes(k));
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function findLawFile(rootPath) {
|
|
95
|
-
try {
|
|
96
|
-
if (!rootPath || !fs.existsSync(rootPath)) return null;
|
|
97
|
-
const stat = fs.statSync(rootPath);
|
|
98
|
-
if (!stat.isDirectory()) {
|
|
99
|
-
// If user passed a file directly, use it.
|
|
100
|
-
return rootPath;
|
|
101
|
-
}
|
|
102
|
-
for (const candidate of LAW_CANDIDATES) {
|
|
103
|
-
const full = path.join(rootPath, candidate);
|
|
104
|
-
if (fs.existsSync(full)) return full;
|
|
105
|
-
}
|
|
106
|
-
// Fallback: first .md/.json in the dir
|
|
107
|
-
const entries = fs.readdirSync(rootPath).filter(
|
|
108
|
-
(f) => f.endsWith('.md') || f.endsWith('.json')
|
|
109
|
-
);
|
|
110
|
-
if (entries.length > 0) return path.join(rootPath, entries[0]);
|
|
111
|
-
return null;
|
|
112
|
-
} catch {
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function readLaw(lawFile) {
|
|
118
|
-
try {
|
|
119
|
-
if (!lawFile || !fs.existsSync(lawFile)) return null;
|
|
120
|
-
let content = fs.readFileSync(lawFile, 'utf8');
|
|
121
|
-
if (content.length > MAX_LAW_PREVIEW) {
|
|
122
|
-
content = content.substring(0, MAX_LAW_PREVIEW) + '\n[...truncated...]';
|
|
123
|
-
}
|
|
124
|
-
return content;
|
|
125
|
-
} catch {
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function buildInjection(section, lawContent, projectCwd) {
|
|
131
|
-
const profileName = section.profileName || 'Custom DS';
|
|
132
|
-
const rootPath = section.rootPath || '';
|
|
133
|
-
const parts = [];
|
|
134
|
-
parts.push(`<ds-grounding source="filesystem" name="${profileName}" project="${projectCwd}">`);
|
|
135
|
-
parts.push(`DS detectado: ${profileName}`);
|
|
136
|
-
parts.push(`Localizacao: ${rootPath}`);
|
|
137
|
-
if (lawContent) {
|
|
138
|
-
parts.push('--- LEI DO DS (primeiros 3000 chars) ---');
|
|
139
|
-
parts.push(lawContent);
|
|
140
|
-
} else {
|
|
141
|
-
parts.push('(nenhum arquivo de lei encontrado em rootPath)');
|
|
142
|
-
}
|
|
143
|
-
parts.push('</ds-grounding>');
|
|
144
|
-
|
|
145
|
-
let full = parts.join('\n');
|
|
146
|
-
if (full.length > MAX_INJECTION_SIZE) {
|
|
147
|
-
const trailer = '\n[...truncated...]\n</ds-grounding>';
|
|
148
|
-
full = full.substring(0, MAX_INJECTION_SIZE - trailer.length) + trailer;
|
|
149
|
-
}
|
|
150
|
-
return full;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
async function main() {
|
|
154
|
-
const input = await readStdin();
|
|
155
|
-
if (!input || typeof input !== 'object') return;
|
|
156
|
-
|
|
157
|
-
const userPrompt = input.prompt || input.user_prompt || input.message || '';
|
|
158
|
-
if (!isUIPrompt(userPrompt)) return;
|
|
159
|
-
|
|
160
|
-
// Anti-double-injection
|
|
161
|
-
if (userPrompt.includes('<ds-grounding')) return;
|
|
162
|
-
|
|
163
|
-
const cfg = loadConfig();
|
|
164
|
-
const section = getSection(cfg, 'designSystem');
|
|
165
|
-
if (!section || !section.rootPath) return;
|
|
166
|
-
|
|
167
|
-
const lawFile = findLawFile(section.rootPath);
|
|
168
|
-
const lawContent = readLaw(lawFile);
|
|
169
|
-
const projectCwd = input.cwd || process.cwd();
|
|
170
|
-
const context = buildInjection(section, lawContent, projectCwd);
|
|
171
|
-
|
|
172
|
-
const output = JSON.stringify({
|
|
173
|
-
hookSpecificOutput: {
|
|
174
|
-
hookEventName: 'UserPromptSubmit',
|
|
175
|
-
additionalContext: context,
|
|
176
|
-
},
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
await new Promise((resolve) => {
|
|
180
|
-
let settled = false;
|
|
181
|
-
const finish = () => { if (!settled) { settled = true; resolve(); } };
|
|
182
|
-
try {
|
|
183
|
-
const flushed = process.stdout.write(output, () => finish());
|
|
184
|
-
if (flushed) setImmediate(finish);
|
|
185
|
-
} catch {
|
|
186
|
-
finish();
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function run() {
|
|
192
|
-
const timer = setTimeout(() => process.exit(0), HOOK_TIMEOUT_MS);
|
|
193
|
-
timer.unref();
|
|
194
|
-
main()
|
|
195
|
-
.then(() => { clearTimeout(timer); process.exitCode = 0; })
|
|
196
|
-
.catch(() => { clearTimeout(timer); process.exitCode = 0; });
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (require.main === module) run();
|
|
200
|
-
|
|
201
|
-
module.exports = {
|
|
202
|
-
loadConfig,
|
|
203
|
-
getSection,
|
|
204
|
-
isUIPrompt,
|
|
205
|
-
findLawFile,
|
|
206
|
-
readLaw,
|
|
207
|
-
buildInjection,
|
|
208
|
-
UI_KEYWORDS,
|
|
209
|
-
CONFIG_PATH,
|
|
210
|
-
MAX_INJECTION_SIZE,
|
|
211
|
-
};
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* SINAPSE Vault Grounding Hook — UserPromptSubmit (framework-shipped).
|
|
6
|
-
*
|
|
7
|
-
* @story GA-1.6
|
|
8
|
-
*
|
|
9
|
-
* Reads `~/.claude/sinapse-ai-config.yaml` (Story 10.47 schema) and, if the
|
|
10
|
-
* `vault` section is enabled, injects `<vault-grounding>` context into the
|
|
11
|
-
* Claude Code prompt. Closes the gap left by Story 10.47 (which shipped the
|
|
12
|
-
* wizard + library hooks but no executable Claude Code hooks).
|
|
13
|
-
*
|
|
14
|
-
* Coexistence with personal hooks: this file lives at
|
|
15
|
-
* .sinapse-ai/hooks/sinapse-vault-grounding.cjs
|
|
16
|
-
* and reads `sinapse-ai-config.yaml`. The user's personal
|
|
17
|
-
* `~/.claude/hooks/vault-grounding.cjs` (if present) reads `vault-routing.json`. The two
|
|
18
|
-
* files do not collide (different filenames, different config sources, and
|
|
19
|
-
* fail-open guarantees that running both is safe).
|
|
20
|
-
*
|
|
21
|
-
* Contract:
|
|
22
|
-
* stdin : JSON `{ "prompt": "...", "session_id": "...", "cwd": "..." }`
|
|
23
|
-
* stdout : JSON `{ "hookSpecificOutput": { "hookEventName":
|
|
24
|
-
* "UserPromptSubmit", "additionalContext": "<vault-grounding>...</vault-grounding>" } }`
|
|
25
|
-
* exit 0 always (fail-open: silent on every error path).
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
const fs = require('fs');
|
|
29
|
-
const os = require('os');
|
|
30
|
-
const path = require('path');
|
|
31
|
-
|
|
32
|
-
const HOOK_TIMEOUT_MS = 3500;
|
|
33
|
-
const MAX_INJECTION_SIZE = 6000; // chars
|
|
34
|
-
const MAX_NOTES = 5;
|
|
35
|
-
const MAX_NOTE_PREVIEW = 500; // chars per note
|
|
36
|
-
const MIN_PROMPT_CHARS = 10;
|
|
37
|
-
const CONFIG_PATH = path.join(os.homedir(), '.claude', 'sinapse-ai-config.yaml');
|
|
38
|
-
|
|
39
|
-
function readStdin() {
|
|
40
|
-
return new Promise((resolve) => {
|
|
41
|
-
let data = '';
|
|
42
|
-
let settled = false;
|
|
43
|
-
const finish = (val) => { if (!settled) { settled = true; resolve(val); } };
|
|
44
|
-
try {
|
|
45
|
-
process.stdin.setEncoding('utf8');
|
|
46
|
-
process.stdin.on('error', () => finish(null));
|
|
47
|
-
process.stdin.on('data', (chunk) => { data += chunk; });
|
|
48
|
-
process.stdin.on('end', () => {
|
|
49
|
-
try { finish(JSON.parse(data)); } catch { finish(null); }
|
|
50
|
-
});
|
|
51
|
-
} catch {
|
|
52
|
-
finish(null);
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Story 10.47: delegate to the shared grounding config loader instead of
|
|
58
|
-
// duplicating the read+parse. The require is guarded so the hook stays
|
|
59
|
-
// fail-open even if the shared module is somehow absent at runtime.
|
|
60
|
-
function loadConfig() {
|
|
61
|
-
try {
|
|
62
|
-
return require('../core/grounding/config-loader.cjs').loadGroundingConfig(CONFIG_PATH);
|
|
63
|
-
} catch {
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function getSection(cfg, name) {
|
|
69
|
-
if (!cfg || !cfg.grounding) return null;
|
|
70
|
-
const s = cfg.grounding[name];
|
|
71
|
-
if (!s || typeof s !== 'object' || !s.enabled) return null;
|
|
72
|
-
return s;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Pick top-N markdown notes from the vault root, ranked by mtime descending.
|
|
77
|
-
* Returns [{ name, content }]. Truncates each note to MAX_NOTE_PREVIEW chars.
|
|
78
|
-
*/
|
|
79
|
-
function pickNotes(vaultPath) {
|
|
80
|
-
try {
|
|
81
|
-
if (!vaultPath || !fs.existsSync(vaultPath)) return [];
|
|
82
|
-
const stat = fs.statSync(vaultPath);
|
|
83
|
-
if (!stat.isDirectory()) return [];
|
|
84
|
-
|
|
85
|
-
const entries = fs.readdirSync(vaultPath, { withFileTypes: true })
|
|
86
|
-
.filter((e) => e.isFile() && e.name.toLowerCase().endsWith('.md'))
|
|
87
|
-
.map((e) => {
|
|
88
|
-
const full = path.join(vaultPath, e.name);
|
|
89
|
-
try {
|
|
90
|
-
const s = fs.statSync(full);
|
|
91
|
-
return { name: e.name, full, mtime: s.mtimeMs };
|
|
92
|
-
} catch { return null; }
|
|
93
|
-
})
|
|
94
|
-
.filter(Boolean);
|
|
95
|
-
|
|
96
|
-
entries.sort((a, b) => b.mtime - a.mtime);
|
|
97
|
-
|
|
98
|
-
const notes = [];
|
|
99
|
-
for (const ent of entries.slice(0, MAX_NOTES)) {
|
|
100
|
-
try {
|
|
101
|
-
let content = fs.readFileSync(ent.full, 'utf8');
|
|
102
|
-
if (content.length > MAX_NOTE_PREVIEW) {
|
|
103
|
-
content = content.substring(0, MAX_NOTE_PREVIEW) + '\n[...truncated...]';
|
|
104
|
-
}
|
|
105
|
-
notes.push({ name: ent.name, content });
|
|
106
|
-
} catch { /* skip unreadable */ }
|
|
107
|
-
}
|
|
108
|
-
return notes;
|
|
109
|
-
} catch {
|
|
110
|
-
return [];
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function buildInjection(section, notes) {
|
|
115
|
-
const domain = (Array.isArray(section.domains) && section.domains.length)
|
|
116
|
-
? section.domains.join(',')
|
|
117
|
-
: 'default';
|
|
118
|
-
const parts = [];
|
|
119
|
-
parts.push(`<vault-grounding domain="${domain}" mode="summary" vault="${section.path}">`);
|
|
120
|
-
parts.push('Indice do vault (top notas mais recentes):');
|
|
121
|
-
if (notes.length === 0) {
|
|
122
|
-
parts.push('(nenhuma nota .md encontrada no path do vault)');
|
|
123
|
-
} else {
|
|
124
|
-
for (const n of notes) {
|
|
125
|
-
parts.push(`--- ${n.name} ---`);
|
|
126
|
-
parts.push(n.content);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
parts.push('</vault-grounding>');
|
|
130
|
-
|
|
131
|
-
let full = parts.join('\n');
|
|
132
|
-
if (full.length > MAX_INJECTION_SIZE) {
|
|
133
|
-
const trailer = '\n[...truncated...]\n</vault-grounding>';
|
|
134
|
-
full = full.substring(0, MAX_INJECTION_SIZE - trailer.length) + trailer;
|
|
135
|
-
}
|
|
136
|
-
return full;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
async function main() {
|
|
140
|
-
const input = await readStdin();
|
|
141
|
-
if (!input || typeof input !== 'object') return;
|
|
142
|
-
|
|
143
|
-
const userPrompt = input.prompt || input.user_prompt || input.message || '';
|
|
144
|
-
if (typeof userPrompt !== 'string' || userPrompt.trim().length < MIN_PROMPT_CHARS) return;
|
|
145
|
-
|
|
146
|
-
// Anti-double-injection: if the prompt already contains <vault-grounding>,
|
|
147
|
-
// assume another hook (or a previous run) has already injected it.
|
|
148
|
-
if (userPrompt.includes('<vault-grounding')) return;
|
|
149
|
-
|
|
150
|
-
const cfg = loadConfig();
|
|
151
|
-
const section = getSection(cfg, 'vault');
|
|
152
|
-
if (!section || !section.path) return;
|
|
153
|
-
|
|
154
|
-
const notes = pickNotes(section.path);
|
|
155
|
-
const context = buildInjection(section, notes);
|
|
156
|
-
|
|
157
|
-
const output = JSON.stringify({
|
|
158
|
-
hookSpecificOutput: {
|
|
159
|
-
hookEventName: 'UserPromptSubmit',
|
|
160
|
-
additionalContext: context,
|
|
161
|
-
},
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
await new Promise((resolve) => {
|
|
165
|
-
let settled = false;
|
|
166
|
-
const finish = () => { if (!settled) { settled = true; resolve(); } };
|
|
167
|
-
try {
|
|
168
|
-
const flushed = process.stdout.write(output, () => finish());
|
|
169
|
-
if (flushed) setImmediate(finish);
|
|
170
|
-
} catch {
|
|
171
|
-
finish();
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function run() {
|
|
177
|
-
const timer = setTimeout(() => process.exit(0), HOOK_TIMEOUT_MS);
|
|
178
|
-
timer.unref();
|
|
179
|
-
main()
|
|
180
|
-
.then(() => { clearTimeout(timer); process.exitCode = 0; })
|
|
181
|
-
.catch(() => { clearTimeout(timer); process.exitCode = 0; });
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (require.main === module) run();
|
|
185
|
-
|
|
186
|
-
module.exports = {
|
|
187
|
-
// Exposed for tests — the executable contract is stdin/stdout.
|
|
188
|
-
loadConfig,
|
|
189
|
-
getSection,
|
|
190
|
-
pickNotes,
|
|
191
|
-
buildInjection,
|
|
192
|
-
CONFIG_PATH,
|
|
193
|
-
MAX_INJECTION_SIZE,
|
|
194
|
-
};
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
// bin/lib/register-grounding-hooks.js — register the 3 framework grounding
|
|
2
|
-
// hooks (sinapse-vault, sinapse-ds, sinapse-brand) in `~/.claude/settings.json`.
|
|
3
|
-
//
|
|
4
|
-
// @story GA-1.6
|
|
5
|
-
//
|
|
6
|
-
// Contract:
|
|
7
|
-
// - Idempotent: re-running never duplicates an entry.
|
|
8
|
-
// - Non-destructive: existing UserPromptSubmit hooks (e.g. user's personal
|
|
9
|
-
// `vault-grounding.cjs`, terminal-bus, etc.) are preserved.
|
|
10
|
-
// - Fail-soft: every error is caught and logged via `notify`; settings.json
|
|
11
|
-
// is never left in a corrupted state (we read → mutate → write atomically).
|
|
12
|
-
|
|
13
|
-
'use strict';
|
|
14
|
-
|
|
15
|
-
const fs = require('fs');
|
|
16
|
-
const path = require('path');
|
|
17
|
-
|
|
18
|
-
const HOOK_FILENAMES = [
|
|
19
|
-
'sinapse-vault-grounding.cjs',
|
|
20
|
-
'sinapse-ds-grounding.cjs',
|
|
21
|
-
'sinapse-brand-grounding.cjs',
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
const HOOK_TIMEOUT_MS = 4000;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Convert a path to forward slashes for use inside a JSON string. Claude Code
|
|
28
|
-
* on Windows tolerates both, but forward slashes avoid double-escaping issues.
|
|
29
|
-
*/
|
|
30
|
-
function toForwardSlash(p) {
|
|
31
|
-
return p.replace(/\\/g, '/');
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Build a UserPromptSubmit hook entry block for a given hook absolute path.
|
|
36
|
-
*/
|
|
37
|
-
function buildHookEntry(absHookPath) {
|
|
38
|
-
return {
|
|
39
|
-
type: 'command',
|
|
40
|
-
command: `node "${toForwardSlash(absHookPath)}"`,
|
|
41
|
-
timeout: HOOK_TIMEOUT_MS,
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Determine if a UserPromptSubmit `hooks[].command` entry already references
|
|
47
|
-
* one of our hook filenames. Filename-based detection lets us survive path
|
|
48
|
-
* differences (case, trailing slash, .sinapse-ai relocation).
|
|
49
|
-
*/
|
|
50
|
-
function commandReferencesHook(commandStr, hookFilename) {
|
|
51
|
-
if (!commandStr || typeof commandStr !== 'string') return false;
|
|
52
|
-
return commandStr.includes(hookFilename);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Register all 3 grounding hooks in settings.json.
|
|
57
|
-
*
|
|
58
|
-
* @param {object} opts
|
|
59
|
-
* @param {string} opts.settingsPath Absolute path to `~/.claude/settings.json`.
|
|
60
|
-
* @param {string} opts.hooksDir Absolute path to `<install>/.sinapse-ai/hooks`.
|
|
61
|
-
* @param {function} [opts.notify] Optional logger `(msg) => void`.
|
|
62
|
-
* @returns {{added: string[], skipped: string[], errors: string[]}}
|
|
63
|
-
*/
|
|
64
|
-
function registerGroundingHooks(opts) {
|
|
65
|
-
const { settingsPath, hooksDir } = opts;
|
|
66
|
-
const notify = typeof opts.notify === 'function' ? opts.notify : () => {};
|
|
67
|
-
const result = { added: [], skipped: [], errors: [] };
|
|
68
|
-
|
|
69
|
-
let settings = {};
|
|
70
|
-
try {
|
|
71
|
-
if (fs.existsSync(settingsPath)) {
|
|
72
|
-
const raw = fs.readFileSync(settingsPath, 'utf8');
|
|
73
|
-
settings = raw.trim() ? JSON.parse(raw) : {};
|
|
74
|
-
}
|
|
75
|
-
} catch (err) {
|
|
76
|
-
result.errors.push(`Could not parse ${settingsPath}: ${err.message}`);
|
|
77
|
-
notify(`WARN: skipping grounding hook registration — ${err.message}`);
|
|
78
|
-
return result;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (!settings || typeof settings !== 'object') settings = {};
|
|
82
|
-
if (!settings.hooks || typeof settings.hooks !== 'object') settings.hooks = {};
|
|
83
|
-
if (!Array.isArray(settings.hooks.UserPromptSubmit)) settings.hooks.UserPromptSubmit = [];
|
|
84
|
-
|
|
85
|
-
// Find or create the matcher block we'll mutate. Prefer an existing block
|
|
86
|
-
// with `matcher === ''` (the default catch-all) so we group with whatever
|
|
87
|
-
// is already there.
|
|
88
|
-
let block = settings.hooks.UserPromptSubmit.find(
|
|
89
|
-
(b) => b && typeof b === 'object' && (b.matcher === '' || b.matcher === undefined || b.matcher === null),
|
|
90
|
-
);
|
|
91
|
-
if (!block) {
|
|
92
|
-
block = { matcher: '', hooks: [] };
|
|
93
|
-
settings.hooks.UserPromptSubmit.push(block);
|
|
94
|
-
}
|
|
95
|
-
if (!Array.isArray(block.hooks)) block.hooks = [];
|
|
96
|
-
|
|
97
|
-
for (const hookName of HOOK_FILENAMES) {
|
|
98
|
-
const absHookPath = path.join(hooksDir, hookName);
|
|
99
|
-
|
|
100
|
-
// Skip registration if the hook file does not exist on disk — registering
|
|
101
|
-
// a missing file would crash Claude Code on every prompt.
|
|
102
|
-
if (!fs.existsSync(absHookPath)) {
|
|
103
|
-
result.errors.push(`Hook file missing: ${absHookPath}`);
|
|
104
|
-
notify(`WARN: hook file missing, skipping ${hookName}`);
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Idempotency: skip if any existing entry references this filename.
|
|
109
|
-
const alreadyRegistered = settings.hooks.UserPromptSubmit.some((b) => {
|
|
110
|
-
if (!b || !Array.isArray(b.hooks)) return false;
|
|
111
|
-
return b.hooks.some((h) => h && commandReferencesHook(h.command, hookName));
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
if (alreadyRegistered) {
|
|
115
|
-
result.skipped.push(hookName);
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
block.hooks.push(buildHookEntry(absHookPath));
|
|
120
|
-
result.added.push(hookName);
|
|
121
|
-
notify(`Registered hook: ${hookName.replace(/\.cjs$/, '')}`);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Persist only if we changed something
|
|
125
|
-
if (result.added.length > 0) {
|
|
126
|
-
try {
|
|
127
|
-
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
128
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
129
|
-
} catch (err) {
|
|
130
|
-
result.errors.push(`Could not write ${settingsPath}: ${err.message}`);
|
|
131
|
-
notify(`WARN: could not write settings.json — ${err.message}`);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return result;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
module.exports = {
|
|
139
|
-
registerGroundingHooks,
|
|
140
|
-
HOOK_FILENAMES,
|
|
141
|
-
HOOK_TIMEOUT_MS,
|
|
142
|
-
// Exposed for tests
|
|
143
|
-
buildHookEntry,
|
|
144
|
-
commandReferencesHook,
|
|
145
|
-
};
|
|
Binary file
|