workflow-agent-cli 2.2.1 → 2.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-AN6UOHBB.js +308 -0
- package/dist/chunk-AN6UOHBB.js.map +1 -0
- package/dist/chunk-RDVTKGQV.js +165 -0
- package/dist/chunk-RDVTKGQV.js.map +1 -0
- package/dist/cli/index.js +247 -2316
- 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 +47 -2
- package/dist/index.js +17 -9
- package/dist/schema-OJg7YAI_.d.ts +181 -0
- package/dist/scripts/postinstall.js +0 -0
- package/dist/validators/index.d.ts +1 -1
- package/package.json +12 -17
- package/LICENSE +0 -21
- 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,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
hasUncommittedChanges,
|
|
4
|
+
runAllChecks,
|
|
5
|
+
stageAllChanges
|
|
6
|
+
} from "../chunk-AN6UOHBB.js";
|
|
2
7
|
import {
|
|
3
8
|
validateBranchName,
|
|
4
9
|
validateCommitMessage,
|
|
5
10
|
validatePRTitle
|
|
6
11
|
} from "../chunk-NMHWD2GA.js";
|
|
7
12
|
import {
|
|
8
|
-
DEFAULT_RESERVED_SCOPE_NAMES,
|
|
9
13
|
hasConfig,
|
|
10
14
|
loadConfig,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
validateScopeName
|
|
14
|
-
} from "../chunk-JCDT4363.js";
|
|
15
|
+
validateScopeDefinitions
|
|
16
|
+
} from "../chunk-RDVTKGQV.js";
|
|
15
17
|
|
|
16
18
|
// src/cli/index.ts
|
|
17
|
-
import { Command
|
|
19
|
+
import { Command } from "commander";
|
|
18
20
|
|
|
19
21
|
// src/cli/commands/init.ts
|
|
20
22
|
import * as p from "@clack/prompts";
|
|
21
23
|
import chalk from "chalk";
|
|
22
|
-
import { existsSync
|
|
23
|
-
import { writeFile
|
|
24
|
-
import { join
|
|
24
|
+
import { existsSync } from "fs";
|
|
25
|
+
import { writeFile, mkdir } from "fs/promises";
|
|
26
|
+
import { join, dirname } from "path";
|
|
25
27
|
import { fileURLToPath } from "url";
|
|
26
28
|
|
|
27
29
|
// src/templates/renderer.ts
|
|
@@ -192,6 +194,18 @@ async function renderTemplateFile(templatePath, outputPath, context) {
|
|
|
192
194
|
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
193
195
|
await fs.writeFile(outputPath, rendered, "utf-8");
|
|
194
196
|
}
|
|
197
|
+
async function renderTemplateDirectory(templateDir, outputDir, context) {
|
|
198
|
+
const files = await fs.readdir(templateDir);
|
|
199
|
+
const rendered = [];
|
|
200
|
+
for (const file of files) {
|
|
201
|
+
if (!file.match(/\.(md|ts|json)$/)) continue;
|
|
202
|
+
const templatePath = path.join(templateDir, file);
|
|
203
|
+
const outputPath = path.join(outputDir, file);
|
|
204
|
+
await renderTemplateFile(templatePath, outputPath, context);
|
|
205
|
+
rendered.push(file);
|
|
206
|
+
}
|
|
207
|
+
return rendered;
|
|
208
|
+
}
|
|
195
209
|
async function getProjectName(projectPath) {
|
|
196
210
|
try {
|
|
197
211
|
const pkgPath = path.join(projectPath, "package.json");
|
|
@@ -222,694 +236,13 @@ async function validateTemplateDirectory(templateDir) {
|
|
|
222
236
|
}
|
|
223
237
|
}
|
|
224
238
|
|
|
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
239
|
// src/cli/commands/init.ts
|
|
907
240
|
var __filename = fileURLToPath(import.meta.url);
|
|
908
241
|
var __dirname = dirname(__filename);
|
|
909
242
|
async function initCommand(options) {
|
|
910
243
|
console.log(chalk.bold.cyan("\n\u{1F680} Workflow Agent Initialization\n"));
|
|
911
244
|
const cwd = process.cwd();
|
|
912
|
-
const isNonInteractive = !!(options.preset && options.name)
|
|
245
|
+
const isNonInteractive = !!(options.preset && options.name);
|
|
913
246
|
if (hasConfig(cwd) && !options.yes && !isNonInteractive) {
|
|
914
247
|
const shouldContinue = await p.confirm({
|
|
915
248
|
message: "Workflow Agent is already configured. Continue and overwrite?",
|
|
@@ -967,10 +300,10 @@ async function initCommand(options) {
|
|
|
967
300
|
try {
|
|
968
301
|
const presetModule = await import(`@workflow/scopes-${preset}`);
|
|
969
302
|
scopes = presetModule.scopes || presetModule.default.scopes;
|
|
970
|
-
const
|
|
971
|
-
|
|
303
|
+
const spinner4 = p.spinner();
|
|
304
|
+
spinner4.start(`Loading ${presetModule.default?.name || preset} preset`);
|
|
972
305
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
973
|
-
|
|
306
|
+
spinner4.stop(`\u2713 Loaded ${scopes.length} scopes from preset`);
|
|
974
307
|
} catch (error) {
|
|
975
308
|
console.log(
|
|
976
309
|
chalk.yellow(
|
|
@@ -979,32 +312,16 @@ async function initCommand(options) {
|
|
|
979
312
|
)
|
|
980
313
|
);
|
|
981
314
|
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
|
-
}
|
|
315
|
+
{ name: "feat", description: "New features", emoji: "\u2728" },
|
|
316
|
+
{ name: "fix", description: "Bug fixes", emoji: "\u{1F41B}" },
|
|
317
|
+
{ name: "docs", description: "Documentation", emoji: "\u{1F4DA}" }
|
|
993
318
|
];
|
|
994
319
|
}
|
|
995
320
|
} else {
|
|
996
321
|
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
|
-
}
|
|
322
|
+
{ name: "feat", description: "New features", emoji: "\u2728" },
|
|
323
|
+
{ name: "fix", description: "Bug fixes", emoji: "\u{1F41B}" },
|
|
324
|
+
{ name: "docs", description: "Documentation", emoji: "\u{1F4DA}" }
|
|
1008
325
|
];
|
|
1009
326
|
console.log(
|
|
1010
327
|
chalk.dim(
|
|
@@ -1016,169 +333,48 @@ async function initCommand(options) {
|
|
|
1016
333
|
projectName,
|
|
1017
334
|
scopes,
|
|
1018
335
|
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
|
|
336
|
+
language: "en"
|
|
1031
337
|
};
|
|
1032
|
-
const configPath =
|
|
1033
|
-
await
|
|
1034
|
-
const workflowDir =
|
|
1035
|
-
if (!
|
|
1036
|
-
await
|
|
1037
|
-
}
|
|
1038
|
-
const
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
\u{1F4CB} Generating ${mandatoryTemplates.length} mandatory guidelines...`
|
|
1046
|
-
)
|
|
1047
|
-
);
|
|
1048
|
-
const guidelinesDir = join4(cwd, "guidelines");
|
|
1049
|
-
const templatesDir = join4(__dirname, "../../templates");
|
|
1050
|
-
let mandatoryGenerated = 0;
|
|
1051
|
-
let optionalGenerated = 0;
|
|
1052
|
-
try {
|
|
1053
|
-
await validateTemplateDirectory(templatesDir);
|
|
1054
|
-
const context = await buildTemplateContext(config, cwd);
|
|
1055
|
-
await mkdir3(guidelinesDir, { recursive: true });
|
|
1056
|
-
for (const template of mandatoryTemplates) {
|
|
1057
|
-
try {
|
|
1058
|
-
const templatePath = join4(templatesDir, template.filename);
|
|
1059
|
-
const outputPath = join4(guidelinesDir, template.filename);
|
|
1060
|
-
await renderTemplateFile(templatePath, outputPath, {
|
|
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
|
-
}
|
|
1092
|
-
console.log(
|
|
1093
|
-
chalk.green(`\u2713 Generated ${optionalGenerated} optional guidelines`)
|
|
1094
|
-
);
|
|
1095
|
-
}
|
|
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."));
|
|
338
|
+
const configPath = join(cwd, "workflow.config.json");
|
|
339
|
+
await writeFile(configPath, JSON.stringify(config, null, 2));
|
|
340
|
+
const workflowDir = join(cwd, ".workflow");
|
|
341
|
+
if (!existsSync(workflowDir)) {
|
|
342
|
+
await mkdir(workflowDir, { recursive: true });
|
|
343
|
+
}
|
|
344
|
+
const shouldGenerateGuidelines = await p.confirm({
|
|
345
|
+
message: "Generate workflow guidelines from templates?",
|
|
346
|
+
initialValue: true
|
|
347
|
+
});
|
|
348
|
+
if (p.isCancel(shouldGenerateGuidelines)) {
|
|
349
|
+
p.cancel("Initialization cancelled");
|
|
350
|
+
process.exit(0);
|
|
1104
351
|
}
|
|
1105
|
-
if (
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
config.hooks || {
|
|
1119
|
-
enabled: true,
|
|
1120
|
-
preCommit: ["validate-branch", "check-guidelines"],
|
|
1121
|
-
commitMsg: ["validate-commit"]
|
|
1122
|
-
},
|
|
1123
|
-
cwd
|
|
352
|
+
if (shouldGenerateGuidelines) {
|
|
353
|
+
const spinner4 = p.spinner();
|
|
354
|
+
spinner4.start("Generating guidelines...");
|
|
355
|
+
try {
|
|
356
|
+
const templatesDir = join(__dirname, "../../templates");
|
|
357
|
+
await validateTemplateDirectory(templatesDir);
|
|
358
|
+
const context = await buildTemplateContext(config, cwd);
|
|
359
|
+
const guidelinesDir = join(cwd, "guidelines");
|
|
360
|
+
await mkdir(guidelinesDir, { recursive: true });
|
|
361
|
+
const renderedFiles = await renderTemplateDirectory(
|
|
362
|
+
templatesDir,
|
|
363
|
+
guidelinesDir,
|
|
364
|
+
context
|
|
1124
365
|
);
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
} else {
|
|
1129
|
-
hookSpinner.stop("\u26A0\uFE0F Some hooks could not be installed");
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
if (repoInfo.isGitHub) {
|
|
1134
|
-
const existingCI = hasCIWorkflow(cwd);
|
|
1135
|
-
if (!existingCI) {
|
|
366
|
+
spinner4.stop(`\u2713 Generated ${renderedFiles.length} guideline documents`);
|
|
367
|
+
} catch (error) {
|
|
368
|
+
spinner4.stop("\u26A0\uFE0F Could not generate guidelines");
|
|
1136
369
|
console.log(
|
|
1137
|
-
chalk.
|
|
1138
|
-
|
|
370
|
+
chalk.yellow(
|
|
371
|
+
`
|
|
372
|
+
Reason: ${error instanceof Error ? error.message : String(error)}`
|
|
1139
373
|
)
|
|
1140
374
|
);
|
|
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
|
-
}
|
|
375
|
+
console.log(
|
|
376
|
+
chalk.dim("You can manually copy guidelines later if needed.")
|
|
377
|
+
);
|
|
1182
378
|
}
|
|
1183
379
|
}
|
|
1184
380
|
p.outro(chalk.green("\u2713 Workflow Agent initialized successfully!"));
|
|
@@ -1186,24 +382,25 @@ async function initCommand(options) {
|
|
|
1186
382
|
console.log(
|
|
1187
383
|
chalk.dim(" 1. Review your configuration in workflow.config.json")
|
|
1188
384
|
);
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
)
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
if (repoInfo.isGitHub) {
|
|
385
|
+
if (shouldGenerateGuidelines) {
|
|
386
|
+
console.log(
|
|
387
|
+
chalk.dim(" 2. Review generated guidelines in guidelines/ directory")
|
|
388
|
+
);
|
|
389
|
+
console.log(chalk.dim(" 3. Run: workflow validate branch"));
|
|
1195
390
|
console.log(
|
|
1196
|
-
chalk.
|
|
391
|
+
chalk.dim(" 4. Run: workflow doctor (for optimization suggestions)\n")
|
|
1197
392
|
);
|
|
393
|
+
} else {
|
|
394
|
+
console.log(chalk.dim(" 2. Run: workflow validate branch"));
|
|
1198
395
|
console.log(
|
|
1199
|
-
chalk.dim("
|
|
396
|
+
chalk.dim(" 3. Run: workflow doctor (for optimization suggestions)\n")
|
|
1200
397
|
);
|
|
1201
398
|
}
|
|
1202
399
|
}
|
|
1203
400
|
|
|
1204
401
|
// src/cli/commands/validate.ts
|
|
1205
402
|
import chalk2 from "chalk";
|
|
1206
|
-
import { execa
|
|
403
|
+
import { execa } from "execa";
|
|
1207
404
|
async function validateCommand(type, value, _options = {}) {
|
|
1208
405
|
const config = await loadConfig();
|
|
1209
406
|
if (!config) {
|
|
@@ -1218,7 +415,7 @@ async function validateCommand(type, value, _options = {}) {
|
|
|
1218
415
|
switch (type) {
|
|
1219
416
|
case "branch": {
|
|
1220
417
|
if (!targetValue) {
|
|
1221
|
-
const { stdout } = await
|
|
418
|
+
const { stdout } = await execa("git", ["branch", "--show-current"]);
|
|
1222
419
|
targetValue = stdout.trim();
|
|
1223
420
|
}
|
|
1224
421
|
result = await validateBranchName(targetValue, config);
|
|
@@ -1226,7 +423,7 @@ async function validateCommand(type, value, _options = {}) {
|
|
|
1226
423
|
}
|
|
1227
424
|
case "commit": {
|
|
1228
425
|
if (!targetValue) {
|
|
1229
|
-
const { stdout } = await
|
|
426
|
+
const { stdout } = await execa("git", ["log", "-1", "--pretty=%s"]);
|
|
1230
427
|
targetValue = stdout.trim();
|
|
1231
428
|
}
|
|
1232
429
|
result = await validateCommitMessage(targetValue, config);
|
|
@@ -1275,324 +472,10 @@ async function validateCommand(type, value, _options = {}) {
|
|
|
1275
472
|
}
|
|
1276
473
|
|
|
1277
474
|
// 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
475
|
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;
|
|
476
|
+
async function configCommand(action, key, value) {
|
|
477
|
+
console.log(chalk3.yellow("Config command not yet implemented"));
|
|
478
|
+
console.log({ action, key, value });
|
|
1596
479
|
}
|
|
1597
480
|
|
|
1598
481
|
// src/cli/commands/suggest.ts
|
|
@@ -1617,370 +500,53 @@ async function suggestCommand(feedback, options = {}) {
|
|
|
1617
500
|
if (p2.isCancel(categoryChoice)) {
|
|
1618
501
|
p2.cancel("Suggestion cancelled");
|
|
1619
502
|
process.exit(0);
|
|
1620
|
-
}
|
|
1621
|
-
category = categoryChoice;
|
|
1622
|
-
}
|
|
1623
|
-
const result = await tracker.submit(feedback, options.author, category);
|
|
1624
|
-
if (!result.success) {
|
|
1625
|
-
console.log(chalk4.red("\u2717 Suggestion rejected"));
|
|
1626
|
-
console.log(chalk4.dim(` Reason: ${result.error}`));
|
|
1627
|
-
process.exit(1);
|
|
1628
|
-
}
|
|
1629
|
-
console.log(chalk4.green("\u2713 Suggestion submitted successfully!"));
|
|
1630
|
-
console.log(chalk4.dim(` ID: ${result.suggestion?.id}`));
|
|
1631
|
-
console.log(chalk4.dim(` Status: ${result.suggestion?.status}`));
|
|
1632
|
-
console.log(chalk4.dim(` Category: ${result.suggestion?.category}`));
|
|
1633
|
-
console.log(chalk4.dim("\nYour suggestion will be:"));
|
|
1634
|
-
console.log(chalk4.dim(" 1. Reviewed by the community"));
|
|
1635
|
-
console.log(chalk4.dim(" 2. Prioritized based on impact"));
|
|
1636
|
-
console.log(
|
|
1637
|
-
chalk4.dim(" 3. Incorporated into future releases if approved\n")
|
|
1638
|
-
);
|
|
1639
|
-
}
|
|
1640
|
-
|
|
1641
|
-
// src/cli/commands/doctor.ts
|
|
1642
|
-
import chalk5 from "chalk";
|
|
1643
|
-
|
|
1644
|
-
// src/validators/guidelines.ts
|
|
1645
|
-
import { existsSync as existsSync6 } from "fs";
|
|
1646
|
-
import { readFile as readFile3, readdir } from "fs/promises";
|
|
1647
|
-
import { join as join6 } from "path";
|
|
1648
|
-
function getEffectiveMandatoryTemplates(guidelinesConfig) {
|
|
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}`));
|
|
503
|
+
}
|
|
504
|
+
category = categoryChoice;
|
|
1960
505
|
}
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
);
|
|
1965
|
-
|
|
506
|
+
const result = await tracker.submit(feedback, options.author, category);
|
|
507
|
+
if (!result.success) {
|
|
508
|
+
console.log(chalk4.red("\u2717 Suggestion rejected"));
|
|
509
|
+
console.log(chalk4.dim(` Reason: ${result.error}`));
|
|
510
|
+
process.exit(1);
|
|
1966
511
|
}
|
|
1967
|
-
console.log(
|
|
1968
|
-
|
|
1969
|
-
|
|
512
|
+
console.log(chalk4.green("\u2713 Suggestion submitted successfully!"));
|
|
513
|
+
console.log(chalk4.dim(` ID: ${result.suggestion?.id}`));
|
|
514
|
+
console.log(chalk4.dim(` Status: ${result.suggestion?.status}`));
|
|
515
|
+
console.log(chalk4.dim(` Category: ${result.suggestion?.category}`));
|
|
516
|
+
console.log(chalk4.dim("\nYour suggestion will be:"));
|
|
517
|
+
console.log(chalk4.dim(" 1. Reviewed by the community"));
|
|
518
|
+
console.log(chalk4.dim(" 2. Prioritized based on impact"));
|
|
519
|
+
console.log(
|
|
520
|
+
chalk4.dim(" 3. Incorporated into future releases if approved\n")
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// src/cli/commands/doctor.ts
|
|
525
|
+
import chalk5 from "chalk";
|
|
526
|
+
async function doctorCommand() {
|
|
527
|
+
console.log(chalk5.bold.cyan("\n\u{1F3E5} Workflow Agent Health Check\n"));
|
|
528
|
+
const config = await loadConfig();
|
|
529
|
+
if (!config) {
|
|
530
|
+
console.error(chalk5.red("\u2717 No workflow configuration found"));
|
|
531
|
+
console.log(chalk5.yellow(" Run: workflow init"));
|
|
1970
532
|
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
533
|
}
|
|
1976
|
-
console.log("");
|
|
534
|
+
console.log(chalk5.green("\u2713 Configuration loaded successfully"));
|
|
535
|
+
console.log(chalk5.dim(` Project: ${config.projectName}`));
|
|
536
|
+
console.log(chalk5.dim(` Scopes: ${config.scopes.length} configured`));
|
|
537
|
+
console.log(chalk5.dim(` Enforcement: ${config.enforcement}`));
|
|
538
|
+
console.log(chalk5.dim(` Language: ${config.language}`));
|
|
539
|
+
console.log(chalk5.cyan("\n\u{1F4A1} Suggestions:\n"));
|
|
540
|
+
console.log(chalk5.dim(" \u2022 Health check analysis coming soon"));
|
|
541
|
+
console.log(chalk5.dim(" \u2022 Git history analysis coming soon"));
|
|
542
|
+
console.log(chalk5.dim(" \u2022 Optimization recommendations coming soon"));
|
|
1977
543
|
}
|
|
1978
544
|
|
|
1979
545
|
// src/cli/commands/setup.ts
|
|
1980
546
|
import * as p3 from "@clack/prompts";
|
|
1981
547
|
import chalk6 from "chalk";
|
|
1982
|
-
import { readFileSync, writeFileSync
|
|
1983
|
-
import { join as
|
|
548
|
+
import { readFileSync, writeFileSync, existsSync as existsSync2 } from "fs";
|
|
549
|
+
import { join as join2 } from "path";
|
|
1984
550
|
var WORKFLOW_SCRIPTS = {
|
|
1985
551
|
"workflow:init": "workflow-agent init",
|
|
1986
552
|
"workflow:validate": "workflow-agent validate",
|
|
@@ -1990,8 +556,8 @@ var WORKFLOW_SCRIPTS = {
|
|
|
1990
556
|
async function setupCommand() {
|
|
1991
557
|
p3.intro(chalk6.bgBlue(" workflow-agent setup "));
|
|
1992
558
|
const cwd = process.cwd();
|
|
1993
|
-
const packageJsonPath =
|
|
1994
|
-
if (!
|
|
559
|
+
const packageJsonPath = join2(cwd, "package.json");
|
|
560
|
+
if (!existsSync2(packageJsonPath)) {
|
|
1995
561
|
p3.cancel("No package.json found in current directory");
|
|
1996
562
|
process.exit(1);
|
|
1997
563
|
}
|
|
@@ -2034,7 +600,7 @@ async function setupCommand() {
|
|
|
2034
600
|
for (const [scriptName, scriptCommand] of Object.entries(scriptsToAdd)) {
|
|
2035
601
|
packageJson.scripts[scriptName] = scriptCommand;
|
|
2036
602
|
}
|
|
2037
|
-
|
|
603
|
+
writeFileSync(
|
|
2038
604
|
packageJsonPath,
|
|
2039
605
|
JSON.stringify(packageJson, null, 2) + "\n",
|
|
2040
606
|
"utf-8"
|
|
@@ -2052,15 +618,15 @@ async function setupCommand() {
|
|
|
2052
618
|
// src/cli/commands/scope-create.ts
|
|
2053
619
|
import * as p4 from "@clack/prompts";
|
|
2054
620
|
import chalk7 from "chalk";
|
|
2055
|
-
import { existsSync as
|
|
2056
|
-
import { writeFile as
|
|
2057
|
-
import { join as
|
|
621
|
+
import { existsSync as existsSync3 } from "fs";
|
|
622
|
+
import { writeFile as writeFile2, mkdir as mkdir2, readFile } from "fs/promises";
|
|
623
|
+
import { join as join3 } from "path";
|
|
2058
624
|
async function scopeCreateCommand(options) {
|
|
2059
625
|
console.log(chalk7.bold.cyan("\n\u{1F3A8} Create Custom Scope Package\n"));
|
|
2060
626
|
const cwd = process.cwd();
|
|
2061
627
|
const isNonInteractive = !!(options.name && options.scopes && options.presetName);
|
|
2062
|
-
const
|
|
2063
|
-
if (
|
|
628
|
+
const isMonorepo = existsSync3(join3(cwd, "pnpm-workspace.yaml"));
|
|
629
|
+
if (isMonorepo) {
|
|
2064
630
|
console.log(chalk7.dim("\u2713 Detected monorepo workspace\n"));
|
|
2065
631
|
}
|
|
2066
632
|
const packageNameInput = isNonInteractive ? options.name : await p4.text({
|
|
@@ -2204,8 +770,8 @@ async function scopeCreateCommand(options) {
|
|
|
2204
770
|
let outputDir;
|
|
2205
771
|
if (options.outputDir) {
|
|
2206
772
|
outputDir = options.outputDir;
|
|
2207
|
-
} else if (
|
|
2208
|
-
outputDir =
|
|
773
|
+
} else if (isMonorepo) {
|
|
774
|
+
outputDir = join3(cwd, "packages", `scopes-${packageName}`);
|
|
2209
775
|
} else {
|
|
2210
776
|
const customDir = await p4.text({
|
|
2211
777
|
message: "Output directory:",
|
|
@@ -2216,9 +782,9 @@ async function scopeCreateCommand(options) {
|
|
|
2216
782
|
p4.cancel("Operation cancelled");
|
|
2217
783
|
process.exit(0);
|
|
2218
784
|
}
|
|
2219
|
-
outputDir =
|
|
785
|
+
outputDir = join3(cwd, customDir);
|
|
2220
786
|
}
|
|
2221
|
-
if (
|
|
787
|
+
if (existsSync3(outputDir)) {
|
|
2222
788
|
const shouldOverwrite = await p4.confirm({
|
|
2223
789
|
message: `Directory ${outputDir} already exists. Overwrite?`,
|
|
2224
790
|
initialValue: false
|
|
@@ -2228,10 +794,10 @@ async function scopeCreateCommand(options) {
|
|
|
2228
794
|
process.exit(0);
|
|
2229
795
|
}
|
|
2230
796
|
}
|
|
2231
|
-
const
|
|
2232
|
-
|
|
797
|
+
const spinner4 = p4.spinner();
|
|
798
|
+
spinner4.start("Creating package structure...");
|
|
2233
799
|
try {
|
|
2234
|
-
await
|
|
800
|
+
await mkdir2(join3(outputDir, "src"), { recursive: true });
|
|
2235
801
|
const packageJson = {
|
|
2236
802
|
name: `@workflow/scopes-${packageName}`,
|
|
2237
803
|
version: "1.0.0",
|
|
@@ -2271,8 +837,8 @@ async function scopeCreateCommand(options) {
|
|
|
2271
837
|
access: "public"
|
|
2272
838
|
}
|
|
2273
839
|
};
|
|
2274
|
-
await
|
|
2275
|
-
|
|
840
|
+
await writeFile2(
|
|
841
|
+
join3(outputDir, "package.json"),
|
|
2276
842
|
JSON.stringify(packageJson, null, 2),
|
|
2277
843
|
"utf-8"
|
|
2278
844
|
);
|
|
@@ -2284,8 +850,8 @@ async function scopeCreateCommand(options) {
|
|
|
2284
850
|
},
|
|
2285
851
|
include: ["src/**/*"]
|
|
2286
852
|
};
|
|
2287
|
-
await
|
|
2288
|
-
|
|
853
|
+
await writeFile2(
|
|
854
|
+
join3(outputDir, "tsconfig.json"),
|
|
2289
855
|
JSON.stringify(tsconfig, null, 2),
|
|
2290
856
|
"utf-8"
|
|
2291
857
|
);
|
|
@@ -2299,7 +865,7 @@ export default defineConfig({
|
|
|
2299
865
|
sourcemap: true,
|
|
2300
866
|
});
|
|
2301
867
|
`;
|
|
2302
|
-
await
|
|
868
|
+
await writeFile2(join3(outputDir, "tsup.config.ts"), tsupConfig, "utf-8");
|
|
2303
869
|
const indexTs = `import type { Scope } from '@hawkinside_out/workflow-agent/config';
|
|
2304
870
|
|
|
2305
871
|
export const scopes: Scope[] = ${JSON.stringify(scopes, null, 2)};
|
|
@@ -2313,7 +879,7 @@ export const preset = {
|
|
|
2313
879
|
|
|
2314
880
|
export default preset;
|
|
2315
881
|
`;
|
|
2316
|
-
await
|
|
882
|
+
await writeFile2(join3(outputDir, "src", "index.ts"), indexTs, "utf-8");
|
|
2317
883
|
if (!options.noTest) {
|
|
2318
884
|
const testFile = `import { describe, it, expect } from 'vitest';
|
|
2319
885
|
import { scopes, preset } from './index.js';
|
|
@@ -2354,16 +920,16 @@ describe('${presetName} Scope Preset', () => {
|
|
|
2354
920
|
});
|
|
2355
921
|
});
|
|
2356
922
|
`;
|
|
2357
|
-
await
|
|
2358
|
-
|
|
923
|
+
await writeFile2(
|
|
924
|
+
join3(outputDir, "src", "index.test.ts"),
|
|
2359
925
|
testFile,
|
|
2360
926
|
"utf-8"
|
|
2361
927
|
);
|
|
2362
928
|
}
|
|
2363
|
-
|
|
2364
|
-
if (
|
|
2365
|
-
const workspaceFile =
|
|
2366
|
-
const workspaceContent = await
|
|
929
|
+
spinner4.stop("\u2713 Package structure created");
|
|
930
|
+
if (isMonorepo) {
|
|
931
|
+
const workspaceFile = join3(cwd, "pnpm-workspace.yaml");
|
|
932
|
+
const workspaceContent = await readFile(workspaceFile, "utf-8");
|
|
2367
933
|
const packagePath = `packages/scopes-${packageName}`;
|
|
2368
934
|
if (!workspaceContent.includes(packagePath) && !workspaceContent.includes("packages/*")) {
|
|
2369
935
|
console.log(
|
|
@@ -2412,7 +978,7 @@ describe('${presetName} Scope Preset', () => {
|
|
|
2412
978
|
);
|
|
2413
979
|
}
|
|
2414
980
|
} catch (error) {
|
|
2415
|
-
|
|
981
|
+
spinner4.stop("\u2717 Failed to create package");
|
|
2416
982
|
console.error(chalk7.red("\nError:"), error);
|
|
2417
983
|
process.exit(1);
|
|
2418
984
|
}
|
|
@@ -2421,9 +987,9 @@ describe('${presetName} Scope Preset', () => {
|
|
|
2421
987
|
// src/cli/commands/scope-migrate.ts
|
|
2422
988
|
import * as p5 from "@clack/prompts";
|
|
2423
989
|
import chalk8 from "chalk";
|
|
2424
|
-
import { existsSync as
|
|
2425
|
-
import { writeFile as
|
|
2426
|
-
import { join as
|
|
990
|
+
import { existsSync as existsSync4 } from "fs";
|
|
991
|
+
import { writeFile as writeFile3, mkdir as mkdir3, readFile as readFile2 } from "fs/promises";
|
|
992
|
+
import { join as join4 } from "path";
|
|
2427
993
|
async function scopeMigrateCommand(options) {
|
|
2428
994
|
console.log(chalk8.bold.cyan("\n\u{1F504} Migrate Scopes to Custom Package\n"));
|
|
2429
995
|
const cwd = process.cwd();
|
|
@@ -2467,8 +1033,8 @@ async function scopeMigrateCommand(options) {
|
|
|
2467
1033
|
p5.cancel("Migration cancelled");
|
|
2468
1034
|
process.exit(0);
|
|
2469
1035
|
}
|
|
2470
|
-
const
|
|
2471
|
-
if (
|
|
1036
|
+
const isMonorepo = existsSync4(join4(cwd, "pnpm-workspace.yaml"));
|
|
1037
|
+
if (isMonorepo) {
|
|
2472
1038
|
console.log(chalk8.dim("\n\u2713 Detected monorepo workspace\n"));
|
|
2473
1039
|
}
|
|
2474
1040
|
const packageNameInput = options.name || await p5.text({
|
|
@@ -2516,8 +1082,8 @@ async function scopeMigrateCommand(options) {
|
|
|
2516
1082
|
let outputDir;
|
|
2517
1083
|
if (options.outputDir) {
|
|
2518
1084
|
outputDir = options.outputDir;
|
|
2519
|
-
} else if (
|
|
2520
|
-
outputDir =
|
|
1085
|
+
} else if (isMonorepo) {
|
|
1086
|
+
outputDir = join4(cwd, "packages", `scopes-${packageName}`);
|
|
2521
1087
|
} else {
|
|
2522
1088
|
const customDir = await p5.text({
|
|
2523
1089
|
message: "Output directory:",
|
|
@@ -2528,9 +1094,9 @@ async function scopeMigrateCommand(options) {
|
|
|
2528
1094
|
p5.cancel("Migration cancelled");
|
|
2529
1095
|
process.exit(0);
|
|
2530
1096
|
}
|
|
2531
|
-
outputDir =
|
|
1097
|
+
outputDir = join4(cwd, customDir);
|
|
2532
1098
|
}
|
|
2533
|
-
if (
|
|
1099
|
+
if (existsSync4(outputDir)) {
|
|
2534
1100
|
const shouldOverwrite = await p5.confirm({
|
|
2535
1101
|
message: `Directory ${outputDir} already exists. Overwrite?`,
|
|
2536
1102
|
initialValue: false
|
|
@@ -2540,10 +1106,10 @@ async function scopeMigrateCommand(options) {
|
|
|
2540
1106
|
process.exit(0);
|
|
2541
1107
|
}
|
|
2542
1108
|
}
|
|
2543
|
-
const
|
|
2544
|
-
|
|
1109
|
+
const spinner4 = p5.spinner();
|
|
1110
|
+
spinner4.start("Migrating scopes to package...");
|
|
2545
1111
|
try {
|
|
2546
|
-
await
|
|
1112
|
+
await mkdir3(join4(outputDir, "src"), { recursive: true });
|
|
2547
1113
|
const packageJson = {
|
|
2548
1114
|
name: `@workflow/scopes-${packageName}`,
|
|
2549
1115
|
version: "1.0.0",
|
|
@@ -2583,8 +1149,8 @@ async function scopeMigrateCommand(options) {
|
|
|
2583
1149
|
access: "public"
|
|
2584
1150
|
}
|
|
2585
1151
|
};
|
|
2586
|
-
await
|
|
2587
|
-
|
|
1152
|
+
await writeFile3(
|
|
1153
|
+
join4(outputDir, "package.json"),
|
|
2588
1154
|
JSON.stringify(packageJson, null, 2),
|
|
2589
1155
|
"utf-8"
|
|
2590
1156
|
);
|
|
@@ -2596,8 +1162,8 @@ async function scopeMigrateCommand(options) {
|
|
|
2596
1162
|
},
|
|
2597
1163
|
include: ["src/**/*"]
|
|
2598
1164
|
};
|
|
2599
|
-
await
|
|
2600
|
-
|
|
1165
|
+
await writeFile3(
|
|
1166
|
+
join4(outputDir, "tsconfig.json"),
|
|
2601
1167
|
JSON.stringify(tsconfig, null, 2),
|
|
2602
1168
|
"utf-8"
|
|
2603
1169
|
);
|
|
@@ -2611,7 +1177,7 @@ export default defineConfig({
|
|
|
2611
1177
|
sourcemap: true,
|
|
2612
1178
|
});
|
|
2613
1179
|
`;
|
|
2614
|
-
await
|
|
1180
|
+
await writeFile3(join4(outputDir, "tsup.config.ts"), tsupConfig, "utf-8");
|
|
2615
1181
|
const indexTs = `import type { Scope } from '@hawkinside_out/workflow-agent/config';
|
|
2616
1182
|
|
|
2617
1183
|
export const scopes: Scope[] = ${JSON.stringify(config.scopes, null, 2)};
|
|
@@ -2625,7 +1191,7 @@ export const preset = {
|
|
|
2625
1191
|
|
|
2626
1192
|
export default preset;
|
|
2627
1193
|
`;
|
|
2628
|
-
await
|
|
1194
|
+
await writeFile3(join4(outputDir, "src", "index.ts"), indexTs, "utf-8");
|
|
2629
1195
|
const testFile = `import { describe, it, expect } from 'vitest';
|
|
2630
1196
|
import { scopes, preset } from './index.js';
|
|
2631
1197
|
import { ScopeSchema } from '@hawkinside_out/workflow-agent/config';
|
|
@@ -2668,11 +1234,11 @@ describe('${presetName} Scope Preset (Migrated)', () => {
|
|
|
2668
1234
|
});
|
|
2669
1235
|
});
|
|
2670
1236
|
`;
|
|
2671
|
-
await
|
|
2672
|
-
|
|
2673
|
-
if (
|
|
2674
|
-
const workspaceFile =
|
|
2675
|
-
const workspaceContent = await
|
|
1237
|
+
await writeFile3(join4(outputDir, "src", "index.test.ts"), testFile, "utf-8");
|
|
1238
|
+
spinner4.stop("\u2713 Package created from migrated scopes");
|
|
1239
|
+
if (isMonorepo) {
|
|
1240
|
+
const workspaceFile = join4(cwd, "pnpm-workspace.yaml");
|
|
1241
|
+
const workspaceContent = await readFile2(workspaceFile, "utf-8");
|
|
2676
1242
|
const packagePath = `packages/scopes-${packageName}`;
|
|
2677
1243
|
if (!workspaceContent.includes(packagePath) && !workspaceContent.includes("packages/*")) {
|
|
2678
1244
|
console.log(
|
|
@@ -2688,7 +1254,7 @@ describe('${presetName} Scope Preset (Migrated)', () => {
|
|
|
2688
1254
|
initialValue: false
|
|
2689
1255
|
});
|
|
2690
1256
|
if (!p5.isCancel(keepConfig) && !keepConfig) {
|
|
2691
|
-
const configPath =
|
|
1257
|
+
const configPath = join4(cwd, "workflow.config.json");
|
|
2692
1258
|
const updatedConfig = {
|
|
2693
1259
|
...config,
|
|
2694
1260
|
scopes: [],
|
|
@@ -2696,7 +1262,7 @@ describe('${presetName} Scope Preset (Migrated)', () => {
|
|
|
2696
1262
|
preset: `scopes-${packageName}`
|
|
2697
1263
|
// Reference the new package
|
|
2698
1264
|
};
|
|
2699
|
-
await
|
|
1265
|
+
await writeFile3(
|
|
2700
1266
|
configPath,
|
|
2701
1267
|
JSON.stringify(updatedConfig, null, 2),
|
|
2702
1268
|
"utf-8"
|
|
@@ -2741,710 +1307,100 @@ describe('${presetName} Scope Preset (Migrated)', () => {
|
|
|
2741
1307
|
)
|
|
2742
1308
|
);
|
|
2743
1309
|
} catch (error) {
|
|
2744
|
-
|
|
1310
|
+
spinner4.stop("\u2717 Migration failed");
|
|
2745
1311
|
console.error(chalk8.red("\nError:"), error);
|
|
2746
1312
|
process.exit(1);
|
|
2747
1313
|
}
|
|
2748
1314
|
}
|
|
2749
1315
|
|
|
2750
|
-
// src/cli/commands/
|
|
1316
|
+
// src/cli/commands/verify.ts
|
|
2751
1317
|
import chalk9 from "chalk";
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
switch (action) {
|
|
2755
|
-
case "install":
|
|
2756
|
-
await installHooksAction(cwd);
|
|
2757
|
-
break;
|
|
2758
|
-
case "uninstall":
|
|
2759
|
-
await uninstallHooksAction(cwd);
|
|
2760
|
-
break;
|
|
2761
|
-
case "status":
|
|
2762
|
-
await statusHooksAction(cwd);
|
|
2763
|
-
break;
|
|
2764
|
-
default:
|
|
2765
|
-
console.error(chalk9.red(`Unknown action: ${action}`));
|
|
2766
|
-
console.log(chalk9.dim("Available actions: install, uninstall, status"));
|
|
2767
|
-
process.exit(1);
|
|
2768
|
-
}
|
|
2769
|
-
}
|
|
2770
|
-
async function installHooksAction(cwd) {
|
|
2771
|
-
console.log(chalk9.bold.cyan("\n\u{1F517} Installing Workflow Agent Git Hooks\n"));
|
|
2772
|
-
if (!hasGitRepo(cwd)) {
|
|
2773
|
-
console.error(chalk9.red("\u2717 No git repository found"));
|
|
2774
|
-
console.log(chalk9.yellow(" Run: git init"));
|
|
2775
|
-
process.exit(1);
|
|
2776
|
-
}
|
|
2777
|
-
const config = await loadConfig();
|
|
2778
|
-
const hooksConfig = config?.hooks;
|
|
2779
|
-
const results = await installHooks(hooksConfig, cwd);
|
|
2780
|
-
let hasErrors = false;
|
|
2781
|
-
for (const result of results) {
|
|
2782
|
-
if (result.success) {
|
|
2783
|
-
if (result.wrappedExisting) {
|
|
2784
|
-
console.log(
|
|
2785
|
-
chalk9.green(
|
|
2786
|
-
`\u2713 Installed ${result.hookType} hook (wrapped existing hook)`
|
|
2787
|
-
)
|
|
2788
|
-
);
|
|
2789
|
-
} else {
|
|
2790
|
-
console.log(chalk9.green(`\u2713 Installed ${result.hookType} hook`));
|
|
2791
|
-
}
|
|
2792
|
-
} else {
|
|
2793
|
-
console.error(
|
|
2794
|
-
chalk9.red(`\u2717 Failed to install ${result.hookType}: ${result.error}`)
|
|
2795
|
-
);
|
|
2796
|
-
hasErrors = true;
|
|
2797
|
-
}
|
|
2798
|
-
}
|
|
2799
|
-
if (!hasErrors) {
|
|
2800
|
-
console.log(chalk9.green("\n\u2713 Git hooks installed successfully"));
|
|
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
|
-
);
|
|
2821
|
-
} else {
|
|
2822
|
-
console.log(chalk9.green(`\u2713 Removed ${result.hookType} hook`));
|
|
2823
|
-
}
|
|
2824
|
-
} else if (result.error) {
|
|
2825
|
-
console.error(
|
|
2826
|
-
chalk9.red(`\u2717 Failed to remove ${result.hookType}: ${result.error}`)
|
|
2827
|
-
);
|
|
2828
|
-
hasErrors = true;
|
|
2829
|
-
}
|
|
2830
|
-
}
|
|
2831
|
-
if (!hasErrors) {
|
|
2832
|
-
console.log(chalk9.green("\n\u2713 Git hooks uninstalled successfully"));
|
|
2833
|
-
} else {
|
|
2834
|
-
process.exit(1);
|
|
2835
|
-
}
|
|
2836
|
-
}
|
|
2837
|
-
async function statusHooksAction(cwd) {
|
|
2838
|
-
console.log(chalk9.bold.cyan("\n\u{1F4CA} Workflow Agent Git Hooks Status\n"));
|
|
2839
|
-
if (!hasGitRepo(cwd)) {
|
|
2840
|
-
console.error(chalk9.red("\u2717 No git repository found"));
|
|
2841
|
-
process.exit(1);
|
|
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)";
|
|
2852
|
-
}
|
|
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) {
|
|
1318
|
+
import { execa as execa2 } from "execa";
|
|
1319
|
+
async function verifyCommand(options) {
|
|
2870
1320
|
const cwd = process.cwd();
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
}
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
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
|
-
}
|
|
2902
|
-
} else {
|
|
2903
|
-
console.log(
|
|
2904
|
-
chalk10.dim(
|
|
2905
|
-
`Repository: ${repoInfo.github?.owner}/${repoInfo.github?.repo}`
|
|
2906
|
-
)
|
|
2907
|
-
);
|
|
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"
|
|
1321
|
+
const maxRetries = options.maxRetries ? parseInt(options.maxRetries, 10) : 10;
|
|
1322
|
+
const autoFix = options.fix ?? false;
|
|
1323
|
+
const shouldCommit = options.commit ?? false;
|
|
1324
|
+
const dryRun = options.dryRun ?? false;
|
|
1325
|
+
console.log(chalk9.bold.cyan("\n\u{1F50D} Workflow Agent Quality Verification\n"));
|
|
1326
|
+
if (dryRun) {
|
|
1327
|
+
console.log(chalk9.yellow("\u{1F4CB} DRY-RUN MODE: No changes will be applied\n"));
|
|
1328
|
+
}
|
|
1329
|
+
console.log(chalk9.dim(` Auto-fix: ${autoFix ? "enabled" : "disabled"}`));
|
|
1330
|
+
console.log(chalk9.dim(` Max retries: ${maxRetries}`));
|
|
1331
|
+
console.log(chalk9.dim(` Commit on success: ${shouldCommit ? "yes" : "no"}`));
|
|
1332
|
+
console.log(chalk9.dim(` Dry-run: ${dryRun ? "yes" : "no"}`));
|
|
1333
|
+
const startTime = Date.now();
|
|
1334
|
+
const result = await runAllChecks(cwd, {
|
|
1335
|
+
maxRetries,
|
|
1336
|
+
autoFix,
|
|
1337
|
+
dryRun
|
|
2932
1338
|
});
|
|
1339
|
+
const totalTime = ((Date.now() - startTime) / 1e3).toFixed(2);
|
|
1340
|
+
console.log(`
|
|
1341
|
+
${"\u2501".repeat(50)}`);
|
|
2933
1342
|
if (result.success) {
|
|
2934
|
-
|
|
2935
|
-
console.log(
|
|
2936
|
-
console.log(
|
|
2937
|
-
console.log(
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
1343
|
+
console.log(chalk9.bold.green("\n\u2705 ALL QUALITY CHECKS PASSED!\n"));
|
|
1344
|
+
console.log(chalk9.dim(` Total time: ${totalTime}s`));
|
|
1345
|
+
console.log(chalk9.dim(` Validation cycles: ${result.totalAttempts}`));
|
|
1346
|
+
console.log(chalk9.dim(` Fixes applied: ${result.fixesApplied}`));
|
|
1347
|
+
if (shouldCommit) {
|
|
1348
|
+
const hasChanges = await hasUncommittedChanges(cwd);
|
|
1349
|
+
if (hasChanges) {
|
|
1350
|
+
console.log(chalk9.cyan("\n\u{1F4E6} Staging and committing changes...\n"));
|
|
1351
|
+
const staged = await stageAllChanges(cwd);
|
|
1352
|
+
if (!staged) {
|
|
1353
|
+
console.log(chalk9.red("\u274C Failed to stage changes"));
|
|
1354
|
+
process.exit(1);
|
|
1355
|
+
}
|
|
1356
|
+
try {
|
|
1357
|
+
await execa2(
|
|
1358
|
+
"git",
|
|
1359
|
+
["commit", "-m", "chore: auto-fix quality issues"],
|
|
1360
|
+
{ cwd }
|
|
1361
|
+
);
|
|
1362
|
+
console.log(chalk9.green("\u2705 Changes committed successfully"));
|
|
1363
|
+
} catch (error) {
|
|
1364
|
+
console.log(chalk9.red("\u274C Failed to commit changes"));
|
|
1365
|
+
console.log(chalk9.dim(error.message));
|
|
1366
|
+
process.exit(1);
|
|
1367
|
+
}
|
|
1368
|
+
} else {
|
|
1369
|
+
console.log(chalk9.dim("\n No changes to commit."));
|
|
1370
|
+
}
|
|
2953
1371
|
}
|
|
2954
|
-
console.log(
|
|
2955
|
-
console.log(
|
|
1372
|
+
console.log(chalk9.cyan("\n\u{1F4A1} Next steps:\n"));
|
|
1373
|
+
console.log(chalk9.dim(" 1. git add ."));
|
|
2956
1374
|
console.log(
|
|
2957
|
-
|
|
1375
|
+
chalk9.dim(' 2. git commit -m "<type>(<scope>): <description>"')
|
|
2958
1376
|
);
|
|
2959
|
-
console.log(
|
|
2960
|
-
|
|
2961
|
-
|
|
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"));
|
|
1377
|
+
console.log(chalk9.dim(" 3. git push origin <branch-name>"));
|
|
1378
|
+
console.log("");
|
|
1379
|
+
process.exit(0);
|
|
3002
1380
|
} else {
|
|
3003
|
-
console.log(
|
|
3004
|
-
console.log(
|
|
3005
|
-
|
|
3006
|
-
}
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
import { dirname as dirname2, join as join10, relative } from "path";
|
|
3012
|
-
import { execa as execa3 } from "execa";
|
|
3013
|
-
import * as p7 from "@clack/prompts";
|
|
3014
|
-
import pc from "picocolors";
|
|
3015
|
-
function extractFilePaths(errorMessage, cwd) {
|
|
3016
|
-
const paths = /* @__PURE__ */ new Set();
|
|
3017
|
-
const patterns = [
|
|
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);
|
|
1381
|
+
console.log(chalk9.bold.red("\n\u274C QUALITY CHECKS FAILED\n"));
|
|
1382
|
+
console.log(chalk9.dim(` Total time: ${totalTime}s`));
|
|
1383
|
+
console.log(chalk9.dim(` Validation cycles: ${result.totalAttempts}`));
|
|
1384
|
+
console.log(chalk9.dim(` Fixes applied: ${result.fixesApplied}`));
|
|
1385
|
+
if (result.pendingFixes && result.pendingFixes.length > 0) {
|
|
1386
|
+
console.log(chalk9.yellow("\n\u{1F4CB} Pending fixes (dry-run):"));
|
|
1387
|
+
for (const fix of result.pendingFixes) {
|
|
1388
|
+
console.log(chalk9.dim(` \u2022 ${fix.check.displayName}: ${fix.command}`));
|
|
3028
1389
|
}
|
|
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) {
|
|
3086
|
-
try {
|
|
3087
|
-
await execa3("git", ["add", "-A"], { cwd });
|
|
3088
|
-
const { stdout: status } = await execa3(
|
|
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 };
|
|
3099
|
-
} catch (error) {
|
|
3100
|
-
return {
|
|
3101
|
-
success: false,
|
|
3102
|
-
error: error instanceof Error ? error.message : String(error)
|
|
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
1390
|
}
|
|
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
1391
|
console.log(
|
|
3160
|
-
|
|
3161
|
-
`\u26A0\uFE0F Low confidence (${Math.round(fix.confidence * 100)}%). Skipping automatic fix.`
|
|
3162
|
-
)
|
|
1392
|
+
chalk9.yellow("\n\u26A0\uFE0F Please fix the errors above and run again.")
|
|
3163
1393
|
);
|
|
3164
|
-
process.exit(1);
|
|
3165
|
-
}
|
|
3166
|
-
if (!options.auto) {
|
|
3167
|
-
const shouldApply = await p7.confirm({
|
|
3168
|
-
message: "Apply suggested changes?",
|
|
3169
|
-
initialValue: false
|
|
3170
|
-
});
|
|
3171
|
-
if (p7.isCancel(shouldApply) || !shouldApply) {
|
|
3172
|
-
console.log(pc.dim("Fix cancelled."));
|
|
3173
|
-
process.exit(0);
|
|
3174
|
-
}
|
|
3175
|
-
}
|
|
3176
|
-
console.log(pc.bold("\nApplying Changes...\n"));
|
|
3177
|
-
await applyChanges(fix.suggestedFix.files, options.dryRun || false);
|
|
3178
|
-
if (options.dryRun) {
|
|
3179
|
-
console.log(pc.yellow("\n\u26A0\uFE0F Dry run complete. No changes were made."));
|
|
3180
|
-
process.exit(0);
|
|
3181
|
-
}
|
|
3182
|
-
if (options.auto) {
|
|
3183
|
-
console.log(pc.bold("\nCommitting and Pushing...\n"));
|
|
3184
|
-
const scope = filePaths.length > 0 ? dirname2(relative(cwd, filePaths[0])).split("/")[0] || "core" : "core";
|
|
3185
|
-
const commitMessage = `fix(${scope}): auto-heal pipeline failure
|
|
3186
|
-
|
|
3187
|
-
${fix.rootCause}
|
|
3188
|
-
|
|
3189
|
-
Auto-generated fix with ${Math.round(fix.confidence * 100)}% confidence.
|
|
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);
|
|
3197
|
-
}
|
|
3198
|
-
} else {
|
|
3199
|
-
console.log(pc.green("\n\u2705 Fix applied! Don't forget to commit and push."));
|
|
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
1394
|
console.log(
|
|
3248
|
-
|
|
3249
|
-
);
|
|
3250
|
-
process.exit(1);
|
|
3251
|
-
}
|
|
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
|
-
console.log("");
|
|
3259
|
-
try {
|
|
3260
|
-
const { chromium } = await import("playwright");
|
|
3261
|
-
const browser = await chromium.launch({ headless: true });
|
|
3262
|
-
const context = await browser.newContext({
|
|
3263
|
-
viewport: { width, height }
|
|
3264
|
-
});
|
|
3265
|
-
const page = await context.newPage();
|
|
3266
|
-
console.log(pc2.dim(" Loading page..."));
|
|
3267
|
-
await page.goto(url, { waitUntil: "networkidle" });
|
|
3268
|
-
if (options.waitFor) {
|
|
3269
|
-
console.log(pc2.dim(` Waiting for selector: ${options.waitFor}`));
|
|
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 });
|
|
3281
|
-
}
|
|
3282
|
-
console.log(pc2.dim(" Capturing screenshot..."));
|
|
3283
|
-
await page.screenshot({
|
|
3284
|
-
path: outputPath,
|
|
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}`);
|
|
3326
|
-
console.log("");
|
|
3327
|
-
const hasPlaywright = await checkPlaywright();
|
|
3328
|
-
if (!hasPlaywright) {
|
|
3329
|
-
console.log(pc2.yellow("\u26A0\uFE0F Playwright is not installed."));
|
|
3330
|
-
process.exit(1);
|
|
3331
|
-
}
|
|
3332
|
-
try {
|
|
3333
|
-
const { chromium } = await import("playwright");
|
|
3334
|
-
const browser = await chromium.launch({ headless: true });
|
|
3335
|
-
const context = await browser.newContext({
|
|
3336
|
-
viewport: { width: baseline.width, height: baseline.height }
|
|
3337
|
-
});
|
|
3338
|
-
const page = await context.newPage();
|
|
3339
|
-
console.log(pc2.dim(" Loading page..."));
|
|
3340
|
-
await page.goto(url, { waitUntil: "networkidle" });
|
|
3341
|
-
const baselinesDir = getBaselinesDir();
|
|
3342
|
-
const comparePath = options.output || join11(
|
|
3343
|
-
baselinesDir,
|
|
3344
|
-
"comparisons",
|
|
3345
|
-
`${options.baseline}-${Date.now()}.png`
|
|
1395
|
+
chalk9.dim(" Run with --fix to auto-fix lint and format issues.")
|
|
3346
1396
|
);
|
|
3347
|
-
const compareDir = dirname3(comparePath);
|
|
3348
|
-
if (!existsSync11(compareDir)) {
|
|
3349
|
-
mkdirSync2(compareDir, { recursive: true });
|
|
3350
|
-
}
|
|
3351
|
-
console.log(pc2.dim(" Capturing screenshot..."));
|
|
3352
|
-
await page.screenshot({ path: comparePath });
|
|
3353
|
-
await browser.close();
|
|
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
|
-
)
|
|
3370
|
-
);
|
|
3371
|
-
console.log(pc2.dim(`Comparison saved to: ${comparePath}`));
|
|
3372
|
-
process.exit(1);
|
|
3373
|
-
}
|
|
3374
|
-
} catch (error) {
|
|
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
1397
|
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);
|
|
3407
|
-
}
|
|
3408
|
-
console.log(pc2.cyan(`
|
|
3409
|
-
\u{1F504} Updating baseline "${name}"...
|
|
3410
|
-
`));
|
|
3411
|
-
await visualCaptureCommand(name, baseline.url, {
|
|
3412
|
-
width: options.width || String(baseline.width),
|
|
3413
|
-
height: options.height || String(baseline.height),
|
|
3414
|
-
fullPage: options.fullPage,
|
|
3415
|
-
output: baseline.path
|
|
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
1398
|
process.exit(1);
|
|
3424
1399
|
}
|
|
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;
|
|
3438
|
-
}
|
|
3439
|
-
console.log(
|
|
3440
|
-
pc2.yellow(
|
|
3441
|
-
"To approve, re-capture the baseline with: workflow visual update " + name
|
|
3442
|
-
)
|
|
3443
|
-
);
|
|
3444
1400
|
}
|
|
3445
1401
|
|
|
3446
1402
|
// src/cli/index.ts
|
|
3447
|
-
var program = new
|
|
1403
|
+
var program = new Command();
|
|
3448
1404
|
program.name("workflow").description(
|
|
3449
1405
|
"A self-evolving workflow management system for AI agent development"
|
|
3450
1406
|
).version("1.0.0");
|
|
@@ -3459,43 +1415,18 @@ program.command("validate <type>").description("Validate branch name, commit mes
|
|
|
3459
1415
|
"--suggest-on-error",
|
|
3460
1416
|
"Offer improvement suggestions on validation errors"
|
|
3461
1417
|
).action(validateCommand);
|
|
3462
|
-
program.
|
|
1418
|
+
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
1419
|
program.command("suggest").description("Submit an improvement suggestion").argument("<feedback>", "Your improvement suggestion").option("--author <author>", "Your name or username").option(
|
|
3464
1420
|
"--category <category>",
|
|
3465
1421
|
"Category: feature, bug, documentation, performance, other"
|
|
3466
1422
|
).action(suggestCommand);
|
|
3467
1423
|
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);
|
|
1424
|
+
program.command("doctor").description("Run health check and get optimization suggestions").action(doctorCommand);
|
|
3474
1425
|
program.command("scope:create").description("Create a custom scope package").option("--name <name>", 'Package name (e.g., "fintech", "gaming")').option(
|
|
3475
1426
|
"--scopes <scopes>",
|
|
3476
1427
|
"Comma-separated scopes (format: name:description:emoji:category)"
|
|
3477
1428
|
).option("--preset-name <preset>", "Preset display name").option("--output-dir <dir>", "Output directory").option("--no-test", "Skip test file generation").action(scopeCreateCommand);
|
|
3478
1429
|
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
|
-
fixCommand({
|
|
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);
|
|
1430
|
+
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);
|
|
3500
1431
|
program.parse();
|
|
3501
1432
|
//# sourceMappingURL=index.js.map
|