@rigour-labs/cli 5.2.2 → 5.2.4
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/dist/commands/check.js +13 -57
- package/dist/commands/constants.d.ts +1 -1
- package/dist/commands/constants.js +10 -14
- package/dist/commands/init.js +24 -101
- package/dist/commands/scan.js +22 -138
- package/dist/init-rules.test.js +4 -4
- package/package.json +14 -5
package/dist/commands/check.js
CHANGED
|
@@ -2,7 +2,7 @@ import fs from 'fs-extra';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import yaml from 'yaml';
|
|
5
|
-
import { GateRunner, ConfigSchema, recordScore, getScoreTrend, resolveDeepOptions, getProvenanceTrends, getQualityTrend, IncrementalCache } from '@rigour-labs/core';
|
|
5
|
+
import { GateRunner, ConfigSchema, recordScore, getScoreTrend, resolveDeepOptions, getProvenanceTrends, getQualityTrend, IncrementalCache, renderFullReport } from '@rigour-labs/core';
|
|
6
6
|
import inquirer from 'inquirer';
|
|
7
7
|
import { randomUUID } from 'crypto';
|
|
8
8
|
// Exit codes per spec
|
|
@@ -417,64 +417,20 @@ function renderDeepOutput(report, config, options, resolvedDeepMode) {
|
|
|
417
417
|
console.log(chalk.yellow(` See ${config.output.report_path} for full details.`));
|
|
418
418
|
}
|
|
419
419
|
/**
|
|
420
|
-
* Render standard AST-only output
|
|
420
|
+
* Render standard AST-only output — uses shared terminal renderer.
|
|
421
421
|
*/
|
|
422
|
-
function renderStandardOutput(report,
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
scoreParts.push(`Score: ${stats.score}/100`);
|
|
433
|
-
if (stats.ai_health_score !== undefined)
|
|
434
|
-
scoreParts.push(`AI Health: ${stats.ai_health_score}/100`);
|
|
435
|
-
if (stats.structural_score !== undefined)
|
|
436
|
-
scoreParts.push(`Structural: ${stats.structural_score}/100`);
|
|
437
|
-
if (scoreParts.length > 0) {
|
|
438
|
-
console.log(chalk.bold(scoreParts.join(' | ')) + '\n');
|
|
439
|
-
}
|
|
440
|
-
// Severity breakdown
|
|
441
|
-
if (stats.severity_breakdown) {
|
|
442
|
-
const parts = Object.entries(stats.severity_breakdown)
|
|
443
|
-
.filter(([, count]) => count > 0)
|
|
444
|
-
.map(([sev, count]) => {
|
|
445
|
-
const color = sev === 'critical' ? chalk.red.bold : sev === 'high' ? chalk.red : sev === 'medium' ? chalk.yellow : chalk.dim;
|
|
446
|
-
return color(`${sev}: ${count}`);
|
|
447
|
-
});
|
|
448
|
-
if (parts.length > 0) {
|
|
449
|
-
console.log('Severity: ' + parts.join(', ') + '\n');
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
// ── Temporal Drift Trends (standard mode) ──
|
|
453
|
-
try {
|
|
454
|
-
const trend = getQualityTrend(process.cwd());
|
|
455
|
-
if (trend !== 'stable') {
|
|
456
|
-
const trendIcon = trend === 'improving' ? chalk.green('↑') : chalk.red('↓');
|
|
457
|
-
const trendColor = trend === 'improving' ? chalk.green : chalk.red;
|
|
458
|
-
console.log(`Trend: ${trendIcon} ${trendColor(trend)} (Z-score)\n`);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
catch { /* ignore */ }
|
|
462
|
-
for (const failure of report.failures) {
|
|
463
|
-
const sev = severityIcon(failure.severity);
|
|
464
|
-
const prov = failure.provenance ? chalk.dim(`[${failure.provenance}]`) : '';
|
|
465
|
-
console.log(`${sev} ${prov} ${chalk.red(`[${failure.id}]`)} ${failure.title}`);
|
|
466
|
-
console.log(chalk.dim(` Details: ${failure.details}`));
|
|
467
|
-
if (failure.files && failure.files.length > 0) {
|
|
468
|
-
console.log(chalk.dim(' Files:'));
|
|
469
|
-
failure.files.forEach((f) => console.log(chalk.dim(` - ${f}`)));
|
|
470
|
-
}
|
|
471
|
-
if (failure.hint) {
|
|
472
|
-
console.log(chalk.cyan(` Hint: ${failure.hint}`));
|
|
473
|
-
}
|
|
474
|
-
console.log('');
|
|
475
|
-
}
|
|
476
|
-
console.log(chalk.yellow(`See ${config.output.report_path} for full details.`));
|
|
422
|
+
function renderStandardOutput(report, _config) {
|
|
423
|
+
const trend = getScoreTrend(process.cwd());
|
|
424
|
+
const renderOpts = {
|
|
425
|
+
showBrain: true,
|
|
426
|
+
brainPatterns: 0,
|
|
427
|
+
brainTrend: 'stable',
|
|
428
|
+
};
|
|
429
|
+
if (trend && trend.recentScores.length >= 3) {
|
|
430
|
+
renderOpts.recentScores = trend.recentScores;
|
|
431
|
+
renderOpts.brainTrend = trend.direction;
|
|
477
432
|
}
|
|
433
|
+
console.log(renderFullReport(report, renderOpts));
|
|
478
434
|
}
|
|
479
435
|
function severityIcon(s) {
|
|
480
436
|
switch (s) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export declare const CODE_QUALITY_RULES = "\n# Code Quality Standards\n\n## PRODUCTION-GRADE CODE ONLY\n- No debug logging in production code\n- No shortcuts or \"temporary\" fixes\n- No over-engineering - simplest solution that works\n- Follow existing code patterns and conventions\n- Handle edge cases properly\n- No TODO/FIXME comments in final code\n\n## MODULAR CODE STRUCTURE\n- Write SMALL, focused functions (< 50 lines ideally)\n- One function = one job, clearly named\n- New features go in SEPARATE FILES, not flooding existing ones\n- Keep files under 500 lines - split if growing larger\n- Extract reusable logic into utility modules\n- Avoid \"god files\" that do everything\n- When adding to existing code, check if a new module is more appropriate\n\n## Technical Standards\n\n### DRY Principle\n- Extract repeated logic into utilities\n- Single Responsibility: One function, one job\n- Defensive coding: Validate inputs at boundaries\n- Lazy initialization for external dependencies (secrets, connections)\n- Graceful degradation over hard failures\n\n### File Organization\n```\n# Good: Separate concerns into focused files\ngovernor/\n main.py # Entry point only\n drift_detector.py # Drift detection logic\n lip_sync_analyzer.py # SyncNet integration\n audio_analyzer.py # Audio analysis\n\n# Bad: One massive file with everything\ngovernor/\n main.py (2000+ lines with all logic mixed)\n```\n\n### API Design\n- Consistent error responses\n- Proper HTTP status codes\n- Authentication at the edge\n- Rate limiting on public endpoints\n\n## PRODUCTION-READY SELF-REVIEW (THE GATEKEEPER)\n\nBefore asking for \"approval,\" internally verify:\n\n- **Zero-Dependency Check**: Does this fix rely on a local environment variable not yet in `talentlyt-kv`?\n- **Side-Effect Audit**: Could this change trigger a 502 Bad Gateway at the `/auth/callback` or `/api/agent` endpoints?\n- **Biometric Integrity**: If touching the `Governor`, have I verified that the `similarity_score` logic remains deterministic?\n- **Cost Impact**: Does this change increase egress costs (e.g., unnecessary cross-region logging)?\n- **Error Handling**: Does the UI have a graceful fallback if the backend service is slow?\n";
|
|
2
2
|
export declare const DEBUGGING_RULES = "\n# Investigation & Debugging Protocol\n\n## INVESTIGATION PROTOCOL\n\nWhen debugging:\n1. Check DEPLOYED environment (Azure, prod), not localhost unless explicitly asked\n2. Trace the actual request flow end-to-end\n3. Collect evidence at each step\n4. Present findings before proposing fixes\n\n## GAP ANALYSIS\n\nWhen debugging or proposing changes:\n\n1. **Trace the actual request flow** end-to-end:\n - Client \u2192 Cloudflare \u2192 Vercel/Container App \u2192 DB\n\n2. **Identify Hidden Gaps** - Explicitly check if the change affects:\n - **Cross-Region Handshakes**: Will this increase latency for users in Pakistan/India?\n - **Forensic Continuity**: Does this change how Maya captures gaze or audio data?\n - **Auth Persistence**: Will this interfere with WorkOS session tokens or M2M keys?\n\n3. **Evidence-First**: Collect logs from `talentlyt-dashboard` before proposing a fix.\n\n## Request Flow Tracing\n\n```\nClient Browser\n \u2193\nCloudflare (CDN/WAF)\n \u2193\nAzure Container Apps\n \u251C\u2500\u2500 talentlyt-dashboard (Next.js)\n \u2514\u2500\u2500 talentlyt-agent (Python/LiveKit)\n \u2193\nPostgreSQL Database\n \u2193\nAzure Blob Storage (recordings, evidence)\n```\n\n## Evidence Collection\n\nBefore proposing any fix:\n1. Get the actual error from logs (not assumed)\n2. Identify the exact file and line number\n3. Trace the data flow that led to the error\n4. Verify the fix doesn't break other paths\n";
|
|
3
3
|
export declare const COLLABORATION_RULES = "\n# Role & Collaboration\n\nYou are a Senior Staff Engineer working alongside a Principal Engineer (the user). \nYou do NOT work autonomously - you work collaboratively with approval at each step.\n\n## 1. NO ASSUMPTIONS\n- Never assume root cause without evidence from logs/code\n- Never assume a fix works without verification\n- Always trace the ACTUAL flow, not the expected flow\n- When debugging, read the DEPLOYED code, not local code\n\n## 2. APPROVAL REQUIRED\nBefore making ANY code change, you MUST:\n1. Show the evidence (logs, code trace) proving the issue\n2. Explain the root cause with proof\n3. Propose the fix with rationale\n4. Wait for explicit approval: \"approved\", \"go ahead\", \"do it\"\n\nException: Only proceed without approval if user explicitly says \"just do it\" or \"fix it\"\n\n## 3. NEVER LOSE TRACK\n- Maintain TODO list for multi-step tasks\n- Complete current task before starting new ones\n- If interrupted, summarize current state before switching\n- Reference previous findings, don't repeat investigations\n\n## Communication\n\n### When Reporting Issues\n```\n**Evidence:** [actual log/error message]\n**Location:** [file:line or endpoint]\n**Root Cause:** [proven, not assumed]\n**Privacy Impact:** [Does this affect biometric/PII data?]\n**Fix:** [proposed solution]\n```\n\n### When Asking for Approval\n```\nI found: [evidence]\nRoot cause: [explanation]\nProposed fix: [code change summary]\n\nApprove to proceed?\n```\n\n### When Stuck\n- Say \"I need more information\" not guess\n- Ask specific questions\n- Propose diagnostic steps\n\n## \u26A0\uFE0F RIGOUR.YML IS SACRED\n\nThe `rigour.yml` file represents **team-agreed quality standards**. You are FORBIDDEN from:\n\n1. \u274C Increasing thresholds to pass checks (e.g., raising `complexity: 100`)\n2. \u274C Adding patterns to `ignore:` to skip problematic files\n3. \u274C Removing items from `required_files`\n4. \u274C Disabling gates to avoid failures\n\n**Your job is to FIX THE CODE to meet the standards, NOT weaken the standards to pass the check.**\n\nIf thresholds genuinely need adjustment, escalate to the team lead with justification.\n\n## Forbidden Actions\n\n1. \u274C Making code changes without showing evidence first\n2. \u274C Testing on localhost when asked to check production\n3. \u274C Adding debug logs as a \"fix\"\n4. \u274C Multiple deployment attempts hoping it works\n5. \u274C Over-engineering simple solutions\n6. \u274C Assuming secrets/env vars are available at init time\n7. \u274C Ignoring user corrections\n8. \u274C Losing context between messages\n9. \u274C Modifying rigour.yml to pass quality checks\n";
|
|
4
|
-
export declare const AGNOSTIC_AI_INSTRUCTIONS = "\n# \
|
|
4
|
+
export declare const AGNOSTIC_AI_INSTRUCTIONS = "\n# Rigour Quality Gates\n\nThis project uses **Rigour MCP tools** for automated quality governance.\nThe tools are self-describing \u2014 follow their descriptions to discover the correct workflow.\n\n## Key Rules\n- **Never** modify `rigour.yml` to pass checks \u2014 fix the code instead.\n- **Never** claim \"done\" until you can demonstrate the feature works or the fix is verified.\n- If you create backend code, wire it to the frontend. If you create a service, connect it to the UI.\n\n## Verification\nBefore saying \"done\", show ONE of:\n- Output proving it works.\n- The exact commands the user can run to verify.\n- The full flow from initiation to result.\n";
|
|
@@ -184,23 +184,19 @@ If thresholds genuinely need adjustment, escalate to the team lead with justific
|
|
|
184
184
|
9. ❌ Modifying rigour.yml to pass quality checks
|
|
185
185
|
`;
|
|
186
186
|
export const AGNOSTIC_AI_INSTRUCTIONS = `
|
|
187
|
-
#
|
|
187
|
+
# Rigour Quality Gates
|
|
188
188
|
|
|
189
|
-
|
|
189
|
+
This project uses **Rigour MCP tools** for automated quality governance.
|
|
190
|
+
The tools are self-describing — follow their descriptions to discover the correct workflow.
|
|
190
191
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
- If you create backend code, you MUST wire it to the frontend.
|
|
196
|
-
- If you create a service, you MUST connect it to the UI.
|
|
197
|
-
|
|
198
|
-
## ✅ VERIFICATION PROOF REQUIRED
|
|
192
|
+
## Key Rules
|
|
193
|
+
- **Never** modify \`rigour.yml\` to pass checks — fix the code instead.
|
|
194
|
+
- **Never** claim "done" until you can demonstrate the feature works or the fix is verified.
|
|
195
|
+
- If you create backend code, wire it to the frontend. If you create a service, connect it to the UI.
|
|
199
196
|
|
|
197
|
+
## Verification
|
|
200
198
|
Before saying "done", show ONE of:
|
|
201
|
-
-
|
|
199
|
+
- Output proving it works.
|
|
202
200
|
- The exact commands the user can run to verify.
|
|
203
|
-
- The full flow from initiation
|
|
204
|
-
|
|
205
|
-
"Code exists" ≠ "Feature works"
|
|
201
|
+
- The full flow from initiation to result.
|
|
206
202
|
`;
|
package/dist/commands/init.js
CHANGED
|
@@ -186,49 +186,16 @@ export async function initCommand(cwd, options = {}) {
|
|
|
186
186
|
const rigourDocsDir = path.join(cwd, 'docs');
|
|
187
187
|
await fs.ensureDir(rigourDocsDir);
|
|
188
188
|
const instructionsPath = path.join(rigourDocsDir, 'AGENT_INSTRUCTIONS.md');
|
|
189
|
-
const ruleContent = `#
|
|
189
|
+
const ruleContent = `# Rigour: Engineering Governance
|
|
190
190
|
|
|
191
|
-
|
|
191
|
+
This project uses **Rigour MCP tools** for automated quality governance. The tools are self-describing — read their descriptions to discover the correct workflow automatically.
|
|
192
192
|
|
|
193
|
-
##
|
|
193
|
+
## Key Rules
|
|
194
194
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
Real-time hooks run automatically after every file edit. If a hook returns a violation, you MUST fix the issue before continuing. Do NOT ignore hook warnings or errors.
|
|
201
|
-
|
|
202
|
-
## ✅ BEFORE Declaring "Done"
|
|
203
|
-
|
|
204
|
-
1. **Run Quality Gates**: Call the \`rigour_check\` MCP tool to verify all quality gates pass.
|
|
205
|
-
2. **If FAIL**: Read the failure details, fix the code, and call \`rigour_check\` again.
|
|
206
|
-
3. **Only declare a task complete when \`rigour_check\` returns PASS.**
|
|
207
|
-
|
|
208
|
-
## 📋 Available Rigour MCP Tools
|
|
209
|
-
|
|
210
|
-
You MUST use these tools. They are registered via MCP and available in your tool list:
|
|
211
|
-
|
|
212
|
-
- \`rigour_recall\` — Load project memory and conventions (call at the START of every task)
|
|
213
|
-
- \`rigour_check_pattern\` — Check if a function/component/hook already exists BEFORE creating a new one
|
|
214
|
-
- \`rigour_check\` — Run all quality gates (call BEFORE declaring done)
|
|
215
|
-
- \`rigour_explain\` — Get detailed, actionable fix instructions for gate failures
|
|
216
|
-
- \`rigour_get_fix_packet\` — Get machine-readable fix data for automated remediation
|
|
217
|
-
- \`rigour_remember\` — Save important decisions, conventions, or context for future tasks
|
|
218
|
-
- \`rigour_security_audit\` — Audit project dependencies for known CVEs
|
|
219
|
-
- \`rigour_review\` — Review a code diff against quality gates
|
|
220
|
-
- \`rigour_hooks_check\` — Run the fast hook checker on specific files manually
|
|
221
|
-
|
|
222
|
-
## ⚖️ Transparency & Accountability
|
|
223
|
-
|
|
224
|
-
Every action, tool call, and code change is automatically logged to the project's local audit trail. This data is visible in real-time in **Rigour Studio** (the human-monitored governance dashboard).
|
|
225
|
-
|
|
226
|
-
## ⚠️ CRITICAL RULES
|
|
227
|
-
|
|
228
|
-
- NEVER skip the governance loop (recall → check_pattern → write → rigour_check)
|
|
229
|
-
- NEVER modify \`rigour.yml\` thresholds or ignore lists to make checks pass
|
|
230
|
-
- NEVER claim "done" without a passing \`rigour_check\` result
|
|
231
|
-
- If you're unsure whether to proceed, call \`rigour_check\` — it's fast and free
|
|
195
|
+
- **Never** modify \`rigour.yml\` thresholds or ignore lists to make checks pass — fix the code instead.
|
|
196
|
+
- **Never** claim "done" without a passing quality gate result.
|
|
197
|
+
- Real-time hooks run automatically after every file edit. If a hook blocks you, fix the issue before continuing.
|
|
198
|
+
- All actions are logged to the project's audit trail, visible in **Rigour Studio**.
|
|
232
199
|
|
|
233
200
|
${AGNOSTIC_AI_INSTRUCTIONS}
|
|
234
201
|
${CODE_QUALITY_RULES}
|
|
@@ -251,40 +218,20 @@ ${COLLABORATION_RULES}
|
|
|
251
218
|
// Cursor .mdc must be SHORT and forceful — long rules get ignored.
|
|
252
219
|
// Keep ONLY the mandatory MCP tool workflow, no generic coding advice.
|
|
253
220
|
const mdcContent = `---
|
|
254
|
-
description:
|
|
221
|
+
description: Rigour governance — use Rigour MCP tools for quality gates.
|
|
255
222
|
globs: **/*
|
|
256
223
|
alwaysApply: true
|
|
257
224
|
---
|
|
258
225
|
|
|
259
|
-
# Rigour Governance
|
|
260
|
-
|
|
261
|
-
You MUST follow this exact sequence for EVERY coding task. No exceptions.
|
|
262
|
-
|
|
263
|
-
## Step 1: BEFORE writing any code
|
|
264
|
-
\`\`\`
|
|
265
|
-
Call rigour_recall → loads project memory and conventions
|
|
266
|
-
Call rigour_check_pattern → checks if the thing you're creating already exists
|
|
267
|
-
\`\`\`
|
|
268
|
-
Both calls are REQUIRED before you write a single line of code.
|
|
269
|
-
|
|
270
|
-
## Step 2: Write your code
|
|
271
|
-
Hooks will automatically check your code after each file edit. If a hook blocks you, fix the issue immediately.
|
|
226
|
+
# Rigour Governance
|
|
272
227
|
|
|
273
|
-
|
|
274
|
-
\`\`\`
|
|
275
|
-
Call rigour_check → runs all quality gates
|
|
276
|
-
\`\`\`
|
|
277
|
-
If it returns FAIL, fix the issues and call rigour_check again. You may ONLY declare done when it returns PASS.
|
|
228
|
+
This project uses **Rigour MCP tools** for automated quality governance. The tools are self-describing — read their descriptions to discover the correct workflow automatically.
|
|
278
229
|
|
|
279
|
-
|
|
280
|
-
\`\`\`
|
|
281
|
-
Call rigour_remember with key="short_snake_case_id" and value="the full text to remember"
|
|
282
|
-
\`\`\`
|
|
230
|
+
Hooks run automatically after every file edit. If a hook blocks you, fix the issue before continuing.
|
|
283
231
|
|
|
284
|
-
##
|
|
285
|
-
- Never
|
|
286
|
-
- Never
|
|
287
|
-
- Never modify rigour.yml to make checks pass
|
|
232
|
+
## Rules
|
|
233
|
+
- Never modify rigour.yml to make checks pass — fix the code instead.
|
|
234
|
+
- Never claim "done" without a passing quality gate result.
|
|
288
235
|
`;
|
|
289
236
|
if (!(await fs.pathExists(mdcPath)) || options.force) {
|
|
290
237
|
await fs.writeFile(mdcPath, mdcContent);
|
|
@@ -308,25 +255,16 @@ Call rigour_remember with key="short_snake_case_id" and value="the full text to
|
|
|
308
255
|
const claudePath = path.join(cwd, 'CLAUDE.md');
|
|
309
256
|
const claudeContent = `# CLAUDE.md - Project Instructions for Claude Code
|
|
310
257
|
|
|
311
|
-
This
|
|
258
|
+
This project uses Rigour for quality gates. Rigour MCP tools are available — they are self-describing.
|
|
312
259
|
|
|
313
|
-
##
|
|
260
|
+
## CLI Commands (alternative to MCP tools)
|
|
314
261
|
|
|
315
262
|
\`\`\`bash
|
|
316
|
-
# Run quality gates
|
|
317
|
-
npx @rigour-labs/cli
|
|
318
|
-
|
|
319
|
-
# Get fix instructions
|
|
320
|
-
npx @rigour-labs/cli explain
|
|
321
|
-
|
|
322
|
-
# Self-healing agent loop
|
|
323
|
-
npx @rigour-labs/cli run -- claude "<task>"
|
|
263
|
+
npx @rigour-labs/cli check # Run quality gates
|
|
264
|
+
npx @rigour-labs/cli explain # Explain failures
|
|
265
|
+
npx @rigour-labs/cli run -- claude "<task>" # Self-healing agent loop
|
|
324
266
|
\`\`\`
|
|
325
267
|
|
|
326
|
-
## Rigour MCP Tools (PREFERRED over CLI)
|
|
327
|
-
|
|
328
|
-
Use the Rigour MCP tools instead of CLI commands when available. They are faster and integrated into your workflow.
|
|
329
|
-
|
|
330
268
|
${ruleContent}`;
|
|
331
269
|
if (!(await fs.pathExists(claudePath)) || options.force) {
|
|
332
270
|
await fs.writeFile(claudePath, claudeContent);
|
|
@@ -340,11 +278,7 @@ ${ruleContent}`;
|
|
|
340
278
|
const geminiStylePath = path.join(geminiDir, 'styleguide.md');
|
|
341
279
|
const geminiContent = `# Gemini Code Assist Style Guide
|
|
342
280
|
|
|
343
|
-
This project uses Rigour for quality gates.
|
|
344
|
-
|
|
345
|
-
## Required Before Completion
|
|
346
|
-
|
|
347
|
-
Always run \`npx @rigour-labs/cli check\` before marking any task complete.
|
|
281
|
+
This project uses Rigour for quality gates. If Rigour MCP tools are available, they are self-describing — use them.
|
|
348
282
|
|
|
349
283
|
${ruleContent}`;
|
|
350
284
|
if (!(await fs.pathExists(geminiStylePath)) || options.force) {
|
|
@@ -355,24 +289,13 @@ ${ruleContent}`;
|
|
|
355
289
|
// OpenAI Codex / Aider (AGENTS.md - Universal Standard)
|
|
356
290
|
if (shouldSetup('codex')) {
|
|
357
291
|
const agentsPath = path.join(cwd, 'AGENTS.md');
|
|
358
|
-
const agentsContent = `# AGENTS.md -
|
|
359
|
-
|
|
360
|
-
This file provides instructions for AI coding agents (Codex, Aider, and others).
|
|
361
|
-
|
|
362
|
-
## Setup
|
|
363
|
-
|
|
364
|
-
\`\`\`bash
|
|
365
|
-
npm install
|
|
366
|
-
npm run dev
|
|
367
|
-
npm test
|
|
368
|
-
\`\`\`
|
|
369
|
-
|
|
370
|
-
## Quality Gates
|
|
292
|
+
const agentsContent = `# AGENTS.md - AI Agent Instructions
|
|
371
293
|
|
|
372
|
-
This project uses Rigour.
|
|
294
|
+
This project uses Rigour for quality gates. If Rigour MCP tools are available, they are self-describing — use them. Otherwise use the CLI:
|
|
373
295
|
|
|
374
296
|
\`\`\`bash
|
|
375
|
-
npx @rigour-labs/cli check
|
|
297
|
+
npx @rigour-labs/cli check # Run quality gates (must PASS before task is done)
|
|
298
|
+
npx @rigour-labs/cli explain # Explain failures
|
|
376
299
|
\`\`\`
|
|
377
300
|
|
|
378
301
|
${ruleContent}`;
|
package/dist/commands/scan.js
CHANGED
|
@@ -3,7 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import yaml from 'yaml';
|
|
5
5
|
import { globby } from 'globby';
|
|
6
|
-
import { GateRunner, ConfigSchema, DiscoveryService, FixPacketService, recordScore, getScoreTrend, } from '@rigour-labs/core';
|
|
6
|
+
import { GateRunner, ConfigSchema, DiscoveryService, FixPacketService, recordScore, getScoreTrend, renderFullReport, } from '@rigour-labs/core';
|
|
7
7
|
import { buildDeepOpts, renderDeepScanResults } from './scan-deep.js';
|
|
8
8
|
// Exit codes per spec
|
|
9
9
|
const EXIT_PASS = 0;
|
|
@@ -73,7 +73,6 @@ export async function scanCommand(cwd, files = [], options = {}) {
|
|
|
73
73
|
else {
|
|
74
74
|
renderScanResults(report, stackSignals, scanCtx.config.output.report_path, cwd);
|
|
75
75
|
}
|
|
76
|
-
renderSummaryTable(report, isDeep);
|
|
77
76
|
process.exit(report.status === 'PASS' ? EXIT_PASS : EXIT_FAIL);
|
|
78
77
|
}
|
|
79
78
|
catch (error) {
|
|
@@ -178,92 +177,32 @@ function renderScanHeader(scanCtx, stackSignals, isDeep = false) {
|
|
|
178
177
|
console.log('');
|
|
179
178
|
}
|
|
180
179
|
function renderScanResults(report, stackSignals, reportPath, cwd) {
|
|
181
|
-
|
|
182
|
-
const criticalSecrets = report.failures.filter(f => f.id === 'security-patterns' && f.severity === 'critical');
|
|
183
|
-
const phantomApis = report.failures.filter(f => f.id === 'phantom-apis');
|
|
184
|
-
const ignoredErrors = report.failures.filter(f => f.id === 'promise-safety' && (f.severity === 'high' || f.severity === 'critical'));
|
|
185
|
-
// --- Scary headlines for the worst findings ---
|
|
186
|
-
let scaryHeadlines = 0;
|
|
187
|
-
if (criticalSecrets.length > 0) {
|
|
188
|
-
console.log(chalk.red.bold(`🔑 HARDCODED SECRETS: ${criticalSecrets.length} credential(s) exposed in plain text`));
|
|
189
|
-
const firstFile = criticalSecrets[0].files?.[0];
|
|
190
|
-
if (firstFile)
|
|
191
|
-
console.log(chalk.dim(` First hit: ${firstFile}`));
|
|
192
|
-
scaryHeadlines++;
|
|
193
|
-
}
|
|
194
|
-
if (fakePackages.length > 0) {
|
|
195
|
-
const unique = [...new Set(fakePackages)];
|
|
196
|
-
console.log(chalk.red.bold(`📦 HALLUCINATED PACKAGES: ${unique.length} import(s) don't exist — will crash at runtime`));
|
|
197
|
-
console.log(chalk.dim(` Examples: ${unique.slice(0, 4).join(', ')}${unique.length > 4 ? `, +${unique.length - 4} more` : ''}`));
|
|
198
|
-
scaryHeadlines++;
|
|
199
|
-
}
|
|
200
|
-
if (phantomApis.length > 0) {
|
|
201
|
-
console.log(chalk.red.bold(`👻 PHANTOM APIs: ${phantomApis.length} call(s) to methods that don't exist in stdlib`));
|
|
202
|
-
scaryHeadlines++;
|
|
203
|
-
}
|
|
204
|
-
if (ignoredErrors.length > 0) {
|
|
205
|
-
console.log(chalk.yellow.bold(`🔇 SILENT FAILURES: ${ignoredErrors.length} async error(s) swallowed — failures will vanish without a trace`));
|
|
206
|
-
scaryHeadlines++;
|
|
207
|
-
}
|
|
208
|
-
if (scaryHeadlines > 0)
|
|
209
|
-
console.log('');
|
|
210
|
-
const statusColor = report.status === 'PASS' ? chalk.green.bold : chalk.red.bold;
|
|
211
|
-
const statusLabel = report.status === 'PASS' ? 'PASS' : 'FAIL';
|
|
212
|
-
const score = report.stats.score ?? 0;
|
|
213
|
-
const aiHealth = report.stats.ai_health_score ?? 0;
|
|
214
|
-
const structural = report.stats.structural_score ?? 0;
|
|
215
|
-
console.log(statusColor(`${statusLabel} | Score ${score}/100 | AI Health ${aiHealth}/100 | Structural ${structural}/100`));
|
|
216
|
-
const severity = report.stats.severity_breakdown || {};
|
|
217
|
-
const sevParts = ['critical', 'high', 'medium', 'low', 'info']
|
|
218
|
-
.filter(level => (severity[level] || 0) > 0)
|
|
219
|
-
.map(level => `${level}: ${severity[level]}`);
|
|
220
|
-
if (sevParts.length > 0) {
|
|
221
|
-
console.log(`Severity: ${sevParts.join(', ')}`);
|
|
222
|
-
}
|
|
223
|
-
renderCoverageWarnings(stackSignals);
|
|
224
|
-
console.log('');
|
|
225
|
-
if (report.status === 'FAIL') {
|
|
226
|
-
// Sort by severity so critical findings appear first
|
|
227
|
-
const SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
|
|
228
|
-
const sorted = [...report.failures].sort((a, b) => (SEVERITY_ORDER[a.severity ?? 'medium'] ?? 2) - (SEVERITY_ORDER[b.severity ?? 'medium'] ?? 2));
|
|
229
|
-
const topFindings = sorted.slice(0, 8);
|
|
230
|
-
for (const failure of topFindings) {
|
|
231
|
-
const sev = failure.severity ?? 'medium';
|
|
232
|
-
const sevColor = sev === 'critical' ? chalk.red.bold
|
|
233
|
-
: sev === 'high' ? chalk.yellow.bold
|
|
234
|
-
: sev === 'medium' ? chalk.white
|
|
235
|
-
: chalk.dim;
|
|
236
|
-
console.log(sevColor(`${sev.toUpperCase().padEnd(8)} [${failure.id}] ${failure.title}`));
|
|
237
|
-
if (failure.files && failure.files.length > 0) {
|
|
238
|
-
console.log(chalk.dim(` ${failure.files.slice(0, 2).join(', ')}`));
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
if (report.failures.length > topFindings.length) {
|
|
242
|
-
console.log(chalk.dim(`\n...and ${report.failures.length - topFindings.length} more. See full report.`));
|
|
243
|
-
}
|
|
244
|
-
}
|
|
180
|
+
// Build render options with Brain + trend data
|
|
245
181
|
const trend = getScoreTrend(cwd);
|
|
182
|
+
const renderOpts = {
|
|
183
|
+
showBrain: true,
|
|
184
|
+
brainPatterns: 0,
|
|
185
|
+
brainTrend: 'stable',
|
|
186
|
+
};
|
|
246
187
|
if (trend && trend.recentScores.length >= 3) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
console.log(color(`\nTrend: ${trend.recentScores.join(' → ')} ${arrow}`));
|
|
188
|
+
renderOpts.recentScores = trend.recentScores;
|
|
189
|
+
renderOpts.brainTrend = trend.direction;
|
|
250
190
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
191
|
+
// Try to get Brain pattern count
|
|
192
|
+
try {
|
|
193
|
+
const dbPath = path.join(cwd, '.rigour', 'rigour.db');
|
|
194
|
+
if (fs.existsSync(dbPath)) {
|
|
195
|
+
renderOpts.brainPatterns = 1; // Indicate Brain is active (exact count comes from SQLite)
|
|
196
|
+
}
|
|
254
197
|
}
|
|
255
|
-
|
|
256
|
-
//
|
|
257
|
-
console.log(
|
|
198
|
+
catch { /* ignore */ }
|
|
199
|
+
// Render the rich report
|
|
200
|
+
console.log(renderFullReport(report, renderOpts));
|
|
201
|
+
// Coverage warnings still relevant
|
|
202
|
+
renderCoverageWarnings(stackSignals);
|
|
203
|
+
console.log(chalk.dim(`\n Report: ${reportPath}`));
|
|
258
204
|
if (report.status === 'FAIL') {
|
|
259
|
-
console.log(chalk.
|
|
260
|
-
console.log(` ${chalk.cyan('rigour explain')} — get plain-English fix suggestions`);
|
|
261
|
-
console.log(` ${chalk.cyan('rigour init')} — add quality gates to your project (blocks AI from repeating this)`);
|
|
262
|
-
console.log(` ${chalk.cyan('rigour check --ci')} — enforce in CI/CD pipeline`);
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
console.log(chalk.green.bold('✓ This repo is clean. Add it to CI to keep it that way:'));
|
|
266
|
-
console.log(` ${chalk.cyan('rigour init')} — write quality gates to rigour.yml + CI config`);
|
|
205
|
+
console.log(chalk.dim(` Fix packet: rigour-fix-packet.json`));
|
|
267
206
|
}
|
|
268
207
|
}
|
|
269
208
|
export function renderCoverageWarnings(stackSignals) {
|
|
@@ -343,58 +282,3 @@ async function writeLastScanJson(cwd, scanCtx, stackSignals, report, isDeep) {
|
|
|
343
282
|
};
|
|
344
283
|
await fs.writeJson(lastScanPath, payload, { spaces: 2 });
|
|
345
284
|
}
|
|
346
|
-
/**
|
|
347
|
-
* Render a compact summary table to the terminal after main scan output.
|
|
348
|
-
* Shows findings grouped by gate and provenance — always printed regardless of deep/standard mode.
|
|
349
|
-
*/
|
|
350
|
-
function renderSummaryTable(report, isDeep) {
|
|
351
|
-
if (report.failures.length === 0)
|
|
352
|
-
return;
|
|
353
|
-
console.log('');
|
|
354
|
-
console.log(chalk.bold.underline('Summary by Gate'));
|
|
355
|
-
console.log('');
|
|
356
|
-
// Group failures by gate ID
|
|
357
|
-
const byGate = new Map();
|
|
358
|
-
for (const f of report.failures) {
|
|
359
|
-
const existing = byGate.get(f.id) || { count: 0, critical: 0, high: 0, medium: 0, low: 0, provenance: f.provenance || 'traditional' };
|
|
360
|
-
existing.count++;
|
|
361
|
-
const sev = f.severity ?? 'medium';
|
|
362
|
-
if (sev === 'critical')
|
|
363
|
-
existing.critical++;
|
|
364
|
-
else if (sev === 'high')
|
|
365
|
-
existing.high++;
|
|
366
|
-
else if (sev === 'medium')
|
|
367
|
-
existing.medium++;
|
|
368
|
-
else
|
|
369
|
-
existing.low++;
|
|
370
|
-
byGate.set(f.id, existing);
|
|
371
|
-
}
|
|
372
|
-
// Sort by total count descending
|
|
373
|
-
const sorted = [...byGate.entries()].sort((a, b) => b[1].count - a[1].count);
|
|
374
|
-
// Print header
|
|
375
|
-
const gateCol = 28;
|
|
376
|
-
const numCol = 6;
|
|
377
|
-
const header = ` ${'Gate'.padEnd(gateCol)} ${'Total'.padStart(numCol)} ${'Crit'.padStart(numCol)} ${'High'.padStart(numCol)} ${'Med'.padStart(numCol)} ${'Low'.padStart(numCol)} Provenance`;
|
|
378
|
-
console.log(chalk.dim(header));
|
|
379
|
-
console.log(chalk.dim(' ' + '─'.repeat(header.length - 2)));
|
|
380
|
-
for (const [gateId, stats] of sorted) {
|
|
381
|
-
const provColor = stats.provenance === 'ai-drift' ? chalk.magenta
|
|
382
|
-
: stats.provenance === 'security' ? chalk.red
|
|
383
|
-
: stats.provenance === 'deep-analysis' ? chalk.blue
|
|
384
|
-
: chalk.dim;
|
|
385
|
-
const critStr = stats.critical > 0 ? chalk.red.bold(String(stats.critical).padStart(numCol)) : chalk.dim(String(stats.critical).padStart(numCol));
|
|
386
|
-
const highStr = stats.high > 0 ? chalk.yellow(String(stats.high).padStart(numCol)) : chalk.dim(String(stats.high).padStart(numCol));
|
|
387
|
-
console.log(` ${gateId.padEnd(gateCol)} ${String(stats.count).padStart(numCol)} ${critStr} ${highStr} ${chalk.dim(String(stats.medium).padStart(numCol))} ${chalk.dim(String(stats.low).padStart(numCol))} ${provColor(stats.provenance)}`);
|
|
388
|
-
}
|
|
389
|
-
// Totals row
|
|
390
|
-
const totals = sorted.reduce((acc, [, s]) => ({
|
|
391
|
-
count: acc.count + s.count,
|
|
392
|
-
critical: acc.critical + s.critical,
|
|
393
|
-
high: acc.high + s.high,
|
|
394
|
-
medium: acc.medium + s.medium,
|
|
395
|
-
low: acc.low + s.low,
|
|
396
|
-
}), { count: 0, critical: 0, high: 0, medium: 0, low: 0 });
|
|
397
|
-
console.log(chalk.dim(' ' + '─'.repeat(header.length - 2)));
|
|
398
|
-
console.log(chalk.bold(` ${'TOTAL'.padEnd(gateCol)} ${String(totals.count).padStart(numCol)} ${String(totals.critical).padStart(numCol)} ${String(totals.high).padStart(numCol)} ${String(totals.medium).padStart(numCol)} ${String(totals.low).padStart(numCol)}`));
|
|
399
|
-
console.log(chalk.dim(`\n Details saved to: .rigour/last-scan.json`));
|
|
400
|
-
}
|
package/dist/init-rules.test.js
CHANGED
|
@@ -25,10 +25,10 @@ describe('Init Command Rules Verification', () => {
|
|
|
25
25
|
const instructionsContent = await fs.readFile(instructionsPath, 'utf-8');
|
|
26
26
|
const mdcContent = await fs.readFile(mdcPath, 'utf-8');
|
|
27
27
|
// Check for agnostic instructions
|
|
28
|
-
expect(instructionsContent).toContain('#
|
|
29
|
-
expect(instructionsContent).toContain('
|
|
28
|
+
expect(instructionsContent).toContain('# Rigour Quality Gates');
|
|
29
|
+
expect(instructionsContent).toContain('Verification');
|
|
30
30
|
// Check for key sections in universal instructions
|
|
31
|
-
expect(instructionsContent).toContain('#
|
|
31
|
+
expect(instructionsContent).toContain('# Rigour: Engineering Governance');
|
|
32
32
|
expect(instructionsContent).toContain('# Code Quality Standards');
|
|
33
33
|
// Check that MDC includes governance rules
|
|
34
34
|
expect(mdcContent).toContain('# Rigour Governance');
|
|
@@ -39,6 +39,6 @@ describe('Init Command Rules Verification', () => {
|
|
|
39
39
|
const clineRulesPath = path.join(testDir, '.clinerules');
|
|
40
40
|
expect(await fs.pathExists(clineRulesPath)).toBe(true);
|
|
41
41
|
const content = await fs.readFile(clineRulesPath, 'utf-8');
|
|
42
|
-
expect(content).toContain('#
|
|
42
|
+
expect(content).toContain('# Rigour: Engineering Governance');
|
|
43
43
|
});
|
|
44
44
|
});
|
package/package.json
CHANGED
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rigour-labs/cli",
|
|
3
|
-
"version": "5.2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "5.2.4",
|
|
4
|
+
"description": "AI-native quality gates with local LLM analysis. Forces AI agents (Claude, Cursor, Copilot, Cline, Windsurf) to meet engineering standards. Bayesian Brain learns your codebase. Zero config: npx rigour-scan.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://rigour.run",
|
|
7
7
|
"keywords": [
|
|
8
|
-
"
|
|
8
|
+
"ai",
|
|
9
|
+
"llm",
|
|
9
10
|
"ai-code-quality",
|
|
11
|
+
"ai-agent",
|
|
12
|
+
"quality-gates",
|
|
10
13
|
"cli",
|
|
11
14
|
"linter",
|
|
12
15
|
"static-analysis",
|
|
16
|
+
"security",
|
|
17
|
+
"vibe-coding",
|
|
13
18
|
"claude",
|
|
14
19
|
"cursor",
|
|
15
20
|
"copilot",
|
|
21
|
+
"cline",
|
|
22
|
+
"windsurf",
|
|
16
23
|
"mcp",
|
|
17
24
|
"code-review",
|
|
18
|
-
"ci-cd"
|
|
25
|
+
"ci-cd",
|
|
26
|
+
"fix-packets",
|
|
27
|
+
"agent-governance"
|
|
19
28
|
],
|
|
20
29
|
"type": "module",
|
|
21
30
|
"bin": {
|
|
@@ -44,7 +53,7 @@
|
|
|
44
53
|
"inquirer": "9.2.16",
|
|
45
54
|
"ora": "^8.0.1",
|
|
46
55
|
"yaml": "^2.8.2",
|
|
47
|
-
"@rigour-labs/core": "5.2.
|
|
56
|
+
"@rigour-labs/core": "5.2.4"
|
|
48
57
|
},
|
|
49
58
|
"devDependencies": {
|
|
50
59
|
"@types/fs-extra": "^11.0.4",
|