@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,153 @@
1
+ // adapters/mcp-ts/src/tools/vibe_pm/status.ts
2
+ // vibe_pm.status - Project status query
3
+ import { runEngineWithCache } from "../../engine.js";
4
+ import { safeJsonParse } from "../../cli.js";
5
+ import { resolveRunId, resolveProjectId, getRunContext, readRunState } from "./context.js";
6
+ import { formatMode, formatPhase, signalToReview } from "./pm_language.js";
7
+ import { validateToolInput } from "../../security/input-validator.js";
8
+ import { SpecHighValidateOutputSchema } from "../../generated/spec_high_validate_output.js";
9
+ /**
10
+ * vibe_pm.status - Query current project status
11
+ *
12
+ * Internal mapping:
13
+ * → spec-high validate <run_id>
14
+ * → status summary generation
15
+ */
16
+ export async function status(input) {
17
+ const basePath = process.cwd();
18
+ validateToolInput({ project_id: input.project_id });
19
+ // Resolve run_id
20
+ const { run_id, is_new } = resolveRunId(input.project_id, basePath);
21
+ const project_id = resolveProjectId(run_id, basePath);
22
+ // If no existing run, return empty status
23
+ if (is_new) {
24
+ return {
25
+ project_id,
26
+ project: {
27
+ name: project_id,
28
+ mode: "미설정",
29
+ started_at: "없음"
30
+ },
31
+ current_state: {
32
+ phase: "프로젝트가 없습니다",
33
+ decisions_made: 0,
34
+ decisions_pending: 0
35
+ },
36
+ risks: ["프로젝트를 먼저 시작해주세요"],
37
+ next_action: {
38
+ tool: "vibe_pm.briefing",
39
+ label: "다음 할 일: 프로젝트 시작하기"
40
+ }
41
+ };
42
+ }
43
+ // Get run context and state
44
+ const context = getRunContext(run_id, basePath);
45
+ const runState = readRunState(run_id, basePath);
46
+ // Run spec-high validate to get current status (with caching)
47
+ let validateResult = null;
48
+ try {
49
+ const { code, stdout } = await runEngineWithCache("spec-high", ["--root", "engines/spec_high", "validate", run_id], {
50
+ timeoutMs: 60_000
51
+ });
52
+ if (code === 0 && stdout) {
53
+ const parsed = safeJsonParse(stdout);
54
+ if (parsed.ok) {
55
+ const validated = SpecHighValidateOutputSchema.safeParse(parsed.value);
56
+ if (validated.success)
57
+ validateResult = validated.data;
58
+ }
59
+ }
60
+ }
61
+ catch {
62
+ // Continue with state-based status if validate fails
63
+ }
64
+ // Determine phase and status from various sources
65
+ const phase = runState?.phase ?? validateResult?.phase ?? "unknown";
66
+ const mode = runState?.mode ?? "balanced";
67
+ const decisionsMade = validateResult?.decisions_made ?? runState?.decisions_made ?? 0;
68
+ const decisionsPending = validateResult?.decisions_pending ?? runState?.decisions_pending ?? 0;
69
+ // Get last review status
70
+ let lastReviewStatus;
71
+ if (validateResult?.signal_level) {
72
+ lastReviewStatus = signalToReview(validateResult.signal_level).result;
73
+ }
74
+ else if (runState?.last_review_status) {
75
+ lastReviewStatus = runState.last_review_status;
76
+ }
77
+ // Collect risks
78
+ const risks = validateResult?.risks ?? [];
79
+ // Determine next action based on state
80
+ const nextAction = determineNextAction(phase, decisionsPending, lastReviewStatus);
81
+ return {
82
+ project_id,
83
+ project: {
84
+ name: project_id,
85
+ mode: formatMode(mode),
86
+ started_at: runState?.created_at ?? "알 수 없음"
87
+ },
88
+ current_state: {
89
+ phase: formatPhase(phase),
90
+ decisions_made: decisionsMade,
91
+ decisions_pending: decisionsPending,
92
+ last_review_status: lastReviewStatus
93
+ },
94
+ risks,
95
+ next_action: nextAction
96
+ };
97
+ }
98
+ function determineNextAction(phase, decisionsPending, lastReviewStatus) {
99
+ // If there are pending decisions
100
+ if (decisionsPending > 0) {
101
+ return {
102
+ tool: "vibe_pm.get_decision",
103
+ label: `다음 할 일: 결재 ${decisionsPending}건 처리`
104
+ };
105
+ }
106
+ // Based on phase
107
+ switch (phase) {
108
+ case "init":
109
+ case "briefing":
110
+ return {
111
+ tool: "vibe_pm.get_decision",
112
+ label: "다음 할 일: 결재 요청 확인"
113
+ };
114
+ case "decision":
115
+ return {
116
+ tool: "vibe_pm.create_work_order",
117
+ label: "다음 할 일: 작업 지시서 발행"
118
+ };
119
+ case "work_order":
120
+ case "implementation":
121
+ return {
122
+ tool: "vibe_pm.inspect_code",
123
+ label: "다음 할 일: 구현 후 검수 요청"
124
+ };
125
+ case "review":
126
+ if (lastReviewStatus === "GO") {
127
+ return {
128
+ tool: "vibe_pm.create_work_order",
129
+ label: "다음 할 일: 다음 작업 지시서 받기"
130
+ };
131
+ }
132
+ else if (lastReviewStatus === "FIX" || lastReviewStatus === "BLOCK") {
133
+ return {
134
+ tool: "vibe_pm.repair_plan",
135
+ label: "다음 할 일: 수정 계획 확인"
136
+ };
137
+ }
138
+ return {
139
+ tool: "vibe_pm.inspect_code",
140
+ label: "다음 할 일: 검수 재시도"
141
+ };
142
+ case "completed":
143
+ return {
144
+ tool: "vibe_pm.briefing",
145
+ label: "다음 할 일: 새 프로젝트 시작"
146
+ };
147
+ default:
148
+ return {
149
+ tool: "vibe_pm.status",
150
+ label: "다음 할 일: 상태 확인"
151
+ };
152
+ }
153
+ }
@@ -0,0 +1,87 @@
1
+ // adapters/mcp-ts/src/tools/vibe_pm/submit_decision.ts
2
+ // vibe_pm.submit_decision - Submit decision
3
+ import { runEngine, invalidateEngineCache } from "../../engine.js";
4
+ import { safeJsonParse } from "../../cli.js";
5
+ import { resolveRunId, resolveProjectId, invalidateStateCache } from "./context.js";
6
+ import { validateToolInput } from "../../security/input-validator.js";
7
+ import { VibecodingHelperAnswerOutputSchema } from "../../generated/vibecoding_helper_answer_output.js";
8
+ /**
9
+ * vibe_pm.submit_decision - Submit an approval decision
10
+ *
11
+ * Internal mapping:
12
+ * → spec-high answer <run_id> --text <choice>
13
+ * → spec-high derive <run_id>
14
+ */
15
+ export async function submitDecision(input) {
16
+ const basePath = process.cwd();
17
+ validateToolInput({ project_id: input.project_id, note: input.note });
18
+ // Resolve run_id
19
+ const { run_id } = resolveRunId(input.project_id, basePath);
20
+ const project_id = resolveProjectId(run_id, basePath);
21
+ // Format answer text (combine choice with note if provided)
22
+ const answerText = input.note ? `${input.choice}: ${input.note}` : input.choice;
23
+ // Step 1: Submit answer
24
+ const answerResult = await runEngine("vibecoding-helper", ["answer", run_id, "--text", answerText], { timeoutMs: 120_000 });
25
+ if (answerResult.code !== 0) {
26
+ throw new Error(`결재 제출 실패: ${answerResult.stderr || "알 수 없는 오류"}`);
27
+ }
28
+ // Parse answer response
29
+ let remaining = 0;
30
+ const answerParsed = safeJsonParse(answerResult.stdout);
31
+ if (!answerParsed.ok) {
32
+ throw new Error("결재 결과를 파싱할 수 없습니다. 업데이트 후 다시 시도해주세요.");
33
+ }
34
+ const validated = VibecodingHelperAnswerOutputSchema.safeParse(answerParsed.value);
35
+ if (!validated.success) {
36
+ throw new Error("결재 결과 형식이 예상과 다릅니다. 업데이트 후 다시 시도해주세요.");
37
+ }
38
+ remaining = validated.data.remaining ?? validated.data.ask_queue_remaining ?? 0;
39
+ // Step 2: Run derive to propagate decisions
40
+ try {
41
+ await runEngine("spec-high", ["--root", "engines/spec_high", "derive", run_id], { timeoutMs: 60_000 });
42
+ }
43
+ catch {
44
+ // Derive failure is non-fatal, continue
45
+ }
46
+ // Step 3: Invalidate caches after state mutation
47
+ invalidateEngineCache(new RegExp(run_id));
48
+ invalidateStateCache(run_id, basePath);
49
+ // Generate summary based on choice
50
+ const summary = generateDecisionSummary(input.choice, input.note);
51
+ // Determine next action
52
+ const nextAction = remaining > 0
53
+ ? {
54
+ tool: "vibe_pm.get_decision",
55
+ reason: `아직 ${remaining}건의 결재가 남아있습니다`
56
+ }
57
+ : {
58
+ tool: "vibe_pm.create_work_order",
59
+ reason: "모든 결재가 완료되었습니다. 작업 지시서를 발행합니다"
60
+ };
61
+ return {
62
+ project_id,
63
+ result: {
64
+ decision_id: input.decision_id,
65
+ chosen: input.choice,
66
+ summary
67
+ },
68
+ remaining_decisions: remaining,
69
+ next_action: nextAction
70
+ };
71
+ }
72
+ /**
73
+ * Generate user-friendly decision summary
74
+ */
75
+ function generateDecisionSummary(choice, note) {
76
+ const choiceSummaries = {
77
+ A: "첫 번째 옵션으로 결정되었습니다",
78
+ B: "두 번째 옵션으로 결정되었습니다",
79
+ C: "세 번째 옵션으로 결정되었습니다",
80
+ U: "추가 검토가 필요한 것으로 표시되었습니다"
81
+ };
82
+ let summary = choiceSummaries[choice] ?? `${choice} 옵션으로 결정되었습니다`;
83
+ if (note) {
84
+ summary += ` (참고: ${note})`;
85
+ }
86
+ return summary;
87
+ }
@@ -0,0 +1,47 @@
1
+ import { lookupRuleMeta } from "./rulebook.js";
2
+ export function designRiskForPrinciple(principleId) {
3
+ switch (principleId) {
4
+ case "STRATEGY_ENFORCEMENT":
5
+ return "분기 로직이 비대해져 변경/확장이 어려워질 수 있습니다.";
6
+ case "ADAPTER_ENFORCEMENT":
7
+ return "외부 의존이 섞이면 테스트/보안/교체 비용이 급증할 수 있습니다.";
8
+ case "OBSERVER_ENFORCEMENT":
9
+ return "이벤트 흐름이 분산되면 추적/재현이 어려워질 수 있습니다.";
10
+ case "BUILDER_ENFORCEMENT":
11
+ return "옵션 폭발로 유지보수 비용이 증가할 수 있습니다.";
12
+ case "SINGLETON_GUARD":
13
+ return "전역 mutable 상태는 테스트/동시성 버그를 유발할 수 있습니다.";
14
+ case "UNMAPPED_RULE":
15
+ default:
16
+ return "설계 규칙 위반 가능성이 있어 확인이 필요합니다.";
17
+ }
18
+ }
19
+ export function semgrepFindingsToIssues(args) {
20
+ const issues = [];
21
+ let error_count = 0;
22
+ for (const f of args.findings) {
23
+ const meta = lookupRuleMeta(args.rulebook, f.check_id);
24
+ const principleId = meta?.principle_id ?? "UNMAPPED_RULE";
25
+ const patternName = meta?.pattern ?? null;
26
+ if (f.severity === "ERROR" || f.severity === "CRITICAL") {
27
+ error_count += 1;
28
+ }
29
+ issues.push({
30
+ type: "RULE_VIOLATION",
31
+ business_risk: designRiskForPrinciple(principleId),
32
+ plain_explanation: f.message,
33
+ technical_detail: {
34
+ file_path: f.path,
35
+ line_range: f.start_line && f.end_line ? [f.start_line, f.end_line] : undefined,
36
+ rule_violated: f.check_id,
37
+ principle_id: principleId,
38
+ pattern: patternName ?? undefined,
39
+ rule_engine: "semgrep",
40
+ rule_id: f.check_id,
41
+ severity: f.severity,
42
+ evidence_ref: args.evidence_ref
43
+ }
44
+ });
45
+ }
46
+ return { issues, error_count };
47
+ }
@@ -0,0 +1,112 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { z } from "zod";
4
+ const RuleMetaSchema = z.object({
5
+ principle_id: z.string().min(1),
6
+ pattern: z.string().min(1).nullable().optional(),
7
+ default_severity: z.string().min(1).optional(),
8
+ notes: z.string().optional()
9
+ });
10
+ const RulebookSchema = z.object({
11
+ version: z.string().optional(),
12
+ engine: z.string().optional(),
13
+ rules: z.record(RuleMetaSchema).default({})
14
+ });
15
+ const BUILTIN_RULEBOOK = {
16
+ version: "v1",
17
+ engine: "semgrep",
18
+ rules: {
19
+ "strategy-no-long-if-elif-chain-python": {
20
+ principle_id: "STRATEGY_ENFORCEMENT",
21
+ pattern: "Strategy",
22
+ default_severity: "WARNING"
23
+ },
24
+ "strategy-no-long-else-if-chain-ts": {
25
+ principle_id: "STRATEGY_ENFORCEMENT",
26
+ pattern: "Strategy",
27
+ default_severity: "WARNING"
28
+ },
29
+ "adapter-no-network-in-core-python": {
30
+ principle_id: "ADAPTER_ENFORCEMENT",
31
+ pattern: "Adapter",
32
+ default_severity: "WARNING"
33
+ },
34
+ "adapter-no-network-in-core-ts": {
35
+ principle_id: "ADAPTER_ENFORCEMENT",
36
+ pattern: "Adapter",
37
+ default_severity: "WARNING"
38
+ },
39
+ "observer-publish-only-in-events-python": {
40
+ principle_id: "OBSERVER_ENFORCEMENT",
41
+ pattern: "Observer",
42
+ default_severity: "WARNING"
43
+ },
44
+ "observer-publish-only-in-events-ts": {
45
+ principle_id: "OBSERVER_ENFORCEMENT",
46
+ pattern: "Observer",
47
+ default_severity: "WARNING"
48
+ },
49
+ "observer-no-eventbus-instantiation-outside-factory-ts": {
50
+ principle_id: "OBSERVER_ENFORCEMENT",
51
+ pattern: "Observer",
52
+ default_severity: "WARNING"
53
+ },
54
+ "observer-no-eventbus-getinstance-outside-events-ts": {
55
+ principle_id: "OBSERVER_ENFORCEMENT",
56
+ pattern: "Observer",
57
+ default_severity: "WARNING"
58
+ },
59
+ "observer-no-eventbus-instantiation-outside-factory-python": {
60
+ principle_id: "OBSERVER_ENFORCEMENT",
61
+ pattern: "Observer",
62
+ default_severity: "WARNING"
63
+ },
64
+ "observer-no-eventbus-getinstance-outside-events-python": {
65
+ principle_id: "OBSERVER_ENFORCEMENT",
66
+ pattern: "Observer",
67
+ default_severity: "WARNING"
68
+ },
69
+ "builder-too-many-init-params-python": {
70
+ principle_id: "BUILDER_ENFORCEMENT",
71
+ pattern: "Builder",
72
+ default_severity: "WARNING"
73
+ },
74
+ "builder-too-many-constructor-params-ts": {
75
+ principle_id: "BUILDER_ENFORCEMENT",
76
+ pattern: "Builder",
77
+ default_severity: "WARNING"
78
+ },
79
+ "singleton-no-global-mutable-python": {
80
+ principle_id: "SINGLETON_GUARD",
81
+ pattern: "Singleton",
82
+ default_severity: "WARNING"
83
+ },
84
+ "singleton-no-exported-mutable-singletons-ts": {
85
+ principle_id: "SINGLETON_GUARD",
86
+ pattern: "Singleton",
87
+ default_severity: "WARNING"
88
+ }
89
+ }
90
+ };
91
+ function repoRulebookPath(basePath) {
92
+ return path.join(basePath, "docs", "DEV_SPEC", "system_design_mapping", "RULEBOOK_SEMGREP_MAP.v1.json");
93
+ }
94
+ export function loadRulebook(basePath) {
95
+ const candidate = repoRulebookPath(basePath);
96
+ if (!fs.existsSync(candidate))
97
+ return { source: "builtin", rulebook: BUILTIN_RULEBOOK };
98
+ try {
99
+ const raw = JSON.parse(fs.readFileSync(candidate, "utf-8"));
100
+ const parsed = RulebookSchema.safeParse(raw);
101
+ if (parsed.success)
102
+ return { source: "repo_file", rulebook: parsed.data };
103
+ }
104
+ catch {
105
+ // ignore
106
+ }
107
+ return { source: "builtin", rulebook: BUILTIN_RULEBOOK };
108
+ }
109
+ export function lookupRuleMeta(rulebook, checkId) {
110
+ const meta = (rulebook.rules ?? {})[checkId];
111
+ return meta ?? null;
112
+ }
@@ -0,0 +1,132 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { z } from "zod";
4
+ import { runCmd, safeJsonParse } from "../../../cli.js";
5
+ import { resolveRunDir } from "../context.js";
6
+ const SemgrepResultSchema = z.object({
7
+ results: z
8
+ .array(z.object({
9
+ check_id: z.string().optional(),
10
+ path: z.string().optional(),
11
+ start: z
12
+ .object({
13
+ line: z.number().int().positive().optional(),
14
+ col: z.number().int().positive().optional()
15
+ })
16
+ .optional(),
17
+ end: z
18
+ .object({
19
+ line: z.number().int().positive().optional(),
20
+ col: z.number().int().positive().optional()
21
+ })
22
+ .optional(),
23
+ extra: z
24
+ .object({
25
+ message: z.string().optional(),
26
+ severity: z.string().optional()
27
+ })
28
+ .optional()
29
+ }))
30
+ .default([])
31
+ });
32
+ function findConfigPath(basePath) {
33
+ const candidates = [
34
+ path.join(basePath, "semgrep", "rules", "design_patterns.yml"),
35
+ path.join(basePath, "semgrep", "rules", "design_patterns.yaml"),
36
+ path.join(basePath, "docs", "DEV_SPEC", "system_design_mapping", "semgrep_design_enforcement_rules.yml")
37
+ ];
38
+ for (const c of candidates) {
39
+ if (fs.existsSync(c))
40
+ return c;
41
+ }
42
+ return null;
43
+ }
44
+ function tryWriteEvidence(basePath, run_id, stdout) {
45
+ if (!stdout.trim())
46
+ return null;
47
+ const resolved = resolveRunDir(run_id, basePath);
48
+ if (!resolved)
49
+ return null;
50
+ try {
51
+ const abs = path.join(resolved.run_dir, "evidence", "semgrep_results.json");
52
+ fs.mkdirSync(path.dirname(abs), { recursive: true });
53
+ fs.writeFileSync(abs, stdout, "utf-8");
54
+ return path.relative(basePath, abs).replace(/\\\\/g, "/");
55
+ }
56
+ catch {
57
+ return null;
58
+ }
59
+ }
60
+ export async function runSemgrepDesignEnforcement(args) {
61
+ const configPath = findConfigPath(args.basePath);
62
+ if (!configPath) {
63
+ return {
64
+ status: "SKIPPED",
65
+ findings: [],
66
+ error: { type: "config_missing", message: "design semgrep config not found" }
67
+ };
68
+ }
69
+ const targetArgs = [];
70
+ for (const t of args.targets) {
71
+ if (!t)
72
+ continue;
73
+ const abs = path.isAbsolute(t) ? t : path.join(args.basePath, t);
74
+ if (!fs.existsSync(abs))
75
+ continue;
76
+ targetArgs.push(path.isAbsolute(t) ? t : t.replace(/\\\\/g, "/"));
77
+ }
78
+ if (targetArgs.length === 0) {
79
+ return { status: "SKIPPED", findings: [] };
80
+ }
81
+ const cmdArgs = ["scan", "--config", configPath, "--json", "--quiet", "--metrics", "off", ...targetArgs];
82
+ const { code, stdout, stderr } = await runCmd("semgrep", cmdArgs, {
83
+ cwd: args.basePath,
84
+ timeoutMs: args.timeoutMs ?? 60_000
85
+ });
86
+ const evidence_ref = tryWriteEvidence(args.basePath, args.run_id, stdout) ?? undefined;
87
+ if (code !== 0) {
88
+ return {
89
+ status: "ERROR",
90
+ findings: [],
91
+ evidence_ref,
92
+ error: { type: "semgrep_failed", message: `semgrep exit_code=${code}`, stderr: (stderr ?? "").slice(0, 2000) }
93
+ };
94
+ }
95
+ const parsedJson = safeJsonParse(stdout);
96
+ if (!parsedJson.ok) {
97
+ return {
98
+ status: "ERROR",
99
+ findings: [],
100
+ evidence_ref,
101
+ error: { type: "json_parse_error", message: parsedJson.error, stderr: (stderr ?? "").slice(0, 2000) }
102
+ };
103
+ }
104
+ const validated = SemgrepResultSchema.safeParse(parsedJson.value);
105
+ if (!validated.success) {
106
+ return {
107
+ status: "ERROR",
108
+ findings: [],
109
+ evidence_ref,
110
+ error: { type: "schema_mismatch", message: "unexpected semgrep json shape", stderr: (stderr ?? "").slice(0, 2000) }
111
+ };
112
+ }
113
+ const findings = [];
114
+ for (const r of validated.data.results) {
115
+ const check_id = (r.check_id ?? "").trim();
116
+ const p = (r.path ?? "").trim();
117
+ if (!check_id || !p)
118
+ continue;
119
+ const sevRaw = (r.extra?.severity ?? "").trim().toUpperCase();
120
+ const severity = sevRaw || "WARNING";
121
+ const message = (r.extra?.message ?? "").trim() || "설계 규칙 위반 가능성이 있습니다";
122
+ findings.push({
123
+ check_id,
124
+ path: p.replace(/\\\\/g, "/"),
125
+ start_line: r.start?.line,
126
+ end_line: r.end?.line,
127
+ severity,
128
+ message
129
+ });
130
+ }
131
+ return { status: "OK", findings, evidence_ref };
132
+ }