@juancr11/sibu 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +198 -0
- package/bin/entrypoints/cli/command.js +1 -0
- package/bin/entrypoints/cli/create-program.js +33 -0
- package/bin/entrypoints/cli/execute-command.js +28 -0
- package/bin/entrypoints/cli/main.js +5 -0
- package/bin/features/doctor-project/command.js +1 -0
- package/bin/features/doctor-project/handler.js +194 -0
- package/bin/features/init-project/command.js +1 -0
- package/bin/features/init-project/handler.js +63 -0
- package/bin/features/list-skills/command.js +1 -0
- package/bin/features/list-skills/handler.js +55 -0
- package/bin/features/stop-managing-file/command.js +1 -0
- package/bin/features/stop-managing-file/handler.js +213 -0
- package/bin/features/sync-project/action-prompt.js +65 -0
- package/bin/features/sync-project/apply-action.js +81 -0
- package/bin/features/sync-project/command.js +1 -0
- package/bin/features/sync-project/handler.js +77 -0
- package/bin/features/sync-project/log-preview.js +62 -0
- package/bin/features/sync-project/preview.js +1 -0
- package/bin/features/use-skill/command.js +1 -0
- package/bin/features/use-skill/handler.js +197 -0
- package/bin/shared/catalog.js +199 -0
- package/bin/shared/hash.js +11 -0
- package/bin/shared/npm-version.js +178 -0
- package/bin/shared/object.js +3 -0
- package/bin/shared/paths.js +41 -0
- package/bin/shared/prompts.js +205 -0
- package/bin/shared/state.js +76 -0
- package/bin/shared/sync-preview.js +166 -0
- package/bin/shared/templates.js +60 -0
- package/bin/shared/types.js +1 -0
- package/bin/shared/workflow-mutation-readiness.js +30 -0
- package/bin/shared/workflow-targets.js +119 -0
- package/bin/sibu.js +6 -0
- package/package.json +68 -0
- package/templates/.codex/config.toml +1 -0
- package/templates/AGENTS.md +60 -0
- package/templates/CLAUDE.md +5 -0
- package/templates/GEMINI.md +5 -0
- package/templates/manifest.json +129 -0
- package/templates/skills/ai-implementation-plan-executor/SKILL.md +138 -0
- package/templates/skills/ai-implementation-planner/SKILL.md +213 -0
- package/templates/skills/architecture/command-pattern/SKILL.md +77 -0
- package/templates/skills/architecture/ddd-hexagonal/SKILL.md +212 -0
- package/templates/skills/clean-code/SKILL.md +109 -0
- package/templates/skills/feature-brief-writer/SKILL.md +219 -0
- package/templates/skills/golang/SKILL.md +82 -0
- package/templates/skills/nextjs/SKILL.md +94 -0
- package/templates/skills/product-vision-writer/SKILL.md +128 -0
- package/templates/skills/react/SKILL.md +75 -0
- package/templates/skills/scrum-master-planner/SKILL.md +191 -0
- package/templates/skills/technical-design-writer/SKILL.md +109 -0
- package/templates/skills/typescript/SKILL.md +111 -0
package/README.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# ai-augmented-workflow
|
|
2
|
+
|
|
3
|
+
A local workspace for developing and refining an AI-augmented software development workflow.
|
|
4
|
+
|
|
5
|
+
## Install Sibu
|
|
6
|
+
|
|
7
|
+
The official way to install and update Sibu is with npm:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install -g @juancr11/sibu
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
After installing, the normal user flow is:
|
|
14
|
+
|
|
15
|
+
1. run `sibu init` in your project
|
|
16
|
+
2. run `sibu doctor` to check workflow health
|
|
17
|
+
3. if `sibu doctor` says a newer npm version exists, run `npm install -g @juancr11/sibu`
|
|
18
|
+
4. rerun `sibu doctor`
|
|
19
|
+
5. if drift is reported, decide whether to run `sibu sync`
|
|
20
|
+
|
|
21
|
+
Updating Sibu alone does not change project files. It only changes what `sibu doctor` can detect against the newer installed templates.
|
|
22
|
+
|
|
23
|
+
## CLI
|
|
24
|
+
|
|
25
|
+
Sibu helps projects adopt and maintain AI workflow support files safely.
|
|
26
|
+
|
|
27
|
+
From a project directory, run:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
sibu init
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`sibu init` is idempotent: it opens a polished interactive flow, lets you select supported agents with the keyboard, creates missing workflow files for that selection, keeps existing files unchanged, and reports when no changes are needed. When `AGENTS.md` is missing, the CLI asks for a project overview and writes the file based on the template in this repository. It can also create Codex, Gemini, and Claude support files when selected. Sibu records the generated workflow state in `.sibu/state.json` so `sibu doctor` can detect drift safely.
|
|
34
|
+
|
|
35
|
+
Use `sibu doctor` to run a read-only health check. It reports missing managed files, local modifications, malformed state metadata, and template version drift without changing files.
|
|
36
|
+
|
|
37
|
+
If `sibu doctor` tells you that a newer npm version exists, update with `npm install -g @juancr11/sibu`, rerun `sibu doctor`, and only then decide whether to run `sibu sync`. Updating Sibu alone changes what doctor can detect; it does not change project files.
|
|
38
|
+
|
|
39
|
+
Use `sibu sync` to review template updates interactively. It explains human-readable template changes, protects local edits from automatic overwrites, and lets you apply safe updates, mark customized files as reviewed, write side templates, stop managing a file, or skip for later. Sibu records each file as `managed`, `customized`, or `unmanaged` in `.sibu/state.json`.
|
|
40
|
+
|
|
41
|
+
Use `sibu skills list` to list available workflow skills and see which selectable skills are currently enabled for the project.
|
|
42
|
+
|
|
43
|
+
Use `sibu skills stop <file>` to stop managing an Sibu-tracked workflow file. The file is marked as `unmanaged` in `.sibu/state.json`, removed from the selected skill state when applicable, and the CLI asks whether to keep or delete the local file.
|
|
44
|
+
|
|
45
|
+
## Release notes and changelog
|
|
46
|
+
|
|
47
|
+
For every Sibu release, update both release-note locations:
|
|
48
|
+
|
|
49
|
+
- `CHANGELOG.md` is the canonical source in the repo
|
|
50
|
+
- the matching GitHub Release is the public release surface
|
|
51
|
+
|
|
52
|
+
When preparing a release, write or update the `CHANGELOG.md` entry first, then publish the matching GitHub Release using that same summary.
|
|
53
|
+
|
|
54
|
+
This keeps one source of truth in the repo while still giving users a public release page they can browse from GitHub.
|
|
55
|
+
|
|
56
|
+
## Maintainer release workflow
|
|
57
|
+
|
|
58
|
+
If you are preparing a new Sibu npm release, use [`docs/releasing.md`](docs/releasing.md). That guide covers version bumping, `CHANGELOG.md`, tarball-based validation, npm publish, GitHub Release creation, and post-publish verification.
|
|
59
|
+
|
|
60
|
+
## Contributor development
|
|
61
|
+
|
|
62
|
+
The rest of this README section is for contributors working on Sibu itself, not for end users installing the CLI.
|
|
63
|
+
|
|
64
|
+
The CLI entrypoint lives in `src/entrypoints/cli/`, command handlers live in `src/features/`, shared workflow utilities live in `src/shared/`, and everything compiles to `bin/sibu.js`. The `bin/` directory is generated by `pnpm build` and should not be edited directly.
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
pnpm install
|
|
68
|
+
pnpm verify
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Contributor local testing
|
|
72
|
+
|
|
73
|
+
Use `pnpm link --global` to make the local `sibu` command available from any directory while developing.
|
|
74
|
+
|
|
75
|
+
This linked workflow is useful for day-to-day contributor development, but it is **not** a supported end-user install path and it is **not** the release-readiness check for npm publishing. For release readiness, prefer tarball-based validation with `npm pack` so you verify the exact package contents users will install.
|
|
76
|
+
|
|
77
|
+
From this repository:
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
pnpm dev:link
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Then test it in a new project directory:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
mkdir /tmp/test-sibu-project
|
|
87
|
+
cd /tmp/test-sibu-project
|
|
88
|
+
git init
|
|
89
|
+
sibu init
|
|
90
|
+
sibu doctor
|
|
91
|
+
sibu sync
|
|
92
|
+
sibu skills list
|
|
93
|
+
find . -maxdepth 2 -type f | sort
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
After editing CLI source files under `src/`, rebuild before testing again:
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
cd /home/juanca/code/ai-augmented-workflow
|
|
100
|
+
pnpm build
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
When you are done testing the linked CLI, remove the global link:
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
pnpm dev:unlink
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Validate the npm package
|
|
110
|
+
|
|
111
|
+
Before treating a build as ready to publish, validate the packaged artifact instead of relying only on `pnpm link`.
|
|
112
|
+
|
|
113
|
+
### 1. Inspect the tarball contents
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
pnpm verify
|
|
117
|
+
mkdir -p /tmp/sibu-pack
|
|
118
|
+
npm pack --json --pack-destination /tmp/sibu-pack
|
|
119
|
+
tar -tzf /tmp/sibu-pack/juancr11-sibu-*.tgz | sort
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Use the tarball listing to confirm the packed artifact includes the expected runtime files:
|
|
123
|
+
|
|
124
|
+
- `bin/sibu.js`
|
|
125
|
+
- runtime code under `bin/entrypoints/`, `bin/features/`, and `bin/shared/`
|
|
126
|
+
- `templates/`
|
|
127
|
+
- `README.md`
|
|
128
|
+
|
|
129
|
+
This check proves the package contents are correct, but it does **not** prove the installed CLI runs correctly after npm global install.
|
|
130
|
+
|
|
131
|
+
### 2. Smoke test the installed tarball runtime
|
|
132
|
+
|
|
133
|
+
```sh
|
|
134
|
+
pnpm run validate:packed-runtime
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
This command:
|
|
138
|
+
|
|
139
|
+
- runs `npm pack` in an isolated temporary workspace
|
|
140
|
+
- installs the produced tarball into an isolated npm prefix
|
|
141
|
+
- verifies `sibu --help` runs from the installed binary
|
|
142
|
+
- creates a temporary fixture project outside this repo
|
|
143
|
+
- runs `sibu doctor` in that fixture project to prove the installed CLI can read bundled runtime assets such as templates
|
|
144
|
+
|
|
145
|
+
If this command fails, treat it as an installed-package runtime problem rather than a normal local-development issue.
|
|
146
|
+
|
|
147
|
+
Treat tarball inspection plus `pnpm run validate:packed-runtime` as the release-readiness path for npm packaging changes. Use `pnpm dev:link` only for interactive local development.
|
|
148
|
+
|
|
149
|
+
## Validate the doctor npm update advisory
|
|
150
|
+
|
|
151
|
+
Use the deterministic override hooks to verify both advisory scenarios without depending on a real npm publish cycle:
|
|
152
|
+
|
|
153
|
+
```sh
|
|
154
|
+
pnpm run validate:doctor-version-advisory
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
This validation proves both of these cases locally:
|
|
158
|
+
|
|
159
|
+
- a newer npm version is available and `sibu doctor` suggests `npm install -g @juancr11/sibu`
|
|
160
|
+
- npm lookup is unavailable and `sibu doctor` still completes the local health check without failing
|
|
161
|
+
|
|
162
|
+
For one-off manual checks, you can also run:
|
|
163
|
+
|
|
164
|
+
```sh
|
|
165
|
+
SIBU_NPM_LATEST_VERSION=9.9.9 node ./bin/sibu.js doctor
|
|
166
|
+
SIBU_NPM_LOOKUP_MODE=offline node ./bin/sibu.js doctor
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Validate post-update drift after upgrading Sibu
|
|
170
|
+
|
|
171
|
+
Use the local two-tarball validation flow to prove that upgrading Sibu can surface new drift without changing project files automatically:
|
|
172
|
+
|
|
173
|
+
```sh
|
|
174
|
+
pnpm run validate:post-update-doctor-drift
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
This validation proves the full explicit flow locally:
|
|
178
|
+
|
|
179
|
+
- install an older local tarball
|
|
180
|
+
- run `sibu doctor` and see the npm update advisory
|
|
181
|
+
- update to a newer local tarball
|
|
182
|
+
- rerun `sibu doctor` and see drift reported
|
|
183
|
+
- confirm no files change until `sibu sync` is explicitly run
|
|
184
|
+
|
|
185
|
+
## Changing templates
|
|
186
|
+
|
|
187
|
+
Templates are versioned product artifacts. When changing any file under `templates/`, use the local template-change skill if available and update `templates/manifest.json` in the same change.
|
|
188
|
+
|
|
189
|
+
Template change checklist:
|
|
190
|
+
|
|
191
|
+
- Bump the global `templateVersion` when any template changes.
|
|
192
|
+
- Bump each changed template's own `version`.
|
|
193
|
+
- Add user-facing change notes that explain what changed and why it matters.
|
|
194
|
+
- Avoid renaming template paths unless migration or backwards compatibility is handled.
|
|
195
|
+
- Run `pnpm verify`.
|
|
196
|
+
- Test `sibu init`, `sibu doctor`, and `sibu sync` in a temporary project when practical.
|
|
197
|
+
|
|
198
|
+
The `changes` entries in `templates/manifest.json` are shown by `sibu sync`, so write them for users rather than as code-level diff notes. If using an AI coding agent, explicitly ask it to use the local template-change skill before editing templates.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Command as CommanderCommand } from 'commander';
|
|
2
|
+
import { SIBU_VERSION } from '../../shared/catalog.js';
|
|
3
|
+
import { executeCliCommand } from './execute-command.js';
|
|
4
|
+
export function createProgram() {
|
|
5
|
+
const cli = new CommanderCommand();
|
|
6
|
+
cli.name('sibu').description('Set up a local AI-augmented development workflow.').version(SIBU_VERSION);
|
|
7
|
+
cli
|
|
8
|
+
.command('init')
|
|
9
|
+
.description('Initialize Sibu workflow files once for a project')
|
|
10
|
+
.action(() => executeCliCommand({ type: 'init' }));
|
|
11
|
+
cli
|
|
12
|
+
.command('doctor')
|
|
13
|
+
.description('Read-only health check for Sibu-managed workflow files')
|
|
14
|
+
.action(() => executeCliCommand({ type: 'doctor' }));
|
|
15
|
+
cli
|
|
16
|
+
.command('sync')
|
|
17
|
+
.description('Interactively review and apply Sibu template updates')
|
|
18
|
+
.action(() => executeCliCommand({ type: 'sync' }));
|
|
19
|
+
const skills = cli.command('skills').description('Manage Sibu workflow skills');
|
|
20
|
+
skills
|
|
21
|
+
.command('list')
|
|
22
|
+
.description('List available Sibu skills')
|
|
23
|
+
.action(() => executeCliCommand({ type: 'skills:list' }));
|
|
24
|
+
skills
|
|
25
|
+
.command('use <skill_name>')
|
|
26
|
+
.description('Add one available selectable skill to a clean Sibu workflow')
|
|
27
|
+
.action((skillName) => executeCliCommand({ type: 'skills:use', skillName }));
|
|
28
|
+
skills
|
|
29
|
+
.command('stop <skill_name>')
|
|
30
|
+
.description('Stop managing one selected Sibu skill')
|
|
31
|
+
.action((skillName) => executeCliCommand({ type: 'skills:stop', skillName }));
|
|
32
|
+
return cli;
|
|
33
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { handleDoctorProject } from '../../features/doctor-project/handler.js';
|
|
2
|
+
import { handleInitProject } from '../../features/init-project/handler.js';
|
|
3
|
+
import { handleListSkills } from '../../features/list-skills/handler.js';
|
|
4
|
+
import { handleStopManagingFile } from '../../features/stop-managing-file/handler.js';
|
|
5
|
+
import { handleSyncProject } from '../../features/sync-project/handler.js';
|
|
6
|
+
import { handleUseSkill } from '../../features/use-skill/handler.js';
|
|
7
|
+
export async function executeCliCommand(command) {
|
|
8
|
+
switch (command.type) {
|
|
9
|
+
case 'init':
|
|
10
|
+
await handleInitProject(command);
|
|
11
|
+
return;
|
|
12
|
+
case 'doctor':
|
|
13
|
+
await handleDoctorProject(command);
|
|
14
|
+
return;
|
|
15
|
+
case 'sync':
|
|
16
|
+
await handleSyncProject(command);
|
|
17
|
+
return;
|
|
18
|
+
case 'skills:list':
|
|
19
|
+
await handleListSkills(command);
|
|
20
|
+
return;
|
|
21
|
+
case 'skills:stop':
|
|
22
|
+
await handleStopManagingFile(command);
|
|
23
|
+
return;
|
|
24
|
+
case 'skills:use':
|
|
25
|
+
await handleUseSkill(command);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { intro, log, outro } from '@clack/prompts';
|
|
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';
|
|
6
|
+
import { sha256 } from '../../shared/hash.js';
|
|
7
|
+
import { checkForLatestSibuVersion } from '../../shared/npm-version.js';
|
|
8
|
+
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';
|
|
13
|
+
export async function handleDoctorProject(_command) {
|
|
14
|
+
await renderIntro();
|
|
15
|
+
intro(chalk.cyan('Checking workflow state'));
|
|
16
|
+
const { rootPath, statePath } = getProjectContext();
|
|
17
|
+
const stateResult = readStateForDoctor(statePath);
|
|
18
|
+
if (!stateResult.ok) {
|
|
19
|
+
log.error(stateResult.message);
|
|
20
|
+
log.info('Run `sibu init` once to create Sibu workflow metadata.');
|
|
21
|
+
outro(chalk.yellow('Setup needs attention.'));
|
|
22
|
+
process.exitCode = 1;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const state = stateResult.state;
|
|
26
|
+
const issues = diagnoseState({ rootPath, state });
|
|
27
|
+
const npmVersionResult = await checkForLatestSibuVersion();
|
|
28
|
+
if (issues.length === 0) {
|
|
29
|
+
log.success('Workflow is healthy. No drift detected.');
|
|
30
|
+
log.info(`Sibu version: ${state.sibuVersion}`);
|
|
31
|
+
log.info(`Template version: ${state.templateVersion}`);
|
|
32
|
+
log.info(`Managed workflow files: ${Object.keys(state.managedFiles).length}`);
|
|
33
|
+
log.info(`File statuses: ${formatManagedFileStatusCounts(state)}`);
|
|
34
|
+
logNpmVersionAdvisory(npmVersionResult);
|
|
35
|
+
outro(chalk.green('Check complete.'));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
log.warn('Found workflow changes that need review.');
|
|
39
|
+
for (const issue of issues) {
|
|
40
|
+
if (issue.severity === 'error') {
|
|
41
|
+
log.error(issue.message);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
log.warn(issue.message);
|
|
45
|
+
}
|
|
46
|
+
if (issue.hint) {
|
|
47
|
+
log.info(issue.hint);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
logNpmVersionAdvisory(npmVersionResult);
|
|
51
|
+
logSyncNextStep();
|
|
52
|
+
outro(chalk.yellow('Review needed.'));
|
|
53
|
+
process.exitCode = 1;
|
|
54
|
+
}
|
|
55
|
+
export function getNpmVersionAdvisoryLines(result) {
|
|
56
|
+
if (result.status !== 'update-available') {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
return [
|
|
60
|
+
`A newer Sibu version is available: ${result.latestVersion} (${result.currentVersion} installed).`,
|
|
61
|
+
'Update with `npm install -g @juancr11/sibu`.',
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
export function getDoctorSyncNextStepLines() {
|
|
65
|
+
return [
|
|
66
|
+
'Run `sibu sync` to review these workflow changes and choose whether to apply them.',
|
|
67
|
+
'Sibu will not change project files until you explicitly run `sibu sync`.',
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
function logNpmVersionAdvisory(result) {
|
|
71
|
+
for (const line of getNpmVersionAdvisoryLines(result)) {
|
|
72
|
+
log.info(line);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function logSyncNextStep() {
|
|
76
|
+
for (const line of getDoctorSyncNextStepLines()) {
|
|
77
|
+
log.info(line);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function diagnoseState({ rootPath, state }) {
|
|
81
|
+
const issues = [];
|
|
82
|
+
const manifest = readTemplateManifest();
|
|
83
|
+
if (state.templateVersion !== manifest.templateVersion) {
|
|
84
|
+
issues.push({
|
|
85
|
+
severity: 'warning',
|
|
86
|
+
message: `State was generated from template version ${state.templateVersion}; current template version is ${manifest.templateVersion}.`,
|
|
87
|
+
hint: 'Run `sibu sync` to review and apply or dismiss template updates.',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
addUnsupportedSelectionIssues(state, issues);
|
|
91
|
+
const reportedMissingPaths = addExpectedTargetIssues(rootPath, state, issues);
|
|
92
|
+
addManagedFileIssues(rootPath, state, issues, reportedMissingPaths);
|
|
93
|
+
return issues;
|
|
94
|
+
}
|
|
95
|
+
function addUnsupportedSelectionIssues(state, issues) {
|
|
96
|
+
for (const selectedAgent of state.selectedAgents) {
|
|
97
|
+
if (!SUPPORTED_AGENTS.some((agent) => agent.id === selectedAgent)) {
|
|
98
|
+
issues.push({ severity: 'warning', message: `State references unsupported agent: ${selectedAgent}.` });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
for (const selectedLanguageSkill of state.selectedLanguageSkills ?? []) {
|
|
102
|
+
if (!SELECTABLE_LANGUAGE_SKILLS.some((skill) => skill.id === selectedLanguageSkill)) {
|
|
103
|
+
issues.push({ severity: 'warning', message: `State references unsupported language skill: ${selectedLanguageSkill}.` });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
for (const reviewedLanguageSkill of state.reviewedLanguageSkills ?? []) {
|
|
107
|
+
if (!SELECTABLE_LANGUAGE_SKILLS.some((skill) => skill.id === reviewedLanguageSkill)) {
|
|
108
|
+
issues.push({ severity: 'warning', message: `State references unsupported reviewed language skill: ${reviewedLanguageSkill}.` });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
for (const selectedFrameworkSkill of state.selectedFrameworkSkills ?? []) {
|
|
112
|
+
if (!SELECTABLE_FRAMEWORK_SKILLS.some((skill) => skill.id === selectedFrameworkSkill)) {
|
|
113
|
+
issues.push({ severity: 'warning', message: `State references unsupported framework skill: ${selectedFrameworkSkill}.` });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (state.selectedArchitectureSkill && !SELECTABLE_ARCHITECTURE_SKILLS.some((skill) => skill.id === state.selectedArchitectureSkill)) {
|
|
117
|
+
issues.push({ severity: 'warning', message: `State references unsupported architecture skill: ${state.selectedArchitectureSkill}.` });
|
|
118
|
+
}
|
|
119
|
+
for (const reviewedArchitectureSkill of state.reviewedArchitectureSkills ?? []) {
|
|
120
|
+
if (!SELECTABLE_ARCHITECTURE_SKILLS.some((skill) => skill.id === reviewedArchitectureSkill)) {
|
|
121
|
+
issues.push({ severity: 'warning', message: `State references unsupported reviewed architecture skill: ${reviewedArchitectureSkill}.` });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function addExpectedTargetIssues(rootPath, state, issues) {
|
|
126
|
+
const reportedMissingPaths = new Set();
|
|
127
|
+
const expectedTargets = getWorkflowTargets(rootPath, getSelectedAgentsFromState(state), getSelectedLanguageSkillsFromState(state), getSelectedFrameworkSkillsFromState(state), getSelectedArchitectureSkillFromState(state));
|
|
128
|
+
for (const target of expectedTargets) {
|
|
129
|
+
const relativePath = path.relative(rootPath, target.targetPath);
|
|
130
|
+
const managedFile = state.managedFiles[relativePath];
|
|
131
|
+
if (managedFile?.status === 'unmanaged') {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (!fs.existsSync(target.targetPath)) {
|
|
135
|
+
reportedMissingPaths.add(relativePath);
|
|
136
|
+
issues.push({
|
|
137
|
+
severity: 'error',
|
|
138
|
+
message: `${relativePath} is missing.`,
|
|
139
|
+
hint: 'Run `sibu sync` to recreate missing managed workflow files.',
|
|
140
|
+
});
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (!managedFile) {
|
|
144
|
+
issues.push({
|
|
145
|
+
severity: 'warning',
|
|
146
|
+
message: `${relativePath} exists but is not recorded in ${STATE_RELATIVE_PATH}.`,
|
|
147
|
+
hint: 'Run `sibu sync` to review and record this workflow file.',
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return reportedMissingPaths;
|
|
152
|
+
}
|
|
153
|
+
function addManagedFileIssues(rootPath, state, issues, reportedMissingPaths) {
|
|
154
|
+
const manifest = readTemplateManifest();
|
|
155
|
+
for (const [relativePath, managedFile] of Object.entries(state.managedFiles)) {
|
|
156
|
+
if (managedFile.status === 'unmanaged') {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
const targetPath = path.join(rootPath, relativePath);
|
|
160
|
+
if (!fs.existsSync(targetPath)) {
|
|
161
|
+
if (!reportedMissingPaths.has(relativePath)) {
|
|
162
|
+
issues.push({
|
|
163
|
+
severity: 'error',
|
|
164
|
+
message: `${relativePath} is recorded in ${STATE_RELATIVE_PATH} but is missing.`,
|
|
165
|
+
hint: 'Run `sibu sync` to recreate missing managed workflow files.',
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const currentHash = sha256(fs.readFileSync(targetPath, 'utf8'));
|
|
171
|
+
if (currentHash !== managedFile.sha256) {
|
|
172
|
+
issues.push({
|
|
173
|
+
severity: 'warning',
|
|
174
|
+
message: `${relativePath} has changed since Sibu last recorded it.`,
|
|
175
|
+
hint: 'If this was intentional, keep it. `sibu sync` will not overwrite local edits automatically.',
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
const currentTemplateVersion = getTemplateVersion(manifest, managedFile.template);
|
|
179
|
+
if (managedFile.templateVersion !== currentTemplateVersion && !hasReviewedTemplateVersion(managedFile, currentTemplateVersion)) {
|
|
180
|
+
issues.push({
|
|
181
|
+
severity: 'warning',
|
|
182
|
+
message: `${relativePath} was generated from template version ${managedFile.templateVersion}; current template version is ${currentTemplateVersion}.`,
|
|
183
|
+
hint: 'Run `sibu sync` to review and apply or dismiss this template update.',
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function formatManagedFileStatusCounts(state) {
|
|
189
|
+
const counts = Object.values(state.managedFiles).reduce((accumulator, managedFile) => {
|
|
190
|
+
accumulator[managedFile.status ?? 'managed'] += 1;
|
|
191
|
+
return accumulator;
|
|
192
|
+
}, { managed: 0, customized: 0, unmanaged: 0 });
|
|
193
|
+
return `managed=${counts.managed}, customized=${counts.customized}, unmanaged=${counts.unmanaged}`;
|
|
194
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { intro, log, outro } from '@clack/prompts';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { STATE_RELATIVE_PATH } from '../../shared/catalog.js';
|
|
6
|
+
import { getProjectContext } from '../../shared/paths.js';
|
|
7
|
+
import { askForArchitectureSkill, askForFrameworkSkills, askForLanguageSkills, askForProjectOverview, askForSupportedAgents, renderIntro } from '../../shared/prompts.js';
|
|
8
|
+
import { readStateForDoctor } from '../../shared/state.js';
|
|
9
|
+
import { getWorkflowTargets, renderMissingWorkflowFiles, writeSibuState } from '../../shared/workflow-targets.js';
|
|
10
|
+
export async function handleInitProject(_command) {
|
|
11
|
+
await renderIntro();
|
|
12
|
+
intro(chalk.cyan('Setting up Sibu'));
|
|
13
|
+
const { rootPath, statePath } = getProjectContext();
|
|
14
|
+
if (fs.existsSync(statePath)) {
|
|
15
|
+
const stateResult = readStateForDoctor(statePath);
|
|
16
|
+
if (stateResult.ok) {
|
|
17
|
+
log.success('This project is already initialized with Sibu.');
|
|
18
|
+
log.info('Run `sibu doctor` for a read-only health check.');
|
|
19
|
+
log.info('Run `sibu sync` to review template updates, missing files, or workflow changes.');
|
|
20
|
+
outro(chalk.green('Setup already complete.'));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
log.error(stateResult.message);
|
|
24
|
+
log.info(`${STATE_RELATIVE_PATH} already exists, so I will not overwrite it from init.`);
|
|
25
|
+
log.info('Fix or restore the state file before running `sibu doctor` or `sibu sync`.');
|
|
26
|
+
outro(chalk.yellow('Setup needs attention.'));
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const selectedAgents = await askForSupportedAgents();
|
|
31
|
+
const selectedLanguageSkills = await askForLanguageSkills();
|
|
32
|
+
const selectedFrameworkSkills = await askForFrameworkSkills();
|
|
33
|
+
const selectedArchitectureSkill = await askForArchitectureSkill();
|
|
34
|
+
const targets = getWorkflowTargets(rootPath, selectedAgents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill);
|
|
35
|
+
const missingTargets = targets.filter((target) => !fs.existsSync(target.targetPath));
|
|
36
|
+
log.message('I will create this project’s initial AI workflow files.');
|
|
37
|
+
for (const target of targets) {
|
|
38
|
+
if (fs.existsSync(target.targetPath)) {
|
|
39
|
+
log.info(`${target.label} already exists. I will keep it unchanged and record it in ${STATE_RELATIVE_PATH}.`);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
log.info(`${target.label} is missing. I will create it.`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
log.info(`${STATE_RELATIVE_PATH} is missing. I will create it.`);
|
|
46
|
+
const shouldAskForOverview = missingTargets.some((target) => target.requiresProjectOverview);
|
|
47
|
+
const overview = shouldAskForOverview ? await askForProjectOverview() : undefined;
|
|
48
|
+
const files = renderMissingWorkflowFiles({
|
|
49
|
+
missingTargets,
|
|
50
|
+
overview,
|
|
51
|
+
selectedLanguageSkills,
|
|
52
|
+
selectedFrameworkSkills,
|
|
53
|
+
selectedArchitectureSkill,
|
|
54
|
+
});
|
|
55
|
+
for (const file of files) {
|
|
56
|
+
fs.mkdirSync(path.dirname(file.targetPath), { recursive: true });
|
|
57
|
+
fs.writeFileSync(file.targetPath, file.contents, { encoding: 'utf8', flag: 'wx' });
|
|
58
|
+
log.success(`Created ${file.label}`);
|
|
59
|
+
}
|
|
60
|
+
writeSibuState({ rootPath, statePath, selectedAgents, selectedLanguageSkills, selectedFrameworkSkills, selectedArchitectureSkill, targets });
|
|
61
|
+
log.success(`Created ${STATE_RELATIVE_PATH}`);
|
|
62
|
+
outro(chalk.green('Setup complete.'));
|
|
63
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { intro, log, outro } from '@clack/prompts';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { SELECTABLE_ARCHITECTURE_SKILLS, SELECTABLE_FRAMEWORK_SKILLS, SELECTABLE_LANGUAGE_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';
|
|
7
|
+
export async function handleListSkills(_command) {
|
|
8
|
+
await renderIntro();
|
|
9
|
+
intro(chalk.cyan('Available workflow skills'));
|
|
10
|
+
const { statePath } = getProjectContext();
|
|
11
|
+
const stateResult = readStateForDoctor(statePath);
|
|
12
|
+
const state = stateResult.ok ? stateResult.state : undefined;
|
|
13
|
+
if (!stateResult.ok) {
|
|
14
|
+
log.warn(`${stateResult.message} Showing available skills without project state.`);
|
|
15
|
+
log.info('Run `sibu init` before selecting project skills.');
|
|
16
|
+
}
|
|
17
|
+
logSkillGroup('Languages', getLanguageSkillItems(state));
|
|
18
|
+
logSkillGroup('Frameworks', getFrameworkSkillItems(state));
|
|
19
|
+
logSkillGroup('Architecture', getArchitectureSkillItems(state));
|
|
20
|
+
outro(chalk.green('Skill list ready.'));
|
|
21
|
+
}
|
|
22
|
+
function getLanguageSkillItems(state) {
|
|
23
|
+
const selectedSkillIds = new Set(state?.selectedLanguageSkills ?? []);
|
|
24
|
+
return SELECTABLE_LANGUAGE_SKILLS.map((skill) => ({
|
|
25
|
+
name: skill.name,
|
|
26
|
+
id: skill.id,
|
|
27
|
+
description: skill.description,
|
|
28
|
+
selected: selectedSkillIds.has(skill.id),
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
function getFrameworkSkillItems(state) {
|
|
32
|
+
const selectedSkillIds = new Set(state?.selectedFrameworkSkills ?? []);
|
|
33
|
+
return SELECTABLE_FRAMEWORK_SKILLS.map((skill) => ({
|
|
34
|
+
name: skill.name,
|
|
35
|
+
id: skill.id,
|
|
36
|
+
description: skill.description,
|
|
37
|
+
selected: selectedSkillIds.has(skill.id),
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
function getArchitectureSkillItems(state) {
|
|
41
|
+
return SELECTABLE_ARCHITECTURE_SKILLS.map((skill) => ({
|
|
42
|
+
name: skill.name,
|
|
43
|
+
id: skill.id,
|
|
44
|
+
description: skill.description,
|
|
45
|
+
selected: state?.selectedArchitectureSkill === skill.id,
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
function logSkillGroup(label, skills) {
|
|
49
|
+
log.message(chalk.bold(label));
|
|
50
|
+
for (const skill of skills) {
|
|
51
|
+
const marker = skill.selected ? chalk.green('● selected') : chalk.dim('○ available');
|
|
52
|
+
console.log(` ${marker} ${chalk.bold(skill.name)} ${chalk.dim(`(${skill.id})`)}`);
|
|
53
|
+
console.log(` ${chalk.dim(skill.description)}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|