@juancr11/sibu 0.5.3 → 0.7.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/bin/admin/changelog.js +155 -0
- package/bin/admin/release.js +217 -0
- package/bin/entrypoints/cli/create-program.js +1 -1
- package/bin/entrypoints/cli/execute-command.js +6 -6
- package/bin/modules/interactive-guidance/index.js +1 -0
- package/bin/{shared → modules/interactive-guidance}/prompts.js +1 -1
- package/bin/modules/maintainer-release-support/generate-changelog/changelog-format.js +213 -0
- package/bin/modules/maintainer-release-support/generate-changelog/changelog-writer.js +91 -0
- package/bin/modules/maintainer-release-support/generate-changelog/git-history.js +93 -0
- package/bin/modules/maintainer-release-support/generate-changelog/handler.js +167 -0
- package/bin/modules/maintainer-release-support/generate-changelog/semver.js +40 -0
- package/bin/modules/maintainer-release-support/index.js +2 -0
- package/bin/modules/maintainer-release-support/release-workflow/git-release.js +114 -0
- package/bin/modules/maintainer-release-support/release-workflow/handler.js +369 -0
- package/bin/modules/maintainer-release-support/release-workflow/package-json.js +181 -0
- package/bin/modules/maintainer-release-support/release-workflow/release-plan.js +87 -0
- package/bin/{features/init-project → modules/project-adoption}/handler.js +3 -3
- package/bin/modules/project-adoption/index.js +1 -0
- package/bin/modules/skill-selection-management/index.js +3 -0
- package/bin/{features → modules/skill-selection-management}/list-skills/handler.js +4 -4
- package/bin/{features → modules/skill-selection-management}/stop-managing-file/handler.js +9 -8
- package/bin/modules/skill-selection-management/use-skill/command.js +1 -0
- package/bin/{features → modules/skill-selection-management}/use-skill/handler.js +7 -6
- package/bin/{features/sync-project → modules/sync-review}/apply-action.js +3 -3
- package/bin/modules/sync-review/command.js +1 -0
- package/bin/{features/sync-project → modules/sync-review}/handler.js +4 -4
- package/bin/modules/sync-review/index.js +5 -0
- package/bin/{shared → modules/sync-review}/sync-preview.js +4 -4
- package/bin/modules/template-catalog-rendering/index.js +1 -0
- package/bin/modules/template-catalog-rendering/templates.js +60 -0
- package/bin/modules/version-advisory/index.js +1 -0
- package/bin/{shared → modules/version-advisory}/npm-version.js +29 -2
- package/bin/modules/workflow-health-diagnosis/command.js +1 -0
- package/bin/{features/doctor-project → modules/workflow-health-diagnosis}/handler.js +8 -7
- package/bin/modules/workflow-health-diagnosis/index.js +1 -0
- package/bin/modules/workflow-mutation-readiness/index.js +1 -0
- package/bin/{shared → modules/workflow-mutation-readiness}/workflow-mutation-readiness.js +3 -3
- package/bin/modules/workflow-state-registry/index.js +1 -0
- package/bin/{shared → modules/workflow-state-registry}/state.js +1 -1
- package/bin/modules/workflow-target-planning/catalog.js +233 -0
- package/bin/modules/workflow-target-planning/index.js +2 -0
- package/bin/modules/workflow-target-planning/workflow-targets.js +125 -0
- package/bin/shared/catalog.js +0 -244
- package/bin/shared/paths.js +1 -12
- package/bin/shared/workflow-targets.js +3 -2
- package/package.json +5 -3
- package/templates/AGENTS.md +4 -1
- package/templates/manifest.json +33 -22
- package/templates/skills/ai-implementation-plan-executor/SKILL.md +93 -51
- package/templates/skills/ai-implementation-planner/SKILL.md +52 -8
- package/templates/skills/architecture/command-pattern/SKILL.md +38 -16
- package/templates/skills/architecture/ddd-hexagonal/SKILL.md +19 -4
- package/templates/skills/deep-module-map-writer/SKILL.md +241 -0
- package/templates/skills/feature-brief-writer/SKILL.md +86 -13
- package/templates/skills/product-vision-writer/SKILL.md +25 -0
- package/templates/skills/scrum-master-planner/SKILL.md +29 -0
- package/templates/skills/technical-design-writer/SKILL.md +48 -5
- package/templates/skills/ux-expert/SKILL.md +28 -0
- package/bin/features/sync-project/preview.js +0 -1
- /package/bin/{features/doctor-project/command.js → modules/cli-command-surface/index.js} +0 -0
- /package/bin/{features/init-project → modules/maintainer-release-support/generate-changelog}/command.js +0 -0
- /package/bin/{features/list-skills → modules/maintainer-release-support/release-workflow}/command.js +0 -0
- /package/bin/{features/stop-managing-file → modules/project-adoption}/command.js +0 -0
- /package/bin/{features/sync-project → modules/skill-selection-management/list-skills}/command.js +0 -0
- /package/bin/{features/use-skill → modules/skill-selection-management/stop-managing-file}/command.js +0 -0
- /package/bin/{features/sync-project → modules/sync-review}/action-prompt.js +0 -0
- /package/bin/{features/sync-project → modules/sync-review}/log-preview.js +0 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createInterface } from 'node:readline/promises';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { handleGenerateChangelogWrite } from '../modules/maintainer-release-support/generate-changelog/handler.js';
|
|
6
|
+
const USAGE = `Usage: pnpm admin:changelog -- [--from <ref>] [--to <ref>] [--version <version>] [--date <yyyy-mm-dd>] [--yes]
|
|
7
|
+
|
|
8
|
+
Options:
|
|
9
|
+
--from <ref> Git ref to start from. Defaults to the latest reachable tag.
|
|
10
|
+
--to <ref> Git ref to end at. Defaults to HEAD.
|
|
11
|
+
--version <version> Release version. Accepts MAJOR.MINOR.PATCH with optional leading v.
|
|
12
|
+
--date <yyyy-mm-dd> Release date for a versioned changelog section.
|
|
13
|
+
--yes Print the preview and write without prompting.
|
|
14
|
+
--help Show this help message.
|
|
15
|
+
`;
|
|
16
|
+
export function parseChangelogArgs(args) {
|
|
17
|
+
const command = {};
|
|
18
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
19
|
+
const arg = args[index];
|
|
20
|
+
switch (arg) {
|
|
21
|
+
case '--':
|
|
22
|
+
break;
|
|
23
|
+
case '--help':
|
|
24
|
+
case '-h':
|
|
25
|
+
return { status: 'help', usage: USAGE };
|
|
26
|
+
case '--yes':
|
|
27
|
+
command.assumeYes = true;
|
|
28
|
+
break;
|
|
29
|
+
case '--from': {
|
|
30
|
+
const value = readFlagValue(args, index, arg);
|
|
31
|
+
if (value.status === 'error') {
|
|
32
|
+
return value.result;
|
|
33
|
+
}
|
|
34
|
+
command.fromRef = value.value;
|
|
35
|
+
index += 1;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
case '--to': {
|
|
39
|
+
const value = readFlagValue(args, index, arg);
|
|
40
|
+
if (value.status === 'error') {
|
|
41
|
+
return value.result;
|
|
42
|
+
}
|
|
43
|
+
command.toRef = value.value;
|
|
44
|
+
index += 1;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
case '--version': {
|
|
48
|
+
const value = readFlagValue(args, index, arg);
|
|
49
|
+
if (value.status === 'error') {
|
|
50
|
+
return value.result;
|
|
51
|
+
}
|
|
52
|
+
command.version = value.value;
|
|
53
|
+
index += 1;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
case '--date': {
|
|
57
|
+
const value = readFlagValue(args, index, arg);
|
|
58
|
+
if (value.status === 'error') {
|
|
59
|
+
return value.result;
|
|
60
|
+
}
|
|
61
|
+
command.date = value.value;
|
|
62
|
+
index += 1;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
default:
|
|
66
|
+
if (arg?.startsWith('--')) {
|
|
67
|
+
return error(`Unknown option \`${arg}\`.`);
|
|
68
|
+
}
|
|
69
|
+
return error(`Unexpected argument \`${arg ?? ''}\`.`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { status: 'ok', command };
|
|
73
|
+
}
|
|
74
|
+
export async function runChangelogCli(args = process.argv.slice(2), cwd = process.cwd()) {
|
|
75
|
+
const parsedArgs = parseChangelogArgs(args);
|
|
76
|
+
if (parsedArgs.status === 'help') {
|
|
77
|
+
process.stdout.write(parsedArgs.usage);
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
if (parsedArgs.status === 'error') {
|
|
81
|
+
process.stderr.write(`${parsedArgs.message}\n\n${parsedArgs.usage}`);
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
84
|
+
const result = await handleGenerateChangelogWrite(parsedArgs.command, createTerminalPorts(), cwd);
|
|
85
|
+
switch (result.status) {
|
|
86
|
+
case 'written':
|
|
87
|
+
process.stdout.write(`\nWrote ${result.changelogPath}\n`);
|
|
88
|
+
return 0;
|
|
89
|
+
case 'declined':
|
|
90
|
+
process.stdout.write('\nNo changes written.\n');
|
|
91
|
+
return 0;
|
|
92
|
+
case 'blocked':
|
|
93
|
+
process.stderr.write(`\nChangelog generation blocked: ${result.message}\n`);
|
|
94
|
+
return 1;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function readFlagValue(args, index, flag) {
|
|
98
|
+
const value = args[index + 1];
|
|
99
|
+
if (!value || value.startsWith('--')) {
|
|
100
|
+
return {
|
|
101
|
+
status: 'error',
|
|
102
|
+
result: error(`Option \`${flag}\` requires a value.`),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return { status: 'ok', value };
|
|
106
|
+
}
|
|
107
|
+
function error(message) {
|
|
108
|
+
return {
|
|
109
|
+
status: 'error',
|
|
110
|
+
message,
|
|
111
|
+
usage: USAGE,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function createTerminalPorts() {
|
|
115
|
+
return {
|
|
116
|
+
readFile(path) {
|
|
117
|
+
if (!fs.existsSync(path)) {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
return fs.readFileSync(path, 'utf8');
|
|
121
|
+
},
|
|
122
|
+
writeFile(path, content) {
|
|
123
|
+
fs.writeFileSync(path, content, 'utf8');
|
|
124
|
+
},
|
|
125
|
+
async confirmWrite() {
|
|
126
|
+
const readline = createInterface({
|
|
127
|
+
input: process.stdin,
|
|
128
|
+
output: process.stdout,
|
|
129
|
+
});
|
|
130
|
+
try {
|
|
131
|
+
const answer = await readline.question('\nWrite CHANGELOG.md with these changes? [y/N] ');
|
|
132
|
+
return /^(y|yes)$/i.test(answer.trim());
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
readline.close();
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
showPreview(preview) {
|
|
139
|
+
process.stdout.write(preview);
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
async function main() {
|
|
144
|
+
try {
|
|
145
|
+
process.exitCode = await runChangelogCli();
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
149
|
+
process.stderr.write(`\nChangelog generation failed: ${message}\n`);
|
|
150
|
+
process.exitCode = 1;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
154
|
+
await main();
|
|
155
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createInterface } from 'node:readline/promises';
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { previewAndConfirmMaintainerRelease } from '../modules/maintainer-release-support/release-workflow/handler.js';
|
|
7
|
+
const USAGE = `Usage: pnpm admin:release -- [--from <ref>] [--to <ref>] [--version <version>] [--date <yyyy-mm-dd>] [--otp <code>] [--yes] [--dry-run]
|
|
8
|
+
|
|
9
|
+
Options:
|
|
10
|
+
--from <ref> Git ref to start from. Defaults to the latest reachable SemVer-like tag.
|
|
11
|
+
--to <ref> Git ref to end at. Defaults to HEAD.
|
|
12
|
+
--version <version> Release version override. Accepts MAJOR.MINOR.PATCH with optional leading v.
|
|
13
|
+
--date <yyyy-mm-dd> Release date for the versioned changelog section.
|
|
14
|
+
--otp <code> npm one-time password for accounts that require 2FA for publishing.
|
|
15
|
+
--yes Print the preview and continue without prompting.
|
|
16
|
+
--dry-run Print the release plan and perform no writes or side effects.
|
|
17
|
+
--help Show this help message.
|
|
18
|
+
`;
|
|
19
|
+
export function parseReleaseArgs(args) {
|
|
20
|
+
const command = {};
|
|
21
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
22
|
+
const arg = args[index];
|
|
23
|
+
switch (arg) {
|
|
24
|
+
case '--':
|
|
25
|
+
break;
|
|
26
|
+
case '--help':
|
|
27
|
+
case '-h':
|
|
28
|
+
return { status: 'help', usage: USAGE };
|
|
29
|
+
case '--yes':
|
|
30
|
+
command.assumeYes = true;
|
|
31
|
+
break;
|
|
32
|
+
case '--dry-run':
|
|
33
|
+
command.dryRun = true;
|
|
34
|
+
break;
|
|
35
|
+
case '--from': {
|
|
36
|
+
const value = readFlagValue(args, index, arg);
|
|
37
|
+
if (value.status === 'error') {
|
|
38
|
+
return value.result;
|
|
39
|
+
}
|
|
40
|
+
command.fromRef = value.value;
|
|
41
|
+
index += 1;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
case '--to': {
|
|
45
|
+
const value = readFlagValue(args, index, arg);
|
|
46
|
+
if (value.status === 'error') {
|
|
47
|
+
return value.result;
|
|
48
|
+
}
|
|
49
|
+
command.toRef = value.value;
|
|
50
|
+
index += 1;
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
case '--version': {
|
|
54
|
+
const value = readFlagValue(args, index, arg);
|
|
55
|
+
if (value.status === 'error') {
|
|
56
|
+
return value.result;
|
|
57
|
+
}
|
|
58
|
+
command.version = value.value;
|
|
59
|
+
index += 1;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case '--date': {
|
|
63
|
+
const value = readFlagValue(args, index, arg);
|
|
64
|
+
if (value.status === 'error') {
|
|
65
|
+
return value.result;
|
|
66
|
+
}
|
|
67
|
+
command.date = value.value;
|
|
68
|
+
index += 1;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
case '--otp': {
|
|
72
|
+
const value = readFlagValue(args, index, arg);
|
|
73
|
+
if (value.status === 'error') {
|
|
74
|
+
return value.result;
|
|
75
|
+
}
|
|
76
|
+
command.otp = value.value;
|
|
77
|
+
index += 1;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
default:
|
|
81
|
+
if (arg?.startsWith('--')) {
|
|
82
|
+
return error(`Unknown option \`${arg}\`.`);
|
|
83
|
+
}
|
|
84
|
+
return error(`Unexpected argument \`${arg ?? ''}\`.`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { status: 'ok', command };
|
|
88
|
+
}
|
|
89
|
+
export async function runReleaseCli(args = process.argv.slice(2), ports = createTerminalPorts(), cwd = process.cwd()) {
|
|
90
|
+
const parsedArgs = parseReleaseArgs(args);
|
|
91
|
+
if (parsedArgs.status === 'help') {
|
|
92
|
+
ports.print(parsedArgs.usage);
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
if (parsedArgs.status === 'error') {
|
|
96
|
+
process.stderr.write(`${parsedArgs.message}\n\n${parsedArgs.usage}`);
|
|
97
|
+
return 1;
|
|
98
|
+
}
|
|
99
|
+
const result = await previewAndConfirmMaintainerRelease(parsedArgs.command, ports, cwd);
|
|
100
|
+
switch (result.status) {
|
|
101
|
+
case 'confirmed':
|
|
102
|
+
printExecutionResult(result.execution, ports);
|
|
103
|
+
return result.execution.status === 'executed' ? 0 : 1;
|
|
104
|
+
case 'dry-run':
|
|
105
|
+
ports.print('\nDry run complete. No changes written.\n');
|
|
106
|
+
return 0;
|
|
107
|
+
case 'declined':
|
|
108
|
+
ports.print('\nRelease declined. No changes written.\n');
|
|
109
|
+
return 0;
|
|
110
|
+
case 'blocked':
|
|
111
|
+
process.stderr.write(`\nRelease planning blocked: ${result.message}\n`);
|
|
112
|
+
return 1;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function readFlagValue(args, index, flag) {
|
|
116
|
+
const value = args[index + 1];
|
|
117
|
+
if (!value || value.startsWith('--')) {
|
|
118
|
+
return {
|
|
119
|
+
status: 'error',
|
|
120
|
+
result: error(`Option \`${flag}\` requires a value.`),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return { status: 'ok', value };
|
|
124
|
+
}
|
|
125
|
+
function error(message) {
|
|
126
|
+
return {
|
|
127
|
+
status: 'error',
|
|
128
|
+
message,
|
|
129
|
+
usage: USAGE,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function createTerminalPorts() {
|
|
133
|
+
return {
|
|
134
|
+
print(message) {
|
|
135
|
+
process.stdout.write(message);
|
|
136
|
+
},
|
|
137
|
+
async confirmRelease(plan) {
|
|
138
|
+
const readline = createInterface({
|
|
139
|
+
input: process.stdin,
|
|
140
|
+
output: process.stdout,
|
|
141
|
+
});
|
|
142
|
+
try {
|
|
143
|
+
const answer = await readline.question(`\nRelease ${plan.targetVersion}? [y/N] `);
|
|
144
|
+
return /^(y|yes)$/i.test(answer.trim());
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
readline.close();
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
writeFile(path, contents) {
|
|
151
|
+
fs.writeFileSync(path, contents, 'utf8');
|
|
152
|
+
},
|
|
153
|
+
run(command, args) {
|
|
154
|
+
try {
|
|
155
|
+
const stdout = execFileSync(command, args, {
|
|
156
|
+
encoding: 'utf8',
|
|
157
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
158
|
+
});
|
|
159
|
+
return {
|
|
160
|
+
exitCode: 0,
|
|
161
|
+
stdout,
|
|
162
|
+
stderr: '',
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
return commandFailure(error);
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function printExecutionResult(result, ports) {
|
|
172
|
+
ports.print('\nRelease execution results:\n');
|
|
173
|
+
for (const completedStep of result.completedSteps) {
|
|
174
|
+
ports.print(`- ${completedStep.message}\n`);
|
|
175
|
+
}
|
|
176
|
+
if (result.status === 'failed') {
|
|
177
|
+
ports.print(`\nRelease failed at ${result.failedStep.name}: ${result.failedStep.message}\n`);
|
|
178
|
+
ports.print(`Recovery: ${result.failedStep.recoveryGuidance}\n`);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
ports.print('\nRelease completed successfully.\n');
|
|
182
|
+
}
|
|
183
|
+
function commandFailure(error) {
|
|
184
|
+
if (isExecFileError(error)) {
|
|
185
|
+
return {
|
|
186
|
+
exitCode: typeof error.status === 'number' ? error.status : 1,
|
|
187
|
+
stdout: bufferToString(error.stdout),
|
|
188
|
+
stderr: bufferToString(error.stderr),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
exitCode: 1,
|
|
193
|
+
stderr: error instanceof Error ? error.message : String(error),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function isExecFileError(error) {
|
|
197
|
+
return typeof error === 'object' && error !== null;
|
|
198
|
+
}
|
|
199
|
+
function bufferToString(value) {
|
|
200
|
+
if (Buffer.isBuffer(value)) {
|
|
201
|
+
return value.toString('utf8');
|
|
202
|
+
}
|
|
203
|
+
return value ?? '';
|
|
204
|
+
}
|
|
205
|
+
async function main() {
|
|
206
|
+
try {
|
|
207
|
+
process.exitCode = await runReleaseCli();
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
211
|
+
process.stderr.write(`\nRelease workflow failed: ${message}\n`);
|
|
212
|
+
process.exitCode = 1;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
216
|
+
await main();
|
|
217
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command as CommanderCommand } from 'commander';
|
|
2
|
-
import { SIBU_VERSION } from '../../
|
|
2
|
+
import { SIBU_VERSION } from '../../modules/version-advisory/index.js';
|
|
3
3
|
import { executeCliCommand } from './execute-command.js';
|
|
4
4
|
export function createProgram() {
|
|
5
5
|
const cli = new CommanderCommand();
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { handleDoctorProject } from '../../
|
|
2
|
-
import { handleInitProject } from '../../
|
|
3
|
-
import { handleListSkills } from '../../
|
|
4
|
-
import { handleStopManagingFile } from '../../
|
|
5
|
-
import { handleSyncProject } from '../../
|
|
6
|
-
import { handleUseSkill } from '../../
|
|
1
|
+
import { handleDoctorProject } from '../../modules/workflow-health-diagnosis/index.js';
|
|
2
|
+
import { handleInitProject } from '../../modules/project-adoption/index.js';
|
|
3
|
+
import { handleListSkills } from '../../modules/skill-selection-management/index.js';
|
|
4
|
+
import { handleStopManagingFile } from '../../modules/skill-selection-management/index.js';
|
|
5
|
+
import { handleSyncProject } from '../../modules/sync-review/index.js';
|
|
6
|
+
import { handleUseSkill } from '../../modules/skill-selection-management/index.js';
|
|
7
7
|
export async function executeCliCommand(command) {
|
|
8
8
|
switch (command.type) {
|
|
9
9
|
case 'init':
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { askForArchitectureSkill, 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 '
|
|
6
|
+
import { SELECTABLE_ARCHITECTURE_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 ⧖'));
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
export const CHANGELOG_CATEGORIES = ['Added', 'Changed', 'Deprecated', 'Removed', 'Fixed', 'Security'];
|
|
2
|
+
export function createEmptyEntriesByCategory() {
|
|
3
|
+
return {
|
|
4
|
+
Added: [],
|
|
5
|
+
Changed: [],
|
|
6
|
+
Deprecated: [],
|
|
7
|
+
Removed: [],
|
|
8
|
+
Fixed: [],
|
|
9
|
+
Security: [],
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function buildChangelogProposal(input) {
|
|
13
|
+
const entriesByCategory = createEmptyEntriesByCategory();
|
|
14
|
+
const warnings = [...(input.warnings ?? [])];
|
|
15
|
+
for (const commit of input.commits) {
|
|
16
|
+
const result = classifyCommit(commit);
|
|
17
|
+
entriesByCategory[result.entry.category].push(result.entry);
|
|
18
|
+
warnings.push(...result.warnings);
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
sourceRange: input.sourceRange,
|
|
22
|
+
targetSection: input.targetSection ?? { type: 'unreleased' },
|
|
23
|
+
semverGuidance: {
|
|
24
|
+
suggestedBump: suggestSemverBump(entriesByCategory),
|
|
25
|
+
},
|
|
26
|
+
commitCount: input.commits.length,
|
|
27
|
+
entriesByCategory,
|
|
28
|
+
warnings,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function retargetChangelogProposal(proposal, targetSection) {
|
|
32
|
+
return {
|
|
33
|
+
...proposal,
|
|
34
|
+
targetSection,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function suggestSemverBump(entriesByCategory) {
|
|
38
|
+
const entries = Object.values(entriesByCategory).flat();
|
|
39
|
+
if (entries.some((entry) => entry.breakingChange)) {
|
|
40
|
+
return 'major';
|
|
41
|
+
}
|
|
42
|
+
if (entriesByCategory.Added.some((entry) => entry.source === 'conventional-commit')) {
|
|
43
|
+
return 'minor';
|
|
44
|
+
}
|
|
45
|
+
return 'patch';
|
|
46
|
+
}
|
|
47
|
+
export function renderChangelogSection(proposal) {
|
|
48
|
+
const lines = [renderTargetSectionHeading(proposal.targetSection), ...renderEntriesByCategory(proposal.entriesByCategory)];
|
|
49
|
+
return `${lines.join('\n')}\n`;
|
|
50
|
+
}
|
|
51
|
+
export function renderChangelogPreview(proposal, targetPath) {
|
|
52
|
+
const lines = [
|
|
53
|
+
'Changelog preview',
|
|
54
|
+
'',
|
|
55
|
+
`Git range: ${formatSourceRange(proposal.sourceRange)}`,
|
|
56
|
+
`Commits inspected: ${proposal.commitCount}`,
|
|
57
|
+
`Target path: ${targetPath}`,
|
|
58
|
+
`Target section: ${formatTargetSection(proposal.targetSection)}`,
|
|
59
|
+
`Suggested SemVer bump: ${proposal.semverGuidance.suggestedBump}`,
|
|
60
|
+
'',
|
|
61
|
+
'Entries:',
|
|
62
|
+
...renderPreviewEntries(proposal.entriesByCategory),
|
|
63
|
+
];
|
|
64
|
+
if (proposal.warnings.length > 0) {
|
|
65
|
+
lines.push('', 'Warnings:', ...proposal.warnings.map(formatWarning));
|
|
66
|
+
}
|
|
67
|
+
return `${lines.join('\n')}\n`;
|
|
68
|
+
}
|
|
69
|
+
function renderTargetSectionHeading(targetSection) {
|
|
70
|
+
if (targetSection.type === 'unreleased') {
|
|
71
|
+
return '## Unreleased';
|
|
72
|
+
}
|
|
73
|
+
return `## ${targetSection.version} - ${targetSection.date}`;
|
|
74
|
+
}
|
|
75
|
+
function renderEntriesByCategory(entriesByCategory) {
|
|
76
|
+
return CHANGELOG_CATEGORIES.flatMap((category) => {
|
|
77
|
+
const entries = entriesByCategory[category];
|
|
78
|
+
if (entries.length === 0) {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
return ['', `### ${category}`, ...entries.map((entry) => `- ${entry.text}`)];
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function renderPreviewEntries(entriesByCategory) {
|
|
85
|
+
const lines = renderEntriesByCategory(entriesByCategory);
|
|
86
|
+
if (lines.length === 0) {
|
|
87
|
+
return ['- No generated entries.'];
|
|
88
|
+
}
|
|
89
|
+
return lines;
|
|
90
|
+
}
|
|
91
|
+
function formatSourceRange(sourceRange) {
|
|
92
|
+
if (sourceRange.fromRef) {
|
|
93
|
+
return `${sourceRange.fromRef}..${sourceRange.toRef}`;
|
|
94
|
+
}
|
|
95
|
+
return sourceRange.toRef;
|
|
96
|
+
}
|
|
97
|
+
function formatTargetSection(targetSection) {
|
|
98
|
+
if (targetSection.type === 'unreleased') {
|
|
99
|
+
return 'Unreleased';
|
|
100
|
+
}
|
|
101
|
+
return `${targetSection.version} - ${targetSection.date}`;
|
|
102
|
+
}
|
|
103
|
+
function formatWarning(warning) {
|
|
104
|
+
const commitSuffix = warning.commitHash ? ` (${warning.commitHash})` : '';
|
|
105
|
+
return `- [${warning.code}] ${warning.message}${commitSuffix}`;
|
|
106
|
+
}
|
|
107
|
+
export function classifyCommit(commit) {
|
|
108
|
+
const conventionalCommit = parseConventionalCommit(commit.subject);
|
|
109
|
+
if (!conventionalCommit) {
|
|
110
|
+
const entry = buildEntry({
|
|
111
|
+
category: classifyFromText(commit.subject),
|
|
112
|
+
text: commit.subject.trim(),
|
|
113
|
+
commit,
|
|
114
|
+
source: 'commit-message',
|
|
115
|
+
breakingChange: hasBreakingChangeMarker(commit),
|
|
116
|
+
reviewNeeded: true,
|
|
117
|
+
});
|
|
118
|
+
return {
|
|
119
|
+
entry,
|
|
120
|
+
warnings: buildReviewWarnings(entry, 'Commit message is not a Conventional Commit. Review the generated changelog entry.'),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const breakingChange = conventionalCommit.breakingChange || hasBreakingChangeMarker(commit);
|
|
124
|
+
const entry = buildEntry({
|
|
125
|
+
category: classifyConventionalCommit(conventionalCommit.type, conventionalCommit.description),
|
|
126
|
+
text: conventionalCommit.description,
|
|
127
|
+
commit,
|
|
128
|
+
source: 'conventional-commit',
|
|
129
|
+
breakingChange,
|
|
130
|
+
reviewNeeded: breakingChange,
|
|
131
|
+
});
|
|
132
|
+
return {
|
|
133
|
+
entry,
|
|
134
|
+
warnings: breakingChange ? buildReviewWarnings(entry, 'Breaking change detected. Review the generated changelog entry.') : [],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function parseConventionalCommit(subject) {
|
|
138
|
+
const match = /^(?<type>[a-z]+)(?:\([^)]+\))?(?<breaking>!)?:\s+(?<description>.+)$/.exec(subject.trim());
|
|
139
|
+
if (!match?.groups) {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
type: match.groups.type,
|
|
144
|
+
description: match.groups.description.trim(),
|
|
145
|
+
breakingChange: match.groups.breaking === '!',
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function classifyConventionalCommit(type, description) {
|
|
149
|
+
const textCategory = classifyFromText(`${type} ${description}`);
|
|
150
|
+
if (textCategory !== 'Changed') {
|
|
151
|
+
return textCategory;
|
|
152
|
+
}
|
|
153
|
+
switch (type) {
|
|
154
|
+
case 'feat':
|
|
155
|
+
return 'Added';
|
|
156
|
+
case 'fix':
|
|
157
|
+
return 'Fixed';
|
|
158
|
+
case 'perf':
|
|
159
|
+
case 'refactor':
|
|
160
|
+
case 'style':
|
|
161
|
+
case 'docs':
|
|
162
|
+
case 'test':
|
|
163
|
+
case 'build':
|
|
164
|
+
case 'ci':
|
|
165
|
+
case 'chore':
|
|
166
|
+
return 'Changed';
|
|
167
|
+
default:
|
|
168
|
+
return 'Changed';
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function classifyFromText(text) {
|
|
172
|
+
const normalizedText = text.toLowerCase();
|
|
173
|
+
if (/\b(security|secure|vulnerability|cve|exploit)\b/.test(normalizedText)) {
|
|
174
|
+
return 'Security';
|
|
175
|
+
}
|
|
176
|
+
if (/\b(deprecat(?:e|ed|es|ing|ion))\b/.test(normalizedText)) {
|
|
177
|
+
return 'Deprecated';
|
|
178
|
+
}
|
|
179
|
+
if (/\b(remove|removed|removes|removing|delete|deleted|deletes|deleting)\b/.test(normalizedText)) {
|
|
180
|
+
return 'Removed';
|
|
181
|
+
}
|
|
182
|
+
return 'Changed';
|
|
183
|
+
}
|
|
184
|
+
function hasBreakingChangeMarker(commit) {
|
|
185
|
+
return /^BREAKING[ -]CHANGE:/im.test(commit.body);
|
|
186
|
+
}
|
|
187
|
+
function buildEntry(input) {
|
|
188
|
+
return {
|
|
189
|
+
category: input.category,
|
|
190
|
+
text: input.text,
|
|
191
|
+
commitHash: input.commit.hash,
|
|
192
|
+
reviewNeeded: input.reviewNeeded,
|
|
193
|
+
breakingChange: input.breakingChange,
|
|
194
|
+
source: input.source,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function buildReviewWarnings(entry, message) {
|
|
198
|
+
const warnings = [
|
|
199
|
+
{
|
|
200
|
+
code: 'review-needed',
|
|
201
|
+
message,
|
|
202
|
+
commitHash: entry.commitHash,
|
|
203
|
+
},
|
|
204
|
+
];
|
|
205
|
+
if (entry.breakingChange) {
|
|
206
|
+
warnings.push({
|
|
207
|
+
code: 'breaking-change',
|
|
208
|
+
message: 'Breaking change detected. Confirm release notes and SemVer impact before publishing.',
|
|
209
|
+
commitHash: entry.commitHash,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
return warnings;
|
|
213
|
+
}
|