@juancr11/sibu 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +66 -29
  2. package/bin/entrypoints/cli/create-program.js +1 -1
  3. package/bin/entrypoints/cli/execute-command.js +4 -7
  4. package/bin/modules/agent-tool-configuration/index.js +1 -0
  5. package/bin/modules/agent-tool-configuration/mcp-config.js +134 -0
  6. package/bin/modules/interactive-guidance/prompts.js +1 -1
  7. package/bin/modules/mcp-server-selection-management/list-mcp-servers/handler.js +2 -2
  8. package/bin/modules/mcp-server-selection-management/stop-mcp-server/handler.js +5 -5
  9. package/bin/modules/mcp-server-selection-management/use-mcp-server/handler.js +4 -3
  10. package/bin/modules/skill-selection-management/list-skills/handler.js +2 -2
  11. package/bin/modules/skill-selection-management/stop-managing-file/handler.js +5 -4
  12. package/bin/modules/skill-selection-management/use-skill/handler.js +5 -4
  13. package/bin/modules/{sync-review → sync-review-orchestrator}/apply-action.js +4 -3
  14. package/bin/modules/{sync-review → sync-review-orchestrator}/handler.js +4 -4
  15. package/bin/modules/{sync-review → sync-review-orchestrator}/index.js +1 -0
  16. package/bin/modules/{sync-review → sync-review-orchestrator}/log-preview.js +1 -1
  17. package/bin/modules/{sync-review → sync-review-orchestrator}/sync-preview.js +3 -3
  18. package/bin/modules/{sync-review → sync-review-orchestrator}/unsupported-agent-cleanup.js +2 -2
  19. package/bin/modules/{workflow-mutation-readiness → sync-review-orchestrator}/workflow-mutation-readiness.js +4 -4
  20. package/bin/modules/{workflow-target-planning → template-catalog}/index.js +2 -1
  21. package/bin/modules/{template-catalog-rendering → template-catalog}/templates.js +42 -115
  22. package/bin/modules/workflow-configuration-manager/index.js +6 -0
  23. package/bin/modules/workflow-configuration-manager/list-mcp-servers/handler.js +46 -0
  24. package/bin/modules/workflow-configuration-manager/list-skills/command.js +1 -0
  25. package/bin/modules/workflow-configuration-manager/list-skills/handler.js +75 -0
  26. package/bin/modules/workflow-configuration-manager/stop-managing-file/command.js +1 -0
  27. package/bin/modules/workflow-configuration-manager/stop-managing-file/handler.js +221 -0
  28. package/bin/modules/workflow-configuration-manager/stop-mcp-server/command.js +1 -0
  29. package/bin/modules/workflow-configuration-manager/stop-mcp-server/handler.js +198 -0
  30. package/bin/modules/workflow-configuration-manager/use-mcp-server/command.js +1 -0
  31. package/bin/modules/workflow-configuration-manager/use-mcp-server/handler.js +193 -0
  32. package/bin/modules/workflow-configuration-manager/use-skill/command.js +1 -0
  33. package/bin/modules/workflow-configuration-manager/use-skill/handler.js +314 -0
  34. package/bin/modules/workflow-health-inspector/command.js +1 -0
  35. package/bin/modules/{workflow-health-diagnosis → workflow-health-inspector}/handler.js +7 -7
  36. package/bin/modules/workflow-installer/command.js +1 -0
  37. package/bin/modules/{project-adoption → workflow-installer}/handler.js +5 -4
  38. package/bin/modules/workflow-state-ledger/index.js +2 -0
  39. package/bin/modules/workflow-state-ledger/state-path.js +1 -0
  40. package/bin/modules/{workflow-state-registry → workflow-state-ledger}/state.js +40 -1
  41. package/bin/{modules/workflow-target-planning/workflow-targets.js → shared/expected-workflow-targets.js} +3 -104
  42. package/bin/shared/paths.js +1 -1
  43. package/bin/support/expected-workflow-targets.js +131 -0
  44. package/bin/support/interactive-guidance/index.js +1 -0
  45. package/bin/support/interactive-guidance/prompts.js +275 -0
  46. package/bin/support/version-advisory/index.js +1 -0
  47. package/bin/support/version-advisory/npm-version.js +205 -0
  48. package/package.json +2 -1
  49. package/templates/AGENTS.md +8 -0
  50. package/templates/manifest.json +5 -4
  51. package/bin/modules/template-catalog-rendering/index.js +0 -1
  52. package/bin/modules/workflow-mutation-readiness/index.js +0 -1
  53. package/bin/modules/workflow-state-registry/index.js +0 -1
  54. /package/bin/{modules/project-adoption/command.js → entrypoints/cli-command-surface/index.js} +0 -0
  55. /package/bin/modules/{sync-review → sync-review-orchestrator}/action-prompt.js +0 -0
  56. /package/bin/modules/{sync-review → sync-review-orchestrator}/command.js +0 -0
  57. /package/bin/modules/{workflow-target-planning → template-catalog}/catalog.js +0 -0
  58. /package/bin/modules/{workflow-health-diagnosis → workflow-configuration-manager/list-mcp-servers}/command.js +0 -0
  59. /package/bin/modules/{workflow-health-diagnosis → workflow-health-inspector}/index.js +0 -0
  60. /package/bin/modules/{project-adoption → workflow-installer}/index.js +0 -0
