@juancr11/sibu 0.17.0 → 0.18.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.
- package/README.md +66 -29
- package/bin/entrypoints/cli/create-program.js +1 -1
- package/bin/entrypoints/cli/execute-command.js +4 -7
- package/bin/modules/agent-tool-configuration/index.js +1 -0
- package/bin/modules/agent-tool-configuration/mcp-config.js +134 -0
- package/bin/modules/interactive-guidance/prompts.js +1 -1
- package/bin/modules/mcp-server-selection-management/list-mcp-servers/handler.js +2 -2
- package/bin/modules/mcp-server-selection-management/stop-mcp-server/handler.js +5 -5
- package/bin/modules/mcp-server-selection-management/use-mcp-server/handler.js +4 -3
- package/bin/modules/skill-selection-management/list-skills/handler.js +2 -2
- package/bin/modules/skill-selection-management/stop-managing-file/handler.js +5 -4
- package/bin/modules/skill-selection-management/use-skill/handler.js +5 -4
- package/bin/modules/{sync-review → sync-review-orchestrator}/apply-action.js +4 -3
- package/bin/modules/{sync-review → sync-review-orchestrator}/handler.js +4 -4
- package/bin/modules/{sync-review → sync-review-orchestrator}/index.js +1 -0
- package/bin/modules/{sync-review → sync-review-orchestrator}/log-preview.js +1 -1
- package/bin/modules/{sync-review → sync-review-orchestrator}/sync-preview.js +3 -3
- package/bin/modules/{sync-review → sync-review-orchestrator}/unsupported-agent-cleanup.js +2 -2
- package/bin/modules/{workflow-mutation-readiness → sync-review-orchestrator}/workflow-mutation-readiness.js +4 -4
- package/bin/modules/{workflow-target-planning → template-catalog}/index.js +2 -1
- package/bin/modules/{template-catalog-rendering → template-catalog}/templates.js +42 -115
- package/bin/modules/workflow-configuration-manager/index.js +6 -0
- package/bin/modules/workflow-configuration-manager/list-mcp-servers/handler.js +46 -0
- package/bin/modules/workflow-configuration-manager/list-skills/command.js +1 -0
- package/bin/modules/workflow-configuration-manager/list-skills/handler.js +75 -0
- package/bin/modules/workflow-configuration-manager/stop-managing-file/command.js +1 -0
- package/bin/modules/workflow-configuration-manager/stop-managing-file/handler.js +221 -0
- package/bin/modules/workflow-configuration-manager/stop-mcp-server/command.js +1 -0
- package/bin/modules/workflow-configuration-manager/stop-mcp-server/handler.js +198 -0
- package/bin/modules/workflow-configuration-manager/use-mcp-server/command.js +1 -0
- package/bin/modules/workflow-configuration-manager/use-mcp-server/handler.js +193 -0
- package/bin/modules/workflow-configuration-manager/use-skill/command.js +1 -0
- package/bin/modules/workflow-configuration-manager/use-skill/handler.js +314 -0
- package/bin/modules/workflow-health-inspector/command.js +1 -0
- package/bin/modules/{workflow-health-diagnosis → workflow-health-inspector}/handler.js +7 -7
- package/bin/modules/workflow-installer/command.js +1 -0
- package/bin/modules/{project-adoption → workflow-installer}/handler.js +5 -4
- package/bin/modules/workflow-state-ledger/index.js +2 -0
- package/bin/modules/workflow-state-ledger/state-path.js +1 -0
- package/bin/modules/{workflow-state-registry → workflow-state-ledger}/state.js +40 -1
- package/bin/{modules/workflow-target-planning/workflow-targets.js → shared/expected-workflow-targets.js} +3 -104
- package/bin/shared/paths.js +1 -1
- package/bin/support/expected-workflow-targets.js +131 -0
- package/bin/support/interactive-guidance/index.js +1 -0
- package/bin/support/interactive-guidance/prompts.js +275 -0
- package/bin/support/version-advisory/index.js +1 -0
- package/bin/support/version-advisory/npm-version.js +205 -0
- package/package.json +2 -1
- package/templates/AGENTS.md +8 -0
- package/templates/manifest.json +5 -4
- package/bin/modules/template-catalog-rendering/index.js +0 -1
- package/bin/modules/workflow-mutation-readiness/index.js +0 -1
- package/bin/modules/workflow-state-registry/index.js +0 -1
- /package/bin/{modules/project-adoption/command.js → entrypoints/cli-command-surface/index.js} +0 -0
- /package/bin/modules/{sync-review → sync-review-orchestrator}/action-prompt.js +0 -0
- /package/bin/modules/{sync-review → sync-review-orchestrator}/command.js +0 -0
- /package/bin/modules/{workflow-target-planning → template-catalog}/catalog.js +0 -0
- /package/bin/modules/{workflow-health-diagnosis → workflow-configuration-manager/list-mcp-servers}/command.js +0 -0
- /package/bin/modules/{workflow-health-diagnosis → workflow-health-inspector}/index.js +0 -0
- /package/bin/modules/{project-adoption → workflow-installer}/index.js +0 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { writeSibuState } from '../../workflow-state-ledger/index.js';
|
|
4
|
+
import { log } from '@clack/prompts';
|
|
5
|
+
import { STATE_RELATIVE_PATH } from '../../workflow-state-ledger/state-path.js';
|
|
6
|
+
import { askForNotionDocsParentPage } from '../../../support/interactive-guidance/index.js';
|
|
7
|
+
import { getMcpServersRequiredByWorkflowSkills, resolveSelectableSkillById } from '../../template-catalog/index.js';
|
|
8
|
+
import { sha256 } from '../../../shared/hash.js';
|
|
9
|
+
import { getProjectContext } from '../../../shared/paths.js';
|
|
10
|
+
import { renderTemplateForSync } from '../../template-catalog/index.js';
|
|
11
|
+
import { getWorkflowMutationReadiness } from '../../sync-review-orchestrator/index.js';
|
|
12
|
+
import { getSelectedAgentsFromState, getSelectedMcpServersFromState, getWorkflowTargets, renderMissingWorkflowFiles, } from '../../template-catalog/index.js';
|
|
13
|
+
const defaultDependencies = {
|
|
14
|
+
askForNotionDocsParentPage,
|
|
15
|
+
};
|
|
16
|
+
export async function handleUseSkill(command, dependencies = defaultDependencies) {
|
|
17
|
+
const { rootPath, statePath } = getProjectContext();
|
|
18
|
+
const readiness = getWorkflowMutationReadiness({ rootPath, statePath });
|
|
19
|
+
if (!readiness.ok) {
|
|
20
|
+
log.error(readiness.message);
|
|
21
|
+
log.info(readiness.hint);
|
|
22
|
+
for (const preview of readiness.actionablePreviews?.slice(0, 3) ?? []) {
|
|
23
|
+
log.info(`${preview.relativePath}: ${preview.status}`);
|
|
24
|
+
}
|
|
25
|
+
process.exitCode = 1;
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const selectionResult = getNextSkillSelection(readiness.state, command.skillName);
|
|
29
|
+
switch (selectionResult.status) {
|
|
30
|
+
case 'noop':
|
|
31
|
+
log.success(selectionResult.message);
|
|
32
|
+
log.info('No files changed.');
|
|
33
|
+
return;
|
|
34
|
+
case 'blocked':
|
|
35
|
+
log.error(selectionResult.message);
|
|
36
|
+
if (selectionResult.hint) {
|
|
37
|
+
log.info(selectionResult.hint);
|
|
38
|
+
}
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
return;
|
|
41
|
+
case 'selected':
|
|
42
|
+
await applySelectedSkill({ rootPath, statePath, state: readiness.state, selectionResult, dependencies });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function getNextSkillSelection(state, skillName) {
|
|
47
|
+
const resolution = resolveSelectableSkillById(skillName);
|
|
48
|
+
if (!resolution.ok) {
|
|
49
|
+
return { status: 'blocked', message: resolution.message };
|
|
50
|
+
}
|
|
51
|
+
const selectedLanguageSkills = [...(state.selectedLanguageSkills ?? [])];
|
|
52
|
+
const selectedFrameworkSkills = [...(state.selectedFrameworkSkills ?? [])];
|
|
53
|
+
const selectedWorkflowSkills = [...(state.selectedWorkflowSkills ?? [])];
|
|
54
|
+
const selectedDatabaseSkills = [...(state.selectedDatabaseSkills ?? [])];
|
|
55
|
+
switch (resolution.resolved.kind) {
|
|
56
|
+
case 'language':
|
|
57
|
+
if (selectedLanguageSkills.includes(resolution.resolved.skill.id)) {
|
|
58
|
+
return { status: 'noop', message: `${resolution.resolved.skill.name} is already selected.` };
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
status: 'selected',
|
|
62
|
+
skillName: resolution.resolved.skill.name,
|
|
63
|
+
selection: {
|
|
64
|
+
selectedLanguageSkills: [...selectedLanguageSkills, resolution.resolved.skill.id].map(getLanguageSkillById),
|
|
65
|
+
selectedFrameworkSkills: selectedFrameworkSkills.map(getFrameworkSkillById),
|
|
66
|
+
selectedArchitectureSkill: getArchitectureSkillById(state.selectedArchitectureSkill),
|
|
67
|
+
selectedWorkflowSkills: selectedWorkflowSkills.map(getWorkflowSkillById),
|
|
68
|
+
selectedDatabaseSkills: selectedDatabaseSkills.map(getDatabaseSkillById),
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
case 'framework':
|
|
72
|
+
if (selectedFrameworkSkills.includes(resolution.resolved.skill.id)) {
|
|
73
|
+
return { status: 'noop', message: `${resolution.resolved.skill.name} is already selected.` };
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
status: 'selected',
|
|
77
|
+
skillName: resolution.resolved.skill.name,
|
|
78
|
+
selection: {
|
|
79
|
+
selectedLanguageSkills: selectedLanguageSkills.map(getLanguageSkillById),
|
|
80
|
+
selectedFrameworkSkills: [...selectedFrameworkSkills, resolution.resolved.skill.id].map(getFrameworkSkillById),
|
|
81
|
+
selectedArchitectureSkill: getArchitectureSkillById(state.selectedArchitectureSkill),
|
|
82
|
+
selectedWorkflowSkills: selectedWorkflowSkills.map(getWorkflowSkillById),
|
|
83
|
+
selectedDatabaseSkills: selectedDatabaseSkills.map(getDatabaseSkillById),
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
case 'architecture':
|
|
87
|
+
if (state.selectedArchitectureSkill === resolution.resolved.skill.id) {
|
|
88
|
+
return { status: 'noop', message: `${resolution.resolved.skill.name} is already selected.` };
|
|
89
|
+
}
|
|
90
|
+
if (state.selectedArchitectureSkill) {
|
|
91
|
+
return {
|
|
92
|
+
status: 'blocked',
|
|
93
|
+
message: `Cannot select ${resolution.resolved.skill.name} because another architecture skill is already selected.`,
|
|
94
|
+
hint: 'Architecture skill replacement is not supported yet. Keep the existing architecture skill or stop managing it first.',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
status: 'selected',
|
|
99
|
+
skillName: resolution.resolved.skill.name,
|
|
100
|
+
selection: {
|
|
101
|
+
selectedLanguageSkills: selectedLanguageSkills.map(getLanguageSkillById),
|
|
102
|
+
selectedFrameworkSkills: selectedFrameworkSkills.map(getFrameworkSkillById),
|
|
103
|
+
selectedArchitectureSkill: resolution.resolved.skill,
|
|
104
|
+
selectedWorkflowSkills: selectedWorkflowSkills.map(getWorkflowSkillById),
|
|
105
|
+
selectedDatabaseSkills: selectedDatabaseSkills.map(getDatabaseSkillById),
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
case 'database':
|
|
109
|
+
if (selectedDatabaseSkills.includes(resolution.resolved.skill.id)) {
|
|
110
|
+
return { status: 'noop', message: `${resolution.resolved.skill.name} is already selected.` };
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
status: 'selected',
|
|
114
|
+
skillName: resolution.resolved.skill.name,
|
|
115
|
+
selection: {
|
|
116
|
+
selectedLanguageSkills: selectedLanguageSkills.map(getLanguageSkillById),
|
|
117
|
+
selectedFrameworkSkills: selectedFrameworkSkills.map(getFrameworkSkillById),
|
|
118
|
+
selectedArchitectureSkill: getArchitectureSkillById(state.selectedArchitectureSkill),
|
|
119
|
+
selectedWorkflowSkills: selectedWorkflowSkills.map(getWorkflowSkillById),
|
|
120
|
+
selectedDatabaseSkills: [...selectedDatabaseSkills, resolution.resolved.skill.id].map(getDatabaseSkillById),
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
case 'workflow':
|
|
124
|
+
if (selectedWorkflowSkills.includes(resolution.resolved.skill.id)) {
|
|
125
|
+
return { status: 'noop', message: `${resolution.resolved.skill.name} is already selected.` };
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
status: 'selected',
|
|
129
|
+
skillName: resolution.resolved.skill.name,
|
|
130
|
+
selection: {
|
|
131
|
+
selectedLanguageSkills: selectedLanguageSkills.map(getLanguageSkillById),
|
|
132
|
+
selectedFrameworkSkills: selectedFrameworkSkills.map(getFrameworkSkillById),
|
|
133
|
+
selectedArchitectureSkill: getArchitectureSkillById(state.selectedArchitectureSkill),
|
|
134
|
+
selectedWorkflowSkills: [...selectedWorkflowSkills, resolution.resolved.skill.id].map(getWorkflowSkillById),
|
|
135
|
+
selectedDatabaseSkills: selectedDatabaseSkills.map(getDatabaseSkillById),
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function applySelectedSkill({ rootPath, statePath, state, selectionResult, dependencies, }) {
|
|
141
|
+
const plan = buildSkillApplicationPlan({ rootPath, state, selectionResult });
|
|
142
|
+
const preflightError = getSkillApplicationPreflightError({ rootPath, state, plan });
|
|
143
|
+
if (preflightError) {
|
|
144
|
+
log.error(preflightError);
|
|
145
|
+
log.info('Run `sibu sync` to review workflow state before selecting a skill.');
|
|
146
|
+
process.exitCode = 1;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const mcpServerConfigs = await getNextMcpServerConfigs({ state, selectedMcpServers: plan.selectedMcpServers, dependencies });
|
|
150
|
+
const dependencyMcpServers = plan.selectedMcpServers.filter((server) => !(state.selectedMcpServers ?? []).includes(server.id));
|
|
151
|
+
for (const server of dependencyMcpServers) {
|
|
152
|
+
log.info(`${selectionResult.skillName} requires ${server.name}. I will add them together.`);
|
|
153
|
+
}
|
|
154
|
+
const nonAgentTargets = plan.affectedTargets.filter((target) => target.label !== 'AGENTS.md');
|
|
155
|
+
const files = renderMissingWorkflowFiles({
|
|
156
|
+
missingTargets: nonAgentTargets,
|
|
157
|
+
selectedLanguageSkills: selectionResult.selection.selectedLanguageSkills,
|
|
158
|
+
selectedFrameworkSkills: selectionResult.selection.selectedFrameworkSkills,
|
|
159
|
+
selectedArchitectureSkill: selectionResult.selection.selectedArchitectureSkill,
|
|
160
|
+
selectedWorkflowSkills: selectionResult.selection.selectedWorkflowSkills,
|
|
161
|
+
selectedDatabaseSkills: selectionResult.selection.selectedDatabaseSkills,
|
|
162
|
+
selectedMcpServers: plan.selectedMcpServers,
|
|
163
|
+
});
|
|
164
|
+
for (const file of files) {
|
|
165
|
+
const fileAlreadyExists = fs.existsSync(file.targetPath);
|
|
166
|
+
fs.mkdirSync(path.dirname(file.targetPath), { recursive: true });
|
|
167
|
+
fs.writeFileSync(file.targetPath, file.contents, fileAlreadyExists ? 'utf8' : { encoding: 'utf8', flag: 'wx' });
|
|
168
|
+
log.success(`${fileAlreadyExists ? 'Updated' : 'Created'} ${file.label}`);
|
|
169
|
+
}
|
|
170
|
+
const agentsContents = renderTemplateForSync({
|
|
171
|
+
templateRelativePath: plan.agentsTarget.templateRelativePath,
|
|
172
|
+
currentPath: plan.agentsTarget.targetPath,
|
|
173
|
+
selectedLanguageSkills: selectionResult.selection.selectedLanguageSkills,
|
|
174
|
+
selectedFrameworkSkills: selectionResult.selection.selectedFrameworkSkills,
|
|
175
|
+
selectedArchitectureSkill: selectionResult.selection.selectedArchitectureSkill,
|
|
176
|
+
selectedWorkflowSkills: selectionResult.selection.selectedWorkflowSkills,
|
|
177
|
+
selectedDatabaseSkills: selectionResult.selection.selectedDatabaseSkills,
|
|
178
|
+
selectedMcpServers: plan.selectedMcpServers,
|
|
179
|
+
});
|
|
180
|
+
fs.writeFileSync(plan.agentsTarget.targetPath, agentsContents, 'utf8');
|
|
181
|
+
log.success('Updated AGENTS.md skill routing');
|
|
182
|
+
writeSibuState({
|
|
183
|
+
rootPath,
|
|
184
|
+
statePath,
|
|
185
|
+
selectedAgents: plan.selectedAgents,
|
|
186
|
+
selectedLanguageSkills: selectionResult.selection.selectedLanguageSkills,
|
|
187
|
+
selectedFrameworkSkills: selectionResult.selection.selectedFrameworkSkills,
|
|
188
|
+
selectedArchitectureSkill: selectionResult.selection.selectedArchitectureSkill,
|
|
189
|
+
selectedWorkflowSkills: selectionResult.selection.selectedWorkflowSkills,
|
|
190
|
+
selectedDatabaseSkills: selectionResult.selection.selectedDatabaseSkills,
|
|
191
|
+
selectedMcpServers: plan.selectedMcpServers,
|
|
192
|
+
mcpServerConfigs,
|
|
193
|
+
targets: plan.targets,
|
|
194
|
+
});
|
|
195
|
+
log.success(`Updated ${STATE_RELATIVE_PATH}`);
|
|
196
|
+
log.success(`Added ${selectionResult.skillName}.`);
|
|
197
|
+
for (const server of dependencyMcpServers) {
|
|
198
|
+
log.success(`Added ${server.name}.`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function buildSkillApplicationPlan({ rootPath, state, selectionResult, }) {
|
|
202
|
+
const selectedAgents = getSelectedAgentsFromState(state);
|
|
203
|
+
const currentMcpServers = getSelectedMcpServersFromState(state);
|
|
204
|
+
const requiredMcpServers = getMcpServersRequiredByWorkflowSkills(selectionResult.selection.selectedWorkflowSkills.map((skill) => skill.id));
|
|
205
|
+
const selectedMcpServers = mergeMcpServers(currentMcpServers, requiredMcpServers);
|
|
206
|
+
const previousTargets = getWorkflowTargets(rootPath, selectedAgents, getSelectedLanguageSkillsFromState(state), getSelectedFrameworkSkillsFromState(state), getArchitectureSkillById(state.selectedArchitectureSkill), getSelectedWorkflowSkillsFromState(state), getSelectedDatabaseSkillsFromState(state), currentMcpServers);
|
|
207
|
+
const targets = getWorkflowTargets(rootPath, selectedAgents, selectionResult.selection.selectedLanguageSkills, selectionResult.selection.selectedFrameworkSkills, selectionResult.selection.selectedArchitectureSkill, selectionResult.selection.selectedWorkflowSkills, selectionResult.selection.selectedDatabaseSkills, selectedMcpServers);
|
|
208
|
+
const previousTargetPaths = new Set(previousTargets.map((target) => target.targetPath));
|
|
209
|
+
const newTargets = targets.filter((target) => !previousTargetPaths.has(target.targetPath) && (target.targetKind === 'skill' || target.targetKind === 'mcp-config'));
|
|
210
|
+
const mcpConfigTargets = targets.filter((target) => target.mcpConfigAgentId && selectedMcpServers.length > currentMcpServers.length);
|
|
211
|
+
const agentsTarget = targets.find((target) => target.label === 'AGENTS.md');
|
|
212
|
+
if (newTargets.length === 0) {
|
|
213
|
+
throw new Error(`No new workflow target found for ${selectionResult.skillName}.`);
|
|
214
|
+
}
|
|
215
|
+
if (!agentsTarget) {
|
|
216
|
+
throw new Error('AGENTS.md target is missing from workflow targets.');
|
|
217
|
+
}
|
|
218
|
+
const affectedTargets = dedupeTargets([...newTargets, ...mcpConfigTargets, agentsTarget]);
|
|
219
|
+
return { agentsTarget, newTargets, affectedTargets, targets, selectedAgents, selectedMcpServers };
|
|
220
|
+
}
|
|
221
|
+
function getSkillApplicationPreflightError({ rootPath, state, plan }) {
|
|
222
|
+
const existingRecordedTargets = plan.affectedTargets.filter((target) => state.managedFiles[path.relative(rootPath, target.targetPath)]);
|
|
223
|
+
const newTargets = plan.affectedTargets.filter((target) => !state.managedFiles[path.relative(rootPath, target.targetPath)]);
|
|
224
|
+
for (const target of newTargets) {
|
|
225
|
+
if (fs.existsSync(target.targetPath)) {
|
|
226
|
+
return `${path.relative(rootPath, target.targetPath)} already exists but is not recorded for this selection.`;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
for (const target of existingRecordedTargets) {
|
|
230
|
+
const relativePath = path.relative(rootPath, target.targetPath);
|
|
231
|
+
const managedFile = state.managedFiles[relativePath];
|
|
232
|
+
if (!managedFile) {
|
|
233
|
+
return `${relativePath} is not recorded in ${STATE_RELATIVE_PATH}.`;
|
|
234
|
+
}
|
|
235
|
+
if (!fs.existsSync(target.targetPath)) {
|
|
236
|
+
return `${relativePath} is missing.`;
|
|
237
|
+
}
|
|
238
|
+
const currentHash = sha256(fs.readFileSync(target.targetPath, 'utf8'));
|
|
239
|
+
if (currentHash !== managedFile.sha256) {
|
|
240
|
+
return `${relativePath} has changed since Sibu last recorded it.`;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
function dedupeTargets(targets) {
|
|
246
|
+
return [...new Map(targets.map((target) => [target.targetPath, target])).values()];
|
|
247
|
+
}
|
|
248
|
+
function mergeMcpServers(...serverGroups) {
|
|
249
|
+
const serversById = new Map();
|
|
250
|
+
for (const server of serverGroups.flat()) {
|
|
251
|
+
serversById.set(server.id, server);
|
|
252
|
+
}
|
|
253
|
+
return [...serversById.values()];
|
|
254
|
+
}
|
|
255
|
+
async function getNextMcpServerConfigs({ state, selectedMcpServers, dependencies, }) {
|
|
256
|
+
if (!selectedMcpServers.some((server) => server.id === 'notion') || state.selectedMcpServers?.includes('notion')) {
|
|
257
|
+
return state.mcpServerConfigs;
|
|
258
|
+
}
|
|
259
|
+
const docsParentPage = await dependencies.askForNotionDocsParentPage();
|
|
260
|
+
return {
|
|
261
|
+
...state.mcpServerConfigs,
|
|
262
|
+
notion: { docsParentPage },
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
function getSelectedLanguageSkillsFromState(state) {
|
|
266
|
+
return (state.selectedLanguageSkills ?? []).map(getLanguageSkillById);
|
|
267
|
+
}
|
|
268
|
+
function getSelectedFrameworkSkillsFromState(state) {
|
|
269
|
+
return (state.selectedFrameworkSkills ?? []).map(getFrameworkSkillById);
|
|
270
|
+
}
|
|
271
|
+
function getSelectedWorkflowSkillsFromState(state) {
|
|
272
|
+
return (state.selectedWorkflowSkills ?? []).map(getWorkflowSkillById);
|
|
273
|
+
}
|
|
274
|
+
function getSelectedDatabaseSkillsFromState(state) {
|
|
275
|
+
return (state.selectedDatabaseSkills ?? []).map(getDatabaseSkillById);
|
|
276
|
+
}
|
|
277
|
+
function getLanguageSkillById(skillId) {
|
|
278
|
+
const resolution = resolveSelectableSkillById(skillId);
|
|
279
|
+
if (!resolution.ok || resolution.resolved.kind !== 'language') {
|
|
280
|
+
throw new Error(`Unsupported language skill in state: ${skillId}`);
|
|
281
|
+
}
|
|
282
|
+
return resolution.resolved.skill;
|
|
283
|
+
}
|
|
284
|
+
function getFrameworkSkillById(skillId) {
|
|
285
|
+
const resolution = resolveSelectableSkillById(skillId);
|
|
286
|
+
if (!resolution.ok || resolution.resolved.kind !== 'framework') {
|
|
287
|
+
throw new Error(`Unsupported framework skill in state: ${skillId}`);
|
|
288
|
+
}
|
|
289
|
+
return resolution.resolved.skill;
|
|
290
|
+
}
|
|
291
|
+
function getArchitectureSkillById(skillId) {
|
|
292
|
+
if (!skillId) {
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
const resolution = resolveSelectableSkillById(skillId);
|
|
296
|
+
if (!resolution.ok || resolution.resolved.kind !== 'architecture') {
|
|
297
|
+
throw new Error(`Unsupported architecture skill in state: ${skillId}`);
|
|
298
|
+
}
|
|
299
|
+
return resolution.resolved.skill;
|
|
300
|
+
}
|
|
301
|
+
function getWorkflowSkillById(skillId) {
|
|
302
|
+
const resolution = resolveSelectableSkillById(skillId);
|
|
303
|
+
if (!resolution.ok || resolution.resolved.kind !== 'workflow') {
|
|
304
|
+
throw new Error(`Unsupported workflow skill in state: ${skillId}`);
|
|
305
|
+
}
|
|
306
|
+
return resolution.resolved.skill;
|
|
307
|
+
}
|
|
308
|
+
function getDatabaseSkillById(skillId) {
|
|
309
|
+
const resolution = resolveSelectableSkillById(skillId);
|
|
310
|
+
if (!resolution.ok || resolution.resolved.kind !== 'database') {
|
|
311
|
+
throw new Error(`Unsupported database skill in state: ${skillId}`);
|
|
312
|
+
}
|
|
313
|
+
return resolution.resolved.skill;
|
|
314
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -2,15 +2,15 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { intro, log, outro } from '@clack/prompts';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
|
-
import { STATE_RELATIVE_PATH } from '
|
|
6
|
-
import { SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_DATABASE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_SKILLS, SELECTABLE_MCP_SERVERS, SUPPORTED_AGENTS } from '../
|
|
5
|
+
import { STATE_RELATIVE_PATH } from '../workflow-state-ledger/state-path.js';
|
|
6
|
+
import { SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_DATABASE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_SKILLS, SELECTABLE_MCP_SERVERS, SUPPORTED_AGENTS } from '../template-catalog/index.js';
|
|
7
7
|
import { sha256 } from '../../shared/hash.js';
|
|
8
|
-
import { checkForLatestSibuVersion } from '
|
|
8
|
+
import { checkForLatestSibuVersion } from '../../support/version-advisory/index.js';
|
|
9
9
|
import { getProjectContext } from '../../shared/paths.js';
|
|
10
|
-
import { renderIntro } from '
|
|
11
|
-
import { hasReviewedTemplateVersion, readStateForDoctor } from '../workflow-state-
|
|
12
|
-
import { getTemplateVersion, readTemplateManifest } from '../template-catalog
|
|
13
|
-
import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedDatabaseSkillsFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedMcpServersFromState, getSelectedWorkflowSkillsFromState, getWorkflowTargets, } from '../
|
|
10
|
+
import { renderIntro } from '../../support/interactive-guidance/index.js';
|
|
11
|
+
import { hasReviewedTemplateVersion, readStateForDoctor } from '../workflow-state-ledger/index.js';
|
|
12
|
+
import { getTemplateVersion, readTemplateManifest } from '../template-catalog/index.js';
|
|
13
|
+
import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedDatabaseSkillsFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedMcpServersFromState, getSelectedWorkflowSkillsFromState, getWorkflowTargets, } from '../template-catalog/index.js';
|
|
14
14
|
export async function handleDoctorProject(_command) {
|
|
15
15
|
await renderIntro();
|
|
16
16
|
intro(chalk.cyan('Checking workflow state'));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { writeSibuState } from '../workflow-state-ledger/index.js';
|
|
3
4
|
import { intro, log, outro } from '@clack/prompts';
|
|
4
5
|
import chalk from 'chalk';
|
|
5
|
-
import { STATE_RELATIVE_PATH } from '
|
|
6
|
+
import { STATE_RELATIVE_PATH } from '../workflow-state-ledger/state-path.js';
|
|
6
7
|
import { getProjectContext } from '../../shared/paths.js';
|
|
7
|
-
import { askForArchitectureSkill, askForDatabaseSkills, askForFrameworkSkills, askForLanguageSkills, askForMcpServers, askForNotionDocsParentPage, askForProjectOverview, askForSupportedAgents, askForWorkflowSkills, renderIntro, } from '
|
|
8
|
-
import { readStateForDoctor } from '../workflow-state-
|
|
9
|
-
import { getWorkflowSkillsImpliedByMcpServers, getWorkflowTargets, renderMissingWorkflowFiles
|
|
8
|
+
import { askForArchitectureSkill, askForDatabaseSkills, askForFrameworkSkills, askForLanguageSkills, askForMcpServers, askForNotionDocsParentPage, askForProjectOverview, askForSupportedAgents, askForWorkflowSkills, renderIntro, } from '../../support/interactive-guidance/index.js';
|
|
9
|
+
import { readStateForDoctor } from '../workflow-state-ledger/index.js';
|
|
10
|
+
import { getWorkflowSkillsImpliedByMcpServers, getWorkflowTargets, renderMissingWorkflowFiles } from '../template-catalog/index.js';
|
|
10
11
|
function mergeWorkflowSkills(...skillGroups) {
|
|
11
12
|
const skillsById = new Map();
|
|
12
13
|
for (const skill of skillGroups.flat()) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const STATE_RELATIVE_PATH = '.sibu/state.json';
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { STATE_RELATIVE_PATH } from '
|
|
3
|
+
import { STATE_RELATIVE_PATH } from './state-path.js';
|
|
4
|
+
import { sha256 } from '../../shared/hash.js';
|
|
5
|
+
import { removeUndefinedFields } from '../../shared/object.js';
|
|
6
|
+
import { getTemplateVersion, readTemplateManifest } from '../template-catalog/index.js';
|
|
7
|
+
import { SIBU_VERSION } from '../../support/version-advisory/index.js';
|
|
4
8
|
export function readStateForDoctor(statePath) {
|
|
5
9
|
if (!fs.existsSync(statePath)) {
|
|
6
10
|
return { ok: false, message: `${STATE_RELATIVE_PATH} is missing.` };
|
|
@@ -37,6 +41,41 @@ export function cloneState(state) {
|
|
|
37
41
|
export function hasReviewedTemplateVersion(managedFile, templateVersion) {
|
|
38
42
|
return managedFile.lastReviewedTemplateVersion === templateVersion;
|
|
39
43
|
}
|
|
44
|
+
export function writeSibuState({ rootPath, statePath, selectedAgents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = [], selectedDatabaseSkills = [], selectedMcpServers, mcpServerConfigs, targets, }) {
|
|
45
|
+
const previousState = readExistingState(statePath);
|
|
46
|
+
const now = new Date().toISOString();
|
|
47
|
+
const manifest = readTemplateManifest();
|
|
48
|
+
const state = {
|
|
49
|
+
sibuVersion: SIBU_VERSION,
|
|
50
|
+
templateVersion: manifest.templateVersion,
|
|
51
|
+
generatedAt: previousState?.generatedAt ?? now,
|
|
52
|
+
updatedAt: now,
|
|
53
|
+
selectedAgents: selectedAgents.map((agent) => agent.id),
|
|
54
|
+
selectedLanguageSkills: selectedLanguageSkills.map((skill) => skill.id),
|
|
55
|
+
selectedFrameworkSkills: selectedFrameworkSkills.map((skill) => skill.id),
|
|
56
|
+
selectedArchitectureSkill: selectedArchitectureSkill?.id,
|
|
57
|
+
selectedWorkflowSkills: selectedWorkflowSkills.map((skill) => skill.id),
|
|
58
|
+
selectedDatabaseSkills: selectedDatabaseSkills.map((skill) => skill.id),
|
|
59
|
+
...(selectedMcpServers !== undefined ? { selectedMcpServers: selectedMcpServers.map((server) => server.id) } : {}),
|
|
60
|
+
...(mcpServerConfigs ?? previousState?.mcpServerConfigs ? { mcpServerConfigs: mcpServerConfigs ?? previousState?.mcpServerConfigs } : {}),
|
|
61
|
+
managedFiles: Object.fromEntries(targets
|
|
62
|
+
.filter((target) => fs.existsSync(target.targetPath))
|
|
63
|
+
.map((target) => {
|
|
64
|
+
const relativePath = path.relative(rootPath, target.targetPath);
|
|
65
|
+
const previousManagedFile = previousState?.managedFiles[relativePath];
|
|
66
|
+
const nextManagedFile = {
|
|
67
|
+
template: target.templateRelativePath,
|
|
68
|
+
templateVersion: getTemplateVersion(manifest, target.templateRelativePath),
|
|
69
|
+
sha256: sha256(fs.readFileSync(target.targetPath, 'utf8')),
|
|
70
|
+
status: previousManagedFile?.status ?? 'managed',
|
|
71
|
+
lastReviewedTemplateVersion: previousManagedFile?.lastReviewedTemplateVersion,
|
|
72
|
+
reason: previousManagedFile?.reason,
|
|
73
|
+
};
|
|
74
|
+
return [relativePath, removeUndefinedFields(nextManagedFile)];
|
|
75
|
+
})),
|
|
76
|
+
};
|
|
77
|
+
writeStateFile(statePath, state);
|
|
78
|
+
}
|
|
40
79
|
function isSibuState(value) {
|
|
41
80
|
if (!value || typeof value !== 'object') {
|
|
42
81
|
return false;
|
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
1
|
import path from 'node:path';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { sha256 } from '../../shared/hash.js';
|
|
6
|
-
import { removeUndefinedFields } from '../../shared/object.js';
|
|
7
|
-
import { readExistingState } from '../workflow-state-registry/index.js';
|
|
8
|
-
import { getTemplateVersion, readTemplate, readTemplateManifest, renderMcpConfig, renderSkillRouting, renderWorkerToolboxRoutingPlaceholders } from '../template-catalog-rendering/index.js';
|
|
2
|
+
import { MANDATORY_SKILLS, SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_DATABASE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_SKILLS, SELECTABLE_MCP_SERVERS, SELECTABLE_WORKFLOW_SKILLS, SESSION_START_HOOKS, SUPPORTED_AGENTS, } from '../modules/template-catalog/catalog.js';
|
|
3
|
+
import { getMcpConfigTargetsForAgents } from '../modules/agent-tool-configuration/index.js';
|
|
9
4
|
export function getSelectedLanguageSkillsFromState(state) {
|
|
10
5
|
return SELECTABLE_LANGUAGE_SKILLS.filter((skill) => state.selectedLanguageSkills?.includes(skill.id));
|
|
11
6
|
}
|
|
@@ -66,41 +61,6 @@ function getSkillTargetsForAgent(skill, agentId) {
|
|
|
66
61
|
}
|
|
67
62
|
return targets;
|
|
68
63
|
}
|
|
69
|
-
export function getSelectedMcpTargetsForAgents(selectedAgents, selectedMcpServers) {
|
|
70
|
-
if (selectedMcpServers.length === 0) {
|
|
71
|
-
return [];
|
|
72
|
-
}
|
|
73
|
-
return selectedAgents.flatMap((agent) => {
|
|
74
|
-
if (agent.id === 'codex') {
|
|
75
|
-
return [
|
|
76
|
-
{
|
|
77
|
-
targetRelativePath: '.codex/config.toml',
|
|
78
|
-
templateRelativePath: '.codex/config.toml',
|
|
79
|
-
agentId: 'codex',
|
|
80
|
-
},
|
|
81
|
-
];
|
|
82
|
-
}
|
|
83
|
-
if (agent.id === 'claude') {
|
|
84
|
-
return [
|
|
85
|
-
{
|
|
86
|
-
targetRelativePath: '.mcp.json',
|
|
87
|
-
templateRelativePath: 'mcp/claude/.mcp.json',
|
|
88
|
-
agentId: 'claude',
|
|
89
|
-
},
|
|
90
|
-
];
|
|
91
|
-
}
|
|
92
|
-
if (agent.id === 'gemini') {
|
|
93
|
-
return [
|
|
94
|
-
{
|
|
95
|
-
targetRelativePath: '.gemini/settings.json',
|
|
96
|
-
templateRelativePath: 'mcp/gemini/settings.json',
|
|
97
|
-
agentId: 'gemini',
|
|
98
|
-
},
|
|
99
|
-
];
|
|
100
|
-
}
|
|
101
|
-
return [];
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
64
|
function getSessionStartHookTargetsForAgents(selectedAgents) {
|
|
105
65
|
const selectedAgentIds = new Set(selectedAgents.map((agent) => agent.id));
|
|
106
66
|
return SESSION_START_HOOKS.filter((hookTemplate) => selectedAgentIds.has(hookTemplate.agentId)).map((hookTemplate) => ({
|
|
@@ -146,7 +106,7 @@ export function getWorkflowTargets(rootPath, selectedAgents, selectedLanguageSki
|
|
|
146
106
|
targetKind: 'agent-support',
|
|
147
107
|
})),
|
|
148
108
|
];
|
|
149
|
-
for (const mcpTarget of
|
|
109
|
+
for (const mcpTarget of getMcpConfigTargetsForAgents(selectedAgents, selectedMcpServers)) {
|
|
150
110
|
const targetPath = path.join(rootPath, mcpTarget.targetRelativePath);
|
|
151
111
|
const existingTarget = targets.find((target) => target.targetPath === targetPath);
|
|
152
112
|
if (existingTarget) {
|
|
@@ -166,67 +126,6 @@ export function getWorkflowTargets(rootPath, selectedAgents, selectedLanguageSki
|
|
|
166
126
|
}
|
|
167
127
|
return targets;
|
|
168
128
|
}
|
|
169
|
-
export function renderMissingWorkflowFiles({ missingTargets, overview, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = [], selectedDatabaseSkills = [], selectedMcpServers = [], }) {
|
|
170
|
-
return missingTargets.map((target) => {
|
|
171
|
-
let contents = readTemplate(target.templateRelativePath);
|
|
172
|
-
if (target.mcpConfigAgentId && (target.selectedMcpServers?.length || selectedMcpServers.length)) {
|
|
173
|
-
contents = renderMcpConfig({
|
|
174
|
-
agentId: target.mcpConfigAgentId,
|
|
175
|
-
baseContents: contents,
|
|
176
|
-
selectedMcpServers: target.selectedMcpServers ?? selectedMcpServers,
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
if (target.requiresProjectOverview) {
|
|
180
|
-
if (!overview?.trim()) {
|
|
181
|
-
throw new Error('Project overview is required to create AGENTS.md.');
|
|
182
|
-
}
|
|
183
|
-
contents = contents.replace('{{PROJECT_OVERVIEW}}', overview.trim());
|
|
184
|
-
}
|
|
185
|
-
contents = renderSkillRouting(contents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, selectedDatabaseSkills);
|
|
186
|
-
contents = renderWorkerToolboxRoutingPlaceholders(contents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, selectedDatabaseSkills);
|
|
187
|
-
return {
|
|
188
|
-
label: target.label,
|
|
189
|
-
targetPath: target.targetPath,
|
|
190
|
-
contents,
|
|
191
|
-
};
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
export function writeSibuState({ rootPath, statePath, selectedAgents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = [], selectedDatabaseSkills = [], selectedMcpServers, mcpServerConfigs, targets, }) {
|
|
195
|
-
const previousState = readExistingState(statePath);
|
|
196
|
-
const now = new Date().toISOString();
|
|
197
|
-
const manifest = readTemplateManifest();
|
|
198
|
-
const state = {
|
|
199
|
-
sibuVersion: SIBU_VERSION,
|
|
200
|
-
templateVersion: manifest.templateVersion,
|
|
201
|
-
generatedAt: previousState?.generatedAt ?? now,
|
|
202
|
-
updatedAt: now,
|
|
203
|
-
selectedAgents: selectedAgents.map((agent) => agent.id),
|
|
204
|
-
selectedLanguageSkills: selectedLanguageSkills.map((skill) => skill.id),
|
|
205
|
-
selectedFrameworkSkills: selectedFrameworkSkills.map((skill) => skill.id),
|
|
206
|
-
selectedArchitectureSkill: selectedArchitectureSkill?.id,
|
|
207
|
-
selectedWorkflowSkills: selectedWorkflowSkills.map((skill) => skill.id),
|
|
208
|
-
selectedDatabaseSkills: selectedDatabaseSkills.map((skill) => skill.id),
|
|
209
|
-
...(selectedMcpServers !== undefined ? { selectedMcpServers: selectedMcpServers.map((server) => server.id) } : {}),
|
|
210
|
-
...(mcpServerConfigs ?? previousState?.mcpServerConfigs ? { mcpServerConfigs: mcpServerConfigs ?? previousState?.mcpServerConfigs } : {}),
|
|
211
|
-
managedFiles: Object.fromEntries(targets
|
|
212
|
-
.filter((target) => fs.existsSync(target.targetPath))
|
|
213
|
-
.map((target) => {
|
|
214
|
-
const relativePath = path.relative(rootPath, target.targetPath);
|
|
215
|
-
const previousManagedFile = previousState?.managedFiles[relativePath];
|
|
216
|
-
const nextManagedFile = {
|
|
217
|
-
template: target.templateRelativePath,
|
|
218
|
-
templateVersion: getTemplateVersion(manifest, target.templateRelativePath),
|
|
219
|
-
sha256: sha256(fs.readFileSync(target.targetPath, 'utf8')),
|
|
220
|
-
status: previousManagedFile?.status ?? 'managed',
|
|
221
|
-
lastReviewedTemplateVersion: previousManagedFile?.lastReviewedTemplateVersion,
|
|
222
|
-
reason: previousManagedFile?.reason,
|
|
223
|
-
};
|
|
224
|
-
return [relativePath, removeUndefinedFields(nextManagedFile)];
|
|
225
|
-
})),
|
|
226
|
-
};
|
|
227
|
-
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
228
|
-
fs.writeFileSync(statePath, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
|
|
229
|
-
}
|
|
230
129
|
export function getSelectedAgentsFromState(state) {
|
|
231
130
|
return SUPPORTED_AGENTS.filter((agent) => state.selectedAgents.includes(agent.id));
|
|
232
131
|
}
|
package/bin/shared/paths.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
|
-
|
|
3
|
+
const STATE_RELATIVE_PATH = '.sibu/state.json';
|
|
4
4
|
const __filename = fileURLToPath(import.meta.url);
|
|
5
5
|
const __dirname = path.dirname(__filename);
|
|
6
6
|
export function getProjectContext() {
|