@vibecheckai/cli 3.6.1 → 3.8.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/README.md +135 -63
- package/bin/_deprecations.js +447 -19
- package/bin/_router.js +1 -1
- package/bin/registry.js +347 -280
- package/bin/runners/context/generators/cursor-enhanced.js +2439 -0
- package/bin/runners/lib/agent-firewall/enforcement/gateway.js +1059 -0
- package/bin/runners/lib/agent-firewall/enforcement/index.js +98 -0
- package/bin/runners/lib/agent-firewall/enforcement/mode.js +318 -0
- package/bin/runners/lib/agent-firewall/enforcement/orchestrator.js +484 -0
- package/bin/runners/lib/agent-firewall/enforcement/proof-artifact.js +418 -0
- package/bin/runners/lib/agent-firewall/enforcement/schemas/change-event.schema.json +173 -0
- package/bin/runners/lib/agent-firewall/enforcement/schemas/intent.schema.json +181 -0
- package/bin/runners/lib/agent-firewall/enforcement/schemas/verdict.schema.json +222 -0
- package/bin/runners/lib/agent-firewall/enforcement/verdict-v2.js +333 -0
- package/bin/runners/lib/agent-firewall/index.js +200 -0
- package/bin/runners/lib/agent-firewall/integration/index.js +20 -0
- package/bin/runners/lib/agent-firewall/integration/ship-gate.js +437 -0
- package/bin/runners/lib/agent-firewall/intent/alignment-engine.js +622 -0
- package/bin/runners/lib/agent-firewall/intent/auto-detect.js +426 -0
- package/bin/runners/lib/agent-firewall/intent/index.js +102 -0
- package/bin/runners/lib/agent-firewall/intent/schema.js +352 -0
- package/bin/runners/lib/agent-firewall/intent/store.js +283 -0
- package/bin/runners/lib/agent-firewall/interception/fs-interceptor.js +502 -0
- package/bin/runners/lib/agent-firewall/interception/index.js +23 -0
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +31 -38
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +68 -3
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +4 -2
- package/bin/runners/lib/agent-firewall/risk/thresholds.js +5 -4
- package/bin/runners/lib/agent-firewall/session/collector.js +451 -0
- package/bin/runners/lib/agent-firewall/session/index.js +26 -0
- package/bin/runners/lib/artifact-envelope.js +540 -0
- package/bin/runners/lib/auth-shared.js +977 -0
- package/bin/runners/lib/checkpoint.js +941 -0
- package/bin/runners/lib/cleanup/engine.js +571 -0
- package/bin/runners/lib/cleanup/index.js +53 -0
- package/bin/runners/lib/cleanup/output.js +375 -0
- package/bin/runners/lib/cleanup/rules.js +1060 -0
- package/bin/runners/lib/doctor/diagnosis-receipt.js +454 -0
- package/bin/runners/lib/doctor/failure-signatures.js +526 -0
- package/bin/runners/lib/doctor/fix-script.js +336 -0
- package/bin/runners/lib/doctor/modules/build-tools.js +453 -0
- package/bin/runners/lib/doctor/modules/index.js +62 -3
- package/bin/runners/lib/doctor/modules/os-quirks.js +706 -0
- package/bin/runners/lib/doctor/modules/repo-integrity.js +485 -0
- package/bin/runners/lib/doctor/safe-repair.js +384 -0
- package/bin/runners/lib/engines/attack-detector.js +1192 -0
- package/bin/runners/lib/entitlements-v2.js +2 -2
- package/bin/runners/lib/error-messages.js +1 -1
- package/bin/runners/lib/missions/briefing.js +427 -0
- package/bin/runners/lib/missions/checkpoint.js +753 -0
- package/bin/runners/lib/missions/hardening.js +851 -0
- package/bin/runners/lib/missions/plan.js +421 -32
- package/bin/runners/lib/missions/safety-gates.js +645 -0
- package/bin/runners/lib/missions/schema.js +478 -0
- package/bin/runners/lib/packs/bundle.js +675 -0
- package/bin/runners/lib/packs/evidence-pack.js +671 -0
- package/bin/runners/lib/packs/pack-factory.js +837 -0
- package/bin/runners/lib/packs/permissions-pack.js +686 -0
- package/bin/runners/lib/packs/proof-graph-pack.js +779 -0
- package/bin/runners/lib/report-output.js +6 -6
- package/bin/runners/lib/safelist/index.js +96 -0
- package/bin/runners/lib/safelist/integration.js +334 -0
- package/bin/runners/lib/safelist/matcher.js +696 -0
- package/bin/runners/lib/safelist/schema.js +948 -0
- package/bin/runners/lib/safelist/store.js +438 -0
- package/bin/runners/lib/schemas/ship-manifest.schema.json +251 -0
- package/bin/runners/lib/ship-gate.js +832 -0
- package/bin/runners/lib/ship-manifest.js +1153 -0
- package/bin/runners/lib/ship-output.js +1 -1
- package/bin/runners/lib/unified-cli-output.js +710 -383
- package/bin/runners/lib/upsell.js +3 -3
- package/bin/runners/lib/why-tree.js +650 -0
- package/bin/runners/runAllowlist.js +33 -4
- package/bin/runners/runApprove.js +240 -1122
- package/bin/runners/runAudit.js +692 -0
- package/bin/runners/runAuth.js +325 -29
- package/bin/runners/runCheckpoint.js +442 -494
- package/bin/runners/runCleanup.js +343 -0
- package/bin/runners/runDoctor.js +269 -19
- package/bin/runners/runFix.js +411 -32
- package/bin/runners/runForge.js +411 -0
- package/bin/runners/runIntent.js +906 -0
- package/bin/runners/runKickoff.js +878 -0
- package/bin/runners/runLaunch.js +2000 -0
- package/bin/runners/runLink.js +785 -0
- package/bin/runners/runMcp.js +1741 -837
- package/bin/runners/runPacks.js +2089 -0
- package/bin/runners/runPolish.js +41 -0
- package/bin/runners/runSafelist.js +1190 -0
- package/bin/runners/runScan.js +21 -9
- package/bin/runners/runShield.js +1282 -0
- package/bin/runners/runShip.js +395 -16
- package/bin/vibecheck.js +34 -6
- package/mcp-server/README.md +117 -158
- package/mcp-server/handlers/tool-handler.ts +3 -3
- package/mcp-server/index.js +16 -0
- package/mcp-server/intent-firewall-interceptor.js +529 -0
- package/mcp-server/manifest.json +473 -0
- package/mcp-server/package.json +1 -1
- package/mcp-server/registry/tool-registry.js +315 -523
- package/mcp-server/registry/tools.json +442 -428
- package/mcp-server/tier-auth.js +164 -16
- package/mcp-server/tools-v3.js +70 -16
- package/package.json +1 -1
- package/bin/runners/runProof.zip +0 -0
|
@@ -0,0 +1,878 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibecheck kickoff - First Run Dopamine
|
|
3
|
+
*
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
* 60 SECONDS TO VALUE. KNOWS WHAT TO DO NEXT.
|
|
6
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
+
*
|
|
8
|
+
* Goal: User sees value and knows exactly what to do next in under 60 seconds.
|
|
9
|
+
*
|
|
10
|
+
* Flow: link → forge → audit → (optional) ship
|
|
11
|
+
*
|
|
12
|
+
* Magic:
|
|
13
|
+
* • Auto-detects intent (frontend? API? full-stack?)
|
|
14
|
+
* • Adapts checks to project type
|
|
15
|
+
* • Produces shareable summary
|
|
16
|
+
* • Clear next steps based on results
|
|
17
|
+
*
|
|
18
|
+
* @version 2.0.0
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
"use strict";
|
|
22
|
+
|
|
23
|
+
const fs = require("fs");
|
|
24
|
+
const path = require("path");
|
|
25
|
+
const crypto = require("crypto");
|
|
26
|
+
const { parseGlobalFlags, shouldShowBanner } = require("./lib/global-flags");
|
|
27
|
+
const { EXIT } = require("./lib/exit-codes");
|
|
28
|
+
|
|
29
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
30
|
+
// CONSTANTS
|
|
31
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
32
|
+
|
|
33
|
+
const VERSION = "2.0.0";
|
|
34
|
+
const SIXTY_SECONDS = 60000;
|
|
35
|
+
|
|
36
|
+
// Project intent types
|
|
37
|
+
const INTENT = {
|
|
38
|
+
FRONTEND: "frontend",
|
|
39
|
+
API: "api",
|
|
40
|
+
FULLSTACK: "fullstack",
|
|
41
|
+
STATIC: "static",
|
|
42
|
+
LIBRARY: "library",
|
|
43
|
+
UNKNOWN: "unknown",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Intent detection markers
|
|
47
|
+
const INTENT_MARKERS = {
|
|
48
|
+
frontend: [
|
|
49
|
+
"next", "react", "vue", "@vue/cli", "svelte", "@sveltejs/kit",
|
|
50
|
+
"nuxt", "@remix-run/react", "gatsby", "vite", "create-react-app",
|
|
51
|
+
"angular", "@angular/core", "solid-js", "qwik",
|
|
52
|
+
],
|
|
53
|
+
api: [
|
|
54
|
+
"express", "fastify", "@nestjs/core", "hono", "koa", "@hapi/hapi",
|
|
55
|
+
"restify", "polka", "micro", "@trpc/server", "graphql-yoga",
|
|
56
|
+
],
|
|
57
|
+
library: [
|
|
58
|
+
"tsup", "rollup", "microbundle", "unbuild", "tsdx",
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
63
|
+
// UNIFIED CLI OUTPUT
|
|
64
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
65
|
+
|
|
66
|
+
let _cliOutput = null;
|
|
67
|
+
|
|
68
|
+
function getCliOutput() {
|
|
69
|
+
if (!_cliOutput) _cliOutput = require("./lib/unified-cli-output");
|
|
70
|
+
return _cliOutput;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Get styling from unified module (with fallback)
|
|
74
|
+
function getStyles() {
|
|
75
|
+
try {
|
|
76
|
+
const cli = getCliOutput();
|
|
77
|
+
return { c: cli.ansi, sym: cli.sym };
|
|
78
|
+
} catch {
|
|
79
|
+
// Fallback if unified module not available
|
|
80
|
+
const SUPPORTS_COLOR = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
81
|
+
return {
|
|
82
|
+
c: SUPPORTS_COLOR ? {
|
|
83
|
+
reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
|
|
84
|
+
red: "\x1b[31m", green: "\x1b[32m", yellow: "\x1b[33m",
|
|
85
|
+
cyan: "\x1b[36m", gray: "\x1b[90m", magenta: "\x1b[35m",
|
|
86
|
+
} : Object.fromEntries(["reset", "bold", "dim", "red", "green", "yellow", "cyan", "gray", "magenta"].map(k => [k, ""])),
|
|
87
|
+
sym: {
|
|
88
|
+
check: "✓", cross: "✗", warning: "⚠", arrow: "→",
|
|
89
|
+
rocket: "🚀", brain: "🧠", search: "🔍", link: "🔗",
|
|
90
|
+
party: "🎉", target: "🎯", sparkle: "✨", lightning: "⚡",
|
|
91
|
+
boxTL: "╔", boxTR: "╗", boxBL: "╚", boxBR: "╝", boxH: "═", boxV: "║",
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const { c, sym } = getStyles();
|
|
98
|
+
|
|
99
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
100
|
+
// BANNER (uses unified CLI output)
|
|
101
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
102
|
+
|
|
103
|
+
function printWelcomeBanner(projectName, intent, projectRoot) {
|
|
104
|
+
const intentLabel = {
|
|
105
|
+
[INTENT.FRONTEND]: "Frontend App",
|
|
106
|
+
[INTENT.API]: "API Service",
|
|
107
|
+
[INTENT.FULLSTACK]: "Full-Stack App",
|
|
108
|
+
[INTENT.STATIC]: "Static Site",
|
|
109
|
+
[INTENT.LIBRARY]: "Library/Package",
|
|
110
|
+
[INTENT.UNKNOWN]: "Node Project",
|
|
111
|
+
}[intent] || "Project";
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const cli = getCliOutput();
|
|
115
|
+
console.log(cli.renderFullHeader("kickoff", {
|
|
116
|
+
version: VERSION,
|
|
117
|
+
tier: "FREE",
|
|
118
|
+
integrity: intentLabel.toUpperCase(),
|
|
119
|
+
target: projectRoot || process.cwd(),
|
|
120
|
+
sessionId: cli.generateSessionId(),
|
|
121
|
+
}));
|
|
122
|
+
} catch {
|
|
123
|
+
// Fallback to simple banner
|
|
124
|
+
console.log();
|
|
125
|
+
console.log(` ${c.cyan}╭${"─".repeat(65)}╮${c.reset}`);
|
|
126
|
+
console.log(` ${c.cyan}│${c.reset} ${c.cyan}│${c.reset}`);
|
|
127
|
+
console.log(` ${c.cyan}│${c.reset} ${sym.rocket} ${c.bold}VIBECHECK KICKOFF${c.reset} ${c.cyan}│${c.reset}`);
|
|
128
|
+
console.log(` ${c.cyan}│${c.reset} ${c.dim}60 seconds to value${c.reset} ${c.cyan}│${c.reset}`);
|
|
129
|
+
console.log(` ${c.cyan}│${c.reset} ${c.cyan}│${c.reset}`);
|
|
130
|
+
console.log(` ${c.cyan}│${c.reset} ${c.bold}Project:${c.reset} ${projectName.substring(0, 40).padEnd(40)} ${c.cyan}│${c.reset}`);
|
|
131
|
+
console.log(` ${c.cyan}│${c.reset} ${c.bold}Type:${c.reset} ${intentLabel.padEnd(43)} ${c.cyan}│${c.reset}`);
|
|
132
|
+
console.log(` ${c.cyan}│${c.reset} ${c.cyan}│${c.reset}`);
|
|
133
|
+
console.log(` ${c.cyan}╰${"─".repeat(65)}╯${c.reset}`);
|
|
134
|
+
console.log();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
139
|
+
// ARGS PARSING
|
|
140
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
141
|
+
|
|
142
|
+
function parseArgs(args) {
|
|
143
|
+
const { flags: globalFlags, cleanArgs } = parseGlobalFlags(args);
|
|
144
|
+
|
|
145
|
+
const opts = {
|
|
146
|
+
path: globalFlags.path || process.cwd(),
|
|
147
|
+
json: globalFlags.json || false,
|
|
148
|
+
ci: globalFlags.ci || false,
|
|
149
|
+
quiet: globalFlags.quiet || false,
|
|
150
|
+
verbose: globalFlags.verbose || false,
|
|
151
|
+
noBanner: globalFlags.noBanner || false,
|
|
152
|
+
help: globalFlags.help || false,
|
|
153
|
+
// Kickoff-specific
|
|
154
|
+
fast: false, // Skip optional steps
|
|
155
|
+
noShip: false, // Skip ship step
|
|
156
|
+
noOpen: false, // Don't open browser
|
|
157
|
+
tier: "standard", // Forge tier
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
for (let i = 0; i < cleanArgs.length; i++) {
|
|
161
|
+
const arg = cleanArgs[i];
|
|
162
|
+
if (arg === "--fast" || arg === "-f") opts.fast = true;
|
|
163
|
+
else if (arg === "--no-ship") opts.noShip = true;
|
|
164
|
+
else if (arg === "--no-open") opts.noOpen = true;
|
|
165
|
+
else if (arg === "--tier" || arg === "-t") opts.tier = cleanArgs[++i] || "standard";
|
|
166
|
+
else if (arg.startsWith("--tier=")) opts.tier = arg.split("=")[1];
|
|
167
|
+
else if (arg === "--path" || arg === "-p") opts.path = cleanArgs[++i] || process.cwd();
|
|
168
|
+
else if (arg.startsWith("--path=")) opts.path = arg.split("=")[1];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
opts.path = path.resolve(opts.path);
|
|
172
|
+
return opts;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function printHelp() {
|
|
176
|
+
console.log(`
|
|
177
|
+
${c.bold}${c.cyan}VIBECHECK KICKOFF${c.reset} - First Run Dopamine (v${VERSION})
|
|
178
|
+
|
|
179
|
+
${c.bold}USAGE${c.reset}
|
|
180
|
+
${c.cyan}vibecheck kickoff${c.reset} [options]
|
|
181
|
+
|
|
182
|
+
${c.bold}DESCRIPTION${c.reset}
|
|
183
|
+
Get value in 60 seconds. Auto-detects your project type and runs the
|
|
184
|
+
optimal validation pipeline. Perfect for first-time setup.
|
|
185
|
+
|
|
186
|
+
${c.dim}Flow: link → forge → audit → (optional) ship${c.reset}
|
|
187
|
+
|
|
188
|
+
${c.bold}OPTIONS${c.reset}
|
|
189
|
+
${c.cyan}--fast, -f${c.reset} Skip optional checks for speed
|
|
190
|
+
${c.cyan}--no-ship${c.reset} Skip the ship verdict step
|
|
191
|
+
${c.cyan}--no-open${c.reset} Don't open summary in browser
|
|
192
|
+
${c.cyan}--tier <tier>${c.reset} Forge tier: minimal, standard, extended
|
|
193
|
+
${c.cyan}--path, -p <dir>${c.reset} Project path ${c.dim}(default: current directory)${c.reset}
|
|
194
|
+
${c.cyan}--json${c.reset} Output as JSON
|
|
195
|
+
${c.cyan}--ci${c.reset} CI mode (non-interactive)
|
|
196
|
+
${c.cyan}--quiet, -q${c.reset} Suppress non-essential output
|
|
197
|
+
${c.cyan}--help, -h${c.reset} Show this help
|
|
198
|
+
|
|
199
|
+
${c.bold}PROJECT TYPES${c.reset}
|
|
200
|
+
${c.cyan}Frontend${c.reset} React, Vue, Svelte, Next.js, etc.
|
|
201
|
+
${c.cyan}API${c.reset} Express, Fastify, NestJS, Hono, etc.
|
|
202
|
+
${c.cyan}Full-Stack${c.reset} Both frontend and backend detected
|
|
203
|
+
${c.cyan}Library${c.reset} npm packages, shared modules
|
|
204
|
+
|
|
205
|
+
${c.bold}EXAMPLES${c.reset}
|
|
206
|
+
${c.dim}# First run${c.reset}
|
|
207
|
+
vibecheck kickoff
|
|
208
|
+
|
|
209
|
+
${c.dim}# Fast mode (30s)${c.reset}
|
|
210
|
+
vibecheck kickoff --fast
|
|
211
|
+
|
|
212
|
+
${c.dim}# Minimal rules tier${c.reset}
|
|
213
|
+
vibecheck kickoff --tier minimal
|
|
214
|
+
|
|
215
|
+
${c.bold}EXIT CODES${c.reset}
|
|
216
|
+
${c.green}0${c.reset} Success - All clear
|
|
217
|
+
${c.yellow}1${c.reset} Warnings - Non-blocking issues found
|
|
218
|
+
${c.red}2${c.reset} Blockers - Issues need attention
|
|
219
|
+
|
|
220
|
+
${c.dim}Documentation: https://docs.vibecheckai.dev/commands/kickoff${c.reset}
|
|
221
|
+
`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
225
|
+
// INTENT DETECTION
|
|
226
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
227
|
+
|
|
228
|
+
function detectIntent(projectRoot) {
|
|
229
|
+
const pkgPath = path.join(projectRoot, "package.json");
|
|
230
|
+
|
|
231
|
+
if (!fs.existsSync(pkgPath)) {
|
|
232
|
+
// Check for static site markers
|
|
233
|
+
if (fs.existsSync(path.join(projectRoot, "index.html"))) {
|
|
234
|
+
return { intent: INTENT.STATIC, confidence: "high", evidence: ["index.html present"] };
|
|
235
|
+
}
|
|
236
|
+
return { intent: INTENT.UNKNOWN, confidence: "low", evidence: ["No package.json"] };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let pkg;
|
|
240
|
+
try {
|
|
241
|
+
pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
242
|
+
} catch {
|
|
243
|
+
return { intent: INTENT.UNKNOWN, confidence: "low", evidence: ["Invalid package.json"] };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
247
|
+
const depNames = Object.keys(deps);
|
|
248
|
+
const evidence = [];
|
|
249
|
+
|
|
250
|
+
// Check for frontend markers
|
|
251
|
+
const hasFrontend = INTENT_MARKERS.frontend.some(marker => {
|
|
252
|
+
if (depNames.includes(marker)) {
|
|
253
|
+
evidence.push(`${marker} dependency`);
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
return false;
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Check for API markers
|
|
260
|
+
const hasAPI = INTENT_MARKERS.api.some(marker => {
|
|
261
|
+
if (depNames.includes(marker)) {
|
|
262
|
+
evidence.push(`${marker} dependency`);
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
return false;
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// Check for library markers
|
|
269
|
+
const isLibrary = INTENT_MARKERS.library.some(marker => {
|
|
270
|
+
if (depNames.includes(marker)) {
|
|
271
|
+
evidence.push(`${marker} (library tooling)`);
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
return false;
|
|
275
|
+
}) || pkg.main || pkg.module || pkg.exports;
|
|
276
|
+
|
|
277
|
+
// Determine intent
|
|
278
|
+
if (hasFrontend && hasAPI) {
|
|
279
|
+
return { intent: INTENT.FULLSTACK, confidence: "high", evidence };
|
|
280
|
+
}
|
|
281
|
+
if (hasFrontend) {
|
|
282
|
+
return { intent: INTENT.FRONTEND, confidence: "high", evidence };
|
|
283
|
+
}
|
|
284
|
+
if (hasAPI) {
|
|
285
|
+
return { intent: INTENT.API, confidence: "high", evidence };
|
|
286
|
+
}
|
|
287
|
+
if (isLibrary) {
|
|
288
|
+
evidence.push("Library structure detected");
|
|
289
|
+
return { intent: INTENT.LIBRARY, confidence: "medium", evidence };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Fallback checks
|
|
293
|
+
if (fs.existsSync(path.join(projectRoot, "src", "pages")) ||
|
|
294
|
+
fs.existsSync(path.join(projectRoot, "pages")) ||
|
|
295
|
+
fs.existsSync(path.join(projectRoot, "app"))) {
|
|
296
|
+
evidence.push("pages/app directory structure");
|
|
297
|
+
return { intent: INTENT.FRONTEND, confidence: "medium", evidence };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (fs.existsSync(path.join(projectRoot, "src", "routes")) ||
|
|
301
|
+
fs.existsSync(path.join(projectRoot, "routes"))) {
|
|
302
|
+
evidence.push("routes directory structure");
|
|
303
|
+
return { intent: INTENT.API, confidence: "medium", evidence };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return { intent: INTENT.UNKNOWN, confidence: "low", evidence: ["Generic Node.js project"] };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
310
|
+
// STEP RUNNERS
|
|
311
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
312
|
+
|
|
313
|
+
async function runLinkStep(projectRoot, opts) {
|
|
314
|
+
const start = Date.now();
|
|
315
|
+
|
|
316
|
+
try {
|
|
317
|
+
const { runLink } = require("./runLink");
|
|
318
|
+
const result = await runLink(["--path", projectRoot, "--quiet"]);
|
|
319
|
+
|
|
320
|
+
// Load the generated receipt
|
|
321
|
+
const receiptPath = path.join(projectRoot, ".vibecheck", "project.json");
|
|
322
|
+
let receipt = null;
|
|
323
|
+
if (fs.existsSync(receiptPath)) {
|
|
324
|
+
try {
|
|
325
|
+
receipt = JSON.parse(fs.readFileSync(receiptPath, "utf-8"));
|
|
326
|
+
} catch {}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
success: result === 0 || result === 1 || result === 12, // 12 = already linked
|
|
331
|
+
duration: Date.now() - start,
|
|
332
|
+
alreadyLinked: result === 12,
|
|
333
|
+
receipt,
|
|
334
|
+
};
|
|
335
|
+
} catch (err) {
|
|
336
|
+
return {
|
|
337
|
+
success: false,
|
|
338
|
+
duration: Date.now() - start,
|
|
339
|
+
error: err.message,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async function runForgeStep(projectRoot, opts, intent) {
|
|
345
|
+
const start = Date.now();
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
const { runForge } = require("./runForge");
|
|
349
|
+
|
|
350
|
+
// Adjust tier based on intent
|
|
351
|
+
let tier = opts.tier;
|
|
352
|
+
if (opts.fast) {
|
|
353
|
+
tier = "minimal";
|
|
354
|
+
} else if (intent === INTENT.API || intent === INTENT.FULLSTACK) {
|
|
355
|
+
tier = tier === "minimal" ? "standard" : tier;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const args = ["--path", projectRoot, "--tier", tier];
|
|
359
|
+
if (opts.quiet || opts.ci) args.push("--quiet");
|
|
360
|
+
|
|
361
|
+
const result = await runForge(args);
|
|
362
|
+
|
|
363
|
+
// Count generated files
|
|
364
|
+
const generated = [];
|
|
365
|
+
const cursorRules = path.join(projectRoot, ".cursorrules");
|
|
366
|
+
if (fs.existsSync(cursorRules)) generated.push(".cursorrules");
|
|
367
|
+
|
|
368
|
+
const cursorDir = path.join(projectRoot, ".cursor", "rules");
|
|
369
|
+
if (fs.existsSync(cursorDir)) {
|
|
370
|
+
const files = fs.readdirSync(cursorDir).filter(f => f.endsWith(".mdc"));
|
|
371
|
+
generated.push(...files.map(f => `.cursor/rules/${f}`));
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
success: result === 0,
|
|
376
|
+
duration: Date.now() - start,
|
|
377
|
+
tier,
|
|
378
|
+
generated,
|
|
379
|
+
rulesCount: generated.length,
|
|
380
|
+
};
|
|
381
|
+
} catch (err) {
|
|
382
|
+
return {
|
|
383
|
+
success: false,
|
|
384
|
+
duration: Date.now() - start,
|
|
385
|
+
error: err.message,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async function runAuditStep(projectRoot, opts, intent) {
|
|
391
|
+
const start = Date.now();
|
|
392
|
+
|
|
393
|
+
try {
|
|
394
|
+
// Try new runAudit first, fall back to runScan
|
|
395
|
+
let runAuditFn;
|
|
396
|
+
try {
|
|
397
|
+
runAuditFn = require("./runAudit").runAudit;
|
|
398
|
+
} catch {
|
|
399
|
+
runAuditFn = require("./runScan").runScan;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const args = ["--path", projectRoot];
|
|
403
|
+
if (opts.fast) args.push("--fast");
|
|
404
|
+
if (opts.quiet || opts.ci) args.push("--quiet");
|
|
405
|
+
|
|
406
|
+
// Focus on relevant checks for intent
|
|
407
|
+
if (intent === INTENT.FRONTEND) {
|
|
408
|
+
args.push("--skip", "auth,integrations");
|
|
409
|
+
} else if (intent === INTENT.API) {
|
|
410
|
+
args.push("--skip", "components,ui");
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const result = await runAuditFn(args);
|
|
414
|
+
|
|
415
|
+
// Load results
|
|
416
|
+
let auditData = null;
|
|
417
|
+
const latestPath = path.join(projectRoot, ".vibecheck", "results", "latest.json");
|
|
418
|
+
const auditPath = path.join(projectRoot, ".vibecheck", "audit", "latest.json");
|
|
419
|
+
|
|
420
|
+
if (fs.existsSync(auditPath)) {
|
|
421
|
+
try { auditData = JSON.parse(fs.readFileSync(auditPath, "utf-8")); } catch {}
|
|
422
|
+
} else if (fs.existsSync(latestPath)) {
|
|
423
|
+
try { auditData = JSON.parse(fs.readFileSync(latestPath, "utf-8")); } catch {}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const findings = auditData?.result?.findings || auditData?.findings || [];
|
|
427
|
+
const blockers = findings.filter(f =>
|
|
428
|
+
f.severity === "BLOCK" || f.severity === "critical" || f.severity === "high"
|
|
429
|
+
);
|
|
430
|
+
const warnings = findings.filter(f =>
|
|
431
|
+
f.severity === "WARN" || f.severity === "warning" || f.severity === "medium"
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
success: true,
|
|
436
|
+
duration: Date.now() - start,
|
|
437
|
+
exitCode: result,
|
|
438
|
+
findings: findings.length,
|
|
439
|
+
blockers: blockers.length,
|
|
440
|
+
warnings: warnings.length,
|
|
441
|
+
topFindings: findings.slice(0, 5).map(f => ({
|
|
442
|
+
type: f.type || f.rule || "issue",
|
|
443
|
+
severity: f.severity,
|
|
444
|
+
message: f.message || f.description,
|
|
445
|
+
file: f.file || f.location?.file,
|
|
446
|
+
})),
|
|
447
|
+
};
|
|
448
|
+
} catch (err) {
|
|
449
|
+
return {
|
|
450
|
+
success: false,
|
|
451
|
+
duration: Date.now() - start,
|
|
452
|
+
error: err.message,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
async function runShipStep(projectRoot, opts) {
|
|
458
|
+
const start = Date.now();
|
|
459
|
+
|
|
460
|
+
try {
|
|
461
|
+
const { runShip } = require("./runShip");
|
|
462
|
+
const args = ["--path", projectRoot, "--no-banner", "--quiet"];
|
|
463
|
+
|
|
464
|
+
const result = await runShip(args);
|
|
465
|
+
|
|
466
|
+
// Load ship results
|
|
467
|
+
const shipPath = path.join(projectRoot, ".vibecheck", "last_ship.json");
|
|
468
|
+
let shipData = null;
|
|
469
|
+
if (fs.existsSync(shipPath)) {
|
|
470
|
+
try { shipData = JSON.parse(fs.readFileSync(shipPath, "utf-8")); } catch {}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const verdict = shipData?.meta?.verdict ||
|
|
474
|
+
(result === 0 ? "SHIP" : result === 1 ? "WARN" : "BLOCK");
|
|
475
|
+
const score = shipData?.proofGraph?.summary?.overallScore ||
|
|
476
|
+
(100 - (shipData?.proofGraph?.summary?.riskScore || 30));
|
|
477
|
+
|
|
478
|
+
return {
|
|
479
|
+
success: true,
|
|
480
|
+
duration: Date.now() - start,
|
|
481
|
+
exitCode: result,
|
|
482
|
+
verdict,
|
|
483
|
+
score: Math.round(score),
|
|
484
|
+
};
|
|
485
|
+
} catch (err) {
|
|
486
|
+
return {
|
|
487
|
+
success: false,
|
|
488
|
+
duration: Date.now() - start,
|
|
489
|
+
error: err.message,
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
495
|
+
// OUTPUT RENDERING
|
|
496
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
497
|
+
|
|
498
|
+
function printStep(num, total, icon, name, description) {
|
|
499
|
+
const progress = `[${num}/${total}]`;
|
|
500
|
+
process.stdout.write(` ${c.cyan}${progress}${c.reset} ${icon} ${name}... ${c.dim}${description}${c.reset}`);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function printStepResult(result, detail = "") {
|
|
504
|
+
const detailStr = detail ? ` ${c.dim}${detail}${c.reset}` : "";
|
|
505
|
+
if (result.success) {
|
|
506
|
+
console.log(` ${c.green}${sym.check}${c.reset}${detailStr}`);
|
|
507
|
+
} else if (result.skipped) {
|
|
508
|
+
console.log(` ${c.yellow}→${c.reset} ${c.dim}skipped${c.reset}`);
|
|
509
|
+
} else {
|
|
510
|
+
console.log(` ${c.red}${sym.cross}${c.reset}${detailStr}`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function printSummaryBox(results, intent, totalDuration) {
|
|
515
|
+
const { link, forge, audit, ship } = results;
|
|
516
|
+
|
|
517
|
+
// Determine overall verdict
|
|
518
|
+
let verdict, verdictIcon, verdictColor;
|
|
519
|
+
if (ship) {
|
|
520
|
+
verdict = ship.verdict;
|
|
521
|
+
verdictIcon = ship.verdict === "SHIP" ? sym.rocket : ship.verdict === "WARN" ? sym.warning : sym.cross;
|
|
522
|
+
verdictColor = ship.verdict === "SHIP" ? c.green : ship.verdict === "WARN" ? c.yellow : c.red;
|
|
523
|
+
} else if (audit) {
|
|
524
|
+
if (audit.blockers > 0) {
|
|
525
|
+
verdict = "NEEDS ATTENTION";
|
|
526
|
+
verdictIcon = sym.target;
|
|
527
|
+
verdictColor = c.yellow;
|
|
528
|
+
} else {
|
|
529
|
+
verdict = "LOOKING GOOD";
|
|
530
|
+
verdictIcon = sym.heart;
|
|
531
|
+
verdictColor = c.green;
|
|
532
|
+
}
|
|
533
|
+
} else {
|
|
534
|
+
verdict = "SETUP COMPLETE";
|
|
535
|
+
verdictIcon = sym.check;
|
|
536
|
+
verdictColor = c.green;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
console.log();
|
|
540
|
+
console.log(` ${c.cyan}╭${"─".repeat(65)}╮${c.reset}`);
|
|
541
|
+
console.log(` ${c.cyan}│${c.reset} ${c.cyan}│${c.reset}`);
|
|
542
|
+
console.log(` ${c.cyan}│${c.reset} ${verdictIcon} ${c.bold}${verdict}${c.reset}${" ".repeat(Math.max(0, 55 - verdict.length))}${c.cyan}│${c.reset}`);
|
|
543
|
+
console.log(` ${c.cyan}│${c.reset} ${c.cyan}│${c.reset}`);
|
|
544
|
+
console.log(` ${c.cyan}├${"─".repeat(65)}┤${c.reset}`);
|
|
545
|
+
|
|
546
|
+
// Stats row
|
|
547
|
+
const stats = [];
|
|
548
|
+
if (forge?.rulesCount) stats.push(`${forge.rulesCount} rules`);
|
|
549
|
+
if (audit?.findings !== undefined) stats.push(`${audit.findings} findings`);
|
|
550
|
+
if (audit?.blockers) stats.push(`${c.red}${audit.blockers} blockers${c.reset}`);
|
|
551
|
+
if (ship?.score) stats.push(`${ship.score}/100 score`);
|
|
552
|
+
|
|
553
|
+
const statsLine = stats.join(" ${c.dim}•${c.reset} ");
|
|
554
|
+
console.log(` ${c.cyan}│${c.reset} ${c.cyan}│${c.reset}`);
|
|
555
|
+
console.log(` ${c.cyan}│${c.reset} ${statsLine}${" ".repeat(Math.max(0, 62 - statsLine.replace(/\x1b\[[0-9;]*m/g, "").length))}${c.cyan}│${c.reset}`);
|
|
556
|
+
console.log(` ${c.cyan}│${c.reset} ${c.dim}Completed in ${(totalDuration / 1000).toFixed(1)}s${c.reset} ${c.cyan}│${c.reset}`);
|
|
557
|
+
console.log(` ${c.cyan}│${c.reset} ${c.cyan}│${c.reset}`);
|
|
558
|
+
console.log(` ${c.cyan}╰${"─".repeat(65)}╯${c.reset}`);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function printTopFindings(findings) {
|
|
562
|
+
if (!findings || findings.length === 0) return;
|
|
563
|
+
|
|
564
|
+
console.log();
|
|
565
|
+
console.log(` ${c.bold}Top Findings:${c.reset}`);
|
|
566
|
+
console.log();
|
|
567
|
+
|
|
568
|
+
findings.slice(0, 3).forEach((f, i) => {
|
|
569
|
+
const icon = f.severity === "BLOCK" || f.severity === "critical" ? c.red + sym.cross : c.yellow + sym.warning;
|
|
570
|
+
const msg = (f.message || f.type).substring(0, 55);
|
|
571
|
+
console.log(` ${icon}${c.reset} ${msg}`);
|
|
572
|
+
if (f.file) {
|
|
573
|
+
const file = path.basename(f.file);
|
|
574
|
+
console.log(` ${c.dim}${file}${c.reset}`);
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function printNextSteps(results, intent, opts) {
|
|
580
|
+
const { audit, ship, forge } = results;
|
|
581
|
+
|
|
582
|
+
console.log();
|
|
583
|
+
console.log(` ${c.bold}${sym.arrow} Next Steps:${c.reset}`);
|
|
584
|
+
console.log();
|
|
585
|
+
|
|
586
|
+
// Determine what to suggest based on results
|
|
587
|
+
if (audit?.blockers > 0) {
|
|
588
|
+
console.log(` ${c.cyan}1.${c.reset} Fix ${audit.blockers} blocker(s):`);
|
|
589
|
+
console.log(` ${c.dim}vibecheck audit --verbose${c.reset} ${c.dim}# See full details${c.reset}`);
|
|
590
|
+
console.log(` ${c.dim}vibecheck fix${c.reset} ${c.dim}# Auto-fix suggestions${c.reset}`);
|
|
591
|
+
console.log();
|
|
592
|
+
console.log(` ${c.cyan}2.${c.reset} Then verify:`);
|
|
593
|
+
console.log(` ${c.dim}vibecheck ship${c.reset} ${c.dim}# Get ship verdict${c.reset}`);
|
|
594
|
+
} else if (!ship && !opts.noShip) {
|
|
595
|
+
console.log(` ${c.cyan}1.${c.reset} Get your ship verdict:`);
|
|
596
|
+
console.log(` ${c.dim}vibecheck ship${c.reset} ${c.dim}# Production readiness check${c.reset}`);
|
|
597
|
+
console.log();
|
|
598
|
+
console.log(` ${c.cyan}2.${c.reset} Before deploying:`);
|
|
599
|
+
console.log(` ${c.dim}vibecheck launch${c.reset} ${c.dim}# Full pre-release wizard${c.reset}`);
|
|
600
|
+
} else if (ship?.verdict === "SHIP") {
|
|
601
|
+
console.log(` ${c.green}${sym.party} Ready to deploy!${c.reset}`);
|
|
602
|
+
console.log();
|
|
603
|
+
console.log(` ${c.cyan}1.${c.reset} Before release:`);
|
|
604
|
+
console.log(` ${c.dim}vibecheck launch${c.reset} ${c.dim}# Full pre-release validation${c.reset}`);
|
|
605
|
+
console.log();
|
|
606
|
+
console.log(` ${c.cyan}2.${c.reset} Ongoing protection:`);
|
|
607
|
+
console.log(` ${c.dim}vibecheck shield${c.reset} ${c.dim}# Enable agent firewall${c.reset}`);
|
|
608
|
+
} else if (ship?.verdict === "WARN") {
|
|
609
|
+
console.log(` ${c.cyan}1.${c.reset} Review warnings:`);
|
|
610
|
+
console.log(` ${c.dim}vibecheck audit --verbose${c.reset} ${c.dim}# See all findings${c.reset}`);
|
|
611
|
+
console.log();
|
|
612
|
+
console.log(` ${c.cyan}2.${c.reset} When ready:`);
|
|
613
|
+
console.log(` ${c.dim}vibecheck launch${c.reset} ${c.dim}# Pre-release validation${c.reset}`);
|
|
614
|
+
} else {
|
|
615
|
+
console.log(` ${c.cyan}1.${c.reset} Fix blockers first:`);
|
|
616
|
+
console.log(` ${c.dim}vibecheck audit --verbose${c.reset} ${c.dim}# See details${c.reset}`);
|
|
617
|
+
console.log(` ${c.dim}vibecheck fix${c.reset} ${c.dim}# Get fix suggestions${c.reset}`);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Add forge tip if rules were generated
|
|
621
|
+
if (forge?.rulesCount > 0) {
|
|
622
|
+
console.log();
|
|
623
|
+
console.log(` ${c.dim}${sym.brain} ${forge.rulesCount} AI rules generated in .cursorrules${c.reset}`);
|
|
624
|
+
console.log(` ${c.dim} Open your IDE to see them in action!${c.reset}`);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
console.log();
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
631
|
+
// SHAREABLE SUMMARY
|
|
632
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
633
|
+
|
|
634
|
+
function generateShareableSummary(results, intent, projectName, duration) {
|
|
635
|
+
const { link, forge, audit, ship } = results;
|
|
636
|
+
const timestamp = new Date().toISOString();
|
|
637
|
+
const runId = `kickoff-${crypto.randomBytes(4).toString("hex")}`;
|
|
638
|
+
|
|
639
|
+
// Determine overall status
|
|
640
|
+
let status;
|
|
641
|
+
if (ship?.verdict === "SHIP") status = "ready";
|
|
642
|
+
else if (ship?.verdict === "WARN") status = "warnings";
|
|
643
|
+
else if (audit?.blockers > 0) status = "blockers";
|
|
644
|
+
else status = "setup";
|
|
645
|
+
|
|
646
|
+
const summary = {
|
|
647
|
+
version: VERSION,
|
|
648
|
+
runId,
|
|
649
|
+
timestamp,
|
|
650
|
+
duration: {
|
|
651
|
+
ms: duration,
|
|
652
|
+
formatted: `${(duration / 1000).toFixed(1)}s`,
|
|
653
|
+
},
|
|
654
|
+
project: {
|
|
655
|
+
name: projectName,
|
|
656
|
+
intent: intent.intent,
|
|
657
|
+
confidence: intent.confidence,
|
|
658
|
+
},
|
|
659
|
+
status,
|
|
660
|
+
steps: {
|
|
661
|
+
link: link ? { success: link.success, duration: link.duration, alreadyLinked: link.alreadyLinked } : null,
|
|
662
|
+
forge: forge ? { success: forge.success, duration: forge.duration, tier: forge.tier, rulesCount: forge.rulesCount } : null,
|
|
663
|
+
audit: audit ? { success: audit.success, duration: audit.duration, findings: audit.findings, blockers: audit.blockers, warnings: audit.warnings } : null,
|
|
664
|
+
ship: ship ? { success: ship.success, duration: ship.duration, verdict: ship.verdict, score: ship.score } : null,
|
|
665
|
+
},
|
|
666
|
+
summary: {
|
|
667
|
+
verdict: ship?.verdict || (audit?.blockers > 0 ? "NEEDS_ATTENTION" : "SETUP_COMPLETE"),
|
|
668
|
+
score: ship?.score || null,
|
|
669
|
+
findings: audit?.findings || 0,
|
|
670
|
+
blockers: audit?.blockers || 0,
|
|
671
|
+
warnings: audit?.warnings || 0,
|
|
672
|
+
rulesGenerated: forge?.rulesCount || 0,
|
|
673
|
+
},
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
return summary;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function saveSummary(summary, projectRoot) {
|
|
680
|
+
const outputDir = path.join(projectRoot, ".vibecheck", "kickoff");
|
|
681
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
682
|
+
|
|
683
|
+
// JSON summary
|
|
684
|
+
const jsonPath = path.join(outputDir, "summary.json");
|
|
685
|
+
fs.writeFileSync(jsonPath, JSON.stringify(summary, null, 2));
|
|
686
|
+
|
|
687
|
+
// Markdown summary
|
|
688
|
+
const mdPath = path.join(outputDir, "summary.md");
|
|
689
|
+
const md = generateMarkdownSummary(summary);
|
|
690
|
+
fs.writeFileSync(mdPath, md);
|
|
691
|
+
|
|
692
|
+
return { jsonPath, mdPath };
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function generateMarkdownSummary(summary) {
|
|
696
|
+
const { project, steps, summary: s } = summary;
|
|
697
|
+
|
|
698
|
+
let md = `# Kickoff Summary\n\n`;
|
|
699
|
+
md += `**Project:** ${project.name}\n`;
|
|
700
|
+
md += `**Type:** ${project.intent}\n`;
|
|
701
|
+
md += `**Date:** ${new Date(summary.timestamp).toLocaleString()}\n`;
|
|
702
|
+
md += `**Duration:** ${summary.duration.formatted}\n\n`;
|
|
703
|
+
|
|
704
|
+
md += `## Results\n\n`;
|
|
705
|
+
md += `| Metric | Value |\n`;
|
|
706
|
+
md += `|--------|-------|\n`;
|
|
707
|
+
md += `| Verdict | ${s.verdict} |\n`;
|
|
708
|
+
if (s.score) md += `| Score | ${s.score}/100 |\n`;
|
|
709
|
+
md += `| Findings | ${s.findings} |\n`;
|
|
710
|
+
md += `| Blockers | ${s.blockers} |\n`;
|
|
711
|
+
md += `| Warnings | ${s.warnings} |\n`;
|
|
712
|
+
md += `| Rules Generated | ${s.rulesGenerated} |\n`;
|
|
713
|
+
md += `\n`;
|
|
714
|
+
|
|
715
|
+
md += `## Steps\n\n`;
|
|
716
|
+
if (steps.link) md += `- [${steps.link.success ? "✓" : "✗"}] Link: ${steps.link.alreadyLinked ? "Already linked" : "Project bound"}\n`;
|
|
717
|
+
if (steps.forge) md += `- [${steps.forge.success ? "✓" : "✗"}] Forge: ${steps.forge.rulesCount} rules (${steps.forge.tier} tier)\n`;
|
|
718
|
+
if (steps.audit) md += `- [${steps.audit.success ? "✓" : "✗"}] Audit: ${steps.audit.findings} findings\n`;
|
|
719
|
+
if (steps.ship) md += `- [${steps.ship.success ? "✓" : "✗"}] Ship: ${steps.ship.verdict} (${steps.ship.score}/100)\n`;
|
|
720
|
+
md += `\n`;
|
|
721
|
+
|
|
722
|
+
md += `---\n`;
|
|
723
|
+
md += `*Generated by vibecheck kickoff v${summary.version}*\n`;
|
|
724
|
+
|
|
725
|
+
return md;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
729
|
+
// MAIN
|
|
730
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
731
|
+
|
|
732
|
+
async function runKickoff(args, context = {}) {
|
|
733
|
+
const startTime = Date.now();
|
|
734
|
+
const opts = parseArgs(args);
|
|
735
|
+
|
|
736
|
+
// Help
|
|
737
|
+
if (opts.help) {
|
|
738
|
+
printHelp();
|
|
739
|
+
return EXIT.SUCCESS;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
const projectRoot = opts.path;
|
|
743
|
+
const projectName = path.basename(projectRoot);
|
|
744
|
+
|
|
745
|
+
// Validate path
|
|
746
|
+
if (!fs.existsSync(projectRoot)) {
|
|
747
|
+
if (!opts.quiet) {
|
|
748
|
+
console.error(`${c.red}${sym.cross}${c.reset} Project path does not exist: ${projectRoot}`);
|
|
749
|
+
}
|
|
750
|
+
return EXIT.USER_ERROR;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// Detect intent
|
|
754
|
+
const intent = detectIntent(projectRoot);
|
|
755
|
+
|
|
756
|
+
// Show banner
|
|
757
|
+
if (!opts.quiet && !opts.ci && !opts.noBanner) {
|
|
758
|
+
printWelcomeBanner(projectName, intent.intent, projectRoot);
|
|
759
|
+
|
|
760
|
+
if (opts.verbose && intent.evidence.length > 0) {
|
|
761
|
+
console.log(` ${c.dim}Detected: ${intent.evidence.slice(0, 2).join(", ")}${c.reset}`);
|
|
762
|
+
console.log();
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Build step list based on options
|
|
767
|
+
const steps = [
|
|
768
|
+
{ id: "link", name: "Link", icon: sym.link, desc: "binding project", runner: runLinkStep },
|
|
769
|
+
{ id: "forge", name: "Forge", icon: sym.brain, desc: "generating AI rules", runner: runForgeStep },
|
|
770
|
+
{ id: "audit", name: "Audit", icon: sym.search, desc: "scanning for issues", runner: runAuditStep },
|
|
771
|
+
];
|
|
772
|
+
|
|
773
|
+
// Add ship step unless skipped or has blockers
|
|
774
|
+
if (!opts.noShip && !opts.fast) {
|
|
775
|
+
steps.push({ id: "ship", name: "Ship", icon: sym.rocket, desc: "computing verdict", runner: runShipStep });
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const results = {};
|
|
779
|
+
let exitCode = EXIT.SUCCESS;
|
|
780
|
+
|
|
781
|
+
// Run steps
|
|
782
|
+
for (let i = 0; i < steps.length; i++) {
|
|
783
|
+
const step = steps[i];
|
|
784
|
+
|
|
785
|
+
if (!opts.quiet && !opts.ci) {
|
|
786
|
+
printStep(i + 1, steps.length, step.icon, step.name, step.desc);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const result = await step.runner(projectRoot, opts, intent.intent);
|
|
790
|
+
results[step.id] = result;
|
|
791
|
+
|
|
792
|
+
// Format detail for output
|
|
793
|
+
let detail = "";
|
|
794
|
+
if (step.id === "link" && result.alreadyLinked) detail = "(already linked)";
|
|
795
|
+
if (step.id === "forge" && result.rulesCount) detail = `(${result.rulesCount} rules)`;
|
|
796
|
+
if (step.id === "audit" && result.findings !== undefined) {
|
|
797
|
+
detail = result.blockers > 0
|
|
798
|
+
? `(${result.blockers} blockers)`
|
|
799
|
+
: `(${result.findings} findings)`;
|
|
800
|
+
}
|
|
801
|
+
if (step.id === "ship" && result.verdict) detail = `(${result.verdict})`;
|
|
802
|
+
|
|
803
|
+
if (!opts.quiet && !opts.ci) {
|
|
804
|
+
printStepResult(result, detail);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Check for blockers after audit - skip ship if blockers found
|
|
808
|
+
if (step.id === "audit" && result.blockers > 0 && steps.find(s => s.id === "ship")) {
|
|
809
|
+
// Remove ship step
|
|
810
|
+
const shipIndex = steps.findIndex(s => s.id === "ship");
|
|
811
|
+
if (shipIndex > i) {
|
|
812
|
+
steps.splice(shipIndex, 1);
|
|
813
|
+
if (!opts.quiet && !opts.ci) {
|
|
814
|
+
console.log(` ${c.dim}${sym.arrow} Skipping ship (blockers found)${c.reset}`);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// Update exit code
|
|
820
|
+
if (!result.success) {
|
|
821
|
+
exitCode = Math.max(exitCode, EXIT.WARNINGS);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
const totalDuration = Date.now() - startTime;
|
|
826
|
+
|
|
827
|
+
// Generate summary
|
|
828
|
+
const summary = generateShareableSummary(results, intent, projectName, totalDuration);
|
|
829
|
+
const saved = saveSummary(summary, projectRoot);
|
|
830
|
+
|
|
831
|
+
// Output
|
|
832
|
+
if (opts.json) {
|
|
833
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
834
|
+
} else if (opts.ci) {
|
|
835
|
+
console.log(`VERDICT=${summary.summary.verdict}`);
|
|
836
|
+
console.log(`SCORE=${summary.summary.score || 0}`);
|
|
837
|
+
console.log(`FINDINGS=${summary.summary.findings}`);
|
|
838
|
+
console.log(`BLOCKERS=${summary.summary.blockers}`);
|
|
839
|
+
console.log(`RULES=${summary.summary.rulesGenerated}`);
|
|
840
|
+
console.log(`DURATION_MS=${totalDuration}`);
|
|
841
|
+
} else if (!opts.quiet) {
|
|
842
|
+
printSummaryBox(results, intent.intent, totalDuration);
|
|
843
|
+
|
|
844
|
+
if (results.audit?.topFindings?.length > 0 && opts.verbose) {
|
|
845
|
+
printTopFindings(results.audit.topFindings);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
printNextSteps(results, intent.intent, opts);
|
|
849
|
+
|
|
850
|
+
console.log(` ${c.dim}📄 Summary saved: ${path.relative(projectRoot, saved.mdPath)}${c.reset}`);
|
|
851
|
+
console.log();
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Determine final exit code
|
|
855
|
+
if (results.audit?.blockers > 0) {
|
|
856
|
+
exitCode = EXIT.BLOCKING;
|
|
857
|
+
} else if (results.audit?.warnings > 0 || results.ship?.verdict === "WARN") {
|
|
858
|
+
exitCode = EXIT.WARNINGS;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
return exitCode;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
865
|
+
// EXPORTS
|
|
866
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
867
|
+
|
|
868
|
+
module.exports = { runKickoff };
|
|
869
|
+
|
|
870
|
+
// Direct execution
|
|
871
|
+
if (require.main === module) {
|
|
872
|
+
runKickoff(process.argv.slice(2))
|
|
873
|
+
.then(code => process.exit(code))
|
|
874
|
+
.catch(err => {
|
|
875
|
+
console.error(err);
|
|
876
|
+
process.exit(10);
|
|
877
|
+
});
|
|
878
|
+
}
|