@@ -0,0 +1,131 @@
1
+ import path from 'node:path';
2
+ import { MANDATORY_SKILLS, SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_DATABASE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_SKILLS, SELECTABLE_MCP_SERVERS, SELECTABLE_WORKFLOW_SKILLS, SESSION_START_HOOKS, SUPPORTED_AGENTS, } from '../modules/template-catalog/catalog.js';
3
+ import { getMcpConfigTargetsForAgents } from '../modules/agent-tool-configuration/index.js';
4
+ export function getSelectedLanguageSkillsFromState(state) {
5
+ return SELECTABLE_LANGUAGE_SKILLS.filter((skill) => state.selectedLanguageSkills?.includes(skill.id));
6
+ }
7
+ export function getSelectedFrameworkSkillsFromState(state) {
8
+ return SELECTABLE_FRAMEWORK_SKILLS.filter((skill) => state.selectedFrameworkSkills?.includes(skill.id));
9
+ }
10
+ export function getSelectedArchitectureSkillFromState(state) {
11
+ return SELECTABLE_ARCHITECTURE_SKILLS.find((skill) => skill.id === state.selectedArchitectureSkill);
12
+ }
13
+ export function getSelectedWorkflowSkillsFromState(state) {
14
+ return SELECTABLE_WORKFLOW_SKILLS.filter((skill) => state.selectedWorkflowSkills?.includes(skill.id));
15
+ }
16
+ export function getSelectedDatabaseSkillsFromState(state) {
17
+ return SELECTABLE_DATABASE_SKILLS.filter((skill) => state.selectedDatabaseSkills?.includes(skill.id));
18
+ }
19
+ export function getSelectedMcpServersFromState(state) {
20
+ return SELECTABLE_MCP_SERVERS.filter((server) => state.selectedMcpServers?.includes(server.id));
21
+ }
22
+ export function getSelectedSkillTargetsForAgents(selectedAgents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = [], selectedDatabaseSkills = []) {
23
+ const skillTargets = new Map();
24
+ const selectedSkills = [
25
+ ...MANDATORY_SKILLS,
26
+ ...selectedLanguageSkills,
27
+ ...selectedFrameworkSkills,
28
+ ...selectedDatabaseSkills,
29
+ ...(selectedArchitectureSkill ? [selectedArchitectureSkill] : []),
30
+ ...selectedWorkflowSkills,
31
+ ];
32
+ for (const agent of selectedAgents) {
33
+ for (const skill of selectedSkills) {
34
+ for (const skillTarget of getSkillTargetsForAgent(skill, agent.id)) {
35
+ skillTargets.set(skillTarget.targetRelativePath, skillTarget);
36
+ }
37
+ }
38
+ }
39
+ return [...skillTargets.values()];
40
+ }
41
+ export function getSkillTargetsForAgents(skill, selectedAgents) {
42
+ const skillTargets = new Map();
43
+ for (const agent of selectedAgents) {
44
+ for (const skillTarget of getSkillTargetsForAgent(skill, agent.id)) {
45
+ skillTargets.set(skillTarget.targetRelativePath, skillTarget);
46
+ }
47
+ }
48
+ return [...skillTargets.values()];
49
+ }
50
+ function getSkillTargetsForAgent(skill, agentId) {
51
+ const targets = [];
52
+ const targetRelativePath = skill.targetRelativePathsByAgent[agentId];
53
+ if (targetRelativePath) {
54
+ targets.push({
55
+ targetRelativePath,
56
+ templateRelativePath: skill.templateRelativePath,
57
+ });
58
+ }
59
+ for (const supplementalTarget of skill.supplementalTargetsByAgent?.[agentId] ?? []) {
60
+ targets.push(supplementalTarget);
61
+ }
62
+ return targets;
63
+ }
64
+ function getSessionStartHookTargetsForAgents(selectedAgents) {
65
+ const selectedAgentIds = new Set(selectedAgents.map((agent) => agent.id));
66
+ return SESSION_START_HOOKS.filter((hookTemplate) => selectedAgentIds.has(hookTemplate.agentId)).map((hookTemplate) => ({
67
+ targetRelativePath: hookTemplate.targetRelativePath,
68
+ templateRelativePath: hookTemplate.templateRelativePath,
69
+ }));
70
+ }
71
+ export function getWorkflowTargets(rootPath, selectedAgents, selectedLanguageSkills = [], selectedFrameworkSkills = [], selectedArchitectureSkill, selectedWorkflowSkills = [], selectedDatabaseSkills = [], selectedMcpServers = []) {
72
+ const targets = [
73
+ {
74
+ label: 'AGENTS.md',
75
+ targetPath: path.join(rootPath, 'AGENTS.md'),
76
+ templateRelativePath: 'AGENTS.md',
77
+ requiresProjectOverview: true,
78
+ targetKind: 'agent-support',
79
+ },
80
+ ...selectedAgents.flatMap((agent) => {
81
+ if (!agent.targetRelativePath || !agent.templateRelativePath) {
82
+ return [];
83
+ }
84
+ return [
85
+ {
86
+ label: agent.targetRelativePath,
87
+ targetPath: path.join(rootPath, agent.targetRelativePath),
88
+ templateRelativePath: agent.templateRelativePath,
89
+ requiresProjectOverview: false,
90
+ targetKind: 'agent-support',
91
+ },
92
+ ];
93
+ }),
94
+ ...getSelectedSkillTargetsForAgents(selectedAgents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, selectedDatabaseSkills).map((skillTarget) => ({
95
+ label: skillTarget.targetRelativePath,
96
+ targetPath: path.join(rootPath, skillTarget.targetRelativePath),
97
+ templateRelativePath: skillTarget.templateRelativePath,
98
+ requiresProjectOverview: false,
99
+ targetKind: 'skill',
100
+ })),
101
+ ...getSessionStartHookTargetsForAgents(selectedAgents).map((hookTarget) => ({
102
+ label: hookTarget.targetRelativePath,
103
+ targetPath: path.join(rootPath, hookTarget.targetRelativePath),
104
+ templateRelativePath: hookTarget.templateRelativePath,
105
+ requiresProjectOverview: false,
106
+ targetKind: 'agent-support',
107
+ })),
108
+ ];
109
+ for (const mcpTarget of getMcpConfigTargetsForAgents(selectedAgents, selectedMcpServers)) {
110
+ const targetPath = path.join(rootPath, mcpTarget.targetRelativePath);
111
+ const existingTarget = targets.find((target) => target.targetPath === targetPath);
112
+ if (existingTarget) {
113
+ existingTarget.selectedMcpServers = selectedMcpServers;
114
+ existingTarget.mcpConfigAgentId = mcpTarget.agentId;
115
+ continue;
116
+ }
117
+ targets.push({
118
+ label: mcpTarget.targetRelativePath,
119
+ targetPath,
120
+ templateRelativePath: mcpTarget.templateRelativePath,
121
+ requiresProjectOverview: false,
122
+ targetKind: 'mcp-config',
123
+ mcpConfigAgentId: mcpTarget.agentId,
124
+ selectedMcpServers,
125
+ });
126
+ }
127
+ return targets;
128
+ }
129
+ export function getSelectedAgentsFromState(state) {
130
+ return SUPPORTED_AGENTS.filter((agent) => state.selectedAgents.includes(agent.id));
131
+ }
@@ -0,0 +1 @@
1
+ export { askForArchitectureSkill, askForDatabaseSkills, askForFrameworkSkills, askForLanguageSkills, askForMcpServers, askForNotionDocsParentPage, askForMissingFrameworkSkills, askForNewArchitectureSkill, askForNewLanguageSkills, askForProjectOverview, askForSupportedAgents, askForWorkflowSkills, MCP_SERVER_SELECTION_MESSAGE, getPromptableWorkflowSkills, renderIntro, shouldAskForNewLanguageSkills, } from './prompts.js';
@@ -0,0 +1,275 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cancel, isCancel, multiselect, select, text } from '@clack/prompts';
3
+ import gradient from 'gradient-string';
4
+ import { Box, Text, render, useApp } from 'ink';
5
+ import { useEffect } from 'react';
6
+ import { SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_DATABASE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_SKILLS, SELECTABLE_MCP_SERVERS, SELECTABLE_WORKFLOW_SKILLS, SUPPORTED_AGENTS, } from '../../modules/template-catalog/index.js';
7
+ const NONE_OPTION_ID = 'none';
8
+ export const MCP_SERVER_SELECTION_MESSAGE = 'Select optional MCP servers to configure. Sibu writes config files only; you own prerequisites and authentication.';
9
+ export async function renderIntro() {
10
+ console.log(gradient(['#39ff14', '#00e5ff', '#9b5de5']).multiline('⧖ S I B U ⧖'));
11
+ const app = render(_jsx(IntroPanel, {}));
12
+ await app.waitUntilExit();
13
+ }
14
+ function IntroPanel() {
15
+ const { exit } = useApp();
16
+ useEffect(() => {
17
+ const timer = setTimeout(exit, 650);
18
+ return () => clearTimeout(timer);
19
+ }, [exit]);
20
+ return (_jsxs(Box, { borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, flexDirection: "column", children: [_jsx(Text, { color: "cyanBright", children: "Sibu is ready" }), _jsx(Text, { color: "greenBright", children: "Build. Test. Rewind. Improve." })] }));
21
+ }
22
+ export async function askForSupportedAgents() {
23
+ const selectedAgentIds = await multiselect({
24
+ message: 'Select the agents this project should support.',
25
+ required: true,
26
+ options: SUPPORTED_AGENTS.map((agent) => ({
27
+ value: agent.id,
28
+ label: agent.name,
29
+ hint: agent.description,
30
+ })),
31
+ });
32
+ if (isCancel(selectedAgentIds)) {
33
+ cancel('Initialization cancelled.');
34
+ process.exit(0);
35
+ }
36
+ return SUPPORTED_AGENTS.filter((agent) => selectedAgentIds.includes(agent.id));
37
+ }
38
+ export async function askForLanguageSkills() {
39
+ const selectedLanguageSkillIds = await multiselect({
40
+ message: 'Select the languages this project should support.',
41
+ required: false,
42
+ options: SELECTABLE_LANGUAGE_SKILLS.map((skill) => ({
43
+ value: skill.id,
44
+ label: skill.name,
45
+ hint: skill.description,
46
+ })),
47
+ });
48
+ if (isCancel(selectedLanguageSkillIds)) {
49
+ cancel('Initialization cancelled.');
50
+ process.exit(0);
51
+ }
52
+ return SELECTABLE_LANGUAGE_SKILLS.filter((skill) => selectedLanguageSkillIds.includes(skill.id));
53
+ }
54
+ export async function askForFrameworkSkills() {
55
+ return askForFrameworkSkillSelection({
56
+ message: 'Select the frameworks this project should support.',
57
+ cancelMessage: 'Initialization cancelled.',
58
+ });
59
+ }
60
+ export async function askForDatabaseSkills() {
61
+ const selectedDatabaseSkillIds = await multiselect({
62
+ message: 'Select the database skills this project should support.',
63
+ required: false,
64
+ options: SELECTABLE_DATABASE_SKILLS.map((skill) => ({
65
+ value: skill.id,
66
+ label: skill.name,
67
+ hint: skill.description,
68
+ })),
69
+ });
70
+ if (isCancel(selectedDatabaseSkillIds)) {
71
+ cancel('Initialization cancelled.');
72
+ process.exit(0);
73
+ }
74
+ return SELECTABLE_DATABASE_SKILLS.filter((skill) => selectedDatabaseSkillIds.includes(skill.id));
75
+ }
76
+ export async function askForMcpServers() {
77
+ const selectedMcpServerIds = await multiselect({
78
+ message: MCP_SERVER_SELECTION_MESSAGE,
79
+ required: false,
80
+ options: SELECTABLE_MCP_SERVERS.map((server) => ({
81
+ value: server.id,
82
+ label: server.name,
83
+ hint: server.description,
84
+ })),
85
+ });
86
+ if (isCancel(selectedMcpServerIds)) {
87
+ cancel('Initialization cancelled.');
88
+ process.exit(0);
89
+ }
90
+ return SELECTABLE_MCP_SERVERS.filter((server) => selectedMcpServerIds.includes(server.id));
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
+ }
108
+ async function askForFrameworkSkillSelection({ message, cancelMessage, }) {
109
+ const selectedFrameworkSkillIds = await multiselect({
110
+ message,
111
+ required: false,
112
+ options: [
113
+ { value: NONE_OPTION_ID, label: 'None', hint: 'Do not install framework-specific guidance.' },
114
+ ...SELECTABLE_FRAMEWORK_SKILLS.map((skill) => ({
115
+ value: skill.id,
116
+ label: skill.name,
117
+ hint: skill.description,
118
+ })),
119
+ ],
120
+ });
121
+ if (isCancel(selectedFrameworkSkillIds)) {
122
+ cancel(cancelMessage);
123
+ process.exit(0);
124
+ }
125
+ if (selectedFrameworkSkillIds.includes(NONE_OPTION_ID)) {
126
+ return [];
127
+ }
128
+ return SELECTABLE_FRAMEWORK_SKILLS.filter((skill) => selectedFrameworkSkillIds.includes(skill.id));
129
+ }
130
+ export async function askForArchitectureSkill() {
131
+ const selectedArchitectureSkillId = await select({
132
+ message: 'Select an architecture style for this project.',
133
+ options: [
134
+ { value: 'none', label: 'None', hint: 'Do not install opinionated architecture guidance.' },
135
+ ...SELECTABLE_ARCHITECTURE_SKILLS.map((skill) => ({
136
+ value: skill.id,
137
+ label: skill.name,
138
+ hint: skill.description,
139
+ })),
140
+ ],
141
+ });
142
+ if (isCancel(selectedArchitectureSkillId)) {
143
+ cancel('Initialization cancelled.');
144
+ process.exit(0);
145
+ }
146
+ if (selectedArchitectureSkillId === 'none') {
147
+ return undefined;
148
+ }
149
+ return SELECTABLE_ARCHITECTURE_SKILLS.find((skill) => skill.id === selectedArchitectureSkillId);
150
+ }
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);
157
+ const selectedWorkflowSkillIds = await multiselect({
158
+ message: 'Select optional workflow skills for this project.',
159
+ required: false,
160
+ options: promptableWorkflowSkills.map((skill) => ({
161
+ value: skill.id,
162
+ label: skill.name,
163
+ hint: skill.description,
164
+ })),
165
+ });
166
+ if (isCancel(selectedWorkflowSkillIds)) {
167
+ cancel('Initialization cancelled.');
168
+ process.exit(0);
169
+ }
170
+ return promptableWorkflowSkills.filter((skill) => selectedWorkflowSkillIds.includes(skill.id));
171
+ }
172
+ export function shouldAskForNewLanguageSkills(state) {
173
+ return (state.selectedLanguageSkills?.length ?? 0) === 0;
174
+ }
175
+ export async function askForNewLanguageSkills(state) {
176
+ if (!shouldAskForNewLanguageSkills(state)) {
177
+ return { state, changedState: false };
178
+ }
179
+ const selectedLanguageSkillIds = new Set(state.selectedLanguageSkills ?? []);
180
+ const unselectedLanguageSkills = SELECTABLE_LANGUAGE_SKILLS.filter((skill) => !selectedLanguageSkillIds.has(skill.id));
181
+ if (unselectedLanguageSkills.length === 0) {
182
+ return { state, changedState: false };
183
+ }
184
+ const selectedNewLanguageSkillIds = await multiselect({
185
+ message: 'Select any new languages this project should support.',
186
+ required: false,
187
+ options: unselectedLanguageSkills.map((skill) => ({
188
+ value: skill.id,
189
+ label: skill.name,
190
+ hint: skill.description,
191
+ })),
192
+ });
193
+ if (isCancel(selectedNewLanguageSkillIds)) {
194
+ cancel('Sync cancelled.');
195
+ process.exit(0);
196
+ }
197
+ for (const selectedSkillId of selectedNewLanguageSkillIds) {
198
+ selectedLanguageSkillIds.add(selectedSkillId);
199
+ }
200
+ if (selectedNewLanguageSkillIds.length === 0) {
201
+ return { state, changedState: false };
202
+ }
203
+ return {
204
+ state: {
205
+ ...state,
206
+ selectedLanguageSkills: [...selectedLanguageSkillIds],
207
+ updatedAt: new Date().toISOString(),
208
+ },
209
+ changedState: true,
210
+ };
211
+ }
212
+ export async function askForMissingFrameworkSkills(state) {
213
+ if (state.selectedFrameworkSkills !== undefined) {
214
+ return { state, changedState: false };
215
+ }
216
+ const selectedFrameworkSkills = await askForFrameworkSkillSelection({
217
+ message: 'Select the frameworks this project should support.',
218
+ cancelMessage: 'Sync cancelled.',
219
+ });
220
+ return {
221
+ state: {
222
+ ...state,
223
+ selectedFrameworkSkills: selectedFrameworkSkills.map((skill) => skill.id),
224
+ updatedAt: new Date().toISOString(),
225
+ },
226
+ changedState: true,
227
+ };
228
+ }
229
+ export async function askForNewArchitectureSkill(state) {
230
+ if (state.selectedArchitectureSkill) {
231
+ return { state, changedState: false };
232
+ }
233
+ const selectedArchitectureSkillId = await select({
234
+ message: 'Select an architecture style for this project.',
235
+ options: [
236
+ { value: 'none', label: 'None', hint: 'Do not install opinionated architecture guidance.' },
237
+ ...SELECTABLE_ARCHITECTURE_SKILLS.map((skill) => ({
238
+ value: skill.id,
239
+ label: skill.name,
240
+ hint: skill.description,
241
+ })),
242
+ ],
243
+ });
244
+ if (isCancel(selectedArchitectureSkillId)) {
245
+ cancel('Sync cancelled.');
246
+ process.exit(0);
247
+ }
248
+ return {
249
+ state: selectedArchitectureSkillId === 'none'
250
+ ? state
251
+ : {
252
+ ...state,
253
+ selectedArchitectureSkill: selectedArchitectureSkillId,
254
+ updatedAt: new Date().toISOString(),
255
+ },
256
+ changedState: selectedArchitectureSkillId !== 'none',
257
+ };
258
+ }
259
+ export async function askForProjectOverview() {
260
+ const overview = await text({
261
+ message: 'Tell me what this project is about.',
262
+ placeholder: 'A local-first notes app for software teams.',
263
+ validate(value) {
264
+ if (!value?.trim()) {
265
+ return 'Please enter a short project overview so I can create AGENTS.md.';
266
+ }
267
+ return undefined;
268
+ },
269
+ });
270
+ if (isCancel(overview)) {
271
+ cancel('Initialization cancelled.');
272
+ process.exit(0);
273
+ }
274
+ return overview;
275
+ }
@@ -0,0 +1 @@
1
+ export { checkForLatestSibuVersion, SIBU_CACHE_HOME_ENV, SIBU_PACKAGE_NAME, SIBU_VERSION } from './npm-version.js';
@@ -0,0 +1,205 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ export const SIBU_PACKAGE_NAME = '@juancr11/sibu';
6
+ export const SIBU_VERSION = readPackageVersion();
7
+ export const NPM_VERSION_LOOKUP_MODE_ENV = 'SIBU_NPM_LOOKUP_MODE';
8
+ export const NPM_VERSION_OVERRIDE_ENV = 'SIBU_NPM_LATEST_VERSION';
9
+ export const SIBU_CACHE_HOME_ENV = 'SIBU_CACHE_HOME';
10
+ export const SUPPORTED_NPM_LOOKUP_MODES = ['live', 'offline'];
11
+ const NPM_VERSION_CACHE_TTL_MS = 6 * 60 * 60 * 1000;
12
+ const NPM_REGISTRY_URL = 'https://registry.npmjs.org';
13
+ export async function checkForLatestSibuVersion(options = {}) {
14
+ const now = options.now ?? new Date();
15
+ const overrideResult = readOverrideResult(now);
16
+ if (overrideResult) {
17
+ return overrideResult;
18
+ }
19
+ const cachedRecord = readCachedNpmVersionResult();
20
+ if (cachedRecord.ok && isFreshCacheRecord(cachedRecord.record, now)) {
21
+ return {
22
+ ...cachedRecord.record,
23
+ source: 'cache',
24
+ };
25
+ }
26
+ const liveOutcome = await fetchLatestVersionOutcome({
27
+ fetchImpl: options.fetchImpl ?? fetch,
28
+ now,
29
+ });
30
+ writeCachedNpmVersionResult(liveOutcome);
31
+ return liveOutcome;
32
+ }
33
+ async function fetchLatestVersionOutcome({ fetchImpl, now }) {
34
+ try {
35
+ const response = await fetchImpl(`${NPM_REGISTRY_URL}/${encodeURIComponent(SIBU_PACKAGE_NAME)}/latest`, {
36
+ headers: { accept: 'application/json' },
37
+ });
38
+ if (!response.ok) {
39
+ return buildUnavailableOutcome({ checkedAt: now.toISOString(), reason: 'network-error' });
40
+ }
41
+ const payload = (await response.json());
42
+ const latestVersion = getLatestVersionFromPayload(payload);
43
+ if (!latestVersion) {
44
+ return buildUnavailableOutcome({ checkedAt: now.toISOString(), reason: 'invalid-response' });
45
+ }
46
+ return buildVersionedOutcome({
47
+ checkedAt: now.toISOString(),
48
+ latestVersion,
49
+ source: 'live',
50
+ status: compareVersions(latestVersion, SIBU_VERSION) > 0 ? 'update-available' : 'up-to-date',
51
+ });
52
+ }
53
+ catch {
54
+ return buildUnavailableOutcome({ checkedAt: now.toISOString(), reason: 'network-error' });
55
+ }
56
+ }
57
+ function readOverrideResult(now) {
58
+ const overrideMode = readLookupModeOverride();
59
+ if (overrideMode === 'offline') {
60
+ return buildUnavailableOutcome({
61
+ checkedAt: now.toISOString(),
62
+ reason: 'override',
63
+ source: 'override',
64
+ });
65
+ }
66
+ const overrideVersion = process.env[NPM_VERSION_OVERRIDE_ENV]?.trim();
67
+ if (!overrideVersion) {
68
+ return undefined;
69
+ }
70
+ return buildVersionedOutcome({
71
+ checkedAt: now.toISOString(),
72
+ latestVersion: overrideVersion,
73
+ source: 'override',
74
+ status: compareVersions(overrideVersion, SIBU_VERSION) > 0 ? 'update-available' : 'up-to-date',
75
+ });
76
+ }
77
+ function readLookupModeOverride() {
78
+ const value = process.env[NPM_VERSION_LOOKUP_MODE_ENV]?.trim().toLowerCase();
79
+ if (!value) {
80
+ return undefined;
81
+ }
82
+ return SUPPORTED_NPM_LOOKUP_MODES.find((mode) => mode === value);
83
+ }
84
+ function readCachedNpmVersionResult() {
85
+ const cachePath = getNpmVersionCachePath();
86
+ if (!fs.existsSync(cachePath)) {
87
+ return { ok: false, reason: 'missing' };
88
+ }
89
+ try {
90
+ const payload = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
91
+ if (!isNpmVersionCacheRecord(payload)) {
92
+ return { ok: false, reason: 'invalid' };
93
+ }
94
+ return { ok: true, record: payload };
95
+ }
96
+ catch {
97
+ return { ok: false, reason: 'invalid' };
98
+ }
99
+ }
100
+ function writeCachedNpmVersionResult(result) {
101
+ const cachePath = getNpmVersionCachePath();
102
+ fs.mkdirSync(path.dirname(cachePath), { recursive: true });
103
+ fs.writeFileSync(cachePath, `${JSON.stringify(result, null, 2)}\n`, 'utf8');
104
+ }
105
+ function isFreshCacheRecord(record, now) {
106
+ const checkedAt = Date.parse(record.checkedAt);
107
+ if (Number.isNaN(checkedAt)) {
108
+ return false;
109
+ }
110
+ return now.getTime() - checkedAt < NPM_VERSION_CACHE_TTL_MS;
111
+ }
112
+ function getLatestVersionFromPayload(payload) {
113
+ if (!payload || typeof payload !== 'object') {
114
+ return undefined;
115
+ }
116
+ const version = payload.version;
117
+ return typeof version === 'string' && version.trim() ? version.trim() : undefined;
118
+ }
119
+ function buildVersionedOutcome({ checkedAt, latestVersion, source, status, }) {
120
+ return {
121
+ checkedAt,
122
+ currentVersion: SIBU_VERSION,
123
+ latestVersion,
124
+ packageName: SIBU_PACKAGE_NAME,
125
+ source,
126
+ status,
127
+ };
128
+ }
129
+ function buildUnavailableOutcome({ checkedAt, reason, source = 'live', }) {
130
+ return {
131
+ checkedAt,
132
+ packageName: SIBU_PACKAGE_NAME,
133
+ reason,
134
+ source,
135
+ status: 'unavailable',
136
+ };
137
+ }
138
+ function compareVersions(left, right) {
139
+ const leftParts = splitVersion(left);
140
+ const rightParts = splitVersion(right);
141
+ const maxLength = Math.max(leftParts.core.length, rightParts.core.length);
142
+ for (let index = 0; index < maxLength; index += 1) {
143
+ const leftValue = leftParts.core[index] ?? 0;
144
+ const rightValue = rightParts.core[index] ?? 0;
145
+ if (leftValue !== rightValue) {
146
+ return leftValue > rightValue ? 1 : -1;
147
+ }
148
+ }
149
+ if (leftParts.prerelease === rightParts.prerelease) {
150
+ return 0;
151
+ }
152
+ if (!leftParts.prerelease) {
153
+ return 1;
154
+ }
155
+ if (!rightParts.prerelease) {
156
+ return -1;
157
+ }
158
+ return leftParts.prerelease.localeCompare(rightParts.prerelease);
159
+ }
160
+ function splitVersion(version) {
161
+ const [core, prerelease] = version.trim().split('-', 2);
162
+ return {
163
+ core: core.split('.').map((part) => Number.parseInt(part, 10) || 0),
164
+ prerelease,
165
+ };
166
+ }
167
+ function isNpmVersionCacheRecord(value) {
168
+ if (!value || typeof value !== 'object') {
169
+ return false;
170
+ }
171
+ const record = value;
172
+ if (record.status === 'unavailable') {
173
+ return (typeof record.checkedAt === 'string' &&
174
+ typeof record.packageName === 'string' &&
175
+ typeof record.reason === 'string' &&
176
+ typeof record.source === 'string');
177
+ }
178
+ return ((record.status === 'up-to-date' || record.status === 'update-available') &&
179
+ typeof record.checkedAt === 'string' &&
180
+ typeof record.currentVersion === 'string' &&
181
+ typeof record.latestVersion === 'string' &&
182
+ typeof record.packageName === 'string' &&
183
+ typeof record.source === 'string');
184
+ }
185
+ function getNpmVersionCachePath() {
186
+ return path.join(getSibuCacheRoot(), 'cache', 'npm-version.json');
187
+ }
188
+ function getSibuCacheRoot() {
189
+ const override = process.env[SIBU_CACHE_HOME_ENV]?.trim();
190
+ if (override) {
191
+ return path.resolve(override);
192
+ }
193
+ return path.join(os.homedir(), '.sibu');
194
+ }
195
+ function readPackageVersion() {
196
+ const packageJsonPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..', 'package.json');
197
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
198
+ if (!isPackageJsonWithVersion(packageJson)) {
199
+ throw new Error(`Could not read Sibu version from ${packageJsonPath}.`);
200
+ }
201
+ return packageJson.version;
202
+ }
203
+ function isPackageJsonWithVersion(value) {
204
+ return Boolean(value && typeof value === 'object' && typeof value.version === 'string');
205
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juancr11/sibu",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "CLI for setting up a local AI-augmented development workflow.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,6 +19,7 @@
19
19
  "bin/admin/",
20
20
  "bin/modules/",
21
21
  "bin/shared/",
22
+ "bin/support/",
22
23
  "!bin/shared/state.js",
23
24
  "!bin/**/*.test.js",
24
25
  "!bin/echo.js",
@@ -35,6 +35,14 @@ Keep responses concise by default, but spend the context needed for correctness,
35
35
  - Ask focused questions when needed; otherwise make reasonable assumptions and proceed.
36
36
  - If the user wants more detail, they can ask follow-up questions.
37
37
 
38
+ ## Judgment and honesty
39
+
40
+ - Be direct and respectful. Avoid patronizing praise, performative agreement, and over-reassurance.
41
+ - Do not invent certainty or provide a polished answer when important context is missing; state uncertainty, ask one focused question, or verify first.
42
+ - Make low-risk assumptions only when they are clearly labeled.
43
+ - Challenge the user when there is a clear factual, safety/security, engineering-quality, repo-instruction, product-goal, or context-reliability reason.
44
+ - When challenging, give a short reason and a better alternative. Do not argue preferences or nitpick style; preserve user control after material tradeoffs are acknowledged unless safety or project rules require refusal.
45
+
38
46
  ## Skill routing
39
47
 
40
48
  For planned product/feature work, use this pipeline: product vision -> business domain model -> capabilities map -> deep module map / feature brief -> technical design -> optional UX -> epics/stories -> AI executor. Business Domain Model work sits after Product Vision and before the Capabilities Map. Deep Module Map and Feature Brief work are sibling downstream artifacts from Product Vision, Business Domain Model, and Capabilities Map. Technical Design remains downstream of both Feature Brief and Deep Module Map, with Scrum planning and AI executor flows after Technical Design. Narrow code fixes and small local changes do not require the full pipeline unless product scope, module ownership, or architecture direction is unclear.
@@ -1,12 +1,13 @@
1
1
  {
2
- "templateVersion": "132",
2
+ "templateVersion": "133",
3
3
  "templates": {
4
4
  "AGENTS.md": {
5
- "version": "33",
5
+ "version": "34",
6
6
  "description": "Project-level agent instructions and Sibu maintenance guidance.",
7
7
  "changes": [
8
- "Adds the Capabilities Map as a formal pipeline stage before sibling Deep Module Map and Feature Brief work.",
9
- "Routes capability map, capability coverage, missing capability, and capability gap requests to capabilities-map-writer."
8
+ "Adds judgment and honesty guidance for direct, non-condescending collaboration.",
9
+ "Clarifies that agents should state uncertainty, ask focused questions, or verify instead of inventing certainty.",
10
+ "Defines when agents should challenge users for clear factual, safety, quality, instruction, product, or context-reliability reasons."
10
11
  ]
11
12
  },
12
13
  ".codex/config.toml": {
@@ -1 +0,0 @@
1
- export { extractProjectOverview, getTemplateVersion, readTemplate, readTemplateManifest, renderMcpConfig, renderSkillRouting, renderTemplateForSync, renderWorkerToolboxRoutingPlaceholders, renderWorkerToolboxRouting, } from './templates.js';
@@ -1 +0,0 @@
1
- export { getWorkflowMutationReadiness } from './workflow-mutation-readiness.js';
@@ -1 +0,0 @@
1
- export { cloneState, hasReviewedTemplateVersion, readExistingState, readStateForDoctor, writeStateFile } from './state.js';