@juancr11/sibu 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +89 -0
  2. package/bin/entrypoints/cli/create-program.js +13 -0
  3. package/bin/entrypoints/cli/execute-command.js +10 -0
  4. package/bin/modules/interactive-guidance/index.js +1 -1
  5. package/bin/modules/interactive-guidance/prompts.js +34 -1
  6. package/bin/modules/mcp-server-selection-management/index.js +3 -0
  7. package/bin/modules/mcp-server-selection-management/list-mcp-servers/command.js +1 -0
  8. package/bin/modules/mcp-server-selection-management/list-mcp-servers/handler.js +46 -0
  9. package/bin/modules/mcp-server-selection-management/stop-mcp-server/command.js +1 -0
  10. package/bin/modules/mcp-server-selection-management/stop-mcp-server/handler.js +194 -0
  11. package/bin/modules/mcp-server-selection-management/use-mcp-server/command.js +1 -0
  12. package/bin/modules/mcp-server-selection-management/use-mcp-server/handler.js +127 -0
  13. package/bin/modules/project-adoption/handler.js +37 -11
  14. package/bin/modules/skill-selection-management/list-skills/handler.js +11 -1
  15. package/bin/modules/skill-selection-management/stop-managing-file/handler.js +7 -1
  16. package/bin/modules/skill-selection-management/use-skill/handler.js +35 -2
  17. package/bin/modules/sync-review/apply-action.js +5 -1
  18. package/bin/modules/sync-review/sync-preview.js +20 -6
  19. package/bin/modules/template-catalog-rendering/index.js +1 -1
  20. package/bin/modules/template-catalog-rendering/templates.js +59 -4
  21. package/bin/modules/workflow-health-diagnosis/handler.js +13 -3
  22. package/bin/modules/workflow-state-registry/state.js +4 -0
  23. package/bin/modules/workflow-target-planning/catalog.js +34 -0
  24. package/bin/modules/workflow-target-planning/index.js +2 -2
  25. package/bin/modules/workflow-target-planning/workflow-targets.js +82 -9
  26. package/package.json +1 -1
  27. package/templates/manifest.json +27 -6
  28. package/templates/mcp/claude/.mcp.json +3 -0
  29. package/templates/mcp/gemini/settings.json +3 -0
  30. package/templates/skills/postgresql-expert/SKILL.md +72 -0
  31. package/templates/skills/scrum-master-planner/SKILL.md +22 -0
@@ -1,6 +1,6 @@
1
1
  import { intro, log, outro } from '@clack/prompts';
2
2
  import chalk from 'chalk';
3
- import { SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_SKILLS, SELECTABLE_WORKFLOW_SKILLS } from '../../workflow-target-planning/index.js';
3
+ import { SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_DATABASE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_SKILLS, SELECTABLE_WORKFLOW_SKILLS } from '../../workflow-target-planning/index.js';
4
4
  import { getProjectContext } from '../../../shared/paths.js';
5
5
  import { renderIntro } from '../../interactive-guidance/index.js';
6
6
  import { readStateForDoctor } from '../../workflow-state-registry/index.js';
@@ -16,6 +16,7 @@ export async function handleListSkills(_command) {
16
16
  }
17
17
  logSkillGroup('Languages', getLanguageSkillItems(state));
18
18
  logSkillGroup('Frameworks', getFrameworkSkillItems(state));
19
+ logSkillGroup('Databases', getDatabaseSkillItems(state));
19
20
  logSkillGroup('Architecture', getArchitectureSkillItems(state));
20
21
  logSkillGroup('Workflow', getWorkflowSkillItems(state));
21
22
  outro(chalk.green('Skill list ready.'));
@@ -38,6 +39,15 @@ function getFrameworkSkillItems(state) {
38
39
  selected: selectedSkillIds.has(skill.id),
39
40
  }));
40
41
  }
