@jigyasudham/veto 0.8.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +217 -54
  2. package/dist/adapters/index.js +4 -3
  3. package/dist/agents/executor.js +36 -3
  4. package/dist/cli.js +246 -7
  5. package/dist/context/reader.js +113 -0
  6. package/dist/council/index.js +3 -1
  7. package/dist/memory/local.js +18 -1
  8. package/dist/memory/schema.js +12 -10
  9. package/dist/plugins/loader.js +49 -0
  10. package/dist/router/index.js +2 -2
  11. package/dist/router/learning-updater.js +45 -1
  12. package/dist/server.js +507 -21
  13. package/dist/watcher/index.js +77 -0
  14. package/dist/workflow/pipeline.js +64 -0
  15. package/package.json +12 -3
  16. package/.claude/settings.local.json +0 -9
  17. package/src/adapters/claude.ts +0 -70
  18. package/src/adapters/codex.ts +0 -71
  19. package/src/adapters/gemini.ts +0 -71
  20. package/src/adapters/index.ts +0 -217
  21. package/src/agents/development/api.ts +0 -120
  22. package/src/agents/development/backend.ts +0 -85
  23. package/src/agents/development/coder.ts +0 -213
  24. package/src/agents/development/database.ts +0 -83
  25. package/src/agents/development/debugger.ts +0 -238
  26. package/src/agents/development/devops.ts +0 -86
  27. package/src/agents/development/frontend.ts +0 -85
  28. package/src/agents/development/migration.ts +0 -144
  29. package/src/agents/development/performance.ts +0 -144
  30. package/src/agents/development/refactor.ts +0 -86
  31. package/src/agents/development/reviewer.ts +0 -268
  32. package/src/agents/development/tester.ts +0 -151
  33. package/src/agents/executor.ts +0 -158
  34. package/src/agents/memory/context-manager.ts +0 -171
  35. package/src/agents/memory/decision-logger.ts +0 -160
  36. package/src/agents/memory/knowledge-base.ts +0 -124
  37. package/src/agents/memory/pattern-learner.ts +0 -143
  38. package/src/agents/memory/project-mapper.ts +0 -118
  39. package/src/agents/quality/accessibility.ts +0 -99
  40. package/src/agents/quality/code-quality.ts +0 -115
  41. package/src/agents/quality/compatibility.ts +0 -58
  42. package/src/agents/quality/documentation.ts +0 -105
  43. package/src/agents/quality/error-handling.ts +0 -96
  44. package/src/agents/research/competitor-analyzer.ts +0 -45
  45. package/src/agents/research/cost-analyzer.ts +0 -54
  46. package/src/agents/research/estimator.ts +0 -60
  47. package/src/agents/research/ethics-bias.ts +0 -113
  48. package/src/agents/research/researcher.ts +0 -114
  49. package/src/agents/research/risk-assessor.ts +0 -63
  50. package/src/agents/research/tech-advisor.ts +0 -55
  51. package/src/agents/security/auth.ts +0 -287
  52. package/src/agents/security/dependency-audit.ts +0 -337
  53. package/src/agents/security/penetration.ts +0 -262
  54. package/src/agents/security/privacy.ts +0 -285
  55. package/src/agents/security/scanner.ts +0 -322
  56. package/src/agents/security/secrets.ts +0 -249
  57. package/src/agents/types.ts +0 -66
  58. package/src/agents/workflow/automation.ts +0 -59
  59. package/src/agents/workflow/file-manager.ts +0 -52
  60. package/src/agents/workflow/git-agent.ts +0 -55
  61. package/src/agents/workflow/reporter.ts +0 -51
  62. package/src/agents/workflow/search-agent.ts +0 -40
  63. package/src/agents/workflow/task-coordinator.ts +0 -41
  64. package/src/agents/workflow/task-planner.ts +0 -47
  65. package/src/cli.ts +0 -204
  66. package/src/council/decision-engine.ts +0 -171
  67. package/src/council/devil-advocate.ts +0 -116
  68. package/src/council/index.ts +0 -44
  69. package/src/council/lead-developer.ts +0 -118
  70. package/src/council/legal-compliance.ts +0 -152
  71. package/src/council/product-manager.ts +0 -102
  72. package/src/council/security.ts +0 -172
  73. package/src/council/system-architect.ts +0 -132
  74. package/src/council/types.ts +0 -33
  75. package/src/council/ux-designer.ts +0 -121
  76. package/src/memory/local.ts +0 -305
  77. package/src/memory/schema.ts +0 -174
  78. package/src/memory/sync.ts +0 -274
  79. package/src/router/complexity-scorer.ts +0 -96
  80. package/src/router/context-compressor.ts +0 -74
  81. package/src/router/index.ts +0 -60
  82. package/src/router/learning-updater.ts +0 -271
  83. package/src/router/model-selector.ts +0 -83
  84. package/src/router/rate-monitor.ts +0 -103
  85. package/src/server.ts +0 -1038
  86. package/src/skills/development/skill-api-design.ts +0 -329
  87. package/src/skills/development/skill-auth.ts +0 -271
  88. package/src/skills/development/skill-ci-cd.ts +0 -0
  89. package/src/skills/development/skill-crud.ts +0 -209
  90. package/src/skills/development/skill-db-schema.ts +0 -0
  91. package/src/skills/development/skill-docker.ts +0 -0
  92. package/src/skills/development/skill-env-setup.ts +0 -0
  93. package/src/skills/development/skill-scaffold.ts +0 -323
  94. package/src/skills/intelligence/skill-complexity-score.ts +0 -69
  95. package/src/skills/intelligence/skill-cost-track.ts +0 -39
  96. package/src/skills/intelligence/skill-learning-loop.ts +0 -69
  97. package/src/skills/intelligence/skill-pattern-detect.ts +0 -38
  98. package/src/skills/intelligence/skill-rate-watch.ts +0 -61
  99. package/src/skills/memory/skill-context-compress.ts +0 -98
  100. package/src/skills/memory/skill-cross-sync.ts +0 -104
  101. package/src/skills/memory/skill-decision-log.ts +0 -119
  102. package/src/skills/memory/skill-session-restore.ts +0 -59
  103. package/src/skills/memory/skill-session-save.ts +0 -94
  104. package/src/skills/quality/skill-accessibility.ts +0 -0
  105. package/src/skills/quality/skill-code-review.ts +0 -84
  106. package/src/skills/quality/skill-docs-gen.ts +0 -0
  107. package/src/skills/quality/skill-perf-audit.ts +0 -0
  108. package/src/skills/quality/skill-security-scan.ts +0 -91
  109. package/src/skills/quality/skill-test-suite.ts +0 -290
  110. package/src/skills/workflow/skill-deploy.ts +0 -0
  111. package/src/skills/workflow/skill-git-workflow.ts +0 -0
  112. package/src/skills/workflow/skill-rollback.ts +0 -0
  113. package/src/skills/workflow/skill-task-breakdown.ts +0 -0
  114. package/tsconfig.json +0 -20
