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 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({ ref, taskId, title, recordedAt });
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("--component <component>", "Component name for multi projects").option("--json", "Output JSON").action(async (featureName, options) => {
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) {