@vibecodetown/mcp-server 2.1.0

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.
Files changed (172) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +269 -0
  3. package/build/auth/gate.js +225 -0
  4. package/build/auth/index.js +55 -0
  5. package/build/auth/public_key.js +27 -0
  6. package/build/auth/token_cache.js +122 -0
  7. package/build/auth/token_verifier.js +103 -0
  8. package/build/bootstrap/doctor.js +115 -0
  9. package/build/bootstrap/installer.js +673 -0
  10. package/build/bootstrap/lock.js +37 -0
  11. package/build/bootstrap/platform.js +26 -0
  12. package/build/bootstrap/registry.js +37 -0
  13. package/build/cache/index.js +147 -0
  14. package/build/cli.js +101 -0
  15. package/build/contracts.js +22 -0
  16. package/build/control_plane/gate.js +161 -0
  17. package/build/control_plane/index.js +6 -0
  18. package/build/dx/activity.js +139 -0
  19. package/build/engine.js +106 -0
  20. package/build/errors.js +171 -0
  21. package/build/generated/activate_input.js +2 -0
  22. package/build/generated/activate_output.js +57 -0
  23. package/build/generated/advisory_review_input.js +2 -0
  24. package/build/generated/advisory_review_output.js +35 -0
  25. package/build/generated/auth_token_file.js +2 -0
  26. package/build/generated/briefing_input.js +2 -0
  27. package/build/generated/briefing_output.js +2 -0
  28. package/build/generated/clinic_bridge_file.js +13 -0
  29. package/build/generated/contracts_bundle_info.js +5 -0
  30. package/build/generated/create_work_order_input.js +2 -0
  31. package/build/generated/create_work_order_output.js +2 -0
  32. package/build/generated/current_work_order_file.js +2 -0
  33. package/build/generated/doctor_input.js +2 -0
  34. package/build/generated/doctor_output.js +24 -0
  35. package/build/generated/execution_result.js +2 -0
  36. package/build/generated/execution_task.js +2 -0
  37. package/build/generated/export_output_input.js +2 -0
  38. package/build/generated/export_output_output.js +2 -0
  39. package/build/generated/finalize_work_input.js +2 -0
  40. package/build/generated/finalize_work_output.js +2 -0
  41. package/build/generated/gate_input.js +2 -0
  42. package/build/generated/gate_output.js +2 -0
  43. package/build/generated/gate_result_v1.js +2 -0
  44. package/build/generated/get_decision_input.js +2 -0
  45. package/build/generated/get_decision_output.js +13 -0
  46. package/build/generated/handoff_to_clinic.js +2 -0
  47. package/build/generated/index.js +75 -0
  48. package/build/generated/inspect_code_input.js +2 -0
  49. package/build/generated/inspect_code_output.js +13 -0
  50. package/build/generated/memory_retrieve_output.js +2 -0
  51. package/build/generated/memory_state_file.js +2 -0
  52. package/build/generated/memory_status_input.js +2 -0
  53. package/build/generated/memory_status_output.js +13 -0
  54. package/build/generated/memory_sync_input.js +2 -0
  55. package/build/generated/memory_sync_output.js +13 -0
  56. package/build/generated/plugin_result.js +2 -0
  57. package/build/generated/react_perf_check_patterns_input.js +2 -0
  58. package/build/generated/react_perf_check_patterns_output.js +2 -0
  59. package/build/generated/react_perf_generate_report_input.js +2 -0
  60. package/build/generated/react_perf_generate_report_output.js +2 -0
  61. package/build/generated/repair_plan_input.js +2 -0
  62. package/build/generated/repair_plan_output.js +2 -0
  63. package/build/generated/run_app_input.js +2 -0
  64. package/build/generated/run_app_output.js +2 -0
  65. package/build/generated/run_state_file.js +13 -0
  66. package/build/generated/scaffold_input.js +2 -0
  67. package/build/generated/scaffold_output.js +2 -0
  68. package/build/generated/search_oss_input.js +2 -0
  69. package/build/generated/search_oss_output.js +2 -0
  70. package/build/generated/selection_validation_result.js +2 -0
  71. package/build/generated/signal_agent_input.js +2 -0
  72. package/build/generated/spec_high_ask_queue_items_file.js +2 -0
  73. package/build/generated/spec_high_clinic_bridge_output.js +2 -0
  74. package/build/generated/spec_high_decision_draft_output.js +2 -0
  75. package/build/generated/spec_high_validate_output.js +2 -0
  76. package/build/generated/status_input.js +2 -0
  77. package/build/generated/status_output.js +2 -0
  78. package/build/generated/submit_decision_input.js +2 -0
  79. package/build/generated/submit_decision_output.js +2 -0
  80. package/build/generated/tool_error_output.js +2 -0
  81. package/build/generated/undo_last_task_input.js +2 -0
  82. package/build/generated/undo_last_task_output.js +2 -0
  83. package/build/generated/update_input.js +2 -0
  84. package/build/generated/update_output.js +2 -0
  85. package/build/generated/vibe_pm_inspection_result.js +2 -0
  86. package/build/generated/vibe_pm_report_markdown.js +2 -0
  87. package/build/generated/vibe_pm_verdict.js +2 -0
  88. package/build/generated/vibe_repo_config.js +2 -0
  89. package/build/generated/vibecoding_helper_answer_output.js +2 -0
  90. package/build/generated/vibecoding_helper_one_loop_selection_output.js +2 -0
  91. package/build/generated/vibecoding_helper_show_ask_queue_output.js +2 -0
  92. package/build/generated/work_order_v1.js +2 -0
  93. package/build/generated/zoekt_evidence_input.js +2 -0
  94. package/build/generated/zoekt_evidence_output.js +2 -0
  95. package/build/index.js +111 -0
  96. package/build/legacy_alias.js +65 -0
  97. package/build/local-mode/bash.js +61 -0
  98. package/build/local-mode/config.js +171 -0
  99. package/build/local-mode/git.js +33 -0
  100. package/build/local-mode/init.js +110 -0
  101. package/build/local-mode/paths.js +24 -0
  102. package/build/local-mode/templates.js +856 -0
  103. package/build/local-mode/work-order.js +41 -0
  104. package/build/resources/index.js +246 -0
  105. package/build/security/input-validator.js +119 -0
  106. package/build/security/path-policy.js +289 -0
  107. package/build/security/sandbox.js +228 -0
  108. package/build/tools/react_perf/check_patterns.js +172 -0
  109. package/build/tools/react_perf/generate_report.js +337 -0
  110. package/build/tools/react_perf/index.js +119 -0
  111. package/build/tools/react_perf/rules/advanced.js +325 -0
  112. package/build/tools/react_perf/rules/async.js +104 -0
  113. package/build/tools/react_perf/rules/bundle.js +101 -0
  114. package/build/tools/react_perf/rules/client.js +186 -0
  115. package/build/tools/react_perf/rules/index.js +74 -0
  116. package/build/tools/react_perf/rules/js.js +148 -0
  117. package/build/tools/react_perf/rules/rendering.js +166 -0
  118. package/build/tools/react_perf/rules/rerender.js +161 -0
  119. package/build/tools/react_perf/rules/server.js +141 -0
  120. package/build/tools/react_perf/types.js +127 -0
  121. package/build/tools/vibe_pm/activate.js +102 -0
  122. package/build/tools/vibe_pm/advisory_review.js +77 -0
  123. package/build/tools/vibe_pm/briefing.js +178 -0
  124. package/build/tools/vibe_pm/context.js +439 -0
  125. package/build/tools/vibe_pm/create_work_order.js +271 -0
  126. package/build/tools/vibe_pm/doc_status_gate.js +370 -0
  127. package/build/tools/vibe_pm/doctor.js +262 -0
  128. package/build/tools/vibe_pm/entity_gate/preflight.js +78 -0
  129. package/build/tools/vibe_pm/export_output.js +135 -0
  130. package/build/tools/vibe_pm/finalize_work.js +393 -0
  131. package/build/tools/vibe_pm/gate.js +33 -0
  132. package/build/tools/vibe_pm/get_decision.js +281 -0
  133. package/build/tools/vibe_pm/index.js +593 -0
  134. package/build/tools/vibe_pm/inspect_code.js +828 -0
  135. package/build/tools/vibe_pm/intent/generator.js +294 -0
  136. package/build/tools/vibe_pm/intent/index.js +5 -0
  137. package/build/tools/vibe_pm/intent/prompt_density.js +227 -0
  138. package/build/tools/vibe_pm/intent/types.js +70 -0
  139. package/build/tools/vibe_pm/intent/verifier.js +237 -0
  140. package/build/tools/vibe_pm/kce/doc_usage.js +51 -0
  141. package/build/tools/vibe_pm/kce/on_finalize.js +11 -0
  142. package/build/tools/vibe_pm/kce/preflight.js +232 -0
  143. package/build/tools/vibe_pm/local_memory.js +26 -0
  144. package/build/tools/vibe_pm/memory_status.js +82 -0
  145. package/build/tools/vibe_pm/memory_sync.js +134 -0
  146. package/build/tools/vibe_pm/modules/decision_snapshot.js +29 -0
  147. package/build/tools/vibe_pm/modules/ensure.js +100 -0
  148. package/build/tools/vibe_pm/modules/fingerprint.js +30 -0
  149. package/build/tools/vibe_pm/modules/fix_dependencies.js +394 -0
  150. package/build/tools/vibe_pm/modules/planning_v1.js +110 -0
  151. package/build/tools/vibe_pm/modules/repo_context.js +56 -0
  152. package/build/tools/vibe_pm/modules/research_v1.js +114 -0
  153. package/build/tools/vibe_pm/modules/skills_v1.js +100 -0
  154. package/build/tools/vibe_pm/pm_language.js +222 -0
  155. package/build/tools/vibe_pm/repair_plan.js +199 -0
  156. package/build/tools/vibe_pm/run_app.js +597 -0
  157. package/build/tools/vibe_pm/run_app_podman.js +64 -0
  158. package/build/tools/vibe_pm/scaffold.js +550 -0
  159. package/build/tools/vibe_pm/search_oss.js +124 -0
  160. package/build/tools/vibe_pm/status.js +153 -0
  161. package/build/tools/vibe_pm/submit_decision.js +87 -0
  162. package/build/tools/vibe_pm/system_design/issue_mapping.js +47 -0
  163. package/build/tools/vibe_pm/system_design/rulebook.js +112 -0
  164. package/build/tools/vibe_pm/system_design/semgrep.js +132 -0
  165. package/build/tools/vibe_pm/types.js +229 -0
  166. package/build/tools/vibe_pm/undo_last_task.js +163 -0
  167. package/build/tools/vibe_pm/update.js +146 -0
  168. package/build/tools/vibe_pm/zoekt_evidence.js +96 -0
  169. package/build/tools.js +269 -0
  170. package/build/version-check.js +239 -0
  171. package/build/vibe-cli.js +631 -0
  172. package/package.json +76 -0
