@juancr11/sibu 0.13.2 → 0.14.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/bin/modules/interactive-guidance/index.js +1 -1
- package/bin/modules/interactive-guidance/prompts.js +8 -3
- package/bin/modules/mcp-server-selection-management/use-mcp-server/handler.js +57 -9
- package/bin/modules/project-adoption/handler.js +13 -2
- package/bin/modules/skill-selection-management/use-skill/handler.js +82 -30
- package/bin/modules/sync-review/apply-action.js +11 -0
- package/bin/modules/sync-review/sync-preview.js +60 -29
- package/bin/modules/workflow-target-planning/catalog.js +62 -0
- package/bin/modules/workflow-target-planning/index.js +1 -1
- package/package.json +1 -1
- package/templates/AGENTS.md +1 -0
- package/templates/manifest.json +32 -11
- package/templates/skills/export-to-github/SKILL.md +39 -0
- package/templates/skills/export-to-notion/SKILL.md +60 -0
- package/templates/skills/feature-brief-writer/SKILL.md +11 -25
- package/templates/skills/feature-idea-capture/SKILL.md +32 -0
- package/templates/skills/scrum-master-planner/SKILL.md +0 -32
- package/templates/skills/technical-design-writer/SKILL.md +0 -25
- package/templates/skills/ux-expert/SKILL.md +0 -25
- package/bin/shared/templates.js +0 -60
- package/bin/shared/workflow-targets.js +0 -125
|
@@ -1 +1 @@
|
|
|
1
|
-
export { askForArchitectureSkill, askForDatabaseSkills, askForFrameworkSkills, askForLanguageSkills, askForMcpServers, askForNotionDocsParentPage, askForMissingFrameworkSkills, askForNewArchitectureSkill, askForNewLanguageSkills, askForProjectOverview, askForSupportedAgents, askForWorkflowSkills, MCP_SERVER_SELECTION_MESSAGE, renderIntro, shouldAskForNewLanguageSkills, } from './prompts.js';
|
|
1
|
+
export { askForArchitectureSkill, askForDatabaseSkills, askForFrameworkSkills, askForLanguageSkills, askForMcpServers, askForNotionDocsParentPage, askForMissingFrameworkSkills, askForNewArchitectureSkill, askForNewLanguageSkills, askForProjectOverview, askForSupportedAgents, askForWorkflowSkills, MCP_SERVER_SELECTION_MESSAGE, getPromptableWorkflowSkills, renderIntro, shouldAskForNewLanguageSkills, } from './prompts.js';
|
|
@@ -148,11 +148,16 @@ export async function askForArchitectureSkill() {
|
|
|
148
148
|
}
|
|
149
149
|
return SELECTABLE_ARCHITECTURE_SKILLS.find((skill) => skill.id === selectedArchitectureSkillId);
|
|
150
150
|
}
|
|
151
|
-
export
|
|
151
|
+
export function getPromptableWorkflowSkills(excludedWorkflowSkillIds = []) {
|
|
152
|
+
const excludedIds = new Set(excludedWorkflowSkillIds);
|
|
153
|
+
return SELECTABLE_WORKFLOW_SKILLS.filter((skill) => !excludedIds.has(skill.id));
|
|
154
|
+
}
|
|
155
|
+
export async function askForWorkflowSkills(excludedWorkflowSkillIds = []) {
|
|
156
|
+
const promptableWorkflowSkills = getPromptableWorkflowSkills(excludedWorkflowSkillIds);
|
|
152
157
|
const selectedWorkflowSkillIds = await multiselect({
|
|
153
158
|
message: 'Select optional workflow skills for this project.',
|
|
154
159
|
required: false,
|
|
155
|
-
options:
|
|
160
|
+
options: promptableWorkflowSkills.map((skill) => ({
|
|
156
161
|
value: skill.id,
|
|
157
162
|
label: skill.name,
|
|
158
163
|
hint: skill.description,
|
|
@@ -162,7 +167,7 @@ export async function askForWorkflowSkills() {
|
|
|
162
167
|
cancel('Initialization cancelled.');
|
|
163
168
|
process.exit(0);
|
|
164
169
|
}
|
|
165
|
-
return
|
|
170
|
+
return promptableWorkflowSkills.filter((skill) => selectedWorkflowSkillIds.includes(skill.id));
|
|
166
171
|
}
|
|
167
172
|
export function shouldAskForNewLanguageSkills(state) {
|
|
168
173
|
return (state.selectedLanguageSkills?.length ?? 0) === 0;
|
|
@@ -2,10 +2,12 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { log } from '@clack/prompts';
|
|
4
4
|
import { STATE_RELATIVE_PATH } from '../../../shared/catalog.js';
|
|
5
|
+
import { sha256 } from '../../../shared/hash.js';
|
|
5
6
|
import { getProjectContext } from '../../../shared/paths.js';
|
|
6
7
|
import { askForNotionDocsParentPage } from '../../interactive-guidance/index.js';
|
|
8
|
+
import { renderTemplateForSync } from '../../template-catalog-rendering/index.js';
|
|
7
9
|
import { getWorkflowMutationReadiness } from '../../workflow-mutation-readiness/index.js';
|
|
8
|
-
import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedDatabaseSkillsFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedMcpServersFromState, getSelectedWorkflowSkillsFromState, getWorkflowTargets, renderMissingWorkflowFiles, resolveSelectableMcpServerById, writeSibuState, } from '../../workflow-target-planning/index.js';
|
|
10
|
+
import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedDatabaseSkillsFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedMcpServersFromState, getSelectedWorkflowSkillsFromState, getWorkflowSkillsImpliedByMcpServers, getWorkflowTargets, renderMissingWorkflowFiles, resolveSelectableMcpServerById, writeSibuState, } from '../../workflow-target-planning/index.js';
|
|
9
11
|
const defaultDependencies = {
|
|
10
12
|
askForNotionDocsParentPage,
|
|
11
13
|
};
|
|
@@ -71,11 +73,13 @@ function applySelectedMcpServer({ rootPath, statePath, state, selectionResult, m
|
|
|
71
73
|
const selectedLanguageSkills = getSelectedLanguageSkillsFromState(state);
|
|
72
74
|
const selectedFrameworkSkills = getSelectedFrameworkSkillsFromState(state);
|
|
73
75
|
const selectedArchitectureSkill = getSelectedArchitectureSkillFromState(state);
|
|
74
|
-
const
|
|
76
|
+
const currentWorkflowSkills = getSelectedWorkflowSkillsFromState(state);
|
|
77
|
+
const impliedWorkflowSkills = getWorkflowSkillsImpliedByMcpServers(selectionResult.selectedMcpServers.map((server) => server.id));
|
|
78
|
+
const selectedWorkflowSkills = mergeWorkflowSkills(currentWorkflowSkills, impliedWorkflowSkills);
|
|
75
79
|
const selectedDatabaseSkills = getSelectedDatabaseSkillsFromState(state);
|
|
76
|
-
const previousTargets = getWorkflowTargets(rootPath, selectedAgents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill,
|
|
80
|
+
const previousTargets = getWorkflowTargets(rootPath, selectedAgents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, currentWorkflowSkills, selectedDatabaseSkills, getSelectedMcpServersFromState(state));
|
|
77
81
|
const targets = getWorkflowTargets(rootPath, selectedAgents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, selectedDatabaseSkills, selectionResult.selectedMcpServers);
|
|
78
|
-
const affectedTargets =
|
|
82
|
+
const affectedTargets = getAffectedTargets(previousTargets, targets);
|
|
79
83
|
const preflightError = getMcpUsePreflightError({ rootPath, state, affectedTargets, previousTargets });
|
|
80
84
|
if (preflightError) {
|
|
81
85
|
log.error(preflightError);
|
|
@@ -83,8 +87,13 @@ function applySelectedMcpServer({ rootPath, statePath, state, selectionResult, m
|
|
|
83
87
|
process.exitCode = 1;
|
|
84
88
|
return;
|
|
85
89
|
}
|
|
90
|
+
const agentsTarget = targets.find((target) => target.label === 'AGENTS.md');
|
|
91
|
+
if (!agentsTarget) {
|
|
92
|
+
throw new Error('AGENTS.md target is missing from workflow targets.');
|
|
93
|
+
}
|
|
94
|
+
const nonAgentTargets = affectedTargets.filter((target) => target.label !== 'AGENTS.md');
|
|
86
95
|
const files = renderMissingWorkflowFiles({
|
|
87
|
-
missingTargets:
|
|
96
|
+
missingTargets: nonAgentTargets,
|
|
88
97
|
selectedLanguageSkills,
|
|
89
98
|
selectedFrameworkSkills,
|
|
90
99
|
selectedArchitectureSkill,
|
|
@@ -95,9 +104,23 @@ function applySelectedMcpServer({ rootPath, statePath, state, selectionResult, m
|
|
|
95
104
|
for (const file of files) {
|
|
96
105
|
const fileAlreadyExists = fs.existsSync(file.targetPath);
|
|
97
106
|
fs.mkdirSync(path.dirname(file.targetPath), { recursive: true });
|
|
98
|
-
fs.writeFileSync(file.targetPath, file.contents, 'utf8');
|
|
107
|
+
fs.writeFileSync(file.targetPath, file.contents, fileAlreadyExists ? 'utf8' : { encoding: 'utf8', flag: 'wx' });
|
|
99
108
|
log.success(`${fileAlreadyExists ? 'Updated' : 'Created'} ${file.label}`);
|
|
100
109
|
}
|
|
110
|
+
if (affectedTargets.some((target) => target.label === 'AGENTS.md')) {
|
|
111
|
+
const agentsContents = renderTemplateForSync({
|
|
112
|
+
templateRelativePath: agentsTarget.templateRelativePath,
|
|
113
|
+
currentPath: agentsTarget.targetPath,
|
|
114
|
+
selectedLanguageSkills,
|
|
115
|
+
selectedFrameworkSkills,
|
|
116
|
+
selectedArchitectureSkill,
|
|
117
|
+
selectedWorkflowSkills,
|
|
118
|
+
selectedDatabaseSkills,
|
|
119
|
+
selectedMcpServers: selectionResult.selectedMcpServers,
|
|
120
|
+
});
|
|
121
|
+
fs.writeFileSync(agentsTarget.targetPath, agentsContents, 'utf8');
|
|
122
|
+
log.success('Updated AGENTS.md skill routing');
|
|
123
|
+
}
|
|
101
124
|
writeSibuState({
|
|
102
125
|
rootPath,
|
|
103
126
|
statePath,
|
|
@@ -113,20 +136,45 @@ function applySelectedMcpServer({ rootPath, statePath, state, selectionResult, m
|
|
|
113
136
|
});
|
|
114
137
|
log.success(`Updated ${STATE_RELATIVE_PATH}`);
|
|
115
138
|
log.success(`Added ${selectionResult.serverName}.`);
|
|
139
|
+
for (const skill of impliedWorkflowSkills.filter((skill) => !currentWorkflowSkills.some((currentSkill) => currentSkill.id === skill.id))) {
|
|
140
|
+
log.success(`Added ${skill.name}.`);
|
|
141
|
+
}
|
|
116
142
|
log.info('Sibu configured MCP files only; prerequisites, credentials, and provider authentication remain user-owned.');
|
|
117
143
|
}
|
|
118
|
-
function
|
|
144
|
+
function getAffectedTargets(previousTargets, targets) {
|
|
119
145
|
const previousTargetPaths = new Set(previousTargets.map((target) => target.targetPath));
|
|
120
|
-
|
|
146
|
+
const newTargets = targets.filter((target) => !previousTargetPaths.has(target.targetPath) && (target.targetKind === 'skill' || target.targetKind === 'mcp-config'));
|
|
147
|
+
const mcpConfigTargets = targets.filter((target) => target.mcpConfigAgentId);
|
|
148
|
+
const agentsTarget = targets.find((target) => target.label === 'AGENTS.md');
|
|
149
|
+
const affectedTargets = new Map();
|
|
150
|
+
for (const target of [...mcpConfigTargets, ...newTargets, ...(newTargets.some((target) => target.targetKind === 'skill') && agentsTarget ? [agentsTarget] : [])]) {
|
|
151
|
+
affectedTargets.set(target.targetPath, target);
|
|
152
|
+
}
|
|
153
|
+
return [...affectedTargets.values()];
|
|
154
|
+
}
|
|
155
|
+
function mergeWorkflowSkills(...skillGroups) {
|
|
156
|
+
const skillsById = new Map();
|
|
157
|
+
for (const skill of skillGroups.flat()) {
|
|
158
|
+
skillsById.set(skill.id, skill);
|
|
159
|
+
}
|
|
160
|
+
return [...skillsById.values()];
|
|
121
161
|
}
|
|
122
162
|
function getMcpUsePreflightError({ rootPath, state, affectedTargets, previousTargets, }) {
|
|
123
163
|
const previousTargetPaths = new Set(previousTargets.map((target) => target.targetPath));
|
|
124
164
|
for (const target of affectedTargets) {
|
|
125
165
|
const relativePath = path.relative(rootPath, target.targetPath);
|
|
126
166
|
if (previousTargetPaths.has(target.targetPath)) {
|
|
127
|
-
|
|
167
|
+
const managedFile = state.managedFiles[relativePath];
|
|
168
|
+
if (!managedFile) {
|
|
128
169
|
return `${relativePath} is not recorded in ${STATE_RELATIVE_PATH}.`;
|
|
129
170
|
}
|
|
171
|
+
if (!fs.existsSync(target.targetPath)) {
|
|
172
|
+
return `${relativePath} is missing.`;
|
|
173
|
+
}
|
|
174
|
+
const currentHash = sha256(fs.readFileSync(target.targetPath, 'utf8'));
|
|
175
|
+
if (currentHash !== managedFile.sha256) {
|
|
176
|
+
return `${relativePath} has changed since Sibu last recorded it.`;
|
|
177
|
+
}
|
|
130
178
|
continue;
|
|
131
179
|
}
|
|
132
180
|
if (fs.existsSync(target.targetPath)) {
|
|
@@ -6,7 +6,14 @@ import { STATE_RELATIVE_PATH } from '../../shared/catalog.js';
|
|
|
6
6
|
import { getProjectContext } from '../../shared/paths.js';
|
|
7
7
|
import { askForArchitectureSkill, askForDatabaseSkills, askForFrameworkSkills, askForLanguageSkills, askForMcpServers, askForNotionDocsParentPage, askForProjectOverview, askForSupportedAgents, askForWorkflowSkills, renderIntro, } from '../interactive-guidance/index.js';
|
|
8
8
|
import { readStateForDoctor } from '../workflow-state-registry/index.js';
|
|
9
|
-
import { getWorkflowTargets, renderMissingWorkflowFiles, writeSibuState } from '../workflow-target-planning/index.js';
|
|
9
|
+
import { getWorkflowSkillsImpliedByMcpServers, getWorkflowTargets, renderMissingWorkflowFiles, writeSibuState } from '../workflow-target-planning/index.js';
|
|
10
|
+
function mergeWorkflowSkills(...skillGroups) {
|
|
11
|
+
const skillsById = new Map();
|
|
12
|
+
for (const skill of skillGroups.flat()) {
|
|
13
|
+
skillsById.set(skill.id, skill);
|
|
14
|
+
}
|
|
15
|
+
return [...skillsById.values()];
|
|
16
|
+
}
|
|
10
17
|
const defaultDependencies = {
|
|
11
18
|
renderIntro,
|
|
12
19
|
askForSupportedAgents,
|
|
@@ -41,12 +48,16 @@ export async function handleInitProject(_command, dependencies = defaultDependen
|
|
|
41
48
|
}
|
|
42
49
|
const selectedAgents = await dependencies.askForSupportedAgents();
|
|
43
50
|
const selectedMcpServers = await dependencies.askForMcpServers();
|
|
51
|
+
const impliedWorkflowSkills = getWorkflowSkillsImpliedByMcpServers(selectedMcpServers.map((server) => server.id));
|
|
52
|
+
for (const skill of impliedWorkflowSkills) {
|
|
53
|
+
log.info(`${skill.name} will be included because its matching MCP server was selected.`);
|
|
54
|
+
}
|
|
44
55
|
const notionDocsParentPage = selectedMcpServers.some((server) => server.id === 'notion') ? await dependencies.askForNotionDocsParentPage() : undefined;
|
|
45
56
|
const selectedLanguageSkills = await dependencies.askForLanguageSkills();
|
|
46
57
|
const selectedFrameworkSkills = await dependencies.askForFrameworkSkills();
|
|
47
58
|
const selectedDatabaseSkills = await dependencies.askForDatabaseSkills();
|
|
48
59
|
const selectedArchitectureSkill = await dependencies.askForArchitectureSkill();
|
|
49
|
-
const selectedWorkflowSkills = await dependencies.askForWorkflowSkills();
|
|
60
|
+
const selectedWorkflowSkills = mergeWorkflowSkills(impliedWorkflowSkills, await dependencies.askForWorkflowSkills(impliedWorkflowSkills.map((skill) => skill.id)));
|
|
50
61
|
const targets = getWorkflowTargets(rootPath, selectedAgents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, selectedDatabaseSkills, selectedMcpServers);
|
|
51
62
|
const missingTargets = targets.filter((target) => !fs.existsSync(target.targetPath));
|
|
52
63
|
log.message('I will create this project’s initial AI workflow files.');
|
|
@@ -2,13 +2,17 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { log } from '@clack/prompts';
|
|
4
4
|
import { STATE_RELATIVE_PATH } from '../../../shared/catalog.js';
|
|
5
|
-
import {
|
|
5
|
+
import { askForNotionDocsParentPage } from '../../interactive-guidance/index.js';
|
|
6
|
+
import { getMcpServersRequiredByWorkflowSkills, resolveSelectableSkillById } from '../../workflow-target-planning/index.js';
|
|
6
7
|
import { sha256 } from '../../../shared/hash.js';
|
|
7
8
|
import { getProjectContext } from '../../../shared/paths.js';
|
|
8
9
|
import { renderTemplateForSync } from '../../template-catalog-rendering/index.js';
|
|
9
10
|
import { getWorkflowMutationReadiness } from '../../workflow-mutation-readiness/index.js';
|
|
10
|
-
import { getSelectedAgentsFromState, getWorkflowTargets, renderMissingWorkflowFiles, writeSibuState } from '../../workflow-target-planning/index.js';
|
|
11
|
-
|
|
11
|
+
import { getSelectedAgentsFromState, getSelectedMcpServersFromState, getWorkflowTargets, renderMissingWorkflowFiles, writeSibuState } from '../../workflow-target-planning/index.js';
|
|
12
|
+
const defaultDependencies = {
|
|
13
|
+
askForNotionDocsParentPage,
|
|
14
|
+
};
|
|
15
|
+
export async function handleUseSkill(command, dependencies = defaultDependencies) {
|
|
12
16
|
const { rootPath, statePath } = getProjectContext();
|
|
13
17
|
const readiness = getWorkflowMutationReadiness({ rootPath, statePath });
|
|
14
18
|
if (!readiness.ok) {
|
|
@@ -34,7 +38,7 @@ export async function handleUseSkill(command) {
|
|
|
34
38
|
process.exitCode = 1;
|
|
35
39
|
return;
|
|
36
40
|
case 'selected':
|
|
37
|
-
applySelectedSkill({ rootPath, statePath, state: readiness.state, selectionResult });
|
|
41
|
+
await applySelectedSkill({ rootPath, statePath, state: readiness.state, selectionResult, dependencies });
|
|
38
42
|
return;
|
|
39
43
|
}
|
|
40
44
|
}
|
|
@@ -132,7 +136,7 @@ export function getNextSkillSelection(state, skillName) {
|
|
|
132
136
|
};
|
|
133
137
|
}
|
|
134
138
|
}
|
|
135
|
-
function applySelectedSkill({ rootPath, statePath, state, selectionResult, }) {
|
|
139
|
+
async function applySelectedSkill({ rootPath, statePath, state, selectionResult, dependencies, }) {
|
|
136
140
|
const plan = buildSkillApplicationPlan({ rootPath, state, selectionResult });
|
|
137
141
|
const preflightError = getSkillApplicationPreflightError({ rootPath, state, plan });
|
|
138
142
|
if (preflightError) {
|
|
@@ -141,25 +145,37 @@ function applySelectedSkill({ rootPath, statePath, state, selectionResult, }) {
|
|
|
141
145
|
process.exitCode = 1;
|
|
142
146
|
return;
|
|
143
147
|
}
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
148
|
+
const mcpServerConfigs = await getNextMcpServerConfigs({ state, selectedMcpServers: plan.selectedMcpServers, dependencies });
|
|
149
|
+
const dependencyMcpServers = plan.selectedMcpServers.filter((server) => !(state.selectedMcpServers ?? []).includes(server.id));
|
|
150
|
+
for (const server of dependencyMcpServers) {
|
|
151
|
+
log.info(`${selectionResult.skillName} requires ${server.name}. I will add them together.`);
|
|
152
|
+
}
|
|
153
|
+
const nonAgentTargets = plan.affectedTargets.filter((target) => target.label !== 'AGENTS.md');
|
|
154
|
+
const files = renderMissingWorkflowFiles({
|
|
155
|
+
missingTargets: nonAgentTargets,
|
|
147
156
|
selectedLanguageSkills: selectionResult.selection.selectedLanguageSkills,
|
|
148
157
|
selectedFrameworkSkills: selectionResult.selection.selectedFrameworkSkills,
|
|
149
158
|
selectedArchitectureSkill: selectionResult.selection.selectedArchitectureSkill,
|
|
150
159
|
selectedWorkflowSkills: selectionResult.selection.selectedWorkflowSkills,
|
|
151
160
|
selectedDatabaseSkills: selectionResult.selection.selectedDatabaseSkills,
|
|
161
|
+
selectedMcpServers: plan.selectedMcpServers,
|
|
152
162
|
});
|
|
153
|
-
const
|
|
154
|
-
|
|
163
|
+
for (const file of files) {
|
|
164
|
+
const fileAlreadyExists = fs.existsSync(file.targetPath);
|
|
165
|
+
fs.mkdirSync(path.dirname(file.targetPath), { recursive: true });
|
|
166
|
+
fs.writeFileSync(file.targetPath, file.contents, fileAlreadyExists ? 'utf8' : { encoding: 'utf8', flag: 'wx' });
|
|
167
|
+
log.success(`${fileAlreadyExists ? 'Updated' : 'Created'} ${file.label}`);
|
|
168
|
+
}
|
|
169
|
+
const agentsContents = renderTemplateForSync({
|
|
170
|
+
templateRelativePath: plan.agentsTarget.templateRelativePath,
|
|
171
|
+
currentPath: plan.agentsTarget.targetPath,
|
|
155
172
|
selectedLanguageSkills: selectionResult.selection.selectedLanguageSkills,
|
|
156
173
|
selectedFrameworkSkills: selectionResult.selection.selectedFrameworkSkills,
|
|
157
174
|
selectedArchitectureSkill: selectionResult.selection.selectedArchitectureSkill,
|
|
175
|
+
selectedWorkflowSkills: selectionResult.selection.selectedWorkflowSkills,
|
|
158
176
|
selectedDatabaseSkills: selectionResult.selection.selectedDatabaseSkills,
|
|
177
|
+
selectedMcpServers: plan.selectedMcpServers,
|
|
159
178
|
});
|
|
160
|
-
fs.mkdirSync(path.dirname(newSkillFile.targetPath), { recursive: true });
|
|
161
|
-
fs.writeFileSync(newSkillFile.targetPath, newSkillFile.contents, { encoding: 'utf8', flag: 'wx' });
|
|
162
|
-
log.success(`Created ${newSkillFile.label}`);
|
|
163
179
|
fs.writeFileSync(plan.agentsTarget.targetPath, agentsContents, 'utf8');
|
|
164
180
|
log.success('Updated AGENTS.md skill routing');
|
|
165
181
|
writeSibuState({
|
|
@@ -171,43 +187,79 @@ function applySelectedSkill({ rootPath, statePath, state, selectionResult, }) {
|
|
|
171
187
|
selectedArchitectureSkill: selectionResult.selection.selectedArchitectureSkill,
|
|
172
188
|
selectedWorkflowSkills: selectionResult.selection.selectedWorkflowSkills,
|
|
173
189
|
selectedDatabaseSkills: selectionResult.selection.selectedDatabaseSkills,
|
|
190
|
+
selectedMcpServers: plan.selectedMcpServers,
|
|
191
|
+
mcpServerConfigs,
|
|
174
192
|
targets: plan.targets,
|
|
175
193
|
});
|
|
176
194
|
log.success(`Updated ${STATE_RELATIVE_PATH}`);
|
|
177
195
|
log.success(`Added ${selectionResult.skillName}.`);
|
|
196
|
+
for (const server of dependencyMcpServers) {
|
|
197
|
+
log.success(`Added ${server.name}.`);
|
|
198
|
+
}
|
|
178
199
|
}
|
|
179
200
|
function buildSkillApplicationPlan({ rootPath, state, selectionResult, }) {
|
|
180
201
|
const selectedAgents = getSelectedAgentsFromState(state);
|
|
181
|
-
const
|
|
182
|
-
const
|
|
202
|
+
const currentMcpServers = getSelectedMcpServersFromState(state);
|
|
203
|
+
const requiredMcpServers = getMcpServersRequiredByWorkflowSkills(selectionResult.selection.selectedWorkflowSkills.map((skill) => skill.id));
|
|
204
|
+
const selectedMcpServers = mergeMcpServers(currentMcpServers, requiredMcpServers);
|
|
205
|
+
const previousTargets = getWorkflowTargets(rootPath, selectedAgents, getSelectedLanguageSkillsFromState(state), getSelectedFrameworkSkillsFromState(state), getArchitectureSkillById(state.selectedArchitectureSkill), getSelectedWorkflowSkillsFromState(state), getSelectedDatabaseSkillsFromState(state), currentMcpServers);
|
|
206
|
+
const targets = getWorkflowTargets(rootPath, selectedAgents, selectionResult.selection.selectedLanguageSkills, selectionResult.selection.selectedFrameworkSkills, selectionResult.selection.selectedArchitectureSkill, selectionResult.selection.selectedWorkflowSkills, selectionResult.selection.selectedDatabaseSkills, selectedMcpServers);
|
|
183
207
|
const previousTargetPaths = new Set(previousTargets.map((target) => target.targetPath));
|
|
184
|
-
const
|
|
208
|
+
const newTargets = targets.filter((target) => !previousTargetPaths.has(target.targetPath) && (target.targetKind === 'skill' || target.targetKind === 'mcp-config'));
|
|
209
|
+
const mcpConfigTargets = targets.filter((target) => target.mcpConfigAgentId && selectedMcpServers.length > currentMcpServers.length);
|
|
185
210
|
const agentsTarget = targets.find((target) => target.label === 'AGENTS.md');
|
|
186
|
-
if (
|
|
211
|
+
if (newTargets.length === 0) {
|
|
187
212
|
throw new Error(`No new workflow target found for ${selectionResult.skillName}.`);
|
|
188
213
|
}
|
|
189
214
|
if (!agentsTarget) {
|
|
190
215
|
throw new Error('AGENTS.md target is missing from workflow targets.');
|
|
191
216
|
}
|
|
192
|
-
|
|
217
|
+
const affectedTargets = dedupeTargets([...newTargets, ...mcpConfigTargets, agentsTarget]);
|
|
218
|
+
return { agentsTarget, newTargets, affectedTargets, targets, selectedAgents, selectedMcpServers };
|
|
193
219
|
}
|
|
194
220
|
function getSkillApplicationPreflightError({ rootPath, state, plan }) {
|
|
195
|
-
|
|
196
|
-
|
|
221
|
+
const existingRecordedTargets = plan.affectedTargets.filter((target) => state.managedFiles[path.relative(rootPath, target.targetPath)]);
|
|
222
|
+
const newTargets = plan.affectedTargets.filter((target) => !state.managedFiles[path.relative(rootPath, target.targetPath)]);
|
|
223
|
+
for (const target of newTargets) {
|
|
224
|
+
if (fs.existsSync(target.targetPath)) {
|
|
225
|
+
return `${path.relative(rootPath, target.targetPath)} already exists but is not recorded for this selection.`;
|
|
226
|
+
}
|
|
197
227
|
}
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
228
|
+
for (const target of existingRecordedTargets) {
|
|
229
|
+
const relativePath = path.relative(rootPath, target.targetPath);
|
|
230
|
+
const managedFile = state.managedFiles[relativePath];
|
|
231
|
+
if (!managedFile) {
|
|
232
|
+
return `${relativePath} is not recorded in ${STATE_RELATIVE_PATH}.`;
|
|
233
|
+
}
|
|
234
|
+
if (!fs.existsSync(target.targetPath)) {
|
|
235
|
+
return `${relativePath} is missing.`;
|
|
236
|
+
}
|
|
237
|
+
const currentHash = sha256(fs.readFileSync(target.targetPath, 'utf8'));
|
|
238
|
+
if (currentHash !== managedFile.sha256) {
|
|
239
|
+
return `${relativePath} has changed since Sibu last recorded it.`;
|
|
240
|
+
}
|
|
202
241
|
}
|
|
203
|
-
|
|
204
|
-
|
|
242
|
+
return undefined;
|
|
243
|
+
}
|
|
244
|
+
function dedupeTargets(targets) {
|
|
245
|
+
return [...new Map(targets.map((target) => [target.targetPath, target])).values()];
|
|
246
|
+
}
|
|
247
|
+
function mergeMcpServers(...serverGroups) {
|
|
248
|
+
const serversById = new Map();
|
|
249
|
+
for (const server of serverGroups.flat()) {
|
|
250
|
+
serversById.set(server.id, server);
|
|
205
251
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
252
|
+
return [...serversById.values()];
|
|
253
|
+
}
|
|
254
|
+
async function getNextMcpServerConfigs({ state, selectedMcpServers, dependencies, }) {
|
|
255
|
+
if (!selectedMcpServers.some((server) => server.id === 'notion') || state.selectedMcpServers?.includes('notion')) {
|
|
256
|
+
return state.mcpServerConfigs;
|
|
209
257
|
}
|
|
210
|
-
|
|
258
|
+
const docsParentPage = await dependencies.askForNotionDocsParentPage();
|
|
259
|
+
return {
|
|
260
|
+
...state.mcpServerConfigs,
|
|
261
|
+
notion: { docsParentPage },
|
|
262
|
+
};
|
|
211
263
|
}
|
|
212
264
|
function getSelectedLanguageSkillsFromState(state) {
|
|
213
265
|
return (state.selectedLanguageSkills ?? []).map(getLanguageSkillById);
|
|
@@ -13,6 +13,7 @@ export function applySyncAction({ rootPath, state, manifest, preview, action, })
|
|
|
13
13
|
const currentTemplateVersion = preview.currentTemplateVersion ?? getTemplateVersion(manifest, managedFile.template);
|
|
14
14
|
switch (action) {
|
|
15
15
|
case 'apply-update': {
|
|
16
|
+
recordImpliedWorkflowSkill(nextState, preview);
|
|
16
17
|
const contents = renderTemplateForSync({
|
|
17
18
|
templateRelativePath: managedFile.template,
|
|
18
19
|
currentPath: targetPath,
|
|
@@ -37,6 +38,7 @@ export function applySyncAction({ rootPath, state, manifest, preview, action, })
|
|
|
37
38
|
return { state: nextState, changedFiles: true, changedState: true };
|
|
38
39
|
}
|
|
39
40
|
case 'mark-reviewed': {
|
|
41
|
+
recordImpliedWorkflowSkill(nextState, preview);
|
|
40
42
|
const contents = fs.readFileSync(targetPath, 'utf8');
|
|
41
43
|
nextState.templateVersion = manifest.templateVersion;
|
|
42
44
|
nextState.updatedAt = new Date().toISOString();
|
|
@@ -85,3 +87,12 @@ export function applySyncAction({ rootPath, state, manifest, preview, action, })
|
|
|
85
87
|
return { state, changedFiles: false, changedState: false };
|
|
86
88
|
}
|
|
87
89
|
}
|
|
90
|
+
function recordImpliedWorkflowSkill(state, preview) {
|
|
91
|
+
if (!preview.impliedWorkflowSkillId) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (state.selectedWorkflowSkills?.includes(preview.impliedWorkflowSkillId)) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
state.selectedWorkflowSkills = [...(state.selectedWorkflowSkills ?? []), preview.impliedWorkflowSkillId];
|
|
98
|
+
}
|
|
@@ -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, getWorkflowTargets, } from '../workflow-target-planning/index.js';
|
|
6
|
+
import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedDatabaseSkillsFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedMcpServersFromState, getSelectedWorkflowSkillsFromState, getWorkflowSkillsImpliedByMcpServers, getWorkflowTargets, } from '../workflow-target-planning/index.js';
|
|
7
7
|
const ACTIONABLE_SYNC_PREVIEW_STATUSES = new Set([
|
|
8
8
|
'new-template',
|
|
9
9
|
'missing',
|
|
@@ -32,7 +32,19 @@ export function getSyncPreviews({ rootPath, state, manifest }) {
|
|
|
32
32
|
const selectedWorkflowSkills = getSelectedWorkflowSkillsFromState(state);
|
|
33
33
|
const selectedDatabaseSkills = getSelectedDatabaseSkillsFromState(state);
|
|
34
34
|
const selectedMcpServers = getSelectedMcpServersFromState(state);
|
|
35
|
-
const
|
|
35
|
+
const impliedWorkflowSkills = getWorkflowSkillsImpliedByMcpServers(selectedMcpServers.map((server) => server.id));
|
|
36
|
+
const plannedWorkflowSkills = mergeWorkflowSkills(selectedWorkflowSkills, impliedWorkflowSkills);
|
|
37
|
+
const missingImpliedWorkflowSkills = impliedWorkflowSkills.filter((skill) => !selectedWorkflowSkills.some((selectedSkill) => selectedSkill.id === skill.id));
|
|
38
|
+
const previews = [];
|
|
39
|
+
const expectedTargets = getWorkflowTargets(rootPath, getSelectedAgentsFromState(state), selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, plannedWorkflowSkills, selectedDatabaseSkills, selectedMcpServers);
|
|
40
|
+
const alreadyPreviewedPaths = new Set();
|
|
41
|
+
for (const skill of missingImpliedWorkflowSkills) {
|
|
42
|
+
for (const preview of getNewExpectedTargetPreviews({ rootPath, manifest, state, expectedTargets, workflowSkill: skill })) {
|
|
43
|
+
previews.push(preview);
|
|
44
|
+
alreadyPreviewedPaths.add(preview.relativePath);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
previews.push(...Object.entries(state.managedFiles).map(([relativePath, managedFile]) => getManagedFileSyncPreview({
|
|
36
48
|
rootPath,
|
|
37
49
|
manifest,
|
|
38
50
|
relativePath,
|
|
@@ -40,46 +52,65 @@ export function getSyncPreviews({ rootPath, state, manifest }) {
|
|
|
40
52
|
selectedLanguageSkills,
|
|
41
53
|
selectedFrameworkSkills,
|
|
42
54
|
selectedArchitectureSkill,
|
|
43
|
-
selectedWorkflowSkills,
|
|
55
|
+
selectedWorkflowSkills: plannedWorkflowSkills,
|
|
44
56
|
selectedDatabaseSkills,
|
|
45
57
|
selectedMcpServers,
|
|
46
|
-
}));
|
|
47
|
-
const expectedTargets = getWorkflowTargets(rootPath, getSelectedAgentsFromState(state), selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, selectedDatabaseSkills, selectedMcpServers);
|
|
58
|
+
})));
|
|
48
59
|
for (const target of expectedTargets) {
|
|
49
60
|
const relativePath = path.relative(rootPath, target.targetPath);
|
|
50
|
-
if (state.managedFiles[relativePath]) {
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
const template = manifest.templates[target.templateRelativePath];
|
|
54
|
-
if (!template) {
|
|
55
|
-
previews.push({
|
|
56
|
-
relativePath,
|
|
57
|
-
managedFile: {
|
|
58
|
-
template: target.templateRelativePath,
|
|
59
|
-
templateVersion: 'unknown',
|
|
60
|
-
sha256: '',
|
|
61
|
-
},
|
|
62
|
-
status: 'unknown-template',
|
|
63
|
-
changes: [],
|
|
64
|
-
hasLocalFile: fs.existsSync(target.targetPath),
|
|
65
|
-
});
|
|
61
|
+
if (state.managedFiles[relativePath] || alreadyPreviewedPaths.has(relativePath)) {
|
|
66
62
|
continue;
|
|
67
63
|
}
|
|
68
|
-
previews.push({
|
|
64
|
+
previews.push(getNewExpectedTargetPreview({ rootPath, manifest, target }));
|
|
65
|
+
}
|
|
66
|
+
return previews;
|
|
67
|
+
}
|
|
68
|
+
function getNewExpectedTargetPreviews({ rootPath, manifest, state, expectedTargets, workflowSkill, }) {
|
|
69
|
+
const workflowSkillTargetPaths = new Set(Object.values(workflowSkill.targetRelativePathsByAgent));
|
|
70
|
+
return expectedTargets
|
|
71
|
+
.filter((target) => workflowSkillTargetPaths.has(path.relative(rootPath, target.targetPath)))
|
|
72
|
+
.filter((target) => !state.managedFiles[path.relative(rootPath, target.targetPath)])
|
|
73
|
+
.map((target) => ({
|
|
74
|
+
...getNewExpectedTargetPreview({ rootPath, manifest, target }),
|
|
75
|
+
impliedWorkflowSkillId: workflowSkill.id,
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
function getNewExpectedTargetPreview({ rootPath, manifest, target }) {
|
|
79
|
+
const relativePath = path.relative(rootPath, target.targetPath);
|
|
80
|
+
const template = manifest.templates[target.templateRelativePath];
|
|
81
|
+
if (!template) {
|
|
82
|
+
return {
|
|
69
83
|
relativePath,
|
|
70
84
|
managedFile: {
|
|
71
85
|
template: target.templateRelativePath,
|
|
72
|
-
templateVersion:
|
|
86
|
+
templateVersion: 'unknown',
|
|
73
87
|
sha256: '',
|
|
74
|
-
status: 'managed',
|
|
75
88
|
},
|
|
76
|
-
status: '
|
|
77
|
-
|
|
78
|
-
changes: template.changes,
|
|
89
|
+
status: 'unknown-template',
|
|
90
|
+
changes: [],
|
|
79
91
|
hasLocalFile: fs.existsSync(target.targetPath),
|
|
80
|
-
}
|
|
92
|
+
};
|
|
81
93
|
}
|
|
82
|
-
return
|
|
94
|
+
return {
|
|
95
|
+
relativePath,
|
|
96
|
+
managedFile: {
|
|
97
|
+
template: target.templateRelativePath,
|
|
98
|
+
templateVersion: template.version,
|
|
99
|
+
sha256: '',
|
|
100
|
+
status: 'managed',
|
|
101
|
+
},
|
|
102
|
+
status: 'new-template',
|
|
103
|
+
currentTemplateVersion: template.version,
|
|
104
|
+
changes: template.changes,
|
|
105
|
+
hasLocalFile: fs.existsSync(target.targetPath),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function mergeWorkflowSkills(...skillGroups) {
|
|
109
|
+
const skillsById = new Map();
|
|
110
|
+
for (const skill of skillGroups.flat()) {
|
|
111
|
+
skillsById.set(skill.id, skill);
|
|
112
|
+
}
|
|
113
|
+
return [...skillsById.values()];
|
|
83
114
|
}
|
|
84
115
|
function getManagedFileSyncPreview({ rootPath, manifest, relativePath, managedFile, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, selectedDatabaseSkills, selectedMcpServers, }) {
|
|
85
116
|
if (managedFile.status === 'unmanaged') {
|
|
@@ -71,6 +71,15 @@ export const MANDATORY_SKILLS = [
|
|
|
71
71
|
windsurf: '.agents/skills/ai-implementation-plan-executor/SKILL.md',
|
|
72
72
|
},
|
|
73
73
|
},
|
|
74
|
+
{
|
|
75
|
+
templateRelativePath: 'skills/feature-idea-capture/SKILL.md',
|
|
76
|
+
targetRelativePathsByAgent: {
|
|
77
|
+
codex: '.agents/skills/feature-idea-capture/SKILL.md',
|
|
78
|
+
gemini: '.agents/skills/feature-idea-capture/SKILL.md',
|
|
79
|
+
claude: '.agents/skills/feature-idea-capture/SKILL.md',
|
|
80
|
+
windsurf: '.agents/skills/feature-idea-capture/SKILL.md',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
74
83
|
];
|
|
75
84
|
export const SELECTABLE_LANGUAGE_SKILLS = [
|
|
76
85
|
{
|
|
@@ -211,7 +220,60 @@ export const SELECTABLE_WORKFLOW_SKILLS = [
|
|
|
211
220
|
windsurf: '.agents/skills/ux-expert/SKILL.md',
|
|
212
221
|
},
|
|
213
222
|
},
|
|
223
|
+
{
|
|
224
|
+
id: 'export-to-github',
|
|
225
|
+
name: 'Export to GitHub',
|
|
226
|
+
description: "Install guidance for exporting a named feature's Epics and User Stories to GitHub using the configured GitHub MCP server",
|
|
227
|
+
routingInstruction: "For exporting a feature's Epics and User Stories to GitHub issues or sub-issues, use `export-to-github`.",
|
|
228
|
+
templateRelativePath: 'skills/export-to-github/SKILL.md',
|
|
229
|
+
targetRelativePathsByAgent: {
|
|
230
|
+
codex: '.agents/skills/export-to-github/SKILL.md',
|
|
231
|
+
gemini: '.agents/skills/export-to-github/SKILL.md',
|
|
232
|
+
claude: '.agents/skills/export-to-github/SKILL.md',
|
|
233
|
+
windsurf: '.agents/skills/export-to-github/SKILL.md',
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
id: 'export-to-notion',
|
|
238
|
+
name: 'Export to Notion',
|
|
239
|
+
description: "Install guidance for exporting a named feature's feature brief, UX design, and technical design to Notion using the configured Notion MCP server",
|
|
240
|
+
routingInstruction: "For exporting a feature's feature brief, UX design, or technical design to Notion, use `export-to-notion`.",
|
|
241
|
+
templateRelativePath: 'skills/export-to-notion/SKILL.md',
|
|
242
|
+
targetRelativePathsByAgent: {
|
|
243
|
+
codex: '.agents/skills/export-to-notion/SKILL.md',
|
|
244
|
+
gemini: '.agents/skills/export-to-notion/SKILL.md',
|
|
245
|
+
claude: '.agents/skills/export-to-notion/SKILL.md',
|
|
246
|
+
windsurf: '.agents/skills/export-to-notion/SKILL.md',
|
|
247
|
+
},
|
|
248
|
+
},
|
|
214
249
|
];
|
|
250
|
+
const EXPORT_WORKFLOW_SKILL_MCP_REQUIREMENTS = {
|
|
251
|
+
'export-to-github': 'github',
|
|
252
|
+
'export-to-notion': 'notion',
|
|
253
|
+
};
|
|
254
|
+
export function getMcpServersRequiredByWorkflowSkills(workflowSkillIds) {
|
|
255
|
+
const requiredServerIds = new Set();
|
|
256
|
+
for (const workflowSkillId of workflowSkillIds) {
|
|
257
|
+
const requiredServerId = getRequiredMcpServerIdForWorkflowSkill(workflowSkillId);
|
|
258
|
+
if (requiredServerId) {
|
|
259
|
+
requiredServerIds.add(requiredServerId);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return SELECTABLE_MCP_SERVERS.filter((server) => requiredServerIds.has(server.id));
|
|
263
|
+
}
|
|
264
|
+
export function getWorkflowSkillsImpliedByMcpServers(mcpServerIds) {
|
|
265
|
+
const selectedServerIds = new Set(mcpServerIds);
|
|
266
|
+
const impliedSkillIds = Object.entries(EXPORT_WORKFLOW_SKILL_MCP_REQUIREMENTS)
|
|
267
|
+
.filter(([, requiredServerId]) => selectedServerIds.has(requiredServerId))
|
|
268
|
+
.map(([workflowSkillId]) => workflowSkillId);
|
|
269
|
+
return SELECTABLE_WORKFLOW_SKILLS.filter((skill) => impliedSkillIds.includes(skill.id));
|
|
270
|
+
}
|
|
271
|
+
function getRequiredMcpServerIdForWorkflowSkill(workflowSkillId) {
|
|
272
|
+
if (workflowSkillId === 'export-to-github' || workflowSkillId === 'export-to-notion') {
|
|
273
|
+
return EXPORT_WORKFLOW_SKILL_MCP_REQUIREMENTS[workflowSkillId];
|
|
274
|
+
}
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
215
277
|
export const SELECTABLE_MCP_SERVERS = [
|
|
216
278
|
{
|
|
217
279
|
id: 'github',
|
|
@@ -1,2 +1,2 @@
|
|
|
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';
|
|
1
|
+
export { MANDATORY_SKILLS, SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_DATABASE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_SKILLS, SELECTABLE_MCP_SERVERS, SELECTABLE_WORKFLOW_SKILLS, SUPPORTED_AGENTS, getMcpServersRequiredByWorkflowSkills, getWorkflowSkillsImpliedByMcpServers, resolveSelectableMcpServerById, resolveSelectableSkillById, } from './catalog.js';
|
|
2
2
|
export { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedDatabaseSkillsFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedMcpServersFromState, getSelectedMcpTargetsForAgents, getSelectedSkillTargetsForAgents, getSelectedWorkflowSkillsFromState, getWorkflowTargets, renderMissingWorkflowFiles, writeSibuState, } from './workflow-targets.js';
|
package/package.json
CHANGED
package/templates/AGENTS.md
CHANGED
|
@@ -47,6 +47,7 @@ For planned product/feature work, use this pipeline: product vision -> deep modu
|
|
|
47
47
|
- For requests to create Epics, User Stories, Scrum planning artifacts, backlog slices, or delivery plans from an approved feature brief and technical design, use `scrum-master-planner`.
|
|
48
48
|
- For explicit planning-only requests to turn a specific User Story into an implementation checklist, coding plan, step-by-step execution plan, or baby-step plan without implementation, use `ai-implementation-planner` and stop after writing the plan. Treat a request as planning-only only when the user explicitly says planning-only, "do not implement," or asks to create the plan without execution.
|
|
49
49
|
- For requests to plan, implement, execute, continue, or work through a specific User Story under `docs/features/<feature-slug>/epics/<epic-slug>/stories/<order>-<story-slug>.md`, an Epic, or an existing story implementation plan under `docs/features/<feature-slug>/epics/<epic-slug>/stories/<order>-<story-slug>.impl_plan/`, use `ai-implementation-plan-executor`; when the story plan is missing, the executor must create it with `ai-implementation-planner` and immediately continue into implementation without a separate plan approval gate.
|
|
50
|
+
- For requests to capture, save, jot down, or add rough future feature ideas, use `feature-idea-capture`.
|
|
50
51
|
{{OPTIONAL_SKILL_ROUTING}}
|
|
51
52
|
|
|
52
53
|
## Sibu maintenance
|
package/templates/manifest.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"templateVersion": "
|
|
2
|
+
"templateVersion": "103",
|
|
3
3
|
"templates": {
|
|
4
4
|
"AGENTS.md": {
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "30",
|
|
6
6
|
"description": "Project-level agent instructions and Sibu maintenance guidance.",
|
|
7
7
|
"changes": [
|
|
8
|
-
"
|
|
8
|
+
"Routes rough future idea capture requests to the mandatory feature-idea-capture skill."
|
|
9
9
|
]
|
|
10
10
|
},
|
|
11
11
|
".codex/config.toml": {
|
|
@@ -65,17 +65,17 @@
|
|
|
65
65
|
]
|
|
66
66
|
},
|
|
67
67
|
"skills/feature-brief-writer/SKILL.md": {
|
|
68
|
-
"version": "
|
|
68
|
+
"version": "13",
|
|
69
69
|
"description": "Mandatory feature brief writer skill installed once at the shared .agents/skills workspace path.",
|
|
70
70
|
"changes": [
|
|
71
|
-
"
|
|
71
|
+
"Keeps feature brief writing focused on local Markdown artifacts and moves Notion export workflow guidance to dedicated exporter skills."
|
|
72
72
|
]
|
|
73
73
|
},
|
|
74
74
|
"skills/technical-design-writer/SKILL.md": {
|
|
75
|
-
"version": "
|
|
75
|
+
"version": "19",
|
|
76
76
|
"description": "Mandatory technical design writer skill installed once at the shared .agents/skills workspace path.",
|
|
77
77
|
"changes": [
|
|
78
|
-
"
|
|
78
|
+
"Keeps technical design writing focused on local Markdown artifacts and moves Notion export workflow guidance to dedicated exporter skills."
|
|
79
79
|
]
|
|
80
80
|
},
|
|
81
81
|
"skills/typescript/SKILL.md": {
|
|
@@ -128,10 +128,10 @@
|
|
|
128
128
|
]
|
|
129
129
|
},
|
|
130
130
|
"skills/scrum-master-planner/SKILL.md": {
|
|
131
|
-
"version": "
|
|
131
|
+
"version": "12",
|
|
132
132
|
"description": "Mandatory Scrum planner skill for creating pragmatic Epics and User Stories from approved feature and technical design docs.",
|
|
133
133
|
"changes": [
|
|
134
|
-
"
|
|
134
|
+
"Keeps Scrum planning focused on local Epic and User Story artifacts and moves GitHub export workflow guidance to dedicated exporter skills."
|
|
135
135
|
]
|
|
136
136
|
},
|
|
137
137
|
"skills/ai-implementation-planner/SKILL.md": {
|
|
@@ -148,6 +148,13 @@
|
|
|
148
148
|
"Clarifies that story and Epic planning requests are confirmation for executor implementation."
|
|
149
149
|
]
|
|
150
150
|
},
|
|
151
|
+
"skills/feature-idea-capture/SKILL.md": {
|
|
152
|
+
"version": "1",
|
|
153
|
+
"description": "Mandatory feature idea capture skill for storing rough future ideas in docs/feature-ideas.md only when the skill is used.",
|
|
154
|
+
"changes": [
|
|
155
|
+
"Adds a mandatory feature idea capture skill for quick, raw idea storage without backlog or planning ceremony."
|
|
156
|
+
]
|
|
157
|
+
},
|
|
151
158
|
"skills/ai-prompt-engineer-master/SKILL.md": {
|
|
152
159
|
"version": "2",
|
|
153
160
|
"description": "Selectable AI prompt engineering skill for prompt creation, optimization, compression, and evaluation.",
|
|
@@ -156,10 +163,10 @@
|
|
|
156
163
|
]
|
|
157
164
|
},
|
|
158
165
|
"skills/ux-expert/SKILL.md": {
|
|
159
|
-
"version": "
|
|
166
|
+
"version": "9",
|
|
160
167
|
"description": "Selectable UX expert skill for UI-changing features, responsive design, flows, states, accessibility, and binding mockups.",
|
|
161
168
|
"changes": [
|
|
162
|
-
"
|
|
169
|
+
"Keeps UX design writing focused on local Markdown artifacts and moves Notion export workflow guidance to dedicated exporter skills."
|
|
163
170
|
]
|
|
164
171
|
},
|
|
165
172
|
"skills/architecture/layered-architecture/SKILL.md": {
|
|
@@ -168,6 +175,20 @@
|
|
|
168
175
|
"changes": [
|
|
169
176
|
"Adds a Layered Architecture skill for smaller apps that need simple separation of concerns."
|
|
170
177
|
]
|
|
178
|
+
},
|
|
179
|
+
"skills/export-to-github/SKILL.md": {
|
|
180
|
+
"version": "1",
|
|
181
|
+
"description": "Selectable GitHub export skill for publishing a named feature's Epics and User Stories to GitHub issues and native sub-issues.",
|
|
182
|
+
"changes": [
|
|
183
|
+
"Adds a dedicated GitHub exporter skill paired with GitHub MCP selection."
|
|
184
|
+
]
|
|
185
|
+
},
|
|
186
|
+
"skills/export-to-notion/SKILL.md": {
|
|
187
|
+
"version": "1",
|
|
188
|
+
"description": "Selectable Notion export skill for publishing a named feature's core documentation files to Notion.",
|
|
189
|
+
"changes": [
|
|
190
|
+
"Adds a dedicated Notion exporter skill paired with Notion MCP selection."
|
|
191
|
+
]
|
|
171
192
|
}
|
|
172
193
|
}
|
|
173
194
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: export-to-github
|
|
3
|
+
description: Export a named feature's Epics and User Stories from local Markdown to GitHub issues and native sub-issues using configured GitHub MCP capabilities.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Export to GitHub
|
|
7
|
+
|
|
8
|
+
Use this skill when the user asks to export a feature's Epics and User Stories to GitHub.
|
|
9
|
+
|
|
10
|
+
## Required input
|
|
11
|
+
|
|
12
|
+
- A feature name or feature slug.
|
|
13
|
+
- Local planning artifacts under `docs/features/<feature-slug>/epics/`.
|
|
14
|
+
- Configured GitHub MCP capabilities for issue creation, issue ID access, and native sub-issue mutation.
|
|
15
|
+
|
|
16
|
+
If the feature name is ambiguous, inspect `docs/features/` and ask one focused clarification question.
|
|
17
|
+
|
|
18
|
+
## Workflow
|
|
19
|
+
|
|
20
|
+
1. Resolve the feature slug from the user's feature name.
|
|
21
|
+
2. Read the feature's Epic briefs and User Story Markdown files under `docs/features/<feature-slug>/epics/**`.
|
|
22
|
+
3. Resolve the target repository from the current local repo's GitHub `origin` remote only. Do not ask for or use another repository.
|
|
23
|
+
4. Verify GitHub MCP can create issues, return issue IDs, and attach native sub-issues.
|
|
24
|
+
5. Ask one explicit opt-in question before any GitHub mutation.
|
|
25
|
+
6. If the user accepts, create a fresh issue set every time:
|
|
26
|
+
- one issue per Epic, with no labels
|
|
27
|
+
- one issue per User Story, with no labels
|
|
28
|
+
- each User Story issue attached as a native sub-issue of its parent Epic issue
|
|
29
|
+
7. Report created issue numbers or URLs.
|
|
30
|
+
|
|
31
|
+
## Export rules
|
|
32
|
+
|
|
33
|
+
- Keep issue bodies concise and source-grounded.
|
|
34
|
+
- Include the source local doc path and relevant summary, scope, acceptance criteria, or validation notes.
|
|
35
|
+
- Preserve each created User Story issue's GitHub issue `id`; native sub-issue APIs need the child issue ID, not only its issue number.
|
|
36
|
+
- Do not search for duplicates or update existing issues.
|
|
37
|
+
- Do not use labels, milestones, projects, assignees, status tracking, Markdown checklists, or loose links as a substitute for native sub-issues.
|
|
38
|
+
- Do not modify local Markdown files with GitHub URLs.
|
|
39
|
+
- If issue creation, issue ID access, or native sub-issue attachment is unavailable, fail clearly and do not fall back to another structure.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: export-to-notion
|
|
3
|
+
description: Export a named feature's local feature brief, UX design, and technical design Markdown files to Notion using configured Notion MCP capabilities.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Export to Notion
|
|
7
|
+
|
|
8
|
+
Use this skill when the user asks to export a feature's core documentation to Notion.
|
|
9
|
+
|
|
10
|
+
## Required input
|
|
11
|
+
|
|
12
|
+
- A feature name or feature slug.
|
|
13
|
+
- Configured Notion MCP capabilities.
|
|
14
|
+
- `.sibu/state.json` with `mcpServerConfigs.notion.docsParentPage` when available.
|
|
15
|
+
|
|
16
|
+
If the feature name is ambiguous, inspect `docs/features/` and ask one focused clarification question.
|
|
17
|
+
|
|
18
|
+
## Source files
|
|
19
|
+
|
|
20
|
+
Export only these local Markdown files when present:
|
|
21
|
+
|
|
22
|
+
```txt
|
|
23
|
+
docs/features/<feature-slug>/feature_brief.md
|
|
24
|
+
docs/features/<feature-slug>/ux.md
|
|
25
|
+
docs/features/<feature-slug>/technical_design.md
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Do not export Epics, User Stories, implementation plans, product vision, Deep Module Maps, or arbitrary docs with this skill.
|
|
29
|
+
|
|
30
|
+
## Notion destination
|
|
31
|
+
|
|
32
|
+
Use this organization under the configured Notion docs parent page:
|
|
33
|
+
|
|
34
|
+
```txt
|
|
35
|
+
<docsParentPage>
|
|
36
|
+
└── <repo name>
|
|
37
|
+
└── Features
|
|
38
|
+
└── <feature name>
|
|
39
|
+
├── Feature Brief
|
|
40
|
+
├── UX Design
|
|
41
|
+
└── Technical Design
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Workflow
|
|
45
|
+
|
|
46
|
+
1. Resolve the feature slug from the user's feature name.
|
|
47
|
+
2. Read `.sibu/state.json` when available and use `mcpServerConfigs.notion.docsParentPage` as the destination parent.
|
|
48
|
+
3. Confirm which of the allowed source files exist.
|
|
49
|
+
4. Ask one explicit opt-in question before creating or modifying Notion pages.
|
|
50
|
+
5. Create or reuse the repo, Features, and feature organization pages under the configured parent.
|
|
51
|
+
6. Export each existing allowed artifact to its matching Notion page.
|
|
52
|
+
7. Report export success or failure per artifact.
|
|
53
|
+
|
|
54
|
+
## Export rules
|
|
55
|
+
|
|
56
|
+
- Local Markdown remains canonical.
|
|
57
|
+
- Do not write Notion URLs back into local Markdown.
|
|
58
|
+
- Do not invent missing source artifacts.
|
|
59
|
+
- Do not export files outside the allowed source list.
|
|
60
|
+
- If Notion MCP capabilities or destination configuration are unavailable, fail clearly and explain what is missing.
|
|
@@ -95,6 +95,17 @@ Use a short kebab-case feature slug that matches the feature name. Keep all arti
|
|
|
95
95
|
|
|
96
96
|
Do not write the brief to technical design, UX, user story, implementation plan, or backlog files unless the user explicitly asks for a separate artifact after the feature brief exists.
|
|
97
97
|
|
|
98
|
+
## Raw idea sources
|
|
99
|
+
|
|
100
|
+
If the user asks for a feature brief from an idea in `docs/feature-ideas.md`, read the relevant idea and treat it as raw/vague input only.
|
|
101
|
+
|
|
102
|
+
- Do not skip the normal interview flow because the idea exists in a file.
|
|
103
|
+
- Use the idea as a seed for the first discovery question, not as complete feature intent.
|
|
104
|
+
- Keep asking one focused question at a time until the usual required context is resolved: problem, target user/scenario, business goal, MVP boundary, out-of-scope boundary, success signals, constraints, and Deep Module fit.
|
|
105
|
+
- Preserve the hard-start requirements for `docs/product-vision.md` and `docs/deep-module-map.md`.
|
|
106
|
+
- After the local `docs/features/<feature-slug>/feature_brief.md` file is successfully written, remove the promoted idea from `docs/feature-ideas.md` while preserving the rest of the file.
|
|
107
|
+
- Do not delete the idea before the feature brief file exists, and do not remove unrelated ideas or headings.
|
|
108
|
+
|
|
98
109
|
## Interview posture
|
|
99
110
|
|
|
100
111
|
Be deliberately interrogative before drafting. The feature brief should reflect the user's intent, not the assistant's assumptions.
|
|
@@ -283,31 +294,6 @@ When shaping a feature brief, prefer:
|
|
|
283
294
|
7. measurable success signals
|
|
284
295
|
8. non-technical acceptance criteria
|
|
285
296
|
|
|
286
|
-
## Optional Notion export after local write
|
|
287
|
-
|
|
288
|
-
After the local Markdown file is written successfully, optionally offer Notion export. Local Markdown remains the canonical pipeline artifact and Notion is only a convenience export destination.
|
|
289
|
-
|
|
290
|
-
1. Do not check Notion export before the local file exists.
|
|
291
|
-
2. After writing the local file, read `.sibu/state.json` if available.
|
|
292
|
-
3. Offer export only when all are true:
|
|
293
|
-
- `selectedMcpServers` includes `notion`
|
|
294
|
-
- `mcpServerConfigs.notion.docsParentPage` is present
|
|
295
|
-
- Notion MCP tools are available in the current agent session
|
|
296
|
-
4. If export is unavailable, do nothing else and finish with the normal local path response.
|
|
297
|
-
5. If export is available, ask for explicit opt-in before creating or modifying any Notion page.
|
|
298
|
-
6. If the user declines, do nothing else.
|
|
299
|
-
7. If the user accepts, create or reuse Notion organization pages under the configured parent page:
|
|
300
|
-
|
|
301
|
-
```txt
|
|
302
|
-
<docsParentPage>
|
|
303
|
-
└── <repo name>
|
|
304
|
-
└── Features
|
|
305
|
-
└── <feature name>
|
|
306
|
-
└── Feature Brief
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
Create a new document page for the just-written artifact content. Do not write Notion URLs back into local Markdown. Report Notion export success or a clear Notion export failure, while preserving the local file as the completed artifact either way.
|
|
310
|
-
|
|
311
297
|
## Final response behavior
|
|
312
298
|
|
|
313
299
|
After writing the file, final-answer with only the path created or updated. Do not paste the feature brief body, excerpt, outline, or section summaries.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: feature-idea-capture
|
|
3
|
+
description: Use when the user wants to capture, save, jot down, or add a rough future feature, product, workflow, or improvement idea without turning it into a backlog item, feature brief, or implementation task.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Feature Idea Capture
|
|
7
|
+
|
|
8
|
+
Capture rough future ideas quickly in `docs/feature-ideas.md`.
|
|
9
|
+
|
|
10
|
+
## Behavior
|
|
11
|
+
|
|
12
|
+
- Write the idea down directly. Do not interview the user before capture.
|
|
13
|
+
- Keep the idea intentionally raw: a short heading and a few bullets are enough.
|
|
14
|
+
- If `docs/feature-ideas.md` does not exist, create it on first use.
|
|
15
|
+
- Append new ideas; do not classify, prioritize, groom, or turn them into backlog work.
|
|
16
|
+
- Do not create feature briefs, epics, stories, implementation plans, or code from captured ideas unless the user separately asks for that later.
|
|
17
|
+
- Do not migrate, rename, inspect, or reconcile pre-existing alternate idea files.
|
|
18
|
+
|
|
19
|
+
## File shape
|
|
20
|
+
|
|
21
|
+
Use this lightweight shape:
|
|
22
|
+
|
|
23
|
+
```md
|
|
24
|
+
# Feature Ideas
|
|
25
|
+
|
|
26
|
+
## <Idea title>
|
|
27
|
+
|
|
28
|
+
- <Short note>
|
|
29
|
+
- <Short note>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
If the file already exists, preserve its current content and append the new idea using the same simple style.
|
|
@@ -213,37 +213,6 @@ Before finishing, verify:
|
|
|
213
213
|
- no Story adds scope that is absent from the feature brief or technical design
|
|
214
214
|
- acceptance criteria are testable enough for a reviewer
|
|
215
215
|
|
|
216
|
-
### 6. Mandatory GitHub export gate
|
|
217
|
-
|
|
218
|
-
After all local Epic and User Story files are written, you must run the GitHub export gate before the final response. This gate is mandatory whenever Scrum planning creates or updates local Epic or User Story files.
|
|
219
|
-
|
|
220
|
-
Do not skip the gate because the user did not mention GitHub. Do not assume GitHub MCP is unavailable. Explicitly check whether GitHub MCP tools are available for issue creation, issue ID access, and native sub-issue mutation.
|
|
221
|
-
|
|
222
|
-
If any required GitHub MCP capability is unavailable, do not offer GitHub export. In the final response, include a single concise note that GitHub export was skipped because the required GitHub MCP capabilities were unavailable.
|
|
223
|
-
|
|
224
|
-
If the required GitHub MCP capabilities are available:
|
|
225
|
-
|
|
226
|
-
1. Resolve the target repository from the current local repo's GitHub `origin` remote only. Do not ask for or use another repository.
|
|
227
|
-
2. Ask one explicit opt-in question before any GitHub mutation: "GitHub MCP is available. Create GitHub Issues for these Epics and User Stories in the current repo?"
|
|
228
|
-
3. If the user declines, perform no GitHub mutation.
|
|
229
|
-
4. If the user accepts, create a fresh issue set every time. Do not search for duplicates or update existing issues.
|
|
230
|
-
5. Create one issue per Epic with no labels.
|
|
231
|
-
6. Create one issue per User Story with no labels.
|
|
232
|
-
7. Attach each User Story issue as a native GitHub sub-issue of its parent Epic issue.
|
|
233
|
-
8. Report created issue numbers or URLs.
|
|
234
|
-
|
|
235
|
-
Keep issue bodies concise and source-grounded. Include the source local doc path and relevant summary, scope, acceptance criteria, or validation notes. Do not modify local Markdown files with GitHub URLs.
|
|
236
|
-
|
|
237
|
-
Native sub-issues are required for this export. Preserve each created User Story issue's GitHub issue `id` because native sub-issue APIs need the child issue ID, not only its issue number. If issue creation, issue ID access, or native sub-issue attachment fails or is unavailable, fail clearly and do not fall back to labels, Markdown checklists, loose links, GitHub Projects, milestones, assignees, or status tracking.
|
|
238
|
-
|
|
239
|
-
Passing the gate means exactly one of these outcomes occurred:
|
|
240
|
-
|
|
241
|
-
- GitHub MCP was unavailable or incomplete, and the final response says export was skipped for that reason.
|
|
242
|
-
- GitHub MCP was available, the user declined export, and no GitHub mutation occurred.
|
|
243
|
-
- GitHub MCP was available, the user accepted export, and the created issue numbers or URLs are reported.
|
|
244
|
-
|
|
245
|
-
Never finish a Scrum planning run without one of these three GitHub export outcomes.
|
|
246
|
-
|
|
247
216
|
## Final response behavior
|
|
248
217
|
|
|
249
218
|
After writing files, final-answer with only:
|
|
@@ -251,6 +220,5 @@ After writing files, final-answer with only:
|
|
|
251
220
|
- the Epic directories created or updated
|
|
252
221
|
- the number of Epics and User Stories
|
|
253
222
|
- any source-scope items intentionally left unresolved or captured as risks
|
|
254
|
-
- the GitHub export gate outcome
|
|
255
223
|
|
|
256
224
|
Do not paste artifact bodies, excerpts, outlines, story text, acceptance criteria, or section summaries. Only include generated artifacts when the user explicitly asks for inline review in the current request.
|
|
@@ -173,31 +173,6 @@ Use this structure as a starting point. Delete sections that do not add value.
|
|
|
173
173
|
|
|
174
174
|
A good technical design is short, specific, and useful. It should not try to be the product brief, architecture skill, clean-code skill, implementation plan, or ticket backlog.
|
|
175
175
|
|
|
176
|
-
## Optional Notion export after local write
|
|
177
|
-
|
|
178
|
-
After the local Markdown file is written successfully, optionally offer Notion export. Local Markdown remains the canonical pipeline artifact and Notion is only a convenience export destination.
|
|
179
|
-
|
|
180
|
-
1. Do not check Notion export before the local file exists.
|
|
181
|
-
2. After writing the local file, read `.sibu/state.json` if available.
|
|
182
|
-
3. Offer export only when all are true:
|
|
183
|
-
- `selectedMcpServers` includes `notion`
|
|
184
|
-
- `mcpServerConfigs.notion.docsParentPage` is present
|
|
185
|
-
- Notion MCP tools are available in the current agent session
|
|
186
|
-
4. If export is unavailable, do nothing else and finish with the normal local path response.
|
|
187
|
-
5. If export is available, ask for explicit opt-in before creating or modifying any Notion page.
|
|
188
|
-
6. If the user declines, do nothing else.
|
|
189
|
-
7. If the user accepts, create or reuse Notion organization pages under the configured parent page:
|
|
190
|
-
|
|
191
|
-
```txt
|
|
192
|
-
<docsParentPage>
|
|
193
|
-
└── <repo name>
|
|
194
|
-
└── Features
|
|
195
|
-
└── <feature name>
|
|
196
|
-
└── Technical Design
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
Create a new document page for the just-written artifact content. Do not write Notion URLs back into local Markdown. Report Notion export success or a clear Notion export failure, while preserving the local file as the completed artifact either way.
|
|
200
|
-
|
|
201
176
|
## Final response behavior
|
|
202
177
|
|
|
203
178
|
After writing the file, final-answer with only the path created or updated. Do not paste the technical design body, excerpt, outline, or section summaries.
|
|
@@ -133,31 +133,6 @@ Use only helpful sections from this shape:
|
|
|
133
133
|
|
|
134
134
|
The Binding Mockups section is authoritative for downstream work unless this UX spec is revised.
|
|
135
135
|
|
|
136
|
-
## Optional Notion export after local write
|
|
137
|
-
|
|
138
|
-
After the local Markdown file is written successfully, optionally offer Notion export. Local Markdown remains the canonical pipeline artifact and Notion is only a convenience export destination.
|
|
139
|
-
|
|
140
|
-
1. Do not check Notion export before the local file exists.
|
|
141
|
-
2. After writing the local file, read `.sibu/state.json` if available.
|
|
142
|
-
3. Offer export only when all are true:
|
|
143
|
-
- `selectedMcpServers` includes `notion`
|
|
144
|
-
- `mcpServerConfigs.notion.docsParentPage` is present
|
|
145
|
-
- Notion MCP tools are available in the current agent session
|
|
146
|
-
4. If export is unavailable, do nothing else and finish with the normal local path response.
|
|
147
|
-
5. If export is available, ask for explicit opt-in before creating or modifying any Notion page.
|
|
148
|
-
6. If the user declines, do nothing else.
|
|
149
|
-
7. If the user accepts, create or reuse Notion organization pages under the configured parent page:
|
|
150
|
-
|
|
151
|
-
```txt
|
|
152
|
-
<docsParentPage>
|
|
153
|
-
└── <repo name>
|
|
154
|
-
└── Features
|
|
155
|
-
└── <feature name>
|
|
156
|
-
└── UX Design
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
Create a new document page for the just-written artifact content. Do not write Notion URLs back into local Markdown. Report Notion export success or a clear Notion export failure, while preserving the local file as the completed artifact either way.
|
|
160
|
-
|
|
161
136
|
## Final response behavior
|
|
162
137
|
|
|
163
138
|
After writing the file, final-answer with only the path created or updated. Do not paste the UX spec body, excerpt, outline, mockups, or section summaries.
|
package/bin/shared/templates.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { getTemplatesPath } from './paths.js';
|
|
4
|
-
export function readTemplate(relativePath) {
|
|
5
|
-
return fs.readFileSync(path.join(getTemplatesPath(), relativePath), 'utf8');
|
|
6
|
-
}
|
|
7
|
-
export function readTemplateManifest() {
|
|
8
|
-
const manifest = JSON.parse(fs.readFileSync(path.join(getTemplatesPath(), 'manifest.json'), 'utf8'));
|
|
9
|
-
if (!isTemplateManifest(manifest)) {
|
|
10
|
-
throw new Error('templates/manifest.json is not a valid template manifest.');
|
|
11
|
-
}
|
|
12
|
-
return manifest;
|
|
13
|
-
}
|
|
14
|
-
export function getTemplateVersion(manifest, templateRelativePath) {
|
|
15
|
-
const template = manifest.templates[templateRelativePath];
|
|
16
|
-
if (!template) {
|
|
17
|
-
throw new Error(`Template ${templateRelativePath} is missing from templates/manifest.json.`);
|
|
18
|
-
}
|
|
19
|
-
return template.version;
|
|
20
|
-
}
|
|
21
|
-
export function renderTemplateForSync({ templateRelativePath, currentPath, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = [], }) {
|
|
22
|
-
let contents = readTemplate(templateRelativePath);
|
|
23
|
-
if (contents.includes('{{PROJECT_OVERVIEW}}')) {
|
|
24
|
-
contents = contents.replace('{{PROJECT_OVERVIEW}}', extractProjectOverview(currentPath) ?? 'Describe this project.');
|
|
25
|
-
}
|
|
26
|
-
return renderSkillRouting(contents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills);
|
|
27
|
-
}
|
|
28
|
-
export function renderSkillRouting(contents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = []) {
|
|
29
|
-
if (!contents.includes('{{OPTIONAL_SKILL_ROUTING}}')) {
|
|
30
|
-
return contents;
|
|
31
|
-
}
|
|
32
|
-
const optionalRouting = [...selectedLanguageSkills, ...selectedFrameworkSkills, ...(selectedArchitectureSkill ? [selectedArchitectureSkill] : []), ...selectedWorkflowSkills]
|
|
33
|
-
.map((skill) => `- ${skill.routingInstruction}`)
|
|
34
|
-
.join('\n');
|
|
35
|
-
return contents.replace('{{OPTIONAL_SKILL_ROUTING}}', optionalRouting);
|
|
36
|
-
}
|
|
37
|
-
export function extractProjectOverview(filePath) {
|
|
38
|
-
if (!fs.existsSync(filePath)) {
|
|
39
|
-
return undefined;
|
|
40
|
-
}
|
|
41
|
-
const contents = fs.readFileSync(filePath, 'utf8');
|
|
42
|
-
const match = contents.match(/## Project overview\s+([\s\S]*?)(?=\n## |$)/);
|
|
43
|
-
const overview = match?.[1]?.trim();
|
|
44
|
-
return overview || undefined;
|
|
45
|
-
}
|
|
46
|
-
function isTemplateManifest(value) {
|
|
47
|
-
if (!value || typeof value !== 'object') {
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
const manifest = value;
|
|
51
|
-
return (typeof manifest.templateVersion === 'string' &&
|
|
52
|
-
!!manifest.templates &&
|
|
53
|
-
typeof manifest.templates === 'object' &&
|
|
54
|
-
Object.values(manifest.templates).every((template) => !!template &&
|
|
55
|
-
typeof template === 'object' &&
|
|
56
|
-
typeof template.version === 'string' &&
|
|
57
|
-
typeof template.description === 'string' &&
|
|
58
|
-
Array.isArray(template.changes) &&
|
|
59
|
-
template.changes.every((change) => typeof change === 'string')));
|
|
60
|
-
}
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { SIBU_VERSION } from './catalog.js';
|
|
4
|
-
import { MANDATORY_SKILLS, SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_SKILLS, SELECTABLE_WORKFLOW_SKILLS, SUPPORTED_AGENTS, } from '../modules/workflow-target-planning/index.js';
|
|
5
|
-
import { sha256 } from './hash.js';
|
|
6
|
-
import { removeUndefinedFields } from './object.js';
|
|
7
|
-
import { readExistingState } from './state.js';
|
|
8
|
-
import { getTemplateVersion, readTemplate, readTemplateManifest, renderSkillRouting } from '../modules/template-catalog-rendering/index.js';
|
|
9
|
-
export function getSelectedLanguageSkillsFromState(state) {
|
|
10
|
-
return SELECTABLE_LANGUAGE_SKILLS.filter((skill) => state.selectedLanguageSkills?.includes(skill.id));
|
|
11
|
-
}
|
|
12
|
-
export function getSelectedFrameworkSkillsFromState(state) {
|
|
13
|
-
return SELECTABLE_FRAMEWORK_SKILLS.filter((skill) => state.selectedFrameworkSkills?.includes(skill.id));
|
|
14
|
-
}
|
|
15
|
-
export function getSelectedArchitectureSkillFromState(state) {
|
|
16
|
-
return SELECTABLE_ARCHITECTURE_SKILLS.find((skill) => skill.id === state.selectedArchitectureSkill);
|
|
17
|
-
}
|
|
18
|
-
export function getSelectedWorkflowSkillsFromState(state) {
|
|
19
|
-
return SELECTABLE_WORKFLOW_SKILLS.filter((skill) => state.selectedWorkflowSkills?.includes(skill.id));
|
|
20
|
-
}
|
|
21
|
-
export function getSelectedSkillTargetsForAgents(selectedAgents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = []) {
|
|
22
|
-
const skillTargets = new Map();
|
|
23
|
-
const selectedSkills = [
|
|
24
|
-
...MANDATORY_SKILLS,
|
|
25
|
-
...selectedLanguageSkills,
|
|
26
|
-
...selectedFrameworkSkills,
|
|
27
|
-
...(selectedArchitectureSkill ? [selectedArchitectureSkill] : []),
|
|
28
|
-
...selectedWorkflowSkills,
|
|
29
|
-
];
|
|
30
|
-
for (const agent of selectedAgents) {
|
|
31
|
-
for (const skill of selectedSkills) {
|
|
32
|
-
const targetRelativePath = skill.targetRelativePathsByAgent[agent.id];
|
|
33
|
-
if (!targetRelativePath) {
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
skillTargets.set(targetRelativePath, {
|
|
37
|
-
targetRelativePath,
|
|
38
|
-
templateRelativePath: skill.templateRelativePath,
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return [...skillTargets.values()];
|
|
43
|
-
}
|
|
44
|
-
export function getWorkflowTargets(rootPath, selectedAgents, selectedLanguageSkills = [], selectedFrameworkSkills = [], selectedArchitectureSkill, selectedWorkflowSkills = []) {
|
|
45
|
-
return [
|
|
46
|
-
{
|
|
47
|
-
label: 'AGENTS.md',
|
|
48
|
-
targetPath: path.join(rootPath, 'AGENTS.md'),
|
|
49
|
-
templateRelativePath: 'AGENTS.md',
|
|
50
|
-
requiresProjectOverview: true,
|
|
51
|
-
},
|
|
52
|
-
...selectedAgents.flatMap((agent) => {
|
|
53
|
-
if (!agent.targetRelativePath || !agent.templateRelativePath) {
|
|
54
|
-
return [];
|
|
55
|
-
}
|
|
56
|
-
return [
|
|
57
|
-
{
|
|
58
|
-
label: agent.targetRelativePath,
|
|
59
|
-
targetPath: path.join(rootPath, agent.targetRelativePath),
|
|
60
|
-
templateRelativePath: agent.templateRelativePath,
|
|
61
|
-
requiresProjectOverview: false,
|
|
62
|
-
},
|
|
63
|
-
];
|
|
64
|
-
}),
|
|
65
|
-
...getSelectedSkillTargetsForAgents(selectedAgents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills).map((skillTarget) => ({
|
|
66
|
-
label: skillTarget.targetRelativePath,
|
|
67
|
-
targetPath: path.join(rootPath, skillTarget.targetRelativePath),
|
|
68
|
-
templateRelativePath: skillTarget.templateRelativePath,
|
|
69
|
-
requiresProjectOverview: false,
|
|
70
|
-
})),
|
|
71
|
-
];
|
|
72
|
-
}
|
|
73
|
-
export function renderMissingWorkflowFiles({ missingTargets, overview, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = [], }) {
|
|
74
|
-
return missingTargets.map((target) => {
|
|
75
|
-
let contents = readTemplate(target.templateRelativePath);
|
|
76
|
-
if (target.requiresProjectOverview) {
|
|
77
|
-
if (!overview?.trim()) {
|
|
78
|
-
throw new Error('Project overview is required to create AGENTS.md.');
|
|
79
|
-
}
|
|
80
|
-
contents = contents.replace('{{PROJECT_OVERVIEW}}', overview.trim());
|
|
81
|
-
}
|
|
82
|
-
contents = renderSkillRouting(contents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills);
|
|
83
|
-
return {
|
|
84
|
-
label: target.label,
|
|
85
|
-
targetPath: target.targetPath,
|
|
86
|
-
contents,
|
|
87
|
-
};
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
export function writeSibuState({ rootPath, statePath, selectedAgents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = [], targets, }) {
|
|
91
|
-
const previousState = readExistingState(statePath);
|
|
92
|
-
const now = new Date().toISOString();
|
|
93
|
-
const manifest = readTemplateManifest();
|
|
94
|
-
const state = {
|
|
95
|
-
sibuVersion: SIBU_VERSION,
|
|
96
|
-
templateVersion: manifest.templateVersion,
|
|
97
|
-
generatedAt: previousState?.generatedAt ?? now,
|
|
98
|
-
updatedAt: now,
|
|
99
|
-
selectedAgents: selectedAgents.map((agent) => agent.id),
|
|
100
|
-
selectedLanguageSkills: selectedLanguageSkills.map((skill) => skill.id),
|
|
101
|
-
selectedFrameworkSkills: selectedFrameworkSkills.map((skill) => skill.id),
|
|
102
|
-
selectedArchitectureSkill: selectedArchitectureSkill?.id,
|
|
103
|
-
selectedWorkflowSkills: selectedWorkflowSkills.map((skill) => skill.id),
|
|
104
|
-
managedFiles: Object.fromEntries(targets
|
|
105
|
-
.filter((target) => fs.existsSync(target.targetPath))
|
|
106
|
-
.map((target) => {
|
|
107
|
-
const relativePath = path.relative(rootPath, target.targetPath);
|
|
108
|
-
const previousManagedFile = previousState?.managedFiles[relativePath];
|
|
109
|
-
const nextManagedFile = {
|
|
110
|
-
template: target.templateRelativePath,
|
|
111
|
-
templateVersion: getTemplateVersion(manifest, target.templateRelativePath),
|
|
112
|
-
sha256: sha256(fs.readFileSync(target.targetPath, 'utf8')),
|
|
113
|
-
status: previousManagedFile?.status ?? 'managed',
|
|
114
|
-
lastReviewedTemplateVersion: previousManagedFile?.lastReviewedTemplateVersion,
|
|
115
|
-
reason: previousManagedFile?.reason,
|
|
116
|
-
};
|
|
117
|
-
return [relativePath, removeUndefinedFields(nextManagedFile)];
|
|
118
|
-
})),
|
|
119
|
-
};
|
|
120
|
-
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
121
|
-
fs.writeFileSync(statePath, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
|
|
122
|
-
}
|
|
123
|
-
export function getSelectedAgentsFromState(state) {
|
|
124
|
-
return SUPPORTED_AGENTS.filter((agent) => state.selectedAgents.includes(agent.id));
|
|
125
|
-
}
|