@juancr11/sibu 0.15.1 → 0.17.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 (42) hide show
  1. package/bin/modules/skill-selection-management/stop-managing-file/handler.js +2 -8
  2. package/bin/modules/sync-review/action-prompt.js +14 -1
  3. package/bin/modules/sync-review/handler.js +54 -7
  4. package/bin/modules/sync-review/index.js +1 -0
  5. package/bin/modules/sync-review/sync-preview.js +4 -4
  6. package/bin/modules/sync-review/unsupported-agent-cleanup.js +57 -0
  7. package/bin/modules/template-catalog-rendering/index.js +1 -1
  8. package/bin/modules/template-catalog-rendering/templates.js +83 -4
  9. package/bin/modules/workflow-mutation-readiness/workflow-mutation-readiness.js +9 -0
  10. package/bin/modules/workflow-target-planning/catalog.js +140 -26
  11. package/bin/modules/workflow-target-planning/index.js +2 -2
  12. package/bin/modules/workflow-target-planning/workflow-targets.js +42 -9
  13. package/package.json +1 -1
  14. package/templates/.claude/agents/github-exporter.md +18 -0
  15. package/templates/.claude/agents/notion-exporter.md +18 -0
  16. package/templates/.claude/agents/sibu-implementation-executor.md +30 -0
  17. package/templates/.claude/agents/sibu-implementation-planner.md +30 -0
  18. package/templates/.claude/settings.json +16 -0
  19. package/templates/.codex/agents/github-exporter.toml +20 -0
  20. package/templates/.codex/agents/notion-exporter.toml +20 -0
  21. package/templates/.codex/agents/sibu-implementation-executor.toml +30 -0
  22. package/templates/.codex/agents/sibu-implementation-planner.toml +30 -0
  23. package/templates/.codex/hooks.json +17 -0
  24. package/templates/.gemini/agents/github-exporter.md +18 -0
  25. package/templates/.gemini/agents/notion-exporter.md +18 -0
  26. package/templates/.gemini/agents/sibu-implementation-executor.md +30 -0
  27. package/templates/.gemini/agents/sibu-implementation-planner.md +30 -0
  28. package/templates/.gemini/settings.json +17 -0
  29. package/templates/AGENTS.md +5 -5
  30. package/templates/manifest.json +162 -22
  31. package/templates/skills/ai-implementation-executor-toolbox/SKILL.md +78 -0
  32. package/templates/skills/ai-implementation-plan-executor/SKILL.md +64 -108
  33. package/templates/skills/ai-implementation-planner/SKILL.md +55 -138
  34. package/templates/skills/ai-implementation-planner-toolbox/SKILL.md +78 -0
  35. package/templates/skills/business-domain-model-writer/SKILL.md +313 -0
  36. package/templates/skills/capabilities-map-writer/SKILL.md +271 -0
  37. package/templates/skills/deep-module-map-writer/SKILL.md +50 -14
  38. package/templates/skills/export-to-github/SKILL.md +36 -1
  39. package/templates/skills/export-to-notion/SKILL.md +36 -1
  40. package/templates/skills/feature-brief-writer/SKILL.md +72 -32
  41. package/templates/skills/technical-design-writer/SKILL.md +10 -10
  42. package/templates/skills/ux-expert/SKILL.md +62 -17
@@ -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, getSelectedDatabaseSkillsFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedWorkflowSkillsFromState, } from '../../workflow-target-planning/index.js';
13
+ import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedDatabaseSkillsFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedWorkflowSkillsFromState, getSkillTargetsForAgents, } 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'));
@@ -136,13 +136,7 @@ function removeSelectedSkill(state, resolved) {
136
136
  }
137
137
  }
