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/ui.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const p = require('@clack/prompts');
|
|
7
|
+
const { adapters, buildStackConfig } = require('./detector');
|
|
8
|
+
const { version: PACKAGE_VERSION } = require('../package.json');
|
|
7
9
|
|
|
8
10
|
// ANSI Color Palette
|
|
9
11
|
const colors = {
|
|
@@ -33,7 +35,7 @@ function printBanner() {
|
|
|
33
35
|
╚██████╗███████╗╚██████╗ ██║ ╚██╗███████╗██║ ██║██║ ╚████║███████╗███████╗
|
|
34
36
|
╚═════╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝
|
|
35
37
|
${colors.reset}`);
|
|
36
|
-
console.log(` ${colors.bgCyan} CLC KERNEL ${colors.reset} ${colors.bold}The AI Agent Governance & Quality Engineering Engine${colors.reset} ${colors.gray}
|
|
38
|
+
console.log(` ${colors.bgCyan} CLC KERNEL ${colors.reset} ${colors.bold}The AI Agent Governance & Quality Engineering Engine${colors.reset} ${colors.gray}v${PACKAGE_VERSION}${colors.reset}\n`);
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
function printBox(title, items) {
|
|
@@ -52,19 +54,37 @@ function printBox(title, items) {
|
|
|
52
54
|
console.log(`${colors.dim}└${'─'.repeat(width)}┘${colors.reset}\n`);
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
!process.env.CI &&
|
|
58
|
-
!process.argv.includes('--yes') &&
|
|
59
|
-
!process.argv.includes('-y');
|
|
57
|
+
/** Second-level select value for the stack-agnostic harness. */
|
|
58
|
+
const GENERIC_STACK = 'generic';
|
|
60
59
|
|
|
61
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Lets the user confirm the detected stack, pick another one, or cancel.
|
|
62
|
+
* Picking another stack returns the config the detector builds for that
|
|
63
|
+
* adapter, so the chosen adapter drives provisioning, AGENTS.md and the hook.
|
|
64
|
+
* @param {object} detected - Config from autoDetectStack
|
|
65
|
+
* @param {object} [options]
|
|
66
|
+
* @param {string} [options.targetDir] - Project root (cwd when absent)
|
|
67
|
+
* @param {object} [options.prompts] - Prompts implementation (@clack/prompts)
|
|
68
|
+
* @param {boolean} [options.interactive] - Defaults to TTY, no CI, no --yes/-y
|
|
69
|
+
* @returns {Promise<object>} Final config
|
|
70
|
+
*/
|
|
71
|
+
async function promptOptions(detected, options = {}) {
|
|
72
|
+
const {
|
|
73
|
+
targetDir = process.cwd(),
|
|
74
|
+
prompts = p,
|
|
75
|
+
interactive = Boolean(process.stdin.isTTY) &&
|
|
76
|
+
!process.env.CI &&
|
|
77
|
+
!process.argv.includes('--yes') &&
|
|
78
|
+
!process.argv.includes('-y')
|
|
79
|
+
} = options;
|
|
80
|
+
|
|
81
|
+
if (!interactive) {
|
|
62
82
|
return detected;
|
|
63
83
|
}
|
|
64
84
|
|
|
65
|
-
|
|
85
|
+
prompts.intro(`${colors.cyan}${colors.bold}CLC Kernel Setup${colors.reset}`);
|
|
66
86
|
|
|
67
|
-
const choice = await
|
|
87
|
+
const choice = await prompts.select({
|
|
68
88
|
message: '¿Cómo deseas inicializar CLC Kernel en este proyecto?',
|
|
69
89
|
initialValue: 'confirm',
|
|
70
90
|
options: [
|
|
@@ -74,19 +94,9 @@ async function promptOptions(detected) {
|
|
|
74
94
|
hint: `Recomendado (${detected.projectType.toUpperCase()} - ${detected.framework})`
|
|
75
95
|
},
|
|
76
96
|
{
|
|
77
|
-
value: '
|
|
78
|
-
label: '
|
|
79
|
-
hint: '
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
value: 'backend',
|
|
83
|
-
label: 'Forzar modo BACKEND',
|
|
84
|
-
hint: 'Salvaguardas TDD, Clean Arch, N+1, Secrets'
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
value: 'agent-os',
|
|
88
|
-
label: 'Forzar modo AGENT-OS',
|
|
89
|
-
hint: 'Salvaguardas HITL, Symlinks, Schemas, Memoria SSOT'
|
|
97
|
+
value: 'other',
|
|
98
|
+
label: 'Elegir otro stack',
|
|
99
|
+
hint: 'Instalar las salvaguardas de otro framework'
|
|
90
100
|
},
|
|
91
101
|
{
|
|
92
102
|
value: 'cancel',
|
|
@@ -96,35 +106,77 @@ async function promptOptions(detected) {
|
|
|
96
106
|
]
|
|
97
107
|
});
|
|
98
108
|
|
|
99
|
-
if (
|
|
100
|
-
|
|
109
|
+
if (prompts.isCancel(choice) || choice === 'cancel') {
|
|
110
|
+
prompts.cancel('Instalación de CLC Kernel cancelada.');
|
|
101
111
|
process.exit(0);
|
|
102
112
|
}
|
|
103
113
|
|
|
104
|
-
if (choice
|
|
105
|
-
detected
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
114
|
+
if (choice !== 'other') {
|
|
115
|
+
return detected;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const stack = await prompts.select({
|
|
119
|
+
message: '¿Qué stack deseas instalar?',
|
|
120
|
+
initialValue: detected.adapter ? detected.adapter.name : GENERIC_STACK,
|
|
121
|
+
options: [
|
|
122
|
+
...adapters.map((adapter) => ({
|
|
123
|
+
value: adapter.name,
|
|
124
|
+
label: adapter.name,
|
|
125
|
+
hint: `${adapter.projectType.toUpperCase()} - ${adapter.testRunner}`
|
|
126
|
+
})),
|
|
127
|
+
{
|
|
128
|
+
value: GENERIC_STACK,
|
|
129
|
+
label: 'Genérico',
|
|
130
|
+
hint: 'Salvaguardas agnósticas de stack'
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (prompts.isCancel(stack)) {
|
|
136
|
+
prompts.cancel('Instalación de CLC Kernel cancelada.');
|
|
137
|
+
process.exit(0);
|
|
110
138
|
}
|
|
111
139
|
|
|
112
|
-
|
|
140
|
+
const adapter = adapters.find((a) => a.name === stack) || null;
|
|
141
|
+
return buildStackConfig(targetDir, adapter);
|
|
113
142
|
}
|
|
114
143
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
144
|
+
/**
|
|
145
|
+
* One line per part of a multi-part config: `dir: framework (test runner)`.
|
|
146
|
+
* Empty for a single-stack config, whose summary stays as it was.
|
|
147
|
+
* @param {object} config - Project config
|
|
148
|
+
* @returns {string[]}
|
|
149
|
+
*/
|
|
150
|
+
function partLines(config) {
|
|
151
|
+
if (!config || !Array.isArray(config.parts)) return [];
|
|
152
|
+
return config.parts.map((part) => `${part.dir}: ${part.framework} (${part.testRunner})`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Prints the installation summary.
|
|
157
|
+
* @param {string} targetDir - Provisioned project root
|
|
158
|
+
* @param {{ file: string, active: boolean }} [hook] - Hook written (relative path)
|
|
159
|
+
* and whether git runs it
|
|
160
|
+
* @param {object} config - Final project config
|
|
161
|
+
* @param {string[]} [installedGuards] - Guards actually copied into tools/
|
|
162
|
+
*/
|
|
163
|
+
function printSuccess(targetDir, hook, config, installedGuards = []) {
|
|
164
|
+
const hookDesc = !hook
|
|
165
|
+
? `${colors.amber}no hook written${colors.reset}`
|
|
166
|
+
: hook.active
|
|
167
|
+
? `active hook ${colors.amber}${hook.file}${colors.reset}`
|
|
168
|
+
: `${colors.amber}${hook.file} written but not active (core.hooksPath unchanged)${colors.reset}`;
|
|
169
|
+
const guardsDesc = installedGuards.length > 0
|
|
170
|
+
? `${colors.emerald}${installedGuards.length} guard(s): ${installedGuards.join(', ')}${colors.reset}`
|
|
171
|
+
: `${colors.amber}no guards installed${colors.reset}`;
|
|
122
172
|
|
|
123
173
|
const summary = [
|
|
124
174
|
`🔨 ${colors.bold}Proyecto Inicializado:${colors.reset} ${colors.cyan}${targetDir}${colors.reset}`,
|
|
125
175
|
`🛡️ ${colors.bold}Salvaguardas:${colors.reset} ${guardsDesc}`,
|
|
126
176
|
`📝 ${colors.bold}Leyes & SDD:${colors.reset} AGENTS.md, sdds/, docs/Journal/ precargados`,
|
|
127
|
-
`🔒 ${colors.bold}Git Hooks:${colors.reset}
|
|
177
|
+
`🔒 ${colors.bold}Git Hooks:${colors.reset} ${hookDesc}`,
|
|
178
|
+
...(partLines(config).length > 0 ? [`🧩 ${colors.bold}Partes:${colors.reset}`] : []),
|
|
179
|
+
...partLines(config).map((line) => ` • ${line}`)
|
|
128
180
|
].join('\n');
|
|
129
181
|
|
|
130
182
|
if (p.note && p.outro) {
|
|
@@ -136,10 +188,151 @@ function printSuccess(targetDir, isFront, config) {
|
|
|
136
188
|
}
|
|
137
189
|
}
|
|
138
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Lines of the install/update summary (sdds/safe-update, decision 4): written,
|
|
193
|
+
* unchanged, protected (with the .clckernel-new path), moved, removed, added
|
|
194
|
+
* to .gitignore and git config; then what was left out on purpose: guards
|
|
195
|
+
* available but not installed, mirrors not present, tracked paths not
|
|
196
|
+
* ignored. A dry run prints the same sections as a plan.
|
|
197
|
+
* @param {object} plan - io.plan()
|
|
198
|
+
* @param {{ dryRun?: boolean }} [options]
|
|
199
|
+
* @returns {string[]}
|
|
200
|
+
*/
|
|
201
|
+
function planLines(plan, { dryRun = false } = {}) {
|
|
202
|
+
const label = (real, planned) => (dryRun ? planned : real);
|
|
203
|
+
const lines = [];
|
|
204
|
+
const section = (title, items, color = colors.emerald) => {
|
|
205
|
+
if (items.length === 0) return;
|
|
206
|
+
lines.push(`${colors.bold}${title} (${items.length}):${colors.reset}`);
|
|
207
|
+
for (const item of items) lines.push(` ${color}•${colors.reset} ${item}`);
|
|
208
|
+
};
|
|
209
|
+
section(label('Written', 'Would write'), plan.written);
|
|
210
|
+
if (plan.unchanged.length > 0) {
|
|
211
|
+
lines.push(`${colors.bold}${label('Unchanged', 'Unchanged')} (${plan.unchanged.length}):${colors.reset} already up to date`);
|
|
212
|
+
}
|
|
213
|
+
section(label('Protected (local changes kept)', 'Would protect (local changes kept)'),
|
|
214
|
+
plan.protected.map((p) => `${p.path} (${p.reason}) -> new version in ${p.newPath}`), colors.amber);
|
|
215
|
+
section(label('Moved', 'Would move'), plan.moved.map((m) => `${m.from} -> ${m.to}`));
|
|
216
|
+
section(label('Removed', 'Would remove'), plan.removed);
|
|
217
|
+
section(label('Added to .gitignore', 'Would add to .gitignore'), plan.gitignore);
|
|
218
|
+
section(label('git config', 'Would set git config'), plan.gitConfig.map((c) => `${c.key} = ${c.value}`));
|
|
219
|
+
// sdds/update-no-additions, decision 5: what was left out on purpose.
|
|
220
|
+
section('Available, not installed (add with --add-guards <name,...|all>)', plan.available || [], colors.cyan);
|
|
221
|
+
section('Mirrors not present (skipped)', plan.mirrorsSkipped || [], colors.cyan);
|
|
222
|
+
section('Tracked, not ignored (git tracks them; no .gitignore entry added)', plan.trackedNotIgnored || [], colors.cyan);
|
|
223
|
+
// sdds/config-foundation: update.exclude in .clckernel.yaml.
|
|
224
|
+
section('Excluded by config (update.exclude; left as they are)', plan.excluded || [], colors.cyan);
|
|
225
|
+
if (plan.protected.length > 0) {
|
|
226
|
+
lines.push('');
|
|
227
|
+
lines.push(`${colors.amber}⚠ ${plan.protected.length} file(s) ${dryRun ? 'would be' : 'were'} protected: the kernel cannot prove they are its own and intact.${colors.reset}`);
|
|
228
|
+
lines.push(`${colors.amber} Review each *.clckernel-new copy, merge what you want, then delete it (it is gitignored).${colors.reset}`);
|
|
229
|
+
}
|
|
230
|
+
if (dryRun) {
|
|
231
|
+
lines.push('');
|
|
232
|
+
lines.push(`${colors.cyan}Dry run: nothing was written (no files, .gitignore or git config).${colors.reset}`);
|
|
233
|
+
}
|
|
234
|
+
return lines;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* AI harnesses by the AGENTS.md mirror they read (sdds/onboarding). Agents
|
|
239
|
+
* that read AGENTS.md itself (Codex, OpenCode, ...) need no mirror.
|
|
240
|
+
*/
|
|
241
|
+
const HARNESS_MIRRORS = [
|
|
242
|
+
{ name: 'Claude Code (CLAUDE.md)', match: (p) => p === 'CLAUDE.md' },
|
|
243
|
+
{ name: 'Gemini (GEMINI.md)', match: (p) => p === 'GEMINI.md' },
|
|
244
|
+
{ name: 'Cursor', match: (p) => p === '.cursorrules' || p.startsWith('.cursor/') },
|
|
245
|
+
{ name: 'Copilot', match: (p) => p === '.github/copilot-instructions.md' },
|
|
246
|
+
{ name: 'Antigravity', match: (p) => p.startsWith('.antigravity/') },
|
|
247
|
+
{ name: 'Windsurf', match: (p) => p === '.windsurfrules' },
|
|
248
|
+
];
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Harness names whose mirror the run wrote or found up to date.
|
|
252
|
+
* @param {object} plan - io.plan()
|
|
253
|
+
* @returns {string[]}
|
|
254
|
+
*/
|
|
255
|
+
function harnessNames(plan) {
|
|
256
|
+
const paths = [...(plan.written || []), ...(plan.unchanged || [])].map((entry) => entry.split(' -> ')[0]);
|
|
257
|
+
return HARNESS_MIRRORS.filter((h) => paths.some(h.match)).map((h) => h.name);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* "Next steps" after a real install (sdds/onboarding), true to what was
|
|
262
|
+
* installed: the hook file and whether git runs it (or the command that
|
|
263
|
+
* activates it), the harness mirrors written and the parts. English, like
|
|
264
|
+
* the plan listing.
|
|
265
|
+
* @param {{ file: string, active: boolean, activate?: string|null }|undefined} hook
|
|
266
|
+
* @param {object} plan - io.plan()
|
|
267
|
+
* @param {object} [config] - Final project config
|
|
268
|
+
* @returns {string[]}
|
|
269
|
+
*/
|
|
270
|
+
function nextStepsLines(hook, plan, config = {}) {
|
|
271
|
+
const c = colors;
|
|
272
|
+
const cmd = (text) => `${c.cyan}${text}${c.reset}`;
|
|
273
|
+
const lines = [`${c.bold}Next steps${c.reset}`];
|
|
274
|
+
const commit = cmd('git add -A && git commit -m "chore: install clckernel"');
|
|
275
|
+
const parts = Array.isArray(config.parts) && config.parts.length > 0
|
|
276
|
+
? ` The hook runs the checks of every part (${config.parts.map((p) => p.dir).join(', ')}) from the repository root.`
|
|
277
|
+
: null;
|
|
278
|
+
if (hook && hook.active) {
|
|
279
|
+
lines.push(` 1. Review and commit the harness: ${commit}`);
|
|
280
|
+
lines.push(` The pre-commit hook (${hook.file}) runs on this commit.`);
|
|
281
|
+
} else if (hook) {
|
|
282
|
+
lines.push(` 1. The pre-commit hook (${hook.file}) is not active yet. Activate it: ${cmd(hook.activate || 'git config core.hooksPath .githooks')}`);
|
|
283
|
+
lines.push(` Then review and commit the harness: ${commit}`);
|
|
284
|
+
} else {
|
|
285
|
+
lines.push(` 1. Review and commit the harness: ${commit} (no pre-commit hook was written)`);
|
|
286
|
+
}
|
|
287
|
+
if (hook && parts) lines.push(parts);
|
|
288
|
+
const harnesses = harnessNames(plan || {});
|
|
289
|
+
const agents = harnesses.length > 0
|
|
290
|
+
? `AGENTS.md mirrors written for: ${harnesses.join(', ')}.`
|
|
291
|
+
: 'No AGENTS.md mirrors written: use an agent that reads AGENTS.md itself.';
|
|
292
|
+
lines.push(` 2. Open your AI agent and ask it to run ${cmd('clckernel start')} to tailor the rules (.clckernel.yaml),`);
|
|
293
|
+
lines.push(' or just start working: it follows AGENTS.md.');
|
|
294
|
+
lines.push(` ${agents}`);
|
|
295
|
+
lines.push(` 3. Run ${cmd('npx clckernel audit')} anytime to see every guard's result.`);
|
|
296
|
+
lines.push(' 4. Project-specific checks go in tools/guards/ (the hook and the audit run them).');
|
|
297
|
+
lines.push(` 5. Later, preview kernel updates with ${cmd('npx clckernel update --dry-run')}.`);
|
|
298
|
+
return lines;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Shorter "Next steps" after an update: only what needs attention
|
|
303
|
+
* (protected files to merge, guards available but not installed); empty
|
|
304
|
+
* when nothing does.
|
|
305
|
+
* @param {object} plan - io.plan()
|
|
306
|
+
* @returns {string[]}
|
|
307
|
+
*/
|
|
308
|
+
function updateNextStepsLines(plan) {
|
|
309
|
+
const c = colors;
|
|
310
|
+
const steps = [];
|
|
311
|
+
for (const entry of plan.protected || []) {
|
|
312
|
+
steps.push(`Review ${entry.newPath}, merge what you want into ${entry.path}, then delete the copy.`);
|
|
313
|
+
}
|
|
314
|
+
if ((plan.available || []).length > 0) {
|
|
315
|
+
steps.push(`Optional guards: ${c.cyan}npx clckernel update --add-guards ${plan.available.join(',')}${c.reset}`);
|
|
316
|
+
}
|
|
317
|
+
if (steps.length === 0) return [];
|
|
318
|
+
return [`${c.bold}Next steps${c.reset}`, ...steps.map((step, i) => ` ${i + 1}. ${step}`)];
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Prints planLines. */
|
|
322
|
+
function printPlan(plan, options) {
|
|
323
|
+
console.log(planLines(plan, options).join('\n'));
|
|
324
|
+
}
|
|
325
|
+
|
|
139
326
|
module.exports = {
|
|
327
|
+
planLines,
|
|
328
|
+
nextStepsLines,
|
|
329
|
+
updateNextStepsLines,
|
|
330
|
+
harnessNames,
|
|
331
|
+
printPlan,
|
|
140
332
|
colors,
|
|
141
333
|
printBanner,
|
|
142
334
|
printBox,
|
|
143
335
|
promptOptions,
|
|
144
|
-
printSuccess
|
|
336
|
+
printSuccess,
|
|
337
|
+
partLines
|
|
145
338
|
};
|