@juancr11/sibu 0.7.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.
@@ -1 +1 @@
1
- export { askForArchitectureSkill, askForFrameworkSkills, askForLanguageSkills, askForMissingFrameworkSkills, askForNewArchitectureSkill, askForNewLanguageSkills, askForProjectOverview, askForSupportedAgents, askForWorkflowSkills, renderIntro, shouldAskForNewLanguageSkills, } from './prompts.js';
1
+ export { askForArchitectureSkill, askForDatabaseSkills, askForFrameworkSkills, askForLanguageSkills, askForMissingFrameworkSkills, askForNewArchitectureSkill, askForNewLanguageSkills, askForProjectOverview, askForSupportedAgents, askForWorkflowSkills, renderIntro, shouldAskForNewLanguageSkills, } from './prompts.js';
@@ -3,7 +3,7 @@ import { cancel, isCancel, multiselect, select, text } from '@clack/prompts';
3
3
  import gradient from 'gradient-string';
4
4
  import { Box, Text, render, useApp } from 'ink';
5
5
  import { useEffect } from 'react';
6
- import { SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_SKILLS, SELECTABLE_WORKFLOW_SKILLS, SUPPORTED_AGENTS } from '../workflow-target-planning/index.js';
6
+ import { SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_DATABASE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_SKILLS, SELECTABLE_WORKFLOW_SKILLS, SUPPORTED_AGENTS } from '../workflow-target-planning/index.js';
7
7
  const NONE_OPTION_ID = 'none';
8
8
  export async function renderIntro() {
9
9
  console.log(gradient(['#39ff14', '#00e5ff', '#9b5de5']).multiline('⧖ S I B U ⧖'));
@@ -56,6 +56,22 @@ export async function askForFrameworkSkills() {
56
56
  cancelMessage: 'Initialization cancelled.',
57
57
  });
58
58
  }
