@holdpoint/cli 0.1.0-alpha.14 → 0.1.0-alpha.16

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 CHANGED
@@ -4,9 +4,10 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // src/commands/init.ts
7
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync, mkdirSync, copyFileSync } from "fs";
8
- import { join, dirname } from "path";
9
- import { fileURLToPath } from "url";
7
+ import { execSync } from "child_process";
8
+ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync } from "fs";
9
+ import { join as join2, dirname as dirname2 } from "path";
10
+ import { fileURLToPath as fileURLToPath2 } from "url";
10
11
  import chalk from "chalk";
11
12
  import ora from "ora";
12
13
  import { buildConfigJson, buildEngine } from "@holdpoint/engine-copilot";
@@ -57,34 +58,52 @@ function detectStack() {
57
58
  return "unknown";
58
59
  }
59
60
 
60
- // src/commands/init.ts
61
+ // src/templates.ts
62
+ import { copyFileSync, existsSync as existsSync2, writeFileSync } from "fs";
63
+ import { join, dirname } from "path";
64
+ import { fileURLToPath } from "url";
61
65
  var __dirname = dirname(fileURLToPath(import.meta.url));
62
- function getTemplatePath(stack) {
63
- const name = stack === "unknown" ? "_base" : stack;
66
+ function getBundledTemplatePath(filename) {
64
67
  const candidates = [
65
- join(__dirname, "templates", `${name}.yaml`),
68
+ join(__dirname, "templates", filename),
66
69
  // dist/templates/ (published package)
67
- join(__dirname, "../../../templates", `${name}.yaml`),
70
+ join(__dirname, "../../../templates", filename),
68
71
  // monorepo dev fallback
69
- join(process.cwd(), "templates", `${name}.yaml`)
72
+ join(process.cwd(), "templates", filename)
70
73
  // cwd fallback
71
74
  ];
72
- for (const p of candidates) {
73
- if (existsSync2(p)) return p;
75
+ for (const candidate of candidates) {
76
+ if (existsSync2(candidate)) return candidate;
74
77
  }
75
78
  return "";
76
79
  }
77
- function getMasterPromptPath() {
80
+ function ensureBundledFile(outputPath, templateFilename, fallbackContent) {
81
+ if (existsSync2(outputPath)) {
82
+ return false;
83
+ }
84
+ const templatePath = getBundledTemplatePath(templateFilename);
85
+ if (templatePath) {
86
+ copyFileSync(templatePath, outputPath);
87
+ } else {
88
+ writeFileSync(outputPath, fallbackContent, "utf8");
89
+ }
90
+ return true;
91
+ }
92
+
93
+ // src/commands/init.ts
94
+ var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
95
+ function getTemplatePath(stack) {
96
+ const name = stack === "unknown" ? "_base" : stack;
78
97
  const candidates = [
79
- join(__dirname, "templates/MASTER_PROMPT.md"),
98
+ join2(__dirname2, "templates", `${name}.yaml`),
80
99
  // dist/templates/ (published package)
81
- join(__dirname, "../../../templates/MASTER_PROMPT.md"),
100
+ join2(__dirname2, "../../../templates", `${name}.yaml`),
82
101
  // monorepo dev fallback
83
- join(process.cwd(), "templates/MASTER_PROMPT.md")
102
+ join2(process.cwd(), "templates", `${name}.yaml`)
84
103
  // cwd fallback
85
104
  ];
86
105
  for (const p of candidates) {
87
- if (existsSync2(p)) return p;
106
+ if (existsSync3(p)) return p;
88
107
  }
89
108
  return "";
90
109
  }
@@ -101,47 +120,62 @@ checks:
101
120
  label: "JSDoc on changed public functions"
102
121
  prompt: "Ensure all changed public functions and exports have JSDoc comments."
103
122
  `;
123
+ var MINIMAL_MASTER_PROMPT = `# Holdpoint
124
+
125
+ Run \`holdpoint check\` before marking any task complete.
126
+ See \`checks.yaml\` for the full list of checks.
127
+ `;
128
+ var MINIMAL_PREREQUISITES = `# Holdpoint prerequisites
129
+
130
+ Holdpoint installed repo-local adapters for one or more AI coding agents. Before relying on them locally, review these setup notes:
131
+
132
+ - **GitHub Copilot CLI** \u2014 Holdpoint's \`.github/extensions/holdpoint/extension.mjs\` uses the Copilot CLI **EXTENSIONS** feature. Today that feature is gated behind experimental mode. In Copilot CLI, run \`/experimental on\` so **EXTENSIONS** appears in the enabled feature set before using Holdpoint locally.
133
+ - **OpenAI Codex** \u2014 project-level hooks require trust approval. Run \`codex trust\` in the Codex TUI or review the hook with \`/hooks\`.
134
+ - **General** \u2014 Holdpoint expects Node.js 18+ and a git repository so \`holdpoint init\`, \`holdpoint update\`, and \`holdpoint check\` can run normally.
135
+
136
+ Docs: https://holdpoint.dev/docs
137
+ `;
104
138
  async function initCommand(options) {
105
139
  const spinner = ora("Initialising Holdpoint\u2026").start();
106
140
  const stack = options.stack ?? detectStack();
107
141
  const agentOpt = options.agent;
108
142
  const agents = !agentOpt || agentOpt === "all" ? ["copilot", "claude", "cursor", "codex"] : [agentOpt];
109
143
  spinner.text = `Stack: ${chalk.cyan(stack)} \u2014 installing for: ${chalk.cyan(agents.join(", "))}`;
144
+ const pm = detectPackageManager();
110
145
  let yamlContent = MINIMAL_CHECKS_YAML;
111
- if (!existsSync2("checks.yaml")) {
146
+ if (!existsSync3("checks.yaml")) {
112
147
  const templatePath = getTemplatePath(stack);
113
148
  if (templatePath) {
114
149
  yamlContent = readFileSync2(templatePath, "utf8");
115
150
  }
116
- const pm = detectPackageManager();
117
151
  if (pm !== "pnpm") {
118
152
  yamlContent = yamlContent.replace(/\bpnpm\b/g, pm);
119
153
  }
120
- writeFileSync("checks.yaml", yamlContent, "utf8");
154
+ writeFileSync2("checks.yaml", yamlContent, "utf8");
121
155
  } else {
122
156
  yamlContent = readFileSync2("checks.yaml", "utf8");
123
157
  }
124
158
  const config = parseHoldpointYaml(yamlContent);
125
159
  const generatedDir = ".github/holdpoint/generated";
126
160
  mkdirSync(generatedDir, { recursive: true });
127
- writeFileSync(`${generatedDir}/checks.immutable.json`, buildConfigJson(config), "utf8");
161
+ writeFileSync2(`${generatedDir}/checks.immutable.json`, buildConfigJson(config), "utf8");
128
162
  if (agents.includes("copilot")) {
129
163
  const extDir = ".github/extensions/holdpoint";
130
164
  mkdirSync(extDir, { recursive: true });
131
- writeFileSync(join(extDir, "extension.mjs"), buildEngine(config), "utf8");
165
+ writeFileSync2(join2(extDir, "extension.mjs"), buildEngine(config), "utf8");
132
166
  }
133
167
  if (agents.includes("claude")) {
134
168
  mkdirSync(".claude", { recursive: true });
135
169
  const settingsPath = ".claude/settings.json";
136
170
  let existing = {};
137
- if (existsSync2(settingsPath)) {
171
+ if (existsSync3(settingsPath)) {
138
172
  try {
139
173
  existing = JSON.parse(readFileSync2(settingsPath, "utf8"));
140
174
  } catch {
141
175
  }
142
176
  }
143
177
  const holdpointHooks = JSON.parse(buildClaudeEngineJson(config));
144
- writeFileSync(
178
+ writeFileSync2(
145
179
  settingsPath,
146
180
  JSON.stringify({ ...existing, hooks: holdpointHooks.hooks }, null, 2),
147
181
  "utf8"
@@ -150,63 +184,80 @@ async function initCommand(options) {
150
184
  if (agents.includes("cursor")) {
151
185
  const cursorRules = buildCursorEngine(config);
152
186
  const cursorPath = ".cursorrules";
153
- if (existsSync2(cursorPath)) {
187
+ if (existsSync3(cursorPath)) {
154
188
  const existing = readFileSync2(cursorPath, "utf8");
155
189
  if (!existing.includes("Holdpoint Rules")) {
156
- writeFileSync(cursorPath, existing + "\n" + cursorRules, "utf8");
190
+ writeFileSync2(cursorPath, existing + "\n" + cursorRules, "utf8");
157
191
  }
158
192
  } else {
159
- writeFileSync(cursorPath, cursorRules, "utf8");
193
+ writeFileSync2(cursorPath, cursorRules, "utf8");
160
194
  }
161
195
  }
162
196
  if (agents.includes("codex")) {
163
197
  mkdirSync(".codex", { recursive: true });
164
- writeFileSync(".codex/hooks.json", buildCodexHooksJson(config), "utf8");
165
- writeFileSync(".codex/holdpoint-check.mjs", buildCodexCheckScript(config), "utf8");
166
- writeFileSync(".codex/config.toml", buildCodexConfigToml(), "utf8");
198
+ writeFileSync2(".codex/hooks.json", buildCodexHooksJson(config), "utf8");
199
+ writeFileSync2(".codex/holdpoint-check.mjs", buildCodexCheckScript(config), "utf8");
200
+ writeFileSync2(".codex/config.toml", buildCodexConfigToml(), "utf8");
167
201
  const agentsMdPath = "AGENTS.md";
168
- const existing = existsSync2(agentsMdPath) ? readFileSync2(agentsMdPath, "utf8") : "";
169
- writeFileSync(agentsMdPath, spliceAgentsMd(existing, config), "utf8");
202
+ const existing = existsSync3(agentsMdPath) ? readFileSync2(agentsMdPath, "utf8") : "";
203
+ writeFileSync2(agentsMdPath, spliceAgentsMd(existing, config), "utf8");
170
204
  }
171
- if (!existsSync2("MASTER_PROMPT.md")) {
172
- const guidePath = getMasterPromptPath();
173
- if (guidePath) {
174
- copyFileSync(guidePath, "MASTER_PROMPT.md");
175
- } else {
176
- writeFileSync(
177
- "MASTER_PROMPT.md",
178
- "# Holdpoint\n\nRun `npx @holdpoint/cli@alpha check` before marking any task complete.\nSee `checks.yaml` for the full list of checks.\n",
179
- "utf8"
180
- );
181
- }
205
+ ensureBundledFile("MASTER_PROMPT.md", "MASTER_PROMPT.md", MINIMAL_MASTER_PROMPT);
206
+ ensureBundledFile(
207
+ "HOLDPOINT_PREREQUISITES.md",
208
+ "HOLDPOINT_PREREQUISITES.md",
209
+ MINIMAL_PREREQUISITES
210
+ );
211
+ spinner.text = "Installing holdpoint as a devDependency\u2026";
212
+ const installCmds = {
213
+ pnpm: "pnpm add -D holdpoint@alpha",
214
+ yarn: "yarn add --dev holdpoint@alpha",
215
+ npm: "npm install --save-dev holdpoint@alpha"
216
+ };
217
+ const installCmd = installCmds[pm];
218
+ try {
219
+ execSync(installCmd, { stdio: "pipe" });
220
+ spinner.succeed(chalk.bold.green("Holdpoint initialised!"));
221
+ } catch {
222
+ spinner.warn(
223
+ chalk.yellow(`Holdpoint initialised, but could not install the package automatically.`) + `
224
+ Run manually: ${chalk.yellow(installCmd)}`
225
+ );
182
226
  }
183
- spinner.succeed(chalk.bold.green("Holdpoint initialised!"));
184
227
  console.log(`
185
228
  ${chalk.cyan("Next steps:")}
186
229
  1. Edit ${chalk.yellow("checks.yaml")} to customise your eval checkpoints
187
- 2. Commit ${chalk.yellow("checks.yaml")} and the generated engine files
188
- 3. Run ${chalk.yellow("npx @holdpoint/cli@alpha check")} at any time to validate
230
+ 2. Review ${chalk.yellow("HOLDPOINT_PREREQUISITES.md")} for agent setup notes
231
+ 3. Commit ${chalk.yellow("checks.yaml")}, ${chalk.yellow("HOLDPOINT_PREREQUISITES.md")}, and the generated engine files
232
+ 4. Run ${chalk.yellow("holdpoint check")} at any time to validate
189
233
 
190
- Visual builder: ${chalk.yellow("npx @holdpoint/cli@alpha builder")} (opens localhost:4321)
234
+ ${chalk.bgYellow.black(" Copilot local use ")} Run ${chalk.yellow("/experimental on")} in GitHub Copilot CLI so the
235
+ ${chalk.yellow("EXTENSIONS")} feature is enabled before using Holdpoint locally.
236
+ See ${chalk.yellow("HOLDPOINT_PREREQUISITES.md")} for the full handoff notes.
237
+
238
+ Visual builder: ${chalk.yellow("holdpoint builder")} (opens localhost:4321)
191
239
  Stack: ${chalk.cyan(stack)} Agents: ${chalk.cyan(agents.join(", "))}
192
240
  `);
193
241
  }
194
242
 
195
243
  // src/commands/check.ts
196
- import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
197
- import { join as join2 } from "path";
244
+ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "fs";
245
+ import { join as join3 } from "path";
198
246
  import chalk2 from "chalk";
199
247
  import ora2 from "ora";
200
248
  import { parseHoldpointYaml as parseHoldpointYaml2, matchesWhen } from "@holdpoint/yaml-core";
201
249
  import { runDeterministicChecks } from "@holdpoint/yaml-core/runner";
202
- import { execSync } from "child_process";
250
+ import { execSync as execSync2 } from "child_process";
251
+ import { randomUUID } from "crypto";
252
+ import { identifyProject } from "@holdpoint/live-daemon";
253
+ import { BridgeClient } from "@holdpoint/sdk";
203
254
  var COMMIT_CACHE_PATH = ".holdpoint/checked-commits.json";
204
255
  var COMMIT_CACHE_MAX = 100;
205
256
  var CHECK_REPORTS_PATH = ".holdpoint/check-reports.json";
206
257
  var CHECK_REPORTS_MAX = 50;
207
258
  function getStagedFiles() {
208
259
  try {
209
- const out = execSync("git diff --cached --name-only", {
260
+ const out = execSync2("git diff --cached --name-only", {
210
261
  encoding: "utf8",
211
262
  stdio: ["pipe", "pipe", "ignore"]
212
263
  });
@@ -217,7 +268,7 @@ function getStagedFiles() {
217
268
  }
218
269
  function getAllChangedFiles() {
219
270
  try {
220
- const out = execSync("git diff --name-only HEAD", {
271
+ const out = execSync2("git diff --name-only HEAD", {
221
272
  encoding: "utf8",
222
273
  stdio: ["pipe", "pipe", "ignore"]
223
274
  });
@@ -228,7 +279,7 @@ function getAllChangedFiles() {
228
279
  }
229
280
  function getLastCommitFiles() {
230
281
  try {
231
- const out = execSync("git diff --name-only HEAD~1 HEAD", {
282
+ const out = execSync2("git diff --name-only HEAD~1 HEAD", {
232
283
  encoding: "utf8",
233
284
  stdio: ["pipe", "pipe", "ignore"]
234
285
  });
@@ -239,7 +290,7 @@ function getLastCommitFiles() {
239
290
  }
240
291
  function getHeadSha() {
241
292
  try {
242
- return execSync("git rev-parse HEAD", {
293
+ return execSync2("git rev-parse HEAD", {
243
294
  encoding: "utf8",
244
295
  stdio: ["pipe", "pipe", "ignore"]
245
296
  }).trim();
@@ -260,16 +311,16 @@ function recordCommitCache(sha) {
260
311
  try {
261
312
  const existing = readCommitCache();
262
313
  const updated = [sha, ...[...existing].filter((s) => s !== sha)].slice(0, COMMIT_CACHE_MAX);
263
- mkdirSync2(join2(COMMIT_CACHE_PATH, ".."), { recursive: true });
264
- writeFileSync2(COMMIT_CACHE_PATH, JSON.stringify({ verified: updated }, null, 2) + "\n", "utf8");
314
+ mkdirSync2(join3(COMMIT_CACHE_PATH, ".."), { recursive: true });
315
+ writeFileSync3(COMMIT_CACHE_PATH, JSON.stringify({ verified: updated }, null, 2) + "\n", "utf8");
265
316
  } catch {
266
317
  }
267
318
  }
268
319
  function recordCheckReport(run) {
269
320
  try {
270
- mkdirSync2(join2(CHECK_REPORTS_PATH, ".."), { recursive: true });
321
+ mkdirSync2(join3(CHECK_REPORTS_PATH, ".."), { recursive: true });
271
322
  let existing = { runs: [] };
272
- if (existsSync3(CHECK_REPORTS_PATH)) {
323
+ if (existsSync4(CHECK_REPORTS_PATH)) {
273
324
  try {
274
325
  existing = JSON.parse(readFileSync3(CHECK_REPORTS_PATH, "utf8"));
275
326
  if (!Array.isArray(existing.runs)) existing.runs = [];
@@ -280,12 +331,12 @@ function recordCheckReport(run) {
280
331
  const updated = {
281
332
  runs: [run, ...existing.runs].slice(0, CHECK_REPORTS_MAX)
282
333
  };
283
- writeFileSync2(CHECK_REPORTS_PATH, JSON.stringify(updated, null, 2) + "\n", "utf8");
334
+ writeFileSync3(CHECK_REPORTS_PATH, JSON.stringify(updated, null, 2) + "\n", "utf8");
284
335
  } catch {
285
336
  }
286
337
  }
287
338
  async function checkCommand(options) {
288
- if (!existsSync3("checks.yaml")) {
339
+ if (!existsSync4("checks.yaml")) {
289
340
  console.error(chalk2.red("No checks.yaml found. Run `holdpoint init` first."));
290
341
  process.exit(1);
291
342
  }
@@ -404,6 +455,30 @@ ${chalk2.cyan("Agent prompts to act on:")}`);
404
455
  }
405
456
  };
406
457
  recordCheckReport(run);
458
+ const project = identifyProject(process.cwd());
459
+ const bridge = new BridgeClient();
460
+ const liveEvents = reportResults.filter(
461
+ (result) => result.kind === "cmd"
462
+ ).map((result, index) => ({
463
+ v: 1,
464
+ id: randomUUID(),
465
+ ts: Date.now() + index,
466
+ engine: "holdpoint",
467
+ session_id: "check-runner",
468
+ project_hash: project.hash,
469
+ cwd: process.cwd(),
470
+ type: "check_run",
471
+ payload: {
472
+ check_id: result.id,
473
+ label: result.label,
474
+ status: result.status,
475
+ duration_ms: 0,
476
+ ...result.output ? { output: result.output } : {}
477
+ }
478
+ }));
479
+ if (liveEvents.length > 0) {
480
+ await bridge.sendEvents(liveEvents);
481
+ }
407
482
  if (failed.length > 0) {
408
483
  process.exit(1);
409
484
  }
@@ -425,11 +500,11 @@ function printResult(result) {
425
500
  }
426
501
 
427
502
  // src/commands/validate.ts
428
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
503
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
429
504
  import chalk3 from "chalk";
430
505
  import { parseHoldpointYaml as parseHoldpointYaml3, validateConfig } from "@holdpoint/yaml-core";
431
506
  async function validateCommand() {
432
- if (!existsSync4("checks.yaml")) {
507
+ if (!existsSync5("checks.yaml")) {
433
508
  console.error(chalk3.red("No checks.yaml found. Run `holdpoint init` first."));
434
509
  process.exit(1);
435
510
  }
@@ -459,7 +534,7 @@ async function validateCommand() {
459
534
  }
460
535
 
461
536
  // src/commands/update.ts
462
- import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
537
+ import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
463
538
  import chalk4 from "chalk";
464
539
  import ora3 from "ora";
465
540
  import { parseHoldpointYaml as parseHoldpointYaml4 } from "@holdpoint/yaml-core";
@@ -472,8 +547,18 @@ import {
472
547
  buildCheckScript as buildCodexCheckScript2,
473
548
  spliceAgentsMd as spliceAgentsMd2
474
549
  } from "@holdpoint/engine-codex";
550
+ var MINIMAL_PREREQUISITES2 = `# Holdpoint prerequisites
551
+
552
+ Holdpoint installed repo-local adapters for one or more AI coding agents. Before relying on them locally, review these setup notes:
553
+
554
+ - **GitHub Copilot CLI** \u2014 Holdpoint's \`.github/extensions/holdpoint/extension.mjs\` uses the Copilot CLI **EXTENSIONS** feature. Today that feature is gated behind experimental mode. In Copilot CLI, run \`/experimental on\` so **EXTENSIONS** appears in the enabled feature set before using Holdpoint locally.
555
+ - **OpenAI Codex** \u2014 project-level hooks require trust approval. Run \`codex trust\` in the Codex TUI or review the hook with \`/hooks\`.
556
+ - **General** \u2014 Holdpoint expects Node.js 18+ and a git repository so \`holdpoint init\`, \`holdpoint update\`, and \`holdpoint check\` can run normally.
557
+
558
+ Docs: https://holdpoint.dev/docs
559
+ `;
475
560
  async function updateCommand() {
476
- if (!existsSync5("checks.yaml")) {
561
+ if (!existsSync6("checks.yaml")) {
477
562
  console.error(chalk4.red("No checks.yaml found. Run `holdpoint init` first."));
478
563
  process.exit(1);
479
564
  }
@@ -483,24 +568,24 @@ async function updateCommand() {
483
568
  const agents = detected.length > 0 ? detected : ["copilot", "claude", "cursor", "codex"];
484
569
  const generatedDir = ".github/holdpoint/generated";
485
570
  mkdirSync3(generatedDir, { recursive: true });
486
- writeFileSync3(`${generatedDir}/checks.immutable.json`, buildConfigJson2(config), "utf8");
571
+ writeFileSync4(`${generatedDir}/checks.immutable.json`, buildConfigJson2(config), "utf8");
487
572
  if (agents.includes("copilot")) {
488
573
  const extDir = ".github/extensions/holdpoint";
489
574
  mkdirSync3(extDir, { recursive: true });
490
- writeFileSync3(`${extDir}/extension.mjs`, buildEngine2(config), "utf8");
575
+ writeFileSync4(`${extDir}/extension.mjs`, buildEngine2(config), "utf8");
491
576
  }
492
577
  if (agents.includes("claude")) {
493
578
  mkdirSync3(".claude", { recursive: true });
494
579
  const settingsPath = ".claude/settings.json";
495
580
  let existing = {};
496
- if (existsSync5(settingsPath)) {
581
+ if (existsSync6(settingsPath)) {
497
582
  try {
498
583
  existing = JSON.parse(readFileSync5(settingsPath, "utf8"));
499
584
  } catch {
500
585
  }
501
586
  }
502
587
  const hooks = JSON.parse(buildClaudeEngineJson2(config));
503
- writeFileSync3(
588
+ writeFileSync4(
504
589
  settingsPath,
505
590
  JSON.stringify({ ...existing, hooks: hooks.hooks }, null, 2) + "\n"
506
591
  );
@@ -508,47 +593,59 @@ async function updateCommand() {
508
593
  if (agents.includes("cursor")) {
509
594
  const cursorRules = buildCursorEngine2(config);
510
595
  const cursorPath = ".cursorrules";
511
- if (existsSync5(cursorPath)) {
596
+ if (existsSync6(cursorPath)) {
512
597
  const content = readFileSync5(cursorPath, "utf8");
513
598
  const start = content.indexOf("# \u2500\u2500\u2500 Holdpoint Rules");
514
599
  const end = content.indexOf("# \u2500\u2500\u2500 End Holdpoint Rules \u2500\u2500\u2500");
515
600
  if (start !== -1 && end !== -1) {
516
601
  const afterEnd = content.indexOf("\n", end);
517
602
  const updated = content.slice(0, start) + cursorRules + content.slice(afterEnd === -1 ? end : afterEnd + 1);
518
- writeFileSync3(cursorPath, updated);
603
+ writeFileSync4(cursorPath, updated);
519
604
  } else {
520
- writeFileSync3(cursorPath, content + "\n" + cursorRules);
605
+ writeFileSync4(cursorPath, content + "\n" + cursorRules);
521
606
  }
522
607
  }
523
608
  }
524
609
  if (agents.includes("codex")) {
525
610
  mkdirSync3(".codex", { recursive: true });
526
- writeFileSync3(".codex/hooks.json", buildCodexHooksJson2(config), "utf8");
527
- writeFileSync3(".codex/holdpoint-check.mjs", buildCodexCheckScript2(config), "utf8");
611
+ writeFileSync4(".codex/hooks.json", buildCodexHooksJson2(config), "utf8");
612
+ writeFileSync4(".codex/holdpoint-check.mjs", buildCodexCheckScript2(config), "utf8");
528
613
  const configTomlPath = ".codex/config.toml";
529
- if (!existsSync5(configTomlPath)) {
530
- writeFileSync3(configTomlPath, buildCodexConfigToml2(), "utf8");
614
+ if (!existsSync6(configTomlPath)) {
615
+ writeFileSync4(configTomlPath, buildCodexConfigToml2(), "utf8");
531
616
  } else {
532
617
  const existing2 = readFileSync5(configTomlPath, "utf8");
533
618
  if (!existing2.includes("[features]")) {
534
- writeFileSync3(configTomlPath, existing2.trimEnd() + "\n\n" + buildCodexConfigToml2(), "utf8");
619
+ writeFileSync4(configTomlPath, existing2.trimEnd() + "\n\n" + buildCodexConfigToml2(), "utf8");
535
620
  }
536
621
  }
537
622
  const agentsMdPath = "AGENTS.md";
538
- const existing = existsSync5(agentsMdPath) ? readFileSync5(agentsMdPath, "utf8") : "";
539
- writeFileSync3(agentsMdPath, spliceAgentsMd2(existing, config), "utf8");
623
+ const existing = existsSync6(agentsMdPath) ? readFileSync5(agentsMdPath, "utf8") : "";
624
+ writeFileSync4(agentsMdPath, spliceAgentsMd2(existing, config), "utf8");
540
625
  }
626
+ const wrotePrerequisites = ensureBundledFile(
627
+ "HOLDPOINT_PREREQUISITES.md",
628
+ "HOLDPOINT_PREREQUISITES.md",
629
+ MINIMAL_PREREQUISITES2
630
+ );
541
631
  spinner.succeed(chalk4.green("Engine files updated from current checks.yaml"));
632
+ if (wrotePrerequisites) {
633
+ console.log(
634
+ chalk4.cyan(
635
+ "Created HOLDPOINT_PREREQUISITES.md with Copilot experimental-mode and other agent setup notes."
636
+ )
637
+ );
638
+ }
542
639
  }
543
640
 
544
641
  // src/commands/build.ts
545
642
  import { createServer } from "http";
546
- import { createReadStream, existsSync as existsSync6 } from "fs";
547
- import { join as join3, extname, dirname as dirname2 } from "path";
548
- import { fileURLToPath as fileURLToPath2 } from "url";
549
- import { execSync as execSync2 } from "child_process";
643
+ import { createReadStream, existsSync as existsSync7 } from "fs";
644
+ import { join as join4, extname, dirname as dirname3 } from "path";
645
+ import { fileURLToPath as fileURLToPath3 } from "url";
646
+ import { execSync as execSync3 } from "child_process";
550
647
  import chalk5 from "chalk";
551
- var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
648
+ var __dirname3 = dirname3(fileURLToPath3(import.meta.url));
552
649
  var MIME = {
553
650
  ".html": "text/html; charset=utf-8",
554
651
  ".js": "text/javascript",
@@ -570,8 +667,8 @@ function serveFile(res, filePath) {
570
667
  function handleRequest(req, res, uiDir) {
571
668
  const url = (req.url ?? "/").split("?")[0] ?? "/";
572
669
  if (url === "/__holdpoint/initial-yaml") {
573
- const checksPath = join3(process.cwd(), "checks.yaml");
574
- if (existsSync6(checksPath)) {
670
+ const checksPath = join4(process.cwd(), "checks.yaml");
671
+ if (existsSync7(checksPath)) {
575
672
  res.writeHead(200, { "Content-Type": "text/yaml; charset=utf-8" });
576
673
  createReadStream(checksPath).pipe(res);
577
674
  } else {
@@ -581,8 +678,8 @@ function handleRequest(req, res, uiDir) {
581
678
  return;
582
679
  }
583
680
  if (url === "/__holdpoint/initial-reports") {
584
- const reportsPath = join3(process.cwd(), ".holdpoint", "check-reports.json");
585
- if (existsSync6(reportsPath)) {
681
+ const reportsPath = join4(process.cwd(), ".holdpoint", "check-reports.json");
682
+ if (existsSync7(reportsPath)) {
586
683
  res.writeHead(200, { "Content-Type": "application/json" });
587
684
  createReadStream(reportsPath).pipe(res);
588
685
  } else {
@@ -591,21 +688,21 @@ function handleRequest(req, res, uiDir) {
591
688
  }
592
689
  return;
593
690
  }
594
- const candidate = join3(uiDir, url === "/" ? "index.html" : url);
595
- const filePath = existsSync6(candidate) ? candidate : join3(uiDir, "index.html");
691
+ const candidate = join4(uiDir, url === "/" ? "index.html" : url);
692
+ const filePath = existsSync7(candidate) ? candidate : join4(uiDir, "index.html");
596
693
  serveFile(res, filePath);
597
694
  }
598
695
  async function buildCommand() {
599
696
  const port = 4321;
600
- const uiDir = join3(__dirname2, "builder-ui");
601
- if (!existsSync6(uiDir)) {
697
+ const uiDir = join4(__dirname3, "builder-ui");
698
+ if (!existsSync7(uiDir)) {
602
699
  console.error(chalk5.red("\u2717 Builder UI not found.\n"));
603
700
  console.log(chalk5.dim(" This is unexpected for a published build of @holdpoint/cli."));
604
701
  console.log(chalk5.dim(" If you installed from source, rebuild with: pnpm turbo build\n"));
605
702
  process.exit(1);
606
703
  }
607
704
  const server = createServer((req, res) => handleRequest(req, res, uiDir));
608
- await new Promise((resolve, reject) => {
705
+ await new Promise((resolve2, reject) => {
609
706
  server.listen(port, () => {
610
707
  console.log(
611
708
  `
@@ -615,29 +712,29 @@ ${chalk5.green("\u2713")} Holdpoint builder running at ${chalk5.cyan(`http://loc
615
712
  console.log(chalk5.dim(" Press Ctrl+C to stop\n"));
616
713
  const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
617
714
  try {
618
- execSync2(`${openCmd} http://localhost:${port}`, { stdio: "ignore" });
715
+ execSync3(`${openCmd} http://localhost:${port}`, { stdio: "ignore" });
619
716
  } catch {
620
717
  }
621
718
  });
622
719
  server.on("error", reject);
623
720
  process.on("SIGINT", () => {
624
721
  console.log(chalk5.dim("\n Stopping builder\u2026"));
625
- server.close(() => resolve());
722
+ server.close(() => resolve2());
626
723
  });
627
724
  });
628
725
  }
629
726
 
630
727
  // src/commands/evolve.ts
631
- import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
632
- import { execSync as execSync5 } from "child_process";
728
+ import { existsSync as existsSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
729
+ import { execSync as execSync6 } from "child_process";
633
730
  import chalk6 from "chalk";
634
731
  import ora4 from "ora";
635
732
  import { parseHoldpointYaml as parseHoldpointYaml5, generateYaml } from "@holdpoint/yaml-core";
636
733
 
637
734
  // src/evolve/scanner.ts
638
- import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync } from "fs";
639
- import { join as join4 } from "path";
640
- import { execSync as execSync3 } from "child_process";
735
+ import { existsSync as existsSync8, readFileSync as readFileSync6, readdirSync } from "fs";
736
+ import { join as join5 } from "path";
737
+ import { execSync as execSync4 } from "child_process";
641
738
  function tryReadJson(path) {
642
739
  try {
643
740
  return JSON.parse(readFileSync6(path, "utf8"));
@@ -653,17 +750,17 @@ function tryReadText(path) {
653
750
  }
654
751
  }
655
752
  function scanProject(cwd = process.cwd()) {
656
- const exists = (p) => existsSync7(join4(cwd, p));
753
+ const exists = (p) => existsSync8(join5(cwd, p));
657
754
  const packageManager = exists("pnpm-lock.yaml") ? "pnpm" : exists("yarn.lock") ? "yarn" : exists("bun.lockb") ? "bun" : "npm";
658
- const pkg = tryReadJson(join4(cwd, "package.json"));
755
+ const pkg = tryReadJson(join5(cwd, "package.json"));
659
756
  const scripts = pkg?.scripts ?? {};
660
757
  const deps = /* @__PURE__ */ new Set([
661
758
  ...Object.keys(pkg?.dependencies ?? {}),
662
759
  ...Object.keys(pkg?.devDependencies ?? {})
663
760
  ]);
664
- const pyprojectText = tryReadText(join4(cwd, "pyproject.toml"));
665
- const requirementsText = tryReadText(join4(cwd, "requirements.txt"));
666
- const pipfileText = tryReadText(join4(cwd, "Pipfile"));
761
+ const pyprojectText = tryReadText(join5(cwd, "pyproject.toml"));
762
+ const requirementsText = tryReadText(join5(cwd, "requirements.txt"));
763
+ const pipfileText = tryReadText(join5(cwd, "Pipfile"));
667
764
  const allPyText = pyprojectText + requirementsText + pipfileText;
668
765
  const hasPytest = exists("pytest.ini") || exists("setup.cfg") || allPyText.includes("pytest") || allPyText.includes("[tool.pytest");
669
766
  const hasRuff = allPyText.includes("ruff") || deps.has("ruff");
@@ -715,9 +812,9 @@ function scanProject(cwd = process.cwd()) {
715
812
  }
716
813
 
717
814
  // src/evolve/dead-checker.ts
718
- import { execSync as execSync4 } from "child_process";
719
- import { readdirSync as readdirSync2, existsSync as existsSync8 } from "fs";
720
- import { join as join5 } from "path";
815
+ import { execSync as execSync5 } from "child_process";
816
+ import { readdirSync as readdirSync2, existsSync as existsSync9 } from "fs";
817
+ import { join as join6 } from "path";
721
818
  var NAMED_SCOPES = /* @__PURE__ */ new Set([
722
819
  "frontend",
723
820
  "backend",
@@ -762,7 +859,7 @@ function walkDir(dir, root, depth, maxDepth) {
762
859
  const results = [];
763
860
  for (const entry of entries) {
764
861
  if (WALK_IGNORED.has(entry) || entry.startsWith(".")) continue;
765
- const full = join5(dir, entry);
862
+ const full = join6(dir, entry);
766
863
  const rel = full.slice(root.length + 1);
767
864
  results.push(rel);
768
865
  const children = walkDir(full, root, depth + 1, maxDepth);
@@ -772,7 +869,7 @@ function walkDir(dir, root, depth, maxDepth) {
772
869
  }
773
870
  function getRepoFiles(cwd) {
774
871
  try {
775
- const out = execSync4("git ls-files", {
872
+ const out = execSync5("git ls-files", {
776
873
  cwd,
777
874
  encoding: "utf8",
778
875
  stdio: ["pipe", "pipe", "ignore"]
@@ -809,7 +906,7 @@ function detectStaleChecks(config, repoFiles) {
809
906
  if (matches.length === 0) {
810
907
  const label = patternAlias ? `Pattern '${patternAlias}' (= '${regexStr}')` : `Regex '${regexStr}'`;
811
908
  const suggestedConditionPath = extractPathFromRegex(regexStr);
812
- const pathGone = !suggestedConditionPath || !existsSync8(join5(process.cwd(), suggestedConditionPath));
909
+ const pathGone = !suggestedConditionPath || !existsSync9(join6(process.cwd(), suggestedConditionPath));
813
910
  if (pathGone) {
814
911
  stale.push({
815
912
  check,
@@ -1010,7 +1107,7 @@ function withHeader(header, newYaml) {
1010
1107
  return header + "\n\n" + newYaml;
1011
1108
  }
1012
1109
  async function evolveCommand(options) {
1013
- if (!existsSync9("checks.yaml")) {
1110
+ if (!existsSync10("checks.yaml")) {
1014
1111
  console.error(chalk6.red("No checks.yaml found. Run `holdpoint init` first."));
1015
1112
  process.exit(1);
1016
1113
  }
@@ -1132,10 +1229,10 @@ async function evolveCommand(options) {
1132
1229
  };
1133
1230
  const header = extractHeader(yamlContent);
1134
1231
  const newYaml = withHeader(header, generateYaml(updatedConfig));
1135
- writeFileSync4("checks.yaml", newYaml, "utf8");
1232
+ writeFileSync5("checks.yaml", newYaml, "utf8");
1136
1233
  applySpinner.text = "Running holdpoint update\u2026";
1137
1234
  try {
1138
- execSync5("npx @holdpoint/cli@alpha update", { stdio: "pipe" });
1235
+ execSync6("npx @holdpoint/cli@alpha update", { stdio: "pipe" });
1139
1236
  } catch {
1140
1237
  applySpinner.warn(
1141
1238
  chalk6.yellow("checks.yaml updated, but `holdpoint update` failed \u2014 run it manually.")
@@ -1157,9 +1254,652 @@ function printAppliedSummary(added, wrapped) {
1157
1254
  );
1158
1255
  }
1159
1256
 
1257
+ // src/commands/live.ts
1258
+ import { existsSync as existsSync11 } from "fs";
1259
+ import { dirname as dirname4, join as join7 } from "path";
1260
+ import chalk7 from "chalk";
1261
+ import { identifyProject as identifyProject2 } from "@holdpoint/live-daemon";
1262
+
1263
+ // src/lib/ensure-daemon.ts
1264
+ import { spawn } from "child_process";
1265
+ import { readHealthyDaemonLock } from "@holdpoint/live-daemon";
1266
+ function sleep(ms) {
1267
+ return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
1268
+ }
1269
+ async function ensureDaemon(timeoutMs = 5e3) {
1270
+ const existing = await readHealthyDaemonLock();
1271
+ if (existing) {
1272
+ return { info: existing, started: false };
1273
+ }
1274
+ const cliEntry = process.argv[1];
1275
+ if (!cliEntry) {
1276
+ throw new Error("Cannot determine the current holdpoint CLI entrypoint");
1277
+ }
1278
+ const child = spawn(process.execPath, [cliEntry, "daemon-serve"], {
1279
+ stdio: "ignore",
1280
+ env: process.env,
1281
+ cwd: process.cwd()
1282
+ });
1283
+ child.unref();
1284
+ const deadline = Date.now() + timeoutMs;
1285
+ while (Date.now() < deadline) {
1286
+ const lock = await readHealthyDaemonLock();
1287
+ if (lock) {
1288
+ return { info: lock, started: true };
1289
+ }
1290
+ await sleep(100);
1291
+ }
1292
+ throw new Error("Daemon unavailable + cannot spawn");
1293
+ }
1294
+
1295
+ // src/lib/open-browser.ts
1296
+ import { execSync as execSync7 } from "child_process";
1297
+ function openBrowser(url) {
1298
+ const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
1299
+ try {
1300
+ execSync7(`${openCmd} ${JSON.stringify(url)}`, { stdio: "ignore" });
1301
+ } catch {
1302
+ }
1303
+ }
1304
+
1305
+ // src/commands/live.ts
1306
+ function findChecksYaml(startDir) {
1307
+ let current = startDir;
1308
+ for (; ; ) {
1309
+ const candidate = join7(current, "checks.yaml");
1310
+ if (existsSync11(candidate)) {
1311
+ return candidate;
1312
+ }
1313
+ const parent = dirname4(current);
1314
+ if (parent === current) {
1315
+ return null;
1316
+ }
1317
+ current = parent;
1318
+ }
1319
+ }
1320
+ function tryResolveCurrentProject() {
1321
+ const checksYaml = findChecksYaml(process.cwd());
1322
+ if (checksYaml) {
1323
+ return identifyProject2(dirname4(checksYaml));
1324
+ }
1325
+ try {
1326
+ return identifyProject2(process.cwd());
1327
+ } catch {
1328
+ return null;
1329
+ }
1330
+ }
1331
+ async function liveCommand(options = {}) {
1332
+ const { info, started } = await ensureDaemon();
1333
+ const baseUrl = new URL(`/__holdpoint/live-auth`, `http://127.0.0.1:${info.port}`);
1334
+ baseUrl.searchParams.set("token", info.token);
1335
+ const currentProject = options.project ? null : tryResolveCurrentProject();
1336
+ if (options.project) {
1337
+ baseUrl.searchParams.set("project", options.project);
1338
+ } else if (currentProject) {
1339
+ baseUrl.searchParams.set("project", currentProject.hash);
1340
+ baseUrl.searchParams.set("name", currentProject.name);
1341
+ baseUrl.searchParams.set("root", currentProject.root);
1342
+ }
1343
+ openBrowser(baseUrl.toString());
1344
+ console.log(
1345
+ chalk7.green(
1346
+ started ? "\u2713 Started Holdpoint Live and opened the browser" : "\u2713 Opened Holdpoint Live"
1347
+ )
1348
+ );
1349
+ console.log(` url: ${chalk7.cyan(baseUrl.toString())}`);
1350
+ }
1351
+
1352
+ // src/commands/daemon.ts
1353
+ import chalk8 from "chalk";
1354
+ import {
1355
+ DaemonAlreadyRunningError,
1356
+ isProcessAlive,
1357
+ readDaemonLock,
1358
+ readHealthyDaemonLock as readHealthyDaemonLock2,
1359
+ removeDaemonLock,
1360
+ startDaemonProcess
1361
+ } from "@holdpoint/live-daemon";
1362
+
1363
+ // src/version.ts
1364
+ var CLI_VERSION = "0.1.0-alpha.15";
1365
+
1366
+ // src/commands/daemon.ts
1367
+ function formatUptime(lock) {
1368
+ const seconds = Math.max(0, Math.floor((Date.now() - lock.started_at) / 1e3));
1369
+ const minutes = Math.floor(seconds / 60);
1370
+ const remainingSeconds = seconds % 60;
1371
+ return minutes > 0 ? `${minutes}m ${remainingSeconds}s` : `${remainingSeconds}s`;
1372
+ }
1373
+ async function fetchSessionCount(lock) {
1374
+ try {
1375
+ const response = await fetch(`http://127.0.0.1:${lock.port}/v1/sessions`, {
1376
+ headers: {
1377
+ authorization: `Bearer ${lock.token}`
1378
+ }
1379
+ });
1380
+ if (!response.ok) return null;
1381
+ const parsed = await response.json();
1382
+ return Array.isArray(parsed.sessions) ? parsed.sessions.length : null;
1383
+ } catch {
1384
+ return null;
1385
+ }
1386
+ }
1387
+ function sleep2(ms) {
1388
+ return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
1389
+ }
1390
+ async function daemonStartCommand() {
1391
+ const { info, started } = await ensureDaemon();
1392
+ const sessionCount = await fetchSessionCount(info);
1393
+ const headline = started ? "Started Holdpoint Live daemon" : "Reused existing Holdpoint Live daemon";
1394
+ console.log(chalk8.green(`\u2713 ${headline}`));
1395
+ console.log(` pid: ${chalk8.cyan(String(info.pid))}`);
1396
+ console.log(` port: ${chalk8.cyan(String(info.port))}`);
1397
+ console.log(` uptime: ${chalk8.cyan(formatUptime(info))}`);
1398
+ if (sessionCount !== null) {
1399
+ console.log(` sessions: ${chalk8.cyan(String(sessionCount))}`);
1400
+ }
1401
+ }
1402
+ async function daemonStatusCommand() {
1403
+ const lock = await readHealthyDaemonLock2();
1404
+ if (!lock) {
1405
+ console.log(chalk8.yellow("Holdpoint Live daemon is not running."));
1406
+ return;
1407
+ }
1408
+ const sessionCount = await fetchSessionCount(lock);
1409
+ console.log(chalk8.green("\u2713 Holdpoint Live daemon is running"));
1410
+ console.log(` pid: ${chalk8.cyan(String(lock.pid))}`);
1411
+ console.log(` port: ${chalk8.cyan(String(lock.port))}`);
1412
+ console.log(` uptime: ${chalk8.cyan(formatUptime(lock))}`);
1413
+ if (sessionCount !== null) {
1414
+ console.log(` sessions: ${chalk8.cyan(String(sessionCount))}`);
1415
+ }
1416
+ }
1417
+ async function daemonStopCommand() {
1418
+ const lock = readDaemonLock();
1419
+ if (!lock) {
1420
+ console.log(chalk8.yellow("Holdpoint Live daemon is not running."));
1421
+ return;
1422
+ }
1423
+ if (!isProcessAlive(lock.pid)) {
1424
+ removeDaemonLock(void 0, lock.token);
1425
+ console.log(chalk8.yellow("Removed stale Holdpoint Live lockfile."));
1426
+ return;
1427
+ }
1428
+ process.kill(lock.pid, "SIGTERM");
1429
+ const deadline = Date.now() + 5e3;
1430
+ while (Date.now() < deadline) {
1431
+ if (!isProcessAlive(lock.pid)) {
1432
+ removeDaemonLock(void 0, lock.token);
1433
+ console.log(chalk8.green(`\u2713 Stopped Holdpoint Live daemon (${lock.pid})`));
1434
+ return;
1435
+ }
1436
+ await sleep2(100);
1437
+ }
1438
+ process.kill(lock.pid, "SIGKILL");
1439
+ await sleep2(100);
1440
+ removeDaemonLock(void 0, lock.token);
1441
+ console.log(chalk8.green(`\u2713 Force-stopped Holdpoint Live daemon (${lock.pid})`));
1442
+ }
1443
+ async function daemonServeCommand(options) {
1444
+ try {
1445
+ const daemon2 = await startDaemonProcess({
1446
+ version: CLI_VERSION,
1447
+ ...options.port ? { port: Number(options.port) } : {}
1448
+ });
1449
+ await daemon2.closed;
1450
+ } catch (error) {
1451
+ if (error instanceof DaemonAlreadyRunningError) {
1452
+ process.exit(0);
1453
+ }
1454
+ throw error;
1455
+ }
1456
+ }
1457
+
1458
+ // src/commands/engines.ts
1459
+ import chalk9 from "chalk";
1460
+
1461
+ // src/engines.ts
1462
+ import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
1463
+ import { dirname as dirname5, join as join8, resolve, sep } from "path";
1464
+ import { createRequire } from "module";
1465
+ import { fileURLToPath as fileURLToPath4, pathToFileURL } from "url";
1466
+ import { parseEventV1 } from "@holdpoint/live-protocol";
1467
+ var require2 = createRequire(import.meta.url);
1468
+ var CLI_SRC_DIR = dirname5(fileURLToPath4(import.meta.url));
1469
+ var MONOREPO_ROOT = resolve(CLI_SRC_DIR, "../../..");
1470
+ var BUILTIN_LIVE_ENGINE_PACKAGES = ["@holdpoint/engine-claude"];
1471
+ var HOLDPOINT_ENGINE_KEYWORD = "holdpoint-engine";
1472
+ function isObject(value) {
1473
+ return value != null && typeof value === "object" && !Array.isArray(value);
1474
+ }
1475
+ function isExternalLiveEnginePackageName(packageName) {
1476
+ return /^holdpoint-engine-[a-z0-9-]+$/.test(packageName) || /^@[a-z0-9_.-]+\/holdpoint-engine-[a-z0-9-]+$/.test(packageName);
1477
+ }
1478
+ function readJsonFile(path) {
1479
+ if (!existsSync12(path)) {
1480
+ return null;
1481
+ }
1482
+ try {
1483
+ const parsed = JSON.parse(readFileSync8(path, "utf8"));
1484
+ return isObject(parsed) ? parsed : null;
1485
+ } catch {
1486
+ return null;
1487
+ }
1488
+ }
1489
+ function findNearestPackageRoot(startDir) {
1490
+ let current = resolve(startDir);
1491
+ while (true) {
1492
+ if (existsSync12(join8(current, "package.json"))) {
1493
+ return current;
1494
+ }
1495
+ const parent = dirname5(current);
1496
+ if (parent === current) {
1497
+ return resolve(startDir);
1498
+ }
1499
+ current = parent;
1500
+ }
1501
+ }
1502
+ function findPackageRootFromFile(path) {
1503
+ let current = dirname5(path);
1504
+ while (true) {
1505
+ if (existsSync12(join8(current, "package.json"))) {
1506
+ return current;
1507
+ }
1508
+ const parent = dirname5(current);
1509
+ if (parent === current) {
1510
+ return null;
1511
+ }
1512
+ current = parent;
1513
+ }
1514
+ }
1515
+ function getDependencyEnginePackageNames(projectRoot) {
1516
+ const packageJson = readJsonFile(join8(projectRoot, "package.json"));
1517
+ if (!packageJson) {
1518
+ return [];
1519
+ }
1520
+ const packageNames = /* @__PURE__ */ new Set();
1521
+ for (const field of ["dependencies", "devDependencies", "optionalDependencies"]) {
1522
+ const deps = packageJson[field];
1523
+ if (!isObject(deps)) {
1524
+ continue;
1525
+ }
1526
+ for (const packageName of Object.keys(deps)) {
1527
+ if (isExternalLiveEnginePackageName(packageName)) {
1528
+ packageNames.add(packageName);
1529
+ }
1530
+ }
1531
+ }
1532
+ return [...packageNames];
1533
+ }
1534
+ function resolvePackageRoot(packageName, projectRoot) {
1535
+ try {
1536
+ const entryPath = require2.resolve(packageName);
1537
+ return findPackageRootFromFile(entryPath);
1538
+ } catch {
1539
+ }
1540
+ try {
1541
+ const entryPath = require2.resolve(packageName, {
1542
+ paths: [projectRoot, process.cwd()]
1543
+ });
1544
+ return findPackageRootFromFile(entryPath);
1545
+ } catch {
1546
+ }
1547
+ try {
1548
+ const packageJsonPath = require2.resolve(`${packageName}/package.json`, {
1549
+ paths: [projectRoot, process.cwd()]
1550
+ });
1551
+ return dirname5(packageJsonPath);
1552
+ } catch {
1553
+ if (packageName.startsWith("@holdpoint/")) {
1554
+ const scopedName = packageName.split("/")[1];
1555
+ if (scopedName) {
1556
+ const packageDir = resolve(MONOREPO_ROOT, "packages", scopedName);
1557
+ if (existsSync12(join8(packageDir, "package.json"))) {
1558
+ return packageDir;
1559
+ }
1560
+ }
1561
+ }
1562
+ return null;
1563
+ }
1564
+ }
1565
+ function formatImportError(error) {
1566
+ return error instanceof Error && error.message ? error.message : String(error);
1567
+ }
1568
+ function parseManifest(value) {
1569
+ if (!isObject(value)) {
1570
+ return null;
1571
+ }
1572
+ if (value.manifestVersion !== 1) {
1573
+ return null;
1574
+ }
1575
+ if (typeof value.id !== "string" || !/^[a-z0-9-]+$/.test(value.id)) {
1576
+ return null;
1577
+ }
1578
+ if (typeof value.displayName !== "string" || !value.displayName.trim()) {
1579
+ return null;
1580
+ }
1581
+ return {
1582
+ manifestVersion: 1,
1583
+ id: value.id,
1584
+ displayName: value.displayName
1585
+ };
1586
+ }
1587
+ function parseLiveCapabilities(value) {
1588
+ if (!isObject(value)) {
1589
+ return null;
1590
+ }
1591
+ const capabilities = {};
1592
+ for (const key of [
1593
+ "can_stream",
1594
+ "can_control",
1595
+ "can_modify_context",
1596
+ "can_register_tools",
1597
+ "control_online"
1598
+ ]) {
1599
+ const entry = value[key];
1600
+ if (entry === void 0) {
1601
+ continue;
1602
+ }
1603
+ if (typeof entry !== "boolean") {
1604
+ return null;
1605
+ }
1606
+ capabilities[key] = entry;
1607
+ }
1608
+ return capabilities;
1609
+ }
1610
+ function parseLiveAdapter(value, manifest) {
1611
+ if (!isObject(value)) {
1612
+ return null;
1613
+ }
1614
+ if (typeof value.id !== "string" || typeof value.displayName !== "string") {
1615
+ return null;
1616
+ }
1617
+ if (value.id !== manifest.id || value.displayName !== manifest.displayName) {
1618
+ return null;
1619
+ }
1620
+ const capabilities = parseLiveCapabilities(value.capabilities);
1621
+ if (!capabilities) {
1622
+ return null;
1623
+ }
1624
+ const generateBridgeCommand = value.generateBridgeCommand;
1625
+ if (typeof generateBridgeCommand !== "function") {
1626
+ return null;
1627
+ }
1628
+ const translateHookInput = value.translateHookInput;
1629
+ if (typeof translateHookInput !== "function") {
1630
+ return null;
1631
+ }
1632
+ return {
1633
+ id: value.id,
1634
+ displayName: value.displayName,
1635
+ capabilities,
1636
+ generateBridgeCommand: () => {
1637
+ const command = generateBridgeCommand();
1638
+ if (typeof command !== "string") {
1639
+ throw new Error("adapter.generateBridgeCommand() must return a string");
1640
+ }
1641
+ return command;
1642
+ },
1643
+ translateHookInput: (raw, options) => {
1644
+ const event = translateHookInput(raw, options);
1645
+ return event == null ? null : parseEventV1(event);
1646
+ }
1647
+ };
1648
+ }
1649
+ async function importModule(modulePath) {
1650
+ const moduleUrl = pathToFileURL(modulePath).href;
1651
+ return await import(moduleUrl);
1652
+ }
1653
+ function resolvePackageAssetPath(packageRoot, relativePath) {
1654
+ const declaredPath = resolve(packageRoot, relativePath);
1655
+ const sourceFallback = resolve(
1656
+ packageRoot,
1657
+ relativePath.replace(/^\.\/dist\//, "./src/").replace(/\.js$/, ".ts")
1658
+ );
1659
+ if (packageRoot.startsWith(resolve(MONOREPO_ROOT, "packages") + sep) && existsSync12(sourceFallback)) {
1660
+ return sourceFallback;
1661
+ }
1662
+ if (existsSync12(declaredPath)) {
1663
+ return declaredPath;
1664
+ }
1665
+ return sourceFallback;
1666
+ }
1667
+ async function resolveCandidate(packageName, source, projectRoot) {
1668
+ const packageRoot = resolvePackageRoot(packageName, projectRoot);
1669
+ if (!packageRoot) {
1670
+ return {
1671
+ packageName,
1672
+ source,
1673
+ status: "ignored",
1674
+ reason: "package could not be resolved from this project"
1675
+ };
1676
+ }
1677
+ const packageJson = readJsonFile(join8(packageRoot, "package.json"));
1678
+ if (!packageJson) {
1679
+ return {
1680
+ packageName,
1681
+ source,
1682
+ status: "ignored",
1683
+ reason: "package.json could not be read"
1684
+ };
1685
+ }
1686
+ const keywords = Array.isArray(packageJson.keywords) ? packageJson.keywords : [];
1687
+ if (!keywords.includes(HOLDPOINT_ENGINE_KEYWORD)) {
1688
+ return {
1689
+ packageName,
1690
+ source,
1691
+ status: "ignored",
1692
+ reason: `missing \`${HOLDPOINT_ENGINE_KEYWORD}\` keyword`
1693
+ };
1694
+ }
1695
+ const metadata = isObject(packageJson.holdpoint) ? packageJson.holdpoint : void 0;
1696
+ if (!metadata?.manifest) {
1697
+ return {
1698
+ packageName,
1699
+ source,
1700
+ status: "ignored",
1701
+ reason: "missing `holdpoint.manifest` package.json field"
1702
+ };
1703
+ }
1704
+ if (!metadata.adapter) {
1705
+ return {
1706
+ packageName,
1707
+ source,
1708
+ status: "ignored",
1709
+ reason: "missing `holdpoint.adapter` package.json field"
1710
+ };
1711
+ }
1712
+ const manifestPath = resolvePackageAssetPath(packageRoot, metadata.manifest);
1713
+ const adapterPath = resolvePackageAssetPath(packageRoot, metadata.adapter);
1714
+ if (!existsSync12(manifestPath)) {
1715
+ return {
1716
+ packageName,
1717
+ source,
1718
+ status: "ignored",
1719
+ reason: "manifest file does not exist"
1720
+ };
1721
+ }
1722
+ if (!existsSync12(adapterPath)) {
1723
+ return {
1724
+ packageName,
1725
+ source,
1726
+ status: "ignored",
1727
+ reason: "adapter file does not exist"
1728
+ };
1729
+ }
1730
+ try {
1731
+ const manifestModule = await importModule(manifestPath);
1732
+ const manifest = parseManifest(manifestModule.manifest);
1733
+ if (!manifest) {
1734
+ return {
1735
+ packageName,
1736
+ source,
1737
+ status: "ignored",
1738
+ reason: "manifest export is invalid"
1739
+ };
1740
+ }
1741
+ return {
1742
+ packageName,
1743
+ source,
1744
+ status: "loaded",
1745
+ manifest,
1746
+ packageRoot,
1747
+ adapterPath
1748
+ };
1749
+ } catch (error) {
1750
+ return {
1751
+ packageName,
1752
+ source,
1753
+ status: "ignored",
1754
+ reason: `manifest import failed: ${formatImportError(error)}`
1755
+ };
1756
+ }
1757
+ }
1758
+ async function discoverLiveEnginesDetailed(options) {
1759
+ const projectRoot = findNearestPackageRoot(options?.cwd ?? process.cwd());
1760
+ const dependencyPackages = getDependencyEnginePackageNames(projectRoot);
1761
+ const seenPackages = /* @__PURE__ */ new Set();
1762
+ const results = [];
1763
+ const loadedIds = /* @__PURE__ */ new Set();
1764
+ const candidates = [
1765
+ ...BUILTIN_LIVE_ENGINE_PACKAGES.map((packageName) => ({
1766
+ packageName,
1767
+ source: "built-in"
1768
+ })),
1769
+ ...dependencyPackages.map((packageName) => ({ packageName, source: "dependency" }))
1770
+ ];
1771
+ for (const candidate of candidates) {
1772
+ if (seenPackages.has(candidate.packageName)) {
1773
+ continue;
1774
+ }
1775
+ seenPackages.add(candidate.packageName);
1776
+ const result = await resolveCandidate(candidate.packageName, candidate.source, projectRoot);
1777
+ if (result.status === "loaded" && result.manifest) {
1778
+ if (loadedIds.has(result.manifest.id)) {
1779
+ results.push({
1780
+ packageName: result.packageName,
1781
+ source: result.source,
1782
+ status: "ignored",
1783
+ reason: `engine id \`${result.manifest.id}\` collides with an already loaded adapter`,
1784
+ manifest: result.manifest
1785
+ });
1786
+ continue;
1787
+ }
1788
+ loadedIds.add(result.manifest.id);
1789
+ }
1790
+ results.push(result);
1791
+ }
1792
+ return results;
1793
+ }
1794
+ async function discoverLiveEngines(options) {
1795
+ const results = await discoverLiveEnginesDetailed(options);
1796
+ return results.map(({ packageName, source, status, reason, manifest }) => ({
1797
+ packageName,
1798
+ source,
1799
+ status,
1800
+ ...reason ? { reason } : {},
1801
+ ...manifest ? { manifest } : {}
1802
+ }));
1803
+ }
1804
+ async function loadLiveAdapter(engineId, options) {
1805
+ const results = await discoverLiveEnginesDetailed(options);
1806
+ const match = results.find(
1807
+ (result) => result.status === "loaded" && result.manifest?.id === engineId
1808
+ );
1809
+ if (!match?.adapterPath || !match.manifest) {
1810
+ return null;
1811
+ }
1812
+ try {
1813
+ const adapterModule = await importModule(match.adapterPath);
1814
+ return parseLiveAdapter(adapterModule.adapter, match.manifest);
1815
+ } catch {
1816
+ return null;
1817
+ }
1818
+ }
1819
+
1820
+ // src/commands/engines.ts
1821
+ async function enginesCommand(options = {}) {
1822
+ const engines = await discoverLiveEngines();
1823
+ if (options.json) {
1824
+ console.log(JSON.stringify(engines, null, 2));
1825
+ return;
1826
+ }
1827
+ if (engines.length === 0) {
1828
+ console.log("No Holdpoint live adapters were discovered.");
1829
+ return;
1830
+ }
1831
+ for (const engine of engines) {
1832
+ if (engine.status === "loaded" && engine.manifest) {
1833
+ console.log(
1834
+ `${chalk9.green("loaded")} ${chalk9.cyan(engine.manifest.id)} (${engine.manifest.displayName}) from ${chalk9.yellow(engine.packageName)} [${engine.source}]`
1835
+ );
1836
+ continue;
1837
+ }
1838
+ console.log(
1839
+ `${chalk9.yellow("ignored")} ${chalk9.yellow(engine.packageName)} [${engine.source}] \u2014 ${engine.reason ?? "unknown reason"}`
1840
+ );
1841
+ }
1842
+ }
1843
+
1844
+ // src/commands/event.ts
1845
+ import { readFileSync as readFileSync9 } from "fs";
1846
+ import { parseEventV1 as parseEventV12, parseEventsBatch } from "@holdpoint/live-protocol";
1847
+ import { BridgeClient as BridgeClient2 } from "@holdpoint/sdk";
1848
+ function readStdin() {
1849
+ return readFileSync9(0, "utf8");
1850
+ }
1851
+ async function eventCommand(options) {
1852
+ const stdin = readStdin().trim();
1853
+ if (!stdin) {
1854
+ if (options.fromHook) {
1855
+ process.exit(0);
1856
+ }
1857
+ console.error("No JSON input received on stdin.");
1858
+ process.exit(3);
1859
+ }
1860
+ let raw;
1861
+ try {
1862
+ raw = JSON.parse(stdin);
1863
+ } catch {
1864
+ if (options.fromHook) {
1865
+ process.exit(0);
1866
+ }
1867
+ console.error("Invalid JSON input.");
1868
+ process.exit(3);
1869
+ }
1870
+ const client = new BridgeClient2();
1871
+ try {
1872
+ if (options.fromHook) {
1873
+ if (!options.engine) {
1874
+ process.exit(0);
1875
+ }
1876
+ const adapter = await loadLiveAdapter(options.engine);
1877
+ if (!adapter) {
1878
+ process.exit(0);
1879
+ }
1880
+ const event = adapter.translateHookInput(raw, { cwd: process.cwd() });
1881
+ if (!event) {
1882
+ process.exit(0);
1883
+ }
1884
+ await client.sendEvent(event);
1885
+ process.exit(0);
1886
+ }
1887
+ if (Array.isArray(raw)) {
1888
+ await client.sendEvents(parseEventsBatch(raw));
1889
+ } else {
1890
+ await client.sendEvent(parseEventV12(raw));
1891
+ }
1892
+ } catch (error) {
1893
+ console.error(error.message);
1894
+ process.exit(3);
1895
+ }
1896
+ }
1897
+
1160
1898
  // src/index.ts
1161
1899
  var program = new Command();
1162
- program.name("holdpoint").description("Universal eval-guard for AI coding agents (alpha)").version("0.1.0-alpha.2");
1900
+ program.name("holdpoint").description("Universal eval-guard for AI coding agents (alpha)").version(CLI_VERSION).action(() => {
1901
+ void liveCommand();
1902
+ });
1163
1903
  program.command("init").description("Initialise Holdpoint in the current project").option("--stack <stack>", "Stack type: typescript | python | nextjs | fullstack").option(
1164
1904
  "--agent <agent>",
1165
1905
  "Agent to install for: copilot | claude | cursor | codex (default: all four)"
@@ -1168,6 +1908,14 @@ program.command("check").description("Run task checks from checks.yaml").option(
1168
1908
  program.command("validate").description("Validate checks.yaml schema and print any errors").action(validateCommand);
1169
1909
  program.command("update").description("Regenerate engine files from current checks.yaml (preserves checks.yaml)").action(updateCommand);
1170
1910
  program.command("builder").description("Open the visual builder UI on localhost:4321").action(buildCommand);
1911
+ program.command("live").description("Open the Holdpoint Live UI").option("--project <project>", "Open the UI focused on a specific project hash").action(liveCommand);
1912
+ var daemon = program.command("daemon").description("Manage the Holdpoint Live daemon");
1913
+ daemon.command("start").description("Start or connect to the singleton Holdpoint Live daemon").action(daemonStartCommand);
1914
+ daemon.command("status").description("Show Holdpoint Live daemon status").action(daemonStatusCommand);
1915
+ daemon.command("stop").description("Stop the running Holdpoint Live daemon").action(daemonStopCommand);
1916
+ program.command("event").description("Internal: read event JSON from stdin and publish it to Holdpoint Live").option("--engine <engine>", "Engine name when converting native hook payloads").option("--from-hook", "Interpret stdin as an engine-native hook payload").action(eventCommand);
1917
+ program.command("engines").description("List discovered Holdpoint Live adapter packages").option("--json", "Print machine-readable discovery output").action(enginesCommand);
1918
+ program.command("daemon-serve").description("Internal: run the Holdpoint Live daemon in the foreground").option("--port <port>", "Fixed port for the daemon process").action(daemonServeCommand);
1171
1919
  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);
1172
1920
  program.parse();
1173
1921
  //# sourceMappingURL=index.js.map