lee-spec-kit 0.6.15 → 0.6.17

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
@@ -139,6 +139,7 @@ var I18N = {
139
139
  "context.suggestionCommandHint": "\uB77C\uBCA8 \uCC38\uACE0 \uBA85\uB839: {command}",
140
140
  "context.suggestionFinalPrompt": "\uD604\uC7AC \uCD94\uCC9C \uB77C\uBCA8: {labels}. \uC751\uB2F5\uC740 \uB77C\uBCA8 \uD1A0\uD070 \uD3EC\uD568 \uD615\uC2DD\uC73C\uB85C \uD574\uC8FC\uC138\uC694. (\uC608: {example}, `A \uC9C4\uD589\uD574`)",
141
141
  "context.suggestion.createFeature": "\uC0C8 Feature\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4",
142
+ "context.suggestion.runOnboard": "\uCD08\uAE30 \uC124\uC815 \uC810\uAC80(onboard)\uC744 \uC2E4\uD589\uD569\uB2C8\uB2E4",
142
143
  "context.suggestion.showDone": "\uC644\uB8CC\uB41C Feature \uBAA9\uB85D\uC744 \uD655\uC778\uD569\uB2C8\uB2E4",
143
144
  "context.suggestion.showAll": "\uC804\uCCB4 Feature \uBAA9\uB85D\uC744 \uD655\uC778\uD569\uB2C8\uB2E4",
144
145
  "context.suggestion.selectFeature": "\uC9C4\uD589\uD560 Feature\uB97C \uC120\uD0DD\uD574 \uC0C1\uC138 \uCEE8\uD14D\uC2A4\uD2B8\uB97C \uC5FD\uB2C8\uB2E4",
@@ -202,6 +203,7 @@ var I18N = {
202
203
  "init.log.nextStepsTitle": "\uB2E4\uC74C \uB2E8\uACC4:",
203
204
  "init.log.nextSteps1": " 1. {docsDir}/prd/README.md \uC791\uC131",
204
205
  "init.log.nextSteps2": " 2. npx lee-spec-kit feature <name> \uC73C\uB85C \uAE30\uB2A5 \uCD94\uAC00",
206
+ "init.log.nextSteps3": " 3. npx lee-spec-kit onboard --strict \uB85C \uCD08\uAE30 \uC124\uC815 \uC810\uAC80",
205
207
  "init.log.gitRepoDetectedCommit": "\u{1F4E6} Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uAC10\uC9C0, docs \uCEE4\uBC0B \uC911...",
206
208
  "init.log.gitInit": "\u{1F4E6} Git \uCD08\uAE30\uD654 \uC911...",
207
209
  "init.warn.stagedChangesSkip": '\u26A0\uFE0F \uD604\uC7AC Git index\uC5D0 \uC774\uBBF8 stage\uB41C \uBCC0\uACBD\uC774 \uC788\uC2B5\uB2C8\uB2E4. (--dir "." \uC778 \uACBD\uC6B0 \uCEE4\uBC0B \uBC94\uC704\uB97C \uC548\uC804\uD558\uAC8C \uC81C\uD55C\uD560 \uC218 \uC5C6\uC5B4 \uC790\uB3D9 \uCEE4\uBC0B\uC744 \uAC74\uB108\uB701\uB2C8\uB2E4)',
@@ -589,6 +591,7 @@ var I18N = {
589
591
  "context.suggestionCommandHint": "Reference command: {command}",
590
592
  "context.suggestionFinalPrompt": "Recommended labels now: {labels}. Please reply with a format that includes a label token. (e.g. {example}, `A proceed`)",
591
593
  "context.suggestion.createFeature": "Create a new feature",
594
+ "context.suggestion.runOnboard": "Run onboarding checks",
592
595
  "context.suggestion.showDone": "Show completed features",
593
596
  "context.suggestion.showAll": "Show all features",
594
597
  "context.suggestion.selectFeature": "Select a feature and open detailed context",
@@ -652,6 +655,7 @@ var I18N = {
652
655
  "init.log.nextStepsTitle": "Next steps:",
653
656
  "init.log.nextSteps1": " 1. Write {docsDir}/prd/README.md",
654
657
  "init.log.nextSteps2": " 2. Add a feature with: npx lee-spec-kit feature <name>",
658
+ "init.log.nextSteps3": " 3. Run setup checks: npx lee-spec-kit onboard --strict",
655
659
  "init.log.gitRepoDetectedCommit": "\u{1F4E6} Git repo detected, committing docs...",
656
660
  "init.log.gitInit": "\u{1F4E6} Initializing Git...",
657
661
  "init.warn.stagedChangesSkip": '\u26A0\uFE0F There are already staged changes in the Git index. (With --dir ".", commit scope cannot be safely restricted, so auto-commit is skipped.)',
@@ -2152,6 +2156,7 @@ async function runInit(options) {
2152
2156
  chalk6.gray(tr(lang, "cli", "init.log.nextSteps1", { docsDir: targetDir }))
2153
2157
  );
2154
2158
  console.log(chalk6.gray(tr(lang, "cli", "init.log.nextSteps2")));
2159
+ console.log(chalk6.gray(tr(lang, "cli", "init.log.nextSteps3")));
2155
2160
  console.log();
2156
2161
  },
2157
2162
  { owner: "init" }
@@ -2159,7 +2164,8 @@ async function runInit(options) {
2159
2164
  }
2160
2165
  async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
2161
2166
  try {
2162
- const runGit = (args, workdir) => {
2167
+ const gitWorkdir = docsRepo === "standalone" ? targetDir : cwd;
2168
+ const runGit2 = (args, workdir) => {
2163
2169
  execFileSync("git", args, { cwd: workdir, stdio: "ignore" });
2164
2170
  };
2165
2171
  const getCachedStagedFiles = (workdir) => {
@@ -2205,14 +2211,14 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
2205
2211
  }
2206
2212
  };
2207
2213
  try {
2208
- runGit(["rev-parse", "--is-inside-work-tree"], cwd);
2214
+ runGit2(["rev-parse", "--is-inside-work-tree"], gitWorkdir);
2209
2215
  console.log(chalk6.blue(tr(lang, "cli", "init.log.gitRepoDetectedCommit")));
2210
2216
  } catch {
2211
2217
  console.log(chalk6.blue(tr(lang, "cli", "init.log.gitInit")));
2212
- runGit(["init"], cwd);
2218
+ runGit2(["init"], gitWorkdir);
2213
2219
  }
2214
- const relativePath = path19.relative(cwd, targetDir);
2215
- const stagedBeforeAdd = getCachedStagedFiles(cwd);
2220
+ const relativePath = docsRepo === "standalone" ? "." : path19.relative(cwd, targetDir);
2221
+ const stagedBeforeAdd = getCachedStagedFiles(gitWorkdir);
2216
2222
  if (relativePath === "." && stagedBeforeAdd && stagedBeforeAdd.length > 0) {
2217
2223
  console.log(
2218
2224
  chalk6.yellow(
@@ -2223,8 +2229,8 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
2223
2229
  console.log();
2224
2230
  return;
2225
2231
  }
2226
- if (relativePath !== "." && isPathIgnored(cwd, relativePath)) {
2227
- const repoRelativePath = toRepoRelativePath(cwd, relativePath);
2232
+ if (relativePath !== "." && isPathIgnored(gitWorkdir, relativePath)) {
2233
+ const repoRelativePath = toRepoRelativePath(gitWorkdir, relativePath);
2228
2234
  console.log(
2229
2235
  chalk6.yellow(
2230
2236
  tr(lang, "cli", "init.warn.docsPathIgnoredSkipCommit", {
@@ -2242,14 +2248,14 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
2242
2248
  console.log();
2243
2249
  return;
2244
2250
  }
2245
- runGit(["add", relativePath], cwd);
2246
- runGit(
2251
+ runGit2(["add", relativePath], gitWorkdir);
2252
+ runGit2(
2247
2253
  ["commit", "-m", "init: docs \uAD6C\uC870 \uCD08\uAE30\uD654 (lee-spec-kit)", "--", relativePath],
2248
- cwd
2254
+ gitWorkdir
2249
2255
  );
2250
2256
  if (docsRepo === "standalone" && pushDocs && docsRemote) {
2251
2257
  try {
2252
- runGit(["remote", "add", "origin", docsRemote], cwd);
2258
+ runGit2(["remote", "add", "origin", docsRemote], gitWorkdir);
2253
2259
  console.log(
2254
2260
  chalk6.green(tr(lang, "cli", "init.log.gitRemoteSet", { remote: docsRemote }))
2255
2261
  );
@@ -6376,9 +6382,14 @@ function buildSuggestionOptions(lang, state, projectType, selectedComponent) {
6376
6382
  const showDoneCommand = `npx lee-spec-kit context --done${componentArg}`;
6377
6383
  const showAllCommand = `npx lee-spec-kit context --all${componentArg}`;
6378
6384
  const showOpenCommand = `npx lee-spec-kit context${componentArg}`;
6385
+ const runOnboardCommand = "npx lee-spec-kit onboard --strict";
6379
6386
  const rawSuggestions = [];
6380
6387
  switch (state.status) {
6381
6388
  case "no_features":
6389
+ rawSuggestions.push({
6390
+ summary: tr(lang, "cli", "context.suggestion.runOnboard"),
6391
+ command: runOnboardCommand
6392
+ });
6382
6393
  rawSuggestions.push({
6383
6394
  summary: tr(lang, "cli", "context.suggestion.createFeature"),
6384
6395
  command: createFeatureCommand
@@ -6487,7 +6498,10 @@ function getCommandExecutionLockPath(action, config) {
6487
6498
  return getProjectExecutionLockPath(action.cwd);
6488
6499
  }
6489
6500
  function contextCommand(program2) {
6490
- program2.command("context [feature-name]").description("Show current feature context and next actions").option("--json", "Output in JSON format for agents").option("--component <component>", "Component name for multi projects").option("--all", "Include completed features when auto-detecting").option("--done", "Show completed (workflow-done) features only").option(
6501
+ program2.command("context [feature-name]").description("Show current feature context and next actions").option("--json", "Output in JSON format for agents").option(
6502
+ "--json-compact",
6503
+ "Output compact JSON for agents (implies --json, reduced duplication)"
6504
+ ).option("--component <component>", "Component name for multi projects").option("--all", "Include completed features when auto-detecting").option("--done", "Show completed (workflow-done) features only").option(
6491
6505
  "--approve <reply>",
6492
6506
  "Approve one labeled option (examples: A, A OK, A proceed, A \uC9C4\uD589\uD574)"
6493
6507
  ).option(
@@ -6508,7 +6522,7 @@ function contextCommand(program2) {
6508
6522
  const lang = config?.lang ?? DEFAULT_LANG;
6509
6523
  const cliError = toCliError(error);
6510
6524
  const suggestions = getCliErrorSuggestions(cliError.code, lang);
6511
- if (options.json) {
6525
+ if (options.json || options.jsonCompact) {
6512
6526
  console.log(
6513
6527
  JSON.stringify({
6514
6528
  status: "error",
@@ -6600,6 +6614,111 @@ function getMultipleFeaturesRecommendation(projectType, selectedComponent) {
6600
6614
  }
6601
6615
  return "Multiple features detected across components. Please specify feature name (slug | F001 | F001-slug) or use --component.";
6602
6616
  }
6617
+ function getFeatureRef(feature) {
6618
+ return feature.folderName || `${feature.type}:${feature.slug}`;
6619
+ }
6620
+ function toCompactFeature(feature) {
6621
+ if (!feature) return null;
6622
+ return {
6623
+ ref: getFeatureRef(feature),
6624
+ id: feature.id ?? null,
6625
+ slug: feature.slug,
6626
+ folderName: feature.folderName,
6627
+ type: feature.type,
6628
+ path: feature.path,
6629
+ currentStep: feature.currentStep,
6630
+ nextAction: feature.nextAction,
6631
+ completion: feature.completion,
6632
+ specStatus: feature.specStatus,
6633
+ planStatus: feature.planStatus,
6634
+ tasks: feature.tasks,
6635
+ prePrReview: {
6636
+ status: feature.prePrReview.status,
6637
+ findings: feature.prePrReview.findings,
6638
+ evidenceProvided: feature.prePrReview.evidenceProvided
6639
+ },
6640
+ prReview: {
6641
+ findings: feature.prReview.findings,
6642
+ evidenceProvided: feature.prReview.evidenceProvided
6643
+ },
6644
+ pr: {
6645
+ link: feature.pr.link,
6646
+ status: feature.pr.status,
6647
+ remote: feature.pr.remote
6648
+ },
6649
+ git: {
6650
+ docsBranch: feature.git.docsBranch,
6651
+ projectBranch: feature.git.projectBranch,
6652
+ projectBranchAvailable: feature.git.projectBranchAvailable,
6653
+ onExpectedBranch: feature.git.onExpectedBranch,
6654
+ docsEverCommitted: feature.git.docsEverCommitted,
6655
+ docsHasUncommittedChanges: feature.git.docsHasUncommittedChanges,
6656
+ projectHasUncommittedChanges: feature.git.projectHasUncommittedChanges,
6657
+ docsPathIgnored: feature.git.docsPathIgnored
6658
+ },
6659
+ docs: {
6660
+ specExists: feature.docs.specExists,
6661
+ planExists: feature.docs.planExists,
6662
+ tasksExists: feature.docs.tasksExists,
6663
+ issueDocIssueFieldExists: feature.docs.issueDocIssueFieldExists,
6664
+ prDocPrFieldExists: feature.docs.prDocPrFieldExists,
6665
+ prDocReviewStatusFieldExists: feature.docs.prDocReviewStatusFieldExists,
6666
+ prFieldExists: feature.docs.prFieldExists,
6667
+ prStatusFieldExists: feature.docs.prStatusFieldExists,
6668
+ prePrReviewFieldExists: feature.docs.prePrReviewFieldExists,
6669
+ prePrFindingsFieldExists: feature.docs.prePrFindingsFieldExists,
6670
+ prePrEvidenceFieldExists: feature.docs.prePrEvidenceFieldExists,
6671
+ prReviewFindingsFieldExists: feature.docs.prReviewFindingsFieldExists,
6672
+ prReviewEvidenceFieldExists: feature.docs.prReviewEvidenceFieldExists
6673
+ },
6674
+ warnings: feature.warnings
6675
+ };
6676
+ }
6677
+ function toCompactActionOption(option) {
6678
+ const base = {
6679
+ label: option.label,
6680
+ summary: option.summary,
6681
+ detail: option.detail,
6682
+ approvalPrompt: option.approvalPrompt,
6683
+ actionType: option.action.type,
6684
+ category: option.action.category,
6685
+ operationType: option.action.operationType,
6686
+ requiresUserCheck: !!option.action.requiresUserCheck
6687
+ };
6688
+ if (option.action.type === "command") {
6689
+ base.scope = option.action.scope;
6690
+ base.cwd = option.action.cwd;
6691
+ base.cmd = option.action.cmd;
6692
+ return base;
6693
+ }
6694
+ base.message = option.action.message;
6695
+ return base;
6696
+ }
6697
+ function toCompactSuggestionOption(option) {
6698
+ return {
6699
+ label: option.label,
6700
+ summary: option.summary,
6701
+ command: option.command
6702
+ };
6703
+ }
6704
+ function resolveContextRecommendation(state, projectType, selectedComponent) {
6705
+ if (state.status === "multiple_active") {
6706
+ return getMultipleFeaturesRecommendation(projectType, selectedComponent);
6707
+ }
6708
+ if (state.status === "no_features") {
6709
+ return "No features found. Run onboarding checks first, then create a feature.";
6710
+ }
6711
+ if (state.status === "no_open") {
6712
+ return "No open features found. Use `context --done` to inspect completed features.";
6713
+ }
6714
+ if (state.status === "no_match") {
6715
+ return "No features found.";
6716
+ }
6717
+ if (state.targetFeatures.length === 1) {
6718
+ return state.targetFeatures[0].nextAction;
6719
+ }
6720
+ return "No matched feature.";
6721
+ }
6603
6722
  async function runContext(featureName, options) {
6604
6723
  const cwd = process.cwd();
6605
6724
  const config = await getConfig(cwd);
@@ -6659,7 +6778,8 @@ async function runContext(featureName, options) {
6659
6778
  );
6660
6779
  return;
6661
6780
  }
6662
- if (options.json) {
6781
+ const jsonMode = !!options.json || !!options.jsonCompact;
6782
+ if (jsonMode) {
6663
6783
  const primaryAction = state.actionOptions[0] ?? null;
6664
6784
  const finalApprovalPrompt = buildFinalApprovalPrompt(lang, state.actionOptions);
6665
6785
  const approveCommand = buildApprovalCommand(
@@ -6674,6 +6794,76 @@ async function runContext(featureName, options) {
6674
6794
  selectedComponent,
6675
6795
  true
6676
6796
  );
6797
+ const recommendation = resolveContextRecommendation(
6798
+ state,
6799
+ config.projectType,
6800
+ selectedComponent
6801
+ );
6802
+ if (options.jsonCompact) {
6803
+ const compactResult = {
6804
+ schema: "context.v2.compact",
6805
+ status: state.status,
6806
+ reasonCode: toReasonCode(state.status),
6807
+ selectionMode: state.selectionMode,
6808
+ selectionFallback: state.selectionFallback,
6809
+ branches: state.branches,
6810
+ warnings: state.warnings,
6811
+ contextVersion: state.contextVersion,
6812
+ matchedFeature: toCompactFeature(state.matchedFeature),
6813
+ candidateRefs: state.targetFeatures.length > 1 ? state.targetFeatures.map((feature) => getFeatureRef(feature)) : [],
6814
+ completedCandidateRefs: state.selectionMode === "open" ? state.doneFeatures.map((feature) => getFeatureRef(feature)) : [],
6815
+ openCandidateRefs: state.selectionMode === "open" ? state.openFeatures.map((feature) => getFeatureRef(feature)) : [],
6816
+ inProgressCandidateRefs: state.selectionMode === "open" ? state.inProgressFeatures.map((feature) => getFeatureRef(feature)) : [],
6817
+ readyToCloseCandidateRefs: state.selectionMode === "open" ? state.readyToCloseFeatures.map((feature) => getFeatureRef(feature)) : [],
6818
+ actionOptions: state.actionOptions.map((option) => toCompactActionOption(option)),
6819
+ suggestionOptions: suggestionOptions.map(
6820
+ (option) => toCompactSuggestionOption(option)
6821
+ ),
6822
+ primaryActionLabel: primaryAction?.label ?? null,
6823
+ workflowPolicy,
6824
+ taskCommitGatePolicy,
6825
+ prePrReviewPolicy,
6826
+ checkPolicy: {
6827
+ docPath: "builtin://agents/policy",
6828
+ token: "<LABEL>",
6829
+ acceptedTokens: ["<LABEL>", "<LABEL> OK", "<LABEL> ...", "... <LABEL> ..."],
6830
+ tokenPattern: "^.*\\b([A-Z]+)\\b.*$",
6831
+ validLabels: state.actionOptions.map((o) => o.label),
6832
+ oneApprovalPerAction: true,
6833
+ requireFreshContext: true,
6834
+ contextVersion: state.contextVersion,
6835
+ config: config.approval ?? { mode: "builtin" }
6836
+ },
6837
+ approvalRequest: {
6838
+ finalPrompt: finalApprovalPrompt,
6839
+ userFacingLines: [
6840
+ ...state.actionOptions.map((o) => o.approvalPrompt),
6841
+ finalApprovalPrompt
6842
+ ].filter((line) => line.length > 0),
6843
+ labels: state.actionOptions.map((o) => o.label),
6844
+ approveCommand,
6845
+ executeCommand,
6846
+ executeRequiresTicket: !!state.actionOptions[0]?.action?.requiresUserCheck
6847
+ },
6848
+ suggestionRequest: {
6849
+ finalPrompt: suggestionFinalPrompt,
6850
+ userFacingLines: [
6851
+ ...suggestionOptions.map((o) => `${o.label}: ${o.summary}`),
6852
+ suggestionFinalPrompt
6853
+ ].filter((line) => line.length > 0),
6854
+ labels: suggestionOptions.map((o) => o.label)
6855
+ },
6856
+ prPolicy: {
6857
+ screenshots: {
6858
+ upload: config.pr?.screenshots?.upload ?? false
6859
+ }
6860
+ },
6861
+ requiredDocs,
6862
+ recommendation
6863
+ };
6864
+ console.log(JSON.stringify(compactResult, null, 2));
6865
+ return;
6866
+ }
6677
6867
  const result = {
6678
6868
  status: state.status,
6679
6869
  reasonCode: toReasonCode(state.status),
@@ -6765,24 +6955,8 @@ async function runContext(featureName, options) {
6765
6955
  }
6766
6956
  },
6767
6957
  requiredDocs,
6768
- recommendation: ""
6958
+ recommendation
6769
6959
  };
6770
- if (result.status === "multiple_active") {
6771
- result.recommendation = getMultipleFeaturesRecommendation(
6772
- config.projectType,
6773
- selectedComponent
6774
- );
6775
- } else if (result.status === "no_features") {
6776
- result.recommendation = "No features found. Create a feature first.";
6777
- } else if (result.status === "no_open") {
6778
- result.recommendation = "No open features found. Use `context --done` to inspect completed features.";
6779
- } else if (result.status === "no_match") {
6780
- result.recommendation = "No features found.";
6781
- } else if (state.targetFeatures.length === 1) {
6782
- result.recommendation = state.targetFeatures[0].nextAction;
6783
- } else {
6784
- result.recommendation = "No matched feature.";
6785
- }
6786
6960
  console.log(JSON.stringify(result, null, 2));
6787
6961
  return;
6788
6962
  }
@@ -7055,6 +7229,7 @@ async function runContext(featureName, options) {
7055
7229
  async function runApprovedOption(state, config, lang, featureName, selectionOptions, options) {
7056
7230
  const approval = options.approve || "";
7057
7231
  const ticketToken = (options.ticket || "").trim();
7232
+ const jsonMode = !!options.json || !!options.jsonCompact;
7058
7233
  let parsedLabel = null;
7059
7234
  if (state.status !== "single_matched" || !state.matchedFeature) {
7060
7235
  throw createCliError(
@@ -7115,7 +7290,7 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
7115
7290
  label: parsedLabel,
7116
7291
  featureRef
7117
7292
  }) : null;
7118
- if (options.json) {
7293
+ if (jsonMode) {
7119
7294
  console.log(
7120
7295
  JSON.stringify(
7121
7296
  {
@@ -7194,7 +7369,7 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
7194
7369
  `Approved label "${parsedLabel}" is instruction-only. Re-run without \`--execute\` or pick a command option.`
7195
7370
  );
7196
7371
  }
7197
- if (options.json) {
7372
+ if (jsonMode) {
7198
7373
  console.log(
7199
7374
  JSON.stringify(
7200
7375
  {
@@ -7219,7 +7394,7 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
7219
7394
  console.log();
7220
7395
  return;
7221
7396
  }
7222
- if (!options.json) {
7397
+ if (!jsonMode) {
7223
7398
  console.log();
7224
7399
  console.log(chalk6.blue(`\u25B6 Executing option ${parsedLabel}...`));
7225
7400
  console.log(chalk6.gray(` ${selectedAction.cmd}`));
@@ -7231,12 +7406,12 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
7231
7406
  lockPath,
7232
7407
  async () => executeCommandAction(
7233
7408
  selectedAction.cmd,
7234
- !!options.json,
7409
+ jsonMode,
7235
7410
  selectedAction.cwd
7236
7411
  ),
7237
7412
  { owner: `context-execute:${selectedAction.scope}` }
7238
7413
  );
7239
- if (options.json) {
7414
+ if (jsonMode) {
7240
7415
  console.log(
7241
7416
  JSON.stringify(
7242
7417
  {
@@ -8475,11 +8650,7 @@ function parsePrArtifactMode(raw, kind, lang) {
8475
8650
  tg(lang, "artifactModeInvalid", { kind, value })
8476
8651
  );
8477
8652
  }
8478
- function isMermaidPreferredComponent(component) {
8479
- const normalized = (component || "").trim().toLowerCase();
8480
- return ["be", "backend", "api", "server", "core"].includes(normalized);
8481
- }
8482
- function resolvePrArtifactPolicy(config, feature, options) {
8653
+ function resolvePrArtifactPolicy(config, options) {
8483
8654
  const screenshotsMode = parsePrArtifactMode(
8484
8655
  options.screenshots,
8485
8656
  "screenshots",
@@ -8491,7 +8662,7 @@ function resolvePrArtifactPolicy(config, feature, options) {
8491
8662
  config.lang
8492
8663
  );
8493
8664
  const includeScreenshots = screenshotsMode === "on" ? true : screenshotsMode === "off" ? false : config.pr?.screenshots?.upload ?? false;
8494
- const includeMermaid = mermaidMode === "on" ? true : mermaidMode === "off" ? false : isMermaidPreferredComponent(feature.type);
8665
+ const includeMermaid = mermaidMode === "on" ? true : mermaidMode === "off" ? false : true;
8495
8666
  return {
8496
8667
  includeScreenshots,
8497
8668
  includeMermaid
@@ -9545,7 +9716,7 @@ function githubCommand(program2) {
9545
9716
  slug: feature.slug
9546
9717
  });
9547
9718
  const title = options.title?.trim() || defaultTitle;
9548
- const artifactPolicy = resolvePrArtifactPolicy(config, feature, options);
9719
+ const artifactPolicy = resolvePrArtifactPolicy(config, options);
9549
9720
  const generatedBody = buildPrBody(
9550
9721
  feature,
9551
9722
  specContent,
@@ -10058,6 +10229,459 @@ async function runDetect(options) {
10058
10229
  }
10059
10230
  console.log();
10060
10231
  }
10232
+ function t(lang, ko, en) {
10233
+ return lang === "ko" ? ko : en;
10234
+ }
10235
+ function quotePath(value) {
10236
+ return `"${value.replace(/"/g, '\\"')}"`;
10237
+ }
10238
+ function toSlug(value) {
10239
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "project";
10240
+ }
10241
+ function runGit(args, cwd) {
10242
+ try {
10243
+ return execFileSync("git", args, {
10244
+ cwd,
10245
+ encoding: "utf-8",
10246
+ stdio: ["ignore", "pipe", "pipe"]
10247
+ }).trim();
10248
+ } catch {
10249
+ return void 0;
10250
+ }
10251
+ }
10252
+ function isGitRepo(cwd) {
10253
+ return runGit(["rev-parse", "--is-inside-work-tree"], cwd) === "true";
10254
+ }
10255
+ function hasHeadCommit(cwd) {
10256
+ return !!runGit(["rev-parse", "--verify", "HEAD"], cwd);
10257
+ }
10258
+ function getOriginUrl(cwd) {
10259
+ const out = runGit(["remote", "get-url", "origin"], cwd);
10260
+ return out || void 0;
10261
+ }
10262
+ function hasTemplateMarkers(content) {
10263
+ const patterns = [
10264
+ /\{\{projectName\}\}/,
10265
+ /\{\{date\}\}/,
10266
+ /\(Write your project mission here\)/,
10267
+ /\(Write project-specific architecture principles here/i,
10268
+ /\(Write project code quality standards here/i,
10269
+ /\(Write project security principles here/i,
10270
+ /\(Write your project-specific rules here\)/,
10271
+ /\(Override default rules or add additional rules here\)/,
10272
+ /\(Write project-specific workflows here\)/,
10273
+ /\(Write other rules here\)/,
10274
+ /\(프로젝트의 미션을 작성하세요\)/,
10275
+ /\(프로젝트별 아키텍처 원칙을 작성하세요/,
10276
+ /\(프로젝트의 코드 품질 기준을 작성하세요/,
10277
+ /\(프로젝트의 보안 원칙을 작성하세요/,
10278
+ /\(여기에 프로젝트만의 규칙을 작성하세요\)/,
10279
+ /\(기본 규칙을 오버라이드하거나 추가 규칙을 작성하세요\)/,
10280
+ /\(프로젝트만의 워크플로우가 있다면 작성하세요\)/,
10281
+ /\(기타 규칙을 작성하세요\)/
10282
+ ];
10283
+ return patterns.some((pattern) => pattern.test(content));
10284
+ }
10285
+ async function countFeatureDirs(docsDir, projectType) {
10286
+ const pattern = projectType === "single" ? "features/*/" : "features/*/*/";
10287
+ const dirs = await glob(pattern, {
10288
+ cwd: docsDir,
10289
+ absolute: false,
10290
+ ignore: ["**/feature-base/**"]
10291
+ });
10292
+ return dirs.map((value) => value.replace(/\\/g, "/").replace(/\/+$/, "")).filter((value) => {
10293
+ const base = path19.posix.basename(value);
10294
+ return !!base && base !== "feature-base";
10295
+ }).length;
10296
+ }
10297
+ async function hasUserPrdFile(prdDir) {
10298
+ if (!await fs15.pathExists(prdDir)) return false;
10299
+ const files = await glob("**/*.md", {
10300
+ cwd: prdDir,
10301
+ nodir: true,
10302
+ absolute: false,
10303
+ ignore: ["**/node_modules/**"]
10304
+ });
10305
+ return files.some((relativePath) => path19.basename(relativePath).toLowerCase() !== "readme.md");
10306
+ }
10307
+ function finalizeChecks(checks) {
10308
+ const summary = checks.reduce(
10309
+ (acc, check) => {
10310
+ acc[check.status] += 1;
10311
+ return acc;
10312
+ },
10313
+ { ok: 0, warn: 0, block: 0 }
10314
+ );
10315
+ const status = summary.block > 0 ? "blocked" : summary.warn > 0 ? "needs_action" : "ready";
10316
+ return { checks, summary, status };
10317
+ }
10318
+ function printOnboardResult(lang, result) {
10319
+ console.log();
10320
+ console.log(chalk6.bold(t(lang, "\u{1F9ED} Onboarding \uC810\uAC80", "\u{1F9ED} Onboarding Checks")));
10321
+ for (const check of result.checks) {
10322
+ const mark = check.status === "ok" ? chalk6.green("\u2705") : check.status === "warn" ? chalk6.yellow("\u26A0\uFE0F") : chalk6.red("\u274C");
10323
+ const level = check.status === "ok" ? chalk6.green("OK") : check.status === "warn" ? chalk6.yellow("WARN") : chalk6.red("BLOCK");
10324
+ console.log(`${mark} [${level}] ${check.title}`);
10325
+ console.log(` ${check.message}`);
10326
+ if (check.path) console.log(chalk6.gray(` path: ${check.path}`));
10327
+ if (check.suggestedCommand) {
10328
+ console.log(chalk6.gray(` ${t(lang, "\uB2E4\uC74C \uBA85\uB839", "next")}: ${check.suggestedCommand}`));
10329
+ }
10330
+ }
10331
+ console.log();
10332
+ console.log(
10333
+ chalk6.bold(
10334
+ t(
10335
+ lang,
10336
+ `\uC694\uC57D: OK ${result.summary.ok}, WARN ${result.summary.warn}, BLOCK ${result.summary.block}`,
10337
+ `Summary: OK ${result.summary.ok}, WARN ${result.summary.warn}, BLOCK ${result.summary.block}`
10338
+ )
10339
+ )
10340
+ );
10341
+ if (result.status === "ready") {
10342
+ console.log(chalk6.green(t(lang, "\uC628\uBCF4\uB529 \uC900\uBE44\uAC00 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "Onboarding checks passed.")));
10343
+ } else if (result.status === "needs_action") {
10344
+ console.log(chalk6.yellow(t(lang, "\uCD94\uAC00 \uC815\uB9AC\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.", "Some onboarding actions are required.")));
10345
+ } else {
10346
+ console.log(chalk6.red(t(lang, "\uC628\uBCF4\uB529 \uC120\uD589 \uC791\uC5C5\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.", "Onboarding is blocked by required setup.")));
10347
+ }
10348
+ console.log();
10349
+ }
10350
+ async function runOnboardChecks(config) {
10351
+ const lang = config.lang;
10352
+ const checks = [];
10353
+ const docsDir = config.docsDir;
10354
+ const docsGitReady = isGitRepo(docsDir);
10355
+ if (!docsGitReady) {
10356
+ checks.push({
10357
+ id: "docs_git_repo",
10358
+ status: "block",
10359
+ title: t(lang, "docs Git \uB808\uD3EC \uCD08\uAE30\uD654", "Docs git repository initialized"),
10360
+ message: t(
10361
+ lang,
10362
+ "docs \uACBD\uB85C\uAC00 Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uAC00 \uC544\uB2D9\uB2C8\uB2E4.",
10363
+ "Docs directory is not a git repository."
10364
+ ),
10365
+ path: docsDir,
10366
+ suggestedCommand: `git -C ${quotePath(docsDir)} init`
10367
+ });
10368
+ } else {
10369
+ checks.push({
10370
+ id: "docs_git_repo",
10371
+ status: "ok",
10372
+ title: t(lang, "docs Git \uB808\uD3EC \uCD08\uAE30\uD654", "Docs git repository initialized"),
10373
+ message: t(lang, "docs Git \uB808\uD3EC\uAC00 \uD655\uC778\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "Docs git repository is available."),
10374
+ path: docsDir
10375
+ });
10376
+ if (!hasHeadCommit(docsDir)) {
10377
+ checks.push({
10378
+ id: "docs_initial_commit",
10379
+ status: "warn",
10380
+ title: t(lang, "docs \uCD08\uAE30 \uCEE4\uBC0B", "Docs initial commit"),
10381
+ message: t(
10382
+ lang,
10383
+ "docs \uCCAB \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uCD08\uAE30 \uC124\uC815 \uCEE4\uBC0B\uC744 \uBA3C\uC800 \uC0DD\uC131\uD558\uC138\uC694.",
10384
+ "No initial commit found in docs repo. Create an initial setup commit first."
10385
+ ),
10386
+ path: docsDir,
10387
+ suggestedCommand: `git -C ${quotePath(docsDir)} add . && git -C ${quotePath(docsDir)} commit -m "docs: onboard setup"`
10388
+ });
10389
+ } else {
10390
+ checks.push({
10391
+ id: "docs_initial_commit",
10392
+ status: "ok",
10393
+ title: t(lang, "docs \uCD08\uAE30 \uCEE4\uBC0B", "Docs initial commit"),
10394
+ message: t(lang, "docs \uCD08\uAE30 \uCEE4\uBC0B\uC774 \uC874\uC7AC\uD569\uB2C8\uB2E4.", "Initial commit exists in docs repo."),
10395
+ path: docsDir
10396
+ });
10397
+ }
10398
+ const docsDirty = runGit(["status", "--porcelain=v1"], docsDir);
10399
+ if (docsDirty === void 0) {
10400
+ checks.push({
10401
+ id: "docs_worktree",
10402
+ status: "warn",
10403
+ title: t(lang, "docs \uC791\uC5C5 \uD2B8\uB9AC \uC0C1\uD0DC", "Docs worktree status"),
10404
+ message: t(
10405
+ lang,
10406
+ "docs \uBCC0\uACBD \uC0C1\uD0DC\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
10407
+ "Unable to read docs worktree status."
10408
+ ),
10409
+ path: docsDir
10410
+ });
10411
+ } else if (docsDirty.trim().length > 0) {
10412
+ checks.push({
10413
+ id: "docs_worktree",
10414
+ status: "warn",
10415
+ title: t(lang, "docs \uC791\uC5C5 \uD2B8\uB9AC \uC0C1\uD0DC", "Docs worktree status"),
10416
+ message: t(
10417
+ lang,
10418
+ "\uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC740 docs \uBCC0\uACBD\uC0AC\uD56D\uC774 \uC788\uC2B5\uB2C8\uB2E4.",
10419
+ "Uncommitted docs changes were found."
10420
+ ),
10421
+ path: docsDir,
10422
+ suggestedCommand: `git -C ${quotePath(docsDir)} add . && git -C ${quotePath(docsDir)} commit -m "docs: onboard updates"`
10423
+ });
10424
+ } else {
10425
+ checks.push({
10426
+ id: "docs_worktree",
10427
+ status: "ok",
10428
+ title: t(lang, "docs \uC791\uC5C5 \uD2B8\uB9AC \uC0C1\uD0DC", "Docs worktree status"),
10429
+ message: t(lang, "docs \uC791\uC5C5 \uD2B8\uB9AC\uAC00 \uAE68\uB057\uD569\uB2C8\uB2E4.", "Docs worktree is clean."),
10430
+ path: docsDir
10431
+ });
10432
+ }
10433
+ }
10434
+ const constitutionPath = path19.join(docsDir, "agents", "constitution.md");
10435
+ if (!await fs15.pathExists(constitutionPath)) {
10436
+ checks.push({
10437
+ id: "constitution_exists",
10438
+ status: "block",
10439
+ title: t(lang, "Constitution \uC791\uC131", "Constitution setup"),
10440
+ message: t(
10441
+ lang,
10442
+ "`agents/constitution.md` \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
10443
+ "`agents/constitution.md` is missing."
10444
+ ),
10445
+ path: constitutionPath,
10446
+ suggestedCommand: `npx lee-spec-kit update --agents`
10447
+ });
10448
+ } else {
10449
+ const content = await fs15.readFile(constitutionPath, "utf-8");
10450
+ if (hasTemplateMarkers(content)) {
10451
+ checks.push({
10452
+ id: "constitution_filled",
10453
+ status: "block",
10454
+ title: t(lang, "Constitution \uC791\uC131", "Constitution setup"),
10455
+ message: t(
10456
+ lang,
10457
+ "Constitution\uC5D0 \uD15C\uD50C\uB9BF placeholder\uAC00 \uB0A8\uC544 \uC788\uC2B5\uB2C8\uB2E4. \uD504\uB85C\uC81D\uD2B8 \uAE30\uC900\uC73C\uB85C \uBA3C\uC800 \uC791\uC131\uD558\uC138\uC694.",
10458
+ "Constitution still contains template placeholders. Fill project-specific content first."
10459
+ ),
10460
+ path: constitutionPath
10461
+ });
10462
+ } else {
10463
+ checks.push({
10464
+ id: "constitution_filled",
10465
+ status: "ok",
10466
+ title: t(lang, "Constitution \uC791\uC131", "Constitution setup"),
10467
+ message: t(lang, "Constitution\uC774 \uC791\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "Constitution looks filled."),
10468
+ path: constitutionPath
10469
+ });
10470
+ }
10471
+ }
10472
+ const customPath = path19.join(docsDir, "agents", "custom.md");
10473
+ if (await fs15.pathExists(customPath)) {
10474
+ const content = await fs15.readFile(customPath, "utf-8");
10475
+ if (hasTemplateMarkers(content)) {
10476
+ checks.push({
10477
+ id: "custom_optional",
10478
+ status: "warn",
10479
+ title: t(lang, "Custom \uADDC\uCE59 \uBB38\uC11C", "Custom rules doc"),
10480
+ message: t(
10481
+ lang,
10482
+ "`agents/custom.md`\uB294 \uC120\uD0DD \uD56D\uBAA9\uC774\uC9C0\uB9CC, \uD604\uC7AC \uD15C\uD50C\uB9BF \uC0C1\uD0DC\uC785\uB2C8\uB2E4. \uD544\uC694\uD558\uBA74 \uADDC\uCE59\uC744 \uC791\uC131\uD558\uC138\uC694.",
10483
+ "`agents/custom.md` is optional, but it still looks like template content. Fill it if your project needs custom rules."
10484
+ ),
10485
+ path: customPath
10486
+ });
10487
+ } else {
10488
+ checks.push({
10489
+ id: "custom_optional",
10490
+ status: "ok",
10491
+ title: t(lang, "Custom \uADDC\uCE59 \uBB38\uC11C", "Custom rules doc"),
10492
+ message: t(lang, "Custom \uADDC\uCE59 \uBB38\uC11C\uAC00 \uAD6C\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "Custom rules doc looks configured."),
10493
+ path: customPath
10494
+ });
10495
+ }
10496
+ }
10497
+ const prdDir = path19.join(docsDir, "prd");
10498
+ const featureCount = await countFeatureDirs(docsDir, config.projectType);
10499
+ const prdReady = await hasUserPrdFile(prdDir);
10500
+ if (!prdReady) {
10501
+ checks.push({
10502
+ id: "prd_ready",
10503
+ status: featureCount === 0 ? "block" : "warn",
10504
+ title: t(lang, "PRD \uC900\uBE44 \uC0C1\uD0DC", "PRD readiness"),
10505
+ message: featureCount === 0 ? t(
10506
+ lang,
10507
+ "PRD \uBB38\uC11C\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. Feature \uC0DD\uC131 \uC804\uC5D0 PRD\uBD80\uD130 \uC791\uC131\uD558\uC138\uC694.",
10508
+ "PRD is empty. Write PRD first before creating features."
10509
+ ) : t(
10510
+ lang,
10511
+ "PRD \uBB38\uC11C\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. \uC774\uBBF8 Feature\uAC00 \uC788\uB2E4\uBA74 PRD\uB97C \uBCF4\uAC15\uD558\uC138\uC694.",
10512
+ "PRD is empty. If features already exist, fill PRD as soon as possible."
10513
+ ),
10514
+ path: prdDir,
10515
+ suggestedCommand: `touch ${quotePath(path19.join(prdDir, `${toSlug(config.projectName || "project")}-prd.md`))}`
10516
+ });
10517
+ } else {
10518
+ checks.push({
10519
+ id: "prd_ready",
10520
+ status: "ok",
10521
+ title: t(lang, "PRD \uC900\uBE44 \uC0C1\uD0DC", "PRD readiness"),
10522
+ message: t(lang, "PRD \uBB38\uC11C\uAC00 \uD655\uC778\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "PRD document is present."),
10523
+ path: prdDir
10524
+ });
10525
+ }
10526
+ const workflowPolicy = resolveWorkflowPolicy(config.workflow);
10527
+ if (workflowPolicy.mode === "github") {
10528
+ const projectKeys = config.projectType === "multi" ? resolveProjectComponents(config.projectType, config.components) : ["single"];
10529
+ for (const key of projectKeys) {
10530
+ const resolved = resolveProjectGitCwd(config, key, lang);
10531
+ const title = t(
10532
+ lang,
10533
+ config.projectType === "multi" ? `\uD504\uB85C\uC81D\uD2B8 Git \uC5F0\uACB0 (${key})` : "\uD504\uB85C\uC81D\uD2B8 Git \uC5F0\uACB0",
10534
+ config.projectType === "multi" ? `Project git connectivity (${key})` : "Project git connectivity"
10535
+ );
10536
+ if (resolved.warning || !resolved.cwd) {
10537
+ checks.push({
10538
+ id: `project_git_${key}`,
10539
+ status: "block",
10540
+ title,
10541
+ message: resolved.warning || t(
10542
+ lang,
10543
+ "\uD504\uB85C\uC81D\uD2B8 \uB808\uD3EC \uACBD\uB85C\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
10544
+ "Project repository path could not be resolved."
10545
+ )
10546
+ });
10547
+ continue;
10548
+ }
10549
+ if (!isGitRepo(resolved.cwd)) {
10550
+ checks.push({
10551
+ id: `project_git_${key}`,
10552
+ status: "block",
10553
+ title,
10554
+ message: t(
10555
+ lang,
10556
+ "\uD504\uB85C\uC81D\uD2B8 \uACBD\uB85C\uAC00 Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uAC00 \uC544\uB2D9\uB2C8\uB2E4.",
10557
+ "Project path is not a git repository."
10558
+ ),
10559
+ path: resolved.cwd,
10560
+ suggestedCommand: `git -C ${quotePath(resolved.cwd)} init`
10561
+ });
10562
+ continue;
10563
+ }
10564
+ const origin = getOriginUrl(resolved.cwd);
10565
+ if (!origin) {
10566
+ checks.push({
10567
+ id: `project_origin_${key}`,
10568
+ status: "block",
10569
+ title: t(
10570
+ lang,
10571
+ config.projectType === "multi" ? `\uD504\uB85C\uC81D\uD2B8 origin \uC124\uC815 (${key})` : "\uD504\uB85C\uC81D\uD2B8 origin \uC124\uC815",
10572
+ config.projectType === "multi" ? `Project origin configured (${key})` : "Project origin configured"
10573
+ ),
10574
+ message: t(
10575
+ lang,
10576
+ "GitHub \uC6CC\uD06C\uD50C\uB85C\uC6B0\uB97C \uC704\uD574 \uD504\uB85C\uC81D\uD2B8 \uB808\uD3EC\uC758 origin remote\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
10577
+ "Project repo origin remote is required for github workflow."
10578
+ ),
10579
+ path: resolved.cwd,
10580
+ suggestedCommand: `git -C ${quotePath(resolved.cwd)} remote add origin <git-url>`
10581
+ });
10582
+ } else {
10583
+ checks.push({
10584
+ id: `project_origin_${key}`,
10585
+ status: "ok",
10586
+ title: t(
10587
+ lang,
10588
+ config.projectType === "multi" ? `\uD504\uB85C\uC81D\uD2B8 origin \uC124\uC815 (${key})` : "\uD504\uB85C\uC81D\uD2B8 origin \uC124\uC815",
10589
+ config.projectType === "multi" ? `Project origin configured (${key})` : "Project origin configured"
10590
+ ),
10591
+ message: t(
10592
+ lang,
10593
+ `origin\uC774 \uC124\uC815\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4: ${origin}`,
10594
+ `origin is configured: ${origin}`
10595
+ ),
10596
+ path: resolved.cwd
10597
+ });
10598
+ }
10599
+ }
10600
+ }
10601
+ if (config.docsRepo === "standalone" && config.pushDocs) {
10602
+ const origin = getOriginUrl(docsDir);
10603
+ if (!origin) {
10604
+ checks.push({
10605
+ id: "docs_origin",
10606
+ status: "block",
10607
+ title: t(lang, "docs origin \uC124\uC815", "Docs origin configured"),
10608
+ message: t(
10609
+ lang,
10610
+ "standalone + pushDocs=true \uC124\uC815\uC5D0\uC11C\uB294 docs origin remote\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
10611
+ "docs origin remote is required when standalone + pushDocs=true."
10612
+ ),
10613
+ path: docsDir,
10614
+ suggestedCommand: `git -C ${quotePath(docsDir)} remote add origin <docs-git-url>`
10615
+ });
10616
+ } else {
10617
+ checks.push({
10618
+ id: "docs_origin",
10619
+ status: "ok",
10620
+ title: t(lang, "docs origin \uC124\uC815", "Docs origin configured"),
10621
+ message: t(lang, `origin\uC774 \uC124\uC815\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4: ${origin}`, `origin is configured: ${origin}`),
10622
+ path: docsDir
10623
+ });
10624
+ }
10625
+ }
10626
+ return finalizeChecks(checks);
10627
+ }
10628
+ function onboardCommand(program2) {
10629
+ program2.command("onboard").description("Run onboarding checks for initial setup").option("--json", "Output in JSON format for agents").option("--strict", "Exit with code 1 when WARN/BLOCK exists").action(async (options) => {
10630
+ try {
10631
+ await runOnboard(options);
10632
+ } catch (error) {
10633
+ const config = await getConfig(process.cwd());
10634
+ const lang = config?.lang ?? DEFAULT_LANG;
10635
+ const cliError = toCliError(error);
10636
+ const suggestions = getCliErrorSuggestions(cliError.code, lang);
10637
+ if (options.json) {
10638
+ console.log(
10639
+ JSON.stringify({
10640
+ status: "error",
10641
+ reasonCode: cliError.code,
10642
+ error: cliError.message,
10643
+ suggestions
10644
+ })
10645
+ );
10646
+ } else {
10647
+ console.error(
10648
+ chalk6.red(tr(lang, "cli", "common.errorLabel")),
10649
+ chalk6.red(`[${cliError.code}] ${cliError.message}`)
10650
+ );
10651
+ printCliErrorSuggestions(suggestions, lang);
10652
+ }
10653
+ process.exit(1);
10654
+ }
10655
+ });
10656
+ }
10657
+ async function runOnboard(options) {
10658
+ const config = await getConfig(process.cwd());
10659
+ if (!config) {
10660
+ throw createCliError(
10661
+ "CONFIG_NOT_FOUND",
10662
+ tr(DEFAULT_LANG, "cli", "common.configNotFound")
10663
+ );
10664
+ }
10665
+ const lang = config.lang;
10666
+ const result = await runOnboardChecks(config);
10667
+ if (options.json) {
10668
+ const payload = {
10669
+ status: "ok",
10670
+ reasonCode: result.status === "ready" ? "ONBOARD_READY" : result.status === "needs_action" ? "ONBOARD_NEEDS_ACTION" : "ONBOARD_BLOCKED",
10671
+ docsDir: config.docsDir,
10672
+ docsRepo: config.docsRepo || "embedded",
10673
+ workflow: resolveWorkflowPolicy(config.workflow),
10674
+ summary: result.summary,
10675
+ checks: result.checks
10676
+ };
10677
+ console.log(JSON.stringify(payload, null, 2));
10678
+ } else {
10679
+ printOnboardResult(lang, result);
10680
+ }
10681
+ if (options.strict && (result.summary.warn > 0 || result.summary.block > 0)) {
10682
+ process.exitCode = 1;
10683
+ }
10684
+ }
10061
10685
  function isBannerDisabled() {
10062
10686
  const v = (process.env.LEE_SPEC_KIT_NO_BANNER || "").trim();
10063
10687
  return v === "1";
@@ -10237,4 +10861,5 @@ flowCommand(program);
10237
10861
  githubCommand(program);
10238
10862
  docsCommand(program);
10239
10863
  detectCommand(program);
10864
+ onboardCommand(program);
10240
10865
  await program.parseAsync();