clckernel 1.2.6 → 1.2.8

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.
Files changed (45) hide show
  1. package/README.es.md +51 -118
  2. package/README.md +48 -116
  3. package/package.json +14 -2
  4. package/src/adapters/agent-os.js +183 -0
  5. package/src/catalog.js +16 -0
  6. package/src/detector.js +2 -0
  7. package/src/index.js +11 -0
  8. package/src/ui.js +79 -35
  9. package/tools/audit.js +0 -0
  10. package/tools/audit.py +0 -0
  11. package/tools/check_custom.js +0 -0
  12. package/tools/check_custom.py +0 -0
  13. package/tools/check_db_efficiency.py +0 -0
  14. package/tools/check_domain_data.js +100 -0
  15. package/tools/check_hitl.js +93 -0
  16. package/tools/check_responsive.js +0 -0
  17. package/tools/check_seo.js +1 -1
  18. package/tools/check_symlinks.js +76 -0
  19. package/tools/scan_secrets.js +12 -1
  20. package/tools/scan_secrets.py +0 -0
  21. package/AGENTS.md +0 -124
  22. package/docs/Journal/001-adr-clckernel-governance.md +0 -18
  23. package/test/adapters/astro.test.js +0 -102
  24. package/test/adapters/base.test.js +0 -86
  25. package/test/adapters/django.test.js +0 -95
  26. package/test/adapters/fastapi.test.js +0 -88
  27. package/test/adapters/go.test.js +0 -81
  28. package/test/adapters/laravel.test.js +0 -106
  29. package/test/adapters/nextjs.test.js +0 -113
  30. package/test/adapters/rails.test.js +0 -108
  31. package/test/adapters/rust.test.js +0 -83
  32. package/test/catalog.test.js +0 -170
  33. package/test/config.test.js +0 -512
  34. package/test/detector.test.js +0 -345
  35. package/test/doctor.test.js +0 -302
  36. package/test/generator.test.js +0 -419
  37. package/test/guards_new.test.js +0 -95
  38. package/test/helpers.js +0 -36
  39. package/test/integration/cli-flow.test.js +0 -270
  40. package/test/technologies/celery_guard.test.js +0 -185
  41. package/test/technologies/detector.test.js +0 -342
  42. package/test/technologies/docker_guard.test.js +0 -187
  43. package/test/technologies/integration.test.js +0 -123
  44. package/test/technologies/postgres_guard.test.js +0 -87
  45. package/test/technologies/redis_guard.test.js +0 -128