59
+ export async function askForDatabaseSkills() {
60
+ const selectedDatabaseSkillIds = await multiselect({
61
+ message: 'Select the database skills this project should support.',
62
+ required: false,
63
+ options: SELECTABLE_DATABASE_SKILLS.map((skill) => ({
64
+ value: skill.id,
65
+ label: skill.name,
66
+ hint: skill.description,
67
+ })),
68
+ });
69
+ if (isCancel(selectedDatabaseSkillIds)) {
70
+ cancel('Initialization cancelled.');
71
+ process.exit(0);
72
+ }
73
+ return SELECTABLE_DATABASE_SKILLS.filter((skill) => selectedDatabaseSkillIds.includes(skill.id));
74
+ }
59
75
  async function askForFrameworkSkillSelection({ message, cancelMessage, }) {
60
76
  const selectedFrameworkSkillIds = await multiselect({
61
77
  message,
@@ -4,7 +4,7 @@ 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, askForFrameworkSkills, askForLanguageSkills, askForProjectOverview, askForSupportedAgents, askForWorkflowSkills, renderIntro } from '../interactive-guidance/index.js';
7
+ import { askForArchitectureSkill, askForDatabaseSkills, askForFrameworkSkills, askForLanguageSkills, 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
  export async function handleInitProject(_command) {
@@ -30,9 +30,10 @@ export async function handleInitProject(_command) {
30
30
  const selectedAgents = await askForSupportedAgents();
31
31
  const selectedLanguageSkills = await askForLanguageSkills();
32
32
  const selectedFrameworkSkills = await askForFrameworkSkills();
33
+ const selectedDatabaseSkills = await askForDatabaseSkills();
33
34
  const selectedArchitectureSkill = await askForArchitectureSkill();
34
35
  const selectedWorkflowSkills = await askForWorkflowSkills();
35
- const targets = getWorkflowTargets(rootPath, selectedAgents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills);
36
+ const targets = getWorkflowTargets(rootPath, selectedAgents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, selectedDatabaseSkills);
36
37
  const missingTargets = targets.filter((target) => !fs.existsSync(target.targetPath));
37
38
  log.message('I will create this project’s initial AI workflow files.');
38
39
  for (const target of targets) {
@@ -53,13 +54,14 @@ export async function handleInitProject(_command) {
53
54
  selectedFrameworkSkills,
54
55
  selectedArchitectureSkill,
55
56
  selectedWorkflowSkills,
57
+ selectedDatabaseSkills,
56
58
  });
57
59
  for (const file of files) {
58
60
  fs.mkdirSync(path.dirname(file.targetPath), { recursive: true });
59
61
  fs.writeFileSync(file.targetPath, file.contents, { encoding: 'utf8', flag: 'wx' });
60
62
  log.success(`Created ${file.label}`);
61
63
  }
62
- writeSibuState({ rootPath, statePath, selectedAgents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, targets });
64
+ writeSibuState({ rootPath, statePath, selectedAgents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, selectedDatabaseSkills, targets });
63
65
  log.success(`Created ${STATE_RELATIVE_PATH}`);
64
66
  outro(chalk.green('Setup complete.'));
65
67
  }
@@ -1,6 +1,6 @@
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 '../../workflow-target-planning/index.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
4
  import { getProjectContext } from '../../../shared/paths.js';
5
5
  import { renderIntro } from '../../interactive-guidance/index.js';
6
6
  import { readStateForDoctor } from '../../workflow-state-registry/index.js';
@@ -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,
@@ -10,7 +10,7 @@ import { renderIntro } from '../../interactive-guidance/index.js';
10
10
  import { cloneState, readStateForDoctor, writeStateFile } from '../../workflow-state-registry/index.js';
11
11
  import { getTemplateVersion, readTemplateManifest, renderTemplateForSync } from '../../template-catalog-rendering/index.js';
12
12
  import { removeUndefinedFields } from '../../../shared/object.js';
13
- import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedWorkflowSkillsFromState, } from '../../workflow-target-planning/index.js';
13
+ import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedDatabaseSkillsFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedWorkflowSkillsFromState, } from '../../workflow-target-planning/index.js';
14
14
  export async function handleStopManagingFile({ skillName }) {
15
15
  await renderIntro();
16
16
  intro(chalk.cyan('Updating skill management'));
@@ -110,6 +110,8 @@ function isSkillSelected(state, resolved) {
110
110
  return state.selectedFrameworkSkills?.includes(resolved.skill.id) ?? false;
111
111
  case 'architecture':
112
112
  return state.selectedArchitectureSkill === resolved.skill.id;
113
+ case 'database':
114
+ return state.selectedDatabaseSkills?.includes(resolved.skill.id) ?? false;
113
115
  case 'workflow':
114
116
  return state.selectedWorkflowSkills?.includes(resolved.skill.id) ?? false;
115
117
  }
@@ -125,6 +127,9 @@ function removeSelectedSkill(state, resolved) {
125
127
  case 'architecture':
126
128
  delete state.selectedArchitectureSkill;
127
129
  return;
130
+ case 'database':
131
+ state.selectedDatabaseSkills = (state.selectedDatabaseSkills ?? []).filter((skillId) => skillId !== resolved.skill.id);
132
+ return;
128
133
  case 'workflow':
129
134
  state.selectedWorkflowSkills = (state.selectedWorkflowSkills ?? []).filter((skillId) => skillId !== resolved.skill.id);
130
135
  return;
@@ -168,6 +173,7 @@ function getAgentsUpdate(rootPath, state) {
168
173
  selectedFrameworkSkills: getSelectedFrameworkSkillsFromState(state),
169
174
  selectedArchitectureSkill: getSelectedArchitectureSkillFromState(state),
170
175
  selectedWorkflowSkills: getSelectedWorkflowSkillsFromState(state),
176
+ selectedDatabaseSkills: getSelectedDatabaseSkillsFromState(state),
171
177
  }),
172
178
  };
173
179
  }
@@ -46,6 +46,7 @@ export function getNextSkillSelection(state, skillName) {
46
46
  const selectedLanguageSkills = [...(state.selectedLanguageSkills ?? [])];
47
47
  const selectedFrameworkSkills = [...(state.selectedFrameworkSkills ?? [])];
48
48
  const selectedWorkflowSkills = [...(state.selectedWorkflowSkills ?? [])];
49
+ const selectedDatabaseSkills = [...(state.selectedDatabaseSkills ?? [])];
49
50
  switch (resolution.resolved.kind) {
50
51
  case 'language':
51
52
  if (selectedLanguageSkills.includes(resolution.resolved.skill.id)) {
@@ -59,6 +60,7 @@ export function getNextSkillSelection(state, skillName) {
59
60
  selectedFrameworkSkills: selectedFrameworkSkills.map(getFrameworkSkillById),
60
61
  selectedArchitectureSkill: getArchitectureSkillById(state.selectedArchitectureSkill),
61
62
  selectedWorkflowSkills: selectedWorkflowSkills.map(getWorkflowSkillById),
63
+ selectedDatabaseSkills: selectedDatabaseSkills.map(getDatabaseSkillById),
62
64
  },
63
65
  };
64
66
  case 'framework':
@@ -73,6 +75,7 @@ export function getNextSkillSelection(state, skillName) {
73
75
  selectedFrameworkSkills: [...selectedFrameworkSkills, resolution.resolved.skill.id].map(getFrameworkSkillById),
74
76
  selectedArchitectureSkill: getArchitectureSkillById(state.selectedArchitectureSkill),
75
77
  selectedWorkflowSkills: selectedWorkflowSkills.map(getWorkflowSkillById),
78
+ selectedDatabaseSkills: selectedDatabaseSkills.map(getDatabaseSkillById),
76
79
  },
77
80
  };
78
81
  case 'architecture':
@@ -94,6 +97,22 @@ export function getNextSkillSelection(state, skillName) {
94
97
  selectedFrameworkSkills: selectedFrameworkSkills.map(getFrameworkSkillById),
95
98
  selectedArchitectureSkill: resolution.resolved.skill,
96
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),
97
116
  },
98
117
  };
