@juancr11/sibu 0.10.0 → 0.11.1

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 CHANGED
@@ -44,7 +44,7 @@ Use `sibu skills stop <file>` to stop managing an Sibu-tracked workflow file. Th
44
44
 
45
45
  ## MCP server setup
46
46
 
47
- Sibu can generate MCP server configuration for supported agents. The first supported server is GitHub's official MCP server.
47
+ Sibu can generate MCP server configuration for supported agents. Supported MCP servers include GitHub and Notion.
48
48
 
49
49
  Sibu only writes and tracks MCP config files. Runtime prerequisites, credentials, and provider authentication remain user-owned.
50
50
 
@@ -121,15 +121,36 @@ export GITHUB_PERSONAL_ACCESS_TOKEN=your_token_here
121
121
 
122
122
  Do not commit tokens or write them into Sibu-managed config files. The generated configs read `GITHUB_PERSONAL_ACCESS_TOKEN` from the environment so credentials can stay outside the repository.
123
123
 
124
- ### 4. Stop managing the GitHub MCP server
124
+ ### 4. Configure the Notion MCP server
125
125
 
126
- To remove GitHub from Sibu's selected MCP server state, run:
126
+ You can select Notion during `sibu init`, or add it later:
127
+
128
+ ```sh
129
+ sibu mcp use notion
130
+ ```
131
+
132
+ When Notion is selected, Sibu asks for a Notion docs destination parent page URL or page ID. Sibu stores that parent page reference in `.sibu/state.json` so feature-doc export guidance knows where Notion pages should go.
133
+
134
+ Sibu configures supported agent MCP files for Notion, but it does not manage Notion OAuth login, workspace selection, integration installation, page permissions, credentials, or live connectivity. Your Notion MCP connection must be authenticated separately and must have access to the configured parent page. For provider setup details, use Notion's MCP documentation: <https://developers.notion.com/guides/mcp/get-started-with-mcp>.
135
+
136
+ Feature briefs, technical designs, and UX docs are still written to local Markdown first. Notion is an optional export destination, not Sibu's source of truth.
137
+
138
+ For Codex with the hosted Notion MCP server, you may need to run the agent-specific MCP login flow after Sibu writes config, such as:
139
+
140
+ ```sh
141
+ codex mcp login notion
142
+ ```
143
+
144
+ ### 5. Stop managing an MCP server
145
+
146
+ To remove a selected MCP server from Sibu's state, run:
127
147
 
128
148
  ```sh
129
149
  sibu mcp stop github
150
+ sibu mcp stop notion
130
151
  ```
131
152
 
132
- Sibu updates generated agent config where possible and asks whether to keep or delete MCP-only config files.
153
+ Sibu updates generated agent config where possible and asks whether to keep or delete MCP-only config files. Stopping Notion only updates local Sibu state and managed MCP config files; it does not delete Notion pages or change Notion permissions.
133
154
 
134
155
  ## Release notes and changelog
135
156
 
@@ -1 +1 @@
1
- export { askForArchitectureSkill, askForDatabaseSkills, askForFrameworkSkills, askForLanguageSkills, askForMcpServers, 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, renderIntro, shouldAskForNewLanguageSkills, } from './prompts.js';
@@ -89,6 +89,22 @@ export async function askForMcpServers() {
89
89
  }
90
90
  return SELECTABLE_MCP_SERVERS.filter((server) => selectedMcpServerIds.includes(server.id));
91
91
  }
