lee-spec-kit 0.6.3 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +5 -0
- package/README.md +5 -0
- package/dist/index.js +747 -48
- package/package.json +1 -1
- package/templates/en/common/agents/skills/create-issue.md +1 -1
- package/templates/en/common/agents/skills/create-pr.md +4 -1
- package/templates/ko/common/agents/skills/create-issue.md +1 -1
- package/templates/ko/common/agents/skills/create-pr.md +4 -1
package/README.en.md
CHANGED
|
@@ -217,6 +217,7 @@ Error payloads (`status: "error"`) include `reasonCode` and labeled `suggestions
|
|
|
217
217
|
### Built-in Docs
|
|
218
218
|
|
|
219
219
|
If you do not restore `agents.md` into project docs, fetch CLI-managed guides directly:
|
|
220
|
+
`docs get create-issue|issue-template|create-pr|pr-template --json` also returns `contract` (required sections / artifact rules).
|
|
220
221
|
|
|
221
222
|
```bash
|
|
222
223
|
# list built-in docs
|
|
@@ -285,6 +286,9 @@ npx lee-spec-kit github issue F001 --create --confirm OK --labels enhancement,fr
|
|
|
285
286
|
# Generate PR body
|
|
286
287
|
npx lee-spec-kit github pr F001
|
|
287
288
|
|
|
289
|
+
# Generate PR body (force screenshots/Mermaid sections)
|
|
290
|
+
npx lee-spec-kit github pr F001 --screenshots on --mermaid on
|
|
291
|
+
|
|
288
292
|
# Generate + create PR + sync tasks.md metadata + merge with retry
|
|
289
293
|
npx lee-spec-kit github pr F001 --create --merge --confirm OK --labels enhancement,frontend
|
|
290
294
|
```
|
|
@@ -295,6 +299,7 @@ Key points:
|
|
|
295
299
|
- Labels are validated (at least one required).
|
|
296
300
|
- `--create`/`--merge` are remote operations and require `--confirm OK`.
|
|
297
301
|
- PR helper can sync `tasks.md` PR URL/PR Status automatically (`--no-sync-tasks` to skip).
|
|
302
|
+
- PR artifact sections are controlled by `--screenshots (auto|on|off)` and `--mermaid (auto|on|off)`.
|
|
298
303
|
- Merge includes retry and automatic head-branch refresh (fetch/rebase/force-push) on out-of-date failures.
|
|
299
304
|
|
|
300
305
|
### Status
|
package/README.md
CHANGED
|
@@ -239,6 +239,7 @@ npx lee-spec-kit context F001 --approve A --execute --execute-strict
|
|
|
239
239
|
### CLI 내장 문서 조회
|
|
240
240
|
|
|
241
241
|
`agents.md`를 프로젝트에 복구하지 않는 환경에서는 아래 명령으로 내장 가이드를 직접 조회할 수 있습니다.
|
|
242
|
+
`docs get create-issue|issue-template|create-pr|pr-template --json` 응답에는 `contract`(필수 섹션/아티팩트 규칙)도 포함됩니다.
|
|
242
243
|
|
|
243
244
|
```bash
|
|
244
245
|
# 조회 가능한 내장 문서 목록
|
|
@@ -307,6 +308,9 @@ npx lee-spec-kit github issue F001 --create --confirm OK --labels enhancement,fr
|
|
|
307
308
|
# PR 본문 생성
|
|
308
309
|
npx lee-spec-kit github pr F001
|
|
309
310
|
|
|
311
|
+
# PR 본문 생성 (스크린샷/Mermaid 섹션 강제 포함)
|
|
312
|
+
npx lee-spec-kit github pr F001 --screenshots on --mermaid on
|
|
313
|
+
|
|
310
314
|
# PR 본문 생성 + PR 생성 + tasks.md 메타데이터 동기화 + merge 재시도
|
|
311
315
|
npx lee-spec-kit github pr F001 --create --merge --confirm OK --labels enhancement,frontend
|
|
312
316
|
```
|
|
@@ -317,6 +321,7 @@ npx lee-spec-kit github pr F001 --create --merge --confirm OK --labels enhanceme
|
|
|
317
321
|
- 라벨은 최소 1개 이상 필수입니다.
|
|
318
322
|
- `--create`/`--merge`는 원격 작업이므로 `--confirm OK`가 필요합니다.
|
|
319
323
|
- PR helper는 기본적으로 `tasks.md`의 `PR`/`PR Status`를 동기화합니다. (`--no-sync-tasks`로 비활성화)
|
|
324
|
+
- PR helper의 아티팩트 섹션은 `--screenshots (auto|on|off)`, `--mermaid (auto|on|off)`로 제어할 수 있습니다.
|
|
320
325
|
- merge 시 head 브랜치가 뒤쳐진 경우 fetch/rebase/force-push 후 재시도합니다.
|
|
321
326
|
|
|
322
327
|
### 상태 확인
|
package/dist/index.js
CHANGED
|
@@ -134,6 +134,8 @@ var I18N = {
|
|
|
134
134
|
"context.checkPolicyHint": "\u2139\uFE0F \uC0AC\uC6A9\uC790 \uD655\uC778 \uC815\uCC45\uC740 `npx lee-spec-kit docs get agents --json`\uC73C\uB85C \uBA3C\uC800 \uD655\uC778\uD558\uC138\uC694. (git push/merge/merge commit \uD3EC\uD568) [\uD655\uC778 \uD544\uC694]\uAC00 \uC788\uC73C\uBA74 \uC0AC\uC6A9\uC790\uC5D0\uAC8C `<\uB77C\uBCA8>` \uB610\uB294 `<\uB77C\uBCA8> OK` (\uC608: `A`, `A OK`) \uC751\uB2F5\uC744 \uBC1B\uC740 \uB4A4 \uC9C4\uD589 (config: approval\uB85C \uC870\uC815 \uAC00\uB2A5)",
|
|
135
135
|
"context.actionOptionHint": "\uC2B9\uC778 \uC751\uB2F5 \uD615\uC2DD: `<\uB77C\uBCA8>` \uB610\uB294 `<\uB77C\uBCA8> OK` (\uC608: `A`, `A OK`)",
|
|
136
136
|
"context.actionExplainHint": "\uC2B9\uC778 \uC694\uCCAD \uC804, \uAC01 \uB77C\uBCA8\uC774 \uBB34\uC5C7\uC744 \uC2E4\uD589/\uBCC0\uACBD\uD558\uB294\uC9C0 \uD55C \uC904 \uC694\uC57D\uACFC \uD568\uAED8 \uC124\uBA85\uD558\uC138\uC694.",
|
|
137
|
+
"context.finalLabelPrompt": "\uD604\uC7AC \uC120\uD0DD \uAC00\uB2A5\uD55C \uB77C\uBCA8: {labels}. \uB9C8\uC9C0\uB9C9 \uC751\uB2F5\uC740 `<\uB77C\uBCA8>` \uB610\uB294 `<\uB77C\uBCA8> OK` \uD615\uC2DD\uC73C\uB85C \uBC1B\uC73C\uC138\uC694. (\uC608: {example})",
|
|
138
|
+
"context.finalLabelCommandHint": "\uB77C\uBCA8\uC744 \uBC1B\uC73C\uBA74 \uBC14\uB85C \uC2E4\uD589: {command}",
|
|
137
139
|
"context.readBuiltinDocFirst": "\uBA3C\uC800 \uB0B4\uC7A5 \uBB38\uC11C\uB97C \uD655\uC778\uD558\uC138\uC694: {command}",
|
|
138
140
|
"context.tipDocsCommitRules": "\uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uADDC\uCE59\uC740 `npx lee-spec-kit docs get git-workflow --json`\uC73C\uB85C \uD655\uC778\uD558\uC138\uC694.",
|
|
139
141
|
"context.list.docsCommitNeeded": "\uBB38\uC11C \uCEE4\uBC0B \uD544\uC694",
|
|
@@ -218,6 +220,8 @@ var I18N = {
|
|
|
218
220
|
"github.optPrMerge": "\uC7AC\uC2DC\uB3C4/\uD5E4\uB4DC \uAC31\uC2E0\uACFC \uD568\uAED8 PR merge \uC218\uD589",
|
|
219
221
|
"github.optPrConfirm": "\uC6D0\uACA9 \uC791\uC5C5(--create/--merge)\uC6A9 \uBA85\uC2DC\uC801 \uC2B9\uC778 \uD1A0\uD070. \uC0AC\uC6A9\uAC12: OK",
|
|
220
222
|
"github.optPrRetry": "merge \uC7AC\uC2DC\uB3C4 \uD69F\uC218 (\uAE30\uBCF8: 3)",
|
|
223
|
+
"github.optPrScreenshots": "PR \uC2A4\uD06C\uB9B0\uC0F7 \uC139\uC158 \uBAA8\uB4DC (auto|on|off, \uAE30\uBCF8: auto)",
|
|
224
|
+
"github.optPrMermaid": "PR Mermaid \uC139\uC158 \uBAA8\uB4DC (auto|on|off, \uAE30\uBCF8: auto)",
|
|
221
225
|
"github.optPrNoSyncTasks": "tasks.md PR URL/PR \uC0C1\uD0DC \uB3D9\uAE30\uD654\uB97C \uAC74\uB108\uB700",
|
|
222
226
|
"github.optPrCommitSync": "tasks.md \uB3D9\uAE30\uD654 \uBCC0\uACBD\uC744 \uC790\uB3D9 commit/push",
|
|
223
227
|
"github.invalidRepoComponentMismatch": "`--repo`\uC640 `--component`\uB97C \uD568\uAED8 \uC4F8 \uB54C\uB294 \uAC19\uC740 \uAC12\uC744 \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.",
|
|
@@ -227,6 +231,12 @@ var I18N = {
|
|
|
227
231
|
"github.ghEmptyJson": "GitHub CLI JSON \uCD9C\uB825\uC774 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.",
|
|
228
232
|
"github.ghInvalidJson": "GitHub CLI JSON \uD30C\uC2F1\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4: {snippet}",
|
|
229
233
|
"github.sectionsMissing": "{kind} \uBCF8\uBB38\uC5D0 \uD544\uC218 \uC139\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4: {sections}",
|
|
234
|
+
"github.todoPlaceholdersRemain": "{kind} \uBCF8\uBB38\uC5D0 TODO \uD56D\uBAA9\uC774 \uB0A8\uC544 \uC788\uC2B5\uB2C8\uB2E4. \uBAA9\uD45C/\uC644\uB8CC \uAE30\uC900 \uB4F1\uC744 \uCC44\uC6B4 \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.",
|
|
235
|
+
"github.artifactModeInvalid": "`--{kind}` \uAC12\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: {value}. \uD5C8\uC6A9\uAC12: auto,on,off",
|
|
236
|
+
"github.prScreenshotsSectionMissing": "PR \uBCF8\uBB38\uC5D0 \uD544\uC218 \uC139\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4: {section}",
|
|
237
|
+
"github.prScreenshotImageMissing": "PR \uBCF8\uBB38\uC758 `{section}` \uC139\uC158\uC5D0 \uC774\uBBF8\uC9C0 \uB9C8\uD06C\uB2E4\uC6B4(``)\uC744 \uCD94\uAC00\uD558\uC138\uC694.",
|
|
238
|
+
"github.prMermaidSectionMissing": "PR \uBCF8\uBB38\uC5D0 \uD544\uC218 \uC139\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4: {section}",
|
|
239
|
+
"github.prMermaidBlockMissing": "PR \uBCF8\uBB38\uC758 `{section}` \uC139\uC158\uC5D0 ```mermaid \uCF54\uB4DC \uBE14\uB85D\uC744 \uCD94\uAC00\uD558\uC138\uC694.",
|
|
230
240
|
"github.docsMissing": "\uAD00\uB828 \uBB38\uC11C \uACBD\uB85C\uAC00 \uC874\uC7AC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: {paths}",
|
|
231
241
|
"github.noFeatures": "Feature\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
232
242
|
"github.multipleFeaturesMatched": "\uC5EC\uB7EC Feature\uAC00 \uB9E4\uCE6D\uB418\uC5C8\uC2B5\uB2C8\uB2E4. feature \uC774\uB984(slug | F001 | F001-slug)\uC744 \uBA85\uC2DC\uD558\uC138\uC694.",
|
|
@@ -383,7 +393,7 @@ var I18N = {
|
|
|
383
393
|
tasksImprove: "tasks.md\uB97C \uBCF4\uC644\uD558\uACE0 \uBB38\uC11C \uC0C1\uD0DC\uB97C Review\uB85C \uBCC0\uACBD\uD558\uC138\uC694.",
|
|
384
394
|
tasksApproval: "tasks.md \uB0B4\uC6A9\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uACF5\uC720\uD558\uACE0 \uC9C4\uD589 \uC2B9\uC778(`A` \uB610\uB294 `A OK` \uD615\uC2DD)\uC744 \uBC1B\uC73C\uC138\uC694. (\uC2B9\uC778 \uD6C4 \uBB38\uC11C \uC0C1\uD0DC\uB97C Approved\uB85C \uBCC0\uACBD)",
|
|
385
395
|
docsCommitPlanning: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(planning): {folderName} \uAE30\uD68D \uBB38\uC11C"',
|
|
386
|
-
issueCreateAndWrite: "`npx lee-spec-kit docs get create-issue --json`\uC73C\uB85C \uC808\uCC28\uB97C \uD655\uC778\uD55C \uB4A4, `npx lee-spec-kit github issue {featureRef} --json`\uC73C\uB85C \uCD08\uC548\uC744 \uC0DD\uC131\uD558\uC138\uC694.
|
|
396
|
+
issueCreateAndWrite: "`npx lee-spec-kit docs get create-issue --json`\uC73C\uB85C \uC808\uCC28\uB97C \uD655\uC778\uD55C \uB4A4, `npx lee-spec-kit github issue {featureRef} --json`\uC73C\uB85C \uCD08\uC548\uC744 \uC0DD\uC131\uD558\uC138\uC694. \uBAA9\uD45C/\uC644\uB8CC \uAE30\uC900\uC744 \uAC80\uD1A0\xB7\uBCF4\uC644\uD558\uACE0 \uC0AC\uC6A9\uC790 \uC2B9\uC778(OK) \uD6C4 `--create --confirm OK`\uB85C \uC0DD\uC131\uD55C \uB2E4\uC74C, spec.md/tasks.md\uC758 \uC774\uC288 \uBC88\uD638\uB97C \uCC44\uC6B0\uACE0 \uBB38\uC11C \uCEE4\uBC0B\uC744 \uC900\uBE44\uD558\uC138\uC694.",
|
|
387
397
|
docsCommitIssueUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(#{issueNumber}): {folderName} \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8"',
|
|
388
398
|
docsCommitUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs: {folderName} \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8"',
|
|
389
399
|
projectCommitIssueUpdate: 'cd "{projectGitCwd}" && git add -A && git commit -m "feat(#{issueNumber}): {folderName} \uAD6C\uD604 \uC5C5\uB370\uC774\uD2B8"',
|
|
@@ -400,7 +410,7 @@ var I18N = {
|
|
|
400
410
|
prePrReviewRun: "PR \uC0DD\uC131 \uC804 \uC0AC\uC804 \uCF54\uB4DC\uB9AC\uBDF0\uB97C \uC9C4\uD589\uD558\uC138\uC694. \uC6B0\uC120\uC21C\uC704 \uC2A4\uD0AC: {skills} (\uC124\uCE58\uB41C \uB354 \uC801\uD569\uD55C \uC2A4\uD0AC\uC774 \uC788\uB2E4\uBA74 \uBA3C\uC800 \uC81C\uC548 \uD6C4 \uC0AC\uC6A9). \uC2A4\uD0AC\uC744 \uC4F8 \uC218 \uC5C6\uC73C\uBA74 `{fallback}` \uC815\uCC45\uC73C\uB85C \uC9C4\uD589\uD558\uACE0 `PR \uC804 \uB9AC\uBDF0`\uB97C Done\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694. Findings \uC815\uCC45: {findingsPolicy}",
|
|
401
411
|
prePrReviewFindingsBlock: "\uC911\uC694 \uC774\uC288\uB294 \uC218\uC815/\uD569\uC758 \uD6C4\uC5D0\uB9CC PR \uC0DD\uC131",
|
|
402
412
|
prePrReviewFindingsWarn: "\uB9AC\uC2A4\uD06C\uB97C \uACF5\uC720\uD558\uBA74 PR \uC0DD\uC131 \uC9C4\uD589 \uAC00\uB2A5",
|
|
403
|
-
prCreate: "`npx lee-spec-kit docs get create-pr --json`\uC73C\uB85C \uC808\uCC28\uB97C \uD655\uC778\uD55C \uB4A4, `npx lee-spec-kit github pr {featureRef} --json`\uC73C\uB85C \uCD08\uC548\uC744 \uC0DD\uC131\uD558\uC138\uC694.
|
|
413
|
+
prCreate: "`npx lee-spec-kit docs get create-pr --json`\uC73C\uB85C \uC808\uCC28\uB97C \uD655\uC778\uD55C \uB4A4, `npx lee-spec-kit github pr {featureRef} --json`\uC73C\uB85C \uCD08\uC548\uC744 \uC0DD\uC131\uD558\uC138\uC694. \uBCC0\uACBD \uC0AC\uD56D/\uD14C\uC2A4\uD2B8 \uC139\uC158\uC744 \uAC80\uD1A0\xB7\uBCF4\uC644\uD558\uACE0 \uC0AC\uC6A9\uC790 \uC2B9\uC778(OK) \uD6C4 `--create --confirm OK`\uB85C \uC0DD\uC131\uD55C \uB2E4\uC74C tasks.md\uC5D0 PR \uB9C1\uD06C\uB97C \uAE30\uB85D\uD558\uC138\uC694.",
|
|
404
414
|
prFillStatus: "tasks.md\uC758 PR \uC0C1\uD0DC\uB97C Review/Approved \uC911 \uD558\uB098\uB85C \uC124\uC815\uD558\uC138\uC694. (merge \uD6C4 Approved\uB85C \uC5C5\uB370\uC774\uD2B8)",
|
|
405
415
|
prResolveReview: "\uB9AC\uBDF0 \uCF54\uBA58\uD2B8\uB97C \uD574\uACB0\uD558\uACE0 PR \uC0C1\uD0DC\uB97C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694. (PR \uC0C1\uD0DC: Review \u2192 Approved)",
|
|
406
416
|
prRequestReview: "\uB9AC\uBDF0\uC5B4\uC5D0\uAC8C \uB9AC\uBDF0\uB97C \uC694\uCCAD\uD558\uACE0 PR \uC0C1\uD0DC\uB97C Review\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.",
|
|
@@ -500,6 +510,8 @@ var I18N = {
|
|
|
500
510
|
"context.checkPolicyHint": "\u2139\uFE0F Check user-approval policy first with `npx lee-spec-kit docs get agents --json` (includes git push/merge and merge commits). If you see [CHECK required], wait for `<label>` or `<label> OK` (e.g. `A`, `A OK`) before proceeding (config: approval can override)",
|
|
501
511
|
"context.actionOptionHint": "Approval reply format: `<label>` or `<label> OK` (e.g. `A`, `A OK`)",
|
|
502
512
|
"context.actionExplainHint": "Before requesting approval, explain what each label will run/change with a one-line summary.",
|
|
513
|
+
"context.finalLabelPrompt": "Available labels now: {labels}. End with a label request in `<label>` or `<label> OK` format. (e.g. {example})",
|
|
514
|
+
"context.finalLabelCommandHint": "When a label is provided, run immediately: {command}",
|
|
503
515
|
"context.readBuiltinDocFirst": "Read built-in docs first: {command}",
|
|
504
516
|
"context.tipDocsCommitRules": "Check commit message rules with `npx lee-spec-kit docs get git-workflow --json`.",
|
|
505
517
|
"context.list.docsCommitNeeded": "Commit docs changes",
|
|
@@ -584,6 +596,8 @@ var I18N = {
|
|
|
584
596
|
"github.optPrMerge": "Merge PR with retry and head-branch refresh",
|
|
585
597
|
"github.optPrConfirm": "Explicit user approval token for remote operations (--create/--merge). Use: OK",
|
|
586
598
|
"github.optPrRetry": "Retry count for merge (default: 3)",
|
|
599
|
+
"github.optPrScreenshots": "PR screenshots section mode (auto|on|off, default: auto)",
|
|
600
|
+
"github.optPrMermaid": "PR Mermaid section mode (auto|on|off, default: auto)",
|
|
587
601
|
"github.optPrNoSyncTasks": "Do not sync PR URL/PR status into tasks.md",
|
|
588
602
|
"github.optPrCommitSync": "Commit and push tasks.md metadata sync automatically",
|
|
589
603
|
"github.invalidRepoComponentMismatch": "`--repo` and `--component` must reference the same value when both are provided.",
|
|
@@ -593,6 +607,12 @@ var I18N = {
|
|
|
593
607
|
"github.ghEmptyJson": "GitHub CLI returned empty JSON output.",
|
|
594
608
|
"github.ghInvalidJson": "GitHub CLI returned invalid JSON: {snippet}",
|
|
595
609
|
"github.sectionsMissing": "{kind} body is missing required sections: {sections}",
|
|
610
|
+
"github.todoPlaceholdersRemain": "{kind} body still contains TODO placeholders. Fill goals/completion criteria before creating remotely.",
|
|
611
|
+
"github.artifactModeInvalid": "Invalid value for `--{kind}`: {value}. Allowed: auto,on,off",
|
|
612
|
+
"github.prScreenshotsSectionMissing": "PR body is missing required section: {section}",
|
|
613
|
+
"github.prScreenshotImageMissing": "Add image markdown (``) to the `{section}` section in PR body.",
|
|
614
|
+
"github.prMermaidSectionMissing": "PR body is missing required section: {section}",
|
|
615
|
+
"github.prMermaidBlockMissing": "Add a ```mermaid code block to the `{section}` section in PR body.",
|
|
596
616
|
"github.docsMissing": "Related document paths do not exist: {paths}",
|
|
597
617
|
"github.noFeatures": "No features found.",
|
|
598
618
|
"github.multipleFeaturesMatched": "Multiple features matched. Specify feature name (slug | F001 | F001-slug).",
|
|
@@ -749,7 +769,7 @@ var I18N = {
|
|
|
749
769
|
tasksImprove: "Improve tasks.md and change Doc Status to Review.",
|
|
750
770
|
tasksApproval: "Share tasks.md with the user and get progress approval (`A` or `A OK` format). (Then set Doc Status to Approved)",
|
|
751
771
|
docsCommitPlanning: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(planning): {folderName} planning docs"',
|
|
752
|
-
issueCreateAndWrite: "Review procedure with `npx lee-spec-kit docs get create-issue --json`, then generate a draft via `npx lee-spec-kit github issue {featureRef} --json`.
|
|
772
|
+
issueCreateAndWrite: "Review procedure with `npx lee-spec-kit docs get create-issue --json`, then generate a draft via `npx lee-spec-kit github issue {featureRef} --json`. Refine goals/completion criteria, get explicit user OK, run `--create --confirm OK`, then update issue number in spec.md/tasks.md and prepare a docs commit.",
|
|
753
773
|
docsCommitIssueUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(#{issueNumber}): {folderName} docs update"',
|
|
754
774
|
docsCommitUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs: {folderName} docs update"',
|
|
755
775
|
projectCommitIssueUpdate: 'cd "{projectGitCwd}" && git add -A && git commit -m "feat(#{issueNumber}): {folderName} implementation update"',
|
|
@@ -766,7 +786,7 @@ var I18N = {
|
|
|
766
786
|
prePrReviewRun: "Run a pre-PR code review before creating the PR. Preferred skills: {skills} (if a better installed skill fits this change, propose it first). If no skill can run, use `{fallback}` and set `Pre-PR Review` to Done in tasks.md. Findings policy: {findingsPolicy}",
|
|
767
787
|
prePrReviewFindingsBlock: "major findings must be fixed/aligned before PR creation",
|
|
768
788
|
prePrReviewFindingsWarn: "you may proceed after sharing the risks",
|
|
769
|
-
prCreate: "Review procedure with `npx lee-spec-kit docs get create-pr --json`, then generate a draft via `npx lee-spec-kit github pr {featureRef} --json`.
|
|
789
|
+
prCreate: "Review procedure with `npx lee-spec-kit docs get create-pr --json`, then generate a draft via `npx lee-spec-kit github pr {featureRef} --json`. Refine changes/tests sections, get explicit user OK, run `--create --confirm OK`, then record the PR link in tasks.md.",
|
|
770
790
|
prFillStatus: "Set PR Status in tasks.md to Review/Approved. (After merge, update it to Approved.)",
|
|
771
791
|
prResolveReview: "Resolve review comments and update PR Status. (PR Status: Review \u2192 Approved)",
|
|
772
792
|
prRequestReview: "Request review and update PR Status to Review.",
|
|
@@ -4673,6 +4693,25 @@ function listLabels(actionOptions) {
|
|
|
4673
4693
|
if (actionOptions.length === 0) return "-";
|
|
4674
4694
|
return actionOptions.map((o) => o.label).join(", ");
|
|
4675
4695
|
}
|
|
4696
|
+
function resolveFeatureRefForApproval(state, featureName) {
|
|
4697
|
+
const raw = featureName?.trim() || state.matchedFeature?.folderName || "<slug|F001|F001-slug>";
|
|
4698
|
+
return raw;
|
|
4699
|
+
}
|
|
4700
|
+
function buildApprovalCommand(state, featureName, selectedComponent, execute) {
|
|
4701
|
+
const featureRef = resolveFeatureRefForApproval(state, featureName);
|
|
4702
|
+
const componentArg = selectedComponent ? ` --component ${selectedComponent}` : "";
|
|
4703
|
+
const executeArg = execute ? " --execute" : "";
|
|
4704
|
+
return `npx lee-spec-kit context ${featureRef}${componentArg} --approve <LABEL>${executeArg}`;
|
|
4705
|
+
}
|
|
4706
|
+
function buildFinalApprovalPrompt(lang, actionOptions) {
|
|
4707
|
+
if (actionOptions.length === 0) return "";
|
|
4708
|
+
const labels = listLabels(actionOptions);
|
|
4709
|
+
const example = actionOptions[0]?.label || "A";
|
|
4710
|
+
return tr(lang, "cli", "context.finalLabelPrompt", {
|
|
4711
|
+
labels,
|
|
4712
|
+
example
|
|
4713
|
+
});
|
|
4714
|
+
}
|
|
4676
4715
|
function formatActionSummary2(action) {
|
|
4677
4716
|
if (action.type === "command") {
|
|
4678
4717
|
return `(${action.scope}) ${action.cmd}`;
|
|
@@ -4848,6 +4887,19 @@ async function runContext(featureName, options) {
|
|
|
4848
4887
|
}
|
|
4849
4888
|
if (options.json) {
|
|
4850
4889
|
const primaryAction = state.actionOptions[0] ?? null;
|
|
4890
|
+
const finalApprovalPrompt = buildFinalApprovalPrompt(lang, state.actionOptions);
|
|
4891
|
+
const approveCommand = buildApprovalCommand(
|
|
4892
|
+
state,
|
|
4893
|
+
featureName,
|
|
4894
|
+
selectedComponent,
|
|
4895
|
+
false
|
|
4896
|
+
);
|
|
4897
|
+
const executeCommand = buildApprovalCommand(
|
|
4898
|
+
state,
|
|
4899
|
+
featureName,
|
|
4900
|
+
selectedComponent,
|
|
4901
|
+
true
|
|
4902
|
+
);
|
|
4851
4903
|
const result = {
|
|
4852
4904
|
status: state.status,
|
|
4853
4905
|
reasonCode: toReasonCode(state.status),
|
|
@@ -4888,6 +4940,10 @@ async function runContext(featureName, options) {
|
|
|
4888
4940
|
},
|
|
4889
4941
|
approvalRequest: {
|
|
4890
4942
|
guidance: "Present each label with summary (e.g. `A: <summary>`) before asking for approval.",
|
|
4943
|
+
finalPrompt: finalApprovalPrompt,
|
|
4944
|
+
labels: state.actionOptions.map((o) => o.label),
|
|
4945
|
+
approveCommand,
|
|
4946
|
+
executeCommand,
|
|
4891
4947
|
options: state.actionOptions.map((o) => ({
|
|
4892
4948
|
label: o.label,
|
|
4893
4949
|
summary: o.summary,
|
|
@@ -5151,6 +5207,23 @@ async function runContext(featureName, options) {
|
|
|
5151
5207
|
);
|
|
5152
5208
|
}
|
|
5153
5209
|
}
|
|
5210
|
+
if (actionOptions.length > 0) {
|
|
5211
|
+
const finalApprovalPrompt = buildFinalApprovalPrompt(lang, actionOptions);
|
|
5212
|
+
const executeCommand = buildApprovalCommand(
|
|
5213
|
+
state,
|
|
5214
|
+
featureName,
|
|
5215
|
+
selectedComponent,
|
|
5216
|
+
true
|
|
5217
|
+
);
|
|
5218
|
+
console.log(chalk6.cyan(` \u21B3 ${finalApprovalPrompt}`));
|
|
5219
|
+
console.log(
|
|
5220
|
+
chalk6.gray(
|
|
5221
|
+
` \u21B3 ${tr(lang, "cli", "context.finalLabelCommandHint", {
|
|
5222
|
+
command: executeCommand
|
|
5223
|
+
})}`
|
|
5224
|
+
)
|
|
5225
|
+
);
|
|
5226
|
+
}
|
|
5154
5227
|
console.log();
|
|
5155
5228
|
}
|
|
5156
5229
|
async function runApprovedOption(state, config, lang, featureName, selectionOptions, options) {
|
|
@@ -6245,6 +6318,70 @@ async function runFlow(featureName, options) {
|
|
|
6245
6318
|
console.log(chalk6.gray("Tip: add --approve <LABEL> [--execute] to run the selected atomic action."));
|
|
6246
6319
|
console.log();
|
|
6247
6320
|
}
|
|
6321
|
+
|
|
6322
|
+
// src/utils/github-draft-contract.ts
|
|
6323
|
+
var ISSUE_CONTRACT = {
|
|
6324
|
+
kind: "issue",
|
|
6325
|
+
requiredSections: {
|
|
6326
|
+
ko: ["\uAC1C\uC694", "\uBAA9\uD45C", "\uC644\uB8CC \uAE30\uC900", "\uAD00\uB828 \uBB38\uC11C", "\uB77C\uBCA8"],
|
|
6327
|
+
en: ["Overview", "Goals", "Completion Criteria", "Related Documents", "Labels"]
|
|
6328
|
+
},
|
|
6329
|
+
artifacts: []
|
|
6330
|
+
};
|
|
6331
|
+
var PR_CONTRACT = {
|
|
6332
|
+
kind: "pr",
|
|
6333
|
+
requiredSections: {
|
|
6334
|
+
ko: ["\uAC1C\uC694", "\uBCC0\uACBD \uC0AC\uD56D", "\uD14C\uC2A4\uD2B8", "\uAD00\uB828 \uBB38\uC11C"],
|
|
6335
|
+
en: ["Overview", "Changes", "Tests", "Related Documents"]
|
|
6336
|
+
},
|
|
6337
|
+
artifacts: [
|
|
6338
|
+
{
|
|
6339
|
+
id: "screenshots",
|
|
6340
|
+
headings: { ko: "\uC2A4\uD06C\uB9B0\uC0F7", en: "Screenshots" },
|
|
6341
|
+
bodyRule: "image-markdown"
|
|
6342
|
+
},
|
|
6343
|
+
{
|
|
6344
|
+
id: "mermaid",
|
|
6345
|
+
headings: { ko: "\uC544\uD0A4\uD14D\uCC98 \uB2E4\uC774\uC5B4\uADF8\uB7A8", en: "Architecture Diagram" },
|
|
6346
|
+
bodyRule: "mermaid-fence"
|
|
6347
|
+
}
|
|
6348
|
+
]
|
|
6349
|
+
};
|
|
6350
|
+
var CONTRACTS = {
|
|
6351
|
+
issue: ISSUE_CONTRACT,
|
|
6352
|
+
pr: PR_CONTRACT
|
|
6353
|
+
};
|
|
6354
|
+
function getGithubDraftRequiredSections(kind, lang) {
|
|
6355
|
+
return [...CONTRACTS[kind].requiredSections[lang]];
|
|
6356
|
+
}
|
|
6357
|
+
function getGithubDraftArtifactHeading(kind, artifactId, lang) {
|
|
6358
|
+
const artifact = CONTRACTS[kind].artifacts.find((item) => item.id === artifactId);
|
|
6359
|
+
if (!artifact) return null;
|
|
6360
|
+
return artifact.headings[lang];
|
|
6361
|
+
}
|
|
6362
|
+
function getGithubDraftContractView(kind, lang) {
|
|
6363
|
+
const definition = CONTRACTS[kind];
|
|
6364
|
+
return {
|
|
6365
|
+
kind: definition.kind,
|
|
6366
|
+
requiredSections: [...definition.requiredSections[lang]],
|
|
6367
|
+
artifacts: definition.artifacts.map((artifact) => ({
|
|
6368
|
+
id: artifact.id,
|
|
6369
|
+
section: artifact.headings[lang],
|
|
6370
|
+
bodyRule: artifact.bodyRule
|
|
6371
|
+
}))
|
|
6372
|
+
};
|
|
6373
|
+
}
|
|
6374
|
+
function getGithubDraftContractForBuiltinDoc(docId, lang) {
|
|
6375
|
+
if (docId === "create-issue" || docId === "issue-template") {
|
|
6376
|
+
return getGithubDraftContractView("issue", lang);
|
|
6377
|
+
}
|
|
6378
|
+
if (docId === "create-pr" || docId === "pr-template") {
|
|
6379
|
+
return getGithubDraftContractView("pr", lang);
|
|
6380
|
+
}
|
|
6381
|
+
return null;
|
|
6382
|
+
}
|
|
6383
|
+
|
|
6384
|
+
// src/commands/github.ts
|
|
6248
6385
|
function tg(lang, key, vars = {}) {
|
|
6249
6386
|
return tr(lang, "cli", `github.${key}`, vars);
|
|
6250
6387
|
}
|
|
@@ -6402,6 +6539,55 @@ function toBodyFilePath(raw, kind, docsDir, component) {
|
|
|
6402
6539
|
const selected = raw?.trim() || path17.join(os.tmpdir(), buildDefaultBodyFileName(kind, docsDir, component));
|
|
6403
6540
|
return path17.resolve(selected);
|
|
6404
6541
|
}
|
|
6542
|
+
function toProjectRootDocsPath(relativePathFromDocs) {
|
|
6543
|
+
const normalized = relativePathFromDocs.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
6544
|
+
if (normalized.startsWith("docs/")) return normalized;
|
|
6545
|
+
return `docs/${normalized}`;
|
|
6546
|
+
}
|
|
6547
|
+
function toBodyDocPaths(paths) {
|
|
6548
|
+
return {
|
|
6549
|
+
...paths,
|
|
6550
|
+
specPath: toProjectRootDocsPath(paths.specPath),
|
|
6551
|
+
planPath: toProjectRootDocsPath(paths.planPath),
|
|
6552
|
+
tasksPath: toProjectRootDocsPath(paths.tasksPath)
|
|
6553
|
+
};
|
|
6554
|
+
}
|
|
6555
|
+
var TODO_PLACEHOLDER_PATTERN = /(^|\n)\s*-\s*\[[ xX]\]\s*TODO:/m;
|
|
6556
|
+
function ensureNoTodoPlaceholders(body, kind, lang) {
|
|
6557
|
+
if (!TODO_PLACEHOLDER_PATTERN.test(body)) return;
|
|
6558
|
+
throw createCliError(
|
|
6559
|
+
"PRECONDITION_FAILED",
|
|
6560
|
+
tg(lang, "todoPlaceholdersRemain", { kind })
|
|
6561
|
+
);
|
|
6562
|
+
}
|
|
6563
|
+
function parsePrArtifactMode(raw, kind, lang) {
|
|
6564
|
+
const value = (raw || "auto").trim().toLowerCase();
|
|
6565
|
+
if (value === "auto" || value === "on" || value === "off") {
|
|
6566
|
+
return value;
|
|
6567
|
+
}
|
|
6568
|
+
throw createCliError(
|
|
6569
|
+
"INVALID_ARGUMENT",
|
|
6570
|
+
tg(lang, "artifactModeInvalid", { kind, value })
|
|
6571
|
+
);
|
|
6572
|
+
}
|
|
6573
|
+
function resolvePrArtifactPolicy(config, feature, options) {
|
|
6574
|
+
const screenshotsMode = parsePrArtifactMode(
|
|
6575
|
+
options.screenshots,
|
|
6576
|
+
"screenshots",
|
|
6577
|
+
config.lang
|
|
6578
|
+
);
|
|
6579
|
+
const mermaidMode = parsePrArtifactMode(
|
|
6580
|
+
options.mermaid,
|
|
6581
|
+
"mermaid",
|
|
6582
|
+
config.lang
|
|
6583
|
+
);
|
|
6584
|
+
const includeScreenshots = screenshotsMode === "on" ? true : screenshotsMode === "off" ? false : config.pr?.screenshots?.upload ?? false;
|
|
6585
|
+
const includeMermaid = mermaidMode === "on" ? true : mermaidMode === "off" ? false : feature.type === "be";
|
|
6586
|
+
return {
|
|
6587
|
+
includeScreenshots,
|
|
6588
|
+
includeMermaid
|
|
6589
|
+
};
|
|
6590
|
+
}
|
|
6405
6591
|
async function resolveFeatureOrThrow(featureName, options, lang) {
|
|
6406
6592
|
const config = await getConfig(process.cwd());
|
|
6407
6593
|
if (!config) {
|
|
@@ -6440,27 +6626,38 @@ function getFeatureDocPaths(feature) {
|
|
|
6440
6626
|
function normalizeHeading(value) {
|
|
6441
6627
|
return value.trim().replace(/\s+/g, " ").toLowerCase();
|
|
6442
6628
|
}
|
|
6443
|
-
function
|
|
6629
|
+
function extractMarkdownByHeadings(content, headings, levels) {
|
|
6444
6630
|
const targets = new Set(headings.map((heading) => normalizeHeading(heading)));
|
|
6445
6631
|
const lines = content.split("\n");
|
|
6446
6632
|
let start = -1;
|
|
6633
|
+
let startLevel = 0;
|
|
6634
|
+
const levelSet = new Set(levels);
|
|
6447
6635
|
for (let i = 0; i < lines.length; i++) {
|
|
6448
|
-
const match = lines[i].match(/^\s
|
|
6636
|
+
const match = lines[i].match(/^\s*(#{2,6})\s+(.+?)\s*$/);
|
|
6449
6637
|
if (!match) continue;
|
|
6450
|
-
|
|
6638
|
+
const level = match[1].length;
|
|
6639
|
+
if (!levelSet.has(level)) continue;
|
|
6640
|
+
if (!targets.has(normalizeHeading(match[2]))) continue;
|
|
6451
6641
|
start = i + 1;
|
|
6642
|
+
startLevel = level;
|
|
6452
6643
|
break;
|
|
6453
6644
|
}
|
|
6454
6645
|
if (start < 0) return void 0;
|
|
6455
6646
|
let end = lines.length;
|
|
6456
6647
|
for (let i = start; i < lines.length; i++) {
|
|
6457
|
-
|
|
6648
|
+
const heading = lines[i].match(/^\s*(#{2,6})\s+(.+?)\s*$/);
|
|
6649
|
+
if (!heading) continue;
|
|
6650
|
+
const level = heading[1].length;
|
|
6651
|
+
if (level <= startLevel) {
|
|
6458
6652
|
end = i;
|
|
6459
6653
|
break;
|
|
6460
6654
|
}
|
|
6461
6655
|
}
|
|
6462
6656
|
return lines.slice(start, end).join("\n");
|
|
6463
6657
|
}
|
|
6658
|
+
function extractMarkdownSection(content, headings) {
|
|
6659
|
+
return extractMarkdownByHeadings(content, headings, [2]);
|
|
6660
|
+
}
|
|
6464
6661
|
function isTemplateLine(line) {
|
|
6465
6662
|
const trimmed = line.trim();
|
|
6466
6663
|
if (!trimmed) return true;
|
|
@@ -6476,6 +6673,337 @@ function sanitizeOverviewSection(raw) {
|
|
|
6476
6673
|
if (lines.length === 0) return void 0;
|
|
6477
6674
|
return lines.slice(0, 5).join("\n");
|
|
6478
6675
|
}
|
|
6676
|
+
function sanitizeDraftItem(raw) {
|
|
6677
|
+
const trimmed = raw.replace(/^\s*-\s*\[[ xX]\]\s*/, "").replace(/^\s*-\s+/, "").replace(/^\s*###\s+/, "").trim();
|
|
6678
|
+
const plain = trimmed.replace(/\*\*/g, "").trim();
|
|
6679
|
+
if (!plain) return void 0;
|
|
6680
|
+
if (isTemplateLine(plain)) return void 0;
|
|
6681
|
+
if (/^todo:/i.test(plain)) return void 0;
|
|
6682
|
+
if (/\{[^}]*\}/.test(plain)) return void 0;
|
|
6683
|
+
if (/^(as a|i want|so that)\b/i.test(plain)) return void 0;
|
|
6684
|
+
if (/^acceptance criteria:?$/i.test(plain)) return void 0;
|
|
6685
|
+
return plain.replace(/\s+/g, " ");
|
|
6686
|
+
}
|
|
6687
|
+
function uniqItems(items) {
|
|
6688
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6689
|
+
const ordered = [];
|
|
6690
|
+
for (const item of items) {
|
|
6691
|
+
const normalized = item.trim().toLowerCase();
|
|
6692
|
+
if (!normalized || seen.has(normalized)) continue;
|
|
6693
|
+
seen.add(normalized);
|
|
6694
|
+
ordered.push(item.trim());
|
|
6695
|
+
}
|
|
6696
|
+
return ordered;
|
|
6697
|
+
}
|
|
6698
|
+
function normalizeSemanticKey(value) {
|
|
6699
|
+
return value.toLowerCase().replace(/[`'"(){}\[\].,:;!?/\\\-_|]/g, " ").replace(/\s+/g, " ").trim().replace(/\s/g, "");
|
|
6700
|
+
}
|
|
6701
|
+
function uniqItemsByContainment(items) {
|
|
6702
|
+
const kept = [];
|
|
6703
|
+
const keys = [];
|
|
6704
|
+
for (const item of items) {
|
|
6705
|
+
const clean = item.trim();
|
|
6706
|
+
if (!clean) continue;
|
|
6707
|
+
const key = normalizeSemanticKey(clean);
|
|
6708
|
+
if (!key) continue;
|
|
6709
|
+
let replaced = false;
|
|
6710
|
+
for (let i = 0; i < keys.length; i++) {
|
|
6711
|
+
const current = keys[i];
|
|
6712
|
+
if (!current.includes(key) && !key.includes(current)) continue;
|
|
6713
|
+
if (key.length > current.length) {
|
|
6714
|
+
keys[i] = key;
|
|
6715
|
+
kept[i] = clean;
|
|
6716
|
+
}
|
|
6717
|
+
replaced = true;
|
|
6718
|
+
break;
|
|
6719
|
+
}
|
|
6720
|
+
if (!replaced) {
|
|
6721
|
+
keys.push(key);
|
|
6722
|
+
kept.push(clean);
|
|
6723
|
+
}
|
|
6724
|
+
}
|
|
6725
|
+
return kept;
|
|
6726
|
+
}
|
|
6727
|
+
function extractSectionLines(raw) {
|
|
6728
|
+
if (!raw) return [];
|
|
6729
|
+
return uniqItems(
|
|
6730
|
+
raw.split("\n").map((line) => sanitizeDraftItem(line)).filter((line) => !!line)
|
|
6731
|
+
);
|
|
6732
|
+
}
|
|
6733
|
+
function extractSectionHeadings(raw) {
|
|
6734
|
+
if (!raw) return [];
|
|
6735
|
+
return uniqItems(
|
|
6736
|
+
raw.split("\n").map((line) => line.match(/^\s*###\s+(.+?)\s*$/)?.[1] || "").map((line) => sanitizeDraftItem(line)).filter((line) => !!line).map((line) => line.replace(/^FR-\d+:\s*/i, "").trim())
|
|
6737
|
+
);
|
|
6738
|
+
}
|
|
6739
|
+
function toChecklistLines(items) {
|
|
6740
|
+
return items.map((item) => `- [ ] ${item}`).join("\n");
|
|
6741
|
+
}
|
|
6742
|
+
function extractChecklistItems(raw) {
|
|
6743
|
+
if (!raw) return [];
|
|
6744
|
+
return uniqItems(
|
|
6745
|
+
raw.split("\n").map((line) => {
|
|
6746
|
+
const match = line.match(/^\s*-\s*\[[ xX]\]\s+(.+?)\s*$/);
|
|
6747
|
+
if (!match) return void 0;
|
|
6748
|
+
return sanitizeDraftItem(match[1]);
|
|
6749
|
+
}).filter((line) => !!line)
|
|
6750
|
+
);
|
|
6751
|
+
}
|
|
6752
|
+
function extractTaskTitles(tasksContent) {
|
|
6753
|
+
return uniqItems(
|
|
6754
|
+
tasksContent.split("\n").map((line) => {
|
|
6755
|
+
const match = line.match(
|
|
6756
|
+
/^\s*-\s*\[(?:TODO|DOING|DONE)\][^\n]*?\s+(?:T-[A-Z0-9-]+\s+)?(.+?)\s*$/
|
|
6757
|
+
);
|
|
6758
|
+
if (!match) return void 0;
|
|
6759
|
+
return sanitizeDraftItem(match[1]);
|
|
6760
|
+
}).filter((line) => !!line)
|
|
6761
|
+
);
|
|
6762
|
+
}
|
|
6763
|
+
function extractTasksAcceptanceItems(tasksContent) {
|
|
6764
|
+
const lines = tasksContent.split("\n");
|
|
6765
|
+
const accepted = [];
|
|
6766
|
+
let inAcceptance = false;
|
|
6767
|
+
for (const line of lines) {
|
|
6768
|
+
if (/^\s*-\s*Acceptance\s*:\s*$/i.test(line)) {
|
|
6769
|
+
inAcceptance = true;
|
|
6770
|
+
continue;
|
|
6771
|
+
}
|
|
6772
|
+
if (inAcceptance && /^\s*-\s*Checklist\s*:\s*$/i.test(line)) {
|
|
6773
|
+
inAcceptance = false;
|
|
6774
|
+
continue;
|
|
6775
|
+
}
|
|
6776
|
+
if (!inAcceptance) continue;
|
|
6777
|
+
const match = line.match(/^\s*-\s*\[[ xX]\]\s+(.+?)\s*$/) || line.match(/^\s*-\s+(.+?)\s*$/);
|
|
6778
|
+
if (!match) continue;
|
|
6779
|
+
const item = sanitizeDraftItem(match[1]);
|
|
6780
|
+
if (!item) continue;
|
|
6781
|
+
accepted.push(item);
|
|
6782
|
+
}
|
|
6783
|
+
return uniqItems(accepted);
|
|
6784
|
+
}
|
|
6785
|
+
function extractScopeItemsFromPlan(planContent, lang) {
|
|
6786
|
+
const section = extractMarkdownSection(
|
|
6787
|
+
planContent,
|
|
6788
|
+
["\uBC94\uC704(\uBA85\uD655\uD654)", "\uBC94\uC704", "Scope", "Scope Clarification"]
|
|
6789
|
+
);
|
|
6790
|
+
if (!section) return { include: [], exclude: [] };
|
|
6791
|
+
const lines = section.split("\n");
|
|
6792
|
+
const include = [];
|
|
6793
|
+
const exclude = [];
|
|
6794
|
+
let mode = null;
|
|
6795
|
+
const includePatterns = lang === "ko" ? [/포함/] : [/in\s*scope/i, /^include$/i, /included/i];
|
|
6796
|
+
const excludePatterns = lang === "ko" ? [/비포함/, /제외/] : [/out\s*of\s*scope/i, /^exclude$/i, /excluded/i];
|
|
6797
|
+
for (const line of lines) {
|
|
6798
|
+
const plain = line.replace(/\*\*/g, "").trim();
|
|
6799
|
+
if (!plain) continue;
|
|
6800
|
+
if (excludePatterns.some((re) => re.test(plain))) {
|
|
6801
|
+
mode = "exclude";
|
|
6802
|
+
continue;
|
|
6803
|
+
}
|
|
6804
|
+
if (includePatterns.some((re) => re.test(plain))) {
|
|
6805
|
+
mode = "include";
|
|
6806
|
+
continue;
|
|
6807
|
+
}
|
|
6808
|
+
const bullet = line.match(/^\s*-\s+(.+?)\s*$/)?.[1];
|
|
6809
|
+
if (!bullet) continue;
|
|
6810
|
+
const item = sanitizeDraftItem(bullet);
|
|
6811
|
+
if (!item) continue;
|
|
6812
|
+
if (mode === "include") {
|
|
6813
|
+
include.push(item);
|
|
6814
|
+
} else if (mode === "exclude") {
|
|
6815
|
+
exclude.push(item);
|
|
6816
|
+
}
|
|
6817
|
+
}
|
|
6818
|
+
return {
|
|
6819
|
+
include: uniqItems(include),
|
|
6820
|
+
exclude: uniqItems(exclude)
|
|
6821
|
+
};
|
|
6822
|
+
}
|
|
6823
|
+
function getIssueGoalsAndCriteria(specContent, planContent, tasksContent, overview, lang) {
|
|
6824
|
+
const purposeLines = extractSectionLines(
|
|
6825
|
+
extractMarkdownSection(specContent, ["\uBAA9\uC801", "Purpose"])
|
|
6826
|
+
);
|
|
6827
|
+
const requirementHeadings = extractSectionHeadings(
|
|
6828
|
+
extractMarkdownSection(specContent, ["\uAE30\uB2A5 \uC694\uAD6C\uC0AC\uD56D", "Functional Requirements"])
|
|
6829
|
+
);
|
|
6830
|
+
const userStoryChecklist = extractChecklistItems(
|
|
6831
|
+
extractMarkdownSection(specContent, ["\uC0AC\uC6A9\uC790 \uC2A4\uD1A0\uB9AC", "User Stories"])
|
|
6832
|
+
);
|
|
6833
|
+
const tasksAcceptance = extractTasksAcceptanceItems(tasksContent);
|
|
6834
|
+
const scopeFromPlan = extractScopeItemsFromPlan(planContent, lang);
|
|
6835
|
+
const taskTitles = extractTaskTitles(tasksContent);
|
|
6836
|
+
const goals = uniqItemsByContainment(uniqItems([
|
|
6837
|
+
...requirementHeadings,
|
|
6838
|
+
...scopeFromPlan.include,
|
|
6839
|
+
...purposeLines.slice(0, 1),
|
|
6840
|
+
sanitizeDraftItem(overview) || ""
|
|
6841
|
+
])).slice(0, 5);
|
|
6842
|
+
while (goals.length < 3) {
|
|
6843
|
+
goals.push(
|
|
6844
|
+
lang === "ko" ? goals.length === 0 ? "spec.md \uBAA9\uC801\uC5D0 \uB9DE\uCDB0 \uAD6C\uD604 \uBC94\uC704\uB97C \uD655\uC815\uD55C\uB2E4." : goals.length === 1 ? "\uD3EC\uD568/\uC81C\uC678 \uBC94\uC704\uB97C \uBA85\uD655\uD788 \uC815\uC758\uD558\uACE0 \uBB38\uC11C\uC640 \uAD6C\uD604\uC744 \uC77C\uCE58\uC2DC\uD0A8\uB2E4." : "\uAD00\uB828 \uD14C\uC2A4\uD2B8 \uBC0F \uAC80\uC99D \uACBD\uB85C\uB97C \uD3EC\uD568\uD574 \uAE30\uB2A5 \uC644\uC131\uB3C4\uB97C \uD655\uBCF4\uD55C\uB2E4." : goals.length === 0 ? "Define implementation scope aligned with spec.md purpose." : goals.length === 1 ? "Clarify in-scope and out-of-scope boundaries and keep docs/code aligned." : "Ensure feature completeness with concrete validation paths."
|
|
6845
|
+
);
|
|
6846
|
+
}
|
|
6847
|
+
const criteria = uniqItemsByContainment(
|
|
6848
|
+
uniqItems([...userStoryChecklist, ...tasksAcceptance])
|
|
6849
|
+
).slice(0, 6);
|
|
6850
|
+
while (criteria.length < 4) {
|
|
6851
|
+
criteria.push(
|
|
6852
|
+
lang === "ko" ? criteria.length === 0 ? "\uD575\uC2EC \uC0AC\uC6A9\uC790 \uC2DC\uB098\uB9AC\uC624\uC5D0\uC11C \uBAA9\uD45C \uB3D9\uC791\uC774 \uC7AC\uD604\uB41C\uB2E4." : criteria.length === 1 ? "\uC694\uAD6C\uC0AC\uD56D\uBCC4 \uC218\uC6A9 \uAE30\uC900\uC774 \uBAA8\uB450 \uCDA9\uC871\uB41C\uB2E4." : criteria.length === 2 ? "\uAC80\uC99D \uBC29\uBC95(\uD14C\uC2A4\uD2B8/\uC218\uB3D9 \uC2DC\uB098\uB9AC\uC624)\uC744 \uAE30\uB85D\uD574 \uC644\uB8CC\uB97C \uD655\uC778\uD560 \uC218 \uC788\uB2E4." : "\uD68C\uADC0 \uC5C6\uC774 \uAE30\uC874 \uB3D9\uC791\uACFC\uC758 \uD638\uD658\uC131\uC744 \uC720\uC9C0\uD55C\uB2E4." : criteria.length === 0 ? "Core user scenarios reproduce expected behavior." : criteria.length === 1 ? "All requirement-level acceptance criteria are satisfied." : criteria.length === 2 ? "Validation method (tests/manual scenarios) is documented for completion checks." : "Compatibility is preserved without regressions."
|
|
6853
|
+
);
|
|
6854
|
+
}
|
|
6855
|
+
const scope = uniqItemsByContainment(
|
|
6856
|
+
uniqItems([...scopeFromPlan.include, ...taskTitles])
|
|
6857
|
+
).slice(0, 6);
|
|
6858
|
+
return {
|
|
6859
|
+
goals: goals.slice(0, 5),
|
|
6860
|
+
criteria: criteria.slice(0, 6),
|
|
6861
|
+
scope
|
|
6862
|
+
};
|
|
6863
|
+
}
|
|
6864
|
+
function extractPlanChangeTargets(planContent, lang) {
|
|
6865
|
+
const section = extractMarkdownSection(
|
|
6866
|
+
planContent,
|
|
6867
|
+
[
|
|
6868
|
+
"\uBCC0\uACBD \uB300\uC0C1(\uC608\uC0C1)",
|
|
6869
|
+
"\uBCC0\uACBD \uB300\uC0C1",
|
|
6870
|
+
"Changed Files",
|
|
6871
|
+
"Change Targets",
|
|
6872
|
+
"Expected Changes"
|
|
6873
|
+
]
|
|
6874
|
+
);
|
|
6875
|
+
if (!section) return [];
|
|
6876
|
+
const lines = section.split("\n");
|
|
6877
|
+
const out = [];
|
|
6878
|
+
let inCode = false;
|
|
6879
|
+
for (const line of lines) {
|
|
6880
|
+
if (/^\s*```/.test(line)) {
|
|
6881
|
+
inCode = !inCode;
|
|
6882
|
+
continue;
|
|
6883
|
+
}
|
|
6884
|
+
if (inCode) {
|
|
6885
|
+
const trimmed = line.trim();
|
|
6886
|
+
if (!trimmed) continue;
|
|
6887
|
+
if (!/[\\/]/.test(trimmed)) continue;
|
|
6888
|
+
out.push(
|
|
6889
|
+
lang === "ko" ? `\`${trimmed}\` \uBCC0\uACBD` : `Update \`${trimmed}\``
|
|
6890
|
+
);
|
|
6891
|
+
continue;
|
|
6892
|
+
}
|
|
6893
|
+
const bullet = line.match(/^\s*-\s+(.+?)\s*$/)?.[1];
|
|
6894
|
+
if (!bullet) continue;
|
|
6895
|
+
const item = sanitizeDraftItem(bullet);
|
|
6896
|
+
if (!item) continue;
|
|
6897
|
+
out.push(item);
|
|
6898
|
+
}
|
|
6899
|
+
return uniqItemsByContainment(uniqItems(out));
|
|
6900
|
+
}
|
|
6901
|
+
function extractCommandsFromSection(raw) {
|
|
6902
|
+
if (!raw) return [];
|
|
6903
|
+
const commands = [];
|
|
6904
|
+
for (const match of raw.matchAll(/`([^`]+)`/g)) {
|
|
6905
|
+
const candidate = match[1].trim();
|
|
6906
|
+
if (!candidate) continue;
|
|
6907
|
+
if (/\{[^}]*\}/.test(candidate)) continue;
|
|
6908
|
+
if (!/\b(pnpm|npm|yarn|bun|vitest|jest|tsx?|node)\b/.test(candidate)) continue;
|
|
6909
|
+
commands.push(candidate);
|
|
6910
|
+
}
|
|
6911
|
+
for (const line of raw.split("\n")) {
|
|
6912
|
+
const trimmed = line.trim().replace(/^-+\s*/, "");
|
|
6913
|
+
if (!trimmed) continue;
|
|
6914
|
+
if (!/\b(pnpm|npm|yarn|bun|vitest|jest|tsx?|node)\b/.test(trimmed)) continue;
|
|
6915
|
+
if (/\{[^}]*\}/.test(trimmed)) continue;
|
|
6916
|
+
commands.push(trimmed);
|
|
6917
|
+
}
|
|
6918
|
+
return uniqItems(commands);
|
|
6919
|
+
}
|
|
6920
|
+
function extractRecordedTestLines(tasksContent, planContent, lang) {
|
|
6921
|
+
const section = extractMarkdownByHeadings(
|
|
6922
|
+
tasksContent,
|
|
6923
|
+
["\uD14C\uC2A4\uD2B8 \uC2E4\uD589 \uAE30\uB85D", "Tests Run", "Test Run Log", "Test Execution Log"],
|
|
6924
|
+
[3, 2]
|
|
6925
|
+
);
|
|
6926
|
+
const records = [];
|
|
6927
|
+
if (section) {
|
|
6928
|
+
for (const line of section.split("\n")) {
|
|
6929
|
+
if (!line.trim().startsWith("|")) continue;
|
|
6930
|
+
const cells = line.split("|").slice(1, -1).map((cell) => cell.trim().replace(/`/g, ""));
|
|
6931
|
+
if (cells.length < 3) continue;
|
|
6932
|
+
const [cmd, time, result] = cells;
|
|
6933
|
+
if (!cmd || /^명령어$/i.test(cmd) || /^command$/i.test(cmd) || /^-+$/.test(cmd)) continue;
|
|
6934
|
+
if (/\{[^}]*\}/.test(cmd) || /\{[^}]*\}/.test(result || "")) continue;
|
|
6935
|
+
const renderedResult = result || (lang === "ko" ? "\uBBF8\uAE30\uB85D" : "not recorded");
|
|
6936
|
+
const renderedTime = time && time !== "-" ? ` (${time})` : "";
|
|
6937
|
+
records.push(`\`${cmd}\` \u2014 ${renderedResult}${renderedTime}`);
|
|
6938
|
+
}
|
|
6939
|
+
const lines = section.split("\n");
|
|
6940
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6941
|
+
const commandMatch = lines[i].match(/^\s*-\s*(?:명령어|Command)\s*:\s*`?([^`]+?)`?\s*$/i);
|
|
6942
|
+
if (!commandMatch) continue;
|
|
6943
|
+
const command = commandMatch[1].trim();
|
|
6944
|
+
if (!command || /\{[^}]*\}/.test(command)) continue;
|
|
6945
|
+
let result = "";
|
|
6946
|
+
for (let j = i + 1; j < Math.min(lines.length, i + 5); j++) {
|
|
6947
|
+
const resultMatch = lines[j].match(/^\s*-\s*(?:결과|Result)\s*:\s*(.+?)\s*$/i);
|
|
6948
|
+
if (!resultMatch) continue;
|
|
6949
|
+
result = resultMatch[1].replace(/`/g, "").trim();
|
|
6950
|
+
break;
|
|
6951
|
+
}
|
|
6952
|
+
if (!result || /\{[^}]*\}/.test(result)) continue;
|
|
6953
|
+
records.push(`\`${command}\` \u2014 ${result}`);
|
|
6954
|
+
}
|
|
6955
|
+
}
|
|
6956
|
+
if (records.length > 0) {
|
|
6957
|
+
return uniqItemsByContainment(uniqItems(records));
|
|
6958
|
+
}
|
|
6959
|
+
const plannedCommands = extractCommandsFromSection(
|
|
6960
|
+
extractMarkdownByHeadings(
|
|
6961
|
+
planContent,
|
|
6962
|
+
["\uAC80\uC99D \uBA85\uB839(\uC608\uC815)", "Validation Commands", "Verification Commands"],
|
|
6963
|
+
[3, 2]
|
|
6964
|
+
)
|
|
6965
|
+
);
|
|
6966
|
+
if (plannedCommands.length > 0) {
|
|
6967
|
+
return plannedCommands.map(
|
|
6968
|
+
(command) => lang === "ko" ? `\`${command}\` \u2014 \uC2E4\uD589 \uACB0\uACFC\uB97C \uAE30\uB85D\uD558\uC138\uC694.` : `\`${command}\` \u2014 record execution result.`
|
|
6969
|
+
);
|
|
6970
|
+
}
|
|
6971
|
+
return [];
|
|
6972
|
+
}
|
|
6973
|
+
function getPrChangesAndTests(specContent, planContent, tasksContent, overview, lang) {
|
|
6974
|
+
const requirementHeadings = extractSectionHeadings(
|
|
6975
|
+
extractMarkdownSection(specContent, ["\uAE30\uB2A5 \uC694\uAD6C\uC0AC\uD56D", "Functional Requirements"])
|
|
6976
|
+
);
|
|
6977
|
+
const scopeFromPlan = extractScopeItemsFromPlan(planContent, lang).include;
|
|
6978
|
+
const planTargets = extractPlanChangeTargets(planContent, lang);
|
|
6979
|
+
const taskTitles = extractTaskTitles(tasksContent);
|
|
6980
|
+
const changes = uniqItemsByContainment(
|
|
6981
|
+
uniqItems([
|
|
6982
|
+
...taskTitles,
|
|
6983
|
+
...scopeFromPlan,
|
|
6984
|
+
...requirementHeadings,
|
|
6985
|
+
...planTargets,
|
|
6986
|
+
sanitizeDraftItem(overview) || ""
|
|
6987
|
+
])
|
|
6988
|
+
).slice(0, 6);
|
|
6989
|
+
while (changes.length < 3) {
|
|
6990
|
+
changes.push(
|
|
6991
|
+
lang === "ko" ? changes.length === 0 ? "\uD575\uC2EC \uAD6C\uD604 \uBCC0\uACBD \uC0AC\uD56D\uC744 \uC694\uC57D\uD574 \uBC18\uC601\uD55C\uB2E4." : changes.length === 1 ? "\uC601\uD5A5 \uBC94\uC704(\uD638\uD658\uC131/\uB9C8\uC774\uADF8\uB808\uC774\uC158 \uD3EC\uD568)\uB97C \uBA85\uC2DC\uD55C\uB2E4." : "\uBB38\uC11C\uC640 \uAD6C\uD604 \uAC04 \uBD88\uC77C\uCE58\uAC00 \uC5C6\uB3C4\uB85D \uC815\uD569\uC131\uC744 \uC810\uAC80\uD55C\uB2E4." : changes.length === 0 ? "Summarize key implementation changes." : changes.length === 1 ? "Document impact scope (including compatibility/migration)." : "Verify document and implementation consistency."
|
|
6992
|
+
);
|
|
6993
|
+
}
|
|
6994
|
+
const tests = uniqItemsByContainment(
|
|
6995
|
+
uniqItems(extractRecordedTestLines(tasksContent, planContent, lang))
|
|
6996
|
+
).slice(0, 4);
|
|
6997
|
+
while (tests.length < 2) {
|
|
6998
|
+
tests.push(
|
|
6999
|
+
lang === "ko" ? tests.length === 0 ? "\uAD00\uB828 \uD14C\uC2A4\uD2B8\uB97C \uC2E4\uD589\uD558\uACE0 \uACB0\uACFC\uB97C \uAE30\uB85D\uD55C\uB2E4." : "\uBBF8\uC2E4\uD589 \uD14C\uC2A4\uD2B8\uAC00 \uC788\uC73C\uBA74 \uC0AC\uC720\uC640 \uB9AC\uC2A4\uD06C\uB97C \uAE30\uB85D\uD55C\uB2E4." : tests.length === 0 ? "Run relevant tests and record results." : "If tests were not run, record rationale and risk."
|
|
7000
|
+
);
|
|
7001
|
+
}
|
|
7002
|
+
return {
|
|
7003
|
+
changes: changes.slice(0, 6),
|
|
7004
|
+
tests: tests.slice(0, 4)
|
|
7005
|
+
};
|
|
7006
|
+
}
|
|
6479
7007
|
function resolveOverviewFromSpec(specContent, feature, lang) {
|
|
6480
7008
|
const fromPurpose = sanitizeOverviewSection(
|
|
6481
7009
|
extractMarkdownSection(specContent, ["\uBAA9\uC801", "Purpose"])
|
|
@@ -6487,7 +7015,112 @@ function resolveOverviewFromSpec(specContent, feature, lang) {
|
|
|
6487
7015
|
if (fromOverview) return fromOverview;
|
|
6488
7016
|
return lang === "ko" ? `\`${feature.folderName}\` \uAE30\uB2A5 \uAC1C\uC694\uB97C spec.md \uAE30\uC900\uC73C\uB85C \uC791\uC131\uD558\uC138\uC694.` : `Summarize feature \`${feature.folderName}\` from spec.md.`;
|
|
6489
7017
|
}
|
|
6490
|
-
function
|
|
7018
|
+
function getPrScreenshotsHeading(lang) {
|
|
7019
|
+
return getGithubDraftArtifactHeading("pr", "screenshots", lang) || (lang === "ko" ? "\uC2A4\uD06C\uB9B0\uC0F7" : "Screenshots");
|
|
7020
|
+
}
|
|
7021
|
+
function getPrMermaidHeading(lang) {
|
|
7022
|
+
return getGithubDraftArtifactHeading("pr", "mermaid", lang) || (lang === "ko" ? "\uC544\uD0A4\uD14D\uCC98 \uB2E4\uC774\uC5B4\uADF8\uB7A8" : "Architecture Diagram");
|
|
7023
|
+
}
|
|
7024
|
+
function buildPrScreenshotsSection(lang) {
|
|
7025
|
+
if (lang === "ko") {
|
|
7026
|
+
return `
|
|
7027
|
+
## \uC2A4\uD06C\uB9B0\uC0F7
|
|
7028
|
+
|
|
7029
|
+
- [ ] \uC5C5\uB85C\uB4DC\uD55C \uC2A4\uD06C\uB9B0\uC0F7 URL\uC744 \uD3EC\uD568\uD558\uC138\uC694. (\uC608: \`\`)
|
|
7030
|
+
`;
|
|
7031
|
+
}
|
|
7032
|
+
return `
|
|
7033
|
+
## Screenshots
|
|
7034
|
+
|
|
7035
|
+
- [ ] Include uploaded screenshot URL(s). (e.g. \`\`)
|
|
7036
|
+
`;
|
|
7037
|
+
}
|
|
7038
|
+
function buildPrMermaidSection(lang) {
|
|
7039
|
+
if (lang === "ko") {
|
|
7040
|
+
return `
|
|
7041
|
+
## \uC544\uD0A4\uD14D\uCC98 \uB2E4\uC774\uC5B4\uADF8\uB7A8
|
|
7042
|
+
|
|
7043
|
+
\`\`\`mermaid
|
|
7044
|
+
sequenceDiagram
|
|
7045
|
+
participant Client
|
|
7046
|
+
participant API
|
|
7047
|
+
participant Service
|
|
7048
|
+
participant DB
|
|
7049
|
+
Client->>API: Request
|
|
7050
|
+
API->>Service: Execute
|
|
7051
|
+
Service->>DB: Query/Command
|
|
7052
|
+
DB-->>Service: Result
|
|
7053
|
+
Service-->>API: Response DTO
|
|
7054
|
+
API-->>Client: Response
|
|
7055
|
+
\`\`\`
|
|
7056
|
+
`;
|
|
7057
|
+
}
|
|
7058
|
+
return `
|
|
7059
|
+
## Architecture Diagram
|
|
7060
|
+
|
|
7061
|
+
\`\`\`mermaid
|
|
7062
|
+
sequenceDiagram
|
|
7063
|
+
participant Client
|
|
7064
|
+
participant API
|
|
7065
|
+
participant Service
|
|
7066
|
+
participant DB
|
|
7067
|
+
Client->>API: Request
|
|
7068
|
+
API->>Service: Execute
|
|
7069
|
+
Service->>DB: Query/Command
|
|
7070
|
+
DB-->>Service: Result
|
|
7071
|
+
Service-->>API: Response DTO
|
|
7072
|
+
API-->>Client: Response
|
|
7073
|
+
\`\`\`
|
|
7074
|
+
`;
|
|
7075
|
+
}
|
|
7076
|
+
function ensurePrArtifacts(body, policy, lang) {
|
|
7077
|
+
if (policy.includeScreenshots) {
|
|
7078
|
+
const heading = getPrScreenshotsHeading(lang);
|
|
7079
|
+
const section = extractMarkdownByHeadings(body, [heading], [2]);
|
|
7080
|
+
if (!section) {
|
|
7081
|
+
throw createCliError(
|
|
7082
|
+
"PRECONDITION_FAILED",
|
|
7083
|
+
tg(lang, "prScreenshotsSectionMissing", { section: heading })
|
|
7084
|
+
);
|
|
7085
|
+
}
|
|
7086
|
+
if (!/!\[[^\]]*]\((?!\s*\))[^)]+\)/m.test(section)) {
|
|
7087
|
+
throw createCliError(
|
|
7088
|
+
"PRECONDITION_FAILED",
|
|
7089
|
+
tg(lang, "prScreenshotImageMissing", { section: heading })
|
|
7090
|
+
);
|
|
7091
|
+
}
|
|
7092
|
+
}
|
|
7093
|
+
if (policy.includeMermaid) {
|
|
7094
|
+
const heading = getPrMermaidHeading(lang);
|
|
7095
|
+
const section = extractMarkdownByHeadings(body, [heading], [2]);
|
|
7096
|
+
if (!section) {
|
|
7097
|
+
throw createCliError(
|
|
7098
|
+
"PRECONDITION_FAILED",
|
|
7099
|
+
tg(lang, "prMermaidSectionMissing", { section: heading })
|
|
7100
|
+
);
|
|
7101
|
+
}
|
|
7102
|
+
if (!/```mermaid[\s\S]*?```/m.test(section)) {
|
|
7103
|
+
throw createCliError(
|
|
7104
|
+
"PRECONDITION_FAILED",
|
|
7105
|
+
tg(lang, "prMermaidBlockMissing", { section: heading })
|
|
7106
|
+
);
|
|
7107
|
+
}
|
|
7108
|
+
}
|
|
7109
|
+
}
|
|
7110
|
+
function buildIssueBody(specContent, planContent, tasksContent, overview, labels, paths, lang) {
|
|
7111
|
+
const bodyPaths = toBodyDocPaths(paths);
|
|
7112
|
+
const draft = getIssueGoalsAndCriteria(specContent, planContent, tasksContent, overview, lang);
|
|
7113
|
+
const goals = toChecklistLines(draft.goals);
|
|
7114
|
+
const criteria = toChecklistLines(draft.criteria);
|
|
7115
|
+
const scopeSection = draft.scope.length > 0 ? lang === "ko" ? `
|
|
7116
|
+
## \uC791\uC5C5 \uBC94\uC704(\uC608\uC815)
|
|
7117
|
+
|
|
7118
|
+
${draft.scope.map((item) => `- ${item}`).join("\n")}
|
|
7119
|
+
` : `
|
|
7120
|
+
## Planned Scope
|
|
7121
|
+
|
|
7122
|
+
${draft.scope.map((item) => `- ${item}`).join("\n")}
|
|
7123
|
+
` : "";
|
|
6491
7124
|
if (lang === "ko") {
|
|
6492
7125
|
return `## \uAC1C\uC694
|
|
6493
7126
|
|
|
@@ -6495,19 +7128,18 @@ ${overview}
|
|
|
6495
7128
|
|
|
6496
7129
|
## \uBAA9\uD45C
|
|
6497
7130
|
|
|
6498
|
-
|
|
6499
|
-
- [ ] TODO: \uAD6C\uD604 \uBC94\uC704(\uD3EC\uD568/\uC81C\uC678)\uB97C \uAD6C\uCCB4\uC801\uC73C\uB85C \uC791\uC131\uD558\uC138\uC694.
|
|
7131
|
+
${goals}
|
|
6500
7132
|
|
|
6501
7133
|
## \uC644\uB8CC \uAE30\uC900
|
|
6502
7134
|
|
|
6503
|
-
|
|
6504
|
-
|
|
7135
|
+
${criteria}
|
|
7136
|
+
${scopeSection}
|
|
6505
7137
|
|
|
6506
7138
|
## \uAD00\uB828 \uBB38\uC11C
|
|
6507
7139
|
|
|
6508
|
-
- **Spec**: \`${
|
|
6509
|
-
- **Plan**: \`${
|
|
6510
|
-
- **Tasks**: \`${
|
|
7140
|
+
- **Spec**: \`${bodyPaths.specPath}\`
|
|
7141
|
+
- **Plan**: \`${bodyPaths.planPath}\`
|
|
7142
|
+
- **Tasks**: \`${bodyPaths.tasksPath}\`
|
|
6511
7143
|
|
|
6512
7144
|
## \uB77C\uBCA8
|
|
6513
7145
|
|
|
@@ -6520,29 +7152,40 @@ ${overview}
|
|
|
6520
7152
|
|
|
6521
7153
|
## Goals
|
|
6522
7154
|
|
|
6523
|
-
|
|
6524
|
-
- [ ] TODO: Clarify in-scope and out-of-scope boundaries.
|
|
7155
|
+
${goals}
|
|
6525
7156
|
|
|
6526
7157
|
## Completion Criteria
|
|
6527
7158
|
|
|
6528
|
-
|
|
6529
|
-
|
|
7159
|
+
${criteria}
|
|
7160
|
+
${scopeSection}
|
|
6530
7161
|
|
|
6531
7162
|
## Related Documents
|
|
6532
7163
|
|
|
6533
|
-
- **Spec**: \`${
|
|
6534
|
-
- **Plan**: \`${
|
|
6535
|
-
- **Tasks**: \`${
|
|
7164
|
+
- **Spec**: \`${bodyPaths.specPath}\`
|
|
7165
|
+
- **Plan**: \`${bodyPaths.planPath}\`
|
|
7166
|
+
- **Tasks**: \`${bodyPaths.tasksPath}\`
|
|
6536
7167
|
|
|
6537
7168
|
## Labels
|
|
6538
7169
|
|
|
6539
7170
|
${labels.map((label) => `- \`${label}\``).join("\n")}
|
|
6540
7171
|
`;
|
|
6541
7172
|
}
|
|
6542
|
-
function buildPrBody(feature, overview, paths, lang) {
|
|
7173
|
+
function buildPrBody(feature, specContent, planContent, tasksContent, overview, paths, artifactPolicy, lang) {
|
|
7174
|
+
const bodyPaths = toBodyDocPaths(paths);
|
|
6543
7175
|
const closes = feature.issueNumber ? `
|
|
6544
7176
|
Closes #${feature.issueNumber}
|
|
6545
7177
|
` : "\n";
|
|
7178
|
+
const draft = getPrChangesAndTests(
|
|
7179
|
+
specContent,
|
|
7180
|
+
planContent,
|
|
7181
|
+
tasksContent,
|
|
7182
|
+
overview,
|
|
7183
|
+
lang
|
|
7184
|
+
);
|
|
7185
|
+
const changes = toChecklistLines(draft.changes);
|
|
7186
|
+
const tests = toChecklistLines(draft.tests);
|
|
7187
|
+
const screenshotsSection = artifactPolicy.includeScreenshots ? buildPrScreenshotsSection(lang) : "";
|
|
7188
|
+
const mermaidSection = artifactPolicy.includeMermaid ? buildPrMermaidSection(lang) : "";
|
|
6546
7189
|
if (lang === "ko") {
|
|
6547
7190
|
return `## \uAC1C\uC694
|
|
6548
7191
|
|
|
@@ -6550,20 +7193,20 @@ ${overview}
|
|
|
6550
7193
|
|
|
6551
7194
|
## \uBCC0\uACBD \uC0AC\uD56D
|
|
6552
7195
|
|
|
6553
|
-
|
|
6554
|
-
- [ ] TODO: \uC601\uD5A5 \uBC94\uC704/\uD638\uD658\uC131(\uB9C8\uC774\uADF8\uB808\uC774\uC158 \uD3EC\uD568)\uC744 \uC791\uC131\uD558\uC138\uC694.
|
|
7196
|
+
${changes}
|
|
6555
7197
|
|
|
6556
7198
|
## \uD14C\uC2A4\uD2B8
|
|
6557
7199
|
|
|
6558
7200
|
### \uC2E4\uD589\uD55C \uD14C\uC2A4\uD2B8
|
|
6559
7201
|
|
|
6560
|
-
|
|
6561
|
-
|
|
7202
|
+
${tests}
|
|
7203
|
+
${screenshotsSection}
|
|
7204
|
+
${mermaidSection}
|
|
6562
7205
|
|
|
6563
7206
|
## \uAD00\uB828 \uBB38\uC11C
|
|
6564
7207
|
|
|
6565
|
-
- **Spec**: \`${
|
|
6566
|
-
- **Tasks**: \`${
|
|
7208
|
+
- **Spec**: \`${bodyPaths.specPath}\`
|
|
7209
|
+
- **Tasks**: \`${bodyPaths.tasksPath}\`${closes}`;
|
|
6567
7210
|
}
|
|
6568
7211
|
return `## Overview
|
|
6569
7212
|
|
|
@@ -6571,26 +7214,26 @@ ${overview}
|
|
|
6571
7214
|
|
|
6572
7215
|
## Changes
|
|
6573
7216
|
|
|
6574
|
-
|
|
6575
|
-
- [ ] TODO: Describe impact/scope (including migration if any).
|
|
7217
|
+
${changes}
|
|
6576
7218
|
|
|
6577
7219
|
## Tests
|
|
6578
7220
|
|
|
6579
7221
|
### Tests Run
|
|
6580
7222
|
|
|
6581
|
-
|
|
6582
|
-
|
|
7223
|
+
${tests}
|
|
7224
|
+
${screenshotsSection}
|
|
7225
|
+
${mermaidSection}
|
|
6583
7226
|
|
|
6584
7227
|
## Related Documents
|
|
6585
7228
|
|
|
6586
|
-
- **Spec**: \`${
|
|
6587
|
-
- **Tasks**: \`${
|
|
7229
|
+
- **Spec**: \`${bodyPaths.specPath}\`
|
|
7230
|
+
- **Tasks**: \`${bodyPaths.tasksPath}\`${closes}`;
|
|
6588
7231
|
}
|
|
6589
7232
|
function getRequiredIssueSections(lang) {
|
|
6590
|
-
return
|
|
7233
|
+
return getGithubDraftRequiredSections("issue", lang);
|
|
6591
7234
|
}
|
|
6592
7235
|
function getRequiredPrSections(lang) {
|
|
6593
|
-
return
|
|
7236
|
+
return getGithubDraftRequiredSections("pr", lang);
|
|
6594
7237
|
}
|
|
6595
7238
|
function replaceListField(content, keys, value) {
|
|
6596
7239
|
for (const key of keys) {
|
|
@@ -6809,14 +7452,24 @@ function githubCommand(program2) {
|
|
|
6809
7452
|
config.lang
|
|
6810
7453
|
);
|
|
6811
7454
|
const specContent = await fs15.readFile(path17.join(config.docsDir, paths.specPath), "utf-8");
|
|
7455
|
+
const planContent = await fs15.readFile(path17.join(config.docsDir, paths.planPath), "utf-8");
|
|
7456
|
+
const tasksContent = await fs15.readFile(path17.join(config.docsDir, paths.tasksPath), "utf-8");
|
|
6812
7457
|
const overview = resolveOverviewFromSpec(specContent, feature, config.lang);
|
|
6813
7458
|
const title = options.title?.trim() || tg(config.lang, "issueDefaultTitle", {
|
|
6814
7459
|
slug: feature.slug,
|
|
6815
7460
|
folder: feature.folderName
|
|
6816
7461
|
});
|
|
6817
|
-
const
|
|
7462
|
+
const generatedBody = buildIssueBody(
|
|
7463
|
+
specContent,
|
|
7464
|
+
planContent,
|
|
7465
|
+
tasksContent,
|
|
7466
|
+
overview,
|
|
7467
|
+
labels,
|
|
7468
|
+
paths,
|
|
7469
|
+
config.lang
|
|
7470
|
+
);
|
|
6818
7471
|
ensureSections(
|
|
6819
|
-
|
|
7472
|
+
generatedBody,
|
|
6820
7473
|
getRequiredIssueSections(config.lang),
|
|
6821
7474
|
tg(config.lang, "kindIssue"),
|
|
6822
7475
|
config.lang
|
|
@@ -6827,10 +7480,23 @@ function githubCommand(program2) {
|
|
|
6827
7480
|
config.docsDir,
|
|
6828
7481
|
feature.type
|
|
6829
7482
|
);
|
|
6830
|
-
|
|
6831
|
-
|
|
7483
|
+
const explicitBodyFile = (options.bodyFile || "").trim();
|
|
7484
|
+
let body = generatedBody;
|
|
7485
|
+
if (options.create && explicitBodyFile && await fs15.pathExists(bodyFile)) {
|
|
7486
|
+
body = await fs15.readFile(bodyFile, "utf-8");
|
|
7487
|
+
ensureSections(
|
|
7488
|
+
body,
|
|
7489
|
+
getRequiredIssueSections(config.lang),
|
|
7490
|
+
tg(config.lang, "kindIssue"),
|
|
7491
|
+
config.lang
|
|
7492
|
+
);
|
|
7493
|
+
} else {
|
|
7494
|
+
await fs15.ensureDir(path17.dirname(bodyFile));
|
|
7495
|
+
await fs15.writeFile(bodyFile, generatedBody, "utf-8");
|
|
7496
|
+
}
|
|
6832
7497
|
let issueUrl;
|
|
6833
7498
|
if (options.create) {
|
|
7499
|
+
ensureNoTodoPlaceholders(body, tg(config.lang, "kindIssue"), config.lang);
|
|
6834
7500
|
assertRemoteApproval(
|
|
6835
7501
|
options.confirm,
|
|
6836
7502
|
tg(config.lang, "operationIssueCreate"),
|
|
@@ -6914,7 +7580,7 @@ function githubCommand(program2) {
|
|
|
6914
7580
|
github.command("pr [feature-name]").description(tg(commandLang, "cmdPrDescription")).option("--json", tg(commandLang, "optJson")).option("--repo <repo>", tg(commandLang, "optRepo")).option("--component <component>", tg(commandLang, "optComponent")).option("--title <title>", tg(commandLang, "optPrTitle")).option("--labels <labels>", tg(commandLang, "optLabels")).option("--body-file <path>", tg(commandLang, "optPrBodyFile")).option("--assignee <assignee>", tg(commandLang, "optPrAssignee")).option("--base <branch>", tg(commandLang, "optPrBase"), "main").option("--create", tg(commandLang, "optPrCreate")).option("--pr <ref>", tg(commandLang, "optPrRef")).option("--merge", tg(commandLang, "optPrMerge")).option(
|
|
6915
7581
|
"--confirm <reply>",
|
|
6916
7582
|
tg(commandLang, "optPrConfirm")
|
|
6917
|
-
).option("--retry <count>", tg(commandLang, "optPrRetry")).option("--no-sync-tasks", tg(commandLang, "optPrNoSyncTasks")).option("--commit-sync", tg(commandLang, "optPrCommitSync")).action(async (featureName, options) => {
|
|
7583
|
+
).option("--retry <count>", tg(commandLang, "optPrRetry")).option("--screenshots <mode>", tg(commandLang, "optPrScreenshots"), "auto").option("--mermaid <mode>", tg(commandLang, "optPrMermaid"), "auto").option("--no-sync-tasks", tg(commandLang, "optPrNoSyncTasks")).option("--commit-sync", tg(commandLang, "optPrCommitSync")).action(async (featureName, options) => {
|
|
6918
7584
|
try {
|
|
6919
7585
|
const selectedComponent = resolveComponentOption4(options, commandLang);
|
|
6920
7586
|
const { config, feature } = await resolveFeatureOrThrow(featureName, {
|
|
@@ -6924,6 +7590,9 @@ function githubCommand(program2) {
|
|
|
6924
7590
|
const paths = getFeatureDocPaths(feature);
|
|
6925
7591
|
ensureDocsExist(config.docsDir, [paths.specPath, paths.tasksPath], config.lang);
|
|
6926
7592
|
const specContent = await fs15.readFile(path17.join(config.docsDir, paths.specPath), "utf-8");
|
|
7593
|
+
const planPath = path17.join(config.docsDir, paths.planPath);
|
|
7594
|
+
const planContent = await fs15.pathExists(planPath) ? await fs15.readFile(planPath, "utf-8") : "";
|
|
7595
|
+
const tasksContent = await fs15.readFile(path17.join(config.docsDir, paths.tasksPath), "utf-8");
|
|
6927
7596
|
const overview = resolveOverviewFromSpec(specContent, feature, config.lang);
|
|
6928
7597
|
const defaultTitle = feature.issueNumber ? tg(config.lang, "prDefaultTitleWithIssue", {
|
|
6929
7598
|
issue: feature.issueNumber,
|
|
@@ -6932,9 +7601,19 @@ function githubCommand(program2) {
|
|
|
6932
7601
|
slug: feature.slug
|
|
6933
7602
|
});
|
|
6934
7603
|
const title = options.title?.trim() || defaultTitle;
|
|
6935
|
-
const
|
|
7604
|
+
const artifactPolicy = resolvePrArtifactPolicy(config, feature, options);
|
|
7605
|
+
const generatedBody = buildPrBody(
|
|
7606
|
+
feature,
|
|
7607
|
+
specContent,
|
|
7608
|
+
planContent,
|
|
7609
|
+
tasksContent,
|
|
7610
|
+
overview,
|
|
7611
|
+
paths,
|
|
7612
|
+
artifactPolicy,
|
|
7613
|
+
config.lang
|
|
7614
|
+
);
|
|
6936
7615
|
ensureSections(
|
|
6937
|
-
|
|
7616
|
+
generatedBody,
|
|
6938
7617
|
getRequiredPrSections(config.lang),
|
|
6939
7618
|
tg(config.lang, "kindPr"),
|
|
6940
7619
|
config.lang
|
|
@@ -6945,13 +7624,27 @@ function githubCommand(program2) {
|
|
|
6945
7624
|
config.docsDir,
|
|
6946
7625
|
feature.type
|
|
6947
7626
|
);
|
|
6948
|
-
|
|
6949
|
-
|
|
7627
|
+
const explicitBodyFile = (options.bodyFile || "").trim();
|
|
7628
|
+
let body = generatedBody;
|
|
7629
|
+
if (options.create && explicitBodyFile && await fs15.pathExists(bodyFile)) {
|
|
7630
|
+
body = await fs15.readFile(bodyFile, "utf-8");
|
|
7631
|
+
ensureSections(
|
|
7632
|
+
body,
|
|
7633
|
+
getRequiredPrSections(config.lang),
|
|
7634
|
+
tg(config.lang, "kindPr"),
|
|
7635
|
+
config.lang
|
|
7636
|
+
);
|
|
7637
|
+
} else {
|
|
7638
|
+
await fs15.ensureDir(path17.dirname(bodyFile));
|
|
7639
|
+
await fs15.writeFile(bodyFile, generatedBody, "utf-8");
|
|
7640
|
+
}
|
|
6950
7641
|
const retryCount = toRetryCount(options.retry, config.lang);
|
|
6951
7642
|
let prUrl = options.pr?.trim() || "";
|
|
6952
7643
|
let mergedAttempts;
|
|
6953
7644
|
let syncChanged = false;
|
|
6954
7645
|
if (options.create) {
|
|
7646
|
+
ensureNoTodoPlaceholders(body, tg(config.lang, "kindPr"), config.lang);
|
|
7647
|
+
ensurePrArtifacts(body, artifactPolicy, config.lang);
|
|
6955
7648
|
assertRemoteApproval(
|
|
6956
7649
|
options.confirm,
|
|
6957
7650
|
tg(config.lang, "operationPrCreate"),
|
|
@@ -7046,6 +7739,10 @@ function githubCommand(program2) {
|
|
|
7046
7739
|
labels,
|
|
7047
7740
|
body,
|
|
7048
7741
|
bodyFile,
|
|
7742
|
+
artifactPolicy: {
|
|
7743
|
+
screenshots: artifactPolicy.includeScreenshots,
|
|
7744
|
+
mermaid: artifactPolicy.includeMermaid
|
|
7745
|
+
},
|
|
7049
7746
|
prUrl: prUrl || void 0,
|
|
7050
7747
|
syncChanged,
|
|
7051
7748
|
merged: !!options.merge,
|
|
@@ -7187,6 +7884,7 @@ function docsCommand(program2) {
|
|
|
7187
7884
|
id,
|
|
7188
7885
|
command: toBuiltinDocCommand(id)
|
|
7189
7886
|
}));
|
|
7887
|
+
const contract = getGithubDraftContractForBuiltinDoc(docId, config.lang);
|
|
7190
7888
|
if (options.json) {
|
|
7191
7889
|
console.log(
|
|
7192
7890
|
JSON.stringify(
|
|
@@ -7202,7 +7900,8 @@ function docsCommand(program2) {
|
|
|
7202
7900
|
hash: loaded.hash,
|
|
7203
7901
|
content: loaded.content
|
|
7204
7902
|
},
|
|
7205
|
-
requiredDocs: followups
|
|
7903
|
+
requiredDocs: followups,
|
|
7904
|
+
contract: contract || void 0
|
|
7206
7905
|
},
|
|
7207
7906
|
null,
|
|
7208
7907
|
2
|
package/package.json
CHANGED
|
@@ -47,7 +47,7 @@ Before creating issue, share and wait for explicit approval (OK):
|
|
|
47
47
|
- Full body draft (from `body`)
|
|
48
48
|
- Labels
|
|
49
49
|
|
|
50
|
-
Also
|
|
50
|
+
Also refine Goals/Completion Criteria based on spec before creating.
|
|
51
51
|
|
|
52
52
|
### 3. Create Issue
|
|
53
53
|
|
|
@@ -26,6 +26,9 @@ npx lee-spec-kit docs get pr-template --json
|
|
|
26
26
|
|
|
27
27
|
# 2) Generate draft body (no remote action)
|
|
28
28
|
npx lee-spec-kit github pr F001 --json
|
|
29
|
+
# - Force screenshots section: --screenshots on
|
|
30
|
+
# - Force Mermaid section: --mermaid on
|
|
31
|
+
# - Auto policy (default): --screenshots auto --mermaid auto
|
|
29
32
|
```
|
|
30
33
|
|
|
31
34
|
Use `docs get pr-template --json` output as the section policy,
|
|
@@ -131,7 +134,7 @@ Before creating PR, share the following **in a code block** and wait for **expli
|
|
|
131
134
|
- Full body draft (from `body`)
|
|
132
135
|
- Labels (at least 1; cannot be empty)
|
|
133
136
|
|
|
134
|
-
Before approval/create,
|
|
137
|
+
Before approval/create, refine the generated draft's Changes/Tests sections based on actual work.
|
|
135
138
|
|
|
136
139
|
### 5. Create PR
|
|
137
140
|
|
|
@@ -26,6 +26,9 @@ npx lee-spec-kit docs get pr-template --json
|
|
|
26
26
|
|
|
27
27
|
# 2) 초안 본문 생성 (원격 작업 아님)
|
|
28
28
|
npx lee-spec-kit github pr F001 --json
|
|
29
|
+
# - 스크린샷 강제 포함: --screenshots on
|
|
30
|
+
# - Mermaid 강제 포함: --mermaid on
|
|
31
|
+
# - 자동 정책(기본): --screenshots auto --mermaid auto
|
|
29
32
|
```
|
|
30
33
|
|
|
31
34
|
`docs get pr-template --json` 출력은 섹션 정책으로 보고,
|
|
@@ -131,7 +134,7 @@ PR 생성 전 다음 내용을 **코드블록으로** 사용자에게 공유하
|
|
|
131
134
|
- 본문 전체 초안 (`body` 기준)
|
|
132
135
|
- 라벨(최소 1개, 비워둘 수 없음)
|
|
133
136
|
|
|
134
|
-
승인/생성 전에 생성된 초안의
|
|
137
|
+
승인/생성 전에 생성된 초안의 변경 사항/테스트 섹션을 실제 작업 기준으로 보완하세요.
|
|
135
138
|
|
|
136
139
|
### 5. PR 생성
|
|
137
140
|
|