lee-spec-kit 0.6.39 → 0.6.41

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
@@ -215,6 +215,7 @@ var koCli = {
215
215
  "doctor.issue.tasksEmpty": "tasks.md\uC5D0 \uD0DC\uC2A4\uD06C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
216
216
  "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.)",
217
217
  "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.",
218
+ "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.",
218
219
  "doctor.issue.duplicateFeatureId": "\uC911\uBCF5 Feature ID \uAC10\uC9C0: {id} ({count}\uAC1C)",
219
220
  "doctor.issue.missingFeatureId": "Feature \uD3F4\uB354\uBA85\uC774 F001-... \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4. (ID\uB97C \uCD94\uCD9C\uD560 \uC218 \uC5C6\uC74C)",
220
221
  "init.selectLangPrompt": "\uBB38\uC11C \uC5B8\uC5B4\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
@@ -475,9 +476,9 @@ var koContext = {
475
476
  "context.commandDetail.branchCreateGeneric": "({scope}) feature \uBE0C\uB79C\uCE58\uC6A9 worktree\uB97C \uC0DD\uC131\uD558\uAC70\uB098 \uC7AC\uC0AC\uC6A9\uD558\uC138\uC694",
476
477
  "context.commandDetail.codeReviewMergeAfterOk": "({scope}) \uBA85\uC2DC\uC801 \uC2B9\uC778 \uD6C4 PR\uC744 \uBA38\uC9C0\uD558\uC138\uC694",
477
478
  "context.commandDetail.codeReviewPushFix": "({scope}) \uB9AC\uBDF0 \uC218\uC815 \uCEE4\uBC0B\uC744 push\uD558\uC138\uC694",
478
- "context.commandDetail.prePrReviewRun": "({scope}) PR \uC804 \uB9AC\uBDF0\uB97C \uC2DC\uC791\uD558\uC138\uC694. (\uBCF4\uC870 \uC5D0\uC774\uC804\uD2B8(sub-agent) \uC2E4\uD589 \uB2E8\uACC4)",
479
+ "context.commandDetail.prePrReviewRun": "({scope}) \uBCF4\uC870 \uC5D0\uC774\uC804\uD2B8(sub-agent)\uB85C PR \uC804 \uB9AC\uBDF0\uB97C \uC2E4\uD589\uD574 `review-trace.json`\uC744 \uC900\uBE44\uD558\uC138\uC694",
479
480
  "context.commandDetail.prePrReviewRecord": "({scope}) Pre-PR \uB9AC\uBDF0 evidence\uB97C decisions.md\uC640 tasks.md\uC5D0 \uAE30\uB85D\uD558\uC138\uC694",
480
- "context.commandDetail.codeReviewRun": "({scope}) PR \uB9AC\uBDF0\uB97C \uC2DC\uC791\uD558\uC138\uC694. (\uBCF4\uC870 \uC5D0\uC774\uC804\uD2B8(sub-agent) \uC2E4\uD589 \uB2E8\uACC4)",
481
+ "context.commandDetail.codeReviewRun": "({scope}) \uBCF4\uC870 \uC5D0\uC774\uC804\uD2B8(sub-agent)\uB85C PR \uB9AC\uBDF0\uB97C \uC2E4\uD589\uD574 evidence/\uC218\uC815 \uC694\uC57D\uC744 \uC900\uBE44\uD558\uC138\uC694",
481
482
  "context.actionSummary.runDocsCommand": "\uBB38\uC11C \uC791\uC5C5 \uBA85\uB839\uC744 \uC2E4\uD589\uD558\uC138\uC694",
482
483
  "context.actionSummary.runProjectCommand": "\uD504\uB85C\uC81D\uD2B8 \uC791\uC5C5 \uBA85\uB839\uC744 \uC2E4\uD589\uD558\uC138\uC694",
483
484
  "context.actionDetail.featureFolder": "Feature \uD3F4\uB354\uC640 \uAE30\uBCF8 \uBB38\uC11C \uACE8\uACA9\uC744 \uC900\uBE44\uD558\uC138\uC694",
@@ -495,12 +496,12 @@ var koContext = {
495
496
  "context.actionDetail.issueCreatePrepareFromDoc": "issue.md \uCD08\uC548\uC744 \uBCF4\uC644\uD558\uACE0 \uC0C1\uD0DC\uB97C Ready\uB85C \uC124\uC815\uD558\uC138\uC694",
496
497
  "context.actionDetail.issueCreateFromDoc": "Ready \uC0C1\uD0DC issue.md\uB85C \uC774\uC288\uB97C \uC0DD\uC131\uD558\uACE0 \uBC88\uD638\uB97C \uB3D9\uAE30\uD654\uD558\uC138\uC694",
497
498
  "context.actionDetail.taskExecute": "\uD604\uC7AC \uD0DC\uC2A4\uD06C\uB97C \uC9C4\uD589\uD558\uC138\uC694",
498
- "context.actionDetail.taskExecuteRun": "\uC791\uC5C5 \uAD6C\uD604\uC744 \uC2DC\uC791\uD558\uC138\uC694. (\uBCF4\uC870 \uC5D0\uC774\uC804\uD2B8(sub-agent) \uC2E4\uD589 \uB2E8\uACC4)",
499
- "context.actionDetail.taskExecuteContinue": "\uC9C4\uD589 \uC911\uC778 \uC791\uC5C5\uC744 \uACC4\uC18D \uC9C4\uD589\uD558\uC138\uC694. (\uBCF4\uC870 \uC5D0\uC774\uC804\uD2B8(sub-agent) \uC2E4\uD589 \uB2E8\uACC4)",
499
+ "context.actionDetail.taskExecuteRun": "\uBCF4\uC870 \uC5D0\uC774\uC804\uD2B8(sub-agent) \uC791\uC5C5 handoff\uB97C \uC900\uBE44\uD558\uACE0 \uD0DC\uC2A4\uD06C\uB97C \uC2DC\uC791\uD558\uC138\uC694. (TODO\uBA74 DOING\uC73C\uB85C \uBCC0\uACBD)",
500
+ "context.actionDetail.taskExecuteContinue": "\uBCF4\uC870 \uC5D0\uC774\uC804\uD2B8(sub-agent) \uC791\uC5C5 handoff\uB97C \uC900\uBE44\uD574 \uC9C4\uD589 \uC911\uC778 \uD0DC\uC2A4\uD06C\uB97C \uC774\uC5B4\uAC00\uC138\uC694",
500
501
  "context.actionDetail.reviewFixCommit": "\uD574\uACB0\uD55C \uB9AC\uBDF0 \uD56D\uBAA9 \uC694\uC57D\uC73C\uB85C \uB9AC\uBDF0 \uC218\uC815 \uCEE4\uBC0B\uC744 \uB9CC\uB4DC\uC138\uC694",
501
- "context.actionDetail.prePrReviewRun": "PR \uC804 \uB9AC\uBDF0\uB97C \uC218\uD589\uD558\uC138\uC694. (`review-trace.json` \uC0DD\uC131, \uBCF4\uC870 \uC5D0\uC774\uC804\uD2B8(sub-agent) \uC2E4\uD589 \uB2E8\uACC4)",
502
+ "context.actionDetail.prePrReviewRun": "\uBCF4\uC870 \uC5D0\uC774\uC804\uD2B8(sub-agent)\uB85C PR \uC804 \uB9AC\uBDF0\uB97C \uC2E4\uD589\uD574 `review-trace.json`\uC744 \uC900\uBE44\uD558\uC138\uC694",
502
503
  "context.actionDetail.prePrReviewRecord": "PR \uC804 \uB9AC\uBDF0 evidence\uB97C decisions.md\uC640 tasks.md\uC5D0 \uAE30\uB85D\uD558\uC138\uC694",
503
- "context.actionDetail.codeReviewRun": "PR \uB9AC\uBDF0\uB97C \uC9C4\uD589\uD558\uC138\uC694. (\uB9AC\uBDF0 evidence/\uC218\uC815 \uC694\uC57D \uC900\uBE44, \uBCF4\uC870 \uC5D0\uC774\uC804\uD2B8(sub-agent) \uC2E4\uD589 \uB2E8\uACC4)",
504
+ "context.actionDetail.codeReviewRun": "\uBCF4\uC870 \uC5D0\uC774\uC804\uD2B8(sub-agent)\uB85C PR \uB9AC\uBDF0\uB97C \uC2E4\uD589\uD574 evidence/\uC218\uC815 \uC694\uC57D\uC744 \uC900\uBE44\uD558\uC138\uC694",
504
505
  "context.actionDetail.prCreate": "PR\uC744 \uC0DD\uC131\uD558\uACE0 tasks \uAE30.md\uC758 PR \uC815\uBCF4\uB97C \uB9DE\uCD94\uC138\uC694",
505
506
  "context.actionDetail.prCreateRequiredSequence": "PR 2\uB2E8\uACC4(\uCD08\uC548/\uC2B9\uC778 \uD6C4 \uC0DD\uC131/\uB3D9\uAE30\uD654)\uB97C \uC21C\uC11C\uB300\uB85C \uC644\uB8CC\uD558\uC138\uC694",
506
507
  "context.actionDetail.prCreatePrepareFromDoc": "pr.md \uCD08\uC548\uC744 \uBCF4\uC644\uD558\uACE0 \uC0C1\uD0DC\uB97C Ready\uB85C \uC124\uC815\uD558\uC138\uC694",
@@ -623,7 +624,7 @@ var koMessages = {
623
624
  taskCommitGateReasonMismatchLastDone: "\uCD5C\uADFC \uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uCEE4\uBC0B\uC774 \uC9C1\uC804 \uC644\uB8CC \uD0DC\uC2A4\uD06C\uC640 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
624
625
  prLegacyAsk: "tasks.md\uC5D0 PR/PR \uC0C1\uD0DC \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uD15C\uD50C\uB9BF\uC744 \uCD5C\uC2E0 \uD3EC\uB9F7\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD560\uAE4C\uC694? (\uD655\uC778 \uD544\uC694)",
625
626
  prePrReviewFieldMissing: "tasks.md\uC5D0 `PR \uC804 \uB9AC\uBDF0` \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. `- **PR \uC804 \uB9AC\uBDF0**: Pending | Done` \uD56D\uBAA9\uC744 \uCD94\uAC00\uD558\uACE0 \uB2E4\uC2DC context\uB97C \uC2E4\uD589\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
626
- prePrReviewRun: "\uCF54\uB4DC \uB9AC\uBDF0 \uC5D0\uC774\uC804\uD2B8\uB97C \uC2E4\uD589\uD574 `spec.md`/`plan.md`/`tasks.md` \uB300\uBE44 \uAD6C\uD604 \uC801\uD569\uC131\uC744 \uAC80\uD1A0\uD558\uACE0, `Summary`/`Feature Intent Summary`/`Implementation Fit`/`Missing Cases`/`Spec Alignment Checked`/`Finding Count`/`Blocking Findings`/`Findings`/`Residual Risks`\uAC00 \uD3EC\uD568\uB41C `review-trace.json`\uC744 \uC0DD\uC131\uD55C \uB4A4 `pre-pr-review`\uB85C \uB9AC\uBDF0 \uACB0\uACFC\uB97C \uAE30\uB85D\uD558\uC138\uC694. \uD604\uC7AC evidence \uC815\uCC45\uC774 \uACBD\uB85C\uB97C \uC694\uAD6C\uD560 \uB54C\uB9CC `--evidence review-trace.json`\uC744 \uD568\uAED8 \uC0AC\uC6A9\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
627
+ prePrReviewRun: "\uCF54\uB4DC \uB9AC\uBDF0 \uC5D0\uC774\uC804\uD2B8\uB97C \uC2E4\uD589\uD574 `spec.md`/`plan.md`/`tasks.md` \uB300\uBE44 \uAD6C\uD604 \uC801\uD569\uC131\uC744 \uAC80\uD1A0\uD558\uACE0, `Summary`/`Feature Intent Summary`/`Implementation Fit`/`Missing Cases`/`Spec Alignment Checked`/`Finding Count`/`Blocking Findings`/`Findings`/`Residual Risks`\uAC00 \uD3EC\uD568\uB41C `review-trace.json`\uC744 \uC0DD\uC131\uD55C \uB4A4 `pre-pr-review`\uB85C \uB9AC\uBDF0 \uACB0\uACFC\uB97C \uAE30\uB85D\uD558\uC138\uC694. `pre-pr-review-run` \uC790\uCCB4\uB294 evidence\uB97C \uC0DD\uC131\uD558\uAC70\uB098 \uC0C1\uD0DC\uB97C \uBC14\uB85C \uB118\uAE30\uC9C0 \uC54A\uC73C\uBA70, \uD604\uC7AC evidence \uC815\uCC45\uC774 \uACBD\uB85C\uB97C \uC694\uAD6C\uD560 \uB54C\uB9CC `--evidence review-trace.json`\uC744 \uD568\uAED8 \uC0AC\uC6A9\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
627
628
  prePrReviewEvidenceMissing: "tasks.md\uC758 `PR \uC804 \uB9AC\uBDF0 Evidence`\uAC00 \uBE44\uC5B4\uC788\uAC70\uB098 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC2E4\uC81C \uD30C\uC77C \uACBD\uB85C\uC640 `Pre-PR Review Log`(\uB610\uB294 `PR \uC804 \uB9AC\uBDF0 \uB85C\uADF8`)\uC5D0 placeholder\uAC00 \uC544\uB2CC `Summary`/`Feature Intent Summary`/`Implementation Fit`/`Missing Cases`/`Spec Alignment Checked`/`Finding Count`/`Blocking Findings`/`Decision`/`Findings`(\uB610\uB294 \uBA85\uC2DC\uC801 `0 findings`)/`Residual Risks`\uB97C \uAE30\uB85D\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
628
629
  prePrReviewDecisionMissing: "tasks.md\uC758 `PR \uC804 \uB9AC\uBDF0 Decision`\uC774 \uBE44\uC5B4\uC788\uAC70\uB098 \uACB0\uC815 \uD615\uC2DD\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `\uACB0\uC815: ...`(\uB610\uB294 `decision: ...`) \uD615\uC2DD\uC73C\uB85C \uAE30\uB85D\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
629
630
  prePrReviewFixRequired: "\uD604\uC7AC `PR \uC804 \uB9AC\uBDF0 Decision`\uC774 `{decision}`\uC785\uB2C8\uB2E4. PR \uC0DD\uC131 \uB2E8\uACC4\uB85C \uC774\uB3D9\uD558\uAE30 \uC804\uC5D0 pre-PR \uC9C0\uC801\uC0AC\uD56D\uC744 \uCF54\uB4DC\uC5D0 \uBC18\uC601\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
@@ -766,6 +767,7 @@ var enCli = {
766
767
  "doctor.issue.tasksEmpty": "tasks.md has no tasks.",
767
768
  "doctor.issue.tasksDocStatusUnset": "tasks.md Doc Status is not set. (Set it to Draft, Review, or Approved.)",
768
769
  "doctor.issue.tasksDocStatusMissing": "tasks.md is missing the Doc Status field. Add `- **Doc Status**: -` and `Values: Draft | Review | Approved`.",
770
+ "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.",
769
771
  "doctor.issue.duplicateFeatureId": "Duplicate Feature ID detected: {id} ({count})",
770
772
  "doctor.issue.missingFeatureId": "Feature folder name is not in F001-... format. (Cannot extract ID)",
771
773
  "init.selectLangPrompt": "Select docs language:",
@@ -1026,9 +1028,9 @@ var enContext = {
1026
1028
  "context.commandDetail.branchCreateGeneric": "({scope}) create or reuse feature branch worktree",
1027
1029
  "context.commandDetail.codeReviewMergeAfterOk": "({scope}) merge PR after explicit OK",
1028
1030
  "context.commandDetail.codeReviewPushFix": "({scope}) push review-fix commits",
1029
- "context.commandDetail.prePrReviewRun": "({scope}) start the pre-PR review. (helper agent/sub-agent execution stage)",
1031
+ "context.commandDetail.prePrReviewRun": "({scope}) run the pre-PR review via a helper agent/sub-agent and prepare `review-trace.json`",
1030
1032
  "context.commandDetail.prePrReviewRecord": "({scope}) record pre-PR review evidence into decisions.md + tasks.md",
1031
- "context.commandDetail.codeReviewRun": "({scope}) start the PR review. (helper agent/sub-agent execution stage)",
1033
+ "context.commandDetail.codeReviewRun": "({scope}) run the PR review via a helper agent/sub-agent and prepare evidence/fix summary",
1032
1034
  "context.actionSummary.runDocsCommand": "Run docs command",
1033
1035
  "context.actionSummary.runProjectCommand": "Run project command",
1034
1036
  "context.actionDetail.featureFolder": "Prepare feature folder and baseline docs",
@@ -1046,12 +1048,12 @@ var enContext = {
1046
1048
  "context.actionDetail.issueCreatePrepareFromDoc": "Refine issue.md draft and set Status to Ready",
1047
1049
  "context.actionDetail.issueCreateFromDoc": "Create GitHub Issue from ready issue.md and sync Issue",
1048
1050
  "context.actionDetail.taskExecute": "Proceed with the current task",
1049
- "context.actionDetail.taskExecuteRun": "Start implementing the task. (helper agent/sub-agent execution stage)",
1050
- "context.actionDetail.taskExecuteContinue": "Continue the in-progress task. (helper agent/sub-agent execution stage)",
1051
+ "context.actionDetail.taskExecuteRun": "Prepare helper agent/sub-agent task handoff and start the task. (TODO becomes DOING)",
1052
+ "context.actionDetail.taskExecuteContinue": "Prepare helper agent/sub-agent handoff and continue the in-progress task",
1051
1053
  "context.actionDetail.reviewFixCommit": "Create a review-fix commit with resolved feedback summary",
1052
- "context.actionDetail.prePrReviewRun": "Run the pre-PR review. (generate `review-trace.json`, helper agent/sub-agent execution stage)",
1054
+ "context.actionDetail.prePrReviewRun": "Run the pre-PR review via a helper agent/sub-agent and prepare `review-trace.json`",
1053
1055
  "context.actionDetail.prePrReviewRecord": "Record pre-PR review evidence into decisions.md and tasks.md",
1054
- "context.actionDetail.codeReviewRun": "Run the PR review. (prepare review evidence/fix summary, helper agent/sub-agent execution stage)",
1056
+ "context.actionDetail.codeReviewRun": "Run the PR review via a helper agent/sub-agent and prepare evidence/fix summary",
1055
1057
  "context.actionDetail.prCreate": "Create PR and sync PR fields in tasks.md",
1056
1058
  "context.actionDetail.prCreateRequiredSequence": "Complete PR 2-step flow: prepare draft + OK, then create and sync",
1057
1059
  "context.actionDetail.prCreatePrepareFromDoc": "Refine pr.md draft and set Status to Ready",
@@ -1174,7 +1176,7 @@ var enMessages = {
1174
1176
  taskCommitGateReasonMismatchLastDone: "The latest project code commit does not match the last completed task",
1175
1177
  prLegacyAsk: "tasks.md is missing PR/PR Status fields. Update to the latest template format? (CHECK required)",
1176
1178
  prePrReviewFieldMissing: "tasks.md is missing the `Pre-PR Review` field. Add `- **Pre-PR Review**: Pending | Done` and run context again. (CHECK required)",
1177
- prePrReviewRun: "Run the code review agent, compare the implementation against `spec.md`/`plan.md`/`tasks.md`, and generate `review-trace.json` with `Summary`, `Feature Intent Summary`, `Implementation Fit`, `Missing Cases`, `Spec Alignment Checked`, `Finding Count`, `Blocking Findings`, `Findings`, and `Residual Risks`. Then record findings with `pre-pr-review`; use `--evidence review-trace.json` when the active evidence policy requires a path. (CHECK required)",
1179
+ prePrReviewRun: "Run the code review agent, compare the implementation against `spec.md`/`plan.md`/`tasks.md`, and generate `review-trace.json` with `Summary`, `Feature Intent Summary`, `Implementation Fit`, `Missing Cases`, `Spec Alignment Checked`, `Finding Count`, `Blocking Findings`, `Findings`, and `Residual Risks`. Then record findings with `pre-pr-review`; `pre-pr-review-run` itself does not generate evidence or advance state, and you should use `--evidence review-trace.json` only when the active evidence policy requires a path. (CHECK required)",
1178
1180
  prePrReviewEvidenceMissing: "tasks.md `Pre-PR Evidence` is empty/invalid. Point to a real file and include a `Pre-PR Review Log` section with non-placeholder `Summary`, `Feature Intent Summary`, `Implementation Fit`, `Missing Cases`, `Spec Alignment Checked`, `Finding Count`, `Blocking Findings`, `Decision`, `Findings` (or explicit `0 findings`), and `Residual Risks`. (CHECK required)",
1179
1181
  prePrReviewDecisionMissing: "tasks.md `Pre-PR Decision` is empty/placeholder or missing decision format. Record it as `decision: ...` (or `\uACB0\uC815: ...`). (CHECK required)",
1180
1182
  prePrReviewFixRequired: "Current `Pre-PR Decision` is `{decision}`. Apply the requested fixes from pre-PR findings before moving to PR creation. (CHECK required)",
@@ -5685,13 +5687,21 @@ function hasStructuredReviewSummary(value) {
5685
5687
  if (!value) return false;
5686
5688
  const trimmed = value.trim();
5687
5689
  if (!trimmed) return false;
5688
- return /^(?:summary|요약)\s*[::]\s*\S.+$/i.test(trimmed);
5690
+ const match = trimmed.match(/^(?:summary|요약)\s*[::]\s*(.+)$/i);
5691
+ if (!match) return false;
5692
+ const payload = (match[1] || "").trim();
5693
+ if (!payload) return false;
5694
+ return !isReviewDraftPlaceholder(payload) && !isPlaceholderReviewEvidence(payload);
5689
5695
  }
5690
5696
  function hasStructuredReviewDecision(value) {
5691
5697
  if (!value) return false;
5692
5698
  const trimmed = value.trim();
5693
5699
  if (!trimmed) return false;
5694
- return /^(?:decision|결정)\s*[::]\s*\S.+$/i.test(trimmed);
5700
+ const match = trimmed.match(/^(?:decision|결정)\s*[::]\s*(.+)$/i);
5701
+ if (!match) return false;
5702
+ const payload = (match[1] || "").trim();
5703
+ if (!payload) return false;
5704
+ return !isReviewDraftPlaceholder(payload) && !isPlaceholderReviewEvidence(payload);
5695
5705
  }
5696
5706
  function parsePrePrDecisionOutcome(value) {
5697
5707
  if (!value) return void 0;
@@ -10106,6 +10116,108 @@ function printChecklist(f, stepDefinitions) {
10106
10116
  console.log(` ${mark} ${definition.step}. ${label} ${detail}`);
10107
10117
  });
10108
10118
  }
10119
+ var PRD_REQUIREMENT_ID_RE = /\bPRD-(?:FR|US|NFR)-\d+\b/gi;
10120
+ function isPrdRequirementId(value) {
10121
+ return /^PRD-(?:FR|US|NFR)-\d+$/i.test(value.trim());
10122
+ }
10123
+ function isNonPrdTag(value) {
10124
+ const trimmed = value.trim();
10125
+ return /^NON[-_ ]?PRD$/i.test(trimmed);
10126
+ }
10127
+ function normalizeRelPath2(value) {
10128
+ return value.replace(/\\/g, "/").replace(/^\.\/+/, "");
10129
+ }
10130
+ function extractTitleAfterId(line, id) {
10131
+ const idx = line.indexOf(id);
10132
+ if (idx < 0) return void 0;
10133
+ const after = line.slice(idx + id.length);
10134
+ const cleaned = after.replace(/^[\s::\-–—)\]]+/, "").trim();
10135
+ return cleaned ? cleaned : void 0;
10136
+ }
10137
+ async function scanPrdRequirements(fsAdapter, docsDir) {
10138
+ const prdDir = path12.join(docsDir, "prd");
10139
+ const files = await walkFiles(fsAdapter, prdDir, {
10140
+ extensions: [".md"],
10141
+ ignoreDirs: [".git", "node_modules", "dist", "tmp"]
10142
+ });
10143
+ const definitions = /* @__PURE__ */ new Map();
10144
+ const duplicates = [];
10145
+ for (const filePath of files) {
10146
+ if (path12.basename(filePath).toLowerCase() === "readme.md") {
10147
+ continue;
10148
+ }
10149
+ let content = "";
10150
+ try {
10151
+ content = await fsAdapter.readFile(filePath, "utf-8");
10152
+ } catch {
10153
+ continue;
10154
+ }
10155
+ const relFile = normalizeRelPath2(path12.relative(docsDir, filePath));
10156
+ const lines = content.split(/\r?\n/);
10157
+ let inCodeBlock = false;
10158
+ for (let i = 0; i < lines.length; i += 1) {
10159
+ const line = lines[i] || "";
10160
+ if (/^\s*(```|~~~)/.test(line)) {
10161
+ inCodeBlock = !inCodeBlock;
10162
+ continue;
10163
+ }
10164
+ if (inCodeBlock) continue;
10165
+ const ids = [...line.matchAll(PRD_REQUIREMENT_ID_RE)].map(
10166
+ (match) => (match[0] || "").toUpperCase()
10167
+ );
10168
+ if (ids.length === 0) continue;
10169
+ for (const id of ids) {
10170
+ const def = {
10171
+ id,
10172
+ title: extractTitleAfterId(line, id),
10173
+ file: relFile,
10174
+ line: i + 1
10175
+ };
10176
+ const existing = definitions.get(id);
10177
+ if (existing) {
10178
+ duplicates.push(def);
10179
+ continue;
10180
+ }
10181
+ definitions.set(id, def);
10182
+ }
10183
+ }
10184
+ }
10185
+ return { definitions, duplicates, filesScanned: files.length };
10186
+ }
10187
+ function parseTaskLines(content) {
10188
+ const out = [];
10189
+ const lines = content.split(/\r?\n/);
10190
+ let inCodeBlock = false;
10191
+ for (let i = 0; i < lines.length; i += 1) {
10192
+ const line = lines[i] || "";
10193
+ if (/^\s*(```|~~~)/.test(line)) {
10194
+ inCodeBlock = !inCodeBlock;
10195
+ continue;
10196
+ }
10197
+ if (inCodeBlock) continue;
10198
+ const match = line.match(
10199
+ /^\s*-\s*\[([A-Z]+)\]((?:\[[^\]]+\])*)\s*(.+?)\s*$/
10200
+ );
10201
+ if (!match) continue;
10202
+ const status = (match[1] || "").trim().toUpperCase();
10203
+ const tagsPart = match[2] || "";
10204
+ const title = (match[3] || "").trim();
10205
+ if (!title) continue;
10206
+ if (status !== "TODO" && status !== "DOING" && status !== "DONE" && status !== "REVIEW") {
10207
+ continue;
10208
+ }
10209
+ const tags = [...tagsPart.matchAll(/\[([^\]]+)\]/g)].map((m) => (m[1] || "").trim()).filter(Boolean);
10210
+ out.push({
10211
+ status,
10212
+ tags,
10213
+ title,
10214
+ line: i + 1
10215
+ });
10216
+ }
10217
+ return out;
10218
+ }
10219
+
10220
+ // src/commands/doctor.ts
10109
10221
  var FIXABLE_ISSUE_CODES = /* @__PURE__ */ new Set([
10110
10222
  "placeholder_left",
10111
10223
  "spec_status_unset",
@@ -10362,6 +10474,10 @@ async function checkDocsStructure(config, cwd) {
10362
10474
  }
10363
10475
  async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
10364
10476
  const issues = [];
10477
+ const { definitions: prdDefinitions } = await scanPrdRequirements(
10478
+ fs,
10479
+ config.docsDir
10480
+ );
10365
10481
  if (features.length === 0) {
10366
10482
  issues.push({
10367
10483
  level: "warn",
@@ -10443,6 +10559,24 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
10443
10559
  path: formatPath(cwd, path12.join(f.path, "tasks.md"))
10444
10560
  });
10445
10561
  }
10562
+ if (f.docs.tasksExists) {
10563
+ const tasksPath = path12.join(f.path, "tasks.md");
10564
+ const tasksContent = await fs.readFile(tasksPath, "utf-8");
10565
+ const unknownPrdTags = [...new Set(
10566
+ parseTaskLines(tasksContent).flatMap((task) => task.tags).filter((tag) => isPrdRequirementId(tag)).map((tag) => tag.trim().toUpperCase()).filter((tag) => !prdDefinitions.has(tag))
10567
+ )].sort();
10568
+ if (unknownPrdTags.length > 0) {
10569
+ issues.push({
10570
+ level: "warn",
10571
+ code: "tasks_prd_tag_unknown",
10572
+ message: tr(config.lang, "cli", "doctor.issue.tasksPrdTagUnknown", {
10573
+ ids: unknownPrdTags.slice(0, 5).join(", "),
10574
+ extra: unknownPrdTags.length > 5 ? ` (+${unknownPrdTags.length - 5} more)` : ""
10575
+ }),
10576
+ path: formatPath(cwd, tasksPath)
10577
+ });
10578
+ }
10579
+ }
10446
10580
  if (f.docs.tasksExists && !f.docs.tasksDocStatusFieldExists && !isInitialTemplateState) {
10447
10581
  issues.push({
10448
10582
  level: "warn",
@@ -13310,7 +13444,9 @@ function insertFieldInGithubIssueSection(content, key, value) {
13310
13444
  }
13311
13445
  function insertFieldInMetadataSection(content, key, value) {
13312
13446
  const lines = content.split("\n");
13313
- const headingIndex = lines.findIndex((line) => /^\s*##\s+Metadata\s*$/.test(line));
13447
+ const headingIndex = lines.findIndex(
13448
+ (line) => /^\s*##\s+(?:Metadata|메타데이터)\s*$/.test(line)
13449
+ );
13314
13450
  if (headingIndex < 0) return { content, changed: false };
13315
13451
  let end = lines.length;
13316
13452
  for (let i = headingIndex + 1; i < lines.length; i++) {
@@ -15073,14 +15209,6 @@ function asNonEmptyString(value, fallback) {
15073
15209
  const trimmed = value.trim();
15074
15210
  return trimmed || fallback;
15075
15211
  }
15076
- function asRequiredNonEmptyString(value, field) {
15077
- const normalized = asNonEmptyString(value, "");
15078
- if (normalized) return normalized;
15079
- throw createCliError(
15080
- "VALIDATION_FAILED",
15081
- `Evidence JSON ${field} is required.`
15082
- );
15083
- }
15084
15212
  function asRequiredBoolean(value, field) {
15085
15213
  if (typeof value === "boolean") return value;
15086
15214
  throw createCliError(
@@ -15097,9 +15225,67 @@ function asRequiredNonNegativeInteger(value, field) {
15097
15225
  `Evidence JSON ${field} must be a non-negative integer.`
15098
15226
  );
15099
15227
  }
15228
+ function asRequiredTextLike(value, field) {
15229
+ if (typeof value === "string") {
15230
+ const trimmed = value.trim();
15231
+ if (trimmed) return trimmed;
15232
+ }
15233
+ if (typeof value === "number" && Number.isFinite(value) && !Number.isNaN(value)) {
15234
+ return String(value);
15235
+ }
15236
+ throw createCliError(
15237
+ "VALIDATION_FAILED",
15238
+ `Evidence JSON ${field} is required.`
15239
+ );
15240
+ }
15241
+ function isPlaceholderReviewEvidence2(value) {
15242
+ return /^(?:-|#)?\s*(?:tbd|todo|n\/a|na|none|pending|미정|없음|-)\s*$/i.test(
15243
+ value.trim()
15244
+ );
15245
+ }
15246
+ function isReviewDraftPlaceholder2(value) {
15247
+ return /^(?:-|#)?\s*(?:tbd|todo|pending|fill(?:\s+in)?|template|example|미정|작성|기입|n\/a|na)\b/i.test(
15248
+ value.trim()
15249
+ );
15250
+ }
15251
+ function isExplicitNoResidualRiskEntry2(value) {
15252
+ const trimmed = value.trim().toLowerCase();
15253
+ return trimmed === "none" || trimmed === "no residual risk" || trimmed === "no residual risks" || trimmed === "no residual risks found" || trimmed === "no residual risks found in reviewed scope" || trimmed === "\uC794\uC5EC \uB9AC\uC2A4\uD06C \uC5C6\uC74C" || trimmed === "\uC794\uC5EC \uC704\uD5D8 \uC5C6\uC74C";
15254
+ }
15255
+ function asRequiredReviewField(value, field, options) {
15256
+ const normalized = asRequiredTextLike(value, field);
15257
+ const allowExplicitNone = !!options?.allowExplicitNone;
15258
+ if (isReviewDraftPlaceholder2(normalized)) {
15259
+ throw createCliError(
15260
+ "VALIDATION_FAILED",
15261
+ `Evidence JSON ${field} contains draft placeholder text.`
15262
+ );
15263
+ }
15264
+ if (isPlaceholderReviewEvidence2(normalized) && !(allowExplicitNone && normalized.trim().toLowerCase() === "none")) {
15265
+ throw createCliError(
15266
+ "VALIDATION_FAILED",
15267
+ `Evidence JSON ${field} contains placeholder evidence text.`
15268
+ );
15269
+ }
15270
+ return normalized;
15271
+ }
15100
15272
  function normalizeCommandsExecuted(value) {
15101
- if (!Array.isArray(value)) return [];
15102
- return value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
15273
+ if (value == null) return [];
15274
+ if (!Array.isArray(value)) {
15275
+ throw createCliError(
15276
+ "VALIDATION_FAILED",
15277
+ 'Evidence JSON "commandsExecuted" must be an array when provided.'
15278
+ );
15279
+ }
15280
+ return value.map((entry, index) => {
15281
+ if (typeof entry !== "string" || !entry.trim()) {
15282
+ throw createCliError(
15283
+ "VALIDATION_FAILED",
15284
+ `Evidence JSON commandsExecuted[${index}] must be a non-empty string.`
15285
+ );
15286
+ }
15287
+ return entry.trim();
15288
+ });
15103
15289
  }
15104
15290
  function normalizeGitPath3(value) {
15105
15291
  return value.trim().replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
@@ -15117,6 +15303,16 @@ function uniquePaths(values) {
15117
15303
  }
15118
15304
  return out;
15119
15305
  }
15306
+ function normalizeFileLine(value, field) {
15307
+ const normalized = asRequiredTextLike(value, field);
15308
+ if (!/^\d+(?:[-:]\d+)?$/.test(normalized)) {
15309
+ throw createCliError(
15310
+ "VALIDATION_FAILED",
15311
+ `Evidence JSON ${field} must start with a numeric line reference (for example "88" or "88-96").`
15312
+ );
15313
+ }
15314
+ return normalized;
15315
+ }
15120
15316
  function normalizeEvidenceFiles(value) {
15121
15317
  if (!Array.isArray(value)) {
15122
15318
  throw createCliError(
@@ -15139,22 +15335,61 @@ function normalizeEvidenceFiles(value) {
15139
15335
  `Evidence JSON files[${index}].path is required.`
15140
15336
  );
15141
15337
  }
15142
- const review = file.review || {};
15338
+ const review = file.review && typeof file.review === "object" ? file.review : file;
15143
15339
  return {
15144
15340
  path: filePath,
15145
15341
  review: {
15146
- risk: asNonEmptyString(review.risk, "not specified"),
15147
- security: asNonEmptyString(review.security, "not specified"),
15148
- perf: asNonEmptyString(review.perf, "not specified"),
15149
- maintainability: asNonEmptyString(
15342
+ risk: asRequiredTextLike(review.risk, `"files[${index}].risk"`),
15343
+ security: asRequiredTextLike(
15344
+ review.security,
15345
+ `"files[${index}].security"`
15346
+ ),
15347
+ perf: asRequiredTextLike(
15348
+ review.perf ?? review.performance,
15349
+ `"files[${index}].perf"`
15350
+ ),
15351
+ maintainability: asRequiredTextLike(
15150
15352
  review.maintainability,
15151
- "not specified"
15353
+ `"files[${index}].maintainability"`
15152
15354
  ),
15153
- fileLine: asNonEmptyString(review.fileLine, "-")
15355
+ fileLine: normalizeFileLine(
15356
+ review.fileLine,
15357
+ `"files[${index}].fileLine"`
15358
+ )
15154
15359
  }
15155
15360
  };
15156
15361
  });
15157
15362
  }
15363
+ function normalizeResidualRisks(value) {
15364
+ if (typeof value === "string" && value.trim()) {
15365
+ const normalized = asRequiredReviewField(
15366
+ value,
15367
+ '"residualRisks"',
15368
+ { allowExplicitNone: true }
15369
+ );
15370
+ if (!isExplicitNoResidualRiskEntry2(normalized) && isPlaceholderReviewEvidence2(normalized)) {
15371
+ throw createCliError(
15372
+ "VALIDATION_FAILED",
15373
+ 'Evidence JSON "residualRisks" contains placeholder evidence text.'
15374
+ );
15375
+ }
15376
+ return [normalized];
15377
+ }
15378
+ if (Array.isArray(value)) {
15379
+ const entries = value.map(
15380
+ (entry, index) => asRequiredReviewField(entry, `"residualRisks[${index}]"`, {
15381
+ allowExplicitNone: true
15382
+ })
15383
+ ).filter(
15384
+ (entry) => isExplicitNoResidualRiskEntry2(entry) || !isReviewDraftPlaceholder2(entry) && !isPlaceholderReviewEvidence2(entry)
15385
+ );
15386
+ if (entries.length > 0) return entries;
15387
+ }
15388
+ throw createCliError(
15389
+ "VALIDATION_FAILED",
15390
+ 'Evidence JSON "residualRisks" must be a non-empty string or string array.'
15391
+ );
15392
+ }
15158
15393
  var PrePrReviewValidator = class {
15159
15394
  constructor(ctx) {
15160
15395
  this.ctx = ctx;
@@ -15181,18 +15416,19 @@ var PrePrReviewValidator = class {
15181
15416
  );
15182
15417
  }
15183
15418
  const normalizedEvidence = {
15184
- summary: asRequiredNonEmptyString(evidence.summary, '"summary"'),
15185
- featureIntentSummary: asRequiredNonEmptyString(
15419
+ summary: asRequiredReviewField(evidence.summary, '"summary"'),
15420
+ featureIntentSummary: asRequiredReviewField(
15186
15421
  evidence.featureIntentSummary,
15187
15422
  '"featureIntentSummary"'
15188
15423
  ),
15189
- implementationFit: asRequiredNonEmptyString(
15424
+ implementationFit: asRequiredReviewField(
15190
15425
  evidence.implementationFit,
15191
15426
  '"implementationFit"'
15192
15427
  ),
15193
- missingCases: asRequiredNonEmptyString(
15428
+ missingCases: asRequiredReviewField(
15194
15429
  evidence.missingCases,
15195
- '"missingCases"'
15430
+ '"missingCases"',
15431
+ { allowExplicitNone: true }
15196
15432
  ),
15197
15433
  specAlignmentChecked: asRequiredBoolean(
15198
15434
  evidence.specAlignmentChecked,
@@ -15207,7 +15443,7 @@ var PrePrReviewValidator = class {
15207
15443
  '"blockingFindings"'
15208
15444
  ),
15209
15445
  files: normalizeEvidenceFiles(evidence.files),
15210
- residualRisks: asNonEmptyString(evidence.residualRisks, "Not specified"),
15446
+ residualRisks: normalizeResidualRisks(evidence.residualRisks),
15211
15447
  commandsExecuted: normalizeCommandsExecuted(evidence.commandsExecuted)
15212
15448
  };
15213
15449
  if (normalizedEvidence.blockingFindings > normalizedEvidence.findingCount) {
@@ -15389,7 +15625,7 @@ var DEFAULT_EVIDENCE_FOR_ANY_MODE = {
15389
15625
  findingCount: 0,
15390
15626
  blockingFindings: 0,
15391
15627
  files: [],
15392
- residualRisks: "Not specified",
15628
+ residualRisks: ["none"],
15393
15629
  commandsExecuted: []
15394
15630
  };
15395
15631
  function escapeRegExp4(value) {
@@ -15476,7 +15712,7 @@ ${normalizedCommands.map((c) => ` - \`${c}\``).join("\n")}
15476
15712
 
15477
15713
  ` : "";
15478
15714
  let filesSection = "";
15479
- if (input.evidence.files.length === 0) {
15715
+ if (input.evidence.findingCount === 0 || input.evidence.files.length === 0) {
15480
15716
  filesSection = " - 0 findings";
15481
15717
  } else {
15482
15718
  filesSection = input.evidence.files.map((f) => {
@@ -15487,6 +15723,7 @@ ${normalizedCommands.map((c) => ` - \`${c}\``).join("\n")}
15487
15723
  - Maintainability: ${f.review.maintainability}`;
15488
15724
  }).join("\n");
15489
15725
  }
15726
+ const residualRisksSection = input.evidence.residualRisks.length > 0 ? input.evidence.residualRisks.map((entry) => ` - ${entry}`).join("\n") : " - none";
15490
15727
  const mainScopeFiles = input.scope.mainChangedFiles.length > 0 ? input.scope.mainChangedFiles.map((entry) => ` - ${entry}`).join("\n") : " - (none)";
15491
15728
  const worktreeScopeFiles = input.scope.worktreeChangedFiles.length > 0 ? input.scope.worktreeChangedFiles.map((entry) => ` - ${entry}`).join("\n") : " - (none)";
15492
15729
  return `## Pre-PR Review Log (${input.date})
@@ -15505,7 +15742,7 @@ ${normalizedCommands.map((c) => ` - \`${c}\``).join("\n")}
15505
15742
  ${commandsRun}
15506
15743
 
15507
15744
  - **Residual Risks**:
15508
- - ${input.evidence.residualRisks}
15745
+ ${residualRisksSection}
15509
15746
 
15510
15747
  - **Review Scope**:
15511
15748
  - **Main Base Ref**: ${input.scope.baseRef}
@@ -15660,6 +15897,9 @@ async function runPrePrReviewRun(featureName, options) {
15660
15897
  feature: featureRef,
15661
15898
  skills: policy.skills,
15662
15899
  fallback: policy.fallback,
15900
+ handoffOnly: true,
15901
+ advancesWorkflow: false,
15902
+ nextStepRequirement: "generate_review_trace_then_record",
15663
15903
  evidenceFile: "review-trace.json",
15664
15904
  prompt,
15665
15905
  recordCommands: {
@@ -15675,6 +15915,11 @@ async function runPrePrReviewRun(featureName, options) {
15675
15915
  }
15676
15916
  console.log(prompt);
15677
15917
  console.log();
15918
+ console.log(
15919
+ chalk8.yellow(
15920
+ config.lang === "ko" ? "\uC774 \uBA85\uB839\uC740 \uB9AC\uBDF0 handoff\uB9CC \uC900\uBE44\uD569\uB2C8\uB2E4. review-trace.json\uC744 \uC9C1\uC811 \uC0DD\uC131\uD558\uAC70\uB098 \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC0C1\uD0DC\uB97C \uBC14\uB85C \uB118\uAE30\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4." : "This command only prepares the review handoff. It does not generate review-trace.json or advance workflow state by itself."
15921
+ )
15922
+ );
15678
15923
  console.log(`Evidence file: review-trace.json`);
15679
15924
  console.log(`Record changes requested: ${changesRequestedCommand}`);
15680
15925
  console.log(`Record approval: ${approveCommand}`);
@@ -15903,6 +16148,8 @@ async function runCodeReviewRun(featureName, options) {
15903
16148
  feature: feature.folderName,
15904
16149
  substateId: "code_review_run",
15905
16150
  owner: "subagent",
16151
+ handoffOnly: true,
16152
+ advancesWorkflow: false,
15906
16153
  nextMainState: "code_review_finalize",
15907
16154
  tasksPath: path12.join(feature.path, "tasks.md"),
15908
16155
  decisionsPath: path12.join(feature.path, "decisions.md"),
@@ -15915,6 +16162,11 @@ async function runCodeReviewRun(featureName, options) {
15915
16162
  }
15916
16163
  console.log(prompt);
15917
16164
  console.log();
16165
+ console.log(
16166
+ chalk8.yellow(
16167
+ config.lang === "ko" ? "\uC774 \uBA85\uB839\uC740 PR \uB9AC\uBDF0 handoff\uB9CC \uC900\uBE44\uD569\uB2C8\uB2E4. \uB9AC\uBDF0 evidence/decision\uC744 \uC9C1\uC811 \uAE30\uB85D\uD558\uAC70\uB098 \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC0C1\uD0DC\uB97C \uBC14\uB85C \uB118\uAE30\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4." : "This command only prepares the PR review handoff. It does not record review evidence/decision or advance workflow state by itself."
16168
+ )
16169
+ );
15918
16170
  console.log(chalk8.gray(`- substate: ${payload.substateId}`));
15919
16171
  console.log(chalk8.gray(`- owner: ${payload.owner}`));
15920
16172
  console.log(chalk8.gray(`- next main state: ${payload.nextMainState}`));
@@ -15949,105 +16201,6 @@ function codeReviewRunCommand(program2) {
15949
16201
  }
15950
16202
  );
15951
16203
  }
15952
- var PRD_REQUIREMENT_ID_RE = /\bPRD-(?:FR|US|NFR)-\d+\b/gi;
15953
- function isPrdRequirementId(value) {
15954
- return /^PRD-(?:FR|US|NFR)-\d+$/i.test(value.trim());
15955
- }
15956
- function isNonPrdTag(value) {
15957
- const trimmed = value.trim();
15958
- return /^NON[-_ ]?PRD$/i.test(trimmed);
15959
- }
15960
- function normalizeRelPath2(value) {
15961
- return value.replace(/\\/g, "/").replace(/^\.\/+/, "");
15962
- }
15963
- function extractTitleAfterId(line, id) {
15964
- const idx = line.indexOf(id);
15965
- if (idx < 0) return void 0;
15966
- const after = line.slice(idx + id.length);
15967
- const cleaned = after.replace(/^[\s::\-–—)\]]+/, "").trim();
15968
- return cleaned ? cleaned : void 0;
15969
- }
15970
- async function scanPrdRequirements(fsAdapter, docsDir) {
15971
- const prdDir = path12.join(docsDir, "prd");
15972
- const files = await walkFiles(fsAdapter, prdDir, {
15973
- extensions: [".md"],
15974
- ignoreDirs: [".git", "node_modules", "dist", "tmp"]
15975
- });
15976
- const definitions = /* @__PURE__ */ new Map();
15977
- const duplicates = [];
15978
- for (const filePath of files) {
15979
- let content = "";
15980
- try {
15981
- content = await fsAdapter.readFile(filePath, "utf-8");
15982
- } catch {
15983
- continue;
15984
- }
15985
- const relFile = normalizeRelPath2(path12.relative(docsDir, filePath));
15986
- const lines = content.split(/\r?\n/);
15987
- let inCodeBlock = false;
15988
- for (let i = 0; i < lines.length; i += 1) {
15989
- const line = lines[i] || "";
15990
- if (/^\s*(```|~~~)/.test(line)) {
15991
- inCodeBlock = !inCodeBlock;
15992
- continue;
15993
- }
15994
- if (inCodeBlock) continue;
15995
- const ids = [...line.matchAll(PRD_REQUIREMENT_ID_RE)].map(
15996
- (match) => (match[0] || "").toUpperCase()
15997
- );
15998
- if (ids.length === 0) continue;
15999
- for (const id of ids) {
16000
- const def = {
16001
- id,
16002
- title: extractTitleAfterId(line, id),
16003
- file: relFile,
16004
- line: i + 1
16005
- };
16006
- const existing = definitions.get(id);
16007
- if (existing) {
16008
- duplicates.push(def);
16009
- continue;
16010
- }
16011
- definitions.set(id, def);
16012
- }
16013
- }
16014
- }
16015
- return { definitions, duplicates, filesScanned: files.length };
16016
- }
16017
- function parseTaskLines(content) {
16018
- const out = [];
16019
- const lines = content.split(/\r?\n/);
16020
- let inCodeBlock = false;
16021
- for (let i = 0; i < lines.length; i += 1) {
16022
- const line = lines[i] || "";
16023
- if (/^\s*(```|~~~)/.test(line)) {
16024
- inCodeBlock = !inCodeBlock;
16025
- continue;
16026
- }
16027
- if (inCodeBlock) continue;
16028
- const match = line.match(
16029
- /^\s*-\s*\[([A-Z]+)\]((?:\[[^\]]+\])*)\s*(.+?)\s*$/
16030
- );
16031
- if (!match) continue;
16032
- const status = (match[1] || "").trim().toUpperCase();
16033
- const tagsPart = match[2] || "";
16034
- const title = (match[3] || "").trim();
16035
- if (!title) continue;
16036
- if (status !== "TODO" && status !== "DOING" && status !== "DONE" && status !== "REVIEW") {
16037
- continue;
16038
- }
16039
- const tags = [...tagsPart.matchAll(/\[([^\]]+)\]/g)].map((m) => (m[1] || "").trim()).filter(Boolean);
16040
- out.push({
16041
- status,
16042
- tags,
16043
- title,
16044
- line: i + 1
16045
- });
16046
- }
16047
- return out;
16048
- }
16049
-
16050
- // src/commands/requirements.ts
16051
16204
  function getRequirementState(tasks) {
16052
16205
  if (tasks.total === 0) return "UNTRACKED";
16053
16206
  if (tasks.done === tasks.total) return "DONE";
@@ -16252,7 +16405,7 @@ async function runRequirements(options) {
16252
16405
  }
16253
16406
  function parseTaskLine(line) {
16254
16407
  const match = line.match(
16255
- /^\s*-\s*\[(TODO|DOING|DONE|REVIEW)\]\[([^\]]+)\]\s+(T-[A-Za-z0-9-]+)\s+(.+?)\s*$/
16408
+ /^\s*-\s*\[(TODO|DOING|DONE|REVIEW)\]\[([^\]]+)\](?:\[[^\]]+\])*\s+(T-[A-Za-z0-9-]+)\s+(.+?)\s*$/
16256
16409
  );
16257
16410
  if (!match) return null;
16258
16411
  return {
@@ -16275,9 +16428,10 @@ function buildTaskRunPrompt(input) {
16275
16428
  ];
16276
16429
  if (input.lang === "ko") {
16277
16430
  return [
16278
- `${input.mode === "start" ? "\uC0C8 task \uC2E4\uD589\uC744 \uC2DC\uC791\uD558\uC138\uC694." : "\uC9C4\uD589 \uC911\uC778 task \uC2E4\uD589\uC744 \uC774\uC5B4\uAC00\uC138\uC694."}`,
16431
+ `${input.mode === "start" ? "\uC0C8 task \uC2E4\uD589\uC6A9 handoff\uB97C \uC900\uBE44\uD558\uC138\uC694." : "\uC9C4\uD589 \uC911\uC778 task handoff\uB97C \uC774\uC5B4\uAC00\uC138\uC694."}`,
16279
16432
  `- Feature: ${input.featureRef}`,
16280
16433
  `- Task: ${input.taskId} ${input.title}`,
16434
+ input.mode === "start" ? "- \uC774 \uBA85\uB839\uC740 `tasks.md`\uC758 \uD604\uC7AC task\uB97C `DOING`\uC73C\uB85C \uBC14\uAFB8\uACE0, \uC774\uD6C4 \uAD6C\uD604 handoff prompt\uB97C \uC900\uBE44\uD569\uB2C8\uB2E4." : "- \uC774 \uBA85\uB839\uC740 \uC9C4\uD589 \uC911 task\uC758 \uAD6C\uD604 handoff prompt\uB97C \uB2E4\uC2DC \uC900\uBE44\uD569\uB2C8\uB2E4.",
16281
16435
  "- \uBA3C\uC800 `spec.md`, `plan.md`, `tasks.md`\uB97C \uC77D\uACE0 \uBC94\uC704\uC640 \uC644\uB8CC \uAE30\uC900\uC744 \uC815\uB9AC\uD558\uC138\uC694.",
16282
16436
  "- \uCF54\uB4DC \uBD84\uC11D\uACFC \uD0D0\uC0C9 \uC791\uC5C5\uC740 \uAE30\uBCF8\uC801\uC73C\uB85C \uC11C\uBE0C\uC5D0\uC774\uC804\uD2B8\uB97C \uC0AC\uC6A9\uD558\uC138\uC694.",
16283
16437
  "- \uC601\uD5A5 \uBC94\uC704 \uBD84\uC11D, \uD14C\uC2A4\uD2B8 \uC704\uCE58 \uD0D0\uC0C9, \uAE30\uC874 \uD328\uD134 \uC870\uC0AC\uCC98\uB7FC \uB3C5\uB9BD\uC801\uC778 \uBD84\uC11D\uC740 \uBCD1\uB82C\uB85C \uC218\uD589\uD558\uC138\uC694.",
@@ -16287,9 +16441,10 @@ function buildTaskRunPrompt(input) {
16287
16441
  ].join("\n");
16288
16442
  }
16289
16443
  return [
16290
- input.mode === "start" ? "Start this task execution." : "Continue this in-progress task execution.",
16444
+ input.mode === "start" ? "Prepare the handoff for this task execution." : "Prepare the handoff for this in-progress task.",
16291
16445
  `- Feature: ${input.featureRef}`,
16292
16446
  `- Task: ${input.taskId} ${input.title}`,
16447
+ `- ${input.mode === "start" ? "This command marks the current task as DOING in tasks.md, then prepares the implementation handoff prompt." : "This command prepares the implementation handoff prompt again for the in-progress task."}`,
16293
16448
  ...shared.map((line) => `- ${line}`)
16294
16449
  ].join("\n");
16295
16450
  }
@@ -16401,7 +16556,9 @@ async function runTaskRun(featureName, options) {
16401
16556
  }
16402
16557
  }
16403
16558
  function taskRunCommand(program2) {
16404
- program2.command("task-run [feature-name]").description("Prepare and start a task execution handoff for sub-agent work").option("--component <component>", "Component name for multi projects").option("--task <task-id>", "Explicit task id to execute").option("--json", "Output JSON").action(async (featureName, options) => {
16559
+ program2.command("task-run [feature-name]").description(
16560
+ "Prepare task execution handoff for sub-agent work (marks TODO tasks as DOING)"
16561
+ ).option("--component <component>", "Component name for multi projects").option("--task <task-id>", "Explicit task id to execute").option("--json", "Output JSON").action(async (featureName, options) => {
16405
16562
  try {
16406
16563
  await runTaskRun(featureName, options);
16407
16564
  } catch (error) {