lee-spec-kit 0.6.40 → 0.6.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5687,13 +5687,21 @@ function hasStructuredReviewSummary(value) {
5687
5687
  if (!value) return false;
5688
5688
  const trimmed = value.trim();
5689
5689
  if (!trimmed) return false;
5690
- return /^(?:summary|요약)\s*[::]\s*\S.+$/i.test(trimmed);
5690
+ const match = trimmed.match(/^(?:summary|요약)\s*[::]\s*(.+)$/i);
5691
+ if (!match) return false;
5692
+ const payload = (match[1] || "").trim();
5693
+ if (!payload) return false;
5694
+ return !isReviewDraftPlaceholder(payload) && !isPlaceholderReviewEvidence(payload);
5691
5695
  }
5692
5696
  function hasStructuredReviewDecision(value) {
5693
5697
  if (!value) return false;
5694
5698
  const trimmed = value.trim();
5695
5699
  if (!trimmed) return false;
5696
- return /^(?:decision|결정)\s*[::]\s*\S.+$/i.test(trimmed);
5700
+ const match = trimmed.match(/^(?:decision|결정)\s*[::]\s*(.+)$/i);
5701
+ if (!match) return false;
5702
+ const payload = (match[1] || "").trim();
5703
+ if (!payload) return false;
5704
+ return !isReviewDraftPlaceholder(payload) && !isPlaceholderReviewEvidence(payload);
5697
5705
  }