138
138
  function getSkillManagedPaths(rootPath, state, resolved) {
139
- const relativePaths = new Set();
140
- for (const agent of getSelectedAgentsFromState(state)) {
141
- const relativePath = resolved.skill.targetRelativePathsByAgent[agent.id];
142
- if (relativePath) {
143
- relativePaths.add(relativePath);
144
- }
145
- }
139
+ const relativePaths = new Set(getSkillTargetsForAgents(resolved.skill, getSelectedAgentsFromState(state)).map((target) => target.targetRelativePath));
146
140
  return [...relativePaths].map((relativePath) => ({
147
141
  relativePath,
148
142
  absolutePath: path.join(rootPath, relativePath),
@@ -1,4 +1,4 @@
1
- import { cancel, isCancel, select } from '@clack/prompts';
1
+ import { cancel, confirm, isCancel, select } from '@clack/prompts';
2
2
  export async function askForSyncAction(preview) {
3
3
  if (preview.status === 'update-available') {
4
4
  const action = await select({
@@ -56,6 +56,19 @@ export async function askForSyncAction(preview) {
56
56
  });
57
57
  return handleSyncActionCancel(action);
58
58
  }
59
+ export async function askForUnsupportedAgentCleanup(plan) {
60
+ const shouldCleanUp = await confirm({
61
+ message: plan.removesSibuState
62
+ ? 'Remove unsupported agent state and all Sibu-managed workflow files?'
63
+ : 'Remove unsupported agent selections and obsolete Sibu-managed files?',
64
+ initialValue: true,
65
+ });
66
+ if (isCancel(shouldCleanUp)) {
67
+ cancel('Sync cancelled.');
68
+ process.exit(0);
69
+ }
70
+ return shouldCleanUp;
71
+ }
59
72
  function handleSyncActionCancel(action) {
60
73
  if (isCancel(action)) {
61
74
  cancel('Sync cancelled.');
@@ -5,12 +5,22 @@ import { getProjectContext } from '../../shared/paths.js';
5
5
  import { askForMissingFrameworkSkills, askForNewArchitectureSkill, askForNewLanguageSkills, renderIntro } from '../interactive-guidance/index.js';
6
6
  import { readStateForDoctor, writeStateFile } from '../workflow-state-registry/index.js';
7
7
  import { readTemplateManifest } from '../template-catalog-rendering/index.js';
8
- import { askForSyncAction } from './action-prompt.js';
8
+ import { askForSyncAction, askForUnsupportedAgentCleanup } from './action-prompt.js';
9
9
  import { applySyncAction } from './apply-action.js';
10
10
  import { logSyncPreview } from './log-preview.js';
11
11
  import { getSyncPreviews, isActionableSyncPreview, shouldAskForSyncAction } from './sync-preview.js';
12
- export async function handleSyncProject(_command) {
13
- await renderIntro();
12
+ import { applyUnsupportedAgentCleanup, getUnsupportedAgentCleanupPlan } from './unsupported-agent-cleanup.js';
13
+ const defaultSyncProjectDependencies = {
14
+ renderIntro,
15
+ askForUnsupportedAgentCleanup,
16
+ askForNewLanguageSkills,
17
+ askForMissingFrameworkSkills,
18
+ askForNewArchitectureSkill,
19
+ askForSyncAction,
20
+ };
21
+ export async function handleSyncProject(_command, dependencies = {}) {
22
+ const syncDependencies = { ...defaultSyncProjectDependencies, ...dependencies };
23
+ await syncDependencies.renderIntro();
14
24
  intro(chalk.cyan('Reviewing workflow updates'));
15
25
  const { rootPath, statePath } = getProjectContext();
16
26
  const stateResult = readStateForDoctor(statePath);
@@ -21,9 +31,46 @@ export async function handleSyncProject(_command) {
21
31
  process.exitCode = 1;
22
32
  return;
23
33
  }
24
- const languageSkillSelection = await askForNewLanguageSkills(stateResult.state);
25
- const frameworkSkillSelection = await askForMissingFrameworkSkills(languageSkillSelection.state);
26
- const architectureSkillSelection = await askForNewArchitectureSkill(frameworkSkillSelection.state);
34
+ const cleanupPlan = getUnsupportedAgentCleanupPlan({ rootPath, state: stateResult.state });
35
+ if (cleanupPlan) {
36
+ log.warn('This project has agent selections that are no longer supported by Sibu.');
37
+ log.info(`Unsupported selections: ${cleanupPlan.unsupportedAgentIds.join(', ')}`);
38
+ if (cleanupPlan.filePathsToDelete.length > 0) {
39
+ log.info('Sibu-managed files to remove:');
40
+ for (const relativePath of cleanupPlan.filePathsToDelete) {
41
+ log.info(`- ${relativePath}`);
42
+ }
43
+ }
44
+ else {
45
+ log.info('No Sibu-managed files need to be deleted for this cleanup.');
46
+ }
47
+ if (cleanupPlan.removesSibuState) {
48
+ log.warn(`No supported agents will remain, so ${STATE_RELATIVE_PATH} will be removed after cleanup.`);
49
+ }
50
+ const shouldCleanUp = await syncDependencies.askForUnsupportedAgentCleanup(cleanupPlan);
51
+ if (!shouldCleanUp) {
52
+ log.warn('Unsupported agent cleanup was skipped.');
53
+ log.info('Run `sibu sync` again and accept cleanup before reviewing other workflow updates.');
54
+ outro(chalk.yellow('Sync stopped.'));
55
+ process.exitCode = 1;
56
+ return;
57
+ }
58
+ const cleanupResult = applyUnsupportedAgentCleanup({ rootPath, statePath, state: stateResult.state, plan: cleanupPlan });
59
+ for (const relativePath of cleanupResult.removedFiles) {
60
+ log.success(`Removed ${relativePath}`);
61
+ }
62
+ if (cleanupResult.removedStateFile) {
63
+ log.success(`Removed ${STATE_RELATIVE_PATH}`);
64
+ outro(chalk.green('Unsupported agent cleanup complete.'));
65
+ return;
66
+ }
67
+ writeStateFile(statePath, cleanupResult.state);
68
+ log.success(`Updated ${STATE_RELATIVE_PATH}`);
69
+ stateResult.state = cleanupResult.state;
70
+ }
71
+ const languageSkillSelection = await syncDependencies.askForNewLanguageSkills(stateResult.state);
72
+ const frameworkSkillSelection = await syncDependencies.askForMissingFrameworkSkills(languageSkillSelection.state);
73
+ const architectureSkillSelection = await syncDependencies.askForNewArchitectureSkill(frameworkSkillSelection.state);
27
74
  let state = architectureSkillSelection.state;
28
75
  const manifest = readTemplateManifest();
29
76
  const previews = getSyncPreviews({ rootPath, state, manifest });
@@ -56,7 +103,7 @@ export async function handleSyncProject(_command) {
56
103
  if (!shouldAskForSyncAction(preview)) {
57
104
  continue;
58
105
  }
59
- const action = await askForSyncAction(preview);
106
+ const action = await syncDependencies.askForSyncAction(preview);
60
107
  if (action === 'skip') {
61
108
  log.info(`Skipped ${preview.relativePath}.`);
62
109
  continue;
@@ -3,3 +3,4 @@ export { applySyncAction } from './apply-action.js';
3
3
  export { handleSyncProject } from './handler.js';
4
4
  export { logSyncPreview } from './log-preview.js';
5
5
  export { getSyncPreviews, isActionableSyncPreview, shouldAskForSyncAction } from './sync-preview.js';
6
+ export { applyUnsupportedAgentCleanup, getUnsupportedAgentCleanupPlan } from './unsupported-agent-cleanup.js';
@@ -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, getSelectedDatabaseSkillsFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedMcpServersFromState, getSelectedWorkflowSkillsFromState, getWorkflowSkillsImpliedByMcpServers, getWorkflowTargets, } from '../workflow-target-planning/index.js';
6
+ import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedDatabaseSkillsFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedMcpServersFromState, getSelectedWorkflowSkillsFromState, getSkillTargetsForAgents, getWorkflowSkillsImpliedByMcpServers, getWorkflowTargets, } from '../workflow-target-planning/index.js';
7
7
  const ACTIONABLE_SYNC_PREVIEW_STATUSES = new Set([
8
8
  'new-template',
9
9
  'missing',
@@ -39,7 +39,7 @@ export function getSyncPreviews({ rootPath, state, manifest }) {
39
39
  const expectedTargets = getWorkflowTargets(rootPath, getSelectedAgentsFromState(state), selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, plannedWorkflowSkills, selectedDatabaseSkills, selectedMcpServers);
40
40
  const alreadyPreviewedPaths = new Set();
41
41
  for (const skill of missingImpliedWorkflowSkills) {
42
- for (const preview of getNewExpectedTargetPreviews({ rootPath, manifest, state, expectedTargets, workflowSkill: skill })) {
42
+ for (const preview of getNewExpectedTargetPreviews({ rootPath, manifest, state, expectedTargets, selectedAgents: getSelectedAgentsFromState(state), workflowSkill: skill })) {
43
43
  previews.push(preview);
44
44
  alreadyPreviewedPaths.add(preview.relativePath);
45
45
  }
@@ -65,8 +65,8 @@ export function getSyncPreviews({ rootPath, state, manifest }) {
65
65
  }
66
66
  return previews;
67
67
  }
68
- function getNewExpectedTargetPreviews({ rootPath, manifest, state, expectedTargets, workflowSkill, }) {
69
- const workflowSkillTargetPaths = new Set(Object.values(workflowSkill.targetRelativePathsByAgent));
68
+ function getNewExpectedTargetPreviews({ rootPath, manifest, state, expectedTargets, selectedAgents, workflowSkill, }) {
69
+ const workflowSkillTargetPaths = new Set(getSkillTargetsForAgents(workflowSkill, selectedAgents).map((target) => target.targetRelativePath));
70
70
  return expectedTargets
71
71
  .filter((target) => workflowSkillTargetPaths.has(path.relative(rootPath, target.targetPath)))
72
72
  .filter((target) => !state.managedFiles[path.relative(rootPath, target.targetPath)])
@@ -0,0 +1,57 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedDatabaseSkillsFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedMcpServersFromState, getSelectedWorkflowSkillsFromState, getWorkflowTargets, SUPPORTED_AGENTS, } from '../workflow-target-planning/index.js';
4
+ import { cloneState } from '../workflow-state-registry/index.js';
5
+ export function getUnsupportedAgentCleanupPlan({ rootPath, state }) {
6
+ const supportedAgentIds = new Set(SUPPORTED_AGENTS.map((agent) => agent.id));
7
+ const unsupportedAgentIds = state.selectedAgents.filter((agentId) => !supportedAgentIds.has(agentId));
8
+ if (unsupportedAgentIds.length === 0) {
9
+ return undefined;
10
+ }
11
+ const remainingSupportedAgents = getSelectedAgentsFromState(state);
12
+ const obsoleteManagedFilePaths = getObsoleteManagedFilePaths({ rootPath, state, remainingSupportedAgents });
13
+ const filePathsToDelete = obsoleteManagedFilePaths.filter((relativePath) => state.managedFiles[relativePath]?.status !== 'unmanaged');
14
+ return {
15
+ unsupportedAgentIds,
16
+ remainingSupportedAgents,
17
+ obsoleteManagedFilePaths,
18
+ filePathsToDelete,
19
+ removesSibuState: remainingSupportedAgents.length === 0,
20
+ };
21
+ }
22
+ export function applyUnsupportedAgentCleanup({ rootPath, statePath, state, plan, }) {
23
+ const removedFiles = [];
24
+ for (const relativePath of plan.filePathsToDelete) {
25
+ const targetPath = path.join(rootPath, relativePath);
26
+ if (!fs.existsSync(targetPath)) {
27
+ continue;
28
+ }
29
+ fs.rmSync(targetPath, { recursive: true, force: true });
30
+ removedFiles.push(relativePath);
31
+ }
32
+ if (plan.removesSibuState) {
33
+ fs.rmSync(statePath, { force: true });
34
+ return { removedStateFile: true, removedFiles };
35
+ }
36
+ const unsupportedAgentIds = new Set(plan.unsupportedAgentIds);
37
+ const obsoleteManagedFilePaths = new Set(plan.obsoleteManagedFilePaths);
38
+ const nextState = cloneState(state);
39
+ nextState.selectedAgents = nextState.selectedAgents.filter((agentId) => !unsupportedAgentIds.has(agentId));
40
+ nextState.updatedAt = new Date().toISOString();
41
+ for (const relativePath of obsoleteManagedFilePaths) {
42
+ delete nextState.managedFiles[relativePath];
43
+ }
44
+ return {
45
+ removedStateFile: false,
46
+ removedFiles,
47
+ state: nextState,
48
+ };
49
+ }
50
+ function getObsoleteManagedFilePaths({ rootPath, state, remainingSupportedAgents, }) {
51
+ if (remainingSupportedAgents.length === 0) {
52
+ return Object.keys(state.managedFiles);
53
+ }
54
+ const expectedTargets = getWorkflowTargets(rootPath, remainingSupportedAgents, getSelectedLanguageSkillsFromState(state), getSelectedFrameworkSkillsFromState(state), getSelectedArchitectureSkillFromState(state), getSelectedWorkflowSkillsFromState(state), getSelectedDatabaseSkillsFromState(state), getSelectedMcpServersFromState(state));
55
+ const expectedManagedFilePaths = new Set(expectedTargets.map((target) => path.relative(rootPath, target.targetPath)));
56
+ return Object.keys(state.managedFiles).filter((relativePath) => !expectedManagedFilePaths.has(relativePath));
57
+ }
@@ -1 +1 @@
1
- export { extractProjectOverview, getTemplateVersion, readTemplate, readTemplateManifest, renderMcpConfig, renderSkillRouting, renderTemplateForSync, } from './templates.js';
1
+ export { extractProjectOverview, getTemplateVersion, readTemplate, readTemplateManifest, renderMcpConfig, renderSkillRouting, renderTemplateForSync, renderWorkerToolboxRoutingPlaceholders, renderWorkerToolboxRouting, } from './templates.js';
@@ -27,7 +27,9 @@ export function renderTemplateForSync({ templateRelativePath, currentPath, selec
27
27
  if (contents.includes('{{PROJECT_OVERVIEW}}')) {
28
28
  contents = contents.replace('{{PROJECT_OVERVIEW}}', extractProjectOverview(currentPath) ?? 'Describe this project.');
29
29
  }
30
- return renderSkillRouting(contents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, selectedDatabaseSkills);
30
+ contents = renderSkillRouting(contents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, selectedDatabaseSkills);
31
+ contents = renderWorkerToolboxRoutingPlaceholders(contents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, selectedDatabaseSkills);
32
+ return contents;
31
33
  }
32
34
  export function renderSkillRouting(contents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = [], selectedDatabaseSkills = []) {
33
35
  if (!contents.includes('{{OPTIONAL_SKILL_ROUTING}}')) {
@@ -38,6 +40,51 @@ export function renderSkillRouting(contents, selectedLanguageSkills, selectedFra
38
40
  .join('\n');
39
41
  return contents.replace('{{OPTIONAL_SKILL_ROUTING}}', optionalRouting);
40
42
  }
43
+ export function renderWorkerToolboxRouting({ profile, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = [], selectedDatabaseSkills = [], }) {
44
+ const selectedSkills = collectWorkerRelevantSkills({
45
+ selectedLanguageSkills,
46
+ selectedFrameworkSkills,
47
+ selectedArchitectureSkill,
48
+ selectedWorkflowSkills,
49
+ selectedDatabaseSkills,
50
+ });
51
+ const selectedSkillLines = selectedSkills.map((skill) => `- ${skill.name}: read \`${getSharedSkillTargetPath(skill)}\` when relevant. ${skill.routingInstruction}`);
52
+ const selectedSkillsSection = selectedSkillLines.length > 0
53
+ ? selectedSkillLines.join('\n')
54
+ : '- No optional implementation-relevant Sibu skills are selected for this project. Use source artifacts and flag unmapped patterns as risks instead of guessing silently.';
55
+ return `## Focused ${profile} worker routing
56
+
57
+ - Always read and apply \`.agents/skills/clean-code/SKILL.md\` before ${profile === 'planner' ? 'writing implementation plan steps' : 'editing code or running story execution'}.
58
+ - Read the worker toolbox skill path provided in the main-agent packet before doing work.
59
+ - Read every required skill path listed in the packet. If a required skill path is missing, stop and report the blocker to the main agent.
60
+ - Read optional installed skill paths only when they are relevant to the story, touched files, source artifacts, or validation work.
61
+ - Treat distilled skill constraints from the packet as binding task constraints.
62
+ - If an optional relevant skill is not installed and you encounter an unmapped language, framework, database, or architecture pattern, do not guess silently; continue only when safe and flag the gap as a ${profile === 'planner' ? 'plan risk' : 'Review Gate risk'}.
63
+
64
+ ### Optional installed skills relevant to ${profile} work
65
+ ${selectedSkillsSection}`;
66
+ }
67
+ export function renderWorkerToolboxRoutingPlaceholders(contents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = [], selectedDatabaseSkills = []) {
68
+ const placeholderProfiles = [
69
+ { placeholder: '{{PLANNER_WORKER_ROUTING}}', profile: 'planner' },
70
+ { placeholder: '{{EXECUTOR_WORKER_ROUTING}}', profile: 'executor' },
71
+ ];
72
+ let renderedContents = contents;
73
+ for (const { placeholder, profile } of placeholderProfiles) {
74
+ if (!renderedContents.includes(placeholder)) {
75
+ continue;
76
+ }
77
+ renderedContents = renderedContents.replace(placeholder, renderWorkerToolboxRouting({
78
+ profile,
79
+ selectedLanguageSkills,
80
+ selectedFrameworkSkills,
81
+ selectedArchitectureSkill,
82
+ selectedWorkflowSkills,
83
+ selectedDatabaseSkills,
84
+ }));
85
+ }
86
+ return renderedContents;
87
+ }
41
88
  export function renderMcpConfig({ agentId, baseContents, selectedMcpServers, }) {
42
89
  if (selectedMcpServers.length === 0) {
43
90
  return baseContents ?? renderJsonMcpConfig({});
@@ -45,7 +92,24 @@ export function renderMcpConfig({ agentId, baseContents, selectedMcpServers, })
45
92
  if (agentId === 'codex') {
46
93
  return renderCodexMcpConfig(baseContents ?? '', selectedMcpServers);
47
94
  }
48
- return renderJsonMcpConfig(buildJsonMcpServerConfigs(agentId, selectedMcpServers));
95
+ return renderJsonMcpConfig(buildJsonMcpServerConfigs(agentId, selectedMcpServers), baseContents);
96
+ }
97
+ function collectWorkerRelevantSkills({ selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, selectedDatabaseSkills, }) {
98
+ const implementationRelevantWorkflowSkills = selectedWorkflowSkills.filter((skill) => skill.id === 'ai-prompt-engineer-master' || skill.id === 'ux-expert');
99
+ return [
100
+ ...selectedLanguageSkills,
101
+ ...selectedFrameworkSkills,
102
+ ...selectedDatabaseSkills,
103
+ ...(selectedArchitectureSkill ? [selectedArchitectureSkill] : []),
104
+ ...implementationRelevantWorkflowSkills,
105
+ ];
106
+ }
107
+ function getSharedSkillTargetPath(skill) {
108
+ const sharedTargetPath = Object.values(skill.targetRelativePathsByAgent).find((targetPath) => targetPath !== undefined);
109
+ if (!sharedTargetPath) {
110
+ throw new Error(`Selectable skill ${skill.id} has no target path.`);
111
+ }
112
+ return sharedTargetPath;
49
113
  }
50
114
  export function extractProjectOverview(filePath) {
51
115
  if (!fs.existsSync(filePath)) {
@@ -93,6 +157,9 @@ function getMcpConfigAgentId(templateRelativePath) {
93
157
  if (templateRelativePath === 'mcp/gemini/settings.json') {
94
158
  return 'gemini';
95
159
  }
160
+ if (templateRelativePath === '.gemini/settings.json') {
161
+ return 'gemini';
162
+ }
96
163
  return undefined;
97
164
  }
98
165
  function buildJsonMcpServerConfigs(agentId, selectedMcpServers) {
@@ -128,8 +195,20 @@ function buildNotionMcpServerConfig() {
128
195
  url: 'https://mcp.notion.com/mcp',
129
196
  };
130
197
  }
131
- function renderJsonMcpConfig(mcpServers) {
132
- return `${JSON.stringify({ mcpServers }, null, 2)}\n`;
198
+ function renderJsonMcpConfig(mcpServers, baseContents) {
199
+ const baseConfig = parseJsonObject(baseContents);
200
+ baseConfig.mcpServers = mcpServers;
201
+ return `${JSON.stringify(baseConfig, null, 2)}\n`;
202
+ }
203
+ function parseJsonObject(contents) {
204
+ if (!contents?.trim()) {
205
+ return {};
206
+ }
207
+ const parsed = JSON.parse(contents);
208
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
209
+ throw new Error('Expected JSON template to contain an object.');
210
+ }
211
+ return parsed;
133
212
  }
134
213
  function isTemplateManifest(value) {
135
214
  if (!value || typeof value !== 'object') {
@@ -1,5 +1,6 @@
1
1
  import { readStateForDoctor } from '../workflow-state-registry/index.js';
2
2
  import { getSyncPreviews, isActionableSyncPreview } from '../sync-review/index.js';
3
+ import { getUnsupportedAgentCleanupPlan } from '../sync-review/unsupported-agent-cleanup.js';
3
4
  import { readTemplateManifest } from '../template-catalog-rendering/index.js';
4
5
  export function getWorkflowMutationReadiness({ rootPath, statePath }) {
5
6
  const stateResult = readStateForDoctor(statePath);
@@ -11,6 +12,14 @@ export function getWorkflowMutationReadiness({ rootPath, statePath }) {
11
12
  };
12
13
  }
13
14
  const manifest = readTemplateManifest();
15
+ const unsupportedAgentCleanupPlan = getUnsupportedAgentCleanupPlan({ rootPath, state: stateResult.state });
16
+ if (unsupportedAgentCleanupPlan) {
17
+ return {
18
+ ok: false,
19
+ message: 'Workflow state is not clean enough to select a skill safely.',
20
+ hint: 'Run `sibu sync` to review workflow state before selecting a skill.',
21
+ };
22
+ }
14
23
  const previews = getSyncPreviews({ rootPath, state: stateResult.state, manifest });
15
24
  const actionablePreviews = previews.filter(isActionableSyncPreview);
16
25
  if (actionablePreviews.length > 0) {