@@ -0,0 +1,134 @@
1
+ // adapters/mcp-ts/src/tools/vibe_pm/memory_sync.ts
2
+ // vibe_pm.memory_sync - Local Memory sync (incremental index update)
3
+ import { runEngine } from "../../engine.js";
4
+ import { safeJsonParse } from "../../cli.js";
5
+ import { validateToolInput } from "../../security/input-validator.js";
6
+ import { resolveProjectId } from "./context.js";
7
+ import { MemorySyncOutputSchema } from "../../generated/memory_sync_output.js";
8
+ const inFlight = new Map();
9
+ function isOfflineMode() {
10
+ const v = (process.env.VIBECODE_OFFLINE ?? "").trim().toLowerCase();
11
+ return v === "1" || v === "true" || v === "yes" || v === "on";
12
+ }
13
+ function defaultPersistDir() {
14
+ return ".vibe/chroma";
15
+ }
16
+ function inFlightKey(args) {
17
+ return `${args.project_id}::${args.docs_root}::${args.persist_dir}::${args.mode}::${args.force_full ? "1" : "0"}`;
18
+ }
19
+ /**
20
+ * vibe_pm.memory_sync - Local Memory sync
21
+ *
22
+ * Internal mapping:
23
+ * → vibecoding-helper memory-sync --project-id ... --docs-root ... --mode ... --force-full ...
24
+ */
25
+ export async function memorySync(input) {
26
+ const basePath = process.cwd();
27
+ validateToolInput({
28
+ project_id: input.project_id,
29
+ custom_input: input.docs_root,
30
+ });
31
+ const project_id = input.project_id ?? resolveProjectId(undefined, basePath);
32
+ const docsRoot = (input.docs_root ?? "docs").trim();
33
+ const persistDir = defaultPersistDir();
34
+ const mode = input.mode ?? "on_demand";
35
+ const forceFull = input.force_full ?? false;
36
+ const key = inFlightKey({
37
+ project_id,
38
+ docs_root: docsRoot,
39
+ persist_dir: persistDir,
40
+ mode,
41
+ force_full: forceFull,
42
+ });
43
+ const existing = inFlight.get(key);
44
+ if (existing)
45
+ return existing;
46
+ const promise = (async () => {
47
+ try {
48
+ const args = [
49
+ "memory-sync",
50
+ "--project-id",
51
+ project_id,
52
+ "--docs-root",
53
+ docsRoot,
54
+ "--mode",
55
+ mode,
56
+ "--persist-dir",
57
+ persistDir
58
+ ];
59
+ if (forceFull)
60
+ args.push("--force-full");
61
+ const startedAt = Date.now();
62
+ const { code, stdout, stderr } = await runEngine("vibecoding-helper", args, { timeoutMs: 300_000 });
63
+ const durationMs = Date.now() - startedAt;
64
+ if (code !== 0) {
65
+ return MemorySyncOutputSchema.parse({
66
+ project_id,
67
+ status: "ERROR",
68
+ summary: "Local Memory 동기화에 실패했습니다.",
69
+ docs_root: docsRoot,
70
+ persist_dir: persistDir,
71
+ collection: "vibe_docs",
72
+ result: {
73
+ scanned_files: 0,
74
+ ingested_files: 0,
75
+ updated_files: 0,
76
+ skipped_files: 0,
77
+ duration_ms: durationMs
78
+ },
79
+ offline_mode: isOfflineMode(),
80
+ issues: [stderr?.trim() ? `engine_error: ${stderr.trim()}` : `engine_exit_code: ${code}`],
81
+ next_action: { tool: "vibe_pm.doctor", reason: "설치/환경 상태를 점검하겠습니다." }
82
+ });
83
+ }
84
+ const parsed = safeJsonParse(stdout);
85
+ if (!parsed.ok) {
86
+ return MemorySyncOutputSchema.parse({
87
+ project_id,
88
+ status: "ERROR",
89
+ summary: "Local Memory 동기화 응답을 해석할 수 없습니다.",
90
+ docs_root: docsRoot,
91
+ persist_dir: persistDir,
92
+ collection: "vibe_docs",
93
+ result: {
94
+ scanned_files: 0,
95
+ ingested_files: 0,
96
+ updated_files: 0,
97
+ skipped_files: 0,
98
+ duration_ms: durationMs
99
+ },
100
+ offline_mode: isOfflineMode(),
101
+ issues: [`invalid_json: ${parsed.error}`],
102
+ next_action: { tool: "vibe_pm.doctor", reason: "설치/환경 상태를 점검하겠습니다." }
103
+ });
104
+ }
105
+ return MemorySyncOutputSchema.parse(parsed.value);
106
+ }
107
+ catch (e) {
108
+ const msg = e instanceof Error ? e.message : String(e);
109
+ return MemorySyncOutputSchema.parse({
110
+ project_id,
111
+ status: "ERROR",
112
+ summary: "Local Memory 동기화 중 오류가 발생했습니다.",
113
+ docs_root: docsRoot,
114
+ persist_dir: persistDir,
115
+ collection: "vibe_docs",
116
+ result: {
117
+ scanned_files: 0,
118
+ ingested_files: 0,
119
+ updated_files: 0,
120
+ skipped_files: 0,
121
+ duration_ms: 0
122
+ },
123
+ offline_mode: isOfflineMode(),
124
+ issues: [msg],
125
+ next_action: { tool: "vibe_pm.doctor", reason: "설치/환경 상태를 점검하겠습니다." }
126
+ });
127
+ }
128
+ })();
129
+ inFlight.set(key, promise);
130
+ promise.finally(() => {
131
+ inFlight.delete(key);
132
+ });
133
+ return promise;
134
+ }
@@ -0,0 +1,29 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ export function loadDecisionSnapshot(specHighRunDir) {
4
+ const p = path.join(specHighRunDir, "decisions", "decision_cards.json");
5
+ if (!fs.existsSync(p))
6
+ return [];
7
+ try {
8
+ const raw = fs.readFileSync(p, "utf-8");
9
+ const doc = JSON.parse(raw);
10
+ const cards = doc.cards ?? [];
11
+ const resolved = cards.filter((c) => c.resolution?.status === "resolved" && c.resolution?.chosen);
12
+ return resolved
13
+ .map((c) => {
14
+ const chosen = c.resolution?.chosen ?? null;
15
+ const chosenLabel = c.options?.find((o) => o.id === chosen)?.label;
16
+ return {
17
+ decision_id: String(c.id ?? ""),
18
+ category: c.category,
19
+ question: c.question,
20
+ chosen: chosen ? String(chosen) : undefined,
21
+ chosen_label: chosenLabel,
22
+ };
23
+ })
24
+ .filter((x) => x.decision_id);
25
+ }
26
+ catch {
27
+ return [];
28
+ }
29
+ }
@@ -0,0 +1,100 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { getSpecHighRunsDir } from "../context.js";
4
+ import { detectRepoContext } from "./repo_context.js";
5
+ import { loadDecisionSnapshot } from "./decision_snapshot.js";
6
+ import { ensureResearchModuleV1 } from "./research_v1.js";
7
+ import { generateSkillsModuleV1 } from "./skills_v1.js";
8
+ import { generatePlanningModuleV1 } from "./planning_v1.js";
9
+ function ensureDir(p) {
10
+ fs.mkdirSync(p, { recursive: true });
11
+ }
12
+ export function getWritableSpecHighRunDir(runId, basePath) {
13
+ const runsRoot = getSpecHighRunsDir(basePath);
14
+ const runDir = path.join(runsRoot, runId);
15
+ ensureDir(runDir);
16
+ return runDir;
17
+ }
18
+ export function writeProjectBriefSnapshot(specHighRunDir, projectBrief) {
19
+ const dir = path.join(specHighRunDir, "modules", "_inputs");
20
+ ensureDir(dir);
21
+ fs.writeFileSync(path.join(dir, "project_brief.txt"), projectBrief.trim() + "\n", "utf-8");
22
+ }
23
+ export function ensureModulesForWorkOrder(opts) {
24
+ const specHighRunDir = getWritableSpecHighRunDir(opts.runId, opts.basePath);
25
+ const repoContext = detectRepoContext(opts.basePath);
26
+ const decisionSnapshot = loadDecisionSnapshot(specHighRunDir);
27
+ if (opts.projectBrief) {
28
+ writeProjectBriefSnapshot(specHighRunDir, opts.projectBrief);
29
+ }
30
+ const research = ensureResearchModuleV1({
31
+ repoRoot: opts.basePath,
32
+ specHighRunDir,
33
+ repoContext,
34
+ decisionSnapshot,
35
+ projectBrief: opts.projectBrief,
36
+ });
37
+ const skills = generateSkillsModuleV1({ repoRoot: opts.basePath, specHighRunDir, repoContext });
38
+ const planning = generatePlanningModuleV1({
39
+ specHighRunDir,
40
+ goal: "implement",
41
+ repoContext,
42
+ skillFingerprint: skills.fingerprint,
43
+ decisionSnapshot,
44
+ });
45
+ return {
46
+ research: { version: "v1", fingerprint: research.fingerprint, path: research.path },
47
+ skills: { version: "v1", fingerprint: skills.fingerprint, path: skills.path },
48
+ planning: { version: "v1", fingerprint: planning.fingerprint, path: planning.path },
49
+ skill_bundle_prompt_path: skills.bundle_prompt_path,
50
+ };
51
+ }
52
+ export function ensureResearchForBriefing(opts) {
53
+ const specHighRunDir = getWritableSpecHighRunDir(opts.runId, opts.basePath);
54
+ const repoContext = detectRepoContext(opts.basePath);
55
+ writeProjectBriefSnapshot(specHighRunDir, opts.projectBrief);
56
+ const research = ensureResearchModuleV1({
57
+ repoRoot: opts.basePath,
58
+ specHighRunDir,
59
+ repoContext,
60
+ decisionSnapshot: [],
61
+ projectBrief: opts.projectBrief,
62
+ });
63
+ return { version: "v1", fingerprint: research.fingerprint, path: research.path };
64
+ }
65
+ export function ensurePlanningForRepair(opts) {
66
+ const specHighRunDir = getWritableSpecHighRunDir(opts.runId, opts.basePath);
67
+ const repoContext = detectRepoContext(opts.basePath);
68
+ const decisionSnapshot = loadDecisionSnapshot(specHighRunDir);
69
+ // Try to reuse existing skill fingerprint if present
70
+ const skillsPath = path.join(specHighRunDir, "modules", "skills", "v1", "selection.json");
71
+ let skillFingerprint = "SKL-unknown";
72
+ if (fs.existsSync(skillsPath)) {
73
+ try {
74
+ const doc = JSON.parse(fs.readFileSync(skillsPath, "utf-8"));
75
+ if (doc.fingerprint)
76
+ skillFingerprint = String(doc.fingerprint);
77
+ }
78
+ catch {
79
+ // ignore
80
+ }
81
+ }
82
+ const planning = generatePlanningModuleV1({
83
+ specHighRunDir,
84
+ goal: opts.goal ?? "repair",
85
+ repoContext,
86
+ skillFingerprint,
87
+ decisionSnapshot,
88
+ });
89
+ return { version: "v1", fingerprint: planning.fingerprint, path: planning.path };
90
+ }
91
+ export function readFileIfExists(p) {
92
+ try {
93
+ if (!fs.existsSync(p))
94
+ return null;
95
+ return fs.readFileSync(p, "utf-8");
96
+ }
97
+ catch {
98
+ return null;
99
+ }
100
+ }
@@ -0,0 +1,30 @@
1
+ import { createHash } from "node:crypto";
2
+ function stableSerialize(value) {
3
+ if (value === null)
4
+ return "null";
5
+ const t = typeof value;
6
+ if (t === "string")
7
+ return JSON.stringify(value);
8
+ if (t === "number")
9
+ return Number.isFinite(value) ? String(value) : JSON.stringify(value);
10
+ if (t === "boolean")
11
+ return value ? "true" : "false";
12
+ if (Array.isArray(value))
13
+ return `[${value.map(stableSerialize).join(",")}]`;
14
+ if (t === "object") {
15
+ const obj = value;
16
+ const keys = Object.keys(obj).sort();
17
+ return `{${keys
18
+ .map((k) => `${JSON.stringify(k)}:${stableSerialize(obj[k])}`)
19
+ .join(",")}}`;
20
+ }
21
+ return JSON.stringify(String(value));
22
+ }
23
+ export function sha256Hex(input) {
24
+ return createHash("sha256").update(input, "utf8").digest("hex");
25
+ }
26
+ export function fingerprintJson(prefix, input) {
27
+ const canonical = stableSerialize(input);
28
+ const hex = sha256Hex(canonical);
29
+ return `${prefix}-${hex.slice(0, 12)}`;
30
+ }
@@ -0,0 +1,394 @@
1
+ // adapters/mcp-ts/src/tools/vibe_pm/modules/fix_dependencies.ts
2
+ // Auto-fix module for dependency issues
3
+ import * as fs from "node:fs";
4
+ import * as path from "node:path";
5
+ /**
6
+ * Fix dependency issues in the project
7
+ *
8
+ * 1. Detect project type (Node.js vs Python)
9
+ * 2. Extract imports from code
10
+ * 3. Parse dependency file
11
+ * 4. Calculate missing packages
12
+ * 5. Add missing packages to dependency file
13
+ */
14
+ export async function fixDependencies(basePath) {
15
+ const fixed = [];
16
+ const errors = [];
17
+ try {
18
+ const checkResult = await checkDependencies(basePath);
19
+ if (checkResult.projectType === "unknown") {
20
+ return { fixed, errors: ["프로젝트 타입을 감지할 수 없습니다"] };
21
+ }
22
+ if (checkResult.missingPackages.length === 0) {
23
+ return { fixed, errors: [] };
24
+ }
25
+ if (!checkResult.dependencyFile) {
26
+ return { fixed, errors: ["의존성 파일을 찾을 수 없습니다"] };
27
+ }
28
+ // Fix missing dependencies
29
+ if (checkResult.projectType === "node") {
30
+ const fixedPackages = await fixNodeDependencies(checkResult.dependencyFile, checkResult.missingPackages);
31
+ for (const pkg of fixedPackages) {
32
+ fixed.push({
33
+ type: "MISSING_DEPENDENCY",
34
+ description: `누락된 패키지 '${pkg}'를 package.json에 추가했습니다`,
35
+ file: checkResult.dependencyFile,
36
+ status: "FIXED"
37
+ });
38
+ }
39
+ }
40
+ else if (checkResult.projectType === "python") {
41
+ const fixedPackages = await fixPythonDependencies(checkResult.dependencyFile, checkResult.missingPackages);
42
+ for (const pkg of fixedPackages) {
43
+ fixed.push({
44
+ type: "MISSING_DEPENDENCY",
45
+ description: `누락된 패키지 '${pkg}'를 requirements.txt에 추가했습니다`,
46
+ file: checkResult.dependencyFile,
47
+ status: "FIXED"
48
+ });
49
+ }
50
+ }
51
+ }
52
+ catch (err) {
53
+ errors.push(err instanceof Error ? err.message : String(err));
54
+ }
55
+ return { fixed, errors };
56
+ }
57
+ /**
58
+ * Check for missing dependencies without fixing
59
+ */
60
+ export async function checkDependencies(basePath) {
61
+ const packageJsonPath = path.join(basePath, "package.json");
62
+ const requirementsPath = path.join(basePath, "requirements.txt");
63
+ // Detect project type
64
+ const hasPackageJson = fs.existsSync(packageJsonPath);
65
+ const hasRequirements = fs.existsSync(requirementsPath);
66
+ if (hasPackageJson) {
67
+ const imports = await extractNodeImports(basePath);
68
+ const declared = parsePackageJsonDeps(packageJsonPath);
69
+ const missing = calculateMissingNodePackages(imports, declared);
70
+ return {
71
+ projectType: "node",
72
+ missingPackages: missing,
73
+ dependencyFile: packageJsonPath
74
+ };
75
+ }
76
+ if (hasRequirements) {
77
+ const imports = await extractPythonImports(basePath);
78
+ const declared = parseRequirementsTxt(requirementsPath);
79
+ const missing = calculateMissingPythonPackages(imports, declared);
80
+ return {
81
+ projectType: "python",
82
+ missingPackages: missing,
83
+ dependencyFile: requirementsPath
84
+ };
85
+ }
86
+ return {
87
+ projectType: "unknown",
88
+ missingPackages: [],
89
+ dependencyFile: null
90
+ };
91
+ }
92
+ /**
93
+ * Extract Node.js imports from source files
94
+ */
95
+ async function extractNodeImports(basePath) {
96
+ const imports = new Set();
97
+ // Patterns for import extraction
98
+ const importPatterns = [
99
+ /import\s+.*?\s+from\s+['"]([^'"./][^'"]*)['"]/g,
100
+ /import\s+['"]([^'"./][^'"]*)['"]/g,
101
+ /require\s*\(\s*['"]([^'"./][^'"]*)['"]\s*\)/g
102
+ ];
103
+ // Find all JS/TS files
104
+ const files = findSourceFiles(basePath, [".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs"]);
105
+ for (const file of files) {
106
+ try {
107
+ const content = fs.readFileSync(file, "utf-8");
108
+ for (const pattern of importPatterns) {
109
+ let match;
110
+ while ((match = pattern.exec(content)) !== null) {
111
+ const pkg = extractPackageName(match[1]);
112
+ if (pkg && !isNodeBuiltin(pkg)) {
113
+ imports.add(pkg);
114
+ }
115
+ }
116
+ pattern.lastIndex = 0; // Reset regex state
117
+ }
118
+ }
119
+ catch {
120
+ // Skip files that can't be read
121
+ }
122
+ }
123
+ return imports;
124
+ }
125
+ /**
126
+ * Extract Python imports from source files
127
+ */
128
+ async function extractPythonImports(basePath) {
129
+ const imports = new Set();
130
+ // Patterns for import extraction
131
+ const importPatterns = [
132
+ /^import\s+(\w+)/gm,
133
+ /^from\s+(\w+)/gm
134
+ ];
135
+ // Find all Python files
136
+ const files = findSourceFiles(basePath, [".py"]);
137
+ for (const file of files) {
138
+ try {
139
+ const content = fs.readFileSync(file, "utf-8");
140
+ for (const pattern of importPatterns) {
141
+ let match;
142
+ while ((match = pattern.exec(content)) !== null) {
143
+ const pkg = match[1];
144
+ if (pkg && !isPythonBuiltin(pkg)) {
145
+ imports.add(pkg);
146
+ }
147
+ }
148
+ pattern.lastIndex = 0;
149
+ }
150
+ }
151
+ catch {
152
+ // Skip files that can't be read
153
+ }
154
+ }
155
+ return imports;
156
+ }
157
+ /**
158
+ * Find source files recursively
159
+ */
160
+ function findSourceFiles(basePath, extensions) {
161
+ const files = [];
162
+ const ignoreDirs = ["node_modules", ".git", "__pycache__", ".venv", "venv", "dist", "build"];
163
+ function walk(dir) {
164
+ try {
165
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
166
+ for (const entry of entries) {
167
+ const fullPath = path.join(dir, entry.name);
168
+ if (entry.isDirectory()) {
169
+ if (!ignoreDirs.includes(entry.name) && !entry.name.startsWith(".")) {
170
+ walk(fullPath);
171
+ }
172
+ }
173
+ else if (entry.isFile()) {
174
+ const ext = path.extname(entry.name);
175
+ if (extensions.includes(ext)) {
176
+ files.push(fullPath);
177
+ }
178
+ }
179
+ }
180
+ }
181
+ catch {
182
+ // Skip directories that can't be read
183
+ }
184
+ }
185
+ walk(basePath);
186
+ return files;
187
+ }
188
+ /**
189
+ * Extract package name from import path
190
+ * e.g., "@types/node" -> "@types/node", "lodash/fp" -> "lodash"
191
+ */
192
+ function extractPackageName(importPath) {
193
+ if (!importPath)
194
+ return null;
195
+ // Handle scoped packages (@org/package)
196
+ if (importPath.startsWith("@")) {
197
+ const parts = importPath.split("/");
198
+ if (parts.length >= 2) {
199
+ return `${parts[0]}/${parts[1]}`;
200
+ }
201
+ return null;
202
+ }
203
+ // Handle regular packages
204
+ return importPath.split("/")[0];
205
+ }
206
+ /**
207
+ * Parse package.json dependencies
208
+ */
209
+ function parsePackageJsonDeps(packageJsonPath) {
210
+ const deps = new Set();
211
+ try {
212
+ const content = fs.readFileSync(packageJsonPath, "utf-8");
213
+ const pkg = JSON.parse(content);
214
+ if (pkg.dependencies) {
215
+ for (const dep of Object.keys(pkg.dependencies)) {
216
+ deps.add(dep);
217
+ }
218
+ }
219
+ if (pkg.devDependencies) {
220
+ for (const dep of Object.keys(pkg.devDependencies)) {
221
+ deps.add(dep);
222
+ }
223
+ }
224
+ if (pkg.peerDependencies) {
225
+ for (const dep of Object.keys(pkg.peerDependencies)) {
226
+ deps.add(dep);
227
+ }
228
+ }
229
+ }
230
+ catch {
231
+ // Return empty set on error
232
+ }
233
+ return deps;
234
+ }
235
+ /**
236
+ * Parse requirements.txt dependencies
237
+ */
238
+ function parseRequirementsTxt(requirementsPath) {
239
+ const deps = new Set();
240
+ try {
241
+ const content = fs.readFileSync(requirementsPath, "utf-8");
242
+ const lines = content.split("\n");
243
+ for (const line of lines) {
244
+ const trimmed = line.trim();
245
+ if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("-")) {
246
+ continue;
247
+ }
248
+ // Extract package name (before version specifier)
249
+ const match = trimmed.match(/^([a-zA-Z0-9_-]+)/);
250
+ if (match) {
251
+ deps.add(match[1].toLowerCase());
252
+ }
253
+ }
254
+ }
255
+ catch {
256
+ // Return empty set on error
257
+ }
258
+ return deps;
259
+ }
260
+ /**
261
+ * Calculate missing Node.js packages
262
+ */
263
+ function calculateMissingNodePackages(imports, declared) {
264
+ const missing = [];
265
+ for (const pkg of imports) {
266
+ if (!declared.has(pkg)) {
267
+ missing.push(pkg);
268
+ }
269
+ }
270
+ return missing;
271
+ }
272
+ /**
273
+ * Calculate missing Python packages
274
+ */
275
+ function calculateMissingPythonPackages(imports, declared) {
276
+ const missing = [];
277
+ for (const pkg of imports) {
278
+ const normalized = pkg.toLowerCase();
279
+ if (!declared.has(normalized)) {
280
+ missing.push(normalized);
281
+ }
282
+ }
283
+ return missing;
284
+ }
285
+ /**
286
+ * Fix Node.js dependencies by adding to package.json
287
+ */
288
+ async function fixNodeDependencies(packageJsonPath, missing) {
289
+ const fixed = [];
290
+ try {
291
+ const content = fs.readFileSync(packageJsonPath, "utf-8");
292
+ const pkg = JSON.parse(content);
293
+ if (!pkg.dependencies) {
294
+ pkg.dependencies = {};
295
+ }
296
+ for (const pkgName of missing) {
297
+ // Add with "*" version (user should run npm install to get proper version)
298
+ pkg.dependencies[pkgName] = "*";
299
+ fixed.push(pkgName);
300
+ }
301
+ // Sort dependencies alphabetically
302
+ pkg.dependencies = Object.fromEntries(Object.entries(pkg.dependencies).sort(([a], [b]) => a.localeCompare(b)));
303
+ fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2) + "\n");
304
+ }
305
+ catch {
306
+ // Return what we fixed so far
307
+ }
308
+ return fixed;
309
+ }
310
+ /**
311
+ * Fix Python dependencies by adding to requirements.txt
312
+ */
313
+ async function fixPythonDependencies(requirementsPath, missing) {
314
+ const fixed = [];
315
+ try {
316
+ let content = "";
317
+ if (fs.existsSync(requirementsPath)) {
318
+ content = fs.readFileSync(requirementsPath, "utf-8");
319
+ if (!content.endsWith("\n")) {
320
+ content += "\n";
321
+ }
322
+ }
323
+ for (const pkg of missing) {
324
+ content += `${pkg}\n`;
325
+ fixed.push(pkg);
326
+ }
327
+ fs.writeFileSync(requirementsPath, content);
328
+ }
329
+ catch {
330
+ // Return what we fixed so far
331
+ }
332
+ return fixed;
333
+ }
334
+ /**
335
+ * Check if package is a Node.js builtin module
336
+ */
337
+ function isNodeBuiltin(pkg) {
338
+ const builtins = new Set([
339
+ "assert", "buffer", "child_process", "cluster", "console", "constants",
340
+ "crypto", "dgram", "dns", "domain", "events", "fs", "http", "https",
341
+ "module", "net", "os", "path", "perf_hooks", "process", "punycode",
342
+ "querystring", "readline", "repl", "stream", "string_decoder", "sys",
343
+ "timers", "tls", "tty", "url", "util", "v8", "vm", "wasi", "worker_threads", "zlib",
344
+ // Node.js built-in module prefixes
345
+ "node:assert", "node:buffer", "node:child_process", "node:cluster",
346
+ "node:console", "node:constants", "node:crypto", "node:dgram",
347
+ "node:dns", "node:domain", "node:events", "node:fs", "node:http",
348
+ "node:https", "node:module", "node:net", "node:os", "node:path",
349
+ "node:perf_hooks", "node:process", "node:punycode", "node:querystring",
350
+ "node:readline", "node:repl", "node:stream", "node:string_decoder",
351
+ "node:sys", "node:timers", "node:tls", "node:tty", "node:url",
352
+ "node:util", "node:v8", "node:vm", "node:wasi", "node:worker_threads", "node:zlib"
353
+ ]);
354
+ return builtins.has(pkg) || pkg.startsWith("node:");
355
+ }
356
+ /**
357
+ * Check if package is a Python builtin module
358
+ */
359
+ function isPythonBuiltin(pkg) {
360
+ const builtins = new Set([
361
+ "abc", "aifc", "argparse", "array", "ast", "asynchat", "asyncio",
362
+ "asyncore", "atexit", "audioop", "base64", "bdb", "binascii", "binhex",
363
+ "bisect", "builtins", "bz2", "calendar", "cgi", "cgitb", "chunk",
364
+ "cmath", "cmd", "code", "codecs", "codeop", "collections", "colorsys",
365
+ "compileall", "concurrent", "configparser", "contextlib", "contextvars",
366
+ "copy", "copyreg", "cProfile", "crypt", "csv", "ctypes", "curses",
367
+ "dataclasses", "datetime", "dbm", "decimal", "difflib", "dis",
368
+ "distutils", "doctest", "email", "encodings", "enum", "errno",
369
+ "faulthandler", "fcntl", "filecmp", "fileinput", "fnmatch", "fractions",
370
+ "ftplib", "functools", "gc", "getopt", "getpass", "gettext", "glob",
371
+ "graphlib", "grp", "gzip", "hashlib", "heapq", "hmac", "html", "http",
372
+ "idlelib", "imaplib", "imghdr", "imp", "importlib", "inspect", "io",
373
+ "ipaddress", "itertools", "json", "keyword", "lib2to3", "linecache",
374
+ "locale", "logging", "lzma", "mailbox", "mailcap", "marshal", "math",
375
+ "mimetypes", "mmap", "modulefinder", "multiprocessing", "netrc", "nis",
376
+ "nntplib", "numbers", "operator", "optparse", "os", "ossaudiodev",
377
+ "pathlib", "pdb", "pickle", "pickletools", "pipes", "pkgutil", "platform",
378
+ "plistlib", "poplib", "posix", "posixpath", "pprint", "profile", "pstats",
379
+ "pty", "pwd", "py_compile", "pyclbr", "pydoc", "queue", "quopri",
380
+ "random", "re", "readline", "reprlib", "resource", "rlcompleter",
381
+ "runpy", "sched", "secrets", "select", "selectors", "shelve", "shlex",
382
+ "shutil", "signal", "site", "smtpd", "smtplib", "sndhdr", "socket",
383
+ "socketserver", "spwd", "sqlite3", "ssl", "stat", "statistics", "string",
384
+ "stringprep", "struct", "subprocess", "sunau", "symtable", "sys",
385
+ "sysconfig", "syslog", "tabnanny", "tarfile", "telnetlib", "tempfile",
386
+ "termios", "test", "textwrap", "threading", "time", "timeit", "tkinter",
387
+ "token", "tokenize", "trace", "traceback", "tracemalloc", "tty", "turtle",
388
+ "turtledemo", "types", "typing", "unicodedata", "unittest", "urllib",
389
+ "uu", "uuid", "venv", "warnings", "wave", "weakref", "webbrowser",
390
+ "winreg", "winsound", "wsgiref", "xdrlib", "xml", "xmlrpc", "zipapp",
391
+ "zipfile", "zipimport", "zlib", "_thread"
392
+ ]);
393
+ return builtins.has(pkg.toLowerCase());
394
+ }