@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,856 @@
1
+ export const VALIDATE_SH_TEMPLATE = `#!/usr/bin/env bash
2
+ #
3
+ # Vibe PM Validation Library (SSOT)
4
+ # Hooks/CLI/CI call validate_changes().
5
+ #
6
+
7
+ set -e
8
+
9
+ # =============================================================================
10
+ # Repo Root (be resilient to hook cwd)
11
+ # =============================================================================
12
+
13
+ REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
14
+ cd "$REPO_ROOT" || exit 1
15
+
16
+ # =============================================================================
17
+ # Config
18
+ # =============================================================================
19
+
20
+ VIBE_DIR=".vibe"
21
+ CONFIG_FILE="$VIBE_DIR/config.json"
22
+ STATE_DIR="$VIBE_DIR/state"
23
+ WORK_ORDER_FILE="$STATE_DIR/current_work_order.json"
24
+ STALE_HOURS=24
25
+ OUTPUT_FORMAT="\${VIBE_OUTPUT_FORMAT:-text}"
26
+
27
+ # =============================================================================
28
+ # Colors
29
+ # =============================================================================
30
+
31
+ RED='\\033[0;31m'
32
+ YELLOW='\\033[1;33m'
33
+ GREEN='\\033[0;32m'
34
+ NC='\\033[0m'
35
+
36
+ # =============================================================================
37
+ # Logging
38
+ # =============================================================================
39
+
40
+ log_info() { echo -e "\${GREEN}[VIBE]\${NC} $1"; }
41
+ log_warn() { echo -e "\${YELLOW}[VIBE WARN]\${NC} $1"; }
42
+ log_error() { echo -e "\${RED}[VIBE BLOCK]\${NC} $1"; }
43
+
44
+ # =============================================================================
45
+ # JSON Helpers (jq optional)
46
+ # =============================================================================
47
+
48
+ json_get() {
49
+ local file="$1"
50
+ local key_path="$2"
51
+ local default="$3"
52
+
53
+ if [ ! -f "$file" ]; then
54
+ echo "$default"
55
+ return 0
56
+ fi
57
+
58
+ if command -v jq &> /dev/null; then
59
+ local value
60
+ value="$(jq -r "$key_path // empty" "$file" 2>/dev/null || true)"
61
+ if [ -n "$value" ] && [ "$value" != "null" ]; then
62
+ echo "$value"
63
+ else
64
+ echo "$default"
65
+ fi
66
+ return 0
67
+ fi
68
+
69
+ node - <<'NODE' "$file" "$key_path" "$default" 2>/dev/null || echo "$default"
70
+ const fs = require("fs");
71
+ const file = process.argv[2];
72
+ const keyPath = (process.argv[3] || "").replace(/^\\./, "");
73
+ const defaultValue = process.argv[4] ?? "";
74
+ let doc;
75
+ try { doc = JSON.parse(fs.readFileSync(file, "utf8")); } catch { process.stdout.write(defaultValue); process.exit(0); }
76
+ let cur = doc;
77
+ for (const part of keyPath.split(".").filter(Boolean)) {
78
+ if (cur && typeof cur === "object" && Object.prototype.hasOwnProperty.call(cur, part)) cur = cur[part];
79
+ else { cur = undefined; break; }
80
+ }
81
+ if (cur === undefined || cur === null || cur === "") { process.stdout.write(defaultValue); process.exit(0); }
82
+ process.stdout.write(String(cur));
83
+ NODE
84
+ }
85
+
86
+ json_get_array() {
87
+ local file="$1"
88
+ local key_path="$2"
89
+
90
+ if [ ! -f "$file" ]; then
91
+ return 0
92
+ fi
93
+
94
+ if command -v jq &> /dev/null; then
95
+ jq -r "$key_path[]? // empty" "$file" 2>/dev/null || true
96
+ return 0
97
+ fi
98
+
99
+ node - <<'NODE' "$file" "$key_path" 2>/dev/null || true
100
+ const fs = require("fs");
101
+ const file = process.argv[2];
102
+ const keyPath = (process.argv[3] || "").replace(/^\\./, "");
103
+ let doc;
104
+ try { doc = JSON.parse(fs.readFileSync(file, "utf8")); } catch { process.exit(0); }
105
+ let cur = doc;
106
+ for (const part of keyPath.split(".").filter(Boolean)) {
107
+ if (cur && typeof cur === "object" && Object.prototype.hasOwnProperty.call(cur, part)) cur = cur[part];
108
+ else { cur = undefined; break; }
109
+ }
110
+ if (!Array.isArray(cur)) process.exit(0);
111
+ for (const item of cur) {
112
+ if (typeof item === "string" && item.length) console.log(item);
113
+ }
114
+ NODE
115
+ }
116
+
117
+ get_config() {
118
+ local key_path="$1"
119
+ local default="$2"
120
+ json_get "$CONFIG_FILE" "$key_path" "$default"
121
+ }
122
+
123
+ get_config_array() {
124
+ local key_path="$1"
125
+ json_get_array "$CONFIG_FILE" "$key_path"
126
+ }
127
+
128
+ # =============================================================================
129
+ # Zone Matching (git pathspec)
130
+ # =============================================================================
131
+
132
+ matches_pathspec() {
133
+ local file="$1"
134
+ local pattern="$2"
135
+
136
+ local ps="$pattern"
137
+ if [[ "$pattern" != ":(glob)"* && "$pattern" != ":(icase)"* && "$pattern" != ":(exclude)"* ]]; then
138
+ ps=":(glob)$pattern"
139
+ fi
140
+
141
+ if git ls-files --cached --others --exclude-standard --full-name -- "$ps" 2>/dev/null | grep -Fxq "$file"; then
142
+ return 0
143
+ fi
144
+
145
+ return 1
146
+ }
147
+
148
+ get_file_zone() {
149
+ local file="$1"
150
+
151
+ local black_patterns="$(get_config_array ".policy.safety_zones.black")"
152
+ local green_patterns="$(get_config_array ".policy.safety_zones.green")"
153
+ local yellow_patterns="$(get_config_array ".policy.safety_zones.yellow")"
154
+ local red_patterns="$(get_config_array ".policy.safety_zones.red")"
155
+
156
+ if [ -z "$black_patterns$green_patterns$yellow_patterns$red_patterns" ]; then
157
+ black_patterns=$'.vibe/state/**\\n.vibe/decisions/**\\n.vibe/chroma/**\\n.git/**'
158
+ green_patterns=$'src/**\\ntests/**\\nlib/**\\nscripts/**'
159
+ yellow_patterns=$'docs/**\\nREADME.md\\n*.md'
160
+ red_patterns=$'config/**\\n.env\\n.env.*\\nsecrets/**\\ncredentials.*\\n*.pem\\n*.key'
161
+ fi
162
+
163
+ for pattern in $black_patterns; do
164
+ if matches_pathspec "$file" "$pattern"; then
165
+ echo "black"
166
+ return 0
167
+ fi
168
+ done
169
+
170
+ for pattern in $red_patterns; do
171
+ if matches_pathspec "$file" "$pattern"; then
172
+ echo "red"
173
+ return 0
174
+ fi
175
+ done
176
+
177
+ for pattern in $yellow_patterns; do
178
+ if matches_pathspec "$file" "$pattern"; then
179
+ echo "yellow"
180
+ return 0
181
+ fi
182
+ done
183
+
184
+ for pattern in $green_patterns; do
185
+ if matches_pathspec "$file" "$pattern"; then
186
+ echo "green"
187
+ return 0
188
+ fi
189
+ done
190
+
191
+ echo "unspecified"
192
+ }
193
+
194
+ # =============================================================================
195
+ # Work Order Validation
196
+ # =============================================================================
197
+
198
+ has_work_order() {
199
+ [ -f "$WORK_ORDER_FILE" ]
200
+ }
201
+
202
+ is_work_order_stale() {
203
+ if ! has_work_order; then
204
+ return 1
205
+ fi
206
+
207
+ local threshold
208
+ threshold="$(get_config ".policy.stale_hours" "24")"
209
+ if ! [[ "$threshold" =~ ^[0-9]+$ ]]; then
210
+ threshold=24
211
+ fi
212
+
213
+ local created_at
214
+ created_at="$(json_get "$WORK_ORDER_FILE" ".created_at" "")"
215
+ if [ -z "$created_at" ]; then
216
+ return 1
217
+ fi
218
+
219
+ local created_epoch
220
+ created_epoch="$(node -e 'const d=new Date(process.argv[1]); const n=Number.isFinite(d.getTime())?Math.floor(d.getTime()/1000):0; process.stdout.write(String(n));' "$created_at" 2>/dev/null || echo "0")"
221
+ if [ "$created_epoch" -le 0 ]; then
222
+ return 1
223
+ fi
224
+
225
+ local now_epoch
226
+ now_epoch="$(node -e 'process.stdout.write(String(Math.floor(Date.now()/1000)));')"
227
+
228
+ local diff_hours=$(( (now_epoch - created_epoch) / 3600 ))
229
+ [ "$diff_hours" -ge "$threshold" ]
230
+ }
231
+
232
+ check_do_not_touch() {
233
+ local file="$1"
234
+ if ! has_work_order; then
235
+ return 1
236
+ fi
237
+
238
+ local patterns
239
+ patterns="$(json_get_array "$WORK_ORDER_FILE" ".do_not_touch")"
240
+ for pattern in $patterns; do
241
+ if matches_pathspec "$file" "$pattern"; then
242
+ return 0
243
+ fi
244
+ done
245
+
246
+ return 1
247
+ }
248
+
249
+ check_scope_violation() {
250
+ local file="$1"
251
+ if ! has_work_order; then
252
+ return 1
253
+ fi
254
+
255
+ local excludes
256
+ excludes="$(json_get_array "$WORK_ORDER_FILE" ".scope.exclude")"
257
+ for pattern in $excludes; do
258
+ if matches_pathspec "$file" "$pattern"; then
259
+ return 0
260
+ fi
261
+ done
262
+
263
+ local includes
264
+ includes="$(json_get_array "$WORK_ORDER_FILE" ".scope.include")"
265
+ if [ -z "$includes" ]; then
266
+ return 1
267
+ fi
268
+
269
+ for pattern in $includes; do
270
+ if matches_pathspec "$file" "$pattern"; then
271
+ return 1
272
+ fi
273
+ done
274
+
275
+ return 0
276
+ }
277
+
278
+ # =============================================================================
279
+ # Main Validation Function
280
+ # =============================================================================
281
+
282
+ # JSON output (machine readable)
283
+ emit_validation_json() {
284
+ local exit_code="$1"; shift
285
+ local enforcement="$1"; shift
286
+ local no_ticket_no_work="$1"; shift
287
+ local stale_hours="$1"; shift
288
+ local has_work_order_val="$1"; shift
289
+ local work_order_stale_val="$1"; shift
290
+ local green_list="$1"; shift
291
+ local yellow_list="$1"; shift
292
+ local red_list="$1"; shift
293
+ local black_list="$1"; shift
294
+ local do_not_touch_list="$1"; shift
295
+ local scope_violation_list="$1"; shift
296
+ local changed_list="$1"; shift
297
+
298
+ VIBE_EXIT_CODE="$exit_code" \
299
+ VIBE_ENFORCEMENT="$enforcement" \
300
+ VIBE_NO_TICKET_NO_WORK="$no_ticket_no_work" \
301
+ VIBE_STALE_HOURS="$stale_hours" \
302
+ VIBE_HAS_WORK_ORDER="$has_work_order_val" \
303
+ VIBE_WORK_ORDER_STALE="$work_order_stale_val" \
304
+ VIBE_CHANGED_FILES="$changed_list" \
305
+ VIBE_GREEN_FILES="$green_list" \
306
+ VIBE_YELLOW_FILES="$yellow_list" \
307
+ VIBE_RED_FILES="$red_list" \
308
+ VIBE_BLACK_FILES="$black_list" \
309
+ VIBE_DO_NOT_TOUCH_FILES="$do_not_touch_list" \
310
+ VIBE_SCOPE_VIOLATION_FILES="$scope_violation_list" \
311
+ node - <<'NODE'
312
+ const env = process.env;
313
+ const nl = String.fromCharCode(10);
314
+ const bs = String.fromCharCode(92);
315
+ const bsN = bs + "n";
316
+
317
+ const normalize = (v) => String(v ?? "").split(bsN).join(nl);
318
+ const list = (name) =>
319
+ normalize(env[name] ?? "")
320
+ .split(nl)
321
+ .map((s) => s.trim())
322
+ .filter(Boolean);
323
+
324
+ const exitCode = parseInt(env.VIBE_EXIT_CODE || "0", 10);
325
+ const status = exitCode === 0 ? "PASS" : exitCode === 1 ? "WARN" : "BLOCK";
326
+ const staleHours = parseInt(env.VIBE_STALE_HOURS || "24", 10);
327
+
328
+ const out = {
329
+ format_version: 1,
330
+ status,
331
+ exit_code: exitCode,
332
+ policy: {
333
+ enforcement: env.VIBE_ENFORCEMENT || "warn",
334
+ no_ticket_no_work: env.VIBE_NO_TICKET_NO_WORK === "true",
335
+ stale_hours: Number.isFinite(staleHours) ? staleHours : 24,
336
+ },
337
+ has_work_order: env.VIBE_HAS_WORK_ORDER === "true",
338
+ work_order_stale: env.VIBE_WORK_ORDER_STALE === "true",
339
+ files: {
340
+ changed: list("VIBE_CHANGED_FILES"),
341
+ green: list("VIBE_GREEN_FILES"),
342
+ yellow: list("VIBE_YELLOW_FILES"),
343
+ red: list("VIBE_RED_FILES"),
344
+ black: list("VIBE_BLACK_FILES"),
345
+ do_not_touch: list("VIBE_DO_NOT_TOUCH_FILES"),
346
+ scope_violation: list("VIBE_SCOPE_VIOLATION_FILES"),
347
+ },
348
+ };
349
+
350
+ process.stdout.write(JSON.stringify(out, null, 2));
351
+ NODE
352
+ }
353
+
354
+ # Exit codes: 0=pass, 1=warn, 2=block
355
+ validate_changes() {
356
+ local changed_files="$1"
357
+
358
+ local enforcement
359
+ enforcement="$(get_config ".policy.enforcement" "warn")"
360
+
361
+ local has_warning=false
362
+ local has_block=false
363
+
364
+ local green_files=""
365
+ local yellow_files=""
366
+ local red_files=""
367
+ local black_files=""
368
+ local do_not_touch_files=""
369
+ local scope_violation_files=""
370
+
371
+ local green_list=""
372
+ local yellow_list=""
373
+ local red_list=""
374
+ local black_list=""
375
+ local do_not_touch_list=""
376
+ local scope_violation_list=""
377
+
378
+ local no_ticket_no_work
379
+ no_ticket_no_work="$(get_config ".policy.no_ticket_no_work" "true")"
380
+
381
+ while IFS= read -r file; do
382
+ if [ -z "$file" ]; then
383
+ continue
384
+ fi
385
+
386
+ # 1) do_not_touch (always BLOCK when present)
387
+ if check_do_not_touch "$file"; then
388
+ do_not_touch_files="$do_not_touch_files\\n - $file"
389
+ do_not_touch_list="$do_not_touch_list\\n$file"
390
+ has_block=true
391
+ continue
392
+ fi
393
+
394
+ # 2) scope violation (warn/block per enforcement)
395
+ if check_scope_violation "$file"; then
396
+ scope_violation_files="$scope_violation_files\\n - $file"
397
+ scope_violation_list="$scope_violation_list\\n$file"
398
+ has_warning=true
399
+ fi
400
+
401
+ # 3) zone checks
402
+ local zone
403
+ zone="$(get_file_zone "$file")"
404
+ case "$zone" in
405
+ black)
406
+ black_files="$black_files\\n - $file"
407
+ black_list="$black_list\\n$file"
408
+ has_block=true
409
+ ;;
410
+ green)
411
+ green_files="$green_files\\n - $file"
412
+ green_list="$green_list\\n$file"
413
+ ;;
414
+ red)
415
+ red_files="$red_files\\n - $file"
416
+ red_list="$red_list\\n$file"
417
+ if [ "$no_ticket_no_work" = "true" ] && ! has_work_order; then
418
+ has_block=true
419
+ fi
420
+ ;;
421
+ yellow|unspecified)
422
+ yellow_files="$yellow_files\\n - $file"
423
+ yellow_list="$yellow_list\\n$file"
424
+ if [ "$no_ticket_no_work" = "true" ] && ! has_work_order; then
425
+ has_warning=true
426
+ fi
427
+ ;;
428
+ esac
429
+ done <<< "$changed_files"
430
+
431
+ local exit_code=0
432
+
433
+ if [ -n "$black_files" ]; then
434
+ exit_code=2
435
+ fi
436
+
437
+ if [ -n "$do_not_touch_files" ]; then
438
+ exit_code=2
439
+ fi
440
+
441
+ if [ -n "$red_files" ] && [ "$no_ticket_no_work" = "true" ] && ! has_work_order; then
442
+ exit_code=2
443
+ fi
444
+
445
+ if [ -n "$scope_violation_files" ]; then
446
+ if [ "$enforcement" = "block" ]; then
447
+ exit_code=2
448
+ elif [ "$exit_code" -lt 1 ]; then
449
+ exit_code=1
450
+ fi
451
+ fi
452
+
453
+ if [ -n "$yellow_files" ] && [ "$no_ticket_no_work" = "true" ] && ! has_work_order; then
454
+ if [ "$enforcement" = "block" ]; then
455
+ exit_code=2
456
+ elif [ "$exit_code" -lt 1 ]; then
457
+ exit_code=1
458
+ fi
459
+ fi
460
+
461
+ local has_work_order_val="false"
462
+ if has_work_order; then
463
+ has_work_order_val="true"
464
+ fi
465
+
466
+ local work_order_stale_val="false"
467
+ if has_work_order && is_work_order_stale; then
468
+ work_order_stale_val="true"
469
+ fi
470
+
471
+ local stale_hours
472
+ stale_hours="$(get_config ".policy.stale_hours" "24")"
473
+ if ! [[ "$stale_hours" =~ ^[0-9]+$ ]]; then
474
+ stale_hours="24"
475
+ fi
476
+
477
+ if [ "$OUTPUT_FORMAT" = "json" ]; then
478
+ emit_validation_json \
479
+ "$exit_code" \
480
+ "$enforcement" \
481
+ "$no_ticket_no_work" \
482
+ "$stale_hours" \
483
+ "$has_work_order_val" \
484
+ "$work_order_stale_val" \
485
+ "$green_list" \
486
+ "$yellow_list" \
487
+ "$red_list" \
488
+ "$black_list" \
489
+ "$do_not_touch_list" \
490
+ "$scope_violation_list" \
491
+ "$changed_files"
492
+ return $exit_code
493
+ fi
494
+
495
+ if [ -n "$black_files" ]; then
496
+ log_error "BLACK zone files (BLOCKED):$black_files"
497
+ fi
498
+
499
+ if [ -n "$do_not_touch_files" ]; then
500
+ log_error "DO_NOT_TOUCH violation (BLOCKED):$do_not_touch_files"
501
+ fi
502
+
503
+ if [ -n "$red_files" ] && [ "$no_ticket_no_work" = "true" ] && ! has_work_order; then
504
+ log_error "RED zone files without work order (BLOCKED):$red_files"
505
+ fi
506
+
507
+ if [ -n "$scope_violation_files" ]; then
508
+ log_warn "Scope violation:$scope_violation_files"
509
+ fi
510
+
511
+ if [ -n "$yellow_files" ] && [ "$no_ticket_no_work" = "true" ] && ! has_work_order; then
512
+ log_warn "YELLOW zone files without work order:$yellow_files"
513
+ fi
514
+
515
+ if has_work_order && is_work_order_stale; then
516
+ local threshold
517
+ threshold="$(get_config ".policy.stale_hours" "24")"
518
+ log_warn "Work order is stale (>\${threshold}h). Consider refreshing."
519
+ if [ "$exit_code" -lt 1 ]; then
520
+ exit_code=1
521
+ fi
522
+ fi
523
+
524
+ if [ "$exit_code" -eq 0 ]; then
525
+ log_info "Validation passed."
526
+ fi
527
+
528
+ return $exit_code
529
+ }
530
+
531
+ # =============================================================================
532
+ # CLI Entrypoint
533
+ # =============================================================================
534
+
535
+ if [[ "\${BASH_SOURCE[0]}" == "$0" ]]; then
536
+ while [[ $# -gt 0 ]]; do
537
+ case "$1" in
538
+ --format)
539
+ OUTPUT_FORMAT="$2"
540
+ shift 2
541
+ ;;
542
+ *)
543
+ shift
544
+ ;;
545
+ esac
546
+ done
547
+
548
+ CHANGED="$(
549
+ (
550
+ git diff --name-only 2>/dev/null
551
+ git diff --name-only --cached 2>/dev/null
552
+ git ls-files --others --exclude-standard 2>/dev/null
553
+ ) | sort -u
554
+ )"
555
+ validate_changes "$CHANGED"
556
+ exit $?
557
+ fi
558
+ `;
559
+ export const GITLEAKS_CONFIG_TEMPLATE = `title = "Vibe PM - Gitleaks config"
560
+
561
+ # Use built-in rules by default; add allowlist paths only when needed.
562
+
563
+ [allowlist]
564
+ description = "Allowlist for known false positives"
565
+ paths = [
566
+ # Example: "fixtures/**"
567
+ ]
568
+ `;
569
+ export const PRE_PUSH_HOOK_TEMPLATE = `#!/usr/bin/env bash
570
+ #
571
+ # Vibe PM pre-push hook
572
+ # Enforces "No Ticket No Work" policy
573
+ #
574
+
575
+ set -e
576
+
577
+ SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
578
+ source "$SCRIPT_DIR/../lib/validate.sh"
579
+
580
+ # Control plane off-limits (applies only to the Vibe PM repo)
581
+ PROTECTED_PATH_REGEX='^(\\.vibe/|vibecoding_helper/|adapters/|engines/|schemas/|fixtures/|docs/ssot/|docs/DEV_SPEC/implemented/|runs/|config/semgrep/|config/gitleaks/|policy/|scripts/generate-contracts\\.sh|scripts/generate-contract-lock\\.py|schemas/contracts\\.(version|lock)\\.json)'
582
+ GITLEAKS_CONFIG="config/gitleaks/.gitleaks.toml"
583
+
584
+ have_gitleaks() {
585
+ command -v gitleaks >/dev/null 2>&1
586
+ }
587
+
588
+ is_control_plane_repo() {
589
+ if [ -f "$REPO_ROOT/vibecoding_helper.spec" ]; then
590
+ return 0
591
+ fi
592
+ if [ -d "$REPO_ROOT/vibecoding_helper" ] && [ -d "$REPO_ROOT/adapters/mcp-ts" ]; then
593
+ return 0
594
+ fi
595
+ return 1
596
+ }
597
+
598
+ main() {
599
+ if [ ! -d "$VIBE_DIR" ]; then
600
+ log_info "Not a Vibe-managed repo. Skipping checks."
601
+ exit 0
602
+ fi
603
+
604
+ local enabled
605
+ enabled="$(get_config ".hooks.pre_push.enabled" "true")"
606
+ if [ "$enabled" != "true" ]; then
607
+ log_info "pre-push hook disabled. Skipping."
608
+ exit 0
609
+ fi
610
+
611
+ local gitleaks_required
612
+ gitleaks_required="$(get_config ".hooks.pre_push.gitleaks_required" "false")"
613
+
614
+ local remote="$1"
615
+ local url="$2"
616
+ local zero_sha="0000000000000000000000000000000000000000"
617
+ local gitleaks_warned="false"
618
+
619
+ while read local_ref local_oid remote_ref remote_oid; do
620
+ if [ "$local_oid" = "$zero_sha" ]; then
621
+ continue
622
+ fi
623
+
624
+ local changed_files
625
+ local log_opts
626
+ if [ "$remote_oid" = "0000000000000000000000000000000000000000" ]; then
627
+ local default_branch
628
+ default_branch="$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@')"
629
+ if [ -z "$default_branch" ]; then
630
+ default_branch="main"
631
+ fi
632
+ changed_files="$(git diff --name-only "origin/$default_branch"..."$local_oid" 2>/dev/null || git ls-files)"
633
+ if git rev-parse --verify "origin/$default_branch" >/dev/null 2>&1; then
634
+ log_opts="origin/$default_branch..$local_oid"
635
+ else
636
+ log_opts="$local_oid"
637
+ fi
638
+ else
639
+ changed_files="$(git diff --name-only "$remote_oid" "$local_oid")"
640
+ log_opts="$remote_oid..$local_oid"
641
+ fi
642
+
643
+ echo ""
644
+ echo "=============================================="
645
+ echo " VIBE PM: Pre-push Validation"
646
+ echo "=============================================="
647
+ echo ""
648
+
649
+ if is_control_plane_repo; then
650
+ if echo "$changed_files" | grep -E "$PROTECTED_PATH_REGEX" >/dev/null 2>&1; then
651
+ log_error "Control plane paths are off-limits (push blocked):"
652
+ echo "$changed_files" | grep -E "$PROTECTED_PATH_REGEX" || true
653
+ exit 1
654
+ fi
655
+ fi
656
+
657
+ set +e
658
+ validate_changes "$changed_files"
659
+ local result=$?
660
+ set -e
661
+
662
+ case $result in
663
+ 0)
664
+ log_info "Pre-push checks passed."
665
+ ;;
666
+ 1)
667
+ log_warn "Push allowed with warnings."
668
+ echo ""
669
+ echo "Consider creating a work order: vibe ticket"
670
+ echo ""
671
+ ;;
672
+ 2)
673
+ log_error "Push BLOCKED. Create a work order first."
674
+ echo ""
675
+ echo "Next action:"
676
+ echo " vibe ticket"
677
+ echo ""
678
+ echo "Or to skip (not recommended):"
679
+ echo " git push --no-verify"
680
+ echo ""
681
+ exit 1
682
+ ;;
683
+ esac
684
+
685
+ if have_gitleaks; then
686
+ if ! gitleaks detect --config "$GITLEAKS_CONFIG" --redact --no-banner --log-opts "$log_opts" --exit-code 1; then
687
+ log_error "gitleaks detected secrets in the push range."
688
+ exit 1
689
+ fi
690
+ else
691
+ if [ "$gitleaks_required" = "true" ]; then
692
+ log_error "gitleaks is required but not installed. Install gitleaks or disable requirement."
693
+ exit 1
694
+ fi
695
+ if [ "$gitleaks_warned" != "true" ]; then
696
+ log_warn "gitleaks not installed. Skipping secret scan."
697
+ gitleaks_warned="true"
698
+ fi
699
+ fi
700
+ done
701
+
702
+ exit 0
703
+ }
704
+
705
+ main "$@"
706
+ `;
707
+ export const GITHUB_ACTIONS_LOCAL_MODE_GUARD_WORKFLOW_TEMPLATE = `name: Vibe PM Local Mode Guard
708
+
709
+ on:
710
+ pull_request:
711
+ push:
712
+ branches: [ main ]
713
+
714
+ jobs:
715
+ vibe-local-mode-guard:
716
+ runs-on: ubuntu-latest
717
+ timeout-minutes: 5
718
+ steps:
719
+ - name: Checkout
720
+ uses: actions/checkout@v4
721
+ with:
722
+ fetch-depth: 0
723
+
724
+ - name: Determine diff range
725
+ shell: bash
726
+ run: |
727
+ set -euo pipefail
728
+ python - <<'PY'
729
+ import json, os, subprocess
730
+
731
+ event_path = os.environ.get("GITHUB_EVENT_PATH")
732
+ event_name = os.environ.get("GITHUB_EVENT_NAME", "")
733
+ head = os.environ.get("GITHUB_SHA", "")
734
+ base = ""
735
+
736
+ if event_path and os.path.exists(event_path):
737
+ with open(event_path, "r", encoding="utf-8") as f:
738
+ event = json.load(f)
739
+ if event_name == "pull_request":
740
+ base = (event.get("pull_request", {}) or {}).get("base", {}).get("sha", "")
741
+ elif event_name == "push":
742
+ base = event.get("before", "")
743
+
744
+ if (not base) or (set(base) == {"0"}):
745
+ base = subprocess.check_output(["git", "rev-list", "--max-parents=0", head]).decode().strip()
746
+
747
+ with open(os.environ["GITHUB_ENV"], "a", encoding="utf-8") as f:
748
+ f.write(f"VIBE_BASE_SHA={base}\\n")
749
+ f.write(f"VIBE_HEAD_SHA={head}\\n")
750
+
751
+ print(f"Base: {base}")
752
+ print(f"Head: {head}")
753
+ PY
754
+
755
+ - name: Compute changed files
756
+ shell: bash
757
+ run: |
758
+ set -euo pipefail
759
+ git diff --name-only "$VIBE_BASE_SHA" "$VIBE_HEAD_SHA" | sort -u > /tmp/vibe_changed_files.txt
760
+ echo "Changed files:"
761
+ cat /tmp/vibe_changed_files.txt || true
762
+
763
+ - name: Run Vibe guard
764
+ shell: bash
765
+ env:
766
+ # Default: WARN does not fail the job (seatbelt philosophy).
767
+ # Opt-in strict mode: set to "true" in the generated workflow.
768
+ VIBE_FAIL_ON_WARN: "false"
769
+ run: |
770
+ set -euo pipefail
771
+ if [[ ! -f ".vibe/lib/validate.sh" ]]; then
772
+ echo "::error title=Vibe guard missing::Missing .vibe/lib/validate.sh. Run 'vibe init' and commit generated files."
773
+ exit 1
774
+ fi
775
+ source ".vibe/lib/validate.sh"
776
+ CHANGED="$(cat /tmp/vibe_changed_files.txt)"
777
+ set +e
778
+ echo "::group::Vibe guard output"
779
+ output="$(validate_changes "$CHANGED" 2>&1)"
780
+ rc=$?
781
+ echo "$output"
782
+ echo "::endgroup::"
783
+ set -e
784
+
785
+ clean="$(echo "$output" | sed -r 's/\\x1B\\[[0-9;]*[mK]//g')"
786
+ {
787
+ echo "## Vibe PM Local Mode Guard"
788
+ echo ""
789
+ echo "**Changed files**"
790
+ echo ""
791
+ echo '~~~'
792
+ cat /tmp/vibe_changed_files.txt || true
793
+ echo '~~~'
794
+ echo ""
795
+ echo "**Validator output**"
796
+ echo ""
797
+ echo '~~~'
798
+ echo "$clean"
799
+ echo '~~~'
800
+ } >> "$GITHUB_STEP_SUMMARY"
801
+
802
+ escape_anno() {
803
+ local s="$1"
804
+ s="\${s//'%'/'%25'}"
805
+ s="\${s//$'\\n'/'%0A'}"
806
+ s="\${s//$'\\r'/'%0D'}"
807
+ echo "$s"
808
+ }
809
+
810
+ emit_annotation() {
811
+ local sev="$1"
812
+ local file="$2"
813
+ local msg="$3"
814
+
815
+ msg="$(escape_anno "$msg")"
816
+ file="$(escape_anno "$file")"
817
+
818
+ if [[ -n "$file" ]]; then
819
+ echo "::\${sev} file=\${file}::\${msg}"
820
+ else
821
+ echo "::\${sev}::\${msg}"
822
+ fi
823
+ }
824
+
825
+ current_sev=""
826
+ current_msg=""
827
+ while IFS= read -r line; do
828
+ if [[ "$line" =~ ^\\[VIBE\\ WARN\\][[:space:]](.*)$ ]]; then
829
+ current_sev="warning"
830
+ current_msg="\${BASH_REMATCH[1]}"
831
+ current_msg="\${current_msg%:}"
832
+ continue
833
+ fi
834
+ if [[ "$line" =~ ^\\[VIBE\\ BLOCK\\][[:space:]](.*)$ ]]; then
835
+ current_sev="error"
836
+ current_msg="\${BASH_REMATCH[1]}"
837
+ current_msg="\${current_msg%:}"
838
+ continue
839
+ fi
840
+ if [[ -n "$current_sev" && "$line" =~ ^[[:space:]]*-[[:space:]](.+)$ ]]; then
841
+ emit_annotation "$current_sev" "\${BASH_REMATCH[1]}" "$current_msg"
842
+ fi
843
+ done <<< "$clean"
844
+
845
+ if [[ $rc -eq 2 ]]; then
846
+ echo "::error title=Vibe guard blocked::Unsafe changes blocked. Create a work order first."
847
+ exit 1
848
+ fi
849
+ if [[ $rc -eq 1 ]]; then
850
+ echo "::warning title=Vibe guard warnings::Warnings found. Merge allowed, but review recommended."
851
+ if [[ "$VIBE_FAIL_ON_WARN" == "true" ]]; then
852
+ echo "::error title=Vibe guard strict mode::WARN treated as failure (VIBE_FAIL_ON_WARN=true)."
853
+ exit 1
854
+ fi
855
+ fi
856
+ `;