42
+ function getDatabaseSkillItems(state) {
43
+ const selectedSkillIds = new Set(state?.selectedDatabaseSkills ?? []);
44
+ return SELECTABLE_DATABASE_SKILLS.map((skill) => ({
45
+ name: skill.name,
46
+ id: skill.id,
47
+ description: skill.description,
48
+ selected: selectedSkillIds.has(skill.id),
49
+ }));
50
+ }
41
51
  function getArchitectureSkillItems(state) {
42
52
  return SELECTABLE_ARCHITECTURE_SKILLS.map((skill) => ({
43
53
  name: skill.name,
@@ -10,7 +10,7 @@ import { renderIntro } from '../../interactive-guidance/index.js';
10
10
  import { cloneState, readStateForDoctor, writeStateFile } from '../../workflow-state-registry/index.js';
11
11
  import { getTemplateVersion, readTemplateManifest, renderTemplateForSync } from '../../template-catalog-rendering/index.js';
12
12
  import { removeUndefinedFields } from '../../../shared/object.js';
13
- import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedWorkflowSkillsFromState, } from '../../workflow-target-planning/index.js';
13
+ import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedDatabaseSkillsFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedWorkflowSkillsFromState, } from '../../workflow-target-planning/index.js';
14
14
  export async function handleStopManagingFile({ skillName }) {
15
15
  await renderIntro();
16
16
  intro(chalk.cyan('Updating skill management'));
@@ -110,6 +110,8 @@ function isSkillSelected(state, resolved) {
110
110
  return state.selectedFrameworkSkills?.includes(resolved.skill.id) ?? false;
111
111
  case 'architecture':
112
112
  return state.selectedArchitectureSkill === resolved.skill.id;
113
+ case 'database':
114
+ return state.selectedDatabaseSkills?.includes(resolved.skill.id) ?? false;
113
115
  case 'workflow':
114
116
  return state.selectedWorkflowSkills?.includes(resolved.skill.id) ?? false;
115
117
  }
@@ -125,6 +127,9 @@ function removeSelectedSkill(state, resolved) {
125
127
  case 'architecture':
126
128
  delete state.selectedArchitectureSkill;
127
129
  return;
130
+ case 'database':
131
+ state.selectedDatabaseSkills = (state.selectedDatabaseSkills ?? []).filter((skillId) => skillId !== resolved.skill.id);
132
+ return;
128
133
  case 'workflow':
129
134
  state.selectedWorkflowSkills = (state.selectedWorkflowSkills ?? []).filter((skillId) => skillId !== resolved.skill.id);
130
135
  return;
@@ -168,6 +173,7 @@ function getAgentsUpdate(rootPath, state) {
168
173
  selectedFrameworkSkills: getSelectedFrameworkSkillsFromState(state),
169
174
  selectedArchitectureSkill: getSelectedArchitectureSkillFromState(state),
170
175
  selectedWorkflowSkills: getSelectedWorkflowSkillsFromState(state),
176
+ selectedDatabaseSkills: getSelectedDatabaseSkillsFromState(state),
171
177
  }),
172
178
  };
173
179
  }
@@ -46,6 +46,7 @@ export function getNextSkillSelection(state, skillName) {
46
46
  const selectedLanguageSkills = [...(state.selectedLanguageSkills ?? [])];
47
47
  const selectedFrameworkSkills = [...(state.selectedFrameworkSkills ?? [])];
48
48
  const selectedWorkflowSkills = [...(state.selectedWorkflowSkills ?? [])];
49
+ const selectedDatabaseSkills = [...(state.selectedDatabaseSkills ?? [])];
49
50
  switch (resolution.resolved.kind) {
50
51
  case 'language':
51
52
  if (selectedLanguageSkills.includes(resolution.resolved.skill.id)) {
@@ -59,6 +60,7 @@ export function getNextSkillSelection(state, skillName) {
59
60
  selectedFrameworkSkills: selectedFrameworkSkills.map(getFrameworkSkillById),
60
61
  selectedArchitectureSkill: getArchitectureSkillById(state.selectedArchitectureSkill),
61
62
  selectedWorkflowSkills: selectedWorkflowSkills.map(getWorkflowSkillById),
63
+ selectedDatabaseSkills: selectedDatabaseSkills.map(getDatabaseSkillById),
62
64
  },
63
65
  };
64
66
  case 'framework':
@@ -73,6 +75,7 @@ export function getNextSkillSelection(state, skillName) {
73
75
  selectedFrameworkSkills: [...selectedFrameworkSkills, resolution.resolved.skill.id].map(getFrameworkSkillById),
74
76
  selectedArchitectureSkill: getArchitectureSkillById(state.selectedArchitectureSkill),
75
77
  selectedWorkflowSkills: selectedWorkflowSkills.map(getWorkflowSkillById),
78
+ selectedDatabaseSkills: selectedDatabaseSkills.map(getDatabaseSkillById),
76
79
  },
77
80
  };
78
81
  case 'architecture':
@@ -94,6 +97,22 @@ export function getNextSkillSelection(state, skillName) {
94
97
  selectedFrameworkSkills: selectedFrameworkSkills.map(getFrameworkSkillById),
95
98
  selectedArchitectureSkill: resolution.resolved.skill,
96
99
  selectedWorkflowSkills: selectedWorkflowSkills.map(getWorkflowSkillById),
100
+ selectedDatabaseSkills: selectedDatabaseSkills.map(getDatabaseSkillById),
101
+ },
102
+ };
103
+ case 'database':
104
+ if (selectedDatabaseSkills.includes(resolution.resolved.skill.id)) {
105
+ return { status: 'noop', message: `${resolution.resolved.skill.name} is already selected.` };
106
+ }
107
+ return {
108
+ status: 'selected',
109
+ skillName: resolution.resolved.skill.name,
110
+ selection: {
111
+ selectedLanguageSkills: selectedLanguageSkills.map(getLanguageSkillById),
112
+ selectedFrameworkSkills: selectedFrameworkSkills.map(getFrameworkSkillById),
113
+ selectedArchitectureSkill: getArchitectureSkillById(state.selectedArchitectureSkill),
114
+ selectedWorkflowSkills: selectedWorkflowSkills.map(getWorkflowSkillById),
115
+ selectedDatabaseSkills: [...selectedDatabaseSkills, resolution.resolved.skill.id].map(getDatabaseSkillById),
97
116
  },
98
117
  };
99
118
  case 'workflow':
@@ -108,6 +127,7 @@ export function getNextSkillSelection(state, skillName) {
108
127
  selectedFrameworkSkills: selectedFrameworkSkills.map(getFrameworkSkillById),
109
128
  selectedArchitectureSkill: getArchitectureSkillById(state.selectedArchitectureSkill),
110
129
  selectedWorkflowSkills: [...selectedWorkflowSkills, resolution.resolved.skill.id].map(getWorkflowSkillById),
130
+ selectedDatabaseSkills: selectedDatabaseSkills.map(getDatabaseSkillById),
111
131
  },
112
132
  };
