clckernel 1.2.19 → 2.0.0
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/skills/clckernel_cli/SKILL.md +23 -6
- package/.agents/skills/clckernel_cli/reference/audit.md +13 -3
- package/.agents/skills/clckernel_cli/reference/commit.md +1 -1
- package/.agents/skills/clckernel_cli/reference/doctor.md +5 -3
- package/.agents/skills/clckernel_cli/reference/feature.md +3 -3
- package/.agents/skills/clckernel_cli/reference/guard.md +48 -12
- package/.agents/skills/clckernel_cli/reference/phase.md +6 -3
- package/.agents/skills/clckernel_cli/reference/start.md +14 -4
- package/.agents/skills/clckernel_cli/reference/test.md +1 -1
- package/.agents/skills/clckernel_cli/reference/update.md +19 -19
- package/CHANGELOG.md +244 -0
- package/LICENSE +21 -0
- package/README.es.md +280 -51
- package/README.md +274 -50
- package/bin/cli.js +8 -0
- package/package.json +12 -2
- package/src/adapters/agent-os.js +161 -84
- package/src/adapters/astro.js +8 -6
- package/src/adapters/base.js +23 -38
- package/src/adapters/django.js +14 -13
- package/src/adapters/fastapi.js +16 -15
- package/src/adapters/go.js +11 -9
- package/src/adapters/laravel.js +12 -10
- package/src/adapters/nextjs.js +7 -5
- package/src/adapters/rails.js +13 -11
- package/src/adapters/rust.js +11 -9
- package/src/catalog.js +58 -49
- package/src/config.js +38 -240
- package/src/detector.js +243 -44
- package/src/doctor.js +18 -9
- package/src/generator.js +734 -302
- package/src/gitignore.js +60 -14
- package/src/index.js +257 -18
- package/src/io.js +409 -0
- package/src/known-hashes.json +309 -0
- package/src/onboarding.js +15 -0
- package/src/technologies/detector.js +1 -1
- package/src/technologies/guards/_shared.js +1 -1
- package/src/ui.js +233 -40
- package/src/updater.js +271 -38
- package/tools/_changes.js +279 -0
- package/tools/_changes.py +233 -0
- package/tools/_config.js +294 -0
- package/tools/_config.py +271 -0
- package/tools/_yaml.py +489 -0
- package/tools/audit.js +511 -111
- package/tools/audit.py +506 -128
- package/tools/celery_guard.py +13 -35
- package/tools/check_a11y.js +8 -28
- package/tools/check_api_contracts.js +13 -39
- package/tools/check_architecture.js +35 -44
- package/tools/check_architecture.py +172 -105
- package/tools/check_custom.js +108 -61
- package/tools/check_custom.py +108 -271
- package/tools/check_data_files.js +88 -0
- package/tools/check_db_efficiency.py +80 -65
- package/tools/check_hitl.js +112 -69
- package/tools/check_links.js +138 -0
- package/tools/check_migrations.py +143 -94
- package/tools/check_performance.js +35 -44
- package/tools/check_placeholders.js +104 -0
- package/tools/check_responsive.js +8 -28
- package/tools/check_scope.py +40 -31
- package/tools/check_seo.js +8 -28
- package/tools/check_skills.js +158 -0
- package/tools/check_storybook.js +8 -21
- package/tools/check_symlinks.js +40 -22
- package/tools/check_ui_reuse.js +17 -29
- package/tools/docker_guard.py +19 -41
- package/tools/postgres_guard.py +15 -36
- package/tools/redis_guard.py +13 -35
- package/tools/scan_secrets.js +134 -67
- package/tools/scan_secrets.py +163 -72
- package/tools/verify_tdd.js +867 -0
- package/tools/verify_tdd.py +407 -165
- package/tools/yaml.js +404 -132
- package/tools/check_domain_data.js +0 -100
- package/tools/check_test_calendar_sync.js +0 -111
package/src/adapters/agent-os.js
CHANGED
|
@@ -1,77 +1,126 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLC Kernel — Agent-OS & Domain Workflows Stack Adapter
|
|
3
|
-
* Supports
|
|
4
|
-
*
|
|
3
|
+
* Supports agent operating systems: repositories of skills, playbooks, rules,
|
|
4
|
+
* prompts, templates and data that AI agents run to carry out tasks.
|
|
5
|
+
* The kernel holds no business-domain rules: the guards below hold for any
|
|
6
|
+
* Agent-OS, and domain checks live in the project (tools/guards/), which the
|
|
7
|
+
* pre-commit hook and the audit run like the kernel's own guards.
|
|
5
8
|
*/
|
|
6
9
|
|
|
7
10
|
const fs = require('fs');
|
|
8
11
|
const path = require('path');
|
|
9
12
|
const BaseAdapter = require('./base');
|
|
13
|
+
const { currentIO, registeredPaths } = require('../io');
|
|
14
|
+
const { FIRST_SESSION_SECTION } = require('../onboarding');
|
|
15
|
+
|
|
16
|
+
/** Skills the harness installs itself: never a detection signal. */
|
|
17
|
+
const HARNESS_SKILLS = new Set(['clckernel_cli', 'clc_forge_cli']);
|
|
18
|
+
const SKILL_ROOTS = ['skills', '.agents/skills', '.claude/skills'];
|
|
19
|
+
const MARKDOWN_SEARCH_DEPTH = 3;
|
|
20
|
+
|
|
21
|
+
/** Folders that make up an Agent-OS, described in AGENTS.md when present. */
|
|
22
|
+
const LAYER_FOLDERS = [
|
|
23
|
+
{ folder: 'skills', skills: true, text: 'one folder per skill with a `SKILL.md` whose frontmatter holds a unique lowercase `name` and a `description`; the agent loads a skill when its description matches the task.' },
|
|
24
|
+
{ folder: '.agents/skills', skills: true, text: 'skills in the shared agent layout (same `SKILL.md` contract as above).' },
|
|
25
|
+
{ folder: '.claude/skills', skills: true, text: 'skills in the Claude Code layout (same `SKILL.md` contract as above).' },
|
|
26
|
+
{ folder: 'playbooks', text: 'step-by-step procedures the agent runs for an operator; each declares its human checkpoint.' },
|
|
27
|
+
{ folder: 'rules', text: 'standing constraints (style, tone, policy) every run must respect.' },
|
|
28
|
+
{ folder: 'prompts', text: 'reusable prompt texts the skills and playbooks reference.' },
|
|
29
|
+
{ folder: 'templates', text: 'output skeletons; double-brace slots are allowed only here.' },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
/** Paths listed in the harness registry (working copy, either format); empty when absent or malformed. */
|
|
33
|
+
function managedPaths(targetDir) {
|
|
34
|
+
return new Set(registeredPaths(targetDir));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** True when root/<name>/SKILL.md exists for a skill the harness did not install. */
|
|
38
|
+
function hasUserSkill(targetDir, root, managed = managedPaths(targetDir)) {
|
|
39
|
+
let entries;
|
|
40
|
+
try {
|
|
41
|
+
entries = fs.readdirSync(path.join(targetDir, root), { withFileTypes: true });
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
return entries.some(entry => {
|
|
46
|
+
if (!entry.isDirectory() || HARNESS_SKILLS.has(entry.name)) return false;
|
|
47
|
+
const rel = `${root}/${entry.name}/SKILL.md`;
|
|
48
|
+
return !managed.has(rel) && fs.existsSync(path.join(targetDir, rel));
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** True when dir holds a Markdown file within `depth` levels. */
|
|
53
|
+
function hasMarkdown(dir, depth = MARKDOWN_SEARCH_DEPTH) {
|
|
54
|
+
let entries;
|
|
55
|
+
try {
|
|
56
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return entries.some(entry => (entry.isFile() && entry.name.endsWith('.md'))
|
|
61
|
+
|| (entry.isDirectory() && depth > 1 && hasMarkdown(path.join(dir, entry.name), depth - 1)));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Layer folders present in targetDir, in LAYER_FOLDERS order. */
|
|
65
|
+
function presentLayers(targetDir) {
|
|
66
|
+
const managed = managedPaths(targetDir);
|
|
67
|
+
return LAYER_FOLDERS.filter(layer => (layer.skills
|
|
68
|
+
? hasUserSkill(targetDir, layer.folder, managed)
|
|
69
|
+
: fs.existsSync(path.join(targetDir, layer.folder))));
|
|
70
|
+
}
|
|
10
71
|
|
|
11
72
|
class AgentOSAdapter extends BaseAdapter {
|
|
12
73
|
constructor() {
|
|
13
74
|
super('Agent-OS (Domain & AI Workflows)', 'agent-os', 'Playbook / Contract Test');
|
|
14
75
|
}
|
|
15
76
|
|
|
77
|
+
/**
|
|
78
|
+
* An agent operating system: a SKILL.md in skills/<name>/, .agents/skills/<name>/
|
|
79
|
+
* or .claude/skills/<name>/ (skills the harness installs excluded), a
|
|
80
|
+
* playbooks/ or rules/ folder with Markdown, or a generic package.json
|
|
81
|
+
* signal (`agent-os` in the name, description or keywords, or the
|
|
82
|
+
* `ai-agents` keyword). A code stack that also matches wins (see detector).
|
|
83
|
+
*/
|
|
16
84
|
detect(targetDir) {
|
|
17
|
-
// 1. package.json check
|
|
18
85
|
const pkgPath = path.join(targetDir, 'package.json');
|
|
19
86
|
if (fs.existsSync(pkgPath)) {
|
|
20
87
|
try {
|
|
21
88
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
22
89
|
const name = (pkg.name || '').toLowerCase();
|
|
23
90
|
const desc = (pkg.description || '').toLowerCase();
|
|
24
|
-
const keywords = Array.isArray(pkg.keywords) ? pkg.keywords.map(k => k.toLowerCase()) : [];
|
|
91
|
+
const keywords = Array.isArray(pkg.keywords) ? pkg.keywords.map(k => String(k).toLowerCase()) : [];
|
|
25
92
|
if (
|
|
26
|
-
name.includes('agent-os') ||
|
|
27
|
-
|
|
28
|
-
keywords.includes('agent-os') || keywords.includes('marketing-os') || keywords.includes('ai-agents')
|
|
93
|
+
name.includes('agent-os') || desc.includes('agent-os') ||
|
|
94
|
+
keywords.includes('agent-os') || keywords.includes('ai-agents')
|
|
29
95
|
) {
|
|
30
96
|
return true;
|
|
31
97
|
}
|
|
32
98
|
} catch {}
|
|
33
99
|
}
|
|
34
100
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (fs.existsSync(skillsDir) || fs.existsSync(agentSkillsDir)) {
|
|
39
|
-
const dirToCheck = fs.existsSync(skillsDir) ? skillsDir : agentSkillsDir;
|
|
40
|
-
try {
|
|
41
|
-
const files = fs.readdirSync(dirToCheck);
|
|
42
|
-
if (files.some(f => f.endsWith('.md'))) {
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
|
-
} catch {}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// 3. brand/ or templates/ check alongside README referencing agents/workflows
|
|
49
|
-
const brandPath = path.join(targetDir, 'brand');
|
|
50
|
-
const templatesPath = path.join(targetDir, 'templates');
|
|
51
|
-
const readmePath = path.join(targetDir, 'README.md');
|
|
52
|
-
if ((fs.existsSync(brandPath) || fs.existsSync(templatesPath)) && fs.existsSync(readmePath)) {
|
|
53
|
-
try {
|
|
54
|
-
const readme = fs.readFileSync(readmePath, 'utf-8').toLowerCase();
|
|
55
|
-
if (readme.includes('subagent') || readme.includes('operating system') || readme.includes('agent')) {
|
|
56
|
-
return true;
|
|
57
|
-
}
|
|
58
|
-
} catch {}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return false;
|
|
101
|
+
const managed = managedPaths(targetDir);
|
|
102
|
+
if (SKILL_ROOTS.some(root => hasUserSkill(targetDir, root, managed))) return true;
|
|
103
|
+
return ['playbooks', 'rules'].some(folder => hasMarkdown(path.join(targetDir, folder)));
|
|
62
104
|
}
|
|
63
105
|
|
|
64
106
|
getSafeguards() {
|
|
65
107
|
return [
|
|
66
108
|
'Dual-Layer Architecture: Layer 1 (Engineering) & Layer 2 (Domain Runtime)',
|
|
67
|
-
'
|
|
68
|
-
'
|
|
69
|
-
'
|
|
70
|
-
'
|
|
71
|
-
'
|
|
109
|
+
'Human-in-the-Loop (HITL) checkpoints: every changed skill and playbook declares a `HITL:` line or a `## Human checkpoint` section (check_hitl)',
|
|
110
|
+
'Skill frontmatter: unique lowercase `name` and a `description` of at most 1024 characters in every changed SKILL.md (check_skills)',
|
|
111
|
+
'Relative Markdown links resolve to files in the commit (check_links)',
|
|
112
|
+
'No forgotten draft placeholders in changed Markdown, YAML and JSON (check_placeholders)',
|
|
113
|
+
'Changed JSON data files parse (check_data_files)',
|
|
114
|
+
'Rule mirrors (CLAUDE.md, GEMINI.md, .cursorrules, .windsurfrules) point to AGENTS.md (check_symlinks)',
|
|
115
|
+
'Project rules from .clckernel.yaml (check_custom)',
|
|
116
|
+
'Secret Leak Guard (scan_secrets)',
|
|
117
|
+
'Single Source of Truth (SSOT) memory protocol (agent-enforced; not checked by a guard)',
|
|
118
|
+
'Local-only Spec Driven Development (sdds/)',
|
|
119
|
+
'Red→Green TDD Verifier (runs changed tests with vitest / jest / node:test)'
|
|
72
120
|
];
|
|
73
121
|
}
|
|
74
122
|
|
|
123
|
+
/** Guards for any Agent-OS; domain checks belong in the project's tools/guards/. */
|
|
75
124
|
getTools() {
|
|
76
125
|
return [
|
|
77
126
|
{
|
|
@@ -79,28 +128,42 @@ class AgentOSAdapter extends BaseAdapter {
|
|
|
79
128
|
language: 'js',
|
|
80
129
|
path: 'check_symlinks.js',
|
|
81
130
|
command: 'node tools/check_symlinks.js',
|
|
82
|
-
description: 'AI IDE
|
|
131
|
+
description: 'AI IDE rule mirrors point to AGENTS.md (symlink or identical copy)'
|
|
83
132
|
},
|
|
84
133
|
{
|
|
85
134
|
name: 'check_hitl',
|
|
86
135
|
language: 'js',
|
|
87
136
|
path: 'check_hitl.js',
|
|
88
137
|
command: 'node tools/check_hitl.js',
|
|
89
|
-
description: 'Human-in-the-Loop
|
|
138
|
+
description: 'Explicit Human-in-the-Loop checkpoint marker in changed skills and playbooks'
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: 'check_skills',
|
|
142
|
+
language: 'js',
|
|
143
|
+
path: 'check_skills.js',
|
|
144
|
+
command: 'node tools/check_skills.js',
|
|
145
|
+
description: 'SKILL.md frontmatter: name and description present, well-formed and unique'
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: 'check_links',
|
|
149
|
+
language: 'js',
|
|
150
|
+
path: 'check_links.js',
|
|
151
|
+
command: 'node tools/check_links.js',
|
|
152
|
+
description: 'Relative links in changed Markdown point to files in the commit'
|
|
90
153
|
},
|
|
91
154
|
{
|
|
92
|
-
name: '
|
|
155
|
+
name: 'check_placeholders',
|
|
93
156
|
language: 'js',
|
|
94
|
-
path: '
|
|
95
|
-
command: 'node tools/
|
|
96
|
-
description: '
|
|
157
|
+
path: 'check_placeholders.js',
|
|
158
|
+
command: 'node tools/check_placeholders.js',
|
|
159
|
+
description: 'Forgotten draft placeholders in changed Markdown, YAML and JSON (templates/ may keep slots)'
|
|
97
160
|
},
|
|
98
161
|
{
|
|
99
|
-
name: '
|
|
162
|
+
name: 'check_data_files',
|
|
100
163
|
language: 'js',
|
|
101
|
-
path: '
|
|
102
|
-
command: 'node tools/
|
|
103
|
-
description: '
|
|
164
|
+
path: 'check_data_files.js',
|
|
165
|
+
command: 'node tools/check_data_files.js',
|
|
166
|
+
description: 'Every changed .json file parses (snapshot content, any folder)'
|
|
104
167
|
},
|
|
105
168
|
{
|
|
106
169
|
name: 'scan_secrets',
|
|
@@ -108,6 +171,20 @@ class AgentOSAdapter extends BaseAdapter {
|
|
|
108
171
|
path: 'scan_secrets.js',
|
|
109
172
|
command: 'node tools/scan_secrets.js',
|
|
110
173
|
description: 'Security & API key leak scanner'
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: 'verify_tdd',
|
|
177
|
+
language: 'js',
|
|
178
|
+
path: 'verify_tdd.js',
|
|
179
|
+
command: 'node tools/verify_tdd.js',
|
|
180
|
+
description: 'Runs changed tests (vitest / jest / node:test) against BASE (must fail) and the staged code (must pass)'
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: 'check_custom',
|
|
184
|
+
language: 'js',
|
|
185
|
+
path: 'check_custom.js',
|
|
186
|
+
command: 'node tools/check_custom.js',
|
|
187
|
+
description: 'Project rules from .clckernel.yaml (e.g. sections every playbook must have)'
|
|
111
188
|
}
|
|
112
189
|
];
|
|
113
190
|
}
|
|
@@ -119,7 +196,11 @@ class AgentOSAdapter extends BaseAdapter {
|
|
|
119
196
|
// would make the guard below skip the dual-layer render entirely.
|
|
120
197
|
super.provision(targetDir, { ...(config || {}), skipAgentsMd: true });
|
|
121
198
|
|
|
122
|
-
// 2. Write Dual-Layer AGENTS.md
|
|
199
|
+
// 2. Write Dual-Layer AGENTS.md, describing only the folders that exist.
|
|
200
|
+
const layers = presentLayers(targetDir);
|
|
201
|
+
const layerMap = layers.length > 0
|
|
202
|
+
? layers.map(layer => `- \`${layer.folder}/\` — ${layer.text}`).join('\n')
|
|
203
|
+
: '- No agent folders yet. Add skills, playbooks, rules, prompts or templates folders and describe them here.';
|
|
123
204
|
const dualLayerAgents = `# Standard Operating Procedure (SOP) — AGENTS
|
|
124
205
|
Governed by **CLC Kernel: The Universal AI Agent Governance Engine**.
|
|
125
206
|
Target System: **${this.name}**
|
|
@@ -129,7 +210,7 @@ Target System: **${this.name}**
|
|
|
129
210
|
## 🛠️ Layer 1: Development & Maintenance Governance (Engineering)
|
|
130
211
|
All AI Agents (Antigravity, OpenCode, Claude Code, Cursor, Windsurf, Copilot) developing or maintaining this codebase MUST adhere to strict engineering standards:
|
|
131
212
|
|
|
132
|
-
> 🚨 **RULE #0: MANDATORY EXECUTION OVERRIDE RULE (
|
|
213
|
+
> 🚨 **RULE #0: MANDATORY EXECUTION OVERRIDE RULE (NO EXCEPTIONS)**
|
|
133
214
|
> Even when the user issues a direct or urgent fix request ("fix this bug", "fix this error", "quick fix"):
|
|
134
215
|
> YOU ARE STRICTLY FORBIDDEN from modifying source code or workflows directly without completing the full quality harness:
|
|
135
216
|
> 1. **Research & Root Cause Analysis:** Investigate tracebacks and affected files before editing.
|
|
@@ -140,7 +221,7 @@ All AI Agents (Antigravity, OpenCode, Claude Code, Cursor, Windsurf, Copilot) de
|
|
|
140
221
|
|
|
141
222
|
### Engineering Rules
|
|
142
223
|
1. **Conventional Commits Only**: Commits must use Conventional Commits (e.g. \`feat(...): ...\`, \`fix(...): ...\`). NEVER add \`Co-Authored-By\` or AI attribution.
|
|
143
|
-
2. **Mirror Links Interoperability**: \`CLAUDE.md\`, \`GEMINI.md\`, \`.cursorrules\`, and \`.windsurfrules\` must remain
|
|
224
|
+
2. **Mirror Links Interoperability**: \`CLAUDE.md\`, \`GEMINI.md\`, \`.cursorrules\`, and \`.windsurfrules\` must remain symlinks to (or identical copies of) \`AGENTS.md\` (checked by \`tools/check_symlinks.js\`).
|
|
144
225
|
3. **No Superficial Symptom Patches**: Always address root causes.
|
|
145
226
|
|
|
146
227
|
---
|
|
@@ -149,10 +230,14 @@ All AI Agents (Antigravity, OpenCode, Claude Code, Cursor, Windsurf, Copilot) de
|
|
|
149
230
|
When operating as domain agents for end-users and non-technical operators:
|
|
150
231
|
|
|
151
232
|
1. **Non-Technical Operator Friendly**: Interact naturally with the operator. Guide step-by-step through conversational prompts without technical jargon.
|
|
152
|
-
2. **Mandatory Human-in-the-Loop (HITL) Checkpoints**: Always pause phase-by-phase during multi-step pipelines. Present draft output and WAIT for explicit user confirmation before executing irreversible steps.
|
|
153
|
-
3. **
|
|
154
|
-
4. **
|
|
155
|
-
5. **
|
|
233
|
+
2. **Mandatory Human-in-the-Loop (HITL) Checkpoints**: Always pause phase-by-phase during multi-step pipelines. Present draft output and WAIT for explicit user confirmation before executing irreversible steps. Every skill and playbook declares its checkpoint with a \`HITL:\` line or a \`## Human checkpoint\` section (checked by \`tools/check_hitl.js\`).
|
|
234
|
+
3. **Skill Contract**: Every \`SKILL.md\` opens with a frontmatter holding a unique lowercase \`name\` and a \`description\` that says when to use the skill (checked by \`tools/check_skills.js\`).
|
|
235
|
+
4. **SSOT Memory Primacy**: Read from and persist to the files this repository designates as its single source of truth; never keep operator data only in a conversation.
|
|
236
|
+
5. **Clean Creation State (No Invented Data)**: Never invent data. A field without a real value stays empty or \`null\` until real data exists.
|
|
237
|
+
6. **Finished Documents**: Deliverables follow the repository's templates and formats, keep no draft placeholders and link only to files that exist (checked by \`tools/check_placeholders.js\` and \`tools/check_links.js\`).
|
|
238
|
+
|
|
239
|
+
### Repository Map
|
|
240
|
+
${layerMap}
|
|
156
241
|
|
|
157
242
|
---
|
|
158
243
|
|
|
@@ -161,27 +246,31 @@ When given a command matching \`clckernel <action>\` or \`/clckernel <action>\`,
|
|
|
161
246
|
|
|
162
247
|
| Command | Lifecycle Phase | Mandatory Execution Protocol |
|
|
163
248
|
|---|---|---|
|
|
164
|
-
| \`clckernel feature <name>\` | New Feature | 1. Research affected layers.<br>2. Author local spec in \`sdds/{name}/spec.md\`.<br>3. Present HIT review to user and wait for approval.<br>4. Write failing test (Red Phase).<br>5. Implement clean code (Green Phase).<br>6. Run \`node tools/audit.js\` (or \`audit.py\`). |
|
|
249
|
+
| \`clckernel feature <name>\` | New Feature | 1. Research affected layers.<br>2. Author local spec in \`sdds/{name}/spec.md\`.<br>3. Present HIT review to user and wait for approval.<br>4. Write failing test (Red Phase).<br>5. Implement clean code (Green Phase).<br>6. Run \`node tools/audit.js --change {name}\` (or \`audit.py --change {name}\`). |
|
|
165
250
|
| \`clckernel fix <issue>\` | Bug Fix | 1. Investigate root cause (no code edits).<br>2. Reproduce with failing test (Red Phase).<br>3. Minimal surgical fix respecting Clean Arch (Green Phase).<br>4. Run audit gate without skipping. |
|
|
166
251
|
| \`clckernel test\` | Verification | Run test suite and verify clean pass (exit code 0). |
|
|
167
|
-
| \`clckernel audit\` | Verification | Run \`
|
|
168
|
-
| \`clckernel doctor\` | Diagnostics |
|
|
252
|
+
| \`clckernel audit\` | Verification | Run \`npx clckernel audit [dir] [--change <name>]\` (every installed orchestrator: \`tools/audit.js\` and/or \`tools/audit.py\`) and output the Educational Summary; only \`--change\` writes the report to \`sdds/<name>/\`. |
|
|
253
|
+
| \`clckernel doctor\` | Diagnostics | Run \`npx clckernel doctor [dir]\`: checks AGENTS.md, sdds/, tools/, the pre-commit hook and that the guard scripts load. |
|
|
169
254
|
| \`clckernel commit <message>\` | Version Control | Run full audit, verify tests pass, ensure no changes outside SDD scope, and commit with conventional commits (never add AI attribution). |
|
|
170
|
-
| \`clckernel guard create <name>\` | Safeguards | Scaffold a new
|
|
171
|
-
| \`clckernel update\` | Maintenance | Non-destructive update:
|
|
172
|
-
| \`clckernel start\` | Setup | Scan codebase
|
|
255
|
+
| \`clckernel guard create <name>\` | Safeguards | Scaffold a new guard script in the stack's native language in \`tools/guards/\`. |
|
|
256
|
+
| \`clckernel update\` | Maintenance | Non-destructive update: run \`npx clckernel update [dir]\` (\`--dry-run\` previews, \`--add-guards <a,b\\|all>\` adds guards); refreshes kernel tools, hook, playbooks & symlinks (preserves SDDs, project guards, edited files & \`.clckernel.yaml\`). |
|
|
257
|
+
| \`clckernel start\` | Setup | Scan codebase and propose a governance plan; if the harness is missing, install it with \`npx clckernel . --yes\`. \`.clckernel.yaml\` is optional: write it only for settings that differ from the defaults. |
|
|
173
258
|
|
|
174
259
|
---
|
|
175
260
|
|
|
176
261
|
## 🛡️ Core AI Safeguards
|
|
177
262
|
${this.getSafeguards().map(s => `- ${s}`).join('\n')}
|
|
178
|
-
`;
|
|
179
263
|
|
|
180
|
-
|
|
181
|
-
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
${FIRST_SESSION_SECTION}`;
|
|
267
|
+
|
|
268
|
+
// Guard: never overwrite a project-customized AGENTS.md (e.g. a law file
|
|
269
|
+
// carrying domain invariants the template cannot know about).
|
|
270
|
+
const io = currentIO(targetDir);
|
|
182
271
|
const dualAgentsPath = path.join(targetDir, 'AGENTS.md');
|
|
183
|
-
if (!
|
|
184
|
-
|
|
272
|
+
if (!io.exists(dualAgentsPath)) {
|
|
273
|
+
io.writeFile(dualAgentsPath, dualLayerAgents);
|
|
185
274
|
}
|
|
186
275
|
|
|
187
276
|
// 3. Create AI IDE mirror symlinks
|
|
@@ -189,26 +278,14 @@ ${this.getSafeguards().map(s => `- ${s}`).join('\n')}
|
|
|
189
278
|
for (const mirror of mirrors) {
|
|
190
279
|
const mirrorPath = path.join(targetDir, mirror);
|
|
191
280
|
try {
|
|
192
|
-
|
|
193
|
-
|
|
281
|
+
// An update never creates a mirror the project deleted (sdds/update-no-additions).
|
|
282
|
+
if (!io.exists(mirrorPath)) {
|
|
283
|
+
io.mirror('AGENTS.md', mirrorPath);
|
|
194
284
|
}
|
|
195
285
|
} catch {}
|
|
196
286
|
}
|
|
197
|
-
|
|
198
|
-
//
|
|
199
|
-
const srcToolsDir = path.join(__dirname, '..', '..', 'tools');
|
|
200
|
-
const targetToolsDir = path.join(targetDir, 'tools');
|
|
201
|
-
const toolsToCopy = ['check_symlinks.js', 'check_hitl.js', 'check_domain_data.js', 'check_test_calendar_sync.js', 'scan_secrets.js', 'audit.js'];
|
|
202
|
-
|
|
203
|
-
for (const toolFile of toolsToCopy) {
|
|
204
|
-
const srcFile = path.join(srcToolsDir, toolFile);
|
|
205
|
-
const destFile = path.join(targetToolsDir, toolFile);
|
|
206
|
-
if (fs.existsSync(srcFile) && !fs.existsSync(destFile)) {
|
|
207
|
-
try {
|
|
208
|
-
fs.copyFileSync(srcFile, destFile);
|
|
209
|
-
} catch {}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
287
|
+
// Guards are copied by the generator from getTools(targetDir), so the
|
|
288
|
+
// registry and the hook list exactly what is installed.
|
|
212
289
|
}
|
|
213
290
|
}
|
|
214
291
|
|
package/src/adapters/astro.js
CHANGED
|
@@ -34,9 +34,9 @@ class AstroAdapter extends BaseAdapter {
|
|
|
34
34
|
'SEO, GEO & Web Performance Guard (metadata, OpenGraph, Core Web Vitals)',
|
|
35
35
|
'Secret & API Key Leak Scanner',
|
|
36
36
|
'Content Collection Schema Validation Guard',
|
|
37
|
-
'
|
|
37
|
+
'Red→Green TDD Verifier (runs changed tests with vitest / jest / node:test)',
|
|
38
38
|
'Educational Audit Gate (05-audit-report.md)',
|
|
39
|
-
'Memory
|
|
39
|
+
'Memory protocol (agent-enforced; Engram / Graphify optional, not checked by a guard)'
|
|
40
40
|
];
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -51,11 +51,13 @@ class AstroAdapter extends BaseAdapter {
|
|
|
51
51
|
{ name: 'check_a11y', language: 'js', path: 'check_a11y.js', command: 'node tools/check_a11y.js', description: 'Validates ARIA and accessibility rules' },
|
|
52
52
|
{ name: 'check_responsive', language: 'js', path: 'check_responsive.js', command: 'node tools/check_responsive.js', description: 'Audits mobile-first responsiveness and fixed-width overflows' },
|
|
53
53
|
{ name: 'check_seo', language: 'js', path: 'check_seo.js', command: 'node tools/check_seo.js', description: 'Validates SEO metadata, OpenGraph, GEO tags, and LCP priority' },
|
|
54
|
-
{ name: '
|
|
55
|
-
{ name: '
|
|
56
|
-
{ name: '
|
|
54
|
+
{ name: 'verify_tdd', language: 'js', path: 'verify_tdd.js', command: 'node tools/verify_tdd.js', description: 'Runs changed tests (vitest / jest / node:test) against BASE (must fail) and the staged code (must pass)' },
|
|
55
|
+
{ name: 'check_custom', language: 'js', path: 'check_custom.js', command: 'node tools/check_custom.js', description: 'Runs user-defined custom rules from .clckernel.yaml' },
|
|
56
|
+
{ name: 'astro_check', language: 'external', path: '', command: 'npx astro check', description: 'Astro type checking', extCLI: 'astro', availableIf: '[ -x node_modules/.bin/astro ]' },
|
|
57
|
+
// vitest fails when there are no test files yet (a fresh project): that is not a failure.
|
|
58
|
+
{ name: 'vitest', language: 'external', path: '', command: 'npx vitest run --passWithNoTests', description: 'Test runner', extCLI: 'vitest', availableIf: '[ -x node_modules/.bin/vitest ]' },
|
|
57
59
|
];
|
|
58
|
-
return
|
|
60
|
+
return baseTools;
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
provision(targetDir, config) {
|
package/src/adapters/base.js
CHANGED
|
@@ -4,17 +4,15 @@
|
|
|
4
4
|
* inherit from this interface to ensure universal polyglot harness behavior.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
const fs = require('fs');
|
|
8
7
|
const path = require('path');
|
|
9
|
-
const {
|
|
10
|
-
const {
|
|
8
|
+
const { currentIO } = require('../io');
|
|
9
|
+
const { FIRST_SESSION_SECTION } = require('../onboarding');
|
|
11
10
|
|
|
12
11
|
class BaseAdapter {
|
|
13
12
|
constructor(name, projectType, testRunner) {
|
|
14
13
|
this.name = name;
|
|
15
14
|
this.projectType = projectType;
|
|
16
15
|
this.testRunner = testRunner;
|
|
17
|
-
this.techDetector = new TechnologyDetector();
|
|
18
16
|
}
|
|
19
17
|
|
|
20
18
|
detect(targetDir) {
|
|
@@ -28,50 +26,36 @@ class BaseAdapter {
|
|
|
28
26
|
/**
|
|
29
27
|
* Returns the tool manifest for this adapter.
|
|
30
28
|
* Each entry describes one pre-commit tool (script or external CLI).
|
|
31
|
-
* Override in subclasses to declare stack-specific tools.
|
|
29
|
+
* Override in subclasses to declare stack-specific tools. The generator
|
|
30
|
+
* passes the project root, so an adapter may add guards whose structure is
|
|
31
|
+
* present; without it the adapter's unconditional set is returned.
|
|
32
|
+
* @param {string} [targetDir] - Project root
|
|
32
33
|
* @returns {Array<{name: string, language: string, path: string, command: string, description: string, extCLI?: string}>}
|
|
33
34
|
*/
|
|
34
|
-
getTools() {
|
|
35
|
+
getTools(targetDir) {
|
|
35
36
|
return [];
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
/**
|
|
39
|
-
* Merges technology-specific tools into the adapter tool manifest.
|
|
40
|
-
* Tech tools are inserted before check_custom to preserve user override escape hatch.
|
|
41
|
-
* @param {Array} baseTools - The adapter's base tool manifest
|
|
42
|
-
* @returns {Array} Merged tool manifest with technology tools inserted
|
|
43
|
-
*/
|
|
44
|
-
_mergeTechTools(baseTools) {
|
|
45
|
-
const techTools = this.techDetector.getToolsForTech(this.detectedTech || []);
|
|
46
|
-
if (techTools.length === 0) return baseTools;
|
|
47
|
-
|
|
48
|
-
const result = [];
|
|
49
|
-
for (const tool of baseTools) {
|
|
50
|
-
if (tool.name === 'check_custom') {
|
|
51
|
-
// Insert tech tools before check_custom
|
|
52
|
-
result.push(...techTools);
|
|
53
|
-
}
|
|
54
|
-
result.push(tool);
|
|
55
|
-
}
|
|
56
|
-
return result;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
39
|
provision(targetDir, config) {
|
|
60
40
|
// Subclasses that render their own AGENTS.md pass { skipAgentsMd: true }
|
|
61
41
|
// so this generic law file is never written alongside (or over) theirs.
|
|
62
42
|
const skipAgentsMd = !!(config && config.skipAgentsMd);
|
|
63
43
|
|
|
44
|
+
// Every write goes through the active io session (sdds/safe-update).
|
|
45
|
+
const io = currentIO(targetDir);
|
|
46
|
+
|
|
64
47
|
// 1. Create docs/ and sdds/
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
48
|
+
io.mkdir(path.join(targetDir, 'docs', 'Journal'));
|
|
49
|
+
io.mkdir(path.join(targetDir, 'docs', 'Architecture'));
|
|
50
|
+
io.mkdir(path.join(targetDir, 'docs', 'Development'));
|
|
51
|
+
io.mkdir(path.join(targetDir, 'sdds'));
|
|
52
|
+
io.mkdir(path.join(targetDir, 'tools'));
|
|
70
53
|
|
|
71
54
|
// 2. Gitignore local SDD specs, ephemeral artifacts, and AI IDE mirrors
|
|
72
55
|
const { ensureGitignoreEntries } = require('../gitignore');
|
|
73
56
|
ensureGitignoreEntries(targetDir);
|
|
74
|
-
|
|
57
|
+
const gitkeep = path.join(targetDir, 'sdds', '.gitkeep');
|
|
58
|
+
if (!io.exists(gitkeep)) io.writeFile(gitkeep, '');
|
|
75
59
|
|
|
76
60
|
// 3. Write AGENTS.md
|
|
77
61
|
const isFront = this.projectType === 'frontend';
|
|
@@ -80,7 +64,7 @@ class BaseAdapter {
|
|
|
80
64
|
This document is the **authoritative law** for AI agents working in this repository.
|
|
81
65
|
Forged by **CLC Kernel: The AI Agent Governance Engine**.
|
|
82
66
|
|
|
83
|
-
> 🚨 **RULE #0: MANDATORY EXECUTION OVERRIDE RULE (
|
|
67
|
+
> 🚨 **RULE #0: MANDATORY EXECUTION OVERRIDE RULE (NO EXCEPTIONS)**
|
|
84
68
|
> Even when the user issues a direct or urgent fix request ("fix this bug", "fix this error", "quick fix"):
|
|
85
69
|
> YOU ARE STRICTLY FORBIDDEN from modifying source code directly without completing the full quality harness:
|
|
86
70
|
> 1. **Research & Root Cause Analysis:** Investigate tracebacks and inspect affected files before editing.
|
|
@@ -94,21 +78,22 @@ Forged by **CLC Kernel: The AI Agent Governance Engine**.
|
|
|
94
78
|
2. **Plan** — Write an SDD under \`sdds/{change-name}/\`. SDDs live 100% locally and are gitignored.
|
|
95
79
|
3. **Test** (TDD) — Write failing test first (${this.testRunner}).
|
|
96
80
|
4. **Implement** — Make test pass.
|
|
97
|
-
5. **Verify & Audit** — Run \`node tools/audit.js\` or \`python tools/audit.py
|
|
81
|
+
5. **Verify & Audit** — Run \`node tools/audit.js --change {change-name}\` or \`python tools/audit.py --change {change-name}\` (report + receipt in \`sdds/{change-name}/\`; without \`--change\` nothing is written).
|
|
98
82
|
6. **DoD** — Lint, typecheck, tests, coverage, docs, memory.
|
|
99
83
|
7. **Commit** — Pre-commit hook runs automated guards.
|
|
100
84
|
8. **PR** — Generate PR body.
|
|
101
85
|
|
|
102
86
|
## 2. Core AI Safeguards (${this.name})
|
|
103
87
|
${this.getSafeguards().map(s => `- ${s}`).join('\n')}
|
|
104
|
-
|
|
88
|
+
|
|
89
|
+
${FIRST_SESSION_SECTION}`;
|
|
105
90
|
// Guard: never overwrite a project-customized AGENTS.md.
|
|
106
91
|
if (skipAgentsMd) {
|
|
107
92
|
return;
|
|
108
93
|
}
|
|
109
94
|
const agentsPath = path.join(targetDir, 'AGENTS.md');
|
|
110
|
-
if (!
|
|
111
|
-
|
|
95
|
+
if (!io.exists(agentsPath)) {
|
|
96
|
+
io.writeFile(agentsPath, agentsContent);
|
|
112
97
|
}
|
|
113
98
|
}
|
|
114
99
|
}
|
package/src/adapters/django.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLC Kernel — Django Stack Adapter
|
|
3
|
-
* Supports Django apps, pytest-django, Ruff
|
|
3
|
+
* Supports Django apps, pytest-django, Ruff, and Python AST layer checks.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const fs = require('fs');
|
|
@@ -32,15 +32,15 @@ class DjangoAdapter extends BaseAdapter {
|
|
|
32
32
|
|
|
33
33
|
getSafeguards() {
|
|
34
34
|
return [
|
|
35
|
-
'
|
|
36
|
-
'Clean Architecture
|
|
37
|
-
'
|
|
38
|
-
'Django
|
|
39
|
-
'
|
|
35
|
+
'Ruff Python Linter (external CLI)',
|
|
36
|
+
'Clean Architecture Guard (Python AST: Domain -> Application -> Views)',
|
|
37
|
+
'N+1 Heuristic (ORM-like calls inside loops, Python AST)',
|
|
38
|
+
'Django Migration Reversibility protocol (agent-enforced; no migration guard installed for this stack)',
|
|
39
|
+
'Red→Green TDD Verifier (runs changed tests with pytest)',
|
|
40
40
|
'Secret & Private Key Leak Scanner',
|
|
41
|
-
'Scope
|
|
41
|
+
'Scope protocol (agent-enforced; not checked by a guard)',
|
|
42
42
|
'Educational Audit Gate (05-audit-report.md)',
|
|
43
|
-
'Memory
|
|
43
|
+
'Memory protocol (agent-enforced; Engram / Graphify optional, not checked by a guard)'
|
|
44
44
|
];
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -53,12 +53,13 @@ class DjangoAdapter extends BaseAdapter {
|
|
|
53
53
|
const baseTools = [
|
|
54
54
|
{ name: 'scan_secrets', language: 'py', path: 'scan_secrets.py', command: 'python3 tools/scan_secrets.py', description: 'Scans for leaked secrets and API keys' },
|
|
55
55
|
{ name: 'check_architecture', language: 'py', path: 'check_architecture.py', command: 'python3 tools/check_architecture.py', description: 'Validates Django layer boundaries' },
|
|
56
|
-
{ name: 'check_db_efficiency', language: 'py', path: 'check_db_efficiency.py', command: 'python3 tools/check_db_efficiency.py', description: '
|
|
57
|
-
{ name: '
|
|
58
|
-
{ name: '
|
|
59
|
-
{ name: '
|
|
56
|
+
{ name: 'check_db_efficiency', language: 'py', path: 'check_db_efficiency.py', command: 'python3 tools/check_db_efficiency.py', description: 'Flags ORM-like calls inside loops (N+1 heuristic)' },
|
|
57
|
+
{ name: 'verify_tdd', language: 'py', path: 'verify_tdd.py', command: 'python3 tools/verify_tdd.py', description: 'Runs changed tests against HEAD (must fail) and staged code (must pass)' },
|
|
58
|
+
{ name: 'check_custom', language: 'python', path: 'check_custom.py', command: 'python3 tools/check_custom.py', description: 'Runs user-defined custom rules from .clckernel.yaml' },
|
|
59
|
+
{ name: 'ruff', language: 'external', path: '', command: 'python3 -m ruff check .', description: 'Python linter', extCLI: 'ruff', availableIf: 'python3 -m ruff --version', excludeManaged: { flag: '--extend-exclude', extensions: ['.py'] } },
|
|
60
|
+
{ name: 'manage', language: 'external', path: '', command: 'python3 manage.py test', description: 'Django test runner', extCLI: 'manage.py', availableIf: '[ -f manage.py ]' },
|
|
60
61
|
];
|
|
61
|
-
return
|
|
62
|
+
return baseTools;
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
provision(targetDir, config) {
|
package/src/adapters/fastapi.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLC Kernel — FastAPI Stack Adapter
|
|
3
|
-
* Supports FastAPI apps, pytest,
|
|
3
|
+
* Supports FastAPI apps, pytest, Alembic migration reversibility, and Python AST layer checks.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const fs = require('fs');
|
|
@@ -29,16 +29,15 @@ class FastApiAdapter extends BaseAdapter {
|
|
|
29
29
|
|
|
30
30
|
getSafeguards() {
|
|
31
31
|
return [
|
|
32
|
-
'Ruff
|
|
33
|
-
'Clean Architecture
|
|
34
|
-
'
|
|
35
|
-
'Alembic Migration
|
|
36
|
-
'
|
|
37
|
-
'Pydantic V2 Response Schema Validation Guard',
|
|
32
|
+
'Ruff Python Linter (external CLI)',
|
|
33
|
+
'Clean Architecture Guard (Python AST: Domain -> Application -> Routes)',
|
|
34
|
+
'N+1 Heuristic (ORM-like calls inside loops, Python AST)',
|
|
35
|
+
'Alembic Migration Reversibility Guard (upgrade/downgrade, destructive operations)',
|
|
36
|
+
'Red→Green TDD Verifier (runs changed tests with pytest)',
|
|
38
37
|
'Secret & Private Key Leak Scanner',
|
|
39
|
-
'Scope
|
|
38
|
+
'Scope protocol (agent-enforced; not checked by a guard)',
|
|
40
39
|
'Educational Audit Gate (05-audit-report.md)',
|
|
41
|
-
'Memory
|
|
40
|
+
'Memory protocol (agent-enforced; Engram / Graphify optional, not checked by a guard)'
|
|
42
41
|
];
|
|
43
42
|
}
|
|
44
43
|
|
|
@@ -51,13 +50,15 @@ class FastApiAdapter extends BaseAdapter {
|
|
|
51
50
|
const baseTools = [
|
|
52
51
|
{ name: 'scan_secrets', language: 'py', path: 'scan_secrets.py', command: 'python3 tools/scan_secrets.py', description: 'Scans for leaked secrets and API keys' },
|
|
53
52
|
{ name: 'check_architecture', language: 'py', path: 'check_architecture.py', command: 'python3 tools/check_architecture.py', description: 'Validates Clean Architecture layer boundaries' },
|
|
54
|
-
{ name: 'check_db_efficiency', language: 'py', path: 'check_db_efficiency.py', command: 'python3 tools/check_db_efficiency.py', description: '
|
|
55
|
-
{ name: 'check_migrations', language: 'py', path: 'check_migrations.py', command: 'python3 tools/check_migrations.py', description: 'Checks Alembic migration
|
|
56
|
-
{ name: '
|
|
57
|
-
{ name: '
|
|
58
|
-
{ name: '
|
|
53
|
+
{ name: 'check_db_efficiency', language: 'py', path: 'check_db_efficiency.py', command: 'python3 tools/check_db_efficiency.py', description: 'Flags ORM-like calls inside loops (N+1 heuristic)' },
|
|
54
|
+
{ name: 'check_migrations', language: 'py', path: 'check_migrations.py', command: 'python3 tools/check_migrations.py', description: 'Checks Alembic migration reversibility (upgrade/downgrade, destructive operations)' },
|
|
55
|
+
{ name: 'verify_tdd', language: 'py', path: 'verify_tdd.py', command: 'python3 tools/verify_tdd.py', description: 'Runs changed tests against HEAD (must fail) and staged code (must pass)' },
|
|
56
|
+
{ name: 'check_custom', language: 'python', path: 'check_custom.py', command: 'python3 tools/check_custom.py', description: 'Runs user-defined custom rules from .clckernel.yaml' },
|
|
57
|
+
{ name: 'ruff', language: 'external', path: '', command: 'python3 -m ruff check .', description: 'Python linter', extCLI: 'ruff', availableIf: 'python3 -m ruff --version', excludeManaged: { flag: '--extend-exclude', extensions: ['.py'] } },
|
|
58
|
+
// pytest exits 5 when it collects no tests (a fresh project): that is not a failure.
|
|
59
|
+
{ name: 'pytest', language: 'external', path: '', command: 'python3 -m pytest || [ $? -eq 5 ]', description: 'Test runner', extCLI: 'pytest', availableIf: 'python3 -m pytest --version' },
|
|
59
60
|
];
|
|
60
|
-
return
|
|
61
|
+
return baseTools;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
provision(targetDir, config) {
|