99
118
  case 'workflow':
@@ -108,6 +127,7 @@ export function getNextSkillSelection(state, skillName) {
108
127
  selectedFrameworkSkills: selectedFrameworkSkills.map(getFrameworkSkillById),
109
128
  selectedArchitectureSkill: getArchitectureSkillById(state.selectedArchitectureSkill),
110
129
  selectedWorkflowSkills: [...selectedWorkflowSkills, resolution.resolved.skill.id].map(getWorkflowSkillById),
130
+ selectedDatabaseSkills: selectedDatabaseSkills.map(getDatabaseSkillById),
111
131
  },
112
132
  };
113
133
  }
@@ -128,12 +148,14 @@ function applySelectedSkill({ rootPath, statePath, state, selectionResult, }) {
128
148
  selectedFrameworkSkills: selectionResult.selection.selectedFrameworkSkills,
129
149
  selectedArchitectureSkill: selectionResult.selection.selectedArchitectureSkill,
130
150
  selectedWorkflowSkills: selectionResult.selection.selectedWorkflowSkills,
151
+ selectedDatabaseSkills: selectionResult.selection.selectedDatabaseSkills,
131
152
  });
132
153
  const [newSkillFile] = renderMissingWorkflowFiles({
133
154
  missingTargets: [plan.newSkillTarget],
134
155
  selectedLanguageSkills: selectionResult.selection.selectedLanguageSkills,
135
156
  selectedFrameworkSkills: selectionResult.selection.selectedFrameworkSkills,
136
157
  selectedArchitectureSkill: selectionResult.selection.selectedArchitectureSkill,
158
+ selectedDatabaseSkills: selectionResult.selection.selectedDatabaseSkills,
137
159
  });
138
160
  fs.mkdirSync(path.dirname(newSkillFile.targetPath), { recursive: true });
139
161
  fs.writeFileSync(newSkillFile.targetPath, newSkillFile.contents, { encoding: 'utf8', flag: 'wx' });
@@ -148,6 +170,7 @@ function applySelectedSkill({ rootPath, statePath, state, selectionResult, }) {
148
170
  selectedFrameworkSkills: selectionResult.selection.selectedFrameworkSkills,
149
171
  selectedArchitectureSkill: selectionResult.selection.selectedArchitectureSkill,
150
172
  selectedWorkflowSkills: selectionResult.selection.selectedWorkflowSkills,
173
+ selectedDatabaseSkills: selectionResult.selection.selectedDatabaseSkills,
151
174
  targets: plan.targets,
152
175
  });
153
176
  log.success(`Updated ${STATE_RELATIVE_PATH}`);
