@juancr11/sibu 0.6.0 → 0.8.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 (69) hide show
  1. package/bin/admin/changelog.js +155 -0
  2. package/bin/admin/release.js +217 -0
  3. package/bin/entrypoints/cli/create-program.js +1 -1
  4. package/bin/entrypoints/cli/execute-command.js +6 -6
  5. package/bin/modules/interactive-guidance/index.js +1 -0
  6. package/bin/{shared → modules/interactive-guidance}/prompts.js +17 -1
  7. package/bin/modules/maintainer-release-support/generate-changelog/changelog-format.js +213 -0
  8. package/bin/modules/maintainer-release-support/generate-changelog/changelog-writer.js +91 -0
  9. package/bin/modules/maintainer-release-support/generate-changelog/git-history.js +93 -0
  10. package/bin/modules/maintainer-release-support/generate-changelog/handler.js +167 -0
  11. package/bin/modules/maintainer-release-support/generate-changelog/semver.js +40 -0
  12. package/bin/modules/maintainer-release-support/index.js +2 -0
  13. package/bin/modules/maintainer-release-support/release-workflow/git-release.js +114 -0
  14. package/bin/modules/maintainer-release-support/release-workflow/handler.js +369 -0
  15. package/bin/modules/maintainer-release-support/release-workflow/package-json.js +181 -0
  16. package/bin/modules/maintainer-release-support/release-workflow/release-plan.js +87 -0
  17. package/bin/{features/init-project → modules/project-adoption}/handler.js +7 -5
  18. package/bin/modules/project-adoption/index.js +1 -0
  19. package/bin/modules/skill-selection-management/index.js +3 -0
  20. package/bin/{features → modules/skill-selection-management}/list-skills/handler.js +14 -4
  21. package/bin/{features → modules/skill-selection-management}/stop-managing-file/handler.js +15 -8
  22. package/bin/modules/skill-selection-management/use-skill/command.js +1 -0
  23. package/bin/{features → modules/skill-selection-management}/use-skill/handler.js +42 -8
  24. package/bin/{features/sync-project → modules/sync-review}/apply-action.js +5 -3
  25. package/bin/modules/sync-review/command.js +1 -0
  26. package/bin/{features/sync-project → modules/sync-review}/handler.js +4 -4
  27. package/bin/modules/sync-review/index.js +5 -0
  28. package/bin/{shared → modules/sync-review}/sync-preview.js +11 -7
  29. package/bin/modules/template-catalog-rendering/index.js +1 -0
  30. package/bin/modules/template-catalog-rendering/templates.js +60 -0
  31. package/bin/modules/version-advisory/index.js +1 -0
  32. package/bin/{shared → modules/version-advisory}/npm-version.js +29 -2
  33. package/bin/modules/workflow-health-diagnosis/command.js +1 -0
  34. package/bin/{features/doctor-project → modules/workflow-health-diagnosis}/handler.js +13 -7
  35. package/bin/modules/workflow-health-diagnosis/index.js +1 -0
  36. package/bin/modules/workflow-mutation-readiness/index.js +1 -0
  37. package/bin/{shared → modules/workflow-mutation-readiness}/workflow-mutation-readiness.js +3 -3
  38. package/bin/modules/workflow-state-registry/index.js +1 -0
  39. package/bin/{shared → modules/workflow-state-registry}/state.js +3 -1
  40. package/bin/modules/workflow-target-planning/catalog.js +252 -0
  41. package/bin/modules/workflow-target-planning/index.js +2 -0
  42. package/bin/modules/workflow-target-planning/workflow-targets.js +130 -0
  43. package/bin/shared/catalog.js +0 -253
  44. package/bin/shared/paths.js +1 -12
  45. package/bin/shared/workflow-targets.js +3 -2
  46. package/package.json +4 -2
  47. package/templates/AGENTS.md +3 -3
  48. package/templates/manifest.json +38 -27
  49. package/templates/skills/ai-implementation-plan-executor/SKILL.md +74 -70
  50. package/templates/skills/ai-implementation-planner/SKILL.md +24 -17
  51. package/templates/skills/architecture/command-pattern/SKILL.md +32 -20
  52. package/templates/skills/architecture/ddd-hexagonal/SKILL.md +14 -9
  53. package/templates/skills/deep-module-map-writer/SKILL.md +241 -0
  54. package/templates/skills/feature-brief-writer/SKILL.md +24 -24
  55. package/templates/skills/postgresql-expert/SKILL.md +72 -0
  56. package/templates/skills/product-vision-writer/SKILL.md +2 -2
  57. package/templates/skills/scrum-master-planner/SKILL.md +2 -2
  58. package/templates/skills/technical-design-writer/SKILL.md +15 -15
  59. package/templates/skills/ux-expert/SKILL.md +1 -1
  60. package/bin/features/sync-project/preview.js +0 -1
  61. package/templates/skills/product-context-map-writer/SKILL.md +0 -211
  62. /package/bin/{features/doctor-project/command.js → modules/cli-command-surface/index.js} +0 -0
  63. /package/bin/{features/init-project → modules/maintainer-release-support/generate-changelog}/command.js +0 -0
  64. /package/bin/{features/list-skills → modules/maintainer-release-support/release-workflow}/command.js +0 -0
  65. /package/bin/{features/stop-managing-file → modules/project-adoption}/command.js +0 -0
  66. /package/bin/{features/sync-project → modules/skill-selection-management/list-skills}/command.js +0 -0
  67. /package/bin/{features/use-skill → modules/skill-selection-management/stop-managing-file}/command.js +0 -0
  68. /package/bin/{features/sync-project → modules/sync-review}/action-prompt.js +0 -0
  69. /package/bin/{features/sync-project → modules/sync-review}/log-preview.js +0 -0
