vibepro 0.1.0-alpha.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 (89) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +9 -0
  3. package/README.ja.md +448 -0
  4. package/README.md +520 -0
  5. package/agent-instructions/codex/AGENTS.vibepro.md +45 -0
  6. package/bin/vibepro.js +9 -0
  7. package/docs/assets/vibepro-header.png +0 -0
  8. package/package.json +51 -0
  9. package/skills/vibepro-diagnosis-packages/SKILL.md +133 -0
  10. package/skills/vibepro-human-review/SKILL.md +73 -0
  11. package/skills/vibepro-story-refactor/SKILL.md +89 -0
  12. package/skills/vibepro-workflow/SKILL.md +139 -0
  13. package/src/agent-harness-map.js +230 -0
  14. package/src/agent-harness-scanner.js +337 -0
  15. package/src/agent-review.js +2180 -0
  16. package/src/api-boundary-scanner.js +452 -0
  17. package/src/architecture-profiler.js +423 -0
  18. package/src/authorization-scoring.js +149 -0
  19. package/src/brainbase-importer.js +534 -0
  20. package/src/change-risk-classifier.js +195 -0
  21. package/src/check-packs.js +605 -0
  22. package/src/checkpoint-manager.js +233 -0
  23. package/src/cli.js +2213 -0
  24. package/src/code-quality-scanner.js +310 -0
  25. package/src/codex-manager.js +143 -0
  26. package/src/component-style-scanner.js +336 -0
  27. package/src/coverage-report.js +99 -0
  28. package/src/database-access-scanner.js +163 -0
  29. package/src/decision-records.js +315 -0
  30. package/src/design-modernize.js +1435 -0
  31. package/src/design-system.js +1732 -0
  32. package/src/diagnostic-engine.js +1945 -0
  33. package/src/diagram-requirement-resolver.js +194 -0
  34. package/src/doctor.js +677 -0
  35. package/src/environment-graph.js +424 -0
  36. package/src/execution-state.js +849 -0
  37. package/src/explore-evidence.js +425 -0
  38. package/src/flow-design-scanner.js +896 -0
  39. package/src/flow-verifier.js +887 -0
  40. package/src/gesture-interaction-scanner.js +330 -0
  41. package/src/graph-context.js +263 -0
  42. package/src/graphify-adapter.js +189 -0
  43. package/src/html-report.js +1035 -0
  44. package/src/journey-map.js +1299 -0
  45. package/src/language.js +48 -0
  46. package/src/lazy-pattern-detector.js +182 -0
  47. package/src/local-dev-scanner.js +135 -0
  48. package/src/managed-worktree-gate.js +187 -0
  49. package/src/managed-worktree.js +766 -0
  50. package/src/merge-manager.js +501 -0
  51. package/src/network-contract-scanner.js +442 -0
  52. package/src/nocodb-story-sync.js +386 -0
  53. package/src/oss-readiness-scanner.js +417 -0
  54. package/src/performance-evidence.js +756 -0
  55. package/src/performance-measurer.js +591 -0
  56. package/src/pr-manager.js +8220 -0
  57. package/src/presets.js +682 -0
  58. package/src/public-discovery-scanner.js +519 -0
  59. package/src/refactoring-delta-reporter.js +367 -0
  60. package/src/refactoring-opportunity-generator.js +797 -0
  61. package/src/regression-risk-scanner.js +146 -0
  62. package/src/repo-status.js +266 -0
  63. package/src/report-fingerprint.js +188 -0
  64. package/src/report-pr-body-prompt-template.md +108 -0
  65. package/src/report-pr-body-schema.json +95 -0
  66. package/src/report-store.js +135 -0
  67. package/src/report-validator.js +192 -0
  68. package/src/requirement-consistency.js +1066 -0
  69. package/src/runtime-info.js +134 -0
  70. package/src/self-dogfood-scanner.js +476 -0
  71. package/src/session-learning.js +164 -0
  72. package/src/skills-manager.js +157 -0
  73. package/src/spec-drift.js +378 -0
  74. package/src/spec-fingerprint.js +445 -0
  75. package/src/spec-prompt-template.md +155 -0
  76. package/src/spec-schema.json +219 -0
  77. package/src/spec-store.js +258 -0
  78. package/src/spec-validator.js +459 -0
  79. package/src/static-site-scanner.js +316 -0
  80. package/src/story-candidate-generator.js +85 -0
  81. package/src/story-catalog-generator.js +2813 -0
  82. package/src/story-html.js +156 -0
  83. package/src/story-manager.js +2144 -0
  84. package/src/story-task-generator.js +522 -0
  85. package/src/task-manager.js +1029 -0
  86. package/src/terminal-link-scanner.js +238 -0
  87. package/src/usage-report.js +417 -0
  88. package/src/verification-evidence.js +284 -0
  89. package/src/workspace.js +126 -0
