lee-spec-kit 0.7.8 → 0.7.9
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 +148 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/en/common/README.md +20 -4
- package/templates/en/common/agents/agents.md +14 -12
- package/templates/en/common/agents/skills/create-issue.md +14 -6
- package/templates/en/common/agents/skills/create-pr.md +15 -6
- package/templates/en/common/agents/skills/execute-task.md +4 -3
- package/templates/en/common/features/feature-base/tasks.md +9 -5
- package/templates/ko/common/README.md +20 -4
- package/templates/ko/common/agents/agents.md +14 -12
- package/templates/ko/common/agents/skills/create-issue.md +14 -6
- package/templates/ko/common/agents/skills/create-pr.md +15 -6
- package/templates/ko/common/agents/skills/execute-task.md +4 -3
- package/templates/ko/common/features/feature-base/tasks.md +9 -5
package/dist/index.js
CHANGED
|
@@ -17875,8 +17875,105 @@ function parseTaskLine(line, index = -1) {
|
|
|
17875
17875
|
title: match[4]
|
|
17876
17876
|
};
|
|
17877
17877
|
}
|
|
17878
|
+
function countLeadingSpaces(line) {
|
|
17879
|
+
const match = line.match(/^(\s*)/);
|
|
17880
|
+
return match?.[1]?.length ?? 0;
|
|
17881
|
+
}
|
|
17882
|
+
function findTaskBlockEnd(lines, taskLineIndex) {
|
|
17883
|
+
let endIndex = lines.length;
|
|
17884
|
+
for (let index = taskLineIndex + 1; index < lines.length; index++) {
|
|
17885
|
+
if (parseTaskLine(lines[index]) || /^\s*##\s+/.test(lines[index])) {
|
|
17886
|
+
endIndex = index;
|
|
17887
|
+
break;
|
|
17888
|
+
}
|
|
17889
|
+
}
|
|
17890
|
+
return endIndex;
|
|
17891
|
+
}
|
|
17892
|
+
function isPlaceholderTaskItem(text) {
|
|
17893
|
+
const normalized = text.trim();
|
|
17894
|
+
return normalized === "" || normalized === "-" || /^todo$/i.test(normalized);
|
|
17895
|
+
}
|
|
17896
|
+
function parseTaskSectionItems(lines, taskLineIndex, headingPattern) {
|
|
17897
|
+
if (taskLineIndex < 0 || taskLineIndex >= lines.length) return void 0;
|
|
17898
|
+
const endIndex = findTaskBlockEnd(lines, taskLineIndex);
|
|
17899
|
+
let sectionHeaderIndex = -1;
|
|
17900
|
+
let sectionHeaderIndent = 0;
|
|
17901
|
+
for (let index = taskLineIndex + 1; index < endIndex; index++) {
|
|
17902
|
+
if (headingPattern.test(lines[index])) {
|
|
17903
|
+
sectionHeaderIndex = index;
|
|
17904
|
+
sectionHeaderIndent = countLeadingSpaces(lines[index]);
|
|
17905
|
+
break;
|
|
17906
|
+
}
|
|
17907
|
+
}
|
|
17908
|
+
if (sectionHeaderIndex === -1) return void 0;
|
|
17909
|
+
const items = [];
|
|
17910
|
+
let placeholderCount = 0;
|
|
17911
|
+
for (let index = sectionHeaderIndex + 1; index < endIndex; index++) {
|
|
17912
|
+
const line = lines[index];
|
|
17913
|
+
if (!line.trim()) continue;
|
|
17914
|
+
const indent = countLeadingSpaces(line);
|
|
17915
|
+
if (indent <= sectionHeaderIndent && /^\s*-\s+/.test(line)) {
|
|
17916
|
+
break;
|
|
17917
|
+
}
|
|
17918
|
+
const match = line.match(/^\s*-\s+(.+?)\s*$/);
|
|
17919
|
+
if (!match) continue;
|
|
17920
|
+
const text = match[1].trim();
|
|
17921
|
+
items.push(text);
|
|
17922
|
+
if (isPlaceholderTaskItem(text)) placeholderCount++;
|
|
17923
|
+
}
|
|
17924
|
+
if (items.length === 0) return void 0;
|
|
17925
|
+
return { items, placeholderCount };
|
|
17926
|
+
}
|
|
17927
|
+
function parseTaskAcceptance(lines, taskLineIndex) {
|
|
17928
|
+
return parseTaskSectionItems(lines, taskLineIndex, /^\s*-\s+Acceptance:\s*$/);
|
|
17929
|
+
}
|
|
17930
|
+
function parseTaskChecklist(lines, taskLineIndex) {
|
|
17931
|
+
if (taskLineIndex < 0 || taskLineIndex >= lines.length) return void 0;
|
|
17932
|
+
const endIndex = findTaskBlockEnd(lines, taskLineIndex);
|
|
17933
|
+
let checklistHeaderIndex = -1;
|
|
17934
|
+
let checklistHeaderIndent = 0;
|
|
17935
|
+
for (let index = taskLineIndex + 1; index < endIndex; index++) {
|
|
17936
|
+
if (/^\s*-\s+Checklist:\s*$/.test(lines[index])) {
|
|
17937
|
+
checklistHeaderIndex = index;
|
|
17938
|
+
checklistHeaderIndent = countLeadingSpaces(lines[index]);
|
|
17939
|
+
break;
|
|
17940
|
+
}
|
|
17941
|
+
}
|
|
17942
|
+
if (checklistHeaderIndex === -1) return void 0;
|
|
17943
|
+
let total = 0;
|
|
17944
|
+
let checked = 0;
|
|
17945
|
+
let placeholderCount = 0;
|
|
17946
|
+
for (let index = checklistHeaderIndex + 1; index < endIndex; index++) {
|
|
17947
|
+
const line = lines[index];
|
|
17948
|
+
if (!line.trim()) continue;
|
|
17949
|
+
const indent = countLeadingSpaces(line);
|
|
17950
|
+
if (indent <= checklistHeaderIndent && /^\s*-\s+/.test(line)) {
|
|
17951
|
+
break;
|
|
17952
|
+
}
|
|
17953
|
+
const match = line.match(/^\s*-\s*\[([ xX])\]\s+/);
|
|
17954
|
+
if (!match) continue;
|
|
17955
|
+
total++;
|
|
17956
|
+
if (match[1].toLowerCase() === "x") checked++;
|
|
17957
|
+
const text = line.replace(/^\s*-\s*\[[ xX]\]\s+/, "").trim();
|
|
17958
|
+
if (isPlaceholderTaskItem(text)) placeholderCount++;
|
|
17959
|
+
}
|
|
17960
|
+
if (total === 0) return void 0;
|
|
17961
|
+
return { total, checked, unchecked: total - checked, placeholderCount };
|
|
17962
|
+
}
|
|
17878
17963
|
|
|
17879
17964
|
// src/commands/task-run.ts
|
|
17965
|
+
function ensureTaskDetailsReady(lines, task) {
|
|
17966
|
+
const acceptance = parseTaskAcceptance(lines, task.index);
|
|
17967
|
+
const checklist = parseTaskChecklist(lines, task.index);
|
|
17968
|
+
const acceptanceHasPlaceholder = !acceptance || acceptance.items.length === 0 || acceptance.placeholderCount > 0;
|
|
17969
|
+
const checklistHasPlaceholder = !checklist || checklist.total === 0 || checklist.placeholderCount > 0;
|
|
17970
|
+
if (acceptanceHasPlaceholder || checklistHasPlaceholder) {
|
|
17971
|
+
throw createCliError(
|
|
17972
|
+
"PRECONDITION_FAILED",
|
|
17973
|
+
`Task "${task.taskId}" still contains Acceptance/Checklist placeholder content. Fill concrete Acceptance items and Checklist items before running task-run.`
|
|
17974
|
+
);
|
|
17975
|
+
}
|
|
17976
|
+
}
|
|
17880
17977
|
function buildTaskRunPrompt(input) {
|
|
17881
17978
|
const shared = [
|
|
17882
17979
|
"Read `spec.md`, `plan.md`, and `tasks.md` before editing code.",
|
|
@@ -17884,8 +17981,8 @@ function buildTaskRunPrompt(input) {
|
|
|
17884
17981
|
"Use additional helper agents only when parallel analysis is clearly worth the extra slot cost.",
|
|
17885
17982
|
"Keep one writer for overlapping files; do not let multiple sub-agents edit the same files concurrently.",
|
|
17886
17983
|
"If helper-agent quota is exhausted, continue the task in the main agent instead of blocking progress.",
|
|
17887
|
-
"Update the assigned task status and verification notes in `tasks.md` before leaving this task.",
|
|
17888
|
-
"Mark the task `DONE` only after code changes and verification are complete."
|
|
17984
|
+
"Update the assigned task status, task-local checklist boxes, and verification notes in `tasks.md` before leaving this task.",
|
|
17985
|
+
"Mark the task `DONE` only after code changes and verification are complete. `task-complete` will reject the transition if checklist items remain unchecked."
|
|
17889
17986
|
];
|
|
17890
17987
|
if (input.lang === "ko") {
|
|
17891
17988
|
return [
|
|
@@ -17973,6 +18070,7 @@ async function runTaskRun(featureName, options) {
|
|
|
17973
18070
|
`Task "${requestedTaskId}" is already DONE.`
|
|
17974
18071
|
);
|
|
17975
18072
|
}
|
|
18073
|
+
ensureTaskDetailsReady(lines, resolvedTask);
|
|
17976
18074
|
const mode = resolvedTask.status === "TODO" ? "start" : "continue";
|
|
17977
18075
|
let tasksUpdated = false;
|
|
17978
18076
|
if (resolvedTask.status === "TODO") {
|
|
@@ -18117,6 +18215,13 @@ async function runTaskComplete(featureName, options) {
|
|
|
18117
18215
|
`Task "${requestedTaskId}" must be DOING/REVIEW before marking it DONE.`
|
|
18118
18216
|
);
|
|
18119
18217
|
}
|
|
18218
|
+
const checklist = parseTaskChecklist(lines, resolvedTask.index);
|
|
18219
|
+
if (checklist && checklist.unchecked > 0) {
|
|
18220
|
+
throw createCliError(
|
|
18221
|
+
"PRECONDITION_FAILED",
|
|
18222
|
+
`Task "${requestedTaskId}" still has unchecked checklist items (${checklist.checked}/${checklist.total}).`
|
|
18223
|
+
);
|
|
18224
|
+
}
|
|
18120
18225
|
lines[resolvedTask.index] = setTaskStatus2(resolvedTask, "DONE");
|
|
18121
18226
|
await fs.writeFile(tasksPath, lines.join("\n"), "utf-8");
|
|
18122
18227
|
const payload = {
|
|
@@ -18175,6 +18280,21 @@ function taskCompleteCommand(program2) {
|
|
|
18175
18280
|
}
|
|
18176
18281
|
);
|
|
18177
18282
|
}
|
|
18283
|
+
function collectRepeatableOption(value, previous = []) {
|
|
18284
|
+
return [...previous, value];
|
|
18285
|
+
}
|
|
18286
|
+
function normalizeTaskDetailItems(values, flagName) {
|
|
18287
|
+
const normalized = (values || []).map((value) => value.trim()).filter(Boolean);
|
|
18288
|
+
for (const value of normalized) {
|
|
18289
|
+
if (value === "-" || /^todo$/i.test(value)) {
|
|
18290
|
+
throw createCliError(
|
|
18291
|
+
"INVALID_ARGUMENT",
|
|
18292
|
+
`${flagName} must contain concrete text, not placeholder values like "-" or "TODO".`
|
|
18293
|
+
);
|
|
18294
|
+
}
|
|
18295
|
+
}
|
|
18296
|
+
return normalized;
|
|
18297
|
+
}
|
|
18178
18298
|
function escapeRegExp8(value) {
|
|
18179
18299
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
18180
18300
|
}
|
|
@@ -18240,9 +18360,9 @@ function formatTaskBlock(input) {
|
|
|
18240
18360
|
`- [TODO][${input.ref}] ${input.taskId} ${input.title}`,
|
|
18241
18361
|
` - Date: ${input.recordedAt}`,
|
|
18242
18362
|
" - Acceptance:",
|
|
18243
|
-
" - -",
|
|
18363
|
+
...input.acceptanceItems.length > 0 ? input.acceptanceItems.map((item) => ` - ${item}`) : [" - -"],
|
|
18244
18364
|
" - Checklist:",
|
|
18245
|
-
" - [ ] -"
|
|
18365
|
+
...input.checklistItems.length > 0 ? input.checklistItems.map((item) => ` - [ ] ${item}`) : [" - [ ] -"]
|
|
18246
18366
|
];
|
|
18247
18367
|
}
|
|
18248
18368
|
async function resolveTaskFeature(featureName, component) {
|
|
@@ -18273,6 +18393,11 @@ async function runTaskAdd(featureName, options) {
|
|
|
18273
18393
|
if (!title) {
|
|
18274
18394
|
throw createCliError("INVALID_ARGUMENT", "`--title` must not be empty.");
|
|
18275
18395
|
}
|
|
18396
|
+
const acceptanceItems = normalizeTaskDetailItems(
|
|
18397
|
+
options.acceptance,
|
|
18398
|
+
"--acceptance"
|
|
18399
|
+
);
|
|
18400
|
+
const checklistItems = normalizeTaskDetailItems(options.check, "--check");
|
|
18276
18401
|
const ref = normalizeTaskRef(options.ref);
|
|
18277
18402
|
if (isPrdRequirementId(ref)) {
|
|
18278
18403
|
const { definitions } = await scanPrdRequirements(ctx.fs, ctx.config.docsDir);
|
|
@@ -18312,7 +18437,14 @@ async function runTaskAdd(featureName, options) {
|
|
|
18312
18437
|
nextSectionHeadingIndex
|
|
18313
18438
|
);
|
|
18314
18439
|
const recordedAt = getLocalDateString();
|
|
18315
|
-
const blockLines = formatTaskBlock({
|
|
18440
|
+
const blockLines = formatTaskBlock({
|
|
18441
|
+
ref,
|
|
18442
|
+
taskId,
|
|
18443
|
+
title,
|
|
18444
|
+
recordedAt,
|
|
18445
|
+
acceptanceItems,
|
|
18446
|
+
checklistItems
|
|
18447
|
+
});
|
|
18316
18448
|
const shouldPrefixBlank = insertIndex > taskListHeadingIndex + 1 && (lines[insertIndex - 1] || "").trim() !== "";
|
|
18317
18449
|
const shouldSuffixBlank = insertIndex < lines.length && (lines[insertIndex] || "").trim() !== "";
|
|
18318
18450
|
const insertLines = [
|
|
@@ -18343,7 +18475,17 @@ async function runTaskAdd(featureName, options) {
|
|
|
18343
18475
|
}
|
|
18344
18476
|
function taskCommand(program2) {
|
|
18345
18477
|
const task = program2.command("task").description("Manage tasks");
|
|
18346
|
-
task.command("add [feature-name]").description("Append a new task to the end of Task List").requiredOption("--title <title>", "Task title").requiredOption("--ref <ref>", "Requirement ref: NON-PRD or PRD-FR-001").option(
|
|
18478
|
+
task.command("add [feature-name]").description("Append a new task to the end of Task List").requiredOption("--title <title>", "Task title").requiredOption("--ref <ref>", "Requirement ref: NON-PRD or PRD-FR-001").option(
|
|
18479
|
+
"--acceptance <text>",
|
|
18480
|
+
"Acceptance item. Repeat to add more than one.",
|
|
18481
|
+
collectRepeatableOption,
|
|
18482
|
+
[]
|
|
18483
|
+
).option(
|
|
18484
|
+
"--check <text>",
|
|
18485
|
+
"Checklist item. Repeat to add more than one.",
|
|
18486
|
+
collectRepeatableOption,
|
|
18487
|
+
[]
|
|
18488
|
+
).option("--component <component>", "Component name for multi projects").option("--json", "Output JSON").action(async (featureName, options) => {
|
|
18347
18489
|
try {
|
|
18348
18490
|
await runTaskAdd(featureName, options);
|
|
18349
18491
|
} catch (error) {
|