113
133
  }
@@ -128,12 +148,14 @@ function applySelectedSkill({ rootPath, statePath, state, selectionResult, }) {
128
148
  selectedFrameworkSkills: selectionResult.selection.selectedFrameworkSkills,
129
149
  selectedArchitectureSkill: selectionResult.selection.selectedArchitectureSkill,
130
150
  selectedWorkflowSkills: selectionResult.selection.selectedWorkflowSkills,
151
+ selectedDatabaseSkills: selectionResult.selection.selectedDatabaseSkills,
131
152
  });
132
153
  const [newSkillFile] = renderMissingWorkflowFiles({
133
154
  missingTargets: [plan.newSkillTarget],
134
155
  selectedLanguageSkills: selectionResult.selection.selectedLanguageSkills,
135
156
  selectedFrameworkSkills: selectionResult.selection.selectedFrameworkSkills,
136
157
  selectedArchitectureSkill: selectionResult.selection.selectedArchitectureSkill,
158
+ selectedDatabaseSkills: selectionResult.selection.selectedDatabaseSkills,
137
159
  });
138
160
  fs.mkdirSync(path.dirname(newSkillFile.targetPath), { recursive: true });
139
161
  fs.writeFileSync(newSkillFile.targetPath, newSkillFile.contents, { encoding: 'utf8', flag: 'wx' });
@@ -148,6 +170,7 @@ function applySelectedSkill({ rootPath, statePath, state, selectionResult, }) {
148
170
  selectedFrameworkSkills: selectionResult.selection.selectedFrameworkSkills,
149
171
  selectedArchitectureSkill: selectionResult.selection.selectedArchitectureSkill,
150
172
  selectedWorkflowSkills: selectionResult.selection.selectedWorkflowSkills,
173
+ selectedDatabaseSkills: selectionResult.selection.selectedDatabaseSkills,
151
174
  targets: plan.targets,
152
175
  });
153
176
  log.success(`Updated ${STATE_RELATIVE_PATH}`);
@@ -155,8 +178,8 @@ function applySelectedSkill({ rootPath, statePath, state, selectionResult, }) {
155
178
  }