package/dist/cli.js CHANGED
@@ -2,10 +2,10 @@
2
2
  // Veto CLI — entry point for `npx veto init`
3
3
  // Suppress Node experimental warnings (node:sqlite) for clean UX
4
4
  process.removeAllListeners('warning');
5
- import { mkdirSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
6
- import { join, dirname } from 'node:path';
5
+ import { mkdirSync, existsSync, readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs';
6
+ import { join, dirname, extname, resolve } from 'node:path';
7
7
  import { homedir } from 'node:os';
8
- const VERSION = '0.8.2';
8
+ const VERSION = '1.0.0';
9
9
  const VETO_DIR = join(homedir(), '.veto');
10
10
  const HOME = homedir();
11
11
  const c = {
@@ -25,7 +25,7 @@ function printBanner() {
25
25
  console.log(c.bold(c.cyan(' ╚████╔╝ ███████╗ ██║ ╚██████╔╝')));
26
26
  console.log(c.bold(c.cyan(' ╚═══╝ ╚══════╝ ╚═╝ ╚═════╝')));
27
27
  console.log('');
28
- console.log(c.dim(` 50 agents. 28 skills. 3 AIs. Self-learning. Zero extra cost.`));
28
+ console.log(c.dim(` 50 agents. 33 tools. 3 AIs. Self-learning. Zero extra cost.`));
29
29
  console.log(c.dim(` v${VERSION}`));
30
30
  console.log('');
31
31
  }
@@ -127,7 +127,20 @@ async function initCommand() {
127
127
  console.error(c.red(` Error initializing database: ${msg}`));
128
128
  process.exit(1);
129
129
  }
130
- // 3. Auto-configure every AI CLI / IDE found on this machine
130
+ // 3. Auto-scan current project and store project map
131
+ const cwd = resolve(process.cwd());
132
+ const { getDb: _getDb, updateProjectMap } = await import('./memory/local.js');
133
+ try {
134
+ process.stdout.write(' · Scanning project directory...');
135
+ const { structure, key_modules, tech_stack } = scanProjectDir(cwd);
136
+ updateProjectMap({ project_dir: cwd, structure: JSON.stringify(structure), key_modules, tech_stack });
137
+ const stackStr = tech_stack.length ? ` (${tech_stack.slice(0, 4).join(', ')})` : '';
138
+ console.log(c.green(' ✓') + ` Project map saved${stackStr}`);
139
+ }
140
+ catch {
141
+ console.log(c.dim(' skipped'));
142
+ }
143
+ // 4. Auto-configure every AI CLI / IDE found on this machine
131
144
  console.log('');
132
145
  console.log(' Configuring all AI tools found on this machine...');
133
146
  console.log('');
@@ -168,6 +181,203 @@ async function initCommand() {
168
181
  console.log('');
169
182
  }
170
183
  }
184
+ // ─── Project Map Scanner ────────────────────────────────────────────────────────
185
+ function scanProjectDir(dir) {
186
+ const structure = {};
187
+ const key_modules = [];
188
+ const tech_stack = [];
189
+ // Read package.json for stack detection
190
+ const pkgPath = join(dir, 'package.json');
191
+ if (existsSync(pkgPath)) {
192
+ try {
193
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
194
+ structure['name'] = pkg.name;
195
+ structure['version'] = pkg.version;
196
+ const allDeps = Object.keys({ ...pkg.dependencies, ...pkg.devDependencies });
197
+ structure['dep_count'] = allDeps.length;
198
+ const stackMap = [
199
+ [['next'], 'Next.js'], [['react'], 'React'], [['vue'], 'Vue'],
200
+ [['express'], 'Express'], [['fastify'], 'Fastify'], [['hono'], 'Hono'],
201
+ [['prisma'], 'Prisma'], [['drizzle-orm'], 'Drizzle'], [['typeorm'], 'TypeORM'],
202
+ [['@modelcontextprotocol/sdk'], 'MCP'], [['typescript'], 'TypeScript'],
203
+ [['jest'], 'Jest'], [['vitest'], 'Vitest'], [['tailwindcss'], 'Tailwind'],
204
+ [['zod'], 'Zod'], [['graphql'], 'GraphQL'],
205
+ ];
206
+ for (const [keywords, label] of stackMap) {
207
+ if (keywords.some(k => allDeps.some(d => d.toLowerCase().includes(k)))) {
208
+ tech_stack.push(label);
209
+ }
210
+ }
211
+ }
212
+ catch { /* malformed */ }
213
+ }
214
+ // Detect key config files
215
+ const CONFIG_FILES = ['tsconfig.json', 'vite.config.ts', 'vite.config.js', 'next.config.ts',
216
+ 'next.config.js', 'tailwind.config.ts', 'drizzle.config.ts', 'prisma/schema.prisma',
217
+ 'docker-compose.yml', '.env.example'];
218
+ const foundConfigs = CONFIG_FILES.filter(f => existsSync(join(dir, f)));
219
+ if (foundConfigs.length)
220
+ structure['config_files'] = foundConfigs;
221
+ // Scan src/ directory
222
+ const srcDir = join(dir, 'src');
223
+ if (existsSync(srcDir)) {
224
+ let tsCount = 0;
225
+ let testCount = 0;
226
+ const topDirs = [];
227
+ for (const entry of readdirSync(srcDir)) {
228
+ const full = join(srcDir, entry);
229
+ try {
230
+ if (statSync(full).isDirectory()) {
231
+ topDirs.push(entry);
232
+ key_modules.push(`src/${entry}`);
233
+ }
234
+ else {
235
+ const ext = extname(entry);
236
+ if (['.ts', '.tsx', '.js', '.jsx'].includes(ext))
237
+ tsCount++;
238
+ if (entry.includes('.test.') || entry.includes('.spec.'))
239
+ testCount++;
240
+ }
241
+ }
242
+ catch { /* skip */ }
243
+ }
244
+ structure['src_dirs'] = topDirs;
245
+ structure['ts_files_in_src'] = tsCount;
246
+ if (testCount > 0)
247
+ structure['test_files'] = testCount;
248
+ }
249
+ // Entry points
250
+ const entryPoints = ['src/index.ts', 'src/main.ts', 'src/app.ts', 'src/server.ts', 'src/cli.ts', 'index.ts'];
251
+ const found = entryPoints.filter(f => existsSync(join(dir, f)));
252
+ if (found.length)
253
+ structure['entry_points'] = found;
254
+ return { structure, key_modules, tech_stack };
255
+ }
256
+ // ─── CLI Subcommands ────────────────────────────────────────────────────────────
257
+ async function statusCommand() {
258
+ const { getDbPath } = await import('./memory/local.js');
259
+ const { getDb } = await import('./memory/local.js');
260
+ const db = getDb();
261
+ const sessionCount = db.prepare('SELECT COUNT(*) as c FROM sessions').get().c;
262
+ const memoryCount = db.prepare('SELECT COUNT(*) as c FROM knowledge_base').get().c;
263
+ const patternCount = db.prepare('SELECT COUNT(*) as c FROM patterns').get().c;
264
+ const outcomeCount = db.prepare('SELECT COUNT(*) as c FROM learning_data').get().c;
265
+ console.log('');
266
+ console.log(c.bold(' Veto Status'));
267
+ console.log(c.dim(' ─────────────────────────────'));
268
+ console.log(` Version ${c.cyan(VERSION)}`);
269
+ console.log(` DB ${c.dim(getDbPath())}`);
270
+ console.log(` Sessions ${c.cyan(String(sessionCount))}`);
271
+ console.log(` Memory ${c.cyan(String(memoryCount))} knowledge entries`);
272
+ console.log(` Patterns ${c.cyan(String(patternCount))}`);
273
+ console.log(` Outcomes ${c.cyan(String(outcomeCount))} recorded`);
274
+ console.log('');
275
+ }
276
+ async function sessionsCommand() {
277
+ const { listSessions } = await import('./memory/local.js');
278
+ const sessions = listSessions(20);
279
+ console.log('');
280
+ console.log(c.bold(' Saved Sessions') + c.dim(` (${sessions.length})`));
281
+ console.log(c.dim(' ─────────────────────────────────────────────────────────────'));
282
+ if (sessions.length === 0) {
283
+ console.log(c.dim(' No sessions saved yet. Use veto_session_save inside an AI session.'));
284
+ }
285
+ else {
286
+ for (const s of sessions) {
287
+ const date = new Date(s.started_at).toLocaleString();
288
+ console.log(` ${c.cyan(s.id.slice(0, 8))} ${c.dim(date)} ${c.bold(s.platform ?? 'claude')} ${s.summary?.slice(0, 60) ?? ''}`);
289
+ }
290
+ }
291
+ console.log('');
292
+ }
293
+ async function memoryCommand() {
294
+ const args = process.argv.slice(3);
295
+ const query = args.join(' ') || undefined;
296
+ const { searchKnowledge } = await import('./memory/local.js');
297
+ const results = searchKnowledge({ query, limit: 20 });
298
+ console.log('');
299
+ console.log(c.bold(' Knowledge Base') + (query ? c.dim(` — "${query}"`) : '') + c.dim(` (${results.length} results)`));
300
+ console.log(c.dim(' ─────────────────────────────────────────────────────────────'));
301
+ if (results.length === 0) {
302
+ console.log(c.dim(' No entries found.'));
303
+ }
304
+ else {
305
+ for (const r of results) {
306
+ const tags = r.tags ? JSON.parse(r.tags).join(', ') : '';
307
+ console.log(` ${c.cyan(`[${r.type}]`)} ${c.bold(r.title)}`);
308
+ if (tags)
309
+ console.log(` ${c.dim('tags: ' + tags)}`);
310
+ console.log(` ${c.dim(r.content.slice(0, 100).replace(/\n/g, ' ') + (r.content.length > 100 ? '...' : ''))}`);
311
+ console.log('');
312
+ }
313
+ }
314
+ }
315
+ async function patternsCommand() {
316
+ const { getPatterns } = await import('./memory/local.js');
317
+ const prefix = process.argv[3];
318
+ const patterns = getPatterns(prefix, 30);
319
+ console.log('');
320
+ console.log(c.bold(' Learned Patterns') + c.dim(` (${patterns.length})`));
321
+ console.log(c.dim(' ─────────────────────────────────────────────────────────────'));
322
+ if (patterns.length === 0) {
323
+ console.log(c.dim(' No patterns yet. Record outcomes with veto_record_outcome to build up patterns.'));
324
+ }
325
+ else {
326
+ for (const p of patterns) {
327
+ const conf = Math.round(p.confidence * 100);
328
+ const confColor = conf >= 80 ? c.green : conf >= 60 ? c.yellow : c.dim;
329
+ console.log(` ${confColor(`${conf}%`)} ${c.cyan(p.pattern_key)} ${c.dim('→')} ${p.pattern_val} ${c.dim(`(seen ${p.seen_count}x)`)}`);
330
+ }
331
+ }
332
+ console.log('');
333
+ }
334
+ function helpCommand() {
335
+ console.log('');
336
+ console.log(c.bold(c.cyan(' veto')) + c.dim(` v${VERSION}`) + c.dim(' — 50 agents. 34 tools. 3 AIs. Self-learning. Zero extra cost.'));
337
+ console.log('');
338
+ console.log(c.bold(' CLI Commands'));
339
+ console.log(c.dim(' ─────────────────────────────────────────────────────'));
340
+ console.log(` ${c.cyan('veto init')} Configure all AI tools + scan project`);
341
+ console.log(` ${c.cyan('veto status')} Version, DB path, memory/session counts`);
342
+ console.log(` ${c.cyan('veto sessions')} List last 20 saved sessions`);
343
+ console.log(` ${c.cyan('veto memory')} ${c.dim('[query]')} Search knowledge base`);
344
+ console.log(` ${c.cyan('veto patterns')} ${c.dim('[prefix]')} List learned agent/routing patterns`);
345
+ console.log(` ${c.cyan('veto help')} Show this help`);
346
+ console.log('');
347
+ console.log(c.bold(' MCP Tools (34)'));
348
+ console.log(c.dim(' ─────────────────────────────────────────────────────'));
349
+ console.log(` ${c.dim('Session')} veto_status · veto_session_save · veto_session_restore · veto_sessions_list`);
350
+ console.log(` ${c.dim('Router')} veto_route_task · veto_rate_status`);
351
+ console.log(` ${c.dim('Council')} veto_council_debate`);
352
+ console.log(` ${c.dim('Agents')} veto_agent_plan · veto_execute_parallel · veto_explain`);
353
+ console.log(` ${c.dim('Review')} veto_code_review · veto_security_scan · veto_secrets_scan · veto_diff_review`);
354
+ console.log(` ${c.dim('Pipeline')} veto_workflow`);
355
+ console.log(` ${c.dim('Watch')} veto_watch · veto_watch_poll · veto_watch_stop`);
356
+ console.log(` ${c.dim('Memory')} veto_memory_store · veto_memory_search · veto_memory_delete`);
357
+ console.log(` veto_project_map_update · veto_project_map_get`);
358
+ console.log(` veto_pattern_store · veto_patterns_list`);
359
+ console.log(` veto_memory_export · veto_memory_import`);
360
+ console.log(` ${c.dim('Learning')} veto_record_outcome · veto_learning_stats · veto_learning_apply`);
361
+ console.log(` ${c.dim('Handoff')} veto_handoff · veto_continue · veto_platform_setup`);
362
+ console.log(` ${c.dim('Plugins')} veto_plugins`);
363
+ console.log('');
364
+ console.log(c.bold(' MCP Resources'));
365
+ console.log(c.dim(' ─────────────────────────────────────────────────────'));
366
+ console.log(` ${c.cyan('veto://sessions')} All saved sessions`);
367
+ console.log(` ${c.cyan('veto://project-map?dir=<path>')} Project structure map`);
368
+ console.log(` ${c.cyan('veto://memory?q=<query>')} Knowledge base search`);
369
+ console.log(` ${c.cyan('veto://patterns')} Learned patterns`);
370
+ console.log('');
371
+ console.log(c.bold(' MCP Prompts'));
372
+ console.log(c.dim(' ─────────────────────────────────────────────────────'));
373
+ console.log(` ${c.cyan('code-review')} · ${c.cyan('security-audit')} · ${c.cyan('deploy-checklist')} · ${c.cyan('explain-file')}`);
374
+ console.log('');
375
+ console.log(c.bold(' Docs'));
376
+ console.log(c.dim(' ─────────────────────────────────────────────────────'));
377
+ console.log(` ${c.dim('GitHub:')} https://github.com/jigyasudham/veto`);
378
+ console.log(` ${c.dim('npm:')} https://www.npmjs.com/package/@jigyasudham/veto`);
379
+ console.log('');
380
+ }
171
381
  // ─── Router ────────────────────────────────────────────────────────────────────
172
382
  const command = process.argv[2] ?? 'init';
173
383
  switch (command) {
@@ -177,9 +387,38 @@ switch (command) {
177
387
  process.exit(1);
178
388
  });
179
389
  break;
390
+ case 'status':
391
+ statusCommand().catch((err) => {
392
+ console.error(c.red(`Error: ${err.message}`));
393
+ process.exit(1);
394
+ });
395
+ break;
396
+ case 'sessions':
397
+ sessionsCommand().catch((err) => {
398
+ console.error(c.red(`Error: ${err.message}`));
399
+ process.exit(1);
400
+ });
401
+ break;
402
+ case 'memory':
403
+ memoryCommand().catch((err) => {
404
+ console.error(c.red(`Error: ${err.message}`));
405
+ process.exit(1);
406
+ });
407
+ break;
408
+ case 'patterns':
409
+ patternsCommand().catch((err) => {
410
+ console.error(c.red(`Error: ${err.message}`));
411
+ process.exit(1);
412
+ });
413
+ break;
414
+ case 'help':
415
+ case '--help':
416
+ case '-h':
417
+ helpCommand();
418
+ break;
180
419
  default:
181
- console.error(`Unknown command: ${command}`);
182
- console.log('Usage: veto init');
420
+ console.error(c.red(` Unknown command: ${command}`));
421
+ helpCommand();
183
422
  process.exit(1);
184
423
  }
185
424
  //# sourceMappingURL=cli.js.map
@@ -0,0 +1,113 @@
1
+ import { readFileSync, existsSync } from 'node:fs';
2
+ import { join, resolve } from 'node:path';
3
+ import { execSync } from 'node:child_process';
4
+ const STACK_INDICATORS = [
5
+ [['next', 'next.js'], 'Next.js'],
6
+ [['react', 'react-dom'], 'React'],
7
+ [['vue'], 'Vue'],
8
+ [['svelte'], 'Svelte'],
9
+ [['express'], 'Express'],
10
+ [['fastify'], 'Fastify'],
11
+ [['hono'], 'Hono'],
12
+ [['prisma'], 'Prisma'],
13
+ [['drizzle-orm'], 'Drizzle'],
14
+ [['typeorm'], 'TypeORM'],
15
+ [['mongoose'], 'Mongoose'],
16
+ [['@modelcontextprotocol/sdk'], 'MCP'],
17
+ [['typescript', 'ts-node', 'tsx'], 'TypeScript'],
18
+ [['jest', 'vitest'], 'Testing'],
19
+ [['tailwindcss'], 'Tailwind'],
20
+ [['zod'], 'Zod'],
21
+ [['trpc', '@trpc/server'], 'tRPC'],
22
+ [['graphql'], 'GraphQL'],
23
+ ];
24
+ function detectStack(deps) {
25
+ const names = Object.keys(deps).map(k => k.toLowerCase());
26
+ const stack = [];
27
+ for (const [keywords, label] of STACK_INDICATORS) {
28
+ if (keywords.some(k => names.some(n => n.includes(k)))) {
29
+ stack.push(label);
30
+ }
31
+ }
32
+ return stack;
33
+ }
34
+ function safeRead(filePath) {
35
+ try {
36
+ return readFileSync(filePath, 'utf8');
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ }
42
+ function safeExec(cmd, cwd) {
43
+ try {
44
+ return execSync(cmd, { cwd, timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).toString().trim();
45
+ }
46
+ catch {
47
+ return '';
48
+ }
49
+ }
50
+ export function readProjectContext(projectDir) {
51
+ const dir = resolve(projectDir);
52
+ if (!existsSync(dir)) {
53
+ return { summary: '', tech_stack: [], git_diff: '', key_files: [], error: `Directory not found: ${dir}` };
54
+ }
55
+ // package.json
56
+ let packageName;
57
+ let packageVersion;
58
+ let techStack = [];
59
+ const pkgRaw = safeRead(join(dir, 'package.json'));
60
+ if (pkgRaw) {
61
+ try {
62
+ const pkg = JSON.parse(pkgRaw);
63
+ packageName = pkg.name;
64
+ packageVersion = pkg.version;
65
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
66
+ techStack = detectStack(allDeps);
67
+ }
68
+ catch { /* malformed package.json */ }
69
+ }
70
+ // git diff — last 60 lines of unstaged + staged changes
71
+ const gitDiff = safeExec('git diff HEAD --stat --no-color', dir);
72
+ // git recent commits (last 5 one-liners for context)
73
+ const gitLog = safeExec('git log --oneline -5 --no-color', dir);
74
+ // key config files present
75
+ const CONFIG_FILES = [
76
+ 'tsconfig.json', 'vite.config.ts', 'vite.config.js',
77
+ 'next.config.ts', 'next.config.js', 'tailwind.config.ts',
78
+ 'drizzle.config.ts', 'prisma/schema.prisma', '.env.example',
79
+ ];
80
+ const keyFiles = CONFIG_FILES.filter(f => existsSync(join(dir, f)));
81
+ // build compact summary string
82
+ const lines = ['[CODEBASE CONTEXT]'];
83
+ if (packageName)
84
+ lines.push(`Project: ${packageName}${packageVersion ? ` v${packageVersion}` : ''}`);
85
+ if (techStack.length)
86
+ lines.push(`Stack: ${techStack.join(', ')}`);
87
+ if (keyFiles.length)
88
+ lines.push(`Config files: ${keyFiles.join(', ')}`);
89
+ if (gitDiff)
90
+ lines.push(`Recent changes:\n${gitDiff}`);
91
+ if (gitLog)
92
+ lines.push(`Recent commits:\n${gitLog}`);
93
+ return {
94
+ summary: lines.join('\n'),
95
+ package_name: packageName,
96
+ package_version: packageVersion,
97
+ tech_stack: techStack,
98
+ git_diff: gitDiff,
99
+ key_files: keyFiles,
100
+ };
101
+ }
102
+ export function buildContextString(projectDir, existingContext) {
103
+ if (!projectDir)
104
+ return existingContext ?? '';
105
+ const ctx = readProjectContext(projectDir);
106
+ if (ctx.error)
107
+ return existingContext ?? '';
108
+ const parts = [ctx.summary];
109
+ if (existingContext)
110
+ parts.push(existingContext);
111
+ return parts.join('\n\n');
112
+ }
113
+ //# sourceMappingURL=reader.js.map
@@ -7,8 +7,10 @@ import { analyze as devilAnalyze } from './devil-advocate.js';
7
7
  import { analyze as legalAnalyze } from './legal-compliance.js';
8
8
  import { analyze as securityAnalyze } from './security.js';
9
9
  import { decide, formatDebate } from './decision-engine.js';
10
+ import { buildContextString } from '../context/reader.js';
10
11
  export async function runDebate(input) {
11
- const fullText = input.context ? `${input.task}\n${input.context}` : input.task;
12
+ const enrichedContext = buildContextString(input.project_dir, input.context);
13
+ const fullText = enrichedContext ? `${input.task}\n\n${enrichedContext}` : input.task;
12
14
  // All 7 agents run in parallel — none depend on each other
13
15
  const [lead_dev, pm, architect, ux, devil, legal, security] = await Promise.all([
14
16
  Promise.resolve(leadDevAnalyze(fullText)),
@@ -19,8 +19,18 @@ export function getDb() {
19
19
  _db.exec(CREATE_TABLES);
20
20
  migrateCouncilOutcomes(_db);
21
21
  migrateCouncilColumns(_db);
22
+ migrateSessionColumns(_db);
22
23
  return _db;
23
24
  }
25
+ // Adds active_client and last_resumed_at columns if they don't exist
26
+ function migrateSessionColumns(db) {
27
+ const cols = db.prepare('PRAGMA table_info(sessions)').all();
28
+ const names = new Set(cols.map(c => c.name));
29
+ if (!names.has('active_client'))
30
+ db.exec('ALTER TABLE sessions ADD COLUMN active_client TEXT');
31
+ if (!names.has('last_resumed_at'))
32
+ db.exec('ALTER TABLE sessions ADD COLUMN last_resumed_at TEXT');
33
+ }
24
34
  // Adds legal and security columns if they don't exist (Phase 3 → Phase 3.1 migration)
25
35
  function migrateCouncilColumns(db) {
26
36
  const cols = db.prepare('PRAGMA table_info(council_outcomes)').all();
@@ -58,11 +68,18 @@ export function saveSession(input) {
58
68
  stmt.run(id, now, input.platform ?? 'claude', input.project_dir ?? null, input.summary ?? null, input.context ? JSON.stringify(input.context) : null, input.task_state ? JSON.stringify(input.task_state) : null, input.token_count ?? 0);
59
69
  return { session_id: id, saved_at: now };
60
70
  }
61
- export function restoreSession(session_id) {
71
+ export function restoreSession(session_id, active_client) {
62
72
  const db = getDb();
63
73
  const row = db.prepare('SELECT * FROM sessions WHERE id = ?').get(session_id);
64
74
  if (!row)
65
75
  return { found: false };
76
+ if (active_client) {
77
+ const now = new Date().toISOString();
78
+ db.prepare('UPDATE sessions SET active_client = ?, last_resumed_at = ? WHERE id = ?')
79
+ .run(active_client, now, session_id);
80
+ row.active_client = active_client;
81
+ row.last_resumed_at = now;
82
+ }
66
83
  return { found: true, session: row };
67
84
  }
68
85
  export function listSessions(limit = 10) {
@@ -2,16 +2,18 @@
2
2
  // All tables created on first run — zero setup required
3
3
  export const CREATE_TABLES = `
4
4
  CREATE TABLE IF NOT EXISTS sessions (
5
- id TEXT PRIMARY KEY,
6
- started_at TEXT NOT NULL,
7
- ended_at TEXT,
8
- platform TEXT NOT NULL DEFAULT 'claude',
9
- project_dir TEXT,
10
- summary TEXT,
11
- context TEXT,
12
- task_state TEXT,
13
- token_count INTEGER DEFAULT 0,
14
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
5
+ id TEXT PRIMARY KEY,
6
+ started_at TEXT NOT NULL,
7
+ ended_at TEXT,
8
+ platform TEXT NOT NULL DEFAULT 'claude',
9
+ active_client TEXT,
10
+ last_resumed_at TEXT,
11
+ project_dir TEXT,
12
+ summary TEXT,
13
+ context TEXT,
14
+ task_state TEXT,
15
+ token_count INTEGER DEFAULT 0,
16
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
15
17
  );
16
18
 
17
19
  CREATE TABLE IF NOT EXISTS decisions (
@@ -0,0 +1,49 @@
1
+ import { readdirSync, existsSync } from 'node:fs';
2
+ import { join, basename, extname } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ const PLUGIN_DIR = join(homedir(), '.veto', 'agents');
5
+ const registry = new Map();
6
+ let loaded = false;
7
+ function pluginName(file) {
8
+ return basename(file, extname(file));
9
+ }
10
+ export async function loadPlugins() {
11
+ if (loaded)
12
+ return Array.from(registry.keys());
13
+ loaded = true;
14
+ if (!existsSync(PLUGIN_DIR))
15
+ return [];
16
+ const files = readdirSync(PLUGIN_DIR).filter(f => /\.(js|mjs)$/.test(f));
17
+ const names = [];
18
+ for (const file of files) {
19
+ const name = pluginName(file);
20
+ try {
21
+ const mod = await import(join(PLUGIN_DIR, file));
22
+ if (mod && typeof mod === 'object' &&
23
+ 'plan' in mod && typeof mod.plan === 'function') {
24
+ registry.set(name, mod);
25
+ names.push(name);
26
+ }
27
+ else {
28
+ process.stderr.write(`[veto] Plugin "${file}" skipped — must export plan(task, context?)\n`);
29
+ }
30
+ }
31
+ catch (err) {
32
+ process.stderr.write(`[veto] Plugin "${file}" failed to load: ${err}\n`);
33
+ }
34
+ }
35
+ return names;
36
+ }
37
+ export function getPlugin(name) {
38
+ return registry.get(name);
39
+ }
40
+ export function isPlugin(name) {
41
+ return registry.has(name);
42
+ }
43
+ export function listPlugins() {
44
+ return Array.from(registry.entries()).map(([name, mod]) => ({
45
+ name,
46
+ has_analyze: typeof mod.analyze === 'function',
47
+ }));
48
+ }
49
+ //# sourceMappingURL=loader.js.map
@@ -3,8 +3,8 @@ import { scoreComplexity } from './complexity-scorer.js';
3
3
  import { selectModel } from './model-selector.js';
4
4
  import { getRateStatus, trackRequest, getRoutingAdvice } from './rate-monitor.js';
5
5
  import { compressContext, estimateTokens } from './context-compressor.js';
6
- import { recordOutcome, getLearningStats, getLearnedThresholds, applyLearnedThresholds, getAgentPerformanceStats, getTaskTypeBreakdown, getCouncilInsights } from './learning-updater.js';
7
- export { estimateTokens, getRateStatus, trackRequest, recordOutcome, getLearningStats, getLearnedThresholds, applyLearnedThresholds, getAgentPerformanceStats, getTaskTypeBreakdown, getCouncilInsights };
6
+ import { recordOutcome, getLearningStats, getLearnedThresholds, applyLearnedThresholds, getAgentPerformanceStats, getTaskTypeBreakdown, getCouncilInsights, getRecommendedAgent } from './learning-updater.js';
7
+ export { estimateTokens, getRateStatus, trackRequest, recordOutcome, getLearningStats, getLearnedThresholds, applyLearnedThresholds, getAgentPerformanceStats, getTaskTypeBreakdown, getCouncilInsights, getRecommendedAgent };
8
8
  export function routeTask(task, options = {}) {
9
9
  const learned = getLearnedThresholds();
10
10
  const complexity = scoreComplexity(task, options.filesAffected, options.forceCouncil, learned.source === 'learned' ? learned : undefined);
@@ -2,10 +2,54 @@
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { getDb } from '../memory/local.js';
4
4
  // ─── Record ───────────────────────────────────────────────────────────────────
5
- export function recordOutcome(taskType, complexity, modelTier, agent, outputQuality, tokensUsed = 0) {
5
+ export function recordOutcome(taskType, complexity, modelTier, agent, outputQuality, tokensUsed = 0, fileExt) {
6
6
  const db = getDb();
7
7
  db.prepare(`INSERT INTO learning_data (id, task_type, complexity, model_tier, output_quality, tokens_used, agent)
8
8
  VALUES (?, ?, ?, ?, ?, ?, ?)`).run(randomUUID(), taskType, complexity, modelTier, outputQuality, tokensUsed, agent);
9
+ // Predictive routing: track agent quality per file extension and task type keyword
10
+ if (agent && agent !== 'dynamic' && outputQuality > 0) {
11
+ const now = new Date().toISOString();
12
+ const upsert = (key) => {
13
+ const existing = db.prepare('SELECT id, pattern_val, confidence, seen_count FROM patterns WHERE pattern_key = ?').get(key);
14
+ if (existing) {
15
+ // running average quality as confidence (0–1)
16
+ const newConf = ((existing.confidence * existing.seen_count) + (outputQuality / 100)) / (existing.seen_count + 1);
17
+ db.prepare('UPDATE patterns SET confidence = ?, seen_count = ?, updated_at = ? WHERE pattern_key = ?')
18
+ .run(Math.min(1, newConf), existing.seen_count + 1, now, key);
19
+ }
20
+ else {
21
+ db.prepare('INSERT INTO patterns (id, pattern_key, pattern_val, confidence, seen_count, updated_at) VALUES (?, ?, ?, ?, ?, ?)')
22
+ .run(randomUUID(), key, agent, outputQuality / 100, 1, now);
23
+ }
24
+ };
25
+ if (fileExt)
26
+ upsert(`file_agent:${fileExt.toLowerCase()}:${agent}`);
27
+ const keyword = taskType.toLowerCase().split(/[\s_-]/)[0];
28
+ if (keyword)
29
+ upsert(`task_agent:${keyword}:${agent}`);
30
+ }
31
+ }
32
+ // ─── Predictive Agent Recommendation ─────────────────────────────────────────
33
+ export function getRecommendedAgent(taskType, fileExt) {
34
+ const db = getDb();
35
+ // Try file extension match first (more specific)
36
+ if (fileExt) {
37
+ const rows = db.prepare(`SELECT pattern_key, pattern_val, confidence, seen_count FROM patterns
38
+ WHERE pattern_key LIKE ? AND seen_count >= 3
39
+ ORDER BY confidence DESC LIMIT 1`).all(`file_agent:${fileExt.toLowerCase()}:%`);
40
+ if (rows.length > 0 && rows[0].confidence >= 0.7)
41
+ return rows[0].pattern_val;
42
+ }
43
+ // Fall back to task keyword match
44
+ const keyword = taskType.toLowerCase().split(/[\s_-]/)[0];
45
+ if (keyword) {
46
+ const rows = db.prepare(`SELECT pattern_key, pattern_val, confidence, seen_count FROM patterns
47
+ WHERE pattern_key LIKE ? AND seen_count >= 3
48
+ ORDER BY confidence DESC LIMIT 1`).all(`task_agent:${keyword}:%`);
49
+ if (rows.length > 0 && rows[0].confidence >= 0.7)
50
+ return rows[0].pattern_val;
51
+ }
52
+ return null;
9
53
  }
10
54
  // ─── Stats ────────────────────────────────────────────────────────────────────
11
55
  export function getLearningStats() {