lee-spec-kit 0.8.3 → 0.8.4

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
@@ -1297,8 +1297,9 @@ Before taking the next workflow step:
1297
1297
  - task commit checkpoints after each completed task
1298
1298
  8. In standalone mode, keep the docs repo on its docs branch and do not create feature branches or worktrees there
1299
1299
  9. In standalone mode, use the project repo through its managed feature worktree under the shared workspace \`.worktrees/\` root instead of checking the feature branch out in the main project repo
1300
- 10. Keep docs and code synchronized; if code changes materially, update the active feature docs in the same turn before stopping
1301
- 11. When docs are synced to code, refresh an explicit marker like \`<!-- lee-spec-kit:workflow-sync 2026-04-16T12:34:56.789Z -->\` in the active feature docs (prefer \`tasks.md\` or \`decisions.md\`) so \`workflow-audit\` can prove the sync happened after the latest code change
1300
+ 10. In standalone mode, do not hand-write \`git worktree add\`; run the exact \`nextAction.command\` from \`workflow-stage\` so the managed workspace path, stale directory cleanup, and \`.env\` / \`.env.*\` copy step stay consistent
1301
+ 11. Keep docs and code synchronized; if code changes materially, update the active feature docs in the same turn before stopping
1302
+ 12. When docs are synced to code, refresh an explicit marker like \`<!-- lee-spec-kit:workflow-sync 2026-04-16T12:34:56.789Z -->\` in the active feature docs (prefer \`tasks.md\` or \`decisions.md\`) so \`workflow-audit\` can prove the sync happened after the latest code change
1302
1303
 
1303
1304
  Approval and remote actions:
1304
1305
 
@@ -1464,10 +1465,33 @@ function resolveManagedWorktreePath(config, projectRoot, branchName) {
1464
1465
  normalizeBranchNameForWorktree(branchName)
1465
1466
  );
1466
1467
  }
1467
- function buildManagedWorktreeEnvLinkCommand(projectRoot, worktreePath) {
1468
- const sourceEnvPath = path8.resolve(projectRoot, ".env");
1469
- const targetEnvPath = path8.resolve(worktreePath, ".env");
1470
- return `if [ -f "${sourceEnvPath}" ] && [ ! -e "${targetEnvPath}" ] && [ ! -L "${targetEnvPath}" ]; then ln -s "${sourceEnvPath}" "${targetEnvPath}"; fi`;
1468
+ var registeredWorktreeCache = /* @__PURE__ */ new Map();
1469
+ function listRegisteredGitWorktrees(projectRoot) {
1470
+ const resolvedProjectRoot = path8.resolve(projectRoot);
1471
+ const cached = registeredWorktreeCache.get(resolvedProjectRoot);
1472
+ if (cached) return cached;
1473
+ const output = runGitCapture(
1474
+ ["worktree", "list", "--porcelain"],
1475
+ resolvedProjectRoot
1476
+ ) || "";
1477
+ const worktrees = /* @__PURE__ */ new Set();
1478
+ for (const line of output.split(/\r?\n/u)) {
1479
+ if (!line.startsWith("worktree ")) continue;
1480
+ const listedPath = line.slice("worktree ".length).trim();
1481
+ if (listedPath) worktrees.add(path8.resolve(listedPath));
1482
+ }
1483
+ registeredWorktreeCache.set(resolvedProjectRoot, worktrees);
1484
+ return worktrees;
1485
+ }
1486
+ function isRegisteredGitWorktree(projectRoot, worktreePath) {
1487
+ const resolvedTarget = path8.resolve(worktreePath);
1488
+ return listRegisteredGitWorktrees(projectRoot).has(resolvedTarget);
1489
+ }
1490
+ function buildManagedWorktreeStaleCleanupCommand(projectRoot, worktreePath) {
1491
+ return `if [ -d "${worktreePath}" ] && ! git -C "${projectRoot}" worktree list --porcelain | grep -Fxq "worktree ${worktreePath}"; then rm -rf "${worktreePath}"; fi`;
1492
+ }
1493
+ function buildManagedWorktreeEnvCopyCommand(projectRoot, worktreePath) {
1494
+ return `sh -c 'source_dir=$1; target_dir=$2; for source_env in "$source_dir"/.env "$source_dir"/.env.*; do [ -e "$source_env" ] || [ -L "$source_env" ] || continue; target_env="$target_dir/$(basename "$source_env")"; if [ ! -e "$target_env" ] && [ ! -L "$target_env" ]; then cp -p "$source_env" "$target_env"; fi; done' sh "${path8.resolve(projectRoot)}" "${path8.resolve(worktreePath)}"`;
1471
1495
  }
1472
1496
 
1473
1497
  // src/utils/init/options.ts
@@ -4023,7 +4047,7 @@ async function resolveExistingManagedWorktreePath(config, projectGitCwd, slug, f
4023
4047
  (candidate) => resolveManagedWorktreePath(config, projectRoot, candidate)
4024
4048
  );
4025
4049
  for (const candidate of candidates) {
4026
- if (await fs.pathExists(candidate)) {
4050
+ if (await fs.pathExists(candidate) && isRegisteredGitWorktree(projectRoot, candidate)) {
4027
4051
  return candidate;
4028
4052
  }
4029
4053
  }
@@ -6744,6 +6768,380 @@ function docsCommand(program2) {
6744
6768
  }
6745
6769
  });
6746
6770
  }
6771
+
6772
+ // src/utils/task-lines.ts
6773
+ function parseTaskLine(line, index = -1) {
6774
+ const match = line.match(
6775
+ /^\s*-\s*\[(TODO|DOING|DONE|REVIEW)\]((?:\[[^\]]+\])*)\s+(T-[A-Za-z0-9-]+)\s+(.+?)\s*$/
6776
+ );
6777
+ if (!match) return null;
6778
+ const tags = [...(match[2] || "").matchAll(/\[([^\]]+)\]/g)].map((entry) => (entry[1] || "").trim()).filter(Boolean);
6779
+ return {
6780
+ index,
6781
+ raw: line,
6782
+ status: match[1],
6783
+ tags,
6784
+ taskId: match[3],
6785
+ title: match[4]
6786
+ };
6787
+ }
6788
+
6789
+ // src/utils/doc-mutation.ts
6790
+ function collectRepeatableOption(value, previous = []) {
6791
+ return [...previous, value];
6792
+ }
6793
+ function normalizeRequiredText(value, label) {
6794
+ const normalized = (value || "").trim();
6795
+ if (!normalized || normalized === "-" || /^todo$/i.test(normalized)) {
6796
+ throw createCliError(
6797
+ "INVALID_ARGUMENT",
6798
+ `${label} must contain concrete text.`
6799
+ );
6800
+ }
6801
+ return normalized;
6802
+ }
6803
+ function normalizeRequiredItems(values, label) {
6804
+ const normalized = (values || []).map((value) => value.trim()).filter(Boolean);
6805
+ if (normalized.length === 0) {
6806
+ throw createCliError(
6807
+ "INVALID_ARGUMENT",
6808
+ `${label} must be provided at least once.`
6809
+ );
6810
+ }
6811
+ for (const value of normalized) {
6812
+ normalizeRequiredText(value, label);
6813
+ }
6814
+ return normalized;
6815
+ }
6816
+ async function resolveFeatureDocTarget(input) {
6817
+ const state = await resolveFeatureSelection(
6818
+ input.cwd,
6819
+ input.selector,
6820
+ input.component
6821
+ );
6822
+ if (state.status !== "selected" || !state.matchedFeature) {
6823
+ throw createCliError(
6824
+ "CONTEXT_SELECTION_REQUIRED",
6825
+ `A single feature is required. Pass <feature-name> explicitly.`
6826
+ );
6827
+ }
6828
+ const targetPath = path8.join(state.matchedFeature.path, input.fileName);
6829
+ if (!await fs.pathExists(targetPath)) {
6830
+ throw createCliError(
6831
+ "PRECONDITION_FAILED",
6832
+ `${input.fileName} not found for feature: ${state.matchedFeature.folderName}`
6833
+ );
6834
+ }
6835
+ return {
6836
+ feature: state.matchedFeature,
6837
+ path: targetPath
6838
+ };
6839
+ }
6840
+ function findSecondLevelHeadingIndex(lines, names) {
6841
+ const alternatives = names.map((name) => name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
6842
+ const pattern = new RegExp(`^\\s*##\\s+(${alternatives.join("|")})\\s*$`);
6843
+ return lines.findIndex((line) => pattern.test(line));
6844
+ }
6845
+ function findNextSecondLevelHeadingIndex(lines, start) {
6846
+ for (let index = start + 1; index < lines.length; index += 1) {
6847
+ if (/^\s*##\s+/.test(lines[index] || "")) return index;
6848
+ }
6849
+ return lines.length;
6850
+ }
6851
+ function normalizeMarkdownEnd(content) {
6852
+ return content.replace(/\s+$/g, "") + "\n";
6853
+ }
6854
+ function localDate() {
6855
+ return getLocalDateString();
6856
+ }
6857
+ function nextTaskSequence(content, featureFolderName) {
6858
+ const escaped = featureFolderName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6859
+ const taskIdPattern = new RegExp(`\\bT-${escaped}-(\\d+)\\b`, "g");
6860
+ let max = 0;
6861
+ for (const match of content.matchAll(taskIdPattern)) {
6862
+ const parsed = Number(match[1] || "0");
6863
+ if (Number.isFinite(parsed) && parsed > max) max = parsed;
6864
+ }
6865
+ return max + 1;
6866
+ }
6867
+ function findTaskInsertIndex(lines, sectionStart, sectionEnd) {
6868
+ let lastTaskIndex = -1;
6869
+ for (let index = sectionStart; index < sectionEnd; index += 1) {
6870
+ if (parseTaskLine(lines[index] || "", index)) lastTaskIndex = index;
6871
+ }
6872
+ if (lastTaskIndex < 0) return sectionEnd;
6873
+ let insertIndex = lastTaskIndex + 1;
6874
+ while (insertIndex < sectionEnd) {
6875
+ const line = lines[insertIndex] || "";
6876
+ if (parseTaskLine(line, insertIndex)) break;
6877
+ if (/^\s{2,}\S/.test(line) || /^\s*$/.test(line)) {
6878
+ insertIndex += 1;
6879
+ continue;
6880
+ }
6881
+ break;
6882
+ }
6883
+ return insertIndex;
6884
+ }
6885
+
6886
+ // src/commands/task.ts
6887
+ function normalizeTaskRef(value) {
6888
+ const ref = normalizeRequiredText(value, "--ref").toUpperCase();
6889
+ if (ref === "NON-PRD") return ref;
6890
+ if (!/^PRD-[A-Z0-9][A-Z0-9-]*$/.test(ref)) {
6891
+ throw createCliError(
6892
+ "INVALID_ARGUMENT",
6893
+ "`--ref` must be `NON-PRD` or a `PRD-*` requirement key."
6894
+ );
6895
+ }
6896
+ return ref;
6897
+ }
6898
+ function formatTaskBlock(input) {
6899
+ return [
6900
+ `- [TODO][${input.ref}] ${input.taskId} ${input.title}`,
6901
+ ` - Date: ${input.date}`,
6902
+ " - Acceptance:",
6903
+ ...input.acceptanceItems.map((item) => ` - ${item}`),
6904
+ " - Checklist:",
6905
+ ...input.checklistItems.map((item) => ` - [ ] ${item}`)
6906
+ ];
6907
+ }
6908
+ async function runTaskAdd(featureName, options) {
6909
+ const target = await resolveFeatureDocTarget({
6910
+ cwd: process.cwd(),
6911
+ selector: featureName,
6912
+ component: options.component,
6913
+ fileName: "tasks.md"
6914
+ });
6915
+ const title = normalizeRequiredText(options.title, "--title");
6916
+ const ref = normalizeTaskRef(options.ref);
6917
+ const acceptanceItems = normalizeRequiredItems(options.acceptance, "--acceptance");
6918
+ const checklistItems = normalizeRequiredItems(options.check, "--check");
6919
+ const content = await fs.readFile(target.path, "utf-8");
6920
+ const lines = content.split("\n");
6921
+ const taskListIndex = findSecondLevelHeadingIndex(lines, ["Task List", "\uD0DC\uC2A4\uD06C \uBAA9\uB85D"]);
6922
+ if (taskListIndex < 0) {
6923
+ throw createCliError(
6924
+ "PRECONDITION_FAILED",
6925
+ "tasks.md is missing a `Task List` section."
6926
+ );
6927
+ }
6928
+ const sectionEnd = findNextSecondLevelHeadingIndex(lines, taskListIndex);
6929
+ const insertIndex = findTaskInsertIndex(lines, taskListIndex + 1, sectionEnd);
6930
+ const taskId = `T-${target.feature.folderName}-${String(
6931
+ nextTaskSequence(content, target.feature.folderName)
6932
+ ).padStart(2, "0")}`;
6933
+ const recordedAt = localDate();
6934
+ const block = formatTaskBlock({
6935
+ ref,
6936
+ taskId,
6937
+ title,
6938
+ date: recordedAt,
6939
+ acceptanceItems,
6940
+ checklistItems
6941
+ });
6942
+ const shouldPrefixBlank = insertIndex > taskListIndex + 1 && (lines[insertIndex - 1] || "").trim() !== "";
6943
+ const shouldSuffixBlank = insertIndex < lines.length && (lines[insertIndex] || "").trim() !== "";
6944
+ lines.splice(
6945
+ insertIndex,
6946
+ 0,
6947
+ ...shouldPrefixBlank ? [""] : [],
6948
+ ...block,
6949
+ ...shouldSuffixBlank ? [""] : []
6950
+ );
6951
+ await fs.writeFile(target.path, normalizeMarkdownEnd(lines.join("\n")), "utf-8");
6952
+ const payload = {
6953
+ status: "ok",
6954
+ reasonCode: "TASK_ADDED",
6955
+ feature: target.feature.folderName,
6956
+ taskId,
6957
+ title,
6958
+ ref,
6959
+ tasksUpdated: true,
6960
+ tasksPath: target.path,
6961
+ recordedAt
6962
+ };
6963
+ if (options.json) {
6964
+ console.log(JSON.stringify(payload, null, 2));
6965
+ return;
6966
+ }
6967
+ console.log(chalk.green(`Added task ${taskId} to ${target.feature.folderName}.`));
6968
+ console.log(chalk.gray(`- tasks.md updated: ${target.path}`));
6969
+ }
6970
+ function taskCommand(program2) {
6971
+ const task = program2.command("task").description("Patch feature task docs");
6972
+ task.command("add [feature-name]").description("Append a docs-only task block to tasks.md").requiredOption("--title <title>", "Task title").requiredOption("--ref <ref>", "Requirement ref: NON-PRD or PRD-* key").option(
6973
+ "--acceptance <text>",
6974
+ "Concrete acceptance item. Repeat to add more than one.",
6975
+ collectRepeatableOption,
6976
+ []
6977
+ ).option(
6978
+ "--check <text>",
6979
+ "Concrete checklist item. Repeat to add more than one.",
6980
+ collectRepeatableOption,
6981
+ []
6982
+ ).option("--component <component>", "Component name for multi projects").option("--json", "Output JSON").action(async (featureName, options) => {
6983
+ try {
6984
+ await runTaskAdd(featureName, options);
6985
+ } catch (error) {
6986
+ const cliError = toCliError(error);
6987
+ const suggestions = getCliErrorSuggestions(cliError.code, DEFAULT_LANG);
6988
+ if (options.json) {
6989
+ console.log(
6990
+ JSON.stringify({
6991
+ status: "error",
6992
+ reasonCode: cliError.code,
6993
+ error: cliError.message,
6994
+ suggestions
6995
+ })
6996
+ );
6997
+ process.exitCode = 1;
6998
+ return;
6999
+ }
7000
+ console.error(chalk.red(`[${cliError.code}] ${cliError.message}`));
7001
+ printCliErrorSuggestions(suggestions, DEFAULT_LANG);
7002
+ process.exitCode = 1;
7003
+ }
7004
+ });
7005
+ }
7006
+ function getNextDecisionSequence(content) {
7007
+ let max = 0;
7008
+ for (const match of content.matchAll(/^##\s+D(\d+):\s+/gm)) {
7009
+ if (/\{(?:Decision Title|결정 제목)\}/.test(match[0])) continue;
7010
+ const parsed = Number(match[1] || "0");
7011
+ if (Number.isFinite(parsed) && parsed > max) max = parsed;
7012
+ }
7013
+ return max + 1;
7014
+ }
7015
+ function findPlaceholderDecisionRange(content) {
7016
+ const match = /^##\s+D(\d+):\s+.*$/m.exec(content);
7017
+ if (!match || match.index === void 0) return null;
7018
+ const afterHeadingIndex = match.index + match[0].length;
7019
+ const nextHeadingMatch = /^##\s+D\d+:\s+/m.exec(content.slice(afterHeadingIndex));
7020
+ const end = nextHeadingMatch ? afterHeadingIndex + nextHeadingMatch.index : content.length;
7021
+ const block = content.slice(match.index, end);
7022
+ const isPlaceholder = /\{(?:Decision Title|결정 제목)\}/.test(match[0]) || /-\s+\*\*Decision\*\*:\s*(Final choice|최종 선택)/.test(block);
7023
+ if (!isPlaceholder) return null;
7024
+ const sequence = Number(match[1] || "1");
7025
+ return {
7026
+ start: match.index,
7027
+ end,
7028
+ sequence: Number.isFinite(sequence) ? sequence : 1
7029
+ };
7030
+ }
7031
+ function formatOptions(options) {
7032
+ return options.length > 0 ? options.join("; ") : "-";
7033
+ }
7034
+ function formatDecisionBlock(input) {
7035
+ return [
7036
+ `## ${input.decisionId}: ${input.title} (${input.date})`,
7037
+ "",
7038
+ `- **Context**: ${input.context}`,
7039
+ `- **Constraints**: ${input.constraints}`,
7040
+ `- **Options**: ${formatOptions(input.options)}`,
7041
+ `- **Decision**: ${input.decision}`,
7042
+ `- **Rationale**: ${input.rationale}`,
7043
+ "- **Trace**:",
7044
+ " - **At DOING start**: Recorded by `decision add` when the decision was created.",
7045
+ " - **Before DONE**: Update this line when the related task is completed.",
7046
+ " - **Post-merge check**: Update this line after merge when applicable.",
7047
+ "- **Evidence**:",
7048
+ ...input.evidence.map((item) => ` - **Test/Log**: ${item}`),
7049
+ `- **Consequences**: ${input.consequence}`
7050
+ ].join("\n");
7051
+ }
7052
+ async function runDecisionAdd(featureName, options) {
7053
+ const target = await resolveFeatureDocTarget({
7054
+ cwd: process.cwd(),
7055
+ selector: featureName,
7056
+ component: options.component,
7057
+ fileName: "decisions.md"
7058
+ });
7059
+ const title = normalizeRequiredText(options.title, "--title");
7060
+ const context = normalizeRequiredText(options.context, "--context");
7061
+ const decision = normalizeRequiredText(options.decision, "--decision");
7062
+ const rationale = normalizeRequiredText(options.rationale, "--rationale");
7063
+ const evidence = normalizeRequiredItems(options.evidence, "--evidence");
7064
+ const constraints = (options.constraints || "").trim() || "-";
7065
+ const consequence = (options.consequence || "").trim() || "-";
7066
+ const optionItems = (options.option || []).map((value) => value.trim()).filter(Boolean);
7067
+ const content = await fs.readFile(target.path, "utf-8");
7068
+ const placeholderRange = findPlaceholderDecisionRange(content);
7069
+ const decisionSequence = placeholderRange?.sequence ?? getNextDecisionSequence(content);
7070
+ const decisionId = `D${String(decisionSequence).padStart(3, "0")}`;
7071
+ const recordedAt = localDate();
7072
+ const block = formatDecisionBlock({
7073
+ decisionId,
7074
+ title,
7075
+ date: recordedAt,
7076
+ context,
7077
+ constraints,
7078
+ options: optionItems,
7079
+ decision,
7080
+ rationale,
7081
+ evidence,
7082
+ consequence
7083
+ });
7084
+ const nextContent = placeholderRange ? normalizeMarkdownEnd(
7085
+ `${content.slice(0, placeholderRange.start)}${block}${content.slice(
7086
+ placeholderRange.end
7087
+ )}`
7088
+ ) : `${normalizeMarkdownEnd(content)}
7089
+ ${block}
7090
+ `;
7091
+ await fs.writeFile(target.path, nextContent, "utf-8");
7092
+ const payload = {
7093
+ status: "ok",
7094
+ reasonCode: "DECISION_ADDED",
7095
+ feature: target.feature.folderName,
7096
+ decisionId,
7097
+ title,
7098
+ decisionsUpdated: true,
7099
+ decisionsPath: target.path,
7100
+ recordedAt
7101
+ };
7102
+ if (options.json) {
7103
+ console.log(JSON.stringify(payload, null, 2));
7104
+ return;
7105
+ }
7106
+ console.log(chalk.green(`Added decision ${decisionId} to ${target.feature.folderName}.`));
7107
+ console.log(chalk.gray(`- decisions.md updated: ${target.path}`));
7108
+ }
7109
+ function decisionCommand(program2) {
7110
+ const decision = program2.command("decision").description("Patch feature decision docs");
7111
+ decision.command("add [feature-name]").description("Append a docs-only ADR block to decisions.md").requiredOption("--title <title>", "Decision title").requiredOption("--context <text>", "Decision context").option("--constraints <text>", "Decision constraints").option(
7112
+ "--option <text>",
7113
+ "Alternative considered. Repeat to add more than one.",
7114
+ collectRepeatableOption,
7115
+ []
7116
+ ).requiredOption("--decision <text>", "Final decision").requiredOption("--rationale <text>", "Decision rationale").option(
7117
+ "--evidence <text>",
7118
+ "Evidence link or test/log note. Repeat to add more than one.",
7119
+ collectRepeatableOption,
7120
+ []
7121
+ ).option("--consequence <text>", "Decision consequence").option("--component <component>", "Component name for multi projects").option("--json", "Output JSON").action(async (featureName, options) => {
7122
+ try {
7123
+ await runDecisionAdd(featureName, options);
7124
+ } catch (error) {
7125
+ const cliError = toCliError(error);
7126
+ const suggestions = getCliErrorSuggestions(cliError.code, DEFAULT_LANG);
7127
+ if (options.json) {
7128
+ console.log(
7129
+ JSON.stringify({
7130
+ status: "error",
7131
+ reasonCode: cliError.code,
7132
+ error: cliError.message,
7133
+ suggestions
7134
+ })
7135
+ );
7136
+ process.exitCode = 1;
7137
+ return;
7138
+ }
7139
+ console.error(chalk.red(`[${cliError.code}] ${cliError.message}`));
7140
+ printCliErrorSuggestions(suggestions, DEFAULT_LANG);
7141
+ process.exitCode = 1;
7142
+ }
7143
+ });
7144
+ }
6747
7145
  function detectCommand(program2) {
6748
7146
  program2.command("detect").description(tr(DEFAULT_LANG, "cli", "detect.cmdDescription")).option("--dir <dir>", tr(DEFAULT_LANG, "cli", "detect.optDir")).option("--json", tr(DEFAULT_LANG, "cli", "detect.optJson")).action(async (options) => {
6749
7147
  try {
@@ -6924,7 +7322,7 @@ function registerCodexHooksIntegration(parent) {
6924
7322
  removeLeeSpecKitCodexHooks,
6925
7323
  resolveCodexHooksRepoRoot,
6926
7324
  upsertLeeSpecKitCodexHooks
6927
- } = await import('./hooks-B5UIIZYN.js');
7325
+ } = await import('./hooks-43P4YKHY.js');
6928
7326
  const repoRoot = config.docsRepo === "standalone" ? resolveConfiguredStandaloneWorkspaceRoot(config) : resolveCodexHooksRepoRoot(process.cwd());
6929
7327
  if (!repoRoot) {
6930
7328
  throw createCliError(
@@ -7385,15 +7783,20 @@ function getExpectedWorktreePath(config, projectGitCwd, branchName) {
7385
7783
  return resolveManagedWorktreePath(config, projectRoot, branchName);
7386
7784
  }
7387
7785
  async function resolveExistingExpectedWorktreePath(config, projectGitCwd, branchName) {
7786
+ const projectRoot = resolveProjectRootFromGitCwd2(projectGitCwd);
7388
7787
  const candidate = getExpectedWorktreePath(config, projectGitCwd, branchName);
7389
- return await fs.pathExists(candidate) ? candidate : null;
7788
+ return await fs.pathExists(candidate) && isRegisteredGitWorktree(projectRoot, candidate) ? candidate : null;
7390
7789
  }
7391
7790
  function buildManagedWorktreeCreateCommand(config, projectGitCwd, branchName) {
7392
7791
  const projectRoot = resolveProjectRootFromGitCwd2(projectGitCwd);
7393
7792
  const worktreePath = getExpectedWorktreePath(config, projectGitCwd, branchName);
7394
7793
  const worktreeParent = path8.dirname(worktreePath);
7395
- const envLinkCommand = buildManagedWorktreeEnvLinkCommand(projectRoot, worktreePath);
7396
- return `mkdir -p "${worktreeParent}" && (git -C "${projectRoot}" worktree add "${worktreePath}" "${branchName}" || git -C "${projectRoot}" worktree add -b "${branchName}" "${worktreePath}") && ${envLinkCommand}`;
7794
+ const staleCleanupCommand = buildManagedWorktreeStaleCleanupCommand(
7795
+ projectRoot,
7796
+ worktreePath
7797
+ );
7798
+ const envCopyCommand = buildManagedWorktreeEnvCopyCommand(projectRoot, worktreePath);
7799
+ return `${staleCleanupCommand} && mkdir -p "${worktreeParent}" && (git -C "${projectRoot}" worktree add "${worktreePath}" "${branchName}" || git -C "${projectRoot}" worktree add -b "${branchName}" "${worktreePath}") && ${envCopyCommand}`;
7397
7800
  }
7398
7801
  function resolveRemotePrMergeMeta(prRef, projectGitCwd) {
7399
7802
  if (!prRef) return null;
@@ -9224,10 +9627,10 @@ function parseStagedPaths(output) {
9224
9627
  staged.set(`path:${normalizeSlashes3(parts[1])}`, `${status}:path`);
9225
9628
  }
9226
9629
  return [...staged.entries()].map(([encodedPath, encodedStatus]) => {
9227
- const [role, path26] = encodedPath.split(":", 2);
9630
+ const [role, path27] = encodedPath.split(":", 2);
9228
9631
  const [status, entryRole] = encodedStatus.split(":", 2);
9229
9632
  return {
9230
- path: path26,
9633
+ path: path27,
9231
9634
  status,
9232
9635
  role: entryRole || role || "path"
9233
9636
  };
@@ -9502,6 +9905,8 @@ function configureRootCommandSurface() {
9502
9905
  ["init", "Docs Schema Commands:"],
9503
9906
  ["idea", "Docs Schema Commands:"],
9504
9907
  ["feature", "Docs Schema Commands:"],
9908
+ ["task", "Docs Schema Commands:"],
9909
+ ["decision", "Docs Schema Commands:"],
9505
9910
  ["docs", "Workflow Policy Commands:"],
9506
9911
  ["detect", "Workflow Policy Commands:"],
9507
9912
  ["github", "Workflow Policy Commands:"],
@@ -9531,6 +9936,8 @@ updateCommand(program);
9531
9936
  configCommand(program);
9532
9937
  githubCommand(program);
9533
9938
  docsCommand(program);
9939
+ taskCommand(program);
9940
+ decisionCommand(program);
9534
9941
  detectCommand(program);
9535
9942
  workflowStageCommand(program);
9536
9943
  integrationsCommand(program);