@@ -0,0 +1,3 @@
1
+ export { handleListSkills } from './list-skills/handler.js';
2
+ export { getNextSkillSelection, handleUseSkill } from './use-skill/handler.js';
3
+ export { handleStopManagingFile, stopSelectedSkill } from './stop-managing-file/handler.js';
@@ -1,9 +1,9 @@
1
1
  import { intro, log, outro } from '@clack/prompts';
2
2
  import chalk from 'chalk';
3
- import { SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_SKILLS, SELECTABLE_WORKFLOW_SKILLS } from '../../shared/catalog.js';
4
- import { getProjectContext } from '../../shared/paths.js';
5
- import { renderIntro } from '../../shared/prompts.js';
6
- import { readStateForDoctor } from '../../shared/state.js';
3
+ import { SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_DATABASE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_SKILLS, SELECTABLE_WORKFLOW_SKILLS } from '../../workflow-target-planning/index.js';
4
+ import { getProjectContext } from '../../../shared/paths.js';
5
+ import { renderIntro } from '../../interactive-guidance/index.js';
6
+ import { readStateForDoctor } from '../../workflow-state-registry/index.js';
7
7
  export async function handleListSkills(_command) {
8
8
  await renderIntro();
9
9
  intro(chalk.cyan('Available workflow skills'));
@@ -16,6 +16,7 @@ export async function handleListSkills(_command) {
16
16
  }
17
17
  logSkillGroup('Languages', getLanguageSkillItems(state));
18
18
  logSkillGroup('Frameworks', getFrameworkSkillItems(state));
19
+ logSkillGroup('Databases', getDatabaseSkillItems(state));
19
20
  logSkillGroup('Architecture', getArchitectureSkillItems(state));
20
21
  logSkillGroup('Workflow', getWorkflowSkillItems(state));
21
22
  outro(chalk.green('Skill list ready.'));
@@ -38,6 +39,15 @@ function getFrameworkSkillItems(state) {
38
39
  selected: selectedSkillIds.has(skill.id),
39
40
  }));
40
41
  }