@@ -155,8 +178,8 @@ function applySelectedSkill({ rootPath, statePath, state, selectionResult, }) {
155
178
  }
156
179
  function buildSkillApplicationPlan({ rootPath, state, selectionResult, }) {
157
180
  const selectedAgents = getSelectedAgentsFromState(state);
158
- const previousTargets = getWorkflowTargets(rootPath, selectedAgents, getSelectedLanguageSkillsFromState(state), getSelectedFrameworkSkillsFromState(state), getArchitectureSkillById(state.selectedArchitectureSkill), getSelectedWorkflowSkillsFromState(state));
159
- 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);
160
183
  const previousTargetPaths = new Set(previousTargets.map((target) => target.targetPath));
161
184
  const newSkillTarget = targets.find((target) => !previousTargetPaths.has(target.targetPath));
162
185
  const agentsTarget = targets.find((target) => target.label === 'AGENTS.md');
@@ -195,6 +218,9 @@ function getSelectedFrameworkSkillsFromState(state) {
195
218
  function getSelectedWorkflowSkillsFromState(state) {
196
219
  return (state.selectedWorkflowSkills ?? []).map(getWorkflowSkillById);
197
220
  }
221
+ function getSelectedDatabaseSkillsFromState(state) {
222
+ return (state.selectedDatabaseSkills ?? []).map(getDatabaseSkillById);
223
+ }
198
224
  function getLanguageSkillById(skillId) {
199
225
  const resolution = resolveSelectableSkillById(skillId);
200
226
  if (!resolution.ok || resolution.resolved.kind !== 'language') {
@@ -226,3 +252,10 @@ function getWorkflowSkillById(skillId) {
226
252
  }
227
253
  return resolution.resolved.skill;
228
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
+ }
@@ -5,7 +5,7 @@ import { sha256 } from '../../shared/hash.js';
5
5
  import { getSideTemplatePath } from '../../shared/paths.js';
6
6
  import { cloneState } from '../workflow-state-registry/index.js';
7
7
  import { getTemplateVersion, renderTemplateForSync } from '../template-catalog-rendering/index.js';
8
- import { getSelectedArchitectureSkillFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedWorkflowSkillsFromState, } from '../workflow-target-planning/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');
@@ -3,7 +3,7 @@ import path from 'node:path';
3
3
  import { sha256 } from '../../shared/hash.js';
4
4
  import { hasReviewedTemplateVersion } from '../workflow-state-registry/index.js';
5
5
  import { renderTemplateForSync } from '../template-catalog-rendering/index.js';
6
- import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedWorkflowSkillsFromState, getWorkflowTargets, } from '../workflow-target-planning/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
  }
@@ -18,18 +18,18 @@ export function getTemplateVersion(manifest, templateRelativePath) {
18
18
  }
19
19
  return template.version;
20
20
  }