@@ -1,81 +0,0 @@
1
- const { describe, it } = require('node:test');
2
- const assert = require('node:assert');
3
- const { createTmpProject, teardown } = require('../helpers');
4
-
5
- describe('GoAdapter', () => {
6
- const GoAdapter = require('../../src/adapters/go');
7
-
8
- describe('constructor', () => {
9
- it('creates adapter with correct name and projectType', () => {
10
- const adapter = new GoAdapter();
11
- assert.strictEqual(adapter.name, 'Go (Golang)');
12
- assert.strictEqual(adapter.projectType, 'backend');
13
- assert.strictEqual(adapter.testRunner, 'go test');
14
- });
15
- });
16
-
17
- describe('detect()', () => {
18
- it('returns true when go.mod exists', () => {
19
- const dir = createTmpProject({ 'go.mod': 'module example.com/myapp\ngo 1.21' });
20
- try {
21
- const adapter = new GoAdapter();
22
- assert.strictEqual(adapter.detect(dir), true);
23
- } finally {
24
- teardown(dir);
25
- }
26
- });
27
-
28
- it('returns false when go.mod does not exist', () => {
29
- const dir = createTmpProject({});
30
- try {
31
- const adapter = new GoAdapter();
32
- assert.strictEqual(adapter.detect(dir), false);
33
- } finally {
34
- teardown(dir);
35
- }
36
- });
37
- });
38
-
39
- describe('getTools()', () => {
40
- it('returns array of tool objects with required fields', () => {
41
- const adapter = new GoAdapter();
42
- const tools = adapter.getTools();
43
- assert.ok(Array.isArray(tools));
44
- assert.ok(tools.length > 0);
45
- for (const tool of tools) {
46
- assert.ok(tool.name);
47
- assert.ok(tool.language);
48
- assert.ok(typeof tool.command === 'string');
49
- assert.ok(tool.description);
50
- }
51
- });
52
-
53
- it('all external tools have language=external and empty path', () => {
54
- const adapter = new GoAdapter();
55
- const tools = adapter.getTools();
56
- const externalTools = tools.filter((t) => t.language === 'external');
57
- for (const tool of externalTools) {
58
- assert.strictEqual(tool.language, 'external');
59
- assert.strictEqual(tool.path, '');
60
- }
61
- });
62
-
63
- it('includes custom rules guard', () => {
64
- const adapter = new GoAdapter();
65
- const tools = adapter.getTools();
66
- const customTool = tools.find((t) => t.name === 'check_custom');
67
- assert.ok(customTool, 'check_custom tool should exist');
68
- assert.strictEqual(customTool.language, 'js');
69
- assert.strictEqual(customTool.path, 'check_custom.js');
70
- });
71
- });
72
-
73
- describe('getSafeguards()', () => {
74
- it('returns non-empty array of strings', () => {
75
- const adapter = new GoAdapter();
76
- const guards = adapter.getSafeguards();
77
- assert.ok(Array.isArray(guards));
78
- assert.ok(guards.length >= 5);
79
- });
80
- });
81
- });
@@ -1,106 +0,0 @@
1
- const { describe, it } = require('node:test');
2
- const assert = require('node:assert');
3
- const { createTmpProject, teardown } = require('../helpers');
4
-
5
- describe('LaravelAdapter', () => {
6
- const LaravelAdapter = require('../../src/adapters/laravel');
7
-
8
- describe('constructor', () => {
9
- it('creates adapter with correct name and projectType', () => {
10
- const adapter = new LaravelAdapter();
11
- assert.strictEqual(adapter.name, 'PHP / Laravel');
12
- assert.strictEqual(adapter.projectType, 'backend');
13
- assert.strictEqual(adapter.testRunner, 'Pest / PHPUnit');
14
- });
15
- });
16
-
17
- describe('detect()', () => {
18
- it('returns true when both composer.json and artisan exist', () => {
19
- const dir = createTmpProject({
20
- 'composer.json': JSON.stringify({ require: { 'laravel/framework': '^10.0' } }),
21
- 'artisan': '#!/usr/bin/env php',
22
- });
23
- try {
24
- const adapter = new LaravelAdapter();
25
- assert.strictEqual(adapter.detect(dir), true);
26
- } finally {
27
- teardown(dir);
28
- }
29
- });
30
-
31
- it('returns false when only composer.json exists', () => {
32
- const dir = createTmpProject({
33
- 'composer.json': JSON.stringify({ require: {} }),
34
- });
35
- try {
36
- const adapter = new LaravelAdapter();
37
- assert.strictEqual(adapter.detect(dir), false);
38
- } finally {
39
- teardown(dir);
40
- }
41
- });
42
-
43
- it('returns false when only artisan exists', () => {
44
- const dir = createTmpProject({ 'artisan': '#!/usr/bin/env php' });
45
- try {
46
- const adapter = new LaravelAdapter();
47
- assert.strictEqual(adapter.detect(dir), false);
48
- } finally {
49
- teardown(dir);
50
- }
51
- });
52
-
53
- it('returns false when directory is empty', () => {
54
- const dir = createTmpProject({});
55
- try {
56
- const adapter = new LaravelAdapter();
57
- assert.strictEqual(adapter.detect(dir), false);
58
- } finally {
59
- teardown(dir);
60
- }
61
- });
62
- });
63
-
64
- describe('getTools()', () => {
65
- it('returns array of tool objects with required fields', () => {
66
- const adapter = new LaravelAdapter();
67
- const tools = adapter.getTools();
68
- assert.ok(Array.isArray(tools));
69
- assert.ok(tools.length > 0);
70
- for (const tool of tools) {
71
- assert.ok(tool.name);
72
- assert.ok(tool.language);
73
- assert.ok(typeof tool.command === 'string');
74
- assert.ok(tool.description);
75
- }
76
- });
77
-
78
- it('all external tools have language=external and empty path', () => {
79
- const adapter = new LaravelAdapter();
80
- const tools = adapter.getTools();
81
- const externalTools = tools.filter((t) => t.language === 'external');
82
- for (const tool of externalTools) {
83
- assert.strictEqual(tool.language, 'external');
84
- assert.strictEqual(tool.path, '');
85
- }
86
- });
87
-
88
- it('includes custom rules guard', () => {
89
- const adapter = new LaravelAdapter();
90
- const tools = adapter.getTools();
91
- const customTool = tools.find((t) => t.name === 'check_custom');
92
- assert.ok(customTool, 'check_custom tool should exist');
93
- assert.strictEqual(customTool.language, 'python');
94
- assert.strictEqual(customTool.path, 'check_custom.py');
95
- });
96
- });
97
-
98
- describe('getSafeguards()', () => {
99
- it('returns non-empty array of strings', () => {
100
- const adapter = new LaravelAdapter();
101
- const guards = adapter.getSafeguards();
102
- assert.ok(Array.isArray(guards));
103
- assert.ok(guards.length >= 5);
104
- });
105
- });
106
- });
@@ -1,113 +0,0 @@
1
- const { describe, it } = require('node:test');
2
- const assert = require('node:assert');
3
- const { createTmpProject, teardown } = require('../helpers');
4
-
5
- describe('NextjsAdapter', () => {
6
- const NextjsAdapter = require('../../src/adapters/nextjs');
7
-
8
- describe('constructor', () => {
9
- it('creates adapter with correct name and projectType', () => {
10
- const adapter = new NextjsAdapter();
11
- assert.strictEqual(adapter.name, 'Next.js (App Router)');
12
- assert.strictEqual(adapter.projectType, 'frontend');
13
- assert.strictEqual(adapter.testRunner, 'Vitest / Jest');
14
- });
15
- });
16
-
17
- describe('detect()', () => {
18
- it('returns true when package.json has next dependency', () => {
19
- const dir = createTmpProject({
20
- 'package.json': JSON.stringify({ dependencies: { next: '14.0.0' } }),
21
- });
22
- try {
23
- const adapter = new NextjsAdapter();
24
- assert.strictEqual(adapter.detect(dir), true);
25
- } finally {
26
- teardown(dir);
27
- }
28
- });
29
-
30
- it('returns true when next is in devDependencies', () => {
31
- const dir = createTmpProject({
32
- 'package.json': JSON.stringify({ devDependencies: { next: '14.0.0' } }),
33
- });
34
- try {
35
- const adapter = new NextjsAdapter();
36
- assert.strictEqual(adapter.detect(dir), true);
37
- } finally {
38
- teardown(dir);
39
- }
40
- });
41
-
42
- it('returns false when package.json has no next dependency', () => {
43
- const dir = createTmpProject({
44
- 'package.json': JSON.stringify({ dependencies: { react: '18.0.0' } }),
45
- });
46
- try {
47
- const adapter = new NextjsAdapter();
48
- assert.strictEqual(adapter.detect(dir), false);
49
- } finally {
50
- teardown(dir);
51
- }
52
- });
53
-
54
- it('returns false when no package.json exists', () => {
55
- const dir = createTmpProject({});
56
- try {
57
- const adapter = new NextjsAdapter();
58
- assert.strictEqual(adapter.detect(dir), false);
59
- } finally {
60
- teardown(dir);
61
- }
62
- });
63
-
64
- it('returns false when package.json is malformed JSON', () => {
65
- const dir = createTmpProject({
66
- 'package.json': '{invalid json',
67
- });
68
- try {
69
- const adapter = new NextjsAdapter();
70
- assert.strictEqual(adapter.detect(dir), false);
71
- } finally {
72
- teardown(dir);
73
- }
74
- });
75
- });
76
-
77
- describe('getTools()', () => {
78
- it('returns array of tool objects with required fields', () => {
79
- const adapter = new NextjsAdapter();
80
- const tools = adapter.getTools();
81
- assert.ok(Array.isArray(tools));
82
- assert.ok(tools.length > 0);
83
- for (const tool of tools) {
84
- assert.ok(tool.name, 'tool must have name');
85
- assert.ok(tool.language, 'tool must have language');
86
- assert.ok(typeof tool.command === 'string', 'tool must have command string');
87
- assert.ok(tool.description, 'tool must have description');
88
- }
89
- });
90
-
91
- it('external tools have language=external and empty path', () => {
92
- const adapter = new NextjsAdapter();
93
- const tools = adapter.getTools();
94
- const external = tools.filter((t) => t.language === 'external');
95
- assert.ok(external.length > 0, 'should have external tools');
96
- for (const tool of external) {
97
- assert.strictEqual(tool.path, '');
98
- }
99
- });
100
- });
101
-
102
- describe('getSafeguards()', () => {
103
- it('returns non-empty array of strings', () => {
104
- const adapter = new NextjsAdapter();
105
- const guards = adapter.getSafeguards();
106
- assert.ok(Array.isArray(guards));
107
- assert.ok(guards.length >= 5, 'should have at least 5 safeguards');
108
- for (const g of guards) {
109
- assert.strictEqual(typeof g, 'string');
110
- }
111
- });
112
- });
113
- });
@@ -1,108 +0,0 @@
1
- const { describe, it } = require('node:test');
2
- const assert = require('node:assert');
3
- const { createTmpProject, teardown } = require('../helpers');
4
-
5
- describe('RailsAdapter', () => {
6
- const RailsAdapter = require('../../src/adapters/rails');
7
-
8
- describe('constructor', () => {
9
- it('creates adapter with correct name and projectType', () => {
10
- const adapter = new RailsAdapter();
11
- assert.strictEqual(adapter.name, 'Ruby on Rails');
12
- assert.strictEqual(adapter.projectType, 'backend');
13
- assert.strictEqual(adapter.testRunner, 'RSpec / Minitest');
14
- });
15
- });
16
-
17
- describe('detect()', () => {
18
- it('returns true when Gemfile and config/routes.rb exist', () => {
19
- const dir = createTmpProject({
20
- 'Gemfile': "source 'https://rubygems.org'\ngem 'rails', '~> 7.0'",
21
- 'config/routes.rb': 'Rails.application.routes.draw do end',
22
- });
23
- try {
24
- const adapter = new RailsAdapter();
25
- assert.strictEqual(adapter.detect(dir), true);
26
- } finally {
27
- teardown(dir);
28
- }
29
- });
30
-
31
- it('returns false when only Gemfile exists (no routes.rb)', () => {
32
- const dir = createTmpProject({
33
- 'Gemfile': "source 'https://rubygems.org'",
34
- });
35
- try {
36
- const adapter = new RailsAdapter();
37
- assert.strictEqual(adapter.detect(dir), false);
38
- } finally {
39
- teardown(dir);
40
- }
41
- });
42
-
43
- it('returns false when only routes.rb exists (no Gemfile)', () => {
44
- const dir = createTmpProject({
45
- 'config/routes.rb': 'Rails.application.routes.draw do end',
46
- });
47
- try {
48
- const adapter = new RailsAdapter();
49
- assert.strictEqual(adapter.detect(dir), false);
50
- } finally {
51
- teardown(dir);
52
- }
53
- });
54
-
55
- it('returns false when directory is empty', () => {
56
- const dir = createTmpProject({});
57
- try {
58
- const adapter = new RailsAdapter();
59
- assert.strictEqual(adapter.detect(dir), false);
60
- } finally {
61
- teardown(dir);
62
- }
63
- });
64
- });
65
-
66
- describe('getTools()', () => {
67
- it('returns array of tool objects with required fields', () => {
68
- const adapter = new RailsAdapter();
69
- const tools = adapter.getTools();
70
- assert.ok(Array.isArray(tools));
71
- assert.ok(tools.length > 0);
72
- for (const tool of tools) {
73
- assert.ok(tool.name);
74
- assert.ok(tool.language);
75
- assert.ok(typeof tool.command === 'string');
76
- assert.ok(tool.description);
77
- }
78
- });
79
-
80
- it('all external tools have language=external and empty path', () => {
81
- const adapter = new RailsAdapter();
82
- const tools = adapter.getTools();
83
- const externalTools = tools.filter((t) => t.language === 'external');
84
- for (const tool of externalTools) {
85
- assert.strictEqual(tool.language, 'external');
86
- assert.strictEqual(tool.path, '');
87
- }
88
- });
89
-
90
- it('includes custom rules guard', () => {
91
- const adapter = new RailsAdapter();
92
- const tools = adapter.getTools();
93
- const customTool = tools.find((t) => t.name === 'check_custom');
94
- assert.ok(customTool, 'check_custom tool should exist');
95
- assert.strictEqual(customTool.language, 'js');
96
- assert.strictEqual(customTool.path, 'check_custom.js');
97
- });
98
- });
99
-
100
- describe('getSafeguards()', () => {
101
- it('returns non-empty array of strings', () => {
102
- const adapter = new RailsAdapter();
103
- const guards = adapter.getSafeguards();
104
- assert.ok(Array.isArray(guards));
105
- assert.ok(guards.length >= 5);
106
- });
107
- });
108
- });
@@ -1,83 +0,0 @@
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
- });
@@ -1,170 +0,0 @@
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
- });