sinapse-ai 7.7.3 → 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/.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 +848 -751
- 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 +8 -0
- package/.sinapse-ai/install-manifest.yaml +35 -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 +11 -3
- package/packages/installer/tests/unit/entity-registry-bootstrap.test.js +2 -2
|
@@ -557,3 +557,13 @@
|
|
|
557
557
|
{"timestamp":"2026-04-02T01:18:15.933Z","action":"change","path":".sinapse-ai/core/doctor/checks/constitution-consistency.js","trigger":"watcher"}
|
|
558
558
|
{"timestamp":"2026-04-02T01:18:15.934Z","action":"change","path":".sinapse-ai/core/health-check/checks/project/constitution-consistency.js","trigger":"watcher"}
|
|
559
559
|
{"timestamp":"2026-04-02T01:18:15.934Z","action":"change","path":".sinapse-ai/product/templates/ide-rules/claude-rules.md","trigger":"watcher"}
|
|
560
|
+
{"timestamp":"2026-04-02T23:48:52.329Z","action":"change","path":".sinapse-ai/core-config.yaml","trigger":"watcher"}
|
|
561
|
+
{"timestamp":"2026-04-02T23:48:52.331Z","action":"add","path":".sinapse-ai/infrastructure/scripts/codex-parity/catalog.js","trigger":"watcher"}
|
|
562
|
+
{"timestamp":"2026-04-02T23:48:52.331Z","action":"change","path":".sinapse-ai/infrastructure/scripts/codex-skills-sync/index.js","trigger":"watcher"}
|
|
563
|
+
{"timestamp":"2026-04-02T23:48:52.331Z","action":"change","path":".sinapse-ai/infrastructure/scripts/codex-skills-sync/validate.js","trigger":"watcher"}
|
|
564
|
+
{"timestamp":"2026-04-02T23:48:52.332Z","action":"add","path":".sinapse-ai/infrastructure/scripts/sync-codex-local-first.js","trigger":"watcher"}
|
|
565
|
+
{"timestamp":"2026-04-02T23:48:52.332Z","action":"add","path":".sinapse-ai/infrastructure/scripts/validate-codex-command-registry.js","trigger":"watcher"}
|
|
566
|
+
{"timestamp":"2026-04-02T23:48:52.333Z","action":"change","path":".sinapse-ai/infrastructure/scripts/validate-codex-integration.js","trigger":"watcher"}
|
|
567
|
+
{"timestamp":"2026-04-02T23:48:52.333Z","action":"add","path":".sinapse-ai/infrastructure/scripts/validate-codex-sync.js","trigger":"watcher"}
|
|
568
|
+
{"timestamp":"2026-04-02T23:48:52.333Z","action":"change","path":".sinapse-ai/infrastructure/scripts/validate-parity.js","trigger":"watcher"}
|
|
569
|
+
{"timestamp":"2026-04-02T23:48:52.333Z","action":"change","path":".sinapse-ai/infrastructure/scripts/validate-paths.js","trigger":"watcher"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const DEFAULT_CONFIG = {
|
|
8
|
+
version: 1,
|
|
9
|
+
catalogMode: 'canonical',
|
|
10
|
+
canonicalAgentsDir: '.sinapse-ai/development/agents',
|
|
11
|
+
codexAgentsDir: '.codex/agents',
|
|
12
|
+
skillsDir: '.codex/skills',
|
|
13
|
+
expectedSkillIds: null,
|
|
14
|
+
generatedSkillMap: {},
|
|
15
|
+
canonicalSkillMap: {},
|
|
16
|
+
pathConventions: {
|
|
17
|
+
allowedSourcePrefixes: ['.sinapse-ai/development/agents/'],
|
|
18
|
+
allowedGreetingScriptPrefixes: ['.sinapse-ai/development/scripts/generate-greeting.js'],
|
|
19
|
+
greetingOptionalSourcePrefixes: [],
|
|
20
|
+
requiredFallbackBySourcePrefix: {},
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function isPlainObject(value) {
|
|
25
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function mergeConfig(base, override) {
|
|
29
|
+
const result = { ...base };
|
|
30
|
+
for (const [key, value] of Object.entries(override || {})) {
|
|
31
|
+
if (Array.isArray(value)) {
|
|
32
|
+
result[key] = [...value];
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (isPlainObject(value) && isPlainObject(base[key])) {
|
|
36
|
+
result[key] = mergeConfig(base[key], value);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
result[key] = value;
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getCatalogConfigPath(projectRoot = process.cwd()) {
|
|
45
|
+
return path.join(projectRoot, '.codex', 'catalog.json');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function loadCodexCatalogConfig(projectRoot = process.cwd()) {
|
|
49
|
+
const configPath = getCatalogConfigPath(projectRoot);
|
|
50
|
+
if (!fs.existsSync(configPath)) {
|
|
51
|
+
return mergeConfig(DEFAULT_CONFIG, {});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
55
|
+
const parsed = JSON.parse(raw);
|
|
56
|
+
return mergeConfig(DEFAULT_CONFIG, parsed);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getConfiguredSkillIds(config) {
|
|
60
|
+
if (!Array.isArray(config.expectedSkillIds) || config.expectedSkillIds.length === 0) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return [...config.expectedSkillIds].sort();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function getGeneratedSkillSpec(agentId, config) {
|
|
67
|
+
return config.generatedSkillMap?.[agentId] || null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function hasAllowedPrefix(content, prefixes = []) {
|
|
71
|
+
return prefixes.some((prefix) => content.includes(prefix));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function detectMatchedSourcePrefix(content, prefixes = []) {
|
|
75
|
+
let matchedPrefix = null;
|
|
76
|
+
let matchedIndex = Number.POSITIVE_INFINITY;
|
|
77
|
+
|
|
78
|
+
for (const prefix of prefixes) {
|
|
79
|
+
const index = content.indexOf(prefix);
|
|
80
|
+
if (index === -1) continue;
|
|
81
|
+
if (index < matchedIndex) {
|
|
82
|
+
matchedIndex = index;
|
|
83
|
+
matchedPrefix = prefix;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return matchedPrefix;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function validateSkillActivationPaths(content, filePath, config) {
|
|
91
|
+
const rules = config.pathConventions || DEFAULT_CONFIG.pathConventions;
|
|
92
|
+
const errors = [];
|
|
93
|
+
|
|
94
|
+
const matchedSourcePrefix = detectMatchedSourcePrefix(content, rules.allowedSourcePrefixes);
|
|
95
|
+
if (!matchedSourcePrefix) {
|
|
96
|
+
errors.push(`${filePath} missing approved source path`);
|
|
97
|
+
return errors;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const requiredFallback = rules.requiredFallbackBySourcePrefix?.[matchedSourcePrefix];
|
|
101
|
+
if (requiredFallback && !content.includes(requiredFallback)) {
|
|
102
|
+
errors.push(`${filePath} missing fallback activation path "${requiredFallback}"`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const greetingOptional = (rules.greetingOptionalSourcePrefixes || [])
|
|
106
|
+
.some((prefix) => matchedSourcePrefix.startsWith(prefix));
|
|
107
|
+
|
|
108
|
+
if (!greetingOptional && !hasAllowedPrefix(content, rules.allowedGreetingScriptPrefixes)) {
|
|
109
|
+
errors.push(`${filePath} missing approved greeting script path`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return errors;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = {
|
|
116
|
+
DEFAULT_CONFIG,
|
|
117
|
+
mergeConfig,
|
|
118
|
+
getCatalogConfigPath,
|
|
119
|
+
loadCodexCatalogConfig,
|
|
120
|
+
getConfiguredSkillIds,
|
|
121
|
+
getGeneratedSkillSpec,
|
|
122
|
+
validateSkillActivationPaths,
|
|
123
|
+
};
|
|
@@ -10,6 +10,10 @@ const {
|
|
|
10
10
|
normalizeCommands,
|
|
11
11
|
getVisibleCommands,
|
|
12
12
|
} = require('../ide-sync/agent-parser');
|
|
13
|
+
const {
|
|
14
|
+
loadCodexCatalogConfig,
|
|
15
|
+
getGeneratedSkillSpec,
|
|
16
|
+
} = require('../codex-parity/catalog');
|
|
13
17
|
|
|
14
18
|
function getCodexHome() {
|
|
15
19
|
return process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
|
|
@@ -43,7 +47,28 @@ function getSkillId(agentId) {
|
|
|
43
47
|
return `sinapse-${id}`;
|
|
44
48
|
}
|
|
45
49
|
|
|
46
|
-
function
|
|
50
|
+
function resolveSkillSpec(agentData, config) {
|
|
51
|
+
const override = getGeneratedSkillSpec(agentData.id, config) || {};
|
|
52
|
+
const sourceOfTruth = override.sourceOfTruth || `.sinapse-ai/development/agents/${agentData.filename}`;
|
|
53
|
+
const fallbackSource = override.fallbackSource || (
|
|
54
|
+
sourceOfTruth === `.sinapse-ai/development/agents/${agentData.filename}`
|
|
55
|
+
? `.codex/agents/${agentData.filename}`
|
|
56
|
+
: null
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
skillId: override.skillId || getSkillId(agentData.id),
|
|
61
|
+
sourceOfTruth,
|
|
62
|
+
fallbackSource,
|
|
63
|
+
canonicalReference: override.canonicalReference || null,
|
|
64
|
+
greetingScript: override.greetingScript || '.sinapse-ai/development/scripts/generate-greeting.js',
|
|
65
|
+
greetingAgentId: override.greetingAgentId || agentData.id,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function buildSkillContent(agentData, options = {}) {
|
|
70
|
+
const config = options.config || loadCodexCatalogConfig(options.projectRoot || process.cwd());
|
|
71
|
+
const spec = resolveSkillSpec(agentData, config);
|
|
47
72
|
const agent = agentData.agent || {};
|
|
48
73
|
const name = agent.name || agentData.id;
|
|
49
74
|
const title = agent.title || 'SINAPSE Agent';
|
|
@@ -57,8 +82,26 @@ function buildSkillContent(agentData) {
|
|
|
57
82
|
.map(c => `- \`*${c.name}\` - ${c.description || 'No description'}`)
|
|
58
83
|
.join('\n');
|
|
59
84
|
|
|
60
|
-
const skillName =
|
|
85
|
+
const skillName = spec.skillId;
|
|
61
86
|
const description = trimText(`${title} (${name}). ${whenToUse}`, 180);
|
|
87
|
+
const sourceOfTruthLine = spec.fallbackSource
|
|
88
|
+
? `1. Load \`${spec.sourceOfTruth}\` as source of truth (fallback: \`${spec.fallbackSource}\`)`
|
|
89
|
+
: `1. Load \`${spec.sourceOfTruth}\` as source of truth`;
|
|
90
|
+
const canonicalReferenceLine = spec.canonicalReference
|
|
91
|
+
? `2. Keep \`${spec.canonicalReference}\` as the shared parity reference.`
|
|
92
|
+
: null;
|
|
93
|
+
const greetingStepNumber = canonicalReferenceLine ? 3 : 2;
|
|
94
|
+
const personaStepNumber = canonicalReferenceLine ? 4 : 3;
|
|
95
|
+
const stayInPersonaStepNumber = canonicalReferenceLine ? 5 : 4;
|
|
96
|
+
|
|
97
|
+
const activationLines = [
|
|
98
|
+
`${sourceOfTruthLine}.`,
|
|
99
|
+
canonicalReferenceLine,
|
|
100
|
+
`${greetingStepNumber}. Generate greeting via \`node ${spec.greetingScript} ${spec.greetingAgentId}\` and show it first.`,
|
|
101
|
+
`${personaStepNumber}. Adopt this agent persona and command system.`,
|
|
102
|
+
`${stayInPersonaStepNumber}. If a starred command is invoked in Codex, resolve it via \`node .codex/scripts/resolve-codex-command.js ${skillName} <command>\` when a registry mapping exists.`,
|
|
103
|
+
`${stayInPersonaStepNumber + 1}. Stay in this persona until the user asks to switch or exit.`,
|
|
104
|
+
].filter(Boolean);
|
|
62
105
|
|
|
63
106
|
return `---
|
|
64
107
|
name: ${skillName}
|
|
@@ -71,10 +114,7 @@ description: ${description}
|
|
|
71
114
|
${whenToUse}
|
|
72
115
|
|
|
73
116
|
## Activation Protocol
|
|
74
|
-
|
|
75
|
-
2. Adopt this agent persona and command system.
|
|
76
|
-
3. Generate greeting via \`node .sinapse-ai/development/scripts/generate-greeting.js ${agentData.id}\` and show it first.
|
|
77
|
-
4. Stay in this persona until the user asks to switch or exit.
|
|
117
|
+
${activationLines.join('\n')}
|
|
78
118
|
|
|
79
119
|
## Starter Commands
|
|
80
120
|
${commands || '- `*help` - List available commands'}
|
|
@@ -86,11 +126,13 @@ ${commands || '- `*help` - List available commands'}
|
|
|
86
126
|
`;
|
|
87
127
|
}
|
|
88
128
|
|
|
89
|
-
function buildSkillPlan(agents, skillsDir) {
|
|
129
|
+
function buildSkillPlan(agents, skillsDir, options = {}) {
|
|
130
|
+
const config = options.config || loadCodexCatalogConfig(options.projectRoot || process.cwd());
|
|
90
131
|
return agents
|
|
91
132
|
.filter(a => !a.error || a.error === 'YAML parse failed, using fallback extraction')
|
|
92
133
|
.map(agentData => {
|
|
93
|
-
const
|
|
134
|
+
const spec = resolveSkillSpec(agentData, config);
|
|
135
|
+
const skillId = spec.skillId;
|
|
94
136
|
const targetDir = path.join(skillsDir, skillId);
|
|
95
137
|
const targetFile = path.join(targetDir, 'SKILL.md');
|
|
96
138
|
return {
|
|
@@ -98,7 +140,7 @@ function buildSkillPlan(agents, skillsDir) {
|
|
|
98
140
|
skillId,
|
|
99
141
|
targetDir,
|
|
100
142
|
targetFile,
|
|
101
|
-
content: buildSkillContent(agentData),
|
|
143
|
+
content: buildSkillContent(agentData, { config, projectRoot: options.projectRoot }),
|
|
102
144
|
};
|
|
103
145
|
});
|
|
104
146
|
}
|
|
@@ -121,15 +163,22 @@ function syncSkills(options = {}) {
|
|
|
121
163
|
if (resolved.globalOnly) {
|
|
122
164
|
resolved.global = true;
|
|
123
165
|
}
|
|
166
|
+
const config = resolved.config || loadCodexCatalogConfig(resolved.projectRoot);
|
|
124
167
|
const agents = parseAllAgents(resolved.sourceDir);
|
|
125
|
-
const plan = buildSkillPlan(agents, resolved.localSkillsDir
|
|
168
|
+
const plan = buildSkillPlan(agents, resolved.localSkillsDir, {
|
|
169
|
+
config,
|
|
170
|
+
projectRoot: resolved.projectRoot,
|
|
171
|
+
});
|
|
126
172
|
|
|
127
173
|
if (!resolved.globalOnly) {
|
|
128
174
|
writeSkillPlan(plan, resolved);
|
|
129
175
|
}
|
|
130
176
|
|
|
131
177
|
if (resolved.global) {
|
|
132
|
-
const globalPlan = buildSkillPlan(agents, resolved.globalSkillsDir
|
|
178
|
+
const globalPlan = buildSkillPlan(agents, resolved.globalSkillsDir, {
|
|
179
|
+
config,
|
|
180
|
+
projectRoot: resolved.projectRoot,
|
|
181
|
+
});
|
|
133
182
|
writeSkillPlan(globalPlan, resolved);
|
|
134
183
|
}
|
|
135
184
|
|
|
@@ -6,6 +6,11 @@ const path = require('path');
|
|
|
6
6
|
|
|
7
7
|
const { parseAllAgents } = require('../ide-sync/agent-parser');
|
|
8
8
|
const { getSkillId } = require('./index');
|
|
9
|
+
const {
|
|
10
|
+
loadCodexCatalogConfig,
|
|
11
|
+
getConfiguredSkillIds,
|
|
12
|
+
validateSkillActivationPaths,
|
|
13
|
+
} = require('../codex-parity/catalog');
|
|
9
14
|
|
|
10
15
|
function getDefaultOptions() {
|
|
11
16
|
const projectRoot = process.cwd();
|
|
@@ -32,30 +37,38 @@ function isParsableAgent(agent) {
|
|
|
32
37
|
return !agent.error || agent.error === 'YAML parse failed, using fallback extraction';
|
|
33
38
|
}
|
|
34
39
|
|
|
35
|
-
function validateSkillContent(content, expected) {
|
|
40
|
+
function validateSkillContent(content, expected, config) {
|
|
36
41
|
const issues = [];
|
|
37
42
|
const requiredChecks = [
|
|
38
43
|
{ ok: content.includes(`name: ${expected.skillId}`), reason: `missing frontmatter name "${expected.skillId}"` },
|
|
39
|
-
{
|
|
40
|
-
ok: content.includes(`.sinapse-ai/development/agents/${expected.filename}`),
|
|
41
|
-
reason: `missing canonical agent path "${expected.filename}"`,
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
ok: content.includes(`generate-greeting.js ${expected.agentId}`),
|
|
45
|
-
reason: `missing canonical greeting command for "${expected.agentId}"`,
|
|
46
|
-
},
|
|
47
44
|
{
|
|
48
45
|
ok: content.includes('source of truth'),
|
|
49
46
|
reason: 'missing source-of-truth activation note',
|
|
50
47
|
},
|
|
51
48
|
];
|
|
52
49
|
|
|
50
|
+
if (expected.filename) {
|
|
51
|
+
requiredChecks.push({
|
|
52
|
+
ok: content.includes(`.sinapse-ai/development/agents/${expected.filename}`) || content.includes(expected.filename),
|
|
53
|
+
reason: `missing canonical agent path "${expected.filename}"`,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (expected.greetingAgentId && content.includes('.sinapse-ai/development/scripts/generate-greeting.js')) {
|
|
58
|
+
requiredChecks.push({
|
|
59
|
+
ok: content.includes(`generate-greeting.js ${expected.greetingAgentId}`),
|
|
60
|
+
reason: `missing canonical greeting command for "${expected.greetingAgentId}"`,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
53
64
|
for (const check of requiredChecks) {
|
|
54
65
|
if (!check.ok) {
|
|
55
66
|
issues.push(check.reason);
|
|
56
67
|
}
|
|
57
68
|
}
|
|
58
69
|
|
|
70
|
+
issues.push(...validateSkillActivationPaths(content, expected.skillId, config));
|
|
71
|
+
|
|
59
72
|
return issues;
|
|
60
73
|
}
|
|
61
74
|
|
|
@@ -63,18 +76,33 @@ function validateCodexSkills(options = {}) {
|
|
|
63
76
|
const resolved = { ...getDefaultOptions(), ...options };
|
|
64
77
|
const errors = [];
|
|
65
78
|
const warnings = [];
|
|
79
|
+
const config = loadCodexCatalogConfig(resolved.projectRoot);
|
|
66
80
|
|
|
67
81
|
if (!fs.existsSync(resolved.skillsDir)) {
|
|
68
82
|
errors.push(`Skills directory not found: ${resolved.skillsDir}`);
|
|
69
83
|
return { ok: false, checked: 0, expected: 0, errors, warnings, missing: [], orphaned: [] };
|
|
70
84
|
}
|
|
71
85
|
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
86
|
+
const configuredSkillIds = getConfiguredSkillIds(config);
|
|
87
|
+
let expected;
|
|
88
|
+
|
|
89
|
+
if (configuredSkillIds) {
|
|
90
|
+
const canonicalMap = config.canonicalSkillMap || {};
|
|
91
|
+
expected = configuredSkillIds.map((skillId) => ({
|
|
92
|
+
skillId,
|
|
93
|
+
agentId: canonicalMap[skillId]?.agentId || null,
|
|
94
|
+
greetingAgentId: canonicalMap[skillId]?.greetingAgentId || canonicalMap[skillId]?.agentId || null,
|
|
95
|
+
filename: canonicalMap[skillId]?.filename || null,
|
|
96
|
+
}));
|
|
97
|
+
} else {
|
|
98
|
+
const agents = parseAllAgents(resolved.sourceDir).filter(isParsableAgent);
|
|
99
|
+
expected = agents.map(agent => ({
|
|
100
|
+
agentId: agent.id,
|
|
101
|
+
greetingAgentId: agent.id,
|
|
102
|
+
filename: agent.filename,
|
|
103
|
+
skillId: getSkillId(agent.id),
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
78
106
|
|
|
79
107
|
const missing = [];
|
|
80
108
|
for (const item of expected) {
|
|
@@ -92,7 +120,7 @@ function validateCodexSkills(options = {}) {
|
|
|
92
120
|
errors.push(`${item.skillId}: unable to read skill file (${error.message})`);
|
|
93
121
|
continue;
|
|
94
122
|
}
|
|
95
|
-
const issues = validateSkillContent(content, item);
|
|
123
|
+
const issues = validateSkillContent(content, item, config);
|
|
96
124
|
for (const issue of issues) {
|
|
97
125
|
errors.push(`${item.skillId}: ${issue}`);
|
|
98
126
|
}
|
|
@@ -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 { syncSkills } = require('./codex-skills-sync/index');
|
|
8
|
+
const { loadCodexCatalogConfig } = require('./codex-parity/catalog');
|
|
9
|
+
|
|
10
|
+
function parseArgs(argv = process.argv.slice(2)) {
|
|
11
|
+
const args = new Set(argv);
|
|
12
|
+
return {
|
|
13
|
+
dryRun: args.has('--dry-run'),
|
|
14
|
+
quiet: args.has('--quiet') || args.has('-q'),
|
|
15
|
+
json: args.has('--json'),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function countMarkdownFiles(dirPath) {
|
|
20
|
+
if (!fs.existsSync(dirPath)) return 0;
|
|
21
|
+
return fs.readdirSync(dirPath).filter((entry) => entry.endsWith('.md')).length;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function countSkillFiles(skillsDir) {
|
|
25
|
+
if (!fs.existsSync(skillsDir)) return 0;
|
|
26
|
+
return fs.readdirSync(skillsDir, { withFileTypes: true })
|
|
27
|
+
.filter((entry) => entry.isDirectory() && entry.name.startsWith('sinapse-'))
|
|
28
|
+
.filter((entry) => fs.existsSync(path.join(skillsDir, entry.name, 'SKILL.md')))
|
|
29
|
+
.length;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function runLegacyCodexSync(projectRoot, options = {}) {
|
|
33
|
+
const args = [
|
|
34
|
+
path.join('.sinapse-ai', 'infrastructure', 'scripts', 'ide-sync', 'index.js'),
|
|
35
|
+
'sync',
|
|
36
|
+
'--ide',
|
|
37
|
+
'codex',
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
if (options.dryRun) args.push('--dry-run');
|
|
41
|
+
if (options.quiet) args.push('--quiet');
|
|
42
|
+
|
|
43
|
+
const result = spawnSync(process.execPath, args, {
|
|
44
|
+
cwd: projectRoot,
|
|
45
|
+
encoding: 'utf8',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
ok: result.status === 0,
|
|
50
|
+
mode: 'canonical',
|
|
51
|
+
delegated: true,
|
|
52
|
+
stdout: result.stdout || '',
|
|
53
|
+
stderr: result.stderr || '',
|
|
54
|
+
status: result.status || 0,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function syncCodexLocalFirst(options = {}, deps = {}) {
|
|
59
|
+
const projectRoot = options.projectRoot || process.cwd();
|
|
60
|
+
const config = loadCodexCatalogConfig(projectRoot);
|
|
61
|
+
const runLegacySync = deps.runLegacyCodexSync || runLegacyCodexSync;
|
|
62
|
+
const runSyncSkills = deps.syncSkills || syncSkills;
|
|
63
|
+
const errors = [];
|
|
64
|
+
|
|
65
|
+
if (config.catalogMode !== 'expanded') {
|
|
66
|
+
return runLegacySync(projectRoot, options);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const agentsDir = path.join(projectRoot, config.codexAgentsDir || '.codex/agents');
|
|
70
|
+
const skillsDir = path.join(projectRoot, config.skillsDir || '.codex/skills');
|
|
71
|
+
const sourceDir = path.join(projectRoot, config.canonicalAgentsDir || '.sinapse-ai/development/agents');
|
|
72
|
+
|
|
73
|
+
if (!fs.existsSync(agentsDir)) {
|
|
74
|
+
errors.push(`Missing Codex agents dir: ${path.relative(projectRoot, agentsDir)}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (errors.length > 0) {
|
|
78
|
+
return {
|
|
79
|
+
ok: false,
|
|
80
|
+
mode: 'expanded',
|
|
81
|
+
delegated: false,
|
|
82
|
+
errors,
|
|
83
|
+
metrics: {
|
|
84
|
+
codexAgents: countMarkdownFiles(agentsDir),
|
|
85
|
+
codexSkills: 0,
|
|
86
|
+
generatedSkills: 0,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const syncResult = runSyncSkills({
|
|
92
|
+
projectRoot,
|
|
93
|
+
sourceDir,
|
|
94
|
+
localSkillsDir: skillsDir,
|
|
95
|
+
dryRun: Boolean(options.dryRun),
|
|
96
|
+
quiet: true,
|
|
97
|
+
config,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
ok: true,
|
|
102
|
+
mode: 'expanded',
|
|
103
|
+
delegated: false,
|
|
104
|
+
errors: [],
|
|
105
|
+
metrics: {
|
|
106
|
+
codexAgents: countMarkdownFiles(agentsDir),
|
|
107
|
+
codexSkills: countSkillFiles(skillsDir),
|
|
108
|
+
generatedSkills: syncResult.generated,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function formatHumanReport(result) {
|
|
114
|
+
if (result.delegated) {
|
|
115
|
+
return [result.stdout, result.stderr].filter(Boolean).join('\n').trim();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!result.ok) {
|
|
119
|
+
return [
|
|
120
|
+
`X Codex local-first sync failed (${result.errors.length} issue(s))`,
|
|
121
|
+
...result.errors.map((error) => `- ${error}`),
|
|
122
|
+
].join('\n');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return `OK Codex local-first sync complete (agents preserved: ${result.metrics.codexAgents}, total skills: ${result.metrics.codexSkills}, skills regenerated: ${result.metrics.generatedSkills})`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function main() {
|
|
129
|
+
const args = parseArgs();
|
|
130
|
+
const result = syncCodexLocalFirst(args);
|
|
131
|
+
|
|
132
|
+
if (!args.quiet) {
|
|
133
|
+
if (args.json) {
|
|
134
|
+
console.log(JSON.stringify(result, null, 2));
|
|
135
|
+
} else {
|
|
136
|
+
console.log(formatHumanReport(result));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!result.ok) {
|
|
141
|
+
process.exitCode = 1;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (require.main === module) {
|
|
146
|
+
main();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = {
|
|
150
|
+
parseArgs,
|
|
151
|
+
syncCodexLocalFirst,
|
|
152
|
+
formatHumanReport,
|
|
153
|
+
runLegacyCodexSync,
|
|
154
|
+
countMarkdownFiles,
|
|
155
|
+
countSkillFiles,
|
|
156
|
+
};
|