@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.
- package/LICENSE +21 -0
- package/README.md +269 -0
- package/build/auth/gate.js +225 -0
- package/build/auth/index.js +55 -0
- package/build/auth/public_key.js +27 -0
- package/build/auth/token_cache.js +122 -0
- package/build/auth/token_verifier.js +103 -0
- package/build/bootstrap/doctor.js +115 -0
- package/build/bootstrap/installer.js +673 -0
- package/build/bootstrap/lock.js +37 -0
- package/build/bootstrap/platform.js +26 -0
- package/build/bootstrap/registry.js +37 -0
- package/build/cache/index.js +147 -0
- package/build/cli.js +101 -0
- package/build/contracts.js +22 -0
- package/build/control_plane/gate.js +161 -0
- package/build/control_plane/index.js +6 -0
- package/build/dx/activity.js +139 -0
- package/build/engine.js +106 -0
- package/build/errors.js +171 -0
- package/build/generated/activate_input.js +2 -0
- package/build/generated/activate_output.js +57 -0
- package/build/generated/advisory_review_input.js +2 -0
- package/build/generated/advisory_review_output.js +35 -0
- package/build/generated/auth_token_file.js +2 -0
- package/build/generated/briefing_input.js +2 -0
- package/build/generated/briefing_output.js +2 -0
- package/build/generated/clinic_bridge_file.js +13 -0
- package/build/generated/contracts_bundle_info.js +5 -0
- package/build/generated/create_work_order_input.js +2 -0
- package/build/generated/create_work_order_output.js +2 -0
- package/build/generated/current_work_order_file.js +2 -0
- package/build/generated/doctor_input.js +2 -0
- package/build/generated/doctor_output.js +24 -0
- package/build/generated/execution_result.js +2 -0
- package/build/generated/execution_task.js +2 -0
- package/build/generated/export_output_input.js +2 -0
- package/build/generated/export_output_output.js +2 -0
- package/build/generated/finalize_work_input.js +2 -0
- package/build/generated/finalize_work_output.js +2 -0
- package/build/generated/gate_input.js +2 -0
- package/build/generated/gate_output.js +2 -0
- package/build/generated/gate_result_v1.js +2 -0
- package/build/generated/get_decision_input.js +2 -0
- package/build/generated/get_decision_output.js +13 -0
- package/build/generated/handoff_to_clinic.js +2 -0
- package/build/generated/index.js +75 -0
- package/build/generated/inspect_code_input.js +2 -0
- package/build/generated/inspect_code_output.js +13 -0
- package/build/generated/memory_retrieve_output.js +2 -0
- package/build/generated/memory_state_file.js +2 -0
- package/build/generated/memory_status_input.js +2 -0
- package/build/generated/memory_status_output.js +13 -0
- package/build/generated/memory_sync_input.js +2 -0
- package/build/generated/memory_sync_output.js +13 -0
- package/build/generated/plugin_result.js +2 -0
- package/build/generated/react_perf_check_patterns_input.js +2 -0
- package/build/generated/react_perf_check_patterns_output.js +2 -0
- package/build/generated/react_perf_generate_report_input.js +2 -0
- package/build/generated/react_perf_generate_report_output.js +2 -0
- package/build/generated/repair_plan_input.js +2 -0
- package/build/generated/repair_plan_output.js +2 -0
- package/build/generated/run_app_input.js +2 -0
- package/build/generated/run_app_output.js +2 -0
- package/build/generated/run_state_file.js +13 -0
- package/build/generated/scaffold_input.js +2 -0
- package/build/generated/scaffold_output.js +2 -0
- package/build/generated/search_oss_input.js +2 -0
- package/build/generated/search_oss_output.js +2 -0
- package/build/generated/selection_validation_result.js +2 -0
- package/build/generated/signal_agent_input.js +2 -0
- package/build/generated/spec_high_ask_queue_items_file.js +2 -0
- package/build/generated/spec_high_clinic_bridge_output.js +2 -0
- package/build/generated/spec_high_decision_draft_output.js +2 -0
- package/build/generated/spec_high_validate_output.js +2 -0
- package/build/generated/status_input.js +2 -0
- package/build/generated/status_output.js +2 -0
- package/build/generated/submit_decision_input.js +2 -0
- package/build/generated/submit_decision_output.js +2 -0
- package/build/generated/tool_error_output.js +2 -0
- package/build/generated/undo_last_task_input.js +2 -0
- package/build/generated/undo_last_task_output.js +2 -0
- package/build/generated/update_input.js +2 -0
- package/build/generated/update_output.js +2 -0
- package/build/generated/vibe_pm_inspection_result.js +2 -0
- package/build/generated/vibe_pm_report_markdown.js +2 -0
- package/build/generated/vibe_pm_verdict.js +2 -0
- package/build/generated/vibe_repo_config.js +2 -0
- package/build/generated/vibecoding_helper_answer_output.js +2 -0
- package/build/generated/vibecoding_helper_one_loop_selection_output.js +2 -0
- package/build/generated/vibecoding_helper_show_ask_queue_output.js +2 -0
- package/build/generated/work_order_v1.js +2 -0
- package/build/generated/zoekt_evidence_input.js +2 -0
- package/build/generated/zoekt_evidence_output.js +2 -0
- package/build/index.js +111 -0
- package/build/legacy_alias.js +65 -0
- package/build/local-mode/bash.js +61 -0
- package/build/local-mode/config.js +171 -0
- package/build/local-mode/git.js +33 -0
- package/build/local-mode/init.js +110 -0
- package/build/local-mode/paths.js +24 -0
- package/build/local-mode/templates.js +856 -0
- package/build/local-mode/work-order.js +41 -0
- package/build/resources/index.js +246 -0
- package/build/security/input-validator.js +119 -0
- package/build/security/path-policy.js +289 -0
- package/build/security/sandbox.js +228 -0
- package/build/tools/react_perf/check_patterns.js +172 -0
- package/build/tools/react_perf/generate_report.js +337 -0
- package/build/tools/react_perf/index.js +119 -0
- package/build/tools/react_perf/rules/advanced.js +325 -0
- package/build/tools/react_perf/rules/async.js +104 -0
- package/build/tools/react_perf/rules/bundle.js +101 -0
- package/build/tools/react_perf/rules/client.js +186 -0
- package/build/tools/react_perf/rules/index.js +74 -0
- package/build/tools/react_perf/rules/js.js +148 -0
- package/build/tools/react_perf/rules/rendering.js +166 -0
- package/build/tools/react_perf/rules/rerender.js +161 -0
- package/build/tools/react_perf/rules/server.js +141 -0
- package/build/tools/react_perf/types.js +127 -0
- package/build/tools/vibe_pm/activate.js +102 -0
- package/build/tools/vibe_pm/advisory_review.js +77 -0
- package/build/tools/vibe_pm/briefing.js +178 -0
- package/build/tools/vibe_pm/context.js +439 -0
- package/build/tools/vibe_pm/create_work_order.js +271 -0
- package/build/tools/vibe_pm/doc_status_gate.js +370 -0
- package/build/tools/vibe_pm/doctor.js +262 -0
- package/build/tools/vibe_pm/entity_gate/preflight.js +78 -0
- package/build/tools/vibe_pm/export_output.js +135 -0
- package/build/tools/vibe_pm/finalize_work.js +393 -0
- package/build/tools/vibe_pm/gate.js +33 -0
- package/build/tools/vibe_pm/get_decision.js +281 -0
- package/build/tools/vibe_pm/index.js +593 -0
- package/build/tools/vibe_pm/inspect_code.js +828 -0
- package/build/tools/vibe_pm/intent/generator.js +294 -0
- package/build/tools/vibe_pm/intent/index.js +5 -0
- package/build/tools/vibe_pm/intent/prompt_density.js +227 -0
- package/build/tools/vibe_pm/intent/types.js +70 -0
- package/build/tools/vibe_pm/intent/verifier.js +237 -0
- package/build/tools/vibe_pm/kce/doc_usage.js +51 -0
- package/build/tools/vibe_pm/kce/on_finalize.js +11 -0
- package/build/tools/vibe_pm/kce/preflight.js +232 -0
- package/build/tools/vibe_pm/local_memory.js +26 -0
- package/build/tools/vibe_pm/memory_status.js +82 -0
- package/build/tools/vibe_pm/memory_sync.js +134 -0
- package/build/tools/vibe_pm/modules/decision_snapshot.js +29 -0
- package/build/tools/vibe_pm/modules/ensure.js +100 -0
- package/build/tools/vibe_pm/modules/fingerprint.js +30 -0
- package/build/tools/vibe_pm/modules/fix_dependencies.js +394 -0
- package/build/tools/vibe_pm/modules/planning_v1.js +110 -0
- package/build/tools/vibe_pm/modules/repo_context.js +56 -0
- package/build/tools/vibe_pm/modules/research_v1.js +114 -0
- package/build/tools/vibe_pm/modules/skills_v1.js +100 -0
- package/build/tools/vibe_pm/pm_language.js +222 -0
- package/build/tools/vibe_pm/repair_plan.js +199 -0
- package/build/tools/vibe_pm/run_app.js +597 -0
- package/build/tools/vibe_pm/run_app_podman.js +64 -0
- package/build/tools/vibe_pm/scaffold.js +550 -0
- package/build/tools/vibe_pm/search_oss.js +124 -0
- package/build/tools/vibe_pm/status.js +153 -0
- package/build/tools/vibe_pm/submit_decision.js +87 -0
- package/build/tools/vibe_pm/system_design/issue_mapping.js +47 -0
- package/build/tools/vibe_pm/system_design/rulebook.js +112 -0
- package/build/tools/vibe_pm/system_design/semgrep.js +132 -0
- package/build/tools/vibe_pm/types.js +229 -0
- package/build/tools/vibe_pm/undo_last_task.js +163 -0
- package/build/tools/vibe_pm/update.js +146 -0
- package/build/tools/vibe_pm/zoekt_evidence.js +96 -0
- package/build/tools.js +269 -0
- package/build/version-check.js +239 -0
- package/build/vibe-cli.js +631 -0
- 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
|
+
}
|