sinapse-ai 7.7.3 → 7.7.5
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/.codex/catalog.json +157 -0
- package/.codex/command-registry.json +441 -0
- package/.codex/delegation-matrix.json +512 -0
- package/.codex/handoff-packet.schema.json +148 -0
- package/.codex/scripts/generate-codex-greeting.js +101 -0
- package/.codex/scripts/resolve-codex-command.js +147 -0
- package/.codex/scripts/resolve-codex-delegation.js +205 -0
- package/.codex/skills/sinapse-analyst/SKILL.md +5 -4
- package/.codex/skills/sinapse-architect/SKILL.md +5 -4
- package/.codex/skills/sinapse-data-engineer/SKILL.md +5 -4
- package/.codex/skills/sinapse-dev/SKILL.md +5 -4
- package/.codex/skills/sinapse-devops/SKILL.md +5 -4
- package/.codex/skills/sinapse-orqx/SKILL.md +10 -15
- package/.codex/skills/sinapse-pm/SKILL.md +5 -4
- package/.codex/skills/sinapse-po/SKILL.md +4 -3
- package/.codex/skills/sinapse-qa/SKILL.md +12 -11
- package/.codex/skills/sinapse-sm/SKILL.md +5 -4
- package/.codex/skills/sinapse-squad-creator/SKILL.md +5 -4
- package/.codex/skills/sinapse-ux-design-expert/SKILL.md +5 -4
- package/.codex/tasks/convene-sinapse-council.md +28 -0
- package/.codex/tasks/create-sinapse-strategic-brief.md +29 -0
- package/.codex/tasks/onboard-sinapse-codex.md +34 -0
- package/.codex/tasks/plan-sinapse-initiative.md +33 -0
- package/.codex/tasks/resolve-sinapse-conflict.md +28 -0
- package/.codex/tasks/route-sinapse-request.md +35 -0
- package/.codex/tasks/status-sinapse-capabilities.md +28 -0
- package/.sinapse-ai/core-config.yaml +1 -1
- package/.sinapse-ai/data/entity-registry.yaml +874 -749
- package/.sinapse-ai/data/registry-update-log.jsonl +13 -0
- package/.sinapse-ai/infrastructure/scripts/codex-parity/catalog.js +123 -0
- package/.sinapse-ai/infrastructure/scripts/codex-skills-sync/index.js +60 -11
- package/.sinapse-ai/infrastructure/scripts/codex-skills-sync/validate.js +44 -16
- package/.sinapse-ai/infrastructure/scripts/sync-codex-local-first.js +156 -0
- package/.sinapse-ai/infrastructure/scripts/validate-codex-command-registry.js +264 -0
- package/.sinapse-ai/infrastructure/scripts/validate-codex-delegation.js +292 -0
- package/.sinapse-ai/infrastructure/scripts/validate-codex-integration.js +15 -6
- package/.sinapse-ai/infrastructure/scripts/validate-codex-sync.js +159 -0
- package/.sinapse-ai/infrastructure/scripts/validate-parity.js +3 -1
- package/.sinapse-ai/infrastructure/scripts/validate-paths.js +8 -10
- package/.sinapse-ai/infrastructure/templates/safe-collab/README.md +8 -0
- package/.sinapse-ai/install-manifest.yaml +39 -19
- package/.sinapse-ai/project-config.yaml +1 -1
- package/bin/utils/collab-start.js +267 -0
- package/bin/utils/git-branch-guard.js +76 -0
- package/bin/utils/pre-push-safety.js +110 -0
- package/bin/utils/staged-secret-scan.js +108 -0
- package/docs/codex-parity-program.md +670 -0
- package/docs/codex-total-parity-orchestration-plan.md +301 -0
- package/docs/codex-workflow-task-parity.md +87 -0
- package/docs/collaboration-autonomy-plan.md +243 -0
- package/docs/guides/framework-contributor-mode.md +310 -0
- package/docs/guides/parallel-collaboration-source-of-truth.md +481 -0
- package/package.json +14 -3
- package/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js +7 -2
- package/packages/installer/tests/unit/entity-registry-bootstrap.test.js +2 -2
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const REQUIRED_COMMAND_COVERAGE = Object.freeze({
|
|
8
|
+
'sinapse-orqx': ['onboard', 'route', 'plan', 'status', 'brief', 'resolve', 'council'],
|
|
9
|
+
'sinapse-pm': [
|
|
10
|
+
'create-prd',
|
|
11
|
+
'create-brownfield-prd',
|
|
12
|
+
'create-epic',
|
|
13
|
+
'create-story',
|
|
14
|
+
'research',
|
|
15
|
+
'execute-epic',
|
|
16
|
+
'gather-requirements',
|
|
17
|
+
'write-spec',
|
|
18
|
+
'shard-prd',
|
|
19
|
+
],
|
|
20
|
+
'sinapse-po': [
|
|
21
|
+
'validate-story',
|
|
22
|
+
'validate-story-draft',
|
|
23
|
+
'backlog-review',
|
|
24
|
+
'backlog-prioritize',
|
|
25
|
+
'backlog-schedule',
|
|
26
|
+
'close-story',
|
|
27
|
+
'sync-story',
|
|
28
|
+
'pull-story',
|
|
29
|
+
'stories-index',
|
|
30
|
+
],
|
|
31
|
+
'sinapse-sm': ['draft', 'story-checklist'],
|
|
32
|
+
'sinapse-dev': [
|
|
33
|
+
'develop',
|
|
34
|
+
'run-tests',
|
|
35
|
+
'apply-qa-fixes',
|
|
36
|
+
'execute-subtask',
|
|
37
|
+
'verify-subtask',
|
|
38
|
+
'build-autonomous',
|
|
39
|
+
'build-resume',
|
|
40
|
+
'build-status',
|
|
41
|
+
'build',
|
|
42
|
+
],
|
|
43
|
+
'sinapse-qa': [
|
|
44
|
+
'review',
|
|
45
|
+
'review-story',
|
|
46
|
+
'code-review',
|
|
47
|
+
'gate',
|
|
48
|
+
'review-build',
|
|
49
|
+
'create-fix-request',
|
|
50
|
+
'test-design',
|
|
51
|
+
'generate-tests',
|
|
52
|
+
'run-tests',
|
|
53
|
+
'nfr-assess',
|
|
54
|
+
'validate-libraries',
|
|
55
|
+
'security-check',
|
|
56
|
+
'validate-migrations',
|
|
57
|
+
'evidence-check',
|
|
58
|
+
'false-positive-check',
|
|
59
|
+
'console-check',
|
|
60
|
+
],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
function parseArgs(argv = process.argv.slice(2)) {
|
|
64
|
+
const args = new Set(argv);
|
|
65
|
+
return {
|
|
66
|
+
quiet: args.has('--quiet') || args.has('-q'),
|
|
67
|
+
json: args.has('--json'),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function normalizeAgentAlias(value) {
|
|
72
|
+
return String(value || '').trim().replace(/^@/, '').toLowerCase();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function normalizeCommandAlias(value) {
|
|
76
|
+
return String(value || '').trim().replace(/^\*/, '').toLowerCase();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function collectAgentAliases(agentId, agentSpec) {
|
|
80
|
+
return [agentId, ...(agentSpec.aliases || [])]
|
|
81
|
+
.map((alias) => normalizeAgentAlias(alias))
|
|
82
|
+
.filter(Boolean);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function collectCommandAliases(commandId, commandSpec) {
|
|
86
|
+
return [commandId, ...(commandSpec.aliases || [])]
|
|
87
|
+
.map((alias) => normalizeCommandAlias(alias))
|
|
88
|
+
.filter(Boolean);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function loadRegistry(projectRoot) {
|
|
92
|
+
const registryPath = path.join(projectRoot, '.codex', 'command-registry.json');
|
|
93
|
+
if (!fs.existsSync(registryPath)) {
|
|
94
|
+
return {
|
|
95
|
+
registryPath,
|
|
96
|
+
registry: null,
|
|
97
|
+
error: `Missing Codex command registry: ${path.relative(projectRoot, registryPath)}`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
return {
|
|
103
|
+
registryPath,
|
|
104
|
+
registry: JSON.parse(fs.readFileSync(registryPath, 'utf8')),
|
|
105
|
+
error: null,
|
|
106
|
+
};
|
|
107
|
+
} catch (error) {
|
|
108
|
+
return {
|
|
109
|
+
registryPath,
|
|
110
|
+
registry: null,
|
|
111
|
+
error: `Unable to parse Codex command registry: ${error.message}`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function validateRequiredCoverage(registry, requiredCoverage, errors) {
|
|
117
|
+
for (const [agentId, requiredCommands] of Object.entries(requiredCoverage || {})) {
|
|
118
|
+
const agentSpec = (registry.agents || {})[agentId];
|
|
119
|
+
if (!agentSpec) {
|
|
120
|
+
errors.push(`missing required agent coverage: ${agentId}`);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const availableCommands = new Set();
|
|
125
|
+
for (const [commandId, commandSpec] of Object.entries(agentSpec.commands || {})) {
|
|
126
|
+
for (const alias of collectCommandAliases(commandId, commandSpec)) {
|
|
127
|
+
availableCommands.add(alias);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
for (const requiredCommand of requiredCommands) {
|
|
132
|
+
if (!availableCommands.has(normalizeCommandAlias(requiredCommand))) {
|
|
133
|
+
errors.push(`${agentId}: missing required command coverage for ${requiredCommand}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function validateCodexCommandRegistry(options = {}) {
|
|
140
|
+
const projectRoot = options.projectRoot || process.cwd();
|
|
141
|
+
const { registryPath, registry, error } = loadRegistry(projectRoot);
|
|
142
|
+
const errors = [];
|
|
143
|
+
const requiredCoverage =
|
|
144
|
+
options.requiredCoverage === false
|
|
145
|
+
? null
|
|
146
|
+
: options.requiredCoverage || REQUIRED_COMMAND_COVERAGE;
|
|
147
|
+
|
|
148
|
+
if (error) {
|
|
149
|
+
errors.push(error);
|
|
150
|
+
return {
|
|
151
|
+
ok: false,
|
|
152
|
+
errors,
|
|
153
|
+
warnings: [],
|
|
154
|
+
metrics: { agents: 0, commands: 0 },
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
let commandCount = 0;
|
|
159
|
+
const seenAgentAliases = new Map();
|
|
160
|
+
for (const [agentId, agentSpec] of Object.entries(registry.agents || {})) {
|
|
161
|
+
const skillPath = path.join(projectRoot, '.codex', 'skills', agentSpec.skillId || agentId, 'SKILL.md');
|
|
162
|
+
if (!fs.existsSync(skillPath)) {
|
|
163
|
+
errors.push(`${agentId}: missing skill file ${path.relative(projectRoot, skillPath)}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const sourceOfTruth = path.join(projectRoot, agentSpec.sourceOfTruth || '');
|
|
167
|
+
if (!fs.existsSync(sourceOfTruth)) {
|
|
168
|
+
errors.push(`${agentId}: missing source of truth ${path.relative(projectRoot, sourceOfTruth)}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
for (const alias of collectAgentAliases(agentId, agentSpec)) {
|
|
172
|
+
const owner = seenAgentAliases.get(alias);
|
|
173
|
+
if (owner && owner !== agentId) {
|
|
174
|
+
errors.push(`duplicate agent alias "${alias}" claimed by ${owner} and ${agentId}`);
|
|
175
|
+
} else {
|
|
176
|
+
seenAgentAliases.set(alias, agentId);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const seenCommandAliases = new Map();
|
|
181
|
+
for (const [commandId, commandSpec] of Object.entries(agentSpec.commands || {})) {
|
|
182
|
+
commandCount += 1;
|
|
183
|
+
|
|
184
|
+
const targetPath = path.join(projectRoot, commandSpec.target || '');
|
|
185
|
+
if (!fs.existsSync(targetPath)) {
|
|
186
|
+
errors.push(`${agentId}.${commandId}: missing target ${path.relative(projectRoot, targetPath)}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
for (const resource of commandSpec.resources || []) {
|
|
190
|
+
const resourcePath = path.join(projectRoot, resource);
|
|
191
|
+
if (!fs.existsSync(resourcePath)) {
|
|
192
|
+
errors.push(`${agentId}.${commandId}: missing resource ${path.relative(projectRoot, resourcePath)}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
for (const alias of collectCommandAliases(commandId, commandSpec)) {
|
|
197
|
+
const owner = seenCommandAliases.get(alias);
|
|
198
|
+
if (owner && owner !== commandId) {
|
|
199
|
+
errors.push(`${agentId}: duplicate command alias "${alias}" claimed by ${owner} and ${commandId}`);
|
|
200
|
+
} else {
|
|
201
|
+
seenCommandAliases.set(alias, commandId);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
validateRequiredCoverage(registry, requiredCoverage, errors);
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
ok: errors.length === 0,
|
|
211
|
+
errors,
|
|
212
|
+
warnings: [],
|
|
213
|
+
metrics: {
|
|
214
|
+
agents: Object.keys(registry.agents || {}).length,
|
|
215
|
+
commands: commandCount,
|
|
216
|
+
registryPath: path.relative(projectRoot, registryPath),
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function formatHumanReport(result) {
|
|
222
|
+
if (result.ok) {
|
|
223
|
+
return `OK Codex command registry validation passed (agents: ${result.metrics.agents}, commands: ${result.metrics.commands})`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return [
|
|
227
|
+
`X Codex command registry validation failed (${result.errors.length} issue(s))`,
|
|
228
|
+
...result.errors.map((error) => `- ${error}`),
|
|
229
|
+
].join('\n');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function main() {
|
|
233
|
+
const args = parseArgs();
|
|
234
|
+
const result = validateCodexCommandRegistry(args);
|
|
235
|
+
|
|
236
|
+
if (!args.quiet) {
|
|
237
|
+
if (args.json) {
|
|
238
|
+
console.log(JSON.stringify(result, null, 2));
|
|
239
|
+
} else {
|
|
240
|
+
console.log(formatHumanReport(result));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (!result.ok) {
|
|
245
|
+
process.exitCode = 1;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (require.main === module) {
|
|
250
|
+
main();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
module.exports = {
|
|
254
|
+
parseArgs,
|
|
255
|
+
loadRegistry,
|
|
256
|
+
normalizeAgentAlias,
|
|
257
|
+
normalizeCommandAlias,
|
|
258
|
+
collectAgentAliases,
|
|
259
|
+
collectCommandAliases,
|
|
260
|
+
validateRequiredCoverage,
|
|
261
|
+
validateCodexCommandRegistry,
|
|
262
|
+
formatHumanReport,
|
|
263
|
+
REQUIRED_COMMAND_COVERAGE,
|
|
264
|
+
};
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const Ajv2020 = require('ajv/dist/2020');
|
|
7
|
+
const {
|
|
8
|
+
buildHandoffPacket,
|
|
9
|
+
loadDelegationMatrix,
|
|
10
|
+
} = require(path.join(__dirname, '..', '..', '..', '.codex', 'scripts', 'resolve-codex-delegation'));
|
|
11
|
+
const {
|
|
12
|
+
loadRegistry: loadCommandRegistryFile,
|
|
13
|
+
} = require('./validate-codex-command-registry');
|
|
14
|
+
|
|
15
|
+
const REQUIRED_ORCHESTRATORS = Object.freeze([
|
|
16
|
+
'sinapse-orqx',
|
|
17
|
+
'brand-orqx',
|
|
18
|
+
'commercial-orqx',
|
|
19
|
+
'content-orqx',
|
|
20
|
+
'copy-orqx',
|
|
21
|
+
'animations-orqx',
|
|
22
|
+
'design-orqx',
|
|
23
|
+
'finance-orqx',
|
|
24
|
+
'growth-orqx',
|
|
25
|
+
'paidmedia-orqx',
|
|
26
|
+
'product-orqx',
|
|
27
|
+
'research-orqx',
|
|
28
|
+
'claude-orqx',
|
|
29
|
+
'council-orqx',
|
|
30
|
+
'storytelling-orqx',
|
|
31
|
+
'cyber-orqx',
|
|
32
|
+
'cloning-orqx',
|
|
33
|
+
'courses-orqx',
|
|
34
|
+
'swarm-orqx',
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
function parseArgs(argv = process.argv.slice(2)) {
|
|
38
|
+
const args = new Set(argv);
|
|
39
|
+
return {
|
|
40
|
+
quiet: args.has('--quiet') || args.has('-q'),
|
|
41
|
+
json: args.has('--json'),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function validateCodexDelegation(options = {}) {
|
|
46
|
+
const projectRoot = options.projectRoot || process.cwd();
|
|
47
|
+
const errors = [];
|
|
48
|
+
const warnings = [];
|
|
49
|
+
const requiredOrchestrators =
|
|
50
|
+
options.requiredOrchestrators === false
|
|
51
|
+
? null
|
|
52
|
+
: options.requiredOrchestrators || REQUIRED_ORCHESTRATORS;
|
|
53
|
+
|
|
54
|
+
let matrix;
|
|
55
|
+
try {
|
|
56
|
+
matrix = loadDelegationMatrix(projectRoot);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
errors.push(`Unable to load Codex delegation matrix: ${error.message}`);
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
errors,
|
|
62
|
+
warnings,
|
|
63
|
+
metrics: { orchestrators: 0, routes: 0 },
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const matrixPath = path.join(projectRoot, '.codex', 'delegation-matrix.json');
|
|
68
|
+
const schemaPath = path.join(projectRoot, matrix.handoffSchema || '.codex/handoff-packet.schema.json');
|
|
69
|
+
if (!fs.existsSync(schemaPath)) {
|
|
70
|
+
errors.push(`Missing handoff schema ${path.relative(projectRoot, schemaPath)}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let schema = null;
|
|
74
|
+
if (fs.existsSync(schemaPath)) {
|
|
75
|
+
try {
|
|
76
|
+
schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8'));
|
|
77
|
+
} catch (error) {
|
|
78
|
+
errors.push(`Unable to parse handoff schema: ${error.message}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const { registry: commandRegistry, error: commandRegistryError } = loadCommandRegistryFile(projectRoot);
|
|
83
|
+
if (commandRegistryError) {
|
|
84
|
+
errors.push(commandRegistryError);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const orchestrators = matrix.orchestrators || {};
|
|
88
|
+
const frameworkAgents = matrix.frameworkAgents || {};
|
|
89
|
+
const routes = matrix.routes || {};
|
|
90
|
+
|
|
91
|
+
if (requiredOrchestrators) {
|
|
92
|
+
for (const orchestratorId of requiredOrchestrators) {
|
|
93
|
+
if (!orchestrators[orchestratorId]) {
|
|
94
|
+
errors.push(`missing required orchestrator coverage: ${orchestratorId}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
for (const [orchestratorId, orchestratorSpec] of Object.entries(orchestrators)) {
|
|
100
|
+
const sourceOfTruth = path.join(projectRoot, orchestratorSpec.sourceOfTruth || '');
|
|
101
|
+
if (!fs.existsSync(sourceOfTruth)) {
|
|
102
|
+
errors.push(`${orchestratorId}: missing source of truth ${path.relative(projectRoot, sourceOfTruth)}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (orchestratorSpec.squadDir) {
|
|
106
|
+
const squadDir = path.join(projectRoot, orchestratorSpec.squadDir);
|
|
107
|
+
if (!fs.existsSync(squadDir)) {
|
|
108
|
+
errors.push(`${orchestratorId}: missing squad dir ${path.relative(projectRoot, squadDir)}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (orchestratorSpec.tasksDir) {
|
|
113
|
+
const tasksDir = path.join(projectRoot, orchestratorSpec.tasksDir);
|
|
114
|
+
if (!fs.existsSync(tasksDir)) {
|
|
115
|
+
errors.push(`${orchestratorId}: missing tasks dir ${path.relative(projectRoot, tasksDir)}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for (const routeId of orchestratorSpec.approvedRoutes || []) {
|
|
120
|
+
if (!routes[routeId]) {
|
|
121
|
+
errors.push(`${orchestratorId}: missing approved route ${routeId}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const routeAliases = new Map();
|
|
127
|
+
const ajv = schema ? new Ajv2020({ allErrors: true, strict: false }) : null;
|
|
128
|
+
const validatePacket = schema ? ajv.compile(schema) : null;
|
|
129
|
+
let routeCount = 0;
|
|
130
|
+
let validatorBackedCount = 0;
|
|
131
|
+
let shimCount = 0;
|
|
132
|
+
let exploratoryCount = 0;
|
|
133
|
+
|
|
134
|
+
for (const [routeId, routeSpec] of Object.entries(routes)) {
|
|
135
|
+
routeCount += 1;
|
|
136
|
+
if (routeSpec.classification === 'validator-backed') validatorBackedCount += 1;
|
|
137
|
+
if (routeSpec.classification === 'codex-only-shim') shimCount += 1;
|
|
138
|
+
if (routeSpec.classification === 'exploratory') exploratoryCount += 1;
|
|
139
|
+
|
|
140
|
+
if (!orchestrators[routeSpec.owner]) {
|
|
141
|
+
errors.push(`${routeId}: unknown route owner ${routeSpec.owner}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!(matrix.routingClasses || []).includes(routeSpec.classification)) {
|
|
145
|
+
errors.push(`${routeId}: invalid classification ${routeSpec.classification}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!(matrix.requestTypes || []).includes(routeSpec.requestType)) {
|
|
149
|
+
errors.push(`${routeId}: invalid request type ${routeSpec.requestType}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
for (const alias of [routeId, ...(routeSpec.aliases || [])]) {
|
|
153
|
+
const normalized = String(alias || '').trim().toLowerCase();
|
|
154
|
+
const owner = routeAliases.get(normalized);
|
|
155
|
+
if (owner && owner !== routeId) {
|
|
156
|
+
errors.push(`duplicate delegation route alias "${normalized}" claimed by ${owner} and ${routeId}`);
|
|
157
|
+
} else {
|
|
158
|
+
routeAliases.set(normalized, routeId);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
for (const resource of routeSpec.resources || []) {
|
|
163
|
+
const resourcePath = path.join(projectRoot, resource);
|
|
164
|
+
if (!fs.existsSync(resourcePath)) {
|
|
165
|
+
errors.push(`${routeId}: missing resource ${path.relative(projectRoot, resourcePath)}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!Array.isArray(routeSpec.delegationChain) || routeSpec.delegationChain.length === 0) {
|
|
170
|
+
errors.push(`${routeId}: missing delegation chain`);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (routeSpec.delegationChain[0].from !== routeSpec.owner) {
|
|
175
|
+
errors.push(`${routeId}: first delegation step must start from the route owner`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
for (const [index, step] of routeSpec.delegationChain.entries()) {
|
|
179
|
+
const stepLabel = `${routeId}.step${index + 1}`;
|
|
180
|
+
|
|
181
|
+
if (step.fromType === 'orqx' && !orchestrators[step.from]) {
|
|
182
|
+
errors.push(`${stepLabel}: unknown from orchestrator ${step.from}`);
|
|
183
|
+
}
|
|
184
|
+
if (step.fromType === 'framework-agent' && !frameworkAgents[step.from]) {
|
|
185
|
+
errors.push(`${stepLabel}: unknown from framework agent ${step.from}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (step.toType === 'orqx') {
|
|
189
|
+
if (!orchestrators[step.to]) {
|
|
190
|
+
errors.push(`${stepLabel}: unknown target orchestrator ${step.to}`);
|
|
191
|
+
}
|
|
192
|
+
} else if (step.toType === 'framework-agent') {
|
|
193
|
+
if (!frameworkAgents[step.to]) {
|
|
194
|
+
errors.push(`${stepLabel}: unknown target framework agent ${step.to}`);
|
|
195
|
+
}
|
|
196
|
+
if (step.resolver !== 'command-registry') {
|
|
197
|
+
errors.push(`${stepLabel}: framework-agent steps must use resolver "command-registry"`);
|
|
198
|
+
}
|
|
199
|
+
const registryAgent = commandRegistry?.agents?.[frameworkAgents[step.to]?.registryAgentId || step.to];
|
|
200
|
+
if (!registryAgent) {
|
|
201
|
+
errors.push(`${stepLabel}: missing command registry entry for ${step.to}`);
|
|
202
|
+
} else if (!registryAgent.commands?.[step.command]) {
|
|
203
|
+
errors.push(`${stepLabel}: missing command registry command ${step.to}.${step.command}`);
|
|
204
|
+
}
|
|
205
|
+
} else if (step.toType === 'specialist') {
|
|
206
|
+
const specialistPath = step.path
|
|
207
|
+
? path.join(projectRoot, step.path)
|
|
208
|
+
: path.join(projectRoot, '.codex', 'agents', `${step.to}.md`);
|
|
209
|
+
if (!fs.existsSync(specialistPath)) {
|
|
210
|
+
errors.push(`${stepLabel}: missing specialist path ${path.relative(projectRoot, specialistPath)}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (step.path) {
|
|
215
|
+
const stepPath = path.join(projectRoot, step.path);
|
|
216
|
+
if (!fs.existsSync(stepPath)) {
|
|
217
|
+
errors.push(`${stepLabel}: missing path ${path.relative(projectRoot, stepPath)}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (step.task) {
|
|
222
|
+
const taskPath = path.join(projectRoot, step.task);
|
|
223
|
+
if (!fs.existsSync(taskPath)) {
|
|
224
|
+
errors.push(`${stepLabel}: missing task ${path.relative(projectRoot, taskPath)}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (validatePacket) {
|
|
230
|
+
const packet = buildHandoffPacket(routeId, projectRoot, matrix);
|
|
231
|
+
const valid = validatePacket(packet);
|
|
232
|
+
if (!valid) {
|
|
233
|
+
errors.push(
|
|
234
|
+
`${routeId}: handoff packet failed schema validation ${ajv.errorsText(validatePacket.errors, { separator: '; ' })}`,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
ok: errors.length === 0,
|
|
242
|
+
errors,
|
|
243
|
+
warnings,
|
|
244
|
+
metrics: {
|
|
245
|
+
matrixPath: path.relative(projectRoot, matrixPath),
|
|
246
|
+
orchestrators: Object.keys(orchestrators).length,
|
|
247
|
+
routes: routeCount,
|
|
248
|
+
validatorBackedRoutes: validatorBackedCount,
|
|
249
|
+
codexOnlyShimRoutes: shimCount,
|
|
250
|
+
exploratoryRoutes: exploratoryCount,
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function formatHumanReport(result) {
|
|
256
|
+
if (result.ok) {
|
|
257
|
+
return `OK Codex delegation validation passed (orchestrators: ${result.metrics.orchestrators}, routes: ${result.metrics.routes})`;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return [
|
|
261
|
+
`X Codex delegation validation failed (${result.errors.length} issue(s))`,
|
|
262
|
+
...result.errors.map((error) => `- ${error}`),
|
|
263
|
+
].join('\n');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function main() {
|
|
267
|
+
const args = parseArgs();
|
|
268
|
+
const result = validateCodexDelegation(args);
|
|
269
|
+
|
|
270
|
+
if (!args.quiet) {
|
|
271
|
+
if (args.json) {
|
|
272
|
+
console.log(JSON.stringify(result, null, 2));
|
|
273
|
+
} else {
|
|
274
|
+
console.log(formatHumanReport(result));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (!result.ok) {
|
|
279
|
+
process.exitCode = 1;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (require.main === module) {
|
|
284
|
+
main();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
module.exports = {
|
|
288
|
+
parseArgs,
|
|
289
|
+
validateCodexDelegation,
|
|
290
|
+
formatHumanReport,
|
|
291
|
+
REQUIRED_ORCHESTRATORS,
|
|
292
|
+
};
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
|
+
const {
|
|
7
|
+
loadCodexCatalogConfig,
|
|
8
|
+
getConfiguredSkillIds,
|
|
9
|
+
} = require('./codex-parity/catalog');
|
|
6
10
|
|
|
7
11
|
function getDefaultOptions() {
|
|
8
12
|
const projectRoot = process.cwd();
|
|
@@ -41,14 +45,15 @@ function countSkillFiles(skillsDir) {
|
|
|
41
45
|
|
|
42
46
|
function validateCodexIntegration(options = {}) {
|
|
43
47
|
const projectRoot = options.projectRoot || process.cwd();
|
|
48
|
+
const config = loadCodexCatalogConfig(projectRoot);
|
|
44
49
|
const resolved = {
|
|
45
50
|
...getDefaultOptions(),
|
|
46
51
|
...options,
|
|
47
52
|
projectRoot,
|
|
48
53
|
instructionsFile: options.instructionsFile || path.join(projectRoot, 'AGENTS.md'),
|
|
49
|
-
agentsDir: options.agentsDir || path.join(projectRoot, '.codex
|
|
50
|
-
skillsDir: options.skillsDir || path.join(projectRoot, '.codex
|
|
51
|
-
sourceAgentsDir: options.sourceAgentsDir || path.join(projectRoot, '.sinapse-ai
|
|
54
|
+
agentsDir: options.agentsDir || path.join(projectRoot, config.codexAgentsDir || '.codex/agents'),
|
|
55
|
+
skillsDir: options.skillsDir || path.join(projectRoot, config.skillsDir || '.codex/skills'),
|
|
56
|
+
sourceAgentsDir: options.sourceAgentsDir || path.join(projectRoot, config.canonicalAgentsDir || '.sinapse-ai/development/agents'),
|
|
52
57
|
};
|
|
53
58
|
const errors = [];
|
|
54
59
|
const warnings = [];
|
|
@@ -70,12 +75,15 @@ function validateCodexIntegration(options = {}) {
|
|
|
70
75
|
const sourceCount = countMarkdownFiles(resolved.sourceAgentsDir);
|
|
71
76
|
const codexAgentsCount = countMarkdownFiles(resolved.agentsDir);
|
|
72
77
|
const codexSkillsCount = countSkillFiles(resolved.skillsDir);
|
|
78
|
+
const expectedSkillIds = getConfiguredSkillIds(config);
|
|
73
79
|
|
|
74
|
-
if (sourceCount > 0 && codexAgentsCount !== sourceCount) {
|
|
80
|
+
if (config.catalogMode !== 'expanded' && sourceCount > 0 && codexAgentsCount !== sourceCount) {
|
|
75
81
|
warnings.push(`Codex agent count differs from source (${codexAgentsCount}/${sourceCount})`);
|
|
76
82
|
}
|
|
77
83
|
|
|
78
|
-
if (
|
|
84
|
+
if (expectedSkillIds && codexSkillsCount !== expectedSkillIds.length) {
|
|
85
|
+
warnings.push(`Codex skill count differs from configured catalog (${codexSkillsCount}/${expectedSkillIds.length})`);
|
|
86
|
+
} else if (!expectedSkillIds && sourceCount > 0 && codexSkillsCount !== sourceCount) {
|
|
79
87
|
warnings.push(`Codex skill count differs from source (${codexSkillsCount}/${sourceCount})`);
|
|
80
88
|
}
|
|
81
89
|
|
|
@@ -84,9 +92,10 @@ function validateCodexIntegration(options = {}) {
|
|
|
84
92
|
errors,
|
|
85
93
|
warnings,
|
|
86
94
|
metrics: {
|
|
87
|
-
|
|
95
|
+
canonicalAgents: sourceCount,
|
|
88
96
|
codexAgents: codexAgentsCount,
|
|
89
97
|
codexSkills: codexSkillsCount,
|
|
98
|
+
expectedSkills: expectedSkillIds ? expectedSkillIds.length : sourceCount,
|
|
90
99
|
},
|
|
91
100
|
};
|
|
92
101
|
}
|