workflow-agent-cli 1.1.2
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/dist/chunk-VFN3BY56.js +120 -0
- package/dist/chunk-VFN3BY56.js.map +1 -0
- package/dist/chunk-X2NQJ2ZY.js +170 -0
- package/dist/chunk-X2NQJ2ZY.js.map +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1206 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config/index.d.ts +8 -0
- package/dist/config/index.js +11 -0
- package/dist/config/index.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/schema-CiJ4W7in.d.ts +97 -0
- package/dist/scripts/postinstall.d.ts +1 -0
- package/dist/scripts/postinstall.js +73 -0
- package/dist/scripts/postinstall.js.map +1 -0
- package/dist/validators/index.d.ts +16 -0
- package/dist/validators/index.js +17 -0
- package/dist/validators/index.js.map +1 -0
- package/package.json +80 -0
- package/templates/AGENT_EDITING_INSTRUCTIONS.md +887 -0
- package/templates/BRANCHING_STRATEGY.md +442 -0
- package/templates/COMPONENT_LIBRARY.md +611 -0
- package/templates/CUSTOM_SCOPE_TEMPLATE.md +228 -0
- package/templates/DEPLOYMENT_STRATEGY.md +509 -0
- package/templates/Guidelines.md +62 -0
- package/templates/LIBRARY_INVENTORY.md +615 -0
- package/templates/PROJECT_TEMPLATE_README.md +347 -0
- package/templates/SCOPE_CREATION_WORKFLOW.md +286 -0
- package/templates/SELF_IMPROVEMENT_MANDATE.md +298 -0
- package/templates/SINGLE_SOURCE_OF_TRUTH.md +492 -0
- package/templates/TESTING_STRATEGY.md +801 -0
- package/templates/_TEMPLATE_EXAMPLE.md +28 -0
|
@@ -0,0 +1,1206 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
validateBranchName,
|
|
4
|
+
validateCommitMessage,
|
|
5
|
+
validatePRTitle
|
|
6
|
+
} from "../chunk-X2NQJ2ZY.js";
|
|
7
|
+
import {
|
|
8
|
+
hasConfig,
|
|
9
|
+
loadConfig,
|
|
10
|
+
validateScopeDefinitions
|
|
11
|
+
} from "../chunk-VFN3BY56.js";
|
|
12
|
+
|
|
13
|
+
// src/cli/index.ts
|
|
14
|
+
import { Command } from "commander";
|
|
15
|
+
|
|
16
|
+
// src/cli/commands/init.ts
|
|
17
|
+
import * as p from "@clack/prompts";
|
|
18
|
+
import chalk from "chalk";
|
|
19
|
+
import { existsSync } from "fs";
|
|
20
|
+
import { writeFile, mkdir } from "fs/promises";
|
|
21
|
+
import { join, dirname } from "path";
|
|
22
|
+
import { fileURLToPath } from "url";
|
|
23
|
+
|
|
24
|
+
// src/templates/renderer.ts
|
|
25
|
+
import fs from "fs/promises";
|
|
26
|
+
import path from "path";
|
|
27
|
+
|
|
28
|
+
// src/adapters/index.ts
|
|
29
|
+
var adapters = {
|
|
30
|
+
"nextjs-app-router": {
|
|
31
|
+
name: "Next.js App Router",
|
|
32
|
+
description: "Next.js 13+ with app directory",
|
|
33
|
+
detect: async () => {
|
|
34
|
+
const fs2 = await import("fs");
|
|
35
|
+
return fs2.existsSync("app") && fs2.existsSync("next.config.ts") || fs2.existsSync("next.config.js");
|
|
36
|
+
},
|
|
37
|
+
paths: {
|
|
38
|
+
actions: "app/actions",
|
|
39
|
+
components: "components",
|
|
40
|
+
lib: "lib",
|
|
41
|
+
hooks: "hooks",
|
|
42
|
+
types: "types",
|
|
43
|
+
tests: "__tests__",
|
|
44
|
+
config: "app"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"nextjs-pages": {
|
|
48
|
+
name: "Next.js Pages Router",
|
|
49
|
+
description: "Next.js with pages directory",
|
|
50
|
+
detect: async () => {
|
|
51
|
+
const fs2 = await import("fs");
|
|
52
|
+
return fs2.existsSync("pages") && (fs2.existsSync("next.config.ts") || fs2.existsSync("next.config.js"));
|
|
53
|
+
},
|
|
54
|
+
paths: {
|
|
55
|
+
components: "components",
|
|
56
|
+
lib: "lib",
|
|
57
|
+
hooks: "hooks",
|
|
58
|
+
types: "types",
|
|
59
|
+
tests: "__tests__",
|
|
60
|
+
config: "pages"
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"vite-react": {
|
|
64
|
+
name: "Vite + React",
|
|
65
|
+
description: "Vite-powered React application",
|
|
66
|
+
detect: async () => {
|
|
67
|
+
const fs2 = await import("fs");
|
|
68
|
+
return fs2.existsSync("vite.config.ts") || fs2.existsSync("vite.config.js");
|
|
69
|
+
},
|
|
70
|
+
paths: {
|
|
71
|
+
components: "src/components",
|
|
72
|
+
lib: "src/lib",
|
|
73
|
+
hooks: "src/hooks",
|
|
74
|
+
types: "src/types",
|
|
75
|
+
tests: "src/__tests__",
|
|
76
|
+
config: "src"
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"remix": {
|
|
80
|
+
name: "Remix",
|
|
81
|
+
description: "Remix full-stack framework",
|
|
82
|
+
detect: async () => {
|
|
83
|
+
const fs2 = await import("fs");
|
|
84
|
+
return fs2.existsSync("app/routes") && (fs2.existsSync("remix.config.js") || fs2.existsSync("package.json"));
|
|
85
|
+
},
|
|
86
|
+
paths: {
|
|
87
|
+
components: "app/components",
|
|
88
|
+
lib: "app/lib",
|
|
89
|
+
types: "app/types",
|
|
90
|
+
tests: "app/__tests__",
|
|
91
|
+
config: "app/root.tsx"
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"astro": {
|
|
95
|
+
name: "Astro",
|
|
96
|
+
description: "Astro static site framework",
|
|
97
|
+
detect: async () => {
|
|
98
|
+
const fs2 = await import("fs");
|
|
99
|
+
return fs2.existsSync("astro.config.mjs") || fs2.existsSync("astro.config.ts");
|
|
100
|
+
},
|
|
101
|
+
paths: {
|
|
102
|
+
components: "src/components",
|
|
103
|
+
lib: "src/lib",
|
|
104
|
+
types: "src/types",
|
|
105
|
+
tests: "src/__tests__",
|
|
106
|
+
config: "src/pages"
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"sveltekit": {
|
|
110
|
+
name: "SvelteKit",
|
|
111
|
+
description: "SvelteKit full-stack framework",
|
|
112
|
+
detect: async () => {
|
|
113
|
+
const fs2 = await import("fs");
|
|
114
|
+
return fs2.existsSync("svelte.config.js") || fs2.existsSync("src/routes");
|
|
115
|
+
},
|
|
116
|
+
paths: {
|
|
117
|
+
components: "src/lib/components",
|
|
118
|
+
lib: "src/lib",
|
|
119
|
+
types: "src/lib/types",
|
|
120
|
+
tests: "src/lib/__tests__",
|
|
121
|
+
config: "src/routes"
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
"generic": {
|
|
125
|
+
name: "Generic Project",
|
|
126
|
+
description: "Standard project structure",
|
|
127
|
+
detect: async () => true,
|
|
128
|
+
// Always matches as fallback
|
|
129
|
+
paths: {
|
|
130
|
+
components: "src/components",
|
|
131
|
+
lib: "src/lib",
|
|
132
|
+
types: "src/types",
|
|
133
|
+
tests: "tests",
|
|
134
|
+
config: "src"
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
async function detectAdapter() {
|
|
139
|
+
for (const [key, adapter] of Object.entries(adapters)) {
|
|
140
|
+
if (key === "generic") continue;
|
|
141
|
+
try {
|
|
142
|
+
if (await adapter.detect()) {
|
|
143
|
+
return key;
|
|
144
|
+
}
|
|
145
|
+
} catch {
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return "generic";
|
|
149
|
+
}
|
|
150
|
+
function getAdapter(name) {
|
|
151
|
+
return adapters[name] || null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/templates/renderer.ts
|
|
155
|
+
function renderTemplate(template, context) {
|
|
156
|
+
return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
157
|
+
return context[key] ?? match;
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
async function buildTemplateContext(config, projectPath = process.cwd()) {
|
|
161
|
+
const detectedFramework = await detectAdapter();
|
|
162
|
+
const adapter = getAdapter(detectedFramework);
|
|
163
|
+
const scopeList = config.scopes.map((s) => `- **${s.name}** - ${s.description}`).join("\n");
|
|
164
|
+
const scopes = config.scopes.map((s) => s.name).join(", ");
|
|
165
|
+
const pathStructure = adapter ? `
|
|
166
|
+
### Path Structure
|
|
167
|
+
|
|
168
|
+
\`\`\`
|
|
169
|
+
${adapter.paths.components}/ \u2192 UI components
|
|
170
|
+
${adapter.paths.lib}/ \u2192 Utility functions and services
|
|
171
|
+
${adapter.paths.hooks}/ \u2192 Custom React hooks
|
|
172
|
+
${adapter.paths.types}/ \u2192 TypeScript type definitions
|
|
173
|
+
\`\`\`
|
|
174
|
+
`.trim() : "N/A";
|
|
175
|
+
const projectName = await getProjectName(projectPath);
|
|
176
|
+
return {
|
|
177
|
+
projectName,
|
|
178
|
+
framework: adapter?.name || "unknown",
|
|
179
|
+
scopes,
|
|
180
|
+
scopeList,
|
|
181
|
+
pathStructure,
|
|
182
|
+
enforcement: config.enforcement,
|
|
183
|
+
year: (/* @__PURE__ */ new Date()).getFullYear().toString()
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
async function renderTemplateFile(templatePath, outputPath, context) {
|
|
187
|
+
const template = await fs.readFile(templatePath, "utf-8");
|
|
188
|
+
const rendered = renderTemplate(template, context);
|
|
189
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
190
|
+
await fs.writeFile(outputPath, rendered, "utf-8");
|
|
191
|
+
}
|
|
192
|
+
async function renderTemplateDirectory(templateDir, outputDir, context) {
|
|
193
|
+
const files = await fs.readdir(templateDir);
|
|
194
|
+
const rendered = [];
|
|
195
|
+
for (const file of files) {
|
|
196
|
+
if (!file.match(/\.(md|ts|json)$/)) continue;
|
|
197
|
+
const templatePath = path.join(templateDir, file);
|
|
198
|
+
const outputPath = path.join(outputDir, file);
|
|
199
|
+
await renderTemplateFile(templatePath, outputPath, context);
|
|
200
|
+
rendered.push(file);
|
|
201
|
+
}
|
|
202
|
+
return rendered;
|
|
203
|
+
}
|
|
204
|
+
async function getProjectName(projectPath) {
|
|
205
|
+
try {
|
|
206
|
+
const pkgPath = path.join(projectPath, "package.json");
|
|
207
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, "utf-8"));
|
|
208
|
+
return pkg.name || path.basename(projectPath);
|
|
209
|
+
} catch {
|
|
210
|
+
return path.basename(projectPath);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async function validateTemplateDirectory(templateDir) {
|
|
214
|
+
try {
|
|
215
|
+
const stat = await fs.stat(templateDir);
|
|
216
|
+
if (!stat.isDirectory()) {
|
|
217
|
+
throw new Error(`Template path is not a directory: ${templateDir}`);
|
|
218
|
+
}
|
|
219
|
+
const files = await fs.readdir(templateDir);
|
|
220
|
+
const templateFiles = files.filter((f) => f.match(/\.(md|ts|json)$/));
|
|
221
|
+
if (templateFiles.length === 0) {
|
|
222
|
+
throw new Error(`No template files found in template directory: ${templateDir}`);
|
|
223
|
+
}
|
|
224
|
+
} catch (error) {
|
|
225
|
+
if (error.code === "ENOENT") {
|
|
226
|
+
throw new Error(`Template directory not found: ${templateDir}`);
|
|
227
|
+
}
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/cli/commands/init.ts
|
|
233
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
234
|
+
var __dirname = dirname(__filename);
|
|
235
|
+
async function initCommand(options) {
|
|
236
|
+
console.log(chalk.bold.cyan("\n\u{1F680} Workflow Agent Initialization\n"));
|
|
237
|
+
const cwd = process.cwd();
|
|
238
|
+
const isNonInteractive = !!(options.preset && options.name);
|
|
239
|
+
if (hasConfig(cwd) && !options.yes && !isNonInteractive) {
|
|
240
|
+
const shouldContinue = await p.confirm({
|
|
241
|
+
message: "Workflow Agent is already configured. Continue and overwrite?",
|
|
242
|
+
initialValue: false
|
|
243
|
+
});
|
|
244
|
+
if (p.isCancel(shouldContinue) || !shouldContinue) {
|
|
245
|
+
p.cancel("Initialization cancelled");
|
|
246
|
+
process.exit(0);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const projectName = isNonInteractive ? options.name : await p.text({
|
|
250
|
+
message: "What is your project name?",
|
|
251
|
+
placeholder: "my-awesome-project",
|
|
252
|
+
defaultValue: process.cwd().split("/").pop() || "my-project"
|
|
253
|
+
});
|
|
254
|
+
if (!isNonInteractive && p.isCancel(projectName)) {
|
|
255
|
+
p.cancel("Initialization cancelled");
|
|
256
|
+
process.exit(0);
|
|
257
|
+
}
|
|
258
|
+
const preset = isNonInteractive ? options.preset : await p.select({
|
|
259
|
+
message: "Choose a scope preset for your project:",
|
|
260
|
+
options: [
|
|
261
|
+
{ value: "saas", label: "\u{1F4E6} SaaS Application - 17 scopes (auth, tasks, boards, sprints, etc.)" },
|
|
262
|
+
{ value: "library", label: "\u{1F4DA} Library/Package - 10 scopes (types, build, docs, examples, etc.)" },
|
|
263
|
+
{ value: "api", label: "\u{1F50C} API/Backend - 13 scopes (auth, endpoints, models, services, etc.)" },
|
|
264
|
+
{ value: "ecommerce", label: "\u{1F6D2} E-commerce - 12 scopes (cart, products, payments, orders, etc.)" },
|
|
265
|
+
{ value: "cms", label: "\u{1F4DD} CMS - 13 scopes (content, pages, media, editor, etc.)" },
|
|
266
|
+
{ value: "custom", label: "\u2728 Custom (define your own scopes manually)" }
|
|
267
|
+
]
|
|
268
|
+
});
|
|
269
|
+
if (!isNonInteractive && p.isCancel(preset)) {
|
|
270
|
+
p.cancel("Initialization cancelled");
|
|
271
|
+
process.exit(0);
|
|
272
|
+
}
|
|
273
|
+
let scopes = [];
|
|
274
|
+
if (preset !== "custom") {
|
|
275
|
+
try {
|
|
276
|
+
const presetModule = await import(`@workflow/scopes-${preset}`);
|
|
277
|
+
scopes = presetModule.scopes || presetModule.default.scopes;
|
|
278
|
+
const spinner4 = p.spinner();
|
|
279
|
+
spinner4.start(`Loading ${presetModule.default?.name || preset} preset`);
|
|
280
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
281
|
+
spinner4.stop(`\u2713 Loaded ${scopes.length} scopes from preset`);
|
|
282
|
+
} catch (error) {
|
|
283
|
+
console.log(chalk.yellow(`
|
|
284
|
+
\u26A0\uFE0F Could not load preset package. Using basic scopes.`));
|
|
285
|
+
scopes = [
|
|
286
|
+
{ name: "feat", description: "New features", emoji: "\u2728" },
|
|
287
|
+
{ name: "fix", description: "Bug fixes", emoji: "\u{1F41B}" },
|
|
288
|
+
{ name: "docs", description: "Documentation", emoji: "\u{1F4DA}" }
|
|
289
|
+
];
|
|
290
|
+
}
|
|
291
|
+
} else {
|
|
292
|
+
scopes = [
|
|
293
|
+
{ name: "feat", description: "New features", emoji: "\u2728" },
|
|
294
|
+
{ name: "fix", description: "Bug fixes", emoji: "\u{1F41B}" },
|
|
295
|
+
{ name: "docs", description: "Documentation", emoji: "\u{1F4DA}" }
|
|
296
|
+
];
|
|
297
|
+
console.log(chalk.dim("\n\u{1F4A1} Tip: Edit workflow.config.json to add your custom scopes"));
|
|
298
|
+
}
|
|
299
|
+
const config = {
|
|
300
|
+
projectName,
|
|
301
|
+
scopes,
|
|
302
|
+
enforcement: "strict",
|
|
303
|
+
language: "en"
|
|
304
|
+
};
|
|
305
|
+
const configPath = join(cwd, "workflow.config.json");
|
|
306
|
+
await writeFile(configPath, JSON.stringify(config, null, 2));
|
|
307
|
+
const workflowDir = join(cwd, ".workflow");
|
|
308
|
+
if (!existsSync(workflowDir)) {
|
|
309
|
+
await mkdir(workflowDir, { recursive: true });
|
|
310
|
+
}
|
|
311
|
+
const shouldGenerateGuidelines = await p.confirm({
|
|
312
|
+
message: "Generate workflow guidelines from templates?",
|
|
313
|
+
initialValue: true
|
|
314
|
+
});
|
|
315
|
+
if (p.isCancel(shouldGenerateGuidelines)) {
|
|
316
|
+
p.cancel("Initialization cancelled");
|
|
317
|
+
process.exit(0);
|
|
318
|
+
}
|
|
319
|
+
if (shouldGenerateGuidelines) {
|
|
320
|
+
const spinner4 = p.spinner();
|
|
321
|
+
spinner4.start("Generating guidelines...");
|
|
322
|
+
try {
|
|
323
|
+
const templatesDir = join(__dirname, "../../../templates");
|
|
324
|
+
await validateTemplateDirectory(templatesDir);
|
|
325
|
+
const context = await buildTemplateContext(config, cwd);
|
|
326
|
+
const guidelinesDir = join(cwd, "guidelines");
|
|
327
|
+
await mkdir(guidelinesDir, { recursive: true });
|
|
328
|
+
const renderedFiles = await renderTemplateDirectory(templatesDir, guidelinesDir, context);
|
|
329
|
+
spinner4.stop(`\u2713 Generated ${renderedFiles.length} guideline documents`);
|
|
330
|
+
} catch (error) {
|
|
331
|
+
spinner4.stop("\u26A0\uFE0F Could not generate guidelines");
|
|
332
|
+
console.log(chalk.yellow(`
|
|
333
|
+
Reason: ${error instanceof Error ? error.message : String(error)}`));
|
|
334
|
+
console.log(chalk.dim("You can manually copy guidelines later if needed."));
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
p.outro(chalk.green("\u2713 Workflow Agent initialized successfully!"));
|
|
338
|
+
console.log(chalk.dim("\nNext steps:"));
|
|
339
|
+
console.log(chalk.dim(" 1. Review your configuration in workflow.config.json"));
|
|
340
|
+
if (shouldGenerateGuidelines) {
|
|
341
|
+
console.log(chalk.dim(" 2. Review generated guidelines in guidelines/ directory"));
|
|
342
|
+
console.log(chalk.dim(" 3. Run: workflow validate branch"));
|
|
343
|
+
console.log(chalk.dim(" 4. Run: workflow doctor (for optimization suggestions)\n"));
|
|
344
|
+
} else {
|
|
345
|
+
console.log(chalk.dim(" 2. Run: workflow validate branch"));
|
|
346
|
+
console.log(chalk.dim(" 3. Run: workflow doctor (for optimization suggestions)\n"));
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// src/cli/commands/validate.ts
|
|
351
|
+
import chalk2 from "chalk";
|
|
352
|
+
import { execa } from "execa";
|
|
353
|
+
async function validateCommand(type, value, _options = {}) {
|
|
354
|
+
const config = await loadConfig();
|
|
355
|
+
if (!config) {
|
|
356
|
+
console.error(chalk2.red("\u2717 No workflow configuration found. Run: workflow init"));
|
|
357
|
+
process.exit(1);
|
|
358
|
+
}
|
|
359
|
+
let targetValue = value;
|
|
360
|
+
try {
|
|
361
|
+
let result;
|
|
362
|
+
switch (type) {
|
|
363
|
+
case "branch": {
|
|
364
|
+
if (!targetValue) {
|
|
365
|
+
const { stdout } = await execa("git", ["branch", "--show-current"]);
|
|
366
|
+
targetValue = stdout.trim();
|
|
367
|
+
}
|
|
368
|
+
result = await validateBranchName(targetValue, config);
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
case "commit": {
|
|
372
|
+
if (!targetValue) {
|
|
373
|
+
const { stdout } = await execa("git", ["log", "-1", "--pretty=%s"]);
|
|
374
|
+
targetValue = stdout.trim();
|
|
375
|
+
}
|
|
376
|
+
result = await validateCommitMessage(targetValue, config);
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
case "pr":
|
|
380
|
+
case "pr-title": {
|
|
381
|
+
if (!targetValue) {
|
|
382
|
+
console.error(chalk2.red("\u2717 PR title must be provided as argument"));
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
result = await validatePRTitle(targetValue, config);
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
default:
|
|
389
|
+
console.error(chalk2.red(`\u2717 Unknown validation type: ${type}`));
|
|
390
|
+
console.error(chalk2.dim("Valid types: branch, commit, pr"));
|
|
391
|
+
process.exit(1);
|
|
392
|
+
}
|
|
393
|
+
if (result.valid) {
|
|
394
|
+
console.log(chalk2.green(`\u2713 ${type} is valid: ${targetValue}`));
|
|
395
|
+
process.exit(0);
|
|
396
|
+
} else {
|
|
397
|
+
console.error(chalk2.red(`\u2717 Invalid ${type}: ${targetValue}`));
|
|
398
|
+
console.error(chalk2.yellow(` ${result.error}`));
|
|
399
|
+
if (result.suggestion) {
|
|
400
|
+
console.error(chalk2.cyan(` \u{1F4A1} ${result.suggestion}`));
|
|
401
|
+
}
|
|
402
|
+
const enforcementLevel = config.enforcement;
|
|
403
|
+
if (enforcementLevel === "strict") {
|
|
404
|
+
process.exit(1);
|
|
405
|
+
} else {
|
|
406
|
+
console.log(chalk2.yellow(`
|
|
407
|
+
\u26A0\uFE0F Advisory mode: validation failed but not blocking`));
|
|
408
|
+
process.exit(0);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
} catch (error) {
|
|
412
|
+
console.error(chalk2.red(`\u2717 Validation error: ${error}`));
|
|
413
|
+
process.exit(1);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// src/cli/commands/config.ts
|
|
418
|
+
import chalk3 from "chalk";
|
|
419
|
+
async function configCommand(action, key, value) {
|
|
420
|
+
console.log(chalk3.yellow("Config command not yet implemented"));
|
|
421
|
+
console.log({ action, key, value });
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// src/cli/commands/suggest.ts
|
|
425
|
+
import chalk4 from "chalk";
|
|
426
|
+
import * as p2 from "@clack/prompts";
|
|
427
|
+
import { createTracker } from "@hawkinside_out/workflow-improvement-tracker";
|
|
428
|
+
async function suggestCommand(feedback, options = {}) {
|
|
429
|
+
console.log(chalk4.cyan("\u{1F4A1} Submitting improvement suggestion...\n"));
|
|
430
|
+
const tracker = createTracker();
|
|
431
|
+
let category = options.category;
|
|
432
|
+
if (!category) {
|
|
433
|
+
const categoryChoice = await p2.select({
|
|
434
|
+
message: "What type of improvement is this?",
|
|
435
|
+
options: [
|
|
436
|
+
{ value: "feature", label: "\u2728 Feature Request" },
|
|
437
|
+
{ value: "bug", label: "\u{1F41B} Bug Report" },
|
|
438
|
+
{ value: "documentation", label: "\u{1F4DA} Documentation" },
|
|
439
|
+
{ value: "performance", label: "\u26A1 Performance" },
|
|
440
|
+
{ value: "other", label: "\u{1F4A1} Other" }
|
|
441
|
+
]
|
|
442
|
+
});
|
|
443
|
+
if (p2.isCancel(categoryChoice)) {
|
|
444
|
+
p2.cancel("Suggestion cancelled");
|
|
445
|
+
process.exit(0);
|
|
446
|
+
}
|
|
447
|
+
category = categoryChoice;
|
|
448
|
+
}
|
|
449
|
+
const result = await tracker.submit(feedback, options.author, category);
|
|
450
|
+
if (!result.success) {
|
|
451
|
+
console.log(chalk4.red("\u2717 Suggestion rejected"));
|
|
452
|
+
console.log(chalk4.dim(` Reason: ${result.error}`));
|
|
453
|
+
process.exit(1);
|
|
454
|
+
}
|
|
455
|
+
console.log(chalk4.green("\u2713 Suggestion submitted successfully!"));
|
|
456
|
+
console.log(chalk4.dim(` ID: ${result.suggestion?.id}`));
|
|
457
|
+
console.log(chalk4.dim(` Status: ${result.suggestion?.status}`));
|
|
458
|
+
console.log(chalk4.dim(` Category: ${result.suggestion?.category}`));
|
|
459
|
+
console.log(chalk4.dim("\nYour suggestion will be:"));
|
|
460
|
+
console.log(chalk4.dim(" 1. Reviewed by the community"));
|
|
461
|
+
console.log(chalk4.dim(" 2. Prioritized based on impact"));
|
|
462
|
+
console.log(chalk4.dim(" 3. Incorporated into future releases if approved\n"));
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// src/cli/commands/doctor.ts
|
|
466
|
+
import chalk5 from "chalk";
|
|
467
|
+
async function doctorCommand() {
|
|
468
|
+
console.log(chalk5.bold.cyan("\n\u{1F3E5} Workflow Agent Health Check\n"));
|
|
469
|
+
const config = await loadConfig();
|
|
470
|
+
if (!config) {
|
|
471
|
+
console.error(chalk5.red("\u2717 No workflow configuration found"));
|
|
472
|
+
console.log(chalk5.yellow(" Run: workflow init"));
|
|
473
|
+
process.exit(1);
|
|
474
|
+
}
|
|
475
|
+
console.log(chalk5.green("\u2713 Configuration loaded successfully"));
|
|
476
|
+
console.log(chalk5.dim(` Project: ${config.projectName}`));
|
|
477
|
+
console.log(chalk5.dim(` Scopes: ${config.scopes.length} configured`));
|
|
478
|
+
console.log(chalk5.dim(` Enforcement: ${config.enforcement}`));
|
|
479
|
+
console.log(chalk5.dim(` Language: ${config.language}`));
|
|
480
|
+
console.log(chalk5.cyan("\n\u{1F4A1} Suggestions:\n"));
|
|
481
|
+
console.log(chalk5.dim(" \u2022 Health check analysis coming soon"));
|
|
482
|
+
console.log(chalk5.dim(" \u2022 Git history analysis coming soon"));
|
|
483
|
+
console.log(chalk5.dim(" \u2022 Optimization recommendations coming soon"));
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// src/cli/commands/setup.ts
|
|
487
|
+
import * as p3 from "@clack/prompts";
|
|
488
|
+
import chalk6 from "chalk";
|
|
489
|
+
import { readFileSync, writeFileSync, existsSync as existsSync2 } from "fs";
|
|
490
|
+
import { join as join2 } from "path";
|
|
491
|
+
var WORKFLOW_SCRIPTS = {
|
|
492
|
+
"workflow:init": "workflow-agent init",
|
|
493
|
+
"workflow:validate": "workflow-agent validate",
|
|
494
|
+
"workflow:suggest": "workflow-agent suggest",
|
|
495
|
+
"workflow:doctor": "workflow-agent doctor"
|
|
496
|
+
};
|
|
497
|
+
async function setupCommand() {
|
|
498
|
+
p3.intro(chalk6.bgBlue(" workflow-agent setup "));
|
|
499
|
+
const cwd = process.cwd();
|
|
500
|
+
const packageJsonPath = join2(cwd, "package.json");
|
|
501
|
+
if (!existsSync2(packageJsonPath)) {
|
|
502
|
+
p3.cancel("No package.json found in current directory");
|
|
503
|
+
process.exit(1);
|
|
504
|
+
}
|
|
505
|
+
const packageJsonContent = readFileSync(packageJsonPath, "utf-8");
|
|
506
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
507
|
+
if (!packageJson.scripts) {
|
|
508
|
+
packageJson.scripts = {};
|
|
509
|
+
}
|
|
510
|
+
const existingScripts = [];
|
|
511
|
+
const scriptsToAdd = {};
|
|
512
|
+
for (const [scriptName, scriptCommand] of Object.entries(WORKFLOW_SCRIPTS)) {
|
|
513
|
+
if (packageJson.scripts[scriptName]) {
|
|
514
|
+
existingScripts.push(scriptName);
|
|
515
|
+
} else {
|
|
516
|
+
scriptsToAdd[scriptName] = scriptCommand;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if (Object.keys(scriptsToAdd).length === 0) {
|
|
520
|
+
p3.outro(chalk6.green("\u2713 All workflow scripts are already configured!"));
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
console.log(chalk6.dim("\nScripts to add:"));
|
|
524
|
+
for (const [scriptName, scriptCommand] of Object.entries(scriptsToAdd)) {
|
|
525
|
+
console.log(chalk6.dim(` ${scriptName}: ${scriptCommand}`));
|
|
526
|
+
}
|
|
527
|
+
if (existingScripts.length > 0) {
|
|
528
|
+
console.log(chalk6.yellow("\nExisting scripts (will be skipped):"));
|
|
529
|
+
existingScripts.forEach((name) => {
|
|
530
|
+
console.log(chalk6.yellow(` ${name}`));
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
const shouldAdd = await p3.confirm({
|
|
534
|
+
message: "Add these scripts to package.json?",
|
|
535
|
+
initialValue: true
|
|
536
|
+
});
|
|
537
|
+
if (p3.isCancel(shouldAdd) || !shouldAdd) {
|
|
538
|
+
p3.cancel("Setup cancelled");
|
|
539
|
+
process.exit(0);
|
|
540
|
+
}
|
|
541
|
+
for (const [scriptName, scriptCommand] of Object.entries(scriptsToAdd)) {
|
|
542
|
+
packageJson.scripts[scriptName] = scriptCommand;
|
|
543
|
+
}
|
|
544
|
+
writeFileSync(
|
|
545
|
+
packageJsonPath,
|
|
546
|
+
JSON.stringify(packageJson, null, 2) + "\n",
|
|
547
|
+
"utf-8"
|
|
548
|
+
);
|
|
549
|
+
p3.outro(chalk6.green(`\u2713 Added ${Object.keys(scriptsToAdd).length} workflow scripts to package.json!`));
|
|
550
|
+
console.log(chalk6.dim("\nRun them with:"));
|
|
551
|
+
console.log(chalk6.dim(" pnpm run workflow:init"));
|
|
552
|
+
console.log(chalk6.dim(" npm run workflow:init\n"));
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// src/cli/commands/scope-create.ts
|
|
556
|
+
import * as p4 from "@clack/prompts";
|
|
557
|
+
import chalk7 from "chalk";
|
|
558
|
+
import { existsSync as existsSync3 } from "fs";
|
|
559
|
+
import { writeFile as writeFile2, mkdir as mkdir2, readFile } from "fs/promises";
|
|
560
|
+
import { join as join3 } from "path";
|
|
561
|
+
async function scopeCreateCommand(options) {
|
|
562
|
+
console.log(chalk7.bold.cyan("\n\u{1F3A8} Create Custom Scope Package\n"));
|
|
563
|
+
const cwd = process.cwd();
|
|
564
|
+
const isNonInteractive = !!(options.name && options.scopes && options.presetName);
|
|
565
|
+
const isMonorepo = existsSync3(join3(cwd, "pnpm-workspace.yaml"));
|
|
566
|
+
if (isMonorepo) {
|
|
567
|
+
console.log(chalk7.dim("\u2713 Detected monorepo workspace\n"));
|
|
568
|
+
}
|
|
569
|
+
const packageNameInput = isNonInteractive ? options.name : await p4.text({
|
|
570
|
+
message: 'What is the package name? (e.g., "fintech", "gaming", "healthcare")',
|
|
571
|
+
placeholder: "my-custom-scope",
|
|
572
|
+
validate: (value) => {
|
|
573
|
+
if (!value || value.length === 0) return "Package name is required";
|
|
574
|
+
if (!/^[a-z0-9-]+$/.test(value)) return "Package name must be lowercase alphanumeric with hyphens";
|
|
575
|
+
if (value.length > 32) return "Package name must be 32 characters or less";
|
|
576
|
+
return void 0;
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
if (!isNonInteractive && p4.isCancel(packageNameInput)) {
|
|
580
|
+
p4.cancel("Operation cancelled");
|
|
581
|
+
process.exit(0);
|
|
582
|
+
}
|
|
583
|
+
const packageName = packageNameInput;
|
|
584
|
+
const presetNameInput = isNonInteractive ? options.presetName : await p4.text({
|
|
585
|
+
message: 'What is the preset display name? (e.g., "FinTech Application", "Gaming Platform")',
|
|
586
|
+
placeholder: "My Custom Preset",
|
|
587
|
+
validate: (value) => {
|
|
588
|
+
if (!value || value.length === 0) return "Preset name is required";
|
|
589
|
+
return void 0;
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
if (!isNonInteractive && p4.isCancel(presetNameInput)) {
|
|
593
|
+
p4.cancel("Operation cancelled");
|
|
594
|
+
process.exit(0);
|
|
595
|
+
}
|
|
596
|
+
const presetName = presetNameInput;
|
|
597
|
+
const scopes = [];
|
|
598
|
+
if (isNonInteractive && options.scopes) {
|
|
599
|
+
const scopeParts = options.scopes.split(",");
|
|
600
|
+
for (const part of scopeParts) {
|
|
601
|
+
const [name, description, emoji, category] = part.split(":");
|
|
602
|
+
scopes.push({
|
|
603
|
+
name: name.trim(),
|
|
604
|
+
description: description?.trim() || "Scope description",
|
|
605
|
+
emoji: emoji?.trim(),
|
|
606
|
+
category: category?.trim()
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
} else {
|
|
610
|
+
console.log(chalk7.dim("\nAdd scopes to your preset (aim for 8-15 scopes):\n"));
|
|
611
|
+
let addMore = true;
|
|
612
|
+
while (addMore) {
|
|
613
|
+
const scopeName = await p4.text({
|
|
614
|
+
message: `Scope #${scopes.length + 1} - Name:`,
|
|
615
|
+
placeholder: "auth",
|
|
616
|
+
validate: (value) => {
|
|
617
|
+
if (!value || value.length === 0) return "Scope name is required";
|
|
618
|
+
if (!/^[a-z0-9-]+$/.test(value)) return "Must be lowercase alphanumeric with hyphens";
|
|
619
|
+
if (value.length > 32) return "Must be 32 characters or less";
|
|
620
|
+
if (scopes.some((s) => s.name === value)) return "Scope name already exists";
|
|
621
|
+
return void 0;
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
if (p4.isCancel(scopeName)) {
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
627
|
+
const scopeDescription = await p4.text({
|
|
628
|
+
message: "Description:",
|
|
629
|
+
placeholder: "Authentication and authorization",
|
|
630
|
+
validate: (value) => {
|
|
631
|
+
if (!value || value.length < 10) return "Description must be at least 10 characters";
|
|
632
|
+
return void 0;
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
if (p4.isCancel(scopeDescription)) {
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
638
|
+
const scopeEmoji = await p4.text({
|
|
639
|
+
message: "Emoji (optional):",
|
|
640
|
+
placeholder: "\u{1F510}"
|
|
641
|
+
});
|
|
642
|
+
if (p4.isCancel(scopeEmoji)) {
|
|
643
|
+
break;
|
|
644
|
+
}
|
|
645
|
+
const scopeCategory = await p4.select({
|
|
646
|
+
message: "Category (optional):",
|
|
647
|
+
options: [
|
|
648
|
+
{ value: "auth", label: "Authentication & Authorization" },
|
|
649
|
+
{ value: "features", label: "Features & Functionality" },
|
|
650
|
+
{ value: "infrastructure", label: "Infrastructure & DevOps" },
|
|
651
|
+
{ value: "documentation", label: "Documentation" },
|
|
652
|
+
{ value: "testing", label: "Testing & QA" },
|
|
653
|
+
{ value: "performance", label: "Performance & Optimization" },
|
|
654
|
+
{ value: "other", label: "Other" },
|
|
655
|
+
{ value: "", label: "None" }
|
|
656
|
+
]
|
|
657
|
+
});
|
|
658
|
+
if (p4.isCancel(scopeCategory)) {
|
|
659
|
+
break;
|
|
660
|
+
}
|
|
661
|
+
scopes.push({
|
|
662
|
+
name: scopeName,
|
|
663
|
+
description: scopeDescription,
|
|
664
|
+
emoji: scopeEmoji ? scopeEmoji : void 0,
|
|
665
|
+
category: scopeCategory ? scopeCategory : void 0
|
|
666
|
+
});
|
|
667
|
+
console.log(chalk7.green(`
|
|
668
|
+
\u2713 Added scope: ${scopeName}
|
|
669
|
+
`));
|
|
670
|
+
if (scopes.length >= 3) {
|
|
671
|
+
addMore = await p4.confirm({
|
|
672
|
+
message: `You have ${scopes.length} scopes. Add another?`,
|
|
673
|
+
initialValue: scopes.length < 10
|
|
674
|
+
});
|
|
675
|
+
if (p4.isCancel(addMore)) {
|
|
676
|
+
break;
|
|
677
|
+
}
|
|
678
|
+
if (!addMore) break;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
if (scopes.length === 0) {
|
|
683
|
+
p4.cancel("No scopes defined. Operation cancelled.");
|
|
684
|
+
process.exit(1);
|
|
685
|
+
}
|
|
686
|
+
const validation = validateScopeDefinitions(scopes);
|
|
687
|
+
if (!validation.valid) {
|
|
688
|
+
console.log(chalk7.red("\n\u2717 Scope validation failed:\n"));
|
|
689
|
+
validation.errors.forEach((error) => console.log(chalk7.red(` \u2022 ${error}`)));
|
|
690
|
+
p4.cancel("Operation cancelled");
|
|
691
|
+
process.exit(1);
|
|
692
|
+
}
|
|
693
|
+
console.log(chalk7.green(`
|
|
694
|
+
\u2713 ${scopes.length} scopes validated successfully
|
|
695
|
+
`));
|
|
696
|
+
let outputDir;
|
|
697
|
+
if (options.outputDir) {
|
|
698
|
+
outputDir = options.outputDir;
|
|
699
|
+
} else if (isMonorepo) {
|
|
700
|
+
outputDir = join3(cwd, "packages", `scopes-${packageName}`);
|
|
701
|
+
} else {
|
|
702
|
+
const customDir = await p4.text({
|
|
703
|
+
message: "Output directory:",
|
|
704
|
+
placeholder: `./scopes-${packageName}`,
|
|
705
|
+
defaultValue: `./scopes-${packageName}`
|
|
706
|
+
});
|
|
707
|
+
if (p4.isCancel(customDir)) {
|
|
708
|
+
p4.cancel("Operation cancelled");
|
|
709
|
+
process.exit(0);
|
|
710
|
+
}
|
|
711
|
+
outputDir = join3(cwd, customDir);
|
|
712
|
+
}
|
|
713
|
+
if (existsSync3(outputDir)) {
|
|
714
|
+
const shouldOverwrite = await p4.confirm({
|
|
715
|
+
message: `Directory ${outputDir} already exists. Overwrite?`,
|
|
716
|
+
initialValue: false
|
|
717
|
+
});
|
|
718
|
+
if (p4.isCancel(shouldOverwrite) || !shouldOverwrite) {
|
|
719
|
+
p4.cancel("Operation cancelled");
|
|
720
|
+
process.exit(0);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
const spinner4 = p4.spinner();
|
|
724
|
+
spinner4.start("Creating package structure...");
|
|
725
|
+
try {
|
|
726
|
+
await mkdir2(join3(outputDir, "src"), { recursive: true });
|
|
727
|
+
const packageJson = {
|
|
728
|
+
name: `@workflow/scopes-${packageName}`,
|
|
729
|
+
version: "1.0.0",
|
|
730
|
+
description: `Scope preset for ${presetName}`,
|
|
731
|
+
keywords: ["workflow", "scopes", packageName, "preset"],
|
|
732
|
+
repository: {
|
|
733
|
+
type: "git",
|
|
734
|
+
url: "git+https://github.com/your-org/your-repo.git",
|
|
735
|
+
directory: `packages/scopes-${packageName}`
|
|
736
|
+
},
|
|
737
|
+
license: "MIT",
|
|
738
|
+
author: "Your Name",
|
|
739
|
+
type: "module",
|
|
740
|
+
exports: {
|
|
741
|
+
".": {
|
|
742
|
+
types: "./dist/index.d.ts",
|
|
743
|
+
import: "./dist/index.js"
|
|
744
|
+
}
|
|
745
|
+
},
|
|
746
|
+
files: ["dist"],
|
|
747
|
+
scripts: {
|
|
748
|
+
build: "tsup",
|
|
749
|
+
dev: "tsup --watch",
|
|
750
|
+
typecheck: "tsc --noEmit",
|
|
751
|
+
test: "vitest run"
|
|
752
|
+
},
|
|
753
|
+
peerDependencies: {
|
|
754
|
+
"@hawkinside_out/workflow-agent": "^1.0.0"
|
|
755
|
+
},
|
|
756
|
+
devDependencies: {
|
|
757
|
+
"@hawkinside_out/workflow-agent": "^1.0.0",
|
|
758
|
+
tsup: "^8.0.1",
|
|
759
|
+
typescript: "^5.3.3",
|
|
760
|
+
vitest: "^1.0.0"
|
|
761
|
+
},
|
|
762
|
+
publishConfig: {
|
|
763
|
+
access: "public"
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
await writeFile2(
|
|
767
|
+
join3(outputDir, "package.json"),
|
|
768
|
+
JSON.stringify(packageJson, null, 2),
|
|
769
|
+
"utf-8"
|
|
770
|
+
);
|
|
771
|
+
const tsconfig = {
|
|
772
|
+
extends: "../../tsconfig.json",
|
|
773
|
+
compilerOptions: {
|
|
774
|
+
outDir: "./dist",
|
|
775
|
+
rootDir: "./src"
|
|
776
|
+
},
|
|
777
|
+
include: ["src/**/*"]
|
|
778
|
+
};
|
|
779
|
+
await writeFile2(
|
|
780
|
+
join3(outputDir, "tsconfig.json"),
|
|
781
|
+
JSON.stringify(tsconfig, null, 2),
|
|
782
|
+
"utf-8"
|
|
783
|
+
);
|
|
784
|
+
const tsupConfig = `import { defineConfig } from 'tsup';
|
|
785
|
+
|
|
786
|
+
export default defineConfig({
|
|
787
|
+
entry: ['src/index.ts'],
|
|
788
|
+
format: ['esm'],
|
|
789
|
+
dts: true,
|
|
790
|
+
clean: true,
|
|
791
|
+
sourcemap: true,
|
|
792
|
+
});
|
|
793
|
+
`;
|
|
794
|
+
await writeFile2(join3(outputDir, "tsup.config.ts"), tsupConfig, "utf-8");
|
|
795
|
+
const indexTs = `import type { Scope } from '@hawkinside_out/workflow-agent/config';
|
|
796
|
+
|
|
797
|
+
export const scopes: Scope[] = ${JSON.stringify(scopes, null, 2)};
|
|
798
|
+
|
|
799
|
+
export const preset = {
|
|
800
|
+
name: '${presetName}',
|
|
801
|
+
description: 'Scope configuration for ${presetName}',
|
|
802
|
+
scopes,
|
|
803
|
+
version: '1.0.0',
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
export default preset;
|
|
807
|
+
`;
|
|
808
|
+
await writeFile2(join3(outputDir, "src", "index.ts"), indexTs, "utf-8");
|
|
809
|
+
if (!options.noTest) {
|
|
810
|
+
const testFile = `import { describe, it, expect } from 'vitest';
|
|
811
|
+
import { scopes, preset } from './index.js';
|
|
812
|
+
import { ScopeSchema } from '@hawkinside_out/workflow-agent/config';
|
|
813
|
+
|
|
814
|
+
describe('${presetName} Scope Preset', () => {
|
|
815
|
+
it('should export valid scopes array', () => {
|
|
816
|
+
expect(scopes).toBeDefined();
|
|
817
|
+
expect(Array.isArray(scopes)).toBe(true);
|
|
818
|
+
expect(scopes.length).toBeGreaterThan(0);
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
it('should have valid preset object', () => {
|
|
822
|
+
expect(preset).toBeDefined();
|
|
823
|
+
expect(preset.name).toBe('${presetName}');
|
|
824
|
+
expect(preset.scopes).toBe(scopes);
|
|
825
|
+
expect(preset.version).toBeDefined();
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
it('should have no duplicate scope names', () => {
|
|
829
|
+
const names = scopes.map(s => s.name);
|
|
830
|
+
const uniqueNames = new Set(names);
|
|
831
|
+
expect(uniqueNames.size).toBe(names.length);
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
it('should have all scopes match schema', () => {
|
|
835
|
+
scopes.forEach(scope => {
|
|
836
|
+
const result = ScopeSchema.safeParse(scope);
|
|
837
|
+
expect(result.success).toBe(true);
|
|
838
|
+
});
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
it('should have descriptions for all scopes', () => {
|
|
842
|
+
scopes.forEach(scope => {
|
|
843
|
+
expect(scope.description).toBeDefined();
|
|
844
|
+
expect(scope.description.length).toBeGreaterThan(0);
|
|
845
|
+
});
|
|
846
|
+
});
|
|
847
|
+
});
|
|
848
|
+
`;
|
|
849
|
+
await writeFile2(join3(outputDir, "src", "index.test.ts"), testFile, "utf-8");
|
|
850
|
+
}
|
|
851
|
+
spinner4.stop("\u2713 Package structure created");
|
|
852
|
+
if (isMonorepo) {
|
|
853
|
+
const workspaceFile = join3(cwd, "pnpm-workspace.yaml");
|
|
854
|
+
const workspaceContent = await readFile(workspaceFile, "utf-8");
|
|
855
|
+
const packagePath = `packages/scopes-${packageName}`;
|
|
856
|
+
if (!workspaceContent.includes(packagePath) && !workspaceContent.includes("packages/*")) {
|
|
857
|
+
console.log(chalk7.yellow("\n\u26A0\uFE0F Add the following to pnpm-workspace.yaml:"));
|
|
858
|
+
console.log(chalk7.dim(` - '${packagePath}'`));
|
|
859
|
+
} else {
|
|
860
|
+
console.log(chalk7.green("\n\u2713 Package will be included in workspace"));
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
console.log(chalk7.green.bold("\n\u2728 Custom scope package created successfully!\n"));
|
|
864
|
+
console.log(chalk7.bold("Package details:"));
|
|
865
|
+
console.log(chalk7.dim(` Location: ${outputDir}`));
|
|
866
|
+
console.log(chalk7.dim(` Package: @workflow/scopes-${packageName}`));
|
|
867
|
+
console.log(chalk7.dim(` Scopes: ${scopes.length} defined
|
|
868
|
+
`));
|
|
869
|
+
console.log(chalk7.bold("Next steps:\n"));
|
|
870
|
+
console.log(chalk7.dim(` 1. cd ${outputDir}`));
|
|
871
|
+
console.log(chalk7.dim(` 2. pnpm install`));
|
|
872
|
+
console.log(chalk7.dim(` 3. pnpm build`));
|
|
873
|
+
if (!options.noTest) {
|
|
874
|
+
console.log(chalk7.dim(` 4. pnpm test`));
|
|
875
|
+
}
|
|
876
|
+
console.log(chalk7.dim(` ${!options.noTest ? "5" : "4"}. Update repository URL in package.json`));
|
|
877
|
+
const shouldPublish = isNonInteractive ? false : await p4.confirm({
|
|
878
|
+
message: "\nWould you like instructions for publishing to npm?",
|
|
879
|
+
initialValue: false
|
|
880
|
+
});
|
|
881
|
+
if (shouldPublish && !p4.isCancel(shouldPublish)) {
|
|
882
|
+
console.log(chalk7.bold("\n\u{1F4E6} Publishing instructions:\n"));
|
|
883
|
+
console.log(chalk7.dim(" 1. npm login (or configure .npmrc with your registry)"));
|
|
884
|
+
console.log(chalk7.dim(" 2. Update version in package.json as needed"));
|
|
885
|
+
console.log(chalk7.dim(" 3. pnpm publish --access public"));
|
|
886
|
+
console.log(chalk7.dim(" 4. Use in other projects: pnpm add @workflow/scopes-" + packageName + "\n"));
|
|
887
|
+
}
|
|
888
|
+
} catch (error) {
|
|
889
|
+
spinner4.stop("\u2717 Failed to create package");
|
|
890
|
+
console.error(chalk7.red("\nError:"), error);
|
|
891
|
+
process.exit(1);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// src/cli/commands/scope-migrate.ts
|
|
896
|
+
import * as p5 from "@clack/prompts";
|
|
897
|
+
import chalk8 from "chalk";
|
|
898
|
+
import { existsSync as existsSync4 } from "fs";
|
|
899
|
+
import { writeFile as writeFile3, mkdir as mkdir3, readFile as readFile2 } from "fs/promises";
|
|
900
|
+
import { join as join4 } from "path";
|
|
901
|
+
async function scopeMigrateCommand(options) {
|
|
902
|
+
console.log(chalk8.bold.cyan("\n\u{1F504} Migrate Scopes to Custom Package\n"));
|
|
903
|
+
const cwd = process.cwd();
|
|
904
|
+
if (!hasConfig(cwd)) {
|
|
905
|
+
p5.cancel("No workflow.config.json found in current directory");
|
|
906
|
+
process.exit(1);
|
|
907
|
+
}
|
|
908
|
+
let config = null;
|
|
909
|
+
try {
|
|
910
|
+
config = await loadConfig(cwd);
|
|
911
|
+
} catch (error) {
|
|
912
|
+
console.error(chalk8.red("Failed to load config:"), error);
|
|
913
|
+
process.exit(1);
|
|
914
|
+
}
|
|
915
|
+
if (!config) {
|
|
916
|
+
p5.cancel("Failed to load configuration");
|
|
917
|
+
process.exit(1);
|
|
918
|
+
}
|
|
919
|
+
if (!config.scopes || config.scopes.length === 0) {
|
|
920
|
+
p5.cancel("No scopes found in workflow.config.json");
|
|
921
|
+
process.exit(1);
|
|
922
|
+
}
|
|
923
|
+
console.log(chalk8.dim(`Found ${config.scopes.length} scopes in workflow.config.json
|
|
924
|
+
`));
|
|
925
|
+
console.log(chalk8.bold("Current scopes:"));
|
|
926
|
+
config.scopes.forEach((scope, i) => {
|
|
927
|
+
console.log(chalk8.dim(` ${i + 1}. ${scope.emoji || "\u2022"} ${scope.name} - ${scope.description}`));
|
|
928
|
+
});
|
|
929
|
+
console.log();
|
|
930
|
+
const shouldContinue = await p5.confirm({
|
|
931
|
+
message: "Migrate these scopes to a custom package?",
|
|
932
|
+
initialValue: true
|
|
933
|
+
});
|
|
934
|
+
if (p5.isCancel(shouldContinue) || !shouldContinue) {
|
|
935
|
+
p5.cancel("Migration cancelled");
|
|
936
|
+
process.exit(0);
|
|
937
|
+
}
|
|
938
|
+
const isMonorepo = existsSync4(join4(cwd, "pnpm-workspace.yaml"));
|
|
939
|
+
if (isMonorepo) {
|
|
940
|
+
console.log(chalk8.dim("\n\u2713 Detected monorepo workspace\n"));
|
|
941
|
+
}
|
|
942
|
+
const packageNameInput = options.name || await p5.text({
|
|
943
|
+
message: "Package name for the scope preset:",
|
|
944
|
+
placeholder: config.projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-"),
|
|
945
|
+
validate: (value) => {
|
|
946
|
+
if (!value || value.length === 0) return "Package name is required";
|
|
947
|
+
if (!/^[a-z0-9-]+$/.test(value)) return "Package name must be lowercase alphanumeric with hyphens";
|
|
948
|
+
if (value.length > 32) return "Package name must be 32 characters or less";
|
|
949
|
+
return void 0;
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
if (p5.isCancel(packageNameInput)) {
|
|
953
|
+
p5.cancel("Migration cancelled");
|
|
954
|
+
process.exit(0);
|
|
955
|
+
}
|
|
956
|
+
const packageName = packageNameInput;
|
|
957
|
+
const presetNameInput = await p5.text({
|
|
958
|
+
message: "Preset display name:",
|
|
959
|
+
placeholder: config.projectName,
|
|
960
|
+
defaultValue: config.projectName
|
|
961
|
+
});
|
|
962
|
+
if (p5.isCancel(presetNameInput)) {
|
|
963
|
+
p5.cancel("Migration cancelled");
|
|
964
|
+
process.exit(0);
|
|
965
|
+
}
|
|
966
|
+
const presetName = presetNameInput;
|
|
967
|
+
const validation = validateScopeDefinitions(config.scopes);
|
|
968
|
+
if (!validation.valid) {
|
|
969
|
+
console.log(chalk8.yellow("\n\u26A0\uFE0F Scope validation warnings:\n"));
|
|
970
|
+
validation.errors.forEach((error) => console.log(chalk8.yellow(` \u2022 ${error}`)));
|
|
971
|
+
const shouldFix = await p5.confirm({
|
|
972
|
+
message: "Some scopes have validation issues. Continue anyway?",
|
|
973
|
+
initialValue: false
|
|
974
|
+
});
|
|
975
|
+
if (p5.isCancel(shouldFix) || !shouldFix) {
|
|
976
|
+
p5.cancel("Migration cancelled. Please fix validation errors first.");
|
|
977
|
+
process.exit(1);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
let outputDir;
|
|
981
|
+
if (options.outputDir) {
|
|
982
|
+
outputDir = options.outputDir;
|
|
983
|
+
} else if (isMonorepo) {
|
|
984
|
+
outputDir = join4(cwd, "packages", `scopes-${packageName}`);
|
|
985
|
+
} else {
|
|
986
|
+
const customDir = await p5.text({
|
|
987
|
+
message: "Output directory:",
|
|
988
|
+
placeholder: `./scopes-${packageName}`,
|
|
989
|
+
defaultValue: `./scopes-${packageName}`
|
|
990
|
+
});
|
|
991
|
+
if (p5.isCancel(customDir)) {
|
|
992
|
+
p5.cancel("Migration cancelled");
|
|
993
|
+
process.exit(0);
|
|
994
|
+
}
|
|
995
|
+
outputDir = join4(cwd, customDir);
|
|
996
|
+
}
|
|
997
|
+
if (existsSync4(outputDir)) {
|
|
998
|
+
const shouldOverwrite = await p5.confirm({
|
|
999
|
+
message: `Directory ${outputDir} already exists. Overwrite?`,
|
|
1000
|
+
initialValue: false
|
|
1001
|
+
});
|
|
1002
|
+
if (p5.isCancel(shouldOverwrite) || !shouldOverwrite) {
|
|
1003
|
+
p5.cancel("Migration cancelled");
|
|
1004
|
+
process.exit(0);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
const spinner4 = p5.spinner();
|
|
1008
|
+
spinner4.start("Migrating scopes to package...");
|
|
1009
|
+
try {
|
|
1010
|
+
await mkdir3(join4(outputDir, "src"), { recursive: true });
|
|
1011
|
+
const packageJson = {
|
|
1012
|
+
name: `@workflow/scopes-${packageName}`,
|
|
1013
|
+
version: "1.0.0",
|
|
1014
|
+
description: `Migrated scope preset for ${presetName}`,
|
|
1015
|
+
keywords: ["workflow", "scopes", packageName, "preset", "migrated"],
|
|
1016
|
+
repository: {
|
|
1017
|
+
type: "git",
|
|
1018
|
+
url: "git+https://github.com/your-org/your-repo.git",
|
|
1019
|
+
directory: `packages/scopes-${packageName}`
|
|
1020
|
+
},
|
|
1021
|
+
license: "MIT",
|
|
1022
|
+
author: "Your Name",
|
|
1023
|
+
type: "module",
|
|
1024
|
+
exports: {
|
|
1025
|
+
".": {
|
|
1026
|
+
types: "./dist/index.d.ts",
|
|
1027
|
+
import: "./dist/index.js"
|
|
1028
|
+
}
|
|
1029
|
+
},
|
|
1030
|
+
files: ["dist"],
|
|
1031
|
+
scripts: {
|
|
1032
|
+
build: "tsup",
|
|
1033
|
+
dev: "tsup --watch",
|
|
1034
|
+
typecheck: "tsc --noEmit",
|
|
1035
|
+
test: "vitest run"
|
|
1036
|
+
},
|
|
1037
|
+
peerDependencies: {
|
|
1038
|
+
"@hawkinside_out/workflow-agent": "^1.0.0"
|
|
1039
|
+
},
|
|
1040
|
+
devDependencies: {
|
|
1041
|
+
"@hawkinside_out/workflow-agent": "^1.0.0",
|
|
1042
|
+
tsup: "^8.0.1",
|
|
1043
|
+
typescript: "^5.3.3",
|
|
1044
|
+
vitest: "^1.0.0"
|
|
1045
|
+
},
|
|
1046
|
+
publishConfig: {
|
|
1047
|
+
access: "public"
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1050
|
+
await writeFile3(
|
|
1051
|
+
join4(outputDir, "package.json"),
|
|
1052
|
+
JSON.stringify(packageJson, null, 2),
|
|
1053
|
+
"utf-8"
|
|
1054
|
+
);
|
|
1055
|
+
const tsconfig = {
|
|
1056
|
+
extends: "../../tsconfig.json",
|
|
1057
|
+
compilerOptions: {
|
|
1058
|
+
outDir: "./dist",
|
|
1059
|
+
rootDir: "./src"
|
|
1060
|
+
},
|
|
1061
|
+
include: ["src/**/*"]
|
|
1062
|
+
};
|
|
1063
|
+
await writeFile3(
|
|
1064
|
+
join4(outputDir, "tsconfig.json"),
|
|
1065
|
+
JSON.stringify(tsconfig, null, 2),
|
|
1066
|
+
"utf-8"
|
|
1067
|
+
);
|
|
1068
|
+
const tsupConfig = `import { defineConfig } from 'tsup';
|
|
1069
|
+
|
|
1070
|
+
export default defineConfig({
|
|
1071
|
+
entry: ['src/index.ts'],
|
|
1072
|
+
format: ['esm'],
|
|
1073
|
+
dts: true,
|
|
1074
|
+
clean: true,
|
|
1075
|
+
sourcemap: true,
|
|
1076
|
+
});
|
|
1077
|
+
`;
|
|
1078
|
+
await writeFile3(join4(outputDir, "tsup.config.ts"), tsupConfig, "utf-8");
|
|
1079
|
+
const indexTs = `import type { Scope } from '@hawkinside_out/workflow-agent/config';
|
|
1080
|
+
|
|
1081
|
+
export const scopes: Scope[] = ${JSON.stringify(config.scopes, null, 2)};
|
|
1082
|
+
|
|
1083
|
+
export const preset = {
|
|
1084
|
+
name: '${presetName}',
|
|
1085
|
+
description: 'Migrated scope configuration for ${presetName}',
|
|
1086
|
+
scopes,
|
|
1087
|
+
version: '1.0.0',
|
|
1088
|
+
};
|
|
1089
|
+
|
|
1090
|
+
export default preset;
|
|
1091
|
+
`;
|
|
1092
|
+
await writeFile3(join4(outputDir, "src", "index.ts"), indexTs, "utf-8");
|
|
1093
|
+
const testFile = `import { describe, it, expect } from 'vitest';
|
|
1094
|
+
import { scopes, preset } from './index.js';
|
|
1095
|
+
import { ScopeSchema } from '@hawkinside_out/workflow-agent/config';
|
|
1096
|
+
|
|
1097
|
+
describe('${presetName} Scope Preset (Migrated)', () => {
|
|
1098
|
+
it('should export valid scopes array', () => {
|
|
1099
|
+
expect(scopes).toBeDefined();
|
|
1100
|
+
expect(Array.isArray(scopes)).toBe(true);
|
|
1101
|
+
expect(scopes.length).toBe(${config.scopes.length});
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
it('should have valid preset object', () => {
|
|
1105
|
+
expect(preset).toBeDefined();
|
|
1106
|
+
expect(preset.name).toBe('${presetName}');
|
|
1107
|
+
expect(preset.scopes).toBe(scopes);
|
|
1108
|
+
expect(preset.version).toBeDefined();
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
it('should have no duplicate scope names', () => {
|
|
1112
|
+
const names = scopes.map(s => s.name);
|
|
1113
|
+
const uniqueNames = new Set(names);
|
|
1114
|
+
expect(uniqueNames.size).toBe(names.length);
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
it('should have all scopes match schema', () => {
|
|
1118
|
+
scopes.forEach(scope => {
|
|
1119
|
+
const result = ScopeSchema.safeParse(scope);
|
|
1120
|
+
if (!result.success) {
|
|
1121
|
+
console.error(\`Scope "\${scope.name}" failed validation:\`, result.error);
|
|
1122
|
+
}
|
|
1123
|
+
expect(result.success).toBe(true);
|
|
1124
|
+
});
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
it('should have descriptions for all scopes', () => {
|
|
1128
|
+
scopes.forEach(scope => {
|
|
1129
|
+
expect(scope.description).toBeDefined();
|
|
1130
|
+
expect(scope.description.length).toBeGreaterThan(0);
|
|
1131
|
+
});
|
|
1132
|
+
});
|
|
1133
|
+
});
|
|
1134
|
+
`;
|
|
1135
|
+
await writeFile3(join4(outputDir, "src", "index.test.ts"), testFile, "utf-8");
|
|
1136
|
+
spinner4.stop("\u2713 Package created from migrated scopes");
|
|
1137
|
+
if (isMonorepo) {
|
|
1138
|
+
const workspaceFile = join4(cwd, "pnpm-workspace.yaml");
|
|
1139
|
+
const workspaceContent = await readFile2(workspaceFile, "utf-8");
|
|
1140
|
+
const packagePath = `packages/scopes-${packageName}`;
|
|
1141
|
+
if (!workspaceContent.includes(packagePath) && !workspaceContent.includes("packages/*")) {
|
|
1142
|
+
console.log(chalk8.yellow("\n\u26A0\uFE0F Add the following to pnpm-workspace.yaml:"));
|
|
1143
|
+
console.log(chalk8.dim(` - '${packagePath}'`));
|
|
1144
|
+
} else {
|
|
1145
|
+
console.log(chalk8.green("\n\u2713 Package will be included in workspace"));
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
const keepConfig = options.keepConfig ?? await p5.confirm({
|
|
1149
|
+
message: "Remove migrated scopes from workflow.config.json?",
|
|
1150
|
+
initialValue: false
|
|
1151
|
+
});
|
|
1152
|
+
if (!p5.isCancel(keepConfig) && !keepConfig) {
|
|
1153
|
+
const configPath = join4(cwd, "workflow.config.json");
|
|
1154
|
+
const updatedConfig = {
|
|
1155
|
+
...config,
|
|
1156
|
+
scopes: [],
|
|
1157
|
+
// Clear inline scopes
|
|
1158
|
+
preset: `scopes-${packageName}`
|
|
1159
|
+
// Reference the new package
|
|
1160
|
+
};
|
|
1161
|
+
await writeFile3(configPath, JSON.stringify(updatedConfig, null, 2), "utf-8");
|
|
1162
|
+
console.log(chalk8.green("\u2713 Updated workflow.config.json"));
|
|
1163
|
+
console.log(chalk8.dim(" \u2022 Cleared inline scopes"));
|
|
1164
|
+
console.log(chalk8.dim(` \u2022 Added preset reference: scopes-${packageName}
|
|
1165
|
+
`));
|
|
1166
|
+
}
|
|
1167
|
+
console.log(chalk8.green.bold("\n\u2728 Migration completed successfully!\n"));
|
|
1168
|
+
console.log(chalk8.bold("Package details:"));
|
|
1169
|
+
console.log(chalk8.dim(` Location: ${outputDir}`));
|
|
1170
|
+
console.log(chalk8.dim(` Package: @workflow/scopes-${packageName}`));
|
|
1171
|
+
console.log(chalk8.dim(` Scopes: ${config.scopes.length} migrated
|
|
1172
|
+
`));
|
|
1173
|
+
console.log(chalk8.bold("Next steps:\n"));
|
|
1174
|
+
console.log(chalk8.dim(` 1. cd ${outputDir}`));
|
|
1175
|
+
console.log(chalk8.dim(` 2. pnpm install`));
|
|
1176
|
+
console.log(chalk8.dim(` 3. pnpm build`));
|
|
1177
|
+
console.log(chalk8.dim(` 4. pnpm test`));
|
|
1178
|
+
console.log(chalk8.dim(` 5. Update repository URL in package.json
|
|
1179
|
+
`));
|
|
1180
|
+
if (!keepConfig) {
|
|
1181
|
+
console.log(chalk8.bold("To use the migrated scopes:\n"));
|
|
1182
|
+
console.log(chalk8.dim(` 1. Install the package: pnpm add -w @workflow/scopes-${packageName}`));
|
|
1183
|
+
console.log(chalk8.dim(` 2. The preset is already referenced in workflow.config.json
|
|
1184
|
+
`));
|
|
1185
|
+
}
|
|
1186
|
+
console.log(chalk8.dim("\u{1F4A1} Tip: You can now reuse this scope package across multiple projects!\n"));
|
|
1187
|
+
} catch (error) {
|
|
1188
|
+
spinner4.stop("\u2717 Migration failed");
|
|
1189
|
+
console.error(chalk8.red("\nError:"), error);
|
|
1190
|
+
process.exit(1);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
// src/cli/index.ts
|
|
1195
|
+
var program = new Command();
|
|
1196
|
+
program.name("workflow").description("A self-evolving workflow management system for AI agent development").version("1.0.0");
|
|
1197
|
+
program.command("init").description("Initialize workflow in current project").option("--migrate", "Auto-detect existing patterns and migrate").option("--workspace", "Initialize for multiple repositories").option("--preset <preset>", "Preset to use (saas, library, api, ecommerce, cms, custom)").option("--name <name>", "Project name").option("-y, --yes", "Skip confirmation prompts").action(initCommand);
|
|
1198
|
+
program.command("validate <type>").description("Validate branch name, commit message, or PR title").argument("<type>", "What to validate: branch, commit, or pr").argument("[value]", "Value to validate (defaults to current branch/HEAD commit)").option("--suggest-on-error", "Offer improvement suggestions on validation errors").action(validateCommand);
|
|
1199
|
+
program.command("config <action>").description("Manage workflow configuration").argument("<action>", "Action: get, set, add, remove").argument("[key]", "Config key").argument("[value]", "Config value").action(configCommand);
|
|
1200
|
+
program.command("suggest").description("Submit an improvement suggestion").argument("<feedback>", "Your improvement suggestion").option("--author <author>", "Your name or username").option("--category <category>", "Category: feature, bug, documentation, performance, other").action(suggestCommand);
|
|
1201
|
+
program.command("setup").description("Add workflow scripts to package.json").action(setupCommand);
|
|
1202
|
+
program.command("doctor").description("Run health check and get optimization suggestions").action(doctorCommand);
|
|
1203
|
+
program.command("scope:create").description("Create a custom scope package").option("--name <name>", 'Package name (e.g., "fintech", "gaming")').option("--scopes <scopes>", "Comma-separated scopes (format: name:description:emoji:category)").option("--preset-name <preset>", "Preset display name").option("--output-dir <dir>", "Output directory").option("--no-test", "Skip test file generation").action(scopeCreateCommand);
|
|
1204
|
+
program.command("scope:migrate").description("Migrate inline scopes to a custom package").option("--name <name>", "Package name for the preset").option("--output-dir <dir>", "Output directory").option("--keep-config", "Keep inline scopes in config after migration").action(scopeMigrateCommand);
|
|
1205
|
+
program.parse();
|
|
1206
|
+
//# sourceMappingURL=index.js.map
|