clckernel 1.2.13 → 1.2.18

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/tools/audit.js CHANGED
@@ -2,7 +2,9 @@
2
2
  /**
3
3
  * Modular Audit Orchestrator — JS
4
4
  * Discovers check_*.js guard scripts at runtime, runs each with graceful
5
- * degradation, and generates an English audit report (05-audit-report.md).
5
+ * degradation, categorizes results into the 4 Canonical RDD Lenses,
6
+ * and generates both an English audit report (05-audit-report.md) and
7
+ * an immutable, machine-readable audit receipt (audit-receipt.json).
6
8
  * Self-contained: uses only Node.js built-ins.
7
9
  */
8
10
 
@@ -11,10 +13,59 @@ const path = require('path');
11
13
 
12
14
  const REPO_ROOT = path.resolve(__dirname, '..');
13
15
 
16
+ // ── 4 Canonical RDD Lenses Mapping ──────────────────────────────────────────
17
+
18
+ const LENS_MAP = {
19
+ // Correctness
20
+ verify_tdd: 'correctness',
21
+ check_scope: 'correctness',
22
+ check_a11y: 'correctness',
23
+ check_api_contracts: 'correctness',
24
+ check_openapi_drift: 'correctness',
25
+ pytest: 'correctness',
26
+ tsc: 'correctness',
27
+ lint: 'correctness',
28
+ ruff: 'correctness',
29
+ // Security
30
+ scan_secrets: 'security',
31
+ docker_guard: 'security',
32
+ redis_guard: 'security',
33
+ celery_guard: 'security',
34
+ postgres_guard: 'security',
35
+ // Architecture
36
+ check_architecture: 'architecture',
37
+ check_custom: 'architecture',
38
+ check_ui_reuse: 'architecture',
39
+ check_symlinks: 'architecture',
40
+ check_hitl: 'architecture',
41
+ check_domain_data: 'architecture',
42
+ check_migrations: 'architecture',
43
+ graphify: 'architecture',
44
+ gentle_ai_review: 'architecture',
45
+ // Performance
46
+ check_db_efficiency: 'performance',
47
+ check_performance: 'performance',
48
+ check_seo: 'performance',
49
+ check_responsive: 'performance',
50
+ check_storybook: 'performance',
51
+ check_test_calendar_sync: 'performance',
52
+ check_observability: 'performance',
53
+ };
54
+
55
+ function getLensForGuard(guardName) {
56
+ try {
57
+ const { lensFor } = require('../src/catalog');
58
+ const lens = lensFor(guardName);
59
+ if (lens) return lens;
60
+ } catch {}
61
+ return LENS_MAP[guardName] || 'architecture';
62
+ }
63
+
14
64
  // ── Helpers ──────────────────────────────────────────────────────────────────
15
65
 
