vibe-coder-kit 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vibe/behaviors/assumption.md +43 -0
- package/.vibe/behaviors/honesty.md +49 -0
- package/.vibe/behaviors/reflection.md +56 -0
- package/.vibe/config.json +25 -0
- package/.vibe/core/circuit-breaker.ts +113 -0
- package/.vibe/core/cli.ts +280 -0
- package/.vibe/core/cost-tracker.ts +157 -0
- package/.vibe/core/dag.ts +320 -0
- package/.vibe/core/event-store.ts +318 -0
- package/.vibe/core/health-check.ts +113 -0
- package/.vibe/core/idempotency.ts +92 -0
- package/.vibe/core/index.ts +16 -0
- package/.vibe/core/knowledge-store.ts +199 -0
- package/.vibe/core/plugin-registry.ts +174 -0
- package/.vibe/core/saga.ts +141 -0
- package/.vibe/core/team-config.ts +232 -0
- package/.vibe/core/telemetry.ts +154 -0
- package/.vibe/core/validator.ts +168 -0
- package/.vibe/flows/00-init.md +34 -0
- package/.vibe/flows/01-clarify.md +45 -0
- package/.vibe/flows/02-brainstorm.md +42 -0
- package/.vibe/flows/03-plan.md +36 -0
- package/.vibe/flows/05-code.md +49 -0
- package/.vibe/flows/06-review.md +41 -0
- package/.vibe/flows/08-learn.md +40 -0
- package/.vibe/phase-graph.json +111 -0
- package/.vibe/plugins/core/quality/rules.yaml +49 -0
- package/.vibe/plugins/core/security/rules.yaml +57 -0
- package/.vibe/templates/github-actions.yml +121 -0
- package/AGENTS.md +88 -0
- package/README.md +129 -0
- package/bin/vibe.js +351 -0
- package/package.json +62 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// Team Configuration & RBAC
|
|
2
|
+
// Multi-tenant, role-based access control
|
|
3
|
+
|
|
4
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
|
|
7
|
+
export type Role = 'junior' | 'senior' | 'lead' | 'admin';
|
|
8
|
+
|
|
9
|
+
export interface TeamMember {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
role: Role;
|
|
13
|
+
teams: string[];
|
|
14
|
+
permissions: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface TeamConfig {
|
|
18
|
+
version: string;
|
|
19
|
+
teams: Record<string, {
|
|
20
|
+
plugins: string[];
|
|
21
|
+
overrides: Record<string, unknown>;
|
|
22
|
+
}>;
|
|
23
|
+
roles: Record<Role, {
|
|
24
|
+
enforce: 'strict' | 'warn' | 'off';
|
|
25
|
+
suggest: boolean;
|
|
26
|
+
permissions: string[];
|
|
27
|
+
}>;
|
|
28
|
+
governance: {
|
|
29
|
+
approvers: string[];
|
|
30
|
+
requiredReviews: number;
|
|
31
|
+
compliance: string[];
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const BLOCKED_KEYS = ['__proto__', 'constructor', 'prototype'];
|
|
36
|
+
|
|
37
|
+
export class TeamManager {
|
|
38
|
+
private config: TeamConfig;
|
|
39
|
+
private members: TeamMember[] = [];
|
|
40
|
+
private configPath: string;
|
|
41
|
+
|
|
42
|
+
constructor(rootDir: string) {
|
|
43
|
+
this.configPath = join(rootDir, '.vibe', 'team.json');
|
|
44
|
+
this.config = this.loadConfig();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private loadConfig(): TeamConfig {
|
|
48
|
+
if (existsSync(this.configPath)) {
|
|
49
|
+
try {
|
|
50
|
+
const content = readFileSync(this.configPath, 'utf-8');
|
|
51
|
+
const parsed = JSON.parse(content);
|
|
52
|
+
return this.sanitizeConfig(parsed);
|
|
53
|
+
} catch (e) {
|
|
54
|
+
console.warn(`[TeamConfig] Failed to parse config: ${(e as Error).message}, using defaults`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const defaultConfig: TeamConfig = {
|
|
59
|
+
version: '2.0',
|
|
60
|
+
teams: {
|
|
61
|
+
default: {
|
|
62
|
+
plugins: ['@vibe/core'],
|
|
63
|
+
overrides: {},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
roles: {
|
|
67
|
+
junior: {
|
|
68
|
+
enforce: 'strict',
|
|
69
|
+
suggest: true,
|
|
70
|
+
permissions: ['read', 'suggest'],
|
|
71
|
+
},
|
|
72
|
+
senior: {
|
|
73
|
+
enforce: 'warn',
|
|
74
|
+
suggest: true,
|
|
75
|
+
permissions: ['read', 'write', 'suggest', 'approve'],
|
|
76
|
+
},
|
|
77
|
+
lead: {
|
|
78
|
+
enforce: 'off',
|
|
79
|
+
suggest: true,
|
|
80
|
+
permissions: ['read', 'write', 'suggest', 'approve', 'config'],
|
|
81
|
+
},
|
|
82
|
+
admin: {
|
|
83
|
+
enforce: 'off',
|
|
84
|
+
suggest: true,
|
|
85
|
+
permissions: ['all'],
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
governance: {
|
|
89
|
+
approvers: [],
|
|
90
|
+
requiredReviews: 1,
|
|
91
|
+
compliance: [],
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
writeFileSync(this.configPath, JSON.stringify(defaultConfig, null, 2));
|
|
96
|
+
return defaultConfig;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private sanitizeConfig(obj: unknown): unknown {
|
|
100
|
+
if (obj === null || typeof obj !== 'object') {
|
|
101
|
+
return obj;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (Array.isArray(obj)) {
|
|
105
|
+
return obj.map(item => this.sanitizeConfig(item));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const sanitized: Record<string, unknown> = {};
|
|
109
|
+
for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {
|
|
110
|
+
if (BLOCKED_KEYS.includes(key)) {
|
|
111
|
+
console.warn(`[TeamConfig] Blocked potentially dangerous key: ${key}`);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
sanitized[key] = this.sanitizeConfig(value);
|
|
115
|
+
}
|
|
116
|
+
return sanitized;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
getTeamConfig(teamName: string): TeamConfig['teams'][string] | undefined {
|
|
120
|
+
return this.config.teams[teamName];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getRoleConfig(role: Role): TeamConfig['roles'][role] | undefined {
|
|
124
|
+
return this.config.roles[role];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
hasPermission(role: Role, permission: string): boolean {
|
|
128
|
+
const roleConfig = this.config.roles[role];
|
|
129
|
+
if (!roleConfig) return false;
|
|
130
|
+
if (roleConfig.permissions.includes('all')) return true;
|
|
131
|
+
return roleConfig.permissions.includes(permission);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
canApprove(role: Role): boolean {
|
|
135
|
+
return this.hasPermission(role, 'approve');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
canDeploy(role: Role): boolean {
|
|
139
|
+
return this.hasPermission(role, 'deploy');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
canEditConfig(role: Role): boolean {
|
|
143
|
+
return this.hasPermission(role, 'config');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
canWriteEvent(role: Role): boolean {
|
|
147
|
+
return this.hasPermission(role, 'write');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
getEnforcementLevel(role: Role): 'strict' | 'warn' | 'off' {
|
|
151
|
+
return this.config.roles[role]?.enforce || 'warn';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
validateRoleAction(role: Role, action: string): { allowed: boolean; reason: string } {
|
|
155
|
+
const roleConfig = this.config.roles[role];
|
|
156
|
+
if (!roleConfig) {
|
|
157
|
+
return { allowed: false, reason: `Unknown role: ${role}` };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (roleConfig.permissions.includes('all')) {
|
|
161
|
+
return { allowed: true, reason: 'Admin has all permissions' };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (roleConfig.permissions.includes(action)) {
|
|
165
|
+
return { allowed: true, reason: `Role '${role}' has permission '${action}'` };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
allowed: false,
|
|
170
|
+
reason: `Role '${role}' does not have permission '${action}'`,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
addMember(member: Omit<TeamMember, 'id'>): TeamMember {
|
|
175
|
+
const newMember: TeamMember = {
|
|
176
|
+
...member,
|
|
177
|
+
id: `member-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
|
178
|
+
};
|
|
179
|
+
this.members.push(newMember);
|
|
180
|
+
return newMember;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
getMember(id: string): TeamMember | undefined {
|
|
184
|
+
return this.members.find(m => m.id === id);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
getMembersByRole(role: Role): TeamMember[] {
|
|
188
|
+
return this.members.filter(m => m.role === role);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
getMembersByTeam(team: string): TeamMember[] {
|
|
192
|
+
return this.members.filter(m => m.teams.includes(team));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
updateConfig(updates: Partial<TeamConfig>, role: Role): { success: boolean; reason: string } {
|
|
196
|
+
// RBAC check
|
|
197
|
+
if (!this.canEditConfig(role)) {
|
|
198
|
+
return {
|
|
199
|
+
success: false,
|
|
200
|
+
reason: `Role '${role}' does not have permission to edit config`,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const sanitizedUpdates = this.sanitizeConfig(updates) as Partial<TeamConfig>;
|
|
205
|
+
this.config = this.deepMerge(this.config, sanitizedUpdates);
|
|
206
|
+
writeFileSync(this.configPath, JSON.stringify(this.config, null, 2));
|
|
207
|
+
return { success: true, reason: 'Config updated successfully' };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private deepMerge<T>(target: T, source: Partial<T>): T {
|
|
211
|
+
const result = { ...target };
|
|
212
|
+
for (const key in source) {
|
|
213
|
+
if (BLOCKED_KEYS.includes(key)) continue;
|
|
214
|
+
|
|
215
|
+
if (source.hasOwnProperty(key)) {
|
|
216
|
+
const sourceVal = source[key];
|
|
217
|
+
const targetVal = (result as Record<string, unknown>)[key];
|
|
218
|
+
if (sourceVal && typeof sourceVal === 'object' && !Array.isArray(sourceVal) &&
|
|
219
|
+
targetVal && typeof targetVal === 'object' && !Array.isArray(targetVal)) {
|
|
220
|
+
(result as Record<string, unknown>)[key] = this.deepMerge(targetVal, sourceVal);
|
|
221
|
+
} else {
|
|
222
|
+
(result as Record<string, unknown>)[key] = sourceVal;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return result;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
exportConfig(): string {
|
|
230
|
+
return JSON.stringify(this.config, null, 2);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// Telemetry & Observability
|
|
2
|
+
// Structured logging, metrics, and tracing
|
|
3
|
+
|
|
4
|
+
import { randomUUID } from 'crypto';
|
|
5
|
+
|
|
6
|
+
export interface TelemetryEvent {
|
|
7
|
+
name: string;
|
|
8
|
+
attributes: Record<string, string | number | boolean>;
|
|
9
|
+
timestamp: string;
|
|
10
|
+
traceId?: string;
|
|
11
|
+
spanId?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface Metric {
|
|
15
|
+
name: string;
|
|
16
|
+
value: number;
|
|
17
|
+
unit: string;
|
|
18
|
+
tags: Record<string, string>;
|
|
19
|
+
timestamp: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class Telemetry {
|
|
23
|
+
private events: TelemetryEvent[] = [];
|
|
24
|
+
private metrics: Metric[] = [];
|
|
25
|
+
private traceId: string;
|
|
26
|
+
|
|
27
|
+
constructor() {
|
|
28
|
+
this.traceId = randomUUID();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Span management
|
|
32
|
+
startSpan(name: string): Span {
|
|
33
|
+
const spanId = randomUUID();
|
|
34
|
+
return new Span(name, spanId, this.traceId, this);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Event recording
|
|
38
|
+
recordEvent(name: string, attributes: Record<string, string | number | boolean> = {}): void {
|
|
39
|
+
this.events.push({
|
|
40
|
+
name,
|
|
41
|
+
attributes,
|
|
42
|
+
timestamp: new Date().toISOString(),
|
|
43
|
+
traceId: this.traceId,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Metric recording
|
|
48
|
+
recordMetric(name: string, value: number, unit: string, tags: Record<string, string> = {}): void {
|
|
49
|
+
this.metrics.push({
|
|
50
|
+
name,
|
|
51
|
+
value,
|
|
52
|
+
unit,
|
|
53
|
+
tags,
|
|
54
|
+
timestamp: new Date().toISOString(),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Counter increment
|
|
59
|
+
incrementCounter(name: string, tags: Record<string, string> = {}): void {
|
|
60
|
+
const existing = this.metrics.find(m => m.name === name && JSON.stringify(m.tags) === JSON.stringify(tags));
|
|
61
|
+
if (existing) {
|
|
62
|
+
existing.value++;
|
|
63
|
+
} else {
|
|
64
|
+
this.recordMetric(name, 1, 'count', tags);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Get all data
|
|
69
|
+
getEvents(): TelemetryEvent[] {
|
|
70
|
+
return [...this.events];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
getMetrics(): Metric[] {
|
|
74
|
+
return [...this.metrics];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Export for storage
|
|
78
|
+
export(): { events: TelemetryEvent[]; metrics: Metric[] } {
|
|
79
|
+
return {
|
|
80
|
+
events: [...this.events],
|
|
81
|
+
metrics: [...this.metrics],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Summary
|
|
86
|
+
getSummary(): {
|
|
87
|
+
totalEvents: number;
|
|
88
|
+
totalMetrics: number;
|
|
89
|
+
phaseMetrics: Record<string, number>;
|
|
90
|
+
} {
|
|
91
|
+
const phaseMetrics: Record<string, number> = {};
|
|
92
|
+
|
|
93
|
+
for (const metric of this.metrics) {
|
|
94
|
+
if (metric.name === 'phase_duration') {
|
|
95
|
+
const phase = metric.tags.phase || 'unknown';
|
|
96
|
+
phaseMetrics[phase] = (phaseMetrics[phase] || 0) + metric.value;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
totalEvents: this.events.length,
|
|
102
|
+
totalMetrics: this.metrics.length,
|
|
103
|
+
phaseMetrics,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export class Span {
|
|
109
|
+
private name: string;
|
|
110
|
+
private spanId: string;
|
|
111
|
+
private traceId: string;
|
|
112
|
+
private telemetry: Telemetry;
|
|
113
|
+
private startTime: number;
|
|
114
|
+
private attributes: Record<string, string | number | boolean> = {};
|
|
115
|
+
private status: 'OK' | 'ERROR' = 'OK';
|
|
116
|
+
private error?: Error;
|
|
117
|
+
|
|
118
|
+
constructor(name: string, spanId: string, traceId: string, telemetry: Telemetry) {
|
|
119
|
+
this.name = name;
|
|
120
|
+
this.spanId = spanId;
|
|
121
|
+
this.traceId = traceId;
|
|
122
|
+
this.telemetry = telemetry;
|
|
123
|
+
this.startTime = Date.now();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
setAttribute(key: string, value: string | number | boolean): void {
|
|
127
|
+
this.attributes[key] = value;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
setStatus(status: 'OK' | 'ERROR', error?: Error): void {
|
|
131
|
+
this.status = status;
|
|
132
|
+
this.error = error;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
recordException(error: Error): void {
|
|
136
|
+
this.error = error;
|
|
137
|
+
this.status = 'ERROR';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
end(): void {
|
|
141
|
+
const duration = Date.now() - this.startTime;
|
|
142
|
+
this.attributes['duration_ms'] = duration;
|
|
143
|
+
this.attributes['status'] = this.status;
|
|
144
|
+
|
|
145
|
+
if (this.error) {
|
|
146
|
+
this.attributes['error.message'] = this.error.message;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
this.telemetry.recordEvent(`span.${this.name}`, this.attributes);
|
|
150
|
+
this.telemetry.recordMetric(`${this.name}.duration`, duration, 'ms', {
|
|
151
|
+
status: this.status,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// Output Validator
|
|
2
|
+
// Validates subagent outputs before committing to state
|
|
3
|
+
|
|
4
|
+
export interface ValidationResult {
|
|
5
|
+
valid: boolean;
|
|
6
|
+
score: number;
|
|
7
|
+
issues: ValidationIssue[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ValidationIssue {
|
|
11
|
+
type: 'error' | 'warning' | 'info';
|
|
12
|
+
message: string;
|
|
13
|
+
field?: string;
|
|
14
|
+
severity: 'critical' | 'high' | 'medium' | 'low';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ValidationRule {
|
|
18
|
+
name: string;
|
|
19
|
+
description: string;
|
|
20
|
+
validate: (input: unknown) => ValidationIssue[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class OutputValidator {
|
|
24
|
+
private rules: ValidationRule[] = [];
|
|
25
|
+
private maxInputLength = 100000; // 100KB limit
|
|
26
|
+
|
|
27
|
+
constructor() {
|
|
28
|
+
this.registerBuiltinRules();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private registerBuiltinRules(): void {
|
|
32
|
+
this.addRule({
|
|
33
|
+
name: 'required-fields',
|
|
34
|
+
description: 'Checks for required fields in output',
|
|
35
|
+
validate: (input: unknown) => {
|
|
36
|
+
const issues: ValidationIssue[] = [];
|
|
37
|
+
if (!input || typeof input !== 'object') {
|
|
38
|
+
issues.push({
|
|
39
|
+
type: 'error',
|
|
40
|
+
message: 'Output must be a non-null object',
|
|
41
|
+
severity: 'critical',
|
|
42
|
+
});
|
|
43
|
+
return issues;
|
|
44
|
+
}
|
|
45
|
+
const obj = input as Record<string, unknown>;
|
|
46
|
+
if (!obj.title && !obj.name) {
|
|
47
|
+
issues.push({
|
|
48
|
+
type: 'warning',
|
|
49
|
+
message: 'Output missing title or name field',
|
|
50
|
+
field: 'title|name',
|
|
51
|
+
severity: 'medium',
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return issues;
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
this.addRule({
|
|
59
|
+
name: 'no-secrets',
|
|
60
|
+
description: 'Checks for accidental secret exposure',
|
|
61
|
+
validate: (input: unknown) => {
|
|
62
|
+
const issues: ValidationIssue[] = [];
|
|
63
|
+
try {
|
|
64
|
+
let text = JSON.stringify(input);
|
|
65
|
+
|
|
66
|
+
// Limit input length to prevent ReDoS
|
|
67
|
+
if (text.length > this.maxInputLength) {
|
|
68
|
+
text = text.substring(0, this.maxInputLength);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const secretPatterns = [
|
|
72
|
+
{ pattern: /API_KEY\s*[:=]\s*['"][^'"]+['"]/i, name: 'API_KEY' },
|
|
73
|
+
{ pattern: /PASSWORD\s*[:=]\s*['"][^'"]+['"]/i, name: 'PASSWORD' },
|
|
74
|
+
{ pattern: /SECRET\s*[:=]\s*['"][^'"]+['"]/i, name: 'SECRET' },
|
|
75
|
+
{ pattern: /PRIVATE_KEY\s*[:=]/i, name: 'PRIVATE_KEY' },
|
|
76
|
+
{ pattern: /-----BEGIN.*PRIVATE KEY-----/, name: 'RSA_PRIVATE_KEY' },
|
|
77
|
+
{ pattern: /AKIA[0-9A-Z]{16}/, name: 'AWS_ACCESS_KEY' },
|
|
78
|
+
{ pattern: /mysql:\/\/[^:]+:[^@]+@/, name: 'MYSQL_CONNECTION' },
|
|
79
|
+
{ pattern: /postgres:\/\/[^:]+:[^@]+@/, name: 'POSTGRES_CONNECTION' },
|
|
80
|
+
{ pattern: /mongodb:\/\/[^:]+:[^@]+@/, name: 'MONGODB_CONNECTION' },
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
// Use simple string matching instead of complex regex for JWT
|
|
84
|
+
if (text.includes('eyJ') && text.includes('.') && text.split('eyJ').length > 1) {
|
|
85
|
+
// Basic JWT detection without complex regex
|
|
86
|
+
const jwtParts = text.match(/eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]*/g);
|
|
87
|
+
if (jwtParts && jwtParts.length > 0) {
|
|
88
|
+
issues.push({
|
|
89
|
+
type: 'error',
|
|
90
|
+
message: 'Potential JWT token detected',
|
|
91
|
+
severity: 'critical',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
for (const { pattern, name } of secretPatterns) {
|
|
97
|
+
if (pattern.test(text)) {
|
|
98
|
+
issues.push({
|
|
99
|
+
type: 'error',
|
|
100
|
+
message: `Potential secret detected: ${name}`,
|
|
101
|
+
severity: 'critical',
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
// JSON.stringify failed, skip check
|
|
107
|
+
}
|
|
108
|
+
return issues;
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
this.addRule({
|
|
113
|
+
name: 'confidence-check',
|
|
114
|
+
description: 'Validates confidence level if present',
|
|
115
|
+
validate: (input: unknown) => {
|
|
116
|
+
const issues: ValidationIssue[] = [];
|
|
117
|
+
if (!input || typeof input !== 'object') return issues;
|
|
118
|
+
const obj = input as Record<string, unknown>;
|
|
119
|
+
|
|
120
|
+
if ('confidence' in obj) {
|
|
121
|
+
const confidence = obj.confidence as number;
|
|
122
|
+
if (typeof confidence !== 'number' || confidence < 0 || confidence > 1) {
|
|
123
|
+
issues.push({
|
|
124
|
+
type: 'error',
|
|
125
|
+
message: 'Confidence must be a number between 0 and 1',
|
|
126
|
+
field: 'confidence',
|
|
127
|
+
severity: 'high',
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return issues;
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
addRule(rule: ValidationRule): void {
|
|
137
|
+
this.rules.push(rule);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
validate(input: unknown): ValidationResult {
|
|
141
|
+
const allIssues: ValidationIssue[] = [];
|
|
142
|
+
|
|
143
|
+
for (const rule of this.rules) {
|
|
144
|
+
try {
|
|
145
|
+
const issues = rule.validate(input);
|
|
146
|
+
allIssues.push(...issues);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
allIssues.push({
|
|
149
|
+
type: 'error',
|
|
150
|
+
message: `Validation rule ${rule.name} threw error: ${(error as Error).message}`,
|
|
151
|
+
severity: 'high',
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const criticalCount = allIssues.filter(i => i.severity === 'critical').length;
|
|
157
|
+
const highCount = allIssues.filter(i => i.severity === 'high').length;
|
|
158
|
+
const mediumCount = allIssues.filter(i => i.severity === 'medium').length;
|
|
159
|
+
const lowCount = allIssues.filter(i => i.severity === 'low').length;
|
|
160
|
+
const score = Math.max(0, 100 - (criticalCount * 30) - (highCount * 15) - (mediumCount * 5) - (lowCount * 2));
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
valid: criticalCount === 0,
|
|
164
|
+
score,
|
|
165
|
+
issues: allIssues,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# 00-init — Project Initialization
|
|
2
|
+
|
|
3
|
+
## Entry Criteria
|
|
4
|
+
- New project or first session
|
|
5
|
+
- User invokes `vibe init`
|
|
6
|
+
|
|
7
|
+
## Steps
|
|
8
|
+
|
|
9
|
+
### 1. Detect Project
|
|
10
|
+
- Scan root directory for package.json, Cargo.toml, requirements.txt, pubspec.yaml
|
|
11
|
+
- Auto-detect technology stack
|
|
12
|
+
- Output: Project type and stack
|
|
13
|
+
|
|
14
|
+
### 2. Create Configuration
|
|
15
|
+
- Generate `.vibe/config.json` with detected settings
|
|
16
|
+
- Create `.vibe/state/events.jsonl`
|
|
17
|
+
- Initialize knowledge base
|
|
18
|
+
|
|
19
|
+
### 3. Initialize Workspace
|
|
20
|
+
- Create workspace directories (plans, reports, archive, incidents)
|
|
21
|
+
- Create initial CONTEXT.md template
|
|
22
|
+
|
|
23
|
+
### 4. Update State
|
|
24
|
+
- Append STARTED event for init phase
|
|
25
|
+
- Append COMPLETED event
|
|
26
|
+
|
|
27
|
+
## Exit Criteria
|
|
28
|
+
- [ ] Config file created
|
|
29
|
+
- [ ] State initialized
|
|
30
|
+
- [ ] Knowledge base ready
|
|
31
|
+
- [ ] Workspace directories created
|
|
32
|
+
|
|
33
|
+
## Duration
|
|
34
|
+
Expected: 1-2 minutes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# 01-clarify — Requirements Clarification
|
|
2
|
+
|
|
3
|
+
## Entry Criteria
|
|
4
|
+
- 00-init completed
|
|
5
|
+
- Task defined by user
|
|
6
|
+
|
|
7
|
+
## Steps
|
|
8
|
+
|
|
9
|
+
### 1. Understand Task
|
|
10
|
+
- Read user's request
|
|
11
|
+
- Identify task type (feature, bug, refactor, etc.)
|
|
12
|
+
- Ask clarifying questions if needed
|
|
13
|
+
|
|
14
|
+
### 2. Critical Assessment
|
|
15
|
+
- Evaluate idea from multiple perspectives:
|
|
16
|
+
- Technical feasibility
|
|
17
|
+
- Business value
|
|
18
|
+
- Risk assessment
|
|
19
|
+
- Scale analysis (will this work at 1M users?)
|
|
20
|
+
- Present pros, cons, and alternatives
|
|
21
|
+
|
|
22
|
+
### 3. Define Scope
|
|
23
|
+
- In scope: What will be done
|
|
24
|
+
- Out of scope: What won't be done
|
|
25
|
+
- Decided later: What needs more research
|
|
26
|
+
|
|
27
|
+
### 4. Resolve Ambiguity
|
|
28
|
+
- Address all open questions
|
|
29
|
+
- Get user confirmation on scope
|
|
30
|
+
- Document decisions
|
|
31
|
+
|
|
32
|
+
### 5. Self-Reflection
|
|
33
|
+
- Confidence level: [1-10]
|
|
34
|
+
- What went well
|
|
35
|
+
- What could be improved
|
|
36
|
+
- Assumptions made
|
|
37
|
+
|
|
38
|
+
## Exit Criteria
|
|
39
|
+
- [ ] Task clearly defined
|
|
40
|
+
- [ ] Scope documented
|
|
41
|
+
- [ ] No critical open questions
|
|
42
|
+
- [ ] User approved scope
|
|
43
|
+
|
|
44
|
+
## Duration
|
|
45
|
+
Expected: 5-15 minutes
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# 02-brainstorm — Research & Alternatives
|
|
2
|
+
|
|
3
|
+
## Entry Criteria
|
|
4
|
+
- 01-clarify completed
|
|
5
|
+
- Technical ambiguity exists
|
|
6
|
+
|
|
7
|
+
## Steps
|
|
8
|
+
|
|
9
|
+
### 1. Research
|
|
10
|
+
- Use codebase-memory to find existing patterns
|
|
11
|
+
- Use context7 for library documentation
|
|
12
|
+
- Use websearch for best practices
|
|
13
|
+
- Check knowledge base for past decisions
|
|
14
|
+
|
|
15
|
+
### 2. Generate Alternatives
|
|
16
|
+
- List 2-3 possible approaches
|
|
17
|
+
- For each: pros, cons, effort estimate
|
|
18
|
+
- Consider: performance, maintainability, team skills
|
|
19
|
+
|
|
20
|
+
### 3. Evaluate
|
|
21
|
+
- Score each approach (0-10)
|
|
22
|
+
- Consider: time to implement, complexity, risk
|
|
23
|
+
- Select best approach with justification
|
|
24
|
+
|
|
25
|
+
### 4. Create Scope Document
|
|
26
|
+
- Architecture overview
|
|
27
|
+
- Component diagram (if needed)
|
|
28
|
+
- Technology choices
|
|
29
|
+
|
|
30
|
+
### 5. Self-Reflection
|
|
31
|
+
- Confidence level: [1-10]
|
|
32
|
+
- Research completeness
|
|
33
|
+
- Alternative quality
|
|
34
|
+
|
|
35
|
+
## Exit Criteria
|
|
36
|
+
- [ ] Alternatives evaluated
|
|
37
|
+
- [ ] Approach selected
|
|
38
|
+
- [ ] Scope documented
|
|
39
|
+
- [ ] User approved approach
|
|
40
|
+
|
|
41
|
+
## Duration
|
|
42
|
+
Expected: 15-30 minutes
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# 03-plan — Planning
|
|
2
|
+
|
|
3
|
+
## Entry Criteria
|
|
4
|
+
- 02-brainstorm completed (or skipped)
|
|
5
|
+
- Scope clear
|
|
6
|
+
|
|
7
|
+
## Steps
|
|
8
|
+
|
|
9
|
+
### 1. Break Down Tasks
|
|
10
|
+
- Create task list with IDs (T001, T002, etc.)
|
|
11
|
+
- Each task: description, effort estimate, dependencies
|
|
12
|
+
- Prioritize tasks (critical path first)
|
|
13
|
+
|
|
14
|
+
### 2. Define Test Strategy
|
|
15
|
+
- Unit tests: What to test
|
|
16
|
+
- Integration tests: Key flows
|
|
17
|
+
- E2E tests: Critical paths
|
|
18
|
+
|
|
19
|
+
### 3. Create Plan Document
|
|
20
|
+
- Save to workspace/plans/plan.md
|
|
21
|
+
- Include: task list, timeline, dependencies
|
|
22
|
+
- Define Definition of Done
|
|
23
|
+
|
|
24
|
+
### 4. Self-Reflection
|
|
25
|
+
- Confidence level: [1-10]
|
|
26
|
+
- Plan completeness
|
|
27
|
+
- Risk assessment
|
|
28
|
+
|
|
29
|
+
## Exit Criteria
|
|
30
|
+
- [ ] Task list created
|
|
31
|
+
- [ ] Test strategy defined
|
|
32
|
+
- [ ] Plan documented
|
|
33
|
+
- [ ] User approved plan
|
|
34
|
+
|
|
35
|
+
## Duration
|
|
36
|
+
Expected: 10-20 minutes
|