42
+ function getDatabaseSkillItems(state) {
43
+ const selectedSkillIds = new Set(state?.selectedDatabaseSkills ?? []);
44
+ return SELECTABLE_DATABASE_SKILLS.map((skill) => ({
45
+ name: skill.name,
46
+ id: skill.id,
47
+ description: skill.description,
48
+ selected: selectedSkillIds.has(skill.id),
49
+ }));
50
+ }
41
51
  function getArchitectureSkillItems(state) {
42
52
  return SELECTABLE_ARCHITECTURE_SKILLS.map((skill) => ({
43
53
  name: skill.name,
@@ -2,14 +2,15 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { cancel, intro, isCancel, log, outro, select } from '@clack/prompts';
4
4
  import chalk from 'chalk';
5
- import { STATE_RELATIVE_PATH, resolveSelectableSkillById } from '../../shared/catalog.js';
6
- import { readFileHashIfPresent, sha256 } from '../../shared/hash.js';
7
- import { getProjectContext } from '../../shared/paths.js';
8
- import { renderIntro } from '../../shared/prompts.js';
9
- import { cloneState, readStateForDoctor, writeStateFile } from '../../shared/state.js';
10
- import { getTemplateVersion, readTemplateManifest, renderTemplateForSync } from '../../shared/templates.js';
11
- import { removeUndefinedFields } from '../../shared/object.js';
12
- import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedWorkflowSkillsFromState, } from '../../shared/workflow-targets.js';
5
+ import { STATE_RELATIVE_PATH } from '../../../shared/catalog.js';
6
+ import { resolveSelectableSkillById } from '../../workflow-target-planning/index.js';
7
+ import { readFileHashIfPresent, sha256 } from '../../../shared/hash.js';
8
+ import { getProjectContext } from '../../../shared/paths.js';
9
+ import { renderIntro } from '../../interactive-guidance/index.js';
10
+ import { cloneState, readStateForDoctor, writeStateFile } from '../../workflow-state-registry/index.js';
11
+ import { getTemplateVersion, readTemplateManifest, renderTemplateForSync } from '../../template-catalog-rendering/index.js';
12
+ import { removeUndefinedFields } from '../../../shared/object.js';
13
+ import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedDatabaseSkillsFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedWorkflowSkillsFromState, } from '../../workflow-target-planning/index.js';
13
14
  export async function handleStopManagingFile({ skillName }) {
14
15
  await renderIntro();
15
16
  intro(chalk.cyan('Updating skill management'));
@@ -109,6 +110,8 @@ function isSkillSelected(state, resolved) {
109
110
  return state.selectedFrameworkSkills?.includes(resolved.skill.id) ?? false;
110
111
  case 'architecture':
111
112
  return state.selectedArchitectureSkill === resolved.skill.id;
113
+ case 'database':
114
+ return state.selectedDatabaseSkills?.includes(resolved.skill.id) ?? false;
112
115
  case 'workflow':
113
116
  return state.selectedWorkflowSkills?.includes(resolved.skill.id) ?? false;
114
117
  }
@@ -124,6 +127,9 @@ function removeSelectedSkill(state, resolved) {
124
127
  case 'architecture':
125
128
  delete state.selectedArchitectureSkill;
126
129
  return;
130
+ case 'database':
131
+ state.selectedDatabaseSkills = (state.selectedDatabaseSkills ?? []).filter((skillId) => skillId !== resolved.skill.id);
132
+ return;
127
133
  case 'workflow':
128
134
  state.selectedWorkflowSkills = (state.selectedWorkflowSkills ?? []).filter((skillId) => skillId !== resolved.skill.id);
129
135
  return;
@@ -167,6 +173,7 @@ function getAgentsUpdate(rootPath, state) {
167
173
  selectedFrameworkSkills: getSelectedFrameworkSkillsFromState(state),
168
174
  selectedArchitectureSkill: getSelectedArchitectureSkillFromState(state),
169
175
  selectedWorkflowSkills: getSelectedWorkflowSkillsFromState(state),
176
+ selectedDatabaseSkills: getSelectedDatabaseSkillsFromState(state),
170
177
  }),
171
178
  };
172
179
  }
@@ -1,12 +1,13 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { log } from '@clack/prompts';
4
- import { STATE_RELATIVE_PATH, resolveSelectableSkillById } from '../../shared/catalog.js';
5
- import { sha256 } from '../../shared/hash.js';
6
- import { getProjectContext } from '../../shared/paths.js';
7
- import { renderTemplateForSync } from '../../shared/templates.js';
8
- import { getWorkflowMutationReadiness } from '../../shared/workflow-mutation-readiness.js';
9
- import { getSelectedAgentsFromState, getWorkflowTargets, renderMissingWorkflowFiles, writeSibuState } from '../../shared/workflow-targets.js';
4
+ import { STATE_RELATIVE_PATH } from '../../../shared/catalog.js';
5
+ import { resolveSelectableSkillById } from '../../workflow-target-planning/index.js';
6
+ import { sha256 } from '../../../shared/hash.js';
7
+ import { getProjectContext } from '../../../shared/paths.js';
8
+ import { renderTemplateForSync } from '../../template-catalog-rendering/index.js';
9
+ import { getWorkflowMutationReadiness } from '../../workflow-mutation-readiness/index.js';
10
+ import { getSelectedAgentsFromState, getWorkflowTargets, renderMissingWorkflowFiles, writeSibuState } from '../../workflow-target-planning/index.js';
10
11
  export async function handleUseSkill(command) {
11
12
  const { rootPath, statePath } = getProjectContext();
12
13
  const readiness = getWorkflowMutationReadiness({ rootPath, statePath });
@@ -45,6 +46,7 @@ export function getNextSkillSelection(state, skillName) {
45
46
  const selectedLanguageSkills = [...(state.selectedLanguageSkills ?? [])];
46
47
  const selectedFrameworkSkills = [...(state.selectedFrameworkSkills ?? [])];
47
48
  const selectedWorkflowSkills = [...(state.selectedWorkflowSkills ?? [])];
49
+ const selectedDatabaseSkills = [...(state.selectedDatabaseSkills ?? [])];
48
50
  switch (resolution.resolved.kind) {
49
51
  case 'language':
50
52
  if (selectedLanguageSkills.includes(resolution.resolved.skill.id)) {
@@ -58,6 +60,7 @@ export function getNextSkillSelection(state, skillName) {
58
60
  selectedFrameworkSkills: selectedFrameworkSkills.map(getFrameworkSkillById),
59
61
  selectedArchitectureSkill: getArchitectureSkillById(state.selectedArchitectureSkill),
60
62
  selectedWorkflowSkills: selectedWorkflowSkills.map(getWorkflowSkillById),
63
+ selectedDatabaseSkills: selectedDatabaseSkills.map(getDatabaseSkillById),
61
64
  },
62
65
  };
63
66
  case 'framework':
@@ -72,6 +75,7 @@ export function getNextSkillSelection(state, skillName) {
72
75
  selectedFrameworkSkills: [...selectedFrameworkSkills, resolution.resolved.skill.id].map(getFrameworkSkillById),
73
76
  selectedArchitectureSkill: getArchitectureSkillById(state.selectedArchitectureSkill),
74
77
  selectedWorkflowSkills: selectedWorkflowSkills.map(getWorkflowSkillById),
78
+ selectedDatabaseSkills: selectedDatabaseSkills.map(getDatabaseSkillById),
75
79
  },
76
80
  };
77
81
  case 'architecture':
@@ -93,6 +97,22 @@ export function getNextSkillSelection(state, skillName) {
93
97
  selectedFrameworkSkills: selectedFrameworkSkills.map(getFrameworkSkillById),
94
98
  selectedArchitectureSkill: resolution.resolved.skill,
95
99
  selectedWorkflowSkills: selectedWorkflowSkills.map(getWorkflowSkillById),
100
+ selectedDatabaseSkills: selectedDatabaseSkills.map(getDatabaseSkillById),
101
+ },
102
+ };
103
+ case 'database':
104
+ if (selectedDatabaseSkills.includes(resolution.resolved.skill.id)) {
105
+ return { status: 'noop', message: `${resolution.resolved.skill.name} is already selected.` };
106
+ }
107
+ return {
108
+ status: 'selected',
109
+ skillName: resolution.resolved.skill.name,
110
+ selection: {
111
+ selectedLanguageSkills: selectedLanguageSkills.map(getLanguageSkillById),
112
+ selectedFrameworkSkills: selectedFrameworkSkills.map(getFrameworkSkillById),
113
+ selectedArchitectureSkill: getArchitectureSkillById(state.selectedArchitectureSkill),
114
+ selectedWorkflowSkills: selectedWorkflowSkills.map(getWorkflowSkillById),
115
+ selectedDatabaseSkills: [...selectedDatabaseSkills, resolution.resolved.skill.id].map(getDatabaseSkillById),
96
116
  },
97
117
  };
98
118
  case 'workflow':
@@ -107,6 +127,7 @@ export function getNextSkillSelection(state, skillName) {
107
127
  selectedFrameworkSkills: selectedFrameworkSkills.map(getFrameworkSkillById),
108
128
  selectedArchitectureSkill: getArchitectureSkillById(state.selectedArchitectureSkill),
109
129
  selectedWorkflowSkills: [...selectedWorkflowSkills, resolution.resolved.skill.id].map(getWorkflowSkillById),
130
+ selectedDatabaseSkills: selectedDatabaseSkills.map(getDatabaseSkillById),
110
131
  },
111
132
  };
112
133
  }
