lee-spec-kit 0.6.40 → 0.6.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -478,7 +478,7 @@ var koContext = {
478
478
  "context.commandDetail.codeReviewPushFix": "({scope}) \uB9AC\uBDF0 \uC218\uC815 \uCEE4\uBC0B\uC744 push\uD558\uC138\uC694",
479
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",
480
480
  "context.commandDetail.prePrReviewRecord": "({scope}) Pre-PR \uB9AC\uBDF0 evidence\uB97C decisions.md\uC640 tasks.md\uC5D0 \uAE30\uB85D\uD558\uC138\uC694",
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
+ "context.commandDetail.codeReviewRun": "({scope}) \uB9AC\uBDF0 \uCF54\uBA58\uD2B8\uB97C \uD655\uC778\uD558\uACE0, \uBCF4\uC870 \uC5D0\uC774\uC804\uD2B8(sub-agent)\uB85C \uC218\uC815 \uC791\uC5C5/evidence \uC815\uB9AC\uB97C \uC9C4\uD589\uD558\uC138\uC694",
482
482
  "context.actionSummary.runDocsCommand": "\uBB38\uC11C \uC791\uC5C5 \uBA85\uB839\uC744 \uC2E4\uD589\uD558\uC138\uC694",
483
483
  "context.actionSummary.runProjectCommand": "\uD504\uB85C\uC81D\uD2B8 \uC791\uC5C5 \uBA85\uB839\uC744 \uC2E4\uD589\uD558\uC138\uC694",
484
484
  "context.actionDetail.featureFolder": "Feature \uD3F4\uB354\uC640 \uAE30\uBCF8 \uBB38\uC11C \uACE8\uACA9\uC744 \uC900\uBE44\uD558\uC138\uC694",
@@ -496,12 +496,12 @@ var koContext = {
496
496
  "context.actionDetail.issueCreatePrepareFromDoc": "issue.md \uCD08\uC548\uC744 \uBCF4\uC644\uD558\uACE0 \uC0C1\uD0DC\uB97C Ready\uB85C \uC124\uC815\uD558\uC138\uC694",
497
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",
498
498
  "context.actionDetail.taskExecute": "\uD604\uC7AC \uD0DC\uC2A4\uD06C\uB97C \uC9C4\uD589\uD558\uC138\uC694",
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",
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: {task}. (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: {task}",
501
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",
502
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",
503
503
  "context.actionDetail.prePrReviewRecord": "PR \uC804 \uB9AC\uBDF0 evidence\uB97C decisions.md\uC640 tasks.md\uC5D0 \uAE30\uB85D\uD558\uC138\uC694",
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
+ "context.actionDetail.codeReviewRun": "\uB9AC\uBDF0 \uCF54\uBA58\uD2B8\uB97C \uD655\uC778\uD558\uACE0, \uBCF4\uC870 \uC5D0\uC774\uC804\uD2B8(sub-agent)\uB85C \uC218\uC815 \uC791\uC5C5/evidence \uC815\uB9AC\uB97C \uC9C4\uD589\uD558\uC138\uC694",
505
505
  "context.actionDetail.prCreate": "PR\uC744 \uC0DD\uC131\uD558\uACE0 tasks \uAE30.md\uC758 PR \uC815\uBCF4\uB97C \uB9DE\uCD94\uC138\uC694",
506
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",
507
507
  "context.actionDetail.prCreatePrepareFromDoc": "pr.md \uCD08\uC548\uC744 \uBCF4\uC644\uD558\uACE0 \uC0C1\uD0DC\uB97C Ready\uB85C \uC124\uC815\uD558\uC138\uC694",
@@ -1030,7 +1030,7 @@ var enContext = {
1030
1030
  "context.commandDetail.codeReviewPushFix": "({scope}) push review-fix commits",
1031
1031
  "context.commandDetail.prePrReviewRun": "({scope}) run the pre-PR review via a helper agent/sub-agent and prepare `review-trace.json`",
1032
1032
  "context.commandDetail.prePrReviewRecord": "({scope}) record pre-PR review evidence into decisions.md + tasks.md",
1033
- "context.commandDetail.codeReviewRun": "({scope}) run the PR review via a helper agent/sub-agent and prepare evidence/fix summary",
1033
+ "context.commandDetail.codeReviewRun": "({scope}) check PR review comments, then use a helper agent/sub-agent for the follow-up fixes and evidence summary",
1034
1034
  "context.actionSummary.runDocsCommand": "Run docs command",
1035
1035
  "context.actionSummary.runProjectCommand": "Run project command",
1036
1036
  "context.actionDetail.featureFolder": "Prepare feature folder and baseline docs",
@@ -1048,12 +1048,12 @@ var enContext = {
1048
1048
  "context.actionDetail.issueCreatePrepareFromDoc": "Refine issue.md draft and set Status to Ready",
1049
1049
  "context.actionDetail.issueCreateFromDoc": "Create GitHub Issue from ready issue.md and sync Issue",
1050
1050
  "context.actionDetail.taskExecute": "Proceed with the current task",
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
+ "context.actionDetail.taskExecuteRun": "Prepare helper agent/sub-agent task handoff and start the task: {task}. (TODO becomes DOING)",
1052
+ "context.actionDetail.taskExecuteContinue": "Prepare helper agent/sub-agent handoff and continue the in-progress task: {task}",
1053
1053
  "context.actionDetail.reviewFixCommit": "Create a review-fix commit with resolved feedback summary",
1054
1054
  "context.actionDetail.prePrReviewRun": "Run the pre-PR review via a helper agent/sub-agent and prepare `review-trace.json`",
1055
1055
  "context.actionDetail.prePrReviewRecord": "Record pre-PR review evidence into decisions.md and tasks.md",
1056
- "context.actionDetail.codeReviewRun": "Run the PR review via a helper agent/sub-agent and prepare evidence/fix summary",
1056
+ "context.actionDetail.codeReviewRun": "Check PR review comments, then use a helper agent/sub-agent for the follow-up fixes and evidence summary",
1057
1057
  "context.actionDetail.prCreate": "Create PR and sync PR fields in tasks.md",
1058
1058
  "context.actionDetail.prCreateRequiredSequence": "Complete PR 2-step flow: prepare draft + OK, then create and sync",
1059
1059
  "context.actionDetail.prCreatePrepareFromDoc": "Refine pr.md draft and set Status to Ready",
@@ -3769,6 +3769,14 @@ function resolveProjectCommitTopic(feature) {
3769
3769
  const topic = withoutTaskId || normalizeCommitTopicText(feature.folderName);
3770
3770
  return toShellSafeCommitTopic(topic);
3771
3771
  }
3772
+ function resolveTaskUiLabel(task) {
3773
+ if (!task) return "T-unknown task";
3774
+ const id = task.id?.trim();
3775
+ const title = task.title.trim();
3776
+ const normalizedTitle = id ? title.replace(new RegExp(`^${id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s+`), "") : title;
3777
+ if (id) return `${id} - ${normalizedTitle || title}`;
3778
+ return title || "T-unknown task";
3779
+ }
3772
3780
  function getReviewFixCommitGuidance(feature, lang, options) {
3773
3781
  const prePr = !!options?.prePr;
3774
3782
  if (prePr) {
@@ -4066,6 +4074,9 @@ function getStepDefinitions(ctx) {
4066
4074
  requiresUserCheck: true,
4067
4075
  taskExecutePhase: "complete",
4068
4076
  uiDetailKey: "context.actionDetail.taskExecuteContinue",
4077
+ uiDetailParams: {
4078
+ task: resolveTaskUiLabel(f.activeTask)
4079
+ },
4069
4080
  scope: "docs",
4070
4081
  cwd: f.git.docsGitCwd,
4071
4082
  cmd: buildSelfCliCommand(
@@ -4188,6 +4199,9 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
4188
4199
  requiresUserCheck: true,
4189
4200
  taskExecutePhase: "start",
4190
4201
  uiDetailKey: "context.actionDetail.taskExecuteRun",
4202
+ uiDetailParams: {
4203
+ task: resolveTaskUiLabel(f.nextTodoTask)
4204
+ },
4191
4205
  scope: "docs",
4192
4206
  cwd: f.git.docsGitCwd,
4193
4207
  cmd: buildSelfCliCommand(
@@ -4348,7 +4362,7 @@ ${tr(lang, "messages", "prePrReviewDecisionReconfirm", {
4348
4362
  const isCodeReviewNeedEvidence = (f) => isCodeReviewCurrent(f) && f.pr.status === "Review" && workflowPolicy.requireReview && !isCodeReviewNeedEvidenceField(f) && !f.prReview.evidenceProvided;
4349
4363
  const isCodeReviewNeedDecisionField = (f) => isCodeReviewCurrent(f) && f.pr.status === "Review" && workflowPolicy.requireReview && !isCodeReviewNeedEvidenceField(f) && f.prReview.evidenceProvided && !f.docs.prReviewDecisionFieldExists;
4350
4364
  const isCodeReviewNeedDecision = (f) => isCodeReviewCurrent(f) && f.pr.status === "Review" && workflowPolicy.requireReview && !isCodeReviewNeedEvidenceField(f) && f.prReview.evidenceProvided && !isCodeReviewNeedDecisionField(f) && !f.prReview.decisionProvided;
4351
- const isCodeReviewRun = (f) => isCodeReviewCurrent(f) && f.pr.status === "Review" && workflowPolicy.requireReview && !isCodeReviewNeedEvidenceField(f) && !f.prReview.evidenceProvided && !f.prReview.decisionProvided;
4365
+ const isCodeReviewRun = (f) => isCodeReviewCurrent(f) && f.pr.status === "Review" && workflowPolicy.requireReview && (f.git.projectBranchAhead || 0) === 0 && !isCodeReviewNeedEvidenceField(f) && !f.prReview.evidenceProvided && !f.prReview.decisionProvided;
4352
4366
  const isCodeReviewFinalize = (f) => isCodeReviewCurrent(f) && !isCodeReviewSyncApproved(f) && (!workflowPolicy.requireReview || f.pr.status === "Review" && !isCodeReviewNeedEvidenceField(f) && !isCodeReviewNeedEvidence(f) && !isCodeReviewNeedDecisionField(f) && !isCodeReviewNeedDecision(f) && f.prReview.evidenceProvided && f.prReview.decisionProvided);
4353
4367
  const isCodeReviewRequestReview = (f) => isCodeReviewCurrent(f) && !!f.pr.status && f.pr.status !== "Review";
4354
4368
  const getCodeReviewRunActions = (f) => [
@@ -5687,13 +5701,21 @@ function hasStructuredReviewSummary(value) {
5687
5701
  if (!value) return false;
5688
5702
  const trimmed = value.trim();
5689
5703
  if (!trimmed) return false;
5690
- return /^(?:summary|요약)\s*[::]\s*\S.+$/i.test(trimmed);
5704
+ const match = trimmed.match(/^(?:summary|요약)\s*[::]\s*(.+)$/i);
5705
+ if (!match) return false;
5706
+ const payload = (match[1] || "").trim();
5707
+ if (!payload) return false;
5708
+ return !isReviewDraftPlaceholder(payload) && !isPlaceholderReviewEvidence(payload);
5691
5709
  }
5692
5710
  function hasStructuredReviewDecision(value) {
5693
5711
  if (!value) return false;
5694
5712
  const trimmed = value.trim();
5695
5713
  if (!trimmed) return false;
5696
- return /^(?:decision|결정)\s*[::]\s*\S.+$/i.test(trimmed);
5714
+ const match = trimmed.match(/^(?:decision|결정)\s*[::]\s*(.+)$/i);
5715
+ if (!match) return false;
5716
+ const payload = (match[1] || "").trim();
5717
+ if (!payload) return false;
5718
+ return !isReviewDraftPlaceholder(payload) && !isPlaceholderReviewEvidence(payload);
5697
5719
  }
5698
5720
  function parsePrePrDecisionOutcome(value) {
5699
5721
  if (!value) return void 0;
@@ -8122,11 +8144,11 @@ function annotateActions(actions) {
8122
8144
  }
8123
8145
  function getActionSummary(action, lang) {
8124
8146
  if (action.uiSummaryKey) {
8125
- const localized = tr(lang, "cli", action.uiSummaryKey);
8147
+ const localized = tr(lang, "cli", action.uiSummaryKey, action.uiDetailParams);
8126
8148
  if (localized !== `cli.${action.uiSummaryKey}`) return localized;
8127
8149
  }
8128
8150
  if (action.uiDetailKey) {
8129
- const localized = tr(lang, "cli", action.uiDetailKey);
8151
+ const localized = tr(lang, "cli", action.uiDetailKey, action.uiDetailParams);
8130
8152
  if (localized !== `cli.${action.uiDetailKey}`) return localized;
8131
8153
  }
8132
8154
  const detailKey = action.category ? ACTION_DETAIL_KEY_BY_CATEGORY[action.category] : void 0;
@@ -8151,7 +8173,7 @@ function toOneLine(text) {
8151
8173
  }
8152
8174
  function buildActionDetail(action, lang) {
8153
8175
  if (action.uiDetailKey) {
8154
- const localized = tr(lang, "cli", action.uiDetailKey);
8176
+ const localized = tr(lang, "cli", action.uiDetailKey, action.uiDetailParams);
8155
8177
  if (localized !== `cli.${action.uiDetailKey}`) return localized;
8156
8178
  }
8157
8179
  const formatBranchCreateDetail = (command) => {
@@ -8274,7 +8296,8 @@ function buildActionSnapshot(actionOptions) {
8274
8296
  operationType: action.operationType,
8275
8297
  requiresUserCheck: !!action.requiresUserCheck,
8276
8298
  uiSummaryKey: action.uiSummaryKey,
8277
- uiDetailKey: action.uiDetailKey
8299
+ uiDetailKey: action.uiDetailKey,
8300
+ uiDetailParams: action.uiDetailParams ? JSON.stringify(action.uiDetailParams) : void 0
8278
8301
  };
8279
8302
  }
8280
8303
  return {
@@ -8285,7 +8308,8 @@ function buildActionSnapshot(actionOptions) {
8285
8308
  operationType: action.operationType,
8286
8309
  requiresUserCheck: !!action.requiresUserCheck,
8287
8310
  uiSummaryKey: action.uiSummaryKey,
8288
- uiDetailKey: action.uiDetailKey
8311
+ uiDetailKey: action.uiDetailKey,
8312
+ uiDetailParams: action.uiDetailParams ? JSON.stringify(action.uiDetailParams) : void 0
8289
8313
  };
8290
8314
  });
8291
8315
  }
@@ -8907,6 +8931,9 @@ function toCompactActionOption(option) {
8907
8931
  if (option.action.taskExecutePhase) {
8908
8932
  base.taskExecutePhase = option.action.taskExecutePhase;
8909
8933
  }
8934
+ if (option.action.uiDetailParams) {
8935
+ base.uiDetailParams = option.action.uiDetailParams;
8936
+ }
8910
8937
  if (option.action.type === "command") {
8911
8938
  base.scope = option.action.scope;
8912
8939
  base.cwd = option.action.cwd;
@@ -9197,7 +9224,11 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
9197
9224
  approval,
9198
9225
  state.actionOptions.map((o) => o.label)
9199
9226
  );
9200
- parsedLabel = parsedApproval?.label ?? null;
9227
+ const replanOptions = state.actionOptions.filter(
9228
+ (option) => option.action.category === "user_request_replan"
9229
+ );
9230
+ const implicitReplanRequest = approval.trim();
9231
+ parsedLabel = parsedApproval?.label ?? (replanOptions.length > 0 && implicitReplanRequest ? replanOptions[0].label : null);
9201
9232
  if (!parsedLabel) {
9202
9233
  throw createCliError(
9203
9234
  "INVALID_APPROVAL",
@@ -9241,7 +9272,7 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
9241
9272
  }
9242
9273
  const selectedAction = freshSelected.action;
9243
9274
  if (selectedAction.category === "user_request_replan") {
9244
- const requestText = parsedApproval?.requestText?.trim();
9275
+ const requestText = (parsedApproval?.requestText?.trim() || (parsedApproval ? "" : implicitReplanRequest)).trim();
9245
9276
  if (!requestText) {
9246
9277
  throw createCliError(
9247
9278
  "INVALID_APPROVAL",
@@ -12401,6 +12432,12 @@ function sanitizeDraftMetadataValue(raw) {
12401
12432
  if (/^\(.+\)$/.test(value)) return void 0;
12402
12433
  return value;
12403
12434
  }
12435
+ function sanitizeDraftTitleValue(raw) {
12436
+ const value = sanitizeDraftMetadataValue(raw);
12437
+ if (!value) return void 0;
12438
+ const normalized = value.replace(/`/g, "").replace(/\*\*(.*?)\*\*/g, "$1").replace(/\[(.*?)\]\((.*?)\)/g, "$1").replace(/\s+/g, " ").trim();
12439
+ return normalized || void 0;
12440
+ }
12404
12441
  function parseWorkflowDraftStatus(raw) {
12405
12442
  const value = (raw || "").trim();
12406
12443
  if (!value) return void 0;
@@ -12412,7 +12449,7 @@ function parseWorkflowDraftMetadata(content) {
12412
12449
  const status = parseWorkflowDraftStatus(
12413
12450
  extractDraftMetadataValue(content, ["Status", "\uC0C1\uD0DC"])
12414
12451
  );
12415
- const title = sanitizeDraftMetadataValue(
12452
+ const title = sanitizeDraftTitleValue(
12416
12453
  extractDraftMetadataValue(content, ["Title", "\uC81C\uBAA9", "PR Title", "PR \uC81C\uBAA9"])
12417
12454
  );
12418
12455
  const labels = sanitizeDraftMetadataValue(
@@ -13436,7 +13473,9 @@ function insertFieldInGithubIssueSection(content, key, value) {
13436
13473
  }
13437
13474
  function insertFieldInMetadataSection(content, key, value) {
13438
13475
  const lines = content.split("\n");
13439
- const headingIndex = lines.findIndex((line) => /^\s*##\s+Metadata\s*$/.test(line));
13476
+ const headingIndex = lines.findIndex(
13477
+ (line) => /^\s*##\s+(?:Metadata|메타데이터)\s*$/.test(line)
13478
+ );
13440
13479
  if (headingIndex < 0) return { content, changed: false };
13441
13480
  let end = lines.length;
13442
13481
  for (let i = headingIndex + 1; i < lines.length; i++) {
@@ -15199,14 +15238,6 @@ function asNonEmptyString(value, fallback) {
15199
15238
  const trimmed = value.trim();
15200
15239
  return trimmed || fallback;
15201
15240
  }
15202
- function asRequiredNonEmptyString(value, field) {
15203
- const normalized = asNonEmptyString(value, "");
15204
- if (normalized) return normalized;
15205
- throw createCliError(
15206
- "VALIDATION_FAILED",
15207
- `Evidence JSON ${field} is required.`
15208
- );
15209
- }
15210
15241
  function asRequiredBoolean(value, field) {
15211
15242
  if (typeof value === "boolean") return value;
15212
15243
  throw createCliError(
@@ -15223,9 +15254,67 @@ function asRequiredNonNegativeInteger(value, field) {
15223
15254
  `Evidence JSON ${field} must be a non-negative integer.`
15224
15255
  );
15225
15256
  }
15257
+ function asRequiredTextLike(value, field) {
15258
+ if (typeof value === "string") {
15259
+ const trimmed = value.trim();
15260
+ if (trimmed) return trimmed;
15261
+ }
15262
+ if (typeof value === "number" && Number.isFinite(value) && !Number.isNaN(value)) {
15263
+ return String(value);
15264
+ }
15265
+ throw createCliError(
15266
+ "VALIDATION_FAILED",
15267
+ `Evidence JSON ${field} is required.`
15268
+ );
15269
+ }
15270
+ function isPlaceholderReviewEvidence2(value) {
15271
+ return /^(?:-|#)?\s*(?:tbd|todo|n\/a|na|none|pending|미정|없음|-)\s*$/i.test(
15272
+ value.trim()
15273
+ );
15274
+ }
15275
+ function isReviewDraftPlaceholder2(value) {
15276
+ return /^(?:-|#)?\s*(?:tbd|todo|pending|fill(?:\s+in)?|template|example|미정|작성|기입|n\/a|na)\b/i.test(
15277
+ value.trim()
15278
+ );
15279
+ }
15280
+ function isExplicitNoResidualRiskEntry2(value) {
15281
+ const trimmed = value.trim().toLowerCase();
15282
+ 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";
15283
+ }
15284
+ function asRequiredReviewField(value, field, options) {
15285
+ const normalized = asRequiredTextLike(value, field);
15286
+ const allowExplicitNone = !!options?.allowExplicitNone;
15287
+ if (isReviewDraftPlaceholder2(normalized)) {
15288
+ throw createCliError(
15289
+ "VALIDATION_FAILED",
15290
+ `Evidence JSON ${field} contains draft placeholder text.`
15291
+ );
15292
+ }
15293
+ if (isPlaceholderReviewEvidence2(normalized) && !(allowExplicitNone && normalized.trim().toLowerCase() === "none")) {
15294
+ throw createCliError(
15295
+ "VALIDATION_FAILED",
15296
+ `Evidence JSON ${field} contains placeholder evidence text.`
15297
+ );
15298
+ }
15299
+ return normalized;
15300
+ }
15226
15301
  function normalizeCommandsExecuted(value) {
15227
- if (!Array.isArray(value)) return [];
15228
- return value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
15302
+ if (value == null) return [];
15303
+ if (!Array.isArray(value)) {
15304
+ throw createCliError(
15305
+ "VALIDATION_FAILED",
15306
+ 'Evidence JSON "commandsExecuted" must be an array when provided.'
15307
+ );
15308
+ }
15309
+ return value.map((entry, index) => {
15310
+ if (typeof entry !== "string" || !entry.trim()) {
15311
+ throw createCliError(
15312
+ "VALIDATION_FAILED",
15313
+ `Evidence JSON commandsExecuted[${index}] must be a non-empty string.`
15314
+ );
15315
+ }
15316
+ return entry.trim();
15317
+ });
15229
15318
  }
15230
15319
  function normalizeGitPath3(value) {
15231
15320
  return value.trim().replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
@@ -15243,6 +15332,16 @@ function uniquePaths(values) {
15243
15332
  }
15244
15333
  return out;
15245
15334
  }
15335
+ function normalizeFileLine(value, field) {
15336
+ const normalized = asRequiredTextLike(value, field);
15337
+ if (!/^\d+(?:[-:]\d+)?$/.test(normalized)) {
15338
+ throw createCliError(
15339
+ "VALIDATION_FAILED",
15340
+ `Evidence JSON ${field} must start with a numeric line reference (for example "88" or "88-96").`
15341
+ );
15342
+ }
15343
+ return normalized;
15344
+ }
15246
15345
  function normalizeEvidenceFiles(value) {
15247
15346
  if (!Array.isArray(value)) {
15248
15347
  throw createCliError(
@@ -15265,22 +15364,61 @@ function normalizeEvidenceFiles(value) {
15265
15364
  `Evidence JSON files[${index}].path is required.`
15266
15365
  );
15267
15366
  }
15268
- const review = file.review || {};
15367
+ const review = file.review && typeof file.review === "object" ? file.review : file;
15269
15368
  return {
15270
15369
  path: filePath,
15271
15370
  review: {
15272
- risk: asNonEmptyString(review.risk, "not specified"),
15273
- security: asNonEmptyString(review.security, "not specified"),
15274
- perf: asNonEmptyString(review.perf, "not specified"),
15275
- maintainability: asNonEmptyString(
15371
+ risk: asRequiredTextLike(review.risk, `"files[${index}].risk"`),
15372
+ security: asRequiredTextLike(
15373
+ review.security,
15374
+ `"files[${index}].security"`
15375
+ ),
15376
+ perf: asRequiredTextLike(
15377
+ review.perf ?? review.performance,
15378
+ `"files[${index}].perf"`
15379
+ ),
15380
+ maintainability: asRequiredTextLike(
15276
15381
  review.maintainability,
15277
- "not specified"
15382
+ `"files[${index}].maintainability"`
15278
15383
  ),
15279
- fileLine: asNonEmptyString(review.fileLine, "-")
15384
+ fileLine: normalizeFileLine(
15385
+ review.fileLine,
15386
+ `"files[${index}].fileLine"`
15387
+ )
15280
15388
  }
15281
15389
  };
15282
15390
  });
15283
15391
  }
15392
+ function normalizeResidualRisks(value) {
15393
+ if (typeof value === "string" && value.trim()) {
15394
+ const normalized = asRequiredReviewField(
15395
+ value,
15396
+ '"residualRisks"',
15397
+ { allowExplicitNone: true }
15398
+ );
15399
+ if (!isExplicitNoResidualRiskEntry2(normalized) && isPlaceholderReviewEvidence2(normalized)) {
15400
+ throw createCliError(
15401
+ "VALIDATION_FAILED",
15402
+ 'Evidence JSON "residualRisks" contains placeholder evidence text.'
15403
+ );
15404
+ }
15405
+ return [normalized];
15406
+ }
15407
+ if (Array.isArray(value)) {
15408
+ const entries = value.map(
15409
+ (entry, index) => asRequiredReviewField(entry, `"residualRisks[${index}]"`, {
15410
+ allowExplicitNone: true
15411
+ })
15412
+ ).filter(
15413
+ (entry) => isExplicitNoResidualRiskEntry2(entry) || !isReviewDraftPlaceholder2(entry) && !isPlaceholderReviewEvidence2(entry)
15414
+ );
15415
+ if (entries.length > 0) return entries;
15416
+ }
15417
+ throw createCliError(
15418
+ "VALIDATION_FAILED",
15419
+ 'Evidence JSON "residualRisks" must be a non-empty string or string array.'
15420
+ );
15421
+ }
15284
15422
  var PrePrReviewValidator = class {
15285
15423
  constructor(ctx) {
15286
15424
  this.ctx = ctx;
@@ -15307,18 +15445,19 @@ var PrePrReviewValidator = class {
15307
15445
  );
15308
15446
  }
15309
15447
  const normalizedEvidence = {
15310
- summary: asRequiredNonEmptyString(evidence.summary, '"summary"'),
15311
- featureIntentSummary: asRequiredNonEmptyString(
15448
+ summary: asRequiredReviewField(evidence.summary, '"summary"'),
15449
+ featureIntentSummary: asRequiredReviewField(
15312
15450
  evidence.featureIntentSummary,
15313
15451
  '"featureIntentSummary"'
15314
15452
  ),
15315
- implementationFit: asRequiredNonEmptyString(
15453
+ implementationFit: asRequiredReviewField(
15316
15454
  evidence.implementationFit,
15317
15455
  '"implementationFit"'
15318
15456
  ),
15319
- missingCases: asRequiredNonEmptyString(
15457
+ missingCases: asRequiredReviewField(
15320
15458
  evidence.missingCases,
15321
- '"missingCases"'
15459
+ '"missingCases"',
15460
+ { allowExplicitNone: true }
15322
15461
  ),
15323
15462
  specAlignmentChecked: asRequiredBoolean(
15324
15463
  evidence.specAlignmentChecked,
@@ -15333,7 +15472,7 @@ var PrePrReviewValidator = class {
15333
15472
  '"blockingFindings"'
15334
15473
  ),
15335
15474
  files: normalizeEvidenceFiles(evidence.files),
15336
- residualRisks: asNonEmptyString(evidence.residualRisks, "Not specified"),
15475
+ residualRisks: normalizeResidualRisks(evidence.residualRisks),
15337
15476
  commandsExecuted: normalizeCommandsExecuted(evidence.commandsExecuted)
15338
15477
  };
15339
15478
  if (normalizedEvidence.blockingFindings > normalizedEvidence.findingCount) {
@@ -15515,7 +15654,7 @@ var DEFAULT_EVIDENCE_FOR_ANY_MODE = {
15515
15654
  findingCount: 0,
15516
15655
  blockingFindings: 0,
15517
15656
  files: [],
15518
- residualRisks: "Not specified",
15657
+ residualRisks: ["none"],
15519
15658
  commandsExecuted: []
15520
15659
  };
15521
15660
  function escapeRegExp4(value) {
@@ -15602,7 +15741,7 @@ ${normalizedCommands.map((c) => ` - \`${c}\``).join("\n")}
15602
15741
 
15603
15742
  ` : "";
15604
15743
  let filesSection = "";
15605
- if (input.evidence.files.length === 0) {
15744
+ if (input.evidence.findingCount === 0 || input.evidence.files.length === 0) {
15606
15745
  filesSection = " - 0 findings";
15607
15746
  } else {
15608
15747
  filesSection = input.evidence.files.map((f) => {
@@ -15613,6 +15752,7 @@ ${normalizedCommands.map((c) => ` - \`${c}\``).join("\n")}
15613
15752
  - Maintainability: ${f.review.maintainability}`;
15614
15753
  }).join("\n");
15615
15754
  }
15755
+ const residualRisksSection = input.evidence.residualRisks.length > 0 ? input.evidence.residualRisks.map((entry) => ` - ${entry}`).join("\n") : " - none";
15616
15756
  const mainScopeFiles = input.scope.mainChangedFiles.length > 0 ? input.scope.mainChangedFiles.map((entry) => ` - ${entry}`).join("\n") : " - (none)";
15617
15757
  const worktreeScopeFiles = input.scope.worktreeChangedFiles.length > 0 ? input.scope.worktreeChangedFiles.map((entry) => ` - ${entry}`).join("\n") : " - (none)";
15618
15758
  return `## Pre-PR Review Log (${input.date})
@@ -15631,7 +15771,7 @@ ${normalizedCommands.map((c) => ` - \`${c}\``).join("\n")}
15631
15771
  ${commandsRun}
15632
15772
 
15633
15773
  - **Residual Risks**:
15634
- - ${input.evidence.residualRisks}
15774
+ ${residualRisksSection}
15635
15775
 
15636
15776
  - **Review Scope**:
15637
15777
  - **Main Base Ref**: ${input.scope.baseRef}
@@ -15992,18 +16132,20 @@ async function runPrePrReview(featureName, options) {
15992
16132
  function buildCodeReviewRunPrompt(input) {
15993
16133
  if (input.lang === "ko") {
15994
16134
  return [
15995
- "PR \uB9AC\uBDF0 \uC2E4\uD589\uC744 \uC2DC\uC791\uD558\uC138\uC694.",
16135
+ "PR \uB9AC\uBDF0 \uCF54\uBA58\uD2B8\uB97C \uD655\uC778\uD558\uACE0 \uBCF4\uC870 \uC5D0\uC774\uC804\uD2B8\uB85C \uC218\uC815 \uC791\uC5C5\uC744 \uC9C4\uD589\uD558\uC138\uC694.",
15996
16136
  `- Feature: ${input.featureRef}`,
15997
16137
  `- ${input.basePrompt}`,
15998
- "- \uB9AC\uBDF0 \uCF54\uBA58\uD2B8 \uBC18\uC601 \uD6C4 `tasks.md`\uC758 `PR \uC804 \uB9AC\uBDF0 Evidence/Decision`\uC774 \uC544\uB2C8\uB77C `PR Review Evidence/Decision`\uC744 \uCD5C\uC2E0\uC73C\uB85C \uAE30\uB85D\uD558\uC138\uC694.",
16138
+ "- \uC0AC\uB78C/CodeRabbit\uC774 \uB0A8\uAE34 \uB9AC\uBDF0 \uCF54\uBA58\uD2B8\uB97C \uAC80\uD1A0\uD558\uACE0 \uD544\uC694\uD55C \uCF54\uB4DC/\uBB38\uC11C \uC218\uC815\uC744 \uC9C4\uD589\uD558\uC138\uC694.",
16139
+ "- \uC218\uC815 \uB0B4\uC6A9\uACFC \uAC80\uD1A0 \uACB0\uACFC\uB97C \uBC18\uC601\uD55C \uB4A4 `tasks.md`\uC758 `PR Review Evidence/Decision`\uC744 \uCD5C\uC2E0\uC73C\uB85C \uAE30\uB85D\uD558\uC138\uC694.",
15999
16140
  "- \uAD00\uB828 \uC218\uC815\uC774 \uC0DD\uAE30\uBA74 \uCF54\uB4DC/\uBB38\uC11C \uBCC0\uACBD\uC744 \uC815\uB9AC\uD558\uACE0, push/merge\uB294 \uBA54\uC778 \uC5D0\uC774\uC804\uD2B8 \uCD5C\uC885 \uC0C1\uD0DC\uC5D0\uC11C\uB9CC \uC9C4\uD589\uD558\uC138\uC694."
16000
16141
  ].join("\n");
16001
16142
  }
16002
16143
  return [
16003
- "Start the PR review execution.",
16144
+ "Review PR comments and use a helper agent/sub-agent for the follow-up fixes.",
16004
16145
  `- Feature: ${input.featureRef}`,
16005
16146
  `- ${input.basePrompt}`,
16006
- "- Update `PR Review Evidence` and `PR Review Decision` in `tasks.md` after reviewing comments and applying fixes.",
16147
+ "- Check human/CodeRabbit review comments and make the required code/docs changes.",
16148
+ "- Update `PR Review Evidence` and `PR Review Decision` in `tasks.md` after applying the fixes and summarizing the outcome.",
16007
16149
  "- If review fixes are needed, keep code/docs changes ready for the main-agent finalize state. Push/merge stays in the main agent."
16008
16150
  ].join("\n");
16009
16151
  }
@@ -16053,7 +16195,7 @@ async function runCodeReviewRun(featureName, options) {
16053
16195
  console.log();
16054
16196
  console.log(
16055
16197
  chalk8.yellow(
16056
- 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."
16198
+ config.lang === "ko" ? "\uC774 \uBA85\uB839\uC740 PR \uB9AC\uBDF0 \uCF54\uBA58\uD2B8 \uB300\uC751\uC6A9 handoff\uB9CC \uC900\uBE44\uD569\uB2C8\uB2E4. \uCF54\uBA58\uD2B8\uB97C \uC9C1\uC811 \uC77D\uC5B4\uC624\uAC70\uB098 evidence/decision\uC744 \uC790\uB3D9 \uAE30\uB85D\uD558\uC9C0 \uC54A\uC73C\uBA70, \uC0C1\uD0DC\uB3C4 \uBC14\uB85C \uB118\uAE30\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4." : "This command only prepares a handoff for addressing PR review comments. It does not fetch comments automatically, record review evidence/decision, or advance workflow state by itself."
16057
16199
  )
16058
16200
  );
16059
16201
  console.log(chalk8.gray(`- substate: ${payload.substateId}`));