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,284 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
3
+ import { mkdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { setTimeout as sleep } from 'node:timers/promises';
6
+ import { promisify } from 'node:util';
7
+
8
+ import { getWorkspaceDir, toWorkspaceRelative } from './workspace.js';
9
+ import { assertManagedWorktreeCommandAllowed } from './managed-worktree-gate.js';
10
+
11
+ const execFileAsync = promisify(execFile);
12
+ const ALLOWED_KINDS = new Set(['unit', 'integration', 'e2e', 'typecheck', 'build']);
13
+ const ALLOWED_STATUSES = new Set(['pass', 'passed', 'success', 'ok', 'fail', 'failed', 'error', 'needs_setup']);
14
+ const EVIDENCE_LOCK_TIMEOUT_MS = 10000;
15
+ const EVIDENCE_LOCK_STALE_MS = 60000;
16
+
17
+ export async function recordVerificationEvidence(repoRoot, options = {}) {
18
+ const storyId = options.storyId;
19
+ if (!storyId) throw new Error('verify record requires --id <story-id>');
20
+ if (!ALLOWED_KINDS.has(options.kind)) {
21
+ throw new Error(`verify record --kind must be one of: ${[...ALLOWED_KINDS].join(', ')}`);
22
+ }
23
+ if (!ALLOWED_STATUSES.has(options.status)) {
24
+ throw new Error(`verify record --status must be one of: ${[...ALLOWED_STATUSES].join(', ')}`);
25
+ }
26
+
27
+ const root = path.resolve(repoRoot);
28
+ await assertInitializedWorkspace(root);
29
+ await assertManagedWorktreeCommandAllowed(root, {
30
+ storyId,
31
+ commandName: 'verify record'
32
+ });
33
+ const prDir = path.join(getWorkspaceDir(root), 'pr', storyId);
34
+ await mkdir(prDir, { recursive: true });
35
+ const evidencePath = path.join(prDir, 'verification-evidence.json');
36
+ const gitContext = await collectEvidenceGitContext(root);
37
+ const evidence = await withEvidenceLock(evidencePath, async () => {
38
+ const existing = await readEvidence(root, evidencePath, storyId);
39
+ const managedWorktreeWarning = normalizeWarning(options.managedWorktreeWarning);
40
+ const command = {
41
+ kind: options.kind,
42
+ status: options.status,
43
+ command: options.command ?? null,
44
+ summary: options.summary ?? options.status,
45
+ artifact: options.artifact ? normalizeArtifact(root, options.artifact) : null,
46
+ executed_at: options.executedAt ?? new Date().toISOString(),
47
+ git_context: gitContext,
48
+ managed_worktree_context: normalizeManagedWorktreeContext(options.managedWorktreeContext),
49
+ warnings: managedWorktreeWarning ? [managedWorktreeWarning] : []
50
+ };
51
+ const commands = [
52
+ command,
53
+ ...existing.commands.filter((item) => item.kind !== command.kind)
54
+ ];
55
+ const nextEvidence = {
56
+ schema_version: '0.1.0',
57
+ story_id: storyId,
58
+ updated_at: new Date().toISOString(),
59
+ warnings: mergeWarnings(existing.warnings, command.warnings),
60
+ commands
61
+ };
62
+ await writeJsonAtomic(evidencePath, nextEvidence);
63
+ return nextEvidence;
64
+ });
65
+ return {
66
+ evidence,
67
+ artifact: toWorkspaceRelative(root, evidencePath)
68
+ };
69
+ }
70
+
71
+ export function renderVerificationEvidenceSummary(result) {
72
+ const latest = result.evidence.commands[0];
73
+ const warnings = latest.warnings?.length
74
+ ? latest.warnings.map((warning) => `- ${warning.id}: ${warning.reason}`).join('\n')
75
+ : '- none';
76
+ return `# VibePro Verification Evidence
77
+
78
+ - story: ${result.evidence.story_id}
79
+ - kind: ${latest.kind}
80
+ - status: ${latest.status}
81
+ - command: ${latest.command ?? '-'}
82
+ - artifact: ${result.artifact}
83
+
84
+ ## Warnings
85
+
86
+ ${warnings}
87
+ `;
88
+ }
89
+
90
+ async function readEvidence(repoRoot, evidencePath, storyId) {
91
+ try {
92
+ const parsed = JSON.parse(await readFile(evidencePath, 'utf8'));
93
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
94
+ throw new SyntaxError('verification evidence root must be a JSON object');
95
+ }
96
+ return {
97
+ ...parsed,
98
+ commands: Array.isArray(parsed.commands) ? parsed.commands : []
99
+ };
100
+ } catch (error) {
101
+ if (error.code === 'ENOENT') {
102
+ return {
103
+ schema_version: '0.1.0',
104
+ story_id: storyId,
105
+ warnings: [],
106
+ commands: []
107
+ };
108
+ }
109
+ if (error instanceof SyntaxError) {
110
+ const backupPath = await quarantineCorruptEvidence(repoRoot, evidencePath);
111
+ throw new Error(
112
+ `verification evidence JSON is corrupt: ${toWorkspaceRelative(repoRoot, evidencePath)}. ` +
113
+ `Moved the corrupt file to ${toWorkspaceRelative(repoRoot, backupPath)}; inspect it before recording new evidence.`
114
+ );
115
+ }
116
+ throw error;
117
+ }
118
+ }
119
+
120
+ function normalizeWarning(warning) {
121
+ return warning && typeof warning === 'object' ? warning : null;
122
+ }
123
+
124
+ function normalizeManagedWorktreeContext(context) {
125
+ return context && typeof context === 'object' ? context : null;
126
+ }
127
+
128
+ function mergeWarnings(existing = [], next = []) {
129
+ const warnings = [];
130
+ const seen = new Set();
131
+ for (const warning of [...next, ...existing]) {
132
+ if (!warning?.id) continue;
133
+ const key = `${warning.id}:${warning.command_name ?? ''}:${warning.reason ?? ''}`;
134
+ if (seen.has(key)) continue;
135
+ seen.add(key);
136
+ warnings.push(warning);
137
+ }
138
+ return warnings;
139
+ }
140
+
141
+ async function quarantineCorruptEvidence(repoRoot, evidencePath) {
142
+ const backupPath = `${evidencePath}.corrupt-${Date.now()}-${process.pid}.bak`;
143
+ await rename(evidencePath, backupPath);
144
+ return backupPath;
145
+ }
146
+
147
+ async function withEvidenceLock(evidencePath, action) {
148
+ const lockPath = `${evidencePath}.lock`;
149
+ await acquireLock(lockPath);
150
+ try {
151
+ return await action();
152
+ } finally {
153
+ await rm(lockPath, { recursive: true, force: true });
154
+ }
155
+ }
156
+
157
+ async function acquireLock(lockPath) {
158
+ const startedAt = Date.now();
159
+ while (Date.now() - startedAt < EVIDENCE_LOCK_TIMEOUT_MS) {
160
+ try {
161
+ await mkdir(lockPath);
162
+ return;
163
+ } catch (error) {
164
+ if (error.code !== 'EEXIST') throw error;
165
+ await removeStaleLock(lockPath);
166
+ await sleep(25 + Math.floor(Math.random() * 25));
167
+ }
168
+ }
169
+ throw new Error(`Timed out waiting for verification evidence lock: ${lockPath}`);
170
+ }
171
+
172
+ async function removeStaleLock(lockPath) {
173
+ try {
174
+ const info = await stat(lockPath);
175
+ if (Date.now() - info.mtimeMs > EVIDENCE_LOCK_STALE_MS) {
176
+ await rm(lockPath, { recursive: true, force: true });
177
+ }
178
+ } catch (error) {
179
+ if (error.code !== 'ENOENT') throw error;
180
+ }
181
+ }
182
+
183
+ async function writeJsonAtomic(filePath, value) {
184
+ const dir = path.dirname(filePath);
185
+ const base = path.basename(filePath);
186
+ const tempPath = path.join(
187
+ dir,
188
+ `.${base}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`
189
+ );
190
+ try {
191
+ await writeFile(tempPath, `${JSON.stringify(value, null, 2)}\n`);
192
+ await rename(tempPath, filePath);
193
+ } catch (error) {
194
+ await rm(tempPath, { force: true });
195
+ throw error;
196
+ }
197
+ }
198
+
199
+ async function assertInitializedWorkspace(repoRoot) {
200
+ try {
201
+ await readFile(path.join(getWorkspaceDir(repoRoot), 'vibepro-manifest.json'), 'utf8');
202
+ } catch (error) {
203
+ if (error.code === 'ENOENT') {
204
+ throw new Error('verify record requires an initialized VibePro workspace. Run `vibepro init <repo>` first.');
205
+ }
206
+ throw error;
207
+ }
208
+ }
209
+
210
+ function normalizeArtifact(repoRoot, artifact) {
211
+ const resolved = path.resolve(repoRoot, artifact);
212
+ return toWorkspaceRelative(repoRoot, resolved);
213
+ }
214
+
215
+ async function collectEvidenceGitContext(repoRoot) {
216
+ const [headSha, currentBranch, statusOutput] = await Promise.all([
217
+ gitOptional(repoRoot, ['rev-parse', 'HEAD']),
218
+ gitOptional(repoRoot, ['branch', '--show-current']),
219
+ gitStatus(repoRoot)
220
+ ]);
221
+ const dirtyDiff = await collectDirtyDiff(repoRoot);
222
+ return {
223
+ head_sha: headSha || null,
224
+ current_branch: currentBranch || null,
225
+ dirty: statusOutput.length > 0,
226
+ status_fingerprint_hash: hashFingerprint(fingerprintStatus(statusOutput, dirtyDiff)),
227
+ recorded_at: new Date().toISOString()
228
+ };
229
+ }
230
+
231
+ async function gitOptional(repoRoot, args) {
232
+ try {
233
+ const { stdout } = await execFileAsync('git', args, { cwd: repoRoot, encoding: 'utf8' });
234
+ return stdout.trim();
235
+ } catch {
236
+ return '';
237
+ }
238
+ }
239
+
240
+ async function gitStatus(repoRoot) {
241
+ try {
242
+ const { stdout } = await execFileAsync('git', ['status', '--porcelain', '-uall'], { cwd: repoRoot, encoding: 'utf8' });
243
+ return stdout.trimEnd();
244
+ } catch {
245
+ return '';
246
+ }
247
+ }
248
+
249
+ async function collectDirtyDiff(repoRoot) {
250
+ const [unstaged, staged, untracked] = await Promise.all([
251
+ gitOptional(repoRoot, ['diff', '--binary']),
252
+ gitOptional(repoRoot, ['diff', '--cached', '--binary']),
253
+ collectUntrackedFileFingerprint(repoRoot)
254
+ ]);
255
+ return [staged, unstaged, untracked].filter(Boolean).join('\n');
256
+ }
257
+
258
+ async function collectUntrackedFileFingerprint(repoRoot) {
259
+ const output = await gitOptional(repoRoot, ['ls-files', '--others', '--exclude-standard']);
260
+ const files = output.split('\n').filter(Boolean).sort().slice(0, 200);
261
+ const chunks = [];
262
+ for (const file of files) {
263
+ try {
264
+ const content = await readFile(path.join(repoRoot, file), 'utf8');
265
+ chunks.push(`untracked:${file}\n${content}`);
266
+ } catch {
267
+ chunks.push(`untracked:${file}\n<unreadable>`);
268
+ }
269
+ }
270
+ return chunks.join('\n');
271
+ }
272
+
273
+ function fingerprintStatus(statusOutput, dirtyDiff = '') {
274
+ return [
275
+ 'git-status --porcelain -uall',
276
+ String(statusOutput ?? '').trimEnd(),
277
+ 'git-diff --binary',
278
+ String(dirtyDiff ?? '').trimEnd()
279
+ ].join('\n');
280
+ }
281
+
282
+ function hashFingerprint(value) {
283
+ return createHash('sha256').update(String(value ?? '')).digest('hex');
284
+ }
@@ -0,0 +1,126 @@
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ export const SCHEMA_VERSION = '0.1.0';
5
+ export const WORKSPACE_DIR = '.vibepro';
6
+ export const MANIFEST_FILE = 'vibepro-manifest.json';
7
+ export const DEFAULT_BRAINBASE_STORIES = [{
8
+ story_id: 'story-vibepro-diagnosis-commercialization-roadmap',
9
+ title: 'M1: VibePro 診断→商用化ロードマップ',
10
+ ssot: 'NocoDB',
11
+ horizon: null,
12
+ view: null,
13
+ period: null,
14
+ started_at: null,
15
+ due_at: null
16
+ }];
17
+
18
+ export async function initWorkspace(repoRoot, options = {}) {
19
+ const root = path.resolve(repoRoot);
20
+ const workspaceDir = path.join(root, WORKSPACE_DIR);
21
+ const outputLanguage = options.language === 'en' ? 'en' : 'ja';
22
+ await mkdir(workspaceDir, { recursive: true });
23
+ await mkdir(path.join(workspaceDir, 'graphify'), { recursive: true });
24
+ await mkdir(path.join(workspaceDir, 'diagnostics'), { recursive: true });
25
+ await mkdir(path.join(workspaceDir, 'raw'), { recursive: true });
26
+ await mkdir(path.join(workspaceDir, 'spec'), { recursive: true });
27
+
28
+ await writeJsonIfMissing(path.join(workspaceDir, 'config.json'), {
29
+ schema_version: SCHEMA_VERSION,
30
+ tool: 'vibepro',
31
+ workspace: WORKSPACE_DIR,
32
+ output: {
33
+ language: outputLanguage
34
+ },
35
+ execution: {
36
+ managed_worktree: 'preferred'
37
+ },
38
+ brainbase: {
39
+ stories: DEFAULT_BRAINBASE_STORIES
40
+ }
41
+ }, 'VibePro config');
42
+
43
+ await writeJsonIfMissing(path.join(workspaceDir, MANIFEST_FILE), createManifest(root), 'VibePro manifest');
44
+ await ensureGitIgnore(root);
45
+
46
+ return { repoRoot: root, workspaceDir };
47
+ }
48
+
49
+ export async function readManifest(repoRoot) {
50
+ const manifestPath = getManifestPath(repoRoot);
51
+ try {
52
+ return JSON.parse(await readFile(manifestPath, 'utf8'));
53
+ } catch (error) {
54
+ if (error.code === 'ENOENT') {
55
+ await initWorkspace(repoRoot);
56
+ return JSON.parse(await readFile(manifestPath, 'utf8'));
57
+ }
58
+ throw error;
59
+ }
60
+ }
61
+
62
+ export async function writeManifest(repoRoot, manifest, options = {}) {
63
+ await writeFile(getManifestPath(repoRoot), `${JSON.stringify(manifest, null, 2)}\n`, options);
64
+ }
65
+
66
+ export function getWorkspaceDir(repoRoot) {
67
+ return path.join(path.resolve(repoRoot), WORKSPACE_DIR);
68
+ }
69
+
70
+ export function toWorkspaceRelative(repoRoot, filePath) {
71
+ return path.relative(path.resolve(repoRoot), filePath).split(path.sep).join('/');
72
+ }
73
+
74
+ function getManifestPath(repoRoot) {
75
+ return path.join(getWorkspaceDir(repoRoot), MANIFEST_FILE);
76
+ }
77
+
78
+ function createManifest(repoRoot) {
79
+ return {
80
+ schema_version: SCHEMA_VERSION,
81
+ tool: 'vibepro',
82
+ repo: {
83
+ root: '.',
84
+ git_remote: null,
85
+ commit: null
86
+ },
87
+ latest_run: null,
88
+ artifacts: {},
89
+ runs: []
90
+ };
91
+ }
92
+
93
+ async function writeJsonIfMissing(filePath, value, label = 'VibePro JSON') {
94
+ try {
95
+ JSON.parse(await readFile(filePath, 'utf8'));
96
+ } catch (error) {
97
+ if (error.code !== 'ENOENT') {
98
+ if (error instanceof SyntaxError) {
99
+ throw new Error(`${label} is invalid JSON: ${filePath}. Repair or remove it before running vibepro init.`);
100
+ }
101
+ throw error;
102
+ }
103
+ await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`);
104
+ }
105
+ }
106
+
107
+ async function ensureGitIgnore(repoRoot) {
108
+ const ignorePath = path.join(path.resolve(repoRoot), '.gitignore');
109
+ const required = [
110
+ '.vibepro/',
111
+ '.worktrees/vibepro/'
112
+ ];
113
+
114
+ let existing = '';
115
+ try {
116
+ existing = await readFile(ignorePath, 'utf8');
117
+ } catch (error) {
118
+ if (error.code !== 'ENOENT') throw error;
119
+ }
120
+
121
+ const missing = required.filter((line) => !existing.includes(line));
122
+ if (missing.length === 0) return;
123
+
124
+ const prefix = existing.trim().length > 0 ? `${existing.trimEnd()}\n` : '';
125
+ await writeFile(ignorePath, `${prefix}${missing.join('\n')}\n`);
126
+ }