16
- function getActiveSddDir() {
17
- const sddsDir = path.join(REPO_ROOT, 'sdds');
66
+ function getActiveSddDir(baseDir) {
67
+ const root = baseDir || REPO_ROOT;
68
+ const sddsDir = path.join(root, 'sdds');
18
69
  if (!fs.existsSync(sddsDir)) {
19
70
  fs.mkdirSync(sddsDir, { recursive: true });
20
71
  }
@@ -25,7 +76,7 @@ function getActiveSddDir() {
25
76
  if (dirs.length > 0) {
26
77
  return path.join(sddsDir, dirs[0]);
27
78
  }
28
- const defaultDir = path.join(sddsDir, 'frontend-audit');
79
+ const defaultDir = path.join(sddsDir, 'audit-run');
29
80
  fs.mkdirSync(defaultDir, { recursive: true });
30
81
  return defaultDir;
31
82
  }
@@ -34,8 +85,9 @@ function getActiveSddDir() {
34
85
  * Discover all check_*.js guard scripts in the tools/ directory.
35
86
  * Returns absolute paths sorted alphabetically for deterministic ordering.
36
87
  */
37
- function discoverGuards() {
38
- const toolsDir = __dirname;
88
+ function discoverGuards(toolsDirParam) {
89
+ const toolsDir = toolsDirParam || __dirname;
90
+ if (!fs.existsSync(toolsDir)) return [];
39
91
  return fs.readdirSync(toolsDir)
40
92
  .filter(f => (f.startsWith('check_') || f.startsWith('scan_')) && f.endsWith('.js') && f !== path.basename(__filename || 'audit.js'))
41
93
  .sort()
@@ -50,12 +102,13 @@ function discoverGuards() {
50
102
  */
51
103
  async function runGuard(guardPath, targetDir) {
52
104
  const guardName = path.basename(guardPath, '.js');
105
+ const lens = getLensForGuard(guardName);
53
106
  try {
54
107
  const mod = require(guardPath);
55
108
  const fn = typeof mod === 'function' ? mod : Object.values(mod)[0];
56
109
  if (typeof fn !== 'function') {
57
110
  return {
58
- exitCode: 0, skipped: true, name: guardName,
111
+ exitCode: 0, skipped: true, name: guardName, lens,
59
112
  data: { message: `Guard ${guardName} does not export a function` },
60
113
  };
61
114
  }
@@ -65,11 +118,12 @@ async function runGuard(guardPath, targetDir) {
65
118
  exitCode: result.exitCode || 0,
66
119
  skipped: Boolean(result.skipped),
67
120
  name: result.name || guardName,
121
+ lens,
68
122
  data: result.data || result,
69
123
  };
70
124
  } catch (e) {
71
125
  return {
72
- exitCode: 0, skipped: true, name: guardName,
126
+ exitCode: 0, skipped: true, name: guardName, lens,
73
127
  data: { message: `Guard unavailable: ${e.message}` },
74
128
  };
75
129
  }
@@ -84,8 +138,9 @@ function formatGuardSection(name, result) {
84
138
  const lines = [];
85
139
  const icon = result.skipped ? '⏭️' : result.exitCode === 0 ? '✅' : '❌';
86
140
  const status = result.skipped ? 'SKIPPED' : result.exitCode === 0 ? 'PASSED' : 'FAILED';
141
+ const lensBadge = (result.lens || 'architecture').toUpperCase();
87
142
 
88
- lines.push(`### ${icon} ${name.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase())} — ${status}`);
143
+ lines.push(`### ${icon} ${name.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase())} [${lensBadge}] — ${status}`);
89
144
  lines.push('');
90
145
 
91
146
  if (result.skipped) {
@@ -96,15 +151,21 @@ function formatGuardSection(name, result) {
96
151
  lines.push(`> ${result.data?.message || 'Issues found'}`);
97
152
  lines.push('');
98
153
  const violations = result.data?.violations || result.data?.findings || {};
99
- const files = Object.keys(violations);
100
- if (files.length > 0) {
101
- for (const file of files) {
102
- const items = violations[file];
103
- for (const item of items) {
104
- const loc = item.lineNo ? ` (line ${item.lineNo})` : '';
105
- const label = item.label || item.rule || item.rule || item.module || '';
106
- const msg = item.message || item.reason || '';
107
- lines.push(`- \`${file}\`${loc}: ${label ? `[${label}] ` : ''}${msg}`);
154
+ if (Array.isArray(violations)) {
155
+ for (const item of violations) {
156
+ lines.push(`- ${item.file ? `\`${item.file}\`: ` : ''}${item.message || item.reason || JSON.stringify(item)}`);
157
+ }
158
+ } else {
159
+ const files = Object.keys(violations);
160
+ if (files.length > 0) {
161
+ for (const file of files) {
162
+ const items = violations[file];
163
+ for (const item of items) {
164
+ const loc = item.lineNo ? ` (line ${item.lineNo})` : '';
165
+ const label = item.label || item.rule || item.module || '';
166
+ const msg = item.message || item.reason || '';
167
+ lines.push(`- \`${file}\`${loc}: ${label ? `[${label}] ` : ''}${msg}`);
168
+ }
108
169
  }
109
170
  }
110
171
  }
@@ -127,8 +188,8 @@ function generateReport(results, sddDir) {
127
188
 
128
189
  sections.push(`# Code Audit Report: ${taskName}`);
129
190
  sections.push('');
130
- sections.push('> **Note for reviewer:** This report summarizes security, architecture, accessibility,');
131
- sections.push('> API contracts, performance, UI reuse, and testing coverage to facilitate review.');
191
+ sections.push('> **Review-Driven Development (RDD) Report:**');
192
+ sections.push('> Segmented across the 4 Canonical Review Lenses (Correctness, Security, Architecture, Performance).');
132
193
  sections.push('');
133
194
  sections.push('---');
134
195
  sections.push('');
@@ -144,9 +205,26 @@ function generateReport(results, sddDir) {
144
205
  sections.push('---');
145
206
  sections.push('');
146
207
 
147
- for (const result of results) {
148
- sections.push(formatGuardSection(result.name, result));
208
+ const lenses = ['correctness', 'security', 'architecture', 'performance'];
209
+ const lensHeaders = {
210
+ correctness: '## 🎯 Correctness Lens',
211
+ security: '## 🛡️ Security Lens',
212
+ architecture: '## 🏛️ Architecture Lens',
213
+ performance: '## ⚡ Performance Lens',
214
+ };
215
+
216
+ for (const lens of lenses) {
217
+ sections.push(lensHeaders[lens]);
149
218
  sections.push('');
219
+ const lensResults = results.filter(r => r.lens === lens);
220
+ if (lensResults.length === 0) {
221
+ sections.push('_No guards registered for this lens._\n');
222
+ } else {
223
+ for (const result of lensResults) {
224
+ sections.push(formatGuardSection(result.name, result));
225
+ sections.push('');
226
+ }
227
+ }
150
228
  sections.push('---');
151
229
  sections.push('');
152
230
  }
@@ -154,33 +232,107 @@ function generateReport(results, sddDir) {
154
232
  return sections.join('\n');
155
233
  }
156
234
 
235
+ /**
236
+ * Generate an immutable machine-readable audit receipt (audit-receipt.json).
237
+ */
238
+ function generateReceipt(results, targetDir) {
239
+ const lenses = ['correctness', 'security', 'architecture', 'performance'];
240
+ const lensSummary = {};
241
+
242
+ let hasFailure = false;
243
+ for (const lens of lenses) {
244
+ const lensResults = results.filter(r => r.lens === lens);
245
+ const passed = lensResults.filter(r => !r.skipped && r.exitCode === 0).length;
246
+ const failed = lensResults.filter(r => !r.skipped && r.exitCode !== 0).length;
247
+ const skipped = lensResults.filter(r => r.skipped).length;
248
+ if (failed > 0) hasFailure = true;
249
+
250
+ lensSummary[lens] = {
251
+ status: failed > 0 ? 'FAILED' : 'PASSED',
252
+ passed,
253
+ failed,
254
+ skipped,
255
+ };
256
+ }
257
+
258
+ let commitHash = 'UNKNOWN';
259
+ let gentleAiDetected = false;
260
+ try {
261
+ const { execSync } = require('child_process');
262
+ commitHash = execSync('git rev-parse HEAD', { cwd: targetDir, stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf-8' }).trim();
263
+ } catch {}
264
+
265
+ try {
266
+ const { execSync } = require('child_process');
267
+ execSync('which gentle-ai', { stdio: 'ignore' });
268
+ gentleAiDetected = true;
269
+ } catch {}
270
+
271
+ return {
272
+ $schema: 'https://clckernel.dev/schemas/audit-receipt.v1.json',
273
+ contract: 'clckernel.audit-receipt/v1',
274
+ timestamp: new Date().toISOString(),
275
+ commitHash,
276
+ verdict: hasFailure ? 'FAILED' : 'PASSED',
277
+ lenses: lensSummary,
278
+ generator: 'clckernel',
279
+ gentle_ai: {
280
+ compatible: true,
281
+ rdd_ready: true,
282
+ cli_installed: gentleAiDetected,
283
+ },
284
+ };
285
+ }
286
+
157
287
  // ── Console Summary ──────────────────────────────────────────────────────────
158
288
 
159
- function printSummary(results, reportPath) {
289
+ function printSummary(results, reportPath, receiptPath) {
160
290
  console.log('\n=================================================================');
161
- console.log(' CLC FORGE — EDUCATIONAL AUDIT SUMMARY');
291
+ console.log(' CLC KERNEL — 4-LENS RDD AUDIT SUMMARY');
162
292
  console.log('=================================================================\n');
163
293
 
164
- for (const result of results) {
165
- const icon = result.skipped ? '⏭️' : result.exitCode === 0 ? '✅' : '❌';
166
- const label = result.name.replace(/_/g, ' ');
167
- const msg = result.data?.message || '';
168
- console.log(` ${icon} ${label}: ${msg}`);
294
+ const lenses = ['correctness', 'security', 'architecture', 'performance'];
295
+ const lensLabels = {
296
+ correctness: '🎯 CORRECTNESS LENS',
297
+ security: '🛡️ SECURITY LENS',
298
+ architecture: '🏛️ ARCHITECTURE LENS',
299
+ performance: '⚡ PERFORMANCE LENS',
300
+ };
301
+
302
+ for (const lens of lenses) {
303
+ const lensResults = results.filter(r => r.lens === lens);
304
+ console.log(` ${lensLabels[lens]}:`);
305
+ if (lensResults.length === 0) {
306
+ console.log(' — No guards active for this lens');
307
+ } else {
308
+ for (const result of lensResults) {
309
+ const icon = result.skipped ? '⏭️' : result.exitCode === 0 ? '✅' : '❌';
310
+ const label = result.name.replace(/_/g, ' ');
311
+ const msg = result.data?.message || (result.exitCode === 0 ? 'Passed' : 'Failed');
312
+ console.log(` ${icon} ${label}: ${msg}`);
313
+ }
314
+ }
315
+ console.log('');
169
316
  }
170
317
 
171
- console.log(`\n📄 Full report: ${reportPath}`);
318
+ console.log(`📄 Full report: ${reportPath}`);
319
+ if (receiptPath) {
320
+ console.log(`🧾 Audit receipt: ${receiptPath}`);
321
+ }
172
322
  console.log('=================================================================\n');
173
323
  }
174
324
 
175
325
  // ── Main ─────────────────────────────────────────────────────────────────────
176
326
 
177
327
  async function main(targetDir) {
178
- const dir = targetDir || process.cwd();
179
- const sddDir = getActiveSddDir();
328
+ const dir = targetDir ? path.resolve(targetDir) : process.cwd();
329
+ const sddDir = getActiveSddDir(dir);
180
330
 
181
- console.log('\nRunning CLC Forge audit...\n');
331
+ console.log('\nRunning CLC Kernel audit...\n');
182
332
 
183
- const guardPaths = discoverGuards();
333
+ const targetTools = path.join(dir, 'tools');
334
+ const toolsDir = fs.existsSync(targetTools) ? targetTools : __dirname;
335
+ const guardPaths = discoverGuards(toolsDir);
184
336
 
185
337
  if (guardPaths.length === 0) {
186
338
  console.log('No guard tools found in tools/. Nothing to audit.');
@@ -200,20 +352,27 @@ async function main(targetDir) {
200
352
  const reportPath = path.join(sddDir, '05-audit-report.md');
201
353
  fs.writeFileSync(reportPath, reportContent, 'utf-8');
202
354
 
355
+ // Generate receipt
356
+ const receipt = generateReceipt(results, dir);
357
+ const receiptPath = path.join(sddDir, 'audit-receipt.json');
358
+ fs.writeFileSync(receiptPath, JSON.stringify(receipt, null, 2) + '\n', 'utf-8');
359
+
203
360
  // Print summary
204
- printSummary(results, reportPath);
361
+ printSummary(results, reportPath, receiptPath);
205
362
 
206
363
  // Exit with failure if any guard failed (not skipped)
207
364
  const hasFailure = results.some(r => !r.skipped && r.exitCode !== 0);
208
- process.exit(hasFailure ? 1 : 0);
365
+ if (hasFailure) {
366
+ process.exit(1);
367
+ }
209
368
  }
210
369
 
211
370
  // Support both require() and direct execution
212
371
  if (require.main === module) {
213
372
  main(process.argv[2]).catch(err => {
214
373
  console.error(`Audit orchestrator error: ${err.message}`);
215
- process.exit(0); // Graceful degradation
374
+ process.exit(1);
216
375
  });
217
376
  }
218
377
 
219
- module.exports = { main, getActiveSddDir, discoverGuards, runGuard };
378
+ module.exports = { main, getActiveSddDir, discoverGuards, runGuard, generateReceipt };
package/tools/audit.py CHANGED
@@ -17,6 +17,48 @@ import traceback
17
17
 
18
18
  REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent
19
19
 
20
+ # ── 4 Canonical RDD Lenses Mapping ──────────────────────────────────────────
21
+
22
+ LENS_MAP = {
23
+ # Correctness
24
+ "verify_tdd": "correctness",
25
+ "check_scope": "correctness",
26
+ "check_a11y": "correctness",
27
+ "check_api_contracts": "correctness",
28
+ "check_openapi_drift": "correctness",
29
+ "pytest": "correctness",
30
+ "tsc": "correctness",
31
+ "lint": "correctness",
32
+ "ruff": "correctness",
33
+ # Security
34
+ "scan_secrets": "security",
35
+ "docker_guard": "security",
36
+ "redis_guard": "security",
37
+ "celery_guard": "security",
38
+ "postgres_guard": "security",
39
+ # Architecture
40
+ "check_architecture": "architecture",
41
+ "check_custom": "architecture",
42
+ "check_ui_reuse": "architecture",
43
+ "check_symlinks": "architecture",
44
+ "check_hitl": "architecture",
45
+ "check_domain_data": "architecture",
46
+ "check_migrations": "architecture",
47
+ "graphify": "architecture",
48
+ "gentle_ai_review": "architecture",
49
+ # Performance
50
+ "check_db_efficiency": "performance",
51
+ "check_performance": "performance",
52
+ "check_seo": "performance",
53
+ "check_responsive": "performance",
54
+ "check_storybook": "performance",
55
+ "check_test_calendar_sync": "performance",
56
+ "check_observability": "performance",
57
+ }
58
+
59
+ def get_lens(guard_name: str) -> str:
60
+ return LENS_MAP.get(guard_name, "architecture")
61
+
20
62
 
21
63
  # ── Helpers ──────────────────────────────────────────────────────────────────
22
64
 
@@ -87,12 +129,14 @@ def run_guard(guard_path: pathlib.Path, target_dir: str) -> dict:
87
129
  "exit_code": result.get("exit_code", 0),
88
130
  "skipped": bool(result.get("skipped", False)),
89
131
  "name": result.get("name", guard_name),
132
+ "lens": get_lens(guard_name),
90
133
  "data": result.get("data", result),
91
134
  }
92
135
 
93
136
  except Exception as exc:
94
137
  return {
95
138
  "exit_code": 0, "skipped": True, "name": guard_name,
139
+ "lens": get_lens(guard_name),
96
140
  "data": {"message": f"Guard unavailable: {exc}"},
97
141
  }
98
142
 
@@ -151,7 +195,7 @@ def _format_guard_section(name: str, result: dict) -> str:
151
195
 
152
196
 
153
197
  def generate_report(results: list[dict], sdd_dir: pathlib.Path) -> str:
154
- """Generate the 05-audit-report.md from aggregated guard results."""
198
+ """Generate the 05-audit-report.md from aggregated guard results grouped by 4 RDD lenses."""
155
199
  task_name = sdd_dir.name
156
200
  passed = sum(1 for r in results if not r.get("skipped") and r.get("exit_code") == 0)
157
201
  failed = sum(1 for r in results if not r.get("skipped") and r.get("exit_code") != 0)
@@ -161,8 +205,8 @@ def generate_report(results: list[dict], sdd_dir: pathlib.Path) -> str:
161
205
  sections: list[str] = []
162
206
  sections.append(f"# Code Audit Report: {task_name}")
163
207
  sections.append("")
164
- sections.append("> **Note for reviewer:** This report summarizes security, architecture,")
165
- sections.append("> scope, migration safety, and TDD coverage to facilitate code review.")
208
+ sections.append("> **Review-Driven Development (RDD) Report:**")
209
+ sections.append("> Segmented across the 4 Canonical Review Lenses (Correctness, Security, Architecture, Performance).")
166
210
  sections.append("")
167
211
  sections.append("---")
168
212
  sections.append("")
@@ -178,39 +222,116 @@ def generate_report(results: list[dict], sdd_dir: pathlib.Path) -> str:
178
222
  sections.append("---")
179
223
  sections.append("")
180
224
 
181
- for result in results:
182
- sections.append(_format_guard_section(result.get("name", "unknown"), result))
225
+ lenses = ["correctness", "security", "architecture", "performance"]
226
+ headers = {
227
+ "correctness": "## 🎯 Correctness Lens",
228
+ "security": "## 🛡️ Security Lens",
229
+ "architecture": "## 🏛️ Architecture Lens",
230
+ "performance": "## ⚡ Performance Lens",
231
+ }
232
+
233
+ for lens in lenses:
234
+ sections.append(headers[lens])
183
235
  sections.append("")
236
+ lens_results = [r for r in results if r.get("lens") == lens]
237
+ if not lens_results:
238
+ sections.append("_No guards registered for this lens._\n")
239
+ else:
240
+ for result in lens_results:
241
+ sections.append(_format_guard_section(result.get("name", "unknown"), result))
242
+ sections.append("")
184
243
  sections.append("---")
185
244
  sections.append("")
186
245
 
187
246
  return "\n".join(sections)
188
247
 
189
248
 
249
+ def generate_receipt(results: list[dict], target_dir: str) -> dict:
250
+ """Generate an immutable machine-readable audit receipt (audit-receipt.json)."""
251
+ import datetime
252
+
253
+ lenses = ["correctness", "security", "architecture", "performance"]
254
+ lens_summary = {}
255
+ has_failure = False
256
+
257
+ for lens in lenses:
258
+ lens_results = [r for r in results if r.get("lens") == lens]
259
+ passed = sum(1 for r in lens_results if not r.get("skipped") and r.get("exit_code") == 0)
260
+ failed = sum(1 for r in lens_results if not r.get("skipped") and r.get("exit_code") != 0)
261
+ skipped = sum(1 for r in lens_results if r.get("skipped"))
262
+ if failed > 0:
263
+ has_failure = True
264
+ lens_summary[lens] = {
265
+ "status": "FAILED" if failed > 0 else "PASSED",
266
+ "passed": passed,
267
+ "failed": failed,
268
+ "skipped": skipped,
269
+ }
270
+
271
+ commit_hash = "UNKNOWN"
272
+ try:
273
+ commit_hash = subprocess.check_output(
274
+ ["git", "rev-parse", "HEAD"],
275
+ cwd=target_dir,
276
+ stderr=subprocess.DEVNULL,
277
+ encoding="utf-8"
278
+ ).strip()
279
+ except Exception:
280
+ pass
281
+
282
+ import shutil
283
+ gentle_ai_detected = shutil.which("gentle-ai") is not None
284
+
285
+ return {
286
+ "$schema": "https://clckernel.dev/schemas/audit-receipt.v1.json",
287
+ "contract": "clckernel.audit-receipt/v1",
288
+ "timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
289
+ "commitHash": commit_hash,
290
+ "verdict": "FAILED" if has_failure else "PASSED",
291
+ "lenses": lens_summary,
292
+ "generator": "clckernel",
293
+ "gentle_ai": {
294
+ "compatible": True,
295
+ "rdd_ready": True,
296
+ "cli_installed": gentle_ai_detected,
297
+ },
298
+ }
299
+
300
+
190
301
  # ── Console Summary ──────────────────────────────────────────────────────────
191
302
 
192
- def print_summary(results: list[dict], report_path: pathlib.Path) -> None:
303
+ def print_summary(results: list[dict], report_path: pathlib.Path, receipt_path: pathlib.Path | None = None) -> None:
193
304
  """Print an educational summary to the terminal."""
194
305
  print("\n" + "=" * 65)
195
- print(" CLC FORGE \u2014 EDUCATIONAL AUDIT SUMMARY")
306
+ print(" CLC KERNEL — 4-LENS RDD AUDIT SUMMARY")
196
307
  print("=" * 65 + "\n")
197
308
 
198
- for result in results:
199
- skipped = result.get("skipped", False)
200
- exit_code = result.get("exit_code", 0)
201
- name = result.get("name", "unknown").replace("_", " ")
202
- msg = result.get("data", {}).get("message", "")
203
-
204
- if skipped:
205
- icon = "\u23ed\ufe0f"
206
- elif exit_code == 0:
207
- icon = "\u2705"
309
+ lenses = ["correctness", "security", "architecture", "performance"]
310
+ labels = {
311
+ "correctness": "🎯 CORRECTNESS LENS",
312
+ "security": "🛡️ SECURITY LENS",
313
+ "architecture": "🏛️ ARCHITECTURE LENS",
314
+ "performance": "⚡ PERFORMANCE LENS",
315
+ }
316
+
317
+ for lens in lenses:
318
+ lens_results = [r for r in results if r.get("lens") == lens]
319
+ print(f" {labels[lens]}:")
320
+ if not lens_results:
321
+ print(" — No guards active for this lens")
208
322
  else:
209
- icon = "\u274c"
210
-
211
- print(f" {icon} {name}: {msg}")
212
-
213
- print(f"\n Report: {report_path}")
323
+ for result in lens_results:
324
+ skipped = result.get("skipped", False)
325
+ exit_code = result.get("exit_code", 0)
326
+ name = result.get("name", "unknown").replace("_", " ")
327
+ msg = result.get("data", {}).get("message", "Passed" if exit_code == 0 else "Failed")
328
+ icon = "⏭️" if skipped else ("✅" if exit_code == 0 else "❌")
329
+ print(f" {icon} {name}: {msg}")
330
+ print("")
331
+
332
+ print(f"📄 Full report: {report_path}")
333
+ if receipt_path:
334
+ print(f"🧾 Audit receipt: {receipt_path}")
214
335
  print("=" * 65 + "\n")
215
336
 
216
337
 
@@ -229,7 +350,7 @@ def main(target_dir: str | None = None) -> int:
229
350
  sdd_dir = sdds_dir / "audit-run"
230
351
  sdd_dir.mkdir(parents=True, exist_ok=True)
231
352
 
232
- print(f"\nRunning CLC Forge audit for: {sdd_dir.name}\n")
353
+ print(f"\nRunning CLC Kernel audit for: {sdd_dir.name}\n")
233
354
 
234
355
  guard_paths = discover_guards(tools_dir)
235
356
 
@@ -253,8 +374,13 @@ def main(target_dir: str | None = None) -> int:
253
374
  report_path = sdd_dir / "05-audit-report.md"
254
375
  report_path.write_text(report_content, encoding="utf-8")
255
376
 
377
+ # Generate and write receipt
378
+ receipt = generate_receipt(results, dir_path)
379
+ receipt_path = sdd_dir / "audit-receipt.json"
380
+ receipt_path.write_text(json.dumps(receipt, indent=2) + "\n", encoding="utf-8")
381
+
256
382
  # Print summary
257
- print_summary(results, report_path)
383
+ print_summary(results, report_path, receipt_path)
258
384
 
259
385
  # Exit with failure if any guard failed (not skipped)
260
386
  has_failure = any(
@@ -270,4 +396,4 @@ if __name__ == "__main__":
270
396
  sys.exit(main(target))
271
397
  except Exception:
272
398
  traceback.print_exc()
273
- sys.exit(0) # Graceful degradation
399
+ sys.exit(1)
@@ -7,7 +7,7 @@
7
7
  * @param {string} targetDir - Directory to scan (defaults to cwd)
8
8
  * @returns {{ exitCode: number, skipped: boolean, name: string, data: object }}
9
9
  */
10
- const { parseYaml, globToRegex } = require('../src/yaml.js');
10
+ const { parseYaml, globToRegex } = require('./yaml.js');
11
11
  const check = async function checkCustom(targetDir) {
12
12
  const fs = require('fs');
13
13
  const path = require('path');
@@ -22,10 +22,22 @@ const check = async function checkHitl(targetDir) {
22
22
  for (const sDir of searchDirs) {
23
23
  if (fs.existsSync(sDir)) {
24
24
  try {
25
- const files = fs.readdirSync(sDir);
26
- for (const file of files) {
27
- if (file.endsWith('.md')) {
28
- skillFiles.push(path.join(sDir, file));
25
+ const entries = fs.readdirSync(sDir, { withFileTypes: true });
26
+ for (const entry of entries) {
27
+ if (entry.isFile() && entry.name.endsWith('.md')) {
28
+ skillFiles.push(path.join(sDir, entry.name));
29
+ } else if (entry.isDirectory()) {
30
+ // Exclude framework CLI scaffolding and tool plugins
31
+ if (entry.name === 'clckernel_cli' || entry.name === 'clc_forge_cli' || entry.name === 'impeccable') {
32
+ continue;
33
+ }
34
+ const subDirPath = path.join(sDir, entry.name);
35
+ const subEntries = fs.readdirSync(subDirPath, { withFileTypes: true });
36
+ for (const sub of subEntries) {
37
+ if (sub.isFile() && sub.name.endsWith('.md')) {
38
+ skillFiles.push(path.join(subDirPath, sub.name));
39
+ }
40
+ }
29
41
  }
30
42
  }
31
43
  } catch {}
File without changes