sinapse-ai 7.7.2 → 7.7.4
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-git-push-authority.sh +34 -2
- package/.claude/rules/safe-collaboration.md +12 -1
- package/.codex/catalog.json +157 -0
- package/.codex/command-registry.json +441 -0
- package/.codex/scripts/generate-codex-greeting.js +101 -0
- package/.codex/scripts/resolve-codex-command.js +147 -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 +33 -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 +903 -805
- package/.sinapse-ai/data/registry-update-log.jsonl +10 -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-integration.js +15 -6
- package/.sinapse-ai/infrastructure/scripts/validate-codex-sync.js +156 -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 +52 -17
- package/.sinapse-ai/infrastructure/templates/safe-collab/apply.sh +85 -0
- package/.sinapse-ai/infrastructure/templates/safe-collab/safe-collaboration-rule.md +11 -0
- package/.sinapse-ai/install-manifest.yaml +41 -21
- 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/ORQX-PLAN.md +3 -2
- 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 +11 -3
- package/packages/installer/tests/unit/entity-registry-bootstrap.test.js +2 -2
- package/scripts/ensure-manifest.js +9 -0
|
@@ -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
|
+
};
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { spawnSync } = require('child_process');
|
|
7
|
+
const { loadCodexCatalogConfig } = require('./codex-parity/catalog');
|
|
8
|
+
const { validateCodexCommandRegistry } = require('./validate-codex-command-registry');
|
|
9
|
+
const { validateCodexIntegration } = require('./validate-codex-integration');
|
|
10
|
+
const { validateCodexSkills } = require('./codex-skills-sync/validate');
|
|
11
|
+
const { validatePaths } = require('./validate-paths');
|
|
12
|
+
|
|
13
|
+
function parseArgs(argv = process.argv.slice(2)) {
|
|
14
|
+
const args = new Set(argv);
|
|
15
|
+
return {
|
|
16
|
+
quiet: args.has('--quiet') || args.has('-q'),
|
|
17
|
+
json: args.has('--json'),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function runLegacyCodexValidate(projectRoot, options = {}) {
|
|
22
|
+
const args = [
|
|
23
|
+
path.join('.sinapse-ai', 'infrastructure', 'scripts', 'ide-sync', 'index.js'),
|
|
24
|
+
'validate',
|
|
25
|
+
'--ide',
|
|
26
|
+
'codex',
|
|
27
|
+
'--strict',
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
if (options.quiet) args.push('--quiet');
|
|
31
|
+
|
|
32
|
+
const result = spawnSync(process.execPath, args, {
|
|
33
|
+
cwd: projectRoot,
|
|
34
|
+
encoding: 'utf8',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
ok: result.status === 0,
|
|
39
|
+
mode: 'canonical',
|
|
40
|
+
checks: [],
|
|
41
|
+
errors: result.status === 0 ? [] : ['Legacy Codex sync validation failed'],
|
|
42
|
+
warnings: [],
|
|
43
|
+
raw: [result.stdout, result.stderr].filter(Boolean).join('\n').trim(),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function normalizeCheck(id, input) {
|
|
48
|
+
return {
|
|
49
|
+
id,
|
|
50
|
+
ok: Boolean(input?.ok),
|
|
51
|
+
errors: Array.isArray(input?.errors) ? input.errors : [],
|
|
52
|
+
warnings: Array.isArray(input?.warnings) ? input.warnings : [],
|
|
53
|
+
metrics: input?.metrics || {},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function validateCodexSync(options = {}, deps = {}) {
|
|
58
|
+
const projectRoot = options.projectRoot || process.cwd();
|
|
59
|
+
const config = loadCodexCatalogConfig(projectRoot);
|
|
60
|
+
const runLegacyValidate = deps.runLegacyCodexValidate || runLegacyCodexValidate;
|
|
61
|
+
const runCodexCommands = deps.validateCodexCommandRegistry || validateCodexCommandRegistry;
|
|
62
|
+
const runCodexIntegration = deps.validateCodexIntegration || validateCodexIntegration;
|
|
63
|
+
const runCodexSkills = deps.validateCodexSkills || validateCodexSkills;
|
|
64
|
+
const runPaths = deps.validatePaths || validatePaths;
|
|
65
|
+
|
|
66
|
+
if (config.catalogMode !== 'expanded') {
|
|
67
|
+
return runLegacyValidate(projectRoot, options);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const catalogPath = path.join(projectRoot, '.codex', 'catalog.json');
|
|
71
|
+
const checks = [
|
|
72
|
+
normalizeCheck('codex-integration', runCodexIntegration({ projectRoot, quiet: true })),
|
|
73
|
+
normalizeCheck('codex-commands', runCodexCommands({ projectRoot, quiet: true })),
|
|
74
|
+
normalizeCheck('codex-skills', runCodexSkills({ projectRoot, strict: true, quiet: true })),
|
|
75
|
+
normalizeCheck('paths', runPaths({ projectRoot, quiet: true })),
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
if (!fs.existsSync(catalogPath)) {
|
|
79
|
+
checks.unshift({
|
|
80
|
+
id: 'codex-catalog',
|
|
81
|
+
ok: false,
|
|
82
|
+
errors: ['Missing Codex catalog file: .codex/catalog.json'],
|
|
83
|
+
warnings: [],
|
|
84
|
+
metrics: {},
|
|
85
|
+
});
|
|
86
|
+
} else {
|
|
87
|
+
checks.unshift({
|
|
88
|
+
id: 'codex-catalog',
|
|
89
|
+
ok: true,
|
|
90
|
+
errors: [],
|
|
91
|
+
warnings: [],
|
|
92
|
+
metrics: {},
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
ok: checks.every((check) => check.ok),
|
|
98
|
+
mode: 'expanded',
|
|
99
|
+
checks,
|
|
100
|
+
errors: checks.flatMap((check) => check.errors.map((error) => `${check.id}: ${error}`)),
|
|
101
|
+
warnings: checks.flatMap((check) => check.warnings.map((warning) => `${check.id}: ${warning}`)),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function formatHumanReport(result) {
|
|
106
|
+
if (result.mode === 'canonical' && result.raw) {
|
|
107
|
+
return result.raw;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const lines = [
|
|
111
|
+
result.ok
|
|
112
|
+
? 'OK Codex sync validation passed'
|
|
113
|
+
: `X Codex sync validation failed (${result.errors.length} issue(s))`,
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
for (const check of result.checks || []) {
|
|
117
|
+
lines.push(`${check.ok ? 'OK' : 'X'} ${check.id}`);
|
|
118
|
+
if (check.errors.length > 0) {
|
|
119
|
+
lines.push(...check.errors.map((error) => `- ${error}`));
|
|
120
|
+
}
|
|
121
|
+
if (check.warnings.length > 0) {
|
|
122
|
+
lines.push(...check.warnings.map((warning) => `! ${warning}`));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return lines.join('\n');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function main() {
|
|
130
|
+
const args = parseArgs();
|
|
131
|
+
const result = validateCodexSync(args);
|
|
132
|
+
|
|
133
|
+
if (!args.quiet) {
|
|
134
|
+
if (args.json) {
|
|
135
|
+
console.log(JSON.stringify(result, null, 2));
|
|
136
|
+
} else {
|
|
137
|
+
console.log(formatHumanReport(result));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (!result.ok) {
|
|
142
|
+
process.exitCode = 1;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (require.main === module) {
|
|
147
|
+
main();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = {
|
|
151
|
+
parseArgs,
|
|
152
|
+
validateCodexSync,
|
|
153
|
+
formatHumanReport,
|
|
154
|
+
runLegacyCodexValidate,
|
|
155
|
+
normalizeCheck,
|
|
156
|
+
};
|
|
@@ -6,6 +6,7 @@ const path = require('path');
|
|
|
6
6
|
const yaml = require('js-yaml');
|
|
7
7
|
const { spawnSync } = require('child_process');
|
|
8
8
|
const { validateClaudeIntegration } = require('./validate-claude-integration');
|
|
9
|
+
const { validateCodexSync } = require('./validate-codex-sync');
|
|
9
10
|
const { validateCodexIntegration } = require('./validate-codex-integration');
|
|
10
11
|
const { validateCodexSkills } = require('./codex-skills-sync/validate');
|
|
11
12
|
const { validatePaths } = require('./validate-paths');
|
|
@@ -218,6 +219,7 @@ function diffCompatibilityContracts(currentContract, previousContract) {
|
|
|
218
219
|
function runParityValidation(options = {}, deps = {}) {
|
|
219
220
|
const projectRoot = options.projectRoot || process.cwd();
|
|
220
221
|
const runSync = deps.runSyncValidate || runSyncValidate;
|
|
222
|
+
const runCodexSync = deps.validateCodexSync || validateCodexSync;
|
|
221
223
|
const runClaudeIntegration = deps.validateClaudeIntegration || validateClaudeIntegration;
|
|
222
224
|
const runCodexIntegration = deps.validateCodexIntegration || validateCodexIntegration;
|
|
223
225
|
const runCodexSkills = deps.validateCodexSkills || validateCodexSkills;
|
|
@@ -234,7 +236,7 @@ function runParityValidation(options = {}, deps = {}) {
|
|
|
234
236
|
const checks = [
|
|
235
237
|
{ id: 'claude-sync', exec: () => runSync('claude-code', projectRoot) },
|
|
236
238
|
{ id: 'claude-integration', exec: () => runClaudeIntegration({ projectRoot }) },
|
|
237
|
-
{ id: 'codex-sync', exec: () =>
|
|
239
|
+
{ id: 'codex-sync', exec: () => runCodexSync({ projectRoot, quiet: true }) },
|
|
238
240
|
{ id: 'codex-integration', exec: () => runCodexIntegration({ projectRoot }) },
|
|
239
241
|
{ id: 'codex-skills', exec: () => runCodexSkills({ projectRoot, strict: true, quiet: true }) },
|
|
240
242
|
{ id: 'paths', exec: () => runPaths({ projectRoot }) },
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
|
+
const {
|
|
7
|
+
loadCodexCatalogConfig,
|
|
8
|
+
validateSkillActivationPaths,
|
|
9
|
+
} = require('./codex-parity/catalog');
|
|
6
10
|
|
|
7
11
|
const FORBIDDEN_ABSOLUTE_PATTERNS = [
|
|
8
12
|
/\/Users\/[^\s/'"]+/g,
|
|
@@ -54,21 +58,15 @@ function collectAbsolutePathViolations(content, filePath) {
|
|
|
54
58
|
return errors;
|
|
55
59
|
}
|
|
56
60
|
|
|
57
|
-
function validateSkillPathConventions(content, filePath) {
|
|
58
|
-
|
|
59
|
-
if (!content.includes('.sinapse-ai/development/agents/')) {
|
|
60
|
-
errors.push(`${filePath} missing canonical source path ".sinapse-ai/development/agents/"`);
|
|
61
|
-
}
|
|
62
|
-
if (!content.includes('.sinapse-ai/development/scripts/generate-greeting.js')) {
|
|
63
|
-
errors.push(`${filePath} missing canonical greeting script path`);
|
|
64
|
-
}
|
|
65
|
-
return errors;
|
|
61
|
+
function validateSkillPathConventions(content, filePath, config) {
|
|
62
|
+
return validateSkillActivationPaths(content, filePath, config);
|
|
66
63
|
}
|
|
67
64
|
|
|
68
65
|
function validatePaths(options = {}) {
|
|
69
66
|
const resolved = { ...getDefaultOptions(), ...options };
|
|
70
67
|
const errors = [];
|
|
71
68
|
const checkedFiles = [];
|
|
69
|
+
const config = loadCodexCatalogConfig(resolved.projectRoot);
|
|
72
70
|
|
|
73
71
|
const skillFiles = listSkillFiles(resolved.skillsDir);
|
|
74
72
|
const filesToCheck = [...resolved.requiredFiles, ...skillFiles];
|
|
@@ -90,7 +88,7 @@ function validatePaths(options = {}) {
|
|
|
90
88
|
errors.push(...collectAbsolutePathViolations(content, path.relative(resolved.projectRoot, file)));
|
|
91
89
|
|
|
92
90
|
if (file.endsWith('SKILL.md')) {
|
|
93
|
-
errors.push(...validateSkillPathConventions(content, path.relative(resolved.projectRoot, file)));
|
|
91
|
+
errors.push(...validateSkillPathConventions(content, path.relative(resolved.projectRoot, file), config));
|
|
94
92
|
}
|
|
95
93
|
}
|
|
96
94
|
|
|
@@ -2,38 +2,73 @@
|
|
|
2
2
|
|
|
3
3
|
Template reutilizavel para configurar colaboracao segura em qualquer projeto.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Fonte canonica
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Para a politica completa e atualizada de colaboracao paralela, use:
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
- `docs/guides/parallel-collaboration-source-of-truth.md`
|
|
10
|
+
|
|
11
|
+
Este README explica o template. O documento acima define a regra mestra.
|
|
12
|
+
|
|
13
|
+
## Uso rapido
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
```bash
|
|
16
|
+
bash apply.sh <projeto> <owner-github> <collab-github> [prefix1] [prefix2]
|
|
17
|
+
```
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
cp CODEOWNERS.template <projeto>/.github/CODEOWNERS
|
|
19
|
+
### Exemplo
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
```bash
|
|
22
|
+
bash .sinapse-ai/infrastructure/templates/safe-collab/apply.sh \
|
|
23
|
+
/path/to/meu-projeto \
|
|
24
|
+
caioimori \
|
|
25
|
+
Matheus-soier \
|
|
26
|
+
caio \
|
|
27
|
+
soier
|
|
21
28
|
```
|
|
22
29
|
|
|
23
|
-
|
|
30
|
+
Isso cria automaticamente:
|
|
31
|
+
- `.claude/rules/safe-collaboration.md` — regras para agentes
|
|
32
|
+
- `.github/PULL_REQUEST_TEMPLATE.md` — template simplificado de PR
|
|
33
|
+
- `.github/CODEOWNERS` — code owners configurado
|
|
34
|
+
- `docs/guides/parallel-workflow.md` — guia para equipe
|
|
35
|
+
- Atualiza `.gitignore` com patterns de runtime
|
|
24
36
|
|
|
25
|
-
|
|
37
|
+
## Configuracao manual no GitHub
|
|
26
38
|
|
|
27
|
-
|
|
28
|
-
|
|
39
|
+
Apos rodar o script, configure no repositorio:
|
|
40
|
+
|
|
41
|
+
1. **Settings > Rules > Rulesets > New ruleset** (target: `main`)
|
|
29
42
|
- Block direct pushes
|
|
30
43
|
- Require 1 PR approval
|
|
31
44
|
- Block force pushes
|
|
32
45
|
- Block branch deletion
|
|
33
46
|
- Dismiss stale reviews
|
|
34
47
|
|
|
35
|
-
2. Settings > Collaborators
|
|
48
|
+
2. **Settings > Collaborators**
|
|
36
49
|
- Adicionar membros com permissao `Write` (nunca Admin)
|
|
37
50
|
|
|
38
|
-
3. Settings > General
|
|
51
|
+
3. **Settings > General**
|
|
39
52
|
- Marcar "Automatically delete head branches"
|
|
53
|
+
|
|
54
|
+
## Arquivos incluidos
|
|
55
|
+
|
|
56
|
+
| Arquivo | Proposito |
|
|
57
|
+
|---------|-----------|
|
|
58
|
+
| `apply.sh` | Script de aplicacao automatica |
|
|
59
|
+
| `safe-collaboration-rule.md` | Regra para `.claude/rules/` |
|
|
60
|
+
| `parallel-workflow-guide.md` | Guia para equipe |
|
|
61
|
+
| `CODEOWNERS.template` | Template de code owners |
|
|
62
|
+
| `pull_request_template.md` | PR template simplificado |
|
|
63
|
+
|
|
64
|
+
## Placeholders
|
|
65
|
+
|
|
66
|
+
| Placeholder | Descricao | Exemplo |
|
|
67
|
+
|-------------|-----------|---------|
|
|
68
|
+
| `{{USER_1}}` | Nome do usuario 1 | Caio |
|
|
69
|
+
| `{{USER_2}}` | Nome do usuario 2 | Matheus |
|
|
70
|
+
| `{{user1}}` | Prefixo de branch user 1 | caio |
|
|
71
|
+
| `{{user2}}` | Prefixo de branch user 2 | soier |
|
|
72
|
+
| `{{OWNER_GITHUB}}` | Username GitHub do owner | caioimori |
|
|
73
|
+
| `{{COLLAB_GITHUB}}` | Username GitHub do collab | Matheus-soier |
|
|
74
|
+
| `{{PROJECT_NAME}}` | Nome do projeto | meu-projeto |
|