21
- export function renderTemplateForSync({ templateRelativePath, currentPath, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = [], }) {
21
+ export function renderTemplateForSync({ templateRelativePath, currentPath, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = [], selectedDatabaseSkills = [], }) {
22
22
  let contents = readTemplate(templateRelativePath);
23
23
  if (contents.includes('{{PROJECT_OVERVIEW}}')) {
24
24
  contents = contents.replace('{{PROJECT_OVERVIEW}}', extractProjectOverview(currentPath) ?? 'Describe this project.');
25
25
  }
26
- return renderSkillRouting(contents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills);
26
+ return renderSkillRouting(contents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, selectedDatabaseSkills);
27
27
  }
28
- export function renderSkillRouting(contents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = []) {
28
+ export function renderSkillRouting(contents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = [], selectedDatabaseSkills = []) {
29
29
  if (!contents.includes('{{OPTIONAL_SKILL_ROUTING}}')) {
30
30
  return contents;
31
31
  }
32
- const optionalRouting = [...selectedLanguageSkills, ...selectedFrameworkSkills, ...(selectedArchitectureSkill ? [selectedArchitectureSkill] : []), ...selectedWorkflowSkills]
32
+ const optionalRouting = [...selectedLanguageSkills, ...selectedFrameworkSkills, ...selectedDatabaseSkills, ...(selectedArchitectureSkill ? [selectedArchitectureSkill] : []), ...selectedWorkflowSkills]
33
33
  .map((skill) => `- ${skill.routingInstruction}`)
34
34
  .join('\n');
35
35
  return contents.replace('{{OPTIONAL_SKILL_ROUTING}}', optionalRouting);
@@ -3,14 +3,14 @@ import path from 'node:path';
3
3
  import { intro, log, outro } from '@clack/prompts';
4
4
  import chalk from 'chalk';
5
5
  import { STATE_RELATIVE_PATH } from '../../shared/catalog.js';
6
- import { SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_SKILLS, SUPPORTED_AGENTS } from '../workflow-target-planning/index.js';
6
+ import { SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_DATABASE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_SKILLS, SUPPORTED_AGENTS } from '../workflow-target-planning/index.js';
7
7
  import { sha256 } from '../../shared/hash.js';
8
8
  import { checkForLatestSibuVersion } from '../version-advisory/index.js';
9
9
  import { getProjectContext } from '../../shared/paths.js';
10
10
  import { renderIntro } from '../interactive-guidance/index.js';
11
11
  import { hasReviewedTemplateVersion, readStateForDoctor } from '../workflow-state-registry/index.js';
12
12
  import { getTemplateVersion, readTemplateManifest } from '../template-catalog-rendering/index.js';
13
- import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getWorkflowTargets, } from '../workflow-target-planning/index.js';
13
+ import { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedDatabaseSkillsFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedWorkflowSkillsFromState, getWorkflowTargets, } from '../workflow-target-planning/index.js';
14
14
  export async function handleDoctorProject(_command) {
15
15
  await renderIntro();
16
16
  intro(chalk.cyan('Checking workflow state'));
@@ -117,6 +117,11 @@ function addUnsupportedSelectionIssues(state, issues) {
117
117
  if (state.selectedArchitectureSkill && !SELECTABLE_ARCHITECTURE_SKILLS.some((skill) => skill.id === state.selectedArchitectureSkill)) {
118
118
  issues.push({ severity: 'warning', message: `State references unsupported architecture skill: ${state.selectedArchitectureSkill}.` });
119
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
+ }
120
125
  for (const reviewedArchitectureSkill of state.reviewedArchitectureSkills ?? []) {
121
126
  if (!SELECTABLE_ARCHITECTURE_SKILLS.some((skill) => skill.id === reviewedArchitectureSkill)) {
122
127
  issues.push({ severity: 'warning', message: `State references unsupported reviewed architecture skill: ${reviewedArchitectureSkill}.` });
@@ -125,7 +130,7 @@ function addUnsupportedSelectionIssues(state, issues) {
125
130
  }
126
131
  function addExpectedTargetIssues(rootPath, state, issues) {
127
132
  const reportedMissingPaths = new Set();
128
- 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));
129
134
  for (const target of expectedTargets) {
130
135
  const relativePath = path.relative(rootPath, target.targetPath);
131
136
  const managedFile = state.managedFiles[relativePath];
@@ -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 &&
@@ -100,6 +100,21 @@ export const SELECTABLE_LANGUAGE_SKILLS = [
100
100
  },
101
101
  },
102
102
  ];
103
+ export const SELECTABLE_DATABASE_SKILLS = [
104
+ {
105
+ id: 'postgresql-expert',
106
+ name: 'PostgreSQL Expert',
107
+ description: 'Install practical guidance for PostgreSQL schema design, migrations, constraints, queries, indexing, and database tradeoffs',
108
+ routingInstruction: 'For PostgreSQL schema design, migrations, constraints, queries, indexing, or database tradeoffs, use `postgresql-expert`.',
109
+ templateRelativePath: 'skills/postgresql-expert/SKILL.md',
110
+ targetRelativePathsByAgent: {
111
+ codex: '.agents/skills/postgresql-expert/SKILL.md',
112
+ gemini: '.agents/skills/postgresql-expert/SKILL.md',
113
+ claude: '.agents/skills/postgresql-expert/SKILL.md',
114
+ windsurf: '.agents/skills/postgresql-expert/SKILL.md',
115
+ },
116
+ },
117
+ ];
103
118
  export const SELECTABLE_FRAMEWORK_SKILLS = [
104
119
  {
105
120
  id: 'react',
@@ -221,6 +236,10 @@ export function resolveSelectableSkillById(skillId) {
221
236
  if (frameworkSkill) {
222
237
  return { ok: true, resolved: { kind: 'framework', skill: frameworkSkill } };
223
238
  }
239
+ const databaseSkill = SELECTABLE_DATABASE_SKILLS.find((skill) => skill.id === skillId);
240
+ if (databaseSkill) {
241
+ return { ok: true, resolved: { kind: 'database', skill: databaseSkill } };
242
+ }
224
243
  const architectureSkill = SELECTABLE_ARCHITECTURE_SKILLS.find((skill) => skill.id === skillId);
225
244
  if (architectureSkill) {
226
245
  return { ok: true, resolved: { kind: 'architecture', skill: architectureSkill } };
@@ -1,2 +1,2 @@
1
- export { MANDATORY_SKILLS, SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_SKILLS, SELECTABLE_WORKFLOW_SKILLS, SUPPORTED_AGENTS, resolveSelectableSkillById, } from './catalog.js';
2
- export { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedSkillTargetsForAgents, getSelectedWorkflowSkillsFromState, getWorkflowTargets, renderMissingWorkflowFiles, writeSibuState, } from './workflow-targets.js';
1
+ export { MANDATORY_SKILLS, SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_DATABASE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_SKILLS, SELECTABLE_WORKFLOW_SKILLS, SUPPORTED_AGENTS, resolveSelectableSkillById, } from './catalog.js';
2
+ export { getSelectedAgentsFromState, getSelectedArchitectureSkillFromState, getSelectedDatabaseSkillsFromState, getSelectedFrameworkSkillsFromState, getSelectedLanguageSkillsFromState, getSelectedSkillTargetsForAgents, getSelectedWorkflowSkillsFromState, getWorkflowTargets, renderMissingWorkflowFiles, writeSibuState, } from './workflow-targets.js';
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { SIBU_VERSION } from '../version-advisory/index.js';
4
- import { MANDATORY_SKILLS, SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_SKILLS, SELECTABLE_WORKFLOW_SKILLS, SUPPORTED_AGENTS, } from './index.js';
4
+ import { MANDATORY_SKILLS, SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_DATABASE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_SKILLS, SELECTABLE_WORKFLOW_SKILLS, SUPPORTED_AGENTS, } from './index.js';
5
5
  import { sha256 } from '../../shared/hash.js';
6
6
  import { removeUndefinedFields } from '../../shared/object.js';
7
7
  import { readExistingState } from '../workflow-state-registry/index.js';
@@ -18,12 +18,16 @@ export function getSelectedArchitectureSkillFromState(state) {
18
18
  export function getSelectedWorkflowSkillsFromState(state) {
19
19
  return SELECTABLE_WORKFLOW_SKILLS.filter((skill) => state.selectedWorkflowSkills?.includes(skill.id));
20
20
  }
21
- export function getSelectedSkillTargetsForAgents(selectedAgents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = []) {
21
+ export function getSelectedDatabaseSkillsFromState(state) {
22
+ return SELECTABLE_DATABASE_SKILLS.filter((skill) => state.selectedDatabaseSkills?.includes(skill.id));
23
+ }
24
+ export function getSelectedSkillTargetsForAgents(selectedAgents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = [], selectedDatabaseSkills = []) {
22
25
  const skillTargets = new Map();
23
26
  const selectedSkills = [
24
27
  ...MANDATORY_SKILLS,
25
28
  ...selectedLanguageSkills,
26
29
  ...selectedFrameworkSkills,
30
+ ...selectedDatabaseSkills,
27
31
  ...(selectedArchitectureSkill ? [selectedArchitectureSkill] : []),
28
32
  ...selectedWorkflowSkills,
29
33
  ];
@@ -41,7 +45,7 @@ export function getSelectedSkillTargetsForAgents(selectedAgents, selectedLanguag
41
45
  }
42
46
  return [...skillTargets.values()];
43
47
  }
44
- export function getWorkflowTargets(rootPath, selectedAgents, selectedLanguageSkills = [], selectedFrameworkSkills = [], selectedArchitectureSkill, selectedWorkflowSkills = []) {
48
+ export function getWorkflowTargets(rootPath, selectedAgents, selectedLanguageSkills = [], selectedFrameworkSkills = [], selectedArchitectureSkill, selectedWorkflowSkills = [], selectedDatabaseSkills = []) {
45
49
  return [
46
50
  {
47
51
  label: 'AGENTS.md',
@@ -62,7 +66,7 @@ export function getWorkflowTargets(rootPath, selectedAgents, selectedLanguageSki
62
66
  },
63
67
  ];
64
68
  }),
65
- ...getSelectedSkillTargetsForAgents(selectedAgents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills).map((skillTarget) => ({
69
+ ...getSelectedSkillTargetsForAgents(selectedAgents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, selectedDatabaseSkills).map((skillTarget) => ({
66
70
  label: skillTarget.targetRelativePath,
67
71
  targetPath: path.join(rootPath, skillTarget.targetRelativePath),
68
72
  templateRelativePath: skillTarget.templateRelativePath,
@@ -70,7 +74,7 @@ export function getWorkflowTargets(rootPath, selectedAgents, selectedLanguageSki
70
74
  })),
71
75
  ];
72
76
  }
73
- export function renderMissingWorkflowFiles({ missingTargets, overview, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = [], }) {
77
+ export function renderMissingWorkflowFiles({ missingTargets, overview, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = [], selectedDatabaseSkills = [], }) {
74
78
  return missingTargets.map((target) => {
75
79
  let contents = readTemplate(target.templateRelativePath);
76
80
  if (target.requiresProjectOverview) {
@@ -79,7 +83,7 @@ export function renderMissingWorkflowFiles({ missingTargets, overview, selectedL
79
83
  }
80
84
  contents = contents.replace('{{PROJECT_OVERVIEW}}', overview.trim());
81
85
  }
82
- contents = renderSkillRouting(contents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills);
86
+ contents = renderSkillRouting(contents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills, selectedDatabaseSkills);
83
87
  return {
84
88
  label: target.label,
85
89
  targetPath: target.targetPath,
@@ -87,7 +91,7 @@ export function renderMissingWorkflowFiles({ missingTargets, overview, selectedL
87
91
  };
88
92
  });
89
93
  }
90
- export function writeSibuState({ rootPath, statePath, selectedAgents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = [], targets, }) {
94
+ export function writeSibuState({ rootPath, statePath, selectedAgents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, selectedWorkflowSkills = [], selectedDatabaseSkills = [], targets, }) {
91
95
  const previousState = readExistingState(statePath);
92
96
  const now = new Date().toISOString();
93
97
  const manifest = readTemplateManifest();
@@ -101,6 +105,7 @@ export function writeSibuState({ rootPath, statePath, selectedAgents, selectedLa
101
105
  selectedFrameworkSkills: selectedFrameworkSkills.map((skill) => skill.id),
102
106
  selectedArchitectureSkill: selectedArchitectureSkill?.id,
103
107
  selectedWorkflowSkills: selectedWorkflowSkills.map((skill) => skill.id),
108
+ selectedDatabaseSkills: selectedDatabaseSkills.map((skill) => skill.id),
104
109
  managedFiles: Object.fromEntries(targets
105
110
  .filter((target) => fs.existsSync(target.targetPath))
106
111
  .map((target) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juancr11/sibu",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
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": "69",
2
+ "templateVersion": "70",
3
3
  "templates": {
4
4
  "AGENTS.md": {
5
5
  "version": "25",
@@ -80,6 +80,13 @@
80
80
  "Adds an optional Go skill with concise, Effective Go\u2013style guidance for .go files, package APIs, interfaces, errors, receivers, concurrency, and tests."
81
81
  ]
82
82
  },
83
+ "skills/postgresql-expert/SKILL.md": {
84
+ "version": "1",
85
+ "description": "Selectable PostgreSQL Expert skill for practical database schema design and PostgreSQL-specific tradeoff guidance.",
86
+ "changes": [
87
+ "Adds an optional PostgreSQL Expert database skill focused on simple schema design, data integrity, migrations, access patterns, and purposeful indexing."
88
+ ]
89
+ },
83
90
  "skills/architecture/ddd-hexagonal/SKILL.md": {
84
91
  "version": "4",
85
92
  "description": "Selectable back-end architecture skill for DDD and Hexagonal Architecture.",
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: postgresql-expert
3
+ description: Use this skill for PostgreSQL schema design, migrations, constraints, queries, indexing, and database tradeoffs when practical database judgment is needed.
4
+ ---
5
+
6
+ # PostgreSQL Expert
7
+
8
+ Use this skill when working with PostgreSQL database design or database-facing application changes.
9
+
10
+ Apply this skill together with `clean-code`. Keep the guidance practical: prefer the simplest database design that protects data integrity and supports the application's real access patterns.
11
+
12
+ ## Use this skill for
13
+
14
+ - designing or reviewing PostgreSQL schemas
15
+ - planning migrations and safe schema evolution
16
+ - choosing keys, constraints, relationships, and transactions
17
+ - reasoning about queries, access patterns, and indexes
18
+ - deciding whether PostgreSQL-specific features are worth using
19
+ - reviewing database-related tradeoffs in application code
20
+
21
+ ## Core principles
22
+
23
+ ### 1. Start with the product model
24
+ - Model the real domain concepts, not speculative future reporting or scaling needs.
25
+ - Prefer clear table and column names that match product language.
26
+ - Keep schemas boring until the product needs something more specialized.
27
+ - Do not add tables, abstractions, or generic metadata columns just in case.
28
+
29
+ ### 2. Protect data integrity in the database
30
+ - Use primary keys, foreign keys, `NOT NULL`, `UNIQUE`, and `CHECK` constraints when they express real invariants.
31
+ - Prefer database-enforced integrity over relying only on application checks for durable rules.
32
+ - Make deletion behavior explicit with appropriate foreign-key actions.
33
+ - Use transactions when a change must succeed or fail as one unit.
34
+
35
+ ### 3. Evolve schemas safely
36
+ - Treat migrations as production changes that need clear rollback or recovery thinking.
37
+ - Prefer small, reversible migration steps for risky changes.
38
+ - Avoid destructive changes until data has been backfilled, verified, and callers have moved over.
39
+ - Consider lock behavior and table size before changing large production tables.
40
+
41
+ ### 4. Design from access patterns, not guesses
42
+ - Understand the queries the application actually needs before optimizing.
43
+ - Keep common reads and writes straightforward before introducing denormalization.
44
+ - Use normalization for clarity and integrity; denormalize only for a measured or well-understood reason.
45
+ - Explain tradeoffs when choosing between simpler schema shape and query convenience.
46
+
47
+ ### 5. Add indexes only when justified
48
+ - Do not add indexes just because a column is used, foreign-keyed, or might be queried someday.
49
+ - Add an index when there is a clear query, filtering, joining, sorting, uniqueness, or constraint need.
50
+ - Remember that indexes speed some reads but add write cost, storage cost, and maintenance overhead.
51
+ - Prefer a small number of purposeful indexes over broad defensive indexing.
52
+
53
+ ### 6. Use PostgreSQL features with restraint
54
+ - Consider PostgreSQL capabilities such as JSONB, arrays, partial indexes, generated columns, enums, extensions, and full-text search when they simplify a real requirement.
55
+ - Do not use PostgreSQL-specific features to appear sophisticated when standard relational modeling is clearer.
56
+ - Be explicit about portability tradeoffs when choosing PostgreSQL-specific behavior.
57
+ - Prefer built-in PostgreSQL capabilities before adding extra infrastructure.
58
+
59
+ ### 7. Keep database advice actionable
60
+ - Tie recommendations to the schema, query, migration, or invariant at hand.
61
+ - Say what evidence would change the recommendation, such as query plans, row counts, write volume, or latency targets.
62
+ - When context is missing, ask for the relevant schema, migration, query, or access pattern instead of guessing.
63
+ - Avoid broad DBA checklists unless the task is explicitly about operations or performance investigation.
64
+
65
+ ## Decision rule
66
+
67
+ When unsure, prefer:
68
+ 1. simple schema design that matches current product needs
69
+ 2. explicit database constraints for real invariants
70
+ 3. safe, incremental migrations
71
+ 4. query-aware design over speculative optimization
72
+ 5. purposeful indexes over blanket indexing