92
+ export async function askForNotionDocsParentPage() {
93
+ const parentPage = await text({
94
+ message: 'Enter the Notion docs destination parent page URL or page ID.',
95
+ placeholder: 'https://www.notion.so/workspace/Sibu-Docs-...',
96
+ validate(value) {
97
+ if (!value?.trim()) {
98
+ return 'Enter a Notion parent page that your Notion MCP connection can access.';
99
+ }
100
+ },
101
+ });
102
+ if (isCancel(parentPage)) {
103
+ cancel('Initialization cancelled.');
104
+ process.exit(0);
105
+ }
106
+ return parentPage.trim();
107
+ }
92
108
  async function askForFrameworkSkillSelection({ message, cancelMessage, }) {
93
109
  const selectedFrameworkSkillIds = await multiselect({
94
110
  message,
@@ -79,6 +79,10 @@ export function stopSelectedMcpServer({ rootPath, state, serverId }) {
79
79
  const stoppedPaths = [];
80
80
  const stoppedFiles = [];
81
81
  nextState.selectedMcpServers = selectionResult.remainingMcpServers.map((server) => server.id);
82
+ if (serverId === 'notion' && nextState.mcpServerConfigs?.notion) {
83
+ const { notion: _notion, ...remainingMcpServerConfigs } = nextState.mcpServerConfigs;
84
+ nextState.mcpServerConfigs = Object.keys(remainingMcpServerConfigs).length > 0 ? remainingMcpServerConfigs : undefined;
85
+ }
82
86
  nextState.templateVersion = manifest.templateVersion;
83
87
  nextState.updatedAt = new Date().toISOString();
84
88
  for (const previousTarget of previousTargets.filter((target) => target.mcpConfigAgentId)) {
@@ -3,9 +3,13 @@ import path from 'node:path';
3
3
  import { log } from '@clack/prompts';
4
4
  import { STATE_RELATIVE_PATH } from '../../../shared/catalog.js';
5
5
  import { getProjectContext } from '../../../shared/paths.js';
6
+ import { askForNotionDocsParentPage } from '../../interactive-guidance/index.js';
6
7
  import { getWorkflowMutationReadiness } from '../../workflow-mutation-readiness/index.js';
7
8
  import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedDatabaseSkillsFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedMcpServersFromState, getSelectedWorkflowSkillsFromState, getWorkflowTargets, renderMissingWorkflowFiles, resolveSelectableMcpServerById, writeSibuState, } from '../../workflow-target-planning/index.js';
8
- export async function handleUseMcpServer(command) {
9
+ const defaultDependencies = {
10
+ askForNotionDocsParentPage,
11
+ };
12
+ export async function handleUseMcpServer(command, dependencies = defaultDependencies) {
9
13
  const { rootPath, statePath } = getProjectContext();
10
14
  const readiness = getWorkflowMutationReadiness({ rootPath, statePath });
11
15
  if (!readiness.ok) {
@@ -30,9 +34,11 @@ export async function handleUseMcpServer(command) {
30
34
  }
31
35
  process.exitCode = 1;
32
36
  return;
33
- case 'selected':
34
- applySelectedMcpServer({ rootPath, statePath, state: readiness.state, selectionResult });
37
+ case 'selected': {
38
+ const mcpServerConfigs = await getNextMcpServerConfigs({ state: readiness.state, selectionResult, dependencies });
39
+ applySelectedMcpServer({ rootPath, statePath, state: readiness.state, selectionResult, mcpServerConfigs });
35
40
  return;
41
+ }
36
42
  }
37
43
  }
38
44
  export function getNextMcpSelection(state, serverId) {
@@ -50,7 +56,17 @@ export function getNextMcpSelection(state, serverId) {
50
56
  selectedMcpServers: [...selectedMcpServerIds, resolution.resolved.server.id].map(getMcpServerById),
51
57
  };
52
58
  }
53
- function applySelectedMcpServer({ rootPath, statePath, state, selectionResult, }) {
59
+ async function getNextMcpServerConfigs({ state, selectionResult, dependencies, }) {
60
+ if (!selectionResult.selectedMcpServers.some((server) => server.id === 'notion') || state.selectedMcpServers?.includes('notion')) {
61
+ return state.mcpServerConfigs;
62
+ }
63
+ const docsParentPage = await dependencies.askForNotionDocsParentPage();
64
+ return {
65
+ ...state.mcpServerConfigs,
66
+ notion: { docsParentPage },
67
+ };
68
+ }
69
+ function applySelectedMcpServer({ rootPath, statePath, state, selectionResult, mcpServerConfigs, }) {
54
70
  const selectedAgents = getSelectedAgentsFromState(state);
55
71
  const selectedLanguageSkills = getSelectedLanguageSkillsFromState(state);
56
72
  const selectedFrameworkSkills = getSelectedFrameworkSkillsFromState(state);
@@ -92,6 +108,7 @@ function applySelectedMcpServer({ rootPath, statePath, state, selectionResult, }
92
108
  selectedWorkflowSkills,
93
109
  selectedDatabaseSkills,
94
110
  selectedMcpServers: selectionResult.selectedMcpServers,
111
+ mcpServerConfigs,
95
112
  targets,
96
113
  });
97
114
  log.success(`Updated ${STATE_RELATIVE_PATH}`);
@@ -4,13 +4,14 @@ import { intro, log, outro } from '@clack/prompts';
4
4
  import chalk from 'chalk';
5
5
  import { STATE_RELATIVE_PATH } from '../../shared/catalog.js';
6
6
  import { getProjectContext } from '../../shared/paths.js';
7
- import { askForArchitectureSkill, askForDatabaseSkills, askForFrameworkSkills, askForLanguageSkills, askForMcpServers, askForProjectOverview, askForSupportedAgents, askForWorkflowSkills, renderIntro, } from '../interactive-guidance/index.js';
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
9
  import { getWorkflowTargets, renderMissingWorkflowFiles, writeSibuState } from '../workflow-target-planning/index.js';
10
10
  const defaultDependencies = {
11
11
  renderIntro,
12
12
  askForSupportedAgents,
13
13
  askForMcpServers,
14
+ askForNotionDocsParentPage,
14
15
  askForLanguageSkills,
15
16
  askForFrameworkSkills,
16
17
  askForDatabaseSkills,
@@ -40,6 +41,7 @@ export async function handleInitProject(_command, dependencies = defaultDependen
40
41
  }
41
42
  const selectedAgents = await dependencies.askForSupportedAgents();
42
43
  const selectedMcpServers = await dependencies.askForMcpServers();
44
+ const notionDocsParentPage = selectedMcpServers.some((server) => server.id === 'notion') ? await dependencies.askForNotionDocsParentPage() : undefined;
43
45
  const selectedLanguageSkills = await dependencies.askForLanguageSkills();
44
46
  const selectedFrameworkSkills = await dependencies.askForFrameworkSkills();
45
47
  const selectedDatabaseSkills = await dependencies.askForDatabaseSkills();
@@ -84,6 +86,7 @@ export async function handleInitProject(_command, dependencies = defaultDependen
84
86
  selectedWorkflowSkills,
85
87
  selectedDatabaseSkills,
86
88
  selectedMcpServers,
89
+ mcpServerConfigs: notionDocsParentPage ? { notion: { docsParentPage: notionDocsParentPage } } : undefined,
87
90
  targets,
88
91
  });
89
92
  log.success(`Created ${STATE_RELATIVE_PATH}`);
@@ -39,16 +39,13 @@ export function renderSkillRouting(contents, selectedLanguageSkills, selectedFra
39
39
  return contents.replace('{{OPTIONAL_SKILL_ROUTING}}', optionalRouting);
40
40
  }
41
41
  export function renderMcpConfig({ agentId, baseContents, selectedMcpServers, }) {
42
- const githubServer = selectedMcpServers.find((server) => server.id === 'github');
43
- if (!githubServer) {
42
+ if (selectedMcpServers.length === 0) {
44
43
  return baseContents ?? renderJsonMcpConfig({});
45
44
  }
46
45
  if (agentId === 'codex') {
47
- return renderCodexGithubMcpConfig(baseContents ?? '');
46
+ return renderCodexMcpConfig(baseContents ?? '', selectedMcpServers);
48
47
  }
49
- return renderJsonMcpConfig({
50
- github: buildGithubMcpServerConfig(agentId),
51
- });
48
+ return renderJsonMcpConfig(buildJsonMcpServerConfigs(agentId, selectedMcpServers));
52
49
  }
53
50
  export function extractProjectOverview(filePath) {
54
51
  if (!fs.existsSync(filePath)) {
@@ -59,13 +56,26 @@ export function extractProjectOverview(filePath) {
59
56
  const overview = match?.[1]?.trim();
60
57
  return overview || undefined;
61
58
  }
62
- function renderCodexGithubMcpConfig(baseContents) {
59
+ function renderCodexMcpConfig(baseContents, selectedMcpServers) {
63
60
  const trimmedBaseContents = baseContents.trimEnd();
61
+ const serverConfigs = selectedMcpServers.map((server) => buildCodexMcpServerConfig(server)).filter((config) => config.length > 0);
62
+ if (serverConfigs.length === 0) {
63
+ return baseContents;
64
+ }
64
65
  const separator = trimmedBaseContents ? '\n\n' : '';
65
- return `${trimmedBaseContents}${separator}[mcp_servers.github]
66
+ return `${trimmedBaseContents}${separator}${serverConfigs.join('\n\n')}\n`;
67
+ }
68
+ function buildCodexMcpServerConfig(server) {
69
+ if (server.id === 'github') {
70
+ return `[mcp_servers.github]
66
71
  url = "https://api.githubcopilot.com/mcp/"
67
- bearer_token_env_var = "GITHUB_PERSONAL_ACCESS_TOKEN"
68
- `;
72
+ bearer_token_env_var = "GITHUB_PERSONAL_ACCESS_TOKEN"`;
73
+ }
74
+ if (server.id === 'notion') {
75
+ return `[mcp_servers.notion]
76
+ url = "https://mcp.notion.com/mcp"`;
77
+ }
78
+ return '';
69
79
  }
70
80
  function getMcpConfigAgentId(templateRelativePath) {
71
81
  if (templateRelativePath === '.codex/config.toml') {
@@ -79,6 +89,18 @@ function getMcpConfigAgentId(templateRelativePath) {
79
89
  }
80
90
  return undefined;
81
91
  }
92
+ function buildJsonMcpServerConfigs(agentId, selectedMcpServers) {
93
+ const mcpServers = {};
94
+ for (const server of selectedMcpServers) {
95
+ if (server.id === 'github') {
96
+ mcpServers.github = buildGithubMcpServerConfig(agentId);
97
+ }
98
+ if (server.id === 'notion') {
99
+ mcpServers.notion = buildNotionMcpServerConfig();
100
+ }
101
+ }
102
+ return mcpServers;
103
+ }
82
104
  function buildGithubMcpServerConfig(agentId) {
83
105
  const headers = {
84
106
  Authorization: 'Bearer ${GITHUB_PERSONAL_ACCESS_TOKEN}',
@@ -95,6 +117,11 @@ function buildGithubMcpServerConfig(agentId) {
95
117
  headers,
96
118
  };
97
119
  }
120
+ function buildNotionMcpServerConfig() {
121
+ return {
122
+ url: 'https://mcp.notion.com/mcp',
123
+ };
124
+ }
98
125
  function renderJsonMcpConfig(mcpServers) {
99
126
  return `${JSON.stringify({ mcpServers }, null, 2)}\n`;
100
127
  }
@@ -61,12 +61,29 @@ function isSibuState(value) {
61
61
  (Array.isArray(state.selectedDatabaseSkills) && state.selectedDatabaseSkills.every((skill) => typeof skill === 'string'))) &&
62
62
  (state.selectedMcpServers === undefined ||
63
63
  (Array.isArray(state.selectedMcpServers) && state.selectedMcpServers.every((server) => typeof server === 'string'))) &&
64
+ (state.mcpServerConfigs === undefined || isMcpServerConfigs(state.mcpServerConfigs)) &&
64
65
  (state.reviewedArchitectureSkills === undefined ||
65
66
  (Array.isArray(state.reviewedArchitectureSkills) && state.reviewedArchitectureSkills.every((skill) => typeof skill === 'string'))) &&
66
67
  !!state.managedFiles &&
67
68
  typeof state.managedFiles === 'object' &&
68
69
  Object.values(state.managedFiles).every(isManagedFileState));
69
70
  }
71
+ function isMcpServerConfigs(value) {
72
+ if (!value || typeof value !== 'object') {
73
+ return false;
74
+ }
75
+ const configs = value;
76
+ if (configs.notion !== undefined) {
77
+ if (!configs.notion || typeof configs.notion !== 'object') {
78
+ return false;
79
+ }
80
+ const notionConfig = configs.notion;
81
+ if (typeof notionConfig.docsParentPage !== 'string') {
82
+ return false;
83
+ }
84
+ }
85
+ return true;
86
+ }
70
87
  function isManagedFileState(value) {
71
88
  if (!value || typeof value !== 'object') {
72
89
  return false;
@@ -206,6 +206,12 @@ export const SELECTABLE_MCP_SERVERS = [
206
206
  description: "Configure GitHub's official MCP server; Sibu writes config only, while prerequisites, runtime availability, credentials, and authentication remain user-owned",
207
207
  source: 'github/github-mcp-server',
208
208
  },
209
+ {
210
+ id: 'notion',
211
+ name: 'Notion MCP Server',
212
+ description: 'Configure Notion MCP server access; Sibu writes config only, while OAuth authentication, workspace access, page permissions, and credentials remain user-owned',
213
+ source: 'developers.notion.com/guides/mcp',
214
+ },
209
215
  ];
210
216
  export const SUPPORTED_AGENTS = [
211
217
  {
@@ -49,7 +49,7 @@ export function getSelectedSkillTargetsForAgents(selectedAgents, selectedLanguag
49
49
  return [...skillTargets.values()];
50
50
  }
51
51
  export function getSelectedMcpTargetsForAgents(selectedAgents, selectedMcpServers) {
52
- if (!selectedMcpServers.some((server) => server.id === 'github')) {
52
+ if (selectedMcpServers.length === 0) {
53
53
  return [];
54
54
  }
55
55
  return selectedAgents.flatMap((agent) => {
@@ -158,7 +158,7 @@ export function renderMissingWorkflowFiles({ missingTargets, overview, selectedL
158
158
  };
159
159
  });
160
160
  }
161
- export function writeSibuState({ rootPath, statePath, selectedAgents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = [], selectedDatabaseSkills = [], selectedMcpServers, targets, }) {
161
+ export function writeSibuState({ rootPath, statePath, selectedAgents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = [], selectedDatabaseSkills = [], selectedMcpServers, mcpServerConfigs, targets, }) {
162
162
  const previousState = readExistingState(statePath);
163
163
  const now = new Date().toISOString();
164
164
  const manifest = readTemplateManifest();
@@ -174,6 +174,7 @@ export function writeSibuState({ rootPath, statePath, selectedAgents, selectedLa
174
174
  selectedWorkflowSkills: selectedWorkflowSkills.map((skill) => skill.id),
175
175
  selectedDatabaseSkills: selectedDatabaseSkills.map((skill) => skill.id),
176
176
  ...(selectedMcpServers !== undefined ? { selectedMcpServers: selectedMcpServers.map((server) => server.id) } : {}),
177
+ ...(mcpServerConfigs ?? previousState?.mcpServerConfigs ? { mcpServerConfigs: mcpServerConfigs ?? previousState?.mcpServerConfigs } : {}),
177
178
  managedFiles: Object.fromEntries(targets
178
179
  .filter((target) => fs.existsSync(target.targetPath))
179
180
  .map((target) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juancr11/sibu",
3
- "version": "0.10.0",
3
+ "version": "0.11.1",
4
4
  "description": "CLI for setting up a local AI-augmented development workflow.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,5 +1,5 @@
1
1
  {
2
- "templateVersion": "89",
2
+ "templateVersion": "91",
3
3
  "templates": {
4
4
  "AGENTS.md": {
5
5
  "version": "28",
@@ -66,17 +66,17 @@
66
66
  ]
67
67
  },
68
68
  "skills/feature-brief-writer/SKILL.md": {
69
- "version": "9",
69
+ "version": "10",
70
70
  "description": "Mandatory feature brief writer skill installed once at the shared .agents/skills workspace path.",
71
71
  "changes": [
72
- "Removes Open Questions from the feature brief template and requires the interview to resolve material feature questions before drafting."
72
+ "Adds optional Notion export guidance after local feature briefs are written while keeping Markdown as the canonical artifact."
73
73
  ]
74
74
  },
75
75
  "skills/technical-design-writer/SKILL.md": {
76
- "version": "16",
76
+ "version": "17",
77
77
  "description": "Mandatory technical design writer skill installed once at the shared .agents/skills workspace path.",
78
78
  "changes": [
79
- "Requires technical designs crossing framework boundaries to name the allowed application/orchestration entrypoint and forbidden lower-level dependencies."
79
+ "Adds optional Notion export guidance after local technical designs are written while keeping Markdown as the canonical artifact."
80
80
  ]
81
81
  },
82
82
  "skills/typescript/SKILL.md": {
@@ -122,10 +122,10 @@
122
122
  ]
123
123
  },
124
124
  "skills/react/SKILL.md": {
125
- "version": "1",
125
+ "version": "2",
126
126
  "description": "Selectable React skill for component design, props, state ownership, and presentational boundaries.",
127
127
  "changes": [
128
- "Adds an optional React skill for projects that create or change React components, props, state ownership, or presentational and data-owning boundaries."
128
+ "Strengthens React component guidance around Single Responsibility Principle splits, stateless versus stateful boundaries, and one component per file by default."
129
129
  ]
130
130
  },
131
131
  "skills/scrum-master-planner/SKILL.md": {
@@ -157,10 +157,10 @@
157
157
  ]
158
158
  },
159
159
  "skills/ux-expert/SKILL.md": {
160
- "version": "6",
160
+ "version": "7",
161
161
  "description": "Selectable UX expert skill for UI-changing features, responsive design, flows, states, accessibility, and binding mockups.",
162
162
  "changes": [
163
- "Updates boundary wording to use Deep Module Maps."
163
+ "Adds optional Notion export guidance after local UX specs are written while keeping Markdown as the canonical artifact."
164
164
  ]
165
165
  }
166
166
  }
@@ -283,6 +283,31 @@ When shaping a feature brief, prefer:
283
283
  7. measurable success signals
284
284
  8. non-technical acceptance criteria
285
285
 
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
+
286
311
  ## Final response behavior
287
312
 
288
313
  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.
@@ -21,15 +21,17 @@ This skill covers React componentization, component responsibility, state owners
21
21
 
22
22
  ### 1. Treat components like focused functions
23
23
  - A component should have one clear reason to exist.
24
- - If a component does too many unrelated things, split it.
25
- - Prefer small components with clear names and explicit responsibilities.
26
- - Do not split components just to create more files; split when it improves understanding.
27
-
28
- ### 2. Separate presentational components from data-owning components
29
- - Presentational components should mostly render UI from props.
30
- - Data-owning components may fetch, subscribe, mutate, or coordinate data.
31
- - Keep it obvious which components interact with the server and which are purely presentational.
32
- - Avoid hiding server interactions deep inside components that look presentational.
24
+ - Apply the Single Responsibility Principle aggressively: split components whenever rendering, state ownership, event handling, data access, layout, formatting, or low-level UI concerns start to blur together.
25
+ - Create as many components as needed to keep each component easy to name, test, scan, and change independently.
26
+ - Prefer small components with clear names and explicit responsibilities over broad components with internal sections.
27
+ - Do not preserve a large component just to avoid adding files; a few extra focused components are better than one file that hides multiple responsibilities.
28
+
29
+ ### 2. Separate stateless, stateful, and data-owning components
30
+ - Stateless presentational components should render UI from props and avoid owning state or side effects.
31
+ - Stateful interaction components may own local UI state when that state belongs only to their focused responsibility.
32
+ - Data-owning components may fetch, subscribe, mutate, or coordinate server data.
33
+ - Keep stateless, stateful, and server-interacting responsibilities visibly separate; split components when those roles mix.
34
+ - Avoid hiding state transitions, server interactions, or mutations deep inside components that look presentational.
33
35
 
34
36
  ### 3. Keep props narrow and explicit
35
37
  - Pass only what the child component needs.
@@ -63,13 +65,18 @@ This skill covers React componentization, component responsibility, state owners
63
65
  - Name components by what they represent in the UI or product.
64
66
  - Keep server-interacting, stateful, and presentational responsibilities easy to identify from the component shape.
65
67
  - Avoid mixing data fetching, mutation, layout, formatting, and low-level UI rendering in one component.
68
+ - Each component should live in its own file by default; do not define multiple components in the same file just because they are currently small.
69
+ - If a helper component is only temporary, promote it to its own file as soon as it represents a meaningful UI responsibility.
66
70
 
67
71
  ## Decision rule
68
72
 
69
73
  When unsure, prefer:
70
74
  1. one clear responsibility per component
71
- 2. presentational components that render from props
72
- 3. data/server interaction in obvious owner components
73
- 4. local state unless coordination requires lifting it
74
- 5. composition over flag-heavy component APIs
75
- 6. no duplicated derived state
75
+ 2. more focused components instead of fewer overloaded components
76
+ 3. one component per file by default
77
+ 4. stateless presentational components that render from props
78
+ 5. stateful components only where local interaction state is genuinely owned
79
+ 6. data/server interaction in obvious owner components
80
+ 7. local state unless coordination requires lifting it
81
+ 8. composition over flag-heavy component APIs
82
+ 9. no duplicated derived state
@@ -158,6 +158,31 @@ Use this structure as a starting point. Delete sections that do not add value.
158
158
 
159
159
  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.
160
160
 
161
+ ## Optional Notion export after local write
162
+
163
+ 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.
164
+
165
+ 1. Do not check Notion export before the local file exists.
166
+ 2. After writing the local file, read `.sibu/state.json` if available.
167
+ 3. Offer export only when all are true:
168
+ - `selectedMcpServers` includes `notion`
169
+ - `mcpServerConfigs.notion.docsParentPage` is present
170
+ - Notion MCP tools are available in the current agent session
171
+ 4. If export is unavailable, do nothing else and finish with the normal local path response.
172
+ 5. If export is available, ask for explicit opt-in before creating or modifying any Notion page.
173
+ 6. If the user declines, do nothing else.
174
+ 7. If the user accepts, create or reuse Notion organization pages under the configured parent page:
175
+
176
+ ```txt
177
+ <docsParentPage>
178
+ └── <repo name>
179
+ └── Features
180
+ └── <feature name>
181
+ └── Technical Design
182
+ ```
183
+
184
+ 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.
185
+
161
186
  ## Final response behavior
162
187
 
163
188
  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.
@@ -118,6 +118,31 @@ Use only helpful sections from this shape:
118
118
 
119
119
  The Binding Mockups section is authoritative for downstream work unless this UX spec is revised.
120
120
 
121
+ ## Optional Notion export after local write
122
+
123
+ 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.
124
+
125
+ 1. Do not check Notion export before the local file exists.
126
+ 2. After writing the local file, read `.sibu/state.json` if available.
127
+ 3. Offer export only when all are true:
128
+ - `selectedMcpServers` includes `notion`
129
+ - `mcpServerConfigs.notion.docsParentPage` is present
130
+ - Notion MCP tools are available in the current agent session
131
+ 4. If export is unavailable, do nothing else and finish with the normal local path response.
132
+ 5. If export is available, ask for explicit opt-in before creating or modifying any Notion page.
133
+ 6. If the user declines, do nothing else.
134
+ 7. If the user accepts, create or reuse Notion organization pages under the configured parent page:
135
+
136
+ ```txt
137
+ <docsParentPage>
138
+ └── <repo name>
139
+ └── Features
140
+ └── <feature name>
141
+ └── UX Design
142
+ ```
143
+
144
+ 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.
145
+
121
146
  ## Final response behavior
122
147
 
123
148
  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.