lee-spec-kit 0.7.5 → 0.7.8

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,69 @@ 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
+ "AGENTS.md",
8742
+ "README.md",
8743
+ ".lee-spec-kit.json",
8744
+ ".gitignore"
8745
+ ];
8746
+ var DOC_LIKE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
8747
+ ".md",
8748
+ ".mdx",
8749
+ ".txt",
8750
+ ".rst",
8751
+ ".adoc"
8752
+ ]);
8753
+ function normalizeEntryName(value) {
8754
+ return value.trim().toLowerCase();
8755
+ }
8756
+ function toAllowedSet(values, extras) {
8757
+ return new Set(
8758
+ [...values, ...extras || []].map((entry) => normalizeEntryName(entry)).filter(Boolean)
8759
+ );
8760
+ }
8761
+ function isDocLikeFile(name) {
8762
+ return DOC_LIKE_FILE_EXTENSIONS.has(path15.extname(name).toLowerCase());
8763
+ }
8764
+ async function collectUnmanagedDocsEntries(docsDir, allowed) {
8765
+ const allowedDirs = toAllowedSet(DEFAULT_MANAGED_DOC_DIRS, allowed?.dirs);
8766
+ const allowedFiles = toAllowedSet(DEFAULT_MANAGED_DOC_FILES, allowed?.files);
8767
+ const entries = await fs.readdir(docsDir, { withFileTypes: true });
8768
+ const unmanaged = [];
8769
+ for (const entry of entries) {
8770
+ const name = entry.name || "";
8771
+ if (!name) continue;
8772
+ if (entry.isDirectory()) {
8773
+ if (name.startsWith(".")) continue;
8774
+ if (allowedDirs.has(normalizeEntryName(name))) continue;
8775
+ unmanaged.push({
8776
+ name,
8777
+ kind: "dir",
8778
+ absPath: path15.join(docsDir, name),
8779
+ relPath: `docs/${name}`
8780
+ });
8781
+ continue;
8782
+ }
8783
+ if (!entry.isFile()) continue;
8784
+ if (allowedFiles.has(normalizeEntryName(name))) continue;
8785
+ if (!isDocLikeFile(name)) continue;
8786
+ unmanaged.push({
8787
+ name,
8788
+ kind: "file",
8789
+ absPath: path15.join(docsDir, name),
8790
+ relPath: `docs/${name}`
8791
+ });
8792
+ }
8793
+ return unmanaged.sort((a, b) => a.relPath.localeCompare(b.relPath));
8794
+ }
8769
8795
 
8770
8796
  // src/utils/context-selection.ts