156
179
  function buildSkillApplicationPlan({ rootPath, state, selectionResult, }) {
157
180
  const selectedAgents = getSelectedAgentsFromState(state);
158
- const previousTargets = getWorkflowTargets(rootPath, selectedAgents, getSelectedLanguageSkillsFromState(state), getSelectedFrameworkSkillsFromState(state), getArchitectureSkillById(state.selectedArchitectureSkill), getSelectedWorkflowSkillsFromState(state));
159
- const targets = getWorkflowTargets(rootPath, selectedAgents, selectionResult.selection.selectedLanguageSkills, selectionResult.selection.selectedFrameworkSkills, selectionResult.selection.selectedArchitectureSkill, selectionResult.selection.selectedWorkflowSkills);
181
+ const previousTargets = getWorkflowTargets(rootPath, selectedAgents, getSelectedLanguageSkillsFromState(state), getSelectedFrameworkSkillsFromState(state), getArchitectureSkillById(state.selectedArchitectureSkill), getSelectedWorkflowSkillsFromState(state), getSelectedDatabaseSkillsFromState(state));
182
+ const targets = getWorkflowTargets(rootPath, selectedAgents, selectionResult.selection.selectedLanguageSkills, selectionResult.selection.selectedFrameworkSkills, selectionResult.selection.selectedArchitectureSkill, selectionResult.selection.selectedWorkflowSkills, selectionResult.selection.selectedDatabaseSkills);
160
183
  const previousTargetPaths = new Set(previousTargets.map((target) => target.targetPath));
161
184
  const newSkillTarget = targets.find((target) => !previousTargetPaths.has(target.targetPath));
162
185
  const agentsTarget = targets.find((target) => target.label === 'AGENTS.md');
@@ -195,6 +218,9 @@ function getSelectedFrameworkSkillsFromState(state) {
195
218
  function getSelectedWorkflowSkillsFromState(state) {
196
219
  return (state.selectedWorkflowSkills ?? []).map(getWorkflowSkillById);
197
220
  }
221
+ function getSelectedDatabaseSkillsFromState(state) {
222
+ return (state.selectedDatabaseSkills ?? []).map(getDatabaseSkillById);
223
+ }
198
224
  function getLanguageSkillById(skillId) {
199
225
  const resolution = resolveSelectableSkillById(skillId);
200
226
  if (!resolution.ok || resolution.resolved.kind !== 'language') {
@@ -226,3 +252,10 @@ function getWorkflowSkillById(skillId) {
226
252
  }
227
253
  return resolution.resolved.skill;
228
254
  }
255
+ function getDatabaseSkillById(skillId) {
256
+ const resolution = resolveSelectableSkillById(skillId);
257
+ if (!resolution.ok || resolution.resolved.kind !== 'database') {
258
+ throw new Error(`Unsupported database skill in state: ${skillId}`);
259
+ }
260
+ return resolution.resolved.skill;
261
+ }
@@ -5,7 +5,7 @@ import { sha256 } from '../../shared/hash.js';
5
5
  import { getSideTemplatePath } from '../../shared/paths.js';
6
6
  import { cloneState } from '../workflow-state-registry/index.js';
7
7
  import { getTemplateVersion, renderTemplateForSync } from '../template-catalog-rendering/index.js';
8
- import { getSelectedArchitectureSkillFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedWorkflowSkillsFromState, } from '../workflow-target-planning/index.js';
8
+ import { getSelectedArchitectureSkillFromState, getSelectedDatabaseSkillsFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedMcpServersFromState, getSelectedWorkflowSkillsFromState, } from '../workflow-target-planning/index.js';
9
9
  export function applySyncAction({ rootPath, state, manifest, preview, action, }) {
10
10
  const nextState = cloneState(state);
11
11
  const managedFile = nextState.managedFiles[preview.relativePath] ?? preview.managedFile;
@@ -20,6 +20,8 @@ export function applySyncAction({ rootPath, state, manifest, preview, action, })
20
20
  selectedFrameworkSkills: getSelectedFrameworkSkillsFromState(nextState),
21
21
  selectedArchitectureSkill: getSelectedArchitectureSkillFromState(nextState),
22
22
  selectedWorkflowSkills: getSelectedWorkflowSkillsFromState(nextState),
23
+ selectedDatabaseSkills: getSelectedDatabaseSkillsFromState(nextState),
24
+ selectedMcpServers: getSelectedMcpServersFromState(nextState),
23
25
  });
24
26
  fs.mkdirSync(path.dirname(targetPath), { recursive: true });
25
27
  fs.writeFileSync(targetPath, contents, 'utf8');
@@ -56,6 +58,8 @@ export function applySyncAction({ rootPath, state, manifest, preview, action, })
56
58
  selectedFrameworkSkills: getSelectedFrameworkSkillsFromState(state),
57
59
  selectedArchitectureSkill: getSelectedArchitectureSkillFromState(state),
58
60
  selectedWorkflowSkills: getSelectedWorkflowSkillsFromState(state),
61
+ selectedDatabaseSkills: getSelectedDatabaseSkillsFromState(state),
62
+ selectedMcpServers: getSelectedMcpServersFromState(state),
59
63
  });
60
64
  fs.mkdirSync(path.dirname(sideTemplatePath), { recursive: true });
61
65
  fs.writeFileSync(sideTemplatePath, contents, 'utf8');
@@ -3,7 +3,7 @@ import path from 'node:path';
3
3
  import { sha256 } from '../../shared/hash.js';
4
4
  import { hasReviewedTemplateVersion } from '../workflow-state-registry/index.js';
5
5
  import { renderTemplateForSync } from '../template-catalog-rendering/index.js';
