sinapse-ai 1.25.0 → 1.25.2
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/settings.json +170 -0
- package/.codex/catalog.json +1 -1
- package/.codex/scripts/sinapse-codex.js +0 -5
- package/.codex/scripts/sync-codex-native.js +4 -37
- package/.codex/scripts/validate-codex-native.js +13 -23
- package/.codex/tasks/onboard-sinapse-codex.md +1 -1
- package/.sinapse-ai/data/entity-registry.yaml +44 -18
- package/.sinapse-ai/install-manifest.yaml +3 -3
- package/CHANGELOG.md +8 -14
- package/README.en.md +38 -30
- package/README.md +33 -25
- package/bin/cli.js +8 -1
- package/bin/commands/help.js +3 -2
- package/bin/commands/install.js +25 -14
- package/bin/commands/status.js +4 -3
- package/bin/commands/uninstall.js +216 -51
- package/bin/commands/update.js +8 -4
- package/bin/lib/global-provider-adapters.js +73 -10
- package/bin/lib/provider-parity.js +22 -0
- package/bin/postinstall.js +11 -11
- package/docs/framework/codex-parity/codex-integration-process.md +6 -7
- package/docs/framework/codex-parity/codex-native-runtime-v2.md +8 -8
- package/docs/framework/codex-parity/codex-parity-program.md +4 -4
- package/docs/framework/codex-parity/codex-total-parity-orchestration-plan.md +1 -1
- package/docs/getting-started.md +13 -5
- package/docs/guides/codex-config.md +35 -51
- package/docs/guides/development-setup.md +1 -2
- package/docs/guides/ide-integration.md +28 -29
- package/docs/guides/project-status-feature.md +35 -55
- package/docs/guides/quality-gates.md +2 -2
- package/docs/guides/user-guide.md +21 -10
- package/docs/ide-integration.md +2 -2
- package/docs/installation/README.md +4 -4
- package/docs/installation/faq.md +84 -70
- package/docs/installation/linux.md +11 -14
- package/docs/installation/macos.md +10 -4
- package/docs/installation/troubleshooting.md +11 -5
- package/docs/installation/v4-quick-start.md +14 -14
- package/docs/installation/windows.md +11 -15
- package/docs/pt/contributing.md +1 -1
- package/docs/pt/guides/development-setup.md +1 -2
- package/docs/pt/guides/project-status-feature.md +35 -55
- package/docs/pt/guides/quality-gates.md +2 -2
- package/docs/pt/guides/user-guide.md +21 -11
- package/docs/pt/ide-integration.md +6 -6
- package/docs/pt/installation/README.md +2 -4
- package/docs/pt/installation/faq.md +54 -89
- package/docs/pt/installation/linux.md +9 -27
- package/docs/pt/installation/macos.md +8 -12
- package/docs/pt/installation/troubleshooting.md +11 -10
- package/docs/pt/installation/v4-quick-start.md +17 -18
- package/docs/pt/installation/windows.md +8 -21
- package/docs/pt/platforms/claude-code.md +4 -4
- package/docs/sinapse-agent-flows/analyst-system.md +1 -1
- package/docs/sinapse-agent-flows/architect-system.md +1 -1
- package/docs/sinapse-agent-flows/data-engineer-system.md +1 -1
- package/docs/sinapse-agent-flows/dev-system.md +1 -1
- package/docs/sinapse-agent-flows/pm-system.md +1 -1
- package/docs/sinapse-agent-flows/qa-system.md +1 -1
- package/docs/sinapse-agent-flows/sm-system.md +1 -1
- package/docs/sinapse-agent-flows/snps-orqx-system.md +1 -1
- package/docs/sinapse-agent-flows/squad-creator-system.md +2 -2
- package/docs/sinapse-agent-flows/ux-design-expert-system.md +1 -1
- package/docs/troubleshooting.md +9 -7
- package/package.json +21 -16
- package/packages/installer/src/config/ide-configs.js +1 -1
- package/packages/installer/src/installer/sinapse-ai-installer.js +282 -10
- package/packages/installer/src/migrations/legacy-claude-agent-command-hashes.json +36 -0
- package/packages/installer/src/migrations/legacy-codex-native-skill-hashes.json +42 -0
- package/packages/installer/src/updater/index.js +24 -7
- package/packages/installer/src/wizard/ide-config-generator.js +4 -0
- package/packages/installer/src/wizard/index.js +6 -2
- package/scripts/validate-article-vii.js +305 -20
- package/scripts/validate-codex-command-registry.js +101 -0
- package/scripts/validate-install-docs.js +87 -3
- package/scripts/validate-provider-adapters.js +117 -1
- package/squads/claude-code-mastery/agents/skill-craftsman.md +2 -2
- package/squads/claude-code-mastery/knowledge-base/skill-creation-patterns.md +1 -1
- package/squads/squad-design/agents/ux-designer.md +1 -1
- package/.codex/skills/sinapse/SKILL.md +0 -10
- package/.codex/skills/sinapse-agent/SKILL.md +0 -22
- package/.codex/skills/sinapse-analyst/SKILL.md +0 -29
- package/.codex/skills/sinapse-animations/SKILL.md +0 -28
- package/.codex/skills/sinapse-architect/SKILL.md +0 -31
- package/.codex/skills/sinapse-brand/SKILL.md +0 -28
- package/.codex/skills/sinapse-claude/SKILL.md +0 -28
- package/.codex/skills/sinapse-cloning/SKILL.md +0 -28
- package/.codex/skills/sinapse-commercial/SKILL.md +0 -28
- package/.codex/skills/sinapse-content/SKILL.md +0 -28
- package/.codex/skills/sinapse-copy/SKILL.md +0 -28
- package/.codex/skills/sinapse-council/SKILL.md +0 -28
- package/.codex/skills/sinapse-courses/SKILL.md +0 -28
- package/.codex/skills/sinapse-cyber/SKILL.md +0 -28
- package/.codex/skills/sinapse-data-engineer/SKILL.md +0 -31
- package/.codex/skills/sinapse-design/SKILL.md +0 -28
- package/.codex/skills/sinapse-dev/SKILL.md +0 -31
- package/.codex/skills/sinapse-devops/SKILL.md +0 -31
- package/.codex/skills/sinapse-finance/SKILL.md +0 -28
- package/.codex/skills/sinapse-growth/SKILL.md +0 -28
- package/.codex/skills/sinapse-loop/SKILL.md +0 -35
- package/.codex/skills/sinapse-orqx/SKILL.md +0 -44
- package/.codex/skills/sinapse-paidmedia/SKILL.md +0 -28
- package/.codex/skills/sinapse-pm/SKILL.md +0 -31
- package/.codex/skills/sinapse-po/SKILL.md +0 -31
- package/.codex/skills/sinapse-product/SKILL.md +0 -28
- package/.codex/skills/sinapse-qa/SKILL.md +0 -31
- package/.codex/skills/sinapse-research/SKILL.md +0 -28
- package/.codex/skills/sinapse-sm/SKILL.md +0 -27
- package/.codex/skills/sinapse-spec-driven/SKILL.md +0 -35
- package/.codex/skills/sinapse-squad-creator/SKILL.md +0 -31
- package/.codex/skills/sinapse-storytelling/SKILL.md +0 -28
- package/.codex/skills/sinapse-swarm/SKILL.md +0 -28
- package/.codex/skills/sinapse-ux-design-expert/SKILL.md +0 -24
- package/.codex/skills/snps/SKILL.md +0 -10
- package/.codex/skills/snps-orqx/SKILL.md +0 -10
- /package/{.codex → .agents}/skills/react-bits-frontend/SKILL.md +0 -0
- /package/{.codex → .agents}/skills/react-bits-frontend/agents/openai.yaml +0 -0
- /package/{.codex → .agents}/skills/react-bits-frontend/references/corpus-map.md +0 -0
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
|
+
const crypto = require('crypto');
|
|
7
|
+
const legacyClaudeCommandHashes = require('../../packages/installer/src/migrations/legacy-claude-agent-command-hashes.json');
|
|
6
8
|
const { getLogger } = require('../../.sinapse-ai/core/logger');
|
|
7
9
|
const {
|
|
8
10
|
HOME,
|
|
@@ -58,22 +60,61 @@ async function removeGitHooksConfig(projectDir = process.cwd()) {
|
|
|
58
60
|
// and removes only those files (preserving anything the user added by hand).
|
|
59
61
|
const INSTALLED_AGENTS_MANIFEST = path.join(SINAPSE_HOME, 'installed-agents.json');
|
|
60
62
|
|
|
61
|
-
function
|
|
63
|
+
function installedAgentsManifestPath(home = HOME) {
|
|
64
|
+
return home === HOME
|
|
65
|
+
? INSTALLED_AGENTS_MANIFEST
|
|
66
|
+
: path.join(home, '.sinapse', 'installed-agents.json');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function digestFile(filePath) {
|
|
62
70
|
try {
|
|
63
|
-
fs.
|
|
64
|
-
|
|
65
|
-
|
|
71
|
+
return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function providerAgentPath(home, provider, filename) {
|
|
78
|
+
const providerDir = provider === 'codex'
|
|
79
|
+
? path.join(home, '.codex', 'agents')
|
|
80
|
+
: path.join(home, '.claude', 'agents');
|
|
81
|
+
return path.join(providerDir, filename);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function inferProviderFromAgentDir(dir) {
|
|
85
|
+
const normalized = path.resolve(dir).replace(/\\/g, '/').toLowerCase();
|
|
86
|
+
if (normalized.includes('/.codex/agents')) return 'codex';
|
|
87
|
+
if (normalized.includes('/.claude/agents')) return 'claude-code';
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function recordInstalledAgents(filenames, ides, home = HOME) {
|
|
92
|
+
try {
|
|
93
|
+
const manifestPath = installedAgentsManifestPath(home);
|
|
94
|
+
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
95
|
+
const artifacts = [...filenames].map((filename) => {
|
|
96
|
+
const provider = filename.endsWith('.toml') ? 'codex' : 'claude-code';
|
|
97
|
+
return {
|
|
98
|
+
provider,
|
|
99
|
+
filename,
|
|
100
|
+
sha256: digestFile(providerAgentPath(home, provider, filename)),
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
fs.writeFileSync(manifestPath, JSON.stringify({
|
|
104
|
+
version: 2,
|
|
66
105
|
timestamp: new Date().toISOString(),
|
|
67
106
|
ides,
|
|
68
107
|
filenames: [...filenames].sort(),
|
|
108
|
+
artifacts: artifacts.sort((a, b) => `${a.provider}/${a.filename}`.localeCompare(`${b.provider}/${b.filename}`)),
|
|
69
109
|
}, null, 2));
|
|
70
110
|
} catch { /* non-critical */ }
|
|
71
111
|
}
|
|
72
112
|
|
|
73
|
-
function readInstalledAgentsManifest() {
|
|
113
|
+
function readInstalledAgentsManifest(home = HOME) {
|
|
74
114
|
try {
|
|
75
|
-
|
|
76
|
-
|
|
115
|
+
const manifestPath = installedAgentsManifestPath(home);
|
|
116
|
+
if (!fs.existsSync(manifestPath)) return null;
|
|
117
|
+
const raw = fs.readFileSync(manifestPath, 'utf8');
|
|
77
118
|
const parsed = JSON.parse(raw);
|
|
78
119
|
if (!parsed || !Array.isArray(parsed.filenames)) return null;
|
|
79
120
|
return parsed;
|
|
@@ -85,35 +126,36 @@ function readInstalledAgentsManifest() {
|
|
|
85
126
|
function removeInstalledAgentsFrom(dir, manifestOverride = null) {
|
|
86
127
|
if (!fs.existsSync(dir)) return { removed: 0, existed: false };
|
|
87
128
|
let removed = 0;
|
|
129
|
+
const manifest = manifestOverride || readInstalledAgentsManifest();
|
|
130
|
+
const provider = inferProviderFromAgentDir(dir);
|
|
88
131
|
let candidates;
|
|
89
132
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
133
|
+
if (manifest?.version >= 2 && Array.isArray(manifest.artifacts)) {
|
|
134
|
+
candidates = manifest.artifacts.filter((artifact) => {
|
|
135
|
+
if (!artifact || typeof artifact.filename !== 'string') return false;
|
|
136
|
+
return provider ? artifact.provider === provider : true;
|
|
137
|
+
});
|
|
94
138
|
} else {
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
try { return /SINAPSE/i.test(fs.readFileSync(path.join(dir, f), 'utf8').slice(0, 4000)); } catch { return false; }
|
|
102
|
-
}
|
|
103
|
-
if (!f.endsWith('.md')) return false;
|
|
104
|
-
if (/-orqx\.md$/.test(f)) return true;
|
|
105
|
-
try {
|
|
106
|
-
const head = fs.readFileSync(path.join(dir, f), 'utf8').slice(0, 600);
|
|
107
|
-
return /^\s*name:\s*sinapse-/im.test(head);
|
|
108
|
-
} catch { return false; }
|
|
109
|
-
});
|
|
110
|
-
} catch { return { removed: 0, existed: true }; }
|
|
139
|
+
// Legacy manifests did not record provider or digests. Ownership must be
|
|
140
|
+
// proven by the marker in the actual provider directory before deletion.
|
|
141
|
+
const filenames = Array.isArray(manifest?.filenames)
|
|
142
|
+
? manifest.filenames
|
|
143
|
+
: fs.readdirSync(dir);
|
|
144
|
+
candidates = filenames.map((filename) => ({ filename, sha256: null, legacy: true }));
|
|
111
145
|
}
|
|
112
146
|
|
|
113
|
-
for (const
|
|
114
|
-
const filepath = path.join(dir,
|
|
147
|
+
for (const { filename, sha256, legacy } of candidates) {
|
|
148
|
+
const filepath = path.join(dir, filename);
|
|
115
149
|
if (!fs.existsSync(filepath)) continue;
|
|
116
150
|
try {
|
|
151
|
+
if (sha256) {
|
|
152
|
+
if (digestFile(filepath) !== sha256) continue;
|
|
153
|
+
} else {
|
|
154
|
+
// A v2 entry without a digest does not prove the installer wrote it.
|
|
155
|
+
if (!legacy) continue;
|
|
156
|
+
const content = fs.readFileSync(filepath, 'utf8');
|
|
157
|
+
if (!content.includes('SINAPSE-MANAGED:global-agent')) continue;
|
|
158
|
+
}
|
|
117
159
|
fs.unlinkSync(filepath);
|
|
118
160
|
removed++;
|
|
119
161
|
} catch { /* best-effort */ }
|
|
@@ -128,44 +170,166 @@ function removeOrqxAgentsFrom(dir) {
|
|
|
128
170
|
return removeInstalledAgentsFrom(dir);
|
|
129
171
|
}
|
|
130
172
|
|
|
131
|
-
const MANAGED_GLOBAL_SKILL_IDS = ['snps', 'sinapse', 'snps-orqx', 'sinapse-agent'];
|
|
173
|
+
const MANAGED_GLOBAL_SKILL_IDS = ['snps', 'sinapse', 'snps-orqx', 'sinapse-orqx', 'sinapse-agent'];
|
|
132
174
|
const MANAGED_GLOBAL_SKILL_MARKER = 'SINAPSE-MANAGED:global-skill';
|
|
133
175
|
|
|
134
|
-
function removeManagedGlobalSkills(home = HOME) {
|
|
176
|
+
function removeManagedGlobalSkills(home = HOME, options = {}) {
|
|
135
177
|
let removed = 0;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (fs.
|
|
145
|
-
|
|
146
|
-
|
|
178
|
+
const providers = options.providers || ['codex', 'claude-code'];
|
|
179
|
+
const skillRoots = [];
|
|
180
|
+
if (providers.includes('codex')) skillRoots.push(path.join(home, '.agents', 'skills'));
|
|
181
|
+
if (providers.includes('claude-code')) skillRoots.push(path.join(home, '.claude', 'skills'));
|
|
182
|
+
for (const skillsRoot of skillRoots) {
|
|
183
|
+
for (const skillId of MANAGED_GLOBAL_SKILL_IDS) {
|
|
184
|
+
const skillDir = path.join(skillsRoot, skillId);
|
|
185
|
+
const skillPath = path.join(skillDir, 'SKILL.md');
|
|
186
|
+
if (!fs.existsSync(skillPath)) continue;
|
|
187
|
+
try {
|
|
188
|
+
const content = fs.readFileSync(skillPath, 'utf8');
|
|
189
|
+
if (!content.includes(MANAGED_GLOBAL_SKILL_MARKER)) continue;
|
|
190
|
+
fs.unlinkSync(skillPath);
|
|
191
|
+
if (fs.readdirSync(skillDir).length === 0) fs.rmdirSync(skillDir);
|
|
192
|
+
removed++;
|
|
193
|
+
} catch { /* best-effort */ }
|
|
194
|
+
}
|
|
147
195
|
}
|
|
148
196
|
return { removed };
|
|
149
197
|
}
|
|
150
198
|
|
|
151
199
|
function reconcileInstalledAgents(home, desiredFilenames) {
|
|
152
|
-
const manifest = readInstalledAgentsManifest();
|
|
153
|
-
if (!manifest) return { removed: 0 };
|
|
200
|
+
const manifest = readInstalledAgentsManifest(home);
|
|
154
201
|
const desired = new Set(desiredFilenames);
|
|
202
|
+
const desiredByProvider = {
|
|
203
|
+
'claude-code': new Set([...desired].filter((name) => name.endsWith('.md'))),
|
|
204
|
+
codex: new Set([...desired].filter((name) => name.endsWith('.toml'))),
|
|
205
|
+
};
|
|
155
206
|
let removed = 0;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
207
|
+
const legacyArtifacts = (manifest?.filenames || []).map((filename) => ({
|
|
208
|
+
provider: filename.endsWith('.toml') ? 'codex' : 'claude-code',
|
|
209
|
+
filename,
|
|
210
|
+
sha256: null,
|
|
211
|
+
}));
|
|
212
|
+
const manifestArtifacts = Array.isArray(manifest?.artifacts) ? manifest.artifacts : legacyArtifacts;
|
|
213
|
+
const manifestOwned = new Set(manifestArtifacts.map(({ provider, filename }) => `${provider}/${filename}`));
|
|
214
|
+
const versionedOwned = new Set(Array.isArray(manifest?.artifacts)
|
|
215
|
+
? manifestArtifacts.map(({ provider, filename }) => `${provider}/${filename}`)
|
|
216
|
+
: []);
|
|
217
|
+
const managedMarker = 'SINAPSE-MANAGED:global-agent';
|
|
218
|
+
for (const { provider, filename, sha256 } of manifestArtifacts) {
|
|
219
|
+
if (!desiredByProvider[provider] || desiredByProvider[provider].has(filename)) continue;
|
|
220
|
+
const filepath = providerAgentPath(home, provider, filename);
|
|
162
221
|
if (!fs.existsSync(filepath)) continue;
|
|
163
222
|
try {
|
|
223
|
+
const content = fs.readFileSync(filepath, 'utf8');
|
|
224
|
+
const digestMatches = Boolean(sha256) && digestFile(filepath) === sha256;
|
|
225
|
+
const legacyMarkerMatches = !Array.isArray(manifest?.artifacts) && content.includes(managedMarker);
|
|
226
|
+
if (sha256 ? !digestMatches : !legacyMarkerMatches) continue;
|
|
164
227
|
fs.unlinkSync(filepath);
|
|
165
228
|
removed++;
|
|
166
229
|
} catch { /* best-effort */ }
|
|
167
230
|
}
|
|
168
|
-
|
|
231
|
+
|
|
232
|
+
for (const [provider, providerDir, extension] of [
|
|
233
|
+
['claude-code', path.join(home, '.claude', 'agents'), '.md'],
|
|
234
|
+
['codex', path.join(home, '.codex', 'agents'), '.toml'],
|
|
235
|
+
['codex', path.join(home, '.codex', 'agents'), '.md'],
|
|
236
|
+
]) {
|
|
237
|
+
if (!fs.existsSync(providerDir)) continue;
|
|
238
|
+
for (const filename of fs.readdirSync(providerDir).filter((name) => name.endsWith(extension))) {
|
|
239
|
+
if (desiredByProvider[provider].has(filename)) continue;
|
|
240
|
+
const filepath = path.join(providerDir, filename);
|
|
241
|
+
try {
|
|
242
|
+
const content = fs.readFileSync(filepath, 'utf8');
|
|
243
|
+
if (versionedOwned.has(`${provider}/${filename}`)) continue;
|
|
244
|
+
if (!manifestOwned.has(`${provider}/${filename}`) && !content.includes(managedMarker)) continue;
|
|
245
|
+
// A manifest entry alone is not proof of current ownership: users can
|
|
246
|
+
// replace a formerly managed file. Only marked artifacts reach this
|
|
247
|
+
// sweep; exact recorded content was handled by the digest path above.
|
|
248
|
+
if (!content.includes(managedMarker)) continue;
|
|
249
|
+
fs.unlinkSync(filepath);
|
|
250
|
+
removed++;
|
|
251
|
+
} catch { /* best-effort */ }
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const legacyCommands = path.join(home, '.claude', 'commands', 'SINAPSE', 'agents');
|
|
256
|
+
let legacyCommandDirectoryRemoved = false;
|
|
257
|
+
let legacyCommandsRemoved = 0;
|
|
258
|
+
let legacyCommandsPreserved = 0;
|
|
259
|
+
if (fs.existsSync(legacyCommands)) {
|
|
260
|
+
for (const entry of fs.readdirSync(legacyCommands, { withFileTypes: true })) {
|
|
261
|
+
const filePath = path.join(legacyCommands, entry.name);
|
|
262
|
+
if (!entry.isFile()) {
|
|
263
|
+
legacyCommandsPreserved++;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
const content = fs.readFileSync(filePath);
|
|
268
|
+
const digest = crypto.createHash('sha256').update(content).digest('hex');
|
|
269
|
+
const knownHashes = legacyClaudeCommandHashes.files[entry.name] || [];
|
|
270
|
+
if (!knownHashes.includes(digest)) {
|
|
271
|
+
legacyCommandsPreserved++;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
fs.unlinkSync(filePath);
|
|
275
|
+
legacyCommandsRemoved++;
|
|
276
|
+
} catch {
|
|
277
|
+
legacyCommandsPreserved++;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
if (fs.readdirSync(legacyCommands).length === 0) {
|
|
282
|
+
fs.rmdirSync(legacyCommands);
|
|
283
|
+
legacyCommandDirectoryRemoved = true;
|
|
284
|
+
}
|
|
285
|
+
} catch { /* best-effort */ }
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
removed,
|
|
289
|
+
legacyCommandDirectoryRemoved,
|
|
290
|
+
legacyCommandsRemoved,
|
|
291
|
+
legacyCommandsPreserved,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function hasManagedInstalledAgents(home = HOME) {
|
|
296
|
+
const manifest = readInstalledAgentsManifest(home);
|
|
297
|
+
if (!manifest) {
|
|
298
|
+
for (const [providerDir, extensions] of [
|
|
299
|
+
[path.join(home, '.claude', 'agents'), new Set(['.md'])],
|
|
300
|
+
[path.join(home, '.codex', 'agents'), new Set(['.toml', '.md'])],
|
|
301
|
+
]) {
|
|
302
|
+
try {
|
|
303
|
+
for (const filename of fs.readdirSync(providerDir)) {
|
|
304
|
+
if (!extensions.has(path.extname(filename))) continue;
|
|
305
|
+
try {
|
|
306
|
+
const content = fs.readFileSync(path.join(providerDir, filename), 'utf8');
|
|
307
|
+
if (content.includes('SINAPSE-MANAGED:global-agent')) return true;
|
|
308
|
+
} catch { /* unreadable or non-regular entries are not evidence */ }
|
|
309
|
+
}
|
|
310
|
+
} catch { /* missing or unreadable provider dir */ }
|
|
311
|
+
}
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
const versioned = Array.isArray(manifest?.artifacts);
|
|
315
|
+
const artifacts = versioned
|
|
316
|
+
? manifest.artifacts
|
|
317
|
+
: (manifest?.filenames || []).map((filename) => ({
|
|
318
|
+
provider: filename.endsWith('.toml') ? 'codex' : 'claude-code',
|
|
319
|
+
filename,
|
|
320
|
+
sha256: null,
|
|
321
|
+
}));
|
|
322
|
+
return artifacts.some(({ provider, filename, sha256 }) => {
|
|
323
|
+
const filePath = providerAgentPath(home, provider, filename);
|
|
324
|
+
try {
|
|
325
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
326
|
+
return sha256
|
|
327
|
+
? digestFile(filePath) === sha256
|
|
328
|
+
: !versioned && content.includes('SINAPSE-MANAGED:global-agent');
|
|
329
|
+
} catch {
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
});
|
|
169
333
|
}
|
|
170
334
|
|
|
171
335
|
// Story 10.40 — Strip SINAPSE-owned keys from ~/.claude/settings.json without
|
|
@@ -312,6 +476,7 @@ module.exports = {
|
|
|
312
476
|
removeOrqxAgentsFrom,
|
|
313
477
|
removeManagedGlobalSkills,
|
|
314
478
|
reconcileInstalledAgents,
|
|
479
|
+
hasManagedInstalledAgents,
|
|
315
480
|
cleanClaudeSettingsJson,
|
|
316
481
|
removeGitHooksConfig,
|
|
317
482
|
INSTALLED_AGENTS_MANIFEST,
|
package/bin/commands/update.js
CHANGED
|
@@ -36,6 +36,7 @@ const {
|
|
|
36
36
|
} = require('./uninstall');
|
|
37
37
|
const { regenerateAgentCommands } = require('../lib/command-generator');
|
|
38
38
|
const { deliverGlobalProviderAdapters, getGlobalCommandStagingDir } = require('../lib/global-provider-adapters');
|
|
39
|
+
const { assertProviderAdapterParity } = require('../lib/provider-parity');
|
|
39
40
|
const { execSync } = require('child_process');
|
|
40
41
|
|
|
41
42
|
// Query the latest version published to npm. Returns null when npm is unreachable.
|
|
@@ -180,12 +181,14 @@ async function cmdUpdateGlobal() {
|
|
|
180
181
|
|
|
181
182
|
// Phase 2b: Install global agents based on LLM choice
|
|
182
183
|
const globalAdapters = deliverGlobalProviderAdapters({ llmChoice, home: HOME, commandsDir: commandStagingDir });
|
|
184
|
+
const activeAgentCount = assertProviderAdapterParity(llmChoice, globalAdapters, totalAgents);
|
|
183
185
|
if (globalAdapters.claude.length) logger.always(` ${GREEN}OK${NC} Claude Code global agents (${globalAdapters.claude.length})`);
|
|
184
186
|
if (globalAdapters.codex.length) logger.always(` ${GREEN}OK${NC} Codex global agents (${globalAdapters.codex.length} TOML, ${globalAdapters.skills.length} skills)`);
|
|
185
187
|
|
|
186
188
|
const installedAgentFilenames = new Set([...globalAdapters.claude, ...globalAdapters.codex]);
|
|
187
189
|
reconcileInstalledAgents(HOME, installedAgentFilenames);
|
|
188
|
-
if (llmChoice === 'claude-code') removeManagedGlobalSkills(HOME);
|
|
190
|
+
if (llmChoice === 'claude-code') removeManagedGlobalSkills(HOME, { providers: ['codex'] });
|
|
191
|
+
if (llmChoice === 'codex') removeManagedGlobalSkills(HOME, { providers: ['claude-code'] });
|
|
189
192
|
const installedIdes = [];
|
|
190
193
|
if (globalAdapters.claude.length) installedIdes.push('claude-code');
|
|
191
194
|
if (globalAdapters.codex.length) installedIdes.push('codex');
|
|
@@ -203,7 +206,8 @@ async function cmdUpdateGlobal() {
|
|
|
203
206
|
meta.updatedAt = new Date().toISOString();
|
|
204
207
|
meta.version = VERSION;
|
|
205
208
|
meta.squads = squads.length;
|
|
206
|
-
meta.
|
|
209
|
+
meta.agents = activeAgentCount;
|
|
210
|
+
meta.commands = activeAgentCount;
|
|
207
211
|
meta.llm = llmChoice;
|
|
208
212
|
fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2));
|
|
209
213
|
|
|
@@ -246,9 +250,9 @@ async function cmdUpdateGlobal() {
|
|
|
246
250
|
logger.always(`${GREEN} SINAPSE AI atualizado para v${VERSION}!${NC}`);
|
|
247
251
|
logger.always(`${GREEN}══════════════════════════════════════════════════════════════${NC}`);
|
|
248
252
|
logger.always('');
|
|
249
|
-
logger.always(` ${BOLD}${squads.length} squads${NC} | ${BOLD}${
|
|
253
|
+
logger.always(` ${BOLD}${squads.length} squads${NC} | ${BOLD}${activeAgentCount} agents${NC} | ${BOLD}${activeAgentCount} native adapters${NC}`);
|
|
250
254
|
logger.always(` ${startCmd}`);
|
|
251
255
|
logger.always('');
|
|
252
256
|
}
|
|
253
257
|
|
|
254
|
-
module.exports = { cmdUpdateGlobal };
|
|
258
|
+
module.exports = { cmdUpdateGlobal, assertProviderAdapterParity };
|
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
|
|
6
|
+
const NOFOLLOW_READ_FLAGS = fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0);
|
|
7
|
+
|
|
8
|
+
function readRegularFileNoFollowSync(filePath, encoding = null) {
|
|
9
|
+
let handle;
|
|
10
|
+
try {
|
|
11
|
+
handle = fs.openSync(filePath, NOFOLLOW_READ_FLAGS);
|
|
12
|
+
if (!fs.fstatSync(handle).isFile()) return null;
|
|
13
|
+
return fs.readFileSync(handle, encoding || undefined);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if (['ENOENT', 'ELOOP', 'EISDIR'].includes(error.code)) return null;
|
|
16
|
+
throw error;
|
|
17
|
+
} finally {
|
|
18
|
+
if (handle !== undefined) fs.closeSync(handle);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
6
22
|
function parseAgentMarkdown(content, fallbackName) {
|
|
7
23
|
const name = content.match(/^name:\s*([^\r\n]+)$/m)?.[1]?.trim() || fallbackName;
|
|
8
24
|
const raw = content.match(/^description:\s*(.+)$/m)?.[1]?.trim() || `SINAPSE ${name} agent`;
|
|
@@ -18,6 +34,15 @@ function renderCodexToml(name, description, instructions) {
|
|
|
18
34
|
].join('\n');
|
|
19
35
|
}
|
|
20
36
|
|
|
37
|
+
function markGlobalAgent(markdown, fileName = '') {
|
|
38
|
+
let content = markdown;
|
|
39
|
+
if (fileName === 'snps-orqx.md') {
|
|
40
|
+
content = content.replace(/^name:\s*snps-orqx\s*$/m, 'name: sinapse-orqx');
|
|
41
|
+
}
|
|
42
|
+
if (content.includes('SINAPSE-MANAGED:global-agent')) return content;
|
|
43
|
+
return `${content.trimEnd()}\n\n<!-- SINAPSE-MANAGED:global-agent -->\n`;
|
|
44
|
+
}
|
|
45
|
+
|
|
21
46
|
function renderGlobalSkill(skillId) {
|
|
22
47
|
const generic = skillId === 'sinapse-agent';
|
|
23
48
|
return [
|
|
@@ -28,7 +53,7 @@ function renderGlobalSkill(skillId) {
|
|
|
28
53
|
'<!-- SINAPSE-MANAGED:global-skill -->', '',
|
|
29
54
|
generic ? '# SINAPSE Global Agent Activator' : '# SINAPSE Global Orchestrator', '',
|
|
30
55
|
generic
|
|
31
|
-
? '
|
|
56
|
+
? 'Resolve the requested ID through the native agent catalog of the active provider; reject unknown IDs without guessing.'
|
|
32
57
|
: 'Resolve this alias to the `sinapse-orqx` custom agent and preserve its delegation-only authority.',
|
|
33
58
|
'Canonical global persona and task sources live under `~/.sinapse/`.',
|
|
34
59
|
'Use native Codex delegation and never start a nested CLI.', '',
|
|
@@ -88,25 +113,63 @@ function writeGlobalSkillIfManaged(skillPath, content) {
|
|
|
88
113
|
}
|
|
89
114
|
}
|
|
90
115
|
|
|
116
|
+
function removeStaleManagedAgents(targetDir, expectedFiles, extension) {
|
|
117
|
+
if (!fs.existsSync(targetDir)) return [];
|
|
118
|
+
const expected = new Set(expectedFiles);
|
|
119
|
+
const removed = [];
|
|
120
|
+
for (const file of fs.readdirSync(targetDir).filter((name) => name.endsWith(extension))) {
|
|
121
|
+
if (expected.has(file)) continue;
|
|
122
|
+
const filePath = path.join(targetDir, file);
|
|
123
|
+
const content = readRegularFileNoFollowSync(filePath, 'utf8');
|
|
124
|
+
if (content === null) continue;
|
|
125
|
+
const explicitlyManaged = content.includes('SINAPSE-MANAGED:global-agent');
|
|
126
|
+
const legacyManaged = content.includes('ACTIVATION-NOTICE: This command activates an agent from sinapse.')
|
|
127
|
+
&& /\.sinapse[\\/]sinapse[\\/]agents[\\/]/.test(content)
|
|
128
|
+
&& content.includes('Load the squad manifest');
|
|
129
|
+
if (!explicitlyManaged && !legacyManaged) continue;
|
|
130
|
+
fs.unlinkSync(filePath);
|
|
131
|
+
removed.push(file);
|
|
132
|
+
}
|
|
133
|
+
return removed;
|
|
134
|
+
}
|
|
135
|
+
|
|
91
136
|
function deliverGlobalProviderAdapters({ llmChoice, home, commandsDir }) {
|
|
92
|
-
|
|
137
|
+
let commandFiles = fs.existsSync(commandsDir)
|
|
93
138
|
? fs.readdirSync(commandsDir).filter((file) => file.endsWith('.md')).sort()
|
|
94
139
|
: [];
|
|
95
|
-
|
|
140
|
+
if (commandFiles.includes('snps-orqx.md')) {
|
|
141
|
+
const aliasFiles = new Set(['sinapse.md', 'sinapse-orqx.md', 'snps.md']);
|
|
142
|
+
commandFiles = commandFiles.filter((file) => !aliasFiles.has(file));
|
|
143
|
+
}
|
|
144
|
+
const written = { claude: [], claudeSkills: [], codex: [], skills: [] };
|
|
96
145
|
|
|
97
146
|
if (llmChoice === 'claude-code' || llmChoice === 'both') {
|
|
98
147
|
const targetDir = path.join(home, '.claude', 'agents');
|
|
99
148
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
149
|
+
removeStaleManagedAgents(targetDir, commandFiles, '.md');
|
|
100
150
|
for (const file of commandFiles) {
|
|
101
|
-
fs.
|
|
151
|
+
const markdown = markGlobalAgent(fs.readFileSync(path.join(commandsDir, file), 'utf8'), file);
|
|
152
|
+
fs.writeFileSync(path.join(targetDir, file), markdown, 'utf8');
|
|
102
153
|
written.claude.push(file);
|
|
103
154
|
}
|
|
155
|
+
const skillsRoot = path.join(home, '.claude', 'skills');
|
|
156
|
+
for (const skillId of ['snps', 'sinapse', 'snps-orqx', 'sinapse-orqx', 'sinapse-agent']) {
|
|
157
|
+
const skillPath = path.join(skillsRoot, skillId, 'SKILL.md');
|
|
158
|
+
if (writeGlobalSkillIfManaged(skillPath, renderGlobalSkill(skillId))) {
|
|
159
|
+
written.claudeSkills.push(skillId);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
104
162
|
}
|
|
105
163
|
if (llmChoice === 'codex' || llmChoice === 'both') {
|
|
106
164
|
const targetDir = path.join(home, '.codex', 'agents');
|
|
107
165
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
166
|
+
removeStaleManagedAgents(
|
|
167
|
+
targetDir,
|
|
168
|
+
commandFiles.map((file) => file.replace(/\.md$/, '.toml')),
|
|
169
|
+
'.toml',
|
|
170
|
+
);
|
|
108
171
|
for (const file of commandFiles) {
|
|
109
|
-
const markdown = fs.readFileSync(path.join(commandsDir, file), 'utf8');
|
|
172
|
+
const markdown = markGlobalAgent(fs.readFileSync(path.join(commandsDir, file), 'utf8'), file);
|
|
110
173
|
const id = file.replace(/\.md$/, '');
|
|
111
174
|
const metadata = parseAgentMarkdown(markdown, id);
|
|
112
175
|
const tomlName = `${id}.toml`;
|
|
@@ -116,7 +179,7 @@ function deliverGlobalProviderAdapters({ llmChoice, home, commandsDir }) {
|
|
|
116
179
|
written.codex.push(tomlName);
|
|
117
180
|
}
|
|
118
181
|
const skillsRoot = path.join(home, '.agents', 'skills');
|
|
119
|
-
for (const skillId of ['snps', 'sinapse', 'snps-orqx', 'sinapse-agent']) {
|
|
182
|
+
for (const skillId of ['snps', 'sinapse', 'snps-orqx', 'sinapse-orqx', 'sinapse-agent']) {
|
|
120
183
|
const skillDir = path.join(skillsRoot, skillId);
|
|
121
184
|
const skillPath = path.join(skillDir, 'SKILL.md');
|
|
122
185
|
if (writeGlobalSkillIfManaged(skillPath, renderGlobalSkill(skillId))) {
|
|
@@ -128,9 +191,9 @@ function deliverGlobalProviderAdapters({ llmChoice, home, commandsDir }) {
|
|
|
128
191
|
}
|
|
129
192
|
|
|
130
193
|
function getGlobalCommandStagingDir({ llmChoice, sinapseHome, claudeCommandsDir }) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
194
|
+
void llmChoice;
|
|
195
|
+
void claudeCommandsDir;
|
|
196
|
+
return path.join(sinapseHome, '.generated', 'agents');
|
|
134
197
|
}
|
|
135
198
|
|
|
136
|
-
module.exports = { parseAgentMarkdown, renderCodexToml, renderGlobalSkill, writeGlobalSkillIfManaged, deliverGlobalProviderAdapters, getGlobalCommandStagingDir };
|
|
199
|
+
module.exports = { parseAgentMarkdown, renderCodexToml, markGlobalAgent, renderGlobalSkill, writeGlobalSkillIfManaged, removeStaleManagedAgents, deliverGlobalProviderAdapters, getGlobalCommandStagingDir };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function assertProviderAdapterParity(llmChoice, adapters, canonicalCount) {
|
|
4
|
+
if (!['claude-code', 'codex', 'both'].includes(llmChoice)) {
|
|
5
|
+
throw new Error(`Invalid LLM choice for provider parity: ${llmChoice}`);
|
|
6
|
+
}
|
|
7
|
+
const providerCounts = [];
|
|
8
|
+
if (llmChoice === 'claude-code' || llmChoice === 'both') {
|
|
9
|
+
providerCounts.push(['Claude Code', adapters.claude.length]);
|
|
10
|
+
}
|
|
11
|
+
if (llmChoice === 'codex' || llmChoice === 'both') {
|
|
12
|
+
providerCounts.push(['Codex', adapters.codex.length]);
|
|
13
|
+
}
|
|
14
|
+
const divergent = providerCounts.filter(([, count]) => count !== canonicalCount);
|
|
15
|
+
if (divergent.length) {
|
|
16
|
+
const details = divergent.map(([provider, count]) => `${provider}: ${count}/${canonicalCount}`).join(', ');
|
|
17
|
+
throw new Error(`Provider adapter parity failed (${details})`);
|
|
18
|
+
}
|
|
19
|
+
return canonicalCount;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { assertProviderAdapterParity };
|
package/bin/postinstall.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*
|
|
18
18
|
* Execution order (see Story A.1 AC 4):
|
|
19
19
|
* 1. env guard (SINAPSE_SKIP_POSTINSTALL / CI)
|
|
20
|
-
* 2. npm run sync:
|
|
20
|
+
* 2. npm run sync:providers
|
|
21
21
|
* 3. mkdir -p .sinapse/handoffs
|
|
22
22
|
* 4. mkdir -p .sinapse/scratchpad
|
|
23
23
|
* 5. sinapse doctor --quiet (exit code 2 = abort, 0/1 = OK)
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
*
|
|
31
31
|
* Exit codes:
|
|
32
32
|
* 0 success OR non-critical warning (framework operational)
|
|
33
|
-
* 2 critical failure (sync
|
|
33
|
+
* 2 critical failure (provider sync failed, or doctor exit >= 2)
|
|
34
34
|
*
|
|
35
35
|
* Note: exit 1 is NOT used. npm treats any non-zero postinstall exit as
|
|
36
36
|
* `command failed`, which scares users when the install is actually operational.
|
|
@@ -273,7 +273,7 @@ function run(command, args, opts = {}) {
|
|
|
273
273
|
* Detect if we're running inside a global npm install.
|
|
274
274
|
* Global installs land in /usr/local/lib/node_modules (Mac/Linux) or
|
|
275
275
|
* %APPDATA%\npm\node_modules (Windows). Agent sync to those paths is pointless
|
|
276
|
-
* — sync:
|
|
276
|
+
* — sync:providers must run inside a real project (per-project install).
|
|
277
277
|
*/
|
|
278
278
|
function isGlobalInstall() {
|
|
279
279
|
if (process.env.npm_config_global === 'true') return true;
|
|
@@ -287,8 +287,8 @@ function isGlobalInstall() {
|
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
/**
|
|
290
|
-
* Step 1:
|
|
291
|
-
*
|
|
290
|
+
* Step 1: synchronize native Claude and Codex provider adapters.
|
|
291
|
+
* Writes Claude agents to .claude/agents and Codex skills to .agents/skills.
|
|
292
292
|
*
|
|
293
293
|
* Skipped on global installs (no project to sync to). EACCES is treated as a
|
|
294
294
|
* non-critical warning — the user can run `sinapse install` inside their project
|
|
@@ -296,17 +296,17 @@ function isGlobalInstall() {
|
|
|
296
296
|
*/
|
|
297
297
|
function stepSyncIde() {
|
|
298
298
|
if (isGlobalInstall()) {
|
|
299
|
-
verboseLog(`${c.dim}sync:
|
|
299
|
+
verboseLog(`${c.dim}sync:providers pulado (install global — rode 'sinapse install' no seu projeto)${c.reset}`);
|
|
300
300
|
return { ok: true, critical: false, skipped: true };
|
|
301
301
|
}
|
|
302
|
-
verboseLog(`${c.cyan}›${c.reset} Sincronizando
|
|
303
|
-
const result = run('npm', ['run', 'sync:
|
|
302
|
+
verboseLog(`${c.cyan}›${c.reset} Sincronizando adapters para Claude Code e Codex...`);
|
|
303
|
+
const result = run('npm', ['run', 'sync:providers', '--silent']);
|
|
304
304
|
if (result.error) {
|
|
305
305
|
if (result.error.code === 'EACCES' || result.error.code === 'EPERM') {
|
|
306
306
|
warn("Sem permissão pra sincronizar agents. Rode 'sinapse install' dentro do seu projeto.");
|
|
307
307
|
return { ok: false, critical: false };
|
|
308
308
|
}
|
|
309
|
-
warn(`sync:
|
|
309
|
+
warn(`sync:providers falhou ao iniciar: ${result.error.message} — rode 'sinapse install' no projeto pra completar.`);
|
|
310
310
|
return { ok: false, critical: false };
|
|
311
311
|
}
|
|
312
312
|
if (result.status !== 0) {
|
|
@@ -315,7 +315,7 @@ function stepSyncIde() {
|
|
|
315
315
|
warn("Sem permissão pra sincronizar agents (npm global sem write). Rode 'sinapse install' no seu projeto.");
|
|
316
316
|
return { ok: false, critical: false };
|
|
317
317
|
}
|
|
318
|
-
warn(`sync:
|
|
318
|
+
warn(`sync:providers saiu com código ${result.status} — rode 'sinapse install' no projeto pra completar.`);
|
|
319
319
|
return { ok: false, critical: false };
|
|
320
320
|
}
|
|
321
321
|
verboseLog(`${c.green}✓${c.reset} Agents sincronizados`);
|
|
@@ -380,7 +380,7 @@ function stepGenerateSynapse() {
|
|
|
380
380
|
return { ok: true, critical: false, skipped: true };
|
|
381
381
|
}
|
|
382
382
|
// Run in-process (not a subprocess) — faster, and keeps the shared run()
|
|
383
|
-
// sequence (sync:
|
|
383
|
+
// sequence (sync:providers → doctor) intact for callers/tests. The wrapper's
|
|
384
384
|
// generate() is tolerant and never throws; preserve our own exit code since
|
|
385
385
|
// the underlying generator sets process.exitCode=1 on a miss.
|
|
386
386
|
const savedExit = process.exitCode;
|