sdd-tool 1.4.0 → 1.4.1

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/cli/index.js CHANGED
@@ -105,6 +105,21 @@ var init_messages = __esm({
105
105
  });
106
106
 
107
107
  // src/errors/base.ts
108
+ function getErrorMessage(error2, fallback = "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4") {
109
+ if (error2 instanceof Error) {
110
+ return error2.message;
111
+ }
112
+ if (typeof error2 === "string") {
113
+ return error2;
114
+ }
115
+ if (error2 && typeof error2 === "object" && "message" in error2) {
116
+ return String(error2.message);
117
+ }
118
+ return fallback;
119
+ }
120
+ function getResultErrorMessage(error2, fallback = "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4") {
121
+ return error2?.message ?? fallback;
122
+ }
108
123
  var SddError, FileSystemError, ValidationError, ChangeError;
109
124
  var init_base = __esm({
110
125
  "src/errors/base.ts"() {
@@ -17717,7 +17732,7 @@ async function executeSyncCommand(specId, options, projectRoot) {
17717
17732
  ...options,
17718
17733
  specId
17719
17734
  });
17720
- if (!syncResult.success) {
17735
+ if (!syncResult.success || !syncResult.data) {
17721
17736
  return failure(syncResult.error || new Error("\uB3D9\uAE30\uD654 \uAC80\uC99D \uC2E4\uD328"));
17722
17737
  }
17723
17738
  return success({
@@ -17756,7 +17771,7 @@ function registerSyncCommand(program2) {
17756
17771
  }
17757
17772
  }
17758
17773
  if (result.data.result.missing.length > 0 && !options.json) {
17759
- process.exit(1);
17774
+ process.exit(ExitCode.VALIDATION_ERROR);
17760
17775
  }
17761
17776
  process.exit(ExitCode.SUCCESS);
17762
17777
  } catch (error2) {
@@ -18570,6 +18585,7 @@ async function executeDiff(projectRoot, options = {}) {
18570
18585
  }
18571
18586
 
18572
18587
  // src/cli/commands/diff.ts
18588
+ init_errors();
18573
18589
  function registerDiffCommand(program2) {
18574
18590
  program2.command("diff [commit1] [commit2]").description("\uC2A4\uD399 \uBCC0\uACBD\uC0AC\uD56D \uC2DC\uAC01\uD654").option("--staged", "\uC2A4\uD14C\uC774\uC9D5\uB41C \uBCC0\uACBD\uC0AC\uD56D\uB9CC \uD45C\uC2DC").option("--stat", "\uBCC0\uACBD \uD1B5\uACC4 \uC694\uC57D \uD45C\uC2DC").option("--name-only", "\uBCC0\uACBD\uB41C \uD30C\uC77C\uBA85\uB9CC \uD45C\uC2DC").option("--json", "JSON \uD615\uC2DD\uC73C\uB85C \uCD9C\uB825").option("--no-color", "\uCEEC\uB7EC \uCD9C\uB825 \uBE44\uD65C\uC131\uD654").option("-s, --spec <id>", "\uD2B9\uC815 \uC2A4\uD399\uB9CC \uBE44\uAD50").action(async (commit1, commit2, options) => {
18575
18591
  const projectRoot = process.cwd();
@@ -18584,10 +18600,12 @@ function registerDiffCommand(program2) {
18584
18600
  commit2
18585
18601
  });
18586
18602
  if (!result.success) {
18587
- console.error(`\uC624\uB958: ${result.error?.message}`);
18588
- process.exit(1);
18603
+ error(getErrorMessage(result.error));
18604
+ process.exit(ExitCode.GENERAL_ERROR);
18605
+ }
18606
+ if (result.data?.output) {
18607
+ console.log(result.data.output);
18589
18608
  }
18590
- console.log(result.data?.output);
18591
18609
  });
18592
18610
  }
18593
18611
 
@@ -19579,6 +19597,7 @@ function formatExportResult(result) {
19579
19597
  }
19580
19598
 
19581
19599
  // src/cli/commands/export.ts
19600
+ init_errors();
19582
19601
  function registerExportCommand(program2) {
19583
19602
  program2.command("export [specId...]").description("\uC2A4\uD399\uC744 HTML, JSON \uB4F1 \uB2E4\uC591\uD55C \uD615\uC2DD\uC73C\uB85C \uB0B4\uBCF4\uB0B4\uAE30").option("-f, --format <format>", "\uCD9C\uB825 \uD615\uC2DD (html, json, markdown, pdf)", "html").option("-o, --output <path>", "\uCD9C\uB825 \uD30C\uC77C \uACBD\uB85C").option("--theme <theme>", "\uD14C\uB9C8 (light, dark)", "light").option("--all", "\uC804\uCCB4 \uC2A4\uD399 \uB0B4\uBCF4\uB0B4\uAE30", false).option("--toc", "\uBAA9\uCC28 \uD3EC\uD568", true).option("--no-toc", "\uBAA9\uCC28 \uC81C\uC678").option("--include-constitution", "Constitution \uD3EC\uD568", false).option("--include-changes", "\uBCC0\uACBD \uC81C\uC548 \uD3EC\uD568", false).option("--json", "JSON \uD615\uC2DD \uCD9C\uB825 (\uACB0\uACFC \uBA54\uD0C0\uC815\uBCF4)").action(async (specIds, options) => {
19584
19603
  const projectRoot = process.cwd();
@@ -19598,7 +19617,10 @@ function registerExportCommand(program2) {
19598
19617
  console.log(formatExportResult(result));
19599
19618
  }
19600
19619
  if (!result.success) {
19601
- process.exit(1);
19620
+ if (result.error) {
19621
+ error("\uB0B4\uBCF4\uB0B4\uAE30 \uC2E4\uD328", result.error);
19622
+ }
19623
+ process.exit(ExitCode.GENERAL_ERROR);
19602
19624
  }
19603
19625
  });
19604
19626
  }
@@ -19985,6 +20007,7 @@ function registerDomainCommand(program2) {
19985
20007
  init_types();
19986
20008
  init_fs();
19987
20009
  import chalk3 from "chalk";
20010
+ init_errors();
19988
20011
  async function executeContextSet(domainIds, options, projectPath) {
19989
20012
  const root = projectPath || await findSddRoot(process.cwd());
19990
20013
  if (!root) {
@@ -20078,18 +20101,18 @@ function registerContextCommand(program2) {
20078
20101
  context.command("set").description("\uC791\uC5C5 \uCEE8\uD14D\uC2A4\uD2B8 \uC124\uC815").argument("<domains...>", "\uD65C\uC131\uD654\uD560 \uB3C4\uBA54\uC778 ID \uBAA9\uB85D").option("--no-include-deps", "\uC758\uC874\uC131 \uB3C4\uBA54\uC778 \uC790\uB3D9 \uD3EC\uD568 \uBE44\uD65C\uC131\uD654").action(async (domains, opts) => {
20079
20102
  const result = await executeContextSet(domains, { includeDeps: opts.includeDeps });
20080
20103
  if (!result.success) {
20081
- console.error(chalk3.red(`\uC624\uB958: ${result.error.message}`));
20082
- process.exit(1);
20104
+ error(getResultErrorMessage(result.error));
20105
+ process.exit(ExitCode.GENERAL_ERROR);
20083
20106
  }
20084
- console.log(chalk3.green("\uCEE8\uD14D\uC2A4\uD2B8\uAC00 \uC124\uC815\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
20107
+ success2("\uCEE8\uD14D\uC2A4\uD2B8\uAC00 \uC124\uC815\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
20085
20108
  console.log("");
20086
20109
  console.log(formatContextInfo(result.data));
20087
20110
  });
20088
20111
  context.command("show").description("\uD604\uC7AC \uCEE8\uD14D\uC2A4\uD2B8 \uD45C\uC2DC").option("--json", "JSON \uD615\uC2DD\uC73C\uB85C \uCD9C\uB825").action(async (opts) => {
20089
20112
  const result = await executeContextShow();
20090
20113
  if (!result.success) {
20091
- console.error(chalk3.red(`\uC624\uB958: ${result.error.message}`));
20092
- process.exit(1);
20114
+ error(getResultErrorMessage(result.error));
20115
+ process.exit(ExitCode.GENERAL_ERROR);
20093
20116
  }
20094
20117
  if (opts.json) {
20095
20118
  console.log(JSON.stringify(result.data, null, 2));
@@ -20100,43 +20123,43 @@ function registerContextCommand(program2) {
20100
20123
  context.command("clear").description("\uCEE8\uD14D\uC2A4\uD2B8 \uD574\uC81C").action(async () => {
20101
20124
  const result = await executeContextClear();
20102
20125
  if (!result.success) {
20103
- console.error(chalk3.red(`\uC624\uB958: ${result.error.message}`));
20104
- process.exit(1);
20126
+ error(getResultErrorMessage(result.error));
20127
+ process.exit(ExitCode.GENERAL_ERROR);
20105
20128
  }
20106
- console.log(chalk3.green("\uCEE8\uD14D\uC2A4\uD2B8\uAC00 \uD574\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
20129
+ success2("\uCEE8\uD14D\uC2A4\uD2B8\uAC00 \uD574\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
20107
20130
  });
20108
20131
  context.command("add").description("\uCEE8\uD14D\uC2A4\uD2B8\uC5D0 \uB3C4\uBA54\uC778 \uCD94\uAC00").argument("<domain>", "\uCD94\uAC00\uD560 \uB3C4\uBA54\uC778 ID").option("--no-include-deps", "\uC758\uC874\uC131 \uB3C4\uBA54\uC778 \uC790\uB3D9 \uD3EC\uD568 \uBE44\uD65C\uC131\uD654").action(async (domain, opts) => {
20109
20132
  const result = await executeContextAdd(domain, { includeDeps: opts.includeDeps });
20110
20133
  if (!result.success) {
20111
- console.error(chalk3.red(`\uC624\uB958: ${result.error.message}`));
20112
- process.exit(1);
20134
+ error(getResultErrorMessage(result.error));
20135
+ process.exit(ExitCode.GENERAL_ERROR);
20113
20136
  }
20114
- console.log(chalk3.green(`\uB3C4\uBA54\uC778 "${domain}"\uC774(\uAC00) \uCEE8\uD14D\uC2A4\uD2B8\uC5D0 \uCD94\uAC00\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`));
20137
+ success2(`\uB3C4\uBA54\uC778 "${domain}"\uC774(\uAC00) \uCEE8\uD14D\uC2A4\uD2B8\uC5D0 \uCD94\uAC00\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`);
20115
20138
  console.log("");
20116
20139
  console.log(formatContextInfo(result.data));
20117
20140
  });
20118
20141
  context.command("remove").alias("rm").description("\uCEE8\uD14D\uC2A4\uD2B8\uC5D0\uC11C \uB3C4\uBA54\uC778 \uC81C\uAC70").argument("<domain>", "\uC81C\uAC70\uD560 \uB3C4\uBA54\uC778 ID").action(async (domain) => {
20119
20142
  const result = await executeContextRemove(domain);
20120
20143
  if (!result.success) {
20121
- console.error(chalk3.red(`\uC624\uB958: ${result.error.message}`));
20122
- process.exit(1);
20144
+ error(getResultErrorMessage(result.error));
20145
+ process.exit(ExitCode.GENERAL_ERROR);
20123
20146
  }
20124
- console.log(chalk3.green(`\uB3C4\uBA54\uC778 "${domain}"\uC774(\uAC00) \uCEE8\uD14D\uC2A4\uD2B8\uC5D0\uC11C \uC81C\uAC70\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`));
20147
+ success2(`\uB3C4\uBA54\uC778 "${domain}"\uC774(\uAC00) \uCEE8\uD14D\uC2A4\uD2B8\uC5D0\uC11C \uC81C\uAC70\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`);
20125
20148
  console.log("");
20126
20149
  console.log(formatContextInfo(result.data));
20127
20150
  });
20128
20151
  context.command("specs").description("\uCEE8\uD14D\uC2A4\uD2B8\uC5D0 \uD3EC\uD568\uB41C \uC2A4\uD399 \uBAA9\uB85D").option("--json", "JSON \uD615\uC2DD\uC73C\uB85C \uCD9C\uB825").action(async (opts) => {
20129
20152
  const result = await executeContextSpecs(opts);
20130
20153
  if (!result.success) {
20131
- console.error(chalk3.red(`\uC624\uB958: ${result.error.message}`));
20132
- process.exit(1);
20154
+ error(getResultErrorMessage(result.error));
20155
+ process.exit(ExitCode.GENERAL_ERROR);
20133
20156
  }
20134
20157
  if (opts.json) {
20135
20158
  console.log(JSON.stringify(result.data, null, 2));
20136
20159
  } else {
20137
20160
  const { active, readOnly } = result.data;
20138
20161
  if (active.length === 0 && readOnly.length === 0) {
20139
- console.log(chalk3.yellow("\uCEE8\uD14D\uC2A4\uD2B8\uC5D0 \uC2A4\uD399\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
20162
+ warn("\uCEE8\uD14D\uC2A4\uD2B8\uC5D0 \uC2A4\uD399\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
20140
20163
  return;
20141
20164
  }
20142
20165
  console.log(chalk3.bold("\uCEE8\uD14D\uC2A4\uD2B8 \uC2A4\uD399 \uBAA9\uB85D:"));
@@ -20159,8 +20182,8 @@ function registerContextCommand(program2) {
20159
20182
  context.action(async () => {
20160
20183
  const result = await executeContextShow();
20161
20184
  if (!result.success) {
20162
- console.error(chalk3.red(`\uC624\uB958: ${result.error.message}`));
20163
- process.exit(1);
20185
+ error(getResultErrorMessage(result.error));
20186
+ process.exit(ExitCode.GENERAL_ERROR);
20164
20187
  }
20165
20188
  console.log(formatContextInfo(result.data));
20166
20189
  });
@@ -20171,6 +20194,7 @@ init_fs();
20171
20194
  import path57 from "path";
20172
20195
  import chalk10 from "chalk";
20173
20196
  init_errors();
20197
+ init_types();
20174
20198
 
20175
20199
  // src/integrations/serena/types.ts
20176
20200
  var SymbolKindNames = {
@@ -20801,12 +20825,12 @@ function inferDomainsFromDirectories(directories, files) {
20801
20825
  }).sort(([, a], [, b]) => b.files.length - a.files.length).slice(0, 10);
20802
20826
  const totalFiles = files.length;
20803
20827
  return sortedDomains.map(([name, info2]) => {
20828
+ const estimatedSymbolCount = info2.files.length * 5;
20804
20829
  const domain = {
20805
20830
  name,
20806
20831
  path: info2.path,
20807
20832
  fileCount: info2.files.length,
20808
- symbolCount: 0
20809
- // TODO: 심볼 분석 시 업데이트
20833
+ symbolCount: estimatedSymbolCount
20810
20834
  };
20811
20835
  return {
20812
20836
  ...domain,
@@ -22896,16 +22920,12 @@ var aiAssistant = new AIAssistant();
22896
22920
 
22897
22921
  // src/cli/commands/reverse.ts
22898
22922
  import { promises as fs38 } from "fs";
22899
- async function handleScan(targetPath, options) {
22900
- const sddRoot = await findSddRoot();
22923
+ async function executeScanCommand(targetPath, options, projectRoot) {
22924
+ const sddRoot = projectRoot || await findSddRoot();
22901
22925
  if (!sddRoot) {
22902
- error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd init`\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
22903
- process.exit(ExitCode.GENERAL_ERROR);
22926
+ return failure(new Error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd init`\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694."));
22904
22927
  }
22905
22928
  const scanPath2 = targetPath ? path57.resolve(targetPath) : sddRoot;
22906
- if (!options.quiet) {
22907
- info(`\uC2A4\uCE94 \uC911: ${scanPath2}`);
22908
- }
22909
22929
  const scanResult = await scanProject(scanPath2, {
22910
22930
  depth: options.depth,
22911
22931
  include: options.include,
@@ -22913,15 +22933,51 @@ async function handleScan(targetPath, options) {
22913
22933
  language: options.language
22914
22934
  });
22915
22935
  if (!scanResult.success) {
22916
- error(scanResult.error.message);
22917
- process.exit(ExitCode.GENERAL_ERROR);
22936
+ return failure(scanResult.error);
22918
22937
  }
22919
22938
  const result = scanResult.data;
22920
22939
  const sddPath = path57.join(sddRoot, ".sdd");
22921
- const metaResult = await addScanToMeta(sddPath, result);
22922
- if (!metaResult.success && !options.quiet) {
22923
- warn("\uC2A4\uCE94 \uBA54\uD0C0\uB370\uC774\uD130 \uC800\uC7A5 \uC2E4\uD328");
22940
+ await addScanToMeta(sddPath, result);
22941
+ let domainsCreated = 0;
22942
+ let domainsSkipped = 0;
22943
+ const shouldCreateDomains = options.createDomains !== false;
22944
+ if (shouldCreateDomains && result.summary.suggestedDomains.length > 0) {
22945
+ const domainService = createDomainService(sddRoot);
22946
+ const existingDomainsResult = await domainService.list();
22947
+ const existingDomainIds = existingDomainsResult.success ? existingDomainsResult.data.map((d) => d.id) : [];
22948
+ for (const suggested of result.summary.suggestedDomains) {
22949
+ if (existingDomainIds.includes(suggested.name)) {
22950
+ domainsSkipped++;
22951
+ continue;
22952
+ }
22953
+ const createResult = await domainService.create(suggested.name, {
22954
+ description: `${suggested.name} \uB3C4\uBA54\uC778 (reverse scan\uC73C\uB85C \uC790\uB3D9 \uC0DD\uC131)`,
22955
+ path: suggested.path
22956
+ });
22957
+ if (createResult.success) {
22958
+ domainsCreated++;
22959
+ }
22960
+ }
22924
22961
  }
22962
+ return success({
22963
+ result,
22964
+ sddRoot,
22965
+ sddPath,
22966
+ domainsCreated,
22967
+ domainsSkipped
22968
+ });
22969
+ }
22970
+ async function handleScan(targetPath, options) {
22971
+ if (!options.quiet) {
22972
+ const scanPath2 = targetPath ? path57.resolve(targetPath) : process.cwd();
22973
+ info(`\uC2A4\uCE94 \uC911: ${scanPath2}`);
22974
+ }
22975
+ const commandResult = await executeScanCommand(targetPath, options);
22976
+ if (!commandResult.success) {
22977
+ error(commandResult.error.message);
22978
+ process.exit(ExitCode.GENERAL_ERROR);
22979
+ }
22980
+ const { result, sddRoot, sddPath, domainsCreated, domainsSkipped } = commandResult.data;
22925
22981
  if (options.compare) {
22926
22982
  const lastScan = await getLastScan(sddPath);
22927
22983
  if (lastScan) {
@@ -22947,37 +23003,16 @@ async function handleScan(targetPath, options) {
22947
23003
  error(`\uACB0\uACFC \uC800\uC7A5 \uC2E4\uD328: ${error2}`);
22948
23004
  }
22949
23005
  }
22950
- const shouldCreateDomains = options.createDomains !== false;
22951
- if (shouldCreateDomains && result.summary.suggestedDomains.length > 0) {
22952
- const domainService = createDomainService(sddRoot);
22953
- const existingDomainsResult = await domainService.list();
22954
- const existingDomainIds = existingDomainsResult.success ? existingDomainsResult.data.map((d) => d.id) : [];
22955
- let createdCount = 0;
22956
- let skippedCount = 0;
22957
- for (const suggested of result.summary.suggestedDomains) {
22958
- if (existingDomainIds.includes(suggested.name)) {
22959
- skippedCount++;
22960
- continue;
22961
- }
22962
- const createResult = await domainService.create(suggested.name, {
22963
- description: `${suggested.name} \uB3C4\uBA54\uC778 (reverse scan\uC73C\uB85C \uC790\uB3D9 \uC0DD\uC131)`,
22964
- path: suggested.path
22965
- });
22966
- if (createResult.success) {
22967
- createdCount++;
22968
- }
23006
+ if (!options.quiet && (domainsCreated > 0 || domainsSkipped > 0)) {
23007
+ console.log("");
23008
+ console.log(chalk10.bold("\u{1F4C1} \uB3C4\uBA54\uC778 \uC790\uB3D9 \uC0DD\uC131:"));
23009
+ if (domainsCreated > 0) {
23010
+ console.log(chalk10.green(` \u2705 ${domainsCreated}\uAC1C \uB3C4\uBA54\uC778 \uC0DD\uC131\uB428`));
22969
23011
  }
22970
- if (!options.quiet && (createdCount > 0 || skippedCount > 0)) {
22971
- console.log("");
22972
- console.log(chalk10.bold("\u{1F4C1} \uB3C4\uBA54\uC778 \uC790\uB3D9 \uC0DD\uC131:"));
22973
- if (createdCount > 0) {
22974
- console.log(chalk10.green(` \u2705 ${createdCount}\uAC1C \uB3C4\uBA54\uC778 \uC0DD\uC131\uB428`));
22975
- }
22976
- if (skippedCount > 0) {
22977
- console.log(chalk10.dim(` \u23ED\uFE0F ${skippedCount}\uAC1C \uB3C4\uBA54\uC778 \uC774\uBBF8 \uC874\uC7AC (\uAC74\uB108\uB700)`));
22978
- }
22979
- console.log("");
23012
+ if (domainsSkipped > 0) {
23013
+ console.log(chalk10.dim(` \u23ED\uFE0F ${domainsSkipped}\uAC1C \uB3C4\uBA54\uC778 \uC774\uBBF8 \uC874\uC7AC (\uAC74\uB108\uB700)`));
22980
23014
  }
23015
+ console.log("");
22981
23016
  }
22982
23017
  if (!options.skipSerenaCheck) {
22983
23018
  const serenaCheck = await ensureSerenaAvailable("scan", { skipSerenaCheck: true, quiet: true });
@@ -22992,14 +23027,52 @@ async function handleScan(targetPath, options) {
22992
23027
  console.log("");
22993
23028
  }
22994
23029
  }
22995
- async function handleExtract(targetPath, options) {
22996
- const sddRoot = await findSddRoot();
23030
+ async function executeExtractCommand(targetPath, options, onProgress, projectRoot) {
23031
+ const sddRoot = projectRoot || await findSddRoot();
22997
23032
  if (!sddRoot) {
22998
- error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd init`\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
22999
- process.exit(ExitCode.GENERAL_ERROR);
23033
+ return failure(new Error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd init`\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694."));
23000
23034
  }
23001
23035
  const extractPath = targetPath ? path57.resolve(targetPath) : sddRoot;
23036
+ const scanResult = await scanProject(extractPath, { depth: 5 });
23037
+ if (!scanResult.success) {
23038
+ return failure(scanResult.error);
23039
+ }
23040
+ const extractResult = await extractSpecs(scanResult.data, {
23041
+ depth: options.depth || "medium",
23042
+ ai: options.ai,
23043
+ domain: options.domain
23044
+ }, onProgress);
23045
+ if (!extractResult.success) {
23046
+ return failure(extractResult.error);
23047
+ }
23048
+ const result = extractResult.data;
23049
+ if (result.specs.length === 0) {
23050
+ return success({
23051
+ specs: [],
23052
+ symbolCount: result.symbolCount,
23053
+ skippedCount: result.skippedCount,
23054
+ overallConfidence: result.overallConfidence
23055
+ });
23056
+ }
23057
+ const sddPath = path57.join(sddRoot, ".sdd");
23058
+ const saveResult = await saveExtractedSpecs(sddPath, result, "json");
23059
+ if (!saveResult.success) {
23060
+ return failure(saveResult.error);
23061
+ }
23062
+ await updateExtractionStatus(sddPath, {
23063
+ extractedCount: result.specs.length,
23064
+ pendingReviewCount: result.specs.length
23065
+ });
23066
+ return success({
23067
+ specs: result.specs.map((s) => ({ id: s.id, confidence: s.confidence })),
23068
+ symbolCount: result.symbolCount,
23069
+ skippedCount: result.skippedCount,
23070
+ overallConfidence: result.overallConfidence
23071
+ });
23072
+ }
23073
+ async function handleExtract(targetPath, options) {
23002
23074
  if (!options.quiet) {
23075
+ const extractPath = targetPath ? path57.resolve(targetPath) : process.cwd();
23003
23076
  info(`\uCD94\uCD9C \uC911: ${extractPath}`);
23004
23077
  if (options.depth) {
23005
23078
  info(`\uAE4A\uC774: ${options.depth}`);
@@ -23008,45 +23081,24 @@ async function handleExtract(targetPath, options) {
23008
23081
  info("AI \uCD94\uB860 \uD65C\uC131\uD654\uB428");
23009
23082
  }
23010
23083
  }
23011
- const scanResult = await scanProject(extractPath, {
23012
- depth: 5
23013
- });
23014
- if (!scanResult.success) {
23015
- error(scanResult.error.message);
23016
- process.exit(ExitCode.GENERAL_ERROR);
23017
- }
23018
- const extractResult = await extractSpecs(scanResult.data, {
23019
- depth: options.depth || "medium",
23020
- ai: options.ai,
23021
- domain: options.domain
23022
- }, (progress) => {
23084
+ const commandResult = await executeExtractCommand(targetPath, options, (progress) => {
23023
23085
  if (!options.quiet) {
23024
23086
  process.stdout.write(`\r \uCC98\uB9AC \uC911: ${progress.processedSymbols}/${progress.totalSymbols} \uC2EC\uBCFC, ${progress.specsGenerated} \uC2A4\uD399 \uC0DD\uC131\uB428`);
23025
23087
  }
23026
23088
  });
23027
- if (!extractResult.success) {
23028
- error(extractResult.error.message);
23089
+ if (!commandResult.success) {
23090
+ error(commandResult.error.message);
23029
23091
  process.exit(ExitCode.GENERAL_ERROR);
23030
23092
  }
23031
23093
  if (!options.quiet) {
23032
23094
  console.log("");
23033
23095
  }
23034
- const result = extractResult.data;
23096
+ const result = commandResult.data;
23035
23097
  if (result.specs.length === 0) {
23036
23098
  warn("\uCD94\uCD9C\uB41C \uC2A4\uD399\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uD504\uB85C\uC81D\uD2B8\uC5D0 \uBD84\uC11D \uAC00\uB2A5\uD55C \uC2EC\uBCFC\uC774 \uC5C6\uAC70\uB098 Serena MCP\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");
23037
23099
  console.log(chalk10.dim("\n\u{1F4A1} Serena MCP\uB97C \uC5F0\uACB0\uD558\uBA74 \uC2EC\uBCFC \uC218\uC900 \uBD84\uC11D\uC774 \uAC00\uB2A5\uD569\uB2C8\uB2E4."));
23038
23100
  return;
23039
23101
  }
23040
- const sddPath = path57.join(sddRoot, ".sdd");
23041
- const saveResult = await saveExtractedSpecs(sddPath, result, "json");
23042
- if (!saveResult.success) {
23043
- error(saveResult.error.message);
23044
- process.exit(ExitCode.GENERAL_ERROR);
23045
- }
23046
- await updateExtractionStatus(sddPath, {
23047
- extractedCount: result.specs.length,
23048
- pendingReviewCount: result.specs.length
23049
- });
23050
23102
  console.log("");
23051
23103
  console.log(chalk10.bold("\u{1F4C4} \uC2A4\uD399 \uCD94\uCD9C \uC644\uB8CC"));
23052
23104
  console.log("\u2500".repeat(40));
@@ -23067,64 +23119,96 @@ async function handleExtract(targetPath, options) {
23067
23119
  console.log(" sdd reverse review # \uCD94\uCD9C\uB41C \uC2A4\uD399 \uB9AC\uBDF0");
23068
23120
  console.log("");
23069
23121
  }
23070
- async function handleReview(specId, options) {
23071
- const sddRoot = await findSddRoot();
23122
+ async function executeReviewCommand(specId, options, projectRoot) {
23123
+ const sddRoot = projectRoot || await findSddRoot();
23072
23124
  if (!sddRoot) {
23073
- error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
23074
- process.exit(ExitCode.GENERAL_ERROR);
23125
+ return failure(new Error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
23075
23126
  }
23076
23127
  const sddPath = path57.join(sddRoot, ".sdd");
23077
23128
  const draftsPath = path57.join(sddPath, ".reverse-drafts");
23078
23129
  if (!await fileExists(draftsPath)) {
23079
- warn("\uCD94\uCD9C\uB41C \uC2A4\uD399\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd reverse extract`\uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
23080
- return;
23130
+ return success({ action: "no_drafts" });
23081
23131
  }
23082
23132
  const loadResult = await loadReviewList(sddPath);
23083
23133
  if (!loadResult.success) {
23084
- error(loadResult.error.message);
23085
- process.exit(ExitCode.GENERAL_ERROR);
23134
+ return failure(loadResult.error);
23086
23135
  }
23087
23136
  const items = loadResult.data;
23088
23137
  if (items.length === 0) {
23089
- warn("\uB9AC\uBDF0\uD560 \uC2A4\uD399\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
23090
- return;
23138
+ return success({ action: "empty" });
23091
23139
  }
23092
23140
  if (specId) {
23093
23141
  const item = items.find((i) => i.specId === specId || i.specId.endsWith(`/${specId}`));
23094
23142
  if (!item) {
23095
- error(`\uC2A4\uD399\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${specId}`);
23096
- process.exit(ExitCode.GENERAL_ERROR);
23143
+ return failure(new Error(`\uC2A4\uD399\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${specId}`));
23097
23144
  }
23098
23145
  if (options.approve) {
23099
23146
  const result = await approveSpec(sddPath, item.specId);
23100
- if (result.success) {
23101
- success2(`\uC2B9\uC778\uB428: ${item.specId}`);
23102
- console.log("");
23103
- console.log(chalk10.bold("\u{1F4A1} \uB2E4\uC74C \uB2E8\uACC4:"));
23104
- console.log(" sdd reverse finalize --all # \uC2B9\uC778\uB41C \uC2A4\uD399 \uD655\uC815");
23105
- } else {
23106
- error(result.error.message);
23107
- process.exit(ExitCode.GENERAL_ERROR);
23147
+ if (!result.success) {
23148
+ return failure(result.error);
23108
23149
  }
23109
- return;
23150
+ return success({ action: "approved", specId: item.specId });
23110
23151
  }
23111
23152
  if (options.reject) {
23112
23153
  const result = await rejectSpec(sddPath, item.specId, options.reason || "\uC0AC\uC6A9\uC790\uC5D0 \uC758\uD574 \uAC70\uBD80\uB428");
23113
- if (result.success) {
23114
- success2(`\uAC70\uBD80\uB428: ${item.specId}`);
23115
- } else {
23116
- error(result.error.message);
23117
- process.exit(ExitCode.GENERAL_ERROR);
23154
+ if (!result.success) {
23155
+ return failure(result.error);
23118
23156
  }
23119
- return;
23157
+ return success({ action: "rejected", specId: item.specId });
23120
23158
  }
23121
- console.log(formatSpecDetail(item));
23122
- console.log(chalk10.bold("\u{1F4A1} \uC791\uC5C5:"));
23123
- console.log(` sdd reverse review ${specId} --approve # \uC2B9\uC778`);
23124
- console.log(` sdd reverse review ${specId} --reject # \uAC70\uBD80`);
23125
- console.log("");
23126
- return;
23159
+ return success({ action: "detail", specId: item.specId, sddPath });
23160
+ }
23161
+ return success({ action: "list", sddPath });
23162
+ }
23163
+ async function handleReview(specId, options) {
23164
+ const commandResult = await executeReviewCommand(specId, options);
23165
+ if (!commandResult.success) {
23166
+ error(commandResult.error.message);
23167
+ process.exit(ExitCode.GENERAL_ERROR);
23127
23168
  }
23169
+ const result = commandResult.data;
23170
+ switch (result.action) {
23171
+ case "no_drafts":
23172
+ warn("\uCD94\uCD9C\uB41C \uC2A4\uD399\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd reverse extract`\uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
23173
+ return;
23174
+ case "empty":
23175
+ warn("\uB9AC\uBDF0\uD560 \uC2A4\uD399\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
23176
+ return;
23177
+ case "approved":
23178
+ success2(`\uC2B9\uC778\uB428: ${result.specId}`);
23179
+ console.log("");
23180
+ console.log(chalk10.bold("\u{1F4A1} \uB2E4\uC74C \uB2E8\uACC4:"));
23181
+ console.log(" sdd reverse finalize --all # \uC2B9\uC778\uB41C \uC2A4\uD399 \uD655\uC815");
23182
+ return;
23183
+ case "rejected":
23184
+ success2(`\uAC70\uBD80\uB428: ${result.specId}`);
23185
+ return;
23186
+ case "detail":
23187
+ if (result.sddPath && result.specId) {
23188
+ const detailLoadResult = await loadReviewList(result.sddPath);
23189
+ if (detailLoadResult.success) {
23190
+ const detailItem = detailLoadResult.data.find(
23191
+ (i) => i.specId === result.specId || i.specId.endsWith(`/${result.specId}`)
23192
+ );
23193
+ if (detailItem) {
23194
+ console.log(formatSpecDetail(detailItem));
23195
+ console.log(chalk10.bold("\u{1F4A1} \uC791\uC5C5:"));
23196
+ console.log(` sdd reverse review ${result.specId} --approve # \uC2B9\uC778`);
23197
+ console.log(` sdd reverse review ${result.specId} --reject # \uAC70\uBD80`);
23198
+ console.log("");
23199
+ }
23200
+ }
23201
+ }
23202
+ return;
23203
+ case "list":
23204
+ break;
23205
+ }
23206
+ const sddRoot = await findSddRoot();
23207
+ if (!sddRoot) return;
23208
+ const sddPath = path57.join(sddRoot, ".sdd");
23209
+ const loadResult = await loadReviewList(sddPath);
23210
+ if (!loadResult.success) return;
23211
+ const items = loadResult.data;
23128
23212
  console.log(formatReviewList(items));
23129
23213
  const pending = items.filter((i) => i.status === "pending");
23130
23214
  if (pending.length > 0) {
@@ -23139,55 +23223,78 @@ async function handleReview(specId, options) {
23139
23223
  console.log("");
23140
23224
  }
23141
23225
  }
23142
- async function handleFinalize(specId, options) {
23143
- const sddRoot = await findSddRoot();
23226
+ async function executeFinalizeCommand(specId, options, projectRoot) {
23227
+ const sddRoot = projectRoot || await findSddRoot();
23144
23228
  if (!sddRoot) {
23145
- error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
23146
- process.exit(ExitCode.GENERAL_ERROR);
23147
- }
23148
- if (!options.quiet) {
23149
- if (specId) {
23150
- info(`\uD655\uC815 \uC911: ${specId}`);
23151
- } else if (options.all) {
23152
- info("\uC2B9\uC778\uB41C \uBAA8\uB4E0 \uC2A4\uD399 \uD655\uC815 \uC911");
23153
- } else if (options.domain) {
23154
- info(`\uB3C4\uBA54\uC778 \uD655\uC815 \uC911: ${options.domain}`);
23155
- }
23229
+ return failure(new Error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
23156
23230
  }
23157
- let result;
23158
23231
  if (specId) {
23159
23232
  const finalizeResult = await finalizeById(sddRoot, specId);
23160
23233
  if (!finalizeResult.success) {
23161
- error(finalizeResult.error.message);
23162
- process.exit(ExitCode.GENERAL_ERROR);
23234
+ return failure(finalizeResult.error);
23163
23235
  }
23164
- result = {
23165
- finalized: [finalizeResult.data],
23166
- skipped: [],
23167
- errors: []
23168
- };
23169
- } else if (options.domain) {
23236
+ return success({
23237
+ action: "single",
23238
+ data: {
23239
+ finalized: [finalizeResult.data],
23240
+ skipped: [],
23241
+ errors: []
23242
+ }
23243
+ });
23244
+ }
23245
+ if (options.domain) {
23170
23246
  const finalizeResult = await finalizeDomain(sddRoot, options.domain);
23171
23247
  if (!finalizeResult.success) {
23172
- error(finalizeResult.error.message);
23173
- process.exit(ExitCode.GENERAL_ERROR);
23248
+ return failure(finalizeResult.error);
23174
23249
  }
23175
- result = finalizeResult.data;
23176
- } else if (options.all) {
23250
+ return success({
23251
+ action: "domain",
23252
+ data: finalizeResult.data
23253
+ });
23254
+ }
23255
+ if (options.all) {
23177
23256
  const finalizeResult = await finalizeAllApproved(sddRoot);
23178
23257
  if (!finalizeResult.success) {
23179
- error(finalizeResult.error.message);
23180
- process.exit(ExitCode.GENERAL_ERROR);
23258
+ return failure(finalizeResult.error);
23181
23259
  }
23182
- result = finalizeResult.data;
23183
- } else {
23184
- warn("\uD655\uC815\uD560 \uB300\uC0C1\uC744 \uC9C0\uC815\uD558\uC138\uC694:");
23185
- console.log(" sdd reverse finalize <spec-id> # \uD2B9\uC815 \uC2A4\uD399");
23186
- console.log(" sdd reverse finalize --all # \uBAA8\uB4E0 \uC2B9\uC778 \uC2A4\uD399");
23187
- console.log(" sdd reverse finalize -d <domain> # \uD2B9\uC815 \uB3C4\uBA54\uC778");
23188
- return;
23260
+ return success({
23261
+ action: "all",
23262
+ data: finalizeResult.data
23263
+ });
23264
+ }
23265
+ return success({ action: "no_target" });
23266
+ }
23267
+ async function handleFinalize(specId, options) {
23268
+ if (!options.quiet) {
23269
+ if (specId) {
23270
+ info(`\uD655\uC815 \uC911: ${specId}`);
23271
+ } else if (options.all) {
23272
+ info("\uC2B9\uC778\uB41C \uBAA8\uB4E0 \uC2A4\uD399 \uD655\uC815 \uC911");
23273
+ } else if (options.domain) {
23274
+ info(`\uB3C4\uBA54\uC778 \uD655\uC815 \uC911: ${options.domain}`);
23275
+ }
23276
+ }
23277
+ const commandResult = await executeFinalizeCommand(specId, options);
23278
+ if (!commandResult.success) {
23279
+ error(commandResult.error.message);
23280
+ process.exit(ExitCode.GENERAL_ERROR);
23281
+ }
23282
+ const result = commandResult.data;
23283
+ switch (result.action) {
23284
+ case "single":
23285
+ case "domain":
23286
+ case "all":
23287
+ if (result.data) {
23288
+ console.log(formatFinalizeResult(result.data));
23289
+ }
23290
+ return;
23291
+ case "no_target":
23292
+ warn("\uD655\uC815\uD560 \uB300\uC0C1\uC744 \uC9C0\uC815\uD558\uC138\uC694:");
23293
+ console.log(" sdd reverse finalize <spec-id> # \uD2B9\uC815 \uC2A4\uD399");
23294
+ console.log(" sdd reverse finalize --all # \uBAA8\uB4E0 \uC2B9\uC778 \uC2A4\uD399");
23295
+ console.log(" sdd reverse finalize -d <domain> # \uD2B9\uC815 \uB3C4\uBA54\uC778");
23296
+ return;
23189
23297
  }
23190
- console.log(formatFinalizeResult(result));
23191
23298
  }
23192
23299
  function showReverseHelp() {
23193
23300
  console.log(`
@@ -23222,9 +23329,20 @@ ${chalk10.bold("\uC6CC\uD06C\uD50C\uB85C\uC6B0:")}
23222
23329
  4. finalize \u2192 \uC815\uC2DD \uC2A4\uD399\uC73C\uB85C \uBCC0\uD658
23223
23330
  `);
23224
23331
  }
23225
- async function handleCheckSerena() {
23226
- const result = await ensureSerenaAvailable("check", { quiet: false });
23332
+ async function executeCheckSerenaCommand() {
23333
+ const result = await ensureSerenaAvailable("check", { quiet: true });
23227
23334
  if (result.success) {
23335
+ return success({ available: true });
23336
+ }
23337
+ return success({ available: false });
23338
+ }
23339
+ async function handleCheckSerena() {
23340
+ const commandResult = await executeCheckSerenaCommand();
23341
+ if (!commandResult.success) {
23342
+ console.log(createInstallGuide());
23343
+ process.exit(ExitCode.GENERAL_ERROR);
23344
+ }
23345
+ if (commandResult.data.available) {
23228
23346
  console.log(chalk10.green("\u2705 Serena MCP \uC0AC\uC6A9 \uAC00\uB2A5"));
23229
23347
  } else {
23230
23348
  console.log(createInstallGuide());