clckernel 1.2.6
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/AGENTS.md +124 -0
- package/README.es.md +166 -0
- package/README.md +167 -0
- package/bin/cli.js +12 -0
- package/docs/Journal/001-adr-clckernel-governance.md +18 -0
- package/package.json +44 -0
- package/src/adapters/astro.js +67 -0
- package/src/adapters/base.js +109 -0
- package/src/adapters/django.js +70 -0
- package/src/adapters/fastapi.js +69 -0
- package/src/adapters/go.js +50 -0
- package/src/adapters/laravel.js +52 -0
- package/src/adapters/nextjs.js +72 -0
- package/src/adapters/rails.js +54 -0
- package/src/adapters/rust.js +51 -0
- package/src/catalog.js +217 -0
- package/src/config.js +250 -0
- package/src/detector.js +85 -0
- package/src/doctor.js +125 -0
- package/src/generator.js +473 -0
- package/src/index.js +52 -0
- package/src/technologies/detector.js +243 -0
- package/src/technologies/guards/_shared.js +66 -0
- package/src/ui.js +101 -0
- package/src/yaml.js +173 -0
- package/test/adapters/astro.test.js +102 -0
- package/test/adapters/base.test.js +86 -0
- package/test/adapters/django.test.js +95 -0
- package/test/adapters/fastapi.test.js +88 -0
- package/test/adapters/go.test.js +81 -0
- package/test/adapters/laravel.test.js +106 -0
- package/test/adapters/nextjs.test.js +113 -0
- package/test/adapters/rails.test.js +108 -0
- package/test/adapters/rust.test.js +83 -0
- package/test/catalog.test.js +170 -0
- package/test/config.test.js +512 -0
- package/test/detector.test.js +345 -0
- package/test/doctor.test.js +302 -0
- package/test/generator.test.js +419 -0
- package/test/guards_new.test.js +95 -0
- package/test/helpers.js +36 -0
- package/test/integration/cli-flow.test.js +270 -0
- package/test/technologies/celery_guard.test.js +185 -0
- package/test/technologies/detector.test.js +342 -0
- package/test/technologies/docker_guard.test.js +187 -0
- package/test/technologies/integration.test.js +123 -0
- package/test/technologies/postgres_guard.test.js +87 -0
- package/test/technologies/redis_guard.test.js +128 -0
- package/tools/audit.js +219 -0
- package/tools/audit.py +273 -0
- package/tools/celery_guard.py +209 -0
- package/tools/check_a11y.js +109 -0
- package/tools/check_api_contracts.js +139 -0
- package/tools/check_architecture.js +139 -0
- package/tools/check_architecture.py +264 -0
- package/tools/check_custom.js +163 -0
- package/tools/check_custom.py +395 -0
- package/tools/check_db_efficiency.py +190 -0
- package/tools/check_migrations.py +223 -0
- package/tools/check_performance.js +142 -0
- package/tools/check_responsive.js +131 -0
- package/tools/check_scope.py +180 -0
- package/tools/check_seo.js +139 -0
- package/tools/check_storybook.js +108 -0
- package/tools/check_ui_reuse.js +135 -0
- package/tools/docker_guard.py +210 -0
- package/tools/postgres_guard.py +192 -0
- package/tools/redis_guard.py +197 -0
- package/tools/scan_secrets.js +109 -0
- package/tools/scan_secrets.py +153 -0
- package/tools/verify_tdd.py +212 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const { describe, it } = require('node:test');
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const { createTmpProject, teardown } = require('../helpers');
|
|
4
|
+
|
|
5
|
+
describe('RustAdapter', () => {
|
|
6
|
+
const RustAdapter = require('../../src/adapters/rust');
|
|
7
|
+
|
|
8
|
+
describe('constructor', () => {
|
|
9
|
+
it('creates adapter with correct name and projectType', () => {
|
|
10
|
+
const adapter = new RustAdapter();
|
|
11
|
+
assert.strictEqual(adapter.name, 'Rust Engine');
|
|
12
|
+
assert.strictEqual(adapter.projectType, 'backend');
|
|
13
|
+
assert.strictEqual(adapter.testRunner, 'cargo test');
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('detect()', () => {
|
|
18
|
+
it('returns true when Cargo.toml exists', () => {
|
|
19
|
+
const dir = createTmpProject({
|
|
20
|
+
'Cargo.toml': '[package]\nname = "myapp"\nversion = "0.1.0"\nedition = "2021"',
|
|
21
|
+
});
|
|
22
|
+
try {
|
|
23
|
+
const adapter = new RustAdapter();
|
|
24
|
+
assert.strictEqual(adapter.detect(dir), true);
|
|
25
|
+
} finally {
|
|
26
|
+
teardown(dir);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('returns false when Cargo.toml does not exist', () => {
|
|
31
|
+
const dir = createTmpProject({});
|
|
32
|
+
try {
|
|
33
|
+
const adapter = new RustAdapter();
|
|
34
|
+
assert.strictEqual(adapter.detect(dir), false);
|
|
35
|
+
} finally {
|
|
36
|
+
teardown(dir);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('getTools()', () => {
|
|
42
|
+
it('returns array of tool objects with required fields', () => {
|
|
43
|
+
const adapter = new RustAdapter();
|
|
44
|
+
const tools = adapter.getTools();
|
|
45
|
+
assert.ok(Array.isArray(tools));
|
|
46
|
+
assert.ok(tools.length > 0);
|
|
47
|
+
for (const tool of tools) {
|
|
48
|
+
assert.ok(tool.name);
|
|
49
|
+
assert.ok(tool.language);
|
|
50
|
+
assert.ok(typeof tool.command === 'string');
|
|
51
|
+
assert.ok(tool.description);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('all external tools have language=external and empty path', () => {
|
|
56
|
+
const adapter = new RustAdapter();
|
|
57
|
+
const tools = adapter.getTools();
|
|
58
|
+
const externalTools = tools.filter((t) => t.language === 'external');
|
|
59
|
+
for (const tool of externalTools) {
|
|
60
|
+
assert.strictEqual(tool.language, 'external');
|
|
61
|
+
assert.strictEqual(tool.path, '');
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('includes custom rules guard', () => {
|
|
66
|
+
const adapter = new RustAdapter();
|
|
67
|
+
const tools = adapter.getTools();
|
|
68
|
+
const customTool = tools.find((t) => t.name === 'check_custom');
|
|
69
|
+
assert.ok(customTool, 'check_custom tool should exist');
|
|
70
|
+
assert.strictEqual(customTool.language, 'js');
|
|
71
|
+
assert.strictEqual(customTool.path, 'check_custom.js');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('getSafeguards()', () => {
|
|
76
|
+
it('returns non-empty array of strings', () => {
|
|
77
|
+
const adapter = new RustAdapter();
|
|
78
|
+
const guards = adapter.getSafeguards();
|
|
79
|
+
assert.ok(Array.isArray(guards));
|
|
80
|
+
assert.ok(guards.length >= 5);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLC Forge — Catalog tests for the central safeguard registry (src/catalog.js).
|
|
3
|
+
* Covers: 24-entry completeness (cross-checked against design §1.2 and the
|
|
4
|
+
* actual tools/ directory), full shape validation, guard-file correlation,
|
|
5
|
+
* uniqueness, and the applyTo/severityFor/gateFor lookup helpers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { describe, it } = require('node:test');
|
|
9
|
+
const assert = require('node:assert');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
const {
|
|
14
|
+
SAFEGUARD_CATALOG,
|
|
15
|
+
VALID_SEVERITIES,
|
|
16
|
+
VALID_GATE_MODES,
|
|
17
|
+
VALID_PHASES,
|
|
18
|
+
VALID_APPLIES_WHEN,
|
|
19
|
+
applyTo,
|
|
20
|
+
severityFor,
|
|
21
|
+
gateFor,
|
|
22
|
+
} = require('../src/catalog.js');
|
|
23
|
+
|
|
24
|
+
const TOOLS_DIR = path.join(__dirname, '..', 'tools');
|
|
25
|
+
|
|
26
|
+
function guardFileExists(file) {
|
|
27
|
+
if (!file) return false;
|
|
28
|
+
return file.split('+').every(variant => fs.existsSync(path.join(TOOLS_DIR, variant)));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('SAFEGUARD_CATALOG', () => {
|
|
32
|
+
it('exposes exactly 26 entries, one per safeguard the framework knows about', () => {
|
|
33
|
+
assert.strictEqual(SAFEGUARD_CATALOG.length, 26, `expected 26, got ${SAFEGUARD_CATALOG.length}`);
|
|
34
|
+
const names = SAFEGUARD_CATALOG.map(e => e.name);
|
|
35
|
+
assert.deepStrictEqual(names, [
|
|
36
|
+
'scan_secrets', 'check_architecture', 'check_custom', 'check_a11y',
|
|
37
|
+
'check_ui_reuse', 'check_performance', 'check_responsive', 'check_seo', 'check_storybook', 'check_api_contracts',
|
|
38
|
+
'check_migrations', 'verify_tdd', 'check_scope', 'celery_guard', 'redis_guard',
|
|
39
|
+
'postgres_guard', 'docker_guard', 'ruff', 'pytest', 'tsc', 'lint', 'graphify',
|
|
40
|
+
'gentle_ai_review', 'check_openapi_drift', 'check_db_efficiency', 'check_observability',
|
|
41
|
+
]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('has unique names and no duplicate alias labels', () => {
|
|
45
|
+
const names = SAFEGUARD_CATALOG.map(e => e.name);
|
|
46
|
+
assert.strictEqual(new Set(names).size, names.length, 'names must be unique');
|
|
47
|
+
const allLabels = SAFEGUARD_CATALOG.flatMap(e => e.labels);
|
|
48
|
+
assert.strictEqual(new Set(allLabels).size, allLabels.length, 'labels must be globally unique');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('validates every required field on every entry (shape loop)', () => {
|
|
52
|
+
const required = ['name', 'labels', 'checks', 'verify', 'applies_when', 'severity', 'gate_mode', 'config_keys', 'phase', 'file', 'manifest', 'planned'];
|
|
53
|
+
for (const entry of SAFEGUARD_CATALOG) {
|
|
54
|
+
for (const key of required) {
|
|
55
|
+
assert.ok(key in entry, `${entry.name} is missing required field "${key}"`);
|
|
56
|
+
}
|
|
57
|
+
assert.ok(typeof entry.name === 'string' && entry.name.length > 0, `${entry.name}: name`);
|
|
58
|
+
assert.ok(Array.isArray(entry.labels) && entry.labels.length > 0, `${entry.name}: labels`);
|
|
59
|
+
assert.ok(entry.labels.includes(entry.name), `${entry.name}: labels must include the name`);
|
|
60
|
+
assert.ok(typeof entry.checks === 'string' && entry.checks.length > 0, `${entry.name}: checks`);
|
|
61
|
+
assert.ok(Array.isArray(entry.verify) && entry.verify.length > 0, `${entry.name}: verify`);
|
|
62
|
+
assert.ok(VALID_APPLIES_WHEN.includes(entry.applies_when), `${entry.name}: applies_when "${entry.applies_when}"`);
|
|
63
|
+
assert.ok(VALID_SEVERITIES.includes(entry.severity), `${entry.name}: severity "${entry.severity}"`);
|
|
64
|
+
assert.ok(VALID_GATE_MODES.includes(entry.gate_mode), `${entry.name}: gate_mode "${entry.gate_mode}"`);
|
|
65
|
+
assert.ok(Array.isArray(entry.config_keys) && entry.config_keys.every(k => typeof k === 'string'), `${entry.name}: config_keys`);
|
|
66
|
+
assert.ok(VALID_PHASES.includes(entry.phase), `${entry.name}: phase "${entry.phase}"`);
|
|
67
|
+
assert.ok(entry.file === null || typeof entry.file === 'string', `${entry.name}: file`);
|
|
68
|
+
assert.strictEqual(typeof entry.manifest, 'boolean', `${entry.name}: manifest`);
|
|
69
|
+
assert.strictEqual(typeof entry.planned, 'boolean', `${entry.name}: planned`);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('every verify token references a plausible verify_* audit goal', () => {
|
|
74
|
+
for (const entry of SAFEGUARD_CATALOG) {
|
|
75
|
+
for (const token of entry.verify) {
|
|
76
|
+
assert.match(token, /^verify_[a-z0-9_]+$/, `${entry.name}: verify token "${token}"`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('every non-null file exists in bundled tools/ unless the entry is planned', () => {
|
|
82
|
+
for (const entry of SAFEGUARD_CATALOG) {
|
|
83
|
+
if (entry.file === null) continue;
|
|
84
|
+
const exists = guardFileExists(entry.file);
|
|
85
|
+
if (entry.planned) {
|
|
86
|
+
assert.ok(!exists, `${entry.name}: planned file "${entry.file}" should NOT exist yet`);
|
|
87
|
+
} else {
|
|
88
|
+
assert.ok(exists, `${entry.name}: file "${entry.file}" missing from tools/`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('flags exactly the 2 Slice C guards as planned', () => {
|
|
94
|
+
const planned = SAFEGUARD_CATALOG.filter(e => e.planned).map(e => e.name);
|
|
95
|
+
assert.deepStrictEqual(planned, ['check_openapi_drift', 'check_observability']);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('keeps design defaults for the new observability guard (warning/advisory)', () => {
|
|
99
|
+
const observability = applyTo('check_observability');
|
|
100
|
+
assert.strictEqual(observability.severity, 'warning');
|
|
101
|
+
assert.strictEqual(observability.gate_mode, 'advisory');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('maps stack-specific guards to their applicability predicates', () => {
|
|
105
|
+
assert.strictEqual(applyTo('redis_guard').applies_when, 'redis');
|
|
106
|
+
assert.strictEqual(applyTo('postgres_guard').applies_when, 'postgresql');
|
|
107
|
+
assert.strictEqual(applyTo('docker_guard').applies_when, 'docker');
|
|
108
|
+
assert.strictEqual(applyTo('celery_guard').applies_when, 'celery');
|
|
109
|
+
assert.strictEqual(applyTo('check_openapi_drift').applies_when, 'fastapi');
|
|
110
|
+
assert.strictEqual(applyTo('check_db_efficiency').applies_when, 'django|sqlalchemy');
|
|
111
|
+
assert.strictEqual(applyTo('check_migrations').applies_when, 'backend,fastapi,django');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('external CLI entries have file null and contribute no tool files', () => {
|
|
115
|
+
for (const name of ['ruff', 'pytest', 'tsc', 'lint', 'graphify', 'gentle_ai_review']) {
|
|
116
|
+
const entry = applyTo(name);
|
|
117
|
+
assert.strictEqual(entry.file, null, `${name}: external row must have file null`);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('applyTo', () => {
|
|
123
|
+
it('resolves the entry by exact name', () => {
|
|
124
|
+
assert.strictEqual(applyTo('check_custom').name, 'check_custom');
|
|
125
|
+
assert.strictEqual(applyTo('redis_guard').name, 'redis_guard');
|
|
126
|
+
assert.strictEqual(applyTo('check_observability').name, 'check_observability');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('resolves alias labels (js/py variants and CLI aliases)', () => {
|
|
130
|
+
assert.strictEqual(applyTo('check_custom.js').name, 'check_custom');
|
|
131
|
+
assert.strictEqual(applyTo('check_custom.py').name, 'check_custom');
|
|
132
|
+
assert.strictEqual(applyTo('scan_secrets.py').name, 'scan_secrets');
|
|
133
|
+
assert.strictEqual(applyTo('lint-staged').name, 'lint');
|
|
134
|
+
assert.strictEqual(applyTo('gentle-ai').name, 'gentle_ai_review');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('normalizes case and whitespace', () => {
|
|
138
|
+
assert.strictEqual(applyTo(' Check_Custom ').name, 'check_custom');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('returns null for unknown names (no throw)', () => {
|
|
142
|
+
assert.strictEqual(applyTo('check_white'), null);
|
|
143
|
+
assert.strictEqual(applyTo('check_black'), null);
|
|
144
|
+
assert.strictEqual(applyTo('check_memory'), null);
|
|
145
|
+
assert.strictEqual(applyTo('check_audit'), null);
|
|
146
|
+
assert.strictEqual(applyTo('nonexistent_guard'), null);
|
|
147
|
+
assert.strictEqual(applyTo(''), null);
|
|
148
|
+
assert.strictEqual(applyTo(undefined), null);
|
|
149
|
+
assert.strictEqual(applyTo(null), null);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('severityFor / gateFor', () => {
|
|
154
|
+
it('resolves catalog defaults for representative entries', () => {
|
|
155
|
+
assert.strictEqual(severityFor('check_custom'), 'error');
|
|
156
|
+
assert.strictEqual(severityFor('check_observability'), 'warning');
|
|
157
|
+
assert.strictEqual(severityFor('redis_guard'), 'error');
|
|
158
|
+
assert.strictEqual(severityFor('check_ui_reuse'), 'warning');
|
|
159
|
+
assert.strictEqual(gateFor('check_custom'), 'hard');
|
|
160
|
+
assert.strictEqual(gateFor('check_observability'), 'advisory');
|
|
161
|
+
assert.strictEqual(gateFor('redis_guard'), 'hard');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('returns null for unknown names (no throw)', () => {
|
|
165
|
+
assert.strictEqual(severityFor('check_black'), null);
|
|
166
|
+
assert.strictEqual(gateFor('check_black'), null);
|
|
167
|
+
assert.strictEqual(severityFor(undefined), null);
|
|
168
|
+
assert.strictEqual(gateFor(''), null);
|
|
169
|
+
});
|
|
170
|
+
});
|