workflow-agent-cli 2.0.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -17
- package/dist/chunk-IPMSSOXR.js +293 -0
- package/dist/chunk-IPMSSOXR.js.map +1 -0
- package/dist/{chunk-X2NQJ2ZY.js → chunk-NMHWD2GA.js} +11 -6
- package/dist/chunk-NMHWD2GA.js.map +1 -0
- package/dist/cli/index.js +777 -165
- package/dist/cli/index.js.map +1 -1
- package/dist/config/index.d.ts +8 -3
- package/dist/config/index.js +9 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +10 -4
- package/dist/schema-C1lmnd7L.d.ts +256 -0
- package/dist/scripts/postinstall.js +6 -2
- package/dist/scripts/postinstall.js.map +1 -1
- package/dist/validators/index.d.ts +1 -1
- package/dist/validators/index.js +1 -1
- package/package.json +17 -15
- package/templates/AGENT_EDITING_INSTRUCTIONS.md +226 -39
- package/templates/COMPONENT_LIBRARY.md +23 -17
- package/templates/CUSTOM_SCOPE_TEMPLATE.md +5 -4
- package/templates/LIBRARY_INVENTORY.md +20 -20
- package/templates/SCOPE_CREATION_WORKFLOW.md +39 -11
- package/templates/SELF_IMPROVEMENT_MANDATE.md +24 -18
- package/templates/SINGLE_SOURCE_OF_TRUTH.md +59 -42
- package/templates/TESTING_STRATEGY.md +79 -69
- package/templates/_TEMPLATE_EXAMPLE.md +2 -1
- package/LICENSE +0 -21
- package/dist/chunk-4BIDFDSR.js +0 -152
- package/dist/chunk-4BIDFDSR.js.map +0 -1
- package/dist/chunk-X2NQJ2ZY.js.map +0 -1
- package/dist/schema-RkQ91pZW.d.ts +0 -161
package/dist/cli/index.js
CHANGED
|
@@ -3,15 +3,18 @@ import {
|
|
|
3
3
|
validateBranchName,
|
|
4
4
|
validateCommitMessage,
|
|
5
5
|
validatePRTitle
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-NMHWD2GA.js";
|
|
7
7
|
import {
|
|
8
|
+
DEFAULT_RESERVED_SCOPE_NAMES,
|
|
8
9
|
hasConfig,
|
|
9
10
|
loadConfig,
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
validateConfig,
|
|
12
|
+
validateScopeDefinitions,
|
|
13
|
+
validateScopeName
|
|
14
|
+
} from "../chunk-IPMSSOXR.js";
|
|
12
15
|
|
|
13
16
|
// src/cli/index.ts
|
|
14
|
-
import { Command } from "commander";
|
|
17
|
+
import { Command as Command2 } from "commander";
|
|
15
18
|
|
|
16
19
|
// src/cli/commands/init.ts
|
|
17
20
|
import * as p from "@clack/prompts";
|
|
@@ -76,7 +79,7 @@ var adapters = {
|
|
|
76
79
|
config: "src"
|
|
77
80
|
}
|
|
78
81
|
},
|
|
79
|
-
|
|
82
|
+
remix: {
|
|
80
83
|
name: "Remix",
|
|
81
84
|
description: "Remix full-stack framework",
|
|
82
85
|
detect: async () => {
|
|
@@ -91,7 +94,7 @@ var adapters = {
|
|
|
91
94
|
config: "app/root.tsx"
|
|
92
95
|
}
|
|
93
96
|
},
|
|
94
|
-
|
|
97
|
+
astro: {
|
|
95
98
|
name: "Astro",
|
|
96
99
|
description: "Astro static site framework",
|
|
97
100
|
detect: async () => {
|
|
@@ -106,7 +109,7 @@ var adapters = {
|
|
|
106
109
|
config: "src/pages"
|
|
107
110
|
}
|
|
108
111
|
},
|
|
109
|
-
|
|
112
|
+
sveltekit: {
|
|
110
113
|
name: "SvelteKit",
|
|
111
114
|
description: "SvelteKit full-stack framework",
|
|
112
115
|
detect: async () => {
|
|
@@ -121,7 +124,7 @@ var adapters = {
|
|
|
121
124
|
config: "src/routes"
|
|
122
125
|
}
|
|
123
126
|
},
|
|
124
|
-
|
|
127
|
+
generic: {
|
|
125
128
|
name: "Generic Project",
|
|
126
129
|
description: "Standard project structure",
|
|
127
130
|
detect: async () => true,
|
|
@@ -207,7 +210,9 @@ async function validateTemplateDirectory(templateDir) {
|
|
|
207
210
|
const files = await fs.readdir(templateDir);
|
|
208
211
|
const templateFiles = files.filter((f) => f.match(/\.(md|ts|json)$/));
|
|
209
212
|
if (templateFiles.length === 0) {
|
|
210
|
-
throw new Error(
|
|
213
|
+
throw new Error(
|
|
214
|
+
`No template files found in template directory: ${templateDir}`
|
|
215
|
+
);
|
|
211
216
|
}
|
|
212
217
|
} catch (error) {
|
|
213
218
|
if (error.code === "ENOENT") {
|
|
@@ -346,6 +351,8 @@ function generatePreCommitHook(config) {
|
|
|
346
351
|
return " workflow validate commit";
|
|
347
352
|
case "check-guidelines":
|
|
348
353
|
return " workflow doctor --check-guidelines-only 2>/dev/null || true";
|
|
354
|
+
case "validate-scopes":
|
|
355
|
+
return " workflow config validate";
|
|
349
356
|
default:
|
|
350
357
|
return "";
|
|
351
358
|
}
|
|
@@ -465,12 +472,14 @@ async function installSingleHook(hookType, config, projectPath = process.cwd())
|
|
|
465
472
|
}
|
|
466
473
|
async function installHooks(config, projectPath = process.cwd()) {
|
|
467
474
|
if (!hasGitRepo(projectPath)) {
|
|
468
|
-
return [
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
+
];
|
|
474
483
|
}
|
|
475
484
|
const results = await Promise.all([
|
|
476
485
|
installSingleHook("pre-commit", config, projectPath),
|
|
@@ -530,7 +539,9 @@ async function isGitRepo(projectPath = process.cwd()) {
|
|
|
530
539
|
}
|
|
531
540
|
async function getGitRemoteUrl(projectPath = process.cwd()) {
|
|
532
541
|
try {
|
|
533
|
-
const { stdout } = await execa("git", ["remote", "get-url", "origin"], {
|
|
542
|
+
const { stdout } = await execa("git", ["remote", "get-url", "origin"], {
|
|
543
|
+
cwd: projectPath
|
|
544
|
+
});
|
|
534
545
|
return stdout.trim() || null;
|
|
535
546
|
} catch {
|
|
536
547
|
return null;
|
|
@@ -553,15 +564,23 @@ function parseGitHubUrl(remoteUrl) {
|
|
|
553
564
|
}
|
|
554
565
|
async function getDefaultBranch(projectPath = process.cwd()) {
|
|
555
566
|
try {
|
|
556
|
-
const { stdout } = await execa(
|
|
557
|
-
|
|
558
|
-
|
|
567
|
+
const { stdout } = await execa(
|
|
568
|
+
"git",
|
|
569
|
+
["symbolic-ref", "refs/remotes/origin/HEAD"],
|
|
570
|
+
{
|
|
571
|
+
cwd: projectPath
|
|
572
|
+
}
|
|
573
|
+
);
|
|
559
574
|
return stdout.trim().replace("refs/remotes/origin/", "") || "main";
|
|
560
575
|
} catch {
|
|
561
576
|
try {
|
|
562
|
-
const { stdout } = await execa(
|
|
563
|
-
|
|
564
|
-
|
|
577
|
+
const { stdout } = await execa(
|
|
578
|
+
"git",
|
|
579
|
+
["rev-parse", "--abbrev-ref", "HEAD"],
|
|
580
|
+
{
|
|
581
|
+
cwd: projectPath
|
|
582
|
+
}
|
|
583
|
+
);
|
|
565
584
|
return stdout.trim() || "main";
|
|
566
585
|
} catch {
|
|
567
586
|
return "main";
|
|
@@ -753,7 +772,11 @@ function generateCIWorkflowContent(options) {
|
|
|
753
772
|
}
|
|
754
773
|
if (checks.includes("typecheck")) {
|
|
755
774
|
if (hasTypecheckScript) {
|
|
756
|
-
const typecheckCmd = getRunCommand(
|
|
775
|
+
const typecheckCmd = getRunCommand(
|
|
776
|
+
packageManager,
|
|
777
|
+
"typecheck",
|
|
778
|
+
isMonorepo2
|
|
779
|
+
);
|
|
757
780
|
steps.push(`
|
|
758
781
|
- name: Type check
|
|
759
782
|
run: ${typecheckCmd}`);
|
|
@@ -765,7 +788,11 @@ function generateCIWorkflowContent(options) {
|
|
|
765
788
|
}
|
|
766
789
|
if (checks.includes("format")) {
|
|
767
790
|
if (hasFormatScript) {
|
|
768
|
-
const formatCmd = getRunCommand(
|
|
791
|
+
const formatCmd = getRunCommand(
|
|
792
|
+
packageManager,
|
|
793
|
+
"format:check",
|
|
794
|
+
isMonorepo2
|
|
795
|
+
);
|
|
769
796
|
steps.push(`
|
|
770
797
|
- name: Format check
|
|
771
798
|
run: ${formatCmd} || npx prettier --check "**/*.{ts,tsx,js,jsx,json,md}"`);
|
|
@@ -839,7 +866,13 @@ async function createCIWorkflow(options = {}) {
|
|
|
839
866
|
const projectInfo = await getProjectInfo(projectPath);
|
|
840
867
|
const packageManager = options.packageManager || projectInfo.packageManager;
|
|
841
868
|
const isMonorepo2 = options.isMonorepo ?? projectInfo.isMonorepo;
|
|
842
|
-
const checks = options.ciConfig?.checks || [
|
|
869
|
+
const checks = options.ciConfig?.checks || [
|
|
870
|
+
"lint",
|
|
871
|
+
"typecheck",
|
|
872
|
+
"format",
|
|
873
|
+
"build",
|
|
874
|
+
"test"
|
|
875
|
+
];
|
|
843
876
|
const nodeVersions = options.nodeVersions || ["20"];
|
|
844
877
|
const defaultBranch = options.defaultBranch || "main";
|
|
845
878
|
if (!existsSync3(workflowsDir)) {
|
|
@@ -899,12 +932,30 @@ async function initCommand(options) {
|
|
|
899
932
|
const preset = isNonInteractive ? options.preset : await p.select({
|
|
900
933
|
message: "Choose a scope preset for your project:",
|
|
901
934
|
options: [
|
|
902
|
-
{
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
{
|
|
907
|
-
|
|
935
|
+
{
|
|
936
|
+
value: "saas",
|
|
937
|
+
label: "\u{1F4E6} SaaS Application - 17 scopes (auth, tasks, boards, sprints, etc.)"
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
value: "library",
|
|
941
|
+
label: "\u{1F4DA} Library/Package - 10 scopes (types, build, docs, examples, etc.)"
|
|
942
|
+
},
|
|
943
|
+
{
|
|
944
|
+
value: "api",
|
|
945
|
+
label: "\u{1F50C} API/Backend - 13 scopes (auth, endpoints, models, services, etc.)"
|
|
946
|
+
},
|
|
947
|
+
{
|
|
948
|
+
value: "ecommerce",
|
|
949
|
+
label: "\u{1F6D2} E-commerce - 12 scopes (cart, products, payments, orders, etc.)"
|
|
950
|
+
},
|
|
951
|
+
{
|
|
952
|
+
value: "cms",
|
|
953
|
+
label: "\u{1F4DD} CMS - 13 scopes (content, pages, media, editor, etc.)"
|
|
954
|
+
},
|
|
955
|
+
{
|
|
956
|
+
value: "custom",
|
|
957
|
+
label: "\u2728 Custom (define your own scopes manually)"
|
|
958
|
+
}
|
|
908
959
|
]
|
|
909
960
|
});
|
|
910
961
|
if (!isNonInteractive && p.isCancel(preset)) {
|
|
@@ -921,27 +972,62 @@ async function initCommand(options) {
|
|
|
921
972
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
922
973
|
spinner5.stop(`\u2713 Loaded ${scopes.length} scopes from preset`);
|
|
923
974
|
} catch (error) {
|
|
924
|
-
console.log(
|
|
925
|
-
|
|
975
|
+
console.log(
|
|
976
|
+
chalk.yellow(
|
|
977
|
+
`
|
|
978
|
+
\u26A0\uFE0F Could not load preset package. Using basic scopes.`
|
|
979
|
+
)
|
|
980
|
+
);
|
|
926
981
|
scopes = [
|
|
927
|
-
{
|
|
982
|
+
{
|
|
983
|
+
name: "feat",
|
|
984
|
+
description: "New features and enhancements",
|
|
985
|
+
emoji: "\u2728"
|
|
986
|
+
},
|
|
928
987
|
{ name: "fix", description: "Bug fixes and patches", emoji: "\u{1F41B}" },
|
|
929
|
-
{
|
|
988
|
+
{
|
|
989
|
+
name: "documentation",
|
|
990
|
+
description: "Documentation updates and improvements",
|
|
991
|
+
emoji: "\u{1F4DA}"
|
|
992
|
+
}
|
|
930
993
|
];
|
|
931
994
|
}
|
|
932
995
|
} else {
|
|
933
996
|
scopes = [
|
|
934
|
-
{
|
|
997
|
+
{
|
|
998
|
+
name: "feat",
|
|
999
|
+
description: "New features and enhancements",
|
|
1000
|
+
emoji: "\u2728"
|
|
1001
|
+
},
|
|
935
1002
|
{ name: "fix", description: "Bug fixes and patches", emoji: "\u{1F41B}" },
|
|
936
|
-
{
|
|
1003
|
+
{
|
|
1004
|
+
name: "documentation",
|
|
1005
|
+
description: "Documentation updates and improvements",
|
|
1006
|
+
emoji: "\u{1F4DA}"
|
|
1007
|
+
}
|
|
937
1008
|
];
|
|
938
|
-
console.log(
|
|
1009
|
+
console.log(
|
|
1010
|
+
chalk.dim(
|
|
1011
|
+
"\n\u{1F4A1} Tip: Edit workflow.config.json to add your custom scopes"
|
|
1012
|
+
)
|
|
1013
|
+
);
|
|
939
1014
|
}
|
|
940
1015
|
const config = {
|
|
941
1016
|
projectName,
|
|
942
1017
|
scopes,
|
|
943
1018
|
enforcement: "strict",
|
|
944
|
-
language: "en"
|
|
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
|
|
945
1031
|
};
|
|
946
1032
|
const configPath = join4(cwd, "workflow.config.json");
|
|
947
1033
|
await writeFile3(configPath, JSON.stringify(config, null, 2));
|
|
@@ -953,8 +1039,12 @@ async function initCommand(options) {
|
|
|
953
1039
|
const projectInfo = await getProjectInfo(cwd);
|
|
954
1040
|
const mandatoryTemplates = getMandatoryTemplates();
|
|
955
1041
|
const optionalTemplates = getOptionalTemplates();
|
|
956
|
-
console.log(
|
|
957
|
-
|
|
1042
|
+
console.log(
|
|
1043
|
+
chalk.dim(
|
|
1044
|
+
`
|
|
1045
|
+
\u{1F4CB} Generating ${mandatoryTemplates.length} mandatory guidelines...`
|
|
1046
|
+
)
|
|
1047
|
+
);
|
|
958
1048
|
const guidelinesDir = join4(cwd, "guidelines");
|
|
959
1049
|
const templatesDir = join4(__dirname, "../../templates");
|
|
960
1050
|
let mandatoryGenerated = 0;
|
|
@@ -967,13 +1057,20 @@ async function initCommand(options) {
|
|
|
967
1057
|
try {
|
|
968
1058
|
const templatePath = join4(templatesDir, template.filename);
|
|
969
1059
|
const outputPath = join4(guidelinesDir, template.filename);
|
|
970
|
-
await renderTemplateFile(templatePath, outputPath,
|
|
1060
|
+
await renderTemplateFile(templatePath, outputPath, {
|
|
1061
|
+
...context,
|
|
1062
|
+
reservedScopeNames: (config.reservedScopeNames || []).join(", ")
|
|
1063
|
+
});
|
|
971
1064
|
mandatoryGenerated++;
|
|
972
1065
|
} catch (error) {
|
|
973
|
-
console.log(
|
|
1066
|
+
console.log(
|
|
1067
|
+
chalk.yellow(` \u26A0\uFE0F Could not generate ${template.filename}`)
|
|
1068
|
+
);
|
|
974
1069
|
}
|
|
975
1070
|
}
|
|
976
|
-
console.log(
|
|
1071
|
+
console.log(
|
|
1072
|
+
chalk.green(`\u2713 Generated ${mandatoryGenerated} mandatory guidelines`)
|
|
1073
|
+
);
|
|
977
1074
|
let shouldGenerateOptional = isNonInteractive;
|
|
978
1075
|
if (!isNonInteractive) {
|
|
979
1076
|
const response = await p.confirm({
|
|
@@ -992,11 +1089,17 @@ async function initCommand(options) {
|
|
|
992
1089
|
} catch {
|
|
993
1090
|
}
|
|
994
1091
|
}
|
|
995
|
-
console.log(
|
|
1092
|
+
console.log(
|
|
1093
|
+
chalk.green(`\u2713 Generated ${optionalGenerated} optional guidelines`)
|
|
1094
|
+
);
|
|
996
1095
|
}
|
|
997
1096
|
} catch (error) {
|
|
998
|
-
console.log(
|
|
999
|
-
|
|
1097
|
+
console.log(
|
|
1098
|
+
chalk.yellow(
|
|
1099
|
+
`
|
|
1100
|
+
\u26A0\uFE0F Could not generate guidelines: ${error instanceof Error ? error.message : String(error)}`
|
|
1101
|
+
)
|
|
1102
|
+
);
|
|
1000
1103
|
console.log(chalk.dim("You can manually copy guidelines later if needed."));
|
|
1001
1104
|
}
|
|
1002
1105
|
if (hasGitRepo(cwd)) {
|
|
@@ -1011,7 +1114,14 @@ async function initCommand(options) {
|
|
|
1011
1114
|
if (shouldInstallHooks) {
|
|
1012
1115
|
const hookSpinner = p.spinner();
|
|
1013
1116
|
hookSpinner.start("Installing git hooks...");
|
|
1014
|
-
const hookResults = await installHooks(
|
|
1117
|
+
const hookResults = await installHooks(
|
|
1118
|
+
config.hooks || {
|
|
1119
|
+
enabled: true,
|
|
1120
|
+
preCommit: ["validate-branch", "check-guidelines"],
|
|
1121
|
+
commitMsg: ["validate-commit"]
|
|
1122
|
+
},
|
|
1123
|
+
cwd
|
|
1124
|
+
);
|
|
1015
1125
|
const allSuccess = hookResults.every((r) => r.success);
|
|
1016
1126
|
if (allSuccess) {
|
|
1017
1127
|
hookSpinner.stop("\u2713 Installed git hooks");
|
|
@@ -1023,19 +1133,29 @@ async function initCommand(options) {
|
|
|
1023
1133
|
if (repoInfo.isGitHub) {
|
|
1024
1134
|
const existingCI = hasCIWorkflow(cwd);
|
|
1025
1135
|
if (!existingCI) {
|
|
1026
|
-
console.log(
|
|
1136
|
+
console.log(
|
|
1137
|
+
chalk.dim(
|
|
1138
|
+
"\n\u{1F527} Setting up GitHub Actions CI (mandatory for GitHub repos)..."
|
|
1139
|
+
)
|
|
1140
|
+
);
|
|
1027
1141
|
const ciResult = await createCIWorkflow({
|
|
1028
1142
|
projectPath: cwd,
|
|
1029
1143
|
packageManager: projectInfo.packageManager,
|
|
1030
1144
|
isMonorepo: projectInfo.isMonorepo,
|
|
1031
|
-
ciConfig: config.ci
|
|
1145
|
+
ciConfig: config.ci || {
|
|
1146
|
+
enabled: true,
|
|
1147
|
+
provider: "github",
|
|
1148
|
+
checks: ["lint", "typecheck", "format", "build", "test"]
|
|
1149
|
+
},
|
|
1032
1150
|
defaultBranch: repoInfo.defaultBranch || "main"
|
|
1033
1151
|
});
|
|
1034
1152
|
if (ciResult.success) {
|
|
1035
1153
|
console.log(chalk.green("\u2713 Created GitHub Actions CI workflow"));
|
|
1036
1154
|
console.log(chalk.dim(` File: .github/workflows/ci.yml`));
|
|
1037
1155
|
} else {
|
|
1038
|
-
console.log(
|
|
1156
|
+
console.log(
|
|
1157
|
+
chalk.yellow(`\u26A0\uFE0F Could not create CI workflow: ${ciResult.error}`)
|
|
1158
|
+
);
|
|
1039
1159
|
}
|
|
1040
1160
|
} else {
|
|
1041
1161
|
console.log(chalk.dim("\n\u2713 GitHub Actions CI workflow already exists"));
|
|
@@ -1063,13 +1183,21 @@ async function initCommand(options) {
|
|
|
1063
1183
|
}
|
|
1064
1184
|
p.outro(chalk.green("\u2713 Workflow Agent initialized successfully!"));
|
|
1065
1185
|
console.log(chalk.dim("\nNext steps:"));
|
|
1066
|
-
console.log(
|
|
1067
|
-
|
|
1186
|
+
console.log(
|
|
1187
|
+
chalk.dim(" 1. Review your configuration in workflow.config.json")
|
|
1188
|
+
);
|
|
1189
|
+
console.log(
|
|
1190
|
+
chalk.dim(" 2. Review generated guidelines in guidelines/ directory")
|
|
1191
|
+
);
|
|
1068
1192
|
console.log(chalk.dim(" 3. Run: workflow validate branch"));
|
|
1069
1193
|
console.log(chalk.dim(" 4. Run: workflow doctor (for health check)\n"));
|
|
1070
1194
|
if (repoInfo.isGitHub) {
|
|
1071
|
-
console.log(
|
|
1072
|
-
|
|
1195
|
+
console.log(
|
|
1196
|
+
chalk.cyan("\u{1F4A1} Recommended: Enable branch protection on GitHub")
|
|
1197
|
+
);
|
|
1198
|
+
console.log(
|
|
1199
|
+
chalk.dim(" Settings \u2192 Branches \u2192 Add rule \u2192 Require status checks\n")
|
|
1200
|
+
);
|
|
1073
1201
|
}
|
|
1074
1202
|
}
|
|
1075
1203
|
|
|
@@ -1079,7 +1207,9 @@ import { execa as execa2 } from "execa";
|
|
|
1079
1207
|
async function validateCommand(type, value, _options = {}) {
|
|
1080
1208
|
const config = await loadConfig();
|
|
1081
1209
|
if (!config) {
|
|
1082
|
-
console.error(
|
|
1210
|
+
console.error(
|
|
1211
|
+
chalk2.red("\u2717 No workflow configuration found. Run: workflow init")
|
|
1212
|
+
);
|
|
1083
1213
|
process.exit(1);
|
|
1084
1214
|
}
|
|
1085
1215
|
let targetValue = value;
|
|
@@ -1129,8 +1259,12 @@ async function validateCommand(type, value, _options = {}) {
|
|
|
1129
1259
|
if (enforcementLevel === "strict") {
|
|
1130
1260
|
process.exit(1);
|
|
1131
1261
|
} else {
|
|
1132
|
-
console.log(
|
|
1133
|
-
|
|
1262
|
+
console.log(
|
|
1263
|
+
chalk2.yellow(
|
|
1264
|
+
`
|
|
1265
|
+
\u26A0\uFE0F Advisory mode: validation failed but not blocking`
|
|
1266
|
+
)
|
|
1267
|
+
);
|
|
1134
1268
|
process.exit(0);
|
|
1135
1269
|
}
|
|
1136
1270
|
}
|
|
@@ -1141,10 +1275,324 @@ async function validateCommand(type, value, _options = {}) {
|
|
|
1141
1275
|
}
|
|
1142
1276
|
|
|
1143
1277
|
// 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";
|
|
1144
1282
|
import chalk3 from "chalk";
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1283
|
+
function createConfigCommand() {
|
|
1284
|
+
const command = new Command("config").description("Manage workflow configuration").option("-f, --force", "Skip validation checks").option("--cwd <path>", "Working directory", process.cwd());
|
|
1285
|
+
command.command("validate").description("Validate workflow configuration").action(async () => {
|
|
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;
|
|
1148
1596
|
}
|
|
1149
1597
|
|
|
1150
1598
|
// src/cli/commands/suggest.ts
|
|
@@ -1185,16 +1633,18 @@ async function suggestCommand(feedback, options = {}) {
|
|
|
1185
1633
|
console.log(chalk4.dim("\nYour suggestion will be:"));
|
|
1186
1634
|
console.log(chalk4.dim(" 1. Reviewed by the community"));
|
|
1187
1635
|
console.log(chalk4.dim(" 2. Prioritized based on impact"));
|
|
1188
|
-
console.log(
|
|
1636
|
+
console.log(
|
|
1637
|
+
chalk4.dim(" 3. Incorporated into future releases if approved\n")
|
|
1638
|
+
);
|
|
1189
1639
|
}
|
|
1190
1640
|
|
|
1191
1641
|
// src/cli/commands/doctor.ts
|
|
1192
1642
|
import chalk5 from "chalk";
|
|
1193
1643
|
|
|
1194
1644
|
// src/validators/guidelines.ts
|
|
1195
|
-
import { existsSync as
|
|
1645
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1196
1646
|
import { readFile as readFile3, readdir } from "fs/promises";
|
|
1197
|
-
import { join as
|
|
1647
|
+
import { join as join6 } from "path";
|
|
1198
1648
|
function getEffectiveMandatoryTemplates(guidelinesConfig) {
|
|
1199
1649
|
const coreMandatory = getMandatoryTemplateFilenames();
|
|
1200
1650
|
if (!guidelinesConfig) {
|
|
@@ -1209,12 +1659,14 @@ function getEffectiveMandatoryTemplates(guidelinesConfig) {
|
|
|
1209
1659
|
}
|
|
1210
1660
|
}
|
|
1211
1661
|
if (guidelinesConfig.optionalOverrides) {
|
|
1212
|
-
mandatory = mandatory.filter(
|
|
1662
|
+
mandatory = mandatory.filter(
|
|
1663
|
+
(t) => !guidelinesConfig.optionalOverrides.includes(t)
|
|
1664
|
+
);
|
|
1213
1665
|
}
|
|
1214
1666
|
return mandatory;
|
|
1215
1667
|
}
|
|
1216
1668
|
async function validateGuidelinesExist(projectPath = process.cwd(), config) {
|
|
1217
|
-
const guidelinesDir =
|
|
1669
|
+
const guidelinesDir = join6(projectPath, "guidelines");
|
|
1218
1670
|
const result = {
|
|
1219
1671
|
valid: true,
|
|
1220
1672
|
missingMandatory: [],
|
|
@@ -1222,11 +1674,13 @@ async function validateGuidelinesExist(projectPath = process.cwd(), config) {
|
|
|
1222
1674
|
presentOptional: [],
|
|
1223
1675
|
errors: []
|
|
1224
1676
|
};
|
|
1225
|
-
if (!
|
|
1677
|
+
if (!existsSync6(guidelinesDir)) {
|
|
1226
1678
|
const mandatory2 = getEffectiveMandatoryTemplates(config?.guidelines);
|
|
1227
1679
|
result.valid = false;
|
|
1228
1680
|
result.missingMandatory = mandatory2;
|
|
1229
|
-
result.errors.push(
|
|
1681
|
+
result.errors.push(
|
|
1682
|
+
"Guidelines directory does not exist. Run: workflow init"
|
|
1683
|
+
);
|
|
1230
1684
|
return result;
|
|
1231
1685
|
}
|
|
1232
1686
|
let existingFiles = [];
|
|
@@ -1234,7 +1688,9 @@ async function validateGuidelinesExist(projectPath = process.cwd(), config) {
|
|
|
1234
1688
|
existingFiles = await readdir(guidelinesDir);
|
|
1235
1689
|
} catch (error) {
|
|
1236
1690
|
result.valid = false;
|
|
1237
|
-
result.errors.push(
|
|
1691
|
+
result.errors.push(
|
|
1692
|
+
`Cannot read guidelines directory: ${error instanceof Error ? error.message : String(error)}`
|
|
1693
|
+
);
|
|
1238
1694
|
return result;
|
|
1239
1695
|
}
|
|
1240
1696
|
const mandatory = getEffectiveMandatoryTemplates(config?.guidelines);
|
|
@@ -1260,7 +1716,7 @@ async function validateGuidelinesExist(projectPath = process.cwd(), config) {
|
|
|
1260
1716
|
return result;
|
|
1261
1717
|
}
|
|
1262
1718
|
async function validateGitHubActionsSetup(projectPath = process.cwd()) {
|
|
1263
|
-
const workflowsDir =
|
|
1719
|
+
const workflowsDir = join6(projectPath, ".github", "workflows");
|
|
1264
1720
|
const result = {
|
|
1265
1721
|
valid: true,
|
|
1266
1722
|
hasWorkflowFile: false,
|
|
@@ -1272,9 +1728,11 @@ async function validateGitHubActionsSetup(projectPath = process.cwd()) {
|
|
|
1272
1728
|
errors: [],
|
|
1273
1729
|
warnings: []
|
|
1274
1730
|
};
|
|
1275
|
-
if (!
|
|
1731
|
+
if (!existsSync6(workflowsDir)) {
|
|
1276
1732
|
result.valid = false;
|
|
1277
|
-
result.errors.push(
|
|
1733
|
+
result.errors.push(
|
|
1734
|
+
"GitHub Actions workflows directory does not exist. Run: workflow github:setup"
|
|
1735
|
+
);
|
|
1278
1736
|
return result;
|
|
1279
1737
|
}
|
|
1280
1738
|
let workflowFiles = [];
|
|
@@ -1282,23 +1740,38 @@ async function validateGitHubActionsSetup(projectPath = process.cwd()) {
|
|
|
1282
1740
|
workflowFiles = await readdir(workflowsDir);
|
|
1283
1741
|
} catch (error) {
|
|
1284
1742
|
result.valid = false;
|
|
1285
|
-
result.errors.push(
|
|
1743
|
+
result.errors.push(
|
|
1744
|
+
`Cannot read workflows directory: ${error instanceof Error ? error.message : String(error)}`
|
|
1745
|
+
);
|
|
1286
1746
|
return result;
|
|
1287
1747
|
}
|
|
1288
|
-
const ciWorkflowNames = [
|
|
1289
|
-
|
|
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
|
+
);
|
|
1290
1761
|
if (ciWorkflows.length === 0) {
|
|
1291
1762
|
result.valid = false;
|
|
1292
1763
|
result.errors.push("No CI workflow file found. Run: workflow github:setup");
|
|
1293
1764
|
return result;
|
|
1294
1765
|
}
|
|
1295
1766
|
result.hasWorkflowFile = true;
|
|
1296
|
-
const workflowPath =
|
|
1767
|
+
const workflowPath = join6(workflowsDir, ciWorkflows[0]);
|
|
1297
1768
|
let workflowContent = "";
|
|
1298
1769
|
try {
|
|
1299
1770
|
workflowContent = await readFile3(workflowPath, "utf-8");
|
|
1300
1771
|
} catch (error) {
|
|
1301
|
-
result.warnings.push(
|
|
1772
|
+
result.warnings.push(
|
|
1773
|
+
`Cannot read workflow file: ${error instanceof Error ? error.message : String(error)}`
|
|
1774
|
+
);
|
|
1302
1775
|
return result;
|
|
1303
1776
|
}
|
|
1304
1777
|
const contentLower = workflowContent.toLowerCase();
|
|
@@ -1327,10 +1800,16 @@ async function validateGitHubActionsSetup(projectPath = process.cwd()) {
|
|
|
1327
1800
|
} else {
|
|
1328
1801
|
result.warnings.push("CI workflow may be missing test step");
|
|
1329
1802
|
}
|
|
1330
|
-
const mandatoryChecks = [
|
|
1803
|
+
const mandatoryChecks = [
|
|
1804
|
+
result.hasLintCheck,
|
|
1805
|
+
result.hasTypecheckCheck,
|
|
1806
|
+
result.hasFormatCheck
|
|
1807
|
+
];
|
|
1331
1808
|
if (!mandatoryChecks.every(Boolean)) {
|
|
1332
1809
|
result.valid = false;
|
|
1333
|
-
result.errors.push(
|
|
1810
|
+
result.errors.push(
|
|
1811
|
+
"CI workflow is missing mandatory checks (lint, typecheck, format)"
|
|
1812
|
+
);
|
|
1334
1813
|
}
|
|
1335
1814
|
return result;
|
|
1336
1815
|
}
|
|
@@ -1367,13 +1846,25 @@ async function doctorCommand(options = {}) {
|
|
|
1367
1846
|
console.log(chalk5.bold("\n\u{1F4DA} Guidelines"));
|
|
1368
1847
|
const guidelinesResult = await validateGuidelinesExist(cwd, config);
|
|
1369
1848
|
if (guidelinesResult.valid) {
|
|
1370
|
-
console.log(
|
|
1849
|
+
console.log(
|
|
1850
|
+
chalk5.green(
|
|
1851
|
+
` \u2713 All ${guidelinesResult.presentMandatory.length} mandatory guidelines present`
|
|
1852
|
+
)
|
|
1853
|
+
);
|
|
1371
1854
|
if (guidelinesResult.presentOptional.length > 0) {
|
|
1372
|
-
console.log(
|
|
1855
|
+
console.log(
|
|
1856
|
+
chalk5.dim(
|
|
1857
|
+
` + ${guidelinesResult.presentOptional.length} optional guidelines`
|
|
1858
|
+
)
|
|
1859
|
+
);
|
|
1373
1860
|
}
|
|
1374
1861
|
} else {
|
|
1375
1862
|
hasErrors = true;
|
|
1376
|
-
console.log(
|
|
1863
|
+
console.log(
|
|
1864
|
+
chalk5.red(
|
|
1865
|
+
` \u2717 Missing ${guidelinesResult.missingMandatory.length} mandatory guidelines:`
|
|
1866
|
+
)
|
|
1867
|
+
);
|
|
1377
1868
|
for (const missing of guidelinesResult.missingMandatory) {
|
|
1378
1869
|
console.log(chalk5.red(` \u2022 ${missing}`));
|
|
1379
1870
|
}
|
|
@@ -1387,14 +1878,20 @@ async function doctorCommand(options = {}) {
|
|
|
1387
1878
|
const hookStatuses = await getAllHooksStatus(cwd);
|
|
1388
1879
|
const installedHooks = hookStatuses.filter((h) => h.installed);
|
|
1389
1880
|
if (installedHooks.length === hookStatuses.length) {
|
|
1390
|
-
console.log(
|
|
1881
|
+
console.log(
|
|
1882
|
+
chalk5.green(` \u2713 All ${installedHooks.length} hooks installed`)
|
|
1883
|
+
);
|
|
1391
1884
|
for (const hook of hookStatuses) {
|
|
1392
1885
|
const extra = hook.wrappedOriginal ? " (wrapping original)" : "";
|
|
1393
1886
|
console.log(chalk5.dim(` \u2022 ${hook.hookType}${extra}`));
|
|
1394
1887
|
}
|
|
1395
1888
|
} else if (installedHooks.length > 0) {
|
|
1396
1889
|
hasWarnings = true;
|
|
1397
|
-
console.log(
|
|
1890
|
+
console.log(
|
|
1891
|
+
chalk5.yellow(
|
|
1892
|
+
` \u26A0 ${installedHooks.length}/${hookStatuses.length} hooks installed`
|
|
1893
|
+
)
|
|
1894
|
+
);
|
|
1398
1895
|
for (const hook of hookStatuses) {
|
|
1399
1896
|
if (hook.installed) {
|
|
1400
1897
|
console.log(chalk5.green(` \u2713 ${hook.hookType}`));
|
|
@@ -1417,7 +1914,11 @@ async function doctorCommand(options = {}) {
|
|
|
1417
1914
|
console.log(chalk5.dim(" \u25CB Not a GitHub repository (CI check skipped)"));
|
|
1418
1915
|
console.log(chalk5.dim(` Remote: ${repoInfo.remoteUrl || "none"}`));
|
|
1419
1916
|
} else {
|
|
1420
|
-
console.log(
|
|
1917
|
+
console.log(
|
|
1918
|
+
chalk5.dim(
|
|
1919
|
+
` Repository: ${repoInfo.github?.owner}/${repoInfo.github?.repo}`
|
|
1920
|
+
)
|
|
1921
|
+
);
|
|
1421
1922
|
const ciResult = await validateGitHubActionsSetup(cwd);
|
|
1422
1923
|
if (ciResult.valid) {
|
|
1423
1924
|
console.log(chalk5.green(" \u2713 GitHub Actions CI configured correctly"));
|
|
@@ -1446,15 +1947,21 @@ async function doctorCommand(options = {}) {
|
|
|
1446
1947
|
if (repoInfo.isGitHub && hasCIWorkflow(cwd)) {
|
|
1447
1948
|
console.log(chalk5.cyan(" \u2192 Enable branch protection on GitHub"));
|
|
1448
1949
|
console.log(chalk5.dim(" Settings \u2192 Branches \u2192 Add rule"));
|
|
1449
|
-
console.log(
|
|
1450
|
-
|
|
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
|
+
);
|
|
1451
1956
|
}
|
|
1452
1957
|
if (config.enforcement !== "strict") {
|
|
1453
1958
|
console.log(chalk5.cyan(` \u2192 Consider switching to 'strict' enforcement`));
|
|
1454
1959
|
console.log(chalk5.dim(` Current: ${config.enforcement}`));
|
|
1455
1960
|
}
|
|
1456
1961
|
if (config.scopes.length < 5) {
|
|
1457
|
-
console.log(
|
|
1962
|
+
console.log(
|
|
1963
|
+
chalk5.cyan(" \u2192 Consider adding more scopes for better organization")
|
|
1964
|
+
);
|
|
1458
1965
|
console.log(chalk5.dim(" Run: workflow config add scope <name>"));
|
|
1459
1966
|
}
|
|
1460
1967
|
console.log(chalk5.bold("\n\u{1F4CA} Summary"));
|
|
@@ -1472,8 +1979,8 @@ async function doctorCommand(options = {}) {
|
|
|
1472
1979
|
// src/cli/commands/setup.ts
|
|
1473
1980
|
import * as p3 from "@clack/prompts";
|
|
1474
1981
|
import chalk6 from "chalk";
|
|
1475
|
-
import { readFileSync, writeFileSync, existsSync as
|
|
1476
|
-
import { join as
|
|
1982
|
+
import { readFileSync, writeFileSync as writeFileSync2, existsSync as existsSync7 } from "fs";
|
|
1983
|
+
import { join as join7 } from "path";
|
|
1477
1984
|
var WORKFLOW_SCRIPTS = {
|
|
1478
1985
|
"workflow:init": "workflow-agent init",
|
|
1479
1986
|
"workflow:validate": "workflow-agent validate",
|
|
@@ -1483,8 +1990,8 @@ var WORKFLOW_SCRIPTS = {
|
|
|
1483
1990
|
async function setupCommand() {
|
|
1484
1991
|
p3.intro(chalk6.bgBlue(" workflow-agent setup "));
|
|
1485
1992
|
const cwd = process.cwd();
|
|
1486
|
-
const packageJsonPath =
|
|
1487
|
-
if (!
|
|
1993
|
+
const packageJsonPath = join7(cwd, "package.json");
|
|
1994
|
+
if (!existsSync7(packageJsonPath)) {
|
|
1488
1995
|
p3.cancel("No package.json found in current directory");
|
|
1489
1996
|
process.exit(1);
|
|
1490
1997
|
}
|
|
@@ -1527,12 +2034,16 @@ async function setupCommand() {
|
|
|
1527
2034
|
for (const [scriptName, scriptCommand] of Object.entries(scriptsToAdd)) {
|
|
1528
2035
|
packageJson.scripts[scriptName] = scriptCommand;
|
|
1529
2036
|
}
|
|
1530
|
-
|
|
2037
|
+
writeFileSync2(
|
|
1531
2038
|
packageJsonPath,
|
|
1532
2039
|
JSON.stringify(packageJson, null, 2) + "\n",
|
|
1533
2040
|
"utf-8"
|
|
1534
2041
|
);
|
|
1535
|
-
p3.outro(
|
|
2042
|
+
p3.outro(
|
|
2043
|
+
chalk6.green(
|
|
2044
|
+
`\u2713 Added ${Object.keys(scriptsToAdd).length} workflow scripts to package.json!`
|
|
2045
|
+
)
|
|
2046
|
+
);
|
|
1536
2047
|
console.log(chalk6.dim("\nRun them with:"));
|
|
1537
2048
|
console.log(chalk6.dim(" pnpm run workflow:init"));
|
|
1538
2049
|
console.log(chalk6.dim(" npm run workflow:init\n"));
|
|
@@ -1541,14 +2052,14 @@ async function setupCommand() {
|
|
|
1541
2052
|
// src/cli/commands/scope-create.ts
|
|
1542
2053
|
import * as p4 from "@clack/prompts";
|
|
1543
2054
|
import chalk7 from "chalk";
|
|
1544
|
-
import { existsSync as
|
|
2055
|
+
import { existsSync as existsSync8 } from "fs";
|
|
1545
2056
|
import { writeFile as writeFile4, mkdir as mkdir4, readFile as readFile4 } from "fs/promises";
|
|
1546
|
-
import { join as
|
|
2057
|
+
import { join as join8 } from "path";
|
|
1547
2058
|
async function scopeCreateCommand(options) {
|
|
1548
2059
|
console.log(chalk7.bold.cyan("\n\u{1F3A8} Create Custom Scope Package\n"));
|
|
1549
2060
|
const cwd = process.cwd();
|
|
1550
2061
|
const isNonInteractive = !!(options.name && options.scopes && options.presetName);
|
|
1551
|
-
const isMonorepo2 =
|
|
2062
|
+
const isMonorepo2 = existsSync8(join8(cwd, "pnpm-workspace.yaml"));
|
|
1552
2063
|
if (isMonorepo2) {
|
|
1553
2064
|
console.log(chalk7.dim("\u2713 Detected monorepo workspace\n"));
|
|
1554
2065
|
}
|
|
@@ -1557,8 +2068,10 @@ async function scopeCreateCommand(options) {
|
|
|
1557
2068
|
placeholder: "my-custom-scope",
|
|
1558
2069
|
validate: (value) => {
|
|
1559
2070
|
if (!value || value.length === 0) return "Package name is required";
|
|
1560
|
-
if (!/^[a-z0-9-]+$/.test(value))
|
|
1561
|
-
|
|
2071
|
+
if (!/^[a-z0-9-]+$/.test(value))
|
|
2072
|
+
return "Package name must be lowercase alphanumeric with hyphens";
|
|
2073
|
+
if (value.length > 32)
|
|
2074
|
+
return "Package name must be 32 characters or less";
|
|
1562
2075
|
return void 0;
|
|
1563
2076
|
}
|
|
1564
2077
|
});
|
|
@@ -1593,7 +2106,9 @@ async function scopeCreateCommand(options) {
|
|
|
1593
2106
|
});
|
|
1594
2107
|
}
|
|
1595
2108
|
} else {
|
|
1596
|
-
console.log(
|
|
2109
|
+
console.log(
|
|
2110
|
+
chalk7.dim("\nAdd scopes to your preset (aim for 8-15 scopes):\n")
|
|
2111
|
+
);
|
|
1597
2112
|
let addMore = true;
|
|
1598
2113
|
while (addMore) {
|
|
1599
2114
|
const scopeName = await p4.text({
|
|
@@ -1601,9 +2116,11 @@ async function scopeCreateCommand(options) {
|
|
|
1601
2116
|
placeholder: "auth",
|
|
1602
2117
|
validate: (value) => {
|
|
1603
2118
|
if (!value || value.length === 0) return "Scope name is required";
|
|
1604
|
-
if (!/^[a-z0-9-]+$/.test(value))
|
|
2119
|
+
if (!/^[a-z0-9-]+$/.test(value))
|
|
2120
|
+
return "Must be lowercase alphanumeric with hyphens";
|
|
1605
2121
|
if (value.length > 32) return "Must be 32 characters or less";
|
|
1606
|
-
if (scopes.some((s) => s.name === value))
|
|
2122
|
+
if (scopes.some((s) => s.name === value))
|
|
2123
|
+
return "Scope name already exists";
|
|
1607
2124
|
return void 0;
|
|
1608
2125
|
}
|
|
1609
2126
|
});
|
|
@@ -1614,7 +2131,8 @@ async function scopeCreateCommand(options) {
|
|
|
1614
2131
|
message: "Description:",
|
|
1615
2132
|
placeholder: "Authentication and authorization",
|
|
1616
2133
|
validate: (value) => {
|
|
1617
|
-
if (!value || value.length < 10)
|
|
2134
|
+
if (!value || value.length < 10)
|
|
2135
|
+
return "Description must be at least 10 characters";
|
|
1618
2136
|
return void 0;
|
|
1619
2137
|
}
|
|
1620
2138
|
});
|
|
@@ -1672,18 +2190,22 @@ async function scopeCreateCommand(options) {
|
|
|
1672
2190
|
const validation = validateScopeDefinitions(scopes);
|
|
1673
2191
|
if (!validation.valid) {
|
|
1674
2192
|
console.log(chalk7.red("\n\u2717 Scope validation failed:\n"));
|
|
1675
|
-
validation.errors.forEach(
|
|
2193
|
+
validation.errors.forEach(
|
|
2194
|
+
(error) => console.log(chalk7.red(` \u2022 ${error}`))
|
|
2195
|
+
);
|
|
1676
2196
|
p4.cancel("Operation cancelled");
|
|
1677
2197
|
process.exit(1);
|
|
1678
2198
|
}
|
|
1679
|
-
console.log(
|
|
2199
|
+
console.log(
|
|
2200
|
+
chalk7.green(`
|
|
1680
2201
|
\u2713 ${scopes.length} scopes validated successfully
|
|
1681
|
-
`)
|
|
2202
|
+
`)
|
|
2203
|
+
);
|
|
1682
2204
|
let outputDir;
|
|
1683
2205
|
if (options.outputDir) {
|
|
1684
2206
|
outputDir = options.outputDir;
|
|
1685
2207
|
} else if (isMonorepo2) {
|
|
1686
|
-
outputDir =
|
|
2208
|
+
outputDir = join8(cwd, "packages", `scopes-${packageName}`);
|
|
1687
2209
|
} else {
|
|
1688
2210
|
const customDir = await p4.text({
|
|
1689
2211
|
message: "Output directory:",
|
|
@@ -1694,9 +2216,9 @@ async function scopeCreateCommand(options) {
|
|
|
1694
2216
|
p4.cancel("Operation cancelled");
|
|
1695
2217
|
process.exit(0);
|
|
1696
2218
|
}
|
|
1697
|
-
outputDir =
|
|
2219
|
+
outputDir = join8(cwd, customDir);
|
|
1698
2220
|
}
|
|
1699
|
-
if (
|
|
2221
|
+
if (existsSync8(outputDir)) {
|
|
1700
2222
|
const shouldOverwrite = await p4.confirm({
|
|
1701
2223
|
message: `Directory ${outputDir} already exists. Overwrite?`,
|
|
1702
2224
|
initialValue: false
|
|
@@ -1709,7 +2231,7 @@ async function scopeCreateCommand(options) {
|
|
|
1709
2231
|
const spinner5 = p4.spinner();
|
|
1710
2232
|
spinner5.start("Creating package structure...");
|
|
1711
2233
|
try {
|
|
1712
|
-
await mkdir4(
|
|
2234
|
+
await mkdir4(join8(outputDir, "src"), { recursive: true });
|
|
1713
2235
|
const packageJson = {
|
|
1714
2236
|
name: `@workflow/scopes-${packageName}`,
|
|
1715
2237
|
version: "1.0.0",
|
|
@@ -1750,7 +2272,7 @@ async function scopeCreateCommand(options) {
|
|
|
1750
2272
|
}
|
|
1751
2273
|
};
|
|
1752
2274
|
await writeFile4(
|
|
1753
|
-
|
|
2275
|
+
join8(outputDir, "package.json"),
|
|
1754
2276
|
JSON.stringify(packageJson, null, 2),
|
|
1755
2277
|
"utf-8"
|
|
1756
2278
|
);
|
|
@@ -1763,7 +2285,7 @@ async function scopeCreateCommand(options) {
|
|
|
1763
2285
|
include: ["src/**/*"]
|
|
1764
2286
|
};
|
|
1765
2287
|
await writeFile4(
|
|
1766
|
-
|
|
2288
|
+
join8(outputDir, "tsconfig.json"),
|
|
1767
2289
|
JSON.stringify(tsconfig, null, 2),
|
|
1768
2290
|
"utf-8"
|
|
1769
2291
|
);
|
|
@@ -1777,7 +2299,7 @@ export default defineConfig({
|
|
|
1777
2299
|
sourcemap: true,
|
|
1778
2300
|
});
|
|
1779
2301
|
`;
|
|
1780
|
-
await writeFile4(
|
|
2302
|
+
await writeFile4(join8(outputDir, "tsup.config.ts"), tsupConfig, "utf-8");
|
|
1781
2303
|
const indexTs = `import type { Scope } from '@hawkinside_out/workflow-agent/config';
|
|
1782
2304
|
|
|
1783
2305
|
export const scopes: Scope[] = ${JSON.stringify(scopes, null, 2)};
|
|
@@ -1791,7 +2313,7 @@ export const preset = {
|
|
|
1791
2313
|
|
|
1792
2314
|
export default preset;
|
|
1793
2315
|
`;
|
|
1794
|
-
await writeFile4(
|
|
2316
|
+
await writeFile4(join8(outputDir, "src", "index.ts"), indexTs, "utf-8");
|
|
1795
2317
|
if (!options.noTest) {
|
|
1796
2318
|
const testFile = `import { describe, it, expect } from 'vitest';
|
|
1797
2319
|
import { scopes, preset } from './index.js';
|
|
@@ -1832,21 +2354,29 @@ describe('${presetName} Scope Preset', () => {
|
|
|
1832
2354
|
});
|
|
1833
2355
|
});
|
|
1834
2356
|
`;
|
|
1835
|
-
await writeFile4(
|
|
2357
|
+
await writeFile4(
|
|
2358
|
+
join8(outputDir, "src", "index.test.ts"),
|
|
2359
|
+
testFile,
|
|
2360
|
+
"utf-8"
|
|
2361
|
+
);
|
|
1836
2362
|
}
|
|
1837
2363
|
spinner5.stop("\u2713 Package structure created");
|
|
1838
2364
|
if (isMonorepo2) {
|
|
1839
|
-
const workspaceFile =
|
|
2365
|
+
const workspaceFile = join8(cwd, "pnpm-workspace.yaml");
|
|
1840
2366
|
const workspaceContent = await readFile4(workspaceFile, "utf-8");
|
|
1841
2367
|
const packagePath = `packages/scopes-${packageName}`;
|
|
1842
2368
|
if (!workspaceContent.includes(packagePath) && !workspaceContent.includes("packages/*")) {
|
|
1843
|
-
console.log(
|
|
2369
|
+
console.log(
|
|
2370
|
+
chalk7.yellow("\n\u26A0\uFE0F Add the following to pnpm-workspace.yaml:")
|
|
2371
|
+
);
|
|
1844
2372
|
console.log(chalk7.dim(` - '${packagePath}'`));
|
|
1845
2373
|
} else {
|
|
1846
2374
|
console.log(chalk7.green("\n\u2713 Package will be included in workspace"));
|
|
1847
2375
|
}
|
|
1848
2376
|
}
|
|
1849
|
-
console.log(
|
|
2377
|
+
console.log(
|
|
2378
|
+
chalk7.green.bold("\n\u2728 Custom scope package created successfully!\n")
|
|
2379
|
+
);
|
|
1850
2380
|
console.log(chalk7.bold("Package details:"));
|
|
1851
2381
|
console.log(chalk7.dim(` Location: ${outputDir}`));
|
|
1852
2382
|
console.log(chalk7.dim(` Package: @workflow/scopes-${packageName}`));
|
|
@@ -1859,17 +2389,27 @@ describe('${presetName} Scope Preset', () => {
|
|
|
1859
2389
|
if (!options.noTest) {
|
|
1860
2390
|
console.log(chalk7.dim(` 4. pnpm test`));
|
|
1861
2391
|
}
|
|
1862
|
-
console.log(
|
|
2392
|
+
console.log(
|
|
2393
|
+
chalk7.dim(
|
|
2394
|
+
` ${!options.noTest ? "5" : "4"}. Update repository URL in package.json`
|
|
2395
|
+
)
|
|
2396
|
+
);
|
|
1863
2397
|
const shouldPublish = isNonInteractive ? false : await p4.confirm({
|
|
1864
2398
|
message: "\nWould you like instructions for publishing to npm?",
|
|
1865
2399
|
initialValue: false
|
|
1866
2400
|
});
|
|
1867
2401
|
if (shouldPublish && !p4.isCancel(shouldPublish)) {
|
|
1868
2402
|
console.log(chalk7.bold("\n\u{1F4E6} Publishing instructions:\n"));
|
|
1869
|
-
console.log(
|
|
2403
|
+
console.log(
|
|
2404
|
+
chalk7.dim(" 1. npm login (or configure .npmrc with your registry)")
|
|
2405
|
+
);
|
|
1870
2406
|
console.log(chalk7.dim(" 2. Update version in package.json as needed"));
|
|
1871
2407
|
console.log(chalk7.dim(" 3. pnpm publish --access public"));
|
|
1872
|
-
console.log(
|
|
2408
|
+
console.log(
|
|
2409
|
+
chalk7.dim(
|
|
2410
|
+
" 4. Use in other projects: pnpm add @workflow/scopes-" + packageName + "\n"
|
|
2411
|
+
)
|
|
2412
|
+
);
|
|
1873
2413
|
}
|
|
1874
2414
|
} catch (error) {
|
|
1875
2415
|
spinner5.stop("\u2717 Failed to create package");
|
|
@@ -1881,9 +2421,9 @@ describe('${presetName} Scope Preset', () => {
|
|
|
1881
2421
|
// src/cli/commands/scope-migrate.ts
|
|
1882
2422
|
import * as p5 from "@clack/prompts";
|
|
1883
2423
|
import chalk8 from "chalk";
|
|
1884
|
-
import { existsSync as
|
|
2424
|
+
import { existsSync as existsSync9 } from "fs";
|
|
1885
2425
|
import { writeFile as writeFile5, mkdir as mkdir5, readFile as readFile5 } from "fs/promises";
|
|
1886
|
-
import { join as
|
|
2426
|
+
import { join as join9 } from "path";
|
|
1887
2427
|
async function scopeMigrateCommand(options) {
|
|
1888
2428
|
console.log(chalk8.bold.cyan("\n\u{1F504} Migrate Scopes to Custom Package\n"));
|
|
1889
2429
|
const cwd = process.cwd();
|
|
@@ -1906,11 +2446,17 @@ async function scopeMigrateCommand(options) {
|
|
|
1906
2446
|
p5.cancel("No scopes found in workflow.config.json");
|
|
1907
2447
|
process.exit(1);
|
|
1908
2448
|
}
|
|
1909
|
-
console.log(
|
|
1910
|
-
`
|
|
2449
|
+
console.log(
|
|
2450
|
+
chalk8.dim(`Found ${config.scopes.length} scopes in workflow.config.json
|
|
2451
|
+
`)
|
|
2452
|
+
);
|
|
1911
2453
|
console.log(chalk8.bold("Current scopes:"));
|
|
1912
2454
|
config.scopes.forEach((scope, i) => {
|
|
1913
|
-
console.log(
|
|
2455
|
+
console.log(
|
|
2456
|
+
chalk8.dim(
|
|
2457
|
+
` ${i + 1}. ${scope.emoji || "\u2022"} ${scope.name} - ${scope.description}`
|
|
2458
|
+
)
|
|
2459
|
+
);
|
|
1914
2460
|
});
|
|
1915
2461
|
console.log();
|
|
1916
2462
|
const shouldContinue = await p5.confirm({
|
|
@@ -1921,7 +2467,7 @@ async function scopeMigrateCommand(options) {
|
|
|
1921
2467
|
p5.cancel("Migration cancelled");
|
|
1922
2468
|
process.exit(0);
|
|
1923
2469
|
}
|
|
1924
|
-
const isMonorepo2 =
|
|
2470
|
+
const isMonorepo2 = existsSync9(join9(cwd, "pnpm-workspace.yaml"));
|
|
1925
2471
|
if (isMonorepo2) {
|
|
1926
2472
|
console.log(chalk8.dim("\n\u2713 Detected monorepo workspace\n"));
|
|
1927
2473
|
}
|
|
@@ -1930,8 +2476,10 @@ async function scopeMigrateCommand(options) {
|
|
|
1930
2476
|
placeholder: config.projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-"),
|
|
1931
2477
|
validate: (value) => {
|
|
1932
2478
|
if (!value || value.length === 0) return "Package name is required";
|
|
1933
|
-
if (!/^[a-z0-9-]+$/.test(value))
|
|
1934
|
-
|
|
2479
|
+
if (!/^[a-z0-9-]+$/.test(value))
|
|
2480
|
+
return "Package name must be lowercase alphanumeric with hyphens";
|
|
2481
|
+
if (value.length > 32)
|
|
2482
|
+
return "Package name must be 32 characters or less";
|
|
1935
2483
|
return void 0;
|
|
1936
2484
|
}
|
|
1937
2485
|
});
|
|
@@ -1953,7 +2501,9 @@ async function scopeMigrateCommand(options) {
|
|
|
1953
2501
|
const validation = validateScopeDefinitions(config.scopes);
|
|
1954
2502
|
if (!validation.valid) {
|
|
1955
2503
|
console.log(chalk8.yellow("\n\u26A0\uFE0F Scope validation warnings:\n"));
|
|
1956
|
-
validation.errors.forEach(
|
|
2504
|
+
validation.errors.forEach(
|
|
2505
|
+
(error) => console.log(chalk8.yellow(` \u2022 ${error}`))
|
|
2506
|
+
);
|
|
1957
2507
|
const shouldFix = await p5.confirm({
|
|
1958
2508
|
message: "Some scopes have validation issues. Continue anyway?",
|
|
1959
2509
|
initialValue: false
|
|
@@ -1967,7 +2517,7 @@ async function scopeMigrateCommand(options) {
|
|
|
1967
2517
|
if (options.outputDir) {
|
|
1968
2518
|
outputDir = options.outputDir;
|
|
1969
2519
|
} else if (isMonorepo2) {
|
|
1970
|
-
outputDir =
|
|
2520
|
+
outputDir = join9(cwd, "packages", `scopes-${packageName}`);
|
|
1971
2521
|
} else {
|
|
1972
2522
|
const customDir = await p5.text({
|
|
1973
2523
|
message: "Output directory:",
|
|
@@ -1978,9 +2528,9 @@ async function scopeMigrateCommand(options) {
|
|
|
1978
2528
|
p5.cancel("Migration cancelled");
|
|
1979
2529
|
process.exit(0);
|
|
1980
2530
|
}
|
|
1981
|
-
outputDir =
|
|
2531
|
+
outputDir = join9(cwd, customDir);
|
|
1982
2532
|
}
|
|
1983
|
-
if (
|
|
2533
|
+
if (existsSync9(outputDir)) {
|
|
1984
2534
|
const shouldOverwrite = await p5.confirm({
|
|
1985
2535
|
message: `Directory ${outputDir} already exists. Overwrite?`,
|
|
1986
2536
|
initialValue: false
|
|
@@ -1993,7 +2543,7 @@ async function scopeMigrateCommand(options) {
|
|
|
1993
2543
|
const spinner5 = p5.spinner();
|
|
1994
2544
|
spinner5.start("Migrating scopes to package...");
|
|
1995
2545
|
try {
|
|
1996
|
-
await mkdir5(
|
|
2546
|
+
await mkdir5(join9(outputDir, "src"), { recursive: true });
|
|
1997
2547
|
const packageJson = {
|
|
1998
2548
|
name: `@workflow/scopes-${packageName}`,
|
|
1999
2549
|
version: "1.0.0",
|
|
@@ -2034,7 +2584,7 @@ async function scopeMigrateCommand(options) {
|
|
|
2034
2584
|
}
|
|
2035
2585
|
};
|
|
2036
2586
|
await writeFile5(
|
|
2037
|
-
|
|
2587
|
+
join9(outputDir, "package.json"),
|
|
2038
2588
|
JSON.stringify(packageJson, null, 2),
|
|
2039
2589
|
"utf-8"
|
|
2040
2590
|
);
|
|
@@ -2047,7 +2597,7 @@ async function scopeMigrateCommand(options) {
|
|
|
2047
2597
|
include: ["src/**/*"]
|
|
2048
2598
|
};
|
|
2049
2599
|
await writeFile5(
|
|
2050
|
-
|
|
2600
|
+
join9(outputDir, "tsconfig.json"),
|
|
2051
2601
|
JSON.stringify(tsconfig, null, 2),
|
|
2052
2602
|
"utf-8"
|
|
2053
2603
|
);
|
|
@@ -2061,7 +2611,7 @@ export default defineConfig({
|
|
|
2061
2611
|
sourcemap: true,
|
|
2062
2612
|
});
|
|
2063
2613
|
`;
|
|
2064
|
-
await writeFile5(
|
|
2614
|
+
await writeFile5(join9(outputDir, "tsup.config.ts"), tsupConfig, "utf-8");
|
|
2065
2615
|
const indexTs = `import type { Scope } from '@hawkinside_out/workflow-agent/config';
|
|
2066
2616
|
|
|
2067
2617
|
export const scopes: Scope[] = ${JSON.stringify(config.scopes, null, 2)};
|
|
@@ -2075,7 +2625,7 @@ export const preset = {
|
|
|
2075
2625
|
|
|
2076
2626
|
export default preset;
|
|
2077
2627
|
`;
|
|
2078
|
-
await writeFile5(
|
|
2628
|
+
await writeFile5(join9(outputDir, "src", "index.ts"), indexTs, "utf-8");
|
|
2079
2629
|
const testFile = `import { describe, it, expect } from 'vitest';
|
|
2080
2630
|
import { scopes, preset } from './index.js';
|
|
2081
2631
|
import { ScopeSchema } from '@hawkinside_out/workflow-agent/config';
|
|
@@ -2118,14 +2668,16 @@ describe('${presetName} Scope Preset (Migrated)', () => {
|
|
|
2118
2668
|
});
|
|
2119
2669
|
});
|
|
2120
2670
|
`;
|
|
2121
|
-
await writeFile5(
|
|
2671
|
+
await writeFile5(join9(outputDir, "src", "index.test.ts"), testFile, "utf-8");
|
|
2122
2672
|
spinner5.stop("\u2713 Package created from migrated scopes");
|
|
2123
2673
|
if (isMonorepo2) {
|
|
2124
|
-
const workspaceFile =
|
|
2674
|
+
const workspaceFile = join9(cwd, "pnpm-workspace.yaml");
|
|
2125
2675
|
const workspaceContent = await readFile5(workspaceFile, "utf-8");
|
|
2126
2676
|
const packagePath = `packages/scopes-${packageName}`;
|
|
2127
2677
|
if (!workspaceContent.includes(packagePath) && !workspaceContent.includes("packages/*")) {
|
|
2128
|
-
console.log(
|
|
2678
|
+
console.log(
|
|
2679
|
+
chalk8.yellow("\n\u26A0\uFE0F Add the following to pnpm-workspace.yaml:")
|
|
2680
|
+
);
|
|
2129
2681
|
console.log(chalk8.dim(` - '${packagePath}'`));
|
|
2130
2682
|
} else {
|
|
2131
2683
|
console.log(chalk8.green("\n\u2713 Package will be included in workspace"));
|
|
@@ -2136,7 +2688,7 @@ describe('${presetName} Scope Preset (Migrated)', () => {
|
|
|
2136
2688
|
initialValue: false
|
|
2137
2689
|
});
|
|
2138
2690
|
if (!p5.isCancel(keepConfig) && !keepConfig) {
|
|
2139
|
-
const configPath =
|
|
2691
|
+
const configPath = join9(cwd, "workflow.config.json");
|
|
2140
2692
|
const updatedConfig = {
|
|
2141
2693
|
...config,
|
|
2142
2694
|
scopes: [],
|
|
@@ -2144,11 +2696,17 @@ describe('${presetName} Scope Preset (Migrated)', () => {
|
|
|
2144
2696
|
preset: `scopes-${packageName}`
|
|
2145
2697
|
// Reference the new package
|
|
2146
2698
|
};
|
|
2147
|
-
await writeFile5(
|
|
2699
|
+
await writeFile5(
|
|
2700
|
+
configPath,
|
|
2701
|
+
JSON.stringify(updatedConfig, null, 2),
|
|
2702
|
+
"utf-8"
|
|
2703
|
+
);
|
|
2148
2704
|
console.log(chalk8.green("\u2713 Updated workflow.config.json"));
|
|
2149
2705
|
console.log(chalk8.dim(" \u2022 Cleared inline scopes"));
|
|
2150
|
-
console.log(
|
|
2151
|
-
`
|
|
2706
|
+
console.log(
|
|
2707
|
+
chalk8.dim(` \u2022 Added preset reference: scopes-${packageName}
|
|
2708
|
+
`)
|
|
2709
|
+
);
|
|
2152
2710
|
}
|
|
2153
2711
|
console.log(chalk8.green.bold("\n\u2728 Migration completed successfully!\n"));
|
|
2154
2712
|
console.log(chalk8.bold("Package details:"));
|
|
@@ -2165,11 +2723,23 @@ describe('${presetName} Scope Preset (Migrated)', () => {
|
|
|
2165
2723
|
`));
|
|
2166
2724
|
if (!keepConfig) {
|
|
2167
2725
|
console.log(chalk8.bold("To use the migrated scopes:\n"));
|
|
2168
|
-
console.log(
|
|
2169
|
-
|
|
2170
|
-
`
|
|
2171
|
-
|
|
2172
|
-
|
|
2726
|
+
console.log(
|
|
2727
|
+
chalk8.dim(
|
|
2728
|
+
` 1. Install the package: pnpm add -w @workflow/scopes-${packageName}`
|
|
2729
|
+
)
|
|
2730
|
+
);
|
|
2731
|
+
console.log(
|
|
2732
|
+
chalk8.dim(
|
|
2733
|
+
` 2. The preset is already referenced in workflow.config.json
|
|
2734
|
+
`
|
|
2735
|
+
)
|
|
2736
|
+
);
|
|
2737
|
+
}
|
|
2738
|
+
console.log(
|
|
2739
|
+
chalk8.dim(
|
|
2740
|
+
"\u{1F4A1} Tip: You can now reuse this scope package across multiple projects!\n"
|
|
2741
|
+
)
|
|
2742
|
+
);
|
|
2173
2743
|
} catch (error) {
|
|
2174
2744
|
spinner5.stop("\u2717 Migration failed");
|
|
2175
2745
|
console.error(chalk8.red("\nError:"), error);
|
|
@@ -2211,12 +2781,18 @@ async function installHooksAction(cwd) {
|
|
|
2211
2781
|
for (const result of results) {
|
|
2212
2782
|
if (result.success) {
|
|
2213
2783
|
if (result.wrappedExisting) {
|
|
2214
|
-
console.log(
|
|
2784
|
+
console.log(
|
|
2785
|
+
chalk9.green(
|
|
2786
|
+
`\u2713 Installed ${result.hookType} hook (wrapped existing hook)`
|
|
2787
|
+
)
|
|
2788
|
+
);
|
|
2215
2789
|
} else {
|
|
2216
2790
|
console.log(chalk9.green(`\u2713 Installed ${result.hookType} hook`));
|
|
2217
2791
|
}
|
|
2218
2792
|
} else {
|
|
2219
|
-
console.error(
|
|
2793
|
+
console.error(
|
|
2794
|
+
chalk9.red(`\u2717 Failed to install ${result.hookType}: ${result.error}`)
|
|
2795
|
+
);
|
|
2220
2796
|
hasErrors = true;
|
|
2221
2797
|
}
|
|
2222
2798
|
}
|
|
@@ -2239,12 +2815,16 @@ async function uninstallHooksAction(cwd) {
|
|
|
2239
2815
|
for (const result of results) {
|
|
2240
2816
|
if (result.success) {
|
|
2241
2817
|
if (result.wrappedExisting) {
|
|
2242
|
-
console.log(
|
|
2818
|
+
console.log(
|
|
2819
|
+
chalk9.green(`\u2713 Removed ${result.hookType} hook (restored original)`)
|
|
2820
|
+
);
|
|
2243
2821
|
} else {
|
|
2244
2822
|
console.log(chalk9.green(`\u2713 Removed ${result.hookType} hook`));
|
|
2245
2823
|
}
|
|
2246
2824
|
} else if (result.error) {
|
|
2247
|
-
console.error(
|
|
2825
|
+
console.error(
|
|
2826
|
+
chalk9.red(`\u2717 Failed to remove ${result.hookType}: ${result.error}`)
|
|
2827
|
+
);
|
|
2248
2828
|
hasErrors = true;
|
|
2249
2829
|
}
|
|
2250
2830
|
}
|
|
@@ -2320,7 +2900,11 @@ async function setupAction(cwd) {
|
|
|
2320
2900
|
process.exit(0);
|
|
2321
2901
|
}
|
|
2322
2902
|
} else {
|
|
2323
|
-
console.log(
|
|
2903
|
+
console.log(
|
|
2904
|
+
chalk10.dim(
|
|
2905
|
+
`Repository: ${repoInfo.github?.owner}/${repoInfo.github?.repo}`
|
|
2906
|
+
)
|
|
2907
|
+
);
|
|
2324
2908
|
}
|
|
2325
2909
|
if (hasCIWorkflow(cwd)) {
|
|
2326
2910
|
const shouldOverwrite = await p6.confirm({
|
|
@@ -2354,7 +2938,13 @@ async function setupAction(cwd) {
|
|
|
2354
2938
|
console.log(chalk10.dim(" \u2022 Push to main/develop branches"));
|
|
2355
2939
|
console.log(chalk10.dim(" \u2022 Pull requests to main/develop branches"));
|
|
2356
2940
|
console.log(chalk10.dim("\nChecks included:"));
|
|
2357
|
-
const checks = ciConfig?.checks || [
|
|
2941
|
+
const checks = ciConfig?.checks || [
|
|
2942
|
+
"lint",
|
|
2943
|
+
"typecheck",
|
|
2944
|
+
"format",
|
|
2945
|
+
"build",
|
|
2946
|
+
"test"
|
|
2947
|
+
];
|
|
2358
2948
|
for (const check of checks) {
|
|
2359
2949
|
const hasScript = check === "lint" ? projectInfo.hasLintScript : check === "typecheck" ? projectInfo.hasTypecheckScript : check === "format" ? projectInfo.hasFormatScript : check === "test" ? projectInfo.hasTestScript : check === "build" ? projectInfo.hasBuildScript : false;
|
|
2360
2950
|
const status = hasScript ? chalk10.green("\u2713") : chalk10.yellow("\u26A0");
|
|
@@ -2363,7 +2953,9 @@ async function setupAction(cwd) {
|
|
|
2363
2953
|
}
|
|
2364
2954
|
console.log(chalk10.cyan("\n\u{1F4A1} Recommended: Enable branch protection"));
|
|
2365
2955
|
console.log(chalk10.dim(" Go to GitHub \u2192 Settings \u2192 Branches \u2192 Add rule"));
|
|
2366
|
-
console.log(
|
|
2956
|
+
console.log(
|
|
2957
|
+
chalk10.dim(' Enable "Require status checks to pass before merging"')
|
|
2958
|
+
);
|
|
2367
2959
|
console.log(chalk10.dim(' Select the "ci" status check'));
|
|
2368
2960
|
} else {
|
|
2369
2961
|
spinner5.stop(chalk10.red("\u2717 Failed to create CI workflow"));
|
|
@@ -2415,17 +3007,37 @@ async function checkAction(cwd) {
|
|
|
2415
3007
|
}
|
|
2416
3008
|
|
|
2417
3009
|
// src/cli/index.ts
|
|
2418
|
-
var program = new
|
|
2419
|
-
program.name("workflow").description(
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
program.command("
|
|
2423
|
-
|
|
3010
|
+
var program = new Command2();
|
|
3011
|
+
program.name("workflow").description(
|
|
3012
|
+
"A self-evolving workflow management system for AI agent development"
|
|
3013
|
+
).version("1.0.0");
|
|
3014
|
+
program.command("init").description("Initialize workflow in current project").option("--migrate", "Auto-detect existing patterns and migrate").option("--workspace", "Initialize for multiple repositories").option(
|
|
3015
|
+
"--preset <preset>",
|
|
3016
|
+
"Preset to use (saas, library, api, ecommerce, cms, custom)"
|
|
3017
|
+
).option("--name <name>", "Project name").option("-y, --yes", "Skip confirmation prompts").action(initCommand);
|
|
3018
|
+
program.command("validate <type>").description("Validate branch name, commit message, or PR title").argument("<type>", "What to validate: branch, commit, or pr").argument(
|
|
3019
|
+
"[value]",
|
|
3020
|
+
"Value to validate (defaults to current branch/HEAD commit)"
|
|
3021
|
+
).option(
|
|
3022
|
+
"--suggest-on-error",
|
|
3023
|
+
"Offer improvement suggestions on validation errors"
|
|
3024
|
+
).action(validateCommand);
|
|
3025
|
+
program.addCommand(createConfigCommand());
|
|
3026
|
+
program.command("suggest").description("Submit an improvement suggestion").argument("<feedback>", "Your improvement suggestion").option("--author <author>", "Your name or username").option(
|
|
3027
|
+
"--category <category>",
|
|
3028
|
+
"Category: feature, bug, documentation, performance, other"
|
|
3029
|
+
).action(suggestCommand);
|
|
2424
3030
|
program.command("setup").description("Add workflow scripts to package.json").action(setupCommand);
|
|
2425
|
-
program.command("doctor").description("Run health check and get optimization suggestions").option(
|
|
3031
|
+
program.command("doctor").description("Run health check and get optimization suggestions").option(
|
|
3032
|
+
"--check-guidelines-only",
|
|
3033
|
+
"Only check mandatory guidelines exist (exits 0 or 1)"
|
|
3034
|
+
).action(doctorCommand);
|
|
2426
3035
|
program.command("hooks").description("Manage git hooks (install, uninstall, status)").argument("<action>", "Action: install, uninstall, status").action(hooksCommand);
|
|
2427
3036
|
program.command("github").description("Manage GitHub Actions CI (setup, check)").argument("<action>", "Action: setup, check").action(githubCommand);
|
|
2428
|
-
program.command("scope:create").description("Create a custom scope package").option("--name <name>", 'Package name (e.g., "fintech", "gaming")').option(
|
|
3037
|
+
program.command("scope:create").description("Create a custom scope package").option("--name <name>", 'Package name (e.g., "fintech", "gaming")').option(
|
|
3038
|
+
"--scopes <scopes>",
|
|
3039
|
+
"Comma-separated scopes (format: name:description:emoji:category)"
|
|
3040
|
+
).option("--preset-name <preset>", "Preset display name").option("--output-dir <dir>", "Output directory").option("--no-test", "Skip test file generation").action(scopeCreateCommand);
|
|
2429
3041
|
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);
|
|
2430
3042
|
program.parse();
|
|
2431
3043
|
//# sourceMappingURL=index.js.map
|