6
- import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedWorkflowSkillsFromState, getWorkflowTargets, } from '../workflow-target-planning/index.js';
6
+ import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedDatabaseSkillsFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedMcpServersFromState, getSelectedWorkflowSkillsFromState, getWorkflowTargets, } from '../workflow-target-planning/index.js';
7
7
  const ACTIONABLE_SYNC_PREVIEW_STATUSES = new Set([
8
8
  'new-template',
9
9
  'missing',
@@ -30,6 +30,8 @@ export function getSyncPreviews({ rootPath, state, manifest }) {
30
30
  const selectedFrameworkSkills = getSelectedFrameworkSkillsFromState(state);
31
31
  const selectedArchitectureSkill = getSelectedArchitectureSkillFromState(state);
32
32
  const selectedWorkflowSkills = getSelectedWorkflowSkillsFromState(state);
33
+ const selectedDatabaseSkills = getSelectedDatabaseSkillsFromState(state);
34
+ const selectedMcpServers = getSelectedMcpServersFromState(state);
33
35
  const previews = Object.entries(state.managedFiles).map(([relativePath, managedFile]) => getManagedFileSyncPreview({
34
36
  rootPath,
35
37
  manifest,
@@ -39,8 +41,10 @@ export function getSyncPreviews({ rootPath, state, manifest }) {
39
41
  selectedFrameworkSkills,
40
42
  selectedArchitectureSkill,
41
43
  selectedWorkflowSkills,
44
+ selectedDatabaseSkills,
45
+ selectedMcpServers,
42
46
  }));
43
- const expectedTargets = getWorkflowTargets(rootPath, getSelectedAgentsFromState(state), selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills);
47
+ const expectedTargets = getWorkflowTargets(rootPath, getSelectedAgentsFromState(state), selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, selectedDatabaseSkills, selectedMcpServers);
44
48
  for (const target of expectedTargets) {
45
49
  const relativePath = path.relative(rootPath, target.targetPath);
46
50
  if (state.managedFiles[relativePath]) {
@@ -77,7 +81,7 @@ export function getSyncPreviews({ rootPath, state, manifest }) {
77
81
  }
78
82
  return previews;
79
83
  }
80
- function getManagedFileSyncPreview({ rootPath, manifest, relativePath, managedFile, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, }) {
84
+ function getManagedFileSyncPreview({ rootPath, manifest, relativePath, managedFile, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, selectedDatabaseSkills, selectedMcpServers, }) {
81
85
  if (managedFile.status === 'unmanaged') {
82
86
  return { relativePath, managedFile, status: 'unmanaged', recordedTemplateVersion: managedFile.templateVersion, changes: [] };
83
87
  }
@@ -97,9 +101,11 @@ function getManagedFileSyncPreview({ rootPath, manifest, relativePath, managedFi
97
101
  selectedFrameworkSkills,
98
102
  selectedArchitectureSkill,
99
103
  selectedWorkflowSkills,
104
+ selectedDatabaseSkills,
105
+ selectedMcpServers,
100
106
  });
101
107
  const hasUpdate = hasTemplateUpdate || hasSelectionUpdate;
102
- const changes = hasTemplateUpdate ? template.changes : ['Refreshes generated skill routing for the current selected skills.'];
108
+ const changes = hasTemplateUpdate ? template.changes : [getSelectionRefreshChange(managedFile.template)];
103
109
  if (!hasLocalFile) {
104
110
  return {
105
111
  relativePath,
@@ -154,8 +160,14 @@ function getManagedFileSyncPreview({ rootPath, manifest, relativePath, managedFi
154
160
  hasLocalFile,
155
161
  };
156
162
  }
157
- function hasRenderedSelectionUpdate({ relativePath, currentPath, managedFile, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, }) {
158
- if (relativePath !== 'AGENTS.md') {
163
+ function getSelectionRefreshChange(templateRelativePath) {
164
+ if (templateRelativePath === '.codex/config.toml' || templateRelativePath === 'mcp/claude/.mcp.json' || templateRelativePath === 'mcp/gemini/settings.json') {
165
+ return 'Refreshes generated MCP configuration for the current selected MCP servers.';
166
+ }
167
+ return 'Refreshes generated skill routing for the current selected skills.';
168
+ }
169
+ function hasRenderedSelectionUpdate({ relativePath, currentPath, managedFile, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, selectedDatabaseSkills, selectedMcpServers, }) {
170
+ if (relativePath !== 'AGENTS.md' && selectedMcpServers.length === 0) {
159
171
  return false;
160
172
  }
161
173
  const renderedContents = renderTemplateForSync({
@@ -165,6 +177,8 @@ function hasRenderedSelectionUpdate({ relativePath, currentPath, managedFile, se
165
177
  selectedFrameworkSkills,
166
178
  selectedArchitectureSkill,
167
179
  selectedWorkflowSkills,
180
+ selectedDatabaseSkills,
181
+ selectedMcpServers,
168
182
  });
169
183
  return sha256(renderedContents) !== managedFile.sha256;
170
184
  }
@@ -1 +1 @@
1
- export { extractProjectOverview, getTemplateVersion, readTemplate, readTemplateManifest, renderSkillRouting, renderTemplateForSync, } from './templates.js';
1
+ export { extractProjectOverview, getTemplateVersion, readTemplate, readTemplateManifest, renderMcpConfig, renderSkillRouting, renderTemplateForSync, } from './templates.js';
@@ -18,22 +18,38 @@ export function getTemplateVersion(manifest, templateRelativePath) {
18
18
  }
19
19
  return template.version;
20
20
  }
21
- export function renderTemplateForSync({ templateRelativePath, currentPath, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = [], }) {
21
+ export function renderTemplateForSync({ templateRelativePath, currentPath, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = [], selectedDatabaseSkills = [], selectedMcpServers = [], }) {
22
22
  let contents = readTemplate(templateRelativePath);
23
+ const mcpConfigAgentId = getMcpConfigAgentId(templateRelativePath);
24
+ if (mcpConfigAgentId) {
25
+ return renderMcpConfig({ agentId: mcpConfigAgentId, baseContents: contents, selectedMcpServers });
26
+ }
23
27
  if (contents.includes('{{PROJECT_OVERVIEW}}')) {
24
28
  contents = contents.replace('{{PROJECT_OVERVIEW}}', extractProjectOverview(currentPath) ?? 'Describe this project.');
25
29
  }
26
- return renderSkillRouting(contents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills);
30
+ return renderSkillRouting(contents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, selectedDatabaseSkills);
27
31
  }
28
- export function renderSkillRouting(contents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = []) {
32
+ export function renderSkillRouting(contents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = [], selectedDatabaseSkills = []) {
29
33
  if (!contents.includes('{{OPTIONAL_SKILL_ROUTING}}')) {
30
34
  return contents;
31
35
  }
32
- const optionalRouting = [...selectedLanguageSkills, ...selectedFrameworkSkills, ...(selectedArchitectureSkill ? [selectedArchitectureSkill] : []), ...selectedWorkflowSkills]
36
+ const optionalRouting = [...selectedLanguageSkills, ...selectedFrameworkSkills, ...selectedDatabaseSkills, ...(selectedArchitectureSkill ? [selectedArchitectureSkill] : []), ...selectedWorkflowSkills]
33
37
  .map((skill) => `- ${skill.routingInstruction}`)
34
38
  .join('\n');
35
39
  return contents.replace('{{OPTIONAL_SKILL_ROUTING}}', optionalRouting);
36
40
  }
41
+ export function renderMcpConfig({ agentId, baseContents, selectedMcpServers, }) {
42
+ const githubServer = selectedMcpServers.find((server) => server.id === 'github');
43
+ if (!githubServer) {
44
+ return baseContents ?? renderJsonMcpConfig({});
45
+ }
46
+ if (agentId === 'codex') {
47
+ return renderCodexGithubMcpConfig(baseContents ?? '');
48
+ }
49
+ return renderJsonMcpConfig({
50
+ github: buildGithubMcpServerConfig(agentId),
51
+ });
52
+ }
37
53
  export function extractProjectOverview(filePath) {
38
54
  if (!fs.existsSync(filePath)) {
39
55
  return undefined;
@@ -43,6 +59,45 @@ export function extractProjectOverview(filePath) {
43
59
  const overview = match?.[1]?.trim();
44
60
  return overview || undefined;
45
61
  }
62
+ function renderCodexGithubMcpConfig(baseContents) {
63
+ const trimmedBaseContents = baseContents.trimEnd();
64
+ const separator = trimmedBaseContents ? '\n\n' : '';
65
+ return `${trimmedBaseContents}${separator}[mcp_servers.github]
66
+ url = "https://api.githubcopilot.com/mcp/"
67
+ bearer_token_env_var = "GITHUB_PERSONAL_ACCESS_TOKEN"
68
+ `;
69
+ }
70
+ function getMcpConfigAgentId(templateRelativePath) {
71
+ if (templateRelativePath === '.codex/config.toml') {
72
+ return 'codex';
73
+ }
74
+ if (templateRelativePath === 'mcp/claude/.mcp.json') {
75
+ return 'claude';
76
+ }
77
+ if (templateRelativePath === 'mcp/gemini/settings.json') {
78
+ return 'gemini';
79
+ }
80
+ return undefined;
81
+ }
82
+ function buildGithubMcpServerConfig(agentId) {
83
+ const headers = {
84
+ Authorization: 'Bearer ${GITHUB_PERSONAL_ACCESS_TOKEN}',
85
+ };
86
+ if (agentId === 'gemini') {
87
+ return {
88
+ httpUrl: 'https://api.githubcopilot.com/mcp/',
89
+ headers,
90
+ };
91
+ }
92
+ return {
93
+ type: 'http',
94
+ url: 'https://api.githubcopilot.com/mcp/',
95
+ headers,
96
+ };
97
+ }
98
+ function renderJsonMcpConfig(mcpServers) {
99
+ return `${JSON.stringify({ mcpServers }, null, 2)}\n`;
100
+ }
46
101
  function isTemplateManifest(value) {
47
102
  if (!value || typeof value !== 'object') {
48
103
  return false;
@@ -3,14 +3,14 @@ import path from 'node:path';
3
3
  import { intro, log, outro } from '@clack/prompts';
4
4
  import chalk from 'chalk';
5
5
  import { STATE_RELATIVE_PATH } from '../../shared/catalog.js';
6
- import { SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_SKILLS, SUPPORTED_AGENTS } from '../workflow-target-planning/index.js';
6
+ import { SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_DATABASE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_SKILLS, SELECTABLE_MCP_SERVERS, SUPPORTED_AGENTS } from '../workflow-target-planning/index.js';
7
7
  import { sha256 } from '../../shared/hash.js';
8
8
  import { checkForLatestSibuVersion } from '../version-advisory/index.js';
9
9
  import { getProjectContext } from '../../shared/paths.js';
10
10
  import { renderIntro } from '../interactive-guidance/index.js';
11
11
  import { hasReviewedTemplateVersion, readStateForDoctor } from '../workflow-state-registry/index.js';
12
12
  import { getTemplateVersion, readTemplateManifest } from '../template-catalog-rendering/index.js';
13
- import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getWorkflowTargets, } from '../workflow-target-planning/index.js';
13
+ import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedDatabaseSkillsFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedMcpServersFromState, getSelectedWorkflowSkillsFromState, getWorkflowTargets, } from '../workflow-target-planning/index.js';
14
14
  export async function handleDoctorProject(_command) {
15
15
  await renderIntro();
16
16
  intro(chalk.cyan('Checking workflow state'));
@@ -117,6 +117,16 @@ function addUnsupportedSelectionIssues(state, issues) {
117
117
  if (state.selectedArchitectureSkill && !SELECTABLE_ARCHITECTURE_SKILLS.some((skill) => skill.id === state.selectedArchitectureSkill)) {
118
118
  issues.push({ severity: 'warning', message: `State references unsupported architecture skill: ${state.selectedArchitectureSkill}.` });
119
119
  }
120
+ for (const selectedDatabaseSkill of state.selectedDatabaseSkills ?? []) {
121
+ if (!SELECTABLE_DATABASE_SKILLS.some((skill) => skill.id === selectedDatabaseSkill)) {
122
+ issues.push({ severity: 'warning', message: `State references unsupported database skill: ${selectedDatabaseSkill}.` });
123
+ }
124
+ }
125
+ for (const selectedMcpServer of state.selectedMcpServers ?? []) {
126
+ if (!SELECTABLE_MCP_SERVERS.some((server) => server.id === selectedMcpServer)) {
127
+ issues.push({ severity: 'warning', message: `State references unsupported MCP server: ${selectedMcpServer}.` });
128
+ }
129
+ }
120
130
  for (const reviewedArchitectureSkill of state.reviewedArchitectureSkills ?? []) {
121
131
  if (!SELECTABLE_ARCHITECTURE_SKILLS.some((skill) => skill.id === reviewedArchitectureSkill)) {
122
132
  issues.push({ severity: 'warning', message: `State references unsupported reviewed architecture skill: ${reviewedArchitectureSkill}.` });
@@ -125,7 +135,7 @@ function addUnsupportedSelectionIssues(state, issues) {
125
135
  }
126
136
  function addExpectedTargetIssues(rootPath, state, issues) {
127
137
  const reportedMissingPaths = new Set();
128
- const expectedTargets = getWorkflowTargets(rootPath, getSelectedAgentsFromState(state), getSelectedLanguageSkillsFromState(state), getSelectedFrameworkSkillsFromState(state), getSelectedArchitectureSkillFromState(state));
138
+ const expectedTargets = getWorkflowTargets(rootPath, getSelectedAgentsFromState(state), getSelectedLanguageSkillsFromState(state), getSelectedFrameworkSkillsFromState(state), getSelectedArchitectureSkillFromState(state), getSelectedWorkflowSkillsFromState(state), getSelectedDatabaseSkillsFromState(state), getSelectedMcpServersFromState(state));
129
139
  for (const target of expectedTargets) {
130
140
  const relativePath = path.relative(rootPath, target.targetPath);
131
141
  const managedFile = state.managedFiles[relativePath];
@@ -57,6 +57,10 @@ function isSibuState(value) {
57
57
  (state.selectedArchitectureSkill === undefined || typeof state.selectedArchitectureSkill === 'string') &&
58
58
  (state.selectedWorkflowSkills === undefined ||
59
59
  (Array.isArray(state.selectedWorkflowSkills) && state.selectedWorkflowSkills.every((skill) => typeof skill === 'string'))) &&
60
+ (state.selectedDatabaseSkills === undefined ||
61
+ (Array.isArray(state.selectedDatabaseSkills) && state.selectedDatabaseSkills.every((skill) => typeof skill === 'string'))) &&
62
+ (state.selectedMcpServers === undefined ||
63
+ (Array.isArray(state.selectedMcpServers) && state.selectedMcpServers.every((server) => typeof server === 'string'))) &&
60
64
  (state.reviewedArchitectureSkills === undefined ||
61
65
  (Array.isArray(state.reviewedArchitectureSkills) && state.reviewedArchitectureSkills.every((skill) => typeof skill === 'string'))) &&
62
66
  !!state.managedFiles &&
@@ -100,6 +100,21 @@ export const SELECTABLE_LANGUAGE_SKILLS = [
100
100
  },
101
101
  },
102
102
  ];
103
+ export const SELECTABLE_DATABASE_SKILLS = [
104
+ {
105
+ id: 'postgresql-expert',
106
+ name: 'PostgreSQL Expert',
107
+ description: 'Install practical guidance for PostgreSQL schema design, migrations, constraints, queries, indexing, and database tradeoffs',
108
+ routingInstruction: 'For PostgreSQL schema design, migrations, constraints, queries, indexing, or database tradeoffs, use `postgresql-expert`.',
109
+ templateRelativePath: 'skills/postgresql-expert/SKILL.md',
110
+ targetRelativePathsByAgent: {
111
+ codex: '.agents/skills/postgresql-expert/SKILL.md',
112
+ gemini: '.agents/skills/postgresql-expert/SKILL.md',
113
+ claude: '.agents/skills/postgresql-expert/SKILL.md',
114
+ windsurf: '.agents/skills/postgresql-expert/SKILL.md',
115
+ },
116
+ },
117
+ ];
103
118
  export const SELECTABLE_FRAMEWORK_SKILLS = [
104
119
  {
105
120
  id: 'react',
@@ -184,6 +199,14 @@ export const SELECTABLE_WORKFLOW_SKILLS = [
184
199
  },
185
200
  },
186
201
  ];
202
+ export const SELECTABLE_MCP_SERVERS = [
203
+ {
204
+ id: 'github',
205
+ name: 'GitHub MCP Server',
206
+ description: "Configure GitHub's official MCP server; Sibu writes config only, while prerequisites, runtime availability, credentials, and authentication remain user-owned",
207
+ source: 'github/github-mcp-server',
208
+ },
209
+ ];
187
210
  export const SUPPORTED_AGENTS = [
188
211
  {
189
212
  id: 'codex',
@@ -212,6 +235,13 @@ export const SUPPORTED_AGENTS = [
212
235
  description: 'Use root AGENTS.md and shared .agents/skills/ discovery',
213
236
  },
214
237
  ];
238
+ export function resolveSelectableMcpServerById(serverId) {
239
+ const server = SELECTABLE_MCP_SERVERS.find((selectableServer) => selectableServer.id === serverId);
240
+ if (server) {
241
+ return { ok: true, resolved: { server } };
242
+ }
243
+ return { ok: false, message: `Unknown MCP server \`${serverId}\`. Run \`sibu mcp list\` to see available MCP servers.` };
244
+ }
215
245
  export function resolveSelectableSkillById(skillId) {
216
246
  const languageSkill = SELECTABLE_LANGUAGE_SKILLS.find((skill) => skill.id === skillId);
217
247
  if (languageSkill) {
@@ -221,6 +251,10 @@ export function resolveSelectableSkillById(skillId) {
221
251
  if (frameworkSkill) {
222
252
  return { ok: true, resolved: { kind: 'framework', skill: frameworkSkill } };
223
253
  }
254
+ const databaseSkill = SELECTABLE_DATABASE_SKILLS.find((skill) => skill.id === skillId);
255
+ if (databaseSkill) {
256
+ return { ok: true, resolved: { kind: 'database', skill: databaseSkill } };
257
+ }
224
258
  const architectureSkill = SELECTABLE_ARCHITECTURE_SKILLS.find((skill) => skill.id === skillId);
225
259
  if (architectureSkill) {
226
260
  return { ok: true, resolved: { kind: 'architecture', skill: architectureSkill } };
@@ -1,2 +1,2 @@
1
- export { MANDATORY_SKILLS, SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_SKILLS, SELECTABLE_WORKFLOW_SKILLS, SUPPORTED_AGENTS, resolveSelectableSkillById, } from './catalog.js';
2
- export { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedSkillTargetsForAgents, getSelectedWorkflowSkillsFromState, getWorkflowTargets, renderMissingWorkflowFiles, writeSibuState, } from './workflow-targets.js';
1
+ export { MANDATORY_SKILLS, SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_DATABASE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_SKILLS, SELECTABLE_MCP_SERVERS, SELECTABLE_WORKFLOW_SKILLS, SUPPORTED_AGENTS, resolveSelectableMcpServerById, resolveSelectableSkillById, } from './catalog.js';
2
+ export { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedDatabaseSkillsFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedMcpServersFromState, getSelectedMcpTargetsForAgents, getSelectedSkillTargetsForAgents, getSelectedWorkflowSkillsFromState, getWorkflowTargets, renderMissingWorkflowFiles, writeSibuState, } from './workflow-targets.js';