lee-spec-kit 0.3.0 → 0.4.1
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/index.js +602 -300
- package/package.json +1 -1
- package/templates/en/fullstack/features/feature-base/tasks.md +2 -0
- package/templates/en/single/features/feature-base/tasks.md +2 -0
- package/templates/ko/fullstack/features/feature-base/tasks.md +2 -0
- package/templates/ko/single/features/feature-base/tasks.md +2 -0
package/dist/index.js
CHANGED
|
@@ -887,6 +887,7 @@ async function runUpdate(options) {
|
|
|
887
887
|
console.log(chalk6.green(`\u2705 \uCD1D ${updatedCount}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!`));
|
|
888
888
|
}
|
|
889
889
|
async function updateFolder(sourceDir, targetDir, force, replacements) {
|
|
890
|
+
const protectedFiles = /* @__PURE__ */ new Set(["custom.md", "constitution.md"]);
|
|
890
891
|
await fs6.ensureDir(targetDir);
|
|
891
892
|
const files = await fs6.readdir(sourceDir);
|
|
892
893
|
let updatedCount = 0;
|
|
@@ -895,7 +896,7 @@ async function updateFolder(sourceDir, targetDir, force, replacements) {
|
|
|
895
896
|
const targetPath = path4.join(targetDir, file);
|
|
896
897
|
const stat = await fs6.stat(sourcePath);
|
|
897
898
|
if (stat.isFile()) {
|
|
898
|
-
if (file
|
|
899
|
+
if (protectedFiles.has(file)) {
|
|
899
900
|
continue;
|
|
900
901
|
}
|
|
901
902
|
let sourceContent = await fs6.readFile(sourcePath, "utf-8");
|
|
@@ -1031,189 +1032,458 @@ async function runConfig(options) {
|
|
|
1031
1032
|
await fs6.writeJson(configPath, configFile, { spaces: 2 });
|
|
1032
1033
|
console.log();
|
|
1033
1034
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1035
|
+
|
|
1036
|
+
// src/utils/context/i18n.ts
|
|
1037
|
+
function formatTemplate(template, vars) {
|
|
1038
|
+
return template.replace(/\{(\w+)\}/g, (_, key) => {
|
|
1039
|
+
const value = vars[key];
|
|
1040
|
+
return value === void 0 ? `{${key}}` : String(value);
|
|
1041
|
+
});
|
|
1036
1042
|
}
|
|
1037
|
-
var
|
|
1038
|
-
{
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1043
|
+
var I18N = {
|
|
1044
|
+
ko: {
|
|
1045
|
+
steps: {
|
|
1046
|
+
featureFolder: "Feature \uD3F4\uB354 \uC0DD\uC131",
|
|
1047
|
+
specWrite: "spec.md \uC791\uC131",
|
|
1048
|
+
specApprove: "spec.md \uC2B9\uC778",
|
|
1049
|
+
planWrite: "plan.md \uC791\uC131",
|
|
1050
|
+
planApprove: "plan.md \uC2B9\uC778",
|
|
1051
|
+
tasksWrite: "tasks.md \uC791\uC131",
|
|
1052
|
+
docsCommitPlanning: "\uBB38\uC11C \uCEE4\uBC0B(\uAE30\uD68D)",
|
|
1053
|
+
issueCreate: "GitHub Issue \uC0DD\uC131",
|
|
1054
|
+
branchCreate: "\uBE0C\uB79C\uCE58 \uC0DD\uC131",
|
|
1055
|
+
tasksExecute: "\uD0DC\uC2A4\uD06C \uC2E4\uD589",
|
|
1056
|
+
prCreate: "PR \uC0DD\uC131",
|
|
1057
|
+
codeReview: "\uCF54\uB4DC \uB9AC\uBDF0",
|
|
1058
|
+
featureDone: "Feature \uC644\uB8CC"
|
|
1059
|
+
},
|
|
1060
|
+
messages: {
|
|
1061
|
+
specCreate: "spec.md \uD15C\uD50C\uB9BF\uC744 \uBCF5\uC0AC\uD574 \uC791\uC131\uD558\uC138\uC694. (features/feature-base/spec.md \uCC38\uACE0)",
|
|
1062
|
+
specImprove: "spec.md\uB97C \uBCF4\uC644\uD558\uACE0 \uC0C1\uD0DC\uB97C Review\uB85C \uBCC0\uACBD\uD558\uC138\uC694.",
|
|
1063
|
+
specApproval: "spec.md \uB0B4\uC6A9\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uACF5\uC720\uD558\uACE0 \uC2B9\uC778(OK)\uC744 \uBC1B\uC73C\uC138\uC694.",
|
|
1064
|
+
planCreate: "plan.md \uD15C\uD50C\uB9BF\uC744 \uBCF5\uC0AC\uD574 \uC791\uC131\uD558\uC138\uC694. (features/feature-base/plan.md \uCC38\uACE0)",
|
|
1065
|
+
planImprove: "plan.md\uB97C \uBCF4\uC644\uD558\uACE0 \uC0C1\uD0DC\uB97C Review\uB85C \uBCC0\uACBD\uD558\uC138\uC694.",
|
|
1066
|
+
planApproval: "plan.md \uB0B4\uC6A9\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uACF5\uC720\uD558\uACE0 \uC2B9\uC778(OK)\uC744 \uBC1B\uC73C\uC138\uC694.",
|
|
1067
|
+
tasksCreate: "tasks.md \uD15C\uD50C\uB9BF\uC744 \uBCF5\uC0AC\uD574 \uD0DC\uC2A4\uD06C\uB97C \uC791\uC131\uD558\uC138\uC694. (features/feature-base/tasks.md \uCC38\uACE0)",
|
|
1068
|
+
tasksNeedAtLeastOne: "tasks.md\uC5D0 \uCD5C\uC18C 1\uAC1C \uC774\uC0C1\uC758 \uD0DC\uC2A4\uD06C\uB97C \uC791\uC131\uD558\uC138\uC694.",
|
|
1069
|
+
docsCommitPlanning: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs({folderName}): \uAE30\uD68D \uBB38\uC11C \uC791\uC131"',
|
|
1070
|
+
issueCreateAndWrite: "GitHub Issue\uB97C \uC0DD\uC131\uD55C \uB4A4, spec.md/tasks.md\uC758 \uC774\uC288 \uBC88\uD638\uB97C \uCC44\uC6B0\uACE0 \uBB38\uC11C \uCEE4\uBC0B\uC744 \uC900\uBE44\uD558\uC138\uC694. (skills/create-issue.md \uCC38\uACE0)",
|
|
1071
|
+
docsCommitIssueUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs({folderName}): \uC774\uC288 #{issueNumber} \uBC18\uC601"',
|
|
1072
|
+
standaloneNeedsProjectRoot: "standalone \uBAA8\uB4DC\uC5D0\uC11C\uB294 projectRoot \uC124\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. (npx lee-spec-kit config --project-root ...)",
|
|
1073
|
+
createBranch: 'cd "{projectGitCwd}" && git checkout -b feat/{issueNumber}-{slug}',
|
|
1074
|
+
tasksAllDoneButNoChecklist: '\uBAA8\uB4E0 \uD0DC\uC2A4\uD06C\uAC00 DONE\uC774\uC9C0\uB9CC \uC644\uB8CC \uC870\uAC74 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uC139\uC158\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. tasks.md\uC758 "\uC644\uB8CC \uC870\uAC74" \uC139\uC158\uC744 \uCD94\uAC00/\uD655\uC778\uD558\uC138\uC694.',
|
|
1075
|
+
tasksAllDoneButChecklist: "\uBAA8\uB4E0 \uD0DC\uC2A4\uD06C\uAC00 DONE\uC774\uC9C0\uB9CC \uC644\uB8CC \uC870\uAC74 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8\uAC00 \uC644\uC804\uD788 \uCCB4\uD06C\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. ({checked}/{total})",
|
|
1076
|
+
finishDoingTask: '\uD604\uC7AC DOING/REVIEW \uC911\uC778 \uD0DC\uC2A4\uD06C\uB97C \uC644\uB8CC\uD558\uC138\uC694: "{title}" ({done}/{total}) (skills/execute-task.md \uCC38\uACE0)',
|
|
1077
|
+
startNextTodoTask: '\uB2E4\uC74C TODO \uD0DC\uC2A4\uD06C\uB97C \uC2DC\uC791\uD558\uC138\uC694: "{title}" ({done}/{total}) (skills/execute-task.md \uCC38\uACE0)',
|
|
1078
|
+
checkTaskStatuses: "\uD0DC\uC2A4\uD06C \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694. ({done}/{total}) (skills/execute-task.md \uCC38\uACE0)",
|
|
1079
|
+
prLegacyAsk: "tasks.md\uC5D0 PR/PR \uC0C1\uD0DC \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uD15C\uD50C\uB9BF\uC744 \uCD5C\uC2E0 \uD3EC\uB9F7\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD560\uAE4C\uC694? (OK \uD544\uC694)",
|
|
1080
|
+
prCreate: "PR\uC744 \uC0DD\uC131\uD558\uACE0 tasks.md\uC5D0 PR \uB9C1\uD06C\uB97C \uAE30\uB85D\uD558\uC138\uC694. (skills/create-pr.md \uCC38\uACE0)",
|
|
1081
|
+
prResolveReview: "\uB9AC\uBDF0 \uCF54\uBA58\uD2B8\uB97C \uD574\uACB0\uD558\uACE0 PR \uC0C1\uD0DC\uB97C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694. (PR \uC0C1\uD0DC: Review \u2192 Approved)",
|
|
1082
|
+
prRequestReview: "\uB9AC\uBDF0\uC5B4\uC5D0\uAC8C \uB9AC\uBDF0\uB97C \uC694\uCCAD\uD558\uACE0 PR \uC0C1\uD0DC\uB97C Review\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.",
|
|
1083
|
+
featureDone: "PR\uC774 Approved\uC774\uACE0 \uBAA8\uB4E0 \uD0DC\uC2A4\uD06C/\uC644\uB8CC \uC870\uAC74\uC774 \uCDA9\uC871\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC774 Feature\uB294 \uC644\uB8CC \uC0C1\uD0DC\uC785\uB2C8\uB2E4.",
|
|
1084
|
+
fallbackRerunContext: "\uC0C1\uD0DC\uB97C \uD310\uBCC4\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBB38\uC11C\uB97C \uD655\uC778\uD55C \uB4A4 \uB2E4\uC2DC context\uB97C \uC2E4\uD589\uD558\uC138\uC694."
|
|
1085
|
+
},
|
|
1086
|
+
warnings: {
|
|
1087
|
+
projectBranchUnavailable: "\uD504\uB85C\uC81D\uD2B8 \uBE0C\uB79C\uCE58\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. (standalone \uBAA8\uB4DC\uC5D0\uC11C\uB294 projectRoot\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.)",
|
|
1088
|
+
docsGitUnavailable: "docs \uB808\uD3EC\uC758 git \uC0C1\uD0DC\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. (\uB808\uD3EC \uC704\uCE58 / git init \uD655\uC778)",
|
|
1089
|
+
legacyTasksPrFields: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. PR \uB2E8\uACC4 \uC804\uC5D0 `PR` \uBC0F `PR \uC0C1\uD0DC` \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694."
|
|
1069
1090
|
}
|
|
1070
1091
|
},
|
|
1071
|
-
{
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1092
|
+
en: {
|
|
1093
|
+
steps: {
|
|
1094
|
+
featureFolder: "Create feature folder",
|
|
1095
|
+
specWrite: "Write spec.md",
|
|
1096
|
+
specApprove: "Approve spec.md",
|
|
1097
|
+
planWrite: "Write plan.md",
|
|
1098
|
+
planApprove: "Approve plan.md",
|
|
1099
|
+
tasksWrite: "Write tasks.md",
|
|
1100
|
+
docsCommitPlanning: "Commit planning docs",
|
|
1101
|
+
issueCreate: "Create GitHub Issue",
|
|
1102
|
+
branchCreate: "Create branch",
|
|
1103
|
+
tasksExecute: "Execute tasks",
|
|
1104
|
+
prCreate: "Create PR",
|
|
1105
|
+
codeReview: "Code review",
|
|
1106
|
+
featureDone: "Feature done"
|
|
1107
|
+
},
|
|
1108
|
+
messages: {
|
|
1109
|
+
specCreate: "Copy the spec.md template and write it. (See features/feature-base/spec.md)",
|
|
1110
|
+
specImprove: "Improve spec.md and set Status to Review.",
|
|
1111
|
+
specApproval: "Share spec.md with the user and get approval (OK).",
|
|
1112
|
+
planCreate: "Copy the plan.md template and write it. (See features/feature-base/plan.md)",
|
|
1113
|
+
planImprove: "Improve plan.md and set Status to Review.",
|
|
1114
|
+
planApproval: "Share plan.md with the user and get approval (OK).",
|
|
1115
|
+
tasksCreate: "Copy the tasks.md template and write tasks. (See features/feature-base/tasks.md)",
|
|
1116
|
+
tasksNeedAtLeastOne: "Add at least one task to tasks.md.",
|
|
1117
|
+
docsCommitPlanning: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs({folderName}): planning docs"',
|
|
1118
|
+
issueCreateAndWrite: "Create a GitHub Issue, then fill in the issue number in spec.md/tasks.md and prepare to commit docs. (See skills/create-issue.md)",
|
|
1119
|
+
docsCommitIssueUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs({folderName}): issue #{issueNumber}"',
|
|
1120
|
+
standaloneNeedsProjectRoot: "In standalone mode, projectRoot is required. (npx lee-spec-kit config --project-root ...)",
|
|
1121
|
+
createBranch: 'cd "{projectGitCwd}" && git checkout -b feat/{issueNumber}-{slug}',
|
|
1122
|
+
tasksAllDoneButNoChecklist: 'All tasks are DONE but no completion checklist section was found. Add/verify the "Completion Criteria" section in tasks.md.',
|
|
1123
|
+
tasksAllDoneButChecklist: "All tasks are DONE but the completion checklist is not fully checked. ({checked}/{total})",
|
|
1124
|
+
finishDoingTask: 'Finish the active DOING/REVIEW task: "{title}" ({done}/{total}) (See skills/execute-task.md)',
|
|
1125
|
+
startNextTodoTask: 'Start the next TODO task: "{title}" ({done}/{total}) (See skills/execute-task.md)',
|
|
1126
|
+
checkTaskStatuses: "Check task statuses. ({done}/{total}) (See skills/execute-task.md)",
|
|
1127
|
+
prLegacyAsk: "Legacy tasks.md format detected (missing PR/PR Status fields). Update to the latest format? (OK required)",
|
|
1128
|
+
prCreate: "Create a PR and record the PR link in tasks.md. (See skills/create-pr.md)",
|
|
1129
|
+
prResolveReview: "Resolve review comments and update PR status. (PR Status: Review \u2192 Approved)",
|
|
1130
|
+
prRequestReview: "Request reviews and update PR status to Review.",
|
|
1131
|
+
featureDone: "PR is Approved and all tasks/completion criteria are satisfied. This feature is done.",
|
|
1132
|
+
fallbackRerunContext: "Unable to determine current state. Verify docs and run context again."
|
|
1133
|
+
},
|
|
1134
|
+
warnings: {
|
|
1135
|
+
projectBranchUnavailable: "Cannot determine project branch. (In standalone mode, projectRoot is required.)",
|
|
1136
|
+
docsGitUnavailable: "Cannot read git status for the docs repo. (Check repo location / git init.)",
|
|
1137
|
+
legacyTasksPrFields: "Legacy tasks.md format detected. Add `PR` and `PR Status` fields before PR steps."
|
|
1083
1138
|
}
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
function tr(lang, category, key, vars = {}) {
|
|
1142
|
+
const template = I18N[lang][category][key] ?? I18N.ko[category][key] ?? `${category}.${key}`;
|
|
1143
|
+
return formatTemplate(template, vars);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// src/utils/context/steps.ts
|
|
1147
|
+
function isCompletionChecklistDone(feature) {
|
|
1148
|
+
return !!feature.completionChecklist && feature.completionChecklist.total > 0 && feature.completionChecklist.checked === feature.completionChecklist.total;
|
|
1149
|
+
}
|
|
1150
|
+
function isPrMetadataConfigured(feature) {
|
|
1151
|
+
return feature.docs.prFieldExists && feature.docs.prStatusFieldExists;
|
|
1152
|
+
}
|
|
1153
|
+
function isFeatureDone(feature) {
|
|
1154
|
+
return feature.docs.tasksExists && feature.tasks.total > 0 && feature.tasks.total === feature.tasks.done && isCompletionChecklistDone(feature) && isPrMetadataConfigured(feature) && !!feature.pr.link && feature.pr.status === "Approved";
|
|
1155
|
+
}
|
|
1156
|
+
function getStepDefinitions(lang) {
|
|
1157
|
+
return [
|
|
1158
|
+
{
|
|
1159
|
+
step: 1,
|
|
1160
|
+
name: tr(lang, "steps", "featureFolder"),
|
|
1161
|
+
checklist: { done: () => true }
|
|
1162
|
+
},
|
|
1163
|
+
{
|
|
1164
|
+
step: 2,
|
|
1165
|
+
name: tr(lang, "steps", "specWrite"),
|
|
1166
|
+
checklist: {
|
|
1167
|
+
done: (f) => f.specStatus === "Review" || f.specStatus === "Approved"
|
|
1168
|
+
},
|
|
1169
|
+
current: {
|
|
1170
|
+
when: (f) => !f.docs.specExists || !f.specStatus || f.specStatus === "Draft",
|
|
1171
|
+
actions: (f) => [
|
|
1172
|
+
{
|
|
1173
|
+
type: "instruction",
|
|
1174
|
+
message: !f.docs.specExists ? tr(lang, "messages", "specCreate") : tr(lang, "messages", "specImprove")
|
|
1175
|
+
}
|
|
1176
|
+
]
|
|
1177
|
+
}
|
|
1178
|
+
},
|
|
1179
|
+
{
|
|
1180
|
+
step: 3,
|
|
1181
|
+
name: tr(lang, "steps", "specApprove"),
|
|
1182
|
+
checklist: { done: (f) => f.specStatus === "Approved" },
|
|
1183
|
+
current: {
|
|
1184
|
+
when: (f) => f.specStatus === "Review",
|
|
1185
|
+
actions: () => [
|
|
1186
|
+
{
|
|
1187
|
+
type: "instruction",
|
|
1188
|
+
requiresUserOk: true,
|
|
1189
|
+
message: tr(lang, "messages", "specApproval")
|
|
1190
|
+
}
|
|
1191
|
+
]
|
|
1192
|
+
}
|
|
1193
|
+
},
|
|
1194
|
+
{
|
|
1195
|
+
step: 4,
|
|
1196
|
+
name: tr(lang, "steps", "planWrite"),
|
|
1197
|
+
checklist: {
|
|
1198
|
+
done: (f) => f.planStatus === "Review" || f.planStatus === "Approved"
|
|
1199
|
+
},
|
|
1200
|
+
current: {
|
|
1201
|
+
when: (f) => f.specStatus === "Approved" && (!f.docs.planExists || !f.planStatus || f.planStatus === "Draft"),
|
|
1202
|
+
actions: (f) => [
|
|
1203
|
+
{
|
|
1204
|
+
type: "instruction",
|
|
1205
|
+
message: !f.docs.planExists ? tr(lang, "messages", "planCreate") : tr(lang, "messages", "planImprove")
|
|
1206
|
+
}
|
|
1207
|
+
]
|
|
1208
|
+
}
|
|
1209
|
+
},
|
|
1210
|
+
{
|
|
1211
|
+
step: 5,
|
|
1212
|
+
name: tr(lang, "steps", "planApprove"),
|
|
1213
|
+
checklist: { done: (f) => f.planStatus === "Approved" },
|
|
1214
|
+
current: {
|
|
1215
|
+
when: (f) => f.planStatus === "Review",
|
|
1216
|
+
actions: () => [
|
|
1217
|
+
{
|
|
1218
|
+
type: "instruction",
|
|
1219
|
+
requiresUserOk: true,
|
|
1220
|
+
message: tr(lang, "messages", "planApproval")
|
|
1221
|
+
}
|
|
1222
|
+
]
|
|
1223
|
+
}
|
|
1224
|
+
},
|
|
1225
|
+
{
|
|
1226
|
+
step: 6,
|
|
1227
|
+
name: tr(lang, "steps", "tasksWrite"),
|
|
1228
|
+
checklist: {
|
|
1229
|
+
done: (f) => f.docs.tasksExists && f.tasks.total > 0 && f.docs.prFieldExists && f.docs.prStatusFieldExists,
|
|
1230
|
+
detail: (f) => f.tasks.total > 0 ? `(${f.tasks.total})` : ""
|
|
1231
|
+
},
|
|
1232
|
+
current: {
|
|
1233
|
+
when: (f) => f.planStatus === "Approved" && (!f.docs.tasksExists || f.tasks.total === 0 || f.docs.tasksExists && f.tasks.total > 0 && (!f.docs.prFieldExists || !f.docs.prStatusFieldExists)),
|
|
1234
|
+
actions: (f) => {
|
|
1235
|
+
if (f.docs.tasksExists && f.tasks.total > 0 && (!f.docs.prFieldExists || !f.docs.prStatusFieldExists)) {
|
|
1236
|
+
return [
|
|
1237
|
+
{
|
|
1238
|
+
type: "instruction",
|
|
1239
|
+
requiresUserOk: true,
|
|
1240
|
+
message: tr(lang, "messages", "prLegacyAsk")
|
|
1241
|
+
}
|
|
1242
|
+
];
|
|
1243
|
+
}
|
|
1244
|
+
if (!f.docs.tasksExists) {
|
|
1245
|
+
return [
|
|
1246
|
+
{
|
|
1247
|
+
type: "instruction",
|
|
1248
|
+
message: tr(lang, "messages", "tasksCreate")
|
|
1249
|
+
}
|
|
1250
|
+
];
|
|
1251
|
+
}
|
|
1093
1252
|
return [
|
|
1094
1253
|
{
|
|
1095
1254
|
type: "instruction",
|
|
1096
|
-
message:
|
|
1255
|
+
message: tr(lang, "messages", "tasksNeedAtLeastOne")
|
|
1097
1256
|
}
|
|
1098
1257
|
];
|
|
1099
1258
|
}
|
|
1100
|
-
|
|
1259
|
+
}
|
|
1260
|
+
},
|
|
1261
|
+
{
|
|
1262
|
+
step: 7,
|
|
1263
|
+
name: tr(lang, "steps", "docsCommitPlanning"),
|
|
1264
|
+
checklist: {
|
|
1265
|
+
done: (f) => f.issueNumber ? true : f.docs.tasksExists && f.tasks.total > 0 && f.specStatus === "Approved" && f.planStatus === "Approved" && !f.git.docsHasUncommittedChanges
|
|
1266
|
+
},
|
|
1267
|
+
current: {
|
|
1268
|
+
when: (f) => f.docs.tasksExists && f.tasks.total > 0 && f.specStatus === "Approved" && f.planStatus === "Approved" && !f.issueNumber && f.git.docsHasUncommittedChanges,
|
|
1269
|
+
actions: (f) => [
|
|
1101
1270
|
{
|
|
1102
1271
|
type: "command",
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1272
|
+
requiresUserOk: true,
|
|
1273
|
+
scope: "docs",
|
|
1274
|
+
cwd: f.git.docsGitCwd,
|
|
1275
|
+
cmd: tr(lang, "messages", "docsCommitPlanning", {
|
|
1276
|
+
docsGitCwd: f.git.docsGitCwd,
|
|
1277
|
+
featurePath: f.docs.featurePathFromDocs,
|
|
1278
|
+
folderName: f.folderName
|
|
1279
|
+
})
|
|
1106
1280
|
}
|
|
1107
|
-
]
|
|
1281
|
+
]
|
|
1108
1282
|
}
|
|
1109
|
-
}
|
|
1110
|
-
},
|
|
1111
|
-
{
|
|
1112
|
-
step: 6,
|
|
1113
|
-
name: "plan.md \uC791\uC131",
|
|
1114
|
-
checklist: { done: (f) => f.planStatus === "Review" || f.planStatus === "Approved" },
|
|
1115
|
-
current: {
|
|
1116
|
-
when: (f) => f.git.onExpectedBranch && (!f.docs.planExists || !f.planStatus || f.planStatus === "Draft"),
|
|
1117
|
-
actions: (f) => [
|
|
1118
|
-
{
|
|
1119
|
-
type: "instruction",
|
|
1120
|
-
message: !f.docs.planExists ? "plan.md\uB97C \uC791\uC131\uD558\uC138\uC694. (\uC0C1\uD0DC: Draft\uBD80\uD130 \uC2DC\uC791)" : "plan.md\uB97C \uC791\uC131/\uBCF4\uC644\uD558\uACE0, Status\uB97C Review\uB85C \uBCC0\uACBD\uD55C \uB4A4 \uC0AC\uC6A9\uC790 \uB9AC\uBDF0\uB97C \uC694\uCCAD\uD558\uC138\uC694."
|
|
1121
|
-
}
|
|
1122
|
-
]
|
|
1123
|
-
}
|
|
1124
|
-
},
|
|
1125
|
-
{
|
|
1126
|
-
step: 7,
|
|
1127
|
-
name: "plan.md \uC2B9\uC778",
|
|
1128
|
-
checklist: { done: (f) => f.planStatus === "Approved" },
|
|
1129
|
-
current: {
|
|
1130
|
-
when: (f) => f.planStatus === "Review",
|
|
1131
|
-
actions: () => [
|
|
1132
|
-
{
|
|
1133
|
-
type: "instruction",
|
|
1134
|
-
message: "\uC0AC\uC6A9\uC790\uC5D0\uAC8C plan.md\uB97C \uACF5\uC720\uD558\uACE0 \uBA85\uC2DC\uC801 \uC2B9\uC778(Approved)\uC744 \uBC1B\uC73C\uC138\uC694."
|
|
1135
|
-
}
|
|
1136
|
-
]
|
|
1137
|
-
}
|
|
1138
|
-
},
|
|
1139
|
-
{
|
|
1140
|
-
step: 8,
|
|
1141
|
-
name: "tasks.md \uC791\uC131/\uC2E4\uD589",
|
|
1142
|
-
checklist: {
|
|
1143
|
-
done: (f) => f.docs.tasksExists && f.tasks.total > 0 && f.tasks.total === f.tasks.done,
|
|
1144
|
-
detail: (f) => f.tasks.total > 0 ? `(${f.tasks.done}/${f.tasks.total})` : ""
|
|
1145
1283
|
},
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1284
|
+
{
|
|
1285
|
+
step: 8,
|
|
1286
|
+
name: tr(lang, "steps", "issueCreate"),
|
|
1287
|
+
checklist: {
|
|
1288
|
+
done: (f) => !!f.issueNumber && !f.git.docsHasUncommittedChanges
|
|
1289
|
+
},
|
|
1290
|
+
current: {
|
|
1291
|
+
when: (f) => f.docs.tasksExists && f.tasks.total > 0 && f.specStatus === "Approved" && f.planStatus === "Approved" && (!f.issueNumber || f.git.docsHasUncommittedChanges),
|
|
1292
|
+
actions: (f) => {
|
|
1293
|
+
if (!f.issueNumber) {
|
|
1294
|
+
return [
|
|
1295
|
+
{
|
|
1296
|
+
type: "instruction",
|
|
1297
|
+
requiresUserOk: true,
|
|
1298
|
+
message: tr(lang, "messages", "issueCreateAndWrite")
|
|
1299
|
+
}
|
|
1300
|
+
];
|
|
1301
|
+
}
|
|
1302
|
+
return [
|
|
1303
|
+
{
|
|
1304
|
+
type: "command",
|
|
1305
|
+
requiresUserOk: true,
|
|
1306
|
+
scope: "docs",
|
|
1307
|
+
cwd: f.git.docsGitCwd,
|
|
1308
|
+
cmd: tr(lang, "messages", "docsCommitIssueUpdate", {
|
|
1309
|
+
docsGitCwd: f.git.docsGitCwd,
|
|
1310
|
+
featurePath: f.docs.featurePathFromDocs,
|
|
1311
|
+
issueNumber: f.issueNumber,
|
|
1312
|
+
folderName: f.folderName
|
|
1313
|
+
})
|
|
1314
|
+
}
|
|
1315
|
+
];
|
|
1161
1316
|
}
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1317
|
+
}
|
|
1318
|
+
},
|
|
1319
|
+
{
|
|
1320
|
+
step: 9,
|
|
1321
|
+
name: tr(lang, "steps", "branchCreate"),
|
|
1322
|
+
checklist: { done: (f) => f.git.onExpectedBranch },
|
|
1323
|
+
current: {
|
|
1324
|
+
when: (f) => !!f.issueNumber && (!f.git.projectBranchAvailable || !f.git.onExpectedBranch),
|
|
1325
|
+
actions: (f) => {
|
|
1326
|
+
if (!f.git.projectBranchAvailable || !f.git.projectGitCwd) {
|
|
1327
|
+
return [
|
|
1328
|
+
{
|
|
1329
|
+
type: "instruction",
|
|
1330
|
+
message: tr(lang, "messages", "standaloneNeedsProjectRoot")
|
|
1331
|
+
}
|
|
1332
|
+
];
|
|
1333
|
+
}
|
|
1334
|
+
return [
|
|
1335
|
+
{
|
|
1336
|
+
type: "command",
|
|
1337
|
+
scope: "project",
|
|
1338
|
+
cwd: f.git.projectGitCwd,
|
|
1339
|
+
cmd: tr(lang, "messages", "createBranch", {
|
|
1340
|
+
projectGitCwd: f.git.projectGitCwd,
|
|
1341
|
+
issueNumber: f.issueNumber,
|
|
1342
|
+
slug: f.slug
|
|
1343
|
+
})
|
|
1344
|
+
}
|
|
1345
|
+
];
|
|
1165
1346
|
}
|
|
1166
|
-
message = `tasks.md\uC758 \uD0DC\uC2A4\uD06C \uC0C1\uD0DC([TODO]/[DOING]/[DONE])\uB97C \uD655\uC778\uD558\uC138\uC694. (\uC9C4\uD589\uB960: ${f.tasks.done}/${f.tasks.total})`;
|
|
1167
|
-
return [{ type: "instruction", message }];
|
|
1168
1347
|
}
|
|
1169
|
-
}
|
|
1170
|
-
},
|
|
1171
|
-
{
|
|
1172
|
-
step: 9,
|
|
1173
|
-
name: "\uBB38\uC11C \uCEE4\uBC0B \uC804 \uD655\uC778",
|
|
1174
|
-
checklist: {
|
|
1175
|
-
done: (f) => isCompletionChecklistDone(f),
|
|
1176
|
-
detail: (f) => f.completionChecklist ? `(${f.completionChecklist.checked}/${f.completionChecklist.total})` : ""
|
|
1177
1348
|
},
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1349
|
+
{
|
|
1350
|
+
step: 10,
|
|
1351
|
+
name: tr(lang, "steps", "tasksExecute"),
|
|
1352
|
+
checklist: {
|
|
1353
|
+
done: (f) => f.docs.tasksExists && f.tasks.total > 0 && f.tasks.total === f.tasks.done && isCompletionChecklistDone(f),
|
|
1354
|
+
detail: (f) => f.tasks.total > 0 ? `(${f.tasks.done}/${f.tasks.total})` : ""
|
|
1355
|
+
},
|
|
1356
|
+
current: {
|
|
1357
|
+
when: (f) => f.git.onExpectedBranch && f.docs.tasksExists && f.tasks.total > 0 && (f.tasks.done < f.tasks.total || !isCompletionChecklistDone(f)),
|
|
1358
|
+
actions: (f) => {
|
|
1359
|
+
if (f.tasks.total === f.tasks.done && !isCompletionChecklistDone(f)) {
|
|
1360
|
+
return [
|
|
1361
|
+
{
|
|
1362
|
+
type: "instruction",
|
|
1363
|
+
requiresUserOk: true,
|
|
1364
|
+
message: !f.completionChecklist ? tr(lang, "messages", "tasksAllDoneButNoChecklist") : tr(lang, "messages", "tasksAllDoneButChecklist", {
|
|
1365
|
+
checked: f.completionChecklist.checked,
|
|
1366
|
+
total: f.completionChecklist.total
|
|
1367
|
+
})
|
|
1368
|
+
}
|
|
1369
|
+
];
|
|
1370
|
+
}
|
|
1371
|
+
if (f.activeTask) {
|
|
1372
|
+
return [
|
|
1373
|
+
{
|
|
1374
|
+
type: "instruction",
|
|
1375
|
+
requiresUserOk: true,
|
|
1376
|
+
message: tr(lang, "messages", "finishDoingTask", {
|
|
1377
|
+
title: f.activeTask.title,
|
|
1378
|
+
done: f.tasks.done,
|
|
1379
|
+
total: f.tasks.total
|
|
1380
|
+
})
|
|
1381
|
+
}
|
|
1382
|
+
];
|
|
1383
|
+
}
|
|
1384
|
+
if (f.nextTodoTask) {
|
|
1385
|
+
return [
|
|
1386
|
+
{
|
|
1387
|
+
type: "instruction",
|
|
1388
|
+
requiresUserOk: true,
|
|
1389
|
+
message: tr(lang, "messages", "startNextTodoTask", {
|
|
1390
|
+
title: f.nextTodoTask.title,
|
|
1391
|
+
done: f.tasks.done,
|
|
1392
|
+
total: f.tasks.total
|
|
1393
|
+
})
|
|
1394
|
+
}
|
|
1395
|
+
];
|
|
1396
|
+
}
|
|
1397
|
+
return [
|
|
1398
|
+
{
|
|
1399
|
+
type: "instruction",
|
|
1400
|
+
requiresUserOk: true,
|
|
1401
|
+
message: tr(lang, "messages", "checkTaskStatuses", {
|
|
1402
|
+
done: f.tasks.done,
|
|
1403
|
+
total: f.tasks.total
|
|
1404
|
+
})
|
|
1405
|
+
}
|
|
1406
|
+
];
|
|
1184
1407
|
}
|
|
1185
|
-
|
|
1186
|
-
}
|
|
1187
|
-
},
|
|
1188
|
-
{
|
|
1189
|
-
step: 10,
|
|
1190
|
-
name: "\uBB38\uC11C \uCEE4\uBC0B",
|
|
1191
|
-
checklist: {
|
|
1192
|
-
done: (f) => isCompletionChecklistDone(f) && !f.git.docsHasUncommittedChanges
|
|
1408
|
+
}
|
|
1193
1409
|
},
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1410
|
+
{
|
|
1411
|
+
step: 11,
|
|
1412
|
+
name: tr(lang, "steps", "prCreate"),
|
|
1413
|
+
checklist: { done: (f) => isPrMetadataConfigured(f) && !!f.pr.link },
|
|
1414
|
+
current: {
|
|
1415
|
+
when: (f) => f.docs.tasksExists && f.tasks.total > 0 && f.tasks.total === f.tasks.done && isCompletionChecklistDone(f) && (!isPrMetadataConfigured(f) || !f.pr.link),
|
|
1416
|
+
actions: (f) => {
|
|
1417
|
+
if (!isPrMetadataConfigured(f)) {
|
|
1418
|
+
return [
|
|
1419
|
+
{
|
|
1420
|
+
type: "instruction",
|
|
1421
|
+
requiresUserOk: true,
|
|
1422
|
+
message: tr(lang, "messages", "prLegacyAsk")
|
|
1423
|
+
}
|
|
1424
|
+
];
|
|
1425
|
+
}
|
|
1426
|
+
return [
|
|
1427
|
+
{
|
|
1428
|
+
type: "instruction",
|
|
1429
|
+
requiresUserOk: true,
|
|
1430
|
+
message: tr(lang, "messages", "prCreate")
|
|
1431
|
+
}
|
|
1432
|
+
];
|
|
1202
1433
|
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1434
|
+
}
|
|
1435
|
+
},
|
|
1436
|
+
{
|
|
1437
|
+
step: 12,
|
|
1438
|
+
name: tr(lang, "steps", "codeReview"),
|
|
1439
|
+
checklist: {
|
|
1440
|
+
done: (f) => isPrMetadataConfigured(f) && f.pr.status === "Approved"
|
|
1441
|
+
},
|
|
1442
|
+
current: {
|
|
1443
|
+
when: (f) => isPrMetadataConfigured(f) && !!f.pr.link && f.pr.status !== "Approved",
|
|
1444
|
+
actions: (f) => {
|
|
1445
|
+
if (f.pr.status === "Review") {
|
|
1446
|
+
return [
|
|
1447
|
+
{
|
|
1448
|
+
type: "instruction",
|
|
1449
|
+
message: tr(lang, "messages", "prResolveReview")
|
|
1450
|
+
}
|
|
1451
|
+
];
|
|
1452
|
+
}
|
|
1453
|
+
return [
|
|
1454
|
+
{
|
|
1455
|
+
type: "instruction",
|
|
1456
|
+
message: tr(lang, "messages", "prRequestReview")
|
|
1457
|
+
}
|
|
1458
|
+
];
|
|
1207
1459
|
}
|
|
1208
|
-
|
|
1460
|
+
}
|
|
1461
|
+
},
|
|
1462
|
+
{
|
|
1463
|
+
step: 13,
|
|
1464
|
+
name: tr(lang, "steps", "featureDone"),
|
|
1465
|
+
checklist: { done: (f) => isFeatureDone(f) },
|
|
1466
|
+
current: {
|
|
1467
|
+
when: (f) => isFeatureDone(f),
|
|
1468
|
+
actions: () => [
|
|
1469
|
+
{
|
|
1470
|
+
type: "instruction",
|
|
1471
|
+
message: tr(lang, "messages", "featureDone")
|
|
1472
|
+
}
|
|
1473
|
+
]
|
|
1474
|
+
}
|
|
1209
1475
|
}
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1476
|
+
];
|
|
1477
|
+
}
|
|
1478
|
+
function getStepsMap(lang) {
|
|
1479
|
+
return Object.fromEntries(getStepDefinitions(lang).map((d) => [d.step, d.name]));
|
|
1480
|
+
}
|
|
1481
|
+
getStepDefinitions("ko");
|
|
1482
|
+
getStepsMap("ko");
|
|
1483
|
+
|
|
1484
|
+
// src/utils/context/progress.ts
|
|
1485
|
+
function resolveFeatureProgress(feature, stepDefinitions, lang) {
|
|
1486
|
+
const ordered = [...stepDefinitions].sort((a, b) => a.step - b.step);
|
|
1217
1487
|
for (const definition of ordered) {
|
|
1218
1488
|
if (!definition.current) continue;
|
|
1219
1489
|
if (definition.current.when(feature)) {
|
|
@@ -1229,9 +1499,12 @@ function resolveFeatureProgress(feature) {
|
|
|
1229
1499
|
return {
|
|
1230
1500
|
currentStep: lastStep?.step ?? 10,
|
|
1231
1501
|
actions: [
|
|
1232
|
-
{
|
|
1502
|
+
{
|
|
1503
|
+
type: "instruction",
|
|
1504
|
+
message: tr(lang, "messages", "fallbackRerunContext")
|
|
1505
|
+
}
|
|
1233
1506
|
],
|
|
1234
|
-
nextAction: "
|
|
1507
|
+
nextAction: tr(lang, "messages", "fallbackRerunContext")
|
|
1235
1508
|
};
|
|
1236
1509
|
}
|
|
1237
1510
|
function getCurrentBranch(cwd) {
|
|
@@ -1245,6 +1518,72 @@ function getCurrentBranch(cwd) {
|
|
|
1245
1518
|
return "";
|
|
1246
1519
|
}
|
|
1247
1520
|
}
|
|
1521
|
+
function getGitStatusPorcelain(cwd, relativePaths) {
|
|
1522
|
+
try {
|
|
1523
|
+
const args = relativePaths.length > 0 ? ` -- ${relativePaths.map((p) => `"${p}"`).join(" ")}` : "";
|
|
1524
|
+
return execSync(`git status --porcelain=v1${args}`, {
|
|
1525
|
+
cwd,
|
|
1526
|
+
encoding: "utf-8",
|
|
1527
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1528
|
+
});
|
|
1529
|
+
} catch {
|
|
1530
|
+
return void 0;
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
function getGitTopLevel(cwd) {
|
|
1534
|
+
try {
|
|
1535
|
+
return execSync("git rev-parse --show-toplevel", {
|
|
1536
|
+
cwd,
|
|
1537
|
+
encoding: "utf-8",
|
|
1538
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1539
|
+
}).trim();
|
|
1540
|
+
} catch {
|
|
1541
|
+
return null;
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
function resolveProjectGitCwd(config, repo) {
|
|
1545
|
+
const docsRepo = config.docsRepo;
|
|
1546
|
+
if (docsRepo !== "standalone") {
|
|
1547
|
+
const topLevel = getGitTopLevel(process.cwd());
|
|
1548
|
+
return { cwd: topLevel || process.cwd() };
|
|
1549
|
+
}
|
|
1550
|
+
if (!config.projectRoot) {
|
|
1551
|
+
return {
|
|
1552
|
+
cwd: null,
|
|
1553
|
+
warning: "standalone \uBAA8\uB4DC\uC785\uB2C8\uB2E4. projectRoot\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC544 \uD504\uB85C\uC81D\uD2B8 \uBE0C\uB79C\uCE58 \uD655\uC778\uC774 \uBD88\uAC00\uB2A5\uD569\uB2C8\uB2E4. (npx lee-spec-kit config --project-root ...)"
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
if (config.projectType === "fullstack") {
|
|
1557
|
+
if (typeof config.projectRoot === "string") {
|
|
1558
|
+
return {
|
|
1559
|
+
cwd: null,
|
|
1560
|
+
warning: 'fullstack standalone \uBAA8\uB4DC\uC778\uB370 projectRoot \uD615\uD0DC\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. (\uC608: { "fe": "...", "be": "..." })'
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
const root = config.projectRoot[repo];
|
|
1564
|
+
if (!root) {
|
|
1565
|
+
return {
|
|
1566
|
+
cwd: null,
|
|
1567
|
+
warning: `projectRoot.${repo}\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4. (npx lee-spec-kit config --project-root ... --repo ${repo})`
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
return { cwd: getGitTopLevel(root) || root };
|
|
1571
|
+
}
|
|
1572
|
+
if (typeof config.projectRoot !== "string") {
|
|
1573
|
+
return {
|
|
1574
|
+
cwd: null,
|
|
1575
|
+
warning: 'single standalone \uBAA8\uB4DC\uC778\uB370 projectRoot \uD615\uD0DC\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. (\uC608: "/path/to/project")'
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
return { cwd: getGitTopLevel(config.projectRoot) || config.projectRoot };
|
|
1579
|
+
}
|
|
1580
|
+
function isExpectedFeatureBranch(branchName, issueNumber, slug, folderName) {
|
|
1581
|
+
if (!branchName || !issueNumber) return false;
|
|
1582
|
+
const match = branchName.match(new RegExp(`^feat\\/${issueNumber}-(.+)$`));
|
|
1583
|
+
if (!match) return false;
|
|
1584
|
+
const rest = match[1];
|
|
1585
|
+
return rest === slug || rest === folderName;
|
|
1586
|
+
}
|
|
1248
1587
|
function escapeRegExp(value) {
|
|
1249
1588
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1250
1589
|
}
|
|
@@ -1279,15 +1618,21 @@ function parseIssueNumber(value) {
|
|
|
1279
1618
|
const match = value.match(/#?(\d+)/);
|
|
1280
1619
|
return match ? match[1] : void 0;
|
|
1281
1620
|
}
|
|
1621
|
+
function parsePrLink(value) {
|
|
1622
|
+
if (!value) return void 0;
|
|
1623
|
+
const trimmed = value.trim();
|
|
1624
|
+
if (!trimmed) return void 0;
|
|
1625
|
+
if (trimmed === "#" || trimmed === "-") return void 0;
|
|
1626
|
+
if (trimmed.includes("{") || trimmed.includes("}")) return void 0;
|
|
1627
|
+
return trimmed;
|
|
1628
|
+
}
|
|
1282
1629
|
function parseTasks(content) {
|
|
1283
1630
|
const summary = { total: 0, todo: 0, doing: 0, done: 0 };
|
|
1284
1631
|
let activeTask;
|
|
1285
1632
|
let nextTodoTask;
|
|
1286
1633
|
const lines = content.split("\n");
|
|
1287
1634
|
for (const line of lines) {
|
|
1288
|
-
const match = line.match(
|
|
1289
|
-
/^\s*-\s*\[([A-Z]+)\]((?:\[[^\]]+\])*)\s*(.+?)\s*$/
|
|
1290
|
-
);
|
|
1635
|
+
const match = line.match(/^\s*-\s*\[([A-Z]+)\]((?:\[[^\]]+\])*)\s*(.+?)\s*$/);
|
|
1291
1636
|
if (!match) continue;
|
|
1292
1637
|
const status = match[1].toUpperCase();
|
|
1293
1638
|
const title = match[3].trim();
|
|
@@ -1322,73 +1667,8 @@ function parseCompletionChecklist(content) {
|
|
|
1322
1667
|
}
|
|
1323
1668
|
return total > 0 ? { total, checked } : void 0;
|
|
1324
1669
|
}
|
|
1325
|
-
function
|
|
1326
|
-
|
|
1327
|
-
const args = relativePaths.length > 0 ? ` -- ${relativePaths.map((p) => `"${p}"`).join(" ")}` : "";
|
|
1328
|
-
return execSync(`git status --porcelain=v1${args}`, {
|
|
1329
|
-
cwd,
|
|
1330
|
-
encoding: "utf-8",
|
|
1331
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1332
|
-
});
|
|
1333
|
-
} catch {
|
|
1334
|
-
return void 0;
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
function getGitTopLevel(cwd) {
|
|
1338
|
-
try {
|
|
1339
|
-
return execSync("git rev-parse --show-toplevel", {
|
|
1340
|
-
cwd,
|
|
1341
|
-
encoding: "utf-8",
|
|
1342
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1343
|
-
}).trim();
|
|
1344
|
-
} catch {
|
|
1345
|
-
return null;
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
function resolveProjectGitCwd(config, repo) {
|
|
1349
|
-
const docsRepo = config.docsRepo;
|
|
1350
|
-
if (docsRepo !== "standalone") {
|
|
1351
|
-
const topLevel = getGitTopLevel(process.cwd());
|
|
1352
|
-
return { cwd: topLevel || process.cwd() };
|
|
1353
|
-
}
|
|
1354
|
-
if (!config.projectRoot) {
|
|
1355
|
-
return {
|
|
1356
|
-
cwd: null,
|
|
1357
|
-
warning: "standalone \uBAA8\uB4DC\uC785\uB2C8\uB2E4. projectRoot\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC544 \uD504\uB85C\uC81D\uD2B8 \uBE0C\uB79C\uCE58 \uD655\uC778\uC774 \uBD88\uAC00\uB2A5\uD569\uB2C8\uB2E4. (npx lee-spec-kit config --project-root ...)"
|
|
1358
|
-
};
|
|
1359
|
-
}
|
|
1360
|
-
if (config.projectType === "fullstack") {
|
|
1361
|
-
if (typeof config.projectRoot === "string") {
|
|
1362
|
-
return {
|
|
1363
|
-
cwd: null,
|
|
1364
|
-
warning: 'fullstack standalone \uBAA8\uB4DC\uC778\uB370 projectRoot \uD615\uD0DC\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. (\uC608: { "fe": "...", "be": "..." })'
|
|
1365
|
-
};
|
|
1366
|
-
}
|
|
1367
|
-
const root = config.projectRoot[repo];
|
|
1368
|
-
if (!root) {
|
|
1369
|
-
return {
|
|
1370
|
-
cwd: null,
|
|
1371
|
-
warning: `projectRoot.${repo}\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4. (npx lee-spec-kit config --project-root ... --repo ${repo})`
|
|
1372
|
-
};
|
|
1373
|
-
}
|
|
1374
|
-
return { cwd: getGitTopLevel(root) || root };
|
|
1375
|
-
}
|
|
1376
|
-
if (typeof config.projectRoot !== "string") {
|
|
1377
|
-
return {
|
|
1378
|
-
cwd: null,
|
|
1379
|
-
warning: 'single standalone \uBAA8\uB4DC\uC778\uB370 projectRoot \uD615\uD0DC\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. (\uC608: "/path/to/project")'
|
|
1380
|
-
};
|
|
1381
|
-
}
|
|
1382
|
-
return { cwd: getGitTopLevel(config.projectRoot) || config.projectRoot };
|
|
1383
|
-
}
|
|
1384
|
-
function isExpectedFeatureBranch(branchName, issueNumber, slug, folderName) {
|
|
1385
|
-
if (!branchName || !issueNumber) return false;
|
|
1386
|
-
const match = branchName.match(new RegExp(`^feat\\/${issueNumber}-(.+)$`));
|
|
1387
|
-
if (!match) return false;
|
|
1388
|
-
const rest = match[1];
|
|
1389
|
-
return rest === slug || rest === folderName;
|
|
1390
|
-
}
|
|
1391
|
-
async function parseFeature(featurePath, type, context) {
|
|
1670
|
+
async function parseFeature(featurePath, type, context, options) {
|
|
1671
|
+
const lang = options.lang;
|
|
1392
1672
|
const folderName = path4.basename(featurePath);
|
|
1393
1673
|
const match = folderName.match(/^(F\d+)-(.+)$/);
|
|
1394
1674
|
const id = match?.[1];
|
|
@@ -1403,11 +1683,7 @@ async function parseFeature(featurePath, type, context) {
|
|
|
1403
1683
|
const content = await fs6.readFile(specPath, "utf-8");
|
|
1404
1684
|
const statusValue = extractFirstSpecValue(content, ["\uC0C1\uD0DC", "Status"]);
|
|
1405
1685
|
specStatus = parseDocStatus(statusValue);
|
|
1406
|
-
const issueValue = extractFirstSpecValue(content, [
|
|
1407
|
-
"\uC774\uC288 \uBC88\uD638",
|
|
1408
|
-
"Issue Number",
|
|
1409
|
-
"Issue"
|
|
1410
|
-
]);
|
|
1686
|
+
const issueValue = extractFirstSpecValue(content, ["\uC774\uC288 \uBC88\uD638", "Issue Number", "Issue"]);
|
|
1411
1687
|
issueNumber = parseIssueNumber(issueValue);
|
|
1412
1688
|
}
|
|
1413
1689
|
let planStatus;
|
|
@@ -1422,6 +1698,10 @@ async function parseFeature(featurePath, type, context) {
|
|
|
1422
1698
|
let activeTask;
|
|
1423
1699
|
let nextTodoTask;
|
|
1424
1700
|
let completionChecklist;
|
|
1701
|
+
let prLink;
|
|
1702
|
+
let prStatus;
|
|
1703
|
+
let prFieldExists = false;
|
|
1704
|
+
let prStatusFieldExists = false;
|
|
1425
1705
|
if (tasksExists) {
|
|
1426
1706
|
const content = await fs6.readFile(tasksPath, "utf-8");
|
|
1427
1707
|
const { summary, activeTask: active, nextTodoTask: nextTodo } = parseTasks(content);
|
|
@@ -1432,12 +1712,20 @@ async function parseFeature(featurePath, type, context) {
|
|
|
1432
1712
|
activeTask = active;
|
|
1433
1713
|
nextTodoTask = nextTodo;
|
|
1434
1714
|
completionChecklist = parseCompletionChecklist(content);
|
|
1715
|
+
if (!issueNumber) {
|
|
1716
|
+
const issueValue = extractFirstSpecValue(content, ["\uC774\uC288 \uBC88\uD638", "Issue Number", "Issue"]);
|
|
1717
|
+
issueNumber = parseIssueNumber(issueValue);
|
|
1718
|
+
}
|
|
1719
|
+
const prValue = extractFirstSpecValue(content, ["PR", "Pull Request"]);
|
|
1720
|
+
prFieldExists = prValue !== void 0;
|
|
1721
|
+
prLink = parsePrLink(prValue);
|
|
1722
|
+
const prStatusValue = extractFirstSpecValue(content, ["PR \uC0C1\uD0DC", "PR Status"]);
|
|
1723
|
+
prStatusFieldExists = prStatusValue !== void 0;
|
|
1724
|
+
prStatus = parseDocStatus(prStatusValue);
|
|
1435
1725
|
}
|
|
1436
1726
|
const warnings = [];
|
|
1437
1727
|
if (context.projectBranchAvailable === false) {
|
|
1438
|
-
warnings.push(
|
|
1439
|
-
"\uD504\uB85C\uC81D\uD2B8 \uBE0C\uB79C\uCE58\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. (standalone \uBAA8\uB4DC\uB77C\uBA74 projectRoot \uC124\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.)"
|
|
1440
|
-
);
|
|
1728
|
+
warnings.push(tr(lang, "warnings", "projectBranchUnavailable"));
|
|
1441
1729
|
}
|
|
1442
1730
|
const onExpectedBranch = isExpectedFeatureBranch(
|
|
1443
1731
|
context.projectBranch,
|
|
@@ -1446,14 +1734,13 @@ async function parseFeature(featurePath, type, context) {
|
|
|
1446
1734
|
folderName
|
|
1447
1735
|
);
|
|
1448
1736
|
const relativeFeaturePathFromDocs = path4.relative(context.docsDir, featurePath);
|
|
1449
|
-
const docsStatus = getGitStatusPorcelain(context.docsGitCwd, [
|
|
1450
|
-
relativeFeaturePathFromDocs
|
|
1451
|
-
]);
|
|
1737
|
+
const docsStatus = getGitStatusPorcelain(context.docsGitCwd, [relativeFeaturePathFromDocs]);
|
|
1452
1738
|
const docsHasUncommittedChanges = docsStatus === void 0 ? true : docsStatus.trim().length > 0;
|
|
1453
1739
|
if (docsStatus === void 0) {
|
|
1454
|
-
warnings.push(
|
|
1455
|
-
|
|
1456
|
-
|
|
1740
|
+
warnings.push(tr(lang, "warnings", "docsGitUnavailable"));
|
|
1741
|
+
}
|
|
1742
|
+
if (tasksExists && (!prFieldExists || !prStatusFieldExists)) {
|
|
1743
|
+
warnings.push(tr(lang, "warnings", "legacyTasksPrFields"));
|
|
1457
1744
|
}
|
|
1458
1745
|
const featureState = {
|
|
1459
1746
|
id,
|
|
@@ -1468,6 +1755,7 @@ async function parseFeature(featurePath, type, context) {
|
|
|
1468
1755
|
activeTask,
|
|
1469
1756
|
nextTodoTask,
|
|
1470
1757
|
completionChecklist,
|
|
1758
|
+
pr: { link: prLink, status: prStatus },
|
|
1471
1759
|
git: {
|
|
1472
1760
|
docsBranch: context.docsBranch,
|
|
1473
1761
|
projectBranch: context.projectBranch,
|
|
@@ -1481,21 +1769,22 @@ async function parseFeature(featurePath, type, context) {
|
|
|
1481
1769
|
featurePathFromDocs: relativeFeaturePathFromDocs,
|
|
1482
1770
|
specExists,
|
|
1483
1771
|
planExists,
|
|
1484
|
-
tasksExists
|
|
1772
|
+
tasksExists,
|
|
1773
|
+
prFieldExists,
|
|
1774
|
+
prStatusFieldExists
|
|
1485
1775
|
}
|
|
1486
1776
|
};
|
|
1487
|
-
const { currentStep, actions, nextAction } = resolveFeatureProgress(
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
warnings
|
|
1494
|
-
};
|
|
1777
|
+
const { currentStep, actions, nextAction } = resolveFeatureProgress(
|
|
1778
|
+
featureState,
|
|
1779
|
+
options.stepDefinitions,
|
|
1780
|
+
lang
|
|
1781
|
+
);
|
|
1782
|
+
return { ...featureState, currentStep, actions, nextAction, warnings };
|
|
1495
1783
|
}
|
|
1496
1784
|
async function scanFeatures(config) {
|
|
1497
1785
|
const features = [];
|
|
1498
1786
|
const warnings = [];
|
|
1787
|
+
const stepDefinitions = getStepDefinitions(config.lang);
|
|
1499
1788
|
const docsBranch = getCurrentBranch(config.docsDir);
|
|
1500
1789
|
const projectBranches = {
|
|
1501
1790
|
single: "",
|
|
@@ -1526,51 +1815,60 @@ async function scanFeatures(config) {
|
|
|
1526
1815
|
for (const dir of featureDirs) {
|
|
1527
1816
|
if ((await fs6.stat(dir)).isDirectory()) {
|
|
1528
1817
|
features.push(
|
|
1529
|
-
await parseFeature(
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1818
|
+
await parseFeature(
|
|
1819
|
+
dir,
|
|
1820
|
+
"single",
|
|
1821
|
+
{
|
|
1822
|
+
projectBranch: projectBranches.single,
|
|
1823
|
+
docsBranch,
|
|
1824
|
+
docsGitCwd: config.docsDir,
|
|
1825
|
+
projectGitCwd: singleProject?.cwd ?? void 0,
|
|
1826
|
+
docsDir: config.docsDir,
|
|
1827
|
+
projectBranchAvailable: Boolean(singleProject?.cwd)
|
|
1828
|
+
},
|
|
1829
|
+
{ lang: config.lang, stepDefinitions }
|
|
1830
|
+
)
|
|
1537
1831
|
);
|
|
1538
1832
|
}
|
|
1539
1833
|
}
|
|
1540
1834
|
} else {
|
|
1541
|
-
const feDirs = await glob("features/fe/*/", {
|
|
1542
|
-
|
|
1543
|
-
absolute: true
|
|
1544
|
-
});
|
|
1545
|
-
const beDirs = await glob("features/be/*/", {
|
|
1546
|
-
cwd: config.docsDir,
|
|
1547
|
-
absolute: true
|
|
1548
|
-
});
|
|
1835
|
+
const feDirs = await glob("features/fe/*/", { cwd: config.docsDir, absolute: true });
|
|
1836
|
+
const beDirs = await glob("features/be/*/", { cwd: config.docsDir, absolute: true });
|
|
1549
1837
|
for (const dir of feDirs) {
|
|
1550
1838
|
if ((await fs6.stat(dir)).isDirectory()) {
|
|
1551
1839
|
features.push(
|
|
1552
|
-
await parseFeature(
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1840
|
+
await parseFeature(
|
|
1841
|
+
dir,
|
|
1842
|
+
"fe",
|
|
1843
|
+
{
|
|
1844
|
+
projectBranch: projectBranches.fe,
|
|
1845
|
+
docsBranch,
|
|
1846
|
+
docsGitCwd: config.docsDir,
|
|
1847
|
+
projectGitCwd: feProject?.cwd ?? void 0,
|
|
1848
|
+
docsDir: config.docsDir,
|
|
1849
|
+
projectBranchAvailable: Boolean(feProject?.cwd)
|
|
1850
|
+
},
|
|
1851
|
+
{ lang: config.lang, stepDefinitions }
|
|
1852
|
+
)
|
|
1560
1853
|
);
|
|
1561
1854
|
}
|
|
1562
1855
|
}
|
|
1563
1856
|
for (const dir of beDirs) {
|
|
1564
1857
|
if ((await fs6.stat(dir)).isDirectory()) {
|
|
1565
1858
|
features.push(
|
|
1566
|
-
await parseFeature(
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1859
|
+
await parseFeature(
|
|
1860
|
+
dir,
|
|
1861
|
+
"be",
|
|
1862
|
+
{
|
|
1863
|
+
projectBranch: projectBranches.be,
|
|
1864
|
+
docsBranch,
|
|
1865
|
+
docsGitCwd: config.docsDir,
|
|
1866
|
+
projectGitCwd: beProject?.cwd ?? void 0,
|
|
1867
|
+
docsDir: config.docsDir,
|
|
1868
|
+
projectBranchAvailable: Boolean(beProject?.cwd)
|
|
1869
|
+
},
|
|
1870
|
+
{ lang: config.lang, stepDefinitions }
|
|
1871
|
+
)
|
|
1574
1872
|
);
|
|
1575
1873
|
}
|
|
1576
1874
|
}
|
|
@@ -1579,9 +1877,7 @@ async function scanFeatures(config) {
|
|
|
1579
1877
|
features,
|
|
1580
1878
|
branches: {
|
|
1581
1879
|
docs: docsBranch,
|
|
1582
|
-
project: {
|
|
1583
|
-
...config.projectType === "single" ? { single: projectBranches.single } : { fe: projectBranches.fe, be: projectBranches.be }
|
|
1584
|
-
}
|
|
1880
|
+
project: config.projectType === "single" ? { single: projectBranches.single } : { fe: projectBranches.fe, be: projectBranches.be }
|
|
1585
1881
|
},
|
|
1586
1882
|
warnings
|
|
1587
1883
|
};
|
|
@@ -1628,9 +1924,12 @@ function detectFromBranch(branchName, features) {
|
|
|
1628
1924
|
async function runContext(featureName, options) {
|
|
1629
1925
|
const cwd = process.cwd();
|
|
1630
1926
|
const config = await getConfig(cwd);
|
|
1927
|
+
const lang = config?.lang ?? "ko";
|
|
1631
1928
|
if (!config) {
|
|
1632
1929
|
throw new Error("\uC124\uC815 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 init\uC744 \uC2E4\uD589\uD574\uC8FC\uC138\uC694.");
|
|
1633
1930
|
}
|
|
1931
|
+
const stepDefinitions = getStepDefinitions(lang);
|
|
1932
|
+
const stepsMap = getStepsMap(lang);
|
|
1634
1933
|
const { features, branches, warnings } = await scanFeatures(config);
|
|
1635
1934
|
let targetFeatures = [];
|
|
1636
1935
|
if (featureName) {
|
|
@@ -1728,7 +2027,7 @@ async function runContext(featureName, options) {
|
|
|
1728
2027
|
);
|
|
1729
2028
|
console.log();
|
|
1730
2029
|
targetFeatures.forEach((f2) => {
|
|
1731
|
-
const stepName2 =
|
|
2030
|
+
const stepName2 = stepsMap[f2.currentStep] || "Unknown";
|
|
1732
2031
|
const typeStr = config.projectType === "fullstack" ? chalk6.cyan(`(${f2.type})`) : "";
|
|
1733
2032
|
console.log(
|
|
1734
2033
|
` \u2022 ${chalk6.bold(f2.folderName)} ${typeStr} - ${chalk6.yellow(stepName2)}`
|
|
@@ -1743,7 +2042,8 @@ async function runContext(featureName, options) {
|
|
|
1743
2042
|
return;
|
|
1744
2043
|
}
|
|
1745
2044
|
const f = targetFeatures[0];
|
|
1746
|
-
const stepName =
|
|
2045
|
+
const stepName = stepsMap[f.currentStep] || "Unknown";
|
|
2046
|
+
const okTag = (requiresUserOk) => requiresUserOk ? chalk6.yellow(lang === "ko" ? "[OK \uD544\uC694] " : "[OK required] ") : "";
|
|
1747
2047
|
console.log(
|
|
1748
2048
|
`\u{1F539} Feature: ${chalk6.bold(f.folderName)} ${config.projectType === "fullstack" ? chalk6.cyan(`(${f.type})`) : ""}`
|
|
1749
2049
|
);
|
|
@@ -1762,12 +2062,12 @@ async function runContext(featureName, options) {
|
|
|
1762
2062
|
console.log(
|
|
1763
2063
|
` \u2022 Active Task: ${chalk6.yellow(`[${f.activeTask.status}]`)} ${f.activeTask.title}`
|
|
1764
2064
|
);
|
|
1765
|
-
} else if (f.nextTodoTask && f.currentStep ===
|
|
2065
|
+
} else if (f.nextTodoTask && f.currentStep === 10) {
|
|
1766
2066
|
console.log(
|
|
1767
2067
|
` \u2022 Next TODO: ${chalk6.gray(`[${f.nextTodoTask.status}]`)} ${f.nextTodoTask.title}`
|
|
1768
2068
|
);
|
|
1769
2069
|
}
|
|
1770
|
-
printChecklist(f);
|
|
2070
|
+
printChecklist(f, stepDefinitions);
|
|
1771
2071
|
if (f.warnings.length > 0) {
|
|
1772
2072
|
console.log();
|
|
1773
2073
|
console.log(chalk6.yellow("\u26A0\uFE0F Feature Warnings:"));
|
|
@@ -1784,10 +2084,12 @@ async function runContext(featureName, options) {
|
|
|
1784
2084
|
const action = f.actions[0];
|
|
1785
2085
|
if (action.type === "command") {
|
|
1786
2086
|
console.log(
|
|
1787
|
-
`\u{1F449} Next Action (${chalk6.cyan(action.scope)}): ${chalk6.green(chalk6.bold(action.cmd))}`
|
|
2087
|
+
`\u{1F449} Next Action (${chalk6.cyan(action.scope)}): ${okTag(action.requiresUserOk)}${chalk6.green(chalk6.bold(action.cmd))}`
|
|
1788
2088
|
);
|
|
1789
2089
|
} else {
|
|
1790
|
-
console.log(
|
|
2090
|
+
console.log(
|
|
2091
|
+
`\u{1F449} Next Action: ${okTag(action.requiresUserOk)}${chalk6.green(chalk6.bold(action.message))}`
|
|
2092
|
+
);
|
|
1791
2093
|
}
|
|
1792
2094
|
console.log();
|
|
1793
2095
|
return;
|
|
@@ -1795,15 +2097,15 @@ async function runContext(featureName, options) {
|
|
|
1795
2097
|
console.log(chalk6.green(chalk6.bold("\u{1F449} Next Actions:")));
|
|
1796
2098
|
f.actions.forEach((action) => {
|
|
1797
2099
|
if (action.type === "command") {
|
|
1798
|
-
console.log(` \u2022 (${action.scope}) ${action.cmd}`);
|
|
2100
|
+
console.log(` \u2022 (${action.scope}) ${okTag(action.requiresUserOk)}${action.cmd}`);
|
|
1799
2101
|
} else {
|
|
1800
|
-
console.log(` \u2022 ${action.message}`);
|
|
2102
|
+
console.log(` \u2022 ${okTag(action.requiresUserOk)}${action.message}`);
|
|
1801
2103
|
}
|
|
1802
2104
|
});
|
|
1803
2105
|
console.log();
|
|
1804
2106
|
}
|
|
1805
|
-
function printChecklist(f) {
|
|
1806
|
-
const checklistSteps = [...
|
|
2107
|
+
function printChecklist(f, stepDefinitions) {
|
|
2108
|
+
const checklistSteps = [...stepDefinitions].sort((a, b) => a.step - b.step);
|
|
1807
2109
|
checklistSteps.forEach((definition) => {
|
|
1808
2110
|
const done = definition.checklist.done(f);
|
|
1809
2111
|
const detail = definition.checklist.detail?.(f) ?? "";
|
package/package.json
CHANGED