@@ -127,12 +148,14 @@ function applySelectedSkill({ rootPath, statePath, state, selectionResult, }) {
127
148
  selectedFrameworkSkills: selectionResult.selection.selectedFrameworkSkills,
128
149
  selectedArchitectureSkill: selectionResult.selection.selectedArchitectureSkill,
129
150
  selectedWorkflowSkills: selectionResult.selection.selectedWorkflowSkills,
151
+ selectedDatabaseSkills: selectionResult.selection.selectedDatabaseSkills,
130
152
  });
131
153
  const [newSkillFile] = renderMissingWorkflowFiles({
132
154
  missingTargets: [plan.newSkillTarget],
133
155
  selectedLanguageSkills: selectionResult.selection.selectedLanguageSkills,
134
156
  selectedFrameworkSkills: selectionResult.selection.selectedFrameworkSkills,
135
157
  selectedArchitectureSkill: selectionResult.selection.selectedArchitectureSkill,
158
+ selectedDatabaseSkills: selectionResult.selection.selectedDatabaseSkills,
136
159
  });
137
160
  fs.mkdirSync(path.dirname(newSkillFile.targetPath), { recursive: true });
138
161
  fs.writeFileSync(newSkillFile.targetPath, newSkillFile.contents, { encoding: 'utf8', flag: 'wx' });
@@ -147,6 +170,7 @@ function applySelectedSkill({ rootPath, statePath, state, selectionResult, }) {
147
170
  selectedFrameworkSkills: selectionResult.selection.selectedFrameworkSkills,
148
171
  selectedArchitectureSkill: selectionResult.selection.selectedArchitectureSkill,
149
172
  selectedWorkflowSkills: selectionResult.selection.selectedWorkflowSkills,
173
+ selectedDatabaseSkills: selectionResult.selection.selectedDatabaseSkills,
150
174
  targets: plan.targets,
151
175
  });
152
176
  log.success(`Updated ${STATE_RELATIVE_PATH}`);
@@ -154,8 +178,8 @@ function applySelectedSkill({ rootPath, statePath, state, selectionResult, }) {
154
178
  }
