lee-spec-kit 0.7.7 → 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
@@ -8738,6 +8738,7 @@ var DEFAULT_MANAGED_DOC_DIRS = [
8738
8738
  "scripts"
8739
8739
  ];
8740
8740
  var DEFAULT_MANAGED_DOC_FILES = [
8741
+ "AGENTS.md",
8741
8742
  "README.md",
8742
8743
  ".lee-spec-kit.json",
8743
8744
  ".gitignore"
@@ -17874,8 +17875,105 @@ function parseTaskLine(line, index = -1) {
17874
17875
  title: match[4]
17875
17876
  };
17876
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
+ }
17877
17963
 
17878
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
+ }
17879
17977
  function buildTaskRunPrompt(input) {
17880
17978
  const shared = [
17881
17979
  "Read `spec.md`, `plan.md`, and `tasks.md` before editing code.",
@@ -17883,8 +17981,8 @@ function buildTaskRunPrompt(input) {
17883
17981
  "Use additional helper agents only when parallel analysis is clearly worth the extra slot cost.",
17884
17982
  "Keep one writer for overlapping files; do not let multiple sub-agents edit the same files concurrently.",
17885
17983
  "If helper-agent quota is exhausted, continue the task in the main agent instead of blocking progress.",
17886
- "Update the assigned task status and verification notes in `tasks.md` before leaving this task.",
17887
- "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."
17888
17986
  ];
17889
17987
  if (input.lang === "ko") {
17890
17988
  return [
@@ -17972,6 +18070,7 @@ async function runTaskRun(featureName, options) {
17972
18070
  `Task "${requestedTaskId}" is already DONE.`
17973
18071
  );
17974
18072
  }
18073
+ ensureTaskDetailsReady(lines, resolvedTask);
17975
18074
  const mode = resolvedTask.status === "TODO" ? "start" : "continue";
17976
18075
  let tasksUpdated = false;
17977
18076
  if (resolvedTask.status === "TODO") {
@@ -18116,6 +18215,13 @@ async function runTaskComplete(featureName, options) {
18116
18215
  `Task "${requestedTaskId}" must be DOING/REVIEW before marking it DONE.`
18117
18216
  );
18118
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
+ }
18119
18225
  lines[resolvedTask.index] = setTaskStatus2(resolvedTask, "DONE");
18120
18226
  await fs.writeFile(tasksPath, lines.join("\n"), "utf-8");
18121
18227
  const payload = {
@@ -18174,6 +18280,21 @@ function taskCompleteCommand(program2) {
18174
18280
  }
18175
18281
  );
18176
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
+ }
18177
18298
  function escapeRegExp8(value) {
18178
18299
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18179
18300
  }
@@ -18239,9 +18360,9 @@ function formatTaskBlock(input) {
18239
18360
  `- [TODO][${input.ref}] ${input.taskId} ${input.title}`,
18240
18361
  ` - Date: ${input.recordedAt}`,
18241
18362
  " - Acceptance:",
18242
- " - -",
18363
+ ...input.acceptanceItems.length > 0 ? input.acceptanceItems.map((item) => ` - ${item}`) : [" - -"],
18243
18364
  " - Checklist:",
18244
- " - [ ] -"
18365
+ ...input.checklistItems.length > 0 ? input.checklistItems.map((item) => ` - [ ] ${item}`) : [" - [ ] -"]
18245
18366
  ];
18246
18367
  }
18247
18368
  async function resolveTaskFeature(featureName, component) {
@@ -18272,6 +18393,11 @@ async function runTaskAdd(featureName, options) {
18272
18393
  if (!title) {
18273
18394
  throw createCliError("INVALID_ARGUMENT", "`--title` must not be empty.");
18274
18395
  }
18396
+ const acceptanceItems = normalizeTaskDetailItems(
18397
+ options.acceptance,
18398
+ "--acceptance"
18399
+ );
18400
+ const checklistItems = normalizeTaskDetailItems(options.check, "--check");
18275
18401
  const ref = normalizeTaskRef(options.ref);
18276
18402
  if (isPrdRequirementId(ref)) {
18277
18403
  const { definitions } = await scanPrdRequirements(ctx.fs, ctx.config.docsDir);
@@ -18311,7 +18437,14 @@ async function runTaskAdd(featureName, options) {
18311
18437
  nextSectionHeadingIndex
18312
18438
  );
18313
18439
  const recordedAt = getLocalDateString();
18314
- const blockLines = formatTaskBlock({ ref, taskId, title, recordedAt });
18440
+ const blockLines = formatTaskBlock({
18441
+ ref,
18442
+ taskId,
18443
+ title,
18444
+ recordedAt,
18445
+ acceptanceItems,
18446
+ checklistItems
18447
+ });
18315
18448
  const shouldPrefixBlank = insertIndex > taskListHeadingIndex + 1 && (lines[insertIndex - 1] || "").trim() !== "";
18316
18449
  const shouldSuffixBlank = insertIndex < lines.length && (lines[insertIndex] || "").trim() !== "";
18317
18450
  const insertLines = [
@@ -18342,7 +18475,17 @@ async function runTaskAdd(featureName, options) {
18342
18475
  }
18343
18476
  function taskCommand(program2) {
18344
18477
  const task = program2.command("task").description("Manage tasks");
18345
- 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) => {
18346
18489
  try {
18347
18490
  await runTaskAdd(featureName, options);
18348
18491
  } catch (error) {