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 +303 -146
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/en/common/README.md +2 -0
- package/templates/en/common/features/README.md +1 -0
- package/templates/en/common/features/feature-base/spec.md +2 -0
- package/templates/en/common/features/feature-base/tasks.md +4 -0
- package/templates/en/common/ideas/README.md +1 -0
- package/templates/en/common/prd/README.md +2 -0
- package/templates/ko/common/README.md +2 -0
- package/templates/ko/common/features/README.md +1 -0
- package/templates/ko/common/features/feature-base/spec.md +2 -0
- package/templates/ko/common/features/feature-base/tasks.md +4 -0
- package/templates/ko/common/ideas/README.md +1 -0
- package/templates/ko/common/prd/README.md +2 -0
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})
|
|
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})
|
|
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 \
|
|
499
|
-
"context.actionDetail.taskExecuteContinue": "\
|
|
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 \
|
|
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 \
|
|
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})
|
|
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})
|
|
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": "
|
|
1050
|
-
"context.actionDetail.taskExecuteContinue": "
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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 (
|
|
15102
|
-
|
|
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:
|
|
15147
|
-
security:
|
|
15148
|
-
|
|
15149
|
-
|
|
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
|
-
"
|
|
15353
|
+
`"files[${index}].maintainability"`
|
|
15152
15354
|
),
|
|
15153
|
-
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:
|
|
15185
|
-
featureIntentSummary:
|
|
15419
|
+
summary: asRequiredReviewField(evidence.summary, '"summary"'),
|
|
15420
|
+
featureIntentSummary: asRequiredReviewField(
|
|
15186
15421
|
evidence.featureIntentSummary,
|
|
15187
15422
|
'"featureIntentSummary"'
|
|
15188
15423
|
),
|
|
15189
|
-
implementationFit:
|
|
15424
|
+
implementationFit: asRequiredReviewField(
|
|
15190
15425
|
evidence.implementationFit,
|
|
15191
15426
|
'"implementationFit"'
|
|
15192
15427
|
),
|
|
15193
|
-
missingCases:
|
|
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:
|
|
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: "
|
|
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
|
-
|
|
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)\]\[([^\]]+)\]
|
|
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\
|
|
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" ? "
|
|
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(
|
|
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) {
|