155
179
  function buildSkillApplicationPlan({ rootPath, state, selectionResult, }) {
156
180
  const selectedAgents = getSelectedAgentsFromState(state);
157
- const previousTargets = getWorkflowTargets(rootPath, selectedAgents, getSelectedLanguageSkillsFromState(state), getSelectedFrameworkSkillsFromState(state), getArchitectureSkillById(state.selectedArchitectureSkill), getSelectedWorkflowSkillsFromState(state));
158
- const targets = getWorkflowTargets(rootPath, selectedAgents, selectionResult.selection.selectedLanguageSkills, selectionResult.selection.selectedFrameworkSkills, selectionResult.selection.selectedArchitectureSkill, selectionResult.selection.selectedWorkflowSkills);
181
+ const previousTargets = getWorkflowTargets(rootPath, selectedAgents, getSelectedLanguageSkillsFromState(state), getSelectedFrameworkSkillsFromState(state), getArchitectureSkillById(state.selectedArchitectureSkill), getSelectedWorkflowSkillsFromState(state), getSelectedDatabaseSkillsFromState(state));
182
+ const targets = getWorkflowTargets(rootPath, selectedAgents, selectionResult.selection.selectedLanguageSkills, selectionResult.selection.selectedFrameworkSkills, selectionResult.selection.selectedArchitectureSkill, selectionResult.selection.selectedWorkflowSkills, selectionResult.selection.selectedDatabaseSkills);
159
183
  const previousTargetPaths = new Set(previousTargets.map((target) => target.targetPath));
160
184
  const newSkillTarget = targets.find((target) => !previousTargetPaths.has(target.targetPath));
161
185
  const agentsTarget = targets.find((target) => target.label === 'AGENTS.md');
@@ -194,6 +218,9 @@ function getSelectedFrameworkSkillsFromState(state) {
194
218
  function getSelectedWorkflowSkillsFromState(state) {
195
219
  return (state.selectedWorkflowSkills ?? []).map(getWorkflowSkillById);
196
220
  }
221
+ function getSelectedDatabaseSkillsFromState(state) {
222
+ return (state.selectedDatabaseSkills ?? []).map(getDatabaseSkillById);
223
+ }
197
224
  function getLanguageSkillById(skillId) {
198
225
  const resolution = resolveSelectableSkillById(skillId);
199
226
  if (!resolution.ok || resolution.resolved.kind !== 'language') {
@@ -225,3 +252,10 @@ function getWorkflowSkillById(skillId) {
225
252
  }
226
253
  return resolution.resolved.skill;
227
254
  }
255
+ function getDatabaseSkillById(skillId) {
256
+ const resolution = resolveSelectableSkillById(skillId);
257
+ if (!resolution.ok || resolution.resolved.kind !== 'database') {
258
+ throw new Error(`Unsupported database skill in state: ${skillId}`);
259
+ }
260
+ return resolution.resolved.skill;
261
+ }
@@ -3,9 +3,9 @@ import path from 'node:path';
3
3
  import { log } from '@clack/prompts';
4
4
  import { sha256 } from '../../shared/hash.js';
5
5
  import { getSideTemplatePath } from '../../shared/paths.js';
6
- import { cloneState } from '../../shared/state.js';
7
- import { getTemplateVersion, renderTemplateForSync } from '../../shared/templates.js';
8
- import { getSelectedArchitectureSkillFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedWorkflowSkillsFromState, } from '../../shared/workflow-targets.js';
6
+ import { cloneState } from '../workflow-state-registry/index.js';
7
+ import { getTemplateVersion, renderTemplateForSync } from '../template-catalog-rendering/index.js';
8
+ import { getSelectedArchitectureSkillFromState, getSelectedDatabaseSkillsFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedWorkflowSkillsFromState, } from '../workflow-target-planning/index.js';
9
9
  export function applySyncAction({ rootPath, state, manifest, preview, action, }) {
10
10
  const nextState = cloneState(state);
11
11
  const managedFile = nextState.managedFiles[preview.relativePath] ?? preview.managedFile;
@@ -20,6 +20,7 @@ export function applySyncAction({ rootPath, state, manifest, preview, action, })
20
20
  selectedFrameworkSkills: getSelectedFrameworkSkillsFromState(nextState),
21
21
  selectedArchitectureSkill: getSelectedArchitectureSkillFromState(nextState),
22
22
  selectedWorkflowSkills: getSelectedWorkflowSkillsFromState(nextState),
23
+ selectedDatabaseSkills: getSelectedDatabaseSkillsFromState(nextState),
23
24
  });
24
25
  fs.mkdirSync(path.dirname(targetPath), { recursive: true });
25
26
  fs.writeFileSync(targetPath, contents, 'utf8');
@@ -56,6 +57,7 @@ export function applySyncAction({ rootPath, state, manifest, preview, action, })
56
57
  selectedFrameworkSkills: getSelectedFrameworkSkillsFromState(state),
57
58
  selectedArchitectureSkill: getSelectedArchitectureSkillFromState(state),
58
59
  selectedWorkflowSkills: getSelectedWorkflowSkillsFromState(state),
60
+ selectedDatabaseSkills: getSelectedDatabaseSkillsFromState(state),
59
61
  });
