@holdpoint/cli 0.1.0-alpha.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/dist/index.js ADDED
@@ -0,0 +1,995 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/commands/init.ts
7
+ import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync, copyFileSync } from "fs";
8
+ import { join, dirname } from "path";
9
+ import { fileURLToPath } from "url";
10
+ import chalk from "chalk";
11
+ import ora from "ora";
12
+ import { buildHookJson, buildCheckScript, buildConfigJson } from "@holdpoint/engine-copilot";
13
+ import { buildEngineJson as buildClaudeEngineJson } from "@holdpoint/engine-claude";
14
+ import { buildEngine as buildCursorEngine } from "@holdpoint/engine-cursor";
15
+ import { parseHoldpointYaml } from "@holdpoint/yaml-core";
16
+
17
+ // src/detect.ts
18
+ import { existsSync } from "fs";
19
+ function detectAgent() {
20
+ if (existsSync(".github/extensions")) return "copilot";
21
+ if (existsSync(".claude")) return "claude";
22
+ if (existsSync(".cursorrules")) return "cursor";
23
+ return "unknown";
24
+ }
25
+ function detectStack() {
26
+ const hasNext = existsSync("next.config.ts") || existsSync("next.config.js") || existsSync("next.config.mjs");
27
+ const hasTsConfig = existsSync("tsconfig.json");
28
+ const hasPyproject = existsSync("pyproject.toml") || existsSync("requirements.txt") || existsSync("setup.py");
29
+ const hasPrisma = existsSync("prisma/schema.prisma");
30
+ const hasApi = existsSync("server") || existsSync("api") || existsSync("backend");
31
+ const hasGoMod = existsSync("go.mod");
32
+ if (hasNext && (hasPrisma || hasApi)) return "fullstack";
33
+ if (hasNext) return "nextjs";
34
+ if (hasTsConfig) return "typescript";
35
+ if (hasPyproject) return "python";
36
+ if (hasGoMod) return "go";
37
+ return "unknown";
38
+ }
39
+
40
+ // src/commands/init.ts
41
+ var __dirname = dirname(fileURLToPath(import.meta.url));
42
+ function getTemplatePath(stack) {
43
+ const name = stack === "unknown" ? "_base" : stack;
44
+ const candidates = [
45
+ join(__dirname, "templates", `${name}.yaml`),
46
+ // dist/templates/ (published package)
47
+ join(__dirname, "../../../templates", `${name}.yaml`),
48
+ // monorepo dev fallback
49
+ join(process.cwd(), "templates", `${name}.yaml`)
50
+ // cwd fallback
51
+ ];
52
+ for (const p of candidates) {
53
+ if (existsSync2(p)) return p;
54
+ }
55
+ return "";
56
+ }
57
+ function getMasterPromptPath() {
58
+ const candidates = [
59
+ join(__dirname, "templates/MASTER_PROMPT.md"),
60
+ // dist/templates/ (published package)
61
+ join(__dirname, "../../../templates/MASTER_PROMPT.md"),
62
+ // monorepo dev fallback
63
+ join(process.cwd(), "templates/MASTER_PROMPT.md")
64
+ // cwd fallback
65
+ ];
66
+ for (const p of candidates) {
67
+ if (existsSync2(p)) return p;
68
+ }
69
+ return "";
70
+ }
71
+ var MINIMAL_CHECKS_YAML = `version: 1
72
+ context:
73
+ guides: {}
74
+ conditions: []
75
+ checks:
76
+ - id: lint
77
+ label: "Lint codebase"
78
+ cmd: "echo 'Add your lint command here'"
79
+
80
+ - id: jsdoc
81
+ label: "JSDoc on changed public functions"
82
+ prompt: "Ensure all changed public functions and exports have JSDoc comments."
83
+ `;
84
+ async function initCommand(options) {
85
+ const spinner = ora("Initialising Holdpoint\u2026").start();
86
+ const stack = options.stack ?? detectStack();
87
+ const agent = options.agent ?? detectAgent();
88
+ spinner.text = `Detected stack: ${chalk.cyan(stack)}, agent: ${chalk.cyan(agent)}`;
89
+ let yamlContent = MINIMAL_CHECKS_YAML;
90
+ if (!existsSync2("checks.yaml")) {
91
+ const templatePath = getTemplatePath(stack);
92
+ if (templatePath) {
93
+ yamlContent = readFileSync(templatePath, "utf8");
94
+ }
95
+ writeFileSync("checks.yaml", yamlContent, "utf8");
96
+ } else {
97
+ yamlContent = readFileSync("checks.yaml", "utf8");
98
+ }
99
+ const config = parseHoldpointYaml(yamlContent);
100
+ const generatedDir = ".github/holdpoint/generated";
101
+ mkdirSync(generatedDir, { recursive: true });
102
+ writeFileSync(`${generatedDir}/checks.immutable.json`, buildConfigJson(config), "utf8");
103
+ if (agent === "copilot" || agent === "unknown") {
104
+ const hooksDir = ".github/hooks";
105
+ mkdirSync(hooksDir, { recursive: true });
106
+ writeFileSync(join(hooksDir, "holdpoint.json"), buildHookJson(config), "utf8");
107
+ writeFileSync(join(hooksDir, "holdpoint-check.mjs"), buildCheckScript(config), "utf8");
108
+ }
109
+ if (agent === "claude") {
110
+ mkdirSync(".claude", { recursive: true });
111
+ const settingsPath = ".claude/settings.json";
112
+ let existing = {};
113
+ if (existsSync2(settingsPath)) {
114
+ try {
115
+ existing = JSON.parse(readFileSync(settingsPath, "utf8"));
116
+ } catch {
117
+ }
118
+ }
119
+ const holdpointHooks = JSON.parse(buildClaudeEngineJson(config));
120
+ writeFileSync(
121
+ settingsPath,
122
+ JSON.stringify({ ...existing, hooks: holdpointHooks.hooks }, null, 2),
123
+ "utf8"
124
+ );
125
+ }
126
+ if (agent === "cursor") {
127
+ const cursorRules = buildCursorEngine(config);
128
+ const cursorPath = ".cursorrules";
129
+ if (existsSync2(cursorPath)) {
130
+ const existing = readFileSync(cursorPath, "utf8");
131
+ if (!existing.includes("Holdpoint Rules")) {
132
+ writeFileSync(cursorPath, existing + "\n" + cursorRules, "utf8");
133
+ }
134
+ } else {
135
+ writeFileSync(cursorPath, cursorRules, "utf8");
136
+ }
137
+ }
138
+ if (!existsSync2("MASTER_PROMPT.md")) {
139
+ const guidePath = getMasterPromptPath();
140
+ if (guidePath) {
141
+ copyFileSync(guidePath, "MASTER_PROMPT.md");
142
+ } else {
143
+ writeFileSync(
144
+ "MASTER_PROMPT.md",
145
+ "# Holdpoint\n\nRun `npx holdpoint check` before marking any task complete.\nSee `checks.yaml` for the full list of checks.\n",
146
+ "utf8"
147
+ );
148
+ }
149
+ }
150
+ spinner.succeed(chalk.bold.green("Holdpoint initialised!"));
151
+ console.log(`
152
+ ${chalk.cyan("Next steps:")}
153
+ 1. Edit ${chalk.yellow("checks.yaml")} to customise your eval checkpoints
154
+ 2. Commit ${chalk.yellow("checks.yaml")} and the generated engine files
155
+ 3. Run ${chalk.yellow("npx holdpoint check")} at any time to validate
156
+
157
+ Visual builder: ${chalk.yellow("npx holdpoint builder")} (opens localhost:4321)
158
+ Stack: ${chalk.cyan(stack)} Agent: ${chalk.cyan(agent)}
159
+ `);
160
+ }
161
+
162
+ // src/commands/check.ts
163
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
164
+ import chalk2 from "chalk";
165
+ import ora2 from "ora";
166
+ import { parseHoldpointYaml as parseHoldpointYaml2, matchesWhen } from "@holdpoint/yaml-core";
167
+ import { runDeterministicChecks } from "@holdpoint/yaml-core/runner";
168
+ import { execSync as execSync3 } from "child_process";
169
+
170
+ // src/evolve/scanner.ts
171
+ import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync } from "fs";
172
+ import { join as join2 } from "path";
173
+ import { execSync } from "child_process";
174
+ function tryReadJson(path) {
175
+ try {
176
+ return JSON.parse(readFileSync2(path, "utf8"));
177
+ } catch {
178
+ return null;
179
+ }
180
+ }
181
+ function tryReadText(path) {
182
+ try {
183
+ return readFileSync2(path, "utf8");
184
+ } catch {
185
+ return "";
186
+ }
187
+ }
188
+ function scanProject(cwd = process.cwd()) {
189
+ const exists = (p) => existsSync3(join2(cwd, p));
190
+ const packageManager = exists("pnpm-lock.yaml") ? "pnpm" : exists("yarn.lock") ? "yarn" : exists("bun.lockb") ? "bun" : "npm";
191
+ const pkg = tryReadJson(join2(cwd, "package.json"));
192
+ const scripts = pkg?.scripts ?? {};
193
+ const deps = /* @__PURE__ */ new Set([
194
+ ...Object.keys(pkg?.dependencies ?? {}),
195
+ ...Object.keys(pkg?.devDependencies ?? {})
196
+ ]);
197
+ const pyprojectText = tryReadText(join2(cwd, "pyproject.toml"));
198
+ const requirementsText = tryReadText(join2(cwd, "requirements.txt"));
199
+ const pipfileText = tryReadText(join2(cwd, "Pipfile"));
200
+ const allPyText = pyprojectText + requirementsText + pipfileText;
201
+ const hasPytest = exists("pytest.ini") || exists("setup.cfg") || allPyText.includes("pytest") || allPyText.includes("[tool.pytest");
202
+ const hasRuff = allPyText.includes("ruff") || deps.has("ruff");
203
+ const hasAlembic = allPyText.includes("alembic") || deps.has("alembic");
204
+ let rootFiles = [];
205
+ try {
206
+ rootFiles = readdirSync(cwd);
207
+ } catch {
208
+ }
209
+ const hasDocker = rootFiles.some((f) => f === "Dockerfile" || f.startsWith("Dockerfile.")) || exists("docker-compose.yml") || exists("docker-compose.yaml") || exists("docker-compose.dev.yml");
210
+ const hasTerraform = rootFiles.some((f) => f.endsWith(".tf")) || exists("terraform") || exists("infra");
211
+ return {
212
+ // Languages
213
+ hasTypeScript: exists("tsconfig.json") || deps.has("typescript"),
214
+ hasPython: Boolean(pyprojectText) || Boolean(requirementsText) || Boolean(pipfileText) || exists("setup.py"),
215
+ hasGo: exists("go.mod"),
216
+ hasRust: exists("Cargo.toml"),
217
+ hasJava: exists("pom.xml") || exists("build.gradle") || exists("build.gradle.kts"),
218
+ hasRuby: exists("Gemfile"),
219
+ // Frameworks
220
+ hasNext: exists("next.config.ts") || exists("next.config.js") || exists("next.config.mjs") || deps.has("next"),
221
+ hasReact: deps.has("react"),
222
+ // Linting
223
+ hasEslint: exists("eslint.config.js") || exists("eslint.config.ts") || exists("eslint.config.mjs") || exists(".eslintrc.js") || exists(".eslintrc.json") || exists(".eslintrc.yml") || exists(".eslintrc.yaml") || deps.has("eslint"),
224
+ hasBiome: exists("biome.json") || exists("biome.jsonc") || deps.has("@biomejs/biome"),
225
+ hasRuff,
226
+ hasPrettier: exists("prettier.config.js") || exists("prettier.config.ts") || exists("prettier.config.mjs") || exists(".prettierrc") || exists(".prettierrc.json") || deps.has("prettier"),
227
+ // Testing
228
+ hasVitest: deps.has("vitest") || Boolean(scripts["test"]?.includes("vitest")),
229
+ hasJest: deps.has("jest") || Boolean(scripts["test"]?.includes("jest")),
230
+ hasPytest,
231
+ // DB
232
+ hasPrisma: exists("prisma/schema.prisma") || deps.has("@prisma/client"),
233
+ hasDrizzle: deps.has("drizzle-orm"),
234
+ hasMigrations: exists("migrations") || exists("db/migrations") || exists("database/migrations"),
235
+ hasAlembic,
236
+ // Infra
237
+ hasDocker,
238
+ hasTerraform,
239
+ hasKubernetes: exists("k8s") || exists("kubernetes") || exists("helm"),
240
+ // API
241
+ hasOpenApi: exists("openapi.yaml") || exists("openapi.yml") || exists("openapi.json") || exists("api/openapi.yaml"),
242
+ // CI
243
+ hasGithubActions: exists(".github/workflows"),
244
+ packageManager,
245
+ scripts,
246
+ deps
247
+ };
248
+ }
249
+
250
+ // src/evolve/templates.ts
251
+ function pmScript(profile, script, fallback) {
252
+ if (!profile.scripts[script]) return fallback;
253
+ if (profile.packageManager === "npm") return `npm run ${script}`;
254
+ return `${profile.packageManager} ${script}`;
255
+ }
256
+ function getTemplates(profile) {
257
+ return [
258
+ // ── Universal checks (always proposed for any project) ──────────────────
259
+ {
260
+ id: "git-commit",
261
+ label: "Commit all changes before finishing",
262
+ cmd: 'git rev-parse --is-inside-work-tree 2>/dev/null || exit 0; [ -z "$(git status --porcelain)" ] && exit 0; git status --short; exit 1',
263
+ trigger: () => true
264
+ },
265
+ {
266
+ id: "changelog-update",
267
+ label: "Add a CHANGELOG.md entry for this session",
268
+ prompt: "Before committing, add an entry to CHANGELOG.md describing what was done. Use Keep a Changelog format \u2014 add under ## [Unreleased] (create the file and that section if absent). Group entries as Added, Changed, Fixed, or Removed. Be concise but specific. The entry text will serve as the commit message.",
269
+ trigger: () => true
270
+ },
271
+ {
272
+ id: "readme-sync",
273
+ label: "Update README.md if user-facing changes were made",
274
+ prompt: "If you added, changed, or removed user-facing functionality \u2014 CLI commands, configuration options, public APIs, or significant new features \u2014 update README.md to reflect those changes.",
275
+ trigger: () => true
276
+ },
277
+ {
278
+ id: "no-todos",
279
+ label: "No TODO/FIXME left in changed code",
280
+ prompt: "Scan the files you changed for any TODO, FIXME, HACK, or XXX comments. Either resolve them before finishing or convert them to GitHub issues. Don't leave incomplete work silently behind.",
281
+ trigger: () => true
282
+ },
283
+ // ── TypeScript / JavaScript ──────────────────────────────────────────────
284
+ {
285
+ id: "typecheck",
286
+ label: "TypeScript type check",
287
+ cmd: pmScript(profile, "typecheck", "npx tsc --noEmit"),
288
+ trigger: (p) => p.hasTypeScript
289
+ },
290
+ {
291
+ id: "lint",
292
+ label: "Lint codebase",
293
+ cmd: profile.hasEslint ? pmScript(profile, "lint", "npx eslint .") : profile.hasBiome ? pmScript(profile, "lint", "npx @biomejs/biome check .") : pmScript(profile, "lint", "echo 'No linter detected'"),
294
+ trigger: (p) => p.hasEslint || p.hasBiome
295
+ },
296
+ {
297
+ id: "format-check",
298
+ label: "Prettier \u2014 format check",
299
+ cmd: pmScript(profile, "format:check", "npx prettier --check ."),
300
+ trigger: (p) => p.hasPrettier
301
+ },
302
+ {
303
+ id: "test",
304
+ label: "Unit tests",
305
+ cmd: profile.hasVitest ? pmScript(profile, "test", "npx vitest run") : pmScript(profile, "test", "npx jest --passWithNoTests"),
306
+ trigger: (p) => p.hasVitest || p.hasJest
307
+ },
308
+ {
309
+ id: "jsdoc",
310
+ label: "JSDoc on changed public functions",
311
+ prompt: "Ensure all changed public functions, classes, and module exports have accurate JSDoc comments (description + @param + @returns where applicable).",
312
+ trigger: (p) => p.hasTypeScript || p.hasReact
313
+ },
314
+ {
315
+ id: "build",
316
+ label: "Production build passes",
317
+ cmd: pmScript(profile, "build", "echo 'No build script detected'"),
318
+ trigger: (p) => Boolean(p.scripts["build"])
319
+ },
320
+ // ── Python ───────────────────────────────────────────────────────────────
321
+ {
322
+ id: "python-lint",
323
+ label: "Ruff \u2014 Python linting",
324
+ cmd: "ruff check .",
325
+ when: "python",
326
+ trigger: (p) => p.hasPython && p.hasRuff
327
+ },
328
+ {
329
+ id: "python-test",
330
+ label: "Pytest \u2014 Python unit tests",
331
+ cmd: "pytest",
332
+ when: "python",
333
+ trigger: (p) => p.hasPython && p.hasPytest
334
+ },
335
+ // ── Go ───────────────────────────────────────────────────────────────────
336
+ {
337
+ id: "go-test",
338
+ label: "Go tests",
339
+ cmd: "go test ./...",
340
+ when: "go",
341
+ trigger: (p) => p.hasGo
342
+ },
343
+ {
344
+ id: "go-vet",
345
+ label: "Go vet",
346
+ cmd: "go vet ./...",
347
+ when: "go",
348
+ trigger: (p) => p.hasGo
349
+ },
350
+ // ── Database ─────────────────────────────────────────────────────────────
351
+ {
352
+ id: "db-migrations",
353
+ label: "Database migration for schema changes",
354
+ when: "database",
355
+ prompt: "If schema or migration files changed, ensure the appropriate migration was generated with your ORM tool (e.g. `prisma migrate dev`, `alembic revision`, `rails db:migrate`) and committed alongside the schema change.",
356
+ trigger: (p) => p.hasPrisma || p.hasAlembic || p.hasMigrations || p.hasDrizzle
357
+ },
358
+ {
359
+ id: "prisma-format",
360
+ label: "Prisma schema format check",
361
+ when: "prisma",
362
+ cmd: "npx prisma format --check 2>/dev/null || npx prisma format",
363
+ conditionId: "has-prisma",
364
+ condition: {
365
+ id: "has-prisma",
366
+ operator: "file_exists",
367
+ path: "prisma/schema.prisma"
368
+ },
369
+ trigger: (p) => p.hasPrisma
370
+ },
371
+ // ── OpenAPI ──────────────────────────────────────────────────────────────
372
+ {
373
+ id: "openapi-sync",
374
+ label: "OpenAPI spec updated for API changes",
375
+ when: "backend",
376
+ conditionId: "has-openapi",
377
+ condition: {
378
+ id: "has-openapi",
379
+ operator: "file_exists",
380
+ path: "openapi.yaml"
381
+ },
382
+ prompt: "If any API routes were added or changed, update openapi.yaml (or openapi.json) to reflect the new endpoints, request/response shapes, and error codes.",
383
+ trigger: (p) => p.hasOpenApi
384
+ },
385
+ // ── Infra ─────────────────────────────────────────────────────────────────
386
+ {
387
+ id: "docker-build",
388
+ label: "Docker build passes",
389
+ when: "infra",
390
+ cmd: "docker build . --quiet -t app:ci",
391
+ conditionId: "has-dockerfile",
392
+ condition: {
393
+ id: "has-dockerfile",
394
+ operator: "file_exists",
395
+ path: "Dockerfile"
396
+ },
397
+ trigger: (p) => p.hasDocker
398
+ },
399
+ {
400
+ id: "terraform-validate",
401
+ label: "Terraform validate",
402
+ when: "infra",
403
+ cmd: "terraform validate",
404
+ trigger: (p) => p.hasTerraform
405
+ },
406
+ // ── Frontend ─────────────────────────────────────────────────────────────
407
+ {
408
+ id: "i18n",
409
+ label: "i18n \u2014 no hardcoded user-facing strings",
410
+ when: "frontend",
411
+ prompt: "Confirm all user-visible strings are wrapped in the project's i18n function (e.g. `t()`, `useTranslation`, `<Trans>`) and that locale files are updated for any new copy.",
412
+ trigger: (p) => p.hasNext && p.hasReact
413
+ }
414
+ ];
415
+ }
416
+
417
+ // src/evolve/dead-checker.ts
418
+ import { execSync as execSync2 } from "child_process";
419
+ import { readdirSync as readdirSync2, existsSync as existsSync4 } from "fs";
420
+ import { join as join3 } from "path";
421
+ var NAMED_SCOPES = /* @__PURE__ */ new Set([
422
+ "frontend",
423
+ "backend",
424
+ "socket",
425
+ "visual",
426
+ "python",
427
+ "go",
428
+ "rust",
429
+ "java",
430
+ "ruby",
431
+ "database",
432
+ "prisma",
433
+ "testing",
434
+ "infra",
435
+ "ci",
436
+ "docs",
437
+ "structural"
438
+ ]);
439
+ var WALK_IGNORED = /* @__PURE__ */ new Set([
440
+ "node_modules",
441
+ ".git",
442
+ "dist",
443
+ "build",
444
+ ".next",
445
+ ".turbo",
446
+ "__pycache__",
447
+ ".venv",
448
+ "venv",
449
+ ".mypy_cache",
450
+ "target",
451
+ ".cache",
452
+ "coverage"
453
+ ]);
454
+ function walkDir(dir, root, depth, maxDepth) {
455
+ if (depth > maxDepth) return [];
456
+ let entries = [];
457
+ try {
458
+ entries = readdirSync2(dir);
459
+ } catch {
460
+ return [];
461
+ }
462
+ const results = [];
463
+ for (const entry of entries) {
464
+ if (WALK_IGNORED.has(entry) || entry.startsWith(".")) continue;
465
+ const full = join3(dir, entry);
466
+ const rel = full.slice(root.length + 1);
467
+ results.push(rel);
468
+ const children = walkDir(full, root, depth + 1, maxDepth);
469
+ results.push(...children);
470
+ }
471
+ return results;
472
+ }
473
+ function getRepoFiles(cwd) {
474
+ try {
475
+ const out = execSync2("git ls-files", {
476
+ cwd,
477
+ encoding: "utf8",
478
+ stdio: ["pipe", "pipe", "ignore"]
479
+ });
480
+ const files = out.trim().split("\n").filter(Boolean);
481
+ if (files.length > 0) return files;
482
+ } catch {
483
+ }
484
+ return walkDir(cwd, cwd, 0, 6);
485
+ }
486
+ function extractPathFromRegex(pattern) {
487
+ const cleaned = pattern.replace(/^\^/, "").replace(/\$$/, "").replace(/\\\./g, ".").replace(/\(\?:/g, "").replace(/\)/g, "").replace(/[|*+?[\]{}()]/g, "");
488
+ const candidate = cleaned.replace(/\/$/, "").trim();
489
+ if (candidate.length > 0 && /^[\w\-./]+$/.test(candidate)) return candidate;
490
+ return void 0;
491
+ }
492
+ function detectStaleChecks(config, repoFiles) {
493
+ const stale = [];
494
+ const userPatterns = config.patterns ?? {};
495
+ for (const check of config.checks) {
496
+ if (!check.when) continue;
497
+ if (NAMED_SCOPES.has(check.when)) continue;
498
+ if (check.conditionId) continue;
499
+ const patternAlias = check.when in userPatterns ? check.when : void 0;
500
+ const regexStr = patternAlias ? userPatterns[patternAlias] : check.when;
501
+ let re;
502
+ try {
503
+ re = new RegExp(regexStr);
504
+ } catch {
505
+ stale.push({ check, reason: `Invalid regex: '${regexStr}'` });
506
+ continue;
507
+ }
508
+ const matches = repoFiles.filter((f) => re.test(f));
509
+ if (matches.length === 0) {
510
+ const label = patternAlias ? `Pattern '${patternAlias}' (= '${regexStr}')` : `Regex '${regexStr}'`;
511
+ const suggestedConditionPath = extractPathFromRegex(regexStr);
512
+ const pathGone = !suggestedConditionPath || !existsSync4(join3(process.cwd(), suggestedConditionPath));
513
+ if (pathGone) {
514
+ stale.push({
515
+ check,
516
+ reason: `${label} matches 0 files in the repo`,
517
+ ...suggestedConditionPath ? { suggestedConditionPath } : {}
518
+ });
519
+ }
520
+ }
521
+ }
522
+ return stale;
523
+ }
524
+
525
+ // src/commands/check.ts
526
+ function getStagedFiles() {
527
+ try {
528
+ const out = execSync3("git diff --cached --name-only", {
529
+ encoding: "utf8",
530
+ stdio: ["pipe", "pipe", "ignore"]
531
+ });
532
+ return out.trim().split("\n").filter(Boolean);
533
+ } catch {
534
+ return [];
535
+ }
536
+ }
537
+ function getAllChangedFiles() {
538
+ try {
539
+ const out = execSync3("git diff --name-only HEAD", {
540
+ encoding: "utf8",
541
+ stdio: ["pipe", "pipe", "ignore"]
542
+ });
543
+ return out.trim().split("\n").filter(Boolean);
544
+ } catch {
545
+ return [];
546
+ }
547
+ }
548
+ async function checkCommand(options) {
549
+ if (!existsSync5("checks.yaml")) {
550
+ console.error(chalk2.red("No checks.yaml found. Run `holdpoint init` first."));
551
+ process.exit(1);
552
+ }
553
+ const yamlContent = readFileSync3("checks.yaml", "utf8");
554
+ let config;
555
+ try {
556
+ config = parseHoldpointYaml2(yamlContent);
557
+ } catch (err) {
558
+ console.error(chalk2.red("Invalid checks.yaml:"), err.message);
559
+ process.exit(1);
560
+ }
561
+ const changedFiles = options.staged ? getStagedFiles() : getAllChangedFiles();
562
+ const guides = Object.entries(config.context?.guides ?? {});
563
+ if (guides.length > 0) {
564
+ console.log(chalk2.cyan("\nProject guides:"));
565
+ for (const [key, text] of guides) {
566
+ console.log(chalk2.bold(` ${key}:`), chalk2.dim(String(text).trim()));
567
+ }
568
+ console.log("");
569
+ }
570
+ if (changedFiles.length === 0) {
571
+ console.log(chalk2.yellow("No changed files detected. Running all checks with no file filter."));
572
+ }
573
+ const taskCount = config.checks.filter((c) => c.cmd !== void 0).length;
574
+ const spinner = ora2(`Running ${taskCount} task(s)\u2026`).start();
575
+ const effectiveFiles = changedFiles.length > 0 ? changedFiles : ["__all__"];
576
+ const results = runDeterministicChecks(config, effectiveFiles);
577
+ const runDrift = matchesWhen("structural", effectiveFiles);
578
+ if (runDrift) {
579
+ const profile = scanProject();
580
+ const existingIds = new Set(config.checks.map((c) => c.id));
581
+ const templates = getTemplates(profile);
582
+ const proposals = templates.filter((t) => t.trigger(profile) && !existingIds.has(t.id));
583
+ const repoFiles = getRepoFiles(process.cwd());
584
+ const staleChecks = detectStaleChecks(config, repoFiles);
585
+ if (proposals.length > 0 || staleChecks.length > 0) {
586
+ const lines = [];
587
+ if (proposals.length > 0) {
588
+ lines.push(`${proposals.length} new check(s) available for your project stack:`);
589
+ for (const p of proposals) lines.push(` + ${p.label}`);
590
+ }
591
+ if (staleChecks.length > 0) {
592
+ lines.push(`${staleChecks.length} stale check(s) no longer match your project:`);
593
+ for (const s of staleChecks) lines.push(` - ${s.check.label}: ${s.reason}`);
594
+ }
595
+ lines.push("\nRun: npx holdpoint evolve --apply");
596
+ results.push({
597
+ check: { id: "__holdpoint_evolve__", label: "Evolve checks with project structure" },
598
+ status: "fail",
599
+ output: lines.join("\n")
600
+ });
601
+ }
602
+ }
603
+ const passed = results.filter((r) => r.status === "pass");
604
+ const failed = results.filter((r) => r.status === "fail");
605
+ const skipped = results.filter((r) => r.status === "skip");
606
+ spinner.stop();
607
+ for (const result of results) {
608
+ printResult(result);
609
+ }
610
+ console.log("");
611
+ console.log(
612
+ [
613
+ chalk2.green(`\u2713 ${passed.length} passed`),
614
+ failed.length > 0 ? chalk2.red(`\u2717 ${failed.length} failed`) : "",
615
+ skipped.length > 0 ? chalk2.gray(`\u25CC ${skipped.length} skipped`) : ""
616
+ ].filter(Boolean).join(" ")
617
+ );
618
+ const promptChecks = config.checks.filter(
619
+ (c) => c.prompt !== void 0 && matchesWhen(c.when, changedFiles.length > 0 ? changedFiles : ["__all__"], config.patterns)
620
+ );
621
+ if (promptChecks.length > 0) {
622
+ console.log(`
623
+ ${chalk2.cyan("Agent prompts to act on:")}`);
624
+ for (const c of promptChecks) {
625
+ console.log(` ${chalk2.yellow("\u25A1")} [${c.label}] ${c.prompt ?? ""}`);
626
+ }
627
+ }
628
+ if (failed.length > 0) {
629
+ process.exit(1);
630
+ }
631
+ }
632
+ function printResult(result) {
633
+ const icon = result.status === "pass" ? chalk2.green("\u2713") : result.status === "fail" ? chalk2.red("\u2717") : result.status === "skip" ? chalk2.gray("\u25CC") : chalk2.yellow("\u2026");
634
+ const label = result.check.label;
635
+ console.log(`${icon} ${label}`);
636
+ if (result.status === "fail" && result.output) {
637
+ const trimmed = result.output.trim().split("\n").slice(0, 10).join("\n");
638
+ console.log(chalk2.dim(trimmed.replace(/^/gm, " ")));
639
+ }
640
+ if (result.status === "skip" && result.skipReason) {
641
+ console.log(chalk2.dim(` ${result.skipReason}`));
642
+ }
643
+ }
644
+
645
+ // src/commands/validate.ts
646
+ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
647
+ import chalk3 from "chalk";
648
+ import { parseHoldpointYaml as parseHoldpointYaml3, validateConfig } from "@holdpoint/yaml-core";
649
+ async function validateCommand() {
650
+ if (!existsSync6("checks.yaml")) {
651
+ console.error(chalk3.red("No checks.yaml found. Run `holdpoint init` first."));
652
+ process.exit(1);
653
+ }
654
+ const text = readFileSync4("checks.yaml", "utf8");
655
+ let config;
656
+ try {
657
+ config = parseHoldpointYaml3(text);
658
+ } catch (err) {
659
+ console.error(chalk3.red("Parse error:"), err.message);
660
+ process.exit(1);
661
+ }
662
+ const result = validateConfig(config);
663
+ if (result.valid) {
664
+ console.log(chalk3.green("\u2713 checks.yaml is valid"));
665
+ console.log(
666
+ chalk3.dim(
667
+ ` ${config.checks.filter((c) => c.cmd !== void 0).length} tasks, ${config.checks.filter((c) => c.prompt !== void 0).length} prompts, ${config.conditions.length} conditions`
668
+ )
669
+ );
670
+ } else {
671
+ console.error(chalk3.red("\u2717 checks.yaml has errors:"));
672
+ for (const err of result.errors) {
673
+ console.error(` ${chalk3.yellow(err.path)}: ${err.message}`);
674
+ }
675
+ process.exit(1);
676
+ }
677
+ }
678
+
679
+ // src/commands/update.ts
680
+ import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
681
+ import chalk4 from "chalk";
682
+ import ora3 from "ora";
683
+ import { parseHoldpointYaml as parseHoldpointYaml4 } from "@holdpoint/yaml-core";
684
+ import { buildHookJson as buildHookJson2, buildCheckScript as buildCheckScript2, buildConfigJson as buildConfigJson2 } from "@holdpoint/engine-copilot";
685
+ import { buildEngineJson as buildClaudeEngineJson2 } from "@holdpoint/engine-claude";
686
+ import { buildEngine as buildCursorEngine2 } from "@holdpoint/engine-cursor";
687
+ async function updateCommand() {
688
+ if (!existsSync7("checks.yaml")) {
689
+ console.error(chalk4.red("No checks.yaml found. Run `holdpoint init` first."));
690
+ process.exit(1);
691
+ }
692
+ const spinner = ora3("Updating Holdpoint engine files\u2026").start();
693
+ const agent = detectAgent();
694
+ const config = parseHoldpointYaml4(readFileSync5("checks.yaml", "utf8"));
695
+ const generatedDir = ".github/holdpoint/generated";
696
+ mkdirSync2(generatedDir, { recursive: true });
697
+ writeFileSync2(`${generatedDir}/checks.immutable.json`, buildConfigJson2(config), "utf8");
698
+ if (agent === "copilot" || agent === "unknown") {
699
+ const hooksDir = ".github/hooks";
700
+ mkdirSync2(hooksDir, { recursive: true });
701
+ writeFileSync2(`${hooksDir}/holdpoint.json`, buildHookJson2(config), "utf8");
702
+ writeFileSync2(`${hooksDir}/holdpoint-check.mjs`, buildCheckScript2(config), "utf8");
703
+ spinner.text = `Updated ${chalk4.green(".github/hooks/holdpoint.json")} and ${chalk4.green(".github/hooks/holdpoint-check.mjs")}`;
704
+ }
705
+ if (agent === "claude") {
706
+ mkdirSync2(".claude", { recursive: true });
707
+ const settingsPath = ".claude/settings.json";
708
+ let existing = {};
709
+ if (existsSync7(settingsPath)) {
710
+ try {
711
+ existing = JSON.parse(readFileSync5(settingsPath, "utf8"));
712
+ } catch {
713
+ }
714
+ }
715
+ const hooks = JSON.parse(buildClaudeEngineJson2(config));
716
+ writeFileSync2(settingsPath, JSON.stringify({ ...existing, hooks: hooks.hooks }, null, 2));
717
+ }
718
+ if (agent === "cursor") {
719
+ const cursorRules = buildCursorEngine2(config);
720
+ const cursorPath = ".cursorrules";
721
+ if (existsSync7(cursorPath)) {
722
+ const content = readFileSync5(cursorPath, "utf8");
723
+ const start = content.indexOf("# \u2500\u2500\u2500 Holdpoint Rules");
724
+ const end = content.indexOf("# \u2500\u2500\u2500 End Holdpoint Rules \u2500\u2500\u2500");
725
+ if (start !== -1 && end !== -1) {
726
+ const afterEnd = content.indexOf("\n", end);
727
+ const updated = content.slice(0, start) + cursorRules + content.slice(afterEnd === -1 ? end : afterEnd + 1);
728
+ writeFileSync2(cursorPath, updated);
729
+ } else {
730
+ writeFileSync2(cursorPath, content + "\n" + cursorRules);
731
+ }
732
+ }
733
+ }
734
+ spinner.succeed(chalk4.green("Engine files updated from current checks.yaml"));
735
+ }
736
+
737
+ // src/commands/build.ts
738
+ import { createServer } from "http";
739
+ import { createReadStream, existsSync as existsSync8 } from "fs";
740
+ import { join as join4, extname, dirname as dirname2 } from "path";
741
+ import { fileURLToPath as fileURLToPath2 } from "url";
742
+ import { execSync as execSync4 } from "child_process";
743
+ import chalk5 from "chalk";
744
+ var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
745
+ var MIME = {
746
+ ".html": "text/html; charset=utf-8",
747
+ ".js": "text/javascript",
748
+ ".mjs": "text/javascript",
749
+ ".css": "text/css",
750
+ ".svg": "image/svg+xml",
751
+ ".png": "image/png",
752
+ ".ico": "image/x-icon",
753
+ ".woff": "font/woff",
754
+ ".woff2": "font/woff2",
755
+ ".ttf": "font/ttf",
756
+ ".json": "application/json"
757
+ };
758
+ function serveFile(res, filePath) {
759
+ const mime = MIME[extname(filePath)] ?? "application/octet-stream";
760
+ res.writeHead(200, { "Content-Type": mime });
761
+ createReadStream(filePath).pipe(res);
762
+ }
763
+ function handleRequest(req, res, uiDir) {
764
+ const url = (req.url ?? "/").split("?")[0] ?? "/";
765
+ if (url === "/__holdpoint/initial-yaml") {
766
+ const checksPath = join4(process.cwd(), "checks.yaml");
767
+ if (existsSync8(checksPath)) {
768
+ res.writeHead(200, { "Content-Type": "text/yaml; charset=utf-8" });
769
+ createReadStream(checksPath).pipe(res);
770
+ } else {
771
+ res.writeHead(404, { "Content-Type": "text/plain" });
772
+ res.end("checks.yaml not found in current directory");
773
+ }
774
+ return;
775
+ }
776
+ const candidate = join4(uiDir, url === "/" ? "index.html" : url);
777
+ const filePath = existsSync8(candidate) ? candidate : join4(uiDir, "index.html");
778
+ serveFile(res, filePath);
779
+ }
780
+ async function buildCommand() {
781
+ const port = 4321;
782
+ const uiDir = join4(__dirname2, "builder-ui");
783
+ if (!existsSync8(uiDir)) {
784
+ console.error(chalk5.red("\u2717 Builder UI not found.\n"));
785
+ console.log(chalk5.dim(" This is unexpected for a published build of @holdpoint/cli."));
786
+ console.log(chalk5.dim(" If you installed from source, rebuild with: pnpm turbo build\n"));
787
+ process.exit(1);
788
+ }
789
+ const server = createServer((req, res) => handleRequest(req, res, uiDir));
790
+ await new Promise((resolve, reject) => {
791
+ server.listen(port, () => {
792
+ console.log(
793
+ `
794
+ ${chalk5.green("\u2713")} Holdpoint builder running at ${chalk5.cyan(`http://localhost:${port}`)}`
795
+ );
796
+ console.log(chalk5.dim(" Edit checks.yaml, then reload the page to see updates"));
797
+ console.log(chalk5.dim(" Press Ctrl+C to stop\n"));
798
+ const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
799
+ try {
800
+ execSync4(`${openCmd} http://localhost:${port}`, { stdio: "ignore" });
801
+ } catch {
802
+ }
803
+ });
804
+ server.on("error", reject);
805
+ process.on("SIGINT", () => {
806
+ console.log(chalk5.dim("\n Stopping builder\u2026"));
807
+ server.close(() => resolve());
808
+ });
809
+ });
810
+ }
811
+
812
+ // src/commands/evolve.ts
813
+ import { existsSync as existsSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
814
+ import { execSync as execSync5 } from "child_process";
815
+ import chalk6 from "chalk";
816
+ import ora4 from "ora";
817
+ import { parseHoldpointYaml as parseHoldpointYaml5, generateYaml } from "@holdpoint/yaml-core";
818
+ function extractHeader(yaml) {
819
+ const lines = yaml.split("\n");
820
+ const commentLines = [];
821
+ for (const line of lines) {
822
+ if (line.startsWith("#") || commentLines.length > 0 && line.trim() === "") {
823
+ commentLines.push(line);
824
+ } else {
825
+ break;
826
+ }
827
+ }
828
+ while (commentLines.length > 0 && commentLines[commentLines.length - 1]?.trim() === "") {
829
+ commentLines.pop();
830
+ }
831
+ return commentLines.join("\n");
832
+ }
833
+ function withHeader(header, newYaml) {
834
+ if (!header) return newYaml;
835
+ return header + "\n\n" + newYaml;
836
+ }
837
+ async function evolveCommand(options) {
838
+ if (!existsSync9("checks.yaml")) {
839
+ console.error(chalk6.red("No checks.yaml found. Run `holdpoint init` first."));
840
+ process.exit(1);
841
+ }
842
+ const spinner = ora4("Scanning project profile\u2026").start();
843
+ const cwd = process.cwd();
844
+ const profile = scanProject(cwd);
845
+ const repoFiles = getRepoFiles(cwd);
846
+ const yamlContent = readFileSync6("checks.yaml", "utf8");
847
+ let config;
848
+ try {
849
+ config = parseHoldpointYaml5(yamlContent);
850
+ } catch (err) {
851
+ spinner.fail(chalk6.red("Invalid checks.yaml:") + " " + err.message);
852
+ process.exit(1);
853
+ }
854
+ spinner.stop();
855
+ const existingIds = new Set(config.checks.map((c) => c.id));
856
+ const existingConditionIds = new Set(config.conditions.map((c) => c.id));
857
+ const allTemplates = getTemplates(profile);
858
+ const proposals = allTemplates.filter((t) => t.trigger(profile) && !existingIds.has(t.id));
859
+ const staleChecks = detectStaleChecks(config, repoFiles);
860
+ console.log(chalk6.bold("\n\u{1F4CB} Project profile:"));
861
+ const traits = [
862
+ ["TypeScript", profile.hasTypeScript, "tsconfig.json"],
863
+ ["ESLint", profile.hasEslint, "eslint.config.*"],
864
+ ["Biome", profile.hasBiome, "biome.json"],
865
+ ["Ruff", profile.hasRuff, "pyproject.toml / ruff"],
866
+ ["Prettier", profile.hasPrettier, ".prettierrc"],
867
+ ["Vitest", profile.hasVitest, "devDependencies"],
868
+ ["Jest", profile.hasJest, "devDependencies"],
869
+ ["Python", profile.hasPython, "pyproject.toml"],
870
+ ["Pytest", profile.hasPytest, "pytest.ini"],
871
+ ["Go", profile.hasGo, "go.mod"],
872
+ ["Rust", profile.hasRust, "Cargo.toml"],
873
+ ["Next.js", profile.hasNext, "next.config.*"],
874
+ ["React", profile.hasReact, "dependencies"],
875
+ ["Prisma", profile.hasPrisma, "prisma/schema.prisma"],
876
+ ["Drizzle", profile.hasDrizzle, "drizzle-orm"],
877
+ ["Migrations", profile.hasMigrations, "migrations/"],
878
+ ["Docker", profile.hasDocker, "Dockerfile"],
879
+ ["Terraform", profile.hasTerraform, "*.tf"],
880
+ ["Kubernetes", profile.hasKubernetes, "k8s/"],
881
+ ["OpenAPI", profile.hasOpenApi, "openapi.yaml"],
882
+ ["GitHub Actions", profile.hasGithubActions, ".github/workflows/"]
883
+ ];
884
+ const detected = traits.filter(([, yes]) => yes);
885
+ if (detected.length === 0) {
886
+ console.log(chalk6.dim(" (empty project \u2014 only universal checks apply)"));
887
+ } else {
888
+ for (const [name, , hint] of detected) {
889
+ console.log(` ${chalk6.green("\u2713")} ${name.padEnd(18)} ${chalk6.dim(hint)}`);
890
+ }
891
+ }
892
+ if (staleChecks.length > 0) {
893
+ console.log(chalk6.bold(`
894
+ \u26A0\uFE0F Stale checks (${staleChecks.length}):`));
895
+ for (const { check, reason, suggestedConditionPath } of staleChecks) {
896
+ const fix = suggestedConditionPath ? chalk6.dim(` \u2192 will wrap with conditionId: file_exists: ${suggestedConditionPath}`) : chalk6.dim(" \u2192 no path inferred; review manually");
897
+ console.log(` ${chalk6.yellow("\u25CC")} ${chalk6.bold(check.id)} ${chalk6.dim(reason)}${fix}`);
898
+ }
899
+ }
900
+ if (proposals.length === 0 && staleChecks.length === 0) {
901
+ console.log(chalk6.green("\n\u2713 checks.yaml is fully in sync with the project profile."));
902
+ return;
903
+ }
904
+ if (proposals.length > 0) {
905
+ console.log(chalk6.bold(`
906
+ \u{1F4A1} Proposed additions (${proposals.length}):`));
907
+ for (const t of proposals) {
908
+ const scope = t.when ? chalk6.cyan(` when: ${t.when}`) : "";
909
+ const type = t.cmd ? chalk6.dim("cmd") : chalk6.dim("prompt");
910
+ const preview = t.cmd ? chalk6.dim(` ${t.cmd.slice(0, 80)}${t.cmd.length > 80 ? "\u2026" : ""}`) : chalk6.dim(` ${(t.prompt ?? "").slice(0, 80)}${(t.prompt?.length ?? 0) > 80 ? "\u2026" : ""}`);
911
+ console.log(` ${chalk6.green("+")} ${chalk6.bold(t.id.padEnd(24))} [${type}]${scope}`);
912
+ console.log(` ${preview}`);
913
+ }
914
+ }
915
+ if (!options.apply) {
916
+ console.log(
917
+ chalk6.red(`
918
+ \u2717 checks.yaml is out of sync with the project profile.`) + `
919
+ Run ${chalk6.bold("npx holdpoint evolve --apply")} to apply these changes.`
920
+ );
921
+ process.exit(1);
922
+ }
923
+ const applySpinner = ora4("Applying changes to checks.yaml\u2026").start();
924
+ const newConditions = [...config.conditions];
925
+ for (const t of proposals) {
926
+ if (t.condition && !existingConditionIds.has(t.condition.id)) {
927
+ newConditions.push(t.condition);
928
+ existingConditionIds.add(t.condition.id);
929
+ }
930
+ }
931
+ const updatedChecks = config.checks.map((check) => {
932
+ const stale = staleChecks.find((s) => s.check.id === check.id);
933
+ if (!stale || !stale.suggestedConditionPath) return check;
934
+ const condId = `has-${check.id}`;
935
+ if (!existingConditionIds.has(condId)) {
936
+ newConditions.push({
937
+ id: condId,
938
+ operator: "file_exists",
939
+ path: stale.suggestedConditionPath
940
+ });
941
+ existingConditionIds.add(condId);
942
+ }
943
+ return { ...check, conditionId: condId };
944
+ });
945
+ const newChecks = proposals.map((t) => ({
946
+ id: t.id,
947
+ label: t.label,
948
+ ...t.when ? { when: t.when } : {},
949
+ ...t.cmd ? { cmd: t.cmd } : {},
950
+ ...t.prompt ? { prompt: t.prompt } : {},
951
+ ...t.conditionId ? { conditionId: t.conditionId } : {}
952
+ }));
953
+ const updatedConfig = {
954
+ ...config,
955
+ conditions: newConditions,
956
+ checks: [...updatedChecks, ...newChecks]
957
+ };
958
+ const header = extractHeader(yamlContent);
959
+ const newYaml = withHeader(header, generateYaml(updatedConfig));
960
+ writeFileSync3("checks.yaml", newYaml, "utf8");
961
+ applySpinner.text = "Running holdpoint update\u2026";
962
+ try {
963
+ execSync5("npx holdpoint update", { stdio: "pipe" });
964
+ } catch {
965
+ applySpinner.warn(
966
+ chalk6.yellow("checks.yaml updated, but `holdpoint update` failed \u2014 run it manually.")
967
+ );
968
+ printAppliedSummary(proposals.length, staleChecks.length);
969
+ return;
970
+ }
971
+ applySpinner.succeed(chalk6.green("checks.yaml updated and engine files regenerated."));
972
+ printAppliedSummary(proposals.length, staleChecks.length);
973
+ }
974
+ function printAppliedSummary(added, wrapped) {
975
+ const parts = [];
976
+ if (added > 0) parts.push(chalk6.green(`${added} check${added === 1 ? "" : "s"} added`));
977
+ if (wrapped > 0)
978
+ parts.push(chalk6.yellow(`${wrapped} stale check${wrapped === 1 ? "" : "s"} wrapped`));
979
+ if (parts.length > 0) console.log(" " + parts.join(" \xB7 "));
980
+ console.log(
981
+ chalk6.dim("\n Review checks.yaml, then commit: ") + chalk6.yellow("git add checks.yaml && git commit -m 'chore: evolve holdpoint checks'")
982
+ );
983
+ }
984
+
985
+ // src/index.ts
986
+ var program = new Command();
987
+ program.name("holdpoint").description("Universal eval-guard for AI coding agents (alpha)").version("0.1.0-alpha.0");
988
+ program.command("init").description("Initialise Holdpoint in the current project").option("--stack <stack>", "Stack type: typescript | python | nextjs | fullstack").option("--agent <agent>", "Agent type: copilot | claude | cursor").action(initCommand);
989
+ program.command("check").description("Run task checks from checks.yaml").option("--staged", "Only check against git-staged files").action(checkCommand);
990
+ program.command("validate").description("Validate checks.yaml schema and print any errors").action(validateCommand);
991
+ program.command("update").description("Regenerate engine files from current checks.yaml (preserves checks.yaml)").action(updateCommand);
992
+ program.command("builder").description("Open the visual builder UI on localhost:4321").action(buildCommand);
993
+ program.command("evolve").description("Scan project and propose (or apply) new checks to keep checks.yaml in sync").option("--apply", "Write proposed changes to checks.yaml and regenerate engine files").action(evolveCommand);
994
+ program.parse();
995
+ //# sourceMappingURL=index.js.map