@@ -0,0 +1,605 @@
1
+ import { mkdir, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ import { scanAgentHarness } from './agent-harness-scanner.js';
5
+ import { scanApiBoundary } from './api-boundary-scanner.js';
6
+ import { profileArchitecture } from './architecture-profiler.js';
7
+ import { scanCodeQuality } from './code-quality-scanner.js';
8
+ import { scanComponentStyle } from './component-style-scanner.js';
9
+ import { scanDatabaseAccess } from './database-access-scanner.js';
10
+ import { scanFlowDesign } from './flow-design-scanner.js';
11
+ import { scanGestureInteraction } from './gesture-interaction-scanner.js';
12
+ import { scanLocalDev } from './local-dev-scanner.js';
13
+ import { scanNetworkContracts } from './network-contract-scanner.js';
14
+ import { scanOssReadiness } from './oss-readiness-scanner.js';
15
+ import { runPerformanceMeasurement } from './performance-measurer.js';
16
+ import { preparePullRequest } from './pr-manager.js';
17
+ import { scanPublicDiscovery } from './public-discovery-scanner.js';
18
+ import { scanRegressionRisk } from './regression-risk-scanner.js';
19
+ import { scanSelfDogfood } from './self-dogfood-scanner.js';
20
+ import { scanStaticSite } from './static-site-scanner.js';
21
+ import { scanTerminalLinkContracts } from './terminal-link-scanner.js';
22
+ import { getWorkspaceDir, initWorkspace, readManifest, toWorkspaceRelative, writeManifest } from './workspace.js';
23
+ import { localizedText, resolveHumanOutputLanguage } from './language.js';
24
+
25
+ export const CHECK_PACKS = {
26
+ ui: {
27
+ title: 'UI experience check',
28
+ checks: ['component_style', 'flow_design', 'gesture_interaction', 'network_contracts', 'terminal_link_contracts']
29
+ },
30
+ security: {
31
+ title: 'Security boundary check',
32
+ checks: ['static_site', 'api_boundary', 'network_contracts', 'code_quality']
33
+ },
34
+ performance: {
35
+ title: 'Performance readiness check',
36
+ checks: ['database_access', 'local_dev', 'code_quality']
37
+ },
38
+ architecture: {
39
+ title: 'Architecture boundary check',
40
+ checks: ['architecture_profile', 'code_quality', 'api_boundary', 'database_access']
41
+ },
42
+ 'pr-readiness': {
43
+ title: 'PR readiness check',
44
+ checks: ['pr_prepare']
45
+ },
46
+ 'launch-readiness': {
47
+ title: 'Launch readiness check',
48
+ checks: ['static_site', 'api_boundary', 'network_contracts', 'component_style', 'flow_design', 'gesture_interaction', 'database_access', 'local_dev', 'code_quality']
49
+ },
50
+ 'agent-harness': {
51
+ title: 'AI agent harness readiness check',
52
+ checks: ['agent_harness']
53
+ },
54
+ 'public-discovery': {
55
+ title: 'Public discovery / AI search readiness check',
56
+ checks: ['public_discovery']
57
+ },
58
+ 'self-dogfood': {
59
+ title: 'VibePro self-dogfood gate readiness check',
60
+ checks: ['self_dogfood']
61
+ },
62
+ 'oss-readiness': {
63
+ title: 'OSS publication readiness check',
64
+ checks: ['oss_readiness']
65
+ },
66
+ 'regression-risk': {
67
+ title: 'Regression-risk (blast-radius) check',
68
+ checks: ['regression_risk']
69
+ },
70
+ all: {
71
+ title: 'All check packs',
72
+ checks: ['static_site', 'api_boundary', 'network_contracts', 'component_style', 'flow_design', 'gesture_interaction', 'terminal_link_contracts', 'database_access', 'local_dev', 'code_quality', 'architecture_profile']
73
+ }
74
+ };
75
+
76
+ export function listCheckPacks() {
77
+ return Object.entries(CHECK_PACKS).map(([id, pack]) => ({
78
+ id,
79
+ title: pack.title,
80
+ checks: pack.checks
81
+ }));
82
+ }
83
+
84
+ export async function runCheckPack(repoRoot, options = {}) {
85
+ await initWorkspace(repoRoot);
86
+ const root = path.resolve(repoRoot);
87
+ const language = await resolveHumanOutputLanguage(root, options);
88
+ const packId = options.packId ?? 'all';
89
+ const pack = CHECK_PACKS[packId];
90
+ if (!pack) {
91
+ throw new Error(`Unknown check pack: ${packId}. Available packs: ${Object.keys(CHECK_PACKS).join(', ')}`);
92
+ }
93
+
94
+ const runId = options.runId ?? new Date().toISOString().replace(/\.\d{3}Z$/, 'Z').replace(/:/g, '');
95
+ const runDir = path.join(getWorkspaceDir(root), 'checks', packId, runId);
96
+ await mkdir(runDir, { recursive: true });
97
+
98
+ const architectureProfile = await profileArchitecture(root);
99
+ const optionalChecks = packId === 'all'
100
+ ? [
101
+ ...(options.includeHarness === true ? ['agent_harness'] : []),
102
+ ...(options.includePublicDiscovery === true ? ['public_discovery'] : [])
103
+ ]
104
+ : [];
105
+ const checksToRun = [...pack.checks, ...optionalChecks];
106
+ const context = { root, pack: { ...pack, checks: checksToRun }, options, architectureProfile };
107
+ const evidence = {};
108
+ for (const check of checksToRun) {
109
+ evidence[check] = await runNamedCheck(check, context);
110
+ }
111
+ if (packId === 'performance' && options.measure === true) {
112
+ evidence.performance_measurement = (await runPerformanceMeasurement(root, {
113
+ runId: `${runId}-measure`,
114
+ baseUrl: options.baseUrl,
115
+ pages: options.pages ?? [],
116
+ apis: options.apis ?? [],
117
+ samples: options.samples ?? 5,
118
+ build: options.build === true,
119
+ typecheck: options.typecheck !== false,
120
+ commands: options.commands ?? [],
121
+ startups: options.startups ?? [],
122
+ prismaLog: options.prismaLog
123
+ })).measurement;
124
+ }
125
+
126
+ const checks = summarizeChecks({ packId, evidence, architectureProfile });
127
+ const status = aggregateStatus(checks);
128
+ const jsonPath = path.join(runDir, 'check.json');
129
+ const markdownPath = path.join(runDir, 'check.md');
130
+ const result = {
131
+ schema_version: '0.1.0',
132
+ run_id: runId,
133
+ created_at: new Date().toISOString(),
134
+ pack_id: packId,
135
+ title: pack.title,
136
+ status,
137
+ output: { language },
138
+ repo: { root: '.' },
139
+ checks,
140
+ artifacts: {
141
+ check_json: toWorkspaceRelative(root, jsonPath),
142
+ check_report: toWorkspaceRelative(root, markdownPath)
143
+ },
144
+ evidence
145
+ };
146
+
147
+ await writeFile(jsonPath, `${JSON.stringify(result, null, 2)}\n`);
148
+ await writeFile(markdownPath, renderCheckPack(result));
149
+
150
+ const manifest = await readManifest(root);
151
+ manifest.latest_check_run = runId;
152
+ manifest.latest_check_run_by_pack = {
153
+ ...(manifest.latest_check_run_by_pack ?? {}),
154
+ [packId]: runId
155
+ };
156
+ manifest.check_runs = [
157
+ {
158
+ run_id: runId,
159
+ pack_id: packId,
160
+ created_at: result.created_at,
161
+ status,
162
+ artifacts: {
163
+ check_json: result.artifacts.check_json,
164
+ check_report: result.artifacts.check_report
165
+ }
166
+ },
167
+ ...(manifest.check_runs ?? []).filter((item) => item.run_id !== runId)
168
+ ];
169
+ await writeManifest(root, manifest);
170
+
171
+ return {
172
+ runDir,
173
+ artifacts: {
174
+ json: toWorkspaceRelative(root, jsonPath),
175
+ markdown: toWorkspaceRelative(root, markdownPath)
176
+ },
177
+ check: result
178
+ };
179
+ }
180
+
181
+ async function runNamedCheck(check, context) {
182
+ const { root, architectureProfile, options } = context;
183
+ if (check === 'architecture_profile') return architectureProfile;
184
+ if (check === 'agent_harness') return scanAgentHarness(root);
185
+ if (check === 'public_discovery') return scanPublicDiscovery(root);
186
+ if (check === 'self_dogfood') return scanSelfDogfood(root, { storyId: options.storyId, env: options.env });
187
+ if (check === 'oss_readiness') return scanOssReadiness(root, { env: options.env });
188
+ if (check === 'regression_risk') return scanRegressionRisk(root, { top: options.top, coverageFile: options.coverageFile });
189
+ if (check === 'static_site') return scanStaticSite(root);
190
+ if (check === 'component_style') return scanComponentStyle(root);
191
+ if (check === 'flow_design') return scanFlowDesign(root, { story: { story_id: options.storyId ?? null, title: options.storyTitle ?? null } });
192
+ if (check === 'gesture_interaction') return scanGestureInteraction(root);
193
+ if (check === 'network_contracts') return scanNetworkContracts(root);
194
+ if (check === 'terminal_link_contracts') return scanTerminalLinkContracts(root);
195
+ if (check === 'database_access') return scanDatabaseAccess(root);
196
+ if (check === 'local_dev') return scanLocalDev(root);
197
+ if (check === 'code_quality') return scanCodeQuality(root);
198
+ if (check === 'api_boundary') {
199
+ return architectureProfile.applicable_checks.includes('api-boundary')
200
+ ? scanApiBoundary(root, architectureProfile)
201
+ : { route_count: 0, routes: [], summary: {}, protection_summary: {}, skipped: true, reason: 'api-boundary is not applicable to this repository' };
202
+ }
203
+ if (check === 'pr_prepare') {
204
+ if (!options.baseRef && !options.headRef) {
205
+ return {
206
+ status: 'needs_setup',
207
+ reason: 'pr-readiness requires --base <ref> or --head <ref> to prepare PR evidence'
208
+ };
209
+ }
210
+ const preparation = await preparePullRequest(root, {
211
+ storyId: options.storyId,
212
+ baseRef: options.baseRef,
213
+ headRef: options.headRef,
214
+ strict: options.strict === true
215
+ });
216
+ return preparation.preparation;
217
+ }
218
+ return { status: 'skipped', reason: `unknown check ${check}` };
219
+ }
220
+
221
+ function summarizeChecks({ packId, evidence, architectureProfile }) {
222
+ const checks = [];
223
+ if (evidence.architecture_profile) {
224
+ checks.push({
225
+ id: 'architecture_profile',
226
+ label: 'Architecture Profile',
227
+ status: 'pass',
228
+ summary: `${architectureProfile.app_type ?? 'unknown'} / checks: ${(architectureProfile.applicable_checks ?? []).join(', ') || '-'}`
229
+ });
230
+ }
231
+ if (evidence.agent_harness) {
232
+ checks.push(summarizeAgentHarness(evidence.agent_harness));
233
+ }
234
+ if (evidence.public_discovery) {
235
+ checks.push(...summarizeRiskGroups('public_discovery', 'Public discovery', evidence.public_discovery, [
236
+ ['structured_data_findings', 'Structured data'],
237
+ ['metadata_findings', 'Metadata'],
238
+ ['eeat_findings', 'E-E-A-T'],
239
+ ['image_findings', 'Images'],
240
+ ['content_findings', 'Content quality'],
241
+ ['ai_bot_findings', 'AI bot access'],
242
+ ['response_header_findings', 'Response headers']
243
+ ]));
244
+ if (evidence.public_discovery.suppressions) {
245
+ checks.push({
246
+ id: 'public_discovery.suppressions',
247
+ label: 'Public discovery: Suppressions',
248
+ status: evidence.public_discovery.suppressions.warnings.length > 0 ? 'needs_review' : 'pass',
249
+ summary: `${evidence.public_discovery.suppressions.suppressed_findings.length} suppressed; warnings=${evidence.public_discovery.suppressions.warnings.length}`
250
+ });
251
+ }
252
+ }
253
+ if (evidence.self_dogfood) {
254
+ const summary = evidence.self_dogfood.risk_summary?.findings ?? { block: 0, review: 0, info: 0 };
255
+ checks.push({
256
+ id: 'self_dogfood',
257
+ label: 'VibePro Self-Dogfood Gate Readiness',
258
+ status: statusFromRiskSummary(summary),
259
+ summary: `${evidence.self_dogfood.summary?.findings ?? 0} findings; block=${summary.block ?? 0}, review=${summary.review ?? 0}, info=${summary.info ?? 0}`
260
+ });
261
+ }
262
+ if (evidence.oss_readiness) {
263
+ const summary = evidence.oss_readiness.risk_summary?.findings ?? { block: 0, review: 0, info: 0 };
264
+ checks.push({
265
+ id: 'oss_readiness',
266
+ label: 'OSS Publication Readiness',
267
+ status: normalizeCheckStatus(evidence.oss_readiness.status),
268
+ summary: `${evidence.oss_readiness.summary?.tool_count ?? 0} tools; pass=${evidence.oss_readiness.summary?.pass ?? 0}, needs_setup=${evidence.oss_readiness.summary?.needs_setup ?? 0}, findings=${evidence.oss_readiness.summary?.findings ?? 0}; block=${summary.block ?? 0}, review=${summary.review ?? 0}, info=${summary.info ?? 0}`
269
+ });
270
+ }
271
+ if (evidence.static_site) {
272
+ checks.push(...summarizeRiskGroups('static_site', 'Static/Security', evidence.static_site, [
273
+ ['secret_hits', 'Secret candidates'],
274
+ ['xss_risk_hits', 'XSS candidates']
275
+ ]));
276
+ }
277
+ if (evidence.api_boundary) {
278
+ checks.push(summarizeApiBoundary(evidence.api_boundary));
279
+ }
280
+ if (evidence.network_contracts) {
281
+ checks.push(summarizeNetworkContracts(evidence.network_contracts));
282
+ }
283
+ if (evidence.component_style) {
284
+ checks.push(...summarizeRiskGroups('component_style', 'UI Style', evidence.component_style, [
285
+ ['legacy_style_hits', 'Legacy style tokens'],
286
+ ['interaction_reliability_hits', 'Interaction reliability']
287
+ ]));
288
+ }
289
+ if (evidence.flow_design) {
290
+ checks.push(summarizeFlowDesign(evidence.flow_design));
291
+ }
292
+ if (evidence.gesture_interaction) {
293
+ checks.push(...summarizeRiskGroups('gesture_interaction', 'Gesture Interaction', evidence.gesture_interaction, [
294
+ ['touch_action_hits', 'Touch action'],
295
+ ['overlay_pointer_hits', 'Overlay pointer'],
296
+ ['drag_tap_hits', 'Drag/tap suppression'],
297
+ ['carousel_hits', 'Carousel and hit area'],
298
+ ['map_marker_hits', 'Map marker layering']
299
+ ]));
300
+ }
301
+ if (evidence.terminal_link_contracts) {
302
+ checks.push(...summarizeRiskGroups('terminal_link_contracts', 'Terminal/File viewer', evidence.terminal_link_contracts, [
303
+ ['dot_directory_link_hits', 'Dot directory links'],
304
+ ['wrapped_terminal_link_hits', 'Wrapped terminal links'],
305
+ ['dot_directory_tree_hits', 'Dot directory tree'],
306
+ ['image_preview_extension_hits', 'Image preview extensions']
307
+ ]));
308
+ }
309
+ if (evidence.database_access) {
310
+ checks.push(...summarizeRiskGroups('database_access', 'Database access', evidence.database_access, [
311
+ ['unbounded_find_many', 'Unbounded findMany']
312
+ ]));
313
+ }
314
+ if (evidence.local_dev) {
315
+ checks.push(...summarizeRiskGroups('local_dev', 'Local development', evidence.local_dev, [
316
+ ['heavy_dev_scripts', 'Heavy dev scripts']
317
+ ]));
318
+ }
319
+ if (evidence.code_quality) {
320
+ checks.push(...summarizeRiskGroups('code_quality', 'Code quality', evidence.code_quality, [
321
+ ['authorization_order_risks', 'Authorization order'],
322
+ ['duplicate_query_shapes', 'Duplicate query shapes'],
323
+ ['responsibility_hotspots', 'Responsibility hotspots']
324
+ ]));
325
+ }
326
+ if (evidence.performance_measurement) {
327
+ checks.push({
328
+ id: 'performance_measurement',
329
+ label: 'Performance Measurement',
330
+ status: evidence.performance_measurement.summary?.items?.some((item) => /fail/i.test(String(item.value))) ? 'fail' : 'pass',
331
+ summary: `${evidence.performance_measurement.summary?.items?.length ?? 0} measurement summaries`
332
+ });
333
+ }
334
+ if (evidence.regression_risk) {
335
+ const regression = evidence.regression_risk;
336
+ const top = regression.hotspots?.[0];
337
+ checks.push({
338
+ id: 'regression_risk',
339
+ label: 'Regression Risk (blast radius)',
340
+ status: normalizeCheckStatus(regression.status),
341
+ summary: regression.status === 'skipped'
342
+ ? regression.reason
343
+ : `${regression.summary?.scored_modules ?? 0} modules; critical=${regression.summary?.critical ?? 0}, high=${regression.summary?.high ?? 0}, moderate=${regression.summary?.moderate ?? 0}${regression.summary?.coverage_source ? ` (coverage: ${regression.summary.coverage_source})` : ' (no coverage)'}${top ? `; top=${top.file} (fan-in ${top.fan_in}${top.coverage_pct !== null && top.coverage_pct !== undefined ? `, cov ${top.coverage_pct}%` : ''})` : ''}`
344
+ });
345
+ }
346
+ if (evidence.pr_prepare) {
347
+ checks.push({
348
+ id: 'pr_prepare',
349
+ label: 'PR Readiness',
350
+ status: normalizeCheckStatus(evidence.pr_prepare.gate_status?.overall_status ?? evidence.pr_prepare.status),
351
+ summary: evidence.pr_prepare.gate_status
352
+ ? `${evidence.pr_prepare.gate_status.overall_status}; ready=${evidence.pr_prepare.gate_status.ready_for_pr_create}`
353
+ : evidence.pr_prepare.reason
354
+ });
355
+ }
356
+ if (checks.length === 0) {
357
+ checks.push({ id: packId, label: 'Check Pack', status: 'skipped', summary: 'No checks were applicable.' });
358
+ }
359
+ return checks;
360
+ }
361
+
362
+ function summarizeRiskGroups(prefix, labelPrefix, evidence, groups) {
363
+ return groups.map(([key, label]) => {
364
+ const hits = Array.isArray(evidence[key]) ? evidence[key] : [];
365
+ const summary = evidence.risk_summary?.[key] ?? summarizeGateEffects(hits);
366
+ return {
367
+ id: `${prefix}.${key}`,
368
+ label: `${labelPrefix}: ${label}`,
369
+ status: statusFromRiskSummary(summary),
370
+ summary: formatRiskSummary(summary, hits.length)
371
+ };
372
+ });
373
+ }
374
+
375
+ function summarizeApiBoundary(apiBoundary) {
376
+ if (apiBoundary.skipped) {
377
+ return { id: 'api_boundary', label: 'API Boundary', status: 'skipped', summary: apiBoundary.reason };
378
+ }
379
+ const riskCount = (apiBoundary.routes ?? []).reduce((count, route) => count + (route.risk_hints?.length ?? 0), 0);
380
+ return {
381
+ id: 'api_boundary',
382
+ label: 'API Boundary',
383
+ status: riskCount > 0 ? 'needs_review' : 'pass',
384
+ summary: `${apiBoundary.route_count ?? 0} routes; ${riskCount} risk hints`
385
+ };
386
+ }
387
+
388
+ function summarizeNetworkContracts(networkContracts) {
389
+ const missing = networkContracts.missing_routes?.length ?? 0;
390
+ const dynamic = networkContracts.dynamic_calls?.length ?? 0;
391
+ const replacements = networkContracts.high_risk_replacements?.length ?? 0;
392
+ return {
393
+ id: 'network_contracts',
394
+ label: 'Network Contracts',
395
+ status: missing > 0 ? 'fail' : dynamic > 0 || replacements > 0 ? 'needs_review' : 'pass',
396
+ summary: `${networkContracts.api_client_call_count ?? 0} API client calls; missing=${missing}, dynamic=${dynamic}, server-function-replacements=${replacements}`
397
+ };
398
+ }
399
+
400
+ function summarizeAgentHarness(agentHarness) {
401
+ const summary = agentHarness.risk_summary?.findings ?? { block: 0, review: 0, info: 0 };
402
+ return {
403
+ id: 'agent_harness',
404
+ label: 'AI Agent Harness Readiness',
405
+ status: statusFromRiskSummary(summary),
406
+ summary: `${agentHarness.summary?.findings ?? 0} findings; codex=${agentHarness.summary?.codex_status ?? '-'}, claude=${agentHarness.summary?.claude_status ?? '-'}, skills=${agentHarness.summary?.skills_status ?? '-'}`
407
+ };
408
+ }
409
+
410
+ function summarizeFlowDesign(flowDesign) {
411
+ const count = [
412
+ flowDesign.silent_noop_hits,
413
+ flowDesign.ambiguous_primary_action_hits,
414
+ flowDesign.selection_side_effect_hits,
415
+ flowDesign.question_dead_end_hits,
416
+ flowDesign.dead_ui_state_hits,
417
+ flowDesign.interactive_contract_hits,
418
+ flowDesign.value_alignment_hits
419
+ ].reduce((total, items) => total + (Array.isArray(items) ? items.length : 0), 0);
420
+ return {
421
+ id: 'flow_design',
422
+ label: 'Flow Design',
423
+ status: normalizeCheckStatus(flowDesign.status),
424
+ summary: `${flowDesign.summary?.scanned_ui_files ?? 0} UI files; ${count} flow findings`
425
+ };
426
+ }
427
+
428
+ function summarizeGateEffects(hits) {
429
+ const summary = { block: 0, review: 0, info: 0 };
430
+ for (const hit of hits) {
431
+ const effect = ['block', 'review', 'info'].includes(hit.gate_effect) ? hit.gate_effect : 'info';
432
+ summary[effect] += 1;
433
+ }
434
+ return summary;
435
+ }
436
+
437
+ function statusFromRiskSummary(summary) {
438
+ if ((summary.block ?? 0) > 0) return 'fail';
439
+ if ((summary.review ?? 0) > 0) return 'needs_review';
440
+ return 'pass';
441
+ }
442
+
443
+ function formatRiskSummary(summary, total) {
444
+ return `${total} hits; block=${summary.block ?? 0}, review=${summary.review ?? 0}, info=${summary.info ?? 0}`;
445
+ }
446
+
447
+ function normalizeCheckStatus(status) {
448
+ if (['pass', 'passed', 'ready_for_review', 'ready'].includes(status)) return 'pass';
449
+ if (['fail', 'failed', 'blocked'].includes(status)) return 'fail';
450
+ if (['needs_setup', 'missing', 'skipped', 'not_required'].includes(status)) return status;
451
+ if (['needs_verification', 'needs_review', 'needs_quality_closure'].includes(status)) return 'needs_review';
452
+ return status ?? 'unknown';
453
+ }
454
+
455
+ function aggregateStatus(checks) {
456
+ const statuses = checks.map((check) => check.status);
457
+ if (statuses.includes('fail')) return 'fail';
458
+ if (statuses.includes('needs_setup')) return 'needs_setup';
459
+ if (statuses.includes('needs_review') || statuses.includes('needs_verification')) return 'needs_review';
460
+ if (statuses.every((status) => ['pass', 'skipped', 'not_required'].includes(status))) return 'pass';
461
+ return 'unknown';
462
+ }
463
+
464
+ export function renderCheckPack(result) {
465
+ const language = result.output?.language ?? 'ja';
466
+ const reviewItems = checksNeedingAttention(result.checks);
467
+ const lines = [
468
+ localizedText(language, { ja: '# VibeProチェックパック', en: '# VibePro Check Pack' }),
469
+ '',
470
+ `${localizedText(language, { ja: 'Run ID', en: 'Run ID' })}: ${result.run_id}`,
471
+ `${localizedText(language, { ja: 'Pack', en: 'Pack' })}: ${result.pack_id} - ${result.title}`,
472
+ `${localizedText(language, { ja: '状態', en: 'Status' })}: ${result.status}`,
473
+ '',
474
+ localizedText(language, { ja: '## チェック', en: '## Checks' }),
475
+ '',
476
+ localizedText(language, { ja: '| Check | 状態 | Summary |', en: '| Check | Status | Summary |' }),
477
+ '| ----- | ------ | ------- |'
478
+ ];
479
+ for (const check of result.checks) {
480
+ lines.push(`| ${check.label} | ${check.status} | ${escapeTable(check.summary ?? '')} |`);
481
+ }
482
+ lines.push(...renderCheckPackFindings(result));
483
+ lines.push(...renderCheckPackOnboarding({
484
+ result,
485
+ reviewItems
486
+ }));
487
+ return `${lines.join('\n')}\n`;
488
+ }
489
+
490
+ export function renderCheckPackSummary(result) {
491
+ const language = result.check.output?.language ?? 'ja';
492
+ const reviewItems = checksNeedingAttention(result.check.checks);
493
+ const lines = [
494
+ localizedText(language, {
495
+ ja: `check packを作成しました: ${result.artifacts.markdown}`,
496
+ en: `check pack created: ${result.artifacts.markdown}`
497
+ }),
498
+ `${localizedText(language, { ja: 'status', en: 'status' })}: ${result.check.status}`,
499
+ '',
500
+ localizedText(language, { ja: '| Check | 状態 | Summary |', en: '| Check | Status | Summary |' }),
501
+ '| ----- | ------ | ------- |'
502
+ ];
503
+ for (const check of result.check.checks) {
504
+ lines.push(`| ${check.label} | ${check.status} | ${escapeTable(check.summary ?? '')} |`);
505
+ }
506
+ lines.push(...renderCheckPackFindings(result.check));
507
+ lines.push(...renderCheckPackOnboarding({
508
+ result: result.check,
509
+ reviewItems,
510
+ artifacts: result.artifacts
511
+ }));
512
+ return `${lines.join('\n')}\n`;
513
+ }
514
+
515
+ function checksNeedingAttention(checks) {
516
+ return checks.filter((check) => !['pass', 'skipped', 'not_required'].includes(check.status));
517
+ }
518
+
519
+ function renderCheckPackFindings(result) {
520
+ const language = result.output?.language ?? 'ja';
521
+ const findings = collectCheckPackFindings(result);
522
+ if (findings.length === 0) return [];
523
+ const lines = [
524
+ '',
525
+ localizedText(language, { ja: '## 検出事項', en: '## Findings' }),
526
+ '',
527
+ localizedText(language, { ja: '| Severity | Finding | Path | Action |', en: '| Severity | Finding | Path | Action |' }),
528
+ '| -------- | ------- | ---- | ------ |'
529
+ ];
530
+ for (const finding of findings.slice(0, 50)) {
531
+ const label = [
532
+ finding.id,
533
+ finding.detail
534
+ ].filter(Boolean).join(' - ');
535
+ lines.push(`| ${finding.severity ?? 'info'} | ${escapeTable(label)} | ${escapeTable(finding.path ?? finding.story_id ?? '')} | ${escapeTable(finding.required_action ?? '')} |`);
536
+ }
537
+ if (findings.length > 50) {
538
+ lines.push(`| info | ${localizedText(language, {
539
+ ja: `${findings.length - 50}件の追加findingはMarkdownから省略しました。JSON evidenceを確認してください。`,
540
+ en: `${findings.length - 50} additional findings omitted from markdown; see JSON evidence.`
541
+ })} | | ${localizedText(language, { ja: 'machine-readable evidenceを確認する。', en: 'See machine-readable evidence.' })} |`);
542
+ }
543
+ return lines;
544
+ }
545
+
546
+ function collectCheckPackFindings(result) {
547
+ const evidence = result.evidence ?? {};
548
+ const findings = [];
549
+ for (const value of Object.values(evidence)) {
550
+ if (Array.isArray(value?.findings)) findings.push(...value.findings);
551
+ if (value && typeof value === 'object') {
552
+ for (const nested of Object.values(value)) {
553
+ if (Array.isArray(nested?.findings)) findings.push(...nested.findings);
554
+ }
555
+ }
556
+ }
557
+ return findings;
558
+ }
559
+
560
+ function renderCheckPackOnboarding({ result, reviewItems, artifacts = null }) {
561
+ const language = result.output?.language ?? 'ja';
562
+ const markdownPath = artifacts?.markdown ?? result.artifacts?.check_report ?? '.vibepro/checks/<pack>/<run-id>/check.md';
563
+ const jsonPath = artifacts?.json ?? result.artifacts?.check_json ?? '.vibepro/checks/<pack>/<run-id>/check.json';
564
+ const attentionSummary = reviewItems.length === 0
565
+ ? ['- none']
566
+ : reviewItems.map((check) => `- ${check.label}: ${check.status} - ${check.summary ?? ''}`);
567
+ const optionalAgentHarness = result.pack_id === 'all' && !result.evidence?.agent_harness
568
+ ? [localizedText(language, {
569
+ ja: '- このrepoでAI駆動開発の標準化も見る場合は `vibepro check agent-harness <repo>` または `vibepro check all <repo> --include-harness` を実行してください。',
570
+ en: '- If you also want to standardize AI-driven development in this repo, run `vibepro check agent-harness <repo>` or `vibepro check all <repo> --include-harness`.'
571
+ })]
572
+ : [];
573
+ const optionalPublicDiscovery = result.pack_id === 'all' && !result.evidence?.public_discovery
574
+ ? [localizedText(language, {
575
+ ja: '- public page / AI-search readiness診断も見る場合は `vibepro check public-discovery <repo>` または `vibepro check all <repo> --include-public-discovery` を実行してください。',
576
+ en: '- If you also want public page / AI-search readiness diagnostics, run `vibepro check public-discovery <repo>` or `vibepro check all <repo> --include-public-discovery`.'
577
+ })]
578
+ : [];
579
+ return [
580
+ '',
581
+ localizedText(language, { ja: '## 次に見る場所', en: '## Next Steps' }),
582
+ '',
583
+ localizedText(language, { ja: `- 人間向けreport: ${markdownPath}`, en: `- Human-readable report: ${markdownPath}` }),
584
+ localizedText(language, { ja: `- 機械可読evidence: ${jsonPath}`, en: `- Machine-readable evidence: ${jsonPath}` }),
585
+ localizedText(language, { ja: '- 初回診断だけの場合は、Statusと needs_review / fail のcheckを共有してください。', en: '- If this is only a first diagnosis, share the Status and the checks listed under needs_review / fail.' }),
586
+ localizedText(language, { ja: '- PR作業の場合はfindingを対応または分類した後、`vibepro pr prepare <repo> --story-id <story-id> --base <base-branch>` を実行してください。', en: '- If this is PR work, run `vibepro pr prepare <repo> --story-id <story-id> --base <base-branch>` after addressing or classifying findings.' }),
587
+ ...optionalAgentHarness,
588
+ ...optionalPublicDiscovery,
589
+ '',
590
+ localizedText(language, { ja: '## 共有テンプレート', en: '## Share Template' }),
591
+ '',
592
+ '```text',
593
+ localizedText(language, { ja: `VibePro check ${result.pack_id} が完了しました。`, en: `VibePro check ${result.pack_id} completed.` }),
594
+ `Run ID: ${result.run_id}`,
595
+ `Status: ${result.status}`,
596
+ `Report: ${markdownPath}`,
597
+ localizedText(language, { ja: 'Needs review / fail:', en: 'Needs review / fail:' }),
598
+ ...attentionSummary,
599
+ '```'
600
+ ];
601
+ }
602
+
603
+ function escapeTable(value) {
604
+ return String(value).replace(/\|/g, '\\|').replace(/\r?\n/g, '<br>');
605
+ }