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,425 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { promisify } from 'node:util';
5
+
6
+ import { getWorkspaceDir, initWorkspace, toWorkspaceRelative } from './workspace.js';
7
+ import { localizedText, resolveHumanOutputLanguage } from './language.js';
8
+
9
+ const execFileAsync = promisify(execFile);
10
+ const DEFAULT_EXPLORE_ROLES = ['codebase_context', 'risk_surface', 'test_surface'];
11
+ const EXPLORE_STATUSES = new Set(['pass', 'needs_review', 'block']);
12
+
13
+ export async function prepareExploreEvidence(repoRoot, options = {}) {
14
+ const storyId = requireStoryId(options.storyId, 'explore prepare');
15
+ const root = path.resolve(repoRoot);
16
+ await initWorkspace(root);
17
+ const language = await resolveHumanOutputLanguage(root, options);
18
+ const exploreDir = getExploreDir(root, storyId);
19
+ const requestDir = path.join(exploreDir, 'requests');
20
+ await mkdir(requestDir, { recursive: true });
21
+
22
+ const roles = parseRoles(options.roles);
23
+ const gitContext = await collectGitContext(root);
24
+ const plan = {
25
+ schema_version: '0.1.0',
26
+ story_id: storyId,
27
+ topic: options.topic ?? 'read-only codebase exploration before implementation',
28
+ created_at: new Date().toISOString(),
29
+ output: { language },
30
+ mode: 'read_only_exploration',
31
+ git_context: gitContext,
32
+ roles,
33
+ instructions: buildExploreInstructions(language),
34
+ requests: roles.map((role) => ({
35
+ role,
36
+ artifact: toWorkspaceRelative(root, getExploreRequestPath(requestDir, role)),
37
+ record_command: buildExploreRecordCommand({ storyId, role })
38
+ }))
39
+ };
40
+
41
+ await writeJson(path.join(exploreDir, 'explore-plan.json'), plan);
42
+ await writeFile(path.join(exploreDir, 'parallel-dispatch.md'), renderExploreDispatch(plan, language));
43
+ for (const role of roles) {
44
+ await writeFile(getExploreRequestPath(requestDir, role), renderExploreRequest({ plan, role, language }));
45
+ }
46
+ const summary = await buildExploreSummary(root, storyId);
47
+ await writeExploreSummary(root, storyId, summary);
48
+ return {
49
+ plan,
50
+ summary,
51
+ artifacts: {
52
+ plan: toWorkspaceRelative(root, path.join(exploreDir, 'explore-plan.json')),
53
+ parallel_dispatch: toWorkspaceRelative(root, path.join(exploreDir, 'parallel-dispatch.md')),
54
+ summary_json: toWorkspaceRelative(root, path.join(exploreDir, 'explore-summary.json')),
55
+ summary_markdown: toWorkspaceRelative(root, path.join(exploreDir, 'explore-summary.md')),
56
+ requests: Object.fromEntries(roles.map((role) => [role, toWorkspaceRelative(root, getExploreRequestPath(requestDir, role))]))
57
+ }
58
+ };
59
+ }
60
+
61
+ export async function recordExploreEvidence(repoRoot, options = {}) {
62
+ const storyId = requireStoryId(options.storyId, 'explore record');
63
+ const role = options.role;
64
+ if (!role) throw new Error('explore record requires --role <role>');
65
+ if (!EXPLORE_STATUSES.has(options.status)) {
66
+ throw new Error(`explore record --status must be one of: ${[...EXPLORE_STATUSES].join(', ')}`);
67
+ }
68
+ if (!options.summary && !options.stdinText) {
69
+ throw new Error('explore record requires --summary <text> or --from-stdin');
70
+ }
71
+ const root = path.resolve(repoRoot);
72
+ await initWorkspace(root);
73
+ const exploreDir = getExploreDir(root, storyId);
74
+ const resultDir = path.join(exploreDir, 'results');
75
+ await mkdir(resultDir, { recursive: true });
76
+ const result = {
77
+ schema_version: '0.1.0',
78
+ story_id: storyId,
79
+ role,
80
+ status: options.status,
81
+ summary: options.summary ?? options.stdinText.trim(),
82
+ findings: parseFindings(options.findings ?? []),
83
+ artifacts: (options.artifacts ?? []).map((artifact) => normalizeArtifact(root, artifact)),
84
+ recorded_at: new Date().toISOString(),
85
+ git_context: await collectGitContext(root),
86
+ agent_provenance: buildAgentProvenance(root, options)
87
+ };
88
+ const resultPath = path.join(resultDir, `${sanitizeId(role)}.json`);
89
+ await writeJson(resultPath, result);
90
+ const summary = await buildExploreSummary(root, storyId);
91
+ await writeExploreSummary(root, storyId, summary);
92
+ return {
93
+ evidence: result,
94
+ summary,
95
+ artifact: toWorkspaceRelative(root, resultPath)
96
+ };
97
+ }
98
+
99
+ export async function getExploreEvidenceStatus(repoRoot, options = {}) {
100
+ const storyId = requireStoryId(options.storyId, 'explore status');
101
+ const root = path.resolve(repoRoot);
102
+ await initWorkspace(root);
103
+ return buildExploreSummary(root, storyId);
104
+ }
105
+
106
+ export async function summarizeExploreEvidenceForPr(repoRoot, options = {}) {
107
+ if (!options.storyId) return null;
108
+ const root = path.resolve(repoRoot);
109
+ const summaryPath = path.join(getExploreDir(root, options.storyId), 'explore-summary.json');
110
+ return readJsonIfExists(summaryPath);
111
+ }
112
+
113
+ export function renderExplorePrepareSummary(result) {
114
+ const language = result.plan.output?.language ?? 'ja';
115
+ if (language === 'en') {
116
+ return `# Explore Prepare
117
+
118
+ - story: ${result.plan.story_id}
119
+ - topic: ${result.plan.topic}
120
+ - roles: ${result.plan.roles.join(', ')}
121
+ - plan: ${result.artifacts.plan}
122
+ - parallel dispatch: ${result.artifacts.parallel_dispatch}
123
+ - summary: ${result.artifacts.summary_markdown}
124
+ `;
125
+ }
126
+ return `# Explore準備
127
+
128
+ - story: ${result.plan.story_id}
129
+ - topic: ${result.plan.topic}
130
+ - roles: ${result.plan.roles.join(', ')}
131
+ - plan: ${result.artifacts.plan}
132
+ - parallel dispatch: ${result.artifacts.parallel_dispatch}
133
+ - summary: ${result.artifacts.summary_markdown}
134
+ `;
135
+ }
136
+
137
+ export function renderExploreRecordSummary(result) {
138
+ return `# Explore Record
139
+
140
+ - story: ${result.evidence.story_id}
141
+ - role: ${result.evidence.role}
142
+ - status: ${result.evidence.status}
143
+ - agent provenance: ${result.evidence.agent_provenance.system}/${result.evidence.agent_provenance.execution_mode}
144
+ - artifact: ${result.artifact}
145
+ `;
146
+ }
147
+
148
+ export function renderExploreStatusSummary(status) {
149
+ const rows = (status.roles ?? []).map((role) => `- ${role.role}: ${role.status} - ${role.summary ?? '-'}`);
150
+ return `# Explore Status
151
+
152
+ - story: ${status.story_id}
153
+ - status: ${status.status}
154
+ - recorded: ${status.summary.recorded_role_count}/${status.summary.expected_role_count}
155
+
156
+ ${rows.join('\n') || '- no explore evidence recorded'}
157
+ `;
158
+ }
159
+
160
+ export function renderExplorePrSection(exploreEvidence) {
161
+ if (!exploreEvidence) return '- Explore evidence未生成';
162
+ const rows = (exploreEvidence.roles ?? []).map((role) => `- ${role.role}: ${role.status}${role.summary ? ` - ${role.summary}` : ''}`);
163
+ return `- Status: ${exploreEvidence.status}
164
+ - Recorded roles: ${exploreEvidence.summary?.recorded_role_count ?? 0}/${exploreEvidence.summary?.expected_role_count ?? 0}
165
+ ${rows.join('\n') || '- no explore evidence recorded'}`;
166
+ }
167
+
168
+ async function buildExploreSummary(root, storyId) {
169
+ const exploreDir = getExploreDir(root, storyId);
170
+ const plan = await readJsonIfExists(path.join(exploreDir, 'explore-plan.json'));
171
+ const expectedRoles = plan?.roles ?? DEFAULT_EXPLORE_ROLES;
172
+ const results = await readExploreResults(path.join(exploreDir, 'results'));
173
+ const roles = expectedRoles.map((role) => {
174
+ const result = results.find((item) => item.role === role);
175
+ return result
176
+ ? {
177
+ role,
178
+ status: result.status,
179
+ summary: result.summary,
180
+ recorded_at: result.recorded_at,
181
+ agent_provenance: result.agent_provenance
182
+ }
183
+ : { role, status: 'missing', summary: null };
184
+ });
185
+ const status = roles.some((role) => role.status === 'block')
186
+ ? 'block'
187
+ : roles.some((role) => ['needs_review', 'missing'].includes(role.status))
188
+ ? 'needs_review'
189
+ : 'pass';
190
+ return {
191
+ schema_version: '0.1.0',
192
+ story_id: storyId,
193
+ status,
194
+ plan_created_at: plan?.created_at ?? null,
195
+ topic: plan?.topic ?? null,
196
+ roles,
197
+ artifacts: {
198
+ plan: await pathExists(path.join(exploreDir, 'explore-plan.json')) ? toWorkspaceRelative(root, path.join(exploreDir, 'explore-plan.json')) : null,
199
+ parallel_dispatch: await pathExists(path.join(exploreDir, 'parallel-dispatch.md')) ? toWorkspaceRelative(root, path.join(exploreDir, 'parallel-dispatch.md')) : null,
200
+ summary_json: toWorkspaceRelative(root, path.join(exploreDir, 'explore-summary.json')),
201
+ summary_markdown: toWorkspaceRelative(root, path.join(exploreDir, 'explore-summary.md'))
202
+ },
203
+ summary: {
204
+ expected_role_count: expectedRoles.length,
205
+ recorded_role_count: results.length,
206
+ missing_role_count: roles.filter((role) => role.status === 'missing').length,
207
+ needs_review_count: roles.filter((role) => role.status === 'needs_review').length,
208
+ block_count: roles.filter((role) => role.status === 'block').length
209
+ }
210
+ };
211
+ }
212
+
213
+ async function writeExploreSummary(root, storyId, summary) {
214
+ const exploreDir = getExploreDir(root, storyId);
215
+ await mkdir(exploreDir, { recursive: true });
216
+ await writeJson(path.join(exploreDir, 'explore-summary.json'), summary);
217
+ await writeFile(path.join(exploreDir, 'explore-summary.md'), renderExploreStatusSummary(summary));
218
+ }
219
+
220
+ function buildExploreInstructions(language = 'ja') {
221
+ return localizedText(language, {
222
+ ja: [
223
+ 'read-only explorationだけを行い、fileを編集しない。',
224
+ '広いsummaryより、具体的なfile path、確認したcommand、観測したriskを優先する。',
225
+ 'PR preparationがこのcontextを表示できるよう、vibepro explore recordで結果を記録する。'
226
+ ],
227
+ en: [
228
+ 'Use read-only exploration only. Do not edit files.',
229
+ 'Prefer concrete file paths, commands, and observed risks over broad summaries.',
230
+ 'Record the result with vibepro explore record so PR preparation can surface this context.'
231
+ ]
232
+ });
233
+ }
234
+
235
+ function renderExploreDispatch(plan, language = plan?.output?.language ?? 'ja') {
236
+ if (language === 'en') {
237
+ return `# VibePro Explore Dispatch
238
+
239
+ Story: ${plan.story_id}
240
+ Topic: ${plan.topic}
241
+
242
+ Dispatch these read-only exploration requests in parallel. Do not edit files.
243
+
244
+ ${plan.requests.map((request) => `- ${request.role}: ${request.artifact}\n - record: \`${request.record_command}\``).join('\n')}
245
+ `;
246
+ }
247
+ return `# VibePro Explore Dispatch
248
+
249
+ Story: ${plan.story_id}
250
+ Topic: ${plan.topic}
251
+
252
+ 下記のread-only exploration requestをparallelでdispatchする。fileは編集しない。
253
+
254
+ ${plan.requests.map((request) => `- ${request.role}: ${request.artifact}\n - record: \`${request.record_command}\``).join('\n')}
255
+ `;
256
+ }
257
+
258
+ function renderExploreRequest({ plan, role, language = plan?.output?.language ?? 'ja' }) {
259
+ if (language === 'en') {
260
+ return `# VibePro Explore Request
261
+
262
+ Story: ${plan.story_id}
263
+ Role: ${role}
264
+ Topic: ${plan.topic}
265
+
266
+ ## Rules
267
+
268
+ - Read-only exploration only.
269
+ - Return concrete file paths, commands inspected, risks, and unknowns.
270
+ - Do not make code changes.
271
+
272
+ ## Output
273
+
274
+ - status: pass | needs_review | block
275
+ - summary
276
+ - findings
277
+
278
+ ## Record
279
+
280
+ \`${buildExploreRecordCommand({ storyId: plan.story_id, role })}\`
281
+ `;
282
+ }
283
+ return `# VibePro Explore Request
284
+
285
+ Story: ${plan.story_id}
286
+ Role: ${role}
287
+ Topic: ${plan.topic}
288
+
289
+ ## ルール
290
+
291
+ - read-only explorationだけを行う。
292
+ - 具体的なfile path、確認したcommand、risk、unknownを返す。
293
+ - code changeは行わない。
294
+
295
+ ## 出力
296
+
297
+ - status: pass | needs_review | block
298
+ - summary
299
+ - findings
300
+
301
+ ## 記録
302
+
303
+ \`${buildExploreRecordCommand({ storyId: plan.story_id, role })}\`
304
+ `;
305
+ }
306
+
307
+ function buildExploreRecordCommand({ storyId, role }) {
308
+ return `vibepro explore record . --id ${storyId} --role ${role} --status <pass|needs_review|block> --summary <text> --agent-system codex|claude_code --execution-mode parallel_subagent --agent-id <id>`;
309
+ }
310
+
311
+ function parseRoles(roles = []) {
312
+ const parsed = roles.flatMap((value) => String(value).split(',')).map((value) => value.trim()).filter(Boolean);
313
+ return parsed.length > 0 ? [...new Set(parsed.map(sanitizeId))] : DEFAULT_EXPLORE_ROLES;
314
+ }
315
+
316
+ async function readExploreResults(resultDir) {
317
+ try {
318
+ const entries = await readdir(resultDir, { withFileTypes: true });
319
+ const results = [];
320
+ for (const entry of entries) {
321
+ if (!entry.isFile() || !entry.name.endsWith('.json')) continue;
322
+ const result = await readJsonIfExists(path.join(resultDir, entry.name));
323
+ if (result) results.push(result);
324
+ }
325
+ return results.sort((a, b) => a.role.localeCompare(b.role));
326
+ } catch (error) {
327
+ if (error.code === 'ENOENT') return [];
328
+ throw error;
329
+ }
330
+ }
331
+
332
+ function buildAgentProvenance(root, options = {}) {
333
+ return {
334
+ system: options.agentSystem ?? 'unknown',
335
+ execution_mode: options.executionMode ?? 'unknown',
336
+ agent_id: options.agentId ?? null,
337
+ agent_model: options.agentModel ?? null,
338
+ transcript: options.agentTranscript ? normalizeArtifact(root, options.agentTranscript) : null,
339
+ recorded_by: options.recordedBy ?? 'vibepro explore record'
340
+ };
341
+ }
342
+
343
+ function parseFindings(values) {
344
+ return values.map((value) => {
345
+ const [severity, id, ...detailParts] = String(value).split(':');
346
+ return {
347
+ severity: severity || 'info',
348
+ id: id || 'finding',
349
+ detail: detailParts.join(':') || value
350
+ };
351
+ });
352
+ }
353
+
354
+ function normalizeArtifact(root, artifact) {
355
+ if (!artifact) return artifact;
356
+ const absolute = path.resolve(root, artifact);
357
+ const relative = path.relative(root, absolute);
358
+ return relative.startsWith('..') ? artifact : relative;
359
+ }
360
+
361
+ async function collectGitContext(root) {
362
+ const [head, status] = await Promise.all([
363
+ runGit(root, ['rev-parse', 'HEAD']),
364
+ runGit(root, ['status', '--porcelain'])
365
+ ]);
366
+ return {
367
+ head: head.trim() || null,
368
+ dirty: status.trim().length > 0,
369
+ dirty_files: status.split('\n').filter(Boolean).map((line) => line.slice(3))
370
+ };
371
+ }
372
+
373
+ async function runGit(root, args) {
374
+ try {
375
+ const { stdout } = await execFileAsync('git', args, { cwd: root });
376
+ return stdout;
377
+ } catch {
378
+ return '';
379
+ }
380
+ }
381
+
382
+ async function readJsonIfExists(filePath) {
383
+ try {
384
+ return JSON.parse(await readFile(filePath, 'utf8'));
385
+ } catch (error) {
386
+ if (error.code === 'ENOENT') return null;
387
+ throw error;
388
+ }
389
+ }
390
+
391
+ async function writeJson(filePath, value) {
392
+ await mkdir(path.dirname(filePath), { recursive: true });
393
+ await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`);
394
+ }
395
+
396
+ async function pathExists(filePath) {
397
+ try {
398
+ await stat(filePath);
399
+ return true;
400
+ } catch (error) {
401
+ if (error.code === 'ENOENT') return false;
402
+ throw error;
403
+ }
404
+ }
405
+
406
+ function getExploreDir(root, storyId) {
407
+ return path.join(getWorkspaceDir(root), 'explore', storyId);
408
+ }
409
+
410
+ function getExploreRequestPath(requestDir, role) {
411
+ return path.join(requestDir, `${sanitizeId(role)}.md`);
412
+ }
413
+
414
+ function sanitizeId(value) {
415
+ return String(value ?? '')
416
+ .trim()
417
+ .toLowerCase()
418
+ .replace(/[^a-z0-9_-]+/g, '-')
419
+ .replace(/^-+|-+$/g, '') || 'explore';
420
+ }
421
+
422
+ function requireStoryId(storyId, commandName) {
423
+ if (!storyId) throw new Error(`${commandName} requires --id <story-id>`);
424
+ return storyId;
425
+ }