sinapse-ai 7.5.1 → 7.6.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/hooks/enforce-architecture-first.cjs +144 -0
- package/.claude/hooks/enforce-delegation.cjs +142 -0
- package/.claude/hooks/enforce-story-gate.cjs +194 -0
- package/.claude/hooks/write-path-validation.cjs +132 -0
- package/.claude/rules/agent-handoff.md +12 -0
- package/.sinapse-ai/development/templates/agent-handoff-tmpl.yaml +3 -0
- package/.sinapse-ai/install-manifest.yaml +4 -4
- package/bin/modules/chrome-brain-installer.js +1 -5
- package/package.json +1 -1
- package/packages/installer/src/installer/sinapse-ai-installer.js +9 -21
- package/packages/installer/src/wizard/ide-config-generator.js +13 -11
- package/packages/installer/src/wizard/index.js +4 -11
- package/packages/installer/src/wizard/validation/report-generator.js +13 -7
- package/packages/sinapse-install/src/capabilities/chrome-brain.js +1 -5
- package/scripts/sinapse-patch.js +0 -7
- package/sinapse/agents/sinapse-orqx.md +8 -8
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hook: Enforce Architecture-First Development (CJS port)
|
|
6
|
+
*
|
|
7
|
+
* RULE: Code in protected paths can only be created/edited if prior
|
|
8
|
+
* architecture documentation exists.
|
|
9
|
+
*
|
|
10
|
+
* Protocol (Claude Code PreToolUse):
|
|
11
|
+
* exit 0 → allow
|
|
12
|
+
* exit 2 → block (message shown to model via stderr)
|
|
13
|
+
*
|
|
14
|
+
* Fail-open: if parsing fails or project root is unresolvable, allow.
|
|
15
|
+
*
|
|
16
|
+
* @module enforce-architecture-first
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Configuration: paths that REQUIRE prior documentation
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
const PROTECTED_PATHS = [
|
|
27
|
+
{
|
|
28
|
+
pattern: 'supabase/functions/',
|
|
29
|
+
docPatterns: [
|
|
30
|
+
'docs/architecture/{name}.md',
|
|
31
|
+
'docs/architecture/{name}-architecture.md',
|
|
32
|
+
'docs/approved-plans/{name}.md',
|
|
33
|
+
],
|
|
34
|
+
extractName(p) {
|
|
35
|
+
const idx = p.indexOf('supabase/functions/');
|
|
36
|
+
if (idx === -1) return null;
|
|
37
|
+
return p.slice(idx + 'supabase/functions/'.length).split('/')[0] || null;
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
pattern: 'supabase/migrations/',
|
|
42
|
+
docPatterns: [
|
|
43
|
+
'docs/approved-plans/migration-{name}.md',
|
|
44
|
+
'docs/architecture/database-changes.md',
|
|
45
|
+
],
|
|
46
|
+
extractName(p) {
|
|
47
|
+
const idx = p.indexOf('supabase/migrations/');
|
|
48
|
+
if (idx === -1) return null;
|
|
49
|
+
return path.basename(p, path.extname(p));
|
|
50
|
+
},
|
|
51
|
+
allowIfExists: true,
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
const ALWAYS_ALLOWED = [
|
|
56
|
+
'.claude/', 'docs/', 'outputs/', 'squads/', '.sinapse-ai/',
|
|
57
|
+
'.sinapse-custom/', 'node_modules/', '.git/',
|
|
58
|
+
'package.json', 'package-lock.json', 'tsconfig.json', '.env', 'README.md',
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Helpers
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
function projectRoot() {
|
|
66
|
+
return process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function relativize(filePath, root) {
|
|
70
|
+
if (filePath.startsWith(root)) {
|
|
71
|
+
return filePath.slice(root.length).replace(/^[/\\]+/, '');
|
|
72
|
+
}
|
|
73
|
+
return filePath;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function isAlwaysAllowed(rel) {
|
|
77
|
+
return ALWAYS_ALLOWED.some((a) => rel.includes(a));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function findProtection(rel) {
|
|
81
|
+
return PROTECTED_PATHS.find((p) => rel.includes(p.pattern)) || null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function docExists(rel, protection, root) {
|
|
85
|
+
const name = protection.extractName(rel);
|
|
86
|
+
if (!name) return true;
|
|
87
|
+
|
|
88
|
+
for (const dp of protection.docPatterns) {
|
|
89
|
+
const docPath = path.join(root, dp.replace('{name}', name));
|
|
90
|
+
if (fs.existsSync(docPath)) return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (protection.allowIfExists) {
|
|
94
|
+
const full = path.isAbsolute(rel) ? rel : path.join(root, rel);
|
|
95
|
+
if (fs.existsSync(full)) return true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// Main
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
function main() {
|
|
106
|
+
let input;
|
|
107
|
+
try {
|
|
108
|
+
input = JSON.parse(fs.readFileSync(0, 'utf8'));
|
|
109
|
+
} catch {
|
|
110
|
+
process.exit(0); // fail-open
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const toolName = input.tool_name || '';
|
|
114
|
+
if (toolName !== 'Write' && toolName !== 'Edit') {
|
|
115
|
+
process.exit(0);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const filePath = (input.tool_input || {}).file_path || '';
|
|
119
|
+
if (!filePath) process.exit(0);
|
|
120
|
+
|
|
121
|
+
const root = projectRoot();
|
|
122
|
+
const rel = relativize(filePath, root);
|
|
123
|
+
|
|
124
|
+
if (isAlwaysAllowed(rel)) process.exit(0);
|
|
125
|
+
|
|
126
|
+
const protection = findProtection(rel);
|
|
127
|
+
if (!protection) process.exit(0);
|
|
128
|
+
|
|
129
|
+
if (docExists(rel, protection, root)) process.exit(0);
|
|
130
|
+
|
|
131
|
+
// BLOCK
|
|
132
|
+
const name = protection.extractName(rel) || 'unknown';
|
|
133
|
+
const accepted = protection.docPatterns.map((d) => ` - ${d.replace('{name}', name)}`).join('\n');
|
|
134
|
+
|
|
135
|
+
process.stderr.write(
|
|
136
|
+
`\nARCHITECTURE-FIRST BLOCK: Documentation required before code.\n` +
|
|
137
|
+
`File: ${rel}\n` +
|
|
138
|
+
`Create one of:\n${accepted}\n` +
|
|
139
|
+
`Then retry the operation.\n`,
|
|
140
|
+
);
|
|
141
|
+
process.exit(2);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
main();
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hook: Enforce Mandatory Delegation — Constitution Article VIII
|
|
6
|
+
*
|
|
7
|
+
* RULE: Orchestrator agents (*-orqx) must NEVER execute domain work directly.
|
|
8
|
+
* They can only read, search, and delegate via Agent/SendMessage.
|
|
9
|
+
*
|
|
10
|
+
* Protocol (Claude Code PreToolUse):
|
|
11
|
+
* exit 0 → allow
|
|
12
|
+
* exit 2 → block (message shown to model via stderr)
|
|
13
|
+
*
|
|
14
|
+
* Fail-open: if session state is unreadable or agent is unknown, allow.
|
|
15
|
+
*
|
|
16
|
+
* Exception: sinapse-orqx is allowed Write/Edit in .sinapse-ai/ paths
|
|
17
|
+
* (framework governance — operates above the story layer).
|
|
18
|
+
*
|
|
19
|
+
* @module enforce-delegation
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Configuration
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
/** Agent IDs that are orchestrators (must delegate, never execute). */
|
|
30
|
+
const ORCHESTRATOR_PATTERN = /-orqx$/;
|
|
31
|
+
|
|
32
|
+
/** Tools that orchestrators are NOT allowed to use. */
|
|
33
|
+
const BLOCKED_TOOLS = ['Write', 'Edit', 'Bash', 'NotebookEdit'];
|
|
34
|
+
|
|
35
|
+
/** Paths where sinapse-orqx IS allowed to Write/Edit (framework governance). */
|
|
36
|
+
const FRAMEWORK_GOVERNANCE_PATHS = [
|
|
37
|
+
'.sinapse-ai/', '.claude/', '.sinapse/', 'bin/',
|
|
38
|
+
'package.json', 'core-config.yaml',
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Helpers
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
function projectRoot() {
|
|
46
|
+
return process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function relativize(filePath, root) {
|
|
50
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
51
|
+
const normalizedRoot = root.replace(/\\/g, '/');
|
|
52
|
+
if (normalized.startsWith(normalizedRoot)) {
|
|
53
|
+
return normalized.slice(normalizedRoot.length).replace(/^\/+/, '');
|
|
54
|
+
}
|
|
55
|
+
return normalized;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Read the active agent from session state.
|
|
60
|
+
* Returns the agent ID string or null if unknown.
|
|
61
|
+
*/
|
|
62
|
+
function getActiveAgent(root) {
|
|
63
|
+
const sessionStatePath = path.join(root, '.sinapse', 'session-state.json');
|
|
64
|
+
try {
|
|
65
|
+
const state = JSON.parse(fs.readFileSync(sessionStatePath, 'utf8'));
|
|
66
|
+
return state.lastAgent || null;
|
|
67
|
+
} catch {
|
|
68
|
+
return null; // fail-open
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isOrchestrator(agentId) {
|
|
73
|
+
return ORCHESTRATOR_PATTERN.test(agentId);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function isFrameworkGovernancePath(rel) {
|
|
77
|
+
return FRAMEWORK_GOVERNANCE_PATHS.some((fp) => rel.startsWith(fp) || rel === fp);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Main
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
function main() {
|
|
85
|
+
let input;
|
|
86
|
+
try {
|
|
87
|
+
input = JSON.parse(fs.readFileSync(0, 'utf8'));
|
|
88
|
+
} catch {
|
|
89
|
+
process.exit(0); // fail-open
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const toolName = input.tool_name || '';
|
|
93
|
+
|
|
94
|
+
// Only intercept domain-execution tools
|
|
95
|
+
if (!BLOCKED_TOOLS.includes(toolName)) {
|
|
96
|
+
process.exit(0);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const root = projectRoot();
|
|
100
|
+
const agentId = getActiveAgent(root);
|
|
101
|
+
|
|
102
|
+
// If no agent tracked or not an orchestrator, allow
|
|
103
|
+
if (!agentId || !isOrchestrator(agentId)) {
|
|
104
|
+
process.exit(0);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Special case: sinapse-orqx allowed for framework governance
|
|
108
|
+
if (agentId === 'sinapse-orqx') {
|
|
109
|
+
if (toolName === 'Write' || toolName === 'Edit') {
|
|
110
|
+
const filePath = (input.tool_input || {}).file_path || '';
|
|
111
|
+
if (filePath) {
|
|
112
|
+
const rel = relativize(filePath, root);
|
|
113
|
+
if (isFrameworkGovernancePath(rel)) {
|
|
114
|
+
process.exit(0); // Framework governance exception
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// sinapse-orqx blocked for non-governance Write/Edit and all Bash
|
|
119
|
+
// (it should delegate to @developer or @devops)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// BLOCK
|
|
123
|
+
const delegationMap = {
|
|
124
|
+
Write: '@developer (Dex)',
|
|
125
|
+
Edit: '@developer (Dex)',
|
|
126
|
+
Bash: '@developer (Dex) or @devops (Gage)',
|
|
127
|
+
NotebookEdit: '@developer (Dex)',
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const delegate = delegationMap[toolName] || '@developer';
|
|
131
|
+
|
|
132
|
+
process.stderr.write(
|
|
133
|
+
`\nMANDATORY DELEGATION BLOCK (Constitution Article VIII)\n` +
|
|
134
|
+
`Agent: ${agentId} (orchestrator)\n` +
|
|
135
|
+
`Tool: ${toolName}\n` +
|
|
136
|
+
`Orchestrators NEVER execute domain work directly.\n` +
|
|
137
|
+
`Delegate to ${delegate} for this operation.\n`,
|
|
138
|
+
);
|
|
139
|
+
process.exit(2);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
main();
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hook: Enforce Story Gate — Constitution Article III (Documentation-First)
|
|
6
|
+
*
|
|
7
|
+
* RULE: Code files in implementation paths cannot be created/edited unless
|
|
8
|
+
* a story exists in docs/stories/ with status >= Ready.
|
|
9
|
+
*
|
|
10
|
+
* Protocol (Claude Code PreToolUse):
|
|
11
|
+
* exit 0 → allow
|
|
12
|
+
* exit 2 → block (message shown to model via stderr)
|
|
13
|
+
*
|
|
14
|
+
* Fail-open: if session state is unreadable or story status is indeterminate,
|
|
15
|
+
* the hook allows the operation (never blocks productive work).
|
|
16
|
+
*
|
|
17
|
+
* @module enforce-story-gate
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Configuration
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
/** Paths that require an active story before code changes. */
|
|
28
|
+
const CODE_PATHS = [
|
|
29
|
+
'packages/', 'src/', 'app/', 'lib/', 'bin/',
|
|
30
|
+
'components/', 'pages/', 'api/', 'services/',
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
/** Paths always exempt from story requirement. */
|
|
34
|
+
const EXEMPT_PATHS = [
|
|
35
|
+
'.claude/', '.sinapse-ai/', '.sinapse/', '.sinapse-custom/',
|
|
36
|
+
'docs/', 'tests/', '__tests__/', 'test/',
|
|
37
|
+
'node_modules/', '.git/', 'squads/', 'outputs/',
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
/** Config files always exempt. */
|
|
41
|
+
const EXEMPT_FILES = [
|
|
42
|
+
'package.json', 'package-lock.json', 'tsconfig.json',
|
|
43
|
+
'.env', '.env.local', '.env.example',
|
|
44
|
+
'.gitignore', '.eslintrc', '.prettierrc',
|
|
45
|
+
'README.md', 'CHANGELOG.md',
|
|
46
|
+
'jest.config.js', 'jest.config.ts',
|
|
47
|
+
'vite.config.ts', 'next.config.js', 'next.config.mjs',
|
|
48
|
+
'tailwind.config.js', 'tailwind.config.ts',
|
|
49
|
+
'postcss.config.js', 'postcss.config.cjs',
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
/** Story statuses that allow implementation. */
|
|
53
|
+
const VALID_STATUSES = ['ready', 'inprogress', 'in progress', 'in_progress', 'inreview', 'in review', 'in_review', 'done'];
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Helpers
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
function projectRoot() {
|
|
60
|
+
return process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function relativize(filePath, root) {
|
|
64
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
65
|
+
const normalizedRoot = root.replace(/\\/g, '/');
|
|
66
|
+
if (normalized.startsWith(normalizedRoot)) {
|
|
67
|
+
return normalized.slice(normalizedRoot.length).replace(/^\/+/, '');
|
|
68
|
+
}
|
|
69
|
+
return normalized;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isExempt(rel) {
|
|
73
|
+
const basename = path.basename(rel);
|
|
74
|
+
if (EXEMPT_FILES.includes(basename)) return true;
|
|
75
|
+
return EXEMPT_PATHS.some((ep) => rel.startsWith(ep));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isCodePath(rel) {
|
|
79
|
+
return CODE_PATHS.some((cp) => rel.startsWith(cp));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Check if there's an active story with valid status.
|
|
84
|
+
* Reads .sinapse/session-state.json for story context,
|
|
85
|
+
* then scans docs/stories/ for any story with status >= Ready.
|
|
86
|
+
*/
|
|
87
|
+
function hasActiveStory(root) {
|
|
88
|
+
// Strategy 1: Check session state for active story
|
|
89
|
+
const sessionStatePath = path.join(root, '.sinapse', 'session-state.json');
|
|
90
|
+
try {
|
|
91
|
+
const state = JSON.parse(fs.readFileSync(sessionStatePath, 'utf8'));
|
|
92
|
+
if (state.activeStory && state.activeStory.status) {
|
|
93
|
+
const status = state.activeStory.status.toLowerCase().replace(/[\s_-]+/g, '');
|
|
94
|
+
if (VALID_STATUSES.some((vs) => vs.replace(/[\s_-]+/g, '') === status)) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
// No session state or invalid — continue to Strategy 2
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Strategy 2: Scan docs/stories/ for any story file
|
|
103
|
+
const storiesDir = path.join(root, 'docs', 'stories');
|
|
104
|
+
try {
|
|
105
|
+
if (!fs.existsSync(storiesDir)) return false;
|
|
106
|
+
|
|
107
|
+
// Recursively find .md files
|
|
108
|
+
const files = walkSync(storiesDir, '.md');
|
|
109
|
+
for (const file of files) {
|
|
110
|
+
try {
|
|
111
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
112
|
+
// Look for status field in YAML frontmatter or markdown
|
|
113
|
+
const statusMatch = content.match(/status:\s*["']?(\w[\w\s]*\w?)["']?/i);
|
|
114
|
+
if (statusMatch) {
|
|
115
|
+
const status = statusMatch[1].toLowerCase().replace(/[\s_-]+/g, '');
|
|
116
|
+
if (VALID_STATUSES.some((vs) => vs.replace(/[\s_-]+/g, '') === status)) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
// Skip unreadable files
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
} catch {
|
|
125
|
+
// Can't scan — fail-open
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Simple recursive file walker. */
|
|
133
|
+
function walkSync(dir, ext) {
|
|
134
|
+
const results = [];
|
|
135
|
+
try {
|
|
136
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
137
|
+
const full = path.join(dir, entry.name);
|
|
138
|
+
if (entry.isDirectory()) {
|
|
139
|
+
results.push(...walkSync(full, ext));
|
|
140
|
+
} else if (entry.name.endsWith(ext)) {
|
|
141
|
+
results.push(full);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
} catch {
|
|
145
|
+
// Skip inaccessible dirs
|
|
146
|
+
}
|
|
147
|
+
return results;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// Main
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
function main() {
|
|
155
|
+
let input;
|
|
156
|
+
try {
|
|
157
|
+
input = JSON.parse(fs.readFileSync(0, 'utf8'));
|
|
158
|
+
} catch {
|
|
159
|
+
process.exit(0); // fail-open
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const toolName = input.tool_name || '';
|
|
163
|
+
if (toolName !== 'Write' && toolName !== 'Edit') {
|
|
164
|
+
process.exit(0);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const filePath = (input.tool_input || {}).file_path || '';
|
|
168
|
+
if (!filePath) process.exit(0);
|
|
169
|
+
|
|
170
|
+
const root = projectRoot();
|
|
171
|
+
const rel = relativize(filePath, root);
|
|
172
|
+
|
|
173
|
+
// Exempt paths and files
|
|
174
|
+
if (isExempt(rel)) process.exit(0);
|
|
175
|
+
|
|
176
|
+
// Only enforce on code paths
|
|
177
|
+
if (!isCodePath(rel)) process.exit(0);
|
|
178
|
+
|
|
179
|
+
// Check for active story
|
|
180
|
+
if (hasActiveStory(root)) process.exit(0);
|
|
181
|
+
|
|
182
|
+
// BLOCK
|
|
183
|
+
process.stderr.write(
|
|
184
|
+
`\nDOCUMENTATION-FIRST BLOCK (Constitution Article III)\n` +
|
|
185
|
+
`File: ${rel}\n` +
|
|
186
|
+
`No active story found with status >= Ready in docs/stories/.\n` +
|
|
187
|
+
`Create a story first: @sprint-lead *draft\n` +
|
|
188
|
+
`Then validate it: @product-lead *validate\n` +
|
|
189
|
+
`Only then can implementation proceed.\n`,
|
|
190
|
+
);
|
|
191
|
+
process.exit(2);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
main();
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hook: Write Path Validation (CJS port)
|
|
6
|
+
*
|
|
7
|
+
* RULE: Documentation files should go to the correct paths per conventions.
|
|
8
|
+
* This hook WARNS (never blocks) when a doc path looks wrong.
|
|
9
|
+
*
|
|
10
|
+
* Protocol: always exit 0 (warn-only, never blocks).
|
|
11
|
+
*
|
|
12
|
+
* @module write-path-validation
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Configuration
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
const PATH_RULES = [
|
|
23
|
+
{
|
|
24
|
+
namePatterns: [/session/i, /handoff/i, /^2\d{3}-\d{2}-\d{2}/],
|
|
25
|
+
expectedPath: 'docs/sessions/',
|
|
26
|
+
description: 'Session logs e handoffs → docs/sessions/YYYY-MM/',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
namePatterns: [/architecture/i, /system-design/i, /infra/i],
|
|
30
|
+
expectedPath: 'docs/architecture/',
|
|
31
|
+
description: 'Docs de arquitetura → docs/architecture/',
|
|
32
|
+
excludePatterns: [/ARCHITECTURE_RULES/i],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
namePatterns: [/guide/i, /tutorial/i, /how-to/i],
|
|
36
|
+
expectedPath: 'docs/guides/',
|
|
37
|
+
description: 'Guias e tutoriais → docs/guides/',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
namePatterns: [/prd\.md$/i, /epic.*\.md$/i, /story.*\.md$/i],
|
|
41
|
+
expectedPath: 'docs/projects/',
|
|
42
|
+
description: 'PRDs, Epics, Stories → docs/projects/{project}/',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
namePatterns: [/mind.*specific/i, /mind.*validation/i],
|
|
46
|
+
expectedPath: 'outputs/minds/',
|
|
47
|
+
description: 'Docs de mind → outputs/minds/{slug}/docs/',
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const ALWAYS_VALID = [
|
|
52
|
+
'.claude/', '.sinapse-ai/', '.sinapse-upstream/', 'squads/',
|
|
53
|
+
'node_modules/', '.git/', 'app/', 'supabase/', 'outputs/',
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const DOC_EXTENSIONS = ['.md', '.mdx', '.txt', '.rst'];
|
|
57
|
+
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Helpers
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
function projectRoot() {
|
|
63
|
+
return process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function relativize(filePath, root) {
|
|
67
|
+
if (filePath.startsWith(root)) return filePath.slice(root.length).replace(/^[/\\]+/, '');
|
|
68
|
+
return filePath;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isAlwaysValid(rel) {
|
|
72
|
+
return ALWAYS_VALID.some((v) => rel.startsWith(v));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isDocFile(rel) {
|
|
76
|
+
return DOC_EXTENSIONS.some((ext) => rel.endsWith(ext));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function checkRules(rel) {
|
|
80
|
+
const filename = path.basename(rel);
|
|
81
|
+
for (const rule of PATH_RULES) {
|
|
82
|
+
const matchesName = rule.namePatterns.some((p) => p.test(filename));
|
|
83
|
+
if (!matchesName) continue;
|
|
84
|
+
|
|
85
|
+
if (rule.excludePatterns && rule.excludePatterns.some((p) => p.test(filename))) continue;
|
|
86
|
+
|
|
87
|
+
if (!rel.startsWith(rule.expectedPath)) {
|
|
88
|
+
return { currentPath: rel, expectedPath: rule.expectedPath, description: rule.description };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Main
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
function main() {
|
|
99
|
+
let input;
|
|
100
|
+
try {
|
|
101
|
+
input = JSON.parse(fs.readFileSync(0, 'utf8'));
|
|
102
|
+
} catch {
|
|
103
|
+
process.exit(0);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const toolName = input.tool_name || '';
|
|
107
|
+
if (toolName !== 'Write' && toolName !== 'Edit') process.exit(0);
|
|
108
|
+
|
|
109
|
+
const filePath = (input.tool_input || {}).file_path || '';
|
|
110
|
+
if (!filePath) process.exit(0);
|
|
111
|
+
|
|
112
|
+
const root = projectRoot();
|
|
113
|
+
const rel = relativize(filePath, root);
|
|
114
|
+
|
|
115
|
+
if (isAlwaysValid(rel)) process.exit(0);
|
|
116
|
+
if (!isDocFile(rel)) process.exit(0);
|
|
117
|
+
|
|
118
|
+
const violation = checkRules(rel);
|
|
119
|
+
if (!violation) process.exit(0);
|
|
120
|
+
|
|
121
|
+
// WARN only — never block
|
|
122
|
+
process.stderr.write(
|
|
123
|
+
`\nPATH WARNING: Document may be in the wrong location.\n` +
|
|
124
|
+
` File: ${violation.currentPath}\n` +
|
|
125
|
+
` Expected: ${violation.expectedPath}\n` +
|
|
126
|
+
` Rule: ${violation.description}\n` +
|
|
127
|
+
` NOTE: This is a WARNING only — the operation will proceed.\n`,
|
|
128
|
+
);
|
|
129
|
+
process.exit(0);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
main();
|
|
@@ -44,6 +44,18 @@ The incoming agent receives:
|
|
|
44
44
|
2. The **handoff artifact** from the previous agent (compact summary)
|
|
45
45
|
3. **NOT** the previous agent's full persona/instructions/tool definitions
|
|
46
46
|
|
|
47
|
+
### Scratchpad Protocol (v1.1)
|
|
48
|
+
|
|
49
|
+
**Before starting work**, the incoming agent MUST:
|
|
50
|
+
1. Check if `.sinapse/scratchpad/{story-id}/` exists
|
|
51
|
+
2. If yes, read ALL files in that directory (discoveries from previous agents)
|
|
52
|
+
3. Use those insights to inform decisions (avoid rediscovering known issues)
|
|
53
|
+
|
|
54
|
+
**Before handing off**, the outgoing agent SHOULD:
|
|
55
|
+
1. Write key discoveries to `.sinapse/scratchpad/{story-id}/{agent-id}.md`
|
|
56
|
+
2. Include the scratchpad path in the handoff artifact `scratchpad_path` field
|
|
57
|
+
3. Keep each file under 2KB (focused insights, not logs)
|
|
58
|
+
|
|
47
59
|
### Compaction Limits
|
|
48
60
|
|
|
49
61
|
| Limit | Value |
|
|
@@ -40,6 +40,9 @@ handoff:
|
|
|
40
40
|
# What the incoming agent should do next (max 2 sentences)
|
|
41
41
|
next_action: "" # e.g., "Run QA gate on TOK-4A. Verify handoff preserves story context."
|
|
42
42
|
|
|
43
|
+
# Cross-agent knowledge sharing (v1.1)
|
|
44
|
+
scratchpad_path: "" # e.g., ".sinapse/scratchpad/TOK-4A/" — read before starting work
|
|
45
|
+
|
|
43
46
|
# --- Compaction Limits (AC 9) ---
|
|
44
47
|
# Max artifact size: 500 tokens (~375 words)
|
|
45
48
|
# Max retained summaries: 3 (oldest discarded on 4th switch)
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
# - SHA256 hashes for change detection
|
|
8
8
|
# - File types for categorization
|
|
9
9
|
#
|
|
10
|
-
version: 7.
|
|
11
|
-
generated_at: "2026-
|
|
10
|
+
version: 7.6.0
|
|
11
|
+
generated_at: "2026-04-01T02:05:35.822Z"
|
|
12
12
|
generator: scripts/generate-install-manifest.js
|
|
13
13
|
file_count: 1104
|
|
14
14
|
files:
|
|
@@ -2565,9 +2565,9 @@ files:
|
|
|
2565
2565
|
type: task
|
|
2566
2566
|
size: 2254
|
|
2567
2567
|
- path: development/templates/agent-handoff-tmpl.yaml
|
|
2568
|
-
hash: sha256:
|
|
2568
|
+
hash: sha256:9b28cc790c81960acf9c8393555d6d5474afa724e152784f8983bbf70be89b3a
|
|
2569
2569
|
type: template
|
|
2570
|
-
size:
|
|
2570
|
+
size: 2124
|
|
2571
2571
|
- path: development/templates/chrome-brain/knowledge-base/chrome-brain.md
|
|
2572
2572
|
hash: sha256:ed47917601edfe48153290ea57b3d367220ceba6b976e7222c2451af6b4a6566
|
|
2573
2573
|
type: template
|
|
@@ -350,11 +350,7 @@ function installScripts(chromePath, platform) {
|
|
|
350
350
|
ok(`${name} created at ${scriptPath}`);
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
-
//
|
|
354
|
-
const pathDirs = (process.env.PATH || '').split(path.delimiter);
|
|
355
|
-
if (!pathDirs.includes(scriptsDir)) {
|
|
356
|
-
warn(`${scriptsDir} not in PATH. Add to your shell profile: export PATH="${scriptsDir}:$PATH"`);
|
|
357
|
-
}
|
|
353
|
+
// PATH check not needed — hooks use absolute paths (v7.4.7+)
|
|
358
354
|
|
|
359
355
|
return scriptsDir;
|
|
360
356
|
}
|
package/package.json
CHANGED
|
@@ -24,36 +24,24 @@ function getSinapseCoreSourcePath() {
|
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Folders to copy from .sinapse-ai
|
|
27
|
-
*
|
|
27
|
+
* v4.0.4 Modular Structure + active auxiliary directories
|
|
28
28
|
* @constant {string[]}
|
|
29
29
|
*/
|
|
30
30
|
const FOLDERS_TO_COPY = [
|
|
31
|
-
// v4.0.4
|
|
31
|
+
// v4.0.4 Four Pillars
|
|
32
32
|
'core', // Framework utilities, config, registry, migration
|
|
33
33
|
'development', // Agents, tasks, workflows, scripts, personas
|
|
34
34
|
'product', // Templates, checklists, cli, api
|
|
35
35
|
'infrastructure', // Hooks, telemetry, integrations, tools
|
|
36
36
|
|
|
37
|
-
//
|
|
38
|
-
'
|
|
39
|
-
'
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
-
'scripts',
|
|
45
|
-
'tasks',
|
|
46
|
-
'templates',
|
|
47
|
-
'tools',
|
|
48
|
-
'workflows',
|
|
49
|
-
|
|
50
|
-
// Additional directories
|
|
51
|
-
'cli', // CLI commands
|
|
52
|
-
'manifests', // Manifest definitions
|
|
53
|
-
'schemas', // JSON schemas for validation (*validate-squad, *migrate-squad)
|
|
37
|
+
// Active auxiliary directories (referenced by code/config)
|
|
38
|
+
'cli', // CLI commands (bin/sinapse.js)
|
|
39
|
+
'data', // Entity registry, tech presets, knowledge base
|
|
40
|
+
'elicitation', // Questionnaires (core-config reference)
|
|
41
|
+
'schemas', // JSON schemas for validation
|
|
42
|
+
'scripts', // Utility scripts (core-config reference)
|
|
43
|
+
'utils', // Shared utilities (tests, format-duration)
|
|
54
44
|
'workflow-intelligence', // Workflow intelligence engine (*next, *patterns)
|
|
55
|
-
'monitor', // Claude Code hooks for monitoring
|
|
56
|
-
'presets', // Configuration presets
|
|
57
45
|
];
|
|
58
46
|
|
|
59
47
|
/**
|
|
@@ -639,17 +639,19 @@ function showSuccessSummary(result) {
|
|
|
639
639
|
return;
|
|
640
640
|
}
|
|
641
641
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
642
|
+
// Compact summary: categorize files instead of listing each one
|
|
643
|
+
const agents = result.files.filter(f => f.includes('agents/') || f.includes('agents\\'));
|
|
644
|
+
const rules = result.files.filter(f => f.includes('rules/') || f.includes('rules\\'));
|
|
645
|
+
const hooks = result.files.filter(f => f.includes('hooks/') || f.includes('hooks\\'));
|
|
646
|
+
const other = result.files.length - agents.length - rules.length - hooks.length;
|
|
647
|
+
|
|
648
|
+
const parts = [];
|
|
649
|
+
if (agents.length) parts.push(`${agents.length} agents`);
|
|
650
|
+
if (rules.length) parts.push(`${rules.length} rules`);
|
|
651
|
+
if (hooks.length) parts.push(`${hooks.length} hooks`);
|
|
652
|
+
if (other > 0) parts.push(`${other} configs`);
|
|
653
|
+
|
|
654
|
+
console.log(`\n✅ IDE: ${result.files.length} files (${parts.join(', ')})`);
|
|
653
655
|
}
|
|
654
656
|
|
|
655
657
|
/**
|
|
@@ -499,17 +499,10 @@ async function runWizard(options = {}) {
|
|
|
499
499
|
});
|
|
500
500
|
|
|
501
501
|
if (sinapseCoreResult.success) {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
);
|
|
506
|
-
console.log(` - Tasks: ${sinapseCoreResult.installedFolders.includes('tasks') ? '✓' : '⨉'}`);
|
|
507
|
-
console.log(
|
|
508
|
-
` - Workflows: ${sinapseCoreResult.installedFolders.includes('workflows') ? '✓' : '⨉'}`,
|
|
509
|
-
);
|
|
510
|
-
console.log(
|
|
511
|
-
` - Templates: ${sinapseCoreResult.installedFolders.includes('templates') ? '✓' : '⨉'}`,
|
|
512
|
-
);
|
|
502
|
+
const pillars = ['core', 'development', 'product', 'infrastructure'];
|
|
503
|
+
const installed = pillars.filter(p => sinapseCoreResult.installedFolders.includes(p));
|
|
504
|
+
const aux = sinapseCoreResult.installedFolders.filter(f => !pillars.includes(f));
|
|
505
|
+
console.log(`✅ SINAPSE core: ${sinapseCoreResult.installedFiles.length} files (${installed.length} pillars, ${aux.length} modules)`);
|
|
513
506
|
}
|
|
514
507
|
answers.sinapseCoreInstalled = true;
|
|
515
508
|
answers.sinapseCoreResult = sinapseCoreResult;
|
|
@@ -104,16 +104,22 @@ function formatComponentSection(title, componentResults, componentName) {
|
|
|
104
104
|
|
|
105
105
|
if (checks.length === 0) return '';
|
|
106
106
|
|
|
107
|
-
const
|
|
107
|
+
const passed = checks.filter((c) => c.status === 'success').length;
|
|
108
|
+
const total = checks.length;
|
|
109
|
+
const allSuccess = passed === total;
|
|
108
110
|
const icon = allSuccess ? chalk.green('✅') : chalk.yellow('⚠️');
|
|
109
111
|
|
|
110
|
-
const lines = [`${icon} ${chalk.bold(title)}`];
|
|
112
|
+
const lines = [`${icon} ${chalk.bold(title)}: ${passed}/${total} checks passed`];
|
|
111
113
|
|
|
112
|
-
checks
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
// Only show individual checks if there are failures
|
|
115
|
+
if (!allSuccess) {
|
|
116
|
+
checks
|
|
117
|
+
.filter((c) => c.status !== 'success')
|
|
118
|
+
.forEach((check) => {
|
|
119
|
+
const message = check.file ? `${check.message} (${check.file})` : check.message;
|
|
120
|
+
lines.push(` ${chalk.yellow('⚠')} ${message}`);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
117
123
|
|
|
118
124
|
lines.push('');
|
|
119
125
|
|
|
@@ -757,11 +757,7 @@ function installChromeBrain(options = {}) {
|
|
|
757
757
|
}
|
|
758
758
|
}
|
|
759
759
|
|
|
760
|
-
//
|
|
761
|
-
const pathDirs = (process.env.PATH || '').split(path.delimiter);
|
|
762
|
-
if (!pathDirs.some((d) => d === binDir || d === binDir + path.sep)) {
|
|
763
|
-
LOG.warn(`${binDir} may not be in your PATH. Add it to your shell profile.`);
|
|
764
|
-
}
|
|
760
|
+
// PATH check not needed — hooks use absolute paths (v7.4.7+)
|
|
765
761
|
} catch (err) {
|
|
766
762
|
const msg = `Failed to create scripts: ${err.message}`;
|
|
767
763
|
LOG.fail(msg);
|
package/scripts/sinapse-patch.js
CHANGED
|
@@ -216,10 +216,3 @@ console.log(`\n=== Patch aplicado com sucesso: ${changes} alteracoes ===`);
|
|
|
216
216
|
console.log('');
|
|
217
217
|
console.log('Feche o terminal e abra novamente para ver as mudancas.');
|
|
218
218
|
console.log('');
|
|
219
|
-
console.log('Para reverter:');
|
|
220
|
-
console.log(' cp "' + BACKUP_PATH + '" "' + CLI_PATH + '"');
|
|
221
|
-
console.log(' ou: npm install -g @anthropic-ai/claude-code');
|
|
222
|
-
console.log('');
|
|
223
|
-
console.log('Para reaplicar apos update do Claude Code:');
|
|
224
|
-
console.log(' node sinapse-patch.js');
|
|
225
|
-
console.log('');
|
|
@@ -147,17 +147,17 @@ intelligent_routing:
|
|
|
147
147
|
direct_to_specialist:
|
|
148
148
|
when: "Single, well-defined task with clear specialist"
|
|
149
149
|
examples:
|
|
150
|
-
- "Crie um headline"
|
|
151
|
-
- "Analise esse concorrente"
|
|
152
|
-
- "Me ajude com pricing"
|
|
153
|
-
- "Revise meu codigo"
|
|
150
|
+
- '"Crie um headline" -> @headline-specialist'
|
|
151
|
+
- '"Analise esse concorrente" -> @deep-researcher'
|
|
152
|
+
- '"Me ajude com pricing" -> @pricing-strategist'
|
|
153
|
+
- '"Revise meu codigo" -> @qa'
|
|
154
154
|
|
|
155
155
|
via_orchestrator:
|
|
156
156
|
when: "Multi-agent workflow or broad domain request"
|
|
157
157
|
examples:
|
|
158
|
-
- "Construa minha marca"
|
|
159
|
-
- "Campanha de lancamento"
|
|
160
|
-
- "Assessment de seguranca"
|
|
158
|
+
- '"Construa minha marca" -> @brand-orqx'
|
|
159
|
+
- '"Campanha de lancamento" -> @paidmedia-orqx + @copy-orqx'
|
|
160
|
+
- '"Assessment de seguranca" -> @cyber-orqx'
|
|
161
161
|
|
|
162
162
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
163
163
|
# COMPLETE ROUTING TABLE — ALL 18 SQUADS
|
|
@@ -597,7 +597,7 @@ framework_compatibility:
|
|
|
597
597
|
- "@sinapse-orqx sends domain request to Imperator"
|
|
598
598
|
- "Imperator routes to correct squad orchestrator(s)"
|
|
599
599
|
- "Squad orchestrator executes with its agents"
|
|
600
|
-
- "Results flow back: squad
|
|
600
|
+
- "Results flow back: squad -> Imperator -> @sinapse-orqx"
|
|
601
601
|
coexistence_rules:
|
|
602
602
|
- "SINAPSE agents own development workflow: code, testing, architecture, stories, deploys"
|
|
603
603
|
- "Sinapse own domain expertise: branding, content, copy, growth, finance, etc."
|