60
62
  fs.mkdirSync(path.dirname(sideTemplatePath), { recursive: true });
61
63
  fs.writeFileSync(sideTemplatePath, contents, 'utf8');
@@ -0,0 +1 @@
1
+ export {};
@@ -2,13 +2,13 @@ import { intro, log, outro } from '@clack/prompts';
2
2
  import chalk from 'chalk';
3
3
  import { STATE_RELATIVE_PATH } from '../../shared/catalog.js';
4
4
  import { getProjectContext } from '../../shared/paths.js';
5
- import { askForMissingFrameworkSkills, askForNewArchitectureSkill, askForNewLanguageSkills, renderIntro } from '../../shared/prompts.js';
6
- import { readStateForDoctor, writeStateFile } from '../../shared/state.js';
7
- import { readTemplateManifest } from '../../shared/templates.js';
5
+ import { askForMissingFrameworkSkills, askForNewArchitectureSkill, askForNewLanguageSkills, renderIntro } from '../interactive-guidance/index.js';
6
+ import { readStateForDoctor, writeStateFile } from '../workflow-state-registry/index.js';
7
+ import { readTemplateManifest } from '../template-catalog-rendering/index.js';
8
8
  import { askForSyncAction } from './action-prompt.js';
9
9
  import { applySyncAction } from './apply-action.js';
10
10
  import { logSyncPreview } from './log-preview.js';
