workflow-agent-cli 2.2.1 → 2.3.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/dist/chunk-RDVTKGQV.js +165 -0
- package/dist/chunk-RDVTKGQV.js.map +1 -0
- package/dist/chunk-YI3LBZ7I.js +1425 -0
- package/dist/chunk-YI3LBZ7I.js.map +1 -0
- package/dist/cli/index.js +380 -2259
- package/dist/cli/index.js.map +1 -1
- package/dist/config/index.d.ts +3 -8
- package/dist/config/index.js +3 -9
- package/dist/index.d.ts +114 -2
- package/dist/index.js +25 -9
- package/dist/schema-OJg7YAI_.d.ts +181 -0
- package/dist/validators/index.d.ts +1 -1
- package/package.json +4 -9
- package/dist/chunk-JCDT4363.js +0 -339
- package/dist/chunk-JCDT4363.js.map +0 -1
- package/dist/schema-Bdv6BQqI.d.ts +0 -437
package/dist/cli/index.js
CHANGED
|
@@ -1,27 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
generateAuditReport,
|
|
4
|
+
hasUncommittedChanges,
|
|
5
|
+
runAllChecks,
|
|
6
|
+
runAllSetups,
|
|
7
|
+
stageAllChanges
|
|
8
|
+
} from "../chunk-YI3LBZ7I.js";
|
|
2
9
|
import {
|
|
3
10
|
validateBranchName,
|
|
4
11
|
validateCommitMessage,
|
|
5
12
|
validatePRTitle
|
|
6
13
|
} from "../chunk-NMHWD2GA.js";
|
|
7
14
|
import {
|
|
8
|
-
DEFAULT_RESERVED_SCOPE_NAMES,
|
|
9
15
|
hasConfig,
|
|
10
16
|
loadConfig,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
validateScopeName
|
|
14
|
-
} from "../chunk-JCDT4363.js";
|
|
17
|
+
validateScopeDefinitions
|
|
18
|
+
} from "../chunk-RDVTKGQV.js";
|
|
15
19
|
|
|
16
20
|
// src/cli/index.ts
|
|
17
|
-
import { Command
|
|
21
|
+
import { Command } from "commander";
|
|
18
22
|
|
|
19
23
|
// src/cli/commands/init.ts
|
|
20
24
|
import * as p from "@clack/prompts";
|
|
21
25
|
import chalk from "chalk";
|
|
22
|
-
import { existsSync
|
|
23
|
-
import { writeFile
|
|
24
|
-
import { join
|
|
26
|
+
import { existsSync } from "fs";
|
|
27
|
+
import { writeFile, mkdir } from "fs/promises";
|
|
28
|
+
import { join, dirname } from "path";
|
|
25
29
|
import { fileURLToPath } from "url";
|
|
26
30
|
|
|
27
31
|
// src/templates/renderer.ts
|
|
@@ -192,6 +196,18 @@ async function renderTemplateFile(templatePath, outputPath, context) {
|
|
|
192
196
|
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
193
197
|
await fs.writeFile(outputPath, rendered, "utf-8");
|
|
194
198
|
}
|
|
199
|
+
async function renderTemplateDirectory(templateDir, outputDir, context) {
|
|
200
|
+
const files = await fs.readdir(templateDir);
|
|
201
|
+
const rendered = [];
|
|
202
|
+
for (const file of files) {
|
|
203
|
+
if (!file.match(/\.(md|ts|json)$/)) continue;
|
|
204
|
+
const templatePath = path.join(templateDir, file);
|
|
205
|
+
const outputPath = path.join(outputDir, file);
|
|
206
|
+
await renderTemplateFile(templatePath, outputPath, context);
|
|
207
|
+
rendered.push(file);
|
|
208
|
+
}
|
|
209
|
+
return rendered;
|
|
210
|
+
}
|
|
195
211
|
async function getProjectName(projectPath) {
|
|
196
212
|
try {
|
|
197
213
|
const pkgPath = path.join(projectPath, "package.json");
|
|
@@ -222,694 +238,13 @@ async function validateTemplateDirectory(templateDir) {
|
|
|
222
238
|
}
|
|
223
239
|
}
|
|
224
240
|
|
|
225
|
-
// src/templates/metadata.ts
|
|
226
|
-
var templateMetadata = {
|
|
227
|
-
"AGENT_EDITING_INSTRUCTIONS.md": {
|
|
228
|
-
filename: "AGENT_EDITING_INSTRUCTIONS.md",
|
|
229
|
-
displayName: "Agent Editing Instructions",
|
|
230
|
-
mandatory: true,
|
|
231
|
-
category: "workflow",
|
|
232
|
-
validators: ["implementation-plan"],
|
|
233
|
-
description: "Core rules for AI agents: implementation plans, coding standards, architecture"
|
|
234
|
-
},
|
|
235
|
-
"BRANCHING_STRATEGY.md": {
|
|
236
|
-
filename: "BRANCHING_STRATEGY.md",
|
|
237
|
-
displayName: "Branching Strategy",
|
|
238
|
-
mandatory: true,
|
|
239
|
-
category: "workflow",
|
|
240
|
-
validators: ["branch-name", "pr-title"],
|
|
241
|
-
description: "Git branch naming conventions, PR requirements, merge policies"
|
|
242
|
-
},
|
|
243
|
-
"TESTING_STRATEGY.md": {
|
|
244
|
-
filename: "TESTING_STRATEGY.md",
|
|
245
|
-
displayName: "Testing Strategy",
|
|
246
|
-
mandatory: true,
|
|
247
|
-
category: "development",
|
|
248
|
-
validators: ["test-coverage"],
|
|
249
|
-
description: "Testing pyramid, Vitest/Playwright patterns, when tests are required"
|
|
250
|
-
},
|
|
251
|
-
"SELF_IMPROVEMENT_MANDATE.md": {
|
|
252
|
-
filename: "SELF_IMPROVEMENT_MANDATE.md",
|
|
253
|
-
displayName: "Self-Improvement Mandate",
|
|
254
|
-
mandatory: true,
|
|
255
|
-
category: "workflow",
|
|
256
|
-
validators: [],
|
|
257
|
-
description: "Continuous improvement tracking, changelog requirements"
|
|
258
|
-
},
|
|
259
|
-
"SINGLE_SOURCE_OF_TRUTH.md": {
|
|
260
|
-
filename: "SINGLE_SOURCE_OF_TRUTH.md",
|
|
261
|
-
displayName: "Single Source of Truth",
|
|
262
|
-
mandatory: true,
|
|
263
|
-
category: "workflow",
|
|
264
|
-
validators: [],
|
|
265
|
-
description: "Canonical code locations, service patterns, avoiding duplication"
|
|
266
|
-
},
|
|
267
|
-
"COMPONENT_LIBRARY.md": {
|
|
268
|
-
filename: "COMPONENT_LIBRARY.md",
|
|
269
|
-
displayName: "Component Library",
|
|
270
|
-
mandatory: false,
|
|
271
|
-
category: "development",
|
|
272
|
-
validators: [],
|
|
273
|
-
description: "UI component patterns, design tokens, decision tree"
|
|
274
|
-
},
|
|
275
|
-
"DEPLOYMENT_STRATEGY.md": {
|
|
276
|
-
filename: "DEPLOYMENT_STRATEGY.md",
|
|
277
|
-
displayName: "Deployment Strategy",
|
|
278
|
-
mandatory: false,
|
|
279
|
-
category: "development",
|
|
280
|
-
validators: [],
|
|
281
|
-
description: "Deployment workflow, environments, migrations, rollback"
|
|
282
|
-
},
|
|
283
|
-
"LIBRARY_INVENTORY.md": {
|
|
284
|
-
filename: "LIBRARY_INVENTORY.md",
|
|
285
|
-
displayName: "Library Inventory",
|
|
286
|
-
mandatory: false,
|
|
287
|
-
category: "development",
|
|
288
|
-
validators: [],
|
|
289
|
-
description: "Dependency catalog, approved libraries, new library process"
|
|
290
|
-
},
|
|
291
|
-
"SCOPE_CREATION_WORKFLOW.md": {
|
|
292
|
-
filename: "SCOPE_CREATION_WORKFLOW.md",
|
|
293
|
-
displayName: "Scope Creation Workflow",
|
|
294
|
-
mandatory: false,
|
|
295
|
-
category: "workflow",
|
|
296
|
-
validators: [],
|
|
297
|
-
description: "Workflow for AI agents creating custom scopes"
|
|
298
|
-
},
|
|
299
|
-
"CUSTOM_SCOPE_TEMPLATE.md": {
|
|
300
|
-
filename: "CUSTOM_SCOPE_TEMPLATE.md",
|
|
301
|
-
displayName: "Custom Scope Template",
|
|
302
|
-
mandatory: false,
|
|
303
|
-
category: "workflow",
|
|
304
|
-
validators: [],
|
|
305
|
-
description: "Template for defining custom scope packages"
|
|
306
|
-
},
|
|
307
|
-
"PROJECT_TEMPLATE_README.md": {
|
|
308
|
-
filename: "PROJECT_TEMPLATE_README.md",
|
|
309
|
-
displayName: "Project Template README",
|
|
310
|
-
mandatory: false,
|
|
311
|
-
category: "documentation",
|
|
312
|
-
validators: [],
|
|
313
|
-
description: "Meta-document describing project structure"
|
|
314
|
-
},
|
|
315
|
-
"Guidelines.md": {
|
|
316
|
-
filename: "Guidelines.md",
|
|
317
|
-
displayName: "Custom Guidelines",
|
|
318
|
-
mandatory: false,
|
|
319
|
-
category: "documentation",
|
|
320
|
-
validators: [],
|
|
321
|
-
description: "Placeholder for custom user guidelines"
|
|
322
|
-
}
|
|
323
|
-
};
|
|
324
|
-
function getMandatoryTemplates() {
|
|
325
|
-
return Object.values(templateMetadata).filter((t) => t.mandatory);
|
|
326
|
-
}
|
|
327
|
-
function getOptionalTemplates() {
|
|
328
|
-
return Object.values(templateMetadata).filter((t) => !t.mandatory);
|
|
329
|
-
}
|
|
330
|
-
function getMandatoryTemplateFilenames() {
|
|
331
|
-
return getMandatoryTemplates().map((t) => t.filename);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// src/utils/hooks.ts
|
|
335
|
-
import { existsSync } from "fs";
|
|
336
|
-
import { readFile, writeFile, unlink, chmod, rename, mkdir } from "fs/promises";
|
|
337
|
-
import { join } from "path";
|
|
338
|
-
function getGitHooksDir(projectPath = process.cwd()) {
|
|
339
|
-
return join(projectPath, ".git", "hooks");
|
|
340
|
-
}
|
|
341
|
-
function hasGitRepo(projectPath = process.cwd()) {
|
|
342
|
-
return existsSync(join(projectPath, ".git"));
|
|
343
|
-
}
|
|
344
|
-
function generatePreCommitHook(config) {
|
|
345
|
-
const checks = config?.preCommit || ["validate-branch", "check-guidelines"];
|
|
346
|
-
const checkCommands = checks.map((check) => {
|
|
347
|
-
switch (check) {
|
|
348
|
-
case "validate-branch":
|
|
349
|
-
return " workflow validate branch";
|
|
350
|
-
case "validate-commit":
|
|
351
|
-
return " workflow validate commit";
|
|
352
|
-
case "check-guidelines":
|
|
353
|
-
return " workflow doctor --check-guidelines-only 2>/dev/null || true";
|
|
354
|
-
case "validate-scopes":
|
|
355
|
-
return " workflow config validate";
|
|
356
|
-
default:
|
|
357
|
-
return "";
|
|
358
|
-
}
|
|
359
|
-
}).filter(Boolean).join("\n");
|
|
360
|
-
return `#!/bin/sh
|
|
361
|
-
# Workflow Agent pre-commit hook
|
|
362
|
-
# Auto-generated - do not edit manually
|
|
363
|
-
# To reinstall: workflow hooks install
|
|
364
|
-
# To uninstall: workflow hooks uninstall
|
|
365
|
-
|
|
366
|
-
# Skip in CI environment
|
|
367
|
-
if [ -n "\${CI:-}" ] || [ -n "\${GITHUB_ACTIONS:-}" ] || [ -n "\${GITLAB_CI:-}" ]; then
|
|
368
|
-
exit 0
|
|
369
|
-
fi
|
|
370
|
-
|
|
371
|
-
# Run workflow checks
|
|
372
|
-
${checkCommands}
|
|
373
|
-
|
|
374
|
-
# Run original hook if it exists
|
|
375
|
-
if [ -f ".git/hooks/pre-commit.original" ]; then
|
|
376
|
-
.git/hooks/pre-commit.original "$@"
|
|
377
|
-
fi
|
|
378
|
-
`;
|
|
379
|
-
}
|
|
380
|
-
function generateCommitMsgHook(config) {
|
|
381
|
-
const checks = config?.commitMsg || ["validate-commit"];
|
|
382
|
-
const checkCommands = checks.map((check) => {
|
|
383
|
-
switch (check) {
|
|
384
|
-
case "validate-commit":
|
|
385
|
-
return ' workflow validate commit "$(cat "$1")"';
|
|
386
|
-
default:
|
|
387
|
-
return "";
|
|
388
|
-
}
|
|
389
|
-
}).filter(Boolean).join("\n");
|
|
390
|
-
return `#!/bin/sh
|
|
391
|
-
# Workflow Agent commit-msg hook
|
|
392
|
-
# Auto-generated - do not edit manually
|
|
393
|
-
# To reinstall: workflow hooks install
|
|
394
|
-
# To uninstall: workflow hooks uninstall
|
|
395
|
-
|
|
396
|
-
# Skip in CI environment
|
|
397
|
-
if [ -n "\${CI:-}" ] || [ -n "\${GITHUB_ACTIONS:-}" ] || [ -n "\${GITLAB_CI:-}" ]; then
|
|
398
|
-
exit 0
|
|
399
|
-
fi
|
|
400
|
-
|
|
401
|
-
# Run workflow checks
|
|
402
|
-
${checkCommands}
|
|
403
|
-
|
|
404
|
-
# Run original hook if it exists
|
|
405
|
-
if [ -f ".git/hooks/commit-msg.original" ]; then
|
|
406
|
-
.git/hooks/commit-msg.original "$@"
|
|
407
|
-
fi
|
|
408
|
-
`;
|
|
409
|
-
}
|
|
410
|
-
async function isWorkflowHook(hookPath) {
|
|
411
|
-
try {
|
|
412
|
-
const content = await readFile(hookPath, "utf-8");
|
|
413
|
-
return content.includes("Workflow Agent") && content.includes("Auto-generated");
|
|
414
|
-
} catch {
|
|
415
|
-
return false;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
async function getHookStatus(hookType, projectPath = process.cwd()) {
|
|
419
|
-
const hooksDir = getGitHooksDir(projectPath);
|
|
420
|
-
const hookPath = join(hooksDir, hookType);
|
|
421
|
-
const originalPath = join(hooksDir, `${hookType}.original`);
|
|
422
|
-
const status = {
|
|
423
|
-
installed: false,
|
|
424
|
-
hookType,
|
|
425
|
-
hasExistingHook: false,
|
|
426
|
-
wrappedOriginal: false
|
|
427
|
-
};
|
|
428
|
-
if (!existsSync(hookPath)) {
|
|
429
|
-
return status;
|
|
430
|
-
}
|
|
431
|
-
status.hasExistingHook = true;
|
|
432
|
-
if (await isWorkflowHook(hookPath)) {
|
|
433
|
-
status.installed = true;
|
|
434
|
-
status.wrappedOriginal = existsSync(originalPath);
|
|
435
|
-
}
|
|
436
|
-
return status;
|
|
437
|
-
}
|
|
438
|
-
async function getAllHooksStatus(projectPath = process.cwd()) {
|
|
439
|
-
return Promise.all([
|
|
440
|
-
getHookStatus("pre-commit", projectPath),
|
|
441
|
-
getHookStatus("commit-msg", projectPath)
|
|
442
|
-
]);
|
|
443
|
-
}
|
|
444
|
-
async function installSingleHook(hookType, config, projectPath = process.cwd()) {
|
|
445
|
-
const hooksDir = getGitHooksDir(projectPath);
|
|
446
|
-
const hookPath = join(hooksDir, hookType);
|
|
447
|
-
const originalPath = join(hooksDir, `${hookType}.original`);
|
|
448
|
-
const result = {
|
|
449
|
-
success: false,
|
|
450
|
-
hookType,
|
|
451
|
-
wrappedExisting: false
|
|
452
|
-
};
|
|
453
|
-
try {
|
|
454
|
-
if (!existsSync(hooksDir)) {
|
|
455
|
-
await mkdir(hooksDir, { recursive: true });
|
|
456
|
-
}
|
|
457
|
-
if (existsSync(hookPath)) {
|
|
458
|
-
const isOurs = await isWorkflowHook(hookPath);
|
|
459
|
-
if (!isOurs) {
|
|
460
|
-
await rename(hookPath, originalPath);
|
|
461
|
-
result.wrappedExisting = true;
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
const hookContent = hookType === "pre-commit" ? generatePreCommitHook(config) : generateCommitMsgHook(config);
|
|
465
|
-
await writeFile(hookPath, hookContent, "utf-8");
|
|
466
|
-
await chmod(hookPath, 493);
|
|
467
|
-
result.success = true;
|
|
468
|
-
} catch (error) {
|
|
469
|
-
result.error = error instanceof Error ? error.message : String(error);
|
|
470
|
-
}
|
|
471
|
-
return result;
|
|
472
|
-
}
|
|
473
|
-
async function installHooks(config, projectPath = process.cwd()) {
|
|
474
|
-
if (!hasGitRepo(projectPath)) {
|
|
475
|
-
return [
|
|
476
|
-
{
|
|
477
|
-
success: false,
|
|
478
|
-
hookType: "pre-commit",
|
|
479
|
-
wrappedExisting: false,
|
|
480
|
-
error: "No git repository found. Run git init first."
|
|
481
|
-
}
|
|
482
|
-
];
|
|
483
|
-
}
|
|
484
|
-
const results = await Promise.all([
|
|
485
|
-
installSingleHook("pre-commit", config, projectPath),
|
|
486
|
-
installSingleHook("commit-msg", config, projectPath)
|
|
487
|
-
]);
|
|
488
|
-
return results;
|
|
489
|
-
}
|
|
490
|
-
async function uninstallSingleHook(hookType, projectPath = process.cwd()) {
|
|
491
|
-
const hooksDir = getGitHooksDir(projectPath);
|
|
492
|
-
const hookPath = join(hooksDir, hookType);
|
|
493
|
-
const originalPath = join(hooksDir, `${hookType}.original`);
|
|
494
|
-
const result = {
|
|
495
|
-
success: false,
|
|
496
|
-
hookType,
|
|
497
|
-
wrappedExisting: false
|
|
498
|
-
};
|
|
499
|
-
try {
|
|
500
|
-
if (!existsSync(hookPath)) {
|
|
501
|
-
result.success = true;
|
|
502
|
-
return result;
|
|
503
|
-
}
|
|
504
|
-
const isOurs = await isWorkflowHook(hookPath);
|
|
505
|
-
if (!isOurs) {
|
|
506
|
-
result.error = "Hook is not managed by Workflow Agent";
|
|
507
|
-
return result;
|
|
508
|
-
}
|
|
509
|
-
await unlink(hookPath);
|
|
510
|
-
if (existsSync(originalPath)) {
|
|
511
|
-
await rename(originalPath, hookPath);
|
|
512
|
-
result.wrappedExisting = true;
|
|
513
|
-
}
|
|
514
|
-
result.success = true;
|
|
515
|
-
} catch (error) {
|
|
516
|
-
result.error = error instanceof Error ? error.message : String(error);
|
|
517
|
-
}
|
|
518
|
-
return result;
|
|
519
|
-
}
|
|
520
|
-
async function uninstallHooks(projectPath = process.cwd()) {
|
|
521
|
-
return Promise.all([
|
|
522
|
-
uninstallSingleHook("pre-commit", projectPath),
|
|
523
|
-
uninstallSingleHook("commit-msg", projectPath)
|
|
524
|
-
]);
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
// src/utils/git-repo.ts
|
|
528
|
-
import { execa } from "execa";
|
|
529
|
-
import { existsSync as existsSync2 } from "fs";
|
|
530
|
-
import { readFile as readFile2 } from "fs/promises";
|
|
531
|
-
import { join as join2 } from "path";
|
|
532
|
-
async function isGitRepo(projectPath = process.cwd()) {
|
|
533
|
-
try {
|
|
534
|
-
await execa("git", ["rev-parse", "--git-dir"], { cwd: projectPath });
|
|
535
|
-
return true;
|
|
536
|
-
} catch {
|
|
537
|
-
return false;
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
async function getGitRemoteUrl(projectPath = process.cwd()) {
|
|
541
|
-
try {
|
|
542
|
-
const { stdout } = await execa("git", ["remote", "get-url", "origin"], {
|
|
543
|
-
cwd: projectPath
|
|
544
|
-
});
|
|
545
|
-
return stdout.trim() || null;
|
|
546
|
-
} catch {
|
|
547
|
-
return null;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
function isGitHubRemote(remoteUrl) {
|
|
551
|
-
if (!remoteUrl) return false;
|
|
552
|
-
return remoteUrl.includes("github.com");
|
|
553
|
-
}
|
|
554
|
-
function parseGitHubUrl(remoteUrl) {
|
|
555
|
-
if (!remoteUrl || !isGitHubRemote(remoteUrl)) return null;
|
|
556
|
-
const match = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
|
|
557
|
-
if (match) {
|
|
558
|
-
return {
|
|
559
|
-
owner: match[1],
|
|
560
|
-
repo: match[2].replace(/\.git$/, "")
|
|
561
|
-
};
|
|
562
|
-
}
|
|
563
|
-
return null;
|
|
564
|
-
}
|
|
565
|
-
async function getDefaultBranch(projectPath = process.cwd()) {
|
|
566
|
-
try {
|
|
567
|
-
const { stdout } = await execa(
|
|
568
|
-
"git",
|
|
569
|
-
["symbolic-ref", "refs/remotes/origin/HEAD"],
|
|
570
|
-
{
|
|
571
|
-
cwd: projectPath
|
|
572
|
-
}
|
|
573
|
-
);
|
|
574
|
-
return stdout.trim().replace("refs/remotes/origin/", "") || "main";
|
|
575
|
-
} catch {
|
|
576
|
-
try {
|
|
577
|
-
const { stdout } = await execa(
|
|
578
|
-
"git",
|
|
579
|
-
["rev-parse", "--abbrev-ref", "HEAD"],
|
|
580
|
-
{
|
|
581
|
-
cwd: projectPath
|
|
582
|
-
}
|
|
583
|
-
);
|
|
584
|
-
return stdout.trim() || "main";
|
|
585
|
-
} catch {
|
|
586
|
-
return "main";
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
async function getRepoInfo(projectPath = process.cwd()) {
|
|
591
|
-
const isRepo = await isGitRepo(projectPath);
|
|
592
|
-
if (!isRepo) {
|
|
593
|
-
return {
|
|
594
|
-
isGitRepo: false,
|
|
595
|
-
remoteUrl: null,
|
|
596
|
-
isGitHub: false,
|
|
597
|
-
github: null,
|
|
598
|
-
defaultBranch: null
|
|
599
|
-
};
|
|
600
|
-
}
|
|
601
|
-
const remoteUrl = await getGitRemoteUrl(projectPath);
|
|
602
|
-
const isGitHub = isGitHubRemote(remoteUrl);
|
|
603
|
-
const github = parseGitHubUrl(remoteUrl);
|
|
604
|
-
const defaultBranch = await getDefaultBranch(projectPath);
|
|
605
|
-
return {
|
|
606
|
-
isGitRepo: true,
|
|
607
|
-
remoteUrl,
|
|
608
|
-
isGitHub,
|
|
609
|
-
github,
|
|
610
|
-
defaultBranch
|
|
611
|
-
};
|
|
612
|
-
}
|
|
613
|
-
async function detectPackageManager(projectPath = process.cwd()) {
|
|
614
|
-
if (existsSync2(join2(projectPath, "pnpm-lock.yaml"))) {
|
|
615
|
-
return "pnpm";
|
|
616
|
-
}
|
|
617
|
-
if (existsSync2(join2(projectPath, "yarn.lock"))) {
|
|
618
|
-
return "yarn";
|
|
619
|
-
}
|
|
620
|
-
if (existsSync2(join2(projectPath, "bun.lockb"))) {
|
|
621
|
-
return "bun";
|
|
622
|
-
}
|
|
623
|
-
if (existsSync2(join2(projectPath, "package-lock.json"))) {
|
|
624
|
-
return "npm";
|
|
625
|
-
}
|
|
626
|
-
try {
|
|
627
|
-
const pkgPath = join2(projectPath, "package.json");
|
|
628
|
-
if (existsSync2(pkgPath)) {
|
|
629
|
-
const pkg = JSON.parse(await readFile2(pkgPath, "utf-8"));
|
|
630
|
-
if (pkg.packageManager) {
|
|
631
|
-
if (pkg.packageManager.startsWith("pnpm")) return "pnpm";
|
|
632
|
-
if (pkg.packageManager.startsWith("yarn")) return "yarn";
|
|
633
|
-
if (pkg.packageManager.startsWith("bun")) return "bun";
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
} catch {
|
|
637
|
-
}
|
|
638
|
-
return "npm";
|
|
639
|
-
}
|
|
640
|
-
async function isMonorepo(projectPath = process.cwd()) {
|
|
641
|
-
if (existsSync2(join2(projectPath, "pnpm-workspace.yaml"))) {
|
|
642
|
-
return true;
|
|
643
|
-
}
|
|
644
|
-
try {
|
|
645
|
-
const pkgPath = join2(projectPath, "package.json");
|
|
646
|
-
if (existsSync2(pkgPath)) {
|
|
647
|
-
const pkg = JSON.parse(await readFile2(pkgPath, "utf-8"));
|
|
648
|
-
if (pkg.workspaces) {
|
|
649
|
-
return true;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
} catch {
|
|
653
|
-
}
|
|
654
|
-
if (existsSync2(join2(projectPath, "lerna.json"))) {
|
|
655
|
-
return true;
|
|
656
|
-
}
|
|
657
|
-
if (existsSync2(join2(projectPath, "nx.json"))) {
|
|
658
|
-
return true;
|
|
659
|
-
}
|
|
660
|
-
if (existsSync2(join2(projectPath, "turbo.json"))) {
|
|
661
|
-
return true;
|
|
662
|
-
}
|
|
663
|
-
return false;
|
|
664
|
-
}
|
|
665
|
-
async function getPackageScripts(projectPath = process.cwd()) {
|
|
666
|
-
try {
|
|
667
|
-
const pkgPath = join2(projectPath, "package.json");
|
|
668
|
-
if (!existsSync2(pkgPath)) {
|
|
669
|
-
return {};
|
|
670
|
-
}
|
|
671
|
-
const pkg = JSON.parse(await readFile2(pkgPath, "utf-8"));
|
|
672
|
-
return pkg.scripts || {};
|
|
673
|
-
} catch {
|
|
674
|
-
return {};
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
async function getProjectInfo(projectPath = process.cwd()) {
|
|
678
|
-
const packageManager = await detectPackageManager(projectPath);
|
|
679
|
-
const monorepo = await isMonorepo(projectPath);
|
|
680
|
-
const scripts = await getPackageScripts(projectPath);
|
|
681
|
-
return {
|
|
682
|
-
packageManager,
|
|
683
|
-
isMonorepo: monorepo,
|
|
684
|
-
hasLintScript: "lint" in scripts,
|
|
685
|
-
hasTypecheckScript: "typecheck" in scripts || "type-check" in scripts,
|
|
686
|
-
hasFormatScript: "format" in scripts || "format:check" in scripts,
|
|
687
|
-
hasTestScript: "test" in scripts,
|
|
688
|
-
hasBuildScript: "build" in scripts
|
|
689
|
-
};
|
|
690
|
-
}
|
|
691
|
-
function getInstallCommand(packageManager) {
|
|
692
|
-
switch (packageManager) {
|
|
693
|
-
case "pnpm":
|
|
694
|
-
return "pnpm install --frozen-lockfile";
|
|
695
|
-
case "yarn":
|
|
696
|
-
return "yarn install --frozen-lockfile";
|
|
697
|
-
case "bun":
|
|
698
|
-
return "bun install --frozen-lockfile";
|
|
699
|
-
case "npm":
|
|
700
|
-
default:
|
|
701
|
-
return "npm ci";
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
function getRunCommand(packageManager, script, isMonorepo2 = false) {
|
|
705
|
-
switch (packageManager) {
|
|
706
|
-
case "pnpm":
|
|
707
|
-
return isMonorepo2 ? `pnpm -r run ${script}` : `pnpm run ${script}`;
|
|
708
|
-
case "yarn":
|
|
709
|
-
return isMonorepo2 ? `yarn workspaces run ${script}` : `yarn run ${script}`;
|
|
710
|
-
case "bun":
|
|
711
|
-
return `bun run ${script}`;
|
|
712
|
-
case "npm":
|
|
713
|
-
default:
|
|
714
|
-
return isMonorepo2 ? `npm run ${script} --workspaces --if-present` : `npm run ${script}`;
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
// src/utils/github-actions.ts
|
|
719
|
-
import { existsSync as existsSync3 } from "fs";
|
|
720
|
-
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
721
|
-
import { join as join3 } from "path";
|
|
722
|
-
function generateCIWorkflowContent(options) {
|
|
723
|
-
const {
|
|
724
|
-
packageManager,
|
|
725
|
-
isMonorepo: isMonorepo2,
|
|
726
|
-
checks,
|
|
727
|
-
nodeVersions,
|
|
728
|
-
defaultBranch,
|
|
729
|
-
hasLintScript,
|
|
730
|
-
hasTypecheckScript,
|
|
731
|
-
hasFormatScript,
|
|
732
|
-
hasTestScript,
|
|
733
|
-
hasBuildScript
|
|
734
|
-
} = options;
|
|
735
|
-
const installCmd = getInstallCommand(packageManager);
|
|
736
|
-
const steps = [];
|
|
737
|
-
steps.push(` - name: Checkout
|
|
738
|
-
uses: actions/checkout@v4`);
|
|
739
|
-
let cacheType = packageManager;
|
|
740
|
-
if (packageManager === "bun") {
|
|
741
|
-
cacheType = "npm";
|
|
742
|
-
}
|
|
743
|
-
const useMatrix = nodeVersions.length > 1;
|
|
744
|
-
const nodeVersionValue = useMatrix ? `\${{ matrix.node-version }}` : nodeVersions[0] || "20";
|
|
745
|
-
steps.push(`
|
|
746
|
-
- name: Setup Node.js
|
|
747
|
-
uses: actions/setup-node@v4
|
|
748
|
-
with:
|
|
749
|
-
node-version: '${nodeVersionValue}'
|
|
750
|
-
cache: '${cacheType}'`);
|
|
751
|
-
if (packageManager === "pnpm") {
|
|
752
|
-
steps.push(`
|
|
753
|
-
- name: Setup pnpm
|
|
754
|
-
uses: pnpm/action-setup@v4
|
|
755
|
-
with:
|
|
756
|
-
version: 9`);
|
|
757
|
-
}
|
|
758
|
-
steps.push(`
|
|
759
|
-
- name: Install dependencies
|
|
760
|
-
run: ${installCmd}`);
|
|
761
|
-
if (checks.includes("lint")) {
|
|
762
|
-
if (hasLintScript) {
|
|
763
|
-
const lintCmd = getRunCommand(packageManager, "lint", isMonorepo2);
|
|
764
|
-
steps.push(`
|
|
765
|
-
- name: Lint
|
|
766
|
-
run: ${lintCmd}`);
|
|
767
|
-
} else {
|
|
768
|
-
steps.push(`
|
|
769
|
-
- name: Lint
|
|
770
|
-
run: echo "No lint script configured - add 'lint' to package.json scripts"`);
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
if (checks.includes("typecheck")) {
|
|
774
|
-
if (hasTypecheckScript) {
|
|
775
|
-
const typecheckCmd = getRunCommand(
|
|
776
|
-
packageManager,
|
|
777
|
-
"typecheck",
|
|
778
|
-
isMonorepo2
|
|
779
|
-
);
|
|
780
|
-
steps.push(`
|
|
781
|
-
- name: Type check
|
|
782
|
-
run: ${typecheckCmd}`);
|
|
783
|
-
} else {
|
|
784
|
-
steps.push(`
|
|
785
|
-
- name: Type check
|
|
786
|
-
run: npx tsc --noEmit`);
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
if (checks.includes("format")) {
|
|
790
|
-
if (hasFormatScript) {
|
|
791
|
-
const formatCmd = getRunCommand(
|
|
792
|
-
packageManager,
|
|
793
|
-
"format:check",
|
|
794
|
-
isMonorepo2
|
|
795
|
-
);
|
|
796
|
-
steps.push(`
|
|
797
|
-
- name: Format check
|
|
798
|
-
run: ${formatCmd} || npx prettier --check "**/*.{ts,tsx,js,jsx,json,md}"`);
|
|
799
|
-
} else {
|
|
800
|
-
steps.push(`
|
|
801
|
-
- name: Format check
|
|
802
|
-
run: npx prettier --check "**/*.{ts,tsx,js,jsx,json,md}"`);
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
if (checks.includes("build")) {
|
|
806
|
-
if (hasBuildScript) {
|
|
807
|
-
const buildCmd = getRunCommand(packageManager, "build", isMonorepo2);
|
|
808
|
-
steps.push(`
|
|
809
|
-
- name: Build
|
|
810
|
-
run: ${buildCmd}`);
|
|
811
|
-
} else {
|
|
812
|
-
steps.push(`
|
|
813
|
-
- name: Build
|
|
814
|
-
run: echo "No build script configured - add 'build' to package.json scripts"`);
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
if (checks.includes("test")) {
|
|
818
|
-
if (hasTestScript) {
|
|
819
|
-
const testCmd = getRunCommand(packageManager, "test", isMonorepo2);
|
|
820
|
-
steps.push(`
|
|
821
|
-
- name: Test
|
|
822
|
-
run: ${testCmd}`);
|
|
823
|
-
} else {
|
|
824
|
-
steps.push(`
|
|
825
|
-
- name: Test
|
|
826
|
-
run: echo "No test script configured - add 'test' to package.json scripts"`);
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
let matrixStrategy = "";
|
|
830
|
-
if (useMatrix) {
|
|
831
|
-
matrixStrategy = `
|
|
832
|
-
strategy:
|
|
833
|
-
matrix:
|
|
834
|
-
node-version: [${nodeVersions.map((v) => `'${v}'`).join(", ")}]`;
|
|
835
|
-
}
|
|
836
|
-
const workflow = `# Workflow Agent CI Pipeline
|
|
837
|
-
# Auto-generated - modifications will be preserved on regeneration
|
|
838
|
-
# To regenerate: workflow github:setup
|
|
839
|
-
|
|
840
|
-
name: CI
|
|
841
|
-
|
|
842
|
-
on:
|
|
843
|
-
push:
|
|
844
|
-
branches: [${defaultBranch}, develop]
|
|
845
|
-
pull_request:
|
|
846
|
-
branches: [${defaultBranch}, develop]
|
|
847
|
-
|
|
848
|
-
jobs:
|
|
849
|
-
ci:
|
|
850
|
-
runs-on: ubuntu-latest${matrixStrategy}
|
|
851
|
-
|
|
852
|
-
steps:
|
|
853
|
-
${steps.join("\n")}
|
|
854
|
-
`;
|
|
855
|
-
return workflow;
|
|
856
|
-
}
|
|
857
|
-
async function createCIWorkflow(options = {}) {
|
|
858
|
-
const projectPath = options.projectPath || process.cwd();
|
|
859
|
-
const workflowsDir = join3(projectPath, ".github", "workflows");
|
|
860
|
-
const workflowPath = join3(workflowsDir, "ci.yml");
|
|
861
|
-
const result = {
|
|
862
|
-
success: false,
|
|
863
|
-
filePath: workflowPath
|
|
864
|
-
};
|
|
865
|
-
try {
|
|
866
|
-
const projectInfo = await getProjectInfo(projectPath);
|
|
867
|
-
const packageManager = options.packageManager || projectInfo.packageManager;
|
|
868
|
-
const isMonorepo2 = options.isMonorepo ?? projectInfo.isMonorepo;
|
|
869
|
-
const checks = options.ciConfig?.checks || [
|
|
870
|
-
"lint",
|
|
871
|
-
"typecheck",
|
|
872
|
-
"format",
|
|
873
|
-
"build",
|
|
874
|
-
"test"
|
|
875
|
-
];
|
|
876
|
-
const nodeVersions = options.nodeVersions || ["20"];
|
|
877
|
-
const defaultBranch = options.defaultBranch || "main";
|
|
878
|
-
if (!existsSync3(workflowsDir)) {
|
|
879
|
-
await mkdir2(workflowsDir, { recursive: true });
|
|
880
|
-
}
|
|
881
|
-
const content = generateCIWorkflowContent({
|
|
882
|
-
packageManager,
|
|
883
|
-
isMonorepo: isMonorepo2,
|
|
884
|
-
checks,
|
|
885
|
-
nodeVersions,
|
|
886
|
-
defaultBranch,
|
|
887
|
-
hasLintScript: projectInfo.hasLintScript,
|
|
888
|
-
hasTypecheckScript: projectInfo.hasTypecheckScript,
|
|
889
|
-
hasFormatScript: projectInfo.hasFormatScript,
|
|
890
|
-
hasTestScript: projectInfo.hasTestScript,
|
|
891
|
-
hasBuildScript: projectInfo.hasBuildScript
|
|
892
|
-
});
|
|
893
|
-
await writeFile2(workflowPath, content, "utf-8");
|
|
894
|
-
result.success = true;
|
|
895
|
-
} catch (error) {
|
|
896
|
-
result.error = error instanceof Error ? error.message : String(error);
|
|
897
|
-
}
|
|
898
|
-
return result;
|
|
899
|
-
}
|
|
900
|
-
function hasCIWorkflow(projectPath = process.cwd()) {
|
|
901
|
-
const workflowsDir = join3(projectPath, ".github", "workflows");
|
|
902
|
-
const possibleNames = ["ci.yml", "ci.yaml", "main.yml", "main.yaml"];
|
|
903
|
-
return possibleNames.some((name) => existsSync3(join3(workflowsDir, name)));
|
|
904
|
-
}
|
|
905
|
-
|
|
906
241
|
// src/cli/commands/init.ts
|
|
907
242
|
var __filename = fileURLToPath(import.meta.url);
|
|
908
243
|
var __dirname = dirname(__filename);
|
|
909
244
|
async function initCommand(options) {
|
|
910
245
|
console.log(chalk.bold.cyan("\n\u{1F680} Workflow Agent Initialization\n"));
|
|
911
246
|
const cwd = process.cwd();
|
|
912
|
-
const isNonInteractive = !!(options.preset && options.name)
|
|
247
|
+
const isNonInteractive = !!(options.preset && options.name);
|
|
913
248
|
if (hasConfig(cwd) && !options.yes && !isNonInteractive) {
|
|
914
249
|
const shouldContinue = await p.confirm({
|
|
915
250
|
message: "Workflow Agent is already configured. Continue and overwrite?",
|
|
@@ -979,32 +314,16 @@ async function initCommand(options) {
|
|
|
979
314
|
)
|
|
980
315
|
);
|
|
981
316
|
scopes = [
|
|
982
|
-
{
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
emoji: "\u2728"
|
|
986
|
-
},
|
|
987
|
-
{ name: "fix", description: "Bug fixes and patches", emoji: "\u{1F41B}" },
|
|
988
|
-
{
|
|
989
|
-
name: "documentation",
|
|
990
|
-
description: "Documentation updates and improvements",
|
|
991
|
-
emoji: "\u{1F4DA}"
|
|
992
|
-
}
|
|
317
|
+
{ name: "feat", description: "New features", emoji: "\u2728" },
|
|
318
|
+
{ name: "fix", description: "Bug fixes", emoji: "\u{1F41B}" },
|
|
319
|
+
{ name: "docs", description: "Documentation", emoji: "\u{1F4DA}" }
|
|
993
320
|
];
|
|
994
321
|
}
|
|
995
322
|
} else {
|
|
996
323
|
scopes = [
|
|
997
|
-
{
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
emoji: "\u2728"
|
|
1001
|
-
},
|
|
1002
|
-
{ name: "fix", description: "Bug fixes and patches", emoji: "\u{1F41B}" },
|
|
1003
|
-
{
|
|
1004
|
-
name: "documentation",
|
|
1005
|
-
description: "Documentation updates and improvements",
|
|
1006
|
-
emoji: "\u{1F4DA}"
|
|
1007
|
-
}
|
|
324
|
+
{ name: "feat", description: "New features", emoji: "\u2728" },
|
|
325
|
+
{ name: "fix", description: "Bug fixes", emoji: "\u{1F41B}" },
|
|
326
|
+
{ name: "docs", description: "Documentation", emoji: "\u{1F4DA}" }
|
|
1008
327
|
];
|
|
1009
328
|
console.log(
|
|
1010
329
|
chalk.dim(
|
|
@@ -1016,169 +335,89 @@ async function initCommand(options) {
|
|
|
1016
335
|
projectName,
|
|
1017
336
|
scopes,
|
|
1018
337
|
enforcement: "strict",
|
|
1019
|
-
language: "en"
|
|
1020
|
-
hooks: {
|
|
1021
|
-
enabled: true,
|
|
1022
|
-
preCommit: ["validate-branch", "check-guidelines"],
|
|
1023
|
-
commitMsg: ["validate-commit"]
|
|
1024
|
-
},
|
|
1025
|
-
ci: {
|
|
1026
|
-
enabled: true,
|
|
1027
|
-
provider: "github",
|
|
1028
|
-
checks: ["lint", "typecheck", "format", "build", "test"]
|
|
1029
|
-
},
|
|
1030
|
-
reservedScopeNames: DEFAULT_RESERVED_SCOPE_NAMES
|
|
338
|
+
language: "en"
|
|
1031
339
|
};
|
|
1032
|
-
const configPath =
|
|
1033
|
-
await
|
|
1034
|
-
const workflowDir =
|
|
1035
|
-
if (!
|
|
1036
|
-
await
|
|
340
|
+
const configPath = join(cwd, "workflow.config.json");
|
|
341
|
+
await writeFile(configPath, JSON.stringify(config, null, 2));
|
|
342
|
+
const workflowDir = join(cwd, ".workflow");
|
|
343
|
+
if (!existsSync(workflowDir)) {
|
|
344
|
+
await mkdir(workflowDir, { recursive: true });
|
|
345
|
+
}
|
|
346
|
+
const shouldGenerateGuidelines = await p.confirm({
|
|
347
|
+
message: "Generate workflow guidelines from templates?",
|
|
348
|
+
initialValue: true
|
|
349
|
+
});
|
|
350
|
+
if (p.isCancel(shouldGenerateGuidelines)) {
|
|
351
|
+
p.cancel("Initialization cancelled");
|
|
352
|
+
process.exit(0);
|
|
1037
353
|
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
...context,
|
|
1062
|
-
reservedScopeNames: (config.reservedScopeNames || []).join(", ")
|
|
1063
|
-
});
|
|
1064
|
-
mandatoryGenerated++;
|
|
1065
|
-
} catch (error) {
|
|
1066
|
-
console.log(
|
|
1067
|
-
chalk.yellow(` \u26A0\uFE0F Could not generate ${template.filename}`)
|
|
1068
|
-
);
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
console.log(
|
|
1072
|
-
chalk.green(`\u2713 Generated ${mandatoryGenerated} mandatory guidelines`)
|
|
1073
|
-
);
|
|
1074
|
-
let shouldGenerateOptional = isNonInteractive;
|
|
1075
|
-
if (!isNonInteractive) {
|
|
1076
|
-
const response = await p.confirm({
|
|
1077
|
-
message: `Generate ${optionalTemplates.length} optional guidelines (deployment, library inventory, etc.)?`,
|
|
1078
|
-
initialValue: true
|
|
1079
|
-
});
|
|
1080
|
-
shouldGenerateOptional = !p.isCancel(response) && response;
|
|
1081
|
-
}
|
|
1082
|
-
if (shouldGenerateOptional) {
|
|
1083
|
-
for (const template of optionalTemplates) {
|
|
1084
|
-
try {
|
|
1085
|
-
const templatePath = join4(templatesDir, template.filename);
|
|
1086
|
-
const outputPath = join4(guidelinesDir, template.filename);
|
|
1087
|
-
await renderTemplateFile(templatePath, outputPath, context);
|
|
1088
|
-
optionalGenerated++;
|
|
1089
|
-
} catch {
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
354
|
+
if (shouldGenerateGuidelines) {
|
|
355
|
+
const spinner5 = p.spinner();
|
|
356
|
+
spinner5.start("Generating guidelines...");
|
|
357
|
+
try {
|
|
358
|
+
const templatesDir = join(__dirname, "../../templates");
|
|
359
|
+
await validateTemplateDirectory(templatesDir);
|
|
360
|
+
const context = await buildTemplateContext(config, cwd);
|
|
361
|
+
const guidelinesDir = join(cwd, "guidelines");
|
|
362
|
+
await mkdir(guidelinesDir, { recursive: true });
|
|
363
|
+
const renderedFiles = await renderTemplateDirectory(
|
|
364
|
+
templatesDir,
|
|
365
|
+
guidelinesDir,
|
|
366
|
+
context
|
|
367
|
+
);
|
|
368
|
+
spinner5.stop(`\u2713 Generated ${renderedFiles.length} guideline documents`);
|
|
369
|
+
} catch (error) {
|
|
370
|
+
spinner5.stop("\u26A0\uFE0F Could not generate guidelines");
|
|
371
|
+
console.log(
|
|
372
|
+
chalk.yellow(
|
|
373
|
+
`
|
|
374
|
+
Reason: ${error instanceof Error ? error.message : String(error)}`
|
|
375
|
+
)
|
|
376
|
+
);
|
|
1092
377
|
console.log(
|
|
1093
|
-
chalk.
|
|
378
|
+
chalk.dim("You can manually copy guidelines later if needed.")
|
|
1094
379
|
);
|
|
1095
380
|
}
|
|
1096
|
-
} catch (error) {
|
|
1097
|
-
console.log(
|
|
1098
|
-
chalk.yellow(
|
|
1099
|
-
`
|
|
1100
|
-
\u26A0\uFE0F Could not generate guidelines: ${error instanceof Error ? error.message : String(error)}`
|
|
1101
|
-
)
|
|
1102
|
-
);
|
|
1103
|
-
console.log(chalk.dim("You can manually copy guidelines later if needed."));
|
|
1104
381
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
const
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
preCommit: ["validate-branch", "check-guidelines"],
|
|
1121
|
-
commitMsg: ["validate-commit"]
|
|
1122
|
-
},
|
|
1123
|
-
cwd
|
|
382
|
+
const shouldAutoSetup = options.yes ? true : await p.confirm({
|
|
383
|
+
message: "Set up linting, formatting, testing, and CI automatically?",
|
|
384
|
+
initialValue: true
|
|
385
|
+
});
|
|
386
|
+
if (p.isCancel(shouldAutoSetup)) {
|
|
387
|
+
p.cancel("Initialization cancelled");
|
|
388
|
+
process.exit(0);
|
|
389
|
+
}
|
|
390
|
+
if (shouldAutoSetup) {
|
|
391
|
+
const setupSpinner = p.spinner();
|
|
392
|
+
setupSpinner.start("Running auto-setup...");
|
|
393
|
+
try {
|
|
394
|
+
const report = await generateAuditReport(cwd);
|
|
395
|
+
setupSpinner.stop(
|
|
396
|
+
`\u2713 Found ${report.totalChanges} configurations to apply`
|
|
1124
397
|
);
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
398
|
+
if (report.totalChanges > 0 || report.allDevDependencies.length > 0) {
|
|
399
|
+
const autoSetupSpinner = p.spinner();
|
|
400
|
+
await runAllSetups(cwd, (step, status) => {
|
|
401
|
+
if (status === "start") {
|
|
402
|
+
autoSetupSpinner.start(step);
|
|
403
|
+
} else if (status === "done") {
|
|
404
|
+
autoSetupSpinner.stop(`\u2713 ${step}`);
|
|
405
|
+
} else {
|
|
406
|
+
autoSetupSpinner.stop(`\u2717 ${step}`);
|
|
407
|
+
}
|
|
408
|
+
});
|
|
1130
409
|
}
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
if (repoInfo.isGitHub) {
|
|
1134
|
-
const existingCI = hasCIWorkflow(cwd);
|
|
1135
|
-
if (!existingCI) {
|
|
410
|
+
} catch (error) {
|
|
411
|
+
setupSpinner.stop("\u26A0\uFE0F Auto-setup encountered issues");
|
|
1136
412
|
console.log(
|
|
1137
|
-
chalk.
|
|
1138
|
-
|
|
413
|
+
chalk.yellow(
|
|
414
|
+
`
|
|
415
|
+
Reason: ${error instanceof Error ? error.message : String(error)}`
|
|
1139
416
|
)
|
|
1140
417
|
);
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
isMonorepo: projectInfo.isMonorepo,
|
|
1145
|
-
ciConfig: config.ci || {
|
|
1146
|
-
enabled: true,
|
|
1147
|
-
provider: "github",
|
|
1148
|
-
checks: ["lint", "typecheck", "format", "build", "test"]
|
|
1149
|
-
},
|
|
1150
|
-
defaultBranch: repoInfo.defaultBranch || "main"
|
|
1151
|
-
});
|
|
1152
|
-
if (ciResult.success) {
|
|
1153
|
-
console.log(chalk.green("\u2713 Created GitHub Actions CI workflow"));
|
|
1154
|
-
console.log(chalk.dim(` File: .github/workflows/ci.yml`));
|
|
1155
|
-
} else {
|
|
1156
|
-
console.log(
|
|
1157
|
-
chalk.yellow(`\u26A0\uFE0F Could not create CI workflow: ${ciResult.error}`)
|
|
1158
|
-
);
|
|
1159
|
-
}
|
|
1160
|
-
} else {
|
|
1161
|
-
console.log(chalk.dim("\n\u2713 GitHub Actions CI workflow already exists"));
|
|
1162
|
-
}
|
|
1163
|
-
} else if (repoInfo.isGitRepo) {
|
|
1164
|
-
let shouldSetupCI = false;
|
|
1165
|
-
if (!isNonInteractive) {
|
|
1166
|
-
const response = await p.confirm({
|
|
1167
|
-
message: "No GitHub remote detected. Create CI workflow anyway?",
|
|
1168
|
-
initialValue: false
|
|
1169
|
-
});
|
|
1170
|
-
shouldSetupCI = !p.isCancel(response) && response;
|
|
1171
|
-
}
|
|
1172
|
-
if (shouldSetupCI) {
|
|
1173
|
-
const ciResult = await createCIWorkflow({
|
|
1174
|
-
projectPath: cwd,
|
|
1175
|
-
packageManager: projectInfo.packageManager,
|
|
1176
|
-
isMonorepo: projectInfo.isMonorepo,
|
|
1177
|
-
defaultBranch: repoInfo.defaultBranch || "main"
|
|
1178
|
-
});
|
|
1179
|
-
if (ciResult.success) {
|
|
1180
|
-
console.log(chalk.green("\u2713 Created CI workflow"));
|
|
1181
|
-
}
|
|
418
|
+
console.log(
|
|
419
|
+
chalk.dim("You can run 'workflow auto-setup' later to retry.")
|
|
420
|
+
);
|
|
1182
421
|
}
|
|
1183
422
|
}
|
|
1184
423
|
p.outro(chalk.green("\u2713 Workflow Agent initialized successfully!"));
|
|
@@ -1186,24 +425,25 @@ async function initCommand(options) {
|
|
|
1186
425
|
console.log(
|
|
1187
426
|
chalk.dim(" 1. Review your configuration in workflow.config.json")
|
|
1188
427
|
);
|
|
1189
|
-
|
|
1190
|
-
chalk.dim(" 2. Review generated guidelines in guidelines/ directory")
|
|
1191
|
-
);
|
|
1192
|
-
console.log(chalk.dim(" 3. Run: workflow validate branch"));
|
|
1193
|
-
console.log(chalk.dim(" 4. Run: workflow doctor (for health check)\n"));
|
|
1194
|
-
if (repoInfo.isGitHub) {
|
|
428
|
+
if (shouldGenerateGuidelines) {
|
|
1195
429
|
console.log(
|
|
1196
|
-
chalk.
|
|
430
|
+
chalk.dim(" 2. Review generated guidelines in guidelines/ directory")
|
|
1197
431
|
);
|
|
432
|
+
console.log(chalk.dim(" 3. Run: workflow verify (to check everything)"));
|
|
1198
433
|
console.log(
|
|
1199
|
-
chalk.dim("
|
|
434
|
+
chalk.dim(" 4. Run: workflow doctor (for optimization suggestions)\n")
|
|
435
|
+
);
|
|
436
|
+
} else {
|
|
437
|
+
console.log(chalk.dim(" 2. Run: workflow verify (to check everything)"));
|
|
438
|
+
console.log(
|
|
439
|
+
chalk.dim(" 3. Run: workflow doctor (for optimization suggestions)\n")
|
|
1200
440
|
);
|
|
1201
441
|
}
|
|
1202
442
|
}
|
|
1203
443
|
|
|
1204
444
|
// src/cli/commands/validate.ts
|
|
1205
445
|
import chalk2 from "chalk";
|
|
1206
|
-
import { execa
|
|
446
|
+
import { execa } from "execa";
|
|
1207
447
|
async function validateCommand(type, value, _options = {}) {
|
|
1208
448
|
const config = await loadConfig();
|
|
1209
449
|
if (!config) {
|
|
@@ -1218,7 +458,7 @@ async function validateCommand(type, value, _options = {}) {
|
|
|
1218
458
|
switch (type) {
|
|
1219
459
|
case "branch": {
|
|
1220
460
|
if (!targetValue) {
|
|
1221
|
-
const { stdout } = await
|
|
461
|
+
const { stdout } = await execa("git", ["branch", "--show-current"]);
|
|
1222
462
|
targetValue = stdout.trim();
|
|
1223
463
|
}
|
|
1224
464
|
result = await validateBranchName(targetValue, config);
|
|
@@ -1226,7 +466,7 @@ async function validateCommand(type, value, _options = {}) {
|
|
|
1226
466
|
}
|
|
1227
467
|
case "commit": {
|
|
1228
468
|
if (!targetValue) {
|
|
1229
|
-
const { stdout } = await
|
|
469
|
+
const { stdout } = await execa("git", ["log", "-1", "--pretty=%s"]);
|
|
1230
470
|
targetValue = stdout.trim();
|
|
1231
471
|
}
|
|
1232
472
|
result = await validateCommitMessage(targetValue, config);
|
|
@@ -1275,324 +515,10 @@ async function validateCommand(type, value, _options = {}) {
|
|
|
1275
515
|
}
|
|
1276
516
|
|
|
1277
517
|
// src/cli/commands/config.ts
|
|
1278
|
-
import { Command } from "commander";
|
|
1279
|
-
import { join as join5 } from "path";
|
|
1280
|
-
import { existsSync as existsSync5, writeFileSync } from "fs";
|
|
1281
|
-
import prompts from "prompts";
|
|
1282
518
|
import chalk3 from "chalk";
|
|
1283
|
-
function
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
const opts = command.opts();
|
|
1287
|
-
await validateConfigAction(opts);
|
|
1288
|
-
});
|
|
1289
|
-
command.command("add").description("Add configuration items").argument("<type>", "Type to add (scope)").action(async (type) => {
|
|
1290
|
-
const opts = command.opts();
|
|
1291
|
-
if (type === "scope") {
|
|
1292
|
-
await addScopeAction(opts);
|
|
1293
|
-
} else {
|
|
1294
|
-
console.error(
|
|
1295
|
-
chalk3.red(
|
|
1296
|
-
`Unknown type: ${type}. Currently only "scope" is supported.`
|
|
1297
|
-
)
|
|
1298
|
-
);
|
|
1299
|
-
process.exit(1);
|
|
1300
|
-
}
|
|
1301
|
-
});
|
|
1302
|
-
command.command("remove").description("Remove configuration items").argument("<type>", "Type to remove (scope)").argument("<name>", "Name of the item to remove").action(async (type, name) => {
|
|
1303
|
-
const opts = command.opts();
|
|
1304
|
-
if (type === "scope") {
|
|
1305
|
-
await removeScopeAction(name, opts);
|
|
1306
|
-
} else {
|
|
1307
|
-
console.error(
|
|
1308
|
-
chalk3.red(
|
|
1309
|
-
`Unknown type: ${type}. Currently only "scope" is supported.`
|
|
1310
|
-
)
|
|
1311
|
-
);
|
|
1312
|
-
process.exit(1);
|
|
1313
|
-
}
|
|
1314
|
-
});
|
|
1315
|
-
command.command("list").description("List configuration items").argument("[type]", "Type to list (scopes, reserved, all)", "all").action(async (type) => {
|
|
1316
|
-
const opts = command.opts();
|
|
1317
|
-
await listConfigAction(type, opts);
|
|
1318
|
-
});
|
|
1319
|
-
command.command("get").description("Get a configuration value").argument("<path>", "Configuration path (e.g., scopes[0].name)").action(async (path2) => {
|
|
1320
|
-
const opts = command.opts();
|
|
1321
|
-
await getConfigValue(path2, opts);
|
|
1322
|
-
});
|
|
1323
|
-
command.command("set").description("Set a configuration value").argument("<path>", "Configuration path (e.g., reservedScopeNames)").argument("<value>", "Value to set").action(async (path2, value) => {
|
|
1324
|
-
const opts = command.opts();
|
|
1325
|
-
await setConfigValue(path2, value, opts);
|
|
1326
|
-
});
|
|
1327
|
-
return command;
|
|
1328
|
-
}
|
|
1329
|
-
async function validateConfigAction(opts) {
|
|
1330
|
-
console.log(chalk3.blue("\u{1F50D} Validating workflow configuration..."));
|
|
1331
|
-
const result = await validateConfig(opts.cwd || process.cwd());
|
|
1332
|
-
if (result.valid) {
|
|
1333
|
-
console.log(chalk3.green("\u2713 Configuration is valid"));
|
|
1334
|
-
process.exit(0);
|
|
1335
|
-
} else {
|
|
1336
|
-
console.log(chalk3.red("\u2717 Configuration has errors:\n"));
|
|
1337
|
-
result.errors.forEach((err) => {
|
|
1338
|
-
console.log(chalk3.red(` \u2022 ${err}`));
|
|
1339
|
-
});
|
|
1340
|
-
if (result.warnings.length > 0) {
|
|
1341
|
-
console.log(chalk3.yellow("\n\u26A0 Warnings:\n"));
|
|
1342
|
-
result.warnings.forEach((warn) => {
|
|
1343
|
-
console.log(chalk3.yellow(` \u2022 ${warn}`));
|
|
1344
|
-
});
|
|
1345
|
-
}
|
|
1346
|
-
console.log(chalk3.gray("\n\u{1F4A1} Fix these issues in workflow.config.json"));
|
|
1347
|
-
process.exit(1);
|
|
1348
|
-
}
|
|
1349
|
-
}
|
|
1350
|
-
async function addScopeAction(opts) {
|
|
1351
|
-
const cwd = opts.cwd || process.cwd();
|
|
1352
|
-
const configPath = join5(cwd, "workflow.config.json");
|
|
1353
|
-
if (!existsSync5(configPath)) {
|
|
1354
|
-
console.error(
|
|
1355
|
-
chalk3.red("No workflow.config.json found. Run: workflow init")
|
|
1356
|
-
);
|
|
1357
|
-
process.exit(1);
|
|
1358
|
-
}
|
|
1359
|
-
const config = await loadConfig(cwd);
|
|
1360
|
-
if (!config) {
|
|
1361
|
-
console.error(chalk3.red("Failed to load configuration"));
|
|
1362
|
-
process.exit(1);
|
|
1363
|
-
}
|
|
1364
|
-
const reservedNames = config.reservedScopeNames || DEFAULT_RESERVED_SCOPE_NAMES;
|
|
1365
|
-
const existingNames = config.scopes.map((s) => s.name);
|
|
1366
|
-
const response = await prompts([
|
|
1367
|
-
{
|
|
1368
|
-
type: "text",
|
|
1369
|
-
name: "name",
|
|
1370
|
-
message: "Scope name:",
|
|
1371
|
-
validate: (value) => {
|
|
1372
|
-
if (!value) return "Name is required";
|
|
1373
|
-
if (existingNames.includes(value))
|
|
1374
|
-
return `Scope "${value}" already exists`;
|
|
1375
|
-
const validation = validateScopeName(value, reservedNames);
|
|
1376
|
-
if (!validation.valid) {
|
|
1377
|
-
return validation.error + (validation.suggestion ? ` Try: ${validation.suggestion}` : "");
|
|
1378
|
-
}
|
|
1379
|
-
return true;
|
|
1380
|
-
}
|
|
1381
|
-
},
|
|
1382
|
-
{
|
|
1383
|
-
type: "text",
|
|
1384
|
-
name: "description",
|
|
1385
|
-
message: "Description:",
|
|
1386
|
-
validate: (value) => value ? true : "Description is required"
|
|
1387
|
-
},
|
|
1388
|
-
{
|
|
1389
|
-
type: "multiselect",
|
|
1390
|
-
name: "allowedTypes",
|
|
1391
|
-
message: "Allowed commit types (space to select, enter to continue):",
|
|
1392
|
-
choices: [
|
|
1393
|
-
{ title: "feat", value: "feat", selected: true },
|
|
1394
|
-
{ title: "fix", value: "fix", selected: true },
|
|
1395
|
-
{ title: "docs", value: "docs", selected: false },
|
|
1396
|
-
{ title: "style", value: "style", selected: false },
|
|
1397
|
-
{ title: "refactor", value: "refactor", selected: false },
|
|
1398
|
-
{ title: "perf", value: "perf", selected: false },
|
|
1399
|
-
{ title: "test", value: "test", selected: false },
|
|
1400
|
-
{ title: "build", value: "build", selected: false },
|
|
1401
|
-
{ title: "ci", value: "ci", selected: false },
|
|
1402
|
-
{ title: "chore", value: "chore", selected: false },
|
|
1403
|
-
{ title: "revert", value: "revert", selected: false }
|
|
1404
|
-
],
|
|
1405
|
-
min: 1
|
|
1406
|
-
},
|
|
1407
|
-
{
|
|
1408
|
-
type: "text",
|
|
1409
|
-
name: "mandatoryGuidelines",
|
|
1410
|
-
message: "Mandatory guidelines (comma-separated, or press enter to skip):",
|
|
1411
|
-
initial: ""
|
|
1412
|
-
}
|
|
1413
|
-
]);
|
|
1414
|
-
if (!response.name) {
|
|
1415
|
-
console.log(chalk3.yellow("Cancelled"));
|
|
1416
|
-
process.exit(0);
|
|
1417
|
-
}
|
|
1418
|
-
if (!opts.force) {
|
|
1419
|
-
const validation = validateScopeName(response.name, reservedNames);
|
|
1420
|
-
if (!validation.valid) {
|
|
1421
|
-
console.error(chalk3.red(`
|
|
1422
|
-
\u2717 ${validation.error}`));
|
|
1423
|
-
if (validation.suggestion) {
|
|
1424
|
-
console.log(chalk3.yellow(`\u{1F4A1} Suggestion: ${validation.suggestion}`));
|
|
1425
|
-
}
|
|
1426
|
-
console.log(chalk3.gray("\nUse --force to override this check"));
|
|
1427
|
-
process.exit(1);
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
const newScope = {
|
|
1431
|
-
name: response.name,
|
|
1432
|
-
description: response.description,
|
|
1433
|
-
allowedTypes: response.allowedTypes
|
|
1434
|
-
};
|
|
1435
|
-
if (response.mandatoryGuidelines) {
|
|
1436
|
-
const guidelines = response.mandatoryGuidelines.split(",").map((g) => g.trim()).filter((g) => g.length > 0);
|
|
1437
|
-
if (guidelines.length > 0) {
|
|
1438
|
-
newScope.mandatoryGuidelines = guidelines;
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
config.scopes.push(newScope);
|
|
1442
|
-
const configContent = JSON.stringify(config, null, 2) + "\n";
|
|
1443
|
-
writeFileSync(configPath, configContent, "utf-8");
|
|
1444
|
-
console.log(chalk3.green(`
|
|
1445
|
-
\u2713 Added scope: ${response.name}`));
|
|
1446
|
-
console.log(chalk3.gray(` Description: ${response.description}`));
|
|
1447
|
-
console.log(chalk3.gray(` Types: ${response.allowedTypes.join(", ")}`));
|
|
1448
|
-
if (newScope.mandatoryGuidelines) {
|
|
1449
|
-
console.log(
|
|
1450
|
-
chalk3.gray(` Guidelines: ${newScope.mandatoryGuidelines.join(", ")}`)
|
|
1451
|
-
);
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1454
|
-
async function removeScopeAction(name, opts) {
|
|
1455
|
-
const cwd = opts.cwd || process.cwd();
|
|
1456
|
-
const configPath = join5(cwd, "workflow.config.json");
|
|
1457
|
-
if (!existsSync5(configPath)) {
|
|
1458
|
-
console.error(chalk3.red("No workflow.config.json found"));
|
|
1459
|
-
process.exit(1);
|
|
1460
|
-
}
|
|
1461
|
-
const config = await loadConfig(cwd);
|
|
1462
|
-
if (!config) {
|
|
1463
|
-
console.error(chalk3.red("Failed to load configuration"));
|
|
1464
|
-
process.exit(1);
|
|
1465
|
-
}
|
|
1466
|
-
const scopeIndex = config.scopes.findIndex((s) => s.name === name);
|
|
1467
|
-
if (scopeIndex === -1) {
|
|
1468
|
-
console.error(chalk3.red(`Scope "${name}" not found`));
|
|
1469
|
-
process.exit(1);
|
|
1470
|
-
}
|
|
1471
|
-
const response = await prompts({
|
|
1472
|
-
type: "confirm",
|
|
1473
|
-
name: "confirmed",
|
|
1474
|
-
message: `Remove scope "${name}"?`,
|
|
1475
|
-
initial: false
|
|
1476
|
-
});
|
|
1477
|
-
if (!response.confirmed) {
|
|
1478
|
-
console.log(chalk3.yellow("Cancelled"));
|
|
1479
|
-
process.exit(0);
|
|
1480
|
-
}
|
|
1481
|
-
config.scopes.splice(scopeIndex, 1);
|
|
1482
|
-
const configContent = JSON.stringify(config, null, 2) + "\n";
|
|
1483
|
-
writeFileSync(configPath, configContent, "utf-8");
|
|
1484
|
-
console.log(chalk3.green(`\u2713 Removed scope: ${name}`));
|
|
1485
|
-
}
|
|
1486
|
-
async function listConfigAction(type, opts) {
|
|
1487
|
-
const cwd = opts.cwd || process.cwd();
|
|
1488
|
-
const config = await loadConfig(cwd);
|
|
1489
|
-
if (!config) {
|
|
1490
|
-
console.error(chalk3.red("No configuration found"));
|
|
1491
|
-
process.exit(1);
|
|
1492
|
-
}
|
|
1493
|
-
if (type === "scopes" || type === "all") {
|
|
1494
|
-
console.log(chalk3.blue("\n\u{1F4CB} Scopes:"));
|
|
1495
|
-
if (config.scopes.length === 0) {
|
|
1496
|
-
console.log(chalk3.gray(" (none)"));
|
|
1497
|
-
} else {
|
|
1498
|
-
config.scopes.forEach((scope, index) => {
|
|
1499
|
-
console.log(chalk3.green(`
|
|
1500
|
-
${index + 1}. ${scope.name}`));
|
|
1501
|
-
console.log(chalk3.gray(` ${scope.description}`));
|
|
1502
|
-
console.log(
|
|
1503
|
-
chalk3.gray(` Types: ${scope.allowedTypes?.join(", ") || "all"}`)
|
|
1504
|
-
);
|
|
1505
|
-
if (scope.mandatoryGuidelines && scope.mandatoryGuidelines.length > 0) {
|
|
1506
|
-
console.log(
|
|
1507
|
-
chalk3.gray(
|
|
1508
|
-
` Guidelines: ${scope.mandatoryGuidelines.join(", ")}`
|
|
1509
|
-
)
|
|
1510
|
-
);
|
|
1511
|
-
}
|
|
1512
|
-
});
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
|
-
if (type === "reserved" || type === "all") {
|
|
1516
|
-
const reserved = config.reservedScopeNames || DEFAULT_RESERVED_SCOPE_NAMES;
|
|
1517
|
-
console.log(chalk3.blue("\n\u{1F6AB} Reserved Scope Names:"));
|
|
1518
|
-
console.log(chalk3.gray(` ${reserved.join(", ")}`));
|
|
1519
|
-
console.log(
|
|
1520
|
-
chalk3.gray(
|
|
1521
|
-
'\n \u{1F4A1} Configure in workflow.config.json: "reservedScopeNames"'
|
|
1522
|
-
)
|
|
1523
|
-
);
|
|
1524
|
-
}
|
|
1525
|
-
}
|
|
1526
|
-
async function getConfigValue(path2, opts) {
|
|
1527
|
-
const cwd = opts.cwd || process.cwd();
|
|
1528
|
-
const config = await loadConfig(cwd);
|
|
1529
|
-
if (!config) {
|
|
1530
|
-
console.error(chalk3.red("No configuration found"));
|
|
1531
|
-
process.exit(1);
|
|
1532
|
-
}
|
|
1533
|
-
const value = resolvePath(config, path2);
|
|
1534
|
-
if (value === void 0) {
|
|
1535
|
-
console.error(chalk3.red(`Path not found: ${path2}`));
|
|
1536
|
-
process.exit(1);
|
|
1537
|
-
}
|
|
1538
|
-
console.log(JSON.stringify(value, null, 2));
|
|
1539
|
-
}
|
|
1540
|
-
async function setConfigValue(path2, value, opts) {
|
|
1541
|
-
const cwd = opts.cwd || process.cwd();
|
|
1542
|
-
const configPath = join5(cwd, "workflow.config.json");
|
|
1543
|
-
if (!existsSync5(configPath)) {
|
|
1544
|
-
console.error(chalk3.red("No workflow.config.json found"));
|
|
1545
|
-
process.exit(1);
|
|
1546
|
-
}
|
|
1547
|
-
const config = await loadConfig(cwd);
|
|
1548
|
-
if (!config) {
|
|
1549
|
-
console.error(chalk3.red("Failed to load configuration"));
|
|
1550
|
-
process.exit(1);
|
|
1551
|
-
}
|
|
1552
|
-
let parsedValue;
|
|
1553
|
-
try {
|
|
1554
|
-
parsedValue = JSON.parse(value);
|
|
1555
|
-
} catch {
|
|
1556
|
-
parsedValue = value;
|
|
1557
|
-
}
|
|
1558
|
-
setPath(config, path2, parsedValue);
|
|
1559
|
-
if (!opts.force) {
|
|
1560
|
-
const validation = await validateConfig(cwd);
|
|
1561
|
-
if (!validation.valid) {
|
|
1562
|
-
console.error(chalk3.red("\u2717 Invalid configuration after change:"));
|
|
1563
|
-
validation.errors.forEach(
|
|
1564
|
-
(err) => console.error(chalk3.red(` \u2022 ${err}`))
|
|
1565
|
-
);
|
|
1566
|
-
console.log(chalk3.gray("\nUse --force to skip validation"));
|
|
1567
|
-
process.exit(1);
|
|
1568
|
-
}
|
|
1569
|
-
}
|
|
1570
|
-
const configContent = JSON.stringify(config, null, 2) + "\n";
|
|
1571
|
-
writeFileSync(configPath, configContent, "utf-8");
|
|
1572
|
-
console.log(chalk3.green(`\u2713 Set ${path2} = ${JSON.stringify(parsedValue)}`));
|
|
1573
|
-
}
|
|
1574
|
-
function resolvePath(obj, path2) {
|
|
1575
|
-
const parts = path2.split(/[\.\[\]]+/).filter(Boolean);
|
|
1576
|
-
let current = obj;
|
|
1577
|
-
for (const part of parts) {
|
|
1578
|
-
if (current === void 0 || current === null) {
|
|
1579
|
-
return void 0;
|
|
1580
|
-
}
|
|
1581
|
-
current = current[part];
|
|
1582
|
-
}
|
|
1583
|
-
return current;
|
|
1584
|
-
}
|
|
1585
|
-
function setPath(obj, path2, value) {
|
|
1586
|
-
const parts = path2.split(/[\.\[\]]+/).filter(Boolean);
|
|
1587
|
-
let current = obj;
|
|
1588
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
1589
|
-
const part = parts[i];
|
|
1590
|
-
if (current[part] === void 0) {
|
|
1591
|
-
current[part] = {};
|
|
1592
|
-
}
|
|
1593
|
-
current = current[part];
|
|
1594
|
-
}
|
|
1595
|
-
current[parts[parts.length - 1]] = value;
|
|
519
|
+
async function configCommand(action, key, value) {
|
|
520
|
+
console.log(chalk3.yellow("Config command not yet implemented"));
|
|
521
|
+
console.log({ action, key, value });
|
|
1596
522
|
}
|
|
1597
523
|
|
|
1598
524
|
// src/cli/commands/suggest.ts
|
|
@@ -1638,349 +564,32 @@ async function suggestCommand(feedback, options = {}) {
|
|
|
1638
564
|
);
|
|
1639
565
|
}
|
|
1640
566
|
|
|
1641
|
-
// src/cli/commands/doctor.ts
|
|
1642
|
-
import chalk5 from "chalk";
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
const coreMandatory = getMandatoryTemplateFilenames();
|
|
1650
|
-
if (!guidelinesConfig) {
|
|
1651
|
-
return coreMandatory;
|
|
1652
|
-
}
|
|
1653
|
-
let mandatory = [...coreMandatory];
|
|
1654
|
-
if (guidelinesConfig.additionalMandatory) {
|
|
1655
|
-
for (const template of guidelinesConfig.additionalMandatory) {
|
|
1656
|
-
if (!mandatory.includes(template) && templateMetadata[template]) {
|
|
1657
|
-
mandatory.push(template);
|
|
1658
|
-
}
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
1661
|
-
if (guidelinesConfig.optionalOverrides) {
|
|
1662
|
-
mandatory = mandatory.filter(
|
|
1663
|
-
(t) => !guidelinesConfig.optionalOverrides.includes(t)
|
|
1664
|
-
);
|
|
1665
|
-
}
|
|
1666
|
-
return mandatory;
|
|
1667
|
-
}
|
|
1668
|
-
async function validateGuidelinesExist(projectPath = process.cwd(), config) {
|
|
1669
|
-
const guidelinesDir = join6(projectPath, "guidelines");
|
|
1670
|
-
const result = {
|
|
1671
|
-
valid: true,
|
|
1672
|
-
missingMandatory: [],
|
|
1673
|
-
presentMandatory: [],
|
|
1674
|
-
presentOptional: [],
|
|
1675
|
-
errors: []
|
|
1676
|
-
};
|
|
1677
|
-
if (!existsSync6(guidelinesDir)) {
|
|
1678
|
-
const mandatory2 = getEffectiveMandatoryTemplates(config?.guidelines);
|
|
1679
|
-
result.valid = false;
|
|
1680
|
-
result.missingMandatory = mandatory2;
|
|
1681
|
-
result.errors.push(
|
|
1682
|
-
"Guidelines directory does not exist. Run: workflow init"
|
|
1683
|
-
);
|
|
1684
|
-
return result;
|
|
1685
|
-
}
|
|
1686
|
-
let existingFiles = [];
|
|
1687
|
-
try {
|
|
1688
|
-
existingFiles = await readdir(guidelinesDir);
|
|
1689
|
-
} catch (error) {
|
|
1690
|
-
result.valid = false;
|
|
1691
|
-
result.errors.push(
|
|
1692
|
-
`Cannot read guidelines directory: ${error instanceof Error ? error.message : String(error)}`
|
|
1693
|
-
);
|
|
1694
|
-
return result;
|
|
1695
|
-
}
|
|
1696
|
-
const mandatory = getEffectiveMandatoryTemplates(config?.guidelines);
|
|
1697
|
-
for (const template of mandatory) {
|
|
1698
|
-
if (existingFiles.includes(template)) {
|
|
1699
|
-
result.presentMandatory.push(template);
|
|
1700
|
-
} else {
|
|
1701
|
-
result.missingMandatory.push(template);
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
const allTemplateNames = Object.keys(templateMetadata);
|
|
1705
|
-
for (const file of existingFiles) {
|
|
1706
|
-
if (allTemplateNames.includes(file) && !mandatory.includes(file)) {
|
|
1707
|
-
result.presentOptional.push(file);
|
|
1708
|
-
}
|
|
1709
|
-
}
|
|
1710
|
-
if (result.missingMandatory.length > 0) {
|
|
1711
|
-
result.valid = false;
|
|
1712
|
-
result.errors.push(
|
|
1713
|
-
`Missing mandatory guidelines: ${result.missingMandatory.join(", ")}. Run: workflow init`
|
|
1714
|
-
);
|
|
1715
|
-
}
|
|
1716
|
-
return result;
|
|
1717
|
-
}
|
|
1718
|
-
async function validateGitHubActionsSetup(projectPath = process.cwd()) {
|
|
1719
|
-
const workflowsDir = join6(projectPath, ".github", "workflows");
|
|
1720
|
-
const result = {
|
|
1721
|
-
valid: true,
|
|
1722
|
-
hasWorkflowFile: false,
|
|
1723
|
-
hasLintCheck: false,
|
|
1724
|
-
hasTypecheckCheck: false,
|
|
1725
|
-
hasFormatCheck: false,
|
|
1726
|
-
hasBuildCheck: false,
|
|
1727
|
-
hasTestCheck: false,
|
|
1728
|
-
errors: [],
|
|
1729
|
-
warnings: []
|
|
1730
|
-
};
|
|
1731
|
-
if (!existsSync6(workflowsDir)) {
|
|
1732
|
-
result.valid = false;
|
|
1733
|
-
result.errors.push(
|
|
1734
|
-
"GitHub Actions workflows directory does not exist. Run: workflow github:setup"
|
|
1735
|
-
);
|
|
1736
|
-
return result;
|
|
1737
|
-
}
|
|
1738
|
-
let workflowFiles = [];
|
|
1739
|
-
try {
|
|
1740
|
-
workflowFiles = await readdir(workflowsDir);
|
|
1741
|
-
} catch (error) {
|
|
1742
|
-
result.valid = false;
|
|
1743
|
-
result.errors.push(
|
|
1744
|
-
`Cannot read workflows directory: ${error instanceof Error ? error.message : String(error)}`
|
|
1745
|
-
);
|
|
1746
|
-
return result;
|
|
1747
|
-
}
|
|
1748
|
-
const ciWorkflowNames = [
|
|
1749
|
-
"ci.yml",
|
|
1750
|
-
"ci.yaml",
|
|
1751
|
-
"main.yml",
|
|
1752
|
-
"main.yaml",
|
|
1753
|
-
"build.yml",
|
|
1754
|
-
"build.yaml",
|
|
1755
|
-
"test.yml",
|
|
1756
|
-
"test.yaml"
|
|
1757
|
-
];
|
|
1758
|
-
const ciWorkflows = workflowFiles.filter(
|
|
1759
|
-
(f) => ciWorkflowNames.includes(f.toLowerCase())
|
|
1760
|
-
);
|
|
1761
|
-
if (ciWorkflows.length === 0) {
|
|
1762
|
-
result.valid = false;
|
|
1763
|
-
result.errors.push("No CI workflow file found. Run: workflow github:setup");
|
|
1764
|
-
return result;
|
|
1765
|
-
}
|
|
1766
|
-
result.hasWorkflowFile = true;
|
|
1767
|
-
const workflowPath = join6(workflowsDir, ciWorkflows[0]);
|
|
1768
|
-
let workflowContent = "";
|
|
1769
|
-
try {
|
|
1770
|
-
workflowContent = await readFile3(workflowPath, "utf-8");
|
|
1771
|
-
} catch (error) {
|
|
1772
|
-
result.warnings.push(
|
|
1773
|
-
`Cannot read workflow file: ${error instanceof Error ? error.message : String(error)}`
|
|
1774
|
-
);
|
|
1775
|
-
return result;
|
|
1776
|
-
}
|
|
1777
|
-
const contentLower = workflowContent.toLowerCase();
|
|
1778
|
-
if (contentLower.includes("lint") || contentLower.includes("eslint")) {
|
|
1779
|
-
result.hasLintCheck = true;
|
|
1780
|
-
} else {
|
|
1781
|
-
result.warnings.push("CI workflow may be missing lint check");
|
|
1782
|
-
}
|
|
1783
|
-
if (contentLower.includes("typecheck") || contentLower.includes("type-check") || contentLower.includes("tsc")) {
|
|
1784
|
-
result.hasTypecheckCheck = true;
|
|
1785
|
-
} else {
|
|
1786
|
-
result.warnings.push("CI workflow may be missing typecheck");
|
|
1787
|
-
}
|
|
1788
|
-
if (contentLower.includes("format") || contentLower.includes("prettier")) {
|
|
1789
|
-
result.hasFormatCheck = true;
|
|
1790
|
-
} else {
|
|
1791
|
-
result.warnings.push("CI workflow may be missing format check");
|
|
1792
|
-
}
|
|
1793
|
-
if (contentLower.includes("build")) {
|
|
1794
|
-
result.hasBuildCheck = true;
|
|
1795
|
-
} else {
|
|
1796
|
-
result.warnings.push("CI workflow may be missing build step");
|
|
1797
|
-
}
|
|
1798
|
-
if (contentLower.includes("test") && !contentLower.includes("typecheck")) {
|
|
1799
|
-
result.hasTestCheck = true;
|
|
1800
|
-
} else {
|
|
1801
|
-
result.warnings.push("CI workflow may be missing test step");
|
|
1802
|
-
}
|
|
1803
|
-
const mandatoryChecks = [
|
|
1804
|
-
result.hasLintCheck,
|
|
1805
|
-
result.hasTypecheckCheck,
|
|
1806
|
-
result.hasFormatCheck
|
|
1807
|
-
];
|
|
1808
|
-
if (!mandatoryChecks.every(Boolean)) {
|
|
1809
|
-
result.valid = false;
|
|
1810
|
-
result.errors.push(
|
|
1811
|
-
"CI workflow is missing mandatory checks (lint, typecheck, format)"
|
|
1812
|
-
);
|
|
1813
|
-
}
|
|
1814
|
-
return result;
|
|
1815
|
-
}
|
|
1816
|
-
|
|
1817
|
-
// src/cli/commands/doctor.ts
|
|
1818
|
-
async function doctorCommand(options = {}) {
|
|
1819
|
-
const cwd = process.cwd();
|
|
1820
|
-
if (options.checkGuidelinesOnly) {
|
|
1821
|
-
const result = await validateGuidelinesExist(cwd);
|
|
1822
|
-
if (!result.valid) {
|
|
1823
|
-
console.error(chalk5.red("\u2717 Missing mandatory guidelines"));
|
|
1824
|
-
for (const missing of result.missingMandatory) {
|
|
1825
|
-
console.error(chalk5.red(` \u2022 ${missing}`));
|
|
1826
|
-
}
|
|
1827
|
-
process.exit(1);
|
|
1828
|
-
}
|
|
1829
|
-
process.exit(0);
|
|
1830
|
-
}
|
|
1831
|
-
console.log(chalk5.bold.cyan("\n\u{1F3E5} Workflow Agent Health Check\n"));
|
|
1832
|
-
const config = await loadConfig();
|
|
1833
|
-
let hasErrors = false;
|
|
1834
|
-
let hasWarnings = false;
|
|
1835
|
-
console.log(chalk5.bold("\u{1F4CB} Configuration"));
|
|
1836
|
-
if (!config) {
|
|
1837
|
-
console.error(chalk5.red(" \u2717 No workflow configuration found"));
|
|
1838
|
-
console.log(chalk5.yellow(" Run: workflow init"));
|
|
1839
|
-
process.exit(1);
|
|
1840
|
-
}
|
|
1841
|
-
console.log(chalk5.green(" \u2713 Configuration loaded successfully"));
|
|
1842
|
-
console.log(chalk5.dim(` Project: ${config.projectName}`));
|
|
1843
|
-
console.log(chalk5.dim(` Scopes: ${config.scopes.length} configured`));
|
|
1844
|
-
console.log(chalk5.dim(` Enforcement: ${config.enforcement}`));
|
|
1845
|
-
console.log(chalk5.dim(` Language: ${config.language}`));
|
|
1846
|
-
console.log(chalk5.bold("\n\u{1F4DA} Guidelines"));
|
|
1847
|
-
const guidelinesResult = await validateGuidelinesExist(cwd, config);
|
|
1848
|
-
if (guidelinesResult.valid) {
|
|
1849
|
-
console.log(
|
|
1850
|
-
chalk5.green(
|
|
1851
|
-
` \u2713 All ${guidelinesResult.presentMandatory.length} mandatory guidelines present`
|
|
1852
|
-
)
|
|
1853
|
-
);
|
|
1854
|
-
if (guidelinesResult.presentOptional.length > 0) {
|
|
1855
|
-
console.log(
|
|
1856
|
-
chalk5.dim(
|
|
1857
|
-
` + ${guidelinesResult.presentOptional.length} optional guidelines`
|
|
1858
|
-
)
|
|
1859
|
-
);
|
|
1860
|
-
}
|
|
1861
|
-
} else {
|
|
1862
|
-
hasErrors = true;
|
|
1863
|
-
console.log(
|
|
1864
|
-
chalk5.red(
|
|
1865
|
-
` \u2717 Missing ${guidelinesResult.missingMandatory.length} mandatory guidelines:`
|
|
1866
|
-
)
|
|
1867
|
-
);
|
|
1868
|
-
for (const missing of guidelinesResult.missingMandatory) {
|
|
1869
|
-
console.log(chalk5.red(` \u2022 ${missing}`));
|
|
1870
|
-
}
|
|
1871
|
-
console.log(chalk5.yellow(" Run: workflow init"));
|
|
1872
|
-
}
|
|
1873
|
-
console.log(chalk5.bold("\n\u{1F517} Git Hooks"));
|
|
1874
|
-
if (!hasGitRepo(cwd)) {
|
|
1875
|
-
console.log(chalk5.yellow(" \u26A0 No git repository found"));
|
|
1876
|
-
hasWarnings = true;
|
|
1877
|
-
} else {
|
|
1878
|
-
const hookStatuses = await getAllHooksStatus(cwd);
|
|
1879
|
-
const installedHooks = hookStatuses.filter((h) => h.installed);
|
|
1880
|
-
if (installedHooks.length === hookStatuses.length) {
|
|
1881
|
-
console.log(
|
|
1882
|
-
chalk5.green(` \u2713 All ${installedHooks.length} hooks installed`)
|
|
1883
|
-
);
|
|
1884
|
-
for (const hook of hookStatuses) {
|
|
1885
|
-
const extra = hook.wrappedOriginal ? " (wrapping original)" : "";
|
|
1886
|
-
console.log(chalk5.dim(` \u2022 ${hook.hookType}${extra}`));
|
|
1887
|
-
}
|
|
1888
|
-
} else if (installedHooks.length > 0) {
|
|
1889
|
-
hasWarnings = true;
|
|
1890
|
-
console.log(
|
|
1891
|
-
chalk5.yellow(
|
|
1892
|
-
` \u26A0 ${installedHooks.length}/${hookStatuses.length} hooks installed`
|
|
1893
|
-
)
|
|
1894
|
-
);
|
|
1895
|
-
for (const hook of hookStatuses) {
|
|
1896
|
-
if (hook.installed) {
|
|
1897
|
-
console.log(chalk5.green(` \u2713 ${hook.hookType}`));
|
|
1898
|
-
} else {
|
|
1899
|
-
console.log(chalk5.yellow(` \u2717 ${hook.hookType}`));
|
|
1900
|
-
}
|
|
1901
|
-
}
|
|
1902
|
-
console.log(chalk5.yellow(" Run: workflow hooks install"));
|
|
1903
|
-
} else {
|
|
1904
|
-
hasWarnings = true;
|
|
1905
|
-
console.log(chalk5.yellow(" \u26A0 No hooks installed"));
|
|
1906
|
-
console.log(chalk5.yellow(" Run: workflow hooks install"));
|
|
1907
|
-
}
|
|
1908
|
-
}
|
|
1909
|
-
console.log(chalk5.bold("\n\u{1F680} CI/CD Pipeline"));
|
|
1910
|
-
const repoInfo = await getRepoInfo(cwd);
|
|
1911
|
-
if (!repoInfo.isGitRepo) {
|
|
1912
|
-
console.log(chalk5.dim(" \u25CB No git repository (CI check skipped)"));
|
|
1913
|
-
} else if (!repoInfo.isGitHub) {
|
|
1914
|
-
console.log(chalk5.dim(" \u25CB Not a GitHub repository (CI check skipped)"));
|
|
1915
|
-
console.log(chalk5.dim(` Remote: ${repoInfo.remoteUrl || "none"}`));
|
|
1916
|
-
} else {
|
|
1917
|
-
console.log(
|
|
1918
|
-
chalk5.dim(
|
|
1919
|
-
` Repository: ${repoInfo.github?.owner}/${repoInfo.github?.repo}`
|
|
1920
|
-
)
|
|
1921
|
-
);
|
|
1922
|
-
const ciResult = await validateGitHubActionsSetup(cwd);
|
|
1923
|
-
if (ciResult.valid) {
|
|
1924
|
-
console.log(chalk5.green(" \u2713 GitHub Actions CI configured correctly"));
|
|
1925
|
-
const checks = [
|
|
1926
|
-
ciResult.hasLintCheck && "lint",
|
|
1927
|
-
ciResult.hasTypecheckCheck && "typecheck",
|
|
1928
|
-
ciResult.hasFormatCheck && "format",
|
|
1929
|
-
ciResult.hasBuildCheck && "build",
|
|
1930
|
-
ciResult.hasTestCheck && "test"
|
|
1931
|
-
].filter(Boolean);
|
|
1932
|
-
console.log(chalk5.dim(` Checks: ${checks.join(", ")}`));
|
|
1933
|
-
} else if (!ciResult.hasWorkflowFile) {
|
|
1934
|
-
hasErrors = true;
|
|
1935
|
-
console.log(chalk5.red(" \u2717 No CI workflow found"));
|
|
1936
|
-
console.log(chalk5.yellow(" Run: workflow github:setup"));
|
|
1937
|
-
} else {
|
|
1938
|
-
hasWarnings = true;
|
|
1939
|
-
console.log(chalk5.yellow(" \u26A0 CI workflow may be incomplete"));
|
|
1940
|
-
for (const warning of ciResult.warnings) {
|
|
1941
|
-
console.log(chalk5.yellow(` \u2022 ${warning}`));
|
|
1942
|
-
}
|
|
1943
|
-
console.log(chalk5.yellow(" Run: workflow github:setup to regenerate"));
|
|
1944
|
-
}
|
|
1945
|
-
}
|
|
1946
|
-
console.log(chalk5.bold("\n\u{1F4A1} Suggestions"));
|
|
1947
|
-
if (repoInfo.isGitHub && hasCIWorkflow(cwd)) {
|
|
1948
|
-
console.log(chalk5.cyan(" \u2192 Enable branch protection on GitHub"));
|
|
1949
|
-
console.log(chalk5.dim(" Settings \u2192 Branches \u2192 Add rule"));
|
|
1950
|
-
console.log(
|
|
1951
|
-
chalk5.dim(" \u2611 Require status checks to pass before merging")
|
|
1952
|
-
);
|
|
1953
|
-
console.log(
|
|
1954
|
-
chalk5.dim(" \u2611 Require branches to be up to date before merging")
|
|
1955
|
-
);
|
|
1956
|
-
}
|
|
1957
|
-
if (config.enforcement !== "strict") {
|
|
1958
|
-
console.log(chalk5.cyan(` \u2192 Consider switching to 'strict' enforcement`));
|
|
1959
|
-
console.log(chalk5.dim(` Current: ${config.enforcement}`));
|
|
1960
|
-
}
|
|
1961
|
-
if (config.scopes.length < 5) {
|
|
1962
|
-
console.log(
|
|
1963
|
-
chalk5.cyan(" \u2192 Consider adding more scopes for better organization")
|
|
1964
|
-
);
|
|
1965
|
-
console.log(chalk5.dim(" Run: workflow config add scope <name>"));
|
|
1966
|
-
}
|
|
1967
|
-
console.log(chalk5.bold("\n\u{1F4CA} Summary"));
|
|
1968
|
-
if (hasErrors) {
|
|
1969
|
-
console.log(chalk5.red(" \u2717 Health check failed - issues found"));
|
|
567
|
+
// src/cli/commands/doctor.ts
|
|
568
|
+
import chalk5 from "chalk";
|
|
569
|
+
async function doctorCommand() {
|
|
570
|
+
console.log(chalk5.bold.cyan("\n\u{1F3E5} Workflow Agent Health Check\n"));
|
|
571
|
+
const config = await loadConfig();
|
|
572
|
+
if (!config) {
|
|
573
|
+
console.error(chalk5.red("\u2717 No workflow configuration found"));
|
|
574
|
+
console.log(chalk5.yellow(" Run: workflow init"));
|
|
1970
575
|
process.exit(1);
|
|
1971
|
-
} else if (hasWarnings) {
|
|
1972
|
-
console.log(chalk5.yellow(" \u26A0 Health check passed with warnings"));
|
|
1973
|
-
} else {
|
|
1974
|
-
console.log(chalk5.green(" \u2713 All checks passed"));
|
|
1975
576
|
}
|
|
1976
|
-
console.log("");
|
|
577
|
+
console.log(chalk5.green("\u2713 Configuration loaded successfully"));
|
|
578
|
+
console.log(chalk5.dim(` Project: ${config.projectName}`));
|
|
579
|
+
console.log(chalk5.dim(` Scopes: ${config.scopes.length} configured`));
|
|
580
|
+
console.log(chalk5.dim(` Enforcement: ${config.enforcement}`));
|
|
581
|
+
console.log(chalk5.dim(` Language: ${config.language}`));
|
|
582
|
+
console.log(chalk5.cyan("\n\u{1F4A1} Suggestions:\n"));
|
|
583
|
+
console.log(chalk5.dim(" \u2022 Health check analysis coming soon"));
|
|
584
|
+
console.log(chalk5.dim(" \u2022 Git history analysis coming soon"));
|
|
585
|
+
console.log(chalk5.dim(" \u2022 Optimization recommendations coming soon"));
|
|
1977
586
|
}
|
|
1978
587
|
|
|
1979
588
|
// src/cli/commands/setup.ts
|
|
1980
589
|
import * as p3 from "@clack/prompts";
|
|
1981
590
|
import chalk6 from "chalk";
|
|
1982
|
-
import { readFileSync, writeFileSync
|
|
1983
|
-
import { join as
|
|
591
|
+
import { readFileSync, writeFileSync, existsSync as existsSync2 } from "fs";
|
|
592
|
+
import { join as join2 } from "path";
|
|
1984
593
|
var WORKFLOW_SCRIPTS = {
|
|
1985
594
|
"workflow:init": "workflow-agent init",
|
|
1986
595
|
"workflow:validate": "workflow-agent validate",
|
|
@@ -1990,8 +599,8 @@ var WORKFLOW_SCRIPTS = {
|
|
|
1990
599
|
async function setupCommand() {
|
|
1991
600
|
p3.intro(chalk6.bgBlue(" workflow-agent setup "));
|
|
1992
601
|
const cwd = process.cwd();
|
|
1993
|
-
const packageJsonPath =
|
|
1994
|
-
if (!
|
|
602
|
+
const packageJsonPath = join2(cwd, "package.json");
|
|
603
|
+
if (!existsSync2(packageJsonPath)) {
|
|
1995
604
|
p3.cancel("No package.json found in current directory");
|
|
1996
605
|
process.exit(1);
|
|
1997
606
|
}
|
|
@@ -2034,7 +643,7 @@ async function setupCommand() {
|
|
|
2034
643
|
for (const [scriptName, scriptCommand] of Object.entries(scriptsToAdd)) {
|
|
2035
644
|
packageJson.scripts[scriptName] = scriptCommand;
|
|
2036
645
|
}
|
|
2037
|
-
|
|
646
|
+
writeFileSync(
|
|
2038
647
|
packageJsonPath,
|
|
2039
648
|
JSON.stringify(packageJson, null, 2) + "\n",
|
|
2040
649
|
"utf-8"
|
|
@@ -2052,15 +661,15 @@ async function setupCommand() {
|
|
|
2052
661
|
// src/cli/commands/scope-create.ts
|
|
2053
662
|
import * as p4 from "@clack/prompts";
|
|
2054
663
|
import chalk7 from "chalk";
|
|
2055
|
-
import { existsSync as
|
|
2056
|
-
import { writeFile as
|
|
2057
|
-
import { join as
|
|
664
|
+
import { existsSync as existsSync3 } from "fs";
|
|
665
|
+
import { writeFile as writeFile2, mkdir as mkdir2, readFile } from "fs/promises";
|
|
666
|
+
import { join as join3 } from "path";
|
|
2058
667
|
async function scopeCreateCommand(options) {
|
|
2059
668
|
console.log(chalk7.bold.cyan("\n\u{1F3A8} Create Custom Scope Package\n"));
|
|
2060
669
|
const cwd = process.cwd();
|
|
2061
670
|
const isNonInteractive = !!(options.name && options.scopes && options.presetName);
|
|
2062
|
-
const
|
|
2063
|
-
if (
|
|
671
|
+
const isMonorepo = existsSync3(join3(cwd, "pnpm-workspace.yaml"));
|
|
672
|
+
if (isMonorepo) {
|
|
2064
673
|
console.log(chalk7.dim("\u2713 Detected monorepo workspace\n"));
|
|
2065
674
|
}
|
|
2066
675
|
const packageNameInput = isNonInteractive ? options.name : await p4.text({
|
|
@@ -2204,8 +813,8 @@ async function scopeCreateCommand(options) {
|
|
|
2204
813
|
let outputDir;
|
|
2205
814
|
if (options.outputDir) {
|
|
2206
815
|
outputDir = options.outputDir;
|
|
2207
|
-
} else if (
|
|
2208
|
-
outputDir =
|
|
816
|
+
} else if (isMonorepo) {
|
|
817
|
+
outputDir = join3(cwd, "packages", `scopes-${packageName}`);
|
|
2209
818
|
} else {
|
|
2210
819
|
const customDir = await p4.text({
|
|
2211
820
|
message: "Output directory:",
|
|
@@ -2216,9 +825,9 @@ async function scopeCreateCommand(options) {
|
|
|
2216
825
|
p4.cancel("Operation cancelled");
|
|
2217
826
|
process.exit(0);
|
|
2218
827
|
}
|
|
2219
|
-
outputDir =
|
|
828
|
+
outputDir = join3(cwd, customDir);
|
|
2220
829
|
}
|
|
2221
|
-
if (
|
|
830
|
+
if (existsSync3(outputDir)) {
|
|
2222
831
|
const shouldOverwrite = await p4.confirm({
|
|
2223
832
|
message: `Directory ${outputDir} already exists. Overwrite?`,
|
|
2224
833
|
initialValue: false
|
|
@@ -2231,7 +840,7 @@ async function scopeCreateCommand(options) {
|
|
|
2231
840
|
const spinner5 = p4.spinner();
|
|
2232
841
|
spinner5.start("Creating package structure...");
|
|
2233
842
|
try {
|
|
2234
|
-
await
|
|
843
|
+
await mkdir2(join3(outputDir, "src"), { recursive: true });
|
|
2235
844
|
const packageJson = {
|
|
2236
845
|
name: `@workflow/scopes-${packageName}`,
|
|
2237
846
|
version: "1.0.0",
|
|
@@ -2271,8 +880,8 @@ async function scopeCreateCommand(options) {
|
|
|
2271
880
|
access: "public"
|
|
2272
881
|
}
|
|
2273
882
|
};
|
|
2274
|
-
await
|
|
2275
|
-
|
|
883
|
+
await writeFile2(
|
|
884
|
+
join3(outputDir, "package.json"),
|
|
2276
885
|
JSON.stringify(packageJson, null, 2),
|
|
2277
886
|
"utf-8"
|
|
2278
887
|
);
|
|
@@ -2284,8 +893,8 @@ async function scopeCreateCommand(options) {
|
|
|
2284
893
|
},
|
|
2285
894
|
include: ["src/**/*"]
|
|
2286
895
|
};
|
|
2287
|
-
await
|
|
2288
|
-
|
|
896
|
+
await writeFile2(
|
|
897
|
+
join3(outputDir, "tsconfig.json"),
|
|
2289
898
|
JSON.stringify(tsconfig, null, 2),
|
|
2290
899
|
"utf-8"
|
|
2291
900
|
);
|
|
@@ -2299,7 +908,7 @@ export default defineConfig({
|
|
|
2299
908
|
sourcemap: true,
|
|
2300
909
|
});
|
|
2301
910
|
`;
|
|
2302
|
-
await
|
|
911
|
+
await writeFile2(join3(outputDir, "tsup.config.ts"), tsupConfig, "utf-8");
|
|
2303
912
|
const indexTs = `import type { Scope } from '@hawkinside_out/workflow-agent/config';
|
|
2304
913
|
|
|
2305
914
|
export const scopes: Scope[] = ${JSON.stringify(scopes, null, 2)};
|
|
@@ -2313,7 +922,7 @@ export const preset = {
|
|
|
2313
922
|
|
|
2314
923
|
export default preset;
|
|
2315
924
|
`;
|
|
2316
|
-
await
|
|
925
|
+
await writeFile2(join3(outputDir, "src", "index.ts"), indexTs, "utf-8");
|
|
2317
926
|
if (!options.noTest) {
|
|
2318
927
|
const testFile = `import { describe, it, expect } from 'vitest';
|
|
2319
928
|
import { scopes, preset } from './index.js';
|
|
@@ -2354,16 +963,16 @@ describe('${presetName} Scope Preset', () => {
|
|
|
2354
963
|
});
|
|
2355
964
|
});
|
|
2356
965
|
`;
|
|
2357
|
-
await
|
|
2358
|
-
|
|
966
|
+
await writeFile2(
|
|
967
|
+
join3(outputDir, "src", "index.test.ts"),
|
|
2359
968
|
testFile,
|
|
2360
969
|
"utf-8"
|
|
2361
970
|
);
|
|
2362
971
|
}
|
|
2363
972
|
spinner5.stop("\u2713 Package structure created");
|
|
2364
|
-
if (
|
|
2365
|
-
const workspaceFile =
|
|
2366
|
-
const workspaceContent = await
|
|
973
|
+
if (isMonorepo) {
|
|
974
|
+
const workspaceFile = join3(cwd, "pnpm-workspace.yaml");
|
|
975
|
+
const workspaceContent = await readFile(workspaceFile, "utf-8");
|
|
2367
976
|
const packagePath = `packages/scopes-${packageName}`;
|
|
2368
977
|
if (!workspaceContent.includes(packagePath) && !workspaceContent.includes("packages/*")) {
|
|
2369
978
|
console.log(
|
|
@@ -2421,9 +1030,9 @@ describe('${presetName} Scope Preset', () => {
|
|
|
2421
1030
|
// src/cli/commands/scope-migrate.ts
|
|
2422
1031
|
import * as p5 from "@clack/prompts";
|
|
2423
1032
|
import chalk8 from "chalk";
|
|
2424
|
-
import { existsSync as
|
|
2425
|
-
import { writeFile as
|
|
2426
|
-
import { join as
|
|
1033
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1034
|
+
import { writeFile as writeFile3, mkdir as mkdir3, readFile as readFile2 } from "fs/promises";
|
|
1035
|
+
import { join as join4 } from "path";
|
|
2427
1036
|
async function scopeMigrateCommand(options) {
|
|
2428
1037
|
console.log(chalk8.bold.cyan("\n\u{1F504} Migrate Scopes to Custom Package\n"));
|
|
2429
1038
|
const cwd = process.cwd();
|
|
@@ -2467,8 +1076,8 @@ async function scopeMigrateCommand(options) {
|
|
|
2467
1076
|
p5.cancel("Migration cancelled");
|
|
2468
1077
|
process.exit(0);
|
|
2469
1078
|
}
|
|
2470
|
-
const
|
|
2471
|
-
if (
|
|
1079
|
+
const isMonorepo = existsSync4(join4(cwd, "pnpm-workspace.yaml"));
|
|
1080
|
+
if (isMonorepo) {
|
|
2472
1081
|
console.log(chalk8.dim("\n\u2713 Detected monorepo workspace\n"));
|
|
2473
1082
|
}
|
|
2474
1083
|
const packageNameInput = options.name || await p5.text({
|
|
@@ -2516,8 +1125,8 @@ async function scopeMigrateCommand(options) {
|
|
|
2516
1125
|
let outputDir;
|
|
2517
1126
|
if (options.outputDir) {
|
|
2518
1127
|
outputDir = options.outputDir;
|
|
2519
|
-
} else if (
|
|
2520
|
-
outputDir =
|
|
1128
|
+
} else if (isMonorepo) {
|
|
1129
|
+
outputDir = join4(cwd, "packages", `scopes-${packageName}`);
|
|
2521
1130
|
} else {
|
|
2522
1131
|
const customDir = await p5.text({
|
|
2523
1132
|
message: "Output directory:",
|
|
@@ -2528,9 +1137,9 @@ async function scopeMigrateCommand(options) {
|
|
|
2528
1137
|
p5.cancel("Migration cancelled");
|
|
2529
1138
|
process.exit(0);
|
|
2530
1139
|
}
|
|
2531
|
-
outputDir =
|
|
1140
|
+
outputDir = join4(cwd, customDir);
|
|
2532
1141
|
}
|
|
2533
|
-
if (
|
|
1142
|
+
if (existsSync4(outputDir)) {
|
|
2534
1143
|
const shouldOverwrite = await p5.confirm({
|
|
2535
1144
|
message: `Directory ${outputDir} already exists. Overwrite?`,
|
|
2536
1145
|
initialValue: false
|
|
@@ -2543,7 +1152,7 @@ async function scopeMigrateCommand(options) {
|
|
|
2543
1152
|
const spinner5 = p5.spinner();
|
|
2544
1153
|
spinner5.start("Migrating scopes to package...");
|
|
2545
1154
|
try {
|
|
2546
|
-
await
|
|
1155
|
+
await mkdir3(join4(outputDir, "src"), { recursive: true });
|
|
2547
1156
|
const packageJson = {
|
|
2548
1157
|
name: `@workflow/scopes-${packageName}`,
|
|
2549
1158
|
version: "1.0.0",
|
|
@@ -2583,8 +1192,8 @@ async function scopeMigrateCommand(options) {
|
|
|
2583
1192
|
access: "public"
|
|
2584
1193
|
}
|
|
2585
1194
|
};
|
|
2586
|
-
await
|
|
2587
|
-
|
|
1195
|
+
await writeFile3(
|
|
1196
|
+
join4(outputDir, "package.json"),
|
|
2588
1197
|
JSON.stringify(packageJson, null, 2),
|
|
2589
1198
|
"utf-8"
|
|
2590
1199
|
);
|
|
@@ -2596,8 +1205,8 @@ async function scopeMigrateCommand(options) {
|
|
|
2596
1205
|
},
|
|
2597
1206
|
include: ["src/**/*"]
|
|
2598
1207
|
};
|
|
2599
|
-
await
|
|
2600
|
-
|
|
1208
|
+
await writeFile3(
|
|
1209
|
+
join4(outputDir, "tsconfig.json"),
|
|
2601
1210
|
JSON.stringify(tsconfig, null, 2),
|
|
2602
1211
|
"utf-8"
|
|
2603
1212
|
);
|
|
@@ -2611,7 +1220,7 @@ export default defineConfig({
|
|
|
2611
1220
|
sourcemap: true,
|
|
2612
1221
|
});
|
|
2613
1222
|
`;
|
|
2614
|
-
await
|
|
1223
|
+
await writeFile3(join4(outputDir, "tsup.config.ts"), tsupConfig, "utf-8");
|
|
2615
1224
|
const indexTs = `import type { Scope } from '@hawkinside_out/workflow-agent/config';
|
|
2616
1225
|
|
|
2617
1226
|
export const scopes: Scope[] = ${JSON.stringify(config.scopes, null, 2)};
|
|
@@ -2625,7 +1234,7 @@ export const preset = {
|
|
|
2625
1234
|
|
|
2626
1235
|
export default preset;
|
|
2627
1236
|
`;
|
|
2628
|
-
await
|
|
1237
|
+
await writeFile3(join4(outputDir, "src", "index.ts"), indexTs, "utf-8");
|
|
2629
1238
|
const testFile = `import { describe, it, expect } from 'vitest';
|
|
2630
1239
|
import { scopes, preset } from './index.js';
|
|
2631
1240
|
import { ScopeSchema } from '@hawkinside_out/workflow-agent/config';
|
|
@@ -2668,11 +1277,11 @@ describe('${presetName} Scope Preset (Migrated)', () => {
|
|
|
2668
1277
|
});
|
|
2669
1278
|
});
|
|
2670
1279
|
`;
|
|
2671
|
-
await
|
|
1280
|
+
await writeFile3(join4(outputDir, "src", "index.test.ts"), testFile, "utf-8");
|
|
2672
1281
|
spinner5.stop("\u2713 Package created from migrated scopes");
|
|
2673
|
-
if (
|
|
2674
|
-
const workspaceFile =
|
|
2675
|
-
const workspaceContent = await
|
|
1282
|
+
if (isMonorepo) {
|
|
1283
|
+
const workspaceFile = join4(cwd, "pnpm-workspace.yaml");
|
|
1284
|
+
const workspaceContent = await readFile2(workspaceFile, "utf-8");
|
|
2676
1285
|
const packagePath = `packages/scopes-${packageName}`;
|
|
2677
1286
|
if (!workspaceContent.includes(packagePath) && !workspaceContent.includes("packages/*")) {
|
|
2678
1287
|
console.log(
|
|
@@ -2688,7 +1297,7 @@ describe('${presetName} Scope Preset (Migrated)', () => {
|
|
|
2688
1297
|
initialValue: false
|
|
2689
1298
|
});
|
|
2690
1299
|
if (!p5.isCancel(keepConfig) && !keepConfig) {
|
|
2691
|
-
const configPath =
|
|
1300
|
+
const configPath = join4(cwd, "workflow.config.json");
|
|
2692
1301
|
const updatedConfig = {
|
|
2693
1302
|
...config,
|
|
2694
1303
|
scopes: [],
|
|
@@ -2696,7 +1305,7 @@ describe('${presetName} Scope Preset (Migrated)', () => {
|
|
|
2696
1305
|
preset: `scopes-${packageName}`
|
|
2697
1306
|
// Reference the new package
|
|
2698
1307
|
};
|
|
2699
|
-
await
|
|
1308
|
+
await writeFile3(
|
|
2700
1309
|
configPath,
|
|
2701
1310
|
JSON.stringify(updatedConfig, null, 2),
|
|
2702
1311
|
"utf-8"
|
|
@@ -2747,704 +1356,240 @@ describe('${presetName} Scope Preset (Migrated)', () => {
|
|
|
2747
1356
|
}
|
|
2748
1357
|
}
|
|
2749
1358
|
|
|
2750
|
-
// src/cli/commands/
|
|
1359
|
+
// src/cli/commands/verify.ts
|
|
2751
1360
|
import chalk9 from "chalk";
|
|
2752
|
-
|
|
1361
|
+
import { execa as execa2 } from "execa";
|
|
1362
|
+
async function verifyCommand(options) {
|
|
2753
1363
|
const cwd = process.cwd();
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
)
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
console.log(chalk9.dim("\nHooks will run automatically on commit."));
|
|
2802
|
-
console.log(chalk9.dim("They will be skipped in CI environments."));
|
|
2803
|
-
} else {
|
|
2804
|
-
process.exit(1);
|
|
2805
|
-
}
|
|
2806
|
-
}
|
|
2807
|
-
async function uninstallHooksAction(cwd) {
|
|
2808
|
-
console.log(chalk9.bold.cyan("\n\u{1F513} Uninstalling Workflow Agent Git Hooks\n"));
|
|
2809
|
-
if (!hasGitRepo(cwd)) {
|
|
2810
|
-
console.error(chalk9.red("\u2717 No git repository found"));
|
|
2811
|
-
process.exit(1);
|
|
2812
|
-
}
|
|
2813
|
-
const results = await uninstallHooks(cwd);
|
|
2814
|
-
let hasErrors = false;
|
|
2815
|
-
for (const result of results) {
|
|
2816
|
-
if (result.success) {
|
|
2817
|
-
if (result.wrappedExisting) {
|
|
2818
|
-
console.log(
|
|
2819
|
-
chalk9.green(`\u2713 Removed ${result.hookType} hook (restored original)`)
|
|
2820
|
-
);
|
|
1364
|
+
const maxRetries = options.maxRetries ? parseInt(options.maxRetries, 10) : 10;
|
|
1365
|
+
const autoFix = options.fix ?? false;
|
|
1366
|
+
const shouldCommit = options.commit ?? false;
|
|
1367
|
+
const dryRun = options.dryRun ?? false;
|
|
1368
|
+
console.log(chalk9.bold.cyan("\n\u{1F50D} Workflow Agent Quality Verification\n"));
|
|
1369
|
+
if (dryRun) {
|
|
1370
|
+
console.log(chalk9.yellow("\u{1F4CB} DRY-RUN MODE: No changes will be applied\n"));
|
|
1371
|
+
}
|
|
1372
|
+
console.log(chalk9.dim(` Auto-fix: ${autoFix ? "enabled" : "disabled"}`));
|
|
1373
|
+
console.log(chalk9.dim(` Max retries: ${maxRetries}`));
|
|
1374
|
+
console.log(chalk9.dim(` Commit on success: ${shouldCommit ? "yes" : "no"}`));
|
|
1375
|
+
console.log(chalk9.dim(` Dry-run: ${dryRun ? "yes" : "no"}`));
|
|
1376
|
+
const startTime = Date.now();
|
|
1377
|
+
const result = await runAllChecks(cwd, {
|
|
1378
|
+
maxRetries,
|
|
1379
|
+
autoFix,
|
|
1380
|
+
dryRun
|
|
1381
|
+
});
|
|
1382
|
+
const totalTime = ((Date.now() - startTime) / 1e3).toFixed(2);
|
|
1383
|
+
console.log(`
|
|
1384
|
+
${"\u2501".repeat(50)}`);
|
|
1385
|
+
if (result.success) {
|
|
1386
|
+
console.log(chalk9.bold.green("\n\u2705 ALL QUALITY CHECKS PASSED!\n"));
|
|
1387
|
+
console.log(chalk9.dim(` Total time: ${totalTime}s`));
|
|
1388
|
+
console.log(chalk9.dim(` Validation cycles: ${result.totalAttempts}`));
|
|
1389
|
+
console.log(chalk9.dim(` Fixes applied: ${result.fixesApplied}`));
|
|
1390
|
+
if (shouldCommit) {
|
|
1391
|
+
const hasChanges = await hasUncommittedChanges(cwd);
|
|
1392
|
+
if (hasChanges) {
|
|
1393
|
+
console.log(chalk9.cyan("\n\u{1F4E6} Staging and committing changes...\n"));
|
|
1394
|
+
const staged = await stageAllChanges(cwd);
|
|
1395
|
+
if (!staged) {
|
|
1396
|
+
console.log(chalk9.red("\u274C Failed to stage changes"));
|
|
1397
|
+
process.exit(1);
|
|
1398
|
+
}
|
|
1399
|
+
try {
|
|
1400
|
+
await execa2(
|
|
1401
|
+
"git",
|
|
1402
|
+
["commit", "-m", "chore: auto-fix quality issues"],
|
|
1403
|
+
{ cwd }
|
|
1404
|
+
);
|
|
1405
|
+
console.log(chalk9.green("\u2705 Changes committed successfully"));
|
|
1406
|
+
} catch (error) {
|
|
1407
|
+
console.log(chalk9.red("\u274C Failed to commit changes"));
|
|
1408
|
+
console.log(chalk9.dim(error.message));
|
|
1409
|
+
process.exit(1);
|
|
1410
|
+
}
|
|
2821
1411
|
} else {
|
|
2822
|
-
console.log(chalk9.
|
|
1412
|
+
console.log(chalk9.dim("\n No changes to commit."));
|
|
2823
1413
|
}
|
|
2824
|
-
} else if (result.error) {
|
|
2825
|
-
console.error(
|
|
2826
|
-
chalk9.red(`\u2717 Failed to remove ${result.hookType}: ${result.error}`)
|
|
2827
|
-
);
|
|
2828
|
-
hasErrors = true;
|
|
2829
1414
|
}
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
console.log(
|
|
1415
|
+
console.log(chalk9.cyan("\n\u{1F4A1} Next steps:\n"));
|
|
1416
|
+
console.log(chalk9.dim(" 1. git add ."));
|
|
1417
|
+
console.log(
|
|
1418
|
+
chalk9.dim(' 2. git commit -m "<type>(<scope>): <description>"')
|
|
1419
|
+
);
|
|
1420
|
+
console.log(chalk9.dim(" 3. git push origin <branch-name>"));
|
|
1421
|
+
console.log("");
|
|
1422
|
+
process.exit(0);
|
|
2833
1423
|
} else {
|
|
2834
|
-
|
|
2835
|
-
}
|
|
2836
|
-
}
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
}
|
|
2843
|
-
const statuses = await getAllHooksStatus(cwd);
|
|
2844
|
-
for (const status of statuses) {
|
|
2845
|
-
const icon = status.installed ? "\u2713" : "\u2717";
|
|
2846
|
-
const color = status.installed ? chalk9.green : chalk9.yellow;
|
|
2847
|
-
let message = `${icon} ${status.hookType}`;
|
|
2848
|
-
if (status.installed) {
|
|
2849
|
-
message += " - installed";
|
|
2850
|
-
if (status.wrappedOriginal) {
|
|
2851
|
-
message += " (wrapping original hook)";
|
|
1424
|
+
console.log(chalk9.bold.red("\n\u274C QUALITY CHECKS FAILED\n"));
|
|
1425
|
+
console.log(chalk9.dim(` Total time: ${totalTime}s`));
|
|
1426
|
+
console.log(chalk9.dim(` Validation cycles: ${result.totalAttempts}`));
|
|
1427
|
+
console.log(chalk9.dim(` Fixes applied: ${result.fixesApplied}`));
|
|
1428
|
+
if (result.pendingFixes && result.pendingFixes.length > 0) {
|
|
1429
|
+
console.log(chalk9.yellow("\n\u{1F4CB} Pending fixes (dry-run):"));
|
|
1430
|
+
for (const fix of result.pendingFixes) {
|
|
1431
|
+
console.log(chalk9.dim(` \u2022 ${fix.check.displayName}: ${fix.command}`));
|
|
2852
1432
|
}
|
|
2853
|
-
} else if (status.hasExistingHook) {
|
|
2854
|
-
message += " - existing hook (not managed by Workflow Agent)";
|
|
2855
|
-
} else {
|
|
2856
|
-
message += " - not installed";
|
|
2857
|
-
}
|
|
2858
|
-
console.log(color(message));
|
|
2859
|
-
}
|
|
2860
|
-
const allInstalled = statuses.every((s) => s.installed);
|
|
2861
|
-
if (!allInstalled) {
|
|
2862
|
-
console.log(chalk9.dim("\nTo install hooks: workflow hooks install"));
|
|
2863
|
-
}
|
|
2864
|
-
}
|
|
2865
|
-
|
|
2866
|
-
// src/cli/commands/github-actions.ts
|
|
2867
|
-
import chalk10 from "chalk";
|
|
2868
|
-
import * as p6 from "@clack/prompts";
|
|
2869
|
-
async function githubCommand(action) {
|
|
2870
|
-
const cwd = process.cwd();
|
|
2871
|
-
switch (action) {
|
|
2872
|
-
case "setup":
|
|
2873
|
-
await setupAction(cwd);
|
|
2874
|
-
break;
|
|
2875
|
-
case "check":
|
|
2876
|
-
await checkAction(cwd);
|
|
2877
|
-
break;
|
|
2878
|
-
default:
|
|
2879
|
-
console.error(chalk10.red(`Unknown action: ${action}`));
|
|
2880
|
-
console.log(chalk10.dim("Available actions: setup, check"));
|
|
2881
|
-
process.exit(1);
|
|
2882
|
-
}
|
|
2883
|
-
}
|
|
2884
|
-
async function setupAction(cwd) {
|
|
2885
|
-
console.log(chalk10.bold.cyan("\n\u{1F527} Setting Up GitHub Actions CI\n"));
|
|
2886
|
-
const repoInfo = await getRepoInfo(cwd);
|
|
2887
|
-
if (!repoInfo.isGitRepo) {
|
|
2888
|
-
console.error(chalk10.red("\u2717 No git repository found"));
|
|
2889
|
-
console.log(chalk10.yellow(" Run: git init"));
|
|
2890
|
-
process.exit(1);
|
|
2891
|
-
}
|
|
2892
|
-
if (!repoInfo.isGitHub) {
|
|
2893
|
-
console.log(chalk10.yellow("\u26A0\uFE0F No GitHub remote detected"));
|
|
2894
|
-
const shouldContinue = await p6.confirm({
|
|
2895
|
-
message: "Create GitHub Actions workflow anyway?",
|
|
2896
|
-
initialValue: true
|
|
2897
|
-
});
|
|
2898
|
-
if (p6.isCancel(shouldContinue) || !shouldContinue) {
|
|
2899
|
-
p6.cancel("Setup cancelled");
|
|
2900
|
-
process.exit(0);
|
|
2901
1433
|
}
|
|
2902
|
-
} else {
|
|
2903
1434
|
console.log(
|
|
2904
|
-
|
|
2905
|
-
`Repository: ${repoInfo.github?.owner}/${repoInfo.github?.repo}`
|
|
2906
|
-
)
|
|
1435
|
+
chalk9.yellow("\n\u26A0\uFE0F Please fix the errors above and run again.")
|
|
2907
1436
|
);
|
|
2908
|
-
}
|
|
2909
|
-
if (hasCIWorkflow(cwd)) {
|
|
2910
|
-
const shouldOverwrite = await p6.confirm({
|
|
2911
|
-
message: "CI workflow already exists. Overwrite?",
|
|
2912
|
-
initialValue: false
|
|
2913
|
-
});
|
|
2914
|
-
if (p6.isCancel(shouldOverwrite) || !shouldOverwrite) {
|
|
2915
|
-
p6.cancel("Setup cancelled");
|
|
2916
|
-
process.exit(0);
|
|
2917
|
-
}
|
|
2918
|
-
}
|
|
2919
|
-
const projectInfo = await getProjectInfo(cwd);
|
|
2920
|
-
console.log(chalk10.dim(`Package manager: ${projectInfo.packageManager}`));
|
|
2921
|
-
console.log(chalk10.dim(`Monorepo: ${projectInfo.isMonorepo ? "yes" : "no"}`));
|
|
2922
|
-
const config = await loadConfig();
|
|
2923
|
-
const ciConfig = config?.ci;
|
|
2924
|
-
const spinner5 = p6.spinner();
|
|
2925
|
-
spinner5.start("Creating CI workflow...");
|
|
2926
|
-
const result = await createCIWorkflow({
|
|
2927
|
-
projectPath: cwd,
|
|
2928
|
-
packageManager: projectInfo.packageManager,
|
|
2929
|
-
isMonorepo: projectInfo.isMonorepo,
|
|
2930
|
-
ciConfig,
|
|
2931
|
-
defaultBranch: repoInfo.defaultBranch || "main"
|
|
2932
|
-
});
|
|
2933
|
-
if (result.success) {
|
|
2934
|
-
spinner5.stop(chalk10.green("\u2713 Created CI workflow"));
|
|
2935
|
-
console.log(chalk10.dim(` File: ${result.filePath}`));
|
|
2936
|
-
console.log(chalk10.green("\n\u2713 GitHub Actions CI setup complete"));
|
|
2937
|
-
console.log(chalk10.dim("\nThe workflow will run on:"));
|
|
2938
|
-
console.log(chalk10.dim(" \u2022 Push to main/develop branches"));
|
|
2939
|
-
console.log(chalk10.dim(" \u2022 Pull requests to main/develop branches"));
|
|
2940
|
-
console.log(chalk10.dim("\nChecks included:"));
|
|
2941
|
-
const checks = ciConfig?.checks || [
|
|
2942
|
-
"lint",
|
|
2943
|
-
"typecheck",
|
|
2944
|
-
"format",
|
|
2945
|
-
"build",
|
|
2946
|
-
"test"
|
|
2947
|
-
];
|
|
2948
|
-
for (const check of checks) {
|
|
2949
|
-
const hasScript = check === "lint" ? projectInfo.hasLintScript : check === "typecheck" ? projectInfo.hasTypecheckScript : check === "format" ? projectInfo.hasFormatScript : check === "test" ? projectInfo.hasTestScript : check === "build" ? projectInfo.hasBuildScript : false;
|
|
2950
|
-
const status = hasScript ? chalk10.green("\u2713") : chalk10.yellow("\u26A0");
|
|
2951
|
-
const note = hasScript ? "" : " (add script to package.json)";
|
|
2952
|
-
console.log(chalk10.dim(` ${status} ${check}${note}`));
|
|
2953
|
-
}
|
|
2954
|
-
console.log(chalk10.cyan("\n\u{1F4A1} Recommended: Enable branch protection"));
|
|
2955
|
-
console.log(chalk10.dim(" Go to GitHub \u2192 Settings \u2192 Branches \u2192 Add rule"));
|
|
2956
1437
|
console.log(
|
|
2957
|
-
|
|
1438
|
+
chalk9.dim(" Run with --fix to auto-fix lint and format issues.")
|
|
2958
1439
|
);
|
|
2959
|
-
console.log(
|
|
2960
|
-
} else {
|
|
2961
|
-
spinner5.stop(chalk10.red("\u2717 Failed to create CI workflow"));
|
|
2962
|
-
console.error(chalk10.red(` Error: ${result.error}`));
|
|
2963
|
-
process.exit(1);
|
|
2964
|
-
}
|
|
2965
|
-
}
|
|
2966
|
-
async function checkAction(cwd) {
|
|
2967
|
-
console.log(chalk10.bold.cyan("\n\u{1F50D} Checking GitHub Actions CI Setup\n"));
|
|
2968
|
-
const result = await validateGitHubActionsSetup(cwd);
|
|
2969
|
-
if (result.hasWorkflowFile) {
|
|
2970
|
-
console.log(chalk10.green("\u2713 CI workflow file found"));
|
|
2971
|
-
} else {
|
|
2972
|
-
console.log(chalk10.red("\u2717 No CI workflow file found"));
|
|
2973
|
-
console.log(chalk10.yellow(" Run: workflow github:setup"));
|
|
2974
|
-
process.exit(1);
|
|
2975
|
-
}
|
|
2976
|
-
const checks = [
|
|
2977
|
-
{ name: "Lint", present: result.hasLintCheck },
|
|
2978
|
-
{ name: "Type check", present: result.hasTypecheckCheck },
|
|
2979
|
-
{ name: "Format", present: result.hasFormatCheck },
|
|
2980
|
-
{ name: "Build", present: result.hasBuildCheck },
|
|
2981
|
-
{ name: "Test", present: result.hasTestCheck }
|
|
2982
|
-
];
|
|
2983
|
-
console.log(chalk10.dim("\nCI checks:"));
|
|
2984
|
-
for (const check of checks) {
|
|
2985
|
-
const icon = check.present ? chalk10.green("\u2713") : chalk10.yellow("\u26A0");
|
|
2986
|
-
console.log(` ${icon} ${check.name}`);
|
|
2987
|
-
}
|
|
2988
|
-
if (result.errors.length > 0) {
|
|
2989
|
-
console.log(chalk10.red("\nErrors:"));
|
|
2990
|
-
for (const error of result.errors) {
|
|
2991
|
-
console.log(chalk10.red(` \u2022 ${error}`));
|
|
2992
|
-
}
|
|
2993
|
-
}
|
|
2994
|
-
if (result.warnings.length > 0) {
|
|
2995
|
-
console.log(chalk10.yellow("\nWarnings:"));
|
|
2996
|
-
for (const warning of result.warnings) {
|
|
2997
|
-
console.log(chalk10.yellow(` \u2022 ${warning}`));
|
|
2998
|
-
}
|
|
2999
|
-
}
|
|
3000
|
-
if (result.valid) {
|
|
3001
|
-
console.log(chalk10.green("\n\u2713 CI setup is valid"));
|
|
3002
|
-
} else {
|
|
3003
|
-
console.log(chalk10.red("\n\u2717 CI setup has issues"));
|
|
3004
|
-
console.log(chalk10.yellow(" Run: workflow github:setup"));
|
|
1440
|
+
console.log("");
|
|
3005
1441
|
process.exit(1);
|
|
3006
1442
|
}
|
|
3007
1443
|
}
|
|
3008
1444
|
|
|
3009
|
-
// src/cli/commands/
|
|
3010
|
-
import
|
|
3011
|
-
import
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
/(?:at\s+)?([\/\w\.\-]+\.(?:ts|js|tsx|jsx|mjs|cjs|vue|svelte))(?::\d+(?::\d+)?)?/g,
|
|
3019
|
-
/(?:Error in |from )\.?([\/\w\.\-]+\.(?:ts|js|tsx|jsx|mjs|cjs|vue|svelte))/g,
|
|
3020
|
-
/['"]([\/\w\.\-]+\.(?:ts|js|tsx|jsx|mjs|cjs|vue|svelte))['"]/g
|
|
3021
|
-
];
|
|
3022
|
-
for (const pattern of patterns) {
|
|
3023
|
-
let match;
|
|
3024
|
-
while ((match = pattern.exec(errorMessage)) !== null) {
|
|
3025
|
-
let filePath = match[1];
|
|
3026
|
-
if (!filePath.startsWith("/")) {
|
|
3027
|
-
filePath = join10(cwd, filePath);
|
|
3028
|
-
}
|
|
3029
|
-
if (existsSync10(filePath)) {
|
|
3030
|
-
paths.add(filePath);
|
|
3031
|
-
}
|
|
3032
|
-
}
|
|
3033
|
-
}
|
|
3034
|
-
return Array.from(paths);
|
|
3035
|
-
}
|
|
3036
|
-
function readFileContents(paths) {
|
|
3037
|
-
const contents = {};
|
|
3038
|
-
for (const filePath of paths) {
|
|
3039
|
-
try {
|
|
3040
|
-
if (existsSync10(filePath)) {
|
|
3041
|
-
contents[filePath] = readFileSync2(filePath, "utf-8");
|
|
3042
|
-
}
|
|
3043
|
-
} catch {
|
|
3044
|
-
}
|
|
3045
|
-
}
|
|
3046
|
-
return contents;
|
|
3047
|
-
}
|
|
3048
|
-
async function generateFixWithLLM(errorMessage, _fileContents, _context) {
|
|
3049
|
-
console.log(pc.dim(" Analyzing error with LLM..."));
|
|
3050
|
-
return {
|
|
3051
|
-
analysis: `Error analysis for: ${errorMessage.slice(0, 100)}...`,
|
|
3052
|
-
rootCause: "Unable to determine root cause without LLM API access",
|
|
3053
|
-
suggestedFix: {
|
|
3054
|
-
description: "Manual intervention required - LLM API not configured",
|
|
3055
|
-
files: []
|
|
3056
|
-
},
|
|
3057
|
-
confidence: 0
|
|
3058
|
-
};
|
|
3059
|
-
}
|
|
3060
|
-
async function applyChanges(changes, dryRun) {
|
|
3061
|
-
for (const change of changes) {
|
|
3062
|
-
const actionColor = change.action === "create" ? pc.green : change.action === "delete" ? pc.red : pc.yellow;
|
|
3063
|
-
console.log(` ${actionColor(change.action.toUpperCase())} ${change.path}`);
|
|
3064
|
-
if (dryRun) {
|
|
3065
|
-
console.log(pc.dim(" (dry run - no changes made)"));
|
|
3066
|
-
continue;
|
|
3067
|
-
}
|
|
3068
|
-
switch (change.action) {
|
|
3069
|
-
case "create":
|
|
3070
|
-
case "modify":
|
|
3071
|
-
if (change.content) {
|
|
3072
|
-
const dir = dirname2(change.path);
|
|
3073
|
-
if (!existsSync10(dir)) {
|
|
3074
|
-
mkdirSync(dir, { recursive: true });
|
|
3075
|
-
}
|
|
3076
|
-
writeFileSync3(change.path, change.content);
|
|
3077
|
-
}
|
|
3078
|
-
break;
|
|
3079
|
-
case "delete":
|
|
3080
|
-
console.log(pc.dim(" (delete skipped for safety)"));
|
|
3081
|
-
break;
|
|
3082
|
-
}
|
|
3083
|
-
}
|
|
3084
|
-
}
|
|
3085
|
-
async function commitAndPush(message, cwd) {
|
|
1445
|
+
// src/cli/commands/auto-setup-command.ts
|
|
1446
|
+
import * as p6 from "@clack/prompts";
|
|
1447
|
+
import chalk10 from "chalk";
|
|
1448
|
+
async function autoSetupCommand(options) {
|
|
1449
|
+
console.log(chalk10.bold.cyan("\n\u{1F527} Workflow Agent Auto-Setup\n"));
|
|
1450
|
+
const cwd = process.cwd();
|
|
1451
|
+
const spinner5 = p6.spinner();
|
|
1452
|
+
spinner5.start("Analyzing project...");
|
|
1453
|
+
let report;
|
|
3086
1454
|
try {
|
|
3087
|
-
await
|
|
3088
|
-
|
|
3089
|
-
"git",
|
|
3090
|
-
["status", "--porcelain"],
|
|
3091
|
-
{ cwd }
|
|
3092
|
-
);
|
|
3093
|
-
if (!status.trim()) {
|
|
3094
|
-
return { success: true };
|
|
3095
|
-
}
|
|
3096
|
-
await execa3("git", ["commit", "-m", message], { cwd });
|
|
3097
|
-
await execa3("git", ["push"], { cwd });
|
|
3098
|
-
return { success: true };
|
|
1455
|
+
report = await generateAuditReport(cwd);
|
|
1456
|
+
spinner5.stop("\u2713 Project analysis complete");
|
|
3099
1457
|
} catch (error) {
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
}
|
|
3105
|
-
}
|
|
3106
|
-
async function fixCommand(options) {
|
|
3107
|
-
const cwd = process.cwd();
|
|
3108
|
-
console.log(pc.cyan("\n\u{1F527} Auto-Heal: Fixing Pipeline Error\n"));
|
|
3109
|
-
if (!options.error) {
|
|
3110
|
-
console.error(pc.red("Error: --error flag is required"));
|
|
3111
|
-
process.exit(1);
|
|
3112
|
-
}
|
|
3113
|
-
console.log(pc.bold("Error Message:"));
|
|
3114
|
-
console.log(pc.dim(options.error.slice(0, 500)));
|
|
3115
|
-
if (options.error.length > 500) {
|
|
3116
|
-
console.log(pc.dim(`... (${options.error.length - 500} more characters)`));
|
|
3117
|
-
}
|
|
3118
|
-
console.log("");
|
|
3119
|
-
let filePaths = options.files || [];
|
|
3120
|
-
if (filePaths.length === 0) {
|
|
3121
|
-
console.log(pc.dim("Extracting file paths from error..."));
|
|
3122
|
-
filePaths = extractFilePaths(options.error, cwd);
|
|
3123
|
-
}
|
|
3124
|
-
if (filePaths.length > 0) {
|
|
3125
|
-
console.log(pc.bold("\nRelevant Files:"));
|
|
3126
|
-
for (const path2 of filePaths) {
|
|
3127
|
-
console.log(` \u{1F4C4} ${relative(cwd, path2)}`);
|
|
3128
|
-
}
|
|
3129
|
-
console.log("");
|
|
3130
|
-
}
|
|
3131
|
-
const fileContents = readFileContents(filePaths);
|
|
3132
|
-
let context;
|
|
3133
|
-
if (options.context) {
|
|
3134
|
-
try {
|
|
3135
|
-
context = JSON.parse(options.context);
|
|
3136
|
-
} catch {
|
|
3137
|
-
context = options.context;
|
|
3138
|
-
}
|
|
3139
|
-
}
|
|
3140
|
-
console.log(pc.bold("Generating Fix...\n"));
|
|
3141
|
-
const fix = await generateFixWithLLM(options.error, fileContents, context);
|
|
3142
|
-
console.log(pc.bold("Analysis:"));
|
|
3143
|
-
console.log(` ${fix.analysis}`);
|
|
3144
|
-
console.log("");
|
|
3145
|
-
console.log(pc.bold("Root Cause:"));
|
|
3146
|
-
console.log(` ${fix.rootCause}`);
|
|
3147
|
-
console.log("");
|
|
3148
|
-
console.log(pc.bold("Suggested Fix:"));
|
|
3149
|
-
console.log(` ${fix.suggestedFix.description}`);
|
|
3150
|
-
console.log(` Confidence: ${Math.round(fix.confidence * 100)}%`);
|
|
3151
|
-
console.log("");
|
|
3152
|
-
if (fix.suggestedFix.files.length === 0) {
|
|
3153
|
-
console.log(
|
|
3154
|
-
pc.yellow("\u26A0\uFE0F No automatic fix available. Manual intervention required.")
|
|
3155
|
-
);
|
|
3156
|
-
process.exit(1);
|
|
3157
|
-
}
|
|
3158
|
-
if (fix.confidence < 0.5) {
|
|
3159
|
-
console.log(
|
|
3160
|
-
pc.yellow(
|
|
3161
|
-
`\u26A0\uFE0F Low confidence (${Math.round(fix.confidence * 100)}%). Skipping automatic fix.`
|
|
1458
|
+
spinner5.stop("\u2717 Failed to analyze project");
|
|
1459
|
+
console.error(
|
|
1460
|
+
chalk10.red(
|
|
1461
|
+
`Error: ${error instanceof Error ? error.message : String(error)}`
|
|
3162
1462
|
)
|
|
3163
1463
|
);
|
|
3164
1464
|
process.exit(1);
|
|
3165
1465
|
}
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
});
|
|
3171
|
-
if (p7.isCancel(shouldApply) || !shouldApply) {
|
|
3172
|
-
console.log(pc.dim("Fix cancelled."));
|
|
3173
|
-
process.exit(0);
|
|
3174
|
-
}
|
|
1466
|
+
console.log("\n" + formatAuditReportColored(report));
|
|
1467
|
+
if (options.audit) {
|
|
1468
|
+
console.log(chalk10.dim("\n--audit mode: No changes applied.\n"));
|
|
1469
|
+
return;
|
|
3175
1470
|
}
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
console.log(pc.yellow("\n\u26A0\uFE0F Dry run complete. No changes were made."));
|
|
3180
|
-
process.exit(0);
|
|
1471
|
+
if (report.totalChanges === 0 && report.allDevDependencies.length === 0) {
|
|
1472
|
+
p6.outro(chalk10.green("\u2713 Project is already fully configured!"));
|
|
1473
|
+
return;
|
|
3181
1474
|
}
|
|
3182
|
-
if (options.
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
`;
|
|
3191
|
-
const result = await commitAndPush(commitMessage, cwd);
|
|
3192
|
-
if (result.success) {
|
|
3193
|
-
console.log(pc.green("\u2705 Fix applied, committed, and pushed!"));
|
|
3194
|
-
} else {
|
|
3195
|
-
console.log(pc.red(`\u274C Failed to commit/push: ${result.error}`));
|
|
3196
|
-
process.exit(1);
|
|
1475
|
+
if (!options.yes) {
|
|
1476
|
+
const shouldProceed = await p6.confirm({
|
|
1477
|
+
message: `Apply ${report.totalChanges} changes and install ${report.allDevDependencies.length} packages?`,
|
|
1478
|
+
initialValue: true
|
|
1479
|
+
});
|
|
1480
|
+
if (p6.isCancel(shouldProceed) || !shouldProceed) {
|
|
1481
|
+
p6.cancel("Setup cancelled");
|
|
1482
|
+
process.exit(0);
|
|
3197
1483
|
}
|
|
3198
1484
|
} else {
|
|
3199
|
-
console.log(
|
|
3200
|
-
}
|
|
3201
|
-
}
|
|
3202
|
-
|
|
3203
|
-
// src/cli/commands/visual.ts
|
|
3204
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
3205
|
-
import { dirname as dirname3, join as join11 } from "path";
|
|
3206
|
-
import * as p8 from "@clack/prompts";
|
|
3207
|
-
import pc2 from "picocolors";
|
|
3208
|
-
function getBaselinesDir() {
|
|
3209
|
-
const cwd = process.cwd();
|
|
3210
|
-
return join11(cwd, ".visual-baselines");
|
|
3211
|
-
}
|
|
3212
|
-
function getMetadataPath() {
|
|
3213
|
-
return join11(getBaselinesDir(), "baselines.json");
|
|
3214
|
-
}
|
|
3215
|
-
function loadMetadata() {
|
|
3216
|
-
const metadataPath = getMetadataPath();
|
|
3217
|
-
if (!existsSync11(metadataPath)) {
|
|
3218
|
-
return {};
|
|
3219
|
-
}
|
|
3220
|
-
try {
|
|
3221
|
-
return JSON.parse(readFileSync3(metadataPath, "utf-8"));
|
|
3222
|
-
} catch {
|
|
3223
|
-
return {};
|
|
3224
|
-
}
|
|
3225
|
-
}
|
|
3226
|
-
function saveMetadata(metadata) {
|
|
3227
|
-
const dir = getBaselinesDir();
|
|
3228
|
-
if (!existsSync11(dir)) {
|
|
3229
|
-
mkdirSync2(dir, { recursive: true });
|
|
3230
|
-
}
|
|
3231
|
-
writeFileSync4(getMetadataPath(), JSON.stringify(metadata, null, 2));
|
|
3232
|
-
}
|
|
3233
|
-
async function checkPlaywright() {
|
|
3234
|
-
try {
|
|
3235
|
-
await import("playwright");
|
|
3236
|
-
return true;
|
|
3237
|
-
} catch {
|
|
3238
|
-
return false;
|
|
3239
|
-
}
|
|
3240
|
-
}
|
|
3241
|
-
async function visualCaptureCommand(name, url, options) {
|
|
3242
|
-
console.log(pc2.cyan("\n\u{1F4F8} Capturing Visual Baseline\n"));
|
|
3243
|
-
const hasPlaywright = await checkPlaywright();
|
|
3244
|
-
if (!hasPlaywright) {
|
|
3245
|
-
console.log(pc2.yellow("\u26A0\uFE0F Playwright is not installed."));
|
|
3246
|
-
console.log(pc2.dim("Install it with: npm install playwright"));
|
|
3247
|
-
console.log(
|
|
3248
|
-
pc2.dim("Then install browsers: npx playwright install chromium")
|
|
3249
|
-
);
|
|
3250
|
-
process.exit(1);
|
|
1485
|
+
console.log(chalk10.dim("\n--yes mode: Auto-approving all changes.\n"));
|
|
3251
1486
|
}
|
|
3252
|
-
const width = parseInt(options.width || "1280");
|
|
3253
|
-
const height = parseInt(options.height || "720");
|
|
3254
|
-
const delay = parseInt(options.delay || "0");
|
|
3255
|
-
console.log(` Name: ${pc2.bold(name)}`);
|
|
3256
|
-
console.log(` URL: ${url}`);
|
|
3257
|
-
console.log(` Viewport: ${width}x${height}`);
|
|
3258
1487
|
console.log("");
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
})
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
await page.waitForSelector(options.waitFor, { timeout: 1e4 });
|
|
3271
|
-
}
|
|
3272
|
-
if (delay > 0) {
|
|
3273
|
-
console.log(pc2.dim(` Waiting ${delay}ms...`));
|
|
3274
|
-
await page.waitForTimeout(delay);
|
|
3275
|
-
}
|
|
3276
|
-
const baselinesDir = getBaselinesDir();
|
|
3277
|
-
const outputPath = options.output || join11(baselinesDir, "screenshots", `${name}.png`);
|
|
3278
|
-
const outputDir = dirname3(outputPath);
|
|
3279
|
-
if (!existsSync11(outputDir)) {
|
|
3280
|
-
mkdirSync2(outputDir, { recursive: true });
|
|
1488
|
+
const setupSpinner = p6.spinner();
|
|
1489
|
+
const stepMessages = [];
|
|
1490
|
+
const results = await runAllSetups(cwd, (step, status) => {
|
|
1491
|
+
if (status === "start") {
|
|
1492
|
+
setupSpinner.start(step);
|
|
1493
|
+
} else if (status === "done") {
|
|
1494
|
+
setupSpinner.stop(`\u2713 ${step}`);
|
|
1495
|
+
stepMessages.push(`\u2713 ${step}`);
|
|
1496
|
+
} else {
|
|
1497
|
+
setupSpinner.stop(`\u2717 ${step}`);
|
|
1498
|
+
stepMessages.push(`\u2717 ${step}`);
|
|
3281
1499
|
}
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
fullPage: options.fullPage
|
|
3286
|
-
});
|
|
3287
|
-
await browser.close();
|
|
3288
|
-
const metadata = loadMetadata();
|
|
3289
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3290
|
-
metadata[name] = {
|
|
3291
|
-
name,
|
|
3292
|
-
url,
|
|
3293
|
-
path: outputPath,
|
|
3294
|
-
width,
|
|
3295
|
-
height,
|
|
3296
|
-
createdAt: metadata[name]?.createdAt || now,
|
|
3297
|
-
updatedAt: now
|
|
3298
|
-
};
|
|
3299
|
-
saveMetadata(metadata);
|
|
3300
|
-
console.log(pc2.green(`
|
|
3301
|
-
\u2705 Baseline "${name}" saved to ${outputPath}`));
|
|
3302
|
-
} catch (error) {
|
|
3303
|
-
console.error(pc2.red("\n\u274C Failed to capture screenshot:"), error);
|
|
3304
|
-
process.exit(1);
|
|
3305
|
-
}
|
|
3306
|
-
}
|
|
3307
|
-
async function visualCompareCommand(url, options) {
|
|
3308
|
-
console.log(pc2.cyan("\n\u{1F50D} Comparing Against Baseline\n"));
|
|
3309
|
-
const metadata = loadMetadata();
|
|
3310
|
-
const baseline = metadata[options.baseline];
|
|
3311
|
-
if (!baseline) {
|
|
3312
|
-
console.log(pc2.red(`\u274C Baseline "${options.baseline}" not found`));
|
|
3313
|
-
console.log(pc2.dim("Available baselines:"));
|
|
3314
|
-
Object.keys(metadata).forEach((name) => {
|
|
3315
|
-
console.log(pc2.dim(` - ${name}`));
|
|
3316
|
-
});
|
|
3317
|
-
process.exit(1);
|
|
3318
|
-
}
|
|
3319
|
-
if (!existsSync11(baseline.path)) {
|
|
3320
|
-
console.log(pc2.red(`\u274C Baseline screenshot not found: ${baseline.path}`));
|
|
3321
|
-
process.exit(1);
|
|
3322
|
-
}
|
|
3323
|
-
console.log(` Baseline: ${pc2.bold(options.baseline)}`);
|
|
3324
|
-
console.log(` URL: ${url}`);
|
|
3325
|
-
console.log(` Viewport: ${baseline.width}x${baseline.height}`);
|
|
1500
|
+
});
|
|
1501
|
+
const successCount = results.filter((r) => r.success).length;
|
|
1502
|
+
const failCount = results.filter((r) => !r.success).length;
|
|
3326
1503
|
console.log("");
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
1504
|
+
if (failCount === 0) {
|
|
1505
|
+
p6.outro(
|
|
1506
|
+
chalk10.green(
|
|
1507
|
+
`\u2713 Auto-setup complete! (${successCount} configurations applied)`
|
|
1508
|
+
)
|
|
1509
|
+
);
|
|
1510
|
+
} else {
|
|
1511
|
+
p6.outro(
|
|
1512
|
+
chalk10.yellow(
|
|
1513
|
+
`\u26A0 Setup completed with issues: ${successCount} succeeded, ${failCount} failed`
|
|
1514
|
+
)
|
|
1515
|
+
);
|
|
3331
1516
|
}
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
1517
|
+
console.log(chalk10.dim("\nNext steps:"));
|
|
1518
|
+
console.log(chalk10.dim(" 1. Review the generated configuration files"));
|
|
1519
|
+
console.log(chalk10.dim(" 2. Run: pnpm verify (or npm/yarn)"));
|
|
1520
|
+
console.log(chalk10.dim(" 3. Commit your changes\n"));
|
|
1521
|
+
}
|
|
1522
|
+
function formatAuditReportColored(report) {
|
|
1523
|
+
const lines = [];
|
|
1524
|
+
lines.push(chalk10.bold("\u{1F4CB} Audit Report\n"));
|
|
1525
|
+
lines.push(chalk10.dim(`Framework: ${report.analysis.framework}`));
|
|
1526
|
+
lines.push(chalk10.dim(`Package Manager: ${report.analysis.packageManager}`));
|
|
1527
|
+
lines.push(
|
|
1528
|
+
chalk10.dim(`TypeScript: ${report.analysis.isTypeScript ? "Yes" : "No"}`)
|
|
1529
|
+
);
|
|
1530
|
+
lines.push(
|
|
1531
|
+
chalk10.dim(`Monorepo: ${report.analysis.isMonorepo ? "Yes" : "No"}`)
|
|
1532
|
+
);
|
|
1533
|
+
lines.push("");
|
|
1534
|
+
for (const plan of report.plans) {
|
|
1535
|
+
const hasChanges = plan.changes.some((c) => c.type !== "unchanged");
|
|
1536
|
+
const icon = hasChanges ? "\u{1F527}" : "\u2713";
|
|
1537
|
+
const titleColor = hasChanges ? chalk10.yellow : chalk10.green;
|
|
1538
|
+
lines.push(
|
|
1539
|
+
titleColor(
|
|
1540
|
+
`${icon} ${plan.name.charAt(0).toUpperCase() + plan.name.slice(1)} - ${plan.description}`
|
|
1541
|
+
)
|
|
3346
1542
|
);
|
|
3347
|
-
const
|
|
3348
|
-
|
|
3349
|
-
|
|
1543
|
+
for (const change of plan.changes) {
|
|
1544
|
+
let symbol;
|
|
1545
|
+
let line;
|
|
1546
|
+
switch (change.type) {
|
|
1547
|
+
case "add":
|
|
1548
|
+
symbol = chalk10.green("+");
|
|
1549
|
+
line = chalk10.green(change.description);
|
|
1550
|
+
break;
|
|
1551
|
+
case "modify":
|
|
1552
|
+
symbol = chalk10.yellow("~");
|
|
1553
|
+
line = chalk10.yellow(change.description);
|
|
1554
|
+
if (change.key && change.oldValue !== void 0 && change.newValue !== void 0) {
|
|
1555
|
+
line += chalk10.dim(
|
|
1556
|
+
` (${String(change.oldValue)} \u2192 ${String(change.newValue)})`
|
|
1557
|
+
);
|
|
1558
|
+
}
|
|
1559
|
+
break;
|
|
1560
|
+
case "unchanged":
|
|
1561
|
+
default:
|
|
1562
|
+
symbol = chalk10.dim("=");
|
|
1563
|
+
line = chalk10.dim(change.description);
|
|
1564
|
+
}
|
|
1565
|
+
lines.push(` ${symbol} ${line}`);
|
|
3350
1566
|
}
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
const baselineBuffer = readFileSync3(baseline.path);
|
|
3355
|
-
const compareBuffer = readFileSync3(comparePath);
|
|
3356
|
-
const areSameSize = baselineBuffer.length === compareBuffer.length;
|
|
3357
|
-
const areIdentical = areSameSize && baselineBuffer.equals(compareBuffer);
|
|
3358
|
-
console.log("");
|
|
3359
|
-
if (areIdentical) {
|
|
3360
|
-
console.log(pc2.green("\u2705 Screenshots are identical"));
|
|
3361
|
-
} else {
|
|
3362
|
-
console.log(pc2.yellow("\u26A0\uFE0F Screenshots differ"));
|
|
3363
|
-
console.log(pc2.dim(` Baseline size: ${baselineBuffer.length} bytes`));
|
|
3364
|
-
console.log(pc2.dim(` Current size: ${compareBuffer.length} bytes`));
|
|
3365
|
-
console.log("");
|
|
3366
|
-
console.log(
|
|
3367
|
-
pc2.dim(
|
|
3368
|
-
"For detailed LLM-based comparison, use the GitHub App's visual testing."
|
|
3369
|
-
)
|
|
1567
|
+
if (plan.devDependencies.length > 0) {
|
|
1568
|
+
lines.push(
|
|
1569
|
+
chalk10.blue(` \u{1F4E6} Install: ${plan.devDependencies.join(", ")}`)
|
|
3370
1570
|
);
|
|
3371
|
-
console.log(pc2.dim(`Comparison saved to: ${comparePath}`));
|
|
3372
|
-
process.exit(1);
|
|
3373
1571
|
}
|
|
3374
|
-
|
|
3375
|
-
console.error(pc2.red("\n\u274C Failed to compare:"), error);
|
|
3376
|
-
process.exit(1);
|
|
3377
|
-
}
|
|
3378
|
-
}
|
|
3379
|
-
async function visualListCommand(options) {
|
|
3380
|
-
const metadata = loadMetadata();
|
|
3381
|
-
const baselines = Object.values(metadata);
|
|
3382
|
-
if (options.json) {
|
|
3383
|
-
console.log(JSON.stringify(baselines, null, 2));
|
|
3384
|
-
return;
|
|
3385
|
-
}
|
|
3386
|
-
if (baselines.length === 0) {
|
|
3387
|
-
console.log(pc2.dim("No baselines found."));
|
|
3388
|
-
console.log(pc2.dim("Create one with: workflow visual capture <name> <url>"));
|
|
3389
|
-
return;
|
|
3390
|
-
}
|
|
3391
|
-
console.log(pc2.cyan("\n\u{1F4F8} Visual Baselines\n"));
|
|
3392
|
-
for (const baseline of baselines) {
|
|
3393
|
-
console.log(` ${pc2.bold(baseline.name)}`);
|
|
3394
|
-
console.log(` URL: ${baseline.url}`);
|
|
3395
|
-
console.log(` Viewport: ${baseline.width}x${baseline.height}`);
|
|
3396
|
-
console.log(` Path: ${baseline.path}`);
|
|
3397
|
-
console.log(` Updated: ${baseline.updatedAt}`);
|
|
3398
|
-
console.log("");
|
|
3399
|
-
}
|
|
3400
|
-
}
|
|
3401
|
-
async function visualUpdateCommand(name, options) {
|
|
3402
|
-
const metadata = loadMetadata();
|
|
3403
|
-
const baseline = metadata[name];
|
|
3404
|
-
if (!baseline) {
|
|
3405
|
-
console.log(pc2.red(`\u274C Baseline "${name}" not found`));
|
|
3406
|
-
process.exit(1);
|
|
1572
|
+
lines.push("");
|
|
3407
1573
|
}
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
});
|
|
3417
|
-
}
|
|
3418
|
-
async function visualApproveCommand(name) {
|
|
3419
|
-
const metadata = loadMetadata();
|
|
3420
|
-
const baseline = metadata[name];
|
|
3421
|
-
if (!baseline) {
|
|
3422
|
-
console.log(pc2.red(`\u274C Baseline "${name}" not found`));
|
|
3423
|
-
process.exit(1);
|
|
3424
|
-
}
|
|
3425
|
-
const confirm8 = await p8.confirm({
|
|
3426
|
-
message: `Update baseline "${name}" with the latest comparison?`,
|
|
3427
|
-
initialValue: false
|
|
3428
|
-
});
|
|
3429
|
-
if (p8.isCancel(confirm8) || !confirm8) {
|
|
3430
|
-
console.log(pc2.dim("Cancelled."));
|
|
3431
|
-
return;
|
|
3432
|
-
}
|
|
3433
|
-
const baselinesDir = getBaselinesDir();
|
|
3434
|
-
const comparisonsDir = join11(baselinesDir, "comparisons");
|
|
3435
|
-
if (!existsSync11(comparisonsDir)) {
|
|
3436
|
-
console.log(pc2.yellow("No comparisons found to approve."));
|
|
3437
|
-
return;
|
|
1574
|
+
if (report.allDevDependencies.length > 0) {
|
|
1575
|
+
lines.push(chalk10.bold("Dependencies to install (batched):"));
|
|
1576
|
+
const pm = report.analysis.packageManager;
|
|
1577
|
+
const cmd = pm === "npm" ? "npm install" : pm === "yarn" ? "yarn add" : `${pm} add`;
|
|
1578
|
+
lines.push(
|
|
1579
|
+
chalk10.cyan(` ${cmd} -D ${report.allDevDependencies.join(" ")}`)
|
|
1580
|
+
);
|
|
1581
|
+
lines.push("");
|
|
3438
1582
|
}
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
1583
|
+
lines.push(
|
|
1584
|
+
chalk10.bold(
|
|
1585
|
+
`Total: ${report.totalChanges} changes, ${report.allDevDependencies.length} packages`
|
|
3442
1586
|
)
|
|
3443
1587
|
);
|
|
1588
|
+
return lines.join("\n");
|
|
3444
1589
|
}
|
|
3445
1590
|
|
|
3446
1591
|
// src/cli/index.ts
|
|
3447
|
-
var program = new
|
|
1592
|
+
var program = new Command();
|
|
3448
1593
|
program.name("workflow").description(
|
|
3449
1594
|
"A self-evolving workflow management system for AI agent development"
|
|
3450
1595
|
).version("1.0.0");
|
|
@@ -3459,43 +1604,19 @@ program.command("validate <type>").description("Validate branch name, commit mes
|
|
|
3459
1604
|
"--suggest-on-error",
|
|
3460
1605
|
"Offer improvement suggestions on validation errors"
|
|
3461
1606
|
).action(validateCommand);
|
|
3462
|
-
program.
|
|
1607
|
+
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);
|
|
3463
1608
|
program.command("suggest").description("Submit an improvement suggestion").argument("<feedback>", "Your improvement suggestion").option("--author <author>", "Your name or username").option(
|
|
3464
1609
|
"--category <category>",
|
|
3465
1610
|
"Category: feature, bug, documentation, performance, other"
|
|
3466
1611
|
).action(suggestCommand);
|
|
3467
1612
|
program.command("setup").description("Add workflow scripts to package.json").action(setupCommand);
|
|
3468
|
-
program.command("doctor").description("Run health check and get optimization suggestions").
|
|
3469
|
-
"--check-guidelines-only",
|
|
3470
|
-
"Only check mandatory guidelines exist (exits 0 or 1)"
|
|
3471
|
-
).action(doctorCommand);
|
|
3472
|
-
program.command("hooks").description("Manage git hooks (install, uninstall, status)").argument("<action>", "Action: install, uninstall, status").action(hooksCommand);
|
|
3473
|
-
program.command("github").description("Manage GitHub Actions CI (setup, check)").argument("<action>", "Action: setup, check").action(githubCommand);
|
|
1613
|
+
program.command("doctor").description("Run health check and get optimization suggestions").action(doctorCommand);
|
|
3474
1614
|
program.command("scope:create").description("Create a custom scope package").option("--name <name>", 'Package name (e.g., "fintech", "gaming")').option(
|
|
3475
1615
|
"--scopes <scopes>",
|
|
3476
1616
|
"Comma-separated scopes (format: name:description:emoji:category)"
|
|
3477
1617
|
).option("--preset-name <preset>", "Preset display name").option("--output-dir <dir>", "Output directory").option("--no-test", "Skip test file generation").action(scopeCreateCommand);
|
|
3478
1618
|
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);
|
|
3479
|
-
program.command("
|
|
3480
|
-
|
|
3481
|
-
error: options.error,
|
|
3482
|
-
context: options.context,
|
|
3483
|
-
files: options.files?.split(","),
|
|
3484
|
-
auto: options.auto,
|
|
3485
|
-
dryRun: options.dryRun
|
|
3486
|
-
});
|
|
3487
|
-
});
|
|
3488
|
-
var visual = program.command("visual").description("Visual testing commands");
|
|
3489
|
-
visual.command("capture").description("Capture a new baseline screenshot").argument("<name>", "Name for the baseline").argument("<url>", "URL to capture").option("-w, --width <width>", "Viewport width", "1280").option("-h, --height <height>", "Viewport height", "720").option("--full-page", "Capture full page").option("-o, --output <path>", "Output path").option("--wait-for <selector>", "Wait for selector before capture").option("--delay <ms>", "Additional delay in ms").action(visualCaptureCommand);
|
|
3490
|
-
visual.command("compare").description("Compare a URL against a baseline").argument("<url>", "URL to compare").option("-b, --baseline <name>", "Baseline name to compare against (required)").option("-o, --output <path>", "Output path for comparison screenshot").option("-t, --threshold <percent>", "Difference threshold percentage").action((url, options) => {
|
|
3491
|
-
if (!options.baseline) {
|
|
3492
|
-
console.error("Error: --baseline is required");
|
|
3493
|
-
process.exit(1);
|
|
3494
|
-
}
|
|
3495
|
-
visualCompareCommand(url, options);
|
|
3496
|
-
});
|
|
3497
|
-
visual.command("list").description("List all baselines").option("--json", "Output as JSON").action(visualListCommand);
|
|
3498
|
-
visual.command("update").description("Update an existing baseline").argument("<name>", "Baseline name to update").option("-w, --width <width>", "Override viewport width").option("-h, --height <height>", "Override viewport height").option("--full-page", "Capture full page").action(visualUpdateCommand);
|
|
3499
|
-
visual.command("approve").description("Approve a comparison as the new baseline").argument("<name>", "Baseline name to approve").action(visualApproveCommand);
|
|
1619
|
+
program.command("verify").description("Run all quality checks with fix-and-revalidate pattern").option("--fix", "Enable auto-fix for lint and format issues").option("--max-retries <n>", "Maximum retry cycles (default: 10)", "10").option("--commit", "Commit changes if all checks pass").option("--dry-run", "Preview fixes without applying them").action(verifyCommand);
|
|
1620
|
+
program.command("auto-setup").description("Automatically configure linting, formatting, testing, and CI").option("-y, --yes", "Auto-approve all prompts").option("--audit", "Show audit report without applying changes").action(autoSetupCommand);
|
|
3500
1621
|
program.parse();
|
|
3501
1622
|
//# sourceMappingURL=index.js.map
|