8771
8797
  var REMOTE_ACTION_CATEGORIES = /* @__PURE__ */ new Set([
@@ -9059,6 +9085,13 @@ function toReasonCode(status) {
9059
9085
  async function resolveContextSelection(ctx, featureName, options) {
9060
9086
  const { config } = ctx;
9061
9087
  const { features, branches, warnings } = await scanFeatures(ctx);
9088
+ const unmanagedDocs = await collectUnmanagedDocsEntries(
9089
+ config.docsDir,
9090
+ config.allowedDocsEntries
9091
+ );
9092
+ const unmanagedDocsWarning = unmanagedDocs.length > 0 ? tr(config.lang, "warnings", "unmanagedDocsEntries", {
9093
+ paths: unmanagedDocs.map((entry) => entry.relPath).join(", ")
9094
+ }) : "";
9062
9095
  const selectedComponent = resolveComponentOption(options.component);
9063
9096
  const scopedFeatures = selectedComponent ? features.filter((f) => f.type === selectedComponent) : features;
9064
9097
  const doneFeatures = scopedFeatures.filter((f) => f.completion.workflowDone);
@@ -9129,14 +9162,36 @@ async function resolveContextSelection(ctx, featureName, options) {
9129
9162
  openFeatures,
9130
9163
  targetFeatures
9131
9164
  );
9132
- const matchedFeature = targetFeatures.length === 1 ? targetFeatures[0] : null;
9165
+ const baseMatchedFeature = targetFeatures.length === 1 ? targetFeatures[0] : null;
9166
+ let matchedFeature = baseMatchedFeature;
9167
+ if (baseMatchedFeature && !baseMatchedFeature.completion.workflowDone && unmanagedDocs.length > 0) {
9168
+ const normalizeMessage = tr(config.lang, "messages", "docsUnmanagedNormalize", {
9169
+ paths: unmanagedDocs.map((entry) => entry.relPath).join(", "),
9170
+ featureRef: baseMatchedFeature.folderName
9171
+ });
9172
+ matchedFeature = {
9173
+ ...baseMatchedFeature,
9174
+ currentSubstateId: "docs_unmanaged_normalize",
9175
+ currentSubstateOwner: "main",
9176
+ currentSubstatePhase: "blocked",
9177
+ warnings: unmanagedDocsWarning ? [...baseMatchedFeature.warnings, unmanagedDocsWarning] : [...baseMatchedFeature.warnings],
9178
+ actions: [
9179
+ {
9180
+ type: "instruction",
9181
+ category: "docs_normalize",
9182
+ message: normalizeMessage
9183
+ }
9184
+ ],
9185
+ nextAction: normalizeMessage
9186
+ };
9187
+ }
9133
9188
  const actions = annotateActions(matchedFeature?.actions ?? []);
9134
9189
  const actionOptions = toActionOptions(actions, config.lang);
9135
9190
  const contextVersion = getContextVersion(matchedFeature, actionOptions);
9136
9191
  return {
9137
9192
  features: scopedFeatures,
9138
9193
  branches,
9139
- warnings,
9194
+ warnings: unmanagedDocsWarning ? [...warnings, unmanagedDocsWarning] : warnings,
9140
9195
  doneFeatures,
9141
9196
  openFeatures,
9142
9197
  inProgressFeatures,
@@ -9186,14 +9241,12 @@ function isTaskExecuteProjectCommitCommand(option) {
9186
9241
  function shouldDelegateCurrentAction(actionOptions, currentSubstateOwner) {
9187
9242
  const primaryOption = actionOptions[0];
9188
9243
  const primaryCategory = primaryOption?.action?.category || null;
9189
- const longRunningSet = new Set(
9190
- LEGACY_LONG_RUNNING_DELEGATION_CATEGORIES
9191
- );
9244
+ const handoffCategories = new Set(SUBAGENT_HANDOFF_CATEGORIES);
9192
9245
  const isCommand = primaryOption?.action?.type === "command";
9193
9246
  const isRemoteCommand = isCommand && primaryOption?.action?.operationType === "remote";
9194
9247
  const ownerDelegates = currentSubstateOwner === "subagent";
9195
- const legacyCategoryDelegates = !currentSubstateOwner && !!primaryCategory && longRunningSet.has(primaryCategory);
9196
- const shouldDelegate = (ownerDelegates || legacyCategoryDelegates) && isCommand && !isRemoteCommand && !isTaskExecuteProjectCommitCommand(primaryOption);
9248
+ const categoryFallbackDelegates = !currentSubstateOwner && !!primaryCategory && handoffCategories.has(primaryCategory);
9249
+ const shouldDelegate = (ownerDelegates || categoryFallbackDelegates) && isCommand && !isRemoteCommand && !isTaskExecuteProjectCommitCommand(primaryOption);
9197
9250
  return {
9198
9251
  shouldDelegate,
9199
9252
  category: primaryCategory
@@ -9222,15 +9275,6 @@ function buildAgentOrchestrationPolicy(actionOptions, autoRunAvailable, autoRunC
9222
9275
  ).digest("hex").slice(0, 12) : "";
9223
9276
  return {
9224
9277
  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
9278
  mainAgentResponsibilities: [
9235
9279
  "Keep user conversation state and approval boundaries",
9236
9280
  "Run the same execution loop directly when sub-agent is unavailable",
@@ -9376,7 +9420,7 @@ function buildDelegatedApprovalGuidance(handoffRequired, handoffMode) {
9376
9420
  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
9421
  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
9422
  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"`.';
9423
+ 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
9424
  return `${base}${delegatedCommand}${nonDelegated}${orchestration}`;
9381
9425
  }
9382
9426
  function buildFinalApprovalPrompt(lang, actionOptions) {
@@ -9415,7 +9459,7 @@ function resolveAutoRunCategories(approval) {
9415
9459
  const known = new Set(ACTION_CATEGORIES);
9416
9460
  const unique2 = /* @__PURE__ */ new Set();
9417
9461
  const unknown = /* @__PURE__ */ new Set();
9418
- for (const raw of approval?.requireCheckCategories ?? approval?.requireOkCategories ?? []) {
9462
+ for (const raw of approval?.requireCheckCategories ?? []) {
9419
9463
  const normalized = normalizeCategoryToken(raw);
9420
9464
  if (!normalized) continue;
9421
9465
  if (known.has(normalized)) {
@@ -9450,7 +9494,7 @@ function resolveAutoRunPlan(lang, state, featureName, selectedComponent, approva
9450
9494
  if (state.status !== "single_matched") return base("NOT_SINGLE_MATCHED");
9451
9495
  if (state.actionOptions.length === 0) return base("NO_ACTION_OPTIONS");
9452
9496
  if (approvalRequired) return base("APPROVAL_REQUIRED");
9453
- const mode = approval?.mode ?? "builtin";
9497
+ const mode = approval?.mode ?? "category";
9454
9498
  if (mode !== "category") return base("APPROVAL_MODE_NOT_CATEGORY");
9455
9499
  const defaultPolicy = approval?.default ?? "keep";
9456
9500
  if (defaultPolicy !== "skip") return base("DEFAULT_NOT_SKIP");
@@ -9780,7 +9824,6 @@ function parseApprovalReply(input, validLabels) {
9780
9824
  }
9781
9825
  return null;
9782
9826
  }
9783
- var LEGACY_APPROVAL_TICKET_FILENAME = ".lee-spec-kit.approval-tickets.json";
9784
9827
  var APPROVAL_TICKET_TTL_MS = 5 * 60 * 1e3;
9785
9828
  function getApprovalSessionId() {
9786
9829
  const explicit = (process.env.LEE_SPEC_KIT_SESSION_ID || "").trim();
@@ -9789,12 +9832,6 @@ function getApprovalSessionId() {
9789
9832
  if (terminalSession) return terminalSession;
9790
9833
  return "";
9791
9834
  }
9792
- function getApprovalTicketPaths(config) {
9793
- return {
9794
- runtimePath: getApprovalTicketStorePath(config.docsDir),
9795
- legacyPath: path15.join(config.docsDir, LEGACY_APPROVAL_TICKET_FILENAME)
9796
- };
9797
- }
9798
9835
  async function loadApprovalTicketStore(storePath) {
9799
9836
  if (!await fs.pathExists(storePath)) return { tickets: [] };
9800
9837
  try {
@@ -9817,32 +9854,11 @@ function pruneApprovalTickets(tickets, nowMs) {
9817
9854
  return expiresAtMs > nowMs;
9818
9855
  });
9819
9856
  }
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
- });
9857
+ async function resolveApprovalTicketStoreAndPath(config, _nowMs) {
9858
+ const runtimePath = getApprovalTicketStorePath(config.docsDir);
9843
9859
  return {
9844
9860
  storePath: runtimePath,
9845
- store: { tickets: migrated }
9861
+ store: await loadApprovalTicketStore(runtimePath)
9846
9862
  };
9847
9863
  }
9848
9864
  function toApprovalActionHash(payload) {
@@ -10653,7 +10669,7 @@ async function runContext(featureName, options) {
10653
10669
  oneApprovalPerAction: approvalRequired,
10654
10670
  requireFreshContext: true,
10655
10671
  contextVersion: state.contextVersion,
10656
- config: config.approval ?? { mode: "builtin" }
10672
+ config: config.approval ?? createDefaultApprovalConfig()
10657
10673
  },
10658
10674
  agentOrchestration,
10659
10675
  delegatedAction,
@@ -11378,6 +11394,20 @@ async function checkDocsStructure(config, cwd) {
11378
11394
  path: formatPath(cwd, configPath)
11379
11395
  });
11380
11396
  }
11397
+ const unmanagedEntries = await collectUnmanagedDocsEntries(
11398
+ config.docsDir,
11399
+ config.allowedDocsEntries
11400
+ );
11401
+ for (const entry of unmanagedEntries) {
11402
+ issues.push({
11403
+ level: "warn",
11404
+ code: "unmanaged_docs_entry",
11405
+ message: tr(config.lang, "cli", "doctor.issue.unmanagedDocsEntry", {
11406
+ path: entry.relPath
11407
+ }),
11408
+ path: formatPath(cwd, entry.absPath)
11409
+ });
11410
+ }
11381
11411
  return issues;
11382
11412
  }
11383
11413
  async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
@@ -11571,7 +11601,15 @@ function doctorCommand(program2) {
11571
11601
  let warnings = scan.warnings;
11572
11602
  let issues = [];
11573
11603
  issues.push(
11574
- ...await checkDocsStructure({ docsDir, projectType, lang }, cwd)
11604
+ ...await checkDocsStructure(
11605
+ {
11606
+ docsDir,
11607
+ projectType,
11608
+ lang,
11609
+ allowedDocsEntries: config.allowedDocsEntries
11610
+ },
11611
+ cwd
11612
+ )
11575
11613
  );
11576
11614
  issues.push(
11577
11615
  ...await checkFeatures(
@@ -11605,7 +11643,15 @@ function doctorCommand(program2) {
11605
11643
  warnings = scan.warnings;
11606
11644
  issues = [];
11607
11645
  issues.push(
11608
- ...await checkDocsStructure({ docsDir, projectType, lang }, cwd)
11646
+ ...await checkDocsStructure(
11647
+ {
11648
+ docsDir,
11649
+ projectType,
11650
+ lang,
11651
+ allowedDocsEntries: config.allowedDocsEntries
11652
+ },
11653
+ cwd
11654
+ )
11609
11655
  );
11610
11656
  issues.push(
11611
11657
  ...await checkFeatures(
@@ -12121,18 +12167,13 @@ function toCompactStatusReport(report) {
12121
12167
  };
12122
12168
  }
12123
12169
  function buildAgentOrchestrationPolicy2(autoRun, featureRef) {
12124
- const preferredResumeCommand = autoRun?.run?.resumeCommand || autoRun?.resume?.flowCommand || null;
12125
- const handoffRequired = !!autoRun && !!preferredResumeCommand;
12170
+ const resumeCommand = autoRun?.run?.resumeCommand || autoRun?.resume?.flowCommand || null;
12171
+ const handoffRequired = !!autoRun && !!resumeCommand;
12126
12172
  const verifyCacheKey = handoffRequired ? `${(featureRef || "unknown").toLowerCase()}|${Buffer$1.from(
12127
- preferredResumeCommand
12173
+ resumeCommand
12128
12174
  ).toString("base64").slice(0, 12)}` : "";
12129
12175
  return {
12130
12176
  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
12177
  mainAgentResponsibilities: [
12137
12178
  "Keep user conversation state and approval boundaries",
12138
12179
  "Run the same execution loop directly when sub-agent is unavailable",
@@ -12151,14 +12192,13 @@ function buildAgentOrchestrationPolicy2(autoRun, featureRef) {
12151
12192
  "AUTO_MANUAL_REQUIRED",
12152
12193
  "command execution error"
12153
12194
  ],
12154
- preferredResumeCommand,
12155
12195
  subAgentHandoff: {
12156
12196
  required: handoffRequired,
12157
12197
  mode: handoffRequired ? "auto_run" : null,
12158
12198
  featureRef,
12159
12199
  category: null,
12160
12200
  cwd: handoffRequired ? process.cwd() : null,
12161
- cmd: handoffRequired ? preferredResumeCommand : null,
12201
+ cmd: handoffRequired ? resumeCommand : null,
12162
12202
  verify: handoffRequired ? {
12163
12203
  runOncePerSession: true,
12164
12204
  cacheKey: verifyCacheKey,
@@ -17818,20 +17858,25 @@ async function runRequirements(options) {
17818
17858
  }
17819
17859
  if (options.strict && issuesFound) process.exitCode = 1;
17820
17860
  }
17821
- function parseTaskLine(line) {
17861
+
17862
+ // src/utils/task-lines.ts
17863
+ function parseTaskLine(line, index = -1) {
17822
17864
  const match = line.match(
17823
- /^\s*-\s*\[(TODO|DOING|DONE|REVIEW)\]\[([^\]]+)\](?:\[[^\]]+\])*\s+(T-[A-Za-z0-9-]+)\s+(.+?)\s*$/
17865
+ /^\s*-\s*\[(TODO|DOING|DONE|REVIEW)\]((?:\[[^\]]+\])*)\s+(T-[A-Za-z0-9-]+)\s+(.+?)\s*$/
17824
17866
  );
17825
17867
  if (!match) return null;
17868
+ const tags = [...(match[2] || "").matchAll(/\[([^\]]+)\]/g)].map((entry) => (entry[1] || "").trim()).filter(Boolean);
17826
17869
  return {
17827
- index: -1,
17870
+ index,
17828
17871
  raw: line,
17829
17872
  status: match[1],
17830
- priority: match[2],
17873
+ tags,
17831
17874
  taskId: match[3],
17832
17875
  title: match[4]
17833
17876
  };
17834
17877
  }
17878
+
17879
+ // src/commands/task-run.ts
17835
17880
  function buildTaskRunPrompt(input) {
17836
17881
  const shared = [
17837
17882
  "Read `spec.md`, `plan.md`, and `tasks.md` before editing code.",
@@ -18003,19 +18048,6 @@ function taskRunCommand(program2) {
18003
18048
  }
18004
18049
  });
18005
18050
  }
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
18051
  function setTaskStatus2(line, nextStatus) {
18020
18052
  return line.raw.replace(
18021
18053
  /^\s*-\s*\[(TODO|DOING|DONE|REVIEW)\]/,
@@ -18064,7 +18096,7 @@ async function runTaskComplete(featureName, options) {
18064
18096
  );
18065
18097
  }
18066
18098
  const resolvedTask = lines.map((line, index) => {
18067
- const parsed = parseTaskLine2(line);
18099
+ const parsed = parseTaskLine(line);
18068
18100
  return parsed ? { ...parsed, index } : null;
18069
18101
  }).find((entry) => entry?.taskId === requestedTaskId);
18070
18102
  if (!resolvedTask) {
@@ -18143,10 +18175,204 @@ function taskCompleteCommand(program2) {
18143
18175
  }
18144
18176
  );
18145
18177
  }
18178
+ function escapeRegExp8(value) {
18179
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18180
+ }
18181
+ function findTaskListHeadingIndex(lines) {
18182
+ return lines.findIndex((line) => /^\s*##\s+(Task List|태스크 목록)\s*$/.test(line));
18183
+ }
18184
+ function findNextSectionHeadingIndex(lines, start) {
18185
+ for (let i = start + 1; i < lines.length; i += 1) {
18186
+ if (/^\s*##\s+/.test(lines[i] || "")) return i;
18187
+ }
18188
+ return lines.length;
18189
+ }
18190
+ function findTaskInsertIndex(lines, sectionStart, sectionEnd) {
18191
+ let lastTaskIndex = -1;
18192
+ for (let i = sectionStart; i < sectionEnd; i += 1) {
18193
+ if (parseTaskLine(lines[i] || "", i)) lastTaskIndex = i;
18194
+ }
18195
+ if (lastTaskIndex < 0) return sectionEnd;
18196
+ let insertIndex = lastTaskIndex + 1;
18197
+ while (insertIndex < sectionEnd) {
18198
+ const line = lines[insertIndex] || "";
18199
+ if (parseTaskLine(line, insertIndex)) break;
18200
+ if (/^\s{2,}\S/.test(line) || /^\s*$/.test(line)) {
18201
+ insertIndex += 1;
18202
+ continue;
18203
+ }
18204
+ break;
18205
+ }
18206
+ return insertIndex;
18207
+ }
18208
+ function normalizeTaskRef(value) {
18209
+ const trimmed = value.trim();
18210
+ if (!trimmed) {
18211
+ throw createCliError(
18212
+ "INVALID_ARGUMENT",
18213
+ "`--ref` is required. Use `NON-PRD` or an existing `PRD-...` requirement ID."
18214
+ );
18215
+ }
18216
+ if (isNonPrdTag(trimmed)) return "NON-PRD";
18217
+ const normalized = trimmed.toUpperCase();
18218
+ if (!isPrdRequirementId(normalized)) {
18219
+ throw createCliError(
18220
+ "INVALID_ARGUMENT",
18221
+ "`--ref` must be `NON-PRD` or an existing `PRD-FR-001`-style requirement ID."
18222
+ );
18223
+ }
18224
+ return normalized;
18225
+ }
18226
+ function getNextTaskSequence(content, featureFolderName) {
18227
+ const taskIdPattern = new RegExp(
18228
+ `\\bT-${escapeRegExp8(featureFolderName)}-(\\d+)\\b`,
18229
+ "g"
18230
+ );
18231
+ let max = 0;
18232
+ for (const match of content.matchAll(taskIdPattern)) {
18233
+ const numeric = Number(match[1] || "0");
18234
+ if (Number.isFinite(numeric) && numeric > max) max = numeric;
18235
+ }
18236
+ return max + 1;
18237
+ }
18238
+ function formatTaskBlock(input) {
18239
+ return [
18240
+ `- [TODO][${input.ref}] ${input.taskId} ${input.title}`,
18241
+ ` - Date: ${input.recordedAt}`,
18242
+ " - Acceptance:",
18243
+ " - -",
18244
+ " - Checklist:",
18245
+ " - [ ] -"
18246
+ ];
18247
+ }
18248
+ async function resolveTaskFeature(featureName, component) {
18249
+ const ctx = await createCliContext({ cwd: process.cwd() });
18250
+ if (!ctx) {
18251
+ throw createCliError(
18252
+ "CONFIG_NOT_FOUND",
18253
+ "No lee-spec-kit config found in this workspace."
18254
+ );
18255
+ }
18256
+ const state = await resolveContextSelection(ctx, featureName, {
18257
+ component: resolveComponentOption(component)
18258
+ });
18259
+ if (state.status !== "single_matched" || !state.matchedFeature) {
18260
+ throw createCliError(
18261
+ "CONTEXT_SELECTION_REQUIRED",
18262
+ "task add requires a single matched feature. Pass <feature-name> explicitly."
18263
+ );
18264
+ }
18265
+ return {
18266
+ ctx,
18267
+ feature: state.matchedFeature
18268
+ };
18269
+ }
18270
+ async function runTaskAdd(featureName, options) {
18271
+ const { ctx, feature } = await resolveTaskFeature(featureName, options.component);
18272
+ const title = options.title.trim();
18273
+ if (!title) {
18274
+ throw createCliError("INVALID_ARGUMENT", "`--title` must not be empty.");
18275
+ }
18276
+ const ref = normalizeTaskRef(options.ref);
18277
+ if (isPrdRequirementId(ref)) {
18278
+ const { definitions } = await scanPrdRequirements(ctx.fs, ctx.config.docsDir);
18279
+ if (!definitions.has(ref)) {
18280
+ throw createCliError(
18281
+ "PRECONDITION_FAILED",
18282
+ `Requirement "${ref}" is not defined in docs/prd or the upstream requirements doc.`
18283
+ );
18284
+ }
18285
+ }
18286
+ const tasksPath = path15.join(feature.path, "tasks.md");
18287
+ if (!await fs.pathExists(tasksPath)) {
18288
+ throw createCliError(
18289
+ "PRECONDITION_FAILED",
18290
+ `tasks.md not found for feature: ${feature.folderName}`
18291
+ );
18292
+ }
18293
+ const content = await fs.readFile(tasksPath, "utf-8");
18294
+ const lines = content.split("\n");
18295
+ const taskListHeadingIndex = findTaskListHeadingIndex(lines);
18296
+ if (taskListHeadingIndex < 0) {
18297
+ throw createCliError(
18298
+ "PRECONDITION_FAILED",
18299
+ "tasks.md is missing a `Task List` section."
18300
+ );
18301
+ }
18302
+ const nextSectionHeadingIndex = findNextSectionHeadingIndex(
18303
+ lines,
18304
+ taskListHeadingIndex
18305
+ );
18306
+ const taskId = `T-${feature.folderName}-${String(
18307
+ getNextTaskSequence(content, feature.folderName)
18308
+ ).padStart(2, "0")}`;
18309
+ const insertIndex = findTaskInsertIndex(
18310
+ lines,
18311
+ taskListHeadingIndex + 1,
18312
+ nextSectionHeadingIndex
18313
+ );
18314
+ const recordedAt = getLocalDateString();
18315
+ const blockLines = formatTaskBlock({ ref, taskId, title, recordedAt });
18316
+ const shouldPrefixBlank = insertIndex > taskListHeadingIndex + 1 && (lines[insertIndex - 1] || "").trim() !== "";
18317
+ const shouldSuffixBlank = insertIndex < lines.length && (lines[insertIndex] || "").trim() !== "";
18318
+ const insertLines = [
18319
+ ...shouldPrefixBlank ? [""] : [],
18320
+ ...blockLines,
18321
+ ...shouldSuffixBlank ? [""] : []
18322
+ ];
18323
+ lines.splice(insertIndex, 0, ...insertLines);
18324
+ await fs.writeFile(tasksPath, lines.join("\n"), "utf-8");
18325
+ const payload = {
18326
+ status: "ok",
18327
+ reasonCode: "TASK_ADDED",
18328
+ feature: feature.folderName,
18329
+ taskId,
18330
+ title,
18331
+ ref,
18332
+ tasksUpdated: true,
18333
+ tasksPath,
18334
+ recordedAt
18335
+ };
18336
+ if (options.json) {
18337
+ console.log(JSON.stringify(payload, null, 2));
18338
+ return;
18339
+ }
18340
+ console.log(chalk9.green(`Added task ${taskId} to ${feature.folderName}.`));
18341
+ console.log(chalk9.gray(`- ref: ${ref}`));
18342
+ console.log(chalk9.gray(`- tasks.md updated: ${tasksPath}`));
18343
+ }
18344
+ function taskCommand(program2) {
18345
+ const task = program2.command("task").description("Manage tasks");
18346
+ task.command("add [feature-name]").description("Append a new task to the end of Task List").requiredOption("--title <title>", "Task title").requiredOption("--ref <ref>", "Requirement ref: NON-PRD or PRD-FR-001").option("--component <component>", "Component name for multi projects").option("--json", "Output JSON").action(async (featureName, options) => {
18347
+ try {
18348
+ await runTaskAdd(featureName, options);
18349
+ } catch (error) {
18350
+ const ctx = await createCliContext({ cwd: process.cwd() });
18351
+ const lang = ctx?.config?.lang ?? DEFAULT_LANG;
18352
+ const cliError = toCliError(error);
18353
+ const suggestions = getCliErrorSuggestions(cliError.code, lang);
18354
+ if (options.json) {
18355
+ console.log(
18356
+ JSON.stringify({
18357
+ status: "error",
18358
+ reasonCode: cliError.code,
18359
+ error: cliError.message,
18360
+ suggestions
18361
+ })
18362
+ );
18363
+ process.exitCode = 1;
18364
+ return;
18365
+ }
18366
+ console.error(chalk9.red(`[${cliError.code}] ${cliError.message}`));
18367
+ printCliErrorSuggestions(suggestions, lang);
18368
+ process.exitCode = 1;
18369
+ }
18370
+ });
18371
+ }
18146
18372
  function setupCommand(program2) {
18147
18373
  const setup = program2.command("setup").description("Developer environment setup helpers");
18148
18374
  setup.command("codex-bootstrap").description(
18149
- "Install a small Codex global bootstrap that reads ./docs/AGENTS.md"
18375
+ "Install a small Codex global bootstrap that reads ./AGENTS.md or ./docs/AGENTS.md"
18150
18376
  ).option(
18151
18377
  "--remove",
18152
18378
  "Remove the lee-spec-kit managed Codex bootstrap block"
@@ -18374,6 +18600,7 @@ prePrReviewCommand(program);
18374
18600
  codeReviewRunCommand(program);
18375
18601
  taskRunCommand(program);
18376
18602
  taskCompleteCommand(program);
18603
+ taskCommand(program);
18377
18604
  requirementsCommand(program);
18378
18605
  setupCommand(program);
18379
18606
  configureRootCommandSurface();