11
- import { getSyncPreviews, isActionableSyncPreview, shouldAskForSyncAction } from './preview.js';
11
+ import { getSyncPreviews, isActionableSyncPreview, shouldAskForSyncAction } from './sync-preview.js';
12
12
  export async function handleSyncProject(_command) {
13
13
  await renderIntro();
14
14
  intro(chalk.cyan('Reviewing workflow updates'));
@@ -0,0 +1,5 @@
1
+ export { askForSyncAction } from './action-prompt.js';
2
+ export { applySyncAction } from './apply-action.js';
3
+ export { handleSyncProject } from './handler.js';
4
+ export { logSyncPreview } from './log-preview.js';
5
+ export { getSyncPreviews, isActionableSyncPreview, shouldAskForSyncAction } from './sync-preview.js';
@@ -1,9 +1,9 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { sha256 } from './hash.js';
4
- import { hasReviewedTemplateVersion } from './state.js';
5
- import { renderTemplateForSync } from './templates.js';
6
- import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedWorkflowSkillsFromState, getWorkflowTargets, } from './workflow-targets.js';
3
+ import { sha256 } from '../../shared/hash.js';
4
+ import { hasReviewedTemplateVersion } from '../workflow-state-registry/index.js';
5
+ import { renderTemplateForSync } from '../template-catalog-rendering/index.js';
6
+ import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedDatabaseSkillsFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedWorkflowSkillsFromState, getWorkflowTargets, } from '../workflow-target-planning/index.js';
7
7
  const ACTIONABLE_SYNC_PREVIEW_STATUSES = new Set([
8
8
  'new-template',
9
9
  'missing',
@@ -30,6 +30,7 @@ export function getSyncPreviews({ rootPath, state, manifest }) {
30
30
  const selectedFrameworkSkills = getSelectedFrameworkSkillsFromState(state);
31
31
  const selectedArchitectureSkill = getSelectedArchitectureSkillFromState(state);
32
32
  const selectedWorkflowSkills = getSelectedWorkflowSkillsFromState(state);
33
+ const selectedDatabaseSkills = getSelectedDatabaseSkillsFromState(state);
33
34
  const previews = Object.entries(state.managedFiles).map(([relativePath, managedFile]) => getManagedFileSyncPreview({
34
35
  rootPath,
35
36
  manifest,
@@ -39,8 +40,9 @@ export function getSyncPreviews({ rootPath, state, manifest }) {
39
40
  selectedFrameworkSkills,
40
41
  selectedArchitectureSkill,
41
42
  selectedWorkflowSkills,
43
+ selectedDatabaseSkills,
42
44
  }));
43
- const expectedTargets = getWorkflowTargets(rootPath, getSelectedAgentsFromState(state), selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills);
45
+ const expectedTargets = getWorkflowTargets(rootPath, getSelectedAgentsFromState(state), selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, selectedDatabaseSkills);
44
46
  for (const target of expectedTargets) {
45
47
  const relativePath = path.relative(rootPath, target.targetPath);
46
48
  if (state.managedFiles[relativePath]) {
@@ -77,7 +79,7 @@ export function getSyncPreviews({ rootPath, state, manifest }) {
77
79
  }
78
80
  return previews;
79
81
  }
80
- function getManagedFileSyncPreview({ rootPath, manifest, relativePath, managedFile, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, }) {
82
+ function getManagedFileSyncPreview({ rootPath, manifest, relativePath, managedFile, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, selectedDatabaseSkills, }) {
81
83
  if (managedFile.status === 'unmanaged') {
82
84
  return { relativePath, managedFile, status: 'unmanaged', recordedTemplateVersion: managedFile.templateVersion, changes: [] };
83
85
  }
@@ -97,6 +99,7 @@ function getManagedFileSyncPreview({ rootPath, manifest, relativePath, managedFi
97
99
  selectedFrameworkSkills,
98
100
  selectedArchitectureSkill,
99
101
  selectedWorkflowSkills,
102
+ selectedDatabaseSkills,
100
103
  });
101
104
  const hasUpdate = hasTemplateUpdate || hasSelectionUpdate;
102
105
  const changes = hasTemplateUpdate ? template.changes : ['Refreshes generated skill routing for the current selected skills.'];
@@ -154,7 +157,7 @@ function getManagedFileSyncPreview({ rootPath, manifest, relativePath, managedFi
154
157
  hasLocalFile,
155
158
  };
156
159
  }
157
- function hasRenderedSelectionUpdate({ relativePath, currentPath, managedFile, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, }) {
160
+ function hasRenderedSelectionUpdate({ relativePath, currentPath, managedFile, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, selectedDatabaseSkills, }) {
158
161
  if (relativePath !== 'AGENTS.md') {
159
162
  return false;
160
163
  }
@@ -165,6 +168,7 @@ function hasRenderedSelectionUpdate({ relativePath, currentPath, managedFile, se
165
168
  selectedFrameworkSkills,
166
169
  selectedArchitectureSkill,
167
170
  selectedWorkflowSkills,
171
+ selectedDatabaseSkills,
168
172
  });
169
173
  return sha256(renderedContents) !== managedFile.sha256;
170
174
  }
@@ -0,0 +1 @@
1
+ export { extractProjectOverview, getTemplateVersion, readTemplate, readTemplateManifest, renderSkillRouting, renderTemplateForSync, } from './templates.js';
@@ -0,0 +1,60 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { getTemplatesPath } from '../../shared/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 = [], selectedDatabaseSkills = [], }) {
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, selectedDatabaseSkills);
27
+ }
28
+ export function renderSkillRouting(contents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = [], selectedDatabaseSkills = []) {
29
+ if (!contents.includes('{{OPTIONAL_SKILL_ROUTING}}')) {
30
+ return contents;
31
+ }
32
+ const optionalRouting = [...selectedLanguageSkills, ...selectedFrameworkSkills, ...selectedDatabaseSkills, ...(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
+ }
@@ -0,0 +1 @@
1
+ export { checkForLatestSibuVersion, SIBU_CACHE_HOME_ENV, SIBU_PACKAGE_NAME, SIBU_VERSION } from './npm-version.js';
@@ -1,7 +1,13 @@
1
1
  import fs from 'node:fs';
2
+ import os from 'node:os';
2
3
  import path from 'node:path';
3
- import { NPM_VERSION_LOOKUP_MODE_ENV, NPM_VERSION_OVERRIDE_ENV, SIBU_PACKAGE_NAME, SIBU_VERSION, SUPPORTED_NPM_LOOKUP_MODES } from './catalog.js';
4
- import { getNpmVersionCachePath } from './paths.js';
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'];
5
11
  const NPM_VERSION_CACHE_TTL_MS = 6 * 60 * 60 * 1000;
6
12
  const NPM_REGISTRY_URL = 'https://registry.npmjs.org';
7
13
  export async function checkForLatestSibuVersion(options = {}) {
@@ -176,3 +182,24 @@ function isNpmVersionCacheRecord(value) {
176
182
  typeof record.packageName === 'string' &&
177
183
  typeof record.source === 'string');
178
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
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -2,14 +2,15 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { intro, log, outro } from '@clack/prompts';
4
4
  import chalk from 'chalk';
5
- import { SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_SKILLS, STATE_RELATIVE_PATH, SUPPORTED_AGENTS } from '../../shared/catalog.js';
5
+ import { STATE_RELATIVE_PATH } from '../../shared/catalog.js';
6
+ import { SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_DATABASE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_SKILLS, SUPPORTED_AGENTS } from '../workflow-target-planning/index.js';
6
7
  import { sha256 } from '../../shared/hash.js';
7
- import { checkForLatestSibuVersion } from '../../shared/npm-version.js';
8
+ import { checkForLatestSibuVersion } from '../version-advisory/index.js';
8
9
  import { getProjectContext } from '../../shared/paths.js';
9
- import { renderIntro } from '../../shared/prompts.js';
10
- import { hasReviewedTemplateVersion, readStateForDoctor } from '../../shared/state.js';
11
- import { getTemplateVersion, readTemplateManifest } from '../../shared/templates.js';
12
- import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getWorkflowTargets, } from '../../shared/workflow-targets.js';
10
+ import { renderIntro } from '../interactive-guidance/index.js';
11
+ import { hasReviewedTemplateVersion, readStateForDoctor } from '../workflow-state-registry/index.js';
12
+ import { getTemplateVersion, readTemplateManifest } from '../template-catalog-rendering/index.js';
13
+ import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedDatabaseSkillsFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedWorkflowSkillsFromState, getWorkflowTargets, } from '../workflow-target-planning/index.js';
13
14
  export async function handleDoctorProject(_command) {
14
15
  await renderIntro();
15
16
  intro(chalk.cyan('Checking workflow state'));
@@ -116,6 +117,11 @@ function addUnsupportedSelectionIssues(state, issues) {
116
117
  if (state.selectedArchitectureSkill && !SELECTABLE_ARCHITECTURE_SKILLS.some((skill) => skill.id === state.selectedArchitectureSkill)) {
117
118
  issues.push({ severity: 'warning', message: `State references unsupported architecture skill: ${state.selectedArchitectureSkill}.` });
118
119
  }
120
+ for (const selectedDatabaseSkill of state.selectedDatabaseSkills ?? []) {
121
+ if (!SELECTABLE_DATABASE_SKILLS.some((skill) => skill.id === selectedDatabaseSkill)) {
122
+ issues.push({ severity: 'warning', message: `State references unsupported database skill: ${selectedDatabaseSkill}.` });
123
+ }
124
+ }
119
125
  for (const reviewedArchitectureSkill of state.reviewedArchitectureSkills ?? []) {
120
126
  if (!SELECTABLE_ARCHITECTURE_SKILLS.some((skill) => skill.id === reviewedArchitectureSkill)) {
121
127
  issues.push({ severity: 'warning', message: `State references unsupported reviewed architecture skill: ${reviewedArchitectureSkill}.` });
@@ -124,7 +130,7 @@ function addUnsupportedSelectionIssues(state, issues) {
124
130
  }
125
131
  function addExpectedTargetIssues(rootPath, state, issues) {
126
132
  const reportedMissingPaths = new Set();
127
- const expectedTargets = getWorkflowTargets(rootPath, getSelectedAgentsFromState(state), getSelectedLanguageSkillsFromState(state), getSelectedFrameworkSkillsFromState(state), getSelectedArchitectureSkillFromState(state));
133
+ const expectedTargets = getWorkflowTargets(rootPath, getSelectedAgentsFromState(state), getSelectedLanguageSkillsFromState(state), getSelectedFrameworkSkillsFromState(state), getSelectedArchitectureSkillFromState(state), getSelectedWorkflowSkillsFromState(state), getSelectedDatabaseSkillsFromState(state));
128
134
  for (const target of expectedTargets) {
129
135
  const relativePath = path.relative(rootPath, target.targetPath);
130
136
  const managedFile = state.managedFiles[relativePath];
@@ -0,0 +1 @@
1
+ export { diagnoseState, getDoctorSyncNextStepLines, getNpmVersionAdvisoryLines, handleDoctorProject } from './handler.js';
@@ -0,0 +1 @@
1
+ export { getWorkflowMutationReadiness } from './workflow-mutation-readiness.js';
@@ -1,6 +1,6 @@
1
- import { readStateForDoctor } from './state.js';
2
- import { getSyncPreviews, isActionableSyncPreview } from './sync-preview.js';
3
- import { readTemplateManifest } from './templates.js';
1
+ import { readStateForDoctor } from '../workflow-state-registry/index.js';
2
+ import { getSyncPreviews, isActionableSyncPreview } from '../sync-review/index.js';
3
+ import { readTemplateManifest } from '../template-catalog-rendering/index.js';
4
4
  export function getWorkflowMutationReadiness({ rootPath, statePath }) {
5
5
  const stateResult = readStateForDoctor(statePath);
6
6
  if (!stateResult.ok) {
@@ -0,0 +1 @@
1
+ export { cloneState, hasReviewedTemplateVersion, readExistingState, readStateForDoctor, writeStateFile } from './state.js';
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { STATE_RELATIVE_PATH } from './catalog.js';
3
+ import { STATE_RELATIVE_PATH } from '../../shared/catalog.js';
4
4
  export function readStateForDoctor(statePath) {
5
5
  if (!fs.existsSync(statePath)) {
6
6
  return { ok: false, message: `${STATE_RELATIVE_PATH} is missing.` };
@@ -57,6 +57,8 @@ function isSibuState(value) {
57
57
  (state.selectedArchitectureSkill === undefined || typeof state.selectedArchitectureSkill === 'string') &&
58
58
  (state.selectedWorkflowSkills === undefined ||
59
59
  (Array.isArray(state.selectedWorkflowSkills) && state.selectedWorkflowSkills.every((skill) => typeof skill === 'string'))) &&
60
+ (state.selectedDatabaseSkills === undefined ||
61
+ (Array.isArray(state.selectedDatabaseSkills) && state.selectedDatabaseSkills.every((skill) => typeof skill === 'string'))) &&
60
62
  (state.reviewedArchitectureSkills === undefined ||
61
63
  (Array.isArray(state.reviewedArchitectureSkills) && state.reviewedArchitectureSkills.every((skill) => typeof skill === 'string'))) &&
62
64
  !!state.managedFiles &&