@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,294 @@
1
+ // adapters/mcp-ts/src/tools/vibe_pm/intent/generator.ts
2
+ // Intent Document Generator
3
+ // briefing 결과를 Intent 문서로 변환
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+ import * as crypto from "node:crypto";
7
+ import * as yaml from "yaml";
8
+ /**
9
+ * Generate an Intent document from briefing data
10
+ */
11
+ export function generateIntentFromBriefing(data) {
12
+ const now = new Date().toISOString();
13
+ // Convert include/exclude to ScopeItems
14
+ const includeItems = (data.include || []).map((item) => ({
15
+ item,
16
+ reason: "MVP 범위에 포함"
17
+ }));
18
+ const excludeItems = (data.exclude || []).map((item) => ({
19
+ item,
20
+ reason: "MVP 이후 구현 예정"
21
+ }));
22
+ // Generate default constraints based on mode
23
+ const constraints = generateModeConstraints(data.mode);
24
+ // Generate risks from top_risk
25
+ const risks = data.topRisk
26
+ ? [
27
+ {
28
+ id: "R-001",
29
+ description: data.topRisk,
30
+ impact: "high",
31
+ mitigation: "범위 명확화 및 우선순위 조정"
32
+ }
33
+ ]
34
+ : [];
35
+ // Generate acceptance criteria from first milestone
36
+ const acceptance = [
37
+ {
38
+ id: "AC-001",
39
+ description: `${data.firstMilestone}이(가) 동작함`,
40
+ verifiable: true,
41
+ verification_method: "수동 테스트"
42
+ },
43
+ {
44
+ id: "AC-002",
45
+ description: "앱이 정상적으로 실행됨",
46
+ verifiable: true,
47
+ verification_method: "빌드 및 실행 확인"
48
+ }
49
+ ];
50
+ return {
51
+ version: "1.0",
52
+ created_at: now,
53
+ updated_at: now,
54
+ run_id: data.runId,
55
+ overview: {
56
+ project_name: data.projectName,
57
+ goal: data.goal,
58
+ target_user: data.targetUser,
59
+ mode: data.mode,
60
+ first_milestone: data.firstMilestone
61
+ },
62
+ scope: {
63
+ include: includeItems,
64
+ exclude: excludeItems,
65
+ do_not_touch: data.doNotTouch || []
66
+ },
67
+ constraints,
68
+ risks,
69
+ acceptance
70
+ };
71
+ }
72
+ /**
73
+ * Generate mode-specific constraints
74
+ */
75
+ function generateModeConstraints(mode) {
76
+ switch (mode) {
77
+ case "mvp_fast":
78
+ return [
79
+ {
80
+ type: "time",
81
+ description: "빠른 출시가 최우선",
82
+ priority: "must"
83
+ },
84
+ {
85
+ type: "tech",
86
+ description: "임시 코드 허용, 리팩토링은 나중에",
87
+ priority: "should"
88
+ }
89
+ ];
90
+ case "balanced":
91
+ return [
92
+ {
93
+ type: "time",
94
+ description: "적정 시간 내 출시",
95
+ priority: "must"
96
+ },
97
+ {
98
+ type: "tech",
99
+ description: "기본 품질 유지, 확장성 고려",
100
+ priority: "should"
101
+ }
102
+ ];
103
+ case "quality_first":
104
+ return [
105
+ {
106
+ type: "tech",
107
+ description: "코드 품질 및 테스트 커버리지 우선",
108
+ priority: "must"
109
+ },
110
+ {
111
+ type: "tech",
112
+ description: "확장 가능한 아키텍처",
113
+ priority: "must"
114
+ }
115
+ ];
116
+ }
117
+ }
118
+ // ============================================================
119
+ // Intent File Operations
120
+ // ============================================================
121
+ /**
122
+ * Save intent document to runs/<run_id>/intent/
123
+ */
124
+ export async function saveIntent(runsDir, intent) {
125
+ const intentDir = path.join(runsDir, intent.run_id, "intent");
126
+ await fs.promises.mkdir(intentDir, { recursive: true });
127
+ // Save main intent document
128
+ const intentPath = path.join(intentDir, "intent.yaml");
129
+ const content = yaml.stringify(intent);
130
+ await fs.promises.writeFile(intentPath, content, "utf-8");
131
+ // Create/update index
132
+ await updateIntentIndex(intentDir, intent);
133
+ return intentPath;
134
+ }
135
+ /**
136
+ * Update intent index file
137
+ */
138
+ async function updateIntentIndex(intentDir, intent) {
139
+ const indexPath = path.join(intentDir, "_index.yaml");
140
+ const intentPath = path.join(intentDir, "intent.yaml");
141
+ const checksum = crypto
142
+ .createHash("sha256")
143
+ .update(yaml.stringify(intent))
144
+ .digest("hex")
145
+ .slice(0, 16);
146
+ const index = {
147
+ version: "1.0",
148
+ run_id: intent.run_id,
149
+ created_at: intent.created_at,
150
+ documents: [
151
+ {
152
+ path: "intent.yaml",
153
+ type: "overview",
154
+ checksum,
155
+ last_modified: intent.updated_at
156
+ }
157
+ ],
158
+ status: "draft"
159
+ };
160
+ await fs.promises.writeFile(indexPath, yaml.stringify(index), "utf-8");
161
+ }
162
+ /**
163
+ * Load intent document from runs/<run_id>/intent/
164
+ */
165
+ export async function loadIntent(runsDir, runId) {
166
+ const intentPath = path.join(runsDir, runId, "intent", "intent.yaml");
167
+ try {
168
+ const content = await fs.promises.readFile(intentPath, "utf-8");
169
+ return yaml.parse(content);
170
+ }
171
+ catch {
172
+ return null;
173
+ }
174
+ }
175
+ /**
176
+ * Load intent index from runs/<run_id>/intent/
177
+ */
178
+ export async function loadIntentIndex(runsDir, runId) {
179
+ const indexPath = path.join(runsDir, runId, "intent", "_index.yaml");
180
+ try {
181
+ const content = await fs.promises.readFile(indexPath, "utf-8");
182
+ return yaml.parse(content);
183
+ }
184
+ catch {
185
+ return null;
186
+ }
187
+ }
188
+ /**
189
+ * Update intent status
190
+ */
191
+ export async function updateIntentStatus(runsDir, runId, status) {
192
+ const index = await loadIntentIndex(runsDir, runId);
193
+ if (!index)
194
+ return;
195
+ index.status = status;
196
+ const indexPath = path.join(runsDir, runId, "intent", "_index.yaml");
197
+ await fs.promises.writeFile(indexPath, yaml.stringify(index), "utf-8");
198
+ }
199
+ // ============================================================
200
+ // Intent Markdown Generation (for human readability)
201
+ // ============================================================
202
+ /**
203
+ * Generate markdown representation of intent
204
+ */
205
+ export function intentToMarkdown(intent) {
206
+ const lines = [];
207
+ lines.push(`# ${intent.overview.project_name}`);
208
+ lines.push("");
209
+ lines.push(`> ${intent.overview.goal}`);
210
+ lines.push("");
211
+ // Overview
212
+ lines.push("## Overview");
213
+ lines.push("");
214
+ lines.push(`- **Target User**: ${intent.overview.target_user}`);
215
+ lines.push(`- **Mode**: ${intent.overview.mode}`);
216
+ lines.push(`- **First Milestone**: ${intent.overview.first_milestone}`);
217
+ lines.push("");
218
+ // Scope
219
+ lines.push("## Scope");
220
+ lines.push("");
221
+ lines.push("### Include");
222
+ for (const item of intent.scope.include) {
223
+ lines.push(`- ${item.item}${item.reason ? ` (${item.reason})` : ""}`);
224
+ }
225
+ lines.push("");
226
+ lines.push("### Exclude");
227
+ for (const item of intent.scope.exclude) {
228
+ lines.push(`- ${item.item}${item.reason ? ` (${item.reason})` : ""}`);
229
+ }
230
+ lines.push("");
231
+ if (intent.scope.do_not_touch.length > 0) {
232
+ lines.push("### Do Not Touch");
233
+ for (const item of intent.scope.do_not_touch) {
234
+ lines.push(`- ${item}`);
235
+ }
236
+ lines.push("");
237
+ }
238
+ // Constraints
239
+ if (intent.constraints.length > 0) {
240
+ lines.push("## Constraints");
241
+ lines.push("");
242
+ for (const c of intent.constraints) {
243
+ lines.push(`- **[${c.type}/${c.priority}]** ${c.description}`);
244
+ }
245
+ lines.push("");
246
+ }
247
+ // Risks
248
+ if (intent.risks.length > 0) {
249
+ lines.push("## Risks");
250
+ lines.push("");
251
+ for (const r of intent.risks) {
252
+ lines.push(`### ${r.id}: ${r.description}`);
253
+ lines.push(`- Impact: ${r.impact}`);
254
+ if (r.mitigation) {
255
+ lines.push(`- Mitigation: ${r.mitigation}`);
256
+ }
257
+ lines.push("");
258
+ }
259
+ }
260
+ // Acceptance Criteria
261
+ if (intent.acceptance.length > 0) {
262
+ lines.push("## Acceptance Criteria");
263
+ lines.push("");
264
+ for (const ac of intent.acceptance) {
265
+ const verifiable = ac.verifiable ? "✅" : "❓";
266
+ lines.push(`- **${ac.id}** ${verifiable} ${ac.description}`);
267
+ if (ac.verification_method) {
268
+ lines.push(` - Verification: ${ac.verification_method}`);
269
+ }
270
+ }
271
+ lines.push("");
272
+ }
273
+ // Metadata
274
+ lines.push("---");
275
+ lines.push(`*Generated: ${intent.created_at}*`);
276
+ lines.push(`*Run ID: ${intent.run_id}*`);
277
+ return lines.join("\n");
278
+ }
279
+ /**
280
+ * Save intent as both YAML and Markdown
281
+ */
282
+ export async function saveIntentWithMarkdown(runsDir, intent) {
283
+ const intentDir = path.join(runsDir, intent.run_id, "intent");
284
+ await fs.promises.mkdir(intentDir, { recursive: true });
285
+ // Save YAML
286
+ const yamlPath = path.join(intentDir, "intent.yaml");
287
+ await fs.promises.writeFile(yamlPath, yaml.stringify(intent), "utf-8");
288
+ // Save Markdown
289
+ const mdPath = path.join(intentDir, "intent.md");
290
+ await fs.promises.writeFile(mdPath, intentToMarkdown(intent), "utf-8");
291
+ // Update index
292
+ await updateIntentIndex(intentDir, intent);
293
+ return { yamlPath, mdPath };
294
+ }
@@ -0,0 +1,5 @@
1
+ // adapters/mcp-ts/src/tools/vibe_pm/intent/index.ts
2
+ // Intent Module Exports
3
+ export { ScopeItemSchema, ConstraintSchema, RiskSchema, AcceptanceCriterionSchema, IntentDocumentSchema, IntentIndexSchema, isIntentDocument, isIntentIndex } from "./types.js";
4
+ export { generateIntentFromBriefing, saveIntent, loadIntent, loadIntentIndex, updateIntentStatus, intentToMarkdown, saveIntentWithMarkdown } from "./generator.js";
5
+ export { verifyAgainstIntent, toInspectionData, runIntentVerification, formatVerificationForPM } from "./verifier.js";
@@ -0,0 +1,227 @@
1
+ // adapters/mcp-ts/src/tools/vibe_pm/intent/prompt_density.ts
2
+ // Prompt density evaluator (hybrid scoring) + run artifact persistence.
3
+ //
4
+ // Spec: docs/DEV_SPEC/PROMPT_DENSITY_ADAPTIVE_UX_V1/PROMPT_DENSITY_ADAPTIVE_UX_SPEC_v1.md
5
+ import * as fs from "node:fs";
6
+ import * as path from "node:path";
7
+ const MODE_0_MAX = 39;
8
+ const MODE_1_MAX = 69;
9
+ const HYSTERESIS_DELTA = 5;
10
+ const THRESHOLDS = {
11
+ mode0_to_mode1: MODE_0_MAX + 1 + HYSTERESIS_DELTA, // 45
12
+ mode1_to_mode0: MODE_0_MAX - HYSTERESIS_DELTA, // 34
13
+ mode1_to_mode2: MODE_1_MAX + 1 + HYSTERESIS_DELTA, // 75
14
+ mode2_to_mode1: MODE_1_MAX - HYSTERESIS_DELTA, // 64
15
+ };
16
+ const RE = {
17
+ goal: /(만들|구현|개발|작성|자동화|서비스|앱|웹|도구|게임|플러그인|확장|extension)/i,
18
+ scopeInclude: /(이번|우선|먼저|v1|mvp|초기|1차)/i,
19
+ scopeExclude: /(나중|추후|v2|2차|미루|제외|out\s*of\s*scope)/i,
20
+ scopeExplicit: /(in\s*scope|out\s*of\s*scope|include|exclude)/i,
21
+ constraints: /(금지|쓰지|사용하지|하면\s*안|절대|못\s*쓰|지양|must\s+not|do\s+not|forbid)/i,
22
+ done: /(완료|수용|통과|기준|AC\b|acceptance|dod\b|definition\s+of\s+done)/i,
23
+ stageAwareness: /(mvp|v1|prototype|프로토타입|실험|최소|초기|1차)/i,
24
+ verification: /(테스트|검증|확인|리뷰|점검|verify|test|ci\b|check)/i,
25
+ failure: /(실패|안\s*되|오류|에러|문제|막히|fail|error|exception)/i,
26
+ reversibility: /(바꿀|되돌|변경|나중에\s*수정|rollback|revert|switch)/i,
27
+ mushy: /(아무거나|상관없|대충|알아서|whatever|anything\s+is\s+fine)/i,
28
+ overscopeMarker: /(그리고|또|추가|뿐만\s+아니라|그리고\s+나서|그리고\s+또)/gi,
29
+ };
30
+ function countMatches(text, re) {
31
+ if (!re.global) {
32
+ return re.test(text) ? 1 : 0;
33
+ }
34
+ const m = text.match(re);
35
+ return m ? m.length : 0;
36
+ }
37
+ function clampScore(n) {
38
+ if (!Number.isFinite(n))
39
+ return 0;
40
+ return Math.max(0, Math.min(100, Math.floor(n)));
41
+ }
42
+ export function modeByScoreTotal(scoreTotal) {
43
+ const total = clampScore(scoreTotal);
44
+ if (total <= MODE_0_MAX)
45
+ return "mode_0";
46
+ if (total <= MODE_1_MAX)
47
+ return "mode_1";
48
+ return "mode_2";
49
+ }
50
+ /**
51
+ * Mode hysteresis to prevent boundary jitter around 39↔40 and 69↔70.
52
+ * - Applies only when previous mode exists.
53
+ * - Uses fixed ±5 stabilization window (SSOT).
54
+ */
55
+ export function resolveModeWithHysteresis(previousMode, scoreTotal) {
56
+ const total = clampScore(scoreTotal);
57
+ switch (previousMode) {
58
+ case "mode_0":
59
+ return total >= THRESHOLDS.mode0_to_mode1 ? "mode_1" : "mode_0";
60
+ case "mode_1":
61
+ if (total <= THRESHOLDS.mode1_to_mode0)
62
+ return "mode_0";
63
+ if (total >= THRESHOLDS.mode1_to_mode2)
64
+ return "mode_2";
65
+ return "mode_1";
66
+ case "mode_2":
67
+ return total <= THRESHOLDS.mode2_to_mode1 ? "mode_1" : "mode_2";
68
+ }
69
+ }
70
+ export function computePromptDensity(projectBrief, runId) {
71
+ const text = String(projectBrief ?? "").trim();
72
+ const signals = [];
73
+ // ---------------------------
74
+ // A) Structure (0~40)
75
+ // ---------------------------
76
+ let structure = 0;
77
+ const hasGoal = text.length >= 8 && RE.goal.test(text);
78
+ if (hasGoal) {
79
+ structure += 10;
80
+ signals.push({ key: "has_goal", points: 10 });
81
+ }
82
+ const hasScopeSplit = RE.scopeExplicit.test(text) || (RE.scopeInclude.test(text) && RE.scopeExclude.test(text));
83
+ if (hasScopeSplit) {
84
+ structure += 10;
85
+ signals.push({ key: "has_scope_split", points: 10 });
86
+ }
87
+ const hasConstraints = RE.constraints.test(text);
88
+ if (hasConstraints) {
89
+ structure += 10;
90
+ signals.push({ key: "has_constraints", points: 10 });
91
+ }
92
+ const hasDoneDefinition = RE.done.test(text);
93
+ if (hasDoneDefinition) {
94
+ structure += 10;
95
+ signals.push({ key: "has_done_definition", points: 10 });
96
+ }
97
+ // ---------------------------
98
+ // B) Conflict (0~30)
99
+ // ---------------------------
100
+ let conflict = 0;
101
+ const notMushy = !RE.mushy.test(text);
102
+ if (notMushy) {
103
+ conflict += 10;
104
+ signals.push({ key: "no_self_contradiction", points: 10 });
105
+ }
106
+ const markerCount = countMatches(text, RE.overscopeMarker);
107
+ const notOverscoped = text.length <= 1200 && markerCount <= 6;
108
+ if (notOverscoped) {
109
+ conflict += 10;
110
+ signals.push({ key: "no_overscope", points: 10 });
111
+ }
112
+ const hasStageAwareness = RE.stageAwareness.test(text);
113
+ if (hasStageAwareness) {
114
+ conflict += 10;
115
+ signals.push({ key: "stage_awareness", points: 10 });
116
+ }
117
+ // ---------------------------
118
+ // C) Verification Awareness (0~30)
119
+ // ---------------------------
120
+ let verification = 0;
121
+ const mentionsVerification = RE.verification.test(text);
122
+ if (mentionsVerification) {
123
+ verification += 10;
124
+ signals.push({ key: "mentions_verification", points: 10 });
125
+ }
126
+ const mentionsFailure = RE.failure.test(text);
127
+ if (mentionsFailure) {
128
+ verification += 10;
129
+ signals.push({ key: "mentions_failure", points: 10 });
130
+ }
131
+ const mentionsReversibility = RE.reversibility.test(text);
132
+ if (mentionsReversibility) {
133
+ verification += 10;
134
+ signals.push({ key: "mentions_reversibility", points: 10 });
135
+ }
136
+ const total = clampScore(structure + conflict + verification);
137
+ const mode = modeByScoreTotal(total);
138
+ return {
139
+ version: "1.0",
140
+ computed_at: new Date().toISOString(),
141
+ run_id: runId,
142
+ score_total: total,
143
+ mode,
144
+ subscores: {
145
+ structure,
146
+ conflict,
147
+ verification,
148
+ },
149
+ signals_matched: signals,
150
+ };
151
+ }
152
+ export function applyPromptDensityHysteresis(args) {
153
+ if (!args.previous)
154
+ return args.current;
155
+ const baseMode = args.current.mode;
156
+ const stableMode = resolveModeWithHysteresis(args.previous.mode, args.current.score_total);
157
+ return {
158
+ ...args.current,
159
+ previous_mode: args.previous.mode,
160
+ mode_without_hysteresis: baseMode,
161
+ hysteresis_applied: stableMode !== baseMode,
162
+ mode: stableMode,
163
+ };
164
+ }
165
+ export function getPromptDensityPath(runsDir, runId) {
166
+ return path.join(runsDir, runId, "intent", "prompt_density.json");
167
+ }
168
+ export function savePromptDensity(runsDir, runId, result) {
169
+ const p = getPromptDensityPath(runsDir, runId);
170
+ fs.mkdirSync(path.dirname(p), { recursive: true });
171
+ fs.writeFileSync(p, JSON.stringify(result, null, 2), "utf-8");
172
+ }
173
+ export function loadPromptDensity(runsDir, runId) {
174
+ const p = getPromptDensityPath(runsDir, runId);
175
+ try {
176
+ if (!fs.existsSync(p))
177
+ return null;
178
+ const raw = fs.readFileSync(p, "utf-8");
179
+ const parsed = JSON.parse(raw);
180
+ if (!parsed || typeof parsed !== "object")
181
+ return null;
182
+ if (parsed.version !== "1.0")
183
+ return null;
184
+ if (!parsed.mode || (parsed.mode !== "mode_0" && parsed.mode !== "mode_1" && parsed.mode !== "mode_2")) {
185
+ return null;
186
+ }
187
+ return parsed;
188
+ }
189
+ catch {
190
+ return null;
191
+ }
192
+ }
193
+ /**
194
+ * Best-effort: load the latest prompt density artifact for a given project_id.
195
+ * Used for hysteresis stabilization across repeated briefings.
196
+ */
197
+ export function loadLatestPromptDensityForProject(runsDir, projectId) {
198
+ try {
199
+ if (!fs.existsSync(runsDir))
200
+ return null;
201
+ const prefix = `${projectId}_`;
202
+ const dirs = fs
203
+ .readdirSync(runsDir, { withFileTypes: true })
204
+ .filter((d) => d.isDirectory() && d.name.startsWith(prefix))
205
+ .map((d) => d.name);
206
+ const candidates = dirs
207
+ .map((runId) => {
208
+ try {
209
+ const stat = fs.statSync(path.join(runsDir, runId));
210
+ return { runId, mtime: stat.mtime.getTime() };
211
+ }
212
+ catch {
213
+ return { runId, mtime: 0 };
214
+ }
215
+ })
216
+ .sort((a, b) => b.mtime - a.mtime);
217
+ for (const c of candidates) {
218
+ const parsed = loadPromptDensity(runsDir, c.runId);
219
+ if (parsed)
220
+ return parsed;
221
+ }
222
+ return null;
223
+ }
224
+ catch {
225
+ return null;
226
+ }
227
+ }
@@ -0,0 +1,70 @@
1
+ // adapters/mcp-ts/src/tools/vibe_pm/intent/types.ts
2
+ // Intent Document TypeScript Interfaces
3
+ // Intent = AI가 이해하는 "프로젝트 의도" 문서 구조
4
+ import { z } from "zod";
5
+ // ============================================================
6
+ // Zod Schemas for Validation
7
+ // ============================================================
8
+ export const ScopeItemSchema = z.object({
9
+ item: z.string(),
10
+ reason: z.string().optional()
11
+ });
12
+ export const ConstraintSchema = z.object({
13
+ type: z.enum(["tech", "time", "resource", "legal", "business"]),
14
+ description: z.string(),
15
+ priority: z.enum(["must", "should", "nice_to_have"])
16
+ });
17
+ export const RiskSchema = z.object({
18
+ id: z.string(),
19
+ description: z.string(),
20
+ impact: z.enum(["high", "medium", "low"]),
21
+ mitigation: z.string().optional()
22
+ });
23
+ export const AcceptanceCriterionSchema = z.object({
24
+ id: z.string(),
25
+ description: z.string(),
26
+ verifiable: z.boolean(),
27
+ verification_method: z.string().optional()
28
+ });
29
+ export const IntentDocumentSchema = z.object({
30
+ version: z.string(),
31
+ created_at: z.string(),
32
+ updated_at: z.string(),
33
+ run_id: z.string(),
34
+ overview: z.object({
35
+ project_name: z.string(),
36
+ goal: z.string(),
37
+ target_user: z.string(),
38
+ mode: z.enum(["mvp_fast", "balanced", "quality_first"]),
39
+ first_milestone: z.string()
40
+ }),
41
+ scope: z.object({
42
+ include: z.array(ScopeItemSchema),
43
+ exclude: z.array(ScopeItemSchema),
44
+ do_not_touch: z.array(z.string())
45
+ }),
46
+ constraints: z.array(ConstraintSchema),
47
+ risks: z.array(RiskSchema),
48
+ acceptance: z.array(AcceptanceCriterionSchema)
49
+ });
50
+ export const IntentIndexSchema = z.object({
51
+ version: z.string(),
52
+ run_id: z.string(),
53
+ created_at: z.string(),
54
+ documents: z.array(z.object({
55
+ path: z.string(),
56
+ type: z.enum(["overview", "scope", "constraints", "acceptance"]),
57
+ checksum: z.string(),
58
+ last_modified: z.string()
59
+ })),
60
+ status: z.enum(["draft", "approved", "implemented", "verified"])
61
+ });
62
+ // ============================================================
63
+ // Type Guards
64
+ // ============================================================
65
+ export function isIntentDocument(obj) {
66
+ return IntentDocumentSchema.safeParse(obj).success;
67
+ }
68
+ export function isIntentIndex(obj) {
69
+ return IntentIndexSchema.safeParse(obj).success;
70
+ }