5698
5706
  function parsePrePrDecisionOutcome(value) {
5699
5707
  if (!value) return void 0;
@@ -13436,7 +13444,9 @@ function insertFieldInGithubIssueSection(content, key, value) {
13436
13444
  }
13437
13445
  function insertFieldInMetadataSection(content, key, value) {
13438
13446
  const lines = content.split("\n");
13439
- const headingIndex = lines.findIndex((line) => /^\s*##\s+Metadata\s*$/.test(line));
13447
+ const headingIndex = lines.findIndex(
13448
+ (line) => /^\s*##\s+(?:Metadata|메타데이터)\s*$/.test(line)
13449
+ );
13440
13450
  if (headingIndex < 0) return { content, changed: false };
13441
13451
  let end = lines.length;
13442
13452
  for (let i = headingIndex + 1; i < lines.length; i++) {
@@ -15199,14 +15209,6 @@ function asNonEmptyString(value, fallback) {
15199
15209
  const trimmed = value.trim();
15200
15210
  return trimmed || fallback;
15201
15211
  }
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
15212
  function asRequiredBoolean(value, field) {
15211
15213
  if (typeof value === "boolean") return value;
15212
15214
  throw createCliError(
@@ -15223,9 +15225,67 @@ function asRequiredNonNegativeInteger(value, field) {
15223
15225
  `Evidence JSON ${field} must be a non-negative integer.`
15224
15226
  );
15225
15227
  }
15228
+ function asRequiredTextLike(value, field) {
15229
+ if (typeof value === "string") {
15230
+ const trimmed = value.trim();
15231
+ if (trimmed) return trimmed;
15232
+ }
15233
+ if (typeof value === "number" && Number.isFinite(value) && !Number.isNaN(value)) {
15234
+ return String(value);
15235
+ }
15236
+ throw createCliError(
15237
+ "VALIDATION_FAILED",
15238
+ `Evidence JSON ${field} is required.`
15239
+ );
15240
+ }
15241
+ function isPlaceholderReviewEvidence2(value) {
15242
+ return /^(?:-|#)?\s*(?:tbd|todo|n\/a|na|none|pending|미정|없음|-)\s*$/i.test(
15243
+ value.trim()
15244
+ );
15245
+ }
15246
+ function isReviewDraftPlaceholder2(value) {
15247
+ return /^(?:-|#)?\s*(?:tbd|todo|pending|fill(?:\s+in)?|template|example|미정|작성|기입|n\/a|na)\b/i.test(
15248
+ value.trim()
15249
+ );
15250
+ }
15251
+ function isExplicitNoResidualRiskEntry2(value) {
15252
+ const trimmed = value.trim().toLowerCase();
15253
+ return trimmed === "none" || trimmed === "no residual risk" || trimmed === "no residual risks" || trimmed === "no residual risks found" || trimmed === "no residual risks found in reviewed scope" || trimmed === "\uC794\uC5EC \uB9AC\uC2A4\uD06C \uC5C6\uC74C" || trimmed === "\uC794\uC5EC \uC704\uD5D8 \uC5C6\uC74C";
15254
+ }
15255
+ function asRequiredReviewField(value, field, options) {
15256
+ const normalized = asRequiredTextLike(value, field);
15257
+ const allowExplicitNone = !!options?.allowExplicitNone;
15258
+ if (isReviewDraftPlaceholder2(normalized)) {
15259
+ throw createCliError(
15260
+ "VALIDATION_FAILED",
15261
+ `Evidence JSON ${field} contains draft placeholder text.`
15262
+ );
15263
+ }
15264
+ if (isPlaceholderReviewEvidence2(normalized) && !(allowExplicitNone && normalized.trim().toLowerCase() === "none")) {
15265
+ throw createCliError(
15266
+ "VALIDATION_FAILED",
15267
+ `Evidence JSON ${field} contains placeholder evidence text.`
15268
+ );
15269
+ }
15270
+ return normalized;
15271
+ }
15226
15272
  function normalizeCommandsExecuted(value) {
15227
- if (!Array.isArray(value)) return [];
15228
- return value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
15273
+ if (value == null) return [];
15274
+ if (!Array.isArray(value)) {
15275
+ throw createCliError(
15276
+ "VALIDATION_FAILED",
15277
+ 'Evidence JSON "commandsExecuted" must be an array when provided.'
15278
+ );
15279
+ }
15280
+ return value.map((entry, index) => {
15281
+ if (typeof entry !== "string" || !entry.trim()) {
15282
+ throw createCliError(
15283
+ "VALIDATION_FAILED",
15284
+ `Evidence JSON commandsExecuted[${index}] must be a non-empty string.`
15285
+ );
15286
+ }
15287
+ return entry.trim();
15288
+ });
15229
15289
  }
15230
15290
  function normalizeGitPath3(value) {
15231
15291
  return value.trim().replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
@@ -15243,6 +15303,16 @@ function uniquePaths(values) {
15243
15303
  }
15244
15304
  return out;
15245
15305
  }
15306
+ function normalizeFileLine(value, field) {
15307
+ const normalized = asRequiredTextLike(value, field);
15308
+ if (!/^\d+(?:[-:]\d+)?$/.test(normalized)) {
15309
+ throw createCliError(
15310
+ "VALIDATION_FAILED",
15311
+ `Evidence JSON ${field} must start with a numeric line reference (for example "88" or "88-96").`
15312
+ );
15313
+ }
15314
+ return normalized;
15315
+ }
15246
15316
  function normalizeEvidenceFiles(value) {
15247
15317
  if (!Array.isArray(value)) {
15248
15318
  throw createCliError(
@@ -15265,22 +15335,61 @@ function normalizeEvidenceFiles(value) {
15265
15335
  `Evidence JSON files[${index}].path is required.`
15266
15336
  );
15267
15337
  }
15268
- const review = file.review || {};
15338
+ const review = file.review && typeof file.review === "object" ? file.review : file;
15269
15339
  return {
15270
15340
  path: filePath,
15271
15341
  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(
15342
+ risk: asRequiredTextLike(review.risk, `"files[${index}].risk"`),
15343
+ security: asRequiredTextLike(
15344
+ review.security,
15345
+ `"files[${index}].security"`
15346
+ ),
15347
+ perf: asRequiredTextLike(
15348
+ review.perf ?? review.performance,
15349
+ `"files[${index}].perf"`
15350
+ ),
15351
+ maintainability: asRequiredTextLike(
15276
15352
  review.maintainability,
15277
- "not specified"
15353
+ `"files[${index}].maintainability"`
15278
15354
  ),
15279
- fileLine: asNonEmptyString(review.fileLine, "-")
15355
+ fileLine: normalizeFileLine(
15356
+ review.fileLine,
15357
+ `"files[${index}].fileLine"`
15358
+ )
15280
15359
  }
15281
15360
  };
15282
15361
  });
15283
15362
  }
15363
+ function normalizeResidualRisks(value) {
15364
+ if (typeof value === "string" && value.trim()) {
15365
+ const normalized = asRequiredReviewField(
15366
+ value,
15367
+ '"residualRisks"',
15368
+ { allowExplicitNone: true }
15369
+ );
15370
+ if (!isExplicitNoResidualRiskEntry2(normalized) && isPlaceholderReviewEvidence2(normalized)) {
15371
+ throw createCliError(
15372
+ "VALIDATION_FAILED",
15373
+ 'Evidence JSON "residualRisks" contains placeholder evidence text.'
15374
+ );
15375
+ }
15376
+ return [normalized];
15377
+ }
15378
+ if (Array.isArray(value)) {
15379
+ const entries = value.map(
15380
+ (entry, index) => asRequiredReviewField(entry, `"residualRisks[${index}]"`, {
15381
+ allowExplicitNone: true
15382
+ })
15383
+ ).filter(
15384
+ (entry) => isExplicitNoResidualRiskEntry2(entry) || !isReviewDraftPlaceholder2(entry) && !isPlaceholderReviewEvidence2(entry)
15385
+ );
15386
+ if (entries.length > 0) return entries;
15387
+ }
15388
+ throw createCliError(
15389
+ "VALIDATION_FAILED",
15390
+ 'Evidence JSON "residualRisks" must be a non-empty string or string array.'
15391
+ );
15392
+ }
15284
15393
  var PrePrReviewValidator = class {
15285
15394
  constructor(ctx) {
15286
15395
  this.ctx = ctx;
@@ -15307,18 +15416,19 @@ var PrePrReviewValidator = class {
15307
15416
  );
15308
15417
  }
15309
15418
  const normalizedEvidence = {
15310
- summary: asRequiredNonEmptyString(evidence.summary, '"summary"'),
15311
- featureIntentSummary: asRequiredNonEmptyString(
15419
+ summary: asRequiredReviewField(evidence.summary, '"summary"'),
15420
+ featureIntentSummary: asRequiredReviewField(
15312
15421
  evidence.featureIntentSummary,
15313
15422
  '"featureIntentSummary"'
15314
15423
  ),
15315
- implementationFit: asRequiredNonEmptyString(
15424
+ implementationFit: asRequiredReviewField(
15316
15425
  evidence.implementationFit,
15317
15426
  '"implementationFit"'
15318
15427
  ),
15319
- missingCases: asRequiredNonEmptyString(
15428
+ missingCases: asRequiredReviewField(
15320
15429
  evidence.missingCases,
15321
- '"missingCases"'
15430
+ '"missingCases"',
15431
+ { allowExplicitNone: true }
15322
15432
  ),
15323
15433
  specAlignmentChecked: asRequiredBoolean(
15324
15434
  evidence.specAlignmentChecked,
@@ -15333,7 +15443,7 @@ var PrePrReviewValidator = class {
15333
15443
  '"blockingFindings"'
15334
15444
  ),
15335
15445
  files: normalizeEvidenceFiles(evidence.files),
15336
- residualRisks: asNonEmptyString(evidence.residualRisks, "Not specified"),
15446
+ residualRisks: normalizeResidualRisks(evidence.residualRisks),
15337
15447
  commandsExecuted: normalizeCommandsExecuted(evidence.commandsExecuted)
15338
15448
  };
15339
15449
  if (normalizedEvidence.blockingFindings > normalizedEvidence.findingCount) {
@@ -15515,7 +15625,7 @@ var DEFAULT_EVIDENCE_FOR_ANY_MODE = {
15515
15625
  findingCount: 0,
15516
15626
  blockingFindings: 0,
15517
15627
  files: [],
15518
- residualRisks: "Not specified",
15628
+ residualRisks: ["none"],
15519
15629
  commandsExecuted: []
15520
15630
  };
15521
15631
  function escapeRegExp4(value) {
@@ -15602,7 +15712,7 @@ ${normalizedCommands.map((c) => ` - \`${c}\``).join("\n")}
15602
15712
 
15603
15713
  ` : "";
15604
15714
  let filesSection = "";
15605
- if (input.evidence.files.length === 0) {
15715
+ if (input.evidence.findingCount === 0 || input.evidence.files.length === 0) {
15606
15716
  filesSection = " - 0 findings";
15607
15717
  } else {
15608
15718
  filesSection = input.evidence.files.map((f) => {
@@ -15613,6 +15723,7 @@ ${normalizedCommands.map((c) => ` - \`${c}\``).join("\n")}
15613
15723
  - Maintainability: ${f.review.maintainability}`;
15614
15724
  }).join("\n");
15615
15725
  }
15726
+ const residualRisksSection = input.evidence.residualRisks.length > 0 ? input.evidence.residualRisks.map((entry) => ` - ${entry}`).join("\n") : " - none";
15616
15727
  const mainScopeFiles = input.scope.mainChangedFiles.length > 0 ? input.scope.mainChangedFiles.map((entry) => ` - ${entry}`).join("\n") : " - (none)";
15617
15728
  const worktreeScopeFiles = input.scope.worktreeChangedFiles.length > 0 ? input.scope.worktreeChangedFiles.map((entry) => ` - ${entry}`).join("\n") : " - (none)";
15618
15729
  return `## Pre-PR Review Log (${input.date})
@@ -15631,7 +15742,7 @@ ${normalizedCommands.map((c) => ` - \`${c}\``).join("\n")}
15631
15742
  ${commandsRun}
15632
15743
 
15633
15744
  - **Residual Risks**:
15634
- - ${input.evidence.residualRisks}
15745
+ ${residualRisksSection}
15635
15746
 
15636
15747
  - **Review Scope**:
15637
15748
  - **Main Base Ref**: ${input.scope.baseRef}