clckernel 1.2.17 → 1.2.19
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.es.md +86 -0
- package/README.md +86 -0
- package/package.json +1 -1
- package/src/adapters/base.js +3 -7
- package/src/catalog.js +41 -30
- package/src/generator.js +2 -6
- package/src/gitignore.js +98 -0
- package/src/updater.js +2 -0
- package/tools/audit.js +197 -38
- package/tools/audit.py +151 -25
package/README.es.md
CHANGED
|
@@ -20,11 +20,14 @@ Los agentes de programación con IA (Cursor, Claude Code, Windsurf, Copilot) cod
|
|
|
20
20
|
## 📋 Tabla de Contenidos
|
|
21
21
|
- [🎯 Casos de Uso](#-casos-de-uso)
|
|
22
22
|
- [🏛️ El Proceso Unificado de IA (AIUP)](#️-el-proceso-unificado-de-ia-aiup)
|
|
23
|
+
- [🔍 Desarrollo Guiado por Revisión (RDD) y los 4 Lentes](#-desarrollo-guiado-por-revisión-rdd-y-los-4-lentes)
|
|
23
24
|
- [⚡ Guía Paso a Paso (Ciclo Completo)](#-guía-paso-a-paso-ciclo-completo)
|
|
24
25
|
- [🎮 Interfaz Universal de Comandos](#-interfaz-universal-de-comandos)
|
|
25
26
|
- [🔄 Actualización de Proyectos Existentes](#-actualización-de-proyectos-existentes)
|
|
26
27
|
- [🌐 Ecosistemas Soportados y Arquetipos](#-ecosistemas-soportados-y-arquetipos)
|
|
28
|
+
- [🏛️ Arquitectura: Standalone vs. Simbiosis con Gentle AI](#-arquitectura-standalone-vs-simbiosis-con-gentle-ai)
|
|
27
29
|
- [🧩 Herramientas Complementarias (Opcionales)](#-herramientas-complementarias-opcionales)
|
|
30
|
+
- [📖 Glosario de Términos Clave](#-glosario-de-términos-clave)
|
|
28
31
|
- [📄 Licencia](#-licencia)
|
|
29
32
|
|
|
30
33
|
---
|
|
@@ -62,6 +65,28 @@ Los agentes de programación con IA (Cursor, Claude Code, Windsurf, Copilot) cod
|
|
|
62
65
|
|
|
63
66
|
---
|
|
64
67
|
|
|
68
|
+
<a id="rdd-y-lentes"></a>
|
|
69
|
+
## 🔍 Desarrollo Guiado por Revisión (RDD) y los 4 Lentes
|
|
70
|
+
|
|
71
|
+
CLC Kernel estructura todas sus compuertas de auditoría bajo los principios de **Review-Driven Development (RDD)**, organizando cada salvaguarda en torno a los **4 Lentes Canónicos de Revisión**:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
75
|
+
│ LOS 4 LENTES CANÓNICOS DE REVISIÓN │
|
|
76
|
+
├─────────────────────┬───────────────────┬───────────────────┬───────────────┤
|
|
77
|
+
│ 🎯 CORRECTITUD │ 🛡️ SEGURIDAD │ 🏛️ ARQUITECTURA │ ⚡ RENDIMIENTO│
|
|
78
|
+
│ Bugs funcionales, │ Fuga de secretos, │ Violación capas, │ N+1 queries, │
|
|
79
|
+
│ scope drift, TDD, │ bypass permisos, │ límites de dominio│ tamaño bundle,│
|
|
80
|
+
│ contratos API │ eval peligroso │ deps circulares │ memory leaks │
|
|
81
|
+
└─────────────────────┴───────────────────┴───────────────────┴───────────────┘
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Al ejecutar `clckernel audit` (o `node tools/audit.js`), CLC Kernel genera:
|
|
85
|
+
1. **Informe Educativo para Humanos (`05-audit-report.md`):** Explica detalladamente qué pasó, falló o fue omitido agrupado por cada lente.
|
|
86
|
+
2. **Recibo Inmutable de Auditoría (`audit-receipt.json`):** Un archivo JSON inmutable y legible por máquina vinculado al `commitHash` actual que certifica el resultado para compuertas automatizadas de revisión (como Gentle AI).
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
65
90
|
<a id="guia-paso-a-paso"></a>
|
|
66
91
|
## ⚡ Guía Paso a Paso (Ciclo Completo)
|
|
67
92
|
|
|
@@ -169,6 +194,49 @@ clckernel update
|
|
|
169
194
|
|
|
170
195
|
---
|
|
171
196
|
|
|
197
|
+
<a id="arquitectura-simbiosis"></a>
|
|
198
|
+
## 🏛️ Arquitectura: Standalone vs. Simbiosis con Gentle AI
|
|
199
|
+
|
|
200
|
+
CLC Kernel está diseñado bajo una **filosofía de cero dependencias forzadas**: opera como un motor de gobernanza 100% autosuficiente y local, ofreciendo a su vez interoperabilidad nativa y fluida con el ecosistema de [Gentle AI](https://github.com/Gentleman-Programming/gentle-ai).
|
|
201
|
+
|
|
202
|
+
<div align="center">
|
|
203
|
+
<img src="./docs/Architecture/clckernel-symbiosis.visual-check.2048x1320.dark.png" alt="CLC Kernel Standalone vs Simbiosis con Gentle AI (Diagrama Archify)" width="100%" />
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
> 💡 *Diagrama Interactivo: Generado con [Archify](https://github.com/tt-a1i/archify). Podés abrir [`docs/Architecture/clckernel-symbiosis.html`](./docs/Architecture/clckernel-symbiosis.html) para explorar la animación de flujo, cambiar entre modo claro/oscuro e inspeccionar detalles de cada nodo.*
|
|
207
|
+
|
|
208
|
+
```mermaid
|
|
209
|
+
flowchart TD
|
|
210
|
+
subgraph CORE["🤖 CLC KERNEL (Motor Central de Gobernanza)"]
|
|
211
|
+
SSOT["1. Ley SSOT<br/>(AGENTS.md y Symlinks para IDEs)"]
|
|
212
|
+
AIUP["2. Arnés AIUP<br/>(SDD + HITL + TDD Rojo-a-Verde)"]
|
|
213
|
+
AST["3. Linters AST <5ms<br/>(Reglas de Capas, Secretos y Migraciones)"]
|
|
214
|
+
RECEIPT["4. Motor de Auditoría 4-Lentes<br/>(Genera audit-receipt.json)"]
|
|
215
|
+
SSOT --> AIUP --> AST --> RECEIPT
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
RECEIPT -->|audit-receipt.json| MODES{Modo de Ejecución}
|
|
219
|
+
|
|
220
|
+
subgraph MODE1["🟢 MODO 1: STANDALONE (100% Local y Desconectado)"]
|
|
221
|
+
M1_1["• Cero dependencias externas"]
|
|
222
|
+
M1_2["• Ejecución nativa AST en < 5ms"]
|
|
223
|
+
M1_3["• Hook Git pre-commit bloquea físicamente commits inválidos"]
|
|
224
|
+
M1_4["• Reporte autosuficiente 05-audit-report.md"]
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
subgraph MODE2["🟣 MODO 2: + SIMBIOSIS CON GENTLE AI"]
|
|
228
|
+
M2_1["🧠 Engram (MCP): Contexto persistente y seguimiento de ADRs"]
|
|
229
|
+
M2_2["🛡️ Gentle AI (RDD): Revisión multi-agente adversarial"]
|
|
230
|
+
M2_3["🕸️ Graphify: Topología de código y análisis de impacto"]
|
|
231
|
+
M2_4["🔗 Sin bloqueo de proveedor: Consume audit-receipt.json sin fricción"]
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
MODES -->|Solo Local| MODE1
|
|
235
|
+
MODES -->|Con Herramientas Companion| MODE2
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
172
240
|
<a id="herramientas-complementarias"></a>
|
|
173
241
|
## 🧩 Herramientas Complementarias (Opcionales)
|
|
174
242
|
|
|
@@ -179,6 +247,24 @@ CLC Kernel se enfoca estrictamente en gobernanza y opera con **cero dependencias
|
|
|
179
247
|
|
|
180
248
|
---
|
|
181
249
|
|
|
250
|
+
<a id="glosario"></a>
|
|
251
|
+
## 📖 Glosario de Términos Clave
|
|
252
|
+
|
|
253
|
+
| Término | Definición |
|
|
254
|
+
|---|---|
|
|
255
|
+
| **SDD** *(Spec-Driven Development)* | Metodología orientada a especificaciones previas donde los contratos técnicos, esquemas, criterios de aceptación y escenarios de prueba se redactan en specs locales gitignoradas (`sdds/{cambio}/spec.md`) antes de modificar código fuente. |
|
|
256
|
+
| **RDD** *(Review-Driven Development)* | Metodología de verificación adversarial y estructurada donde los cambios se auditan a través de cuatro lentes ortogonales (Correctitud, Seguridad, Arquitectura y Rendimiento) para garantizar compuertas sistemáticas de calidad antes del merge o commit. |
|
|
257
|
+
| **AIUP** *(AI Unified Process)* | El ciclo de vida gobernado de punta a punta implementado por CLC Kernel: Investigación $\rightarrow$ SDD local $\rightarrow$ Aprobación Human-in-the-Loop (HITL) $\rightarrow$ TDD Rojo a Verde $\rightarrow$ Compuerta de Auditoría AST $\rightarrow$ Conventional Commit. |
|
|
258
|
+
| **HIT / HITL** *(Human-in-the-Loop)* | Compuerta obligatoria de aprobación donde un ingeniero humano valida contratos, decisiones arquitectónicas y pruebas propuestas antes de que un agente autónomo edite código de producción. |
|
|
259
|
+
| **TDD** *(Test-Driven Development)* | Práctica de ingeniería de reproducir bugs o especificar nuevas funcionalidades mediante un test automatizado que falle (Fase Roja) antes de escribir el código mínimo de producción para que pase (Fase Verde). |
|
|
260
|
+
| **Guard AST** *(Salvaguarda de Árbol de Sintaxis Abstracta)* | Script liviano de análisis estático sin dependencias foráneas escrito en el lenguaje nativo del stack (Python `ast`, TypeScript Compiler API, Go `go/parser`, RuboCop, etc.) que se ejecuta en menos de 5ms para bloquear físicamente violaciones antes del commit. |
|
|
261
|
+
| **SSOT** *(Single Source of Truth / Única Fuente de Verdad)* | Principio de gobernanza centralizada donde `AGENTS.md` funciona como la ley única, proyectándose automáticamente a todos los entornos de agentes (`CLAUDE.md`, `GEMINI.md`, `.cursorrules`, Copilot) mediante symlinks del sistema de archivos. |
|
|
262
|
+
| **Recibo de Auditoría** *(Audit Receipt)* | Artefacto JSON inmutable y legible por máquina (`audit-receipt.json`) producido por el motor de auditoría con la marca de tiempo, hash del commit, veredictos por cada lente y metadatos de compatibilidad RDD para Gentle AI. |
|
|
263
|
+
| **ADR** *(Architectural Decision Record)* | Documento estandarizado bajo `docs/Journal/` que captura decisiones arquitectónicas clave, contexto histórico, alternativas evaluadas y consecuencias. |
|
|
264
|
+
| **Agent-OS** | Arquetipo de sistema operativo para flujos de agentes y dominios no técnicos (por ejemplo: Marketing OS, Content Ops, Research OS) gobernado por arquitectura de dos capas, validación estricta de esquemas y compuertas HITL. |
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
182
268
|
<a id="licencia"></a>
|
|
183
269
|
## 📄 Licencia
|
|
184
270
|
MIT © [CarlosLeonCode](https://github.com/carlosleoncode)
|
package/README.md
CHANGED
|
@@ -20,11 +20,14 @@ AI coding agents (Cursor, Claude Code, Windsurf, Copilot) code fast, but without
|
|
|
20
20
|
## 📋 Table of Contents
|
|
21
21
|
- [🎯 Use Cases](#-use-cases)
|
|
22
22
|
- [🏛️ The AI Unified Process (AIUP)](#️-the-ai-unified-process-aiup)
|
|
23
|
+
- [🔍 Review-Driven Development (RDD) & the 4 Canonical Lenses](#-review-driven-development-rdd--the-4-canonical-lenses)
|
|
23
24
|
- [⚡ Step-by-Step Guide (Full Lifecycle)](#-step-by-step-guide-full-lifecycle)
|
|
24
25
|
- [🎮 Universal Command Interface](#-universal-command-interface)
|
|
25
26
|
- [🔄 Updating an Existing Project](#-updating-an-existing-project)
|
|
26
27
|
- [🌐 Supported Stacks & Archetypes](#-supported-stacks--archetypes)
|
|
28
|
+
- [🏛️ Architecture: Standalone vs. Gentle AI Symbiosis](#-architecture-standalone-vs-gentle-ai-symbiosis)
|
|
27
29
|
- [🧩 Optional Ecosystem Companions](#-optional-ecosystem-companions)
|
|
30
|
+
- [📖 Glossary of Key Terms](#-glossary-of-key-terms)
|
|
28
31
|
- [📄 License](#-license)
|
|
29
32
|
|
|
30
33
|
---
|
|
@@ -62,6 +65,28 @@ AI coding agents (Cursor, Claude Code, Windsurf, Copilot) code fast, but without
|
|
|
62
65
|
|
|
63
66
|
---
|
|
64
67
|
|
|
68
|
+
<a id="rdd-and-lenses"></a>
|
|
69
|
+
## 🔍 Review-Driven Development (RDD) & the 4 Canonical Lenses
|
|
70
|
+
|
|
71
|
+
CLC Kernel structures all verification and audit gates around **Review-Driven Development (RDD)**, categorizing every safeguard across the **4 Canonical Review Lenses**:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
75
|
+
│ THE 4 CANONICAL REVIEW LENSES │
|
|
76
|
+
├─────────────────────┬───────────────────┬───────────────────┬───────────────┤
|
|
77
|
+
│ 🎯 CORRECTNESS │ 🛡️ SECURITY │ 🏛️ ARCHITECTURE │ ⚡ PERFORMANCE│
|
|
78
|
+
│ Functional bugs, │ Secret leaks, │ Layer violations, │ N+1 queries, │
|
|
79
|
+
│ scope drift, TDD, │ permission bypass,│ domain boundaries,│ bundle size, │
|
|
80
|
+
│ API contracts │ dangerous eval │ circular deps │ memory leaks │
|
|
81
|
+
└─────────────────────┴───────────────────┴───────────────────┴───────────────┘
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
When you run `clckernel audit` (or `node tools/audit.js`), CLC Kernel generates:
|
|
85
|
+
1. **Human-Readable Educational Report (`05-audit-report.md`):** Explains exactly what passed, failed, or was skipped grouped by lens.
|
|
86
|
+
2. **Machine-Readable Audit Receipt (`audit-receipt.json`):** An immutable JSON receipt bound to the active `commitHash` certifying the verdict for automated review gates (such as Gentle AI).
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
65
90
|
<a id="step-by-step-guide"></a>
|
|
66
91
|
## ⚡ Step-by-Step Guide (Full Lifecycle)
|
|
67
92
|
|
|
@@ -169,6 +194,49 @@ clckernel update
|
|
|
169
194
|
|
|
170
195
|
---
|
|
171
196
|
|
|
197
|
+
<a id="architecture-symbiosis"></a>
|
|
198
|
+
## 🏛️ Architecture: Standalone vs. Gentle AI Symbiosis
|
|
199
|
+
|
|
200
|
+
CLC Kernel is architected with a **zero-lock-in philosophy**: it functions as a 100% self-sufficient local governance engine, while offering seamless, native interoperability with the [Gentle AI](https://github.com/Gentleman-Programming/gentle-ai) ecosystem.
|
|
201
|
+
|
|
202
|
+
<div align="center">
|
|
203
|
+
<img src="./docs/Architecture/clckernel-symbiosis.visual-check.2048x1320.dark.png" alt="CLC Kernel Standalone vs Gentle AI Symbiosis (Archify Diagram)" width="100%" />
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
> 💡 *Interactive Diagram: Built with [Archify](https://github.com/tt-a1i/archify). Open [`docs/Architecture/clckernel-symbiosis.html`](./docs/Architecture/clckernel-symbiosis.html) for full trace animation, theme switching, and deep-link node inspection.*
|
|
207
|
+
|
|
208
|
+
```mermaid
|
|
209
|
+
flowchart TD
|
|
210
|
+
subgraph CORE["🤖 CLC KERNEL (Core Governance Engine)"]
|
|
211
|
+
SSOT["1. SSOT Law<br/>(AGENTS.md & IDE Mirrors)"]
|
|
212
|
+
AIUP["2. AIUP Harness<br/>(SDD + HITL + TDD Red-to-Green)"]
|
|
213
|
+
AST["3. AST Guards <5ms<br/>(Layer, Secret & Migration Rules)"]
|
|
214
|
+
RECEIPT["4. 4-Lens Audit Engine<br/>(Emits audit-receipt.json)"]
|
|
215
|
+
SSOT --> AIUP --> AST --> RECEIPT
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
RECEIPT -->|audit-receipt.json| MODES{Deployment Mode}
|
|
219
|
+
|
|
220
|
+
subgraph MODE1["🟢 MODE 1: STANDALONE (100% Local & Offline)"]
|
|
221
|
+
M1_1["• Zero foreign dependencies"]
|
|
222
|
+
M1_2["• Native stack AST execution in < 5ms"]
|
|
223
|
+
M1_3["• Git pre-commit hook physically blocks invalid commits"]
|
|
224
|
+
M1_4["• Self-contained 05-audit-report.md"]
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
subgraph MODE2["🟣 MODE 2: + GENTLE AI SYMBIOSIS"]
|
|
228
|
+
M2_1["🧠 Engram (MCP): Persistent context & ADR tracking"]
|
|
229
|
+
M2_2["🛡️ Gentle AI (RDD): Multi-agent adversarial review"]
|
|
230
|
+
M2_3["🕸️ Graphify: Codebase topology & blast radius analysis"]
|
|
231
|
+
M2_4["🔗 Zero-Lock-In: Consumes audit-receipt.json seamlessly"]
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
MODES -->|Local Only| MODE1
|
|
235
|
+
MODES -->|With Companions| MODE2
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
172
240
|
<a id="ecosystem-companions"></a>
|
|
173
241
|
## 🧩 Optional Ecosystem Companions
|
|
174
242
|
|
|
@@ -179,6 +247,24 @@ CLC Kernel focuses strictly on governance and runs with **zero foreign dependenc
|
|
|
179
247
|
|
|
180
248
|
---
|
|
181
249
|
|
|
250
|
+
<a id="glossary"></a>
|
|
251
|
+
## 📖 Glossary of Key Terms
|
|
252
|
+
|
|
253
|
+
| Term | Definition |
|
|
254
|
+
|---|---|
|
|
255
|
+
| **SDD** *(Spec-Driven Development)* | A specification-first methodology where detailed technical contracts, schemas, acceptance criteria, and test scenarios are authored in local, gitignored specs (`sdds/{change}/spec.md`) before modifying source code. |
|
|
256
|
+
| **RDD** *(Review-Driven Development)* | An adversarial and structured code verification methodology where changes are audited across four orthogonal lenses (Correctness, Security, Architecture, Performance) to ensure systematic quality gates before merge or commit. |
|
|
257
|
+
| **AIUP** *(AI Unified Process)* | The end-to-end governed lifecycle implemented by CLC Kernel: Research $\rightarrow$ Local SDD $\rightarrow$ Human-in-the-Loop (HITL) approval $\rightarrow$ TDD Red-to-Green $\rightarrow$ AST Guard Audit Gate $\rightarrow$ Conventional Commit. |
|
|
258
|
+
| **HIT / HITL** *(Human-in-the-Loop)* | A mandatory approval gate where a human engineer validates contracts, architecture decisions, and proposed tests before an autonomous agent executes production code edits. |
|
|
259
|
+
| **TDD** *(Test-Driven Development)* | The engineering practice of writing a failing automated regression test (Red Phase) to reproduce an issue or specify a new feature before writing the minimal production code to make it pass (Green Phase). |
|
|
260
|
+
| **AST Guard** *(Abstract Syntax Tree Safeguard)* | A lightweight, zero-dependency static analysis script in the stack's native language (Python `ast`, TypeScript Compiler API, Go `go/parser`, RuboCop, etc.) executing in under 5ms to physically block architectural violations before git commits. |
|
|
261
|
+
| **SSOT** *(Single Source of Truth)* | The centralized governance principle where `AGENTS.md` acts as the single source of truth, mirrored automatically to all AI IDE config files (`CLAUDE.md`, `GEMINI.md`, `.cursorrules`, Copilot) via filesystem symlinks. |
|
|
262
|
+
| **Audit Receipt** | An immutable, machine-readable JSON artifact (`audit-receipt.json`) produced by the audit engine containing the timestamp, commit hash, per-lens verdicts, and Gentle AI RDD compatibility metadata. |
|
|
263
|
+
| **ADR** *(Architectural Decision Record)* | A standardized document in `docs/Journal/` capturing significant architectural decisions, context, evaluated alternatives, and consequences. |
|
|
264
|
+
| **Agent-OS** | An operating system archetype for non-tech domain workflows (e.g. Marketing OS, Content Ops, Research OS) governed by dual-layer architecture, strict schema validation, and HITL checkpoints. |
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
182
268
|
<a id="license"></a>
|
|
183
269
|
## 📄 License
|
|
184
270
|
MIT © [CarlosLeonCode](https://github.com/carlosleoncode)
|
package/package.json
CHANGED
package/src/adapters/base.js
CHANGED
|
@@ -68,13 +68,9 @@ class BaseAdapter {
|
|
|
68
68
|
fs.mkdirSync(path.join(targetDir, 'sdds'), { recursive: true });
|
|
69
69
|
fs.mkdirSync(path.join(targetDir, 'tools'), { recursive: true });
|
|
70
70
|
|
|
71
|
-
// 2. Gitignore
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
if (!gitignore.includes('sdds/*')) {
|
|
75
|
-
gitignore += '\n\n# Local Spec-Driven Development (SDD) files\nsdds/*\n!sdds/.gitkeep\nopenspec/*\n!openspec/.gitkeep\n.ruff_cache/\n';
|
|
76
|
-
fs.writeFileSync(gitignorePath, gitignore, 'utf-8');
|
|
77
|
-
}
|
|
71
|
+
// 2. Gitignore local SDD specs, ephemeral artifacts, and AI IDE mirrors
|
|
72
|
+
const { ensureGitignoreEntries } = require('../gitignore');
|
|
73
|
+
ensureGitignoreEntries(targetDir);
|
|
78
74
|
fs.writeFileSync(path.join(targetDir, 'sdds', '.gitkeep'), '', 'utf-8');
|
|
79
75
|
|
|
80
76
|
// 3. Write AGENTS.md
|
package/src/catalog.js
CHANGED
|
@@ -28,152 +28,152 @@ const SAFEGUARD_CATALOG = [
|
|
|
28
28
|
{ name: 'scan_secrets', labels: ['scan_secrets', 'scan_secrets.js', 'scan_secrets.py'],
|
|
29
29
|
checks: 'Secret & private-key leak scan', verify: ['verify_secret_leaks', 'verify_no_hardcoded_secrets'],
|
|
30
30
|
applies_when: 'always', severity: 'error', gate_mode: 'hard', config_keys: ['scope', 'exclude_paths'],
|
|
31
|
-
phase: 'audit', file: 'scan_secrets.js+scan_secrets.py', manifest: true, planned: false },
|
|
31
|
+
phase: 'audit', file: 'scan_secrets.js+scan_secrets.py', manifest: true, planned: false, lens: 'security' },
|
|
32
32
|
|
|
33
33
|
{ name: 'check_architecture', labels: ['check_architecture', 'check_architecture.js', 'check_architecture.py'],
|
|
34
34
|
checks: 'Clean Architecture layer / server-component boundaries', verify: ['verify_architecture_layers', 'verify_server_component_boundaries'],
|
|
35
35
|
applies_when: 'frontend,backend', severity: 'error', gate_mode: 'hard', config_keys: ['scope', 'exclude_paths'],
|
|
36
|
-
phase: 'audit', file: 'check_architecture.js+check_architecture.py', manifest: true, planned: false },
|
|
36
|
+
phase: 'audit', file: 'check_architecture.js+check_architecture.py', manifest: true, planned: false, lens: 'architecture' },
|
|
37
37
|
|
|
38
38
|
{ name: 'check_custom', labels: ['check_custom', 'check_custom.js', 'check_custom.py'],
|
|
39
39
|
checks: 'User-defined regex rules from `.clc-forge.yml`', verify: ['verify_custom_rules', 'verify_user_defined_regex_rules'],
|
|
40
40
|
applies_when: 'always', severity: 'error', gate_mode: 'hard', config_keys: ['scope', 'exclude_paths'],
|
|
41
|
-
phase: 'audit', file: 'check_custom.js+check_custom.py', manifest: true, planned: false },
|
|
41
|
+
phase: 'audit', file: 'check_custom.js+check_custom.py', manifest: true, planned: false, lens: 'architecture' },
|
|
42
42
|
|
|
43
43
|
{ name: 'check_a11y', labels: ['check_a11y', 'check_a11y.js'],
|
|
44
44
|
checks: 'ARIA + alt accessibility', verify: ['verify_a11y', 'verify_accessibility'],
|
|
45
45
|
applies_when: 'frontend', severity: 'error', gate_mode: 'hard', config_keys: ['scope', 'exclude_paths'],
|
|
46
|
-
phase: 'audit', file: 'check_a11y.js', manifest: true, planned: false },
|
|
46
|
+
phase: 'audit', file: 'check_a11y.js', manifest: true, planned: false, lens: 'correctness' },
|
|
47
47
|
|
|
48
48
|
{ name: 'check_ui_reuse', labels: ['check_ui_reuse', 'check_ui_reuse.js'],
|
|
49
49
|
checks: 'UI primitive reuse from components/ui/', verify: ['verify_ui_reuse', 'verify_component_reuse'],
|
|
50
50
|
applies_when: 'frontend', severity: 'warning', gate_mode: 'advisory', config_keys: ['scope', 'exclude_paths'],
|
|
51
|
-
phase: 'audit', file: 'check_ui_reuse.js', manifest: true, planned: false },
|
|
51
|
+
phase: 'audit', file: 'check_ui_reuse.js', manifest: true, planned: false, lens: 'architecture' },
|
|
52
52
|
|
|
53
53
|
{ name: 'check_performance', labels: ['check_performance', 'check_performance.js'],
|
|
54
54
|
checks: 'next/image + tree-shaking', verify: ['verify_performance', 'verify_image_optimization'],
|
|
55
55
|
applies_when: 'frontend', severity: 'warning', gate_mode: 'advisory', config_keys: ['scope', 'exclude_paths'],
|
|
56
|
-
phase: 'audit', file: 'check_performance.js', manifest: true, planned: false },
|
|
56
|
+
phase: 'audit', file: 'check_performance.js', manifest: true, planned: false, lens: 'performance' },
|
|
57
57
|
|
|
58
58
|
{ name: 'check_responsive', labels: ['check_responsive', 'check_responsive.js'],
|
|
59
59
|
checks: 'Mobile-first responsiveness, fixed-width overflow & touch target size', verify: ['verify_responsive', 'verify_mobile_adaptability'],
|
|
60
60
|
applies_when: 'frontend', severity: 'warning', gate_mode: 'advisory', config_keys: ['scope', 'exclude_paths'],
|
|
61
|
-
phase: 'audit', file: 'check_responsive.js', manifest: true, planned: false },
|
|
61
|
+
phase: 'audit', file: 'check_responsive.js', manifest: true, planned: false, lens: 'performance' },
|
|
62
62
|
|
|
63
63
|
{ name: 'check_seo', labels: ['check_seo', 'check_seo.js'],
|
|
64
64
|
checks: 'SEO metadata, OpenGraph, GEO tags, semantic HTML & LCP priority', verify: ['verify_seo', 'verify_geo_metadata', 'verify_core_web_vitals'],
|
|
65
65
|
applies_when: 'frontend', severity: 'warning', gate_mode: 'advisory', config_keys: ['scope', 'exclude_paths'],
|
|
66
|
-
phase: 'audit', file: 'check_seo.js', manifest: true, planned: false },
|
|
66
|
+
phase: 'audit', file: 'check_seo.js', manifest: true, planned: false, lens: 'performance' },
|
|
67
67
|
|
|
68
68
|
{ name: 'check_storybook', labels: ['check_storybook', 'check_storybook.js'],
|
|
69
69
|
checks: 'Storybook coverage (`.stories.tsx`)', verify: ['verify_storybook_coverage'],
|
|
70
70
|
applies_when: 'frontend', severity: 'warning', gate_mode: 'advisory', config_keys: ['scope', 'exclude_paths'],
|
|
71
|
-
phase: 'audit', file: 'check_storybook.js', manifest: true, planned: false },
|
|
71
|
+
phase: 'audit', file: 'check_storybook.js', manifest: true, planned: false, lens: 'performance' },
|
|
72
72
|
|
|
73
73
|
{ name: 'check_api_contracts', labels: ['check_api_contracts', 'check_api_contracts.js'],
|
|
74
74
|
checks: 'Zod response-schema parsing', verify: ['verify_api_contracts', 'verify_response_schemas'],
|
|
75
75
|
applies_when: 'frontend', severity: 'error', gate_mode: 'hard', config_keys: ['scope', 'exclude_paths'],
|
|
76
|
-
phase: 'audit', file: 'check_api_contracts.js', manifest: true, planned: false },
|
|
76
|
+
phase: 'audit', file: 'check_api_contracts.js', manifest: true, planned: false, lens: 'correctness' },
|
|
77
77
|
|
|
78
78
|
{ name: 'check_migrations', labels: ['check_migrations', 'check_migrations.py'],
|
|
79
79
|
checks: 'Alembic/Django migration idempotency', verify: ['verify_migration_idempotency'],
|
|
80
80
|
applies_when: 'backend,fastapi,django', severity: 'error', gate_mode: 'hard', config_keys: ['scope', 'exclude_paths'],
|
|
81
|
-
phase: 'audit', file: 'check_migrations.py', manifest: true, planned: false },
|
|
81
|
+
phase: 'audit', file: 'check_migrations.py', manifest: true, planned: false, lens: 'architecture' },
|
|
82
82
|
|
|
83
83
|
{ name: 'verify_tdd', labels: ['verify_tdd', 'verify_tdd.py'],
|
|
84
84
|
checks: 'Real red→green TDD validation', verify: ['verify_tdd_red_green', 'verify_tdd_evidence'],
|
|
85
85
|
applies_when: 'backend', severity: 'error', gate_mode: 'hard', config_keys: ['scope', 'exclude_paths'],
|
|
86
|
-
phase: 'audit', file: 'verify_tdd.py', manifest: false, planned: false },
|
|
86
|
+
phase: 'audit', file: 'verify_tdd.py', manifest: false, planned: false, lens: 'correctness' },
|
|
87
87
|
|
|
88
88
|
{ name: 'check_scope', labels: ['check_scope', 'check_scope.py'],
|
|
89
89
|
checks: 'git-diff vs SDD-authorization scope guardrail', verify: ['verify_scope_authorization', 'verify_sdd_scope'],
|
|
90
90
|
applies_when: 'always', severity: 'error', gate_mode: 'hard', config_keys: ['scope', 'exclude_paths'],
|
|
91
|
-
phase: 'audit', file: 'check_scope.py', manifest: false, planned: false },
|
|
91
|
+
phase: 'audit', file: 'check_scope.py', manifest: false, planned: false, lens: 'correctness' },
|
|
92
92
|
|
|
93
93
|
{ name: 'celery_guard', labels: ['celery_guard', 'celery_guard.py'],
|
|
94
94
|
checks: 'Broker URL exposure, ignore_result, result backend', verify: ['verify_celery_broker_config'],
|
|
95
95
|
applies_when: 'celery', severity: 'error', gate_mode: 'hard', config_keys: ['scope', 'exclude_paths'],
|
|
96
|
-
phase: 'audit', file: 'celery_guard.py', manifest: true, planned: false },
|
|
96
|
+
phase: 'audit', file: 'celery_guard.py', manifest: true, planned: false, lens: 'security' },
|
|
97
97
|
|
|
98
98
|
{ name: 'redis_guard', labels: ['redis_guard', 'redis_guard.py'],
|
|
99
99
|
checks: 'decode_responses, connect timeout, hardcoded URLs', verify: ['verify_redis_config'],
|
|
100
100
|
applies_when: 'redis', severity: 'error', gate_mode: 'hard', config_keys: ['scope', 'exclude_paths'],
|
|
101
|
-
phase: 'audit', file: 'redis_guard.py', manifest: true, planned: false },
|
|
101
|
+
phase: 'audit', file: 'redis_guard.py', manifest: true, planned: false, lens: 'security' },
|
|
102
102
|
|
|
103
103
|
{ name: 'postgres_guard', labels: ['postgres_guard', 'postgres_guard.py'],
|
|
104
104
|
checks: 'Raw SQL string formatting, hstore misuse', verify: ['verify_postgres_sql_usage'],
|
|
105
105
|
applies_when: 'postgresql', severity: 'error', gate_mode: 'hard', config_keys: ['scope', 'exclude_paths'],
|
|
106
|
-
phase: 'audit', file: 'postgres_guard.py', manifest: true, planned: false },
|
|
106
|
+
phase: 'audit', file: 'postgres_guard.py', manifest: true, planned: false, lens: 'security' },
|
|
107
107
|
|
|
108
108
|
{ name: 'docker_guard', labels: ['docker_guard', 'docker_guard.py'],
|
|
109
109
|
checks: 'Non-root USER, ENV secrets, missing HEALTHCHECK', verify: ['verify_dockerfile_security'],
|
|
110
110
|
applies_when: 'docker', severity: 'error', gate_mode: 'hard', config_keys: ['scope', 'exclude_paths'],
|
|
111
|
-
phase: 'audit', file: 'docker_guard.py', manifest: true, planned: false },
|
|
111
|
+
phase: 'audit', file: 'docker_guard.py', manifest: true, planned: false, lens: 'security' },
|
|
112
112
|
|
|
113
113
|
{ name: 'check_symlinks', labels: ['check_symlinks', 'check_symlinks.js'],
|
|
114
114
|
checks: 'AI IDE symlinks and rule mirror verification', verify: ['verify_symlinks', 'verify_rule_mirrors'],
|
|
115
115
|
applies_when: 'agent-os', severity: 'error', gate_mode: 'hard', config_keys: ['scope'],
|
|
116
|
-
phase: 'audit', file: 'check_symlinks.js', manifest: true, planned: false },
|
|
116
|
+
phase: 'audit', file: 'check_symlinks.js', manifest: true, planned: false, lens: 'architecture' },
|
|
117
117
|
|
|
118
118
|
{ name: 'check_hitl', labels: ['check_hitl', 'check_hitl.js'],
|
|
119
119
|
checks: 'Human-in-the-Loop review checkpoint validator in subagent skills', verify: ['verify_hitl', 'verify_human_review_checkpoints'],
|
|
120
120
|
applies_when: 'agent-os', severity: 'error', gate_mode: 'hard', config_keys: ['scope'],
|
|
121
|
-
phase: 'audit', file: 'check_hitl.js', manifest: true, planned: false },
|
|
121
|
+
phase: 'audit', file: 'check_hitl.js', manifest: true, planned: false, lens: 'architecture' },
|
|
122
122
|
|
|
123
123
|
{ name: 'check_domain_data', labels: ['check_domain_data', 'check_domain_data.js'],
|
|
124
124
|
checks: 'Domain deliverables schema and clean state integrity', verify: ['verify_domain_data', 'verify_clean_state_metrics'],
|
|
125
125
|
applies_when: 'agent-os', severity: 'error', gate_mode: 'hard', config_keys: ['scope'],
|
|
126
|
-
phase: 'audit', file: 'check_domain_data.js', manifest: true, planned: false },
|
|
126
|
+
phase: 'audit', file: 'check_domain_data.js', manifest: true, planned: false, lens: 'architecture' },
|
|
127
127
|
|
|
128
128
|
{ name: 'check_test_calendar_sync', labels: ['check_test_calendar_sync', 'check_test_calendar_sync.js'],
|
|
129
129
|
checks: 'Test calendar harness synchronization and parity check', verify: ['verify_calendar_sync', 'verify_test_calendar_sync'],
|
|
130
130
|
applies_when: 'agent-os', severity: 'error', gate_mode: 'hard', config_keys: ['scope'],
|
|
131
|
-
phase: 'audit', file: 'check_test_calendar_sync.js', manifest: true, planned: false },
|
|
131
|
+
phase: 'audit', file: 'check_test_calendar_sync.js', manifest: true, planned: false, lens: 'performance' },
|
|
132
132
|
|
|
133
133
|
{ name: 'ruff', labels: ['ruff'],
|
|
134
134
|
checks: 'Python AST linter (external CLI)', verify: ['verify_python_lint'],
|
|
135
135
|
applies_when: 'backend', severity: 'warning', gate_mode: 'advisory', config_keys: [],
|
|
136
|
-
phase: 'test', file: null, manifest: true, planned: false },
|
|
136
|
+
phase: 'test', file: null, manifest: true, planned: false, lens: 'correctness' },
|
|
137
137
|
|
|
138
138
|
{ name: 'pytest', labels: ['pytest'],
|
|
139
139
|
checks: 'Test runner (external CLI)', verify: ['verify_test_coverage'],
|
|
140
140
|
applies_when: 'backend', severity: 'warning', gate_mode: 'advisory', config_keys: [],
|
|
141
|
-
phase: 'test', file: null, manifest: true, planned: false },
|
|
141
|
+
phase: 'test', file: null, manifest: true, planned: false, lens: 'correctness' },
|
|
142
142
|
|
|
143
143
|
{ name: 'tsc', labels: ['tsc'],
|
|
144
144
|
checks: 'TypeScript typecheck (external)', verify: ['verify_typescript_types'],
|
|
145
145
|
applies_when: 'frontend', severity: 'error', gate_mode: 'hard', config_keys: [],
|
|
146
|
-
phase: 'test', file: null, manifest: true, planned: false },
|
|
146
|
+
phase: 'test', file: null, manifest: true, planned: false, lens: 'correctness' },
|
|
147
147
|
|
|
148
148
|
{ name: 'lint', labels: ['lint', 'lint-staged'],
|
|
149
149
|
checks: 'lint-staged (external)', verify: ['verify_lint_staged'],
|
|
150
150
|
applies_when: 'frontend', severity: 'warning', gate_mode: 'advisory', config_keys: [],
|
|
151
|
-
phase: 'test', file: null, manifest: true, planned: false },
|
|
151
|
+
phase: 'test', file: null, manifest: true, planned: false, lens: 'correctness' },
|
|
152
152
|
|
|
153
153
|
{ name: 'graphify', labels: ['graphify'],
|
|
154
154
|
checks: 'Memory graph (external, graceful-skip)', verify: ['verify_memory_graph'],
|
|
155
155
|
applies_when: 'always', severity: 'warning', gate_mode: 'advisory', config_keys: [],
|
|
156
|
-
phase: 'review', file: null, manifest: false, planned: false },
|
|
156
|
+
phase: 'review', file: null, manifest: false, planned: false, lens: 'architecture' },
|
|
157
157
|
|
|
158
158
|
{ name: 'gentle_ai_review', labels: ['gentle_ai_review', 'gentle-ai'],
|
|
159
159
|
checks: 'Native bounded review gate (external, graceful-skip)', verify: ['verify_rdd_review_gate'],
|
|
160
160
|
applies_when: 'always', severity: 'error', gate_mode: 'hard', config_keys: [],
|
|
161
|
-
phase: 'review', file: null, manifest: false, planned: false },
|
|
161
|
+
phase: 'review', file: null, manifest: false, planned: false, lens: 'architecture' },
|
|
162
162
|
|
|
163
163
|
{ name: 'check_openapi_drift', labels: ['check_openapi_drift', 'check_openapi_drift.py'],
|
|
164
164
|
checks: 'FastAPI OpenAPI schema drift vs app routes', verify: ['verify_openapi_drift'],
|
|
165
165
|
applies_when: 'fastapi', severity: 'error', gate_mode: 'hard', config_keys: ['scope', 'exclude_paths'],
|
|
166
|
-
phase: 'audit', file: 'check_openapi_drift.py', manifest: true, planned: true },
|
|
166
|
+
phase: 'audit', file: 'check_openapi_drift.py', manifest: true, planned: true, lens: 'correctness' },
|
|
167
167
|
|
|
168
168
|
{ name: 'check_db_efficiency', labels: ['check_db_efficiency', 'check_db_efficiency.py'],
|
|
169
169
|
checks: 'ORM N+1 / select_related / prefetch_related', verify: ['verify_db_efficiency', 'verify_no_n_plus_1'],
|
|
170
170
|
applies_when: 'django|sqlalchemy', severity: 'error', gate_mode: 'hard', config_keys: ['scope', 'exclude_paths'],
|
|
171
|
-
phase: 'audit', file: 'check_db_efficiency.py', manifest: true, planned: false },
|
|
171
|
+
phase: 'audit', file: 'check_db_efficiency.py', manifest: true, planned: false, lens: 'performance' },
|
|
172
172
|
|
|
173
173
|
{ name: 'check_observability', labels: ['check_observability', 'check_observability.py'],
|
|
174
174
|
checks: 'structlog structured logging; bare print/logger ban', verify: ['verify_structured_logging'],
|
|
175
175
|
applies_when: 'backend', severity: 'warning', gate_mode: 'advisory', config_keys: ['scope', 'exclude_paths'],
|
|
176
|
-
phase: 'audit', file: 'check_observability.py', manifest: true, planned: true },
|
|
176
|
+
phase: 'audit', file: 'check_observability.py', manifest: true, planned: true, lens: 'performance' },
|
|
177
177
|
];
|
|
178
178
|
|
|
179
179
|
// Freeze the registry so config validation and generation can rely on it.
|
|
@@ -195,6 +195,9 @@ const VALID_APPLIES_WHEN = [
|
|
|
195
195
|
'agent-os',
|
|
196
196
|
];
|
|
197
197
|
|
|
198
|
+
/** Canonical 4 RDD Lenses */
|
|
199
|
+
const VALID_LENSES = ['correctness', 'security', 'architecture', 'performance'];
|
|
200
|
+
|
|
198
201
|
/** Normalize an input label for catalog lookup. */
|
|
199
202
|
function normalizeLabel(name) {
|
|
200
203
|
return typeof name === 'string' ? name.trim().toLowerCase() : '';
|
|
@@ -226,13 +229,21 @@ function gateFor(name) {
|
|
|
226
229
|
return entry ? entry.gate_mode : null;
|
|
227
230
|
}
|
|
228
231
|
|
|
232
|
+
/** Canonical RDD review lens for a guard name/label; null when unknown. */
|
|
233
|
+
function lensFor(name) {
|
|
234
|
+
const entry = applyTo(name);
|
|
235
|
+
return entry ? entry.lens : null;
|
|
236
|
+
}
|
|
237
|
+
|
|
229
238
|
module.exports = {
|
|
230
239
|
SAFEGUARD_CATALOG,
|
|
231
240
|
VALID_SEVERITIES,
|
|
232
241
|
VALID_GATE_MODES,
|
|
233
242
|
VALID_PHASES,
|
|
234
243
|
VALID_APPLIES_WHEN,
|
|
244
|
+
VALID_LENSES,
|
|
235
245
|
applyTo,
|
|
236
246
|
severityFor,
|
|
237
247
|
gateFor,
|
|
248
|
+
lensFor,
|
|
238
249
|
};
|
package/src/generator.js
CHANGED
|
@@ -330,12 +330,8 @@ function generateHarness(targetDir, configOrNull) {
|
|
|
330
330
|
fs.mkdirSync(path.join(targetDir, 'sdds'), { recursive: true });
|
|
331
331
|
fs.mkdirSync(path.join(targetDir, 'tools'), { recursive: true });
|
|
332
332
|
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
if (!gitignoreContent.includes('sdds/*')) {
|
|
336
|
-
gitignoreContent += '\n\n# Local Spec-Driven Development (SDD) files\nsdds/*\n!sdds/.gitkeep\nopenspec/*\n!openspec/.gitkeep\n.ruff_cache/\n';
|
|
337
|
-
fs.writeFileSync(gitignorePath, gitignoreContent, 'utf-8');
|
|
338
|
-
}
|
|
333
|
+
const { ensureGitignoreEntries } = require('./gitignore');
|
|
334
|
+
ensureGitignoreEntries(targetDir);
|
|
339
335
|
fs.writeFileSync(path.join(targetDir, 'sdds', '.gitkeep'), '', 'utf-8');
|
|
340
336
|
|
|
341
337
|
const agentsContent = `# CLC Kernel ${config.framework || 'Generic'} (${(config.projectType || 'unknown').toUpperCase()}) — AGENTS
|
package/src/gitignore.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLC Kernel — Gitignore Provisioning Engine
|
|
3
|
+
* Ensures AI IDE mirror symlinks, local SDD specs, and ephemeral runtime artifacts
|
|
4
|
+
* are safely ignored in consumer repositories to preserve AGENTS.md as the SSOT.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const MANAGED_GITIGNORE_SECTIONS = [
|
|
11
|
+
{
|
|
12
|
+
header: '# CLC Kernel: Local AI IDE Mirror Symlinks (SSOT: AGENTS.md)',
|
|
13
|
+
entries: [
|
|
14
|
+
'CLAUDE.md',
|
|
15
|
+
'GEMINI.md',
|
|
16
|
+
'.cursorrules',
|
|
17
|
+
'.windsurfrules',
|
|
18
|
+
'.cursor/',
|
|
19
|
+
'.antigravity/',
|
|
20
|
+
'.github/copilot-instructions.md',
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
header: '# CLC Kernel: CLI Agent Skills & Playbooks',
|
|
25
|
+
entries: [
|
|
26
|
+
'.agents/skills/clckernel_cli/',
|
|
27
|
+
'.agents/skills/clc_forge_cli/',
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
header: '# CLC Kernel: Local SDD Specs & Ephemeral Harness Artifacts',
|
|
32
|
+
entries: [
|
|
33
|
+
'sdds/*',
|
|
34
|
+
'!sdds/.gitkeep',
|
|
35
|
+
'openspec/*',
|
|
36
|
+
'!openspec/.gitkeep',
|
|
37
|
+
'.clckernel.resolved.json',
|
|
38
|
+
'.clc-forge.resolved.json',
|
|
39
|
+
'.husky/_/',
|
|
40
|
+
'.ruff_cache/',
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Idempotently appends missing CLC Kernel entries to target project's .gitignore.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} targetDir - Target project root
|
|
49
|
+
* @returns {boolean} True if .gitignore was created or modified, false if already complete.
|
|
50
|
+
*/
|
|
51
|
+
function ensureGitignoreEntries(targetDir) {
|
|
52
|
+
const gitignorePath = path.join(targetDir, '.gitignore');
|
|
53
|
+
let currentContent = '';
|
|
54
|
+
if (fs.existsSync(gitignorePath)) {
|
|
55
|
+
try {
|
|
56
|
+
currentContent = fs.readFileSync(gitignorePath, 'utf-8');
|
|
57
|
+
} catch (e) {
|
|
58
|
+
currentContent = '';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const existingLines = new Set(
|
|
63
|
+
currentContent
|
|
64
|
+
.split('\n')
|
|
65
|
+
.map(line => line.trim())
|
|
66
|
+
.filter(line => line.length > 0)
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const sectionsToAppend = [];
|
|
70
|
+
|
|
71
|
+
for (const section of MANAGED_GITIGNORE_SECTIONS) {
|
|
72
|
+
const missingEntries = section.entries.filter(entry => !existingLines.has(entry.trim()));
|
|
73
|
+
if (missingEntries.length > 0) {
|
|
74
|
+
sectionsToAppend.push(`${section.header}\n${missingEntries.join('\n')}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (sectionsToAppend.length === 0) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let newContent = currentContent;
|
|
83
|
+
if (newContent.length > 0 && !newContent.endsWith('\n')) {
|
|
84
|
+
newContent += '\n';
|
|
85
|
+
}
|
|
86
|
+
if (newContent.length > 0) {
|
|
87
|
+
newContent += '\n';
|
|
88
|
+
}
|
|
89
|
+
newContent += sectionsToAppend.join('\n\n') + '\n';
|
|
90
|
+
|
|
91
|
+
fs.writeFileSync(gitignorePath, newContent, 'utf-8');
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = {
|
|
96
|
+
ensureGitignoreEntries,
|
|
97
|
+
MANAGED_GITIGNORE_SECTIONS,
|
|
98
|
+
};
|
package/src/updater.js
CHANGED
|
@@ -32,6 +32,8 @@ function updateHarness(targetDir = process.cwd(), options = {}) {
|
|
|
32
32
|
|
|
33
33
|
// 3. Run harness generation (refreshes tools, hooks, skills, and symlinks)
|
|
34
34
|
generateHarness(resolvedDir, detected);
|
|
35
|
+
const { ensureGitignoreEntries } = require('./gitignore');
|
|
36
|
+
ensureGitignoreEntries(resolvedDir);
|
|
35
37
|
|
|
36
38
|
// 4. Ensure AGENTS.md documents the update command in its command table.
|
|
37
39
|
// Non-destructive: only inject into a file that already carries the CLC
|
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,
|
|
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
|
|
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, '
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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('> **
|
|
131
|
-
sections.push('>
|
|
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
|
-
|
|
148
|
-
|
|
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
|
|
291
|
+
console.log(' CLC KERNEL — 4-LENS RDD AUDIT SUMMARY');
|
|
162
292
|
console.log('=================================================================\n');
|
|
163
293
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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(
|
|
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
|
|
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
|
|
331
|
+
console.log('\nRunning CLC Kernel audit...\n');
|
|
182
332
|
|
|
183
|
-
const
|
|
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
|
-
|
|
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(
|
|
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("> **
|
|
165
|
-
sections.append(">
|
|
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
|
-
|
|
182
|
-
|
|
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
|
|
306
|
+
print(" CLC KERNEL — 4-LENS RDD AUDIT SUMMARY")
|
|
196
307
|
print("=" * 65 + "\n")
|
|
197
308
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
|
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(
|
|
399
|
+
sys.exit(1)
|