lee-spec-kit 0.7.5 → 0.7.7

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
@@ -188,12 +188,7 @@ var koCli = {
188
188
  "update.langLabel": "\uC5B8\uC5B4",
189
189
  "update.typeLabel": "\uD0C0\uC785",
190
190
  "update.updatingAgents": "\u{1F4C1} agents/ \uD3F4\uB354 \uC5C5\uB370\uC774\uD2B8 \uC911...",
191
- "update.updatingSkills": "\u{1F4C1} agents/skills \uD3F4\uB354 \uC5C5\uB370\uC774\uD2B8 \uC911...",
192
191
  "update.agentsUpdated": "agents/ \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC",
193
- "update.skillsUpdated": "agents/skills \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC",
194
- "update.updatingFeatureBase": "\u{1F4C1} features/feature-base/ \uD3F4\uB354 \uC5C5\uB370\uC774\uD2B8 \uC911...",
195
- "update.engineManagedSkillsBuiltin": "agents/skills\uB294 CLI \uB0B4\uC7A5 \uADDC\uCE59\uC73C\uB85C \uAD00\uB9AC\uB418\uC5B4 docs\uB85C \uB3D9\uAE30\uD654\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
196
- "update.engineManagedFeatureBaseBuiltin": "features/feature-base\uB294 CLI \uB0B4\uC7A5 \uD15C\uD50C\uB9BF\uC73C\uB85C \uAD00\uB9AC\uB418\uC5B4 docs\uB85C \uB3D9\uAE30\uD654\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
197
192
  "update.engineManagedPruned": "docs\uC5D0\uC11C CLI \uAD00\uB9AC \uBB38\uC11C {count}\uAC1C\uB97C \uC815\uB9AC\uD588\uC2B5\uB2C8\uB2E4.",
198
193
  "update.filesUpdated": "{count}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC",
199
194
  "update.updatedTotal": "\uCD1D {count}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!",
@@ -218,6 +213,7 @@ var koCli = {
218
213
  "doctor.issue.tasksDocStatusUnset": "tasks.md\uC758 \uBB38\uC11C \uC0C1\uD0DC(Doc Status)\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. (Draft/Review/Approved \uC911 \uD558\uB098\uB85C \uC124\uC815\uD558\uC138\uC694.)",
219
214
  "doctor.issue.tasksDocStatusMissing": "tasks.md\uC5D0 \uBB38\uC11C \uC0C1\uD0DC(Doc Status) \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. `- **\uBB38\uC11C \uC0C1\uD0DC**: -`\uC640 `\uAC12: Draft | Review | Approved`\uB97C \uCD94\uAC00\uD558\uC138\uC694.",
220
215
  "doctor.issue.tasksPrdTagUnknown": "tasks.md\uC5D0 \uC815\uC758\uB418\uC9C0 \uC54A\uC740 PRD \uD0DC\uADF8\uAC00 \uC788\uC2B5\uB2C8\uB2E4: {ids}{extra}. tasks.md\uC5D0\uC11C PRD-FR-001 \uAC19\uC740 ID\uB97C \uC784\uC758\uB85C \uB9CC\uB4E4\uC9C0 \uB9D0\uACE0, \uBA3C\uC800 docs/prd \uB610\uB294 \uC0C1\uC704 \uC694\uAD6C\uC0AC\uD56D \uBB38\uC11C\uC5D0 ID\uB97C backfill\uD55C \uB4A4 spec.md `PRD Refs`\uC640 tasks \uD0DC\uADF8\uB97C \uD568\uAED8 \uB9DE\uCD94\uC138\uC694.",
216
+ "doctor.issue.unmanagedDocsEntry": "lee-spec-kit \uBB38\uC11C \uD45C\uBA74 \uBC16\uC758 \uBB38\uC11C \uC5D4\uD2B8\uB9AC\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4: {path}. \uCD5C\uC885 SSOT\uB85C \uC4F0\uC9C0 \uB9D0\uACE0 feature-local docs\uB85C \uC815\uADDC\uD654\uD558\uAC70\uB098, \uC758\uB3C4\uB41C \uACBD\uB85C\uB77C\uBA74 `.lee-spec-kit.json`\uC758 `allowedDocsEntries`\uC5D0 \uCD94\uAC00\uD558\uC138\uC694.",
221
217
  "doctor.issue.duplicateFeatureId": "\uC911\uBCF5 Feature ID \uAC10\uC9C0: {id} ({count}\uAC1C)",
222
218
  "doctor.issue.missingFeatureId": "Feature \uD3F4\uB354\uBA85\uC774 F001-... \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4. (ID\uB97C \uCD94\uCD9C\uD560 \uC218 \uC5C6\uC74C)",
223
219
  "init.selectLangPrompt": "\uBB38\uC11C \uC5B8\uC5B4\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
@@ -684,6 +680,7 @@ var koMessages = {
684
680
  featureScopeSplitTwo: "Feature \uBC94\uC704\uAC00 \uD07D\uB2C8\uB2E4. (tasks: {taskCount}, decisions \uC904\uC218: {decisionsLineCount}) \uAD8C\uC7A5 \uADDC\uCE59\uC0C1 40~79 \uD0DC\uC2A4\uD06C\uC774\uBA74\uC11C \uD558\uB4DC \uAE30\uC900 \uBBF8\uB9CC\uC774\uBA74 2\uAC1C \uC774\uC288 \uBD84\uD560\uC774 \uAE30\uBCF8\uC785\uB2C8\uB2E4. `{guideCommand}`\uB97C \uB530\uB77C \uACB0\uD569\uB3C4/\uD30C\uC77C\uACB9\uCE68/\uD14C\uC2A4\uD2B8/\uBC30\uD3EC \uAE30\uC900\uC73C\uB85C \uBD84\uD560\uD558\uC138\uC694. \uAC01 \uC774\uC288\uC5D0\uB294 \uB2E4\uC74C \uD15C\uD50C\uB9BF\uC744 \uAE30\uB85D\uD558\uC138\uC694: \uBAA9\uD45C, \uD3EC\uD568 \uBC94\uC704, \uC81C\uC678 \uBC94\uC704, \uC120\uD589 \uC758\uC874\uC131, PR \uC644\uB8CC \uAE30\uC900.",
685
681
  featureScopeSplitFour: "Feature \uBC94\uC704\uAC00 \uD07D\uB2C8\uB2E4. (tasks: {taskCount}, decisions \uC904\uC218: {decisionsLineCount}) tasks >= {recommendFourTaskThreshold} \uB610\uB294 decisions \uC904\uC218 >= {recommendFourDecisionsThreshold}\uC774\uBA74 4\uAC1C \uC774\uC288 \uBD84\uD560\uC744 \uAC15\uD558\uAC8C \uAD8C\uC7A5\uD569\uB2C8\uB2E4. `{guideCommand}`\uB97C \uB530\uB77C 4\uAC1C\uC758 \uC5F0\uAD00 \uC774\uC288\uB85C \uBD84\uB9AC\uD558\uACE0 \uC758\uC874 \uC21C\uC11C\uB97C \uBA85\uC2DC\uD55C \uB4A4 PR\uC744 \uC21C\uCC28 \uBA38\uC9C0\uD558\uC138\uC694. \uAC01 \uC774\uC288 \uD15C\uD50C\uB9BF: \uBAA9\uD45C, \uD3EC\uD568 \uBC94\uC704, \uC81C\uC678 \uBC94\uC704, \uC120\uD589 \uC758\uC874\uC131, PR \uC644\uB8CC \uAE30\uC900.",
686
682
  userRequestReplan: "\uD604\uC7AC \uB2E8\uACC4\uC640 \uBCC4\uAC1C\uB85C \uC0AC\uC6A9\uC790\uAC00 \uC81C\uC548\uD55C \uC0C8 \uC694\uAD6C\uB97C \uBA3C\uC800 \uBC18\uC601\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uC694\uAD6C\uC0AC\uD56D\uC744 \uC694\uC57D\uD574 tasks.md\uC5D0 \uCD94\uAC00\uD558\uAC70\uB098 \uBCC4\uB3C4 Feature\uB85C \uBD84\uB9AC\uD55C \uB4A4, \uBB38\uC11C \uC0C1\uD0DC\uB97C \uB9DE\uCD94\uACE0 context\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.",
683
+ docsUnmanagedNormalize: "lee-spec-kit \uBB38\uC11C \uD45C\uBA74 \uBC16\uC758 \uBB38\uC11C \uC5D4\uD2B8\uB9AC\uAC00 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4: {paths}. Feature `{featureRef}`\uB97C \uACC4\uC18D \uC9C4\uD589\uD558\uAE30 \uC804\uC5D0 \uC774 \uBB38\uC11C\uB4E4\uC740 reference \uC785\uB825\uC73C\uB85C\uB9CC \uCDE8\uAE09\uD558\uC138\uC694. \uC0AC\uC6A9\uC790 \uBC94\uC704/acceptance\uB294 `spec.md`, \uC544\uD0A4\uD14D\uCC98/\uD30C\uC77C/\uD14C\uC2A4\uD2B8 \uB0B4\uC6A9\uC740 `plan.md`, \uC2E4\uD589 \uC791\uC5C5\uC740 `tasks.md`, \uADFC\uAC70/\uD2B8\uB808\uC774\uB4DC\uC624\uD504\uB294 `decisions.md`\uB85C \uC815\uADDC\uD654\uD55C \uB4A4 `npx lee-spec-kit context {featureRef}`\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.",
687
684
  featureDone: "\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC694\uAD6C\uC0AC\uD56D\uACFC \uB85C\uCEEC \uC815\uB9AC\uAE4C\uC9C0 \uBAA8\uB450 \uB05D\uB0AC\uC2B5\uB2C8\uB2E4. \uC774 Feature\uB294 \uC644\uC804\uD788 \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.",
688
685
  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."
689
686
  };
@@ -695,6 +692,7 @@ var koWarnings = {
695
692
  workflowWorktreeRequired: "`workflow.requireWorktree=true` \uC124\uC815\uC73C\uB85C \uC778\uD574 \uD0DC\uC2A4\uD06C \uC2E4\uD589\uC740 `.worktrees/*` \uACBD\uB85C\uC5D0\uC11C\uB9CC \uD5C8\uC6A9\uB429\uB2C8\uB2E4.",
696
693
  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)",
697
694
  docsPathIgnored: "\uD604\uC7AC Feature \uBB38\uC11C \uACBD\uB85C\uAC00 git ignore \uB300\uC0C1\uC785\uB2C8\uB2E4: {path} (docs \uCEE4\uBC0B \uAC10\uC9C0\uAC00 \uC81C\uD55C\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4.)",
695
+ unmanagedDocsEntries: "lee-spec-kit \uBB38\uC11C \uD45C\uBA74 \uBC16\uC758 \uBB38\uC11C \uC5D4\uD2B8\uB9AC\uAC00 \uC788\uC2B5\uB2C8\uB2E4: {paths}. reference \uC785\uB825\uC73C\uB85C\uB9CC \uCDE8\uAE09\uD558\uACE0, \uACC4\uC18D \uC9C4\uD589\uD558\uAE30 \uC804\uC5D0 feature-local docs\uB85C \uC815\uADDC\uD654\uD558\uC138\uC694.",
698
696
  docsUncommittedChanges: "\uBB38\uC11C \uBCC0\uACBD\uC0AC\uD56D\uC774 \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. (\uCD94\uAC00 \uBB38\uC11C \uCEE4\uBC0B \uD544\uC694) \uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uADDC\uCE59\uC740 git-workflow \uAC00\uC774\uB4DC\uB97C \uAE30\uC900\uC73C\uB85C \uD655\uC778\uD558\uC138\uC694.",
699
697
  projectUncommittedChanges: "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uBCC0\uACBD\uC0AC\uD56D\uC774 \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. (\uCD94\uAC00 \uCF54\uB4DC \uCEE4\uBC0B \uD544\uC694)",
700
698
  featureScopeSplitSuggested: "Feature \uBC94\uC704\uAC00 \uB2E8\uC77C \uC774\uC288\uB85C \uCC98\uB9AC\uD558\uAE30\uC5D0 \uD07D\uB2C8\uB2E4. (tasks: {taskCount}, decisions \uC904\uC218: {decisionsLineCount}; \uBD84\uD560 \uC81C\uC548 \uAE30\uC900: tasks {taskThreshold}\uAC1C \uB610\uB294 decisions {decisionsThreshold}\uC904) \uD604\uC7AC \uAD8C\uC7A5 \uBD84\uD560: {recommendedIssues}\uAC1C \uC774\uC288 (4\uBD84\uD560 \uD558\uB4DC \uAE30\uC900: tasks >= {recommendFourTaskThreshold} \uB610\uB294 decisions \uC904\uC218 >= {recommendFourDecisionsThreshold}).",
@@ -767,12 +765,7 @@ var enCli = {
767
765
  "update.langLabel": "Lang",
768
766
  "update.typeLabel": "Type",
769
767
  "update.updatingAgents": "\u{1F4C1} Updating agents/ folder...",
770
- "update.updatingSkills": "\u{1F4C1} Updating agents/skills folder...",
771
768
  "update.agentsUpdated": "agents/ updated",
772
- "update.skillsUpdated": "agents/skills updated",
773
- "update.updatingFeatureBase": "\u{1F4C1} Updating features/feature-base/ folder...",
774
- "update.engineManagedSkillsBuiltin": "agents/skills is CLI-managed and is not synced into docs.",
775
- "update.engineManagedFeatureBaseBuiltin": "features/feature-base is CLI-managed and is not synced into docs.",
776
769
  "update.engineManagedPruned": "Removed {count} CLI-managed docs entries from this docs tree.",
777
770
  "update.filesUpdated": "{count} files updated",
778
771
  "update.updatedTotal": "Updated {count} files!",
@@ -797,6 +790,7 @@ var enCli = {
797
790
  "doctor.issue.tasksDocStatusUnset": "tasks.md Doc Status is not set. (Set it to Draft, Review, or Approved.)",
798
791
  "doctor.issue.tasksDocStatusMissing": "tasks.md is missing the Doc Status field. Add `- **Doc Status**: -` and `Values: Draft | Review | Approved`.",
799
792
  "doctor.issue.tasksPrdTagUnknown": "tasks.md uses PRD tags with no matching source definition: {ids}{extra}. Do not invent IDs like PRD-FR-001 in tasks.md. Backfill IDs in docs/prd or the upstream requirements doc first, then align spec.md `PRD Refs` and task tags.",
793
+ "doctor.issue.unmanagedDocsEntry": "Unmanaged docs entry detected outside the lee-spec-kit docs surface: {path}. Treat it as reference input only, normalize it into feature-local docs, or allowlist it in `.lee-spec-kit.json` `allowedDocsEntries`.",
800
794
  "doctor.issue.duplicateFeatureId": "Duplicate Feature ID detected: {id} ({count})",
801
795
  "doctor.issue.missingFeatureId": "Feature folder name is not in F001-... format. (Cannot extract ID)",
802
796
  "init.selectLangPrompt": "Select docs language:",
@@ -1263,6 +1257,7 @@ var enMessages = {
1263
1257
  featureScopeSplitTwo: "Feature scope is large (tasks: {taskCount}, decisions lines: {decisionsLineCount}). Recommendation rule: 40~79 tasks and below the hard threshold usually maps to 2 issues. Follow `{guideCommand}` and split by coupling/file-overlap/test/deploy criteria. For each new issue, document this template: Goal, Included Scope, Excluded Scope, Depends On, PR Done Criteria.",
1264
1258
  featureScopeSplitFour: "Feature scope is large (tasks: {taskCount}, decisions lines: {decisionsLineCount}). Hard recommendation is 4 issues when tasks >= {recommendFourTaskThreshold} or decisions lines >= {recommendFourDecisionsThreshold}. Follow `{guideCommand}` and split into 4 linked issues with explicit dependency order and sequential PR merges. Use the per-issue template: Goal, Included Scope, Excluded Scope, Depends On, PR Done Criteria.",
1265
1259
  userRequestReplan: "You can pause this step and handle a newly requested user requirement first. Summarize it, add it to tasks.md or split it into a separate Feature, then align document statuses and rerun context.",
1260
+ docsUnmanagedNormalize: "Unmanaged docs entries were found outside the lee-spec-kit docs surface: {paths}. Before continuing feature `{featureRef}`, treat them as reference inputs only. Normalize user-facing scope and acceptance into `spec.md`, architecture/file/test details into `plan.md`, executable work into `tasks.md`, and rationale/trade-offs into `decisions.md`, then rerun `npx lee-spec-kit context {featureRef}`.",
1266
1261
  featureDone: "All workflow requirements and local cleanup are complete. This feature is fully finished.",
1267
1262
  fallbackRerunContext: "Cannot determine status. Check the docs and run context again."
1268
1263
  };
@@ -1274,6 +1269,7 @@ var enWarnings = {
1274
1269
  workflowWorktreeRequired: "With `workflow.requireWorktree=true`, task execution is allowed only from `.worktrees/*` paths.",
1275
1270
  docsGitUnavailable: "Cannot read git status for the docs repo. (Check repo location / git init.)",
1276
1271
  docsPathIgnored: "Current feature docs path is ignored by git: {path} (docs commit detection may be limited).",
1272
+ unmanagedDocsEntries: "Unmanaged docs entries are present outside the lee-spec-kit docs surface: {paths}. Treat them as reference inputs only and normalize them into feature-local docs before continuing.",
1277
1273
  docsUncommittedChanges: "Docs changes are not committed. (Additional docs commit needed.) Check commit message rules against the git-workflow guide.",
1278
1274
  projectUncommittedChanges: "Project code changes are not committed. (Additional code commit needed.)",
1279
1275
  featureScopeSplitSuggested: "Feature scope may be too large for one issue (tasks: {taskCount}, decisions lines: {decisionsLineCount}; suggest split at {taskThreshold} tasks or {decisionsThreshold} decision lines). Current recommendation: split into {recommendedIssues} issue units (hard 4-way rule: tasks >= {recommendFourTaskThreshold} or decisions lines >= {recommendFourDecisionsThreshold}).",
@@ -2048,7 +2044,7 @@ In approval-waiting state:
2048
2044
  2. End with \`approvalRequest.finalPrompt\` exactly as provided.
2049
2045
  3. Do not paraphrase or omit these lines.
2050
2046
  4. Prefer \`approvalRequest.userFacingLines\` as the source for user-facing approval text.
2051
- 5. Prefer \`matchedFeature.currentSubstateOwner\` plus \`agentOrchestration.subAgentHandoff\` as the delegation SSOT. Treat \`currentActionShouldDelegate\` as a compatibility mirror for older consumers.
2047
+ 5. Prefer \`matchedFeature.currentSubstateOwner\` plus \`agentOrchestration.subAgentHandoff\` as the delegation SSOT.
2052
2048
  6. When \`matchedFeature.currentSubstateOwner="subagent"\` and \`agentOrchestration.subAgentHandoff.required=true\` with \`mode="command"\`, call \`spawn_agent\` first and do not execute the delegated command directly from the main agent. If the delegated command is handoff-only, continue the delegated work immediately and do not re-open the same approval label.
2053
2049
 
2054
2050
  In non-approval state (progress updates, analysis, tool execution logs, unrelated Q&A):
@@ -2103,15 +2099,20 @@ async function upsertLeeSpecKitAgentsMd(filePath, options) {
2103
2099
  }
2104
2100
  var LEE_SPEC_KIT_CODEX_BOOTSTRAP_BEGIN = "# lee-spec-kit:codex-bootstrap:begin";
2105
2101
  var LEE_SPEC_KIT_CODEX_BOOTSTRAP_END = "# lee-spec-kit:codex-bootstrap:end";
2106
- var REQUIRED_FALLBACK = "docs/AGENTS.md";
2102
+ var REQUIRED_FALLBACKS = ["AGENTS.md", "docs/AGENTS.md"];
2107
2103
  var REQUIRED_COMPACT_LINES = [
2104
+ "Preserve any instructions loaded from ./AGENTS.md or ./docs/AGENTS.md in the compacted summary.",
2105
+ "After context compression/reset, read ./AGENTS.md or ./docs/AGENTS.md again before resuming project-specific work."
2106
+ ];
2107
+ var LEGACY_FALLBACKS = ["docs/AGENTS.md"];
2108
+ var LEGACY_COMPACT_LINES = [
2108
2109
  "Preserve any instructions loaded from ./docs/AGENTS.md in the compacted summary.",
2109
2110
  "After context compression/reset, read ./docs/AGENTS.md again before resuming project-specific work."
2110
2111
  ];
2111
2112
  function renderManagedSegment2() {
2112
2113
  return [
2113
2114
  LEE_SPEC_KIT_CODEX_BOOTSTRAP_BEGIN,
2114
- `project_doc_fallback_filenames = ["${REQUIRED_FALLBACK}"]`,
2115
+ `project_doc_fallback_filenames = ["${REQUIRED_FALLBACKS.join('", "')}"]`,
2115
2116
  'compact_prompt = """',
2116
2117
  ...REQUIRED_COMPACT_LINES,
2117
2118
  '"""',
@@ -2132,7 +2133,9 @@ function getCodexConfigPath() {
2132
2133
  return path15.join(getCodexHome(), "config.toml");
2133
2134
  }
2134
2135
  function contentIncludesRequiredBootstrap(content) {
2135
- return content.includes(REQUIRED_FALLBACK) && REQUIRED_COMPACT_LINES.every((line) => content.includes(line));
2136
+ const matchesCurrent = REQUIRED_FALLBACKS.every((value) => content.includes(value)) && REQUIRED_COMPACT_LINES.every((line) => content.includes(line));
2137
+ const matchesLegacy = LEGACY_FALLBACKS.every((value) => content.includes(value)) && LEGACY_COMPACT_LINES.every((line) => content.includes(line));
2138
+ return matchesCurrent || matchesLegacy;
2136
2139
  }
2137
2140
  function hasConflictingTopLevelKey(content, key) {
2138
2141
  const keyPattern = new RegExp(`^\\s*${key}\\s*=`, "m");
@@ -2367,6 +2370,7 @@ async function getConfig(cwd) {
2367
2370
  pushDocs: configFile.pushDocs,
2368
2371
  docsRemote: configFile.docsRemote,
2369
2372
  projectRoot: configFile.projectRoot,
2373
+ allowedDocsEntries: configFile.allowedDocsEntries,
2370
2374
  pr: configFile.pr,
2371
2375
  workflow: configFile.workflow,
2372
2376
  approval: configFile.approval
@@ -3863,6 +3867,7 @@ var ACTION_CATEGORIES = [
3863
3867
  "plan_approve",
3864
3868
  "tasks_write",
3865
3869
  "tasks_approve",
3870
+ "docs_normalize",
3866
3871
  "docs_commit",
3867
3872
  "issue_create",
3868
3873
  "branch_create",
@@ -3882,7 +3887,7 @@ var ACTION_CATEGORIES = [
3882
3887
  "feature_done",
3883
3888
  "fallback"
3884
3889
  ];
3885
- var LEGACY_LONG_RUNNING_DELEGATION_CATEGORIES = [
3890
+ var SUBAGENT_HANDOFF_CATEGORIES = [
3886
3891
  "task_execute",
3887
3892
  "code_review_run",
3888
3893
  "code_review",
@@ -4346,7 +4351,7 @@ function countDoneTransitionsInLatestTasksCommit(ctx, feature) {
4346
4351
  if (!diff.trim()) return 0;
4347
4352
  const removedByTask = /* @__PURE__ */ new Map();
4348
4353
  const addedByTask = /* @__PURE__ */ new Map();
4349
- const parseTaskLine3 = (line) => {
4354
+ const parseTaskLine2 = (line) => {
4350
4355
  const match = line.match(
4351
4356
  /^\s*-\s*\[(TODO|DOING|DONE|REVIEW)\]\s+(.+?)\s*$/i
4352
4357
  );
@@ -4361,7 +4366,7 @@ function countDoneTransitionsInLatestTasksCommit(ctx, feature) {
4361
4366
  for (const line of diff.split("\n")) {
4362
4367
  if (line.startsWith("---") || line.startsWith("+++")) continue;
4363
4368
  if (line.startsWith("-")) {
4364
- const parsed = parseTaskLine3(line.slice(1));
4369
+ const parsed = parseTaskLine2(line.slice(1));
4365
4370
  if (!parsed) continue;
4366
4371
  const existing = removedByTask.get(parsed.key) || /* @__PURE__ */ new Set();
4367
4372
  existing.add(parsed.status);
@@ -4369,7 +4374,7 @@ function countDoneTransitionsInLatestTasksCommit(ctx, feature) {
4369
4374
  continue;
4370
4375
  }
4371
4376
  if (line.startsWith("+")) {
4372
- const parsed = parseTaskLine3(line.slice(1));
4377
+ const parsed = parseTaskLine2(line.slice(1));
4373
4378
  if (!parsed) continue;
4374
4379
  const existing = addedByTask.get(parsed.key) || /* @__PURE__ */ new Set();
4375
4380
  existing.add(parsed.status);
@@ -5663,35 +5668,12 @@ function normalizeApprovalToken(value) {
5663
5668
  return (value ?? "").trim().toLowerCase();
5664
5669
  }
5665
5670
  function applyApprovalPolicy(step, actions, approval, currentSubstatePhase) {
5666
- const taskExecuteCheckPolicy = approval?.taskExecuteCheck === "start_only" ? "start_only" : "both";
5667
- if (!approval) {
5668
- return actions.map((action) => ({
5669
- ...action,
5670
- requiresUserCheck: applyTaskExecutePhaseCheck(
5671
- action,
5672
- Boolean(action.requiresUserCheck),
5673
- taskExecuteCheckPolicy,
5674
- false,
5675
- currentSubstatePhase
5676
- )
5677
- }));
5678
- }
5679
- const mode = approval.mode ?? "builtin";
5680
- if (mode === "builtin") {
5681
- return actions.map((action) => ({
5682
- ...action,
5683
- requiresUserCheck: applyTaskExecutePhaseCheck(
5684
- action,
5685
- Boolean(action.requiresUserCheck),
5686
- taskExecuteCheckPolicy,
5687
- false,
5688
- currentSubstatePhase
5689
- )
5690
- }));
5691
- }
5671
+ const effectiveApproval = approval ?? createDefaultApprovalConfig();
5672
+ const taskExecuteCheckPolicy = effectiveApproval.taskExecuteCheck === "start_only" ? "start_only" : "both";
5673
+ const mode = effectiveApproval.mode ?? "category";
5692
5674
  if (mode === "steps") {
5693
5675
  const required = new Set(
5694
- (approval.requireCheckSteps ?? approval.requireOkSteps ?? []).map((n) => typeof n === "number" ? n : Number(n)).filter((n) => Number.isFinite(n))
5676
+ (effectiveApproval.requireCheckSteps ?? []).map((n) => typeof n === "number" ? n : Number(n)).filter((n) => Number.isFinite(n))
5695
5677
  );
5696
5678
  const requiresUserCheck = required.has(step);
5697
5679
  return actions.map((action) => ({
@@ -5706,12 +5688,12 @@ function applyApprovalPolicy(step, actions, approval, currentSubstatePhase) {
5706
5688
  }));
5707
5689
  }
5708
5690
  const requiredCategories = new Set(
5709
- (approval.requireCheckCategories ?? approval.requireOkCategories ?? []).map((c) => normalizeApprovalToken(c)).filter(Boolean)
5691
+ (effectiveApproval.requireCheckCategories ?? []).map((c) => normalizeApprovalToken(c)).filter(Boolean)
5710
5692
  );
5711
5693
  const skippedCategories = new Set(
5712
- (approval.skipCheckCategories ?? approval.skipOkCategories ?? []).map((c) => normalizeApprovalToken(c)).filter(Boolean)
5694
+ (effectiveApproval.skipCheckCategories ?? []).map((c) => normalizeApprovalToken(c)).filter(Boolean)
5713
5695
  );
5714
- const defaultPolicy = approval.default ?? "keep";
5696
+ const defaultPolicy = effectiveApproval.default ?? "keep";
5715
5697
  return actions.map((a) => {
5716
5698
  const builtin = Boolean(a.requiresUserCheck);
5717
5699
  const category = normalizeApprovalToken(a.category ?? "uncategorized");
@@ -7976,21 +7958,18 @@ function hasOwnKey(value, key) {
7976
7958
  }
7977
7959
  function isLegacyGeneratedApprovalConfig(approval) {
7978
7960
  const mode = typeof approval.mode === "string" ? approval.mode : "";
7979
- if (mode && mode !== "builtin") return false;
7961
+ if (mode && mode !== "category" && mode !== "steps") return false;
7980
7962
  const overrideKeys = [
7981
7963
  "default",
7982
7964
  "requireCheckSteps",
7983
- "requireOkSteps",
7984
7965
  "requireCheckCategories",
7985
- "requireOkCategories",
7986
7966
  "skipCheckCategories",
7987
- "skipOkCategories",
7988
7967
  "taskExecuteCheck"
7989
7968
  ];
7990
7969
  return !overrideKeys.some((key) => hasOwnKey(approval, key));
7991
7970
  }
7992
7971
  function updateCommand(program2) {
7993
- program2.command("update").description("Update docs templates to the latest version").option("--agents", "Update agents/ folder only").option("--agents-md", "Sync project-scoped AGENTS.md entrypoint").option("--skills", "Cleanup legacy agents/skills copies (CLI-managed)").option("--templates", "Cleanup legacy feature-base copies (CLI-managed)").option(
7972
+ program2.command("update").description("Update docs templates to the latest version").option("--agents", "Update agents/ folder only").option("--agents-md", "Sync project-scoped AGENTS.md entrypoint").option(
7994
7973
  "-f, --force",
7995
7974
  "Force overwrite even if docs has uncommitted changes"
7996
7975
  ).action(async (options) => {
@@ -8033,11 +8012,9 @@ async function runUpdate(options) {
8033
8012
  const docsLockPath = getDocsLockPath(docsDir);
8034
8013
  const forceOverwrite = !!options.force || await isDocsWorktreeCleanOrThrow(docsDir, lang, [docsLockPath]);
8035
8014
  const configBackfill = await backfillMissingConfigDefaults(docsDir);
8036
- const hasExplicitSelection = !!(options.agents || options.agentsMd || options.skills || options.templates);
8037
- const updateAgents = options.agents || options.skills || !hasExplicitSelection;
8015
+ const hasExplicitSelection = !!(options.agents || options.agentsMd);
8016
+ const updateAgents = options.agents || !hasExplicitSelection;
8038
8017
  const updateAgentsMd = options.agentsMd || !hasExplicitSelection;
8039
- const updateTemplates = options.templates || !hasExplicitSelection;
8040
- const agentsMode = options.skills && !options.agents ? "skills" : "all";
8041
8018
  console.log(chalk9.blue(tr(lang, "cli", "update.start")));
8042
8019
  console.log(chalk9.gray(` - ${tr(lang, "cli", "update.langLabel")}: ${lang}`));
8043
8020
  console.log(
@@ -8046,50 +8023,40 @@ async function runUpdate(options) {
8046
8023
  console.log();
8047
8024
  let updatedCount = 0;
8048
8025
  if (updateAgents) {
8049
- if (agentsMode === "skills") {
8050
- console.log(chalk9.blue(tr(lang, "cli", "update.updatingSkills")));
8051
- console.log(
8052
- chalk9.gray(tr(lang, "cli", "update.engineManagedSkillsBuiltin"))
8053
- );
8054
- console.log(chalk9.green(` \u2705 ${tr(lang, "cli", "update.skillsUpdated")}`));
8055
- } else {
8056
- console.log(chalk9.blue(tr(lang, "cli", "update.updatingAgents")));
8057
- }
8058
- if (agentsMode === "all") {
8059
- const commonAgentsBase = path15.join(templatesDir, lang, "common", "agents");
8060
- const targetAgentsBase = path15.join(docsDir, "agents");
8061
- const commonAgents = commonAgentsBase;
8062
- const targetAgents = targetAgentsBase;
8063
- const featurePath = projectType === "multi" ? "docs/features/{component}" : "docs/features";
8064
- const projectName = config.projectName ?? "{{projectName}}";
8065
- const commonReplacements = {
8066
- "{{projectName}}": projectName,
8067
- "{{featurePath}}": featurePath
8068
- };
8069
- if (await fs.pathExists(commonAgents)) {
8070
- const count = await updateFolder(
8071
- commonAgents,
8072
- targetAgents,
8073
- forceOverwrite,
8074
- commonReplacements,
8075
- lang,
8076
- {
8077
- protectedFiles: /* @__PURE__ */ new Set([
8078
- "custom.md",
8079
- "constitution.md",
8080
- ...ENGINE_MANAGED_AGENT_FILES
8081
- ]),
8082
- skipDirectories: new Set(ENGINE_MANAGED_AGENT_DIRS)
8083
- }
8084
- );
8085
- updatedCount += count;
8086
- }
8087
- console.log(
8088
- chalk9.green(
8089
- ` \u2705 ${tr(lang, "cli", "update.agentsUpdated")}`
8090
- )
8026
+ console.log(chalk9.blue(tr(lang, "cli", "update.updatingAgents")));
8027
+ const commonAgentsBase = path15.join(templatesDir, lang, "common", "agents");
8028
+ const targetAgentsBase = path15.join(docsDir, "agents");
8029
+ const commonAgents = commonAgentsBase;
8030
+ const targetAgents = targetAgentsBase;
8031
+ const featurePath = projectType === "multi" ? "docs/features/{component}" : "docs/features";
8032
+ const projectName = config.projectName ?? "{{projectName}}";
8033
+ const commonReplacements = {
8034
+ "{{projectName}}": projectName,
8035
+ "{{featurePath}}": featurePath
8036
+ };
8037
+ if (await fs.pathExists(commonAgents)) {
8038
+ const count = await updateFolder(
8039
+ commonAgents,
8040
+ targetAgents,
8041
+ forceOverwrite,
8042
+ commonReplacements,
8043
+ lang,
8044
+ {
8045
+ protectedFiles: /* @__PURE__ */ new Set([
8046
+ "custom.md",
8047
+ "constitution.md",
8048
+ ...ENGINE_MANAGED_AGENT_FILES
8049
+ ]),
8050
+ skipDirectories: new Set(ENGINE_MANAGED_AGENT_DIRS)
8051
+ }
8091
8052
  );
8053
+ updatedCount += count;
8092
8054
  }
8055
+ console.log(
8056
+ chalk9.green(
8057
+ ` \u2705 ${tr(lang, "cli", "update.agentsUpdated")}`
8058
+ )
8059
+ );
8093
8060
  }
8094
8061
  if (updateAgentsMd) {
8095
8062
  const agentsMdTargets = await collectAgentsMdTargets(cwd, config);
@@ -8103,10 +8070,6 @@ async function runUpdate(options) {
8103
8070
  }
8104
8071
  }
8105
8072
  }
8106
- if (updateTemplates) {
8107
- console.log(chalk9.blue(tr(lang, "cli", "update.updatingFeatureBase")));
8108
- console.log(chalk9.gray(tr(lang, "cli", "update.engineManagedFeatureBaseBuiltin")));
8109
- }
8110
8073
  const pruned = await pruneEngineManagedDocs(docsDir);
8111
8074
  if (pruned.length > 0) {
8112
8075
  console.log(
@@ -8696,15 +8659,15 @@ function getBuiltinDocIds() {
8696
8659
  function normalizeBuiltinDocId(input) {
8697
8660
  const normalized = input.trim().toLowerCase().replace(/_/g, "-");
8698
8661
  if (normalized === "git-workflow") return "git-workflow";
8699
- if (normalized === "issue-doc" || normalized === "issue-md") return "issue-doc";
8700
- if (normalized === "pr-doc" || normalized === "pr-md") return "pr-doc";
8662
+ if (normalized === "issue-doc") return "issue-doc";
8663
+ if (normalized === "pr-doc") return "pr-doc";
8701
8664
  if (normalized === "issue-template") return "issue-doc";
8702
8665
  if (normalized === "pr-template") return "pr-doc";
8703
8666
  if (normalized === "create-feature") return "create-feature";
8704
8667
  if (normalized === "execute-task") return "execute-task";
8705
8668
  if (normalized === "create-issue") return "create-issue";
8706
8669
  if (normalized === "create-pr") return "create-pr";
8707
- if (normalized === "split-feature" || normalized === "feature-split") {
8670
+ if (normalized === "split-feature") {
8708
8671
  return "split-feature";
8709
8672
  }
8710
8673
  if (normalized === "agents") return "agents";
@@ -8766,6 +8729,68 @@ function resolveComponentOption(value) {
8766
8729
  const component = (value || "").trim().toLowerCase();
8767
8730
  return component || void 0;
8768
8731
  }
8732
+ var DEFAULT_MANAGED_DOC_DIRS = [
8733
+ "agents",
8734
+ "designs",
8735
+ "features",
8736
+ "ideas",
8737
+ "prd",
8738
+ "scripts"
8739
+ ];
8740
+ var DEFAULT_MANAGED_DOC_FILES = [
8741
+ "README.md",
8742
+ ".lee-spec-kit.json",
8743
+ ".gitignore"
8744
+ ];
8745
+ var DOC_LIKE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
8746
+ ".md",
8747
+ ".mdx",
8748
+ ".txt",
8749
+ ".rst",
8750
+ ".adoc"
8751
+ ]);
8752
+ function normalizeEntryName(value) {
8753
+ return value.trim().toLowerCase();
8754
+ }
8755
+ function toAllowedSet(values, extras) {
8756
+ return new Set(
8757
+ [...values, ...extras || []].map((entry) => normalizeEntryName(entry)).filter(Boolean)
8758
+ );
8759
+ }
8760
+ function isDocLikeFile(name) {
8761
+ return DOC_LIKE_FILE_EXTENSIONS.has(path15.extname(name).toLowerCase());
8762
+ }
8763
+ async function collectUnmanagedDocsEntries(docsDir, allowed) {
8764
+ const allowedDirs = toAllowedSet(DEFAULT_MANAGED_DOC_DIRS, allowed?.dirs);
8765
+ const allowedFiles = toAllowedSet(DEFAULT_MANAGED_DOC_FILES, allowed?.files);
8766
+ const entries = await fs.readdir(docsDir, { withFileTypes: true });
8767
+ const unmanaged = [];
8768
+ for (const entry of entries) {
8769
+ const name = entry.name || "";
8770
+ if (!name) continue;
8771
+ if (entry.isDirectory()) {
8772
+ if (name.startsWith(".")) continue;
8773
+ if (allowedDirs.has(normalizeEntryName(name))) continue;
8774
+ unmanaged.push({
8775
+ name,
8776
+ kind: "dir",
8777
+ absPath: path15.join(docsDir, name),
8778
+ relPath: `docs/${name}`
8779
+ });
8780
+ continue;
8781
+ }
8782
+ if (!entry.isFile()) continue;
8783
+ if (allowedFiles.has(normalizeEntryName(name))) continue;
8784
+ if (!isDocLikeFile(name)) continue;
8785
+ unmanaged.push({
8786
+ name,
8787
+ kind: "file",
8788
+ absPath: path15.join(docsDir, name),
8789
+ relPath: `docs/${name}`
8790
+ });
8791
+ }
8792
+ return unmanaged.sort((a, b) => a.relPath.localeCompare(b.relPath));
8793
+ }
8769
8794
 
8770
8795
  // src/utils/context-selection.ts
8771
8796
  var REMOTE_ACTION_CATEGORIES = /* @__PURE__ */ new Set([
@@ -9059,6 +9084,13 @@ function toReasonCode(status) {
9059
9084
  async function resolveContextSelection(ctx, featureName, options) {
9060
9085
  const { config } = ctx;
9061
9086
  const { features, branches, warnings } = await scanFeatures(ctx);
9087
+ const unmanagedDocs = await collectUnmanagedDocsEntries(
9088
+ config.docsDir,
9089
+ config.allowedDocsEntries
9090
+ );
9091
+ const unmanagedDocsWarning = unmanagedDocs.length > 0 ? tr(config.lang, "warnings", "unmanagedDocsEntries", {
9092
+ paths: unmanagedDocs.map((entry) => entry.relPath).join(", ")
9093
+ }) : "";
9062
9094
  const selectedComponent = resolveComponentOption(options.component);
9063
9095
  const scopedFeatures = selectedComponent ? features.filter((f) => f.type === selectedComponent) : features;
9064
9096
  const doneFeatures = scopedFeatures.filter((f) => f.completion.workflowDone);
@@ -9129,14 +9161,36 @@ async function resolveContextSelection(ctx, featureName, options) {
9129
9161
  openFeatures,
9130
9162
  targetFeatures
9131
9163
  );
9132
- const matchedFeature = targetFeatures.length === 1 ? targetFeatures[0] : null;
9164
+ const baseMatchedFeature = targetFeatures.length === 1 ? targetFeatures[0] : null;
9165
+ let matchedFeature = baseMatchedFeature;
9166
+ if (baseMatchedFeature && !baseMatchedFeature.completion.workflowDone && unmanagedDocs.length > 0) {
9167
+ const normalizeMessage = tr(config.lang, "messages", "docsUnmanagedNormalize", {
9168
+ paths: unmanagedDocs.map((entry) => entry.relPath).join(", "),
9169
+ featureRef: baseMatchedFeature.folderName
9170
+ });
9171
+ matchedFeature = {
9172
+ ...baseMatchedFeature,
9173
+ currentSubstateId: "docs_unmanaged_normalize",
9174
+ currentSubstateOwner: "main",
9175
+ currentSubstatePhase: "blocked",
9176
+ warnings: unmanagedDocsWarning ? [...baseMatchedFeature.warnings, unmanagedDocsWarning] : [...baseMatchedFeature.warnings],
9177
+ actions: [
9178
+ {
9179
+ type: "instruction",
9180
+ category: "docs_normalize",
9181
+ message: normalizeMessage
9182
+ }
9183
+ ],
9184
+ nextAction: normalizeMessage
9185
+ };
9186
+ }
9133
9187
  const actions = annotateActions(matchedFeature?.actions ?? []);
9134
9188
  const actionOptions = toActionOptions(actions, config.lang);
9135
9189
  const contextVersion = getContextVersion(matchedFeature, actionOptions);
9136
9190
  return {
9137
9191
  features: scopedFeatures,
9138
9192
  branches,
9139
- warnings,
9193
+ warnings: unmanagedDocsWarning ? [...warnings, unmanagedDocsWarning] : warnings,
9140
9194
  doneFeatures,
9141
9195
  openFeatures,
9142
9196
  inProgressFeatures,
@@ -9186,14 +9240,12 @@ function isTaskExecuteProjectCommitCommand(option) {
9186
9240
  function shouldDelegateCurrentAction(actionOptions, currentSubstateOwner) {
9187
9241
  const primaryOption = actionOptions[0];
9188
9242
  const primaryCategory = primaryOption?.action?.category || null;
9189
- const longRunningSet = new Set(
9190
- LEGACY_LONG_RUNNING_DELEGATION_CATEGORIES
9191
- );
9243
+ const handoffCategories = new Set(SUBAGENT_HANDOFF_CATEGORIES);
9192
9244
  const isCommand = primaryOption?.action?.type === "command";
9193
9245
  const isRemoteCommand = isCommand && primaryOption?.action?.operationType === "remote";
9194
9246
  const ownerDelegates = currentSubstateOwner === "subagent";
9195
- const legacyCategoryDelegates = !currentSubstateOwner && !!primaryCategory && longRunningSet.has(primaryCategory);
9196
- const shouldDelegate = (ownerDelegates || legacyCategoryDelegates) && isCommand && !isRemoteCommand && !isTaskExecuteProjectCommitCommand(primaryOption);
9247
+ const categoryFallbackDelegates = !currentSubstateOwner && !!primaryCategory && handoffCategories.has(primaryCategory);
9248
+ const shouldDelegate = (ownerDelegates || categoryFallbackDelegates) && isCommand && !isRemoteCommand && !isTaskExecuteProjectCommitCommand(primaryOption);
9197
9249
  return {
9198
9250
  shouldDelegate,
9199
9251
  category: primaryCategory
@@ -9222,15 +9274,6 @@ function buildAgentOrchestrationPolicy(actionOptions, autoRunAvailable, autoRunC
9222
9274
  ).digest("hex").slice(0, 12) : "";
9223
9275
  return {
9224
9276
  mode: "main_orchestrates_subagent_execution",
9225
- delegationPolicy: "prefer_main_delegate_long_running_fallback_main",
9226
- delegateCommandExecution: "long_running_only",
9227
- delegateAutoRunExecution: true,
9228
- fallbackToMainAgentWhenSubAgentUnavailable: true,
9229
- longRunningCategories: [...LEGACY_LONG_RUNNING_DELEGATION_CATEGORIES],
9230
- currentActionShouldDelegate: delegation.shouldDelegate,
9231
- autoRunDelegationAvailable: autoRunAvailable,
9232
- autoRunShouldDelegate: shouldDelegateAutoRunNow,
9233
- currentActionCategory: delegation.category,
9234
9277
  mainAgentResponsibilities: [
9235
9278
  "Keep user conversation state and approval boundaries",
9236
9279
  "Run the same execution loop directly when sub-agent is unavailable",
@@ -9376,7 +9419,7 @@ function buildDelegatedApprovalGuidance(handoffRequired, handoffMode) {
9376
9419
  const base = "Before asking for approval, show only `actionOptions[].approvalPrompt` lines and `approvalRequest.finalPrompt` to the user. Keep `requiredDocs`, `checkPolicy`, and raw execution commands as internal guidance. For commit actions, include scope (`docs`/`project`) and commit message in the visible prompt. User replies should include the label token (e.g. `A`, `A OK`, `A proceed`, `A \uC9C4\uD589\uD574`).";
9377
9420
  const delegatedCommand = handoffRequired && handoffMode === "command" ? ' When `matchedFeature.currentSubstateOwner="subagent"` and `agentOrchestration.subAgentHandoff.required=true` with `mode="command"`, call spawn_agent first and do not execute the delegated command directly from the main agent. If the delegated command is handoff-only, `--execute` only prepares the handoff; continue the delegated work immediately and do not re-approve the same label.' : "";
9378
9421
  const nonDelegated = " For non-delegated command actions, prefer one-shot `npx lee-spec-kit flow <featureRef> --approve <LABEL> --execute` to avoid session mismatch after context compression/reset. Use ticket-based `context --execute --ticket` only when explicitly needed.";
9379
- const orchestration = ' Use main-agent orchestration: keep short steps in main agent. Prefer `matchedFeature.currentSubstateOwner` + `agentOrchestration.subAgentHandoff` as the delegation SSOT; `currentActionShouldDelegate` is a compatibility mirror. Delegate auto-run only when `agentOrchestration.subAgentHandoff.required=true` with `mode="auto_run"`.';
9422
+ const orchestration = ' Use main-agent orchestration: keep short steps in main agent. Prefer `matchedFeature.currentSubstateOwner` + `agentOrchestration.subAgentHandoff` as the delegation SSOT. Delegate auto-run only when `agentOrchestration.subAgentHandoff.required=true` with `mode="auto_run"`.';
9380
9423
  return `${base}${delegatedCommand}${nonDelegated}${orchestration}`;
9381
9424
  }
9382
9425
  function buildFinalApprovalPrompt(lang, actionOptions) {
@@ -9415,7 +9458,7 @@ function resolveAutoRunCategories(approval) {
9415
9458
  const known = new Set(ACTION_CATEGORIES);
9416
9459
  const unique2 = /* @__PURE__ */ new Set();
9417
9460
  const unknown = /* @__PURE__ */ new Set();
9418
- for (const raw of approval?.requireCheckCategories ?? approval?.requireOkCategories ?? []) {
9461
+ for (const raw of approval?.requireCheckCategories ?? []) {
9419
9462
  const normalized = normalizeCategoryToken(raw);
9420
9463
  if (!normalized) continue;
9421
9464
  if (known.has(normalized)) {
@@ -9450,7 +9493,7 @@ function resolveAutoRunPlan(lang, state, featureName, selectedComponent, approva
9450
9493
  if (state.status !== "single_matched") return base("NOT_SINGLE_MATCHED");
9451
9494
  if (state.actionOptions.length === 0) return base("NO_ACTION_OPTIONS");
9452
9495
  if (approvalRequired) return base("APPROVAL_REQUIRED");
9453
- const mode = approval?.mode ?? "builtin";
9496
+ const mode = approval?.mode ?? "category";
9454
9497
  if (mode !== "category") return base("APPROVAL_MODE_NOT_CATEGORY");
9455
9498
  const defaultPolicy = approval?.default ?? "keep";
9456
9499
  if (defaultPolicy !== "skip") return base("DEFAULT_NOT_SKIP");
@@ -9780,7 +9823,6 @@ function parseApprovalReply(input, validLabels) {
9780
9823
  }
9781
9824
  return null;
9782
9825
  }
9783
- var LEGACY_APPROVAL_TICKET_FILENAME = ".lee-spec-kit.approval-tickets.json";
9784
9826
  var APPROVAL_TICKET_TTL_MS = 5 * 60 * 1e3;
9785
9827
  function getApprovalSessionId() {
9786
9828
  const explicit = (process.env.LEE_SPEC_KIT_SESSION_ID || "").trim();
@@ -9789,12 +9831,6 @@ function getApprovalSessionId() {
9789
9831
  if (terminalSession) return terminalSession;
9790
9832
  return "";
9791
9833
  }
9792
- function getApprovalTicketPaths(config) {
9793
- return {
9794
- runtimePath: getApprovalTicketStorePath(config.docsDir),
9795
- legacyPath: path15.join(config.docsDir, LEGACY_APPROVAL_TICKET_FILENAME)
9796
- };
9797
- }
9798
9834
  async function loadApprovalTicketStore(storePath) {
9799
9835
  if (!await fs.pathExists(storePath)) return { tickets: [] };
9800
9836
  try {
@@ -9817,32 +9853,11 @@ function pruneApprovalTickets(tickets, nowMs) {
9817
9853
  return expiresAtMs > nowMs;
9818
9854
  });
9819
9855
  }
9820
- async function resolveApprovalTicketStoreAndPath(config, nowMs) {
9821
- const { runtimePath, legacyPath } = getApprovalTicketPaths(config);
9822
- if (await fs.pathExists(runtimePath)) {
9823
- return {
9824
- storePath: runtimePath,
9825
- store: await loadApprovalTicketStore(runtimePath)
9826
- };
9827
- }
9828
- if (!await fs.pathExists(legacyPath)) {
9829
- return {
9830
- storePath: runtimePath,
9831
- store: { tickets: [] }
9832
- };
9833
- }
9834
- const legacyStore = await loadApprovalTicketStore(legacyPath);
9835
- const migrated = pruneApprovalTickets(legacyStore.tickets, nowMs);
9836
- await saveApprovalTicketStore(runtimePath, {
9837
- tickets: migrated,
9838
- updatedAt: new Date(nowMs).toISOString(),
9839
- migratedFrom: legacyPath
9840
- });
9841
- await fs.remove(legacyPath).catch(() => {
9842
- });
9856
+ async function resolveApprovalTicketStoreAndPath(config, _nowMs) {
9857
+ const runtimePath = getApprovalTicketStorePath(config.docsDir);
9843
9858
  return {
9844
9859
  storePath: runtimePath,
9845
- store: { tickets: migrated }
9860
+ store: await loadApprovalTicketStore(runtimePath)
9846
9861
  };
9847
9862
  }
9848
9863
  function toApprovalActionHash(payload) {
@@ -10653,7 +10668,7 @@ async function runContext(featureName, options) {
10653
10668
  oneApprovalPerAction: approvalRequired,
10654
10669
  requireFreshContext: true,
10655
10670
  contextVersion: state.contextVersion,
10656
- config: config.approval ?? { mode: "builtin" }
10671
+ config: config.approval ?? createDefaultApprovalConfig()
10657
10672
  },
10658
10673
  agentOrchestration,
10659
10674
  delegatedAction,
@@ -11378,6 +11393,20 @@ async function checkDocsStructure(config, cwd) {
11378
11393
  path: formatPath(cwd, configPath)
11379
11394
  });
11380
11395
  }
11396
+ const unmanagedEntries = await collectUnmanagedDocsEntries(
11397
+ config.docsDir,
11398
+ config.allowedDocsEntries
11399
+ );
11400
+ for (const entry of unmanagedEntries) {
11401
+ issues.push({
11402
+ level: "warn",
11403
+ code: "unmanaged_docs_entry",
11404
+ message: tr(config.lang, "cli", "doctor.issue.unmanagedDocsEntry", {
11405
+ path: entry.relPath
11406
+ }),
11407
+ path: formatPath(cwd, entry.absPath)
11408
+ });
11409
+ }
11381
11410
  return issues;
11382
11411
  }
11383
11412
  async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
@@ -11571,7 +11600,15 @@ function doctorCommand(program2) {
11571
11600
  let warnings = scan.warnings;
11572
11601
  let issues = [];
11573
11602
  issues.push(
11574
- ...await checkDocsStructure({ docsDir, projectType, lang }, cwd)
11603
+ ...await checkDocsStructure(
11604
+ {
11605
+ docsDir,
11606
+ projectType,
11607
+ lang,
11608
+ allowedDocsEntries: config.allowedDocsEntries
11609
+ },
11610
+ cwd
11611
+ )
11575
11612
  );
11576
11613
  issues.push(
11577
11614
  ...await checkFeatures(
@@ -11605,7 +11642,15 @@ function doctorCommand(program2) {
11605
11642
  warnings = scan.warnings;
11606
11643
  issues = [];
11607
11644
  issues.push(
11608
- ...await checkDocsStructure({ docsDir, projectType, lang }, cwd)
11645
+ ...await checkDocsStructure(
11646
+ {
11647
+ docsDir,
11648
+ projectType,
11649
+ lang,
11650
+ allowedDocsEntries: config.allowedDocsEntries
11651
+ },
11652
+ cwd
11653
+ )
11609
11654
  );
11610
11655
  issues.push(
11611
11656
  ...await checkFeatures(
@@ -12121,18 +12166,13 @@ function toCompactStatusReport(report) {
12121
12166
  };
12122
12167
  }
12123
12168
  function buildAgentOrchestrationPolicy2(autoRun, featureRef) {
12124
- const preferredResumeCommand = autoRun?.run?.resumeCommand || autoRun?.resume?.flowCommand || null;
12125
- const handoffRequired = !!autoRun && !!preferredResumeCommand;
12169
+ const resumeCommand = autoRun?.run?.resumeCommand || autoRun?.resume?.flowCommand || null;
12170
+ const handoffRequired = !!autoRun && !!resumeCommand;
12126
12171
  const verifyCacheKey = handoffRequired ? `${(featureRef || "unknown").toLowerCase()}|${Buffer$1.from(
12127
- preferredResumeCommand
12172
+ resumeCommand
12128
12173
  ).toString("base64").slice(0, 12)}` : "";
12129
12174
  return {
12130
12175
  mode: "main_orchestrates_subagent_execution",
12131
- delegationPolicy: "prefer_main_delegate_long_running_fallback_main",
12132
- delegateCommandExecution: "long_running_only",
12133
- delegateAutoRunExecution: true,
12134
- fallbackToMainAgentWhenSubAgentUnavailable: true,
12135
- longRunningCategories: [...LEGACY_LONG_RUNNING_DELEGATION_CATEGORIES],
12136
12176
  mainAgentResponsibilities: [
12137
12177
  "Keep user conversation state and approval boundaries",
12138
12178
  "Run the same execution loop directly when sub-agent is unavailable",
@@ -12151,14 +12191,13 @@ function buildAgentOrchestrationPolicy2(autoRun, featureRef) {
12151
12191
  "AUTO_MANUAL_REQUIRED",
12152
12192
  "command execution error"
12153
12193
  ],
12154
- preferredResumeCommand,
12155
12194
  subAgentHandoff: {
12156
12195
  required: handoffRequired,
12157
12196
  mode: handoffRequired ? "auto_run" : null,
12158
12197
  featureRef,
12159
12198
  category: null,
12160
12199
  cwd: handoffRequired ? process.cwd() : null,
12161
- cmd: handoffRequired ? preferredResumeCommand : null,
12200
+ cmd: handoffRequired ? resumeCommand : null,
12162
12201
  verify: handoffRequired ? {
12163
12202
  runOncePerSession: true,
12164
12203
  cacheKey: verifyCacheKey,
@@ -17818,20 +17857,25 @@ async function runRequirements(options) {
17818
17857
  }
17819
17858
  if (options.strict && issuesFound) process.exitCode = 1;
17820
17859
  }
17821
- function parseTaskLine(line) {
17860
+
17861
+ // src/utils/task-lines.ts
17862
+ function parseTaskLine(line, index = -1) {
17822
17863
  const match = line.match(
17823
- /^\s*-\s*\[(TODO|DOING|DONE|REVIEW)\]\[([^\]]+)\](?:\[[^\]]+\])*\s+(T-[A-Za-z0-9-]+)\s+(.+?)\s*$/
17864
+ /^\s*-\s*\[(TODO|DOING|DONE|REVIEW)\]((?:\[[^\]]+\])*)\s+(T-[A-Za-z0-9-]+)\s+(.+?)\s*$/
17824
17865
  );
17825
17866
  if (!match) return null;
17867
+ const tags = [...(match[2] || "").matchAll(/\[([^\]]+)\]/g)].map((entry) => (entry[1] || "").trim()).filter(Boolean);
17826
17868
  return {
17827
- index: -1,
17869
+ index,
17828
17870
  raw: line,
17829
17871
  status: match[1],
17830
- priority: match[2],
17872
+ tags,
17831
17873
  taskId: match[3],
17832
17874
  title: match[4]
17833
17875
  };
17834
17876
  }
17877
+
17878
+ // src/commands/task-run.ts
17835
17879
  function buildTaskRunPrompt(input) {
17836
17880
  const shared = [
17837
17881
  "Read `spec.md`, `plan.md`, and `tasks.md` before editing code.",
@@ -18003,19 +18047,6 @@ function taskRunCommand(program2) {
18003
18047
  }
18004
18048
  });
18005
18049
  }
18006
- function parseTaskLine2(line) {
18007
- const match = line.match(
18008
- /^\s*-\s*\[(TODO|DOING|DONE|REVIEW)\]\[[^\]]+\](?:\[[^\]]+\])*\s+(T-[A-Za-z0-9-]+)\s+(.+?)\s*$/
18009
- );
18010
- if (!match) return null;
18011
- return {
18012
- index: -1,
18013
- raw: line,
18014
- status: match[1],
18015
- taskId: match[2],
18016
- title: match[3]
18017
- };
18018
- }
18019
18050
  function setTaskStatus2(line, nextStatus) {
18020
18051
  return line.raw.replace(
18021
18052
  /^\s*-\s*\[(TODO|DOING|DONE|REVIEW)\]/,
@@ -18064,7 +18095,7 @@ async function runTaskComplete(featureName, options) {
18064
18095
  );
18065
18096
  }
18066
18097
  const resolvedTask = lines.map((line, index) => {
18067
- const parsed = parseTaskLine2(line);
18098
+ const parsed = parseTaskLine(line);
18068
18099
  return parsed ? { ...parsed, index } : null;
18069
18100
  }).find((entry) => entry?.taskId === requestedTaskId);
18070
18101
  if (!resolvedTask) {
@@ -18143,10 +18174,204 @@ function taskCompleteCommand(program2) {
18143
18174
  }
18144
18175
  );
18145
18176
  }
18177
+ function escapeRegExp8(value) {
18178
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18179
+ }
18180
+ function findTaskListHeadingIndex(lines) {
18181
+ return lines.findIndex((line) => /^\s*##\s+(Task List|태스크 목록)\s*$/.test(line));
18182
+ }
18183
+ function findNextSectionHeadingIndex(lines, start) {
18184
+ for (let i = start + 1; i < lines.length; i += 1) {
18185
+ if (/^\s*##\s+/.test(lines[i] || "")) return i;
18186
+ }
18187
+ return lines.length;
18188
+ }
18189
+ function findTaskInsertIndex(lines, sectionStart, sectionEnd) {
18190
+ let lastTaskIndex = -1;
18191
+ for (let i = sectionStart; i < sectionEnd; i += 1) {
18192
+ if (parseTaskLine(lines[i] || "", i)) lastTaskIndex = i;
18193
+ }
18194
+ if (lastTaskIndex < 0) return sectionEnd;
18195
+ let insertIndex = lastTaskIndex + 1;
18196
+ while (insertIndex < sectionEnd) {
18197
+ const line = lines[insertIndex] || "";
18198
+ if (parseTaskLine(line, insertIndex)) break;
18199
+ if (/^\s{2,}\S/.test(line) || /^\s*$/.test(line)) {
18200
+ insertIndex += 1;
18201
+ continue;
18202
+ }
18203
+ break;
18204
+ }
18205
+ return insertIndex;
18206
+ }
18207
+ function normalizeTaskRef(value) {
18208
+ const trimmed = value.trim();
18209
+ if (!trimmed) {
18210
+ throw createCliError(
18211
+ "INVALID_ARGUMENT",
18212
+ "`--ref` is required. Use `NON-PRD` or an existing `PRD-...` requirement ID."
18213
+ );
18214
+ }
18215
+ if (isNonPrdTag(trimmed)) return "NON-PRD";
18216
+ const normalized = trimmed.toUpperCase();
18217
+ if (!isPrdRequirementId(normalized)) {
18218
+ throw createCliError(
18219
+ "INVALID_ARGUMENT",
18220
+ "`--ref` must be `NON-PRD` or an existing `PRD-FR-001`-style requirement ID."
18221
+ );
18222
+ }
18223
+ return normalized;
18224
+ }
18225
+ function getNextTaskSequence(content, featureFolderName) {
18226
+ const taskIdPattern = new RegExp(
18227
+ `\\bT-${escapeRegExp8(featureFolderName)}-(\\d+)\\b`,
18228
+ "g"
18229
+ );
18230
+ let max = 0;
18231
+ for (const match of content.matchAll(taskIdPattern)) {
18232
+ const numeric = Number(match[1] || "0");
18233
+ if (Number.isFinite(numeric) && numeric > max) max = numeric;
18234
+ }
18235
+ return max + 1;
18236
+ }
18237
+ function formatTaskBlock(input) {
18238
+ return [
18239
+ `- [TODO][${input.ref}] ${input.taskId} ${input.title}`,
18240
+ ` - Date: ${input.recordedAt}`,
18241
+ " - Acceptance:",
18242
+ " - -",
18243
+ " - Checklist:",
18244
+ " - [ ] -"
18245
+ ];
18246
+ }
18247
+ async function resolveTaskFeature(featureName, component) {
18248
+ const ctx = await createCliContext({ cwd: process.cwd() });
18249
+ if (!ctx) {
18250
+ throw createCliError(
18251
+ "CONFIG_NOT_FOUND",
18252
+ "No lee-spec-kit config found in this workspace."
18253
+ );
18254
+ }
18255
+ const state = await resolveContextSelection(ctx, featureName, {
18256
+ component: resolveComponentOption(component)
18257
+ });
18258
+ if (state.status !== "single_matched" || !state.matchedFeature) {
18259
+ throw createCliError(
18260
+ "CONTEXT_SELECTION_REQUIRED",
18261
+ "task add requires a single matched feature. Pass <feature-name> explicitly."
18262
+ );
18263
+ }
18264
+ return {
18265
+ ctx,
18266
+ feature: state.matchedFeature
18267
+ };
18268
+ }
18269
+ async function runTaskAdd(featureName, options) {
18270
+ const { ctx, feature } = await resolveTaskFeature(featureName, options.component);
18271
+ const title = options.title.trim();
18272
+ if (!title) {
18273
+ throw createCliError("INVALID_ARGUMENT", "`--title` must not be empty.");
18274
+ }
18275
+ const ref = normalizeTaskRef(options.ref);
18276
+ if (isPrdRequirementId(ref)) {
18277
+ const { definitions } = await scanPrdRequirements(ctx.fs, ctx.config.docsDir);
18278
+ if (!definitions.has(ref)) {
18279
+ throw createCliError(
18280
+ "PRECONDITION_FAILED",
18281
+ `Requirement "${ref}" is not defined in docs/prd or the upstream requirements doc.`
18282
+ );
18283
+ }
18284
+ }
18285
+ const tasksPath = path15.join(feature.path, "tasks.md");
18286
+ if (!await fs.pathExists(tasksPath)) {
18287
+ throw createCliError(
18288
+ "PRECONDITION_FAILED",
18289
+ `tasks.md not found for feature: ${feature.folderName}`
18290
+ );
18291
+ }
18292
+ const content = await fs.readFile(tasksPath, "utf-8");
18293
+ const lines = content.split("\n");
18294
+ const taskListHeadingIndex = findTaskListHeadingIndex(lines);
18295
+ if (taskListHeadingIndex < 0) {
18296
+ throw createCliError(
18297
+ "PRECONDITION_FAILED",
18298
+ "tasks.md is missing a `Task List` section."
18299
+ );
18300
+ }
18301
+ const nextSectionHeadingIndex = findNextSectionHeadingIndex(
18302
+ lines,
18303
+ taskListHeadingIndex
18304
+ );
18305
+ const taskId = `T-${feature.folderName}-${String(
18306
+ getNextTaskSequence(content, feature.folderName)
18307
+ ).padStart(2, "0")}`;
18308
+ const insertIndex = findTaskInsertIndex(
18309
+ lines,
18310
+ taskListHeadingIndex + 1,
18311
+ nextSectionHeadingIndex
18312
+ );
18313
+ const recordedAt = getLocalDateString();
18314
+ const blockLines = formatTaskBlock({ ref, taskId, title, recordedAt });
18315
+ const shouldPrefixBlank = insertIndex > taskListHeadingIndex + 1 && (lines[insertIndex - 1] || "").trim() !== "";
18316
+ const shouldSuffixBlank = insertIndex < lines.length && (lines[insertIndex] || "").trim() !== "";
18317
+ const insertLines = [
18318
+ ...shouldPrefixBlank ? [""] : [],
18319
+ ...blockLines,
18320
+ ...shouldSuffixBlank ? [""] : []
18321
+ ];
18322
+ lines.splice(insertIndex, 0, ...insertLines);
18323
+ await fs.writeFile(tasksPath, lines.join("\n"), "utf-8");
18324
+ const payload = {
18325
+ status: "ok",
18326
+ reasonCode: "TASK_ADDED",
18327
+ feature: feature.folderName,
18328
+ taskId,
18329
+ title,
18330
+ ref,
18331
+ tasksUpdated: true,
18332
+ tasksPath,
18333
+ recordedAt
18334
+ };
18335
+ if (options.json) {
18336
+ console.log(JSON.stringify(payload, null, 2));
18337
+ return;
18338
+ }
18339
+ console.log(chalk9.green(`Added task ${taskId} to ${feature.folderName}.`));
18340
+ console.log(chalk9.gray(`- ref: ${ref}`));
18341
+ console.log(chalk9.gray(`- tasks.md updated: ${tasksPath}`));
18342
+ }
18343
+ function taskCommand(program2) {
18344
+ 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) => {
18346
+ try {
18347
+ await runTaskAdd(featureName, options);
18348
+ } catch (error) {
18349
+ const ctx = await createCliContext({ cwd: process.cwd() });
18350
+ const lang = ctx?.config?.lang ?? DEFAULT_LANG;
18351
+ const cliError = toCliError(error);
18352
+ const suggestions = getCliErrorSuggestions(cliError.code, lang);
18353
+ if (options.json) {
18354
+ console.log(
18355
+ JSON.stringify({
18356
+ status: "error",
18357
+ reasonCode: cliError.code,
18358
+ error: cliError.message,
18359
+ suggestions
18360
+ })
18361
+ );
18362
+ process.exitCode = 1;
18363
+ return;
18364
+ }
18365
+ console.error(chalk9.red(`[${cliError.code}] ${cliError.message}`));
18366
+ printCliErrorSuggestions(suggestions, lang);
18367
+ process.exitCode = 1;
18368
+ }
18369
+ });
18370
+ }
18146
18371
  function setupCommand(program2) {
18147
18372
  const setup = program2.command("setup").description("Developer environment setup helpers");
18148
18373
  setup.command("codex-bootstrap").description(
18149
- "Install a small Codex global bootstrap that reads ./docs/AGENTS.md"
18374
+ "Install a small Codex global bootstrap that reads ./AGENTS.md or ./docs/AGENTS.md"
18150
18375
  ).option(
18151
18376
  "--remove",
18152
18377
  "Remove the lee-spec-kit managed Codex bootstrap block"
@@ -18374,6 +18599,7 @@ prePrReviewCommand(program);
18374
18599
  codeReviewRunCommand(program);
18375
18600
  taskRunCommand(program);
18376
18601
  taskCompleteCommand(program);
18602
+ taskCommand(program);
18377
18603
  requirementsCommand(program);
18378
18604
  setupCommand(program);
18379
18605
  configureRootCommandSurface();